wolfBoot/tools/fs-test/mkfixtures.sh

208 lines
7.6 KiB
Bash
Executable File

#!/bin/bash
# Generate assorted REAL filesystem images for tools/fs-test.
#
# Everything here uses mkfs.vfat + mtools and mke2fs -d, so no root, no
# loopback mounts and no sudo. Images are sparse on the host, so the
# directory costs far less than its apparent size.
#
# Each fixture is a whole-disk image with a real partition table, so
# src/disk.c parses an actual MBR or GPT rather than a synthetic one.
set -e
cd "$(dirname "$0")"
OUT=fixtures
mkdir -p "$OUT"
need() {
command -v "$1" >/dev/null 2>&1 || { echo "SKIP: $1 not installed"; return 1; }
}
# stamp_mbr <disk.img> <part_start_bytes> <part_size_bytes> <ptype>
stamp_mbr() {
python3 - "$1" "$2" "$3" "$4" <<'PY'
import sys, struct
img, off, size, ptype = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4], 0)
sec = 512
with open(img, 'r+b') as f:
f.seek(0)
mbr = bytearray(512)
e = 0x1BE
mbr[e + 4] = ptype
struct.pack_into('<I', mbr, e + 8, off // sec)
struct.pack_into('<I', mbr, e + 12, size // sec)
mbr[0x1FE] = 0x55
mbr[0x1FF] = 0xAA
f.write(mbr)
PY
}
# stamp_gpt <disk.img> <part_start_bytes> <part_size_bytes> <name>
stamp_gpt() {
python3 - "$1" "$2" "$3" "$4" <<'PY'
import sys, struct, binascii
img, off, size, name = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]), sys.argv[4]
sec = 512
first, last = off // sec, (off + size) // sec - 1
with open(img, 'r+b') as f:
# protective MBR
mbr = bytearray(512)
e = 0x1BE
mbr[e + 4] = 0xEE
struct.pack_into('<I', mbr, e + 8, 1)
struct.pack_into('<I', mbr, e + 12, 0xFFFFFFFF)
mbr[0x1FE] = 0x55; mbr[0x1FF] = 0xAA
f.seek(0); f.write(mbr)
# one partition entry at LBA 2
ent = bytearray(128)
ent[0:16] = bytes(range(1, 17)) # type GUID (non-zero)
ent[16:32] = bytes(range(17, 33)) # unique GUID
struct.pack_into('<Q', ent, 32, first)
struct.pack_into('<Q', ent, 40, last)
nm = name.encode('utf-16-le')[:72]
ent[56:56+len(nm)] = nm
arr = bytes(ent) + bytes(128 * 3) # 4 entries, 3 empty
f.seek(2 * sec); f.write(arr)
hdr = bytearray(92)
hdr[0:8] = b'EFI PART'
struct.pack_into('<I', hdr, 8, 0x00010000)
struct.pack_into('<I', hdr, 12, 92)
struct.pack_into('<Q', hdr, 24, 1) # current LBA
struct.pack_into('<Q', hdr, 72, 2) # start of array
struct.pack_into('<I', hdr, 80, 4) # n_part
struct.pack_into('<I', hdr, 84, 128) # array_sz
struct.pack_into('<I', hdr, 88, binascii.crc32(arr) & 0xFFFFFFFF)
struct.pack_into('<I', hdr, 16, 0)
struct.pack_into('<I', hdr, 16, binascii.crc32(bytes(hdr)) & 0xFFFFFFFF)
f.seek(sec); f.write(bytes(hdr) + bytes(sec - 92))
PY
}
# wrap <fs.img> <disk.img> <mbr|gpt> <ptype-or-name>
wrap() {
local fs="$1" disk="$2" mode="$3" tag="$4"
local off=$((1024 * 1024))
local size
size=$(stat -c %s "$fs")
rm -f "$disk"
truncate -s $((off + size)) "$disk"
dd if="$fs" of="$disk" bs=1M seek=1 conv=notrunc status=none
if [ "$mode" = "gpt" ]; then
stamp_gpt "$disk" "$off" "$size" "$tag"
else
stamp_mbr "$disk" "$off" "$size" "$tag"
fi
rm -f "$fs"
}
# QUICK=1 skips the two expensive fixtures (a 2.5 GiB volume and a 160 MiB
# payload). Everything else is small, which makes the set usable in CI.
QUICK=${QUICK:-0}
echo "=== payloads ==="
mkdir -p "$OUT/payload"
# A signed-image-shaped payload: a header-sized run of zeros then real data,
# which is exactly the shape that makes mke2fs punch a hole.
{ head -c 1024 /dev/zero; head -c $((1024 * 1024 - 1024)) /dev/urandom; } \
> "$OUT/payload/os.itb"
if [ "$QUICK" != 1 ]; then
head -c $((160 * 1024 * 1024)) /dev/urandom > "$OUT/payload/big.itb"
fi
head -c 4096 /dev/urandom > "$OUT/payload/small.itb"
# ----------------------------------------------------------------- FAT32
if need mkfs.vfat && need mcopy; then
echo "=== fat32: default cluster size, MBR ==="
truncate -s 96M "$OUT/f1.fs"
mkfs.vfat -F 32 -n SLOTA "$OUT/f1.fs" >/dev/null
mmd -i "$OUT/f1.fs" ::/boot
mcopy -i "$OUT/f1.fs" "$OUT/payload/os.itb" ::/boot/os.itb
mcopy -i "$OUT/f1.fs" "$OUT/payload/small.itb" ::/boot/small.itb
wrap "$OUT/f1.fs" "$OUT/fat32-default.img" mbr 0x0C
# 32 KiB clusters need >= 65525 of them to be FAT32 at all, so the
# volume has to be over 2 GiB. The image is sparse, so this costs
# almost nothing on disk.
if [ "$QUICK" != 1 ]; then
echo "=== fat32: 32 KiB clusters (2.5 GiB), long filenames, deep path ==="
truncate -s 2600M "$OUT/f2.fs"
mkfs.vfat -F 32 -s 64 -n BIGCLUS "$OUT/f2.fs" >/dev/null
mmd -i "$OUT/f2.fs" ::/a
mmd -i "$OUT/f2.fs" ::/a/b
mmd -i "$OUT/f2.fs" "::/a/b/a-very-long-directory-name"
mcopy -i "$OUT/f2.fs" "$OUT/payload/os.itb" \
"::/a/b/a-very-long-directory-name/an-extremely-long-image-name.itb"
wrap "$OUT/f2.fs" "$OUT/fat32-bigclus-lfn.img" mbr 0x0C
fi
echo "=== fat32: fragmented file ==="
truncate -s 96M "$OUT/f3.fs"
mkfs.vfat -F 32 -n FRAG "$OUT/f3.fs" >/dev/null
mmd -i "$OUT/f3.fs" ::/boot
# Interleave filler with the target, then delete the filler, so the
# target's clusters are left scattered across the volume.
for i in 0 1 2 3 4 5 6 7; do
head -c $((512 * 1024)) /dev/urandom > "$OUT/pad$i"
mcopy -i "$OUT/f3.fs" "$OUT/pad$i" "::/boot/pad$i.bin"
head -c $((512 * 1024)) /dev/urandom > "$OUT/frag$i"
mcopy -i "$OUT/f3.fs" "$OUT/frag$i" "::/boot/frag$i.bin"
done
for i in 0 1 2 3 4 5 6 7; do
mdel -i "$OUT/f3.fs" "::/boot/pad$i.bin"
done
mcopy -i "$OUT/f3.fs" "$OUT/payload/os.itb" ::/boot/os.itb
rm -f "$OUT"/pad? "$OUT"/frag?
wrap "$OUT/f3.fs" "$OUT/fat32-fragmented.img" mbr 0x0C
fi
# ------------------------------------------------------------------ ext4
if need mke2fs; then
echo "=== ext4: 4 KiB blocks, stock features, GPT ==="
mkdir -p "$OUT/root4k/boot"
cp "$OUT/payload/os.itb" "$OUT/root4k/boot/os.itb"
cp "$OUT/payload/small.itb" "$OUT/root4k/boot/small.itb"
truncate -s 128M "$OUT/e1.fs"
mke2fs -q -t ext4 -b 4096 -L SLOTB -d "$OUT/root4k" -F "$OUT/e1.fs"
wrap "$OUT/e1.fs" "$OUT/ext4-4k.img" gpt "bootpart"
echo "=== ext4: 1 KiB blocks (first_data_block == 1) ==="
mkdir -p "$OUT/root1k/boot"
cp "$OUT/payload/os.itb" "$OUT/root1k/boot/os.itb"
truncate -s 64M "$OUT/e2.fs"
mke2fs -q -t ext4 -b 1024 -L ONEK -d "$OUT/root1k" -F "$OUT/e2.fs"
wrap "$OUT/e2.fs" "$OUT/ext4-1k.img" mbr 0x83
echo "=== ext4: deep path + long name ==="
mkdir -p "$OUT/rootdeep/a/b/c/d/e/f"
cp "$OUT/payload/os.itb" \
"$OUT/rootdeep/a/b/c/d/e/f/an-extremely-long-image-name.itb"
truncate -s 64M "$OUT/e3.fs"
mke2fs -q -t ext4 -b 4096 -L DEEP -d "$OUT/rootdeep" -F "$OUT/e3.fs"
wrap "$OUT/e3.fs" "$OUT/ext4-deep.img" mbr 0x83
if [ "$QUICK" != 1 ]; then
echo "=== ext4: multi-extent file (>128 MiB forces extent split) ==="
mkdir -p "$OUT/rootbig/boot"
cp "$OUT/payload/big.itb" "$OUT/rootbig/boot/os.itb"
truncate -s 400M "$OUT/e4.fs"
mke2fs -q -t ext4 -b 4096 -L BIG -d "$OUT/rootbig" -F "$OUT/e4.fs"
wrap "$OUT/e4.fs" "$OUT/ext4-multiextent.img" mbr 0x83
fi
echo "=== ext4: 2 KiB blocks ==="
mkdir -p "$OUT/root2k/boot"
cp "$OUT/payload/os.itb" "$OUT/root2k/boot/os.itb"
truncate -s 64M "$OUT/e5.fs"
mke2fs -q -t ext4 -b 2048 -L TWOK -d "$OUT/root2k" -F "$OUT/e5.fs"
wrap "$OUT/e5.fs" "$OUT/ext4-2k.img" mbr 0x83
rm -rf "$OUT/root4k" "$OUT/root1k" "$OUT/rootdeep" "$OUT/rootbig" \
"$OUT/root2k"
fi
echo
echo "=== fixtures ==="
ls -lh "$OUT"/*.img 2>/dev/null | awk '{print " "$9" "$5}'
echo "(apparent size; images are sparse)"