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