diff options
Diffstat (limited to 'tools/bootconfig')
36 files changed, 1635 insertions, 0 deletions
diff --git a/tools/bootconfig/.gitignore b/tools/bootconfig/.gitignore new file mode 100644 index 000000000..b77513cae --- /dev/null +++ b/tools/bootconfig/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +bootconfig diff --git a/tools/bootconfig/Makefile b/tools/bootconfig/Makefile new file mode 100644 index 000000000..566c3e0ee --- /dev/null +++ b/tools/bootconfig/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for bootconfig command +include ../scripts/Makefile.include + +bindir ?= /usr/bin + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +LIBSRC = $(srctree)/lib/bootconfig.c $(srctree)/include/linux/bootconfig.h +CFLAGS = -Wall -g -I$(CURDIR)/include + +ALL_TARGETS := bootconfig +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) test + +$(OUTPUT)bootconfig: main.c include/linux/bootconfig.h $(LIBSRC) + $(CC) $(filter %.c,$^) $(CFLAGS) -o $@ + +test: $(ALL_PROGRAMS) test-bootconfig.sh + ./test-bootconfig.sh $(OUTPUT) + +install: $(ALL_PROGRAMS) + install $(OUTPUT)bootconfig $(DESTDIR)$(bindir) + +clean: + $(RM) -f $(OUTPUT)*.o $(ALL_PROGRAMS) diff --git a/tools/bootconfig/include/linux/bootconfig.h b/tools/bootconfig/include/linux/bootconfig.h new file mode 100644 index 000000000..6784296a0 --- /dev/null +++ b/tools/bootconfig/include/linux/bootconfig.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BOOTCONFIG_LINUX_BOOTCONFIG_H +#define _BOOTCONFIG_LINUX_BOOTCONFIG_H + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <ctype.h> +#include <errno.h> +#include <string.h> + + +#ifndef fallthrough +# define fallthrough +#endif + +#define WARN_ON(cond) \ + ((cond) ? printf("Internal warning(%s:%d, %s): %s\n", \ + __FILE__, __LINE__, __func__, #cond) : 0) + +#define unlikely(cond) (cond) + +/* 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); +} + +#define __init +#define __initdata + +#include "../../../../include/linux/bootconfig.h" + +#endif diff --git a/tools/bootconfig/main.c b/tools/bootconfig/main.c new file mode 100644 index 000000000..156b62a16 --- /dev/null +++ b/tools/bootconfig/main.c @@ -0,0 +1,528 @@ +// 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 <endian.h> + +#include <linux/bootconfig.h> + +#define pr_err(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) + +static int xbc_show_value(struct xbc_node *node, bool semicolon) +{ + const char *val, *eol; + char q; + int i = 0; + + eol = semicolon ? ";\n" : "\n"; + xbc_array_for_each_value(node, val) { + if (strchr(val, '"')) + q = '\''; + else + q = '"'; + printf("%c%s%c%s", q, val, q, xbc_node_is_array(node) ? ", " : eol); + i++; + } + return i; +} + +static void xbc_show_compact_tree(void) +{ + struct xbc_node *node, *cnode = NULL, *vnode; + int depth = 0, i; + + node = xbc_root_node(); + while (node && xbc_node_is_key(node)) { + for (i = 0; i < depth; i++) + printf("\t"); + if (!cnode) + cnode = xbc_node_get_child(node); + while (cnode && xbc_node_is_key(cnode) && !cnode->next) { + vnode = xbc_node_get_child(cnode); + /* + * If @cnode has value and subkeys, this + * should show it as below. + * + * key(@node) { + * key(@cnode) = value; + * key(@cnode) { + * subkeys; + * } + * } + */ + if (vnode && xbc_node_is_value(vnode) && vnode->next) + break; + printf("%s.", xbc_node_get_data(node)); + node = cnode; + cnode = vnode; + } + if (cnode && xbc_node_is_key(cnode)) { + printf("%s {\n", xbc_node_get_data(node)); + depth++; + node = cnode; + cnode = NULL; + continue; + } else if (cnode && xbc_node_is_value(cnode)) { + printf("%s = ", xbc_node_get_data(node)); + xbc_show_value(cnode, true); + /* + * If @node has value and subkeys, continue + * looping on subkeys with same node. + */ + if (cnode->next) { + cnode = xbc_node_get_next(cnode); + continue; + } + } else { + printf("%s;\n", xbc_node_get_data(node)); + } + cnode = NULL; + + 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; + if (depth) { + depth--; + for (i = 0; i < depth; i++) + printf("\t"); + printf("}\n"); + } + } + node = xbc_node_get_next(node); + } +} + +static void xbc_show_list(void) +{ + char key[XBC_KEYLEN_MAX]; + struct xbc_node *leaf; + const char *val; + int ret; + + xbc_for_each_key_value(leaf, val) { + ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX); + if (ret < 0) { + fprintf(stderr, "Failed to compose key %d\n", ret); + break; + } + printf("%s = ", key); + if (!val || val[0] == '\0') { + printf("\"\"\n"); + continue; + } + xbc_show_value(xbc_node_get_child(leaf), false); + } +} + +#define PAGE_SIZE 4096 + +static 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 */ +static 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; +} + +static int pr_errno(const char *msg, int err) +{ + pr_err("%s: %d\n", msg, err); + return err; +} + +static int load_xbc_from_initrd(int fd, char **buf) +{ + struct stat stat; + int ret; + uint32_t size = 0, csum = 0, rcsum; + char magic[BOOTCONFIG_MAGIC_LEN]; + const char *msg; + + ret = fstat(fd, &stat); + if (ret < 0) + return -errno; + + if (stat.st_size < 8 + BOOTCONFIG_MAGIC_LEN) + return 0; + + if (lseek(fd, -BOOTCONFIG_MAGIC_LEN, SEEK_END) < 0) + return pr_errno("Failed to lseek for magic", -errno); + + if (read(fd, magic, BOOTCONFIG_MAGIC_LEN) < 0) + return pr_errno("Failed to read", -errno); + + /* Check the bootconfig magic bytes */ + if (memcmp(magic, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN) != 0) + return 0; + + if (lseek(fd, -(8 + BOOTCONFIG_MAGIC_LEN), SEEK_END) < 0) + return pr_errno("Failed to lseek for size", -errno); + + if (read(fd, &size, sizeof(uint32_t)) < 0) + return pr_errno("Failed to read size", -errno); + size = le32toh(size); + + if (read(fd, &csum, sizeof(uint32_t)) < 0) + return pr_errno("Failed to read checksum", -errno); + csum = le32toh(csum); + + /* Wrong size error */ + if (stat.st_size < size + 8 + BOOTCONFIG_MAGIC_LEN) { + pr_err("bootconfig size is too big\n"); + return -E2BIG; + } + + if (lseek(fd, stat.st_size - (size + 8 + BOOTCONFIG_MAGIC_LEN), + SEEK_SET) < 0) + return pr_errno("Failed to lseek", -errno); + + ret = load_xbc_fd(fd, buf, size); + if (ret < 0) + return ret; + + /* Wrong Checksum */ + rcsum = xbc_calc_checksum(*buf, size); + if (csum != rcsum) { + pr_err("checksum error: %d != %d\n", csum, rcsum); + return -EINVAL; + } + + ret = xbc_init(*buf, size, &msg, NULL); + /* Wrong data */ + if (ret < 0) { + pr_err("parse error: %s.\n", msg); + return ret; + } + + return size; +} + +static void show_xbc_error(const char *data, const char *msg, int pos) +{ + int lin = 1, col, i; + + if (pos < 0) { + pr_err("Error: %s.\n", msg); + return; + } + + /* Note that pos starts from 0 but lin and col should start from 1. */ + col = pos + 1; + for (i = 0; i < pos; i++) { + if (data[i] == '\n') { + lin++; + col = pos - i; + } + } + pr_err("Parse Error: %s at %d:%d\n", msg, lin, col); + +} + +static int init_xbc_with_error(char *buf, int len) +{ + char *copy = strdup(buf); + const char *msg; + int ret, pos; + + if (!copy) + return -ENOMEM; + + ret = xbc_init(buf, len, &msg, &pos); + if (ret < 0) + show_xbc_error(copy, msg, pos); + free(copy); + + return ret; +} + +static int show_xbc(const char *path, bool list) +{ + int ret, fd; + char *buf = NULL; + struct stat st; + + ret = stat(path, &st); + if (ret < 0) { + ret = -errno; + pr_err("Failed to stat %s: %d\n", path, ret); + return ret; + } + + fd = open(path, O_RDONLY); + if (fd < 0) { + ret = -errno; + pr_err("Failed to open initrd %s: %d\n", path, ret); + return ret; + } + + ret = load_xbc_from_initrd(fd, &buf); + close(fd); + if (ret < 0) { + pr_err("Failed to load a boot config from initrd: %d\n", ret); + goto out; + } + /* Assume a bootconfig file if it is enough small */ + if (ret == 0 && st.st_size <= XBC_DATA_MAX) { + ret = load_xbc_file(path, &buf); + if (ret < 0) { + pr_err("Failed to load a boot config: %d\n", ret); + goto out; + } + if (init_xbc_with_error(buf, ret) < 0) + goto out; + } + if (list) + xbc_show_list(); + else + xbc_show_compact_tree(); + ret = 0; +out: + free(buf); + + return ret; +} + +static 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) { + ret = -errno; + pr_err("Failed to open initrd %s: %d\n", path, ret); + return ret; + } + + size = load_xbc_from_initrd(fd, &buf); + if (size < 0) { + ret = size; + pr_err("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 - BOOTCONFIG_MAGIC_LEN); + if (ret) + ret = -errno; + } /* Ignore if there is no boot config in initrd */ + + close(fd); + free(buf); + + return ret; +} + +static int apply_xbc(const char *path, const char *xbc_path) +{ + char *buf, *data, *p; + size_t total_size; + struct stat stat; + const char *msg; + uint32_t size, csum; + int pos, pad; + int ret, fd; + + ret = load_xbc_file(xbc_path, &buf); + if (ret < 0) { + pr_err("Failed to load %s : %d\n", xbc_path, ret); + return ret; + } + size = strlen(buf) + 1; + csum = xbc_calc_checksum(buf, size); + + /* Backup the bootconfig data */ + data = calloc(size + BOOTCONFIG_ALIGN + + sizeof(uint32_t) + sizeof(uint32_t) + BOOTCONFIG_MAGIC_LEN, 1); + if (!data) + return -ENOMEM; + memcpy(data, buf, size); + + /* Check the data format */ + ret = xbc_init(buf, size, &msg, &pos); + if (ret < 0) { + show_xbc_error(data, msg, pos); + free(data); + free(buf); + + return ret; + } + printf("Apply %s to %s\n", xbc_path, path); + xbc_get_info(&ret, NULL); + printf("\tNumber of nodes: %d\n", ret); + printf("\tSize: %u bytes\n", (unsigned int)size); + printf("\tChecksum: %d\n", (unsigned int)csum); + + /* TODO: Check the options by schema */ + xbc_exit(); + free(buf); + + /* Remove old boot config if exists */ + ret = delete_xbc(path); + if (ret < 0) { + pr_err("Failed to delete previous boot config: %d\n", ret); + free(data); + return ret; + } + + /* Apply new one */ + fd = open(path, O_RDWR | O_APPEND); + if (fd < 0) { + ret = -errno; + pr_err("Failed to open %s: %d\n", path, ret); + free(data); + return ret; + } + /* TODO: Ensure the @path is initramfs/initrd image */ + if (fstat(fd, &stat) < 0) { + ret = -errno; + pr_err("Failed to get the size of %s\n", path); + goto out; + } + + /* To align up the total size to BOOTCONFIG_ALIGN, get padding size */ + total_size = stat.st_size + size + sizeof(uint32_t) * 2 + BOOTCONFIG_MAGIC_LEN; + pad = ((total_size + BOOTCONFIG_ALIGN - 1) & (~BOOTCONFIG_ALIGN_MASK)) - total_size; + size += pad; + + /* Add a footer */ + p = data + size; + *(uint32_t *)p = htole32(size); + p += sizeof(uint32_t); + + *(uint32_t *)p = htole32(csum); + p += sizeof(uint32_t); + + memcpy(p, BOOTCONFIG_MAGIC, BOOTCONFIG_MAGIC_LEN); + p += BOOTCONFIG_MAGIC_LEN; + + total_size = p - data; + + ret = write(fd, data, total_size); + if (ret < total_size) { + if (ret < 0) + ret = -errno; + pr_err("Failed to apply a boot config: %d\n", ret); + if (ret >= 0) + goto out_rollback; + } else + ret = 0; + +out: + close(fd); + free(data); + + return ret; + +out_rollback: + /* Map the partial write to -ENOSPC */ + if (ret >= 0) + ret = -ENOSPC; + if (ftruncate(fd, stat.st_size) < 0) { + ret = -errno; + pr_err("Failed to rollback the write error: %d\n", ret); + pr_err("The initrd %s may be corrupted. Recommend to rebuild.\n", path); + } + goto out; +} + +static int usage(void) +{ + printf("Usage: bootconfig [OPTIONS] <INITRD>\n" + "Or bootconfig <CONFIG>\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" + " -l : list boot config in initrd or file\n\n" + " If no option is given, show the bootconfig in the given file.\n"); + return -1; +} + +int main(int argc, char **argv) +{ + char *path = NULL; + char *apply = NULL; + bool delete = false, list = false; + int opt; + + while ((opt = getopt(argc, argv, "hda:l")) != -1) { + switch (opt) { + case 'd': + delete = true; + break; + case 'a': + apply = optarg; + break; + case 'l': + list = true; + break; + case 'h': + default: + return usage(); + } + } + + if ((apply && delete) || (delete && list) || (apply && list)) { + pr_err("Error: You can give one of -a, -d or -l at once.\n"); + return usage(); + } + + if (optind >= argc) { + pr_err("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, list); +} diff --git a/tools/bootconfig/samples/bad-array-space-comment.bconf b/tools/bootconfig/samples/bad-array-space-comment.bconf new file mode 100644 index 000000000..fda19e47d --- /dev/null +++ b/tools/bootconfig/samples/bad-array-space-comment.bconf @@ -0,0 +1,5 @@ +key = # comment + "value1", # comment1 + "value2" # comment2 +, + "value3" diff --git a/tools/bootconfig/samples/bad-array.bconf b/tools/bootconfig/samples/bad-array.bconf new file mode 100644 index 000000000..0174af019 --- /dev/null +++ b/tools/bootconfig/samples/bad-array.bconf @@ -0,0 +1,2 @@ +# Array must be comma separated. +key = "value1" "value2" diff --git a/tools/bootconfig/samples/bad-dotword.bconf b/tools/bootconfig/samples/bad-dotword.bconf new file mode 100644 index 000000000..ba5557b2b --- /dev/null +++ b/tools/bootconfig/samples/bad-dotword.bconf @@ -0,0 +1,4 @@ +# do not start keyword with . +key { + .word = 1 +} diff --git a/tools/bootconfig/samples/bad-empty.bconf b/tools/bootconfig/samples/bad-empty.bconf new file mode 100644 index 000000000..2ba3f6cc6 --- /dev/null +++ b/tools/bootconfig/samples/bad-empty.bconf @@ -0,0 +1 @@ +# Wrong boot config: comment only diff --git a/tools/bootconfig/samples/bad-keyerror.bconf b/tools/bootconfig/samples/bad-keyerror.bconf new file mode 100644 index 000000000..b6e247a09 --- /dev/null +++ b/tools/bootconfig/samples/bad-keyerror.bconf @@ -0,0 +1,2 @@ +# key word can not contain "," +key,word diff --git a/tools/bootconfig/samples/bad-longkey.bconf b/tools/bootconfig/samples/bad-longkey.bconf new file mode 100644 index 000000000..eb97369f9 --- /dev/null +++ b/tools/bootconfig/samples/bad-longkey.bconf @@ -0,0 +1 @@ +key_word_is_too_long01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345 diff --git a/tools/bootconfig/samples/bad-manywords.bconf b/tools/bootconfig/samples/bad-manywords.bconf new file mode 100644 index 000000000..8db81967c --- /dev/null +++ b/tools/bootconfig/samples/bad-manywords.bconf @@ -0,0 +1 @@ +key1.is2.too3.long4.5.6.7.8.9.10.11.12.13.14.15.16.17 diff --git a/tools/bootconfig/samples/bad-no-keyword.bconf b/tools/bootconfig/samples/bad-no-keyword.bconf new file mode 100644 index 000000000..eff268085 --- /dev/null +++ b/tools/bootconfig/samples/bad-no-keyword.bconf @@ -0,0 +1,2 @@ +# No keyword +{} diff --git a/tools/bootconfig/samples/bad-nonprintable.bconf b/tools/bootconfig/samples/bad-nonprintable.bconf new file mode 100644 index 000000000..3bb1a2864 --- /dev/null +++ b/tools/bootconfig/samples/bad-nonprintable.bconf @@ -0,0 +1,2 @@ +# Non printable +key = "" diff --git a/tools/bootconfig/samples/bad-samekey.bconf b/tools/bootconfig/samples/bad-samekey.bconf new file mode 100644 index 000000000..e8d983a45 --- /dev/null +++ b/tools/bootconfig/samples/bad-samekey.bconf @@ -0,0 +1,6 @@ +# Same key value is not allowed +key { + foo = value + bar = value2 +} +key.foo = value diff --git a/tools/bootconfig/samples/bad-spaceword.bconf b/tools/bootconfig/samples/bad-spaceword.bconf new file mode 100644 index 000000000..90c703d32 --- /dev/null +++ b/tools/bootconfig/samples/bad-spaceword.bconf @@ -0,0 +1,2 @@ +# No space between words +key . word diff --git a/tools/bootconfig/samples/bad-tree.bconf b/tools/bootconfig/samples/bad-tree.bconf new file mode 100644 index 000000000..5a6038edc --- /dev/null +++ b/tools/bootconfig/samples/bad-tree.bconf @@ -0,0 +1,5 @@ +# brace is not closing +tree { + node { + value = 1 +} diff --git a/tools/bootconfig/samples/bad-value.bconf b/tools/bootconfig/samples/bad-value.bconf new file mode 100644 index 000000000..a1217fed8 --- /dev/null +++ b/tools/bootconfig/samples/bad-value.bconf @@ -0,0 +1,3 @@ +# Quotes error +value = "data + diff --git a/tools/bootconfig/samples/escaped.bconf b/tools/bootconfig/samples/escaped.bconf new file mode 100644 index 000000000..9f72043b3 --- /dev/null +++ b/tools/bootconfig/samples/escaped.bconf @@ -0,0 +1,3 @@ +key1 = "A\B\C" +key2 = '\'\'' +key3 = "\\" diff --git a/tools/bootconfig/samples/good-array-space-comment.bconf b/tools/bootconfig/samples/good-array-space-comment.bconf new file mode 100644 index 000000000..45b938dc0 --- /dev/null +++ b/tools/bootconfig/samples/good-array-space-comment.bconf @@ -0,0 +1,4 @@ +key = # comment + "value1", # comment1 + "value2" , # comment2 + "value3" diff --git a/tools/bootconfig/samples/good-comment-after-value.bconf b/tools/bootconfig/samples/good-comment-after-value.bconf new file mode 100644 index 000000000..0d92a853d --- /dev/null +++ b/tools/bootconfig/samples/good-comment-after-value.bconf @@ -0,0 +1 @@ +key = "value" # comment diff --git a/tools/bootconfig/samples/good-mixed-append.bconf b/tools/bootconfig/samples/good-mixed-append.bconf new file mode 100644 index 000000000..b99a089a0 --- /dev/null +++ b/tools/bootconfig/samples/good-mixed-append.bconf @@ -0,0 +1,4 @@ +key = foo +keyx.subkey = value +key += bar + diff --git a/tools/bootconfig/samples/good-mixed-kv1.bconf b/tools/bootconfig/samples/good-mixed-kv1.bconf new file mode 100644 index 000000000..1761547dd --- /dev/null +++ b/tools/bootconfig/samples/good-mixed-kv1.bconf @@ -0,0 +1,3 @@ +# value -> subkey pattern +key = value +key.subkey = another-value diff --git a/tools/bootconfig/samples/good-mixed-kv2.bconf b/tools/bootconfig/samples/good-mixed-kv2.bconf new file mode 100644 index 000000000..6b32e0c38 --- /dev/null +++ b/tools/bootconfig/samples/good-mixed-kv2.bconf @@ -0,0 +1,3 @@ +# subkey -> value pattern +key.subkey = value +key = another-value diff --git a/tools/bootconfig/samples/good-mixed-kv3.bconf b/tools/bootconfig/samples/good-mixed-kv3.bconf new file mode 100644 index 000000000..2ce2b0222 --- /dev/null +++ b/tools/bootconfig/samples/good-mixed-kv3.bconf @@ -0,0 +1,6 @@ +# mixed key and subkeys with braces +key = value +key { + subkey1 + subkey2 = foo +} diff --git a/tools/bootconfig/samples/good-mixed-override.bconf b/tools/bootconfig/samples/good-mixed-override.bconf new file mode 100644 index 000000000..18195b287 --- /dev/null +++ b/tools/bootconfig/samples/good-mixed-override.bconf @@ -0,0 +1,4 @@ +key.foo = bar +key = value +# mixed key value can be overridden +key := value2 diff --git a/tools/bootconfig/samples/good-override.bconf b/tools/bootconfig/samples/good-override.bconf new file mode 100644 index 000000000..7d31d5f8f --- /dev/null +++ b/tools/bootconfig/samples/good-override.bconf @@ -0,0 +1,6 @@ +# Override the value +key.word = 1,2,4 +key.word := 2,3 + +# No pre-defined key +key.new.word := "new" diff --git a/tools/bootconfig/samples/good-printables.bconf b/tools/bootconfig/samples/good-printables.bconf new file mode 100644 index 000000000..ebb985a66 --- /dev/null +++ b/tools/bootconfig/samples/good-printables.bconf @@ -0,0 +1,2 @@ +key = " + !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~" diff --git a/tools/bootconfig/samples/good-simple.bconf b/tools/bootconfig/samples/good-simple.bconf new file mode 100644 index 000000000..37dd6d21c --- /dev/null +++ b/tools/bootconfig/samples/good-simple.bconf @@ -0,0 +1,11 @@ +# A good simple bootconfig + +key.word1 = 1 +key.word2=2 +key.word3 = 3; + +key { +word4 = 4 } + +key { word5 = 5; word6 = 6 } + diff --git a/tools/bootconfig/samples/good-single.bconf b/tools/bootconfig/samples/good-single.bconf new file mode 100644 index 000000000..98e55ad8b --- /dev/null +++ b/tools/bootconfig/samples/good-single.bconf @@ -0,0 +1,4 @@ +# single key style +key = 1 +key2 = 2 +key3 = "alpha", "beta" diff --git a/tools/bootconfig/samples/good-space-after-value.bconf b/tools/bootconfig/samples/good-space-after-value.bconf new file mode 100644 index 000000000..56c15cbc5 --- /dev/null +++ b/tools/bootconfig/samples/good-space-after-value.bconf @@ -0,0 +1 @@ +key = "value" diff --git a/tools/bootconfig/samples/good-tree.bconf b/tools/bootconfig/samples/good-tree.bconf new file mode 100644 index 000000000..f2ddefc8b --- /dev/null +++ b/tools/bootconfig/samples/good-tree.bconf @@ -0,0 +1,12 @@ +key { + word { + tree { + value = "0"} + } + word2 { + tree { + value = 1,2 } + } +} +other.tree { + value = 2; value2 = 3;} diff --git a/tools/bootconfig/scripts/bconf2ftrace.sh b/tools/bootconfig/scripts/bconf2ftrace.sh new file mode 100755 index 000000000..850c20734 --- /dev/null +++ b/tools/bootconfig/scripts/bconf2ftrace.sh @@ -0,0 +1,301 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +usage() { + echo "Ftrace boottime trace test tool" + echo "Usage: $0 [--apply|--init] [--debug] BOOTCONFIG-FILE" + echo " --apply: Test actual apply to tracefs (need sudo)" + echo " --init: Initialize ftrace before applying (imply --apply)" + exit 1 +} + +[ $# -eq 0 ] && usage + +BCONF= +DEBUG= +APPLY= +INIT= +while [ x"$1" != x ]; do + case "$1" in + "--debug") + DEBUG=$1;; + "--apply") + APPLY=$1;; + "--init") + APPLY=$1 + INIT=$1;; + *) + [ ! -f $1 ] && usage + BCONF=$1;; + esac + shift 1 +done + +if [ x"$APPLY" != x ]; then + if [ `id -u` -ne 0 ]; then + echo "This must be run by root user. Try sudo." 1>&2 + exec sudo $0 $DEBUG $APPLY $BCONF + fi +fi + +run_cmd() { # command + echo "$*" + if [ x"$APPLY" != x ]; then # apply command + eval $* + fi +} + +if [ x"$DEBUG" != x ]; then + set -x +fi + +TRACEFS=`grep -m 1 -w tracefs /proc/mounts | cut -f 2 -d " "` +if [ -z "$TRACEFS" ]; then + if ! grep -wq debugfs /proc/mounts; then + echo "Error: No tracefs/debugfs was mounted." 1>&2 + exit 1 + fi + TRACEFS=`grep -m 1 -w debugfs /proc/mounts | cut -f 2 -d " "`/tracing + if [ ! -d $TRACEFS ]; then + echo "Error: ftrace is not enabled on this kernel." 1>&2 + exit 1 + fi +fi + +if [ x"$INIT" != x ]; then + . `dirname $0`/ftrace.sh + (cd $TRACEFS; initialize_ftrace) +fi + +. `dirname $0`/xbc.sh + +######## main ######### +set -e + +xbc_init $BCONF + +set_value_of() { # key file + if xbc_has_key $1; then + val=`xbc_get_val $1 1` + run_cmd "echo '$val' >> $2" + fi +} + +set_array_of() { # key file + if xbc_has_key $1; then + xbc_get_val $1 | while read line; do + run_cmd "echo '$line' >> $2" + done + fi +} + +compose_synth() { # event_name branch + echo -n "$1 " + xbc_get_val $2 | while read field; do echo -n "$field; "; done +} + +print_hist_array() { # prefix key + __sep="=" + if xbc_has_key ${1}.${2}; then + echo -n ":$2" + xbc_get_val ${1}.${2} | while read field; do + echo -n "$__sep$field"; __sep="," + done + fi +} + +print_hist_action_array() { # prefix key + __sep="(" + echo -n ".$2" + xbc_get_val ${1}.${2} | while read field; do + echo -n "$__sep$field"; __sep="," + done + echo -n ")" +} + +print_hist_one_action() { # prefix handler param + echo -n ":${2}("`xbc_get_val ${1}.${3}`")" + if xbc_has_key "${1}.trace"; then + print_hist_action_array ${1} "trace" + elif xbc_has_key "${1}.save"; then + print_hist_action_array ${1} "save" + elif xbc_has_key "${1}.snapshot"; then + echo -n ".snapshot()" + fi +} + +print_hist_actions() { # prefix handler param + for __hdr in `xbc_subkeys ${1}.${2} 1 ".[0-9]"`; do + print_hist_one_action ${1}.${2}.$__hdr ${2} ${3} + done + if xbc_has_key ${1}.${2}.${3} ; then + print_hist_one_action ${1}.${2} ${2} ${3} + fi +} + +print_hist_var() { # prefix varname + echo -n ":${2}="`xbc_get_val ${1}.var.${2} | tr -d [:space:]` +} + +print_one_histogram() { # prefix + echo -n "hist" + print_hist_array $1 "keys" + print_hist_array $1 "values" + print_hist_array $1 "sort" + if xbc_has_key "${1}.size"; then + echo -n ":size="`xbc_get_val ${1}.size` + fi + if xbc_has_key "${1}.name"; then + echo -n ":name="`xbc_get_val ${1}.name` + fi + for __var in `xbc_subkeys "${1}.var" 1`; do + print_hist_var ${1} ${__var} + done + if xbc_has_key "${1}.pause"; then + echo -n ":pause" + elif xbc_has_key "${1}.continue"; then + echo -n ":continue" + elif xbc_has_key "${1}.clear"; then + echo -n ":clear" + fi + print_hist_actions ${1} "onmax" "var" + print_hist_actions ${1} "onchange" "var" + print_hist_actions ${1} "onmatch" "event" + + if xbc_has_key "${1}.filter"; then + echo -n " if "`xbc_get_val ${1}.filter` + fi +} + +setup_one_histogram() { # prefix trigger-file + run_cmd "echo '`print_one_histogram ${1}`' >> ${2}" +} + +setup_histograms() { # prefix trigger-file + for __hist in `xbc_subkeys ${1} 1 ".[0-9]"`; do + setup_one_histogram ${1}.$__hist ${2} + done + if xbc_has_key ${1}.keys; then + setup_one_histogram ${1} ${2} + fi +} + +setup_event() { # prefix group event [instance] + branch=$1.$2.$3 + if [ "$4" ]; then + eventdir="$TRACEFS/instances/$4/events/$2/$3" + else + eventdir="$TRACEFS/events/$2/$3" + fi + # group enable + if [ "$3" = "enable" ]; then + run_cmd "echo 1 > ${eventdir}" + return + fi + + case $2 in + kprobes) + xbc_get_val ${branch}.probes | while read line; do + run_cmd "echo 'p:kprobes/$3 $line' >> $TRACEFS/kprobe_events" + done + ;; + synthetic) + run_cmd "echo '`compose_synth $3 ${branch}.fields`' >> $TRACEFS/synthetic_events" + ;; + esac + + set_value_of ${branch}.filter ${eventdir}/filter + set_array_of ${branch}.actions ${eventdir}/trigger + + setup_histograms ${branch}.hist ${eventdir}/trigger + + if xbc_has_key ${branch}.enable; then + run_cmd "echo 1 > ${eventdir}/enable" + fi +} + +setup_events() { # prefix("ftrace" or "ftrace.instance.INSTANCE") [instance] + prefix="${1}.event" + if xbc_has_branch ${1}.event; then + for grpev in `xbc_subkeys ${1}.event 2`; do + setup_event $prefix ${grpev%.*} ${grpev#*.} $2 + done + fi + if xbc_has_branch ${1}.event.enable; then + if [ "$2" ]; then + run_cmd "echo 1 > $TRACEFS/instances/$2/events/enable" + else + run_cmd "echo 1 > $TRACEFS/events/enable" + fi + fi +} + +size2kb() { # size[KB|MB] + case $1 in + *KB) + echo ${1%KB};; + *MB) + expr ${1%MB} \* 1024;; + *) + expr $1 / 1024 ;; + esac +} + +setup_instance() { # [instance] + if [ "$1" ]; then + instance="ftrace.instance.${1}" + instancedir=$TRACEFS/instances/$1 + else + instance="ftrace" + instancedir=$TRACEFS + fi + + set_array_of ${instance}.options ${instancedir}/trace_options + set_value_of ${instance}.trace_clock ${instancedir}/trace_clock + set_value_of ${instance}.cpumask ${instancedir}/tracing_cpumask + set_value_of ${instance}.tracing_on ${instancedir}/tracing_on + set_value_of ${instance}.tracer ${instancedir}/current_tracer + set_array_of ${instance}.ftrace.filters \ + ${instancedir}/set_ftrace_filter + set_array_of ${instance}.ftrace.notrace \ + ${instancedir}/set_ftrace_notrace + + if xbc_has_key ${instance}.alloc_snapshot; then + run_cmd "echo 1 > ${instancedir}/snapshot" + fi + + if xbc_has_key ${instance}.buffer_size; then + size=`xbc_get_val ${instance}.buffer_size 1` + size=`eval size2kb $size` + run_cmd "echo $size >> ${instancedir}/buffer_size_kb" + fi + + setup_events ${instance} $1 + set_array_of ${instance}.events ${instancedir}/set_event +} + +# ftrace global configs (kernel.*) +if xbc_has_key "kernel.dump_on_oops"; then + dump_mode=`xbc_get_val "kernel.dump_on_oops" 1` + [ "$dump_mode" ] && dump_mode=`eval echo $dump_mode` || dump_mode=1 + run_cmd "echo \"$dump_mode\" > /proc/sys/kernel/ftrace_dump_on_oops" +fi + +set_value_of kernel.fgraph_max_depth $TRACEFS/max_graph_depth +set_array_of kernel.fgraph_filters $TRACEFS/set_graph_function +set_array_of kernel.fgraph_notraces $TRACEFS/set_graph_notrace + +# Per-instance/per-event configs +if ! xbc_has_branch "ftrace" ; then + exit 0 +fi + +setup_instance # root instance + +if xbc_has_branch "ftrace.instance"; then + for i in `xbc_subkeys "ftrace.instance" 1`; do + run_cmd "mkdir -p $TRACEFS/instances/$i" + setup_instance $i + done +fi + diff --git a/tools/bootconfig/scripts/ftrace.sh b/tools/bootconfig/scripts/ftrace.sh new file mode 100644 index 000000000..186eed923 --- /dev/null +++ b/tools/bootconfig/scripts/ftrace.sh @@ -0,0 +1,109 @@ +# SPDX-License-Identifier: GPL-2.0-only + +clear_trace() { # reset trace output + echo > trace +} + +disable_tracing() { # stop trace recording + echo 0 > tracing_on +} + +enable_tracing() { # start trace recording + echo 1 > tracing_on +} + +reset_tracer() { # reset the current tracer + echo nop > current_tracer +} + +reset_trigger_file() { + # remove action triggers first + grep -H ':on[^:]*(' $@ | + while read line; do + cmd=`echo $line | cut -f2- -d: | cut -f1 -d"["` + file=`echo $line | cut -f1 -d:` + echo "!$cmd" >> $file + done + grep -Hv ^# $@ | + while read line; do + cmd=`echo $line | cut -f2- -d: | cut -f1 -d"["` + file=`echo $line | cut -f1 -d:` + echo "!$cmd" > $file + done +} + +reset_trigger() { # reset all current setting triggers + if [ -d events/synthetic ]; then + reset_trigger_file events/synthetic/*/trigger + fi + reset_trigger_file events/*/*/trigger +} + +reset_events_filter() { # reset all current setting filters + grep -v ^none events/*/*/filter | + while read line; do + echo 0 > `echo $line | cut -f1 -d:` + done +} + +reset_ftrace_filter() { # reset all triggers in set_ftrace_filter + if [ ! -f set_ftrace_filter ]; then + return 0 + fi + echo > set_ftrace_filter + grep -v '^#' set_ftrace_filter | while read t; do + tr=`echo $t | cut -d: -f2` + if [ "$tr" = "" ]; then + continue + fi + if ! grep -q "$t" set_ftrace_filter; then + continue; + fi + name=`echo $t | cut -d: -f1 | cut -d' ' -f1` + if [ $tr = "enable_event" -o $tr = "disable_event" ]; then + tr=`echo $t | cut -d: -f2-4` + limit=`echo $t | cut -d: -f5` + else + tr=`echo $t | cut -d: -f2` + limit=`echo $t | cut -d: -f3` + fi + if [ "$limit" != "unlimited" ]; then + tr="$tr:$limit" + fi + echo "!$name:$tr" > set_ftrace_filter + done +} + +disable_events() { + echo 0 > events/enable +} + +clear_synthetic_events() { # reset all current synthetic events + grep -v ^# synthetic_events | + while read line; do + echo "!$line" >> synthetic_events + done +} + +initialize_ftrace() { # Reset ftrace to initial-state +# As the initial state, ftrace will be set to nop tracer, +# no events, no triggers, no filters, no function filters, +# no probes, and tracing on. + disable_tracing + reset_tracer + reset_trigger + reset_events_filter + reset_ftrace_filter + disable_events + [ -f set_event_pid ] && echo > set_event_pid + [ -f set_ftrace_pid ] && echo > set_ftrace_pid + [ -f set_ftrace_notrace ] && echo > set_ftrace_notrace + [ -f set_graph_function ] && echo | tee set_graph_* + [ -f stack_trace_filter ] && echo > stack_trace_filter + [ -f kprobe_events ] && echo > kprobe_events + [ -f uprobe_events ] && echo > uprobe_events + [ -f synthetic_events ] && echo > synthetic_events + [ -f snapshot ] && echo 0 > snapshot + clear_trace + enable_tracing +} diff --git a/tools/bootconfig/scripts/ftrace2bconf.sh b/tools/bootconfig/scripts/ftrace2bconf.sh new file mode 100755 index 000000000..1603801cf --- /dev/null +++ b/tools/bootconfig/scripts/ftrace2bconf.sh @@ -0,0 +1,260 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +usage() { + echo "Dump boot-time tracing bootconfig from ftrace" + echo "Usage: $0 [--debug] [ > BOOTCONFIG-FILE]" + exit 1 +} + +DEBUG= +while [ x"$1" != x ]; do + case "$1" in + "--debug") + DEBUG=$1;; + -*) + usage + ;; + esac + shift 1 +done + +if [ x"$DEBUG" != x ]; then + set -x +fi + +TRACEFS=`grep -m 1 -w tracefs /proc/mounts | cut -f 2 -d " "` +if [ -z "$TRACEFS" ]; then + if ! grep -wq debugfs /proc/mounts; then + echo "Error: No tracefs/debugfs was mounted." + exit 1 + fi + TRACEFS=`grep -m 1 -w debugfs /proc/mounts | cut -f 2 -d " "`/tracing + if [ ! -d $TRACEFS ]; then + echo "Error: ftrace is not enabled on this kernel." 1>&2 + exit 1 + fi +fi + +######## main ######### + +set -e + +emit_kv() { # key =|+= value + echo "$@" +} + +global_options() { + val=`cat $TRACEFS/max_graph_depth` + [ $val != 0 ] && emit_kv kernel.fgraph_max_depth = $val + if grep -qv "^#" $TRACEFS/set_graph_function $TRACEFS/set_graph_notrace ; then + cat 1>&2 << EOF +# WARN: kernel.fgraph_filters and kernel.fgraph_notrace are not supported, since the wild card expression was expanded and lost from memory. +EOF + fi +} + +kprobe_event_options() { + cat $TRACEFS/kprobe_events | while read p args; do + case $p in + r*) + cat 1>&2 << EOF +# WARN: A return probe found but it is not supported by bootconfig. Skip it. +EOF + continue;; + esac + p=${p#*:} + event=${p#*/} + group=${p%/*} + if [ $group != "kprobes" ]; then + cat 1>&2 << EOF +# WARN: kprobes group name $group is changed to "kprobes" for bootconfig. +EOF + fi + emit_kv $PREFIX.event.kprobes.$event.probes += $args + done +} + +synth_event_options() { + cat $TRACEFS/synthetic_events | while read event fields; do + emit_kv $PREFIX.event.synthetic.$event.fields = `echo $fields | sed "s/;/,/g"` + done +} + +# Variables resolver +DEFINED_VARS= +UNRESOLVED_EVENTS= + +defined_vars() { # event-dir + grep "^hist" $1/trigger | grep -o ':[a-zA-Z0-9]*=' +} +referred_vars() { + grep "^hist" $1/trigger | grep -o '$[a-zA-Z0-9]*' +} + +event_is_enabled() { # enable-file + test -f $1 && grep -q "1" $1 +} + +per_event_options() { # event-dir + evdir=$1 + # Check the special event which has no filter and no trigger + [ ! -f $evdir/filter ] && return + + if grep -q "^hist:" $evdir/trigger; then + # hist action can refer the undefined variables + __vars=`defined_vars $evdir` + for v in `referred_vars $evdir`; do + if echo $DEFINED_VARS $__vars | grep -vqw ${v#$}; then + # $v is not defined yet, defer it + UNRESOLVED_EVENTS="$UNRESOLVED_EVENTS $evdir" + return; + fi + done + DEFINED_VARS="$DEFINED_VARS "`defined_vars $evdir` + fi + grep -v "^#" $evdir/trigger | while read action active; do + emit_kv $PREFIX.event.$group.$event.actions += \'$action\' + done + + if [ $GROUP_ENABLED -eq 0 ] && event_is_enabled $evdir/enable; then + emit_kv $PREFIX.event.$group.$event.enable + fi + val=`cat $evdir/filter` + if [ "$val" != "none" ]; then + emit_kv $PREFIX.event.$group.$event.filter = "$val" + fi +} + +retry_unresolved() { + unresolved=$UNRESOLVED_EVENTS + UNRESOLVED_EVENTS= + for evdir in $unresolved; do + event=${evdir##*/} + group=${evdir%/*}; group=${group##*/} + per_event_options $evdir + done +} + +event_options() { + # PREFIX and INSTANCE must be set + if [ $PREFIX = "ftrace" ]; then + # define the dynamic events + kprobe_event_options + synth_event_options + fi + ALL_ENABLED=0 + if event_is_enabled $INSTANCE/events/enable; then + emit_kv $PREFIX.event.enable + ALL_ENABLED=1 + fi + for group in `ls $INSTANCE/events/` ; do + [ ! -d $INSTANCE/events/$group ] && continue + GROUP_ENABLED=$ALL_ENABLED + if [ $ALL_ENABLED -eq 0 ] && \ + event_is_enabled $INSTANCE/events/$group/enable ;then + emit_kv $PREFIX.event.$group.enable + GROUP_ENABLED=1 + fi + for event in `ls $INSTANCE/events/$group/` ;do + [ ! -d $INSTANCE/events/$group/$event ] && continue + per_event_options $INSTANCE/events/$group/$event + done + done + retry=0 + while [ $retry -lt 3 ]; do + retry_unresolved + retry=$((retry + 1)) + done + if [ "$UNRESOLVED_EVENTS" ]; then + cat 1>&2 << EOF +! ERROR: hist triggers in $UNRESOLVED_EVENTS use some undefined variables. +EOF + fi +} + +is_default_trace_option() { # option +grep -qw $1 << EOF +print-parent +nosym-offset +nosym-addr +noverbose +noraw +nohex +nobin +noblock +trace_printk +annotate +nouserstacktrace +nosym-userobj +noprintk-msg-only +context-info +nolatency-format +record-cmd +norecord-tgid +overwrite +nodisable_on_free +irq-info +markers +noevent-fork +nopause-on-trace +function-trace +nofunction-fork +nodisplay-graph +nostacktrace +notest_nop_accept +notest_nop_refuse +EOF +} + +instance_options() { # [instance-name] + if [ $# -eq 0 ]; then + PREFIX="ftrace" + INSTANCE=$TRACEFS + else + PREFIX="ftrace.instance.$1" + INSTANCE=$TRACEFS/instances/$1 + fi + val= + for i in `cat $INSTANCE/trace_options`; do + is_default_trace_option $i && continue + val="$val, $i" + done + [ "$val" ] && emit_kv $PREFIX.options = "${val#,}" + val="local" + for i in `cat $INSTANCE/trace_clock` ; do + [ "${i#*]}" ] && continue + i=${i%]}; val=${i#[} + done + [ $val != "local" ] && emit_kv $PREFIX.trace_clock = $val + val=`cat $INSTANCE/buffer_size_kb` + if echo $val | grep -vq "expanded" ; then + emit_kv $PREFIX.buffer_size = $val"KB" + fi + if grep -q "is allocated" $INSTANCE/snapshot ; then + emit_kv $PREFIX.alloc_snapshot + fi + val=`cat $INSTANCE/tracing_cpumask` + if [ `echo $val | sed -e s/f//g`x != x ]; then + emit_kv $PREFIX.cpumask = $val + fi + val=`cat $INSTANCE/tracing_on` + if [ "$val" = "0" ]; then + emit_kv $PREFIX.tracing_on = 0 + fi + + val=`cat $INSTANCE/current_tracer` + [ $val != nop ] && emit_kv $PREFIX.tracer = $val + if grep -qv "^#" $INSTANCE/set_ftrace_filter $INSTANCE/set_ftrace_notrace; then + cat 1>&2 << EOF +# WARN: kernel.ftrace.filters and kernel.ftrace.notrace are not supported, since the wild card expression was expanded and lost from memory. +EOF + fi + event_options +} + +global_options +instance_options +for i in `ls $TRACEFS/instances` ; do + instance_options $i +done diff --git a/tools/bootconfig/scripts/xbc.sh b/tools/bootconfig/scripts/xbc.sh new file mode 100644 index 000000000..1f0ebf50d --- /dev/null +++ b/tools/bootconfig/scripts/xbc.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +# bootconfig utility functions + +XBC_TMPFILE= +XBC_BASEDIR=`dirname $0` +BOOTCONFIG=${BOOTCONFIG:=$XBC_BASEDIR/../bootconfig} +if [ ! -x "$BOOTCONFIG" ]; then + BOOTCONFIG=`which bootconfig` + if [ -z "$BOOTCONFIG" ]; then + echo "Erorr: bootconfig command is not found" 1>&2 + exit 1 + fi +fi + +xbc_cleanup() { + if [ "$XBC_TMPFILE" ]; then + rm -f "$XBC_TMPFILE" + fi +} + +xbc_init() { # bootconfig-file + xbc_cleanup + XBC_TMPFILE=`mktemp bconf-XXXX` + trap xbc_cleanup EXIT TERM + + $BOOTCONFIG -l $1 > $XBC_TMPFILE || exit 1 +} + +nr_args() { # args + echo $# +} + +xbc_get_val() { # key [maxnum] + if [ "$2" ]; then + MAXOPT="-L $2" + fi + grep "^$1 =" $XBC_TMPFILE | cut -d= -f2- | \ + sed -e 's/", /" /g' -e "s/',/' /g" | \ + xargs $MAXOPT -n 1 echo +} + +xbc_has_key() { # key + grep -q "^$1 =" $XBC_TMPFILE +} + +xbc_has_branch() { # prefix-key + grep -q "^$1" $XBC_TMPFILE +} + +xbc_subkeys() { # prefix-key depth [subkey-pattern] + __keys=`echo $1 | sed "s/\./ /g"` + __s=`nr_args $__keys` + grep "^$1$3" $XBC_TMPFILE | cut -d= -f1| cut -d. -f$((__s + 1))-$((__s + $2)) | uniq +} diff --git a/tools/bootconfig/test-bootconfig.sh b/tools/bootconfig/test-bootconfig.sh new file mode 100755 index 000000000..a2c484c24 --- /dev/null +++ b/tools/bootconfig/test-bootconfig.sh @@ -0,0 +1,195 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-only + +echo "Boot config test script" + +if [ -d "$1" ]; then + TESTDIR=$1 +else + TESTDIR=. +fi +BOOTCONF=${TESTDIR}/bootconfig +ALIGN=4 + +INITRD=`mktemp ${TESTDIR}/initrd-XXXX` +TEMPCONF=`mktemp ${TESTDIR}/temp-XXXX.bconf` +OUTFILE=`mktemp ${TESTDIR}/tempout-XXXX` +NG=0 + +cleanup() { + rm -f $INITRD $TEMPCONF $OUTFILE + exit $NG +} + +trap cleanup EXIT TERM + +NO=1 + +xpass() { # pass test command + echo "test case $NO ($*)... " + if ! ($@ && echo "\t\t[OK]"); then + echo "\t\t[NG]"; NG=$((NG + 1)) + fi + NO=$((NO + 1)) +} + +xfail() { # fail test command + echo "test case $NO ($*)... " + if ! (! $@ && echo "\t\t[OK]"); then + echo "\t\t[NG]"; NG=$((NG + 1)) + fi + NO=$((NO + 1)) +} + +echo "Basic command test" +xpass $BOOTCONF $INITRD + +echo "Delete command should success without bootconfig" +xpass $BOOTCONF -d $INITRD + +dd if=/dev/zero of=$INITRD bs=4096 count=1 +echo "key = value;" > $TEMPCONF +bconf_size=$(stat -c %s $TEMPCONF) +initrd_size=$(stat -c %s $INITRD) + +echo "Apply command test" +xpass $BOOTCONF -a $TEMPCONF $INITRD +new_size=$(stat -c %s $INITRD) + +echo "Show command test" +xpass $BOOTCONF $INITRD + +echo "File size check" +total_size=$(expr $bconf_size + $initrd_size + 9 + 12 + $ALIGN - 1 ) +total_size=$(expr $total_size / $ALIGN) +total_size=$(expr $total_size \* $ALIGN) +xpass test $new_size -eq $total_size + +echo "Apply command repeat test" +xpass $BOOTCONF -a $TEMPCONF $INITRD + +echo "File size check" +xpass test $new_size -eq $(stat -c %s $INITRD) + +echo "Delete command check" +xpass $BOOTCONF -d $INITRD + +echo "File size check" +new_size=$(stat -c %s $INITRD) +xpass test $new_size -eq $initrd_size + +echo "No error messge while applying" +dd if=/dev/zero of=$INITRD bs=4096 count=1 +printf " \0\0\0 \0\0\0" >> $INITRD +$BOOTCONF -a $TEMPCONF $INITRD > $OUTFILE 2>&1 +xfail grep -i "failed" $OUTFILE +xfail grep -i "error" $OUTFILE + +echo "Max node number check" + +awk ' +BEGIN { + for (i = 0; i < 26; i += 1) + printf("%c\n", 65 + i % 26) + for (i = 26; i < 8192; i += 1) + printf("%c%c%c\n", 65 + i % 26, 65 + (i / 26) % 26, 65 + (i / 26 / 26)) +} +' > $TEMPCONF +xpass $BOOTCONF -a $TEMPCONF $INITRD + +echo "badnode" >> $TEMPCONF +xfail $BOOTCONF -a $TEMPCONF $INITRD + +echo "Max filesize check" + +# Max size is 32767 (including terminal byte) +echo -n "data = \"" > $TEMPCONF +dd if=/dev/urandom bs=768 count=32 | base64 -w0 >> $TEMPCONF +echo "\"" >> $TEMPCONF +xfail $BOOTCONF -a $TEMPCONF $INITRD + +truncate -s 32764 $TEMPCONF +echo "\"" >> $TEMPCONF # add 2 bytes + terminal ('\"\n\0') +xpass $BOOTCONF -a $TEMPCONF $INITRD + +echo "Adding same-key values" +cat > $TEMPCONF << EOF +key = bar, baz +key += qux +EOF +echo > $INITRD + +xpass $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $OUTFILE +xpass grep -q "bar" $OUTFILE +xpass grep -q "baz" $OUTFILE +xpass grep -q "qux" $OUTFILE + +echo "Override same-key values" +cat > $TEMPCONF << EOF +key = bar, baz +key := qux +EOF +echo > $INITRD + +xpass $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $OUTFILE +xfail grep -q "bar" $OUTFILE +xfail grep -q "baz" $OUTFILE +xpass grep -q "qux" $OUTFILE + +echo "Double/single quotes test" +echo "key = '\"string\"';" > $TEMPCONF +$BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $TEMPCONF +cat $TEMPCONF +xpass grep \'\"string\"\' $TEMPCONF + +echo "Repeat same-key tree" +cat > $TEMPCONF << EOF +foo +bar +foo { buz } +EOF +echo > $INITRD + +xpass $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $OUTFILE +xpass grep -q "bar" $OUTFILE + + +echo "Remove/keep tailing spaces" +cat > $TEMPCONF << EOF +foo = val # comment +bar = "val2 " # comment +EOF +echo > $INITRD + +xpass $BOOTCONF -a $TEMPCONF $INITRD +$BOOTCONF $INITRD > $OUTFILE +xfail grep -q val[[:space:]] $OUTFILE +xpass grep -q val2[[:space:]] $OUTFILE + +echo "=== expected failure cases ===" +for i in samples/bad-* ; do + xfail $BOOTCONF -a $i $INITRD +done + +echo "=== expected success cases ===" +for i in samples/good-* ; do + xpass $BOOTCONF -a $i $INITRD +done + + +echo +echo "=== Summary ===" +echo "# of Passed: $(expr $NO - $NG - 1)" +echo "# of Failed: $NG" + +echo +if [ $NG -eq 0 ]; then + echo "All tests passed" +else + echo "$NG tests failed" + exit 1 +fi |