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 = © 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 = © 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(©, 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(©, buf); 188 buffer_seek(©, next_available_event_offset(©)); 189 190 if (!eventlog_init_event(©, 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(©, buf); 236 buffer_seek(©, 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(©); 244 memset(buffer_get(©), ELOG_TYPE_EOL, buffer_size(©)); 245 246 if (!eventlog_init_event(©, 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(©, buf); 349 buffer_seek(©, sizeof(struct elog_header)); 350 351 threshold = buffer_size(©) * 3 / 4; 352 next_event = next_available_event_offset(©); 353 354 if (next_event > threshold) { 355 /* Shrink ~ 1/4 of the size */ 356 ret = shrink_buffer(©, buffer_size(buf) - threshold); 357 if (ret != ELOGTOOL_EXIT_SUCCESS) 358 return ret; 359 next_event = next_available_event_offset(©); 360 } 361 362 buffer_seek(©, next_event); 363 364 if (!eventlog_init_event(©, 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 }