tools: bootconfig: Add bootconfig command
authorMasami Hiramatsu <mhiramat@kernel.org>
Fri, 10 Jan 2020 16:03:56 +0000 (01:03 +0900)
committerSteven Rostedt (VMware) <rostedt@goodmis.org>
Mon, 13 Jan 2020 18:19:39 +0000 (13:19 -0500)
Add "bootconfig" command which operates the bootconfig
config-data on initrd image.

User can add/delete/verify the boot config on initrd
image using this command.

e.g.
Add a boot config to initrd image
 # bootconfig -a myboot.conf /boot/initrd.img

Remove it.
 # bootconfig -d /boot/initrd.img

Or verify (and show) it.
 # bootconfig /boot/initrd.img

Link: http://lkml.kernel.org/r/157867223582.17873.14342161849213219982.stgit@devnote2
Signed-off-by: Masami Hiramatsu <mhiramat@kernel.org>
[ Removed extra blank line at end of bootconfig.c ]
Signed-off-by: Steven Rostedt (VMware) <rostedt@goodmis.org>
12 files changed:
MAINTAINERS
tools/Makefile
tools/bootconfig/.gitignore [new file with mode: 0644]
tools/bootconfig/Makefile [new file with mode: 0644]
tools/bootconfig/include/linux/bootconfig.h [new file with mode: 0644]
tools/bootconfig/include/linux/bug.h [new file with mode: 0644]
tools/bootconfig/include/linux/ctype.h [new file with mode: 0644]
tools/bootconfig/include/linux/errno.h [new file with mode: 0644]
tools/bootconfig/include/linux/kernel.h [new file with mode: 0644]
tools/bootconfig/include/linux/printk.h [new file with mode: 0644]
tools/bootconfig/include/linux/string.h [new file with mode: 0644]
tools/bootconfig/main.c [new file with mode: 0644]

index 8597285eb7c8c4f85afb67632a4374010aa55912..06005006de7cb8e436ddfcbcfaa1372ab4f36c48 100644 (file)
@@ -15775,6 +15775,7 @@ M:      Masami Hiramatsu <mhiramat@kernel.org>
 S:     Maintained
 F:     lib/bootconfig.c
 F:     include/linux/bootconfig.h
+F:     tools/bootconfig/*
 
 SUN3/3X
 M:     Sam Creasey <sammy@sammy.net>
index 7e42f7b8bfa7349fd505303ed85c44e17a16ae1b..bd778812e9157adfec138be1c1b2b31c3588a96b 100644 (file)
@@ -28,6 +28,7 @@ help:
        @echo '  pci                    - PCI tools'
        @echo '  perf                   - Linux performance measurement and analysis tool'
        @echo '  selftests              - various kernel selftests'
+       @echo '  bootconfig             - boot config tool'
        @echo '  spi                    - spi tools'
        @echo '  tmon                   - thermal monitoring and tuning tool'
        @echo '  turbostat              - Intel CPU idle stats and freq reporting tool'
@@ -63,7 +64,7 @@ acpi: FORCE
 cpupower: FORCE
        $(call descend,power/$@)
 
-cgroup firewire hv guest spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware debugging: FORCE
+cgroup firewire hv guest bootconfig spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware debugging: FORCE
        $(call descend,$@)
 
 liblockdep: FORCE
@@ -96,7 +97,7 @@ kvm_stat: FORCE
        $(call descend,kvm/$@)
 
 all: acpi cgroup cpupower gpio hv firewire liblockdep \
-               perf selftests spi turbostat usb \
+               perf selftests bootconfig spi turbostat usb \
                virtio vm bpf x86_energy_perf_policy \
                tmon freefall iio objtool kvm_stat wmi \
                pci debugging
@@ -107,7 +108,7 @@ acpi_install:
 cpupower_install:
        $(call descend,power/$(@:_install=),install)
 
-cgroup_install firewire_install gpio_install hv_install iio_install perf_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install pci_install debugging_install:
+cgroup_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install pci_install debugging_install:
        $(call descend,$(@:_install=),install)
 
 liblockdep_install:
@@ -141,7 +142,7 @@ acpi_clean:
 cpupower_clean:
        $(call descend,power/cpupower,clean)
 
-cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean:
+cgroup_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean:
        $(call descend,$(@:_clean=),clean)
 
 liblockdep_clean:
@@ -176,7 +177,7 @@ build_clean:
        $(call descend,build,clean)
 
 clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \
-               perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \
+               perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \
                vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
                freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \
                gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \
diff --git a/tools/bootconfig/.gitignore b/tools/bootconfig/.gitignore
new file mode 100644 (file)
index 0000000..e7644df
--- /dev/null
@@ -0,0 +1 @@
+bootconfig
diff --git a/tools/bootconfig/Makefile b/tools/bootconfig/Makefile
new file mode 100644 (file)
index 0000000..681b7ae
--- /dev/null
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0
+# Makefile for bootconfig command
+
+bindir ?= /usr/bin
+
+HEADER = include/linux/bootconfig.h
+CFLAGS = -Wall -g -I./include
+
+PROGS = bootconfig
+
+all: $(PROGS)
+
+bootconfig: ../../lib/bootconfig.c main.c $(HEADER)
+       $(CC) $(filter %.c,$^) $(CFLAGS) -o $@
+
+install: $(PROGS)
+       install bootconfig $(DESTDIR)$(bindir)
+
+clean:
+       $(RM) -f *.o bootconfig
diff --git a/tools/bootconfig/include/linux/bootconfig.h b/tools/bootconfig/include/linux/bootconfig.h
new file mode 100644 (file)
index 0000000..078cbd2
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BOOTCONFIG_LINUX_BOOTCONFIG_H
+#define _BOOTCONFIG_LINUX_BOOTCONFIG_H
+
+#include "../../../../include/linux/bootconfig.h"
+
+#endif
diff --git a/tools/bootconfig/include/linux/bug.h b/tools/bootconfig/include/linux/bug.h
new file mode 100644 (file)
index 0000000..7b65a38
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SKC_LINUX_BUG_H
+#define _SKC_LINUX_BUG_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define WARN_ON(cond)  \
+       ((cond) ? printf("Internal warning(%s:%d, %s): %s\n",   \
+                       __FILE__, __LINE__, __func__, #cond) : 0)
+
+#endif
diff --git a/tools/bootconfig/include/linux/ctype.h b/tools/bootconfig/include/linux/ctype.h
new file mode 100644 (file)
index 0000000..c56ecc1
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SKC_LINUX_CTYPE_H
+#define _SKC_LINUX_CTYPE_H
+
+#include <ctype.h>
+
+#endif
diff --git a/tools/bootconfig/include/linux/errno.h b/tools/bootconfig/include/linux/errno.h
new file mode 100644 (file)
index 0000000..5d9f91e
--- /dev/null
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SKC_LINUX_ERRNO_H
+#define _SKC_LINUX_ERRNO_H
+
+#include <asm/errno.h>
+
+#endif
diff --git a/tools/bootconfig/include/linux/kernel.h b/tools/bootconfig/include/linux/kernel.h
new file mode 100644 (file)
index 0000000..2d93320
--- /dev/null
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SKC_LINUX_KERNEL_H
+#define _SKC_LINUX_KERNEL_H
+
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include <linux/printk.h>
+
+typedef unsigned short u16;
+typedef unsigned int   u32;
+
+#define unlikely(cond) (cond)
+
+#define __init
+#define __initdata
+
+#endif
diff --git a/tools/bootconfig/include/linux/printk.h b/tools/bootconfig/include/linux/printk.h
new file mode 100644 (file)
index 0000000..017bcd6
--- /dev/null
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SKC_LINUX_PRINTK_H
+#define _SKC_LINUX_PRINTK_H
+
+#include <stdio.h>
+
+/* controllable printf */
+extern int pr_output;
+#define printk(fmt, ...)       \
+       (pr_output ? printf(fmt, __VA_ARGS__) : 0)
+
+#define pr_err printk
+#define pr_warn        printk
+#define pr_info        printk
+#define pr_debug printk
+
+#endif
diff --git a/tools/bootconfig/include/linux/string.h b/tools/bootconfig/include/linux/string.h
new file mode 100644 (file)
index 0000000..8267af7
--- /dev/null
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SKC_LINUX_STRING_H
+#define _SKC_LINUX_STRING_H
+
+#include <string.h>
+
+/* Copied from lib/string.c */
+static inline char *skip_spaces(const char *str)
+{
+       while (isspace(*str))
+               ++str;
+       return (char *)str;
+}
+
+static inline char *strim(char *s)
+{
+       size_t size;
+       char *end;
+
+       size = strlen(s);
+       if (!size)
+               return s;
+
+       end = s + size - 1;
+       while (end >= s && isspace(*end))
+               end--;
+       *(end + 1) = '\0';
+
+       return skip_spaces(s);
+}
+
+#endif
diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c
new file mode 100644 (file)
index 0000000..b8f174f
--- /dev/null
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Boot config tool for initrd image
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <linux/kernel.h>
+#include <linux/bootconfig.h>
+
+int pr_output = 1;
+
+static int xbc_show_array(struct xbc_node *node)
+{
+       const char *val;
+       int i = 0;
+
+       xbc_array_for_each_value(node, val) {
+               printf("\"%s\"%s", val, node->next ? ", " : ";\n");
+               i++;
+       }
+       return i;
+}
+
+static void xbc_show_compact_tree(void)
+{
+       struct xbc_node *node, *cnode;
+       int depth = 0, i;
+
+       node = xbc_root_node();
+       while (node && xbc_node_is_key(node)) {
+               for (i = 0; i < depth; i++)
+                       printf("\t");
+               cnode = xbc_node_get_child(node);
+               while (cnode && xbc_node_is_key(cnode) && !cnode->next) {
+                       printf("%s.", xbc_node_get_data(node));
+                       node = cnode;
+                       cnode = xbc_node_get_child(node);
+               }
+               if (cnode && xbc_node_is_key(cnode)) {
+                       printf("%s {\n", xbc_node_get_data(node));
+                       depth++;
+                       node = cnode;
+                       continue;
+               } else if (cnode && xbc_node_is_value(cnode)) {
+                       printf("%s = ", xbc_node_get_data(node));
+                       if (cnode->next)
+                               xbc_show_array(cnode);
+                       else
+                               printf("\"%s\";\n", xbc_node_get_data(cnode));
+               } else {
+                       printf("%s;\n", xbc_node_get_data(node));
+               }
+
+               if (node->next) {
+                       node = xbc_node_get_next(node);
+                       continue;
+               }
+               while (!node->next) {
+                       node = xbc_node_get_parent(node);
+                       if (!node)
+                               return;
+                       if (!xbc_node_get_child(node)->next)
+                               continue;
+                       depth--;
+                       for (i = 0; i < depth; i++)
+                               printf("\t");
+                       printf("}\n");
+               }
+               node = xbc_node_get_next(node);
+       }
+}
+
+/* Simple real checksum */
+int checksum(unsigned char *buf, int len)
+{
+       int i, sum = 0;
+
+       for (i = 0; i < len; i++)
+               sum += buf[i];
+
+       return sum;
+}
+
+#define PAGE_SIZE      4096
+
+int load_xbc_fd(int fd, char **buf, int size)
+{
+       int ret;
+
+       *buf = malloc(size + 1);
+       if (!*buf)
+               return -ENOMEM;
+
+       ret = read(fd, *buf, size);
+       if (ret < 0)
+               return -errno;
+       (*buf)[size] = '\0';
+
+       return ret;
+}
+
+/* Return the read size or -errno */
+int load_xbc_file(const char *path, char **buf)
+{
+       struct stat stat;
+       int fd, ret;
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return -errno;
+       ret = fstat(fd, &stat);
+       if (ret < 0)
+               return -errno;
+
+       ret = load_xbc_fd(fd, buf, stat.st_size);
+
+       close(fd);
+
+       return ret;
+}
+
+int load_xbc_from_initrd(int fd, char **buf)
+{
+       struct stat stat;
+       int ret;
+       u32 size = 0, csum = 0, rcsum;
+
+       ret = fstat(fd, &stat);
+       if (ret < 0)
+               return -errno;
+
+       if (stat.st_size < 8)
+               return 0;
+
+       if (lseek(fd, -8, SEEK_END) < 0) {
+               printf("Faile to lseek: %d\n", -errno);
+               return -errno;
+       }
+
+       if (read(fd, &size, sizeof(u32)) < 0)
+               return -errno;
+
+       if (read(fd, &csum, sizeof(u32)) < 0)
+               return -errno;
+
+       /* Wrong size, maybe no boot config here */
+       if (stat.st_size < size + 8)
+               return 0;
+
+       if (lseek(fd, stat.st_size - 8 - size, SEEK_SET) < 0) {
+               printf("Faile to lseek: %d\n", -errno);
+               return -errno;
+       }
+
+       ret = load_xbc_fd(fd, buf, size);
+       if (ret < 0)
+               return ret;
+
+       /* Wrong Checksum, maybe no boot config here */
+       rcsum = checksum((unsigned char *)*buf, size);
+       if (csum != rcsum) {
+               printf("checksum error: %d != %d\n", csum, rcsum);
+               return 0;
+       }
+
+       ret = xbc_init(*buf);
+       /* Wrong data, maybe no boot config here */
+       if (ret < 0)
+               return 0;
+
+       return size;
+}
+
+int show_xbc(const char *path)
+{
+       int ret, fd;
+       char *buf = NULL;
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0) {
+               printf("Failed to open initrd %s: %d\n", path, fd);
+               return -errno;
+       }
+
+       ret = load_xbc_from_initrd(fd, &buf);
+       if (ret < 0)
+               printf("Failed to load a boot config from initrd: %d\n", ret);
+       else
+               xbc_show_compact_tree();
+
+       close(fd);
+       free(buf);
+
+       return ret;
+}
+
+int delete_xbc(const char *path)
+{
+       struct stat stat;
+       int ret = 0, fd, size;
+       char *buf = NULL;
+
+       fd = open(path, O_RDWR);
+       if (fd < 0) {
+               printf("Failed to open initrd %s: %d\n", path, fd);
+               return -errno;
+       }
+
+       /*
+        * Suppress error messages in xbc_init() because it can be just a
+        * data which concidentally matches the size and checksum footer.
+        */
+       pr_output = 0;
+       size = load_xbc_from_initrd(fd, &buf);
+       pr_output = 1;
+       if (size < 0) {
+               ret = size;
+               printf("Failed to load a boot config from initrd: %d\n", ret);
+       } else if (size > 0) {
+               ret = fstat(fd, &stat);
+               if (!ret)
+                       ret = ftruncate(fd, stat.st_size - size - 8);
+               if (ret)
+                       ret = -errno;
+       } /* Ignore if there is no boot config in initrd */
+
+       close(fd);
+       free(buf);
+
+       return ret;
+}
+
+int apply_xbc(const char *path, const char *xbc_path)
+{
+       u32 size, csum;
+       char *buf, *data;
+       int ret, fd;
+
+       ret = load_xbc_file(xbc_path, &buf);
+       if (ret < 0) {
+               printf("Failed to load %s : %d\n", xbc_path, ret);
+               return ret;
+       }
+       size = strlen(buf) + 1;
+       csum = checksum((unsigned char *)buf, size);
+
+       /* Prepare xbc_path data */
+       data = malloc(size + 8);
+       if (!data)
+               return -ENOMEM;
+       strcpy(data, buf);
+       *(u32 *)(data + size) = size;
+       *(u32 *)(data + size + 4) = csum;
+
+       /* Check the data format */
+       ret = xbc_init(buf);
+       if (ret < 0) {
+               printf("Failed to parse %s: %d\n", xbc_path, ret);
+               free(data);
+               free(buf);
+               return ret;
+       }
+       printf("Apply %s to %s\n", xbc_path, path);
+       printf("\tSize: %u bytes\n", (unsigned int)size);
+       printf("\tChecksum: %d\n", (unsigned int)csum);
+
+       /* TODO: Check the options by schema */
+       xbc_destroy_all();
+       free(buf);
+
+       /* Remove old boot config if exists */
+       ret = delete_xbc(path);
+       if (ret < 0) {
+               printf("Failed to delete previous boot config: %d\n", ret);
+               return ret;
+       }
+
+       /* Apply new one */
+       fd = open(path, O_RDWR | O_APPEND);
+       if (fd < 0) {
+               printf("Failed to open %s: %d\n", path, fd);
+               return fd;
+       }
+       /* TODO: Ensure the @path is initramfs/initrd image */
+       ret = write(fd, data, size + 8);
+       if (ret < 0) {
+               printf("Failed to apply a boot config: %d\n", ret);
+               return ret;
+       }
+       close(fd);
+       free(data);
+
+       return 0;
+}
+
+int usage(void)
+{
+       printf("Usage: bootconfig [OPTIONS] <INITRD>\n"
+               " Apply, delete or show boot config to initrd.\n"
+               " Options:\n"
+               "               -a <config>: Apply boot config to initrd\n"
+               "               -d : Delete boot config file from initrd\n\n"
+               " If no option is given, show current applied boot config.\n");
+       return -1;
+}
+
+int main(int argc, char **argv)
+{
+       char *path = NULL;
+       char *apply = NULL;
+       bool delete = false;
+       int opt;
+
+       while ((opt = getopt(argc, argv, "hda:")) != -1) {
+               switch (opt) {
+               case 'd':
+                       delete = true;
+                       break;
+               case 'a':
+                       apply = optarg;
+                       break;
+               case 'h':
+               default:
+                       return usage();
+               }
+       }
+
+       if (apply && delete) {
+               printf("Error: You can not specify both -a and -d at once.\n");
+               return usage();
+       }
+
+       if (optind >= argc) {
+               printf("Error: No initrd is specified.\n");
+               return usage();
+       }
+
+       path = argv[optind];
+
+       if (apply)
+               return apply_xbc(path, apply);
+       else if (delete)
+               return delete_xbc(path);
+
+       return show_xbc(path);
+}