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