diff options
| author | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-18 18:20:16 +0000 |
|---|---|---|
| committer | Bruce Ashfield <bruce.ashfield@gmail.com> | 2026-02-26 01:05:01 +0000 |
| commit | 6fe3f4cd1a1f408e0ef6b52b793927984449c9f6 (patch) | |
| tree | be2aefe4f3dd6142bf4f918034264d0e2a298234 | |
| parent | ba23ccd3390b7fbebfed641ebfcd978a0ba406dd (diff) | |
| download | meta-virtualization-6fe3f4cd1a1f408e0ef6b52b793927984449c9f6.tar.gz | |
vxn: fix terminal mode hang and enable interactive container support
The containerd shim's Create RPC hung indefinitely because go-runc
captures the OCI runtime's stdout via a pipe, and cmd.Wait() blocks
until all holders of the pipe's write end close it. The background
monitor subshell inherited this pipe fd and held it open, preventing
the shim from ever proceeding to ReceiveMaster() or calling Start.
Fix by closing inherited stdout/stderr in the terminal-mode monitor
with exec >/dev/null before entering the domain poll loop. Non-terminal
mode is unaffected because the shim configures IO via FIFO dup2, where
cmd.Wait() only waits for process exit.
Additional changes for terminal mode support:
- vxn-sendtty: set PTY to raw mode (cfmakeraw) before sending fd
- vxn-oci-runtime: wait up to 5s for xenconsoled PTY, capture sendtty
return code, write persistent debug file to /root/vxn-tty-debug,
log every runtime invocation, remove stale debug logging
- vxn-init.sh: add [vxn] diagnostic markers for terminal visibility,
suppress kernel console messages early in interactive mode
- vcontainer-preinit.sh: suppress kernel messages in quiet mode
Signed-off-by: Bruce Ashfield <bruce.ashfield@gmail.com>
4 files changed, 67 insertions, 17 deletions
diff --git a/recipes-containers/vcontainer/files/vcontainer-preinit.sh b/recipes-containers/vcontainer/files/vcontainer-preinit.sh index 7d4e7072..7c0fa590 100644 --- a/recipes-containers/vcontainer/files/vcontainer-preinit.sh +++ b/recipes-containers/vcontainer/files/vcontainer-preinit.sh | |||
| @@ -35,6 +35,10 @@ log() { | |||
| 35 | [ "$QUIET" = "0" ] && echo "$@" | 35 | [ "$QUIET" = "0" ] && echo "$@" |
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | # Suppress kernel console messages in interactive mode so they don't | ||
| 39 | # pollute the terminal (loop device messages bypass loglevel=0) | ||
| 40 | [ "$QUIET" = "1" ] && { dmesg -n 1 2>/dev/null || true; } | ||
| 41 | |||
| 38 | log "=== vcontainer preinit (squashfs) ===" | 42 | log "=== vcontainer preinit (squashfs) ===" |
| 39 | 43 | ||
| 40 | # Wait for block devices to appear | 44 | # Wait for block devices to appear |
diff --git a/recipes-containers/vcontainer/files/vxn-init.sh b/recipes-containers/vcontainer/files/vxn-init.sh index 4b9b630a..c22fa8ea 100755 --- a/recipes-containers/vcontainer/files/vxn-init.sh +++ b/recipes-containers/vcontainer/files/vxn-init.sh | |||
| @@ -343,9 +343,10 @@ exec_in_container() { | |||
| 343 | if [ "$RUNTIME_INTERACTIVE" = "1" ]; then | 343 | if [ "$RUNTIME_INTERACTIVE" = "1" ]; then |
| 344 | # Interactive mode: connect stdin/stdout directly | 344 | # Interactive mode: connect stdin/stdout directly |
| 345 | export TERM=linux | 345 | export TERM=linux |
| 346 | printf '\r\033[K' | 346 | dmesg -n 1 2>/dev/null || true |
| 347 | echo "[vxn] chroot: $rootfs $cmd" > /dev/console 2>/dev/null | ||
| 347 | if [ "$use_sh" = "true" ]; then | 348 | if [ "$use_sh" = "true" ]; then |
| 348 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; $cmd" | 349 | chroot "$rootfs" /bin/sh -c "cd '$workdir' 2>/dev/null; exec $cmd" |
| 349 | else | 350 | else |
| 350 | chroot "$rootfs" $cmd | 351 | chroot "$rootfs" $cmd |
| 351 | fi | 352 | fi |
| @@ -630,6 +631,13 @@ mount_base_filesystems | |||
| 630 | # Check for quiet boot mode | 631 | # Check for quiet boot mode |
| 631 | check_quiet_boot | 632 | check_quiet_boot |
| 632 | 633 | ||
| 634 | # Interactive mode: suppress kernel console messages early (before mounts | ||
| 635 | # that trigger loop device messages) and emit markers visible through PTY | ||
| 636 | if [ "$QUIET_BOOT" = "1" ]; then | ||
| 637 | dmesg -n 1 2>/dev/null || true | ||
| 638 | echo "[vxn] init" > /dev/console 2>/dev/null | ||
| 639 | fi | ||
| 640 | |||
| 633 | log "=== vxn Init ===" | 641 | log "=== vxn Init ===" |
| 634 | log "Version: $VCONTAINER_VERSION" | 642 | log "Version: $VCONTAINER_VERSION" |
| 635 | 643 | ||
| @@ -720,6 +728,7 @@ else | |||
| 720 | fi | 728 | fi |
| 721 | 729 | ||
| 722 | # Execute in container rootfs | 730 | # Execute in container rootfs |
| 731 | [ "$QUIET_BOOT" = "1" ] && echo "[vxn] exec: $EXEC_CMD" > /dev/console 2>/dev/null | ||
| 723 | exec_in_container "$CONTAINER_ROOT" "$EXEC_CMD" | 732 | exec_in_container "$CONTAINER_ROOT" "$EXEC_CMD" |
| 724 | fi | 733 | fi |
| 725 | 734 | ||
diff --git a/recipes-containers/vcontainer/files/vxn-oci-runtime b/recipes-containers/vcontainer/files/vxn-oci-runtime index 6158cddd..57144aea 100644 --- a/recipes-containers/vcontainer/files/vxn-oci-runtime +++ b/recipes-containers/vcontainer/files/vxn-oci-runtime | |||
| @@ -169,14 +169,6 @@ cmd_create() { | |||
| 169 | [ -f "$bundle/config.json" ] || die "create: $bundle/config.json not found" | 169 | [ -f "$bundle/config.json" ] || die "create: $bundle/config.json not found" |
| 170 | 170 | ||
| 171 | log "CREATE: id=$container_id bundle=$bundle console_socket=$console_socket" | 171 | log "CREATE: id=$container_id bundle=$bundle console_socket=$console_socket" |
| 172 | # Debug: log what the shim gives us | ||
| 173 | log " DEBUG: fd0=$(readlink /proc/$$/fd/0 2>/dev/null) fd1=$(readlink /proc/$$/fd/1 2>/dev/null) fd2=$(readlink /proc/$$/fd/2 2>/dev/null)" | ||
| 174 | log " DEBUG: bundle dir: $(ls -F $bundle/ 2>/dev/null | tr '\n' ' ')" | ||
| 175 | local _taskdir | ||
| 176 | _taskdir=$(dirname "$bundle") | ||
| 177 | log " DEBUG: task dir ($bundle): $(ls -F $bundle/ 2>/dev/null | tr '\n' ' ')" | ||
| 178 | log " DEBUG: parent dir ($_taskdir): $(ls -F $_taskdir/ 2>/dev/null | tr '\n' ' ')" | ||
| 179 | log " DEBUG: all pipes: $(find /run -type p 2>/dev/null | tr '\n' ' ')" | ||
| 180 | 172 | ||
| 181 | detect_arch | 173 | detect_arch |
| 182 | 174 | ||
| @@ -283,10 +275,17 @@ XENEOF | |||
| 283 | 275 | ||
| 284 | log " Domain $domname created (paused)" | 276 | log " Domain $domname created (paused)" |
| 285 | 277 | ||
| 286 | # Get domid and read Xen console PTY from xenstore | 278 | # Get domid and read Xen console PTY from xenstore. |
| 279 | # xenconsoled may not have created the PTY yet — wait for it. | ||
| 287 | local domid pty_path | 280 | local domid pty_path |
| 288 | domid=$(xl domid "$domname" 2>/dev/null) || die "create: failed to get domid for $domname" | 281 | domid=$(xl domid "$domname" 2>/dev/null) || die "create: failed to get domid for $domname" |
| 289 | pty_path=$(xenstore-read "/local/domain/$domid/console/tty" 2>/dev/null) || true | 282 | pty_path="" |
| 283 | local _try | ||
| 284 | for _try in 1 2 3 4 5 6 7 8 9 10; do | ||
| 285 | pty_path=$(xenstore-read "/local/domain/$domid/console/tty" 2>/dev/null) || true | ||
| 286 | [ -n "$pty_path" ] && break | ||
| 287 | sleep 0.5 | ||
| 288 | done | ||
| 290 | log " domid=$domid pty=$pty_path" | 289 | log " domid=$domid pty=$pty_path" |
| 291 | 290 | ||
| 292 | if [ -n "$pty_path" ]; then | 291 | if [ -n "$pty_path" ]; then |
| @@ -294,14 +293,32 @@ XENEOF | |||
| 294 | fi | 293 | fi |
| 295 | 294 | ||
| 296 | # Terminal mode: send PTY fd to shim via console-socket (SCM_RIGHTS) | 295 | # Terminal mode: send PTY fd to shim via console-socket (SCM_RIGHTS) |
| 296 | local sendtty_rc="-" | ||
| 297 | if [ -n "$console_socket" ] && [ -n "$pty_path" ]; then | 297 | if [ -n "$console_socket" ] && [ -n "$pty_path" ]; then |
| 298 | if command -v vxn-sendtty >/dev/null 2>&1; then | 298 | if command -v vxn-sendtty >/dev/null 2>&1; then |
| 299 | vxn-sendtty "$console_socket" "$pty_path" \ | 299 | vxn-sendtty "$console_socket" "$pty_path" |
| 300 | || log " WARNING: vxn-sendtty failed (socket=$console_socket pty=$pty_path)" | 300 | sendtty_rc=$? |
| 301 | log " Sent PTY fd to console-socket" | 301 | [ "$sendtty_rc" -ne 0 ] && log " WARNING: vxn-sendtty failed (rc=$sendtty_rc)" |
| 302 | log " Sent PTY fd to console-socket (rc=$sendtty_rc)" | ||
| 302 | else | 303 | else |
| 304 | sendtty_rc="missing" | ||
| 303 | log " WARNING: vxn-sendtty not found, cannot send PTY to shim" | 305 | log " WARNING: vxn-sendtty not found, cannot send PTY to shim" |
| 304 | fi | 306 | fi |
| 307 | elif [ -n "$console_socket" ] && [ -z "$pty_path" ]; then | ||
| 308 | sendtty_rc="no-pty" | ||
| 309 | log " WARNING: no PTY path from xenstore, cannot send to shim" | ||
| 310 | fi | ||
| 311 | |||
| 312 | # Write terminal debug to persistent storage (survives reboot) | ||
| 313 | if [ -n "$console_socket" ]; then | ||
| 314 | cat > /root/vxn-tty-debug 2>/dev/null <<DBGEOF | ||
| 315 | domid=$domid | ||
| 316 | pty=$pty_path | ||
| 317 | console_socket=$console_socket | ||
| 318 | sendtty_rc=$sendtty_rc | ||
| 319 | terminal=$terminal | ||
| 320 | domname=$domname | ||
| 321 | DBGEOF | ||
| 305 | fi | 322 | fi |
| 306 | 323 | ||
| 307 | # Persistent log dir — survives container deletion by shim | 324 | # Persistent log dir — survives container deletion by shim |
| @@ -349,8 +366,12 @@ XENEOF | |||
| 349 | done < "$_logdir/console.log" | 366 | done < "$_logdir/console.log" |
| 350 | fi | 367 | fi |
| 351 | else | 368 | else |
| 352 | # Terminal mode: shim owns PTY — just wait for domain death | 369 | # Terminal mode: shim owns PTY — just wait for domain death. |
| 353 | while xl list "$_dn" >/dev/null 2>&1; do sleep 2; done | 370 | # Close inherited stdout/stderr: go-runc captures runtime stdout |
| 371 | # via a pipe and cmd.Wait() blocks until EOF. If we hold the | ||
| 372 | # pipe's write end open, Create never returns to the shim. | ||
| 373 | exec >/dev/null 2>/dev/null | ||
| 374 | while xl list "$_dn" >/dev/null 2>&1; do sleep 0.5; done | ||
| 354 | fi | 375 | fi |
| 355 | ) & | 376 | ) & |
| 356 | local monitor_pid=$! | 377 | local monitor_pid=$! |
| @@ -627,6 +648,9 @@ done | |||
| 627 | command="${1:-}" | 648 | command="${1:-}" |
| 628 | shift || true | 649 | shift || true |
| 629 | 650 | ||
| 651 | # Log every invocation for debugging (before command dispatch) | ||
| 652 | log "INVOKE: cmd=$command args=$* root=$RUNTIME_ROOT" | ||
| 653 | |||
| 630 | case "$command" in | 654 | case "$command" in |
| 631 | create) cmd_create "$@" ;; | 655 | create) cmd_create "$@" ;; |
| 632 | start) cmd_start "$@" ;; | 656 | start) cmd_start "$@" ;; |
diff --git a/recipes-containers/vcontainer/files/vxn-sendtty.c b/recipes-containers/vcontainer/files/vxn-sendtty.c index a253b129..b1257100 100644 --- a/recipes-containers/vcontainer/files/vxn-sendtty.c +++ b/recipes-containers/vcontainer/files/vxn-sendtty.c | |||
| @@ -19,6 +19,7 @@ | |||
| 19 | #include <string.h> | 19 | #include <string.h> |
| 20 | #include <unistd.h> | 20 | #include <unistd.h> |
| 21 | #include <fcntl.h> | 21 | #include <fcntl.h> |
| 22 | #include <termios.h> | ||
| 22 | #include <sys/socket.h> | 23 | #include <sys/socket.h> |
| 23 | #include <sys/un.h> | 24 | #include <sys/un.h> |
| 24 | 25 | ||
| @@ -44,6 +45,18 @@ int main(int argc, char *argv[]) | |||
| 44 | return 1; | 45 | return 1; |
| 45 | } | 46 | } |
| 46 | 47 | ||
| 48 | /* Set PTY to raw mode for direct terminal I/O. | ||
| 49 | * Without this, the PTY slave's line discipline (echo, canonical mode) | ||
| 50 | * buffers input and echoes characters, preventing the shim from | ||
| 51 | * bridging the terminal correctly. */ | ||
| 52 | { | ||
| 53 | struct termios tio; | ||
| 54 | if (tcgetattr(pty_fd, &tio) == 0) { | ||
| 55 | cfmakeraw(&tio); | ||
| 56 | tcsetattr(pty_fd, TCSANOW, &tio); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 47 | sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); | 60 | sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); |
| 48 | if (sock_fd < 0) { | 61 | if (sock_fd < 0) { |
| 49 | perror("socket"); | 62 | perror("socket"); |
