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