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