blob: 2d346c25b471857d57daf8fc6715380a1ad3e6bf [file] [log] [blame]
/*
// 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 (sockAttrIface && sockAttrIface->is_initialized())
{
internalSet = true;
sockAttrIface->set_property(sockAttrPropPort, 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 (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<std::string>(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<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 (!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<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::filesystem::path ovrUnitFileDir(systemdOverrideUnitBasePath);
ovrUnitFileDir += socketUnitName;
ovrUnitFileDir += ".d";
if (!std::filesystem::exists(ovrUnitFileDir))
{
if (!std::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_) :
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<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()
{
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<uint8_t>(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<uint8_t>(UpdatedProp::maskedState)) |
(1 << static_cast<uint8_t>(UpdatedProp::enabledState)) |
(1 << static_cast<uint8_t>(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<phosphor::logging::level::ERR>(
"Invalid value specified");
return -EINVAL;
}
unitEnabledState = req;
updatedFlag |=
(1 << static_cast<uint8_t>(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<phosphor::logging::level::ERR>(
"Invalid value specified");
return -EINVAL;
}
unitRunningState = req;
updatedFlag |=
(1 << static_cast<uint8_t>(UpdatedProp::runningState));
startServiceRestartTimer();
}
res = req;
return 1;
});
srvCfgIface->initialize();
if (!socketObjectPath.empty())
{
sockAttrIface->initialize();
}
return;
}
} // namespace service
} // namespace phosphor