vpd-tool #kw support

This commit is to enable vpd-tool read and write support for pound
keywords starting with # and for numeric keywords.

This commit enables a way to read and write keyword values using --file
option, where --file takes a file with absolute path.

when --file option used with --readKeyword flag - vpd-tool saves the
output in the given file.
and when the file option used with --writeKeyword flag - the vpd-tool
takes the value from file and performs write operation.

Test:
-----------------------------------------------
Case 1: Read from hardware and save in text file
-----------------------------------------------
vpd-tool -r -H -O /sys/bus/i2c/drivers/at24/8-0050/eeprom -R PSPD -K "#D" --file /tmp/output.txt
Value read is saved in the file /tmp/output.txt

-----------------------------------------------
Case 2: Write to hardware by taking input from a text file
-----------------------------------------------
:~# cat /tmp/write.txt
00102030405060607020304050601020304050606040302010

:~# vpd-tool -w -O /sys/bus/i2c/drivers/at24/8-0050/eeprom -R PSPD -K "#D" --file /tmp/write.txt -H
Data updated successfully

:~# vpd-tool -r -H -O /sys/bus/i2c/drivers/at24/8-0050/eeprom -R PSPD -K "#D"
{
    "/sys/bus/i2c/drivers/at24/8-0050/eeprom": {
        "#D": "0x0010203040506060702030405060102030405060604030201000000000 ...0000"
    }
}

-----------------------------------------------
Case 3: Read from cache and pipe to text file
-----------------------------------------------
:~# vpd-tool -r -O /system/chassis/motherboard -R PSPD -K "#D" --file /tmp/read-motherboard-#D.txt
Value read is saved in the file /tmp/read-motherboard-#D.txt

-----------------------------------------------
Case 4: Write to cache by taking input from text file
-----------------------------------------------
:~# cat /tmp/write.txt
00102030405060607020304050601020304050606040302010

:~# vpd-tool -w -O /system/chassis/motherboard -R PSPD -K "#D" --file /tmp/write.txt
Data updated successfully

:~# vpd-tool -r -O /system/chassis/motherboard -R PSPD -K "#D"
{
    "/system/chassis/motherboard": {
        "#D": "0x0010203040506060702030405060102030405060604030201000000000000000000....."
    }
}

-----------------------------------------------
Case 5: Write to cache by taking hex input from console
-----------------------------------------------
:~# vpd-tool -w -O /system/chassis/motherboard -R PSPD -K "#D" -V 0x65
Data updated successfully

:~# vpd-tool -r -O /system/chassis/motherboard -R PSPD -K "#D"
{
    "/system/chassis/motherboard": {
        "#D": "0x65100c0c000000000000000 ...
    }
}

Case 5.1: Write to cache by providing ascii values as input from console

vpd-tool -w -O /system/chassis/motherboard -R PSPD -K "#D" -V abcd
Data updated successfully

vpd-tool -r -O /system/chassis/motherboard -R PSPD -K "#D"
{
    "/system/chassis/motherboard": {
        "#D": "0x6162636440506060702030405060102030405060604030201000000 .."
    }
}

-----------------------------------------------
Case 6: Read from cache and display on console
-----------------------------------------------
:~# vpd-tool -r -O /system/chassis/motherboard -R PSPD -K "#D"
{
    "/system/chassis/motherboard": {
        "#D": "0x00100c0c00000000000000000000000000000000000000 .....
    }
}

-----------------------------------------------
Case 7: Read from hardware and display on console
-----------------------------------------------
vpd-tool -r -O /sys/bus/i2c/drivers/at24/8-0050/eeprom -R PSPD -K "#D" -H
{
    "/sys/bus/i2c/drivers/at24/8-0050/eeprom": {
        "#D": "0x651020304050606070203040506010203040506060403020100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 ......... 000000000000000000000000000000000000000"
    }
}

-----------------------------------------------
Case 8: Write to hardware via console
-----------------------------------------------
vpd-tool -w -H -O /sys/bus/i2c/drivers/at24/8-0050/eeprom -R PSPD -K "#D" -V 0x00100c0c000000000000000000000000000000000000
Data updated successfully

vpd-tool -r -O /sys/bus/i2c/drivers/at24/8-0050/eeprom -R PSPD -K "#D" -H
{
    "/sys/bus/i2c/drivers/at24/8-0050/eeprom": {
        "#D": "0x00100c0c00000000000000000000000000000000000030201000000000000000000000000 ...
    }
}

-----------------------------------------------
Case 9: Write 10240 bytes on dbus
-----------------------------------------------
time vpd-tool -w -O /system/chassis/motherboard -R PSPD -K "#D" --file /tmp/output.txt

Data updated successfully

real	0m49.564s
user	0m0.047s
sys	0m0.009s

time vpd-tool -r -O /system/chassis/motherboard -R PSPD -K "#D"
{
    "/system/chassis/motherboard": {
        "#D": "0x01100c0c0000.....0000123456782746002"
    }
}

real	0m0.072s
user	0m0.057s
sys	0m0.001s
------------------------------------------------

Signed-off-by: Priyanga Ramasamy <priyanga24@in.ibm.com>
Change-Id: I3977a7778b28ebcada7788f619b18bbca6ed0c8c
diff --git a/const.hpp b/const.hpp
index 6cafd6f..7d9666e 100644
--- a/const.hpp
+++ b/const.hpp
@@ -25,6 +25,8 @@
 static constexpr auto MAC_ADDRESS_LEN_BYTES = 6;
 static constexpr auto LAST_KW = "PF";
 static constexpr auto POUND_KW = '#';
+static constexpr auto POUND_KW_PREFIX = "PD_";
+static constexpr auto NUMERIC_KW_PREFIX = "N_";
 static constexpr auto UUID_LEN_BYTES = 16;
 static constexpr auto UUID_TIME_LOW_END = 8;
 static constexpr auto UUID_TIME_MID_END = 13;
diff --git a/ibm_vpd_utils.cpp b/ibm_vpd_utils.cpp
index 341fb02..a085d0e 100644
--- a/ibm_vpd_utils.cpp
+++ b/ibm_vpd_utils.cpp
@@ -1052,5 +1052,19 @@
 
     return vpdVector;
 }
+
+std::string getDbusNameForThisKw(const std::string& keyword)
+{
+    if (keyword[0] == constants::POUND_KW)
+    {
+        return (std::string(constants::POUND_KW_PREFIX) + keyword[1]);
+    }
+    else if (isdigit(keyword[0]))
+    {
+        return (std::string(constants::NUMERIC_KW_PREFIX) + keyword);
+    }
+    return keyword;
+}
+
 } // namespace vpd
 } // namespace openpower
\ No newline at end of file
diff --git a/ibm_vpd_utils.hpp b/ibm_vpd_utils.hpp
index 46a707a..ad3fbea 100644
--- a/ibm_vpd_utils.hpp
+++ b/ibm_vpd_utils.hpp
@@ -489,5 +489,16 @@
  * @return A byte array containing the raw VPD.
  */
 Binary getVpdDataInVector(const nlohmann::json& js, const std::string& file);
+
+/**
+ * @brief Get D-bus name for the keyword
+ * Some of the VPD keywords has different name in PIM when compared with its
+ * name from hardware. This method returns the D-bus name for the given keyword.
+ *
+ * @param[in] keyword - Keyword name
+ * @return D-bus name for the keyword
+ */
+std::string getDbusNameForThisKw(const std::string& keyword);
+
 } // namespace vpd
 } // namespace openpower
\ No newline at end of file
diff --git a/vpd-manager/editor_impl.cpp b/vpd-manager/editor_impl.cpp
index 426dd7b..f95f4cf 100644
--- a/vpd-manager/editor_impl.cpp
+++ b/vpd-manager/editor_impl.cpp
@@ -422,7 +422,8 @@
 
         if (isInherit)
         {
-            prop.emplace(thisRecord.recKWd, thisRecord.kwdUpdatedData);
+            prop.emplace(getDbusNameForThisKw(thisRecord.recKWd),
+                         thisRecord.kwdUpdatedData);
             interfaces.emplace(
                 (IPZ_INTERFACE + (std::string) "." + thisRecord.recName),
                 std::move(prop));
diff --git a/vpd_tool.cpp b/vpd_tool.cpp
index 61518bd..bc5e37e 100644
--- a/vpd_tool.cpp
+++ b/vpd_tool.cpp
@@ -34,7 +34,7 @@
         "in hex. ascii eg: 01234; hex eg: 0x30313233");
     app.add_option("--seek, -s", offset,
                    "User can provide VPD offset using this option. Default "
-                   "offset value is 0. Using --offset is optional and is valid "
+                   "offset value is 0. Using --seek is optional and is valid "
                    "only while using --Hardware/-H option.");
 
     auto dumpObjFlag =
@@ -63,11 +63,18 @@
                "Update the value. { vpd-tool-exe "
                "--writeKeyword/-w/--updateKeyword/-u "
                "--object/-O object-name --record/-R record-name --keyword/-K "
-               "keyword-name --value/-V value-to-be-updated }")
+               "keyword-name --value/-V (or) --file }. Value can be given "
+               "directly via console using --value or via file using --file")
             ->needs(object)
             ->needs(record)
-            ->needs(kw)
-            ->needs(valOption);
+            ->needs(kw);
+
+    auto fileOption = app.add_option(
+        "--file", val,
+        "Enter the file name with its absolute path. This option can be used "
+        "in read and write operations. When used in read, the read value will "
+        "be saved to this file and when used in write, the value to be written "
+        "will be taken from this file.");
 
     auto forceResetFlag =
         app.add_flag("--forceReset, -f, -F",
@@ -99,6 +106,11 @@
 
     try
     {
+        if ((*kw) && (keyword.size() != 2))
+        {
+            throw runtime_error("Keyword " + keyword + " not supported.");
+        }
+
         if (*Hardware)
         {
             if (!fs::exists(objectPath)) // if dbus object path is given or
@@ -112,6 +124,23 @@
                 throw runtime_error(errorMsg);
             }
         }
+
+        if (*writeFlag)
+        {
+            if ((!*fileOption) && (!*valOption))
+            {
+                throw runtime_error("Please provide the data that needs to be "
+                                    "updated. Use --value/--file to "
+                                    "input data. Refer --help.");
+            }
+
+            if ((*fileOption) && (!fs::exists(val)))
+            {
+                throw runtime_error("Please provide a valid file with absolute "
+                                    "path in --file.");
+            }
+        }
+
         if (*dumpObjFlag)
         {
             VpdTool vpdToolObj(move(objectPath));
@@ -127,7 +156,7 @@
         else if (*readFlag && !*Hardware)
         {
             VpdTool vpdToolObj(move(objectPath), move(recordName),
-                               move(keyword));
+                               move(keyword), move(val));
             vpdToolObj.readKeyword();
         }
 
@@ -164,7 +193,7 @@
         else if (*readFlag && *Hardware)
         {
             VpdTool vpdToolObj(move(objectPath), move(recordName),
-                               move(keyword));
+                               move(keyword), move(val));
             vpdToolObj.readKwFromHw(offset);
         }
         else if (*fixSystemVPDFlag)
@@ -203,12 +232,13 @@
 
         if (*Hardware)
         {
-            std::cerr << "\nDid you provide a valid offset? By default VPD "
-                         "offset is taken as 0. To input offset, use --offset. "
-                         "Refer vpd-tool help.";
+            std::cerr << "Did you provide a valid offset? By default VPD "
+                         "offset is taken as 0. To input offset, use --seek. "
+                         "Refer vpd-tool help."
+                      << std::endl;
         }
         rc = -1;
     }
 
     return rc;
-}
+}
\ No newline at end of file
diff --git a/vpd_tool_impl.cpp b/vpd_tool_impl.cpp
index 93c4d40..5870659 100644
--- a/vpd_tool_impl.cpp
+++ b/vpd_tool_impl.cpp
@@ -22,6 +22,71 @@
 using namespace openpower::vpd::parser::factory;
 using namespace openpower::vpd::parser::interface;
 
+bool VpdTool::fileToVector(Binary& data)
+{
+    try
+    {
+        std::ifstream file(value, std::ifstream::in);
+
+        if (file)
+        {
+            std::string line;
+            while (std::getline(file, line))
+            {
+                std::istringstream iss(line);
+                std::string byteStr;
+                while (iss >> std::setw(2) >> std::hex >> byteStr)
+                {
+                    uint8_t byte = strtoul(byteStr.c_str(), nullptr, 16);
+                    data.emplace(data.end(), byte);
+                }
+            }
+            return true;
+        }
+        else
+        {
+            std::cerr << "Unable to open the given file " << value << std::endl;
+        }
+    }
+    catch (std::exception& e)
+    {
+        std::cerr << e.what();
+    }
+    return false;
+}
+
+bool VpdTool::copyStringToFile(const std::string& input)
+{
+    try
+    {
+        std::ofstream outFile(value, std::ofstream::out);
+
+        if (outFile.is_open())
+        {
+            std::string hexString = input;
+            if (input.substr(0, 2) == "0x")
+            {
+                // truncating prefix 0x
+                hexString = input.substr(2);
+            }
+            outFile.write(hexString.c_str(), hexString.length());
+        }
+        else
+        {
+            std::cerr << "Error opening output file " << value << std::endl;
+            return false;
+        }
+
+        outFile.close();
+    }
+    catch (std::exception& e)
+    {
+        std::cerr << e.what();
+        return false;
+    }
+    return true;
+}
+
 static void
     getVPDInMap(const std::string& vpdPath,
                 std::unordered_map<std::string, DbusPropertyMap>& vpdMap,
@@ -320,8 +385,9 @@
     }
     catch (const sdbusplus::exception::SdBusError& e)
     {
-        // not required to handle the exception. Present will be set to Unknown
+        presence = "Unknown";
     }
+
     json js;
     js.emplace("Present", presence);
     return js;
@@ -408,14 +474,14 @@
 
 void VpdTool::readKeyword()
 {
+    const std::string& kw = getDbusNameForThisKw(keyword);
+
     string interface = "com.ibm.ipzvpd.";
     variant<Binary> response;
 
     try
     {
-        json output = json::object({});
-        json kwVal = json::object({});
-        makeDBusCall(INVENTORY_PATH + fruPath, interface + recordName, keyword)
+        makeDBusCall(INVENTORY_PATH + fruPath, interface + recordName, kw)
             .read(response);
 
         string printableVal{};
@@ -423,6 +489,25 @@
         {
             printableVal = getPrintableValue(*vec);
         }
+
+        if (!value.empty())
+        {
+            if (copyStringToFile(printableVal))
+            {
+                std::cout << "Value read is saved in the file " << value
+                          << std::endl;
+                return;
+            }
+            else
+            {
+                std::cerr << "Error while saving the read value in file. "
+                             "Displaying the read value on console"
+                          << std::endl;
+            }
+        }
+
+        json output = json::object({});
+        json kwVal = json::object({});
         kwVal.emplace(keyword, printableVal);
 
         output.emplace(fruPath, kwVal);
@@ -438,7 +523,22 @@
 
 int VpdTool::updateKeyword()
 {
-    Binary val = toBinary(value);
+    Binary val;
+
+    if (std::filesystem::exists(value))
+    {
+        if (!fileToVector(val))
+        {
+            std::cout << "Keyword " << keyword << " update failed."
+                      << std::endl;
+            return 1;
+        }
+    }
+    else
+    {
+        val = toBinary(value);
+    }
+
     auto bus = sdbusplus::bus::new_default();
     auto properties =
         bus.new_method_call(BUSNAME, OBJPATH, IFACE, "WriteKeyword");
@@ -446,12 +546,18 @@
     properties.append(recordName);
     properties.append(keyword);
     properties.append(val);
-    auto result = bus.call(properties);
+
+    // When there is a request to write 10K bytes, there occurs a delay in dbus
+    // call which leads to dbus timeout exception. To avoid such exceptions
+    // increase the timeout period from default 25 seconds to 60 seconds.
+    auto timeoutInMicroSeconds = 60 * 1000000L;
+    auto result = bus.call(properties, timeoutInMicroSeconds);
 
     if (result.is_method_error())
     {
         throw runtime_error("Get api failed");
     }
+    std::cout << "Data updated successfully " << std::endl;
     return 0;
 }
 
@@ -505,7 +611,21 @@
 int VpdTool::updateHardware(const uint32_t offset)
 {
     int rc = 0;
-    const Binary& val = static_cast<const Binary&>(toBinary(value));
+    Binary val;
+    if (std::filesystem::exists(value))
+    {
+        if (!fileToVector(val))
+        {
+            std::cout << "Keyword " << keyword << " update failed."
+                      << std::endl;
+            return 1;
+        }
+    }
+    else
+    {
+        val = toBinary(value);
+    }
+
     ifstream inventoryJson(INVENTORY_JSON_SYM_LINK);
     try
     {
@@ -518,6 +638,7 @@
     {
         throw(VpdJsonException("Json Parsing failed", INVENTORY_JSON_SYM_LINK));
     }
+    std::cout << "Data updated successfully " << std::endl;
     return rc;
 }
 
@@ -558,23 +679,39 @@
              vpdStartOffset);
     std::string keywordVal = obj.readKwFromHw(recordName, keyword);
 
-    if (!keywordVal.empty())
-    {
-        json output = json::object({});
-        json kwVal = json::object({});
-        kwVal.emplace(keyword, getPrintableValue(keywordVal));
+    keywordVal = getPrintableValue(keywordVal);
 
-        output.emplace(fruPath, kwVal);
-
-        debugger(output);
-    }
-    else
+    if (keywordVal.empty())
     {
         std::cerr << "The given keyword " << keyword << " or record "
                   << recordName
                   << " or both are not present in the given FRU path "
                   << fruPath << std::endl;
+        return;
     }
+
+    if (!value.empty())
+    {
+        if (copyStringToFile(keywordVal))
+        {
+            std::cout << "Value read is saved in the file " << value
+                      << std::endl;
+            return;
+        }
+        else
+        {
+            std::cerr
+                << "Error while saving the read value in file. Displaying "
+                   "the read value on console"
+                << std::endl;
+        }
+    }
+
+    json output = json::object({});
+    json kwVal = json::object({});
+    kwVal.emplace(keyword, keywordVal);
+    output.emplace(fruPath, kwVal);
+    debugger(output);
 }
 
 void VpdTool::printFixSystemVPDOption(UserOption option)
@@ -998,10 +1135,9 @@
             }
         }
 
-        std::cout
-            << "\n The critical keywords from system backplane VPD has been "
-               "reset successfully."
-            << std::endl;
+        std::cout << "\n The critical keywords from system backplane VPD has "
+                     "been reset successfully."
+                  << std::endl;
     }
     catch (const std::exception& e)
     {
@@ -1010,4 +1146,4 @@
             << "\nManufacturing reset on system vpd keywords is unsuccessful";
     }
     return 0;
-}
+}
\ No newline at end of file
diff --git a/vpd_tool_impl.hpp b/vpd_tool_impl.hpp
index 16c3a34..82a1b8c 100644
--- a/vpd_tool_impl.hpp
+++ b/vpd_tool_impl.hpp
@@ -172,6 +172,26 @@
     void getSystemDataFromCache(
         openpower::vpd::inventory::IntfPropMap& svpdBusData);
 
+    /**
+     * @brief Get data from file and store in binary format
+     *
+     * @param[out] data - The resulting binary data
+     *
+     * @return If operation is success return true, else on failure return
+     * false.
+     */
+    bool fileToVector(openpower::vpd::Binary& data);
+
+    /**
+     * @brief Copy string data to file.
+     *
+     * @param[in] input - input string
+     *
+     * @return If operation is success return true, else on failure return
+     * false.
+     */
+    bool copyStringToFile(const std::string& input);
+
   public:
     /**
      * @brief Dump the complete inventory in JSON format
@@ -191,7 +211,8 @@
      * @brief Read keyword
      * Read the given object path, record name and keyword
      * from the inventory and display the value of the keyword
-     * in JSON format.
+     * in JSON format. The read value will be piped to file if --file is given
+     * by the user. Else the value read will be displayed on console.
      */
     void readKeyword();
 
@@ -232,7 +253,8 @@
      * initialising the constructor.
      * The user can now read keyword from any hardware path irrespective of
      * whether its present or not in VPD JSON, by providing a valid offset. By
-     * default offset takes 0.
+     * default offset takes 0. The read value can be either saved in a
+     * file/displayed on console.
      *
      * @param[in] startOffset - VPD offset.
      */
@@ -277,18 +299,6 @@
     /**
      * @brief Constructor
      * Constructor is called during the
-     * object instantiation for readKeyword option.
-     */
-    VpdTool(const std::string&& fru, const std::string&& recName,
-            const std::string&& kw) :
-        fruPath(std::move(fru)),
-        recordName(std::move(recName)), keyword(std::move(kw))
-    {
-    }
-
-    /**
-     * @brief Constructor
-     * Constructor is called during the
      * object instantiation for updateKeyword option.
      */
 
@@ -299,4 +309,4 @@
         value(std::move(val))
     {
     }
-};
+};
\ No newline at end of file