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