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