1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "config.h"
17 
18 #include "pel.hpp"
19 
20 #include "bcd_time.hpp"
21 #include "extended_user_data.hpp"
22 #include "extended_user_header.hpp"
23 #include "failing_mtms.hpp"
24 #include "fru_identity.hpp"
25 #include "json_utils.hpp"
26 #include "log_id.hpp"
27 #include "pel_rules.hpp"
28 #include "pel_values.hpp"
29 #include "section_factory.hpp"
30 #include "src.hpp"
31 #include "stream.hpp"
32 #include "user_data_formats.hpp"
33 
34 #ifdef PEL_ENABLE_PHAL
35 #include "phal_service_actions.hpp"
36 #include "sbe_ffdc_handler.hpp"
37 
38 #include <libguard/guard_interface.hpp>
39 #include <libguard/include/guard_record.hpp>
40 
41 namespace libguard = openpower::guard;
42 #endif
43 
44 #include <sys/stat.h>
45 #include <unistd.h>
46 
47 #include <phosphor-logging/lg2.hpp>
48 
49 #include <format>
50 #include <iostream>
51 #include <ranges>
52 
53 namespace openpower
54 {
55 namespace pels
56 {
57 namespace pv = openpower::pels::pel_values;
58 
59 constexpr auto unknownValue = "Unknown";
60 constexpr auto AdDIMMInfoFetchError = "DIMMs Info Fetch Error";
61 
PEL(const message::Entry & regEntry,uint32_t obmcLogID,uint64_t timestamp,phosphor::logging::Entry::Level severity,const AdditionalData & additionalData,const PelFFDC & ffdcFilesIn,const DataInterfaceBase & dataIface,const JournalBase & journal)62 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
63          phosphor::logging::Entry::Level severity,
64          const AdditionalData& additionalData, const PelFFDC& ffdcFilesIn,
65          const DataInterfaceBase& dataIface, const JournalBase& journal)
66 {
67     // No changes in input, for non SBE error related requests
68     PelFFDC ffdcFiles = ffdcFilesIn;
69 
70 #ifdef PEL_ENABLE_PHAL
71     // Add sbe ffdc processed data into ffdcfiles.
72     namespace sbe = openpower::pels::sbe;
73     auto processReq =
74         std::any_of(ffdcFiles.begin(), ffdcFiles.end(), [](const auto& file) {
75             return file.format == UserDataFormat::custom &&
76                    file.subType == sbe::sbeFFDCSubType;
77         });
78     // sbeFFDC can't be destroyed until the end of the PEL constructor
79     // because it needs to keep around the FFDC Files to be used below.
80     std::unique_ptr<sbe::SbeFFDC> sbeFFDCPtr;
81     if (processReq)
82     {
83         sbeFFDCPtr =
84             std::make_unique<sbe::SbeFFDC>(additionalData, ffdcFilesIn);
85         const auto& sbeFFDCFiles = sbeFFDCPtr->getSbeFFDC();
86         ffdcFiles.insert(ffdcFiles.end(), sbeFFDCFiles.begin(),
87                          sbeFFDCFiles.end());
88 
89         // update pel priority for spare clock failures
90         if (auto customSeverity = sbeFFDCPtr->getSeverity())
91         {
92             severity = customSeverity.value();
93         }
94     }
95 #endif
96 
97     DebugData debugData;
98     nlohmann::json callouts;
99 
100     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
101                                           timestamp);
102     _uh = std::make_unique<UserHeader>(regEntry, severity, additionalData,
103                                        dataIface);
104 
105     // Extract any callouts embedded in an FFDC file.
106     if (!ffdcFiles.empty())
107     {
108         try
109         {
110             callouts = getCalloutJSON(ffdcFiles);
111         }
112         catch (const std::exception& e)
113         {
114             debugData.emplace("FFDC file JSON callouts error",
115                               std::vector<std::string>{e.what()});
116         }
117     }
118 
119     auto src =
120         std::make_unique<SRC>(regEntry, additionalData, callouts, dataIface);
121 
122     nlohmann::json adSysInfoData(nlohmann::json::value_t::object);
123     addAdDetailsForDIMMsCallout(src, dataIface, adSysInfoData, debugData);
124 
125     if (!src->getDebugData().empty())
126     {
127         // Something didn't go as planned
128         debugData.emplace("SRC", src->getDebugData());
129     }
130 
131     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src);
132 
133     _optionalSections.push_back(std::move(src));
134     _optionalSections.push_back(std::move(euh));
135 
136     auto mtms = std::make_unique<FailingMTMS>(dataIface);
137     _optionalSections.push_back(std::move(mtms));
138 
139     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface, true,
140                                                adSysInfoData);
141     addUserDataSection(std::move(ud));
142 
143     //  Check for pel severity of type - 0x51 = critical error, system
144     //  termination and update terminate bit in SRC for pels
145     updateTerminateBitInSRCSection();
146 
147     // Create a UserData section from AdditionalData.
148     if (!additionalData.empty())
149     {
150         ud = util::makeADUserDataSection(additionalData);
151         addUserDataSection(std::move(ud));
152     }
153 
154     // Add any FFDC files into UserData sections
155     for (const auto& file : ffdcFiles)
156     {
157         ud = util::makeFFDCuserDataSection(regEntry.componentID, file);
158         if (!ud)
159         {
160             // Add this error into the debug data UserData section
161             std::ostringstream msg;
162             msg << "Could not make PEL FFDC UserData section from file"
163                 << std::hex << regEntry.componentID << " " << file.subType
164                 << " " << file.version;
165             if (debugData.count("FFDC File"))
166             {
167                 debugData.at("FFDC File").push_back(msg.str());
168             }
169             else
170             {
171                 debugData.emplace("FFDC File",
172                                   std::vector<std::string>{msg.str()});
173             }
174 
175             continue;
176         }
177 
178         addUserDataSection(std::move(ud));
179     }
180 
181 #ifdef PEL_ENABLE_PHAL
182     auto path = std::string(OBJ_ENTRY) + '/' + std::to_string(obmcLogID);
183     openpower::pels::phal::createServiceActions(callouts, dataIface, plid());
184 #endif
185 
186     // Store in the PEL any important debug data created while
187     // building the PEL sections.
188     if (!debugData.empty())
189     {
190         nlohmann::json data;
191         data["PEL Internal Debug Data"] = debugData;
192         ud = util::makeJSONUserDataSection(data);
193 
194         addUserDataSection(std::move(ud));
195 
196         // Also put in the journal for debug
197         for (const auto& [name, msgs] : debugData)
198         {
199             for (const auto& message : msgs)
200             {
201                 lg2::info("{NAME}: {MSG}", "NAME", name, "MSG", message);
202             }
203         }
204     }
205 
206     addJournalSections(regEntry, journal);
207 
208     _ph->setSectionCount(2 + _optionalSections.size());
209 
210     checkRulesAndFix();
211 }
212 
PEL(std::vector<uint8_t> & data)213 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0) {}
214 
PEL(std::vector<uint8_t> & data,uint32_t obmcLogID)215 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
216 {
217     populateFromRawData(data, obmcLogID);
218 }
219 
populateFromRawData(std::vector<uint8_t> & data,uint32_t obmcLogID)220 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
221 {
222     Stream pelData{data};
223     _ph = std::make_unique<PrivateHeader>(pelData);
224     if (obmcLogID != 0)
225     {
226         _ph->setOBMCLogID(obmcLogID);
227     }
228 
229     _uh = std::make_unique<UserHeader>(pelData);
230 
231     // Use the section factory to create the rest of the objects
232     for (size_t i = 2; i < _ph->sectionCount(); i++)
233     {
234         auto section = section_factory::create(pelData);
235         _optionalSections.push_back(std::move(section));
236     }
237 }
238 
valid() const239 bool PEL::valid() const
240 {
241     bool valid = _ph->valid();
242 
243     if (valid)
244     {
245         valid = _uh->valid();
246     }
247 
248     if (valid)
249     {
250         if (!std::all_of(_optionalSections.begin(), _optionalSections.end(),
251                          [](const auto& section) { return section->valid(); }))
252         {
253             valid = false;
254         }
255     }
256 
257     return valid;
258 }
259 
setCommitTime()260 void PEL::setCommitTime()
261 {
262     auto now = std::chrono::system_clock::now();
263     _ph->setCommitTimestamp(getBCDTime(now));
264 }
265 
assignID()266 void PEL::assignID()
267 {
268     _ph->setID(generatePELID());
269 }
270 
flatten(std::vector<uint8_t> & pelBuffer) const271 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
272 {
273     Stream pelData{pelBuffer};
274 
275     if (!valid())
276     {
277         lg2::warning("Unflattening an invalid PEL");
278     }
279 
280     _ph->flatten(pelData);
281     _uh->flatten(pelData);
282 
283     for (auto& section : _optionalSections)
284     {
285         section->flatten(pelData);
286     }
287 }
288 
data() const289 std::vector<uint8_t> PEL::data() const
290 {
291     std::vector<uint8_t> pelData;
292     flatten(pelData);
293     return pelData;
294 }
295 
size() const296 size_t PEL::size() const
297 {
298     size_t size = 0;
299 
300     if (_ph)
301     {
302         size += _ph->header().size;
303     }
304 
305     if (_uh)
306     {
307         size += _uh->header().size;
308     }
309 
310     for (const auto& section : _optionalSections)
311     {
312         size += section->header().size;
313     }
314 
315     return size;
316 }
317 
primarySRC() const318 std::optional<SRC*> PEL::primarySRC() const
319 {
320     auto src = std::find_if(
321         _optionalSections.begin(), _optionalSections.end(), [](auto& section) {
322             return section->header().id ==
323                    static_cast<uint16_t>(SectionID::primarySRC);
324         });
325     if (src != _optionalSections.end())
326     {
327         return static_cast<SRC*>(src->get());
328     }
329 
330     return std::nullopt;
331 }
332 
checkRulesAndFix()333 void PEL::checkRulesAndFix()
334 {
335     // Only fix if the action flags are at their default value which
336     // means they weren't specified in the registry.  Otherwise
337     // assume the user knows what they are doing.
338     if (_uh->actionFlags() == actionFlagsDefault)
339     {
340         auto [actionFlags, eventType] =
341             pel_rules::check(0, _uh->eventType(), _uh->severity());
342 
343         _uh->setActionFlags(actionFlags);
344         _uh->setEventType(eventType);
345     }
346 }
347 
printSectionInJSON(const Section & section,std::string & buf,std::map<uint16_t,size_t> & pluralSections,message::Registry & registry,const std::vector<std::string> & plugins,uint8_t creatorID) const348 void PEL::printSectionInJSON(
349     const Section& section, std::string& buf,
350     std::map<uint16_t, size_t>& pluralSections, message::Registry& registry,
351     const std::vector<std::string>& plugins, uint8_t creatorID) const
352 {
353     char tmpB[5];
354     uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
355                     static_cast<uint8_t>(section.header().id)};
356     sprintf(tmpB, "%c%c", id[0], id[1]);
357     std::string sectionID(tmpB);
358     std::string sectionName = pv::sectionTitles.count(sectionID)
359                                   ? pv::sectionTitles.at(sectionID)
360                                   : "Unknown Section";
361 
362     // Add a count if there are multiple of this type of section
363     auto count = pluralSections.find(section.header().id);
364     if (count != pluralSections.end())
365     {
366         sectionName += " " + std::to_string(count->second);
367         count->second++;
368     }
369 
370     if (section.valid())
371     {
372         std::optional<std::string> json;
373         if (sectionID == "PS" || sectionID == "SS")
374         {
375             json = section.getJSON(registry, plugins, creatorID);
376         }
377         else if ((sectionID == "UD") || (sectionID == "ED"))
378         {
379             json = section.getJSON(creatorID, plugins);
380         }
381         else
382         {
383             json = section.getJSON(creatorID);
384         }
385 
386         buf += "\"" + sectionName + "\": {\n";
387 
388         if (json)
389         {
390             buf += *json + "\n},\n";
391         }
392         else
393         {
394             jsonInsert(buf, pv::sectionVer,
395                        getNumberString("%d", section.header().version), 1);
396             jsonInsert(buf, pv::subSection,
397                        getNumberString("%d", section.header().subType), 1);
398             jsonInsert(buf, pv::createdBy,
399                        getNumberString("0x%X", section.header().componentID),
400                        1);
401 
402             std::vector<uint8_t> data;
403             Stream s{data};
404             section.flatten(s);
405             std::string dstr =
406                 dumpHex(std::data(data) + SectionHeader::flattenedSize(),
407                         data.size() - SectionHeader::flattenedSize(), 2)
408                     .get();
409             std::string jsonIndent(indentLevel, 0x20);
410             buf += jsonIndent + "\"Data\": [\n";
411             buf += dstr;
412             buf += jsonIndent + "]\n";
413             buf += "},\n";
414         }
415     }
416     else
417     {
418         buf += "\n\"Invalid Section\": [\n    \"invalid\"\n],\n";
419     }
420 }
421 
getPluralSections() const422 std::map<uint16_t, size_t> PEL::getPluralSections() const
423 {
424     std::map<uint16_t, size_t> sectionCounts;
425 
426     for (const auto& section : optionalSections())
427     {
428         if (sectionCounts.find(section->header().id) == sectionCounts.end())
429         {
430             sectionCounts[section->header().id] = 1;
431         }
432         else
433         {
434             sectionCounts[section->header().id]++;
435         }
436     }
437 
438     std::map<uint16_t, size_t> sections;
439     for (const auto& [id, count] : sectionCounts)
440     {
441         if (count > 1)
442         {
443             // Start with 0 here and printSectionInJSON()
444             // will increment it as it goes.
445             sections.emplace(id, 0);
446         }
447     }
448 
449     return sections;
450 }
451 
toJSON(message::Registry & registry,const std::vector<std::string> & plugins) const452 void PEL::toJSON(message::Registry& registry,
453                  const std::vector<std::string>& plugins) const
454 {
455     auto sections = getPluralSections();
456 
457     std::string buf = "{\n";
458     printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins,
459                        _ph->creatorID());
460     printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins,
461                        _ph->creatorID());
462     for (auto& section : this->optionalSections())
463     {
464         printSectionInJSON(*(section.get()), buf, sections, registry, plugins,
465                            _ph->creatorID());
466     }
467     buf += "}";
468     std::size_t found = buf.rfind(",");
469     if (found != std::string::npos)
470         buf.replace(found, 1, "");
471     std::cout << buf << std::endl;
472 }
473 
addUserDataSection(std::unique_ptr<UserData> userData)474 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData)
475 {
476     if (size() + userData->header().size > _maxPELSize)
477     {
478         if (userData->shrink(_maxPELSize - size()))
479         {
480             _optionalSections.push_back(std::move(userData));
481         }
482         else
483         {
484             lg2::warning("Could not shrink UserData section. Dropping. "
485                          "Section size = {SSIZE}, Component ID = {COMP_ID}, "
486                          "Subtype = {SUBTYPE}, Version = {VERSION}",
487                          "SSIZE", userData->header().size, "COMP_ID",
488                          userData->header().componentID, "SUBTYPE",
489                          userData->header().subType, "VERSION",
490                          userData->header().version);
491             return false;
492         }
493     }
494     else
495     {
496         _optionalSections.push_back(std::move(userData));
497     }
498     return true;
499 }
500 
getCalloutJSON(const PelFFDC & ffdcFiles)501 nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles)
502 {
503     nlohmann::json callouts;
504 
505     for (const auto& file : ffdcFiles)
506     {
507         if ((file.format == UserDataFormat::json) &&
508             (file.subType == jsonCalloutSubtype))
509         {
510             auto data = util::readFD(file.fd);
511             if (data.empty())
512             {
513                 throw std::runtime_error{
514                     "Could not get data from JSON callout file descriptor"};
515             }
516 
517             std::string jsonString{data.begin(), data.begin() + data.size()};
518 
519             callouts = nlohmann::json::parse(jsonString);
520             break;
521         }
522     }
523 
524     return callouts;
525 }
526 
isHwCalloutPresent() const527 bool PEL::isHwCalloutPresent() const
528 {
529     auto pSRC = primarySRC();
530     if (!pSRC)
531     {
532         return false;
533     }
534 
535     bool calloutPresent = false;
536     if ((*pSRC)->callouts())
537     {
538         for (auto& i : (*pSRC)->callouts()->callouts())
539         {
540             if (((*i).fruIdentity()))
541             {
542                 auto& fruId = (*i).fruIdentity();
543                 if ((*fruId).failingComponentType() ==
544                     src::FRUIdentity::hardwareFRU)
545                 {
546                     calloutPresent = true;
547                     break;
548                 }
549             }
550         }
551     }
552 
553     return calloutPresent;
554 }
555 
updateSysInfoInExtendedUserDataSection(const DataInterfaceBase & dataIface)556 void PEL::updateSysInfoInExtendedUserDataSection(
557     const DataInterfaceBase& dataIface)
558 {
559     const AdditionalData additionalData;
560 
561     // Check for PEL from Hostboot
562     if (_ph->creatorID() == static_cast<uint8_t>(CreatorID::hostboot))
563     {
564         // Get the ED section from PEL
565         auto op = std::find_if(
566             _optionalSections.begin(), _optionalSections.end(),
567             [](auto& section) {
568                 return section->header().id ==
569                        static_cast<uint16_t>(SectionID::extUserData);
570             });
571 
572         // Check for ED section found and its not the last section of PEL
573         if (op != _optionalSections.end())
574         {
575             // Get the extended user data class mapped to found section
576             auto extUserData = static_cast<ExtendedUserData*>(op->get());
577 
578             // Check for the creator ID is for OpenBMC
579             if (extUserData->creatorID() ==
580                 static_cast<uint8_t>(CreatorID::openBMC))
581             {
582                 // Update subtype and component id
583                 auto subType = static_cast<uint8_t>(UserDataFormat::json);
584                 auto componentId =
585                     static_cast<uint16_t>(ComponentID::phosphorLogging);
586 
587                 // Update system data to ED section
588                 auto ud = util::makeSysInfoUserDataSection(additionalData,
589                                                            dataIface, false);
590                 extUserData->updateDataSection(subType, componentId,
591                                                ud->data());
592             }
593         }
594     }
595 }
596 
getDeconfigFlag() const597 bool PEL::getDeconfigFlag() const
598 {
599     auto creator = static_cast<CreatorID>(_ph->creatorID());
600 
601     if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot))
602     {
603         auto src = primarySRC();
604         return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured);
605     }
606     return false;
607 }
608 
getGuardFlag() const609 bool PEL::getGuardFlag() const
610 {
611     auto creator = static_cast<CreatorID>(_ph->creatorID());
612 
613     if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot))
614     {
615         auto src = primarySRC();
616         return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::guarded);
617     }
618     return false;
619 }
620 
updateTerminateBitInSRCSection()621 void PEL::updateTerminateBitInSRCSection()
622 {
623     //  Check for pel severity of type - 0x51 = critical error, system
624     //  termination
625     if (_uh->severity() == 0x51)
626     {
627         // Get the primary SRC section
628         auto pSRC = primarySRC();
629         if (pSRC)
630         {
631             (*pSRC)->setTerminateBit();
632         }
633     }
634 }
635 
addJournalSections(const message::Entry & regEntry,const JournalBase & journal)636 void PEL::addJournalSections(const message::Entry& regEntry,
637                              const JournalBase& journal)
638 {
639     if (!regEntry.journalCapture)
640     {
641         return;
642     }
643 
644     // Write all unwritten journal data to disk.
645     journal.sync();
646 
647     const auto& jc = regEntry.journalCapture.value();
648     std::vector<std::vector<std::string>> allMessages;
649 
650     if (std::holds_alternative<size_t>(jc))
651     {
652         // Get the previous numLines journal entries
653         const auto& numLines = std::get<size_t>(jc);
654         try
655         {
656             auto messages = journal.getMessages("", numLines);
657             if (!messages.empty())
658             {
659                 allMessages.push_back(std::move(messages));
660             }
661         }
662         catch (const std::exception& e)
663         {
664             lg2::error("Failed during journal collection: {ERROR}", "ERROR", e);
665         }
666     }
667     else if (std::holds_alternative<message::AppCaptureList>(jc))
668     {
669         // Get journal entries based on the syslog id field.
670         const auto& sections = std::get<message::AppCaptureList>(jc);
671         for (const auto& [syslogID, numLines] : sections)
672         {
673             try
674             {
675                 auto messages = journal.getMessages(syslogID, numLines);
676                 if (!messages.empty())
677                 {
678                     allMessages.push_back(std::move(messages));
679                 }
680             }
681             catch (const std::exception& e)
682             {
683                 lg2::error("Failed during journal collection: {ERROR}", "ERROR",
684                            e);
685             }
686         }
687     }
688 
689     // Create the UserData sections
690     for (const auto& messages : allMessages)
691     {
692         auto buffer = util::flattenLines(messages);
693 
694         // If the buffer is way too big, it can overflow the uint16_t
695         // PEL section size field that is checked below so do a cursory
696         // check here.
697         if (buffer.size() > _maxPELSize)
698         {
699             lg2::warning(
700                 "Journal UserData section does not fit in PEL, dropping. "
701                 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}",
702                 "PEL_SIZE", size(), "DATA_SIZE", buffer.size());
703             continue;
704         }
705 
706         // Sections must be 4 byte aligned.
707         while (buffer.size() % 4 != 0)
708         {
709             buffer.push_back(0);
710         }
711 
712         auto ud = std::make_unique<UserData>(
713             static_cast<uint16_t>(ComponentID::phosphorLogging),
714             static_cast<uint8_t>(UserDataFormat::text),
715             static_cast<uint8_t>(UserDataFormatVersion::text), buffer);
716 
717         if (size() + ud->header().size <= _maxPELSize)
718         {
719             _optionalSections.push_back(std::move(ud));
720         }
721         else
722         {
723             // Don't attempt to shrink here since we'd be dropping the
724             // most recent journal entries which would be confusing.
725             lg2::warning(
726                 "Journal UserData section does not fit in PEL, dropping. "
727                 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}",
728                 "PEL_SIZE", size(), "DATA_SIZE", buffer.size());
729             ud.reset();
730             continue;
731         }
732     }
733 }
734 
addAdDetailsForDIMMsCallout(const std::unique_ptr<SRC> & src,const DataInterfaceBase & dataIface,nlohmann::json & adSysInfoData,DebugData & debugData)735 void PEL::addAdDetailsForDIMMsCallout(
736     const std::unique_ptr<SRC>& src, const DataInterfaceBase& dataIface,
737     nlohmann::json& adSysInfoData, DebugData& debugData)
738 {
739     if (!src->callouts())
740     {
741         // No callouts
742         return;
743     }
744 
745     auto isDIMMCallout = [&dataIface, &debugData](const auto& callout) {
746         auto locCode{callout->locationCode()};
747         if (locCode.empty())
748         {
749             // Not a hardware callout. No action required
750             return false;
751         }
752         else
753         {
754             return const_cast<DataInterfaceBase&>(dataIface).isDIMM(locCode);
755         }
756     };
757     auto addAdDIMMDetails = [&dataIface, &adSysInfoData,
758                              &debugData](const auto& callout) {
759         auto dimmLocCode{callout->locationCode()};
760 
761         auto diPropVal = dataIface.getDIProperty(dimmLocCode);
762         if (!diPropVal.has_value())
763         {
764             std::string errMsg{
765                 std::format("Failed reading DI property from "
766                             "VINI Interface for the LocationCode:[{}]",
767                             dimmLocCode)};
768             debugData[AdDIMMInfoFetchError].emplace_back(errMsg);
769         }
770         else
771         {
772             util::addDIMMInfo(dimmLocCode, diPropVal.value(), adSysInfoData);
773         }
774     };
775 
776     auto DIMMsCallouts = src->callouts()->callouts() |
777                          std::views::filter(isDIMMCallout);
778 
779     std::ranges::for_each(DIMMsCallouts, addAdDIMMDetails);
780 }
781 
782 namespace util
783 {
784 
makeJSONUserDataSection(const nlohmann::json & json)785 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
786 {
787     std::string jsonString;
788     try
789     {
790         jsonString = json.dump();
791     }
792     catch (const std::exception& e)
793     {
794         lg2::error("json.dump() failed with: {ERROR}", "ERROR", e);
795         jsonString = "Invalid JSON passed to makeJSONUserDataSection!";
796     }
797 
798     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
799 
800     // Pad to a 4 byte boundary
801     while ((jsonData.size() % 4) != 0)
802     {
803         jsonData.push_back(0);
804     }
805 
806     return std::make_unique<UserData>(
807         static_cast<uint16_t>(ComponentID::phosphorLogging),
808         static_cast<uint8_t>(UserDataFormat::json),
809         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
810 }
811 
makeADUserDataSection(const AdditionalData & ad)812 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
813 {
814     assert(!ad.empty());
815     nlohmann::json json;
816 
817     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
818     if (ad.getValue("ESEL"))
819     {
820         auto newAD = ad;
821         newAD.remove("ESEL");
822         json = newAD.toJSON();
823     }
824     else
825     {
826         json = ad.toJSON();
827     }
828 
829     return makeJSONUserDataSection(json);
830 }
831 
addProcessNameToJSON(nlohmann::json & json,const std::optional<std::string> & pid,const DataInterfaceBase & dataIface)832 void addProcessNameToJSON(nlohmann::json& json,
833                           const std::optional<std::string>& pid,
834                           const DataInterfaceBase& dataIface)
835 {
836     std::string name{unknownValue};
837 
838     try
839     {
840         if (pid)
841         {
842             auto n = dataIface.getProcessName(*pid);
843             if (n)
844             {
845                 name = *n;
846             }
847         }
848     }
849     catch (const std::exception& e)
850     {}
851 
852     if (pid)
853     {
854         json["Process Name"] = std::move(name);
855     }
856 }
857 
addBMCFWVersionIDToJSON(nlohmann::json & json,const DataInterfaceBase & dataIface)858 void addBMCFWVersionIDToJSON(nlohmann::json& json,
859                              const DataInterfaceBase& dataIface)
860 {
861     auto id = dataIface.getBMCFWVersionID();
862     if (id.empty())
863     {
864         id = unknownValue;
865     }
866 
867     json["FW Version ID"] = std::move(id);
868 }
869 
lastSegment(char separator,std::string data)870 std::string lastSegment(char separator, std::string data)
871 {
872     auto pos = data.find_last_of(separator);
873     if (pos != std::string::npos)
874     {
875         data = data.substr(pos + 1);
876     }
877 
878     return data;
879 }
880 
addIMKeyword(nlohmann::json & json,const DataInterfaceBase & dataIface)881 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface)
882 {
883     auto keyword = dataIface.getSystemIMKeyword();
884 
885     std::string value{};
886 
887     std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) {
888         value += std::format("{:02X}", byte);
889     });
890 
891     json["System IM"] = value;
892 }
893 
addStatesToJSON(nlohmann::json & json,const DataInterfaceBase & dataIface)894 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
895 {
896     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
897     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
898     json["HostState"] = lastSegment('.', dataIface.getHostState());
899     json["BootState"] = lastSegment('.', dataIface.getBootState());
900 }
901 
addBMCUptime(nlohmann::json & json,const DataInterfaceBase & dataIface)902 void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface)
903 {
904     auto seconds = dataIface.getUptimeInSeconds();
905     if (seconds)
906     {
907         json["BMCUptime"] = dataIface.getBMCUptime(*seconds);
908     }
909     else
910     {
911         json["BMCUptime"] = "";
912     }
913     json["BMCLoad"] = dataIface.getBMCLoadAvg();
914 }
915 
makeSysInfoUserDataSection(const AdditionalData & ad,const DataInterfaceBase & dataIface,bool addUptime,const nlohmann::json & adSysInfoData)916 std::unique_ptr<UserData> makeSysInfoUserDataSection(
917     const AdditionalData& ad, const DataInterfaceBase& dataIface,
918     bool addUptime, const nlohmann::json& adSysInfoData)
919 {
920     nlohmann::json json;
921 
922     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
923     addBMCFWVersionIDToJSON(json, dataIface);
924     addIMKeyword(json, dataIface);
925     addStatesToJSON(json, dataIface);
926 
927     if (addUptime)
928     {
929         addBMCUptime(json, dataIface);
930     }
931 
932     if (!adSysInfoData.empty())
933     {
934         json.update(adSysInfoData);
935     }
936 
937     return makeJSONUserDataSection(json);
938 }
939 
readFD(int fd)940 std::vector<uint8_t> readFD(int fd)
941 {
942     std::vector<uint8_t> data;
943 
944     // Get the size
945     struct stat s;
946     int r = fstat(fd, &s);
947     if (r != 0)
948     {
949         auto e = errno;
950         lg2::error("Could not get FFDC file size from FD, errno = {ERRNO}",
951                    "ERRNO", e);
952         return data;
953     }
954 
955     if (0 == s.st_size)
956     {
957         lg2::error("FFDC file is empty");
958         return data;
959     }
960 
961     data.resize(s.st_size);
962 
963     // Make sure its at the beginning, as maybe another
964     // extension already used it.
965     r = lseek(fd, 0, SEEK_SET);
966     if (r == -1)
967     {
968         auto e = errno;
969         lg2::error("Could not seek to beginning of FFDC file, errno = {ERRNO}",
970                    "ERRNO", e);
971         return data;
972     }
973 
974     r = read(fd, data.data(), s.st_size);
975     if (r == -1)
976     {
977         auto e = errno;
978         lg2::error("Could not read FFDC file, errno = {ERRNO}", "ERRNO", e);
979     }
980     else if (r != s.st_size)
981     {
982         lg2::warning("Could not read full FFDC file. "
983                      "File size = {FSIZE}, Size read = {SIZE_READ}",
984                      "FSIZE", s.st_size, "SIZE_READ", r);
985     }
986 
987     return data;
988 }
989 
990 std::unique_ptr<UserData>
makeFFDCuserDataSection(uint16_t componentID,const PelFFDCfile & file)991     makeFFDCuserDataSection(uint16_t componentID, const PelFFDCfile& file)
992 {
993     auto data = readFD(file.fd);
994 
995     if (data.empty())
996     {
997         return std::unique_ptr<UserData>();
998     }
999 
1000     // The data needs 4 Byte alignment, and save amount padded for the
1001     // CBOR case.
1002     uint32_t pad = 0;
1003     while (data.size() % 4)
1004     {
1005         data.push_back(0);
1006         pad++;
1007     }
1008 
1009     // For JSON, CBOR, and Text use our component ID, subType, and version,
1010     // otherwise use the supplied ones.
1011     uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging);
1012     uint8_t subType{};
1013     uint8_t version{};
1014 
1015     switch (file.format)
1016     {
1017         case UserDataFormat::json:
1018             subType = static_cast<uint8_t>(UserDataFormat::json);
1019             version = static_cast<uint8_t>(UserDataFormatVersion::json);
1020             break;
1021         case UserDataFormat::cbor:
1022             subType = static_cast<uint8_t>(UserDataFormat::cbor);
1023             version = static_cast<uint8_t>(UserDataFormatVersion::cbor);
1024 
1025             // The CBOR parser will fail on the extra pad bytes since they
1026             // aren't CBOR.  Add the amount we padded to the end and other
1027             // code will remove it all before parsing.
1028             {
1029                 data.resize(data.size() + 4);
1030                 Stream stream{data};
1031                 stream.offset(data.size() - 4);
1032                 stream << pad;
1033             }
1034 
1035             break;
1036         case UserDataFormat::text:
1037             subType = static_cast<uint8_t>(UserDataFormat::text);
1038             version = static_cast<uint8_t>(UserDataFormatVersion::text);
1039             break;
1040         case UserDataFormat::custom:
1041         default:
1042             // Use the passed in values
1043             compID = componentID;
1044             subType = file.subType;
1045             version = file.version;
1046             break;
1047     }
1048 
1049     return std::make_unique<UserData>(compID, subType, version, data);
1050 }
1051 
flattenLines(const std::vector<std::string> & lines)1052 std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines)
1053 {
1054     std::vector<uint8_t> out;
1055 
1056     for (const auto& line : lines)
1057     {
1058         out.insert(out.end(), line.begin(), line.end());
1059 
1060         if (out.back() != '\n')
1061         {
1062             out.push_back('\n');
1063         }
1064     }
1065 
1066     return out;
1067 }
1068 
addDIMMInfo(const std::string & locationCode,const std::vector<std::uint8_t> & diPropVal,nlohmann::json & adSysInfoData)1069 void addDIMMInfo(const std::string& locationCode,
1070                  const std::vector<std::uint8_t>& diPropVal,
1071                  nlohmann::json& adSysInfoData)
1072 {
1073     nlohmann::json dimmInfoObj;
1074     dimmInfoObj["Location Code"] = locationCode;
1075     std::ranges::transform(
1076         diPropVal, std::back_inserter(dimmInfoObj["DRAM Manufacturer ID"]),
1077         [](const auto& diPropEachByte) {
1078             return std::format("{:#04x}", diPropEachByte);
1079         });
1080     adSysInfoData["DIMMs Additional Info"] += dimmInfoObj;
1081 }
1082 
1083 } // namespace util
1084 
1085 } // namespace pels
1086 } // namespace openpower
1087