summaryrefslogtreecommitdiffstats
path: root/src/kernel-install
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xsrc/kernel-install/50-depmod.install53
-rwxr-xr-xsrc/kernel-install/90-loaderentry.install165
-rw-r--r--src/kernel-install/install.conf11
-rwxr-xr-xsrc/kernel-install/kernel-install.in393
-rw-r--r--src/kernel-install/meson.build21
-rwxr-xr-xsrc/kernel-install/test-kernel-install.sh84
6 files changed, 727 insertions, 0 deletions
diff --git a/src/kernel-install/50-depmod.install b/src/kernel-install/50-depmod.install
new file mode 100755
index 0000000..88f858f
--- /dev/null
+++ b/src/kernel-install/50-depmod.install
@@ -0,0 +1,53 @@
+#!/bin/sh
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <https://www.gnu.org/licenses/>.
+
+set -e
+
+COMMAND="${1:?}"
+KERNEL_VERSION="${2:?}"
+
+[ -w "/lib/modules" ] || exit 0
+
+case "$COMMAND" in
+ add)
+ [ -d "/lib/modules/$KERNEL_VERSION/kernel" ] || exit 0
+ command -v depmod >/dev/null || exit 0
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "+depmod -a $KERNEL_VERSION"
+ exec depmod -a "$KERNEL_VERSION"
+ ;;
+ remove)
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
+ echo "Removing /lib/modules/${KERNEL_VERSION}/modules.dep and associated files"
+ exec rm -f \
+ "/lib/modules/$KERNEL_VERSION/modules.alias" \
+ "/lib/modules/$KERNEL_VERSION/modules.alias.bin" \
+ "/lib/modules/$KERNEL_VERSION/modules.builtin.bin" \
+ "/lib/modules/$KERNEL_VERSION/modules.builtin.alias.bin" \
+ "/lib/modules/$KERNEL_VERSION/modules.dep" \
+ "/lib/modules/$KERNEL_VERSION/modules.dep.bin" \
+ "/lib/modules/$KERNEL_VERSION/modules.devname" \
+ "/lib/modules/$KERNEL_VERSION/modules.softdep" \
+ "/lib/modules/$KERNEL_VERSION/modules.symbols" \
+ "/lib/modules/$KERNEL_VERSION/modules.symbols.bin"
+ ;;
+ *)
+ exit 0
+ ;;
+esac
diff --git a/src/kernel-install/90-loaderentry.install b/src/kernel-install/90-loaderentry.install
new file mode 100755
index 0000000..41a0553
--- /dev/null
+++ b/src/kernel-install/90-loaderentry.install
@@ -0,0 +1,165 @@
+#!/bin/sh
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <https://www.gnu.org/licenses/>.
+
+set -e
+
+COMMAND="${1:?}"
+KERNEL_VERSION="${2:?}"
+ENTRY_DIR_ABS="${3:?}"
+KERNEL_IMAGE="$4"
+INITRD_OPTIONS_SHIFT=4
+
+[ "$KERNEL_INSTALL_LAYOUT" = "bls" ] || exit 0
+
+MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID"
+ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN"
+BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT"
+
+BOOT_MNT="$(stat -c %m "$BOOT_ROOT")"
+if [ "$BOOT_MNT" = '/' ]; then
+ ENTRY_DIR="$ENTRY_DIR_ABS"
+else
+ ENTRY_DIR="${ENTRY_DIR_ABS#"$BOOT_MNT"}"
+fi
+
+case "$COMMAND" in
+ remove)
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
+ echo "Removing $BOOT_ROOT/loader/entries/$ENTRY_TOKEN-$KERNEL_VERSION*.conf"
+ exec rm -f \
+ "$BOOT_ROOT/loader/entries/$ENTRY_TOKEN-$KERNEL_VERSION.conf" \
+ "$BOOT_ROOT/loader/entries/$ENTRY_TOKEN-$KERNEL_VERSION+"*".conf"
+ ;;
+ add)
+ ;;
+ *)
+ exit 0
+ ;;
+esac
+
+if [ -f /etc/os-release ]; then
+ # shellcheck source=/dev/null
+ . /etc/os-release
+elif [ -f /usr/lib/os-release ]; then
+ # shellcheck source=/dev/null
+ . /usr/lib/os-release
+fi
+
+[ -n "$PRETTY_NAME" ] || PRETTY_NAME="Linux $KERNEL_VERSION"
+
+SORT_KEY="$IMAGE_ID"
+[ -z "$SORT_KEY" ] && SORT_KEY="$ID"
+
+if [ -n "$KERNEL_INSTALL_CONF_ROOT" ]; then
+ if [ -f "$KERNEL_INSTALL_CONF_ROOT/cmdline" ]; then
+ BOOT_OPTIONS="$(tr -s "$IFS" ' ' <"$KERNEL_INSTALL_CONF_ROOT/cmdline")"
+ fi
+elif [ -f /etc/kernel/cmdline ]; then
+ BOOT_OPTIONS="$(tr -s "$IFS" ' ' </etc/kernel/cmdline)"
+elif [ -f /usr/lib/kernel/cmdline ]; then
+ BOOT_OPTIONS="$(tr -s "$IFS" ' ' </usr/lib/kernel/cmdline)"
+else
+ BOOT_OPTIONS="$(tr -s "$IFS" '\n' </proc/cmdline | grep -ve '^BOOT_IMAGE=' -e '^initrd=' | tr '\n' ' ')"
+fi
+
+BOOT_OPTIONS="${BOOT_OPTIONS% }"
+
+# If the boot entries are named after the machine ID, then suffix the kernel
+# command line with the machine ID we use, so that the machine ID remains
+# stable, even during factory reset, in the initrd (where the system's machine
+# ID is not directly accessible yet), and if the root file system is volatile.
+if [ "$ENTRY_TOKEN" = "$MACHINE_ID" ] && ! echo "$BOOT_OPTIONS" | grep -q "systemd.machine_id="; then
+ BOOT_OPTIONS="$BOOT_OPTIONS systemd.machine_id=$MACHINE_ID"
+fi
+
+TRIES_FILE="${KERNEL_INSTALL_CONF_ROOT:-/etc/kernel}/tries"
+
+if [ -f "$TRIES_FILE" ]; then
+ read -r TRIES <"$TRIES_FILE"
+ if ! echo "$TRIES" | grep -q '^[0-9][0-9]*$'; then
+ echo "$TRIES_FILE does not contain an integer." >&2
+ exit 1
+ fi
+ LOADER_ENTRY="$BOOT_ROOT/loader/entries/$ENTRY_TOKEN-$KERNEL_VERSION+$TRIES.conf"
+else
+ LOADER_ENTRY="$BOOT_ROOT/loader/entries/$ENTRY_TOKEN-$KERNEL_VERSION.conf"
+fi
+
+if ! [ -d "$ENTRY_DIR_ABS" ]; then
+ echo "Error: entry directory '$ENTRY_DIR_ABS' does not exist" >&2
+ exit 1
+fi
+
+install -m 0644 "$KERNEL_IMAGE" "$ENTRY_DIR_ABS/linux" || {
+ echo "Error: could not copy '$KERNEL_IMAGE' to '$ENTRY_DIR_ABS/linux'." >&2
+ exit 1
+}
+chown root:root "$ENTRY_DIR_ABS/linux" || :
+
+shift "$INITRD_OPTIONS_SHIFT"
+# All files listed as arguments, and staged files starting with "initrd" are installed as initrds.
+for initrd in "$@" "${KERNEL_INSTALL_STAGING_AREA}"/initrd*; do
+ [ -f "$initrd" ] || {
+ [ "$initrd" = "${KERNEL_INSTALL_STAGING_AREA}/initrd*" ] && continue
+ echo "Error: initrd '$initrd' not a file." >&2
+ exit 1
+ }
+
+ initrd_basename="${initrd##*/}"
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $ENTRY_DIR_ABS/$initrd_basename"
+ install -m 0644 "$initrd" "$ENTRY_DIR_ABS/$initrd_basename" || {
+ echo "Error: could not copy '$initrd' to '$ENTRY_DIR_ABS/$initrd_basename'." >&2
+ exit 1
+ }
+ chown root:root "$ENTRY_DIR_ABS/$initrd_basename" || :
+done
+
+mkdir -p "${LOADER_ENTRY%/*}" || {
+ echo "Error: could not create loader entry directory '${LOADER_ENTRY%/*}'." >&2
+ exit 1
+}
+
+[ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Creating $LOADER_ENTRY"
+{
+ echo "title $PRETTY_NAME"
+ echo "version $KERNEL_VERSION"
+ if [ "$ENTRY_TOKEN" = "$MACHINE_ID" ]; then
+ # See similar logic above for the systemd.machine_id= kernel command line option
+ echo "machine-id $MACHINE_ID"
+ fi
+ [ -n "$SORT_KEY" ] && echo "sort-key $SORT_KEY"
+ echo "options $BOOT_OPTIONS"
+ echo "linux $ENTRY_DIR/linux"
+
+ have_initrd=
+ for initrd in "${@}" "${KERNEL_INSTALL_STAGING_AREA}"/initrd*; do
+ [ -f "$initrd" ] || continue
+ echo "initrd $ENTRY_DIR/${initrd##*/}"
+ have_initrd=yes
+ done
+
+ # Try "initrd", generated by dracut in its kernel-install hook, if no initrds were supplied
+ [ -z "$have_initrd" ] && [ -f "$ENTRY_DIR_ABS/initrd" ] && echo "initrd $ENTRY_DIR/initrd"
+ :
+} >"$LOADER_ENTRY" || {
+ echo "Error: could not create loader entry '$LOADER_ENTRY'." >&2
+ exit 1
+}
+exit 0
diff --git a/src/kernel-install/install.conf b/src/kernel-install/install.conf
new file mode 100644
index 0000000..43b6e7d
--- /dev/null
+++ b/src/kernel-install/install.conf
@@ -0,0 +1,11 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# See kernel-install(8) for details.
+
+#layout=bls|other|...
+#initrd_generator=dracut|...
diff --git a/src/kernel-install/kernel-install.in b/src/kernel-install/kernel-install.in
new file mode 100755
index 0000000..fa2c0d5
--- /dev/null
+++ b/src/kernel-install/kernel-install.in
@@ -0,0 +1,393 @@
+#!/bin/sh
+# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
+# ex: ts=8 sw=4 sts=4 et filetype=sh
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <https://www.gnu.org/licenses/>.
+
+skip_remaining=77
+
+set -e
+
+usage()
+{
+ echo "Usage:"
+ echo " kernel-install [OPTIONS...] add KERNEL-VERSION KERNEL-IMAGE [INITRD-FILE...]"
+ echo " kernel-install [OPTIONS...] remove KERNEL-VERSION"
+ echo " kernel-install [OPTIONS...] inspect"
+ echo "Options:"
+ echo " -h, --help Print this help and exit"
+ echo " --version Print version string and exit"
+ echo " -v, --verbose Increase verbosity"
+}
+
+dropindirs_sort()
+{
+ suffix="$1"
+ shift
+
+ for d; do
+ for i in "$d/"*"$suffix"; do
+ [ -e "$i" ] && echo "${i##*/}"
+ done
+ done | sort -Vu | while read -r f; do
+ for d; do
+ if [ -e "$d/$f" ]; then
+ [ -x "$d/$f" ] && echo "$d/$f"
+ continue 2
+ fi
+ done
+ done
+}
+
+export LC_COLLATE=C
+
+for i; do
+ if [ "$i" = "--help" ] || [ "$i" = "-h" ]; then
+ usage
+ exit 0
+ fi
+done
+
+for i; do
+ if [ "$i" = "--version" ]; then
+ echo "kernel-install {{PROJECT_VERSION}} ({{GIT_VERSION}})"
+ exit 0
+ fi
+done
+
+if [ "$KERNEL_INSTALL_BYPASS" = "1" ]; then
+ echo "kernel-install: Skipping execution because KERNEL_INSTALL_BYPASS=1"
+ exit 0
+fi
+
+export KERNEL_INSTALL_VERBOSE=0
+if [ "$1" = "--verbose" ] || [ "$1" = "-v" ]; then
+ shift
+ export KERNEL_INSTALL_VERBOSE=1
+ log_verbose() { printf "%s\n" "$*"; }
+else
+ log_verbose() { :; }
+fi
+
+if [ "${0##*/}" = "installkernel" ]; then
+ COMMAND=add
+ # kernel's install.sh invokes us as
+ # /sbin/installkernel <version> <vmlinuz> <map> <installation-dir>
+ # We ignore the last two arguments.
+ set -- "${1:?}" "${2:?}"
+else
+ COMMAND="$1"
+ [ $# -ge 1 ] && shift
+fi
+
+if [ "$COMMAND" = "inspect" ]; then
+ KERNEL_VERSION=""
+else
+ if [ $# -lt 1 ]; then
+ echo "Error: not enough arguments" >&2
+ exit 1
+ fi
+
+ KERNEL_VERSION="$1"
+ shift
+fi
+
+# These two settings are only settable via install.conf
+layout=
+initrd_generator=
+# These two settings can be inherited from the environment
+_MACHINE_ID_SAVED="$MACHINE_ID"
+_BOOT_ROOT_SAVED="$BOOT_ROOT"
+
+if [ -n "$KERNEL_INSTALL_CONF_ROOT" ]; then
+ install_conf="$KERNEL_INSTALL_CONF_ROOT/install.conf"
+elif [ -f "/etc/kernel/install.conf" ]; then
+ install_conf="/etc/kernel/install.conf"
+elif [ -f "/usr/lib/kernel/install.conf" ]; then
+ install_conf="/usr/lib/kernel/install.conf"
+else
+ install_conf=
+fi
+
+if [ -f "$install_conf" ]; then
+ log_verbose "Reading $install_conf…"
+ # shellcheck source=/dev/null
+ . "$install_conf"
+fi
+
+[ -n "$layout" ] && log_verbose "$install_conf configures layout=$layout"
+[ -n "$initrd_generator" ] && \
+ log_verbose "$install_conf configures initrd_generator=$initrd_generator"
+
+if [ -n "$_MACHINE_ID_SAVED" ]; then
+ MACHINE_ID="$_MACHINE_ID_SAVED"
+ log_verbose "MACHINE_ID=$MACHINE_ID set via environment"
+else
+ [ -n "$MACHINE_ID" ] && log_verbose "MACHINE_ID=$MACHINE_ID set via install.conf"
+fi
+
+if [ -n "$_BOOT_ROOT_SAVED" ]; then
+ BOOT_ROOT="$_BOOT_ROOT_SAVED"
+ log_verbose "BOOT_ROOT=$BOOT_ROOT set via environment"
+else
+ [ -n "$BOOT_ROOT" ] && log_verbose "BOOT_ROOT=$BOOT_ROOT set via install.conf"
+fi
+
+# If /etc/machine-id is initialized we'll use it, otherwise we'll use a freshly
+# generated one. If the user configured an explicit machine ID to use in
+# /etc/machine-info to use for our purpose, we'll use that instead (for
+# compatibility).
+# shellcheck source=/dev/null
+if [ -z "$MACHINE_ID" ] && [ -f /etc/machine-info ]; then
+ . /etc/machine-info
+ MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID"
+ [ -n "$MACHINE_ID" ] && \
+ log_verbose "machine-id $MACHINE_ID acquired from /etc/machine-info"
+fi
+if [ -z "$MACHINE_ID" ] && [ -s /etc/machine-id ]; then
+ read -r MACHINE_ID </etc/machine-id
+ [ "$MACHINE_ID" = "uninitialized" ] && unset MACHINE_ID
+ [ -n "$MACHINE_ID" ] && \
+ log_verbose "machine-id $MACHINE_ID acquired from /etc/machine-id"
+fi
+if [ -z "$MACHINE_ID" ]; then
+ MACHINE_ID="$(systemd-id128 new)" || exit 1
+ log_verbose "new machine-id $MACHINE_ID generated"
+fi
+
+# Now that we determined the machine ID to use, let's determine the "token" for
+# the boot loader entry to generate. We use that for naming the directory below
+# $BOOT where we want to place the kernel/initrd and related resources, as well
+# for naming the .conf boot loader spec entry. Typically this is just the
+# machine ID, but it can be anything else, too, if we are told so.
+ENTRY_TOKEN_FILE="${KERNEL_INSTALL_CONF_ROOT:-/etc/kernel}/entry-token"
+
+if [ -z "$ENTRY_TOKEN" ] && [ -f "$ENTRY_TOKEN_FILE" ]; then
+ read -r ENTRY_TOKEN <"$ENTRY_TOKEN_FILE"
+ log_verbose "entry-token \"$ENTRY_TOKEN\" acquired from $ENTRY_TOKEN_FILE"
+fi
+if [ -z "$ENTRY_TOKEN" ]; then
+ # If not configured explicitly, then use a few candidates: the machine ID,
+ # the IMAGE_ID= and ID= fields from /etc/os-release and finally the fixed
+ # string "Default"
+ ENTRY_TOKEN_SEARCH="$MACHINE_ID"
+ # shellcheck source=/dev/null
+ [ -f /etc/os-release ] && . /etc/os-release
+ [ -n "$IMAGE_ID" ] && ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN_SEARCH $IMAGE_ID"
+ [ -n "$ID" ] && ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN_SEARCH $ID"
+ ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN_SEARCH Default"
+else
+ ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN"
+fi
+log_verbose "Entry-token candidates: $ENTRY_TOKEN_SEARCH"
+
+# NB: The $MACHINE_ID is guaranteed to be a valid machine ID, but
+# $ENTRY_TOKEN can be any string that fits into a VFAT filename, though
+# typically is just the machine ID.
+
+if [ -n "$BOOT_ROOT" ]; then
+ # If this was already configured, don't try to guess
+ BOOT_ROOT_SEARCH="$BOOT_ROOT"
+else
+ BOOT_ROOT_SEARCH="/efi /boot /boot/efi"
+fi
+
+for pref in $BOOT_ROOT_SEARCH; do
+ for suff in $ENTRY_TOKEN_SEARCH; do
+ if [ -d "$pref/$suff" ]; then
+ [ -z "$BOOT_ROOT" ] && BOOT_ROOT="$pref"
+ [ -z "$ENTRY_TOKEN" ] && ENTRY_TOKEN="$suff"
+
+ log_verbose "$pref/$suff exists, using BOOT_ROOT=$BOOT_ROOT, ENTRY_TOKEN=$ENTRY_TOKEN"
+ break 2
+ else
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "$pref/$suff not found…"
+ fi
+
+ if [ -d "$pref/loader/entries" ]; then
+ [ -z "$BOOT_ROOT" ] && BOOT_ROOT="$pref"
+ log_verbose "$pref/loader/entries exists, using BOOT_ROOT=$BOOT_ROOT"
+ break 2
+ else
+ log_verbose "$pref/loader/entries not found…"
+ fi
+ done
+done
+
+[ -z "$BOOT_ROOT" ] && for pref in "/efi" "/boot/efi"; do
+ if mountpoint -q "$pref"; then
+ BOOT_ROOT="$pref"
+ log_verbose "$pref is a mount point, using BOOT_ROOT=$BOOT_ROOT"
+ break
+ else
+ log_verbose "$pref is not a mount point…"
+ fi
+done
+
+if [ -z "$BOOT_ROOT" ]; then
+ BOOT_ROOT="/boot"
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
+ echo "KERNEL_INSTALL_BOOT_ROOT autodection yielded no candidates, using \"$BOOT_ROOT\""
+fi
+
+if [ -z "$ENTRY_TOKEN" ]; then
+ ENTRY_TOKEN="$MACHINE_ID"
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
+ echo "No entry-token candidate matched, using \"$ENTRY_TOKEN\" from machine-id"
+fi
+
+if [ -z "$layout" ]; then
+ # No layout configured by the administrator. Let's try to figure it out
+ # automatically from metadata already contained in $BOOT_ROOT.
+ if [ -e "$BOOT_ROOT/loader/entries.srel" ]; then
+ read -r ENTRIES_SREL <"$BOOT_ROOT/loader/entries.srel"
+ if [ "$ENTRIES_SREL" = "type1" ]; then
+ # The loader/entries.srel file clearly indicates that the installed
+ # boot loader implements the proper standard upstream boot loader
+ # spec for Type #1 entries. Let's default to that, then.
+ layout="bls"
+ else
+ # The loader/entries.srel file indicates some other spec is
+ # implemented and owns the /loader/entries/ directory. Since we
+ # have no idea what that means, let's stay away from it by default.
+ layout="other"
+ fi
+ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
+ echo "$BOOT_ROOT/loader/entries.srel with '$ENTRIES_SREL' found, using layout=$layout"
+
+ elif [ -d "$BOOT_ROOT/$ENTRY_TOKEN" ]; then
+ # If the metadata in $BOOT_ROOT doesn't tell us anything, then check if
+ # the entry token directory already exists. If so, let's assume it's
+ # the standard boot loader spec, too.
+ layout="bls"
+
+ log_verbose "$BOOT_ROOT/$ENTRY_TOKEN exists, using layout=$layout"
+ else
+ # There's no metadata in $BOOT_ROOT, and apparently no entry token
+ # directory installed? Then we really don't know anything.
+ layout="other"
+
+ log_verbose "Entry-token directory not found, using layout=$layout"
+ fi
+fi
+
+ENTRY_DIR_ABS="$BOOT_ROOT/$ENTRY_TOKEN/$KERNEL_VERSION"
+log_verbose "Using ENTRY_DIR_ABS=$ENTRY_DIR_ABS"
+
+# Provide a directory where to store generated initrds
+cleanup() {
+ [ -n "$KERNEL_INSTALL_STAGING_AREA" ] && rm -rf "$KERNEL_INSTALL_STAGING_AREA"
+}
+
+trap cleanup EXIT
+
+KERNEL_INSTALL_STAGING_AREA="$(mktemp -d -t kernel-install.staging.XXXXXXX)"
+
+export KERNEL_INSTALL_MACHINE_ID="$MACHINE_ID"
+export KERNEL_INSTALL_ENTRY_TOKEN="$ENTRY_TOKEN"
+export KERNEL_INSTALL_BOOT_ROOT="$BOOT_ROOT"
+export KERNEL_INSTALL_LAYOUT="$layout"
+export KERNEL_INSTALL_INITRD_GENERATOR="$initrd_generator"
+export KERNEL_INSTALL_STAGING_AREA
+
+MAKE_ENTRY_DIR_ABS=0
+[ "$layout" = "bls" ] || MAKE_ENTRY_DIR_ABS=1
+
+ret=0
+
+if [ -z "$KERNEL_INSTALL_PLUGINS" ]; then
+ KERNEL_INSTALL_PLUGINS="$(
+ dropindirs_sort ".install" \
+ "/etc/kernel/install.d" \
+ "/usr/lib/kernel/install.d"
+ )"
+fi
+
+if [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ]; then
+ printf '%s\n' "Plugin files:"
+ for f in $KERNEL_INSTALL_PLUGINS; do
+ printf '%s\n' "$f"
+ done
+fi
+
+case "$COMMAND" in
+ add)
+ if [ $# -lt 1 ]; then
+ echo "Error: command 'add' requires a kernel image" >&2
+ exit 1
+ fi
+
+ if ! [ -f "$1" ]; then
+ echo "Error: kernel image argument $1 not a file" >&2
+ exit 1
+ fi
+
+ if [ "$MAKE_ENTRY_DIR_ABS" -eq 0 ]; then
+ # Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN
+ # to signal to 00-entry-directory to create $ENTRY_DIR_ABS
+ # to serve as the indication to use or to not use the BLS
+ if [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ]; then
+ echo "+mkdir -v -p $ENTRY_DIR_ABS"
+ mkdir -v -p "$ENTRY_DIR_ABS" || exit 1
+ else
+ mkdir -p "$ENTRY_DIR_ABS" || exit 1
+ fi
+ fi
+
+ for f in $KERNEL_INSTALL_PLUGINS; do
+ log_verbose "+$f add $KERNEL_VERSION $ENTRY_DIR_ABS" "$@"
+ err=0
+ "$f" add "$KERNEL_VERSION" "$ENTRY_DIR_ABS" "$@" || err=$?
+ [ $err -eq $skip_remaining ] && break
+ [ $err -ne 0 ] && exit $err
+ done
+ ;;
+
+ remove)
+ for f in $KERNEL_INSTALL_PLUGINS; do
+ log_verbose "+$f remove $KERNEL_VERSION $ENTRY_DIR_ABS"
+ err=0
+ "$f" remove "$KERNEL_VERSION" "$ENTRY_DIR_ABS" || err=$?
+ [ $err -eq $skip_remaining ] && break
+ [ $err -ne 0 ] && exit $err
+ done
+
+ if [ "$MAKE_ENTRY_DIR_ABS" -eq 0 ]; then
+ log_verbose "Removing $ENTRY_DIR_ABS/"
+ rm -rf "$ENTRY_DIR_ABS"
+ fi
+ ;;
+
+ inspect)
+ echo "KERNEL_INSTALL_MACHINE_ID: $KERNEL_INSTALL_MACHINE_ID"
+ echo "KERNEL_INSTALL_ENTRY_TOKEN: $KERNEL_INSTALL_ENTRY_TOKEN"
+ echo "KERNEL_INSTALL_BOOT_ROOT: $KERNEL_INSTALL_BOOT_ROOT"
+ echo "KERNEL_INSTALL_LAYOUT: $KERNEL_INSTALL_LAYOUT"
+ echo "KERNEL_INSTALL_INITRD_GENERATOR: $KERNEL_INSTALL_INITRD_GENERATOR"
+ echo "ENTRY_DIR_ABS: $KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN/\$KERNEL_VERSION"
+
+ # Assert that ENTRY_DIR_ABS actually matches what we are printing here
+ [ "${ENTRY_DIR_ABS%/*}" = "$KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN" ] || { echo "Assertion didn't pass." >&2; exit 1; }
+ ;;
+
+ *)
+ echo "Error: unknown command '$COMMAND'" >&2
+ exit 1
+ ;;
+esac
+
+exit "$ret"
diff --git a/src/kernel-install/meson.build b/src/kernel-install/meson.build
new file mode 100644
index 0000000..90a0e3a
--- /dev/null
+++ b/src/kernel-install/meson.build
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+kernel_install_in = files('kernel-install.in')
+loaderentry_install = files('90-loaderentry.install')
+
+if want_kernel_install
+ install_data('50-depmod.install',
+ loaderentry_install,
+ install_mode : 'rwxr-xr-x',
+ install_dir : kernelinstalldir)
+
+ install_data('install.conf',
+ install_dir : kerneldir)
+
+ if install_sysconfdir
+ meson.add_install_script('sh', '-c',
+ mkdir_p.format(sysconfdir / 'kernel/install.d'))
+ endif
+
+ test_kernel_install_sh = find_program('test-kernel-install.sh')
+endif
diff --git a/src/kernel-install/test-kernel-install.sh b/src/kernel-install/test-kernel-install.sh
new file mode 100755
index 0000000..2e44063
--- /dev/null
+++ b/src/kernel-install/test-kernel-install.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eu
+set -o pipefail
+
+kernel_install="${1:?}"
+plugin="${2:?}"
+
+D="$(mktemp --tmpdir --directory "test-kernel-install.XXXXXXXXXX")"
+
+# shellcheck disable=SC2064
+trap "rm -rf '$D'" EXIT INT QUIT PIPE
+mkdir -p "$D/boot"
+mkdir -p "$D/efi"
+mkdir -p "$D/sources"
+
+echo 'buzy image' >"$D/sources/linux"
+echo 'the initrd' >"$D/sources/initrd"
+echo 'the-token' >"$D/sources/entry-token"
+echo 'opt1 opt2' >"$D/sources/cmdline"
+
+cat >"$D/sources/install.conf" <<EOF
+layout=bls
+initrd_generator=none
+# those are overridden by envvars
+BOOT_ROOT="$D/badboot"
+MACHINE_ID=badbadbadbadbadbad6abadbadbadbad
+EOF
+
+export KERNEL_INSTALL_CONF_ROOT="$D/sources"
+export KERNEL_INSTALL_PLUGINS="$plugin"
+export BOOT_ROOT="$D/boot"
+export MACHINE_ID='3e0484f3634a418b8e6a39e8828b03e3'
+
+"$kernel_install" -v add 1.1.1 "$D/sources/linux" "$D/sources/initrd"
+
+entry="$BOOT_ROOT/loader/entries/the-token-1.1.1.conf"
+test -f "$entry"
+grep -qE '^title ' "$entry"
+grep -qE '^version +1.1.1' "$entry"
+grep -qE '^options +opt1 opt2' "$entry"
+grep -qE '^linux .*/the-token/1.1.1/linux' "$entry"
+grep -qE '^initrd .*/the-token/1.1.1/initrd' "$entry"
+
+grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
+grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
+
+"$kernel_install" inspect
+
+"$kernel_install" -v remove 1.1.1
+test ! -f "$entry"
+test ! -f "$BOOT_ROOT/the-token/1.1.1/linux"
+test ! -f "$BOOT_ROOT/the-token/1.1.1/initrd"
+
+# Invoke kernel-install as installkernel
+ln -s --relative -v "$kernel_install" "$D/sources/installkernel"
+"$D/sources/installkernel" -v 1.1.2 "$D/sources/linux" System.map /somedirignored
+
+entry="$BOOT_ROOT/loader/entries/the-token-1.1.2.conf"
+test -f "$entry"
+grep -qE '^title ' "$entry"
+grep -qE '^version +1.1.2' "$entry"
+grep -qE '^options +opt1 opt2' "$entry"
+grep -qE '^linux .*/the-token/1.1.2/linux' "$entry"
+( ! grep -qE '^initrd' "$entry" )
+
+grep -qE 'image' "$BOOT_ROOT/the-token/1.1.2/linux"
+test ! -e "$BOOT_ROOT/the-token/1.1.2/initrd"
+
+# Check installation with boot counting
+echo '56' >"$D/sources/tries"
+
+"$kernel_install" -v add 1.1.1 "$D/sources/linux" "$D/sources/initrd"
+entry="$BOOT_ROOT/loader/entries/the-token-1.1.1+56.conf"
+test -f "$entry"
+grep -qE '^title ' "$entry"
+grep -qE '^version +1.1.1' "$entry"
+grep -qE '^options +opt1 opt2' "$entry"
+grep -qE '^linux .*/the-token/1.1.1/linux' "$entry"
+grep -qE '^initrd .*/the-token/1.1.1/initrd' "$entry"
+
+grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
+grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"