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