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