Loading .github/workflows/review.yml +1 −1 Original line number Diff line number Diff line Loading @@ -66,5 +66,5 @@ jobs: level: warning fail_level: error reporter: github-pr-review shellcheck_flags: -x -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 github_token: ${{ secrets.GITHUB_TOKEN }} src/config.sh +1 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ set -Eeuo pipefail DEF_OPTS="-nodefaults -boot strict=on" RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID" CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1" MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}" DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4" Loading src/entry.sh +13 −9 Original line number Diff line number Diff line Loading @@ -24,19 +24,23 @@ cd /run trap - ERR version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') cmd=(qemu-system-x86_64) version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }') info "Booting $APP using QEMU v$version..." if [[ "$CONSOLE" == [Yy]* ]]; then exec qemu-system-x86_64 ${ARGS:+ $ARGS} if [[ "$SHUTDOWN" != [Yy1]* ]]; then exec "${cmd[@]}" ${ARGS:+ $ARGS} fi { qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : (( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 if [ ! -t 1 ] || [ ! -c /dev/tty ]; then "${cmd[@]}" ${ARGS:+ $ARGS} & else "${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty & fi terminal tail -fn +0 "$QEMU_LOG" --pid=$$ 2>/dev/null & cat "$QEMU_TERM" 2>/dev/null & wait $! || : rc=0 wait $! || rc=$? [ -f "$QEMU_END" ] && exit "$rc" sleep 1 & wait $! [ ! -f "$QEMU_END" ] && finish 0 finish "$rc" src/network.sh +6 −16 Original line number Diff line number Diff line Loading @@ -198,9 +198,7 @@ configureDNS() { getHostPorts() { local list="" list+="$MON_PORT," list+="${HOST_PORTS// /}," local list="${HOST_PORTS// /}," # Remove duplicates list=$(echo "${list//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g') Loading Loading @@ -590,11 +588,8 @@ configureNAT() { closeBridge() { [ -s "$PASST_PID" ] && pKill "$(<"$PASST_PID")" rm -f "$PASST_PID" [ -s "$DNSMASQ_PID" ] && pKill "$(<"$DNSMASQ_PID")" rm -f "$DNSMASQ_PID" local pids=( "$PASST_PID" "$DNSMASQ_PID" ) mKill "${pids[@]}" ip link set "$VM_NET_TAP" down promisc off &> /dev/null || : ip link delete "$VM_NET_TAP" &> /dev/null || : Loading @@ -608,14 +603,8 @@ closeBridge() { closeWeb() { # Shutdown nginx nginx -s stop 2> /dev/null fWait "nginx" # Shutdown websocket local pid="/var/run/websocketd.pid" [ -s "$pid" ] && pKill "$(<"$pid")" rm -f "$pid" local pids=( "$WEB_PID" "$WSD_PID" ) mKill "${pids[@]}" return 0 } Loading @@ -632,6 +621,7 @@ closeNetwork() { exec 40<&- || true closeBridge return 0 } Loading src/power.sh +85 −115 Original line number Diff line number Diff line #!/usr/bin/env bash set -Eeuo pipefail : "${API_TIMEOUT:="50"}" # API Call timeout : "${QEMU_TIMEOUT:="50"}" # QEMU Termination timeout : "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown : "${TIMEOUT:="115"}" # QEMU termination timeout : "${API_TIMEOUT:="90"}" # External API call timeout # Configure QEMU for graceful shutdown API_CMD=6 API_HOST="127.0.0.1:$COM_PORT" QEMU_TERM="" QEMU_LOG="$QEMU_DIR/qemu.log" QEMU_OUT="$QEMU_DIR/qemu.out" QEMU_END="$QEMU_DIR/qemu.end" # Configure QEMU for graceful shutdown if [[ "$KVM" == [Nn]* ]]; then API_TIMEOUT=$(( API_TIMEOUT*2 )) QEMU_TIMEOUT=$(( QEMU_TIMEOUT*2 )) fi QEMU_END="$QEMU_DIR/qemu.end" _trap() { local func="$1"; shift local sig TRAP_PID=$BASHPID for sig; do trap "$func $sig" "$sig" done } app() { echo "$APP" && return 0 } finish() { local pid local cnt=0 local i=0 local pid="" local reason=$1 local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \ "${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" ) touch "$QEMU_END" if [ -s "$QEMU_PID" ]; then pid=$(<"$QEMU_PID") echo && error "Forcefully terminating Virtual DSM, reason: $reason..." { kill -15 "$pid" || true; } 2>/dev/null while isAlive "$pid"; do sleep 1 (( cnt++ )) # Workaround for zombie pid [ ! -s "$QEMU_PID" ] && break if [ "$cnt" -eq 5 ]; then echo && error "QEMU did not terminate itself, forcefully killing process..." { kill -9 "$pid" || true; } 2>/dev/null 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 done fi mKill "${pids[@]}" fKill "print.sh" fKill "host.bin" closeNetwork sleep 1 echo && echo "❯ Shutdown completed!" exit "$reason" } terminal() { local dev="" if [ -s "$QEMU_OUT" ]; then local msg msg=$(<"$QEMU_OUT") if [ -n "$msg" ]; then if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then echo "$msg" fi dev="${msg#*/dev/p}" dev="/dev/p${dev%% *}" fi fi if [ ! -c "$dev" ]; then dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$MON_PORT" | tr -d '\000') dev="${dev#*serial0}" dev="${dev#*pty:}" dev="${dev%%$'\n'*}" dev="${dev%%$'\r'*}" if ! waitPidFile "$QEMU_PID" 10; then warn "Timed out while waiting for $(app) to exit!" fi if [ ! -c "$dev" ]; then error "Device '$dev' not found!" finish 34 && return 34 fi QEMU_TERM="$dev" return 0 (( reason != 1 )) && echo && echo "❯ Shutdown completed!" exit "$reason" } _graceful_shutdown() { graceful_shutdown() { local sig="$1" local pid="" local code=0 local pid url response local start url response elapsed [[ $BASHPID != "$TRAP_PID" ]] && return case "$sig" in SIGTERM) code=143 ;; SIGINT) code=130 ;; SIGHUP) code=129 ;; SIGABRT) code=134 ;; SIGINT) code=130 ;; SIGQUIT) code=131 ;; SIGABRT) code=134 ;; SIGTERM) code=143 ;; esac if [ -f "$QEMU_END" ]; then Loading @@ -126,23 +91,22 @@ _graceful_shutdown() { fi set +e start=$SECONDS touch "$QEMU_END" echo && info "Received $1 signal, sending shutdown command..." if [ ! -s "$QEMU_PID" ]; then echo && error "QEMU PID file does not exist?" finish "$code" && return "$code" if [ ! -s "$QEMU_PID" ] || ! read -r pid <"$QEMU_PID"; then warn "QEMU PID file ($QEMU_PID) does not exist?" finish "$code" fi pid=$(<"$QEMU_PID") if ! isAlive "$pid"; then echo && error "QEMU process does not exist?" finish "$code" && return "$code" if [ -z "$pid" ] || ! isAlive "$pid"; then warn "QEMU process with PID $pid does not exist?" finish "$code" fi # Don't send the powerdown signal because vDSM ignores ACPI signals # echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null # nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || : # Send shutdown command to guest agent via serial port url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT" Loading @@ -157,46 +121,52 @@ _graceful_shutdown() { response="${response#*message\"\: \"}" [ -z "$response" ] && response="second signal" echo && error "Forcefully terminating because of: ${response%%\"*}" { kill -15 "$pid" || true; } 2>/dev/null { kill -15 -- "$pid" || :; } 2>/dev/null fi local cnt=0 local cnt=0 abort=0 factor=3 offset=3 min max name while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do [[ "$TIMEOUT" =~ ^[0-9]+$ ]] || TIMEOUT=115 [ "$TIMEOUT" -ge 15 ] && factor=4 && offset=4 [ "$TIMEOUT" -ge 30 ] && factor=5 && offset=5 min=$(( factor + offset + 1 )) [ "$TIMEOUT" -lt "$min" ] && TIMEOUT="$min" elapsed=$(( SECONDS - start )) max=$(( TIMEOUT - offset - elapsed )) [ "$max" -lt "$factor" ] && max=$(( factor + 1 )) abort=$(( max - factor )) name="$(app)" ! isAlive "$pid" && break sleep 1 (( cnt++ )) while [ "$cnt" -le "$max" ]; do [[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)" sleep 1 & local slp=$! ! isAlive "$pid" && break # Workaround for zombie pid [ ! -s "$QEMU_PID" ] && break done if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then echo && error "Shutdown timeout reached, aborting..." if [ "$cnt" -ne "$abort" ]; then if [ "$cnt" -gt 0 ] && [[ "$DEBUG" == [Yy1]* ]]; then info "Waiting for $name to shut down... ($cnt/$max)" fi else info "${name^} is still running, sending SIGTERM... ($cnt/$max)" { kill -15 -- "$pid" || :; } 2>/dev/null fi finish "$code" && return "$code" } touch "$QEMU_LOG" MON_OPTS="\ -pidfile $QEMU_PID \ -name $PROCESS,process=$PROCESS,debug-threads=on \ -monitor telnet:localhost:$MON_PORT,server,nowait,nodelay" wait $slp (( cnt++ )) if [[ "$CONSOLE" != [Yy]* ]]; then done MON_OPTS+=" -daemonize -D $QEMU_LOG" finish "$code" } _trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT [[ "$SHUTDOWN" != [Yy1]* ]] && return 0 [ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT" fi _trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT return 0 Loading
.github/workflows/review.yml +1 −1 Original line number Diff line number Diff line Loading @@ -66,5 +66,5 @@ jobs: level: warning fail_level: error reporter: github-pr-review shellcheck_flags: -x -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 shellcheck_flags: -x -e SC1091 -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 -e SC2028 github_token: ${{ secrets.GITHUB_TOKEN }}
src/config.sh +1 −0 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ set -Eeuo pipefail DEF_OPTS="-nodefaults -boot strict=on" RAM_OPTS=$(echo "-m ${RAM_SIZE^^}" | sed 's/MB/M/g;s/GB/G/g;s/TB/T/g') MON_OPTS="-name $PROCESS,process=$PROCESS,debug-threads=on -pidfile $QEMU_PID" CPU_OPTS="-cpu $CPU_FLAGS -smp $CPU_CORES,sockets=1,dies=1,cores=$CPU_CORES,threads=1" MAC_OPTS="-machine type=q35,smm=off,usb=off,vmport=off,dump-guest-core=off,hpet=off${KVM_OPTS}" DEV_OPTS="-device virtio-balloon-pci,id=balloon0,bus=pcie.0,addr=0x4" Loading
src/entry.sh +13 −9 Original line number Diff line number Diff line Loading @@ -24,19 +24,23 @@ cd /run trap - ERR version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') cmd=(qemu-system-x86_64) version=$("${cmd[@]}" --version | awk 'NR==1 { print $4 }') info "Booting $APP using QEMU v$version..." if [[ "$CONSOLE" == [Yy]* ]]; then exec qemu-system-x86_64 ${ARGS:+ $ARGS} if [[ "$SHUTDOWN" != [Yy1]* ]]; then exec "${cmd[@]}" ${ARGS:+ $ARGS} fi { qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : (( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 if [ ! -t 1 ] || [ ! -c /dev/tty ]; then "${cmd[@]}" ${ARGS:+ $ARGS} & else "${cmd[@]}" ${ARGS:+ $ARGS} </dev/tty >/dev/tty & fi terminal tail -fn +0 "$QEMU_LOG" --pid=$$ 2>/dev/null & cat "$QEMU_TERM" 2>/dev/null & wait $! || : rc=0 wait $! || rc=$? [ -f "$QEMU_END" ] && exit "$rc" sleep 1 & wait $! [ ! -f "$QEMU_END" ] && finish 0 finish "$rc"
src/network.sh +6 −16 Original line number Diff line number Diff line Loading @@ -198,9 +198,7 @@ configureDNS() { getHostPorts() { local list="" list+="$MON_PORT," list+="${HOST_PORTS// /}," local list="${HOST_PORTS// /}," # Remove duplicates list=$(echo "${list//,,/,}," | awk 'BEGIN{RS=ORS=","} !seen[$0]++' | sed 's/,*$//g') Loading Loading @@ -590,11 +588,8 @@ configureNAT() { closeBridge() { [ -s "$PASST_PID" ] && pKill "$(<"$PASST_PID")" rm -f "$PASST_PID" [ -s "$DNSMASQ_PID" ] && pKill "$(<"$DNSMASQ_PID")" rm -f "$DNSMASQ_PID" local pids=( "$PASST_PID" "$DNSMASQ_PID" ) mKill "${pids[@]}" ip link set "$VM_NET_TAP" down promisc off &> /dev/null || : ip link delete "$VM_NET_TAP" &> /dev/null || : Loading @@ -608,14 +603,8 @@ closeBridge() { closeWeb() { # Shutdown nginx nginx -s stop 2> /dev/null fWait "nginx" # Shutdown websocket local pid="/var/run/websocketd.pid" [ -s "$pid" ] && pKill "$(<"$pid")" rm -f "$pid" local pids=( "$WEB_PID" "$WSD_PID" ) mKill "${pids[@]}" return 0 } Loading @@ -632,6 +621,7 @@ closeNetwork() { exec 40<&- || true closeBridge return 0 } Loading
src/power.sh +85 −115 Original line number Diff line number Diff line #!/usr/bin/env bash set -Eeuo pipefail : "${API_TIMEOUT:="50"}" # API Call timeout : "${QEMU_TIMEOUT:="50"}" # QEMU Termination timeout : "${SHUTDOWN:="Y"}" # Graceful ACPI shutdown : "${TIMEOUT:="115"}" # QEMU termination timeout : "${API_TIMEOUT:="90"}" # External API call timeout # Configure QEMU for graceful shutdown API_CMD=6 API_HOST="127.0.0.1:$COM_PORT" QEMU_TERM="" QEMU_LOG="$QEMU_DIR/qemu.log" QEMU_OUT="$QEMU_DIR/qemu.out" QEMU_END="$QEMU_DIR/qemu.end" # Configure QEMU for graceful shutdown if [[ "$KVM" == [Nn]* ]]; then API_TIMEOUT=$(( API_TIMEOUT*2 )) QEMU_TIMEOUT=$(( QEMU_TIMEOUT*2 )) fi QEMU_END="$QEMU_DIR/qemu.end" _trap() { local func="$1"; shift local sig TRAP_PID=$BASHPID for sig; do trap "$func $sig" "$sig" done } app() { echo "$APP" && return 0 } finish() { local pid local cnt=0 local i=0 local pid="" local reason=$1 local pids=( "${HOST_PID:-}" "${WSD_PID:-}" \ "${WEB_PID:-}" "${PASST_PID:-}" "${DNSMASQ_PID:-}" ) touch "$QEMU_END" if [ -s "$QEMU_PID" ]; then pid=$(<"$QEMU_PID") echo && error "Forcefully terminating Virtual DSM, reason: $reason..." { kill -15 "$pid" || true; } 2>/dev/null while isAlive "$pid"; do sleep 1 (( cnt++ )) # Workaround for zombie pid [ ! -s "$QEMU_PID" ] && break if [ "$cnt" -eq 5 ]; then echo && error "QEMU did not terminate itself, forcefully killing process..." { kill -9 "$pid" || true; } 2>/dev/null 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 done fi mKill "${pids[@]}" fKill "print.sh" fKill "host.bin" closeNetwork sleep 1 echo && echo "❯ Shutdown completed!" exit "$reason" } terminal() { local dev="" if [ -s "$QEMU_OUT" ]; then local msg msg=$(<"$QEMU_OUT") if [ -n "$msg" ]; then if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then echo "$msg" fi dev="${msg#*/dev/p}" dev="/dev/p${dev%% *}" fi fi if [ ! -c "$dev" ]; then dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$MON_PORT" | tr -d '\000') dev="${dev#*serial0}" dev="${dev#*pty:}" dev="${dev%%$'\n'*}" dev="${dev%%$'\r'*}" if ! waitPidFile "$QEMU_PID" 10; then warn "Timed out while waiting for $(app) to exit!" fi if [ ! -c "$dev" ]; then error "Device '$dev' not found!" finish 34 && return 34 fi QEMU_TERM="$dev" return 0 (( reason != 1 )) && echo && echo "❯ Shutdown completed!" exit "$reason" } _graceful_shutdown() { graceful_shutdown() { local sig="$1" local pid="" local code=0 local pid url response local start url response elapsed [[ $BASHPID != "$TRAP_PID" ]] && return case "$sig" in SIGTERM) code=143 ;; SIGINT) code=130 ;; SIGHUP) code=129 ;; SIGABRT) code=134 ;; SIGINT) code=130 ;; SIGQUIT) code=131 ;; SIGABRT) code=134 ;; SIGTERM) code=143 ;; esac if [ -f "$QEMU_END" ]; then Loading @@ -126,23 +91,22 @@ _graceful_shutdown() { fi set +e start=$SECONDS touch "$QEMU_END" echo && info "Received $1 signal, sending shutdown command..." if [ ! -s "$QEMU_PID" ]; then echo && error "QEMU PID file does not exist?" finish "$code" && return "$code" if [ ! -s "$QEMU_PID" ] || ! read -r pid <"$QEMU_PID"; then warn "QEMU PID file ($QEMU_PID) does not exist?" finish "$code" fi pid=$(<"$QEMU_PID") if ! isAlive "$pid"; then echo && error "QEMU process does not exist?" finish "$code" && return "$code" if [ -z "$pid" ] || ! isAlive "$pid"; then warn "QEMU process with PID $pid does not exist?" finish "$code" fi # Don't send the powerdown signal because vDSM ignores ACPI signals # echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null # nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || : # Send shutdown command to guest agent via serial port url="http://$API_HOST/read?command=$API_CMD&timeout=$API_TIMEOUT" Loading @@ -157,46 +121,52 @@ _graceful_shutdown() { response="${response#*message\"\: \"}" [ -z "$response" ] && response="second signal" echo && error "Forcefully terminating because of: ${response%%\"*}" { kill -15 "$pid" || true; } 2>/dev/null { kill -15 -- "$pid" || :; } 2>/dev/null fi local cnt=0 local cnt=0 abort=0 factor=3 offset=3 min max name while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do [[ "$TIMEOUT" =~ ^[0-9]+$ ]] || TIMEOUT=115 [ "$TIMEOUT" -ge 15 ] && factor=4 && offset=4 [ "$TIMEOUT" -ge 30 ] && factor=5 && offset=5 min=$(( factor + offset + 1 )) [ "$TIMEOUT" -lt "$min" ] && TIMEOUT="$min" elapsed=$(( SECONDS - start )) max=$(( TIMEOUT - offset - elapsed )) [ "$max" -lt "$factor" ] && max=$(( factor + 1 )) abort=$(( max - factor )) name="$(app)" ! isAlive "$pid" && break sleep 1 (( cnt++ )) while [ "$cnt" -le "$max" ]; do [[ "$DEBUG" == [Yy1]* ]] && info "Shutting down, waiting... ($cnt/$QEMU_TIMEOUT)" sleep 1 & local slp=$! ! isAlive "$pid" && break # Workaround for zombie pid [ ! -s "$QEMU_PID" ] && break done if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then echo && error "Shutdown timeout reached, aborting..." if [ "$cnt" -ne "$abort" ]; then if [ "$cnt" -gt 0 ] && [[ "$DEBUG" == [Yy1]* ]]; then info "Waiting for $name to shut down... ($cnt/$max)" fi else info "${name^} is still running, sending SIGTERM... ($cnt/$max)" { kill -15 -- "$pid" || :; } 2>/dev/null fi finish "$code" && return "$code" } touch "$QEMU_LOG" MON_OPTS="\ -pidfile $QEMU_PID \ -name $PROCESS,process=$PROCESS,debug-threads=on \ -monitor telnet:localhost:$MON_PORT,server,nowait,nodelay" wait $slp (( cnt++ )) if [[ "$CONSOLE" != [Yy]* ]]; then done MON_OPTS+=" -daemonize -D $QEMU_LOG" finish "$code" } _trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT [[ "$SHUTDOWN" != [Yy1]* ]] && return 0 [ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT" fi _trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT return 0