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() /
44                            message::registryFileName);
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] fullPEL - Boolean to print full JSON representation of PEL
213  * @param[in] foundPEL - Boolean to check if any PEL is present
214  * @param[in] scrubRegex - SRC regex object
215  * @return std::string - JSON string of PEL entry (empty if fullPEL is true)
216  */
217 template <typename T>
218 std::string genPELJSON(T itr, bool hidden, bool fullPEL, bool& foundPEL,
219                        const std::optional<std::regex>& scrubRegex)
220 {
221     std::size_t found;
222     std::string val;
223     char tmpValStr[50];
224     std::string listStr;
225     char name[50];
226     sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", itr.second.yearMSB,
227             itr.second.yearLSB, itr.second.month, itr.second.day,
228             itr.second.hour, itr.second.minutes, itr.second.seconds,
229             itr.second.hundredths, itr.first);
230     std::string fileName(name);
231     fileName = EXTENSION_PERSIST_DIR "/pels/logs/" + fileName;
232     try
233     {
234         std::vector<uint8_t> data = getFileData(fileName);
235         if (data.empty())
236         {
237             log<level::ERR>("Empty PEL file",
238                             entry("FILENAME=%s", fileName.c_str()));
239             return listStr;
240         }
241         PEL pel{data};
242         if (!pel.valid())
243         {
244             return listStr;
245         }
246         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
247         if (!hidden && actionFlags.test(hiddenFlagBit))
248         {
249             return listStr;
250         }
251         if (pel.primarySRC() && scrubRegex)
252         {
253             val = pel.primarySRC().value()->asciiString();
254             if (std::regex_search(trimEnd(val), scrubRegex.value(),
255                                   std::regex_constants::match_not_null))
256             {
257                 return listStr;
258             }
259         }
260         if (fullPEL)
261         {
262             if (!foundPEL)
263             {
264                 std::cout << "[\n";
265                 foundPEL = true;
266             }
267             else
268             {
269                 std::cout << ",\n\n";
270             }
271             pel.toJSON(registry);
272         }
273         else
274         {
275             // id
276             listStr += "\t\"" +
277                        getNumberString("0x%X", pel.privateHeader().id()) +
278                        "\": {\n";
279             // ASCII
280             if (pel.primarySRC())
281             {
282                 val = pel.primarySRC().value()->asciiString();
283                 listStr += "\t\t\"SRC\": \"" + trimEnd(val) + "\",\n";
284                 // Registry message
285                 auto regVal = pel.primarySRC().value()->getErrorDetails(
286                     registry, DetailLevel::message, true);
287                 if (regVal)
288                 {
289                     val = regVal.value();
290                     listStr += "\t\t\"Message\": \"" + val + "\",\n";
291                 }
292             }
293             else
294             {
295                 listStr += "\t\t\"SRC\": \"No SRC\",\n";
296             }
297             // platformid
298             listStr += "\t\t\"PLID\": \"" +
299                        getNumberString("0x%X", pel.privateHeader().plid()) +
300                        "\",\n";
301             // creatorid
302             std::string creatorID =
303                 getNumberString("%c", pel.privateHeader().creatorID());
304             val = pv::creatorIDs.count(creatorID) ? pv::creatorIDs.at(creatorID)
305                                                   : "Unknown Creator ID";
306             listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
307             // subsytem
308             std::string subsystem = pv::getValue(pel.userHeader().subsystem(),
309                                                  pel_values::subsystemValues);
310             listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
311             // commit time
312             sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
313                     pel.privateHeader().commitTimestamp().month,
314                     pel.privateHeader().commitTimestamp().day,
315                     pel.privateHeader().commitTimestamp().yearMSB,
316                     pel.privateHeader().commitTimestamp().yearLSB,
317                     pel.privateHeader().commitTimestamp().hour,
318                     pel.privateHeader().commitTimestamp().minutes,
319                     pel.privateHeader().commitTimestamp().seconds);
320             val = std::string(tmpValStr);
321             listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
322             // severity
323             std::string severity = pv::getValue(pel.userHeader().severity(),
324                                                 pel_values::severityValues);
325             listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
326             // compID
327             listStr += "\t\t\"CompID\": \"" +
328                        getNumberString(
329                            "0x%X", pel.privateHeader().header().componentID) +
330                        "\",\n ";
331             found = listStr.rfind(",");
332             if (found != std::string::npos)
333             {
334                 listStr.replace(found, 1, "");
335                 listStr += "\t},\n";
336             }
337             foundPEL = true;
338         }
339     }
340     catch (std::exception& e)
341     {
342         log<level::ERR>("Hit exception while reading PEL File",
343                         entry("FILENAME=%s", fileName.c_str()),
344                         entry("ERROR=%s", e.what()));
345     }
346     return listStr;
347 }
348 
349 /**
350  * @brief Print a list of PELs or a JSON array of PELs
351  * @param[in] order - Boolean to print in reverse orser
352  * @param[in] hidden - Boolean to include hidden PELs
353  * @param[in] fullPEL - Boolean to print full PEL into a JSON array
354  * @param[in] scrubRegex - SRC regex object
355  */
356 void printPELs(bool order, bool hidden, bool fullPEL,
357                const std::optional<std::regex>& scrubRegex)
358 {
359     std::string listStr;
360     std::map<uint32_t, BCDTime> PELs;
361     listStr = "{\n";
362     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
363          it != fs::directory_iterator(); ++it)
364     {
365         if (!fs::is_regular_file((*it).path()))
366         {
367             continue;
368         }
369         else
370         {
371             PELs.emplace(fileNameToPELId((*it).path().filename()),
372                          fileNameToTimestamp((*it).path().filename()));
373         }
374     }
375     bool foundPEL = false;
376     auto buildJSON = [&listStr, &hidden, &fullPEL, &foundPEL,
377                       &scrubRegex](const auto& i) {
378         listStr += genPELJSON(i, hidden, fullPEL, foundPEL, scrubRegex);
379     };
380     if (order)
381     {
382         std::for_each(PELs.rbegin(), PELs.rend(), buildJSON);
383     }
384     else
385     {
386         std::for_each(PELs.begin(), PELs.end(), buildJSON);
387     }
388 
389     if (foundPEL)
390     {
391         if (fullPEL)
392         {
393             std::cout << "]" << std::endl;
394         }
395         else
396         {
397             std::size_t found;
398             found = listStr.rfind(",");
399             if (found != std::string::npos)
400             {
401                 listStr.replace(found, 1, "");
402                 listStr += "}\n";
403                 printf("%s", listStr.c_str());
404             }
405         }
406     }
407     else
408     {
409         std::string emptyJSON = fullPEL ? "[]" : "{}";
410         std::cout << emptyJSON << std::endl;
411     }
412 }
413 
414 /**
415  * @brief Calls the function passed in on the PEL with the ID
416  *        passed in.
417  *
418  * @param[in] id - The string version of the PEL ID, either with or
419  *                 without the 0x prefix.
420  * @param[in] func - The std::function<void(const PEL&)> function to run.
421  */
422 void callFunctionOnPEL(const std::string& id, const PELFunc& func)
423 {
424     std::string pelID{id};
425     std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
426 
427     if (pelID.find("0X") == 0)
428     {
429         pelID.erase(0, 2);
430     }
431 
432     bool found = false;
433 
434     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
435          it != fs::directory_iterator(); ++it)
436     {
437         // The PEL ID is part of the filename, so use that to find the PEL.
438 
439         if (!fs::is_regular_file((*it).path()))
440         {
441             continue;
442         }
443 
444         if (ends_with((*it).path(), pelID))
445         {
446             found = true;
447 
448             auto data = getFileData((*it).path());
449             if (!data.empty())
450             {
451                 PEL pel{data};
452 
453                 try
454                 {
455                     func(pel);
456                 }
457                 catch (std::exception& e)
458                 {
459                     std::cerr
460                         << " Internal function threw an exception: " << e.what()
461                         << "\n";
462                     exit(1);
463                 }
464             }
465             else
466             {
467                 std::cerr << "Could not read PEL file\n";
468                 exit(1);
469             }
470             break;
471         }
472     }
473 
474     if (!found)
475     {
476         std::cerr << "PEL not found\n";
477         exit(1);
478     }
479 }
480 
481 /**
482  * @brief Delete a PEL by deleting its corresponding event log.
483  *
484  * @param[in] pel - The PEL to delete
485  */
486 void deletePEL(const PEL& pel)
487 {
488     std::string path{object_path::logEntry};
489     path += std::to_string(pel.obmcLogID());
490 
491     try
492     {
493         auto bus = sdbusplus::bus::new_default();
494         auto method = bus.new_method_call(service::logging, path.c_str(),
495                                           interface::deleteObj, "Delete");
496         auto reply = bus.call(method);
497     }
498     catch (const SdBusError& e)
499     {
500         std::cerr << "D-Bus call to delete event log " << pel.obmcLogID()
501                   << " failed: " << e.what() << "\n";
502         exit(1);
503     }
504 }
505 
506 /**
507  * @brief Delete all PELs by deleting all event logs.
508  */
509 void deleteAllPELs()
510 {
511     try
512     {
513         // This may move to an audit log some day
514         log<level::INFO>("peltool deleting all event logs");
515 
516         auto bus = sdbusplus::bus::new_default();
517         auto method =
518             bus.new_method_call(service::logging, object_path::logging,
519                                 interface::deleteAll, "DeleteAll");
520         auto reply = bus.call(method);
521     }
522     catch (const SdBusError& e)
523     {
524         std::cerr << "D-Bus call to delete all event logs failed: " << e.what()
525                   << "\n";
526         exit(1);
527     }
528 }
529 
530 /**
531  * @brief Display a single PEL
532  *
533  * @param[in] pel - the PEL to display
534  */
535 void displayPEL(const PEL& pel)
536 {
537     if (pel.valid())
538     {
539         pel.toJSON(registry);
540     }
541     else
542     {
543         std::cerr << "PEL was malformed\n";
544         exit(1);
545     }
546 }
547 
548 /**
549  * @brief Print number of PELs
550  * @param[in] hidden - Bool to include hidden logs
551  * @param[in] scrubRegex - SRC regex object
552  */
553 void printPELCount(bool hidden, const std::optional<std::regex>& scrubRegex)
554 {
555     std::size_t count = 0;
556     for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
557          it != fs::directory_iterator(); ++it)
558     {
559         if (!fs::is_regular_file((*it).path()))
560         {
561             continue;
562         }
563         std::vector<uint8_t> data = getFileData((*it).path());
564         if (data.empty())
565         {
566             continue;
567         }
568         PEL pel{data};
569         if (!pel.valid())
570         {
571             continue;
572         }
573         std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
574         if (!hidden && actionFlags.test(hiddenFlagBit))
575         {
576             continue;
577         }
578         if (pel.primarySRC() && scrubRegex)
579         {
580             std::string val = pel.primarySRC().value()->asciiString();
581             if (std::regex_search(trimEnd(val), scrubRegex.value(),
582                                   std::regex_constants::match_not_null))
583             {
584                 continue;
585             }
586         }
587         count++;
588     }
589     std::cout << "{\n"
590               << "    \"Number of PELs found\": "
591               << getNumberString("%d", count) << "\n}\n";
592 }
593 
594 /**
595  * @brief Generate regex pattern object from file contents
596  * @param[in] scrubFile - File containing regex pattern
597  * @return std::regex - SRC regex object
598  */
599 std::regex genRegex(std::string& scrubFile)
600 {
601     std::string pattern;
602     std::ifstream contents(scrubFile);
603     if (contents.fail())
604     {
605         std::cerr << "Can't open \"" << scrubFile << "\"\n";
606         exit(1);
607     }
608     std::string line;
609     while (std::getline(contents, line))
610     {
611         if (!line.empty())
612         {
613             pattern.append(line + "|");
614         }
615     }
616     try
617     {
618         std::regex scrubRegex(pattern, std::regex::icase);
619         return scrubRegex;
620     }
621     catch (std::regex_error& e)
622     {
623         if (e.code() == std::regex_constants::error_collate)
624             std::cerr << "Invalid collating element request\n";
625         else if (e.code() == std::regex_constants::error_ctype)
626             std::cerr << "Invalid character class\n";
627         else if (e.code() == std::regex_constants::error_escape)
628             std::cerr << "Invalid escape character or trailing escape\n";
629         else if (e.code() == std::regex_constants::error_backref)
630             std::cerr << "Invalid back reference\n";
631         else if (e.code() == std::regex_constants::error_brack)
632             std::cerr << "Mismatched bracket ([ or ])\n";
633         else if (e.code() == std::regex_constants::error_paren)
634         {
635             // to catch return code error_badrepeat when error_paren is retured
636             // instead
637             size_t pos = pattern.find_first_of("*+?{");
638             while (pos != std::string::npos)
639             {
640                 if (pos == 0 || pattern.substr(pos - 1, 1) == "|")
641                 {
642                     std::cerr
643                         << "A repetition character (*, ?, +, or {) was not "
644                            "preceded by a valid regular expression\n";
645                     exit(1);
646                 }
647                 pos = pattern.find_first_of("*+?{", pos + 1);
648             }
649             std::cerr << "Mismatched parentheses (( or ))\n";
650         }
651         else if (e.code() == std::regex_constants::error_brace)
652             std::cerr << "Mismatched brace ({ or })\n";
653         else if (e.code() == std::regex_constants::error_badbrace)
654             std::cerr << "Invalid range inside a { }\n";
655         else if (e.code() == std::regex_constants::error_range)
656             std::cerr << "Invalid character range (e.g., [z-a])\n";
657         else if (e.code() == std::regex_constants::error_space)
658             std::cerr << "Insufficient memory to handle regular expression\n";
659         else if (e.code() == std::regex_constants::error_badrepeat)
660             std::cerr << "A repetition character (*, ?, +, or {) was not "
661                          "preceded by a valid regular expression\n";
662         else if (e.code() == std::regex_constants::error_complexity)
663             std::cerr << "The requested match is too complex\n";
664         else if (e.code() == std::regex_constants::error_stack)
665             std::cerr << "Insufficient memory to evaluate a match\n";
666         exit(1);
667     }
668 }
669 
670 static void exitWithError(const std::string& help, const char* err)
671 {
672     std::cerr << "ERROR: " << err << std::endl << help << std::endl;
673     exit(-1);
674 }
675 
676 int main(int argc, char** argv)
677 {
678     CLI::App app{"OpenBMC PEL Tool"};
679     std::string fileName;
680     std::string idPEL;
681     std::string idToDelete;
682     std::string scrubFile;
683     std::optional<std::regex> scrubRegex;
684     bool listPEL = false;
685     bool listPELDescOrd = false;
686     bool hidden = false;
687     bool deleteAll = false;
688     bool showPELCount = false;
689     bool fullPEL = false;
690 
691     app.set_help_flag("--help", "Print this help message and exit");
692     app.add_option("-f,--file", fileName,
693                    "Display a PEL using its Raw PEL file");
694     app.add_option("-i, --id", idPEL, "Display a PEL based on its ID");
695     app.add_flag("-a", fullPEL, "Display all PELs");
696     app.add_flag("-l", listPEL, "List PELs");
697     app.add_flag("-n", showPELCount, "Show number of PELs");
698     app.add_flag("-r", listPELDescOrd, "Reverse order of output");
699     app.add_flag("-h", hidden, "Include hidden PELs");
700     app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID");
701     app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs");
702     app.add_option("-s, --scrub", scrubFile,
703                    "File containing SRC regular expressions to ignore");
704 
705     CLI11_PARSE(app, argc, argv);
706 
707     if (!fileName.empty())
708     {
709         std::vector<uint8_t> data = getFileData(fileName);
710         if (!data.empty())
711         {
712             PEL pel{data};
713             pel.toJSON(registry);
714         }
715         else
716         {
717             exitWithError(app.help("", CLI::AppFormatMode::All),
718                           "Raw PEL file can't be read.");
719         }
720     }
721     else if (!idPEL.empty())
722     {
723         callFunctionOnPEL(idPEL, displayPEL);
724     }
725     else if (fullPEL || listPEL)
726     {
727         if (!scrubFile.empty())
728         {
729             scrubRegex = genRegex(scrubFile);
730         }
731         printPELs(listPELDescOrd, hidden, fullPEL, scrubRegex);
732     }
733     else if (showPELCount)
734     {
735         if (!scrubFile.empty())
736         {
737             scrubRegex = genRegex(scrubFile);
738         }
739         printPELCount(hidden, scrubRegex);
740     }
741     else if (!idToDelete.empty())
742     {
743         callFunctionOnPEL(idToDelete, deletePEL);
744     }
745     else if (deleteAll)
746     {
747         deleteAllPELs();
748     }
749     else
750     {
751         std::cout << app.help("", CLI::AppFormatMode::All) << std::endl;
752     }
753     return 0;
754 }
755