Sync keyword update to inherited FRUs

This commit syncs keyword update to all inherited FRUs.
When a keyword value is updated either from vpd-tool or from vpd-manager
WriteKeyword API, the keyword update should also be synced to all
inherited FRUs on PIM to prevent mismatch.

Test:
```
Tested on a rainier 2s2u simics setup.

1. Ensure BMC is in Ready state
2. Read VINI PN of /system/chassis/motherboard using vpd-tool
3. Update VINI PN of /system/chassis/motherboard using vpd-tool
4. Once vpd-tool returns success, read VINI PN of inherited FRUs
   /system/chassis and /system/chassis/motherboard/tod_battery
5. VINI PN value should be same on the inherited FRUs.
```

Change-Id: Iff7340a111722f9e5336c0c846a79ffec8bbb8db
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-manager/include/utility/vpd_specific_utility.hpp b/vpd-manager/include/utility/vpd_specific_utility.hpp
index bcdce9d..0228e74 100644
--- a/vpd-manager/include/utility/vpd_specific_utility.hpp
+++ b/vpd-manager/include/utility/vpd_specific_utility.hpp
@@ -804,5 +804,88 @@
     }
     return false;
 }
+
+/**
+ * @brief API to sync keyword update to inherited FRUs.
+ *
+ * For a given keyword update on a EEPROM path, this API syncs the keyword
+ * update to all inherited FRUs' respective interface, property on PIM.
+ *
+ * @param[in] i_fruPath - EEPROM path of FRU.
+ * @param[in] i_paramsToWriteData - Input details.
+ * @param[in] i_sysCfgJsonObj - System config JSON.
+ *
+ */
+inline void updateKwdOnInheritedFrus(
+    const std::string& i_fruPath,
+    const types::WriteVpdParams& i_paramsToWriteData,
+    const nlohmann::json& i_sysCfgJsonObj) noexcept
+{
+    try
+    {
+        if (!i_sysCfgJsonObj.contains("frus"))
+        {
+            throw std::runtime_error("Mandatory tag(s) missing from JSON");
+        }
+
+        if (!i_sysCfgJsonObj["frus"].contains(i_fruPath))
+        {
+            throw std::runtime_error(
+                "VPD path [" + i_fruPath + "] not found in system config JSON");
+        }
+
+        const types::IpzData* l_ipzData =
+            std::get_if<types::IpzData>(&i_paramsToWriteData);
+
+        if (!l_ipzData)
+        {
+            throw std::runtime_error("Unsupported VPD type");
+        }
+        //  iterate through all inventory paths for given EEPROM path,
+        //  except the base FRU.
+        //  if for an inventory path, "inherit" tag is true,
+        //  update the inventory path's com.ibm.ipzvpd.<record>,keyword
+        //  property
+
+        types::ObjectMap l_objectInterfaceMap;
+
+        auto l_populateInterfaceMap =
+            [&l_objectInterfaceMap,
+             &l_ipzData = std::as_const(l_ipzData)](const auto& l_Fru) {
+                // update inherited FRUs only
+                if (l_Fru.value("inherit", true))
+                {
+                    l_objectInterfaceMap.emplace(
+                        sdbusplus::message::object_path{l_Fru["inventoryPath"]},
+                        types::InterfaceMap{
+                            {std::string{constants::ipzVpdInf +
+                                         std::get<0>(*l_ipzData)},
+                             types::PropertyMap{{std::get<1>(*l_ipzData),
+                                                 std::get<2>(*l_ipzData)}}}});
+                }
+            };
+
+        // iterate through all FRUs except the base FRU
+        std::for_each(
+            i_sysCfgJsonObj["frus"][i_fruPath].begin() + constants::VALUE_1,
+            i_sysCfgJsonObj["frus"][i_fruPath].end(), l_populateInterfaceMap);
+
+        if (!l_objectInterfaceMap.empty())
+        {
+            // notify PIM
+            if (!dbusUtility::callPIM(move(l_objectInterfaceMap)))
+            {
+                throw std::runtime_error(
+                    "Call to PIM failed for VPD file " + i_fruPath);
+            }
+        }
+    }
+    catch (const std::exception& l_ex)
+    {
+        logging::logMessage(
+            "Failed to sync keyword update to inherited FRUs of FRU [" +
+            i_fruPath + "]. Error: " + std::string(l_ex.what()));
+    }
+}
 } // namespace vpdSpecificUtility
 } // namespace vpd
diff --git a/vpd-manager/src/manager.cpp b/vpd-manager/src/manager.cpp
index ade45b7..fadd965 100644
--- a/vpd-manager/src/manager.cpp
+++ b/vpd-manager/src/manager.cpp
@@ -197,6 +197,14 @@
                     l_fruPath + "]");
             }
         }
+
+        // update keyword in inherited FRUs
+        if (l_rc != constants::FAILURE)
+        {
+            vpdSpecificUtility::updateKwdOnInheritedFrus(
+                l_fruPath, i_paramsToWriteData, l_sysCfgJsonObj);
+        }
+
         return l_rc;
     }
     catch (const std::exception& l_exception)