Skip to content
Open
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
- VirtIO-9p file sharing for Linux and macOS guests
- [QEMU Guest Agent
support](https://wiki.qemu.org/Features/GuestAgent); provides access
Expand Down
133 changes: 125 additions & 8 deletions quickemu
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1544,15 +1546,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 👆"
Expand Down Expand Up @@ -1616,6 +1619,89 @@ 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"
# 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}"
--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>"${virtiofsd_stderr}" &
VIRTIOFSD_PID=$!
sleep 0.25

if ! kill -0 "${VIRTIOFSD_PID}" 2>/dev/null; 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."
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_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
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
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"
Expand Down Expand Up @@ -1666,6 +1752,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,,}
Expand Down Expand Up @@ -2053,12 +2140,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
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
# 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
Expand Down Expand Up @@ -2165,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
Expand Down Expand Up @@ -2469,6 +2568,21 @@ function fileshare_param_check() {
PUBLIC_PERMS=$(${STAT} -c "%A" "${PUBLIC}")
fi
fi

# 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.
# 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
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
VIRTIOFSD=$(command -v virtiofsd 2>/dev/null)
if [ ! -x "${VIRTIOFSD}" ]; then
VIRTIOFSD=""
fi
fi
}

function parse_ports_from_file {
Expand Down Expand Up @@ -2579,6 +2693,9 @@ MONITOR_CMD=""
PUBLIC=""
PUBLIC_PERMS=""
PUBLIC_TAG=""
VIRTIOFSD=""
VIRTIOFSD_PID=""
VIRTIOFSD_SOCKET=""
SHORTCUT_OPTIONS=""
SNAPSHOT_ACTION=""
SNAPSHOT_TAG=""
Expand Down
Loading