1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include <sys/utsname.h>
7 #include "boot-entry.h"
10 #include "conf-files.h"
11 #include "dirent-util.h"
14 #include "exec-util.h"
18 #include "format-table.h"
20 #include "id128-util.h"
21 #include "image-policy.h"
22 #include "kernel-image.h"
23 #include "main-func.h"
25 #include "mount-util.h"
26 #include "parse-argument.h"
27 #include "path-util.h"
28 #include "pretty-print.h"
29 #include "recurse-dir.h"
31 #include "stat-util.h"
32 #include "string-table.h"
33 #include "string-util.h"
35 #include "tmpfile-util.h"
38 static bool arg_verbose
= false;
39 static char *arg_esp_path
= NULL
;
40 static char *arg_xbootldr_path
= NULL
;
41 static int arg_make_entry_directory
= -1; /* tristate */
42 static PagerFlags arg_pager_flags
= 0;
43 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
44 static char *arg_root
= NULL
;
45 static char *arg_image
= NULL
;
46 static ImagePolicy
*arg_image_policy
= NULL
;
47 static bool arg_legend
= true;
49 STATIC_DESTRUCTOR_REGISTER(arg_esp_path
, freep
);
50 STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path
, freep
);
51 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
52 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
53 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
60 _ACTION_INVALID
= -EINVAL
,
69 _LAYOUT_INVALID
= -EINVAL
,
72 static const char * const layout_table
[_LAYOUT_MAX
] = {
73 [LAYOUT_AUTO
] = "auto",
76 [LAYOUT_OTHER
] = "other",
79 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(layout
, Layout
);
81 typedef struct Context
{
84 sd_id128_t machine_id
;
85 bool machine_id_is_random
;
86 KernelImageType kernel_image_type
;
91 BootEntryTokenType entry_token_type
;
97 char *initrd_generator
;
105 #define CONTEXT_NULL (Context) { .rfd = -EBADF }
107 static void context_done(Context
*c
) {
110 free(c
->layout_other
);
113 free(c
->entry_token
);
117 strv_free(c
->initrds
);
118 free(c
->initrd_generator
);
119 free(c
->uki_generator
);
120 if (c
->action
== ACTION_INSPECT
)
121 free(c
->staging_area
);
123 rm_rf_physical_and_free(c
->staging_area
);
124 strv_free(c
->plugins
);
131 static int context_copy(const Context
*source
, Context
*ret
) {
136 assert(source
->rfd
>= 0 || source
->rfd
== AT_FDCWD
);
138 _cleanup_(context_done
) Context copy
= (Context
) {
140 .action
= source
->action
,
141 .machine_id
= source
->machine_id
,
142 .machine_id_is_random
= source
->machine_id_is_random
,
143 .kernel_image_type
= source
->kernel_image_type
,
144 .layout
= source
->layout
,
145 .entry_token_type
= source
->entry_token_type
,
148 if (source
->rfd
>= 0) {
149 copy
.rfd
= fd_reopen(source
->rfd
, O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
154 r
= strdup_or_null(source
->layout_other
, ©
.layout_other
);
157 r
= strdup_or_null(source
->conf_root
, ©
.conf_root
);
160 r
= strdup_or_null(source
->boot_root
, ©
.boot_root
);
163 r
= strdup_or_null(source
->entry_token
, ©
.entry_token
);
166 r
= strdup_or_null(source
->entry_dir
, ©
.entry_dir
);
169 r
= strdup_or_null(source
->version
, ©
.version
);
172 r
= strdup_or_null(source
->kernel
, ©
.kernel
);
175 r
= strv_copy_unless_empty(source
->initrds
, ©
.initrds
);
178 r
= strdup_or_null(source
->initrd_generator
, ©
.initrd_generator
);
181 r
= strdup_or_null(source
->uki_generator
, ©
.uki_generator
);
184 r
= strdup_or_null(source
->staging_area
, ©
.staging_area
);
187 r
= strv_copy_unless_empty(source
->plugins
, ©
.plugins
);
190 r
= strv_copy_unless_empty(source
->argv
, ©
.argv
);
193 r
= strv_copy_unless_empty(source
->envp
, ©
.envp
);
203 static int context_open_root(Context
*c
) {
209 if (isempty(arg_root
))
212 r
= path_is_root(arg_root
);
214 return log_error_errno(r
, "Failed to determine if '%s' is the root directory: %m", arg_root
);
218 c
->rfd
= open(empty_to_root(arg_root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
220 return log_error_errno(errno
, "Failed to open root directory '%s': %m", empty_to_root(arg_root
));
225 static const char* context_get_layout(const Context
*c
) {
227 assert(c
->layout
>= 0);
229 return c
->layout_other
?: layout_to_string(c
->layout
);
232 static int context_set_layout(Context
*c
, const char *s
, const char *source
) {
238 if (c
->layout
>= 0 || !s
)
241 assert(!c
->layout_other
);
243 t
= layout_from_string(s
);
247 c
->layout
= LAYOUT_AUTO
;
249 c
->layout_other
= strdup(s
);
250 if (!c
->layout_other
)
253 c
->layout
= LAYOUT_OTHER
;
256 log_debug("layout=%s set via %s", context_get_layout(c
), source
);
260 static int context_set_machine_id(Context
*c
, const char *s
, const char *source
) {
266 if (!sd_id128_is_null(c
->machine_id
) || !s
)
269 r
= sd_id128_from_string(s
, &c
->machine_id
);
271 return log_warning_errno(r
, "Failed to parse machine ID specified via %s, ignoring.", source
);
273 if (sd_id128_is_null(c
->machine_id
))
276 log_debug("MACHINE_ID=%s set via %s.", SD_ID128_TO_STRING(c
->machine_id
), source
);
280 static int context_set_string(const char *s
, const char *source
, const char *name
, char **dest
) {
294 log_debug("%s (%s) set via %s.", name
, p
, source
);
300 static int context_set_initrd_generator(Context
*c
, const char *s
, const char *source
) {
302 return context_set_string(s
, source
, "INITRD_GENERATOR", &c
->initrd_generator
);
305 static int context_set_uki_generator(Context
*c
, const char *s
, const char *source
) {
307 return context_set_string(s
, source
, "UKI_GENERATOR", &c
->uki_generator
);
310 static int context_set_version(Context
*c
, const char *s
) {
313 if (s
&& !filename_is_valid(s
))
314 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid version specified: %s", s
);
316 return context_set_string(s
, "command line", "kernel version", &c
->version
);
319 static int context_set_path(Context
*c
, const char *s
, const char *source
, const char *name
, char **dest
) {
332 r
= chaseat(c
->rfd
, s
, CHASE_AT_RESOLVE_IN_ROOT
, &p
, /* ret_fd = */ NULL
);
334 return log_warning_errno(r
, "Failed to chase path %s for %s specified via %s, ignoring: %m",
337 r
= path_make_absolute_cwd(s
, &p
);
339 return log_warning_errno(r
, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
343 log_debug("%s (%s) set via %s.", name
, p
, source
);
349 static int context_set_boot_root(Context
*c
, const char *s
, const char *source
) {
351 return context_set_path(c
, s
, source
, "BOOT_ROOT", &c
->boot_root
);
354 static int context_set_conf_root(Context
*c
, const char *s
, const char *source
) {
356 return context_set_path(c
, s
, source
, "CONF_ROOT", &c
->conf_root
);
359 static int context_set_kernel(Context
*c
, const char *s
) {
361 return context_set_path(c
, s
, "command line", "kernel image file", &c
->kernel
);
364 static int context_set_path_strv(Context
*c
, char* const* strv
, const char *source
, const char *name
, char ***dest
) {
365 _cleanup_strv_free_
char **w
= NULL
;
376 STRV_FOREACH(s
, strv
) {
380 r
= chaseat(c
->rfd
, *s
, CHASE_AT_RESOLVE_IN_ROOT
, &p
, /* ret_fd = */ NULL
);
382 return log_warning_errno(r
, "Failed to chase path %s for %s specified via %s: %m",
385 r
= path_make_absolute_cwd(*s
, &p
);
387 return log_warning_errno(r
, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
390 r
= strv_consume(&w
, p
);
398 log_debug("%s set via %s", name
, source
);
404 static int context_set_plugins(Context
*c
, const char *s
, const char *source
) {
405 _cleanup_strv_free_
char **v
= NULL
;
409 if (c
->plugins
|| !s
)
412 v
= strv_split(s
, NULL
);
416 return context_set_path_strv(c
, v
, source
, "plugins", &c
->plugins
);
419 static int context_set_initrds(Context
*c
, char* const* strv
) {
421 return context_set_path_strv(c
, strv
, "command line", "initrds", &c
->initrds
);
424 static int context_load_environment(Context
*c
) {
427 (void) context_set_machine_id(c
, getenv("MACHINE_ID"), "environment");
428 (void) context_set_boot_root(c
, getenv("BOOT_ROOT"), "environment");
429 (void) context_set_conf_root(c
, getenv("KERNEL_INSTALL_CONF_ROOT"), "environment");
430 (void) context_set_plugins(c
, getenv("KERNEL_INSTALL_PLUGINS"), "environment");
434 static int context_load_install_conf_one(Context
*c
, const char *path
) {
435 _cleanup_fclose_
FILE *f
= NULL
;
437 *conf
= NULL
, *machine_id
= NULL
, *boot_root
= NULL
, *layout
= NULL
,
438 *initrd_generator
= NULL
, *uki_generator
= NULL
;
444 conf
= path_join(path
, "install.conf");
448 r
= chase_and_fopenat_unlocked(c
->rfd
, conf
, CHASE_AT_RESOLVE_IN_ROOT
, "re", NULL
, &f
);
452 return log_error_errno(r
, "Failed to chase %s: %m", conf
);
454 log_debug("Loading %s…", conf
);
456 r
= parse_env_file(f
, conf
,
457 "MACHINE_ID", &machine_id
,
458 "BOOT_ROOT", &boot_root
,
460 "initrd_generator", &initrd_generator
,
461 "uki_generator", &uki_generator
);
463 return log_error_errno(r
, "Failed to parse '%s': %m", conf
);
465 (void) context_set_machine_id(c
, machine_id
, conf
);
466 (void) context_set_boot_root(c
, boot_root
, conf
);
467 (void) context_set_layout(c
, layout
, conf
);
468 (void) context_set_initrd_generator(c
, initrd_generator
, conf
);
469 (void) context_set_uki_generator(c
, uki_generator
, conf
);
471 log_debug("Loaded %s.", conf
);
475 static int context_load_install_conf(Context
*c
) {
481 r
= context_load_install_conf_one(c
, c
->conf_root
);
486 FOREACH_STRING(p
, "/etc/kernel", "/usr/lib/kernel") {
487 r
= context_load_install_conf_one(c
, p
);
495 static int context_load_machine_info(Context
*c
) {
496 _cleanup_fclose_
FILE *f
= NULL
;
497 _cleanup_free_
char *machine_id
= NULL
, *layout
= NULL
;
498 static const char *path
= "/etc/machine-info";
503 /* If the user configured an explicit machine ID in /etc/machine-info to use for our purpose, we'll
504 * use that instead (for compatibility). */
506 if (!sd_id128_is_null(c
->machine_id
) && c
->layout
>= 0)
509 /* For testing. To make not read host's /etc/machine-info. */
510 r
= getenv_bool("KERNEL_INSTALL_READ_MACHINE_INFO");
511 if (r
< 0 && r
!= -ENXIO
)
512 log_warning_errno(r
, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m");
514 log_debug("Skipping to read /etc/machine-info.");
518 r
= chase_and_fopenat_unlocked(c
->rfd
, path
, CHASE_AT_RESOLVE_IN_ROOT
, "re", NULL
, &f
);
522 return log_error_errno(r
, "Failed to chase %s: %m", path
);
524 log_debug("Loading %s…", path
);
526 r
= parse_env_file(f
, path
,
527 "KERNEL_INSTALL_MACHINE_ID", &machine_id
,
528 "KERNEL_INSTALL_LAYOUT", &layout
);
530 return log_error_errno(r
, "Failed to parse '%s': %m", path
);
532 (void) context_set_machine_id(c
, machine_id
, path
);
533 (void) context_set_layout(c
, layout
, path
);
537 static int context_load_machine_id(Context
*c
) {
542 r
= id128_get_machine_at(c
->rfd
, &c
->machine_id
);
543 if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r
))
546 return log_error_errno(r
, "Failed to load machine ID from /etc/machine-id: %m");
548 log_debug("MACHINE_ID=%s set via /etc/machine-id.", SD_ID128_TO_STRING(c
->machine_id
));
549 return 1; /* loaded */
552 static int context_ensure_machine_id(Context
*c
) {
557 if (!sd_id128_is_null(c
->machine_id
))
560 /* If /etc/machine-id is initialized we'll use it. */
561 r
= context_load_machine_id(c
);
565 /* Otherwise we'll use a freshly generated one. */
566 r
= sd_id128_randomize(&c
->machine_id
);
568 return log_error_errno(r
, "Failed to generate random ID: %m");
570 c
->machine_id_is_random
= true;
571 log_debug("New machine ID '%s' generated.", SD_ID128_TO_STRING(c
->machine_id
));
575 static int context_acquire_xbootldr(Context
*c
) {
579 assert(!c
->boot_root
);
581 r
= find_xbootldr_and_warn_at(
583 /* path = */ arg_xbootldr_path
,
584 /* unprivileged_mode= */ -1,
585 /* ret_path = */ &c
->boot_root
,
586 /* ret_uuid = */ NULL
,
587 /* ret_devid = */ NULL
);
589 log_debug_errno(r
, "Couldn't find an XBOOTLDR partition.");
592 if (r
== -EACCES
&& geteuid() != 0)
593 return log_error_errno(r
, "Failed to determine XBOOTLDR partition: %m");
597 log_debug("Using XBOOTLDR partition at %s as $BOOT_ROOT.", c
->boot_root
);
598 return 1; /* found */
601 static int context_acquire_esp(Context
*c
) {
605 assert(!c
->boot_root
);
607 r
= find_esp_and_warn_at(
609 /* path = */ arg_esp_path
,
610 /* unprivileged_mode= */ -1,
611 /* ret_path = */ &c
->boot_root
,
612 /* ret_part = */ NULL
,
613 /* ret_pstart = */ NULL
,
614 /* ret_psize = */ NULL
,
615 /* ret_uuid = */ NULL
,
616 /* ret_devid = */ NULL
);
618 log_debug_errno(r
, "Couldn't find EFI system partition, ignoring.");
621 if (r
== -EACCES
&& geteuid() != 0)
622 return log_error_errno(r
, "Failed to determine EFI system partition: %m");
626 log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c
->boot_root
);
627 return 1; /* found */
630 static int context_ensure_boot_root(Context
*c
) {
635 /* If BOOT_ROOT is specified via environment or install.conf, then use it. */
639 /* Otherwise, use XBOOTLDR partition, if mounted. */
640 r
= context_acquire_xbootldr(c
);
644 /* Otherwise, use EFI system partition, if mounted. */
645 r
= context_acquire_esp(c
);
649 /* If all else fails, use /boot. */
651 r
= chaseat(c
->rfd
, "/boot", CHASE_AT_RESOLVE_IN_ROOT
, &c
->boot_root
, /* ret_fd = */ NULL
);
653 return log_error_errno(r
, "Failed to chase '/boot': %m");
655 c
->boot_root
= strdup("/boot");
660 log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c
->boot_root
);
664 static int context_ensure_entry_token(Context
*c
) {
669 /* Now that we determined the machine ID to use, let's determine the "token" for the boot loader
670 * entry to generate. We use that for naming the directory below $BOOT where we want to place the
671 * kernel/initrd and related resources, as well for naming the .conf boot loader spec entry.
672 * Typically this is just the machine ID, but it can be anything else, too, if we are told so. */
674 r
= boot_entry_token_ensure_at(
678 c
->machine_id_is_random
,
679 &c
->entry_token_type
,
684 log_debug("Using entry token: %s", c
->entry_token
);
688 static int context_load_plugins(Context
*c
) {
696 r
= conf_files_list_strv_at(
700 CONF_FILES_EXECUTABLE
| CONF_FILES_REGULAR
| CONF_FILES_FILTER_MASKED
,
701 STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d"));
703 return log_error_errno(r
, "Failed to find plugins: %m");
708 static int context_init(Context
*c
) {
713 r
= context_open_root(c
);
717 r
= context_load_environment(c
);
721 r
= context_load_install_conf(c
);
725 r
= context_load_machine_info(c
);
729 r
= context_ensure_machine_id(c
);
733 r
= context_ensure_boot_root(c
);
737 r
= context_ensure_entry_token(c
);
741 r
= context_load_plugins(c
);
748 static int context_inspect_kernel(Context
*c
) {
754 return inspect_kernel(c
->rfd
, c
->kernel
, &c
->kernel_image_type
, NULL
, NULL
, NULL
);
757 static int context_ensure_layout(Context
*c
) {
761 assert(c
->boot_root
);
762 assert(c
->entry_token
);
764 if (c
->layout
>= 0 && c
->layout
!= LAYOUT_AUTO
)
767 /* No layout configured by the administrator. Let's try to figure it out automatically from metadata
768 * already contained in $BOOT_ROOT. */
770 if (c
->kernel_image_type
== KERNEL_IMAGE_TYPE_UKI
) {
771 c
->layout
= LAYOUT_UKI
;
772 log_debug("Kernel image type is %s, using layout=%s.",
773 kernel_image_type_to_string(c
->kernel_image_type
), layout_to_string(c
->layout
));
777 _cleanup_free_
char *srel_path
= path_join(c
->boot_root
, "loader/entries.srel");
781 _cleanup_free_
char *srel
= NULL
;
782 r
= read_one_line_file_at(c
->rfd
, srel_path
, &srel
);
784 if (streq(srel
, "type1"))
785 /* The loader/entries.srel file clearly indicates that the installed boot loader
786 * implements the proper standard upstream boot loader spec for Type #1 entries.
787 * Let's default to that, then. */
788 c
->layout
= LAYOUT_BLS
;
790 /* The loader/entries.srel file indicates some other spec is implemented and owns the
791 * /loader/entries/ directory. Since we have no idea what that means, let's stay away
792 * from it by default. */
793 c
->layout
= LAYOUT_OTHER
;
795 log_debug("%s with '%s' found, using layout=%s.", srel_path
, srel
, layout_to_string(c
->layout
));
797 } else if (r
!= -ENOENT
)
798 return log_error_errno(r
, "Failed to read %s: %m", srel_path
);
800 _cleanup_free_
char *entry_token_path
= path_join(c
->boot_root
, c
->entry_token
);
801 if (!entry_token_path
)
804 r
= is_dir_full(c
->rfd
, entry_token_path
, /* follow = */ false);
805 if (r
< 0 && r
!= -ENOENT
)
806 return log_error_errno(r
, "Failed to check if '%s' is a directory: %m", entry_token_path
);
808 /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token
809 * directory already exists. If so, let's assume it's the standard boot loader spec, too. */
810 c
->layout
= LAYOUT_BLS
;
811 log_debug("%s exists, using layout=%s.", entry_token_path
, layout_to_string(c
->layout
));
815 /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we
816 * really don't know anything. */
817 c
->layout
= LAYOUT_OTHER
;
818 log_debug("Entry-token directory not found, using layout=%s.", layout_to_string(c
->layout
));
822 static int context_set_up_staging_area(Context
*c
) {
823 static const char *template = "/tmp/kernel-install.staging.XXXXXX";
831 if (c
->action
== ACTION_INSPECT
) {
832 /* This is only used for display. The directory will not be created. */
833 c
->staging_area
= strdup(template);
834 if (!c
->staging_area
)
837 r
= mkdtemp_malloc(template, &c
->staging_area
);
839 return log_error_errno(r
, "Failed to create staging area: %m");
845 static int context_build_entry_dir(Context
*c
) {
847 assert(c
->boot_root
);
848 assert(c
->entry_token
);
849 assert(c
->version
|| c
->action
== ACTION_INSPECT
);
854 c
->entry_dir
= path_join(c
->boot_root
, c
->entry_token
, c
->version
?: "KERNEL_VERSION");
858 log_debug("Using ENTRY_DIR=%s", c
->entry_dir
);
862 static bool context_should_make_entry_dir(Context
*c
) {
865 /* Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN to signal to
866 * 00-entry-directory to create $ENTRY_DIR to serve as the indication to use or to not use the BLS */
868 if (arg_make_entry_directory
< 0)
869 return c
->layout
== LAYOUT_BLS
;
871 return arg_make_entry_directory
;
874 static int context_make_entry_dir(Context
*c
) {
875 _cleanup_close_
int fd
= -EBADF
;
878 assert(c
->entry_dir
);
880 if (c
->action
!= ACTION_ADD
)
883 if (!context_should_make_entry_dir(c
))
886 log_debug("mkdir -p %s", c
->entry_dir
);
887 fd
= chase_and_openat(c
->rfd
, c
->entry_dir
, CHASE_AT_RESOLVE_IN_ROOT
| CHASE_MKDIR_0755
,
888 O_CLOEXEC
| O_CREAT
| O_DIRECTORY
| O_PATH
, NULL
);
890 return log_error_errno(fd
, "Failed to make directory '%s': %m", c
->entry_dir
);
895 static int context_remove_entry_dir(Context
*c
) {
896 _cleanup_free_
char *p
= NULL
;
897 _cleanup_close_
int fd
= -EBADF
;
902 assert(c
->entry_dir
);
904 if (c
->action
!= ACTION_REMOVE
)
907 if (!context_should_make_entry_dir(c
))
910 log_debug("rm -rf %s", c
->entry_dir
);
911 fd
= chase_and_openat(c
->rfd
, c
->entry_dir
, CHASE_AT_RESOLVE_IN_ROOT
, O_CLOEXEC
| O_DIRECTORY
, &p
);
913 if (IN_SET(fd
, -ENOTDIR
, -ENOENT
))
915 return log_debug_errno(fd
, "Failed to chase and open %s, ignoring: %m", c
->entry_dir
);
918 if (fstat(fd
, &st
) < 0)
919 return log_debug_errno(errno
, "Failed to stat %s: %m", p
);
921 r
= rm_rf_children(TAKE_FD(fd
), REMOVE_PHYSICAL
|REMOVE_MISSING_OK
|REMOVE_CHMOD
, &st
);
923 log_debug_errno(r
, "Failed to remove children of %s, ignoring: %m", p
);
925 if (unlinkat(c
->rfd
, p
, AT_REMOVEDIR
) < 0)
926 log_debug_errno(errno
, "Failed to remove %s, ignoring: %m", p
);
931 static int context_build_arguments(Context
*c
) {
932 _cleanup_strv_free_
char **a
= NULL
;
937 assert(c
->entry_dir
);
961 assert_not_reached();
964 a
= strv_new("dummy-arg", /* to make strv_free() works for this variable. */
966 c
->version
?: "KERNEL_VERSION",
971 if (c
->action
== ACTION_ADD
) {
972 r
= strv_extend(&a
, c
->kernel
);
976 r
= strv_extend_strv(&a
, c
->initrds
, /* filter_duplicates = */ false);
980 } else if (c
->action
== ACTION_INSPECT
) {
981 r
= strv_extend_many(
983 c
->kernel
?: "[KERNEL_IMAGE]",
989 c
->argv
= TAKE_PTR(a
);
993 static int context_build_environment(Context
*c
) {
994 _cleanup_strv_free_
char **e
= NULL
;
1002 r
= strv_env_assign_many(&e
,
1003 "LC_COLLATE", SYSTEMD_DEFAULT_LOCALE
,
1004 "KERNEL_INSTALL_VERBOSE", one_zero(arg_verbose
),
1005 "KERNEL_INSTALL_IMAGE_TYPE", kernel_image_type_to_string(c
->kernel_image_type
),
1006 "KERNEL_INSTALL_MACHINE_ID", SD_ID128_TO_STRING(c
->machine_id
),
1007 "KERNEL_INSTALL_ENTRY_TOKEN", c
->entry_token
,
1008 "KERNEL_INSTALL_BOOT_ROOT", c
->boot_root
,
1009 "KERNEL_INSTALL_LAYOUT", context_get_layout(c
),
1010 "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c
->initrd_generator
),
1011 "KERNEL_INSTALL_UKI_GENERATOR", strempty(c
->uki_generator
),
1012 "KERNEL_INSTALL_STAGING_AREA", c
->staging_area
);
1014 return log_error_errno(r
, "Failed to build environment variables for plugins: %m");
1016 c
->envp
= TAKE_PTR(e
);
1020 static int context_prepare_execution(Context
*c
) {
1025 r
= context_inspect_kernel(c
);
1029 r
= context_ensure_layout(c
);
1033 r
= context_set_up_staging_area(c
);
1037 r
= context_build_entry_dir(c
);
1041 r
= context_build_arguments(c
);
1045 r
= context_build_environment(c
);
1052 static int context_execute(Context
*c
) {
1057 r
= context_make_entry_dir(c
);
1061 if (DEBUG_LOGGING
) {
1062 _cleanup_free_
char *x
= strv_join_full(c
->plugins
, "", "\n ", /* escape_separator = */ false);
1063 log_debug("Using plugins: %s", strna(x
));
1065 _cleanup_free_
char *y
= strv_join_full(c
->envp
, "", "\n ", /* escape_separator = */ false);
1066 log_debug("Plugin environment: %s", strna(y
));
1068 _cleanup_free_
char *z
= strv_join(strv_skip(c
->argv
, 1), " ");
1069 log_debug("Plugin arguments: %s", strna(z
));
1077 /* callbacks = */ NULL
,
1078 /* callback_args = */ NULL
,
1081 EXEC_DIR_SKIP_REMAINING
);
1083 r
= context_remove_entry_dir(c
);
1087 /* This returns 0 on success, positive exit code on plugin failure, negative errno on other failures. */
1091 static bool bypass(void) {
1094 r
= getenv_bool("KERNEL_INSTALL_BYPASS");
1095 if (r
< 0 && r
!= -ENXIO
)
1096 log_debug_errno(r
, "Failed to parse $KERNEL_INSTALL_BYPASS, assuming no.");
1100 log_debug("$KERNEL_INSTALL_BYPASS is enabled, skipping execution.");
1106 const char *version
,
1116 r
= context_set_version(c
, version
);
1120 r
= context_set_kernel(c
, kernel
);
1124 r
= context_set_initrds(c
, initrds
);
1128 r
= context_prepare_execution(c
);
1132 return context_execute(c
);
1135 static int kernel_from_version(const char *version
, char **ret_kernel
) {
1136 _cleanup_free_
char *vmlinuz
= NULL
;
1141 vmlinuz
= path_join("/usr/lib/modules/", version
, "/vmlinuz");
1145 r
= laccess(vmlinuz
, F_OK
);
1148 return log_error_errno(r
, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz
);
1150 return log_error_errno(r
, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz
);
1153 *ret_kernel
= TAKE_PTR(vmlinuz
);
1157 static int verb_add(int argc
, char *argv
[], void *userdata
) {
1158 Context
*c
= ASSERT_PTR(userdata
);
1159 _cleanup_free_
char *vmlinuz
= NULL
;
1160 const char *version
, *kernel
;
1168 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'add' does not support --root= or --image=.");
1173 c
->action
= ACTION_ADD
;
1175 /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
1176 * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
1177 * optional, not the 2nd. */
1178 version
= argc
> 2 ? empty_or_dash_to_null(argv
[1]) : NULL
;
1179 kernel
= argc
> 2 ? empty_or_dash_to_null(argv
[2]) :
1180 (argc
> 1 ? empty_or_dash_to_null(argv
[1]) : NULL
);
1181 initrds
= strv_skip(argv
, 3);
1184 assert_se(uname(&un
) >= 0);
1185 version
= un
.release
;
1189 r
= kernel_from_version(version
, &vmlinuz
);
1196 return do_add(c
, version
, kernel
, initrds
);
1199 static int verb_add_all(int argc
, char *argv
[], void *userdata
) {
1200 Context
*c
= ASSERT_PTR(userdata
);
1201 _cleanup_close_
int fd
= -EBADF
;
1208 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'add-all' does not support --root= or --image=.");
1213 c
->action
= ACTION_ADD
;
1215 fd
= chase_and_openat(c
->rfd
, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
, NULL
);
1217 return log_error_errno(fd
, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root
));
1219 _cleanup_free_ DirectoryEntries
*de
= NULL
;
1220 r
= readdir_all(fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
1222 return log_error_errno(r
, "Failed to numerate /usr/lib/modules/ contents: %m");
1224 FOREACH_ARRAY(d
, de
->entries
, de
->n_entries
) {
1225 r
= dirent_ensure_type(fd
, *d
);
1227 if (r
!= -ENOENT
) /* don't log if just gone by now */
1228 log_debug_errno(r
, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1232 if ((*d
)->d_type
!= DT_DIR
)
1235 _cleanup_free_
char *fn
= path_join((*d
)->d_name
, "vmlinuz");
1239 if (faccessat(fd
, fn
, F_OK
, AT_SYMLINK_NOFOLLOW
) < 0) {
1240 if (errno
!= ENOENT
)
1241 log_debug_errno(errno
, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1243 log_notice("Not adding version '%s', because kernel image not found.", (*d
)->d_name
);
1247 _cleanup_(context_done
) Context copy
= CONTEXT_NULL
;
1249 r
= context_copy(c
, ©
);
1251 return log_error_errno(r
, "Failed to copy execution context: %m");
1253 /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1254 * with arg_root here. */
1255 _cleanup_free_
char *full
= path_join("/usr/lib/modules/", fn
);
1260 /* version= */ (*d
)->d_name
,
1262 /* initrds= */ NULL
);
1270 log_debug("Installed %zu kernel(s).", n
);
1272 ret
= log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "No kernels to install found.");
1277 static int run_as_installkernel(int argc
, char *argv
[], Context
*c
) {
1278 /* kernel's install.sh invokes us as
1279 * /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1280 * We ignore the last two arguments. */
1281 if (optind
+ 2 > argc
)
1282 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "'installkernel' command requires at least two arguments.");
1284 return verb_add(3, STRV_MAKE("add", argv
[optind
], argv
[optind
+1]), c
);
1287 static int verb_remove(int argc
, char *argv
[], void *userdata
) {
1288 Context
*c
= ASSERT_PTR(userdata
);
1295 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'remove' does not support --root= or --image=.");
1298 log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
1299 "Ignoring residual arguments.");
1304 c
->action
= ACTION_REMOVE
;
1306 /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do
1307 * it for the "add" verb), since we don't want to make it too easy to uninstall your running
1308 * kernel, as a safety precaution */
1310 r
= context_set_version(c
, argv
[1]);
1314 r
= context_prepare_execution(c
);
1318 return context_execute(c
);
1321 static int verb_inspect(int argc
, char *argv
[], void *userdata
) {
1322 Context
*c
= ASSERT_PTR(userdata
);
1323 _cleanup_(table_unrefp
) Table
*t
= NULL
;
1324 _cleanup_free_
char *vmlinuz
= NULL
;
1325 const char *version
, *kernel
;
1330 c
->action
= ACTION_INSPECT
;
1332 /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1333 * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1334 * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1335 * kernel image path derived automatically), then they may specify an empty string or "dash" as
1336 * kernel image path. */
1337 version
= argc
> 2 ? empty_or_dash_to_null(argv
[1]) : NULL
;
1338 kernel
= argc
> 2 ? empty_or_dash_to_null(argv
[2]) :
1339 (argc
> 1 ? empty_or_dash_to_null(argv
[1]) : NULL
);
1340 initrds
= strv_skip(argv
, 3);
1342 if (!version
&& !arg_root
) {
1343 assert_se(uname(&un
) >= 0);
1344 version
= un
.release
;
1347 if (!kernel
&& version
) {
1348 r
= kernel_from_version(version
, &vmlinuz
);
1355 r
= context_set_version(c
, version
);
1359 r
= context_set_kernel(c
, kernel
);
1363 r
= context_set_initrds(c
, initrds
);
1367 r
= context_prepare_execution(c
);
1371 t
= table_new_vertical();
1375 r
= table_add_many(t
,
1376 TABLE_FIELD
, "Machine ID",
1377 TABLE_ID128
, c
->machine_id
,
1378 TABLE_FIELD
, "Kernel Image Type",
1379 TABLE_STRING
, kernel_image_type_to_string(c
->kernel_image_type
),
1380 TABLE_FIELD
, "Layout",
1381 TABLE_STRING
, context_get_layout(c
),
1382 TABLE_FIELD
, "Boot Root",
1383 TABLE_STRING
, c
->boot_root
,
1384 TABLE_FIELD
, "Entry Token Type",
1385 TABLE_STRING
, boot_entry_token_type_to_string(c
->entry_token_type
),
1386 TABLE_FIELD
, "Entry Token",
1387 TABLE_STRING
, c
->entry_token
,
1388 TABLE_FIELD
, "Entry Directory",
1389 TABLE_STRING
, c
->entry_dir
,
1390 TABLE_FIELD
, "Kernel Version",
1391 TABLE_STRING
, c
->version
,
1392 TABLE_FIELD
, "Kernel",
1393 TABLE_STRING
, c
->kernel
,
1394 TABLE_FIELD
, "Initrds",
1395 TABLE_STRV
, c
->initrds
,
1396 TABLE_FIELD
, "Initrd Generator",
1397 TABLE_STRING
, c
->initrd_generator
,
1398 TABLE_FIELD
, "UKI Generator",
1399 TABLE_STRING
, c
->uki_generator
,
1400 TABLE_FIELD
, "Plugins",
1401 TABLE_STRV
, c
->plugins
,
1402 TABLE_FIELD
, "Plugin Environment",
1403 TABLE_STRV
, c
->envp
);
1405 return table_log_add_error(r
);
1407 if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
1408 r
= table_add_many(t
,
1409 TABLE_FIELD
, "Plugin Arguments",
1410 TABLE_STRV
, strv_skip(c
->argv
, 1));
1412 return table_log_add_error(r
);
1415 table_set_ersatz_string(t
, TABLE_ERSATZ_UNSET
);
1417 for (size_t row
= 1; row
< table_get_rows(t
); row
++) {
1418 _cleanup_free_
char *name
= NULL
;
1420 name
= strdup(table_get_at(t
, row
, 0));
1424 r
= table_set_json_field_name(t
, row
- 1, delete_chars(name
, " "));
1426 return log_error_errno(r
, "Failed to set JSON field name: %m");
1429 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, /* show_header= */ false);
1432 static int verb_list(int argc
, char *argv
[], void *userdata
) {
1433 Context
*c
= ASSERT_PTR(userdata
);
1434 _cleanup_close_
int fd
= -EBADF
;
1437 fd
= chase_and_openat(c
->rfd
, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
, NULL
);
1439 return log_error_errno(fd
, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root
));
1441 _cleanup_free_ DirectoryEntries
*de
= NULL
;
1442 r
= readdir_all(fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
1444 return log_error_errno(r
, "Failed to numerate /usr/lib/modules/ contents: %m");
1446 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1447 table
= table_new("version", "has kernel", "path");
1451 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1452 table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
1454 FOREACH_ARRAY(d
, de
->entries
, de
->n_entries
) {
1455 _cleanup_free_
char *j
= path_join("/usr/lib/modules/", (*d
)->d_name
);
1459 r
= dirent_ensure_type(fd
, *d
);
1461 if (r
!= -ENOENT
) /* don't log if just gone by now */
1462 log_debug_errno(r
, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root
), j
);
1466 if ((*d
)->d_type
!= DT_DIR
)
1469 _cleanup_free_
char *fn
= path_join((*d
)->d_name
, "vmlinuz");
1474 if (faccessat(fd
, fn
, F_OK
, AT_SYMLINK_NOFOLLOW
) < 0) {
1475 if (errno
!= ENOENT
)
1476 log_debug_errno(errno
, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1482 r
= table_add_many(table
,
1483 TABLE_STRING
, (*d
)->d_name
,
1484 TABLE_BOOLEAN_CHECKMARK
, exists
,
1485 TABLE_SET_COLOR
, ansi_highlight_green_red(exists
),
1488 return table_log_add_error(r
);
1491 return table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
1494 static int help(void) {
1495 _cleanup_free_
char *link
= NULL
;
1498 r
= terminal_urlify_man("kernel-install", "8", &link
);
1502 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
1503 "%5$sAdd and remove kernel and initrd images to and from /boot/%6$s\n"
1504 "\n%3$sUsage:%4$s\n"
1505 " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n"
1506 " kernel-install [OPTIONS...] add-all\n"
1507 " kernel-install [OPTIONS...] remove KERNEL-VERSION\n"
1508 " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n"
1510 " kernel-install [OPTIONS...] list\n"
1511 "\n%3$sOptions:%4$s\n"
1512 " -h --help Show this help\n"
1513 " --version Show package version\n"
1514 " -v --verbose Increase verbosity\n"
1515 " --esp-path=PATH Path to the EFI System Partition (ESP)\n"
1516 " --boot-path=PATH Path to the $BOOT partition\n"
1517 " --make-entry-directory=yes|no|auto\n"
1518 " Create $BOOT/ENTRY-TOKEN/ directory\n"
1519 " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
1520 " Entry token to use for this installation\n"
1521 " --no-pager Do not pipe inspect output into a pager\n"
1522 " --json=pretty|short|off Generate JSON output\n"
1523 " --no-legend Do not show the headers and footers\n"
1524 " --root=PATH Operate on an alternate filesystem root\n"
1525 " --image=PATH Operate on disk image as filesystem root\n"
1526 " --image-policy=POLICY Specify disk image dissection policy\n"
1528 "This program may also be invoked as 'installkernel':\n"
1529 " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
1530 "(The optional arguments are passed by kernel build system, but ignored.)\n"
1532 "See the %2$s for details.\n",
1533 program_invocation_short_name
,
1543 static int parse_argv(int argc
, char *argv
[], Context
*c
) {
1545 ARG_VERSION
= 0x100,
1549 ARG_MAKE_ENTRY_DIRECTORY
,
1557 static const struct option options
[] = {
1558 { "help", no_argument
, NULL
, 'h' },
1559 { "version", no_argument
, NULL
, ARG_VERSION
},
1560 { "verbose", no_argument
, NULL
, 'v' },
1561 { "esp-path", required_argument
, NULL
, ARG_ESP_PATH
},
1562 { "boot-path", required_argument
, NULL
, ARG_BOOT_PATH
},
1563 { "make-entry-directory", required_argument
, NULL
, ARG_MAKE_ENTRY_DIRECTORY
},
1564 { "entry-token", required_argument
, NULL
, ARG_ENTRY_TOKEN
},
1565 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1566 { "json", required_argument
, NULL
, ARG_JSON
},
1567 { "root", required_argument
, NULL
, ARG_ROOT
},
1568 { "image", required_argument
, NULL
, ARG_IMAGE
},
1569 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
1570 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1579 while ((t
= getopt_long(argc
, argv
, "hv", options
, NULL
)) >= 0)
1592 log_set_max_level(LOG_DEBUG
);
1597 r
= parse_path_argument(optarg
, /* suppress_root = */ false, &arg_esp_path
);
1603 r
= parse_path_argument(optarg
, /* suppress_root = */ false, &arg_xbootldr_path
);
1608 case ARG_MAKE_ENTRY_DIRECTORY
:
1609 if (streq(optarg
, "auto"))
1610 arg_make_entry_directory
= -1;
1612 r
= parse_boolean_argument("--make-entry-directory=", optarg
, NULL
);
1616 arg_make_entry_directory
= r
;
1620 case ARG_ENTRY_TOKEN
:
1621 r
= parse_boot_entry_token_type(optarg
, &c
->entry_token_type
, &c
->entry_token
);
1627 arg_pager_flags
|= PAGER_DISABLE
;
1631 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1637 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_root
);
1643 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
1648 case ARG_IMAGE_POLICY
:
1649 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
1658 assert_not_reached();
1661 if (arg_image
&& arg_root
)
1662 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
1667 static int run(int argc
, char* argv
[]) {
1668 static const Verb verbs
[] = {
1669 { "add", 1, VERB_ANY
, 0, verb_add
},
1670 { "add-all", 1, 1, 0, verb_add_all
},
1671 { "remove", 2, VERB_ANY
, 0, verb_remove
},
1672 { "inspect", 1, VERB_ANY
, VERB_DEFAULT
, verb_inspect
},
1673 { "list", 1, 1, 0, verb_list
},
1676 _cleanup_(context_done
) Context c
= {
1678 .action
= _ACTION_INVALID
,
1679 .kernel_image_type
= KERNEL_IMAGE_TYPE_UNKNOWN
,
1680 .layout
= _LAYOUT_INVALID
,
1681 .entry_token_type
= BOOT_ENTRY_TOKEN_AUTO
,
1683 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
1684 _cleanup_(umount_and_freep
) char *mounted_dir
= NULL
;
1689 r
= parse_argv(argc
, argv
, &c
);
1696 r
= mount_image_privately_interactively(
1699 DISSECT_IMAGE_GENERIC_ROOT
|
1700 DISSECT_IMAGE_REQUIRE_ROOT
|
1701 DISSECT_IMAGE_RELAX_VAR_CHECK
|
1702 DISSECT_IMAGE_VALIDATE_OS
|
1703 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY
,
1705 /* ret_dir_fd= */ NULL
,
1710 arg_root
= strdup(mounted_dir
);
1715 r
= context_init(&c
);
1719 if (invoked_as(argv
, "installkernel"))
1720 return run_as_installkernel(argc
, argv
, &c
);
1722 return dispatch_verb(argc
, argv
, verbs
, &c
);
1725 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);