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