diff --git a/vmbox/bin/vm b/vmbox/bin/vm index 3e127e9..9bb3b3a 100755 --- a/vmbox/bin/vm +++ b/vmbox/bin/vm @@ -22,6 +22,8 @@ Usage: vm [args] new [--rtc ] Create a sandbox (instant; thin overlay) run Run a command in the sandbox, report the verdict ssh [args...] Interactive shell in the sandbox + share Expose a host dir to sandboxes (read-only bind) + unshare Stop exposing it repo [--worktree] Copy a host repo in (clone=HEAD, --worktree=uncommitted) screenshot [out.png] Capture the guest screen (for locker tests) @@ -78,6 +80,8 @@ main() { ssh) source "$VMBOX_LIB_DIR/ssh.sh"; vm_ssh_interactive "$@" ;; screenshot) source "$VMBOX_LIB_DIR/screenshot.sh"; vm_screenshot "$@" ;; repo) source "$VMBOX_LIB_DIR/repo.sh"; cmd_repo "$@" ;; + share) source "$VMBOX_LIB_DIR/repo.sh"; repo_share "$@" ;; + unshare) source "$VMBOX_LIB_DIR/repo.sh"; repo_unshare "$@" ;; list|ls) cmd_list ;; -h|--help|help|'') usage ;; *) die "unknown command '$cmd' (try: vm --help)" ;; diff --git a/vmbox/lib/launch.sh b/vmbox/lib/launch.sh index b1cebef..879306a 100644 --- a/vmbox/lib/launch.sh +++ b/vmbox/lib/launch.sh @@ -33,7 +33,10 @@ launch_vm() { serial_n="$(vm_next_serial "$name")" serial="$(vm_serial "$name" "$serial_n")" qmp="$(vm_qmp_sock "$name")" - # Symlink the repos you want testable into here; nothing else is exposed. + # Repos you want testable go here. Use `vm share ` -- it BIND-MOUNTS + # rather than symlinks, because 9p exports a directory tree and will not + # follow a symlink pointing outside it (the guest would see a dangling + # link). Nothing outside this directory is ever exposed. share="${VMBOX_SHARE:-$VMBOX_HOME/share}" install -d -m 755 "$share" diff --git a/vmbox/lib/repo.sh b/vmbox/lib/repo.sh index 9b89718..503c253 100644 --- a/vmbox/lib/repo.sh +++ b/vmbox/lib/repo.sh @@ -17,6 +17,41 @@ source "$(dirname "${BASH_SOURCE[0]}")/ssh.sh" readonly GUEST_MOUNT="/mnt/hostrepo" +# Expose a host directory to sandboxes. Bind-mount rather than symlink: 9p +# exports a directory TREE and does not follow links out of it, so a symlinked +# repo appears in the guest as a dangling link (measured, not assumed). +# The bind is read-only, so the guest cannot write to the real repo. +repo_share() { + local src="${1:-}" name share + [[ -n "$src" ]] || die "usage: vm share " + [[ -d "$src" ]] || die "no such directory: $src" + src="$(realpath "$src")" + name="$(basename "$src")" + share="${VMBOX_SHARE:-$VMBOX_HOME/share}" + install -d -m 755 "$share/$name" + + if mountpoint -q "$share/$name"; then + ok "$name is already shared" + return 0 + fi + # A read-only bind needs two steps: mount, then remount ro. + sudo mount --bind "$src" "$share/$name" || + die "bind mount failed (needs sudo once per boot)" + sudo mount -o remount,ro,bind "$share/$name" || + warn "could not remount read-only -- the guest mount is still ro via 9p" + ok "shared $src -> sandboxes see it at $GUEST_MOUNT/$name (read-only)" +} + +repo_unshare() { + local name="${1:-}" share + share="${VMBOX_SHARE:-$VMBOX_HOME/share}" + [[ -n "$name" ]] || die "usage: vm unshare " + mountpoint -q "$share/$name" || { ok "$name is not shared"; return 0; } + sudo umount "$share/$name" || die "umount failed" + rmdir "$share/$name" 2>/dev/null || true + ok "unshared $name" +} + # $1 vm name, $2 host repo path, $3 mode (clone|worktree) repo_sync() { local name="$1" src="$2" mode="${3:-clone}" repo_name