| /** |
| * 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 "../paths.hpp" |
| #include "../pel.hpp" |
| #include "../pel_types.hpp" |
| #include "../pel_values.hpp" |
| |
| #include <CLI/CLI.hpp> |
| #include <bitset> |
| #include <iostream> |
| #include <phosphor-logging/log.hpp> |
| #include <regex> |
| #include <string> |
| #include <xyz/openbmc_project/Common/File/error.hpp> |
| |
| namespace fs = std::filesystem; |
| using namespace phosphor::logging; |
| using namespace openpower::pels; |
| namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error; |
| namespace message = openpower::pels::message; |
| namespace pv = openpower::pels::pel_values; |
| |
| /** |
| * @brief helper function to get PEL commit timestamp from file name |
| * @retrun BCDTime - PEL commit timestamp |
| * @param[in] std::string - file name |
| */ |
| BCDTime fileNameToTimestamp(const std::string& fileName) |
| { |
| std::string token = fileName.substr(0, fileName.find("_")); |
| int i = 0; |
| BCDTime tmp; |
| if (token.length() >= 14) |
| { |
| try |
| { |
| tmp.yearMSB = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.yearLSB = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.month = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.day = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.hour = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.minutes = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.seconds = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| i += 2; |
| try |
| { |
| tmp.hundredths = std::stoi(token.substr(i, 2), 0, 16); |
| } |
| catch (std::exception& err) |
| { |
| std::cout << "Conversion failure: " << err.what() << std::endl; |
| } |
| } |
| return tmp; |
| } |
| |
| /** |
| * @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::stoi(fileName.substr(fileName.find("_") + 1), 0, 16); |
| } |
| catch (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<uint8_t> char vector read from raw PEL file. |
| */ |
| std::vector<uint8_t> getFileData(const std::string& name) |
| { |
| std::ifstream file(name, std::ifstream::in); |
| if (file.good()) |
| { |
| std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), |
| std::istreambuf_iterator<char>()}; |
| return data; |
| } |
| else |
| { |
| printf("Can't open raw PEL file"); |
| return {}; |
| } |
| } |
| |
| template <typename T> |
| std::string genPELJSON(T itr, bool hidden, message::Registry& registry) |
| { |
| std::size_t found; |
| std::string val; |
| char tmpValStr[50]; |
| std::string listStr; |
| char name[50]; |
| sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", itr.second.yearMSB, |
| itr.second.yearLSB, itr.second.month, itr.second.day, |
| itr.second.hour, itr.second.minutes, itr.second.seconds, |
| itr.second.hundredths, itr.first); |
| std::string fileName(name); |
| fileName = EXTENSION_PERSIST_DIR "/pels/logs/" + fileName; |
| try |
| { |
| std::vector<uint8_t> data = getFileData(fileName); |
| if (!data.empty()) |
| { |
| PEL pel{data}; |
| std::bitset<16> actionFlags{pel.userHeader().actionFlags()}; |
| if (pel.valid() && (hidden || !actionFlags.test(hiddenFlagBit))) |
| { |
| // id |
| sprintf(tmpValStr, "0x%X", pel.privateHeader().id()); |
| val = std::string(tmpValStr); |
| listStr += "\t\"" + val + "\": {\n"; |
| // ASCII |
| if (pel.primarySRC()) |
| { |
| val = pel.primarySRC().value()->asciiString(); |
| listStr += "\t\t\"SRC\": \"" + |
| val.substr(0, val.find(0x20)) + "\",\n"; |
| // Registry message |
| auto regVal = pel.primarySRC().value()->getErrorDetails( |
| registry, DetailLevel::message, true); |
| if (regVal) |
| { |
| val = regVal.value(); |
| listStr += "\t\t\"Message\": \"" + val + "\",\n"; |
| } |
| } |
| else |
| { |
| listStr += "\t\t\"SRC\": \"No SRC\",\n"; |
| } |
| // platformid |
| sprintf(tmpValStr, "0x%X", pel.privateHeader().plid()); |
| val = std::string(tmpValStr); |
| listStr += "\t\t\"PLID\": \"" + val + "\",\n"; |
| // creatorid |
| sprintf(tmpValStr, "%c", pel.privateHeader().creatorID()); |
| std::string creatorID(tmpValStr); |
| val = pv::creatorIDs.count(creatorID) |
| ? pv::creatorIDs.at(creatorID) |
| : "Unknown Creator ID"; |
| listStr += "\t\t\"CreatorID\": \"" + val + "\",\n"; |
| // subsytem |
| std::string subsystem = pv::getValue( |
| pel.userHeader().subsystem(), pel_values::subsystemValues); |
| listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n"; |
| // 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); |
| val = std::string(tmpValStr); |
| listStr += "\t\t\"Commit Time\": \"" + val + "\",\n"; |
| // severity |
| std::string severity = pv::getValue(pel.userHeader().severity(), |
| pel_values::severityValues); |
| listStr += "\t\t\"Sev\": \"" + severity + "\",\n "; |
| // compID |
| sprintf(tmpValStr, "0x%X", |
| pel.privateHeader().header().componentID); |
| val = std::string(tmpValStr); |
| listStr += "\t\t\"CompID\": \"" + val + "\",\n "; |
| |
| found = listStr.rfind(","); |
| if (found != std::string::npos) |
| { |
| listStr.replace(found, 1, ""); |
| listStr += "\t}, \n"; |
| } |
| } |
| } |
| else |
| { |
| log<level::ERR>("Empty PEL file", |
| entry("FILENAME=%s", fileName.c_str()), |
| entry("ERROR=%s", "Empty PEL file")); |
| } |
| } |
| catch (std::exception& e) |
| { |
| log<level::ERR>("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 |
| */ |
| void printList(bool order, bool hidden) |
| { |
| std::string listStr; |
| std::map<uint32_t, BCDTime> PELs; |
| std::size_t found; |
| listStr = "{\n"; |
| for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs"); |
| it != fs::directory_iterator(); ++it) |
| { |
| if (!fs::is_regular_file((*it).path())) |
| { |
| continue; |
| } |
| else |
| { |
| PELs.emplace(fileNameToPELId((*it).path().filename()), |
| fileNameToTimestamp((*it).path().filename())); |
| } |
| } |
| message::Registry registry(getMessageRegistryPath() / |
| message::registryFileName); |
| auto buildJSON = [&listStr, &hidden, ®istry](const auto& i) { |
| listStr += genPELJSON(i, hidden, registry); |
| }; |
| if (order) |
| { |
| std::for_each(PELs.rbegin(), PELs.rend(), buildJSON); |
| } |
| else |
| { |
| std::for_each(PELs.begin(), PELs.end(), buildJSON); |
| } |
| |
| found = listStr.rfind(","); |
| if (found != std::string::npos) |
| { |
| listStr.replace(found, 1, ""); |
| listStr += "\n}\n"; |
| printf("%s", listStr.c_str()); |
| } |
| } |
| |
| 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; |
| bool listPEL = false; |
| bool listPELDescOrd = false; |
| bool listPELShowHidden = false; |
| app.add_option("-f,--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_flag("-l", listPEL, "List PELs"); |
| app.add_flag("-r", listPELDescOrd, "Reverse order of output"); |
| app.add_flag("-s", listPELShowHidden, "Show hidden PELs"); |
| CLI11_PARSE(app, argc, argv); |
| |
| if (!fileName.empty()) |
| { |
| std::vector<uint8_t> data = getFileData(fileName); |
| if (!data.empty()) |
| { |
| PEL pel{data}; |
| pel.toJSON(); |
| } |
| else |
| { |
| exitWithError(app.help("", CLI::AppFormatMode::All), |
| "Raw PEL file can't be read."); |
| } |
| } |
| |
| else if (!idPEL.empty()) |
| { |
| for (auto it = |
| fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs"); |
| it != fs::directory_iterator(); ++it) |
| { |
| if (!fs::is_regular_file((*it).path())) |
| { |
| continue; |
| } |
| try |
| { |
| for (auto& c : idPEL) |
| c = toupper(c); |
| size_t found = idPEL.find("0X"); |
| if (found == 0) |
| { |
| idPEL.erase(0, 2); |
| } |
| if (ends_with((*it).path(), idPEL)) |
| { |
| std::vector<uint8_t> data = getFileData((*it).path()); |
| if (!data.empty()) |
| { |
| PEL pel{data}; |
| if (pel.valid()) |
| { |
| pel.toJSON(); |
| } |
| else |
| { |
| log<level::ERR>( |
| "PEL File contains invalid PEL", |
| entry("FILENAME=%s", (*it).path().c_str()), |
| entry("ERROR=%s", "file contains invalid PEL")); |
| } |
| } |
| break; |
| } |
| } |
| catch (std::exception& e) |
| { |
| log<level::ERR>("Hit exception while reading PEL File", |
| entry("FILENAME=%s", (*it).path().c_str()), |
| entry("ERROR=%s", e.what())); |
| } |
| } |
| } |
| else if (listPEL) |
| { |
| |
| printList(listPELDescOrd, listPELShowHidden); |
| } |
| else |
| { |
| exitWithError(app.help("", CLI::AppFormatMode::All), |
| "Raw PEL file path not specified."); |
| } |
| return 0; |
| } |