#!/usr/bin/env bash # Check Rustbelt's read-only btrfs driver against the host's own tools. # # 1. `btrfs check --readonly` every image build.rs made, so we know the # images themselves are sound (made by mkfs.btrfs / btrfs-convert). # 2. Boot the tests/btrfs.rs kernel on them (snapshot=on: its last test # damages a disk on purpose). It prints one line per file it read: # [btrfs-file] /mnt/diskN F path size crc32 # 3. Independently of the manifests that kernel compares against, walk # the host's copies of the trees (build.rs's staging directories) with # Python's zlib.crc32 and compare every file, symlink and directory # with what Rustbelt printed. # # Usage: ./scripts/btrfs-check.sh (after `cargo test --test btrfs`, which # builds the images and the test kernel) set -euo pipefail cd "$(dirname "$0")/.." T="${CARGO_TARGET_DIR:-target}" # stage 21: the build output (cargo's target dir) export PATH="$HOME/.cargo/bin:$PATH:/usr/sbin:/sbin" for tool in btrfs qemu-system-x86_64 python3; do command -v $tool >/dev/null || { echo "need $tool (apt install btrfs-progs qemu-system-x86)"; exit 1; } done status=0 for img in btrfs btrfs-lzo btrfs-zstd btrfs-convert btrfs-sha256; do echo "== btrfs check --readonly $T/$img.img" btrfs check --readonly $T/$img.img 2>&1 | grep -E "^(found|no error|ERROR)" | sed 's/^/ /' \ || true btrfs check --readonly $T/$img.img >/dev/null 2>&1 || { echo "!! btrfs check found problems"; status=1; } done echo "== run the tests/btrfs.rs kernel" KERNEL=$(ls -t $T/x86_64-rust_kernel/debug/build/rustbelt/*/out/bootimage-btrfs-*.bin 2>/dev/null | head -1 || true) [ -n "$KERNEL" ] || { echo "no test kernel: run 'cargo test --test btrfs' first"; exit 1; } LOG=$T/btrfs-check.log drives=() for img in disk ext2 exfat btrfs ext2-4k ext2-rev0 exfat-512 btrfs-lzo btrfs-zstd btrfs-convert btrfs-sha256; do drives+=(-drive "file=$T/$img.img,format=raw,if=virtio,snapshot=on") done set +e timeout 120 qemu-system-x86_64 -smp 4 -drive format=raw,file="$KERNEL" -no-reboot "${drives[@]}" \ -device isa-debug-exit,iobase=0xf4,iosize=0x04 -serial stdio -display none > $LOG code=$? set -e echo " $(grep -c '\[ok\]' $LOG) btrfs tests ok" [ $code = 33 ] || { echo "!! test kernel failed (exit $code), see $LOG"; exit 1; } echo "== compare what Rustbelt read with the host's files (zlib.crc32)" OUT=$(dirname "$(ls -td $T/x86_64-rust_kernel/debug/build/rustbelt/*/out/btrfs-tree | head -1)") python3 - "$LOG" "$OUT" <<'PY' || status=1 import os, sys, zlib log, out = sys.argv[1:] def host(root, skip=()): """Every entry under root as the kernel prints it: {path: line}.""" seen = {} for dirpath, dirs, files in os.walk(root): for name in dirs + files: full = os.path.join(dirpath, name) rel = os.path.relpath(full, root) if rel.split('/')[0] in skip: continue if os.path.islink(full): seen[rel] = f"L\t{rel}\t{os.readlink(full)}" elif os.path.isdir(full): seen[rel] = f"D\t{rel}" else: data = open(full, 'rb').read() seen[rel] = f"F\t{rel}\t{len(data)}\t{zlib.crc32(data):08x}" return seen tree, conv = host(f"{out}/btrfs-tree"), host(f"{out}/btrfs-convert-tree") want = {"/mnt/disk3": tree, "/mnt/disk7": tree, "/mnt/disk8": tree, "/mnt/disk9": conv} got = {m: {} for m in want} for line in open(log, errors='replace'): if "[btrfs-file] " in line: # (the first may follow the test's name) mount, rest = line.split("[btrfs-file] ", 1)[1].rstrip('\n').split('\t', 1) got[mount][rest.split('\t')[1]] = rest total_bad = 0 for mount, files in want.items(): bad = 0 # Only on the converted disk: made by mke2fs / btrfs-convert, not in the tree. extra = {p for p in got[mount] if p in ("lost+found", "ext2_saved") or p.startswith("ext2_saved/")} for p in sorted(set(files) | set(got[mount]) - extra): if files.get(p) != got[mount].get(p): print(f" !! {mount} {p}: host {files.get(p)!r}, Rustbelt {got[mount].get(p)!r}"); bad += 1 n = sum(1 for l in files.values() if l[0] == 'F') size = sum(int(l.split('\t')[2]) for l in files.values() if l[0] == 'F') print(f" {mount}: {len(files)} entries ({n} files, {size} bytes) {'match' if not bad else 'DIFFER'}") total_bad += bad sys.exit(1 if total_bad else 0) PY [ $status = 0 ] && echo "== all good" || echo "== problems found" exit $status