Files
LithosAnanke/scripts/mkdiskimage.sh
Claude 5bb247d678 wip(kernel): Artemis grows to fill the stick at its first boot -- metadata only, capped at 256 GiB (#92, Task 2) [skip ci]
Not yet accepted (plan 2026-10-29-stick-5, Task 2, step 3).
- gpt.c: the writer, gpt_grow_last() -- the backup entry array and header to
  the disk's end, the last partition to the last usable sector (whole MiB,
  capped), both headers' CRCs, the backup before the primary, the
  protective MBR's size; gpt_alternate_lba().
- sk_v4_grow.c: the volume grown to its partition -- its fence (the Artemis
  signature, Zuse's genesis marker) moved to the new top, the header last;
  the partition grown no further than the BAM tracks (the block subsystem
  places the fence by the device's size, so a volume must fill it).
- sk_v4_identity.c: a stick of 8 GB or more whose GPT ends short of the disk
  is grown before its partitions are opened; "Artemis: grown to fill the
  stick" / "under 8 GB -- not grown" / "past 256 GiB ... left unused".
- block_subsystem: blk_volume_compute_totals() (compute_totals_from_B,
  public) and blk_subsys_set_format_bam_for(); v3's behaviour unchanged.
- mkartemis --bam-for, mkdiskimage.sh --bam-for, kernel/Makefile
  STICK_GROW_MIN_BYTES / STICK_ARTEMIS_CAP_GIB: the stick's and data stick's
  Artemis BAM sized for 256 GiB (6144 devblocks). STICK.md 5a: 256 GiB (R1).

After: stick_grow PASS (16 GiB: 12129370 user blocks, 8 s POST to prompt;
second boot not grown, recognised; sfdisk --verify: no errors), stick_small
PASS, check_images.sh PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-08 12:03:46 +00:00

165 lines
7.8 KiB
Bash

#!/usr/bin/env bash
# mkdiskimage.sh -- build a single-partition FAT32 boot disk image.
#
# Used by `make boot_image TARGET=<board>` (kernel/Makefile). One script for
# every board so the geometry rules live in one place:
# - partition 1 starts at 1 MiB (sector 2048) and runs to the last usable
# sector; the FAT32 filesystem is sized to the partition exactly (a
# larger filesystem overruns the partition -- the bug that made early
# SER5 thumbdrive images unbootable, see kernel/Makefile `thumbdrive`);
# - GPT tables reserve 33 sectors at the end of the disk for the backup
# header, MBR tables do not.
#
# Usage:
# mkdiskimage.sh --out IMG --table gpt|mbr --size-mib N --label LABEL \
# SRC:DEST [SRC:DEST ...]
#
# --table gpt -> partition type "EFI System" (UEFI firmware, U-Boot bootefi)
#
# The StarshipOS stick and data stick (docs/v4.0.0/STICK.md 4; --table gpt only):
# --starship IDENT_MIB --mkartemis TOOL --esp-mib E [--bam-for SECTORS]
# lays out an ESP of E MiB holding the SRC:DEST files (E=0: no ESP, no files),
# then a StarshipOS identity partition of IDENT_MIB MiB (all zeros), then a
# StarshipOS Artemis partition to the last whole MiB before the backup GPT,
# formatted and signed by TOOL (tools/mkartemis.c) -- with --bam-for, its BAM sized for an Artemis
# of SECTORS, so it can grow that far on a larger stick (STICK.md 5a). Every partition starts and
# ends on a whole MiB, so on 1 KiB blocks, as the kernel's GPT reader needs.
#
# Zuse's minted pair as a stick (docs/v4.0.0/plans/2026-10-27-stick-4-one-disk.md, Task 1):
# --starship-from IDENT_IMG ART_IMG --esp-mib E (no --size-mib, no --mkartemis)
# the same layout, but the identity partition is exactly IDENT_IMG's size and holds its bytes,
# and the Artemis partition is exactly ART_IMG's size and holds its bytes (Artemis's signature
# and fence are placed from the volume's size, so it cannot grow). Each must be a whole MiB.
# The image is 1 + E + |IDENT_IMG| + |ART_IMG| + 1 MiB.
# --table mbr -> partition type 0x0c FAT32 (LBA) (Raspberry Pi firmware)
# DEST is an absolute path inside the FAT filesystem; parent directories are
# created as needed.
#
# Needs only sfdisk (util-linux), mkfs.fat (dosfstools) and mtools -- no
# root, no loop devices.
set -euo pipefail
OUT="" TABLE="" SIZE_MIB="" LABEL="" IDENT_MIB="" MKARTEMIS="" ESP_MIB="" FROM_IDENT="" FROM_ART="" BAM_FOR=""
while [ $# -gt 0 ]; do
case "$1" in
--out) OUT="$2"; shift 2 ;;
--table) TABLE="$2"; shift 2 ;;
--size-mib) SIZE_MIB="$2"; shift 2 ;;
--label) LABEL="$2"; shift 2 ;;
--starship) IDENT_MIB="$2"; shift 2 ;;
--mkartemis) MKARTEMIS="$2"; shift 2 ;;
--esp-mib) ESP_MIB="$2"; shift 2 ;;
--bam-for) BAM_FOR="$2"; shift 2 ;;
--starship-from) FROM_IDENT="$2"; FROM_ART="$3"; shift 3 ;;
--) shift; break ;;
-*) echo "mkdiskimage.sh: unknown option $1" >&2; exit 2 ;;
*) break ;;
esac
done
if [ -n "$FROM_IDENT" ]; then
[ "$TABLE" = gpt ] && [ -n "$ESP_MIB" ] && [ -z "$SIZE_MIB" ] && [ -z "$MKARTEMIS" ] || {
echo "mkdiskimage.sh: --starship-from needs --table gpt and --esp-mib, and no --size-mib or --mkartemis" >&2; exit 2; }
for f in "$FROM_IDENT" "$FROM_ART"; do
[ -f "$f" ] || { echo "mkdiskimage.sh: missing input $f" >&2; exit 1; }
[ $(( $(stat -c %s "$f") % 1048576 )) -eq 0 ] || { echo "mkdiskimage.sh: $f is not a whole MiB" >&2; exit 2; }
done
IDENT_MIB=$(( $(stat -c %s "$FROM_IDENT") / 1048576 ))
FROM_ART_MIB=$(( $(stat -c %s "$FROM_ART") / 1048576 ))
SIZE_MIB=$(( 1 + ESP_MIB + IDENT_MIB + FROM_ART_MIB + 1 ))
MKARTEMIS=-
fi
[ -n "$OUT" ] && [ -n "$TABLE" ] && [ -n "$SIZE_MIB" ] && [ -n "$LABEL" ] || {
echo "mkdiskimage.sh: --out, --table, --size-mib and --label are required" >&2; exit 2; }
if [ -n "$IDENT_MIB" ]; then
[ "$TABLE" = gpt ] && [ -n "$MKARTEMIS" ] && [ -n "$ESP_MIB" ] || {
echo "mkdiskimage.sh: --starship needs --table gpt, --mkartemis and --esp-mib" >&2; exit 2; }
[ "$ESP_MIB" -gt 0 ] || [ $# -eq 0 ] || { echo "mkdiskimage.sh: --esp-mib 0 takes no files" >&2; exit 2; }
fi
[ $# -gt 0 ] || [ "${ESP_MIB:-}" = 0 ] || { echo "mkdiskimage.sh: no SRC:DEST files given" >&2; exit 2; }
for tool in sfdisk mkfs.fat mmd mcopy; do
command -v "$tool" >/dev/null 2>&1 || PATH="$PATH:/usr/sbin:/sbin"
command -v "$tool" >/dev/null 2>&1 || {
echo "mkdiskimage.sh: $tool not found (install util-linux, dosfstools, mtools)" >&2; exit 1; }
done
TOTAL_SECTORS=$(( SIZE_MIB * 2048 ))
START=2048
case "$TABLE" in
gpt) LAST=$(( TOTAL_SECTORS - 34 )); PTYPE="C12A7328-F81F-11D2-BA4B-00A0C93EC93B" ;;
mbr) LAST=$(( TOTAL_SECTORS - 1 )); PTYPE="c" ;;
*) echo "mkdiskimage.sh: --table must be gpt or mbr" >&2; exit 2 ;;
esac
PART_SECTORS=$(( LAST - START + 1 ))
# StarshipOS partition types (kernel/src/gpt.c)
IDENT_TYPE="23BEBFE1-4817-4CF3-A537-9069BF159788"
ART_TYPE="BCD7B586-1C8E-4EEF-86A3-273A8D8EE624"
if [ -n "$IDENT_MIB" ]; then
PART_SECTORS=$(( ESP_MIB * 2048 ))
IDENT_START=$(( START + PART_SECTORS ))
IDENT_SECTORS=$(( IDENT_MIB * 2048 ))
ART_START=$(( IDENT_START + IDENT_SECTORS ))
ART_SECTORS=$(( (LAST + 1 - ART_START) / 2048 * 2048 ))
[ -z "$FROM_ART" ] || ART_SECTORS=$(( FROM_ART_MIB * 2048 ))
[ "$ART_SECTORS" -gt 0 ] || { echo "mkdiskimage.sh: no room for Artemis in ${SIZE_MIB} MiB" >&2; exit 2; }
fi
ESP="$OUT.part1"
rm -f "$OUT" "$ESP"
mkdir -p "$(dirname "$OUT")"
truncate -s $(( TOTAL_SECTORS * 512 )) "$OUT"
if [ -n "$IDENT_MIB" ]; then
{
echo "label: gpt"
[ "$ESP_MIB" -gt 0 ] && printf 'start=%d, size=%d, type=%s, name="EFI System"\n' "$START" "$PART_SECTORS" "$PTYPE"
printf 'start=%d, size=%d, type=%s, name="StarshipOS identity"\n' "$IDENT_START" "$IDENT_SECTORS" "$IDENT_TYPE"
printf 'start=%d, size=%d, type=%s, name="StarshipOS Artemis"\n' "$ART_START" "$ART_SECTORS" "$ART_TYPE"
} | sfdisk --quiet "$OUT"
if [ -n "$FROM_IDENT" ]; then
dd if="$FROM_IDENT" of="$OUT" bs=1M seek=$(( IDENT_START / 2048 )) conv=notrunc,sparse status=none
dd if="$FROM_ART" of="$OUT" bs=1M seek=$(( ART_START / 2048 )) conv=notrunc,sparse status=none
else
"$MKARTEMIS" --format "$OUT" --first-lba "$ART_START" --sectors "$ART_SECTORS" \
${BAM_FOR:+--bam-for "$BAM_FOR"} >/dev/null || {
echo "mkdiskimage.sh: formatting Artemis failed" >&2; rm -f "$OUT"; exit 1; }
fi
if [ "$ESP_MIB" = 0 ]; then
echo " IMAGE $OUT (gpt, ${SIZE_MIB} MiB: StarshipOS identity ${IDENT_MIB} MiB, Artemis $(( ART_SECTORS / 2048 )) MiB)"
exit 0
fi
elif [ "$TABLE" = gpt ]; then
printf 'label: gpt\nstart=%d, size=%d, type=%s, name="EFI System"\n' \
"$START" "$PART_SECTORS" "$PTYPE" | sfdisk --quiet "$OUT"
else
printf 'label: dos\nstart=%d, size=%d, type=%s, bootable\n' \
"$START" "$PART_SECTORS" "$PTYPE" | sfdisk --quiet "$OUT"
fi
truncate -s $(( PART_SECTORS * 512 )) "$ESP"
mkfs.fat -F 32 -n "$LABEL" "$ESP" >/dev/null
for spec in "$@"; do
src="${spec%%:*}"
dest="${spec#*:}"
[ -f "$src" ] || { echo "mkdiskimage.sh: missing input $src" >&2; rm -f "$OUT" "$ESP"; exit 1; }
dir="$(dirname "$dest")"
path=""
IFS=/ read -r -a parts <<< "${dir#/}"
for p in "${parts[@]}"; do
[ -n "$p" ] || continue
path="$path/$p"
mmd -D s -i "$ESP" "::$path" 2>/dev/null || true
done
mcopy -o -i "$ESP" "$src" "::$dest"
done
dd if="$ESP" of="$OUT" bs=512 seek="$START" conv=notrunc status=none
rm -f "$ESP"
if [ -n "$IDENT_MIB" ]; then
echo " IMAGE $OUT (gpt, ${SIZE_MIB} MiB: ESP ${ESP_MIB} MiB FAT32 '$LABEL', StarshipOS identity ${IDENT_MIB} MiB, Artemis $(( ART_SECTORS / 2048 )) MiB)"
else
echo " IMAGE $OUT ($TABLE, ${SIZE_MIB} MiB, FAT32 '$LABEL')"
fi