From e377c62aaf3efe9de7ea38677670be73d7226d86 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sun, 5 Jul 2026 22:04:07 +0200 Subject: [PATCH] feat: Refactor shutdown logic (#1804) --- src/power.sh | 302 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 199 insertions(+), 103 deletions(-) diff --git a/src/power.sh b/src/power.sh index 9e97201..9729ca1 100644 --- a/src/power.sh +++ b/src/power.sh @@ -23,18 +23,67 @@ app() { echo "$APP" && return 0 } +signalCode() { + local sig="$1" + + case "$sig" in + SIGHUP) echo 129 ;; + SIGINT) echo 130 ;; + SIGQUIT) echo 131 ;; + SIGABRT) echo 134 ;; + SIGTERM) echo 143 ;; + *) echo 0 ;; + esac + + return 0 +} + +displayReason() { + local reason="$1" + + case "$reason" in + 129 ) echo "SIGHUP" ;; + 130 ) echo "SIGINT" ;; + 131 ) echo "SIGQUIT" ;; + 134 ) echo "SIGABRT" ;; + 143 ) echo "SIGTERM" ;; + * ) echo "$reason" ;; + esac + + return 0 +} + +readQemuPid() { + local file="$1" + local __var="$2" + local pid="" + + [ -s "$file" ] || return 1 + read -r pid <"$file" || return 1 + [ -n "$pid" ] || return 1 + + printf -v "$__var" '%s' "$pid" + return 0 +} + +bootFailed() { + local fail="" + + if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then + grep -Fq "No bootable device." "$QEMU_PTY" && fail="y" + grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y" + fi + + [ -n "$fail" ] +} + boot() { [ -f "$QEMU_END" ] && return 0 if [ -s "$QEMU_PTY" ]; then if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then - local fail="" - if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then - grep -Fq "No bootable device." "$QEMU_PTY" && fail="y" - grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y" - fi - if [ -z "$fail" ]; then + if ! bootFailed; then info "$(app) started successfully, visit http://127.0.0.1:8006/ to view the screen..." return 0 fi @@ -47,19 +96,26 @@ boot() { return 0 } +legacyBootReady() { + local last + local bios="Booting from Hard" + + last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1) + [[ "${last,,}" != "${bios,,}"* ]] && return 1 + grep -Fq "No bootable device." "$QEMU_PTY" && return 1 + grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1 + + return 0 +} + ready() { [ -f "$STORAGE/windows.boot" ] && return 0 [ ! -s "$QEMU_PTY" ] && return 1 if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then - local last - local bios="Booting from Hard" - last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1) - [[ "${last,,}" != "${bios,,}"* ]] && return 1 - grep -Fq "No bootable device." "$QEMU_PTY" && return 1 - grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1 - return 0 + legacyBootReady && return 0 + return 1 fi local line="\"Windows Boot Manager\"" @@ -68,48 +124,65 @@ ready() { return 1 } -finish() { - - local i=0 +forceKillQemu() { + local reason="$1" local pid="" - local reason=$1 + local display + + if readQemuPid "$QEMU_PID" pid; then + if isAlive "$pid"; then + display=$(displayReason "$reason") + error "Forcefully terminating $(app), reason: $display..." + { disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null + fi + fi + + return 0 +} + +markWindowsBooted() { + local file="$STORAGE/windows.boot" + + if [ -f "$file" ] || [ ! -f "$BOOT" ]; then + return 0 + fi + + # Remove CD-ROM ISO after install + if ready; then + touch "$file" + ! setOwner "$file" && error "Failed to set the owner for \"$file\" !" + if ! disabled "$REMOVE"; then + rm -f "$BOOT" 2>/dev/null || true + fi + fi + + return 0 +} + +cleanupHelpers() { local pids=( "${SMB_PID:-}" "${NMB_PID:-}" "${DDN_PID:-}" "${TPM_PID:-}" "${WSD_PID:-}" \ "${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" "${BALLOONING_PID:-}" ) - touch "$QEMU_END" - - if [ -s "$QEMU_PID" ]; then - if read -r pid <"$QEMU_PID"; then - if [ -n "$pid" ] && isAlive "$pid"; then - local display="$reason" - case "$reason" in - 129 ) display="SIGHUP" ;; - 130 ) display="SIGINT" ;; - 131 ) display="SIGQUIT" ;; - 134 ) display="SIGABRT" ;; - 143 ) display="SIGTERM" ;; - esac - error "Forcefully terminating $(app), reason: $display..." - { disown "$pid" || :; kill -9 -- "$pid" || :; } 2>/dev/null - fi - fi - fi - - if [ ! -f "$STORAGE/windows.boot" ] && [ -f "$BOOT" ]; then - # Remove CD-ROM ISO after install - if ready; then - local file="$STORAGE/windows.boot" - touch "$file" - ! setOwner "$file" && error "Failed to set the owner for \"$file\" !" - if ! disabled "$REMOVE"; then - rm -f "$BOOT" 2>/dev/null || true - fi - fi - fi - mKill "${pids[@]}" closeNetwork + return 0 +} + +finish() { + + local reason=$1 + + touch "$QEMU_END" + + forceKillQemu "$reason" + + if [ ! -f "$STORAGE/windows.boot" ]; then + markWindowsBooted + fi + + cleanupHelpers + if ! waitPidFile "$QEMU_PID" 10; then warn "Timed out while waiting for $(app) to exit!" fi @@ -118,55 +191,11 @@ finish() { exit "$reason" } -graceful_shutdown() { +normalizeTimeout() { + local min - local sig="$1" - local pid="" - local code=0 - - [[ $BASHPID != "$TRAP_PID" ]] && return - - case "$sig" in - SIGHUP) code=129 ;; - SIGINT) code=130 ;; - SIGQUIT) code=131 ;; - SIGABRT) code=134 ;; - SIGTERM) code=143 ;; - esac - - if [ -f "$QEMU_END" ]; then - echo && info "Received $1 signal while already shutting down..." - return - fi - - set +e - touch "$QEMU_END" - echo && info "Received $1 signal, sending ACPI shutdown signal..." - - if [ ! -s "$QEMU_PID" ] || ! read -r pid <"$QEMU_PID"; then - warn "QEMU PID file ($QEMU_PID) does not exist?" - finish "$code" - fi - - if [ -z "$pid" ] || ! isAlive "$pid"; then - warn "QEMU process with PID $pid does not exist?" - finish "$code" - fi - - if ! ready; then - info "Cannot send ACPI signal during $(app) setup, aborting..." - sKill "$QEMU_PID" - if ! waitPidFile "$QEMU_PID" 5; then - warn "Timed out while waiting for $(app) to exit!" - fi - finish "$code" - fi - - local name - name="$(app)" - - local term_grace=3 # seconds before loop ends to send SIGTERM - local cleanup_grace=3 # seconds reserved after the loop for cleanup + term_grace=3 # seconds before loop ends to send SIGTERM + cleanup_grace=3 # seconds reserved after the loop for cleanup TIMEOUT=$(strip "$TIMEOUT") if [[ ! "$TIMEOUT" =~ ^[0-9]+$ ]]; then @@ -181,18 +210,45 @@ graceful_shutdown() { cleanup_grace=4 fi - local cnt=0 sigterm_at=0 min wait_until - min=$((term_grace + cleanup_grace + 1)) (( TIMEOUT < min )) && (( TIMEOUT = min )) wait_until=$((TIMEOUT - cleanup_grace)) sigterm_at=$((wait_until - term_grace)) + return 0 +} + +sendAcpiShutdown() { + # Send ACPI shutdown signal + if [ -S "$QEMU_DIR/monitor.sock" ]; then + nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || : + fi + + return 0 +} + +abortDuringSetup() { + local code="$1" + + info "Cannot send ACPI signal during $(app) setup, aborting..." + sKill "$QEMU_PID" + if ! waitPidFile "$QEMU_PID" 5; then + warn "Timed out while waiting for $(app) to exit!" + fi + finish "$code" +} + +waitForShutdown() { + local cnt=0 + local name="$1" + local pid="$2" + local slp + while (( cnt <= wait_until )); do sleep 1 & - local slp=$! + slp=$! # Stop waiting if the process has exited ! isAlive "$pid" && break @@ -207,16 +263,56 @@ graceful_shutdown() { info "Waiting for $name to shut down... ($cnt/$wait_until)" fi - # Send ACPI shutdown signal - if [ -S "$QEMU_DIR/monitor.sock" ]; then - nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || : - fi + sendAcpiShutdown wait "$slp" (( cnt++ )) done + return 0 +} + +graceful_shutdown() { + + local sig="$1" + local pid="" + local code=0 + local name + local term_grace cleanup_grace + local sigterm_at=0 wait_until=0 + + [[ $BASHPID != "$TRAP_PID" ]] && return + + code=$(signalCode "$sig") + + if [ -f "$QEMU_END" ]; then + echo && info "Received $1 signal while already shutting down..." + return + fi + + set +e + touch "$QEMU_END" + echo && info "Received $1 signal, sending ACPI shutdown signal..." + + if ! readQemuPid "$QEMU_PID" pid; then + warn "QEMU PID file ($QEMU_PID) does not exist?" + finish "$code" + fi + + if ! isAlive "$pid"; then + warn "QEMU process with PID $pid does not exist?" + finish "$code" + fi + + if ! ready; then + abortDuringSetup "$code" + fi + + name="$(app)" + normalizeTimeout + waitForShutdown "$name" "$pid" + finish "$code" }