Enable pre and post actions

This commit enables the VPD parser to take certain
pre and post collection actions.

-- Set a GPIO in order to enable hardware paths (such as I2C)
-- Bind device drivers to the I2C device so as to generate a udev
   event.
-- Set a GPIO as a post action on failure to collect VPD.

Pre-actions are taken when collecting system VPD.
Post actions can be taken either after a failure
to bind drivers or after failing to collect/parse VPD.

Change-Id: I26754000a72db53f00a5afc4925de27e3f7c3ba8
Signed-off-by: Alpana Kumari <alpankum@in.ibm.com>
diff --git a/ibm_vpd_app.cpp b/ibm_vpd_app.cpp
index 6241188..b09ed79 100644
--- a/ibm_vpd_app.cpp
+++ b/ibm_vpd_app.cpp
@@ -16,6 +16,7 @@
 #include <exception>
 #include <filesystem>
 #include <fstream>
+#include <gpiod.hpp>
 #include <iostream>
 #include <iterator>
 #include <nlohmann/json.hpp>
@@ -133,6 +134,7 @@
     }
     return expanded;
 }
+
 /**
  * @brief Populate FRU specific interfaces.
  *
@@ -250,7 +252,7 @@
     }
 }
 
-Binary getVpdDataInVector(nlohmann::json& js, const string& file)
+static Binary getVpdDataInVector(const nlohmann::json& js, const string& file)
 {
     uint32_t offset = 0;
     // check if offset present?
@@ -275,6 +277,172 @@
     return vpdVector;
 }
 
+/* It does nothing. Just an empty function to return null
+ * at the end of variadic template args
+ */
+static string getCommand()
+{
+    return "";
+}
+
+/* This function to arrange all arguments to make command
+ */
+template <typename T, typename... Types>
+static string getCommand(T arg1, Types... args)
+{
+    string cmd = " " + arg1 + getCommand(args...);
+
+    return cmd;
+}
+
+/* This API takes arguments and run that command
+ * returns output of that command
+ */
+template <typename T, typename... Types>
+static vector<string> executeCmd(T&& path, Types... args)
+{
+    vector<string> stdOutput;
+    array<char, 128> buffer;
+
+    string cmd = path + getCommand(args...);
+
+    unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
+    if (!pipe)
+    {
+        throw runtime_error("popen() failed!");
+    }
+    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
+    {
+        stdOutput.emplace_back(buffer.data());
+    }
+
+    return stdOutput;
+}
+
+/** This API will be called at the end of VPD collection to perform any post
+ * actions.
+ *
+ * @param[in] json - json object
+ * @param[in] file - eeprom file path
+ */
+static void postFailAction(const nlohmann::json& json, const string& file)
+{
+    if ((json["frus"][file].at(0)).find("postActionFail") ==
+        json["frus"][file].at(0).end())
+    {
+        return;
+    }
+
+    uint8_t pinValue = 0;
+    string pinName;
+
+    for (const auto& postAction :
+         (json["frus"][file].at(0))["postActionFail"].items())
+    {
+        if (postAction.key() == "pin")
+        {
+            pinName = postAction.value();
+        }
+        else if (postAction.key() == "value")
+        {
+            // Get the value to set
+            pinValue = postAction.value();
+        }
+    }
+
+    cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl;
+
+    try
+    {
+        gpiod::line outputLine = gpiod::find_line(pinName);
+
+        if (!outputLine)
+        {
+            cout << "Couldn't find output line:" << pinName
+                 << " on GPIO. Skipping...\n";
+
+            return;
+        }
+        outputLine.request(
+            {"Disable line", ::gpiod::line_request::DIRECTION_OUTPUT, 0},
+            pinValue);
+    }
+    catch (system_error&)
+    {
+        cerr << "Failed to set post-action GPIO" << endl;
+    }
+}
+
+/** Performs any pre-action needed to get the FRU setup for collection.
+ *
+ * @param[in] json - json object
+ * @param[in] file - eeprom file path
+ */
+static void preAction(const nlohmann::json& json, const string& file)
+{
+    if ((json["frus"][file].at(0)).find("preAction") ==
+        json["frus"][file].at(0).end())
+    {
+        return;
+    }
+
+    uint8_t pinValue = 0;
+    string pinName;
+
+    for (const auto& postAction :
+         (json["frus"][file].at(0))["preAction"].items())
+    {
+        if (postAction.key() == "pin")
+        {
+            pinName = postAction.value();
+        }
+        else if (postAction.key() == "value")
+        {
+            // Get the value to set
+            pinValue = postAction.value();
+        }
+    }
+
+    cout << "Setting GPIO: " << pinName << " to " << (int)pinValue << endl;
+    try
+    {
+        gpiod::line outputLine = gpiod::find_line(pinName);
+
+        if (!outputLine)
+        {
+            cout << "Couldn't find output line:" << pinName
+                 << " on GPIO. Skipping...\n";
+
+            return;
+        }
+        outputLine.request(
+            {"FRU pre-action", ::gpiod::line_request::DIRECTION_OUTPUT, 0},
+            pinValue);
+    }
+    catch (system_error&)
+    {
+        cerr << "Failed to set pre-action GPIO" << endl;
+        return;
+    }
+
+    // Now bind the device
+    string bind = json["frus"][file].at(0).value("bind", "");
+    cout << "Binding device " << bind << endl;
+    string bindCmd = string("echo \"") + bind +
+                     string("\" > /sys/bus/i2c/drivers/at24/bind");
+    cout << bindCmd << endl;
+    executeCmd(bindCmd);
+
+    // Check if device showed up (test for file)
+    if (!fs::exists(file))
+    {
+        cout << "EEPROM " << file << " does not exist. Take failure action"
+             << endl;
+        // If not, then take failure postAction
+        postFailAction(json, file);
+    }
+}
+
 /**
  * @brief Prime the Inventory
  * Prime the inventory by populating only the location code,
@@ -294,6 +462,8 @@
 
     for (auto& itemFRUS : jsObject["frus"].items())
     {
+        // Take pre actions
+        preAction(jsObject, itemFRUS.key());
         for (auto& itemEEPROM : itemFRUS.value())
         {
             inventory::InterfaceMap interfaces;
@@ -338,48 +508,6 @@
     return objects;
 }
 
-/* It does nothing. Just an empty function to return null
- * at the end of variadic template args
- */
-string getCommand()
-{
-    return "";
-}
-
-/* This function to arrange all arguments to make command
- */
-template <typename T, typename... Types>
-string getCommand(T arg1, Types... args)
-{
-    string cmd = " " + arg1 + getCommand(args...);
-
-    return cmd;
-}
-
-/* This API takes arguments and run that command
- * returns output of that command
- */
-template <typename T, typename... Types>
-static vector<string> executeCmd(T& path, Types... args)
-{
-    vector<string> stdOutput;
-    array<char, 128> buffer;
-
-    string cmd = path + getCommand(args...);
-
-    unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"), pclose);
-    if (!pipe)
-    {
-        throw runtime_error("popen() failed!");
-    }
-    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
-    {
-        stdOutput.emplace_back(buffer.data());
-    }
-
-    return stdOutput;
-}
-
 /**
  * @brief This API executes command to set environment variable
  *        And then reboot the system
@@ -619,8 +747,7 @@
         string file{};
 
         app.add_option("-f, --file", file, "File containing VPD (IPZ/KEYWORD)")
-            ->required()
-            ->check(ExistingFile);
+            ->required();
 
         CLI11_PARSE(app, argc, argv);
 
@@ -657,6 +784,13 @@
             return 0;
         }
 
+        if (!fs::exists(file))
+        {
+            cout << "Device path: " << file
+                 << " does not exist. Spurious udev event? Exiting." << endl;
+            return 0;
+        }
+
         baseFruInventoryPath = js["frus"][file][0]["inventoryPath"];
         // Check if we can read the VPD file based on the power state
         if (js["frus"][file].at(0).value("powerOffOnly", false))
@@ -675,17 +809,30 @@
         variant<KeywordVpdMap, Store> parseResult;
         parseResult = parser->parse();
 
-        if (auto pVal = get_if<Store>(&parseResult))
+        try
         {
-            populateDbus(pVal->getVpdMap(), js, file);
-        }
-        else if (auto pVal = get_if<KeywordVpdMap>(&parseResult))
-        {
-            populateDbus(*pVal, js, file);
-        }
+            Binary vpdVector = getVpdDataInVector(js, file);
+            ParserInterface* parser = ParserFactory::getParser(move(vpdVector));
 
-        // release the parser object
-        ParserFactory::freeParser(parser);
+            variant<KeywordVpdMap, Store> parseResult;
+            parseResult = parser->parse();
+            if (auto pVal = get_if<Store>(&parseResult))
+            {
+                populateDbus(pVal->getVpdMap(), js, file);
+            }
+            else if (auto pVal = get_if<KeywordVpdMap>(&parseResult))
+            {
+                populateDbus(*pVal, js, file);
+            }
+
+            // release the parser object
+            ParserFactory::freeParser(parser);
+        }
+        catch (exception& e)
+        {
+            postFailAction(js, file);
+            throw e;
+        }
     }
     catch (const VpdJsonException& ex)
     {
diff --git a/meson.build b/meson.build
index 1b7e271..32775ad 100644
--- a/meson.build
+++ b/meson.build
@@ -44,6 +44,7 @@
   )
 
 if get_option('ibm-parser').enabled()
+        libgpiodcxx = dependency('libgpiodcxx')
         ibm_read_vpd_SOURCES = ['ibm_vpd_app.cpp',
                                 'vpd-parser/ipz_parser.cpp',
                                 'impl.cpp',
@@ -61,6 +62,7 @@
                                 dependencies: [
                                         sdbusplus,
                                         phosphor_logging,
+                                        libgpiodcxx,
                                 ],
                                 include_directories : 'vpd-parser/',
                                 install: true,