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