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