xref: /openbmc/service-config-manager/src/main.cpp (revision 67e4186a296775d592884584863050323e118a9b)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #include "srvcfg_manager.hpp"
17 
18 #include <boost/algorithm/string/replace.hpp>
19 #include <cereal/archives/json.hpp>
20 #include <cereal/types/tuple.hpp>
21 #include <cereal/types/unordered_map.hpp>
22 #include <sdbusplus/bus/match.hpp>
23 
24 #include <filesystem>
25 #include <fstream>
26 #include <unordered_map>
27 
28 std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
29 std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
30 std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
31     srvMgrObjects;
32 static bool unitQueryStarted = false;
33 
34 static constexpr const char* srvCfgMgrFileOld = "/etc/srvcfg-mgr.json";
35 static constexpr const char* srvCfgMgrFile = "srvcfg-mgr.json";
36 static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
37 
38 // Base service name list. All instance of these services and
39 // units(service/socket) will be managed by this daemon.
40 static std::unordered_map<std::string /* unitName */,
41                           bool /* isSocketActivated */>
42     managedServices = {{"phosphor-ipmi-net", false}, {"bmcweb", false},
43                        {"phosphor-ipmi-kcs", false}, {"obmc-ikvm", false},
44                        {"obmc-console", false},      {"dropbear", true},
45                        {"obmc-console-ssh", true},   {"ssifbridge", false}};
46 
47 enum class UnitType
48 {
49     service,
50     socket,
51     target,
52     device,
53     invalid
54 };
55 
56 using MonitorListMap =
57     std::unordered_map<std::string, std::tuple<std::string, std::string,
58                                                std::string, std::string>>;
59 MonitorListMap unitsToMonitor;
60 
61 enum class monitorElement
62 {
63     unitName,
64     instanceName,
65     serviceObjPath,
66     socketObjPath
67 };
68 
getUnitNameTypeAndInstance(const std::string & fullUnitName)69 std::tuple<std::string, UnitType, std::string> getUnitNameTypeAndInstance(
70     const std::string& fullUnitName)
71 {
72     UnitType type = UnitType::invalid;
73     std::string instanceName;
74     std::string unitName;
75     // get service type
76     auto typePos = fullUnitName.rfind(".");
77     if (typePos != std::string::npos)
78     {
79         const auto& typeStr = fullUnitName.substr(typePos + 1);
80         // Ignore types other than service and socket
81         if (typeStr == "service")
82         {
83             type = UnitType::service;
84         }
85         else if (typeStr == "socket")
86         {
87             type = UnitType::socket;
88         }
89         // get instance name if available
90         auto instancePos = fullUnitName.rfind("@");
91         if (instancePos != std::string::npos)
92         {
93             instanceName =
94                 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
95             unitName = fullUnitName.substr(0, instancePos);
96         }
97         else
98         {
99             unitName = fullUnitName.substr(0, typePos);
100         }
101     }
102     return std::make_tuple(unitName, type, instanceName);
103 }
104 
handleListUnitsResponse(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn,boost::system::error_code,const std::vector<ListUnitsType> & listUnits)105 static inline void handleListUnitsResponse(
106     sdbusplus::asio::object_server& server,
107     std::shared_ptr<sdbusplus::asio::connection>& conn,
108     boost::system::error_code /*ec*/,
109     const std::vector<ListUnitsType>& listUnits)
110 {
111     // Loop through all units, and mark all units, which has to be
112     // managed, irrespective of instance name.
113     for (const auto& unit : listUnits)
114     {
115         // Ignore non-existent units
116         if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
117             loadStateNotFound)
118         {
119             continue;
120         }
121 
122         const auto& fullUnitName =
123             std::get<static_cast<int>(ListUnitElements::name)>(unit);
124         auto [unitName, type,
125               instanceName] = getUnitNameTypeAndInstance(fullUnitName);
126         if (managedServices.count(unitName))
127         {
128             // For socket-activated units, ignore all its instances
129             if (managedServices.at(unitName) == true && !instanceName.empty())
130             {
131                 continue;
132             }
133 
134             std::string instantiatedUnitName =
135                 unitName + addInstanceName(instanceName, "@");
136             const sdbusplus::message::object_path& objectPath =
137                 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
138             // Group the service & socket units together.. Same services
139             // are managed together.
140             auto it = unitsToMonitor.find(instantiatedUnitName);
141             if (it != unitsToMonitor.end())
142             {
143                 auto& value = it->second;
144                 if (type == UnitType::service)
145                 {
146                     std::get<static_cast<int>(monitorElement::serviceObjPath)>(
147                         value) = objectPath.str;
148                 }
149                 else if (type == UnitType::socket)
150                 {
151                     std::get<static_cast<int>(monitorElement::socketObjPath)>(
152                         value) = objectPath.str;
153                 }
154                 continue;
155             }
156             // If not grouped with any existing entry, create a new one
157             if (type == UnitType::service)
158             {
159                 unitsToMonitor.emplace(instantiatedUnitName,
160                                        std::make_tuple(unitName, instanceName,
161                                                        objectPath.str, ""));
162             }
163             else if (type == UnitType::socket)
164             {
165                 unitsToMonitor.emplace(instantiatedUnitName,
166                                        std::make_tuple(unitName, instanceName,
167                                                        "", objectPath.str));
168             }
169         }
170     }
171 
172     bool updateRequired = false;
173 
174     // Determine if we need to create our persistent config dir
175     if (!std::filesystem::exists(srvDataBaseDir))
176     {
177         std::filesystem::create_directories(srvDataBaseDir);
178     }
179 
180     std::string srvCfgMgrFilePath = std::string(srvDataBaseDir) + srvCfgMgrFile;
181 
182     // First check if our config manager file is in the old spot.
183     // If it is, then move it to the new spot
184     if ((std::filesystem::exists(srvCfgMgrFileOld)) &&
185         (!std::filesystem::exists(srvCfgMgrFilePath)))
186     {
187         lg2::info("Moving {OLDFILEPATH} to new location, {FILEPATH}",
188                   "OLDFILEPATH", srvCfgMgrFileOld, "FILEPATH",
189                   srvCfgMgrFilePath);
190         std::filesystem::rename(srvCfgMgrFileOld, srvCfgMgrFilePath);
191     }
192 
193     bool jsonExist = std::filesystem::exists(srvCfgMgrFilePath);
194     if (jsonExist)
195     {
196         try
197         {
198             std::ifstream file(srvCfgMgrFilePath);
199             cereal::JSONInputArchive archive(file);
200             MonitorListMap savedMonitorList;
201             archive(savedMonitorList);
202 
203             // compare the unit list read from systemd1 and the save list.
204             MonitorListMap diffMap;
205             std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
206                                 begin(savedMonitorList), end(savedMonitorList),
207                                 std::inserter(diffMap, begin(diffMap)));
208             for (auto& unitIt : diffMap)
209             {
210                 auto it = savedMonitorList.find(unitIt.first);
211                 if (it == savedMonitorList.end())
212                 {
213                     savedMonitorList.insert(unitIt);
214                     updateRequired = true;
215                 }
216             }
217             unitsToMonitor = savedMonitorList;
218         }
219         catch (const std::exception& e)
220         {
221             lg2::error(
222                 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
223                 "FILEPATH", srvCfgMgrFilePath, "ERROR", e);
224 
225             // The "bad" files need to be moved to /tmp/ so that we can try to
226             // find out the cause of the file corruption. If we encounter this
227             // failure multiple times, we will only overwrite it to ensure that
228             // we don't accidentally fill up /tmp/.
229             std::error_code ec;
230             std::filesystem::copy_file(
231                 srvCfgMgrFilePath, tmpFileBad,
232                 std::filesystem::copy_options::overwrite_existing, ec);
233             if (ec)
234             {
235                 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
236                            "SRCFILE", srvCfgMgrFilePath, "DSTFILE", tmpFileBad);
237             }
238 
239             updateRequired = true;
240         }
241     }
242     if (!jsonExist || updateRequired)
243     {
244         std::ofstream file(srvCfgMgrFilePath);
245         cereal::JSONOutputArchive archive(file);
246         archive(CEREAL_NVP(unitsToMonitor));
247     }
248 
249 #ifdef USB_CODE_UPDATE
250     unitsToMonitor.emplace(
251         "phosphor-usb-code-update",
252         std::make_tuple(
253             phosphor::service::usbCodeUpdateUnitName, "",
254             "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
255             ""));
256 #endif
257 
258     // create objects for needed services
259     for (auto& it : unitsToMonitor)
260     {
261         sdbusplus::message::object_path basePath(
262             phosphor::service::srcCfgMgrBasePath);
263         std::string objPath(basePath / it.first);
264         auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
265             server, conn, objPath,
266             std::get<static_cast<int>(monitorElement::unitName)>(it.second),
267             std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
268             std::get<static_cast<int>(monitorElement::serviceObjPath)>(
269                 it.second),
270             std::get<static_cast<int>(monitorElement::socketObjPath)>(
271                 it.second));
272         srvMgrObjects.emplace(
273             std::make_pair(std::move(objPath), std::move(srvCfgObj)));
274     }
275 }
276 
init(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn)277 void init(sdbusplus::asio::object_server& server,
278           std::shared_ptr<sdbusplus::asio::connection>& conn)
279 {
280     // Go through all systemd units, and dynamically detect and manage
281     // the service daemons
282     conn->async_method_call(
283         [&server, &conn](boost::system::error_code ec,
284                          const std::vector<ListUnitsType>& listUnits) {
285             if (ec)
286             {
287                 lg2::error("async_method_call error: ListUnits failed: {EC}",
288                            "EC", ec.value());
289                 return;
290             }
291             handleListUnitsResponse(server, conn, ec, listUnits);
292         },
293         sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
294 }
295 
checkAndInit(sdbusplus::asio::object_server & server,std::shared_ptr<sdbusplus::asio::connection> & conn)296 void checkAndInit(sdbusplus::asio::object_server& server,
297                   std::shared_ptr<sdbusplus::asio::connection>& conn)
298 {
299     // Check whether systemd completed all the loading before initializing
300     conn->async_method_call(
301         [&server, &conn](boost::system::error_code ec,
302                          const std::variant<uint64_t>& value) {
303             if (ec)
304             {
305                 lg2::error("async_method_call error: ListUnits failed: {EC}",
306                            "EC", ec.value());
307                 return;
308             }
309             if (std::get<uint64_t>(value))
310             {
311                 if (!unitQueryStarted)
312                 {
313                     unitQueryStarted = true;
314                     init(server, conn);
315                 }
316             }
317             else
318             {
319                 // FIX-ME: Latest up-stream sync caused issue in receiving
320                 // StartupFinished signal. Unable to get StartupFinished signal
321                 // from systemd1 hence using poll method too, to trigger it
322                 // properly.
323                 constexpr size_t pollTimeout = 10; // seconds
324                 initTimer->expires_after(std::chrono::seconds(pollTimeout));
325                 initTimer->async_wait([&server, &conn](
326                                           const boost::system::error_code& ec) {
327                     if (ec == boost::asio::error::operation_aborted)
328                     {
329                         // Timer reset.
330                         return;
331                     }
332                     if (ec)
333                     {
334                         lg2::error(
335                             "service config mgr - init - async wait error: {EC}",
336                             "EC", ec.value());
337                         return;
338                     }
339                     checkAndInit(server, conn);
340                 });
341             }
342         },
343         sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
344         "FinishTimestamp");
345 }
346 
main()347 int main()
348 {
349     boost::asio::io_context io;
350     auto conn = std::make_shared<sdbusplus::asio::connection>(io);
351     timer = std::make_unique<boost::asio::steady_timer>(io);
352     initTimer = std::make_unique<boost::asio::steady_timer>(io);
353     conn->request_name(phosphor::service::serviceConfigSrvName);
354     auto server = sdbusplus::asio::object_server(conn, true);
355     server.add_manager(phosphor::service::srcCfgMgrBasePath);
356     // Initialize the objects after systemd indicated startup finished.
357     auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
358         static_cast<sdbusplus::bus_t&>(*conn),
359         "type='signal',"
360         "member='StartupFinished',path='/org/freedesktop/systemd1',"
361         "interface='org.freedesktop.systemd1.Manager'",
362         [&server, &conn](sdbusplus::message_t& /*msg*/) {
363             if (!unitQueryStarted)
364             {
365                 unitQueryStarted = true;
366                 init(server, conn);
367             }
368         });
369     // this will make sure to initialize the objects, when daemon is
370     // restarted.
371     checkAndInit(server, conn);
372 
373     io.run();
374 
375     return 0;
376 }
377