Unverified Commit 8d2f27bd authored by Kroese's avatar Kroese Committed by GitHub
Browse files

feat: Improved start and stop logic (#1758)

parent bcae867d
Loading
Loading
Loading
Loading
+26 −12
Original line number Diff line number Diff line
@@ -29,21 +29,35 @@ 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}${BOOT_DESC} using QEMU v$version..."

{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || :
(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15
if [[ "$SHUTDOWN" != [Yy1]* ]]; then
  exec "${cmd[@]}" ${ARGS:+ $ARGS}
fi

terminal
( sleep 30; boot ) &
tail -fn +0 "$QEMU_LOG" --pid=$$ 2>/dev/null &
cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" | \
sed -u -e 's/\x1B\[[=0-9;]*[a-z]//gi' \
-e 's/\x1B\x63//g' -e 's/\x1B\[[=?]7l//g' \
-e '/^$/d' -e 's/\x44\x53\x73//g' \
pipe="$QEMU_DIR/qemu.pipe"
rm -f "$pipe" && mkfifo "$pipe"

tee "$QEMU_PTY" <"$pipe" |
sed -u \
  -e 's/\x1B\[[=0-9;]*[a-z]//gi' \
  -e 's/\x1B\x63//g' \
  -e 's/\x1B\[[=?]7l//g' \
  -e '/^$/d' \
  -e 's/\x44\x53\x73//g' \
  -e 's/failed to load Boot/skipped Boot/g' \
-e 's/0): Not Found/0)/g' & wait $! || :
  -e 's/0): Not Found/0)/g' &

"${cmd[@]}" ${ARGS:+ $ARGS} >"$pipe" &

pid=$!
( sleep 30; boot ) &

rc=0
wait "$pid" || rc=$?
[ -f "$QEMU_END" ] && exit "$rc"

sleep 1 & wait $!
[ ! -f "$QEMU_END" ] && finish 0
finish "$rc"
+88 −121
Original line number Diff line number Diff line
#!/usr/bin/env bash
set -Eeuo pipefail

: "${QEMU_TIMEOUT:="110"}"  # QEMU Termination timeout
: "${SHUTDOWN:="Y"}"        # Graceful ACPI shutdown
: "${TIMEOUT:="115"}"       # QEMU termination timeout

# Configure QEMU for graceful shutdown

QEMU_TERM=""
QEMU_PTY="$QEMU_DIR/qemu.pty"
QEMU_LOG="$QEMU_DIR/qemu.log"
QEMU_OUT="$QEMU_DIR/qemu.out"
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
}

boot() {

  [ -f "$QEMU_END" ] && return 0
@@ -30,17 +35,14 @@ boot() {
        grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y"
      fi
      if [ -z "$fail" ]; then
        info "Windows started successfully, visit http://127.0.0.1:8006/ to view the screen..."
        info "$(app) started successfully, visit http://127.0.0.1:8006/ to view the screen..."
        return 0
      fi
    fi
  fi

  error "Timeout while waiting for QEMU to boot the machine!"

  local pid
  pid=$(<"$QEMU_PID")
  { kill -15 "$pid" || true; } 2>/dev/null
  error "Timeout while waiting for QEMU to boot the machine, aborting..."
  sKill "$QEMU_PID"

  return 0
}
@@ -68,39 +70,29 @@ ready() {

finish() {

  local pid
  local cnt=0
  local i=0
  local pid=""
  local reason=$1
  local pids=(
        "/var/run/tpm.pid"
        "/var/run/wsdd.pid"
        "/var/run/samba/nmbd.pid"
        "/var/run/samba/smbd.pid"
      )
  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

    pid=$(<"$QEMU_PID")
    echo && error "Forcefully terminating Windows, 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

  if [ ! -f "$STORAGE/windows.boot" ] && [ -f "$BOOT" ]; then
@@ -115,131 +107,106 @@ finish() {
    fi
  fi

  for pid in "${pids[@]}"; do
      if [[ -s "$pid" ]]; then 
          pKill "$(<"$pid")"
      fi
      rm -f "$pid"
  done 

  mKill "${pids[@]}"
  closeNetwork

  sleep 0.5
  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'*}"
  fi

  if [ ! -c "$dev" ]; then
    error "Device '$dev' not found!"
    finish 34 && return 34
  if ! waitPidFile "$QEMU_PID" 10; then
    warn "Timed out while waiting for $(app) to exit!"
  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

  [[ $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
    info "Received $1 while already shutting down..."
    echo && info "Received $1 while already shutting down..."
    return
  fi

  set +e
  touch "$QEMU_END"
  info "Received $1, sending ACPI shutdown signal..."
  echo && info "Received $1, sending ACPI shutdown signal..."

  if [ ! -s "$QEMU_PID" ]; then
    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

  local pid=""
  pid=$(<"$QEMU_PID")

  if ! isAlive "$pid"; then
    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

  if ! ready; then
    info "Cannot send ACPI signal during Windows setup, aborting..."
    finish "$code" && return "$code"
    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

  # Send ACPI shutdown signal
  echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null
  local cnt=0 abort=0 factor=3 offset=3 min max name

  local cnt=0
  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"
  max=$(( TIMEOUT - offset ))
  abort=$(( max - factor ))
  name="$(app)"

    sleep 1
    (( cnt++ ))
  while [ "$cnt" -le "$max" ]; do

    sleep 1 &
    local slp=$!

    ! isAlive "$pid" && break
    # Workaround for zombie pid
    [ ! -s "$QEMU_PID" ] && break

    info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"
    if [ "$cnt" -ne "$abort" ]; then
      if [ "$cnt" -gt 0 ]; 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

    # Send ACPI shutdown signal
    echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null
    if [ -S "$QEMU_DIR/monitor.sock" ]; then
      nc -q 1 -w 1 -U "$QEMU_DIR/monitor.sock" &> /dev/null <<<'system_powerdown' || :
    fi

  done
    wait $slp
    (( cnt++ ))

  if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
    error "Shutdown timeout reached, aborting..."
  fi
  done

  finish "$code" && return "$code"
  finish "$code"
}

touch "$QEMU_LOG"

SERIAL="pty"
MONITOR="telnet:localhost:$MON_PORT,server,nowait,nodelay -daemonize -D $QEMU_LOG"
[[ "$SHUTDOWN" != [Yy1]* ]] && return 0
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"

_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
_trap graceful_shutdown SIGTERM SIGHUP SIGABRT SIGQUIT

return 0
+6 −5
Original line number Diff line number Diff line
@@ -7,9 +7,11 @@ set -Eeuo pipefail
tmp="/tmp/smb"
rm -rf "$tmp"

rm -f /var/run/wsdd.pid
rm -f /var/run/samba/nmbd.pid
rm -f /var/run/samba/smbd.pid
DDN_PID="/var/run/wsdd.pid"
NMB_PID="/var/run/samba/nmbd.pid"
SMB_PID="/var/run/samba/smbd.pid"

rm -f "$SMB_PID" "$NMB_PID" "$DDN_PID"

[[ "$SAMBA" == [Nn]* ]] && return 0
[[ "$NETWORK" == [Nn]* ]] && return 0
@@ -206,10 +208,9 @@ else

  # Enable Web Service Discovery on Vista and up
  [[ "$DEBUG" == [Yy1]* ]] && echo "Starting wsddn daemon..."

  rm -f /var/log/wsddn.log

  if ! wsddn -i "${interfaces%%,*}" -H "$hostname" --unixd --log-file=/var/log/wsddn.log --pid-file=/var/run/wsdd.pid; then
  if ! wsddn -i "${interfaces%%,*}" -H "$hostname" --unixd --log-file=/var/log/wsddn.log --pid-file="$DDN_PID"; then
    SAMBA_DEBUG="Y"
    error "Failed to start wsddn daemon!"
  fi