functions: bios-attr: Add match for PLDM

Add a match to watch for PLDM name owner change to detect when PLDM
starts. The callback would check if the new owner is set, which means
the service started instead of stopped, and then gets the properties
from entity manager to call the function that parse the json file and
sets the bios attr. If the entity manager interfaces are empty, it just
returns and the entity manager callback would take over when entity
manager starts.

This bios attr subcommand is now a vector to be able to return the two
matches, the one for EM and the new PLDM one.

Tested: Verify the bios attr was set when EM and PLDM were stopped and
EM was started then PLDM, and also having PLDM start first then EM.

Change-Id: I75fe1aae8084191226a8bd3b69087ca39ae2a43f
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/functions.cpp b/functions.cpp
index 373dcc1..f35ab9c 100644
--- a/functions.cpp
+++ b/functions.cpp
@@ -30,6 +30,11 @@
 {
 
 using namespace phosphor::logging;
+using InterfacesPropertiesMap =
+    std::map<std::string,
+             std::map<std::string, std::variant<std::vector<std::string>>>>;
+using ManagedObjectType =
+    std::map<sdbusplus::message::object_path, InterfacesPropertiesMap>;
 
 /**
  * @brief GetObject function to find the service given an object path.
@@ -63,6 +68,30 @@
 }
 
 /**
+ * @brief Returns the managed objects for a given service
+ */
+ManagedObjectType getManagedObjects(sdbusplus::bus::bus& bus,
+                                    const std::string& service)
+{
+    auto method = bus.new_method_call(service.c_str(), "/",
+                                      "org.freedesktop.DBus.ObjectManager",
+                                      "GetManagedObjects");
+
+    ManagedObjectType objects;
+
+    try
+    {
+        auto reply = bus.call(method);
+        reply.read(objects);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        return ManagedObjectType{};
+    }
+    return objects;
+}
+
+/**
  * @brief Issue callbacks safely
  *
  * std::function can be empty, so this wrapper method checks for that prior to
@@ -663,12 +692,14 @@
  * @param[in] loop - Program event loop.
  * @return nullptr
  */
-std::shared_ptr<void> updateBiosAttrTable(
+std::vector<std::shared_ptr<void>> updateBiosAttrTable(
     sdbusplus::bus::bus& bus,
     std::map<std::string, std::vector<std::string>> extensionMap,
     std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
 {
     constexpr auto pldmPath = "/xyz/openbmc_project/pldm";
+    constexpr auto entityManagerServiceName =
+        "xyz.openbmc_project.EntityManager";
 
     auto pExtensionMap =
         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
@@ -682,7 +713,8 @@
         std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
                   std::cref(*pElementsJsonFilePath), std::placeholders::_1);
 
-    auto interfacesAddedMatch = std::make_shared<sdbusplus::bus::match::match>(
+    std::vector<std::shared_ptr<void>> matches;
+    matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
         bus,
         sdbusplus::bus::match::rules::interfacesAdded() +
             sdbusplus::bus::match::rules::sender(
@@ -699,43 +731,59 @@
             {
                 loop.exit(0);
             }
-        });
+        }));
+    matches.emplace_back(std::make_shared<sdbusplus::bus::match::match>(
+        bus,
+        sdbusplus::bus::match::rules::nameOwnerChanged() +
+            sdbusplus::bus::match::rules::arg0namespace(
+                "xyz.openbmc_project.PLDM"),
+        [pExtensionMap, pElementsJsonFilePath, maybeSetAttrWithArgsBound,
+         &loop](auto& message) {
+            std::string name;
+            std::string oldOwner;
+            std::string newOwner;
+            message.read(name, oldOwner, newOwner);
+
+            if (newOwner.empty())
+            {
+                return;
+            }
+
+            auto bus = sdbusplus::bus::new_default();
+            InterfacesPropertiesMap interfacesAndProperties;
+            auto objects = getManagedObjects(bus, entityManagerServiceName);
+            for (const auto& pair : objects)
+            {
+                std::tie(std::ignore, interfacesAndProperties) = pair;
+                if (maybeCall(interfacesAndProperties,
+                              maybeSetAttrWithArgsBound))
+                {
+                    loop.exit(0);
+                }
+            }
+        }));
 
     // The BIOS attribute table can only be updated if PLDM is running because
     // PLDM is the one that exposes this property. Return if it's not running.
     auto pldmObject = getObject(bus, pldmPath);
     if (pldmObject.empty())
     {
-        return interfacesAddedMatch;
+        return matches;
     }
 
-    auto getManagedObjects = bus.new_method_call(
-        "xyz.openbmc_project.EntityManager", "/",
-        "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
-    std::map<std::string,
-             std::map<std::string, std::variant<std::vector<std::string>>>>
-        interfacesAndProperties;
-    std::map<sdbusplus::message::object_path, decltype(interfacesAndProperties)>
-        objects;
-    try
-    {
-        auto reply = bus.call(getManagedObjects);
-        reply.read(objects);
-    }
-    catch (const sdbusplus::exception::SdBusError& e)
-    {}
-
+    InterfacesPropertiesMap interfacesAndProperties;
+    auto objects = getManagedObjects(bus, entityManagerServiceName);
     for (const auto& pair : objects)
     {
         std::tie(std::ignore, interfacesAndProperties) = pair;
         if (maybeCall(interfacesAndProperties, maybeSetAttrWithArgsBound))
         {
             loop.exit(0);
-            return nullptr;
+            return {};
         }
     }
 
-    return interfacesAddedMatch;
+    return matches;
 }
 
 } // namespace process_hostfirmware