From nobody Thu Jan 8 17:49:53 2026 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Received-SPF: pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) client-ip=8.43.85.245; envelope-from=devel-bounces@lists.libvirt.org; helo=lists.libvirt.org; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of lists.libvirt.org designates 8.43.85.245 as permitted sender) smtp.mailfrom=devel-bounces@lists.libvirt.org; dmarc=fail(p=none dis=none) header.from=gmail.com Return-Path: Received: from lists.libvirt.org (lists.libvirt.org [8.43.85.245]) by mx.zohomail.com with SMTPS id 1767603816757930.1925174941344; Mon, 5 Jan 2026 01:03:36 -0800 (PST) Received: by lists.libvirt.org (Postfix, from userid 993) id 39C3D3FAC9; Mon, 5 Jan 2026 04:03:36 -0500 (EST) Received: from [172.19.199.83] (lists.libvirt.org [8.43.85.245]) by lists.libvirt.org (Postfix) with ESMTP id 3938641B83; Mon, 5 Jan 2026 03:58:03 -0500 (EST) Received: by lists.libvirt.org (Postfix, from userid 993) id 61C153F351; Sun, 28 Dec 2025 17:39:11 -0500 (EST) Received: from mail-pg1-f169.google.com (mail-pg1-f169.google.com [209.85.215.169]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by lists.libvirt.org (Postfix) with ESMTPS id 8DA2A417EB for ; Sun, 28 Dec 2025 17:39:09 -0500 (EST) Received: by mail-pg1-f169.google.com with SMTP id 41be03b00d2f7-c2dc870e194so1985120a12.2 for ; Sun, 28 Dec 2025 14:39:09 -0800 (PST) Received: from localhost (ip68-7-161-56.sd.sd.cox.net. [68.7.161.56]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-1217254d369sm112766106c88.16.2025.12.28.14.39.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 28 Dec 2025 14:39:07 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-26) on lists.libvirt.org X-Spam-Level: X-Spam-Status: No, score=-5.0 required=5.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED,RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED,SPF_PASS autolearn=unavailable autolearn_force=no version=4.0.1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766961548; x=1767566348; darn=lists.libvirt.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=OcJVFHHwd1VlgVvxOSTbewqexNz8N9fUhA1y6yQy6CM=; b=bWbpxId38D8yYIYuIvbgyL4WmXDQbJv3H/0HQfbB3gG6M9yFuwoQI6az8X6e8Fi5rJ K9vKcltmeoedii4YUot/564ErFPii7UveA3JqYB4xCrD+8bA55Cf8lPd2ZO+pz/0HPEl FAjVvC4mAO059xmLR1pIk+FQVyWwjCBVFvO7IfGNDgYPXVA9eOTQGwOT+uZ96PX2eanF GtVfrhMDJ31BbDAF8Yr8Qzb5eEQXiwykcJFdhh6BkhouiGEX11zE28St0MzPgk6ve2K5 a2n8tjCkeOQJhARkmFWLec9vGWkUqf/rsKK6bxahEsvuNvNNaU+HZsvarykP87PYYb6k 9CIQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766961548; x=1767566348; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=OcJVFHHwd1VlgVvxOSTbewqexNz8N9fUhA1y6yQy6CM=; b=V79+znR2PRd8r4AD3zRimxCMpXfNUCrpYUgd4QGXwQ5fMsjHtRF8O/vmExjTFEeXTu 2262H4WL1MqfEp9W6oUIwo6IY1U9NaiF18zgZmeMIYxFKAHX9flNZhw1FkxyzoKxi4dA kDt4XzpusLs35r4+mcIsovMBaW5mAFxmGI9i/kZfDUdzYx1VbWQDaW7oV89HrdOzDxLK 3LNC2I8wBn2QNK9w8tjosEseqenI7Ndunti/UmVJJPYydXlSXG9f8xTvqMnhFytfxeF+ NaecjVEAKk4GrEHm5YzFtYx8/bEHiv/iz3V0MdnqgoftmO0ebs1MhRoyBBOkQBXnWBSJ BnUQ== X-Gm-Message-State: AOJu0YwWoItin/ttRNQceP24ifu/Fuw2ZC3lTyQND6iYuPUS82CAFBlb feZXPISkGCd/eP3p2uHVrrIUrGMha5UALe50e679HMvZXdE089nSwTZVuluVRDVq X-Gm-Gg: AY/fxX709RtK4j8YKqGdkincv9MthSSnCnhUnXiunjCC8rauArUKJtba9vHLZx8FwQ8 CNuTi++vfWlhVCYI+hhuvZ9Ii2JRddAwK7fYNN75VYO+NpcOy6HX9tvDy0EJIJQp54ezIJQ0M13 jLHtHQyT/ny28474lb3H/gDHXEJOpRnrCsbebMs1Hb6mEA4cBbSd8oZGWatS2AlzSoOs2wNWmgG 2QQJWRzXk4VpIjhN6h6U5BR66ciiToRLw6LFqEsEejiPuUHdbJ1JBe2xMiY/zUMtjFpFm7jFn0y /X9LPeWtLGb2L9sBBrix87mI6XxWZ2POv/beymM3k09X2/0tZVKDZRmmL5ouHxCfI6QhBW7h7lJ XkTTptpkuedJnOgTSCXK2qAiFI6APqoxd3ASYntjaxw7WraIxL8u2EFPWHXetEJwXkLz9oGOai2 YNQUzX5+PSO+zi+ueGi5BmMmqXHMaVsNc+duD1EQ== X-Google-Smtp-Source: AGHT+IEEwUfnYBE3N6aKNHBZMJjussYLvEeV53okawj+SPpjNr+2GN42g+vG27SfVOckqjWfICFtfQ== X-Received: by 2002:a05:7022:4425:b0:11d:f0d3:c5da with SMTP id a92af1059eb24-121722faaf3mr28956712c88.43.1766961548041; Sun, 28 Dec 2025 14:39:08 -0800 (PST) From: Mitchel Humpherys To: devel@lists.libvirt.org Subject: [PATCH] qemu: Add stop hook support for domain shutdown Date: Sun, 28 Dec 2025 14:38:38 -0800 Message-ID: <20251228223838.546249-1-mitch.special@gmail.com> X-Mailer: git-send-email 2.52.0 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-MailFrom: mitch.special@gmail.com X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-devel.lists.libvirt.org-0; emergency; member-moderation Message-ID-Hash: HO3QXA6QFOWQABIHUNNUP45J5Q647RUI X-Message-ID-Hash: HO3QXA6QFOWQABIHUNNUP45J5Q647RUI X-Mailman-Approved-At: Mon, 05 Jan 2026 08:57:34 +0000 CC: mitch.special@gmail.com, pkrempa@redhat.com, jdenemar@redhat.com X-Mailman-Version: 3.3.10 Precedence: list List-Id: Development discussions about the libvirt library & tools Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-ZohoMail-DKIM: fail (Invalid signature record: Signature is expired since 10h.) X-ZM-MESSAGEID: 1767603818856154100 Content-Type: text/plain; charset="utf-8" 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 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=3D"/etc/libvirt" readonly DOMAIN_NAME=3D"${1:-}" readonly OPERATION=3D"${2:-}" # Only act on stop operation if [[ "$OPERATION" !=3D "stop" ]]; then exit 0 fi readonly PROTECT_LIST=3D"${PROTECT_DIR}/protected-domains" if [[ -f "$PROTECT_LIST" ]]; then # Read file, strip comments and empty lines, check for exact domain mat= ch if grep -Fxq -- "$DOMAIN_NAME" <(sed -e 's/#.*$//' -e '/^[[:space:]]*$/= d' "$PROTECT_LIST"); then logger -t libvirt-hook-test "20-protection: BLOCKING stop for prote= cted 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= =3DC LD_LIBRARY_PATH=3D/opt/libvirt-test/lib/x86_64-linux-gnu:/opt/libvirt-= test/lib PATH=3D/usr/local/sbin:/usr/local/bin:/usr/bin HOME=3D/root USER= =3Droot LOGNAME=3Droot /opt/libvirt-test/etc/libvirt/hooks/qemu.d/20-protec= tion protected-vm stop begin -) unexpected exit status 1: vm-protection: st= op blocked for protected-vm > virsh list --all Id Name State ------------------------------- 2 protected-vm running - test-vm shut off ``` Signed-off-by: Mitchel Humpherys --- 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. =20 /etc/libvirt/hooks/qemu guest_name started begin - =20 -- 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.1= 0.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 c= ode: + + :: + + /etc/libvirt/hooks/qemu guest_name stop begin - + + The second location, :since:`since 0.8.0`, is called after the QEMU pro= cess + has terminated but before libvirt restores any labels: =20 :: =20 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 =3D vm->privateData; unsigned int killFlags =3D forceKill ? VIR_QEMU_PROCESS_KILL_FORCE : 0; =20 + /* call stop hook if present */ + if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) { + virQEMUDriver *driver =3D priv->driver; + g_autofree char *xml =3D qemuDomainDefFormatXML(driver, NULL, vm->= def, 0); + int hookret; + + if (!xml) + return -1; + + hookret =3D 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 { =20 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 */ --=20 2.52.0