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