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