xref: /openbmc/service-config-manager/src/srvcfg_manager.cpp (revision a19b509352cc7d9fcacf345f81163bf6998ec7b4)
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/asio/spawn.hpp>
19 
20 #include <fstream>
21 #include <regex>
22 
23 extern std::unique_ptr<boost::asio::steady_timer> timer;
24 extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
25     srvMgrObjects;
26 static bool updateInProgress = false;
27 
28 namespace phosphor
29 {
30 namespace service
31 {
32 
33 static constexpr const char* overrideConfFileName = "override.conf";
34 static constexpr const size_t restartTimeout = 15; // seconds
35 
36 static constexpr const char* systemd1UnitBasePath =
37     "/org/freedesktop/systemd1/unit/";
38 static constexpr const char* systemdOverrideUnitBasePath =
39     "/etc/systemd/system/";
40 
41 void ServiceConfig::updateSocketProperties(
42     const boost::container::flat_map<std::string, VariantType>& propertyMap)
43 {
44     auto listenIt = propertyMap.find("Listen");
45     if (listenIt != propertyMap.end())
46     {
47         auto listenVal =
48             std::get<std::vector<std::tuple<std::string, std::string>>>(
49                 listenIt->second);
50         if (listenVal.size())
51         {
52             protocol = std::get<0>(listenVal[0]);
53             std::string port = std::get<1>(listenVal[0]);
54             auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1),
55                                   nullptr, 10);
56             if (tmp > std::numeric_limits<uint16_t>::max())
57             {
58                 throw std::out_of_range("Out of range");
59             }
60             portNum = tmp;
61             if (sockAttrIface && sockAttrIface->is_initialized())
62             {
63                 internalSet = true;
64                 sockAttrIface->set_property(sockAttrPropPort, portNum);
65                 internalSet = false;
66             }
67         }
68     }
69 }
70 
71 void ServiceConfig::updateServiceProperties(
72     const boost::container::flat_map<std::string, VariantType>& propertyMap)
73 {
74     auto stateIt = propertyMap.find("UnitFileState");
75     if (stateIt != propertyMap.end())
76     {
77         stateValue = std::get<std::string>(stateIt->second);
78         unitEnabledState = unitMaskedState = false;
79         if (stateValue == stateMasked)
80         {
81             unitMaskedState = true;
82         }
83         else if (stateValue == stateEnabled)
84         {
85             unitEnabledState = true;
86         }
87         if (srvCfgIface && srvCfgIface->is_initialized())
88         {
89             internalSet = true;
90             srvCfgIface->set_property(srvCfgPropMasked, unitMaskedState);
91             srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
92             internalSet = false;
93         }
94     }
95     auto subStateIt = propertyMap.find("SubState");
96     if (subStateIt != propertyMap.end())
97     {
98         subStateValue = std::get<std::string>(subStateIt->second);
99         if (subStateValue == subStateRunning ||
100             subStateValue == subStateListening)
101         {
102             unitRunningState = true;
103         }
104         if (srvCfgIface && srvCfgIface->is_initialized())
105         {
106             internalSet = true;
107             srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
108             internalSet = false;
109         }
110     }
111 }
112 
113 void ServiceConfig::queryAndUpdateProperties()
114 {
115     std::string objectPath =
116         isDropBearService ? socketObjectPath : serviceObjectPath;
117     if (objectPath.empty())
118     {
119         return;
120     }
121 
122     conn->async_method_call(
123         [this](boost::system::error_code ec,
124                const boost::container::flat_map<std::string, VariantType>&
125                    propertyMap) {
126             if (ec)
127             {
128                 phosphor::logging::log<phosphor::logging::level::ERR>(
129                     "async_method_call error: Failed to service unit "
130                     "properties");
131                 return;
132             }
133             try
134             {
135                 updateServiceProperties(propertyMap);
136                 if (!socketObjectPath.empty())
137                 {
138                     conn->async_method_call(
139                         [this](boost::system::error_code ec,
140                                const boost::container::flat_map<
141                                    std::string, VariantType>& propertyMap) {
142                             if (ec)
143                             {
144                                 phosphor::logging::log<
145                                     phosphor::logging::level::ERR>(
146                                     "async_method_call error: Failed to get "
147                                     "all property");
148                                 return;
149                             }
150                             try
151                             {
152                                 updateSocketProperties(propertyMap);
153                                 if (!srvCfgIface)
154                                 {
155                                     registerProperties();
156                                 }
157                             }
158                             catch (const std::exception& e)
159                             {
160                                 phosphor::logging::log<
161                                     phosphor::logging::level::ERR>(
162                                     "Exception in getting socket properties",
163                                     phosphor::logging::entry("WHAT=%s",
164                                                              e.what()));
165                                 return;
166                             }
167                         },
168                         sysdService, socketObjectPath, dBusPropIntf,
169                         dBusGetAllMethod, sysdSocketIntf);
170                 }
171                 else if (!srvCfgIface)
172                 {
173                     registerProperties();
174                 }
175             }
176             catch (const std::exception& e)
177             {
178                 phosphor::logging::log<phosphor::logging::level::ERR>(
179                     "Exception in getting socket properties",
180                     phosphor::logging::entry("WHAT=%s", e.what()));
181                 return;
182             }
183         },
184         sysdService, objectPath, dBusPropIntf, dBusGetAllMethod, sysdUnitIntf);
185     return;
186 }
187 
188 void ServiceConfig::createSocketOverrideConf()
189 {
190     if (!socketObjectPath.empty())
191     {
192         std::string socketUnitName(instantiatedUnitName + ".socket");
193         /// Check override socket directory exist, if not create it.
194         std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath);
195         ovrUnitFileDir += socketUnitName;
196         ovrUnitFileDir += ".d";
197         if (!std::filesystem::exists(ovrUnitFileDir))
198         {
199             if (!std::filesystem::create_directories(ovrUnitFileDir))
200             {
201                 phosphor::logging::log<phosphor::logging::level::ERR>(
202                     "Unable to create the directory.",
203                     phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str()));
204                 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
205                                             Common::Error::InternalFailure>();
206             }
207         }
208         overrideConfDir = std::string(ovrUnitFileDir);
209     }
210 }
211 
212 ServiceConfig::ServiceConfig(
213     sdbusplus::asio::object_server& srv_,
214     std::shared_ptr<sdbusplus::asio::connection>& conn_,
215     const std::string& objPath_, const std::string& baseUnitName_,
216     const std::string& instanceName_, const std::string& serviceObjPath_,
217     const std::string& socketObjPath_) :
218     conn(conn_),
219     server(srv_), objPath(objPath_), baseUnitName(baseUnitName_),
220     instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
221     socketObjectPath(socketObjPath_)
222 {
223     if (baseUnitName == "dropbear")
224     {
225         isDropBearService = true;
226     }
227     instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
228     updatedFlag = 0;
229     queryAndUpdateProperties();
230     return;
231 }
232 
233 std::string ServiceConfig::getSocketUnitName()
234 {
235     return instantiatedUnitName + ".socket";
236 }
237 
238 std::string ServiceConfig::getServiceUnitName()
239 {
240     return instantiatedUnitName + ".service";
241 }
242 
243 bool ServiceConfig::isMaskedOut()
244 {
245     // return true  if state is masked & no request to update the maskedState
246     return (
247         stateValue == "masked" &&
248         !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
249 }
250 
251 void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
252 {
253     if (!updatedFlag || isMaskedOut())
254     {
255         // No updates / masked - Just return.
256         return;
257     }
258     phosphor::logging::log<phosphor::logging::level::INFO>(
259         "Applying new settings.",
260         phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
261     if (subStateValue == subStateRunning || subStateValue == subStateListening)
262     {
263         if (!socketObjectPath.empty())
264         {
265             systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
266         }
267         if (!isDropBearService)
268         {
269             systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
270         }
271         else
272         {
273             // Get the ListUnits property, find all the services of
274             // `dropbear@<ip><port>.service` and stop the service through
275             // the systemdUnitAction method
276             boost::system::error_code ec;
277             auto listUnits =
278                 conn->yield_method_call<std::vector<ListUnitsType>>(
279                     yield, ec, sysdService, sysdObjPath, sysdMgrIntf,
280                     "ListUnits");
281 
282             checkAndThrowInternalFailure(
283                 ec, "yield_method_call error: ListUnits failed");
284 
285             for (const auto& unit : listUnits)
286             {
287                 const auto& service =
288                     std::get<static_cast<int>(ListUnitElements::name)>(unit);
289                 const auto& status =
290                     std::get<static_cast<int>(ListUnitElements::subState)>(
291                         unit);
292                 if (service.find("dropbear@") != std::string::npos &&
293                     service.find(".service") != std::string::npos &&
294                     status == subStateRunning)
295                 {
296                     systemdUnitAction(conn, yield, service, sysdStopUnit);
297                 }
298             }
299         }
300     }
301 
302     if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
303     {
304         createSocketOverrideConf();
305         // Create override config file and write data.
306         std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
307         std::string tmpFile{ovrCfgFile + "_tmp"};
308         std::ofstream cfgFile(tmpFile, std::ios::out);
309         if (!cfgFile.good())
310         {
311             phosphor::logging::log<phosphor::logging::level::ERR>(
312                 "Failed to open override.conf_tmp file");
313             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
314                                         Error::InternalFailure>();
315         }
316 
317         // Write the socket header
318         cfgFile << "[Socket]\n";
319         // Listen
320         cfgFile << "Listen" << protocol << "="
321                 << "\n";
322         cfgFile << "Listen" << protocol << "=" << portNum << "\n";
323         cfgFile.close();
324 
325         if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
326         {
327             phosphor::logging::log<phosphor::logging::level::ERR>(
328                 "Failed to rename tmp file as override.conf");
329             std::remove(tmpFile.c_str());
330             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
331                                         Error::InternalFailure>();
332         }
333     }
334 
335     if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
336                        (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
337     {
338         std::vector<std::string> unitFiles;
339         if (socketObjectPath.empty())
340         {
341             unitFiles = {getServiceUnitName()};
342         }
343         else if (!socketObjectPath.empty() && isDropBearService)
344         {
345             unitFiles = {getSocketUnitName()};
346         }
347         else
348         {
349             unitFiles = {getSocketUnitName(), getServiceUnitName()};
350         }
351         systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
352                                     unitMaskedState, unitEnabledState);
353     }
354     return;
355 }
356 void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
357 {
358     if (!updatedFlag || isMaskedOut())
359     {
360         // No updates. Just return.
361         return;
362     }
363 
364     if (unitRunningState)
365     {
366         if (!socketObjectPath.empty())
367         {
368             systemdUnitAction(conn, yield, getSocketUnitName(),
369                               sysdRestartUnit);
370         }
371         if (!isDropBearService)
372         {
373             systemdUnitAction(conn, yield, getServiceUnitName(),
374                               sysdRestartUnit);
375         }
376     }
377 
378     // Reset the flag
379     updatedFlag = 0;
380 
381     phosphor::logging::log<phosphor::logging::level::INFO>(
382         "Applied new settings",
383         phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
384 
385     queryAndUpdateProperties();
386     return;
387 }
388 
389 void ServiceConfig::startServiceRestartTimer()
390 {
391     timer->expires_after(std::chrono::seconds(restartTimeout));
392     timer->async_wait([this](const boost::system::error_code& ec) {
393         if (ec == boost::asio::error::operation_aborted)
394         {
395             // Timer reset.
396             return;
397         }
398         else if (ec)
399         {
400             phosphor::logging::log<phosphor::logging::level::ERR>(
401                 "async wait error.");
402             return;
403         }
404         updateInProgress = true;
405         boost::asio::spawn(conn->get_io_context(),
406                            [this](boost::asio::yield_context yield) {
407                                // Stop and apply configuration for all objects
408                                for (auto& srvMgrObj : srvMgrObjects)
409                                {
410                                    auto& srvObj = srvMgrObj.second;
411                                    if (srvObj->updatedFlag)
412                                    {
413                                        srvObj->stopAndApplyUnitConfig(yield);
414                                    }
415                                }
416                                // Do system reload
417                                systemdDaemonReload(conn, yield);
418                                // restart unit config.
419                                for (auto& srvMgrObj : srvMgrObjects)
420                                {
421                                    auto& srvObj = srvMgrObj.second;
422                                    if (srvObj->updatedFlag)
423                                    {
424                                        srvObj->restartUnitConfig(yield);
425                                    }
426                                }
427                                updateInProgress = false;
428                            });
429     });
430 }
431 
432 void ServiceConfig::registerProperties()
433 {
434     srvCfgIface = server.add_interface(objPath, serviceConfigIntfName);
435 
436     if (!socketObjectPath.empty())
437     {
438         sockAttrIface = server.add_interface(objPath, sockAttrIntfName);
439         sockAttrIface->register_property(
440             sockAttrPropPort, portNum,
441             [this](const uint16_t& req, uint16_t& res) {
442                 if (!internalSet)
443                 {
444                     if (req == res)
445                     {
446                         return 1;
447                     }
448                     if (updateInProgress)
449                     {
450                         return 0;
451                     }
452                     portNum = req;
453                     updatedFlag |=
454                         (1 << static_cast<uint8_t>(UpdatedProp::port));
455                     startServiceRestartTimer();
456                 }
457                 res = req;
458                 return 1;
459             });
460     }
461 
462     srvCfgIface->register_property(
463         srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
464             if (!internalSet)
465             {
466                 if (req == res)
467                 {
468                     return 1;
469                 }
470                 if (updateInProgress)
471                 {
472                     return 0;
473                 }
474                 unitMaskedState = req;
475                 unitEnabledState = !unitMaskedState;
476                 unitRunningState = !unitMaskedState;
477                 updatedFlag |=
478                     (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
479                     (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
480                     (1 << static_cast<uint8_t>(UpdatedProp::runningState));
481                 internalSet = true;
482                 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
483                 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
484                 internalSet = false;
485                 startServiceRestartTimer();
486             }
487             res = req;
488             return 1;
489         });
490 
491     srvCfgIface->register_property(
492         srvCfgPropEnabled, unitEnabledState,
493         [this](const bool& req, bool& res) {
494             if (!internalSet)
495             {
496                 if (req == res)
497                 {
498                     return 1;
499                 }
500                 if (updateInProgress)
501                 {
502                     return 0;
503                 }
504                 if (unitMaskedState)
505                 { // block updating if masked
506                     phosphor::logging::log<phosphor::logging::level::ERR>(
507                         "Invalid value specified");
508                     return -EINVAL;
509                 }
510                 unitEnabledState = req;
511                 updatedFlag |=
512                     (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
513                 startServiceRestartTimer();
514             }
515             res = req;
516             return 1;
517         });
518 
519     srvCfgIface->register_property(
520         srvCfgPropRunning, unitRunningState,
521         [this](const bool& req, bool& res) {
522             if (!internalSet)
523             {
524                 if (req == res)
525                 {
526                     return 1;
527                 }
528                 if (updateInProgress)
529                 {
530                     return 0;
531                 }
532                 if (unitMaskedState)
533                 { // block updating if masked
534                     phosphor::logging::log<phosphor::logging::level::ERR>(
535                         "Invalid value specified");
536                     return -EINVAL;
537                 }
538                 unitRunningState = req;
539                 updatedFlag |=
540                     (1 << static_cast<uint8_t>(UpdatedProp::runningState));
541                 startServiceRestartTimer();
542             }
543             res = req;
544             return 1;
545         });
546 
547     srvCfgIface->initialize();
548     if (!socketObjectPath.empty())
549     {
550         sockAttrIface->initialize();
551     }
552     return;
553 }
554 
555 } // namespace service
556 } // namespace phosphor
557