| #include "config.h" |
| |
| #include "editor_impl.hpp" |
| |
| #include "common_utility.hpp" |
| #include "ibm_vpd_utils.hpp" |
| #include "ipz_parser.hpp" |
| #include "parser_factory.hpp" |
| |
| #include "vpdecc/vpdecc.h" |
| |
| using namespace openpower::vpd::parser::interface; |
| using namespace openpower::vpd::constants; |
| using namespace openpower::vpd::parser::factory; |
| using namespace openpower::vpd::ipz::parser; |
| |
| namespace openpower |
| { |
| namespace vpd |
| { |
| namespace manager |
| { |
| namespace editor |
| { |
| |
| 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(const 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); |
| |
| // update data in file buffer as it will be needed to update ECC |
| // avoiding extra stream operation here |
| auto iteratorToKWdData = vpdFile.begin(); |
| std::advance(iteratorToKWdData, thisRecord.kwDataOffset); |
| std::copy(iteratorToNewdata, end, iteratorToKWdData); |
| |
| #ifdef ManagerTest |
| auto startItr = vpdFile.begin(); |
| std::advance(iteratorToKWdData, thisRecord.kwDataOffset); |
| auto endItr = startItr; |
| std::advance(endItr, thisRecord.kwdDataLength); |
| |
| Binary updatedData(startItr, endItr); |
| if (updatedData == kwdData) |
| { |
| throw std::runtime_error("Data updated successfully"); |
| } |
| #else |
| |
| // update data in EEPROM as well. As we will not write complete file back |
| vpdFileStream.seekp(startOffset + thisRecord.kwDataOffset, std::ios::beg); |
| |
| iteratorToNewdata = kwdData.cbegin(); |
| std::copy(iteratorToNewdata, end, |
| std::ostreambuf_iterator<char>(vpdFileStream)); |
| |
| // get a hold to new data in case encoding is needed |
| thisRecord.kwdUpdatedData.resize(thisRecord.kwdDataLength); |
| auto itrToKWdData = vpdFile.cbegin(); |
| std::advance(itrToKWdData, thisRecord.kwDataOffset); |
| auto kwdDataEnd = itrToKWdData; |
| std::advance(kwdDataEnd, thisRecord.kwdDataLength); |
| std::copy(itrToKWdData, kwdDataEnd, thisRecord.kwdUpdatedData.begin()); |
| #endif |
| } |
| |
| void EditorImpl::checkRecordForKwd() |
| { |
| RecordOffset recOffset = thisRecord.recOffset; |
| |
| // Amount to skip for record ID, size, and the RT keyword |
| constexpr auto skipBeg = sizeof(RecordId) + sizeof(RecordSize) + |
| lengths::KW_NAME + sizeof(KwSize); |
| |
| auto iterator = vpdFile.cbegin(); |
| std::advance(iterator, recOffset + skipBeg + lengths::RECORD_NAME); |
| |
| auto end = iterator; |
| std::advance(end, thisRecord.recSize); |
| 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) |
| { |
| thisRecord.kwDataOffset = std::distance(vpdFile.cbegin(), iterator); |
| 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() |
| { |
| auto itrToRecordData = vpdFile.cbegin(); |
| std::advance(itrToRecordData, thisRecord.recOffset); |
| |
| auto itrToRecordECC = vpdFile.cbegin(); |
| std::advance(itrToRecordECC, thisRecord.recECCoffset); |
| |
| auto l_status = vpdecc_create_ecc( |
| const_cast<uint8_t*>(&itrToRecordData[0]), thisRecord.recSize, |
| const_cast<uint8_t*>(&itrToRecordECC[0]), &thisRecord.recECCLength); |
| if (l_status != VPD_ECC_OK) |
| { |
| throw std::runtime_error("Ecc update failed"); |
| } |
| |
| auto end = itrToRecordECC; |
| std::advance(end, thisRecord.recECCLength); |
| |
| #ifndef ManagerTest |
| vpdFileStream.seekp(startOffset + thisRecord.recECCoffset, std::ios::beg); |
| std::copy(itrToRecordECC, end, |
| std::ostreambuf_iterator<char>(vpdFileStream)); |
| #endif |
| } |
| |
| auto EditorImpl::getValue(offsets::Offsets offset) |
| { |
| auto itr = vpdFile.cbegin(); |
| std::advance(itr, offset); |
| LE2ByteData lowByte = *itr; |
| LE2ByteData highByte = *(itr + 1); |
| lowByte |= (highByte << 8); |
| |
| return lowByte; |
| } |
| |
| void EditorImpl::checkECC(Binary::const_iterator& itrToRecData, |
| Binary::const_iterator& itrToECCData, |
| RecordLength recLength, ECCLength eccLength) |
| { |
| auto l_status = |
| vpdecc_check_data(const_cast<uint8_t*>(&itrToRecData[0]), recLength, |
| const_cast<uint8_t*>(&itrToECCData[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); |
| |
| auto itrToRecord = vpdFile.cbegin(); |
| std::advance(itrToRecord, tocOffset); |
| |
| auto iteratorToECC = vpdFile.cbegin(); |
| std::advance(iteratorToECC, tocECCOffset); |
| |
| // validate ecc for the record |
| checkECC(itrToRecord, iteratorToECC, tocLength, tocECCLength); |
| |
| // to get to the record name. |
| std::advance(itrToRecord, sizeof(RecordId) + sizeof(RecordSize) + |
| // Skip past the RT keyword, which contains |
| // the record name. |
| lengths::KW_NAME + sizeof(KwSize)); |
| |
| std::string recordName(itrToRecord, itrToRecord + lengths::RECORD_NAME); |
| |
| if ("VTOC" != recordName) |
| { |
| throw std::runtime_error("VTOC record not found"); |
| } |
| |
| // jump to length of PT kwd |
| std::advance(itrToRecord, lengths::RECORD_NAME + lengths::KW_NAME); |
| |
| // Note size of PT |
| Byte ptLen = *itrToRecord; |
| std::advance(itrToRecord, 1); |
| |
| checkPTForRecord(itrToRecord, ptLen); |
| } |
| |
| template <typename T> |
| void EditorImpl::makeDbusCall(const std::string& object, |
| const std::string& interface, |
| const std::string& property, |
| const std::variant<T>& data) |
| { |
| auto bus = sdbusplus::bus::new_default(); |
| auto properties = |
| bus.new_method_call(INVENTORY_MANAGER_SERVICE, object.c_str(), |
| "org.freedesktop.DBus.Properties", "Set"); |
| properties.append(interface); |
| properties.append(property); |
| properties.append(data); |
| |
| auto result = bus.call(properties); |
| |
| if (result.is_method_error()) |
| { |
| throw std::runtime_error("bus call failed"); |
| } |
| } |
| |
| void EditorImpl::processAndUpdateCI(const std::string& objectPath) |
| { |
| inventory::ObjectMap objects; |
| for (auto& commonInterface : jsonFile["commonInterfaces"].items()) |
| { |
| for (auto& ciPropertyList : commonInterface.value().items()) |
| { |
| if (ciPropertyList.value().type() == |
| nlohmann::json::value_t::object) |
| { |
| if ((ciPropertyList.value().value("recordName", "") == |
| thisRecord.recName) && |
| (ciPropertyList.value().value("keywordName", "") == |
| thisRecord.recKWd)) |
| { |
| inventory::PropertyMap prop; |
| inventory::InterfaceMap interfaces; |
| std::string kwdData(thisRecord.kwdUpdatedData.begin(), |
| thisRecord.kwdUpdatedData.end()); |
| |
| prop.emplace(ciPropertyList.key(), move(kwdData)); |
| interfaces.emplace(commonInterface.key(), move(prop)); |
| objects.emplace(objectPath, move(interfaces)); |
| } |
| } |
| } |
| } |
| // Notify PIM |
| common::utility::callPIM(move(objects)); |
| } |
| |
| void EditorImpl::processAndUpdateEI(const nlohmann::json& Inventory, |
| const inventory::Path& objPath) |
| { |
| inventory::ObjectMap objects; |
| for (const auto& extraInterface : Inventory["extraInterfaces"].items()) |
| { |
| if (extraInterface.value() != NULL) |
| { |
| for (const auto& eiPropertyList : extraInterface.value().items()) |
| { |
| if (eiPropertyList.value().type() == |
| nlohmann::json::value_t::object) |
| { |
| if ((eiPropertyList.value().value("recordName", "") == |
| thisRecord.recName) && |
| ((eiPropertyList.value().value("keywordName", "") == |
| thisRecord.recKWd))) |
| { |
| inventory::PropertyMap prop; |
| inventory::InterfaceMap interfaces; |
| std::string kwdData(thisRecord.kwdUpdatedData.begin(), |
| thisRecord.kwdUpdatedData.end()); |
| encodeKeyword(kwdData, eiPropertyList.value().value( |
| "encoding", "")); |
| |
| prop.emplace(eiPropertyList.key(), move(kwdData)); |
| interfaces.emplace(extraInterface.key(), move(prop)); |
| objects.emplace(objPath, move(interfaces)); |
| } |
| } |
| } |
| } |
| } |
| // Notify PIM |
| common::utility::callPIM(move(objects)); |
| } |
| |
| void EditorImpl::updateCache() |
| { |
| const std::vector<nlohmann::json>& groupEEPROM = |
| jsonFile["frus"][vpdFilePath].get_ref<const nlohmann::json::array_t&>(); |
| |
| inventory::ObjectMap objects; |
| // iterate through all the inventories for this file path |
| for (const auto& singleInventory : groupEEPROM) |
| { |
| inventory::PropertyMap prop; |
| inventory::InterfaceMap interfaces; |
| // by default inherit property is true |
| bool isInherit = true; |
| bool isInheritEI = true; |
| bool isCpuModuleOnly = false; |
| |
| if (singleInventory.find("inherit") != singleInventory.end()) |
| { |
| isInherit = singleInventory["inherit"].get<bool>(); |
| } |
| |
| if (singleInventory.find("inheritEI") != singleInventory.end()) |
| { |
| isInheritEI = singleInventory["inheritEI"].get<bool>(); |
| } |
| |
| // "type" exists only in CPU module and FRU |
| if (singleInventory.find("type") != singleInventory.end()) |
| { |
| if (singleInventory["type"] == "moduleOnly") |
| { |
| isCpuModuleOnly = true; |
| } |
| } |
| |
| if (isInherit) |
| { |
| // update com interface |
| // For CPU- update com interface only when isCI true |
| if ((!isCpuModuleOnly) || (isCpuModuleOnly && isCI)) |
| { |
| prop.emplace(thisRecord.recKWd, thisRecord.kwdUpdatedData); |
| interfaces.emplace( |
| (IPZ_INTERFACE + (std::string) "." + thisRecord.recName), |
| move(prop)); |
| objects.emplace( |
| (singleInventory["inventoryPath"].get<std::string>()), |
| move(interfaces)); |
| } |
| |
| // process Common interface |
| processAndUpdateCI(singleInventory["inventoryPath"] |
| .get_ref<const nlohmann::json::string_t&>()); |
| } |
| |
| if (isInheritEI) |
| { |
| if (isCpuModuleOnly) |
| { |
| prop.emplace(thisRecord.recKWd, thisRecord.kwdUpdatedData); |
| interfaces.emplace( |
| (IPZ_INTERFACE + (std::string) "." + thisRecord.recName), |
| move(prop)); |
| objects.emplace( |
| (singleInventory["inventoryPath"].get<std::string>()), |
| move(interfaces)); |
| } |
| |
| // process extra interfaces |
| processAndUpdateEI(singleInventory, |
| singleInventory["inventoryPath"] |
| .get_ref<const nlohmann::json::string_t&>()); |
| } |
| |
| // check if we need to copy some specific records in this case. |
| if (singleInventory.find("copyRecords") != singleInventory.end()) |
| { |
| if (find(singleInventory["copyRecords"].begin(), |
| singleInventory["copyRecords"].end(), |
| thisRecord.recName) != |
| singleInventory["copyRecords"].end()) |
| { |
| prop.emplace(thisRecord.recKWd, thisRecord.kwdUpdatedData); |
| interfaces.emplace( |
| (IPZ_INTERFACE + std::string{"."} + thisRecord.recName), |
| move(prop)); |
| objects.emplace( |
| (singleInventory["inventoryPath"].get<std::string>()), |
| move(interfaces)); |
| } |
| } |
| } |
| // Notify PIM |
| common::utility::callPIM(move(objects)); |
| } |
| |
| void EditorImpl::expandLocationCode(const std::string& locationCodeType) |
| { |
| std::string propertyFCorTM{}; |
| std::string propertySE{}; |
| |
| if (locationCodeType == "fcs") |
| { |
| propertyFCorTM = |
| readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "FC"); |
| propertySE = |
| readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "SE"); |
| } |
| else if (locationCodeType == "mts") |
| { |
| propertyFCorTM = |
| readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "TM"); |
| propertySE = |
| readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "SE"); |
| } |
| |
| const nlohmann::json& groupFRUS = |
| jsonFile["frus"].get_ref<const nlohmann::json::object_t&>(); |
| inventory::ObjectMap objects; |
| |
| for (const auto& itemFRUS : groupFRUS.items()) |
| { |
| const std::vector<nlohmann::json>& groupEEPROM = |
| itemFRUS.value().get_ref<const nlohmann::json::array_t&>(); |
| for (const auto& itemEEPROM : groupEEPROM) |
| { |
| inventory::PropertyMap prop; |
| inventory::InterfaceMap interfaces; |
| const auto& objectPath = itemEEPROM["inventoryPath"]; |
| sdbusplus::message::object_path object(objectPath); |
| |
| // check if the given item implements location code interface |
| if (itemEEPROM["extraInterfaces"].find(IBM_LOCATION_CODE_INF) != |
| itemEEPROM["extraInterfaces"].end()) |
| { |
| const std::string& unexpandedLocationCode = |
| itemEEPROM["extraInterfaces"][IBM_LOCATION_CODE_INF] |
| ["LocationCode"] |
| .get_ref<const nlohmann::json::string_t&>(); |
| std::size_t idx = unexpandedLocationCode.find(locationCodeType); |
| if (idx != std::string::npos) |
| { |
| std::string expandedLocationCode(unexpandedLocationCode); |
| |
| if (locationCodeType == "fcs") |
| { |
| expandedLocationCode.replace( |
| idx, 3, |
| propertyFCorTM.substr(0, 4) + ".ND0." + propertySE); |
| } |
| else if (locationCodeType == "mts") |
| { |
| std::replace(propertyFCorTM.begin(), |
| propertyFCorTM.end(), '-', '.'); |
| expandedLocationCode.replace( |
| idx, 3, propertyFCorTM + "." + propertySE); |
| } |
| |
| // update the DBUS interface COM as well as XYZ path |
| prop.emplace("LocationCode", expandedLocationCode); |
| // TODO depricate this com.ibm interface later |
| interfaces.emplace(IBM_LOCATION_CODE_INF, prop); |
| interfaces.emplace(XYZ_LOCATION_CODE_INF, move(prop)); |
| } |
| } |
| objects.emplace(move(object), move(interfaces)); |
| } |
| } |
| // Notify PIM |
| common::utility::callPIM(move(objects)); |
| } |
| |
| string EditorImpl::getSysPathForThisFruType(const string& moduleObjPath, |
| const string& fruType) |
| { |
| string fruVpdPath; |
| |
| // get all FRUs list |
| for (const auto& eachFru : jsonFile["frus"].items()) |
| { |
| bool moduleObjPathMatched = false; |
| bool expectedFruFound = false; |
| |
| for (const auto& eachInventory : eachFru.value()) |
| { |
| const auto& thisObjectPath = eachInventory["inventoryPath"]; |
| |
| // "type" exists only in CPU module and FRU |
| if (eachInventory.find("type") != eachInventory.end()) |
| { |
| // If inventory type is fruAndModule then set flag |
| if (eachInventory["type"] == fruType) |
| { |
| expectedFruFound = true; |
| } |
| } |
| |
| if (thisObjectPath == moduleObjPath) |
| { |
| moduleObjPathMatched = true; |
| } |
| } |
| |
| // If condition satisfies then collect this sys path and exit |
| if (expectedFruFound && moduleObjPathMatched) |
| { |
| fruVpdPath = eachFru.key(); |
| break; |
| } |
| } |
| |
| return fruVpdPath; |
| } |
| |
| void EditorImpl::getVpdPathForCpu() |
| { |
| isCI = false; |
| // keep a backup In case we need it later |
| inventory::Path vpdFilePathBackup = vpdFilePath; |
| |
| // TODO 1:Temp hardcoded list. create it dynamically. |
| std::vector<std::string> commonIntVINIKwds = {"PN", "SN", "DR"}; |
| std::vector<std::string> commonIntVR10Kwds = {"DC"}; |
| std::unordered_map<std::string, std::vector<std::string>> |
| commonIntRecordsList = {{"VINI", commonIntVINIKwds}, |
| {"VR10", commonIntVR10Kwds}}; |
| |
| // If requested Record&Kw is one among CI, then update 'FRU' type sys |
| // path, SPI2 |
| unordered_map<std::string, vector<string>>::const_iterator isCommonInt = |
| commonIntRecordsList.find(thisRecord.recName); |
| |
| if ((isCommonInt != commonIntRecordsList.end()) && |
| (find(isCommonInt->second.begin(), isCommonInt->second.end(), |
| thisRecord.recKWd) != isCommonInt->second.end())) |
| { |
| isCI = true; |
| vpdFilePath = getSysPathForThisFruType(objPath, "fruAndModule"); |
| } |
| else |
| { |
| for (const auto& eachFru : jsonFile["frus"].items()) |
| { |
| for (const auto& eachInventory : eachFru.value()) |
| { |
| if (eachInventory.find("type") != eachInventory.end()) |
| { |
| const auto& thisObjectPath = eachInventory["inventoryPath"]; |
| if ((eachInventory["type"] == "moduleOnly") && |
| (eachInventory.value("inheritEI", true)) && |
| (thisObjectPath == static_cast<string>(objPath))) |
| { |
| vpdFilePath = eachFru.key(); |
| } |
| } |
| } |
| } |
| } |
| // If it is not a CPU fru then go ahead with default vpdFilePath from |
| // fruMap |
| if (vpdFilePath.empty()) |
| { |
| vpdFilePath = vpdFilePathBackup; |
| } |
| } |
| |
| void EditorImpl::updateKeyword(const Binary& kwdData, const bool& updCache) |
| { |
| startOffset = 0; |
| #ifndef ManagerTest |
| |
| getVpdPathForCpu(); |
| |
| // check if offset present? |
| for (const auto& item : jsonFile["frus"][vpdFilePath]) |
| { |
| if (item.find("offset") != item.end()) |
| { |
| startOffset = item["offset"]; |
| } |
| } |
| |
| // TODO: Figure out a better way to get max possible VPD size. |
| Binary completeVPDFile; |
| completeVPDFile.resize(65504); |
| vpdFileStream.open(vpdFilePath, |
| std::ios::in | std::ios::out | std::ios::binary); |
| |
| vpdFileStream.seekg(startOffset, ios_base::cur); |
| vpdFileStream.read(reinterpret_cast<char*>(&completeVPDFile[0]), 65504); |
| completeVPDFile.resize(vpdFileStream.gcount()); |
| vpdFileStream.clear(std::ios_base::eofbit); |
| |
| vpdFile = completeVPDFile; |
| |
| #else |
| |
| Binary completeVPDFile = vpdFile; |
| |
| #endif |
| if (vpdFile.empty()) |
| { |
| throw std::runtime_error("Invalid File"); |
| } |
| auto iterator = vpdFile.cbegin(); |
| std::advance(iterator, IPZ_DATA_START); |
| |
| Byte vpdType = *iterator; |
| if (vpdType == KW_VAL_PAIR_START_TAG) |
| { |
| ParserInterface* Iparser = ParserFactory::getParser(completeVPDFile); |
| IpzVpdParser* ipzParser = dynamic_cast<IpzVpdParser*>(Iparser); |
| |
| try |
| { |
| if (ipzParser == nullptr) |
| { |
| throw std::runtime_error("Invalid cast"); |
| } |
| |
| ipzParser->processHeader(); |
| delete ipzParser; |
| ipzParser = nullptr; |
| // ParserFactory::freeParser(Iparser); |
| |
| // 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(); |
| |
| if (updCache) |
| { |
| #ifndef ManagerTest |
| // update the cache once data has been updated |
| updateCache(); |
| #endif |
| } |
| } |
| catch (const std::exception& e) |
| { |
| if (ipzParser != nullptr) |
| { |
| delete ipzParser; |
| } |
| throw std::runtime_error(e.what()); |
| } |
| return; |
| } |
| } |
| } // namespace editor |
| } // namespace manager |
| } // namespace vpd |
| } // namespace openpower |