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