blob: 846575765e80a2e2027f88c412a172119fe043b0 [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
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070021enum elogtool_return {
22 ELOGTOOL_EXIT_SUCCESS = 0,
23 ELOGTOOL_EXIT_BAD_ARGS,
Ricardo Quesada49a96a92021-08-16 11:25:52 -070024 ELOGTOOL_EXIT_READ_ERROR,
25 ELOGTOOL_EXIT_WRITE_ERROR,
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070026 ELOGTOOL_EXIT_NOT_ENOUGH_MEMORY,
27 ELOGTOOL_EXIT_INVALID_ELOG_FORMAT,
Ricardo Quesada49a96a92021-08-16 11:25:52 -070028 ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE,
29};
30
31static int cmd_list(const struct buffer *);
32static int cmd_clear(const struct buffer *);
Ricardo Quesada683b294d2021-09-03 17:47:43 -070033static int cmd_add(const struct buffer *);
Ricardo Quesada49a96a92021-08-16 11:25:52 -070034
35static const struct {
36 const char *name;
37 int (*func)(const struct buffer *buf);
38 /* Whether it requires to write the buffer back */
39 bool write_back;
40} cmds[] = {
41 {"list", cmd_list, false},
42 {"clear", cmd_clear, true},
Ricardo Quesada683b294d2021-09-03 17:47:43 -070043 {"add", cmd_add, true},
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070044};
45
Ricardo Quesada683b294d2021-09-03 17:47:43 -070046static char **cmd_argv; /* Command arguments */
47static char *argv0; /* Used as invoked_as */
48
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070049static struct option long_options[] = {
50 {"file", required_argument, 0, 'f'},
51 {"help", no_argument, 0, 'h'},
52 {NULL, 0, 0, 0},
53};
54
55static void usage(char *invoked_as)
56{
Ricardo Quesada683b294d2021-09-03 17:47:43 -070057 fprintf(stderr, "elogtool: edit elog events\n\n"
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070058 "USAGE:\n"
59 "\t%s COMMAND [-f <filename>]\n\n"
60 "where, COMMAND is:\n"
Ricardo Quesada683b294d2021-09-03 17:47:43 -070061 " list lists all the event logs in human readable format\n"
62 " clear clears all the event logs\n"
63 " add <event_type> [event_data] add an entry to the event log\n"
Ricardo Quesada49a96a92021-08-16 11:25:52 -070064 "\n"
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070065 "ARGS\n"
Ricardo Quesada49a96a92021-08-16 11:25:52 -070066 "-f, --file <filename> File that holds event log partition.\n"
67 " If empty it will try to read/write from/to\n"
68 " the " ELOG_RW_REGION_NAME " using flashrom.\n"
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070069 "-h, --help Print this help\n",
70 invoked_as);
71}
72
Ricardo Quesada49a96a92021-08-16 11:25:52 -070073/*
74 * If filename is empty, read RW_ELOG from flashrom.
75 * Otherwise read the RW_ELOG from a file.
76 * It fails if the ELOG header is invalid.
77 * On success, buffer must be freed by caller.
78 */
79static int elog_read(struct buffer *buffer, const char *filename)
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070080{
Edward O'Callaghand74b8d92021-11-29 11:02:46 +110081 if (filename == NULL) {
Edward O'Callaghan774dcff2022-06-06 20:01:04 +100082 if (flashrom_host_read(buffer, ELOG_RW_REGION_NAME) != 0) {
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070083 fprintf(stderr, "Could not read RW_ELOG region using flashrom\n");
Ricardo Quesada49a96a92021-08-16 11:25:52 -070084 return ELOGTOOL_EXIT_READ_ERROR;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070085 }
Ricardo Quesada49a96a92021-08-16 11:25:52 -070086 } else if (buffer_from_file(buffer, filename) != 0) {
87 fprintf(stderr, "Could not read input file: %s\n", filename);
88 return ELOGTOOL_EXIT_READ_ERROR;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070089 }
90
Ricardo Quesada49a96a92021-08-16 11:25:52 -070091 if (elog_verify_header(buffer_get(buffer)) != CB_SUCCESS) {
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070092 fprintf(stderr, "FATAL: Invalid elog header\n");
Ricardo Quesada49a96a92021-08-16 11:25:52 -070093 buffer_delete(buffer);
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070094 return ELOGTOOL_EXIT_INVALID_ELOG_FORMAT;
95 }
96
Ricardo Quesada49a96a92021-08-16 11:25:52 -070097 return ELOGTOOL_EXIT_SUCCESS;
98}
Ricardo Quesadac2cf3942021-07-16 16:49:04 -070099
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700100/*
101 * If filename is NULL, it saves the buffer using flashrom.
102 * Otherwise, it saves the buffer in the given filename.
103 */
Edward O'Callaghan774dcff2022-06-06 20:01:04 +1000104static int elog_write(struct buffer *buffer, const char *filename)
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700105{
106 if (filename == NULL) {
Edward O'Callaghan774dcff2022-06-06 20:01:04 +1000107 if (flashrom_host_write(buffer, ELOG_RW_REGION_NAME) != 0) {
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700108 fprintf(stderr,
109 "Failed to write to RW_ELOG region using flashrom\n");
110 return ELOGTOOL_EXIT_WRITE_ERROR;
111 }
112 return ELOGTOOL_EXIT_SUCCESS;
113 }
114
Edward O'Callaghan774dcff2022-06-06 20:01:04 +1000115 if (buffer_write_file(buffer, filename) != 0) {
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700116 fprintf(stderr, "Failed to write to file %s\n", filename);
117 return ELOGTOOL_EXIT_WRITE_ERROR;
118 }
119 return ELOGTOOL_EXIT_SUCCESS;
120}
121
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700122/* Buffer offset must point to a valid event_header struct */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700123static size_t next_available_event_offset(const struct buffer *buf)
124{
125 const struct event_header *event;
126 struct buffer copy, *iter = &copy;
127
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700128 assert(buffer_offset(buf) >= sizeof(struct elog_header));
129
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700130 buffer_clone(iter, buf);
131
132 while (buffer_size(iter) >= sizeof(struct event_header)) {
133 event = buffer_get(iter);
134 if (event->type == ELOG_TYPE_EOL || event->length == 0)
135 break;
136
137 assert(event->length <= buffer_size(iter));
138 buffer_seek(iter, event->length);
139 }
140
141 return buffer_offset(iter) - buffer_offset(buf);
142}
143
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700144/*
145 * Shrinks buffer by ~bytes_to_shrink, then appends a LOG_CLEAR event,
146 * and finally fills the remaining area with EOL events.
147 * Buffer offset must point to a valid event_header struct.
148 */
149static int shrink_buffer(const struct buffer *buf, size_t bytes_to_shrink)
150{
151 struct buffer copy, *iter = &copy;
152 const struct event_header *event;
153 uint32_t cleared;
154 int remaining;
155 uint8_t *data;
156
157 assert(buffer_offset(buf) >= sizeof(struct elog_header));
158
159 buffer_clone(&copy, buf);
160
161 /* Save copy of first event for later */
162 data = buffer_get(buf);
163
164 /* Set buffer offset pointing to the event right after bytes_to_shrink */
165 while (buffer_offset(iter) < bytes_to_shrink) {
166 event = buffer_get(iter);
167 assert(!(event->type == ELOG_TYPE_EOL || event->length == 0));
168
169 buffer_seek(iter, event->length);
170 }
171
172 /* Must be relative to the buffer offset */
173 cleared = buffer_offset(iter) - buffer_offset(buf);
174 remaining = buffer_size(iter);
175
176 /* Overlapping copy */
177 memmove(data, data + cleared, remaining);
178 memset(data + remaining, ELOG_TYPE_EOL, cleared);
179
180 /* Re-init copy to have a clean offset. Needed for init_event() */
181 buffer_clone(&copy, buf);
182 buffer_seek(&copy, next_available_event_offset(&copy));
183
184 if (!eventlog_init_event(&copy, ELOG_TYPE_LOG_CLEAR, &cleared, sizeof(cleared)))
185 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
186
187 return ELOGTOOL_EXIT_SUCCESS;
188}
189
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700190static int cmd_list(const struct buffer *buf)
191{
192 const struct event_header *event;
193 unsigned int count = 0;
194
195 /* Point to the first event */
196 event = buffer_get(buf) + sizeof(struct elog_header);
197
198 while ((const void *)(event) < buffer_end(buf)) {
Kapil Porwal0b6954b2023-01-20 01:00:42 +0530199 if (((const void *)event + sizeof(*event)) >= buffer_end(buf)
200 || event->length <= sizeof(*event)
201 || event->length > ELOG_MAX_EVENT_SIZE
202 || ((const void *)event + event->length) >= buffer_end(buf)
203 || event->type == ELOG_TYPE_EOL)
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700204 break;
205
206 eventlog_print_event(event, count);
207 event = elog_get_next_event(event);
208 count++;
209 }
210
211 return ELOGTOOL_EXIT_SUCCESS;
212}
213
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700214/*
215 * Clears the elog events from the given buffer, which is a valid RW_ELOG region.
216 * A LOG_CLEAR event is appended.
217 */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700218static int cmd_clear(const struct buffer *buf)
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700219{
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700220 uint32_t used_data_size;
221 struct buffer copy;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700222
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700223 /* Clone the buffer to avoid changing the offset of the original buffer */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700224 buffer_clone(&copy, buf);
225 buffer_seek(&copy, sizeof(struct elog_header));
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700226
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700227 /*
228 * Calculate the size of the "used" buffer, needed for ELOG_TYPE_LOG_CLEAR.
229 * Then overwrite the entire buffer with ELOG_TYPE_EOL.
230 * Finally insert a LOG_CLEAR event into the buffer.
231 */
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700232 used_data_size = next_available_event_offset(&copy);
233 memset(buffer_get(&copy), ELOG_TYPE_EOL, buffer_size(&copy));
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700234
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700235 if (!eventlog_init_event(&copy, ELOG_TYPE_LOG_CLEAR,
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700236 &used_data_size, sizeof(used_data_size)))
237 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
238
239 return ELOGTOOL_EXIT_SUCCESS;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700240}
241
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700242static void cmd_add_usage(void)
243{
244 usage(argv0);
245
246 fprintf(stderr, "\n\nSpecific to ADD command:\n"
247 "\n"
248 "<event_type>: an hexadecimal number (0-255). Prefix '0x' is optional\n"
249 "[event_data]: (optional) a series of hexadecimal numbers. Must be:\n"
250 " - len(event_data) %% 2 == 0\n"
Ricardo Quesadae48043b2021-09-20 17:17:07 -0700251 " - len(event_data) in bytes <= %zu\n"
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700252 "\n"
253 "Example:\n"
254 "%s add 0x16 01ABF0 # 01ABF0 is actually three bytes: 0x01, 0xAB and 0xF0\n"
255 "%s add 17 # 17 is in hexa\n",
256 ELOG_MAX_EVENT_DATA_SIZE, argv0, argv0
257 );
258}
259
260static int cmd_add_parse_args(uint8_t *type, uint8_t *data, size_t *data_size)
261{
262 char byte[3] = {0};
263 int argc = 0;
264 char *endptr;
265 long value;
266 int len;
267
268 while (cmd_argv[argc] != NULL)
269 argc++;
270
271 if (argc != 1 && argc != 2)
272 return ELOGTOOL_EXIT_BAD_ARGS;
273
274 /* Force type to be an hexa value to be consistent with the data values */
275 value = strtol(cmd_argv[0], NULL, 16);
276 if (value > 255) {
277 fprintf(stderr, "Error: Event type should be between 0-0xff; "
278 "got: 0x%04lx\n", value);
279 return ELOGTOOL_EXIT_BAD_ARGS;
280 }
281
282 *type = value;
283
284 if (argc == 1)
285 return ELOGTOOL_EXIT_SUCCESS;
286
287 /* Assuming argc == 2 */
288 len = strlen(cmd_argv[1]);
289
290 /* Needs 2 bytes per number */
291 if (len % 2 != 0) {
292 fprintf(stderr,
293 "Error: Event data length should be an even number; got: %d\n", len);
294 return ELOGTOOL_EXIT_BAD_ARGS;
295 }
296
297 *data_size = len / 2;
298
299 if (*data_size > ELOG_MAX_EVENT_DATA_SIZE) {
300 fprintf(stderr,
Ricardo Quesadae48043b2021-09-20 17:17:07 -0700301 "Error: Event data length (in bytes) should be <= %zu; got: %zu\n",
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700302 ELOG_MAX_EVENT_DATA_SIZE, *data_size);
303 return ELOGTOOL_EXIT_BAD_ARGS;
304 }
305
306 for (unsigned int i = 0; i < *data_size; i++) {
307 byte[0] = *cmd_argv[1]++;
308 byte[1] = *cmd_argv[1]++;
309 data[i] = strtol(byte, &endptr, 16);
310 if (endptr != &byte[2]) {
311 fprintf(stderr, "Error: Event data length contains invalid data. "
312 "Only hexa digits are valid\n");
313 return ELOGTOOL_EXIT_BAD_ARGS;
314 }
315 }
316
317 return ELOGTOOL_EXIT_SUCCESS;
318}
319
320/* Appends an elog entry to EventLog buffer. */
321static int cmd_add(const struct buffer *buf)
322{
323 uint8_t data[ELOG_MAX_EVENT_DATA_SIZE];
324 size_t data_size = 0;
325 struct buffer copy;
326 uint8_t type = 0;
327 size_t next_event;
328 size_t threshold;
329 int ret;
330
331 if (cmd_add_parse_args(&type, data, &data_size) != ELOGTOOL_EXIT_SUCCESS) {
332 cmd_add_usage();
333 return ELOGTOOL_EXIT_BAD_ARGS;
334 }
335
336 buffer_clone(&copy, buf);
337 buffer_seek(&copy, sizeof(struct elog_header));
338
339 threshold = buffer_size(&copy) * 3 / 4;
340 next_event = next_available_event_offset(&copy);
341
342 if (next_event > threshold) {
343 /* Shrink ~ 1/4 of the size */
344 ret = shrink_buffer(&copy, buffer_size(buf) - threshold);
345 if (ret != ELOGTOOL_EXIT_SUCCESS)
346 return ret;
347 next_event = next_available_event_offset(&copy);
348 }
349
350 buffer_seek(&copy, next_event);
351
352 if (!eventlog_init_event(&copy, type, data, data_size))
353 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
354
355 return ELOGTOOL_EXIT_SUCCESS;
356}
357
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700358int main(int argc, char **argv)
359{
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700360 char *filename = NULL;
361 struct buffer buf;
362 unsigned int i;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700363 int argflag;
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700364 int ret;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700365
366 if (argc < 2) {
367 usage(argv[0]);
368 return ELOGTOOL_EXIT_BAD_ARGS;
369 }
370
371 while (1) {
372 int option_index;
373 argflag = getopt_long(argc, argv, "hf:", long_options, &option_index);
374 if (argflag == -1)
375 break;
376
377 switch (argflag) {
378 case 'h':
379 case '?':
380 usage(argv[0]);
381 return ELOGTOOL_EXIT_SUCCESS;
382
383 case 'f':
384 if (!optarg) {
385 usage(argv[0]);
386 return ELOGTOOL_EXIT_BAD_ARGS;
387 }
388
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700389 filename = optarg;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700390 break;
391
392 default:
393 break;
394 }
395 }
396
397 /* At least one command must be available. */
398 if (optind >= argc) {
399 usage(argv[0]);
400 return ELOGTOOL_EXIT_BAD_ARGS;
401 }
402
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700403 /* Returned buffer must be freed. */
404 ret = elog_read(&buf, filename);
405 if (ret)
406 return ret;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700407
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700408 for (i = 0; i < ARRAY_SIZE(cmds); i++) {
409 if (!strcmp(cmds[i].name, argv[optind])) {
Ricardo Quesada683b294d2021-09-03 17:47:43 -0700410 /* For commands that parse their own arguments. */
411 cmd_argv = &argv[optind+1];
412 argv0 = argv[0];
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700413 ret = cmds[i].func(&buf);
414 break;
415 }
416 }
417
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700418 if (i == ARRAY_SIZE(cmds)) {
419 usage(argv[0]);
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700420 ret = ELOGTOOL_EXIT_BAD_ARGS;
Ricardo Quesadaa2bf94d2021-09-03 17:30:31 -0700421 }
Ricardo Quesada49a96a92021-08-16 11:25:52 -0700422
423 if (!ret && cmds[i].write_back)
424 ret = elog_write(&buf, filename);
425
426 buffer_delete(&buf);
427
428 return ret;
Ricardo Quesadac2cf3942021-07-16 16:49:04 -0700429}