diff --git a/TODO b/TODO
index 216ed7e58d..25165de973 100644
--- a/TODO
+++ b/TODO
@@ -1086,9 +1086,6 @@ Features:
uses RootDirectory= or RootImage=. (Might also over-mount
/sys/class/dmi/id/*{uuid,serial} with /dev/null).
-* kernel-install:
- - add --all switch for rerunning kernel-install for all installed kernels
-
* doc: prep a document explaining resolved's internal objects, i.e. Query
vs. Question vs. Transaction vs. Stream and so on.
diff --git a/man/kernel-install.xml b/man/kernel-install.xml
index f7a5d8a76b..ee8c0069df 100644
--- a/man/kernel-install.xml
+++ b/man/kernel-install.xml
@@ -44,6 +44,11 @@
KERNEL-IMAGE
INITRD-FILE
+
+ kernel-install
+ OPTIONS
+ list
+
@@ -81,15 +86,17 @@
- add
- KERNEL-VERSION
- KERNEL-IMAGE [INITRD-FILE ...]
+ add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD-FILE ...]]
- This command expects a kernel version string and a path to a kernel image file as arguments.
- Optionally, one or more initrd images may be specified as well (note that plugins might generate
- additional ones).
+ This command takes a kernel version string and a path to a kernel image file as arguments. If
+ the former is omitted, specified as an empty string or as "-" it defaults to the current kernel
+ version, i.e. the same string uname -r returns. If the latter is omitted,
+ specified as an empty string or as "-" defaults to
+ /usr/lib/modules/KERNEL_VERSION/vmlinuz. Optionally,
+ one or more initrd images may be specified as well (note that plugins might generate additional
+ ones).
The executable files from /usr/lib/kernel/install.d/*.install and
/etc/kernel/install.d/*.install (i.e. the plugins) are called with the
@@ -144,6 +151,20 @@
+
+
+ add-all
+
+
+
+ This is the same as add (see above), but invokes the operation iteratively
+ for every installed kernel in /usr/lib/modules/. This operation is only
+ supported on systems where the kernel image is installed in
+ /usr/lib/modules/KERNEL-VERSION/vmlinuz.
+
+
+
+
remove KERNEL-VERSION
@@ -179,17 +200,28 @@
- inspect [[KERNEL-VERSION] KERNEL-IMAGE] [INITRD-FILE ...]
+ inspect [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD-FILE ...]]
+ Takes the same parameters as add.
+
Shows the various paths and parameters configured or auto-detected. In particular shows the
- values of the various $KERNEL_INSTALL_* environment variables listed
- below. The option can be used to get the output of this verb as a JSON
- object.
+ values of the various $KERNEL_INSTALL_* environment variables listed below, as
+ they would be passed to plugins. The option can be used to get the output
+ of this verb as a JSON object.
+
+ list
+
+ Shows the various installed kernels. This enumerates the subdirectories of
+ /usr/lib/modules/, and shows whether a kernel image is installed there.
+
+
+
+
@@ -332,6 +364,7 @@
+
@@ -641,6 +674,31 @@
+
+
+ /usr/lib/modules/KERNEL-VERSION/
+
+
+ Location for installed kernel modules and other kernel related resources. For each locally
+ installed kernel a directory named after the kernel version (uname -r) is
+ kept.
+
+
+
+
+
+
+ /usr/lib/modules/KERNEL-VERSION/vmlinuz
+
+
+ Location for installed kernel images. This is the recommended location for OS package
+ managers to install kernel images into (as applicable), from which kernel-install
+ add then copies it into the final boot partition.
+
+
+
+
+
For various cases listed above, if the $KERNEL_INSTALL_CONF_ROOT environment
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index 6b6a5746fe..b6d8be3083 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -301,3 +301,24 @@ bool version_is_valid(const char *s);
bool version_is_valid_versionspec(const char *s);
ssize_t strlevenshtein(const char *x, const char *y);
+
+static inline int strdup_or_null(const char *s, char **ret) {
+ char *c;
+
+ assert(ret);
+
+ /* This is a lot like strdup(), but is happy with NULL strings, and does not treat that as error, but
+ * copies the NULL value. */
+
+ if (!s) {
+ *ret = NULL;
+ return 0;
+ }
+
+ c = strdup(s);
+ if (!c)
+ return -ENOMEM;
+
+ *ret = c;
+ return 1;
+}
diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c
index cae93362b6..6631054d07 100644
--- a/src/kernel-install/kernel-install.c
+++ b/src/kernel-install/kernel-install.c
@@ -2,11 +2,13 @@
#include
#include
+#include
-#include "build.h"
#include "boot-entry.h"
+#include "build.h"
#include "chase.h"
#include "conf-files.h"
+#include "dirent-util.h"
#include "env-file.h"
#include "env-util.h"
#include "exec-util.h"
@@ -14,6 +16,7 @@
#include "fileio.h"
#include "find-esp.h"
#include "format-table.h"
+#include "fs-util.h"
#include "id128-util.h"
#include "image-policy.h"
#include "kernel-image.h"
@@ -23,6 +26,7 @@
#include "parse-argument.h"
#include "path-util.h"
#include "pretty-print.h"
+#include "recurse-dir.h"
#include "rm-rf.h"
#include "stat-util.h"
#include "string-table.h"
@@ -40,6 +44,7 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
static char *arg_root = NULL;
static char *arg_image = NULL;
static ImagePolicy *arg_image_policy = NULL;
+static bool arg_legend = true;
STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep);
@@ -96,6 +101,8 @@ typedef struct Context {
char **envp;
} Context;
+#define CONTEXT_NULL (Context) { .rfd = -EBADF }
+
static void context_done(Context *c) {
assert(c);
@@ -120,6 +127,75 @@ static void context_done(Context *c) {
safe_close(c->rfd);
}
+static int context_copy(const Context *source, Context *ret) {
+ int r;
+
+ assert(source);
+ assert(ret);
+
+ _cleanup_(context_done) Context copy = (Context) {
+ .rfd = -EBADF,
+ .action = source->action,
+ .machine_id = source->machine_id,
+ .machine_id_is_random = source->machine_id_is_random,
+ .kernel_image_type = source->kernel_image_type,
+ .layout = source->layout,
+ .entry_token_type = source->entry_token_type,
+ };
+
+ copy.rfd = fd_reopen(source->rfd, O_CLOEXEC|O_DIRECTORY|O_PATH);
+ if (copy.rfd < 0)
+ return copy.rfd;
+
+ r = strdup_or_null(source->layout_other, ©.layout_other);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->conf_root, ©.conf_root);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->boot_root, ©.boot_root);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->entry_token, ©.entry_token);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->entry_dir, ©.entry_dir);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->version, ©.version);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->kernel, ©.kernel);
+ if (r < 0)
+ return r;
+ copy.initrds = strv_copy(source->initrds);
+ if (!copy.initrds)
+ return -ENOMEM;
+ r = strdup_or_null(source->initrd_generator, ©.initrd_generator);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->uki_generator, ©.uki_generator);
+ if (r < 0)
+ return r;
+ r = strdup_or_null(source->staging_area, ©.staging_area);
+ if (r < 0)
+ return r;
+ copy.plugins = strv_copy(source->plugins);
+ if (!copy.plugins)
+ return -ENOMEM;
+ copy.argv = strv_copy(source->argv);
+ if (!copy.argv)
+ return -ENOMEM;
+ copy.envp = strv_copy(source->envp);
+ if (!copy.envp)
+ return -ENOMEM;
+
+ *ret = copy;
+ copy = CONTEXT_NULL;
+
+ return 0;
+}
+
static int context_open_root(Context *c) {
assert(c);
assert(c->rfd < 0);
@@ -1019,11 +1095,67 @@ static bool bypass(void) {
return true;
}
+static int do_add(
+ Context *c,
+ const char *version,
+ const char *kernel,
+ char **initrds) {
+
+ int r;
+
+ assert(c);
+ assert(version);
+ assert(kernel);
+
+ r = context_set_version(c, version);
+ if (r < 0)
+ return r;
+
+ r = context_set_kernel(c, kernel);
+ if (r < 0)
+ return r;
+
+ r = context_set_initrds(c, initrds);
+ if (r < 0)
+ return r;
+
+ r = context_prepare_execution(c);
+ if (r < 0)
+ return r;
+
+ return context_execute(c);
+}
+
+static int kernel_from_version(const char *version, char **ret_kernel) {
+ _cleanup_free_ char *vmlinuz = NULL;
+ int r;
+
+ assert(version);
+
+ vmlinuz = path_join("/usr/lib/modules/", version, "/vmlinuz");
+ if (!vmlinuz)
+ return log_oom();
+
+ r = laccess(vmlinuz, F_OK);
+ if (r < 0) {
+ if (r == -ENOENT)
+ return log_error_errno(r, "Kernel image not installed to '%s', requiring manual kernel image path specification.", vmlinuz);
+
+ return log_error_errno(r, "Failed to determin if kernel image is installed to '%s': %m", vmlinuz);
+ }
+
+ *ret_kernel = TAKE_PTR(vmlinuz);
+ return 0;
+}
+
static int verb_add(int argc, char *argv[], void *userdata) {
Context *c = ASSERT_PTR(userdata);
+ _cleanup_free_ char *vmlinuz = NULL;
+ const char *version, *kernel;
+ char **initrds;
+ struct utsname un;
int r;
- assert(argc >= 3);
assert(argv);
if (arg_root)
@@ -1034,23 +1166,106 @@ static int verb_add(int argc, char *argv[], void *userdata) {
c->action = ACTION_ADD;
- r = context_set_version(c, argv[1]);
- if (r < 0)
- return r;
+ /* We use the same order of arguments that "inspect" introduced, i.e. if only on argument is
+ * specified we take it as the kernel path, not the version, i.e. it's the first argument that is
+ * optional, not the 2nd. */
+ version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
+ kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
+ (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
+ initrds = strv_skip(argv, 3);
- r = context_set_kernel(c, argv[2]);
- if (r < 0)
- return r;
+ if (!version) {
+ assert_se(uname(&un) >= 0);
+ version = un.release;
+ }
- r = context_set_initrds(c, strv_skip(argv, 3));
- if (r < 0)
- return r;
+ if (!kernel) {
+ r = kernel_from_version(version, &vmlinuz);
+ if (r < 0)
+ return r;
- r = context_prepare_execution(c);
- if (r < 0)
- return r;
+ kernel = vmlinuz;
+ }
- return context_execute(c);
+ return do_add(c, version, kernel, initrds);
+}
+
+static int verb_add_all(int argc, char *argv[], void *userdata) {
+ Context *c = ASSERT_PTR(userdata);
+ _cleanup_close_ int fd = -EBADF;
+ size_t n = 0;
+ int ret = 0, r;
+
+ assert(argv);
+
+ if (bypass())
+ return 0;
+
+ c->action = ACTION_ADD;
+
+ fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m");
+
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+ if (r < 0)
+ return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
+
+ FOREACH_ARRAY(d, de->entries, de->n_entries) {
+
+ _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
+ if (!j)
+ return log_oom();
+
+ r = dirent_ensure_type(fd, *d);
+ if (r < 0) {
+ if (r != -ENOENT) /* don't log if just gone by now */
+ log_debug_errno(r, "Failed to check if '%s' is a directory, ignoring: %m", j);
+ continue;
+ }
+
+ if ((*d)->d_type != DT_DIR)
+ continue;
+
+ _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
+ if (!fn)
+ return log_oom();
+
+ if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno != ENOENT)
+ log_debug_errno(errno, "Failed to check if '/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", (*d)->d_name);
+
+ log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name);
+ continue;
+ }
+
+ _cleanup_(context_done) Context copy = CONTEXT_NULL;
+
+ r = context_copy(c, ©);
+ if (r < 0)
+ return log_error_errno(r, "Failed to copy execution context: %m");
+
+ _cleanup_free_ char *full = path_join("/usr/lib/modules/", fn);
+ if (!full)
+ return log_oom();
+
+ r = do_add(©,
+ /* version= */ (*d)->d_name,
+ /* kernel= */ full,
+ /* initrds= */ NULL);
+ RET_GATHER(ret, r);
+
+ if (r >= 0)
+ n++;
+ }
+
+ if (n > 0)
+ log_info("Installed %zu kernels.", n);
+ else if (ret == 0)
+ ret = log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No kernels to install found.");
+
+ return ret;
}
static int run_as_installkernel(int argc, char *argv[], Context *c) {
@@ -1082,6 +1297,10 @@ static int verb_remove(int argc, char *argv[], void *userdata) {
c->action = ACTION_REMOVE;
+ /* Note, we do not automatically derive the kernel version to remove from uname() here (unlike we do
+ * it for the "add" verb), since we don't want to make it too easy to uninstall your running
+ * kernel, as a safety precaution */
+
r = context_set_version(c, argv[1]);
if (r < 0)
return r;
@@ -1096,28 +1315,49 @@ static int verb_remove(int argc, char *argv[], void *userdata) {
static int verb_inspect(int argc, char *argv[], void *userdata) {
Context *c = ASSERT_PTR(userdata);
_cleanup_(table_unrefp) Table *t = NULL;
+ _cleanup_free_ char *vmlinuz = NULL;
+ const char *version, *kernel;
+ char **initrds;
+ struct utsname un;
int r;
c->action = ACTION_INSPECT;
- if (argc == 2) {
- r = context_set_kernel(c, argv[1]);
- if (r < 0)
- return r;
- } else if (argc >= 3) {
- r = context_set_version(c, argv[1]);
- if (r < 0)
- return r;
+ /* When only a single parameter is specified 'inspect' it's the kernel image path, and not the kernel
+ * version. i.e. it's the first argument that is optional, not the 2nd. That's a bit unfortunate, but
+ * we keep the behaviour for compatibility. If users want to specify only the version (and have the
+ * kernel image path derived automatically), then they may specify an empty string or "dash" as
+ * kernel image path. */
+ version = argc > 2 ? empty_or_dash_to_null(argv[1]) : NULL;
+ kernel = argc > 2 ? empty_or_dash_to_null(argv[2]) :
+ (argc > 1 ? empty_or_dash_to_null(argv[1]) : NULL);
+ initrds = strv_skip(argv, 3);
- r = context_set_kernel(c, argv[2]);
- if (r < 0)
- return r;
-
- r = context_set_initrds(c, strv_skip(argv, 3));
- if (r < 0)
- return r;
+ if (!version) {
+ assert_se(uname(&un) >= 0);
+ version = un.release;
}
+ if (!kernel) {
+ r = kernel_from_version(version, &vmlinuz);
+ if (r < 0)
+ return r;
+
+ kernel = vmlinuz;
+ }
+
+ r = context_set_version(c, version);
+ if (r < 0)
+ return r;
+
+ r = context_set_kernel(c, kernel);
+ if (r < 0)
+ return r;
+
+ r = context_set_initrds(c, initrds);
+ if (r < 0)
+ return r;
+
r = context_prepare_execution(c);
if (r < 0)
return r;
@@ -1183,6 +1423,68 @@ static int verb_inspect(int argc, char *argv[], void *userdata) {
return table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, /* show_header= */ false);
}
+static int verb_list(int argc, char *argv[], void *userdata) {
+ _cleanup_close_ int fd = -EBADF;
+ int r;
+
+ fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m");
+
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+ if (r < 0)
+ return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m");
+
+ _cleanup_(table_unrefp) Table *table = NULL;
+ table = table_new("version", "has kernel", "path");
+ if (!table)
+ return log_oom();
+
+ table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
+ table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
+
+ FOREACH_ARRAY(d, de->entries, de->n_entries) {
+
+ _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name);
+ if (!j)
+ return log_oom();
+
+ r = dirent_ensure_type(fd, *d);
+ if (r < 0) {
+ if (r != -ENOENT) /* don't log if just gone by now */
+ log_debug_errno(r, "Failed to check if '%s' is a directory, ignoring: %m", j);
+ continue;
+ }
+
+ if ((*d)->d_type != DT_DIR)
+ continue;
+
+ _cleanup_free_ char *fn = path_join((*d)->d_name, "vmlinuz");
+ if (!fn)
+ return log_oom();
+
+ bool exists;
+ if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno != ENOENT)
+ log_debug_errno(errno, "Failed to check if '/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", (*d)->d_name);
+
+ exists = false;
+ } else
+ exists = true;
+
+ r = table_add_many(table,
+ TABLE_STRING, (*d)->d_name,
+ TABLE_BOOLEAN_CHECKMARK, exists,
+ TABLE_SET_COLOR, ansi_highlight_green_red(exists),
+ TABLE_PATH, j);
+ if (r < 0)
+ return table_log_add_error(r);
+ }
+
+ return table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
+}
+
static int help(void) {
_cleanup_free_ char *link = NULL;
int r;
@@ -1192,13 +1494,15 @@ static int help(void) {
return log_oom();
printf("%1$s [OPTIONS...] COMMAND ...\n\n"
- "%2$sAdd and remove kernel and initrd images to and from /boot%3$s\n"
- "\nUsage:\n"
- " kernel-install [OPTIONS...] add KERNEL-VERSION KERNEL-IMAGE [INITRD ...]\n"
+ "%5$sAdd and remove kernel and initrd images to and from /boot/%6$s\n"
+ "\n%3$sUsage:%4$s\n"
+ " kernel-install [OPTIONS...] add [[[KERNEL-VERSION] KERNEL-IMAGE] [INITRD ...]]\n"
+ " kernel-install [OPTIONS...] add-all\n"
" kernel-install [OPTIONS...] remove KERNEL-VERSION\n"
- " kernel-install [OPTIONS...] inspect [KERNEL-VERSION] KERNEL-IMAGE [INITRD ...]\n"
- "\n"
- "Options:\n"
+ " kernel-install [OPTIONS...] inspect [[[KERNEL-VERSION] KERNEL-IMAGE]\n"
+ " [INITRD ...]]\n"
+ " kernel-install [OPTIONS...] list\n"
+ "\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -v --verbose Increase verbosity\n"
@@ -1210,6 +1514,7 @@ static int help(void) {
" Entry token to use for this installation\n"
" --no-pager Do not pipe inspect output into a pager\n"
" --json=pretty|short|off Generate JSON output\n"
+ " --no-legend Do not show the headers and footers\n"
" --root=PATH Operate on an alternate filesystem root\n"
" --image=PATH Operate on disk image as filesystem root\n"
" --image-policy=POLICY Specify disk image dissection policy\n"
@@ -1218,11 +1523,13 @@ static int help(void) {
" installkernel [OPTIONS...] VERSION VMLINUZ [MAP] [INSTALLATION-DIR]\n"
"(The optional arguments are passed by kernel build system, but ignored.)\n"
"\n"
- "See the %4$s for details.\n",
+ "See the %2$s for details.\n",
program_invocation_short_name,
- ansi_highlight(),
+ link,
+ ansi_underline(),
ansi_normal(),
- link);
+ ansi_highlight(),
+ ansi_normal());
return 0;
}
@@ -1230,6 +1537,7 @@ static int help(void) {
static int parse_argv(int argc, char *argv[], Context *c) {
enum {
ARG_VERSION = 0x100,
+ ARG_NO_LEGEND,
ARG_ESP_PATH,
ARG_BOOT_PATH,
ARG_MAKE_ENTRY_DIRECTORY,
@@ -1253,6 +1561,7 @@ static int parse_argv(int argc, char *argv[], Context *c) {
{ "root", required_argument, NULL, ARG_ROOT },
{ "image", required_argument, NULL, ARG_IMAGE },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{}
};
int t, r;
@@ -1269,6 +1578,10 @@ static int parse_argv(int argc, char *argv[], Context *c) {
case ARG_VERSION:
return version();
+ case ARG_NO_LEGEND:
+ arg_legend = false;
+ break;
+
case 'v':
log_set_max_level(LOG_DEBUG);
arg_verbose = true;
@@ -1347,9 +1660,11 @@ static int parse_argv(int argc, char *argv[], Context *c) {
static int run(int argc, char* argv[]) {
static const Verb verbs[] = {
- { "add", 3, VERB_ANY, 0, verb_add },
+ { "add", 1, VERB_ANY, 0, verb_add },
+ { "add-all", 1, 1, 0, verb_add_all },
{ "remove", 2, VERB_ANY, 0, verb_remove },
{ "inspect", 1, VERB_ANY, VERB_DEFAULT, verb_inspect },
+ { "list", 1, 1, 0, verb_list },
{}
};
_cleanup_(context_done) Context c = {
diff --git a/src/kernel-install/test-kernel-install.sh b/src/kernel-install/test-kernel-install.sh
index 931fae5372..db412ec524 100755
--- a/src/kernel-install/test-kernel-install.sh
+++ b/src/kernel-install/test-kernel-install.sh
@@ -71,7 +71,7 @@ grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
grep -qE 'DTBDTB' "$BOOT_ROOT/the-token/1.1.1/whatever.dtb"
-"$kernel_install" inspect
+test -f /usr/lib/modules/"$(uname -r)"/vmlinuz && "$kernel_install" inspect
"$kernel_install" inspect "$D/sources/linux"
"$kernel_install" -v remove 1.1.1