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