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