/** * Copyright © 2019 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include "../bcd_time.hpp" #include "../json_utils.hpp" #include "../paths.hpp" #include "../pel.hpp" #include "../pel_types.hpp" #include "../pel_values.hpp" #include #include #include #include #include #include #include #include #include "config_main.h" namespace fs = std::filesystem; using namespace phosphor::logging; using namespace openpower::pels; namespace message = openpower::pels::message; namespace pv = openpower::pels::pel_values; const uint8_t critSysTermSeverity = 0x51; using PELFunc = std::function; message::Registry registry(getPELReadOnlyDataPath() / message::registryFileName, false); namespace service { constexpr auto logging = "xyz.openbmc_project.Logging"; } // namespace service namespace interface { constexpr auto deleteObj = "xyz.openbmc_project.Object.Delete"; constexpr auto deleteAll = "xyz.openbmc_project.Collection.DeleteAll"; } // namespace interface namespace object_path { constexpr auto logEntry = "/xyz/openbmc_project/logging/entry/"; constexpr auto logging = "/xyz/openbmc_project/logging"; } // namespace object_path std::string pelLogDir() { return std::string(EXTENSION_PERSIST_DIR) + "/pels/logs"; } /** * @brief helper function to get PEL commit timestamp from file name * @retrun uint64_t - PEL commit timestamp * @param[in] std::string - file name */ uint64_t fileNameToTimestamp(const std::string& fileName) { std::string token = fileName.substr(0, fileName.find("_")); int i = 0; uint64_t bcdTime = 0; if (token.length() >= 14) { try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (static_cast(tmp) << 56); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (static_cast(tmp) << 48); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (static_cast(tmp) << 40); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (static_cast(tmp) << 32); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (tmp << 24); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (tmp << 16); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= (tmp << 8); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } i += 2; try { auto tmp = std::stoul(token.substr(i, 2), 0, 16); bcdTime |= tmp; } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } } return bcdTime; } /** * @brief helper function to get PEL id from file name * @retrun uint32_t - PEL id * @param[in] std::string - file name */ uint32_t fileNameToPELId(const std::string& fileName) { uint32_t num = 0; try { num = std::stoul(fileName.substr(fileName.find("_") + 1), 0, 16); } catch (const std::exception& err) { std::cout << "Conversion failure: " << err.what() << std::endl; } return num; } /** * @brief helper function to check string suffix * @retrun bool - true with suffix matches * @param[in] std::string - string to check for suffix * @param[in] std::string - suffix string */ bool ends_with(const std::string& str, const std::string& end) { size_t slen = str.size(), elen = end.size(); if (slen < elen) return false; while (elen) { if (str[--slen] != end[--elen]) return false; } return true; } /** * @brief get data form raw PEL file. * @param[in] std::string Name of file with raw PEL * @return std::vector char vector read from raw PEL file. */ std::vector getFileData(const std::string& name) { std::ifstream file(name, std::ifstream::in); if (file.good()) { std::vector data{std::istreambuf_iterator(file), std::istreambuf_iterator()}; return data; } else { return {}; } } /** * @brief Initialize Python interpreter and gather all UD parser modules under * the paths found in Python sys.path and the current user directory. * This is to prevent calling a non-existant module which causes Python * to print an import error message and breaking JSON output. * * @return std::vector Vector of plugins found in filesystem */ std::vector getPlugins() { Py_Initialize(); std::vector plugins; std::vector siteDirs; std::array parserDirs = {"udparsers", "srcparsers"}; PyObject* pName = PyUnicode_FromString("sys"); PyObject* pModule = PyImport_Import(pName); Py_XDECREF(pName); PyObject* pDict = PyModule_GetDict(pModule); Py_XDECREF(pModule); PyObject* pResult = PyDict_GetItemString(pDict, "path"); PyObject* pValue = PyUnicode_FromString("."); PyList_Append(pResult, pValue); Py_XDECREF(pValue); auto list_size = PyList_Size(pResult); for (auto i = 0; i < list_size; i++) { PyObject* item = PyList_GetItem(pResult, i); PyObject* pBytes = PyUnicode_AsEncodedString(item, "utf-8", "~E~"); const char* output = PyBytes_AS_STRING(pBytes); Py_XDECREF(pBytes); std::string tmpStr(output); siteDirs.push_back(tmpStr); } for (const auto& dir : siteDirs) { for (const auto& parserDir : parserDirs) { if (fs::exists(dir + "/" + parserDir)) { for (const auto& entry : fs::directory_iterator(dir + "/" + parserDir)) { if (entry.is_directory() and fs::exists(entry.path().string() + "/" + entry.path().stem().string() + ".py")) { plugins.push_back(entry.path().stem()); } } } } } return plugins; } /** * @brief Creates JSON string of a PEL entry if fullPEL is false or prints to * stdout the full PEL in JSON if fullPEL is true * @param[in] itr - std::map iterator of * @param[in] hidden - Boolean to include hidden PELs * @param[in] includeInfo - Boolean to include informational PELs * @param[in] critSysTerm - Boolean to include critical error and system * termination PELs * @param[in] fullPEL - Boolean to print full JSON representation of PEL * @param[in] foundPEL - Boolean to check if any PEL is present * @param[in] scrubRegex - SRC regex object * @param[in] plugins - Vector of strings of plugins found in filesystem * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON * @return std::string - JSON string of PEL entry (empty if fullPEL is true) */ template std::string genPELJSON(T itr, bool hidden, bool includeInfo, bool critSysTerm, bool fullPEL, bool& foundPEL, const std::optional& scrubRegex, const std::vector& plugins, bool hexDump, bool archive) { std::size_t found; std::string val; char tmpValStr[50]; std::string listStr; char name[51]; sprintf(name, "/%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", static_cast((itr.second >> 56) & 0xFF), static_cast((itr.second >> 48) & 0xFF), static_cast((itr.second >> 40) & 0xFF), static_cast((itr.second >> 32) & 0xFF), static_cast((itr.second >> 24) & 0xFF), static_cast((itr.second >> 16) & 0xFF), static_cast((itr.second >> 8) & 0xFF), static_cast(itr.second & 0xFF), itr.first); auto fileName = (archive ? pelLogDir() + "/archive" : pelLogDir()) + name; try { std::vector data = getFileData(fileName); if (data.empty()) { log("Empty PEL file", entry("FILENAME=%s", fileName.c_str())); return listStr; } PEL pel{data}; if (!pel.valid()) { return listStr; } if (!includeInfo && pel.userHeader().severity() == 0) { return listStr; } if (critSysTerm && pel.userHeader().severity() != critSysTermSeverity) { return listStr; } std::bitset<16> actionFlags{pel.userHeader().actionFlags()}; if (!hidden && actionFlags.test(hiddenFlagBit)) { return listStr; } if (pel.primarySRC() && scrubRegex) { val = pel.primarySRC().value()->asciiString(); if (std::regex_search(trimEnd(val), scrubRegex.value(), std::regex_constants::match_not_null)) { return listStr; } } if (hexDump) { std::cout << dumpHex(std::data(pel.data()), pel.size(), 0, false) << std::endl; } else if (fullPEL) { if (!foundPEL) { std::cout << "[\n"; foundPEL = true; } else { std::cout << ",\n\n"; } pel.toJSON(registry, plugins); } else { // id listStr += " \"" + getNumberString("0x%X", pel.privateHeader().id()) + "\": {\n"; // ASCII if (pel.primarySRC()) { val = pel.primarySRC().value()->asciiString(); jsonInsert(listStr, "SRC", trimEnd(val), 2); // Registry message auto regVal = pel.primarySRC().value()->getErrorDetails( registry, DetailLevel::message, true); if (regVal) { val = regVal.value(); jsonInsert(listStr, "Message", val, 2); } } else { jsonInsert(listStr, "SRC", "No SRC", 2); } // platformid jsonInsert(listStr, "PLID", getNumberString("0x%X", pel.privateHeader().plid()), 2); // creatorid std::string creatorID = getNumberString("%c", pel.privateHeader().creatorID()); val = pv::creatorIDs.count(creatorID) ? pv::creatorIDs.at(creatorID) : "Unknown Creator ID"; jsonInsert(listStr, "CreatorID", val, 2); // subsystem std::string subsystem = pv::getValue(pel.userHeader().subsystem(), pel_values::subsystemValues); jsonInsert(listStr, "Subsystem", subsystem, 2); // commit time sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X", pel.privateHeader().commitTimestamp().month, pel.privateHeader().commitTimestamp().day, pel.privateHeader().commitTimestamp().yearMSB, pel.privateHeader().commitTimestamp().yearLSB, pel.privateHeader().commitTimestamp().hour, pel.privateHeader().commitTimestamp().minutes, pel.privateHeader().commitTimestamp().seconds); jsonInsert(listStr, "Commit Time", tmpValStr, 2); // severity std::string severity = pv::getValue(pel.userHeader().severity(), pel_values::severityValues); jsonInsert(listStr, "Sev", severity, 2); // compID jsonInsert(listStr, "CompID", getNumberString( "0x%X", pel.privateHeader().header().componentID), 2); found = listStr.rfind(","); if (found != std::string::npos) { listStr.replace(found, 1, ""); listStr += " },\n"; } foundPEL = true; } } catch (const std::exception& e) { log("Hit exception while reading PEL File", entry("FILENAME=%s", fileName.c_str()), entry("ERROR=%s", e.what())); } return listStr; } /** * @brief Print a list of PELs or a JSON array of PELs * @param[in] order - Boolean to print in reverse orser * @param[in] hidden - Boolean to include hidden PELs * @param[in] includeInfo - Boolean to include informational PELs * @param[in] critSysTerm - Boolean to include critical error and system * termination PELs * @param[in] fullPEL - Boolean to print full PEL into a JSON array * @param[in] scrubRegex - SRC regex object * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON */ void printPELs(bool order, bool hidden, bool includeInfo, bool critSysTerm, bool fullPEL, const std::optional& scrubRegex, bool hexDump, bool archive = false) { std::string listStr; std::vector> PELs; std::vector plugins; listStr = "{\n"; for (auto it = (archive ? fs::directory_iterator(pelLogDir() + "/archive") : fs::directory_iterator(pelLogDir())); it != fs::directory_iterator(); ++it) { if (!fs::is_regular_file((*it).path())) { continue; } else { PELs.emplace_back(fileNameToPELId((*it).path().filename()), fileNameToTimestamp((*it).path().filename())); } } // Sort the pairs based on second time parameter std::sort(PELs.begin(), PELs.end(), [](auto& left, auto& right) { return left.second < right.second; }); bool foundPEL = false; if (fullPEL && !hexDump) { plugins = getPlugins(); } auto buildJSON = [&listStr, &hidden, &includeInfo, &critSysTerm, &fullPEL, &foundPEL, &scrubRegex, &plugins, &hexDump, &archive](const auto& i) { listStr += genPELJSON(i, hidden, includeInfo, critSysTerm, fullPEL, foundPEL, scrubRegex, plugins, hexDump, archive); }; if (order) { std::for_each(PELs.rbegin(), PELs.rend(), buildJSON); } else { std::for_each(PELs.begin(), PELs.end(), buildJSON); } if (hexDump) { return; } if (foundPEL) { if (fullPEL) { std::cout << "]" << std::endl; } else { std::size_t found; found = listStr.rfind(","); if (found != std::string::npos) { listStr.replace(found, 1, ""); listStr += "}\n"; printf("%s", listStr.c_str()); } } } else { std::string emptyJSON = fullPEL ? "[]" : "{}"; std::cout << emptyJSON << std::endl; } } /** * @brief Calls the function passed in on the PEL with the ID * passed in. * * @param[in] id - The string version of the PEL or BMC Log ID, either with or * without the 0x prefix. * @param[in] func - The std::function function * to run. * @param[in] useBMC - if true, search by BMC Log ID, else search by PEL ID * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON */ void callFunctionOnPEL(const std::string& id, const PELFunc& func, bool useBMC = false, bool hexDump = false, bool archive = false) { std::string pelID{id}; if (!useBMC) { std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper); if (pelID.find("0X") == 0) { pelID.erase(0, 2); } } bool found = false; for (auto it = (archive ? fs::directory_iterator(pelLogDir() + "/archive") : fs::directory_iterator(pelLogDir())); it != fs::directory_iterator(); ++it) { // The PEL ID is part of the filename, so use that to find the PEL if // "useBMC" is set to false, otherwise we have to search within the PEL if (!fs::is_regular_file((*it).path())) { continue; } if ((ends_with((*it).path(), pelID) && !useBMC) || useBMC) { auto data = getFileData((*it).path()); if (!data.empty()) { PEL pel{data}; if (!useBMC || (useBMC && pel.obmcLogID() == std::stoul(id, nullptr, 0))) { found = true; try { func(pel, hexDump); break; } catch (const std::exception& e) { std::cerr << " Internal function threw an exception: " << e.what() << "\n"; exit(1); } } } else { std::cerr << "Could not read PEL file\n"; exit(1); } } } if (!found) { std::cerr << "PEL not found\n"; exit(1); } } /** * @brief Delete a PEL file. * * @param[in] id - The PEL ID to delete. */ void deletePEL(const std::string& id) { std::string pelID{id}; std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper); if (pelID.find("0X") == 0) { pelID.erase(0, 2); } for (auto it = fs::directory_iterator(pelLogDir()); it != fs::directory_iterator(); ++it) { if (ends_with((*it).path(), pelID)) { fs::remove((*it).path()); } } } /** * @brief Delete all PEL files. */ void deleteAllPELs() { log("peltool deleting all event logs"); for (const auto& entry : fs::directory_iterator(pelLogDir())) { if (!fs::is_regular_file(entry.path())) { continue; } fs::remove(entry.path()); } } /** * @brief Display a single PEL * * @param[in] pel - the PEL to display * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON */ void displayPEL(const PEL& pel, bool hexDump) { if (pel.valid()) { if (hexDump) { std::string dstr = dumpHex(std::data(pel.data()), pel.size(), 0, false); std::cout << dstr << std::endl; } else { auto plugins = getPlugins(); pel.toJSON(registry, plugins); } } else { std::cerr << "PEL was malformed\n"; exit(1); } } /** * @brief Print number of PELs * @param[in] hidden - Bool to include hidden logs * @param[in] includeInfo - Bool to include informational logs * @param[in] critSysTerm - Bool to include CritSysTerm * @param[in] scrubRegex - SRC regex object */ void printPELCount(bool hidden, bool includeInfo, bool critSysTerm, const std::optional& scrubRegex) { std::size_t count = 0; for (auto it = fs::directory_iterator(pelLogDir()); it != fs::directory_iterator(); ++it) { if (!fs::is_regular_file((*it).path())) { continue; } std::vector data = getFileData((*it).path()); if (data.empty()) { continue; } PEL pel{data}; if (!pel.valid()) { continue; } if (!includeInfo && pel.userHeader().severity() == 0) { continue; } if (critSysTerm && pel.userHeader().severity() != critSysTermSeverity) { continue; } std::bitset<16> actionFlags{pel.userHeader().actionFlags()}; if (!hidden && actionFlags.test(hiddenFlagBit)) { continue; } if (pel.primarySRC() && scrubRegex) { std::string val = pel.primarySRC().value()->asciiString(); if (std::regex_search(trimEnd(val), scrubRegex.value(), std::regex_constants::match_not_null)) { continue; } } count++; } std::cout << "{\n" << " \"Number of PELs found\": " << getNumberString("%d", count) << "\n}\n"; } /** * @brief Generate regex pattern object from file contents * @param[in] scrubFile - File containing regex pattern * @return std::regex - SRC regex object */ std::regex genRegex(std::string& scrubFile) { std::string pattern; std::ifstream contents(scrubFile); if (contents.fail()) { std::cerr << "Can't open \"" << scrubFile << "\"\n"; exit(1); } std::string line; while (std::getline(contents, line)) { if (!line.empty()) { pattern.append(line + "|"); } } try { std::regex scrubRegex(pattern, std::regex::icase); return scrubRegex; } catch (const std::regex_error& e) { if (e.code() == std::regex_constants::error_collate) std::cerr << "Invalid collating element request\n"; else if (e.code() == std::regex_constants::error_ctype) std::cerr << "Invalid character class\n"; else if (e.code() == std::regex_constants::error_escape) std::cerr << "Invalid escape character or trailing escape\n"; else if (e.code() == std::regex_constants::error_backref) std::cerr << "Invalid back reference\n"; else if (e.code() == std::regex_constants::error_brack) std::cerr << "Mismatched bracket ([ or ])\n"; else if (e.code() == std::regex_constants::error_paren) { // to catch return code error_badrepeat when error_paren is retured // instead size_t pos = pattern.find_first_of("*+?{"); while (pos != std::string::npos) { if (pos == 0 || pattern.substr(pos - 1, 1) == "|") { std::cerr << "A repetition character (*, ?, +, or {) was not " "preceded by a valid regular expression\n"; exit(1); } pos = pattern.find_first_of("*+?{", pos + 1); } std::cerr << "Mismatched parentheses (( or ))\n"; } else if (e.code() == std::regex_constants::error_brace) std::cerr << "Mismatched brace ({ or })\n"; else if (e.code() == std::regex_constants::error_badbrace) std::cerr << "Invalid range inside a { }\n"; else if (e.code() == std::regex_constants::error_range) std::cerr << "Invalid character range (e.g., [z-a])\n"; else if (e.code() == std::regex_constants::error_space) std::cerr << "Insufficient memory to handle regular expression\n"; else if (e.code() == std::regex_constants::error_badrepeat) std::cerr << "A repetition character (*, ?, +, or {) was not " "preceded by a valid regular expression\n"; else if (e.code() == std::regex_constants::error_complexity) std::cerr << "The requested match is too complex\n"; else if (e.code() == std::regex_constants::error_stack) std::cerr << "Insufficient memory to evaluate a match\n"; exit(1); } } static void exitWithError(const std::string& help, const char* err) { std::cerr << "ERROR: " << err << std::endl << help << std::endl; exit(-1); } int main(int argc, char** argv) { CLI::App app{"OpenBMC PEL Tool"}; std::string fileName; std::string idPEL; std::string bmcId; std::string idToDelete; std::string scrubFile; std::optional scrubRegex; bool listPEL = false; bool listPELDescOrd = false; bool hidden = false; bool includeInfo = false; bool critSysTerm = false; bool deleteAll = false; bool showPELCount = false; bool fullPEL = false; bool hexDump = false; bool archive = false; app.set_help_flag("--help", "Print this help message and exit"); app.add_option("--file", fileName, "Display a PEL using its Raw PEL file"); app.add_option("-i, --id", idPEL, "Display a PEL based on its ID"); app.add_option("--bmc-id", bmcId, "Display a PEL based on its BMC Event ID"); app.add_flag("-a", fullPEL, "Display all PELs"); app.add_flag("-l", listPEL, "List PELs"); app.add_flag("-n", showPELCount, "Show number of PELs"); app.add_flag("-r", listPELDescOrd, "Reverse order of output"); app.add_flag("-h", hidden, "Include hidden PELs"); app.add_flag("-f,--info", includeInfo, "Include informational PELs"); app.add_flag("-t, --termination", critSysTerm, "List only critical system terminating PELs"); app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID"); app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs"); app.add_option("-s, --scrub", scrubFile, "File containing SRC regular expressions to ignore"); app.add_flag("-x", hexDump, "Display PEL(s) in hexdump instead of JSON"); app.add_flag("--archive", archive, "List or display archived PELs"); CLI11_PARSE(app, argc, argv); if (!fileName.empty()) { std::vector data = getFileData(fileName); if (!data.empty()) { PEL pel{data}; if (hexDump) { std::string dstr = dumpHex(std::data(pel.data()), pel.size(), 0, false); std::cout << dstr << std::endl; } else { auto plugins = getPlugins(); pel.toJSON(registry, plugins); } } else { exitWithError(app.help("", CLI::AppFormatMode::All), "Raw PEL file can't be read."); } } else if (!idPEL.empty()) { callFunctionOnPEL(idPEL, displayPEL, false, hexDump, archive); } else if (!bmcId.empty()) { callFunctionOnPEL(bmcId, displayPEL, true, hexDump, archive); } else if (fullPEL || listPEL) { if (!scrubFile.empty()) { scrubRegex = genRegex(scrubFile); } printPELs(listPELDescOrd, hidden, includeInfo, critSysTerm, fullPEL, scrubRegex, hexDump, archive); } else if (showPELCount) { if (!scrubFile.empty()) { scrubRegex = genRegex(scrubFile); } printPELCount(hidden, includeInfo, critSysTerm, scrubRegex); } else if (!idToDelete.empty()) { deletePEL(idToDelete); } else if (deleteAll) { deleteAllPELs(); } else { std::cout << app.help("", CLI::AppFormatMode::All) << std::endl; } Py_Finalize(); return 0; }