functions: Parse json file to set bios attr table

The elements.json file have entries as follow:
    "lids": [
        {
            "element_name": "HBD.RAINIER_2U_XML",
            "short_lid_name": "81e00630",
        },
        {
            "element_name": "HBD.RAINIER_2U_XML.iplTime",
            "short_lid_name": "81e0068d",
        },
        {
            "element_name": "NVRAM.P10",
            "short_lid_name": "81e0066b",
        },
        {
            "element_name": "HDAT.RAINIER_4U_XML",
            "short_lid_name": "81e00675",
        },

Need to parse the element name and lid name depending on the system. The
.P10 files are common to all P10 systems, the .<system> applies only to
the system name provided by Entity Manager. The .iplTIme takes
precedence and should be the lid number that it's picked up, so use []
instead of insert to overwrite an entry if it had already being set.

In the example above, the string for a rainier 2U should look like:
HBD=81e0068d,NVRAM=81e0066b.

Change-Id: I55b317ffef6a7fbe3fd5ee92e7f0188d831b7972
Signed-off-by: Adriana Kobylak <anoo@us.ibm.com>
diff --git a/functions.cpp b/functions.cpp
index 2b6fbad..40bfc83 100644
--- a/functions.cpp
+++ b/functions.cpp
@@ -6,6 +6,7 @@
 
 #include "functions.hpp"
 
+#include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/bus/match.hpp>
@@ -14,6 +15,7 @@
 #include <sdeventplus/event.hpp>
 
 #include <filesystem>
+#include <fstream>
 #include <functional>
 #include <iostream>
 #include <map>
@@ -196,13 +198,113 @@
 }
 
 /**
- * @brief Set the bios attribute table with details of the host firmware data
- * for this system.
+ * @brief Parse the elements json file and construct a string with the data to
+ *        be used to update the bios attribute table.
+ *
+ * @param[in] elementsJsonFilePath - The path to the host firmware json file.
+ * @param[in] extensions - The extensions of the firmware blob files.
  */
-void setBiosAttr()
+std::string getBiosAttrStr(const std::filesystem::path& elementsJsonFilePath,
+                           const std::vector<std::string>& extensions)
 {
     std::string biosAttrStr{};
 
+    std::ifstream jsonFile(elementsJsonFilePath.c_str());
+    if (!jsonFile)
+    {
+        return {};
+    }
+
+    std::map<std::string, std::string> attr;
+    auto data = nlohmann::json::parse(jsonFile, nullptr, false);
+    if (data.is_discarded())
+    {
+        log<level::ERR>("Error parsing JSON file",
+                        entry("FILE=%s", elementsJsonFilePath.c_str()));
+        return {};
+    }
+
+    // .get requires a non-const iterator
+    for (auto& iter : data["lids"])
+    {
+        std::string name{};
+        std::string lid{};
+
+        try
+        {
+            name = iter["element_name"].get<std::string>();
+            lid = iter["short_lid_name"].get<std::string>();
+        }
+        catch (std::exception& e)
+        {
+            // Possibly the element or lid name field was not found
+            log<level::ERR>("Error reading JSON field",
+                            entry("FILE=%s", elementsJsonFilePath.c_str()),
+                            entry("ERROR=%s", e.what()));
+            continue;
+        }
+
+        // The elements with the ipl extension have higher priority. Therefore
+        // Use operator[] to overwrite value if an entry for it already exists.
+        // Ex: if the JSON contains an entry A.P10 followed by A.P10.iplTime,
+        // the lid value for the latter one will be overwrite the value of the
+        // first one.
+        constexpr auto iplExtension = ".iplTime";
+        std::filesystem::path path(name);
+        if (path.extension() == iplExtension)
+        {
+            // Some elements have an additional extension, ex: .P10.iplTime
+            // Strip off the ipl extension with stem(), then check if there is
+            // an additional extension with extension().
+            if (!path.stem().extension().empty())
+            {
+                // Check if the extension matches the extensions for this system
+                if (std::find(extensions.begin(), extensions.end(),
+                              path.stem().extension()) == extensions.end())
+                {
+                    continue;
+                }
+            }
+            // Get the element name without extensions by calling stem() twice
+            // since stem() returns the base name if no periods are found.
+            // Therefore both "element.P10" and "element.P10.iplTime" would
+            // become "element".
+            attr[path.stem().stem()] = lid;
+            continue;
+        }
+
+        // Process all other extensions. The extension should match the list of
+        // supported extensions for this system. Use .insert() to only add
+        // entries that do not exist, so to not overwrite the values that may
+        // had been added that had the ipl extension.
+        if (std::find(extensions.begin(), extensions.end(), path.extension()) !=
+            extensions.end())
+        {
+            attr.insert({path.stem(), lid});
+        }
+    }
+    for (const auto& a : attr)
+    {
+        // Build the bios attribute string with format:
+        // "element1=lid1,element2=lid2,elementN=lidN,"
+        biosAttrStr += a.first + "=" + a.second + ",";
+    }
+
+    return biosAttrStr;
+}
+
+/**
+ * @brief Set the bios attribute table with details of the host firmware data
+ * for this system.
+ *
+ * @param[in] elementsJsonFilePath - The path to the host firmware json file.
+ * @param[in] extentions - The extensions of the firmware blob files.
+ */
+void setBiosAttr(const std::filesystem::path& elementsJsonFilePath,
+                 const std::vector<std::string>& extensions)
+{
+    auto biosAttrStr = getBiosAttrStr(elementsJsonFilePath, extensions);
+
     constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
     constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
     constexpr auto dbusAttrName = "hb_lid_ids";
@@ -370,18 +472,20 @@
  * @param[in] extensionMap a map of
  * xyz.openbmc_project.Configuration.IBMCompatibleSystem to host firmware blob
  * file extensions.
+ * @param[in] elementsJsonFilePath The file path to the json file
  * @param[in] ibmCompatibleSystem The names property of an instance of
  * xyz.openbmc_project.Configuration.IBMCompatibleSystem
  */
 void maybeSetBiosAttr(
     const std::map<std::string, std::vector<std::string>>& extensionMap,
+    const std::filesystem::path& elementsJsonFilePath,
     const std::vector<std::string>& ibmCompatibleSystem)
 {
     std::vector<std::string> extensions;
     if (getExtensionsForIbmCompatibleSystem(extensionMap, ibmCompatibleSystem,
                                             extensions))
     {
-        setBiosAttr();
+        setBiosAttr(elementsJsonFilePath, extensions);
     }
 }
 
@@ -524,16 +628,20 @@
  * @param[in] bus - D-Bus client connection.
  * @param[in] extensionMap - Map of IBMCompatibleSystem names and host firmware
  *                           file extensions.
+ * @param[in] elementsJsonFilePath - The Path to the json file
  * @param[in] loop - Program event loop.
  * @return nullptr
  */
 std::shared_ptr<void> updateBiosAttrTable(
     sdbusplus::bus::bus& bus,
     std::map<std::string, std::vector<std::string>> extensionMap,
-    sdeventplus::Event& loop)
+    std::filesystem::path elementsJsonFilePath, sdeventplus::Event& loop)
 {
     auto pExtensionMap =
         std::make_shared<decltype(extensionMap)>(std::move(extensionMap));
+    auto pElementsJsonFilePath =
+        std::make_shared<decltype(elementsJsonFilePath)>(
+            std::move(elementsJsonFilePath));
 
     auto getManagedObjects = bus.new_method_call(
         "xyz.openbmc_project.EntityManager", "/",
@@ -551,8 +659,9 @@
     catch (const sdbusplus::exception::SdBusError& e)
     {}
 
-    auto maybeSetAttrWithArgsBound = std::bind(
-        maybeSetBiosAttr, std::cref(*pExtensionMap), std::placeholders::_1);
+    auto maybeSetAttrWithArgsBound =
+        std::bind(maybeSetBiosAttr, std::cref(*pExtensionMap),
+                  std::cref(*pElementsJsonFilePath), std::placeholders::_1);
 
     for (const auto& pair : objects)
     {
diff --git a/functions.hpp b/functions.hpp
index b364393..bd4007d 100644
--- a/functions.hpp
+++ b/functions.hpp
@@ -48,6 +48,6 @@
 std::shared_ptr<void>
     updateBiosAttrTable(sdbusplus::bus::bus&,
                         std::map<std::string, std::vector<std::string>>,
-                        sdeventplus::Event&);
+                        std::filesystem::path, sdeventplus::Event&);
 } // namespace process_hostfirmware
 } // namespace functions
diff --git a/item_updater_main.cpp b/item_updater_main.cpp
index 21164e9..c6a8175 100644
--- a/item_updater_main.cpp
+++ b/item_updater_main.cpp
@@ -91,9 +91,11 @@
                            "Update the bios attribute table with the host "
                            "firmware data details.")
             ->callback([&bus, &loop, &subcommandContext, extensionMap]() {
+                auto elementsJsonFilePath = "/usr/share/hostfw/elements.json"s;
                 subcommandContext.push_back(
                     functions::process_hostfirmware::updateBiosAttrTable(
-                        bus, extensionMap, loop));
+                        bus, extensionMap, std::move(elementsJsonFilePath),
+                        loop));
             }));
 
     CLI11_PARSE(app, argc, argv);