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