#include "config.h" #include "editor_impl.hpp" #include "vpdecc/vpdecc.h" #include "common_utility.hpp" #include "ibm_vpd_utils.hpp" #include "ipz_parser.hpp" #include "parser_factory.hpp" #include "vpd_exceptions.hpp" #include #include 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(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(&itrToRecordData[0]), thisRecord.recSize, const_cast(&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(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::checkRecordData() { auto itrToRecordData = vpdFile.cbegin(); std::advance(itrToRecordData, thisRecord.recOffset); auto itrToRecordECC = vpdFile.cbegin(); std::advance(itrToRecordECC, thisRecord.recECCoffset); checkECC(itrToRecordData, itrToRecordECC, thisRecord.recSize, thisRecord.recECCLength); } void EditorImpl::checkECC(Binary::const_iterator& itrToRecData, Binary::const_iterator& itrToECCData, RecordLength recLength, ECCLength eccLength) { auto l_status = vpdecc_check_data(const_cast(&itrToRecData[0]), recLength, const_cast(&itrToECCData[0]), eccLength); if (l_status == VPD_ECC_CORRECTABLE_DATA) { try { if (vpdFileStream.is_open()) { vpdFileStream.seekp(startOffset + thisRecord.recOffset, std::ios::beg); auto end = itrToRecData; std::advance(end, recLength); std::copy(itrToRecData, end, std::ostreambuf_iterator(vpdFileStream)); } else { throw std::runtime_error("Ecc correction failed"); } } catch (const std::fstream::failure& e) { std::cout << "Error while operating on file with exception"; throw std::runtime_error("Ecc correction failed"); } } else if (l_status != VPD_ECC_OK) { throw std::runtime_error("Ecc check failed"); } } 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 void EditorImpl::makeDbusCall( const std::string& object, const std::string& interface, const std::string& property, const std::variant& 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(), std::move(kwdData)); interfaces.emplace(commonInterface.key(), std::move(prop)); objects.emplace(objectPath, std::move(interfaces)); } } } } // Notify PIM common::utility::callPIM(std::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(), std::move(kwdData)); interfaces.emplace(extraInterface.key(), std::move(prop)); objects.emplace(objPath, std::move(interfaces)); } } } } } // Notify PIM common::utility::callPIM(std::move(objects)); } void EditorImpl::updateCache() { const std::vector& groupEEPROM = jsonFile["frus"][vpdFilePath].get_ref(); 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; if (singleInventory.find("inherit") != singleInventory.end()) { isInherit = singleInventory["inherit"].get(); } if (isInherit) { prop.emplace(getDbusNameForThisKw(thisRecord.recKWd), thisRecord.kwdUpdatedData); interfaces.emplace( (IPZ_INTERFACE + (std::string) "." + thisRecord.recName), std::move(prop)); objects.emplace( (singleInventory["inventoryPath"].get()), std::move(interfaces)); // process Common interface processAndUpdateCI(singleInventory["inventoryPath"] .get_ref()); } // process extra interfaces processAndUpdateEI(singleInventory, singleInventory["inventoryPath"] .get_ref()); // 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), std::move(prop)); objects.emplace( (singleInventory["inventoryPath"].get()), std::move(interfaces)); } } } // Notify PIM common::utility::callPIM(std::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(); inventory::ObjectMap objects; for (const auto& itemFRUS : groupFRUS.items()) { const std::vector& groupEEPROM = itemFRUS.value().get_ref(); 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(); 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 deprecate this com.ibm interface later interfaces.emplace(IBM_LOCATION_CODE_INF, prop); interfaces.emplace(XYZ_LOCATION_CODE_INF, std::move(prop)); } } objects.emplace(std::move(object), std::move(interfaces)); } } // Notify PIM common::utility::callPIM(std::move(objects)); } #ifndef ManagerTest static void enableRebootGuard() { try { auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit"); method.append("reboot-guard-enable.service", "replace"); bus.call_noreply(method); } catch (const sdbusplus::exception_t& e) { std::string errMsg = "Bus call to enable BMC reboot failed for reason: "; errMsg += e.what(); throw std::runtime_error(errMsg); } } static void disableRebootGuard() { try { auto bus = sdbusplus::bus::new_default(); auto method = bus.new_method_call( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartUnit"); method.append("reboot-guard-disable.service", "replace"); bus.call_noreply(method); } catch (const sdbusplus::exception_t& e) { using namespace phosphor::logging; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; std::string errMsg = "Bus call to disable BMC reboot failed for reason: "; errMsg += e.what(); log("Disable boot guard failed"); elog(); throw std::runtime_error(errMsg); } } #endif void EditorImpl::updateKeyword(const Binary& kwdData, uint32_t offset, const bool& updCache) { try { startOffset = offset; #ifndef ManagerTest // Restrict BMC from rebooting when VPD is being written. This will // prevent any data/ECC corruption in case BMC reboots while VPD update. enableRebootGuard(); Binary completeVPDFile; vpdFileStream.exceptions( std::ifstream::badbit | std::ifstream::failbit); try { vpdFileStream.open(vpdFilePath, std::ios::in | std::ios::out | std::ios::binary); auto vpdFileSize = std::min(std::filesystem::file_size(vpdFilePath), MAX_VPD_SIZE); if (vpdFileSize == 0) { std::cerr << "File size is 0 for " << vpdFilePath << std::endl; throw std::runtime_error("File size is 0."); } completeVPDFile.resize(vpdFileSize); vpdFileStream.seekg(startOffset, std::ios_base::cur); vpdFileStream.read(reinterpret_cast(&completeVPDFile[0]), vpdFileSize); vpdFileStream.clear(std::ios_base::eofbit); } catch (const std::system_error& fail) { std::cerr << "Exception in file handling [" << vpdFilePath << "] error : " << fail.what(); std::cerr << "Stream file size = " << vpdFileStream.gcount() << std::endl; throw; } vpdFile = completeVPDFile; if (objPath.empty() && jsonFile["frus"].find(vpdFilePath) != jsonFile["frus"].end()) { objPath = jsonFile["frus"][vpdFilePath][0]["inventoryPath"] .get_ref(); } #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) { // objPath should be empty only in case of test run. ParserInterface* Iparser = ParserFactory::getParser( completeVPDFile, objPath, vpdFilePath, startOffset); IpzVpdParser* ipzParser = dynamic_cast(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(); // Check Data before updating checkRecordData(); // 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()); } #ifndef ManagerTest // Once VPD data and Ecc update is done, disable BMC boot guard. disableRebootGuard(); #endif return; } else { throw openpower::vpd::exceptions::VpdDataException( "Could not find start tag in VPD " + vpdFilePath); } } catch (const std::exception& e) { #ifndef ManagerTest // Disable reboot guard. disableRebootGuard(); #endif throw std::runtime_error(e.what()); } } } // namespace editor } // namespace manager } // namespace vpd } // namespace openpower