Skip to contentSkip to conversation

Add more tests for multi fs block atomic writes

PATCH Superseded Ojaswin Mujoo 5 participants 49 messages Sep 19, 2025 – Nov 3, 2025 XFS filesystem

lore
Share & tools
lore
#0 lore raw

v7 cover Add more tests for multi fs block atomic writes

Changes in v7:
- Picked up reviews from John! (Thanks)
- [9/11] Change "integrity test.." -> "torn write test.." for better clarity

Changes in v6:[5]
- Picked up reviews from Darrick, Zorro and John! (Thanks)
- Added _require_fio_atomic_writes helper in patch 3 as a wrapper arounc __require_fio_version
- minor spelling and refactors

[5] https://lore.kernel.org/fstests/[email protected]/

Changes in v5: (Thanks to John & Darrick for reviews)
- commor/rc: Add a _require_fio_version helper
- fsx: Switch atomic writes off if direct IO (-Z) not passed
- fio tests: better commit messages to explain what we are testing
- ext4/06{1..2}: Refactor code, also test only a few combinations of bs clustersize rather than every single

Changes in v4: (Thanks to Darrick, John and Zorro for the reviews) [4]

- g/1226,1227: Modify fio threads to not issue overlapping atomic writes
- g/1228: Use xfs_io -c "shutdown" instead of _scratch_shutdown to avoid bash overhead
- g/1229: Remove FSX_AVOID handling for bigalloc from common/rc. It is part of the specific test now
- ext4/063: add more clearer extent diagram
- ext4/064: Drop the test for now as im taking sometime to understand the behavior better.
- Removed test numbers from commit message
- For tests with significant changes I've removed the RVBs

[4] https://lore.kernel.org/fstests/[email protected]/T/#mef34a8c13cbee466bfc162db637d6e1cf0a8b06d

Changes in v3 [3]:

- (2/13) use dumpe2fs to figure out if FS is bigalloc
- (9/13) generic/1230: Detect device speeds for more accurate testing. ALso speeds up the test
- fio tests - switch to write followed by verify approach to avoid false failures due to fio verify reads splitting and racing with atomic writes. Discussion thread:

https://lore.kernel.org/fstests/[email protected]/

[3] https://lore.kernel.org/fstests/[email protected]/

Changes in v2 [1]:

- (1/13) new patch with _min and _max helpers
- (2/13) remove setup_fs_options and add fsx specific helper
- (4/13) skip atomic write instead of falling back to normal write (fsx)
- (4/13) make atomic write default on instead of default off (fsx)
- (5,6/13) refactor and cleanup fio tests
- (7/13) refactored common code
- (8/13) dont ignore mmap writes for fsx with atomic writes
- (9/13) use od instead of xxd. handle cleanup of bg threads in _cleanup()
- (10-13/13) minor refactors
- change all tests use _fail for better consistency
- use higher tests numbers for easier merging

[1] https://lore.kernel.org/fstests/[email protected]/

* Original cover [2] *

These are the tests we were using to verify that filesystems are not tearing multi fs block atomic writes. Infact some of the tests like generic/772 (now: g/1230) actually helped us catch and fix issues in ext4's early implementations of multi fs block atomic writes and hence we feel these tests are useful to have in xfstests.

We have tested these with scsi debug as well as a real nvme device supporting multi fs block atomic writes.

Thoughts and suggestions are welcome!

[2] rfc: https://lore.kernel.org/fstests/[email protected]/

Ojaswin Mujoo (10):
common/rc: Add _min() and _max() helpers
common/rc: Add fio atomic write helpers
common/rc: Add a helper to run fsx on a given file ltp/fsx.c: Add atomic writes support to fsx
generic: Add atomic write test using fio crc check verifier
generic: Add atomic write test using fio verify on file mixed mappings
generic: Add atomic write multi-fsblock O_[D]SYNC tests
generic: Stress fsx with atomic writes enabled
generic: Add sudden shutdown tests for multi block atomic writes
ext4: Atomic write test for extent split across leaf nodes

Ritesh Harjani (IBM) (2):
ext4: Test atomic write and ioend codepaths with bigalloc
ext4: Test atomic writes allocation and write codepaths with bigalloc

 common/rc              |  88 +++++++++-
 ltp/fsx.c              | 115 ++++++++++++-
 tests/ext4/061         | 155 +++++++++++++++++
 tests/ext4/061.out     |   2 +
 tests/ext4/062         | 203 +++++++++++++++++++++++
 tests/ext4/062.out     |   2 +
 tests/ext4/063         | 129 +++++++++++++++
 tests/ext4/063.out     |   2 +
 tests/generic/1226     | 108 ++++++++++++
 tests/generic/1226.out |   2 +
 tests/generic/1227     | 132 +++++++++++++++
 tests/generic/1227.out |   2 +
 tests/generic/1228     | 138 ++++++++++++++++
 tests/generic/1228.out |   2 +
 tests/generic/1229     |  68 ++++++++
 tests/generic/1229.out |   2 +
 tests/generic/1230     | 368 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1230.out |   2 +
 18 files changed, 1512 insertions(+), 8 deletions(-)
 create mode 100755 tests/ext4/061
 create mode 100644 tests/ext4/061.out
 create mode 100755 tests/ext4/062
 create mode 100644 tests/ext4/062.out
 create mode 100755 tests/ext4/063
 create mode 100644 tests/ext4/063.out
 create mode 100755 tests/generic/1226
 create mode 100644 tests/generic/1226.out
 create mode 100755 tests/generic/1227
 create mode 100644 tests/generic/1227.out
 create mode 100755 tests/generic/1228
 create mode 100644 tests/generic/1228.out
 create mode 100755 tests/generic/1229
 create mode 100644 tests/generic/1229.out
 create mode 100755 tests/generic/1230
 create mode 100644 tests/generic/1230.out
Signature · 1 line
2.49.0
#1 lore raw

PATCH v7 1/12 common/rc: Add _min() and _max() helpers

Many programs open code these functionalities so add it as a generic helper in common/rc

---
 common/rc | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
common/rc +22 -0
index 81587dad..28fbbcbb 100644
@@ -5978,6 +5978,28 @@ _require_inplace_writes()
 	fi
 }
 
+_min() {
+	local ret
+
+	for arg in "$@"; do
+		if [ -z "$ret" ] || (( $arg < $ret )); then
+			ret="$arg"
+		fi
+	done
+	echo $ret
+}
+
+_max() {
+	local ret
+
+	for arg in "$@"; do
+		if [ -z "$ret" ] || (( $arg > $ret )); then
+			ret="$arg"
+		fi
+	done
+	echo $ret
+}
+
 ################################################################################
 # make sure this script returns success
 /bin/true
Signature · 1 line
2.49.0
#2 lore raw

PATCH v7 2/12 common/rc: Add fio atomic write helpers

In reply to Ojaswin Mujoo

The main motivation of adding this function on top of _require_fio is that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where _require_fio w/ atomic=1 will succeed even though it would not be doing atomic writes.

Hence, add an internal helper __require_fio_version to require specific versions of fio to work past such issues. Further, add the high level _require_fio_atomic_writes helper which tests can use to ensure fio has the right version for atomic writes.

---
 common/rc | 43 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)
common/rc +43 -0
index 28fbbcbb..8a023b9d 100644
@@ -6000,6 +6000,49 @@ _max() {
 	echo $ret
 }
 
+# Due to reasons explained in fio commit 40f1fc11d, fio version between
+# v3.33 and v3.38 have atomic= feature but it is a no-op and doesn't do
+# RWF_ATOMIC write. Hence, use this helper to ensure fio has the
+# required support. Currently, the simplest way we have is to ensure
+# the version.
+_require_fio_atomic_writes() {
+	__require_fio_version "3.38+"
+}
+
+# Check the required fio version. Examples:
+#   __require_fio_version 3.38 (matches 3.38 only)
+#   __require_fio_version 3.38+ (matches 3.38 and above)
+#   __require_fio_version 3.38- (matches 3.38 and below)
+#
+# Internal helper, avoid using directly in tests.
+__require_fio_version() {
+	local req_ver="$1"
+	local fio_ver
+
+	_require_fio
+	_require_math
+
+	fio_ver=$(fio -v | cut -d"-" -f2)
+
+	case "$req_ver" in
+	*+)
+		req_ver=${req_ver%+}
+		test $(_math "$fio_ver >= $req_ver") -eq 1 || \
+			_notrun "need fio >= $req_ver (found $fio_ver)"
+		;;
+	*-)
+		req_ver=${req_ver%-}
+		test $(_math "$fio_ver <= $req_ver") -eq 1 || \
+			_notrun "need fio <= $req_ver (found $fio_ver)"
+		;;
+	*)
+		req_ver=${req_ver%-}
+		test $(_math "$fio_ver == $req_ver") -eq 1 || \
+			_notrun "need fio = $req_ver (found $fio_ver)"
+		;;
+	esac
+}
+
 ################################################################################
 # make sure this script returns success
 /bin/true
Signature · 1 line
2.49.0
#4 lore raw

PATCH v7 3/12 common/rc: Add a helper to run fsx on a given file

In reply to Ojaswin Mujoo

Currently run_fsx is hardcoded to run on a file in $TEST_DIR. Add a helper _run_fsx_on_file so that we can run fsx on any given file including in $SCRATCH_MNT. Also, refactor _run_fsx to use this helper.

No functional change is intended in this patch.

---
 common/rc | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)
common/rc +20 -3
index 8a023b9d..ac77a650 100644
@@ -5203,13 +5203,24 @@ _require_hugepage_fsx()
 		_notrun "fsx binary does not support MADV_COLLAPSE"
 }
 
-_run_fsx()
+_run_fsx_on_file()
 {
+	local testfile=$1
+	shift
+
+	if ! [ -f $testfile ]
+	then
+		echo "_run_fsx_on_file: $testfile doesn't exist. Creating" >> $seqres.full
+		touch $testfile
+	fi
+
 	echo "fsx $*"
 	local args=`echo $@ | sed -e "s/ BSIZE / $bsize /g" -e "s/ PSIZE / $psize /g"`
-	set -- $FSX_PROG $args $FSX_AVOID $TEST_DIR/junk
+
+	set -- $FSX_PROG $args $FSX_AVOID $testfile
+
 	echo "$@" >>$seqres.full
-	rm -f $TEST_DIR/junk
+	rm -f $testfile
 	"$@" 2>&1 | tee -a $seqres.full >$tmp.fsx
 	local res=${PIPESTATUS[0]}
 	if [ $res -ne 0 ]; then
@@ -5221,6 +5232,12 @@ _run_fsx()
 	return 0
 }
 
+_run_fsx()
+{
+	_run_fsx_on_file $TEST_DIR/junk $@
+	return $?
+}
+
 # Run fsx with -h(ugepage buffers).  If we can't set up a hugepage then skip
 # the test, but if any other error occurs then exit the test.
 _run_hugepage_fsx() {
Signature · 1 line
2.49.0
#39 lore raw

PATCH v7 5/12 generic: Add atomic write test using fio crc check verifier

In reply to Ojaswin Mujoo

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc header for each data block, which is verified later to ensure there is no data corruption or torn write.

This test essentially does a lot of parallel RWF_ATOMIC IO on a preallocated file to stress the write and end-io unwritten conversion code paths. The idea is to increase code coverage to ensure RWF_ATOMIC hasn't introduced any issues.

Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.

---
 tests/generic/1226     | 108 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1226.out |   2 +
 2 files changed, 110 insertions(+)
 create mode 100755 tests/generic/1226
 create mode 100644 tests/generic/1226.out
tests/generic/1226 new file +108 -0
new file mode 100755
index 00000000..7ad74554
@@ -0,0 +1,108 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 1226
+#
+# Validate FS atomic write using fio crc check verifier.
+#
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto aio rw atomicwrites
+
+_require_scratch_write_atomic
+_require_odirect
+_require_aio
+_require_fio_atomic_writes
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount
+_require_xfs_io_command "falloc"
+
+touch "$SCRATCH_MNT/f1"
+awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1")
+awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1")
+
+blocksize=$(_max "$awu_min_write" "$((awu_max_write/2))")
+threads=$(_min "$(($(nproc) * 2 * LOAD_FACTOR))" "100")
+filesize=$((blocksize * threads * 100))
+depth=$threads
+io_size=$((filesize / threads))
+io_inc=$io_size
+testfile=$SCRATCH_MNT/test-file
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+fio_aw_config=$tmp.aw.fio
+fio_verify_config=$tmp.verify.fio
+
+function create_fio_configs()
+{
+	create_fio_aw_config
+	create_fio_verify_config
+}
+
+function create_fio_verify_config()
+{
+cat >$fio_verify_config <<EOF
+	[verify-job]
+	direct=1
+	ioengine=libaio
+	rw=read
+	bs=$blocksize
+	filename=$testfile
+	size=$filesize
+	iodepth=$depth
+	group_reporting=1
+
+	verify_only=1
+	verify=crc32c
+	verify_fatal=1
+	verify_state_save=0
+	verify_write_sequence=0
+EOF
+}
+
+function create_fio_aw_config()
+{
+cat >$fio_aw_config <<EOF
+	[atomicwrite-job]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$blocksize
+	filename=$testfile
+	size=$io_inc
+	offset_increment=$io_inc
+	iodepth=$depth
+	numjobs=$threads
+	group_reporting=1
+	atomic=1
+
+	verify_state_save=0
+	verify=crc32c
+	do_verify=0
+EOF
+}
+
+create_fio_configs
+_require_fio $fio_aw_config
+
+cat $fio_aw_config >> $seqres.full
+cat $fio_verify_config >> $seqres.full
+
+$XFS_IO_PROG -fc "falloc 0 $filesize" $testfile >> $seqres.full
+
+$FIO_PROG $fio_aw_config >> $seqres.full
+ret1=$?
+$FIO_PROG $fio_verify_config >> $seqres.full
+ret2=$?
+
+[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed"
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/1226.out new file +2 -0
new file mode 100644
index 00000000..6dce0ea5
@@ -0,0 +1,2 @@
+QA output created by 1226
+Silence is golden
Signature · 1 line
2.49.0
#5 lore raw

PATCH v7 4/12 ltp/fsx.c: Add atomic writes support to fsx

In reply to Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---
 ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 110 insertions(+), 5 deletions(-)
ltp/fsx.c +110 -5
index 163b9453..bdb87ca9 100644
@@ -40,6 +40,7 @@
 #include <liburing.h>
 #endif
 #include <sys/syscall.h>
+#include "statx.h"
 
 #ifndef MAP_FILE
 # define MAP_FILE 0
@@ -49,6 +50,10 @@
 #define RWF_DONTCACHE	0x80
 #endif
 
+#ifndef RWF_ATOMIC
+#define RWF_ATOMIC	0x40
+#endif
+
 #define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
 
 /* Operation flags (bitmask) */
@@ -110,6 +115,7 @@ enum {
 	OP_READ_DONTCACHE,
 	OP_WRITE,
 	OP_WRITE_DONTCACHE,
+	OP_WRITE_ATOMIC,
 	OP_MAPREAD,
 	OP_MAPWRITE,
 	OP_MAX_LITE,
@@ -200,6 +206,11 @@ int	uring = 0;
 int	mark_nr = 0;
 int	dontcache_io = 1;
 int	hugepages = 0;                  /* -h flag */
+int	do_atomic_writes = 1;		/* -a flag disables */
+
+/* User for atomic writes */
+int awu_min = 0;
+int awu_max = 0;
 
 /* Stores info needed to periodically collapse hugepages */
 struct hugepages_collapse_info {
@@ -288,6 +299,7 @@ static const char *op_names[] = {
 	[OP_READ_DONTCACHE] = "read_dontcache",
 	[OP_WRITE] = "write",
 	[OP_WRITE_DONTCACHE] = "write_dontcache",
+	[OP_WRITE_ATOMIC] = "write_atomic",
 	[OP_MAPREAD] = "mapread",
 	[OP_MAPWRITE] = "mapwrite",
 	[OP_TRUNCATE] = "truncate",
@@ -422,6 +434,7 @@ logdump(void)
 				prt("\t***RRRR***");
 			break;
 		case OP_WRITE_DONTCACHE:
+		case OP_WRITE_ATOMIC:
 		case OP_WRITE:
 			prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
 			    lp->args[0], lp->args[0] + lp->args[1] - 1,
@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size)
 	file_size = offset + size;
 }
 
+static int is_power_of_2(unsigned n) {
+	return ((n & (n - 1)) == 0);
+}
+
+/*
+ * Round down n to nearest power of 2.
+ * If n is already a power of 2, return n;
+ */
+static int rounddown_pow_of_2(int n) {
+	int i = 0;
+
+	if (is_power_of_2(n))
+		return n;
+
+	for (; (1 << i) < n; i++);
+
+	return 1 << (i - 1);
+}
+
 void
 dowrite(unsigned offset, unsigned size, int flags)
 {
@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags)
 	offset -= offset % writebdy;
 	if (o_direct)
 		size -= size % writebdy;
+	if (flags & RWF_ATOMIC) {
+		/* atomic write len must be between awu_min and awu_max */
+		if (size < awu_min)
+			size = awu_min;
+		if (size > awu_max)
+			size = awu_max;
+
+		/* atomic writes need power-of-2 sizes */
+		size = rounddown_pow_of_2(size);
+
+		/* atomic writes need naturally aligned offsets */
+		offset -= offset % size;
+
+		/* Skip the write if we are crossing max filesize */
+		if ((offset + size) > maxfilelen) {
+			if (!quiet && testcalls > simulatedopcount)
+				prt("skipping atomic write past maxfilelen\n");
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			return;
+		}
+	}
 	if (size == 0) {
 		if (!quiet && testcalls > simulatedopcount && !o_direct)
 			prt("skipping zero size write\n");
@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)
 		return;
 	}
 
-	log4(OP_WRITE, offset, size, FL_NONE);
+	if (flags & RWF_ATOMIC)
+		log4(OP_WRITE_ATOMIC, offset, size, FL_NONE);
+	else
+		log4(OP_WRITE, offset, size, FL_NONE);
 
 	gendata(original_buf, good_buf, offset, size);
 	if (offset + size > file_size) {
@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags)
 		       (monitorstart == -1 ||
 			(offset + size > monitorstart &&
 			(monitorend == -1 || offset <= monitorend))))))
-		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n", testcalls,
-		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0);
+		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n", testcalls,
+		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0,
+		    (flags & RWF_ATOMIC) != 0);
 	iret = fsxwrite(fd, good_buf + offset, size, offset, flags);
 	if (iret != size) {
 		if (iret == -1)
@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	if (o_direct != O_DIRECT) {
+		fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+				"disabling!\n");
+		return 0;
+	}
+
+	ret = xfstests_statx(AT_FDCWD, fname, 0, STATX_WRITE_ATOMIC, &stx);
+	if (ret < 0) {
+		fprintf(stderr, "main: Statx failed with %d."
+			" Failed to determine atomic write limits, "
+			" disabling!\n", ret);
+		return 0;
+	}
+
+	if (stx.stx_attributes & STATX_ATTR_WRITE_ATOMIC &&
+	    stx.stx_atomic_write_unit_min > 0) {
+		awu_min = stx.stx_atomic_write_unit_min;
+		awu_max = stx.stx_atomic_write_unit_max;
+		return 1;
+	}
+
+	fprintf(stderr, "main: IO Stack does not support "
+			"atomic writes, disabling!\n");
+	return 0;
+}
+
 #ifdef HAVE_COPY_FILE_RANGE
 int
 test_copy_range(void)
@@ -2356,6 +2443,12 @@ have_op:
 			goto out;
 		}
 		break;
+	case OP_WRITE_ATOMIC:
+		if (!do_atomic_writes) {
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			goto out;
+		}
+		break;
 	}
 
 	switch (op) {
@@ -2385,6 +2478,11 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		dowrite(offset, size, RWF_ATOMIC);
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2609,14 @@ void
 usage(void)
 {
 	fprintf(stdout, "usage: %s",
-		"fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\
+		"fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\
 	   [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\
 	   [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\
 	   [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\
 	   [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\
 	   [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\
 	   ... fname\n\
+	-a: disable atomic writes\n\
 	-b opnum: beginning operation number (default 1)\n\
 	-c P: 1 in P chance of file close+open at each op (default infinity)\n\
 	-d: debug output for all operations\n\
@@ -3059,9 +3158,13 @@ main(int argc, char **argv)
 	setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
 
 	while ((ch = getopt_long(argc, argv,
-				 "0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
+				 "0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
 				 longopts, NULL)) != EOF)
 		switch (ch) {
+		case 'a':
+			prt("main(): Atomic writes disabled\n");
+			do_atomic_writes = 0;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3578,8 @@ main(int argc, char **argv)
 		exchange_range_calls = test_exchange_range();
 	if (dontcache_io)
 		dontcache_io = test_dontcache_io();
+	if (do_atomic_writes)
+		do_atomic_writes = test_atomic_writes();
 
 	while (keep_running())
 		if (!test())
Signature · 1 line
2.49.0
#42 lore raw

PATCH v7 6/12 generic: Add atomic write test using fio verify on file mixed mappings

In reply to Ojaswin Mujoo

This test uses fio to first create a file with mixed mappings. Then it does atomic writes using aio dio with parallel jobs to the same file with mixed mappings. Finally, we perform a fio verify step to ensure there is no data corruption or torn write.

The aim is to stress the FS block allocation and extent handling logic to ensure it handles mixed mappings with RWF_ATOMIC correctly without tearing or losing data.

Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.

---
 tests/generic/1227     | 132 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1227.out |   2 +
 2 files changed, 134 insertions(+)
 create mode 100755 tests/generic/1227
 create mode 100644 tests/generic/1227.out
tests/generic/1227 new file +132 -0
new file mode 100755
index 00000000..26177508
@@ -0,0 +1,132 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 1227
+#
+# Validate FS atomic write using fio crc check verifier on mixed mappings
+# of a file.
+#
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto aio rw atomicwrites
+
+_require_scratch_write_atomic_multi_fsblock
+_require_odirect
+_require_aio
+_require_fio_atomic_writes
+_require_xfs_io_command "truncate"
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount
+
+touch "$SCRATCH_MNT/f1"
+awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1")
+awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1")
+
+aw_bsize=$(_max "$awu_min_write" "$((awu_max_write/4))")
+fsbsize=$(_get_block_size $SCRATCH_MNT)
+
+threads=$(_min "$(($(nproc) * 2 * LOAD_FACTOR))" "100")
+filesize=$((aw_bsize * threads * 100))
+depth=$threads
+aw_io_size=$((filesize / threads))
+aw_io_inc=$aw_io_size
+testfile=$SCRATCH_MNT/test-file
+
+fio_prep_config=$tmp.prep.fio
+fio_aw_config=$tmp.aw.fio
+fio_verify_config=$tmp.verify.fio
+fio_out=$tmp.fio.out
+
+cat >$fio_prep_config <<EOF
+# prep file to have mixed mappings
+[global]
+ioengine=libaio
+filename=$testfile
+size=$filesize
+bs=$fsbsize
+direct=1
+iodepth=$depth
+group_reporting=1
+
+# Create written extents
+[prep_written_blocks]
+ioengine=libaio
+rw=randwrite
+io_size=$((filesize/3))
+random_generator=lfsr
+
+# Create unwritten extents
+[prep_unwritten_blocks]
+ioengine=falloc
+rw=randwrite
+io_size=$((filesize/3))
+random_generator=lfsr
+EOF
+
+cat >$fio_aw_config <<EOF
+# atomic write to mixed mappings of written/unwritten/holes
+[atomic_write_job]
+ioengine=libaio
+rw=randwrite
+direct=1
+atomic=1
+random_generator=lfsr
+group_reporting=1
+
+filename=$testfile
+bs=$aw_bsize
+size=$aw_io_size
+offset_increment=$aw_io_inc
+iodepth=$depth
+numjobs=$threads
+
+verify_state_save=0
+verify=crc32c
+do_verify=0
+EOF
+
+cat >$fio_verify_config <<EOF
+# verify atomic writes done by previous job
+[verify_job]
+ioengine=libaio
+rw=read
+random_generator=lfsr
+group_reporting=1
+
+filename=$testfile
+size=$filesize
+bs=$aw_bsize
+iodepth=$depth
+
+verify_state_save=0
+verify_only=1
+verify=crc32c
+verify_fatal=1
+verify_write_sequence=0
+EOF
+
+_require_fio $fio_aw_config
+_require_fio $fio_verify_config
+
+cat $fio_prep_config >> $seqres.full
+cat $fio_aw_config >> $seqres.full
+cat $fio_verify_config >> $seqres.full
+
+$XFS_IO_PROG -fc "truncate $filesize" $testfile >> $seqres.full
+
+#prepare file with mixed mappings
+$FIO_PROG $fio_prep_config >> $seqres.full
+
+# do atomic writes without verifying
+$FIO_PROG $fio_aw_config >> $seqres.full
+
+# verify data is not torn
+$FIO_PROG $fio_verify_config >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/1227.out new file +2 -0
new file mode 100644
index 00000000..2605d062
@@ -0,0 +1,2 @@
+QA output created by 1227
+Silence is golden
Signature · 1 line
2.49.0
#43 lore raw

PATCH v7 7/12 generic: Add atomic write multi-fsblock O_[D]SYNC tests

In reply to Ojaswin Mujoo

This adds various atomic write multi-fsblock stress tests with mixed mappings and O_SYNC, to ensure the data and metadata is atomically persisted even if there is a shutdown.

---
 tests/generic/1228     | 138 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1228.out |   2 +
 2 files changed, 140 insertions(+)
 create mode 100755 tests/generic/1228
 create mode 100644 tests/generic/1228.out
tests/generic/1228 new file +138 -0
new file mode 100755
index 00000000..730bf91e
@@ -0,0 +1,138 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 1228
+#
+# Atomic write multi-fsblock data integrity tests with mixed mappings
+# and O_SYNC
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest auto quick rw atomicwrites
+
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+_require_scratch_shutdown
+_require_xfs_io_command "truncate"
+
+_scratch_mkfs >> $seqres.full
+_scratch_mount >> $seqres.full
+
+check_data_integrity() {
+	actual=$(_hexdump $testfile)
+	if [[ "$expected" != "$actual" ]]
+	then
+		echo "Integrity check failed"
+		echo "Integrity check failed" >> $seqres.full
+		echo "# Expected file contents:" >> $seqres.full
+		echo "$expected" >> $seqres.full
+		echo "# Actual file contents:" >> $seqres.full
+		echo "$actual" >> $seqres.full
+
+		_fail "Data integrity check failed. The atomic write was torn."
+	fi
+}
+
+prep_mixed_mapping() {
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+	local off=0
+	local mapping=""
+
+	local operations=("W" "H" "U")
+	local num_blocks=$((awu_max / blksz))
+	for ((i=0; i<num_blocks; i++)); do
+		local index=$((RANDOM % ${#operations[@]}))
+		local map="${operations[$index]}"
+		local mapping="${mapping}${map}"
+
+		case "$map" in
+			"W")
+				$XFS_IO_PROG -dc "pwrite -S 0x61 -b $blksz $off $blksz" $testfile > /dev/null
+				;;
+			"H")
+				# No operation needed for hole
+				;;
+			"U")
+				$XFS_IO_PROG -c "falloc $off $blksz" $testfile >> /dev/null
+				;;
+		esac
+		off=$((off + blksz))
+	done
+
+	echo "+ + Mixed mapping prep done. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+}
+
+verify_atomic_write() {
+	test $bytes_written -eq $awu_max || _fail "atomic write len=$awu_max assertion failed"
+	check_data_integrity
+}
+
+mixed_mapping_test() {
+	prep_mixed_mapping
+
+	echo -"+ + Performing O_DSYNC atomic write from 0 to $awu_max" >> $seqres.full
+	if [[ "$1" == "shutdown" ]]
+	then
+		bytes_written=$($XFS_IO_PROG -x -dc \
+				"pwrite -DA -V1 -b $awu_max 0 $awu_max" \
+				-c "shutdown" $testfile | grep wrote | \
+				awk -F'[/ ]' '{print $2}')
+		_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed"
+	else
+		bytes_written=$($XFS_IO_PROG -dc \
+				"pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				grep wrote | awk -F'[/ ]' '{print $2}')
+	fi
+
+	verify_atomic_write
+}
+
+testfile=$SCRATCH_MNT/testfile
+touch $testfile
+
+awu_max=$(_get_atomic_write_unit_max $testfile)
+blksz=$(_get_block_size $SCRATCH_MNT)
+
+# Create an expected pattern to compare with
+$XFS_IO_PROG -tc "pwrite -b $awu_max 0 $awu_max" $testfile >> $seqres.full
+expected=$(_hexdump $testfile)
+echo "# Expected file contents:" >> $seqres.full
+echo "$expected" >> $seqres.full
+echo >> $seqres.full
+
+echo "# Test 1: Do O_DSYNC atomic write on random mixed mapping:" >> $seqres.full
+echo >> $seqres.full
+
+iterations=10
+for ((i=1; i<=$iterations; i++)); do
+	echo "=== Mixed Mapping Test Iteration $i ===" >> $seqres.full
+
+	echo "+ Testing without shutdown..." >> $seqres.full
+	mixed_mapping_test
+	echo "Passed!" >> $seqres.full
+
+	echo "+ Testing with sudden shutdown..." >> $seqres.full
+	mixed_mapping_test "shutdown"
+	echo "Passed!" >> $seqres.full
+
+	echo "Iteration $i completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done
+echo "# Test 1: Do O_SYNC atomic write on random mixed mapping ($iterations iterations): OK" >> $seqres.full
+
+
+echo >> $seqres.full
+echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full
+bytes_written=$($XFS_IO_PROG -x -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" \
+		-c "shutdown" $testfile | grep wrote | awk -F'[/ ]' '{print $2}')
+_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed"
+verify_atomic_write
+echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
tests/generic/1228.out new file +2 -0
new file mode 100644
index 00000000..1baffa91
@@ -0,0 +1,2 @@
+QA output created by 1228
+Silence is golden
Signature · 1 line
2.49.0
#44 lore raw

PATCH v7 8/12 generic: Stress fsx with atomic writes enabled

In reply to Ojaswin Mujoo

Stress file with atomic writes to ensure we exercise codepaths where we are mixing different FS operations with atomic writes

---
 tests/generic/1229     | 68 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/1229.out |  2 ++
 2 files changed, 70 insertions(+)
 create mode 100755 tests/generic/1229
 create mode 100644 tests/generic/1229.out
tests/generic/1229 new file +68 -0
new file mode 100755
index 00000000..6d4dcfed
@@ -0,0 +1,68 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 1229
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+_require_odirect
+_require_scratch_write_atomic
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount  >> $seqres.full 2>&1
+
+testfile=$SCRATCH_MNT/testfile
+touch $testfile
+
+awu_max=$(_get_atomic_write_unit_max $testfile)
+blksz=$(_get_block_size $SCRATCH_MNT)
+bsize=`$here/src/min_dio_alignment $SCRATCH_MNT $SCRATCH_DEV`
+
+set_fsx_avoid() {
+	local file=$1
+
+	case "$FSTYP" in
+	"ext4")
+		local dev=$(findmnt -n -o SOURCE --target $testfile)
+
+		# fsx insert/collapse range support for ext4+bigalloc is
+		# currently broken, so disable it. Also disable in case we
+		# can't detect bigalloc to be on safer side.
+		if [ -z "$DUMPE2FS_PROG" ]; then
+			echo "dumpe2fs not found, disabling insert/collapse range" >> $seqres.full
+			FSX_AVOID+=" -I -C"
+			return
+		fi
+
+		$DUMPE2FS_PROG -h $dev 2>&1 | grep -q bigalloc && {
+			echo "fsx insert/collapse range not supported with bigalloc. Disabling.." >> $seqres.full
+			FSX_AVOID+=" -I -C"
+		}
+		;;
+	*)
+		;;
+	esac
+}
+
+# fsx usage:
+#
+# -N numops: total # operations to do
+# -l flen: the upper bound on file size
+# -o oplen: the upper bound on operation size (64k default)
+# -Z: O_DIRECT ()
+
+set_fsx_avoid
+_run_fsx_on_file $testfile -N 10000 -o $awu_max -A -l 500000 -r $bsize -w $bsize -Z $FSX_AVOID  >> $seqres.full
+if [[ "$?" != "0" ]]
+then
+	_fail "fsx returned error: $?"
+fi
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/1229.out new file +2 -0
new file mode 100644
index 00000000..737d61c6
@@ -0,0 +1,2 @@
+QA output created by 1229
+Silence is golden
Signature · 1 line
2.49.0
#45 lore raw

PATCH v7 9/12 generic: Add sudden shutdown tests for multi block atomic writes

In reply to Ojaswin Mujoo

This test is intended to ensure that multi blocks atomic writes maintain atomic guarantees across sudden FS shutdowns.

The way we work is that we lay out a file with random mix of written, unwritten and hole extents. Then we start performing atomic writes sequentially on the file while we parallelly shutdown the FS. Then we note the last offset where the atomic write happened just before shut down and then make sure blocks around it either have completely old data or completely new data, ie the write was not torn during shutdown.

We repeat the same with completely written, completely unwritten and completely empty file to ensure these cases are not torn either. Finally, we have a similar test for append atomic writes

---
 tests/generic/1230     | 368 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1230.out |   2 +
 2 files changed, 370 insertions(+)
 create mode 100755 tests/generic/1230
 create mode 100644 tests/generic/1230.out
tests/generic/1230 new file +368 -0
new file mode 100755
index 00000000..cba75441
@@ -0,0 +1,368 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test No. 1230
+#
+# Test multi block atomic writes with sudden FS shutdowns to ensure
+# the FS is not tearing the write operation
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest auto atomicwrites
+
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+_require_scratch_shutdown
+_require_xfs_io_command "truncate"
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount >> $seqres.full
+
+testfile=$SCRATCH_MNT/testfile
+touch $testfile
+
+awu_max=$(_get_atomic_write_unit_max $testfile)
+blksz=$(_get_block_size $SCRATCH_MNT)
+echo "Awu max: $awu_max" >> $seqres.full
+
+num_blocks=$((awu_max / blksz))
+# keep initial value high for dry run. This will be
+# tweaked in dry_run() based on device write speed.
+filesize=$(( 10 * 1024 * 1024 * 1024 ))
+
+_cleanup() {
+	[ -n "$awloop_pid" ] && kill $awloop_pid &> /dev/null
+	wait
+}
+
+atomic_write_loop() {
+	local off=0
+	local size=$awu_max
+	for ((i=0; i<$((filesize / $size )); i++)); do
+		# Due to sudden shutdown this can produce errors so just
+		# redirect them to seqres.full
+		$XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $size $off $size" >> /dev/null 2>>$seqres.full
+		echo "Written to offset: $off" >> $tmp.aw
+		off=$((off + $size))
+	done
+}
+
+start_atomic_write_and_shutdown() {
+	atomic_write_loop &
+	awloop_pid=$!
+
+	local i=0
+	# Wait for at least first write to be recorded or 10s
+	while [ ! -f "$tmp.aw" -a $i -le 50 ]; do i=$((i + 1)); sleep 0.2; done
+
+	if [[ $i -gt 50 ]]
+	then
+		_fail "atomic write process took too long to start"
+	fi
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown
+
+	kill $awloop_pid 2>/dev/null  # the process might have finished already
+	wait $awloop_pid
+	unset $awloop_pid
+}
+
+# This test has the following flow:
+# 1. Start doing sequential atomic writes in background, upto $filesize
+# 2. Sleep for 0.2s and shutdown the FS
+# 3. kill the atomic write process
+# 4. verify the writes were not torn
+#
+# We ideally want the shutdown to happen while an atomic write is ongoing
+# but this gets tricky since faster devices can actually finish the whole
+# atomic write loop before sleep 0.2s completes, resulting in the shutdown
+# happening after the write loop which is not what we want. A simple solution
+# to this is to increase $filesize so step 1 takes long enough but a big
+# $filesize leads to create_mixed_mappings() taking very long, which is not
+# ideal.
+#
+# Hence, use the dry_run function to figure out the rough device speed and set
+# $filesize accordingly.
+dry_run() {
+	echo >> $seqres.full
+	echo "# Estimating ideal filesize..." >> $seqres.full
+
+	start_atomic_write_and_shutdown
+
+	bytes_written=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	echo "# Bytes written in 0.2s: $bytes_written" >> $seqres.full
+
+	filesize=$((bytes_written * 3))
+	echo "# Setting \$filesize=$filesize" >> $seqres.full
+
+	rm $tmp.aw
+	sleep 0.5
+
+	_scratch_cycle_mount
+
+}
+
+create_mixed_mappings() {
+	local file=$1
+	local size_bytes=$2
+
+	echo "# Filling file $file with alternate mappings till size $size_bytes" >> $seqres.full
+	#Fill the file with alternate written and unwritten blocks
+	local off=0
+	local operations=("W" "U")
+
+	for ((i=0; i<$((size_bytes / blksz )); i++)); do
+		index=$(($i % ${#operations[@]}))
+		map="${operations[$index]}"
+
+		case "$map" in
+		    "W")
+			$XFS_IO_PROG -fc "pwrite -b $blksz $off $blksz" $file  >> /dev/null
+			;;
+		    "U")
+			$XFS_IO_PROG -fc "falloc $off $blksz" $file >> /dev/null
+			;;
+		esac
+		off=$((off + blksz))
+	done
+
+	sync $file
+}
+
+populate_expected_data() {
+	# create a dummy file with expected old data for different cases
+	create_mixed_mappings $testfile.exp_old_mixed $awu_max
+	expected_data_old_mixed=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mixed)
+
+	$XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full
+	expected_data_old_zeroes=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_zeroes)
+
+	$XFS_IO_PROG -fc "pwrite -b $awu_max 0 $awu_max" $testfile.exp_old_mapped >> $seqres.full
+	expected_data_old_mapped=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_old_mapped)
+
+	# create a dummy file with expected new data
+	$XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp_new >> $seqres.full
+	expected_data_new=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp_new)
+}
+
+verify_data_blocks() {
+	local verify_start=$1
+	local verify_end=$2
+	local expected_data_old="$3"
+	local expected_data_new="$4"
+
+	echo >> $seqres.full
+	echo "# Checking for torn write from $verify_start to $verify_end" >> $seqres.full
+
+	# After an atomic write, for every chunk we ensure that the underlying
+	# data is either the old data or new data as writes shouldn't get torn.
+	local off=$verify_start
+	while [[ "$off" -lt "$verify_end" ]]
+	do
+		#actual_data=$(xxd -s $off -l $awu_max -p $testfile)
+		actual_data=$(od -An -t x1 -j $off -N $awu_max $testfile)
+		if [[ "$actual_data" != "$expected_data_new" ]] && [[ "$actual_data" != "$expected_data_old" ]]
+		then
+			echo "Checksum match failed at off: $off size: $awu_max"
+			echo "Expected contents: (Either of the 2 below):"
+			echo
+			echo "Old: "
+			echo "$expected_data_old"
+			echo
+			echo "New: "
+			echo "$expected_data_new"
+			echo
+			echo "Actual contents: "
+			echo "$actual_data"
+
+			_fail
+		fi
+		echo -n "Check at offset $off succeeded! " >> $seqres.full
+		if [[ "$actual_data" == "$expected_data_new" ]]
+		then
+			echo "matched new" >> $seqres.full
+		elif [[ "$actual_data" == "$expected_data_old" ]]
+		then
+			echo "matched old" >> $seqres.full
+		fi
+		off=$(( off + awu_max ))
+	done
+}
+
+# test torn write for file by shutting down in between atomic writes
+test_torn_write() {
+	echo >> $seqres.full
+	echo "# Writing atomically to file in background" >> $seqres.full
+
+	start_atomic_write_and_shutdown
+
+	last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	if [[ -z $last_offset ]]
+	then
+		last_offset=0
+	fi
+
+	echo >> $seqres.full
+	echo "# Last offset of atomic write: $last_offset" >> $seqres.full
+
+	rm $tmp.aw
+	sleep 0.5
+
+	_scratch_cycle_mount
+
+	# we want to verify all blocks around which the shutdown happened
+	verify_start=$(( last_offset - (awu_max * 5)))
+	if [[ $verify_start < 0 ]]
+	then
+		verify_start=0
+	fi
+
+	verify_end=$(( last_offset + (awu_max * 5)))
+	if [[ "$verify_end" -gt "$filesize" ]]
+	then
+		verify_end=$filesize
+	fi
+}
+
+# test torn write for file with written and unwritten mappings
+test_torn_write_mixed() {
+	$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Creating testfile with mixed mappings" >> $seqres.full
+	create_mixed_mappings $testfile $filesize
+
+	test_torn_write
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new"
+}
+
+# test torn write for file with completely written mappings
+test_torn_write_written() {
+	$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Creating testfile with fully written mapping" >> $seqres.full
+	$XFS_IO_PROG -c "pwrite -b $filesize 0 $filesize" $testfile >> $seqres.full
+	sync $testfile
+
+	test_torn_write
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new"
+}
+
+# test torn write for file with completely unwritten mappings
+test_torn_write_unwritten() {
+	$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Creating testfile with fully unwritten mappings" >> $seqres.full
+	$XFS_IO_PROG -c "falloc 0 $filesize" $testfile >> $seqres.full
+	sync $testfile
+
+	test_torn_write
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+}
+
+# test torn write for file with no mappings
+test_torn_write_hole() {
+	$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Creating testfile with no mappings" >> $seqres.full
+	$XFS_IO_PROG -c "truncate $filesize" $testfile >> $seqres.full
+	sync $testfile
+
+	test_torn_write
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+}
+
+test_append_torn_write() {
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Performing append atomic writes over file in background" >> $seqres.full
+
+	start_atomic_write_and_shutdown
+
+	local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	if [[ -z $last_offset ]]
+	then
+		last_offset=0
+	fi
+
+	echo >> $seqres.full
+	echo "# Last offset of atomic write: $last_offset" >> $seqres.full
+	rm $tmp.aw
+	sleep 0.5
+
+	_scratch_cycle_mount
+	local filesize=$(_get_filesize $testfile)
+	echo >> $seqres.full
+	echo "# Filesize after shutdown: $filesize" >> $seqres.full
+
+	# To confirm that the write went atomically, we check:
+	# 1. The last block should be a multiple of awu_max
+	# 2. The last block should be the completely new data
+
+	if (( $filesize % $awu_max ))
+	then
+		echo "Filesize after shutdown ($filesize) not a multiple of atomic write unit ($awu_max)"
+	fi
+
+	verify_start=$(( filesize - (awu_max * 5)))
+	if [[ $verify_start < 0 ]]
+	then
+		verify_start=0
+	fi
+
+	local verify_end=$filesize
+
+	# Here the blocks should always match new data hence, for simplicity of
+	# code, just corrupt the $expected_data_old buffer so it never matches
+	local expected_data_old="POISON"
+	verify_data_blocks $verify_start $verify_end "$expected_data_old" "$expected_data_new"
+}
+
+$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+dry_run
+
+echo >> $seqres.full
+echo "# Populating expected data buffers" >> $seqres.full
+populate_expected_data
+
+# Loop 20 times to shake out any races due to shutdown
+for ((iter=0; iter<20; iter++))
+do
+	echo >> $seqres.full
+	echo "------ Iteration $iter ------" >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Starting torn write test for atomic writes over mixed mapping" >> $seqres.full
+	test_torn_write_mixed
+
+	echo >> $seqres.full
+	echo "# Starting torn write test for atomic writes over fully written mapping" >> $seqres.full
+	test_torn_write_written
+
+	echo >> $seqres.full
+	echo "# Starting torn write test for atomic writes over fully unwritten mapping" >> $seqres.full
+	test_torn_write_unwritten
+
+	echo >> $seqres.full
+	echo "# Starting torn write test for atomic writes over holes" >> $seqres.full
+	test_torn_write_hole
+
+	echo >> $seqres.full
+	echo "# Starting shutdown torn write test for append atomic writes" >> $seqres.full
+	test_append_torn_write
+done
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/1230.out new file +2 -0
new file mode 100644
index 00000000..d01f54ea
@@ -0,0 +1,2 @@
+QA output created by 1230
+Silence is golden
Signature · 1 line
2.49.0
#46 lore raw

PATCH v7 10/12 ext4: Test atomic write and ioend codepaths with bigalloc

In reply to Ojaswin Mujoo

From: "Ritesh Harjani (IBM)" <[email protected]>

This test does a lot of parallel RWF_ATOMIC IO on a preallocated file to stress the write and end-io unwritten conversion code paths. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.

Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.

The value of this test is that we make sure the RWF_ATOMIC is handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.

---
 tests/ext4/061     | 155 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/061.out |   2 +
 2 files changed, 157 insertions(+)
 create mode 100755 tests/ext4/061
 create mode 100644 tests/ext4/061.out
tests/ext4/061 new file +155 -0
new file mode 100755
index 00000000..1d61c8b0
@@ -0,0 +1,155 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 061
+#
+# This test does a lot of parallel RWF_ATOMIC IO on a preallocated file to
+# stress the write and end-io unwritten conversion code paths. We brute force
+# this for all possible blocksize and clustersizes and after each iteration we
+# ensure the data was not torn or corrupted using fio crc verification.
+#
+# Note that in this test we use overlapping atomic writes of same io size.
+# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and
+# SCSI provide this guarantee as an inseparable feature to power-fail
+# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the
+# write due to racing ioend unwritten conversion.
+#
+# The value of this test is that we make sure the RWF_ATOMIC is handled
+# correctly by ext4 as well as test that the block layer doesn't split or only
+# generate multiple bios for an atomic write.
+
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto rw stress atomicwrites
+
+_require_scratch_write_atomic
+_require_fio_atomic_writes
+_require_aiodio
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((100*1024*1024))
+
+# Calculate fsblocksize as per bdev atomic write units.
+bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV)
+bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV)
+bs=$(_max 4096 "$bdev_awu_min")
+
+function create_fio_configs()
+{
+	local bsize=$1
+	create_fio_aw_config $bsize
+	create_fio_verify_config $bsize
+}
+
+function create_fio_verify_config()
+{
+	local bsize=$1
+cat >$fio_verify_config <<EOF
+	[aio-dio-aw-verify]
+	direct=1
+	ioengine=libaio
+	rw=read
+	bs=$bsize
+	fallocate=native
+	filename=$SCRATCH_MNT/test-file
+	size=$SIZE
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	atomic=1
+	group_reporting=1
+
+	verify_only=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_write_sequence=0
+EOF
+}
+
+function create_fio_aw_config()
+{
+	local bsize=$1
+cat >$fio_aw_config <<EOF
+	[aio-dio-aw]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$bsize
+	fallocate=native
+	filename=$SCRATCH_MNT/test-file
+	size=$SIZE
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	atomic=1
+
+	verify_state_save=0
+	verify=crc32c
+	do_verify=0
+
+EOF
+}
+
+run_test_one() {
+	local bs=$1
+	local cs=$2
+	local iosize=$3
+
+	MKFS_OPTIONS="-O bigalloc -b $bs -C $cs"
+	_scratch_mkfs_ext4  >> $seqres.full 2>&1 || return
+	if _try_scratch_mount >> $seqres.full 2>&1; then
+		echo "== Testing: bs=$bs cs=$cs iosize=$iosize ==" >> $seqres.full
+
+		touch $SCRATCH_MNT/f1
+		create_fio_configs $iosize
+
+		cat $fio_aw_config >> $seqres.full
+		echo >> $seqres.full
+		cat $fio_verify_config >> $seqres.full
+
+		$FIO_PROG $fio_aw_config >> $seqres.full
+		ret1=$?
+
+		$FIO_PROG $fio_verify_config >> $seqres.full
+		ret2=$?
+
+		_scratch_unmount
+
+		[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed"
+	fi
+}
+
+run_test() {
+	local bs=$1
+
+	# cluster sizes above 16 x blocksize are experimental so avoid them
+	# Also, cap cluster size at 128kb to keep it reasonable for large
+	# blocks size
+	max_cs=$(_min $((16 * bs)) "$bdev_awu_max" $((128 * 1024)))
+
+	# Fuzz for combinations of blocksize, clustersize and
+	# iosize that cover most of the cases
+	run_test_one $bs $bs $bs
+	run_test_one $bs $max_cs $bs
+	run_test_one $bs $max_cs $max_cs
+	run_test_one $bs $max_cs $(_max "$((max_cs/2))" $bs)
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_aw_config=$tmp.aw.fio
+fio_verify_config=$tmp.verify.fio
+fio_out=$tmp.fio.out
+
+create_fio_configs $bs
+_require_fio $fio_aw_config
+
+for ((bs=$bs; bs <= $(_get_page_size); bs = $bs << 1)); do
+	run_test $bs
+done
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/ext4/061.out new file +2 -0
new file mode 100644
index 00000000..273be9e0
@@ -0,0 +1,2 @@
+QA output created by 061
+Silence is golden
Signature · 1 line
2.49.0
#48 lore raw

PATCH v7 12/12 ext4: Atomic write test for extent split across leaf nodes

In reply to Ojaswin Mujoo

In ext4, even if an allocated range is physically and logically contiguous, it can still be split into 2 extents. This is because ext4 does not merge extents across leaf nodes. This is an issue for atomic writes since even for a continuous extent the map block could (in rare cases) return a shorter map, hence tearning the write. This test creates such a file and ensures that the atomic write handles this case correctly

---
 tests/ext4/063     | 129 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/063.out |   2 +
 2 files changed, 131 insertions(+)
 create mode 100755 tests/ext4/063
 create mode 100644 tests/ext4/063.out
tests/ext4/063 new file +129 -0
new file mode 100755
index 00000000..9d6265a8
@@ -0,0 +1,129 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# In ext4, even if an allocated range is physically and logically contiguous,
+# it can still be split into 2 or more extents. This is because ext4 does not
+# merge extents across leaf nodes. This is an issue for atomic writes since
+# even for a continuous extent the map block could (in rare cases) return a
+# shorter map, hence tearing the write. This test creates such a file and
+# ensures that the atomic write handles this case correctly
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest auto atomicwrites
+
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+_require_command "$DEBUGFS_PROG" debugfs
+
+prep() {
+	local bs=`_get_block_size $SCRATCH_MNT`
+	local ex_hdr_bytes=12
+	local ex_entry_bytes=12
+	local entries_per_blk=$(( (bs - ex_hdr_bytes) / ex_entry_bytes ))
+
+	# fill the extent tree leaf with bs len extents at alternate offsets.
+	# The tree should look as follows
+	#
+	#                    +---------+---------+
+	#                    | index 1 | index 2 |
+	#                    +-----+---+-----+---+
+	#                   +------+         +-----------+
+	#                   |                            |
+	#      +-------+-------+---+---------+     +-----+----+
+	#      | ex 1  | ex 2  |   |  ex n   |     |  ex n+1  |
+	#      | off:0 | off:2 |...| off:678 |     |  off:680 |
+	#      | len:1 | len:1 |   |  len:1  |     |   len:1  |
+	#      +-------+-------+---+---------+     +----------+
+	#
+	for i in $(seq 0 $entries_per_blk)
+	do
+		$XFS_IO_PROG -fc "pwrite -b $bs $((i * 2 * bs)) $bs" $testfile > /dev/null
+	done
+	sync $testfile
+
+	echo >> $seqres.full
+	echo "Create file with extents spanning 2 leaves. Extents:">> $seqres.full
+	echo "...">> $seqres.full
+	$DEBUGFS_PROG -R "ex `basename $testfile`" $SCRATCH_DEV |& tail >> $seqres.full
+
+	# Now try to insert a new extent ex(new) between ex(n) and ex(n+1).
+	# Since this is a new FS the allocator would find continuous blocks
+	# such that ex(n) ex(new) ex(n+1) are physically(and logically)
+	# contiguous. However, since we don't merge extents across leaf we will
+	# end up with a tree as:
+	#
+	#                    +---------+---------+
+	#                    | index 1 | index 2 |
+	#                    +-----+---+-----+---+
+	#                   +------+         +------------+
+	#                   |                             |
+	#      +-------+-------+---+---------+     +------+-----------+
+	#      | ex 1  | ex 2  |   |  ex n   |     |  ex n+1 (merged) |
+	#      | off:0 | off:2 |...| off:678 |     |      off:679     |
+	#      | len:1 | len:1 |   |  len:1  |     |      len:2       |
+	#      +-------+-------+---+---------+     +------------------+
+	#
+	echo >> $seqres.full
+	torn_ex_offset=$((((entries_per_blk * 2) - 1) * bs))
+	$XFS_IO_PROG -c "pwrite $torn_ex_offset $bs" $testfile >> /dev/null
+	sync $testfile
+
+	echo >> $seqres.full
+	echo "Perform 1 block write at $torn_ex_offset to create torn extent. Extents:">> $seqres.full
+	echo "...">> $seqres.full
+	$DEBUGFS_PROG -R "ex `basename $testfile`" $SCRATCH_DEV |& tail >> $seqres.full
+
+	_scratch_cycle_mount
+}
+
+_scratch_mkfs >> $seqres.full
+_scratch_mount >> $seqres.full
+
+testfile=$SCRATCH_MNT/testfile
+touch $testfile
+awu_max=$(_get_atomic_write_unit_max $testfile)
+
+echo >> $seqres.full
+echo "# Prepping the file" >> $seqres.full
+prep
+
+torn_aw_offset=$((torn_ex_offset - (torn_ex_offset % awu_max)))
+
+echo >> $seqres.full
+echo "# Performing atomic IO on the torn extent range. Command: " >> $seqres.full
+echo $XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $awu_max $torn_aw_offset $awu_max" >> $seqres.full
+$XFS_IO_PROG -c "open -fsd $testfile" -c "pwrite -S 0x61 -DA -V1 -b $awu_max $torn_aw_offset $awu_max" >> $seqres.full
+
+echo >> $seqres.full
+echo "Extent state after atomic write:">> $seqres.full
+echo "...">> $seqres.full
+$DEBUGFS_PROG -R "ex `basename $testfile`" $SCRATCH_DEV |& tail >> $seqres.full
+
+echo >> $seqres.full
+echo "# Checking data integrity" >> $seqres.full
+
+# create a dummy file with expected data
+$XFS_IO_PROG -fc "pwrite -S 0x61 -b $awu_max 0 $awu_max" $testfile.exp >> /dev/null
+expected_data=$(od -An -t x1 -j 0 -N $awu_max $testfile.exp)
+
+# We ensure that the data after atomic writes should match the expected data
+actual_data=$(od -An -t x1 -j $torn_aw_offset -N $awu_max $testfile)
+if [[ "$actual_data" != "$expected_data" ]]
+then
+	echo "Checksum match failed at off: $torn_aw_offset size: $awu_max"
+	echo
+	echo "Expected: "
+	echo "$expected_data"
+	echo
+	echo "Actual contents: "
+	echo "$actual_data"
+
+	_fail
+fi
+
+echo -n "Data verification at offset $torn_aw_offset succeeded!" >> $seqres.full
+echo "Silence is golden"
+status=0
+exit
tests/ext4/063.out new file +2 -0
new file mode 100644
index 00000000..de35fc52
@@ -0,0 +1,2 @@
+QA output created by 063
+Silence is golden
Signature · 1 line
2.49.0
#47 lore raw

PATCH v7 11/12 ext4: Test atomic writes allocation and write codepaths with bigalloc

In reply to Ojaswin Mujoo

From: "Ritesh Harjani (IBM)" <[email protected]>

This test does a parallel RWF_ATOMIC IO on a multiple truncated files in a small FS. The idea is to stress ext4 allocator to ensure we are able to handle low space scenarios correctly with atomic writes. We brute force this for different blocksize and clustersizes and after each iteration we ensure the data was not torn or corrupted using fio crc verification.

Note that in this test we use overlapping atomic writes of same io size. Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and SCSI provide this guarantee as an inseparable feature to power-fail atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the write due to racing ioend unwritten conversion.

The value of this test is that we make sure the RWF_ATOMIC is handled correctly by ext4 as well as test that the block layer doesn't split or only generate multiple bios for an atomic write.

---
 tests/ext4/062     | 203 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/062.out |   2 +
 2 files changed, 205 insertions(+)
 create mode 100755 tests/ext4/062
 create mode 100644 tests/ext4/062.out
tests/ext4/062 new file +203 -0
new file mode 100755
index 00000000..05cce696
@@ -0,0 +1,203 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 062
+#
+# This test does a parallel RWF_ATOMIC IO on a multiple truncated files in a
+# small FS. The idea is to stress ext4 allocator to ensure we are able to
+# handle low space scenarios correctly with atomic writes.. We brute force this
+# for all possible blocksize and clustersizes and after each iteration we
+# ensure the data was not torn or corrupted using fio crc verification.
+#
+# Note that in this test we use overlapping atomic writes of same io size.
+# Although serializing racing writes is not guaranteed for RWF_ATOMIC, NVMe and
+# SCSI provide this guarantee as an inseparable feature to power-fail
+# atomicity. Keeping the iosize as same also ensures that ext4 doesn't tear the
+# write due to racing ioend unwritten conversion.
+#
+# The value of this test is that we make sure the RWF_ATOMIC is handled
+# correctly by ext4 as well as test that the block layer doesn't split or only
+# generate multiple bios for an atomic write.
+#
+
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto rw stress atomicwrites
+
+_require_scratch_write_atomic
+_require_fio_atomic_writes
+_require_aiodio
+
+FSSIZE=$((360*1024*1024))
+FIO_LOAD=$(($(nproc) * LOAD_FACTOR))
+
+# Calculate bs as per bdev atomic write units.
+bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV)
+bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV)
+bs=$(_max 4096 "$bdev_awu_min")
+
+function create_fio_configs()
+{
+	local bsize=$1
+	create_fio_aw_config $bsize
+	create_fio_verify_config $bsize
+}
+
+function create_fio_verify_config()
+{
+	local bsize=$1
+cat >$fio_verify_config <<EOF
+	[global]
+	direct=1
+	ioengine=libaio
+	rw=read
+	bs=$bsize
+	fallocate=truncate
+	size=$((FSSIZE / 12))
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	atomic=1
+
+	verify_only=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_write_sequence=0
+
+	[verify-job1]
+	filename=$SCRATCH_MNT/testfile-job1
+
+	[verify-job2]
+	filename=$SCRATCH_MNT/testfile-job2
+
+	[verify-job3]
+	filename=$SCRATCH_MNT/testfile-job3
+
+	[verify-job4]
+	filename=$SCRATCH_MNT/testfile-job4
+
+	[verify-job5]
+	filename=$SCRATCH_MNT/testfile-job5
+
+	[verify-job6]
+	filename=$SCRATCH_MNT/testfile-job6
+
+	[verify-job7]
+	filename=$SCRATCH_MNT/testfile-job7
+
+	[verify-job8]
+	filename=$SCRATCH_MNT/testfile-job8
+
+EOF
+}
+
+function create_fio_aw_config()
+{
+	local bsize=$1
+cat >$fio_aw_config <<EOF
+	[global]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$bsize
+	fallocate=truncate
+	size=$((FSSIZE / 12))
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	atomic=1
+
+	verify_state_save=0
+	verify=crc32c
+	do_verify=0
+
+	[write-job1]
+	filename=$SCRATCH_MNT/testfile-job1
+
+	[write-job2]
+	filename=$SCRATCH_MNT/testfile-job2
+
+	[write-job3]
+	filename=$SCRATCH_MNT/testfile-job3
+
+	[write-job4]
+	filename=$SCRATCH_MNT/testfile-job4
+
+	[write-job5]
+	filename=$SCRATCH_MNT/testfile-job5
+
+	[write-job6]
+	filename=$SCRATCH_MNT/testfile-job6
+
+	[write-job7]
+	filename=$SCRATCH_MNT/testfile-job7
+
+	[write-job8]
+	filename=$SCRATCH_MNT/testfile-job8
+
+EOF
+}
+
+run_test_one() {
+	local bs=$1
+	local cs=$2
+	local iosize=$3
+
+	MKFS_OPTIONS="-O bigalloc -b $bs -C $cs"
+	_scratch_mkfs_sized "$FSSIZE" >> $seqres.full 2>&1 || return
+	if _try_scratch_mount >> $seqres.full 2>&1; then
+		echo "Testing: bs=$bs cs=$cs iosize=$iosize" >> $seqres.full
+
+		touch $SCRATCH_MNT/f1
+		create_fio_configs $iosize
+
+		cat $fio_aw_config >> $seqres.full
+		cat $fio_verify_config >> $seqres.full
+
+		$FIO_PROG $fio_aw_config >> $seqres.full
+		ret1=$?
+
+		$FIO_PROG $fio_verify_config  >> $seqres.full
+		ret2=$?
+
+		_scratch_unmount
+
+		[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed"
+	fi
+}
+
+run_test() {
+	local bs=$1
+
+	# cluster sizes above 16 x blocksize are experimental so avoid them
+	# Also, cap cluster size at 128kb to keep it reasonable for large
+	# blocks size
+	max_cs=$(_min $((16 * bs)) "$bdev_awu_max" $((128 * 1024)))
+
+	# Fuzz for combinations of blocksize, clustersize and
+	# iosize that cover most of the cases
+	run_test_one $bs $bs $bs
+	run_test_one $bs $max_cs $bs
+	run_test_one $bs $max_cs $max_cs
+	run_test_one $bs $max_cs $(_max "$((max_cs/2))" $bs)
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_aw_config=$tmp.aw.fio
+fio_verify_config=$tmp.verify.fio
+fio_out=$tmp.fio.out
+
+create_fio_configs $bs
+_require_fio $fio_aw_config
+
+for ((bs=$bs; bs <= $(_get_page_size); bs = $bs << 1)); do
+	run_test $bs $cs $iosize
+done
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/ext4/062.out new file +2 -0
new file mode 100644
index 00000000..a1578f48
@@ -0,0 +1,2 @@
+QA output created by 062
+Silence is golden
Signature · 1 line
2.49.0
Reviewed-by on v7 2/12
#3 lore raw

Re: [PATCH v7 02/12] common/rc: Add fio atomic write helpers

In reply to Ojaswin Mujoo
Quoted 8 lines · Ojaswin Mujoo

The main motivation of adding this function on top of _require_fio is that there has been a case in fio where atomic= option was added but later it was changed to noop since kernel didn't yet have support for atomic writes. It was then again utilized to do atomic writes in a later version, once kernel got the support. Due to this there is a point in fio where _require_fio w/ atomic=1 will succeed even though it would not be doing atomic writes.

Quoted 8 lines

Hence, add an internal helper __require_fio_version to require specific versions of fio to work past such issues. Further, add the high level _require_fio_atomic_writes helper which tests can use to ensure fio has the right version for atomic writes.

Looks ok to me,

--D

Quoted 61 lines
---
 common/rc | 43 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)
common/rc +43 -0
index 28fbbcbb..8a023b9d 100644
@@ -6000,6 +6000,49 @@ _max() {
 	echo $ret
 }
 
+# Due to reasons explained in fio commit 40f1fc11d, fio version between
+# v3.33 and v3.38 have atomic= feature but it is a no-op and doesn't do
+# RWF_ATOMIC write. Hence, use this helper to ensure fio has the
+# required support. Currently, the simplest way we have is to ensure
+# the version.
+_require_fio_atomic_writes() {
+	__require_fio_version "3.38+"
+}
+
+# Check the required fio version. Examples:
+#   __require_fio_version 3.38 (matches 3.38 only)
+#   __require_fio_version 3.38+ (matches 3.38 and above)
+#   __require_fio_version 3.38- (matches 3.38 and below)
+#
+# Internal helper, avoid using directly in tests.
+__require_fio_version() {
+	local req_ver="$1"
+	local fio_ver
+
+	_require_fio
+	_require_math
+
+	fio_ver=$(fio -v | cut -d"-" -f2)
+
+	case "$req_ver" in
+	*+)
+		req_ver=${req_ver%+}
+		test $(_math "$fio_ver >= $req_ver") -eq 1 || \
+			_notrun "need fio >= $req_ver (found $fio_ver)"
+		;;
+	*-)
+		req_ver=${req_ver%-}
+		test $(_math "$fio_ver <= $req_ver") -eq 1 || \
+			_notrun "need fio <= $req_ver (found $fio_ver)"
+		;;
+	*)
+		req_ver=${req_ver%-}
+		test $(_math "$fio_ver == $req_ver") -eq 1 || \
+			_notrun "need fio = $req_ver (found $fio_ver)"
+		;;
+	esac
+}
+
 ################################################################################
 # make sure this script returns success
 /bin/true
Signature · 1 line
2.49.0
#6 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

In reply to Ojaswin Mujoo
Quoted 149 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---
 ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 110 insertions(+), 5 deletions(-)
ltp/fsx.c +60 -3
index 163b9453..bdb87ca9 100644
@@ -40,6 +40,7 @@
 #include <liburing.h>
 #endif
 #include <sys/syscall.h>
+#include "statx.h"
 
 #ifndef MAP_FILE
 # define MAP_FILE 0
@@ -49,6 +50,10 @@
 #define RWF_DONTCACHE	0x80
 #endif
 
+#ifndef RWF_ATOMIC
+#define RWF_ATOMIC	0x40
+#endif
+
 #define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
 
 /* Operation flags (bitmask) */
@@ -110,6 +115,7 @@ enum {
 	OP_READ_DONTCACHE,
 	OP_WRITE,
 	OP_WRITE_DONTCACHE,
+	OP_WRITE_ATOMIC,
 	OP_MAPREAD,
 	OP_MAPWRITE,
 	OP_MAX_LITE,
@@ -200,6 +206,11 @@ int	uring = 0;
 int	mark_nr = 0;
 int	dontcache_io = 1;
 int	hugepages = 0;                  /* -h flag */
+int	do_atomic_writes = 1;		/* -a flag disables */
+
+/* User for atomic writes */
+int awu_min = 0;
+int awu_max = 0;
 
 /* Stores info needed to periodically collapse hugepages */
 struct hugepages_collapse_info {
@@ -288,6 +299,7 @@ static const char *op_names[] = {
 	[OP_READ_DONTCACHE] = "read_dontcache",
 	[OP_WRITE] = "write",
 	[OP_WRITE_DONTCACHE] = "write_dontcache",
+	[OP_WRITE_ATOMIC] = "write_atomic",
 	[OP_MAPREAD] = "mapread",
 	[OP_MAPWRITE] = "mapwrite",
 	[OP_TRUNCATE] = "truncate",
@@ -422,6 +434,7 @@ logdump(void)
 				prt("\t***RRRR***");
 			break;
 		case OP_WRITE_DONTCACHE:
+		case OP_WRITE_ATOMIC:
 		case OP_WRITE:
 			prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
 			    lp->args[0], lp->args[0] + lp->args[1] - 1,
@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size)
 	file_size = offset + size;
 }
 
+static int is_power_of_2(unsigned n) {
+	return ((n & (n - 1)) == 0);
+}
+
+/*
+ * Round down n to nearest power of 2.
+ * If n is already a power of 2, return n;
+ */
+static int rounddown_pow_of_2(int n) {
+	int i = 0;
+
+	if (is_power_of_2(n))
+		return n;
+
+	for (; (1 << i) < n; i++);
+
+	return 1 << (i - 1);
+}
+
 void
 dowrite(unsigned offset, unsigned size, int flags)
 {
@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags)
 	offset -= offset % writebdy;
 	if (o_direct)
 		size -= size % writebdy;
+	if (flags & RWF_ATOMIC) {
+		/* atomic write len must be between awu_min and awu_max */
+		if (size < awu_min)
+			size = awu_min;
+		if (size > awu_max)
+			size = awu_max;
+
+		/* atomic writes need power-of-2 sizes */
+		size = rounddown_pow_of_2(size);
+
+		/* atomic writes need naturally aligned offsets */
+		offset -= offset % size;
+
+		/* Skip the write if we are crossing max filesize */
+		if ((offset + size) > maxfilelen) {
+			if (!quiet && testcalls > simulatedopcount)
+				prt("skipping atomic write past maxfilelen\n");
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			return;
+		}
+	}
 	if (size == 0) {
 		if (!quiet && testcalls > simulatedopcount && !o_direct)
 			prt("skipping zero size write\n");
@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)
 		return;
 	}
 
-	log4(OP_WRITE, offset, size, FL_NONE);
+	if (flags & RWF_ATOMIC)
+		log4(OP_WRITE_ATOMIC, offset, size, FL_NONE);
+	else
+		log4(OP_WRITE, offset, size, FL_NONE);
 
 	gendata(original_buf, good_buf, offset, size);
 	if (offset + size > file_size) {
@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags)
 		       (monitorstart == -1 ||
 			(offset + size > monitorstart &&
 			(monitorend == -1 || offset <= monitorend))))))
-		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n", testcalls,
-		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0);
+		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n", testcalls,
+		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0,
+		    (flags & RWF_ATOMIC) != 0);
 	iret = fsxwrite(fd, good_buf + offset, size, offset, flags);
 	if (iret != size) {
 		if (iret == -1)

ltp/fsx.c do_dedupe_range(unsigned offset, unsigned length, unsigned dest) · line 1851

Quoted 10 lines
ltp/fsx.c +7 -0
@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	if (o_direct != O_DIRECT) {
+		fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+				"disabling!\n");

This patch causes some fsx test cases fail (e.g. g/127, g/231) as:

127.out.bad renamed from dev/fd/63 +6 -0
@@ -1,7 +1,13 @@
 QA output created by 127
+main: atomic writes need O_DIRECT (-Z), disabling!
 All 100000 operations completed A-OK!
+main: atomic writes need O_DIRECT (-Z), disabling!
 All 100000 operations completed A-OK!
+main: atomic writes need O_DIRECT (-Z), disabling!
 All 100000 operations completed A-OK!
+main: atomic writes need O_DIRECT (-Z), disabling!
 All 100000 operations completed A-OK!
+main: atomic writes need O_DIRECT (-Z), disabling!
 All 100000 operations completed A-OK!
+main: atomic writes need O_DIRECT (-Z), disabling!
 All 100000 operations completed A-OK!

Due to you didn't deal with the "-q" (quiet) option at here. If you don't have objection, I'll change this part as:

  if (o_direct != O_DIRECT && !quiet)

And ...

Quoted 10 lines
ltp/fsx.c +10 -0
+		return 0;
+	}
+
+	ret = xfstests_statx(AT_FDCWD, fname, 0, STATX_WRITE_ATOMIC, &stx);
+	if (ret < 0) {
+		fprintf(stderr, "main: Statx failed with %d."
+			" Failed to determine atomic write limits, "
+			" disabling!\n", ret);
+		return 0;
+	}

ltp/fsx.c do_dedupe_range(unsigned offset, unsigned length, unsigned dest) · line 1871

Quoted 10 lines
ltp/fsx.c +10 -0
+
+	if (stx.stx_attributes & STATX_ATTR_WRITE_ATOMIC &&
+	    stx.stx_atomic_write_unit_min > 0) {
+		awu_min = stx.stx_atomic_write_unit_min;
+		awu_max = stx.stx_atomic_write_unit_max;
+		return 1;
+	}
+
+	fprintf(stderr, "main: IO Stack does not support "
+			"atomic writes, disabling!\n");

... change this line as:

if (!quiet)

	fprintf(stderr, "main: IO Stack does not support "
			"atomic writes, disabling!\n");

When I merge it.

Thanks,
Zorro

Quoted 74 lines
ltp/fsx.c +23 -2
+	return 0;
+}
+
 #ifdef HAVE_COPY_FILE_RANGE
 int
 test_copy_range(void)
@@ -2356,6 +2443,12 @@ have_op:
 			goto out;
 		}
 		break;
+	case OP_WRITE_ATOMIC:
+		if (!do_atomic_writes) {
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			goto out;
+		}
+		break;
 	}
 
 	switch (op) {
@@ -2385,6 +2478,11 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		dowrite(offset, size, RWF_ATOMIC);
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2609,14 @@ void
 usage(void)
 {
 	fprintf(stdout, "usage: %s",
-		"fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\
+		"fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\
 	   [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\
 	   [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\
 	   [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\
 	   [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\
 	   [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\
 	   ... fname\n\
+	-a: disable atomic writes\n\
 	-b opnum: beginning operation number (default 1)\n\
 	-c P: 1 in P chance of file close+open at each op (default infinity)\n\
 	-d: debug output for all operations\n\
@@ -3059,9 +3158,13 @@ main(int argc, char **argv)
 	setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
 
 	while ((ch = getopt_long(argc, argv,
-				 "0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
+				 "0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
 				 longopts, NULL)) != EOF)
 		switch (ch) {
+		case 'a':
+			prt("main(): Atomic writes disabled\n");
+			do_atomic_writes = 0;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3578,8 @@ main(int argc, char **argv)
 		exchange_range_calls = test_exchange_range();
 	if (dontcache_io)
 		dontcache_io = test_dontcache_io();
+	if (do_atomic_writes)
+		do_atomic_writes = test_atomic_writes();
 
 	while (keep_running())
 		if (!test())
Signature · 1 line
2.49.0
#7 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

In reply to Ojaswin Mujoo
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Thanks,
Zorro

Quoted 245 lines
 ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 110 insertions(+), 5 deletions(-)
ltp/fsx.c +110 -5
index 163b9453..bdb87ca9 100644
@@ -40,6 +40,7 @@
 #include <liburing.h>
 #endif
 #include <sys/syscall.h>
+#include "statx.h"
 
 #ifndef MAP_FILE
 # define MAP_FILE 0
@@ -49,6 +50,10 @@
 #define RWF_DONTCACHE	0x80
 #endif
 
+#ifndef RWF_ATOMIC
+#define RWF_ATOMIC	0x40
+#endif
+
 #define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
 
 /* Operation flags (bitmask) */
@@ -110,6 +115,7 @@ enum {
 	OP_READ_DONTCACHE,
 	OP_WRITE,
 	OP_WRITE_DONTCACHE,
+	OP_WRITE_ATOMIC,
 	OP_MAPREAD,
 	OP_MAPWRITE,
 	OP_MAX_LITE,
@@ -200,6 +206,11 @@ int	uring = 0;
 int	mark_nr = 0;
 int	dontcache_io = 1;
 int	hugepages = 0;                  /* -h flag */
+int	do_atomic_writes = 1;		/* -a flag disables */
+
+/* User for atomic writes */
+int awu_min = 0;
+int awu_max = 0;
 
 /* Stores info needed to periodically collapse hugepages */
 struct hugepages_collapse_info {
@@ -288,6 +299,7 @@ static const char *op_names[] = {
 	[OP_READ_DONTCACHE] = "read_dontcache",
 	[OP_WRITE] = "write",
 	[OP_WRITE_DONTCACHE] = "write_dontcache",
+	[OP_WRITE_ATOMIC] = "write_atomic",
 	[OP_MAPREAD] = "mapread",
 	[OP_MAPWRITE] = "mapwrite",
 	[OP_TRUNCATE] = "truncate",
@@ -422,6 +434,7 @@ logdump(void)
 				prt("\t***RRRR***");
 			break;
 		case OP_WRITE_DONTCACHE:
+		case OP_WRITE_ATOMIC:
 		case OP_WRITE:
 			prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
 			    lp->args[0], lp->args[0] + lp->args[1] - 1,
@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size)
 	file_size = offset + size;
 }
 
+static int is_power_of_2(unsigned n) {
+	return ((n & (n - 1)) == 0);
+}
+
+/*
+ * Round down n to nearest power of 2.
+ * If n is already a power of 2, return n;
+ */
+static int rounddown_pow_of_2(int n) {
+	int i = 0;
+
+	if (is_power_of_2(n))
+		return n;
+
+	for (; (1 << i) < n; i++);
+
+	return 1 << (i - 1);
+}
+
 void
 dowrite(unsigned offset, unsigned size, int flags)
 {
@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags)
 	offset -= offset % writebdy;
 	if (o_direct)
 		size -= size % writebdy;
+	if (flags & RWF_ATOMIC) {
+		/* atomic write len must be between awu_min and awu_max */
+		if (size < awu_min)
+			size = awu_min;
+		if (size > awu_max)
+			size = awu_max;
+
+		/* atomic writes need power-of-2 sizes */
+		size = rounddown_pow_of_2(size);
+
+		/* atomic writes need naturally aligned offsets */
+		offset -= offset % size;
+
+		/* Skip the write if we are crossing max filesize */
+		if ((offset + size) > maxfilelen) {
+			if (!quiet && testcalls > simulatedopcount)
+				prt("skipping atomic write past maxfilelen\n");
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			return;
+		}
+	}
 	if (size == 0) {
 		if (!quiet && testcalls > simulatedopcount && !o_direct)
 			prt("skipping zero size write\n");
@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)
 		return;
 	}
 
-	log4(OP_WRITE, offset, size, FL_NONE);
+	if (flags & RWF_ATOMIC)
+		log4(OP_WRITE_ATOMIC, offset, size, FL_NONE);
+	else
+		log4(OP_WRITE, offset, size, FL_NONE);
 
 	gendata(original_buf, good_buf, offset, size);
 	if (offset + size > file_size) {
@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags)
 		       (monitorstart == -1 ||
 			(offset + size > monitorstart &&
 			(monitorend == -1 || offset <= monitorend))))))
-		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n", testcalls,
-		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0);
+		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n", testcalls,
+		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0,
+		    (flags & RWF_ATOMIC) != 0);
 	iret = fsxwrite(fd, good_buf + offset, size, offset, flags);
 	if (iret != size) {
 		if (iret == -1)
@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	if (o_direct != O_DIRECT) {
+		fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+				"disabling!\n");
+		return 0;
+	}
+
+	ret = xfstests_statx(AT_FDCWD, fname, 0, STATX_WRITE_ATOMIC, &stx);
+	if (ret < 0) {
+		fprintf(stderr, "main: Statx failed with %d."
+			" Failed to determine atomic write limits, "
+			" disabling!\n", ret);
+		return 0;
+	}
+
+	if (stx.stx_attributes & STATX_ATTR_WRITE_ATOMIC &&
+	    stx.stx_atomic_write_unit_min > 0) {
+		awu_min = stx.stx_atomic_write_unit_min;
+		awu_max = stx.stx_atomic_write_unit_max;
+		return 1;
+	}
+
+	fprintf(stderr, "main: IO Stack does not support "
+			"atomic writes, disabling!\n");
+	return 0;
+}
+
 #ifdef HAVE_COPY_FILE_RANGE
 int
 test_copy_range(void)
@@ -2356,6 +2443,12 @@ have_op:
 			goto out;
 		}
 		break;
+	case OP_WRITE_ATOMIC:
+		if (!do_atomic_writes) {
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			goto out;
+		}
+		break;
 	}
 
 	switch (op) {
@@ -2385,6 +2478,11 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		dowrite(offset, size, RWF_ATOMIC);
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2609,14 @@ void
 usage(void)
 {
 	fprintf(stdout, "usage: %s",
-		"fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\
+		"fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\
 	   [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\
 	   [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\
 	   [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\
 	   [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\
 	   [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\
 	   ... fname\n\
+	-a: disable atomic writes\n\
 	-b opnum: beginning operation number (default 1)\n\
 	-c P: 1 in P chance of file close+open at each op (default infinity)\n\
 	-d: debug output for all operations\n\
@@ -3059,9 +3158,13 @@ main(int argc, char **argv)
 	setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
 
 	while ((ch = getopt_long(argc, argv,
-				 "0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
+				 "0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
 				 longopts, NULL)) != EOF)
 		switch (ch) {
+		case 'a':
+			prt("main(): Atomic writes disabled\n");
+			do_atomic_writes = 0;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3578,8 @@ main(int argc, char **argv)
 		exchange_range_calls = test_exchange_range();
 	if (dontcache_io)
 		dontcache_io = test_dontcache_io();
+	if (do_atomic_writes)
+		do_atomic_writes = test_atomic_writes();
 
 	while (keep_running())
 		if (!test())
Signature · 1 line
2.49.0
#8 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Regards,
ojaswin

Quoted 249 lines

Thanks,
Zorro

Quoted 245 lines
 ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 110 insertions(+), 5 deletions(-)
ltp/fsx.c +110 -5
index 163b9453..bdb87ca9 100644
@@ -40,6 +40,7 @@
 #include <liburing.h>
 #endif
 #include <sys/syscall.h>
+#include "statx.h"
 
 #ifndef MAP_FILE
 # define MAP_FILE 0
@@ -49,6 +50,10 @@
 #define RWF_DONTCACHE	0x80
 #endif
 
+#ifndef RWF_ATOMIC
+#define RWF_ATOMIC	0x40
+#endif
+
 #define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
 
 /* Operation flags (bitmask) */
@@ -110,6 +115,7 @@ enum {
 	OP_READ_DONTCACHE,
 	OP_WRITE,
 	OP_WRITE_DONTCACHE,
+	OP_WRITE_ATOMIC,
 	OP_MAPREAD,
 	OP_MAPWRITE,
 	OP_MAX_LITE,
@@ -200,6 +206,11 @@ int	uring = 0;
 int	mark_nr = 0;
 int	dontcache_io = 1;
 int	hugepages = 0;                  /* -h flag */
+int	do_atomic_writes = 1;		/* -a flag disables */
+
+/* User for atomic writes */
+int awu_min = 0;
+int awu_max = 0;
 
 /* Stores info needed to periodically collapse hugepages */
 struct hugepages_collapse_info {
@@ -288,6 +299,7 @@ static const char *op_names[] = {
 	[OP_READ_DONTCACHE] = "read_dontcache",
 	[OP_WRITE] = "write",
 	[OP_WRITE_DONTCACHE] = "write_dontcache",
+	[OP_WRITE_ATOMIC] = "write_atomic",
 	[OP_MAPREAD] = "mapread",
 	[OP_MAPWRITE] = "mapwrite",
 	[OP_TRUNCATE] = "truncate",
@@ -422,6 +434,7 @@ logdump(void)
 				prt("\t***RRRR***");
 			break;
 		case OP_WRITE_DONTCACHE:
+		case OP_WRITE_ATOMIC:
 		case OP_WRITE:
 			prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
 			    lp->args[0], lp->args[0] + lp->args[1] - 1,
@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size)
 	file_size = offset + size;
 }
 
+static int is_power_of_2(unsigned n) {
+	return ((n & (n - 1)) == 0);
+}
+
+/*
+ * Round down n to nearest power of 2.
+ * If n is already a power of 2, return n;
+ */
+static int rounddown_pow_of_2(int n) {
+	int i = 0;
+
+	if (is_power_of_2(n))
+		return n;
+
+	for (; (1 << i) < n; i++);
+
+	return 1 << (i - 1);
+}
+
 void
 dowrite(unsigned offset, unsigned size, int flags)
 {
@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags)
 	offset -= offset % writebdy;
 	if (o_direct)
 		size -= size % writebdy;
+	if (flags & RWF_ATOMIC) {
+		/* atomic write len must be between awu_min and awu_max */
+		if (size < awu_min)
+			size = awu_min;
+		if (size > awu_max)
+			size = awu_max;
+
+		/* atomic writes need power-of-2 sizes */
+		size = rounddown_pow_of_2(size);
+
+		/* atomic writes need naturally aligned offsets */
+		offset -= offset % size;
+
+		/* Skip the write if we are crossing max filesize */
+		if ((offset + size) > maxfilelen) {
+			if (!quiet && testcalls > simulatedopcount)
+				prt("skipping atomic write past maxfilelen\n");
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			return;
+		}
+	}
 	if (size == 0) {
 		if (!quiet && testcalls > simulatedopcount && !o_direct)
 			prt("skipping zero size write\n");
@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)
 		return;
 	}
 
-	log4(OP_WRITE, offset, size, FL_NONE);
+	if (flags & RWF_ATOMIC)
+		log4(OP_WRITE_ATOMIC, offset, size, FL_NONE);
+	else
+		log4(OP_WRITE, offset, size, FL_NONE);
 
 	gendata(original_buf, good_buf, offset, size);
 	if (offset + size > file_size) {
@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags)
 		       (monitorstart == -1 ||
 			(offset + size > monitorstart &&
 			(monitorend == -1 || offset <= monitorend))))))
-		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n", testcalls,
-		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0);
+		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n", testcalls,
+		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0,
+		    (flags & RWF_ATOMIC) != 0);
 	iret = fsxwrite(fd, good_buf + offset, size, offset, flags);
 	if (iret != size) {
 		if (iret == -1)
@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	if (o_direct != O_DIRECT) {
+		fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+				"disabling!\n");
+		return 0;
+	}
+
+	ret = xfstests_statx(AT_FDCWD, fname, 0, STATX_WRITE_ATOMIC, &stx);
+	if (ret < 0) {
+		fprintf(stderr, "main: Statx failed with %d."
+			" Failed to determine atomic write limits, "
+			" disabling!\n", ret);
+		return 0;
+	}
+
+	if (stx.stx_attributes & STATX_ATTR_WRITE_ATOMIC &&
+	    stx.stx_atomic_write_unit_min > 0) {
+		awu_min = stx.stx_atomic_write_unit_min;
+		awu_max = stx.stx_atomic_write_unit_max;
+		return 1;
+	}
+
+	fprintf(stderr, "main: IO Stack does not support "
+			"atomic writes, disabling!\n");
+	return 0;
+}
+
 #ifdef HAVE_COPY_FILE_RANGE
 int
 test_copy_range(void)
@@ -2356,6 +2443,12 @@ have_op:
 			goto out;
 		}
 		break;
+	case OP_WRITE_ATOMIC:
+		if (!do_atomic_writes) {
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			goto out;
+		}
+		break;
 	}
 
 	switch (op) {
@@ -2385,6 +2478,11 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		dowrite(offset, size, RWF_ATOMIC);
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2609,14 @@ void
 usage(void)
 {
 	fprintf(stdout, "usage: %s",
-		"fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\
+		"fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\
 	   [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\
 	   [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\
 	   [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\
 	   [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\
 	   [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\
 	   ... fname\n\
+	-a: disable atomic writes\n\
 	-b opnum: beginning operation number (default 1)\n\
 	-c P: 1 in P chance of file close+open at each op (default infinity)\n\
 	-d: debug output for all operations\n\
@@ -3059,9 +3158,13 @@ main(int argc, char **argv)
 	setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
 
 	while ((ch = getopt_long(argc, argv,
-				 "0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
+				 "0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
 				 longopts, NULL)) != EOF)
 		switch (ch) {
+		case 'a':
+			prt("main(): Atomic writes disabled\n");
+			do_atomic_writes = 0;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3578,8 @@ main(int argc, char **argv)
 		exchange_range_calls = test_exchange_range();
 	if (dontcache_io)
 		dontcache_io = test_dontcache_io();
+	if (do_atomic_writes)
+		do_atomic_writes = test_atomic_writes();
 
 	while (keep_running())
 		if (!test())
Signature · 1 line
2.49.0
#9 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

[1]

263.out.bad renamed from dev/fd/63 +335 -1

338 lines not on this page · raw message

Thanks,
Zorro

Quoted 254 lines

Regards,
ojaswin

Quoted 249 lines

Thanks,
Zorro

Quoted 245 lines
 ltp/fsx.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 110 insertions(+), 5 deletions(-)
ltp/fsx.c +110 -5
index 163b9453..bdb87ca9 100644
@@ -40,6 +40,7 @@
 #include <liburing.h>
 #endif
 #include <sys/syscall.h>
+#include "statx.h"
 
 #ifndef MAP_FILE
 # define MAP_FILE 0
@@ -49,6 +50,10 @@
 #define RWF_DONTCACHE	0x80
 #endif
 
+#ifndef RWF_ATOMIC
+#define RWF_ATOMIC	0x40
+#endif
+
 #define NUMPRINTCOLUMNS 32	/* # columns of data to print on each line */
 
 /* Operation flags (bitmask) */
@@ -110,6 +115,7 @@ enum {
 	OP_READ_DONTCACHE,
 	OP_WRITE,
 	OP_WRITE_DONTCACHE,
+	OP_WRITE_ATOMIC,
 	OP_MAPREAD,
 	OP_MAPWRITE,
 	OP_MAX_LITE,
@@ -200,6 +206,11 @@ int	uring = 0;
 int	mark_nr = 0;
 int	dontcache_io = 1;
 int	hugepages = 0;                  /* -h flag */
+int	do_atomic_writes = 1;		/* -a flag disables */
+
+/* User for atomic writes */
+int awu_min = 0;
+int awu_max = 0;
 
 /* Stores info needed to periodically collapse hugepages */
 struct hugepages_collapse_info {
@@ -288,6 +299,7 @@ static const char *op_names[] = {
 	[OP_READ_DONTCACHE] = "read_dontcache",
 	[OP_WRITE] = "write",
 	[OP_WRITE_DONTCACHE] = "write_dontcache",
+	[OP_WRITE_ATOMIC] = "write_atomic",
 	[OP_MAPREAD] = "mapread",
 	[OP_MAPWRITE] = "mapwrite",
 	[OP_TRUNCATE] = "truncate",
@@ -422,6 +434,7 @@ logdump(void)
 				prt("\t***RRRR***");
 			break;
 		case OP_WRITE_DONTCACHE:
+		case OP_WRITE_ATOMIC:
 		case OP_WRITE:
 			prt("WRITE    0x%x thru 0x%x\t(0x%x bytes)",
 			    lp->args[0], lp->args[0] + lp->args[1] - 1,
@@ -1073,6 +1086,25 @@ update_file_size(unsigned offset, unsigned size)
 	file_size = offset + size;
 }
 
+static int is_power_of_2(unsigned n) {
+	return ((n & (n - 1)) == 0);
+}
+
+/*
+ * Round down n to nearest power of 2.
+ * If n is already a power of 2, return n;
+ */
+static int rounddown_pow_of_2(int n) {
+	int i = 0;
+
+	if (is_power_of_2(n))
+		return n;
+
+	for (; (1 << i) < n; i++);
+
+	return 1 << (i - 1);
+}
+
 void
 dowrite(unsigned offset, unsigned size, int flags)
 {
@@ -1081,6 +1113,27 @@ dowrite(unsigned offset, unsigned size, int flags)
 	offset -= offset % writebdy;
 	if (o_direct)
 		size -= size % writebdy;
+	if (flags & RWF_ATOMIC) {
+		/* atomic write len must be between awu_min and awu_max */
+		if (size < awu_min)
+			size = awu_min;
+		if (size > awu_max)
+			size = awu_max;
+
+		/* atomic writes need power-of-2 sizes */
+		size = rounddown_pow_of_2(size);
+
+		/* atomic writes need naturally aligned offsets */
+		offset -= offset % size;
+
+		/* Skip the write if we are crossing max filesize */
+		if ((offset + size) > maxfilelen) {
+			if (!quiet && testcalls > simulatedopcount)
+				prt("skipping atomic write past maxfilelen\n");
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			return;
+		}
+	}
 	if (size == 0) {
 		if (!quiet && testcalls > simulatedopcount && !o_direct)
 			prt("skipping zero size write\n");
@@ -1088,7 +1141,10 @@ dowrite(unsigned offset, unsigned size, int flags)
 		return;
 	}
 
-	log4(OP_WRITE, offset, size, FL_NONE);
+	if (flags & RWF_ATOMIC)
+		log4(OP_WRITE_ATOMIC, offset, size, FL_NONE);
+	else
+		log4(OP_WRITE, offset, size, FL_NONE);
 
 	gendata(original_buf, good_buf, offset, size);
 	if (offset + size > file_size) {
@@ -1108,8 +1164,9 @@ dowrite(unsigned offset, unsigned size, int flags)
 		       (monitorstart == -1 ||
 			(offset + size > monitorstart &&
 			(monitorend == -1 || offset <= monitorend))))))
-		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d\n", testcalls,
-		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0);
+		prt("%lld write\t0x%x thru\t0x%x\t(0x%x bytes)\tdontcache=%d atomic_wr=%d\n", testcalls,
+		    offset, offset + size - 1, size, (flags & RWF_DONTCACHE) != 0,
+		    (flags & RWF_ATOMIC) != 0);
 	iret = fsxwrite(fd, good_buf + offset, size, offset, flags);
 	if (iret != size) {
 		if (iret == -1)
@@ -1785,6 +1842,36 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	if (o_direct != O_DIRECT) {
+		fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+				"disabling!\n");
+		return 0;
+	}
+
+	ret = xfstests_statx(AT_FDCWD, fname, 0, STATX_WRITE_ATOMIC, &stx);
+	if (ret < 0) {
+		fprintf(stderr, "main: Statx failed with %d."
+			" Failed to determine atomic write limits, "
+			" disabling!\n", ret);
+		return 0;
+	}
+
+	if (stx.stx_attributes & STATX_ATTR_WRITE_ATOMIC &&
+	    stx.stx_atomic_write_unit_min > 0) {
+		awu_min = stx.stx_atomic_write_unit_min;
+		awu_max = stx.stx_atomic_write_unit_max;
+		return 1;
+	}
+
+	fprintf(stderr, "main: IO Stack does not support "
+			"atomic writes, disabling!\n");
+	return 0;
+}
+
 #ifdef HAVE_COPY_FILE_RANGE
 int
 test_copy_range(void)
@@ -2356,6 +2443,12 @@ have_op:
 			goto out;
 		}
 		break;
+	case OP_WRITE_ATOMIC:
+		if (!do_atomic_writes) {
+			log4(OP_WRITE_ATOMIC, offset, size, FL_SKIPPED);
+			goto out;
+		}
+		break;
 	}
 
 	switch (op) {
@@ -2385,6 +2478,11 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		dowrite(offset, size, RWF_ATOMIC);
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2609,14 @@ void
 usage(void)
 {
 	fprintf(stdout, "usage: %s",
-		"fsx [-dfhknqxyzBEFHIJKLORWXZ0]\n\
+		"fsx [-adfhknqxyzBEFHIJKLORWXZ0]\n\
 	   [-b opnum] [-c Prob] [-g filldata] [-i logdev] [-j logid]\n\
 	   [-l flen] [-m start:end] [-o oplen] [-p progressinterval]\n\
 	   [-r readbdy] [-s style] [-t truncbdy] [-w writebdy]\n\
 	   [-A|-U] [-D startingop] [-N numops] [-P dirpath] [-S seed]\n\
 	   [--replay-ops=opsfile] [--record-ops[=opsfile]] [--duration=seconds]\n\
 	   ... fname\n\
+	-a: disable atomic writes\n\
 	-b opnum: beginning operation number (default 1)\n\
 	-c P: 1 in P chance of file close+open at each op (default infinity)\n\
 	-d: debug output for all operations\n\
@@ -3059,9 +3158,13 @@ main(int argc, char **argv)
 	setvbuf(stdout, (char *)0, _IOLBF, 0); /* line buffered stdout */
 
 	while ((ch = getopt_long(argc, argv,
-				 "0b:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
+				 "0ab:c:de:fg:hi:j:kl:m:no:p:qr:s:t:uw:xyABD:EFJKHzCILN:OP:RS:UWXZ",
 				 longopts, NULL)) != EOF)
 		switch (ch) {
+		case 'a':
+			prt("main(): Atomic writes disabled\n");
+			do_atomic_writes = 0;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3578,8 @@ main(int argc, char **argv)
 		exchange_range_calls = test_exchange_range();
 	if (dontcache_io)
 		dontcache_io = test_dontcache_io();
+	if (do_atomic_writes)
+		do_atomic_writes = test_atomic_writes();
 
 	while (keep_running())
 		if (!test())
Signature · 1 line
2.49.0
#10 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

Regards,
ojaswin

#11 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#12 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 145 lines · Zorro Lang
Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

Quoted 6 lines
# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

I'm not sure how this will affect the upcoming release, if you want shall I send a small patch to make the atomic writes feature default off instead of default on till we root cause this?

Regards,
Ojaswin

Quoted 9 lines

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#13 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 179 lines · Ojaswin Mujoo
Quoted 145 lines · Zorro Lang
Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

Quoted 6 lines
# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Quoted 6 lines

I'm not sure how this will affect the upcoming release, if you want shall I send a small patch to make the atomic writes feature default off instead of default on till we root cause this?

Regards,
Ojaswin

Hi Zorro,

So I'm able to narrow down the opoerations and replicate it via the following replay file:

# -----

# replay.fsxops
# -----
write_atomic 0x57000 0x1000 0x69690
write_atomic 0x66000 0x1000 0x4de00
write_atomic 0x18000 0x1000 0x2c800
copy_range 0x20000 0x1000 0xe00 0x70e00
write_atomic 0x18000 0x1000 0x70e00
copy_range 0x21000 0x1000 0x23000 0x74218
truncate 0x0 0x11200 0x4daef *
write_atomic 0x43000 0x1000 0x11200 *
write_atomic 0x15000 0x1000 0x44000
copy_range 0xd000 0x1000 0x1d800 0x44000
mapread 0x1c000 0x1803 0x1e400 *

Command: ./ltp/fsx -N 10000 -o 8192 -l 500000 -r 4096 -t 512 -w 512 -Z -FKuHzI --replay-ops replay.fsxops $MNT/junk

$MNT/junk is always opened O_TRUNC and is an on an XFS FS where the disk is non-atomic so all RWF_ATOMIC writes are software emulated.

Here are the logs generated for this run:

Seed set to 1
main: filesystem does not support exchange range, disabling!

READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/test/junk

OFFSET      GOOD    BAD     RANGE
0x1d000     0x0000  0xf322  0x0

operation# (mod 256) for the bad data may be 243
0x1d001 0x0000 0x22f3 0x1
operation# (mod 256) for the bad data may be 243
0x1d002 0x0000 0xf391 0x2
operation# (mod 256) for the bad data may be 243
0x1d003 0x0000 0x91f3 0x3
<... a few more such lines ..>

LOG DUMP (11 total operations):
openat(AT_FDCWD, "/mnt/test/junk.fsxops", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7

1(  1 mod 256): WRITE    0x57000 thru 0x57fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
2(  2 mod 256): WRITE    0x66000 thru 0x66fff   (0x1000 bytes) HOLE ATOMIC
3(  3 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
4(  4 mod 256): COPY 0x20000 thru 0x20fff       (0x1000 bytes) to 0xe00 thru 0x1dff
5(  5 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
6(  6 mod 256): COPY 0x21000 thru 0x21fff       (0x1000 bytes) to 0x23000 thru 0x23fff
7(  7 mod 256): TRUNCATE DOWN   from 0x67000 to 0x11200 ******WWWW
8(  8 mod 256): WRITE    0x43000 thru 0x43fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
9(  9 mod 256): WRITE    0x15000 thru 0x15fff   (0x1000 bytes) ATOMIC

10( 10 mod 256): COPY 0xd000 thru 0xdfff (0x1000 bytes) to 0x1d800 thru 0x1e7ff
11( 11 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR*** Log of operations saved to "/mnt/test/junk.fsxops"; replay with --replay-ops Correct content saved for comparison
(maybe hexdump "/mnt/test/junk" vs "/mnt/test/junk.fsxgood")
+++ exited with 110 +++

We can see that the bad data is detected in the final MAPREAD operation and and bad offset is at 0x1d000. If we look at the operations dump above its clear that none of the operations should be modifying the 0x1d000 so we should have been reading 0s but yet we see some junk data there in the file:

$ hexdump /mnt/test/junk -s 0x1c000 -n0x1020
001c000 0000 0000 0000 0000 0000 0000 0000 0000
*
001d000 22f3 91f3 7ff3 3af3 39f3 23f3 6df3 c2f3
001d010 c5f3 f6f3 a6f3 1ef3 58f3 40f3 32f3 5ff3
001d020

Another thing to not is that I can't reproduce the above on scsi-debug device. @Darrick, @John, could this be an issue in kernel?

Regards,
ojaswin

Quoted 10 lines
Quoted 10 lines

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#14 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 260 lines · Ojaswin Mujoo
Quoted 179 lines · Ojaswin Mujoo
Quoted 145 lines · Zorro Lang
Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

Quoted 6 lines
# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Quoted 6 lines

I'm not sure how this will affect the upcoming release, if you want shall I send a small patch to make the atomic writes feature default off instead of default on till we root cause this?

Regards,
Ojaswin

Hi Zorro,

So I'm able to narrow down the opoerations and replicate it via the following replay file:

# -----

# replay.fsxops
# -----
write_atomic 0x57000 0x1000 0x69690
write_atomic 0x66000 0x1000 0x4de00
write_atomic 0x18000 0x1000 0x2c800
copy_range 0x20000 0x1000 0xe00 0x70e00
write_atomic 0x18000 0x1000 0x70e00
copy_range 0x21000 0x1000 0x23000 0x74218
truncate 0x0 0x11200 0x4daef *
write_atomic 0x43000 0x1000 0x11200 *
write_atomic 0x15000 0x1000 0x44000
copy_range 0xd000 0x1000 0x1d800 0x44000
mapread 0x1c000 0x1803 0x1e400 *

Command: ./ltp/fsx -N 10000 -o 8192 -l 500000 -r 4096 -t 512 -w 512 -Z -FKuHzI --replay-ops replay.fsxops $MNT/junk

$MNT/junk is always opened O_TRUNC and is an on an XFS FS where the disk is non-atomic so all RWF_ATOMIC writes are software emulated.

Here are the logs generated for this run:

Seed set to 1
main: filesystem does not support exchange range, disabling!

READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/test/junk

OFFSET      GOOD    BAD     RANGE
0x1d000     0x0000  0xf322  0x0

operation# (mod 256) for the bad data may be 243
0x1d001 0x0000 0x22f3 0x1
operation# (mod 256) for the bad data may be 243
0x1d002 0x0000 0xf391 0x2
operation# (mod 256) for the bad data may be 243
0x1d003 0x0000 0x91f3 0x3
<... a few more such lines ..>

LOG DUMP (11 total operations):
openat(AT_FDCWD, "/mnt/test/junk.fsxops", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7

1(  1 mod 256): WRITE    0x57000 thru 0x57fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
2(  2 mod 256): WRITE    0x66000 thru 0x66fff   (0x1000 bytes) HOLE ATOMIC
3(  3 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
4(  4 mod 256): COPY 0x20000 thru 0x20fff       (0x1000 bytes) to 0xe00 thru 0x1dff
5(  5 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
6(  6 mod 256): COPY 0x21000 thru 0x21fff       (0x1000 bytes) to 0x23000 thru 0x23fff
7(  7 mod 256): TRUNCATE DOWN   from 0x67000 to 0x11200 ******WWWW
8(  8 mod 256): WRITE    0x43000 thru 0x43fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
9(  9 mod 256): WRITE    0x15000 thru 0x15fff   (0x1000 bytes) ATOMIC

10( 10 mod 256): COPY 0xd000 thru 0xdfff (0x1000 bytes) to 0x1d800 thru 0x1e7ff
11( 11 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR*** Log of operations saved to "/mnt/test/junk.fsxops"; replay with --replay-ops Correct content saved for comparison
(maybe hexdump "/mnt/test/junk" vs "/mnt/test/junk.fsxgood")
+++ exited with 110 +++

We can see that the bad data is detected in the final MAPREAD operation and and bad offset is at 0x1d000. If we look at the operations dump above its clear that none of the operations should be modifying the 0x1d000 so we should have been reading 0s but yet we see some junk data there in the file:

$ hexdump /mnt/test/junk -s 0x1c000 -n0x1020
001c000 0000 0000 0000 0000 0000 0000 0000 0000
*
001d000 22f3 91f3 7ff3 3af3 39f3 23f3 6df3 c2f3
001d010 c5f3 f6f3 a6f3 1ef3 58f3 40f3 32f3 5ff3
001d020
Quoted 2 lines

Another thing to not is that I can't reproduce the above on scsi-debug device. @Darrick, @John, could this be an issue in kernel?

Hi Ojaswin,

If we can be sure this's a kernel bug, rather than a fstests (patch) issue, I think we can merge this patchset to expose this bug. Does this make sense to you and others?

Thanks,
Zorro

Quoted 14 lines

Regards,
ojaswin

Quoted 10 lines
Quoted 10 lines

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#15 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 261 lines · Zorro Lang
Quoted 260 lines · Ojaswin Mujoo
Quoted 179 lines · Ojaswin Mujoo
Quoted 145 lines · Zorro Lang
Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

Quoted 6 lines
# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Quoted 6 lines

I'm not sure how this will affect the upcoming release, if you want shall I send a small patch to make the atomic writes feature default off instead of default on till we root cause this?

Regards,
Ojaswin

Hi Zorro,

So I'm able to narrow down the opoerations and replicate it via the following replay file:

# -----

# replay.fsxops
# -----
write_atomic 0x57000 0x1000 0x69690
write_atomic 0x66000 0x1000 0x4de00
write_atomic 0x18000 0x1000 0x2c800
copy_range 0x20000 0x1000 0xe00 0x70e00
write_atomic 0x18000 0x1000 0x70e00
copy_range 0x21000 0x1000 0x23000 0x74218
truncate 0x0 0x11200 0x4daef *
write_atomic 0x43000 0x1000 0x11200 *
write_atomic 0x15000 0x1000 0x44000
copy_range 0xd000 0x1000 0x1d800 0x44000
mapread 0x1c000 0x1803 0x1e400 *

Command: ./ltp/fsx -N 10000 -o 8192 -l 500000 -r 4096 -t 512 -w 512 -Z -FKuHzI --replay-ops replay.fsxops $MNT/junk

$MNT/junk is always opened O_TRUNC and is an on an XFS FS where the disk is non-atomic so all RWF_ATOMIC writes are software emulated.

Here are the logs generated for this run:

Seed set to 1
main: filesystem does not support exchange range, disabling!

READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/test/junk

OFFSET      GOOD    BAD     RANGE
0x1d000     0x0000  0xf322  0x0

operation# (mod 256) for the bad data may be 243
0x1d001 0x0000 0x22f3 0x1
operation# (mod 256) for the bad data may be 243
0x1d002 0x0000 0xf391 0x2
operation# (mod 256) for the bad data may be 243
0x1d003 0x0000 0x91f3 0x3
<... a few more such lines ..>

LOG DUMP (11 total operations):
openat(AT_FDCWD, "/mnt/test/junk.fsxops", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7

1(  1 mod 256): WRITE    0x57000 thru 0x57fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
2(  2 mod 256): WRITE    0x66000 thru 0x66fff   (0x1000 bytes) HOLE ATOMIC
3(  3 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
4(  4 mod 256): COPY 0x20000 thru 0x20fff       (0x1000 bytes) to 0xe00 thru 0x1dff
5(  5 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
6(  6 mod 256): COPY 0x21000 thru 0x21fff       (0x1000 bytes) to 0x23000 thru 0x23fff
7(  7 mod 256): TRUNCATE DOWN   from 0x67000 to 0x11200 ******WWWW
8(  8 mod 256): WRITE    0x43000 thru 0x43fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
9(  9 mod 256): WRITE    0x15000 thru 0x15fff   (0x1000 bytes) ATOMIC

10( 10 mod 256): COPY 0xd000 thru 0xdfff (0x1000 bytes) to 0x1d800 thru 0x1e7ff
11( 11 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR*** Log of operations saved to "/mnt/test/junk.fsxops"; replay with --replay-ops Correct content saved for comparison
(maybe hexdump "/mnt/test/junk" vs "/mnt/test/junk.fsxgood")
+++ exited with 110 +++

We can see that the bad data is detected in the final MAPREAD operation and and bad offset is at 0x1d000. If we look at the operations dump above its clear that none of the operations should be modifying the 0x1d000 so we should have been reading 0s but yet we see some junk data there in the file:

$ hexdump /mnt/test/junk -s 0x1c000 -n0x1020
001c000 0000 0000 0000 0000 0000 0000 0000 0000
*
001d000 22f3 91f3 7ff3 3af3 39f3 23f3 6df3 c2f3
001d010 c5f3 f6f3 a6f3 1ef3 58f3 40f3 32f3 5ff3
001d020
Quoted 7 lines
Quoted 2 lines

Another thing to not is that I can't reproduce the above on scsi-debug device. @Darrick, @John, could this be an issue in kernel?

Hi Ojaswin,

If we can be sure this's a kernel bug, rather than a fstests (patch) issue, I think we can merge this patchset to expose this bug. Does this make sense to you and others?

Looks like a kernel bug to me...

--D

Quoted 19 lines

Thanks,
Zorro

Quoted 14 lines

Regards,
ojaswin

Quoted 10 lines
Quoted 10 lines

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#16 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 267 lines · Darrick J. Wong
Quoted 266 lines · Zorro Lang
Quoted 260 lines · Ojaswin Mujoo
Quoted 179 lines · Ojaswin Mujoo
Quoted 145 lines · Zorro Lang
Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

Quoted 6 lines
# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Quoted 6 lines

I'm not sure how this will affect the upcoming release, if you want shall I send a small patch to make the atomic writes feature default off instead of default on till we root cause this?

Regards,
Ojaswin

Hi Zorro,

So I'm able to narrow down the opoerations and replicate it via the following replay file:

# -----

# replay.fsxops
# -----
write_atomic 0x57000 0x1000 0x69690
write_atomic 0x66000 0x1000 0x4de00
write_atomic 0x18000 0x1000 0x2c800
copy_range 0x20000 0x1000 0xe00 0x70e00
write_atomic 0x18000 0x1000 0x70e00
copy_range 0x21000 0x1000 0x23000 0x74218
truncate 0x0 0x11200 0x4daef *
write_atomic 0x43000 0x1000 0x11200 *
write_atomic 0x15000 0x1000 0x44000
copy_range 0xd000 0x1000 0x1d800 0x44000
mapread 0x1c000 0x1803 0x1e400 *

Command: ./ltp/fsx -N 10000 -o 8192 -l 500000 -r 4096 -t 512 -w 512 -Z -FKuHzI --replay-ops replay.fsxops $MNT/junk

$MNT/junk is always opened O_TRUNC and is an on an XFS FS where the disk is non-atomic so all RWF_ATOMIC writes are software emulated.

Here are the logs generated for this run:

Seed set to 1
main: filesystem does not support exchange range, disabling!

READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/test/junk

OFFSET      GOOD    BAD     RANGE
0x1d000     0x0000  0xf322  0x0

operation# (mod 256) for the bad data may be 243
0x1d001 0x0000 0x22f3 0x1
operation# (mod 256) for the bad data may be 243
0x1d002 0x0000 0xf391 0x2
operation# (mod 256) for the bad data may be 243
0x1d003 0x0000 0x91f3 0x3
<... a few more such lines ..>

LOG DUMP (11 total operations):
openat(AT_FDCWD, "/mnt/test/junk.fsxops", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7

1(  1 mod 256): WRITE    0x57000 thru 0x57fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
2(  2 mod 256): WRITE    0x66000 thru 0x66fff   (0x1000 bytes) HOLE ATOMIC
3(  3 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
4(  4 mod 256): COPY 0x20000 thru 0x20fff       (0x1000 bytes) to 0xe00 thru 0x1dff
5(  5 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
6(  6 mod 256): COPY 0x21000 thru 0x21fff       (0x1000 bytes) to 0x23000 thru 0x23fff
7(  7 mod 256): TRUNCATE DOWN   from 0x67000 to 0x11200 ******WWWW
8(  8 mod 256): WRITE    0x43000 thru 0x43fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
9(  9 mod 256): WRITE    0x15000 thru 0x15fff   (0x1000 bytes) ATOMIC

10( 10 mod 256): COPY 0xd000 thru 0xdfff (0x1000 bytes) to 0x1d800 thru 0x1e7ff
11( 11 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR*** Log of operations saved to "/mnt/test/junk.fsxops"; replay with --replay-ops Correct content saved for comparison
(maybe hexdump "/mnt/test/junk" vs "/mnt/test/junk.fsxgood")
+++ exited with 110 +++

We can see that the bad data is detected in the final MAPREAD operation and and bad offset is at 0x1d000. If we look at the operations dump above its clear that none of the operations should be modifying the 0x1d000 so we should have been reading 0s but yet we see some junk data there in the file:

$ hexdump /mnt/test/junk -s 0x1c000 -n0x1020
001c000 0000 0000 0000 0000 0000 0000 0000 0000
*
001d000 22f3 91f3 7ff3 3af3 39f3 23f3 6df3 c2f3
001d010 c5f3 f6f3 a6f3 1ef3 58f3 40f3 32f3 5ff3
001d020
Quoted 2 lines

Another thing to not is that I can't reproduce the above on scsi-debug device. @Darrick, @John, could this be an issue in kernel?

Hi Ojaswin,

Quoted 4 lines
Quoted 2 lines

If we can be sure this's a kernel bug, rather than a fstests (patch) issue, I think we can merge this patchset to expose this bug. Does this make sense to you and others?

Looks like a kernel bug to me...

Thanks Darrick! If I merge this patchset, the fstests users might hit some unexpected fsx test failures in their regular regression test. I don't want to make panic, if no one mind that, I'll merge it. Or do you want to disable the atomic write fsx test by default currently?

Thanks,
Zorro

Quoted 23 lines

--D

Quoted 19 lines

Thanks,
Zorro

Quoted 14 lines

Regards,
ojaswin

Quoted 10 lines
Quoted 10 lines

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#17 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 271 lines · Zorro Lang
Quoted 270 lines · Darrick J. Wong
Quoted 268 lines · Zorro Lang
Quoted 260 lines · Ojaswin Mujoo
Quoted 179 lines · Ojaswin Mujoo
Quoted 145 lines · Zorro Lang
Quoted 135 lines · Ojaswin Mujoo
Quoted 34 lines · Zorro Lang
Quoted 24 lines · Ojaswin Mujoo
Quoted 10 lines · Zorro Lang
Quoted 8 lines · Ojaswin Mujoo

Implement atomic write support to help fuzz atomic writes with fsx.

---

Quoted 4 lines

Hmm... this patch causes more regular fsx test cases fail on old kernel, (e.g. g/760, g/617, g/263 ...) except set "FSX_AVOID=-a". Is there a way to disable "atomic write" automatically if it's not supported by current system?

Hi Zorro,
Sorry for being late, I've been on vacation this week.

Yes so by design we should be automatically disabling atomic writes when they are not supported by the stack but seems like the issue is that when we do disable it we print some extra messages to stdout/err which show up in the xfstests output causing failure.

Quoted 8 lines

I can think of 2 ways around this:

1. Don't print anything and just silently drop atomic writes if stack doesn't support them.

2. Make atomic writes as a default off instead of default on feature but his loses a bit of coverage as existing tests wont get atomic write testing free of cost any more.

Quoted 6 lines

Hi Ojaswin,

Please have a nice vacation :)

It's not the "extra messages" cause failure, those "quiet" failures can be fixed by:

Oh okay got it.

Quoted 37 lines
ltp/fsx.c +6 -4
index bdb87ca90..0a035b37b 100644
@@ -1847,8 +1847,9 @@ int test_atomic_writes(void) {
        struct statx stx;
 
        if (o_direct != O_DIRECT) {
-               fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
-                               "disabling!\n");
+               if (!quiet)
+                       fprintf(stderr, "main: atomic writes need O_DIRECT (-Z), "
+                                       "disabling!\n");
                return 0;
        }
 
@@ -1867,8 +1868,9 @@ int test_atomic_writes(void) {
                return 1;
        }
 
-       fprintf(stderr, "main: IO Stack does not support "
-                       "atomic writes, disabling!\n");
+       if (!quiet)
+               fprintf(stderr, "main: IO Stack does not support "
+                               "atomic writes, disabling!\n");
        return 0;
 }
 

But I hit more read or write failures e.g. [1], this failure can't be reproduced with FSX_AVOID=-a. Is it a atomic write bug or an unexpected test failure?

Thanks,
Zorro

<...>

Quoted 51 lines

+244(244 mod 256): SKIPPED (no operation)
+245(245 mod 256): FALLOC 0x695c5 thru 0x6a2e6 (0xd21 bytes) INTERIOR
+246(246 mod 256): MAPWRITE 0x5ac00 thru 0x5b185 (0x586 bytes)
+247(247 mod 256): WRITE 0x31200 thru 0x313ff (0x200 bytes)
+248(248 mod 256): SKIPPED (no operation)
+249(249 mod 256): TRUNCATE DOWN from 0x78242 to 0xf200 ******WWWW
+250(250 mod 256): FALLOC 0x65000 thru 0x66f26 (0x1f26 bytes) PAST_EOF
+251(251 mod 256): WRITE 0x45400 thru 0x467ff (0x1400 bytes) HOLE ***WWWW
+252(252 mod 256): SKIPPED (no operation)
+253(253 mod 256): SKIPPED (no operation)
+254(254 mod 256): MAPWRITE 0x4be00 thru 0x4daee (0x1cef bytes)
+255(255 mod 256): MAPREAD 0xc000 thru 0xcae9 (0xaea bytes)

+256(  0 mod 256): READ     0x3e000 thru 0x3efff	(0x1000 bytes)
+257(  1 mod 256): SKIPPED (no operation)
+258(  2 mod 256): INSERT 0x45000 thru 0x45fff	(0x1000 bytes)
+259(  3 mod 256): ZERO     0x1d7d5 thru 0x1f399	(0x1bc5 bytes)	******ZZZZ
+260(  4 mod 256): TRUNCATE DOWN	from 0x4eaef to 0x11200	******WWWW
+261(  5 mod 256): WRITE    0x43000 thru 0x43fff	(0x1000 bytes) HOLE	***WWWW
+262(  6 mod 256): WRITE    0x2200 thru 0x31ff	(0x1000 bytes)
+263(  7 mod 256): WRITE    0x15000 thru 0x15fff	(0x1000 bytes)
+264(  8 mod 256): WRITE    0x2e400 thru 0x2e7ff	(0x400 bytes)
+265(  9 mod 256): COPY 0xd000 thru 0xdfff	(0x1000 bytes) to 0x1d800 thru 0x1e7ff	******EEEE

+266( 10 mod 256): CLONE 0x2a000 thru 0x2afff (0x1000 bytes) to 0x21000 thru 0x21fff
+267( 11 mod 256): MAPREAD 0x31000 thru 0x31d0a (0xd0b bytes)
+268( 12 mod 256): SKIPPED (no operation)
+269( 13 mod 256): WRITE 0x25000 thru 0x25fff (0x1000 bytes)
+270( 14 mod 256): SKIPPED (no operation)

+271( 15 mod 256): MAPREAD  0x30000 thru 0x30577	(0x578 bytes)
+272( 16 mod 256): PUNCH    0x1a267 thru 0x1c093	(0x1e2d bytes)
+273( 17 mod 256): MAPREAD  0x1f000 thru 0x1f9c9	(0x9ca bytes)
+274( 18 mod 256): WRITE    0x40800 thru 0x40dff	(0x600 bytes)

+275( 19 mod 256): SKIPPED (no operation)
+276( 20 mod 256): MAPWRITE 0x20600 thru 0x22115 (0x1b16 bytes)
+277( 21 mod 256): MAPWRITE 0x3d000 thru 0x3ee5a (0x1e5b bytes)
+278( 22 mod 256): WRITE 0x2ee00 thru 0x2efff (0x200 bytes)
+279( 23 mod 256): WRITE 0x76200 thru 0x769ff (0x800 bytes) HOLE
+280( 24 mod 256): SKIPPED (no operation)
+281( 25 mod 256): SKIPPED (no operation)
+282( 26 mod 256): MAPREAD 0xa000 thru 0xa5e7 (0x5e8 bytes)
+283( 27 mod 256): SKIPPED (no operation)
+284( 28 mod 256): SKIPPED (no operation)
+285( 29 mod 256): SKIPPED (no operation)
+286( 30 mod 256): SKIPPED (no operation)
+287( 31 mod 256): COLLAPSE 0x11000 thru 0x11fff (0x1000 bytes)
+288( 32 mod 256): COPY 0x5d000 thru 0x5dfff (0x1000 bytes) to 0x4ca00 thru 0x4d9ff
+289( 33 mod 256): TRUNCATE DOWN from 0x75a00 to 0x1e400
+290( 34 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR***

ltp/fsx.c +3 -0
+Log of operations saved to "/mnt/xfstests/test/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/xfstests/test/junk" vs "/mnt/xfstests/test/junk.fsxgood")
Quoted 5 lines
Quoted 2 lines

Thanks,
Zorro

Hi Zorro, just to confirm is this on an older kernel that doesnt support RWF_ATOMIC or on a kernle that does support it.

I tested on linux 6.16 and current latest linux v6.17+ (will be 6.18-rc1 later). About the RWF_ATOMIC flag in my system:

Quoted 6 lines
# grep -rsn RWF_ATOMIC /usr/include/
/usr/include/bits/uio-ext.h:51:#define RWF_ATOMIC       0x00000040 /* Write is to be issued with torn-write
/usr/include/linux/fs.h:424:#define RWF_ATOMIC  ((__kernel_rwf_t)0x00000040)
/usr/include/linux/fs.h:431:                     RWF_APPEND | RWF_NOAPPEND | RWF_ATOMIC |\
/usr/include/xfs/linux.h:236:#ifndef RWF_ATOMIC
/usr/include/xfs/linux.h:237:#define RWF_ATOMIC ((__kernel_rwf_t)0x00000040)

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Quoted 6 lines

I'm not sure how this will affect the upcoming release, if you want shall I send a small patch to make the atomic writes feature default off instead of default on till we root cause this?

Regards,
Ojaswin

Hi Zorro,

So I'm able to narrow down the opoerations and replicate it via the following replay file:

# -----

# replay.fsxops
# -----
write_atomic 0x57000 0x1000 0x69690
write_atomic 0x66000 0x1000 0x4de00
write_atomic 0x18000 0x1000 0x2c800
copy_range 0x20000 0x1000 0xe00 0x70e00
write_atomic 0x18000 0x1000 0x70e00
copy_range 0x21000 0x1000 0x23000 0x74218
truncate 0x0 0x11200 0x4daef *
write_atomic 0x43000 0x1000 0x11200 *
write_atomic 0x15000 0x1000 0x44000
copy_range 0xd000 0x1000 0x1d800 0x44000
mapread 0x1c000 0x1803 0x1e400 *

Command: ./ltp/fsx -N 10000 -o 8192 -l 500000 -r 4096 -t 512 -w 512 -Z -FKuHzI --replay-ops replay.fsxops $MNT/junk

$MNT/junk is always opened O_TRUNC and is an on an XFS FS where the disk is non-atomic so all RWF_ATOMIC writes are software emulated.

Here are the logs generated for this run:

Seed set to 1
main: filesystem does not support exchange range, disabling!

READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/test/junk

OFFSET      GOOD    BAD     RANGE
0x1d000     0x0000  0xf322  0x0

operation# (mod 256) for the bad data may be 243
0x1d001 0x0000 0x22f3 0x1
operation# (mod 256) for the bad data may be 243
0x1d002 0x0000 0xf391 0x2
operation# (mod 256) for the bad data may be 243
0x1d003 0x0000 0x91f3 0x3
<... a few more such lines ..>

LOG DUMP (11 total operations):
openat(AT_FDCWD, "/mnt/test/junk.fsxops", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7

1(  1 mod 256): WRITE    0x57000 thru 0x57fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
2(  2 mod 256): WRITE    0x66000 thru 0x66fff   (0x1000 bytes) HOLE ATOMIC
3(  3 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
4(  4 mod 256): COPY 0x20000 thru 0x20fff       (0x1000 bytes) to 0xe00 thru 0x1dff
5(  5 mod 256): WRITE    0x18000 thru 0x18fff   (0x1000 bytes) ATOMIC
6(  6 mod 256): COPY 0x21000 thru 0x21fff       (0x1000 bytes) to 0x23000 thru 0x23fff
7(  7 mod 256): TRUNCATE DOWN   from 0x67000 to 0x11200 ******WWWW
8(  8 mod 256): WRITE    0x43000 thru 0x43fff   (0x1000 bytes) HOLE     ***WWWW ATOMIC
9(  9 mod 256): WRITE    0x15000 thru 0x15fff   (0x1000 bytes) ATOMIC

10( 10 mod 256): COPY 0xd000 thru 0xdfff (0x1000 bytes) to 0x1d800 thru 0x1e7ff
11( 11 mod 256): MAPREAD 0x1c000 thru 0x1d802 (0x1803 bytes) ***RRRR*** Log of operations saved to "/mnt/test/junk.fsxops"; replay with --replay-ops Correct content saved for comparison
(maybe hexdump "/mnt/test/junk" vs "/mnt/test/junk.fsxgood")
+++ exited with 110 +++

We can see that the bad data is detected in the final MAPREAD operation and and bad offset is at 0x1d000. If we look at the operations dump above its clear that none of the operations should be modifying the 0x1d000 so we should have been reading 0s but yet we see some junk data there in the file:

$ hexdump /mnt/test/junk -s 0x1c000 -n0x1020
001c000 0000 0000 0000 0000 0000 0000 0000 0000
*
001d000 22f3 91f3 7ff3 3af3 39f3 23f3 6df3 c2f3
001d010 c5f3 f6f3 a6f3 1ef3 58f3 40f3 32f3 5ff3
001d020
Quoted 2 lines

Another thing to not is that I can't reproduce the above on scsi-debug device. @Darrick, @John, could this be an issue in kernel?

Hi Ojaswin,

If we can be sure this's a kernel bug, rather than a fstests (patch) issue, I think we can merge this patchset to expose this bug. Does this make sense to you and others?

Quoted 6 lines
Quoted 1 line

Looks like a kernel bug to me...

Thanks Darrick! If I merge this patchset, the fstests users might hit some unexpected fsx test failures in their regular regression test. I don't want to make panic, if no one mind that, I'll merge it. Or do you want to disable the atomic write fsx test by default currently?

Nah, leave it enabled, or else nobody will be testing it.

--D

Quoted 28 lines

Thanks,
Zorro

Quoted 23 lines

--D

Quoted 19 lines

Thanks,
Zorro

Quoted 14 lines

Regards,
ojaswin

Quoted 10 lines
Quoted 10 lines

Thanks,
Zorro

Quoted 4 lines

Regards,
ojaswin

#18 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

In reply to Ojaswin Mujoo
Quoted 22 lines · Ojaswin Mujoo

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

Quoted 3 lines

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Hi Ojaswin,

Sorry for the very slow response.

Are you still checking this issue?

To replicate, should I just take latest xfs kernel and run this series on top of latest xfstests? Is it 100% reproducible?

Thanks,
John

#19 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 32 lines · John Garry
Quoted 22 lines · Ojaswin Mujoo

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

Quoted 3 lines

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Hi Ojaswin,

Sorry for the very slow response.

Quoted 7 lines

Are you still checking this issue?

To replicate, should I just take latest xfs kernel and run this series on top of latest xfstests? Is it 100% reproducible?

Thanks,
John

Hi John,

Yes Im looking into it but I'm now starting to run into some reflink/cow based concepts that are taking time to understand. Let me share what I have till now:

So the test.sh that I'm using can be found here [1] which just uses an fsx replay file (which replays all operations) present in the same repo [2]. If you see the replay file, there are a bunch of random operations followed by the last 2 commented out operations:

# copy_range 0xd000 0x1000 0x1d800 0x44000   <--- # operations <start> <len> <dest of copy> <filesize (can be ignored)>
# mapread 0x1e000 0x1000 0x1e400 *

The copy_range here is the one which causes (or exposes) the corruption at 0x1e800 (the end of copy range destination gets corrupted).

To have more control, I commented these 2 operations and am doing it by hand in the test.sh file, with xfs_io. I'm also using a non atomic write device so we only have S/W fallback.

Now some observations:

1. The copy_range operations is actually copying from a hole to a hole, so we should be reading all 0s. But What I see is the following happening:

vfs_copy_file_range
do_splice_direct
do_splice_direct_actor
do_splice_read
# Adds the folio at src offset to the pipe. I confirmed this is all 0x0.
splice_direct_to_actor
direct_splice_actor
do_splice_from
iter_file_splice_write
xfs_file_write_iter
xfs_file_buffered_write
iomap_file_buferred_write
iomap_iter
xfs_buferred_write_iomap_begin
# Here we correctly see that there is noting at the # destination in data fork, but somehow we find a mapped # extent in cow fork which is returned to iomap.
iomap_write_iter
__iomap_write_begin
# Here we notice folio is not uptodate and call
# iomap_read_folio_range() to read from the cow_fork # mapping we found earlier. This results in folio having # incorrect data at 0x1e800 offset.

So it seems like the fsx operations might be corrupting the cow fork state somehow leading to stale data exposure.

2. If we disable atomic writes we dont hit the issue.

3. If I do a -c pread of the destination range before doing the copy_range operation then I don't see the corruption any more.

I'm now trying to figure out why the mapping returned is not IOMAP_HOLE as it should be. I don't know the COW path in xfs so there are some gaps in my understanding. Let me know if you need any other information since I'm reliably able to replicate on 6.17.0-rc4.

[1]
https://github.com/OjaswinM/fsx-aw-issue/tree/master

[2] https://github.com/OjaswinM/fsx-aw-issue/blob/master/repro.fsxops

regards,
ojaswin

#20 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 96 lines · Ojaswin Mujoo
Quoted 32 lines · John Garry
Quoted 22 lines · Ojaswin Mujoo

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

Quoted 3 lines

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Hi Ojaswin,

Sorry for the very slow response.

Quoted 7 lines

Are you still checking this issue?

To replicate, should I just take latest xfs kernel and run this series on top of latest xfstests? Is it 100% reproducible?

Thanks,
John

Hi John,

Yes Im looking into it but I'm now starting to run into some reflink/cow based concepts that are taking time to understand. Let me share what I have till now:

So the test.sh that I'm using can be found here [1] which just uses an fsx replay file (which replays all operations) present in the same repo [2]. If you see the replay file, there are a bunch of random operations followed by the last 2 commented out operations:

# copy_range 0xd000 0x1000 0x1d800 0x44000   <--- # operations <start> <len> <dest of copy> <filesize (can be ignored)>
# mapread 0x1e000 0x1000 0x1e400 *

The copy_range here is the one which causes (or exposes) the corruption at 0x1e800 (the end of copy range destination gets corrupted).

To have more control, I commented these 2 operations and am doing it by hand in the test.sh file, with xfs_io. I'm also using a non atomic write device so we only have S/W fallback.

Now some observations:

1. The copy_range operations is actually copying from a hole to a hole, so we should be reading all 0s. But What I see is the following happening:

vfs_copy_file_range
do_splice_direct
do_splice_direct_actor
do_splice_read
# Adds the folio at src offset to the pipe. I confirmed this is all 0x0.
splice_direct_to_actor
direct_splice_actor
do_splice_from
iter_file_splice_write
xfs_file_write_iter
xfs_file_buffered_write
iomap_file_buferred_write
iomap_iter
xfs_buferred_write_iomap_begin
# Here we correctly see that there is noting at the # destination in data fork, but somehow we find a mapped # extent in cow fork which is returned to iomap.
iomap_write_iter
__iomap_write_begin
# Here we notice folio is not uptodate and call
# iomap_read_folio_range() to read from the cow_fork # mapping we found earlier. This results in folio having # incorrect data at 0x1e800 offset.

So it seems like the fsx operations might be corrupting the cow fork state somehow leading to stale data exposure.

2. If we disable atomic writes we dont hit the issue.

Quoted 8 lines

3. If I do a -c pread of the destination range before doing the copy_range operation then I don't see the corruption any more.

I'm now trying to figure out why the mapping returned is not IOMAP_HOLE as it should be. I don't know the COW path in xfs so there are some gaps in my understanding. Let me know if you need any other information since I'm reliably able to replicate on 6.17.0-rc4.

I haven't followed your issue closely, but just on this hole vs. COW thing, XFS has a bit of a quirk where speculative COW fork preallocation can expand out over holes in the data fork. If iomap lookup for buffered write sees COW fork blocks present, it reports those blocks as the primary mapping even if the data fork happens to be a hole (since there's no point in allocating blocks to the data fork when we can just remap).

Again I've no idea if this relates to your issue or what you're referring to as a hole (i.e. data fork only?), but just pointing it out. The latest iomap/xfs patches I posted a few days ago kind of dance around this a bit, but I was somewhat hoping that maybe the cleanups there would trigger some thoughts on better iomap reporting in that regard.

Brian

Quoted 8 lines

[1]
https://github.com/OjaswinM/fsx-aw-issue/tree/master

[2] https://github.com/OjaswinM/fsx-aw-issue/blob/master/repro.fsxops

regards,
ojaswin

#21 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 114 lines · Brian Foster
Quoted 96 lines · Ojaswin Mujoo
Quoted 32 lines · John Garry
Quoted 22 lines · Ojaswin Mujoo

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

Quoted 3 lines

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Hi Ojaswin,

Sorry for the very slow response.

Quoted 7 lines

Are you still checking this issue?

To replicate, should I just take latest xfs kernel and run this series on top of latest xfstests? Is it 100% reproducible?

Thanks,
John

Hi John,

Yes Im looking into it but I'm now starting to run into some reflink/cow based concepts that are taking time to understand. Let me share what I have till now:

So the test.sh that I'm using can be found here [1] which just uses an fsx replay file (which replays all operations) present in the same repo [2]. If you see the replay file, there are a bunch of random operations followed by the last 2 commented out operations:

# copy_range 0xd000 0x1000 0x1d800 0x44000   <--- # operations <start> <len> <dest of copy> <filesize (can be ignored)>
# mapread 0x1e000 0x1000 0x1e400 *

The copy_range here is the one which causes (or exposes) the corruption at 0x1e800 (the end of copy range destination gets corrupted).

To have more control, I commented these 2 operations and am doing it by hand in the test.sh file, with xfs_io. I'm also using a non atomic write device so we only have S/W fallback.

Now some observations:

1. The copy_range operations is actually copying from a hole to a hole, so we should be reading all 0s. But What I see is the following happening:

vfs_copy_file_range
do_splice_direct
do_splice_direct_actor
do_splice_read
# Adds the folio at src offset to the pipe. I confirmed this is all 0x0.
splice_direct_to_actor
direct_splice_actor
do_splice_from
iter_file_splice_write
xfs_file_write_iter
xfs_file_buffered_write
iomap_file_buferred_write
iomap_iter
xfs_buferred_write_iomap_begin
# Here we correctly see that there is noting at the # destination in data fork, but somehow we find a mapped # extent in cow fork which is returned to iomap.
iomap_write_iter
__iomap_write_begin
# Here we notice folio is not uptodate and call
# iomap_read_folio_range() to read from the cow_fork # mapping we found earlier. This results in folio having # incorrect data at 0x1e800 offset.

So it seems like the fsx operations might be corrupting the cow fork state somehow leading to stale data exposure.

2. If we disable atomic writes we dont hit the issue.

Quoted 8 lines

3. If I do a -c pread of the destination range before doing the copy_range operation then I don't see the corruption any more.

I'm now trying to figure out why the mapping returned is not IOMAP_HOLE as it should be. I don't know the COW path in xfs so there are some gaps in my understanding. Let me know if you need any other information since I'm reliably able to replicate on 6.17.0-rc4.

I haven't followed your issue closely, but just on this hole vs. COW thing, XFS has a bit of a quirk where speculative COW fork preallocation can expand out over holes in the data fork. If iomap lookup for buffered write sees COW fork blocks present, it reports those blocks as the primary mapping even if the data fork happens to be a hole (since there's no point in allocating blocks to the data fork when we can just remap).

Quoted 6 lines

Again I've no idea if this relates to your issue or what you're referring to as a hole (i.e. data fork only?), but just pointing it out. The latest iomap/xfs patches I posted a few days ago kind of dance around this a bit, but I was somewhat hoping that maybe the cleanups there would trigger some thoughts on better iomap reporting in that regard.

Hi Brian, Thanks for the details and yes by "hole" i did mean hole in data fork only. The part that I'm now confused about is does this sort of preallocation extent hold any valid data? IIUC it should not, so I would expect it to trigger iomap_block_needs_zeroing() to write zeroes to the folio. Instead, what I see in the issue is that we are trying to do disk read.

Regards,
ojaswin

Quoted 13 lines

Brian

Quoted 8 lines

[1]
https://github.com/OjaswinM/fsx-aw-issue/tree/master

[2] https://github.com/OjaswinM/fsx-aw-issue/blob/master/repro.fsxops

regards,
ojaswin

#22 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 93 lines · Ojaswin Mujoo
Quoted 92 lines · Brian Foster
Quoted 91 lines · Ojaswin Mujoo
Quoted 32 lines · John Garry
Quoted 22 lines · Ojaswin Mujoo

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

Quoted 3 lines

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Hi Ojaswin,

Sorry for the very slow response.

Quoted 7 lines

Are you still checking this issue?

To replicate, should I just take latest xfs kernel and run this series on top of latest xfstests? Is it 100% reproducible?

Thanks,
John

Hi John,

Yes Im looking into it but I'm now starting to run into some reflink/cow based concepts that are taking time to understand. Let me share what I have till now:

So the test.sh that I'm using can be found here [1] which just uses an fsx replay file (which replays all operations) present in the same repo [2]. If you see the replay file, there are a bunch of random operations followed by the last 2 commented out operations:

# copy_range 0xd000 0x1000 0x1d800 0x44000   <--- # operations <start> <len> <dest of copy> <filesize (can be ignored)>
# mapread 0x1e000 0x1000 0x1e400 *

The copy_range here is the one which causes (or exposes) the corruption at 0x1e800 (the end of copy range destination gets corrupted).

To have more control, I commented these 2 operations and am doing it by hand in the test.sh file, with xfs_io. I'm also using a non atomic write device so we only have S/W fallback.

Now some observations:

1. The copy_range operations is actually copying from a hole to a hole, so we should be reading all 0s. But What I see is the following happening:

vfs_copy_file_range
do_splice_direct
do_splice_direct_actor
do_splice_read
# Adds the folio at src offset to the pipe. I confirmed this is all 0x0.
splice_direct_to_actor
direct_splice_actor
do_splice_from
iter_file_splice_write
xfs_file_write_iter
xfs_file_buffered_write
iomap_file_buferred_write
iomap_iter
xfs_buferred_write_iomap_begin
# Here we correctly see that there is noting at the # destination in data fork, but somehow we find a mapped # extent in cow fork which is returned to iomap.
iomap_write_iter
__iomap_write_begin
# Here we notice folio is not uptodate and call
# iomap_read_folio_range() to read from the cow_fork # mapping we found earlier. This results in folio having # incorrect data at 0x1e800 offset.

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

So it seems like the fsx operations might be corrupting the cow fork state somehow leading to stale data exposure.

2. If we disable atomic writes we dont hit the issue.

3. If I do a -c pread of the destination range before doing the copy_range operation then I don't see the corruption any more.

Yeah, I stopped seeing failures after adding -X (verify data after every operation) to FSX_AVOID.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

I'm now trying to figure out why the mapping returned is not IOMAP_HOLE as it should be. I don't know the COW path in xfs so there are some gaps in my understanding. Let me know if you need any other information since I'm reliably able to replicate on 6.17.0-rc4.

Quoted 7 lines
Quoted 7 lines

I haven't followed your issue closely, but just on this hole vs. COW thing, XFS has a bit of a quirk where speculative COW fork preallocation can expand out over holes in the data fork. If iomap lookup for buffered write sees COW fork blocks present, it reports those blocks as the primary mapping even if the data fork happens to be a hole (since there's no point in allocating blocks to the data fork when we can just remap).

That sounds like a bug -- if a sub-fsblock write to an uncached file range has to read data in from disk, then xfs needs to pass the data fork mapping to iomap even if it's a read.

Can you capture the ftrace output of the iomap_iter_*map tracepoints?

Quoted 10 lines
Quoted 6 lines

Again I've no idea if this relates to your issue or what you're referring to as a hole (i.e. data fork only?), but just pointing it out. The latest iomap/xfs patches I posted a few days ago kind of dance around this a bit, but I was somewhat hoping that maybe the cleanups there would trigger some thoughts on better iomap reporting in that regard.

Hi Brian, Thanks for the details and yes by "hole" i did mean hole in data fork only. The part that I'm now confused about is does this sort of preallocation extent hold any valid data? IIUC it should not, so I

No. Mappings in the cow fork are not fully written and should never be used for reads.

Quoted 3 lines

would expect it to trigger iomap_block_needs_zeroing() to write zeroes to the folio. Instead, what I see in the issue is that we are trying to do disk read.

Hrm. Part of the problem here might be that iomap_read_folio_range ignores iomap_iter::srcmap if it's type IOMAP_HOLE (see iomap_iter_srcmap), even if the filesystem actually *set* the srcmap to a hole.

FWIW I see a somewhat different failure -- not data corruption, but pwrite returning failure:

var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 renamed from run/fstests/bin/tests/generic/521.out 2025-07-15 14:45:15.100315255 -0700 +22 -0
@@ -1,2 +1,668 @@
 QA output created by 521
+dowrite: write: Input/output error
+LOG DUMP (661 total operations):
+1(  1 mod 256): TRUNCATE UP    from 0x0 to 0x1d000
+2(  2 mod 256): DEDUPE 0x19000 thru 0x1bfff    (0x3000 bytes) to 0x13000 thru 0x15fff
+3(  3 mod 256): SKIPPED (no operation)
+4(  4 mod 256): PUNCH    0x5167 thru 0x12d1c   (0xdbb6 bytes)
+5(  5 mod 256): WRITE    0x79000 thru 0x86fff  (0xe000 bytes) HOLE
+6(  6 mod 256): PUNCH    0x32344 thru 0x36faf  (0x4c6c bytes)
+7(  7 mod 256): READ     0x0 thru 0xfff        (0x1000 bytes)
+8(  8 mod 256): WRITE    0xe000 thru 0x11fff   (0x4000 bytes)
+9(  9 mod 256): PUNCH    0x71324 thru 0x86fff  (0x15cdc bytes)
+10( 10 mod 256): MAPREAD  0x5b000 thru 0x6d218 (0x12219 bytes)
+11( 11 mod 256): COLLAPSE 0x70000 thru 0x79fff (0xa000 bytes)
+12( 12 mod 256): WRITE    0x41000 thru 0x50fff (0x10000 bytes)
+13( 13 mod 256): INSERT 0x39000 thru 0x4dfff   (0x15000 bytes)
+14( 14 mod 256): WRITE    0x34000 thru 0x37fff (0x4000 bytes)
+15( 15 mod 256): MAPREAD  0x55000 thru 0x6ee44 (0x19e45 bytes)
+16( 16 mod 256): READ     0x46000 thru 0x55fff (0x10000 bytes)
+17( 17 mod 256): PUNCH    0x1ccea thru 0x23b2e (0x6e45 bytes)
+18( 18 mod 256): COPY 0x2a000 thru 0x35fff     (0xc000 bytes) to 0x52000 thru 0x5dfff
+19( 19 mod 256): SKIPPED (no operation)
+20( 20 mod 256): WRITE    0x10000 thru 0x1ffff (0x10000 bytes)

<snip>

+645(133 mod 256): READ     0x5000 thru 0x16fff (0x12000 bytes)
+646(134 mod 256): PUNCH    0x3a51d thru 0x41978        (0x745c bytes)
+647(135 mod 256): FALLOC   0x47f4c thru 0x54867        (0xc91b bytes) INTERIOR
+648(136 mod 256): WRITE    0xa000 thru 0x1dfff (0x14000 bytes)
+649(137 mod 256): CLONE 0x83000 thru 0x89fff   (0x7000 bytes) to 0x4b000 thru 0x51fff
+650(138 mod 256): TRUNCATE DOWN        from 0x8bac4 to 0x7e000
+651(139 mod 256): MAPWRITE 0x13000 thru 0x170e6        (0x40e7 bytes)
+652(140 mod 256): XCHG 0x6a000 thru 0x7cfff    (0x13000 bytes) to 0x8000 thru 0x1afff
+653(141 mod 256): XCHG 0x35000 thru 0x3cfff    (0x8000 bytes) to 0x1b000 thru 0x22fff
+654(142 mod 256): CLONE 0x47000 thru 0x60fff   (0x1a000 bytes) to 0x65000 thru 0x7efff
+655(143 mod 256): DEDUPE 0x79000 thru 0x7dfff  (0x5000 bytes) to 0x6e000 thru 0x72fff
+656(144 mod 256): XCHG 0x4d000 thru 0x5ffff    (0x13000 bytes) to 0x8000 thru 0x1afff
+657(145 mod 256): PUNCH    0x7194f thru 0x7efff        (0xd6b1 bytes)
+658(146 mod 256): PUNCH    0x7af7e thru 0x7efff        (0x4082 bytes)
+659(147 mod 256): MAPREAD  0x77000 thru 0x7e55d        (0x755e bytes)
+660(148 mod 256): READ     0x58000 thru 0x64fff        (0xd000 bytes)
+661(149 mod 256): WRITE    0x88000 thru 0x8bfff        (0x4000 bytes) HOLE

+Log of operations saved to "/mnt/junk.fsxops"; replay with --replay-ops +Correct content saved for comparison
+(maybe hexdump "/mnt/junk" vs "/mnt/junk.fsxgood")

Curiously there are no EIO errors logged in dmesg.

--D

Quoted 16 lines

Regards,
ojaswin

Quoted 13 lines

Brian

Quoted 8 lines

[1]
https://github.com/OjaswinM/fsx-aw-issue/tree/master

[2] https://github.com/OjaswinM/fsx-aw-issue/blob/master/repro.fsxops

regards,
ojaswin

#23 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 119 lines · Darrick J. Wong
Quoted 93 lines · Ojaswin Mujoo
Quoted 92 lines · Brian Foster
Quoted 91 lines · Ojaswin Mujoo
Quoted 32 lines · John Garry
Quoted 22 lines · Ojaswin Mujoo

Hi Zorro, thanks for checking this. So correct me if im wrong but I understand that you have run this test on an atomic writes enabled kernel where the stack also supports atomic writes.

Looking at the bad data log:

	+READ BAD DATA: offset = 0x1c000, size = 0x1803, fname = /mnt/xfstests/test/junk
	+OFFSET      GOOD    BAD     RANGE
	+0x1c000     0x0000  0xcdcd  0x0
	+operation# (mod 256) for the bad data may be 205

We see that 0x0000 was expected but we got 0xcdcd. Now the operation that caused this is indicated to be 205, but looking at that operation:

+205(205 mod 256): ZERO 0x6dbe6 thru 0x6e6aa (0xac5 bytes)

This doesn't even overlap the range that is bad. (0x1c000 to 0x1c00f). Infact, it does seem like an unlikely coincidence that the actual data in the bad range is 0xcdcd which is something xfs_io -c "pwrite" writes to default (fsx writes random data in even offsets and operation num in odd).

Quoted 3 lines

I am able to replicate this but only on XFS but not on ext4 (atleast not in 20 runs). I'm trying to better understand if this is a test issue or not. Will keep you update.

Hi Ojaswin,

Sorry for the very slow response.

Quoted 7 lines

Are you still checking this issue?

To replicate, should I just take latest xfs kernel and run this series on top of latest xfstests? Is it 100% reproducible?

Thanks,
John

Hi John,

Yes Im looking into it but I'm now starting to run into some reflink/cow based concepts that are taking time to understand. Let me share what I have till now:

So the test.sh that I'm using can be found here [1] which just uses an fsx replay file (which replays all operations) present in the same repo [2]. If you see the replay file, there are a bunch of random operations followed by the last 2 commented out operations:

# copy_range 0xd000 0x1000 0x1d800 0x44000   <--- # operations <start> <len> <dest of copy> <filesize (can be ignored)>
# mapread 0x1e000 0x1000 0x1e400 *

The copy_range here is the one which causes (or exposes) the corruption at 0x1e800 (the end of copy range destination gets corrupted).

To have more control, I commented these 2 operations and am doing it by hand in the test.sh file, with xfs_io. I'm also using a non atomic write device so we only have S/W fallback.

Now some observations:

1. The copy_range operations is actually copying from a hole to a hole, so we should be reading all 0s. But What I see is the following happening:

vfs_copy_file_range
do_splice_direct
do_splice_direct_actor
do_splice_read
# Adds the folio at src offset to the pipe. I confirmed this is all 0x0.
splice_direct_to_actor
direct_splice_actor
do_splice_from
iter_file_splice_write
xfs_file_write_iter
xfs_file_buffered_write
iomap_file_buferred_write
iomap_iter
xfs_buferred_write_iomap_begin
# Here we correctly see that there is noting at the # destination in data fork, but somehow we find a mapped # extent in cow fork which is returned to iomap.
iomap_write_iter
__iomap_write_begin
# Here we notice folio is not uptodate and call
# iomap_read_folio_range() to read from the cow_fork # mapping we found earlier. This results in folio having # incorrect data at 0x1e800 offset.

Quoted 7 lines
Quoted 7 lines
Quoted 7 lines

So it seems like the fsx operations might be corrupting the cow fork state somehow leading to stale data exposure.

2. If we disable atomic writes we dont hit the issue.

3. If I do a -c pread of the destination range before doing the copy_range operation then I don't see the corruption any more.

Yeah, I stopped seeing failures after adding -X (verify data after every operation) to FSX_AVOID.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

I'm now trying to figure out why the mapping returned is not IOMAP_HOLE as it should be. I don't know the COW path in xfs so there are some gaps in my understanding. Let me know if you need any other information since I'm reliably able to replicate on 6.17.0-rc4.

Quoted 7 lines
Quoted 7 lines

I haven't followed your issue closely, but just on this hole vs. COW thing, XFS has a bit of a quirk where speculative COW fork preallocation can expand out over holes in the data fork. If iomap lookup for buffered write sees COW fork blocks present, it reports those blocks as the primary mapping even if the data fork happens to be a hole (since there's no point in allocating blocks to the data fork when we can just remap).

Quoted 5 lines

That sounds like a bug -- if a sub-fsblock write to an uncached file range has to read data in from disk, then xfs needs to pass the data fork mapping to iomap even if it's a read.

Can you capture the ftrace output of the iomap_iter_*map tracepoints?

Hey Darrick, below are the details:

Command:

	sudo ./xfstests-dev/ltp/fsx -N 10000 -o 8192 -l 500000 -r 4096 -t 512 -w 512 -Z -FKuHzI -g B --replay-ops prep.fsxops /mnt/test/junk
	sudo perf record -e 'iomap:iomap_iter_*map' xfs_io -c 'copy_range -s 0xd000 -d 0x1d800 -l 0x1000 /mnt/test/junk' -c 'pread -v 0x1e7f0 0x20' /mnt/test/junk

	(prep.fsxops is here: https://github.com/OjaswinM/fsx-aw-issue/blob/master/repro.fsxops)

stdout:

	Seed set to 1
	main: filesystem does not support exchange range, disabling!
	truncating to largest ever: 0x50e00
	truncating to largest ever: 0x70e00
	All 136 operations completed A-OK!

	0001e7f0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0001e800:  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
	read 16/16 bytes at offset 124927
	16.000000 bytes, 1 ops; 0.0000 sec (339.674 KiB/sec and 21739.1304 ops/sec)
	[ perf record: Woken up 1 times to write data ]
	[ perf record: Captured and wrote 0.003 MB perf.data (1 samples) ]

perf output:

	xfs_io     981 [000]   254.331537: iomap:iomap_iter_dstmap: dev 254:32 ino 0x83 bdev 254:32 addr 0xb8000 offset 0x1d000 length 0x23000 type MAPPED (0x2) flags DIRTY|SHARED (0x6)

perf output if I call -c pread before -c copy_range:

	xfs_io    1098 [000]   911.542373: iomap:iomap_iter_dstmap: dev 254:32 ino 0x83 bdev 254:32 addr 0xffffffffffffffff offset 0x1e000 length 0x1000 type HOLE (0x0) flags DIRTY (0x2)
	xfs_io    1098 [000]   911.542776: iomap:iomap_iter_dstmap: dev 254:32 ino 0x83 bdev 254:32 addr 0xb8000 offset 0x1d000 length 0x23000 type MAPPED (0x2) flags DIRTY|SHARED (0x6)

(The first event is from -c pread)

Fiemap before and after output incase it helps:

	$ xfs_io -c 'fiemap -v' -c 'copy_range -s 0xd000 -d 0x1d800 -l 0x1000 /mnt/test/junk' -c 'pread -v 0x1e7f0 0x20' -c 'fiemap -v' /mnt/test/junk
	/mnt/test/junk:
	 EXT: FILE-OFFSET      BLOCK-RANGE      TOTAL FLAGS
		 0: [0..7]:          408..415             8   0x0
		 1: [8..31]:         520..543            24   0x0
		 2: [32..167]:       hole               136
		 3: [168..175]:      1408..1415           8   0x0
		 4: [176..367]:      hole               192
		 5: [368..375]:      1608..1615           8   0x0
		 6: [376..535]:      hole               160
		 7: [536..543]:      1048..1055           8   0x1
	0001e7f0:  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
	0001e800:  42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42  BBBBBBBBBBBBBBBB
	read 32/32 bytes at offset 124912
	32.000000 bytes, 1 ops; 0.0000 sec (325.521 KiB/sec and 10416.6667 ops/sec)
	/mnt/test/junk:
	 EXT: FILE-OFFSET      BLOCK-RANGE      TOTAL FLAGS
		 0: [0..7]:          408..415             8   0x0
		 1: [8..31]:         520..543            24   0x0
		 2: [32..167]:       hole               136
		 3: [168..175]:      1408..1415           8   0x0
		 4: [176..231]:      hole                56
		 5: [232..247]:      1472..1487          16   0x0
		 6: [248..367]:      hole               120
		 7: [368..375]:      1608..1615           8   0x0
		 8: [376..535]:      hole               160
		 9: [536..543]:      1048..1055           8   0x1
Quoted 31 lines
Quoted 11 lines
Quoted 7 lines

Again I've no idea if this relates to your issue or what you're referring to as a hole (i.e. data fork only?), but just pointing it out. The latest iomap/xfs patches I posted a few days ago kind of dance around this a bit, but I was somewhat hoping that maybe the cleanups there would trigger some thoughts on better iomap reporting in that regard.

Hi Brian, Thanks for the details and yes by "hole" i did mean hole in data fork only. The part that I'm now confused about is does this sort of preallocation extent hold any valid data? IIUC it should not, so I

No. Mappings in the cow fork are not fully written and should never be used for reads.

Quoted 3 lines

would expect it to trigger iomap_block_needs_zeroing() to write zeroes to the folio. Instead, what I see in the issue is that we are trying to do disk read.

Hrm. Part of the problem here might be that iomap_read_folio_range ignores iomap_iter::srcmap if it's type IOMAP_HOLE (see iomap_iter_srcmap), even if the filesystem actually *set* the srcmap to a hole.

FWIW I see a somewhat different failure -- not data corruption, but pwrite returning failure:

var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 line 2

Quoted 2 lines
var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 renamed from run/fstests/bin/tests/generic/521.out 2025-07-15 14:45:15.100315255 -0700 +1 -0
@@ -1,2 +1,668 @@
 QA output created by 521
+dowrite: write: Input/output error

Hmm that's strange. Can you try running the above command with prep.fsxops that I've shared. You'll need to pull the fsx atomic write changes in this patch for it to work. I've been running on non atomic write device btw.

Quoted 63 lines
var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 renamed from run/fstests/bin/tests/generic/521.out 2025-07-15 14:45:15.100315255 -0700 +41 -0
+LOG DUMP (661 total operations):
+1(  1 mod 256): TRUNCATE UP    from 0x0 to 0x1d000
+2(  2 mod 256): DEDUPE 0x19000 thru 0x1bfff    (0x3000 bytes) to 0x13000 thru 0x15fff
+3(  3 mod 256): SKIPPED (no operation)
+4(  4 mod 256): PUNCH    0x5167 thru 0x12d1c   (0xdbb6 bytes)
+5(  5 mod 256): WRITE    0x79000 thru 0x86fff  (0xe000 bytes) HOLE
+6(  6 mod 256): PUNCH    0x32344 thru 0x36faf  (0x4c6c bytes)
+7(  7 mod 256): READ     0x0 thru 0xfff        (0x1000 bytes)
+8(  8 mod 256): WRITE    0xe000 thru 0x11fff   (0x4000 bytes)
+9(  9 mod 256): PUNCH    0x71324 thru 0x86fff  (0x15cdc bytes)
+10( 10 mod 256): MAPREAD  0x5b000 thru 0x6d218 (0x12219 bytes)
+11( 11 mod 256): COLLAPSE 0x70000 thru 0x79fff (0xa000 bytes)
+12( 12 mod 256): WRITE    0x41000 thru 0x50fff (0x10000 bytes)
+13( 13 mod 256): INSERT 0x39000 thru 0x4dfff   (0x15000 bytes)
+14( 14 mod 256): WRITE    0x34000 thru 0x37fff (0x4000 bytes)
+15( 15 mod 256): MAPREAD  0x55000 thru 0x6ee44 (0x19e45 bytes)
+16( 16 mod 256): READ     0x46000 thru 0x55fff (0x10000 bytes)
+17( 17 mod 256): PUNCH    0x1ccea thru 0x23b2e (0x6e45 bytes)
+18( 18 mod 256): COPY 0x2a000 thru 0x35fff     (0xc000 bytes) to 0x52000 thru 0x5dfff
+19( 19 mod 256): SKIPPED (no operation)
+20( 20 mod 256): WRITE    0x10000 thru 0x1ffff (0x10000 bytes)
<snip>
+645(133 mod 256): READ     0x5000 thru 0x16fff (0x12000 bytes)
+646(134 mod 256): PUNCH    0x3a51d thru 0x41978        (0x745c bytes)
+647(135 mod 256): FALLOC   0x47f4c thru 0x54867        (0xc91b bytes) INTERIOR
+648(136 mod 256): WRITE    0xa000 thru 0x1dfff (0x14000 bytes)
+649(137 mod 256): CLONE 0x83000 thru 0x89fff   (0x7000 bytes) to 0x4b000 thru 0x51fff
+650(138 mod 256): TRUNCATE DOWN        from 0x8bac4 to 0x7e000
+651(139 mod 256): MAPWRITE 0x13000 thru 0x170e6        (0x40e7 bytes)
+652(140 mod 256): XCHG 0x6a000 thru 0x7cfff    (0x13000 bytes) to 0x8000 thru 0x1afff
+653(141 mod 256): XCHG 0x35000 thru 0x3cfff    (0x8000 bytes) to 0x1b000 thru 0x22fff
+654(142 mod 256): CLONE 0x47000 thru 0x60fff   (0x1a000 bytes) to 0x65000 thru 0x7efff
+655(143 mod 256): DEDUPE 0x79000 thru 0x7dfff  (0x5000 bytes) to 0x6e000 thru 0x72fff
+656(144 mod 256): XCHG 0x4d000 thru 0x5ffff    (0x13000 bytes) to 0x8000 thru 0x1afff
+657(145 mod 256): PUNCH    0x7194f thru 0x7efff        (0xd6b1 bytes)
+658(146 mod 256): PUNCH    0x7af7e thru 0x7efff        (0x4082 bytes)
+659(147 mod 256): MAPREAD  0x77000 thru 0x7e55d        (0x755e bytes)
+660(148 mod 256): READ     0x58000 thru 0x64fff        (0xd000 bytes)
+661(149 mod 256): WRITE    0x88000 thru 0x8bfff        (0x4000 bytes) HOLE
+Log of operations saved to "/mnt/junk.fsxops"; replay with --replay-ops
+Correct content saved for comparison
+(maybe hexdump "/mnt/junk" vs "/mnt/junk.fsxgood")

Curiously there are no EIO errors logged in dmesg.

--D

Quoted 16 lines

Regards,
ojaswin

Quoted 13 lines

Brian

Quoted 8 lines

[1]
https://github.com/OjaswinM/fsx-aw-issue/tree/master

[2] https://github.com/OjaswinM/fsx-aw-issue/blob/master/repro.fsxops

regards,
ojaswin

#24 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 11 lines · Ojaswin Mujoo

var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 line 2

Quoted 7 lines

FWIW I see a somewhat different failure -- not data corruption, but pwrite returning failure:

var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 renamed from run/fstests/bin/tests/generic/521.out 2025-07-15 14:45:15.100315255 -0700 +1 -0
@@ -1,2 +1,668 @@
  QA output created by 521
+dowrite: write: Input/output error

Hmm that's strange. Can you try running the above command with prep.fsxops that I've shared. You'll need to pull the fsx atomic write changes in this patch for it to work. I've been running on non atomic write device btw.

JFYI, I can see this issue with v6.18-rc2 and generic/760

I'll investigate...

#25 lore raw

Re: [PATCH v7 04/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 13 lines · John Garry
Quoted 11 lines · Ojaswin Mujoo

var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 line 2

Quoted 7 lines

FWIW I see a somewhat different failure -- not data corruption, but pwrite returning failure:

var/tmp/fstests/generic/521.out.bad 2025-10-21 10:33:39.032263811 -0700 renamed from run/fstests/bin/tests/generic/521.out 2025-07-15 14:45:15.100315255 -0700 +1 -0
@@ -1,2 +1,668 @@
  QA output created by 521
+dowrite: write: Input/output error

Hmm that's strange. Can you try running the above command with prep.fsxops that I've shared. You'll need to pull the fsx atomic write changes in this patch for it to work. I've been running on non atomic write device btw.

Quoted 3 lines

JFYI, I can see this issue with v6.18-rc2 and generic/760

I'll investigate...

I figured out why I'm seeing EIO from the ftrace output. Apparently xfs_atomic_write_cow_iomap_begin isn't detecting delalloc extents in the cow fork and converting them to unwritten:

fsx-35984 [003]  1996.389571: xfs_file_direct_write: dev 8:0 ino 0x127 disize 0x88000 pos 0x20000 bytecount 0x10000
fsx-35984 [003]  1996.389572: iomap_dio_rw_begin:   dev 8:0 ino 0x127 size 0x88000 offset 0x20000 length 0x10000 done_before 0x0 flags ATOMIC|DIRECT dio_flags  aio 0
fsx-35984 [003]  1996.389583: iomap_iter:           dev 8:0 ino 0x127 pos 0x20000 length 0x10000 status 0 flags WRITE|DIRECT|ATOMIC (0x211) ops xfs_atomic_write_cow_iomap_ops caller __iomap_dio_rw+0x1cb
fsx-35984 [003]  1996.389583: xfs_iomap_atomic_write_cow: dev 8:0 ino 0x127 pos 0x20000 bytecount 0x10000
fsx-35984 [003]  1996.389584: xfs_iomap_found:      dev 8:0 ino 0x127 disize 0x88000 pos 0x20000 bytecount 0x10000 fork cow startoff 0x20 startblock 0xffffffffe0007 fsbcount 0x10
fsx-35984 [003]  1996.389584: iomap_iter_dstmap:    dev 8:0 ino 0x127 bdev 8:0 addr 0xffffffffffffffff offset 0x20000 length 0x10000 type DELALLOC (0x1) flags DIRTY|SHARED (0x6)
fsx-35984 [003]  1996.389595: console:              Direct I/O collision with buffered writes! File: /junk Comm: fsx
fsx-35984 [003]  1996.391183: iomap_iter:           dev 8:0 ino 0x127 pos 0x20000 length 0x10000 status -5 flags WRITE|DIRECT|ATOMIC (0x211) ops xfs_atomic_write_cow_iomap_ops caller __iomap_dio_rw+0x1cb
fsx-35984 [003]  1996.391184: iomap_dio_complete:   dev 8:0 ino 0x127 size 0x88000 offset 0x20000 flags ATOMIC|DIRECT aio 0 error -5 ret -5

I suspect this needs a xfs_bmapi_convert_delalloc call somewhere around the end of that function.

--D

#40 lore raw

Re: [PATCH v7 05/12] generic: Add atomic write test using fio crc check verifier

In reply to Ojaswin Mujoo
Quoted 31 lines · Ojaswin Mujoo

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc header for each data block, which is verified later to ensure there is no data corruption or torn write.

This test essentially does a lot of parallel RWF_ATOMIC IO on a preallocated file to stress the write and end-io unwritten conversion code paths. The idea is to increase code coverage to ensure RWF_ATOMIC hasn't introduced any issues.

Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.

---
 tests/generic/1226     | 108 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1226.out |   2 +
 2 files changed, 110 insertions(+)
 create mode 100755 tests/generic/1226
 create mode 100644 tests/generic/1226.out
tests/generic/1226 new file +0 -0
new file mode 100755
index 00000000..7ad74554

tests/generic/1226 line 5

Quoted 5 lines
tests/generic/1226 new file +5 -0
@@ -0,0 +1,108 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 1226

Hey Zorro,

Thanks for picking these in for-next. I just noticed that the test number for this test has become 773, but we missed changing:

# FS QA Test 1226

to

# FS QA Test 773

Commit: https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/commit/?h=for-next&id=1499d4ff2365803e97af3279ba3312bba2cc9a80

Regards,
Ojaswin

Quoted 114 lines
tests/generic/1226 new file +103 -0
+#
+# Validate FS atomic write using fio crc check verifier.
+#
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto aio rw atomicwrites
+
+_require_scratch_write_atomic
+_require_odirect
+_require_aio
+_require_fio_atomic_writes
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount
+_require_xfs_io_command "falloc"
+
+touch "$SCRATCH_MNT/f1"
+awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1")
+awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1")
+
+blocksize=$(_max "$awu_min_write" "$((awu_max_write/2))")
+threads=$(_min "$(($(nproc) * 2 * LOAD_FACTOR))" "100")
+filesize=$((blocksize * threads * 100))
+depth=$threads
+io_size=$((filesize / threads))
+io_inc=$io_size
+testfile=$SCRATCH_MNT/test-file
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+fio_aw_config=$tmp.aw.fio
+fio_verify_config=$tmp.verify.fio
+
+function create_fio_configs()
+{
+	create_fio_aw_config
+	create_fio_verify_config
+}
+
+function create_fio_verify_config()
+{
+cat >$fio_verify_config <<EOF
+	[verify-job]
+	direct=1
+	ioengine=libaio
+	rw=read
+	bs=$blocksize
+	filename=$testfile
+	size=$filesize
+	iodepth=$depth
+	group_reporting=1
+
+	verify_only=1
+	verify=crc32c
+	verify_fatal=1
+	verify_state_save=0
+	verify_write_sequence=0
+EOF
+}
+
+function create_fio_aw_config()
+{
+cat >$fio_aw_config <<EOF
+	[atomicwrite-job]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$blocksize
+	filename=$testfile
+	size=$io_inc
+	offset_increment=$io_inc
+	iodepth=$depth
+	numjobs=$threads
+	group_reporting=1
+	atomic=1
+
+	verify_state_save=0
+	verify=crc32c
+	do_verify=0
+EOF
+}
+
+create_fio_configs
+_require_fio $fio_aw_config
+
+cat $fio_aw_config >> $seqres.full
+cat $fio_verify_config >> $seqres.full
+
+$XFS_IO_PROG -fc "falloc 0 $filesize" $testfile >> $seqres.full
+
+$FIO_PROG $fio_aw_config >> $seqres.full
+ret1=$?
+$FIO_PROG $fio_verify_config >> $seqres.full
+ret2=$?
+
+[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed"
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/1226.out new file +2 -0
new file mode 100644
index 00000000..6dce0ea5
@@ -0,0 +1,2 @@
+QA output created by 1226
+Silence is golden
Signature · 1 line
2.49.0
#26 lore raw

PATCH 1/1 xfs: fix write failures in software-provided atomic writes

In reply to Ojaswin Mujoo

From: Darrick J. Wong <[email protected]>

With the 5 Oct 2025 release of fstests, generic/521 fails for me on regular (aka non-block-atomic-writes) storage:

QA output created by 521
dowrite: write: Input/output error
LOG DUMP (8553 total operations):

1(  1 mod 256): SKIPPED (no operation)
2(  2 mod 256): WRITE    0x7e000 thru 0x8dfff	(0x10000 bytes) HOLE
3(  3 mod 256): READ     0x69000 thru 0x79fff	(0x11000 bytes)
4(  4 mod 256): FALLOC   0x53c38 thru 0x5e853	(0xac1b bytes) INTERIOR
5(  5 mod 256): COPY 0x55000 thru 0x59fff	(0x5000 bytes) to 0x25000 thru 0x29fff
6(  6 mod 256): WRITE    0x74000 thru 0x88fff	(0x15000 bytes)
7(  7 mod 256): ZERO     0xedb1 thru 0x11693	(0x28e3 bytes)

<snip>

with a warning in dmesg from iomap about XFS trying to give it a delalloc mapping for a directio write. Fix the software atomic write iomap_begin code to convert the reservation into a written mapping. This doesn't fix the data corruption problems reported by generic/760, but it's a start.

Cc <[email protected]> # v6.16
Fixes bd1d2c21d5d249 ("xfs: add xfs_atomic_write_cow_iomap_begin()")
---
 fs/xfs/xfs_iomap.c |   21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)
fs/xfs/xfs_iomap.c +19 -2
index d3f6e3e42a1191..e1da06b157cf94 100644
@@ -1130,7 +1130,7 @@ xfs_atomic_write_cow_iomap_begin(
 		return -EAGAIN;
 
 	trace_xfs_iomap_atomic_write_cow(ip, offset, length);
-
+retry:
 	xfs_ilock(ip, XFS_ILOCK_EXCL);
 
 	if (!ip->i_cowfp) {
@@ -1141,6 +1141,8 @@ xfs_atomic_write_cow_iomap_begin(
 	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
 		cmap.br_startoff = end_fsb;
 	if (cmap.br_startoff <= offset_fsb) {
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
 		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
 		goto found;
 	}
@@ -1169,8 +1171,10 @@ xfs_atomic_write_cow_iomap_begin(
 	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
 		cmap.br_startoff = end_fsb;
 	if (cmap.br_startoff <= offset_fsb) {
-		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
 		xfs_trans_cancel(tp);
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
+		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
 		goto found;
 	}
 
@@ -1210,6 +1214,19 @@ xfs_atomic_write_cow_iomap_begin(
 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
 	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 
+convert:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	error = xfs_bmapi_convert_delalloc(ip, XFS_COW_FORK, offset, iomap,
+			NULL);
+	if (error)
+		return error;
+
+	/*
+	 * Try the lookup again, because the delalloc conversion might have
+	 * turned the COW mapping into unwritten, but we need it to be in
+	 * written state.
+	 */
+	goto retry;
 out_unlock:
 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
 	return error;
#27 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 15 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

With the 5 Oct 2025 release of fstests, generic/521 fails for me on regular (aka non-block-atomic-writes) storage:

QA output created by 521
dowrite: write: Input/output error
LOG DUMP (8553 total operations):

1(  1 mod 256): SKIPPED (no operation)
2(  2 mod 256): WRITE    0x7e000 thru 0x8dfff	(0x10000 bytes) HOLE
3(  3 mod 256): READ     0x69000 thru 0x79fff	(0x11000 bytes)
4(  4 mod 256): FALLOC   0x53c38 thru 0x5e853	(0xac1b bytes) INTERIOR
5(  5 mod 256): COPY 0x55000 thru 0x59fff	(0x5000 bytes) to 0x25000 thru 0x29fff
6(  6 mod 256): WRITE    0x74000 thru 0x88fff	(0x15000 bytes)
7(  7 mod 256): ZERO     0xedb1 thru 0x11693	(0x28e3 bytes)
Quoted 7 lines

<snip>

with a warning in dmesg from iomap about XFS trying to give it a delalloc mapping for a directio write. Fix the software atomic write iomap_begin code to convert the reservation into a written mapping. This doesn't fix the data corruption problems reported by generic/760, but it's a start.

Grrrr I forgot to remove the reply-to tag when I harvested the cc list from this old thread for a bug fix. Sorry about that.

--D

Quoted 63 lines
Cc <[email protected]> # v6.16
Fixes bd1d2c21d5d249 ("xfs: add xfs_atomic_write_cow_iomap_begin()")
---
 fs/xfs/xfs_iomap.c |   21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)
fs/xfs/xfs_iomap.c +19 -2
index d3f6e3e42a1191..e1da06b157cf94 100644
@@ -1130,7 +1130,7 @@ xfs_atomic_write_cow_iomap_begin(
 		return -EAGAIN;
 
 	trace_xfs_iomap_atomic_write_cow(ip, offset, length);
-
+retry:
 	xfs_ilock(ip, XFS_ILOCK_EXCL);
 
 	if (!ip->i_cowfp) {
@@ -1141,6 +1141,8 @@ xfs_atomic_write_cow_iomap_begin(
 	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
 		cmap.br_startoff = end_fsb;
 	if (cmap.br_startoff <= offset_fsb) {
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
 		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
 		goto found;
 	}
@@ -1169,8 +1171,10 @@ xfs_atomic_write_cow_iomap_begin(
 	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
 		cmap.br_startoff = end_fsb;
 	if (cmap.br_startoff <= offset_fsb) {
-		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
 		xfs_trans_cancel(tp);
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
+		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
 		goto found;
 	}
 
@@ -1210,6 +1214,19 @@ xfs_atomic_write_cow_iomap_begin(
 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
 	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 
+convert:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	error = xfs_bmapi_convert_delalloc(ip, XFS_COW_FORK, offset, iomap,
+			NULL);
+	if (error)
+		return error;
+
+	/*
+	 * Try the lookup again, because the delalloc conversion might have
+	 * turned the COW mapping into unwritten, but we need it to be in
+	 * written state.
+	 */
+	goto retry;
 out_unlock:
 	xfs_iunlock(ip, XFS_ILOCK_EXCL);
 	return error;
#28 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

In reply to Darrick J. Wong
Quoted 15 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

With the 5 Oct 2025 release of fstests, generic/521 fails for me on regular (aka non-block-atomic-writes) storage:

QA output created by 521
dowrite: write: Input/output error
LOG DUMP (8553 total operations):

1(  1 mod 256): SKIPPED (no operation)
2(  2 mod 256): WRITE    0x7e000 thru 0x8dfff	(0x10000 bytes) HOLE
3(  3 mod 256): READ     0x69000 thru 0x79fff	(0x11000 bytes)
4(  4 mod 256): FALLOC   0x53c38 thru 0x5e853	(0xac1b bytes) INTERIOR
5(  5 mod 256): COPY 0x55000 thru 0x59fff	(0x5000 bytes) to 0x25000 thru 0x29fff
6(  6 mod 256): WRITE    0x74000 thru 0x88fff	(0x15000 bytes)
7(  7 mod 256): ZERO     0xedb1 thru 0x11693	(0x28e3 bytes)
Quoted 7 lines

<snip>

with a warning in dmesg from iomap about XFS trying to give it a delalloc mapping for a directio write. Fix the software atomic write iomap_begin code to convert the reservation into a written mapping. This doesn't fix the data corruption problems reported by generic/760, but it's a start.

I was seeing the corruption and, as expected, unfortunately this does not fix the issue. Indeed, I don't even touch the new codepath when testing (for that corruption).

As for that corruption, I am seeing the same behaviour as Ojaswin described. The failure is in a read operation.

It seems to be a special combo of atomic write, write, and then read which reliably shows the issue. The regular write seems to write to the cow fork, so I am guessing that the atomic write does not leave it in proper state.

I do notice for the atomic write that we are writing (calling xfs_atomic_write_cow_iomap_begin() -> xfs_bmapi_write()) for more blocks that are required for the atomic write. The regular write overwrites these blocks, and the read is corrupted in the blocks just after the atomic write. It's as if the blocks just after atomic write are not left in the proper state.

Quoted 62 lines
Cc <[email protected]> # v6.16
Fixes bd1d2c21d5d249 ("xfs: add xfs_atomic_write_cow_iomap_begin()")
---
  fs/xfs/xfs_iomap.c |   21 +++++++++++++++++++--
  1 file changed, 19 insertions(+), 2 deletions(-)
fs/xfs/xfs_iomap.c +19 -2
index d3f6e3e42a1191..e1da06b157cf94 100644
@@ -1130,7 +1130,7 @@ xfs_atomic_write_cow_iomap_begin(
  		return -EAGAIN;
 
  	trace_xfs_iomap_atomic_write_cow(ip, offset, length);
-
+retry:
  	xfs_ilock(ip, XFS_ILOCK_EXCL);
 
  	if (!ip->i_cowfp) {
@@ -1141,6 +1141,8 @@ xfs_atomic_write_cow_iomap_begin(
  	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
  		cmap.br_startoff = end_fsb;
  	if (cmap.br_startoff <= offset_fsb) {
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
  		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
  		goto found;
  	}
@@ -1169,8 +1171,10 @@ xfs_atomic_write_cow_iomap_begin(
  	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
  		cmap.br_startoff = end_fsb;
  	if (cmap.br_startoff <= offset_fsb) {
-		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
  		xfs_trans_cancel(tp);
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
+		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
  		goto found;
  	}
 
@@ -1210,6 +1214,19 @@ xfs_atomic_write_cow_iomap_begin(
  	xfs_iunlock(ip, XFS_ILOCK_EXCL);
  	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 
+convert:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	error = xfs_bmapi_convert_delalloc(ip, XFS_COW_FORK, offset, iomap,
+			NULL);
+	if (error)
+		return error;
+
+	/*
+	 * Try the lookup again, because the delalloc conversion might have
+	 * turned the COW mapping into unwritten, but we need it to be in
+	 * written state.
+	 */
+	goto retry;
  out_unlock:
  	xfs_iunlock(ip, XFS_ILOCK_EXCL);
  	return error;
#29 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 24 lines · John Garry
Quoted 15 lines · Darrick J. Wong

From: Darrick J. Wong <[email protected]>

With the 5 Oct 2025 release of fstests, generic/521 fails for me on regular (aka non-block-atomic-writes) storage:

QA output created by 521
dowrite: write: Input/output error
LOG DUMP (8553 total operations):

1(  1 mod 256): SKIPPED (no operation)
2(  2 mod 256): WRITE    0x7e000 thru 0x8dfff	(0x10000 bytes) HOLE
3(  3 mod 256): READ     0x69000 thru 0x79fff	(0x11000 bytes)
4(  4 mod 256): FALLOC   0x53c38 thru 0x5e853	(0xac1b bytes) INTERIOR
5(  5 mod 256): COPY 0x55000 thru 0x59fff	(0x5000 bytes) to 0x25000 thru 0x29fff
6(  6 mod 256): WRITE    0x74000 thru 0x88fff	(0x15000 bytes)
7(  7 mod 256): ZERO     0xedb1 thru 0x11693	(0x28e3 bytes)
Quoted 7 lines

<snip>

with a warning in dmesg from iomap about XFS trying to give it a delalloc mapping for a directio write. Fix the software atomic write iomap_begin code to convert the reservation into a written mapping. This doesn't fix the data corruption problems reported by generic/760, but it's a start.

Quoted 3 lines

I was seeing the corruption and, as expected, unfortunately this does not fix the issue. Indeed, I don't even touch the new codepath when testing (for that corruption).

Yeah, I know. This fix enables me to move on to what I think is the corruption that you and Ojaswin are seeing.

Quoted 7 lines

As for that corruption, I am seeing the same behaviour as Ojaswin described. The failure is in a read operation.

It seems to be a special combo of atomic write, write, and then read which reliably shows the issue. The regular write seems to write to the cow fork, so I am guessing that the atomic write does not leave it in proper state.

Quoted 6 lines

I do notice for the atomic write that we are writing (calling xfs_atomic_write_cow_iomap_begin() -> xfs_bmapi_write()) for more blocks that are required for the atomic write. The regular write overwrites these blocks, and the read is corrupted in the blocks just after the atomic write. It's as if the blocks just after atomic write are not left in the proper state.

That's a good breadcrumb for me to follow; I will turn on the rmap tracepoints to see if they give me a better idea of what's going on. I mentioned earlier that I think the problem could be that iomap treats srcmap::type == IOMAP_HOLE as if the srcmap isn't there, and so it'll read from the cow fork blocks even though that's not right.

--D

Quoted 59 lines
Quoted 58 lines
Cc <[email protected]> # v6.16
Fixes bd1d2c21d5d249 ("xfs: add xfs_atomic_write_cow_iomap_begin()")
---
  fs/xfs/xfs_iomap.c |   21 +++++++++++++++++++--
  1 file changed, 19 insertions(+), 2 deletions(-)
fs/xfs/xfs_iomap.c +19 -2
index d3f6e3e42a1191..e1da06b157cf94 100644
@@ -1130,7 +1130,7 @@ xfs_atomic_write_cow_iomap_begin(
  		return -EAGAIN;
  	trace_xfs_iomap_atomic_write_cow(ip, offset, length);
-
+retry:
  	xfs_ilock(ip, XFS_ILOCK_EXCL);
  	if (!ip->i_cowfp) {
@@ -1141,6 +1141,8 @@ xfs_atomic_write_cow_iomap_begin(
  	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
  		cmap.br_startoff = end_fsb;
  	if (cmap.br_startoff <= offset_fsb) {
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
  		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
  		goto found;
  	}
@@ -1169,8 +1171,10 @@ xfs_atomic_write_cow_iomap_begin(
  	if (!xfs_iext_lookup_extent(ip, ip->i_cowfp, offset_fsb, &icur, &cmap))
  		cmap.br_startoff = end_fsb;
  	if (cmap.br_startoff <= offset_fsb) {
-		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
  		xfs_trans_cancel(tp);
+		if (isnullstartblock(cmap.br_startblock))
+			goto convert;
+		xfs_trim_extent(&cmap, offset_fsb, count_fsb);
  		goto found;
  	}
@@ -1210,6 +1214,19 @@ xfs_atomic_write_cow_iomap_begin(
  	xfs_iunlock(ip, XFS_ILOCK_EXCL);
  	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
+convert:
+	xfs_iunlock(ip, XFS_ILOCK_EXCL);
+	error = xfs_bmapi_convert_delalloc(ip, XFS_COW_FORK, offset, iomap,
+			NULL);
+	if (error)
+		return error;
+
+	/*
+	 * Try the lookup again, because the delalloc conversion might have
+	 * turned the COW mapping into unwritten, but we need it to be in
+	 * written state.
+	 */
+	goto retry;
  out_unlock:
  	xfs_iunlock(ip, XFS_ILOCK_EXCL);
  	return error;
#30 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 7 lines · Darrick J. Wong
Quoted 7 lines

As for that corruption, I am seeing the same behaviour as Ojaswin described. The failure is in a read operation.

It seems to be a special combo of atomic write, write, and then read which reliably shows the issue. The regular write seems to write to the cow fork, so I am guessing that the atomic write does not leave it in proper state.

Quoted 7 lines
Quoted 6 lines

I do notice for the atomic write that we are writing (calling xfs_atomic_write_cow_iomap_begin() -> xfs_bmapi_write()) for more blocks that are required for the atomic write. The regular write overwrites these blocks, and the read is corrupted in the blocks just after the atomic write. It's as if the blocks just after atomic write are not left in the proper state.

That's a good breadcrumb for me to follow;

I hope that it is ...

Quoted 5 lines

I will turn on the rmap
tracepoints to see if they give me a better idea of what's going on. I mentioned earlier that I think the problem could be that iomap treats srcmap::type == IOMAP_HOLE as if the srcmap isn't there, and so it'll read from the cow fork blocks even though that's not right.

Something else I notice for my failing test is that we do the regular write, it ends in a sub-fs block write on a hole. But that fs block (which was part of a hole) ends up being filled with all the same data pattern (when I would expect the unwritten region to be 0s when read back) - and this is what the compare fails on.

cheers

#31 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 5 lines · John Garry
Quoted 2 lines
Quoted 1 line

That's a good breadcrumb for me to follow;

I hope that it is ...

Quoted 7 lines
Quoted 5 lines

I will turn on the rmap
tracepoints to see if they give me a better idea of what's going on. I mentioned earlier that I think the problem could be that iomap treats srcmap::type == IOMAP_HOLE as if the srcmap isn't there, and so it'll read from the cow fork blocks even though that's not right.

Something else I notice for my failing test is that we do the regular write, it ends in a sub-fs block write on a hole. But that fs block (which was part of a hole) ends up being filled with all the same data pattern (when I would expect the unwritten region to be 0s when read back) - and this is what the compare fails on.

This makes the problem go away for me:

fs/xfs/xfs_iomap.c +2 -1
index e1da06b157cf..e04af830d196 100644
@@ -1113,6 +1113,7 @@ xfs_atomic_write_cow_iomap_begin(
 	unsigned int		dblocks = 0, rblocks = 0;
 	int			error;
 	u64			seq;
+	xfs_filblks_t		count_fsb_orig = count_fsb;
 
 	ASSERT(flags & IOMAP_WRITE);
 	ASSERT(flags & IOMAP_DIRECT);
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
 found:
 	if (cmap.br_state != XFS_EXT_NORM) {
 		error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
-				count_fsb);
+				count_fsb_orig);
 		if (error)
 			goto out_unlock;
 		cmap.br_state = XFS_EXT_NORM;
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
 	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

thanks

#32 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 44 lines · John Garry
Quoted 11 lines · John Garry
Quoted 2 lines
Quoted 1 line

That's a good breadcrumb for me to follow;

I hope that it is ...

Quoted 5 lines

I will turn on the rmap
tracepoints to see if they give me a better idea of what's going on. I mentioned earlier that I think the problem could be that iomap treats srcmap::type == IOMAP_HOLE as if the srcmap isn't there, and so it'll read from the cow fork blocks even though that's not right.

Quoted 5 lines

Something else I notice for my failing test is that we do the regular write, it ends in a sub-fs block write on a hole. But that fs block (which was part of a hole) ends up being filled with all the same data pattern (when I would expect the unwritten region to be 0s when read back) - and this is what the compare fails on.

This makes the problem go away for me:

fs/xfs/xfs_iomap.c +2 -1
index e1da06b157cf..e04af830d196 100644
@@ -1113,6 +1113,7 @@ xfs_atomic_write_cow_iomap_begin(
 	unsigned int		dblocks = 0, rblocks = 0;
 	int			error;
 	u64			seq;
+	xfs_filblks_t		count_fsb_orig = count_fsb;
 
 	ASSERT(flags & IOMAP_WRITE);
 	ASSERT(flags & IOMAP_DIRECT);
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
 found:
 	if (cmap.br_state != XFS_EXT_NORM) {
 		error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
-				count_fsb);
+				count_fsb_orig);
 		if (error)
 			goto out_unlock;
 		cmap.br_state = XFS_EXT_NORM;
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
 	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 
Quoted 3 lines

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

That looks like a good correction to me; I'll run that on my test fleet overnight and we'll see what happens. Thanks for putting this together!

--D

Quoted 2 lines

thanks

#37 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

In reply to John Garry
Quoted 44 lines · John Garry
Quoted 11 lines · John Garry
Quoted 2 lines
Quoted 1 line

That's a good breadcrumb for me to follow;

I hope that it is ...

Quoted 5 lines

I will turn on the rmap
tracepoints to see if they give me a better idea of what's going on. I mentioned earlier that I think the problem could be that iomap treats srcmap::type == IOMAP_HOLE as if the srcmap isn't there, and so it'll read from the cow fork blocks even though that's not right.

Quoted 5 lines

Something else I notice for my failing test is that we do the regular write, it ends in a sub-fs block write on a hole. But that fs block (which was part of a hole) ends up being filled with all the same data pattern (when I would expect the unwritten region to be 0s when read back) - and this is what the compare fails on.

This makes the problem go away for me:

fs/xfs/xfs_iomap.c +2 -1
index e1da06b157cf..e04af830d196 100644
@@ -1113,6 +1113,7 @@ xfs_atomic_write_cow_iomap_begin(
 	unsigned int		dblocks = 0, rblocks = 0;
 	int			error;
 	u64			seq;
+	xfs_filblks_t		count_fsb_orig = count_fsb;
 
 	ASSERT(flags & IOMAP_WRITE);
 	ASSERT(flags & IOMAP_DIRECT);
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
 found:
 	if (cmap.br_state != XFS_EXT_NORM) {
 		error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
-				count_fsb);
+				count_fsb_orig);
 		if (error)
 			goto out_unlock;
 		cmap.br_state = XFS_EXT_NORM;
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
 	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 
Quoted 3 lines

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

Hi John, this seems to be fixing the issue for me.

Thanks,
Ojaswin

Quoted 2 lines

thanks

#38 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 4 lines · Ojaswin Mujoo
Quoted 3 lines

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

Hi John, this seems to be fixing the issue for me.

Thanks for checking!

I'll discuss posting an updated patch with Darrick.

#33 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

In reply to Darrick J. Wong
Quoted 8 lines · Darrick J. Wong
Quoted 6 lines
fs/xfs/xfs_iomap.c +0 -0
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
  	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

That looks like a good correction to me; I'll run that on my test fleet overnight and we'll see what happens. Thanks for putting this together!

Cool, but I am not confident that it is a completely correct. Here's the updated code:

int error;
u64 seq;
+ xfs_filblks_t count_fsb_orig = count_fsb;

ASSERT(flags & IOMAP_WRITE);
ASSERT(flags & IOMAP_DIRECT);
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
found:
if (cmap.br_state != XFS_EXT_NORM) {
error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
- count_fsb);
+ count_fsb_orig);
if (error)
goto out_unlock;
cmap.br_state = XFS_EXT_NORM;

cmap may be longer than count_fsb_orig (which was the failing scenario). In that case, after calling xfs_reflink_convert_cow_locked(), we would have partially converted cmap, so it is proper to set cmap.br_state = XFS_EXT_NORM? We should trim cmap to count_fsb_orig also, right?

I don't think that it makes much of a difference, but it seems the proper thing to do. Maybe the subsequent traces length values would be inconsistent with other path to @found label if we don't trim.

Thank,
John

#34 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 20 lines · John Garry
Quoted 8 lines · Darrick J. Wong
Quoted 6 lines
fs/xfs/xfs_iomap.c +0 -0
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
  	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

That looks like a good correction to me; I'll run that on my test fleet overnight and we'll see what happens. Thanks for putting this together!

Cool, but I am not confident that it is a completely correct. Here's the updated code:

fs/xfs/xfs_iomap.c +1 -0
 	int			error;
 	u64			seq;
+	xfs_filblks_t		count_fsb_orig = count_fsb;
 
 	ASSERT(flags & IOMAP_WRITE);
 	ASSERT(flags & IOMAP_DIRECT);

fs/xfs/xfs_iomap.c xfs_atomic_write_cow_iomap_begin( · line 1209

Quoted 8 lines
fs/xfs/xfs_iomap.c +1 -1
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
 found:
 	if (cmap.br_state != XFS_EXT_NORM) {
 		error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
-				count_fsb);
+				count_fsb_orig);
 		if (error)
 			goto out_unlock;
 		cmap.br_state = XFS_EXT_NORM;

Er... this is exactly the same snippet as yesterday. <confused>

(That snippet seems to have survived overnight fsx)

Quoted 4 lines

cmap may be longer than count_fsb_orig (which was the failing scenario). In that case, after calling xfs_reflink_convert_cow_locked(), we would have partially converted cmap, so it is proper to set cmap.br_state = XFS_EXT_NORM?

Hrm. Let me walk through this function again.

At the start, {offset,count}_fsb express the offset/length parameters, but in fsblock units and expanded to align with an fsblock.

If the cow fork has a mapping (cmap) that starts before the offset, we trim the mapping to the desired range and goto found. cmap cannot end before offset_fsb because that's how xfs_iext_lookup_extent works.

If the cow fork doesn't have a mapping or the one it does have doesn't start until after offset_fsb, then we trim count_fsb so that (offset_fsb, count_fsb) is the range that isn't mapped to space.

Then we cycle the ILOCK to get a transaction and do the lookup again due to "extent layout could have changed since the unlock". Same rules as the first lookup. I wonder if that second xfs_trim_extent should be using orig_count_fsb, because at this point it's trimming the cmap to the shorter range. It's probably not a big deal because iomap will just call ->iomap_begin on the rest of the range, but it's more work.

If the second lookup doesn't produce a mapping, then we call xfs_bmapi_write to fill the hole and drop down to @found.

Now, we're at the found: label, having arrived here in one of three ways:

1) The first cmap lookup found at least one block that it can use. (offset_fsb, count_fsb) is the original range that iomap asked for. This mapping could be written or unwritten, and it's been trimmed so that it won't extend outside the requested write range.

2) The second cmap lookup found at least one block that it can use. (offset_fsb, count_fsb) is a truncated range because the cow fork has a mapping that starts at (offset_fsb + count_fsb). This mapping could also be written or unwritten, and it also has been trimmed so that it won't extend outside the hole range.

Why do we trim cmap to the hole range? The original write range will suite iomap just fine.

3) We xfs_bmapi_write'd a hole, and now we have an unwritten mapping. (offset_fsb, count_fsb) is the same truncated range from 2). cmap is potentially much larger than (offset_fsb, count_fsb).

Why do we not trim this mapping to the original write range?

If at this point the mapping is unwritten, we convert it to written mapping with xfs_reflink_convert_cow_locked. offset_fsb retains its original value, but what is count_fsb? It's either the original value
(1) or the smaller one from filling the hole (2) or (3)? Why don't we pass the cmap startoff/blockcount to this function?

As an aside: Changing count_fsb makes it harder for me to understand what's going on in this function.

Now, having converted either the range we arrived at via (1-3) (or with your patch, the original range) to written state, we set br_state and pass that back to iomap. I think in case (3) it's possible that xfs incorrectly reports to iomap an overly large mapping that might not actually reflect the cow fork contents, because we only converted so much of the mapping state.

Quoted 1 line

We should trim cmap to count_fsb_orig also, right?

iomap doesn't care if the mapping it receives spans more space than just the range it asked for, but XFS shouldn't be exposing mappings that don't reflect the actual state of the cow fork.

I think there are several things wrong with this function:

A) xfs_bmapi_write can return a much larger unwritten mapping than what the caller asked for. We convert part of that range to written, but return the entire written mapping to iomap even though that's inaccurate.

B) The arguments to xfs_reflink_convert_cow_locked are wrong -- an unwritten mapping could be *smaller* than the write range (or even the hole range). In this case, we convert too much file range to written state because we then return a smaller mapping to iomap.

C) It doesn't handle delalloc mappings. This I covered in the patch that I already sent to the list.

D) Reassigning count_fsb to handle the hole means that if the second cmap lookup attempt succeeds (due to racing with someone else) we trim the mapping more than is strictly necessary. The changing meaning of count_fsb makes this harder to notice.

E) The tracepoint is kinda wrong because @length is mutated. That makes it harder to chase the data flows through this function because you can't just grep on the pos/bytecount strings.

F) We don't actually check that the br_state = XFS_EXT_NORM assignment is accurate, i.e. that the cow fork contains a written mapping for the range that we're interested in.

G) Somewhat inadequate documentation of why we need to xfs_trim_extent so aggressively in this function.

H) Not sure why xfs_iomap_end_fsb is used here, the vfs already clamped the write range to s_maxbytes.

Quoted 3 lines

I don't think that it makes much of a difference, but it seems the proper thing to do. Maybe the subsequent traces length values would be inconsistent with other path to @found label if we don't trim.

With A-H fixed, all the atomic writes issues I was seeing went away. I'll run this through the atomic writes QA tests and send a fix series to the list.

--D

Quoted 5 lines

Thank,
John

#41 lore raw

Re: [PATCH v7 05/12] generic: Add atomic write test using fio crc check verifier

In reply to Ojaswin Mujoo
Quoted 40 lines · Ojaswin Mujoo
Quoted 31 lines · Ojaswin Mujoo

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc header for each data block, which is verified later to ensure there is no data corruption or torn write.

This test essentially does a lot of parallel RWF_ATOMIC IO on a preallocated file to stress the write and end-io unwritten conversion code paths. The idea is to increase code coverage to ensure RWF_ATOMIC hasn't introduced any issues.

Avoid doing overlapping parallel atomic writes because it might give unexpected results. Use offset_increment=, size= fio options to achieve this behavior.

---
 tests/generic/1226     | 108 +++++++++++++++++++++++++++++++++++++++++
 tests/generic/1226.out |   2 +
 2 files changed, 110 insertions(+)
 create mode 100755 tests/generic/1226
 create mode 100644 tests/generic/1226.out
tests/generic/1226 new file +0 -0
new file mode 100755
index 00000000..7ad74554

tests/generic/1226 line 5

Quoted 5 lines
tests/generic/1226 new file +5 -0
@@ -0,0 +1,108 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 1226

Hey Zorro,

Quoted 8 lines

Thanks for picking these in for-next. I just noticed that the test number for this test has become 773, but we missed changing:

# FS QA Test 1226

to

# FS QA Test 773

Hmm...weird, I change the case number by running ./tools/mvtest generic/1226 generic/773. The "FS QA Test 1226" should be changed to "FS QA Test No. 773" by:

  sed -e "s/^# FS[[:space:]]*QA.*Test.*[0-9]\+$/# FS QA Test No. ${did}/g" -i "tests/${dest}"

I'm not sure what happened at that time, I'll check if there're more "wrong number" issues, will correct them in one patch. Thanks for reporting:)

Thanks,
Zorro

Quoted 122 lines

Commit: https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/commit/?h=for-next&id=1499d4ff2365803e97af3279ba3312bba2cc9a80

Regards,
Ojaswin

Quoted 114 lines
tests/generic/1226 new file +103 -0
+#
+# Validate FS atomic write using fio crc check verifier.
+#
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto aio rw atomicwrites
+
+_require_scratch_write_atomic
+_require_odirect
+_require_aio
+_require_fio_atomic_writes
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount
+_require_xfs_io_command "falloc"
+
+touch "$SCRATCH_MNT/f1"
+awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1")
+awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1")
+
+blocksize=$(_max "$awu_min_write" "$((awu_max_write/2))")
+threads=$(_min "$(($(nproc) * 2 * LOAD_FACTOR))" "100")
+filesize=$((blocksize * threads * 100))
+depth=$threads
+io_size=$((filesize / threads))
+io_inc=$io_size
+testfile=$SCRATCH_MNT/test-file
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+fio_aw_config=$tmp.aw.fio
+fio_verify_config=$tmp.verify.fio
+
+function create_fio_configs()
+{
+	create_fio_aw_config
+	create_fio_verify_config
+}
+
+function create_fio_verify_config()
+{
+cat >$fio_verify_config <<EOF
+	[verify-job]
+	direct=1
+	ioengine=libaio
+	rw=read
+	bs=$blocksize
+	filename=$testfile
+	size=$filesize
+	iodepth=$depth
+	group_reporting=1
+
+	verify_only=1
+	verify=crc32c
+	verify_fatal=1
+	verify_state_save=0
+	verify_write_sequence=0
+EOF
+}
+
+function create_fio_aw_config()
+{
+cat >$fio_aw_config <<EOF
+	[atomicwrite-job]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$blocksize
+	filename=$testfile
+	size=$io_inc
+	offset_increment=$io_inc
+	iodepth=$depth
+	numjobs=$threads
+	group_reporting=1
+	atomic=1
+
+	verify_state_save=0
+	verify=crc32c
+	do_verify=0
+EOF
+}
+
+create_fio_configs
+_require_fio $fio_aw_config
+
+cat $fio_aw_config >> $seqres.full
+cat $fio_verify_config >> $seqres.full
+
+$XFS_IO_PROG -fc "falloc 0 $filesize" $testfile >> $seqres.full
+
+$FIO_PROG $fio_aw_config >> $seqres.full
+ret1=$?
+$FIO_PROG $fio_verify_config >> $seqres.full
+ret2=$?
+
+[[ $ret1 -eq 0 && $ret2 -eq 0 ]] || _fail "fio with atomic write failed"
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/1226.out new file +2 -0
new file mode 100644
index 00000000..6dce0ea5
@@ -0,0 +1,2 @@
+QA output created by 1226
+Silence is golden
Signature · 1 line
2.49.0
#35 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

In reply to Darrick J. Wong
Quoted 18 lines · Darrick J. Wong
Quoted 17 lines · John Garry
Quoted 8 lines · Darrick J. Wong
Quoted 6 lines
fs/xfs/xfs_iomap.c +0 -0
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
   	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

That looks like a good correction to me; I'll run that on my test fleet overnight and we'll see what happens. Thanks for putting this together!

Cool, but I am not confident that it is a completely correct. Here's the updated code:

int error;
u64 seq;

fs/xfs/xfs_iomap.c +1 -0
+	xfs_filblks_t		count_fsb_orig = count_fsb;
Quoted 13 lines

fs/xfs/xfs_iomap.c xfs_atomic_write_cow_iomap_begin( · line 1209

Quoted 11 lines
  	ASSERT(flags & IOMAP_WRITE);
  	ASSERT(flags & IOMAP_DIRECT);
fs/xfs/xfs_iomap.c +1 -1
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
  found:
  	if (cmap.br_state != XFS_EXT_NORM) {
  		error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
-				count_fsb);
+				count_fsb_orig);
  		if (error)
  			goto out_unlock;
  		cmap.br_state = XFS_EXT_NORM;

Er... this is exactly the same snippet as yesterday. <confused>

Yes, I was just showing it for context.

Quoted 8 lines

(That snippet seems to have survived overnight fsx)

Quoted 4 lines

cmap may be longer than count_fsb_orig (which was the failing scenario). In that case, after calling xfs_reflink_convert_cow_locked(), we would have partially converted cmap, so it is proper to set cmap.br_state = XFS_EXT_NORM?

Quoted 4 lines

Hrm. Let me walk through this function again.

At the start, {offset,count}_fsb express the offset/length parameters, but in fsblock units and expanded to align with an fsblock.

I do also note that the usage of xfs_iomap_end_fsb() is a bit dubious, as this will truncate the write if it goes over s_maxbytes. However, we never want to truncate an atomic write.

Quoted 9 lines

If the cow fork has a mapping (cmap) that starts before the offset, we trim the mapping to the desired range and goto found. cmap cannot end before offset_fsb because that's how xfs_iext_lookup_extent works.

If the cow fork doesn't have a mapping or the one it does have doesn't start until after offset_fsb, then we trim count_fsb so that (offset_fsb, count_fsb) is the range that isn't mapped to space.

Quoted 5 lines

Then we cycle the ILOCK to get a transaction and do the lookup again due to "extent layout could have changed since the unlock". Same rules as the first lookup. I wonder if that second xfs_trim_extent should be using orig_count_fsb, because at this point it's trimming the cmap to the shorter range.

Yes, I think so.

Quoted 14 lines

It's probably not a big deal because iomap will
just call ->iomap_begin on the rest of the range, but it's more work.

If the second lookup doesn't produce a mapping, then we call xfs_bmapi_write to fill the hole and drop down to @found.

Now, we're at the found: label, having arrived here in one of three ways:

1) The first cmap lookup found at least one block that it can use. (offset_fsb, count_fsb) is the original range that iomap asked for. This mapping could be written or unwritten, and it's been trimmed so that it won't extend outside the requested write range.

Quoted 8 lines

2) The second cmap lookup found at least one block that it can use. (offset_fsb, count_fsb) is a truncated range because the cow fork has a mapping that starts at (offset_fsb + count_fsb). This mapping could also be written or unwritten, and it also has been trimmed so that it won't extend outside the hole range.

Why do we trim cmap to the hole range? The original write range will suite iomap just fine.

I think that the xfs_iext_lookup_extent() and xfs_trim_extent() pattern may have been just copied without considering this.

Quoted 7 lines

3) We xfs_bmapi_write'd a hole, and now we have an unwritten mapping. (offset_fsb, count_fsb) is the same truncated range from 2). cmap is potentially much larger than (offset_fsb, count_fsb).

Why do we not trim this mapping to the original write range?

I don't know, but this is what I was suggesting should happen.

Quoted 5 lines

If at this point the mapping is unwritten, we convert it to written mapping with xfs_reflink_convert_cow_locked. offset_fsb retains its original value, but what is count_fsb? It's either the original value
(1) or the smaller one from filling the hole (2) or (3)? Why don't we pass the cmap startoff/blockcount to this function?

I think that we should

Quoted 32 lines

As an aside: Changing count_fsb makes it harder for me to understand what's going on in this function.

Now, having converted either the range we arrived at via (1-3) (or with your patch, the original range) to written state, we set br_state and pass that back to iomap. I think in case (3) it's possible that xfs incorrectly reports to iomap an overly large mapping that might not actually reflect the cow fork contents, because we only converted so much of the mapping state.

Quoted 1 line

We should trim cmap to count_fsb_orig also, right?

iomap doesn't care if the mapping it receives spans more space than just the range it asked for, but XFS shouldn't be exposing mappings that don't reflect the actual state of the cow fork.

I think there are several things wrong with this function:

A) xfs_bmapi_write can return a much larger unwritten mapping than what the caller asked for. We convert part of that range to written, but return the entire written mapping to iomap even though that's inaccurate.

B) The arguments to xfs_reflink_convert_cow_locked are wrong -- an unwritten mapping could be *smaller* than the write range (or even the hole range). In this case, we convert too much file range to written state because we then return a smaller mapping to iomap.

C) It doesn't handle delalloc mappings. This I covered in the patch that I already sent to the list.

Quoted 8 lines

D) Reassigning count_fsb to handle the hole means that if the second cmap lookup attempt succeeds (due to racing with someone else) we trim the mapping more than is strictly necessary. The changing meaning of count_fsb makes this harder to notice.

E) The tracepoint is kinda wrong because @length is mutated. That makes it harder to chase the data flows through this function because you can't just grep on the pos/bytecount strings.

Yes, I was noticing this also.

Quoted 10 lines

F) We don't actually check that the br_state = XFS_EXT_NORM assignment is accurate, i.e. that the cow fork contains a written mapping for the range that we're interested in.

G) Somewhat inadequate documentation of why we need to xfs_trim_extent so aggressively in this function.

H) Not sure why xfs_iomap_end_fsb is used here, the vfs already clamped the write range to s_maxbytes.

Please note that I mentioned about this above.

Quoted 8 lines
Quoted 4 lines

I don't think that it makes much of a difference, but it seems the proper thing to do. Maybe the subsequent traces length values would be inconsistent with other path to @found label if we don't trim.

With A-H fixed, all the atomic writes issues I was seeing went away. I'll run this through the atomic writes QA tests and send a fix series to the list.

ok, thanks!

#36 lore raw

Re: [PATCH] xfs: fix write failures in software-provided atomic writes

Quoted 174 lines · John Garry
Quoted 18 lines · Darrick J. Wong
Quoted 17 lines · John Garry
Quoted 8 lines · Darrick J. Wong
Quoted 6 lines
fs/xfs/xfs_iomap.c +0 -0
@@ -1215,6 +1216,7 @@ xfs_atomic_write_cow_iomap_begin(
   	return xfs_bmbt_to_iomap(ip, iomap, &cmap, flags, IOMAP_F_SHARED, seq);
 

I think that the problem may be that we were converting an inappropriate number of blocks from unwritten to real allocations (but never writing to the excess blocks). Does it look ok?

That looks like a good correction to me; I'll run that on my test fleet overnight and we'll see what happens. Thanks for putting this together!

Cool, but I am not confident that it is a completely correct. Here's the updated code:

int error;
u64 seq;

fs/xfs/xfs_iomap.c +1 -0
+	xfs_filblks_t		count_fsb_orig = count_fsb;
Quoted 13 lines

fs/xfs/xfs_iomap.c xfs_atomic_write_cow_iomap_begin( · line 1209

Quoted 11 lines
  	ASSERT(flags & IOMAP_WRITE);
  	ASSERT(flags & IOMAP_DIRECT);
fs/xfs/xfs_iomap.c +1 -1
@@ -1202,7 +1203,7 @@ xfs_atomic_write_cow_iomap_begin(
  found:
  	if (cmap.br_state != XFS_EXT_NORM) {
  		error = xfs_reflink_convert_cow_locked(ip, offset_fsb,
-				count_fsb);
+				count_fsb_orig);
  		if (error)
  			goto out_unlock;
  		cmap.br_state = XFS_EXT_NORM;

Er... this is exactly the same snippet as yesterday. <confused>

Yes, I was just showing it for context.

Quoted 8 lines

(That snippet seems to have survived overnight fsx)

Quoted 4 lines

cmap may be longer than count_fsb_orig (which was the failing scenario). In that case, after calling xfs_reflink_convert_cow_locked(), we would have partially converted cmap, so it is proper to set cmap.br_state = XFS_EXT_NORM?

Quoted 4 lines

Hrm. Let me walk through this function again.

At the start, {offset,count}_fsb express the offset/length parameters, but in fsblock units and expanded to align with an fsblock.

I do also note that the usage of xfs_iomap_end_fsb() is a bit dubious, as this will truncate the write if it goes over s_maxbytes. However, we never want to truncate an atomic write.

Quoted 9 lines

If the cow fork has a mapping (cmap) that starts before the offset, we trim the mapping to the desired range and goto found. cmap cannot end before offset_fsb because that's how xfs_iext_lookup_extent works.

If the cow fork doesn't have a mapping or the one it does have doesn't start until after offset_fsb, then we trim count_fsb so that (offset_fsb, count_fsb) is the range that isn't mapped to space.

Quoted 5 lines

Then we cycle the ILOCK to get a transaction and do the lookup again due to "extent layout could have changed since the unlock". Same rules as the first lookup. I wonder if that second xfs_trim_extent should be using orig_count_fsb, because at this point it's trimming the cmap to the shorter range.

Yes, I think so.

Quoted 14 lines

It's probably not a big deal because iomap will
just call ->iomap_begin on the rest of the range, but it's more work.

If the second lookup doesn't produce a mapping, then we call xfs_bmapi_write to fill the hole and drop down to @found.

Now, we're at the found: label, having arrived here in one of three ways:

1) The first cmap lookup found at least one block that it can use. (offset_fsb, count_fsb) is the original range that iomap asked for. This mapping could be written or unwritten, and it's been trimmed so that it won't extend outside the requested write range.

Quoted 8 lines

2) The second cmap lookup found at least one block that it can use. (offset_fsb, count_fsb) is a truncated range because the cow fork has a mapping that starts at (offset_fsb + count_fsb). This mapping could also be written or unwritten, and it also has been trimmed so that it won't extend outside the hole range.

Why do we trim cmap to the hole range? The original write range will suite iomap just fine.

I think that the xfs_iext_lookup_extent() and xfs_trim_extent() pattern may have been just copied without considering this.

Quoted 7 lines

3) We xfs_bmapi_write'd a hole, and now we have an unwritten mapping. (offset_fsb, count_fsb) is the same truncated range from 2). cmap is potentially much larger than (offset_fsb, count_fsb).

Why do we not trim this mapping to the original write range?

I don't know, but this is what I was suggesting should happen.

Quoted 5 lines

If at this point the mapping is unwritten, we convert it to written mapping with xfs_reflink_convert_cow_locked. offset_fsb retains its original value, but what is count_fsb? It's either the original value
(1) or the smaller one from filling the hole (2) or (3)? Why don't we pass the cmap startoff/blockcount to this function?

I think that we should

Quoted 32 lines

As an aside: Changing count_fsb makes it harder for me to understand what's going on in this function.

Now, having converted either the range we arrived at via (1-3) (or with your patch, the original range) to written state, we set br_state and pass that back to iomap. I think in case (3) it's possible that xfs incorrectly reports to iomap an overly large mapping that might not actually reflect the cow fork contents, because we only converted so much of the mapping state.

Quoted 1 line

We should trim cmap to count_fsb_orig also, right?

iomap doesn't care if the mapping it receives spans more space than just the range it asked for, but XFS shouldn't be exposing mappings that don't reflect the actual state of the cow fork.

I think there are several things wrong with this function:

A) xfs_bmapi_write can return a much larger unwritten mapping than what the caller asked for. We convert part of that range to written, but return the entire written mapping to iomap even though that's inaccurate.

B) The arguments to xfs_reflink_convert_cow_locked are wrong -- an unwritten mapping could be *smaller* than the write range (or even the hole range). In this case, we convert too much file range to written state because we then return a smaller mapping to iomap.

C) It doesn't handle delalloc mappings. This I covered in the patch that I already sent to the list.

Quoted 8 lines

D) Reassigning count_fsb to handle the hole means that if the second cmap lookup attempt succeeds (due to racing with someone else) we trim the mapping more than is strictly necessary. The changing meaning of count_fsb makes this harder to notice.

E) The tracepoint is kinda wrong because @length is mutated. That makes it harder to chase the data flows through this function because you can't just grep on the pos/bytecount strings.

Yes, I was noticing this also.

Quoted 10 lines

F) We don't actually check that the br_state = XFS_EXT_NORM assignment is accurate, i.e. that the cow fork contains a written mapping for the range that we're interested in.

G) Somewhat inadequate documentation of why we need to xfs_trim_extent so aggressively in this function.

H) Not sure why xfs_iomap_end_fsb is used here, the vfs already clamped the write range to s_maxbytes.

Please note that I mentioned about this above.

Quoted 5 lines
Quoted 4 lines

I don't think that it makes much of a difference, but it seems the proper thing to do. Maybe the subsequent traces length values would be inconsistent with other path to @found label if we don't trim.

Quoted 5 lines
Quoted 3 lines

With A-H fixed, all the atomic writes issues I was seeing went away. I'll run this through the atomic writes QA tests and send a fix series to the list.

ok, thanks!

<nod> Fixes have now been sent to the list.

--D