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