xref: /openbmc/phosphor-logging/extensions/openpower-pels/tools/peltool.cpp (revision feeb108287a095108283f3978dfa608502d55494)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3 
4 #include "config.h"
5 
6 #include "config_main.h"
7 
8 #include "../bcd_time.hpp"
9 #include "../json_utils.hpp"
10 #include "../paths.hpp"
11 #include "../pel.hpp"
12 #include "../pel_types.hpp"
13 #include "../pel_values.hpp"
14 
15 #include <Python.h>
16 
17 #include <CLI/CLI.hpp>
18 #include <phosphor-logging/log.hpp>
19 
20 #include <bitset>
21 #include <fstream>
22 #include <iostream>
23 #include <regex>
24 #include <string>
25 
26 namespace fs = std::filesystem;
27 using namespace phosphor::logging;
28 using namespace openpower::pels;
29 namespace message = openpower::pels::message;
30 namespace pv = openpower::pels::pel_values;
31 
32 const uint8_t critSysTermSeverity = 0x51;
33 
34 using PELFunc = std::function<void(const PEL&, bool hexDump)>;
35 message::Registry registry(getPELReadOnlyDataPath() / message::registryFileName,
36                            false);
37 
pelLogDir()38 std::string pelLogDir()
39 {
40     return std::string(phosphor::logging::paths::extension()) + "/pels/logs";
41 }
42 
43 /**
44  * @brief helper function to get PEL commit timestamp from file name
45  * @retrun uint64_t - PEL commit timestamp
46  * @param[in] std::string - file name
47  */
fileNameToTimestamp(const std::string & fileName)48 uint64_t fileNameToTimestamp(const std::string& fileName)
49 {
50     std::string token = fileName.substr(0, fileName.find("_"));
51     uint64_t bcdTime = 0;
52     if (token.length() >= 14)
53     {
54         int i = 0;
55 
56         try
57         {
58             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
59             bcdTime |= (static_cast<uint64_t>(tmp) << 56);
60         }
61         catch (const std::exception& err)
62         {
63             std::cout << "Conversion failure: " << err.what() << std::endl;
64         }
65         i += 2;
66         try
67         {
68             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
69             bcdTime |= (static_cast<uint64_t>(tmp) << 48);
70         }
71         catch (const std::exception& err)
72         {
73             std::cout << "Conversion failure: " << err.what() << std::endl;
74         }
75         i += 2;
76         try
77         {
78             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
79             bcdTime |= (static_cast<uint64_t>(tmp) << 40);
80         }
81         catch (const std::exception& err)
82         {
83             std::cout << "Conversion failure: " << err.what() << std::endl;
84         }
85         i += 2;
86         try
87         {
88             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
89             bcdTime |= (static_cast<uint64_t>(tmp) << 32);
90         }
91         catch (const std::exception& err)
92         {
93             std::cout << "Conversion failure: " << err.what() << std::endl;
94         }
95         i += 2;
96         try
97         {
98             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
99             bcdTime |= (tmp << 24);
100         }
101         catch (const std::exception& err)
102         {
103             std::cout << "Conversion failure: " << err.what() << std::endl;
104         }
105         i += 2;
106         try
107         {
108             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
109             bcdTime |= (tmp << 16);
110         }
111         catch (const std::exception& err)
112         {
113             std::cout << "Conversion failure: " << err.what() << std::endl;
114         }
115         i += 2;
116         try
117         {
118             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
119             bcdTime |= (tmp << 8);
120         }
121         catch (const std::exception& err)
122         {
123             std::cout << "Conversion failure: " << err.what() << std::endl;
124         }
125         i += 2;
126         try
127         {
128             auto tmp = std::stoul(token.substr(i, 2), nullptr, 16);
129             bcdTime |= tmp;
130         }
131         catch (const std::exception& err)
132         {
133             std::cout << "Conversion failure: " << err.what() << std::endl;
134         }
135     }
136     return bcdTime;
137 }
138 
139 /**
140  * @brief helper function to get PEL id from file name
141  * @retrun uint32_t - PEL id
142  * @param[in] std::string - file name
143  */
fileNameToPELId(const std::string & fileName)144 uint32_t fileNameToPELId(const std::string& fileName)
145 {
146     uint32_t num = 0;
147     try
148     {
149         num = std::stoul(fileName.substr(fileName.find("_") + 1), nullptr, 16);
150     }
151     catch (const std::exception& err)
152     {
153         std::cout << "Conversion failure: " << err.what() << std::endl;
154     }
155     return num;
156 }
157 
158 /**
159  * @brief Check if the string ends with the PEL ID string passed in
160  * @param[in] str - string to check for PEL ID
161  * @param[in] pelID - PEL id string
162  *
163  * @return bool - true with suffix matches
164  */
endsWithPelID(const std::string & str,const std::string & pelID)165 bool endsWithPelID(const std::string& str, const std::string& pelID)
166 {
167     constexpr size_t pelIDSize = 8;
168 
169     if (pelID.size() != pelIDSize)
170     {
171         return false;
172     }
173 
174     size_t slen = str.size(), elen = pelID.size();
175     if (slen < elen)
176         return false;
177     while (elen)
178     {
179         if (str[--slen] != pelID[--elen])
180             return false;
181     }
182     return true;
183 }
184 
185 /**
186  * @brief get data form raw PEL file.
187  * @param[in] std::string Name of file with raw PEL
188  * @return std::vector<uint8_t> char vector read from raw PEL file.
189  */
getFileData(const std::string & name)190 std::vector<uint8_t> getFileData(const std::string& name)
191 {
192     std::ifstream file(name, std::ifstream::in);
193     if (file.good())
194     {
195         std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
196                                   std::istreambuf_iterator<char>()};
197         return data;
198     }
199     else
200     {
201         return {};
202     }
203 }
204 
205 /**
206  * @brief Initialize Python interpreter and gather all UD parser modules under
207  *        the paths found in Python sys.path and the current user directory.
208  *        This is to prevent calling a non-existant module which causes Python
209  *        to print an import error message and breaking JSON output.
210  *
211  * @return std::vector<std::string> Vector of plugins found in filesystem
212  */
getPlugins()213 std::vector<std::string> getPlugins()
214 {
215     Py_Initialize();
216     std::vector<std::string> plugins;
217     std::vector<std::string> siteDirs;
218     std::array<std::string, 2> parserDirs = {"udparsers", "srcparsers"};
219     PyObject* pName = PyUnicode_FromString("sys");
220     PyObject* pModule = PyImport_Import(pName);
221     Py_XDECREF(pName);
222     PyObject* pDict = PyModule_GetDict(pModule);
223     Py_XDECREF(pModule);
224     PyObject* pResult = PyDict_GetItemString(pDict, "path");
225     PyObject* pValue = PyUnicode_FromString(".");
226     PyList_Append(pResult, pValue);
227     Py_XDECREF(pValue);
228     auto list_size = PyList_Size(pResult);
229     for (auto i = 0; i < list_size; i++)
230     {
231         PyObject* item = PyList_GetItem(pResult, i);
232         PyObject* pBytes = PyUnicode_AsEncodedString(item, "utf-8", "~E~");
233         const char* output = PyBytes_AS_STRING(pBytes);
234         Py_XDECREF(pBytes);
235         std::string tmpStr(output);
236         siteDirs.push_back(tmpStr);
237     }
238     for (const auto& dir : siteDirs)
239     {
240         for (const auto& parserDir : parserDirs)
241         {
242             if (fs::exists(dir + "/" + parserDir))
243             {
244                 for (const auto& entry :
245                      fs::directory_iterator(dir + "/" + parserDir))
246                 {
247                     if (entry.is_directory() and
248                         fs::exists(entry.path().string() + "/" +
249                                    entry.path().stem().string() + ".py"))
250                     {
251                         plugins.push_back(entry.path().stem());
252                     }
253                 }
254             }
255         }
256     }
257     return plugins;
258 }
259 
260 /**
261  * @brief Creates JSON string of a PEL entry if fullPEL is false or prints to
262  *        stdout the full PEL in JSON if fullPEL is true
263  * @param[in] itr - std::map iterator of <uint32_t, BCDTime>
264  * @param[in] hidden - Boolean to include hidden PELs
265  * @param[in] includeInfo - Boolean to include informational PELs
266  * @param[in] critSysTerm - Boolean to include critical error and system
267  * termination PELs
268  * @param[in] fullPEL - Boolean to print full JSON representation of PEL
269  * @param[in] foundPEL - Boolean to check if any PEL is present
270  * @param[in] scrubRegex - SRC regex object
271  * @param[in] plugins - Vector of strings of plugins found in filesystem
272  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
273  * @return std::string - JSON string of PEL entry (empty if fullPEL is true)
274  */
275 template <typename T>
genPELJSON(T itr,bool hidden,bool includeInfo,bool critSysTerm,bool fullPEL,bool & foundPEL,const std::optional<std::regex> & scrubRegex,const std::vector<std::string> & plugins,bool hexDump,bool archive)276 std::string genPELJSON(
277     T itr, bool hidden, bool includeInfo, bool critSysTerm, bool fullPEL,
278     bool& foundPEL, const std::optional<std::regex>& scrubRegex,
279     const std::vector<std::string>& plugins, bool hexDump, bool archive)
280 {
281     std::string val;
282     std::string listStr;
283     char name[51];
284     sprintf(name, "/%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X",
285             static_cast<uint8_t>((itr.second >> 56) & 0xFF),
286             static_cast<uint8_t>((itr.second >> 48) & 0xFF),
287             static_cast<uint8_t>((itr.second >> 40) & 0xFF),
288             static_cast<uint8_t>((itr.second >> 32) & 0xFF),
289             static_cast<uint8_t>((itr.second >> 24) & 0xFF),
290             static_cast<uint8_t>((itr.second >> 16) & 0xFF),
291             static_cast<uint8_t>((itr.second >> 8) & 0xFF),
292             static_cast<uint8_t>(itr.second & 0xFF), itr.first);
293 
294     auto fileName = (archive ? pelLogDir() + "/archive" : pelLogDir()) + name;
295     try
296     {
297         std::vector<uint8_t> data = getFileData(fileName);
298         if (data.empty())
299         {
300             log<level::ERR>("Empty PEL file",
301                             entry("FILENAME=%s", fileName.c_str()));
302             return listStr;
303         }
304         PEL pel{data};
305         if (!pel.valid())
306         {
307             return listStr;
308         }
309         if (!includeInfo && pel.userHeader().severity() == 0)
310         {
311             return listStr;
312         }
313         if (critSysTerm && pel.userHeader().severity() != critSysTermSeverity)
314         {
315             return listStr;
316         }
317         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
318         if (!hidden && actionFlags.test(hiddenFlagBit))
319         {
320             return listStr;
321         }
322         if (pel.primarySRC() && scrubRegex)
323         {
324             val = pel.primarySRC().value()->asciiString();
325             if (std::regex_search(trimEnd(val), scrubRegex.value(),
326                                   std::regex_constants::match_not_null))
327             {
328                 return listStr;
329             }
330         }
331         if (hexDump)
332         {
333             std::cout
334                 << dumpHex(std::data(pel.data()), pel.size(), 0, false).get()
335                 << std::endl;
336         }
337         else if (fullPEL)
338         {
339             if (!foundPEL)
340             {
341                 std::cout << "[\n";
342                 foundPEL = true;
343             }
344             else
345             {
346                 std::cout << ",\n\n";
347             }
348             pel.toJSON(registry, plugins);
349         }
350         else
351         {
352             // id
353             listStr += "    \"" +
354                        getNumberString("0x%X", pel.privateHeader().id()) +
355                        "\": {\n";
356             // ASCII
357             if (pel.primarySRC())
358             {
359                 val = pel.primarySRC().value()->asciiString();
360                 jsonInsert(listStr, "SRC", trimEnd(val), 2);
361 
362                 // Registry message
363                 auto regVal = pel.primarySRC().value()->getErrorDetails(
364                     registry, DetailLevel::message, true);
365                 if (regVal)
366                 {
367                     val = regVal.value();
368                     jsonInsert(listStr, "Message", val, 2);
369                 }
370             }
371             else
372             {
373                 jsonInsert(listStr, "SRC", "No SRC", 2);
374             }
375 
376             // platformid
377             jsonInsert(listStr, "PLID",
378                        getNumberString("0x%X", pel.privateHeader().plid()), 2);
379 
380             // creatorid
381             std::string creatorID =
382                 getNumberString("%c", pel.privateHeader().creatorID());
383             val = pv::creatorIDs.count(creatorID) ? pv::creatorIDs.at(creatorID)
384                                                   : "Unknown Creator ID";
385             jsonInsert(listStr, "CreatorID", val, 2);
386 
387             // subsystem
388             std::string subsystem = pv::getValue(pel.userHeader().subsystem(),
389                                                  pel_values::subsystemValues);
390             jsonInsert(listStr, "Subsystem", subsystem, 2);
391 
392             // commit time
393             char tmpValStr[50];
394             sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
395                     pel.privateHeader().commitTimestamp().month,
396                     pel.privateHeader().commitTimestamp().day,
397                     pel.privateHeader().commitTimestamp().yearMSB,
398                     pel.privateHeader().commitTimestamp().yearLSB,
399                     pel.privateHeader().commitTimestamp().hour,
400                     pel.privateHeader().commitTimestamp().minutes,
401                     pel.privateHeader().commitTimestamp().seconds);
402             jsonInsert(listStr, "Commit Time", tmpValStr, 2);
403 
404             // severity
405             std::string severity = pv::getValue(pel.userHeader().severity(),
406                                                 pel_values::severityValues);
407             jsonInsert(listStr, "Sev", severity, 2);
408 
409             // compID
410             jsonInsert(
411                 listStr, "CompID",
412                 getComponentName(pel.privateHeader().header().componentID,
413                                  pel.privateHeader().creatorID()),
414                 2);
415 
416             auto found = listStr.rfind(",");
417             if (found != std::string::npos)
418             {
419                 listStr.replace(found, 1, "");
420                 listStr += "    },\n";
421             }
422             foundPEL = true;
423         }
424     }
425     catch (const std::exception& e)
426     {
427         log<level::ERR>("Hit exception while reading PEL File",
428                         entry("FILENAME=%s", fileName.c_str()),
429                         entry("ERROR=%s", e.what()));
430     }
431     return listStr;
432 }
433 
434 /**
435  * @brief Print a list of PELs or a JSON array of PELs
436  * @param[in] order - Boolean to print in reverse orser
437  * @param[in] hidden - Boolean to include hidden PELs
438  * @param[in] includeInfo - Boolean to include informational PELs
439  * @param[in] critSysTerm - Boolean to include critical error and system
440  * termination PELs
441  * @param[in] fullPEL - Boolean to print full PEL into a JSON array
442  * @param[in] scrubRegex - SRC regex object
443  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
444  */
printPELs(bool order,bool hidden,bool includeInfo,bool critSysTerm,bool fullPEL,const std::optional<std::regex> & scrubRegex,bool hexDump,bool archive=false)445 void printPELs(bool order, bool hidden, bool includeInfo, bool critSysTerm,
446                bool fullPEL, const std::optional<std::regex>& scrubRegex,
447                bool hexDump, bool archive = false)
448 {
449     std::string listStr;
450     std::vector<std::pair<uint32_t, uint64_t>> PELs;
451     std::vector<std::string> plugins;
452     listStr = "{\n";
453     for (auto it = (archive ? fs::directory_iterator(pelLogDir() + "/archive")
454                             : fs::directory_iterator(pelLogDir()));
455          it != fs::directory_iterator(); ++it)
456     {
457         if (!fs::is_regular_file((*it).path()))
458         {
459             continue;
460         }
461         else
462         {
463             PELs.emplace_back(fileNameToPELId((*it).path().filename()),
464                               fileNameToTimestamp((*it).path().filename()));
465         }
466     }
467 
468     // Sort the pairs based on second time parameter
469     std::sort(PELs.begin(), PELs.end(),
470               [](const auto& left, const auto& right) {
471                   return left.second < right.second;
472               });
473 
474     bool foundPEL = false;
475 
476     if (fullPEL && !hexDump)
477     {
478         plugins = getPlugins();
479     }
480     auto buildJSON = [&listStr, &hidden, &includeInfo, &critSysTerm, &fullPEL,
481                       &foundPEL, &scrubRegex, &plugins, &hexDump,
482                       &archive](const auto& i) {
483         listStr += genPELJSON(i, hidden, includeInfo, critSysTerm, fullPEL,
484                               foundPEL, scrubRegex, plugins, hexDump, archive);
485     };
486     if (order)
487     {
488         std::for_each(PELs.rbegin(), PELs.rend(), buildJSON);
489     }
490     else
491     {
492         std::for_each(PELs.begin(), PELs.end(), buildJSON);
493     }
494     if (hexDump)
495     {
496         return;
497     }
498     if (foundPEL)
499     {
500         if (fullPEL)
501         {
502             std::cout << "]" << std::endl;
503         }
504         else
505         {
506             std::size_t found;
507             found = listStr.rfind(",");
508             if (found != std::string::npos)
509             {
510                 listStr.replace(found, 1, "");
511                 listStr += "}\n";
512                 printf("%s", listStr.c_str());
513             }
514         }
515     }
516     else
517     {
518         std::string emptyJSON = fullPEL ? "[]" : "{}";
519         std::cout << emptyJSON << std::endl;
520     }
521 }
522 
523 /**
524  * @brief Calls the function passed in on the PEL with the ID
525  *        passed in.
526  *
527  * @param[in] id - The string version of the PEL or BMC Log ID, either with or
528  *                 without the 0x prefix.
529  * @param[in] func - The std::function<void(const PEL&, bool hexDump)> function
530  *                   to run.
531  * @param[in] useBMC - if true, search by BMC Log ID, else search by PEL ID
532  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
533  */
callFunctionOnPEL(const std::string & id,const PELFunc & func,bool useBMC=false,bool hexDump=false,bool archive=false)534 void callFunctionOnPEL(const std::string& id, const PELFunc& func,
535                        bool useBMC = false, bool hexDump = false,
536                        bool archive = false)
537 {
538     std::string pelID{id};
539     if (!useBMC)
540     {
541         std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
542 
543         if (pelID.starts_with("0X"))
544         {
545             pelID.erase(0, 2);
546         }
547     }
548 
549     bool found = false;
550 
551     for (auto it = (archive ? fs::directory_iterator(pelLogDir() + "/archive")
552                             : fs::directory_iterator(pelLogDir()));
553          it != fs::directory_iterator(); ++it)
554     {
555         // The PEL ID is part of the filename, so use that to find the PEL if
556         // "useBMC" is set to false, otherwise we have to search within the PEL
557 
558         if (!fs::is_regular_file((*it).path()))
559         {
560             continue;
561         }
562 
563         if ((endsWithPelID((*it).path(), pelID) && !useBMC) || useBMC)
564         {
565             auto data = getFileData((*it).path());
566             if (!data.empty())
567             {
568                 PEL pel{data};
569                 if (!useBMC ||
570                     (useBMC && pel.obmcLogID() == std::stoul(id, nullptr, 0)))
571                 {
572                     found = true;
573                     try
574                     {
575                         func(pel, hexDump);
576                         break;
577                     }
578                     catch (const std::exception& e)
579                     {
580                         std::cerr << " Internal function threw an exception: "
581                                   << e.what() << "\n";
582                         exit(1);
583                     }
584                 }
585             }
586             else
587             {
588                 std::cerr << "Could not read PEL file\n";
589                 exit(1);
590             }
591         }
592     }
593 
594     if (!found)
595     {
596         std::cerr << "PEL not found\n";
597         exit(1);
598     }
599 }
600 
601 /**
602  * @brief Delete a PEL file.
603  *
604  * @param[in] id - The PEL ID to delete.
605  */
deletePEL(const std::string & id)606 void deletePEL(const std::string& id)
607 {
608     std::string pelID{id};
609 
610     std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
611 
612     if (pelID.starts_with("0X"))
613     {
614         pelID.erase(0, 2);
615     }
616 
617     for (auto it = fs::directory_iterator(pelLogDir());
618          it != fs::directory_iterator(); ++it)
619     {
620         if (endsWithPelID((*it).path(), pelID))
621         {
622             fs::remove((*it).path());
623         }
624     }
625 }
626 
627 /**
628  * @brief Delete all PEL files.
629  */
deleteAllPELs()630 void deleteAllPELs()
631 {
632     log<level::INFO>("peltool deleting all event logs");
633 
634     for (const auto& entry : fs::directory_iterator(pelLogDir()))
635     {
636         if (!fs::is_regular_file(entry.path()))
637         {
638             continue;
639         }
640         fs::remove(entry.path());
641     }
642 }
643 
644 /**
645  * @brief Display a single PEL
646  *
647  * @param[in] pel - the PEL to display
648  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
649  */
displayPEL(const PEL & pel,bool hexDump)650 void displayPEL(const PEL& pel, bool hexDump)
651 {
652     if (pel.valid())
653     {
654         if (hexDump)
655         {
656             std::string dstr =
657                 dumpHex(std::data(pel.data()), pel.size(), 0, false).get();
658             std::cout << dstr << std::endl;
659         }
660         else
661         {
662             auto plugins = getPlugins();
663             pel.toJSON(registry, plugins);
664         }
665     }
666     else
667     {
668         std::cerr << "PEL was malformed\n";
669         exit(1);
670     }
671 }
672 
673 /**
674  * @brief Print number of PELs
675  * @param[in] hidden - Bool to include hidden logs
676  * @param[in] includeInfo - Bool to include informational logs
677  * @param[in] critSysTerm - Bool to include CritSysTerm
678  * @param[in] scrubRegex - SRC regex object
679  */
printPELCount(bool hidden,bool includeInfo,bool critSysTerm,const std::optional<std::regex> & scrubRegex)680 void printPELCount(bool hidden, bool includeInfo, bool critSysTerm,
681                    const std::optional<std::regex>& scrubRegex)
682 {
683     std::size_t count = 0;
684 
685     for (auto it = fs::directory_iterator(pelLogDir());
686          it != fs::directory_iterator(); ++it)
687     {
688         if (!fs::is_regular_file((*it).path()))
689         {
690             continue;
691         }
692         std::vector<uint8_t> data = getFileData((*it).path());
693         if (data.empty())
694         {
695             continue;
696         }
697         PEL pel{data};
698         if (!pel.valid())
699         {
700             continue;
701         }
702         if (!includeInfo && pel.userHeader().severity() == 0)
703         {
704             continue;
705         }
706         if (critSysTerm && pel.userHeader().severity() != critSysTermSeverity)
707         {
708             continue;
709         }
710         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
711         if (!hidden && actionFlags.test(hiddenFlagBit))
712         {
713             continue;
714         }
715         if (pel.primarySRC() && scrubRegex)
716         {
717             std::string val = pel.primarySRC().value()->asciiString();
718             if (std::regex_search(trimEnd(val), scrubRegex.value(),
719                                   std::regex_constants::match_not_null))
720             {
721                 continue;
722             }
723         }
724         count++;
725     }
726     std::cout << "{\n"
727               << "    \"Number of PELs found\": "
728               << getNumberString("%d", count) << "\n}\n";
729 }
730 
731 /**
732  * @brief Generate regex pattern object from file contents
733  * @param[in] scrubFile - File containing regex pattern
734  * @return std::regex - SRC regex object
735  */
genRegex(std::string & scrubFile)736 std::regex genRegex(std::string& scrubFile)
737 {
738     std::string pattern;
739     std::ifstream contents(scrubFile);
740     if (contents.fail())
741     {
742         std::cerr << "Can't open \"" << scrubFile << "\"\n";
743         exit(1);
744     }
745     std::string line;
746     while (std::getline(contents, line))
747     {
748         if (!line.empty())
749         {
750             pattern.append(line + "|");
751         }
752     }
753     try
754     {
755         std::regex scrubRegex(pattern, std::regex::icase);
756         return scrubRegex;
757     }
758     catch (const std::regex_error& e)
759     {
760         if (e.code() == std::regex_constants::error_collate)
761             std::cerr << "Invalid collating element request\n";
762         else if (e.code() == std::regex_constants::error_ctype)
763             std::cerr << "Invalid character class\n";
764         else if (e.code() == std::regex_constants::error_escape)
765             std::cerr << "Invalid escape character or trailing escape\n";
766         else if (e.code() == std::regex_constants::error_backref)
767             std::cerr << "Invalid back reference\n";
768         else if (e.code() == std::regex_constants::error_brack)
769             std::cerr << "Mismatched bracket ([ or ])\n";
770         else if (e.code() == std::regex_constants::error_paren)
771         {
772             // to catch return code error_badrepeat when error_paren is retured
773             // instead
774             size_t pos = pattern.find_first_of("*+?{");
775             while (pos != std::string::npos)
776             {
777                 if (pos == 0 || pattern.substr(pos - 1, 1) == "|")
778                 {
779                     std::cerr
780                         << "A repetition character (*, ?, +, or {) was not "
781                            "preceded by a valid regular expression\n";
782                     exit(1);
783                 }
784                 pos = pattern.find_first_of("*+?{", pos + 1);
785             }
786             std::cerr << "Mismatched parentheses (( or ))\n";
787         }
788         else if (e.code() == std::regex_constants::error_brace)
789             std::cerr << "Mismatched brace ({ or })\n";
790         else if (e.code() == std::regex_constants::error_badbrace)
791             std::cerr << "Invalid range inside a { }\n";
792         else if (e.code() == std::regex_constants::error_range)
793             std::cerr << "Invalid character range (e.g., [z-a])\n";
794         else if (e.code() == std::regex_constants::error_space)
795             std::cerr << "Insufficient memory to handle regular expression\n";
796         else if (e.code() == std::regex_constants::error_badrepeat)
797             std::cerr << "A repetition character (*, ?, +, or {) was not "
798                          "preceded by a valid regular expression\n";
799         else if (e.code() == std::regex_constants::error_complexity)
800             std::cerr << "The requested match is too complex\n";
801         else if (e.code() == std::regex_constants::error_stack)
802             std::cerr << "Insufficient memory to evaluate a match\n";
803         exit(1);
804     }
805 }
806 
exitWithError(const std::string & help,const char * err)807 static void exitWithError(const std::string& help, const char* err)
808 {
809     std::cerr << "ERROR: " << err << std::endl << help << std::endl;
810     exit(-1);
811 }
812 
main(int argc,char ** argv)813 int main(int argc, char** argv)
814 {
815     CLI::App app{"OpenBMC PEL Tool"};
816     std::string fileName;
817     std::string idPEL;
818     std::string bmcId;
819     std::string idToDelete;
820     std::string scrubFile;
821     std::optional<std::regex> scrubRegex;
822     bool listPEL = false;
823     bool listPELDescOrd = false;
824     bool hidden = false;
825     bool includeInfo = false;
826     bool critSysTerm = false;
827     bool deleteAll = false;
828     bool showPELCount = false;
829     bool fullPEL = false;
830     bool hexDump = false;
831     bool archive = false;
832 
833     app.set_help_flag("--help", "Print this help message and exit");
834     app.add_option("--file", fileName, "Display a PEL using its Raw PEL file");
835     app.add_option("-i, --id", idPEL, "Display a PEL based on its ID");
836     app.add_option("--bmc-id", bmcId,
837                    "Display a PEL based on its BMC Event ID");
838     app.add_flag("-a", fullPEL, "Display all PELs");
839     app.add_flag("-l", listPEL, "List PELs");
840     app.add_flag("-n", showPELCount, "Show number of PELs");
841     app.add_flag("-r", listPELDescOrd, "Reverse order of output");
842     app.add_flag("-h", hidden, "Include hidden PELs");
843     app.add_flag("-f,--info", includeInfo, "Include informational PELs");
844     app.add_flag("-t, --termination", critSysTerm,
845                  "List only critical system terminating PELs");
846     app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID");
847     app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs");
848     app.add_option("-s, --scrub", scrubFile,
849                    "File containing SRC regular expressions to ignore");
850     app.add_flag("-x", hexDump, "Display PEL(s) in hexdump instead of JSON");
851     app.add_flag("--archive", archive, "List or display archived PELs");
852 
853     CLI11_PARSE(app, argc, argv);
854 
855     if (!fileName.empty())
856     {
857         std::vector<uint8_t> data = getFileData(fileName);
858         if (!data.empty())
859         {
860             PEL pel{data};
861             if (hexDump)
862             {
863                 std::string dstr =
864                     dumpHex(std::data(pel.data()), pel.size(), 0, false).get();
865                 std::cout << dstr << std::endl;
866             }
867             else
868             {
869                 auto plugins = getPlugins();
870                 pel.toJSON(registry, plugins);
871             }
872         }
873         else
874         {
875             exitWithError(app.help("", CLI::AppFormatMode::All),
876                           "Raw PEL file can't be read.");
877         }
878     }
879     else if (!idPEL.empty())
880     {
881         callFunctionOnPEL(idPEL, displayPEL, false, hexDump, archive);
882     }
883     else if (!bmcId.empty())
884     {
885         callFunctionOnPEL(bmcId, displayPEL, true, hexDump, archive);
886     }
887     else if (fullPEL || listPEL)
888     {
889         if (!scrubFile.empty())
890         {
891             scrubRegex = genRegex(scrubFile);
892         }
893         printPELs(listPELDescOrd, hidden, includeInfo, critSysTerm, fullPEL,
894                   scrubRegex, hexDump, archive);
895     }
896     else if (showPELCount)
897     {
898         if (!scrubFile.empty())
899         {
900             scrubRegex = genRegex(scrubFile);
901         }
902         printPELCount(hidden, includeInfo, critSysTerm, scrubRegex);
903     }
904     else if (!idToDelete.empty())
905     {
906         deletePEL(idToDelete);
907     }
908     else if (deleteAll)
909     {
910         deleteAllPELs();
911     }
912     else
913     {
914         std::cout << app.help("", CLI::AppFormatMode::All) << std::endl;
915     }
916     Py_Finalize();
917     return 0;
918 }
919