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 += "\t\"" +
356                        getNumberString("0x%X", pel.privateHeader().id()) +
357                        "\": {\n";
358             // ASCII
359             if (pel.primarySRC())
360             {
361                 val = pel.primarySRC().value()->asciiString();
362                 listStr += "\t\t\"SRC\": \"" + trimEnd(val) + "\",\n";
363                 // Registry message
364                 auto regVal = pel.primarySRC().value()->getErrorDetails(
365                     registry, DetailLevel::message, true);
366                 if (regVal)
367                 {
368                     val = regVal.value();
369                     listStr += "\t\t\"Message\": \"" + val + "\",\n";
370                 }
371             }
372             else
373             {
374                 listStr += "\t\t\"SRC\": \"No SRC\",\n";
375             }
376             // platformid
377             listStr += "\t\t\"PLID\": \"" +
378                        getNumberString("0x%X", pel.privateHeader().plid()) +
379                        "\",\n";
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             listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
386             // subsytem
387             std::string subsystem = pv::getValue(pel.userHeader().subsystem(),
388                                                  pel_values::subsystemValues);
389             listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
390             // commit time
391             sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
392                     pel.privateHeader().commitTimestamp().month,
393                     pel.privateHeader().commitTimestamp().day,
394                     pel.privateHeader().commitTimestamp().yearMSB,
395                     pel.privateHeader().commitTimestamp().yearLSB,
396                     pel.privateHeader().commitTimestamp().hour,
397                     pel.privateHeader().commitTimestamp().minutes,
398                     pel.privateHeader().commitTimestamp().seconds);
399             val = std::string(tmpValStr);
400             listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
401             // severity
402             std::string severity = pv::getValue(pel.userHeader().severity(),
403                                                 pel_values::severityValues);
404             listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
405             // compID
406             listStr += "\t\t\"CompID\": \"" +
407                        getNumberString(
408                            "0x%X", pel.privateHeader().header().componentID) +
409                        "\",\n ";
410             found = listStr.rfind(",");
411             if (found != std::string::npos)
412             {
413                 listStr.replace(found, 1, "");
414                 listStr += "\t},\n";
415             }
416             foundPEL = true;
417         }
418     }
419     catch (std::exception& e)
420     {
421         log<level::ERR>("Hit exception while reading PEL File",
422                         entry("FILENAME=%s", fileName.c_str()),
423                         entry("ERROR=%s", e.what()));
424     }
425     return listStr;
426 }
427 
428 /**
429  * @brief Print a list of PELs or a JSON array of PELs
430  * @param[in] order - Boolean to print in reverse orser
431  * @param[in] hidden - Boolean to include hidden PELs
432  * @param[in] includeInfo - Boolean to include informational PELs
433  * @param[in] critSysTerm - Boolean to include critical error and system
434  * termination PELs
435  * @param[in] fullPEL - Boolean to print full PEL into a JSON array
436  * @param[in] scrubRegex - SRC regex object
437  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
438  */
439 void printPELs(bool order, bool hidden, bool includeInfo, bool critSysTerm,
440                bool fullPEL, const std::optional<std::regex>& scrubRegex,
441                bool hexDump)
442 {
443     std::string listStr;
444     std::map<uint32_t, BCDTime> PELs;
445     std::vector<std::string> plugins;
446     listStr = "{\n";
447     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
448          it != fs::directory_iterator(); ++it)
449     {
450         if (!fs::is_regular_file((*it).path()))
451         {
452             continue;
453         }
454         else
455         {
456             PELs.emplace(fileNameToPELId((*it).path().filename()),
457                          fileNameToTimestamp((*it).path().filename()));
458         }
459     }
460     bool foundPEL = false;
461 
462     if (fullPEL && !hexDump)
463     {
464         plugins = getPlugins();
465     }
466     auto buildJSON = [&listStr, &hidden, &includeInfo, &critSysTerm, &fullPEL,
467                       &foundPEL, &scrubRegex, &plugins,
468                       &hexDump](const auto& i) {
469         listStr += genPELJSON(i, hidden, includeInfo, critSysTerm, fullPEL,
470                               foundPEL, scrubRegex, plugins, hexDump);
471     };
472     if (order)
473     {
474         std::for_each(PELs.rbegin(), PELs.rend(), buildJSON);
475     }
476     else
477     {
478         std::for_each(PELs.begin(), PELs.end(), buildJSON);
479     }
480     if (hexDump)
481     {
482         return;
483     }
484     if (foundPEL)
485     {
486         if (fullPEL)
487         {
488             std::cout << "]" << std::endl;
489         }
490         else
491         {
492             std::size_t found;
493             found = listStr.rfind(",");
494             if (found != std::string::npos)
495             {
496                 listStr.replace(found, 1, "");
497                 listStr += "}\n";
498                 printf("%s", listStr.c_str());
499             }
500         }
501     }
502     else
503     {
504         std::string emptyJSON = fullPEL ? "[]" : "{}";
505         std::cout << emptyJSON << std::endl;
506     }
507 }
508 
509 /**
510  * @brief Calls the function passed in on the PEL with the ID
511  *        passed in.
512  *
513  * @param[in] id - The string version of the PEL or BMC Log ID, either with or
514  *                 without the 0x prefix.
515  * @param[in] func - The std::function<void(const PEL&, bool hexDump)> function
516  *                   to run.
517  * @param[in] useBMC - if true, search by BMC Log ID, else search by PEL ID
518  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
519  */
520 void callFunctionOnPEL(const std::string& id, const PELFunc& func,
521                        bool useBMC = false, bool hexDump = false)
522 {
523     std::string pelID{id};
524     if (!useBMC)
525     {
526         std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
527 
528         if (pelID.find("0X") == 0)
529         {
530             pelID.erase(0, 2);
531         }
532     }
533 
534     bool found = false;
535 
536     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
537          it != fs::directory_iterator(); ++it)
538     {
539         // The PEL ID is part of the filename, so use that to find the PEL if
540         // "useBMC" is set to false, otherwise we have to search within the PEL
541 
542         if (!fs::is_regular_file((*it).path()))
543         {
544             continue;
545         }
546 
547         if ((ends_with((*it).path(), pelID) && !useBMC) || useBMC)
548         {
549             auto data = getFileData((*it).path());
550             if (!data.empty())
551             {
552                 PEL pel{data};
553                 if (!useBMC ||
554                     (useBMC && pel.obmcLogID() == std::stoul(id, nullptr, 0)))
555                 {
556                     found = true;
557                     try
558                     {
559                         func(pel, hexDump);
560                         break;
561                     }
562                     catch (std::exception& e)
563                     {
564                         std::cerr << " Internal function threw an exception: "
565                                   << e.what() << "\n";
566                         exit(1);
567                     }
568                 }
569             }
570             else
571             {
572                 std::cerr << "Could not read PEL file\n";
573                 exit(1);
574             }
575         }
576     }
577 
578     if (!found)
579     {
580         std::cerr << "PEL not found\n";
581         exit(1);
582     }
583 }
584 
585 /**
586  * @brief Delete a PEL file.
587  *
588  * @param[in] id - The PEL ID to delete.
589  */
590 void deletePEL(const std::string& id)
591 {
592     std::string pelID{id};
593 
594     std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
595 
596     if (pelID.find("0X") == 0)
597     {
598         pelID.erase(0, 2);
599     }
600 
601     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
602          it != fs::directory_iterator(); ++it)
603     {
604         if (ends_with((*it).path(), pelID))
605         {
606             fs::remove((*it).path());
607         }
608     }
609 }
610 
611 /**
612  * @brief Delete all PEL files.
613  */
614 void deleteAllPELs()
615 {
616     log<level::INFO>("peltool deleting all event logs");
617 
618     for (const auto& entry :
619          fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs"))
620     {
621         fs::remove(entry.path());
622     }
623 }
624 
625 /**
626  * @brief Display a single PEL
627  *
628  * @param[in] pel - the PEL to display
629  * @param[in] hexDump - Boolean to print hexdump of PEL instead of JSON
630  */
631 void displayPEL(const PEL& pel, bool hexDump)
632 {
633     if (pel.valid())
634     {
635         if (hexDump)
636         {
637             std::string dstr =
638                 dumpHex(std::data(pel.data()), pel.size(), 0, false);
639             std::cout << dstr << std::endl;
640         }
641         else
642         {
643             auto plugins = getPlugins();
644             pel.toJSON(registry, plugins);
645         }
646     }
647     else
648     {
649         std::cerr << "PEL was malformed\n";
650         exit(1);
651     }
652 }
653 
654 /**
655  * @brief Print number of PELs
656  * @param[in] hidden - Bool to include hidden logs
657  * @param[in] includeInfo - Bool to include informational logs
658  * @param[in] critSysTerm - Bool to include CritSysTerm
659  * @param[in] scrubRegex - SRC regex object
660  */
661 void printPELCount(bool hidden, bool includeInfo, bool critSysTerm,
662                    const std::optional<std::regex>& scrubRegex)
663 {
664     std::size_t count = 0;
665     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
666          it != fs::directory_iterator(); ++it)
667     {
668         if (!fs::is_regular_file((*it).path()))
669         {
670             continue;
671         }
672         std::vector<uint8_t> data = getFileData((*it).path());
673         if (data.empty())
674         {
675             continue;
676         }
677         PEL pel{data};
678         if (!pel.valid())
679         {
680             continue;
681         }
682         if (!includeInfo && pel.userHeader().severity() == 0)
683         {
684             continue;
685         }
686         if (critSysTerm && pel.userHeader().severity() != critSysTermSeverity)
687         {
688             continue;
689         }
690         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
691         if (!hidden && actionFlags.test(hiddenFlagBit))
692         {
693             continue;
694         }
695         if (pel.primarySRC() && scrubRegex)
696         {
697             std::string val = pel.primarySRC().value()->asciiString();
698             if (std::regex_search(trimEnd(val), scrubRegex.value(),
699                                   std::regex_constants::match_not_null))
700             {
701                 continue;
702             }
703         }
704         count++;
705     }
706     std::cout << "{\n"
707               << "    \"Number of PELs found\": "
708               << getNumberString("%d", count) << "\n}\n";
709 }
710 
711 /**
712  * @brief Generate regex pattern object from file contents
713  * @param[in] scrubFile - File containing regex pattern
714  * @return std::regex - SRC regex object
715  */
716 std::regex genRegex(std::string& scrubFile)
717 {
718     std::string pattern;
719     std::ifstream contents(scrubFile);
720     if (contents.fail())
721     {
722         std::cerr << "Can't open \"" << scrubFile << "\"\n";
723         exit(1);
724     }
725     std::string line;
726     while (std::getline(contents, line))
727     {
728         if (!line.empty())
729         {
730             pattern.append(line + "|");
731         }
732     }
733     try
734     {
735         std::regex scrubRegex(pattern, std::regex::icase);
736         return scrubRegex;
737     }
738     catch (std::regex_error& e)
739     {
740         if (e.code() == std::regex_constants::error_collate)
741             std::cerr << "Invalid collating element request\n";
742         else if (e.code() == std::regex_constants::error_ctype)
743             std::cerr << "Invalid character class\n";
744         else if (e.code() == std::regex_constants::error_escape)
745             std::cerr << "Invalid escape character or trailing escape\n";
746         else if (e.code() == std::regex_constants::error_backref)
747             std::cerr << "Invalid back reference\n";
748         else if (e.code() == std::regex_constants::error_brack)
749             std::cerr << "Mismatched bracket ([ or ])\n";
750         else if (e.code() == std::regex_constants::error_paren)
751         {
752             // to catch return code error_badrepeat when error_paren is retured
753             // instead
754             size_t pos = pattern.find_first_of("*+?{");
755             while (pos != std::string::npos)
756             {
757                 if (pos == 0 || pattern.substr(pos - 1, 1) == "|")
758                 {
759                     std::cerr
760                         << "A repetition character (*, ?, +, or {) was not "
761                            "preceded by a valid regular expression\n";
762                     exit(1);
763                 }
764                 pos = pattern.find_first_of("*+?{", pos + 1);
765             }
766             std::cerr << "Mismatched parentheses (( or ))\n";
767         }
768         else if (e.code() == std::regex_constants::error_brace)
769             std::cerr << "Mismatched brace ({ or })\n";
770         else if (e.code() == std::regex_constants::error_badbrace)
771             std::cerr << "Invalid range inside a { }\n";
772         else if (e.code() == std::regex_constants::error_range)
773             std::cerr << "Invalid character range (e.g., [z-a])\n";
774         else if (e.code() == std::regex_constants::error_space)
775             std::cerr << "Insufficient memory to handle regular expression\n";
776         else if (e.code() == std::regex_constants::error_badrepeat)
777             std::cerr << "A repetition character (*, ?, +, or {) was not "
778                          "preceded by a valid regular expression\n";
779         else if (e.code() == std::regex_constants::error_complexity)
780             std::cerr << "The requested match is too complex\n";
781         else if (e.code() == std::regex_constants::error_stack)
782             std::cerr << "Insufficient memory to evaluate a match\n";
783         exit(1);
784     }
785 }
786 
787 static void exitWithError(const std::string& help, const char* err)
788 {
789     std::cerr << "ERROR: " << err << std::endl << help << std::endl;
790     exit(-1);
791 }
792 
793 int main(int argc, char** argv)
794 {
795     CLI::App app{"OpenBMC PEL Tool"};
796     std::string fileName;
797     std::string idPEL;
798     std::string bmcId;
799     std::string idToDelete;
800     std::string scrubFile;
801     std::optional<std::regex> scrubRegex;
802     bool listPEL = false;
803     bool listPELDescOrd = false;
804     bool hidden = false;
805     bool includeInfo = false;
806     bool critSysTerm = false;
807     bool deleteAll = false;
808     bool showPELCount = false;
809     bool fullPEL = false;
810     bool hexDump = false;
811 
812     app.set_help_flag("--help", "Print this help message and exit");
813     app.add_option("--file", fileName, "Display a PEL using its Raw PEL file");
814     app.add_option("-i, --id", idPEL, "Display a PEL based on its ID");
815     app.add_option("--bmc-id", bmcId,
816                    "Display a PEL based on its BMC Event ID");
817     app.add_flag("-a", fullPEL, "Display all PELs");
818     app.add_flag("-l", listPEL, "List PELs");
819     app.add_flag("-n", showPELCount, "Show number of PELs");
820     app.add_flag("-r", listPELDescOrd, "Reverse order of output");
821     app.add_flag("-h", hidden, "Include hidden PELs");
822     app.add_flag("-f,--info", includeInfo, "Include informational PELs");
823     app.add_flag("-t, --termination", critSysTerm,
824                  "List only critical system terminating PELs");
825     app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID");
826     app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs");
827     app.add_option("-s, --scrub", scrubFile,
828                    "File containing SRC regular expressions to ignore");
829     app.add_flag("-x", hexDump, "Display PEL(s) in hexdump instead of JSON");
830 
831     CLI11_PARSE(app, argc, argv);
832 
833     if (!fileName.empty())
834     {
835         std::vector<uint8_t> data = getFileData(fileName);
836         if (!data.empty())
837         {
838             PEL pel{data};
839             if (hexDump)
840             {
841                 std::string dstr =
842                     dumpHex(std::data(pel.data()), pel.size(), 0, false);
843                 std::cout << dstr << std::endl;
844             }
845             else
846             {
847                 auto plugins = getPlugins();
848                 pel.toJSON(registry, plugins);
849             }
850         }
851         else
852         {
853             exitWithError(app.help("", CLI::AppFormatMode::All),
854                           "Raw PEL file can't be read.");
855         }
856     }
857     else if (!idPEL.empty())
858     {
859         callFunctionOnPEL(idPEL, displayPEL, false, hexDump);
860     }
861     else if (!bmcId.empty())
862     {
863         callFunctionOnPEL(bmcId, displayPEL, true, hexDump);
864     }
865     else if (fullPEL || listPEL)
866     {
867         if (!scrubFile.empty())
868         {
869             scrubRegex = genRegex(scrubFile);
870         }
871         printPELs(listPELDescOrd, hidden, includeInfo, critSysTerm, fullPEL,
872                   scrubRegex, hexDump);
873     }
874     else if (showPELCount)
875     {
876         if (!scrubFile.empty())
877         {
878             scrubRegex = genRegex(scrubFile);
879         }
880         printPELCount(hidden, includeInfo, critSysTerm, scrubRegex);
881     }
882     else if (!idToDelete.empty())
883     {
884         deletePEL(idToDelete);
885     }
886     else if (deleteAll)
887     {
888         deleteAllPELs();
889     }
890     else
891     {
892         std::cout << app.help("", CLI::AppFormatMode::All) << std::endl;
893     }
894     Py_Finalize();
895     return 0;
896 }
897