blob: 3a771d94bd57304bacf36fd781f771c709e7ea05 [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>
#ifdef USB_CODE_UPDATE
#include <cereal/archives/json.hpp>
#include <cereal/types/tuple.hpp>
#include <cereal/types/unordered_map.hpp>
#include <cstdio>
#endif
#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/";
#ifdef USB_CODE_UPDATE
static constexpr const char* usbCodeUpdateStateFilePath =
"/var/lib/srvcfg_manager";
static constexpr const char* usbCodeUpdateStateFile =
"/var/lib/srvcfg_manager/usb-code-update-state";
static constexpr const char* emptyUsbCodeUpdateRulesFile =
"/etc/udev/rules.d/70-bmc-usb.rules";
using UsbCodeUpdateStateMap = std::unordered_map<std::string, bool>;
void ServiceConfig::setUSBCodeUpdateState(const bool& state)
{
// Enable usb code update
if (state)
{
if (std::filesystem::exists(emptyUsbCodeUpdateRulesFile))
{
lg2::info("Enable usb code update");
std::filesystem::remove(emptyUsbCodeUpdateRulesFile);
}
return;
}
// Disable usb code update
if (std::filesystem::exists(emptyUsbCodeUpdateRulesFile))
{
std::filesystem::remove(emptyUsbCodeUpdateRulesFile);
}
std::error_code ec;
std::filesystem::create_symlink("/dev/null", emptyUsbCodeUpdateRulesFile,
ec);
if (ec)
{
lg2::error("Disable usb code update failed");
return;
}
lg2::info("Disable usb code update");
}
void ServiceConfig::saveUSBCodeUpdateStateToFile(const bool& maskedState,
const bool& enabledState)
{
if (!std::filesystem::exists(usbCodeUpdateStateFilePath))
{
std::filesystem::create_directories(usbCodeUpdateStateFilePath);
}
UsbCodeUpdateStateMap usbCodeUpdateState;
usbCodeUpdateState[srvCfgPropMasked] = maskedState;
usbCodeUpdateState[srvCfgPropEnabled] = enabledState;
std::ofstream file(usbCodeUpdateStateFile, std::ios::out);
cereal::JSONOutputArchive archive(file);
archive(CEREAL_NVP(usbCodeUpdateState));
}
void ServiceConfig::getUSBCodeUpdateStateFromFile()
{
if (!std::filesystem::exists(usbCodeUpdateStateFile))
{
lg2::info("usb-code-update-state file does not exist");
unitMaskedState = false;
unitEnabledState = true;
unitRunningState = true;
setUSBCodeUpdateState(unitEnabledState);
return;
}
std::ifstream file(usbCodeUpdateStateFile);
cereal::JSONInputArchive archive(file);
UsbCodeUpdateStateMap usbCodeUpdateState;
archive(usbCodeUpdateState);
auto iterMask = usbCodeUpdateState.find(srvCfgPropMasked);
if (iterMask != usbCodeUpdateState.end())
{
unitMaskedState = iterMask->second;
if (unitMaskedState)
{
unitEnabledState = !unitMaskedState;
unitRunningState = !unitMaskedState;
setUSBCodeUpdateState(unitEnabledState);
return;
}
auto iterEnable = usbCodeUpdateState.find(srvCfgPropEnabled);
if (iterEnable != usbCodeUpdateState.end())
{
unitEnabledState = iterEnable->second;
unitRunningState = iterEnable->second;
setUSBCodeUpdateState(unitEnabledState);
}
}
}
#endif
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 ||
subStateValue == subStateListening)
{
unitRunningState = true;
}
if (srvCfgIface && srvCfgIface->is_initialized())
{
internalSet = true;
srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
internalSet = false;
}
}
#ifdef USB_CODE_UPDATE
if (baseUnitName == usbCodeUpdateUnitName)
{
getUSBCodeUpdateStateFromFile();
}
#endif
}
void ServiceConfig::queryAndUpdateProperties()
{
std::string objectPath = isSocketActivatedService ? socketObjectPath
: serviceObjectPath;
if (objectPath.empty())
{
return;
}
conn->async_method_call(
[this](boost::system::error_code ec,
const boost::container::flat_map<std::string, VariantType>&
propertyMap) {
if (ec)
{
lg2::error(
"async_method_call error: Failed to service unit properties: {EC}",
"EC", ec.value());
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)
{
lg2::error(
"async_method_call error: Failed to get all property: {EC}",
"EC", ec.value());
return;
}
try
{
updateSocketProperties(propertyMap);
if (!srvCfgIface)
{
registerProperties();
}
}
catch (const std::exception& e)
{
lg2::error(
"Exception in getting socket properties: {ERROR}",
"ERROR", e);
return;
}
},
sysdService, socketObjectPath, dBusPropIntf,
dBusGetAllMethod, sysdSocketIntf);
}
else if (!srvCfgIface)
{
registerProperties();
}
}
catch (const std::exception& e)
{
lg2::error("Exception in getting socket properties: {ERROR}",
"ERROR", e);
return;
}
},
sysdService, objectPath, 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))
{
lg2::error("Unable to create the {DIR} directory.", "DIR",
ovrUnitFileDir);
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_)
{
isSocketActivatedService = serviceObjectPath.empty();
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;
}
lg2::info("Applying new settings: {OBJPATH}", "OBJPATH", objPath);
if (subStateValue == subStateRunning || subStateValue == subStateListening)
{
if (!socketObjectPath.empty())
{
systemdUnitAction(conn, yield, getSocketUnitName(), sysdStopUnit);
}
if (!isSocketActivatedService)
{
systemdUnitAction(conn, yield, getServiceUnitName(), sysdStopUnit);
}
else
{
// For socket-activated service, each connection will spawn a
// service instance from template. Need to find all spawned service
// `<unitName>@<attribute>.service` and stop them through the
// systemdUnitAction method
boost::system::error_code ec;
auto listUnits =
conn->yield_method_call<std::vector<ListUnitsType>>(
yield, ec, sysdService, sysdObjPath, sysdMgrIntf,
"ListUnits");
checkAndThrowInternalFailure(
ec, "yield_method_call error: ListUnits failed");
for (const auto& unit : listUnits)
{
const auto& service =
std::get<static_cast<int>(ListUnitElements::name)>(unit);
const auto& status =
std::get<static_cast<int>(ListUnitElements::subState)>(
unit);
if (service.find(baseUnitName + "@") != std::string::npos &&
service.find(".service") != std::string::npos &&
status == subStateRunning)
{
systemdUnitAction(conn, yield, service, 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())
{
lg2::error("Failed to open the {TMPFILE} file.", "TMPFILE",
tmpFile);
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)
{
lg2::error("Failed to rename {TMPFILE} file as {OVERCFGFILE} file.",
"TMPFILE", tmpFile, "OVERCFGFILE", ovrCfgFile);
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 if (serviceObjectPath.empty())
{
unitFiles = {getSocketUnitName()};
}
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);
}
if (!serviceObjectPath.empty())
{
systemdUnitAction(conn, yield, getServiceUnitName(),
sysdRestartUnit);
}
}
// Reset the flag
updatedFlag = 0;
lg2::info("Applied new settings: {OBJPATH}", "OBJPATH", objPath);
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)
{
lg2::error("async wait error: {EC}", "EC", ec.value());
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)
{
#ifdef USB_CODE_UPDATE
if (baseUnitName == usbCodeUpdateUnitName)
{
unitMaskedState = req;
unitEnabledState = !unitMaskedState;
unitRunningState = !unitMaskedState;
internalSet = true;
srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
srvCfgIface->set_property(srvCfgPropMasked, unitMaskedState);
internalSet = false;
setUSBCodeUpdateState(unitEnabledState);
saveUSBCodeUpdateStateToFile(unitMaskedState, unitEnabledState);
return 1;
}
#endif
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)
{
#ifdef USB_CODE_UPDATE
if (baseUnitName == usbCodeUpdateUnitName)
{
if (unitMaskedState)
{ // block updating if masked
lg2::error("Invalid value specified");
return -EINVAL;
}
unitEnabledState = req;
unitRunningState = req;
internalSet = true;
srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
internalSet = false;
setUSBCodeUpdateState(unitEnabledState);
saveUSBCodeUpdateStateToFile(unitMaskedState, unitEnabledState);
res = req;
return 1;
}
#endif
if (req == res)
{
return 1;
}
if (updateInProgress)
{
return 0;
}
if (unitMaskedState)
{ // block updating if masked
lg2::error("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)
{
#ifdef USB_CODE_UPDATE
if (baseUnitName == usbCodeUpdateUnitName)
{
if (unitMaskedState)
{ // block updating if masked
lg2::error("Invalid value specified");
return -EINVAL;
}
unitEnabledState = req;
unitRunningState = req;
internalSet = true;
srvCfgIface->set_property(srvCfgPropEnabled, unitEnabledState);
srvCfgIface->set_property(srvCfgPropRunning, unitRunningState);
internalSet = false;
setUSBCodeUpdateState(unitEnabledState);
saveUSBCodeUpdateStateToFile(unitMaskedState, unitEnabledState);
res = req;
return 1;
}
#endif
if (req == res)
{
return 1;
}
if (updateInProgress)
{
return 0;
}
if (unitMaskedState)
{ // block updating if masked
lg2::error("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