xref: /openbmc/service-config-manager/src/srvcfg_manager.cpp (revision f27f431faa0c40c0253e50cddd92ffcb99081604)
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 #ifdef USB_CODE_UPDATE
21 #include <cereal/archives/json.hpp>
22 #include <cereal/types/tuple.hpp>
23 #include <cereal/types/unordered_map.hpp>
24 
25 #include <cstdio>
26 #endif
27 
28 #include <fstream>
29 #include <regex>
30 
31 extern std::unique_ptr<boost::asio::steady_timer> timer;
32 extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
33     srvMgrObjects;
34 static bool updateInProgress = false;
35 
36 namespace phosphor
37 {
38 namespace service
39 {
40 
41 static constexpr const char* overrideConfFileName = "override.conf";
42 static constexpr const size_t restartTimeout = 15; // seconds
43 
44 static constexpr const char* systemd1UnitBasePath =
45     "/org/freedesktop/systemd1/unit/";
46 static constexpr const char* systemdOverrideUnitBasePath =
47     "/etc/systemd/system/";
48 
49 #ifdef USB_CODE_UPDATE
50 static constexpr const char* usbCodeUpdateStateFilePath =
51     "/var/lib/srvcfg_manager";
52 static constexpr const char* usbCodeUpdateStateFile =
53     "/var/lib/srvcfg_manager/usb-code-update-state";
54 static constexpr const char* emptyUsbCodeUpdateRulesFile =
55     "/etc/udev/rules.d/70-bmc-usb.rules";
56 static constexpr const char* usbCodeUpdateObjectPath =
57     "/xyz/openbmc_project/control/service/phosphor_2dusb_2dcode_2dupdate";
58 
59 using UsbCodeUpdateStateMap = std::unordered_map<std::string, bool>;
60 
61 void ServiceConfig::setUSBCodeUpdateState(const bool& state)
62 {
63     // Enable usb code update
64     if (state)
65     {
66         if (std::filesystem::exists(emptyUsbCodeUpdateRulesFile))
67         {
68             lg2::info("Enable usb code update");
69             std::filesystem::remove(emptyUsbCodeUpdateRulesFile);
70         }
71         return;
72     }
73 
74     // Disable usb code update
75     if (std::filesystem::exists(emptyUsbCodeUpdateRulesFile))
76     {
77         std::filesystem::remove(emptyUsbCodeUpdateRulesFile);
78     }
79     std::error_code ec;
80     std::filesystem::create_symlink("/dev/null", emptyUsbCodeUpdateRulesFile,
81                                     ec);
82     if (ec)
83     {
84         lg2::error("Disable usb code update failed");
85         return;
86     }
87     lg2::info("Disable usb code update");
88 }
89 
90 void ServiceConfig::saveUSBCodeUpdateStateToFile(const bool& maskedState,
91                                                  const bool& enabledState)
92 {
93     if (!std::filesystem::exists(usbCodeUpdateStateFilePath))
94     {
95         std::filesystem::create_directories(usbCodeUpdateStateFilePath);
96     }
97 
98     UsbCodeUpdateStateMap usbCodeUpdateState;
99     usbCodeUpdateState[srvCfgPropMasked] = maskedState;
100     usbCodeUpdateState[srvCfgPropEnabled] = enabledState;
101 
102     std::ofstream file(usbCodeUpdateStateFile, std::ios::out);
103     cereal::JSONOutputArchive archive(file);
104     archive(CEREAL_NVP(usbCodeUpdateState));
105 }
106 
107 void ServiceConfig::getUSBCodeUpdateStateFromFile()
108 {
109     if (!std::filesystem::exists(usbCodeUpdateStateFile))
110     {
111         lg2::info("usb-code-update-state file does not exist");
112 
113         unitMaskedState = false;
114         unitEnabledState = true;
115         unitRunningState = true;
116         setUSBCodeUpdateState(unitEnabledState);
117         return;
118     }
119 
120     std::ifstream file(usbCodeUpdateStateFile);
121     cereal::JSONInputArchive archive(file);
122     UsbCodeUpdateStateMap usbCodeUpdateState;
123     archive(usbCodeUpdateState);
124 
125     auto iterMask = usbCodeUpdateState.find(srvCfgPropMasked);
126     if (iterMask != usbCodeUpdateState.end())
127     {
128         unitMaskedState = iterMask->second;
129         if (unitMaskedState)
130         {
131             unitEnabledState = !unitMaskedState;
132             unitRunningState = !unitMaskedState;
133             setUSBCodeUpdateState(unitEnabledState);
134             return;
135         }
136 
137         auto iterEnable = usbCodeUpdateState.find(srvCfgPropEnabled);
138         if (iterEnable != usbCodeUpdateState.end())
139         {
140             unitEnabledState = iterEnable->second;
141             unitRunningState = iterEnable->second;
142             setUSBCodeUpdateState(unitEnabledState);
143         }
144     }
145 }
146 #endif
147 
148 void ServiceConfig::updateSocketProperties(
149     const boost::container::flat_map<std::string, VariantType>& propertyMap)
150 {
151     auto listenIt = propertyMap.find("Listen");
152     if (listenIt != propertyMap.end())
153     {
154         auto listenVal =
155             std::get<std::vector<std::tuple<std::string, std::string>>>(
156                 listenIt->second);
157         if (listenVal.size())
158         {
159             protocol = std::get<0>(listenVal[0]);
160             std::string port = std::get<1>(listenVal[0]);
161             auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1),
162                                   nullptr, 10);
163             if (tmp > std::numeric_limits<uint16_t>::max())
164             {
165                 throw std::out_of_range("Out of range");
166             }
167             portNum = tmp;
168             if (sockAttrIface && sockAttrIface->is_initialized())
169             {
170                 internalSet = true;
171                 sockAttrIface->set_property(sockAttrPropPort, portNum);
172                 internalSet = false;
173             }
174         }
175     }
176 }
177 
178 void ServiceConfig::updateServiceProperties(
179     const boost::container::flat_map<std::string, VariantType>& propertyMap)
180 {
181     auto stateIt = propertyMap.find("UnitFileState");
182     if (stateIt != propertyMap.end())
183     {
184         stateValue = std::get<std::string>(stateIt->second);
185         unitEnabledState = unitMaskedState = false;
186         if (stateValue == stateMasked)
187         {
188             unitMaskedState = true;
189         }
190         else if (stateValue == stateEnabled)
191         {
192             unitEnabledState = true;
193         }
194         if (srvCfgIface && srvCfgIface->is_initialized())
195         {
196             internalSet = true;
197             srvCfgIface->set_property(srvCfgPropMasked, unitMaskedState);
198             srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
199             internalSet = false;
200         }
201     }
202     auto subStateIt = propertyMap.find("SubState");
203     if (subStateIt != propertyMap.end())
204     {
205         subStateValue = std::get<std::string>(subStateIt->second);
206         if (subStateValue == subStateRunning ||
207             subStateValue == subStateListening)
208         {
209             unitRunningState = true;
210         }
211         if (srvCfgIface && srvCfgIface->is_initialized())
212         {
213             internalSet = true;
214             srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
215             internalSet = false;
216         }
217     }
218 
219 #ifdef USB_CODE_UPDATE
220     if (objPath == usbCodeUpdateObjectPath)
221     {
222         getUSBCodeUpdateStateFromFile();
223     }
224 #endif
225 }
226 
227 void ServiceConfig::queryAndUpdateProperties()
228 {
229     std::string objectPath =
230         isSocketActivatedService ? socketObjectPath : serviceObjectPath;
231     if (objectPath.empty())
232     {
233         return;
234     }
235 
236     conn->async_method_call(
237         [this](boost::system::error_code ec,
238                const boost::container::flat_map<std::string, VariantType>&
239                    propertyMap) {
240             if (ec)
241             {
242                 lg2::error(
243                     "async_method_call error: Failed to service unit properties: {EC}",
244                     "EC", ec.value());
245                 return;
246             }
247             try
248             {
249                 updateServiceProperties(propertyMap);
250                 if (!socketObjectPath.empty())
251                 {
252                     conn->async_method_call(
253                         [this](boost::system::error_code ec,
254                                const boost::container::flat_map<
255                                    std::string, VariantType>& propertyMap) {
256                             if (ec)
257                             {
258                                 lg2::error(
259                                     "async_method_call error: Failed to get all property: {EC}",
260                                     "EC", ec.value());
261                                 return;
262                             }
263                             try
264                             {
265                                 updateSocketProperties(propertyMap);
266                                 if (!srvCfgIface)
267                                 {
268                                     registerProperties();
269                                 }
270                             }
271                             catch (const std::exception& e)
272                             {
273                                 lg2::error(
274                                     "Exception in getting socket properties: {ERROR}",
275                                     "ERROR", e);
276                                 return;
277                             }
278                         },
279                         sysdService, socketObjectPath, dBusPropIntf,
280                         dBusGetAllMethod, sysdSocketIntf);
281                 }
282                 else if (!srvCfgIface)
283                 {
284                     registerProperties();
285                 }
286             }
287             catch (const std::exception& e)
288             {
289                 lg2::error("Exception in getting socket properties: {ERROR}",
290                            "ERROR", e);
291                 return;
292             }
293         },
294         sysdService, objectPath, dBusPropIntf, dBusGetAllMethod, sysdUnitIntf);
295     return;
296 }
297 
298 void ServiceConfig::createSocketOverrideConf()
299 {
300     if (!socketObjectPath.empty())
301     {
302         std::string socketUnitName(instantiatedUnitName + ".socket");
303         /// Check override socket directory exist, if not create it.
304         std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath);
305         ovrUnitFileDir += socketUnitName;
306         ovrUnitFileDir += ".d";
307         if (!std::filesystem::exists(ovrUnitFileDir))
308         {
309             if (!std::filesystem::create_directories(ovrUnitFileDir))
310             {
311                 lg2::error("Unable to create the {DIR} directory.", "DIR",
312                            ovrUnitFileDir);
313                 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::
314                                             Common::Error::InternalFailure>();
315             }
316         }
317         overrideConfDir = std::string(ovrUnitFileDir);
318     }
319 }
320 
321 ServiceConfig::ServiceConfig(
322     sdbusplus::asio::object_server& srv_,
323     std::shared_ptr<sdbusplus::asio::connection>& conn_,
324     const std::string& objPath_, const std::string& baseUnitName_,
325     const std::string& instanceName_, const std::string& serviceObjPath_,
326     const std::string& socketObjPath_) :
327     conn(conn_),
328     server(srv_), objPath(objPath_), baseUnitName(baseUnitName_),
329     instanceName(instanceName_), serviceObjectPath(serviceObjPath_),
330     socketObjectPath(socketObjPath_)
331 {
332     isSocketActivatedService = serviceObjectPath.empty();
333     instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@");
334     updatedFlag = 0;
335     queryAndUpdateProperties();
336     return;
337 }
338 
339 std::string ServiceConfig::getSocketUnitName()
340 {
341     return instantiatedUnitName + ".socket";
342 }
343 
344 std::string ServiceConfig::getServiceUnitName()
345 {
346     return instantiatedUnitName + ".service";
347 }
348 
349 bool ServiceConfig::isMaskedOut()
350 {
351     // return true  if state is masked & no request to update the maskedState
352     return (
353         stateValue == "masked" &&
354         !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState))));
355 }
356 
357 void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield)
358 {
359     if (!updatedFlag || isMaskedOut())
360     {
361         // No updates / masked - Just return.
362         return;
363     }
364     lg2::info("Applying new settings: {OBJPATH}", "OBJPATH", objPath);
365     if (subStateValue == subStateRunning || subStateValue == subStateListening)
366     {
367         if (!socketObjectPath.empty())
368         {
369             systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
370         }
371         if (!isSocketActivatedService)
372         {
373             systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
374         }
375         else
376         {
377             // For socket-activated service, each connection will spawn a
378             // service instance from template. Need to find all spawned service
379             // `<unitName>@<attribute>.service` and stop them through the
380             // systemdUnitAction method
381             boost::system::error_code ec;
382             auto listUnits =
383                 conn->yield_method_call<std::vector<ListUnitsType>>(
384                     yield, ec, sysdService, sysdObjPath, sysdMgrIntf,
385                     "ListUnits");
386 
387             checkAndThrowInternalFailure(
388                 ec, "yield_method_call error: ListUnits failed");
389 
390             for (const auto& unit : listUnits)
391             {
392                 const auto& service =
393                     std::get<static_cast<int>(ListUnitElements::name)>(unit);
394                 const auto& status =
395                     std::get<static_cast<int>(ListUnitElements::subState)>(
396                         unit);
397                 if (service.find(baseUnitName + "@") != std::string::npos &&
398                     service.find(".service") != std::string::npos &&
399                     status == subStateRunning)
400                 {
401                     systemdUnitAction(conn, yield, service, sysdStopUnit);
402                 }
403             }
404         }
405     }
406 
407     if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port)))
408     {
409         createSocketOverrideConf();
410         // Create override config file and write data.
411         std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName};
412         std::string tmpFile{ovrCfgFile + "_tmp"};
413         std::ofstream cfgFile(tmpFile, std::ios::out);
414         if (!cfgFile.good())
415         {
416             lg2::error("Failed to open the {TMPFILE} file.", "TMPFILE",
417                        tmpFile);
418             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
419                                         Error::InternalFailure>();
420         }
421 
422         // Write the socket header
423         cfgFile << "[Socket]\n";
424         // Listen
425         cfgFile << "Listen" << protocol << "="
426                 << "\n";
427         cfgFile << "Listen" << protocol << "=" << portNum << "\n";
428         cfgFile.close();
429 
430         if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0)
431         {
432             lg2::error("Failed to rename {TMPFILE} file as {OVERCFGFILE} file.",
433                        "TMPFILE", tmpFile, "OVERCFGFILE", ovrCfgFile);
434             std::remove(tmpFile.c_str());
435             phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common::
436                                         Error::InternalFailure>();
437         }
438     }
439 
440     if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
441                        (1 << static_cast<uint8_t>(UpdatedProp::enabledState))))
442     {
443         std::vector<std::string> unitFiles;
444         if (socketObjectPath.empty())
445         {
446             unitFiles = {getServiceUnitName()};
447         }
448         else if (serviceObjectPath.empty())
449         {
450             unitFiles = {getSocketUnitName()};
451         }
452         else
453         {
454             unitFiles = {getSocketUnitName(), getServiceUnitName()};
455         }
456         systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue,
457                                     unitMaskedState, unitEnabledState);
458     }
459     return;
460 }
461 void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield)
462 {
463     if (!updatedFlag || isMaskedOut())
464     {
465         // No updates. Just return.
466         return;
467     }
468 
469     if (unitRunningState)
470     {
471         if (!socketObjectPath.empty())
472         {
473             systemdUnitAction(conn, yield, getSocketUnitName(),
474                               sysdRestartUnit);
475         }
476         if (!serviceObjectPath.empty())
477         {
478             systemdUnitAction(conn, yield, getServiceUnitName(),
479                               sysdRestartUnit);
480         }
481     }
482 
483     // Reset the flag
484     updatedFlag = 0;
485 
486     lg2::info("Applied new settings: {OBJPATH}", "OBJPATH", objPath);
487 
488     queryAndUpdateProperties();
489     return;
490 }
491 
492 void ServiceConfig::startServiceRestartTimer()
493 {
494     timer->expires_after(std::chrono::seconds(restartTimeout));
495     timer->async_wait([this](const boost::system::error_code& ec) {
496         if (ec == boost::asio::error::operation_aborted)
497         {
498             // Timer reset.
499             return;
500         }
501         else if (ec)
502         {
503             lg2::error("async wait error: {EC}", "EC", ec.value());
504             return;
505         }
506         updateInProgress = true;
507         boost::asio::spawn(conn->get_io_context(),
508                            [this](boost::asio::yield_context yield) {
509                                // Stop and apply configuration for all objects
510                                for (auto& srvMgrObj : srvMgrObjects)
511                                {
512                                    auto& srvObj = srvMgrObj.second;
513                                    if (srvObj->updatedFlag)
514                                    {
515                                        srvObj->stopAndApplyUnitConfig(yield);
516                                    }
517                                }
518                                // Do system reload
519                                systemdDaemonReload(conn, yield);
520                                // restart unit config.
521                                for (auto& srvMgrObj : srvMgrObjects)
522                                {
523                                    auto& srvObj = srvMgrObj.second;
524                                    if (srvObj->updatedFlag)
525                                    {
526                                        srvObj->restartUnitConfig(yield);
527                                    }
528                                }
529                                updateInProgress = false;
530                            });
531     });
532 }
533 
534 void ServiceConfig::registerProperties()
535 {
536     srvCfgIface = server.add_interface(objPath, serviceConfigIntfName);
537 
538     if (!socketObjectPath.empty())
539     {
540         sockAttrIface = server.add_interface(objPath, sockAttrIntfName);
541         sockAttrIface->register_property(
542             sockAttrPropPort, portNum,
543             [this](const uint16_t& req, uint16_t& res) {
544                 if (!internalSet)
545                 {
546                     if (req == res)
547                     {
548                         return 1;
549                     }
550                     if (updateInProgress)
551                     {
552                         return 0;
553                     }
554                     portNum = req;
555                     updatedFlag |=
556                         (1 << static_cast<uint8_t>(UpdatedProp::port));
557                     startServiceRestartTimer();
558                 }
559                 res = req;
560                 return 1;
561             });
562     }
563 
564     srvCfgIface->register_property(
565         srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
566             if (!internalSet)
567             {
568 #ifdef USB_CODE_UPDATE
569                 if (objPath == usbCodeUpdateObjectPath)
570                 {
571                     unitMaskedState = req;
572                     unitEnabledState = !unitMaskedState;
573                     unitRunningState = !unitMaskedState;
574                     internalSet = true;
575                     srvCfgIface->set_property(srvCfgPropEnabled,
576                                               unitEnabledState);
577                     srvCfgIface->set_property(srvCfgPropRunning,
578                                               unitRunningState);
579                     srvCfgIface->set_property(srvCfgPropMasked,
580                                               unitMaskedState);
581                     internalSet = false;
582                     setUSBCodeUpdateState(unitEnabledState);
583                     saveUSBCodeUpdateStateToFile(unitMaskedState,
584                                                  unitEnabledState);
585                     return 1;
586                 }
587 #endif
588                 if (req == res)
589                 {
590                     return 1;
591                 }
592                 if (updateInProgress)
593                 {
594                     return 0;
595                 }
596                 unitMaskedState = req;
597                 unitEnabledState = !unitMaskedState;
598                 unitRunningState = !unitMaskedState;
599                 updatedFlag |=
600                     (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) |
601                     (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
602                     (1 << static_cast<uint8_t>(UpdatedProp::runningState));
603                 internalSet = true;
604                 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
605                 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
606                 internalSet = false;
607                 startServiceRestartTimer();
608             }
609             res = req;
610             return 1;
611         });
612 
613     srvCfgIface->register_property(
614         srvCfgPropEnabled, unitEnabledState,
615         [this](const bool& req, bool& res) {
616             if (!internalSet)
617             {
618 #ifdef USB_CODE_UPDATE
619                 if (objPath == usbCodeUpdateObjectPath)
620                 {
621                     if (unitMaskedState)
622                     { // block updating if masked
623                         lg2::error("Invalid value specified");
624                         return -EINVAL;
625                     }
626                     unitEnabledState = req;
627                     unitRunningState = req;
628                     internalSet = true;
629                     srvCfgIface->set_property(srvCfgPropEnabled,
630                                               unitEnabledState);
631                     srvCfgIface->set_property(srvCfgPropRunning,
632                                               unitRunningState);
633                     internalSet = false;
634                     setUSBCodeUpdateState(unitEnabledState);
635                     saveUSBCodeUpdateStateToFile(unitMaskedState,
636                                                  unitEnabledState);
637                     res = req;
638                     return 1;
639                 }
640 #endif
641                 if (req == res)
642                 {
643                     return 1;
644                 }
645                 if (updateInProgress)
646                 {
647                     return 0;
648                 }
649                 if (unitMaskedState)
650                 { // block updating if masked
651                     lg2::error("Invalid value specified");
652                     return -EINVAL;
653                 }
654                 unitEnabledState = req;
655                 updatedFlag |=
656                     (1 << static_cast<uint8_t>(UpdatedProp::enabledState));
657                 startServiceRestartTimer();
658             }
659             res = req;
660             return 1;
661         });
662 
663     srvCfgIface->register_property(
664         srvCfgPropRunning, unitRunningState,
665         [this](const bool& req, bool& res) {
666             if (!internalSet)
667             {
668 #ifdef USB_CODE_UPDATE
669                 if (objPath == usbCodeUpdateObjectPath)
670                 {
671                     if (unitMaskedState)
672                     { // block updating if masked
673                         lg2::error("Invalid value specified");
674                         return -EINVAL;
675                     }
676                     unitEnabledState = req;
677                     unitRunningState = req;
678                     internalSet = true;
679                     srvCfgIface->set_property(srvCfgPropEnabled,
680                                               unitEnabledState);
681                     srvCfgIface->set_property(srvCfgPropRunning,
682                                               unitRunningState);
683                     internalSet = false;
684                     setUSBCodeUpdateState(unitEnabledState);
685                     saveUSBCodeUpdateStateToFile(unitMaskedState,
686                                                  unitEnabledState);
687                     res = req;
688                     return 1;
689                 }
690 #endif
691                 if (req == res)
692                 {
693                     return 1;
694                 }
695                 if (updateInProgress)
696                 {
697                     return 0;
698                 }
699                 if (unitMaskedState)
700                 { // block updating if masked
701                     lg2::error("Invalid value specified");
702                     return -EINVAL;
703                 }
704                 unitRunningState = req;
705                 updatedFlag |=
706                     (1 << static_cast<uint8_t>(UpdatedProp::runningState));
707                 startServiceRestartTimer();
708             }
709             res = req;
710             return 1;
711         });
712 
713     srvCfgIface->initialize();
714     if (!socketObjectPath.empty())
715     {
716         sockAttrIface->initialize();
717     }
718     return;
719 }
720 
721 } // namespace service
722 } // namespace phosphor
723