power.sh 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. # Configure QEMU for graceful shutdown
  4. QEMU_TERM=""
  5. QEMU_PORT=7100
  6. QEMU_TIMEOUT=110
  7. QEMU_PID="/run/shm/qemu.pid"
  8. QEMU_LOG="/run/shm/qemu.log"
  9. QEMU_OUT="/run/shm/qemu.out"
  10. QEMU_END="/run/shm/qemu.end"
  11. rm -f /run/shm/qemu.*
  12. touch "$QEMU_LOG"
  13. _trap() {
  14. func="$1" ; shift
  15. for sig ; do
  16. trap "$func $sig" "$sig"
  17. done
  18. }
  19. finish() {
  20. local pid
  21. local reason=$1
  22. if [ -f "$QEMU_PID" ]; then
  23. pid=$(<"$QEMU_PID")
  24. echo && error "Forcefully terminating Windows, reason: $reason..."
  25. { kill -15 "$pid" || true; } 2>/dev/null
  26. while isAlive "$pid"; do
  27. sleep 1
  28. # Workaround for zombie pid
  29. [ ! -f "$QEMU_PID" ] && break
  30. done
  31. fi
  32. pid="/var/run/tpm.pid"
  33. [ -f "$pid" ] && pKill "$(<"$pid")"
  34. closeNetwork
  35. sleep 1
  36. echo && echo "❯ Shutdown completed!"
  37. exit "$reason"
  38. }
  39. terminal() {
  40. local dev=""
  41. if [ -f "$QEMU_OUT" ]; then
  42. local msg
  43. msg=$(<"$QEMU_OUT")
  44. if [ -n "$msg" ]; then
  45. if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then
  46. echo "$msg"
  47. fi
  48. dev="${msg#*/dev/p}"
  49. dev="/dev/p${dev%% *}"
  50. fi
  51. fi
  52. if [ ! -c "$dev" ]; then
  53. dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000')
  54. dev="${dev#*serial0}"
  55. dev="${dev#*pty:}"
  56. dev="${dev%%$'\n'*}"
  57. dev="${dev%%$'\r'*}"
  58. fi
  59. if [ ! -c "$dev" ]; then
  60. error "Device '$dev' not found!"
  61. finish 34 && return 34
  62. fi
  63. QEMU_TERM="$dev"
  64. return 0
  65. }
  66. _graceful_shutdown() {
  67. local code=$?
  68. set +e
  69. if [ -f "$QEMU_END" ]; then
  70. echo && info "Received $1 while already shutting down..."
  71. return
  72. fi
  73. touch "$QEMU_END"
  74. echo && info "Received $1, sending ACPI shutdown signal..."
  75. if [ ! -f "$QEMU_PID" ]; then
  76. echo && error "QEMU PID file does not exist?"
  77. finish "$code" && return "$code"
  78. fi
  79. local pid=""
  80. pid=$(<"$QEMU_PID")
  81. if ! isAlive "$pid"; then
  82. echo && error "QEMU process does not exist?"
  83. finish "$code" && return "$code"
  84. fi
  85. # Send ACPI shutdown signal
  86. echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
  87. local cnt=0
  88. while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do
  89. sleep 1
  90. cnt=$((cnt+1))
  91. ! isAlive "$pid" && break
  92. # Workaround for zombie pid
  93. [ ! -f "$QEMU_PID" ] && break
  94. info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)"
  95. # Send ACPI shutdown signal
  96. echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null
  97. done
  98. if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then
  99. echo && error "Shutdown timeout reached, aborting..."
  100. fi
  101. finish "$code" && return "$code"
  102. }
  103. SERIAL="pty"
  104. MONITOR="telnet:localhost:$QEMU_PORT,server,nowait,nodelay"
  105. MONITOR="$MONITOR -daemonize -D $QEMU_LOG -pidfile $QEMU_PID"
  106. _trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT
  107. return 0