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