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 =
68         std::any_of(ffdcFiles.begin(), ffdcFiles.end(), [](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(
313         _optionalSections.begin(), _optionalSections.end(), [](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(), [](auto& section) {
558                                    return section->header().id ==
559                                           static_cast<uint16_t>(
560                                               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 void PEL::updateTerminateBitInSRCSection()
589 {
590     //  Check for pel severity of type - 0x51 = critical error, system
591     //  termination
592     if (_uh->severity() == 0x51)
593     {
594         // Get the primary SRC section
595         auto pSRC = primarySRC();
596         if (pSRC)
597         {
598             (*pSRC)->setTerminateBit();
599         }
600     }
601 }
602 
603 void PEL::addJournalSections(const message::Entry& regEntry,
604                              const JournalBase& journal)
605 {
606     if (!regEntry.journalCapture)
607     {
608         return;
609     }
610 
611     // Write all unwritten journal data to disk.
612     journal.sync();
613 
614     const auto& jc = regEntry.journalCapture.value();
615     std::vector<std::vector<std::string>> allMessages;
616 
617     if (std::holds_alternative<size_t>(jc))
618     {
619         // Get the previous numLines journal entries
620         const auto& numLines = std::get<size_t>(jc);
621         try
622         {
623             auto messages = journal.getMessages("", numLines);
624             if (!messages.empty())
625             {
626                 allMessages.push_back(std::move(messages));
627             }
628         }
629         catch (const std::exception& e)
630         {
631             log<level::ERR>(
632                 fmt::format("Failed during journal collection: {}", e.what())
633                     .c_str());
634         }
635     }
636     else if (std::holds_alternative<message::AppCaptureList>(jc))
637     {
638         // Get journal entries based on the syslog id field.
639         const auto& sections = std::get<message::AppCaptureList>(jc);
640         for (const auto& [syslogID, numLines] : sections)
641         {
642             try
643             {
644                 auto messages = journal.getMessages(syslogID, numLines);
645                 if (!messages.empty())
646                 {
647                     allMessages.push_back(std::move(messages));
648                 }
649             }
650             catch (const std::exception& e)
651             {
652                 log<level::ERR>(
653                     fmt::format("Failed during journal collection: {}",
654                                 e.what())
655                         .c_str());
656             }
657         }
658     }
659 
660     // Create the UserData sections
661     for (const auto& messages : allMessages)
662     {
663         auto buffer = util::flattenLines(messages);
664 
665         // If the buffer is way too big, it can overflow the uint16_t
666         // PEL section size field that is checked below so do a cursory
667         // check here.
668         if (buffer.size() > _maxPELSize)
669         {
670             log<level::WARNING>(
671                 "Journal UserData section does not fit in PEL, dropping");
672             log<level::WARNING>(fmt::format("PEL size = {}, data size = {}",
673                                             size(), buffer.size())
674                                     .c_str());
675             continue;
676         }
677 
678         // Sections must be 4 byte aligned.
679         while (buffer.size() % 4 != 0)
680         {
681             buffer.push_back(0);
682         }
683 
684         auto ud = std::make_unique<UserData>(
685             static_cast<uint16_t>(ComponentID::phosphorLogging),
686             static_cast<uint8_t>(UserDataFormat::text),
687             static_cast<uint8_t>(UserDataFormatVersion::text), buffer);
688 
689         if (size() + ud->header().size <= _maxPELSize)
690         {
691             _optionalSections.push_back(std::move(ud));
692         }
693         else
694         {
695             // Don't attempt to shrink here since we'd be dropping the
696             // most recent journal entries which would be confusing.
697             log<level::WARNING>(
698                 "Journal UserData section does not fit in PEL, dropping");
699             log<level::WARNING>(fmt::format("PEL size = {}, UserData size = {}",
700                                             size(), ud->header().size)
701                                     .c_str());
702             ud.reset();
703             continue;
704         }
705     }
706 }
707 
708 namespace util
709 {
710 
711 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
712 {
713     auto jsonString = json.dump();
714     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
715 
716     // Pad to a 4 byte boundary
717     while ((jsonData.size() % 4) != 0)
718     {
719         jsonData.push_back(0);
720     }
721 
722     return std::make_unique<UserData>(
723         static_cast<uint16_t>(ComponentID::phosphorLogging),
724         static_cast<uint8_t>(UserDataFormat::json),
725         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
726 }
727 
728 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
729 {
730     assert(!ad.empty());
731     nlohmann::json json;
732 
733     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
734     if (ad.getValue("ESEL"))
735     {
736         auto newAD = ad;
737         newAD.remove("ESEL");
738         json = newAD.toJSON();
739     }
740     else
741     {
742         json = ad.toJSON();
743     }
744 
745     return makeJSONUserDataSection(json);
746 }
747 
748 void addProcessNameToJSON(nlohmann::json& json,
749                           const std::optional<std::string>& pid,
750                           const DataInterfaceBase& dataIface)
751 {
752     std::string name{unknownValue};
753 
754     try
755     {
756         if (pid)
757         {
758             auto n = dataIface.getProcessName(*pid);
759             if (n)
760             {
761                 name = *n;
762             }
763         }
764     }
765     catch (const std::exception& e)
766     {}
767 
768     if (pid)
769     {
770         json["Process Name"] = std::move(name);
771     }
772 }
773 
774 void addBMCFWVersionIDToJSON(nlohmann::json& json,
775                              const DataInterfaceBase& dataIface)
776 {
777     auto id = dataIface.getBMCFWVersionID();
778     if (id.empty())
779     {
780         id = unknownValue;
781     }
782 
783     json["FW Version ID"] = std::move(id);
784 }
785 
786 std::string lastSegment(char separator, std::string data)
787 {
788     auto pos = data.find_last_of(separator);
789     if (pos != std::string::npos)
790     {
791         data = data.substr(pos + 1);
792     }
793 
794     return data;
795 }
796 
797 void addIMKeyword(nlohmann::json& json, const DataInterfaceBase& dataIface)
798 {
799     auto keyword = dataIface.getSystemIMKeyword();
800 
801     std::string value{};
802 
803     std::for_each(keyword.begin(), keyword.end(), [&](const auto& byte) {
804         value += fmt::format("{:02X}", byte);
805     });
806 
807     json["System IM"] = value;
808 }
809 
810 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
811 {
812     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
813     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
814     json["HostState"] = lastSegment('.', dataIface.getHostState());
815     json["BootState"] = lastSegment('.', dataIface.getBootState());
816 }
817 
818 void addBMCUptime(nlohmann::json& json, const DataInterfaceBase& dataIface)
819 {
820     auto seconds = dataIface.getUptimeInSeconds();
821     if (seconds)
822     {
823         json["BMCUptime"] = dataIface.getBMCUptime(*seconds);
824     }
825     else
826     {
827         json["BMCUptime"] = "";
828     }
829     json["BMCLoad"] = dataIface.getBMCLoadAvg();
830 }
831 
832 std::unique_ptr<UserData>
833     makeSysInfoUserDataSection(const AdditionalData& ad,
834                                const DataInterfaceBase& dataIface,
835                                bool addUptime)
836 {
837     nlohmann::json json;
838 
839     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
840     addBMCFWVersionIDToJSON(json, dataIface);
841     addIMKeyword(json, dataIface);
842     addStatesToJSON(json, dataIface);
843 
844     if (addUptime)
845     {
846         addBMCUptime(json, dataIface);
847     }
848 
849     return makeJSONUserDataSection(json);
850 }
851 
852 std::vector<uint8_t> readFD(int fd)
853 {
854     std::vector<uint8_t> data;
855 
856     // Get the size
857     struct stat s;
858     int r = fstat(fd, &s);
859     if (r != 0)
860     {
861         auto e = errno;
862         log<level::ERR>("Could not get FFDC file size from FD",
863                         entry("ERRNO=%d", e));
864         return data;
865     }
866 
867     if (0 == s.st_size)
868     {
869         log<level::ERR>("FFDC file is empty");
870         return data;
871     }
872 
873     data.resize(s.st_size);
874 
875     // Make sure its at the beginning, as maybe another
876     // extension already used it.
877     r = lseek(fd, 0, SEEK_SET);
878     if (r == -1)
879     {
880         auto e = errno;
881         log<level::ERR>("Could not seek to beginning of FFDC file",
882                         entry("ERRNO=%d", e));
883         return data;
884     }
885 
886     r = read(fd, data.data(), s.st_size);
887     if (r == -1)
888     {
889         auto e = errno;
890         log<level::ERR>("Could not read FFDC file", entry("ERRNO=%d", e));
891     }
892     else if (r != s.st_size)
893     {
894         log<level::WARNING>("Could not read full FFDC file",
895                             entry("FILE_SIZE=%d", s.st_size),
896                             entry("SIZE_READ=%d", r));
897     }
898 
899     return data;
900 }
901 
902 std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
903                                                   const PelFFDCfile& file)
904 {
905     auto data = readFD(file.fd);
906 
907     if (data.empty())
908     {
909         return std::unique_ptr<UserData>();
910     }
911 
912     // The data needs 4 Byte alignment, and save amount padded for the
913     // CBOR case.
914     uint32_t pad = 0;
915     while (data.size() % 4)
916     {
917         data.push_back(0);
918         pad++;
919     }
920 
921     // For JSON, CBOR, and Text use our component ID, subType, and version,
922     // otherwise use the supplied ones.
923     uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging);
924     uint8_t subType{};
925     uint8_t version{};
926 
927     switch (file.format)
928     {
929         case UserDataFormat::json:
930             subType = static_cast<uint8_t>(UserDataFormat::json);
931             version = static_cast<uint8_t>(UserDataFormatVersion::json);
932             break;
933         case UserDataFormat::cbor:
934             subType = static_cast<uint8_t>(UserDataFormat::cbor);
935             version = static_cast<uint8_t>(UserDataFormatVersion::cbor);
936 
937             // The CBOR parser will fail on the extra pad bytes since they
938             // aren't CBOR.  Add the amount we padded to the end and other
939             // code will remove it all before parsing.
940             {
941                 data.resize(data.size() + 4);
942                 Stream stream{data};
943                 stream.offset(data.size() - 4);
944                 stream << pad;
945             }
946 
947             break;
948         case UserDataFormat::text:
949             subType = static_cast<uint8_t>(UserDataFormat::text);
950             version = static_cast<uint8_t>(UserDataFormatVersion::text);
951             break;
952         case UserDataFormat::custom:
953         default:
954             // Use the passed in values
955             compID = componentID;
956             subType = file.subType;
957             version = file.version;
958             break;
959     }
960 
961     return std::make_unique<UserData>(compID, subType, version, data);
962 }
963 
964 std::vector<uint8_t> flattenLines(const std::vector<std::string>& lines)
965 {
966     std::vector<uint8_t> out;
967 
968     for (const auto& line : lines)
969     {
970         out.insert(out.end(), line.begin(), line.end());
971 
972         if (out.back() != '\n')
973         {
974             out.push_back('\n');
975         }
976     }
977 
978     return out;
979 }
980 
981 } // namespace util
982 
983 } // namespace pels
984 } // namespace openpower
985