Editor implementation for VPD manager
This commit implements editor class for VPD manager app.
This calss holds responsibility of handling and editing
vpd data related requests on behalf of manager app and
updating the cache with new data if required.
This commit also changes parser class to implement api
needed to validate vpd header file in case of write call.
Steps to build:
meson <build_directory>
ninja -C <directory_path>
-Tested with sample json and sample vpd file
sample command to test
busctl call com.ibm.VPD.Manager /com/ibm/VPD/Manager
com.ibm.VPD.Manager WriteKeyword sssay
<"inventory_path"> <"Record_Name"> <"Keyword_Name">
<no. of bytes to update> <array_of_bytes>
Signed-off-by: Sunny Srivastava <sunnsr25@in.ibm.com>
Change-Id: I569f03e329b0f62937c6e143a344d8e4ff02db6f
diff --git a/const.hpp b/const.hpp
index b62621c..5169987 100644
--- a/const.hpp
+++ b/const.hpp
@@ -66,6 +66,9 @@
VHDR = 17,
VHDR_TOC_ENTRY = 29,
VTOC_PTR = 35,
+ VTOC_REC_LEN = 37,
+ VTOC_ECC_OFF = 39,
+ VTOC_ECC_LEN = 41,
VTOC_DATA = 13,
VHDR_ECC = 0,
VHDR_RECORD = 11
diff --git a/impl.cpp b/impl.cpp
index d647d8b..c7036b7 100644
--- a/impl.cpp
+++ b/impl.cpp
@@ -192,16 +192,13 @@
}
}
-internal::OffsetList Impl::readTOC() const
+std::size_t Impl::readTOC(Binary::const_iterator& iterator) const
{
- internal::OffsetList offsets{};
-
// The offset to VTOC could be 1 or 2 bytes long
RecordOffset vtocOffset = getVtocOffset();
// Got the offset to VTOC, skip past record header and keyword header
// to get to the record name.
- auto iterator = vpd.cbegin();
std::advance(iterator, vtocOffset + sizeof(RecordId) + sizeof(RecordSize) +
// Skip past the RT keyword, which contains
// the record name.
@@ -233,8 +230,8 @@
// Skip past PT size
std::advance(iterator, sizeof(KwSize));
- // vpdBuffer is now pointing to PT data
- return readPT(iterator, ptLen);
+ // length of PT keyword
+ return ptLen;
}
internal::OffsetList Impl::readPT(Binary::const_iterator iterator,
@@ -476,9 +473,14 @@
// Check if the VHDR record is present
checkHeader();
+ auto iterator = vpd.cbegin();
+
+ // Read the table of contents record
+ std::size_t ptLen = readTOC(iterator);
+
// Read the table of contents record, to get offsets
// to other records.
- auto offsets = readTOC();
+ auto offsets = readPT(iterator, ptLen);
for (const auto& offset : offsets)
{
processRecord(offset);
@@ -488,6 +490,12 @@
return Store(std::move(out));
}
+void Impl::checkVPDHeader()
+{
+ // Check if the VHDR record is present and is valid
+ checkHeader();
+}
+
} // namespace parser
} // namespace vpd
} // namespace openpower
diff --git a/impl.hpp b/impl.hpp
index a6ac39d..6dca5e0 100644
--- a/impl.hpp
+++ b/impl.hpp
@@ -78,12 +78,17 @@
*/
Store run();
- private:
- /** @brief Process the table of contents record, VHDR
- *
- * @returns List of offsets to records in VPD
+ /** @brief check if VPD header is valid
*/
- internal::OffsetList readTOC() const;
+ void checkVPDHeader();
+
+ private:
+ /** @brief Process the table of contents record
+ *
+ * @param[in] iterator - iterator to buffer containing VPD
+ * @returns Size of the PT keyword in VTOC
+ */
+ std::size_t readTOC(Binary::const_iterator& iterator) const;
/** @brief Read the PT keyword contained in the VHDR record,
* to obtain offsets to other records in the VPD.
diff --git a/parser.cpp b/parser.cpp
index 3b9776e..2b8da60 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -14,5 +14,19 @@
return s;
}
+namespace keyword
+{
+namespace editor
+{
+
+void processHeader(Binary&& vpd)
+{
+ parser::Impl p(std::move(vpd));
+ p.checkVPDHeader();
+}
+
+} // namespace editor
+} // namespace keyword
+
} // namespace vpd
} // namespace openpower
diff --git a/parser.hpp b/parser.hpp
index 097b14a..1379f90 100644
--- a/parser.hpp
+++ b/parser.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "const.hpp"
#include "store.hpp"
#include <vector>
@@ -17,5 +18,18 @@
*/
Store parse(Binary&& vpd);
+namespace keyword
+{
+namespace editor
+{
+using namespace openpower::vpd::constants;
+/** @brief API to check vpd header
+ * @param [in] vpd - VPDheader in binary format
+ */
+void processHeader(Binary&& vpd);
+
+} // namespace editor
+} // namespace keyword
+
} // namespace vpd
} // namespace openpower
diff --git a/utils.cpp b/utils.cpp
index 276f406..e0bf6f4 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -65,5 +65,14 @@
} // namespace inventory
+using namespace openpower::vpd::constants;
+LE2ByteData readUInt16LE(Binary::const_iterator iterator)
+{
+ LE2ByteData lowByte = *iterator;
+ LE2ByteData highByte = *(iterator + 1);
+ lowByte |= (highByte << 8);
+ return lowByte;
+}
+
} // namespace vpd
} // namespace openpower
diff --git a/utils.hpp b/utils.hpp
index 55bc900..59b33ea 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "const.hpp"
#include "types.hpp"
namespace openpower
@@ -32,5 +33,12 @@
} // namespace inventory
+/**@brief This API reads 2 Bytes of data and swap the read data
+ * @param[in] iterator- Pointer pointing to the data to be read
+ * @return returns 2 Byte data read at the given pointer
+ */
+openpower::vpd::constants::LE2ByteData
+ readUInt16LE(Binary::const_iterator iterator);
+
} // namespace vpd
} // namespace openpower
diff --git a/vpd-manager/editor_impl.cpp b/vpd-manager/editor_impl.cpp
new file mode 100644
index 0000000..3bad2f8
--- /dev/null
+++ b/vpd-manager/editor_impl.cpp
@@ -0,0 +1,280 @@
+#include "editor_impl.hpp"
+
+#include "utils.hpp"
+
+#include <fstream>
+#include <iostream>
+#include <iterator>
+
+#include "vpdecc/vpdecc.h"
+
+namespace openpower
+{
+namespace vpd
+{
+namespace manager
+{
+namespace editor
+{
+using namespace openpower::vpd::constants;
+
+void EditorImpl::checkPTForRecord(Binary::const_iterator& iterator,
+ Byte ptLength)
+{
+ // auto iterator = ptRecord.cbegin();
+ auto end = std::next(iterator, ptLength + 1);
+
+ // Look at each entry in the PT keyword for the record name
+ while (iterator < end)
+ {
+ auto stop = std::next(iterator, lengths::RECORD_NAME);
+ std::string record(iterator, stop);
+
+ if (record == thisRecord.recName)
+ {
+ // Skip record name and record type
+ std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType));
+
+ // Get record offset
+ thisRecord.recOffset = readUInt16LE(iterator);
+
+ // pass the record offset length to read record length
+ std::advance(iterator, lengths::RECORD_OFFSET);
+ thisRecord.recSize = readUInt16LE(iterator);
+
+ std::advance(iterator, lengths::RECORD_LENGTH);
+ thisRecord.recECCoffset = readUInt16LE(iterator);
+
+ ECCLength len;
+ std::advance(iterator, lengths::RECORD_ECC_OFFSET);
+ len = readUInt16LE(iterator);
+ thisRecord.recECCLength = len;
+
+ // once we find the record we don't need to look further
+ return;
+ }
+ else
+ {
+ // Jump the record
+ std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType) +
+ sizeof(RecordOffset) +
+ sizeof(RecordLength) +
+ sizeof(ECCOffset) + sizeof(ECCLength));
+ }
+ }
+
+ // imples the record was not found
+ throw std::runtime_error("Record not found");
+}
+
+void EditorImpl::updateData(Binary kwdData)
+{
+ std::size_t lengthToUpdate = kwdData.size() <= thisRecord.kwdDataLength
+ ? kwdData.size()
+ : thisRecord.kwdDataLength;
+
+ auto iteratorToNewdata = kwdData.cbegin();
+ auto end = iteratorToNewdata;
+ std::advance(end, lengthToUpdate);
+
+ std::copy(iteratorToNewdata, end,
+ std::ostreambuf_iterator<char>(vpdFileStream));
+}
+
+void EditorImpl::checkRecordForKwd()
+{
+ RecordOffset recOffset = thisRecord.recOffset;
+
+ // Jump to record name
+ auto nameOffset = recOffset + sizeof(RecordId) + sizeof(RecordSize) +
+ // Skip past the RT keyword, which contains
+ // the record name.
+ lengths::KW_NAME + sizeof(KwSize);
+
+ vpdFileStream.seekg(nameOffset + lengths::RECORD_NAME, std::ios::beg);
+
+ (thisRecord.recData).resize(thisRecord.recSize);
+ vpdFileStream.read(reinterpret_cast<char*>((thisRecord.recData).data()),
+ thisRecord.recSize);
+
+ auto iterator = (thisRecord.recData).cbegin();
+ auto end = (thisRecord.recData).cend();
+
+ std::size_t dataLength = 0;
+ while (iterator < end)
+ {
+ // Note keyword name
+ std::string kw(iterator, iterator + lengths::KW_NAME);
+
+ // Check if the Keyword starts with '#'
+ char kwNameStart = *iterator;
+ std::advance(iterator, lengths::KW_NAME);
+
+ // if keyword starts with #
+ if (POUND_KW == kwNameStart)
+ {
+ // Note existing keyword data length
+ dataLength = readUInt16LE(iterator);
+
+ // Jump past 2Byte keyword length + data
+ std::advance(iterator, sizeof(PoundKwSize));
+ }
+ else
+ {
+ // Note existing keyword data length
+ dataLength = *iterator;
+
+ // Jump past keyword length and data
+ std::advance(iterator, sizeof(KwSize));
+ }
+
+ if (thisRecord.recKWd == kw)
+ {
+ // We're done
+ std::size_t kwdOffset =
+ std::distance((thisRecord.recData).cbegin(), iterator);
+ vpdFileStream.seekp(nameOffset + lengths::RECORD_NAME + kwdOffset,
+ std::ios::beg);
+ thisRecord.kwdDataLength = dataLength;
+
+ return;
+ }
+
+ // jump the data of current kwd to point to next kwd name
+ std::advance(iterator, dataLength);
+ }
+
+ throw std::runtime_error("Keyword not found");
+}
+
+void EditorImpl::updateRecordECC()
+{
+ vpdFileStream.seekp(thisRecord.recECCoffset, std::ios::beg);
+
+ (thisRecord.recEccData).resize(thisRecord.recECCLength);
+ vpdFileStream.read(reinterpret_cast<char*>((thisRecord.recEccData).data()),
+ thisRecord.recECCLength);
+
+ auto recPtr = (thisRecord.recData).cbegin();
+ auto recEccPtr = (thisRecord.recEccData).cbegin();
+
+ auto l_status = vpdecc_create_ecc(
+ const_cast<uint8_t*>(&recPtr[0]), thisRecord.recSize,
+ const_cast<uint8_t*>(&recEccPtr[0]), &thisRecord.recECCLength);
+ if (l_status != VPD_ECC_OK)
+ {
+ throw std::runtime_error("Ecc update failed");
+ }
+
+ auto end = (thisRecord.recEccData).cbegin();
+ std::advance(end, thisRecord.recECCLength);
+
+ std::copy((thisRecord.recEccData).cbegin(), end,
+ std::ostreambuf_iterator<char>(vpdFileStream));
+}
+
+auto EditorImpl::getValue(offsets::Offsets offset)
+{
+ Byte data = 0;
+ vpdFileStream.seekg(offset, std::ios::beg)
+ .get(*(reinterpret_cast<char*>(&data)));
+ LE2ByteData lowByte = data;
+
+ vpdFileStream.seekg(offset + 1, std::ios::beg)
+ .get(*(reinterpret_cast<char*>(&data)));
+ LE2ByteData highByte = data;
+ lowByte |= (highByte << 8);
+
+ return lowByte;
+}
+
+void EditorImpl::checkECC(const Binary& tocRecData, const Binary& tocECCData,
+ RecordLength recLength, ECCLength eccLength)
+{
+ auto l_status =
+ vpdecc_check_data(const_cast<uint8_t*>(&tocRecData[0]), recLength,
+ const_cast<uint8_t*>(&tocECCData[0]), eccLength);
+ if (l_status != VPD_ECC_OK)
+ {
+ throw std::runtime_error("Ecc check failed for VTOC");
+ }
+}
+
+void EditorImpl::readVTOC()
+{
+ // read VTOC offset
+ RecordOffset tocOffset = getValue(offsets::VTOC_PTR);
+
+ // read VTOC record length
+ RecordLength tocLength = getValue(offsets::VTOC_REC_LEN);
+
+ // read TOC ecc offset
+ ECCOffset tocECCOffset = getValue(offsets::VTOC_ECC_OFF);
+
+ // read TOC ecc length
+ ECCLength tocECCLength = getValue(offsets::VTOC_ECC_LEN);
+
+ // read toc record data
+ Binary vtocRecord(tocLength);
+ vpdFileStream.seekg(tocOffset, std::ios::beg);
+ vpdFileStream.read(reinterpret_cast<char*>(vtocRecord.data()), tocLength);
+
+ // read toc ECC for ecc check
+ Binary vtocECC(tocECCLength);
+ vpdFileStream.seekg(tocECCOffset, std::ios::beg);
+ vpdFileStream.read(reinterpret_cast<char*>(vtocECC.data()), tocECCLength);
+
+ auto iterator = vtocRecord.cbegin();
+
+ // to get to the record name.
+ std::advance(iterator, sizeof(RecordId) + sizeof(RecordSize) +
+ // Skip past the RT keyword, which contains
+ // the record name.
+ lengths::KW_NAME + sizeof(KwSize));
+
+ std::string recordName(iterator, iterator + lengths::RECORD_NAME);
+
+ if ("VTOC" != recordName)
+ {
+ throw std::runtime_error("VTOC record not found");
+ }
+
+ // validate ecc for the record
+ checkECC(vtocRecord, vtocECC, tocLength, tocECCLength);
+
+ // jump to length of PT kwd
+ std::advance(iterator, lengths::RECORD_NAME + lengths::KW_NAME);
+
+ // Note size of PT
+ Byte ptLen = *iterator;
+ std::advance(iterator, 1);
+
+ checkPTForRecord(iterator, ptLen);
+}
+
+void EditorImpl::updateKeyword(const Binary& kwdData)
+{
+ vpdFileStream.open(vpdFilePath,
+ std::ios::in | std::ios::out | std::ios::binary);
+ if (!vpdFileStream)
+ {
+ throw std::runtime_error("unable to open vpd file to edit");
+ }
+
+ // process VTOC for PTT rkwd
+ readVTOC();
+
+ // check record for keywrod
+ checkRecordForKwd();
+
+ // update the data to the file
+ updateData(kwdData);
+
+ // update the ECC data for the record once data has been updated
+ updateRecordECC();
+}
+
+} // namespace editor
+} // namespace manager
+} // namespace vpd
+} // namespace openpower
diff --git a/vpd-manager/editor_impl.hpp b/vpd-manager/editor_impl.hpp
new file mode 100644
index 0000000..997bf85
--- /dev/null
+++ b/vpd-manager/editor_impl.hpp
@@ -0,0 +1,139 @@
+#pragma once
+
+#include "const.hpp"
+#include "types.hpp"
+
+#include <cstddef>
+#include <fstream>
+#include <tuple>
+
+namespace openpower
+{
+namespace vpd
+{
+namespace manager
+{
+namespace editor
+{
+
+/** @class Editor
+ * @brief Implements VPD editing related functinality, currently
+ * implemented to support only keyword data update functionality.
+ *
+ * An Editor object must be constructed by passing in VPD in
+ * binary format. To edit the keyword data, call the updateKeyword() method.
+ * The method looks for the record name to update in VTOC and
+ * then looks for the keyword name in that record.
+ * when found it updates the data of keyword with the given data.
+ * It does not block keyword data update in case the length of new data is
+ * greater than or less than the current data length.
+ * If the new data length is more than the length alotted to that keyword
+ * the new data will be truncated to update only the allotted length.
+ * Similarly if the new data length is less then only that much data will
+ * be updated for the keyword and remaining bits will be left unchanged.
+ *
+ * Following is the algorithm used to update keyword:
+ * 1) Look for the record name in the given VPD file
+ * 2) Look for the keyword name for which data needs to be updated
+ * which is the table of contents record.
+ * 3) update the data for that keyword with the new data
+ */
+class EditorImpl
+{
+ public:
+ EditorImpl() = delete;
+ EditorImpl(const EditorImpl&) = delete;
+ EditorImpl& operator=(const EditorImpl&) = delete;
+ EditorImpl(EditorImpl&&) = delete;
+ EditorImpl& operator=(EditorImpl&&) = delete;
+ ~EditorImpl() = default;
+
+ /** @brief Construct EditorImpl class
+ *
+ * @param[in] path - Path to the vpd file
+ */
+ EditorImpl(const inventory::Path& path, const std::string& record,
+ const std::string& kwd) :
+ vpdFilePath(path),
+ thisRecord(record, kwd)
+ {
+ }
+
+ /** @brief Update data for keyword
+ * @param[in] kwdData - data to update
+ */
+ void updateKeyword(const Binary& kwdData);
+
+ private:
+ /** @brief read VTOC record from the vpd file
+ */
+ void readVTOC();
+
+ /** @brief validate ecc data for the VTOC record
+ * @param[in] tocRecData - VTOC record data
+ * @param[in] tocECCData - VTOC ECC data
+ * @param[in] recLength - Lenght of VTOC record
+ * @param[in] eccLength - Length of ECC record
+ */
+ void checkECC(const Binary& tocRecData, const Binary& tocECCData,
+ openpower::vpd::constants::RecordLength recLength,
+ openpower::vpd::constants::ECCLength eccLength);
+
+ /** @brief reads value at the given offset
+ * @param[in] offset - offset value
+ * @return[out] - value at that offset in bigendian
+ */
+ auto getValue(openpower::vpd::constants::offsets::Offsets offset);
+
+ /** @brief Checks if required record name exist in the VPD file
+ * @param[in] iterator - pointing to start of PT kwd
+ * @param[in] ptLength - length of the PT kwd
+ */
+ void checkPTForRecord(Binary::const_iterator& iterator, Byte ptLength);
+
+ /** @brief Checks for reuired keyword in the record
+ */
+ void checkRecordForKwd();
+
+ /** @brief update data for given keyword
+ * @param[in] kwdData- data to be updated
+ */
+ void updateData(Binary kwdData);
+
+ /** @brief update record ECC
+ */
+ void updateRecordECC();
+
+ // path to the VPD file to edit
+ const inventory::Path& vpdFilePath;
+
+ // stream to perform operation on file
+ std::fstream vpdFileStream;
+
+ // structure to hold info about record to edit
+ struct RecInfo
+ {
+ Binary recData;
+ Binary recEccData;
+ const std::string& recName;
+ const std::string& recKWd;
+ openpower::vpd::constants::RecordOffset recOffset;
+ openpower::vpd::constants::ECCOffset recECCoffset;
+ std::size_t recECCLength;
+ std::size_t kwdDataLength;
+ openpower::vpd::constants::RecordSize recSize;
+
+ // constructor
+ RecInfo(const std::string& rec, const std::string& kwd) :
+ recName(rec), recKWd(kwd), recOffset(0), recECCoffset(0),
+ recECCLength(0), kwdDataLength(0), recSize(0)
+ {
+ }
+ } thisRecord;
+
+}; // class EditorImpl
+
+} // namespace editor
+} // namespace manager
+} // namespace vpd
+} // namespace openpower
diff --git a/vpd-manager/manager.cpp b/vpd-manager/manager.cpp
index ce8bea5..79ab5a5 100644
--- a/vpd-manager/manager.cpp
+++ b/vpd-manager/manager.cpp
@@ -5,11 +5,8 @@
#include "const.hpp"
#include "parser.hpp"
-#include <exception>
-#include <fstream>
-#include <iostream>
-#include <nlohmann/json.hpp>
-#include <vector>
+using namespace openpower::vpd::constants;
+using namespace openpower::vpd::manager::editor;
namespace openpower
{
@@ -79,7 +76,49 @@
const std::string recordName,
const std::string keyword, const Binary value)
{
- // implement the interface to write keyword VPD data
+ try
+ {
+ if (frus.find(path) == frus.end())
+ {
+ throw std::runtime_error("Inventory path not found");
+ }
+
+ inventory::Path vpdFilePath = frus.find(path)->second;
+ std::ifstream vpdStream(vpdFilePath, std::ios::binary);
+ if (!vpdStream)
+ {
+ throw std::runtime_error("file not found");
+ }
+
+ Byte data;
+ vpdStream.seekg(IPZ_DATA_START, std::ios::beg);
+ vpdStream.get(*(reinterpret_cast<char*>(&data)));
+
+ // implies it is IPZ VPD
+ if (data == KW_VAL_PAIR_START_TAG)
+ {
+ Binary vpdHeader(lengths::VHDR_RECORD_LENGTH +
+ lengths::VHDR_ECC_LENGTH);
+ vpdStream.seekg(0);
+ vpdStream.read(reinterpret_cast<char*>(vpdHeader.data()),
+ vpdHeader.capacity());
+
+ // check if header is valid
+ openpower::vpd::keyword::editor::processHeader(
+ std::move(vpdHeader));
+
+ // instantiate editor class to update the data
+ EditorImpl edit(vpdFilePath, recordName, keyword);
+ edit.updateKeyword(value);
+
+ return;
+ }
+ throw std::runtime_error("Invalid VPD file type");
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << e.what() << std::endl;
+ }
}
std::vector<sdbusplus::message::object_path>
diff --git a/vpd-manager/manager.hpp b/vpd-manager/manager.hpp
index e04250c..e5882b5 100644
--- a/vpd-manager/manager.hpp
+++ b/vpd-manager/manager.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "editor_impl.hpp"
#include "types.hpp"
#include <com/ibm/VPD/Manager/server.hpp>
diff --git a/vpd-manager/meson.build b/vpd-manager/meson.build
index 7393379..da736d7 100644
--- a/vpd-manager/meson.build
+++ b/vpd-manager/meson.build
@@ -1,5 +1,6 @@
project('vpd-manager',
'cpp',
+ 'c',
default_options : ['cpp_std=c++17'],
version : '1.0')
sdbusplus = dependency('sdbusplus')
@@ -26,7 +27,11 @@
'manager.cpp',
'server.cpp',
'error.cpp',
- '../impl.cpp'
+ 'editor_impl.cpp',
+ '../impl.cpp',
+ '../parser.cpp',
+ '../utils.cpp',
+ '../vpdecc/vpdecc.c'
]
vpd_manager_exe = executable('vpd-manager',