RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / scripts / run-headless.sh
8451 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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env bash
# Build the kernel with the `headless` feature and boot it in QEMU with no
# window. Serial output goes to this terminal; the kernel powers QEMU off via
# the isa-debug-exit device when done. A timeout guards against hangs.
#
# A normal boot runs the shell self-test (disk/scripts/selftest.sh); the
# shell's exit code decides success, and this script then also checks that
# the expected lines appear in the output.
#
# Usage: ./scripts/run-headless.sh                 # boot + shell self-test
#        ./scripts/run-headless.sh stack-overflow  # extra cargo features, comma separated
#        EXT2_IMG=copy.img ./scripts/run-headless.sh
#            use copy.img as the ext2 disk *without* snapshot=on, so the
#            self-test's writes land in that file (scripts/ext2-check.sh
#            then runs e2fsck on it)
#        EXFAT_IMG=copy.img ./scripts/run-headless.sh
#            the same for the exFAT disk (scripts/exfat-check.sh runs
#            fsck.exfat on it)
#        BTRFS_IMG=other.img ./scripts/run-headless.sh
#            use other.img as drive E: (read-only, so always snapshot=on)
set -euo pipefail
cd "$(dirname "$0")/.."

cargo build --features "headless${1:+,$1}"
# Force bootimage to repack the disk image (see scripts/runner.sh for why).
touch target/x86_64-rust_kernel/debug/rustbelt
cargo bootimage --features "headless${1:+,$1}"
IMG=target/x86_64-rust_kernel/debug/bootimage-rustbelt.bin

if [ -n "${EXT2_IMG:-}" ]; then
    EXT2_DRIVE="file=$EXT2_IMG,format=raw,if=virtio"
else
    EXT2_DRIVE="file=target/ext2.img,format=raw,if=virtio,snapshot=on"
fi

if [ -n "${EXFAT_IMG:-}" ]; then
    EXFAT_DRIVE="file=$EXFAT_IMG,format=raw,if=virtio"
else
    EXFAT_DRIVE="file=target/exfat.img,format=raw,if=virtio,snapshot=on"
fi

BTRFS_DRIVE="file=${BTRFS_IMG:-target/btrfs.img},format=raw,if=virtio,snapshot=on"

# Network: QEMU's user-mode NAT (see Cargo.toml run-args). guestfwd runs a
# tiny test web server for 10.0.2.100:80; hostfwd forwards a free host port
# to the guest's port 80, where the self-test's `httpd -n 1` waits, and a
# background curl below fetches its page.
HTTP_PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1])')
NETDEV="user,id=net0,guestfwd=tcp:10.0.2.100:80-cmd:sh scripts/http-reply.sh,guestfwd=tcp:10.0.2.100:7-cmd:cat"
NETDEV="$NETDEV,hostfwd=tcp:127.0.0.1:$HTTP_PORT-:80"
CURL_OUT=target/headless-curl.txt
rm -f "$CURL_OUT"
( for _ in $(seq 1 80); do      # retry until httpd is up (~1 s apart)
      sleep 1
      curl -s -m 5 -o "$CURL_OUT" "http://127.0.0.1:$HTTP_PORT/" 2>/dev/null && [ -s "$CURL_OUT" ] && break
  done ) &
CURL_PID=$!

LOG=target/headless-serial.log
set +e
timeout 90 qemu-system-x86_64 -smp 4 \
    -drive format=raw,file="$IMG" \
    -drive file=target/disk.img,format=raw,if=virtio,snapshot=on \
    -drive "$EXT2_DRIVE" \
    -drive "$EXFAT_DRIVE" \
    -drive "$BTRFS_DRIVE" \
    -netdev "$NETDEV" -device virtio-net-pci,netdev=net0 \
    -device isa-debug-exit,iobase=0xf4,iosize=0x04 \
    -serial stdio -display none -no-reboot | tee "$LOG"
code=${PIPESTATUS[0]}
kill $CURL_PID 2>/dev/null; wait $CURL_PID 2>/dev/null
set -e

# Lines the shell self-test must have printed (only for a plain boot).
if [ -z "${1:-}" ] && [ "$code" = 33 ]; then
    expect=(
        "Rustbelt 0.12.0 (stage 12: networking) x86_64" # ver
        "[smp] 4 CPUs online"                          # smp.rs started 3 more cores
        "B:  /mnt/disk0   fat32"                       # drives
        "C:  /mnt/disk1   ext2"
        "D:  /mnt/disk2   exfat"
        "E:  /mnt/disk3   btrfs"
        "B:/ $ ls"                                     # cd B: changed the prompt
        "readme.txt  scripts/"                         # ls in columns
        "In the Rustbelt shell this disk is drive B:"  # type B:/readme.txt
        "second line"                                  # >> and <
        "       125  todo.txt"                         # cp + ls -l
        '[trace 18] open("/mnt/disk0/notes/todo.txt", 0o0) = 3'   # run --trace
        "sleep started in the background"              # start
        "[done] pid 19 (start sleep 1), exit code 0"   # wait
        "C:/ $ ls"                                     # the ext2 disk
        "Hello from the ext2 disk!"                    # cat through a symlink
        "big.txt line 12345"                           # double-indirect block
        "written by Rustbelt"                          # mkdir + create on ext2
        "-rw-r--r--        125  todo.txt"              # cp B: -> C:, ls -l
        "lrwxrwxrwx             hello-link -> hello.txt"
        "drwxr-xr-x      <DIR>  made-here/"
        "D:/ $ ls"                                     # the exFAT disk
        "This file has a long name with spaces in it." # quoted name, other case
        "MixedCase.TXT: exFAT keeps the case"          # cp D: -> C:, cat
        "hi from exFAT"                                # md + echo >, other case
        "       125  todo.txt"                         # cp B: -> D:, ls -l D:/
        "     <DIR>  New Folder/"
        "    340000  numbers.txt"
        "E:/ $ ls -l"                                  # the btrfs disk
        "lrwxrwxrwx             hello-link -> hello.txt"
        "Hello from btrfs! (an inline extent"          # inline, through a symlink
        "btrfs never overwrites a block in place"      # compressed inline extent
        " 120000  360000 2160000 big.txt"              # 17 zlib extents
        "btrfs line 119999"
        "Stage 10: a read-only btrfs driver"           # inside subvolume projects
        "sh: new.txt: Read-only file system"           # writes refused
        "rm: hello.txt: Read-only file system"
        "mkdir: newdir: Read-only file system"
        "[exit code 139]"                              # run evil
        "(overflow) killed: stack overflow"            # user stack guard zone
        "4 CPUs online (this program ran on CPU"       # sysinfo
        "  CPU3 ["
        "[net] DHCP lease: 10.0.2.15/24, gateway 10.0.2.2, DNS 10.0.2.3" # net/
        "      inet 10.0.2.15/24  gateway 10.0.2.2  DNS 10.0.2.3  (DHCP)" # ifconfig
        "64 bytes from 10.0.2.2: icmp_seq=3"           # ping the slirp gateway
        "3 packets transmitted, 3 received, 0% packet loss"
        "Hello from the host, via QEMU guestfwd!"      # fetch (TCP + HTTP)
        "fetch: HTTP/1.0 200 OK, saved 108894 bytes to /mnt/disk2/seq.txt" # wget -o D:
        "[httpd] served 1 request, bye"                # httpd, reached via hostfwd
        "sh: nosuchprogram: command not found"
        "cd: there is no drive Q:"
        "A:/tmp $ pwd"
    )
    missing=0
    for line in "${expect[@]}"; do
        grep -qF -- "$line" "$LOG" || { echo "[run-headless] MISSING: $line"; missing=1; }
    done
    # Pipe results: `... | grep -i EXT | wc -l` -> 1, `dir bin | wc -l` -> 22,
    # `cat C:/chunks.txt | wc -l` (copied from E:) -> 3,
    # `type D:/COW.TXT | wc -l` (copied from E:) -> 4, and
    # `cat D:/seq.txt | wc -l` (downloaded by wget) -> 20000.
    for n in 1 22 3 4 20000; do
        grep -qx -- "$n" "$LOG" || { echo "[run-headless] MISSING pipe result: $n"; missing=1; }
    done
    # No memory leaks: once the shell and all ~60 programs it ran have
    # exited, exactly as many frames must be in use as before it started.
    if grep -qE '^\[memory\] ([0-9]+) frames in use before the shell, \1 after' "$LOG"; then
        echo "[run-headless] frames in use returned to the baseline"
    else
        echo "[run-headless] MISSING: frames back at the baseline after the shell"; missing=1
    fi
    # The other direction: the host fetched a page from httpd in the guest.
    if grep -qF "Hello from Rustbelt! Rustbelt 0.12.0" "$CURL_OUT" 2>/dev/null; then
        echo "[run-headless] host curl got httpd's page: $(head -1 "$CURL_OUT")"
    else
        echo "[run-headless] MISSING: host curl through hostfwd to httpd"; missing=1
    fi
    [ $missing = 0 ] && echo "[run-headless] all ${#expect[@]}+5 expected shell lines found" || code=35
fi

# isa-debug-exit: QEMU exit status = (value << 1) | 1, so Success(0x10) -> 33.
case $code in
    33)  echo "[run-headless] kernel exited OK"; exit 0 ;;
    35)  echo "[run-headless] kernel reported FAILURE"; exit 1 ;;
    124) echo "[run-headless] timed out (kernel hung?)"; exit 124 ;;
    *)   echo "[run-headless] QEMU exited with $code"; exit "$code" ;;
esac