summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer/files/vrunner.sh
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-containers/vcontainer/files/vrunner.sh')
-rwxr-xr-xrecipes-containers/vcontainer/files/vrunner.sh433
1 files changed, 151 insertions, 282 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner.sh b/recipes-containers/vcontainer/files/vrunner.sh
index 22e9229a..8ad45afe 100755
--- a/recipes-containers/vcontainer/files/vrunner.sh
+++ b/recipes-containers/vcontainer/files/vrunner.sh
@@ -4,29 +4,29 @@
4# SPDX-License-Identifier: GPL-2.0-only 4# SPDX-License-Identifier: GPL-2.0-only
5# 5#
6# vrunner.sh 6# vrunner.sh
7# Core runner for vdkr/vpdmn: execute container commands in QEMU-emulated environment 7# Core runner for vdkr/vpdmn/vxn: execute container commands in a hypervisor VM
8# 8#
9# This script is runtime-agnostic and supports both Docker and Podman via --runtime. 9# This script is runtime-agnostic and supports both Docker and Podman via --runtime.
10# It is also hypervisor-agnostic via pluggable backends (QEMU, Xen).
10# 11#
11# Boot flow: 12# Boot flow:
12# 1. QEMU loads kernel + tiny initramfs (busybox + preinit) 13# 1. Hypervisor boots kernel + tiny initramfs (busybox + preinit)
13# 2. preinit mounts rootfs.img (/dev/vda) and does switch_root 14# 2. preinit mounts rootfs.img and does switch_root
14# 3. Real /init runs on actual ext4 filesystem 15# 3. Real /init runs on actual filesystem
15# 4. Container runtime starts, executes command, outputs results 16# 4. Container runtime starts, executes command, outputs results
16# 17#
17# This two-stage boot is required because runc needs pivot_root, 18# This two-stage boot is required because runc needs pivot_root,
18# which doesn't work from initramfs (rootfs isn't a mount point). 19# which doesn't work from initramfs (rootfs isn't a mount point).
19# 20#
20# Drive layout: 21# Drive layout (device names vary by hypervisor):
21# /dev/vda = rootfs.img (ro, ext4 with container tools) 22# QEMU: /dev/vda, /dev/vdb, /dev/vdc (virtio-blk)
22# /dev/vdb = input disk (optional, user data) 23# Xen: /dev/xvda, /dev/xvdb, /dev/xvdc (xen-blkfront)
23# /dev/vdc = state disk (optional, persistent container storage)
24# 24#
25# Version: 3.4.0 25# Version: 3.5.0
26 26
27set -e 27set -e
28 28
29VERSION="3.4.0" 29VERSION="3.5.0"
30SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 30SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31 31
32# Runtime selection: docker or podman 32# Runtime selection: docker or podman
@@ -42,19 +42,19 @@ VERBOSE="${VDKR_VERBOSE:-${VPDMN_VERBOSE:-false}}"
42set_runtime_config() { 42set_runtime_config() {
43 case "$RUNTIME" in 43 case "$RUNTIME" in
44 docker) 44 docker)
45 TOOL_NAME="vdkr" 45 TOOL_NAME="${VCONTAINER_RUNTIME_NAME:-vdkr}"
46 BLOB_SUBDIR="vdkr-blobs" 46 BLOB_SUBDIR="vdkr-blobs"
47 BLOB_SUBDIR_ALT="blobs" 47 BLOB_SUBDIR_ALT="blobs"
48 CMDLINE_PREFIX="docker" 48 CMDLINE_PREFIX="docker"
49 STATE_DIR_BASE="${VDKR_STATE_DIR:-$HOME/.vdkr}" 49 STATE_DIR_BASE="${VDKR_STATE_DIR:-$HOME/.${TOOL_NAME}}"
50 STATE_FILE="docker-state.img" 50 STATE_FILE="docker-state.img"
51 ;; 51 ;;
52 podman) 52 podman)
53 TOOL_NAME="vpdmn" 53 TOOL_NAME="${VCONTAINER_RUNTIME_NAME:-vpdmn}"
54 BLOB_SUBDIR="vpdmn-blobs" 54 BLOB_SUBDIR="vpdmn-blobs"
55 BLOB_SUBDIR_ALT="blobs/vpdmn" 55 BLOB_SUBDIR_ALT="blobs/vpdmn"
56 CMDLINE_PREFIX="podman" 56 CMDLINE_PREFIX="podman"
57 STATE_DIR_BASE="${VPDMN_STATE_DIR:-$HOME/.vpdmn}" 57 STATE_DIR_BASE="${VPDMN_STATE_DIR:-$HOME/.${TOOL_NAME}}"
58 STATE_FILE="podman-state.img" 58 STATE_FILE="podman-state.img"
59 ;; 59 ;;
60 *) 60 *)
@@ -417,6 +417,10 @@ while [ $# -gt 0 ]; do
417 DISABLE_KVM="true" 417 DISABLE_KVM="true"
418 shift 418 shift
419 ;; 419 ;;
420 --hypervisor)
421 VCONTAINER_HYPERVISOR="$2"
422 shift 2
423 ;;
420 --batch-import) 424 --batch-import)
421 BATCH_IMPORT="true" 425 BATCH_IMPORT="true"
422 # Force storage output type for batch import 426 # Force storage output type for batch import
@@ -485,6 +489,18 @@ done
485set_runtime_config 489set_runtime_config
486set_blob_dir 490set_blob_dir
487 491
492# Load hypervisor backend
493VCONTAINER_HYPERVISOR="${VCONTAINER_HYPERVISOR:-qemu}"
494VCONTAINER_LIBDIR="${VCONTAINER_LIBDIR:-$SCRIPT_DIR}"
495HV_BACKEND="$VCONTAINER_LIBDIR/vrunner-backend-${VCONTAINER_HYPERVISOR}.sh"
496if [ ! -f "$HV_BACKEND" ]; then
497 echo "ERROR: Hypervisor backend not found: $HV_BACKEND" >&2
498 echo "Available backends:" >&2
499 ls "$VCONTAINER_LIBDIR"/vrunner-backend-*.sh 2>/dev/null | sed 's/.*vrunner-backend-//;s/\.sh$//' >&2
500 exit 1
501fi
502source "$HV_BACKEND"
503
488# Daemon mode handling 504# Daemon mode handling
489# Set default socket directory based on architecture 505# Set default socket directory based on architecture
490# If --state-dir was provided, use it for daemon files too 506# If --state-dir was provided, use it for daemon files too
@@ -503,6 +519,12 @@ DAEMON_INPUT_SIZE_MB=2048 # 2GB input disk for daemon mode
503 519
504# Daemon helper functions 520# Daemon helper functions
505daemon_is_running() { 521daemon_is_running() {
522 # Use backend-specific check if available (e.g. Xen xl list)
523 if type hv_daemon_is_running >/dev/null 2>&1; then
524 hv_daemon_is_running
525 return $?
526 fi
527 # Default: check PID (works for QEMU)
506 if [ -f "$DAEMON_PID_FILE" ]; then 528 if [ -f "$DAEMON_PID_FILE" ]; then
507 local pid=$(cat "$DAEMON_PID_FILE" 2>/dev/null) 529 local pid=$(cat "$DAEMON_PID_FILE" 2>/dev/null)
508 if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then 530 if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
@@ -514,8 +536,10 @@ daemon_is_running() {
514 536
515daemon_status() { 537daemon_status() {
516 if daemon_is_running; then 538 if daemon_is_running; then
517 local pid=$(cat "$DAEMON_PID_FILE") 539 local pid=$(cat "$DAEMON_PID_FILE" 2>/dev/null)
518 echo "Daemon running (PID: $pid)" 540 local vm_id
541 vm_id=$(hv_get_vm_id 2>/dev/null || echo "$pid")
542 echo "Daemon running (VM: $vm_id)"
519 echo "Socket: $DAEMON_SOCKET" 543 echo "Socket: $DAEMON_SOCKET"
520 echo "Architecture: $TARGET_ARCH" 544 echo "Architecture: $TARGET_ARCH"
521 return 0 545 return 0
@@ -531,6 +555,14 @@ daemon_stop() {
531 return 0 555 return 0
532 fi 556 fi
533 557
558 # Use backend-specific stop if available (e.g. Xen xl shutdown/destroy)
559 if type hv_daemon_stop >/dev/null 2>&1; then
560 hv_daemon_stop
561 rm -f "$DAEMON_PID_FILE" "$DAEMON_SOCKET"
562 return 0
563 fi
564
565 # Default: PID-based stop (works for QEMU)
534 local pid=$(cat "$DAEMON_PID_FILE") 566 local pid=$(cat "$DAEMON_PID_FILE")
535 log "INFO" "Stopping daemon (PID: $pid)..." 567 log "INFO" "Stopping daemon (PID: $pid)..."
536 568
@@ -933,36 +965,16 @@ log "INFO" "Output type: $OUTPUT_TYPE"
933[ "$NETWORK" = "true" ] && log "INFO" "Networking: enabled (slirp)" 965[ "$NETWORK" = "true" ] && log "INFO" "Networking: enabled (slirp)"
934[ "$INTERACTIVE" = "true" ] && log "INFO" "Interactive mode: enabled" 966[ "$INTERACTIVE" = "true" ] && log "INFO" "Interactive mode: enabled"
935 967
936# Find kernel, initramfs, and rootfs 968# Initialize hypervisor backend: set arch-specific paths and commands
937case "$TARGET_ARCH" in 969hv_setup_arch
938 aarch64) 970hv_check_accel
939 KERNEL_IMAGE="$BLOB_DIR/aarch64/Image" 971hv_find_command
940 INITRAMFS="$BLOB_DIR/aarch64/initramfs.cpio.gz"
941 ROOTFS_IMG="$BLOB_DIR/aarch64/rootfs.img"
942 QEMU_CMD="qemu-system-aarch64"
943 QEMU_MACHINE="-M virt -cpu cortex-a57"
944 CONSOLE="ttyAMA0"
945 ;;
946 x86_64)
947 KERNEL_IMAGE="$BLOB_DIR/x86_64/bzImage"
948 INITRAMFS="$BLOB_DIR/x86_64/initramfs.cpio.gz"
949 ROOTFS_IMG="$BLOB_DIR/x86_64/rootfs.img"
950 QEMU_CMD="qemu-system-x86_64"
951 # Use q35 + Skylake-Client to match oe-core qemux86-64 machine
952 QEMU_MACHINE="-M q35 -cpu Skylake-Client"
953 CONSOLE="ttyS0"
954 ;;
955 *)
956 log "ERROR" "Unsupported architecture: $TARGET_ARCH"
957 exit 1
958 ;;
959esac
960 972
961# Check for kernel 973# Check for kernel
962if [ ! -f "$KERNEL_IMAGE" ]; then 974if [ ! -f "$KERNEL_IMAGE" ]; then
963 log "ERROR" "Kernel not found: $KERNEL_IMAGE" 975 log "ERROR" "Kernel not found: $KERNEL_IMAGE"
964 log "ERROR" "Set VDKR_BLOB_DIR or --blob-dir to location of vdkr blobs" 976 log "ERROR" "Set --blob-dir to location of blobs"
965 log "ERROR" "Build with: MACHINE=qemuarm64 bitbake vdkr-initramfs-build" 977 log "ERROR" "Build with: bitbake ${TOOL_NAME}-initramfs-create"
966 exit 1 978 exit 1
967fi 979fi
968 980
@@ -973,61 +985,20 @@ if [ ! -f "$INITRAMFS" ]; then
973 exit 1 985 exit 1
974fi 986fi
975 987
976# Check for rootfs image (ext4 with Docker tools) 988# Check for rootfs image
977if [ ! -f "$ROOTFS_IMG" ]; then 989if [ ! -f "$ROOTFS_IMG" ]; then
978 log "ERROR" "Rootfs image not found: $ROOTFS_IMG" 990 log "ERROR" "Rootfs image not found: $ROOTFS_IMG"
979 log "ERROR" "Build with: MACHINE=qemuarm64 bitbake vdkr-initramfs-create" 991 log "ERROR" "Build with: MACHINE=qemuarm64 bitbake vdkr-initramfs-create"
980 exit 1 992 exit 1
981fi 993fi
982 994
983# Find QEMU - check PATH and common locations 995log "DEBUG" "Using initramfs: $INITRAMFS"
984if ! command -v "$QEMU_CMD" >/dev/null 2>&1; then
985 # Try common locations
986 for path in \
987 "${STAGING_BINDIR_NATIVE:-}" \
988 "/usr/bin"; do
989 if [ -n "$path" ] && [ -x "$path/$QEMU_CMD" ]; then
990 QEMU_CMD="$path/$QEMU_CMD"
991 break
992 fi
993 done
994fi
995
996if ! command -v "$QEMU_CMD" >/dev/null 2>&1 && [ ! -x "$QEMU_CMD" ]; then
997 log "ERROR" "QEMU not found: $QEMU_CMD"
998 exit 1
999fi
1000
1001log "DEBUG" "Using QEMU: $QEMU_CMD"
1002 996
1003# Check for KVM acceleration (when host matches target) 997# Let backend prepare container image if needed (e.g., Xen pulls OCI via skopeo)
1004USE_KVM="false" 998if type hv_prepare_container >/dev/null 2>&1; then
1005if [ "$DISABLE_KVM" = "true" ]; then 999 hv_prepare_container
1006 log "DEBUG" "KVM disabled by --no-kvm flag"
1007else
1008 HOST_ARCH=$(uname -m)
1009 if [ "$HOST_ARCH" = "$TARGET_ARCH" ] || \
1010 { [ "$HOST_ARCH" = "x86_64" ] && [ "$TARGET_ARCH" = "x86_64" ]; }; then
1011 if [ -w /dev/kvm ]; then
1012 USE_KVM="true"
1013 # Use host CPU for best performance with KVM
1014 case "$TARGET_ARCH" in
1015 x86_64)
1016 QEMU_MACHINE="-M q35 -cpu host"
1017 ;;
1018 aarch64)
1019 QEMU_MACHINE="-M virt -cpu host"
1020 ;;
1021 esac
1022 log "INFO" "KVM acceleration enabled"
1023 else
1024 log "DEBUG" "KVM not available (no write access to /dev/kvm)"
1025 fi
1026 fi
1027fi 1000fi
1028 1001
1029log "DEBUG" "Using initramfs: $INITRAMFS"
1030
1031# Create input disk image if needed 1002# Create input disk image if needed
1032DISK_OPTS="" 1003DISK_OPTS=""
1033if [ -n "$INPUT_PATH" ] && [ "$INPUT_TYPE" != "none" ]; then 1004if [ -n "$INPUT_PATH" ] && [ "$INPUT_TYPE" != "none" ]; then
@@ -1062,8 +1033,10 @@ if [ -n "$INPUT_PATH" ] && [ "$INPUT_TYPE" != "none" ]; then
1062fi 1033fi
1063 1034
1064# Create state disk for persistent storage (--state-dir) 1035# Create state disk for persistent storage (--state-dir)
1036# Xen backend skips this: DomU Docker storage lives in the guest's overlay
1037# filesystem and persists as long as the domain is running (daemon mode).
1065STATE_DISK_OPTS="" 1038STATE_DISK_OPTS=""
1066if [ -n "$STATE_DIR" ]; then 1039if [ -n "$STATE_DIR" ] && ! type hv_skip_state_disk >/dev/null 2>&1; then
1067 mkdir -p "$STATE_DIR" 1040 mkdir -p "$STATE_DIR"
1068 STATE_IMG="$STATE_DIR/$STATE_FILE" 1041 STATE_IMG="$STATE_DIR/$STATE_FILE"
1069 1042
@@ -1090,6 +1063,10 @@ if [ -n "$STATE_DIR" ]; then
1090 # Combined with graceful shutdown wait, this ensures data integrity 1063 # Combined with graceful shutdown wait, this ensures data integrity
1091 STATE_DISK_OPTS="-drive file=$STATE_IMG,if=virtio,format=raw,cache=directsync" 1064 STATE_DISK_OPTS="-drive file=$STATE_IMG,if=virtio,format=raw,cache=directsync"
1092 log "DEBUG" "State disk: $(ls -lh "$STATE_IMG" | awk '{print $5}')" 1065 log "DEBUG" "State disk: $(ls -lh "$STATE_IMG" | awk '{print $5}')"
1066elif [ -n "$STATE_DIR" ]; then
1067 # Backend skips state disk but we still need the directory for daemon files
1068 mkdir -p "$STATE_DIR"
1069 log "DEBUG" "State disk: skipped (${VCONTAINER_HYPERVISOR} backend manages guest storage)"
1093fi 1070fi
1094 1071
1095# Create state disk from input-storage tar (--input-storage) 1072# Create state disk from input-storage tar (--input-storage)
@@ -1147,9 +1124,9 @@ DOCKER_CMD_B64=$(echo -n "$DOCKER_CMD" | base64 -w0)
1147# In interactive mode, use 'quiet' to suppress kernel boot messages 1124# In interactive mode, use 'quiet' to suppress kernel boot messages
1148# Use CMDLINE_PREFIX for runtime-specific parameters (docker_ or podman_) 1125# Use CMDLINE_PREFIX for runtime-specific parameters (docker_ or podman_)
1149if [ "$INTERACTIVE" = "true" ]; then 1126if [ "$INTERACTIVE" = "true" ]; then
1150 KERNEL_APPEND="console=$CONSOLE,115200 quiet loglevel=0 init=/init" 1127 KERNEL_APPEND="console=$(hv_get_console_device),115200 quiet loglevel=0 init=/init"
1151else 1128else
1152 KERNEL_APPEND="console=$CONSOLE,115200 init=/init" 1129 KERNEL_APPEND="console=$(hv_get_console_device),115200 init=/init"
1153fi 1130fi
1154# Tell init script which runtime we're using 1131# Tell init script which runtime we're using
1155KERNEL_APPEND="$KERNEL_APPEND runtime=$RUNTIME" 1132KERNEL_APPEND="$KERNEL_APPEND runtime=$RUNTIME"
@@ -1198,66 +1175,24 @@ if [ "$INTERACTIVE" = "true" ]; then
1198 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_interactive=1" 1175 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_interactive=1"
1199fi 1176fi
1200 1177
1201# Build QEMU command 1178# Build VM configuration via hypervisor backend
1202# Drive ordering is important: 1179# Drive ordering is important:
1203# /dev/vda = rootfs.img (read-only, ext4 with Docker tools) 1180# rootfs.img (read-only), input disk (if any), state disk (if any)
1204# /dev/vdb = input disk (if any) 1181hv_build_disk_opts
1205# /dev/vdc = state disk (if any) 1182hv_build_network_opts
1206# The preinit script in initramfs mounts /dev/vda and does switch_root 1183hv_build_vm_cmd
1207# Build QEMU options
1208QEMU_OPTS="$QEMU_MACHINE -nographic -smp 2 -m 2048 -no-reboot"
1209if [ "$USE_KVM" = "true" ]; then
1210 QEMU_OPTS="$QEMU_OPTS -enable-kvm"
1211fi
1212QEMU_OPTS="$QEMU_OPTS -kernel $KERNEL_IMAGE"
1213QEMU_OPTS="$QEMU_OPTS -initrd $INITRAMFS"
1214QEMU_OPTS="$QEMU_OPTS -drive file=$ROOTFS_IMG,if=virtio,format=raw,readonly=on"
1215QEMU_OPTS="$QEMU_OPTS $DISK_OPTS"
1216QEMU_OPTS="$QEMU_OPTS $STATE_DISK_OPTS"
1217
1218# Add networking if enabled (slirp user-mode networking)
1219if [ "$NETWORK" = "true" ]; then
1220 # Slirp provides NAT'd outbound connectivity without root privileges
1221 # Guest gets 10.0.2.15, gateway is 10.0.2.2, DNS is 10.0.2.3
1222 NETDEV_OPTS="user,id=net0"
1223
1224 # Add port forwards - QEMU forwards host:port -> VM:port
1225 # Docker's iptables handles VM:port -> container:port
1226 for pf in "${PORT_FORWARDS[@]}"; do
1227 # Parse host_port:container_port or host_port:container_port/protocol
1228 HOST_PORT="${pf%%:*}"
1229 CONTAINER_PART="${pf#*:}"
1230 CONTAINER_PORT="${CONTAINER_PART%%/*}"
1231
1232 # Check for protocol suffix (default to tcp)
1233 if [[ "$CONTAINER_PART" == */* ]]; then
1234 PROTOCOL="${CONTAINER_PART##*/}"
1235 else
1236 PROTOCOL="tcp"
1237 fi
1238
1239 # Forward to HOST_PORT on VM; Docker -p handles container port mapping
1240 NETDEV_OPTS="$NETDEV_OPTS,hostfwd=$PROTOCOL::$HOST_PORT-:$HOST_PORT"
1241 log "INFO" "Port forward: host:$HOST_PORT -> VM:$HOST_PORT (Docker maps to container:$CONTAINER_PORT)"
1242 done
1243
1244 QEMU_OPTS="$QEMU_OPTS -netdev $NETDEV_OPTS -device virtio-net-pci,netdev=net0"
1245else
1246 # Explicitly disable networking
1247 QEMU_OPTS="$QEMU_OPTS -nic none"
1248fi
1249 1184
1250# Batch-import mode: add virtio-9p for fast output (instead of slow console base64) 1185# Batch-import mode: add 9p for fast output (instead of slow console base64)
1251if [ "$BATCH_IMPORT" = "true" ]; then 1186if [ "$BATCH_IMPORT" = "true" ]; then
1252 BATCH_SHARE_DIR="$TEMP_DIR/share" 1187 BATCH_SHARE_DIR="$TEMP_DIR/share"
1253 mkdir -p "$BATCH_SHARE_DIR" 1188 mkdir -p "$BATCH_SHARE_DIR"
1254 SHARE_TAG="${TOOL_NAME}_share" 1189 SHARE_TAG="${TOOL_NAME}_share"
1255 QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$BATCH_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,id=$SHARE_TAG" 1190 HV_OPTS="$HV_OPTS $(hv_build_9p_opts "$BATCH_SHARE_DIR" "$SHARE_TAG")"
1256 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" 1191 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1"
1257 log "INFO" "Using virtio-9p for fast storage output" 1192 log "INFO" "Using 9p for fast storage output"
1258fi 1193fi
1259 1194
1260# Daemon mode: add virtio-serial for command channel 1195# Daemon mode: add serial channel for command I/O
1261if [ "$DAEMON_MODE" = "start" ]; then 1196if [ "$DAEMON_MODE" = "start" ]; then
1262 # Check for required tools 1197 # Check for required tools
1263 if ! command -v socat >/dev/null 2>&1; then 1198 if ! command -v socat >/dev/null 2>&1; then
@@ -1275,30 +1210,18 @@ if [ "$DAEMON_MODE" = "start" ]; then
1275 # Create socket directory 1210 # Create socket directory
1276 mkdir -p "$DAEMON_SOCKET_DIR" 1211 mkdir -p "$DAEMON_SOCKET_DIR"
1277 1212
1278 # Create shared directory for file I/O (virtio-9p) 1213 # Create shared directory for file I/O (9p)
1279 DAEMON_SHARE_DIR="$DAEMON_SOCKET_DIR/share" 1214 DAEMON_SHARE_DIR="$DAEMON_SOCKET_DIR/share"
1280 mkdir -p "$DAEMON_SHARE_DIR" 1215 mkdir -p "$DAEMON_SHARE_DIR"
1281 1216
1282 # Add virtio-9p for shared directory access 1217 # Add 9p for shared directory access
1283 # Host writes to $DAEMON_SHARE_DIR, guest mounts as /mnt/share
1284 # Use runtime-specific mount tag (vdkr_share or vpdmn_share)
1285 SHARE_TAG="${TOOL_NAME}_share" 1218 SHARE_TAG="${TOOL_NAME}_share"
1286 # Use security_model=none for simplest file sharing (no permission mapping) 1219 HV_OPTS="$HV_OPTS $(hv_build_9p_opts "$DAEMON_SHARE_DIR" "$SHARE_TAG")"
1287 # This allows writes from container (running as root) to propagate to host
1288 QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$DAEMON_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,id=$SHARE_TAG"
1289 # Tell init script to mount the share
1290 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" 1220 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1"
1291 1221
1292 # Add virtio-serial device for command channel 1222 # Add daemon command channel (backend-specific: virtio-serial or PV console)
1293 # Using virtserialport creates /dev/vport0p1 in guest, host sees unix socket 1223 hv_build_daemon_opts
1294 # virtconsole would use hvc* but requires virtio_console kernel module 1224 HV_OPTS="$HV_OPTS $HV_DAEMON_OPTS"
1295 QEMU_OPTS="$QEMU_OPTS -chardev socket,id=vdkr,path=$DAEMON_SOCKET,server=on,wait=off"
1296 QEMU_OPTS="$QEMU_OPTS -device virtio-serial-pci"
1297 QEMU_OPTS="$QEMU_OPTS -device virtserialport,chardev=vdkr,name=vdkr"
1298
1299 # Add QMP socket for dynamic control (port forwarding, etc.)
1300 QMP_SOCKET="$DAEMON_SOCKET_DIR/qmp.sock"
1301 QEMU_OPTS="$QEMU_OPTS -qmp unix:$QMP_SOCKET,server,nowait"
1302 1225
1303 # Tell init script to run in daemon mode with idle timeout 1226 # Tell init script to run in daemon mode with idle timeout
1304 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_daemon=1" 1227 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_daemon=1"
@@ -1308,36 +1231,18 @@ if [ "$DAEMON_MODE" = "start" ]; then
1308 if [ "$NETWORK" != "true" ]; then 1231 if [ "$NETWORK" != "true" ]; then
1309 log "INFO" "Enabling networking for daemon mode" 1232 log "INFO" "Enabling networking for daemon mode"
1310 NETWORK="true" 1233 NETWORK="true"
1311 # Build netdev options with any port forwards 1234 hv_build_network_opts
1312 DAEMON_NETDEV="user,id=net0" 1235 # Re-add network opts (they were built without port forwards initially)
1236 # The rebuild includes port forwards since NETWORK is now true
1237 fi
1238 # Ensure port forwards are logged
1239 if [ ${#PORT_FORWARDS[@]} -gt 0 ]; then
1313 for pf in "${PORT_FORWARDS[@]}"; do 1240 for pf in "${PORT_FORWARDS[@]}"; do
1314 # Parse host_port:container_port or host_port:container_port/protocol
1315 HOST_PORT="${pf%%:*}" 1241 HOST_PORT="${pf%%:*}"
1316 CONTAINER_PART="${pf#*:}" 1242 CONTAINER_PART="${pf#*:}"
1317 CONTAINER_PORT="${CONTAINER_PART%%/*}" 1243 CONTAINER_PORT="${CONTAINER_PART%%/*}"
1318 if [[ "$CONTAINER_PART" == */* ]]; then 1244 log "INFO" "Port forward configured: $HOST_PORT -> $CONTAINER_PORT"
1319 PROTOCOL="${CONTAINER_PART##*/}"
1320 else
1321 PROTOCOL="tcp"
1322 fi
1323 # Forward to HOST_PORT on VM; Docker -p handles container port mapping
1324 DAEMON_NETDEV="$DAEMON_NETDEV,hostfwd=$PROTOCOL::$HOST_PORT-:$HOST_PORT"
1325 log "INFO" "Port forward: host:$HOST_PORT -> VM:$HOST_PORT (Docker maps to container:$CONTAINER_PORT)"
1326 done 1245 done
1327 QEMU_OPTS="$QEMU_OPTS -netdev $DAEMON_NETDEV -device virtio-net-pci,netdev=net0"
1328 else
1329 # NETWORK was already true, but check if we need to add port forwards
1330 # that weren't included in the earlier networking setup
1331 # (This happens when NETWORK was set to true before daemon mode was detected)
1332 if [ ${#PORT_FORWARDS[@]} -gt 0 ]; then
1333 # Port forwards should already be included from earlier networking setup
1334 for pf in "${PORT_FORWARDS[@]}"; do
1335 HOST_PORT="${pf%%:*}"
1336 CONTAINER_PART="${pf#*:}"
1337 CONTAINER_PORT="${CONTAINER_PART%%/*}"
1338 log "INFO" "Port forward configured: $HOST_PORT -> $CONTAINER_PORT"
1339 done
1340 fi
1341 fi 1246 fi
1342 1247
1343 # Copy CA certificate to shared folder (too large for kernel cmdline) 1248 # Copy CA certificate to shared folder (too large for kernel cmdline)
@@ -1349,23 +1254,23 @@ if [ "$DAEMON_MODE" = "start" ]; then
1349 log "INFO" "Starting daemon..." 1254 log "INFO" "Starting daemon..."
1350 log "DEBUG" "PID file: $DAEMON_PID_FILE" 1255 log "DEBUG" "PID file: $DAEMON_PID_FILE"
1351 log "DEBUG" "Socket: $DAEMON_SOCKET" 1256 log "DEBUG" "Socket: $DAEMON_SOCKET"
1352 log "DEBUG" "Command: $QEMU_CMD $QEMU_OPTS -append \"$KERNEL_APPEND\""
1353 1257
1354 # Start QEMU in background 1258 # Start VM in background via backend
1355 $QEMU_CMD $QEMU_OPTS -append "$KERNEL_APPEND" > "$DAEMON_QEMU_LOG" 2>&1 & 1259 hv_start_vm_background "$KERNEL_APPEND" "$DAEMON_QEMU_LOG" ""
1356 QEMU_PID=$! 1260 echo "$HV_VM_PID" > "$DAEMON_PID_FILE"
1357 echo "$QEMU_PID" > "$DAEMON_PID_FILE"
1358 1261
1359 log "INFO" "QEMU started (PID: $QEMU_PID)" 1262 # Let backend save any extra state (e.g. Xen domain name)
1263 if type hv_daemon_save_state >/dev/null 2>&1; then
1264 hv_daemon_save_state
1265 fi
1360 1266
1361 # Wait for socket to appear (Docker starting) 1267 log "INFO" "VM started (PID: $HV_VM_PID)"
1362 # Docker can take 60+ seconds to start, so wait up to 120 seconds 1268
1269 # Wait for socket to appear (container runtime starting)
1363 log "INFO" "Waiting for daemon to be ready..." 1270 log "INFO" "Waiting for daemon to be ready..."
1364 READY=false 1271 READY=false
1365 for i in $(seq 1 120); do 1272 for i in $(seq 1 120); do
1366 if [ -S "$DAEMON_SOCKET" ]; then 1273 if [ -S "$DAEMON_SOCKET" ]; then
1367 # Socket exists, try to connect
1368 # Keep stdin open for 3 seconds to allow response to arrive
1369 RESPONSE=$( { echo "===PING==="; sleep 3; } | timeout 10 socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true) 1274 RESPONSE=$( { echo "===PING==="; sleep 3; } | timeout 10 socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true)
1370 if echo "$RESPONSE" | grep -q "===PONG==="; then 1275 if echo "$RESPONSE" | grep -q "===PONG==="; then
1371 log "DEBUG" "Got PONG response" 1276 log "DEBUG" "Got PONG response"
@@ -1376,64 +1281,54 @@ if [ "$DAEMON_MODE" = "start" ]; then
1376 fi 1281 fi
1377 fi 1282 fi
1378 1283
1379 # Check if QEMU died 1284 # Check if VM died
1380 if ! kill -0 "$QEMU_PID" 2>/dev/null; then 1285 if ! hv_is_vm_running; then
1381 log "ERROR" "QEMU process died during startup" 1286 log "ERROR" "VM process died during startup"
1382 cat "$DAEMON_QEMU_LOG" >&2 1287 cat "$DAEMON_QEMU_LOG" >&2
1383 rm -f "$DAEMON_PID_FILE" 1288 rm -f "$DAEMON_PID_FILE"
1384 exit 1 1289 exit 1
1385 fi 1290 fi
1386 1291
1387 log "DEBUG" "Waiting... ($i/60)" 1292 log "DEBUG" "Waiting... ($i/120)"
1388 sleep 1 1293 sleep 1
1389 done 1294 done
1390 1295
1391 if [ "$READY" = "true" ]; then 1296 if [ "$READY" = "true" ]; then
1392 log "INFO" "Daemon is ready!" 1297 log "INFO" "Daemon is ready!"
1393 1298
1299 # Set up port forwards via backend (e.g., iptables for Xen)
1300 hv_setup_port_forwards
1301
1394 # Start host-side idle watchdog if timeout is set 1302 # Start host-side idle watchdog if timeout is set
1395 if [ "$IDLE_TIMEOUT" -gt 0 ] 2>/dev/null; then 1303 if [ "$IDLE_TIMEOUT" -gt 0 ] 2>/dev/null; then
1396 ACTIVITY_FILE="$DAEMON_SOCKET_DIR/activity" 1304 ACTIVITY_FILE="$DAEMON_SOCKET_DIR/activity"
1397 touch "$ACTIVITY_FILE" 1305 touch "$ACTIVITY_FILE"
1398 1306
1399 # Spawn background watchdog
1400 ( 1307 (
1401 # Container status file - guest writes this via virtio-9p share
1402 # This avoids sending commands through daemon socket which corrupts output
1403 CONTAINER_STATUS_FILE="$DAEMON_SHARE_DIR/.containers_running" 1308 CONTAINER_STATUS_FILE="$DAEMON_SHARE_DIR/.containers_running"
1404
1405 # Scale check interval to idle timeout (check ~5 times before timeout)
1406 CHECK_INTERVAL=$((IDLE_TIMEOUT / 5)) 1309 CHECK_INTERVAL=$((IDLE_TIMEOUT / 5))
1407 [ "$CHECK_INTERVAL" -lt 10 ] && CHECK_INTERVAL=10 1310 [ "$CHECK_INTERVAL" -lt 10 ] && CHECK_INTERVAL=10
1408 [ "$CHECK_INTERVAL" -gt 60 ] && CHECK_INTERVAL=60 1311 [ "$CHECK_INTERVAL" -gt 60 ] && CHECK_INTERVAL=60
1409 1312
1410 while true; do 1313 while true; do
1411 sleep "$CHECK_INTERVAL" 1314 sleep "$CHECK_INTERVAL"
1412 [ -f "$ACTIVITY_FILE" ] || exit 0 # Clean exit if file removed 1315 [ -f "$ACTIVITY_FILE" ] || exit 0
1413 [ -f "$DAEMON_PID_FILE" ] || exit 0 # PID file gone 1316 [ -f "$DAEMON_PID_FILE" ] || exit 0
1414 1317
1415 # Check if QEMU process is still running 1318 # Check if VM is still running (backend-aware)
1416 QEMU_PID=$(cat "$DAEMON_PID_FILE" 2>/dev/null) 1319 hv_is_vm_running || exit 0
1417 [ -n "$QEMU_PID" ] && kill -0 "$QEMU_PID" 2>/dev/null || exit 0
1418 1320
1419 LAST_ACTIVITY=$(stat -c %Y "$ACTIVITY_FILE" 2>/dev/null || echo 0) 1321 LAST_ACTIVITY=$(stat -c %Y "$ACTIVITY_FILE" 2>/dev/null || echo 0)
1420 NOW=$(date +%s) 1322 NOW=$(date +%s)
1421 IDLE_SECONDS=$((NOW - LAST_ACTIVITY)) 1323 IDLE_SECONDS=$((NOW - LAST_ACTIVITY))
1422 1324
1423 if [ "$IDLE_SECONDS" -ge "$IDLE_TIMEOUT" ]; then 1325 if [ "$IDLE_SECONDS" -ge "$IDLE_TIMEOUT" ]; then
1424 # Check if any containers are running via shared file
1425 # Guest-side watchdog writes container IDs to this file
1426 if [ -f "$CONTAINER_STATUS_FILE" ] && [ -s "$CONTAINER_STATUS_FILE" ]; then 1326 if [ -f "$CONTAINER_STATUS_FILE" ] && [ -s "$CONTAINER_STATUS_FILE" ]; then
1427 # Containers are running - reset activity and skip shutdown
1428 touch "$ACTIVITY_FILE" 1327 touch "$ACTIVITY_FILE"
1429 continue 1328 continue
1430 fi 1329 fi
1431 1330 # Use backend-specific idle shutdown
1432 # No containers running - send QMP quit to gracefully stop QEMU 1331 hv_idle_shutdown
1433 if [ -S "$QMP_SOCKET" ]; then
1434 echo '{"execute":"qmp_capabilities"}{"execute":"quit"}' | \
1435 socat - "UNIX-CONNECT:$QMP_SOCKET" >/dev/null 2>&1 || true
1436 fi
1437 rm -f "$ACTIVITY_FILE" 1332 rm -f "$ACTIVITY_FILE"
1438 exit 0 1333 exit 0
1439 fi 1334 fi
@@ -1442,119 +1337,103 @@ if [ "$DAEMON_MODE" = "start" ]; then
1442 log "DEBUG" "Started host-side idle watchdog (timeout: ${IDLE_TIMEOUT}s)" 1337 log "DEBUG" "Started host-side idle watchdog (timeout: ${IDLE_TIMEOUT}s)"
1443 fi 1338 fi
1444 1339
1445 echo "Daemon running (PID: $QEMU_PID)" 1340 echo "Daemon running (PID: $HV_VM_PID)"
1446 echo "Socket: $DAEMON_SOCKET" 1341 echo "Socket: $DAEMON_SOCKET"
1447 exit 0 1342 exit 0
1448 else 1343 else
1449 log "ERROR" "Daemon failed to become ready within 120 seconds" 1344 log "ERROR" "Daemon failed to become ready within 120 seconds"
1450 cat "$DAEMON_QEMU_LOG" >&2 1345 cat "$DAEMON_QEMU_LOG" >&2
1451 kill "$QEMU_PID" 2>/dev/null || true 1346 hv_destroy_vm
1452 rm -f "$DAEMON_PID_FILE" "$DAEMON_SOCKET" 1347 rm -f "$DAEMON_PID_FILE" "$DAEMON_SOCKET"
1453 exit 1 1348 exit 1
1454 fi 1349 fi
1455fi 1350fi
1456 1351
1457# For non-daemon mode with CA cert, we need virtio-9p to pass the cert 1352# For non-daemon mode with CA cert, we need 9p to pass the cert
1458# (kernel cmdline is too small for base64-encoded certs)
1459if [ -n "$CA_CERT" ] && [ -f "$CA_CERT" ]; then 1353if [ -n "$CA_CERT" ] && [ -f "$CA_CERT" ]; then
1460 # Create temp share dir for CA cert
1461 CA_SHARE_DIR="$TEMP_DIR/ca_share" 1354 CA_SHARE_DIR="$TEMP_DIR/ca_share"
1462 mkdir -p "$CA_SHARE_DIR" 1355 mkdir -p "$CA_SHARE_DIR"
1463 cp "$CA_CERT" "$CA_SHARE_DIR/ca.crt" 1356 cp "$CA_CERT" "$CA_SHARE_DIR/ca.crt"
1464 1357
1465 # Add virtio-9p mount for CA cert
1466 SHARE_TAG="${TOOL_NAME}_share" 1358 SHARE_TAG="${TOOL_NAME}_share"
1467 QEMU_OPTS="$QEMU_OPTS -virtfs local,path=$CA_SHARE_DIR,mount_tag=$SHARE_TAG,security_model=none,readonly=on,id=cashare" 1359 HV_OPTS="$HV_OPTS $(hv_build_9p_opts "$CA_SHARE_DIR" "$SHARE_TAG" "readonly=on")"
1468 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1" 1360 KERNEL_APPEND="$KERNEL_APPEND ${CMDLINE_PREFIX}_9p=1"
1469 log "DEBUG" "CA certificate available via virtio-9p" 1361 log "DEBUG" "CA certificate available via 9p"
1470fi 1362fi
1471 1363
1472log "INFO" "Starting QEMU..." 1364log "INFO" "Starting VM ($VCONTAINER_HYPERVISOR)..."
1473log "DEBUG" "Command: $QEMU_CMD $QEMU_OPTS -append \"$KERNEL_APPEND\""
1474 1365
1475# Interactive mode runs QEMU in foreground with stdio connected 1366# Interactive mode runs VM in foreground with stdio connected
1476if [ "$INTERACTIVE" = "true" ]; then 1367if [ "$INTERACTIVE" = "true" ]; then
1477 # Check if stdin is a terminal
1478 if [ ! -t 0 ]; then 1368 if [ ! -t 0 ]; then
1479 log "WARN" "Interactive mode requested but stdin is not a terminal" 1369 log "WARN" "Interactive mode requested but stdin is not a terminal"
1480 fi 1370 fi
1481 1371
1482 # Show a starting message
1483 # The init script will clear this line when the container is ready
1484 if [ -t 1 ]; then 1372 if [ -t 1 ]; then
1485 printf "\r\033[0;36m[vdkr]\033[0m Starting container... \r" 1373 printf "\r\033[0;36m[${TOOL_NAME}]\033[0m Starting container... \r"
1486 fi 1374 fi
1487 1375
1488 # Save terminal settings to restore later
1489 if [ -t 0 ]; then 1376 if [ -t 0 ]; then
1490 SAVED_STTY=$(stty -g) 1377 SAVED_STTY=$(stty -g)
1491 # Put terminal in raw mode so Ctrl+C etc go to guest
1492 stty raw -echo 1378 stty raw -echo
1493 fi 1379 fi
1494 1380
1495 # Run QEMU with stdio (not in background) 1381 hv_start_vm_foreground "$KERNEL_APPEND"
1496 # The -serial mon:stdio connects the serial console to our terminal 1382 VM_EXIT=$?
1497 $QEMU_CMD $QEMU_OPTS -append "$KERNEL_APPEND"
1498 QEMU_EXIT=$?
1499 1383
1500 # Restore terminal settings
1501 if [ -t 0 ]; then 1384 if [ -t 0 ]; then
1502 stty "$SAVED_STTY" 1385 stty "$SAVED_STTY"
1503 fi 1386 fi
1504 1387
1505 echo "" 1388 echo ""
1506 log "INFO" "Interactive session ended (exit code: $QEMU_EXIT)" 1389 log "INFO" "Interactive session ended (exit code: $VM_EXIT)"
1507 exit $QEMU_EXIT 1390 exit $VM_EXIT
1508fi 1391fi
1509 1392
1510# Non-interactive mode: run QEMU in background and capture output 1393# Non-interactive mode: run VM in background and capture output
1511QEMU_OUTPUT="$TEMP_DIR/qemu_output.txt" 1394VM_OUTPUT="$TEMP_DIR/vm_output.txt"
1512timeout $TIMEOUT $QEMU_CMD $QEMU_OPTS -append "$KERNEL_APPEND" > "$QEMU_OUTPUT" 2>&1 & 1395hv_start_vm_background "$KERNEL_APPEND" "$VM_OUTPUT" "$TIMEOUT"
1513QEMU_PID=$!
1514 1396
1515# Monitor for completion 1397# Monitor for completion
1516COMPLETE=false 1398COMPLETE=false
1517for i in $(seq 1 $TIMEOUT); do 1399for i in $(seq 1 $TIMEOUT); do
1518 if [ ! -d "/proc/$QEMU_PID" ]; then 1400 if ! hv_is_vm_running; then
1519 log "DEBUG" "QEMU ended after $i seconds" 1401 log "DEBUG" "VM ended after $i seconds"
1520 break 1402 break
1521 fi 1403 fi
1522 1404
1523 # Check for completion markers based on output type 1405 # Check for completion markers based on output type
1524 case "$OUTPUT_TYPE" in 1406 case "$OUTPUT_TYPE" in
1525 text) 1407 text)
1526 if grep -q "===OUTPUT_END===" "$QEMU_OUTPUT" 2>/dev/null; then 1408 if grep -q "===OUTPUT_END===" "$VM_OUTPUT" 2>/dev/null; then
1527 COMPLETE=true 1409 COMPLETE=true
1528 break 1410 break
1529 fi 1411 fi
1530 ;; 1412 ;;
1531 tar) 1413 tar)
1532 if grep -q "===TAR_END===" "$QEMU_OUTPUT" 2>/dev/null; then 1414 if grep -q "===TAR_END===" "$VM_OUTPUT" 2>/dev/null; then
1533 COMPLETE=true 1415 COMPLETE=true
1534 break 1416 break
1535 fi 1417 fi
1536 ;; 1418 ;;
1537 storage) 1419 storage)
1538 # Check for both console (STORAGE_END) and virtio-9p (9P_STORAGE_DONE) markers 1420 if grep -qE "===STORAGE_END===|===9P_STORAGE_DONE===" "$VM_OUTPUT" 2>/dev/null; then
1539 if grep -qE "===STORAGE_END===|===9P_STORAGE_DONE===" "$QEMU_OUTPUT" 2>/dev/null; then
1540 COMPLETE=true 1421 COMPLETE=true
1541 break 1422 break
1542 fi 1423 fi
1543 ;; 1424 ;;
1544 esac 1425 esac
1545 1426
1546 # Check for error 1427 if grep -q "===ERROR===" "$VM_OUTPUT" 2>/dev/null; then
1547 if grep -q "===ERROR===" "$QEMU_OUTPUT" 2>/dev/null; then 1428 log "ERROR" "Error in VM:"
1548 log "ERROR" "Error in QEMU:" 1429 grep -A10 "===ERROR===" "$VM_OUTPUT"
1549 grep -A10 "===ERROR===" "$QEMU_OUTPUT"
1550 break 1430 break
1551 fi 1431 fi
1552 1432
1553 # Progress indicator
1554 if [ $((i % 30)) -eq 0 ]; then 1433 if [ $((i % 30)) -eq 0 ]; then
1555 if grep -q "Docker daemon is ready" "$QEMU_OUTPUT" 2>/dev/null; then 1434 if grep -q "Docker daemon is ready" "$VM_OUTPUT" 2>/dev/null; then
1556 log "INFO" "Docker is running, executing command..." 1435 log "INFO" "Docker is running, executing command..."
1557 elif grep -q "Starting Docker" "$QEMU_OUTPUT" 2>/dev/null; then 1436 elif grep -q "Starting Docker" "$VM_OUTPUT" 2>/dev/null; then
1558 log "INFO" "Docker is starting..." 1437 log "INFO" "Docker is starting..."
1559 fi 1438 fi
1560 fi 1439 fi
@@ -1562,38 +1441,28 @@ for i in $(seq 1 $TIMEOUT); do
1562 sleep 1 1441 sleep 1
1563done 1442done
1564 1443
1565# Wait for QEMU to exit gracefully (poweroff from inside flushes disks properly) 1444# Wait for VM to exit gracefully (poweroff from inside flushes disks properly)
1566# Only kill if it hangs after seeing completion marker 1445if [ "$COMPLETE" = "true" ] && hv_is_vm_running; then
1567if [ "$COMPLETE" = "true" ] && [ -d "/proc/$QEMU_PID" ]; then 1446 log "DEBUG" "Waiting for VM to complete graceful shutdown..."
1568 log "DEBUG" "Waiting for QEMU to complete graceful shutdown..." 1447 hv_wait_vm_exit 30 && log "DEBUG" "VM shutdown complete"
1569 # Give QEMU up to 30 seconds to poweroff after command completes
1570 for wait_i in $(seq 1 30); do
1571 if [ ! -d "/proc/$QEMU_PID" ]; then
1572 log "DEBUG" "QEMU shutdown complete"
1573 break
1574 fi
1575 sleep 1
1576 done
1577fi 1448fi
1578 1449
1579# Force kill QEMU only if still running after grace period 1450# Force kill VM only if still running after grace period
1580if [ -d "/proc/$QEMU_PID" ]; then 1451if hv_is_vm_running; then
1581 log "WARN" "QEMU still running, forcing termination..." 1452 hv_stop_vm
1582 kill $QEMU_PID 2>/dev/null || true
1583 wait $QEMU_PID 2>/dev/null || true
1584fi 1453fi
1585 1454
1586# Extract results 1455# Extract results
1587if [ "$COMPLETE" = "true" ]; then 1456if [ "$COMPLETE" = "true" ]; then
1588 # Get exit code 1457 # Get exit code
1589 EXIT_CODE=$(grep -oP '===EXIT_CODE=\K[0-9]+' "$QEMU_OUTPUT" | head -1) 1458 EXIT_CODE=$(grep -oP '===EXIT_CODE=\K[0-9]+' "$VM_OUTPUT" | head -1)
1590 EXIT_CODE="${EXIT_CODE:-0}" 1459 EXIT_CODE="${EXIT_CODE:-0}"
1591 1460
1592 case "$OUTPUT_TYPE" in 1461 case "$OUTPUT_TYPE" in
1593 text) 1462 text)
1594 log "INFO" "=== Command Output ===" 1463 log "INFO" "=== Command Output ==="
1595 # Use awk for precise extraction between markers 1464 # Use awk for precise extraction between markers
1596 awk '/===OUTPUT_START===/{capture=1; next} /===OUTPUT_END===/{capture=0} capture' "$QEMU_OUTPUT" 1465 awk '/===OUTPUT_START===/{capture=1; next} /===OUTPUT_END===/{capture=0} capture' "$VM_OUTPUT"
1597 log "INFO" "=== Exit Code: $EXIT_CODE ===" 1466 log "INFO" "=== Exit Code: $EXIT_CODE ==="
1598 ;; 1467 ;;
1599 1468
@@ -1601,7 +1470,7 @@ if [ "$COMPLETE" = "true" ]; then
1601 log "INFO" "Extracting tar output..." 1470 log "INFO" "Extracting tar output..."
1602 # Use awk for precise extraction between markers 1471 # Use awk for precise extraction between markers
1603 # Strip ANSI escape codes and non-base64 characters from serial console output 1472 # Strip ANSI escape codes and non-base64 characters from serial console output
1604 awk '/===TAR_START===/{capture=1; next} /===TAR_END===/{capture=0} capture' "$QEMU_OUTPUT" | \ 1473 awk '/===TAR_START===/{capture=1; next} /===TAR_END===/{capture=0} capture' "$VM_OUTPUT" | \
1605 tr -d '\r' | sed 's/\x1b\[[0-9;]*m//g' | tr -cd 'A-Za-z0-9+/=\n' | base64 -d > "$OUTPUT_FILE" 2>"${TEMP_DIR}/b64_errors.txt" 1474 tr -d '\r' | sed 's/\x1b\[[0-9;]*m//g' | tr -cd 'A-Za-z0-9+/=\n' | base64 -d > "$OUTPUT_FILE" 2>"${TEMP_DIR}/b64_errors.txt"
1606 1475
1607 if [ -s "${TEMP_DIR}/b64_errors.txt" ]; then 1476 if [ -s "${TEMP_DIR}/b64_errors.txt" ]; then
@@ -1634,7 +1503,7 @@ if [ "$COMPLETE" = "true" ]; then
1634 # 3. sed: remove ANSI escape codes 1503 # 3. sed: remove ANSI escape codes
1635 # 4. grep -v: remove kernel log messages (lines starting with [ followed by timestamp) 1504 # 4. grep -v: remove kernel log messages (lines starting with [ followed by timestamp)
1636 # 5. tr -cd: keep only valid base64 characters 1505 # 5. tr -cd: keep only valid base64 characters
1637 awk '/===STORAGE_START===/{capture=1; next} /===STORAGE_END===/{capture=0} capture' "$QEMU_OUTPUT" | \ 1506 awk '/===STORAGE_START===/{capture=1; next} /===STORAGE_END===/{capture=0} capture' "$VM_OUTPUT" | \
1638 tr -d '\r' | \ 1507 tr -d '\r' | \
1639 sed 's/\x1b\[[0-9;]*m//g' | \ 1508 sed 's/\x1b\[[0-9;]*m//g' | \
1640 grep -v '^\[[[:space:]]*[0-9]' | \ 1509 grep -v '^\[[[:space:]]*[0-9]' | \
@@ -1675,11 +1544,11 @@ if [ "$COMPLETE" = "true" ]; then
1675 exit "${EXIT_CODE:-0}" 1544 exit "${EXIT_CODE:-0}"
1676else 1545else
1677 log "ERROR" "Command execution failed or timed out" 1546 log "ERROR" "Command execution failed or timed out"
1678 log "ERROR" "QEMU output saved to: $QEMU_OUTPUT" 1547 log "ERROR" "QEMU output saved to: $VM_OUTPUT"
1679 1548
1680 if [ "$VERBOSE" = "true" ]; then 1549 if [ "$VERBOSE" = "true" ]; then
1681 log "DEBUG" "=== Last 50 lines of QEMU output ===" 1550 log "DEBUG" "=== Last 50 lines of QEMU output ==="
1682 tail -50 "$QEMU_OUTPUT" 1551 tail -50 "$VM_OUTPUT"
1683 fi 1552 fi
1684 1553
1685 exit 1 1554 exit 1