VPD tool : dumpInventory & dumpObject

VPD tool has four options.

a) Dump Complete Inventory - no additional arguments are needed.
b) Dump Specific Object - by providing the object name.
c) Read the keyword - by providing the object name, record name and keyword to be read.
d) Write/Update the keyword - by providing the object name,  record name, keyword and the value to be updated.

{value - in ascii or hex & all the other arguments in string}

"--help" option provides details on how to use the above mentioned options.

This commit has implementation of dump inventory and dump specific object.

Output:

---------Dump Inventory---------
Not displaying the complete output.

root@rainier:/tmp# ./vpd-tool -i
[
    {
        "/system": {
            "LocationCode": "U9105.22A.SIMP10R",
            "Model": "9105-22A",
            "SerialNumber": "SIMP10R",
            "type": "xyz.openbmc_project.Inventory.Item.System"
        },
        "/system/chassis": {
            "LocationCode": "U78DA.ND1.1234567",
            "type": "xyz.openbmc_project.Inventory.Item.Chassis"
        },
        .
        .
        .
        .
       and so on..
]

---------Dump Object----------

root@rainier:/tmp# ./vpd-tool -o -O /system/chassis/motherboard/ebmc_card_bmc
[
    {
        "/system/chassis/motherboard/ebmc_card_bmc": {
            "CC": "6B58",
            "DR": "EBMC            ",
            "FN": "F191014",
            "LocationCode": "U78DA.ND1.1234567-P0-C5",
            "PN": "PN12345",
            "SN": "YL6B58010000",
            "type": "xyz.openbmc_project.Inventory.Item.Bmc"
        }
    }
]

Flag to enable VPD tool:
There is no seperate flag for VPD tool.
ibm-parser flag will generate the vpd-tool binary.

Steps to build and generate the executable using meson:
meson -Denabled=ibm-parser build
ninja -C build

Test:
Tested on simics.

Signed-off-by: PriyangaRamasamy <priyanga24@in.ibm.com>
Change-Id: I712f7ad4303eefa68f232685b6b0e53646f859f5
diff --git a/vpd_tool_impl.cpp b/vpd_tool_impl.cpp
new file mode 100644
index 0000000..ccc2765
--- /dev/null
+++ b/vpd_tool_impl.cpp
@@ -0,0 +1,234 @@
+#include "vpd_tool_impl.hpp"
+
+#include <iostream>
+#include <sdbusplus/bus.hpp>
+#include <sstream>
+#include <variant>
+#include <vector>
+
+using namespace std;
+using sdbusplus::exception::SdBusError;
+using namespace openpower::vpd;
+
+void VpdTool::debugger(json output)
+{
+    cout << output.dump(4) << '\n';
+}
+
+auto VpdTool::makeDBusCall(const string& objectName, const string& interface,
+                           const string& kw)
+{
+    auto bus = sdbusplus::bus::new_default();
+    auto properties =
+        bus.new_method_call(INVENTORY_MANAGER_SERVICE, objectName.c_str(),
+                            "org.freedesktop.DBus.Properties", "Get");
+    properties.append(interface);
+    properties.append(kw);
+    auto result = bus.call(properties);
+
+    if (result.is_method_error())
+    {
+        throw runtime_error("Get api failed");
+    }
+    return result;
+}
+
+void VpdTool::addFruTypeAndLocation(json exIntf, const string& object,
+                                    json& kwVal)
+{
+    for (const auto& intf : exIntf.items())
+    {
+        if ((intf.key().find("Item") != string::npos) &&
+            (intf.value().is_null()))
+        {
+            kwVal.emplace("type", intf.key());
+            break;
+        }
+    }
+
+    // Add location code.
+    constexpr auto LOCATION_CODE_IF = "com.ibm.ipzvpd.Location";
+    constexpr auto LOCATION_CODE_PROP = "LocationCode";
+
+    try
+    {
+        variant<string> response;
+        makeDBusCall(object, LOCATION_CODE_IF, LOCATION_CODE_PROP)
+            .read(response);
+
+        if (auto prop = get_if<string>(&response))
+        {
+            kwVal.emplace(LOCATION_CODE_PROP, *prop);
+        }
+    }
+    catch (const SdBusError& e)
+    {
+        kwVal.emplace(LOCATION_CODE_PROP, "");
+    }
+}
+
+json VpdTool::getVINIProperties(string invPath, json exIntf)
+{
+    variant<Binary> response;
+    json output = json::object({});
+    json kwVal = json::object({});
+
+    vector<string> keyword{"CC", "SN", "PN", "FN", "DR"};
+    string interface = "com.ibm.ipzvpd.VINI";
+    string objectName = INVENTORY_PATH + invPath;
+
+    for (string kw : keyword)
+    {
+        try
+        {
+            makeDBusCall(objectName, interface, kw).read(response);
+
+            if (auto vec = get_if<Binary>(&response))
+            {
+                kwVal.emplace(kw, string(vec->begin(), vec->end()));
+            }
+        }
+        catch (const SdBusError& e)
+        {
+            output.emplace(invPath, json::object({}));
+        }
+    }
+
+    addFruTypeAndLocation(exIntf, objectName, kwVal);
+    output.emplace(invPath, kwVal);
+    return output;
+}
+
+void VpdTool::getExtraInterfaceProperties(string invPath, string extraInterface,
+                                          json prop, json exIntf, json& output)
+{
+    variant<string> response;
+
+    string objectName = INVENTORY_PATH + invPath;
+
+    for (const auto& itProp : prop.items())
+    {
+        string kw = itProp.key();
+        try
+        {
+            makeDBusCall(objectName, extraInterface, kw).read(response);
+
+            if (auto str = get_if<string>(&response))
+            {
+                output.emplace(kw, *str);
+            }
+        }
+        catch (const SdBusError& e)
+        {
+            output.emplace(invPath, json::object({}));
+        }
+    }
+    addFruTypeAndLocation(exIntf, objectName, output);
+}
+
+json VpdTool::interfaceDecider(json& itemEEPROM)
+{
+    if (itemEEPROM.find("inventoryPath") == itemEEPROM.end())
+    {
+        throw runtime_error("Inventory Path not found");
+    }
+
+    if (itemEEPROM.find("extraInterfaces") == itemEEPROM.end())
+    {
+        throw runtime_error("Extra Interfaces not found");
+    }
+
+    bool exIntfCheck = false;
+    json output = json::object({});
+
+    if (itemEEPROM.value("inherit", true))
+    {
+        json j = getVINIProperties(itemEEPROM.at("inventoryPath"),
+                                   itemEEPROM["extraInterfaces"]);
+        output.insert(j.begin(), j.end());
+    }
+    else
+    {
+        json js;
+        for (const auto& ex : itemEEPROM["extraInterfaces"].items())
+        {
+            if (!(ex.value().is_null()))
+            {
+                exIntfCheck = true;
+                getExtraInterfaceProperties(itemEEPROM.at("inventoryPath"),
+                                            ex.key(), ex.value(),
+                                            itemEEPROM["extraInterfaces"], js);
+            }
+        }
+        output.emplace(itemEEPROM.at("inventoryPath"), js);
+    }
+    return output;
+}
+
+json VpdTool::parseInvJson(const json& jsObject, char flag, string fruPath)
+{
+    json output = json::object({});
+    bool validObject = false;
+
+    if (jsObject.find("frus") == jsObject.end())
+    {
+        throw runtime_error("Frus missing in Inventory json");
+    }
+    else
+    {
+        for (const auto& itemFRUS : jsObject["frus"].items())
+        {
+            for (auto itemEEPROM : itemFRUS.value())
+            {
+                try
+                {
+                    if (flag == 'O')
+                    {
+                        if (itemEEPROM.find("inventoryPath") ==
+                            itemEEPROM.end())
+                        {
+                            throw runtime_error("Inventory Path not found");
+                        }
+
+                        else if (itemEEPROM.at("inventoryPath") == fruPath)
+                        {
+                            validObject = true;
+                            json j = interfaceDecider(itemEEPROM);
+                            output.insert(j.begin(), j.end());
+                            return output;
+                        }
+                    }
+                    else
+                    {
+                        json j = interfaceDecider(itemEEPROM);
+                        output.insert(j.begin(), j.end());
+                    }
+                }
+                catch (exception& e)
+                {
+                    cerr << e.what();
+                }
+            }
+        }
+        if ((flag == 'O') && (!validObject))
+        {
+            throw runtime_error(
+                "Invalid object path. Refer --dumpInventory/-i option.");
+        }
+    }
+    return output;
+}
+
+void VpdTool::dumpInventory(const nlohmann::basic_json<>& jsObject)
+{
+    char flag = 'I';
+    json output = parseInvJson(jsObject, flag, "");
+    debugger(output);
+}
+
+void VpdTool::dumpObject(const nlohmann::basic_json<>& jsObject)
+{
+    char flag = 'O';
+    json output = parseInvJson(jsObject, flag, fruPath);
+    debugger(output);
+}