led-use-json: Use specific config files

- When enabled `led-use-json`, it should use the JsonConfig class to
  find the JSON file name, and do not start until JSON config is
  found.

- If it has to wait for the IBMCompatible interface to show up on
  D-bus first, it will wait for that.

Tested:
- If there is a `/etc/phosphor-led-manager/led-group-config.json`
  path, congfile=/etc/phosphor-led-manager/led-group-config.json
- If there is a `/usr/share/phosphor-led-manager/led-group-config.json`
  path and there is not `/etc/phosphor-led-manager/`,
  congfile=/usr/share/phosphor-led-manager/led-group-config.json
- If the above two paths do not exist, get the `Name` property of the
  `xyz.openbmc_project.Configuration.IBMCompatibleSystem` interface,
  congfile=/usr/share/phosphor-led-manager/${Name}/led-group-config.json

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I1528a3d3711ef6b604868387ad42cb3c0afde119
diff --git a/json-config.hpp b/json-config.hpp
index e0448c5..462a84e 100644
--- a/json-config.hpp
+++ b/json-config.hpp
@@ -1,147 +1,229 @@
-#include "config.h"
+#include "utils.hpp"
 
-#include "ledlayout.hpp"
-
-#include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdeventplus/event.hpp>
 
 #include <filesystem>
 #include <fstream>
-#include <iostream>
 
 namespace fs = std::filesystem;
 
-using Json = nlohmann::json;
-using LedAction = std::set<phosphor::led::Layout::LedAction>;
-using LedMap = std::map<std::string, LedAction>;
+using namespace phosphor::logging;
 
-// Priority for a particular LED needs to stay SAME across all groups
-// phosphor::led::Layout::Action can only be one of `Blink` and `On`
-using PriorityMap = std::map<std::string, phosphor::led::Layout::Action>;
-
-/** @brief Parse LED JSON file and output Json object
- *
- *  @param[in] path - path of LED JSON file
- *
- *  @return const Json - Json object
- */
-const Json readJson(const fs::path& path)
+namespace phosphor
 {
-    using namespace phosphor::logging;
+namespace led
+{
 
-    if (!fs::exists(path) || fs::is_empty(path))
+static constexpr auto confFileName = "led-group-config.json";
+static constexpr auto confOverridePath = "/etc/phosphor-led-manager";
+static constexpr auto confBasePath = "/usr/share/phosphor-led-manager";
+static constexpr auto confCompatibleInterface =
+    "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
+static constexpr auto confCompatibleProperty = "Names";
+
+class JsonConfig
+{
+  public:
+    /**
+     * @brief Constructor
+     *
+     * Looks for the JSON config file.  If it can't find one, then it
+     * will watch entity-manager for the IBMCompatibleSystem interface
+     * to show up.
+     *
+     * @param[in] bus       - The D-Bus object
+     * @param[in] event     - sd event handler
+     */
+    JsonConfig(sdbusplus::bus::bus& bus, sdeventplus::Event& event) :
+        event(event)
     {
-        log<level::ERR>("Incorrect File Path or empty file",
-                        entry("FILE_PATH=%s", path.c_str()));
-        throw std::runtime_error("Incorrect File Path or empty file");
+        match = std::make_unique<sdbusplus::server::match::match>(
+            bus,
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::sender(
+                    "xyz.openbmc_project.EntityManager"),
+            std::bind(&JsonConfig::ifacesAddedCallback, this,
+                      std::placeholders::_1));
+        getFilePath();
+
+        if (!confFile.empty())
+        {
+            match.reset();
+        }
     }
 
-    try
+    /**
+     * @brief Get the configuration file
+     *
+     * @return filesystem path
+     */
+    inline const fs::path& getConfFile() const
     {
-        std::ifstream jsonFile(path);
-        return Json::parse(jsonFile);
+        return confFile;
     }
-    catch (const std::exception& e)
+
+  private:
+    /** @brief Check the file path exists
+     *
+     *  @param[in]  names   -  Vector of the confCompatible Property
+     *
+     *  @return             -  True or False
+     */
+    bool filePathExists(const std::vector<std::string>& names)
     {
-        log<level::ERR>("Failed to parse config file",
+        auto it =
+            std::find_if(names.begin(), names.end(), [this](const auto& name) {
+                auto tempConfFile =
+                    fs::path{confBasePath} / name / confFileName;
+                if (fs::exists(tempConfFile))
+                {
+                    confFile = tempConfFile;
+                    return true;
+                }
+                return false;
+            });
+        return it == names.end() ? false : true;
+    }
+
+    /**
+     * @brief The interfacesAdded callback function that looks for
+     *        the IBMCompatibleSystem interface.  If it finds it,
+     *        it uses the Names property in the interface to find
+     *        the JSON config file to use.
+     *
+     * @param[in] msg - The D-Bus message contents
+     */
+    void ifacesAddedCallback(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        std::map<std::string,
+                 std::map<std::string, std::variant<std::vector<std::string>>>>
+            interfaces;
+
+        msg.read(path, interfaces);
+
+        if (interfaces.find(confCompatibleInterface) == interfaces.end())
+        {
+            return;
+        }
+
+        // Get the "Name" property value of the
+        // "xyz.openbmc_project.Configuration.IBMCompatibleSystem" interface
+        const auto& properties = interfaces.at(confCompatibleInterface);
+
+        if (properties.find(confCompatibleProperty) == properties.end())
+        {
+            return;
+        }
+        auto names = std::get<std::vector<std::string>>(
+            properties.at(confCompatibleProperty));
+
+        if (filePathExists(names))
+        {
+            match.reset();
+
+            // This results in event.loop() exiting in getSystemLedMap
+            event.exit(0);
+        }
+    }
+
+    /**
+     * Get the json configuration file. The first location found to contain the
+     * json config file from the following locations in order.
+     * confOverridePath: /etc/phosphor-led-manager/led-group-config.json
+     * confBasePath: /usr/shard/phosphor-led-manager/led-group-config.json
+     * the name property of the confCompatibleInterface:
+     * /usr/shard/phosphor-led-manager/${Name}/led-group-config.json
+     *
+     * @brief Get the configuration file to be used
+     *
+     * @return
+     */
+    const void getFilePath()
+    {
+        // Check override location
+        confFile = fs::path{confOverridePath} / confFileName;
+        if (fs::exists(confFile))
+        {
+            return;
+        }
+
+        // If the default file is there, use it
+        confFile = fs::path{confBasePath} / confFileName;
+        if (fs::exists(confFile))
+        {
+            return;
+        }
+        confFile.clear();
+
+        try
+        {
+            // Get all objects implementing the compatible interface
+            auto objects =
+                dBusHandler.getSubTreePaths("/", confCompatibleInterface);
+            for (const auto& path : objects)
+            {
+                try
+                {
+                    // Retrieve json config compatible relative path locations
+                    auto value = dBusHandler.getProperty(
+                        path, confCompatibleInterface, confCompatibleProperty);
+
+                    auto confCompatValues =
+                        std::get<std::vector<std::string>>(value);
+
+                    // Look for a config file at each name relative to the base
+                    // path and use the first one found
+                    if (filePathExists(confCompatValues))
+                    {
+                        // Use the first config file found at a listed location
+                        break;
+                    }
+                    confFile.clear();
+                }
+                catch (const sdbusplus::exception::SdBusError& e)
+                {
+                    // Property unavailable on object.
+                    log<level::ERR>(
+                        "Failed to get Names property",
                         entry("ERROR=%s", e.what()),
-                        entry("FILE_PATH=%s", path.c_str()));
-        throw std::runtime_error("Failed to parse config file");
-    }
-}
+                        entry("INTERFACE=%s", confCompatibleInterface),
+                        entry("PATH=%s", path.c_str()));
 
-/** @brief Returns action enum based on string
- *
- *  @param[in] action - action string
- *
- *  @return Action - action enum (On/Blink)
- */
-phosphor::led::Layout::Action getAction(const std::string& action)
-{
-    assert(action == "On" || action == "Blink");
-
-    return action == "Blink" ? phosphor::led::Layout::Blink
-                             : phosphor::led::Layout::On;
-}
-
-/** @brief Validate the Priority of an LED is same across ALL groups
- *
- *  @param[in] name - led name member of each group
- *  @param[in] priority - member priority of each group
- *  @param[out] priorityMap - std::map, key:name, value:priority
- *
- *  @return
- */
-void validatePriority(const std::string& name,
-                      const phosphor::led::Layout::Action& priority,
-                      PriorityMap& priorityMap)
-{
-    using namespace phosphor::logging;
-
-    auto iter = priorityMap.find(name);
-    if (iter == priorityMap.end())
-    {
-        priorityMap.emplace(name, priority);
+                    confFile.clear();
+                }
+            }
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            log<level::ERR>("Failed to call the SubTreePaths method",
+                            entry("ERROR=%s", e.what()),
+                            entry("INTERFACE=%s", confCompatibleInterface));
+        }
         return;
     }
 
-    if (iter->second != priority)
-    {
-        log<level::ERR>("Priority of LED is not same across all",
-                        entry("Name=%s", name.c_str()),
-                        entry(" Old Priority=%d", iter->second),
-                        entry(" New priority=%d", priority));
+  private:
+    /**
+     * @brief sd event handler.
+     */
+    sdeventplus::Event& event;
 
-        throw std::runtime_error(
-            "Priority of at least one LED is not same across groups");
-    }
-}
+    /**
+     * @brief The JSON config file
+     */
+    fs::path confFile;
 
-/** @brief Load JSON config and return led map
- *
- *  @return LedMap - Generated an std::map of LedAction
- */
-const LedMap loadJsonConfig(const fs::path& path)
-{
-    LedMap ledMap{};
-    PriorityMap priorityMap{};
+    /**
+     * @brief The interfacesAdded match that is used to wait
+     *        for the IBMCompatibleSystem interface to show up.
+     */
+    std::unique_ptr<sdbusplus::server::match::match> match;
 
-    // define the default JSON as empty
-    const Json empty{};
-    auto json = readJson(path);
-    auto leds = json.value("leds", empty);
-
-    for (const auto& entry : leds)
-    {
-        fs::path tmpPath(std::string{OBJPATH});
-        tmpPath /= entry.value("group", "");
-        auto objpath = tmpPath.string();
-        auto members = entry.value("members", empty);
-
-        LedAction ledActions{};
-        for (const auto& member : members)
-        {
-            auto name = member.value("Name", "");
-            auto action = getAction(member.value("Action", ""));
-            uint8_t dutyOn = member.value("DutyOn", 50);
-            uint16_t period = member.value("Period", 0);
-
-            // Since only have Blink/On and default priority is Blink
-            auto priority = getAction(member.value("Priority", "Blink"));
-
-            // Same LEDs can be part of multiple groups. However, their
-            // priorities across groups need to match.
-            validatePriority(name, priority, priorityMap);
-
-            phosphor::led::Layout::LedAction ledAction{name, action, dutyOn,
-                                                       period, priority};
-            ledActions.emplace(ledAction);
-        }
-
-        // Generated an std::map of LedGroupNames to std::set of LEDs
-        // containing the name and properties.
-        ledMap.emplace(objpath, ledActions);
-    }
-
-    return ledMap;
-}
+    /** DBusHandler class handles the D-Bus operations */
+    utils::DBusHandler dBusHandler;
+};
+} // namespace led
+} // namespace phosphor