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