mirror of
https://gitlab.redox-os.org/redox-os/redox.git
synced 2026-11-05 04:06:38 +08:00
Merge branch 'qemu-virtiofsd' into 'master'
share host directories with Redox from 'make qemu' and add Cloud Hypervisor See merge request redox-os/redox!2383
This commit is contained in:
commit
760d777fd2
2
Makefile
2
Makefile
@ -96,8 +96,10 @@ include mk/repo.mk
|
|||||||
include mk/disk.mk
|
include mk/disk.mk
|
||||||
|
|
||||||
# Emulation recipes
|
# Emulation recipes
|
||||||
|
include mk/virtiofs.mk
|
||||||
include mk/qemu.mk
|
include mk/qemu.mk
|
||||||
include mk/virtualbox.mk
|
include mk/virtualbox.mk
|
||||||
|
include mk/ch.mk
|
||||||
|
|
||||||
# CI
|
# CI
|
||||||
include mk/ci.mk
|
include mk/ci.mk
|
||||||
|
|||||||
162
mk/ch.mk
Normal file
162
mk/ch.mk
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Configuration file for Cloud Hypervisor
|
||||||
|
#
|
||||||
|
# make ch # boot $(DISK), serial console on this terminal
|
||||||
|
# make ch share=... # also share host directories, see mk/virtiofs.mk
|
||||||
|
# make ch net=tap # use the tap device $(CH_TAP) instead of passt
|
||||||
|
# make ch net=no # no network device
|
||||||
|
# make ch-shutdown # stop the VM, from another terminal
|
||||||
|
# make ch-fetch # only download Cloud Hypervisor and its firmware
|
||||||
|
#
|
||||||
|
# x86_64 only. The guest boots through the CLOUDHV.fd UEFI firmware
|
||||||
|
# (https://github.com/cloud-hypervisor/edk2/releases). The disks are virtio-blk.
|
||||||
|
#
|
||||||
|
# Cloud Hypervisor (cloud-hypervisor-static, ch-remote-static) and the firmware
|
||||||
|
# are pinned below. They are downloaded on first use into
|
||||||
|
# build/cloud-hypervisor/$(ARCH) and checked against their sha256. Set CH,
|
||||||
|
# CH_REMOTE or CH_FIRMWARE to use other files, e.g. CH=cloud-hypervisor for
|
||||||
|
# the one in PATH.
|
||||||
|
#
|
||||||
|
# The serial console puts this terminal in raw mode, so Ctrl-C goes to Redox.
|
||||||
|
# Redox cannot power off Cloud Hypervisor by itself yet: use make ch-shutdown.
|
||||||
|
#
|
||||||
|
# The network is passt by default: a user-mode network stack, started by
|
||||||
|
# scripts/passt-run.sh, that answers the guest's DHCP and DNS and carries its
|
||||||
|
# traffic through ordinary host sockets. No tap, root or firewall changes are
|
||||||
|
# needed. The guest gets the host's address, and nothing reaches it from
|
||||||
|
# outside unless forwarded. CH_PASST_FLAGS passes options to passt, e.g.
|
||||||
|
# "-t 8022:22" (host port 8022 to guest port 22) or
|
||||||
|
# "--pcap $XDG_RUNTIME_DIR/redox.pcap" (capture the traffic).
|
||||||
|
#
|
||||||
|
# net=tap uses the tap device $(CH_TAP) instead: a real link that the host
|
||||||
|
# can reach on any port and watch with tcpdump. Creating it needs root:
|
||||||
|
# scripts/ch-tap-setup.sh (firewalld only) creates it and makes the host
|
||||||
|
# the guest's DHCP server and NAT gateway.
|
||||||
|
|
||||||
|
# Pinned releases. To update one, change its version and the sha256 of its
|
||||||
|
# assets, which GitHub shows next to each file on the release page. Every
|
||||||
|
# version is downloaded into its own directory.
|
||||||
|
CH_VERSION=v54.0
|
||||||
|
CH_FIRMWARE_VERSION=ch-97eeb7b09
|
||||||
|
CH_URL=https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/$(CH_VERSION)
|
||||||
|
CH_FIRMWARE_URL=https://github.com/cloud-hypervisor/edk2/releases/download/$(CH_FIRMWARE_VERSION)
|
||||||
|
CH_DIR=build/cloud-hypervisor/$(ARCH)
|
||||||
|
|
||||||
|
# Release assets and their sha256, per ARCH
|
||||||
|
CH_ASSET_x86_64=cloud-hypervisor-static
|
||||||
|
CH_SHA256_x86_64=4f4f98a3d661a40f3e5272627cc895a1b2e525f2f6bedf5f895f9cd21ba5c65b
|
||||||
|
CH_REMOTE_ASSET_x86_64=ch-remote-static
|
||||||
|
CH_REMOTE_SHA256_x86_64=acd5be240d5c73373effbe818908fc03e313ec8486237904bff11d93b71b1c83
|
||||||
|
CH_FIRMWARE_ASSET_x86_64=CLOUDHV.fd
|
||||||
|
CH_FIRMWARE_SHA256_x86_64=dc2fc8f0e43b96712d9fccc52a3a590769606412b3e1bc911d217addd3bef624
|
||||||
|
|
||||||
|
ifneq ($(CH_ASSET_$(ARCH)),)
|
||||||
|
CH_PINNED=$(CH_DIR)/$(CH_VERSION)/$(CH_ASSET_$(ARCH))
|
||||||
|
CH_REMOTE_PINNED=$(CH_DIR)/$(CH_VERSION)/$(CH_REMOTE_ASSET_$(ARCH))
|
||||||
|
CH_FIRMWARE_PINNED=$(CH_DIR)/edk2-$(CH_FIRMWARE_VERSION)/$(CH_FIRMWARE_ASSET_$(ARCH))
|
||||||
|
endif
|
||||||
|
|
||||||
|
CH?=$(CH_PINNED)
|
||||||
|
CH_REMOTE?=$(CH_REMOTE_PINNED)
|
||||||
|
CH_FIRMWARE?=$(CH_FIRMWARE_PINNED)
|
||||||
|
# Only the pinned files in use are downloaded.
|
||||||
|
CH_DOWNLOADS=$(filter $(CH_PINNED) $(CH_REMOTE_PINNED) $(CH_FIRMWARE_PINNED),$(CH) $(CH_REMOTE) $(CH_FIRMWARE))
|
||||||
|
|
||||||
|
CH_SMP?=4
|
||||||
|
CH_MEM?=2048
|
||||||
|
CH_API_SOCK?=$(BUILD)/ch.sock
|
||||||
|
CH_LOG?=$(BUILD)/ch.log
|
||||||
|
CH_TAP?=redox0
|
||||||
|
CH_MAC?=52:54:00:12:34:56
|
||||||
|
# Not in $(BUILD): SELinux limits passt to $XDG_RUNTIME_DIR and /tmp.
|
||||||
|
CH_PASST_SOCK?=$(or $(XDG_RUNTIME_DIR),/tmp)/redox-passt-$(ARCH)-$(CONFIG_NAME).sock
|
||||||
|
CH_PASST_FLAGS?=
|
||||||
|
CHFLAGS?=-v --log-file $(CH_LOG)
|
||||||
|
|
||||||
|
CHFLAGS+=--cpus boot=$(CH_SMP)
|
||||||
|
CHFLAGS+=--firmware $(CH_FIRMWARE)
|
||||||
|
CHFLAGS+=--serial tty --console off
|
||||||
|
CHFLAGS+=--api-socket path=$(CH_API_SOCK)
|
||||||
|
CHFLAGS+= \
|
||||||
|
--disk path=$(DISK),image_type=raw,serial=NVME_SERIAL \
|
||||||
|
--disk path=$(EXTRA_DISK),image_type=raw,serial=NVME_EXTRA
|
||||||
|
|
||||||
|
PASST_RUN=
|
||||||
|
|
||||||
|
ifeq ($(net),tap)
|
||||||
|
# The Redox virtio-net driver only acknowledges VIRTIO_NET_F_MAC at the moment.
|
||||||
|
CHFLAGS+=--net tap=$(CH_TAP),mac=$(CH_MAC),offload_tso=off,offload_ufo=off,offload_csum=off
|
||||||
|
else ifneq ($(net),no)
|
||||||
|
CHFLAGS+=--net vhost_user=true,socket=$(CH_PASST_SOCK),mac=$(CH_MAC)
|
||||||
|
PASST_RUN=scripts/passt-run.sh "$(CH_PASST_SOCK)" $(CH_PASST_FLAGS) --
|
||||||
|
endif
|
||||||
|
|
||||||
|
ch_tap_found=$(shell ip link show "$(CH_TAP)" >/dev/null 2>&1 && echo yes)
|
||||||
|
|
||||||
|
# vhost-user backends (virtiofsd, passt) need shared guest RAM.
|
||||||
|
ifneq ($(strip $(share))$(PASST_RUN),)
|
||||||
|
CHFLAGS+=--memory size=$(CH_MEM)M,shared=on
|
||||||
|
else
|
||||||
|
CHFLAGS+=--memory size=$(CH_MEM)M
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(strip $(share)),)
|
||||||
|
CHFLAGS+=$(foreach i,$(share_idx),--fs tag=$(call vfs_tag,$(i)),socket=$(call vfs_sock,$(i)))
|
||||||
|
endif
|
||||||
|
|
||||||
|
# $(1): CH, CH_REMOTE or CH_FIRMWARE. When it is set to something other than
|
||||||
|
# the pinned download, it must be an existing file or a command in PATH.
|
||||||
|
ch_check_set=$(if $(filter-out $($(1)_PINNED),$($(1))),$(if $(or $(wildcard $($(1))),$(shell command -v $($(1)))),,$(error $(1)=$($(1)) not found. Leave $(1) unset to use the pinned download)))
|
||||||
|
|
||||||
|
.PHONY: ch ch-check ch-deps ch-fetch ch-shutdown
|
||||||
|
|
||||||
|
ch-check:
|
||||||
|
$(if $(filter x86_64,$(ARCH)),,$(error Cloud Hypervisor: only ARCH=x86_64 is supported))
|
||||||
|
$(if $(filter $(HOST_ARCH),$(ARCH)),,$(error Cloud Hypervisor: ARCH=$(ARCH) cannot run on this $(HOST_ARCH) host))
|
||||||
|
$(call ch_check_set,CH)
|
||||||
|
$(call ch_check_set,CH_REMOTE)
|
||||||
|
$(call ch_check_set,CH_FIRMWARE)
|
||||||
|
$(if $(filter tap,$(net)),$(if $(ch_tap_found),,$(error no tap device $(CH_TAP). Create it with scripts/ch-tap-setup.sh or leave out net=tap to use passt)))
|
||||||
|
|
||||||
|
ch-deps: $(DISK) $(EXTRA_DISK) $(CH_DOWNLOADS)
|
||||||
|
|
||||||
|
ch-fetch: ch-check $(CH_DOWNLOADS)
|
||||||
|
|
||||||
|
ch: ch-check ch-deps
|
||||||
|
ifneq ($(wildcard $(MOUNT_DIR)),)
|
||||||
|
@echo "Disk is mounted. Unmounting the disk..."
|
||||||
|
$(MAKE) unmount
|
||||||
|
endif
|
||||||
|
@if $(CH_REMOTE) --api-socket "$(CH_API_SOCK)" ping >/dev/null 2>&1; then \
|
||||||
|
echo "ch: a VM is already running on $(CH_API_SOCK); stop it with make ch-shutdown" >&2; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
# A leftover socket file makes Cloud Hypervisor fail with "Address in use".
|
||||||
|
rm -f "$(CH_API_SOCK)"
|
||||||
|
$(VIRTIOFS_RUN) $(PASST_RUN) $(CH) $(CHFLAGS)
|
||||||
|
|
||||||
|
ch-shutdown: $(filter $(CH_REMOTE_PINNED),$(CH_REMOTE))
|
||||||
|
$(call ch_check_set,CH_REMOTE)
|
||||||
|
# shutdown alone would leave Cloud Hypervisor running.
|
||||||
|
$(CH_REMOTE) --api-socket "$(CH_API_SOCK)" shutdown-vmm
|
||||||
|
|
||||||
|
# Downloads $(1) to $@ if its sha256 is $(2), and sets mode $(3) if given.
|
||||||
|
# It goes through $@.partial, so $@ only ever holds a checked download.
|
||||||
|
define ch_download
|
||||||
|
mkdir -p "$(@D)"
|
||||||
|
curl -fL --retry 3 --retry-delay 1 -o "$@.partial" "$(1)"
|
||||||
|
echo "$(2) $@.partial" | sha256sum --check --quiet - \
|
||||||
|
|| { rm -f "$@.partial"; echo "ch: $(1) does not match the pinned sha256 $(2)" >&2; exit 1; }
|
||||||
|
$(if $(3),chmod $(3) "$@.partial")
|
||||||
|
mv "$@.partial" "$@"
|
||||||
|
endef
|
||||||
|
|
||||||
|
ifneq ($(CH_PINNED),)
|
||||||
|
$(CH_PINNED):
|
||||||
|
$(call ch_download,$(CH_URL)/$(@F),$(CH_SHA256_$(ARCH)),+x)
|
||||||
|
|
||||||
|
$(CH_REMOTE_PINNED):
|
||||||
|
$(call ch_download,$(CH_URL)/$(@F),$(CH_REMOTE_SHA256_$(ARCH)),+x)
|
||||||
|
|
||||||
|
$(CH_FIRMWARE_PINNED):
|
||||||
|
$(call ch_download,$(CH_FIRMWARE_URL)/$(@F),$(CH_FIRMWARE_SHA256_$(ARCH)))
|
||||||
|
endif
|
||||||
18
mk/qemu.mk
18
mk/qemu.mk
@ -189,6 +189,20 @@ else
|
|||||||
QEMUFLAGS+=-machine $(QEMU_MACHINE)
|
QEMUFLAGS+=-machine $(QEMU_MACHINE)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# virtio-fs shares (share=...): the list is parsed in mk/virtiofs.mk.
|
||||||
|
ifneq ($(strip $(share)),)
|
||||||
|
# vhost-user needs guest RAM to be shared with the virtiofsd processes, and
|
||||||
|
# the NUMA memdev must be exactly as large as -m. One backend serves all
|
||||||
|
# shares.
|
||||||
|
ifeq ($(UNAME),Linux)
|
||||||
|
QEMUFLAGS+=-object memory-backend-memfd,id=mem,size=$(QEMU_MEM)M,share=on
|
||||||
|
else
|
||||||
|
QEMUFLAGS+=-object memory-backend-file,id=mem,size=$(QEMU_MEM)M,mem-path=/dev/shm,share=on
|
||||||
|
endif
|
||||||
|
QEMUFLAGS+=-numa node,memdev=mem
|
||||||
|
QEMUFLAGS+=$(foreach i,$(share_idx),-chardev socket,id=vfs$(i),path=$(call vfs_sock,$(i)) -device vhost-user-fs-pci,queue-size=1024,chardev=vfs$(i),tag=$(call vfs_tag,$(i)))
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(audio),no)
|
ifeq ($(audio),no)
|
||||||
# No audio
|
# No audio
|
||||||
else ifeq ($(audio),ac97)
|
else ifeq ($(audio),ac97)
|
||||||
@ -383,9 +397,9 @@ $(BUILD)/qemu_uboot.rom:
|
|||||||
|
|
||||||
qemu: qemu-deps
|
qemu: qemu-deps
|
||||||
ifeq ($(wildcard $(MOUNT_DIR)),)
|
ifeq ($(wildcard $(MOUNT_DIR)),)
|
||||||
$(QEMU) $(QEMUFLAGS)
|
$(VIRTIOFS_RUN) $(QEMU) $(QEMUFLAGS)
|
||||||
else
|
else
|
||||||
@echo "\033[1;38;5;196mDisk is mounted. Unmounting the disk...\033[0m";
|
@echo "\033[1;38;5;196mDisk is mounted. Unmounting the disk...\033[0m";
|
||||||
$(MAKE) unmount
|
$(MAKE) unmount
|
||||||
$(QEMU) $(QEMUFLAGS)
|
$(VIRTIOFS_RUN) $(QEMU) $(QEMUFLAGS)
|
||||||
endif
|
endif
|
||||||
|
|||||||
44
mk/virtiofs.mk
Normal file
44
mk/virtiofs.mk
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Share host directories with the guest through virtio-fs (QEMU and Cloud Hypervisor).
|
||||||
|
# make qemu share=/path/to/dir # one share, tag is "host" by default
|
||||||
|
# make qemu share="tag=/path/to/dir" # one share, tag is specified as "tag"
|
||||||
|
# make qemu share=/path/a share_tag=tag # one share, tag is specified as "tag"
|
||||||
|
# make qemu share="tag_1=/path/a tag_2=/path/b" # several shares: tag=dir ...
|
||||||
|
# make qemu share=redox # this build tree, tag "redox"
|
||||||
|
# make qemu share="redox=/other/path" # ordinary share, tag "redox"
|
||||||
|
# make qemu share=/path/to/dir:ro # read-only, also tag=dir:ro and redox:ro
|
||||||
|
# make ch share=... # the same, on Cloud Hypervisor
|
||||||
|
#
|
||||||
|
# Each share gets its own host-side virtiofsd daemon (started and stopped by
|
||||||
|
# scripts/virtiofs-run.sh), a vhost-user-fs device, and a virtio-fsd scheme
|
||||||
|
# process on Redox. In the guest, a share appears at /scheme/virtio-fsd.<tag>.
|
||||||
|
# The emulator flags for each share are added in mk/qemu.mk and mk/ch.mk.
|
||||||
|
# Shared directories must exist. The guest cannot change anything in a :ro share.
|
||||||
|
#
|
||||||
|
# WARNING: share=redox exposes the whole build tree to the guest, including
|
||||||
|
# the harddrive.img Redox is booted from, .git and .config. Writing to them
|
||||||
|
# from Redox can corrupt the build system. share=redox:ro avoids that.
|
||||||
|
|
||||||
|
share?=
|
||||||
|
share_tag?=host
|
||||||
|
share_sockdir?=$(BUILD)
|
||||||
|
VIRTIOFS_RUN=
|
||||||
|
|
||||||
|
ifneq ($(strip $(share)),)
|
||||||
|
share_norm:=$(foreach s,$(share),$(if $(filter redox redox:ro,$(s)),redox=$(ROOT)$(if $(filter %:ro,$(s)),:ro),$(s)))
|
||||||
|
ifeq ($(words $(share_norm)),1)
|
||||||
|
share_list:=$(if $(findstring =,$(share_norm)),$(share_norm),$(share_tag)=$(share_norm))
|
||||||
|
else
|
||||||
|
share_list:=$(share_norm)
|
||||||
|
share_bare:=$(foreach s,$(share_list),$(if $(findstring =,$(s)),,$(s)))
|
||||||
|
ifneq ($(strip $(share_bare)),)
|
||||||
|
# Keep the next line free of leading tabs: make would read it as a recipe.
|
||||||
|
$(error share: every entry must be tag=dir when sharing several directories (bad: $(share_bare)); directories containing spaces are not supported)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
share_idx:=$(shell seq 1 $(words $(share_list)))
|
||||||
|
vfs_entry=$(word $(1),$(share_list))
|
||||||
|
vfs_tag=$(firstword $(subst =, ,$(call vfs_entry,$(1))))
|
||||||
|
vfs_sock=$(share_sockdir)/virtiofsd-$(1).sock
|
||||||
|
|
||||||
|
VIRTIOFS_RUN=scripts/virtiofs-run.sh $(foreach i,$(share_idx),--share "$(call vfs_sock,$(i))" "$(call vfs_entry,$(i))") --
|
||||||
|
endif
|
||||||
@ -472,6 +472,116 @@ solus()
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# virtiofsd is needed on the host by `make qemu share=<dir>` to share
|
||||||
|
# directories with Redox (scripts/virtiofs-run.sh only looks for it).
|
||||||
|
###############################################################################
|
||||||
|
virtiofsd_find()
|
||||||
|
{
|
||||||
|
local c
|
||||||
|
for c in "${VIRTIOFSD:-}" virtiofsd /usr/libexec/virtiofsd /usr/lib/qemu/virtiofsd \
|
||||||
|
"${XDG_CACHE_HOME:-$HOME/.cache}/virtiofsd-redox/virtiofsd"; do
|
||||||
|
[ -n "$c" ] || continue
|
||||||
|
if [ -f "$c" ] && [ -x "$c" ]; then
|
||||||
|
echo "$c"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v "$c" >/dev/null 2>&1; then
|
||||||
|
command -v "$c"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Download a static x86_64 virtiofsd build from the upstream CI into
|
||||||
|
# ~/.cache/virtiofsd-redox, after asking for confirmation.
|
||||||
|
# Override the source with VIRTIOFSD_URL.
|
||||||
|
#
|
||||||
|
# TODO: this is the CI build of upstream's main branch, so it is neither
|
||||||
|
# pinned nor checked: virtiofsd releases do not ship binaries or checksums
|
||||||
|
# yet. Once they do, move this download into mk/virtiofs.mk, pinned to a
|
||||||
|
# release and checked against its sha256 like Cloud Hypervisor in mk/ch.mk,
|
||||||
|
# and pass it to scripts/virtiofs-run.sh through VIRTIOFSD.
|
||||||
|
###############################################################################
|
||||||
|
virtiofsd_download()
|
||||||
|
{
|
||||||
|
local url cache dest tmp answer
|
||||||
|
url="${VIRTIOFSD_URL:-https://gitlab.com/virtio-fs/virtiofsd/-/jobs/artifacts/main/raw/target/x86_64-unknown-linux-musl/release/virtiofsd?job=publish}"
|
||||||
|
cache="${XDG_CACHE_HOME:-$HOME/.cache}/virtiofsd-redox"
|
||||||
|
dest="$cache/virtiofsd"
|
||||||
|
|
||||||
|
if [ "$(uname -m)" != "x86_64" ]; then
|
||||||
|
echo "No prebuilt virtiofsd is available for $(uname -m)."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "A static virtiofsd build can be downloaded from"
|
||||||
|
echo " gitlab.com/virtio-fs/virtiofsd (CI artifact of the main branch)"
|
||||||
|
echo "to $dest"
|
||||||
|
printf "Download it now? (y/N): "
|
||||||
|
read answer || answer=""
|
||||||
|
if ! echo "$answer" | grep -iq "^y"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$cache"
|
||||||
|
tmp="$(mktemp "$cache/virtiofsd.XXXXXX")"
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -fL --retry 3 --retry-delay 1 -o "$tmp" "$url" || { rm -f "$tmp"; return 1; }
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -O "$tmp" "$url" || { rm -f "$tmp"; return 1; }
|
||||||
|
else
|
||||||
|
echo "Need curl or wget to download virtiofsd."
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "$(head -c 4 "$tmp")" != "$(printf '\177ELF')" ]; then
|
||||||
|
echo "The download is not an ELF binary (see $url)."
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
chmod +x "$tmp"
|
||||||
|
mv -f "$tmp" "$dest"
|
||||||
|
echo "virtiofsd installed at $dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# This function makes sure virtiofsd is available: it looks for an existing
|
||||||
|
# one, then tries the distribution package, then offers a download.
|
||||||
|
# It never fails the bootstrap, virtiofsd is only needed for `share=`.
|
||||||
|
# @params: $1 the package manager to use on Debian-based Linux
|
||||||
|
###############################################################################
|
||||||
|
virtiofsd_setup()
|
||||||
|
{
|
||||||
|
local found
|
||||||
|
echo "Checking for virtiofsd (used by 'make qemu share=<dir>')..."
|
||||||
|
if found="$(virtiofsd_find)"; then
|
||||||
|
echo "virtiofsd found at $found"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "virtiofsd not found."
|
||||||
|
|
||||||
|
if command -v pacman >/dev/null 2>&1; then
|
||||||
|
sudo pacman -S --needed virtiofsd || true
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
sudo dnf install virtiofsd || true
|
||||||
|
elif command -v apt-get >/dev/null 2>&1 && apt-cache show virtiofsd >/dev/null 2>&1; then
|
||||||
|
sudo "$1" install virtiofsd || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if found="$(virtiofsd_find)"; then
|
||||||
|
echo "virtiofsd found at $found"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if virtiofsd_download; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Continuing without virtiofsd. 'make qemu share=<dir>' will not work until"
|
||||||
|
echo "it is installed (or set VIRTIOFSD=/path/to/virtiofsd), re-run this script"
|
||||||
|
echo "with -d to retry."
|
||||||
|
}
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# This function outlines the different options available for bootstrap
|
# This function outlines the different options available for bootstrap
|
||||||
######################################################################
|
######################################################################
|
||||||
@ -659,6 +769,11 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Directory sharing with `make qemu share=<dir>` needs virtiofsd and a Linux host
|
||||||
|
if [ "$emulator" == "qemu" ] && [ "$(uname -s)" == "Linux" ]; then
|
||||||
|
virtiofsd_setup "$defpackman"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$dependenciesonly" = false ]; then
|
if [ "$dependenciesonly" = false ]; then
|
||||||
boot
|
boot
|
||||||
fi
|
fi
|
||||||
|
|||||||
7
recipes/tests/virtio-fsd-selftest/recipe.toml
Normal file
7
recipes/tests/virtio-fsd-selftest/recipe.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[source]
|
||||||
|
# The selftest is a member of the base workspace (base/selftests/virtio-fsd)
|
||||||
|
same_as = "../../core/base"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
template = "cargo"
|
||||||
|
cargopackages = ["virtio-fsd-selftest"]
|
||||||
365
scripts/ch-tap-setup.sh
Executable file
365
scripts/ch-tap-setup.sh
Executable file
@ -0,0 +1,365 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Host setup for `make ch net=tap`: tap redox0, IPv4 forwarding, firewalld NAT
|
||||||
|
# and dnsmasq DHCP/DNS. Needs firewalld and dnsmasq. Re-run after a reboot.
|
||||||
|
# As root without sudo, set TAP_USER=<user>.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Debian does not put the sbin directories in a normal user's PATH.
|
||||||
|
PATH="${PATH}:/usr/local/sbin:/usr/sbin:/sbin"
|
||||||
|
|
||||||
|
TAP=redox0
|
||||||
|
GUEST_MAC=52:54:00:12:34:56
|
||||||
|
TAP_ADDR=192.168.249.1
|
||||||
|
TAP_CIDR="${TAP_ADDR}/24"
|
||||||
|
DHCP_FIRST=192.168.249.10
|
||||||
|
DHCP_LAST=192.168.249.50
|
||||||
|
DHCP_RANGE="${DHCP_FIRST},${DHCP_LAST},255.255.255.0,12h"
|
||||||
|
ZONE=chnet
|
||||||
|
POLICY=chnet-nat
|
||||||
|
SYSCTL_DROPIN=/etc/sysctl.d/90-redox-tap.conf
|
||||||
|
DNSMASQ_PID=/run/redox0-dnsmasq.pid
|
||||||
|
WAIT_TIMEOUT=3 # seconds
|
||||||
|
|
||||||
|
step() {
|
||||||
|
printf '\n==> %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
info() {
|
||||||
|
printf ' %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "error: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
as_root() {
|
||||||
|
if [[ "${EUID}" -eq 0 ]]; then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
sudo "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_until() {
|
||||||
|
local deadline=$((SECONDS + $1))
|
||||||
|
shift
|
||||||
|
until "$@"; do
|
||||||
|
((SECONDS < deadline)) || return 1
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only tun/tap devices have tun_flags. IFF_TAP is 0x0002, IFF_UP is 0x1,
|
||||||
|
# owner -1 means none.
|
||||||
|
tap_exists() {
|
||||||
|
[[ -e "/sys/class/net/${TAP}" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_tap() {
|
||||||
|
[[ -r "/sys/class/net/${TAP}/tun_flags" ]] \
|
||||||
|
&& (($(<"/sys/class/net/${TAP}/tun_flags") & 0x0002))
|
||||||
|
}
|
||||||
|
|
||||||
|
tap_owner() {
|
||||||
|
cat "/sys/class/net/${TAP}/owner"
|
||||||
|
}
|
||||||
|
|
||||||
|
link_is_up() {
|
||||||
|
(($(<"/sys/class/net/${TAP}/flags") & 0x1))
|
||||||
|
}
|
||||||
|
|
||||||
|
tap_addrs() {
|
||||||
|
ip -o -4 addr show dev "${TAP}" | awk '{ print $4 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
has_addr() {
|
||||||
|
tap_addrs | grep -qx "${TAP_CIDR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
forwarding_on() {
|
||||||
|
[[ "$(sysctl -n net.ipv4.ip_forward)" == "1" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
forwarding_persisted_by() {
|
||||||
|
grep -RlsE '^[[:space:]]*net\.ipv4\.ip_forward[[:space:]]*=[[:space:]]*1[[:space:]]*(#.*)?$' \
|
||||||
|
/etc/sysctl.conf /etc/sysctl.d /usr/lib/sysctl.d | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
FW_CHANGED=0
|
||||||
|
|
||||||
|
fw_ensure_exists() {
|
||||||
|
local kind="$1" name="$2" list
|
||||||
|
case "${kind}" in
|
||||||
|
zone) list=--get-zones ;;
|
||||||
|
policy) list=--get-policies ;;
|
||||||
|
esac
|
||||||
|
if firewall-cmd --permanent "${list}" | tr ' ' '\n' | grep -qx "${name}"; then
|
||||||
|
info "${kind} ${name}: exists"
|
||||||
|
else
|
||||||
|
info "${kind} ${name}: creating"
|
||||||
|
as_root firewall-cmd --permanent "--new-${kind}=${name}" >/dev/null
|
||||||
|
FW_CHANGED=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
fw_ensure() {
|
||||||
|
local what="$1" scope="$2" query="$3" add="$4"
|
||||||
|
if firewall-cmd --permanent "${scope}" "${query}" >/dev/null; then
|
||||||
|
info "${what}: already set"
|
||||||
|
else
|
||||||
|
info "${what}: adding"
|
||||||
|
as_root firewall-cmd --permanent "${scope}" "${add}" >/dev/null
|
||||||
|
FW_CHANGED=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# --get-target is permanent-only. The runtime value is the target: line.
|
||||||
|
policy_accepts() {
|
||||||
|
[[ "$(firewall-cmd --policy="${POLICY}" --list-all | awk '$1 == "target:" { print $2 }')" == "ACCEPT" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsmasq_pids() {
|
||||||
|
pgrep -f "(^|/)dnsmasq .*--interface=${TAP}( |$)" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# dnsmasq binds DHCP to 0.0.0.0%redox0:67 but DNS to 192.168.249.1:53.
|
||||||
|
dhcp_listening() {
|
||||||
|
ss -H -uln | awk -v t="${TAP}" '$4 ~ ("%" t ":67$") { f = 1 } END { exit !f }'
|
||||||
|
}
|
||||||
|
|
||||||
|
dns_listening() {
|
||||||
|
ss -H -uln | awk -v a="${TAP_ADDR}" -v t="${TAP}" \
|
||||||
|
'$4 == (a ":53") || $4 == (a "%" t ":53") { f = 1 } END { exit !f }'
|
||||||
|
}
|
||||||
|
|
||||||
|
dnsmasq_answering() {
|
||||||
|
dhcp_listening && dns_listening
|
||||||
|
}
|
||||||
|
|
||||||
|
dhcp_port_free() {
|
||||||
|
! dhcp_listening
|
||||||
|
}
|
||||||
|
|
||||||
|
preflight() {
|
||||||
|
if [[ -z "${TAP_USER:-}" ]]; then
|
||||||
|
if [[ "${EUID}" -eq 0 ]]; then
|
||||||
|
TAP_USER="${SUDO_USER:-}"
|
||||||
|
else
|
||||||
|
TAP_USER="$(id -un)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -z "${TAP_USER}" || "${TAP_USER}" == "root" ]]; then
|
||||||
|
die "run this as the user that starts cloud-hypervisor (sudo from that user is fine), or set TAP_USER=<user>."
|
||||||
|
fi
|
||||||
|
TAP_UID="$(id -u "${TAP_USER}")" || die "no such user: ${TAP_USER}"
|
||||||
|
|
||||||
|
if ! command -v firewall-cmd >/dev/null 2>&1 || ! firewall-cmd --state >/dev/null 2>&1; then
|
||||||
|
die "firewalld is not running. This script only supports firewalld; without it, use plain 'make ch', whose passt network needs no tap."
|
||||||
|
fi
|
||||||
|
local cmd
|
||||||
|
for cmd in ip sysctl dnsmasq ss pgrep; do
|
||||||
|
command -v "${cmd}" >/dev/null 2>&1 || die "missing command: ${cmd}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Setting up ${TAP} for 'make ch net=tap': host ${TAP_CIDR}, guest DHCP pool ${DHCP_FIRST}-${DHCP_LAST}."
|
||||||
|
if [[ "${EUID}" -ne 0 ]]; then
|
||||||
|
echo "Privileged steps run through sudo, which may ask you to authenticate."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_tap() {
|
||||||
|
step "Tap device ${TAP}"
|
||||||
|
TAP_IS_NEW=0
|
||||||
|
if tap_exists; then
|
||||||
|
is_tap || die "${TAP} exists but is not a tap device. Remove or rename it first."
|
||||||
|
local owner
|
||||||
|
owner="$(tap_owner)"
|
||||||
|
if [[ "${owner}" == "${TAP_UID}" ]]; then
|
||||||
|
info "exists and is owned by ${TAP_USER} (uid ${TAP_UID}), keeping it"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "${owner}" == "-1" ]]; then
|
||||||
|
info "exists but has no owner, not ${TAP_USER} (uid ${TAP_UID}): deleting it"
|
||||||
|
else
|
||||||
|
info "exists but is owned by uid ${owner}, not ${TAP_USER} (uid ${TAP_UID}): deleting it"
|
||||||
|
fi
|
||||||
|
as_root ip link del "${TAP}"
|
||||||
|
else
|
||||||
|
info "does not exist"
|
||||||
|
fi
|
||||||
|
info "creating it, owned by ${TAP_USER} so cloud-hypervisor can open it without root"
|
||||||
|
as_root ip tuntap add "${TAP}" mode tap user "${TAP_USER}"
|
||||||
|
TAP_IS_NEW=1
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_address() {
|
||||||
|
step "Host address ${TAP_CIDR} on ${TAP} (the guest's gateway and DNS server)"
|
||||||
|
local addr
|
||||||
|
for addr in $(tap_addrs); do
|
||||||
|
[[ "${addr}" == "${TAP_CIDR}" ]] || die "${TAP} already has ${addr}; this script expects ${TAP_CIDR}."
|
||||||
|
done
|
||||||
|
if has_addr; then
|
||||||
|
info "already set"
|
||||||
|
else
|
||||||
|
info "adding it"
|
||||||
|
as_root ip addr add "${TAP_CIDR}" dev "${TAP}"
|
||||||
|
fi
|
||||||
|
if link_is_up; then
|
||||||
|
info "link is already up"
|
||||||
|
else
|
||||||
|
info "setting the link up"
|
||||||
|
as_root ip link set "${TAP}" up
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_forwarding() {
|
||||||
|
step "IPv4 forwarding (routes guest packets out of the host)"
|
||||||
|
if forwarding_on; then
|
||||||
|
info "net.ipv4.ip_forward is already 1"
|
||||||
|
else
|
||||||
|
info "setting net.ipv4.ip_forward=1"
|
||||||
|
as_root sysctl -q -w net.ipv4.ip_forward=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local persist
|
||||||
|
persist="$(forwarding_persisted_by || true)"
|
||||||
|
if [[ -n "${persist}" ]]; then
|
||||||
|
info "kept across reboots by ${persist}"
|
||||||
|
else
|
||||||
|
info "writing ${SYSCTL_DROPIN} so it survives a reboot"
|
||||||
|
printf 'net.ipv4.ip_forward = 1\n' | as_root tee "${SYSCTL_DROPIN}" >/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_firewalld() {
|
||||||
|
step "firewalld zone ${ZONE} (lets the guest reach dnsmasq on the host)"
|
||||||
|
fw_ensure_exists zone "${ZONE}"
|
||||||
|
fw_ensure "interface ${TAP}" --zone="${ZONE}" --query-interface="${TAP}" --add-interface="${TAP}"
|
||||||
|
fw_ensure "service dhcp (UDP 67)" --zone="${ZONE}" --query-service=dhcp --add-service=dhcp
|
||||||
|
fw_ensure "service dns (port 53)" --zone="${ZONE}" --query-service=dns --add-service=dns
|
||||||
|
|
||||||
|
step "firewalld policy ${POLICY} (forwards and masquerades guest traffic to the outside)"
|
||||||
|
fw_ensure_exists policy "${POLICY}"
|
||||||
|
fw_ensure "ingress zone ${ZONE}" --policy="${POLICY}" --query-ingress-zone="${ZONE}" --add-ingress-zone="${ZONE}"
|
||||||
|
fw_ensure "egress zone ANY" --policy="${POLICY}" --query-egress-zone=ANY --add-egress-zone=ANY
|
||||||
|
if [[ "$(firewall-cmd --permanent --policy="${POLICY}" --get-target)" == "ACCEPT" ]]; then
|
||||||
|
info "target ACCEPT: already set"
|
||||||
|
else
|
||||||
|
info "target ACCEPT: setting"
|
||||||
|
as_root firewall-cmd --permanent --policy="${POLICY}" --set-target=ACCEPT >/dev/null
|
||||||
|
FW_CHANGED=1
|
||||||
|
fi
|
||||||
|
fw_ensure "masquerade" --policy="${POLICY}" --query-masquerade --add-masquerade
|
||||||
|
|
||||||
|
step "Applying firewalld settings"
|
||||||
|
if [[ "${FW_CHANGED}" -eq 1 ]]; then
|
||||||
|
info "reloading firewalld to apply the permanent changes"
|
||||||
|
as_root firewall-cmd --reload >/dev/null
|
||||||
|
else
|
||||||
|
info "nothing changed, no reload needed"
|
||||||
|
fi
|
||||||
|
# firewalld does not always attach an interface that appeared after it
|
||||||
|
# started.
|
||||||
|
if firewall-cmd --zone="${ZONE}" --query-interface="${TAP}" >/dev/null; then
|
||||||
|
info "${TAP} is in zone ${ZONE} at runtime"
|
||||||
|
else
|
||||||
|
info "attaching ${TAP} to zone ${ZONE} at runtime (firewalld missed the new interface)"
|
||||||
|
as_root firewall-cmd --zone="${ZONE}" --add-interface="${TAP}" >/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# dnsmasq --bind-interfaces keeps the old interface index, so a new tap needs
|
||||||
|
# a new dnsmasq.
|
||||||
|
setup_dnsmasq() {
|
||||||
|
step "dnsmasq on ${TAP} (DHCP ${DHCP_FIRST}-${DHCP_LAST}, gateway and DNS ${TAP_ADDR})"
|
||||||
|
local pids pid
|
||||||
|
mapfile -t pids < <(dnsmasq_pids)
|
||||||
|
|
||||||
|
if [[ "${TAP_IS_NEW}" -eq 0 ]] && dnsmasq_answering; then
|
||||||
|
info "already answering DHCP and DNS (pid ${pids[*]:-unknown}), keeping it"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if [[ "${TAP_IS_NEW}" -eq 0 ]]; then
|
||||||
|
info "dnsmasq is not answering both DHCP and DNS on ${TAP}"
|
||||||
|
elif [[ ${#pids[@]} -gt 0 ]]; then
|
||||||
|
info "${TAP} was recreated, so dnsmasq (pid ${pids[*]}) is bound to the old interface"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for pid in "${pids[@]}"; do
|
||||||
|
info "stopping dnsmasq pid ${pid}"
|
||||||
|
as_root kill "${pid}" || true
|
||||||
|
done
|
||||||
|
if ! wait_until "${WAIT_TIMEOUT}" dhcp_port_free; then
|
||||||
|
ss -uln | grep "${TAP}" >&2 || true
|
||||||
|
die "UDP 67 on ${TAP} is still in use."
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "starting dnsmasq (pid file ${DNSMASQ_PID})"
|
||||||
|
# --conf-file=/dev/null: /etc/dnsmasq.conf must not add other interfaces.
|
||||||
|
as_root dnsmasq \
|
||||||
|
--conf-file=/dev/null \
|
||||||
|
--interface="${TAP}" \
|
||||||
|
--bind-interfaces \
|
||||||
|
--except-interface=lo \
|
||||||
|
--pid-file="${DNSMASQ_PID}" \
|
||||||
|
--dhcp-range="${DHCP_RANGE}" \
|
||||||
|
--dhcp-option=3,"${TAP_ADDR}" \
|
||||||
|
--dhcp-option=6,"${TAP_ADDR}"
|
||||||
|
wait_until "${WAIT_TIMEOUT}" dnsmasq_answering || true
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK_FAILED=0
|
||||||
|
|
||||||
|
check() {
|
||||||
|
local what="$1"
|
||||||
|
shift
|
||||||
|
if "$@" >/dev/null 2>&1; then
|
||||||
|
info "ok ${what}"
|
||||||
|
else
|
||||||
|
info "FAILED ${what}"
|
||||||
|
CHECK_FAILED=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_checks() {
|
||||||
|
step "Checking"
|
||||||
|
check "${TAP} has ${TAP_CIDR}" has_addr
|
||||||
|
check "net.ipv4.ip_forward is 1" forwarding_on
|
||||||
|
check "${TAP} is in zone ${ZONE}" firewall-cmd --zone="${ZONE}" --query-interface="${TAP}"
|
||||||
|
check "zone ${ZONE} allows dhcp" firewall-cmd --zone="${ZONE}" --query-service=dhcp
|
||||||
|
check "zone ${ZONE} allows dns" firewall-cmd --zone="${ZONE}" --query-service=dns
|
||||||
|
check "policy ${POLICY} target is ACCEPT" policy_accepts
|
||||||
|
check "policy ${POLICY} masquerades" firewall-cmd --policy="${POLICY}" --query-masquerade
|
||||||
|
check "dnsmasq answers DHCP on ${TAP} (UDP 67)" dhcp_listening
|
||||||
|
check "dnsmasq answers DNS on ${TAP_ADDR} (UDP 53)" dns_listening
|
||||||
|
|
||||||
|
if [[ "${CHECK_FAILED}" -ne 0 ]]; then
|
||||||
|
echo >&2
|
||||||
|
echo "Sockets on ${TAP}:" >&2
|
||||||
|
ss -uln | grep -E "%${TAP}:|${TAP_ADDR}:" >&2 || true
|
||||||
|
die "setup is incomplete, see the FAILED lines above."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
echo
|
||||||
|
echo "${TAP} is ready. It shows NO-CARRIER until cloud-hypervisor opens it; that is normal."
|
||||||
|
echo "Start Redox with 'make ch net=tap'. It adds:"
|
||||||
|
echo " --net tap=${TAP},mac=${GUEST_MAC},offload_tso=off,offload_ufo=off,offload_csum=off"
|
||||||
|
echo "Then optionally run 'dhcpd -v' in Redox and watch leases happen on the host with 'journalctl -t dnsmasq-dhcp -f'."
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
preflight
|
||||||
|
setup_tap
|
||||||
|
setup_address
|
||||||
|
setup_forwarding
|
||||||
|
setup_firewalld
|
||||||
|
setup_dnsmasq
|
||||||
|
run_checks
|
||||||
|
print_summary
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
96
scripts/passt-run.sh
Executable file
96
scripts/passt-run.sh
Executable file
@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Runs a command next to a passt vhost-user network backend, for `make ch`.
|
||||||
|
# passt-run.sh <socket>.sock [passt options...] -- <command...>
|
||||||
|
# Under SELinux, passt may only use sockets in $XDG_RUNTIME_DIR or /tmp.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STARTUP_TIMEOUT=5 # seconds
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "passt: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "usage: $0 <socket>.sock [passt options...] -- <command...>" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $# -ge 1 ]] || usage
|
||||||
|
SOCK="$1"
|
||||||
|
shift
|
||||||
|
PASST_ARGS=()
|
||||||
|
while [[ $# -gt 0 && "$1" != "--" ]]; do
|
||||||
|
PASST_ARGS+=("$1")
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
[[ $# -ge 2 ]] || usage
|
||||||
|
shift # the "--"
|
||||||
|
|
||||||
|
[[ "${SOCK}" == *.sock ]] || die "socket '${SOCK}' must end in .sock"
|
||||||
|
[[ ${#SOCK} -lt 108 ]] || die "socket path too long (${#SOCK} bytes): ${SOCK}"
|
||||||
|
LOG="${SOCK%.sock}.log"
|
||||||
|
REPAIR_SOCK="${SOCK}.repair"
|
||||||
|
|
||||||
|
PASST_BIN="${PASST:-passt}"
|
||||||
|
command -v "${PASST_BIN}" >/dev/null 2>&1 \
|
||||||
|
|| die "passt not found. Install the passt package (dnf, apt and pacman all call it passt) or set PASST=/path/to/passt"
|
||||||
|
"${PASST_BIN}" --help 2>&1 | grep -q -- '--vhost-user' \
|
||||||
|
|| die "${PASST_BIN} has no --vhost-user mode, it is too old. Install a passt from 2025 or later."
|
||||||
|
|
||||||
|
socket_in_use() {
|
||||||
|
ss -Hxl | awk -v sock="${SOCK}" '$5 == sock { found = 1 } END { exit !found }'
|
||||||
|
}
|
||||||
|
|
||||||
|
if socket_in_use; then
|
||||||
|
die "${SOCK} is in use by another passt. Stop that VM first, or choose another socket."
|
||||||
|
fi
|
||||||
|
|
||||||
|
PASST_PID=
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
if [[ -n "${PASST_PID}" ]]; then
|
||||||
|
kill "${PASST_PID}" 2>/dev/null || true
|
||||||
|
wait "${PASST_PID}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
rm -f "${SOCK}" "${REPAIR_SOCK}"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'exit 130' INT
|
||||||
|
trap 'exit 143' TERM
|
||||||
|
|
||||||
|
start_passt() {
|
||||||
|
mkdir -p "$(dirname "${SOCK}")"
|
||||||
|
rm -f "${SOCK}" "${REPAIR_SOCK}"
|
||||||
|
"${PASST_BIN}" --vhost-user --foreground --one-off --socket "${SOCK}" "${PASST_ARGS[@]}" \
|
||||||
|
</dev/null >"${LOG}" 2>&1 &
|
||||||
|
PASST_PID=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
start_failed() {
|
||||||
|
if [[ -s "${LOG}" ]]; then
|
||||||
|
# After a bad option passt prints its whole usage text.
|
||||||
|
sed '/^Usage:/,$d' "${LOG}" | tail -n 20 >&2
|
||||||
|
die "$1; see ${LOG}"
|
||||||
|
fi
|
||||||
|
die "$1 and logged nothing. If SELinux is enforcing, put the socket in \$XDG_RUNTIME_DIR or /tmp."
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_socket() {
|
||||||
|
local deadline=$((SECONDS + STARTUP_TIMEOUT))
|
||||||
|
until [[ -S "${SOCK}" ]]; do
|
||||||
|
kill -0 "${PASST_PID}" 2>/dev/null || start_failed "passt exited"
|
||||||
|
((SECONDS < deadline)) || start_failed "passt did not create ${SOCK} within ${STARTUP_TIMEOUT}s"
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
start_passt
|
||||||
|
wait_for_socket
|
||||||
|
echo "passt: user-mode network at ${SOCK} (log: ${LOG})" >&2
|
||||||
|
|
||||||
|
# Not exec'd, so cleanup runs when the command ends.
|
||||||
|
"$@"
|
||||||
150
scripts/virtiofs-run.sh
Executable file
150
scripts/virtiofs-run.sh
Executable file
@ -0,0 +1,150 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Runs a command next to one virtiofsd per shared directory, for
|
||||||
|
# `make qemu share=...` and `make ch share=...`.
|
||||||
|
# virtiofs-run.sh --share <socket>.sock <tag>=<dir>[:ro] [--share ...] -- <command...>
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STARTUP_TIMEOUT=5 # seconds
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "virtiofs: $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "usage: $0 --share <socket>.sock <tag>=<dir>[:ro] [--share ...] -- <command...>" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKS=()
|
||||||
|
TAGS=()
|
||||||
|
DIRS=()
|
||||||
|
OPTS=()
|
||||||
|
LABELS=()
|
||||||
|
while [[ "${1:-}" == --share ]]; do
|
||||||
|
[[ $# -ge 3 ]] || usage
|
||||||
|
[[ "$3" == *=* ]] || die "share '$3': expected <tag>=<dir>"
|
||||||
|
entry="$3" opt="" label=""
|
||||||
|
if [[ "${entry}" == *:ro ]]; then
|
||||||
|
entry="${entry%:ro}" opt=--readonly label=" (read-only)"
|
||||||
|
fi
|
||||||
|
OPTS+=("${opt}")
|
||||||
|
LABELS+=("${label}")
|
||||||
|
SOCKS+=("$2")
|
||||||
|
TAGS+=("${entry%%=*}")
|
||||||
|
DIRS+=("${entry#*=}")
|
||||||
|
shift 3
|
||||||
|
done
|
||||||
|
[[ ${#SOCKS[@]} -gt 0 && "${1:-}" == -- && $# -ge 2 ]] || usage
|
||||||
|
shift # the "--"
|
||||||
|
|
||||||
|
# virtiofsd holds a lock on <socket>.pid for as long as it runs.
|
||||||
|
share_in_use() {
|
||||||
|
[[ -e "$1.pid" ]] && ! flock -n "$1.pid" true
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -A SEEN_TAGS=()
|
||||||
|
validate_share() {
|
||||||
|
local sock="${SOCKS[$1]}" tag="${TAGS[$1]}" dir="${DIRS[$1]}"
|
||||||
|
[[ -n "${tag}" ]] || die "share '=${dir}': empty tag"
|
||||||
|
[[ "${tag}" =~ ^[A-Za-z0-9._-]+$ ]] \
|
||||||
|
|| die "tag '${tag}': only letters, digits, '.', '_' and '-' are allowed (is '${tag}=${dir}' a directory with an '=' in it? use tag=dir)"
|
||||||
|
[[ ${#tag} -le 36 ]] || die "tag '${tag}': longer than 36 bytes"
|
||||||
|
[[ -z "${SEEN_TAGS[${tag}]:-}" ]] || die "tag '${tag}' is used twice"
|
||||||
|
SEEN_TAGS[${tag}]=1
|
||||||
|
[[ -n "${dir}" ]] || die "share '${tag}': empty directory"
|
||||||
|
[[ "${dir}" != *[[:space:]]* ]] \
|
||||||
|
|| die "share '${tag}': directories containing whitespace are not supported ('${dir}')"
|
||||||
|
[[ -d "${dir}" ]] || die "share '${tag}': ${dir} is not a directory"
|
||||||
|
[[ "${sock}" == *.sock ]] || die "socket '${sock}' must end in .sock"
|
||||||
|
[[ ${#sock} -lt 108 ]] \
|
||||||
|
|| die "socket path too long (${#sock} bytes): ${sock}; use a shorter share_sockdir"
|
||||||
|
if share_in_use "${sock}"; then
|
||||||
|
die "${sock} is in use by another virtiofsd. Stop that VM first, or use another share_sockdir."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in "${!SOCKS[@]}"; do
|
||||||
|
validate_share "${i}"
|
||||||
|
done
|
||||||
|
|
||||||
|
find_virtiofsd() {
|
||||||
|
local c
|
||||||
|
for c in "${VIRTIOFSD:-}" virtiofsd /usr/libexec/virtiofsd /usr/lib/qemu/virtiofsd \
|
||||||
|
"${XDG_CACHE_HOME:-${HOME:-/tmp}/.cache}/virtiofsd-redox/virtiofsd"; do
|
||||||
|
[[ -n "${c}" ]] && command -v "${c}" && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
VIRTIOFSD_BIN="$(find_virtiofsd)" \
|
||||||
|
|| die "virtiofsd not found. Install it (e.g. 'dnf install virtiofsd' or 'pacman -S virtiofsd'), run ./podman_bootstrap.sh -d, or set VIRTIOFSD=/path/to/virtiofsd"
|
||||||
|
|
||||||
|
PIDS=()
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
trap - EXIT INT TERM
|
||||||
|
if [[ ${#PIDS[@]} -gt 0 ]]; then
|
||||||
|
kill "${PIDS[@]}" 2>/dev/null || true
|
||||||
|
wait "${PIDS[@]}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
local sock
|
||||||
|
for sock in "${SOCKS[@]}"; do
|
||||||
|
rm -f "${sock}" "${sock}.pid"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'exit 130' INT
|
||||||
|
trap 'exit 143' TERM
|
||||||
|
|
||||||
|
declare -A SHARED_DIRS=()
|
||||||
|
start_virtiofsd() {
|
||||||
|
local sock="${SOCKS[$1]}" dir
|
||||||
|
mkdir -p "$(dirname "${sock}")"
|
||||||
|
dir="$(cd "${DIRS[$1]}" && pwd)"
|
||||||
|
if [[ -n "${SHARED_DIRS[${dir}]:-}" ]]; then
|
||||||
|
echo "virtiofs: warning: ${dir} is shared more than once; the instances do not share caches" >&2
|
||||||
|
fi
|
||||||
|
SHARED_DIRS[${dir}]=1
|
||||||
|
|
||||||
|
"${VIRTIOFSD_BIN}" \
|
||||||
|
--socket-path="${sock}" \
|
||||||
|
--shared-dir "${dir}" \
|
||||||
|
--sandbox none \
|
||||||
|
--log-level warn \
|
||||||
|
--translate-uid="map:0:$(id -u):1" \
|
||||||
|
--translate-gid="map:0:$(id -g):1" \
|
||||||
|
${OPTS[$1]} \
|
||||||
|
</dev/null >"${sock%.sock}.log" 2>&1 &
|
||||||
|
PIDS+=("$!")
|
||||||
|
}
|
||||||
|
|
||||||
|
start_failed() {
|
||||||
|
local log="${SOCKS[$1]%.sock}.log"
|
||||||
|
tail -n 20 "${log}" >&2 || true
|
||||||
|
die "virtiofsd for '${TAGS[$1]}' $2; see ${log}"
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_sockets() {
|
||||||
|
local deadline=$((SECONDS + STARTUP_TIMEOUT)) i
|
||||||
|
for i in "${!SOCKS[@]}"; do
|
||||||
|
until [[ -S "${SOCKS[$i]}" ]]; do
|
||||||
|
kill -0 "${PIDS[$i]}" 2>/dev/null || start_failed "${i}" "exited"
|
||||||
|
((SECONDS < deadline)) || start_failed "${i}" "did not create ${SOCKS[$i]} within ${STARTUP_TIMEOUT}s"
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in "${!SOCKS[@]}"; do
|
||||||
|
start_virtiofsd "${i}"
|
||||||
|
done
|
||||||
|
wait_for_sockets
|
||||||
|
for i in "${!SOCKS[@]}"; do
|
||||||
|
echo "virtiofs: sharing '${TAGS[$i]}' = ${DIRS[$i]}${LABELS[$i]} (log: ${SOCKS[$i]%.sock}.log)" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Not exec'd, so cleanup runs when the command ends.
|
||||||
|
"$@"
|
||||||
Loading…
Reference in New Issue
Block a user