From 810a53980f5821a4421adfe44525315f913b940f Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 17 Jul 2026 21:09:04 +0200 Subject: [PATCH] feat: Preserve graceful shutdown for interactive console (#1876) --- src/entry.sh | 8 ++++- src/power.sh | 84 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/entry.sh b/src/entry.sh index f755c419..00bd2539 100644 --- a/src/entry.sh +++ b/src/entry.sh @@ -53,13 +53,19 @@ if ! enabled "$SHUTDOWN"; then exec "${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe" 2>&1 fi -"${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe" 2>&1 & +if ! interactive; then + "${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe" 2>&1 & +else + startConsole "$pipe" + setsid -w "${cmd[@]}" ${ARGS:+ $ARGS} "$pipe" 2>&1 & +fi pid=$! ( sleep 30; boot ) & rc=0 wait "$pid" || rc=$? +interactive && stopConsole wait "$output" || : [ -f "$QEMU_END" ] && exit "$rc" diff --git a/src/power.sh b/src/power.sh index aa300dd2..53a063a4 100644 --- a/src/power.sh +++ b/src/power.sh @@ -6,13 +6,19 @@ set -Eeuo pipefail # Configure QEMU for graceful shutdown +SHUTDOWN_SKIP=0 +SHUTDOWN_SIGNAL=0 + QEMU_PTY="$QEMU_DIR/qemu.pty" QEMU_END="$QEMU_DIR/qemu.end" +CONSOLE_PID="$QEMU_DIR/console.pid" +CONSOLE_SOCKET="$QEMU_DIR/console.sock" _trap() { local func="$1"; shift local sig + TRAP_PID=$BASHPID for sig; do @@ -133,7 +139,7 @@ forceKillQemu() { ! readQemuPid pid && return 0 ! isAlive "$pid" && return 0 - + display=$(displayReason "$reason") error "Forcefully terminating $(app), reason: $display..." { disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null @@ -174,8 +180,8 @@ cleanupHelpers() { local pids=( "${SMB_PID:-}" "${NMB_PID:-}" "${DDN_PID:-}" \ "${TPM_PID:-}" "${WSD_PID:-}" "${WEB_PID:-}" \ - "${AUX_PID:-}" "${AUDIO_PID:-}" "${PASST_PID:-}" \ - "${DNSMASQ_PID:-}" "${BALLOONING_PID:-}" ) + "${AUX_PID:-}" "${AUDIO_PID:-}" "${CONSOLE_PID:-}" \ + "${PASST_PID:-}" "${DNSMASQ_PID:-}" "${BALLOONING_PID:-}" ) mKill "${pids[@]}" @@ -183,6 +189,55 @@ cleanupHelpers() { return 0 } +startConsole() { + + local cnt=0 + local pid="" + local output="${1:-/dev/tty}" + + rm -f -- "$CONSOLE_SOCKET" "$CONSOLE_PID" + + if ! stty -icanon -echo isig -ixon min 1 time 0 "$output" + ) & + + pid=$! + echo "$pid" > "$CONSOLE_PID" + + while [ ! -S "$CONSOLE_SOCKET" ]; do + + if ! isAlive "$pid"; then + rm -f -- "$CONSOLE_PID" + error "Serial console relay exited unexpectedly!" + return 1 + fi + + sleep 0.02 + cnt=$((cnt + 1)) + + if (( cnt > 100 )); then + error "Failed to start serial console relay!" + return 1 + fi + + done + + return 0 +} + +stopConsole() { + + mKill "$CONSOLE_PID" + + return 0 +} + finish() { local reason=$1 @@ -276,7 +331,7 @@ waitForShutdown() { local name="$APP" local slp - while (( cnt <= wait_until )); do + while (( cnt <= wait_until && SHUTDOWN_SKIP == 0 )); do sleep 1 & slp=$! @@ -296,7 +351,7 @@ waitForShutdown() { sendAcpiShutdown - wait "$slp" + wait "$slp" || : (( cnt++ )) done @@ -315,20 +370,29 @@ graceful_shutdown() { code=$(signalCode "$sig") if [ -f "$QEMU_END" ]; then - echo && info "Received $1 signal while already shutting down..." + + if (( code == 130 && SHUTDOWN_SIGNAL == code )); then + SHUTDOWN_SKIP=1 + echo && info "Received SIGINT again, forcing shutdown..." + return + fi + + echo && info "Received $sig signal while already shutting down..." return fi set +e + SHUTDOWN_SIGNAL=$code + touch "$QEMU_END" - echo && info "Received $1 signal, sending ACPI shutdown signal..." + echo && info "Received $sig signal, sending ACPI shutdown signal..." if ! readQemuPid pid; then warn "QEMU PID file ($QEMU_PID) does not exist?" finish "$code" fi - if ! isAlive "$pid"; then + if [ -z "$pid" ] || ! isAlive "$pid"; then warn "QEMU process with PID $pid does not exist?" finish "$code" fi @@ -346,6 +410,10 @@ graceful_shutdown() { ! enabled "$SHUTDOWN" && return 0 [ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT" +if interactive; then + _trap graceful_shutdown SIGINT +fi + _trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT return 0