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