Revamped code for VPD parser
The commit removes all the pre-existing code from the branch
and pushes the revamped code.
Major modification includes:
- Movement from multi exe to single daemon model.
- Multithreaded approach to parse FRU VPD.
- Better error handling.
- Refactored code for performance optimization.
Note: This code supports all the existing functionalities as it is.
Change-Id: I1ddce1f0725ac59020b72709689a1013643bda8b
Signed-off-by: Sunny Srivastava <sunnsr25@in.ibm.com>
diff --git a/vpd-tool/README.md b/vpd-tool/README.md
new file mode 100644
index 0000000..4d02336
--- /dev/null
+++ b/vpd-tool/README.md
@@ -0,0 +1,6 @@
+# VPD Tool Overview
+
+VPD Tool is designed for managing BMC system FRU's Vital Product Data(VPD). It
+provides command line interface to read and write FRU's VPD. More information
+can be found
+[here](https://github.ibm.com/openbmc/openbmc/wiki/VPD-TOOL-HELPER).
diff --git a/vpd-tool/include/tool_constants.hpp b/vpd-tool/include/tool_constants.hpp
new file mode 100644
index 0000000..6e89957
--- /dev/null
+++ b/vpd-tool/include/tool_constants.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <cstdint>
+
+namespace vpd
+{
+namespace constants
+{
+static constexpr auto KEYWORD_SIZE = 2;
+static constexpr auto RECORD_SIZE = 4;
+static constexpr auto INDENTATION = 4;
+static constexpr auto SUCCESS = 0;
+static constexpr auto FAILURE = -1;
+
+// To be explicitly used for string comparison.
+static constexpr auto STR_CMP_SUCCESS = 0;
+
+constexpr auto inventoryManagerService =
+ "xyz.openbmc_project.Inventory.Manager";
+constexpr auto baseInventoryPath = "/xyz/openbmc_project/inventory";
+constexpr auto ipzVpdInfPrefix = "com.ibm.ipzvpd.";
+
+constexpr auto vpdManagerService = "com.ibm.VPD.Manager";
+constexpr auto vpdManagerObjectPath = "/com/ibm/VPD/Manager";
+constexpr auto vpdManagerInfName = "com.ibm.VPD.Manager";
+constexpr auto inventoryItemInf = "xyz.openbmc_project.Inventory.Item";
+constexpr auto kwdVpdInf = "com.ibm.ipzvpd.VINI";
+constexpr auto locationCodeInf = "com.ibm.ipzvpd.Location";
+constexpr auto assetInf = "xyz.openbmc_project.Inventory.Decorator.Asset";
+constexpr auto objectMapperService = "xyz.openbmc_project.ObjectMapper";
+constexpr auto objectMapperObjectPath = "/xyz/openbmc_project/object_mapper";
+constexpr auto objectMapperInfName = "xyz.openbmc_project.ObjectMapper";
+} // namespace constants
+} // namespace vpd
diff --git a/vpd-tool/include/tool_types.hpp b/vpd-tool/include/tool_types.hpp
new file mode 100644
index 0000000..1e9ff7d
--- /dev/null
+++ b/vpd-tool/include/tool_types.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+#include <sdbusplus/message/types.hpp>
+
+#include <cstdint>
+#include <tuple>
+#include <variant>
+#include <vector>
+
+namespace vpd
+{
+namespace types
+{
+using BinaryVector = std::vector<uint8_t>;
+
+// This covers mostly all the data type supported over DBus for a property.
+// clang-format off
+using DbusVariantType = std::variant<
+ std::vector<std::tuple<std::string, std::string, std::string>>,
+ std::vector<std::string>,
+ std::vector<double>,
+ std::string,
+ int64_t,
+ uint64_t,
+ double,
+ int32_t,
+ uint32_t,
+ int16_t,
+ uint16_t,
+ uint8_t,
+ bool,
+ BinaryVector,
+ std::vector<uint32_t>,
+ std::vector<uint16_t>,
+ sdbusplus::message::object_path,
+ std::tuple<uint64_t, std::vector<std::tuple<std::string, std::string, double, uint64_t>>>,
+ std::vector<std::tuple<std::string, std::string>>,
+ std::vector<std::tuple<uint32_t, std::vector<uint32_t>>>,
+ std::vector<std::tuple<uint32_t, size_t>>,
+ std::vector<std::tuple<sdbusplus::message::object_path, std::string,
+ std::string, std::string>>
+ >;
+
+//IpzType contains tuple of <Record, Keyword>
+using IpzType = std::tuple<std::string, std::string>;
+
+//ReadVpdParams either of IPZ or keyword format
+using ReadVpdParams = std::variant<IpzType, std::string>;
+
+//KwData contains tuple of <keywordName, KeywordValue>
+using KwData = std::tuple<std::string, BinaryVector>;
+
+//IpzData contains tuple of <RecordName, KeywordName, KeywordValue>
+using IpzData = std::tuple<std::string, std::string, BinaryVector>;
+
+//WriteVpdParams either of IPZ or keyword format
+using WriteVpdParams = std::variant<IpzData, KwData>;
+// Return type of ObjectMapper GetObject API
+using MapperGetObject = std::map<std::string,std::vector<std::string>>;
+
+// Table row data
+using TableRowData = std::vector<std::string>;
+
+// Type used to populate table data
+using TableInputData = std::vector<TableRowData>;
+
+// A table column name-size pair
+using TableColumnNameSizePair = std::pair<std::string, std::size_t>;
+
+enum UserOption
+{
+ Exit,
+ UseBackupDataForAll,
+ UseSystemBackplaneDataForAll,
+ MoreOptions,
+ UseBackupDataForCurrent,
+ UseSystemBackplaneDataForCurrent,
+ NewValueOnBoth,
+ SkipCurrent
+};
+
+} // namespace types
+} // namespace vpd
diff --git a/vpd-tool/include/tool_utils.hpp b/vpd-tool/include/tool_utils.hpp
new file mode 100644
index 0000000..4d650c1
--- /dev/null
+++ b/vpd-tool/include/tool_utils.hpp
@@ -0,0 +1,726 @@
+#pragma once
+
+#include "tool_constants.hpp"
+#include "tool_types.hpp"
+
+#include <nlohmann/json.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+
+#include <fstream>
+#include <iostream>
+
+namespace vpd
+{
+namespace utils
+{
+/**
+ * @brief An API to read property from Dbus.
+ *
+ * API reads the property value for the specified interface and object path from
+ * the given Dbus service.
+ *
+ * The caller of the API needs to validate the validity and correctness of the
+ * type and value of data returned. The API will just fetch and return the data
+ * without any data validation.
+ *
+ * Note: It will be caller's responsibility to check for empty value returned
+ * and generate appropriate error if required.
+ *
+ * @param[in] i_serviceName - Name of the Dbus service.
+ * @param[in] i_objectPath - Object path under the service.
+ * @param[in] i_interface - Interface under which property exist.
+ * @param[in] i_property - Property whose value is to be read.
+ *
+ * @return - Value read from Dbus.
+ *
+ * @throw std::runtime_error
+ */
+inline types::DbusVariantType readDbusProperty(
+ const std::string& i_serviceName, const std::string& i_objectPath,
+ const std::string& i_interface, const std::string& i_property)
+{
+ types::DbusVariantType l_propertyValue;
+
+ // Mandatory fields to make a dbus call.
+ if (i_serviceName.empty() || i_objectPath.empty() || i_interface.empty() ||
+ i_property.empty())
+ {
+ // TODO: Enable logging when verbose is enabled.
+ /*std::cout << "One of the parameter to make Dbus read call is empty."
+ << std::endl;*/
+ throw std::runtime_error("Empty Parameter");
+ }
+
+ try
+ {
+ auto l_bus = sdbusplus::bus::new_default();
+ auto l_method =
+ l_bus.new_method_call(i_serviceName.c_str(), i_objectPath.c_str(),
+ "org.freedesktop.DBus.Properties", "Get");
+ l_method.append(i_interface, i_property);
+
+ auto result = l_bus.call(l_method);
+ result.read(l_propertyValue);
+ }
+ catch (const sdbusplus::exception::SdBusError& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ // std::cout << std::string(l_ex.what()) << std::endl;
+ throw std::runtime_error(std::string(l_ex.what()));
+ }
+ return l_propertyValue;
+}
+
+/**
+ * @brief An API to print json data on stdout.
+ *
+ * @param[in] i_jsonData - JSON object.
+ */
+inline void printJson(const nlohmann::json& i_jsonData)
+{
+ try
+ {
+ std::cout << i_jsonData.dump(constants::INDENTATION) << std::endl;
+ }
+ catch (const nlohmann::json::type_error& l_ex)
+ {
+ throw std::runtime_error(
+ "Failed to dump JSON data, error: " + std::string(l_ex.what()));
+ }
+}
+
+/**
+ * @brief An API to convert binary value into ascii/hex representation.
+ *
+ * If given data contains printable characters, ASCII formated string value of
+ * the input data will be returned. Otherwise if the data has any non-printable
+ * value, returns the hex represented value of the given data in string format.
+ *
+ * @param[in] i_keywordValue - Data in binary format.
+ *
+ * @throw - Throws std::bad_alloc or std::terminate in case of error.
+ *
+ * @return - Returns the converted string value.
+ */
+inline std::string getPrintableValue(const types::BinaryVector& i_keywordValue)
+{
+ bool l_allPrintable =
+ std::all_of(i_keywordValue.begin(), i_keywordValue.end(),
+ [](const auto& l_byte) { return std::isprint(l_byte); });
+
+ std::ostringstream l_oss;
+ if (l_allPrintable)
+ {
+ l_oss << std::string(i_keywordValue.begin(), i_keywordValue.end());
+ }
+ else
+ {
+ l_oss << "0x";
+ for (const auto& l_byte : i_keywordValue)
+ {
+ l_oss << std::setfill('0') << std::setw(2) << std::hex
+ << static_cast<int>(l_byte);
+ }
+ }
+
+ return l_oss.str();
+}
+
+/**
+ * @brief API to read keyword's value from hardware.
+ *
+ * This API reads keyword's value by requesting DBus service(vpd-manager) who
+ * hosts the 'ReadKeyword' method to read keyword's value.
+ *
+ * @param[in] i_eepromPath - EEPROM file path.
+ * @param[in] i_paramsToReadData - Property whose value has to be read.
+ *
+ * @return - Value read from hardware
+ *
+ * @throw std::runtime_error, sdbusplus::exception::SdBusError
+ */
+inline types::DbusVariantType
+ readKeywordFromHardware(const std::string& i_eepromPath,
+ const types::ReadVpdParams i_paramsToReadData)
+{
+ if (i_eepromPath.empty())
+ {
+ throw std::runtime_error("Empty EEPROM path");
+ }
+
+ try
+ {
+ types::DbusVariantType l_propertyValue;
+
+ auto l_bus = sdbusplus::bus::new_default();
+
+ auto l_method = l_bus.new_method_call(
+ constants::vpdManagerService, constants::vpdManagerObjectPath,
+ constants::vpdManagerInfName, "ReadKeyword");
+
+ l_method.append(i_eepromPath, i_paramsToReadData);
+ auto l_result = l_bus.call(l_method);
+
+ l_result.read(l_propertyValue);
+
+ return l_propertyValue;
+ }
+ catch (const sdbusplus::exception::SdBusError& l_error)
+ {
+ throw;
+ }
+}
+
+/**
+ * @brief API to save keyword's value on file.
+ *
+ * API writes keyword's value on the given file path. If the data is in hex
+ * format, API strips '0x' and saves the value on the given file.
+ *
+ * @param[in] i_filePath - File path.
+ * @param[in] i_keywordValue - Keyword's value.
+ *
+ * @return - true on successfully writing to file, false otherwise.
+ */
+inline bool saveToFile(const std::string& i_filePath,
+ const std::string& i_keywordValue)
+{
+ bool l_returnStatus = false;
+
+ if (i_keywordValue.empty())
+ {
+ // ToDo: log only when verbose is enabled
+ std::cerr << "Save to file[ " << i_filePath
+ << "] failed, reason: Empty keyword's value received"
+ << std::endl;
+ return l_returnStatus;
+ }
+
+ std::string l_keywordValue{i_keywordValue};
+ if (i_keywordValue.substr(0, 2).compare("0x") == constants::STR_CMP_SUCCESS)
+ {
+ l_keywordValue = i_keywordValue.substr(2);
+ }
+
+ std::ofstream l_outPutFileStream;
+ l_outPutFileStream.exceptions(
+ std::ifstream::badbit | std::ifstream::failbit);
+ try
+ {
+ l_outPutFileStream.open(i_filePath);
+
+ if (l_outPutFileStream.is_open())
+ {
+ l_outPutFileStream.write(l_keywordValue.c_str(),
+ l_keywordValue.size());
+ l_returnStatus = true;
+ }
+ else
+ {
+ // ToDo: log only when verbose is enabled
+ std::cerr << "Error opening output file " << i_filePath
+ << std::endl;
+ }
+ }
+ catch (const std::ios_base::failure& l_ex)
+ {
+ // ToDo: log only when verbose is enabled
+ std::cerr
+ << "Failed to write to file: " << i_filePath
+ << ", either base folder path doesn't exist or internal error occured, error: "
+ << l_ex.what() << '\n';
+ }
+
+ return l_returnStatus;
+}
+
+/**
+ * @brief API to print data in JSON format on console
+ *
+ * @param[in] i_fruPath - FRU path.
+ * @param[in] i_keywordName - Keyword name.
+ * @param[in] i_keywordStrValue - Keyword's value.
+ */
+inline void displayOnConsole(const std::string& i_fruPath,
+ const std::string& i_keywordName,
+ const std::string& i_keywordStrValue)
+{
+ nlohmann::json l_resultInJson = nlohmann::json::object({});
+ nlohmann::json l_keywordValInJson = nlohmann::json::object({});
+
+ l_keywordValInJson.emplace(i_keywordName, i_keywordStrValue);
+ l_resultInJson.emplace(i_fruPath, l_keywordValInJson);
+
+ printJson(l_resultInJson);
+}
+
+/**
+ * @brief API to write keyword's value.
+ *
+ * This API writes keyword's value by requesting DBus service(vpd-manager) who
+ * hosts the 'UpdateKeyword' method to update keyword's value.
+ *
+ * @param[in] i_vpdPath - EEPROM or object path, where keyword is present.
+ * @param[in] i_paramsToWriteData - Data required to update keyword's value.
+ *
+ * @return - Number of bytes written on success, -1 on failure.
+ *
+ * @throw - std::runtime_error, sdbusplus::exception::SdBusError
+ */
+inline int writeKeyword(const std::string& i_vpdPath,
+ const types::WriteVpdParams& i_paramsToWriteData)
+{
+ if (i_vpdPath.empty())
+ {
+ throw std::runtime_error("Empty path");
+ }
+
+ int l_rc = constants::FAILURE;
+ auto l_bus = sdbusplus::bus::new_default();
+
+ auto l_method = l_bus.new_method_call(
+ constants::vpdManagerService, constants::vpdManagerObjectPath,
+ constants::vpdManagerInfName, "UpdateKeyword");
+
+ l_method.append(i_vpdPath, i_paramsToWriteData);
+ auto l_result = l_bus.call(l_method);
+
+ l_result.read(l_rc);
+ return l_rc;
+}
+
+/**
+ * @brief API to write keyword's value on hardware.
+ *
+ * This API writes keyword's value by requesting DBus service(vpd-manager) who
+ * hosts the 'WriteKeywordOnHardware' method to update keyword's value.
+ *
+ * Note: This API updates keyword's value only on the given hardware path, any
+ * backup or redundant EEPROM (if exists) paths won't get updated.
+ *
+ * @param[in] i_eepromPath - EEPROM where keyword is present.
+ * @param[in] i_paramsToWriteData - Data required to update keyword's value.
+ *
+ * @return - Number of bytes written on success, -1 on failure.
+ *
+ * @throw - std::runtime_error, sdbusplus::exception::SdBusError
+ */
+inline int
+ writeKeywordOnHardware(const std::string& i_eepromPath,
+ const types::WriteVpdParams& i_paramsToWriteData)
+{
+ if (i_eepromPath.empty())
+ {
+ throw std::runtime_error("Empty path");
+ }
+
+ int l_rc = constants::FAILURE;
+ auto l_bus = sdbusplus::bus::new_default();
+
+ auto l_method = l_bus.new_method_call(
+ constants::vpdManagerService, constants::vpdManagerObjectPath,
+ constants::vpdManagerInfName, "WriteKeywordOnHardware");
+
+ l_method.append(i_eepromPath, i_paramsToWriteData);
+ auto l_result = l_bus.call(l_method);
+
+ l_result.read(l_rc);
+
+ if (l_rc > 0)
+ {
+ std::cout << "Data updated successfully " << std::endl;
+ }
+ return l_rc;
+}
+
+/**
+ * @brief API to get data in binary format.
+ *
+ * This API converts given string value into array of binary data.
+ *
+ * @param[in] i_value - Input data.
+ *
+ * @return - Array of binary data on success, throws as exception in case
+ * of any error.
+ *
+ * @throw std::runtime_error, std::out_of_range, std::bad_alloc,
+ * std::invalid_argument
+ */
+inline types::BinaryVector convertToBinary(const std::string& i_value)
+{
+ if (i_value.empty())
+ {
+ throw std::runtime_error(
+ "Provide a valid hexadecimal input. (Ex. 0x30313233)");
+ }
+
+ std::vector<uint8_t> l_binaryValue{};
+
+ if (i_value.substr(0, 2).compare("0x") == constants::STR_CMP_SUCCESS)
+ {
+ if (i_value.length() % 2 != 0)
+ {
+ throw std::runtime_error(
+ "Write option accepts 2 digit hex numbers. (Ex. 0x1 "
+ "should be given as 0x01).");
+ }
+
+ auto l_value = i_value.substr(2);
+
+ if (l_value.empty())
+ {
+ throw std::runtime_error(
+ "Provide a valid hexadecimal input. (Ex. 0x30313233)");
+ }
+
+ if (l_value.find_first_not_of("0123456789abcdefABCDEF") !=
+ std::string::npos)
+ {
+ throw std::runtime_error("Provide a valid hexadecimal input.");
+ }
+
+ for (size_t l_pos = 0; l_pos < l_value.length(); l_pos += 2)
+ {
+ uint8_t l_byte = static_cast<uint8_t>(
+ std::stoi(l_value.substr(l_pos, 2), nullptr, 16));
+ l_binaryValue.push_back(l_byte);
+ }
+ }
+ else
+ {
+ l_binaryValue.assign(i_value.begin(), i_value.end());
+ }
+ return l_binaryValue;
+}
+
+/**
+ * @brief API to parse respective JSON.
+ *
+ * @param[in] i_pathToJson - Path to JSON.
+ *
+ * @return Parsed JSON, throws exception in case of error.
+ *
+ * @throw std::runtime_error
+ */
+inline nlohmann::json getParsedJson(const std::string& i_pathToJson)
+{
+ if (i_pathToJson.empty())
+ {
+ throw std::runtime_error("Path to JSON is missing");
+ }
+
+ std::error_code l_ec;
+ if (!std::filesystem::exists(i_pathToJson, l_ec))
+ {
+ std::string l_message{
+ "file system call failed for file: " + i_pathToJson};
+
+ if (l_ec)
+ {
+ l_message += ", error: " + l_ec.message();
+ }
+ throw std::runtime_error(l_message);
+ }
+
+ if (std::filesystem::is_empty(i_pathToJson, l_ec))
+ {
+ throw std::runtime_error("Empty file: " + i_pathToJson);
+ }
+ else if (l_ec)
+ {
+ throw std::runtime_error("is_empty file system call failed for file: " +
+ i_pathToJson + ", error: " + l_ec.message());
+ }
+
+ std::ifstream l_jsonFile(i_pathToJson);
+ if (!l_jsonFile)
+ {
+ throw std::runtime_error("Failed to access Json path: " + i_pathToJson);
+ }
+
+ try
+ {
+ return nlohmann::json::parse(l_jsonFile);
+ }
+ catch (const nlohmann::json::parse_error& l_ex)
+ {
+ throw std::runtime_error("Failed to parse JSON file: " + i_pathToJson);
+ }
+}
+
+/**
+ * @brief API to get list of interfaces under a given object path.
+ *
+ * Given a DBus object path, this API returns a map of service -> implemented
+ * interface(s) under that object path. This API calls DBus method GetObject
+ * hosted by ObjectMapper DBus service.
+ *
+ * @param[in] i_objectPath - DBus object path.
+ * @param[in] i_constrainingInterfaces - An array of result set constraining
+ * interfaces.
+ *
+ * @return On success, returns a map of service -> implemented interface(s),
+ * else returns an empty map. The caller of this
+ * API should check for empty map.
+ */
+inline types::MapperGetObject GetServiceInterfacesForObject(
+ const std::string& i_objectPath,
+ const std::vector<std::string>& i_constrainingInterfaces) noexcept
+{
+ types::MapperGetObject l_serviceInfMap;
+ if (i_objectPath.empty())
+ {
+ // TODO: log only when verbose is enabled
+ std::cerr << "Object path is empty." << std::endl;
+ return l_serviceInfMap;
+ }
+
+ try
+ {
+ auto l_bus = sdbusplus::bus::new_default();
+ auto l_method = l_bus.new_method_call(
+ constants::objectMapperService, constants::objectMapperObjectPath,
+ constants::objectMapperInfName, "GetObject");
+
+ l_method.append(i_objectPath, i_constrainingInterfaces);
+
+ auto l_result = l_bus.call(l_method);
+ l_result.read(l_serviceInfMap);
+ }
+ catch (const sdbusplus::exception::SdBusError& l_ex)
+ {
+ // TODO: log only when verbose is enabled
+ // std::cerr << std::string(l_ex.what()) << std::endl;
+ }
+ return l_serviceInfMap;
+}
+
+/** @brief API to get list of sub tree paths for a given object path
+ *
+ * Given a DBus object path, this API returns a list of object paths under that
+ * object path in the DBus tree. This API calls DBus method GetSubTreePaths
+ * hosted by ObjectMapper DBus service.
+ *
+ * @param[in] i_objectPath - DBus object path.
+ * @param[in] i_constrainingInterfaces - An array of result set constraining
+ * interfaces.
+ * @param[in] i_depth - The maximum subtree depth for which results should be
+ * fetched. For unconstrained fetches use a depth of zero.
+ *
+ * @return On success, returns a std::vector<std::string> of object paths in
+ * Phosphor Inventory Manager DBus service's tree, else returns an empty vector.
+ * The caller of this API should check for empty vector.
+ */
+inline std::vector<std::string> GetSubTreePaths(
+ const std::string i_objectPath, const int i_depth = 0,
+ const std::vector<std::string>& i_constrainingInterfaces = {}) noexcept
+{
+ std::vector<std::string> l_objectPaths;
+
+ try
+ {
+ auto l_bus = sdbusplus::bus::new_default();
+ auto l_method = l_bus.new_method_call(
+ constants::objectMapperService, constants::objectMapperObjectPath,
+ constants::objectMapperInfName, "GetSubTreePaths");
+
+ l_method.append(i_objectPath, i_depth, i_constrainingInterfaces);
+
+ auto l_result = l_bus.call(l_method);
+ l_result.read(l_objectPaths);
+ }
+ catch (const sdbusplus::exception::SdBusError& l_ex)
+ {
+ // TODO: log only when verbose is enabled
+ std::cerr << std::string(l_ex.what()) << std::endl;
+ }
+ return l_objectPaths;
+}
+
+/**
+ * @brief A class to print data in tabular format
+ *
+ * This class implements methods to print data in a two dimensional tabular
+ * format. All entries in the table must be in string format.
+ *
+ */
+class Table
+{
+ class Column : public types::TableColumnNameSizePair
+ {
+ public:
+ /**
+ * @brief API to get the name of the Column
+ *
+ * @return Name of the Column.
+ */
+ const std::string& Name() const
+ {
+ return this->first;
+ }
+
+ /**
+ * @brief API to get the width of the Column
+ *
+ * @return Width of the Column.
+ */
+ std::size_t Width() const
+ {
+ return this->second;
+ }
+ };
+
+ // Current width of the table
+ std::size_t m_currentWidth;
+
+ // Character to be used as fill character between entries
+ char m_fillCharacter;
+
+ // Separator character to be used between columns
+ char m_separator;
+
+ // Array of columns
+ std::vector<Column> m_columns;
+
+ /**
+ * @brief API to Print Header
+ *
+ * Header line prints the names of the Column headers separated by the
+ * specified separator character and spaced accordingly.
+ *
+ * @throw std::out_of_range, std::length_error, std::bad_alloc
+ */
+ void PrintHeader() const
+ {
+ for (const auto& l_column : m_columns)
+ {
+ PrintEntry(l_column.Name(), l_column.Width());
+ }
+ std::cout << m_separator << std::endl;
+ }
+
+ /**
+ * @brief API to Print Horizontal Line
+ *
+ * A horizontal line is a sequence of '*'s.
+ *
+ * @throw std::out_of_range, std::length_error, std::bad_alloc
+ */
+ void PrintHorizontalLine() const
+ {
+ std::cout << std::string(m_currentWidth, '*') << std::endl;
+ }
+
+ /**
+ * @brief API to print an entry in the table
+ *
+ * An entry is a separator character followed by the text to print.
+ * The text is centre-aligned.
+ *
+ * @param[in] i_text - text to print
+ * @param[in] i_columnWidth - width of the column
+ *
+ * @throw std::out_of_range, std::length_error, std::bad_alloc
+ */
+ void PrintEntry(const std::string& i_text, std::size_t i_columnWidth) const
+ {
+ const std::size_t l_textLength{i_text.length()};
+
+ constexpr std::size_t l_minFillChars{3};
+ const std::size_t l_numFillChars =
+ ((l_textLength >= i_columnWidth ? l_minFillChars
+ : i_columnWidth - l_textLength)) -
+ 1; // -1 for the separator character
+
+ const unsigned l_oddFill = l_numFillChars % 2;
+
+ std::cout << m_separator
+ << std::string((l_numFillChars / 2) + l_oddFill,
+ m_fillCharacter)
+ << i_text << std::string(l_numFillChars / 2, m_fillCharacter);
+ }
+
+ public:
+ /**
+ * @brief Table Constructor
+ *
+ * Parameterized constructor for a Table object
+ *
+ */
+ constexpr explicit Table(const char i_fillCharacter = ' ',
+ const char i_separator = '|') noexcept :
+ m_currentWidth{0}, m_fillCharacter{i_fillCharacter},
+ m_separator{i_separator}
+ {}
+
+ // deleted methods
+ Table(const Table&) = delete;
+ Table operator=(const Table&) = delete;
+ Table(const Table&&) = delete;
+ Table operator=(const Table&&) = delete;
+
+ ~Table() = default;
+
+ /**
+ * @brief API to add column to Table
+ *
+ * @param[in] i_name - Name of the column.
+ *
+ * @param[in] i_width - Width to allocate for the column.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ */
+ int AddColumn(const std::string& i_name, std::size_t i_width)
+ {
+ if (i_width < i_name.length())
+ return constants::FAILURE;
+ m_columns.emplace_back(types::TableColumnNameSizePair(i_name, i_width));
+ m_currentWidth += i_width;
+ return constants::SUCCESS;
+ }
+
+ /**
+ * @brief API to print the Table to console.
+ *
+ * This API prints the table data to console.
+ *
+ * @param[in] i_tableData - The data to be printed.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ *
+ * @throw std::out_of_range, std::length_error, std::bad_alloc
+ */
+ int Print(const types::TableInputData& i_tableData) const
+ {
+ PrintHorizontalLine();
+ PrintHeader();
+ PrintHorizontalLine();
+
+ // print the table data
+ for (const auto& l_row : i_tableData)
+ {
+ unsigned l_columnNumber{0};
+
+ // number of columns in input data is greater than the number of
+ // columns specified in Table
+ if (l_row.size() > m_columns.size())
+ {
+ return constants::FAILURE;
+ }
+
+ for (const auto& l_entry : l_row)
+ {
+ PrintEntry(l_entry, m_columns[l_columnNumber].Width());
+
+ ++l_columnNumber;
+ }
+ std::cout << m_separator << std::endl;
+ }
+ PrintHorizontalLine();
+ return constants::SUCCESS;
+ }
+};
+
+} // namespace utils
+} // namespace vpd
diff --git a/vpd-tool/include/vpd_tool.hpp b/vpd-tool/include/vpd_tool.hpp
new file mode 100644
index 0000000..80be8e5
--- /dev/null
+++ b/vpd-tool/include/vpd_tool.hpp
@@ -0,0 +1,287 @@
+#pragma once
+
+#include "tool_utils.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <optional>
+#include <string>
+
+namespace vpd
+{
+/**
+ * @brief Class to support operations on VPD.
+ *
+ * The class provides API's to,
+ * Read keyword value from DBus/hardware.
+ * Update keyword value to DBus/hardware.
+ * Dump DBus object's critical information.
+ * Fix system VPD if any mismatch between DBus and hardware data.
+ * Reset specific system VPD keywords to its default value.
+ * Force VPD collection for hardware.
+ */
+class VpdTool
+{
+ private:
+ /**
+ * @brief Get specific properties of a FRU in JSON format.
+ *
+ * For a given object path of a FRU, this API returns the following
+ * properties of the FRU in JSON format:
+ * - Pretty Name, Location Code, Sub Model
+ * - SN, PN, CC, FN, DR keywords under VINI record.
+ *
+ * @param[in] i_objectPath - DBus object path
+ *
+ * @return On success, returns the properties of the FRU in JSON format,
+ * otherwise returns an empty JSON.
+ * If FRU's "Present" property is false, this API returns an empty JSON.
+ * Note: The caller of this API should handle empty JSON.
+ *
+ * @throw json::exception
+ */
+ nlohmann::json getFruProperties(const std::string& i_objectPath) const;
+
+ /**
+ * @brief Get any inventory property in JSON.
+ *
+ * API to get any property of a FRU in JSON format. Given an object path,
+ * interface and property name, this API does a D-Bus read property on PIM
+ * to get the value of that property and returns it in JSON format. This API
+ * returns empty JSON in case of failure. The caller of the API must check
+ * for empty JSON.
+ *
+ * @param[in] i_objectPath - DBus object path
+ * @param[in] i_interface - Interface name
+ * @param[in] i_propertyName - Property name
+ *
+ * @return On success, returns the property and its value in JSON format,
+ * otherwise return empty JSON.
+ * {"SN" : "ABCD"}
+ */
+ template <typename PropertyType>
+ nlohmann::json getInventoryPropertyJson(
+ const std::string& i_objectPath, const std::string& i_interface,
+ const std::string& i_propertyName) const noexcept;
+
+ /**
+ * @brief Get the "type" property for a FRU.
+ *
+ * Given a FRU path, and parsed System Config JSON, this API returns the
+ * "type" property for the FRU in JSON format. This API gets
+ * these properties from Phosphor Inventory Manager.
+ *
+ * @param[in] i_objectPath - DBus object path.
+ *
+ * @return On success, returns the "type" property in JSON
+ * format, otherwise returns empty JSON. The caller of this API should
+ * handle empty JSON.
+ */
+ nlohmann::json
+ getFruTypeProperty(const std::string& i_objectPath) const noexcept;
+
+ /**
+ * @brief Check if a FRU is present in the system.
+ *
+ * Given a FRU's object path, this API checks if the FRU is present in the
+ * system by reading the "Present" property of the FRU.
+ *
+ * @param[in] i_objectPath - DBus object path.
+ *
+ * @return true if FRU's "Present" property is true, false otherwise.
+ */
+ bool isFruPresent(const std::string& i_objectPath) const noexcept;
+
+ /**
+ * @brief An API to get backup-restore config JSON of the system.
+ *
+ * API gets this file by prasing system config JSON file and reading
+ * backupRestoreConfigPath tag.
+ *
+ * @return On success returns valid JSON object, otherwise returns empty
+ * JSON object.
+ *
+ * Note: The caller of this API should verify, is received JSON object is
+ * empty or not.
+ */
+ nlohmann::json getBackupRestoreCfgJsonObj() const noexcept;
+
+ /**
+ * @brief Prints the user options for fix system VPD command.
+ *
+ * @param[in] i_option - Option to use.
+ */
+ void printFixSystemVpdOption(
+ const types::UserOption& i_option) const noexcept;
+
+ /**
+ * @brief API to update source and destination keyword's value.
+ *
+ * API fetches source and destination keyword's value,
+ * for each keyword entries found in the input JSON object and updates the
+ * JSON object. If the path(source / destination) in JSON object is
+ * inventory object path, API sends the request to Inventory.Manager DBus
+ * service. Otherwise if its a hardware path, API sends the request to
+ * vpd-manager DBus service to get the keyword's value.
+ *
+ * @param[in,out] io_parsedJsonObj - Parsed JSON object.
+ *
+ * @return true on success, false in case of any error.
+ */
+ bool fetchKeywordInfo(nlohmann::json& io_parsedJsonObj) const noexcept;
+
+ /**
+ * @brief API to print system VPD keyword's information.
+ *
+ * The API prints source and destination keyword's information in the table
+ * format, found in the JSON object.
+ *
+ * @param[in] i_parsedJsonObj - Parsed JSON object.
+ */
+ void printSystemVpd(const nlohmann::json& i_parsedJsonObj) const noexcept;
+
+ /**
+ * @brief API to update keyword's value.
+ *
+ * API iterates the given JSON object for all record-keyword pairs, if there
+ * is any mismatch between source and destination keyword's value, API calls
+ * the utils::writeKeyword API to update keyword's value.
+ *
+ * Note: writeKeyword API, internally updates primary, backup, redundant
+ * EEPROM paths(if exists) with the given keyword's value.
+ *
+ * @param i_parsedJsonObj - Parsed JSON object.
+ * @param i_useBackupData - Specifies whether to use source or destination
+ * keyword's value to update the keyword's value.
+ *
+ * @return On success return 0, otherwise return -1.
+ */
+ int updateAllKeywords(const nlohmann::json& i_parsedJsonObj,
+ bool i_useBackupData) const noexcept;
+
+ /**
+ * @brief API to handle more option for fix system VPD command.
+ *
+ * @param i_parsedJsonObj - Parsed JSON object.
+ *
+ * @return On success return 0, otherwise return -1.
+ */
+ int handleMoreOption(const nlohmann::json& i_parsedJsonObj) const noexcept;
+
+ public:
+ /**
+ * @brief Read keyword value.
+ *
+ * API to read VPD keyword's value from the given input path.
+ * If the provided i_onHardware option is true, read keyword's value from
+ * the hardware. Otherwise read keyword's value from DBus.
+ *
+ * @param[in] i_vpdPath - DBus object path or EEPROM path.
+ * @param[in] i_recordName - Record name.
+ * @param[in] i_keywordName - Keyword name.
+ * @param[in] i_onHardware - True if i_vpdPath is EEPROM path, false
+ * otherwise.
+ * @param[in] i_fileToSave - File path to save keyword's value, if not given
+ * result will redirect to a console.
+ *
+ * @return On success return 0, otherwise return -1.
+ */
+ int readKeyword(const std::string& i_vpdPath,
+ const std::string& i_recordName,
+ const std::string& i_keywordName, const bool i_onHardware,
+ const std::string& i_fileToSave = {});
+
+ /**
+ * @brief Dump the given inventory object in JSON format to console.
+ *
+ * For a given object path of a FRU, this API dumps the following properties
+ * of the FRU in JSON format to console:
+ * - Pretty Name, Location Code, Sub Model
+ * - SN, PN, CC, FN, DR keywords under VINI record.
+ * If the FRU's "Present" property is not true, the above properties are not
+ * dumped to console.
+ *
+ * @param[in] i_fruPath - DBus object path.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ */
+ int dumpObject(std::string i_fruPath) const noexcept;
+
+ /**
+ * @brief API to fix system VPD keywords.
+ *
+ * The API to fix the system VPD keywords. Mainly used when there
+ * is a mismatch between the primary and backup(secondary) VPD. User can
+ * choose option to update all primary keywords value with corresponding
+ * backup keywords value or can choose primary keyword value to sync
+ * secondary VPD. Otherwise, user can also interactively choose different
+ * action for individual keyword.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ */
+ int fixSystemVpd() const noexcept;
+
+ /**
+ * @brief Write keyword's value.
+ *
+ * API to update VPD keyword's value to the given input path.
+ * If i_onHardware value in true, i_vpdPath is considered has hardware path
+ * otherwise it will be considered as DBus object path.
+ *
+ * For provided DBus object path both primary path or secondary path will
+ * get updated, also redundant EEPROM(if any) path with new keyword's value.
+ *
+ * In case of hardware path, only given hardware path gets updated with new
+ * keyword’s value, any backup or redundant EEPROM (if exists) paths won't
+ * get updated.
+ *
+ * @param[in] i_vpdPath - DBus object path or EEPROM path.
+ * @param[in] i_recordName - Record name.
+ * @param[in] i_keywordName - Keyword name.
+ * @param[in] i_keywordValue - Keyword value.
+ * @param[in] i_onHardware - True if i_vpdPath is EEPROM path, false
+ * otherwise.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ */
+ int writeKeyword(std::string i_vpdPath, const std::string& i_recordName,
+ const std::string& i_keywordName,
+ const std::string& i_keywordValue,
+ const bool i_onHardware) noexcept;
+
+ /**
+ * @brief Reset specific keywords on System VPD to default value.
+ *
+ * This API resets specific System VPD keywords to default value. The
+ * keyword values are reset on:
+ * 1. Primary EEPROM path.
+ * 2. Secondary EEPROM path.
+ * 3. D-Bus cache.
+ * 4. Backup path.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ */
+ int cleanSystemVpd() const noexcept;
+
+ /**
+ * @brief Dump all the inventory objects in JSON or table format to console.
+ *
+ * This API dumps specific properties of all the inventory objects to
+ * console in JSON or table format to console. The inventory object paths
+ * are extracted from PIM. For each object, the following properties are
+ * dumped to console:
+ * - Present property, Pretty Name, Location Code, Sub Model
+ * - SN, PN, CC, FN, DR keywords under VINI record.
+ * If the "Present" property of a FRU is false, the FRU is not dumped to
+ * console.
+ * FRUs whose object path end in "unit([0-9][0-9]?)" are also not dumped to
+ * console.
+ *
+ * @param[in] i_dumpTable - Flag which specifies if the inventory should be
+ * dumped in table format or not.
+ *
+ * @return On success returns 0, otherwise returns -1.
+ */
+ int dumpInventory(bool i_dumpTable = false) const noexcept;
+};
+} // namespace vpd
diff --git a/vpd-tool/meson.build b/vpd-tool/meson.build
new file mode 100644
index 0000000..a2c1ea1
--- /dev/null
+++ b/vpd-tool/meson.build
@@ -0,0 +1,19 @@
+compiler = meson.get_compiler('cpp')
+if compiler.has_header('CLI/CLI.hpp')
+ CLI11_dep = declare_dependency()
+else
+ CLI11_dep = dependency('CLI11')
+endif
+
+sdbusplus = dependency('sdbusplus', fallback: [ 'sdbusplus', 'sdbusplus_dep' ])
+dependency_list = [CLI11_dep, sdbusplus]
+
+sources = ['src/vpd_tool_main.cpp',
+ 'src/vpd_tool.cpp']
+
+vpd_tool_exe = executable('vpd-tool',
+ sources,
+ include_directories : ['../', 'include/'],
+ dependencies: dependency_list,
+ install: true
+ )
\ No newline at end of file
diff --git a/vpd-tool/src/vpd_tool.cpp b/vpd-tool/src/vpd_tool.cpp
new file mode 100644
index 0000000..f6e4cd9
--- /dev/null
+++ b/vpd-tool/src/vpd_tool.cpp
@@ -0,0 +1,1195 @@
+#include "config.h"
+
+#include "vpd_tool.hpp"
+
+#include "tool_constants.hpp"
+#include "tool_types.hpp"
+#include "tool_utils.hpp"
+
+#include <iostream>
+#include <regex>
+#include <tuple>
+namespace vpd
+{
+int VpdTool::readKeyword(
+ const std::string& i_vpdPath, const std::string& i_recordName,
+ const std::string& i_keywordName, const bool i_onHardware,
+ const std::string& i_fileToSave)
+{
+ int l_rc = constants::FAILURE;
+ try
+ {
+ types::DbusVariantType l_keywordValue;
+ if (i_onHardware)
+ {
+ l_keywordValue = utils::readKeywordFromHardware(
+ i_vpdPath, std::make_tuple(i_recordName, i_keywordName));
+ }
+ else
+ {
+ std::string l_inventoryObjectPath(
+ constants::baseInventoryPath + i_vpdPath);
+
+ l_keywordValue = utils::readDbusProperty(
+ constants::inventoryManagerService, l_inventoryObjectPath,
+ constants::ipzVpdInfPrefix + i_recordName, i_keywordName);
+ }
+
+ if (const auto l_value =
+ std::get_if<types::BinaryVector>(&l_keywordValue);
+ l_value && !l_value->empty())
+ {
+ // ToDo: Print value in both ASCII and hex formats
+ const std::string& l_keywordStrValue =
+ utils::getPrintableValue(*l_value);
+
+ if (i_fileToSave.empty())
+ {
+ utils::displayOnConsole(i_vpdPath, i_keywordName,
+ l_keywordStrValue);
+ l_rc = constants::SUCCESS;
+ }
+ else
+ {
+ if (utils::saveToFile(i_fileToSave, l_keywordStrValue))
+ {
+ std::cout
+ << "Value read is saved on the file: " << i_fileToSave
+ << std::endl;
+ l_rc = constants::SUCCESS;
+ }
+ else
+ {
+ std::cerr
+ << "Error while saving the read value on the file: "
+ << i_fileToSave
+ << "\nDisplaying the read value on console"
+ << std::endl;
+ utils::displayOnConsole(i_vpdPath, i_keywordName,
+ l_keywordStrValue);
+ }
+ }
+ }
+ else
+ {
+ // TODO: Enable logging when verbose is enabled.
+ // std::cout << "Invalid data type or empty data received." <<
+ // std::endl;
+ }
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ /*std::cerr << "Read keyword's value for path: " << i_vpdPath
+ << ", Record: " << i_recordName
+ << ", Keyword: " << i_keywordName
+ << " is failed, exception: " << l_ex.what() << std::endl;*/
+ }
+ return l_rc;
+}
+
+int VpdTool::dumpObject(std::string i_fruPath) const noexcept
+{
+ int l_rc{constants::FAILURE};
+ try
+ {
+ // ToDo: For PFuture system take only full path from the user.
+ i_fruPath = constants::baseInventoryPath + i_fruPath;
+
+ nlohmann::json l_resultJsonArray = nlohmann::json::array({});
+ const nlohmann::json l_fruJson = getFruProperties(i_fruPath);
+ if (!l_fruJson.empty())
+ {
+ l_resultJsonArray += l_fruJson;
+
+ utils::printJson(l_resultJsonArray);
+ }
+ else
+ {
+ std::cout << "FRU [" << i_fruPath
+ << "] is not present in the system" << std::endl;
+ }
+ l_rc = constants::SUCCESS;
+ }
+ catch (std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ // std::cerr << "Dump Object failed for FRU [" << i_fruPath
+ // << "], Error: " << l_ex.what() << std::endl;
+ }
+ return l_rc;
+}
+
+nlohmann::json VpdTool::getFruProperties(const std::string& i_objectPath) const
+{
+ // check if FRU is present in the system
+ if (!isFruPresent(i_objectPath))
+ {
+ return nlohmann::json::object_t();
+ }
+
+ nlohmann::json l_fruJson = nlohmann::json::object_t({});
+
+ l_fruJson.emplace(i_objectPath, nlohmann::json::object_t({}));
+
+ auto& l_fruObject = l_fruJson[i_objectPath];
+
+ const auto l_prettyNameInJson = getInventoryPropertyJson<std::string>(
+ i_objectPath, constants::inventoryItemInf, "PrettyName");
+ if (!l_prettyNameInJson.empty())
+ {
+ l_fruObject.insert(l_prettyNameInJson.cbegin(),
+ l_prettyNameInJson.cend());
+ }
+
+ const auto l_locationCodeInJson = getInventoryPropertyJson<std::string>(
+ i_objectPath, constants::locationCodeInf, "LocationCode");
+ if (!l_locationCodeInJson.empty())
+ {
+ l_fruObject.insert(l_locationCodeInJson.cbegin(),
+ l_locationCodeInJson.cend());
+ }
+
+ const auto l_subModelInJson = getInventoryPropertyJson<std::string>(
+ i_objectPath, constants::assetInf, "SubModel");
+
+ if (!l_subModelInJson.empty() &&
+ !l_subModelInJson.value("SubModel", "").empty())
+ {
+ l_fruObject.insert(l_subModelInJson.cbegin(), l_subModelInJson.cend());
+ }
+
+ // Get the properties under VINI interface.
+
+ nlohmann::json l_viniPropertiesInJson = nlohmann::json::object({});
+
+ auto l_readViniKeyWord = [i_objectPath, &l_viniPropertiesInJson,
+ this](const std::string& i_keyWord) {
+ const nlohmann::json l_keyWordJson =
+ getInventoryPropertyJson<vpd::types::BinaryVector>(
+ i_objectPath, constants::kwdVpdInf, i_keyWord);
+ l_viniPropertiesInJson.insert(l_keyWordJson.cbegin(),
+ l_keyWordJson.cend());
+ };
+
+ const std::vector<std::string> l_viniKeywords = {"SN", "PN", "CC", "FN",
+ "DR"};
+
+ std::for_each(l_viniKeywords.cbegin(), l_viniKeywords.cend(),
+ l_readViniKeyWord);
+
+ if (!l_viniPropertiesInJson.empty())
+ {
+ l_fruObject.insert(l_viniPropertiesInJson.cbegin(),
+ l_viniPropertiesInJson.cend());
+ }
+
+ const auto l_typePropertyJson = getFruTypeProperty(i_objectPath);
+ if (!l_typePropertyJson.empty())
+ {
+ l_fruObject.insert(l_typePropertyJson.cbegin(),
+ l_typePropertyJson.cend());
+ }
+
+ return l_fruJson;
+}
+
+template <typename PropertyType>
+nlohmann::json VpdTool::getInventoryPropertyJson(
+ const std::string& i_objectPath, const std::string& i_interface,
+ const std::string& i_propertyName) const noexcept
+{
+ nlohmann::json l_resultInJson = nlohmann::json::object({});
+ try
+ {
+ types::DbusVariantType l_keyWordValue;
+
+ l_keyWordValue =
+ utils::readDbusProperty(constants::inventoryManagerService,
+ i_objectPath, i_interface, i_propertyName);
+
+ if (const auto l_value = std::get_if<PropertyType>(&l_keyWordValue))
+ {
+ if constexpr (std::is_same<PropertyType, std::string>::value)
+ {
+ l_resultInJson.emplace(i_propertyName, *l_value);
+ }
+ else if constexpr (std::is_same<PropertyType, bool>::value)
+ {
+ l_resultInJson.emplace(i_propertyName,
+ *l_value ? "true" : "false");
+ }
+ else if constexpr (std::is_same<PropertyType,
+ types::BinaryVector>::value)
+ {
+ const std::string& l_keywordStrValue =
+ vpd::utils::getPrintableValue(*l_value);
+
+ l_resultInJson.emplace(i_propertyName, l_keywordStrValue);
+ }
+ }
+ else
+ {
+ // TODO: Enable logging when verbose is enabled.
+ // std::cout << "Invalid data type received." << std::endl;
+ }
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ /*std::cerr << "Read " << i_propertyName << " value for FRU path: " <<
+ i_objectPath
+ << ", failed with exception: " << l_ex.what() << std::endl;*/
+ }
+ return l_resultInJson;
+}
+
+int VpdTool::fixSystemVpd() const noexcept
+{
+ int l_rc = constants::FAILURE;
+
+ nlohmann::json l_backupRestoreCfgJsonObj = getBackupRestoreCfgJsonObj();
+ if (!fetchKeywordInfo(l_backupRestoreCfgJsonObj))
+ {
+ return l_rc;
+ }
+
+ printSystemVpd(l_backupRestoreCfgJsonObj);
+
+ do
+ {
+ printFixSystemVpdOption(types::UserOption::UseBackupDataForAll);
+ printFixSystemVpdOption(
+ types::UserOption::UseSystemBackplaneDataForAll);
+ printFixSystemVpdOption(types::UserOption::MoreOptions);
+ printFixSystemVpdOption(types::UserOption::Exit);
+
+ int l_userSelectedOption = types::UserOption::Exit;
+ std::cin >> l_userSelectedOption;
+
+ std::cout << std::endl << std::string(191, '=') << std::endl;
+
+ if (types::UserOption::UseBackupDataForAll == l_userSelectedOption)
+ {
+ l_rc = updateAllKeywords(l_backupRestoreCfgJsonObj, true);
+ break;
+ }
+ else if (types::UserOption::UseSystemBackplaneDataForAll ==
+ l_userSelectedOption)
+ {
+ l_rc = updateAllKeywords(l_backupRestoreCfgJsonObj, false);
+ break;
+ }
+ else if (types::UserOption::MoreOptions == l_userSelectedOption)
+ {
+ l_rc = handleMoreOption(l_backupRestoreCfgJsonObj);
+ break;
+ }
+ else if (types::UserOption::Exit == l_userSelectedOption)
+ {
+ std::cout << "Exit successfully" << std::endl;
+ break;
+ }
+ else
+ {
+ std::cout << "Provide a valid option. Retry." << std::endl;
+ }
+ } while (true);
+
+ return l_rc;
+}
+
+int VpdTool::writeKeyword(
+ std::string i_vpdPath, const std::string& i_recordName,
+ const std::string& i_keywordName, const std::string& i_keywordValue,
+ const bool i_onHardware) noexcept
+{
+ int l_rc = constants::FAILURE;
+ try
+ {
+ if (i_vpdPath.empty() || i_recordName.empty() ||
+ i_keywordName.empty() || i_keywordValue.empty())
+ {
+ throw std::runtime_error("Received input is empty.");
+ }
+
+ auto l_paramsToWrite =
+ std::make_tuple(i_recordName, i_keywordName,
+ utils::convertToBinary(i_keywordValue));
+
+ if (i_onHardware)
+ {
+ l_rc = utils::writeKeywordOnHardware(i_vpdPath, l_paramsToWrite);
+ }
+ else
+ {
+ i_vpdPath = constants::baseInventoryPath + i_vpdPath;
+ l_rc = utils::writeKeyword(i_vpdPath, l_paramsToWrite);
+ }
+
+ if (l_rc > 0)
+ {
+ std::cout << "Data updated successfully " << std::endl;
+ l_rc = constants::SUCCESS;
+ }
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable log when verbose is enabled.
+ std::cerr << "Write keyword's value for path: " << i_vpdPath
+ << ", Record: " << i_recordName
+ << ", Keyword: " << i_keywordName
+ << " is failed. Exception: " << l_ex.what() << std::endl;
+ }
+ return l_rc;
+}
+
+nlohmann::json VpdTool::getBackupRestoreCfgJsonObj() const noexcept
+{
+ nlohmann::json l_parsedBackupRestoreJson{};
+ try
+ {
+ nlohmann::json l_parsedSystemJson =
+ utils::getParsedJson(INVENTORY_JSON_SYM_LINK);
+
+ // check for mandatory fields at this point itself.
+ if (!l_parsedSystemJson.contains("backupRestoreConfigPath"))
+ {
+ throw std::runtime_error(
+ "backupRestoreConfigPath tag is missing from system config JSON : " +
+ std::string(INVENTORY_JSON_SYM_LINK));
+ }
+
+ l_parsedBackupRestoreJson =
+ utils::getParsedJson(l_parsedSystemJson["backupRestoreConfigPath"]);
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << l_ex.what() << std::endl;
+ }
+
+ return l_parsedBackupRestoreJson;
+}
+
+int VpdTool::cleanSystemVpd() const noexcept
+{
+ try
+ {
+ // get the keyword map from backup_restore json
+ // iterate through the keyword map get default value of
+ // l_keywordName.
+ // use writeKeyword API to update default value on hardware,
+ // backup and D - Bus.
+ const nlohmann::json l_parsedBackupRestoreJson =
+ getBackupRestoreCfgJsonObj();
+
+ // check for mandatory tags
+ if (l_parsedBackupRestoreJson.contains("source") &&
+ l_parsedBackupRestoreJson.contains("backupMap") &&
+ l_parsedBackupRestoreJson["source"].contains("hardwarePath") &&
+ l_parsedBackupRestoreJson["backupMap"].is_array())
+ {
+ // get the source hardware path
+ const auto& l_hardwarePath =
+ l_parsedBackupRestoreJson["source"]["hardwarePath"];
+
+ // iterate through the backup map
+ for (const auto& l_aRecordKwInfo :
+ l_parsedBackupRestoreJson["backupMap"])
+ {
+ // check if Manufacturing Reset is required for this entry
+ const bool l_isMfgCleanRequired =
+ l_aRecordKwInfo.value("isManufactureResetRequired", false);
+
+ if (l_isMfgCleanRequired)
+ {
+ // get the Record name and Keyword name
+ const std::string& l_srcRecordName =
+ l_aRecordKwInfo.value("sourceRecord", "");
+ const std::string& l_srcKeywordName =
+ l_aRecordKwInfo.value("sourceKeyword", "");
+
+ // validate the Record name, Keyword name and the
+ // defaultValue
+ if (!l_srcRecordName.empty() && !l_srcKeywordName.empty() &&
+ l_aRecordKwInfo.contains("defaultValue") &&
+ l_aRecordKwInfo["defaultValue"].is_array())
+ {
+ const types::BinaryVector l_defaultBinaryValue =
+ l_aRecordKwInfo["defaultValue"]
+ .get<types::BinaryVector>();
+
+ // update the Keyword with default value, use D-Bus
+ // method UpdateKeyword exposed by vpd-manager.
+ // Note: writing to all paths (Primary EEPROM path,
+ // Secondary EEPROM path, D-Bus cache and Backup path)
+ // is the responsibility of vpd-manager's UpdateKeyword
+ // API
+ if (constants::FAILURE ==
+ utils::writeKeyword(
+ l_hardwarePath,
+ std::make_tuple(l_srcRecordName,
+ l_srcKeywordName,
+ l_defaultBinaryValue)))
+ {
+ // TODO: Enable logging when verbose
+ // is enabled.
+ std::cerr << "Failed to update " << l_srcRecordName
+ << ":" << l_srcKeywordName << std::endl;
+ }
+ }
+ else
+ {
+ std::cerr
+ << "Unrecognized Entry Record [" << l_srcRecordName
+ << "] Keyword [" << l_srcKeywordName
+ << "] in Backup Restore JSON backup map"
+ << std::endl;
+ }
+ } // mfgClean required check
+ } // keyword list loop
+ }
+ else // backupRestoreJson is not valid
+ {
+ std::cerr << "Backup Restore JSON is not valid" << std::endl;
+ }
+
+ // success/failure message
+ std::cout << "The critical keywords from system backplane VPD has "
+ "been reset successfully."
+ << std::endl;
+
+ } // try block end
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr
+ << "Manufacturing reset on system vpd keywords is unsuccessful. Error : "
+ << l_ex.what() << std::endl;
+ }
+ return constants::SUCCESS;
+}
+
+bool VpdTool::fetchKeywordInfo(nlohmann::json& io_parsedJsonObj) const noexcept
+{
+ bool l_returnValue = false;
+ try
+ {
+ if (io_parsedJsonObj.empty() || !io_parsedJsonObj.contains("source") ||
+ !io_parsedJsonObj.contains("destination") ||
+ !io_parsedJsonObj.contains("backupMap"))
+ {
+ throw std::runtime_error("Invalid JSON");
+ }
+
+ std::string l_srcVpdPath;
+ std::string l_dstVpdPath;
+
+ bool l_isSourceOnHardware = false;
+ if (l_srcVpdPath = io_parsedJsonObj["source"].value("hardwarePath", "");
+ !l_srcVpdPath.empty())
+ {
+ l_isSourceOnHardware = true;
+ }
+ else if (l_srcVpdPath =
+ io_parsedJsonObj["source"].value("inventoryPath", "");
+ l_srcVpdPath.empty())
+ {
+ throw std::runtime_error("Source path is empty in JSON");
+ }
+
+ bool l_isDestinationOnHardware = false;
+ if (l_dstVpdPath =
+ io_parsedJsonObj["destination"].value("hardwarePath", "");
+ !l_dstVpdPath.empty())
+ {
+ l_isDestinationOnHardware = true;
+ }
+ else if (l_dstVpdPath =
+ io_parsedJsonObj["destination"].value("inventoryPath", "");
+ l_dstVpdPath.empty())
+ {
+ throw std::runtime_error("Destination path is empty in JSON");
+ }
+
+ for (auto& l_aRecordKwInfo : io_parsedJsonObj["backupMap"])
+ {
+ const std::string& l_srcRecordName =
+ l_aRecordKwInfo.value("sourceRecord", "");
+ const std::string& l_srcKeywordName =
+ l_aRecordKwInfo.value("sourceKeyword", "");
+ const std::string& l_dstRecordName =
+ l_aRecordKwInfo.value("destinationRecord", "");
+ const std::string& l_dstKeywordName =
+ l_aRecordKwInfo.value("destinationKeyword", "");
+
+ if (l_srcRecordName.empty() || l_dstRecordName.empty() ||
+ l_srcKeywordName.empty() || l_dstKeywordName.empty())
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cout << "Record or keyword not found in the JSON."
+ << std::endl;
+ continue;
+ }
+
+ types::DbusVariantType l_srcKeywordVariant;
+ if (l_isSourceOnHardware)
+ {
+ l_srcKeywordVariant = utils::readKeywordFromHardware(
+ l_srcVpdPath,
+ std::make_tuple(l_srcRecordName, l_srcKeywordName));
+ }
+ else
+ {
+ l_srcKeywordVariant = utils::readDbusProperty(
+ constants::inventoryManagerService, l_srcVpdPath,
+ constants::ipzVpdInfPrefix + l_srcRecordName,
+ l_srcKeywordName);
+ }
+
+ if (auto l_srcKeywordValue =
+ std::get_if<types::BinaryVector>(&l_srcKeywordVariant);
+ l_srcKeywordValue && !l_srcKeywordValue->empty())
+ {
+ l_aRecordKwInfo["sourcekeywordValue"] = *l_srcKeywordValue;
+ }
+ else
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cout
+ << "Invalid data type or empty data received, for source record: "
+ << l_srcRecordName << ", keyword: " << l_srcKeywordName
+ << std::endl;
+ continue;
+ }
+
+ types::DbusVariantType l_dstKeywordVariant;
+ if (l_isDestinationOnHardware)
+ {
+ l_dstKeywordVariant = utils::readKeywordFromHardware(
+ l_dstVpdPath,
+ std::make_tuple(l_dstRecordName, l_dstKeywordName));
+ }
+ else
+ {
+ l_dstKeywordVariant = utils::readDbusProperty(
+ constants::inventoryManagerService, l_dstVpdPath,
+ constants::ipzVpdInfPrefix + l_dstRecordName,
+ l_dstKeywordName);
+ }
+
+ if (auto l_dstKeywordValue =
+ std::get_if<types::BinaryVector>(&l_dstKeywordVariant);
+ l_dstKeywordValue && !l_dstKeywordValue->empty())
+ {
+ l_aRecordKwInfo["destinationkeywordValue"] = *l_dstKeywordValue;
+ }
+ else
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cout
+ << "Invalid data type or empty data received, for destination record: "
+ << l_dstRecordName << ", keyword: " << l_dstKeywordName
+ << std::endl;
+ continue;
+ }
+ }
+
+ l_returnValue = true;
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << l_ex.what() << std::endl;
+ }
+
+ return l_returnValue;
+}
+
+nlohmann::json
+ VpdTool::getFruTypeProperty(const std::string& i_objectPath) const noexcept
+{
+ nlohmann::json l_resultInJson = nlohmann::json::object({});
+ std::vector<std::string> l_pimInfList;
+
+ auto l_serviceInfMap = utils::GetServiceInterfacesForObject(
+ i_objectPath, std::vector<std::string>{constants::inventoryItemInf});
+ if (l_serviceInfMap.contains(constants::inventoryManagerService))
+ {
+ l_pimInfList = l_serviceInfMap[constants::inventoryManagerService];
+
+ // iterate through the list and find
+ // "xyz.openbmc_project.Inventory.Item.*"
+ for (const auto& l_interface : l_pimInfList)
+ {
+ if (l_interface.find(constants::inventoryItemInf) !=
+ std::string::npos &&
+ l_interface.length() >
+ std::string(constants::inventoryItemInf).length())
+ {
+ l_resultInJson.emplace("type", l_interface);
+ }
+ }
+ }
+ return l_resultInJson;
+}
+
+bool VpdTool::isFruPresent(const std::string& i_objectPath) const noexcept
+{
+ bool l_returnValue{false};
+ try
+ {
+ types::DbusVariantType l_keyWordValue;
+
+ l_keyWordValue = utils::readDbusProperty(
+ constants::inventoryManagerService, i_objectPath,
+ constants::inventoryItemInf, "Present");
+
+ if (const auto l_value = std::get_if<bool>(&l_keyWordValue))
+ {
+ l_returnValue = *l_value;
+ }
+ }
+ catch (const std::runtime_error& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ // std::cerr << "Failed to check \"Present\" property for FRU "
+ // << i_objectPath << " Error: " << l_ex.what() <<
+ // std::endl;
+ }
+ return l_returnValue;
+}
+
+void VpdTool::printFixSystemVpdOption(
+ const types::UserOption& i_option) const noexcept
+{
+ switch (i_option)
+ {
+ case types::UserOption::Exit:
+ std::cout << "Enter 0 => To exit successfully : ";
+ break;
+ case types::UserOption::UseBackupDataForAll:
+ std::cout << "Enter 1 => If you choose the data on backup for all "
+ "mismatching record-keyword pairs"
+ << std::endl;
+ break;
+ case types::UserOption::UseSystemBackplaneDataForAll:
+ std::cout << "Enter 2 => If you choose the data on primary for all "
+ "mismatching record-keyword pairs"
+ << std::endl;
+ break;
+ case types::UserOption::MoreOptions:
+ std::cout << "Enter 3 => If you wish to explore more options"
+ << std::endl;
+ break;
+ case types::UserOption::UseBackupDataForCurrent:
+ std::cout << "Enter 4 => If you choose the data on backup as the "
+ "right value"
+ << std::endl;
+ break;
+ case types::UserOption::UseSystemBackplaneDataForCurrent:
+ std::cout << "Enter 5 => If you choose the data on primary as the "
+ "right value"
+ << std::endl;
+ break;
+ case types::UserOption::NewValueOnBoth:
+ std::cout
+ << "Enter 6 => If you wish to enter a new value to update "
+ "both on backup and primary"
+ << std::endl;
+ break;
+ case types::UserOption::SkipCurrent:
+ std::cout << "Enter 7 => If you wish to skip the above "
+ "record-keyword pair"
+ << std::endl;
+ break;
+ }
+}
+
+int VpdTool::dumpInventory(bool i_dumpTable) const noexcept
+{
+ int l_rc{constants::FAILURE};
+
+ try
+ {
+ // get all object paths under PIM
+ const auto l_objectPaths = utils::GetSubTreePaths(
+ constants::baseInventoryPath, 0,
+ std::vector<std::string>{constants::inventoryItemInf});
+
+ if (!l_objectPaths.empty())
+ {
+ nlohmann::json l_resultInJson = nlohmann::json::array({});
+
+ std::for_each(l_objectPaths.begin(), l_objectPaths.end(),
+ [&](const auto& l_objectPath) {
+ const auto l_fruJson =
+ getFruProperties(l_objectPath);
+ if (!l_fruJson.empty())
+ {
+ if (l_resultInJson.empty())
+ {
+ l_resultInJson += l_fruJson;
+ }
+ else
+ {
+ l_resultInJson.at(0).insert(
+ l_fruJson.cbegin(), l_fruJson.cend());
+ }
+ }
+ });
+
+ if (i_dumpTable)
+ {
+ // create Table object
+ utils::Table l_inventoryTable{};
+
+ // columns to be populated in the Inventory table
+ const std::vector<types::TableColumnNameSizePair>
+ l_tableColumns = {
+ {"FRU", 100}, {"CC", 6}, {"DR", 20},
+ {"LocationCode", 32}, {"PN", 8}, {"PrettyName", 80},
+ {"SubModel", 10}, {"SN", 15}, {"type", 60}};
+
+ types::TableInputData l_tableData;
+
+ // First prepare the Table Columns
+ for (const auto& l_column : l_tableColumns)
+ {
+ if (constants::FAILURE ==
+ l_inventoryTable.AddColumn(l_column.first,
+ l_column.second))
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "Failed to add column " << l_column.first
+ << " in Inventory Table." << std::endl;
+ }
+ }
+
+ // iterate through the json array
+ for (const auto& l_fruEntry : l_resultInJson[0].items())
+ {
+ // if object path ends in "unit([0-9][0-9]?)", skip adding
+ // the object path in the table
+ if (std::regex_search(l_fruEntry.key(),
+ std::regex("unit([0-9][0-9]?)")))
+ {
+ continue;
+ }
+
+ std::vector<std::string> l_row;
+ for (const auto& l_column : l_tableColumns)
+ {
+ const auto& l_fruJson = l_fruEntry.value();
+
+ if (l_column.first == "FRU")
+ {
+ l_row.push_back(l_fruEntry.key());
+ }
+ else
+ {
+ if (l_fruJson.contains(l_column.first))
+ {
+ l_row.push_back(l_fruJson[l_column.first]);
+ }
+ else
+ {
+ l_row.push_back("");
+ }
+ }
+ }
+
+ l_tableData.push_back(l_row);
+ }
+
+ l_rc = l_inventoryTable.Print(l_tableData);
+ }
+ else
+ {
+ // print JSON to console
+ utils::printJson(l_resultInJson);
+ l_rc = constants::SUCCESS;
+ }
+ }
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "Dump inventory failed. Error: " << l_ex.what()
+ << std::endl;
+ }
+ return l_rc;
+}
+
+void VpdTool::printSystemVpd(
+ const nlohmann::json& i_parsedJsonObj) const noexcept
+{
+ if (i_parsedJsonObj.empty() || !i_parsedJsonObj.contains("backupMap"))
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "Invalid JSON to print system VPD" << std::endl;
+ }
+
+ std::string l_outline(191, '=');
+ std::cout << "\nRestorable record-keyword pairs and their data on backup & "
+ "primary.\n\n"
+ << l_outline << std::endl;
+
+ std::cout << std::left << std::setw(6) << "S.No" << std::left
+ << std::setw(8) << "Record" << std::left << std::setw(9)
+ << "Keyword" << std::left << std::setw(75) << "Data On Backup"
+ << std::left << std::setw(75) << "Data On Primary" << std::left
+ << std::setw(14) << "Data Mismatch\n"
+ << l_outline << std::endl;
+
+ uint8_t l_slNum = 0;
+
+ for (const auto& l_aRecordKwInfo : i_parsedJsonObj["backupMap"])
+ {
+ if (l_aRecordKwInfo.contains("sourceRecord") ||
+ l_aRecordKwInfo.contains("sourceKeyword") ||
+ l_aRecordKwInfo.contains("destinationkeywordValue") ||
+ l_aRecordKwInfo.contains("sourcekeywordValue"))
+ {
+ std::string l_mismatchFound{
+ (l_aRecordKwInfo["destinationkeywordValue"] !=
+ l_aRecordKwInfo["sourcekeywordValue"])
+ ? "YES"
+ : "NO"};
+
+ std::string l_splitLine(191, '-');
+
+ try
+ {
+ std::cout << std::left << std::setw(6)
+ << static_cast<int>(++l_slNum) << std::left
+ << std::setw(8)
+ << l_aRecordKwInfo.value("sourceRecord", "")
+ << std::left << std::setw(9)
+ << l_aRecordKwInfo.value("sourceKeyword", "")
+ << std::left << std::setw(75) << std::setfill(' ')
+ << utils::getPrintableValue(
+ l_aRecordKwInfo["destinationkeywordValue"])
+ << std::left << std::setw(75) << std::setfill(' ')
+ << utils::getPrintableValue(
+ l_aRecordKwInfo["sourcekeywordValue"])
+ << std::left << std::setw(14) << l_mismatchFound
+ << '\n'
+ << l_splitLine << std::endl;
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << l_ex.what() << std::endl;
+ }
+ }
+ }
+}
+
+int VpdTool::updateAllKeywords(const nlohmann::json& i_parsedJsonObj,
+ bool i_useBackupData) const noexcept
+{
+ int l_rc = constants::FAILURE;
+
+ if (i_parsedJsonObj.empty() || !i_parsedJsonObj.contains("source") ||
+ !i_parsedJsonObj.contains("backupMap"))
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "Invalid JSON" << std::endl;
+ return l_rc;
+ }
+
+ std::string l_srcVpdPath;
+ if (auto l_vpdPath = i_parsedJsonObj["source"].value("hardwarePath", "");
+ !l_vpdPath.empty())
+ {
+ l_srcVpdPath = l_vpdPath;
+ }
+ else if (auto l_vpdPath =
+ i_parsedJsonObj["source"].value("inventoryPath", "");
+ !l_vpdPath.empty())
+ {
+ l_srcVpdPath = l_vpdPath;
+ }
+ else
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "source path information is missing in JSON" << std::endl;
+ return l_rc;
+ }
+
+ bool l_anyMismatchFound = false;
+ for (const auto& l_aRecordKwInfo : i_parsedJsonObj["backupMap"])
+ {
+ if (!l_aRecordKwInfo.contains("sourceRecord") ||
+ !l_aRecordKwInfo.contains("sourceKeyword") ||
+ !l_aRecordKwInfo.contains("destinationkeywordValue") ||
+ !l_aRecordKwInfo.contains("sourcekeywordValue"))
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "Missing required information in the JSON"
+ << std::endl;
+ continue;
+ }
+
+ if (l_aRecordKwInfo["sourcekeywordValue"] !=
+ l_aRecordKwInfo["destinationkeywordValue"])
+ {
+ l_anyMismatchFound = true;
+
+ auto l_keywordValue =
+ i_useBackupData ? l_aRecordKwInfo["destinationkeywordValue"]
+ : l_aRecordKwInfo["sourcekeywordValue"];
+
+ auto l_paramsToWrite = std::make_tuple(
+ l_aRecordKwInfo["sourceRecord"],
+ l_aRecordKwInfo["sourceKeyword"], l_keywordValue);
+
+ try
+ {
+ l_rc = utils::writeKeyword(l_srcVpdPath, l_paramsToWrite);
+ if (l_rc > 0)
+ {
+ l_rc = constants::SUCCESS;
+ }
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << "write keyword failed for record: "
+ << l_aRecordKwInfo["sourceRecord"]
+ << ", keyword: " << l_aRecordKwInfo["sourceKeyword"]
+ << ", error: " << l_ex.what() << std::ends;
+ }
+ }
+ }
+
+ std::string l_dataUsed =
+ (i_useBackupData ? "data from backup" : "data from primary VPD");
+ if (l_anyMismatchFound)
+ {
+ std::cout << "Data updated successfully for all mismatching "
+ "record-keyword pairs by choosing their corresponding "
+ << l_dataUsed << ". Exit successfully." << std::endl;
+ }
+ else
+ {
+ std::cout << "No mismatch found for any of the above mentioned "
+ "record-keyword pair. Exit successfully."
+ << std::endl;
+ }
+
+ return l_rc;
+}
+
+int VpdTool::handleMoreOption(
+ const nlohmann::json& i_parsedJsonObj) const noexcept
+{
+ int l_rc = constants::FAILURE;
+
+ try
+ {
+ if (i_parsedJsonObj.empty() || !i_parsedJsonObj.contains("backupMap"))
+ {
+ throw std::runtime_error("Invalid JSON");
+ }
+
+ std::string l_srcVpdPath;
+
+ if (auto l_vpdPath =
+ i_parsedJsonObj["source"].value("hardwarePath", "");
+ !l_vpdPath.empty())
+ {
+ l_srcVpdPath = l_vpdPath;
+ }
+ else if (auto l_vpdPath =
+ i_parsedJsonObj["source"].value("inventoryPath", "");
+ !l_vpdPath.empty())
+ {
+ l_srcVpdPath = l_vpdPath;
+ }
+ else
+ {
+ throw std::runtime_error(
+ "source path information is missing in JSON");
+ }
+
+ auto updateKeywordValue =
+ [](std::string io_vpdPath, const std::string& i_recordName,
+ const std::string& i_keywordName,
+ const types::BinaryVector& i_keywordValue) -> int {
+ int l_rc = constants::FAILURE;
+
+ try
+ {
+ auto l_paramsToWrite = std::make_tuple(
+ i_recordName, i_keywordName, i_keywordValue);
+ l_rc = utils::writeKeyword(io_vpdPath, l_paramsToWrite);
+
+ if (l_rc > 0)
+ {
+ std::cout << std::endl
+ << "Data updated successfully." << std::endl;
+ }
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable log when verbose is enabled.
+ std::cerr << l_ex.what() << std::endl;
+ }
+ return l_rc;
+ };
+
+ do
+ {
+ int l_slNum = 0;
+ bool l_exit = false;
+
+ for (const auto& l_aRecordKwInfo : i_parsedJsonObj["backupMap"])
+ {
+ if (!l_aRecordKwInfo.contains("sourceRecord") ||
+ !l_aRecordKwInfo.contains("sourceKeyword") ||
+ !l_aRecordKwInfo.contains("destinationkeywordValue") ||
+ !l_aRecordKwInfo.contains("sourcekeywordValue"))
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr
+ << "Source or destination information is missing in the JSON."
+ << std::endl;
+ continue;
+ }
+
+ const std::string l_mismatchFound{
+ (l_aRecordKwInfo["sourcekeywordValue"] !=
+ l_aRecordKwInfo["destinationkeywordValue"])
+ ? "YES"
+ : "NO"};
+
+ std::cout << std::endl
+ << std::left << std::setw(6) << "S.No" << std::left
+ << std::setw(8) << "Record" << std::left
+ << std::setw(9) << "Keyword" << std::left
+ << std::setw(75) << std::setfill(' ') << "Backup Data"
+ << std::left << std::setw(75) << std::setfill(' ')
+ << "Primary Data" << std::left << std::setw(14)
+ << "Data Mismatch" << std::endl;
+
+ std::cout << std::left << std::setw(6)
+ << static_cast<int>(++l_slNum) << std::left
+ << std::setw(8)
+ << l_aRecordKwInfo.value("sourceRecord", "")
+ << std::left << std::setw(9)
+ << l_aRecordKwInfo.value("sourceKeyword", "")
+ << std::left << std::setw(75) << std::setfill(' ')
+ << utils::getPrintableValue(
+ l_aRecordKwInfo["destinationkeywordValue"])
+ << std::left << std::setw(75) << std::setfill(' ')
+ << utils::getPrintableValue(
+ l_aRecordKwInfo["sourcekeywordValue"])
+ << std::left << std::setw(14) << l_mismatchFound
+ << std::endl;
+
+ std::cout << std::string(191, '=') << std::endl;
+
+ if (constants::STR_CMP_SUCCESS ==
+ l_mismatchFound.compare("YES"))
+ {
+ printFixSystemVpdOption(
+ types::UserOption::UseBackupDataForCurrent);
+ printFixSystemVpdOption(
+ types::UserOption::UseSystemBackplaneDataForCurrent);
+ printFixSystemVpdOption(types::UserOption::NewValueOnBoth);
+ printFixSystemVpdOption(types::UserOption::SkipCurrent);
+ printFixSystemVpdOption(types::UserOption::Exit);
+ }
+ else
+ {
+ std::cout << "No mismatch found." << std::endl << std::endl;
+ printFixSystemVpdOption(types::UserOption::NewValueOnBoth);
+ printFixSystemVpdOption(types::UserOption::SkipCurrent);
+ printFixSystemVpdOption(types::UserOption::Exit);
+ }
+
+ int l_userSelectedOption = types::UserOption::Exit;
+ std::cin >> l_userSelectedOption;
+
+ if (types::UserOption::UseBackupDataForCurrent ==
+ l_userSelectedOption)
+ {
+ l_rc = updateKeywordValue(
+ l_srcVpdPath, l_aRecordKwInfo["sourceRecord"],
+ l_aRecordKwInfo["sourceKeyword"],
+ l_aRecordKwInfo["destinationkeywordValue"]);
+ }
+ else if (types::UserOption::UseSystemBackplaneDataForCurrent ==
+ l_userSelectedOption)
+ {
+ l_rc = updateKeywordValue(
+ l_srcVpdPath, l_aRecordKwInfo["sourceRecord"],
+ l_aRecordKwInfo["sourceKeyword"],
+ l_aRecordKwInfo["sourcekeywordValue"]);
+ }
+ else if (types::UserOption::NewValueOnBoth ==
+ l_userSelectedOption)
+ {
+ std::string l_newValue;
+ std::cout
+ << std::endl
+ << "Enter the new value to update on both "
+ "primary & backup. Value should be in ASCII or "
+ "in HEX(prefixed with 0x) : ";
+ std::cin >> l_newValue;
+ std::cout << std::endl
+ << std::string(191, '=') << std::endl;
+
+ try
+ {
+ l_rc = updateKeywordValue(
+ l_srcVpdPath, l_aRecordKwInfo["sourceRecord"],
+ l_aRecordKwInfo["sourceKeyword"],
+ utils::convertToBinary(l_newValue));
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << l_ex.what() << std::endl;
+ }
+ }
+ else if (types::UserOption::SkipCurrent == l_userSelectedOption)
+ {
+ std::cout << std::endl
+ << "Skipped the above record-keyword pair. "
+ "Continue to the next available pair."
+ << std::endl;
+ }
+ else if (types::UserOption::Exit == l_userSelectedOption)
+ {
+ std::cout << "Exit successfully" << std::endl;
+ l_exit = true;
+ break;
+ }
+ else
+ {
+ std::cout << "Provide a valid option. Retrying for the "
+ "current record-keyword pair"
+ << std::endl;
+ }
+ }
+ if (l_exit)
+ {
+ l_rc = constants::SUCCESS;
+ break;
+ }
+ } while (true);
+ }
+ catch (const std::exception& l_ex)
+ {
+ // TODO: Enable logging when verbose is enabled.
+ std::cerr << l_ex.what() << std::endl;
+ }
+
+ return l_rc;
+}
+
+} // namespace vpd
diff --git a/vpd-tool/src/vpd_tool_main.cpp b/vpd-tool/src/vpd_tool_main.cpp
new file mode 100644
index 0000000..3fdf5d1
--- /dev/null
+++ b/vpd-tool/src/vpd_tool_main.cpp
@@ -0,0 +1,335 @@
+#include "tool_constants.hpp"
+#include "vpd_tool.hpp"
+
+#include <CLI/CLI.hpp>
+
+#include <filesystem>
+#include <iostream>
+
+/**
+ * @brief API to perform manufacturing clean.
+ *
+ * @param[in] i_mfgCleanConfirmFlag - Confirmation flag to perform manufacturing
+ * clean.
+ *
+ * @return Status returned by cleanSystemVpd operation, success otherwise.
+ */
+int doMfgClean(const auto& i_mfgCleanConfirmFlag)
+{
+ if (i_mfgCleanConfirmFlag->empty())
+ {
+ constexpr auto MAX_CONFIRMATION_STR_LENGTH{3};
+ std::string l_confirmation{};
+ std::cout
+ << "This option resets some of the system VPD keywords to their default values. Do you really wish to proceed further?[yes/no]:";
+ std::cin >> std::setw(MAX_CONFIRMATION_STR_LENGTH) >> l_confirmation;
+
+ if (l_confirmation != "yes")
+ {
+ return vpd::constants::SUCCESS;
+ }
+ }
+
+ vpd::VpdTool l_vpdToolObj;
+ return l_vpdToolObj.cleanSystemVpd();
+}
+
+/**
+ * @brief API to write keyword's value.
+ *
+ * @param[in] i_hardwareFlag - Flag to perform write on hardware.
+ * @param[in] i_keywordValueOption - Option to read keyword value from command.
+ * @param[in] i_vpdPath - DBus object path or EEPROM path.
+ * @param[in] i_recordName - Record to be updated.
+ * @param[in] i_keywordName - Keyword to be updated.
+ * @param[in] i_keywordValue - Value to be updated in keyword.
+ *
+ * @return Status of writeKeyword operation, failure otherwise.
+ */
+int writeKeyword(const auto& i_hardwareFlag, const auto& i_keywordValueOption,
+ const std::string& i_vpdPath, const std::string& i_recordName,
+ const std::string& i_keywordName,
+ const std::string& i_keywordValue)
+{
+ std::error_code l_ec;
+
+ if (!i_hardwareFlag->empty() && !std::filesystem::exists(i_vpdPath, l_ec))
+ {
+ std::cerr << "Given EEPROM file path doesn't exist : " + i_vpdPath
+ << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ if (l_ec)
+ {
+ std::cerr << "filesystem call exists failed for file: " << i_vpdPath
+ << ", reason: " + l_ec.message() << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ if (!i_keywordValueOption->empty() && i_keywordValue.empty())
+ {
+ std::cerr
+ << "Please provide keyword value.\nUse --value/--file to give "
+ "keyword value. Refer --help."
+ << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ if (i_keywordValueOption->empty())
+ {
+ std::cerr
+ << "Please provide keyword value.\nUse --value/--file to give "
+ "keyword value. Refer --help."
+ << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ vpd::VpdTool l_vpdToolObj;
+ return l_vpdToolObj.writeKeyword(i_vpdPath, i_recordName, i_keywordName,
+ i_keywordValue, !i_hardwareFlag->empty());
+}
+
+/**
+ * @brief API to read keyword's value.
+ *
+ * @param[in] i_hardwareFlag - Flag to perform write on hardware.
+ * @param[in] i_vpdPath - DBus object path or EEPROM path.
+ * @param[in] i_recordName - Record to be updated.
+ * @param[in] i_keywordName - Keyword to be updated.
+ * @param[in] i_filePath - File path to save keyword's read value.
+ *
+ * @return Status of readKeyword operation, failure otherwise.
+ */
+int readKeyword(const auto& i_hardwareFlag, const std::string& i_vpdPath,
+ const std::string& i_recordName,
+ const std::string& i_keywordName, const std::string& i_filePath)
+{
+ std::error_code l_ec;
+
+ if (!i_hardwareFlag->empty() && !std::filesystem::exists(i_vpdPath, l_ec))
+ {
+ std::string l_errMessage{
+ "Given EEPROM file path doesn't exist : " + i_vpdPath};
+
+ if (l_ec)
+ {
+ l_errMessage += ". filesystem call exists failed, reason: " +
+ l_ec.message();
+ }
+
+ std::cerr << l_errMessage << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ bool l_isHardwareOperation = (!i_hardwareFlag->empty() ? true : false);
+
+ vpd::VpdTool l_vpdToolObj;
+ return l_vpdToolObj.readKeyword(i_vpdPath, i_recordName, i_keywordName,
+ l_isHardwareOperation, i_filePath);
+}
+
+/**
+ * @brief API to check option value pair in the tool command.
+ *
+ * In VPD tool command, some of the option(s) mandate values to be passed along
+ * with the option. This API based on option, detects those mandatory value(s).
+ *
+ * @param[in] i_objectOption - Option to pass object path.
+ * @param[in] i_vpdPath - Object path, DBus or EEPROM.
+ * @param[in] i_recordOption - Option to pass record name.
+ * @param[in] i_recordName - Record name.
+ * @param[in] i_keywordOption - Option to pass keyword name.
+ * @param[in] i_keywordName - Keyword name.
+ *
+ * @return Success if corresponding value is found against option, failure
+ * otherwise.
+ */
+int checkOptionValuePair(const auto& i_objectOption, const auto& i_vpdPath,
+ const auto& i_recordOption, const auto& i_recordName,
+ const auto& i_keywordOption, const auto& i_keywordName)
+{
+ if (!i_objectOption->empty() && i_vpdPath.empty())
+ {
+ std::cout << "Given path is empty." << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ if (!i_recordOption->empty() &&
+ (i_recordName.size() != vpd::constants::RECORD_SIZE))
+ {
+ std::cerr << "Record " << i_recordName << " is not supported."
+ << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ if (!i_keywordOption->empty() &&
+ (i_keywordName.size() != vpd::constants::KEYWORD_SIZE))
+ {
+ std::cerr << "Keyword " << i_keywordName << " is not supported."
+ << std::endl;
+ return vpd::constants::FAILURE;
+ }
+
+ return vpd::constants::SUCCESS;
+}
+
+/**
+ * @brief API to create app footer.
+ *
+ * @param[in] i_app - CLI::App object.
+ */
+void updateFooter(CLI::App& i_app)
+{
+ i_app.footer(
+ "Read:\n"
+ " IPZ Format:\n"
+ " From DBus to console: "
+ "vpd-tool -r -O <DBus Object Path> -R <Record Name> -K <Keyword Name>\n"
+ " From DBus to file: "
+ "vpd-tool -r -O <DBus Object Path> -R <Record Name> -K <Keyword Name> --file <File Path>\n"
+ " From hardware to console: "
+ "vpd-tool -r -H -O <EEPROM Path> -R <Record Name> -K <Keyword Name>\n"
+ " From hardware to file: "
+ "vpd-tool -r -H -O <EEPROM Path> -R <Record Name> -K <Keyword Name> --file <File Path>\n"
+ "Write:\n"
+ " IPZ Format:\n"
+ " On DBus: "
+ "vpd-tool -w/-u -O <DBus Object Path> -R <Record Name> -K <Keyword Name> -V <Keyword Value>\n"
+ " On DBus, take keyword value from file:\n"
+ " vpd-tool -w/-u -O <DBus Object Path> -R <Record Name> -K <Keyword Name> --file <File Path>\n"
+ " On hardware: "
+ "vpd-tool -w/-u -H -O <EEPROM Path> -R <Record Name> -K <Keyword Name> -V <Keyword Value>\n"
+ " On hardware, take keyword value from file:\n"
+ " vpd-tool -w/-u -H -O <EEPROM Path> -R <Record Name> -K <Keyword Name> --file <File Path>\n"
+ "Dump Object:\n"
+ " From DBus to console: "
+ "vpd-tool -o -O <DBus Object Path>\n"
+ "Fix System VPD:\n"
+ " vpd-tool --fixSystemVPD\n"
+ "MfgClean:\n"
+ " Flag to clean and reset specific keywords on system VPD to its default value.\n"
+ " vpd-tool --mfgClean\n"
+ "Dump Inventory:\n"
+ " From DBus to console in JSON format: "
+ "vpd-tool -i\n"
+ " From DBus to console in Table format: "
+ "vpd-tool -i -t\n");
+}
+
+int main(int argc, char** argv)
+{
+ CLI::App l_app{"VPD Command Line Tool"};
+
+ std::string l_vpdPath{};
+ std::string l_recordName{};
+ std::string l_keywordName{};
+ std::string l_filePath{};
+ std::string l_keywordValue{};
+
+ updateFooter(l_app);
+
+ auto l_objectOption =
+ l_app.add_option("--object, -O", l_vpdPath, "File path");
+ auto l_recordOption =
+ l_app.add_option("--record, -R", l_recordName, "Record name");
+ auto l_keywordOption =
+ l_app.add_option("--keyword, -K", l_keywordName, "Keyword name");
+
+ // Enable when file option is implemented.
+ /*auto l_fileOption = l_app.add_option("--file", l_filePath,
+ "Absolute file path");*/
+
+ auto l_keywordValueOption =
+ l_app.add_option("--value, -V", l_keywordValue,
+ "Keyword value in ascii/hex format."
+ " ascii ex: 01234; hex ex: 0x30313233");
+
+ auto l_hardwareFlag =
+ l_app.add_flag("--Hardware, -H", "CAUTION: Developer only option.");
+
+ auto l_readFlag = l_app.add_flag("--readKeyword, -r", "Read keyword")
+ ->needs(l_objectOption)
+ ->needs(l_recordOption)
+ ->needs(l_keywordOption);
+
+ auto l_writeFlag =
+ l_app
+ .add_flag(
+ "--writeKeyword, -w,--updateKeyword, -u",
+ "Write keyword, Note: Irrespective of DBus or hardware path provided, primary and backup, redundant EEPROM(if any) paths will get updated with given key value")
+ ->needs(l_objectOption)
+ ->needs(l_recordOption)
+ ->needs(l_keywordOption);
+
+ // ToDo: Take offset value from user for hardware path.
+
+ auto l_dumpObjFlag =
+ l_app
+ .add_flag("--dumpObject, -o",
+ "Dump specific properties of an inventory object")
+ ->needs(l_objectOption);
+
+ auto l_fixSystemVpdFlag = l_app.add_flag(
+ "--fixSystemVPD",
+ "Use this option to interactively fix critical system VPD keywords");
+ auto l_dumpInventoryFlag =
+ l_app.add_flag("--dumpInventory, -i", "Dump all the inventory objects");
+
+ auto l_mfgCleanFlag = l_app.add_flag(
+ "--mfgClean", "Manufacturing clean on system VPD keyword");
+
+ auto l_mfgCleanConfirmFlag = l_app.add_flag(
+ "--yes", "Using this flag with --mfgClean option, assumes "
+ "yes to proceed without confirmation.");
+
+ auto l_dumpInventoryTableFlag =
+ l_app.add_flag("--table, -t", "Dump inventory in table format");
+
+ CLI11_PARSE(l_app, argc, argv);
+
+ if (checkOptionValuePair(l_objectOption, l_vpdPath, l_recordOption,
+ l_recordName, l_keywordOption, l_keywordName) ==
+ vpd::constants::FAILURE)
+ {
+ return vpd::constants::FAILURE;
+ }
+
+ if (!l_readFlag->empty())
+ {
+ return readKeyword(l_hardwareFlag, l_vpdPath, l_recordName,
+ l_keywordName, l_filePath);
+ }
+
+ if (!l_writeFlag->empty())
+ {
+ return writeKeyword(l_hardwareFlag, l_keywordValueOption, l_vpdPath,
+ l_recordName, l_keywordName, l_keywordValue);
+ }
+
+ if (!l_dumpObjFlag->empty())
+ {
+ vpd::VpdTool l_vpdToolObj;
+ return l_vpdToolObj.dumpObject(l_vpdPath);
+ }
+
+ if (!l_fixSystemVpdFlag->empty())
+ {
+ vpd::VpdTool l_vpdToolObj;
+ return l_vpdToolObj.fixSystemVpd();
+ }
+
+ if (!l_mfgCleanFlag->empty())
+ {
+ return doMfgClean(l_mfgCleanConfirmFlag);
+ }
+
+ if (!l_dumpInventoryFlag->empty())
+ {
+ vpd::VpdTool l_vpdToolObj;
+ return l_vpdToolObj.dumpInventory(!l_dumpInventoryTableFlag->empty());
+ }
+
+ std::cout << l_app.help() << std::endl;
+ return vpd::constants::FAILURE;
+}