/* // 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 #include #include #include #include #include #include #include std::unique_ptr timer = nullptr; std::unique_ptr initTimer = nullptr; std::map> 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 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>; MonitorListMap unitsToMonitor; enum class monitorElement { unitName, instanceName, serviceObjPath, socketObjPath }; std::tuple 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& conn, boost::system::error_code /*ec*/, const std::vector& 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(ListUnitElements::loadState)>(unit) == loadStateNotFound) { continue; } const auto& fullUnitName = std::get(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(ListUnitElements::objectPath)>(unit); // Group the service & socket units together.. Same services // are managed together. auto it = unitsToMonitor.find(instantiatedUnitName); if (it != unitsToMonitor.end()) { auto& value = it->second; if (type == UnitType::service) { std::get(monitorElement::serviceObjPath)>( value) = objectPath.str; } else if (type == UnitType::socket) { std::get(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( server, conn, objPath, std::get(monitorElement::unitName)>(it.second), std::get(monitorElement::instanceName)>(it.second), std::get(monitorElement::serviceObjPath)>( it.second), std::get(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& 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& 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& conn) { // Check whether systemd completed all the loading before initializing conn->async_method_call( [&server, &conn](boost::system::error_code ec, const std::variant& value) { if (ec) { lg2::error("async_method_call error: ListUnits failed: {EC}", "EC", ec.value()); return; } if (std::get(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(io); timer = std::make_unique(io); initTimer = std::make_unique(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( static_cast(*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; }