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