kernel: the initramfs script takes over a default-config preset and checks each image (issue 1977, L7-L9)
mesh/delivery merged: source.main moved to novox/mesh-catalog@b9abfde492b2
mesh/check checking f7616c6a9bd4 on b9abfde492b2
mesh/delivery merged: source.main moved to novox/mesh-catalog@b9abfde492b2
mesh/check checking f7616c6a9bd4 on b9abfde492b2
Before mkinitcpio -P, a preset naming ALL_config="/etc/mkinitcpio.conf" is copied to the state's taken-over/ and loses that line (another ALL_config is left and named); after it, an image without kernel/x86/microcode/ in its first 64 KiB fails the unit and records nothing. The unit may write /etc/mkinitcpio.d. The README names no node. Tests: 1977/AC7, 1977/AC8; AC3/AC4 fixtures write images. Builds: L7, L8, L9 (issue 1977 note 4)
This commit is contained in:
@@ -56,7 +56,7 @@ built for the fallback kernel too) after its boot-entries service, so the entry
|
||||
## Microcode in every initramfs (novox/hq issue 1977)
|
||||
|
||||
Before this, `amd-ucode` was installed by hand on two machines and no initramfs carried it, so a machine booted from
|
||||
its default entry ran the CPU's older microcode (ace: revision 0x0a201016 while the package held 0x0a20102e). The module
|
||||
its default entry ran the CPU's older microcode (one machine: revision 0x0a201016 while the package held 0x0a20102e). The module
|
||||
declares the packages and makes mkinitcpio's own `microcode` hook part of every image, so every boot entry loads it,
|
||||
a default entry written by hand included, and no entry is edited. The fallback entry's own microcode lines stay (a second
|
||||
early archive is harmless).
|
||||
@@ -65,8 +65,8 @@ early archive is harmless).
|
||||
|---|---|---|
|
||||
| packages | `amd-ucode`, `intel-ucode` | both on every machine; the `autodetect` hook keeps only the running CPU's microcode in the default image |
|
||||
| file | `/etc/mkinitcpio.conf.d/mesh-microcode.conf` | read by mkinitcpio after `mkinitcpio.conf`: appends `microcode` to `HOOKS` unless it names it |
|
||||
| file | `${dir:state}/initramfs` | the script, `apply`: when the drop-in differs from the one the images were last built with (`${dir:state}/initramfs-built`) it runs `mkinitcpio -P` and then records the drop-in; otherwise it says "as it was". A failed mkinitcpio fails the unit, says so and records nothing |
|
||||
| file | `/etc/systemd/system/mesh-kernel-initramfs.service` | oneshot, sandboxed: the file system read-only but for `/boot` and the module's state, a private `/tmp`, no home, no network |
|
||||
| file | `${dir:state}/initramfs` | the script, `apply`: when the drop-in differs from the one the images were last built with (`${dir:state}/initramfs-built`) it runs `mkinitcpio -P` and then records the drop-in; otherwise it says "as it was". Before building it takes a preset's `ALL_config="/etc/mkinitcpio.conf"` line out (that line makes mkinitcpio skip `mkinitcpio.conf.d`; a copy of the preset goes to `${dir:state}/taken-over/`; another `ALL_config` is left and named); after building, an image without `kernel/x86/microcode/` in its first 64 KiB fails the unit, which names it. A failed mkinitcpio fails the unit, says so and records nothing |
|
||||
| file | `/etc/systemd/system/mesh-kernel-initramfs.service` | oneshot, sandboxed: the file system read-only but for `/boot`, `/etc/mkinitcpio.d` and the module's state, a private `/tmp`, no home, no network |
|
||||
| service | `mesh-kernel-initramfs.service` | running and enabled; restarted when the unit, the script or the drop-in changes. Later kernel upgrades build their images through the distribution's own hook, with the drop-in in place |
|
||||
|
||||
The module does not reboot: a machine loads the new microcode at its next boot. `kernel_boot` says the revision the CPU
|
||||
|
||||
@@ -58,30 +58,35 @@ func TestAC2TheDropInAddsMicrocodeToHooksOnce(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type initBed struct{ script, dropin, state, runs, bin string }
|
||||
type initBed struct{ script, dropin, state, runs, bin, presets, boot string }
|
||||
|
||||
func newInitBed(t *testing.T, mkinitcpioExit string) initBed {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
b := initBed{script: filepath.Join(dir, "initramfs"), dropin: filepath.Join(dir, "mesh-microcode.conf"), state: filepath.Join(dir, "state"),
|
||||
runs: filepath.Join(dir, "runs"), bin: filepath.Join(dir, "bin")}
|
||||
for _, d := range []string{b.state, b.bin} {
|
||||
runs: filepath.Join(dir, "runs"), bin: filepath.Join(dir, "bin"), presets: filepath.Join(dir, "presets"), boot: filepath.Join(dir, "boot")}
|
||||
for _, d := range []string{b.state, b.bin, b.presets, b.boot} {
|
||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
content := strings.ReplaceAll(manifest(t).resource(t, "initramfs").text(t), "${dir:state}", b.state)
|
||||
if strings.Contains(content, "${dir") {
|
||||
if strings.Contains(content, "${") {
|
||||
t.Fatal("a substitution is left in the script")
|
||||
}
|
||||
write(t, b.script, content, 0o755)
|
||||
write(t, b.dropin, "HOOKS+=(microcode)\n", 0o644)
|
||||
write(t, filepath.Join(b.bin, "mkinitcpio"), "#!/bin/sh\necho \"$@\" >>"+b.runs+"\nexit "+mkinitcpioExit+"\n", 0o755)
|
||||
// the stand-in mkinitcpio writes an image whose early archive holds microcode.
|
||||
b.mkinitcpio(t, "kernel/x86/microcode/AuthenticAMD.bin", mkinitcpioExit)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b initBed) mkinitcpio(t *testing.T, early, exit string) {
|
||||
write(t, filepath.Join(b.bin, "mkinitcpio"), "#!/bin/sh\necho \"$@\" >>"+b.runs+"\nprintf '070701%s\\0' "+early+" >"+b.boot+"/initramfs-linux.img\nexit "+exit+"\n", 0o755)
|
||||
}
|
||||
|
||||
func (b initBed) run(t *testing.T) (string, error) {
|
||||
cmd := exec.Command("sh", b.script, "apply", b.dropin, b.state)
|
||||
cmd := exec.Command("sh", b.script, "apply", b.dropin, b.state, b.presets, b.boot)
|
||||
cmd.Env = append(os.Environ(), "PATH="+b.bin+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
out, err := cmd.CombinedOutput()
|
||||
return string(out), err
|
||||
@@ -148,3 +153,38 @@ func TestAC5KernelBootNamesTheRunningMicrocodeAndWhichImagesCarryIt(t *testing.T
|
||||
t.Fatalf("unreadable: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 1977/AC7
|
||||
func TestAC7ADefaultConfigPresetIsTakenOverAndAnotherIsLeftAndNamed(t *testing.T) {
|
||||
b := newInitBed(t, "0")
|
||||
preset := "ALL_kver=\"/boot/vmlinuz-linux\"\nALL_config=\"/etc/mkinitcpio.conf\"\nPRESETS=('default')\n"
|
||||
write(t, filepath.Join(b.presets, "linux.preset"), preset, 0o644)
|
||||
other := "ALL_config=\"/etc/other.conf\"\nPRESETS=('default')\n"
|
||||
write(t, filepath.Join(b.presets, "linux-lts.preset"), other, 0o644)
|
||||
out, err := b.run(t)
|
||||
if err != nil {
|
||||
t.Fatalf("%v %s", err, out)
|
||||
}
|
||||
if got, _ := os.ReadFile(filepath.Join(b.presets, "linux.preset")); string(got) != "ALL_kver=\"/boot/vmlinuz-linux\"\nPRESETS=('default')\n" {
|
||||
t.Fatalf("preset: %q", got)
|
||||
}
|
||||
if got, _ := os.ReadFile(filepath.Join(b.state, "taken-over", "linux.preset")); string(got) != preset {
|
||||
t.Fatalf("copy: %q", got)
|
||||
}
|
||||
if got, _ := os.ReadFile(filepath.Join(b.presets, "linux-lts.preset")); string(got) != other || !strings.Contains(out, "linux-lts.preset sets ALL_config=\"/etc/other.conf\"") {
|
||||
t.Fatalf("other: %q %s", got, out)
|
||||
}
|
||||
}
|
||||
|
||||
// 1977/AC8
|
||||
func TestAC8AnImageWithoutMicrocodeFailsTheUnitNamesItAndRecordsNothing(t *testing.T) {
|
||||
b := newInitBed(t, "0")
|
||||
b.mkinitcpio(t, "early/other.bin", "0")
|
||||
out, err := b.run(t)
|
||||
if err == nil || !strings.Contains(out, "initramfs-linux.img") {
|
||||
t.Fatalf("%v %s", err, out)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(b.state, "initramfs-built")); !os.IsNotExist(err) {
|
||||
t.Fatalf("recorded: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,18 +2,28 @@
|
||||
# The initramfs images as the mesh builds them (module kernel, novox/hq issue 1977). Run by
|
||||
# mesh-kernel-initramfs.service:
|
||||
#
|
||||
# initramfs apply [<the microcode drop-in> [<this module's state> [<mkinitcpio file>]]]
|
||||
# initramfs apply [<the microcode drop-in> [<this module's state> [<preset directory> [<boot directory>]]]]
|
||||
#
|
||||
# `apply` builds every image again (mkinitcpio -P) when the drop-in differs from the one the images were last built
|
||||
# with, kept in the state as `initramfs-built`, and records the drop-in only after mkinitcpio succeeded. Later
|
||||
# kernel upgrades build the images through the distribution's own hook, with the drop-in in place. The arguments
|
||||
# are only for the module's tests.
|
||||
# kernel upgrades build the images through the distribution's own hook, with the drop-in in place.
|
||||
#
|
||||
# A preset that names the default configuration (ALL_config="/etc/mkinitcpio.conf") makes mkinitcpio pass -c and skip
|
||||
# mkinitcpio.conf.d, so the drop-in is never read: before building, that line is taken out of the preset, a copy of
|
||||
# the preset kept in the state's taken-over directory (novox/hq ADR 0508 §10's way with a file the mesh did not
|
||||
# write); any other ALL_config is left and named. After building, every image must hold microcode in its first
|
||||
# 64 KiB (the early uncompressed archive), else the unit fails and records nothing. The arguments are only for the
|
||||
# module's tests.
|
||||
set -eu
|
||||
|
||||
dropin=/etc/mkinitcpio.conf.d/mesh-microcode.conf
|
||||
state='${dir:state}'
|
||||
[ $# -ge 2 ] && dropin=$2
|
||||
[ $# -ge 3 ] && state=$3
|
||||
presets=/etc/mkinitcpio.d
|
||||
boot=/boot
|
||||
[ $# -ge 4 ] && presets=$4
|
||||
[ $# -ge 5 ] && boot=$5
|
||||
|
||||
[ $# -ge 1 ] && [ "$1" = apply ] || {
|
||||
echo "usage: initramfs apply [<drop-in> [<state directory>]]" >&2
|
||||
@@ -27,10 +37,35 @@ if [ -f "$state/initramfs-built" ] && cmp -s "$dropin" "$state/initramfs-built";
|
||||
echo "initramfs: the images were built with this drop-in, as it was"
|
||||
exit 0
|
||||
fi
|
||||
for preset in "$presets"/*.preset; do
|
||||
[ -f "$preset" ] || continue
|
||||
if grep -qx 'ALL_config="/etc/mkinitcpio.conf"' "$preset"; then
|
||||
mkdir -p "$state/taken-over"
|
||||
cp "$preset" "$state/taken-over/$(basename "$preset")"
|
||||
grep -vx 'ALL_config="/etc/mkinitcpio.conf"' "$preset" >"$preset.new" || true
|
||||
cat "$preset.new" >"$preset"
|
||||
rm -f "$preset.new"
|
||||
echo "initramfs: $preset named the default configuration, which skips mkinitcpio.conf.d; that line is taken out, a copy is in $state/taken-over"
|
||||
elif grep -q '^ALL_config=' "$preset"; then
|
||||
echo "initramfs: $preset sets $(grep '^ALL_config=' "$preset" | head -n 1), so the microcode drop-in may not be read; left as it is"
|
||||
fi
|
||||
done
|
||||
if ! mkinitcpio -P; then
|
||||
echo "initramfs: mkinitcpio failed; nothing recorded, the next start tries again" >&2
|
||||
exit 1
|
||||
fi
|
||||
missing=
|
||||
for img in "$boot"/initramfs-*.img; do
|
||||
[ -e "$img" ] || continue
|
||||
if ! head -c 65536 "$img" >"$state/image-head.tmp" || ! grep -aq 'kernel/x86/microcode/' "$state/image-head.tmp"; then
|
||||
missing="$missing $(basename "$img")"
|
||||
fi
|
||||
done
|
||||
rm -f "$state/image-head.tmp"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "initramfs: mkinitcpio ran, but no microcode is in the early archive of:$missing; nothing recorded" >&2
|
||||
exit 1
|
||||
fi
|
||||
cp "$dropin" "$state/initramfs-built.new"
|
||||
mv "$state/initramfs-built.new" "$state/initramfs-built"
|
||||
echo "initramfs: the images are built with the microcode drop-in"
|
||||
|
||||
@@ -10,9 +10,9 @@ Wants=local-fs.target
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=${dir:state}/initramfs apply
|
||||
# It writes the boot partition and its own state; mkinitcpio keeps its work in a private /tmp.
|
||||
# It writes the boot partition, the presets and its own state; mkinitcpio keeps its work in a private /tmp.
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/boot ${dir:state}
|
||||
ReadWritePaths=/boot /etc/mkinitcpio.d ${dir:state}
|
||||
PrivateTmp=yes
|
||||
ProtectHome=yes
|
||||
PrivateNetwork=yes
|
||||
|
||||
Reference in New Issue
Block a user