1 /* SPDX-License-Identifier: BSD-3-Clause */
13 #include <commonlib/bsd/elog.h>
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)
22 ELOGTOOL_FLAG_UTC
= (1 << 0),
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
,
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
);
41 int (*func
)(const struct buffer
*buf
, enum elogtool_flag flags
);
42 /* Whether it requires to write the buffer back */
45 {"list", cmd_list
, false},
46 {"clear", cmd_clear
, true},
47 {"add", cmd_add
, true},
50 static char **cmd_argv
; /* Command arguments */
51 static char *argv0
; /* Used as invoked_as */
53 static struct option long_options
[] = {
54 {"file", required_argument
, 0, 'f'},
55 {"help", no_argument
, 0, 'h'},
56 {"utc", no_argument
, 0, 'U'},
60 static void usage(char *invoked_as
)
62 fprintf(stderr
, "elogtool: edit elog events\n\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"
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",
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.
85 static int elog_read(struct buffer
*buffer
, const char *filename
)
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
;
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
;
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
;
103 return ELOGTOOL_EXIT_SUCCESS
;
107 * If filename is NULL, it saves the buffer using flashrom.
108 * Otherwise, it saves the buffer in the given filename.
110 static int elog_write(struct buffer
*buffer
, const char *filename
)
112 if (filename
== NULL
) {
113 if (flashrom_host_write(buffer
, ELOG_RW_REGION_NAME
) != 0) {
115 "Failed to write to RW_ELOG region using flashrom\n");
116 return ELOGTOOL_EXIT_WRITE_ERROR
;
118 return ELOGTOOL_EXIT_SUCCESS
;
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
;
125 return ELOGTOOL_EXIT_SUCCESS
;
128 /* Buffer offset must point to a valid event_header struct */
129 static size_t next_available_event_offset(const struct buffer
*buf
)
131 const struct event_header
*event
;
132 struct buffer copy
, *iter
= ©
;
134 assert(buffer_offset(buf
) >= sizeof(struct elog_header
));
136 buffer_clone(iter
, buf
);
138 while (buffer_size(iter
) >= sizeof(struct event_header
)) {
139 event
= buffer_get(iter
);
140 if (event
->type
== ELOG_TYPE_EOL
|| event
->length
== 0)
143 assert(event
->length
<= buffer_size(iter
));
144 buffer_seek(iter
, event
->length
);
147 return buffer_offset(iter
) - buffer_offset(buf
);
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.
155 static int shrink_buffer(const struct buffer
*buf
, size_t bytes_to_shrink
)
157 struct buffer copy
, *iter
= ©
;
158 const struct event_header
*event
;
163 assert(buffer_offset(buf
) >= sizeof(struct elog_header
));
165 buffer_clone(©
, buf
);
167 /* Save copy of first event for later */
168 data
= buffer_get(buf
);
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));
175 buffer_seek(iter
, event
->length
);
178 /* Must be relative to the buffer offset */
179 cleared
= buffer_offset(iter
) - buffer_offset(buf
);
180 remaining
= buffer_size(iter
);
182 /* Overlapping copy */
183 memmove(data
, data
+ cleared
, remaining
);
184 memset(data
+ remaining
, ELOG_TYPE_EOL
, cleared
);
186 /* Re-init copy to have a clean offset. Needed for init_event() */
187 buffer_clone(©
, buf
);
188 buffer_seek(©
, next_available_event_offset(©
));
190 if (!eventlog_init_event(©
, ELOG_TYPE_LOG_CLEAR
, &cleared
, sizeof(cleared
)))
191 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE
;
193 return ELOGTOOL_EXIT_SUCCESS
;
196 static int cmd_list(const struct buffer
*buf
, enum elogtool_flag flags
)
198 enum eventlog_timezone tz
= EVENTLOG_TIMEZONE_LOCALTIME
;
199 const struct event_header
*event
;
200 unsigned int count
= 0;
202 if (flags
& ELOGTOOL_FLAG_UTC
)
203 tz
= EVENTLOG_TIMEZONE_UTC
;
205 /* Point to the first event */
206 event
= buffer_get(buf
) + sizeof(struct elog_header
);
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
)
216 eventlog_print_event(event
, count
, tz
);
217 event
= elog_get_next_event(event
);
221 return ELOGTOOL_EXIT_SUCCESS
;
225 * Clears the elog events from the given buffer, which is a valid RW_ELOG region.
226 * A LOG_CLEAR event is appended.
228 static int cmd_clear(const struct buffer
*buf
,
229 enum elogtool_flag flags __maybe_unused
)
231 uint32_t used_data_size
;
234 /* Clone the buffer to avoid changing the offset of the original buffer */
235 buffer_clone(©
, buf
);
236 buffer_seek(©
, sizeof(struct elog_header
));
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.
243 used_data_size
= next_available_event_offset(©
);
244 memset(buffer_get(©
), ELOG_TYPE_EOL
, buffer_size(©
));
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
;
250 return ELOGTOOL_EXIT_SUCCESS
;
253 static void cmd_add_usage(void)
257 fprintf(stderr
, "\n\nSpecific to ADD command:\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"
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
271 static int cmd_add_parse_args(uint8_t *type
, uint8_t *data
, size_t *data_size
)
279 while (cmd_argv
[argc
] != NULL
)
282 if (argc
!= 1 && argc
!= 2)
283 return ELOGTOOL_EXIT_BAD_ARGS
;
285 /* Force type to be an hexa value to be consistent with the data values */
286 value
= strtol(cmd_argv
[0], NULL
, 16);
288 fprintf(stderr
, "Error: Event type should be between 0-0xff; "
289 "got: 0x%04lx\n", value
);
290 return ELOGTOOL_EXIT_BAD_ARGS
;
296 return ELOGTOOL_EXIT_SUCCESS
;
298 /* Assuming argc == 2 */
299 len
= strlen(cmd_argv
[1]);
301 /* Needs 2 bytes per number */
304 "Error: Event data length should be an even number; got: %d\n", len
);
305 return ELOGTOOL_EXIT_BAD_ARGS
;
308 *data_size
= len
/ 2;
310 if (*data_size
> ELOG_MAX_EVENT_DATA_SIZE
) {
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
;
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
;
328 return ELOGTOOL_EXIT_SUCCESS
;
331 /* Appends an elog entry to EventLog buffer. */
332 static int cmd_add(const struct buffer
*buf
,
333 enum elogtool_flag flags __maybe_unused
)
335 uint8_t data
[ELOG_MAX_EVENT_DATA_SIZE
];
336 size_t data_size
= 0;
343 if (cmd_add_parse_args(&type
, data
, &data_size
) != ELOGTOOL_EXIT_SUCCESS
) {
345 return ELOGTOOL_EXIT_BAD_ARGS
;
348 buffer_clone(©
, buf
);
349 buffer_seek(©
, sizeof(struct elog_header
));
351 threshold
= buffer_size(©
) * 3 / 4;
352 next_event
= next_available_event_offset(©
);
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
)
359 next_event
= next_available_event_offset(©
);
362 buffer_seek(©
, next_event
);
364 if (!eventlog_init_event(©
, type
, data
, data_size
))
365 return ELOGTOOL_EXIT_NOT_ENOUGH_BUFFER_SPACE
;
367 return ELOGTOOL_EXIT_SUCCESS
;
370 int main(int argc
, char **argv
)
372 char *filename
= NULL
;
373 enum elogtool_flag flags
= 0;
381 return ELOGTOOL_EXIT_BAD_ARGS
;
386 argflag
= getopt_long(argc
, argv
, "Uhf:", long_options
, &option_index
);
394 return ELOGTOOL_EXIT_SUCCESS
;
399 return ELOGTOOL_EXIT_BAD_ARGS
;
405 flags
|= ELOGTOOL_FLAG_UTC
;
413 /* At least one command must be available. */
414 if (optind
>= argc
) {
416 return ELOGTOOL_EXIT_BAD_ARGS
;
419 /* Returned buffer must be freed. */
420 ret
= elog_read(&buf
, filename
);
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];
429 ret
= cmds
[i
].func(&buf
, flags
);
434 if (i
== ARRAY_SIZE(cmds
)) {
436 ret
= ELOGTOOL_EXIT_BAD_ARGS
;
439 if (!ret
&& cmds
[i
].write_back
)
440 ret
= elog_write(&buf
, filename
);