From bde83a9e1ed2a86d518c0a1461ac7cb0a773564e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 5 Apr 2026 20:51:37 +0000 Subject: [PATCH] vmspawn: Redirect QEMU's stdin/stdout/stderr to the PTY When a PTY is allocated for the console, QEMU's own stdio file descriptors were still inherited directly from vmspawn, meaning any output QEMU writes to stdout/stderr (e.g. warnings) would bypass the PTY forwarder and go straight to the terminal. Similarly, QEMU could read directly from the terminal's stdin. Fix this by opening the PTY slave side and passing it as stdio_fds to the fork call with FORK_REARRANGE_STDIO, so that all of QEMU's I/O goes through the PTY and is properly forwarded. --- src/vmspawn/vmspawn.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 96ca4cee91..ee2c49b778 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -3517,12 +3517,20 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { log_debug("Executing: %s", joined); } + _cleanup_close_ int child_pty = -EBADF; + if (master >= 0) { + child_pty = pty_open_peer(master, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (child_pty < 0) + return log_error_errno(child_pty, "Failed to open PTY slave: %m"); + } + _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL; r = pidref_safe_fork_full( qemu_binary, - /* stdio_fds= */ NULL, + child_pty >= 0 ? (const int[]) { child_pty, child_pty, child_pty } : NULL, pass_fds, n_pass_fds, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE| + (child_pty >= 0 ? FORK_REARRANGE_STDIO : 0), &child_pidref); if (r < 0) return r; @@ -3539,6 +3547,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { } /* Close relevant fds we passed to qemu in the parent. We don't need them anymore. */ + child_pty = safe_close(child_pty); child_vsock_fd = safe_close(child_vsock_fd); tap_fd = safe_close(tap_fd);