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