1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * Boot config tool for initrd image
4 */
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <string.h>
12 #include <errno.h>
13 #include <endian.h>
14 #include <assert.h>
15
16 #include <linux/bootconfig.h>
17
18 #define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
19
20 /* Bootconfig footer is [size][csum][BOOTCONFIG_MAGIC]. */
21 #define BOOTCONFIG_FOOTER_SIZE \
22 (sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN)
23
xbc_show_value(struct xbc_node * node,bool semicolon)24 static int xbc_show_value(struct xbc_node *node, bool semicolon)
25 {
26 const char *val, *eol;
27 char q;
28 int i = 0;
29
30 eol = semicolon ? ";\n" : "\n";
31 xbc_array_for_each_value(node, val) {
32 if (strchr(val, '"'))
33 q = '\'';
34 else
35 q = '"';
36 printf("%c%s%c%s", q, val, q, xbc_node_is_array(node) ? ", " : eol);
37 i++;
38 }
39 return i;
40 }
41
xbc_show_compact_tree(void)42 static void xbc_show_compact_tree(void)
43 {
44 struct xbc_node *node, *cnode = NULL, *vnode;
45 int depth = 0, i;
46
47 node = xbc_root_node();
48 while (node && xbc_node_is_key(node)) {
49 for (i = 0; i < depth; i++)
50 printf("\t");
51 if (!cnode)
52 cnode = xbc_node_get_child(node);
53 while (cnode && xbc_node_is_key(cnode) && !cnode->next) {
54 vnode = xbc_node_get_child(cnode);
55 /*
56 * If @cnode has value and subkeys, this
57 * should show it as below.
58 *
59 * key(@node) {
60 * key(@cnode) = value;
61 * key(@cnode) {
62 * subkeys;
63 * }
64 * }
65 */
66 if (vnode && xbc_node_is_value(vnode) && vnode->next)
67 break;
68 printf("%s.", xbc_node_get_data(node));
69 node = cnode;
70 cnode = vnode;
71 }
72 if (cnode && xbc_node_is_key(cnode)) {
73 printf("%s {\n", xbc_node_get_data(node));
74 depth++;
75 node = cnode;
76 cnode = NULL;
77 continue;
78 } else if (cnode && xbc_node_is_value(cnode)) {
79 printf("%s = ", xbc_node_get_data(node));
80 xbc_show_value(cnode, true);
81 /*
82 * If @node has value and subkeys, continue
83 * looping on subkeys with same node.
84 */
85 if (cnode->next) {
86 cnode = xbc_node_get_next(cnode);
87 continue;
88 }
89 } else {
90 printf("%s;\n", xbc_node_get_data(node));
91 }
92 cnode = NULL;
93
94 if (node->next) {
95 node = xbc_node_get_next(node);
96 continue;
97 }
98 while (!node->next) {
99 node = xbc_node_get_parent(node);
100 if (!node)
101 return;
102 if (!xbc_node_get_child(node)->next)
103 continue;
104 if (depth) {
105 depth--;
106 for (i = 0; i < depth; i++)
107 printf("\t");
108 printf("}\n");
109 }
110 }
111 node = xbc_node_get_next(node);
112 }
113 }
114
xbc_show_list(void)115 static void xbc_show_list(void)
116 {
117 char key[XBC_KEYLEN_MAX];
118 struct xbc_node *leaf;
119 const char *val;
120 int ret;
121
122 xbc_for_each_key_value(leaf, val) {
123 ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX);
124 if (ret < 0) {
125 fprintf(stderr, "Failed to compose key %d\n", ret);
126 break;
127 }
128 printf("%s = ", key);
129 if (!val || val[0] == '\0') {
130 printf("\"\"\n");
131 continue;
132 }
133 xbc_show_value(xbc_node_get_child(leaf), false);
134 }
135 }
136
137 #define PAGE_SIZE 4096
138
load_xbc_fd(int fd,char ** buf,int size)139 static int load_xbc_fd(int fd, char **buf, int size)
140 {
141 int ret;
142
143 *buf = malloc(size + 1);
144 if (!*buf)
145 return -ENOMEM;
146
147 ret = read(fd, *buf, size);
148 if (ret < 0)
149 return -errno;
150 (*buf)[size] = '\0';
151
152 return ret;
153 }
154
155 /* Return the read size or -errno */
load_xbc_file(const char * path,char ** buf)156 static int load_xbc_file(const char *path, char **buf)
157 {
158 struct stat stat;
159 int fd, ret;
160
161 fd = open(path, O_RDONLY);
162 if (fd < 0)
163 return -errno;
164 ret = fstat(fd, &stat);
165 if (ret < 0) {
166 ret = -errno;
167 close(fd);
168 return ret;
169 }
170
171 ret = load_xbc_fd(fd, buf, stat.st_size);
172
173 close(fd);
174
175 return ret;
176 }
177
pr_errno(const char * msg,int err)178 static int pr_errno(const char *msg, int err)
179 {
180 pr_err("%s: %d\n", msg, err);
181 return err;
182 }
183
load_xbc_from_initrd(int fd,char ** buf)184 static int load_xbc_from_initrd(int fd, char **buf)
185 {
186 struct stat stat;
187 int ret;
188 uint32_t size = 0, csum = 0, rcsum;
189 char magic[BOOTCONFIG_MAGIC_LEN];
190 const char *msg;
191
192 ret = fstat(fd, &stat);
193 if (ret < 0)
194 return -errno;
195
196 if (stat.st_size < BOOTCONFIG_FOOTER_SIZE)
197 return 0;
198
199 if (lseek(fd, -(off_t)BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0)
200 return pr_errno("Failed to lseek for magic", -errno);
201
202 if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0)
203 return pr_errno("Failed to read", -errno);
204
205 /* Check the bootconfig magic bytes */
206 if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0)
207 return 0;
208
209 if (lseek(fd, -(off_t)BOOTCONFIG_FOOTER_SIZE, SEEK_END) < 0)
210 return pr_errno("Failed to lseek for size", -errno);
211
212 if (read(fd, &size, sizeof(uint32_t)) < 0)
213 return pr_errno("Failed to read size", -errno);
214 size = le32toh(size);
215
216 if (read(fd, &csum, sizeof(uint32_t)) < 0)
217 return pr_errno("Failed to read checksum", -errno);
218 csum = le32toh(csum);
219
220 /* Wrong size error */
221 if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) {
222 pr_err("bootconfig size is too big\n");
223 return -E2BIG;
224 }
225
226 if (lseek(fd, stat.st_size - (size + BOOTCONFIG_FOOTER_SIZE),
227 SEEK_SET) < 0)
228 return pr_errno("Failed to lseek", -errno);
229
230 ret = load_xbc_fd(fd, buf, size);
231 if (ret < 0)
232 return ret;
233
234 /* Wrong Checksum */
235 rcsum = xbc_calc_checksum(*buf, size);
236 if (csum != rcsum) {
237 pr_err("checksum error: %u != %u\n", csum, rcsum);
238 return -EINVAL;
239 }
240
241 ret = xbc_init(*buf, size, &msg, NULL);
242 /* Wrong data */
243 if (ret < 0) {
244 pr_err("parse error: %s.\n", msg);
245 return ret;
246 }
247
248 return size;
249 }
250
show_xbc_error(const char * data,const char * msg,int pos)251 static void show_xbc_error(const char *data, const char *msg, int pos)
252 {
253 int lin = 1, col, i;
254
255 if (pos < 0) {
256 pr_err("Error: %s.\n", msg);
257 return;
258 }
259
260 /* Note that pos starts from 0 but lin and col should start from 1. */
261 col = pos + 1;
262 for (i = 0; i < pos; i++) {
263 if (data[i] == '\n') {
264 lin++;
265 col = pos - i;
266 }
267 }
268 pr_err("Parse Error: %s at %d:%d\n", msg, lin, col);
269
270 }
271
init_xbc_with_error(char * buf,int len)272 static int init_xbc_with_error(char *buf, int len)
273 {
274 char *copy = strdup(buf);
275 const char *msg;
276 int ret, pos;
277
278 if (!copy)
279 return -ENOMEM;
280
281 ret = xbc_init(buf, len, &msg, &pos);
282 if (ret < 0)
283 show_xbc_error(copy, msg, pos);
284 free(copy);
285
286 return ret;
287 }
288
show_xbc(const char * path,bool list)289 static int show_xbc(const char *path, bool list)
290 {
291 int ret, fd;
292 char *buf = NULL;
293 struct stat st;
294
295 ret = stat(path, &st);
296 if (ret < 0) {
297 ret = -errno;
298 pr_err("Failed to stat %s: %d\n", path, ret);
299 return ret;
300 }
301
302 fd = open(path, O_RDONLY);
303 if (fd < 0) {
304 ret = -errno;
305 pr_err("Failed to open initrd %s: %d\n", path, ret);
306 return ret;
307 }
308
309 ret = load_xbc_from_initrd(fd, &buf);
310 close(fd);
311 if (ret < 0) {
312 pr_err("Failed to load a boot config from initrd: %d\n", ret);
313 goto out;
314 }
315 /* Assume a bootconfig file if it is enough small */
316 if (ret == 0 && st.st_size <= XBC_DATA_MAX) {
317 ret = load_xbc_file(path, &buf);
318 if (ret < 0) {
319 pr_err("Failed to load a boot config: %d\n", ret);
320 goto out;
321 }
322 if (init_xbc_with_error(buf, ret) < 0)
323 goto out;
324 }
325 if (list)
326 xbc_show_list();
327 else
328 xbc_show_compact_tree();
329 ret = 0;
330 out:
331 free(buf);
332
333 return ret;
334 }
335
delete_xbc(const char * path)336 static int delete_xbc(const char *path)
337 {
338 struct stat stat;
339 int ret = 0, fd, size;
340 char *buf = NULL;
341
342 fd = open(path, O_RDWR);
343 if (fd < 0) {
344 ret = -errno;
345 pr_err("Failed to open initrd %s: %d\n", path, ret);
346 return ret;
347 }
348
349 size = load_xbc_from_initrd(fd, &buf);
350 if (size < 0) {
351 ret = size;
352 pr_err("Failed to load a boot config from initrd: %d\n", ret);
353 } else if (size > 0) {
354 ret = fstat(fd, &stat);
355 if (!ret)
356 ret = ftruncate(fd, stat.st_size
357 - size - BOOTCONFIG_FOOTER_SIZE);
358 if (ret)
359 ret = -errno;
360 } /* Ignore if there is no boot config in initrd */
361
362 close(fd);
363 free(buf);
364
365 return ret;
366 }
367
apply_xbc(const char * path,const char * xbc_path)368 static int apply_xbc(const char *path, const char *xbc_path)
369 {
370 struct {
371 uint32_t size;
372 uint32_t csum;
373 char magic[BOOTCONFIG_MAGIC_LEN];
374 } footer;
375 char *buf, *data;
376 size_t total_size;
377 struct stat stat;
378 const char *msg;
379 uint32_t size, csum;
380 int pos, pad;
381 int ret, fd;
382
383 ret = load_xbc_file(xbc_path, &buf);
384 if (ret < 0) {
385 pr_err("Failed to load %s : %d\n", xbc_path, ret);
386 return ret;
387 }
388 size = strlen(buf) + 1;
389 csum = xbc_calc_checksum(buf, size);
390
391 /* Backup the bootconfig data */
392 data = calloc(size + BOOTCONFIG_ALIGN + BOOTCONFIG_FOOTER_SIZE, 1);
393 if (!data)
394 return -ENOMEM;
395 memcpy(data, buf, size);
396
397 /* Check the data format */
398 ret = xbc_init(buf, size, &msg, &pos);
399 if (ret < 0) {
400 show_xbc_error(data, msg, pos);
401 free(data);
402 free(buf);
403
404 return ret;
405 }
406 printf("Apply %s to %s\n", xbc_path, path);
407 xbc_get_info(&ret, NULL);
408 printf("\tNumber of nodes: %d\n", ret);
409 printf("\tSize: %u bytes\n", (unsigned int)size);
410 printf("\tChecksum: %u\n", (unsigned int)csum);
411
412 /* TODO: Check the options by schema */
413 xbc_exit();
414 free(buf);
415
416 /* Remove old boot config if exists */
417 ret = delete_xbc(path);
418 if (ret < 0) {
419 pr_err("Failed to delete previous boot config: %d\n", ret);
420 free(data);
421 return ret;
422 }
423
424 /* Apply new one */
425 fd = open(path, O_RDWR | O_APPEND);
426 if (fd < 0) {
427 ret = -errno;
428 pr_err("Failed to open %s: %d\n", path, ret);
429 free(data);
430 return ret;
431 }
432 /* TODO: Ensure the @path is initramfs/initrd image */
433 if (fstat(fd, &stat) < 0) {
434 ret = -errno;
435 pr_err("Failed to get the size of %s\n", path);
436 goto out;
437 }
438
439 /* To align up the total size to BOOTCONFIG_ALIGN, get padding size */
440 total_size = stat.st_size + size + BOOTCONFIG_FOOTER_SIZE;
441 pad = ((total_size + BOOTCONFIG_ALIGN - 1) & (~BOOTCONFIG_ALIGN_MASK)) - total_size;
442 size += pad;
443
444 /* Add a footer */
445 footer.size = htole32(size);
446 footer.csum = htole32(csum);
447 memcpy(footer.magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN);
448 static_assert(sizeof(footer) == BOOTCONFIG_FOOTER_SIZE);
449 memcpy(data + size, &footer, BOOTCONFIG_FOOTER_SIZE);
450
451 total_size = size + BOOTCONFIG_FOOTER_SIZE;
452
453 ret = write(fd, data, total_size);
454 if (ret < total_size) {
455 if (ret < 0)
456 ret = -errno;
457 pr_err("Failed to apply a boot config: %d\n", ret);
458 if (ret >= 0)
459 goto out_rollback;
460 } else
461 ret = 0;
462
463 out:
464 close(fd);
465 free(data);
466
467 return ret;
468
469 out_rollback:
470 /* Map the partial write to -ENOSPC */
471 if (ret >= 0)
472 ret = -ENOSPC;
473 if (ftruncate(fd, stat.st_size) < 0) {
474 ret = -errno;
475 pr_err("Failed to rollback the write error: %d\n", ret);
476 pr_err("The initrd %s may be corrupted. Recommend to rebuild.\n", path);
477 }
478 goto out;
479 }
480
usage(void)481 static int usage(void)
482 {
483 printf("Usage: bootconfig [OPTIONS] <INITRD>\n"
484 "Or bootconfig <CONFIG>\n"
485 " Apply, delete or show boot config to initrd.\n"
486 " Options:\n"
487 " -a <config>: Apply boot config to initrd\n"
488 " -d : Delete boot config file from initrd\n"
489 " -l : list boot config in initrd or file\n\n"
490 " If no option is given, show the bootconfig in the given file.\n");
491 return -1;
492 }
493
main(int argc,char ** argv)494 int main(int argc, char **argv)
495 {
496 char *path = NULL;
497 char *apply = NULL;
498 bool delete = false, list = false;
499 int opt;
500
501 while ((opt = getopt(argc, argv, "hda:l")) != -1) {
502 switch (opt) {
503 case 'd':
504 delete = true;
505 break;
506 case 'a':
507 apply = optarg;
508 break;
509 case 'l':
510 list = true;
511 break;
512 case 'h':
513 default:
514 return usage();
515 }
516 }
517
518 if ((apply && delete) || (delete && list) || (apply && list)) {
519 pr_err("Error: You can give one of -a, -d or -l at once.\n");
520 return usage();
521 }
522
523 if (optind >= argc) {
524 pr_err("Error: No initrd is specified.\n");
525 return usage();
526 }
527
528 path = argv[optind];
529
530 if (apply)
531 return apply_xbc(path, apply);
532 else if (delete)
533 return delete_xbc(path);
534
535 return show_xbc(path, list);
536 }
537