/ util / cbfstool / elogtool.c
elogtool.c
  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  
 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   */
 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   */
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 */
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   */
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  
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   */
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  
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  
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. */
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  
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  }