RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / scripts / btrfs-check.sh
4483 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/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")/.."
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 target/$img.img"
    btrfs check --readonly target/$img.img 2>&1 | grep -E "^(found|no error|ERROR)" | sed 's/^/  /' \
        || true
    btrfs check --readonly target/$img.img >/dev/null 2>&1 || { echo "!! btrfs check found problems"; status=1; }
done

echo "== run the tests/btrfs.rs kernel"
KERNEL=$(ls -t target/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=target/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=target/$img.img,format=raw,if=virtio,snapshot=on")
done
set +e
timeout 120 qemu-system-x86_64 -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 target/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