diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33d1f98..2d27847 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,7 @@ jobs: - postmark - dbench - diod-regression + - qemu-9p2000.L env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} KERNEL_RELEASE: ${{ inputs.kernel_release || 'kernel-main' }} @@ -33,6 +34,17 @@ jobs: - name: Checkout harness uses: actions/checkout@v4 + - name: Ensure GitHub CLI present (for release download) + run: | + set -euo pipefail + if command -v gh >/dev/null 2>&1; then + gh --version + exit 0 + fi + sudo apt-get update + sudo apt-get install -y --no-install-recommends gh + gh --version + - name: Download published kernel Image run: | set -euo pipefail diff --git a/scripts/v9fs-9p-protocol-tests b/scripts/v9fs-9p-protocol-tests new file mode 100755 index 0000000..482bcec --- /dev/null +++ b/scripts/v9fs-9p-protocol-tests @@ -0,0 +1,195 @@ +#!/usr/bin/env bash +set -euo pipefail + +server="${1:-}" +dialect="${2:-}" + +if [ -z "${server}" ] || [ -z "${dialect}" ]; then + echo "usage: $0 <9p2000|9p2000.u|9p2000.L>" >&2 + exit 2 +fi + +echo "9p-proto: server=${server} dialect=${dialect}" + +# Kernel config probe (preferred source of truth when available). +if [ -r /proc/config.gz ]; then + echo "== kernel config (from /proc/config.gz) ==" + cfg_out="/home/v9fs-test/test/logs/9p-kconfig.txt" + mkdir -p "$(dirname "$cfg_out")" + (zcat /proc/config.gz 2>/dev/null || gzip -dc /proc/config.gz 2>/dev/null || cat /proc/config.gz) \ + | /usr/bin/grep -E '^(CONFIG_(NET_9P|9P|P9|VIRTIO_9P|NET_9P_VIRTIO|NET_9P_DEBUG|NET_9P_RDMA|NET_9P_XEN)|# CONFIG_(NET_9P|NET_9P_VIRTIO|NET_9P_RDMA) is not set)' \ + | sort -u \ + | tee "$cfg_out" || true +else + echo "== kernel config ==" + echo "SKIP: /proc/config.gz not present/readable" +fi + +mnt="/workspaces/tmpdir/9p-mnt" +umount_mnt() { umount -l "$mnt" 2>/dev/null || true; } +trap umount_mnt EXIT +umount_mnt +mkdir -p "$mnt" + +case "$server" in + qemu) + # We are already running inside a chroot that is the virtio-9p mount, + # mounted with the requested dialect by init. Use an on-mount directory. + exp="/workspaces/tmpdir/9p-exp-qemu" + mkdir -p "$exp" + cli="$exp" + ;; + diod) + # Run diod in the guest and mount it via tcp. Backing store is tmpfs so the + # diod tests do not recurse through the QEMU 9p server. + exp="/workspaces/tmpdir/9p-exp-diod" + sockdir="/workspaces/tmpdir" + mkdir -p "$exp" + mount -t tmpfs -o mode=0777 tmpfs "$exp" 2>/dev/null || true + + port="${DIOD_PORT:-45670}" + diod_log="/home/v9fs-test/test/logs/diod-server.log" + + # Ensure loopback is up for trans=tcp mounts. + (ip link set lo up 2>/dev/null || true) + (ip addr add 127.0.0.1/8 dev lo 2>/dev/null || true) + (ifconfig lo 127.0.0.1 up 2>/dev/null || true) + + # Best-effort: load 9p network transport support if modular. + (modprobe 9pnet 2>/dev/null || true) + (modprobe 9pnet_fd 2>/dev/null || true) + (modprobe 9pnet_virtio 2>/dev/null || true) + + (diod --foreground --listen "127.0.0.1:${port}" \ + --config-file=/dev/null --debug=0x1 \ + --runas-uid 0 --no-auth --export "$exp" \ + >/dev/null 2>>"$diod_log") & + diod_pid="$!" + echo "9p-proto: started diod pid=${diod_pid} port=${port} export=${exp}" + + for _ in $(seq 1 50); do + (echo >/dev/tcp/127.0.0.1/"$port") >/dev/null 2>&1 && break + sleep 0.1 + done + + if ! mount -t 9p -o "trans=tcp,version=${dialect},port=${port},uname=root,access=any" 127.0.0.1 "$mnt"; then + echo "9p-proto: mount failed, dmesg tail:" + dmesg | tail -n 200 || true + exit 32 + fi + cli="$mnt" + ;; + *) + echo "9p-proto: unknown server=${server} (expected qemu|diod)" >&2 + exit 2 + ;; +esac + +echo "9p-proto: exp=${exp}" +echo "9p-proto: cli=${cli}" + +rm -rf "$exp"/tcase 2>/dev/null || true +rm -rf "$cli"/tcase 2>/dev/null || true +mkdir -p "$exp/tcase" +mkdir -p "$cli/tcase" + +stat_min() { stat -c "mode=%f owner=%u:%g size=%s links=%h mtime=%Y" "$1"; } + +fail() { echo "FAIL: $*"; exit 1; } +assert_eq() { [ "$1" = "$2" ] || fail "expected '$1' == '$2'"; } + +echo "== create/read/write ==" +echo hello >"$cli/tcase/a" +assert_eq "$(cat "$exp/tcase/a")" "hello" +dd if=/dev/zero of="$cli/tcase/b" bs=4096 count=32 conv=fsync status=noxfer +cmp "$cli/tcase/b" "$exp/tcase/b" + +echo "== rename within dir + across dirs ==" +mv "$cli/tcase/a" "$cli/tcase/a2" +mkdir -p "$cli/tcase/dir2" +mv "$cli/tcase/a2" "$cli/tcase/dir2/a" +test -f "$exp/tcase/dir2/a" + +echo "== rename over existing (atomic replace) ==" +echo old >"$cli/tcase/old" +echo new >"$cli/tcase/new" +mv -f "$cli/tcase/new" "$cli/tcase/old" +assert_eq "$(cat "$exp/tcase/old")" "new" +test ! -e "$exp/tcase/new" + +echo "== symlink + readlink ==" +ln -s "dir2/a" "$cli/tcase/syma" +assert_eq "$(readlink "$exp/tcase/syma")" "dir2/a" + +echo "== hardlink ==" +ln "$cli/tcase/dir2/a" "$cli/tcase/a_hard" +assert_eq "$(stat -c %h "$exp/tcase/dir2/a")" "$(stat -c %h "$exp/tcase/a_hard")" + +echo "== truncate and sparse writes ==" +# Write a single byte at a large offset. This should expand apparent size without +# requiring the backend to report real allocation semantics. +dd if=/dev/zero of="$cli/tcase/sparse" bs=1 count=1 seek=$((1024*1024)) conv=notrunc status=noxfer +sz_cli="$(stat -c %s "$cli/tcase/sparse")" +sz_exp="$(stat -c %s "$exp/tcase/sparse")" +test "$sz_cli" -ge $((1024*1024 + 1)) || fail "expected sparse size >= 1MiB+1 on client view, got $sz_cli (exp=$sz_exp)" +: >"$cli/tcase/sparse" +assert_eq "$(stat -c %s "$exp/tcase/sparse")" "0" + +echo "== unlink while open ==" +echo hold >"$cli/tcase/open_unlink" +( + exec 3<"$cli/tcase/open_unlink" + rm -f "$cli/tcase/open_unlink" + cat <&3 >/dev/null + exec 3<&- +) +test ! -e "$exp/tcase/open_unlink" + +echo "== long filename and deep pathwalk ==" +longname="$(printf 'a%.0s' $(seq 1 200))" +touch "$cli/tcase/$longname" +test -f "$exp/tcase/$longname" +mkdir -p "$cli/tcase/deep/1/2/3/4/5/6/7/8/9" +echo deep >"$cli/tcase/deep/1/2/3/4/5/6/7/8/9/file" +assert_eq "$(cat "$exp/tcase/deep/1/2/3/4/5/6/7/8/9/file")" "deep" + +echo "== chmod propagation ==" +chmod 640 "$cli/tcase/dir2/a" +assert_eq "$(stat -c %a "$exp/tcase/dir2/a")" "640" +chmod 600 "$exp/tcase/dir2/a" +assert_eq "$(stat -c %a "$cli/tcase/dir2/a")" "600" + +echo "== chown/chgrp (best-effort) ==" +if chown 0:0 "$cli/tcase/dir2/a" 2>/dev/null; then + assert_eq "$(stat -c %u:%g "$exp/tcase/dir2/a")" "0:0" +else + echo "SKIP: chown not permitted" +fi + +echo "== utimensat/timestamps (best-effort) ==" +if touch -m -t 202001010101.01 "$cli/tcase/dir2/a" 2>/dev/null; then + mt_cli="$(stat -c %Y "$cli/tcase/dir2/a")" + mt_exp="$(stat -c %Y "$exp/tcase/dir2/a")" + assert_eq "$mt_cli" "$mt_exp" +else + echo "SKIP: touch -t failed" +fi + +echo "== statfs sanity ==" +stat -f "$mnt" >/dev/null 2>&1 || df "$mnt" + +echo "== unlink, rmdir ==" +rm -f "$cli/tcase/b" +test ! -e "$exp/tcase/b" +rmdir "$cli/tcase/deep/1/2/3/4/5/6/7/8/9" 2>/dev/null || true +rm -f "$cli/tcase/deep/1/2/3/4/5/6/7/8/9/file" 2>/dev/null || true +rm -rf "$cli/tcase/deep" 2>/dev/null || true +rm -f "$cli/tcase/$longname" 2>/dev/null || true +rm -f "$cli/tcase/old" "$cli/tcase/sparse" 2>/dev/null || true +rmdir "$cli/tcase/dir2" 2>/dev/null || true +rm -f "$cli/tcase/dir2/a" "$cli/tcase/a_hard" "$cli/tcase/syma" 2>/dev/null || true +rmdir "$cli/tcase/dir2" +rmdir "$cli/tcase" + +echo "PASS: 9p-proto server=${server} dialect=${dialect}" + diff --git a/scripts/v9fs-build-initrd b/scripts/v9fs-build-initrd index 373495c..cd6ab90 100755 --- a/scripts/v9fs-build-initrd +++ b/scripts/v9fs-build-initrd @@ -25,7 +25,7 @@ mount -t proc proc /proc || true mount -t sysfs sysfs /sys || true mount -t devtmpfs devtmpfs /dev || true -# Best-effort: start cpud in background (not used for smoke path). +# Best-effort: start cpud in background (not used for primary test path). if [ -x /bbin/cpud ]; then echo "init: starting cpud in background" /bbin/cpud >/dev/console 2>&1 & @@ -36,7 +36,10 @@ mkdir -p /run /tmp /mnt/9 tests="__V9FS_TESTS__" echo "init: tests=${tests}" -if mount -t 9p -o trans=virtio,version=9p2000.L,cache=loose hostshare /mnt/9; then +bootver="9p2000.L" +echo "init: bootver=${bootver}" + +if mount -t 9p -o "trans=virtio,version=${bootver},cache=loose" hostshare /mnt/9; then echo "init: mounted hostshare at /mnt/9" if [ -x /mnt/9/usr/sbin/chroot ] && [ -x /mnt/9/usr/bin/bash ]; then echo "init: chroot -> /mnt/9, running container bash+ls" @@ -54,6 +57,16 @@ if mount -t 9p -o trans=virtio,version=9p2000.L,cache=loose hostshare /mnt/9; th dbench) /mnt/9/usr/sbin/chroot /mnt/9 /bin/bash -lc 'set -e; mkdir -p /workspaces/tmpdir/dbench; /workspaces/tmp/testbin/dbench/dbench -t 30 -c /workspaces/tmp/testbin/dbench/client.txt -D /workspaces/tmpdir/dbench 16' || rc=$? ;; + qemu-9p2000.L|diod-9p2000.L) + /mnt/9/usr/sbin/chroot /mnt/9 /bin/bash -lc ' + set -euo pipefail + tests="__V9FS_TESTS__" + server="${tests%%-*}" + dialect="${tests#*-}" + mkdir -p /home/v9fs-test/test/logs + /home/v9fs-test/test/scripts/v9fs-9p-protocol-tests "$server" "$dialect" 2>&1 | tee -a /home/v9fs-test/test/logs/9p-proto.log + ' || rc=$? + ;; diod-regression) # Run a subset of diod's kernel-client regression tests (sharness) inside the guest. # Build artifacts are prepared on the host side under /workspaces/tmp/diod-build. diff --git a/scripts/v9fs-prepare-diod-server b/scripts/v9fs-prepare-diod-server new file mode 100755 index 0000000..8ee9aa0 --- /dev/null +++ b/scripts/v9fs-prepare-diod-server @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ensure diod server binary is available inside the chrooted container FS. +# Installs from distro packages (fast) rather than building from source. + +export DEBIAN_FRONTEND=noninteractive +apt-get update -y +apt-get install -y --no-install-recommends diod + +command -v diod >/dev/null 2>&1 || { echo "ERROR: diod not found after install"; exit 2; } +diod --help >/dev/null 2>&1 || true + +echo "Prepared diod server: $(command -v diod)" + diff --git a/scripts/v9fs-run-tests b/scripts/v9fs-run-tests index 2e3b8d2..bce7d80 100755 --- a/scripts/v9fs-run-tests +++ b/scripts/v9fs-run-tests @@ -25,8 +25,13 @@ case "${tests}" in diod-regression) ./scripts/v9fs-prepare-diod-regression ;; + qemu-9p2000.L) + ;; + diod-9p2000.L) + ./scripts/v9fs-prepare-diod-server + ;; *) - echo "ERROR: unknown tests=${tests} (expected smoke|fsx|postmark|dbench|diod-regression)" + echo "ERROR: unknown tests=${tests} (expected smoke|fsx|postmark|dbench|diod-regression|qemu-9p2000.L|diod-9p2000.L)" exit 2 ;; esac