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 <fstream> 17 #include <regex> 18 #include <boost/asio/spawn.hpp> 19 #include "srvcfg_manager.hpp" 20 21 extern std::shared_ptr<boost::asio::deadline_timer> timer; 22 extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>> 23 srvMgrObjects; 24 static bool updateInProgress = false; 25 26 namespace phosphor 27 { 28 namespace service 29 { 30 31 static constexpr const char *overrideConfFileName = "override.conf"; 32 static constexpr const size_t restartTimeout = 15; // seconds 33 34 static constexpr const char *systemd1UnitBasePath = 35 "/org/freedesktop/systemd1/unit/"; 36 static constexpr const char *systemdOverrideUnitBasePath = 37 "/etc/systemd/system/"; 38 39 void ServiceConfig::updateSocketProperties( 40 const boost::container::flat_map<std::string, VariantType> &propertyMap) 41 { 42 auto listenIt = propertyMap.find("Listen"); 43 if (listenIt != propertyMap.end()) 44 { 45 auto listenVal = 46 std::get<std::vector<std::tuple<std::string, std::string>>>( 47 listenIt->second); 48 if (listenVal.size()) 49 { 50 protocol = std::get<0>(listenVal[0]); 51 std::string port = std::get<1>(listenVal[0]); 52 auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1), 53 nullptr, 10); 54 if (tmp > std::numeric_limits<uint16_t>::max()) 55 { 56 throw std::out_of_range("Out of range"); 57 } 58 portNum = tmp; 59 if (iface && iface->is_initialized()) 60 { 61 internalSet = true; 62 iface->set_property(srvCfgPropPort, portNum); 63 internalSet = false; 64 } 65 } 66 } 67 } 68 69 void ServiceConfig::updateServiceProperties( 70 const boost::container::flat_map<std::string, VariantType> &propertyMap) 71 { 72 auto stateIt = propertyMap.find("UnitFileState"); 73 if (stateIt != propertyMap.end()) 74 { 75 stateValue = std::get<std::string>(stateIt->second); 76 unitEnabledState = unitMaskedState = false; 77 if (stateValue == stateMasked) 78 { 79 unitMaskedState = true; 80 } 81 else if (stateValue == stateEnabled) 82 { 83 unitEnabledState = true; 84 } 85 if (iface && iface->is_initialized()) 86 { 87 internalSet = true; 88 iface->set_property(srvCfgPropMasked, unitMaskedState); 89 iface->set_property(srvCfgPropEnabled, unitEnabledState); 90 internalSet = false; 91 } 92 } 93 auto subStateIt = propertyMap.find("SubState"); 94 if (subStateIt != propertyMap.end()) 95 { 96 subStateValue = std::get<std::string>(subStateIt->second); 97 if (subStateValue == subStateRunning) 98 { 99 unitRunningState = true; 100 } 101 if (iface && iface->is_initialized()) 102 { 103 internalSet = true; 104 iface->set_property(srvCfgPropRunning, unitRunningState); 105 internalSet = false; 106 } 107 } 108 } 109 110 void ServiceConfig::queryAndUpdateProperties() 111 { 112 conn->async_method_call( 113 [this](boost::system::error_code ec, 114 const boost::container::flat_map<std::string, VariantType> 115 &propertyMap) { 116 if (ec) 117 { 118 phosphor::logging::log<phosphor::logging::level::ERR>( 119 "async_method_call error: Failed to service unit " 120 "properties"); 121 return; 122 } 123 try 124 { 125 updateServiceProperties(propertyMap); 126 if (!socketObjectPath.empty()) 127 { 128 conn->async_method_call( 129 [this](boost::system::error_code ec, 130 const boost::container::flat_map< 131 std::string, VariantType> &propertyMap) { 132 if (ec) 133 { 134 phosphor::logging::log< 135 phosphor::logging::level::ERR>( 136 "async_method_call error: Failed to get " 137 "all property"); 138 return; 139 } 140 try 141 { 142 updateSocketProperties(propertyMap); 143 if (!iface) 144 { 145 registerProperties(); 146 } 147 } 148 catch (const std::exception &e) 149 { 150 phosphor::logging::log< 151 phosphor::logging::level::ERR>( 152 "Exception in getting socket properties", 153 phosphor::logging::entry("WHAT=%s", 154 e.what())); 155 return; 156 } 157 }, 158 sysdService, socketObjectPath, dBusPropIntf, 159 dBusGetAllMethod, sysdSocketIntf); 160 } 161 else if (!iface) 162 { 163 registerProperties(); 164 } 165 } 166 catch (const std::exception &e) 167 { 168 phosphor::logging::log<phosphor::logging::level::ERR>( 169 "Exception in getting socket properties", 170 phosphor::logging::entry("WHAT=%s", e.what())); 171 return; 172 } 173 }, 174 sysdService, serviceObjectPath, dBusPropIntf, dBusGetAllMethod, 175 sysdUnitIntf); 176 return; 177 } 178 179 void ServiceConfig::createSocketOverrideConf() 180 { 181 if (!socketObjectPath.empty()) 182 { 183 std::string socketUnitName(instantiatedUnitName + ".socket"); 184 /// Check override socket directory exist, if not create it. 185 std::experimental::filesystem::path ovrUnitFileDir( 186 systemdOverrideUnitBasePath); 187 ovrUnitFileDir += socketUnitName; 188 ovrUnitFileDir += ".d"; 189 if (!std::experimental::filesystem::exists(ovrUnitFileDir)) 190 { 191 if (!std::experimental::filesystem::create_directories( 192 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 server(srv_), 212 conn(conn_), 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_from_now(boost::posix_time::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