regulators: Obtain config file name from D-Bus

Determine the correct JSON configuration file name based on the values
in the D-Bus compatible systems interface.

Also support a default configuration file name for systems that do not
use the compatible systems interface.

Tested:
* Verified correct config file name found on Rainier system
  * When phosphor-regulators starts before entity-manager
  * When phosphor-regulators starts after entity-manager
* Verified correct config file name found on Everest system
  * When phosphor-regulators starts before entity-manager
  * When phosphor-regulators starts after entity-manager
* Error cases
* For full test plan see
  https://gist.github.com/smccarney/2dbc81aa55e3fa6250f0827eab62fff7

Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I98902116b55297085ca3c40ce48f40972c3a3827
diff --git a/phosphor-regulators/src/manager.cpp b/phosphor-regulators/src/manager.cpp
index 1bffe24..51c8944 100644
--- a/phosphor-regulators/src/manager.cpp
+++ b/phosphor-regulators/src/manager.cpp
@@ -22,11 +22,11 @@
 #include "rule.hpp"
 #include "utility.hpp"
 
-#include <sdbusplus/bus.hpp>
-
+#include <algorithm>
 #include <chrono>
 #include <exception>
-#include <stdexcept>
+#include <functional>
+#include <map>
 #include <tuple>
 #include <utility>
 #include <variant>
@@ -36,6 +36,18 @@
 
 namespace fs = std::filesystem;
 
+constexpr auto busName = "xyz.openbmc_project.Power.Regulators";
+constexpr auto managerObjPath = "/xyz/openbmc_project/power/regulators/manager";
+constexpr auto compatibleIntf =
+    "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
+constexpr auto compatibleNamesProp = "Names";
+
+/**
+ * Default configuration file name.  This is used when the system does not
+ * implement the D-Bus compatible interface.
+ */
+constexpr auto defaultConfigFileName = "config.json";
+
 /**
  * Standard configuration file directory.  This directory is part of the
  * firmware install image.  It contains the standard version of the config file.
@@ -49,30 +61,28 @@
 const fs::path testConfigFileDir{"/etc/phosphor-regulators"};
 
 Manager::Manager(sdbusplus::bus::bus& bus, const sdeventplus::Event& event) :
-    ManagerObject{bus, objPath, true}, bus{bus}, eventLoop{event}, services{bus}
+    ManagerObject{bus, managerObjPath, true}, bus{bus}, eventLoop{event},
+    services{bus}
 {
-    /* Temporarily comment out until D-Bus interface is defined and available.
-        // Subscribe to interfacesAdded signal for filename property
-        std::unique_ptr<sdbusplus::server::match::match> matchPtr =
-            std::make_unique<sdbusplus::server::match::match>(
-                bus,
-                sdbusplus::bus::match::rules::interfacesAdded(sysDbusObj).c_str(),
-                std::bind(std::mem_fn(&Manager::signalHandler), this,
-                          std::placeholders::_1));
-        signals.emplace_back(std::move(matchPtr));
+    // Subscribe to D-Bus interfacesAdded signal from Entity Manager.  This
+    // notifies us if the compatible interface becomes available later.
+    std::string matchStr = sdbusplus::bus::match::rules::interfacesAdded() +
+                           sdbusplus::bus::match::rules::sender(
+                               "xyz.openbmc_project.EntityManager");
+    std::unique_ptr<sdbusplus::server::match::match> matchPtr =
+        std::make_unique<sdbusplus::server::match::match>(
+            bus, matchStr,
+            std::bind(&Manager::interfacesAddedHandler, this,
+                      std::placeholders::_1));
+    signals.emplace_back(std::move(matchPtr));
 
-        // Attempt to get the filename property from dbus
-        setFileName(getFileNameDbus());
-    */
+    // Try to find compatible system types using D-Bus compatible interface.
+    // Note that it might not be supported on this system, or the service that
+    // provides the interface might not be running yet.
+    findCompatibleSystemTypes();
 
-    // Temporarily hard-code JSON config file name to first system that will use
-    // this application.  Remove this when D-Bus interface is available.
-    fileName = "ibm_rainier.json";
-
-    if (!fileName.empty())
-    {
-        loadConfigFile();
-    }
+    // Try to find and load the JSON configuration file
+    loadConfigFile();
 
     // Obtain dbus service name
     bus.request_name(busName);
@@ -98,6 +108,55 @@
     // fail the call(busctl) to this method
 }
 
+void Manager::interfacesAddedHandler(sdbusplus::message::message& msg)
+{
+    // Verify message is valid
+    if (!msg)
+    {
+        return;
+    }
+
+    try
+    {
+        // Read object path for object that was created or had interface added
+        sdbusplus::message::object_path objPath;
+        msg.read(objPath);
+
+        // Read the dictionary whose keys are interface names and whose values
+        // are dictionaries containing the interface property names and values
+        std::map<std::string,
+                 std::map<std::string, std::variant<std::vector<std::string>>>>
+            intfProp;
+        msg.read(intfProp);
+
+        // Find the compatible interface, if present
+        auto itIntf = intfProp.find(compatibleIntf);
+        if (itIntf != intfProp.cend())
+        {
+            // Find the Names property of the compatible interface, if present
+            auto itProp = itIntf->second.find(compatibleNamesProp);
+            if (itProp != itIntf->second.cend())
+            {
+                // Get value of Names property
+                auto propValue = std::get<0>(itProp->second);
+                if (!propValue.empty())
+                {
+                    // Store list of compatible system types
+                    compatibleSystemTypes = propValue;
+
+                    // Find and load JSON config file based on system types
+                    loadConfigFile();
+                }
+            }
+        }
+    }
+    catch (const std::exception&)
+    {
+        // Error trying to read interfacesAdded message.  One possible cause
+        // could be a property whose value is not a std::vector<std::string>.
+    }
+}
+
 void Manager::monitor(bool enable)
 {
     if (enable)
@@ -128,100 +187,100 @@
     }
 }
 
+void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
+                            const struct signalfd_siginfo* /*sigInfo*/)
+{
+    // Reload the JSON configuration file
+    loadConfigFile();
+}
+
 void Manager::timerExpired()
 {
     // TODO Analyze, refresh sensor status, and
     // collect/update telemetry for each regulator
 }
 
-void Manager::sighupHandler(sdeventplus::source::Signal& /*sigSrc*/,
-                            const struct signalfd_siginfo* /*sigInfo*/)
+void Manager::findCompatibleSystemTypes()
 {
-    if (!fileName.empty())
-    {
-        loadConfigFile();
-    }
-}
-
-void Manager::signalHandler(sdbusplus::message::message& msg)
-{
-    if (msg)
-    {
-        sdbusplus::message::object_path op;
-        msg.read(op);
-        if (static_cast<const std::string&>(op) != sysDbusPath)
-        {
-            // Object path does not match the path
-            return;
-        }
-
-        // An interfacesAdded signal returns a dictionary of interface
-        // names to a dictionary of properties and their values
-        // https://dbus.freedesktop.org/doc/dbus-specification.html
-        std::map<std::string, std::map<std::string, std::variant<std::string>>>
-            intfProp;
-        msg.read(intfProp);
-        auto itIntf = intfProp.find(sysDbusIntf);
-        if (itIntf == intfProp.cend())
-        {
-            // Interface not found on the path
-            return;
-        }
-        auto itProp = itIntf->second.find(sysDbusProp);
-        if (itProp == itIntf->second.cend())
-        {
-            // Property not found on the interface
-            return;
-        }
-        // Set fileName and call parse json function
-        setFileName(std::get<std::string>(itProp->second));
-        if (!fileName.empty())
-        {
-            loadConfigFile();
-        }
-    }
-}
-
-const std::string Manager::getFileNameDbus()
-{
-    std::string fileName = "";
     using namespace phosphor::power::util;
 
     try
     {
-        // Do not log an error when service or property are not found
-        auto service = getService(sysDbusPath, sysDbusIntf, bus, false);
-        if (!service.empty())
+        // Query object mapper for object paths that implement the compatible
+        // interface.  Returns a map of object paths to a map of services names
+        // to their interfaces.
+        DbusSubtree subTree = getSubTree(bus, "/xyz/openbmc_project/inventory",
+                                         compatibleIntf, 0);
+
+        // Get the first object path
+        auto objectIt = subTree.cbegin();
+        if (objectIt != subTree.cend())
         {
-            getProperty(sysDbusIntf, sysDbusProp, sysDbusPath, service, bus,
-                        fileName);
+            std::string objPath = objectIt->first;
+
+            // Get the first service name
+            auto serviceIt = objectIt->second.cbegin();
+            if (serviceIt != objectIt->second.cend())
+            {
+                std::string service = serviceIt->first;
+                if (!service.empty())
+                {
+                    // Get compatible system types property value
+                    getProperty(compatibleIntf, compatibleNamesProp, objPath,
+                                service, bus, compatibleSystemTypes);
+                }
+            }
         }
     }
-    catch (const sdbusplus::exception::SdBusError&)
+    catch (const std::exception&)
     {
-        // File name property not available on dbus
-        fileName = "";
+        // Compatible system types information is not available.  The current
+        // system might not support the interface, or the service that
+        // implements the interface might not be running yet.
     }
-
-    return fileName;
 }
 
 fs::path Manager::findConfigFile()
 {
-    // First look in the test directory
-    fs::path pathName{testConfigFileDir / fileName};
-    if (!fs::exists(pathName))
+    // Build list of possible base file names
+    std::vector<std::string> fileNames{};
+
+    // Add possible file names based on compatible system types (if any)
+    for (const std::string& systemType : compatibleSystemTypes)
     {
-        // Look in the standard directory
-        pathName = standardConfigFileDir / fileName;
-        if (!fs::exists(pathName))
+        // Replace all spaces and commas in system type name with underscores
+        std::string fileName{systemType};
+        std::replace(fileName.begin(), fileName.end(), ' ', '_');
+        std::replace(fileName.begin(), fileName.end(), ',', '_');
+
+        // Append .json suffix and add to list
+        fileName.append(".json");
+        fileNames.emplace_back(fileName);
+    }
+
+    // Add default file name for systems that don't use compatible interface
+    fileNames.emplace_back(defaultConfigFileName);
+
+    // Look for a config file with one of the possible base names
+    for (const std::string& fileName : fileNames)
+    {
+        // Check if file exists in test directory
+        fs::path pathName{testConfigFileDir / fileName};
+        if (fs::exists(pathName))
         {
-            throw std::runtime_error{"Configuration file does not exist: " +
-                                     pathName.string()};
+            return pathName;
+        }
+
+        // Check if file exists in standard directory
+        pathName = standardConfigFileDir / fileName;
+        if (fs::exists(pathName))
+        {
+            return pathName;
         }
     }
 
-    return pathName;
+    // No config file found; return empty path
+    return fs::path{};
 }
 
 void Manager::loadConfigFile()
@@ -230,19 +289,22 @@
     {
         // Find the absolute path to the config file
         fs::path pathName = findConfigFile();
+        if (!pathName.empty())
+        {
+            // Log info message in journal; config file path is important
+            services.getJournal().logInfo("Loading configuration file " +
+                                          pathName.string());
 
-        // Log info message in journal; config file path is important
-        services.getJournal().logInfo("Loading configuration file " +
-                                      pathName.string());
+            // Parse the config file
+            std::vector<std::unique_ptr<Rule>> rules{};
+            std::vector<std::unique_ptr<Chassis>> chassis{};
+            std::tie(rules, chassis) = config_file_parser::parse(pathName);
 
-        // Parse the config file
-        std::vector<std::unique_ptr<Rule>> rules{};
-        std::vector<std::unique_ptr<Chassis>> chassis{};
-        std::tie(rules, chassis) = config_file_parser::parse(pathName);
-
-        // Store config file information in a new System object.  The old System
-        // object, if any, is automatically deleted.
-        system = std::make_unique<System>(std::move(rules), std::move(chassis));
+            // Store config file information in a new System object.  The old
+            // System object, if any, is automatically deleted.
+            system =
+                std::make_unique<System>(std::move(rules), std::move(chassis));
+        }
     }
     catch (const std::exception& e)
     {
diff --git a/phosphor-regulators/src/manager.hpp b/phosphor-regulators/src/manager.hpp
index b52489e..6c77fb7 100644
--- a/phosphor-regulators/src/manager.hpp
+++ b/phosphor-regulators/src/manager.hpp
@@ -26,7 +26,6 @@
 #include <sdeventplus/source/signal.hpp>
 #include <sdeventplus/utility/timer.hpp>
 
-#include <algorithm>
 #include <filesystem>
 #include <memory>
 #include <string>
@@ -35,13 +34,6 @@
 namespace phosphor::power::regulators
 {
 
-constexpr auto busName = "xyz.openbmc_project.Power.Regulators";
-constexpr auto objPath = "/xyz/openbmc_project/power/regulators/manager";
-constexpr auto sysDbusObj = "/xyz/openbmc_project/inventory";
-constexpr auto sysDbusPath = "/xyz/openbmc_project/inventory/system";
-constexpr auto sysDbusIntf = "xyz.openbmc_project.Inventory.Item.System";
-constexpr auto sysDbusProp = "Identifier";
-
 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
 
 using ManagerObject = sdbusplus::server::object::object<
@@ -72,6 +64,13 @@
     void configure() override;
 
     /**
+     * Callback function to handle interfacesAdded D-Bus signals
+     *
+     * @param msg Expanded sdbusplus message data
+     */
+    void interfacesAddedHandler(sdbusplus::message::message& msg);
+
+    /**
      * Overridden manager object's monitor method
      *
      * @param enable Enable or disable regulator monitoring
@@ -79,11 +78,6 @@
     void monitor(bool enable) override;
 
     /**
-     * Timer expired callback function
-     */
-    void timerExpired();
-
-    /**
      * Callback function to handle receiving a HUP signal
      * to reload the configuration data.
      *
@@ -94,63 +88,47 @@
                        const struct signalfd_siginfo* sigInfo);
 
     /**
-     * Callback function to handle interfacesAdded dbus signals
-     *
-     * @param msg Expanded sdbusplus message data
+     * Timer expired callback function
      */
-    void signalHandler(sdbusplus::message::message& msg);
+    void timerExpired();
 
   private:
     /**
-     * Set the JSON configuration data filename
+     * Finds the list of compatible system types using D-Bus methods.
      *
-     * @param fName filename without `.json` extension
-     */
-    inline void setFileName(const std::string& fName)
-    {
-        fileName = fName;
-        if (!fileName.empty())
-        {
-            // Replace all spaces with underscores
-            std::replace(fileName.begin(), fileName.end(), ' ', '_');
-            fileName.append(".json");
-        }
-    };
-
-    /**
-     * Get the JSON configuration data filename from dbus
+     * This list is used to find the correct JSON configuration file for the
+     * current system.
      *
-     * @return JSON configuration data filename
+     * Note that some systems do not support the D-Bus compatible interface.
+     *
+     * If a list of compatible system types is found, it is stored in the
+     * compatibleSystemTypes data member.
      */
-    const std::string getFileNameDbus();
+    void findCompatibleSystemTypes();
 
     /**
      * Finds the JSON configuration file.
      *
-     * Looks for the config file in the test directory and standard directory.
+     * Looks for a configuration file based on the list of compatable system
+     * types.  If no file is found, looks for a file with the default name.
      *
-     * Throws an exception if the file cannot be found or a file system error
-     * occurs.
+     * Looks for the file in the test directory and standard directory.
      *
-     * The base name of the config file must have already been obtained and
-     * stored in the fileName data member.
+     * Throws an exception if an operating system error occurs while checking
+     * for the existance of a file.
      *
-     * @return absolute path to config file
+     * @return absolute path to config file, or an empty path if none found
      */
     std::filesystem::path findConfigFile();
 
     /**
      * Loads the JSON configuration file.
      *
-     * Looks for the config file in the test directory and standard directory.
+     * Looks for the config file using findConfigFile().
      *
      * If the config file is found, it is parsed and the resulting information
-     * is stored in the system data member.
-     *
-     * If the config file cannot be found or parsing fails, an error is logged.
-     *
-     * The base name of the config file must have already been obtained and
-     * stored in the fileName data member.
+     * is stored in the system data member.  If parsing fails, an error is
+     * logged.
      */
     void loadConfigFile();
 
@@ -180,9 +158,11 @@
     std::vector<std::unique_ptr<sdbusplus::bus::match::match>> signals{};
 
     /**
-     * JSON configuration file base name.
+     * List of compatible system types for the current system.
+     *
+     * Used to find the JSON configuration file.
      */
-    std::string fileName{};
+    std::vector<std::string> compatibleSystemTypes{};
 
     /**
      * Computer system being controlled and monitored by the BMC.