SchOS/tools/posix/mkiso.sh
Nexoniarz df1394eaa3 SCFF: the live root ships compressed, with Xorg and Window Maker
SCFF, the SchOS Compressed File Format (include/fs/scff.h), is a block
image cut into 128 KB chunks, each its own zstd frame, behind an index.
tools/mkscff writes it from fs-ram.img on every core (-19, 11 s) and
checks each chunk decompresses to itself. The kernel carries the .scff
as its ramdisk and unpacks a chunk the first time a block in it is
read; zero blocks share one page. The kernel and the ISO are the
compressed size, and the root costs memory only for what is touched.
See docs/architecture/scff.md.

zstd 1.5.7 comes from ports/zstd like uACPI: the decoder is compiled
into the kernel at -O2, with a static context and a 64 KB stack of its
own, and mkscff builds the whole library from the same release.

world now has xorg-server, the fbdev and evdev drivers, xinit, Window
Maker, xterm and DejaVu, and /root/.xinitrc runs wmaker. That root is
358 MB, 102 MB as SCFF.

The ISO no longer carries the root a third time as an ISO 9660 tree,
which nothing read. schos.iso: 1389 MB -> 318 MB. El Torito keeps its
own copy of the EFI image, the layout real machines have booted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-24 21:17:31 +02:00

198 lines
9.2 KiB
Shell

#!/bin/sh
# Build a bootable SchOS ISO.
#
# tools/posix/mkiso.sh [output.iso]
#
# The disc carries one El Torito boot entry and the loader and kernel as files:
#
# /boot/efiboot.img a FAT image holding the loader and the kernel
# /EFI/BOOT/BOOTX64.EFI the same loader, visible as a file on the disc
# /schos/kernel the same kernel, for chainloaders (below)
#
# The root filesystem is inside the kernel, as its ramdisk. The disc used to
# carry it a third time as an ISO 9660 tree, which nothing reads -- SchOS has
# no ISO 9660 driver -- and once Xorg was in the root that copy alone was
# 360 MB.
#
# --- Why only one entry -----------------------------------------------------
#
# There was a second, for BIOS: cdboot and stage2 concatenated with the kernel
# and loaded by El Torito in no-emulation mode. The BIOS loader is gone, so the
# entry that started it is too.
#
# What UEFI wants is not that mechanism at all. Firmware does not run
# no-emulation boot code; it looks for a FAT filesystem containing
# EFI/BOOT/BOOTX64.EFI and hands that volume to the application it finds. So
# the entry is a FAT image built with mtools, and it carries the kernel as well
# as the loader -- BOOTX64.EFI opens \schos\kernel on the volume it was itself
# loaded from, and for a UEFI boot that volume is this FAT image, not the ISO
# 9660 tree around it. Without the kernel in here the loader comes up and halts
# with EFI_NOT_FOUND, which is what it did.
#
# The loader is also copied into the ISO tree at /EFI/BOOT/BOOTX64.EFI, because
# some firmware looks there directly when booting from a USB stick.
#
# --- Booting from a USB stick -----------------------------------------------
#
# -isohybrid-gpt-basdat adds a GPT that marks the FAT image as a basic data
# partition, so writing this .iso to a stick with dd produces something UEFI
# will boot. That was already the only arrangement that worked from a stick:
# the firmware presents the medium as a hard disk, while the BIOS loader read
# the kernel back over ATAPI, so it never booted this way even when it existed.
#
# --- Rock Ridge and Joliet --------------------------------------------------
#
# Plain ISO 9660 gives 8.3 uppercase names with no permissions, so the sysroot
# would arrive as /BIN/TOYBOX.;1 with every file marked the same. Rock Ridge
# restores POSIX names, permissions and symlinks for Unix readers; Joliet does
# long names for Windows. Both are additions to the same image -- a reader that
# understands neither still sees a valid, if ugly, ISO 9660 disc.
#
# -cache-inodes matters more than it looks: the sysroot is a toybox multicall
# binary with a couple of hundred hard links to it. Without inode caching each
# link becomes its own copy and the disc grows by two orders of magnitude.
set -e
# Run from anywhere: resolve the repo root from this script's own location
# rather than trusting the caller's working directory. Everything below is
# relative to it, and running from tools/posix/ used to fail with "missing
# build/kernel/kernel -- run make first" on a tree that was perfectly built.
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
repo=$(CDPATH= cd -- "$script_dir/../.." && pwd)
# An output path given on the command line means where the *caller* is, not
# where the repo is, so it is made absolute before the cd below moves the goal
# posts under it. Without an argument the disc lands at the repo root, which is
# where `make iso` has always put it.
invoked_from=$(pwd)
OUT=${1:-schos.iso}
case $OUT in
/*) ;;
*) OUT=$invoked_from/$OUT ;;
esac
cd "$repo"
BUILD=${BUILD:-build}
STAGING="$BUILD/iso"
KERNEL="$BUILD/kernel/kernel"
EFIAPP="$BUILD/uefi/BOOTX64.EFI"
[ -f "$KERNEL" ] || { echo "mkiso: missing $KERNEL -- run make first" >&2; exit 1; }
[ -f "$EFIAPP" ] || { echo "mkiso: missing $EFIAPP -- run 'make uefi' first" >&2; exit 1; }
# mtools is not optional any more. It used to be: without it the disc fell back
# to being BIOS-only, which was a degraded disc rather than no disc. There is no
# other entry to fall back to now, so a missing mformat means the ISO would not
# boot anything, and failing here says so instead of shipping it.
if ! command -v mformat >/dev/null 2>&1 || ! command -v mcopy >/dev/null 2>&1; then
echo "mkiso: need mtools (mformat, mcopy) to build the EFI system partition" >&2
echo " Fedora: sudo dnf install mtools" >&2
echo " Debian/Ubuntu: apt install mtools" >&2
exit 1
fi
rm -rf "$STAGING"
mkdir -p "$STAGING/boot" "$STAGING/EFI/BOOT" "$STAGING/schos"
cp "$EFIAPP" "$STAGING/EFI/BOOT/BOOTX64.EFI"
# The kernel in the ISO 9660 tree as well as inside the FAT image.
#
# Not redundancy for its own sake: it depends on who loaded us. Booting the
# disc directly, firmware runs the El Torito FAT image and the loader opens
# \schos\kernel on that volume. But a chainloader -- Ventoy is the common one
# -- ignores El Torito entirely, mounts the ISO 9660 tree and runs
# \EFI\BOOT\BOOTX64.EFI from *there*. The loader then looks for the kernel on
# the volume it was itself loaded from, which is now the ISO tree, and a disc
# with the kernel only in the FAT image cannot answer.
#
# The symptom is precise and was reported from a real machine: the loader comes
# up, draws its menu, and then dies with EFI_NOT_FOUND.
cp "$KERNEL" "$STAGING/schos/kernel"
# The FAT image for the UEFI El Torito entry.
#
# It carries the kernel as well as the loader. BOOTX64.EFI opens \schos\kernel
# on the volume it was itself loaded from, and for a UEFI boot that volume is
# this FAT image -- not the ISO 9660 tree around it. Without the kernel in here
# the loader comes up and halts with EFI_NOT_FOUND, which is what it did.
#
# Sized from what actually goes in plus slack, rather than fixed: a hardcoded
# size is a thing that breaks quietly the first time the kernel grows.
EFI_IMG="$STAGING/boot/efiboot.img"
payload_kb=$(( ( $(wc -c < "$EFIAPP") + $(wc -c < "$KERNEL") + 1023 ) / 1024 ))
img_kb=$(( ( (payload_kb + 512 + 31) / 32 ) * 32 ))
# FAT16, chosen by cluster count: a firmware decides FAT12/16/32 from the
# number of clusters alone, as the spec says to, and FAT12 is the type older
# firmware is least likely to read on a removable disk. So at least 8 MB, and
# the smallest power-of-two cluster that keeps the count under FAT16's 65525.
[ "$img_kb" -lt 8192 ] && img_kb=8192
spc=1
while [ $(( img_kb * 2 / spc )) -gt 65000 ]; do spc=$(( spc * 2 )); done
rm -f "$EFI_IMG"
if true; then
# Create the file first and format it with an explicit geometry.
#
# Not `mformat -f`: that takes one of the standard floppy capacities
# (360, 720, 1440, 2880 KB) and rejects anything else -- "Bad size 512".
# Giving heads, sectors-per-track and a total sector count instead lets the
# image be whatever the loader actually needs, which is the point of sizing
# it from the file rather than hardcoding 1.44 MB.
dd if=/dev/zero of="$EFI_IMG" bs=1024 count="$img_kb" status=none
mformat -i "$EFI_IMG" -h 64 -s 32 -c $spc -T $((img_kb * 2)) ::
mmd -i "$EFI_IMG" ::/EFI ::/EFI/BOOT ::/schos
mcopy -i "$EFI_IMG" "$EFIAPP" ::/EFI/BOOT/BOOTX64.EFI
mcopy -i "$EFI_IMG" "$KERNEL" ::/schos/kernel
fi
# xorriso if it is here, mkisofs otherwise. Both accept the El Torito flags
# below unchanged -- xorriso's -as mkisofs mode exists precisely to be
# argument-compatible with it. The fallback is not academic: there is no xorriso
# package for Windows in scoop, where this is developed, but cdrtools provides
# mkisofs.exe.
if command -v xorriso >/dev/null 2>&1; then
MKISOFS="xorriso -as mkisofs"
# The same FAT image again, as a partition of its own in an MBR.
#
# This used to be -isohybrid-gpt-basdat, which does nothing without an
# MBR template (-isohybrid-mbr) -- so the ISO had no partition table at
# all, and written to a stick it was a CD filesystem at sector 0. A
# B550's firmware reads that as though it were a disc and boots it; an
# HP all-in-one from 2012 looks for an EFI system partition on a USB
# disk, finds none, and lists the stick under Legacy only. A partition
# of type 0xEF is what every UEFI firmware looks for on a removable
# disk, MBR rather than GPT because the oldest ones read that best.
HYBRID="-append_partition 2 0xef $EFI_IMG"
# El Torito keeps its own copy of the image in the tree, the layout real
# machines have booted. Pointing it into the partition instead
# (--interval:appended_partition_2:all::, as Arch does) saves a copy, and
# an ASUS laptop stopped listing the stick the day that was tried. With
# the root compressed (SCFF) the copy is only the kernel's size.
elif command -v mkisofs >/dev/null 2>&1; then
MKISOFS="mkisofs"
HYBRID=""
else
echo "mkiso: need xorriso or mkisofs on PATH" >&2
echo " Windows: scoop install cdrtools" >&2
echo " Debian/Ubuntu: apt install xorriso" >&2
exit 1
fi
# shellcheck disable=SC2086 # EFI_ARGS is a deliberate word list
$MKISOFS \
-quiet \
-V SCHOS \
-A "SchOS" \
-R -J -joliet-long \
-cache-inodes \
-e boot/efiboot.img \
-no-emul-boot \
$HYBRID \
-o "$OUT" \
"$STAGING"
echo "==> $OUT ($(wc -c < "$OUT") bytes)"
echo " UEFI: qemu-system-x86_64 -bios OVMF_CODE.fd -cdrom $OUT"
echo " USB: dd if=$OUT of=/dev/sdX bs=4M"