1#!/bin/sh
2# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
3# ex: ts=8 sw=4 sts=4 et filetype=sh
4# SPDX-License-Identifier: LGPL-2.1-or-later
5#
6# This file is part of systemd.
7#
8# systemd is free software; you can redistribute it and/or modify it
9# under the terms of the GNU Lesser General Public License as published by
10# the Free Software Foundation; either version 2.1 of the License, or
11# (at your option) any later version.
12#
13# systemd is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16# General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with systemd; If not, see <http://www.gnu.org/licenses/>.
20
21skip_remaining=77
22
23usage()
24{
25    echo "Usage:"
26    echo "  kernel-install [OPTIONS...] add KERNEL-VERSION KERNEL-IMAGE [INITRD-FILE...]"
27    echo "  kernel-install [OPTIONS...] remove KERNEL-VERSION"
28    echo "  kernel-install [OPTIONS...] inspect"
29    echo "Options:"
30    echo "  -h, --help     Print this help and exit"
31    echo "      --version  Print version string and exit"
32    echo "  -v, --verbose  Increase verbosity"
33}
34
35dropindirs_sort()
36{
37    suffix="$1"
38    shift
39
40    for d; do
41        for i in "$d/"*"$suffix"; do
42            [ -e "$i" ] && echo "${i##*/}"
43        done
44    done | sort -Vu | while read -r f; do
45        for d; do
46            if [ -e "$d/$f" ]; then
47                [ -x "$d/$f" ] && echo "$d/$f"
48                continue 2
49            fi
50        done
51    done
52}
53
54export LC_COLLATE=C
55
56for i; do
57    if [ "$i" = "--help" ] || [ "$i" = "-h" ]; then
58        usage
59        exit 0
60    fi
61done
62
63for i; do
64    if [ "$i" = "--version" ]; then
65        echo "kernel-install {{PROJECT_VERSION}} ({{GIT_VERSION}})"
66        exit 0
67    fi
68done
69
70if [ "$KERNEL_INSTALL_BYPASS" = "1" ]; then
71    echo "kernel-install: Skipping execution because KERNEL_INSTALL_BYPASS=1"
72    exit 0
73fi
74
75export KERNEL_INSTALL_VERBOSE=0
76if [ "$1" = "--verbose" ] || [ "$1" = "-v" ]; then
77    shift
78    export KERNEL_INSTALL_VERBOSE=1
79fi
80
81if [ "${0##*/}" = "installkernel" ]; then
82    COMMAND=add
83    # make install doesn't pass any initrds
84else
85    COMMAND="$1"
86    [ $# -ge 1 ] && shift
87fi
88
89if [ "$COMMAND" = "inspect" ]; then
90    KERNEL_VERSION=""
91else
92    if [ $# -lt 1 ]; then
93        echo "Error: not enough arguments" >&2
94        exit 1
95    fi
96
97    KERNEL_VERSION="$1"
98    shift
99fi
100
101# These two settings are settable in install.conf
102layout=
103initrd_generator=
104
105if [ -r "/etc/kernel/install.conf" ]; then
106    install_conf="/etc/kernel/install.conf"
107elif [ -r "/usr/lib/kernel/install.conf" ]; then
108    install_conf="/usr/lib/kernel/install.conf"
109else
110    install_conf=
111fi
112
113if [ -n "$install_conf" ]; then
114    [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Reading $install_conf…"
115    . "$install_conf"
116    # FIXME: This may override configuration in environment variables, e.g. $BOOT_ROOT.
117fi
118
119[ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && [ -n "$layout" ] && \
120    echo "$install_conf configures layout=$layout"
121[ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && [ -n "$initrd_generator" ] && \
122    echo "$install_conf configures initrd_generator=$initrd_generator"
123
124[ -n "$MACHINE_ID" ] && [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
125    echo "machine-id $MACHINE_ID set via environment or install.conf"
126[ -n "$BOOT_ROOT" ] && [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
127    echo "BOOT_ROOT=$BOOT_ROOT set via environment or install.conf"
128
129# If /etc/machine-id is initialized we'll use it, otherwise we'll use a freshly
130# generated one. If the user configured an explicit machine ID to use in
131# /etc/machine-info to use for our purpose, we'll use that instead (for
132# compatibility).
133if [ -z "$MACHINE_ID" ] && [ -r /etc/machine-info ] && . /etc/machine-info && MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID"; then
134    [ -n "$MACHINE_ID" ] && [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
135        echo "machine-id $MACHINE_ID acquired from /etc/machine-info"
136fi
137if [ -z "$MACHINE_ID" ] && [ -r /etc/machine-id ] && read -r MACHINE_ID </etc/machine-id; then
138    [ -n "$MACHINE_ID" ] && [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
139        echo "machine-id $MACHINE_ID acquired from /etc/machine-id"
140fi
141if [ -z "$MACHINE_ID" ]; then
142    MACHINE_ID="$(systemd-id128 new)" || exit 1
143    [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "new machine-id $MACHINE_ID generated"
144fi
145
146# Now that we determined the machine ID to use, let's determine the "token" for
147# the boot loader entry to generate. We use that for naming the directory below
148# $BOOT where we want to place the kernel/initrd and related resources, as well
149# for naming the .conf boot loader spec entry. Typically this is just the
150# machine ID, but it can be anything else, too, if we are told so.
151if [ -z "$ENTRY_TOKEN" ] && [ -r /etc/kernel/entry-token ] && read -r ENTRY_TOKEN </etc/kernel/entry-token; then
152    [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
153        echo "entry-token \"$ENTRY_TOKEN\" acquired from /etc/kernel/entry-token"
154fi
155if [ -z "$ENTRY_TOKEN" ]; then
156    # If not configured explicitly, then use a few candidates: the machine ID,
157    # the IMAGE_ID= and ID= fields from /etc/os-release and finally the fixed
158    # string "Default"
159    ENTRY_TOKEN_SEARCH="$MACHINE_ID"
160    [ -r /etc/os-release ] && . /etc/os-release
161    [ -n "$IMAGE_ID" ] && ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN_SEARCH $IMAGE_ID"
162    [ -n "$ID" ] && ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN_SEARCH $ID"
163    ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN_SEARCH Default"
164else
165    ENTRY_TOKEN_SEARCH="$ENTRY_TOKEN"
166fi
167[ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Entry-token candidates: $ENTRY_TOKEN_SEARCH"
168
169# NB: The $MACHINE_ID is guaranteed to be a valid machine ID, but
170#     $ENTRY_TOKEN can be any string that fits into a VFAT filename, though
171#     typically is just the machine ID.
172
173if [ -n "$BOOT_ROOT" ]; then
174    # If this was already configured, don't try to guess
175    BOOT_ROOT_SEARCH="$BOOT_ROOT"
176else
177    BOOT_ROOT_SEARCH="/efi /boot /boot/efi"
178fi
179
180for pref in $BOOT_ROOT_SEARCH; do
181    for suff in $ENTRY_TOKEN_SEARCH; do
182        if [ -d "$pref/$suff" ]; then
183            [ -z "$BOOT_ROOT" ] && BOOT_ROOT="$pref"
184            [ -z "$ENTRY_TOKEN" ] && ENTRY_TOKEN="$suff"
185
186            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
187                echo "$pref/$suff exists, using BOOT_ROOT=$BOOT_ROOT, ENTRY_TOKEN=$ENTRY_TOKEN"
188            break 2
189        else
190            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "$pref/$suff not found…"
191        fi
192
193        if [ -d "$pref/loader/entries" ]; then
194            [ -z "$BOOT_ROOT" ] && BOOT_ROOT="$pref"
195            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
196                echo "$pref/loader/entries exists, using BOOT_ROOT=$BOOT_ROOT"
197            break 2
198        else
199            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "$pref/loader/entries not found…"
200        fi
201    done
202done
203
204[ -z "$BOOT_ROOT" ] && for pref in "/efi" "/boot/efi"; do
205    if mountpoint -q "$pref"; then
206        BOOT_ROOT="$pref"
207        [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
208            echo "$pref is a mount point, using BOOT_ROOT=$BOOT_ROOT"
209        break
210    else
211        [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "$pref is not a mount point…"
212    fi
213done
214
215if [ -z "$BOOT_ROOT" ]; then
216    BOOT_ROOT="/boot"
217    [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
218        echo "KERNEL_INSTALL_BOOT_ROOT autodection yielded no candidates, using \"$BOOT_ROOT\""
219fi
220
221if [ -z "$ENTRY_TOKEN" ]; then
222    ENTRY_TOKEN="$MACHINE_ID"
223    [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
224        echo "No entry-token candidate matched, using \"$ENTRY_TOKEN\" from machine-id"
225fi
226
227if [ -z "$layout" ]; then
228    # No layout configured by the administrator. Let's try to figure it out
229    # automatically from metadata already contained in $BOOT_ROOT.
230    if [ -e "$BOOT_ROOT/loader/entries.srel" ]; then
231        read -r ENTRIES_SREL <"$BOOT_ROOT/loader/entries.srel"
232        if [ "$ENTRIES_SREL" = "type1" ]; then
233            # The loader/entries.srel file clearly indicates that the installed
234            # boot loader implements the proper standard upstream boot loader
235            # spec for Type #1 entries. Let's default to that, then.
236            layout="bls"
237        else
238            # The loader/entries.srel file indicates some other spec is
239            # implemented and owns the /loader/entries/ directory. Since we
240            # have no idea what that means, let's stay away from it by default.
241            layout="other"
242        fi
243        [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \
244            echo "$BOOT_ROOT/loader/entries.srel with '$ENTRIES_SREL' found, using layout=$layout"
245
246    elif [ -d "$BOOT_ROOT/$ENTRY_TOKEN" ]; then
247        # If the metadata in $BOOT_ROOT doesn't tell us anything, then check if
248        # the entry token directory already exists. If so, let's assume it's
249        # the standard boot loader spec, too.
250        layout="bls"
251
252        [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "$BOOT_ROOT/$ENTRY_TOKEN exists, using layout=$layout"
253    else
254        # There's no metadata in $BOOT_ROOT, and apparently no entry token
255        # directory installed? Then we really don't know anything.
256        layout="other"
257
258        [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Entry-token directory not found, using layout=$layout"
259    fi
260fi
261
262ENTRY_DIR_ABS="$BOOT_ROOT/$ENTRY_TOKEN/$KERNEL_VERSION"
263[ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Using ENTRY_DIR_ABS=$ENTRY_DIR_ABS"
264
265# Provide a directory where to store generated initrds
266cleanup() {
267    [ -n "$KERNEL_INSTALL_STAGING_AREA" ] && rm -rf "$KERNEL_INSTALL_STAGING_AREA"
268}
269
270trap cleanup EXIT
271
272KERNEL_INSTALL_STAGING_AREA="$(mktemp -d -t kernel-install.staging.XXXXXXX)"
273
274export KERNEL_INSTALL_MACHINE_ID="$MACHINE_ID"
275export KERNEL_INSTALL_ENTRY_TOKEN="$ENTRY_TOKEN"
276export KERNEL_INSTALL_BOOT_ROOT="$BOOT_ROOT"
277export KERNEL_INSTALL_LAYOUT="$layout"
278export KERNEL_INSTALL_INITRD_GENERATOR="$initrd_generator"
279export KERNEL_INSTALL_STAGING_AREA
280
281[ "$layout" = "bls" ]
282MAKE_ENTRY_DIR_ABS=$?
283
284ret=0
285
286PLUGINS="$(
287    dropindirs_sort ".install" \
288        "/etc/kernel/install.d" \
289        "/usr/lib/kernel/install.d"
290)"
291IFS="
292"
293
294[ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo -e "Plugin files:\n$PLUGINS"
295
296case "$COMMAND" in
297    add)
298        if [ $# -lt 1 ]; then
299            echo "Error: command 'add' requires a kernel image" >&2
300            exit 1
301        fi
302
303        if ! [ -f "$1" ]; then
304            echo "Error: kernel image argument $1 not a file" >&2
305            exit 1
306        fi
307
308        if [ "$MAKE_ENTRY_DIR_ABS" -eq 0 ]; then
309            # Compatibility with earlier versions that used the presence of $BOOT_ROOT/$ENTRY_TOKEN
310            # to signal to 00-entry-directory to create $ENTRY_DIR_ABS
311            # to serve as the indication to use or to not use the BLS
312            if [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ]; then
313                echo "+mkdir -v -p $ENTRY_DIR_ABS"
314                mkdir -v -p "$ENTRY_DIR_ABS" || exit 1
315            else
316                mkdir -p "$ENTRY_DIR_ABS" || exit 1
317            fi
318        fi
319
320        for f in $PLUGINS; do
321            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "+$f add $KERNEL_VERSION $ENTRY_DIR_ABS $*"
322            "$f" add "$KERNEL_VERSION" "$ENTRY_DIR_ABS" "$@"
323            err=$?
324            [ $err -eq $skip_remaining ] && break
325            ret=$(( ret + err ))
326        done
327        ;;
328
329    remove)
330        for f in $PLUGINS; do
331            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "+$f remove $KERNEL_VERSION $ENTRY_DIR_ABS"
332            "$f" remove "$KERNEL_VERSION" "$ENTRY_DIR_ABS"
333            err=$?
334            [ $err -eq $skip_remaining ] && break
335            ret=$(( ret + err ))
336        done
337
338        if [ "$MAKE_ENTRY_DIR_ABS" -eq 0 ]; then
339            [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Removing $ENTRY_DIR_ABS/"
340            rm -rf "$ENTRY_DIR_ABS"
341        fi
342        ;;
343
344    inspect)
345        echo "KERNEL_INSTALL_MACHINE_ID: $KERNEL_INSTALL_MACHINE_ID"
346        echo "KERNEL_INSTALL_ENTRY_TOKEN: $KERNEL_INSTALL_ENTRY_TOKEN"
347        echo "KERNEL_INSTALL_BOOT_ROOT: $KERNEL_INSTALL_BOOT_ROOT"
348        echo "KERNEL_INSTALL_LAYOUT: $KERNEL_INSTALL_LAYOUT"
349        echo "KERNEL_INSTALL_INITRD_GENERATOR: $KERNEL_INSTALL_INITRD_GENERATOR"
350        echo "ENTRY_DIR_ABS: $KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN/\$KERNEL_VERSION"
351
352        # Assert that ENTRY_DIR_ABS actually matches what we are printing here
353        [ "${ENTRY_DIR_ABS%/*}" = "$KERNEL_INSTALL_BOOT_ROOT/$ENTRY_TOKEN" ] || { echo "Assertion didn't pass." >&2; exit 1; }
354
355        ;;
356    *)
357        echo "Error: unknown command '$COMMAND'" >&2
358        exit 1
359        ;;
360esac
361
362exit "$ret"
363