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 return -errno;
167
168 ret = load_xbc_fd(fd, buf, stat.st_size);
169
170 close(fd);
171
172 return ret;
173 }
174
pr_errno(const char * msg,int err)175 static int pr_errno(const char *msg, int err)
176 {
177 pr_err("%s: %d\n", msg, err);
178 return err;
179 }
180
load_xbc_from_initrd(int fd,char ** buf)181 static int load_xbc_from_initrd(int fd, char **buf)
182 {
183 struct stat stat;
184 int ret;
185 uint32_t size = 0, csum = 0, rcsum;
186 char magic[BOOTCONFIG_MAGIC_LEN];
187 const char *msg;
188
189 ret = fstat(fd, &stat);
190 if (ret < 0)
191 return -errno;
192
193 if (stat.st_size < BOOTCONFIG_FOOTER_SIZE)
194 return 0;
195
196 if (lseek(fd, -BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0)
197 return pr_errno("Failed to lseek for magic", -errno);
198
199 if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0)
200 return pr_errno("Failed to read", -errno);
201
202 /* Check the bootconfig magic bytes */
203 if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0)
204 return 0;
205
206 if (lseek(fd, -BOOTCONFIG_FOOTER_SIZE, SEEK_END) < 0)
207 return pr_errno("Failed to lseek for size", -errno);
208
209 if (read(fd, &size, sizeof(uint32_t)) < 0)
210 return pr_errno("Failed to read size", -errno);
211 size = le32toh(size);
212
213 if (read(fd, &csum, sizeof(uint32_t)) < 0)
214 return pr_errno("Failed to read checksum", -errno);
215 csum = le32toh(csum);
216
217 /* Wrong size error */
218 if (stat.st_size < size + BOOTCONFIG_FOOTER_SIZE) {
219 pr_err("bootconfig size is too big\n");
220 return -E2BIG;
221 }
222
223 if (lseek(fd, stat.st_size - (size + BOOTCONFIG_FOOTER_SIZE),
224 SEEK_SET) < 0)
225 return pr_errno("Failed to lseek", -errno);
226
227 ret = load_xbc_fd(fd, buf, size);
228 if (ret < 0)
229 return ret;
230
231 /* Wrong Checksum */
232 rcsum = xbc_calc_checksum(*buf, size);
233 if (csum != rcsum) {
234 pr_err("checksum error: %u != %u\n", csum, rcsum);
235 return -EINVAL;
236 }
237
238 ret = xbc_init(*buf, size, &msg, NULL);
239 /* Wrong data */
240 if (ret < 0) {
241 pr_err("parse error: %s.\n", msg);
242 return ret;
243 }
244
245 return size;
246 }
247
show_xbc_error(const char * data,const char * msg,int pos)248 static void show_xbc_error(const char *data, const char *msg, int pos)
249 {
250 int lin = 1, col, i;
251
252 if (pos < 0) {
253 pr_err("Error: %s.\n", msg);
254 return;
255 }
256
257 /* Note that pos starts from 0 but lin and col should start from 1. */
258 col = pos + 1;
259 for (i = 0; i < pos; i++) {
260 if (data[i] == '\n') {
261 lin++;
262 col = pos - i;
263 }
264 }
265 pr_err("Parse Error: %s at %d:%d\n", msg, lin, col);
266
267 }
268
init_xbc_with_error(char * buf,int len)269 static int init_xbc_with_error(char *buf, int len)
270 {
271 char *copy = strdup(buf);
272 const char *msg;
273 int ret, pos;
274
275 if (!copy)
276 return -ENOMEM;
277
278 ret = xbc_init(buf, len, &msg, &pos);
279 if (ret < 0)
280 show_xbc_error(copy, msg, pos);
281 free(copy);
282
283 return ret;
284 }
285
show_xbc(const char * path,bool list)286 static int show_xbc(const char *path, bool list)
287 {
288 int ret, fd;
289 char *buf = NULL;
290 struct stat st;
291
292 ret = stat(path, &st);
293 if (ret < 0) {
294 ret = -errno;
295 pr_err("Failed to stat %s: %d\n", path, ret);
296 return ret;
297 }
298
299 fd = open(path, O_RDONLY);
300 if (fd < 0) {
301 ret = -errno;
302 pr_err("Failed to open initrd %s: %d\n", path, ret);
303 return ret;
304 }
305
306 ret = load_xbc_from_initrd(fd, &buf);
307 close(fd);
308 if (ret < 0) {
309 pr_err("Failed to load a boot config from initrd: %d\n", ret);
310 goto out;
311 }
312 /* Assume a bootconfig file if it is enough small */
313 if (ret == 0 && st.st_size <= XBC_DATA_MAX) {
314 ret = load_xbc_file(path, &buf);
315 if (ret < 0) {
316 pr_err("Failed to load a boot config: %d\n", ret);
317 goto out;
318 }
319 if (init_xbc_with_error(buf, ret) < 0)
320 goto out;
321 }
322 if (list)
323 xbc_show_list();
324 else
325 xbc_show_compact_tree();
326 ret = 0;
327 out:
328 free(buf);
329
330 return ret;
331 }
332
delete_xbc(const char * path)333 static int delete_xbc(const char *path)
334 {
335 struct stat stat;
336 int ret = 0, fd, size;
337 char *buf = NULL;
338
339 fd = open(path, O_RDWR);
340 if (fd < 0) {
341 ret = -errno;
342 pr_err("Failed to open initrd %s: %d\n", path, ret);
343 return ret;
344 }
345
346 size = load_xbc_from_initrd(fd, &buf);
347 if (size < 0) {
348 ret = size;
349 pr_err("Failed to load a boot config from initrd: %d\n", ret);
350 } else if (size > 0) {
351 ret = fstat(fd, &stat);
352 if (!ret)
353 ret = ftruncate(fd, stat.st_size
354 - size - BOOTCONFIG_FOOTER_SIZE);
355 if (ret)
356 ret = -errno;
357 } /* Ignore if there is no boot config in initrd */
358
359 close(fd);
360 free(buf);
361
362 return ret;
363 }
364
apply_xbc(const char * path,const char * xbc_path)365 static int apply_xbc(const char *path, const char *xbc_path)
366 {
367 struct {
368 uint32_t size;
369 uint32_t csum;
370 char magic[BOOTCONFIG_MAGIC_LEN];
371 } footer;
372 char *buf, *data;
373 size_t total_size;
374 struct stat stat;
375 const char *msg;
376 uint32_t size, csum;
377 int pos, pad;
378 int ret, fd;
379
380 ret = load_xbc_file(xbc_path, &buf);
381 if (ret < 0) {
382 pr_err("Failed to load %s : %d\n", xbc_path, ret);
383 return ret;
384 }
385 size = strlen(buf) + 1;
386 csum = xbc_calc_checksum(buf, size);
387
388 /* Backup the bootconfig data */
389 data = calloc(size + BOOTCONFIG_ALIGN + BOOTCONFIG_FOOTER_SIZE, 1);
390 if (!data)
391 return -ENOMEM;
392 memcpy(data, buf, size);
393
394 /* Check the data format */
395 ret = xbc_init(buf, size, &msg, &pos);
396 if (ret < 0) {
397 show_xbc_error(data, msg, pos);
398 free(data);
399 free(buf);
400
401 return ret;
402 }
403 printf("Apply %s to %s\n", xbc_path, path);
404 xbc_get_info(&ret, NULL);
405 printf("\tNumber of nodes: %d\n", ret);
406 printf("\tSize: %u bytes\n", (unsigned int)size);
407 printf("\tChecksum: %u\n", (unsigned int)csum);
408
409 /* TODO: Check the options by schema */
410 xbc_exit();
411 free(buf);
412
413 /* Remove old boot config if exists */
414 ret = delete_xbc(path);
415 if (ret < 0) {
416 pr_err("Failed to delete previous boot config: %d\n", ret);
417 free(data);
418 return ret;
419 }
420
421 /* Apply new one */
422 fd = open(path, O_RDWR | O_APPEND);
423 if (fd < 0) {
424 ret = -errno;
425 pr_err("Failed to open %s: %d\n", path, ret);
426 free(data);
427 return ret;
428 }
429 /* TODO: Ensure the @path is initramfs/initrd image */
430 if (fstat(fd, &stat) < 0) {
431 ret = -errno;
432 pr_err("Failed to get the size of %s\n", path);
433 goto out;
434 }
435
436 /* To align up the total size to BOOTCONFIG_ALIGN, get padding size */
437 total_size = stat.st_size + size + BOOTCONFIG_FOOTER_SIZE;
438 pad = ((total_size + BOOTCONFIG_ALIGN - 1) & (~BOOTCONFIG_ALIGN_MASK)) - total_size;
439 size += pad;
440
441 /* Add a footer */
442 footer.size = htole32(size);
443 footer.csum = htole32(csum);
444 memcpy(footer.magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN);
445 static_assert(sizeof(footer) == BOOTCONFIG_FOOTER_SIZE);
446 memcpy(data + size, &footer, BOOTCONFIG_FOOTER_SIZE);
447
448 total_size = size + BOOTCONFIG_FOOTER_SIZE;
449
450 ret = write(fd, data, total_size);
451 if (ret < total_size) {
452 if (ret < 0)
453 ret = -errno;
454 pr_err("Failed to apply a boot config: %d\n", ret);
455 if (ret >= 0)
456 goto out_rollback;
457 } else
458 ret = 0;
459
460 out:
461 close(fd);
462 free(data);
463
464 return ret;
465
466 out_rollback:
467 /* Map the partial write to -ENOSPC */
468 if (ret >= 0)
469 ret = -ENOSPC;
470 if (ftruncate(fd, stat.st_size) < 0) {
471 ret = -errno;
472 pr_err("Failed to rollback the write error: %d\n", ret);
473 pr_err("The initrd %s may be corrupted. Recommend to rebuild.\n", path);
474 }
475 goto out;
476 }
477
usage(void)478 static int usage(void)
479 {
480 printf("Usage: bootconfig [OPTIONS] <INITRD>\n"
481 "Or bootconfig <CONFIG>\n"
482 " Apply, delete or show boot config to initrd.\n"
483 " Options:\n"
484 " -a <config>: Apply boot config to initrd\n"
485 " -d : Delete boot config file from initrd\n"
486 " -l : list boot config in initrd or file\n\n"
487 " If no option is given, show the bootconfig in the given file.\n");
488 return -1;
489 }
490
main(int argc,char ** argv)491 int main(int argc, char **argv)
492 {
493 char *path = NULL;
494 char *apply = NULL;
495 bool delete = false, list = false;
496 int opt;
497
498 while ((opt = getopt(argc, argv, "hda:l")) != -1) {
499 switch (opt) {
500 case 'd':
501 delete = true;
502 break;
503 case 'a':
504 apply = optarg;
505 break;
506 case 'l':
507 list = true;
508 break;
509 case 'h':
510 default:
511 return usage();
512 }
513 }
514
515 if ((apply && delete) || (delete && list) || (apply && list)) {
516 pr_err("Error: You can give one of -a, -d or -l at once.\n");
517 return usage();
518 }
519
520 if (optind >= argc) {
521 pr_err("Error: No initrd is specified.\n");
522 return usage();
523 }
524
525 path = argv[optind];
526
527 if (apply)
528 return apply_xbc(path, apply);
529 else if (delete)
530 return delete_xbc(path);
531
532 return show_xbc(path, list);
533 }
534