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 *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 */ 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 175 static int pr_errno(const char *msg, int err) 176 { 177 pr_err("%s: %d\n", msg, err); 178 return err; 179 } 180 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, -(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 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 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 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 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 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 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 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