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