/* | |
// 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 <boost/asio/spawn.hpp> | |
#include <fstream> | |
#include <regex> | |
extern std::unique_ptr<boost::asio::steady_timer> timer; | |
extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>> | |
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<std::string, VariantType>& propertyMap) | |
{ | |
auto listenIt = propertyMap.find("Listen"); | |
if (listenIt != propertyMap.end()) | |
{ | |
auto listenVal = | |
std::get<std::vector<std::tuple<std::string, std::string>>>( | |
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<uint16_t>::max()) | |
{ | |
throw std::out_of_range("Out of range"); | |
} | |
portNum = tmp; | |
if (iface && iface->is_initialized()) | |
{ | |
internalSet = true; | |
iface->set_property(srvCfgPropPort, portNum); | |
internalSet = false; | |
} | |
} | |
} | |
} | |
void ServiceConfig::updateServiceProperties( | |
const boost::container::flat_map<std::string, VariantType>& propertyMap) | |
{ | |
auto stateIt = propertyMap.find("UnitFileState"); | |
if (stateIt != propertyMap.end()) | |
{ | |
stateValue = std::get<std::string>(stateIt->second); | |
unitEnabledState = unitMaskedState = false; | |
if (stateValue == stateMasked) | |
{ | |
unitMaskedState = true; | |
} | |
else if (stateValue == stateEnabled) | |
{ | |
unitEnabledState = true; | |
} | |
if (iface && iface->is_initialized()) | |
{ | |
internalSet = true; | |
iface->set_property(srvCfgPropMasked, unitMaskedState); | |
iface->set_property(srvCfgPropEnabled, unitEnabledState); | |
internalSet = false; | |
} | |
} | |
auto subStateIt = propertyMap.find("SubState"); | |
if (subStateIt != propertyMap.end()) | |
{ | |
subStateValue = std::get<std::string>(subStateIt->second); | |
if (subStateValue == subStateRunning) | |
{ | |
unitRunningState = true; | |
} | |
if (iface && iface->is_initialized()) | |
{ | |
internalSet = true; | |
iface->set_property(srvCfgPropRunning, unitRunningState); | |
internalSet = false; | |
} | |
} | |
} | |
void ServiceConfig::queryAndUpdateProperties() | |
{ | |
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 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 (!iface) | |
{ | |
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 (!iface) | |
{ | |
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, 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::experimental::filesystem::path ovrUnitFileDir( | |
systemdOverrideUnitBasePath); | |
ovrUnitFileDir += socketUnitName; | |
ovrUnitFileDir += ".d"; | |
if (!std::experimental::filesystem::exists(ovrUnitFileDir)) | |
{ | |
if (!std::experimental::filesystem::create_directories( | |
ovrUnitFileDir)) | |
{ | |
phosphor::logging::log<phosphor::logging::level::ERR>( | |
"Unable to create the directory.", | |
phosphor::logging::entry("DIR=%s", ovrUnitFileDir.c_str())); | |
phosphor::logging::elog<sdbusplus::xyz::openbmc_project:: | |
Common::Error::InternalFailure>(); | |
} | |
} | |
overrideConfDir = std::string(ovrUnitFileDir); | |
} | |
} | |
ServiceConfig::ServiceConfig( | |
sdbusplus::asio::object_server& srv_, | |
std::shared_ptr<sdbusplus::asio::connection>& conn_, | |
const std::string& objPath_, const std::string& baseUnitName_, | |
const std::string& instanceName_, const std::string& serviceObjPath_, | |
const std::string& socketObjPath_) : | |
server(srv_), | |
conn(conn_), 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<uint8_t>(UpdatedProp::maskedState)))); | |
} | |
void ServiceConfig::stopAndApplyUnitConfig(boost::asio::yield_context yield) | |
{ | |
if (!updatedFlag || isMaskedOut()) | |
{ | |
// No updates / masked - Just return. | |
return; | |
} | |
phosphor::logging::log<phosphor::logging::level::INFO>( | |
"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<uint8_t>(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<phosphor::logging::level::ERR>( | |
"Failed to open override.conf_tmp file"); | |
phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: | |
Error::InternalFailure>(); | |
} | |
// 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<phosphor::logging::level::ERR>( | |
"Failed to rename tmp file as override.conf"); | |
std::remove(tmpFile.c_str()); | |
phosphor::logging::elog<sdbusplus::xyz::openbmc_project::Common:: | |
Error::InternalFailure>(); | |
} | |
} | |
if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::maskedState)) | | |
(1 << static_cast<uint8_t>(UpdatedProp::enabledState)))) | |
{ | |
std::vector<std::string> 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<phosphor::logging::level::INFO>( | |
"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<phosphor::logging::level::ERR>( | |
"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() | |
{ | |
iface = server.add_interface(objPath, serviceConfigIntfName); | |
if (!socketObjectPath.empty()) | |
{ | |
iface->register_property( | |
srvCfgPropPort, 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<uint8_t>(UpdatedProp::port)); | |
startServiceRestartTimer(); | |
} | |
res = req; | |
return 1; | |
}); | |
} | |
iface->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<uint8_t>(UpdatedProp::maskedState)) | | |
(1 << static_cast<uint8_t>(UpdatedProp::enabledState)) | | |
(1 << static_cast<uint8_t>(UpdatedProp::runningState)); | |
internalSet = true; | |
iface->set_property(srvCfgPropEnabled, unitEnabledState); | |
iface->set_property(srvCfgPropRunning, unitRunningState); | |
internalSet = false; | |
startServiceRestartTimer(); | |
} | |
res = req; | |
return 1; | |
}); | |
iface->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<phosphor::logging::level::ERR>( | |
"Invalid value specified"); | |
return -EINVAL; | |
} | |
unitEnabledState = req; | |
updatedFlag |= | |
(1 << static_cast<uint8_t>(UpdatedProp::enabledState)); | |
startServiceRestartTimer(); | |
} | |
res = req; | |
return 1; | |
}); | |
iface->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<phosphor::logging::level::ERR>( | |
"Invalid value specified"); | |
return -EINVAL; | |
} | |
unitRunningState = req; | |
updatedFlag |= | |
(1 << static_cast<uint8_t>(UpdatedProp::runningState)); | |
startServiceRestartTimer(); | |
} | |
res = req; | |
return 1; | |
}); | |
iface->initialize(); | |
return; | |
} | |
} // namespace service | |
} // namespace phosphor |