summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer/files/vrunner-backend-xen.sh
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-containers/vcontainer/files/vrunner-backend-xen.sh')
-rw-r--r--recipes-containers/vcontainer/files/vrunner-backend-xen.sh301
1 files changed, 258 insertions, 43 deletions
diff --git a/recipes-containers/vcontainer/files/vrunner-backend-xen.sh b/recipes-containers/vcontainer/files/vrunner-backend-xen.sh
index 9f423dc6..55e87bd1 100644
--- a/recipes-containers/vcontainer/files/vrunner-backend-xen.sh
+++ b/recipes-containers/vcontainer/files/vrunner-backend-xen.sh
@@ -13,8 +13,8 @@
13# Key differences from QEMU backend: 13# Key differences from QEMU backend:
14# - Block devices appear as /dev/xvd* instead of /dev/vd* 14# - Block devices appear as /dev/xvd* instead of /dev/vd*
15# - Network uses bridge + iptables NAT instead of QEMU slirp 15# - Network uses bridge + iptables NAT instead of QEMU slirp
16# - Console uses PV console (hvc0/hvc1) instead of virtio-serial 16# - Console uses PV console (hvc0) with serial='pty' for PTY on Dom0
17# - 9p file sharing uses trans=xen instead of trans=virtio 17# - Daemon IPC uses direct PTY I/O (no socat bridge needed)
18# - VM tracking uses domain name instead of PID 18# - VM tracking uses domain name instead of PID
19 19
20# ============================================================================ 20# ============================================================================
@@ -43,8 +43,12 @@ hv_setup_arch() {
43 ;; 43 ;;
44 esac 44 esac
45 45
46 # Xen domain name (unique per instance) 46 # Xen domain name: use container name if set, otherwise PID-based
47 HV_DOMNAME="vxn-$$" 47 if [ -n "${CONTAINER_NAME:-}" ]; then
48 HV_DOMNAME="vxn-${CONTAINER_NAME}"
49 else
50 HV_DOMNAME="vxn-$$"
51 fi
48 HV_VM_PID="" 52 HV_VM_PID=""
49 53
50 # Xen domain config path (generated at runtime) 54 # Xen domain config path (generated at runtime)
@@ -97,6 +101,8 @@ hv_prepare_container() {
97 fi 101 fi
98 102
99 # Parse image name and any trailing command from "docker run [opts] <image> [cmd...]" 103 # Parse image name and any trailing command from "docker run [opts] <image> [cmd...]"
104 # Uses word counting + cut to extract the user command portion from the
105 # original string, preserving internal spaces (e.g., -c "echo hello && sleep 5").
100 local args 106 local args
101 args=$(echo "$DOCKER_CMD" | sed 's/^[a-z]* run //') 107 args=$(echo "$DOCKER_CMD" | sed 's/^[a-z]* run //')
102 108
@@ -104,16 +110,16 @@ hv_prepare_container() {
104 local user_cmd="" 110 local user_cmd=""
105 local skip_next=false 111 local skip_next=false
106 local found_image=false 112 local found_image=false
113 local word_count=0
107 for arg in $args; do 114 for arg in $args; do
115 if [ "$found_image" = "true" ]; then
116 break
117 fi
118 word_count=$((word_count + 1))
108 if [ "$skip_next" = "true" ]; then 119 if [ "$skip_next" = "true" ]; then
109 skip_next=false 120 skip_next=false
110 continue 121 continue
111 fi 122 fi
112 if [ "$found_image" = "true" ]; then
113 # Everything after image name is the user command
114 user_cmd="$user_cmd $arg"
115 continue
116 fi
117 case "$arg" in 123 case "$arg" in
118 --rm|--detach|-d|-i|--interactive|-t|--tty|--privileged|-it) 124 --rm|--detach|-d|-i|--interactive|-t|--tty|--privileged|-it)
119 ;; 125 ;;
@@ -130,7 +136,21 @@ hv_prepare_container() {
130 ;; 136 ;;
131 esac 137 esac
132 done 138 done
133 user_cmd=$(echo "$user_cmd" | sed 's/^ *//') 139
140 # Extract user command from original string using cut (preserves internal spaces)
141 if [ "$found_image" = "true" ]; then
142 user_cmd=$(echo "$args" | cut -d' ' -f$((word_count + 1))-)
143 # If cut returns the whole string (no fields after image), clear it
144 [ "$user_cmd" = "$args" ] && [ "$word_count" -ge "$(echo "$args" | wc -w)" ] && user_cmd=""
145 fi
146
147 # Strip /bin/sh -c wrapper from user command — the guest already wraps
148 # with /bin/sh -c in exec_in_container_background(), so passing it through
149 # would create nested shells with broken quoting.
150 case "$user_cmd" in
151 "/bin/sh -c "*) user_cmd="${user_cmd#/bin/sh -c }" ;;
152 "sh -c "*) user_cmd="${user_cmd#sh -c }" ;;
153 esac
134 154
135 if [ -z "$image" ]; then 155 if [ -z "$image" ]; then
136 log "DEBUG" "hv_prepare_container: no image found in DOCKER_CMD" 156 log "DEBUG" "hv_prepare_container: no image found in DOCKER_CMD"
@@ -245,22 +265,17 @@ hv_build_network_opts() {
245hv_build_9p_opts() { 265hv_build_9p_opts() {
246 local share_dir="$1" 266 local share_dir="$1"
247 local share_tag="$2" 267 local share_tag="$2"
248 # For Xen, 9p is configured in the domain config, not as a command-line option 268 # Xen 9p (xen_9pfsd) is not reliable in all environments (e.g. nested
249 # We accumulate these and include them in the config file 269 # QEMU→Xen). Keep the interface for future use but don't depend on it
250 _XEN_9P+=("{ 'tag': '$share_tag', 'path': '$share_dir', 'security_model': 'none' }") 270 # for daemon IPC — we use serial/PTY + socat instead.
251 # Return empty string since Xen doesn't use command-line 9p options 271 _XEN_9P+=("'tag=$share_tag,path=$share_dir,security_model=none,type=xen_9pfsd'")
252 echo ""
253} 272}
254 273
255hv_build_daemon_opts() { 274hv_build_daemon_opts() {
256 HV_DAEMON_OPTS="" 275 HV_DAEMON_OPTS=""
257 # Xen uses PV console (hvc1) for daemon command channel 276 # Xen daemon mode uses hvc0 with serial='pty' for bidirectional IPC.
258 # The init scripts already have /dev/hvc1 as a fallback for the daemon port 277 # The PTY is created on Dom0 and bridged to a Unix socket via socat.
259 # No extra config needed - hvc1 is automatically available in PV guests 278 # This is the same approach runx used (serial_start).
260 #
261 # For the host-side socket, we'll use xl console with a pipe
262 # The daemon socket is handled differently for Xen:
263 # We create a socat bridge between the Xen console and a unix socket
264} 279}
265 280
266hv_build_vm_cmd() { 281hv_build_vm_cmd() {
@@ -316,6 +331,8 @@ extra = "console=hvc0 quiet loglevel=0 init=/init vcontainer.blk=xvd vcontainer.
316disk = [ $disk_array ] 331disk = [ $disk_array ]
317vif = [ $vif_array ] 332vif = [ $vif_array ]
318 333
334serial = 'pty'
335
319on_poweroff = "destroy" 336on_poweroff = "destroy"
320on_reboot = "destroy" 337on_reboot = "destroy"
321on_crash = "destroy" 338on_crash = "destroy"
@@ -353,16 +370,36 @@ hv_start_vm_background() {
353 # Create the domain 370 # Create the domain
354 xl create "$HV_XEN_CFG" >> "$log_file" 2>&1 371 xl create "$HV_XEN_CFG" >> "$log_file" 2>&1
355 372
356 # For background monitoring, we need a PID-like concept 373 # Xen domains don't have a PID on Dom0 — xl manages them by name.
357 # Use the domain name as VM identifier 374 # For daemon mode, start a lightweight monitor process that stays alive
358 HV_VM_PID="$$" # Use our PID as a placeholder for compatibility 375 # while the domain exists. This gives vcontainer-common.sh a real PID
376 # to check in /proc/$pid for daemon_is_running().
377 HV_VM_PID="$$"
359 378
360 if [ "$DAEMON_MODE" = "start" ]; then 379 if [ "$DAEMON_MODE" = "start" ]; then
361 # Daemon mode: bridge xl console (hvc1) to the daemon unix socket 380 # Daemon mode: get the domain's hvc0 PTY for direct I/O.
362 # xl console -n 1 connects to the second PV console (hvc1) 381 # serial='pty' in the xl config creates a PTY on Dom0.
363 socat "UNIX-LISTEN:$DAEMON_SOCKET,fork" "EXEC:xl console -n 1 $HV_DOMNAME" & 382 # We read/write this PTY directly — no socat bridge needed.
364 _XEN_SOCAT_PID=$! 383 local domid
365 log "DEBUG" "Console-socket bridge started (PID: $_XEN_SOCAT_PID)" 384 domid=$(xl domid "$HV_DOMNAME" 2>/dev/null)
385 if [ -n "$domid" ]; then
386 _XEN_PTY=$(xenstore-read "/local/domain/$domid/console/tty" 2>/dev/null)
387 if [ -n "$_XEN_PTY" ]; then
388 log "DEBUG" "Domain $HV_DOMNAME (domid $domid) console PTY: $_XEN_PTY"
389 echo "$_XEN_PTY" > "$DAEMON_SOCKET_DIR/daemon.pty"
390 else
391 log "ERROR" "Could not read console PTY from xenstore for domid $domid"
392 fi
393 else
394 log "ERROR" "Could not get domid for $HV_DOMNAME"
395 fi
396
397 # Monitor process: stays alive while domain exists.
398 # vcontainer-common.sh checks /proc/$pid → alive means daemon running.
399 # When domain dies (xl destroy, guest reboot), monitor exits.
400 local _domname="$HV_DOMNAME"
401 (while xl list "$_domname" >/dev/null 2>&1; do sleep 10; done) &
402 HV_VM_PID=$!
366 else 403 else
367 # Ephemeral mode: capture guest console (hvc0) to log file 404 # Ephemeral mode: capture guest console (hvc0) to log file
368 # so the monitoring loop in vrunner.sh can see output markers 405 # so the monitoring loop in vrunner.sh can see output markers
@@ -413,11 +450,6 @@ hv_destroy_vm() {
413 if [ -n "${_XEN_CONSOLE_PID:-}" ]; then 450 if [ -n "${_XEN_CONSOLE_PID:-}" ]; then
414 kill $_XEN_CONSOLE_PID 2>/dev/null || true 451 kill $_XEN_CONSOLE_PID 2>/dev/null || true
415 fi 452 fi
416
417 # Clean up console bridge (daemon mode)
418 if [ -n "${_XEN_SOCAT_PID:-}" ]; then
419 kill $_XEN_SOCAT_PID 2>/dev/null || true
420 fi
421} 453}
422 454
423hv_get_vm_id() { 455hv_get_vm_id() {
@@ -524,6 +556,181 @@ hv_daemon_is_running() {
524 [ -n "$HV_DOMNAME" ] && xl list "$HV_DOMNAME" >/dev/null 2>&1 556 [ -n "$HV_DOMNAME" ] && xl list "$HV_DOMNAME" >/dev/null 2>&1
525} 557}
526 558
559# PTY-based daemon readiness check.
560# The guest emits ===PONG=== on hvc0 at daemon startup and in response to PING.
561# We read the PTY (saved by hv_start_vm_background) looking for this marker.
562hv_daemon_ping() {
563 local pty_file="$DAEMON_SOCKET_DIR/daemon.pty"
564 [ -f "$pty_file" ] || return 1
565 local pty
566 pty=$(cat "$pty_file")
567 [ -c "$pty" ] || return 1
568
569 # Open PTY for read/write on fd 3
570 exec 3<>"$pty"
571
572 # Send PING (guest also emits PONG at startup)
573 echo "===PING===" >&3
574
575 # Read lines looking for PONG (skip boot messages, log lines)
576 local line
577 while IFS= read -t 5 -r line <&3; do
578 line=$(echo "$line" | tr -d '\r')
579 case "$line" in
580 *"===PONG==="*) exec 3<&- 3>&-; return 0 ;;
581 esac
582 done
583
584 exec 3<&- 3>&- 2>/dev/null
585 return 1
586}
587
588# PTY-based daemon command send.
589# Writes base64-encoded command to PTY, reads response with markers.
590# Same protocol as socat-based daemon_send in vrunner.sh.
591hv_daemon_send() {
592 local cmd="$1"
593 local pty_file="$DAEMON_SOCKET_DIR/daemon.pty"
594 [ -f "$pty_file" ] || { log "ERROR" "No daemon PTY file"; return 1; }
595 local pty
596 pty=$(cat "$pty_file")
597 [ -c "$pty" ] || { log "ERROR" "PTY $pty not a character device"; return 1; }
598
599 # Update activity timestamp
600 touch "$DAEMON_SOCKET_DIR/activity" 2>/dev/null || true
601
602 # Encode command
603 local cmd_b64
604 cmd_b64=$(echo -n "$cmd" | base64 -w0)
605
606 # Open PTY for read/write on fd 3
607 exec 3<>"$pty"
608
609 # Drain any pending output (boot messages, prior log lines)
610 while IFS= read -t 0.5 -r _discard <&3; do :; done
611
612 # Send command
613 echo "$cmd_b64" >&3
614
615 # Read response with markers
616 local EXIT_CODE=0
617 local in_output=false
618 local line
619 while IFS= read -t 60 -r line <&3; do
620 line=$(echo "$line" | tr -d '\r')
621 case "$line" in
622 *"===OUTPUT_START==="*)
623 in_output=true
624 ;;
625 *"===OUTPUT_END==="*)
626 in_output=false
627 ;;
628 *"===EXIT_CODE="*"==="*)
629 EXIT_CODE=$(echo "$line" | sed 's/.*===EXIT_CODE=\([0-9]*\)===/\1/')
630 ;;
631 *"===END==="*)
632 break
633 ;;
634 *)
635 if [ "$in_output" = "true" ]; then
636 echo "$line"
637 fi
638 ;;
639 esac
640 done
641
642 exec 3<&- 3>&- 2>/dev/null
643 return ${EXIT_CODE:-0}
644}
645
646# Run a container in a memres (persistent) DomU.
647# Hot-plugs an input disk, sends ===RUN_CONTAINER=== command via PTY,
648# reads output, detaches disk.
649# Usage: hv_daemon_run_container <resolved_cmd> <input_disk_path>
650hv_daemon_run_container() {
651 local cmd="$1"
652 local input_disk="$2"
653
654 hv_daemon_load_state
655 if [ -z "$HV_DOMNAME" ]; then
656 log "ERROR" "No memres domain name"
657 return 1
658 fi
659
660 # Hot-plug the input disk as xvdb (read-only)
661 if [ -n "$input_disk" ] && [ -f "$input_disk" ]; then
662 log "INFO" "Hot-plugging container disk to $HV_DOMNAME..."
663 xl block-attach "$HV_DOMNAME" "format=raw,vdev=xvdb,access=ro,target=$input_disk" 2>/dev/null || {
664 log "ERROR" "Failed to attach block device"
665 return 1
666 }
667 sleep 1 # Let the kernel register the device
668 fi
669
670 # Build the command line: ===RUN_CONTAINER===<cmd_b64>
671 local raw_line="===RUN_CONTAINER==="
672 if [ -n "$cmd" ]; then
673 raw_line="${raw_line}$(echo -n "$cmd" | base64 -w0)"
674 fi
675
676 # Send via PTY and read response (same protocol as hv_daemon_send)
677 local pty_file="$DAEMON_SOCKET_DIR/daemon.pty"
678 [ -f "$pty_file" ] || { log "ERROR" "No daemon PTY file"; return 1; }
679 local pty
680 pty=$(cat "$pty_file")
681 [ -c "$pty" ] || { log "ERROR" "PTY $pty not a character device"; return 1; }
682
683 touch "$DAEMON_SOCKET_DIR/activity" 2>/dev/null || true
684
685 exec 3<>"$pty"
686
687 # Drain pending output
688 while IFS= read -t 0.5 -r _discard <&3; do :; done
689
690 # Send command
691 echo "$raw_line" >&3
692
693 # Read response with markers
694 local EXIT_CODE=0
695 local in_output=false
696 local line
697 while IFS= read -t 120 -r line <&3; do
698 line=$(echo "$line" | tr -d '\r')
699 case "$line" in
700 *"===OUTPUT_START==="*)
701 in_output=true
702 ;;
703 *"===OUTPUT_END==="*)
704 in_output=false
705 ;;
706 *"===EXIT_CODE="*"==="*)
707 EXIT_CODE=$(echo "$line" | sed 's/.*===EXIT_CODE=\([0-9]*\)===/\1/')
708 ;;
709 *"===ERROR==="*)
710 in_output=true
711 ;;
712 *"===END==="*)
713 break
714 ;;
715 *)
716 if [ "$in_output" = "true" ]; then
717 echo "$line"
718 fi
719 ;;
720 esac
721 done
722
723 exec 3<&- 3>&- 2>/dev/null
724
725 # Detach the input disk
726 if [ -n "$input_disk" ] && [ -f "$input_disk" ]; then
727 log "DEBUG" "Detaching container disk from $HV_DOMNAME..."
728 xl block-detach "$HV_DOMNAME" xvdb 2>/dev/null || true
729 fi
730
731 return ${EXIT_CODE:-0}
732}
733
527hv_daemon_stop() { 734hv_daemon_stop() {
528 hv_daemon_load_state 735 hv_daemon_load_state
529 if [ -z "$HV_DOMNAME" ]; then 736 if [ -z "$HV_DOMNAME" ]; then
@@ -532,10 +739,15 @@ hv_daemon_stop() {
532 739
533 log "INFO" "Shutting down Xen domain $HV_DOMNAME..." 740 log "INFO" "Shutting down Xen domain $HV_DOMNAME..."
534 741
535 # Send shutdown command via socket first (graceful guest shutdown) 742 # Send shutdown command via PTY (graceful guest shutdown)
536 if [ -S "$DAEMON_SOCKET" ]; then 743 local pty_file="$DAEMON_SOCKET_DIR/daemon.pty"
537 echo "===SHUTDOWN===" | socat - "UNIX-CONNECT:$DAEMON_SOCKET" 2>/dev/null || true 744 if [ -f "$pty_file" ]; then
538 sleep 2 745 local pty
746 pty=$(cat "$pty_file")
747 if [ -c "$pty" ]; then
748 echo "===SHUTDOWN===" > "$pty" 2>/dev/null || true
749 sleep 2
750 fi
539 fi 751 fi
540 752
541 # Try graceful xl shutdown 753 # Try graceful xl shutdown
@@ -554,11 +766,14 @@ hv_daemon_stop() {
554 xl destroy "$HV_DOMNAME" 2>/dev/null || true 766 xl destroy "$HV_DOMNAME" 2>/dev/null || true
555 fi 767 fi
556 768
557 # Clean up console bridge 769 # Kill monitor process (PID stored in daemon.pid)
558 if [ -n "${_XEN_SOCAT_PID:-}" ]; then 770 local pid_file="$DAEMON_SOCKET_DIR/daemon.pid"
559 kill $_XEN_SOCAT_PID 2>/dev/null || true 771 if [ -f "$pid_file" ]; then
772 local mpid
773 mpid=$(cat "$pid_file" 2>/dev/null)
774 [ -n "$mpid" ] && kill "$mpid" 2>/dev/null || true
560 fi 775 fi
561 776
562 rm -f "$(_xen_domname_file)" 777 rm -f "$(_xen_domname_file)" "$pty_file" "$pid_file"
563 log "INFO" "Xen domain stopped" 778 log "INFO" "Xen domain stopped"
564} 779}