| /* |
| // 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; |
| } |