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