From ff9f3863997b3484a13b9f09ff44cf7b6fcd02be Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 16:37:20 +0000 Subject: [PATCH 01/13] feat: add virtiofs support for Linux guests Automatically use virtiofsd (vhost-user-fs-pci) instead of 9p when virtiofsd is present on the host and the guest OS is Linux. Falls back silently to 9p when virtiofsd is unavailable. Updates README to document the new capability. --- README.md | 1 + quickemu | 73 +++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7697a22..e107a4e 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ required to run the virtual machines. - **Nearly 1000 operating system editions are supported!** - Full SPICE support including host/guest clipboard sharing - VirtIO-webdavd file sharing for Linux and Windows guests +- VirtIO-fs file sharing for Linux guests (*automatically preferred over 9p when `virtiofsd` is installed on the host*) - VirtIO-9p file sharing for Linux and macOS guests - [QEMU Guest Agent support](https://wiki.qemu.org/Features/GuestAgent); provides access diff --git a/quickemu b/quickemu index 9b65359..145c266 100755 --- a/quickemu +++ b/quickemu @@ -1544,15 +1544,16 @@ function configure_file_sharing() { *) echo " - WebDAV: On guest: dav://localhost:9843/";; esac - # 9P + # virtiofs or 9p depending on host capability if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ]; then - echo -n " - 9P: On guest: " - if [ "${guest_os}" == "linux" ]; then - echo "sudo mount -t 9p -o trans=virtio,version=9p2000.L,msize=104857600 ${PUBLIC_TAG} ~/$(basename "${PUBLIC}")" + if [ "${guest_os}" == "linux" ] && [ -n "${VIRTIOFSD}" ]; then + echo " - virtiofs: On guest: sudo mount -t virtiofs ${PUBLIC_TAG} ~/$(basename "${PUBLIC}")" + elif [ "${guest_os}" == "linux" ]; then + echo " - 9P: On guest: sudo mount -t 9p -o trans=virtio,version=9p2000.L,msize=104857600 ${PUBLIC_TAG} ~/$(basename "${PUBLIC}")" elif [ "${guest_os}" == "macos" ]; then # PUBLICSHARE needs to be world writeable for seamless integration with # macOS. Test if it is world writeable, and prompt what to do if not. - echo "sudo mount_9p ${PUBLIC_TAG}" + echo " - 9P: On guest: sudo mount_9p ${PUBLIC_TAG}" if [ "${PUBLIC_PERMS}" != "drwxrwxrwx" ]; then echo " - 9P: On host: chmod 777 ${PUBLIC}" echo " Required for macOS integration 👆" @@ -1616,6 +1617,37 @@ function configure_cpu_pinning() { echo " - CPU Pinning: Bind guest cores to host cores (${GUEST_CPUS} -> ${CPU_PINNING})" } +function start_virtiofsd() { + # Start virtiofsd as a background daemon and record its PID so it can be + # cleaned up when the VM exits. The socket path is placed alongside other + # VM runtime files in VMDIR. + if [ -z "${VIRTIOFSD}" ]; then + return + fi + + VIRTIOFSD_SOCKET="${VMDIR}/${VMNAME}.virtiofsd-sock" + local virtiofsd_args=( + --socket-path="${VIRTIOFSD_SOCKET}" + --shared-dir="${PUBLIC}" + --announce-submounts + ) + + echo "${VIRTIOFSD} ${virtiofsd_args[*]} &" >> "${VMDIR}/${VMNAME}.sh" + ${VIRTIOFSD} "${virtiofsd_args[@]}" >> "${VMDIR}/${VMNAME}.log" 2>&1 & + VIRTIOFSD_PID=$! + sleep 0.25 + + if ! kill -0 "${VIRTIOFSD_PID}" 2>/dev/null; then + echo " - WARNING! virtiofsd failed to start; falling back to 9p." + VIRTIOFSD="" + VIRTIOFSD_SOCKET="" + VIRTIOFSD_PID="" + return + fi + + echo " - virtiofsd: ${VIRTIOFSD_SOCKET} (${VIRTIOFSD_PID})" +} + function vm_boot() { AUDIO_DEV="" BALLOON="-device virtio-balloon" @@ -2053,12 +2085,23 @@ function vm_boot() { fi fi + # File sharing: prefer virtiofs (shared memory, lower latency) over 9p when + # virtiofsd is available; virtiofsd must already be running at this point. # https://wiki.qemu.org/Documentation/9psetup # https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ] && [ -n "${PUBLIC}" ]; then - # shellcheck disable=SC2054 - args+=(-fsdev local,id=fsdev0,path="${PUBLIC}",security_model=mapped-xattr - -device virtio-9p-pci,fsdev=fsdev0,mount_tag="${PUBLIC_TAG}") + if [ -n "${VIRTIOFSD_SOCKET}" ]; then + # virtiofs requires a shared-memory backend; the size mirrors the VM RAM. + # shellcheck disable=SC2054 + args+=(-object "memory-backend-file,id=mem,size=${RAM_VM},mem-path=/dev/shm,share=on" + -numa node,memdev=mem + -chardev "socket,id=char0,path=${VIRTIOFSD_SOCKET}" + -device "vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=${PUBLIC_TAG}") + else + # shellcheck disable=SC2054 + args+=(-fsdev local,id=fsdev0,path="${PUBLIC}",security_model=mapped-xattr + -device virtio-9p-pci,fsdev=fsdev0,mount_tag="${PUBLIC_TAG}") + fi fi if [ -n "${USB_PASSTHROUGH}" ]; then @@ -2469,6 +2512,16 @@ function fileshare_param_check() { PUBLIC_PERMS=$(${STAT} -c "%A" "${PUBLIC}") fi fi + + # Prefer virtiofs over 9p when virtiofsd is available and the guest is Linux. + # virtiofs uses shared memory rather than a transport protocol, giving much + # lower latency and higher throughput than 9p. + if [ -n "${PUBLIC}" ] && [ "${guest_os}" == "linux" ]; then + VIRTIOFSD=$(command -v virtiofsd 2>/dev/null || echo "/usr/lib/qemu/virtiofsd") + if [ ! -x "${VIRTIOFSD}" ]; then + VIRTIOFSD="" + fi + fi } function parse_ports_from_file { @@ -2579,6 +2632,9 @@ MONITOR_CMD="" PUBLIC="" PUBLIC_PERMS="" PUBLIC_TAG="" +VIRTIOFSD="" +VIRTIOFSD_PID="" +VIRTIOFSD_SOCKET="" SHORTCUT_OPTIONS="" SNAPSHOT_ACTION="" SNAPSHOT_TAG="" @@ -2903,6 +2959,7 @@ viewer_param_check fileshare_param_check if [ -z "${VM_PID}" ]; then + start_virtiofsd vm_boot start_viewer # If the VM being started is an uninstalled Windows VM then auto-skip the press-any key prompt. From f695b046d654bb7a1d8ddd2e10da7196d65f7d44 Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:19:37 +0000 Subject: [PATCH 02/13] fix: use standalone virtiofsd only; improve failure diagnostics Drop the fallback to the QEMU-bundled C virtiofsd (/usr/lib/qemu/virtiofsd) as it uses incompatible CLI syntax and requires root privileges. Only the standalone Rust virtiofsd in PATH is supported. Capture virtiofsd stderr to a temp file rather than the shared VM log to avoid false-positive permission-error matches from stale log entries. Move start_virtiofsd() call inside vm_boot() so the Quickemu banner is printed before any virtiofsd warnings. --- quickemu | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/quickemu b/quickemu index 145c266..15901b5 100755 --- a/quickemu +++ b/quickemu @@ -1632,18 +1632,31 @@ function start_virtiofsd() { --announce-submounts ) + # Capture virtiofsd stderr separately so we can inspect it without + # false-matching stale entries from previous runs in the shared VM log. + local virtiofsd_stderr + virtiofsd_stderr=$(mktemp) echo "${VIRTIOFSD} ${virtiofsd_args[*]} &" >> "${VMDIR}/${VMNAME}.sh" - ${VIRTIOFSD} "${virtiofsd_args[@]}" >> "${VMDIR}/${VMNAME}.log" 2>&1 & + ${VIRTIOFSD} "${virtiofsd_args[@]}" >> "${VMDIR}/${VMNAME}.log" 2>"${virtiofsd_stderr}" & VIRTIOFSD_PID=$! sleep 0.25 if ! kill -0 "${VIRTIOFSD_PID}" 2>/dev/null; then - echo " - WARNING! virtiofsd failed to start; falling back to 9p." + if grep -q "Operation not permitted" "${virtiofsd_stderr}" 2>/dev/null; then + echo " - WARNING! virtiofsd failed to start (insufficient permissions); falling back to 9p." + echo " Install the standalone virtiofsd package to enable virtiofs support." + else + echo " - WARNING! virtiofsd failed to start; falling back to 9p." + fi + cat "${virtiofsd_stderr}" >> "${VMDIR}/${VMNAME}.log" + rm -f "${virtiofsd_stderr}" VIRTIOFSD="" VIRTIOFSD_SOCKET="" VIRTIOFSD_PID="" return fi + cat "${virtiofsd_stderr}" >> "${VMDIR}/${VMNAME}.log" + rm -f "${virtiofsd_stderr}" echo " - virtiofsd: ${VIRTIOFSD_SOCKET} (${VIRTIOFSD_PID})" } @@ -1698,6 +1711,7 @@ function vm_boot() { echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}" echo " - Host: ${OS_RELEASE} running ${KERNEL_NAME} ${KERNEL_VER} ${KERNEL_NODE}" + start_virtiofsd # Force to lowercase. boot=${boot,,} @@ -2513,11 +2527,14 @@ function fileshare_param_check() { fi fi - # Prefer virtiofs over 9p when virtiofsd is available and the guest is Linux. - # virtiofs uses shared memory rather than a transport protocol, giving much - # lower latency and higher throughput than 9p. + # Prefer virtiofs over 9p when the standalone virtiofsd is available and the + # guest is Linux. virtiofs uses shared memory rather than a transport protocol, + # giving much lower latency and higher throughput than 9p. + # NOTE: only the standalone virtiofsd (Rust) is supported — the legacy + # QEMU-bundled C daemon (/usr/lib/qemu/virtiofsd) uses incompatible CLI + # syntax and requires root, so it is intentionally ignored here. if [ -n "${PUBLIC}" ] && [ "${guest_os}" == "linux" ]; then - VIRTIOFSD=$(command -v virtiofsd 2>/dev/null || echo "/usr/lib/qemu/virtiofsd") + VIRTIOFSD=$(command -v virtiofsd 2>/dev/null) if [ ! -x "${VIRTIOFSD}" ]; then VIRTIOFSD="" fi @@ -2959,7 +2976,6 @@ viewer_param_check fileshare_param_check if [ -z "${VM_PID}" ]; then - start_virtiofsd vm_boot start_viewer # If the VM being started is an uninstalled Windows VM then auto-skip the press-any key prompt. From 26e4dcf8ee17cbdef36de03476d2066cb0b2028a Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:39:39 +0000 Subject: [PATCH 03/13] fix: remove stale virtiofsd socket before starting daemon --- quickemu | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quickemu b/quickemu index 15901b5..7d3141a 100755 --- a/quickemu +++ b/quickemu @@ -1626,6 +1626,10 @@ function start_virtiofsd() { fi VIRTIOFSD_SOCKET="${VMDIR}/${VMNAME}.virtiofsd-sock" + # Remove any stale socket left by an unclean shutdown. quickemu already + # checks the PID file and refuses to start if the VM is running, so a + # live virtiofsd cannot be behind this socket at this point. + rm -f "${VIRTIOFSD_SOCKET}" local virtiofsd_args=( --socket-path="${VIRTIOFSD_SOCKET}" --shared-dir="${PUBLIC}" From d66d9c3c1bba97f418f12280709a743d0ea08cb8 Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 17:50:14 +0000 Subject: [PATCH 04/13] feat: stop virtiofsd gracefully when VM is killed or fails to start Write virtiofsd PID to a file alongside other VM runtime files. Add stop_virtiofsd() which sends SIGTERM and waits up to 1s for a clean exit before falling back to SIGKILL. Call it from kill_vm() and on QEMU startup failure. --- quickemu | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/quickemu b/quickemu index 7d3141a..27701d4 100755 --- a/quickemu +++ b/quickemu @@ -167,12 +167,14 @@ function kill_vm() { rm -f "${VMDIR}/${VMNAME}.pid" rm -f "${VMDIR}/${VMNAME}.spice" rm -f "${VMDIR}/${VMNAME}.sock" + stop_virtiofsd elif [ -n "${VM_PID}" ]; then if kill -9 "${VM_PID}" > /dev/null 2>&1; then echo " - ${VMNAME} (${VM_PID}) killed." rm -f "${VMDIR}/${VMNAME}.pid" rm -f "${VMDIR}/${VMNAME}.spice" rm -f "${VMDIR}/${VMNAME}.sock" + stop_virtiofsd else echo " - ${VMNAME} (${VM_PID}) was not killed." fi @@ -1662,9 +1664,44 @@ function start_virtiofsd() { cat "${virtiofsd_stderr}" >> "${VMDIR}/${VMNAME}.log" rm -f "${virtiofsd_stderr}" + echo "${VIRTIOFSD_PID}" > "${VMDIR}/${VMNAME}.virtiofsd-pid" echo " - virtiofsd: ${VIRTIOFSD_SOCKET} (${VIRTIOFSD_PID})" } +function stop_virtiofsd() { + local pid_file="${VMDIR}/${VMNAME}.virtiofsd-pid" + local pid="" + + if [ -n "${VIRTIOFSD_PID}" ]; then + pid="${VIRTIOFSD_PID}" + elif [ -r "${pid_file}" ]; then + pid=$(cat "${pid_file}") + fi + + if [ -z "${pid}" ]; then + return + fi + + if kill -0 "${pid}" 2>/dev/null; then + # Ask virtiofsd to shut down gracefully first; it will close the + # vhost-user socket and flush any pending I/O before exiting. + kill -TERM "${pid}" 2>/dev/null + local i + for i in 1 2 3 4 5; do + kill -0 "${pid}" 2>/dev/null || break + sleep 0.2 + done + # Force-kill only if it is still alive after the grace period. + if kill -0 "${pid}" 2>/dev/null; then + kill -KILL "${pid}" 2>/dev/null + fi + fi + + rm -f "${pid_file}" "${VMDIR}/${VMNAME}.virtiofsd-sock" + VIRTIOFSD_PID="" + VIRTIOFSD_SOCKET="" +} + function vm_boot() { AUDIO_DEV="" BALLOON="-device virtio-balloon" @@ -2226,6 +2263,7 @@ function vm_boot() { rm -f "${VMDIR}/${VMNAME}.pid" rm -f "${VMDIR}/${VMNAME}.spice" rm -f "${VMDIR}/${VMNAME}.sock" + stop_virtiofsd echo && cat "${VMDIR}/${VMNAME}.log" exit 1 fi From 058e229d5325cb7cd5f715287a1de0d6766e08aa Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 18:47:18 +0000 Subject: [PATCH 05/13] fix: skip virtiofs during OS installation to prevent installer hangs The shared-memory NUMA backend required for virtiofs can confuse installers. Fall back to 9p when an ISO is present (install mode). --- quickemu | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quickemu b/quickemu index 27701d4..4f72511 100755 --- a/quickemu +++ b/quickemu @@ -2575,7 +2575,9 @@ function fileshare_param_check() { # NOTE: only the standalone virtiofsd (Rust) is supported — the legacy # QEMU-bundled C daemon (/usr/lib/qemu/virtiofsd) uses incompatible CLI # syntax and requires root, so it is intentionally ignored here. - if [ -n "${PUBLIC}" ] && [ "${guest_os}" == "linux" ]; then + # Skip virtiofs during OS installation: the required shared-memory NUMA + # backend can confuse installers and cause hangs. 9p is used instead. + if [ -n "${PUBLIC}" ] && [ "${guest_os}" == "linux" ] && [ -z "${iso}" ]; then VIRTIOFSD=$(command -v virtiofsd 2>/dev/null) if [ ! -x "${VIRTIOFSD}" ]; then VIRTIOFSD="" From 4544d1ff9656af1275814fdbac43811f56eb3c35 Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:11:17 +0000 Subject: [PATCH 06/13] =?UTF-8?q?fix:=20correct=20virtiofsd=20lifecycle=20?= =?UTF-8?q?=E2=80=94=20forking,=20iso=20check=20ordering,=20and=20fallback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit virtiofsd forks after startup; check socket existence rather than parent PID to detect success, and use pgrep to track the daemon child for cleanup. Move start_virtiofsd() call to after configure_storage() so $iso is correctly cleared before the installer check runs. Clear VIRTIOFSD on early return so configure_file_sharing() falls back to 9p instructions when virtiofsd is not started. --- quickemu | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/quickemu b/quickemu index 4f72511..c2b63ef 100755 --- a/quickemu +++ b/quickemu @@ -1627,6 +1627,15 @@ function start_virtiofsd() { return fi + # Skip virtiofs when booting from an ISO (installation). By this point in + # vm_boot(), quickemu has already cleared $iso when the disk is in use, so + # a non-empty $iso reliably means we are booting the installer. The + # shared-memory NUMA backend required for virtiofs can cause installer hangs. + if [ -n "${iso}" ]; then + VIRTIOFSD="" + return + fi + VIRTIOFSD_SOCKET="${VMDIR}/${VMNAME}.virtiofsd-sock" # Remove any stale socket left by an unclean shutdown. quickemu already # checks the PID file and refuses to start if the VM is running, so a @@ -1644,10 +1653,11 @@ function start_virtiofsd() { virtiofsd_stderr=$(mktemp) echo "${VIRTIOFSD} ${virtiofsd_args[*]} &" >> "${VMDIR}/${VMNAME}.sh" ${VIRTIOFSD} "${virtiofsd_args[@]}" >> "${VMDIR}/${VMNAME}.log" 2>"${virtiofsd_stderr}" & - VIRTIOFSD_PID=$! - sleep 0.25 + sleep 0.5 - if ! kill -0 "${VIRTIOFSD_PID}" 2>/dev/null; then + # virtiofsd forks: the shell child we spawned exits once the daemon child + # is running. Check the socket rather than the parent PID to detect success. + if [ ! -S "${VIRTIOFSD_SOCKET}" ]; then if grep -q "Operation not permitted" "${virtiofsd_stderr}" 2>/dev/null; then echo " - WARNING! virtiofsd failed to start (insufficient permissions); falling back to 9p." echo " Install the standalone virtiofsd package to enable virtiofs support." @@ -1664,6 +1674,8 @@ function start_virtiofsd() { cat "${virtiofsd_stderr}" >> "${VMDIR}/${VMNAME}.log" rm -f "${virtiofsd_stderr}" + # The parent exits after forking the daemon child; find the child by socket. + VIRTIOFSD_PID=$(pgrep -f "virtiofsd.*${VIRTIOFSD_SOCKET}" 2>/dev/null | head -1) echo "${VIRTIOFSD_PID}" > "${VMDIR}/${VMNAME}.virtiofsd-pid" echo " - virtiofsd: ${VIRTIOFSD_SOCKET} (${VIRTIOFSD_PID})" } @@ -1752,7 +1764,6 @@ function vm_boot() { echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}" echo " - Host: ${OS_RELEASE} running ${KERNEL_NAME} ${KERNEL_VER} ${KERNEL_NODE}" - start_virtiofsd # Force to lowercase. boot=${boot,,} @@ -1769,6 +1780,7 @@ function vm_boot() { configure_bios configure_os_quirks configure_storage + start_virtiofsd configure_display configure_audio configure_ports @@ -2575,9 +2587,7 @@ function fileshare_param_check() { # NOTE: only the standalone virtiofsd (Rust) is supported — the legacy # QEMU-bundled C daemon (/usr/lib/qemu/virtiofsd) uses incompatible CLI # syntax and requires root, so it is intentionally ignored here. - # Skip virtiofs during OS installation: the required shared-memory NUMA - # backend can confuse installers and cause hangs. 9p is used instead. - if [ -n "${PUBLIC}" ] && [ "${guest_os}" == "linux" ] && [ -z "${iso}" ]; then + if [ -n "${PUBLIC}" ] && [ "${guest_os}" == "linux" ]; then VIRTIOFSD=$(command -v virtiofsd 2>/dev/null) if [ ! -x "${VIRTIOFSD}" ]; then VIRTIOFSD="" From a9a6871b9f9c6bd347fcc6ce0aa8ba12ca0f07d8 Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:25:59 +0000 Subject: [PATCH 07/13] fix: address code review issues from cubic-dev-ai - README: clarify virtiofs requires a public directory to be configured - stop_virtiofsd: guard against PID reuse by verifying /proc//comm before signalling - start_virtiofsd: replace fixed sleep with poll loop for socket readiness; use fuser (or comm-verified pgrep fallback) for safe PID resolution - vm_boot: check QEMU supports vhost-user-fs-pci before committing to virtiofs; stop virtiofsd and fall back to 9p if unsupported --- README.md | 2 +- quickemu | 69 +++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e107a4e..826ccae 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ required to run the virtual machines. - **Nearly 1000 operating system editions are supported!** - Full SPICE support including host/guest clipboard sharing - VirtIO-webdavd file sharing for Linux and Windows guests -- VirtIO-fs file sharing for Linux guests (*automatically preferred over 9p when `virtiofsd` is installed on the host*) +- VirtIO-fs file sharing for Linux guests (*automatically preferred over 9p when `virtiofsd` is installed on the host and a public directory is configured*) - VirtIO-9p file sharing for Linux and macOS guests - [QEMU Guest Agent support](https://wiki.qemu.org/Features/GuestAgent); provides access diff --git a/quickemu b/quickemu index c2b63ef..136b7e3 100755 --- a/quickemu +++ b/quickemu @@ -1653,10 +1653,15 @@ function start_virtiofsd() { virtiofsd_stderr=$(mktemp) echo "${VIRTIOFSD} ${virtiofsd_args[*]} &" >> "${VMDIR}/${VMNAME}.sh" ${VIRTIOFSD} "${virtiofsd_args[@]}" >> "${VMDIR}/${VMNAME}.log" 2>"${virtiofsd_stderr}" & - sleep 0.5 - # virtiofsd forks: the shell child we spawned exits once the daemon child - # is running. Check the socket rather than the parent PID to detect success. + # virtiofsd forks: the shell child exits once the daemon child is running. + # Poll for the socket rather than using a fixed sleep to avoid a race. + local i + for i in $(seq 1 20); do + [ -S "${VIRTIOFSD_SOCKET}" ] && break + sleep 0.1 + done + if [ ! -S "${VIRTIOFSD_SOCKET}" ]; then if grep -q "Operation not permitted" "${virtiofsd_stderr}" 2>/dev/null; then echo " - WARNING! virtiofsd failed to start (insufficient permissions); falling back to 9p." @@ -1674,14 +1679,25 @@ function start_virtiofsd() { cat "${virtiofsd_stderr}" >> "${VMDIR}/${VMNAME}.log" rm -f "${virtiofsd_stderr}" - # The parent exits after forking the daemon child; find the child by socket. - VIRTIOFSD_PID=$(pgrep -f "virtiofsd.*${VIRTIOFSD_SOCKET}" 2>/dev/null | head -1) + # Resolve the daemon child's PID via the socket to avoid PID reuse issues. + # Prefer fuser (unambiguous socket owner); fall back to pgrep with a comm + # check to confirm the process is actually virtiofsd. + if command -v fuser >/dev/null 2>&1; then + VIRTIOFSD_PID=$(fuser "${VIRTIOFSD_SOCKET}" 2>/dev/null | tr -s ' ' '\n' | grep -m1 '[0-9]') + else + local candidate + candidate=$(pgrep -f "virtiofsd" 2>/dev/null | head -1) + if grep -q 'virtiofsd' "/proc/${candidate}/comm" 2>/dev/null; then + VIRTIOFSD_PID="${candidate}" + fi + fi echo "${VIRTIOFSD_PID}" > "${VMDIR}/${VMNAME}.virtiofsd-pid" echo " - virtiofsd: ${VIRTIOFSD_SOCKET} (${VIRTIOFSD_PID})" } function stop_virtiofsd() { local pid_file="${VMDIR}/${VMNAME}.virtiofsd-pid" + local socket="${VMDIR}/${VMNAME}.virtiofsd-sock" local pid="" if [ -n "${VIRTIOFSD_PID}" ]; then @@ -1690,26 +1706,26 @@ function stop_virtiofsd() { pid=$(cat "${pid_file}") fi - if [ -z "${pid}" ]; then - return - fi - - if kill -0 "${pid}" 2>/dev/null; then - # Ask virtiofsd to shut down gracefully first; it will close the - # vhost-user socket and flush any pending I/O before exiting. - kill -TERM "${pid}" 2>/dev/null - local i - for i in 1 2 3 4 5; do - kill -0 "${pid}" 2>/dev/null || break - sleep 0.2 - done - # Force-kill only if it is still alive after the grace period. - if kill -0 "${pid}" 2>/dev/null; then - kill -KILL "${pid}" 2>/dev/null + if [ -n "${pid}" ] && kill -0 "${pid}" 2>/dev/null; then + # Guard against PID reuse: only signal the process if it is still + # a virtiofsd instance. /proc//comm holds the executable name. + if grep -q 'virtiofsd' "/proc/${pid}/comm" 2>/dev/null; then + # Ask virtiofsd to shut down gracefully first; it will close the + # vhost-user socket and flush any pending I/O before exiting. + kill -TERM "${pid}" 2>/dev/null + local i + for i in 1 2 3 4 5; do + kill -0 "${pid}" 2>/dev/null || break + sleep 0.2 + done + # Force-kill only if it is still alive after the grace period. + if kill -0 "${pid}" 2>/dev/null; then + kill -KILL "${pid}" 2>/dev/null + fi fi fi - rm -f "${pid_file}" "${VMDIR}/${VMNAME}.virtiofsd-sock" + rm -f "${pid_file}" "${socket}" VIRTIOFSD_PID="" VIRTIOFSD_SOCKET="" } @@ -2157,6 +2173,15 @@ function vm_boot() { # https://wiki.qemu.org/Documentation/9psetup # https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ] && [ -n "${PUBLIC}" ]; then + if [ -n "${VIRTIOFSD_SOCKET}" ]; then + # Verify QEMU supports vhost-user-fs-pci before using it; older QEMU + # builds silently lack the device and would abort VM startup. + if ! "${QEMU}" -device vhost-user-fs-pci,help 2>&1 | grep -q "vhost-user-fs-pci"; then + echo " - WARNING! QEMU does not support vhost-user-fs-pci; falling back to 9p." + stop_virtiofsd + VIRTIOFSD_SOCKET="" + fi + fi if [ -n "${VIRTIOFSD_SOCKET}" ]; then # virtiofs requires a shared-memory backend; the size mirrors the VM RAM. # shellcheck disable=SC2054 From 53bf54cdb3df55ce67ae469d13e3e77b03c99b3a Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:38:32 +0000 Subject: [PATCH 08/13] =?UTF-8?q?fix:=20address=20remaining=20cubic=20revi?= =?UTF-8?q?ew=20issues=20=E2=80=94=20pgrep=20scope,=20/proc=20portability,?= =?UTF-8?q?=20QEMU=20device=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Scope pgrep fallback to the VM socket path to avoid matching unrelated virtiofsd instances - Replace /proc//comm checks with portable `ps -p ... -o comm=` in both start and stop - Fix QEMU virtiofs capability check: use `-device help` and match quoted device name to avoid false-positives on error output --- quickemu | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quickemu b/quickemu index 136b7e3..cde805f 100755 --- a/quickemu +++ b/quickemu @@ -1686,8 +1686,8 @@ function start_virtiofsd() { VIRTIOFSD_PID=$(fuser "${VIRTIOFSD_SOCKET}" 2>/dev/null | tr -s ' ' '\n' | grep -m1 '[0-9]') else local candidate - candidate=$(pgrep -f "virtiofsd" 2>/dev/null | head -1) - if grep -q 'virtiofsd' "/proc/${candidate}/comm" 2>/dev/null; then + candidate=$(pgrep -f "virtiofsd.*${VIRTIOFSD_SOCKET}" 2>/dev/null | head -1) + if ps -p "${candidate}" -o comm= 2>/dev/null | grep -q 'virtiofsd'; then VIRTIOFSD_PID="${candidate}" fi fi @@ -1708,8 +1708,8 @@ function stop_virtiofsd() { if [ -n "${pid}" ] && kill -0 "${pid}" 2>/dev/null; then # Guard against PID reuse: only signal the process if it is still - # a virtiofsd instance. /proc//comm holds the executable name. - if grep -q 'virtiofsd' "/proc/${pid}/comm" 2>/dev/null; then + # a virtiofsd instance. + if ps -p "${pid}" -o comm= 2>/dev/null | grep -q 'virtiofsd'; then # Ask virtiofsd to shut down gracefully first; it will close the # vhost-user socket and flush any pending I/O before exiting. kill -TERM "${pid}" 2>/dev/null @@ -2176,7 +2176,7 @@ function vm_boot() { if [ -n "${VIRTIOFSD_SOCKET}" ]; then # Verify QEMU supports vhost-user-fs-pci before using it; older QEMU # builds silently lack the device and would abort VM startup. - if ! "${QEMU}" -device vhost-user-fs-pci,help 2>&1 | grep -q "vhost-user-fs-pci"; then + if ! "${QEMU}" -device help 2>&1 | grep -q '"vhost-user-fs-pci"'; then echo " - WARNING! QEMU does not support vhost-user-fs-pci; falling back to 9p." stop_virtiofsd VIRTIOFSD_SOCKET="" From ccac3a32670132a4019b003c687b2d370068bd11 Mon Sep 17 00:00:00 2001 From: Dino Korah <691011+codemedic@users.noreply.github.com> Date: Sun, 22 Mar 2026 21:47:08 +0000 Subject: [PATCH 09/13] fix: escape socket path regex metacharacters in pgrep fallback --- quickemu | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/quickemu b/quickemu index cde805f..52d4da1 100755 --- a/quickemu +++ b/quickemu @@ -1685,8 +1685,10 @@ function start_virtiofsd() { if command -v fuser >/dev/null 2>&1; then VIRTIOFSD_PID=$(fuser "${VIRTIOFSD_SOCKET}" 2>/dev/null | tr -s ' ' '\n' | grep -m1 '[0-9]') else - local candidate - candidate=$(pgrep -f "virtiofsd.*${VIRTIOFSD_SOCKET}" 2>/dev/null | head -1) + local candidate socket_pat + # Escape regex metacharacters in the socket path before passing to pgrep -f. + socket_pat=$(printf '%s' "${VIRTIOFSD_SOCKET}" | sed 's/[[\.*^$()+?{}|]/\\&/g') + candidate=$(pgrep -f "virtiofsd.*${socket_pat}" 2>/dev/null | head -1) if ps -p "${candidate}" -o comm= 2>/dev/null | grep -q 'virtiofsd'; then VIRTIOFSD_PID="${candidate}" fi From 7a477bc91a55b0e2cb2aa79e5b9d1767a7ea055a Mon Sep 17 00:00:00 2001 From: Matej Cotman Date: Tue, 21 Apr 2026 12:32:43 +0300 Subject: [PATCH 10/13] feat(nix-flake): add virtiofsd for linux --- package.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.nix b/package.nix index 15478ec..660fd16 100644 --- a/package.nix +++ b/package.nix @@ -1,6 +1,5 @@ { lib, - fetchFromGitHub, installShellFiles, makeWrapper, stdenv, @@ -28,6 +27,7 @@ zsync, OVMF ? null, OVMFFull ? null, + virtiofsd, }: let runtimePaths = [ @@ -55,6 +55,7 @@ let OVMFFull usbutils xdg-user-dirs + virtiofsd ]; # Extract version using builtins.split to avoid regex backtracking on large files. # builtins.match with .* patterns on multi-kilobyte files can cause stack overflow. From 6b4f2e603eead167b9a3d195ac8c580c4df09b6c Mon Sep 17 00:00:00 2001 From: Matej Cotman Date: Tue, 21 Apr 2026 22:13:48 +0300 Subject: [PATCH 11/13] fix(test-nix-build): update nixpkgs --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 2f32387..174a085 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1771848320, - "narHash": "sha256-0MAd+0mun3K/Ns8JATeHT1sX28faLII5hVLq0L3BdZU=", + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2fc6539b481e1d2569f25f8799236694180c0993", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", "type": "github" }, "original": { From 092d8d7d8050d5bbef8145af63971da3e7f74d31 Mon Sep 17 00:00:00 2001 From: Matej Cotman Date: Tue, 21 Apr 2026 22:44:28 +0300 Subject: [PATCH 12/13] fix(virtiofsd): writing inside guest --- quickemu | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quickemu b/quickemu index 52d4da1..2d82865 100755 --- a/quickemu +++ b/quickemu @@ -1645,6 +1645,8 @@ function start_virtiofsd() { --socket-path="${VIRTIOFSD_SOCKET}" --shared-dir="${PUBLIC}" --announce-submounts + --translate-uid="squash-guest:0:$(id -u):65536" # any guest UID in 0..65535 becomes current host UID + --translate-gid="squash-guest:0:$(id -g):65536" # any guest GID in 0..65535 becomes current host GID ) # Capture virtiofsd stderr separately so we can inspect it without From 6e1b4d6789d8736f212c205592c7fcf8e71dc456 Mon Sep 17 00:00:00 2001 From: Dino Korah Date: Mon, 1 Jun 2026 17:25:30 +0100 Subject: [PATCH 13/13] Update package.nix Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nix b/package.nix index 660fd16..32f1775 100644 --- a/package.nix +++ b/package.nix @@ -27,7 +27,7 @@ zsync, OVMF ? null, OVMFFull ? null, - virtiofsd, + virtiofsd ? null, }: let runtimePaths = [