mkdiskimage.sh --starship-from IDENT ART lays a minted pair out as a StarshipOS stick: an identity partition holding IDENT's bytes and an Artemis partition holding ART's, each exactly its file's size (Artemis's signature and fence are placed from the volume's size). kernel/Makefile: a zusestick target, made fresh for every boot, is ZUSEDISK's default (ZUSE_PAIR names the pair); STICK_PAIR puts the pair in the PC stick. qemu_session.py's Zuse drive is the pair as a stick. check_images.sh checks both forms byte for byte against the pair (60 PASS). On today's kernel all 23 sessions pass on the Zuse stick, and acceptance: amd64, aarch64, riscv64 to [zuse@Hera] ok>, POST 538/538, dict_hash 0xfec8c47cfc6f437f; v4 suite and test-32 0 failures. The plan gains Task 5, screen=boot (ruled 2026-10-28). Plan: docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 1 (R1 as recommended). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
176 lines
9.3 KiB
Bash
Executable File
176 lines
9.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# check_images.sh -- build the stick and the data stick and check them
|
|
# (docs/v4.0.0/plans/2026-10-27-stick-3-images.md, Task 1; docs/v4.0.0/STICK.md 4).
|
|
#
|
|
# The stick: make boot_image TARGET=PC -> build/boards/pc/lithos-pc.img
|
|
# The data stick: make -f kernel/Makefile ARCH=amd64 datastick -> build/amd64/kernel/datastick.img
|
|
# Zuse's pair as a stick (docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 1):
|
|
# make -f kernel/Makefile ARCH=amd64 zusestick -> build/amd64/kernel/zusestick.img
|
|
# make boot_image TARGET=PC STICK_PAIR=disk/zuse-scratch -> the PC stick with the pair in it
|
|
#
|
|
# Prints PASS:/FAIL: per check and RESULT:; exits 0 only if every check passes.
|
|
set -uo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
PATH="$PATH:/usr/sbin:/sbin"
|
|
|
|
STICK=build/boards/pc/lithos-pc.img
|
|
DATA=build/amd64/kernel/datastick.img
|
|
ZSTICK=build/amd64/kernel/zusestick.img
|
|
PAIR=disk/zuse-scratch
|
|
MKARTEMIS=build/amd64/kernel/tools/mkartemis
|
|
ESP_T=C12A7328-F81F-11D2-BA4B-00A0C93EC93B
|
|
IDENT_T=23BEBFE1-4817-4CF3-A537-9069BF159788
|
|
ART_T=BCD7B586-1C8E-4EEF-86A3-273A8D8EE624
|
|
ok=1
|
|
|
|
check() { # check "what" command...
|
|
local what="$1"; shift
|
|
if "$@" >/dev/null 2>&1; then echo "PASS: $what"; else echo "FAIL: $what"; ok=0; fi
|
|
}
|
|
|
|
# part IMG N FIELD -- a field (start, size, type) of partition N (1-based) from sfdisk --json
|
|
part() {
|
|
sfdisk --json "$1" 2>/dev/null | python3 -c "
|
|
import json, sys
|
|
p = json.load(sys.stdin)['partitiontable']
|
|
parts = p['partitions']
|
|
n = int(sys.argv[1])
|
|
if sys.argv[2] == 'count': print(len(parts))
|
|
elif sys.argv[2] == 'lastlba': print(p['lastlba'])
|
|
elif n <= len(parts): print(str(parts[n - 1][sys.argv[2]]).upper())
|
|
" "$2" "$3" 2>/dev/null
|
|
}
|
|
|
|
eq() { [ "$1" = "$2" ]; }
|
|
|
|
# geometry IMG -- every partition starts and ends on a whole MiB, the last one within a MiB of
|
|
# the last usable LBA, and each starts where the one before it ends
|
|
geometry() {
|
|
local img="$1" n i prev=2048 s z last
|
|
n=$(part "$img" 0 count); last=$(part "$img" 0 lastlba)
|
|
for i in $(seq 1 "$n"); do
|
|
s=$(part "$img" "$i" start); z=$(part "$img" "$i" size)
|
|
[ "$s" = "$prev" ] && [ $((s % 2048)) -eq 0 ] && [ $((z % 2048)) -eq 0 ] || return 1
|
|
prev=$((s + z))
|
|
done
|
|
[ $((prev - 1)) -le "$last" ] && [ $((last - (prev - 1))) -lt 2048 ]
|
|
}
|
|
|
|
zeros() { # zeros IMG N -- partition N is all zeros
|
|
local s z
|
|
s=$(part "$1" "$2" start); z=$(part "$1" "$2" size)
|
|
[ -n "$s" ] && [ -n "$z" ] && cmp -s -i "$((s * 512)):0" -n "$((z * 512))" "$1" /dev/zero
|
|
}
|
|
|
|
artemis_check() { # artemis_check IMG N -- mkartemis --check on partition N
|
|
"$MKARTEMIS" --check "$1" --first-lba "$(part "$1" "$2" start)" --sectors "$(part "$1" "$2" size)"
|
|
}
|
|
|
|
same() { # same IMG N FILE -- partition N holds FILE's bytes, and is exactly its size
|
|
local s z
|
|
s=$(part "$1" "$2" start); z=$(part "$1" "$2" size)
|
|
[ -n "$s" ] && [ -n "$z" ] && [ $((z * 512)) -eq "$(stat -c %s "$3")" ] &&
|
|
cmp -s -i "$((s * 512)):0" -n "$((z * 512))" "$1" "$3"
|
|
}
|
|
|
|
uuid() { artemis_check "$1" "$2" 2>/dev/null | sed -n 's/.*disk_uuid \([0-9a-f]*\).*/\1/p'; }
|
|
|
|
echo "== building"
|
|
# no image from an earlier run may stand in for one this run failed to build
|
|
rm -f "$STICK" "$DATA"
|
|
check "the stick builds (make boot_image TARGET=PC)" make boot_image TARGET=PC
|
|
check "the data stick builds (make datastick)" make -f kernel/Makefile ARCH=amd64 datastick
|
|
|
|
echo "== the stick ($STICK)"
|
|
check "it exists, 2048 MiB" eq "$(stat -c %s "$STICK" 2>/dev/null)" "$((2048 * 1048576))"
|
|
check "three partitions" eq "$(part "$STICK" 0 count)" 3
|
|
check "1: EFI System, 64 MiB at LBA 2048" eq "$(part "$STICK" 1 type)/$(part "$STICK" 1 start)/$(part "$STICK" 1 size)" "$ESP_T/2048/131072"
|
|
check "2: StarshipOS identity, 500 MiB" eq "$(part "$STICK" 2 type)/$(part "$STICK" 2 start)/$(part "$STICK" 2 size)" "$IDENT_T/133120/1024000"
|
|
check "3: StarshipOS Artemis, the rest" eq "$(part "$STICK" 3 type)/$(part "$STICK" 3 start)" "$ART_T/1157120"
|
|
check "whole MiB, contiguous, Artemis to the end" geometry "$STICK"
|
|
check "sfdisk --verify: the GPT is clean" sfdisk --verify "$STICK"
|
|
OFF=$((2048 * 512))
|
|
check "the ESP holds EFI/BOOT/BOOTX64.EFI" mdir -i "$STICK@@$OFF" ::/EFI/BOOT/BOOTX64.EFI
|
|
check "the ESP holds startup.nsh" mdir -i "$STICK@@$OFF" ::/startup.nsh
|
|
check "starforth.cfg's first word is system=v4" eq "$(mtype -i "$STICK@@$OFF" ::/starforth.cfg 2>/dev/null | awk '{print $1; exit}')" "system=v4"
|
|
check "the identity partition is all zeros" zeros "$STICK" 2
|
|
check "Artemis: STFR v2, signature ok" artemis_check "$STICK" 3
|
|
|
|
echo "== the data stick ($DATA)"
|
|
check "it exists, 1536 MiB" eq "$(stat -c %s "$DATA" 2>/dev/null)" "$((1536 * 1048576))"
|
|
check "two partitions" eq "$(part "$DATA" 0 count)" 2
|
|
check "1: StarshipOS identity, 500 MiB at LBA 2048" eq "$(part "$DATA" 1 type)/$(part "$DATA" 1 start)/$(part "$DATA" 1 size)" "$IDENT_T/2048/1024000"
|
|
check "2: StarshipOS Artemis, the rest" eq "$(part "$DATA" 2 type)/$(part "$DATA" 2 start)" "$ART_T/1026048"
|
|
check "whole MiB, contiguous, Artemis to the end" geometry "$DATA"
|
|
check "sfdisk --verify: the GPT is clean" sfdisk --verify "$DATA"
|
|
check "the identity partition is all zeros" zeros "$DATA" 1
|
|
check "Artemis: STFR v2, signature ok" artemis_check "$DATA" 2
|
|
U1=$(uuid "$DATA" 2)
|
|
check "the data stick builds again" make -f kernel/Makefile ARCH=amd64 datastick
|
|
U2=$(uuid "$DATA" 2)
|
|
check "made again, it is fresh: its own disk_uuid ($U1 / $U2)" test -n "$U1" -a -n "$U2" -a "$U1" != "$U2"
|
|
|
|
echo "== Zuse's pair as a stick ($ZSTICK)"
|
|
rm -f "$ZSTICK"
|
|
check "it builds (make zusestick)" make -f kernel/Makefile ARCH=amd64 zusestick
|
|
check "it exists, 1090 MiB (1 + 64 + 1024 + 1)" eq "$(stat -c %s "$ZSTICK" 2>/dev/null)" "$((1090 * 1048576))"
|
|
check "two partitions" eq "$(part "$ZSTICK" 0 count)" 2
|
|
check "1: StarshipOS identity, 64 MiB at LBA 2048" eq "$(part "$ZSTICK" 1 type)/$(part "$ZSTICK" 1 start)/$(part "$ZSTICK" 1 size)" "$IDENT_T/2048/131072"
|
|
check "2: StarshipOS Artemis, 1 GiB after it" eq "$(part "$ZSTICK" 2 type)/$(part "$ZSTICK" 2 start)/$(part "$ZSTICK" 2 size)" "$ART_T/133120/2097152"
|
|
check "sfdisk --verify: the GPT is clean" sfdisk --verify "$ZSTICK"
|
|
check "the identity partition is $PAIR/zuse-thumb-ident.img" same "$ZSTICK" 1 "$PAIR/zuse-thumb-ident.img"
|
|
check "the Artemis partition is $PAIR/artemis.img" same "$ZSTICK" 2 "$PAIR/artemis.img"
|
|
check "Artemis: STFR v2, signature ok" artemis_check "$ZSTICK" 2
|
|
|
|
echo "== the PC stick with the pair in it (STICK_PAIR=$PAIR)"
|
|
rm -f "$STICK"
|
|
check "it builds (make boot_image TARGET=PC STICK_PAIR=$PAIR)" make boot_image TARGET=PC STICK_PAIR=$PAIR
|
|
check "three partitions" eq "$(part "$STICK" 0 count)" 3
|
|
check "1: EFI System, 64 MiB at LBA 2048" eq "$(part "$STICK" 1 type)/$(part "$STICK" 1 start)/$(part "$STICK" 1 size)" "$ESP_T/2048/131072"
|
|
check "2: StarshipOS identity, 64 MiB" eq "$(part "$STICK" 2 type)/$(part "$STICK" 2 start)/$(part "$STICK" 2 size)" "$IDENT_T/133120/131072"
|
|
check "3: StarshipOS Artemis, 1 GiB" eq "$(part "$STICK" 3 type)/$(part "$STICK" 3 start)/$(part "$STICK" 3 size)" "$ART_T/264192/2097152"
|
|
check "sfdisk --verify: the GPT is clean" sfdisk --verify "$STICK"
|
|
check "the ESP holds EFI/BOOT/BOOTX64.EFI" mdir -i "$STICK@@$OFF" ::/EFI/BOOT/BOOTX64.EFI
|
|
check "the identity partition is the pair's" same "$STICK" 2 "$PAIR/zuse-thumb-ident.img"
|
|
check "the Artemis partition is the pair's" same "$STICK" 3 "$PAIR/artemis.img"
|
|
check "Artemis: STFR v2, signature ok" artemis_check "$STICK" 3
|
|
check "a pair file that is not a whole MiB is refused" eval '{ bash scripts/mkdiskimage.sh --out build/amd64/kernel/tools/check-images/odd.img --table gpt --label X --esp-mib 0 --starship-from scripts/check_images.sh "$PAIR/artemis.img" 2>&1 || true; } | grep -q "not a whole MiB"'
|
|
|
|
echo "== mkartemis refuses what it cannot do safely"
|
|
T=build/amd64/kernel/tools/check-images; mkdir -p "$T"
|
|
fresh() { rm -f "$T/t.img"; truncate -s 64M "$T/t.img"; }
|
|
untouched() { cmp -s -n $((64 * 1048576)) "$T/t.img" /dev/zero; }
|
|
refused() { ! "$MKARTEMIS" --format "$T/t.img" --first-lba "$1" --sectors "$2" && untouched; }
|
|
for bad in abc "" -2 2048x " 2048" +2048 99999999999999999999999; do
|
|
fresh; check "--first-lba '$bad' refused, nothing written" refused "$bad" 63488
|
|
fresh; check "--sectors '$bad' refused, nothing written" refused 2048 "$bad"
|
|
done
|
|
# a header write that does not land: --format reads the volume back and fails
|
|
cat > "$T/failhdr.c" <<'SHIM'
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
ssize_t pwrite(int fd, const void *b, size_t n, off_t off)
|
|
{
|
|
static ssize_t (*real)(int, const void *, size_t, off_t);
|
|
const char *at = getenv("FAIL_AT");
|
|
if (!real) real = (ssize_t (*)(int, const void *, size_t, off_t))dlsym(RTLD_NEXT, "pwrite");
|
|
if (at && off == (off_t)strtoll(at, NULL, 10)) { errno = EIO; return -1; }
|
|
return real(fd, b, n, off);
|
|
}
|
|
SHIM
|
|
cc -shared -fPIC -o "$T/failhdr.so" "$T/failhdr.c" -ldl
|
|
fresh
|
|
check "a header write that fails, fails --format" eval '! FAIL_AT=$((2048 * 512)) LD_PRELOAD="$T/failhdr.so" "$MKARTEMIS" --format "$T/t.img" --first-lba 2048 --sectors 63488'
|
|
fresh
|
|
check "a good format reads back" "$MKARTEMIS" --format "$T/t.img" --first-lba 2048 --sectors 63488
|
|
rm -f "$T/t.img"
|
|
|
|
# the builds regenerate these; put them back as committed (as qemu_session.py does)
|
|
git checkout -q -- v3/include/version.h capsules/BLOCK_MAP.md sbom.spdx sbom.spdx.json
|
|
|
|
echo "RESULT: $([ $ok = 1 ] && echo PASS || echo FAIL)"
|
|
[ $ok = 1 ]
|