summaryrefslogtreecommitdiffstats
path: root/recipes-containers
diff options
context:
space:
mode:
authorBruce Ashfield <bruce.ashfield@gmail.com>2026-02-19 19:07:17 +0000
committerBruce Ashfield <bruce.ashfield@gmail.com>2026-02-26 01:05:01 +0000
commit5c84f660f7f717765b5662fd69c52cacf0ab64d9 (patch)
tree6bf52fd570442bc2885684f832c28e42870af3d8 /recipes-containers
parent9377aede3157a3e7b702dc389c15f27523b673e7 (diff)
downloadmeta-virtualization-5c84f660f7f717765b5662fd69c52cacf0ab64d9.tar.gz
vcontainer: add QEMU hypervisor backend and register in recipes
Add vrunner-backend-qemu.sh implementing the hypervisor interface for QEMU (arch setup, KVM detection, disk/network/9p options, VM lifecycle, QMP control). Register backend scripts in vcontainer-native and vcontainer-tarball recipes so they are available in both build-time and standalone tarball contexts. Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
Diffstat (limited to 'recipes-containers')
-rw-r--r--recipes-containers/vcontainer/files/vrunner-backend-qemu.sh260
-rw-r--r--recipes-containers/vcontainer/vcontainer-native.bb4
-rw-r--r--recipes-containers/vcontainer/vcontainer-tarball.bb4
3 files changed, 267 insertions, 1 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh b/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh
new file mode 100644
index 00000000..87054876
--- /dev/null
+++ b/recipes-containers/vcontainer/files/vrunner-backend-qemu.sh
@@ -0,0 +1,260 @@
1#!/bin/bash
2# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
3#
4# SPDX-License-Identifier: GPL-2.0-only
5#
6# vrunner-backend-qemu.sh
7# QEMU hypervisor backend for vrunner.sh
8#
9# This backend implements the hypervisor interface for QEMU.
10# It is sourced by vrunner.sh when VCONTAINER_HYPERVISOR=qemu.
11#
12# Backend interface functions:
13# hv_setup_arch() - Set arch-specific QEMU command and machine type
14# hv_check_accel() - Detect KVM acceleration
15# hv_build_disk_opts() - Build QEMU disk drive options
16# hv_build_network_opts() - Build QEMU network options
17# hv_build_9p_opts() - Build QEMU virtio-9p options
18# hv_build_daemon_opts() - Build QEMU daemon mode options (serial, QMP)
19# hv_build_vm_cmd() - Assemble final QEMU command line
20# hv_start_vm_background() - Start QEMU in background, capture PID
21# hv_start_vm_foreground() - Start QEMU in foreground (interactive)
22# hv_is_vm_running() - Check if QEMU process is alive
23# hv_wait_vm_exit() - Wait for QEMU to exit
24# hv_stop_vm() - Graceful shutdown via QMP
25# hv_destroy_vm() - Force kill QEMU process
26# hv_get_vm_id() - Return QEMU PID
27# hv_setup_port_forwards() - Port forwards via QEMU hostfwd (built into netdev)
28# hv_cleanup_port_forwards() - No-op for QEMU (forwards die with process)
29# hv_idle_shutdown() - Send QMP quit for idle timeout
30# hv_get_console_device() - Return arch-specific console device name
31
32# ============================================================================
33# Architecture Setup
34# ============================================================================
35
36hv_setup_arch() {
37 case "$TARGET_ARCH" in
38 aarch64)
39 KERNEL_IMAGE="$BLOB_DIR/aarch64/Image"
40 INITRAMFS="$BLOB_DIR/aarch64/initramfs.cpio.gz"
41 ROOTFS_IMG="$BLOB_DIR/aarch64/rootfs.img"
42 HV_CMD="qemu-system-aarch64"
43 HV_MACHINE="-M virt -cpu cortex-a57"
44 HV_CONSOLE="ttyAMA0"
45 ;;
46 x86_64)
47 KERNEL_IMAGE="$BLOB_DIR/x86_64/bzImage"
48 INITRAMFS="$BLOB_DIR/x86_64/initramfs.cpio.gz"
49 ROOTFS_IMG="$BLOB_DIR/x86_64/rootfs.img"
50 HV_CMD="qemu-system-x86_64"
51 HV_MACHINE="-M q35 -cpu Skylake-Client"
52 HV_CONSOLE="ttyS0"
53 ;;
54 *)
55 log "ERROR" "Unsupported architecture: $TARGET_ARCH"
56 exit 1
57 ;;
58 esac
59}
60
61hv_check_accel() {
62 USE_KVM="false"
63 if [ "$DISABLE_KVM" = "true" ]; then
64 log "DEBUG" "KVM disabled by --no-kvm flag"
65 return
66 fi
67
68 HOST_ARCH=$(uname -m)
69 if [ "$HOST_ARCH" = "$TARGET_ARCH" ] || \
70 { [ "$HOST_ARCH" = "x86_64" ] && [ "$TARGET_ARCH" = "x86_64" ]; }; then
71 if [ -w /dev/kvm ]; then
72 USE_KVM="true"
73 case "$TARGET_ARCH" in
74 x86_64) HV_MACHINE="-M q35 -cpu host" ;;
75 aarch64) HV_MACHINE="-M virt -cpu host" ;;
76 esac
77 log "INFO" "KVM acceleration enabled"
78 else
79 log "DEBUG" "KVM not available (no write access to /dev/kvm)"
80 fi
81 fi
82}
83
84hv_find_command() {
85 if ! command -v "$HV_CMD" >/dev/null 2>&1; then
86 for path in \
87 "${STAGING_BINDIR_NATIVE:-}" \
88 "/usr/bin"; do
89 if [ -n "$path" ] && [ -x "$path/$HV_CMD" ]; then
90 HV_CMD="$path/$HV_CMD"
91 break
92 fi
93 done
94 fi
95
96 if ! command -v "$HV_CMD" >/dev/null 2>&1 && [ ! -x "$HV_CMD" ]; then
97 log "ERROR" "QEMU not found: $HV_CMD"
98 exit 1
99 fi
100 log "DEBUG" "Using QEMU: $HV_CMD"
101}
102
103hv_get_console_device() {
104 echo "$HV_CONSOLE"
105}
106
107# ============================================================================
108# VM Configuration Building
109# ============================================================================
110
111hv_build_disk_opts() {
112 # Rootfs (read-only)
113 HV_DISK_OPTS="-drive file=$ROOTFS_IMG,if=virtio,format=raw,readonly=on"
114
115 # Input disk (if any)
116 if [ -n "$DISK_OPTS" ]; then
117 HV_DISK_OPTS="$HV_DISK_OPTS $DISK_OPTS"
118 fi
119
120 # State disk (if any)
121 if [ -n "$STATE_DISK_OPTS" ]; then
122 HV_DISK_OPTS="$HV_DISK_OPTS $STATE_DISK_OPTS"
123 fi
124}
125
126hv_build_network_opts() {
127 HV_NET_OPTS=""
128 if [ "$NETWORK" = "true" ]; then
129 NETDEV_OPTS="user,id=net0"
130
131 # Add port forwards
132 for pf in "${PORT_FORWARDS[@]}"; do
133 HOST_PORT="${pf%%:*}"
134 CONTAINER_PART="${pf#*:}"
135 CONTAINER_PORT="${CONTAINER_PART%%/*}"
136 if [[ "$CONTAINER_PART" == */* ]]; then
137 PROTOCOL="${CONTAINER_PART##*/}"
138 else
139 PROTOCOL="tcp"
140 fi
141 NETDEV_OPTS="$NETDEV_OPTS,hostfwd=$PROTOCOL::$HOST_PORT-:$HOST_PORT"
142 log "INFO" "Port forward: host:$HOST_PORT -> VM:$HOST_PORT (Docker maps to container:$CONTAINER_PORT)"
143 done
144
145 HV_NET_OPTS="-netdev $NETDEV_OPTS -device virtio-net-pci,netdev=net0"
146 else
147 HV_NET_OPTS="-nic none"
148 fi
149}
150
151hv_build_9p_opts() {
152 local share_dir="$1"
153 local share_tag="$2"
154 local extra_opts="${3:-}"
155 HV_OPTS="$HV_OPTS -virtfs local,path=$share_dir,mount_tag=$share_tag,security_model=none${extra_opts:+,$extra_opts},id=$share_tag"
156}
157
158hv_build_daemon_opts() {
159 HV_DAEMON_OPTS=""
160
161 # virtio-serial for command channel
162 HV_DAEMON_OPTS="$HV_DAEMON_OPTS -chardev socket,id=vdkr,path=$DAEMON_SOCKET,server=on,wait=off"
163 HV_DAEMON_OPTS="$HV_DAEMON_OPTS -device virtio-serial-pci"
164 HV_DAEMON_OPTS="$HV_DAEMON_OPTS -device virtserialport,chardev=vdkr,name=vdkr"
165
166 # QMP socket for dynamic control
167 QMP_SOCKET="$DAEMON_SOCKET_DIR/qmp.sock"
168 HV_DAEMON_OPTS="$HV_DAEMON_OPTS -qmp unix:$QMP_SOCKET,server,nowait"
169}
170
171hv_build_vm_cmd() {
172 HV_OPTS="$HV_MACHINE -nographic -smp 2 -m 2048 -no-reboot"
173 if [ "$USE_KVM" = "true" ]; then
174 HV_OPTS="$HV_OPTS -enable-kvm"
175 fi
176 HV_OPTS="$HV_OPTS -kernel $KERNEL_IMAGE"
177 HV_OPTS="$HV_OPTS -initrd $INITRAMFS"
178 HV_OPTS="$HV_OPTS $HV_DISK_OPTS"
179 HV_OPTS="$HV_OPTS $HV_NET_OPTS"
180}
181
182# ============================================================================
183# VM Lifecycle
184# ============================================================================
185
186hv_start_vm_background() {
187 local kernel_append="$1"
188 local log_file="$2"
189 local timeout_val="$3"
190
191 if [ -n "$timeout_val" ]; then
192 timeout $timeout_val $HV_CMD $HV_OPTS -append "$kernel_append" > "$log_file" 2>&1 &
193 else
194 $HV_CMD $HV_OPTS -append "$kernel_append" > "$log_file" 2>&1 &
195 fi
196 HV_VM_PID=$!
197}
198
199hv_start_vm_foreground() {
200 local kernel_append="$1"
201 $HV_CMD $HV_OPTS -append "$kernel_append"
202}
203
204hv_is_vm_running() {
205 [ -n "$HV_VM_PID" ] && [ -d "/proc/$HV_VM_PID" ]
206}
207
208hv_wait_vm_exit() {
209 local timeout="${1:-30}"
210 for i in $(seq 1 "$timeout"); do
211 hv_is_vm_running || return 0
212 sleep 1
213 done
214 return 1
215}
216
217hv_stop_vm() {
218 if [ -n "$HV_VM_PID" ] && kill -0 "$HV_VM_PID" 2>/dev/null; then
219 log "WARN" "QEMU still running, forcing termination..."
220 kill $HV_VM_PID 2>/dev/null || true
221 wait $HV_VM_PID 2>/dev/null || true
222 fi
223}
224
225hv_destroy_vm() {
226 if [ -n "$HV_VM_PID" ]; then
227 kill -9 $HV_VM_PID 2>/dev/null || true
228 wait $HV_VM_PID 2>/dev/null || true
229 fi
230}
231
232hv_get_vm_id() {
233 echo "$HV_VM_PID"
234}
235
236# ============================================================================
237# Port Forwarding (handled by QEMU hostfwd, no separate setup needed)
238# ============================================================================
239
240hv_setup_port_forwards() {
241 # QEMU port forwards are built into the -netdev hostfwd= options
242 # Nothing extra to do at runtime
243 :
244}
245
246hv_cleanup_port_forwards() {
247 # QEMU port forwards die with the process
248 :
249}
250
251# ============================================================================
252# Idle Timeout / QMP Control
253# ============================================================================
254
255hv_idle_shutdown() {
256 if [ -S "$QMP_SOCKET" ]; then
257 echo '{"execute":"qmp_capabilities"}{"execute":"quit"}' | \
258 socat - "UNIX-CONNECT:$QMP_SOCKET" >/dev/null 2>&1 || true
259 fi
260}
diff --git a/recipes-containers/vcontainer/vcontainer-native.bb b/recipes-containers/vcontainer/vcontainer-native.bb
index 1d19ac1b..85aecf97 100644
--- a/recipes-containers/vcontainer/vcontainer-native.bb
+++ b/recipes-containers/vcontainer/vcontainer-native.bb
@@ -29,6 +29,8 @@ DEPENDS = "coreutils-native socat-native"
29 29
30SRC_URI = "\ 30SRC_URI = "\
31 file://vrunner.sh \ 31 file://vrunner.sh \
32 file://vrunner-backend-qemu.sh \
33 file://vrunner-backend-xen.sh \
32 file://vcontainer-common.sh \ 34 file://vcontainer-common.sh \
33" 35"
34 36
@@ -37,6 +39,8 @@ S = "${UNPACKDIR}"
37do_install() { 39do_install() {
38 install -d ${D}${bindir} 40 install -d ${D}${bindir}
39 install -m 0755 ${S}/vrunner.sh ${D}${bindir}/vrunner.sh 41 install -m 0755 ${S}/vrunner.sh ${D}${bindir}/vrunner.sh
42 install -m 0755 ${S}/vrunner-backend-qemu.sh ${D}${bindir}/vrunner-backend-qemu.sh
43 install -m 0755 ${S}/vrunner-backend-xen.sh ${D}${bindir}/vrunner-backend-xen.sh
40 install -m 0644 ${S}/vcontainer-common.sh ${D}${bindir}/vcontainer-common.sh 44 install -m 0644 ${S}/vcontainer-common.sh ${D}${bindir}/vcontainer-common.sh
41} 45}
42 46
diff --git a/recipes-containers/vcontainer/vcontainer-tarball.bb b/recipes-containers/vcontainer/vcontainer-tarball.bb
index 2223bc8f..432bea9a 100644
--- a/recipes-containers/vcontainer/vcontainer-tarball.bb
+++ b/recipes-containers/vcontainer/vcontainer-tarball.bb
@@ -34,6 +34,8 @@ TOOLCHAIN_SHAR_EXT_TMPL = "${THISDIR}/files/toolchain-shar-extract.sh"
34# Declare script sources so BitBake tracks changes and rebuilds when they change 34# Declare script sources so BitBake tracks changes and rebuilds when they change
35SRC_URI = "\ 35SRC_URI = "\
36 file://vrunner.sh \ 36 file://vrunner.sh \
37 file://vrunner-backend-qemu.sh \
38 file://vrunner-backend-xen.sh \
37 file://vcontainer-common.sh \ 39 file://vcontainer-common.sh \
38 file://vdkr.sh \ 40 file://vdkr.sh \
39 file://vpdmn.sh \ 41 file://vpdmn.sh \
@@ -236,7 +238,7 @@ create_sdk_files:append () {
236 FILES_DIR="${THISDIR}/files" 238 FILES_DIR="${THISDIR}/files"
237 239
238 # Copy shared scripts 240 # Copy shared scripts
239 for script in vrunner.sh vcontainer-common.sh; do 241 for script in vrunner.sh vrunner-backend-qemu.sh vrunner-backend-xen.sh vcontainer-common.sh; do
240 if [ -f "${FILES_DIR}/${script}" ]; then 242 if [ -f "${FILES_DIR}/${script}" ]; then
241 cp "${FILES_DIR}/${script}" "${SDK_OUT}/" 243 cp "${FILES_DIR}/${script}" "${SDK_OUT}/"
242 chmod 755 "${SDK_OUT}/${script}" 244 chmod 755 "${SDK_OUT}/${script}"