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 "config.h" 17 18 #include "../bcd_time.hpp" 19 #include "../json_utils.hpp" 20 #include "../paths.hpp" 21 #include "../pel.hpp" 22 #include "../pel_types.hpp" 23 #include "../pel_values.hpp" 24 25 #include <CLI/CLI.hpp> 26 #include <bitset> 27 #include <fstream> 28 #include <iostream> 29 #include <phosphor-logging/log.hpp> 30 #include <regex> 31 #include <string> 32 #include <xyz/openbmc_project/Common/File/error.hpp> 33 34 namespace fs = std::filesystem; 35 using namespace phosphor::logging; 36 using namespace openpower::pels; 37 using sdbusplus::exception::SdBusError; 38 namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error; 39 namespace message = openpower::pels::message; 40 namespace pv = openpower::pels::pel_values; 41 42 using PELFunc = std::function<void(const PEL&)>; 43 message::Registry registry(getMessageRegistryPath() / message::registryFileName, 44 false); 45 namespace service 46 { 47 constexpr auto logging = "xyz.openbmc_project.Logging"; 48 } // namespace service 49 50 namespace interface 51 { 52 constexpr auto deleteObj = "xyz.openbmc_project.Object.Delete"; 53 constexpr auto deleteAll = "xyz.openbmc_project.Collection.DeleteAll"; 54 } // namespace interface 55 56 namespace object_path 57 { 58 constexpr auto logEntry = "/xyz/openbmc_project/logging/entry/"; 59 constexpr auto logging = "/xyz/openbmc_project/logging"; 60 } // namespace object_path 61 62 /** 63 * @brief helper function to get PEL commit timestamp from file name 64 * @retrun BCDTime - PEL commit timestamp 65 * @param[in] std::string - file name 66 */ 67 BCDTime fileNameToTimestamp(const std::string& fileName) 68 { 69 std::string token = fileName.substr(0, fileName.find("_")); 70 int i = 0; 71 BCDTime tmp; 72 if (token.length() >= 14) 73 { 74 try 75 { 76 tmp.yearMSB = std::stoi(token.substr(i, 2), 0, 16); 77 } 78 catch (std::exception& err) 79 { 80 std::cout << "Conversion failure: " << err.what() << std::endl; 81 } 82 i += 2; 83 try 84 { 85 tmp.yearLSB = std::stoi(token.substr(i, 2), 0, 16); 86 } 87 catch (std::exception& err) 88 { 89 std::cout << "Conversion failure: " << err.what() << std::endl; 90 } 91 i += 2; 92 try 93 { 94 tmp.month = std::stoi(token.substr(i, 2), 0, 16); 95 } 96 catch (std::exception& err) 97 { 98 std::cout << "Conversion failure: " << err.what() << std::endl; 99 } 100 i += 2; 101 try 102 { 103 tmp.day = std::stoi(token.substr(i, 2), 0, 16); 104 } 105 catch (std::exception& err) 106 { 107 std::cout << "Conversion failure: " << err.what() << std::endl; 108 } 109 i += 2; 110 try 111 { 112 tmp.hour = std::stoi(token.substr(i, 2), 0, 16); 113 } 114 catch (std::exception& err) 115 { 116 std::cout << "Conversion failure: " << err.what() << std::endl; 117 } 118 i += 2; 119 try 120 { 121 tmp.minutes = std::stoi(token.substr(i, 2), 0, 16); 122 } 123 catch (std::exception& err) 124 { 125 std::cout << "Conversion failure: " << err.what() << std::endl; 126 } 127 i += 2; 128 try 129 { 130 tmp.seconds = std::stoi(token.substr(i, 2), 0, 16); 131 } 132 catch (std::exception& err) 133 { 134 std::cout << "Conversion failure: " << err.what() << std::endl; 135 } 136 i += 2; 137 try 138 { 139 tmp.hundredths = std::stoi(token.substr(i, 2), 0, 16); 140 } 141 catch (std::exception& err) 142 { 143 std::cout << "Conversion failure: " << err.what() << std::endl; 144 } 145 } 146 return tmp; 147 } 148 149 /** 150 * @brief helper function to get PEL id from file name 151 * @retrun uint32_t - PEL id 152 * @param[in] std::string - file name 153 */ 154 uint32_t fileNameToPELId(const std::string& fileName) 155 { 156 uint32_t num = 0; 157 try 158 { 159 num = std::stoi(fileName.substr(fileName.find("_") + 1), 0, 16); 160 } 161 catch (std::exception& err) 162 { 163 std::cout << "Conversion failure: " << err.what() << std::endl; 164 } 165 return num; 166 } 167 168 /** 169 * @brief helper function to check string suffix 170 * @retrun bool - true with suffix matches 171 * @param[in] std::string - string to check for suffix 172 * @param[in] std::string - suffix string 173 */ 174 bool ends_with(const std::string& str, const std::string& end) 175 { 176 size_t slen = str.size(), elen = end.size(); 177 if (slen < elen) 178 return false; 179 while (elen) 180 { 181 if (str[--slen] != end[--elen]) 182 return false; 183 } 184 return true; 185 } 186 187 /** 188 * @brief get data form raw PEL file. 189 * @param[in] std::string Name of file with raw PEL 190 * @return std::vector<uint8_t> char vector read from raw PEL file. 191 */ 192 std::vector<uint8_t> getFileData(const std::string& name) 193 { 194 std::ifstream file(name, std::ifstream::in); 195 if (file.good()) 196 { 197 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 198 std::istreambuf_iterator<char>()}; 199 return data; 200 } 201 else 202 { 203 return {}; 204 } 205 } 206 207 /** 208 * @brief Creates JSON string of a PEL entry if fullPEL is false or prints to 209 * stdout the full PEL in JSON if fullPEL is true 210 * @param[in] itr - std::map iterator of <uint32_t, BCDTime> 211 * @param[in] hidden - Boolean to include hidden PELs 212 * @param[in] includeInfo - Boolean to include informational PELs 213 * @param[in] fullPEL - Boolean to print full JSON representation of PEL 214 * @param[in] foundPEL - Boolean to check if any PEL is present 215 * @param[in] scrubRegex - SRC regex object 216 * @return std::string - JSON string of PEL entry (empty if fullPEL is true) 217 */ 218 template <typename T> 219 std::string genPELJSON(T itr, bool hidden, bool includeInfo, bool fullPEL, 220 bool& foundPEL, 221 const std::optional<std::regex>& scrubRegex) 222 { 223 std::size_t found; 224 std::string val; 225 char tmpValStr[50]; 226 std::string listStr; 227 char name[50]; 228 sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", itr.second.yearMSB, 229 itr.second.yearLSB, itr.second.month, itr.second.day, 230 itr.second.hour, itr.second.minutes, itr.second.seconds, 231 itr.second.hundredths, itr.first); 232 std::string fileName(name); 233 fileName = EXTENSION_PERSIST_DIR "/pels/logs/" + fileName; 234 try 235 { 236 std::vector<uint8_t> data = getFileData(fileName); 237 if (data.empty()) 238 { 239 log<level::ERR>("Empty PEL file", 240 entry("FILENAME=%s", fileName.c_str())); 241 return listStr; 242 } 243 PEL pel{data}; 244 if (!pel.valid()) 245 { 246 return listStr; 247 } 248 if (!includeInfo && pel.userHeader().severity() == 0) 249 { 250 return listStr; 251 } 252 std::bitset<16> actionFlags{pel.userHeader().actionFlags()}; 253 if (!hidden && actionFlags.test(hiddenFlagBit)) 254 { 255 return listStr; 256 } 257 if (pel.primarySRC() && scrubRegex) 258 { 259 val = pel.primarySRC().value()->asciiString(); 260 if (std::regex_search(trimEnd(val), scrubRegex.value(), 261 std::regex_constants::match_not_null)) 262 { 263 return listStr; 264 } 265 } 266 if (fullPEL) 267 { 268 if (!foundPEL) 269 { 270 std::cout << "[\n"; 271 foundPEL = true; 272 } 273 else 274 { 275 std::cout << ",\n\n"; 276 } 277 pel.toJSON(registry); 278 } 279 else 280 { 281 // id 282 listStr += "\t\"" + 283 getNumberString("0x%X", pel.privateHeader().id()) + 284 "\": {\n"; 285 // ASCII 286 if (pel.primarySRC()) 287 { 288 val = pel.primarySRC().value()->asciiString(); 289 listStr += "\t\t\"SRC\": \"" + trimEnd(val) + "\",\n"; 290 // Registry message 291 auto regVal = pel.primarySRC().value()->getErrorDetails( 292 registry, DetailLevel::message, true); 293 if (regVal) 294 { 295 val = regVal.value(); 296 listStr += "\t\t\"Message\": \"" + val + "\",\n"; 297 } 298 } 299 else 300 { 301 listStr += "\t\t\"SRC\": \"No SRC\",\n"; 302 } 303 // platformid 304 listStr += "\t\t\"PLID\": \"" + 305 getNumberString("0x%X", pel.privateHeader().plid()) + 306 "\",\n"; 307 // creatorid 308 std::string creatorID = 309 getNumberString("%c", pel.privateHeader().creatorID()); 310 val = pv::creatorIDs.count(creatorID) ? pv::creatorIDs.at(creatorID) 311 : "Unknown Creator ID"; 312 listStr += "\t\t\"CreatorID\": \"" + val + "\",\n"; 313 // subsytem 314 std::string subsystem = pv::getValue(pel.userHeader().subsystem(), 315 pel_values::subsystemValues); 316 listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n"; 317 // commit time 318 sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X", 319 pel.privateHeader().commitTimestamp().month, 320 pel.privateHeader().commitTimestamp().day, 321 pel.privateHeader().commitTimestamp().yearMSB, 322 pel.privateHeader().commitTimestamp().yearLSB, 323 pel.privateHeader().commitTimestamp().hour, 324 pel.privateHeader().commitTimestamp().minutes, 325 pel.privateHeader().commitTimestamp().seconds); 326 val = std::string(tmpValStr); 327 listStr += "\t\t\"Commit Time\": \"" + val + "\",\n"; 328 // severity 329 std::string severity = pv::getValue(pel.userHeader().severity(), 330 pel_values::severityValues); 331 listStr += "\t\t\"Sev\": \"" + severity + "\",\n "; 332 // compID 333 listStr += "\t\t\"CompID\": \"" + 334 getNumberString( 335 "0x%X", pel.privateHeader().header().componentID) + 336 "\",\n "; 337 found = listStr.rfind(","); 338 if (found != std::string::npos) 339 { 340 listStr.replace(found, 1, ""); 341 listStr += "\t},\n"; 342 } 343 foundPEL = true; 344 } 345 } 346 catch (std::exception& e) 347 { 348 log<level::ERR>("Hit exception while reading PEL File", 349 entry("FILENAME=%s", fileName.c_str()), 350 entry("ERROR=%s", e.what())); 351 } 352 return listStr; 353 } 354 355 /** 356 * @brief Print a list of PELs or a JSON array of PELs 357 * @param[in] order - Boolean to print in reverse orser 358 * @param[in] hidden - Boolean to include hidden PELs 359 * @param[in] includeInfo - Boolean to include informational PELs 360 * @param[in] fullPEL - Boolean to print full PEL into a JSON array 361 * @param[in] scrubRegex - SRC regex object 362 */ 363 void printPELs(bool order, bool hidden, bool includeInfo, bool fullPEL, 364 const std::optional<std::regex>& scrubRegex) 365 { 366 std::string listStr; 367 std::map<uint32_t, BCDTime> PELs; 368 listStr = "{\n"; 369 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs"); 370 it != fs::directory_iterator(); ++it) 371 { 372 if (!fs::is_regular_file((*it).path())) 373 { 374 continue; 375 } 376 else 377 { 378 PELs.emplace(fileNameToPELId((*it).path().filename()), 379 fileNameToTimestamp((*it).path().filename())); 380 } 381 } 382 bool foundPEL = false; 383 auto buildJSON = [&listStr, &hidden, &includeInfo, &fullPEL, &foundPEL, 384 &scrubRegex](const auto& i) { 385 listStr += 386 genPELJSON(i, hidden, includeInfo, fullPEL, foundPEL, scrubRegex); 387 }; 388 if (order) 389 { 390 std::for_each(PELs.rbegin(), PELs.rend(), buildJSON); 391 } 392 else 393 { 394 std::for_each(PELs.begin(), PELs.end(), buildJSON); 395 } 396 397 if (foundPEL) 398 { 399 if (fullPEL) 400 { 401 std::cout << "]" << std::endl; 402 } 403 else 404 { 405 std::size_t found; 406 found = listStr.rfind(","); 407 if (found != std::string::npos) 408 { 409 listStr.replace(found, 1, ""); 410 listStr += "}\n"; 411 printf("%s", listStr.c_str()); 412 } 413 } 414 } 415 else 416 { 417 std::string emptyJSON = fullPEL ? "[]" : "{}"; 418 std::cout << emptyJSON << std::endl; 419 } 420 } 421 422 /** 423 * @brief Calls the function passed in on the PEL with the ID 424 * passed in. 425 * 426 * @param[in] id - The string version of the PEL ID, either with or 427 * without the 0x prefix. 428 * @param[in] func - The std::function<void(const PEL&)> function to run. 429 */ 430 void callFunctionOnPEL(const std::string& id, const PELFunc& func) 431 { 432 std::string pelID{id}; 433 std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper); 434 435 if (pelID.find("0X") == 0) 436 { 437 pelID.erase(0, 2); 438 } 439 440 bool found = false; 441 442 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs"); 443 it != fs::directory_iterator(); ++it) 444 { 445 // The PEL ID is part of the filename, so use that to find the PEL. 446 447 if (!fs::is_regular_file((*it).path())) 448 { 449 continue; 450 } 451 452 if (ends_with((*it).path(), pelID)) 453 { 454 found = true; 455 456 auto data = getFileData((*it).path()); 457 if (!data.empty()) 458 { 459 PEL pel{data}; 460 461 try 462 { 463 func(pel); 464 } 465 catch (std::exception& e) 466 { 467 std::cerr 468 << " Internal function threw an exception: " << e.what() 469 << "\n"; 470 exit(1); 471 } 472 } 473 else 474 { 475 std::cerr << "Could not read PEL file\n"; 476 exit(1); 477 } 478 break; 479 } 480 } 481 482 if (!found) 483 { 484 std::cerr << "PEL not found\n"; 485 exit(1); 486 } 487 } 488 489 /** 490 * @brief Delete a PEL by deleting its corresponding event log. 491 * 492 * @param[in] pel - The PEL to delete 493 */ 494 void deletePEL(const PEL& pel) 495 { 496 std::string path{object_path::logEntry}; 497 path += std::to_string(pel.obmcLogID()); 498 499 try 500 { 501 auto bus = sdbusplus::bus::new_default(); 502 auto method = bus.new_method_call(service::logging, path.c_str(), 503 interface::deleteObj, "Delete"); 504 auto reply = bus.call(method); 505 } 506 catch (const SdBusError& e) 507 { 508 std::cerr << "D-Bus call to delete event log " << pel.obmcLogID() 509 << " failed: " << e.what() << "\n"; 510 exit(1); 511 } 512 } 513 514 /** 515 * @brief Delete all PELs by deleting all event logs. 516 */ 517 void deleteAllPELs() 518 { 519 try 520 { 521 // This may move to an audit log some day 522 log<level::INFO>("peltool deleting all event logs"); 523 524 auto bus = sdbusplus::bus::new_default(); 525 auto method = 526 bus.new_method_call(service::logging, object_path::logging, 527 interface::deleteAll, "DeleteAll"); 528 auto reply = bus.call(method); 529 } 530 catch (const SdBusError& e) 531 { 532 std::cerr << "D-Bus call to delete all event logs failed: " << e.what() 533 << "\n"; 534 exit(1); 535 } 536 } 537 538 /** 539 * @brief Display a single PEL 540 * 541 * @param[in] pel - the PEL to display 542 */ 543 void displayPEL(const PEL& pel) 544 { 545 if (pel.valid()) 546 { 547 pel.toJSON(registry); 548 } 549 else 550 { 551 std::cerr << "PEL was malformed\n"; 552 exit(1); 553 } 554 } 555 556 /** 557 * @brief Print number of PELs 558 * @param[in] hidden - Bool to include hidden logs 559 * @param[in] includeInfo - Bool to include informational logs 560 * @param[in] scrubRegex - SRC regex object 561 */ 562 void printPELCount(bool hidden, bool includeInfo, 563 const std::optional<std::regex>& scrubRegex) 564 { 565 std::size_t count = 0; 566 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs"); 567 it != fs::directory_iterator(); ++it) 568 { 569 if (!fs::is_regular_file((*it).path())) 570 { 571 continue; 572 } 573 std::vector<uint8_t> data = getFileData((*it).path()); 574 if (data.empty()) 575 { 576 continue; 577 } 578 PEL pel{data}; 579 if (!pel.valid()) 580 { 581 continue; 582 } 583 if (!includeInfo && pel.userHeader().severity() == 0) 584 { 585 continue; 586 } 587 std::bitset<16> actionFlags{pel.userHeader().actionFlags()}; 588 if (!hidden && actionFlags.test(hiddenFlagBit)) 589 { 590 continue; 591 } 592 if (pel.primarySRC() && scrubRegex) 593 { 594 std::string val = pel.primarySRC().value()->asciiString(); 595 if (std::regex_search(trimEnd(val), scrubRegex.value(), 596 std::regex_constants::match_not_null)) 597 { 598 continue; 599 } 600 } 601 count++; 602 } 603 std::cout << "{\n" 604 << " \"Number of PELs found\": " 605 << getNumberString("%d", count) << "\n}\n"; 606 } 607 608 /** 609 * @brief Generate regex pattern object from file contents 610 * @param[in] scrubFile - File containing regex pattern 611 * @return std::regex - SRC regex object 612 */ 613 std::regex genRegex(std::string& scrubFile) 614 { 615 std::string pattern; 616 std::ifstream contents(scrubFile); 617 if (contents.fail()) 618 { 619 std::cerr << "Can't open \"" << scrubFile << "\"\n"; 620 exit(1); 621 } 622 std::string line; 623 while (std::getline(contents, line)) 624 { 625 if (!line.empty()) 626 { 627 pattern.append(line + "|"); 628 } 629 } 630 try 631 { 632 std::regex scrubRegex(pattern, std::regex::icase); 633 return scrubRegex; 634 } 635 catch (std::regex_error& e) 636 { 637 if (e.code() == std::regex_constants::error_collate) 638 std::cerr << "Invalid collating element request\n"; 639 else if (e.code() == std::regex_constants::error_ctype) 640 std::cerr << "Invalid character class\n"; 641 else if (e.code() == std::regex_constants::error_escape) 642 std::cerr << "Invalid escape character or trailing escape\n"; 643 else if (e.code() == std::regex_constants::error_backref) 644 std::cerr << "Invalid back reference\n"; 645 else if (e.code() == std::regex_constants::error_brack) 646 std::cerr << "Mismatched bracket ([ or ])\n"; 647 else if (e.code() == std::regex_constants::error_paren) 648 { 649 // to catch return code error_badrepeat when error_paren is retured 650 // instead 651 size_t pos = pattern.find_first_of("*+?{"); 652 while (pos != std::string::npos) 653 { 654 if (pos == 0 || pattern.substr(pos - 1, 1) == "|") 655 { 656 std::cerr 657 << "A repetition character (*, ?, +, or {) was not " 658 "preceded by a valid regular expression\n"; 659 exit(1); 660 } 661 pos = pattern.find_first_of("*+?{", pos + 1); 662 } 663 std::cerr << "Mismatched parentheses (( or ))\n"; 664 } 665 else if (e.code() == std::regex_constants::error_brace) 666 std::cerr << "Mismatched brace ({ or })\n"; 667 else if (e.code() == std::regex_constants::error_badbrace) 668 std::cerr << "Invalid range inside a { }\n"; 669 else if (e.code() == std::regex_constants::error_range) 670 std::cerr << "Invalid character range (e.g., [z-a])\n"; 671 else if (e.code() == std::regex_constants::error_space) 672 std::cerr << "Insufficient memory to handle regular expression\n"; 673 else if (e.code() == std::regex_constants::error_badrepeat) 674 std::cerr << "A repetition character (*, ?, +, or {) was not " 675 "preceded by a valid regular expression\n"; 676 else if (e.code() == std::regex_constants::error_complexity) 677 std::cerr << "The requested match is too complex\n"; 678 else if (e.code() == std::regex_constants::error_stack) 679 std::cerr << "Insufficient memory to evaluate a match\n"; 680 exit(1); 681 } 682 } 683 684 static void exitWithError(const std::string& help, const char* err) 685 { 686 std::cerr << "ERROR: " << err << std::endl << help << std::endl; 687 exit(-1); 688 } 689 690 int main(int argc, char** argv) 691 { 692 CLI::App app{"OpenBMC PEL Tool"}; 693 std::string fileName; 694 std::string idPEL; 695 std::string idToDelete; 696 std::string scrubFile; 697 std::optional<std::regex> scrubRegex; 698 bool listPEL = false; 699 bool listPELDescOrd = false; 700 bool hidden = false; 701 bool includeInfo = false; 702 bool deleteAll = false; 703 bool showPELCount = false; 704 bool fullPEL = false; 705 706 app.set_help_flag("--help", "Print this help message and exit"); 707 app.add_option("--file", fileName, "Display a PEL using its Raw PEL file"); 708 app.add_option("-i, --id", idPEL, "Display a PEL based on its ID"); 709 app.add_flag("-a", fullPEL, "Display all PELs"); 710 app.add_flag("-l", listPEL, "List PELs"); 711 app.add_flag("-n", showPELCount, "Show number of PELs"); 712 app.add_flag("-r", listPELDescOrd, "Reverse order of output"); 713 app.add_flag("-h", hidden, "Include hidden PELs"); 714 app.add_flag("-f,--info", includeInfo, "Include informational PELs"); 715 app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID"); 716 app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs"); 717 app.add_option("-s, --scrub", scrubFile, 718 "File containing SRC regular expressions to ignore"); 719 720 CLI11_PARSE(app, argc, argv); 721 722 if (!fileName.empty()) 723 { 724 std::vector<uint8_t> data = getFileData(fileName); 725 if (!data.empty()) 726 { 727 PEL pel{data}; 728 pel.toJSON(registry); 729 } 730 else 731 { 732 exitWithError(app.help("", CLI::AppFormatMode::All), 733 "Raw PEL file can't be read."); 734 } 735 } 736 else if (!idPEL.empty()) 737 { 738 callFunctionOnPEL(idPEL, displayPEL); 739 } 740 else if (fullPEL || listPEL) 741 { 742 if (!scrubFile.empty()) 743 { 744 scrubRegex = genRegex(scrubFile); 745 } 746 printPELs(listPELDescOrd, hidden, includeInfo, fullPEL, scrubRegex); 747 } 748 else if (showPELCount) 749 { 750 if (!scrubFile.empty()) 751 { 752 scrubRegex = genRegex(scrubFile); 753 } 754 printPELCount(hidden, includeInfo, scrubRegex); 755 } 756 else if (!idToDelete.empty()) 757 { 758 callFunctionOnPEL(idToDelete, deletePEL); 759 } 760 else if (deleteAll) 761 { 762 deleteAllPELs(); 763 } 764 else 765 { 766 std::cout << app.help("", CLI::AppFormatMode::All) << std::endl; 767 } 768 return 0; 769 } 770