Merge pull request #29876 from poettering/kernel-install-all

add "kernel-install list" and "kernel-install add-all"
This commit is contained in:
Lennart Poettering
2023-11-07 18:29:47 +01:00
committed by GitHub
5 changed files with 445 additions and 54 deletions
-3
View File
@@ -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.
+68 -10
View File
@@ -44,6 +44,11 @@
<arg choice="opt"><replaceable>KERNEL-IMAGE</replaceable></arg>
<arg choice="opt" rep="repeat"><replaceable>INITRD-FILE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>kernel-install</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">list</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
@@ -81,15 +86,17 @@
<variablelist>
<varlistentry>
<term>
<command>add</command>
<replaceable>KERNEL-VERSION</replaceable>
<replaceable>KERNEL-IMAGE</replaceable> [<replaceable>INITRD-FILE</replaceable> ...]
<command>add [[[<replaceable>KERNEL-VERSION</replaceable>] <replaceable>KERNEL-IMAGE</replaceable>] [<replaceable>INITRD-FILE</replaceable> ...]]</command>
</term>
<listitem>
<para>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).</para>
<para>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 <command>uname -r</command> returns. If the latter is omitted,
specified as an empty string or as "-" defaults to
<filename>/usr/lib/modules/<replaceable>KERNEL_VERSION</replaceable>/vmlinuz</filename>. Optionally,
one or more initrd images may be specified as well (note that plugins might generate additional
ones).</para>
<para>The executable files from <filename>/usr/lib/kernel/install.d/*.install</filename> and
<filename>/etc/kernel/install.d/*.install</filename> (i.e. the plugins) are called with the
@@ -144,6 +151,20 @@
<xi:include href="version-info.xml" xpointer="v198"/>
</listitem>
</varlistentry>
<varlistentry>
<term>
<command>add-all</command>
</term>
<listitem>
<para>This is the same as <command>add</command> (see above), but invokes the operation iteratively
for every installed kernel in <filename>/usr/lib/modules/</filename>. This operation is only
supported on systems where the kernel image is installed in
<filename>/usr/lib/modules/<replaceable>KERNEL-VERSION</replaceable>/vmlinuz</filename>.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><command>remove <replaceable>KERNEL-VERSION</replaceable></command></term>
<listitem>
@@ -179,17 +200,28 @@
</varlistentry>
<varlistentry>
<term>
<command>inspect [[<replaceable>KERNEL-VERSION</replaceable>] <replaceable>KERNEL-IMAGE</replaceable>] [<replaceable>INITRD-FILE</replaceable> ...]</command>
<command>inspect [[[<replaceable>KERNEL-VERSION</replaceable>] <replaceable>KERNEL-IMAGE</replaceable>] [<replaceable>INITRD-FILE</replaceable> ...]]</command>
</term>
<listitem>
<para>Takes the same parameters as <command>add</command>.</para>
<para>Shows the various paths and parameters configured or auto-detected. In particular shows the
values of the various <varname>$KERNEL_INSTALL_*</varname> environment variables listed
below. The <option>--json</option> option can be used to get the output of this verb as a JSON
object.</para>
values of the various <varname>$KERNEL_INSTALL_*</varname> environment variables listed below, as
they would be passed to plugins. The <option>--json</option> option can be used to get the output
of this verb as a JSON object.</para>
<xi:include href="version-info.xml" xpointer="v251"/>
</listitem>
</varlistentry>
<varlistentry>
<term><command>list</command></term>
<listitem>
<para>Shows the various installed kernels. This enumerates the subdirectories of
<filename>/usr/lib/modules/</filename>, and shows whether a kernel image is installed there.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
@@ -332,6 +364,7 @@
<xi:include href="standard-options.xml" xpointer="no-pager"/>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="image-policy-open" />
<xi:include href="standard-options.xml" xpointer="no-legend"/>
</variablelist>
</refsect1>
@@ -641,6 +674,31 @@
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>/usr/lib/modules/<replaceable>KERNEL-VERSION/</replaceable></filename></term>
<listitem>
<para>Location for installed kernel modules and other kernel related resources. For each locally
installed kernel a directory named after the kernel version (<command>uname -r</command>) is
kept.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
<varlistentry>
<term><filename>/usr/lib/modules/<replaceable>KERNEL-VERSION/vmlinuz</replaceable></filename></term>
<listitem>
<para>Location for installed kernel images. This is the recommended location for OS package
managers to install kernel images into (as applicable), from which <command>kernel-install
add</command> then copies it into the final boot partition.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
</variablelist>
<para>For various cases listed above, if the <varname>$KERNEL_INSTALL_CONF_ROOT</varname> environment
+21
View File
@@ -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;
}
+355 -40
View File
@@ -2,11 +2,13 @@
#include <getopt.h>
#include <stdbool.h>
#include <sys/utsname.h>
#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, &copy.layout_other);
if (r < 0)
return r;
r = strdup_or_null(source->conf_root, &copy.conf_root);
if (r < 0)
return r;
r = strdup_or_null(source->boot_root, &copy.boot_root);
if (r < 0)
return r;
r = strdup_or_null(source->entry_token, &copy.entry_token);
if (r < 0)
return r;
r = strdup_or_null(source->entry_dir, &copy.entry_dir);
if (r < 0)
return r;
r = strdup_or_null(source->version, &copy.version);
if (r < 0)
return r;
r = strdup_or_null(source->kernel, &copy.kernel);
if (r < 0)
return r;
copy.initrds = strv_copy(source->initrds);
if (!copy.initrds)
return -ENOMEM;
r = strdup_or_null(source->initrd_generator, &copy.initrd_generator);
if (r < 0)
return r;
r = strdup_or_null(source->uki_generator, &copy.uki_generator);
if (r < 0)
return r;
r = strdup_or_null(source->staging_area, &copy.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, &copy);
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(&copy,
/* 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 = {
+1 -1
View File
@@ -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