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