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(Context
*c
) {
435 _cleanup_free_
char *machine_id
= NULL
, *boot_root
= NULL
, *layout
= NULL
,
436 *initrd_generator
= NULL
, *uki_generator
= NULL
;
437 const ConfigTableItem items
[] = {
438 { NULL
, "MACHINE_ID", config_parse_string
, 0, &machine_id
},
439 { NULL
, "BOOT_ROOT", config_parse_string
, 0, &boot_root
},
440 { NULL
, "layout", config_parse_string
, 0, &layout
},
441 { NULL
, "initrd_generator", config_parse_string
, 0, &initrd_generator
},
442 { NULL
, "uki_generator", config_parse_string
, 0, &uki_generator
},
450 _cleanup_free_
char *conf
= NULL
;
452 conf
= path_join(c
->conf_root
, "install.conf");
456 r
= config_parse_many(
457 STRV_MAKE_CONST(conf
),
458 STRV_MAKE_CONST(c
->conf_root
),
460 /* root= */ NULL
, /* $KERNEL_INSTALL_CONF_ROOT and --root are independent */
461 /* sections= */ NULL
,
462 config_item_table_lookup
, items
,
464 /* userdata = */ NULL
,
465 /* ret_stats_by_path= */ NULL
,
466 /* ret_dropin_files= */ NULL
);
468 r
= config_parse_standard_file_with_dropins_full(
470 "kernel/install.conf",
471 /* sections= */ NULL
,
472 config_item_table_lookup
, items
,
474 /* userdata = */ NULL
,
475 /* ret_stats_by_path= */ NULL
,
476 /* ret_dropin_files= */ NULL
);
478 return r
== -ENOENT
? 0 : r
;
480 (void) context_set_machine_id(c
, machine_id
, "config");
481 (void) context_set_boot_root(c
, boot_root
, "config");
482 (void) context_set_layout(c
, layout
, "config");
483 (void) context_set_initrd_generator(c
, initrd_generator
, "config");
484 (void) context_set_uki_generator(c
, uki_generator
, "config");
486 log_debug("Loaded config.");
490 static int context_load_machine_info(Context
*c
) {
491 _cleanup_fclose_
FILE *f
= NULL
;
492 _cleanup_free_
char *machine_id
= NULL
, *layout
= NULL
;
493 static const char *path
= "/etc/machine-info";
498 /* If the user configured an explicit machine ID in /etc/machine-info to use for our purpose, we'll
499 * use that instead (for compatibility). */
501 if (!sd_id128_is_null(c
->machine_id
) && c
->layout
>= 0)
504 /* For testing. To make not read host's /etc/machine-info. */
505 r
= getenv_bool("KERNEL_INSTALL_READ_MACHINE_INFO");
506 if (r
< 0 && r
!= -ENXIO
)
507 log_warning_errno(r
, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m");
509 log_debug("Skipping reading of /etc/machine-info.");
513 r
= chase_and_fopenat_unlocked(c
->rfd
, path
, CHASE_AT_RESOLVE_IN_ROOT
, "re", NULL
, &f
);
517 return log_error_errno(r
, "Failed to chase %s: %m", path
);
519 log_debug("Loading %s…", path
);
521 r
= parse_env_file(f
, path
,
522 "KERNEL_INSTALL_MACHINE_ID", &machine_id
,
523 "KERNEL_INSTALL_LAYOUT", &layout
);
525 return log_error_errno(r
, "Failed to parse '%s': %m", path
);
527 (void) context_set_machine_id(c
, machine_id
, path
);
528 (void) context_set_layout(c
, layout
, path
);
532 static int context_load_machine_id(Context
*c
) {
537 r
= id128_get_machine_at(c
->rfd
, &c
->machine_id
);
538 if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r
))
541 return log_error_errno(r
, "Failed to load machine ID from /etc/machine-id: %m");
543 log_debug("MACHINE_ID=%s set via /etc/machine-id.", SD_ID128_TO_STRING(c
->machine_id
));
544 return 1; /* loaded */
547 static int context_ensure_machine_id(Context
*c
) {
552 if (!sd_id128_is_null(c
->machine_id
))
555 /* If /etc/machine-id is initialized we'll use it. */
556 r
= context_load_machine_id(c
);
560 /* Otherwise we'll use a freshly generated one. */
561 r
= sd_id128_randomize(&c
->machine_id
);
563 return log_error_errno(r
, "Failed to generate random ID: %m");
565 c
->machine_id_is_random
= true;
566 log_debug("New machine ID '%s' generated.", SD_ID128_TO_STRING(c
->machine_id
));
570 static int context_acquire_xbootldr(Context
*c
) {
574 assert(!c
->boot_root
);
576 r
= find_xbootldr_and_warn_at(
578 /* path = */ arg_xbootldr_path
,
579 /* unprivileged_mode= */ -1,
580 /* ret_path = */ &c
->boot_root
,
581 /* ret_uuid = */ NULL
,
582 /* ret_devid = */ NULL
);
584 log_debug_errno(r
, "Couldn't find an XBOOTLDR partition.");
587 if (r
== -EACCES
&& geteuid() != 0)
588 return log_error_errno(r
, "Failed to determine XBOOTLDR partition: %m");
592 log_debug("Using XBOOTLDR partition at %s as $BOOT_ROOT.", c
->boot_root
);
593 return 1; /* found */
596 static int context_acquire_esp(Context
*c
) {
600 assert(!c
->boot_root
);
602 r
= find_esp_and_warn_at(
604 /* path = */ arg_esp_path
,
605 /* unprivileged_mode= */ -1,
606 /* ret_path = */ &c
->boot_root
,
607 /* ret_part = */ NULL
,
608 /* ret_pstart = */ NULL
,
609 /* ret_psize = */ NULL
,
610 /* ret_uuid = */ NULL
,
611 /* ret_devid = */ NULL
);
613 log_debug_errno(r
, "Couldn't find EFI system partition, ignoring.");
616 if (r
== -EACCES
&& geteuid() != 0)
617 return log_error_errno(r
, "Failed to determine EFI system partition: %m");
621 log_debug("Using EFI System Partition at %s as $BOOT_ROOT.", c
->boot_root
);
622 return 1; /* found */
625 static int context_ensure_boot_root(Context
*c
) {
630 /* If BOOT_ROOT is specified via environment or install.conf, then use it. */
634 /* Otherwise, use XBOOTLDR partition, if mounted. */
635 r
= context_acquire_xbootldr(c
);
639 /* Otherwise, use EFI system partition, if mounted. */
640 r
= context_acquire_esp(c
);
644 /* If all else fails, use /boot. */
646 r
= chaseat(c
->rfd
, "/boot", CHASE_AT_RESOLVE_IN_ROOT
, &c
->boot_root
, /* ret_fd = */ NULL
);
648 return log_error_errno(r
, "Failed to chase '/boot': %m");
650 c
->boot_root
= strdup("/boot");
655 log_debug("KERNEL_INSTALL_BOOT_ROOT autodetection yielded no candidates, using \"%s\".", c
->boot_root
);
659 static int context_ensure_entry_token(Context
*c
) {
664 /* Now that we determined the machine ID to use, let's determine the "token" for the boot loader
665 * entry to generate. We use that for naming the directory below $BOOT where we want to place the
666 * kernel/initrd and related resources, as well for naming the .conf boot loader spec entry.
667 * Typically this is just the machine ID, but it can be anything else, too, if we are told so. */
669 r
= boot_entry_token_ensure_at(
673 c
->machine_id_is_random
,
674 &c
->entry_token_type
,
679 log_debug("Using entry token: %s", c
->entry_token
);
683 static int context_load_plugins(Context
*c
) {
691 r
= conf_files_list_strv_at(
695 CONF_FILES_EXECUTABLE
| CONF_FILES_REGULAR
| CONF_FILES_FILTER_MASKED
,
696 STRV_MAKE_CONST("/etc/kernel/install.d", "/usr/lib/kernel/install.d"));
698 return log_error_errno(r
, "Failed to find plugins: %m");
703 static int context_init(Context
*c
) {
708 r
= context_open_root(c
);
712 r
= context_load_environment(c
);
716 r
= context_load_install_conf(c
);
720 r
= context_load_machine_info(c
);
724 r
= context_ensure_machine_id(c
);
728 r
= context_ensure_boot_root(c
);
732 r
= context_ensure_entry_token(c
);
736 r
= context_load_plugins(c
);
743 static int context_inspect_kernel(Context
*c
) {
749 return inspect_kernel(c
->rfd
, c
->kernel
, &c
->kernel_image_type
, NULL
, NULL
, NULL
);
752 static int context_ensure_layout(Context
*c
) {
756 assert(c
->boot_root
);
757 assert(c
->entry_token
);
759 if (c
->layout
>= 0 && c
->layout
!= LAYOUT_AUTO
)
762 /* No layout configured by the administrator. Let's try to figure it out automatically from metadata
763 * already contained in $BOOT_ROOT. */
765 if (c
->kernel_image_type
== KERNEL_IMAGE_TYPE_UKI
) {
766 c
->layout
= LAYOUT_UKI
;
767 log_debug("Kernel image type is %s, using layout=%s.",
768 kernel_image_type_to_string(c
->kernel_image_type
), layout_to_string(c
->layout
));
772 _cleanup_free_
char *srel_path
= path_join(c
->boot_root
, "loader/entries.srel");
776 _cleanup_free_
char *srel
= NULL
;
777 r
= read_one_line_file_at(c
->rfd
, srel_path
, &srel
);
779 if (streq(srel
, "type1"))
780 /* The loader/entries.srel file clearly indicates that the installed boot loader
781 * implements the proper standard upstream boot loader spec for Type #1 entries.
782 * Let's default to that, then. */
783 c
->layout
= LAYOUT_BLS
;
785 /* The loader/entries.srel file indicates some other spec is implemented and owns the
786 * /loader/entries/ directory. Since we have no idea what that means, let's stay away
787 * from it by default. */
788 c
->layout
= LAYOUT_OTHER
;
790 log_debug("%s with '%s' found, using layout=%s.", srel_path
, srel
, layout_to_string(c
->layout
));
792 } else if (r
!= -ENOENT
)
793 return log_error_errno(r
, "Failed to read %s: %m", srel_path
);
795 _cleanup_free_
char *entry_token_path
= path_join(c
->boot_root
, c
->entry_token
);
796 if (!entry_token_path
)
799 r
= is_dir_at(c
->rfd
, entry_token_path
, /* follow = */ false);
800 if (r
< 0 && r
!= -ENOENT
)
801 return log_error_errno(r
, "Failed to check if '%s' is a directory: %m", entry_token_path
);
803 /* If the metadata in $BOOT_ROOT doesn't tell us anything, then check if the entry token
804 * directory already exists. If so, let's assume it's the standard boot loader spec, too. */
805 c
->layout
= LAYOUT_BLS
;
806 log_debug("%s exists, using layout=%s.", entry_token_path
, layout_to_string(c
->layout
));
810 /* There's no metadata in $BOOT_ROOT, and apparently no entry token directory installed? Then we
811 * really don't know anything. */
812 c
->layout
= LAYOUT_OTHER
;
813 log_debug("Entry-token directory not found, using layout=%s.", layout_to_string(c
->layout
));
817 static int context_set_up_staging_area(Context
*c
) {
818 static const char *template = "/tmp/kernel-install.staging.XXXXXX";
826 if (c
->action
== ACTION_INSPECT
) {
827 /* This is only used for display. The directory will not be created. */
828 c
->staging_area
= strdup(template);
829 if (!c
->staging_area
)
832 r
= mkdtemp_malloc(template, &c
->staging_area
);
834 return log_error_errno(r
, "Failed to create staging area: %m");
840 static int context_build_entry_dir(Context
*c
) {
842 assert(c
->boot_root
);
843 assert(c
->entry_token
);
844 assert(c
->version
|| c
->action
== ACTION_INSPECT
);
849 c
->entry_dir
= path_join(c
->boot_root
, c
->entry_token
, c
->version
?: "KERNEL_VERSION");
853 log_debug("Using ENTRY_DIR=%s", c
->entry_dir
);
857 static bool context_should_make_entry_dir(Context
*c
) {
860 /* Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN to signal to
861 * 00-entry-directory to create $ENTRY_DIR to serve as the indication to use or to not use the BLS */
863 if (arg_make_entry_directory
< 0)
864 return c
->layout
== LAYOUT_BLS
;
866 return arg_make_entry_directory
;
869 static int context_make_entry_dir(Context
*c
) {
870 _cleanup_close_
int fd
= -EBADF
;
873 assert(c
->entry_dir
);
875 if (c
->action
!= ACTION_ADD
)
878 if (!context_should_make_entry_dir(c
))
881 log_debug("mkdir -p %s", c
->entry_dir
);
882 fd
= chase_and_openat(c
->rfd
, c
->entry_dir
, CHASE_AT_RESOLVE_IN_ROOT
| CHASE_MKDIR_0755
,
883 O_CLOEXEC
| O_CREAT
| O_DIRECTORY
| O_PATH
, NULL
);
885 return log_error_errno(fd
, "Failed to make directory '%s': %m", c
->entry_dir
);
890 static int context_remove_entry_dir(Context
*c
) {
891 _cleanup_free_
char *p
= NULL
;
892 _cleanup_close_
int fd
= -EBADF
;
897 assert(c
->entry_dir
);
899 if (c
->action
!= ACTION_REMOVE
)
902 if (!context_should_make_entry_dir(c
))
905 log_debug("rm -rf %s", c
->entry_dir
);
906 fd
= chase_and_openat(c
->rfd
, c
->entry_dir
, CHASE_AT_RESOLVE_IN_ROOT
, O_CLOEXEC
| O_DIRECTORY
, &p
);
908 if (IN_SET(fd
, -ENOTDIR
, -ENOENT
))
910 return log_debug_errno(fd
, "Failed to chase and open %s, ignoring: %m", c
->entry_dir
);
913 if (fstat(fd
, &st
) < 0)
914 return log_debug_errno(errno
, "Failed to stat %s: %m", p
);
916 r
= rm_rf_children(TAKE_FD(fd
), REMOVE_PHYSICAL
|REMOVE_MISSING_OK
|REMOVE_CHMOD
, &st
);
918 log_debug_errno(r
, "Failed to remove children of %s, ignoring: %m", p
);
920 if (unlinkat(c
->rfd
, p
, AT_REMOVEDIR
) < 0)
921 log_debug_errno(errno
, "Failed to remove %s, ignoring: %m", p
);
926 static int context_build_arguments(Context
*c
) {
927 _cleanup_strv_free_
char **a
= NULL
;
932 assert(c
->entry_dir
);
956 assert_not_reached();
959 a
= strv_new("dummy-arg", /* to make strv_free() works for this variable. */
961 c
->version
?: "KERNEL_VERSION",
966 if (c
->action
== ACTION_ADD
) {
967 r
= strv_extend(&a
, c
->kernel
);
971 r
= strv_extend_strv(&a
, c
->initrds
, /* filter_duplicates = */ false);
975 } else if (c
->action
== ACTION_INSPECT
) {
976 r
= strv_extend_many(
978 c
->kernel
?: "[KERNEL_IMAGE]",
984 c
->argv
= TAKE_PTR(a
);
988 static int context_build_environment(Context
*c
) {
989 _cleanup_strv_free_
char **e
= NULL
;
997 r
= strv_env_assign_many(&e
,
998 "LC_COLLATE", SYSTEMD_DEFAULT_LOCALE
,
999 "KERNEL_INSTALL_VERBOSE", one_zero(arg_verbose
),
1000 "KERNEL_INSTALL_IMAGE_TYPE", kernel_image_type_to_string(c
->kernel_image_type
),
1001 "KERNEL_INSTALL_MACHINE_ID", SD_ID128_TO_STRING(c
->machine_id
),
1002 "KERNEL_INSTALL_ENTRY_TOKEN", c
->entry_token
,
1003 "KERNEL_INSTALL_BOOT_ROOT", c
->boot_root
,
1004 "KERNEL_INSTALL_LAYOUT", context_get_layout(c
),
1005 "KERNEL_INSTALL_INITRD_GENERATOR", strempty(c
->initrd_generator
),
1006 "KERNEL_INSTALL_UKI_GENERATOR", strempty(c
->uki_generator
),
1007 "KERNEL_INSTALL_STAGING_AREA", c
->staging_area
);
1009 return log_error_errno(r
, "Failed to build environment variables for plugins: %m");
1011 c
->envp
= TAKE_PTR(e
);
1015 static int context_prepare_execution(Context
*c
) {
1020 r
= context_inspect_kernel(c
);
1024 r
= context_ensure_layout(c
);
1028 r
= context_set_up_staging_area(c
);
1032 r
= context_build_entry_dir(c
);
1036 r
= context_build_arguments(c
);
1040 r
= context_build_environment(c
);
1047 static int context_execute(Context
*c
) {
1052 r
= context_make_entry_dir(c
);
1056 if (DEBUG_LOGGING
) {
1057 _cleanup_free_
char *x
= strv_join_full(c
->plugins
, "", "\n ", /* escape_separator = */ false);
1058 log_debug("Using plugins: %s", strna(x
));
1060 _cleanup_free_
char *y
= strv_join_full(c
->envp
, "", "\n ", /* escape_separator = */ false);
1061 log_debug("Plugin environment: %s", strna(y
));
1063 _cleanup_free_
char *z
= strv_join(strv_skip(c
->argv
, 1), " ");
1064 log_debug("Plugin arguments: %s", strna(z
));
1072 /* callbacks = */ NULL
,
1073 /* callback_args = */ NULL
,
1076 EXEC_DIR_SKIP_REMAINING
);
1078 r
= context_remove_entry_dir(c
);
1082 /* This returns 0 on success, positive exit code on plugin failure, negative errno on other failures. */
1086 static bool bypass(void) {
1089 r
= getenv_bool("KERNEL_INSTALL_BYPASS");
1090 if (r
< 0 && r
!= -ENXIO
)
1091 log_debug_errno(r
, "Failed to parse $KERNEL_INSTALL_BYPASS, assuming no.");
1095 log_debug("$KERNEL_INSTALL_BYPASS is enabled, skipping execution.");
1101 const char *version
,
1111 r
= context_set_version(c
, version
);
1115 r
= context_set_kernel(c
, kernel
);
1119 r
= context_set_initrds(c
, initrds
);
1123 r
= context_prepare_execution(c
);
1127 return context_execute(c
);
1130 static int kernel_from_version(const char *version
, char **ret_kernel
) {
1131 _cleanup_free_
char *vmlinuz
= NULL
;
1136 vmlinuz
= path_join("/usr/lib/modules/", version
, "/vmlinuz");
1140 r
= laccess(vmlinuz
, F_OK
);
1143 return log_error_errno(r
, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz
);
1145 return log_error_errno(r
, "Failed to determine if kernel image is installed to '%s': %m", vmlinuz
);
1148 *ret_kernel
= TAKE_PTR(vmlinuz
);
1152 static int verb_add(int argc
, char *argv
[], void *userdata
) {
1153 Context
*c
= ASSERT_PTR(userdata
);
1154 _cleanup_free_
char *vmlinuz
= NULL
;
1155 const char *version
, *kernel
;
1163 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'add' does not support --root= or --image=.");
1168 c
->action
= ACTION_ADD
;
1170 /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
1171 * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
1172 * optional, not the 2nd. */
1173 version
= argc
> 2 ? empty_or_dash_to_null(argv
[1]) : NULL
;
1174 kernel
= argc
> 2 ? empty_or_dash_to_null(argv
[2]) :
1175 (argc
> 1 ? empty_or_dash_to_null(argv
[1]) : NULL
);
1176 initrds
= strv_skip(argv
, 3);
1179 assert_se(uname(&un
) >= 0);
1180 version
= un
.release
;
1184 r
= kernel_from_version(version
, &vmlinuz
);
1191 return do_add(c
, version
, kernel
, initrds
);
1194 static int verb_add_all(int argc
, char *argv
[], void *userdata
) {
1195 Context
*c
= ASSERT_PTR(userdata
);
1196 _cleanup_close_
int fd
= -EBADF
;
1203 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'add-all' does not support --root= or --image=.");
1208 c
->action
= ACTION_ADD
;
1210 fd
= chase_and_openat(c
->rfd
, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
, NULL
);
1212 return log_error_errno(fd
, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root
));
1214 _cleanup_free_ DirectoryEntries
*de
= NULL
;
1215 r
= readdir_all(fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
1217 return log_error_errno(r
, "Failed to numerate /usr/lib/modules/ contents: %m");
1219 FOREACH_ARRAY(d
, de
->entries
, de
->n_entries
) {
1220 r
= dirent_ensure_type(fd
, *d
);
1222 if (r
!= -ENOENT
) /* don't log if just gone by now */
1223 log_debug_errno(r
, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1227 if ((*d
)->d_type
!= DT_DIR
)
1230 _cleanup_free_
char *fn
= path_join((*d
)->d_name
, "vmlinuz");
1234 if (faccessat(fd
, fn
, F_OK
, AT_SYMLINK_NOFOLLOW
) < 0) {
1235 if (errno
!= ENOENT
)
1236 log_debug_errno(errno
, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1238 log_notice("Not adding version '%s', because kernel image not found.", (*d
)->d_name
);
1242 _cleanup_(context_done
) Context copy
= CONTEXT_NULL
;
1244 r
= context_copy(c
, ©
);
1246 return log_error_errno(r
, "Failed to copy execution context: %m");
1248 /* do_add() will look up the path in the correct root directory so we don't need to prefix it
1249 * with arg_root here. */
1250 _cleanup_free_
char *full
= path_join("/usr/lib/modules/", fn
);
1255 /* version= */ (*d
)->d_name
,
1257 /* initrds= */ NULL
);
1265 log_debug("Installed %zu kernel(s).", n
);
1267 ret
= log_error_errno(SYNTHETIC_ERRNO(ENOENT
), "No kernels to install found.");
1272 static int run_as_installkernel(int argc
, char *argv
[], Context
*c
) {
1273 /* kernel's install.sh invokes us as
1274 * /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
1275 * We ignore the last two arguments. */
1276 if (optind
+ 2 > argc
)
1277 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "'installkernel' command requires at least two arguments.");
1279 return verb_add(3, STRV_MAKE("add", argv
[optind
], argv
[optind
+1]), c
);
1282 static int verb_remove(int argc
, char *argv
[], void *userdata
) {
1283 Context
*c
= ASSERT_PTR(userdata
);
1290 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "'remove' does not support --root= or --image=.");
1293 log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. "
1294 "Ignoring residual arguments.");
1299 c
->action
= ACTION_REMOVE
;
1301 /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do
1302 * it for the "add" verb), since we don't want to make it too easy to uninstall your running
1303 * kernel, as a safety precaution */
1305 r
= context_set_version(c
, argv
[1]);
1309 r
= context_prepare_execution(c
);
1313 return context_execute(c
);
1316 static int verb_inspect(int argc
, char *argv
[], void *userdata
) {
1317 Context
*c
= ASSERT_PTR(userdata
);
1318 _cleanup_(table_unrefp
) Table
*t
= NULL
;
1319 _cleanup_free_
char *vmlinuz
= NULL
;
1320 const char *version
, *kernel
;
1325 c
->action
= ACTION_INSPECT
;
1327 /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
1328 * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
1329 * we keep the behaviour for compatibility. If users want to specify only the version (and have the
1330 * kernel image path derived automatically), then they may specify an empty string or "dash" as
1331 * kernel image path. */
1332 version
= argc
> 2 ? empty_or_dash_to_null(argv
[1]) : NULL
;
1333 kernel
= argc
> 2 ? empty_or_dash_to_null(argv
[2]) :
1334 (argc
> 1 ? empty_or_dash_to_null(argv
[1]) : NULL
);
1335 initrds
= strv_skip(argv
, 3);
1337 if (!version
&& !arg_root
) {
1338 assert_se(uname(&un
) >= 0);
1339 version
= un
.release
;
1342 if (!kernel
&& version
) {
1343 r
= kernel_from_version(version
, &vmlinuz
);
1350 r
= context_set_version(c
, version
);
1354 r
= context_set_kernel(c
, kernel
);
1358 r
= context_set_initrds(c
, initrds
);
1362 r
= context_prepare_execution(c
);
1366 t
= table_new_vertical();
1370 r
= table_add_many(t
,
1371 TABLE_FIELD
, "Machine ID",
1372 TABLE_ID128
, c
->machine_id
,
1373 TABLE_FIELD
, "Kernel Image Type",
1374 TABLE_STRING
, kernel_image_type_to_string(c
->kernel_image_type
),
1375 TABLE_FIELD
, "Layout",
1376 TABLE_STRING
, context_get_layout(c
),
1377 TABLE_FIELD
, "Boot Root",
1378 TABLE_STRING
, c
->boot_root
,
1379 TABLE_FIELD
, "Entry Token Type",
1380 TABLE_STRING
, boot_entry_token_type_to_string(c
->entry_token_type
),
1381 TABLE_FIELD
, "Entry Token",
1382 TABLE_STRING
, c
->entry_token
,
1383 TABLE_FIELD
, "Entry Directory",
1384 TABLE_STRING
, c
->entry_dir
,
1385 TABLE_FIELD
, "Kernel Version",
1386 TABLE_STRING
, c
->version
,
1387 TABLE_FIELD
, "Kernel",
1388 TABLE_STRING
, c
->kernel
,
1389 TABLE_FIELD
, "Initrds",
1390 TABLE_STRV
, c
->initrds
,
1391 TABLE_FIELD
, "Initrd Generator",
1392 TABLE_STRING
, c
->initrd_generator
,
1393 TABLE_FIELD
, "UKI Generator",
1394 TABLE_STRING
, c
->uki_generator
,
1395 TABLE_FIELD
, "Plugins",
1396 TABLE_STRV
, c
->plugins
,
1397 TABLE_FIELD
, "Plugin Environment",
1398 TABLE_STRV
, c
->envp
);
1400 return table_log_add_error(r
);
1402 if (arg_json_format_flags
& JSON_FORMAT_OFF
) {
1403 r
= table_add_many(t
,
1404 TABLE_FIELD
, "Plugin Arguments",
1405 TABLE_STRV
, strv_skip(c
->argv
, 1));
1407 return table_log_add_error(r
);
1410 table_set_ersatz_string(t
, TABLE_ERSATZ_UNSET
);
1412 for (size_t row
= 1; row
< table_get_rows(t
); row
++) {
1413 _cleanup_free_
char *name
= NULL
;
1415 name
= strdup(table_get_at(t
, row
, 0));
1419 r
= table_set_json_field_name(t
, row
- 1, delete_chars(name
, " "));
1421 return log_error_errno(r
, "Failed to set JSON field name: %m");
1424 return table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, /* show_header= */ false);
1427 static int verb_list(int argc
, char *argv
[], void *userdata
) {
1428 Context
*c
= ASSERT_PTR(userdata
);
1429 _cleanup_close_
int fd
= -EBADF
;
1432 fd
= chase_and_openat(c
->rfd
, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT
, O_DIRECTORY
|O_RDONLY
|O_CLOEXEC
, NULL
);
1434 return log_error_errno(fd
, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root
));
1436 _cleanup_free_ DirectoryEntries
*de
= NULL
;
1437 r
= readdir_all(fd
, RECURSE_DIR_SORT
|RECURSE_DIR_IGNORE_DOT
, &de
);
1439 return log_error_errno(r
, "Failed to numerate /usr/lib/modules/ contents: %m");
1441 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1442 table
= table_new("version", "has kernel", "path");
1446 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
1447 table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
1449 FOREACH_ARRAY(d
, de
->entries
, de
->n_entries
) {
1450 _cleanup_free_
char *j
= path_join("/usr/lib/modules/", (*d
)->d_name
);
1454 r
= dirent_ensure_type(fd
, *d
);
1456 if (r
!= -ENOENT
) /* don't log if just gone by now */
1457 log_debug_errno(r
, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root
), j
);
1461 if ((*d
)->d_type
!= DT_DIR
)
1464 _cleanup_free_
char *fn
= path_join((*d
)->d_name
, "vmlinuz");
1469 if (faccessat(fd
, fn
, F_OK
, AT_SYMLINK_NOFOLLOW
) < 0) {
1470 if (errno
!= ENOENT
)
1471 log_debug_errno(errno
, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root
), (*d
)->d_name
);
1477 r
= table_add_many(table
,
1478 TABLE_STRING
, (*d
)->d_name
,
1479 TABLE_BOOLEAN_CHECKMARK
, exists
,
1480 TABLE_SET_COLOR
, ansi_highlight_green_red(exists
),
1483 return table_log_add_error(r
);
1486 return table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
1489 static int help(void) {
1490 _cleanup_free_
char *link
= NULL
;
1493 r
= terminal_urlify_man("kernel-install", "8", &link
);
1497 printf("%1$s [OPTIONS...] COMMAND ...\n\n"
1498 "%5$sAdd and remove kernel and initrd images to and from /boot/%6$s\n"
1499 "\n%3$sUsage:%4$s\n"
1500 " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n"
1501 " kernel-install [OPTIONS...] add-all\n"
1502 " kernel-install [OPTIONS...] remove KERNEL-VERSION\n"
1503 " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n"
1505 " kernel-install [OPTIONS...] list\n"
1506 "\n%3$sOptions:%4$s\n"
1507 " -h --help Show this help\n"
1508 " --version Show package version\n"
1509 " -v --verbose Increase verbosity\n"
1510 " --esp-path=PATH Path to the EFI System Partition (ESP)\n"
1511 " --boot-path=PATH Path to the $BOOT partition\n"
1512 " --make-entry-directory=yes|no|auto\n"
1513 " Create $BOOT/ENTRY-TOKEN/ directory\n"
1514 " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
1515 " Entry token to use for this installation\n"
1516 " --no-pager Do not pipe inspect output into a pager\n"
1517 " --json=pretty|short|off Generate JSON output\n"
1518 " --no-legend Do not show the headers and footers\n"
1519 " --root=PATH Operate on an alternate filesystem root\n"
1520 " --image=PATH Operate on disk image as filesystem root\n"
1521 " --image-policy=POLICY Specify disk image dissection policy\n"
1523 "This program may also be invoked as 'installkernel':\n"
1524 " installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
1525 "(The optional arguments are passed by kernel build system, but ignored.)\n"
1527 "See the %2$s for details.\n",
1528 program_invocation_short_name
,
1538 static int parse_argv(int argc
, char *argv
[], Context
*c
) {
1540 ARG_VERSION
= 0x100,
1544 ARG_MAKE_ENTRY_DIRECTORY
,
1552 static const struct option options
[] = {
1553 { "help", no_argument
, NULL
, 'h' },
1554 { "version", no_argument
, NULL
, ARG_VERSION
},
1555 { "verbose", no_argument
, NULL
, 'v' },
1556 { "esp-path", required_argument
, NULL
, ARG_ESP_PATH
},
1557 { "boot-path", required_argument
, NULL
, ARG_BOOT_PATH
},
1558 { "make-entry-directory", required_argument
, NULL
, ARG_MAKE_ENTRY_DIRECTORY
},
1559 { "entry-token", required_argument
, NULL
, ARG_ENTRY_TOKEN
},
1560 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1561 { "json", required_argument
, NULL
, ARG_JSON
},
1562 { "root", required_argument
, NULL
, ARG_ROOT
},
1563 { "image", required_argument
, NULL
, ARG_IMAGE
},
1564 { "image-policy", required_argument
, NULL
, ARG_IMAGE_POLICY
},
1565 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1574 while ((t
= getopt_long(argc
, argv
, "hv", options
, NULL
)) >= 0)
1587 log_set_max_level(LOG_DEBUG
);
1592 r
= parse_path_argument(optarg
, /* suppress_root = */ false, &arg_esp_path
);
1598 r
= parse_path_argument(optarg
, /* suppress_root = */ false, &arg_xbootldr_path
);
1603 case ARG_MAKE_ENTRY_DIRECTORY
:
1604 if (streq(optarg
, "auto"))
1605 arg_make_entry_directory
= -1;
1607 r
= parse_boolean_argument("--make-entry-directory=", optarg
, NULL
);
1611 arg_make_entry_directory
= r
;
1615 case ARG_ENTRY_TOKEN
:
1616 r
= parse_boot_entry_token_type(optarg
, &c
->entry_token_type
, &c
->entry_token
);
1622 arg_pager_flags
|= PAGER_DISABLE
;
1626 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1632 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_root
);
1638 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
1643 case ARG_IMAGE_POLICY
:
1644 r
= parse_image_policy_argument(optarg
, &arg_image_policy
);
1653 assert_not_reached();
1656 if (arg_image
&& arg_root
)
1657 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
1662 static int run(int argc
, char* argv
[]) {
1663 static const Verb verbs
[] = {
1664 { "add", 1, VERB_ANY
, 0, verb_add
},
1665 { "add-all", 1, 1, 0, verb_add_all
},
1666 { "remove", 2, VERB_ANY
, 0, verb_remove
},
1667 { "inspect", 1, VERB_ANY
, VERB_DEFAULT
, verb_inspect
},
1668 { "list", 1, 1, 0, verb_list
},
1671 _cleanup_(context_done
) Context c
= {
1673 .action
= _ACTION_INVALID
,
1674 .kernel_image_type
= KERNEL_IMAGE_TYPE_UNKNOWN
,
1675 .layout
= _LAYOUT_INVALID
,
1676 .entry_token_type
= BOOT_ENTRY_TOKEN_AUTO
,
1678 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
1679 _cleanup_(umount_and_freep
) char *mounted_dir
= NULL
;
1684 r
= parse_argv(argc
, argv
, &c
);
1691 r
= mount_image_privately_interactively(
1694 DISSECT_IMAGE_GENERIC_ROOT
|
1695 DISSECT_IMAGE_REQUIRE_ROOT
|
1696 DISSECT_IMAGE_RELAX_VAR_CHECK
|
1697 DISSECT_IMAGE_VALIDATE_OS
|
1698 DISSECT_IMAGE_ALLOW_USERSPACE_VERITY
,
1700 /* ret_dir_fd= */ NULL
,
1705 arg_root
= strdup(mounted_dir
);
1710 r
= context_init(&c
);
1714 if (invoked_as(argv
, "installkernel"))
1715 return run_as_installkernel(argc
, argv
, &c
);
1717 return dispatch_verb(argc
, argv
, verbs
, &c
);
1720 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run
);