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