srvcfg: Updated for dynamic service/socket mgmt

Fixed srvcfg crash manager issue, by doing the service &
socket unit file queries dynamically and exposing the objects
to be controlled. This takes care of instanced service &
socket files like phosphor-ipmi-net@eth0 or
phosphor-ipmi-net@eth1 dynamically.

Tested:
1. Verified as per the base service name, all the instanced
service & socket names are dynamically queried and exposed
2. Made sure to list the disabled services thorugh this method
3. Made sure new services listed after fw-update are also
exposed as objects
4. Verfied phosphor-ipmi-net@eth0 port change using ipmitool
-p <newport> option
5. Verified permanent disable of the unit.
6. Verified run time enable / disable of the service.
7. Verified phosphor-ipmi-kcs service permanent & run time
enable / disable

Change-Id: Ib933a2fbff73eae4348a5940d350ae7972db03fb
Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
diff --git a/src/main.cpp b/src/main.cpp
index abbb0b3..cca4899 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -14,35 +14,287 @@
 // limitations under the License.

 */

 #include "srvcfg_manager.hpp"

+#include <boost/algorithm/string/replace.hpp>

+#include <sdbusplus/bus/match.hpp>

+#include <filesystem>

+#include <cereal/archives/json.hpp>

+#include <cereal/types/tuple.hpp>

+#include <cereal/types/unordered_map.hpp>

+#include <fstream>

 

 std::shared_ptr<boost::asio::deadline_timer> timer = nullptr;

 std::map<std::string, std::shared_ptr<phosphor::service::ServiceConfig>>

     srvMgrObjects;

 

-static std::map<std::string, std::string> serviceList = {

-    {"netipmid", "phosphor-ipmi-net"}, {"web", "bmcweb"}, {"ssh", "dropbear"}};

+static constexpr const char* srvCfgMgrFile = "/etc/srvcfg-mgr.json";

+

+// Base service name list. All instance of these services and

+// units(service/socket) will be managed by this daemon.

+static std::vector<std::string> serviceNames = {

+    "phosphor-ipmi-net", "bmcweb", "phosphor-ipmi-kcs", "start-ipkvm"};

+

+using ListUnitsType =

+    std::tuple<std::string, std::string, std::string, std::string, std::string,

+               std::string, sdbusplus::message::object_path, uint32_t,

+               std::string, sdbusplus::message::object_path>;

+

+enum class ListUnitElements

+{

+    name,

+    descriptionString,

+    loadState,

+    activeState,

+    subState,

+    followedUnit,

+    objectPath,

+    queuedJobType,

+    jobType,

+    jobObject

+};

+

+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)

+    {

+        const auto& fullUnitName =

+            std::get<static_cast<int>(ListUnitElements::name)>(unit);

+        auto [unitName, type, instanceName] =

+            getUnitNameTypeAndInstance(fullUnitName);

+        if (std::find(serviceNames.begin(), serviceNames.end(), unitName) !=

+            serviceNames.end())

+        {

+            std::string instantiatedUnitName =

+                unitName + addInstanceName(instanceName, "_40");

+            boost::replace_all(instantiatedUnitName, "-", "_2d");

+            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::unitName)>(

+                        value) = unitName;

+                    std::get<static_cast<int>(monitorElement::instanceName)>(

+                        value) = instanceName;

+                    std::get<static_cast<int>(monitorElement::serviceObjPath)>(

+                        value) = objectPath;

+                }

+                else if (type == UnitType::socket)

+                {

+                    std::get<static_cast<int>(monitorElement::socketObjPath)>(

+                        value) = objectPath;

+                }

+            }

+            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("", "", "", objectPath.str));

+            }

+        }

+    }

+

+    bool updateRequired = false;

+    bool jsonExist = std::filesystem::exists(srvCfgMgrFile);

+    if (jsonExist)

+    {

+        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;

+    }

+    if (!jsonExist || updateRequired)

+    {

+        std::ofstream file(srvCfgMgrFile);

+        cereal::JSONOutputArchive archive(file);

+        archive(CEREAL_NVP(unitsToMonitor));

+    }

+

+    // create objects for needed services

+    for (auto& it : unitsToMonitor)

+    {

+        std::string objPath(std::string(phosphor::service::srcCfgMgrBasePath) +

+                            "/" + it.first);

+        std::string instanciatedUnitName =

+            std::get<static_cast<int>(monitorElement::unitName)>(it.second) +

+            addInstanceName(

+                std::get<static_cast<int>(monitorElement::instanceName)>(

+                    it.second),

+                "@");

+        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)

+            {

+                phosphor::logging::log<phosphor::logging::level::ERR>(

+                    "async_method_call error: ListUnits failed");

+                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)

+            {

+                phosphor::logging::log<phosphor::logging::level::ERR>(

+                    "async_method_call error: ListUnits failed");

+                return;

+            }

+            if (std::get<uint64_t>(value))

+            {

+                if (!srvMgrObjects.size())

+                {

+                    init(server, conn);

+                }

+            }

+        },

+        sysdService, sysdObjPath, dBusPropIntf, dBusGetMethod, sysdMgrIntf,

+        "FinishTimestamp");

+}

 

 int main()

 {

-    // setup connection to dbus

     boost::asio::io_service io;

     auto conn = std::make_shared<sdbusplus::asio::connection>(io);

     timer = std::make_shared<boost::asio::deadline_timer>(io);

     conn->request_name(phosphor::service::serviceConfigSrvName);

     auto server = sdbusplus::asio::object_server(conn, true);

     auto mgrInterface =

-        server.add_interface("/xyz/openbmc_project/control/service", "");

+        server.add_interface(phosphor::service::srcCfgMgrBasePath, "");

     mgrInterface->initialize();

-    server.add_manager("/xyz/openbmc_project/control/service");

-    for (const auto &service : serviceList)

-    {

-        std::string objPath("/xyz/openbmc_project/control/service/" +

-                            service.first);

-        auto srvCfgObj = std::make_unique<phosphor::service::ServiceConfig>(

-            server, conn, objPath, service.second);

-        srvMgrObjects.emplace(

-            std::make_pair(std::move(service.first), std::move(srvCfgObj)));

-    }

+    server.add_manager(phosphor::service::srcCfgMgrBasePath);

+    // Initialize the objects after systemd indicated startup finished.

+    auto userUpdatedSignal = std::make_unique<sdbusplus::bus::match::match>(

+        static_cast<sdbusplus::bus::bus&>(*conn),

+        "type='signal',"

+        "member='StartupFinished',path='/org/freedesktop/systemd1',"

+        "interface='org.freedesktop.systemd1.Manager'",

+        [&server, &conn](sdbusplus::message::message& msg) {

+            if (!srvMgrObjects.size())

+            {

+                init(server, conn);

+            }

+        });

+    // this will make sure to initialize the objects, when daemon is

+    // restarted.

+    checkAndInit(server, conn);

+

     io.run();

 

     return 0;