blob: 42a42465d6696bb8e6135cd5c25601edddeb9b2c [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/algorithm/string/replace.hpp>
#include <cereal/archives/json.hpp>
#include <cereal/types/tuple.hpp>
#include <cereal/types/unordered_map.hpp>
#include <sdbusplus/bus/match.hpp>
#include <filesystem>
#include <fstream>
#include <unordered_map>
std::unique_ptr<boost::asio::steady_timer> timer = nullptr;
std::unique_ptr<boost::asio::steady_timer> initTimer = nullptr;
std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>
srvMgrObjects;
static bool unitQueryStarted = false;
static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";
static constexpr const char* tmpFileBad = "/tmp/srvcfg-mgr.json.bad";
// Base service name list. All instance of these services and
// units(service/socket) will be managed by this daemon.
static std::unordered_map<std::string /* unitName */,
bool /* isSocketActivated */>
managedServices = {{"phosphor-ipmi-net", false}, {"bmcweb", false},
{"phosphor-ipmi-kcs", false}, {"start-ipkvm", false},
{"obmc-console", false}, {"dropbear", true},
{"obmc-console-ssh", true}};
enum class UnitType
{
service,
socket,
target,
device,
invalid
};
using MonitorListMap =
std::unordered_map<std::string, std::tuple<std::string, std::string,
std::string, std::string>>;
MonitorListMap unitsToMonitor;
enum class monitorElement
{
unitName,
instanceName,
serviceObjPath,
socketObjPath
};
std::tuple<std::string, UnitType, std::string>
getUnitNameTypeAndInstance(const std::string& fullUnitName)
{
UnitType type = UnitType::invalid;
std::string instanceName;
std::string unitName;
// get service type
auto typePos = fullUnitName.rfind(".");
if (typePos != std::string::npos)
{
const auto& typeStr = fullUnitName.substr(typePos + 1);
// Ignore types other than service and socket
if (typeStr == "service")
{
type = UnitType::service;
}
else if (typeStr == "socket")
{
type = UnitType::socket;
}
// get instance name if available
auto instancePos = fullUnitName.rfind("@");
if (instancePos != std::string::npos)
{
instanceName = fullUnitName.substr(instancePos + 1,
typePos - instancePos - 1);
unitName = fullUnitName.substr(0, instancePos);
}
else
{
unitName = fullUnitName.substr(0, typePos);
}
}
return std::make_tuple(unitName, type, instanceName);
}
static inline void
handleListUnitsResponse(sdbusplus::asio::object_server& server,
std::shared_ptr<sdbusplus::asio::connection>& conn,
boost::system::error_code /*ec*/,
const std::vector<ListUnitsType>& listUnits)
{
// Loop through all units, and mark all units, which has to be
// managed, irrespective of instance name.
for (const auto& unit : listUnits)
{
// Ignore non-existent units
if (std::get<static_cast<int>(ListUnitElements::loadState)>(unit) ==
loadStateNotFound)
{
continue;
}
const auto& fullUnitName =
std::get<static_cast<int>(ListUnitElements::name)>(unit);
auto [unitName, type,
instanceName] = getUnitNameTypeAndInstance(fullUnitName);
if (managedServices.count(unitName))
{
// For socket-activated units, ignore all its instances
if (managedServices.at(unitName) == true && !instanceName.empty())
{
continue;
}
std::string instantiatedUnitName =
unitName + addInstanceName(instanceName, "@");
const sdbusplus::message::object_path& objectPath =
std::get<static_cast<int>(ListUnitElements::objectPath)>(unit);
// Group the service & socket units togther.. Same services
// are managed together.
auto it = unitsToMonitor.find(instantiatedUnitName);
if (it != unitsToMonitor.end())
{
auto& value = it->second;
if (type == UnitType::service)
{
std::get<static_cast<int>(monitorElement::serviceObjPath)>(
value) = objectPath.str;
}
else if (type == UnitType::socket)
{
std::get<static_cast<int>(monitorElement::socketObjPath)>(
value) = objectPath.str;
}
continue;
}
// If not grouped with any existing entry, create a new one
if (type == UnitType::service)
{
unitsToMonitor.emplace(instantiatedUnitName,
std::make_tuple(unitName, instanceName,
objectPath.str, ""));
}
else if (type == UnitType::socket)
{
unitsToMonitor.emplace(instantiatedUnitName,
std::make_tuple(unitName, instanceName,
"", objectPath.str));
}
}
}
bool updateRequired = false;
bool jsonExist = std::filesystem::exists(srvCfgMgrFile);
if (jsonExist)
{
try
{
std::ifstream file(srvCfgMgrFile);
cereal::JSONInputArchive archive(file);
MonitorListMap savedMonitorList;
archive(savedMonitorList);
// compare the unit list read from systemd1 and the save list.
MonitorListMap diffMap;
std::set_difference(begin(unitsToMonitor), end(unitsToMonitor),
begin(savedMonitorList), end(savedMonitorList),
std::inserter(diffMap, begin(diffMap)));
for (auto& unitIt : diffMap)
{
auto it = savedMonitorList.find(unitIt.first);
if (it == savedMonitorList.end())
{
savedMonitorList.insert(unitIt);
updateRequired = true;
}
}
unitsToMonitor = savedMonitorList;
}
catch (const std::exception& e)
{
lg2::error(
"Failed to load {FILEPATH} file, need to rewrite: {ERROR}.",
"FILEPATH", srvCfgMgrFile, "ERROR", e);
// The "bad" files need to be moved to /tmp/ so that we can try to
// find out the cause of the file corruption. If we encounter this
// failure multiple times, we will only overwrite it to ensure that
// we don't accidentally fill up /tmp/.
std::error_code ec;
std::filesystem::copy_file(
srvCfgMgrFile, tmpFileBad,
std::filesystem::copy_options::overwrite_existing, ec);
if (ec)
{
lg2::error("Failed to copy {SRCFILE} file to {DSTFILE}.",
"SRCFILE", srvCfgMgrFile, "DSTFILE", tmpFileBad);
}
updateRequired = true;
}
}
if (!jsonExist || updateRequired)
{
std::ofstream file(srvCfgMgrFile);
cereal::JSONOutputArchive archive(file);
archive(CEREAL_NVP(unitsToMonitor));
}
#ifdef USB_CODE_UPDATE
unitsToMonitor.emplace(
"phosphor-usb-code-update",
std::make_tuple(
phosphor::service::usbCodeUpdateUnitName, "",
"/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
""));
#endif
// create objects for needed services
for (auto& it : unitsToMonitor)
{
sdbusplus::message::object_path basePath(
phosphor::service::srcCfgMgrBasePath);
std::string objPath(basePath / it.first);
auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(
server, conn, objPath,
std::get<static_cast<int>(monitorElement::unitName)>(it.second),
std::get<static_cast<int>(monitorElement::instanceName)>(it.second),
std::get<static_cast<int>(monitorElement::serviceObjPath)>(
it.second),
std::get<static_cast<int>(monitorElement::socketObjPath)>(
it.second));
srvMgrObjects.emplace(
std::make_pair(std::move(objPath), std::move(srvCfgObj)));
}
}
void init(sdbusplus::asio::object_server& server,
std::shared_ptr<sdbusplus::asio::connection>& conn)
{
// Go through all systemd units, and dynamically detect and manage
// the service daemons
conn->async_method_call(
[&server, &conn](boost::system::error_code ec,
const std::vector<ListUnitsType>& listUnits) {
if (ec)
{
lg2::error("async_method_call error: ListUnits failed: {EC}", "EC",
ec.value());
return;
}
handleListUnitsResponse(server, conn, ec, listUnits);
},
sysdService, sysdObjPath, sysdMgrIntf, "ListUnits");
}
void checkAndInit(sdbusplus::asio::object_server& server,
std::shared_ptr<sdbusplus::asio::connection>& conn)
{
// Check whether systemd completed all the loading before initializing
conn->async_method_call(
[&server, &conn](boost::system::error_code ec,
const std::variant<uint64_t>& value) {
if (ec)
{
lg2::error("async_method_call error: ListUnits failed: {EC}", "EC",
ec.value());
return;
}
if (std::get<uint64_t>(value))
{
if (!unitQueryStarted)
{
unitQueryStarted = true;
init(server, conn);
}
}
else
{
// FIX-ME: Latest up-stream sync caused issue in receiving
// StartupFinished signal. Unable to get StartupFinished signal
// from systemd1 hence using poll method too, to trigger it
// properly.
constexpr size_t pollTimeout = 10; // seconds
initTimer->expires_after(std::chrono::seconds(pollTimeout));
initTimer->async_wait(
[&server, &conn](const boost::system::error_code& ec) {
if (ec == boost::asio::error::operation_aborted)
{
// Timer reset.
return;
}
if (ec)
{
lg2::error(
"service config mgr - init - async wait error: {EC}",
"EC", ec.value());
return;
}
checkAndInit(server, conn);
});
}
},
sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,
"FinishTimestamp");
}
int main()
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
timer = std::make_unique<boost::asio::steady_timer>(io);
initTimer = std::make_unique<boost::asio::steady_timer>(io);
conn->request_name(phosphor::service::serviceConfigSrvName);
auto server = sdbusplus::asio::object_server(conn, true);
server.add_manager(phosphor::service::srcCfgMgrBasePath);
// Initialize the objects after systemd indicated startup finished.
auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>(
static_cast<sdbusplus::bus_t&>(*conn),
"type='signal',"
"member='StartupFinished',path='/org/freedesktop/systemd1',"
"interface='org.freedesktop.systemd1.Manager'",
[&server, &conn](sdbusplus::message_t& /*msg*/) {
if (!unitQueryStarted)
{
unitQueryStarted = true;
init(server, conn);
}
});
// this will make sure to initialize the objects, when daemon is
// restarted.
checkAndInit(server, conn);
io.run();
return 0;
}