blob: 1dd653b1f6c57bd1151c2592f5144d755b462215 [file] [log] [blame]
Ricardo Quesadac2cf3942021-07-16 16:49:04 -07001/* SPDX-License-Identifier: BSD-3-Clause */
2
Ricardo Quesada683b294d2021-09-03 17:47:43 -07003#include <assert.h>
Ricardo Quesadac2cf3942021-07-16 16:49:04 -07004#include <getopt.h>
Ricardo Quesada49a96a92021-08-16 11:25:52 -07005#include <stdbool.h>
Ricardo Quesadac2cf3942021-07-16 16:49:04 -07006#include <stdint.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12#include <common.h>
13#include <commonlib/bsd/elog.h>
Martin Rothe3e965b2022-02-25 17:28:04 -070014#include <flashrom.h>
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070015
16#include "eventlog.h"
17
Ricardo Quesada683b294d2021-09-03 17:47:43 -070018/* Only refers to the data max size. The "-1" is the checksum byte */
19#define ELOG_MAX_EVENT_DATA_SIZE (ELOG_MAX_EVENT_SIZE - sizeof(struct event_header) - 1)
20
Wojciech Macek9edaccd2023-02-23 09:33:06 +010021enum elogtool_flag {
22 ELOGTOOL_FLAG_UTC = (1 << 0),
23};
24
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070025enum elogtool_return {
26 ELOGTOOL_EXIT_SUCCESS = 0,
27 ELOGTOOL_EXIT_BAD_ARGS,
Ricardo Quesada49a96a92021-08-16 11:25:52 -070028 ELOGTOOL_EXIT_READ_ERROR,
29 ELOGTOOL_EXIT_WRITE_ERROR,
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070030 ELOGTOOL_EXIT_NOT_ENOUGH_MEMORY,
31 ELOGTOOL_EXIT_INVALID_ELOG_FORMAT,
Ricardo Quesada49a96a92021-08-16 11:25:52 -070032 ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE,
33};
34
Wojciech Macek9edaccd2023-02-23 09:33:06 +010035static int cmd_list(const struct buffer *, enum elogtool_flag);
36static int cmd_clear(const struct buffer *, enum elogtool_flag);
37static int cmd_add(const struct buffer *, enum elogtool_flag);
Ricardo Quesada49a96a92021-08-16 11:25:52 -070038
39static const struct {
40 const char *name;
Wojciech Macek9edaccd2023-02-23 09:33:06 +010041 int (*func)(const struct buffer *buf, enum elogtool_flag flags);
Ricardo Quesada49a96a92021-08-16 11:25:52 -070042 /* Whether it requires to write the buffer back */
43 bool write_back;
44} cmds[] = {
45 {"list", cmd_list, false},
46 {"clear", cmd_clear, true},
Ricardo Quesada683b294d2021-09-03 17:47:43 -070047 {"add", cmd_add, true},
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070048};
49
Ricardo Quesada683b294d2021-09-03 17:47:43 -070050static char **cmd_argv; /* Command arguments */
51static char *argv0; /* Used as invoked_as */
52
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070053static struct option long_options[] = {
54 {"file", required_argument, 0, 'f'},
55 {"help", no_argument, 0, 'h'},
Wojciech Macek9edaccd2023-02-23 09:33:06 +010056 {"utc", no_argument, 0, 'U'},
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070057 {NULL, 0, 0, 0},
58};
59
60static void usage(char *invoked_as)
61{
Ricardo Quesada683b294d2021-09-03 17:47:43 -070062 fprintf(stderr, "elogtool: edit elog events\n\n"
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070063 "USAGE:\n"
64 "\t%s COMMAND [-f <filename>]\n\n"
65 "where, COMMAND is:\n"
Ricardo Quesada683b294d2021-09-03 17:47:43 -070066 " list lists all the event logs in human readable format\n"
67 " clear clears all the event logs\n"
68 " add <event_type> [event_data] add an entry to the event log\n"
Ricardo Quesada49a96a92021-08-16 11:25:52 -070069 "\n"
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070070 "ARGS\n"
Ricardo Quesada49a96a92021-08-16 11:25:52 -070071 "-f, --file <filename> File that holds event log partition.\n"
72 " If empty it will try to read/write from/to\n"
73 " the " ELOG_RW_REGION_NAME " using flashrom.\n"
Wojciech Macek9edaccd2023-02-23 09:33:06 +010074 "-U, --utc Print timestamps in UTC time zone\n"
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070075 "-h, --help Print this help\n",
76 invoked_as);
77}
78
Ricardo Quesada49a96a92021-08-16 11:25:52 -070079/*
80 * If filename is empty, read RW_ELOG from flashrom.
81 * Otherwise read the RW_ELOG from a file.
82 * It fails if the ELOG header is invalid.
83 * On success, buffer must be freed by caller.
84 */
85static int elog_read(struct buffer *buffer, const char *filename)
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070086{
Edward O'Callaghand74b8d92021-11-29 11:02:46 +110087 if (filename == NULL) {
Edward O'Callaghan774dcff2022-06-06 20:01:04 +100088 if (flashrom_host_read(buffer, ELOG_RW_REGION_NAME) != 0) {
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070089 fprintf(stderr, "Could not read RW_ELOG region using flashrom\n");
Ricardo Quesada49a96a92021-08-16 11:25:52 -070090 return ELOGTOOL_EXIT_READ_ERROR;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070091 }
Ricardo Quesada49a96a92021-08-16 11:25:52 -070092 } else if (buffer_from_file(buffer, filename) != 0) {
93 fprintf(stderr, "Could not read input file: %s\n", filename);
94 return ELOGTOOL_EXIT_READ_ERROR;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070095 }
96
Ricardo Quesada49a96a92021-08-16 11:25:52 -070097 if (elog_verify_header(buffer_get(buffer)) != CB_SUCCESS) {
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070098 fprintf(stderr, "FATAL: Invalid elog header\n");
Ricardo Quesada49a96a92021-08-16 11:25:52 -070099 buffer_delete(buffer);
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700100 return ELOGTOOL_EXIT_INVALID_ELOG_FORMAT;
101 }
102
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700103 return ELOGTOOL_EXIT_SUCCESS;
104}
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700105
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700106/*
107 * If filename is NULL, it saves the buffer using flashrom.
108 * Otherwise, it saves the buffer in the given filename.
109 */
Edward O'Callaghan774dcff2022-06-06 20:01:04 +1000110static int elog_write(struct buffer *buffer, const char *filename)
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700111{
112 if (filename == NULL) {
Edward O'Callaghan774dcff2022-06-06 20:01:04 +1000113 if (flashrom_host_write(buffer, ELOG_RW_REGION_NAME) != 0) {
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700114 fprintf(stderr,
115 "Failed to write to RW_ELOG region using flashrom\n");
116 return ELOGTOOL_EXIT_WRITE_ERROR;
117 }
118 return ELOGTOOL_EXIT_SUCCESS;
119 }
120
Edward O'Callaghan774dcff2022-06-06 20:01:04 +1000121 if (buffer_write_file(buffer, filename) != 0) {
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700122 fprintf(stderr, "Failed to write to file %s\n", filename);
123 return ELOGTOOL_EXIT_WRITE_ERROR;
124 }
125 return ELOGTOOL_EXIT_SUCCESS;
126}
127
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700128/* Buffer offset must point to a valid event_header struct */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700129static size_t next_available_event_offset(const struct buffer *buf)
130{
131 const struct event_header *event;
132 struct buffer copy, *iter = &copy;
133
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700134 assert(buffer_offset(buf) >= sizeof(struct elog_header));
135
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700136 buffer_clone(iter, buf);
137
138 while (buffer_size(iter) >= sizeof(struct event_header)) {
139 event = buffer_get(iter);
140 if (event->type == ELOG_TYPE_EOL || event->length == 0)
141 break;
142
143 assert(event->length <= buffer_size(iter));
144 buffer_seek(iter, event->length);
145 }
146
147 return buffer_offset(iter) - buffer_offset(buf);
148}
149
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700150/*
151 * Shrinks buffer by ~bytes_to_shrink, then appends a LOG_CLEAR event,
152 * and finally fills the remaining area with EOL events.
153 * Buffer offset must point to a valid event_header struct.
154 */
155static int shrink_buffer(const struct buffer *buf, size_t bytes_to_shrink)
156{
157 struct buffer copy, *iter = &copy;
158 const struct event_header *event;
159 uint32_t cleared;
160 int remaining;
161 uint8_t *data;
162
163 assert(buffer_offset(buf) >= sizeof(struct elog_header));
164
165 buffer_clone(&copy, buf);
166
167 /* Save copy of first event for later */
168 data = buffer_get(buf);
169
170 /* Set buffer offset pointing to the event right after bytes_to_shrink */
171 while (buffer_offset(iter) < bytes_to_shrink) {
172 event = buffer_get(iter);
173 assert(!(event->type == ELOG_TYPE_EOL || event->length == 0));
174
175 buffer_seek(iter, event->length);
176 }
177
178 /* Must be relative to the buffer offset */
179 cleared = buffer_offset(iter) - buffer_offset(buf);
180 remaining = buffer_size(iter);
181
182 /* Overlapping copy */
183 memmove(data, data + cleared, remaining);
184 memset(data + remaining, ELOG_TYPE_EOL, cleared);
185
186 /* Re-init copy to have a clean offset. Needed for init_event() */
187 buffer_clone(&copy, buf);
188 buffer_seek(&copy, next_available_event_offset(&copy));
189
190 if (!eventlog_init_event(&copy, ELOG_TYPE_LOG_CLEAR, &cleared, sizeof(cleared)))
191 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
192
193 return ELOGTOOL_EXIT_SUCCESS;
194}
195
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100196static int cmd_list(const struct buffer *buf, enum elogtool_flag flags)
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700197{
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100198 enum eventlog_timezone tz = EVENTLOG_TIMEZONE_LOCALTIME;
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700199 const struct event_header *event;
200 unsigned int count = 0;
201
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100202 if (flags & ELOGTOOL_FLAG_UTC)
203 tz = EVENTLOG_TIMEZONE_UTC;
204
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700205 /* Point to the first event */
206 event = buffer_get(buf) + sizeof(struct elog_header);
207
208 while ((const void *)(event) < buffer_end(buf)) {
Kapil Porwal0b6954b2023-01-20 01:00:42 +0530209 if (((const void *)event + sizeof(*event)) >= buffer_end(buf)
210 || event->length <= sizeof(*event)
211 || event->length > ELOG_MAX_EVENT_SIZE
212 || ((const void *)event + event->length) >= buffer_end(buf)
213 || event->type == ELOG_TYPE_EOL)
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700214 break;
215
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100216 eventlog_print_event(event, count, tz);
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700217 event = elog_get_next_event(event);
218 count++;
219 }
220
221 return ELOGTOOL_EXIT_SUCCESS;
222}
223
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700224/*
225 * Clears the elog events from the given buffer, which is a valid RW_ELOG region.
226 * A LOG_CLEAR event is appended.
227 */
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100228static int cmd_clear(const struct buffer *buf,
229 enum elogtool_flag flags __maybe_unused)
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700230{
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700231 uint32_t used_data_size;
232 struct buffer copy;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700233
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700234 /* Clone the buffer to avoid changing the offset of the original buffer */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700235 buffer_clone(&copy, buf);
236 buffer_seek(&copy, sizeof(struct elog_header));
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700237
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700238 /*
239 * Calculate the size of the "used" buffer, needed for ELOG_TYPE_LOG_CLEAR.
240 * Then overwrite the entire buffer with ELOG_TYPE_EOL.
241 * Finally insert a LOG_CLEAR event into the buffer.
242 */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700243 used_data_size = next_available_event_offset(&copy);
244 memset(buffer_get(&copy), ELOG_TYPE_EOL, buffer_size(&copy));
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700245
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700246 if (!eventlog_init_event(&copy, ELOG_TYPE_LOG_CLEAR,
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700247 &used_data_size, sizeof(used_data_size)))
248 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
249
250 return ELOGTOOL_EXIT_SUCCESS;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700251}
252
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700253static void cmd_add_usage(void)
254{
255 usage(argv0);
256
257 fprintf(stderr, "\n\nSpecific to ADD command:\n"
258 "\n"
259 "<event_type>: an hexadecimal number (0-255). Prefix '0x' is optional\n"
260 "[event_data]: (optional) a series of hexadecimal numbers. Must be:\n"
261 " - len(event_data) %% 2 == 0\n"
Ricardo Quesadae48043b2021-09-20 17:17:07 -0700262 " - len(event_data) in bytes <= %zu\n"
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700263 "\n"
264 "Example:\n"
265 "%s add 0x16 01ABF0 # 01ABF0 is actually three bytes: 0x01, 0xAB and 0xF0\n"
266 "%s add 17 # 17 is in hexa\n",
267 ELOG_MAX_EVENT_DATA_SIZE, argv0, argv0
268 );
269}
270
271static int cmd_add_parse_args(uint8_t *type, uint8_t *data, size_t *data_size)
272{
273 char byte[3] = {0};
274 int argc = 0;
275 char *endptr;
276 long value;
277 int len;
278
279 while (cmd_argv[argc] != NULL)
280 argc++;
281
282 if (argc != 1 && argc != 2)
283 return ELOGTOOL_EXIT_BAD_ARGS;
284
285 /* Force type to be an hexa value to be consistent with the data values */
286 value = strtol(cmd_argv[0], NULL, 16);
287 if (value > 255) {
288 fprintf(stderr, "Error: Event type should be between 0-0xff; "
289 "got: 0x%04lx\n", value);
290 return ELOGTOOL_EXIT_BAD_ARGS;
291 }
292
293 *type = value;
294
295 if (argc == 1)
296 return ELOGTOOL_EXIT_SUCCESS;
297
298 /* Assuming argc == 2 */
299 len = strlen(cmd_argv[1]);
300
301 /* Needs 2 bytes per number */
302 if (len % 2 != 0) {
303 fprintf(stderr,
304 "Error: Event data length should be an even number; got: %d\n", len);
305 return ELOGTOOL_EXIT_BAD_ARGS;
306 }
307
308 *data_size = len / 2;
309
310 if (*data_size > ELOG_MAX_EVENT_DATA_SIZE) {
311 fprintf(stderr,
Ricardo Quesadae48043b2021-09-20 17:17:07 -0700312 "Error: Event data length (in bytes) should be <= %zu; got: %zu\n",
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700313 ELOG_MAX_EVENT_DATA_SIZE, *data_size);
314 return ELOGTOOL_EXIT_BAD_ARGS;
315 }
316
317 for (unsigned int i = 0; i < *data_size; i++) {
318 byte[0] = *cmd_argv[1]++;
319 byte[1] = *cmd_argv[1]++;
320 data[i] = strtol(byte, &endptr, 16);
321 if (endptr != &byte[2]) {
322 fprintf(stderr, "Error: Event data length contains invalid data. "
323 "Only hexa digits are valid\n");
324 return ELOGTOOL_EXIT_BAD_ARGS;
325 }
326 }
327
328 return ELOGTOOL_EXIT_SUCCESS;
329}
330
331/* Appends an elog entry to EventLog buffer. */
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100332static int cmd_add(const struct buffer *buf,
333 enum elogtool_flag flags __maybe_unused)
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700334{
335 uint8_t data[ELOG_MAX_EVENT_DATA_SIZE];
336 size_t data_size = 0;
337 struct buffer copy;
338 uint8_t type = 0;
339 size_t next_event;
340 size_t threshold;
341 int ret;
342
343 if (cmd_add_parse_args(&type, data, &data_size) != ELOGTOOL_EXIT_SUCCESS) {
344 cmd_add_usage();
345 return ELOGTOOL_EXIT_BAD_ARGS;
346 }
347
348 buffer_clone(&copy, buf);
349 buffer_seek(&copy, sizeof(struct elog_header));
350
351 threshold = buffer_size(&copy) * 3 / 4;
352 next_event = next_available_event_offset(&copy);
353
354 if (next_event > threshold) {
355 /* Shrink ~ 1/4 of the size */
356 ret = shrink_buffer(&copy, buffer_size(buf) - threshold);
357 if (ret != ELOGTOOL_EXIT_SUCCESS)
358 return ret;
359 next_event = next_available_event_offset(&copy);
360 }
361
362 buffer_seek(&copy, next_event);
363
364 if (!eventlog_init_event(&copy, type, data, data_size))
365 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
366
367 return ELOGTOOL_EXIT_SUCCESS;
368}
369
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700370int main(int argc, char **argv)
371{
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700372 char *filename = NULL;
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100373 enum elogtool_flag flags = 0;
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700374 struct buffer buf;
375 unsigned int i;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700376 int argflag;
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700377 int ret;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700378
379 if (argc < 2) {
380 usage(argv[0]);
381 return ELOGTOOL_EXIT_BAD_ARGS;
382 }
383
384 while (1) {
385 int option_index;
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100386 argflag = getopt_long(argc, argv, "Uhf:", long_options, &option_index);
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700387 if (argflag == -1)
388 break;
389
390 switch (argflag) {
391 case 'h':
392 case '?':
393 usage(argv[0]);
394 return ELOGTOOL_EXIT_SUCCESS;
395
396 case 'f':
397 if (!optarg) {
398 usage(argv[0]);
399 return ELOGTOOL_EXIT_BAD_ARGS;
400 }
401
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700402 filename = optarg;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700403 break;
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100404 case 'U':
405 flags |= ELOGTOOL_FLAG_UTC;
406 break;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700407
408 default:
409 break;
410 }
411 }
412
413 /* At least one command must be available. */
414 if (optind >= argc) {
415 usage(argv[0]);
416 return ELOGTOOL_EXIT_BAD_ARGS;
417 }
418
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700419 /* Returned buffer must be freed. */
420 ret = elog_read(&buf, filename);
421 if (ret)
422 return ret;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700423
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700424 for (i = 0; i < ARRAY_SIZE(cmds); i++) {
425 if (!strcmp(cmds[i].name, argv[optind])) {
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700426 /* For commands that parse their own arguments. */
427 cmd_argv = &argv[optind+1];
428 argv0 = argv[0];
Wojciech Macek9edaccd2023-02-23 09:33:06 +0100429 ret = cmds[i].func(&buf, flags);
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700430 break;
431 }
432 }
433
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700434 if (i == ARRAY_SIZE(cmds)) {
435 usage(argv[0]);
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700436 ret = ELOGTOOL_EXIT_BAD_ARGS;
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700437 }
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700438
439 if (!ret && cmds[i].write_back)
440 ret = elog_write(&buf, filename);
441
442 buffer_delete(&buf);
443
444 return ret;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700445}