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