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