[PATCH 04/14] xfs: set up common code for testing xfs_healer

Darrick J. Wong
2026-03-10T03:51:11Z

From: Darrick J. Wong <djwong@kernel.org>

Add a bunch of common code so that we can test the xfs_healer daemon.
Most of the changes here are to make it easier to manage the systemd
service units for xfs_healer and xfs_scrub.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
common/config | 14 ++++++++
common/rc | 5 +++
common/systemd | 39 ++++++++++++++++++++++
common/xfs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tests/xfs/802 | 4 +-
5 files changed, 158 insertions(+), 2 deletions(-)

diff --git a/common/config b/common/config
index 1420e35ddfee42..8468a60081f50c 100644
--- a/common/config
+++ b/common/config
@@ -161,6 +161,20 @@ export XFS_ADMIN_PROG="$(type -P xfs_admin)"
export XFS_GROWFS_PROG=$(type -P xfs_growfs)
export XFS_SPACEMAN_PROG="$(type -P xfs_spaceman)"
export XFS_SCRUB_PROG="$(type -P xfs_scrub)"
+
+XFS_HEALER_PROG="$(type -P xfs_healer)"
+XFS_HEALER_START_PROG="$(type -P xfs_healer_start)"
+
+# If not found, try the ones installed in libexec
+if [ ! -x "$XFS_HEALER_PROG" ] && [ -e /usr/libexec/xfsprogs/xfs_healer ]; then
+ XFS_HEALER_PROG=/usr/libexec/xfsprogs/xfs_healer
+fi
+if [ ! -x "$XFS_HEALER_START_PROG" ] && [ -e /usr/libexec/xfsprogs/xfs_healer_start ]; then
+ XFS_HEALER_START_PROG=/usr/libexec/xfsprogs/xfs_healer_start
+fi
+export XFS_HEALER_PROG
+export XFS_HEALER_START_PROG
+
export XFS_PARALLEL_REPAIR_PROG="$(type -P xfs_prepair)"
export XFS_PARALLEL_REPAIR64_PROG="$(type -P xfs_prepair64)"
export __XFSDUMP_PROG="$(type -P xfsdump)"
diff --git a/common/rc b/common/rc
index ccb78baf5bd41a..0b740595d231b5 100644
--- a/common/rc
+++ b/common/rc
@@ -3021,6 +3021,11 @@ _require_xfs_io_command()
"label")
testio=`$XFS_IO_PROG -c "label" $TEST_DIR 2>&1`
;;
+ "verifymedia")
+ testio=`$XFS_IO_PROG -x -c "verifymedia $* 0 0" 2>&1`
+ echo $testio | grep -q "invalid option" && \
+ _notrun "xfs_io $command support is missing"
+ ;;
"open")
# -c "open $f" is broken in xfs_io <= 4.8. Along with the fix,
# a new -C flag was introduced to execute one shot commands.
diff --git a/common/systemd b/common/systemd
index b2e24f267b2d93..589aad1bef2637 100644
--- a/common/systemd
+++ b/common/systemd
@@ -44,6 +44,18 @@ _systemd_unit_active() {
test "$(systemctl is-active "$1")" = "active"
}

+# Wait for up to a certain number of seconds for a service to reach inactive
+# state.
+_systemd_unit_wait() {
+ local svcname="$1"
+ local timeout="${2:-30}"
+
+ for ((i = 0; i < (timeout * 2); i++)); do
+ test "$(systemctl is-active "$svcname")" = "inactive" && break
+ sleep 0.5
+ done
+}
+
_require_systemd_unit_active() {
_require_systemd_unit_defined "$1"
_systemd_unit_active "$1" || \
@@ -71,3 +83,30 @@ _systemd_unit_status() {
_systemd_installed || return 1
systemctl status "$1"
}
+
+# Start a running systemd unit
+_systemd_unit_start() {
+ systemctl start "$1"
+}
+# Stop a running systemd unit
+_systemd_unit_stop() {
+ systemctl stop "$1"
+}
+
+# Mask or unmask a running systemd unit
+_systemd_unit_mask() {
+ systemctl mask "$1"
+}
+_systemd_unit_unmask() {
+ systemctl unmask "$1"
+}
+_systemd_unit_masked() {
+ systemctl status "$1" 2>/dev/null | grep -q 'Loaded: masked'
+}
+
+_systemd_service_unit_path() {
+ local template="$1"
+ local path="$2"
+
+ systemd-escape --template "$template" --path "$path"
+}
diff --git a/common/xfs b/common/xfs
index 7fa0db2e26b4c9..f276325df8fbac 100644
--- a/common/xfs
+++ b/common/xfs
@@ -2301,3 +2301,101 @@ _filter_bmap_gno()
if ($ag =~ /\d+/) {print "$ag "} ;
'
}
+
+# Run the xfs_healer program on some filesystem
+_xfs_healer() {
+ $XFS_HEALER_PROG "$@"
+}
+
+# Compute the xfs_healer systemd service instance name for a given path.
+# This is easy because xfs_healer has always supported --svcname.
+_xfs_healer_svcname()
+{
+ _xfs_healer --svcname "$@"
+}
+
+# Compute the xfs_scrub systemd service instance name for a given path. This
+# is tricky because xfs_scrub only gained --svcname when xfs_healer was made.
+_xfs_scrub_svcname()
+{
+ local ret
+
+ if ret="$($XFS_SCRUB_PROG --svcname "$@")"; then
+ echo "$ret"
+ return 0
+ fi
+
+ # ...but if not, we can fall back to brute force systemd invocations.
+ _systemd_service_unit_path "xfs_scrub@.service" "$*"
+}
+
+# Run the xfs_healer program on the scratch fs
+_scratch_xfs_healer() {
+ _xfs_healer "$@" "$SCRATCH_MNT"
+}
+
+# Turn off the background xfs_healer service if any so that it doesn't fix
+# injected metadata errors; then start a background copy of xfs_healer to
+# capture that.
+_invoke_xfs_healer() {
+ local mount="$1"
+ local logfile="$2"
+ shift; shift
+
+ if _systemd_is_running; then
+ local svc="$(_xfs_healer_svcname "$mount")"
+ _systemd_unit_stop "$svc" &>> $seqres.full
+ fi
+
+ $XFS_HEALER_PROG "$mount" "$@" &> "$logfile" &
+ XFS_HEALER_PID=$!
+
+ # Wait 30s for the healer program to really start up
+ for ((i = 0; i < 60; i++)); do
+ test -e "$logfile" && \
+ grep -q 'monitoring started' "$logfile" && \
+ break
+ sleep 0.5
+ done
+}
+
+# Run our own copy of xfs_healer against the scratch device. Note that
+# unmounting the scratch fs causes the healer daemon to exit, so we don't need
+# to kill it explicitly from _cleanup.
+_scratch_invoke_xfs_healer() {
+ _invoke_xfs_healer "$SCRATCH_MNT" "$@"
+}
+
+# Unmount the filesystem to kill the xfs_healer instance started by
+# _invoke_xfs_healer, and wait up to a certain amount of time for it to exit.
+_kill_xfs_healer() {
+ local unmount="$1"
+ local timeout="${2:-30}"
+ local i
+
+ # Unmount fs to kill healer, then wait for it to finish
+ for ((i = 0; i < (timeout * 2); i++)); do
+ $unmount &>> $seqres.full && break
+ sleep 0.5
+ done
+
+ test -n "$XFS_HEALER_PID" && \
+ kill $XFS_HEALER_PID &>> $seqres.full
+ wait
+ unset XFS_HEALER_PID
+}
+
+# Unmount the scratch fs to kill a _scratch_invoke_xfs_healer instance.
+_scratch_kill_xfs_healer() {
+ local unmount="${1:-_scratch_unmount}"
+ shift
+
+ _kill_xfs_healer "$unmount" "$@"
+}
+
+# Does this mounted filesystem support xfs_healer?
+_require_xfs_healer()
+{
+ _xfs_healer --supported "$@" &>/dev/null || \
+ _notrun "health monitoring not supported on this kernel"
+}
diff --git a/tests/xfs/802 b/tests/xfs/802
index fc4767acb66a55..18312b15b645bd 100755
--- a/tests/xfs/802
+++ b/tests/xfs/802
@@ -105,8 +105,8 @@ run_scrub_service() {
}

echo "Scrub Scratch FS"
-scratch_path=$(systemd-escape --path "$SCRATCH_MNT")
-run_scrub_service xfs_scrub@$scratch_path
+svc="$(_xfs_scrub_svcname "$SCRATCH_MNT")"
+run_scrub_service "$svc"
find_scrub_trace "$SCRATCH_MNT"

# Remove the xfs_scrub_all media scan stamp directory (if specified) because we