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