[PATCH] xfs/842: test close() deadlocks on frozen filesystems
Aditya Srivastava <[email protected]>
| Newsgroups | org.kernel.vger.fstests,org.kernel.vger.linux-xfs |
|---|---|
| Message-ID | <[email protected]> |
From: Aditya Prakash Srivastava <[email protected]> When a file with active speculative post-EOF preallocations is closed, XFS synchronously attempts to free them via xfs_free_eofblocks(). This requires allocating a write transaction, which blocks indefinitely if the filesystem is frozen, causing close() to hang. Add a regression test using a helper C program to verify that close() returns cleanly (with -EAGAIN if using XFS_TRANS_WRITECOUNT_TRYLOCK) without blocking the close system call. This is a regression test for the corresponding kernel patch: "xfs: prevent close() from hanging on frozen filesystems" On a successfully patched kernel, the close system call returns cleanly and the test helper outputs 'close() completed immediately!', matching the golden output. On an unpatched kernel, the close system call blocks, the helper times out and outputs 'close() hung under freeze!', cleanly flagging the deadlock to fstests. Link: https://bugzilla.kernel.org/show_bug.cgi?id=205833 Link: https://bugzilla.redhat.com/show_bug.cgi?id=1474726 Signed-off-by: Aditya Prakash Srivastava <[email protected]> --- src/Makefile | 1 + src/xfs_freeze_close_repro.c | 127 +++++++++++++++++++++++++++++++++++ tests/xfs/842 | 32 +++++++++ tests/xfs/842.out | 2 + 4 files changed, 162 insertions(+) create mode 100644 src/xfs_freeze_close_repro.c create mode 100755 tests/xfs/842 create mode 100644 tests/xfs/842.out diff --git a/src/Makefile b/src/Makefile index 31ac43b2..9553e7c9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -68,6 +68,7 @@ LCFLAGS += -DHAVE_FILE_GETATTR endif ifeq ($(PKG_PLATFORM),linux) +LINUX_TARGETS += xfs_freeze_close_repro TARGETS += $(LINUX_TARGETS) endif diff --git a/src/xfs_freeze_close_repro.c b/src/xfs_freeze_close_repro.c new file mode 100644 index 00000000..075f2d18 --- /dev/null +++ b/src/xfs_freeze_close_repro.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2026 Aditya Prakash Srivastava. All Rights Reserved. + * + * xfstests helper to reproduce close() hang on frozen XFS filesystems. + */ +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <pthread.h> +#include <sys/ioctl.h> +#include <sys/vfs.h> +#include <linux/fs.h> +#include <libgen.h> + +int close_started; +int close_completed; + +void * +close_thread(void *arg) +{ + int fd = *(int *)arg; + + close_started = 1; + close(fd); + close_completed = 1; + return NULL; +} + +int +main(int argc, char *argv[]) +{ + struct statfs sfs; + char *dir_buf; + char *parent_dir; + int freeze_fd; + int write_fd; + char buf[65536]; + pthread_t thread; + int hung = 1; + int i; + + if (argc < 2) { + fprintf(stderr, "Usage: %s <file_on_xfs>\n", argv[0]); + return 1; + } + + if (statfs(argv[1], &sfs) < 0) { + dir_buf = strdup(argv[1]); + parent_dir = dirname(dir_buf); + if (statfs(parent_dir, &sfs) < 0) { + perror("statfs"); + free(dir_buf); + return 1; + } + free(dir_buf); + } + if (sfs.f_type != 0x58465342) { + fprintf(stderr, "Not on XFS\n"); + return 1; + } + + dir_buf = strdup(argv[1]); + freeze_fd = open(dirname(dir_buf), O_RDONLY); + if (freeze_fd < 0) { + perror("open dir"); + free(dir_buf); + return 1; + } + free(dir_buf); + + write_fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (write_fd < 0) { + perror("open file"); + close(freeze_fd); + return 1; + } + + memset(buf, 'A', sizeof(buf)); + for (i = 0; i < 320; i++) { + if (write(write_fd, buf, sizeof(buf)) < 0) { + perror("write"); + close(write_fd); + close(freeze_fd); + return 1; + } + } + + if (ioctl(freeze_fd, FIFREEZE, 0) < 0) { + perror("ioctl FIFREEZE"); + close(write_fd); + close(freeze_fd); + return 1; + } + + if (pthread_create(&thread, NULL, close_thread, &write_fd) != 0) { + perror("pthread_create"); + ioctl(freeze_fd, FITHAW, 0); + close(write_fd); + close(freeze_fd); + return 1; + } + + while (!close_started) + usleep(1000); + + for (i = 0; i < 30; i++) { + usleep(100000); + if (close_completed) { + hung = 0; + break; + } + } + + if (hung) + printf("close() hung under freeze!\n"); + else + printf("close() completed immediately!\n"); + + ioctl(freeze_fd, FITHAW, 0); + pthread_join(thread, NULL); + close(freeze_fd); + return hung ? 0 : 1; +} diff --git a/tests/xfs/842 b/tests/xfs/842 new file mode 100755 index 00000000..8b225ea2 --- /dev/null +++ b/tests/xfs/842 @@ -0,0 +1,32 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Aditya Prakash Srivastava. All Rights Reserved. +# +# FS QA Test No. 842 +# +# Verify that closing a file with active speculative post-EOF preallocations +# on a frozen XFS filesystem does not deadlock inside close(). +# +. ./common/preamble +_begin_fstest auto quick freeze + +# Import helper functions +. ./common/rc +. ./common/filter + +# Real-world configs +_supported_fs xfs +_require_scratch +_require_test_program "xfs_freeze_close_repro" + +_scratch_mkfs >> $seqres.full 2>&1 +_scratch_mount + +TEST_FILE=$SCRATCH_MNT/test_eofblocks_freeze + +# Execute the reproducer helper on scratch mount +$here/src/xfs_freeze_close_repro $TEST_FILE + +status=$? +_scratch_unmount +_exit diff --git a/tests/xfs/842.out b/tests/xfs/842.out new file mode 100644 index 00000000..386fb9cc --- /dev/null +++ b/tests/xfs/842.out @@ -0,0 +1,2 @@ +QA output created by 842 +close() completed immediately! -- 2.47.3