/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include "srvcfg_manager.hpp" #include #include #include extern std::unique_ptr timer; extern std::map> srvMgrObjects; static bool updateInProgress = false; namespace phosphor { namespace service { static constexpr const char* overrideConfFileName = "override.conf"; static constexpr const size_t restartTimeout = 15; // seconds static constexpr const char* systemd1UnitBasePath = "/org/freedesktop/systemd1/unit/"; static constexpr const char* systemdOverrideUnitBasePath = "/etc/systemd/system/"; void ServiceConfig::updateSocketProperties( const boost::container::flat_map& propertyMap) { auto listenIt = propertyMap.find("Listen"); if (listenIt != propertyMap.end()) { auto listenVal = std::get>>( listenIt->second); if (listenVal.size()) { protocol = std::get<0>(listenVal[0]); std::string port = std::get<1>(listenVal[0]); auto tmp = std::stoul(port.substr(port.find_last_of(":") + 1), nullptr, 10); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } portNum = tmp; if (sockAttrIface && sockAttrIface->is_initialized()) { internalSet = true; sockAttrIface->set_property(sockAttrPropPort, portNum); internalSet = false; } } } } void ServiceConfig::updateServiceProperties( const boost::container::flat_map& propertyMap) { auto stateIt = propertyMap.find("UnitFileState"); if (stateIt != propertyMap.end()) { stateValue = std::get(stateIt->second); unitEnabledState = unitMaskedState = false; if (stateValue == stateMasked) { unitMaskedState = true; } else if (stateValue == stateEnabled) { unitEnabledState = true; } if (srvCfgIface && srvCfgIface->is_initialized()) { internalSet = true; srvCfgIface->set_property(srvCfgPropMasked, unitMaskedState); srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState); internalSet = false; } } auto subStateIt = propertyMap.find("SubState"); if (subStateIt != propertyMap.end()) { subStateValue = std::get(subStateIt->second); if (subStateValue == subStateRunning) { unitRunningState = true; } if (srvCfgIface && srvCfgIface->is_initialized()) { internalSet = true; srvCfgIface->set_property(srvCfgPropRunning, unitRunningState); internalSet = false; } } } void ServiceConfig::queryAndUpdateProperties() { conn->async_method_call( [this](boost::system::error_code ec, const boost::container::flat_map& propertyMap) { if (ec) { phosphor::logging::log( "async_method_call error: Failed to service unit " "properties"); return; } try { updateServiceProperties(propertyMap); if (!socketObjectPath.empty()) { conn->async_method_call( [this](boost::system::error_code ec, const boost::container::flat_map< std::string, VariantType>& propertyMap) { if (ec) { phosphor::logging::log< phosphor::logging::level::ERR>( "async_method_call error: Failed to get " "all property"); return; } try { updateSocketProperties(propertyMap); if (!srvCfgIface) { registerProperties(); } } catch (const std::exception& e) { phosphor::logging::log< phosphor::logging::level::ERR>( "Exception in getting socket properties", phosphor::logging::entry("WHAT=%s", e.what())); return; } }, sysdService, socketObjectPath, dBusPropIntf, dBusGetAllMethod, sysdSocketIntf); } else if (!srvCfgIface) { registerProperties(); } } catch (const std::exception& e) { phosphor::logging::log( "Exception in getting socket properties", phosphor::logging::entry("WHAT=%s", e.what())); return; } }, sysdService, serviceObjectPath, dBusPropIntf, dBusGetAllMethod, sysdUnitIntf); return; } void ServiceConfig::createSocketOverrideConf() { if (!socketObjectPath.empty()) { std::string socketUnitName(instantiatedUnitName + ".socket"); /// Check override socket directory exist, if not create it. std::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath); ovrUnitFileDir += socketUnitName; ovrUnitFileDir += ".d"; if (!std::filesystem::exists(ovrUnitFileDir)) { if (!std::filesystem::create_directories(ovrUnitFileDir)) { phosphor::logging::log( "Unable to create the directory.", phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str())); phosphor::logging::elog(); } } overrideConfDir = std::string(ovrUnitFileDir); } } ServiceConfig::ServiceConfig( sdbusplus::asio::object_server& srv_, std::shared_ptr& conn_, const std::string& objPath_, const std::string& baseUnitName_, const std::string& instanceName_, const std::string& serviceObjPath_, const std::string& socketObjPath_) : conn(conn_), server(srv_), objPath(objPath_), baseUnitName(baseUnitName_), instanceName(instanceName_), serviceObjectPath(serviceObjPath_), socketObjectPath(socketObjPath_) { instantiatedUnitName = baseUnitName + addInstanceName(instanceName, "@"); updatedFlag = 0; queryAndUpdateProperties(); return; } std::string ServiceConfig::getSocketUnitName() { return instantiatedUnitName + ".socket"; } std::string ServiceConfig::getServiceUnitName() { return instantiatedUnitName + ".service"; } bool ServiceConfig::isMaskedOut() { // return true if state is masked & no request to update the maskedState return ( stateValue == "masked" && !(updatedFlag & (1 << static_cast(UpdatedProp::maskedState)))); } void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield) { if (!updatedFlag || isMaskedOut()) { // No updates / masked - Just return. return; } phosphor::logging::log( "Applying new settings.", phosphor::logging::entry("OBJPATH=%s", objPath.c_str())); if (subStateValue == "running") { if (!socketObjectPath.empty()) { systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit); } systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit); } if (updatedFlag & (1 << static_cast(UpdatedProp::port))) { createSocketOverrideConf(); // Create override config file and write data. std::string ovrCfgFile{overrideConfDir + "/" + overrideConfFileName}; std::string tmpFile{ovrCfgFile + "_tmp"}; std::ofstream cfgFile(tmpFile, std::ios::out); if (!cfgFile.good()) { phosphor::logging::log( "Failed to open override.conf_tmp file"); phosphor::logging::elog(); } // Write the socket header cfgFile << "[Socket]\n"; // Listen cfgFile << "Listen" << protocol << "=" << "\n"; cfgFile << "Listen" << protocol << "=" << portNum << "\n"; cfgFile.close(); if (std::rename(tmpFile.c_str(), ovrCfgFile.c_str()) != 0) { phosphor::logging::log( "Failed to rename tmp file as override.conf"); std::remove(tmpFile.c_str()); phosphor::logging::elog(); } } if (updatedFlag & ((1 << static_cast(UpdatedProp::maskedState)) | (1 << static_cast(UpdatedProp::enabledState)))) { std::vector unitFiles; if (socketObjectPath.empty()) { unitFiles = {getServiceUnitName()}; } else { unitFiles = {getSocketUnitName(), getServiceUnitName()}; } systemdUnitFilesStateChange(conn, yield, unitFiles, stateValue, unitMaskedState, unitEnabledState); } return; } void ServiceConfig::restartUnitConfig(boost::asio::yield_context yield) { if (!updatedFlag || isMaskedOut()) { // No updates. Just return. return; } if (unitRunningState) { if (!socketObjectPath.empty()) { systemdUnitAction(conn, yield, getSocketUnitName(), sysdRestartUnit); } systemdUnitAction(conn, yield, getServiceUnitName(), sysdRestartUnit); } // Reset the flag updatedFlag = 0; phosphor::logging::log( "Applied new settings", phosphor::logging::entry("OBJPATH=%s", objPath.c_str())); queryAndUpdateProperties(); return; } void ServiceConfig::startServiceRestartTimer() { timer->expires_after(std::chrono::seconds(restartTimeout)); timer->async_wait([this](const boost::system::error_code& ec) { if (ec == boost::asio::error::operation_aborted) { // Timer reset. return; } else if (ec) { phosphor::logging::log( "async wait error."); return; } updateInProgress = true; boost::asio::spawn(conn->get_io_context(), [this](boost::asio::yield_context yield) { // Stop and apply configuration for all objects for (auto& srvMgrObj : srvMgrObjects) { auto& srvObj = srvMgrObj.second; if (srvObj->updatedFlag) { srvObj->stopAndApplyUnitConfig(yield); } } // Do system reload systemdDaemonReload(conn, yield); // restart unit config. for (auto& srvMgrObj : srvMgrObjects) { auto& srvObj = srvMgrObj.second; if (srvObj->updatedFlag) { srvObj->restartUnitConfig(yield); } } updateInProgress = false; }); }); } void ServiceConfig::registerProperties() { srvCfgIface = server.add_interface(objPath, serviceConfigIntfName); if (!socketObjectPath.empty()) { sockAttrIface = server.add_interface(objPath, sockAttrIntfName); sockAttrIface->register_property( sockAttrPropPort, portNum, [this](const uint16_t& req, uint16_t& res) { if (!internalSet) { if (req == res) { return 1; } if (updateInProgress) { return 0; } portNum = req; updatedFlag |= (1 << static_cast(UpdatedProp::port)); startServiceRestartTimer(); } res = req; return 1; }); } srvCfgIface->register_property( srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) { if (!internalSet) { if (req == res) { return 1; } if (updateInProgress) { return 0; } unitMaskedState = req; unitEnabledState = !unitMaskedState; unitRunningState = !unitMaskedState; updatedFlag |= (1 << static_cast(UpdatedProp::maskedState)) | (1 << static_cast(UpdatedProp::enabledState)) | (1 << static_cast(UpdatedProp::runningState)); internalSet = true; srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState); srvCfgIface->set_property(srvCfgPropRunning, unitRunningState); internalSet = false; startServiceRestartTimer(); } res = req; return 1; }); srvCfgIface->register_property( srvCfgPropEnabled, unitEnabledState, [this](const bool& req, bool& res) { if (!internalSet) { if (req == res) { return 1; } if (updateInProgress) { return 0; } if (unitMaskedState) { // block updating if masked phosphor::logging::log( "Invalid value specified"); return -EINVAL; } unitEnabledState = req; updatedFlag |= (1 << static_cast(UpdatedProp::enabledState)); startServiceRestartTimer(); } res = req; return 1; }); srvCfgIface->register_property( srvCfgPropRunning, unitRunningState, [this](const bool& req, bool& res) { if (!internalSet) { if (req == res) { return 1; } if (updateInProgress) { return 0; } if (unitMaskedState) { // block updating if masked phosphor::logging::log( "Invalid value specified"); return -EINVAL; } unitRunningState = req; updatedFlag |= (1 << static_cast(UpdatedProp::runningState)); startServiceRestartTimer(); } res = req; return 1; }); srvCfgIface->initialize(); if (!socketObjectPath.empty()) { sockAttrIface->initialize(); } return; } } // namespace service } // namespace phosphor