1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include <sys/utsname.h>
9 #include "boot-entry.h"
12 #include "conf-files.h"
13 #include "dirent-util.h"
14 #include "dissect-image.h"
17 #include "exec-util.h"
18 #include "extract-word.h"
22 #include "format-table.h"
24 #include "id128-util.h"
25 #include "image-policy.h"
26 #include "kernel-config.h"
27 #include "kernel-image.h"
28 #include "loop-util.h"
29 #include "main-func.h"
30 #include "mount-util.h"
31 #include "parse-argument.h"
32 #include "path-util.h"
33 #include "pretty-print.h"
34 #include "recurse-dir.h"
36 #include "stat-util.h"
37 #include "string-table.h"
38 #include "string-util.h"
40 #include "tmpfile-util.h"
43 static bool arg_verbose
= false;
44 static char *arg_esp_path
= NULL
;
45 static char *arg_xbootldr_path
= NULL
;
46 static int arg_make_entry_directory
= -1; /* tristate */
47 static PagerFlags arg_pager_flags
= 0;
48 static sd_json_format_flags_t arg_json_format_flags
= SD_JSON_FORMAT_OFF
;
49 static char *arg_root
= NULL
;
50 static char *arg_image
= NULL
;
51 static ImagePolicy
*arg_image_policy
= NULL
;
52 static bool arg_legend
= true;
54 STATIC_DESTRUCTOR_REGISTER(arg_esp_path
, freep
);
55 STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path
, freep
);
56 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
57 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
58 STATIC_DESTRUCTOR_REGISTER(arg_image_policy
, image_policy_freep
);
65 _ACTION_INVALID
= -EINVAL
,
74 _LAYOUT_INVALID
= -EINVAL
,
77 static const char * const layout_table
[_LAYOUT_MAX
] = {
78 [LAYOUT_AUTO
] = "auto",
81 [LAYOUT_OTHER
] = "other",
84 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(layout
, Layout
);
86 typedef struct Context
{
89 sd_id128_t machine_id
;
90 bool machine_id_is_random
;
91 KernelImageType kernel_image_type
;
96 BootEntryTokenType entry_token_type
;
102 char *initrd_generator
;
110 #define CONTEXT_NULL (Context) { .rfd = -EBADF }
112 static void context_done(Context
*c
) {
115 free(c
->layout_other
);
118 free(c
->entry_token
);
122 strv_free(c
->initrds
);
123 free(c
->initrd_generator
);
124 free(c
->uki_generator
);
125 if (c
->action
== ACTION_INSPECT
)
126 free(c
->staging_area
);
128 rm_rf_physical_and_free(c
->staging_area
);
129 strv_free(c
->plugins
);
136 static int context_copy(const Context
*source
, Context
*ret
) {
141 assert(source
->rfd
>= 0 || source
->rfd
== AT_FDCWD
);
143 _cleanup_(context_done
) Context copy
= (Context
) {
145 .action
= source
->action
,
146 .machine_id
= source
->machine_id
,
147 .machine_id_is_random
= source
->machine_id_is_random
,
148 .kernel_image_type
= source
->kernel_image_type
,
149 .layout
= source
->layout
,
150 .entry_token_type
= source
->entry_token_type
,
153 if (source
->rfd
>= 0) {
154 copy
.rfd
= fd_reopen(source
->rfd
, O_CLOEXEC
|O_DIRECTORY
|O_PATH
);
159 r
= strdup_to(©
.layout_other
, source
->layout_other
);
162 r
= strdup_to(©
.conf_root
, source
->conf_root
);
165 r
= strdup_to(©
.boot_root
, source
->boot_root
);
168 r
= strdup_to(©
.entry_token
, source
->entry_token
);
171 r
= strdup_to(©
.entry_dir
, source
->entry_dir
);
174 r
= strdup_to(©
.version
, source
->version
);
177 r
= strdup_to(©
.kernel
, source
->kernel
);
180 r
= strv_copy_unless_empty(source
->initrds
, ©
.initrds
);
183 r
= strdup_to(©
.initrd_generator
, source
->initrd_generator
);
186 r
= strdup_to(©
.uki_generator
, source
->uki_generator
);
189 r
= strdup_to(©
.staging_area
, source
->staging_area
);
192 r
= strv_copy_unless_empty(source
->plugins
, ©
.plugins
);
195 r
= strv_copy_unless_empty(source
->argv
, ©
.argv
);
198 r
= strv_copy_unless_empty(source
->envp
, ©
.envp
);
208 static int context_open_root(Context
*c
) {
214 if (isempty(arg_root
))
217 r
= path_is_root(arg_root
);
219 return log_error_errno(r
, "Failed to determine if '%s' is the root directory: %m", arg_root
);
223 c
->rfd
= open(empty_to_root(arg_root
), O_CLOEXEC
| O_DIRECTORY
| O_PATH
);
225 return log_error_errno(errno
, "Failed to open root directory '%s': %m", empty_to_root(arg_root
));
230 static const char* context_get_layout(const Context
*c
) {
232 assert(c
->layout
>= 0);
234 return c
->layout_other
?: layout_to_string(c
->layout
);
237 static int context_set_layout(Context
*c
, const char *s
, const char *source
) {
243 if (c
->layout
>= 0 || !s
)
246 assert(!c
->layout_other
);
248 t
= layout_from_string(s
);
252 c
->layout
= LAYOUT_AUTO
;
254 c
->layout_other
= strdup(s
);
255 if (!c
->layout_other
)
258 c
->layout
= LAYOUT_OTHER
;
261 log_debug("layout=%s set via %s", context_get_layout(c
), source
);
265 static int context_set_machine_id(Context
*c
, const char *s
, const char *source
) {
271 if (!sd_id128_is_null(c
->machine_id
) || !s
)
274 r
= sd_id128_from_string(s
, &c
->machine_id
);
276 return log_warning_errno(r
, "Failed to parse machine ID specified via %s, ignoring.", source
);
278 if (sd_id128_is_null(c
->machine_id
))
281 log_debug("MACHINE_ID=%s set via %s.", SD_ID128_TO_STRING(c
->machine_id
), source
);
285 static int context_set_string(const char *s
, const char *source
, const char *name
, char **dest
) {
299 log_debug("%s (%s) set via %s.", name
, p
, source
);
305 static int context_set_initrd_generator(Context
*c
, const char *s
, const char *source
) {
307 return context_set_string(s
, source
, "INITRD_GENERATOR", &c
->initrd_generator
);
310 static int context_set_uki_generator(Context
*c
, const char *s
, const char *source
) {
312 return context_set_string(s
, source
, "UKI_GENERATOR", &c
->uki_generator
);
315 static int context_set_version(Context
*c
, const char *s
) {
318 if (s
&& !filename_is_valid(s
))
319 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid version specified: %s", s
);
321 return context_set_string(s
, "command line", "kernel version", &c
->version
);
324 static int context_set_path(Context
*c
, const char *s
, const char *source
, const char *name
, char **dest
) {
337 r
= chaseat(c
->rfd
, s
, CHASE_AT_RESOLVE_IN_ROOT
, &p
, /* ret_fd = */ NULL
);
339 return log_warning_errno(r
, "Failed to chase path %s for %s specified via %s, ignoring: %m",
342 r
= path_make_absolute_cwd(s
, &p
);
344 return log_warning_errno(r
, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
348 log_debug("%s (%s) set via %s.", name
, p
, source
);
354 static int context_set_boot_root(Context
*c
, const char *s
, const char *source
) {
356 return context_set_path(c
, s
, source
, "BOOT_ROOT", &c
->boot_root
);
359 static int context_set_conf_root(Context
*c
, const char *s
, const char *source
) {
361 return context_set_path(c
, s
, source
, "CONF_ROOT", &c
->conf_root
);
364 static int context_set_kernel(Context
*c
, const char *s
) {
366 return context_set_path(c
, s
, "command line", "kernel image file", &c
->kernel
);
369 static int context_set_path_strv(Context
*c
, char* const* strv
, const char *source
, const char *name
, char ***dest
) {
370 _cleanup_strv_free_
char **w
= NULL
;
381 STRV_FOREACH(s
, strv
) {
385 r
= chaseat(c
->rfd
, *s
, CHASE_AT_RESOLVE_IN_ROOT
, &p
, /* ret_fd = */ NULL
);
387 return log_warning_errno(r
, "Failed to chase path %s for %s specified via %s: %m",
390 r
= path_make_absolute_cwd(*s
, &p
);
392 return log_warning_errno(r
, "Failed to make path '%s' for %s specified via %s absolute, ignoring: %m",
395 r
= strv_consume(&w
, p
);
403 log_debug("%s set via %s", name
, source
);
409 static int context_set_plugins(Context
*c
, const char *s
, const char *source
) {
410 _cleanup_strv_free_
char **v
= NULL
;
415 if (c
->plugins
|| !s
)
418 r
= strv_split_full(&v
, s
, NULL
, EXTRACT_UNQUOTE
);
420 return log_error_errno(r
, "Failed to parse plugin paths from %s: %m", source
);
422 return context_set_path_strv(c
, v
, source
, "plugins", &c
->plugins
);
425 static int context_set_initrds(Context
*c
, char* const* strv
) {
427 return context_set_path_strv(c
, strv
, "command line", "initrds", &c
->initrds
);
430 static int context_load_environment(Context
*c
) {
433 (void) context_set_machine_id(c
, getenv("MACHINE_ID"), "environment");
434 (void) context_set_boot_root(c
, getenv("BOOT_ROOT"), "environment");
435 (void) context_set_conf_root(c
, getenv("KERNEL_INSTALL_CONF_ROOT"), "environment");
436 (void) context_set_plugins(c
, getenv("KERNEL_INSTALL_PLUGINS"), "environment");
440 static int context_load_install_conf(Context
*c
) {
441 _cleanup_free_
char *machine_id
= NULL
, *boot_root
= NULL
, *layout
= NULL
,
442 *initrd_generator
= NULL
, *uki_generator
= NULL
;
447 r
= load_kernel_install_conf(arg_root
,
457 (void) context_set_machine_id(c
, machine_id
, "config");
458 (void) context_set_boot_root(c
, boot_root
, "config");
459 (void) context_set_layout(c
, layout
, "config");
460 (void) context_set_initrd_generator(c
, initrd_generator
, "config");
461 (void) context_set_uki_generator(c
, uki_generator
, "config");
463 log_debug("Loaded config.");
467 static int context_load_machine_info(Context
*c
) {
468 _cleanup_fclose_
FILE *f
= NULL
;
469 _cleanup_free_
char *machine_id
= NULL
, *layout
= NULL
;
470 static const char *path
= "/etc/machine-info";
475 /* If the user configured an explicit machine ID in /etc/machine-info to use for our purpose, we'll
476 * use that instead (for compatibility). */
478 if (!sd_id128_is_null(c
->machine_id
) && c
->layout
>= 0)
481 /* For testing. To make not read host's /etc/machine-info. */
482 r
= getenv_bool("KERNEL_INSTALL_READ_MACHINE_INFO");
483 if (r
< 0 && r
!= -ENXIO
)
484 log_warning_errno(r
, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m");
486 log_debug("Skipping reading of /etc/machine-info.");
490 r
= chase_and_fopenat_unlocked(c
->rfd
, path
, CHASE_AT_RESOLVE_IN_ROOT
, "re", NULL
, &f
);
494 return log_error_errno(r
, "Failed to chase %s: %m", path
);
496 log_debug("Loading %s…", path
);
498 r
= parse_env_file(f
, path
,
499 "KERNEL_INSTALL_MACHINE_ID", &machine_id
,
500 "KERNEL_INSTALL_LAYOUT", &layout
);
502 return log_error_errno(r
, "Failed to parse '%s': %m", path
);
504 (void) context_set_machine_id(c
, machine_id
, path
);
505 (void) context_set_layout(c
, layout
, path
);
509 static int context_load_machine_id(Context
*c
) {
514 r
= id128_get_machine_at(c
->rfd
, &c
->machine_id
);
515 if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r
))
518 return log_error_errno(r
, "Failed to load machine ID from /etc/machine-id: %m");
520 log_debug("MACHINE_ID=%s set via /etc/machine-id.", SD_ID128_TO_STRING(c
->machine_id
));
521 return 1; /* loaded */
524 static int context_ensure_machine_id(Context
*c
) {
529 if (!sd_id128_is_null(c
->machine_id
))
532 /* If /etc/machine-id is initialized we'll use it. */
533 r
= context_load_machine_id(c
);
537 /* Otherwise we'll use a freshly generated one. */
538 r
= sd_id128_randomize(&c
->machine_id
);
540 return log_error_errno(r
, "Failed to generate random ID: %m");
542 c
->machine_id_is_random
= true;
543 log_debug("New machine ID '%s' generated.", SD_ID128_TO_STRING(c
->machine_id
));
547 static int context_acquire_xbootldr(Context
*c
) {
551 assert(!c
->boot_root
);
553 r
= find_xbootldr_and_warn_at(
555 /* path = */ arg_xbootldr_path
,
556 /* unprivileged_mode= */ -1,
557 /* ret_path = */ &c
->boot_root
,
558 /* ret_uuid = */ NULL
,
559 /* ret_devid = */ NULL
);
561 log_debug_errno(r
, "Couldn't find an XBOOTLDR partition.");
564 if (r
== -EACCES
&& geteuid() != 0)
565 return log_error_errno(r
, "Failed to determine XBOOTLDR partition: %m");
569 log_debug("Using XBOOTLDR partition at %s as $BOOT_ROOT.", c
->boot_root
);
570 return 1; /* found */
573 static int context_acquire_esp(Context
*c
) {
577 assert(!c
->boot_root
);
579 r
= find_esp_and_warn_at(
581 /* path = */ arg_esp_path
,
582 /* unprivileged_mode= */ -1,
583 /* ret_path = */ &c
->boot_root
,
584 /* ret_part = */ NULL
,
585 /* ret_pstart = */ NULL
,
586 /* ret_psize = */ NULL
,
587 /* ret_uuid = */ NULL
,
588 /* ret_devid = */ NULL
);
590 log_debug_errno(r
, "Couldn't find EFI system partition, ignoring.");
593 if (r
== -EACCES
&& geteuid() != 0)
594 return log_error_errno(r
, "Failed to determine EFI system partition: %m");
598 log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c
->boot_root
);
599 return 1; /* found */
602 static int context_ensure_boot_root(Context
*c
) {
607 /* If BOOT_ROOT is specified via environment or install.conf, then use it. */
611 /* Otherwise, use XBOOTLDR partition, if mounted. */
612 r
= context_acquire_xbootldr(c
);
616 /* Otherwise, use EFI system partition, if mounted. */
617 r
= context_acquire_esp(c
);
621 /* If all else fails, use /boot. */
623 r
= chaseat(c
->rfd
, "/boot", CHASE_AT_RESOLVE_IN_ROOT
, &c
->boot_root
, /* ret_fd = */ NULL
);
625 return log_error_errno(r
, "Failed to chase '/boot/': %m");
627 c
->boot_root
= strdup("/boot");
632 log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c
->boot_root
);
636 static int context_ensure_entry_token(Context
*c
) {
641 /* Now that we determined the machine ID to use, let's determine the "token" for the boot loader
642 * entry to generate. We use that for naming the directory below $BOOT where we want to place the
643 * kernel/initrd and related resources, as well for naming the .conf boot loader spec entry.
644 * Typically this is just the machine ID, but it can be anything else, too, if we are told so. */
646 r
= boot_entry_token_ensure_at(
650 c
->machine_id_is_random
,
651 &c
->entry_token_type
,
656 log_debug("Using entry token: %s", c
->entry_token
);
660 static int context_load_plugins(Context
*c
) {
668 r
= conf_files_list_strv_at(
672 CONF_FILES_EXECUTABLE
| CONF_FILES_REGULAR
| CONF_FILES_FILTER_MASKED
,
673 STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d"));
675 return log_error_errno(r
, "Failed to find plugins: %m");
680 static int context_init(Context
*c
) {
685 r
= context_open_root(c
);
689 r
= context_load_environment(c
);
693 r
= context_load_install_conf(c
);
697 r
= context_load_machine_info(c
);
701 r
= context_ensure_machine_id(c
);
705 r
= context_ensure_boot_root(c
);
709 r
= context_ensure_entry_token(c
);
713 r
= context_load_plugins(c
);
720 static int context_inspect_kernel(Context
*c
) {
726 return inspect_kernel(c
->rfd
, c
->kernel
, &c
->kernel_image_type
, NULL
, NULL
, NULL
);
729 static int context_ensure_layout(Context
*c
) {
733 assert(c
->boot_root
);
734 assert(c
->entry_token
);
736 if (c
->layout
>= 0 && c
->layout
!= LAYOUT_AUTO
)
739 /* No layout configured by the administrator. Let's try to figure it out automatically from metadata
740 * already contained in $BOOT_ROOT. */
742 if (c
->kernel_image_type
== KERNEL_IMAGE_TYPE_UKI
) {
743 c
->layout
= LAYOUT_UKI
;
744 log_debug("Kernel image type is %s, using layout=%s.",
745 kernel_image_type_to_string(c
->kernel_image_type
), layout_to_string(c
->layout
));
749 _cleanup_free_
char *srel_path
= path_join(c
->boot_root
, "loader/entries.srel");
753 _cleanup_free_
char *srel
= NULL
;
754 r
= read_one_line_file_at(c
->rfd
, srel_path
, &srel
);
756 if (streq(srel
, "type1"))
757 /* The loader/entries.srel file clearly indicates that the installed boot loader
758 * implements the proper standard upstream boot loader spec for Type #1 entries.
759 * Let's default to that, then. */
760 c
->layout
= LAYOUT_BLS
;
762 /* The loader/entries.srel file indicates some other spec is implemented and owns the
763 * /loader/entries/ directory. Since we have no idea what that means, let's stay away
764 * from it by default. */
765 c
->layout
= LAYOUT_OTHER
;
767 log_debug("%s with '%s' found, using layout=%s.", srel_path
, srel
, layout_to_string(c
->layout
));
769 } else if (r
!= -ENOENT
)
770 return log_error_errno(r
, "Failed to read %s: %m", srel_path
);
772 _cleanup_free_
char *entry_token_path
= path_join(c
->boot_root
, c
->entry_token
);
773 if (!entry_token_path
)
776 r
= is_dir_at(c
->rfd
, entry_token_path
, /* follow = */ false);
777 if (r
< 0 && r
!= -ENOENT
)
778 return log_error_errno(r
, "Failed to check if '%s' is a directory: %m", entry_token_path
);
780 /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token
781 * directory already exists. If so, let's assume it's the standard boot loader spec, too. */
782 c
->layout
= LAYOUT_BLS
;
783 log_debug("%s exists, using layout=%s.", entry_token_path
, layout_to_string(c
->layout
));
787 /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we
788 * really don't know anything. */
789 c
->layout
= LAYOUT_OTHER
;
790 log_debug("Entry-token directory %s not found, using layout=%s.",
792 layout_to_string(c
->layout
));
796 static int context_set_up_staging_area(Context
*c
) {
797 static const char *template = "/tmp/kernel-install.staging.XXXXXX";
805 if (c
->action
== ACTION_INSPECT
) {
806 /* This is only used for display. The directory will not be created. */
807 c
->staging_area
= strdup(template);
808 if (!c
->staging_area
)
811 r
= mkdtemp_malloc(template, &c
->staging_area
);
813 return log_error_errno(r
, "Failed to create staging area: %m");
819 static int context_build_entry_dir(Context
*c
) {
821 assert(c
->boot_root
);
822 assert(c
->entry_token
);
823 assert(c
->version
|| c
->action
== ACTION_INSPECT
);
828 c
->entry_dir
= path_join(c
->boot_root
, c
->entry_token
, c
->version
?: "KERNEL_VERSION");
832 log_debug("Using ENTRY_DIR=%s", c
->entry_dir
);
836 static bool context_should_make_entry_dir(Context
*c
) {
839 /* Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN to signal to
840 * 00-entry-directory to create $ENTRY_DIR to serve as the indication to use or to not use the BLS */
842 if (arg_make_entry_directory
< 0)
843 return c
->layout
== LAYOUT_BLS
;
845 return arg_make_entry_directory
;
848 static int context_make_entry_dir(Context
*c
) {
849 _cleanup_close_
int fd
= -EBADF
;
852 assert(c
->entry_dir
);
854 if (c
->action
!= ACTION_ADD
)
857 if (!context_should_make_entry_dir(c
))
860 log_debug("mkdir -p %s", c
->entry_dir
);
861 fd
= chase_and_openat(c
->rfd
, c
->entry_dir
, CHASE_AT_RESOLVE_IN_ROOT
| CHASE_MKDIR_0755
,
862 O_CLOEXEC
| O_CREAT
| O_DIRECTORY
| O_PATH
, NULL
);
864 return log_error_errno(fd
, "Failed to make directory '%s': %m", c
->entry_dir
);
869 static int context_remove_entry_dir(Context
*c
) {
870 _cleanup_free_
char *p
= NULL
;
871 _cleanup_close_
int fd
= -EBADF
;
876 assert(c
->entry_dir
);
878 if (c
->action
!= ACTION_REMOVE
)
881 if (!context_should_make_entry_dir(c
))
884 log_debug("rm -rf %s", c
->entry_dir
);
885 fd
= chase_and_openat(c
->rfd
, c
->entry_dir
, CHASE_AT_RESOLVE_IN_ROOT
, O_CLOEXEC
| O_DIRECTORY
, &p
);
887 if (IN_SET(fd
, -ENOTDIR
, -ENOENT
))
889 return log_debug_errno(fd
, "Failed to chase and open %s, ignoring: %m", c
->entry_dir
);
892 if (fstat(fd
, &st
) < 0)
893 return log_debug_errno(errno
, "Failed to stat %s: %m", p
);
895 r
= rm_rf_children(TAKE_FD(fd
), REMOVE_PHYSICAL
|REMOVE_MISSING_OK
|REMOVE_CHMOD
, &st
);
897 log_debug_errno(r
, "Failed to remove children of %s, ignoring: %m", p
);
899 if (unlinkat(c
->rfd
, p
, AT_REMOVEDIR
) < 0)
900 log_debug_errno(errno
, "Failed to remove %s, ignoring: %m", p
);
905 static int context_build_arguments(Context
*c
) {
906 _cleanup_strv_free_
char **a
= NULL
;
911 assert(c
->entry_dir
);
935 assert_not_reached();
938 a
= strv_new("dummy-arg", /* to make strv_free() works for this variable. */
940 c
->version
?: "KERNEL_VERSION",
945 if (c
->action
== ACTION_ADD
) {
946 r
= strv_extend(&a
, c
->kernel
);
950 r
= strv_extend_strv(&a
, c
->initrds
, /* filter_duplicates = */ false);
954 } else if (c
->action
== ACTION_INSPECT
) {
955 r
= strv_extend_many(
957 c
->kernel
?: "[KERNEL_IMAGE]",
963 c
->argv
= TAKE_PTR(a
);
967 static int context_build_environment(Context
*c
) {
968 _cleanup_strv_free_
char **e
= NULL
;
976 r
= strv_env_assign_many(&e
,
977 "LC_COLLATE", SYSTEMD_DEFAULT_LOCALE
,
978 "KERNEL_INSTALL_VERBOSE", one_zero(arg_verbose
),
979 "KERNEL_INSTALL_IMAGE_TYPE", kernel_image_type_to_string(c
->kernel_image_type
),
980 "KERNEL_INSTALL_MACHINE_ID", SD_ID128_TO_STRING(c
->machine_id
),
981 "KERNEL_INSTALL_ENTRY_TOKEN", c
->entry_token
,
982 "KERNEL_INSTALL_BOOT_ROOT", c
->boot_root
,
983 "KERNEL_INSTALL_LAYOUT", context_get_layout(c
),
984 "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c
->initrd_generator
),
985 "KERNEL_INSTALL_UKI_GENERATOR", strempty(c
->uki_generator
),
986 "KERNEL_INSTALL_STAGING_AREA", c
->staging_area
);
988 return log_error_errno(r
, "Failed to build environment variables for plugins: %m");
990 c
->envp
= TAKE_PTR(e
);
994 static int context_prepare_execution(Context
*c
) {
999 r
= context_inspect_kernel(c
);
1003 r
= context_ensure_layout(c
);
1007 r
= context_set_up_staging_area(c
);
1011 r
= context_build_entry_dir(c
);
1015 r
= context_build_arguments(c
);
1019 r
= context_build_environment(c
);
1026 static int context_execute(Context
*c
) {
1031 r
= context_make_entry_dir(c
);
1035 if (DEBUG_LOGGING
) {
1036 _cleanup_free_
char *x
= strv_join_full(c
->plugins
, "", "\n ", /* escape_separator = */ false);
1037 log_debug("Using plugins: %s", strna(x
));
1039 _cleanup_free_
char *y
= strv_join_full(c
->envp
, "", "\n ", /* escape_separator = */ false);
1040 log_debug("Plugin environment: %s", strna(y
));
1042 _cleanup_free_
char *z
= strv_join(strv_skip(c
->argv
, 1), " ");
1043 log_debug("Plugin arguments: %s", strna(z
));
1051 /* callbacks = */ NULL
,
1052 /* callback_args = */ NULL
,
1055 EXEC_DIR_SKIP_REMAINING
);
1057 r
= context_remove_entry_dir(c
);
1061 /* This returns 0 on success, positive exit code on plugin failure, negative errno on other failures. */
1065 static bool bypass(void) {
1066 return should_bypass("KERNEL_INSTALL");
1071 const char *version
,
1081 r
= context_set_version(c
, version
);
1085 r
= context_set_kernel(c
, kernel
);
1089 r
= context_set_initrds(c
, initrds
);
1093 r
= context_prepare_execution(c
);
1097 return context_execute(c
);
1100 static int kernel_from_version(const char *version
, char **ret_kernel
) {
1101 _cleanup_free_
char *vmlinuz
= NULL
;
1106 vmlinuz
= path_join("/usr/lib/modules/", version
, "/vmlinuz");
1110 r
= access_nofollow(vmlinuz
, F_OK
);
1112 return log_error_errno(r
, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz
);
1114 return log_error_errno(r
, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz
);
1116 *ret_kernel
= TAKE_PTR(vmlinuz
);
1120 static int verb_add(int argc
, char *argv
[], void *userdata
) {
1121 Context
*c
= ASSERT_PTR(userdata
);
1122 _cleanup_free_
char *vmlinuz
= NULL
;
1123 const char *version
, *kernel
;
1131 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'add' does not support --root= or --image=.");
1136 c
->action
= ACTION_ADD
;
1138 /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
1139 * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
1140 * optional, not the 2nd. */
1141 version
= argc
> 2 ? empty_or_dash_to_null(argv
[1]) : NULL
;
1142 kernel
= argc
> 2 ? empty_or_dash_to_null(argv
[2]) :
1143 (argc
> 1 ? empty_or_dash_to_null(argv
[1]) : NULL
);
1144 initrds
= strv_skip(argv
, 3);
1147 assert_se(uname(&un
) >= 0);
1148 version
= un
.release
;
1152 r
= kernel_from_version(version
, &vmlinuz
);
1159 return do_add(c
, version
, kernel
, initrds
);
1162 static int verb_add_all(int argc
, char *argv
[], void *userdata
) {
1163 Context
*c
= ASSERT_PTR(userdata
);
1164 _cleanup_close_
int fd
= -EBADF
;
1171 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'add-all' does not support --root= or --image=.");
1176 c
->action
= ACTION_ADD
;
1178 fd
= chase_and_openat(c
->rfd
, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
, NULL
);
1180 return log_error_errno(fd
, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root
));
1182 _cleanup_free_ DirectoryEntries
*de
= NULL
;
1183 r
= readdir_all(fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
1185 return log_error_errno(r
, "Failed to numerate /usr/lib/modules/ contents: %m");
1187 FOREACH_ARRAY(d
, de
->entries
, de
->n_entries
) {
1188 r
= dirent_ensure_type(fd
, *d
);
1190 if (r
!= -ENOENT
) /* don't log if just gone by now */
1191 log_debug_errno(r
, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1195 if ((*d
)->d_type
!= DT_DIR
)
1198 _cleanup_free_
char *fn
= path_join((*d
)->d_name
, "vmlinuz");
1202 if (faccessat(fd
, fn
, F_OK
, AT_SYMLINK_NOFOLLOW
) < 0) {
1203 if (errno
!= ENOENT
)
1204 log_debug_errno(errno
, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1206 log_notice("Not adding version '%s', because kernel image not found.", (*d
)->d_name
);
1210 _cleanup_(context_done
) Context copy
= CONTEXT_NULL
;
1212 r
= context_copy(c
, ©
);
1214 return log_error_errno(r
, "Failed to copy execution context: %m");
1216 /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1217 * with arg_root here. */
1218 _cleanup_free_
char *full
= path_join("/usr/lib/modules/", fn
);
1223 /* version= */ (*d
)->d_name
,
1225 /* initrds= */ NULL
);
1233 log_debug("Installed %zu kernel(s).", n
);
1235 ret
= log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "No kernels to install found.");
1240 static int run_as_installkernel(int argc
, char *argv
[], Context
*c
) {
1241 /* kernel's install.sh invokes us as
1242 * /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1243 * We ignore the last two arguments. */
1244 if (optind
+ 2 > argc
)
1245 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "'installkernel' command requires at least two arguments.");
1247 return verb_add(3, STRV_MAKE("add", argv
[optind
], argv
[optind
+1]), c
);
1250 static int verb_remove(int argc
, char *argv
[], void *userdata
) {
1251 Context
*c
= ASSERT_PTR(userdata
);
1258 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'remove' does not support --root= or --image=.");
1261 log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
1262 "Ignoring residual arguments.");
1267 c
->action
= ACTION_REMOVE
;
1269 /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do
1270 * it for the "add" verb), since we don't want to make it too easy to uninstall your running
1271 * kernel, as a safety precaution */
1273 r
= context_set_version(c
, argv
[1]);
1277 r
= context_prepare_execution(c
);
1281 return context_execute(c
);
1284 static int verb_inspect(int argc
, char *argv
[], void *userdata
) {
1285 Context
*c
= ASSERT_PTR(userdata
);
1286 _cleanup_(table_unrefp
) Table
*t
= NULL
;
1287 _cleanup_free_
char *vmlinuz
= NULL
;
1288 const char *version
, *kernel
;
1293 c
->action
= ACTION_INSPECT
;
1295 /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1296 * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1297 * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1298 * kernel image path derived automatically), then they may specify an empty string or "dash" as
1299 * kernel image path. */
1300 version
= argc
> 2 ? empty_or_dash_to_null(argv
[1]) : NULL
;
1301 kernel
= argc
> 2 ? empty_or_dash_to_null(argv
[2]) :
1302 (argc
> 1 ? empty_or_dash_to_null(argv
[1]) : NULL
);
1303 initrds
= strv_skip(argv
, 3);
1305 if (!version
&& !arg_root
) {
1306 assert_se(uname(&un
) >= 0);
1307 version
= un
.release
;
1310 if (!kernel
&& version
) {
1311 r
= kernel_from_version(version
, &vmlinuz
);
1318 r
= context_set_version(c
, version
);
1322 r
= context_set_kernel(c
, kernel
);
1326 r
= context_set_initrds(c
, initrds
);
1330 r
= context_prepare_execution(c
);
1334 t
= table_new_vertical();
1338 r
= table_add_many(t
,
1339 TABLE_FIELD
, "Machine ID",
1340 TABLE_ID128
, c
->machine_id
,
1341 TABLE_FIELD
, "Kernel Image Type",
1342 TABLE_STRING
, kernel_image_type_to_string(c
->kernel_image_type
),
1343 TABLE_FIELD
, "Layout",
1344 TABLE_STRING
, context_get_layout(c
),
1345 TABLE_FIELD
, "Boot Root",
1346 TABLE_STRING
, c
->boot_root
,
1347 TABLE_FIELD
, "Entry Token Type",
1348 TABLE_STRING
, boot_entry_token_type_to_string(c
->entry_token_type
),
1349 TABLE_FIELD
, "Entry Token",
1350 TABLE_STRING
, c
->entry_token
,
1351 TABLE_FIELD
, "Entry Directory",
1352 TABLE_STRING
, c
->entry_dir
,
1353 TABLE_FIELD
, "Kernel Version",
1354 TABLE_STRING
, c
->version
,
1355 TABLE_FIELD
, "Kernel",
1356 TABLE_STRING
, c
->kernel
,
1357 TABLE_FIELD
, "Initrds",
1358 TABLE_STRV
, c
->initrds
,
1359 TABLE_FIELD
, "Initrd Generator",
1360 TABLE_STRING
, c
->initrd_generator
,
1361 TABLE_FIELD
, "UKI Generator",
1362 TABLE_STRING
, c
->uki_generator
,
1363 TABLE_FIELD
, "Plugins",
1364 TABLE_STRV
, c
->plugins
,
1365 TABLE_FIELD
, "Plugin Environment",
1366 TABLE_STRV
, c
->envp
);
1368 return table_log_add_error(r
);
1370 if (!sd_json_format_enabled(arg_json_format_flags
)) {
1371 r
= table_add_many(t
,
1372 TABLE_FIELD
, "Plugin Arguments",
1373 TABLE_STRV
, strv_skip(c
->argv
, 1));
1375 return table_log_add_error(r
);
1378 table_set_ersatz_string(t
, TABLE_ERSATZ_UNSET
);
1380 for (size_t row
= 1; row
< table_get_rows(t
); row
++) {
1381 _cleanup_free_
char *name
= NULL
;
1383 name
= strdup(table_get_at(t
, row
, 0));
1387 r
= table_set_json_field_name(t
, row
- 1, delete_chars(name
, " "));
1389 return log_error_errno(r
, "Failed to set JSON field name: %m");
1392 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, /* show_header= */ false);
1395 static int verb_list(int argc
, char *argv
[], void *userdata
) {
1396 Context
*c
= ASSERT_PTR(userdata
);
1397 _cleanup_close_
int fd
= -EBADF
;
1400 fd
= chase_and_openat(c
->rfd
, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
, NULL
);
1402 return log_error_errno(fd
, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root
));
1404 _cleanup_free_ DirectoryEntries
*de
= NULL
;
1405 r
= readdir_all(fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
1407 return log_error_errno(r
, "Failed to numerate /usr/lib/modules/ contents: %m");
1409 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1410 table
= table_new("version", "has kernel", "path");
1414 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1415 table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
1417 FOREACH_ARRAY(d
, de
->entries
, de
->n_entries
) {
1418 _cleanup_free_
char *j
= path_join("/usr/lib/modules/", (*d
)->d_name
);
1422 r
= dirent_ensure_type(fd
, *d
);
1424 if (r
!= -ENOENT
) /* don't log if just gone by now */
1425 log_debug_errno(r
, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root
), j
);
1429 if ((*d
)->d_type
!= DT_DIR
)
1432 _cleanup_free_
char *fn
= path_join((*d
)->d_name
, "vmlinuz");
1437 if (faccessat(fd
, fn
, F_OK
, AT_SYMLINK_NOFOLLOW
) < 0) {
1438 if (errno
!= ENOENT
)
1439 log_debug_errno(errno
, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1445 r
= table_add_many(table
,
1446 TABLE_STRING
, (*d
)->d_name
,
1447 TABLE_BOOLEAN_CHECKMARK
, exists
,
1448 TABLE_SET_COLOR
, ansi_highlight_green_red(exists
),
1451 return table_log_add_error(r
);
1454 return table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
1457 static int help(void) {
1458 _cleanup_free_
char *link
= NULL
;
1461 r
= terminal_urlify_man("kernel-install", "8", &link
);
1465 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
1466 "%5$sAdd and remove kernel and initrd images to and from the boot partition.%6$s\n"
1467 "\n%3$sUsage:%4$s\n"
1468 " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n"
1469 " kernel-install [OPTIONS...] add-all\n"
1470 " kernel-install [OPTIONS...] remove KERNEL-VERSION\n"
1471 " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n"
1473 " kernel-install [OPTIONS...] list\n"
1474 "\n%3$sOptions:%4$s\n"
1475 " -h --help Show this help\n"
1476 " --version Show package version\n"
1477 " -v --verbose Increase verbosity\n"
1478 " --esp-path=PATH Path to the EFI System Partition (ESP)\n"
1479 " --boot-path=PATH Path to the $BOOT partition\n"
1480 " --make-entry-directory=yes|no|auto\n"
1481 " Create $BOOT/ENTRY-TOKEN/ directory\n"
1482 " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
1483 " Entry token to use for this installation\n"
1484 " --no-pager Do not pipe inspect output into a pager\n"
1485 " --json=pretty|short|off Generate JSON output\n"
1486 " --no-legend Do not show the headers and footers\n"
1487 " --root=PATH Operate on an alternate filesystem root\n"
1488 " --image=PATH Operate on disk image as filesystem root\n"
1489 " --image-policy=POLICY Specify disk image dissection policy\n"
1491 "This program may also be invoked as 'installkernel':\n"
1492 " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
1493 "(The optional arguments are passed by kernel build system, but ignored.)\n"
1495 "See the %2$s for details.\n",
1496 program_invocation_short_name
,
1506 static int parse_argv(int argc
, char *argv
[], Context
*c
) {
1508 ARG_VERSION
= 0x100,
1512 ARG_MAKE_ENTRY_DIRECTORY
,
1520 static const struct option options
[] = {
1521 { "help", no_argument
, NULL
, 'h' },
1522 { "version", no_argument
, NULL
, ARG_VERSION
},
1523 { "verbose", no_argument
, NULL
, 'v' },
1524 { "esp-path", required_argument
, NULL
, ARG_ESP_PATH
},
1525 { "boot-path", required_argument
, NULL
, ARG_BOOT_PATH
},
1526 { "make-entry-directory", required_argument
, NULL
, ARG_MAKE_ENTRY_DIRECTORY
},
1527 { "entry-token", required_argument
, NULL
, ARG_ENTRY_TOKEN
},
1528 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1529 { "json", required_argument
, NULL
, ARG_JSON
},
1530 { "root", required_argument
, NULL
, ARG_ROOT
},
1531 { "image", required_argument
, NULL
, ARG_IMAGE
},
1532 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
1533 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1542 while ((t
= getopt_long(argc
, argv
, "hv", options
, NULL
)) >= 0)
1555 log_set_max_level(LOG_DEBUG
);
1560 r
= parse_path_argument(optarg
, /* suppress_root = */ false, &arg_esp_path
);
1566 r
= parse_path_argument(optarg
, /* suppress_root = */ false, &arg_xbootldr_path
);
1571 case ARG_MAKE_ENTRY_DIRECTORY
:
1572 if (streq(optarg
, "auto"))
1573 arg_make_entry_directory
= -1;
1575 r
= parse_boolean_argument("--make-entry-directory=", optarg
, NULL
);
1579 arg_make_entry_directory
= r
;
1583 case ARG_ENTRY_TOKEN
:
1584 r
= parse_boot_entry_token_type(optarg
, &c
->entry_token_type
, &c
->entry_token
);
1590 arg_pager_flags
|= PAGER_DISABLE
;
1594 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1600 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_root
);
1606 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
1611 case ARG_IMAGE_POLICY
:
1612 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
1621 assert_not_reached();
1624 if (arg_image
&& arg_root
)
1625 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
1630 static int run(int argc
, char* argv
[]) {
1631 static const Verb verbs
[] = {
1632 { "add", 1, VERB_ANY
, 0, verb_add
},
1633 { "add-all", 1, 1, 0, verb_add_all
},
1634 { "remove", 2, VERB_ANY
, 0, verb_remove
},
1635 { "inspect", 1, VERB_ANY
, VERB_DEFAULT
, verb_inspect
},
1636 { "list", 1, 1, 0, verb_list
},
1639 _cleanup_(context_done
) Context c
= {
1641 .action
= _ACTION_INVALID
,
1642 .kernel_image_type
= KERNEL_IMAGE_TYPE_UNKNOWN
,
1643 .layout
= _LAYOUT_INVALID
,
1644 .entry_token_type
= BOOT_ENTRY_TOKEN_AUTO
,
1646 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
1647 _cleanup_(umount_and_freep
) char *mounted_dir
= NULL
;
1652 r
= parse_argv(argc
, argv
, &c
);
1659 r
= mount_image_privately_interactively(
1662 DISSECT_IMAGE_GENERIC_ROOT
|
1663 DISSECT_IMAGE_REQUIRE_ROOT
|
1664 DISSECT_IMAGE_RELAX_VAR_CHECK
|
1665 DISSECT_IMAGE_VALIDATE_OS
|
1666 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY
,
1668 /* ret_dir_fd= */ NULL
,
1673 arg_root
= strdup(mounted_dir
);
1678 r
= context_init(&c
);
1682 if (invoked_as(argv
, "installkernel"))
1683 return run_as_installkernel(argc
, argv
, &c
);
1685 return dispatch_verb(argc
, argv
, verbs
, &c
);
1688 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);