blob: 04dae179726eb045d19361cd0dfd4c4a75afee77 [file] [log] [blame]
Matt Spinler711d51d2019-11-06 09:36:51 -06001/**
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 */
Aatir7b291ec2019-11-19 10:37:37 -060016#include "config.h"
17
18#include "../bcd_time.hpp"
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +080019#include "../paths.hpp"
Aatir186ce8c2019-10-20 15:13:39 -050020#include "../pel.hpp"
Aatir7b291ec2019-11-19 10:37:37 -060021#include "../pel_types.hpp"
22#include "../pel_values.hpp"
Aatir186ce8c2019-10-20 15:13:39 -050023
24#include <CLI/CLI.hpp>
Aatir7b291ec2019-11-19 10:37:37 -060025#include <bitset>
Aatir186ce8c2019-10-20 15:13:39 -050026#include <iostream>
Aatir7b291ec2019-11-19 10:37:37 -060027#include <phosphor-logging/log.hpp>
28#include <regex>
Aatir186ce8c2019-10-20 15:13:39 -050029#include <string>
Aatir7b291ec2019-11-19 10:37:37 -060030#include <xyz/openbmc_project/Common/File/error.hpp>
Aatir186ce8c2019-10-20 15:13:39 -050031
Aatir7b291ec2019-11-19 10:37:37 -060032namespace fs = std::filesystem;
Aatir186ce8c2019-10-20 15:13:39 -050033using namespace phosphor::logging;
34using namespace openpower::pels;
Matt Spinler27de6f32020-02-21 08:35:57 -060035using sdbusplus::exception::SdBusError;
Aatir7b291ec2019-11-19 10:37:37 -060036namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error;
37namespace message = openpower::pels::message;
38namespace pv = openpower::pels::pel_values;
Aatir186ce8c2019-10-20 15:13:39 -050039
Matt Spinler27de6f32020-02-21 08:35:57 -060040using PELFunc = std::function<void(const PEL&)>;
41
42namespace service
43{
44constexpr auto logging = "xyz.openbmc_project.Logging";
45} // namespace service
46
47namespace interface
48{
49constexpr auto deleteObj = "xyz.openbmc_project.Object.Delete";
50constexpr auto deleteAll = "xyz.openbmc_project.Collection.DeleteAll";
51} // namespace interface
52
53namespace object_path
54{
55constexpr auto logEntry = "/xyz/openbmc_project/logging/entry/";
56constexpr auto logging = "/xyz/openbmc_project/logging";
57} // namespace object_path
58
Aatire340c132019-12-09 14:19:29 -060059/**
Aatir37822f62019-12-10 14:40:27 -060060 * @brief helper function to get PEL commit timestamp from file name
61 * @retrun BCDTime - PEL commit timestamp
62 * @param[in] std::string - file name
63 */
64BCDTime fileNameToTimestamp(const std::string& fileName)
65{
66 std::string token = fileName.substr(0, fileName.find("_"));
67 int i = 0;
68 BCDTime tmp;
69 if (token.length() >= 14)
70 {
71 try
72 {
73 tmp.yearMSB = std::stoi(token.substr(i, 2), 0, 16);
74 }
75 catch (std::exception& err)
76 {
77 std::cout << "Conversion failure: " << err.what() << std::endl;
78 }
79 i += 2;
80 try
81 {
82 tmp.yearLSB = std::stoi(token.substr(i, 2), 0, 16);
83 }
84 catch (std::exception& err)
85 {
86 std::cout << "Conversion failure: " << err.what() << std::endl;
87 }
88 i += 2;
89 try
90 {
91 tmp.month = std::stoi(token.substr(i, 2), 0, 16);
92 }
93 catch (std::exception& err)
94 {
95 std::cout << "Conversion failure: " << err.what() << std::endl;
96 }
97 i += 2;
98 try
99 {
100 tmp.day = std::stoi(token.substr(i, 2), 0, 16);
101 }
102 catch (std::exception& err)
103 {
104 std::cout << "Conversion failure: " << err.what() << std::endl;
105 }
106 i += 2;
107 try
108 {
109 tmp.hour = std::stoi(token.substr(i, 2), 0, 16);
110 }
111 catch (std::exception& err)
112 {
113 std::cout << "Conversion failure: " << err.what() << std::endl;
114 }
115 i += 2;
116 try
117 {
118 tmp.minutes = std::stoi(token.substr(i, 2), 0, 16);
119 }
120 catch (std::exception& err)
121 {
122 std::cout << "Conversion failure: " << err.what() << std::endl;
123 }
124 i += 2;
125 try
126 {
127 tmp.seconds = std::stoi(token.substr(i, 2), 0, 16);
128 }
129 catch (std::exception& err)
130 {
131 std::cout << "Conversion failure: " << err.what() << std::endl;
132 }
133 i += 2;
134 try
135 {
136 tmp.hundredths = std::stoi(token.substr(i, 2), 0, 16);
137 }
138 catch (std::exception& err)
139 {
140 std::cout << "Conversion failure: " << err.what() << std::endl;
141 }
142 }
143 return tmp;
144}
145
146/**
147 * @brief helper function to get PEL id from file name
148 * @retrun uint32_t - PEL id
149 * @param[in] std::string - file name
150 */
151uint32_t fileNameToPELId(const std::string& fileName)
152{
153 uint32_t num = 0;
154 try
155 {
156 num = std::stoi(fileName.substr(fileName.find("_") + 1), 0, 16);
157 }
158 catch (std::exception& err)
159 {
160 std::cout << "Conversion failure: " << err.what() << std::endl;
161 }
162 return num;
163}
164
165/**
Aatire340c132019-12-09 14:19:29 -0600166 * @brief helper function to check string suffix
167 * @retrun bool - true with suffix matches
168 * @param[in] std::string - string to check for suffix
169 * @param[in] std::string - suffix string
170 */
171bool ends_with(const std::string& str, const std::string& end)
172{
173 size_t slen = str.size(), elen = end.size();
174 if (slen < elen)
175 return false;
176 while (elen)
177 {
178 if (str[--slen] != end[--elen])
179 return false;
180 }
181 return true;
182}
183
Aatir37822f62019-12-10 14:40:27 -0600184/**
185 * @brief get data form raw PEL file.
186 * @param[in] std::string Name of file with raw PEL
187 * @return std::vector<uint8_t> char vector read from raw PEL file.
188 */
Aatirbad5f8a2019-12-10 15:27:16 -0600189std::vector<uint8_t> getFileData(const std::string& name)
Aatir37822f62019-12-10 14:40:27 -0600190{
191 std::ifstream file(name, std::ifstream::in);
192 if (file.good())
193 {
194 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file),
195 std::istreambuf_iterator<char>()};
196 return data;
197 }
198 else
199 {
Aatir37822f62019-12-10 14:40:27 -0600200 return {};
201 }
202}
203
Aatir7b291ec2019-11-19 10:37:37 -0600204template <typename T>
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800205std::string genPELJSON(T itr, bool hidden, message::Registry& registry)
Aatir7b291ec2019-11-19 10:37:37 -0600206{
207 std::size_t found;
208 std::string val;
209 char tmpValStr[50];
210 std::string listStr;
211 char name[50];
212 sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", itr.second.yearMSB,
213 itr.second.yearLSB, itr.second.month, itr.second.day,
214 itr.second.hour, itr.second.minutes, itr.second.seconds,
215 itr.second.hundredths, itr.first);
216 std::string fileName(name);
217 fileName = EXTENSION_PERSIST_DIR "/pels/logs/" + fileName;
218 try
219 {
Aatir37822f62019-12-10 14:40:27 -0600220 std::vector<uint8_t> data = getFileData(fileName);
Aatir37822f62019-12-10 14:40:27 -0600221 if (!data.empty())
222 {
Aatir37822f62019-12-10 14:40:27 -0600223 PEL pel{data};
224 std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
225 if (pel.valid() && (hidden || !actionFlags.test(hiddenFlagBit)))
Aatir7b291ec2019-11-19 10:37:37 -0600226 {
Aatir37822f62019-12-10 14:40:27 -0600227 // id
228 sprintf(tmpValStr, "0x%X", pel.privateHeader().id());
229 val = std::string(tmpValStr);
230 listStr += "\t\"" + val + "\": {\n";
231 // ASCII
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800232 if (pel.primarySRC())
233 {
234 val = pel.primarySRC().value()->asciiString();
235 listStr += "\t\t\"SRC\": \"" +
236 val.substr(0, val.find(0x20)) + "\",\n";
237 // Registry message
238 auto regVal = pel.primarySRC().value()->getErrorDetails(
239 registry, DetailLevel::message, true);
240 if (regVal)
241 {
242 val = regVal.value();
243 listStr += "\t\t\"Message\": \"" + val + "\",\n";
244 }
245 }
246 else
247 {
248 listStr += "\t\t\"SRC\": \"No SRC\",\n";
249 }
Aatir37822f62019-12-10 14:40:27 -0600250 // platformid
251 sprintf(tmpValStr, "0x%X", pel.privateHeader().plid());
252 val = std::string(tmpValStr);
253 listStr += "\t\t\"PLID\": \"" + val + "\",\n";
254 // creatorid
255 sprintf(tmpValStr, "%c", pel.privateHeader().creatorID());
256 std::string creatorID(tmpValStr);
257 val = pv::creatorIDs.count(creatorID)
258 ? pv::creatorIDs.at(creatorID)
259 : "Unknown Creator ID";
260 listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
261 // subsytem
262 std::string subsystem = pv::getValue(
263 pel.userHeader().subsystem(), pel_values::subsystemValues);
264 listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
265 // commit time
Harisuddin Mohamed Isa160c51c2020-02-13 23:42:20 +0800266 sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
Aatir37822f62019-12-10 14:40:27 -0600267 pel.privateHeader().commitTimestamp().month,
268 pel.privateHeader().commitTimestamp().day,
269 pel.privateHeader().commitTimestamp().yearMSB,
270 pel.privateHeader().commitTimestamp().yearLSB,
271 pel.privateHeader().commitTimestamp().hour,
272 pel.privateHeader().commitTimestamp().minutes,
273 pel.privateHeader().commitTimestamp().seconds);
274 val = std::string(tmpValStr);
275 listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
276 // severity
277 std::string severity = pv::getValue(pel.userHeader().severity(),
278 pel_values::severityValues);
279 listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
280 // compID
281 sprintf(tmpValStr, "0x%X",
282 pel.privateHeader().header().componentID);
283 val = std::string(tmpValStr);
284 listStr += "\t\t\"CompID\": \"" + val + "\",\n ";
285
286 found = listStr.rfind(",");
287 if (found != std::string::npos)
288 {
289 listStr.replace(found, 1, "");
290 listStr += "\t}, \n";
291 }
Aatir7b291ec2019-11-19 10:37:37 -0600292 }
293 }
Aatir37822f62019-12-10 14:40:27 -0600294 else
295 {
296 log<level::ERR>("Empty PEL file",
297 entry("FILENAME=%s", fileName.c_str()),
298 entry("ERROR=%s", "Empty PEL file"));
299 }
Aatir7b291ec2019-11-19 10:37:37 -0600300 }
301 catch (std::exception& e)
302 {
303 log<level::ERR>("Hit exception while reading PEL File",
304 entry("FILENAME=%s", fileName.c_str()),
305 entry("ERROR=%s", e.what()));
306 }
307 return listStr;
308}
309/**
310 * @brief Print a list of PELs
311 */
312void printList(bool order, bool hidden)
313{
314 std::string listStr;
315 std::map<uint32_t, BCDTime> PELs;
316 std::size_t found;
317 listStr = "{\n";
318 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
319 it != fs::directory_iterator(); ++it)
320 {
321 if (!fs::is_regular_file((*it).path()))
322 {
323 continue;
324 }
Aatir37822f62019-12-10 14:40:27 -0600325 else
Aatir7b291ec2019-11-19 10:37:37 -0600326 {
Aatir37822f62019-12-10 14:40:27 -0600327 PELs.emplace(fileNameToPELId((*it).path().filename()),
328 fileNameToTimestamp((*it).path().filename()));
Aatir7b291ec2019-11-19 10:37:37 -0600329 }
330 }
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800331 message::Registry registry(getMessageRegistryPath() /
332 message::registryFileName);
333 auto buildJSON = [&listStr, &hidden, &registry](const auto& i) {
334 listStr += genPELJSON(i, hidden, registry);
Aatir37822f62019-12-10 14:40:27 -0600335 };
Aatir7b291ec2019-11-19 10:37:37 -0600336 if (order)
337 {
338 std::for_each(PELs.rbegin(), PELs.rend(), buildJSON);
339 }
340 else
341 {
342 std::for_each(PELs.begin(), PELs.end(), buildJSON);
343 }
344
345 found = listStr.rfind(",");
346 if (found != std::string::npos)
347 {
348 listStr.replace(found, 1, "");
349 listStr += "\n}\n";
350 printf("%s", listStr.c_str());
351 }
352}
Aatir186ce8c2019-10-20 15:13:39 -0500353
Matt Spinler27de6f32020-02-21 08:35:57 -0600354/**
355 * @brief Calls the function passed in on the PEL with the ID
356 * passed in.
357 *
358 * @param[in] id - The string version of the PEL ID, either with or
359 * without the 0x prefix.
360 * @param[in] func - The std::function<void(const PEL&)> function to run.
361 */
362void callFunctionOnPEL(const std::string& id, const PELFunc& func)
363{
364 std::string pelID{id};
365 std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
366
367 if (pelID.find("0X") == 0)
368 {
369 pelID.erase(0, 2);
370 }
371
372 bool found = false;
373
374 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
375 it != fs::directory_iterator(); ++it)
376 {
377 // The PEL ID is part of the filename, so use that to find the PEL.
378
379 if (!fs::is_regular_file((*it).path()))
380 {
381 continue;
382 }
383
384 if (ends_with((*it).path(), pelID))
385 {
386 found = true;
387
388 auto data = getFileData((*it).path());
389 if (!data.empty())
390 {
391 PEL pel{data};
392
393 try
394 {
395 func(pel);
396 }
397 catch (std::exception& e)
398 {
399 std::cerr
400 << " Internal function threw an exception: " << e.what()
401 << "\n";
402 exit(1);
403 }
404 }
405 else
406 {
407 std::cerr << "Could not read PEL file\n";
408 exit(1);
409 }
410 break;
411 }
412 }
413
414 if (!found)
415 {
416 std::cerr << "PEL not found\n";
417 exit(1);
418 }
419}
420
421/**
422 * @brief Delete a PEL by deleting its corresponding event log.
423 *
424 * @param[in] pel - The PEL to delete
425 */
426void deletePEL(const PEL& pel)
427{
428 std::string path{object_path::logEntry};
429 path += std::to_string(pel.obmcLogID());
430
431 try
432 {
433 auto bus = sdbusplus::bus::new_default();
434 auto method = bus.new_method_call(service::logging, path.c_str(),
435 interface::deleteObj, "Delete");
436 auto reply = bus.call(method);
437 }
438 catch (const SdBusError& e)
439 {
440 std::cerr << "D-Bus call to delete event log " << pel.obmcLogID()
441 << " failed: " << e.what() << "\n";
442 exit(1);
443 }
444}
445
446/**
447 * @brief Delete all PELs by deleting all event logs.
448 */
449void deleteAllPELs()
450{
451 try
452 {
453 // This may move to an audit log some day
454 log<level::INFO>("peltool deleting all event logs");
455
456 auto bus = sdbusplus::bus::new_default();
457 auto method =
458 bus.new_method_call(service::logging, object_path::logging,
459 interface::deleteAll, "DeleteAll");
460 auto reply = bus.call(method);
461 }
462 catch (const SdBusError& e)
463 {
464 std::cerr << "D-Bus call to delete all event logs failed: " << e.what()
465 << "\n";
466 exit(1);
467 }
468}
469
Matt Spinler1b420bc2020-02-21 08:54:25 -0600470/**
471 * @brief Display a single PEL
472 *
473 * @param[in] pel - the PEL to display
474 */
475void displayPEL(const PEL& pel)
476{
477 if (pel.valid())
478 {
479 pel.toJSON();
480 }
481 else
482 {
483 std::cerr << "PEL was malformed\n";
484 exit(1);
485 }
486}
487
Aatir186ce8c2019-10-20 15:13:39 -0500488static void exitWithError(const std::string& help, const char* err)
489{
490 std::cerr << "ERROR: " << err << std::endl << help << std::endl;
491 exit(-1);
492}
493
494int main(int argc, char** argv)
495{
496 CLI::App app{"OpenBMC PEL Tool"};
497 std::string fileName;
Aatire340c132019-12-09 14:19:29 -0600498 std::string idPEL;
Matt Spinler27de6f32020-02-21 08:35:57 -0600499 std::string idToDelete;
Aatire340c132019-12-09 14:19:29 -0600500 bool listPEL = false;
501 bool listPELDescOrd = false;
502 bool listPELShowHidden = false;
Matt Spinler27de6f32020-02-21 08:35:57 -0600503 bool deleteAll = false;
504
Aatire340c132019-12-09 14:19:29 -0600505 app.add_option("-f,--file", fileName,
506 "Display a PEL using its Raw PEL file");
507 app.add_option("-i, --id", idPEL, "Display a PEL based on its ID");
Aatirbad5f8a2019-12-10 15:27:16 -0600508 app.add_flag("-l", listPEL, "List PELs");
Aatir7b291ec2019-11-19 10:37:37 -0600509 app.add_flag("-r", listPELDescOrd, "Reverse order of output");
510 app.add_flag("-s", listPELShowHidden, "Show hidden PELs");
Matt Spinler27de6f32020-02-21 08:35:57 -0600511 app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID");
512 app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs");
513
Aatir186ce8c2019-10-20 15:13:39 -0500514 CLI11_PARSE(app, argc, argv);
515
516 if (!fileName.empty())
517 {
518 std::vector<uint8_t> data = getFileData(fileName);
519 if (!data.empty())
520 {
521 PEL pel{data};
522 pel.toJSON();
523 }
524 else
525 {
526 exitWithError(app.help("", CLI::AppFormatMode::All),
527 "Raw PEL file can't be read.");
528 }
529 }
Aatir7b291ec2019-11-19 10:37:37 -0600530
Aatire340c132019-12-09 14:19:29 -0600531 else if (!idPEL.empty())
532 {
Matt Spinler1b420bc2020-02-21 08:54:25 -0600533 callFunctionOnPEL(idPEL, displayPEL);
Aatire340c132019-12-09 14:19:29 -0600534 }
Aatir7b291ec2019-11-19 10:37:37 -0600535 else if (listPEL)
536 {
537
538 printList(listPELDescOrd, listPELShowHidden);
539 }
Matt Spinler27de6f32020-02-21 08:35:57 -0600540 else if (!idToDelete.empty())
541 {
542 callFunctionOnPEL(idToDelete, deletePEL);
543 }
544 else if (deleteAll)
545 {
546 deleteAllPELs();
547 }
Aatir186ce8c2019-10-20 15:13:39 -0500548 else
549 {
550 exitWithError(app.help("", CLI::AppFormatMode::All),
551 "Raw PEL file path not specified.");
552 }
553 return 0;
554}