initPickRoots can only offer MOUNTED filesystems, so on a box whose second drive
is new — unmounted, often unformatted — it returned in silence and the "where
should LibrePortal keep things?" prompt never appeared. The users most likely to
want a separate disk were the ones told nothing.
Add a notice listing what is attached and how to use it, deliberately only a
notice: mounting or formatting someone's disk is not something an installer
should do unasked, and `storage add` does it properly later with the
empty-directory admission rule and the fitness checks behind it.
Two things it has to get right, and both bit during development:
* "has no mount point" is not "is free" — the disk holding root has no mount
point of its own, its partition does, so the naive check offered the user
the disk they booted from. Walk the parent chain and mark holders in use.
* lsblk -r renders an empty mount point as a run of spaces, which `read`
collapses, shifting every later column left; an LVM member then parsed as a
mount point and was offered as free space. Use -P.
scripts/dev/lp-installer-disks-test runs the real function against a stubbed
lsblk that honours the output flag it is passed — without that the stub answers
-P to everything and cannot tell the two parsers apart, which is the bug being
guarded against. Verified both regressions fail it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
5.2 KiB
Bash
Executable File
114 lines
5.2 KiB
Bash
Executable File
#!/bin/bash
|
|
# Does the installer mention a spare drive — and only a genuinely spare one?
|
|
#
|
|
# scripts/dev/lp-installer-disks-test
|
|
#
|
|
# initPickRoots can only offer MOUNTED filesystems, so on a box whose second
|
|
# drive is new (unmounted, often unformatted) it returned in silence and the
|
|
# prompt never appeared at all. That taught exactly the users who have a spare
|
|
# disk that the feature does not exist.
|
|
#
|
|
# The notice that fills that gap has to be careful in two specific ways, and
|
|
# both are cases below:
|
|
#
|
|
# 1. "Has no mount point" is not "is free". The disk holding the root
|
|
# filesystem has no mount point of its own — its partition does — so the
|
|
# obvious check offers the user the disk they booted from.
|
|
# 2. lsblk's raw output renders an empty mount point as a run of spaces, which
|
|
# `read` collapses, shifting every later column left. An LVM member then
|
|
# parses as a mount point and gets offered as free space. Hence -P.
|
|
#
|
|
# Runs the REAL _initMentionUnmounted extracted from init.sh against a stubbed
|
|
# lsblk, so editing the heuristic is what makes this fail.
|
|
|
|
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
fail=0
|
|
chk(){ if [[ "$2" == "$3" ]]; then echo " ok $1"; else echo " FAIL $1: got '$2' want '$3'"; fail=1; fi; }
|
|
|
|
FN=$(awk '/^_initMentionUnmounted\(\)/,/^\}/' "$REPO/init.sh")
|
|
[[ -n "$FN" ]] || { echo " FAIL could not extract _initMentionUnmounted from init.sh"; exit 1; }
|
|
|
|
# Fixtures are written as lsblk -P rows. The stub HONOURS the output flag it is
|
|
# called with: asked for -r it re-renders them raw, collapsing the empty fields
|
|
# the way lsblk really does. Without that the stub answers -P to everything and
|
|
# the test cannot tell the two parsers apart — which is the bug being guarded
|
|
# against, so a stub that ignores the flag guards nothing.
|
|
STUB_SRC=$(mktemp)
|
|
{
|
|
echo 'lsblk() {'
|
|
echo ' case " $* " in'
|
|
echo ' *" -P"*) cat "$ROWS_FILE"; return 0 ;;'
|
|
echo ' esac'
|
|
echo ' sed -E '"'"'s/[A-Z]+="([^"]*)"/\1/g; s/ +/ /g; s/^ +| +$//g'"'"' "$ROWS_FILE"'
|
|
echo '}'
|
|
} > "$STUB_SRC"
|
|
trap 'rm -f "$STUB_SRC"' EXIT
|
|
|
|
runrows() {
|
|
local tmp rf out
|
|
tmp=$(mktemp); rf=$(mktemp)
|
|
cat > "$rf"
|
|
{
|
|
echo 'isNotice(){ echo "NOTICE: $*"; }'
|
|
cat "$STUB_SRC"
|
|
echo "$FN"
|
|
echo '_initMentionUnmounted'
|
|
} > "$tmp"
|
|
out=$(ROWS_FILE="$rf" bash "$tmp" 2>/dev/null)
|
|
rm -f "$tmp" "$rf"
|
|
printf '%s\n' "$out"
|
|
}
|
|
|
|
ROOT_ROWS='NAME="nvme0n1" TYPE="disk" SIZE="931.5G" MOUNTPOINT="" FSTYPE="" PKNAME=""
|
|
NAME="nvme0n1p1" TYPE="part" SIZE="1G" MOUNTPOINT="/boot/efi" FSTYPE="vfat" PKNAME="nvme0n1"
|
|
NAME="nvme0n1p2" TYPE="part" SIZE="930G" MOUNTPOINT="" FSTYPE="crypto_LUKS" PKNAME="nvme0n1"
|
|
NAME="dm-0" TYPE="crypt" SIZE="930G" MOUNTPOINT="/" FSTYPE="ext4" PKNAME="nvme0n1p2"'
|
|
|
|
echo "--- a normal single-disk box (root on LUKS over nvme0n1p2) ---"
|
|
out=$(runrows <<< "$ROOT_ROWS")
|
|
chk "offers nothing" "$(grep -c NOTICE <<< "$out")" "0"
|
|
|
|
echo "--- a spare unformatted disk ---"
|
|
out=$(runrows <<< "$ROOT_ROWS
|
|
NAME=\"sdb\" TYPE=\"disk\" SIZE=\"3.6T\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"\"")
|
|
chk "mentions it" "$(grep -c NOTICE <<< "$out")" "1"
|
|
chk "names the device" "$(grep -c '/dev/sdb' <<< "$out")" "1"
|
|
chk "says it is blank" "$(grep -c 'no filesystem yet' <<< "$out")" "1"
|
|
|
|
echo "--- a spare formatted partition ---"
|
|
out=$(runrows <<< "$ROOT_ROWS
|
|
NAME=\"sdb\" TYPE=\"disk\" SIZE=\"3.6T\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"\"
|
|
NAME=\"sdb1\" TYPE=\"part\" SIZE=\"3.6T\" MOUNTPOINT=\"\" FSTYPE=\"ext4\" PKNAME=\"sdb\"")
|
|
chk "names the partition" "$(grep -c '/dev/sdb1' <<< "$out")" "1"
|
|
chk "not the whole disk as well" "$(grep -cE '/dev/sdb ' <<< "$out")" "0"
|
|
|
|
echo "--- an LVM member is not free space (the column-shift case) ---"
|
|
out=$(runrows <<< "$ROOT_ROWS
|
|
NAME=\"sdb\" TYPE=\"disk\" SIZE=\"2T\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"\"
|
|
NAME=\"sdb1\" TYPE=\"part\" SIZE=\"2T\" MOUNTPOINT=\"\" FSTYPE=\"LVM2_member\" PKNAME=\"sdb\"")
|
|
chk "offers nothing" "$(grep -c NOTICE <<< "$out")" "0"
|
|
|
|
echo "--- a mounted spare is already usable, so the picker handles it ---"
|
|
out=$(runrows <<< "$ROOT_ROWS
|
|
NAME=\"sdb\" TYPE=\"disk\" SIZE=\"2T\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"\"
|
|
NAME=\"sdb1\" TYPE=\"part\" SIZE=\"2T\" MOUNTPOINT=\"/mnt/data\" FSTYPE=\"ext4\" PKNAME=\"sdb\"")
|
|
chk "offers nothing" "$(grep -c NOTICE <<< "$out")" "0"
|
|
|
|
echo "--- a partition whose CHILD is mounted (nothing on the part itself) ---"
|
|
# sdb1 has no mount point and no member fstype of its own; only dm-1 below it is
|
|
# mounted. Without walking the parent chain, sdb1 looks like free space.
|
|
out=$(runrows <<< "$ROOT_ROWS
|
|
NAME=\"sdb\" TYPE=\"disk\" SIZE=\"2T\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"\"
|
|
NAME=\"sdb1\" TYPE=\"part\" SIZE=\"2T\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"sdb\"
|
|
NAME=\"dm-1\" TYPE=\"crypt\" SIZE=\"2T\" MOUNTPOINT=\"/data\" FSTYPE=\"ext4\" PKNAME=\"sdb1\"")
|
|
chk "offers nothing" "$(grep -c NOTICE <<< "$out")" "0"
|
|
|
|
echo "--- small stubs are not app-data drives ---"
|
|
out=$(runrows <<< "$ROOT_ROWS
|
|
NAME=\"sdb\" TYPE=\"disk\" SIZE=\"512M\" MOUNTPOINT=\"\" FSTYPE=\"\" PKNAME=\"\"")
|
|
chk "offers nothing" "$(grep -c NOTICE <<< "$out")" "0"
|
|
|
|
echo ""
|
|
if (( fail )); then echo "FAILED"; exit 1; fi
|
|
echo "All installer disk-notice checks passed."
|