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 (iface && iface->is_initialized()) 62 { 63 internalSet = true; 64 iface->set_property(srvCfgPropPort, 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 (iface && iface->is_initialized()) 88 { 89 internalSet = true; 90 iface->set_property(srvCfgPropMasked, unitMaskedState); 91 iface->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 (iface && iface->is_initialized()) 104 { 105 internalSet = true; 106 iface->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 (!iface) 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 (!iface) 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 iface = server.add_interface(objPath, serviceConfigIntfName); 384 385 if (!socketObjectPath.empty()) 386 { 387 iface->register_property( 388 srvCfgPropPort, portNum, 389 [this](const uint16_t& req, uint16_t& res) { 390 if (!internalSet) 391 { 392 if (req == res) 393 { 394 return 1; 395 } 396 if (updateInProgress) 397 { 398 return 0; 399 } 400 portNum = req; 401 updatedFlag |= 402 (1 << static_cast<uint8_t>(UpdatedProp::port)); 403 startServiceRestartTimer(); 404 } 405 res = req; 406 return 1; 407 }); 408 } 409 410 iface->register_property( 411 srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) { 412 if (!internalSet) 413 { 414 if (req == res) 415 { 416 return 1; 417 } 418 if (updateInProgress) 419 { 420 return 0; 421 } 422 unitMaskedState = req; 423 unitEnabledState = !unitMaskedState; 424 unitRunningState = !unitMaskedState; 425 updatedFlag |= 426 (1 << static_cast<uint8_t>(UpdatedProp::maskedState)) | 427 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)) | 428 (1 << static_cast<uint8_t>(UpdatedProp::runningState)); 429 internalSet = true; 430 iface->set_property(srvCfgPropEnabled, unitEnabledState); 431 iface->set_property(srvCfgPropRunning, unitRunningState); 432 internalSet = false; 433 startServiceRestartTimer(); 434 } 435 res = req; 436 return 1; 437 }); 438 439 iface->register_property( 440 srvCfgPropEnabled, unitEnabledState, 441 [this](const bool& req, bool& res) { 442 if (!internalSet) 443 { 444 if (req == res) 445 { 446 return 1; 447 } 448 if (updateInProgress) 449 { 450 return 0; 451 } 452 if (unitMaskedState) 453 { // block updating if masked 454 phosphor::logging::log<phosphor::logging::level::ERR>( 455 "Invalid value specified"); 456 return -EINVAL; 457 } 458 unitEnabledState = req; 459 updatedFlag |= 460 (1 << static_cast<uint8_t>(UpdatedProp::enabledState)); 461 startServiceRestartTimer(); 462 } 463 res = req; 464 return 1; 465 }); 466 467 iface->register_property( 468 srvCfgPropRunning, unitRunningState, 469 [this](const bool& req, bool& res) { 470 if (!internalSet) 471 { 472 if (req == res) 473 { 474 return 1; 475 } 476 if (updateInProgress) 477 { 478 return 0; 479 } 480 if (unitMaskedState) 481 { // block updating if masked 482 phosphor::logging::log<phosphor::logging::level::ERR>( 483 "Invalid value specified"); 484 return -EINVAL; 485 } 486 unitRunningState = req; 487 updatedFlag |= 488 (1 << static_cast<uint8_t>(UpdatedProp::runningState)); 489 startServiceRestartTimer(); 490 } 491 res = req; 492 return 1; 493 }); 494 495 iface->initialize(); 496 return; 497 } 498 499 } // namespace service 500 } // namespace phosphor 501