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