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