#!/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