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