xref: /aosp_15_r20/external/coreboot/util/cbfstool/elogtool.c (revision b9411a12aaaa7e1e6a6fb7c5e057f44ee179a49c)
1 /* SPDX-License-Identifier: BSD-3-Clause */
2 
3 #include <assert.h>
4 #include <getopt.h>
5 #include <stdbool.h>
6 #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>
14 #include <flashrom.h>
15 
16 #include "eventlog.h"
17 
18 /* 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 
21 enum elogtool_flag {
22 	ELOGTOOL_FLAG_UTC = (1 << 0),
23 };
24 
25 enum elogtool_return {
26 	ELOGTOOL_EXIT_SUCCESS = 0,
27 	ELOGTOOL_EXIT_BAD_ARGS,
28 	ELOGTOOL_EXIT_READ_ERROR,
29 	ELOGTOOL_EXIT_WRITE_ERROR,
30 	ELOGTOOL_EXIT_NOT_ENOUGH_MEMORY,
31 	ELOGTOOL_EXIT_INVALID_ELOG_FORMAT,
32 	ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE,
33 };
34 
35 static int cmd_list(const struct buffer *, enum elogtool_flag);
36 static int cmd_clear(const struct buffer *, enum elogtool_flag);
37 static int cmd_add(const struct buffer *, enum elogtool_flag);
38 
39 static const struct {
40 	const char *name;
41 	int (*func)(const struct buffer *buf, enum elogtool_flag flags);
42 	/* Whether it requires to write the buffer back */
43 	bool write_back;
44 } cmds[] = {
45 	{"list", cmd_list, false},
46 	{"clear", cmd_clear, true},
47 	{"add", cmd_add, true},
48 };
49 
50 static char **cmd_argv;		/* Command arguments */
51 static char *argv0;		/* Used as invoked_as */
52 
53 static struct option long_options[] = {
54 	{"file", required_argument, 0, 'f'},
55 	{"help", no_argument, 0, 'h'},
56 	{"utc", no_argument, 0, 'U'},
57 	{NULL, 0, 0, 0},
58 };
59 
usage(char * invoked_as)60 static void usage(char *invoked_as)
61 {
62 	fprintf(stderr, "elogtool: edit elog events\n\n"
63 			"USAGE:\n"
64 			"\t%s COMMAND [-f <filename>]\n\n"
65 			"where, COMMAND is:\n"
66 			"  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"
69 			"\n"
70 			"ARGS\n"
71 			"-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"
74 			"-U, --utc               Print timestamps in UTC time zone\n"
75 			"-h, --help              Print this help\n",
76 			invoked_as);
77 }
78 
79 /*
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  */
elog_read(struct buffer * buffer,const char * filename)85 static int elog_read(struct buffer *buffer, const char *filename)
86 {
87 	if (filename == NULL) {
88 		if (flashrom_host_read(buffer, ELOG_RW_REGION_NAME) != 0) {
89 			fprintf(stderr, "Could not read RW_ELOG region using flashrom\n");
90 			return ELOGTOOL_EXIT_READ_ERROR;
91 		}
92 	} 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;
95 	}
96 
97 	if (elog_verify_header(buffer_get(buffer)) != CB_SUCCESS) {
98 		fprintf(stderr, "FATAL: Invalid elog header\n");
99 		buffer_delete(buffer);
100 		return ELOGTOOL_EXIT_INVALID_ELOG_FORMAT;
101 	}
102 
103 	return ELOGTOOL_EXIT_SUCCESS;
104 }
105 
106 /*
107  * If filename is NULL, it saves the buffer using flashrom.
108  * Otherwise, it saves the buffer in the given filename.
109  */
elog_write(struct buffer * buffer,const char * filename)110 static int elog_write(struct buffer *buffer, const char *filename)
111 {
112 	if (filename == NULL) {
113 		if (flashrom_host_write(buffer, ELOG_RW_REGION_NAME) != 0) {
114 			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 
121 	if (buffer_write_file(buffer, filename) != 0) {
122 		fprintf(stderr, "Failed to write to file %s\n", filename);
123 		return ELOGTOOL_EXIT_WRITE_ERROR;
124 	}
125 	return ELOGTOOL_EXIT_SUCCESS;
126 }
127 
128 /* Buffer offset must point to a valid event_header struct */
next_available_event_offset(const struct buffer * buf)129 static size_t next_available_event_offset(const struct buffer *buf)
130 {
131 	const struct event_header *event;
132 	struct buffer copy, *iter = &copy;
133 
134 	assert(buffer_offset(buf) >= sizeof(struct elog_header));
135 
136 	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 
150 /*
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  */
shrink_buffer(const struct buffer * buf,size_t bytes_to_shrink)155 static 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 
cmd_list(const struct buffer * buf,enum elogtool_flag flags)196 static int cmd_list(const struct buffer *buf, enum elogtool_flag flags)
197 {
198 	enum eventlog_timezone tz = EVENTLOG_TIMEZONE_LOCALTIME;
199 	const struct event_header *event;
200 	unsigned int count = 0;
201 
202 	if (flags & ELOGTOOL_FLAG_UTC)
203 		tz = EVENTLOG_TIMEZONE_UTC;
204 
205 	/* Point to the first event */
206 	event = buffer_get(buf) + sizeof(struct elog_header);
207 
208 	while ((const void *)(event) < buffer_end(buf)) {
209 		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)
214 			break;
215 
216 		eventlog_print_event(event, count, tz);
217 		event = elog_get_next_event(event);
218 		count++;
219 	}
220 
221 	return ELOGTOOL_EXIT_SUCCESS;
222 }
223 
224 /*
225  * Clears the elog events from the given buffer, which is a valid RW_ELOG region.
226  * A LOG_CLEAR event is appended.
227  */
cmd_clear(const struct buffer * buf,enum elogtool_flag flags __maybe_unused)228 static int cmd_clear(const struct buffer *buf,
229 		     enum elogtool_flag flags __maybe_unused)
230 {
231 	uint32_t used_data_size;
232 	struct buffer copy;
233 
234 	/* Clone the buffer to avoid changing the offset of the original buffer */
235 	buffer_clone(&copy, buf);
236 	buffer_seek(&copy, sizeof(struct elog_header));
237 
238 	/*
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 	 */
243 	used_data_size = next_available_event_offset(&copy);
244 	memset(buffer_get(&copy), ELOG_TYPE_EOL, buffer_size(&copy));
245 
246 	if (!eventlog_init_event(&copy, ELOG_TYPE_LOG_CLEAR,
247 				 &used_data_size, sizeof(used_data_size)))
248 		return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE;
249 
250 	return ELOGTOOL_EXIT_SUCCESS;
251 }
252 
cmd_add_usage(void)253 static 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"
262 		"                       - len(event_data) in bytes <= %zu\n"
263 		"\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 
cmd_add_parse_args(uint8_t * type,uint8_t * data,size_t * data_size)271 static 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,
312 			"Error: Event data length (in bytes) should be <= %zu; got: %zu\n",
313 			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. */
cmd_add(const struct buffer * buf,enum elogtool_flag flags __maybe_unused)332 static int cmd_add(const struct buffer *buf,
333 		   enum elogtool_flag flags __maybe_unused)
334 {
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 
main(int argc,char ** argv)370 int main(int argc, char **argv)
371 {
372 	char *filename = NULL;
373 	enum elogtool_flag flags = 0;
374 	struct buffer buf;
375 	unsigned int i;
376 	int argflag;
377 	int ret;
378 
379 	if (argc < 2) {
380 		usage(argv[0]);
381 		return ELOGTOOL_EXIT_BAD_ARGS;
382 	}
383 
384 	while (1) {
385 		int option_index;
386 		argflag = getopt_long(argc, argv, "Uhf:", long_options, &option_index);
387 		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 
402 			filename = optarg;
403 			break;
404 		case 'U':
405 			flags |= ELOGTOOL_FLAG_UTC;
406 			break;
407 
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 
419 	/* Returned buffer must be freed. */
420 	ret = elog_read(&buf, filename);
421 	if (ret)
422 		return ret;
423 
424 	for (i = 0; i < ARRAY_SIZE(cmds); i++) {
425 		if (!strcmp(cmds[i].name, argv[optind])) {
426 			/* For commands that parse their own arguments. */
427 			cmd_argv = &argv[optind+1];
428 			argv0 = argv[0];
429 			ret = cmds[i].func(&buf, flags);
430 			break;
431 		}
432 	}
433 
434 	if (i == ARRAY_SIZE(cmds)) {
435 		usage(argv[0]);
436 		ret = ELOGTOOL_EXIT_BAD_ARGS;
437 	}
438 
439 	if (!ret && cmds[i].write_back)
440 		ret = elog_write(&buf, filename);
441 
442 	buffer_delete(&buf);
443 
444 	return ret;
445 }
446