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