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