xref: /openbmc/phosphor-logging/extensions/openpower-pels/pel.cpp (revision 48c44dbb3cf5b7c6f419e96b602d06ff24a13f6e)
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 "pel.hpp"
17 
18 #include "bcd_time.hpp"
19 #include "extended_user_header.hpp"
20 #include "failing_mtms.hpp"
21 #include "json_utils.hpp"
22 #include "log_id.hpp"
23 #include "pel_rules.hpp"
24 #include "pel_values.hpp"
25 #include "section_factory.hpp"
26 #include "src.hpp"
27 #include "stream.hpp"
28 #include "user_data_formats.hpp"
29 
30 #include <sys/stat.h>
31 #include <unistd.h>
32 
33 #include <iostream>
34 #include <phosphor-logging/log.hpp>
35 
36 namespace openpower
37 {
38 namespace pels
39 {
40 namespace message = openpower::pels::message;
41 namespace pv = openpower::pels::pel_values;
42 using namespace phosphor::logging;
43 
44 constexpr auto unknownValue = "Unknown";
45 
46 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
47          phosphor::logging::Entry::Level severity,
48          const AdditionalData& additionalData, const PelFFDC& ffdcFiles,
49          const DataInterfaceBase& dataIface)
50 {
51     std::map<std::string, std::vector<std::string>> debugData;
52 
53     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
54                                           timestamp);
55     _uh = std::make_unique<UserHeader>(regEntry, severity, dataIface);
56 
57     auto src = std::make_unique<SRC>(regEntry, additionalData, dataIface);
58     if (!src->getDebugData().empty())
59     {
60         // Something didn't go as planned
61         debugData.emplace("SRC", src->getDebugData());
62     }
63 
64     auto euh = std::make_unique<ExtendedUserHeader>(dataIface, regEntry, *src);
65 
66     _optionalSections.push_back(std::move(src));
67     _optionalSections.push_back(std::move(euh));
68 
69     auto mtms = std::make_unique<FailingMTMS>(dataIface);
70     _optionalSections.push_back(std::move(mtms));
71 
72     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
73     addUserDataSection(std::move(ud));
74 
75     // Create a UserData section from AdditionalData.
76     if (!additionalData.empty())
77     {
78         ud = util::makeADUserDataSection(additionalData);
79         addUserDataSection(std::move(ud));
80     }
81 
82     // Add any FFDC files into UserData sections
83     for (const auto& file : ffdcFiles)
84     {
85         ud = util::makeFFDCuserDataSection(regEntry.componentID, file);
86         if (!ud)
87         {
88             // Add this error into the debug data UserData section
89             std::ostringstream msg;
90             msg << "Could not make PEL FFDC UserData section from file"
91                 << std::hex << regEntry.componentID << " " << file.subType
92                 << " " << file.version;
93             if (debugData.count("FFDC File"))
94             {
95                 debugData.at("FFDC File").push_back(msg.str());
96             }
97             else
98             {
99                 debugData.emplace("FFDC File",
100                                   std::vector<std::string>{msg.str()});
101             }
102 
103             continue;
104         }
105 
106         addUserDataSection(std::move(ud));
107     }
108 
109     // Store in the PEL any important debug data created while
110     // building the PEL sections.
111     if (!debugData.empty())
112     {
113         nlohmann::json data;
114         data["PEL Internal Debug Data"] = debugData;
115         ud = util::makeJSONUserDataSection(data);
116 
117         addUserDataSection(std::move(ud));
118 
119         // Also put in the journal for debug
120         for (const auto& [name, data] : debugData)
121         {
122             for (const auto& message : data)
123             {
124                 std::string entry = name + ": " + message;
125                 log<level::INFO>(entry.c_str());
126             }
127         }
128     }
129 
130     _ph->setSectionCount(2 + _optionalSections.size());
131 
132     checkRulesAndFix();
133 }
134 
135 PEL::PEL(std::vector<uint8_t>& data) : PEL(data, 0)
136 {
137 }
138 
139 PEL::PEL(std::vector<uint8_t>& data, uint32_t obmcLogID)
140 {
141     populateFromRawData(data, obmcLogID);
142 }
143 
144 void PEL::populateFromRawData(std::vector<uint8_t>& data, uint32_t obmcLogID)
145 {
146     Stream pelData{data};
147     _ph = std::make_unique<PrivateHeader>(pelData);
148     if (obmcLogID != 0)
149     {
150         _ph->setOBMCLogID(obmcLogID);
151     }
152 
153     _uh = std::make_unique<UserHeader>(pelData);
154 
155     // Use the section factory to create the rest of the objects
156     for (size_t i = 2; i < _ph->sectionCount(); i++)
157     {
158         auto section = section_factory::create(pelData);
159         _optionalSections.push_back(std::move(section));
160     }
161 }
162 
163 bool PEL::valid() const
164 {
165     bool valid = _ph->valid();
166 
167     if (valid)
168     {
169         valid = _uh->valid();
170     }
171 
172     if (valid)
173     {
174         if (!std::all_of(_optionalSections.begin(), _optionalSections.end(),
175                          [](const auto& section) { return section->valid(); }))
176         {
177             valid = false;
178         }
179     }
180 
181     return valid;
182 }
183 
184 void PEL::setCommitTime()
185 {
186     auto now = std::chrono::system_clock::now();
187     _ph->setCommitTimestamp(getBCDTime(now));
188 }
189 
190 void PEL::assignID()
191 {
192     _ph->setID(generatePELID());
193 }
194 
195 void PEL::flatten(std::vector<uint8_t>& pelBuffer) const
196 {
197     Stream pelData{pelBuffer};
198 
199     if (!valid())
200     {
201         log<level::WARNING>("Unflattening an invalid PEL");
202     }
203 
204     _ph->flatten(pelData);
205     _uh->flatten(pelData);
206 
207     for (auto& section : _optionalSections)
208     {
209         section->flatten(pelData);
210     }
211 }
212 
213 std::vector<uint8_t> PEL::data() const
214 {
215     std::vector<uint8_t> pelData;
216     flatten(pelData);
217     return pelData;
218 }
219 
220 size_t PEL::size() const
221 {
222     size_t size = 0;
223 
224     if (_ph)
225     {
226         size += _ph->header().size;
227     }
228 
229     if (_uh)
230     {
231         size += _uh->header().size;
232     }
233 
234     for (const auto& section : _optionalSections)
235     {
236         size += section->header().size;
237     }
238 
239     return size;
240 }
241 
242 std::optional<SRC*> PEL::primarySRC() const
243 {
244     auto src = std::find_if(
245         _optionalSections.begin(), _optionalSections.end(), [](auto& section) {
246             return section->header().id ==
247                    static_cast<uint16_t>(SectionID::primarySRC);
248         });
249     if (src != _optionalSections.end())
250     {
251         return static_cast<SRC*>(src->get());
252     }
253 
254     return std::nullopt;
255 }
256 
257 void PEL::checkRulesAndFix()
258 {
259     auto [actionFlags, eventType] =
260         pel_rules::check(_uh->actionFlags(), _uh->eventType(), _uh->severity());
261 
262     _uh->setActionFlags(actionFlags);
263     _uh->setEventType(eventType);
264 }
265 
266 void PEL::printSectionInJSON(const Section& section, std::string& buf,
267                              std::map<uint16_t, size_t>& pluralSections,
268                              message::Registry& registry,
269                              const std::vector<std::string>& plugins,
270                              uint8_t creatorID) const
271 {
272     char tmpB[5];
273     uint8_t id[] = {static_cast<uint8_t>(section.header().id >> 8),
274                     static_cast<uint8_t>(section.header().id)};
275     sprintf(tmpB, "%c%c", id[0], id[1]);
276     std::string sectionID(tmpB);
277     std::string sectionName = pv::sectionTitles.count(sectionID)
278                                   ? pv::sectionTitles.at(sectionID)
279                                   : "Unknown Section";
280 
281     // Add a count if there are multiple of this type of section
282     auto count = pluralSections.find(section.header().id);
283     if (count != pluralSections.end())
284     {
285         sectionName += " " + std::to_string(count->second);
286         count->second++;
287     }
288 
289     if (section.valid())
290     {
291         std::optional<std::string> json;
292         if (sectionID == "PS" || sectionID == "SS")
293         {
294             json = section.getJSON(registry);
295         }
296         else if (sectionID == "UD")
297         {
298             std::string subsystem = getNumberString("%c", tolower(creatorID));
299             std::string component =
300                 getNumberString("%04x", section.header().componentID);
301             if ((std::find(plugins.begin(), plugins.end(),
302                            subsystem + component) != plugins.end()) ||
303                 pv::creatorIDs.at(getNumberString("%c", creatorID)) == "BMC")
304             {
305                 json = section.getJSON(creatorID);
306             }
307         }
308         else
309         {
310             json = section.getJSON();
311         }
312 
313         buf += "\"" + sectionName + "\": {\n";
314 
315         if (json)
316         {
317             buf += *json + "\n},\n";
318         }
319         else
320         {
321             jsonInsert(buf, pv::sectionVer,
322                        getNumberString("%d", section.header().version), 1);
323             jsonInsert(buf, pv::subSection,
324                        getNumberString("%d", section.header().subType), 1);
325             jsonInsert(buf, pv::createdBy,
326                        getNumberString("0x%X", section.header().componentID),
327                        1);
328 
329             std::vector<uint8_t> data;
330             Stream s{data};
331             section.flatten(s);
332             std::string dstr =
333                 dumpHex(std::data(data) + SectionHeader::flattenedSize(),
334                         data.size() - SectionHeader::flattenedSize(), 2);
335             std::string jsonIndent(indentLevel, 0x20);
336             buf += jsonIndent + "\"Data\": [\n";
337             buf += dstr;
338             buf += jsonIndent + "]\n";
339             buf += "},\n";
340         }
341     }
342     else
343     {
344         buf += "\n\"Invalid Section\": [\n    \"invalid\"\n],\n";
345     }
346 }
347 
348 std::map<uint16_t, size_t> PEL::getPluralSections() const
349 {
350     std::map<uint16_t, size_t> sectionCounts;
351 
352     for (const auto& section : optionalSections())
353     {
354         if (sectionCounts.find(section->header().id) == sectionCounts.end())
355         {
356             sectionCounts[section->header().id] = 1;
357         }
358         else
359         {
360             sectionCounts[section->header().id]++;
361         }
362     }
363 
364     std::map<uint16_t, size_t> sections;
365     for (const auto& [id, count] : sectionCounts)
366     {
367         if (count > 1)
368         {
369             // Start with 0 here and printSectionInJSON()
370             // will increment it as it goes.
371             sections.emplace(id, 0);
372         }
373     }
374 
375     return sections;
376 }
377 
378 void PEL::toJSON(message::Registry& registry,
379                  const std::vector<std::string>& plugins) const
380 {
381     auto sections = getPluralSections();
382 
383     std::string buf = "{\n";
384     printSectionInJSON(*(_ph.get()), buf, sections, registry, plugins);
385     printSectionInJSON(*(_uh.get()), buf, sections, registry, plugins);
386     for (auto& section : this->optionalSections())
387     {
388         printSectionInJSON(*(section.get()), buf, sections, registry, plugins,
389                            _ph->creatorID());
390     }
391     buf += "}";
392     std::size_t found = buf.rfind(",");
393     if (found != std::string::npos)
394         buf.replace(found, 1, "");
395     std::cout << buf << std::endl;
396 }
397 
398 bool PEL::addUserDataSection(std::unique_ptr<UserData> userData)
399 {
400     if (size() + userData->header().size > _maxPELSize)
401     {
402         if (userData->shrink(_maxPELSize - size()))
403         {
404             _optionalSections.push_back(std::move(userData));
405         }
406         else
407         {
408             log<level::WARNING>(
409                 "Could not shrink UserData section. Dropping",
410                 entry("SECTION_SIZE=%d\n", userData->header().size),
411                 entry("COMPONENT_ID=0x%02X", userData->header().componentID),
412                 entry("SUBTYPE=0x%X", userData->header().subType),
413                 entry("VERSION=0x%X", userData->header().version));
414             return false;
415         }
416     }
417     else
418     {
419         _optionalSections.push_back(std::move(userData));
420     }
421     return true;
422 }
423 
424 namespace util
425 {
426 
427 std::unique_ptr<UserData> makeJSONUserDataSection(const nlohmann::json& json)
428 {
429     auto jsonString = json.dump();
430     std::vector<uint8_t> jsonData(jsonString.begin(), jsonString.end());
431 
432     // Pad to a 4 byte boundary
433     while ((jsonData.size() % 4) != 0)
434     {
435         jsonData.push_back(0);
436     }
437 
438     return std::make_unique<UserData>(
439         static_cast<uint16_t>(ComponentID::phosphorLogging),
440         static_cast<uint8_t>(UserDataFormat::json),
441         static_cast<uint8_t>(UserDataFormatVersion::json), jsonData);
442 }
443 
444 std::unique_ptr<UserData> makeADUserDataSection(const AdditionalData& ad)
445 {
446     assert(!ad.empty());
447     nlohmann::json json;
448 
449     // Remove the 'ESEL' entry, as it contains a full PEL in the value.
450     if (ad.getValue("ESEL"))
451     {
452         auto newAD = ad;
453         newAD.remove("ESEL");
454         json = newAD.toJSON();
455     }
456     else
457     {
458         json = ad.toJSON();
459     }
460 
461     return makeJSONUserDataSection(json);
462 }
463 
464 void addProcessNameToJSON(nlohmann::json& json,
465                           const std::optional<std::string>& pid,
466                           const DataInterfaceBase& dataIface)
467 {
468     std::string name{unknownValue};
469 
470     try
471     {
472         if (pid)
473         {
474             auto n = dataIface.getProcessName(*pid);
475             if (n)
476             {
477                 name = *n;
478             }
479         }
480     }
481     catch (std::exception& e)
482     {
483     }
484 
485     json["Process Name"] = std::move(name);
486 }
487 
488 void addBMCFWVersionIDToJSON(nlohmann::json& json,
489                              const DataInterfaceBase& dataIface)
490 {
491     auto id = dataIface.getBMCFWVersionID();
492     if (id.empty())
493     {
494         id = unknownValue;
495     }
496 
497     json["BMC Version ID"] = std::move(id);
498 }
499 
500 std::string lastSegment(char separator, std::string data)
501 {
502     auto pos = data.find_last_of(separator);
503     if (pos != std::string::npos)
504     {
505         data = data.substr(pos + 1);
506     }
507 
508     return data;
509 }
510 
511 void addStatesToJSON(nlohmann::json& json, const DataInterfaceBase& dataIface)
512 {
513     json["BMCState"] = lastSegment('.', dataIface.getBMCState());
514     json["ChassisState"] = lastSegment('.', dataIface.getChassisState());
515     json["HostState"] = lastSegment('.', dataIface.getHostState());
516 }
517 
518 std::unique_ptr<UserData>
519     makeSysInfoUserDataSection(const AdditionalData& ad,
520                                const DataInterfaceBase& dataIface)
521 {
522     nlohmann::json json;
523 
524     addProcessNameToJSON(json, ad.getValue("_PID"), dataIface);
525     addBMCFWVersionIDToJSON(json, dataIface);
526     addStatesToJSON(json, dataIface);
527 
528     return makeJSONUserDataSection(json);
529 }
530 
531 std::vector<uint8_t> readFD(int fd)
532 {
533     std::vector<uint8_t> data;
534 
535     // Get the size
536     struct stat s;
537     int r = fstat(fd, &s);
538     if (r != 0)
539     {
540         auto e = errno;
541         log<level::ERR>("Could not get FFDC file size from FD",
542                         entry("ERRNO=%d", e));
543         return data;
544     }
545 
546     if (0 == s.st_size)
547     {
548         log<level::ERR>("FFDC file is empty");
549         return data;
550     }
551 
552     data.resize(s.st_size);
553 
554     // Make sure its at the beginning, as maybe another
555     // extension already used it.
556     r = lseek(fd, 0, SEEK_SET);
557     if (r == -1)
558     {
559         auto e = errno;
560         log<level::ERR>("Could not seek to beginning of FFDC file",
561                         entry("ERRNO=%d", e));
562         return data;
563     }
564 
565     r = read(fd, data.data(), s.st_size);
566     if (r == -1)
567     {
568         auto e = errno;
569         log<level::ERR>("Could not read FFDC file", entry("ERRNO=%d", e));
570     }
571     else if (r != s.st_size)
572     {
573         log<level::WARNING>("Could not read full FFDC file",
574                             entry("FILE_SIZE=%d", s.st_size),
575                             entry("SIZE_READ=%d", r));
576     }
577 
578     return data;
579 }
580 
581 std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
582                                                   const PelFFDCfile& file)
583 {
584     auto data = readFD(file.fd);
585 
586     if (data.empty())
587     {
588         return std::unique_ptr<UserData>();
589     }
590 
591     // The data needs 4 Byte alignment, and save amount padded for the
592     // CBOR case.
593     uint32_t pad = 0;
594     while (data.size() % 4)
595     {
596         data.push_back(0);
597         pad++;
598     }
599 
600     // For JSON, CBOR, and Text use our component ID, subType, and version,
601     // otherwise use the supplied ones.
602     uint16_t compID = static_cast<uint16_t>(ComponentID::phosphorLogging);
603     uint8_t subType{};
604     uint8_t version{};
605 
606     switch (file.format)
607     {
608         case UserDataFormat::json:
609             subType = static_cast<uint8_t>(UserDataFormat::json);
610             version = static_cast<uint8_t>(UserDataFormatVersion::json);
611             break;
612         case UserDataFormat::cbor:
613             subType = static_cast<uint8_t>(UserDataFormat::cbor);
614             version = static_cast<uint8_t>(UserDataFormatVersion::cbor);
615 
616             // The CBOR parser will fail on the extra pad bytes since they
617             // aren't CBOR.  Add the amount we padded to the end and other
618             // code will remove it all before parsing.
619             {
620                 data.resize(data.size() + 4);
621                 Stream stream{data};
622                 stream.offset(data.size() - 4);
623                 stream << pad;
624             }
625 
626             break;
627         case UserDataFormat::text:
628             subType = static_cast<uint8_t>(UserDataFormat::text);
629             version = static_cast<uint8_t>(UserDataFormatVersion::text);
630             break;
631         case UserDataFormat::custom:
632         default:
633             // Use the passed in values
634             compID = componentID;
635             subType = file.subType;
636             version = file.version;
637             break;
638     }
639 
640     return std::make_unique<UserData>(compID, subType, version, data);
641 }
642 
643 } // namespace util
644 
645 } // namespace pels
646 } // namespace openpower
647