Do not collect VPD unless required

This commit adds some checks before we attempt VPD collection
on udev events. The following conditions are added:

-- Check if the FRU is marked concurrently maintainable or pluggable at
standby. If either is true, proceed.

-- Check if the BMC is at a NotReady state - if yes, proceeed.

-- Check if the FRU has never been colelcted before (Present is false) -
if yes, proceed.

In all other scenarios, the collection is skipped. This helps eliminate
cases where grabbing the SPI mux causes a whole bunch of parsers to run,
and an immediate power on leads to I2C arbitration loss errors.

Signed-off-by: Santosh Puranik <santosh.puranik@in.ibm.com>
Change-Id: I8f92014e1fc0becf5f7c56019a31bb2e46c6ccf0
diff --git a/ibm_vpd_app.cpp b/ibm_vpd_app.cpp
index 613f4af..ba03318 100644
--- a/ibm_vpd_app.cpp
+++ b/ibm_vpd_app.cpp
@@ -77,6 +77,109 @@
 }
 
 /**
+ * @brief Returns the BMC state
+ */
+static auto getBMCState()
+{
+    std::string bmcState;
+    try
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto properties = bus.new_method_call(
+            "xyz.openbmc_project.State.BMC", "/xyz/openbmc_project/state/bmc0",
+            "org.freedesktop.DBus.Properties", "Get");
+        properties.append("xyz.openbmc_project.State.BMC");
+        properties.append("CurrentBMCState");
+        auto result = bus.call(properties);
+        std::variant<std::string> val;
+        result.read(val);
+        if (auto pVal = std::get_if<std::string>(&val))
+        {
+            bmcState = *pVal;
+        }
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        // Ignore any error
+        std::cerr << "Failed to get BMC state: " << e.what() << "\n";
+    }
+    return bmcState;
+}
+
+/**
+ * @brief Check if the FRU is in the cache
+ *
+ * Checks if the FRU associated with the supplied D-Bus object path is already
+ * on D-Bus. This can be used to test if a VPD collection is required for this
+ * FRU. It uses the "xyz.openbmc_project.Inventory.Item, Present" property to
+ * determine the presence of a FRU in the cache.
+ *
+ * @param objectPath - The D-Bus object path without the PIM prefix.
+ * @return true if the object exists on D-Bus, false otherwise.
+ */
+static auto isFruInVpdCache(const std::string& objectPath)
+{
+    try
+    {
+        auto bus = sdbusplus::bus::new_default();
+        auto invPath = std::string{pimPath} + objectPath;
+        auto props = bus.new_method_call(
+            "xyz.openbmc_project.Inventory.Manager", invPath.c_str(),
+            "org.freedesktop.DBus.Properties", "Get");
+        props.append("xyz.openbmc_project.Inventory.Item");
+        props.append("Present");
+        auto result = bus.call(props);
+        std::variant<bool> present;
+        result.read(present);
+        if (auto pVal = std::get_if<bool>(&present))
+        {
+            return *pVal;
+        }
+        return false;
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        std::cout << "FRU: " << objectPath << " not in D-Bus\n";
+        // Assume not present in case of an error
+        return false;
+    }
+}
+
+/**
+ * @brief Check if VPD recollection is needed for the given EEPROM
+ *
+ * Not all FRUs can be swapped at BMC ready state. This function does the
+ * following:
+ * -- Check if the FRU is marked as "pluggableAtStandby" OR
+ *    "concurrentlyMaintainable". If so, return true.
+ * -- Check if we are at BMC NotReady state. If we are, then return true.
+ * -- Else check if the FRU is not present in the VPD cache (to cover for VPD
+ *    force collection). If not found in the cache, return true.
+ * -- Else return false.
+ *
+ * @param js - JSON Object.
+ * @param filePath - The EEPROM file.
+ * @return true if collection should be attempted, false otherwise.
+ */
+static auto needsRecollection(const nlohmann::json& js, const string& filePath)
+{
+    if (js["frus"][filePath].at(0).value("pluggableAtStandby", false) ||
+        js["frus"][filePath].at(0).value("concurrentlyMaintainable", false))
+    {
+        return true;
+    }
+    if (getBMCState() == "xyz.openbmc_project.State.BMC.BMCState.NotReady")
+    {
+        return true;
+    }
+    if (!isFruInVpdCache(js["frus"][filePath].at(0).value("inventoryPath", "")))
+    {
+        return true;
+    }
+    return false;
+}
+
+/**
  * @brief Expands location codes
  */
 static auto expandLocationCode(const string& unexpanded, const Parsed& vpdMap,
@@ -1419,6 +1522,13 @@
             }
         }
 
+        // Check if this VPD should be recollected at all
+        if (!needsRecollection(js, file))
+        {
+            cout << "Skip VPD recollection for: " << file << endl;
+            return 0;
+        }
+
         try
         {
             vpdVector = getVpdDataInVector(js, file);