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 
27 std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
28 std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
29 std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
30     srvMgrObjects;
31 static bool unitQueryStarted = false;
32 
33 static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
34 static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
35 
36 // Base service name list. All instance of these services and
37 // units(service/socket) will be managed by this daemon.
38 static std::array<std::string, 6> serviceNames = {
39     "phosphor-ipmi-net", "bmcweb",       "phosphor-ipmi-kcs",
40     "start-ipkvm",       "obmc-console", "dropbear"};
41 
42 enum class UnitType
43 {
44     service,
45     socket,
46     target,
47     device,
48     invalid
49 };
50 
51 using MonitorListMap =
52     std::unordered_map<std::string, std::tuple<std::string, std::string,
53                                                std::string, std::string>>;
54 MonitorListMap unitsToMonitor;
55 
56 enum class monitorElement
57 {
58     unitName,
59     instanceName,
60     serviceObjPath,
61     socketObjPath
62 };
63 
64 std::tuple<std::string, UnitType, std::string>
65     getUnitNameTypeAndInstance(const std::string& fullUnitName)
66 {
67     UnitType type = UnitType::invalid;
68     std::string instanceName;
69     std::string unitName;
70     // get service type
71     auto typePos = fullUnitName.rfind(".");
72     if (typePos != std::string::npos)
73     {
74         const auto& typeStr = fullUnitName.substr(typePos + 1);
75         // Ignore types other than service and socket
76         if (typeStr == "service")
77         {
78             type = UnitType::service;
79         }
80         else if (typeStr == "socket")
81         {
82             type = UnitType::socket;
83         }
84         // get instance name if available
85         auto instancePos = fullUnitName.rfind("@");
86         if (instancePos != std::string::npos)
87         {
88             instanceName =
89                 fullUnitName.substr(instancePos + 1, typePos - instancePos - 1);
90             unitName = fullUnitName.substr(0, instancePos);
91         }
92         else
93         {
94             unitName = fullUnitName.substr(0, typePos);
95         }
96     }
97     return std::make_tuple(unitName, type, instanceName);
98 }
99 
100 static inline void
101     handleListUnitsResponse(sdbusplus::asio::object_server& server,
102                             std::shared_ptr<sdbusplus::asio::connection>& conn,
103                             boost::system::error_code /*ec*/,
104                             const std::vector<ListUnitsType>& listUnits)
105 {
106     // Loop through all units, and mark all units, which has to be
107     // managed, irrespective of instance name.
108     for (const auto& unit : listUnits)
109     {
110         const auto& fullUnitName =
111             std::get<static_cast<int>(ListUnitElements::name)>(unit);
112         auto [unitName, type, instanceName] =
113             getUnitNameTypeAndInstance(fullUnitName);
114         if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=
115             serviceNames.end())
116         {
117             std::string instantiatedUnitName =
118                 unitName + addInstanceName(instanceName, "_40");
119             boost::replace_all(instantiatedUnitName, "-", "_2d");
120             const sdbusplus::message::object_path& objectPath =
121                 std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
122             // Group the service & socket units togther.. Same services
123             // are managed together.
124             auto it = unitsToMonitor.find(instantiatedUnitName);
125             if (it != unitsToMonitor.end())
126             {
127                 auto& value = it->second;
128                 if (type == UnitType::service)
129                 {
130                     std::get<static_cast<int>(monitorElement::unitName)>(
131                         value) = unitName;
132                     std::get<static_cast<int>(monitorElement::instanceName)>(
133                         value) = instanceName;
134                     std::get<static_cast<int>(monitorElement::serviceObjPath)>(
135                         value) = objectPath;
136                 }
137                 else if (type == UnitType::socket)
138                 {
139                     std::get<static_cast<int>(monitorElement::socketObjPath)>(
140                         value) = objectPath;
141                 }
142             }
143             if (type == UnitType::service)
144             {
145                 unitsToMonitor.emplace(instantiatedUnitName,
146                                        std::make_tuple(unitName, instanceName,
147                                                        objectPath.str, ""));
148             }
149             else if (type == UnitType::socket)
150             {
151                 unitsToMonitor.emplace(
152                     instantiatedUnitName,
153                     std::make_tuple("", "", "", objectPath.str));
154             }
155         }
156     }
157 
158     bool updateRequired = false;
159     bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
160     if (jsonExist)
161     {
162         try
163         {
164             std::ifstream file(srvCfgMgrFile);
165             cereal::JSONInputArchive archive(file);
166             MonitorListMap savedMonitorList;
167             archive(savedMonitorList);
168 
169             // compare the unit list read from systemd1 and the save list.
170             MonitorListMap diffMap;
171             std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
172                                 begin(savedMonitorList), end(savedMonitorList),
173                                 std::inserter(diffMap, begin(diffMap)));
174             for (auto& unitIt : diffMap)
175             {
176                 auto it = savedMonitorList.find(unitIt.first);
177                 if (it == savedMonitorList.end())
178                 {
179                     savedMonitorList.insert(unitIt);
180                     updateRequired = true;
181                 }
182             }
183             unitsToMonitor = savedMonitorList;
184         }
185         catch (const std::exception& e)
186         {
187             lg2::error(
188                 "Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
189                 "FILEPATH", srvCfgMgrFile, "ERROR", e);
190 
191             // The "bad" files need to be moved to /tmp/ so that we can try to
192             // find out the cause of the file corruption. If we encounter this
193             // failure multiple times, we will only overwrite it to ensure that
194             // we don't accidentally fill up /tmp/.
195             std::error_code ec;
196             std::filesystem::copy_file(
197                 srvCfgMgrFile, tmpFileBad,
198                 std::filesystem::copy_options::overwrite_existing, ec);
199             if (ec)
200             {
201                 lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
202                            "SRCFILE", srvCfgMgrFile, "DSTFILE", tmpFileBad);
203             }
204 
205             updateRequired = true;
206         }
207     }
208     if (!jsonExist || updateRequired)
209     {
210         std::ofstream file(srvCfgMgrFile);
211         cereal::JSONOutputArchive archive(file);
212         archive(CEREAL_NVP(unitsToMonitor));
213     }
214 
215 #ifdef USB_CODE_UPDATE
216     unitsToMonitor.emplace(
217         "phosphor_2dusb_2dcode_2dupdate",
218         std::make_tuple(
219             "phosphor_usb_code_update", "",
220             "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
221             ""));
222 #endif
223 
224     // create objects for needed services
225     for (auto& it : unitsToMonitor)
226     {
227         std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +
228                             "/" + it.first);
229         std::string instanciatedUnitName =
230             std::get<static_cast<int>(monitorElement::unitName)>(it.second) +
231             addInstanceName(
232                 std::get<static_cast<int>(monitorElement::instanceName)>(
233                     it.second),
234                 "@");
235         auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
236             server, conn, objPath,
237             std::get<static_cast<int>(monitorElement::unitName)>(it.second),
238             std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
239             std::get<static_cast<int>(monitorElement::serviceObjPath)>(
240                 it.second),
241             std::get<static_cast<int>(monitorElement::socketObjPath)>(
242                 it.second));
243         srvMgrObjects.emplace(
244             std::make_pair(std::move(objPath), std::move(srvCfgObj)));
245     }
246 }
247 
248 void init(sdbusplus::asio::object_server& server,
249           std::shared_ptr<sdbusplus::asio::connection>& conn)
250 {
251     // Go through all systemd units, and dynamically detect and manage
252     // the service daemons
253     conn->async_method_call(
254         [&server, &conn](boost::system::error_code ec,
255                          const std::vector<ListUnitsType>& listUnits) {
256             if (ec)
257             {
258                 lg2::error("async_method_call error: ListUnits failed: {EC}",
259                            "EC", ec.value());
260                 return;
261             }
262             handleListUnitsResponse(server, conn, ec, listUnits);
263         },
264         sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
265 }
266 
267 void checkAndInit(sdbusplus::asio::object_server& server,
268                   std::shared_ptr<sdbusplus::asio::connection>& conn)
269 {
270     // Check whether systemd completed all the loading before initializing
271     conn->async_method_call(
272         [&server, &conn](boost::system::error_code ec,
273                          const std::variant<uint64_t>& value) {
274             if (ec)
275             {
276                 lg2::error("async_method_call error: ListUnits failed: {EC}",
277                            "EC", ec.value());
278                 return;
279             }
280             if (std::get<uint64_t>(value))
281             {
282                 if (!unitQueryStarted)
283                 {
284                     unitQueryStarted = true;
285                     init(server, conn);
286                 }
287             }
288             else
289             {
290                 // FIX-ME: Latest up-stream sync caused issue in receiving
291                 // StartupFinished signal. Unable to get StartupFinished signal
292                 // from systemd1 hence using poll method too, to trigger it
293                 // properly.
294                 constexpr size_t pollTimeout = 10; // seconds
295                 initTimer->expires_after(std::chrono::seconds(pollTimeout));
296                 initTimer->async_wait([&server, &conn](
297                                           const boost::system::error_code& ec) {
298                     if (ec == boost::asio::error::operation_aborted)
299                     {
300                         // Timer reset.
301                         return;
302                     }
303                     if (ec)
304                     {
305                         lg2::error(
306                             "service config mgr - init - async wait error: {EC}",
307                             "EC", ec.value());
308                         return;
309                     }
310                     checkAndInit(server, conn);
311                 });
312             }
313         },
314         sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
315         "FinishTimestamp");
316 }
317 
318 int main()
319 {
320     boost::asio::io_service io;
321     auto conn = std::make_shared<sdbusplus::asio::connection>(io);
322     timer = std::make_unique<boost::asio::steady_timer>(io);
323     initTimer = std::make_unique<boost::asio::steady_timer>(io);
324     conn->request_name(phosphor::service::serviceConfigSrvName);
325     auto server = sdbusplus::asio::object_server(conn, true);
326     server.add_manager(phosphor::service::srcCfgMgrBasePath);
327     // Initialize the objects after systemd indicated startup finished.
328     auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(
329         static_cast<sdbusplus::bus::bus&>(*conn),
330         "type='signal',"
331         "member='StartupFinished',path='/org/freedesktop/systemd1',"
332         "interface='org.freedesktop.systemd1.Manager'",
333         [&server, &conn](sdbusplus::message::message& /*msg*/) {
334             if (!unitQueryStarted)
335             {
336                 unitQueryStarted = true;
337                 init(server, conn);
338             }
339         });
340     // this will make sure to initialize the objects, when daemon is
341     // restarted.
342     checkAndInit(server, conn);
343 
344     io.run();
345 
346     return 0;
347 }
348