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