From patchwork Mon Mar 19 09:27:23 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Javier Martinez Canillas X-Patchwork-Id: 887632 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [103.22.144.68]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 404W1s6MN4z9sD0 for ; Mon, 19 Mar 2018 20:27:45 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=redhat.com Received: from bilbo.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 404W1s4qzrzF1Yb for ; Mon, 19 Mar 2018 20:27:45 +1100 (AEDT) Authentication-Results: lists.ozlabs.org; dmarc=pass (p=none dis=none) header.from=redhat.com X-Original-To: petitboot@lists.ozlabs.org Delivered-To: petitboot@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=redhat.com (client-ip=209.85.128.178; helo=mail-wr0-f178.google.com; envelope-from=javierm@redhat.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=pass (p=none dis=none) header.from=redhat.com Received: from mail-wr0-f178.google.com (mail-wr0-f178.google.com [209.85.128.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 404W1Z6ZmXzF1VY for ; Mon, 19 Mar 2018 20:27:30 +1100 (AEDT) Received: by mail-wr0-f178.google.com with SMTP id h2so17760644wre.12 for ; Mon, 19 Mar 2018 02:27:30 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=cVWCBhXy5shEjgR7HDhtehRR+xyvubJK1rncXav8pYY=; b=D17//DGjMwOvIwqfa+w/k4VJ9GFI7MqGBOByPv935YU4rJlyWghx3IiHJycxrwma+D i99VM0MaQdaryMqmaS9sIseR6m+NqJgWtQbfiiGx1kKFRBbQ4uQw89mh4TvDiTHQqF4e MvXZU0RPM4/VyWr4AaNz6/jGR6I7GAOJsOneTMy/gnF5o28egNxlgDT63wfBZ5yYxCfI pDwchRb/+vw28Bhd4wBRcZBef8Jpee+ducMjLLVO8Q6HUJaUsjQSTMjT5zmyYJlrqYkm kV+y9JMRoGiDloNIjIOacuo62lRy+nfe3uwEz/AsXECLDH7EVP4RHShFtBNvvO3PogrL SwJw== X-Gm-Message-State: AElRT7EclWCpIxu6SHzg/X3eMCpfw5dRDuQ+wLVMllop/yXUj7yxuB7c 6F8QWu1CUlpi/PFKnQyabsej1VhdnAA= X-Google-Smtp-Source: AG47ELuiqREDeNRTuJvyvDqzSj9hdk9Z+snhIZtjyFhxOShM67rIWg9hJr4v7yUUaw8ITVMyuGQoHA== X-Received: by 10.223.200.138 with SMTP id k10mr5151024wrh.282.1521451646983; Mon, 19 Mar 2018 02:27:26 -0700 (PDT) Received: from minerva.redhat.com ([195.166.127.210]) by smtp.gmail.com with ESMTPSA id d4sm13762138wmh.42.2018.03.19.02.27.25 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Mon, 19 Mar 2018 02:27:26 -0700 (PDT) From: Javier Martinez Canillas To: petitboot@lists.ozlabs.org Subject: [PATCH v3] discover/grub: Add blscfg command support to parse BootLoaderSpec files Date: Mon, 19 Mar 2018 10:27:23 +0100 Message-Id: <20180319092723.1047-1-javierm@redhat.com> X-Mailer: git-send-email 2.14.3 X-BeenThere: petitboot@lists.ozlabs.org X-Mailman-Version: 2.1.26 Precedence: list List-Id: Petitboot bootloader development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Javier Martinez Canillas , Peter Jones , Jan Hlavac , Samuel Mendoza-Jonas MIME-Version: 1.0 Errors-To: petitboot-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Petitboot" The BootLoaderSpec (BLS) defines a file format for boot configurations, so bootloaders can parse these files and create their boot menu entries by using the information provided by them [0]. This allow to configure the boot items as drop-in files in a directory instead of having to parse and modify a bootloader configuration file. The GRUB 2 bootloader provides a blscfg command that parses these files and creates menu entries using this information. Add support for it. [0]: https://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/ Signed-off-by: Javier Martinez Canillas --- Hello, From Fedora 28 there will be an option to use BootLoaderSpec snippets to update GRUB's boot menu entries. So I'm posting this patch to allow this to also work on ppc64 machines using petitboot, instead of grub-ieee1275. This can be tested by creating a BLS config under /boot/loader/entries, for example following /boot/loader/entries/4.15.6-300.fc27.x86_64.conf: title Fedora (4.15.6-300.fc27.x86_64) 27 (Twenty Seven) linux /vmlinuz-4.15.6-300.fc27.x86_64 initrd /initramfs-4.15.6-300.fc27.x86_64.img options root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root rd.luks.uuid=luks-0b078909-4a1c-4a57-91b8-b9f724e86a1a rd.lvm.lv=fedora/swap rhgb quiet LANG=en_US.UTF-8 id fedora-20180214051518-4.15.6-300.fc27.x86_64.x86_64 grub_users $grub_users grub_arg --unrestricted grub_class kernel And a grub.cfg that calls the blscfg command, it could just be the following: blscfg Best regards, Javier Changes in v3: - Populate boot option id using the linux field instead of title field. - Don't fill boot option name using the optional title field, instead fill this from optional fields that are present in the BLS fragment. - Add support for the devicetree field. Changes in v2: - Allow optional fields, only require the linux field to be present. - Remove unused identifier from bls_filter() struct dirent * param. - Remove redundant check in builtin_blscfg() while loop. discover/grub2/Makefile.am | 1 + discover/grub2/blscfg.c | 221 +++++++++++++++++++++++++++ discover/grub2/builtins.c | 9 +- discover/parser.c | 16 ++ discover/parser.h | 8 + test/parser/Makefile.am | 3 + test/parser/test-grub2-blscfg-multiple-bls.c | 44 ++++++ test/parser/test-grub2-blscfg-opts-config.c | 29 ++++ test/parser/test-grub2-blscfg-opts-grubenv.c | 34 +++++ test/parser/utils.c | 59 +++++++ 10 files changed, 423 insertions(+), 1 deletion(-) create mode 100644 discover/grub2/blscfg.c create mode 100644 test/parser/test-grub2-blscfg-multiple-bls.c create mode 100644 test/parser/test-grub2-blscfg-opts-config.c create mode 100644 test/parser/test-grub2-blscfg-opts-grubenv.c diff --git a/discover/grub2/Makefile.am b/discover/grub2/Makefile.am index 130ede88e18c..b240106d7a54 100644 --- a/discover/grub2/Makefile.am +++ b/discover/grub2/Makefile.am @@ -15,6 +15,7 @@ noinst_PROGRAMS += discover/grub2/grub2-parser.ro discover_grub2_grub2_parser_ro_SOURCES = \ + discover/grub2/blscfg.c \ discover/grub2/builtins.c \ discover/grub2/env.c \ discover/grub2/grub2.h \ diff --git a/discover/grub2/blscfg.c b/discover/grub2/blscfg.c new file mode 100644 index 000000000000..5677aa081531 --- /dev/null +++ b/discover/grub2/blscfg.c @@ -0,0 +1,221 @@ + +#define _GNU_SOURCE + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "grub2.h" +#include "discover/parser-conf.h" +#include "discover/parser.h" + +#define BLS_DIR "/loader/entries" + +struct bls_state { + struct discover_boot_option *opt; + struct grub2_script *script; + const char *filename; + const char *title; + const char *version; + const char *machine_id; + const char *image; + const char *initrd; + const char *dtb; +}; + +static void bls_process_pair(struct conf_context *conf, const char *name, + char *value) +{ + struct bls_state *state = conf->parser_info; + struct discover_boot_option *opt = state->opt; + struct boot_option *option = opt->option; + const char *boot_args; + + if (streq(name, "title")) { + state->title = talloc_strdup(state, value); + return; + } + + if (streq(name, "version")) { + state->version = talloc_strdup(state, value); + return; + } + + if (streq(name, "machine-id")) { + state->machine_id = talloc_strdup(state, value); + return; + } + + if (streq(name, "linux")) { + state->image = talloc_strdup(state, value); + return; + } + + if (streq(name, "initrd")) { + state->initrd = talloc_strdup(state, value); + return; + } + + if (streq(name, "devicetree")) { + state->dtb = talloc_strdup(state, value); + return; + } + + if (streq(name, "options")) { + if (value[0] == '$') { + boot_args = script_env_get(state->script, value + 1); + if (!boot_args) + return; + + option->boot_args = talloc_strdup(opt, boot_args); + } else { + option->boot_args = talloc_strdup(opt, value); + } + return; + } +} + +static void bls_finish(struct conf_context *conf) +{ + struct discover_device *dev = conf->dc->device; + struct bls_state *state = conf->parser_info; + struct discover_context *dc = conf->dc; + struct discover_boot_option *opt = state->opt; + struct boot_option *option = opt->option; + const char *root; + + if (!state->image) { + device_handler_status_dev_info(dc->handler, dc->device, + _("linux field not found in %s"), + state->filename); + return; + } + + option->id = talloc_asprintf(option, "%s#%s", dev->device->id, + state->image); + + if (state->title) + option->name = talloc_strdup(option, state->title); + else if (state->machine_id && state->version) + option->name = talloc_asprintf(option, "%s %s", + state->machine_id, + state->version); + else if (state->version) + option->name = talloc_strdup(option, state->version); + else + option->name = talloc_strdup(option, option->id); + + root = script_env_get(state->script, "root"); + + opt->boot_image = create_grub2_resource(opt, conf->dc->device, + root, state->image); + + if (state->initrd) + opt->initrd = create_grub2_resource(opt, conf->dc->device, + root, state->initrd); + + if (state->dtb) + opt->dtb = create_grub2_resource(opt, conf->dc->device, + root, state->dtb); + discover_context_add_boot_option(dc, opt); + + device_handler_status_dev_info(dc->handler, dc->device, + _("Created menu entry from BLS file %s"), + state->filename); +} + +static int bls_filter(const struct dirent *ent) +{ + int offset = strlen(ent->d_name) - strlen(".conf"); + + if (offset < 0) + return 0; + + return strncmp(ent->d_name + offset, ".conf", strlen(".conf")) == 0; +} + +static int bls_sort(const struct dirent **ent_a, const struct dirent **ent_b) +{ + return strverscmp((*ent_b)->d_name, (*ent_a)->d_name); +} + +int builtin_blscfg(struct grub2_script *script, + void *data __attribute__((unused)), + int argc __attribute__((unused)), + char *argv[] __attribute__((unused))); + +int builtin_blscfg(struct grub2_script *script, + void *data __attribute__((unused)), + int argc __attribute__((unused)), + char *argv[] __attribute__((unused))) +{ + struct discover_context *dc = script->ctx; + struct dirent **bls_entries; + struct conf_context *conf; + struct bls_state *state; + char *buf, *filename; + int n, len, rc = -1; + + conf = talloc_zero(dc, struct conf_context); + if (!conf) + return rc; + + conf->dc = dc; + conf->get_pair = conf_get_pair_space; + conf->process_pair = bls_process_pair; + conf->finish = bls_finish; + + n = parser_scandir(dc, BLS_DIR, &bls_entries, bls_filter, bls_sort); + if (n <= 0) + goto err; + + while (n--) { + filename = talloc_asprintf(dc, BLS_DIR"/%s", + bls_entries[n]->d_name); + if (!filename) + break; + + state = talloc_zero(conf, struct bls_state); + if (!state) + break; + + state->opt = discover_boot_option_create(dc, dc->device); + if (!state->opt) + break; + + state->script = script; + state->filename = filename; + conf->parser_info = state; + + rc = parser_request_file(dc, dc->device, filename, &buf, &len); + if (rc) + break; + + conf_parse_buf(conf, buf, len); + + talloc_free(buf); + talloc_free(state); + talloc_free(filename); + free(bls_entries[n]); + } + + if (n > 0) { + device_handler_status_dev_info(dc->handler, dc->device, + _("Scanning %s failed"), + BLS_DIR); + do { + free(bls_entries[n]); + } while (n-- > 0); + } + + free(bls_entries); +err: + talloc_free(conf); + return rc; +} diff --git a/discover/grub2/builtins.c b/discover/grub2/builtins.c index c16b6390225a..e42821a64a9a 100644 --- a/discover/grub2/builtins.c +++ b/discover/grub2/builtins.c @@ -330,7 +330,10 @@ extern int builtin_load_env(struct grub2_script *script, int builtin_save_env(struct grub2_script *script, void *data __attribute__((unused)), int argc, char *argv[]); - +int builtin_blscfg(struct grub2_script *script, + void *data __attribute__((unused)), + int argc __attribute__((unused)), + char *argv[] __attribute__((unused))); static struct { const char *name; @@ -380,6 +383,10 @@ static struct { .name = "save_env", .fn = builtin_save_env, }, + { + .name = "blscfg", + .fn = builtin_blscfg, + } }; static const char *nops[] = { diff --git a/discover/parser.c b/discover/parser.c index 5598f963e236..9fe1925d94c4 100644 --- a/discover/parser.c +++ b/discover/parser.c @@ -128,6 +128,22 @@ out: return -1; } +int parser_scandir(struct discover_context *ctx, const char *dirname, + struct dirent ***files, int (*filter)(const struct dirent *), + int (*comp)(const struct dirent **, const struct dirent **)) +{ + char *path; + int n; + + path = talloc_asprintf(ctx, "%s%s", ctx->device->mount_path, dirname); + if (!path) + return -1; + + n = scandir(path, files, filter, comp); + talloc_free(path); + return n; +} + void iterate_parsers(struct discover_context *ctx) { struct p_item* i; diff --git a/discover/parser.h b/discover/parser.h index fc165c5aeda4..bff52e30d09f 100644 --- a/discover/parser.h +++ b/discover/parser.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "device-handler.h" @@ -76,5 +77,12 @@ int parser_request_url(struct discover_context *ctx, struct pb_url *url, int parser_stat_path(struct discover_context *ctx, struct discover_device *dev, const char *path, struct stat *statbuf); +/* Function used to list the files on a directory. The dirname should + * be relative to the discover context device mount path. It returns + * the number of files returned in files or a negative value on error. + */ +int parser_scandir(struct discover_context *ctx, const char *dirname, + struct dirent ***files, int (*filter)(const struct dirent *), + int (*comp)(const struct dirent **, const struct dirent **)); #endif /* _PARSER_H */ diff --git a/test/parser/Makefile.am b/test/parser/Makefile.am index a0795dbcf899..b943408c4942 100644 --- a/test/parser/Makefile.am +++ b/test/parser/Makefile.am @@ -40,6 +40,9 @@ parser_TESTS = \ test/parser/test-grub2-parser-error \ test/parser/test-grub2-test-file-ops \ test/parser/test-grub2-single-yocto \ + test/parser/test-grub2-blscfg-multiple-bls \ + test/parser/test-grub2-blscfg-opts-config \ + test/parser/test-grub2-blscfg-opts-grubenv \ test/parser/test-kboot-single \ test/parser/test-yaboot-empty \ test/parser/test-yaboot-single \ diff --git a/test/parser/test-grub2-blscfg-multiple-bls.c b/test/parser/test-grub2-blscfg-multiple-bls.c new file mode 100644 index 000000000000..8fd218c371e8 --- /dev/null +++ b/test/parser/test-grub2-blscfg-multiple-bls.c @@ -0,0 +1,44 @@ +#include "parser-test.h" + +#if 0 /* PARSER_EMBEDDED_CONFIG */ +blscfg +#endif + +void run_test(struct parser_test *test) +{ + struct discover_boot_option *opt; + struct discover_context *ctx; + + test_add_file_string(test, test->ctx->device, + "/loader/entries/6c063c8e48904f2684abde8eea303f41-4.15.2-302.fc28.x86_64.conf", + "title Fedora (4.15.2-302.fc28.x86_64) 28 (Twenty Eight)\n" + "linux /vmlinuz-4.15.2-302.fc28.x86_64\n" + "initrd /initramfs-4.15.2-302.fc28.x86_64.img\n" + "options root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root\n\n"); + + test_add_file_string(test, test->ctx->device, + "/loader/entries/6c063c8e48904f2684abde8eea303f41-4.14.18-300.fc28.x86_64.conf", + "title Fedora (4.14.18-300.fc28.x86_64) 28 (Twenty Eight)\n" + "linux /vmlinuz-4.14.18-300.fc28.x86_64\n" + "initrd /initramfs-4.14.18-300.fc28.x86_64.img\n" + "options root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root\n"); + + test_read_conf_embedded(test, "/boot/grub2/grub.cfg"); + + test_run_parser(test, "grub2"); + + ctx = test->ctx; + + check_boot_option_count(ctx, 2); + opt = get_boot_option(ctx, 0); + + check_name(opt, "Fedora (4.15.2-302.fc28.x86_64) 28 (Twenty Eight)"); + check_resolved_local_resource(opt->boot_image, ctx->device, + "/vmlinuz-4.15.2-302.fc28.x86_64"); + + opt = get_boot_option(ctx, 1); + + check_name(opt, "Fedora (4.14.18-300.fc28.x86_64) 28 (Twenty Eight)"); + check_resolved_local_resource(opt->initrd, ctx->device, + "/initramfs-4.14.18-300.fc28.x86_64.img"); +} diff --git a/test/parser/test-grub2-blscfg-opts-config.c b/test/parser/test-grub2-blscfg-opts-config.c new file mode 100644 index 000000000000..856aae2adf5f --- /dev/null +++ b/test/parser/test-grub2-blscfg-opts-config.c @@ -0,0 +1,29 @@ +#include "parser-test.h" + +#if 0 /* PARSER_EMBEDDED_CONFIG */ +set kernelopts=root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root +blscfg +#endif + +void run_test(struct parser_test *test) +{ + struct discover_boot_option *opt; + struct discover_context *ctx; + + test_add_file_string(test, test->ctx->device, + "/loader/entries/6c063c8e48904f2684abde8eea303f41-4.15.2-302.fc28.x86_64.conf", + "title Fedora (4.15.2-302.fc28.x86_64) 28 (Twenty Eight)\n" + "linux /vmlinuz-4.15.2-302.fc28.x86_64\n" + "initrd /initramfs-4.15.2-302.fc28.x86_64.img\n" + "options $kernelopts\n"); + + test_read_conf_embedded(test, "/boot/grub2/grub.cfg"); + + test_run_parser(test, "grub2"); + + ctx = test->ctx; + + opt = get_boot_option(ctx, 0); + + check_args(opt, "root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root"); +} diff --git a/test/parser/test-grub2-blscfg-opts-grubenv.c b/test/parser/test-grub2-blscfg-opts-grubenv.c new file mode 100644 index 000000000000..c77c589b7707 --- /dev/null +++ b/test/parser/test-grub2-blscfg-opts-grubenv.c @@ -0,0 +1,34 @@ +#include "parser-test.h" + +#if 0 /* PARSER_EMBEDDED_CONFIG */ +load_env +blscfg +#endif + +void run_test(struct parser_test *test) +{ + struct discover_boot_option *opt; + struct discover_context *ctx; + + test_add_file_string(test, test->ctx->device, + "/boot/grub2/grubenv", + "# GRUB Environment Block\n" + "kernelopts=root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root\n"); + + test_add_file_string(test, test->ctx->device, + "/loader/entries/6c063c8e48904f2684abde8eea303f41-4.15.2-302.fc28.x86_64.conf", + "title Fedora (4.15.2-302.fc28.x86_64) 28 (Twenty Eight)\n" + "linux /vmlinuz-4.15.2-302.fc28.x86_64\n" + "initrd /initramfs-4.15.2-302.fc28.x86_64.img\n" + "options $kernelopts\n"); + + test_read_conf_embedded(test, "/boot/grub2/grub.cfg"); + + test_run_parser(test, "grub2"); + + ctx = test->ctx; + + opt = get_boot_option(ctx, 0); + + check_args(opt, "root=/dev/mapper/fedora-root ro rd.lvm.lv=fedora/root"); +} diff --git a/test/parser/utils.c b/test/parser/utils.c index 8900bd72bebd..683bba7d0379 100644 --- a/test/parser/utils.c +++ b/test/parser/utils.c @@ -309,6 +309,65 @@ int parser_replace_file(struct discover_context *ctx, return 0; } +int parser_scandir(struct discover_context *ctx, const char *dirname, + struct dirent ***files, int (*filter)(const struct dirent *) + __attribute__((unused)), + int (*comp)(const struct dirent **, const struct dirent **) + __attribute__((unused))) +{ + struct parser_test *test = ctx->test_data; + struct test_file *f; + char *filename; + struct dirent **dirents = NULL, **new_dirents; + int n = 0, namelen; + + list_for_each_entry(&test->files, f, list) { + if (f->dev != ctx->device) + continue; + + filename = strrchr(f->name, '/'); + if (!filename) + continue; + + namelen = strlen(filename); + + if (strncmp(f->name, dirname, strlen(f->name) - namelen)) + continue; + + if (!dirents) { + dirents = malloc(sizeof(struct dirent *)); + } else { + new_dirents = realloc(dirents, sizeof(struct dirent *) + * (n + 1)); + if (!new_dirents) + goto err_cleanup; + + dirents = new_dirents; + } + + dirents[n] = malloc(sizeof(struct dirent) + namelen + 1); + + if (!dirents[n]) + goto err_cleanup; + + strcpy(dirents[n]->d_name, filename + 1); + n++; + } + + *files = dirents; + + return n; + +err_cleanup: + do { + free(dirents[n]); + } while (n-- > 0); + + free(dirents); + + return -1; +} + struct load_url_result *load_url_async(void *ctx, struct pb_url *url, load_url_complete async_cb, void *async_data, waiter_cb stdout_cb, void *stdout_data)