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