blob: d92654e3ed3dd9dfd92fc55e03148e4355700b8d [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 {
200 printf("Can't open raw PEL file");
201 return {};
202 }
203}
204
Aatir7b291ec2019-11-19 10:37:37 -0600205template <typename T>
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800206std::string genPELJSON(T itr, bool hidden, message::Registry& registry)
Aatir7b291ec2019-11-19 10:37:37 -0600207{
208 std::size_t found;
209 std::string val;
210 char tmpValStr[50];
211 std::string listStr;
212 char name[50];
213 sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", itr.second.yearMSB,
214 itr.second.yearLSB, itr.second.month, itr.second.day,
215 itr.second.hour, itr.second.minutes, itr.second.seconds,
216 itr.second.hundredths, itr.first);
217 std::string fileName(name);
218 fileName = EXTENSION_PERSIST_DIR "/pels/logs/" + fileName;
219 try
220 {
Aatir37822f62019-12-10 14:40:27 -0600221 std::vector<uint8_t> data = getFileData(fileName);
Aatir37822f62019-12-10 14:40:27 -0600222 if (!data.empty())
223 {
Aatir37822f62019-12-10 14:40:27 -0600224 PEL pel{data};
225 std::bitset<16> actionFlags{pel.userHeader().actionFlags()};
226 if (pel.valid() && (hidden || !actionFlags.test(hiddenFlagBit)))
Aatir7b291ec2019-11-19 10:37:37 -0600227 {
Aatir37822f62019-12-10 14:40:27 -0600228 // id
229 sprintf(tmpValStr, "0x%X", pel.privateHeader().id());
230 val = std::string(tmpValStr);
231 listStr += "\t\"" + val + "\": {\n";
232 // ASCII
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800233 if (pel.primarySRC())
234 {
235 val = pel.primarySRC().value()->asciiString();
236 listStr += "\t\t\"SRC\": \"" +
237 val.substr(0, val.find(0x20)) + "\",\n";
238 // Registry message
239 auto regVal = pel.primarySRC().value()->getErrorDetails(
240 registry, DetailLevel::message, true);
241 if (regVal)
242 {
243 val = regVal.value();
244 listStr += "\t\t\"Message\": \"" + val + "\",\n";
245 }
246 }
247 else
248 {
249 listStr += "\t\t\"SRC\": \"No SRC\",\n";
250 }
Aatir37822f62019-12-10 14:40:27 -0600251 // platformid
252 sprintf(tmpValStr, "0x%X", pel.privateHeader().plid());
253 val = std::string(tmpValStr);
254 listStr += "\t\t\"PLID\": \"" + val + "\",\n";
255 // creatorid
256 sprintf(tmpValStr, "%c", pel.privateHeader().creatorID());
257 std::string creatorID(tmpValStr);
258 val = pv::creatorIDs.count(creatorID)
259 ? pv::creatorIDs.at(creatorID)
260 : "Unknown Creator ID";
261 listStr += "\t\t\"CreatorID\": \"" + val + "\",\n";
262 // subsytem
263 std::string subsystem = pv::getValue(
264 pel.userHeader().subsystem(), pel_values::subsystemValues);
265 listStr += "\t\t\"Subsystem\": \"" + subsystem + "\",\n";
266 // commit time
Harisuddin Mohamed Isa160c51c2020-02-13 23:42:20 +0800267 sprintf(tmpValStr, "%02X/%02X/%02X%02X %02X:%02X:%02X",
Aatir37822f62019-12-10 14:40:27 -0600268 pel.privateHeader().commitTimestamp().month,
269 pel.privateHeader().commitTimestamp().day,
270 pel.privateHeader().commitTimestamp().yearMSB,
271 pel.privateHeader().commitTimestamp().yearLSB,
272 pel.privateHeader().commitTimestamp().hour,
273 pel.privateHeader().commitTimestamp().minutes,
274 pel.privateHeader().commitTimestamp().seconds);
275 val = std::string(tmpValStr);
276 listStr += "\t\t\"Commit Time\": \"" + val + "\",\n";
277 // severity
278 std::string severity = pv::getValue(pel.userHeader().severity(),
279 pel_values::severityValues);
280 listStr += "\t\t\"Sev\": \"" + severity + "\",\n ";
281 // compID
282 sprintf(tmpValStr, "0x%X",
283 pel.privateHeader().header().componentID);
284 val = std::string(tmpValStr);
285 listStr += "\t\t\"CompID\": \"" + val + "\",\n ";
286
287 found = listStr.rfind(",");
288 if (found != std::string::npos)
289 {
290 listStr.replace(found, 1, "");
291 listStr += "\t}, \n";
292 }
Aatir7b291ec2019-11-19 10:37:37 -0600293 }
294 }
Aatir37822f62019-12-10 14:40:27 -0600295 else
296 {
297 log<level::ERR>("Empty PEL file",
298 entry("FILENAME=%s", fileName.c_str()),
299 entry("ERROR=%s", "Empty PEL file"));
300 }
Aatir7b291ec2019-11-19 10:37:37 -0600301 }
302 catch (std::exception& e)
303 {
304 log<level::ERR>("Hit exception while reading PEL File",
305 entry("FILENAME=%s", fileName.c_str()),
306 entry("ERROR=%s", e.what()));
307 }
308 return listStr;
309}
310/**
311 * @brief Print a list of PELs
312 */
313void printList(bool order, bool hidden)
314{
315 std::string listStr;
316 std::map<uint32_t, BCDTime> PELs;
317 std::size_t found;
318 listStr = "{\n";
319 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
320 it != fs::directory_iterator(); ++it)
321 {
322 if (!fs::is_regular_file((*it).path()))
323 {
324 continue;
325 }
Aatir37822f62019-12-10 14:40:27 -0600326 else
Aatir7b291ec2019-11-19 10:37:37 -0600327 {
Aatir37822f62019-12-10 14:40:27 -0600328 PELs.emplace(fileNameToPELId((*it).path().filename()),
329 fileNameToTimestamp((*it).path().filename()));
Aatir7b291ec2019-11-19 10:37:37 -0600330 }
331 }
Harisuddin Mohamed Isa0f717e12020-01-15 20:05:33 +0800332 message::Registry registry(getMessageRegistryPath() /
333 message::registryFileName);
334 auto buildJSON = [&listStr, &hidden, &registry](const auto& i) {
335 listStr += genPELJSON(i, hidden, registry);
Aatir37822f62019-12-10 14:40:27 -0600336 };
Aatir7b291ec2019-11-19 10:37:37 -0600337 if (order)
338 {
339 std::for_each(PELs.rbegin(), PELs.rend(), buildJSON);
340 }
341 else
342 {
343 std::for_each(PELs.begin(), PELs.end(), buildJSON);
344 }
345
346 found = listStr.rfind(",");
347 if (found != std::string::npos)
348 {
349 listStr.replace(found, 1, "");
350 listStr += "\n}\n";
351 printf("%s", listStr.c_str());
352 }
353}
Aatir186ce8c2019-10-20 15:13:39 -0500354
Matt Spinler27de6f32020-02-21 08:35:57 -0600355/**
356 * @brief Calls the function passed in on the PEL with the ID
357 * passed in.
358 *
359 * @param[in] id - The string version of the PEL ID, either with or
360 * without the 0x prefix.
361 * @param[in] func - The std::function<void(const PEL&)> function to run.
362 */
363void callFunctionOnPEL(const std::string& id, const PELFunc& func)
364{
365 std::string pelID{id};
366 std::transform(pelID.begin(), pelID.end(), pelID.begin(), toupper);
367
368 if (pelID.find("0X") == 0)
369 {
370 pelID.erase(0, 2);
371 }
372
373 bool found = false;
374
375 for (auto it = fs::directory_iterator(EXTENSION_PERSIST_DIR "/pels/logs");
376 it != fs::directory_iterator(); ++it)
377 {
378 // The PEL ID is part of the filename, so use that to find the PEL.
379
380 if (!fs::is_regular_file((*it).path()))
381 {
382 continue;
383 }
384
385 if (ends_with((*it).path(), pelID))
386 {
387 found = true;
388
389 auto data = getFileData((*it).path());
390 if (!data.empty())
391 {
392 PEL pel{data};
393
394 try
395 {
396 func(pel);
397 }
398 catch (std::exception& e)
399 {
400 std::cerr
401 << " Internal function threw an exception: " << e.what()
402 << "\n";
403 exit(1);
404 }
405 }
406 else
407 {
408 std::cerr << "Could not read PEL file\n";
409 exit(1);
410 }
411 break;
412 }
413 }
414
415 if (!found)
416 {
417 std::cerr << "PEL not found\n";
418 exit(1);
419 }
420}
421
422/**
423 * @brief Delete a PEL by deleting its corresponding event log.
424 *
425 * @param[in] pel - The PEL to delete
426 */
427void deletePEL(const PEL& pel)
428{
429 std::string path{object_path::logEntry};
430 path += std::to_string(pel.obmcLogID());
431
432 try
433 {
434 auto bus = sdbusplus::bus::new_default();
435 auto method = bus.new_method_call(service::logging, path.c_str(),
436 interface::deleteObj, "Delete");
437 auto reply = bus.call(method);
438 }
439 catch (const SdBusError& e)
440 {
441 std::cerr << "D-Bus call to delete event log " << pel.obmcLogID()
442 << " failed: " << e.what() << "\n";
443 exit(1);
444 }
445}
446
447/**
448 * @brief Delete all PELs by deleting all event logs.
449 */
450void deleteAllPELs()
451{
452 try
453 {
454 // This may move to an audit log some day
455 log<level::INFO>("peltool deleting all event logs");
456
457 auto bus = sdbusplus::bus::new_default();
458 auto method =
459 bus.new_method_call(service::logging, object_path::logging,
460 interface::deleteAll, "DeleteAll");
461 auto reply = bus.call(method);
462 }
463 catch (const SdBusError& e)
464 {
465 std::cerr << "D-Bus call to delete all event logs failed: " << e.what()
466 << "\n";
467 exit(1);
468 }
469}
470
Matt Spinler1b420bc2020-02-21 08:54:25 -0600471/**
472 * @brief Display a single PEL
473 *
474 * @param[in] pel - the PEL to display
475 */
476void displayPEL(const PEL& pel)
477{
478 if (pel.valid())
479 {
480 pel.toJSON();
481 }
482 else
483 {
484 std::cerr << "PEL was malformed\n";
485 exit(1);
486 }
487}
488
Aatir186ce8c2019-10-20 15:13:39 -0500489static void exitWithError(const std::string& help, const char* err)
490{
491 std::cerr << "ERROR: " << err << std::endl << help << std::endl;
492 exit(-1);
493}
494
495int main(int argc, char** argv)
496{
497 CLI::App app{"OpenBMC PEL Tool"};
498 std::string fileName;
Aatire340c132019-12-09 14:19:29 -0600499 std::string idPEL;
Matt Spinler27de6f32020-02-21 08:35:57 -0600500 std::string idToDelete;
Aatire340c132019-12-09 14:19:29 -0600501 bool listPEL = false;
502 bool listPELDescOrd = false;
503 bool listPELShowHidden = false;
Matt Spinler27de6f32020-02-21 08:35:57 -0600504 bool deleteAll = false;
505
Aatire340c132019-12-09 14:19:29 -0600506 app.add_option("-f,--file", fileName,
507 "Display a PEL using its Raw PEL file");
508 app.add_option("-i, --id", idPEL, "Display a PEL based on its ID");
Aatirbad5f8a2019-12-10 15:27:16 -0600509 app.add_flag("-l", listPEL, "List PELs");
Aatir7b291ec2019-11-19 10:37:37 -0600510 app.add_flag("-r", listPELDescOrd, "Reverse order of output");
511 app.add_flag("-s", listPELShowHidden, "Show hidden PELs");
Matt Spinler27de6f32020-02-21 08:35:57 -0600512 app.add_option("-d, --delete", idToDelete, "Delete a PEL based on its ID");
513 app.add_flag("-D, --delete-all", deleteAll, "Delete all PELs");
514
Aatir186ce8c2019-10-20 15:13:39 -0500515 CLI11_PARSE(app, argc, argv);
516
517 if (!fileName.empty())
518 {
519 std::vector<uint8_t> data = getFileData(fileName);
520 if (!data.empty())
521 {
522 PEL pel{data};
523 pel.toJSON();
524 }
525 else
526 {
527 exitWithError(app.help("", CLI::AppFormatMode::All),
528 "Raw PEL file can't be read.");
529 }
530 }
Aatir7b291ec2019-11-19 10:37:37 -0600531
Aatire340c132019-12-09 14:19:29 -0600532 else if (!idPEL.empty())
533 {
Matt Spinler1b420bc2020-02-21 08:54:25 -0600534 callFunctionOnPEL(idPEL, displayPEL);
Aatire340c132019-12-09 14:19:29 -0600535 }
Aatir7b291ec2019-11-19 10:37:37 -0600536 else if (listPEL)
537 {
538
539 printList(listPELDescOrd, listPELShowHidden);
540 }
Matt Spinler27de6f32020-02-21 08:35:57 -0600541 else if (!idToDelete.empty())
542 {
543 callFunctionOnPEL(idToDelete, deletePEL);
544 }
545 else if (deleteAll)
546 {
547 deleteAllPELs();
548 }
Aatir186ce8c2019-10-20 15:13:39 -0500549 else
550 {
551 exitWithError(app.help("", CLI::AppFormatMode::All),
552 "Raw PEL file path not specified.");
553 }
554 return 0;
555}