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 { 101 unitRunningState = true; 102 } 103 if (srvCfgIface && srvCfgIface->is_initialized()) 104 { 105 internalSet = true; 106 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState); 107 internalSet = false; 108 } 109 } 110 } 111 112 void ServiceConfig::queryAndUpdateProperties() 113 { 114 conn->async_method_call( 115 [this](boost::system::error_code ec, 116 const boost::container::flat_map<std::string, VariantType>& 117 propertyMap) { 118 if (ec) 119 { 120 phosphor::logging::log<phosphor::logging::level::ERR>( 121 "async_method_call error: Failed to service unit " 122 "properties"); 123 return; 124 } 125 try 126 { 127 updateServiceProperties(propertyMap); 128 if (!socketObjectPath.empty()) 129 { 130 conn->async_method_call( 131 [this](boost::system::error_code ec, 132 const boost::container::flat_map< 133 std::string, VariantType>& propertyMap) { 134 if (ec) 135 { 136 phosphor::logging::log< 137 phosphor::logging::level::ERR>( 138 "async_method_call error: Failed to get " 139 "all property"); 140 return; 141 } 142 try 143 { 144 updateSocketProperties(propertyMap); 145 if (!srvCfgIface) 146 { 147 registerProperties(); 148 } 149 } 150 catch (const std::exception& e) 151 { 152 phosphor::logging::log< 153 phosphor::logging::level::ERR>( 154 "Exception in getting socket properties", 155 phosphor::logging::entry("WHAT=%s", 156 e.what())); 157 return; 158 } 159 }, 160 sysdService, socketObjectPath, dBusPropIntf, 161 dBusGetAllMethod, sysdSocketIntf); 162 } 163 else if (!srvCfgIface) 164 { 165 registerProperties(); 166 } 167 } 168 catch (const std::exception& e) 169 { 170 phosphor::logging::log<phosphor::logging::level::ERR>( 171 "Exception in getting socket properties", 172 phosphor::logging::entry("WHAT=%s", e.what())); 173 return; 174 } 175 }, 176 sysdService, serviceObjectPath, dBusPropIntf, dBusGetAllMethod, 177 sysdUnitIntf); 178 return; 179 } 180 181 void ServiceConfig::createSocketOverrideConf() 182 { 183 if (!socketObjectPath.empty()) 184 { 185 std::string socketUnitName(instantiatedUnitName + ".socket"); 186 /// Check override socket directory exist, if not create it. 187 std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath); 188 ovrUnitFileDir += socketUnitName; 189 ovrUnitFileDir += ".d"; 190 if (!std::filesystem::exists(ovrUnitFileDir)) 191 { 192 if (!std::filesystem::create_directories(ovrUnitFileDir)) 193 { 194 phosphor::logging::log<phosphor::logging::level::ERR>( 195 "Unable to create the directory.", 196 phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str())); 197 phosphor::logging::elog<sdbusplus::xyz::openbmc_project:: 198 Common::Error::InternalFailure>(); 199 } 200 } 201 overrideConfDir = std::string(ovrUnitFileDir); 202 } 203 } 204 205 ServiceConfig::ServiceConfig( 206 sdbusplus::asio::object_server& srv_, 207 std::shared_ptr<sdbusplus::asio::connection>& conn_, 208 const std::string& objPath_, const std::string& baseUnitName_, 209 const std::string& instanceName_, const std::string& serviceObjPath_, 210 const std::string& socketObjPath_) : 211 conn(conn_), 212 server(srv_), objPath(objPath_), baseUnitName(baseUnitName_), 213 instanceName(instanceName_), serviceObjectPath(serviceObjPath_), 214 socketObjectPath(socketObjPath_) 215 { 216 instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@"); 217 updatedFlag = 0; 218 queryAndUpdateProperties(); 219 return; 220 } 221 222 std::string ServiceConfig::getSocketUnitName() 223 { 224 return instantiatedUnitName + ".socket"; 225 } 226 227 std::string ServiceConfig::getServiceUnitName() 228 { 229 return instantiatedUnitName + ".service"; 230 } 231 232 bool ServiceConfig::isMaskedOut() 233 { 234 // return true if state is masked & no request to update the maskedState 235 return ( 236 stateValue == "masked" && 237 !(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::maskedState)))); 238 } 239 240 void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield) 241 { 242 if (!updatedFlag || isMaskedOut()) 243 { 244 // No updates / masked - Just return. 245 return; 246 } 247 phosphor::logging::log<phosphor::logging::level::INFO>( 248 "Applying new settings.", 249 phosphor::logging::entry("OBJPATH=%s", objPath.c_str())); 250 if (subStateValue == "running") 251 { 252 if (!socketObjectPath.empty()) 253 { 254 systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit); 255 } 256 systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit); 257 } 258 259 if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::port))) 260 { 261 createSocketOverrideConf(); 262 // Create override config file and write data. 263 std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName}; 264 std::string tmpFile{ovrCfgFile + "_tmp"}; 265 std::ofstream cfgFile(tmpFile, std::ios::out); 266 if (!cfgFile.good()) 267 { 268 phosphor::logging::log<phosphor::logging::level::ERR>( 269 "Failed to open override.conf_tmp file"); 270 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: 271 Error::InternalFailure>(); 272 } 273 274 // Write the socket header 275 cfgFile << "[Socket]\n"; 276 // Listen 277 cfgFile << "Listen" << protocol << "=" 278 << "\n"; 279 cfgFile << "Listen" << protocol << "=" << portNum << "\n"; 280 cfgFile.close(); 281 282 if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0) 283 { 284 phosphor::logging::log<phosphor::logging::level::ERR>( 285 "Failed to rename tmp file as override.conf"); 286 std::remove(tmpFile.c_str()); 287 phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: 288 Error::InternalFailure>(); 289 } 290 } 291 292 if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) | 293 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)))) 294 { 295 std::vector<std::string> unitFiles; 296 if (socketObjectPath.empty()) 297 { 298 unitFiles = {getServiceUnitName()}; 299 } 300 else 301 { 302 unitFiles = {getSocketUnitName(), getServiceUnitName()}; 303 } 304 systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue, 305 unitMaskedState, unitEnabledState); 306 } 307 return; 308 } 309 void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield) 310 { 311 if (!updatedFlag || isMaskedOut()) 312 { 313 // No updates. Just return. 314 return; 315 } 316 317 if (unitRunningState) 318 { 319 if (!socketObjectPath.empty()) 320 { 321 systemdUnitAction(conn, yield, getSocketUnitName(), 322 sysdRestartUnit); 323 } 324 systemdUnitAction(conn, yield, getServiceUnitName(), sysdRestartUnit); 325 } 326 327 // Reset the flag 328 updatedFlag = 0; 329 330 phosphor::logging::log<phosphor::logging::level::INFO>( 331 "Applied new settings", 332 phosphor::logging::entry("OBJPATH=%s", objPath.c_str())); 333 334 queryAndUpdateProperties(); 335 return; 336 } 337 338 void ServiceConfig::startServiceRestartTimer() 339 { 340 timer->expires_after(std::chrono::seconds(restartTimeout)); 341 timer->async_wait([this](const boost::system::error_code& ec) { 342 if (ec == boost::asio::error::operation_aborted) 343 { 344 // Timer reset. 345 return; 346 } 347 else if (ec) 348 { 349 phosphor::logging::log<phosphor::logging::level::ERR>( 350 "async wait error."); 351 return; 352 } 353 updateInProgress = true; 354 boost::asio::spawn(conn->get_io_context(), 355 [this](boost::asio::yield_context yield) { 356 // Stop and apply configuration for all objects 357 for (auto& srvMgrObj : srvMgrObjects) 358 { 359 auto& srvObj = srvMgrObj.second; 360 if (srvObj->updatedFlag) 361 { 362 srvObj->stopAndApplyUnitConfig(yield); 363 } 364 } 365 // Do system reload 366 systemdDaemonReload(conn, yield); 367 // restart unit config. 368 for (auto& srvMgrObj : srvMgrObjects) 369 { 370 auto& srvObj = srvMgrObj.second; 371 if (srvObj->updatedFlag) 372 { 373 srvObj->restartUnitConfig(yield); 374 } 375 } 376 updateInProgress = false; 377 }); 378 }); 379 } 380 381 void ServiceConfig::registerProperties() 382 { 383 srvCfgIface = server.add_interface(objPath, serviceConfigIntfName); 384 385 if (!socketObjectPath.empty()) 386 { 387 sockAttrIface = server.add_interface(objPath, sockAttrIntfName); 388 sockAttrIface->register_property( 389 sockAttrPropPort, portNum, 390 [this](const uint16_t& req, uint16_t& res) { 391 if (!internalSet) 392 { 393 if (req == res) 394 { 395 return 1; 396 } 397 if (updateInProgress) 398 { 399 return 0; 400 } 401 portNum = req; 402 updatedFlag |= 403 (1 << static_cast<uint8_t>(UpdatedProp::port)); 404 startServiceRestartTimer(); 405 } 406 res = req; 407 return 1; 408 }); 409 } 410 411 srvCfgIface->register_property( 412 srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) { 413 if (!internalSet) 414 { 415 if (req == res) 416 { 417 return 1; 418 } 419 if (updateInProgress) 420 { 421 return 0; 422 } 423 unitMaskedState = req; 424 unitEnabledState = !unitMaskedState; 425 unitRunningState = !unitMaskedState; 426 updatedFlag |= 427 (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) | 428 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) | 429 (1 << static_cast<uint8_t>(UpdatedProp::runningState)); 430 internalSet = true; 431 srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState); 432 srvCfgIface->set_property(srvCfgPropRunning, unitRunningState); 433 internalSet = false; 434 startServiceRestartTimer(); 435 } 436 res = req; 437 return 1; 438 }); 439 440 srvCfgIface->register_property( 441 srvCfgPropEnabled, unitEnabledState, 442 [this](const bool& req, bool& res) { 443 if (!internalSet) 444 { 445 if (req == res) 446 { 447 return 1; 448 } 449 if (updateInProgress) 450 { 451 return 0; 452 } 453 if (unitMaskedState) 454 { // block updating if masked 455 phosphor::logging::log<phosphor::logging::level::ERR>( 456 "Invalid value specified"); 457 return -EINVAL; 458 } 459 unitEnabledState = req; 460 updatedFlag |= 461 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)); 462 startServiceRestartTimer(); 463 } 464 res = req; 465 return 1; 466 }); 467 468 srvCfgIface->register_property( 469 srvCfgPropRunning, unitRunningState, 470 [this](const bool& req, bool& res) { 471 if (!internalSet) 472 { 473 if (req == res) 474 { 475 return 1; 476 } 477 if (updateInProgress) 478 { 479 return 0; 480 } 481 if (unitMaskedState) 482 { // block updating if masked 483 phosphor::logging::log<phosphor::logging::level::ERR>( 484 "Invalid value specified"); 485 return -EINVAL; 486 } 487 unitRunningState = req; 488 updatedFlag |= 489 (1 << static_cast<uint8_t>(UpdatedProp::runningState)); 490 startServiceRestartTimer(); 491 } 492 res = req; 493 return 1; 494 }); 495 496 srvCfgIface->initialize(); 497 if (!socketObjectPath.empty()) 498 { 499 sockAttrIface->initialize(); 500 } 501 return; 502 } 503 504 } // namespace service 505 } // namespace phosphor 506