diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index b79e830844..a38dcaab8b 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -51,12 +51,16 @@ static char *arg_root = NULL; static char *arg_image = NULL; static ImagePolicy *arg_image_policy = NULL; static bool arg_legend = true; +static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO; +static char *arg_entry_token = NULL; +static BootEntryType arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); typedef enum Action { ACTION_ADD, @@ -109,7 +113,15 @@ typedef struct Context { char **envp; } Context; -#define CONTEXT_NULL (Context) { .rfd = -EBADF } +#define CONTEXT_NULL \ + (Context) { \ + .rfd = XAT_FDROOT, \ + .action = _ACTION_INVALID, \ + .kernel_image_type = _KERNEL_IMAGE_TYPE_INVALID, \ + .layout = _LAYOUT_INVALID, \ + .entry_type = _BOOT_ENTRY_TYPE_INVALID, \ + .entry_token_type = _BOOT_ENTRY_TOKEN_TYPE_INVALID, \ + } static void context_done(Context *c) { assert(c); @@ -429,20 +441,6 @@ static int context_set_initrds(Context *c, char* const* strv) { return context_set_path_strv(c, strv, "command line", "initrds", &c->initrds); } -static int context_set_entry_type(Context *c, const char *s) { - assert(c); - BootEntryType e; - if (isempty(s) || streq(s, "all")) { - c->entry_type = _BOOT_ENTRY_TYPE_INVALID; - return 0; - } - e = boot_entry_type_from_string(s); - if (e < 0) - return log_error_errno(e, "Invalid entry type: %s", s); - c->entry_type = e; - return 1; -} - static int context_load_environment(Context *c) { assert(c); @@ -695,11 +693,16 @@ static int context_load_plugins(Context *c) { return 0; } -static int context_init(Context *c) { +static int context_setup(Context *c) { int r; assert(c); + if (c->kernel_image_type < 0) + c->kernel_image_type = KERNEL_IMAGE_TYPE_UNKNOWN; + if (c->entry_token_type < 0) + c->entry_token_type = BOOT_ENTRY_TOKEN_AUTO; + r = context_open_root(c); if (r < 0) return r; @@ -735,6 +738,24 @@ static int context_init(Context *c) { return 0; } +static int context_from_cmdline(Context *c, Action action) { + int r; + + assert(c); + + c->action = action; + + c->entry_type = arg_boot_entry_type; + + r = free_and_strdup_warn(&c->entry_token, arg_entry_token); + if (r < 0) + return r; + + c->entry_token_type = arg_entry_token_type; + + return context_setup(c); +} + static int context_inspect_kernel(Context *c) { assert(c); @@ -768,9 +789,18 @@ static int context_ensure_layout(Context *c) { if (!srel_path) return log_oom(); - _cleanup_free_ char *srel = NULL; - r = read_one_line_file_at(c->rfd, srel_path, &srel); - if (r >= 0) { + _cleanup_fclose_ FILE *f = NULL; + r = chase_and_fopenat_unlocked(c->rfd, srel_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to open '%s': %m", srel_path); + } else { + _cleanup_free_ char *srel = NULL; + + r = read_line(f, LONG_LINE_MAX, &srel); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", srel_path); + if (streq(srel, "type1")) /* The loader/entries.srel file clearly indicates that the installed boot loader * implements the proper standard upstream boot loader spec for Type #1 entries. @@ -784,17 +814,17 @@ static int context_ensure_layout(Context *c) { log_debug("%s with '%s' found, using layout=%s.", srel_path, srel, layout_to_string(c->layout)); return 0; - } else if (r != -ENOENT) - return log_error_errno(r, "Failed to read %s: %m", srel_path); + } _cleanup_free_ char *entry_token_path = path_join(c->boot_root, c->entry_token); if (!entry_token_path) return log_oom(); - r = is_dir_at(c->rfd, entry_token_path, /* follow= */ false); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to check if '%s' is a directory: %m", entry_token_path); - if (r > 0) { + r = chaseat(c->rfd, entry_token_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); + if (r < 0) { + if (!IN_SET(r, -ENOENT, -ENOTDIR)) + return log_error_errno(r, "Failed to check if '%s' exists and is a directory: %m", entry_token_path); + } else { /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token * directory already exists. If so, let's assume it's the standard boot loader spec, too. */ c->layout = LAYOUT_BLS; @@ -1091,6 +1121,26 @@ static bool bypass(void) { return should_bypass("KERNEL_INSTALL"); } +static int kernel_from_version(const char *version, char **ret_kernel) { + _cleanup_free_ char *vmlinuz = NULL; + int r; + + assert(version); + + vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz"); + if (!vmlinuz) + return log_oom(); + + r = access_nofollow(vmlinuz, F_OK); + if (r == -ENOENT) + return log_error_errno(r, "Kernel image not installed to '%s', specify kernel kernel image path explicitly.", vmlinuz); + if (r < 0) + return log_error_errno(r, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz); + + *ret_kernel = TAKE_PTR(vmlinuz); + return 0; +} + static int do_add( Context *c, const char *version, @@ -1100,8 +1150,21 @@ static int do_add( int r; assert(c); - assert(version); - assert(kernel); + + struct utsname un; + if (!version) { + assert_se(uname(&un) >= 0); + version = un.release; + } + + _cleanup_free_ char *vmlinuz = NULL; + if (!kernel) { + r = kernel_from_version(version, &vmlinuz); + if (r < 0) + return r; + + kernel = vmlinuz; + } r = context_set_version(c, version); if (r < 0) @@ -1122,32 +1185,9 @@ static int do_add( return context_execute(c); } -static int kernel_from_version(const char *version, char **ret_kernel) { - _cleanup_free_ char *vmlinuz = NULL; - int r; - - assert(version); - - vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz"); - if (!vmlinuz) - return log_oom(); - - r = access_nofollow(vmlinuz, F_OK); - if (r == -ENOENT) - return log_error_errno(r, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz); - if (r < 0) - return log_error_errno(r, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz); - - *ret_kernel = TAKE_PTR(vmlinuz); - return 0; -} - static int verb_add(int argc, char *argv[], void *userdata) { - Context *c = ASSERT_PTR(userdata); - _cleanup_free_ char *vmlinuz = NULL; const char *version, *kernel; char **initrds; - struct utsname un; int r; assert(argv); @@ -1158,7 +1198,10 @@ static int verb_add(int argc, char *argv[], void *userdata) { if (bypass()) return 0; - c->action = ACTION_ADD; + _cleanup_(context_done) Context c = CONTEXT_NULL; + r = context_from_cmdline(&c, ACTION_ADD); + if (r < 0) + return r; /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is * specified we take it as the kernel path, not the version, i.e. it's the first argument that is @@ -1168,24 +1211,10 @@ static int verb_add(int argc, char *argv[], void *userdata) { (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL); initrds = strv_skip(argv, 3); - if (!version) { - assert_se(uname(&un) >= 0); - version = un.release; - } - - if (!kernel) { - r = kernel_from_version(version, &vmlinuz); - if (r < 0) - return r; - - kernel = vmlinuz; - } - - return do_add(c, version, kernel, initrds); + return do_add(&c, version, kernel, initrds); } static int verb_add_all(int argc, char *argv[], void *userdata) { - Context *c = ASSERT_PTR(userdata); _cleanup_close_ int fd = -EBADF; size_t n = 0; int ret = 0, r; @@ -1198,9 +1227,12 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { if (bypass()) return 0; - c->action = ACTION_ADD; + _cleanup_(context_done) Context c = CONTEXT_NULL; + r = context_from_cmdline(&c, ACTION_ADD); + if (r < 0) + return r; - fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); @@ -1234,7 +1266,7 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { _cleanup_(context_done) Context copy = CONTEXT_NULL; - r = context_copy(c, ©); + r = context_copy(&c, ©); if (r < 0) return log_error_errno(r, "Failed to copy execution context: %m"); @@ -1262,18 +1294,17 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { return ret; } -static int run_as_installkernel(int argc, char *argv[], Context *c) { +static int run_as_installkernel(int argc, char *argv[]) { /* kernel's install.sh invokes us as * /sbin/installkernel * We ignore the last two arguments. */ if (optind + 2 > argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "'installkernel' command requires at least two arguments."); - return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), c); + return verb_add(3, STRV_MAKE("add", argv[optind], argv[optind+1]), /* userdata= */ NULL); } static int verb_remove(int argc, char *argv[], void *userdata) { - Context *c = ASSERT_PTR(userdata); int r; assert(argc >= 2); @@ -1289,25 +1320,27 @@ static int verb_remove(int argc, char *argv[], void *userdata) { if (bypass()) return 0; - c->action = ACTION_REMOVE; + _cleanup_(context_done) Context c = CONTEXT_NULL; + r = context_from_cmdline(&c, ACTION_REMOVE); + if (r < 0) + return r; /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do * it for the "add" verb), since we don't want to make it too easy to uninstall your running * kernel, as a safety precaution */ - r = context_set_version(c, argv[1]); + r = context_set_version(&c, argv[1]); if (r < 0) return r; - r = context_prepare_execution(c); + r = context_prepare_execution(&c); if (r < 0) return r; - return context_execute(c); + return context_execute(&c); } static int verb_inspect(int argc, char *argv[], void *userdata) { - Context *c = ASSERT_PTR(userdata); _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *vmlinuz = NULL; const char *version, *kernel; @@ -1315,7 +1348,10 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { struct utsname un; int r; - c->action = ACTION_INSPECT; + _cleanup_(context_done) Context c = CONTEXT_NULL; + r = context_from_cmdline(&c, ACTION_INSPECT); + if (r < 0) + return r; /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but @@ -1340,19 +1376,19 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { kernel = vmlinuz; } - r = context_set_version(c, version); + r = context_set_version(&c, version); if (r < 0) return r; - r = context_set_kernel(c, kernel); + r = context_set_kernel(&c, kernel); if (r < 0) return r; - r = context_set_initrds(c, initrds); + r = context_set_initrds(&c, initrds); if (r < 0) return r; - r = context_prepare_execution(c); + r = context_prepare_execution(&c); if (r < 0) return r; @@ -1362,40 +1398,40 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { r = table_add_many(t, TABLE_FIELD, "Machine ID", - TABLE_ID128, c->machine_id, + TABLE_ID128, c.machine_id, TABLE_FIELD, "Kernel Image Type", - TABLE_STRING, kernel_image_type_to_string(c->kernel_image_type), + TABLE_STRING, kernel_image_type_to_string(c.kernel_image_type), TABLE_FIELD, "Layout", - TABLE_STRING, context_get_layout(c), + TABLE_STRING, context_get_layout(&c), TABLE_FIELD, "Boot Root", - TABLE_STRING, c->boot_root, + TABLE_STRING, c.boot_root, TABLE_FIELD, "Entry Token Type", - TABLE_STRING, boot_entry_token_type_to_string(c->entry_token_type), + TABLE_STRING, boot_entry_token_type_to_string(c.entry_token_type), TABLE_FIELD, "Entry Token", - TABLE_STRING, c->entry_token, + TABLE_STRING, c.entry_token, TABLE_FIELD, "Entry Directory", - TABLE_STRING, c->entry_dir, + TABLE_STRING, c.entry_dir, TABLE_FIELD, "Kernel Version", - TABLE_VERSION, c->version, + TABLE_VERSION, c.version, TABLE_FIELD, "Kernel", - TABLE_STRING, c->kernel, + TABLE_STRING, c.kernel, TABLE_FIELD, "Initrds", - TABLE_STRV, c->initrds, + TABLE_STRV, c.initrds, TABLE_FIELD, "Initrd Generator", - TABLE_STRING, c->initrd_generator, + TABLE_STRING, c.initrd_generator, TABLE_FIELD, "UKI Generator", - TABLE_STRING, c->uki_generator, + TABLE_STRING, c.uki_generator, TABLE_FIELD, "Plugins", - TABLE_STRV, c->plugins, + TABLE_STRV, c.plugins, TABLE_FIELD, "Plugin Environment", - TABLE_STRV, c->envp); + TABLE_STRV, c.envp); if (r < 0) return table_log_add_error(r); if (!sd_json_format_enabled(arg_json_format_flags)) { r = table_add_many(t, TABLE_FIELD, "Plugin Arguments", - TABLE_STRV, strv_skip(c->argv, 1)); + TABLE_STRV, strv_skip(c.argv, 1)); if (r < 0) return table_log_add_error(r); } @@ -1418,11 +1454,15 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { } static int verb_list(int argc, char *argv[], void *userdata) { - Context *c = ASSERT_PTR(userdata); _cleanup_close_ int fd = -EBADF; int r; - fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + _cleanup_(context_done) Context c = CONTEXT_NULL; + r = context_from_cmdline(&c, ACTION_INSPECT); + if (r < 0) + return r; + + fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); @@ -1532,7 +1572,7 @@ static int help(void) { return 0; } -static int parse_argv(int argc, char *argv[], Context *c) { +static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_LEGEND, @@ -1568,7 +1608,6 @@ static int parse_argv(int argc, char *argv[], Context *c) { assert(argc >= 0); assert(argv); - assert(c); while ((t = getopt_long(argc, argv, "hv", options, NULL)) >= 0) switch (t) { @@ -1612,7 +1651,7 @@ static int parse_argv(int argc, char *argv[], Context *c) { break; case ARG_ENTRY_TOKEN: - r = parse_boot_entry_token_type(optarg, &c->entry_token_type, &c->entry_token); + r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); if (r < 0) return r; break; @@ -1645,11 +1684,18 @@ static int parse_argv(int argc, char *argv[], Context *c) { return r; break; - case ARG_BOOT_ENTRY_TYPE: - r = context_set_entry_type(c, optarg); - if (r < 0) - return r; + case ARG_BOOT_ENTRY_TYPE: { + if (isempty(optarg) || streq(optarg, "all")) { + arg_boot_entry_type = _BOOT_ENTRY_TYPE_INVALID; + break; + } + + BootEntryType e = boot_entry_type_from_string(optarg); + if (e < 0) + return log_error_errno(e, "Invalid entry type: %s", optarg); + arg_boot_entry_type = e; break; + } case '?': return -EINVAL; @@ -1664,33 +1710,30 @@ static int parse_argv(int argc, char *argv[], Context *c) { return 1; } -static int run(int argc, char* argv[]) { +static int kernel_install_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "add", 1, VERB_ANY, 0, verb_add }, - { "add-all", 1, 1, 0, verb_add_all }, - { "remove", 2, VERB_ANY, 0, verb_remove }, - { "inspect", 1, VERB_ANY, VERB_DEFAULT, verb_inspect }, - { "list", 1, 1, 0, verb_list }, + { "add", 1, VERB_ANY, 0, verb_add }, + { "add-all", 1, 1, 0, verb_add_all }, + { "remove", 2, VERB_ANY, 0, verb_remove }, + { "inspect", 1, VERB_ANY, VERB_DEFAULT, verb_inspect }, + { "list", 1, 1, 0, verb_list }, {} }; - _cleanup_(context_done) Context c = { - .rfd = AT_FDCWD, - .action = _ACTION_INVALID, - .kernel_image_type = KERNEL_IMAGE_TYPE_UNKNOWN, - .layout = _LAYOUT_INVALID, - .entry_type = _BOOT_ENTRY_TYPE_INVALID, - .entry_token_type = BOOT_ENTRY_TOKEN_AUTO, - }; - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_freep) char *mounted_dir = NULL; + + return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL); +} + +static int run(int argc, char* argv[]) { int r; log_setup(); - r = parse_argv(argc, argv, &c); + r = parse_argv(argc, argv); if (r <= 0) return r; + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_freep) char *mounted_dir = NULL; if (arg_image) { assert(!arg_root); @@ -1713,14 +1756,10 @@ static int run(int argc, char* argv[]) { return log_oom(); } - r = context_init(&c); - if (r < 0) - return r; - if (invoked_as(argv, "installkernel")) - return run_as_installkernel(argc, argv, &c); + return run_as_installkernel(argc, argv); - return dispatch_verb(argc, argv, verbs, &c); + return kernel_install_main(argc, argv); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);