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             std::string jsonIndent(indentLevel, 0x20);
400             buf += jsonIndent + "\"Data\": [\n";
401             buf += dstr;
402             buf += jsonIndent + "]\n";
403             buf += "},\n";
404         }
405     }
406     else
407     {
408         buf += "\n\"Invalid Section\": [\n    \"invalid\"\n],\n";
409     }
410 }
411 
412 std::map<uint16_t, size_t> PEL::getPluralSections() const
413 {
414     std::map<uint16_t, size_t> sectionCounts;
415 
416     for (const auto& section : optionalSections())
417     {
418         if (sectionCounts.find(section->header().id) == sectionCounts.end())
419         {
420             sectionCounts[section->header().id] = 1;
421         }
422         else
423         {
424             sectionCounts[section->header().id]++;
425         }
426     }
427 
428     std::map<uint16_t, size_t> sections;
429     for (const auto& [id, count] : sectionCounts)
430     {
431         if (count > 1)
432         {
433             // Start with 0 here and printSectionInJSON()
434             // will increment it as it goes.
435             sections.emplace(id, 0);
436         }
437     }
438 
439     return sections;
440 }
441 
442 void PEL::toJSON(message::Registry& registry,
443                  const std::vector<std::string>& plugins) const
444 {
445     auto sections = getPluralSections();
446 
447     std::string buf = "{\n";
448     printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins,
449                        _ph->creatorID());
450     printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins,
451                        _ph->creatorID());
452     for (auto& section : this->optionalSections())
453     {
454         printSectionInJSON(*(section.get()), buf, sections, registry, plugins,
455                            _ph->creatorID());
456     }
457     buf += "}";
458     std::size_t found = buf.rfind(",");
459     if (found != std::string::npos)
460         buf.replace(found, 1, "");
461     std::cout << buf << std::endl;
462 }
463 
464 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData)
465 {
466     if (size() + userData->header().size > _maxPELSize)
467     {
468         if (userData->shrink(_maxPELSize - size()))
469         {
470             _optionalSections.push_back(std::move(userData));
471         }
472         else
473         {
474             lg2::warning("Could not shrink UserData section. Dropping. "
475                          "Section size = {SSIZE}, Component ID = {COMP_ID}, "
476                          "Subtype = {SUBTYPE}, Version = {VERSION}",
477                          "SSIZE", userData->header().size, "COMP_ID",
478                          userData->header().componentID, "SUBTYPE",
479                          userData->header().subType, "VERSION",
480                          userData->header().version);
481             return false;
482         }
483     }
484     else
485     {
486         _optionalSections.push_back(std::move(userData));
487     }
488     return true;
489 }
490 
491 nlohmann::json PEL::getCalloutJSON(const PelFFDC& ffdcFiles)
492 {
493     nlohmann::json callouts;
494 
495     for (const auto& file : ffdcFiles)
496     {
497         if ((file.format == UserDataFormat::json) &&
498             (file.subType == jsonCalloutSubtype))
499         {
500             auto data = util::readFD(file.fd);
501             if (data.empty())
502             {
503                 throw std::runtime_error{
504                     "Could not get data from JSON callout file descriptor"};
505             }
506 
507             std::string jsonString{data.begin(), data.begin() + data.size()};
508 
509             callouts = nlohmann::json::parse(jsonString);
510             break;
511         }
512     }
513 
514     return callouts;
515 }
516 
517 bool PEL::isHwCalloutPresent() const
518 {
519     auto pSRC = primarySRC();
520     if (!pSRC)
521     {
522         return false;
523     }
524 
525     bool calloutPresent = false;
526     if ((*pSRC)->callouts())
527     {
528         for (auto& i : (*pSRC)->callouts()->callouts())
529         {
530             if (((*i).fruIdentity()))
531             {
532                 auto& fruId = (*i).fruIdentity();
533                 if ((*fruId).failingComponentType() ==
534                     src::FRUIdentity::hardwareFRU)
535                 {
536                     calloutPresent = true;
537                     break;
538                 }
539             }
540         }
541     }
542 
543     return calloutPresent;
544 }
545 
546 void PEL::updateSysInfoInExtendedUserDataSection(
547     const DataInterfaceBase& dataIface)
548 {
549     const AdditionalData additionalData;
550 
551     // Check for PEL from Hostboot
552     if (_ph->creatorID() == static_cast<uint8_t>(CreatorID::hostboot))
553     {
554         // Get the ED section from PEL
555         auto op = std::find_if(_optionalSections.begin(),
556                                _optionalSections.end(), [](auto& section) {
557             return section->header().id ==
558                    static_cast<uint16_t>(SectionID::extUserData);
559         });
560 
561         // Check for ED section found and its not the last section of PEL
562         if (op != _optionalSections.end())
563         {
564             // Get the extended user data class mapped to found section
565             auto extUserData = static_cast<ExtendedUserData*>(op->get());
566 
567             // Check for the creator ID is for OpenBMC
568             if (extUserData->creatorID() ==
569                 static_cast<uint8_t>(CreatorID::openBMC))
570             {
571                 // Update subtype and component id
572                 auto subType = static_cast<uint8_t>(UserDataFormat::json);
573                 auto componentId =
574                     static_cast<uint16_t>(ComponentID::phosphorLogging);
575 
576                 // Update system data to ED section
577                 auto ud = util::makeSysInfoUserDataSection(additionalData,
578                                                            dataIface, false);
579                 extUserData->updateDataSection(subType, componentId,
580                                                ud->data());
581             }
582         }
583     }
584 }
585 
586 bool PEL::getDeconfigFlag() const
587 {
588     auto creator = static_cast<CreatorID>(_ph->creatorID());
589 
590     if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot))
591     {
592         auto src = primarySRC();
593         return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::deconfigured);
594     }
595     return false;
596 }
597 
598 bool PEL::getGuardFlag() const
599 {
600     auto creator = static_cast<CreatorID>(_ph->creatorID());
601 
602     if ((creator == CreatorID::openBMC) || (creator == CreatorID::hostboot))
603     {
604         auto src = primarySRC();
605         return (*src)->getErrorStatusFlag(SRC::ErrorStatusFlags::guarded);
606     }
607     return false;
608 }
609 
610 void PEL::updateTerminateBitInSRCSection()
611 {
612     //  Check for pel severity of type - 0x51 = critical error, system
613     //  termination
614     if (_uh->severity() == 0x51)
615     {
616         // Get the primary SRC section
617         auto pSRC = primarySRC();
618         if (pSRC)
619         {
620             (*pSRC)->setTerminateBit();
621         }
622     }
623 }
624 
625 void PEL::addJournalSections(const message::Entry& regEntry,
626                              const JournalBase& journal)
627 {
628     if (!regEntry.journalCapture)
629     {
630         return;
631     }
632 
633     // Write all unwritten journal data to disk.
634     journal.sync();
635 
636     const auto& jc = regEntry.journalCapture.value();
637     std::vector<std::vector<std::string>> allMessages;
638 
639     if (std::holds_alternative<size_t>(jc))
640     {
641         // Get the previous numLines journal entries
642         const auto& numLines = std::get<size_t>(jc);
643         try
644         {
645             auto messages = journal.getMessages("", numLines);
646             if (!messages.empty())
647             {
648                 allMessages.push_back(std::move(messages));
649             }
650         }
651         catch (const std::exception& e)
652         {
653             lg2::error("Failed during journal collection: {ERROR}", "ERROR", e);
654         }
655     }
656     else if (std::holds_alternative<message::AppCaptureList>(jc))
657     {
658         // Get journal entries based on the syslog id field.
659         const auto& sections = std::get<message::AppCaptureList>(jc);
660         for (const auto& [syslogID, numLines] : sections)
661         {
662             try
663             {
664                 auto messages = journal.getMessages(syslogID, numLines);
665                 if (!messages.empty())
666                 {
667                     allMessages.push_back(std::move(messages));
668                 }
669             }
670             catch (const std::exception& e)
671             {
672                 lg2::error("Failed during journal collection: {ERROR}", "ERROR",
673                            e);
674             }
675         }
676     }
677 
678     // Create the UserData sections
679     for (const auto& messages : allMessages)
680     {
681         auto buffer = util::flattenLines(messages);
682 
683         // If the buffer is way too big, it can overflow the uint16_t
684         // PEL section size field that is checked below so do a cursory
685         // check here.
686         if (buffer.size() > _maxPELSize)
687         {
688             lg2::warning(
689                 "Journal UserData section does not fit in PEL, dropping. "
690                 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}",
691                 "PEL_SIZE", size(), "DATA_SIZE", buffer.size());
692             continue;
693         }
694 
695         // Sections must be 4 byte aligned.
696         while (buffer.size() % 4 != 0)
697         {
698             buffer.push_back(0);
699         }
700 
701         auto ud = std::make_unique<UserData>(
702             static_cast<uint16_t>(ComponentID::phosphorLogging),
703             static_cast<uint8_t>(UserDataFormat::text),
704             static_cast<uint8_t>(UserDataFormatVersion::text), buffer);
705 
706         if (size() + ud->header().size <= _maxPELSize)
707         {
708             _optionalSections.push_back(std::move(ud));
709         }
710         else
711         {
712             // Don't attempt to shrink here since we'd be dropping the
713             // most recent journal entries which would be confusing.
714             lg2::warning(
715                 "Journal UserData section does not fit in PEL, dropping. "
716                 "PEL size = {PEL_SIZE}, data size = {DATA_SIZE}",
717                 "PEL_SIZE", size(), "DATA_SIZE", buffer.size());
718             ud.reset();
719             continue;
720         }
721     }
722 }
723 
724 namespace util
725 {
726 
727 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
728 {
729     auto jsonString = json.dump();
730     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
731 
732     // Pad to a 4 byte boundary
733     while ((jsonData.size() % 4) != 0)
734     {
735         jsonData.push_back(0);
736     }
737 
738     return std::make_unique<UserData>(
739         static_cast<uint16_t>(ComponentID::phosphorLogging),
740         static_cast<uint8_t>(UserDataFormat::json),
741         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
742 }
743 
744 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
745 {
746     assert(!ad.empty());
747     nlohmann::json json;
748 
749     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
750     if (ad.getValue("ESEL"))
751     {
752         auto newAD = ad;
753         newAD.remove("ESEL");
754         json = newAD.toJSON();
755     }
756     else
757     {
758         json = ad.toJSON();
759     }
760 
761     return makeJSONUserDataSection(json);
762 }
763 
764 void addProcessNameToJSON(nlohmann::json& json,
765                           const std::optional<std::string>& pid,
766                           const DataInterfaceBase& dataIface)
767 {
768     std::string name{unknownValue};
769 
770     try
771     {
772         if (pid)
773         {
774             auto n = dataIface.getProcessName(*pid);
775             if (n)
776             {
777                 name = *n;
778             }
779         }
780     }
781     catch (const std::exception& e)
782     {}
783 
784     if (pid)
785     {
786         json["Process Name"] = std::move(name);
787     }
788 }
789 
790 void addBMCFWVersionIDToJSON(nlohmann::json& json,
791                              const DataInterfaceBase& dataIface)
792 {
793     auto id = dataIface.getBMCFWVersionID();
794     if (id.empty())
795     {
796         id = unknownValue;
797     }
798 
799     json["FW Version ID"] = std::move(id);
800 }
801 
802 std::string lastSegment(char separator, std::string data)
803 {
804     auto pos = data.find_last_of(separator);
805     if (pos != std::string::npos)
806     {
807         data = data.substr(pos + 1);
808     }
809 
810     return data;
811 }
812 
813 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface)
814 {
815     auto keyword = dataIface.getSystemIMKeyword();
816 
817     std::string value{};
818 
819     std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) {
820         value += std::format("{:02X}", byte);
821     });
822 
823     json["System IM"] = value;
824 }
825 
826 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
827 {
828     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
829     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
830     json["HostState"] = lastSegment('.', dataIface.getHostState());
831     json["BootState"] = lastSegment('.', dataIface.getBootState());
832 }
833 
834 void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface)
835 {
836     auto seconds = dataIface.getUptimeInSeconds();
837     if (seconds)
838     {
839         json["BMCUptime"] = dataIface.getBMCUptime(*seconds);
840     }
841     else
842     {
843         json["BMCUptime"] = "";
844     }
845     json["BMCLoad"] = dataIface.getBMCLoadAvg();
846 }
847 
848 std::unique_ptr<UserData>
849     makeSysInfoUserDataSection(const AdditionalData& ad,
850                                const DataInterfaceBase& dataIface,
851                                bool addUptime)
852 {
853     nlohmann::json json;
854 
855     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
856     addBMCFWVersionIDToJSON(json, dataIface);
857     addIMKeyword(json, dataIface);
858     addStatesToJSON(json, dataIface);
859 
860     if (addUptime)
861     {
862         addBMCUptime(json, dataIface);
863     }
864 
865     return makeJSONUserDataSection(json);
866 }
867 
868 std::vector<uint8_t> readFD(int fd)
869 {
870     std::vector<uint8_t> data;
871 
872     // Get the size
873     struct stat s;
874     int r = fstat(fd, &s);
875     if (r != 0)
876     {
877         auto e = errno;
878         lg2::error("Could not get FFDC file size from FD, errno = {ERRNO}",
879                    "ERRNO", e);
880         return data;
881     }
882 
883     if (0 == s.st_size)
884     {
885         lg2::error("FFDC file is empty");
886         return data;
887     }
888 
889     data.resize(s.st_size);
890 
891     // Make sure its at the beginning, as maybe another
892     // extension already used it.
893     r = lseek(fd, 0, SEEK_SET);
894     if (r == -1)
895     {
896         auto e = errno;
897         lg2::error("Could not seek to beginning of FFDC file, errno = {ERRNO}",
898                    "ERRNO", e);
899         return data;
900     }
901 
902     r = read(fd, data.data(), s.st_size);
903     if (r == -1)
904     {
905         auto e = errno;
906         lg2::error("Could not read FFDC file, errno = {ERRNO}", "ERRNO", e);
907     }
908     else if (r != s.st_size)
909     {
910         lg2::warning("Could not read full FFDC file. "
911                      "File size = {FSIZE}, Size read = {SIZE_READ}",
912                      "FSIZE", s.st_size, "SIZE_READ", r);
913     }
914 
915     return data;
916 }
917 
918 std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
919                                                   const PelFFDCfile& file)
920 {
921     auto data = readFD(file.fd);
922 
923     if (data.empty())
924     {
925         return std::unique_ptr<UserData>();
926     }
927 
928     // The data needs 4 Byte alignment, and save amount padded for the
929     // CBOR case.
930     uint32_t pad = 0;
931     while (data.size() % 4)
932     {
933         data.push_back(0);
934         pad++;
935     }
936 
937     // For JSON, CBOR, and Text use our component ID, subType, and version,
938     // otherwise use the supplied ones.
939     uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging);
940     uint8_t subType{};
941     uint8_t version{};
942 
943     switch (file.format)
944     {
945         case UserDataFormat::json:
946             subType = static_cast<uint8_t>(UserDataFormat::json);
947             version = static_cast<uint8_t>(UserDataFormatVersion::json);
948             break;
949         case UserDataFormat::cbor:
950             subType = static_cast<uint8_t>(UserDataFormat::cbor);
951             version = static_cast<uint8_t>(UserDataFormatVersion::cbor);
952 
953             // The CBOR parser will fail on the extra pad bytes since they
954             // aren't CBOR.  Add the amount we padded to the end and other
955             // code will remove it all before parsing.
956             {
957                 data.resize(data.size() + 4);
958                 Stream stream{data};
959                 stream.offset(data.size() - 4);
960                 stream << pad;
961             }
962 
963             break;
964         case UserDataFormat::text:
965             subType = static_cast<uint8_t>(UserDataFormat::text);
966             version = static_cast<uint8_t>(UserDataFormatVersion::text);
967             break;
968         case UserDataFormat::custom:
969         default:
970             // Use the passed in values
971             compID = componentID;
972             subType = file.subType;
973             version = file.version;
974             break;
975     }
976 
977     return std::make_unique<UserData>(compID, subType, version, data);
978 }
979 
980 std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines)
981 {
982     std::vector<uint8_t> out;
983 
984     for (const auto& line : lines)
985     {
986         out.insert(out.end(), line.begin(), line.end());
987 
988         if (out.back() != '\n')
989         {
990             out.push_back('\n');
991         }
992     }
993 
994     return out;
995 }
996 
997 } // namespace util
998 
999 } // namespace pels
1000 } // namespace openpower
1001