blob: 333c0017e9b963610bdbd30e33da4eec3743810a [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 <fstream>
#include <regex>
#include "srvcfg_manager.hpp"
extern std::shared_ptr<boost::asio::deadline_timer> timer;
extern std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
srvMgrObjects;
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::syncWithSystemD1Properties()
{
// Read systemd1 socket/service property and load.
conn->async_method_call(
[this](boost::system::error_code ec,
const sdbusplus::message::variant<
std::vector<std::tuple<std::string, std::string>>> &value) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"async_method_call error: Failed to get property");
return;
}
try
{
auto listenVal = sdbusplus::message::variant_ns::get<
std::vector<std::tuple<std::string, std::string>>>(value);
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;
}
catch (const std::exception &e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Exception for port number",
phosphor::logging::entry("WHAT=%s", e.what()));
return;
}
conn->async_method_call(
[](boost::system::error_code ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"async_method_call error: Failed to set property");
return;
}
},
serviceConfigSrvName, objPath.c_str(),
"org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
"Port", sdbusplus::message::variant<uint16_t>(portNum));
},
"org.freedesktop.systemd1", sysDSockObjPath.c_str(),
"org.freedesktop.DBus.Properties", "Get",
"org.freedesktop.systemd1.Socket", "Listen");
conn->async_method_call(
[this](boost::system::error_code ec,
const sdbusplus::message::variant<std::string> &pValue) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"async_method_call error: Failed to get property");
return;
}
channelList.clear();
std::istringstream stm(
sdbusplus::message::variant_ns::get<std::string>(pValue));
std::string token;
while (std::getline(stm, token, ','))
{
channelList.push_back(token);
}
conn->async_method_call(
[](boost::system::error_code ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"async_method_call error: Failed to set property");
return;
}
},
serviceConfigSrvName, objPath.c_str(),
"org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
"Channel",
sdbusplus::message::variant<std::vector<std::string>>(
channelList));
},
"org.freedesktop.systemd1", sysDSockObjPath.c_str(),
"org.freedesktop.DBus.Properties", "Get",
"org.freedesktop.systemd1.Socket", "BindToDevice");
std::string srvUnitName(sysDUnitName);
if (srvUnitName == "dropbear")
{
// Dropbear service expects template arguments.
srvUnitName.append("@");
}
srvUnitName.append(".service");
conn->async_method_call(
[this](boost::system::error_code ec, const std::string &pValue) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"async_method_call error: Failed to get property");
return;
}
if ((pValue == "enabled") || (pValue == "static") ||
(pValue == "unmasked"))
{
stateValue = "enabled";
}
else if ((pValue == "disabled") || (pValue == "masked"))
{
stateValue = "disabled";
}
conn->async_method_call(
[](boost::system::error_code ec) {
if (ec)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"async_method_call error: Failed to set property");
return;
}
},
serviceConfigSrvName, objPath.c_str(),
"org.freedesktop.DBus.Properties", "Set", serviceConfigIntfName,
"State", sdbusplus::message::variant<std::string>(stateValue));
},
"org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "GetUnitFileState", srvUnitName);
return;
}
ServiceConfig::ServiceConfig(
sdbusplus::asio::object_server &srv_,
std::shared_ptr<sdbusplus::asio::connection> &conn_, std::string objPath_,
std::string unitName) :
server(srv_),
conn(conn_), objPath(objPath_), sysDUnitName(unitName)
{
std::string socketUnitName(sysDUnitName + ".socket");
// .socket systemd service files are handled.
// Regular .service only files are ignored.
if (!checkSystemdUnitExist(socketUnitName))
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Unit doesn't exist.",
phosphor::logging::entry("UNITNAME=%s", socketUnitName.c_str()));
phosphor::logging::elog<
sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
}
/// 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>();
}
}
/* Store require info locally */
unitSocketFilePath = std::string(ovrUnitFileDir);
sysDSockObjPath = systemd1UnitBasePath;
sysDSockObjPath.append(
std::regex_replace(sysDUnitName, std::regex("-"), "_2d"));
sysDSockObjPath.append("_2esocket");
// Adds interface, object and Properties....
registerProperties();
syncWithSystemD1Properties();
updatedFlag = 0;
return;
}
void ServiceConfig::applySystemDServiceConfig()
{
if (updatedFlag)
{
// No updates. Just return.
return;
}
phosphor::logging::log<phosphor::logging::level::INFO>(
"Applying new settings.",
phosphor::logging::entry("OBJPATH=%s", objPath.c_str()));
if (updatedFlag & ((1 << static_cast<uint8_t>(UpdatedProp::channel)) |
(1 << static_cast<uint8_t>(UpdatedProp::port))))
{
// Create override config file and write data.
std::string ovrCfgFile{unitSocketFilePath + "/" + 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";
// BindToDevice
bool firstElement = true;
cfgFile << "BindToDevice=";
for (const auto &it : channelList)
{
if (firstElement)
{
cfgFile << it;
firstElement = false;
}
else
{
cfgFile << "," << it;
}
}
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>();
}
}
std::string socketUnitName(sysDUnitName + ".socket");
std::string srvUnitName(sysDUnitName);
if (srvUnitName == "dropbear")
{
// Dropbear service expects template arguments.
// Todo: Unit action for service, fails with error
// "missing the instance name". Needs to implement
// getting all running instances and use it. This
// impact runtime but works fine during reboot.
srvUnitName.append("@");
}
srvUnitName.append(".service");
// Stop the running service in below scenarios.
// 1. State changed from "enabled" to "disabled"
// 2. No change in state and existing stateValue is
// "enabled" and there is change in other properties.
if (((stateValue == "disabled") &&
(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))) ||
(!(updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state))) &&
(stateValue == "enabled") && (updatedFlag)))
{
systemdUnitAction(conn, socketUnitName, "StopUnit");
systemdUnitAction(conn, srvUnitName, "StopUnit");
}
if (updatedFlag & (1 << static_cast<uint8_t>(UpdatedProp::state)))
{
std::vector<std::string> unitFiles = {socketUnitName, srvUnitName};
systemdUnitFilesStateChange(conn, unitFiles, stateValue);
}
// Perform daemon reload to read new settings
systemdDaemonReload(conn);
if (stateValue == "enabled")
{
// Restart the socket
systemdUnitAction(conn, socketUnitName, "StartUnit");
}
// Reset the flag
updatedFlag = 0;
// All done. Lets reload the properties which are applied on systemd1.
// TODO: We need to capture the service restart signal and reload data
// inside the signal handler. So that we can update the service
// properties modified, outside of this service as well.
syncWithSystemD1Properties();
return;
}
void ServiceConfig::startServiceRestartTimer()
{
timer->expires_from_now(boost::posix_time::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;
}
for (auto &srvMgrObj : srvMgrObjects)
{
auto &srvObj = srvMgrObj.second;
if (srvObj->updatedFlag)
{
srvObj->applySystemDServiceConfig();
}
}
});
}
void ServiceConfig::registerProperties()
{
std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
server.add_interface(objPath, serviceConfigIntfName);
iface->register_property(
"Port", portNum, [this](const uint16_t &req, uint16_t &res) {
if (req == res)
{
return 1;
}
portNum = req;
updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::port));
startServiceRestartTimer();
res = req;
return 1;
});
iface->register_property(
"Channel", channelList,
[this](const std::vector<std::string> &req,
std::vector<std::string> &res) {
if (req == res)
{
return 1;
}
channelList.clear();
std::copy(req.begin(), req.end(), back_inserter(channelList));
updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::channel));
startServiceRestartTimer();
res = req;
return 1;
});
iface->register_property(
"State", stateValue, [this](const std::string &req, std::string &res) {
if (req == res)
{
return 1;
}
if ((req != "enabled") && (req != "disabled"))
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Invalid value specified");
return -EINVAL;
}
stateValue = req;
updatedFlag |= (1 << static_cast<uint8_t>(UpdatedProp::state));
startServiceRestartTimer();
res = req;
return 1;
});
iface->initialize();
return;
}
} // namespace service
} // namespace phosphor