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