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