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