[LTP] [PATCH v4 1/1] syscalls/statx13: Add basic test for STATX_WRITE_ATOMIC on regular file

Andrea Cervesato andrea.cervesato@suse.com
Mon Oct 12 14:43:28 CEST 2026


Hi Gaurav,

> From: Gaurav Pathak <gaurav.pathak@suse.com>
> 
> This patch adds a new test to validate these atomic write limit fields.
> The test ensures the filesystem correctly advertises the STATX_ATTR_WRITE_ATOMIC
> attribute when queried on a file opened with O_DIRECT. It also verifies that the
> reported optimized maximum is logically consistent by falling within the
> absolute minimum and maximum boundaries. Furthermore, it checks that all
> reported atomic write unit sizes are valid powers of two, adhering to the strict
> requirements of the kernel block layer.
> 
> If the underlying storage hardware or filesystem lacks atomic write
> support, the test gracefully skips with TCONF.
> 
> Closes: #1224

we don't add Github tags in the git history, please remove it.

> 
> Signed-off-by: Gaurav Pathak <gaurav.pathak@suse.com>
> ---
>  configure.ac                               |   4 +-
>  include/lapi/stat.h                        |  17 ++-
>  runtest/syscalls                           |   1 +
>  testcases/kernel/syscalls/statx/.gitignore |   1 +
>  testcases/kernel/syscalls/statx/statx13.c  | 139 +++++++++++++++++++++
>  5 files changed, 160 insertions(+), 2 deletions(-)
>  create mode 100644 testcases/kernel/syscalls/statx/statx13.c
> 
> diff --git a/configure.ac b/configure.ac
> index 18bfdb88c..d5a609fa7 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -181,7 +181,9 @@ AC_CHECK_MEMBERS([struct iocb.aio_rw_flags],,,[#include <linux/aio_abi.h>])
>  AC_CHECK_MEMBERS([struct fanotify_event_info_fid.fsid.__val],,,[#include <sys/fanotify.h>])
>  AC_CHECK_MEMBERS([struct perf_event_mmap_page.aux_head],,,[#include <linux/perf_event.h>])
>  AC_CHECK_MEMBERS([struct sigaction.sa_sigaction],[],[],[#include <signal.h>])
> -AC_CHECK_MEMBERS([struct statx.stx_mnt_id, struct statx.stx_dio_mem_align],,,[
> +AC_CHECK_MEMBERS([struct statx.stx_mnt_id, struct statx.stx_dio_mem_align, struct statx.stx_atomic_write_unit_max_opt,
> +				  struct statx.stx_atomic_write_unit_min, struct statx.stx_atomic_write_unit_max,
> +				  struct statx.stx_atomic_write_segments_max],,,[
>  #define _GNU_SOURCE
>  #include <sys/stat.h>
>  ])
> diff --git a/include/lapi/stat.h b/include/lapi/stat.h
> index 17b62ea98..859e410ab 100644
> --- a/include/lapi/stat.h
> +++ b/include/lapi/stat.h
> @@ -93,7 +93,14 @@ struct statx_timestamp {
>  	uint64_t	stx_mnt_id; \
>  	uint32_t	stx_dio_mem_align; \
>  	uint32_t	stx_dio_offset_align; \
> -	uint64_t	__spare3[12]; \
> +	uint64_t	stx_subvol; \
> +	uint32_t	stx_atomic_write_unit_min; \
> +	uint32_t	stx_atomic_write_unit_max; \
> +	uint32_t	stx_atomic_write_segments_max; \
> +	uint32_t	stx_dio_read_offset_align; \
> +	uint32_t	stx_atomic_write_unit_max_opt; \
> +	uint32_t	__spare2[1]; \
> +	uint64_t	__spare3[8]; \
>  };
>  
>  LTP_DEFINE_STATX_STRUCT(statx_fallback);
> @@ -197,6 +204,10 @@ static inline int statx(int dirfd, const char *pathname, unsigned int flags,
>  # define STATX_DIOALIGN		0x00002000U
>  #endif
>  
> +#ifndef STATX_WRITE_ATOMIC
> +#define STATX_WRITE_ATOMIC  0x00010000U
> +#endif
> +
>  #ifndef STATX__RESERVED
>  # define STATX__RESERVED	0x80000000U
>  #endif
> @@ -248,6 +259,10 @@ static inline int statx(int dirfd, const char *pathname, unsigned int flags,
>  # define STATX_MNT_ID_UNIQUE  0x00004000U
>  #endif
>  
> +#ifndef STATX_ATTR_WRITE_ATOMIC
> +#define STATX_ATTR_WRITE_ATOMIC	0x00400000
> +#endif
> +
>  #define SAFE_FCHMODAT2(dfd, filename, mode, flags) \
>  	safe_fchmodat2(__FILE__, __LINE__, (dfd), (filename), (mode), (flags))
>  
> diff --git a/runtest/syscalls b/runtest/syscalls
> index d953f0558..b8c7fbb0f 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -1926,6 +1926,7 @@ statx09 statx09
>  statx10 statx10
>  statx11 statx11
>  statx12 statx12
> +statx13 statx13
>  
>  membarrier01 membarrier01
>  
> diff --git a/testcases/kernel/syscalls/statx/.gitignore b/testcases/kernel/syscalls/statx/.gitignore
> index f6a423eed..e601a46a3 100644
> --- a/testcases/kernel/syscalls/statx/.gitignore
> +++ b/testcases/kernel/syscalls/statx/.gitignore
> @@ -10,3 +10,4 @@
>  /statx10
>  /statx11
>  /statx12
> +/statx13
> diff --git a/testcases/kernel/syscalls/statx/statx13.c b/testcases/kernel/syscalls/statx/statx13.c
> new file mode 100644
> index 000000000..cc445fa57
> --- /dev/null
> +++ b/testcases/kernel/syscalls/statx/statx13.c
> @@ -0,0 +1,139 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2026 SUSE LLC <gaurav.pathak@suse.com>
> + */
> +
> +/*\
> + * This test validates the STATX_WRITE_ATOMIC feature (introduced in Linux 6.13).
> + * It ensures that supported filesystems (xfs and nfs) correctly report their
> + * atomic write limits to user space when queried via statx().
> + *
> + * The test performs the following validations:
> + *
> + * - Creates a test file using O_DIRECT (a prerequisite for atomic writes).
> + * - Calls statx() with the STATX_WRITE_ATOMIC mask to retrieve the limits.
> + * - Verifies that stx_atomic_write_unit_min, stx_atomic_write_unit_max, and
> + *   stx_atomic_write_unit_max_opt are logically consistent (e.g., max_opt is
> + *   within the min and max bounds).
> + * - Ensures all reported atomic write unit sizes are valid powers of two.

This looks more like [Algorithm] section.

> + */
> +
> +#define _GNU_SOURCE
> +#include "tst_test.h"
> +#include "lapi/stat.h"
> +
> +#define MNTPOINT "mnt_point"
> +#define TESTFILE MNTPOINT "/testfile"
> +#define MODE 0644
> +
> +#define WRITE_SIZE 4096
> +#define ALIGNMENT  4096

We need to use the right page size when we use O_DIRECT. Please take a look
at the other tests using O_DIRECT, so we avoid defining a default value for it.

> +
> +static int file_fd = -1;
> +
> +static void verify_statx(void)
> +{
> +	struct ltp_statx buff;
> +
> +	TST_EXP_PASS_SILENT(SAFE_STATX(AT_FDCWD, TESTFILE, 0, STATX_BASIC_STATS | STATX_WRITE_ATOMIC, &buff));

SAFE_STATX() will fail the syscall, so it shadows TST_EXP_PASS_SILENT().
We need to use raw statx() in these cases, because it's our subject of test,
performing a TST_EXP_PASS() check on it.

We really want to know if statx() fails when STATX_WRITE_ATOMIC is passed.

> +
> +	if (!TST_PASS)
> +		return;
> +
> +	if (!(buff.data.stx_attributes & STATX_ATTR_WRITE_ATOMIC))
> +		tst_brk(TCONF, "Filesystem does not support STATX_WRITE_ATOMIC");
> +
> +#if HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MIN
> +	if (buff.data.stx_atomic_write_unit_min <= buff.data.stx_atomic_write_unit_max)
> +		tst_res(TPASS, "stx_atomic_write_unit_min(%u) <= stx_atomic_write_unit_max(%u)",
> +				buff.data.stx_atomic_write_unit_min, buff.data.stx_atomic_write_unit_max);
> +	else
> +		tst_res(TFAIL, "stx_atomic_write_unit_min(%u) > stx_atomic_write_unit_max(%u)",
> +				buff.data.stx_atomic_write_unit_min, buff.data.stx_atomic_write_unit_max);
> +
> +	if (buff.data.stx_atomic_write_unit_min > 0 &&
> +			__builtin_popcount(buff.data.stx_atomic_write_unit_min) == 1)
> +		tst_res(TPASS, "stx_atomic_write_unit_min(%u) is power of 2",
> +				buff.data.stx_atomic_write_unit_min);
> +	else
> +		tst_res(TFAIL, "stx_atomic_write_unit_min(%u) is not a power of 2",
> +				buff.data.stx_atomic_write_unit_min);
> +
> +	if (buff.data.stx_atomic_write_unit_max > 0 &&
> +			__builtin_popcount(buff.data.stx_atomic_write_unit_max) == 1)
> +		tst_res(TPASS, "stx_atomic_write_unit_max(%u) is power of 2",
> +				buff.data.stx_atomic_write_unit_max);
> +	else
> +		tst_res(TFAIL, "stx_atomic_write_unit_max(%u) is not a power of 2",
> +				buff.data.stx_atomic_write_unit_max);
> +#else
> +	tst_res(TCONF, "stx_atomic_write_unit_max or stx_atomic_write_unit_min is not defined in struct statx");
> +#endif

This is not correct. If stx_atomic_write_unit_max is not defined, we should
fallback that into the LTP fallback headers and _before_ even starting the
test, check if statx() supports it. If it doesn't then we can TCONF.

> +
> +#if HAVE_STRUCT_STATX_STX_ATOMIC_WRITE_UNIT_MAX_OPT
> +	if (buff.data.stx_atomic_write_unit_max_opt == 0) {
> +		tst_res(TPASS, "stx_atomic_write_unit_max_opt is 0 (no optimized max reported)");
> +	} else {
> +		if (buff.data.stx_atomic_write_unit_max_opt > buff.data.stx_atomic_write_unit_max)
> +			tst_res(TFAIL, "stx_atomic_write_unit_max_opt (%u) exceeds max (%u)",
> +					buff.data.stx_atomic_write_unit_max_opt,
> +					buff.data.stx_atomic_write_unit_max);
> +
> +		else if (buff.data.stx_atomic_write_unit_max_opt < buff.data.stx_atomic_write_unit_min)
> +			tst_res(TFAIL, "stx_atomic_write_unit_max_opt (%u) is less than min (%u)",
> +					buff.data.stx_atomic_write_unit_max_opt,
> +					buff.data.stx_atomic_write_unit_min);
> +		else
> +			tst_res(TPASS, "stx_atomic_write_unit_max_opt (%u) is within valid range [%u, %u]",
> +					buff.data.stx_atomic_write_unit_max_opt,
> +					buff.data.stx_atomic_write_unit_min,
> +					buff.data.stx_atomic_write_unit_max);
> +
> +		if (__builtin_popcount(buff.data.stx_atomic_write_unit_max_opt) != 1)
> +			tst_res(TFAIL, "stx_atomic_write_unit_max_opt (%u) is not a power of 2",
> +					buff.data.stx_atomic_write_unit_max_opt);
> +	}
> +#else
> +	tst_res(TCONF, "stx_atomic_write_unit_max_opt is not defined in struct statx");
> +#endif

Same mistake here.

> +}
> +
> +static void setup(void)
> +{
> +	char *data_buff = SAFE_MEMALIGN(ALIGNMENT, WRITE_SIZE);
> +
> +	umask(0);

This is not needed.

> +	memset(data_buff, '@', WRITE_SIZE);
> +
> +	file_fd =  SAFE_OPEN(TESTFILE, O_RDWR | O_CREAT | O_DIRECT, MODE);
> +	SAFE_WRITE(SAFE_WRITE_ALL, file_fd, data_buff, WRITE_SIZE);

SAFE_CLOSE() goes here.

> +
> +	free(data_buff);
> +}
> +
> +static void cleanup(void)
> +{
> +	if (file_fd != -1)
> +		SAFE_CLOSE(file_fd);

We can close it right after SAFE_WRITE() because file descriptior is never
in use during the test.

> +}
> +
> +static struct tst_test test = {
> +	.test_all = verify_statx,
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.min_kver = "6.11",
> +	.needs_root = 1,
> +	.mntpoint = MNTPOINT,
> +	.mount_device = 1,
> +	.filesystems = (struct tst_fs[]) {
> +		{
> +			.type = "xfs",
> +			.mkfs_opts = (const char *const []){"-f", "-bsize=16K", NULL},
> +		},
> +		{
> +			.type = "ext4",
> +			.mkfs_opts = (const char *const []){"-O", "bigalloc", "-b", "4096", "-C", "65536", NULL},
> +		},
> +		{}
> +	},
> +};
> -- 
> 2.51.0
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp

--
Andrea Cervesato
SUSE QE Automation Engineer Linux
andrea.cervesato@suse.com


More information about the ltp mailing list