feat(qemu-ga): failure-propagating fsfreeze dispatcher; enabled on nh3-svos (hooks were never run)

This commit is contained in:
vh
2026-10-05 19:25:14 -07:00
parent 36289f701a
commit 9175fbd539
2 changed files with 67 additions and 1 deletions
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# fsfreeze-dispatch: qemu-ga fsfreeze hook dispatcher that PROPAGATES failure (eshpfi configs/qemu-ga/, 2026-10-05).
#
# Debian's stock /etc/qemu/fsfreeze-hook logs a child hook's nonzero status but always exits 0, so a hook that
# refuses to quiesce still lets the snapshot proceed (found by svos-dev on nh3-svos). This one does not:
# freeze: run hooks in name order; on the first failure, thaw the FAILING hook and then every hook that had
# completed, in reverse order (best effort), log any unwind failure, and exit 1.
# thaw: run every hook in reverse name order, exit 1 if any failed (never stop early).
# Wired in by /etc/qemu/qemu-ga.conf `fsfreeze-hook=/usr/local/sbin/fsfreeze-dispatch`. The packaged
# /etc/qemu/fsfreeze-hook (a dpkg conffile) is left untouched. Same hook dir and log file as the stock hook.
set -u
HOOKDIR=/etc/qemu/fsfreeze-hook.d
LOGFILE=/var/log/qga-fsfreeze-hook.log
action=${1:-}
log() { printf '%s: %s\n' "$(date)" "$1" >>"$LOGFILE" 2>/dev/null || logger -t qemu-ga-freeze-hook "$1"; }
ignored() {
case "$1" in
*~ | *.bak | *.orig | *.rpmnew | *.rpmorig | *.rpmsave | *.sample | *.dpkg-old | *.dpkg-new | *.dpkg-tmp | \
*.dpkg-dist | *.dpkg-bak | *.dpkg-backup | *.dpkg-remove) return 0 ;;
esac
return 1
}
hooks=()
if [ -d "$HOOKDIR" ]; then
while IFS= read -r -d '' f; do
ignored "$f" && continue
[ -f "$f" ] && [ -x "$f" ] && hooks+=("$f")
done < <(find "$HOOKDIR" -mindepth 1 -maxdepth 1 -print0 | LC_ALL=C sort -z)
fi
run() { # $1 hook, $2 action
log "Executing $1 $2"
"$1" "$2" >>"$LOGFILE" 2>&1
local st=$?
[ "$st" -ne 0 ] && log "Error: $1 $2 finished with status=$st"
return "$st"
}
case "$action" in
freeze)
completed=()
for h in "${hooks[@]}"; do
if run "$h" freeze; then
completed+=("$h")
else
log "freeze FAILED at $h: unwinding (thaw $h, then ${#completed[@]} completed hook(s) in reverse)"
unwind_failed=0
run "$h" thaw || unwind_failed=1
for ((i = ${#completed[@]} - 1; i >= 0; i--)); do run "${completed[$i]}" thaw || unwind_failed=1; done
[ "$unwind_failed" -ne 0 ] && log "UNWIND FAILURE: at least one thaw during the freeze unwind failed"
exit 1
fi
done
exit 0 ;;
thaw)
rc=0
for ((i = ${#hooks[@]} - 1; i >= 0; i--)); do run "${hooks[$i]}" thaw || rc=1; done
[ "$rc" -ne 0 ] && log "thaw: at least one hook failed"
exit "$rc" ;;
*)
log "fsfreeze-dispatch: unknown action '$action'"
exit 1 ;;
esac