fsfreeze-hook is not POSIX or Dash compatible causing weird behavior on (at least) Debian
<!--This is the upstream QEMU issue tracker. If you are able to, it will greatly facilitate bug triage if you attempt to reproduce the problem with the latest qemu.git master built from source. See https://www.qemu.org/download/#source for instructions on how to do this. QEMU generally supports the last two releases advertised on https://www.qemu.org/. Problems with distro-packaged versions of QEMU older than this should be reported to the distribution instead. See https://www.qemu.org/contribute/report-a-bug/ for additional guidance. If this is a security issue, please consult https://www.qemu.org/contribute/security-process/--> ## Host environment - Operating system: Debian 12.13 - OS/kernel version: 6.1.0-43-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.162-1 (2026-02-08) x86_64 GNU/Linux - Architecture: x86_64 - QEMU flavor: <!--qemu-system-x86_64, qemu-aarch64, qemu-img, etc.--> - QEMU version: 7.2.22 (Debian 1:7.2+dfsg-7+deb12u18) - QEMU command line: `virsh domfsfreeze $ID` ## Emulated/Virtualized environment - Operating system: Debian forky/sid - OS/kernel version: 6.18.15+deb14-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.18.15-1 (2026-02-27) x86_64 GNU/Linux - Architecture: x86_64 - qemu-guest-agent: 1:10.2.1+ds-1 amd64 ## Description of problem Using the default qemu-guest-agent package on Debian forky (trixie should have the same issues from looking at the source package) and configuring an fsfreeze-hook leads to the following error on the first run of `virsh domfsfreeze`: ``` error: Unable to freeze filesystems error: internal error: unable to execute QEMU agent command 'guest-fsfreeze-freeze': child process has failed to execute fsfreeze hook: /etc/qemu/fsfreeze-hook: 52: Bad substitution ``` Every run after that is "fine", because `/var/log/qga-fsfreeze-hook.log` was touched and exists - which causes the Dash-incompatible behavior (in particular the erroring line 52 `STATUS=${PIPESTATUS[0]}` ) to be skipped as `USE_SYSLOG` is never set to 1. (Aside, the `USE_SYSLOG` logic isn't particularly great on systems that default to Systemd/journald.) On the guest, after first configuring qemu-ga.conf as follows to enable fsfreeze-hook: ``` [general] fsfreeze-hook=/etc/qemu/fsfreeze-hook ``` Adding a simple test hook script to cause execution of the loop in line 42: ```bash cat <<'EOF' > /etc/qemu/fsfreeze-hook.d/test.sh #!/bin/bash echo "$@" EOF chmod +x /etc/qemu/fsfreeze-hook.d/test.sh ``` (Ensuring that the log file doesn't exist: `rm /var/log/qga-fsfreeze-hook.log` ) On the host, executing `virsh domfsfreeze $ID` for the guest VM, then outputs the error above. The issue is that the script uses `#!/bin/sh` which is linked to dash on Debian and does not support arrays/PIPESTATUS. Output of shellcheck shows the issues in details: ``` Line 27: touch "$LOGFILE" &>/dev/null || USE_SYSLOG=1 ^-- SC3020 (warning): In POSIX sh, &> is undefined. Line 31: local message="$1" ^-- SC3043 (warning): In POSIX sh, 'local' is undefined. Line 46: log_message "Executing $file $@" ^-- SC2145 (error): Argument mixes string and array. Use * or separate argument. Line 52: STATUS=${PIPESTATUS[0]} ^-- SC3028 (warning): In POSIX sh, PIPESTATUS is undefined. ^-- SC3054 (warning): In POSIX sh, array references are undefined. Line 55: if [ $STATUS -ne 0 ]; then ^-- SC2086 (info): Double quote to prevent globbing and word splitting. ``` In my opinion the `LOGFILE/USE_SYSLOG` logic can just be cut without loss - logging can be configured in the system. rsyslog can be configured to redirect to a specific file for this case (if necessary qemu-guest-agent could contain this config). journald users likely don't want it in a separate file either, or can use a journalctl filtering service appending to file or override `StandardOutput` of the qemu-guest-agent service if needed. Alternatively, a separate config variable for a logfile could be added instead of a hard-coded path. ## Additional information This issue has been observed by others in the past, e.g.: https://github.com/orgs/OpenCCU/discussions/3539 who have replaced the upstream script with a custom one just using `logger` : https://github.com/OpenCCU/OpenCCU/pull/3594/changes <!--Attach logs, stack traces, screenshots, etc. Compress the files if necessary. If using libvirt, libvirt logs and XML domain information may be relevant.--> <!--The line below ensures that proper tags are added to the issue. Please do not remove it.-->
issue