docs/hooks.rst | 14 +++++++++++--- src/qemu/qemu_process.c | 17 +++++++++++++++++ src/util/virhook.c | 1 + src/util/virhook.h | 1 + 4 files changed, 30 insertions(+), 3 deletions(-)
Introduce a new QEMU hook operation "stop" that is called before a
domain is terminated (via virsh shutdown or virsh destroy). This allows
external scripts to perform cleanup or veto the stop process by
returning a non-zero exit code. The hook is called as:
/etc/libvirt/hooks/qemu <guest_name> stop begin -
The full domain XML is provided on stdin.
Example qemu hook that protects domains from being stopped (to be
dropped in /etc/libfirt/hooks/qemu.d/10-vm-protection or similar):
```bash
#!/bin/bash
# Protection hook - vetos domain stops for domains listed in
# /etc/libvirt/protected-domains
set -euo pipefail
readonly PROTECT_DIR="/etc/libvirt"
readonly DOMAIN_NAME="${1:-}"
readonly OPERATION="${2:-}"
# Only act on stop operation
if [[ "$OPERATION" != "stop" ]]; then
exit 0
fi
readonly PROTECT_LIST="${PROTECT_DIR}/protected-domains"
if [[ -f "$PROTECT_LIST" ]]; then
# Read file, strip comments and empty lines, check for exact domain match
if grep -Fxq -- "$DOMAIN_NAME" <(sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' "$PROTECT_LIST"); then
logger -t libvirt-hook-test "20-protection: BLOCKING stop for protected domain '$DOMAIN_NAME'"
echo "$(date '+%Y-%m-%d %H:%M:%S'): BLOCKED stop for '$DOMAIN_NAME'" \
>> /tmp/libvirt-test-logs/protection.log
printf 'vm-protection: stop blocked for %s\n' "$DOMAIN_NAME" >&2
exit 1
fi
fi
exit 0
```
With a list of domains to protect in /etc/libvirt/protected-domains.
With the above hook in place, attempting to destroy a domain listed in
/etc/libvirt/protected-domains will fail:
```
> virsh list --all
Id Name State
-------------------------------
2 protected-vm running
- test-vm shut off
> virsh destroy protected-vm
error: Failed to destroy domain 'protected-vm'
error: Hook script execution failed: internal error: Child process (LC_ALL=C LD_LIBRARY_PATH=/opt/libvirt-test/lib/x86_64-linux-gnu:/opt/libvirt-test/lib PATH=/usr/local/sbin:/usr/local/bin:/usr/bin HOME=/root USER=root LOGNAME=root /opt/libvirt-test/etc/libvirt/hooks/qemu.d/20-protection protected-vm stop begin -) unexpected exit status 1: vm-protection: stop blocked for protected-vm
> virsh list --all
Id Name State
-------------------------------
2 protected-vm running
- test-vm shut off
```
Signed-off-by: Mitchel Humpherys <mitch.special@gmail.com>
---
docs/hooks.rst | 14 +++++++++++---
src/qemu/qemu_process.c | 17 +++++++++++++++++
src/util/virhook.c | 1 +
src/util/virhook.h | 1 +
4 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/docs/hooks.rst b/docs/hooks.rst
index e1745b8cc7..5ae03a36cd 100644
--- a/docs/hooks.rst
+++ b/docs/hooks.rst
@@ -202,9 +202,17 @@ operation. There is no specific operation to indicate a "restart" is occurring.
/etc/libvirt/hooks/qemu guest_name started begin -
-- When a QEMU guest is stopped, the qemu hook script is called in two
- locations, to match the startup. First, :since:`since 0.8.0`, the hook is
- called before libvirt restores any labels:
+- When a QEMU guest is stopped, the qemu hook script is called in three
+ locations, to match the startup. The first location, :since:`since 11.10.0`,
+ is called before the domain is stopped. This allows the hook to perform
+ cleanup tasks or veto the stop operation by returning a non-zero exit code:
+
+ ::
+
+ /etc/libvirt/hooks/qemu guest_name stop begin -
+
+ The second location, :since:`since 0.8.0`, is called after the QEMU process
+ has terminated but before libvirt restores any labels:
::
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 0e50cd1ccc..fa2b691d99 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -9015,6 +9015,23 @@ qemuProcessBeginStopJob(virDomainObj *vm,
qemuDomainObjPrivate *priv = vm->privateData;
unsigned int killFlags = forceKill ? VIR_QEMU_PROCESS_KILL_FORCE : 0;
+ /* call stop hook if present */
+ if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
+ virQEMUDriver *driver = priv->driver;
+ g_autofree char *xml = qemuDomainDefFormatXML(driver, NULL, vm->def, 0);
+ int hookret;
+
+ if (!xml)
+ return -1;
+
+ hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name,
+ VIR_HOOK_QEMU_OP_STOP, VIR_HOOK_SUBOP_BEGIN,
+ NULL, xml, NULL);
+
+ if (hookret < 0)
+ return -1;
+ }
+
/* We need to prevent monitor EOF callback from doing our work (and
* sending misleading events) while the vm is unlocked inside
* BeginJob/ProcessKill API or any other code path before 'vm->def->id' is
diff --git a/src/util/virhook.c b/src/util/virhook.c
index d012bb1825..01ba17e406 100644
--- a/src/util/virhook.c
+++ b/src/util/virhook.c
@@ -76,6 +76,7 @@ VIR_ENUM_IMPL(virHookSubop,
VIR_ENUM_IMPL(virHookQemuOp,
VIR_HOOK_QEMU_OP_LAST,
"start",
+ "stop",
"stopped",
"prepare",
"release",
diff --git a/src/util/virhook.h b/src/util/virhook.h
index d8237c837e..ea8c540c3f 100644
--- a/src/util/virhook.h
+++ b/src/util/virhook.h
@@ -52,6 +52,7 @@ typedef enum {
typedef enum {
VIR_HOOK_QEMU_OP_START, /* domain is about to start */
+ VIR_HOOK_QEMU_OP_STOP, /* domain is about to stop */
VIR_HOOK_QEMU_OP_STOPPED, /* domain has stopped */
VIR_HOOK_QEMU_OP_PREPARE, /* domain startup initiated */
VIR_HOOK_QEMU_OP_RELEASE, /* domain destruction is over */
--
2.52.0
On Sun, Dec 28, 2025 at 02:38:38PM -0800, Mitchel Humpherys wrote: > Introduce a new QEMU hook operation "stop" that is called before a > domain is terminated (via virsh shutdown or virsh destroy). This allows > external scripts to perform cleanup or veto the stop process by > returning a non-zero exit code. The hook is called as: IMHO this idea is conceptually flawed. We cannot guarantee to veto a stop process because the guest OS itself can shutdown and/or crash at any time, and libvirt can't prevent it doing that. So this hook is only able to veto a shutdown initiated by a host admin. At that point this looks rather like an access control mechanism, and we already have that available with our API access control system. So IMHO only the existing "stopped" hook makes conceptual sense. With regards, Daniel -- |: https://berrange.com -o- https://www.flickr.com/photos/dberrange :| |: https://libvirt.org -o- https://fstop138.berrange.com :| |: https://entangle-photo.org -o- https://www.instagram.com/dberrange :|
© 2016 - 2026 Red Hat, Inc.