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