mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-14 21:30:59 +01:00
clean_shadow_files() assumed UEFI with an ESP mounted at /boot. On a
BIOS/GRUB system with no ESP, /boot is a plain directory on the root
filesystem holding the only kernel the machine has -- and neither existing
guard catches it: findmnt /boot is empty (GUARD 1 passes) and there is no
EFI/ or loader/ (GUARD 2 passes). boot-repair therefore read the real
vmlinuz-linux and initramfs-linux.img as "orphaned kernel file(s)
shadowing the ESP" and deleted them, leaving the machine unbootable.
Measured 2026-08-22 in a vmbox BIOS/GRUB sandbox, not theorised: after
sudo boot-repair the guest never booted again.
GUARD 3: if /sys/firmware/efi does not exist the system booted via BIOS,
so there is no ESP and nothing in /boot can be a shadow. warn + return 1,
matching the existing guards' tone and exit convention. Overridable with
the existing --esp DEVICE -- naming the ESP by hand asserts the firmware
assumption the guard otherwise checks -- rather than adding a new flag.
The check goes through rpath(), so a --root fixture declares its own
firmware type and the case is testable without a VM. On the live UEFI
machine it resolves to the real /sys path, so behaviour there is unchanged.
Audit of the other BIOS-unsafe paths (none destructive, left alone):
* mount_esp(): resolve_esp_device fails with no /boot fstab entry, so it
reports and does not mount. Non-destructive.
* sync_kernel_image()/rebuild_initramfs(): gated behind boot_is_mounted,
which is false on BIOS, so they never write.
* check_fstab_options(): reports "no /boot entry" -- noise on BIOS, but
it writes nothing.
* run_preflight(): aborts every kernel upgrade on a BIOS box. Fail-closed
and non-destructive, so out of scope here.
Tests: three cases in tests/lib/boot_repair_bios_cases.sh (new file; the
existing case lib is at the 250-line cap) -- the guard refuses and explains,
--esp overrides it, and a UEFI fixture still cleans shadows as before.
Verified failing without the guard (38/4) and passing with it (42/0).
Sandbox: BIOS guest keeps both kernel files, says why, and still boots
afterwards (uname -r 7.1.9-arch1-2 after a clean poweroff). shellcheck clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
109 lines
2.4 KiB
Bash
Executable File
109 lines
2.4 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# Fixture tests for boot-repair.
|
|
#
|
|
# Each test builds a throwaway root tree in a temp dir and runs boot-repair
|
|
# against it with --root, so nothing on the real machine is touched. This
|
|
# exercises detection, repair and the safety guards; it cannot exercise
|
|
# mount/modprobe, which need the live system.
|
|
# ============================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
|
readonly SCRIPT_DIR
|
|
readonly BOOT_REPAIR="$SCRIPT_DIR/../boot-repair"
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
WORKROOT=""
|
|
|
|
cleanup() {
|
|
[[ -n $WORKROOT && -d $WORKROOT ]] && rm -rf "$WORKROOT"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
pass() {
|
|
PASS=$((PASS + 1))
|
|
printf ' [PASS] %s\n' "$1"
|
|
}
|
|
|
|
fail() {
|
|
FAIL=$((FAIL + 1))
|
|
printf ' [FAIL] %s\n' "$1"
|
|
[[ -n ${2:-} ]] && printf ' %s\n' "$2"
|
|
}
|
|
|
|
# assert_contains <description> <haystack> <needle>
|
|
assert_contains() {
|
|
if [[ $2 == *"$3"* ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1" "expected to find: $3"
|
|
fi
|
|
}
|
|
|
|
# assert_not_contains <description> <haystack> <needle>
|
|
assert_not_contains() {
|
|
if [[ $2 != *"$3"* ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1" "did not expect: $3"
|
|
fi
|
|
}
|
|
|
|
# assert_eq <description> <actual> <expected>
|
|
assert_eq() {
|
|
if [[ $2 == "$3" ]]; then
|
|
pass "$1"
|
|
else
|
|
fail "$1" "expected '$3', got '$2'"
|
|
fi
|
|
}
|
|
|
|
# shellcheck source=lib/boot_repair_cases.sh
|
|
source "$SCRIPT_DIR/lib/boot_repair_cases.sh"
|
|
# shellcheck source=lib/boot_repair_bios_cases.sh
|
|
source "$SCRIPT_DIR/lib/boot_repair_bios_cases.sh"
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Runner
|
|
# ----------------------------------------------------------------------------
|
|
|
|
main() {
|
|
WORKROOT="$(mktemp -d)" || {
|
|
echo "mktemp failed" >&2
|
|
exit 2
|
|
}
|
|
|
|
[[ -x $BOOT_REPAIR ]] || {
|
|
echo "boot-repair not executable at $BOOT_REPAIR" >&2
|
|
exit 2
|
|
}
|
|
|
|
echo "=== boot-repair fixture tests ==="
|
|
echo
|
|
|
|
test_healthy_system
|
|
test_stale_esp_kernel_detected
|
|
test_incomplete_tree_ignored
|
|
test_picks_newest_complete
|
|
test_fstab_nofail_fixed
|
|
test_shadow_files_removed
|
|
test_shadow_guard_refuses_real_esp
|
|
test_bios_guard_refuses_to_delete_real_kernel
|
|
test_bios_guard_overridden_by_explicit_esp
|
|
test_uefi_shadow_cleanup_still_works
|
|
test_parallel_downloads_fixed
|
|
test_missing_modules_dep
|
|
test_no_kernel_at_all
|
|
test_help_and_bad_args
|
|
|
|
echo
|
|
echo "=== $PASS passed, $FAIL failed ==="
|
|
[[ $FAIL -eq 0 ]]
|
|
}
|
|
|
|
main "$@"
|