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