Handle CPU Module and FRU in write VPD

Test-
1. Using vpd-tool
 ./vpd-tool --readKeyword   --object  /system/chassis/motherboard --record VINI --keyword SN
{
    "/system/chassis/motherboard": {
        "SN": "YL2E2D010000"
    }
}
 ./vpd-tool --writeKeyword    --object  /system/chassis/motherboard --record VINI --keyword SN --value 0x303030

 ./vpd-tool --readKeyword   --object  /system/chassis/motherboard --record VINI --keyword SN           {
    "/system/chassis/motherboard": {
        "SN": "000E2D010000"
    }
}

COM interface-

Change-Id: I39ee0448483be581da254f5633b0817637292dff
-------------
.SN                                                  property  ay        12 48 48 48 69 50 68 48 49 48 48 48 48   emits-change writable

Common Interface-
----------------
xyz.openbmc_project.Inventory.Decorator.Asset        interface -         -                                        -
.SerialNumber                                        property  s         "000E2D010000"                           emits-change writable

sys path updated-
--------
00000110  35 53 4e 0c 30 30 30 45  32 44 30 31 30 30 30 30  |5SN.000E2D010000|

2. Tested on simics with spi driver supported image for 1 DCM
verified CPUs and other FRUs to work as expected.

1- busctl call WriteKeyword ossay  "/system/chassis/motherboard/cpu0" "VINI" "DR" 1 65
Updating CI, so updated cpu0, cpu1 and spi2 vpd.

2- busctl call WriteKeyword ossay  "/system/chassis/motherboard/cpu1" "VINI" "DR" 1 66
Updating CI, so updated cpu0, cpu1 and spi2 vpd.

3- busctl call WriteKeyword ossay  "/system/chassis/motherboard/cpu0" "VINI" "CC" 1 67
Not a CI, so only cpu0 and spi2 vpd updated.

4- busctl call WriteKeyword ossay  "/system/chassis/motherboard/cpu1" "VINI" "CC" 1 67
Not a CI, so only cpu1 and spi6 vpd updated.

cpu1 updated 1 Byte data of DR (7)-
busctl call WriteKeyword ossay "/system/chassis/motherboard/cpu1" "VINI" "DR" 1 55

Read file info at spi2.0/spi2.00/nvmem offset 0x30000-
000001a0  04 56 49 4e 49 44 52 10  37 20 57 41 59 20 20 50  |.VINIDR.7 WAY  P|  <--DR updated 1 Byte

Change-Id: I5e99c1ac4e09b2d0e05be18a10d1d50eba22fea2
Signed-off-by: Alpana Kumari <alpankum@in.ibm.com>
diff --git a/vpd-manager/editor_impl.cpp b/vpd-manager/editor_impl.cpp
index b9104f0..e5d698a 100644
--- a/vpd-manager/editor_impl.cpp
+++ b/vpd-manager/editor_impl.cpp
@@ -95,7 +95,8 @@
 #else
 
     // update data in EEPROM as well. As we will not write complete file back
-    vpdFileStream.seekg(thisRecord.kwDataOffset, std::ios::beg);
+    vpdFileStream.seekp(offset + thisRecord.kwDataOffset, std::ios::beg);
+
     iteratorToNewdata = kwdData.cbegin();
     std::copy(iteratorToNewdata, end,
               std::ostreambuf_iterator<char>(vpdFileStream));
@@ -186,7 +187,7 @@
     std::advance(end, thisRecord.recECCLength);
 
 #ifndef ManagerTest
-    vpdFileStream.seekp(thisRecord.recECCoffset, std::ios::beg);
+    vpdFileStream.seekp(offset + thisRecord.recECCoffset, std::ios::beg);
     std::copy(itrToRecordECC, end,
               std::ostreambuf_iterator<char>(vpdFileStream));
 #endif
@@ -352,30 +353,62 @@
     {
         // by default inherit property is true
         bool isInherit = true;
+        bool isInheritEI = true;
+        bool isCpuModuleOnly = false;
 
         if (singleInventory.find("inherit") != singleInventory.end())
         {
             isInherit = singleInventory["inherit"].get<bool>();
         }
 
+        if (singleInventory.find("inheritEI") != singleInventory.end())
+        {
+            isInheritEI = singleInventory["inheritEI"].get<bool>();
+        }
+
+        // "type" exists only in CPU module and FRU
+        if (singleInventory.find("type") != singleInventory.end())
+        {
+            if (singleInventory["type"] == "moduleOnly")
+            {
+                isCpuModuleOnly = true;
+            }
+        }
+
         if (isInherit)
         {
             // update com interface
-            makeDbusCall<Binary>(
-                (INVENTORY_PATH +
-                 singleInventory["inventoryPath"].get<std::string>()),
-                (IPZ_INTERFACE + (std::string) "." + thisRecord.recName),
-                thisRecord.recKWd, thisRecord.kwdUpdatedData);
+            // For CPU- update  com interface only when isCI true
+            if ((!isCpuModuleOnly) || (isCpuModuleOnly && isCI))
+            {
+                makeDbusCall<Binary>(
+                    (INVENTORY_PATH +
+                     singleInventory["inventoryPath"].get<std::string>()),
+                    (IPZ_INTERFACE + (std::string) "." + thisRecord.recName),
+                    thisRecord.recKWd, thisRecord.kwdUpdatedData);
+            }
 
             // process Common interface
             processAndUpdateCI(singleInventory["inventoryPath"]
                                    .get_ref<const nlohmann::json::string_t&>());
         }
 
-        // process extra interfaces
-        processAndUpdateEI(singleInventory,
-                           singleInventory["inventoryPath"]
-                               .get_ref<const nlohmann::json::string_t&>());
+        if (isInheritEI)
+        {
+            if (isCpuModuleOnly)
+            {
+                makeDbusCall<Binary>(
+                    (INVENTORY_PATH +
+                     singleInventory["inventoryPath"].get<std::string>()),
+                    (IPZ_INTERFACE + (std::string) "." + thisRecord.recName),
+                    thisRecord.recKWd, thisRecord.kwdUpdatedData);
+            }
+
+            // process extra interfaces
+            processAndUpdateEI(singleInventory,
+                               singleInventory["inventoryPath"]
+                                   .get_ref<const nlohmann::json::string_t&>());
+        }
     }
 }
 
@@ -446,29 +479,139 @@
     }
 }
 
+string EditorImpl::getSysPathForThisFruType(const string& moduleObjPath,
+                                            const string& fruType)
+{
+    string fruVpdPath;
+
+    // get all FRUs list
+    for (const auto& eachFru : jsonFile["frus"].items())
+    {
+        bool moduleObjPathMatched = false;
+        bool expectedFruFound = false;
+
+        for (const auto& eachInventory : eachFru.value())
+        {
+            const auto& thisObjectPath = eachInventory["inventoryPath"];
+
+            // "type" exists only in CPU module and FRU
+            if (eachInventory.find("type") != eachInventory.end())
+            {
+                // If inventory type is fruAndModule then set flag
+                if (eachInventory["type"] == fruType)
+                {
+                    expectedFruFound = true;
+                }
+            }
+
+            if (thisObjectPath == moduleObjPath)
+            {
+                moduleObjPathMatched = true;
+            }
+        }
+
+        // If condition satisfies then collect this sys path and exit
+        if (expectedFruFound && moduleObjPathMatched)
+        {
+            fruVpdPath = eachFru.key();
+            break;
+        }
+    }
+
+    return fruVpdPath;
+}
+
+void EditorImpl::getVpdPathForCpu()
+{
+    isCI = false;
+    // keep a backup In case we need it later
+    inventory::Path vpdFilePathBackup = vpdFilePath;
+
+    // TODO 1:Temp hardcoded list. create it dynamically.
+    std::vector<std::string> commonIntVINIKwds = {"PN", "SN", "DR"};
+    std::vector<std::string> commonIntVR10Kwds = {"DC"};
+    std::unordered_map<std::string, std::vector<std::string>>
+        commonIntRecordsList = {{"VINI", commonIntVINIKwds},
+                                {"VR10", commonIntVR10Kwds}};
+
+    // If requested Record&Kw is one among CI, then update 'FRU' type sys
+    // path, SPI2
+    unordered_map<std::string, vector<string>>::const_iterator isCommonInt =
+        commonIntRecordsList.find(thisRecord.recName);
+
+    if ((isCommonInt != commonIntRecordsList.end()) &&
+        (find(isCommonInt->second.begin(), isCommonInt->second.end(),
+              thisRecord.recKWd) != isCommonInt->second.end()))
+    {
+        isCI = true;
+        vpdFilePath = getSysPathForThisFruType(objPath, "fruAndModule");
+    }
+    else
+    {
+        for (const auto& eachFru : jsonFile["frus"].items())
+        {
+            for (const auto& eachInventory : eachFru.value())
+            {
+                if (eachInventory.find("type") != eachInventory.end())
+                {
+                    const auto& thisObjectPath = eachInventory["inventoryPath"];
+                    if ((eachInventory["type"] == "moduleOnly") &&
+                        (eachInventory.value("inheritEI", true)) &&
+                        (thisObjectPath == static_cast<string>(objPath)))
+                    {
+                        vpdFilePath = eachFru.key();
+                    }
+                }
+            }
+        }
+    }
+    // If it is not a CPU fru then go ahead with default vpdFilePath from
+    // fruMap
+    if (vpdFilePath.empty())
+    {
+        vpdFilePath = vpdFilePathBackup;
+    }
+}
+
 void EditorImpl::updateKeyword(const Binary& kwdData)
 {
-
+    offset = 0;
 #ifndef ManagerTest
+
+    getVpdPathForCpu();
+
+    uint32_t offset = 0;
+    // check if offset present?
+    for (const auto& item : jsonFile["frus"][vpdFilePath])
+    {
+        if (item.find("offset") != item.end())
+        {
+            offset = item["offset"];
+        }
+    }
+
+    // TODO: Figure out a better way to get max possible VPD size.
+    Binary completeVPDFile;
+    completeVPDFile.resize(65504);
     vpdFileStream.open(vpdFilePath,
                        std::ios::in | std::ios::out | std::ios::binary);
 
-    if (!vpdFileStream)
-    {
-        throw std::runtime_error("unable to open vpd file to edit");
-    }
+    vpdFileStream.seekg(offset, ios_base::cur);
+    vpdFileStream.read(reinterpret_cast<char*>(&completeVPDFile[0]), 65504);
+    completeVPDFile.resize(vpdFileStream.gcount());
+    vpdFileStream.clear(std::ios_base::eofbit);
 
-    Binary completeVPDFile((std::istreambuf_iterator<char>(vpdFileStream)),
-                           std::istreambuf_iterator<char>());
     vpdFile = completeVPDFile;
+
 #else
+
     Binary completeVPDFile = vpdFile;
+
 #endif
     if (vpdFile.empty())
     {
         throw std::runtime_error("Invalid File");
     }
-
     auto iterator = vpdFile.cbegin();
     std::advance(iterator, IPZ_DATA_START);
 
diff --git a/vpd-manager/editor_impl.hpp b/vpd-manager/editor_impl.hpp
index b3d24f7..d4d5598 100644
--- a/vpd-manager/editor_impl.hpp
+++ b/vpd-manager/editor_impl.hpp
@@ -65,9 +65,10 @@
      *  @param[in] path - Path to the vpd file
      */
     EditorImpl(const inventory::Path& path, const nlohmann::json& json,
-               const std::string& record, const std::string& kwd) :
+               const std::string& record, const std::string& kwd,
+               const sdbusplus::message::object_path& inventoryPath) :
         vpdFilePath(path),
-        jsonFile(json), thisRecord(record, kwd)
+        objPath(inventoryPath), jsonFile(json), thisRecord(record, kwd)
     {
     }
 
@@ -151,11 +152,17 @@
                       const std::string& property, const std::variant<T>& data);
 
     // path to the VPD file to edit
-    const inventory::Path vpdFilePath;
+    inventory::Path vpdFilePath;
+
+    // inventory path of fru/module to update keyword
+    const inventory::Path objPath;
 
     // stream to perform operation on file
     std::fstream vpdFileStream;
 
+    // offset to get vpd data from EEPROM
+    uint32_t offset;
+
     // file to store parsed json
     const nlohmann::json jsonFile;
 
@@ -181,6 +188,25 @@
 
     Binary vpdFile;
 
+    // If requested Interface is common Interface
+    bool isCI;
+
+    /** @brief This API will be used to find out Parent FRU of Module/CPU
+     *
+     * @param[in] - moduleObjPath, object path of that FRU
+     * @param[in] - fruType, Type of Parent FRU
+     *              for Module/CPU Parent Type- FruAndModule
+     *
+     * @return returns vpd file path of Parent Fru of that Module
+     */
+    std::string getSysPathForThisFruType(const std::string& moduleObjPath,
+                                         const std::string& fruType);
+
+    /** @brief This API will search for correct EEPROM path for asked CPU
+     *         and will init vpdFilePath
+     */
+    void getVpdPathForCpu();
+
 }; // class EditorImpl
 
 } // namespace editor
diff --git a/vpd-manager/manager.cpp b/vpd-manager/manager.cpp
index 3d8462c..717a83a 100644
--- a/vpd-manager/manager.cpp
+++ b/vpd-manager/manager.cpp
@@ -80,11 +80,16 @@
                              .get_ref<const nlohmann::json::string_t&>(),
                          std::make_pair(itemFRUS.key(), isMotherboard));
 
-            fruLocationCode.emplace(
-                itemEEPROM["extraInterfaces"][LOCATION_CODE_INF]["LocationCode"]
-                    .get_ref<const nlohmann::json::string_t&>(),
-                itemEEPROM["inventoryPath"]
-                    .get_ref<const nlohmann::json::string_t&>());
+            if (itemEEPROM["extraInterfaces"].find(LOCATION_CODE_INF) !=
+                itemEEPROM["extraInterfaces"].end())
+            {
+                fruLocationCode.emplace(
+                    itemEEPROM["extraInterfaces"][LOCATION_CODE_INF]
+                              ["LocationCode"]
+                                  .get_ref<const nlohmann::json::string_t&>(),
+                    itemEEPROM["inventoryPath"]
+                        .get_ref<const nlohmann::json::string_t&>());
+            }
         }
     }
 }
@@ -103,7 +108,7 @@
         inventory::Path vpdFilePath = frus.find(path)->second.first;
 
         // instantiate editor class to update the data
-        EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword);
+        EditorImpl edit(vpdFilePath, jsonFile, recordName, keyword, path);
         edit.updateKeyword(value);
 
         // if it is a motehrboard FRU need to check for location expansion