Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
24static 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
42static 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
115static 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
139static 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 */
156static 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
175static int pr_errno(const char *msg, int err)
176{
177 pr_err("%s: %d\n", msg, err);
178 return err;
179}
180
181static 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, -(off_t)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, -(off_t)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
248static 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
269static 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
286static 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;
327out:
328 free(buf);
329
330 return ret;
331}
332
333static 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
365static 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
460out:
461 close(fd);
462 free(data);
463
464 return ret;
465
466out_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
478static 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
491int 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}