Packagemanager-LFS-PackageUser-System git · main
Package-user based package manager for LFS/BLFS
Shell 63.7% Markdown 35.8%git clone https://git.christianimmanuel.de/linux-from-scratch/Packagemanager-LFS-PackageUser-System.gitwget https://git.christianimmanuel.de/linux-from-scratch/Packagemanager-LFS-PackageUser-System/archive/Packagemanager-LFS-PackageUser-System.tar.gzlfs-kernel raw
#!/bin/bash
# lfs-kernel -- build and install a kernel.
#
# lfs-kernel what a build would do (nothing is built)
# lfs-kernel --run build and install the configured version
# lfs-kernel load <module> [--boot]
# load it now, and at every boot with --boot
# lfs-kernel list what kernel.org offers right now
# lfs-kernel 6.12.40 --run that version, once (config untouched)
# lfs-kernel latest --run the newest stable, once
# lfs-kernel detect-esp print the EFI directory this would use
# lfs-kernel verify [ver] check that everything is installed
# lfs-kernel firmware install what FIRMWARE_ONLY names, no kernel build
# lfs-kernel status how far the last build got, what is installed
# lfs-kernel promote [ver] [--run]
# make the test kernel (vmlinuz_new) the one that
# boots; the current one stays as vmlinuz_old
# lfs-kernel enable --list hardware this tool knows by name
# lfs-kernel enable ath9k-htc
# put what that hardware needs into kernel.conf
# lfs-kernel modules [text] what this kernel has, and what is loaded
# lfs-kernel config what the next build's .config comes from
# lfs-kernel config list the saved configs (the last two that BOOTED)
# lfs-kernel config diff [a] [b]
# what changed between two saved configs
# lfs-kernel config search <text>
# which symbols that text means, and their state
# lfs-kernel config set SYM=y|m|n [...]
# add them to CONFIG_SET in kernel.conf
# lfs-kernel config save [--good]
# keep the running config; --good: it BOOTS
# lfs-kernel config from last-good
# build from that config, not from the running
# kernel
# lfs-kernel config unset SYM [...]
# take them out again
# lfs-kernel firmware --missing [--fix] [--all]
# firmware this machine asks for and has not got,
# from the boot log, this tree's modules and the
# config; --fix installs it at exactly the path
# the driver asked for, --all includes the files
# a module merely DECLARES (other chips' -- large
# and mostly unused)
#
# Settings: /etc/pkgusr/kernel.conf, INSTALLED by `make install` so it can be
# edited before the first run. The build commands are in one block below,
# after the line THE COMMANDS; everything above it picks a version and checks
# paths.
set -u
CONF=/etc/pkgusr/kernel.conf
MIRROR_JSON=https://www.kernel.org/releases.json
MIRROR_TAR=https://cdn.kernel.org/pub/linux/kernel
# --------------------------------------------------------------------------
# where does the kernel go? $ESP is the directory on the EFI System
# Partition that your boot manager reads -- for rEFInd, the one holding your
# vmlinuz. Find it with: findmnt -t vfat / ls /boot/efi/EFI
# --------------------------------------------------------------------------
_detect_esp() {
local m
for m in $(findmnt -rn -t vfat,msdos -o TARGET 2>/dev/null); do
# a directory that already holds a kernel is the right one
for d in "$m"/EFI/*/ "$m"/EFI/BOOT/ "$m"/; do
[ -d "$d" ] || continue
case "$(echo "$d"*vmlinuz* "$d"*bzImage* 2>/dev/null)" in
*vmlinuz*|*bzImage*) printf '%s' "${d%/}"; return 0 ;;
esac
done
done
# nothing with a kernel in it: the first vfat mount, if any
findmnt -rn -t vfat,msdos -o TARGET 2>/dev/null | head -n1
}
# A BUILD IS NOT SOMETHING YOU START BY TYPING FOUR WORDS.
#
# `lfs-kernel` alone compiled a kernel, installed it over the one the
# machine boots, installed the modules, fetched firmware and copied the
# Documentation -- an hour, from a bare command. Every other tool here
# prints the plan and waits for --run; this one now does too. --run is
# taken from anywhere on the line, so `lfs-kernel 7.2.6 --run` and
# `lfs-kernel --run` both read naturally.
_RUN=0
_rest=()
for _a in "$@"; do
case "$_a" in
--run) _RUN=1 ;;
*) _rest+=("$_a") ;;
esac
done
set -- ${_rest[@]+"${_rest[@]}"}
case "${1:-}" in
list)
echo "# kernel.org offers:"
_releases | awk '{printf " %-12s %s\n", $1, $2}'
echo "# build one: lfs-kernel <version>"
exit 0 ;;
detect-esp)
printf '%s\n' "$(_detect_esp)"; exit 0 ;;
verify)
shift; _VERIFY=1; _CLI_VERSION="${1:-}" ;;
enable)
# hardware by name: what symbols it needs, written into kernel.conf
shift; _ENABLE=1; _ENARGS=("$@") ;;
modules|mods)
# what this kernel actually has, and what is loaded
shift; _MODCMD=1; _MODARGS=("$@") ;;
load|unload)
# load a module now, and (--boot) at every boot
_LOADCMD="$1"; shift; _LOADARGS=("$@") ;;
status|state)
# how far the last build got, and what is installed -- reads only
shift; _STATUS=1 ;;
promote)
# the test kernel becomes the default one (vmlinuz_new -> vmlinuz)
shift; _PROMOTE=1; _PROMOTE_ARG="${1:-}" ;;
config)
# the config, without building anything: what it will be made of,
# what the last two booted kernels used, and the one place this
# machine's own answers are written down (CONFIG_SET)
shift; _CFGCMD=1; _CFGARGS=("$@") ;;
firmware)
# firmware alone, with no kernel build: the blobs are a separate
# package and a missing one is a run-time problem, so fixing it must
# not mean rebuilding a kernel that is already installed and booted.
shift; _FWONLY=1; _FWARG=""; _FWFIX=0; _FWALL=0
for _a in "$@"; do
case "$_a" in
--fix) _FWFIX=1 ;;
--all) _FWALL=1 ;;
--missing|missing) _FWARG="--missing" ;;
*) echo "!! lfs-kernel firmware: unknown argument '$_a'"; exit 1 ;;
esac
done
[ "$_FWFIX" = 1 ] || [ "$_FWALL" = 1 ] && _FWARG="--missing" ;;
-h|--help)
# the slice is the header above, which grew: derive the end from
# the header itself rather than hard-coding a line number that a
# new command silently pushes out of range.
sed -n '2,/^# Settings:/p' "$0" | sed '$d' | sed 's/^# \?//'; exit 0 ;;
"") ;;
search|find)
# `lfs-kernel search AR9271` is what a person types, and it used to
# mean "build the kernel called search" -- see the version check
# below for what that really did. It is the config search.
shift; _CFGCMD=1; _CFGARGS=("search" "$@") ;;
set)
shift; _CFGCMD=1; _CFGARGS=("set" "$@") ;;
-*) echo "!! unknown option: $1 (try: lfs-kernel --help)"; exit 1 ;;
*)
# A WORD THAT IS NOT A VERSION IS NOT A VERSION.
#
# Anything unrecognised became the version to build, and the version
# from kernel.conf then overwrote it (below) -- so a typo, or a
# subcommand this tool does not have, silently started a FULL BUILD
# of the configured kernel. `lfs-kernel search AR9271` rebuilt and
# reinstalled 7.2.6, fetched the firmware and said nothing about the
# word `search` at all.
case "$1" in
latest|[0-9]*.[0-9]*) _CLI_VERSION="$1" ;;
*) echo "!! lfs-kernel: '$1' is neither a command nor a version."
echo " commands: status, list, enable, modules, load,"
echo " config, search, firmware, verify, detect-esp"
echo " a version looks like 7.2.6, or the word latest."
case "$1" in
conf*|cfg) echo " did you mean: lfs-kernel config" ;;
mod*|lsmod) echo " did you mean: lfs-kernel modules" ;;
en*|add) echo " did you mean: lfs-kernel enable --list" ;;
fw|firm*) echo " did you mean: lfs-kernel firmware --missing" ;;
esac
exit 1 ;;
esac ;;
esac
# The config is INSTALLED, not written on first run: it is a file to edit
# before building, not a side effect of trying to. `make install` places it
# and never overwrites an edited one.
if [ ! -f "$CONF" ]; then
echo "!! $CONF is missing. It ships with the tools:"
echo " make install # places $CONF"
echo " or write one; lfs-kernel detect-esp prints the EFI directory."
exit 1
fi
. "$CONF"
# THE COMMAND LINE WINS. `lfs-kernel 6.12.40` sets VERSION, and then this
# file -- sourced after it -- set VERSION=latest straight over the top: the
# documented "that version, once" built whatever kernel.conf said instead,
# and `verify 7.2.4` reported on another kernel entirely. Re-apply it here,
# where nothing can overwrite it again.
[ -n "${_CLI_VERSION:-}" ] && VERSION="$_CLI_VERSION"
# A CONFIG FROM AN OLDER RELEASE.
#
# kernel.conf is never overwritten once edited, so an installed copy can
# predate a change to what the settings MEAN. SRC in particular was dropped
# in 1.14.123 -- the sources live in the package user's home now -- and a
# config still setting it is describing a directory nothing reads.
if [ -n "${SRC:-}" ]; then
echo "# note: SRC is set in $CONF and is no longer used -- the sources"
echo "# live in the package user's home (see PKG). A newer config"
echo "# without it is at $CONF.new if make install left one."
unset SRC
fi
[ -n "${PKG:-}" ] || {
echo "!! $CONF has no PKG setting, so it predates 1.14.123."
echo " Take the new one: cp $CONF{.new,} (yours is $CONF.new's basis)"
echo " or add: PKG=linux"
exit 1
}
: "${PKG:=linux}"; : "${JOBS:=-j$(nproc)}"
: "${CONFIG_FROM:=running}"; : "${V4L2LOOPBACK:=no}"
: "${KERNEL_NAME:=vmlinuz}"
# --------------------------------------------------------------------------
# which version?
# --------------------------------------------------------------------------
_releases() { # "<moniker> <version>" per line, in kernel.org's order
# Pair the keys by NAME, not by position: releases.json lists "version"
# before "moniker", and a paste of alternate lines silently swapped the
# two columns -- so `awk $1=="stable"` matched nothing and "latest"
# resolved to the empty string. Key order in someone else's JSON is not
# a thing to depend on.
wget -qO- "$MIRROR_JSON" 2>/dev/null \
| tr ',{}' '\n\n\n' \
| awk -F'"' '
/"moniker"/ { m = $4 }
/"version"/ { v = $4 }
m != "" && v != "" { print m, v; m = ""; v = "" }'
}
_latest_stable() { _releases | awk '$1=="stable"{print $2; exit}'; }
# `verify` inspects what is on disk, so it must not fetch: resolving
# "latest" would ask kernel.org for a version it is never going to look at,
# and fail on a machine with no network while reporting on one that is
# perfectly installed.
if [ "${_VERIFY:-0}" = 1 ] || [ "${_CFGCMD:-0}" = 1 ] \
|| [ "${_ENABLE:-0}" = 1 ] || [ "${_MODCMD:-0}" = 1 ] \
|| [ "${_STATUS:-0}" = 1 ] || [ -n "${_LOADCMD:-}" ] \
|| [ "${_PROMOTE:-0}" = 1 ]; then
:
elif [ "${_FWONLY:-0}" = 1 ] && [ "$VERSION" = latest ]; then
# A FIRMWARE REPORT IS ABOUT THIS MACHINE, NOT ABOUT KERNEL.ORG.
#
# `lfs-kernel firmware --missing` asked the website which kernel is
# newest -- for a question answered entirely by /lib/modules and the
# boot log -- printed "newest stable on kernel.org", and on a machine
# with no network it EXITED instead of reporting.
VERSION="$(uname -r | sed "s/-.*//")"
if [ ! -d "/lib/modules/$VERSION" ] && [ ! -d "/lib/modules/$(uname -r)" ]; then
VERSION="$(_latest_stable)"
[ -n "$VERSION" ] || { echo "!! no /lib/modules for the running kernel, and kernel.org is unreachable"; exit 1; }
fi
elif [ "$VERSION" = latest ]; then
VERSION="$(_latest_stable)"
[ -n "$VERSION" ] || { echo "!! could not reach $MIRROR_JSON -- set VERSION in $CONF"; exit 1; }
echo "# newest stable on kernel.org: $VERSION"
fi
# THE KERNEL IS A PACKAGE. Its sources belong in its own account's home,
# next to every other package's, so `packagemanager verify`, pkg.lst and
# `remove` all work on it. SRC used to be a free-standing directory in the
# config -- a location invented outside the model, which is exactly what the
# model exists to avoid.
acct="p_$PKG"
# `verify` must not CREATE anything -- a report that makes an account is not
# a report. It only reads $acct's home below.
if [ "${_VERIFY:-0}" != 1 ] && [ "${_CFGCMD:-0}" != 1 ] \
&& [ "${_ENABLE:-0}" != 1 ] && [ "${_MODCMD:-0}" != 1 ] \
&& [ "${_STATUS:-0}" != 1 ] && [ -z "${_LOADCMD:-}" ] \
&& [ "${_PROMOTE:-0}" != 1 ] \
&& ! id "$acct" >/dev/null 2>&1; then
packagemanager add-user "$PKG" || { echo "!! could not create $acct"; exit 1; }
fi
[ "${_VERIFY:-0}" = 1 ] || [ "${_CFGCMD:-0}" = 1 ] \
|| [ "${_ENABLE:-0}" = 1 ] || [ "${_MODCMD:-0}" = 1 ] \
|| [ "${_STATUS:-0}" = 1 ] || [ -n "${_LOADCMD:-}" ] \
|| [ "${_PROMOTE:-0}" = 1 ] \
|| lfs-helper fix-home "$acct" --run >/dev/null 2>&1 || true
SRC="$(getent passwd "$acct" 2>/dev/null | cut -d: -f6)/src"
[ "${_VERIFY:-0}" = 1 ] || [ "${_CFGCMD:-0}" = 1 ] \
|| [ "${_ENABLE:-0}" = 1 ] || [ "${_MODCMD:-0}" = 1 ] \
|| [ "${_STATUS:-0}" = 1 ] || [ -n "${_LOADCMD:-}" ] \
|| [ "${_PROMOTE:-0}" = 1 ] \
|| install -d -o "$acct" -g "$acct" -m 755 "$SRC"
# Everything that touches the tree runs AS THE PACKAGE USER: a root-owned
# file left in its home would break the next build and every rule about who
# owns what. Only the tarball fetch and the ESP copies are root's.
_as_pkg() { su - "$acct" -c "cd '$tree' 2>/dev/null || cd '$SRC'; $*"; }
tree="$SRC/linux-$VERSION"
tarball="$SRC/linux-$VERSION.tar.xz"
major="${VERSION%%.*}"
# --------------------------------------------------------------------------
# THE CONFIGS ARE KEPT, OUTSIDE THE TREE.
#
# A .config lives in a source tree, and a source tree is deleted by hand or
# by `packagemanager remove linux`, which takes the account's home,
# and a failed build can leave one unusable. What is lost with it is the
# only record of every answer this machine has ever given -- hours of
# menuconfig, and the difference between a kernel that boots and one that
# does not.
#
# So every config this tool builds with is copied to $KCFG_DIR, which is
# root's, small (a config is ~200 kB) and has nothing to do with the tree:
#
# config-<version>-<date> every build, newest CONFIG_KEEP kept
# last-good.config the config of the kernel that BOOTED
# prev-good.config the one before that
#
# The last two are the ones worth having: they are known to work on this
# hardware. CONFIG_FROM=last-good starts from one, `config diff` says what
# changed between them, and `config set` is how an answer is added without
# hand-editing a file that the next build overwrites.
# --------------------------------------------------------------------------
KCFG_DIR="${KCFG_DIR:-/etc/pkgusr/kernel-configs}"
: "${CONFIG_KEEP:=5}"
_kcfg_readable() { # a config file with something in it
[ -s "$1" ] && grep -q '^CONFIG_' "$1" 2>/dev/null
}
_kcfg_newest() { # the newest archived build config, or empty
ls -1t "$KCFG_DIR"/config-* 2>/dev/null | head -n1
}
_kcfg_ver() { # the kernel version a config file was built for
sed -n 's/^# Linux\/[^ ]* \([0-9][^ ]*\) Kernel Configuration.*/\1/p' "$1" \
2>/dev/null | head -n1
}
_kcfg_archive() { # <config> <version> -- keep a copy of what we built with
local src="$1" ver="$2" dst
_kcfg_readable "$src" || return 0
install -d -m 755 "$KCFG_DIR" || return 0
dst="$KCFG_DIR/config-$ver-$(date +%Y%m%d-%H%M%S)"
cp "$src" "$dst" 2>/dev/null || return 0
chmod 644 "$dst"
echo "# config kept as $dst"
# PRUNE THE BUILD SNAPSHOTS ONLY. last-good/prev-good are not in this
# pattern and are never deleted here.
ls -1t "$KCFG_DIR"/config-* 2>/dev/null | tail -n +"$((CONFIG_KEEP + 1))" \
| while read -r _old; do rm -f "$_old"; done
}
_kcfg_mark_good() { # <config> <version> -- this kernel has booted
local src="$1" ver="$2"
_kcfg_readable "$src" || return 0
install -d -m 755 "$KCFG_DIR" || return 0
# the same version again is the same kernel: refresh it, do not push the
# OTHER working config out with a copy of this one
if [ -f "$KCFG_DIR/last-good.config" ] \
&& [ "$(_kcfg_ver "$KCFG_DIR/last-good.config")" != "$ver" ]; then
cp "$KCFG_DIR/last-good.config" "$KCFG_DIR/prev-good.config" 2>/dev/null
chmod 644 "$KCFG_DIR/prev-good.config" 2>/dev/null
fi
cp "$src" "$KCFG_DIR/last-good.config" 2>/dev/null || return 0
chmod 644 "$KCFG_DIR/last-good.config"
echo "# the config of $ver is now $KCFG_DIR/last-good.config"
}
# THE CONFIG THIS MACHINE IS RUNNING ON, wherever it can be read from.
_kcfg_current() {
local c
for c in "$tree/.config" "$KCFG_DIR/last-good.config" "$(_kcfg_newest)"; do
[ -n "$c" ] && _kcfg_readable "$c" && { printf '%s' "$c"; return 0; }
done
if [ -r /proc/config.gz ]; then
c="$(mktemp)"; gunzip < /proc/config.gz > "$c" 2>/dev/null \
&& { printf '%s' "$c"; return 0; }
rm -f "$c"
fi
return 1
}
_kcfg_state() { # <config> <SYMBOL> -- what that symbol is, in words
local f="$1" sym="CONFIG_${2#CONFIG_}" l
l="$(grep -E "^($sym=|# $sym is not set)" "$f" 2>/dev/null | head -n1)"
[ -n "$l" ] || { printf 'absent'; return; }
case "$l" in "# $sym is not set") printf 'not set' ;; *) printf '%s' "${l#*=}" ;; esac
}
# ONE WRITER FOR kernel.conf. `config set`, `config from` and `enable` all
# change one KEY="value" line in a file the user edits by hand, so they all
# back it up the same way and none of them rewrites anything else.
_conf_set_var() { # <KEY> <value>
local key="$1" val="$2" bak tmp
bak="$CONF.bak-$(date +%Y%m%d-%H%M%S)"
cp -p "$CONF" "$bak" || { echo "!! could not back up $CONF"; return 1; }
if grep -qE "^[[:space:]]*$key=" "$CONF"; then
tmp="$(mktemp)"
awk -v v="$key=\"$val\"" -v k="^[[:space:]]*$key=" '
$0 ~ k && !done { print v; done = 1; next }
{ print }' "$CONF" > "$tmp" && cat "$tmp" > "$CONF"
rm -f "$tmp"
else
printf '\n%s="%s"\n' "$key" "$val" >> "$CONF"
fi
echo "# $CONF (backup: $(basename "$bak"))"
echo "# $key=\"$val\""
}
# WHAT A SYMBOL IS, ACCORDING TO THE TREE: bool, tristate, or unknown --
# which decides whether `enable` may say =m.
_ksym_type() { # <SYMBOL>
local sym="${1#CONFIG_}"
[ -d "$tree" ] || return 0
grep -rhA3 --include='Kconfig*' -E "^[[:space:]]*(menu)?config[[:space:]]+$sym\$" \
"$tree" 2>/dev/null \
| grep -m1 -oE '^[[:space:]]*(bool|tristate|string|int|hex)' \
| tr -d '[:space:]'
}
# THE SYMBOLS A KCONFIG FILE DEFINES, with their prompt -- the only way a
# search can say "ATH9K_HTC: Atheros HTC based wireless cards" instead of
# printing every symbol in a file that happens to mention the word.
_ksym_scan() { # <pattern> -- SYMBOL<TAB>type<TAB>prompt, for matches only
local pat="$1"
[ -d "$tree" ] || return 0
grep -rlis --include='Kconfig*' -- "$pat" "$tree" 2>/dev/null | head -n 40 \
| xargs -r awk -v pat="$pat" '
function low(x) { return tolower(x) }
/^[[:space:]]*(menu)?config[[:space:]]+[A-Za-z0-9_]+/ {
sym = $2; next }
sym != "" && /^[[:space:]]*(bool|tristate|string|int|hex)([[:space:]]|$)/ {
type = $1
p = $0
# the prompt is the FIRST quoted string on the line, if any:
# `tristate "Atheros HTC based wireless cards support"`
if (match(p, /"[^"]*"/))
p = substr(p, RSTART + 1, RLENGTH - 2)
else
p = ""
if (index(low(sym), low(pat)) || index(low(p), low(pat)))
print sym "\t" type "\t" p
sym = ""; next }
{ if ($0 ~ /^[[:space:]]*$/) sym = "" }' 2>/dev/null \
| sort -u
}
# THE MODULES THIS KERNEL HAS: which version's /lib/modules to read.
_kmod_ver() {
local r; r="$(uname -r)"
[ -d "/lib/modules/$r" ] && { printf '%s' "$r"; return 0; }
ls -1 /lib/modules 2>/dev/null | sort -V | tail -n1
}
# THE NEWEST SOURCE TREE, for a `config` command that names no version.
_kcfg_tree() {
[ -d "${SRC:-}" ] || return 0
ls -1d "$SRC"/linux-*/ 2>/dev/null | grep -v '\-headers' | sort -V | tail -n1 \
| sed 's:/$::'
}
if [ "${_CFGCMD:-0}" = 1 ]; then
[ -d "$tree" ] || tree="$(_kcfg_tree)"
_sub="${_CFGARGS[0]-show}"
case "$_sub" in
show|"")
echo "# the next build's .config"
echo "# CONFIG_FROM = $CONFIG_FROM"
echo "# CONFIG_REFRESH = ${CONFIG_REFRESH:-olddefconfig}"
echo "# CONFIG_SET = ${CONFIG_SET:-(nothing)}"
echo "# kept in $KCFG_DIR (CONFIG_KEEP=$CONFIG_KEEP builds)"
echo
for _f in last-good prev-good; do
if [ -f "$KCFG_DIR/$_f.config" ]; then
printf '# %-16s %s (%s)\n' "$_f.config" \
"$(_kcfg_ver "$KCFG_DIR/$_f.config")" \
"$(date -r "$KCFG_DIR/$_f.config" '+%Y-%m-%d %H:%M')"
else
printf '# %-16s -- none yet\n' "$_f.config"
fi
done
echo
echo "# add an option: lfs-kernel config set NET_VENDOR_ATHEROS=y"
echo "# find its name: lfs-kernel config search atheros"
echo "# then build: lfs-kernel"
exit 0 ;;
list|ls)
if [ ! -d "$KCFG_DIR" ]; then
echo "# no saved configs yet ($KCFG_DIR) -- the next build writes one"
exit 0
fi
echo "# $KCFG_DIR"
for _f in "$KCFG_DIR"/last-good.config "$KCFG_DIR"/prev-good.config; do
[ -f "$_f" ] && printf ' %-34s %-10s %s\n' "$(basename "$_f")" \
"$(_kcfg_ver "$_f")" "$(date -r "$_f" '+%Y-%m-%d %H:%M')"
done
ls -1t "$KCFG_DIR"/config-* 2>/dev/null | while read -r _f; do
printf ' %-34s %-10s %s\n' "$(basename "$_f")" \
"$(_kcfg_ver "$_f")" "$(date -r "$_f" '+%Y-%m-%d %H:%M')"
done
echo "# start from one: CONFIG_FROM=file:<path> in $CONF"
echo "# or CONFIG_FROM=last-good / prev-good / saved"
exit 0 ;;
diff)
_a="${_CFGARGS[1]:-$KCFG_DIR/prev-good.config}"
_b="${_CFGARGS[2]:-$KCFG_DIR/last-good.config}"
[ -f "$_a" ] || _a="$KCFG_DIR/$_a"
[ -f "$_b" ] || _b="$KCFG_DIR/$_b"
for _f in "$_a" "$_b"; do
[ -f "$_f" ] || { echo "!! no such config: $_f"; echo " lfs-kernel config list"; exit 1; }
done
echo "# $_a ($(_kcfg_ver "$_a")) -> $_b ($(_kcfg_ver "$_b"))"
echo "# < only in the first, > only in the second"
diff <(grep -E '^(CONFIG_|# CONFIG_)' "$_a" | sort) \
<(grep -E '^(CONFIG_|# CONFIG_)' "$_b" | sort) \
| grep -E '^[<>]' | sort -k2 || echo "# identical"
exit 0 ;;
search|find)
_pat="${_CFGARGS[1]:-}"
[ -n "$_pat" ] || { echo "!! lfs-kernel config search <text>"; exit 1; }
_cfg="$(_kcfg_current || true)"
if [ -n "$_cfg" ]; then
echo "# '$_pat' in $_cfg"
grep -iE "^(CONFIG_[A-Z0-9_]*${_pat}[A-Z0-9_]*=|# CONFIG_[A-Z0-9_]*${_pat}[A-Z0-9_]* is not set)" \
"$_cfg" | head -n 25 | sed 's/^/ /'
fi
echo
if [ -d "$tree" ]; then
# BY NAME OR BY PROMPT, NOT BY FILE. Listing every symbol of
# every Kconfig that mentions the word gave 40 lines of MIPS
# console options for "atheros". A symbol is a hit when its own
# name or its prompt says so, and the prompt is what tells you
# which one you want.
echo "# symbols in $tree (state, type, what it is)"
_hits="$(_ksym_scan "$_pat")"
if [ -n "$_hits" ]; then
printf '%s\n' "$_hits" | head -n 30 | while IFS="$(printf '\t')" read -r _sym _ty _pr; do
printf ' %-26s %-9s %-9s %s\n' "$_sym" \
"$( [ -n "$_cfg" ] && _kcfg_state "$_cfg" "$_sym" )" \
"$_ty" "$_pr"
done
_n="$(printf '%s\n' "$_hits" | wc -l)"
[ "$_n" -gt 30 ] && echo " ... $((_n - 30)) more"
else
echo " (nothing whose name or prompt says '$_pat')"
fi
else
echo "# no kernel tree here to search; unpack one with: lfs-kernel <version>"
fi
echo
echo "# a NET_VENDOR_* / WLAN_VENDOR_* symbol only opens the submenu:"
echo "# the DRIVER under it has to be set too."
echo "# known hardware, by name: lfs-kernel enable --list"
echo "# set a symbol: lfs-kernel config set ATH9K_HTC=m"
[ -d "$tree" ] \
&& echo "# read the help text: cd $tree && make menuconfig (/ searches)"
exit 0 ;;
save)
# SEED THE ARCHIVE WITHOUT A BUILD. Before the first build there is
# nothing saved, so CONFIG_FROM=last-good has nothing to read -- and
# the config worth keeping is already on this machine.
_src="${_CFGARGS[1]:-}"
_good=0
for _a in ${_CFGARGS[@]+"${_CFGARGS[@]}"}; do
[ "$_a" = "--good" ] && _good=1
done
[ "$_src" = "--good" ] && _src=""
if [ -z "$_src" ]; then
if [ -r /proc/config.gz ]; then
_src="$(mktemp)"; gunzip < /proc/config.gz > "$_src"
_what="the running kernel"
elif _kcfg_readable "$tree/.config"; then
_src="$tree/.config"; _what="$tree"
else
echo "!! nothing to save: no /proc/config.gz and no .config in a tree"
exit 1
fi
else
_what="$_src"
fi
_kcfg_readable "$_src" || { echo "!! $_src is not a kernel config"; exit 1; }
_ver="$(_kcfg_ver "$_src")"
[ -n "$_ver" ] || _ver="$(uname -r | sed 's/-.*//')"
echo "# saving $_what (kernel $_ver)"
_kcfg_archive "$_src" "$_ver"
if [ "$_good" = 1 ]; then
_kcfg_mark_good "$_src" "$_ver"
else
echo "# it is a build snapshot, not a known-good config."
echo "# --good marks it as one (use it for a kernel that BOOTS)."
fi
echo "# start from it next time: lfs-kernel config from last-good"
exit 0 ;;
from)
_val="${_CFGARGS[1]:-}"
case "$_val" in
running|last-good|good|prev-good|prev|saved|last|keep|file:*) ;;
"") echo "# CONFIG_FROM = $CONFIG_FROM"
echo "# running | last-good | prev-good | saved | keep | file:<path>"
exit 0 ;;
*) echo "!! CONFIG_FROM=$_val is not one of:"
echo " running last-good prev-good saved keep file:<path>"; exit 1 ;;
esac
case "$_val" in
last-good|good) _kcfg_readable "$KCFG_DIR/last-good.config" \
|| { echo "!! there is no $KCFG_DIR/last-good.config yet."
echo " Make one from the kernel running now:"
echo " lfs-kernel config save --good"; exit 1; } ;;
esac
_conf_set_var CONFIG_FROM "$_val" || exit 1
exit 0 ;;
set|unset)
_items=("${_CFGARGS[@]:1}")
[ "${#_items[@]}" -gt 0 ] || {
echo "!! lfs-kernel config $_sub SYMBOL[=VALUE] ..."; exit 1; }
# the current list, as words
_new=""
for _w in ${CONFIG_SET:-}; do
_keep=1
for _i in "${_items[@]}"; do
_isym="CONFIG_${_i%%=*}"; _isym="CONFIG_${_isym#CONFIG_}"
_wsym="CONFIG_${_w%%=*}"; _wsym="CONFIG_${_wsym#CONFIG_}"
[ "$_isym" = "$_wsym" ] && _keep=0
done
[ "$_keep" = 1 ] && _new="$_new $_w"
done
if [ "$_sub" = set ]; then
for _i in "${_items[@]}"; do
case "$_i" in
*=*) ;;
*) echo "!! '$_i' has no value -- SYMBOL=y|m|n|<number>|\"text\""; exit 1 ;;
esac
_val="${_i#*=}"
case "$_val" in
y|m|n|[0-9]*|\"*) ;;
*) echo "# note: '$_val' is taken as a string value" ;;
esac
_new="$_new ${_i#CONFIG_}"
done
fi
_new="$(printf '%s' "$_new" | sed 's/^ *//')"
_conf_set_var CONFIG_SET "$_new" || exit 1
echo
echo "# nothing is built yet. Apply it:"
echo "# lfs-kernel # rebuild and install"
echo "# Each entry is read back from the final .config: one the"
echo "# kernel refuses (missing dependency, no such symbol) is"
echo "# named and stops the build."
exit 0 ;;
*) echo "!! lfs-kernel config: show | list | diff | search | set | unset"
echo " | save [--good] | from <source>"
exit 1 ;;
esac
fi
# --------------------------------------------------------------------------
# HARDWARE BY NAME.
#
# "I have an AR9271 USB adapter" is the question; CONFIG_ATH9K_HTC plus
# WLAN_VENDOR_ATH plus the htc_9271 firmware is the answer, and nothing on
# the machine connects the two. menuconfig will not either: it wants the
# symbol you do not know yet.
#
# Each line is: <name>|<symbols>|<firmware>|<what it is>
# `enable <name>` puts the symbols into CONFIG_SET and the firmware into
# FIRMWARE_ONLY, both in kernel.conf, both backed up. A symbol this kernel
# refuses is reported by the build, which reads every CONFIG_SET entry back.
_KDEV_TABLE='
ath9k-htc|WLAN_VENDOR_ATH=y ATH9K_HTC=m|ath9k_htc htc_9271*|Atheros AR9271/AR7010 USB wifi (TP-Link TL-WN722N v1, Alfa AWUS036NHA)
ath9k|WLAN_VENDOR_ATH=y ATH9K=m ATH9K_PCI=y|ath9k_htc|Atheros AR9xxx PCI/PCIe wifi
ath10k|WLAN_VENDOR_ATH=y ATH10K=m ATH10K_PCI=m|ath10k|Qualcomm Atheros QCA988x PCI wifi
ath11k|WLAN_VENDOR_ATH=y ATH11K=m ATH11K_PCI=m|ath11k|Qualcomm QCA6390/WCN685x wifi
alx|NET_VENDOR_ATHEROS=y ALX=m|-|Atheros AR816x/AR817x, Killer E2200 ethernet
atl1c|NET_VENDOR_ATHEROS=y ATL1C=m|-|Atheros AR813x/AR815x ethernet
iwlwifi|WLAN_VENDOR_INTEL=y IWLWIFI=m IWLMVM=m|intel iwlwifi-*.ucode|Intel wifi (AX200 and friends)
rtl8xxxu|WLAN_VENDOR_REALTEK=y RTL8XXXU=m|rtlwifi rtw88|Realtek RTL8188/8192 USB wifi
bluetooth-usb|BT=m BT_HCIBTUSB=m BT_RFCOMM=m BT_BNEP=m|rtl_bt intel|USB Bluetooth (most laptop and dongle radios)
btrfs|BTRFS_FS=m BTRFS_FS_POSIX_ACL=y|-|the btrfs filesystem (stack: btrfs-progs)
exfat|EXFAT_FS=m|-|exFAT, the usual format of big SD cards
fuse|FUSE_FS=m|-|FUSE, userspace filesystems (ntfs-3g, sshfs)
ntfs3|NTFS3_FS=m NTFS3_LZX_XPRESS=y|-|NTFS read/write (the in-kernel driver)
nvme|BLK_DEV_NVME=y|-|NVMe SSDs
usb-serial|USB_SERIAL=m USB_SERIAL_CP210X=m USB_SERIAL_FTDI_SIO=m USB_SERIAL_CH341=m|-|USB serial adapters (CP210x, FTDI, CH340)
v4l2-usb|MEDIA_SUPPORT=m MEDIA_USB_SUPPORT=y USB_VIDEO_CLASS=m|-|USB webcams (UVC)
'
_kdev_line() { # <name> -- the table line, or empty
printf '%s\n' "$_KDEV_TABLE" | awk -F'|' -v n="$1" 'NF && $1 == n {print; exit}'
}
_kdev_list() {
echo "# hardware this tool knows by name:"
printf '%s\n' "$_KDEV_TABLE" | awk -F'|' 'NF { printf " %-15s %s\n", $1, $4 }'
echo
echo "# enable one: lfs-kernel enable ath9k-htc"
echo "# or a symbol: lfs-kernel enable ATH9K_HTC"
echo "# what is set: lfs-kernel config show modules: lfs-kernel modules"
}
if [ "${_ENABLE:-0}" = 1 ]; then
[ -d "$tree" ] || tree="$(_kcfg_tree)"
_args=("${_ENARGS[@]}")
if [ "${#_args[@]}" = 0 ] || [ "${_args[0]}" = "--list" ] || [ "${_args[0]}" = "list" ]; then
_kdev_list; exit 0
fi
_syms=""; _fw=""; _unknown=""
for _a in "${_args[@]}"; do
_row="$(_kdev_line "$_a")"
if [ -n "$_row" ]; then
_syms="$_syms $(printf '%s' "$_row" | cut -d'|' -f2)"
_f="$(printf '%s' "$_row" | cut -d'|' -f3)"
[ "$_f" = "-" ] || _fw="$_fw $_f"
echo "# $_a: $(printf '%s' "$_row" | cut -d'|' -f4)"
continue
fi
# not a name in the table: take it as a symbol, and ask the tree
# whether it can be a module before saying =m
case "$_a" in
*=*) _syms="$_syms ${_a#CONFIG_}"; continue ;;
esac
_sym="$(printf '%s' "${_a#CONFIG_}" | tr '[:lower:]' '[:upper:]')"
case "$(_ksym_type "$_sym")" in
tristate) _syms="$_syms $_sym=m"
echo "# $_sym: a tristate symbol -- set to m (a module)" ;;
bool) _syms="$_syms $_sym=y"
echo "# $_sym: a bool symbol -- set to y (built in)" ;;
"") _unknown="$_unknown $_a" ;;
*) _syms="$_syms $_sym=y" ;;
esac
done
if [ -n "$_unknown" ]; then
echo "!! not a name I know, and not a symbol in this tree:$_unknown"
echo " lfs-kernel enable --list # the names"
echo " lfs-kernel config search <text> # the symbols"
echo " lfs-kernel enable SYM=m # if you are sure"
exit 1
fi
# merge into CONFIG_SET, last value wins, order kept
_new=""
for _w in ${CONFIG_SET:-}; do
_keep=1
for _i in $_syms; do
[ "CONFIG_${_i%%=*}" = "CONFIG_${_w%%=*}" ] && _keep=0
done
[ "$_keep" = 1 ] && _new="$_new $_w"
done
for _i in $_syms; do _new="$_new $_i"; done
_new="$(printf '%s' "$_new" | sed 's/^ *//')"
_conf_set_var CONFIG_SET "$_new" || exit 1
# the firmware, if this hardware needs some and it is not already named
if [ -n "$_fw" ] && [ "${FIRMWARE_FROM:-none}" != none ]; then
_fwnew="${FIRMWARE_ONLY:-}"
if [ -n "$_fwnew" ]; then # empty means "everything": leave it
for _f in $_fw; do
case " $_fwnew " in *" $_f "*) ;; *) _fwnew="$_fwnew $_f" ;; esac
done
[ "$_fwnew" = "${FIRMWARE_ONLY:-}" ] \
|| _conf_set_var FIRMWARE_ONLY "$_fwnew"
fi
fi
echo
echo "# nothing is built yet. Apply it:"
echo "# lfs-kernel # rebuild, install, and the firmware"
echo "# then, after the reboot:"
echo "# lfs-kernel modules ${_args[0]}"
echo "# lfs-kernel firmware --missing"
exit 0
fi
if [ "${_MODCMD:-0}" = 1 ]; then
_mver="$(_kmod_ver)"
[ -n "$_mver" ] || { echo "!! no /lib/modules -- no kernel is installed here"; exit 1; }
_md="/lib/modules/$_mver"
_pat="${_MODARGS[0]-}"
_loaded="$(cut -d' ' -f1 /proc/modules 2>/dev/null | tr '_' '-' | sort -u)"
_all="$(find "$_md" -name '*.ko' -o -name '*.ko.*' 2>/dev/null \
| sed 's:.*/::; s:\.ko.*::' | tr '_' '-' | sort -u)"
_builtin="$(sed 's:.*/::; s:\.ko.*::' "$_md/modules.builtin" 2>/dev/null \
| tr '_' '-' | sort -u)"
_nall="$(printf '%s\n' "$_all" | grep -c . || true)"
_nld="$(printf '%s\n' "$_loaded" | grep -c . || true)"
_nbi="$(printf '%s\n' "$_builtin" | grep -c . || true)"
echo "# kernel $_mver: $_nall module(s) built, $_nld loaded, $_nbi built in"
[ "$_mver" = "$(uname -r)" ] \
|| echo "# (the running kernel is $(uname -r) -- this is another one)"
if [ -z "$_pat" ]; then
echo "# loaded now:"
printf '%s\n' "$_loaded" | grep . | tr '\n' ' ' | fold -s -w 72 | sed 's/^/ /'
echo
echo "# one subject: lfs-kernel modules ath"
echo "# not built yet: lfs-kernel config search ath9k"
exit 0
fi
_hit="$(printf '%s\n' "$_all" "$_builtin" | sort -u \
| grep -i -- "$(printf '%s' "$_pat" | tr '_' '-')" || true)"
if [ -z "$_hit" ]; then
echo "# no module of this kernel is called anything like '$_pat'."
echo "# is it even configured? lfs-kernel config search $_pat"
echo "# what it would be called: lfs-kernel enable --list"
exit 1
fi
echo "# modules matching '$_pat'"
printf '%s\n' "$_hit" | while read -r _m; do
_st="built"
printf '%s\n' "$_builtin" | grep -qx -- "$_m" && _st="built-in"
printf '%s\n' "$_loaded" | grep -qx -- "$_m" && _st="LOADED"
_desc="$(modinfo -F description "$(printf '%s' "$_m" | tr '-' '_')" \
2>/dev/null | head -n1)"
printf ' %-24s %-9s %s\n' "$_m" "$_st" "${_desc:-}"
done
echo
echo "# load one now: lfs-kernel load <name>"
echo "# and at boot: lfs-kernel load <name> --boot"
echo "# (a USB device loads its driver when you plug it in, if udev runs)"
exit 0
fi
# --------------------------------------------------------------------------
# WHERE ARE WE?
#
# A kernel build is long enough to be interrupted -- Ctrl-C, a reboot, a
# session that timed out -- and afterwards nothing on the machine says how
# far it got. The tree holds a .config that may or may not carry your
# answers, object files that may or may not be a finished image, and
# /lib/modules and the ESP each hold a kernel from some earlier day.
#
# `lfs-kernel status` reads all four and says, in order: what is running,
# what the tree has, what is installed, and what to do next. It builds
# nothing, needs no network, and does not care whether the ESP is mounted.
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# PROMOTE: THE TEST KERNEL BECOMES THE ONE THAT BOOTS.
#
# With KERNEL_NAME=vmlinuz_new every build lands beside the working kernel,
# so a bad one cannot cost the machine -- and the machine keeps booting the
# old one until someone copies files around on the ESP by hand, which is
# where a vmlinuz_new_old and a vmlinuz.back come from. This is that copy,
# checked: the image's own version (read from its header, as file(1) does),
# its modules present, the previous default kept as vmlinuz_old.
# --------------------------------------------------------------------------
_kimg_version() { # <bzImage> -> its kernel release, or nothing
local f="$1" off
[ -s "$f" ] || return 0
off="$(od -An -tu2 -j526 -N2 "$f" 2>/dev/null | tr -d ' ')"
[ -n "$off" ] || return 0
dd if="$f" bs=1 skip=$((off + 512)) count=128 2>/dev/null \
| tr '\0' '\n' | head -n1 | awk '{print $1}'
}
if [ "${_PROMOTE:-0}" = 1 ]; then
_def="vmlinuz"
[ -n "${ESP:-}" ] || ESP="$(_detect_esp)"
[ -d "${ESP:-}" ] || { echo "!! no ESP directory: ${ESP:-(not set, none found)} (is it mounted?)"; exit 1; }
case "$_PROMOTE_ARG" in
"") _src="$ESP/${KERNEL_NAME}" ;;
*/*) _src="$_PROMOTE_ARG" ;;
[0-9]*) _src=""
for _c in "$ESP/${KERNEL_NAME}-$_PROMOTE_ARG" "$ESP/vmlinuz_new-$_PROMOTE_ARG" \
"$ESP/vmlinuz-$_PROMOTE_ARG"; do
[ -s "$_c" ] && { _src="$_c"; break; }
done ;;
*) _src="$ESP/$_PROMOTE_ARG" ;;
esac
if [ -z "$_src" ] || [ ! -s "$_src" ]; then
echo "!! nothing to promote: ${_src:-no image for $_PROMOTE_ARG} is not there"
echo " kernels on the ESP:"
for _k in "$ESP"/vmlinuz*; do
[ -s "$_k" ] && printf ' %-28s %s\n' "${_k##*/}" "$(_kimg_version "$_k")"
done
exit 1
fi
_sv="$(_kimg_version "$_src")"
_dv="$(_kimg_version "$ESP/$_def")"
echo "# promote ${_src##*/} ($_sv) to $_def"
echo " now booting by default: $_def (${_dv:-none})"
echo " running: $(uname -r)"
[ -n "$_sv" ] || { echo "!! ${_src##*/} has no readable kernel version -- not a bzImage?"; exit 1; }
if [ "$_src" = "$ESP/$_def" ] || { [ -f "$ESP/$_def" ] && cmp -s "$_src" "$ESP/$_def"; }; then
echo "# $_def already is $_sv -- nothing to do"
exit 0
fi
if [ ! -d "/lib/modules/$_sv" ]; then
echo "!! /lib/modules/$_sv is missing -- that kernel would boot without"
echo " its modules. Install them first (lfs-kernel $_sv --run)."
exit 1
fi
echo " plan:"
[ -f "$ESP/$_def" ] && echo " $_def -> ${_def}_old (your way back: pick it in the boot menu)"
echo " ${_src##*/} -> $_def"
echo " ${_src##*/} -> $_def-$_sv (so verify finds it)"
_strays=""
for _x in vmlinuz- config- System.map-; do
[ -e "$ESP/$_x" ] && _strays="$_strays $ESP/$_x"
done
if [ "$_RUN" != 1 ]; then
echo "# dry run -- add --run"
else
if [ -f "$ESP/$_def" ]; then
cp -a "$ESP/$_def" "$ESP/${_def}_old" || { echo "!! could not keep $_def as ${_def}_old"; exit 1; }
fi
cp -a "$_src" "$ESP/$_def.tmp" && mv -f "$ESP/$_def.tmp" "$ESP/$_def" \
|| { echo "!! could not write $ESP/$_def"; exit 1; }
[ -f "$ESP/$_def-$_sv" ] || cp -a "$_src" "$ESP/$_def-$_sv"
if cmp -s "$_src" "$ESP/$_def"; then
echo "# $_def is now $_sv; the previous default is ${_def}_old"
else
echo "!! $ESP/$_def does not match ${_src##*/} after the copy"; exit 1
fi
fi
if [ "$KERNEL_NAME" != "$_def" ]; then
echo "# future builds still land as $KERNEL_NAME. To install them as the"
echo "# default directly (previous kept as ${_def}_old), in $CONF:"
echo "# KERNEL_NAME=$_def"
fi
if [ -n "$_strays" ]; then
echo "# left over from a run with no version (safe to delete):"
echo "# rm$_strays"
fi
exit 0
fi
if [ "${_STATUS:-0}" = 1 ]; then
[ -d "$tree" ] || tree="$(_kcfg_tree)"
_ver="$(basename "${tree:-}" 2>/dev/null | sed 's/^linux-//')"
_run="$(uname -r)"; _runv="$(echo "$_run" | sed 's/-.*//')"
echo "### lfs-kernel status"
echo
echo "# running now"
echo " kernel $_run"
if [ -s /etc/pkgusr/kernel.last-good ]; then
echo " known to boot $(cat /etc/pkgusr/kernel.last-good)"
else
echo " known to boot -- nothing recorded yet"
fi
if [ -d "/lib/modules/$_run" ]; then
echo " its modules /lib/modules/$_run"
else
echo " its modules MISSING (/lib/modules/$_run is not there)"
fi
echo
echo "# the config"
echo " CONFIG_FROM $CONFIG_FROM"
for _f in last-good prev-good; do
if [ -f "$KCFG_DIR/$_f.config" ]; then
echo " $_f $(_kcfg_ver "$KCFG_DIR/$_f.config") ($(date -r "$KCFG_DIR/$_f.config" '+%Y-%m-%d %H:%M'))"
else
echo " $_f -- none"
fi
done
# DOES THE TREE'S CONFIG CARRY WHAT YOU ASKED FOR? This is the question
# after an interrupted run: the answers are applied BEFORE the compile,
# so they are in the file even when no kernel came out of it.
if [ -n "${CONFIG_SET:-}" ] && [ -f "${tree:-}/.config" ]; then
_miss=""
for _cs in $CONFIG_SET; do
_sym="CONFIG_${_cs%%=*}"; _sym="CONFIG_${_sym#CONFIG_}"; _val="${_cs#*=}"
case "$_val" in
n) grep -qE "^$_sym=(y|m)" "$tree/.config" && _miss="$_miss $_cs" ;;
*) grep -qx "$_sym=$_val" "$tree/.config" || _miss="$_miss $_cs" ;;
esac
done
if [ -z "$_miss" ]; then
echo " CONFIG_SET all $(echo $CONFIG_SET | wc -w) in $tree/.config"
else
echo " CONFIG_SET NOT in the tree's .config:$_miss"
echo " (the next build applies them)"
fi
fi
echo
echo "# the tree"
if [ -z "${tree:-}" ] || [ ! -d "$tree" ]; then
echo " none in $SRC -- nothing has been unpacked here"
else
echo " $tree"
_bz="$tree/arch/x86/boot/bzImage"
if [ ! -f "$tree/.config" ]; then
echo " config none yet"
elif [ -f "$_bz" ] && [ "$_bz" -nt "$tree/.config" ]; then
echo " build FINISHED -- bzImage is newer than .config"
echo " $(date -r "$_bz" '+%Y-%m-%d %H:%M')"
elif [ -f "$tree/vmlinux" ] || find "$tree" -maxdepth 2 -name '*.o' -print -quit 2>/dev/null | grep -q .; then
echo " build UNFINISHED -- object files, no current image"
echo " (an interrupted make; lfs-kernel continues"
echo " from here, it does not start over)"
else
echo " build not started"
fi
_nko="$(find "$tree" -name '*.ko' 2>/dev/null | wc -l)"
echo " modules built $_nko in the tree"
fi
echo
echo "# installed"
if [ -n "$_ver" ] && [ -d "/lib/modules/$_ver" ]; then
echo " /lib/modules/$_ver $(test -s "/lib/modules/$_ver/modules.dep" && echo "(depmod has run)" || echo "-- NO modules.dep")"
echo " $(find "/lib/modules/$_ver" -name '*.ko*' 2>/dev/null | wc -l) module(s)"
elif [ -n "$_ver" ]; then
echo " /lib/modules/$_ver -- not there: this kernel was never installed"
fi
if [ -d "${ESP:-}" ]; then
if [ -f "$ESP/$KERNEL_NAME" ]; then
echo " $ESP/$KERNEL_NAME $(date -r "$ESP/$KERNEL_NAME" '+%Y-%m-%d %H:%M')"
fi
for _k in "$ESP/$KERNEL_NAME"-*; do
[ -f "$_k" ] && echo " $(basename "$_k")"
done
if [ -n "$_ver" ] && [ -f "$ESP/config-$_ver" ] && [ -f "$tree/.config" ]; then
if cmp -s "$ESP/config-$_ver" "$tree/.config"; then
echo " the installed kernel was built from the tree's CURRENT config"
else
echo " the installed config DIFFERS from the tree's .config --"
echo " what is in the tree has not been built and installed yet"
fi
fi
else
echo " $ESP is not mounted -- cannot say what is on the EFI partition"
fi
echo
echo "# next"
if [ -n "$_ver" ] && [ -f "$tree/.config" ] \
&& { [ ! -f "$ESP/config-$_ver" ] || ! cmp -s "$ESP/config-$_ver" "$tree/.config"; }; then
echo " lfs-kernel # build and install what the tree now has"
echo " an interrupted build continues; nothing is thrown away."
else
echo " nothing owed: the tree, /lib/modules and the ESP agree."
fi
echo " lfs-kernel modules ath # is a driver there, is it loaded"
echo " lfs-kernel config show # what the next build would use"
exit 0
fi
# --------------------------------------------------------------------------
# LOADING A MODULE, NOW AND AT EVERY BOOT.
#
# A driver built as a module does nothing until something loads it. For a
# USB device udev does that when you plug it in; for everything else, and
# on a machine where udev is not running, it is modprobe -- and then the
# same name in /etc/sysconfig/modules, which is the file LFS's `modules`
# bootscript reads. Two places, one command.
# --------------------------------------------------------------------------
if [ -n "${_LOADCMD:-}" ]; then
_MODFILE="${MODULES_FILE:-/etc/sysconfig/modules}"
_boot=0; _names=()
for _a in ${_LOADARGS[@]+"${_LOADARGS[@]}"}; do
case "$_a" in
--boot) _boot=1 ;;
-*) echo "!! lfs-kernel $_LOADCMD: unknown option '$_a'"; exit 1 ;;
*) _names+=("$_a") ;;
esac
done
if [ "${#_names[@]}" = 0 ]; then
echo "!! lfs-kernel $_LOADCMD <module> [...] [--boot]"
echo " which modules there are: lfs-kernel modules <text>"
exit 1
fi
_rc=0
for _m in "${_names[@]}"; do
_m="$(printf '%s' "$_m" | tr '-' '_')"
if [ "$_LOADCMD" = unload ]; then
if modprobe -r "$_m" 2>&1; then
echo "# $_m: unloaded"
else
echo "!! $_m: could not be unloaded (in use? lsmod | grep $_m)"
_rc=1
fi
if [ -f "$_MODFILE" ] && grep -qE "^[[:space:]]*$_m([[:space:]]|$)" "$_MODFILE"; then
cp -p "$_MODFILE" "$_MODFILE.bak-$(date +%Y%m%d-%H%M%S)"
_t="$(mktemp)"
grep -vE "^[[:space:]]*$_m([[:space:]]|$)" "$_MODFILE" > "$_t" \
&& cat "$_t" > "$_MODFILE"
rm -f "$_t"
echo "# and taken out of $_MODFILE"
fi
continue
fi
if grep -qE "^${_m}[[:space:]]" /proc/modules 2>/dev/null; then
echo "# $_m: already loaded"
elif modprobe "$_m" 2>&1; then
if grep -qE "^${_m}[[:space:]]" /proc/modules 2>/dev/null; then
echo "# $_m: loaded"
else
# BUILT IN IS NOT A FAILURE. modprobe succeeds silently for
# a symbol that is =y; the module simply never appears in
# /proc/modules because it is part of the image.
echo "# $_m: modprobe returned success but it is not in"
echo "# /proc/modules -- it is probably built INTO the kernel"
echo "# (lfs-kernel modules $_m says which)"
fi
else
echo "!! $_m: modprobe refused it."
echo " is it built? lfs-kernel modules $_m"
echo " is it set? lfs-kernel config search $_m"
_rc=1
continue
fi
# WHAT THE KERNEL SAID ABOUT IT, which is where a missing firmware
# blob or an unsupported device id turns up.
if command -v dmesg >/dev/null 2>&1; then
_say="$(dmesg 2>/dev/null | tail -n 40 \
| grep -iE "${_m}|${_m//_/-}|firmware" | tail -n 5)"
[ -n "$_say" ] && printf '%s\n' "$_say" | sed 's/^/ /'
fi
if [ "$_boot" = 1 ]; then
if [ -f "$_MODFILE" ] && grep -qE "^[[:space:]]*$_m([[:space:]]|$)" "$_MODFILE"; then
echo "# $_MODFILE already lists it"
else
[ -f "$_MODFILE" ] \
&& cp -p "$_MODFILE" "$_MODFILE.bak-$(date +%Y%m%d-%H%M%S)"
if [ ! -f "$_MODFILE" ]; then
install -d "$(dirname "$_MODFILE")"
{
echo "# /etc/sysconfig/modules -- one module per line,"
echo "# loaded at boot by the 'modules' bootscript."
echo "# Arguments may follow the name."
} > "$_MODFILE"
fi
printf '%s\n' "$_m" >> "$_MODFILE"
echo "# added to $_MODFILE (loaded at every boot)"
fi
else
echo "# at every boot as well: lfs-kernel load $_m --boot"
fi
done
exit $_rc
fi
# --------------------------------------------------------------------------
# the ESP has to be the EFI partition, not a directory of the same name.
# Every copy below would SUCCEED on the root filesystem and the firmware
# would find nothing -- a failure that only appears at the next boot.
# --------------------------------------------------------------------------
[ -n "${ESP:-}" ] || { echo "!! ESP is not set in $CONF. Find it with: findmnt -t vfat"; exit 1; }
if [ ! -d "$ESP" ]; then
echo "!! $ESP does not exist. Mount the EFI partition first, or fix ESP"
echo " in $CONF -- creating it here would put vmlinuz where the"
echo " firmware cannot read it."
exit 1
fi
case "$(stat -f -c %T "$ESP" 2>/dev/null)" in
msdos|vfat|fat*|exfat) ;;
"") echo "# could not identify the filesystem under $ESP -- check it before rebooting" ;;
*) echo "!! $ESP is not on a vfat filesystem, so it is not the EFI partition."
echo " findmnt -t vfat # and set ESP in $CONF"; exit 1 ;;
esac
# BUILDING IN A CHROOT IS FINE -- IF THE PATHS ARE THIS TREE'S.
# The chroot usually IS the system that will boot. What can go astray is
# each path, so name them and carry on. Judge only on positive evidence:
# where /proc/1/root cannot be read, the comparison says nothing.
_realroot="$(stat -c %d:%i /proc/1/root/. 2>/dev/null || true)"
_thisroot="$(stat -c %d:%i / 2>/dev/null || true)"
if [ "${_VERIFY:-0}" != 1 ] \
&& [ -n "$_realroot" ] && [ -n "$_thisroot" ] && [ "$_realroot" != "$_thisroot" ]; then
echo "# a chroot: fine, as long as these are the tree you boot --"
echo "# $ESP (EFI partition, mounted here)"
echo "# /lib/modules (modules_install and depmod write here)"
echo "# /proc/config.gz (shared with the host: the RUNNING kernel)"
echo "# set CONFIG_FROM=file:<path> in $CONF to start from another config"
fi
# ---- verify: is everything where it should be? ---------------------------
#
# "is the kernel part done? do we have a validation command so we know
# everything is installed as expected?" Every piece this tool installs is
# checked here, by looking at the system rather than at what the build
# printed: the image and its companions on the ESP, the modules and their
# dependency index, the headers, the embedded firmware, and the two package
# manifests.
if [ "${_VERIFY:-0}" = 1 ]; then
_bad=0
_ck() { # _ck <description> <test...>
local what="$1"; shift
if "$@" >/dev/null 2>&1; then
echo " ok $what"
else
echo " FAIL $what"
_bad=$((_bad + 1))
fi
}
if [ -z "$VERSION" ] || [ "$VERSION" = latest ]; then
# whatever was installed most recently, rather than whatever
# kernel.org offers today
VERSION="$(ls -1t "$ESP/$KERNEL_NAME"-* 2>/dev/null | head -1)"
VERSION="${VERSION##*/$KERNEL_NAME-}"
fi
[ -n "$VERSION" ] || { echo "!! nothing installed as $ESP/$KERNEL_NAME-* yet"; exit 1; }
echo "### verifying kernel $VERSION"
_ck "$ESP/$KERNEL_NAME (what the firmware loads)" test -s "$ESP/$KERNEL_NAME"
_ck "$ESP/$KERNEL_NAME-$VERSION" test -s "$ESP/$KERNEL_NAME-$VERSION"
_ck "the two are the same image" cmp -s "$ESP/$KERNEL_NAME" "$ESP/$KERNEL_NAME-$VERSION"
_ck "$ESP/System.map-$VERSION" test -s "$ESP/System.map-$VERSION"
_ck "$ESP/config-$VERSION" test -s "$ESP/config-$VERSION"
_ck "/lib/modules/$VERSION" test -d "/lib/modules/$VERSION"
_ck "modules.dep (depmod has run)" test -s "/lib/modules/$VERSION/modules.dep"
_ck "headers in /usr/include/linux" test -s /usr/include/linux/version.h
# the firmware this config embeds must be on disk, or the image is
# missing pieces it thinks it has
_cfg="$ESP/config-$VERSION"
_fwlist="$(sed -n 's/^CONFIG_EXTRA_FIRMWARE="\(.*\)"$/\1/p' "$_cfg" 2>/dev/null)"
if [ -n "$_fwlist" ]; then
for _f in $_fwlist; do
_ck "firmware /lib/firmware/$_f" test -e "/lib/firmware/$_f"
done
fi
# every directory FIRMWARE_ONLY asked for should be on disk. The
# embedded list above only covers blobs the IMAGE carries; the wireless
# firmware is loaded at run time and was checked by nothing at all --
# which is why "what about the iwl stuff?" had to be asked.
for _d in ${FIRMWARE_ONLY:-}; do
case "$_d" in
*[*?[]*) _ck "firmware matching $_d" \
sh -c "ls -d /lib/firmware/$_d >/dev/null 2>&1" ;;
*) _ck "firmware /lib/firmware/$_d" test -e "/lib/firmware/$_d" ;;
esac
done
# named for what it actually is, since intel/ is where the wireless lives
[ -d /lib/firmware/intel/iwlwifi ] \
&& echo " note $(ls /lib/firmware/intel/iwlwifi 2>/dev/null | wc -l) Intel wireless blob(s) in intel/iwlwifi"
# ...and the out-of-tree module, when the config asks for one. verify
# checked everything the tool installs EXCEPT this -- the one piece whose
# build failed three times, which is precisely the thing a report should
# not be silent about.
case "${V4L2LOOPBACK:-no}" in
yes|1|true)
_ck "v4l2loopback.ko in /lib/modules/$VERSION" \
sh -c "find /lib/modules/'$VERSION' -name 'v4l2loopback.ko*' \
-print -quit 2>/dev/null | grep -q ."
_ck "modules.dep knows v4l2loopback" \
sh -c "grep -q v4l2loopback /lib/modules/'$VERSION'/modules.dep 2>/dev/null" ;;
esac
# and the package side: both accounts should have a manifest
for _a in "p_$PKG" p_linux-firmware; do
_h="$(getent passwd "$_a" 2>/dev/null | cut -d: -f6)"
if [ -n "$_h" ]; then
_ck "$_a records its files" test -s "$_h/pkg.lst"
fi
done
# A manifest with a source tree in it is a manifest that would delete a
# source tree. 266k entries was the symptom; anything above a few
# thousand for the kernel means the home slipped back in.
_h="$(getent passwd "p_$PKG" 2>/dev/null | cut -d: -f6)"
if [ -n "$_h" ] && [ -s "$_h/pkg.lst" ]; then
_n="$(grep -c . "$_h/pkg.lst")"
echo " note p_$PKG records $_n file(s)"
# ...and if that is implausible, SHOW the lines rather than guessing
# at which path let them in. Two releases were spent reasoning about
# a filter without ever looking at what it failed to match.
if [ "$_n" -gt 50000 ]; then
echo " FAIL that is far too many for a kernel -- a source tree is in it."
echo " the first few entries are:"
head -3 "$_h/pkg.lst" | sed 's/^/ /'
echo " and around the middle:"
sed -n "$((_n / 2))p" "$_h/pkg.lst" | sed 's/^/ /'
_bad=$((_bad + 1))
fi
# Two ways a source tree gets in: directly, or through
# /lib/modules/<ver>/build, which is a SYMLINK to it. The second is
# the one that actually happened -- and the check that looked only
# for the home said nothing about 266k entries.
if grep -q "^$_h/" "$_h/pkg.lst" 2>/dev/null; then
echo " FAIL the manifest includes the account's own home"
echo " (sources, not installed files)"
_bad=$((_bad + 1))
fi
if grep -qE "^/lib/modules/[^/]+/(build|source)/." "$_h/pkg.lst" 2>/dev/null; then
echo " FAIL the manifest was scanned THROUGH"
echo " /lib/modules/$VERSION/build, a symlink to the source"
echo " tree -- so it lists the whole tree as installed files."
_bad=$((_bad + 1))
fi
[ "$_bad" = 0 ] || echo " fix both with: packagemanager reload-pkg-list $PKG"
fi
_lg=/etc/pkgusr/kernel.last-good
if [ -s "$_lg" ]; then
_lgv="$(cat "$_lg")"
if [ "$_lgv" = "$(uname -r | sed 's/-.*//')" ]; then
echo " note last kernel known to boot: $_lgv (it is running now)"
else
echo " note last kernel known to boot: $_lgv"
fi
else
echo " note no kernel confirmed to boot yet -- $VERSION is untested,"
echo " and the running $(uname -r) was not built by this tool"
fi
echo
if [ "$_bad" = 0 ]; then
echo "everything the kernel step installs is present."
echo " running kernel: $(uname -r) installed: $VERSION"
exit 0
fi
echo "$_bad check(s) failed -- run lfs-kernel to (re)install."
exit 1
fi
# ---- firmware, as its own package ----------------------------------------
#
# The drivers need it at RUN time and CONFIG_EXTRA_FIRMWARE needs it at BUILD
# time, so it is installed before the check below rather than skipped. It
# gets its own package user: linux-firmware is a separate upstream project
# with its own release pace, and `packagemanager verify` should see it as
# one thing.
: "${FIRMWARE_FROM:=none}"
# A pattern is anything with a wildcard; cone mode cannot express one.
_sparse_mode() {
case "$*" in *[*?[]*) printf '%s' "--no-cone" ;; *) printf '' ;; esac
}
# In --no-cone mode each entry is a gitignore-style pattern, and a bare
# directory name has to become /dir/ to mean "that directory".
_sparse_args() {
local mode e out=""
mode="$(_sparse_mode "$@")"
for e in $*; do
if [ -n "$mode" ]; then
case "$e" in
*/|*[*?[]*) out="$out $e" ;;
*) out="$out /$e/" ;;
esac
else
out="$out $e"
fi
done
# WHENCE lists the flat-name links (see _fw_whence_links). Cone mode
# always checks out top-level files; pattern mode only what it is told.
[ -n "$mode" ] && out="$out /WHENCE"
printf '%s' "${out# }"
}
# WHAT IS THERE, NOT JUST WHAT IS NOT. linux-firmware moves blobs between
# the top level and subdirectories between releases, so a name or pattern
# from last year can simply be gone. "iwlwifi does not exist" sends the
# reader to go and look; the search below is the answer.
_firmware_not_found() {
local src="$1" want="$2" stem
stem="$(printf '%s' "$want" | sed 's/[*?].*//; s/[-_]*$//')"
echo "!! FIRMWARE_ONLY asks for '$want', which is not in $src"
[ -n "$stem" ] || return 0
# ASK THE INDEX, NOT THE WORKING TREE.
#
# A sparse checkout REMOVES everything it did not select, so searching
# the directory finds only what is already installed -- amdgpu and intel
# -- and reports "nothing" for the very file being looked for. git's
# index lists every path the repository has, selected or not, which is
# the question actually being asked.
local _hits=""
if [ -d "$src/.git" ]; then
_hits="$(_fw_index "$src" | grep -i -- "$stem" | head -12)"
if [ -n "$_FW_INDEX_ERR" ]; then
echo " (could not read the repository index, so this is what is on"
echo " disk only: $_FW_INDEX_ERR)"
fi
fi
[ -n "$_hits" ] || _hits="$(find "$src" -maxdepth 2 -iname "*$stem*" \
-printf '%P\n' 2>/dev/null | head -12)"
if [ -n "$_hits" ]; then
echo " the repository has these with '$stem' in the name:"
printf ' %s\n' $_hits
echo " Put a directory or a pattern covering them in FIRMWARE_ONLY"
echo " in $CONF -- a directory name is the simplest thing that works."
else
echo " nothing in the repository matches '$stem' at all."
if [ -d "$src/.git" ]; then
echo " its top level holds:"
git -C "$src" ls-tree --name-only HEAD 2>/dev/null | head -12 \
| sed 's/^/ /'
fi
fi
}
# THE REGULATORY DATABASE IS NOT IN linux-firmware.
#
# cfg80211 asks the loader for regulatory.db, linux-firmware does not carry
# it, and no FIRMWARE_ONLY entry can conjure it -- worse, an entry naming it
# makes the whole firmware install FAIL, because a pattern matching nothing
# in the repository is an error. It is released on its own as
# wireless-regdb, and it belongs to the same package user as the rest of
# /lib/firmware: one directory, one owner.
REGDB_BASE="${REGDB_BASE:-https://www.kernel.org/pub/software/network/wireless-regdb}"
_install_regdb() {
local acct=p_linux-firmware home tar url ver work
case "${REGDB:-yes}" in yes|1|true) ;; *) return 0 ;; esac
[ -e /lib/firmware/regulatory.db ] && [ "${1:-}" != force ] && return 0
id "$acct" >/dev/null 2>&1 || packagemanager add-user linux-firmware || return 1
home="$(getent passwd "$acct" | cut -d: -f6)"
# ASK, DO NOT ASSUME A VERSION: the directory listing names the releases,
# and a hard-coded date goes stale between them.
ver="$(wget -qO- "$REGDB_BASE/" 2>/dev/null \
| sed -n 's/.*wireless-regdb-\([0-9.]*\)\.tar\.xz.*/\1/p' \
| sort -V | tail -n1)"
if [ -z "$ver" ]; then
echo "!! could not read $REGDB_BASE/ -- no network, or the layout moved."
echo " Fetch a wireless-regdb tarball by hand, then:"
echo " su - $acct -c 'cp regulatory.db regulatory.db.p7s /lib/firmware/'"
return 1
fi
url="$REGDB_BASE/wireless-regdb-$ver.tar.xz"
tar="$home/src/wireless-regdb-$ver.tar.xz"
install -d -o "$acct" -g "$acct" -m 755 "$home/src" 2>/dev/null
# CONFIRM THE DIRECTORY BEFORE DOWNLOADING INTO IT. `install -d -o` on
# an account that does not exist fails quietly, and the next line then
# reports a download failure for a URL that was never the problem.
[ -d "$home/src" ] || {
echo "!! $home/src does not exist and could not be created"
echo " (does the account $acct have that home? lfs-helper fix-home $acct --run)"
return 1; }
echo "# wireless-regdb $ver (the regulatory database, not in linux-firmware)"
[ -s "$tar" ] || su - "$acct" -c "wget -qO '$tar' '$url'" || {
echo "!! could not download $url"; rm -f "$tar"; return 1; }
work="$home/src/wireless-regdb-$ver"
su - "$acct" -c "cd '$home/src' && tar -xf '$tar'" || return 1
[ -s "$work/regulatory.db" ] || {
echo "!! $tar unpacked without a regulatory.db in it"; return 1; }
install -d -m 755 /lib/firmware 2>/dev/null
[ "$(stat -c %U /lib/firmware 2>/dev/null)" = "$acct" ] \
|| chown "$acct:$acct" /lib/firmware 2>/dev/null
su - "$acct" -c "cp -a '$work/regulatory.db' '$work/regulatory.db.p7s' /lib/firmware/" \
|| { echo "!! could not install the database as $acct"; return 1; }
if [ -s /lib/firmware/regulatory.db ]; then
echo "# installed /lib/firmware/regulatory.db ($(stat -c %s /lib/firmware/regulatory.db) bytes)"
echo "# /lib/firmware/regulatory.db.p7s"
packagemanager reload-pkg-list linux-firmware >/dev/null 2>&1 || true
return 0
fi
echo "!! /lib/firmware/regulatory.db is absent after the copy"
return 1
}
_install_firmware() {
local acct="p_linux-firmware" src="" only="${FIRMWARE_ONLY:-}"
id "$acct" >/dev/null 2>&1 || packagemanager add-user linux-firmware || return 1
lfs-helper fix-home "$acct" --run >/dev/null 2>&1 || true
local home; home="$(getent passwd "$acct" | cut -d: -f6)"
case "$FIRMWARE_FROM" in
none) return 0 ;;
copy:*)
src="${FIRMWARE_FROM#copy:}"
[ -d "$src" ] || { echo "!! $src does not exist (FIRMWARE_FROM)"; return 1; } ;;
git)
src="$home/src/linux-firmware"
install -d -o "$acct" -g "$acct" -m 755 "$home/src"
# linux-firmware is over ten gigabytes. With FIRMWARE_ONLY set
# a SPARSE checkout fetches those directories and nothing else --
# amdgpu alone is a few hundred megabytes. Without it, the whole
# thing, which is what "empty means everything" costs.
if [ -d "$src/.git" ]; then
echo "# updating linux-firmware"
su - "$acct" -c "cd '$src' && git pull --ff-only" || return 1
if [ -n "$only" ]; then
# not `|| true`: if this fails the checkout keeps whatever
# the last run selected, and the missing firmware is then
# blamed on the pattern
su - "$acct" -c "cd '$src' && git sparse-checkout set $(_sparse_mode "$only") $(_sparse_args "$only")" \
|| { echo "!! could not narrow the firmware checkout to: $only"; return 1; }
fi
elif [ -n "$only" ]; then
echo "# cloning linux-firmware, only: $only"
su - "$acct" -c "git clone --depth 1 --filter=blob:none --sparse '$FIRMWARE_GIT' '$src'" || return 1
# --no-cone when any entry is a PATTERN rather than a
# directory: Intel wireless firmware is iwlwifi-cc-a0-*.ucode
# at the top level, and cone mode (the default) matches
# directories only, so those files would never arrive.
su - "$acct" -c "cd '$src' && git sparse-checkout set $(_sparse_mode "$only") $(_sparse_args "$only")" || return 1
else
echo "# cloning ALL of linux-firmware -- this is over 10 GB."
echo "# set FIRMWARE_ONLY in $CONF to fetch only what you need."
su - "$acct" -c "git clone --depth 1 '$FIRMWARE_GIT' '$src'" || return 1
fi ;;
*) echo "!! FIRMWARE_FROM=$FIRMWARE_FROM not understood"; return 1 ;;
esac
# LIB/FIRMWARE IS THIS PACKAGE'S DIRECTORY.
#
# It already existed, owned by root or by whoever made it first, so every
# copy failed with "Permission denied" -- two hundred lines of it. A
# package user cannot chown, and firmware has exactly one owner, so root
# hands the directory over here. (Where another package genuinely shares
# a directory, the collector groups do this; that is not the case for
# /lib/firmware.)
local d
install -d -m 755 /lib/firmware || return 1
if [ "$(stat -c %U /lib/firmware 2>/dev/null)" != "$acct" ]; then
echo "# handing /lib/firmware to $acct (it owns the firmware)"
chown "$acct:$acct" /lib/firmware || return 1
fi
if [ -z "$only" ]; then
# An EMPTY value is nearly always a quoting accident, not a request
# for ten gigabytes: `FIRMWARE_ONLY=amdgpu intel iwlwifi-cc-*`
# unquoted runs `intel` as a command and assigns nothing. Ask.
echo "!! FIRMWARE_ONLY is empty, which means ALL of linux-firmware"
echo " (over 10 GB). If that is really what you want, set"
echo " FIRMWARE_ALL=yes"
echo " in $CONF. If not, the value probably needs quoting:"
echo ' FIRMWARE_ONLY="amdgpu intel iwlwifi-cc-*"'
[ "${FIRMWARE_ALL:-no}" = yes ] || return 1
echo "# installing ALL of linux-firmware (FIRMWARE_ALL=yes)"
su - "$acct" -c "cp -a '$src'/* /lib/firmware/" || return 1
else
for d in $only; do
# AN ENTRY FOR SOMETHING THIS REPOSITORY DOES NOT HAVE IS NOT AN
# ERROR WHEN WE KNOW WHERE IT DOES LIVE. `regulatory.db*` in
# FIRMWARE_ONLY failed the entire firmware install, and the
# message blamed the pattern.
_skip=0
while IFS='|' read -r _ep _epkg _eurl; do
[ -n "${_ep:-}" ] || continue
case "$_ep" in
$d) _skip=1 ;;
esac
done < <(_fw_elsewhere)
if [ "$_skip" = 1 ]; then
echo "# '$d' is not in linux-firmware -- it is wireless-regdb;"
echo "# installing that instead (REGDB=${REGDB:-yes})"
_install_regdb || echo "# ...which did not work; see above"
continue
fi
# a pattern matches files at the top level; a name is a directory
case "$d" in
*[*?[]*)
# A PATTERN MATCHES AT ANY DEPTH, AND A LINK NAME COUNTS.
#
# linux-firmware moved iwlwifi-*.ucode into intel/iwlwifi/
# and keeps the flat names the driver asks for as `Link:`
# lines in WHENCE. The sparse checkout (gitignore rules)
# already fetched them at their new depth; this looked
# only at the top level and stopped the kernel build.
local _hits _links
_hits="$(cd "$src" && find . -name .git -prune -o \( -type f -o -type l \) -name "$d" -print 2>/dev/null | sed 's|^\./||')"
_links="$(_fw_whence_link_names "$src" | while IFS= read -r _ln; do
case "$_ln" in $d) echo "$_ln" ;; esac; done)"
if [ -z "$_hits" ] && [ -z "$_links" ]; then
_firmware_not_found "$src" "$d"; return 1
fi
echo "# installing firmware: $d ($(printf '%s\n' "$_hits" | grep -c .) file(s)$([ -n "$_links" ] && printf ', %s flat link(s) from WHENCE' "$(printf '%s\n' "$_links" | grep -c .)"))"
# --parents, and from inside the checkout: `cp -a
# $src/intel/iwlwifi /lib/firmware/` dropped the intel/
# and landed as /lib/firmware/iwlwifi, where nothing
# looks for it. An entry keeps its path.
if [ -n "$_hits" ]; then
printf '%s\n' "$_hits" \
| su - "$acct" -c "cd '$src' && xargs -r -d '\\n' cp -a --parents -t /lib/firmware/" || return 1
fi ;;
*)
# ...the same help for a NAME. Only the pattern case
# searched, so `iwlwifi` (a directory that does not
# exist in this release) said "does not exist" and left
# the reader exactly where the pattern had.
[ -e "$src/$d" ] || { _firmware_not_found "$src" "$d"; return 1; }
echo "# installing firmware: $d"
su - "$acct" -c "cd '$src' && cp -a --parents '$d' /lib/firmware/" || return 1 ;;
esac
done
fi
[ -n "$only" ] && _fw_whence_links "$src" "$acct"
_install_regdb || true
packagemanager reload-pkg-list linux-firmware >/dev/null 2>&1 || true
return 0
}
# WHENCE's `Link: <name> -> <target>` lines are how linux-firmware keeps the
# names drivers ask for after moving a blob (iwlwifi-cc-a0-77.ucode ->
# intel/iwlwifi/iwlwifi-cc-a0-77.ucode). Its own copy-firmware.sh makes
# them; a selective install has to as well. The target is relative to the
# link's directory.
_fw_whence_link_names() {
[ -r "$1/WHENCE" ] || return 0
sed -n 's/^Link:[[:space:]]*\([^[:space:]]*\)[[:space:]]*->.*/\1/p' "$1/WHENCE"
}
_fw_whence_links() {
local src="$1" acct="$2" name target made=0
[ -r "$src/WHENCE" ] || { echo "# (no WHENCE in the checkout -- flat firmware names not linked)"; return 0; }
while read -r name target; do
[ -n "$name" ] && [ -n "$target" ] || continue
# only for blobs that were installed, and never over a real file
[ -e "/lib/firmware/$(dirname "$name")/$target" ] || continue
[ -e "/lib/firmware/$name" ] || [ -L "/lib/firmware/$name" ] && continue
su - "$acct" -c "mkdir -p '/lib/firmware/$(dirname "$name")' && ln -s '$target' '/lib/firmware/$name'" \
&& made=$((made + 1))
done < <(sed -n 's/^Link:[[:space:]]*\([^[:space:]]*\)[[:space:]]*->[[:space:]]*\([^[:space:]]*\).*/\1 \2/p' "$src/WHENCE")
[ "$made" -gt 0 ] && echo "# linked $made flat firmware name(s) from WHENCE"
return 0
}
# REPORT WHAT YOU FOUND, NOT WHAT YOU EXPECTED.
#
# FIRMWARE_ONLY="... intel" installed intel/, and the AX200 still had no
# firmware, because the driver asks for a FLAT path:
# iwlwifi 0000:03:00.0: Direct firmware load for iwlwifi-cc-a0-77.ucode failed
# iwlwifi 0000:03:00.0: no suitable firmware found!
# Four releases went into guessing linux-firmware's layout from the outside.
# The running kernel already knows the answer -- it printed the exact path it
# wanted -- and the checkout's git index knows where that file lives. So ask
# both, instead of reasoning about directory names.
_fw_requests() {
# every firmware path THIS boot asked for and did not get
dmesg 2>/dev/null | sed -n \
-e 's/.*Direct firmware load for \([^ ]*\) failed.*/\1/p' \
-e 's/.*firmware: failed to load \([^ ]*\).*/\1/p' \
| sed 's/[,:]$//' | sort -u
}
# ASK THE INDEX -- AND NOTICE WHEN THE ASKING FAILED.
#
# `git -C "$src" ls-files 2>/dev/null` returned nothing and rc 128, because
# the repository belongs to p_linux-firmware and this runs as root:
# fatal: detected dubious ownership in repository at '...'
# The empty output was then reported as a FACT -- "linux-firmware does not
# have a file by that name at all" -- about a file that is right there. An
# error hidden behind 2>/dev/null becomes a confident wrong answer.
_FW_INDEX_ERR=""
_fw_index() { # _fw_index <repo> [<basename to match>]
local src="$1" want="${2:-}" out="" err=""
_FW_INDEX_ERR=""
if [ -d "$src/.git" ]; then
# -c safe.directory: the checkout is the firmware account's and this
# runs as root, which git refuses to touch without being told.
# the scratch file goes in /tmp, not in the repository: leaving
# untracked files in someone else's checkout is not this tool's business
local tmp; tmp="$(mktemp)"
# ls-TREE, not ls-files. With a sparse index git collapses every
# excluded directory into one entry, so `ls-files` answered `intel/`
# and every nested blob under it looked absent -- 56 files reported
# as "this checkout has no file by that name" while they were all
# right there. ls-tree reads the commit, which no sparse setting
# touches.
err="$(git -c "safe.directory=$src" -C "$src" ls-tree -r --name-only HEAD 2>&1 >"$tmp")"
if [ -n "$err" ] && [ ! -s "$tmp" ]; then
_FW_INDEX_ERR="$err"
rm -f "$tmp"
return 1
fi
out="$(cat "$tmp")"
rm -f "$tmp"
else
out="$(cd "$src" && find . -type f -printf '%P\n' 2>/dev/null)"
fi
if [ -n "$want" ]; then
printf '%s\n' "$out" | grep -- "\(^\|/\)$want$"
else
printf '%s\n' "$out"
fi
}
_fw_repo() { # the linux-firmware checkout, or empty
local home
home="$(getent passwd p_linux-firmware 2>/dev/null | cut -d: -f6)"
case "$FIRMWARE_FROM" in
copy:*) printf '%s' "${FIRMWARE_FROM#copy:}" ;;
*) [ -n "$home" ] && printf '%s' "$home/src/linux-firmware" ;;
esac
}
# WHICH KERNEL'S MODULES? In a chroot the running kernel is the host's, so
# `uname -r` is the wrong answer. Prefer the version this tool is configured
# for, then whatever is installed under /lib/modules, and SAY which one.
_fw_modver() {
local v
for v in "${VERSION:-}" "$(uname -r)"; do
[ -n "$v" ] && [ -d "/lib/modules/$v" ] && { printf '%s' "$v"; return 0; }
done
ls -1d /lib/modules/*/ 2>/dev/null | sed 's|.*/modules/||; s|/$||' \
| sort -V | tail -n1
}
# Firmware that is NOT in linux-firmware, so "not in the checkout" is the
# right answer and updating the checkout will never help.
_fw_elsewhere() {
cat <<'ELSE'
regulatory.db|wireless-regdb|https://kernel.org/pub/software/network/wireless-regdb/
regulatory.db.p7s|wireless-regdb|https://kernel.org/pub/software/network/wireless-regdb/
ELSE
}
# WHAT WILL BE ASKED FOR -- from sources that also work in a chroot.
#
# dmesg answers "what did the kernel that is running ask for", which in a
# chroot is the host's boot: it reported one missing file for 7.2.0 while the
# kernel of interest was 7.2.4, and said nothing about the wireless at all.
# The tree can be asked directly instead: the modules it installed declare
# their firmware (modinfo -F firmware), and the hardware is right here in
# /sys, so the two together give the list for THIS machine and THIS kernel
# without booting it.
#
# Prints "<source>|<path>" so every line can say where the fact came from.
_fw_wanted() { # _fw_wanted <modules-version>
local ver="${1:-}" a m f cfg
# 1. the boot that is running -- _fw_requests is the one dmesg parser
_fw_requests | sed 's/^/boot|/'
# 2. this tree's modules, for the hardware that is actually present
if [ -n "$ver" ] && [ -d "/lib/modules/$ver" ] \
&& command -v modinfo >/dev/null 2>&1; then
find /sys/devices -name modalias -print 2>/dev/null \
| xargs -r cat 2>/dev/null | sort -u \
| while IFS= read -r a; do
[ -n "$a" ] || continue
# modprobe resolves an alias through the TREE's
# modules.alias when told which version to use
modprobe -S "$ver" -R -- "$a" 2>/dev/null || true
done | sort -u \
| while IFS= read -r m; do
[ -n "$m" ] || continue
# the MODULE's name travels with the request: a module
# declares firmware for every chip it supports, so these
# are candidates, and which module they came from is the
# only thing that makes them readable
modinfo -k "$ver" -F firmware -- "$m" 2>/dev/null \
| sed "s|^|modules:$m\||"
done
fi
# 3. what the config embeds -- those must be on disk to BUILD, and are
# the one part a running kernel never complains about
for cfg in "$ESP/config-$ver" "/boot/config-$ver" /proc/config.gz; do
[ -r "$cfg" ] || continue
case "$cfg" in
*.gz) zcat "$cfg" 2>/dev/null ;;
*) cat "$cfg" ;;
esac | sed -n 's/^CONFIG_EXTRA_FIRMWARE="\(.*\)"$/\1/p' \
| tr ' ' '\n' | sed '/^$/d; s/^/config|/'
break
done
}
# A MODULE DECLARES FIRMWARE FOR EVERY CHIP IT SUPPORTS.
#
# The first version of the modules scan printed 56 lines of "MISSING", each
# with its own remedy: amdgpu blobs for Vega, Sienna Cichlid and Aldebaran,
# iwlwifi blobs for five RF families this laptop does not have. Every one of
# them was a fact -- `modinfo -F firmware amdgpu` really does list them --
# and the conclusion drawn from them ("56 missing") was wrong: the driver
# loads what its own chip needs and ignores the rest. So a boot failure and
# a module declaration are not the same kind of statement and must not be
# printed the same way.
# WHAT IS THERE, NEXT TO WHAT IS NOT.
#
# `iwlwifi-cc-a0-77.ucode` missing, on a machine whose wifi worked under the
# previous kernel, means one of two things -- the file is on disk under
# another directory, or an OLDER API version of it is installed and the new
# driver will not take it -- and the report said neither. Both are one
# `find` and one glob away, and both change what you do next.
_FW_LOCAL=""
_fw_neighbours() { # _fw_neighbours <requested-path> [<repo>]
# THE EXPLANATION GOES TO THE READER, THE PATH GOES IN A VARIABLE.
# Returning the path on stdout meant the caller's `$(...)` swallowed
# every line of the explanation with it, and the report said only
# "installed" with no word about where the file had been.
local req="$1" src="${2:-}" base stem ext found sib _api
_FW_LOCAL=""
base="$(basename "$req")"
# 1. the same file, somewhere else under /lib/firmware. FIRMWARE_ONLY
# entries keep the repository's path, and a driver asks the loader for
# its own -- so `intel` installs intel/iwlwifi/x.ucode while the
# driver wants x.ucode at the top.
found="$(find /lib/firmware -name "$base" -printf '%P\n' 2>/dev/null \
| grep -v "^$req$" | head -3)"
if [ -n "$found" ]; then
echo " it IS on disk, at another path:"
printf ' /lib/firmware/%s\n' $found
echo " the loader asks for /lib/firmware/$req and nothing else,"
echo " so --fix puts a copy there."
_FW_LOCAL="$(printf '%s' "$found" | head -n1)"
return 0
fi
# 2. an API-versioned sibling: iwlwifi-cc-a0-72.ucode when -77 is asked
# for. The number is the firmware API the driver demands, so an older
# one on disk is the whole story: this kernel needs a newer blob.
case "$base" in
*-[0-9][0-9].*|*-[0-9][0-9][0-9].*)
ext="${base##*.}"
stem="${base%-*}"
sib="$(ls -1 "/lib/firmware/$(dirname "$req")/$stem"-*."$ext" \
2>/dev/null | xargs -r -n1 basename | tr '\n' ' ')"
[ -n "$sib" ] && {
echo " installed already, for the same device but an older"
echo " firmware API: $sib"
_api="${base##*-}"; _api="${_api%.*}"
echo " This kernel's driver requires API $_api and will not"
echo " fall back, so the checkout needs to be newer."; }
if [ -n "$src" ] && [ -d "$src" ]; then
sib="$(_fw_index "$src" | grep -- "\(^\|/\)$stem-[0-9]*\.$ext$" \
| xargs -r -n1 basename | sort -V | tail -3 | tr '\n' ' ')"
[ -n "$sib" ] && echo " the checkout has: $sib"
fi ;;
esac
return 1
}
_firmware_missing() {
local fix="${1:-0}" all="${2:-0}" src req f hits n=0 gone=0 fixed=0 failed=0
local sugg="" ver source chroot=0 _rr _tr seen="" cand=0 cdir mod
src="$(_fw_repo)"
ver="$(_fw_modver)"
cdir="$(mktemp -d)"
_rr="$(stat -c %d:%i /proc/1/root/. 2>/dev/null || true)"
_tr="$(stat -c %d:%i / 2>/dev/null || true)"
[ -n "$_rr" ] && [ -n "$_tr" ] && [ "$_rr" != "$_tr" ] && chroot=1
echo "### firmware this machine will ask for, and has not got"
echo "# modules scanned: ${ver:-(none installed under /lib/modules)}"
echo "# running kernel: $(uname -r)$([ "$chroot" = 1 ] && printf ' (the HOST kernel -- this is a chroot)')"
if [ "$chroot" = 1 ]; then
echo "# in a chroot, 'boot' lines describe the HOST's boot -- possibly"
echo "# another distribution on other hardware, in which case they can"
echo "# neither confirm nor deny anything about this tree. 'modules'"
echo "# and 'config' lines are this tree's."
fi
[ -z "$ver" ] && echo "# no modules to scan, so only the running boot can be asked."
echo
while IFS='|' read -r source req; do
[ -n "${req:-}" ] || continue
case "$req" in */) continue ;; esac
case " $seen " in *" $req "*) continue ;; esac
seen="${seen:-} $req"
n=$((n + 1))
if [ -e "/lib/firmware/$req" ]; then
# SAY WHAT IS SATISFIED, especially here. A boot line that is
# now present is the answer to "will this work when I boot it",
# and it was skipped silently -- so a run with everything in
# place looked identical to one that had not looked at all.
case "$source" in
modules:*) printf '%s\n' "$req" >> "$cdir/${source#modules:}.ok" ;;
*) echo " ok $req ($source) -- installed now" ;;
esac
continue
fi
# A DECLARATION IS A CANDIDATE, NOT A FAILURE. Collected per module
# and summarised below; only boot and config lines are treated as
# things that must be fixed.
case "$source" in
modules:*)
mod="${source#modules:}"
printf '%s\n' "$req" >> "$cdir/$mod"
cand=$((cand + 1))
[ "$all" = 1 ] || continue ;;
esac
gone=$((gone + 1))
echo " MISSING /lib/firmware/$req ($source)"
while IFS='|' read -r _ep _epkg _eurl; do
[ "$_ep" = "$req" ] || continue
echo " not part of linux-firmware: it is released as $_epkg"
echo " $_eurl"
if [ "$fix" = 1 ] && [ "$_epkg" = wireless-regdb ]; then
_install_regdb force && fixed=$((fixed + 1))
else
echo " install it: lfs-kernel firmware --missing --fix"
echo " (without it cfg80211 falls back to the most"
echo " conservative channels and power -- wifi still works)"
fi
continue 2
done < <(_fw_elsewhere)
# WHAT IS THERE, before what is not: a copy under another path, or an
# older API version, answers the question without the repository.
_fw_neighbours "$req" "$src"
if [ -n "$_FW_LOCAL" ] && [ -e "/lib/firmware/$_FW_LOCAL" ]; then
if [ "$fix" = 1 ]; then
_fw_place "/lib/firmware" "$req" "$_FW_LOCAL" \
&& fixed=$((fixed + 1))
fi
continue
fi
if [ -z "$src" ] || [ ! -d "$src" ]; then
echo " no linux-firmware checkout to look in"
echo " (FIRMWARE_FROM=$FIRMWARE_FROM; run lfs-kernel firmware first)"
continue
fi
hits="$(_fw_index "$src" "$(basename "$req")" | head -4)"
if [ -n "$_FW_INDEX_ERR" ]; then
echo " could not read the repository, so where this file"
echo " lives is UNKNOWN -- not absent:"
printf ' %s\n' "$_FW_INDEX_ERR"
failed=$((failed + 1))
continue
fi
if [ -z "$hits" ]; then
echo " this checkout has no file by that name."
echo " su - p_linux-firmware -c 'git -C $src pull'"
continue
fi
printf ' the repository has it at: %s\n' $hits
for f in $hits; do
if [ "$f" = "$req" ]; then
case "$f" in
*/) ;;
*/*) sugg="$sugg ${f%/*}" ;;
*) sugg="$sugg $f" ;;
esac
else
echo " no FIRMWARE_ONLY entry can express this: the"
echo " repository keeps it at '$f', the driver asks for"
echo " '$req'. --fix installs it where the loader looks."
fi
done
[ "$fix" = 1 ] && _fw_place "$src" "$req" "$(echo $hits | cut -d" " -f1)" \
&& fixed=$((fixed + 1))
done < <(_fw_wanted "$ver")
[ "$gone" = 0 ] && [ "$n" -gt 0 ] \
&& echo " nothing that has actually been asked for is missing."
# ---- the candidates, per module, in a few lines rather than fifty ----
if [ "$cand" -gt 0 ] && [ "$all" != 1 ]; then
echo
echo "Declared but not installed -- CANDIDATES, not failures. A module"
echo "declares firmware for every chip it supports; yours loads only"
echo "the ones it needs, and never asks for the others:"
for f in "$cdir"/*; do
[ -f "$f" ] || continue
case "$f" in *.ok) continue ;; esac
mod="$(basename "$f")"
echo " $mod: $(grep -c . "$f") of $(( $(grep -c . "$f") \
+ $( [ -f "$cdir/$mod.ok" ] && grep -c . "$cdir/$mod.ok" || echo 0) )) \
declared file(s) not installed, e.g. $(head -n1 "$f")"
done
if [ "$chroot" = 1 ]; then
echo " If a driver you need is silent, boot THIS kernel and run this"
echo " again there: the 'boot' list above is the host's, and says"
echo " nothing about the driver you are asking about."
else
echo " If a driver you need is silent, look at what it actually asked"
echo " for -- the 'boot' list above, which is this kernel's."
fi
echo " To install every declared file anyway (large, mostly unused):"
echo " lfs-kernel firmware --missing --fix --all"
fi
# WHICH FILE A DRIVER PICKS IS DECIDED AT RUN TIME. iwlwifi derives the
# name from the RF and CRF ids it reads off the card, so no amount of
# looking at modules and directories can promise it will find one. Say
# that, rather than implying a green run is a working driver.
if [ "$cand" -gt 0 ] || [ "$gone" -gt 0 ]; then
echo
echo " Only a boot of THIS kernel settles it: a driver derives the"
echo " firmware name from the ids it reads off the card, so what is"
echo " installed can be checked here, and what will be asked for"
echo " cannot."
fi
rm -rf "$cdir"
echo
if [ "$n" = 0 ]; then
echo "Nothing asks for firmware here: no failed load in this boot, and"
echo "no module under /lib/modules${ver:+/$ver} declares any."
return 0
fi
if [ "$gone" = 0 ]; then
return 0
fi
if [ "$fix" = 1 ]; then
echo "Placed $fixed of $gone file(s) at the exact path the driver asked for."
if [ -n "$sugg" ]; then
echo "Make it survive the next full run -- in $CONF:"
echo " FIRMWARE_ONLY=\"$FIRMWARE_ONLY$(printf '%s' "$sugg")\""
fi
[ "$fixed" -gt 0 ] && {
echo "Then load the driver again (or reboot):"
echo " modprobe -r <driver> # iwlwifi, amdgpu, ..."; }
if [ "$failed" -gt 0 ]; then
echo "$failed lookup(s) failed, so nothing is known about those files."
return 1
fi
[ "$fixed" = "$gone" ] && return 0
echo "Still missing: $((gone - fixed))."
return 1
fi
echo "Nothing was changed. Install what linux-firmware has:"
echo " lfs-kernel firmware --missing --fix"
if [ -n "$sugg" ]; then
echo "or add them to FIRMWARE_ONLY in $CONF and re-run lfs-kernel firmware:"
echo " FIRMWARE_ONLY=\"$FIRMWARE_ONLY$(printf '%s' "$sugg")\""
fi
return 1
}
# Install ONE blob at the path the driver actually asked for. The driver's
# path and the repository's path are not the same thing (iwlwifi asks for a
# flat name; the repository may keep it under a directory), and it is the
# driver's that has to exist.
#
# ONE FILE MEANS ONE FILE. The first version widened the sparse checkout,
# and fell back to `sparse-checkout disable` when the add was refused -- in
# cone mode a file path is refused, so the fallback would have materialised
# the whole of linux-firmware: over ten gigabytes, to fix one 2 MB blob.
# `git show HEAD:<path>` reads exactly one blob out of the repository and
# needs no sparse mode at all (in a partial clone it fetches that blob and
# nothing else).
_fw_place() { # <src-repo> <requested-path> <repo-path>
local src="$1" req="$2" rel="$3" acct=p_linux-firmware
# SEPARATE STATEMENT. `local a="$2" d="/lib/firmware/$a"` reads $a before
# this local has assigned it, and `set -u` kills the function:
# lfs-kernel: line ...: req: unbound variable
local dst="/lib/firmware/$req"
id "$acct" >/dev/null 2>&1 || packagemanager add-user linux-firmware || return 1
# /lib/firmware is this package's directory (the full run hands it over);
# the account has to be able to write the file it is about to own.
install -d -m 755 /lib/firmware 2>/dev/null
[ "$(stat -c %U /lib/firmware 2>/dev/null)" = "$acct" ] \
|| chown "$acct:$acct" /lib/firmware 2>/dev/null
case "$req" in
*/*) install -d -o "$acct" -g "$acct" -m 755 "/lib/firmware/$(dirname "$req")" \
2>/dev/null || true ;;
esac
if [ -e "$src/$rel" ]; then
local _cerr
_cerr="$(su - "$acct" -c "cp -a '$src/$rel' '$dst'" 2>&1)" || {
echo " !! could not copy it as $acct:"
printf ' %s\n' "${_cerr:-(no message)}"; return 1; }
elif [ -d "$src/.git" ]; then
echo " reading it out of the repository (it is in the index,"
echo " not in the sparse checkout)"
local _gerr
# git's own message, not a guess about what went wrong: "does not
# exist in HEAD" and "unable to read sha1 file" (a partial clone with
# no network) need different answers from the reader.
_gerr="$(su - "$acct" -c "git -c 'safe.directory=$src' -C '$src' show 'HEAD:$rel' > '$dst'" 2>&1)" || {
echo " !! git could not produce $rel:"
printf ' %s\n' "${_gerr:-(no message)}"
echo " a partial clone fetches the blob on demand, so"
echo " this needs the network; check with:"
echo " su - $acct -c 'git -C $src fetch --depth 1'"
rm -f "$dst"; return 1; }
else
echo " !! $rel is not in $src"
return 1
fi
# CONFIRM YOUR OWN EFFECT: a failed `git show` can leave an empty file
# behind, which looks exactly like a successful install.
if [ -s "$dst" ]; then
echo " installed $dst ($(stat -c %s "$dst") bytes)"
packagemanager reload-pkg-list linux-firmware >/dev/null 2>&1 || true
return 0
fi
echo " !! $dst is empty or absent after the copy"
rm -f "$dst"
return 1
}
# ---- firmware without a kernel build --------------------------------------
if [ "${_FWONLY:-0}" = 1 ]; then
case "${_FWARG:-}" in
--missing|missing)
_firmware_missing "${_FWFIX:-0}" "${_FWALL:-0}"; exit $? ;;
"")
[ "$FIRMWARE_FROM" = none ] \
&& { echo "!! FIRMWARE_FROM=none in $CONF -- nothing to install."; exit 1; }
_install_firmware || exit 1
echo "# firmware installed; no kernel was built or touched."
exit 0 ;;
*) echo "!! lfs-kernel firmware takes --missing [--fix] or nothing"; exit 1 ;;
esac
fi
echo "# kernel $VERSION -> $ESP (tree $tree, $JOBS)"
# ---- the plan, when --run was not given ----------------------------------
if [ "${_RUN:-0}" != 1 ]; then
echo
echo "### what a build would do (nothing has been built)"
echo " version $VERSION"
if [ -d "$tree" ]; then
_bz="$tree/arch/x86/boot/bzImage"
if [ -f "$tree/.config" ] && [ -f "$_bz" ] && [ "$_bz" -nt "$tree/.config" ]; then
echo " tree $tree (already built -- make would do little)"
elif [ -f "$tree/.config" ]; then
echo " tree $tree (a build was started; make CONTINUES)"
else
echo " tree $tree (unpacked, not configured)"
fi
else
echo " tree $tree -- would be fetched and unpacked"
fi
echo " config from $CONFIG_FROM, refreshed with ${CONFIG_REFRESH:-olddefconfig}"
[ -n "${CONFIG_SET:-}" ] && echo " then set $CONFIG_SET"
echo " installs $ESP/$KERNEL_NAME (previous kept as ${KERNEL_NAME}_old)"
echo " /lib/modules/$VERSION, and the config beside the image"
case "${FIRMWARE_FROM:-none}" in
none) echo " firmware not touched (FIRMWARE_FROM=none)" ;;
*) echo " firmware ${FIRMWARE_FROM}: ${FIRMWARE_ONLY:-everything}" ;;
esac
[ "${HEADERS:-no}" = yes ] \
&& echo " headers /usr/include, as ${HEADERS_PKG:-linux-headers}"
case "${V4L2LOOPBACK:-no}" in yes|1|true)
echo " also v4l2loopback, built against this tree" ;;
esac
echo
echo " This is an hour of compiling on most machines, and it REPLACES"
echo " what $ESP/$KERNEL_NAME boots."
echo " Go ahead: lfs-kernel${_CLI_VERSION:+ $_CLI_VERSION} --run"
echo " Where we are: lfs-kernel status"
exit 0
fi
# --------------------------------------------------------------------------
# THE COMMANDS. Everything above chooses the version and checks the paths;
# everything below is the build, and is meant to be read.
# --------------------------------------------------------------------------
install -d "$SRC"
if [ ! -d "$tree" ]; then
[ -f "$tarball" ] || {
echo "# fetching $MIRROR_TAR/v$major.x/linux-$VERSION.tar.xz"
wget -q --show-progress -O "$tarball.part" \
"$MIRROR_TAR/v$major.x/linux-$VERSION.tar.xz" \
&& mv "$tarball.part" "$tarball" \
|| { rm -f "$tarball.part"
echo "!! download failed -- KERNEL_LIST=1 shows what exists"; exit 1; }
}
tar -xf "$tarball" -C "$SRC" || exit 1
chown -R "$acct:$acct" "$tree" "$tarball" 2>/dev/null || true
fi
[ -d "$tree" ] || { echo "!! $tree is missing"; exit 1; }
# boot logo (optional)
if [ -n "${BOOTLOGO:-}" ]; then
logo="$SRC/$(basename "$BOOTLOGO")"
[ -f "$logo" ] || { wget -qO "$logo" "$BOOTLOGO" && chown "$acct:$acct" "$logo"; } || logo=""
[ -n "$logo" ] && _as_pkg "cp -v '$logo' drivers/video/logo/logo_linux_clut224.ppm"
fi
# .config
# running /proc/config.gz -- the kernel carrying the machine
# last-good the config of the last kernel that BOOTED ($KCFG_DIR)
# prev-good the one before it -- the way back from a bad answer
# saved the newest config this tool built with, booted or not
# file:<path> any file; keep the tree's own .config
_cfg_src=""
case "$CONFIG_FROM" in
running)
if [ -r /proc/config.gz ]; then
_as_pkg "gunzip < /proc/config.gz > .config" || exit 1
elif _kcfg_readable "$KCFG_DIR/last-good.config"; then
# NOT A FAILURE WORTH STOPPING FOR. A kernel built without
# CONFIG_IKCONFIG_PROC has no /proc/config.gz, and the config of
# the last kernel that booted is the same answer from a file.
echo "# /proc/config.gz is not readable (no CONFIG_IKCONFIG_PROC?)"
_cfg_src="$KCFG_DIR/last-good.config"
else
echo "!! /proc/config.gz unreadable, and no saved config either."
echo " Set CONFIG_FROM=file:<path> in $CONF, or see:"
echo " lfs-kernel config list"
exit 1
fi ;;
last-good|good) _cfg_src="$KCFG_DIR/last-good.config" ;;
prev-good|prev) _cfg_src="$KCFG_DIR/prev-good.config" ;;
saved|last) _cfg_src="$(_kcfg_newest)" ;;
file:*) _as_pkg "cp -v '${CONFIG_FROM#file:}' .config" || exit 1 ;;
keep) [ -f "$tree/.config" ] || { echo "!! no .config in $tree"; exit 1; } ;;
*) echo "!! CONFIG_FROM=$CONFIG_FROM not understood"; exit 1 ;;
esac
if [ -n "$_cfg_src" ]; then
_kcfg_readable "$_cfg_src" || {
echo "!! CONFIG_FROM=$CONFIG_FROM, but $_cfg_src is not a config."
echo " lfs-kernel config list # what there is"
exit 1; }
echo "# starting from $_cfg_src (kernel $(_kcfg_ver "$_cfg_src"))"
_as_pkg "cp '$_cfg_src' .config" || exit 1
fi
# ---- refreshing the config for a NEW kernel ------------------------------
#
# A config from one kernel is never complete for another: every version adds
# symbols the old file has no answer for. Something must decide them, and
# which something matters:
#
# olddefconfig take the DEFAULT for each new symbol, no questions. The
# right choice for an unattended run, and what this tool has
# always done -- but the defaults are upstream's, not yours.
# oldconfig ask about each new symbol, one at a time. Slow, and needs
# someone at the terminal, but nothing is decided silently.
# menuconfig open the editor and decide everything at once.
# none use the .config exactly as it is; `make` will then ask, or
# fail, depending on what is missing.
#
# It also says how many symbols the refresh had to answer, because "47 new
# symbols were set to their defaults" is worth knowing before a boot.
: "${CONFIG_REFRESH:=olddefconfig}"
_cfg_before="$(mktemp)"; cp "$tree/.config" "$_cfg_before" 2>/dev/null || true
case "$CONFIG_REFRESH" in
none) echo "# not refreshing the config (CONFIG_REFRESH=none)" ;;
olddefconfig|oldconfig|menuconfig|*config)
echo "# refreshing the config for $VERSION (make $CONFIG_REFRESH) ..."
_as_pkg "make $CONFIG_REFRESH" || exit 1 ;;
*) echo "!! CONFIG_REFRESH=$CONFIG_REFRESH is not understood"; exit 1 ;;
esac
if [ -s "$_cfg_before" ] && [ "$CONFIG_REFRESH" != none ]; then
_newsym="$(diff <(sort "$_cfg_before") <(sort "$tree/.config") 2>/dev/null \
| grep -c '^>' || true)"
[ "${_newsym:-0}" -gt 0 ] \
&& echo "# $_newsym config line(s) differ from the config you started from"
fi
rm -f "$_cfg_before"
# ---- CONFIG_SET: this machine's own answers, applied last ----------------
#
# The config comes from the running kernel (or a file), then a refresh
# answers the new symbols. An option you decided on has to survive both, so
# it is applied AFTER them, resolved once more (olddefconfig -- a forced
# symbol can pull or drop others), and READ BACK: `scripts/config` writes
# what it is told, and the resolve then silently reverts anything whose
# dependencies are not met.
if [ -n "${CONFIG_SET:-}" ]; then
_cs_args=""
for _cs in $CONFIG_SET; do
_sym="${_cs%%=*}"; _val="${_cs#*=}"; _sym="${_sym#CONFIG_}"
[ "$_sym" != "$_cs" ] || { echo "!! CONFIG_SET entry '$_cs' has no '=' -- SYMBOL=VALUE"; exit 1; }
case "$_val" in
y) _cs_args="$_cs_args --enable $_sym" ;;
m) _cs_args="$_cs_args --module $_sym" ;;
n) _cs_args="$_cs_args --disable $_sym" ;;
[0-9]*) _cs_args="$_cs_args --set-val $_sym $_val" ;;
*) _cs_args="$_cs_args --set-str $_sym ${_val//\"/}" ;;
esac
done
echo "# applying CONFIG_SET: $CONFIG_SET"
_as_pkg "./scripts/config $_cs_args && make olddefconfig" >/dev/null || exit 1
_cs_bad=""
for _cs in $CONFIG_SET; do
_sym="${_cs%%=*}"; _val="${_cs#*=}"; _sym="CONFIG_${_sym#CONFIG_}"
case "$_val" in
n) grep -qE "^$_sym=(y|m)" "$tree/.config" && _cs_bad="$_cs_bad $_sym=n" ;;
y|m) grep -qx "$_sym=$_val" "$tree/.config" || _cs_bad="$_cs_bad $_sym=$_val" ;;
*) grep -qx "$_sym=$_val" "$tree/.config" \
|| grep -qx "$_sym=\"${_val//\"/}\"" "$tree/.config" \
|| _cs_bad="$_cs_bad $_sym=$_val" ;;
esac
done
if [ -n "$_cs_bad" ]; then
echo "!! CONFIG_SET did not take -- the kernel reverted these:"
for _cs in $_cs_bad; do
echo " $_cs (now: $(grep -E "^(# )?${_cs%%=*}[= ]" "$tree/.config" | head -1))"
done
echo " a dependency is missing, or $VERSION has no such symbol."
echo " Look it up: cd $tree && make menuconfig (then / and the name)"
exit 1
fi
echo "# all $(echo $CONFIG_SET | wc -w) set, read back from .config"
fi
# THE CONFIG IS KEPT BEFORE ANYTHING IS COMPILED. It is finished here --
# source, refresh and CONFIG_SET all applied -- and a build that dies at
# hour two must not take the answers with it.
_kcfg_archive "$tree/.config" "$VERSION"
# what a Wayland compositor needs; a warning, not a refusal
for opt in CONFIG_DRM CONFIG_DRM_FBDEV_EMULATION CONFIG_INPUT_EVDEV CONFIG_SYSVIPC; do
grep -qE "^$opt=(y|m)" "$tree/.config" || echo "!! $opt is not set (sway needs it)"
done
# Drivers that need firmware are no use without the driver. These are the
# ones a laptop misses most often; the firmware for each is in FIRMWARE_ONLY.
for opt in CONFIG_IWLWIFI:"Intel wireless (AX200 and friends)" \
CONFIG_IWLMVM:"the AX200's own MVM firmware layer" \
CONFIG_BT_HCIBTUSB:"USB Bluetooth (the AX200's other half)" \
CONFIG_DRM_AMDGPU:"AMD graphics"; do
_o="${opt%%:*}"; _why="${opt#*:}"
grep -qE "^$_o=(y|m)" "$tree/.config" \
|| echo "!! $_o is not set -- no $_why"
done
# FIRMWARE TROUBLE SHOULD NOT COST YOU THE KERNEL.
#
# This ran before the build and exited on failure, so a wrong FIRMWARE_ONLY
# meant no kernel was built at all -- and the message did not say so. A
# missing blob only BLOCKS the build when the config embeds it
# (CONFIG_EXTRA_FIRMWARE); otherwise it is a run-time concern, and the check
# further down catches the case that matters.
_fw_ok=1
if [ "$FIRMWARE_FROM" != none ]; then
_install_firmware || _fw_ok=0
fi
if [ "$_fw_ok" = 0 ]; then
if grep -q '^CONFIG_EXTRA_FIRMWARE="..*"$' "$tree/.config"; then
echo "!! this config builds firmware INTO the kernel, so the build"
echo " cannot proceed without it. Fix FIRMWARE_ONLY above."
exit 1
fi
echo "!! carrying on WITHOUT the firmware above: this config does not"
echo " embed any (no CONFIG_EXTRA_FIRMWARE), so the kernel will build."
echo " The drivers that wanted it will fail at run time until it is"
echo " installed -- re-run lfs-kernel once FIRMWARE_ONLY is right."
fi
# FIRMWARE BUILT INTO THE KERNEL MUST BE ON DISK.
#
# CONFIG_EXTRA_FIRMWARE embeds blobs in the image, so the build reads them
# from CONFIG_EXTRA_FIRMWARE_DIR (/lib/firmware by default). A config taken
# from a distribution kernel usually lists a dozen, and an LFS tree has no
# linux-firmware -- so the build dies deep in drivers/base, ten minutes in,
# with a message that names one file and no reason:
# No rule to make target '/lib/firmware/amdgpu/renoir_asd.bin'
# Check them all before compiling anything, and name every one that is
# missing.
_fw="$(sed -n 's/^CONFIG_EXTRA_FIRMWARE="\(.*\)"$/\1/p' "$tree/.config")"
if [ -n "$_fw" ]; then
_fwdir="$(sed -n 's/^CONFIG_EXTRA_FIRMWARE_DIR="\(.*\)"$/\1/p' "$tree/.config")"
: "${_fwdir:=/lib/firmware}"
_missing_fw=""
for _f in $_fw; do
[ -f "$_fwdir/$_f" ] || _missing_fw="$_missing_fw $_f"
done
if [ -n "$_missing_fw" ]; then
echo "!! this config builds firmware INTO the kernel"
echo " (CONFIG_EXTRA_FIRMWARE), and these are not in $_fwdir:"
for _f in $_missing_fw; do echo " $_f"; done
echo " The build would fail in drivers/base after compiling for"
echo " several minutes. Two ways out:"
echo " * add them to FIRMWARE_ONLY in $CONF -- this tool installs"
echo " firmware itself (FIRMWARE_FROM=$FIRMWARE_FROM). The"
echo " subdirectory of each file above is what to list."
echo " * build without it:"
echo " cd $tree && ./scripts/config --set-str EXTRA_FIRMWARE ''"
echo " then run lfs-kernel again"
echo " the drivers then load it at RUN time from $_fwdir instead."
exit 1
fi
fi
# /lib/modules, /usr/include and the doc directory end up owned and recorded
# like any other package's files.
echo "# compiling ($JOBS) -- this is the long one; expect many minutes ..."
_as_pkg "make $JOBS" || exit 1
_as_pkg "make modules_prepare" || exit 1
echo "# installing modules into /lib/modules/$VERSION ..."
_as_pkg "make modules_install" || exit 1
# KERNEL_NAME is what the boot manager loads (rEFInd reads the file name);
# with ESP it is the pair that decides whether this is a test kernel or the
# real one.
# THE VERSION OF WHAT WAS BUILT, NOT OF WHAT WAS ASKED FOR. An empty
# VERSION installed `vmlinuz-`, `config-` and `System.map-` beside the real
# ones. The tree knows its own release once it has compiled.
_built="$(cat "$tree/include/config/kernel.release" 2>/dev/null)"
if [ -n "$_built" ] && [ "$_built" != "$VERSION" ]; then
[ -n "$VERSION" ] && echo "# the tree says $_built (asked for $VERSION) -- installing as $_built"
VERSION="$_built"
fi
[ -n "$VERSION" ] || { echo "!! no kernel version known for $tree -- not installing it under an empty name"; exit 1; }
[ -f "$ESP/$KERNEL_NAME" ] && cp -v "$ESP/$KERNEL_NAME" "$ESP/${KERNEL_NAME}_old"
cp -v "$tree/arch/x86/boot/bzImage" "$ESP/$KERNEL_NAME"
cp -v "$tree/arch/x86/boot/bzImage" "$ESP/$KERNEL_NAME-$VERSION"
cp -v "$tree/System.map" "$ESP/System.map-$VERSION"
cp -v "$tree/.config" "$ESP/config-$VERSION"
# SAY IT BEFORE IT TAKES A WHILE, NOT AFTER.
#
# Documentation is about ten thousand small files and the copy runs for a
# minute or two in complete silence; so does the manifest scan below, which
# walks the filesystem. A tool that goes quiet for two minutes is
# indistinguishable from one that has hung.
echo "# copying Documentation to /usr/share/doc/linux-$VERSION (~10k files, a minute or two) ..."
_as_pkg "install -d /usr/share/doc/linux-$VERSION" || exit 1
_as_pkg "cp -r Documentation -T /usr/share/doc/linux-$VERSION" || exit 1
# DEPMOD THE KERNEL WE BUILT, NOT THE ONE THAT IS RUNNING.
#
# `depmod -a` with no argument means `uname -r` -- 7.2.0, the kernel this
# chroot booted, whose /lib/modules does not exist here:
# depmod: ERROR: could not open directory /lib/modules/7.2.0
# The modules that were just installed are 7.2.4's, and that is what needs
# an index.
depmod -a "$VERSION" || exit 1
# RECORD THE FILES NOW, WHILE THEY ARE INSTALLED.
#
# This used to run at the very end, after v4l2loopback and the headers -- so
# when the module build failed, the manifest was never written and `verify`
# reported "p_linux records its files: FAIL" about a kernel that was
# perfectly installed. The kernel is on disk at this point; record it.
# (Run again after the headers, which add /usr/include.)
_record() {
echo "# recording what $acct owns (a scan AS $acct, skipping /proc, the"
echo "# package source trees and the caches; --root if something is missed)"
packagemanager reload-pkg-list "$PKG" >/dev/null 2>&1 || return 0
local _h; _h="$(getent passwd "$acct" | cut -d: -f6)"
echo "# recorded $(grep -c . "$_h/pkg.lst" 2>/dev/null) file(s) for $acct"
}
_record
# v4l2loopback (optional, out of tree)
case "$V4L2LOOPBACK" in yes|1|true)
d="$SRC/v4l2loopback"
# THE REPOSITORY IS THE PACKAGE USER'S, FROM THE FIRST COMMAND.
#
# git pull/clone ran as ROOT and the chown came afterwards, so the next
# command -- run as the package user -- met a repository owned by
# somebody else:
# fatal: detected dubious ownership in repository at '.../v4l2loopback'
# Owning it first and cloning as the account avoids the whole class,
# rather than papering over it with safe.directory.
install -d -o "$acct" -g "$acct" -m 755 "$d" || exit 1
[ -d "$d/.git" ] && chown -R "$acct:$acct" "$d" 2>/dev/null
if [ -d "$d/.git" ]; then
su - "$acct" -c "cd '$d' && git pull --ff-only" || exit 1
else
su - "$acct" -c "git clone --depth 1 '$V4L2LOOPBACK_GIT' '$d'" || exit 1
fi
# BUILD IT AGAINST THE TREE, NOT AGAINST /lib/modules/$(uname -r).
#
# An out-of-tree module's own Makefile defaults to
# /lib/modules/`uname -r`/build, which is the RUNNING kernel:
# make[1]: *** /lib/modules/7.2.0/build: No such file or directory
# Driving kbuild from the tree directly sidesteps every such default and
# works for any module, not just this one.
su - "$acct" -c "make -C '$tree' M='$d' clean" || exit 1
su - "$acct" -c "make -C '$tree' M='$d' modules" || exit 1
su - "$acct" -c "make -C '$tree' M='$d' modules_install" || exit 1
depmod -a "$VERSION" || exit 1 ;;
esac
# THE HEADERS BELONG TO THE HEADERS PACKAGE.
#
# /usr/include/asm/*.h is owned by p_linux-headers -- the LFS step that
# installed them -- and /usr/include is sticky, so p_linux cannot replace a
# single one:
# cp: cannot create regular file '/usr/include/asm/types.h': Permission denied
# Installing a newer kernel's headers is an UPDATE OF THAT PACKAGE, not a
# second package writing over it. So the copy runs as that account, and its
# manifest is refreshed afterwards. (HEADERS=no in kernel.conf leaves
# /usr/include alone entirely, which is right when the running system's
# headers are deliberately older than the kernel being built.)
: "${HEADERS:=yes}"
: "${HEADERS_PKG:=linux-headers}"
case "$HEADERS" in
yes|1|true)
_hacct="p_$HEADERS_PKG"
if ! id "$_hacct" >/dev/null 2>&1; then
packagemanager add-user "$HEADERS_PKG" \
|| { echo "!! could not create $_hacct"; exit 1; }
lfs-helper fix-home "$_hacct" --run >/dev/null 2>&1 || true
fi
echo "# generating headers ..."
_as_pkg "make headers" || exit 1
_as_pkg "find usr/include -type f ! -name '*.h' -delete"
# the tree is p_linux's; the destination is p_linux-headers'. Copy
# via a place both can read.
# ROOT COPIES, THEN HANDS THE FILES OVER.
#
# Running the copy AS p_linux-headers cannot work: the source is
# inside p_linux's home, which is 0750, so it cannot even stat it --
# cp: cannot stat '.../linux-7.2.4/usr/include': Permission denied
# and opening one account's home to another would undo the point of
# having accounts. Root can read both sides; ownership afterwards is
# what makes the files the headers package's. Same shape as the ESP
# copies and the setuid bit.
echo "# installing headers into /usr/include (as root, owned by $_hacct) ..."
# WHAT IT INSTALLED, NOT WHERE IT INSTALLED.
#
# `chown -R $_hacct /usr/include` gave this package every header on
# the system. /usr/include/gstreamer-1.0/gst/mse then belonged to
# p_linux-headers, so gst-plugins-bad could not install into its own
# directory, the repair handed it one file per round, and after three
# rounds the bundled-copy heuristic concluded meson was vendoring
# linux-headers. Every step of that was correct reasoning from a
# wrong fact.
#
# So: copy, remember exactly which paths came out of the tree, and
# chown only those.
_hlist="$(mktemp)"
( cd "$tree/usr/include" && find . -mindepth 1 -printf '%P\n' ) > "$_hlist"
cp -r "$tree/usr/include" /usr || {
echo "!! could not install the headers into /usr/include"
echo " if some of them belong to a third package:"
echo " packagemanager reclaim $HEADERS_PKG --run"
rm -f "$_hlist"; exit 1; }
( cd /usr/include && tr '\n' '\0' < "$_hlist" \
| xargs -0 -r chown -h "$_hacct:$_hacct" ) 2>/dev/null || true
# the directory itself stays root's: it is shared by every package
# that ships a header, and one owner for it is what caused this
echo "# $(grep -c . "$_hlist") path(s) now owned by $_hacct;"
echo "# /usr/include itself left as $(stat -c %U /usr/include)"
rm -f "$_hlist"
packagemanager reload-pkg-list "$HEADERS_PKG" >/dev/null 2>&1 || true
echo "# headers installed and recorded for $_hacct" ;;
*) echo "# headers: skipped (HEADERS=$HEADERS)" ;;
esac
# THE LAST WORKING SOURCE IS NEVER DELETED.
#
# A kernel that boots is the only way back from one that does not, and its
# TREE is what you need to rebuild or bisect -- not just the bzImage. The
# version that is running right now is recorded as last-good before this one
# is; nothing here ever removes that tree. (An older comment here promised
# a `lfs-kernel prune`; there is none, and before 1.14.324 typing it started
# a full build instead of saying so.)
# ONLY A KERNEL THAT HAS BOOTED IS KNOWN TO BOOT.
#
# The old `elif` wrote the version just built into last-good whenever the
# file was empty -- so `verify` reported "last kernel known to boot: 7.2.4"
# about a kernel that has never run, while 7.2.0 was the one carrying the
# machine. A fallback that records a guess as a fact is worse than no
# record: the whole point of this file is to name something you can go back
# to.
_lastgood=/etc/pkgusr/kernel.last-good
_running="$(uname -r | sed 's/-.*//')"
if [ "$_running" = "$VERSION" ]; then
# the kernel just built is the one running: it demonstrably boots
printf '%s\n' "$VERSION" > "$_lastgood"
echo "# $VERSION is running, so it is now the kernel known to boot"
_kcfg_mark_good "$tree/.config" "$VERSION"
elif [ -d "$SRC/linux-$_running" ]; then
printf '%s\n' "$_running" > "$_lastgood"
echo "# keeping $SRC/linux-$_running as the last kernel known to boot"
# ...and ITS config is the one known to work, not the one just built
_kcfg_mark_good "$SRC/linux-$_running/.config" "$_running"
else
echo "# the running kernel is $_running, and there is no tree for it here,"
echo "# so nothing is recorded as known-good. $VERSION will be recorded"
echo "# the first time lfs-kernel runs while $VERSION is booted."
[ -s "$_lastgood" ] && echo "# (unchanged: $(cat "$_lastgood"))"
fi
_record
echo
echo "# $VERSION installed as $ESP/$KERNEL_NAME"
echo "# previous kernel kept as $ESP/${KERNEL_NAME}_old"
echo "# its source kept at $SRC/linux-$(cat "$_lastgood" 2>/dev/null)"
echo "# owned by $acct (packagemanager verify | grep $PKG)"
echo "# its config kept in $KCFG_DIR (lfs-kernel config list)"
echo "# running kernel is $(uname -r) -- reboot to use $VERSION"
[ "$_fw_ok" = 1 ] || echo "# NOTE: firmware was NOT installed (see the message above)"