xref: /openbmc/openpower-vpd-parser/vpd-manager/editor_impl.cpp (revision c0a534f4075fc515d143321e6a535006bf36ac87)
1 #include "config.h"
2 
3 #include "editor_impl.hpp"
4 
5 #include "ipz_parser.hpp"
6 #include "parser_factory.hpp"
7 #include "utils.hpp"
8 
9 #include "vpdecc/vpdecc.h"
10 
11 using namespace openpower::vpd::parser::interface;
12 using namespace openpower::vpd::constants;
13 using namespace openpower::vpd::parser::factory;
14 using namespace openpower::vpd::ipz::parser;
15 
16 namespace openpower
17 {
18 namespace vpd
19 {
20 namespace manager
21 {
22 namespace editor
23 {
24 
25 void EditorImpl::checkPTForRecord(Binary::const_iterator& iterator,
26                                   Byte ptLength)
27 {
28     // auto iterator = ptRecord.cbegin();
29     auto end = std::next(iterator, ptLength + 1);
30 
31     // Look at each entry in the PT keyword for the record name
32     while (iterator < end)
33     {
34         auto stop = std::next(iterator, lengths::RECORD_NAME);
35         std::string record(iterator, stop);
36 
37         if (record == thisRecord.recName)
38         {
39             // Skip record name and record type
40             std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType));
41 
42             // Get record offset
43             thisRecord.recOffset = readUInt16LE(iterator);
44 
45             // pass the record offset length to read record length
46             std::advance(iterator, lengths::RECORD_OFFSET);
47             thisRecord.recSize = readUInt16LE(iterator);
48 
49             std::advance(iterator, lengths::RECORD_LENGTH);
50             thisRecord.recECCoffset = readUInt16LE(iterator);
51 
52             ECCLength len;
53             std::advance(iterator, lengths::RECORD_ECC_OFFSET);
54             len = readUInt16LE(iterator);
55             thisRecord.recECCLength = len;
56 
57             // once we find the record we don't need to look further
58             return;
59         }
60         else
61         {
62             // Jump the record
63             std::advance(iterator, lengths::RECORD_NAME + sizeof(RecordType) +
64                                        sizeof(RecordOffset) +
65                                        sizeof(RecordLength) +
66                                        sizeof(ECCOffset) + sizeof(ECCLength));
67         }
68     }
69     // imples the record was not found
70     throw std::runtime_error("Record not found");
71 }
72 
73 void EditorImpl::updateData(const Binary& kwdData)
74 {
75     std::size_t lengthToUpdate = kwdData.size() <= thisRecord.kwdDataLength
76                                      ? kwdData.size()
77                                      : thisRecord.kwdDataLength;
78 
79     auto iteratorToNewdata = kwdData.cbegin();
80     auto end = iteratorToNewdata;
81     std::advance(end, lengthToUpdate);
82 
83     // update data in file buffer as it will be needed to update ECC
84     // avoiding extra stream operation here
85     auto iteratorToKWdData = vpdFile.begin();
86     std::advance(iteratorToKWdData, thisRecord.kwDataOffset);
87     std::copy(iteratorToNewdata, end, iteratorToKWdData);
88 
89 #ifdef ManagerTest
90     auto startItr = vpdFile.begin();
91     std::advance(iteratorToKWdData, thisRecord.kwDataOffset);
92     auto endItr = startItr;
93     std::advance(endItr, thisRecord.kwdDataLength);
94 
95     Binary updatedData(startItr, endItr);
96     if (updatedData == kwdData)
97     {
98         throw std::runtime_error("Data updated successfully");
99     }
100 #else
101 
102     // update data in EEPROM as well. As we will not write complete file back
103     vpdFileStream.seekp(startOffset + thisRecord.kwDataOffset, std::ios::beg);
104 
105     iteratorToNewdata = kwdData.cbegin();
106     std::copy(iteratorToNewdata, end,
107               std::ostreambuf_iterator<char>(vpdFileStream));
108 
109     // get a hold to new data in case encoding is needed
110     thisRecord.kwdUpdatedData.resize(thisRecord.kwdDataLength);
111     auto itrToKWdData = vpdFile.cbegin();
112     std::advance(itrToKWdData, thisRecord.kwDataOffset);
113     auto kwdDataEnd = itrToKWdData;
114     std::advance(kwdDataEnd, thisRecord.kwdDataLength);
115     std::copy(itrToKWdData, kwdDataEnd, thisRecord.kwdUpdatedData.begin());
116 #endif
117 }
118 
119 void EditorImpl::checkRecordForKwd()
120 {
121     RecordOffset recOffset = thisRecord.recOffset;
122 
123     // Amount to skip for record ID, size, and the RT keyword
124     constexpr auto skipBeg = sizeof(RecordId) + sizeof(RecordSize) +
125                              lengths::KW_NAME + sizeof(KwSize);
126 
127     auto iterator = vpdFile.cbegin();
128     std::advance(iterator, recOffset + skipBeg + lengths::RECORD_NAME);
129 
130     auto end = iterator;
131     std::advance(end, thisRecord.recSize);
132     std::size_t dataLength = 0;
133 
134     while (iterator < end)
135     {
136         // Note keyword name
137         std::string kw(iterator, iterator + lengths::KW_NAME);
138 
139         // Check if the Keyword starts with '#'
140         char kwNameStart = *iterator;
141         std::advance(iterator, lengths::KW_NAME);
142 
143         // if keyword starts with #
144         if (POUND_KW == kwNameStart)
145         {
146             // Note existing keyword data length
147             dataLength = readUInt16LE(iterator);
148 
149             // Jump past 2Byte keyword length + data
150             std::advance(iterator, sizeof(PoundKwSize));
151         }
152         else
153         {
154             // Note existing keyword data length
155             dataLength = *iterator;
156 
157             // Jump past keyword length and data
158             std::advance(iterator, sizeof(KwSize));
159         }
160 
161         if (thisRecord.recKWd == kw)
162         {
163             thisRecord.kwDataOffset = std::distance(vpdFile.cbegin(), iterator);
164             thisRecord.kwdDataLength = dataLength;
165             return;
166         }
167 
168         // jump the data of current kwd to point to next kwd name
169         std::advance(iterator, dataLength);
170     }
171 
172     throw std::runtime_error("Keyword not found");
173 }
174 
175 void EditorImpl::updateRecordECC()
176 {
177     auto itrToRecordData = vpdFile.cbegin();
178     std::advance(itrToRecordData, thisRecord.recOffset);
179 
180     auto itrToRecordECC = vpdFile.cbegin();
181     std::advance(itrToRecordECC, thisRecord.recECCoffset);
182 
183     auto l_status = vpdecc_create_ecc(
184         const_cast<uint8_t*>(&itrToRecordData[0]), thisRecord.recSize,
185         const_cast<uint8_t*>(&itrToRecordECC[0]), &thisRecord.recECCLength);
186     if (l_status != VPD_ECC_OK)
187     {
188         throw std::runtime_error("Ecc update failed");
189     }
190 
191     auto end = itrToRecordECC;
192     std::advance(end, thisRecord.recECCLength);
193 
194 #ifndef ManagerTest
195     vpdFileStream.seekp(startOffset + thisRecord.recECCoffset, std::ios::beg);
196     std::copy(itrToRecordECC, end,
197               std::ostreambuf_iterator<char>(vpdFileStream));
198 #endif
199 }
200 
201 auto EditorImpl::getValue(offsets::Offsets offset)
202 {
203     auto itr = vpdFile.cbegin();
204     std::advance(itr, offset);
205     LE2ByteData lowByte = *itr;
206     LE2ByteData highByte = *(itr + 1);
207     lowByte |= (highByte << 8);
208 
209     return lowByte;
210 }
211 
212 void EditorImpl::checkECC(Binary::const_iterator& itrToRecData,
213                           Binary::const_iterator& itrToECCData,
214                           RecordLength recLength, ECCLength eccLength)
215 {
216     auto l_status =
217         vpdecc_check_data(const_cast<uint8_t*>(&itrToRecData[0]), recLength,
218                           const_cast<uint8_t*>(&itrToECCData[0]), eccLength);
219 
220     if (l_status != VPD_ECC_OK)
221     {
222         throw std::runtime_error("Ecc check failed for VTOC");
223     }
224 }
225 
226 void EditorImpl::readVTOC()
227 {
228     // read VTOC offset
229     RecordOffset tocOffset = getValue(offsets::VTOC_PTR);
230 
231     // read VTOC record length
232     RecordLength tocLength = getValue(offsets::VTOC_REC_LEN);
233 
234     // read TOC ecc offset
235     ECCOffset tocECCOffset = getValue(offsets::VTOC_ECC_OFF);
236 
237     // read TOC ecc length
238     ECCLength tocECCLength = getValue(offsets::VTOC_ECC_LEN);
239 
240     auto itrToRecord = vpdFile.cbegin();
241     std::advance(itrToRecord, tocOffset);
242 
243     auto iteratorToECC = vpdFile.cbegin();
244     std::advance(iteratorToECC, tocECCOffset);
245 
246     // validate ecc for the record
247     checkECC(itrToRecord, iteratorToECC, tocLength, tocECCLength);
248 
249     // to get to the record name.
250     std::advance(itrToRecord, sizeof(RecordId) + sizeof(RecordSize) +
251                                   // Skip past the RT keyword, which contains
252                                   // the record name.
253                                   lengths::KW_NAME + sizeof(KwSize));
254 
255     std::string recordName(itrToRecord, itrToRecord + lengths::RECORD_NAME);
256 
257     if ("VTOC" != recordName)
258     {
259         throw std::runtime_error("VTOC record not found");
260     }
261 
262     // jump to length of PT kwd
263     std::advance(itrToRecord, lengths::RECORD_NAME + lengths::KW_NAME);
264 
265     // Note size of PT
266     Byte ptLen = *itrToRecord;
267     std::advance(itrToRecord, 1);
268 
269     checkPTForRecord(itrToRecord, ptLen);
270 }
271 
272 template <typename T>
273 void EditorImpl::makeDbusCall(const std::string& object,
274                               const std::string& interface,
275                               const std::string& property,
276                               const std::variant<T>& data)
277 {
278     auto bus = sdbusplus::bus::new_default();
279     auto properties =
280         bus.new_method_call(INVENTORY_MANAGER_SERVICE, object.c_str(),
281                             "org.freedesktop.DBus.Properties", "Set");
282     properties.append(interface);
283     properties.append(property);
284     properties.append(data);
285 
286     auto result = bus.call(properties);
287 
288     if (result.is_method_error())
289     {
290         throw std::runtime_error("bus call failed");
291     }
292 }
293 
294 void EditorImpl::processAndUpdateCI(const std::string& objectPath)
295 {
296     for (auto& commonInterface : jsonFile["commonInterfaces"].items())
297     {
298         for (auto& ciPropertyList : commonInterface.value().items())
299         {
300             if (ciPropertyList.value().type() ==
301                 nlohmann::json::value_t::object)
302             {
303                 if ((ciPropertyList.value().value("recordName", "") ==
304                      thisRecord.recName) &&
305                     (ciPropertyList.value().value("keywordName", "") ==
306                      thisRecord.recKWd))
307                 {
308                     std::string kwdData(thisRecord.kwdUpdatedData.begin(),
309                                         thisRecord.kwdUpdatedData.end());
310 
311                     makeDbusCall<std::string>((INVENTORY_PATH + objectPath),
312                                               commonInterface.key(),
313                                               ciPropertyList.key(), kwdData);
314                 }
315             }
316         }
317     }
318 }
319 
320 void EditorImpl::processAndUpdateEI(const nlohmann::json& Inventory,
321                                     const inventory::Path& objPath)
322 {
323     for (const auto& extraInterface : Inventory["extraInterfaces"].items())
324     {
325         if (extraInterface.value() != NULL)
326         {
327             for (const auto& eiPropertyList : extraInterface.value().items())
328             {
329                 if (eiPropertyList.value().type() ==
330                     nlohmann::json::value_t::object)
331                 {
332                     if ((eiPropertyList.value().value("recordName", "") ==
333                          thisRecord.recName) &&
334                         ((eiPropertyList.value().value("keywordName", "") ==
335                           thisRecord.recKWd)))
336                     {
337                         std::string kwdData(thisRecord.kwdUpdatedData.begin(),
338                                             thisRecord.kwdUpdatedData.end());
339                         makeDbusCall<std::string>(
340                             (INVENTORY_PATH + objPath), extraInterface.key(),
341                             eiPropertyList.key(),
342                             encodeKeyword(kwdData, eiPropertyList.value().value(
343                                                        "encoding", "")));
344                     }
345                 }
346             }
347         }
348     }
349 }
350 
351 void EditorImpl::updateCache()
352 {
353     const std::vector<nlohmann::json>& groupEEPROM =
354         jsonFile["frus"][vpdFilePath].get_ref<const nlohmann::json::array_t&>();
355 
356     // iterate through all the inventories for this file path
357     for (const auto& singleInventory : groupEEPROM)
358     {
359         // by default inherit property is true
360         bool isInherit = true;
361         bool isInheritEI = true;
362         bool isCpuModuleOnly = false;
363 
364         if (singleInventory.find("inherit") != singleInventory.end())
365         {
366             isInherit = singleInventory["inherit"].get<bool>();
367         }
368 
369         if (singleInventory.find("inheritEI") != singleInventory.end())
370         {
371             isInheritEI = singleInventory["inheritEI"].get<bool>();
372         }
373 
374         // "type" exists only in CPU module and FRU
375         if (singleInventory.find("type") != singleInventory.end())
376         {
377             if (singleInventory["type"] == "moduleOnly")
378             {
379                 isCpuModuleOnly = true;
380             }
381         }
382 
383         if (isInherit)
384         {
385             // update com interface
386             // For CPU- update  com interface only when isCI true
387             if ((!isCpuModuleOnly) || (isCpuModuleOnly && isCI))
388             {
389                 makeDbusCall<Binary>(
390                     (INVENTORY_PATH +
391                      singleInventory["inventoryPath"].get<std::string>()),
392                     (IPZ_INTERFACE + (std::string) "." + thisRecord.recName),
393                     thisRecord.recKWd, thisRecord.kwdUpdatedData);
394             }
395 
396             // process Common interface
397             processAndUpdateCI(singleInventory["inventoryPath"]
398                                    .get_ref<const nlohmann::json::string_t&>());
399         }
400 
401         if (isInheritEI)
402         {
403             if (isCpuModuleOnly)
404             {
405                 makeDbusCall<Binary>(
406                     (INVENTORY_PATH +
407                      singleInventory["inventoryPath"].get<std::string>()),
408                     (IPZ_INTERFACE + (std::string) "." + thisRecord.recName),
409                     thisRecord.recKWd, thisRecord.kwdUpdatedData);
410             }
411 
412             // process extra interfaces
413             processAndUpdateEI(singleInventory,
414                                singleInventory["inventoryPath"]
415                                    .get_ref<const nlohmann::json::string_t&>());
416         }
417     }
418 }
419 
420 void EditorImpl::expandLocationCode(const std::string& locationCodeType)
421 {
422     std::string propertyFCorTM{};
423     std::string propertySE{};
424 
425     if (locationCodeType == "fcs")
426     {
427         propertyFCorTM =
428             readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "FC");
429         propertySE =
430             readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VCEN", "SE");
431     }
432     else if (locationCodeType == "mts")
433     {
434         propertyFCorTM =
435             readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "TM");
436         propertySE =
437             readBusProperty(SYSTEM_OBJECT, "com.ibm.ipzvpd.VSYS", "SE");
438     }
439 
440     const nlohmann::json& groupFRUS =
441         jsonFile["frus"].get_ref<const nlohmann::json::object_t&>();
442     for (const auto& itemFRUS : groupFRUS.items())
443     {
444         const std::vector<nlohmann::json>& groupEEPROM =
445             itemFRUS.value().get_ref<const nlohmann::json::array_t&>();
446         for (const auto& itemEEPROM : groupEEPROM)
447         {
448             // check if the given item implements location code interface
449             if (itemEEPROM["extraInterfaces"].find(LOCATION_CODE_INF) !=
450                 itemEEPROM["extraInterfaces"].end())
451             {
452                 const std::string& unexpandedLocationCode =
453                     itemEEPROM["extraInterfaces"][LOCATION_CODE_INF]
454                               ["LocationCode"]
455                                   .get_ref<const nlohmann::json::string_t&>();
456                 std::size_t idx = unexpandedLocationCode.find(locationCodeType);
457                 if (idx != std::string::npos)
458                 {
459                     std::string expandedLoctionCode(unexpandedLocationCode);
460 
461                     if (locationCodeType == "fcs")
462                     {
463                         expandedLoctionCode.replace(
464                             idx, 3,
465                             propertyFCorTM.substr(0, 4) + ".ND0." + propertySE);
466                     }
467                     else if (locationCodeType == "mts")
468                     {
469                         std::replace(propertyFCorTM.begin(),
470                                      propertyFCorTM.end(), '-', '.');
471                         expandedLoctionCode.replace(
472                             idx, 3, propertyFCorTM + "." + propertySE);
473                     }
474 
475                     // update the DBUS interface
476                     makeDbusCall<std::string>(
477                         (INVENTORY_PATH +
478                          itemEEPROM["inventoryPath"]
479                              .get_ref<const nlohmann::json::string_t&>()),
480                         LOCATION_CODE_INF, "LocationCode", expandedLoctionCode);
481                 }
482             }
483         }
484     }
485 }
486 
487 string EditorImpl::getSysPathForThisFruType(const string& moduleObjPath,
488                                             const string& fruType)
489 {
490     string fruVpdPath;
491 
492     // get all FRUs list
493     for (const auto& eachFru : jsonFile["frus"].items())
494     {
495         bool moduleObjPathMatched = false;
496         bool expectedFruFound = false;
497 
498         for (const auto& eachInventory : eachFru.value())
499         {
500             const auto& thisObjectPath = eachInventory["inventoryPath"];
501 
502             // "type" exists only in CPU module and FRU
503             if (eachInventory.find("type") != eachInventory.end())
504             {
505                 // If inventory type is fruAndModule then set flag
506                 if (eachInventory["type"] == fruType)
507                 {
508                     expectedFruFound = true;
509                 }
510             }
511 
512             if (thisObjectPath == moduleObjPath)
513             {
514                 moduleObjPathMatched = true;
515             }
516         }
517 
518         // If condition satisfies then collect this sys path and exit
519         if (expectedFruFound && moduleObjPathMatched)
520         {
521             fruVpdPath = eachFru.key();
522             break;
523         }
524     }
525 
526     return fruVpdPath;
527 }
528 
529 void EditorImpl::getVpdPathForCpu()
530 {
531     isCI = false;
532     // keep a backup In case we need it later
533     inventory::Path vpdFilePathBackup = vpdFilePath;
534 
535     // TODO 1:Temp hardcoded list. create it dynamically.
536     std::vector<std::string> commonIntVINIKwds = {"PN", "SN", "DR"};
537     std::vector<std::string> commonIntVR10Kwds = {"DC"};
538     std::unordered_map<std::string, std::vector<std::string>>
539         commonIntRecordsList = {{"VINI", commonIntVINIKwds},
540                                 {"VR10", commonIntVR10Kwds}};
541 
542     // If requested Record&Kw is one among CI, then update 'FRU' type sys
543     // path, SPI2
544     unordered_map<std::string, vector<string>>::const_iterator isCommonInt =
545         commonIntRecordsList.find(thisRecord.recName);
546 
547     if ((isCommonInt != commonIntRecordsList.end()) &&
548         (find(isCommonInt->second.begin(), isCommonInt->second.end(),
549               thisRecord.recKWd) != isCommonInt->second.end()))
550     {
551         isCI = true;
552         vpdFilePath = getSysPathForThisFruType(objPath, "fruAndModule");
553     }
554     else
555     {
556         for (const auto& eachFru : jsonFile["frus"].items())
557         {
558             for (const auto& eachInventory : eachFru.value())
559             {
560                 if (eachInventory.find("type") != eachInventory.end())
561                 {
562                     const auto& thisObjectPath = eachInventory["inventoryPath"];
563                     if ((eachInventory["type"] == "moduleOnly") &&
564                         (eachInventory.value("inheritEI", true)) &&
565                         (thisObjectPath == static_cast<string>(objPath)))
566                     {
567                         vpdFilePath = eachFru.key();
568                     }
569                 }
570             }
571         }
572     }
573     // If it is not a CPU fru then go ahead with default vpdFilePath from
574     // fruMap
575     if (vpdFilePath.empty())
576     {
577         vpdFilePath = vpdFilePathBackup;
578     }
579 }
580 
581 void EditorImpl::updateKeyword(const Binary& kwdData, const bool& updCache)
582 {
583     startOffset = 0;
584 #ifndef ManagerTest
585 
586     getVpdPathForCpu();
587 
588     // check if offset present?
589     for (const auto& item : jsonFile["frus"][vpdFilePath])
590     {
591         if (item.find("offset") != item.end())
592         {
593             startOffset = item["offset"];
594         }
595     }
596 
597     // TODO: Figure out a better way to get max possible VPD size.
598     Binary completeVPDFile;
599     completeVPDFile.resize(65504);
600     vpdFileStream.open(vpdFilePath,
601                        std::ios::in | std::ios::out | std::ios::binary);
602 
603     vpdFileStream.seekg(startOffset, ios_base::cur);
604     vpdFileStream.read(reinterpret_cast<char*>(&completeVPDFile[0]), 65504);
605     completeVPDFile.resize(vpdFileStream.gcount());
606     vpdFileStream.clear(std::ios_base::eofbit);
607 
608     vpdFile = completeVPDFile;
609 
610 #else
611 
612     Binary completeVPDFile = vpdFile;
613 
614 #endif
615     if (vpdFile.empty())
616     {
617         throw std::runtime_error("Invalid File");
618     }
619     auto iterator = vpdFile.cbegin();
620     std::advance(iterator, IPZ_DATA_START);
621 
622     Byte vpdType = *iterator;
623     if (vpdType == KW_VAL_PAIR_START_TAG)
624     {
625         ParserInterface* Iparser =
626             ParserFactory::getParser(std::move(completeVPDFile));
627         IpzVpdParser* ipzParser = dynamic_cast<IpzVpdParser*>(Iparser);
628 
629         try
630         {
631             if (ipzParser == nullptr)
632             {
633                 throw std::runtime_error("Invalid cast");
634             }
635 
636             ipzParser->processHeader();
637             delete ipzParser;
638             ipzParser = nullptr;
639             // ParserFactory::freeParser(Iparser);
640 
641             // process VTOC for PTT rkwd
642             readVTOC();
643 
644             // check record for keywrod
645             checkRecordForKwd();
646 
647             // update the data to the file
648             updateData(kwdData);
649 
650             // update the ECC data for the record once data has been updated
651             updateRecordECC();
652 
653 #ifndef ManagerTest
654             if (updCache)
655             {
656                 // update the cache once data has been updated
657                 updateCache();
658             }
659 #endif
660         }
661         catch (const std::exception& e)
662         {
663             if (ipzParser != nullptr)
664             {
665                 delete ipzParser;
666             }
667             throw std::runtime_error(e.what());
668         }
669         return;
670     }
671 }
672 } // namespace editor
673 } // namespace manager
674 } // namespace vpd
675 } // namespace openpower
676