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