Skip to contentSkip to conversation

Add more tests for multi fs block atomic writes

RFC Superseded Ojaswin Mujoo 4 participants 61 messages Jun 11, 2025 – Jun 30, 2025

v1 of 7 Superseded Latest is v7 →

lore
Share & tools
lore
#0 lore raw

cover Add more tests for multi fs block atomic writes

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 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!

(This is rebased over Darrick's atomic write tests:
https://lore.kernel.org/fstests/[email protected]/T/#t)

Ojaswin Mujoo (7):
common/rc: Add a helper to run fsx on a given file ltp/fsx.c: Add atomic writes support to fsx
generic/770: Add atomic write multi-fsblock O_[D]SYNC tests generic/771: Stress fsx with atomic writes enabled generic/772: Add sudden shutdown tests for multi block atomic writes ext4/063: Atomic write test for extent split across leaf nodes ext4/064: Add atomic write tests for journal credit calculation

Ritesh Harjani (IBM) (5):
common/preamble: Fix fsx for ext4 with bigalloc generic/767: Add atomic write test using fio crc check verifier generic/769: Add atomic write test using fio verify on file mixed
mappings
ext4/061: Atomic writes stress test for bigalloc using fio crc
verifier
ext4/062: Atomic writes test for bigalloc using fio crc verifier on
multiple files

 common/preamble       |  16 ++
 common/rc             |  21 ++-
 ltp/fsx.c             | 105 +++++++++++-
 tests/ext4/061        | 107 +++++++++++++
 tests/ext4/061.out    |   2 +
 tests/ext4/062        | 131 +++++++++++++++
 tests/ext4/062.out    |   2 +
 tests/ext4/063        | 125 +++++++++++++++
 tests/ext4/063.out    |   2 +
 tests/ext4/064        |  75 +++++++++
 tests/ext4/064.out    |   2 +
 tests/generic/767     |  84 ++++++++++
 tests/generic/767.out |   2 +
 tests/generic/769     | 101 ++++++++++++
 tests/generic/769.out |   2 +
 tests/generic/770     | 161 +++++++++++++++++++
 tests/generic/770.out |   2 +
 tests/generic/771     |  49 ++++++
 tests/generic/771.out |   2 +
 tests/generic/772     | 360 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/772.out |   2 +
 21 files changed, 1345 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/ext4/064
 create mode 100644 tests/ext4/064.out
 create mode 100755 tests/generic/767
 create mode 100644 tests/generic/767.out
 create mode 100755 tests/generic/769
 create mode 100644 tests/generic/769.out
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
 create mode 100755 tests/generic/772
 create mode 100644 tests/generic/772.out
Signature · 1 line
2.49.0
#1 lore raw

RFC 1/12 common/preamble: Fix fsx for ext4 with bigalloc

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +16 -0
index ba029a34..2bccff74 100644
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#2 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Querying that programmatically might be ... interesting though. Is there a good way to do that for ext4 bigalloc?

(See detect_xfs_alloc_unit in punch-alternating.c)

--D

Quoted 46 lines

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +16 -0
index ba029a34..2bccff74 100644
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#3 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 11 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Hey Darrick,

Yes makes sense but as you mentioned, I'm not sure if there is a way to programatically detect the bigalloc cluster size (or allocation unit in general) like we do for xfs.

(Adding Ted to CC in case he has some idea)

Quoted 54 lines

Querying that programmatically might be ... interesting though. Is there a good way to do that for ext4 bigalloc?

(See detect_xfs_alloc_unit in punch-alternating.c)

--D

Quoted 46 lines

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +16 -0
index ba029a34..2bccff74 100644
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#4 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 9 lines · Ojaswin Mujoo
Quoted 8 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Quoted 8 lines
Quoted 3 lines

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Hey Darrick,

Yes makes sense but as you mentioned, I'm not sure if there is a way to programatically detect the bigalloc cluster size (or allocation unit in general) like we do for xfs.

I don't either, but maybe we should have a way reveal the allocation unit size for a given file? Yet another statx field? :P

(It /would/ be useful for programs that use collapse/insert range)

--D

Quoted 56 lines

(Adding Ted to CC in case he has some idea)

Quoted 54 lines

Querying that programmatically might be ... interesting though. Is there a good way to do that for ext4 bigalloc?

(See detect_xfs_alloc_unit in punch-alternating.c)

--D

Quoted 46 lines

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +16 -0
index ba029a34..2bccff74 100644
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#5 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 15 lines · Darrick J. Wong
Quoted 14 lines · Ojaswin Mujoo
Quoted 11 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Hey Darrick,

Quoted 8 lines
Quoted 3 lines

Yes makes sense but as you mentioned, I'm not sure if there is a way to programatically detect the bigalloc cluster size (or allocation unit in general) like we do for xfs.

I don't either, but maybe we should have a way reveal the allocation unit size for a given file? Yet another statx field? :P

(It /would/ be useful for programs that use collapse/insert range)

Yes it would, at the very least, help with defining clear semantics for collapse/insert range with bigalloc/rtvol because right now those operations just EINVAL if the range is not aligned correctly, which is confusing since it is not documented how to do it properly.

xfs does have an ioctl to get the geometry for rtvol. I think you are suggesting a more generic statx field which can be used by other FSes as well, right?

Regards,
ojaswin

Quoted 3 lines

--D

#6 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 25 lines · Ojaswin Mujoo
Quoted 15 lines · Darrick J. Wong
Quoted 14 lines · Ojaswin Mujoo
Quoted 11 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Hey Darrick,

Quoted 8 lines
Quoted 3 lines

Yes makes sense but as you mentioned, I'm not sure if there is a way to programatically detect the bigalloc cluster size (or allocation unit in general) like we do for xfs.

I don't either, but maybe we should have a way reveal the allocation unit size for a given file? Yet another statx field? :P

(It /would/ be useful for programs that use collapse/insert range)

Quoted 8 lines

Yes it would, at the very least, help with defining clear semantics for collapse/insert range with bigalloc/rtvol because right now those operations just EINVAL if the range is not aligned correctly, which is confusing since it is not documented how to do it properly.

xfs does have an ioctl to get the geometry for rtvol. I think you are suggesting a more generic statx field which can be used by other FSes as well, right?

Right, since other filesystems (fat, ntfs, etc) also have allocation units larger than the fsblock size. Most of the time the allocunit amplification simply doesn't matter to applications, but once in a while it does (collapse/insert range, cow) affect performance.

--D

Quoted 7 lines

Regards,
ojaswin

Quoted 3 lines

--D

#7 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 35 lines · Darrick J. Wong
Quoted 25 lines · Ojaswin Mujoo
Quoted 15 lines · Darrick J. Wong
Quoted 14 lines · Ojaswin Mujoo
Quoted 11 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Hey Darrick,

Quoted 8 lines
Quoted 3 lines

Yes makes sense but as you mentioned, I'm not sure if there is a way to programatically detect the bigalloc cluster size (or allocation unit in general) like we do for xfs.

I don't either, but maybe we should have a way reveal the allocation unit size for a given file? Yet another statx field? :P

(It /would/ be useful for programs that use collapse/insert range)

Quoted 8 lines

Yes it would, at the very least, help with defining clear semantics for collapse/insert range with bigalloc/rtvol because right now those operations just EINVAL if the range is not aligned correctly, which is confusing since it is not documented how to do it properly.

xfs does have an ioctl to get the geometry for rtvol. I think you are suggesting a more generic statx field which can be used by other FSes as well, right?

Quoted 6 lines

Right, since other filesystems (fat, ntfs, etc) also have allocation units larger than the fsblock size. Most of the time the allocunit amplification simply doesn't matter to applications, but once in a while it does (collapse/insert range, cow) affect performance.

--D

Makes sense Darrick. I can look into it.

For this patch, is it okay to keep the approach of disabling collapse/insert range for bigalloc for now and we can change fsx later if we add support for exposing alloc units.

Regards,
Ojaswin

#8 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 41 lines · Ojaswin Mujoo
Quoted 40 lines · Darrick J. Wong
Quoted 25 lines · Ojaswin Mujoo
Quoted 15 lines · Darrick J. Wong
Quoted 14 lines · Ojaswin Mujoo
Quoted 11 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

Hmmm, insert/collapse-range have the same behavior on xfs realtime, maybe we should amend test() in fsx to round to the allocation unit size?

Hey Darrick,

Quoted 8 lines
Quoted 3 lines

Yes makes sense but as you mentioned, I'm not sure if there is a way to programatically detect the bigalloc cluster size (or allocation unit in general) like we do for xfs.

I don't either, but maybe we should have a way reveal the allocation unit size for a given file? Yet another statx field? :P

(It /would/ be useful for programs that use collapse/insert range)

Quoted 8 lines

Yes it would, at the very least, help with defining clear semantics for collapse/insert range with bigalloc/rtvol because right now those operations just EINVAL if the range is not aligned correctly, which is confusing since it is not documented how to do it properly.

xfs does have an ioctl to get the geometry for rtvol. I think you are suggesting a more generic statx field which can be used by other FSes as well, right?

Right, since other filesystems (fat, ntfs, etc) also have allocation units larger than the fsblock size. Most of the time the allocunit amplification simply doesn't matter to applications, but once in a while it does (collapse/insert range, cow) affect performance.

Quoted 7 lines
Quoted 1 line

--D

Makes sense Darrick. I can look into it.

For this patch, is it okay to keep the approach of disabling collapse/insert range for bigalloc for now and we can change fsx later if we add support for exposing alloc units.

Yeah, particularly since there's no easy way for userspace to find out the collapse/insert unit size.

--D

Quoted 3 lines

Regards,
Ojaswin

#9 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

In reply to Ojaswin Mujoo
Quoted 22 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +0 -0
index ba029a34..2bccff74 100644

common/preamble _register_cleanup() · line 28

Quoted 5 lines
common/preamble +2 -0
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {

If this's a function for fsx only, better to name it with "fsx", e.g. _setup_default_fsx_avoid (or some other names).

common/preamble _register_cleanup() · line 32

Quoted 4 lines
common/preamble +4 -0
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"

Hmm... I'm also wondering if this's an issue should be fixed in fstests. How about let the testers who tests ext4 with MKFS_OPTIONS="-O bigalloc" write local.config as below?

[ext4-bigalloc]
...
MKFS_OPTIONS="-O bigalloc"
FSX_AVOID="-I -C"

Thanks,
Zorro

Quoted 22 lines
common/preamble +10 -0
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#10 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 41 lines · Zorro Lang
Quoted 22 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +0 -0
index ba029a34..2bccff74 100644

common/preamble _register_cleanup() · line 28

Quoted 5 lines
common/preamble +2 -0
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {

If this's a function for fsx only, better to name it with "fsx", e.g. _setup_default_fsx_avoid (or some other names).

common/preamble _register_cleanup() · line 32

Quoted 4 lines
common/preamble +4 -0
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"

Hmm... I'm also wondering if this's an issue should be fixed in fstests. How about let the testers who tests ext4 with MKFS_OPTIONS="-O bigalloc" write local.config as below?

Quoted 7 lines

[ext4-bigalloc]
...
MKFS_OPTIONS="-O bigalloc"
FSX_AVOID="-I -C"

Thanks,
Zorro

Hey Zorro,

Basically the idea is that _setup_fs_options is a generic function that can be used to do any fs specific modifications to the global options.

This way we can set options to avoid known issues with different FSes, which can otherwise confuse the user if they are not aware of such issues.

Does that sound okay?

Regards,
ojaswin

Quoted 25 lines
Quoted 24 lines
common/preamble +10 -0
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#11 lore raw

Re: [RFC 01/12] common/preamble: Fix fsx for ext4 with bigalloc

Quoted 52 lines · Ojaswin Mujoo
Quoted 41 lines · Zorro Lang
Quoted 22 lines · Ojaswin Mujoo

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

Insert range and collapse range only works with bigalloc in case the range is cluster size aligned, which fsx doesnt take care. To work past this, disable insert range and collapse range on ext4, if bigalloc is enabled.

This is achieved by defining a new function _setup_fs_options which can serve as a mechanism to apply FS-wide options to the tests.

---
 common/preamble | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
common/preamble +0 -0
index ba029a34..2bccff74 100644

common/preamble _register_cleanup() · line 28

Quoted 5 lines
common/preamble +2 -0
@@ -24,6 +24,20 @@ _register_cleanup()
 	trap "${cleanup}exit \$status" EXIT HUP INT QUIT TERM $*
 }
 
+# setup FS options only to be available for each test run
+_setup_fs_options() {

If this's a function for fsx only, better to name it with "fsx", e.g. _setup_default_fsx_avoid (or some other names).

common/preamble _register_cleanup() · line 32

Quoted 4 lines
common/preamble +4 -0
+	case "$FSTYP" in
+	"ext4")
+		if [[ "$MKFS_OPTIONS" =~ bigalloc ]]; then
+			export FSX_AVOID="-I -C"

Hmm... I'm also wondering if this's an issue should be fixed in fstests. How about let the testers who tests ext4 with MKFS_OPTIONS="-O bigalloc" write local.config as below?

Quoted 7 lines

[ext4-bigalloc]
...
MKFS_OPTIONS="-O bigalloc"
FSX_AVOID="-I -C"

Thanks,
Zorro

Hey Zorro,

Quoted 8 lines

Basically the idea is that _setup_fs_options is a generic function that can be used to do any fs specific modifications to the global options.

This way we can set options to avoid known issues with different FSes, which can otherwise confuse the user if they are not aware of such issues.

Does that sound okay?

I think we should give this choice to the user or the test case who wants to test ext4 bigalloc feature. And MKFS_OPTIONS isn't the only way to enable bigalloc, some test cases can do _scratch_mkfs "-O bigalloc".

**But, if *ext4 list* really hope to force set FSX_AVOID="-I -C" for "$MKFS_OPTIONS" =~ bigalloc, then better to do this in _run_fsx* function at first, I don't think it's worth having a global _setup_fs_options, and call it at the beginning of each case running for now.

Thanks,
Zorro

Quoted 30 lines

Regards,
ojaswin

Quoted 25 lines
Quoted 24 lines
common/preamble +10 -0
+		fi
+		;;
+	# Add other filesystem types here as needed
+	*)
+		;;
+	esac
+}
+
 # Prepare to run a fstest by initializing the required global variables to
 # their defaults, sourcing common functions, registering a cleanup function,
 # and removing the $seqres.full file.
@@ -55,4 +69,6 @@ _begin_fstest()
 	# remove previous $seqres.full before test
 	rm -f $seqres.full $seqres.hints
 
+	# setup filesystem options for a given test execution
+	_setup_fs_options
 }
Signature · 1 line
2.49.0
#12 lore raw

RFC 2/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 | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)
common/rc +18 -3
index cfbe2a5f..a5d811a1 100644
@@ -5115,13 +5115,22 @@ _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
@@ -5133,6 +5142,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
#13 lore raw

Re: [RFC 02/12] common/rc: Add a helper to run fsx on a given file

Quoted 26 lines · 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 | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)
common/rc +4 -1
index cfbe2a5f..a5d811a1 100644
@@ -5115,13 +5115,22 @@ _require_hugepage_fsx()
 		_notrun "fsx binary does not support MADV_COLLAPSE"
 }
 
-_run_fsx()
+_run_fsx_on_file()
 {
+	local testfile=$1
+	shift
+

common/rc _require_hugepage_fsx() · line 5131

Quoted 10 lines
common/rc +7 -1
+	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
	local testfile="${1:-$TEST_DIR/junk}"
	...
	set -- $FSX_PROG $args $FSX_AVOID $testfile

Then you don't need the extra helper.

--D

Quoted 23 lines
common/rc +7 -1
 	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
@@ -5133,6 +5142,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
#14 lore raw

Re: [RFC 02/12] common/rc: Add a helper to run fsx on a given file

Quoted 42 lines · Darrick J. Wong
Quoted 26 lines · 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 | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)
common/rc +4 -1
index cfbe2a5f..a5d811a1 100644
@@ -5115,13 +5115,22 @@ _require_hugepage_fsx()
 		_notrun "fsx binary does not support MADV_COLLAPSE"
 }
 
-_run_fsx()
+_run_fsx_on_file()
 {
+	local testfile=$1
+	shift
+

common/rc _require_hugepage_fsx() · line 5131

Quoted 10 lines
common/rc +7 -1
+	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
	local testfile="${1:-$TEST_DIR/junk}"
	...
	set -- $FSX_PROG $args $FSX_AVOID $testfile
Quoted 1 line

Then you don't need the extra helper.

Hi Darrick,

I originally added the extra helper so that we won't need to change the existing run_fsx calls. For example, with the change you suggested, generic/263 would need to be changed from:

run_fsx -N 10000 -o 8192 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z

to

run_fsx $TEST_DIR/junk -N 10000 -o 8192 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z

because otherwise, the $1 would actually be "-N" and run_fsx would get confused.

However, if you would prefer to drop the helper and rather modify run_fsx and the call sites to pass a filename, that should be doable as well. Please let me know your preference on this.

Regards,
ojaswin

Quoted 26 lines

--D

Quoted 23 lines
common/rc +7 -1
 	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
@@ -5133,6 +5142,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
Reviewed-by
#15 lore raw

Re: [RFC 02/12] common/rc: Add a helper to run fsx on a given file

Quoted 54 lines · Ojaswin Mujoo
Quoted 42 lines · Darrick J. Wong
Quoted 26 lines · 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 | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)
common/rc +4 -1
index cfbe2a5f..a5d811a1 100644
@@ -5115,13 +5115,22 @@ _require_hugepage_fsx()
 		_notrun "fsx binary does not support MADV_COLLAPSE"
 }
 
-_run_fsx()
+_run_fsx_on_file()
 {
+	local testfile=$1
+	shift
+

common/rc _require_hugepage_fsx() · line 5131

Quoted 10 lines
common/rc +7 -1
+	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
	local testfile="${1:-$TEST_DIR/junk}"
	...
	set -- $FSX_PROG $args $FSX_AVOID $testfile
Quoted 1 line

Then you don't need the extra helper.

Hi Darrick,

I originally added the extra helper so that we won't need to change the existing run_fsx calls. For example, with the change you suggested, generic/263 would need to be changed from:

run_fsx -N 10000 -o 8192 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z

to

Quoted 8 lines

run_fsx $TEST_DIR/junk -N 10000 -o 8192 -l 500000 -r PSIZE -t BSIZE -w BSIZE -Z

because otherwise, the $1 would actually be "-N" and run_fsx would get confused.

However, if you would prefer to drop the helper and rather modify run_fsx and the call sites to pass a filename, that should be doable as well. Please let me know your preference on this.

Ah, right. I forgot that the function filters its arguments but then passes them on to fsx. Ignore this comment, then. :)

--D

Quoted 31 lines

Regards,
ojaswin

Quoted 26 lines

--D

Quoted 23 lines
common/rc +7 -1
 	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
@@ -5133,6 +5142,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
#16 lore raw

RFC 3/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 | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 100 insertions(+), 5 deletions(-)
ltp/fsx.c +100 -5
index 163b9453..9353fe6f 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 = 0;		/* -a flag */
+
+/* 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 inbetween 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,30 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	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)
@@ -2385,6 +2466,14 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		if (do_atomic_writes)
+			dowrite(offset, size, RWF_ATOMIC);
+		else
+			dowrite(offset, size, 0);
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2600,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: enable atomic writes if IO stack supports it\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 +3149,12 @@ 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':
+			do_atomic_writes = 1;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3568,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
#17 lore raw

Re: [RFC 03/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 178 lines · Ojaswin Mujoo

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

---
 ltp/fsx.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 100 insertions(+), 5 deletions(-)
ltp/fsx.c +84 -3
index 163b9453..9353fe6f 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 = 0;		/* -a flag */
+
+/* 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 inbetween 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,30 @@ do_dedupe_range(unsigned offset, unsigned length, unsigned dest)
 }
 #endif
 
+int test_atomic_writes(void) {
+	int ret;
+	struct statx stx;
+
+	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)

ltp/fsx.c have_op: · line 2474

Quoted 9 lines
ltp/fsx.c +6 -0
@@ -2385,6 +2466,14 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		if (do_atomic_writes)
+			dowrite(offset, size, RWF_ATOMIC);
+		else
+			dowrite(offset, size, 0);

Er.... shouldn't we skip OP_ATOMIC_WRITE if !do_atomic_writes? There's a whole switch statement further up in test() that does things like:

	case OP_COPY_RANGE:
		if (!copy_range_calls) {
			log5(op, offset, size, offset2, FL_SKIPPED);
			goto out;
		}
		break;

to break out early.

--D

Quoted 48 lines
ltp/fsx.c +10 -2
+		break;
+
 	case OP_MAPREAD:
 		TRIM_OFF_LEN(offset, size, file_size);
 		domapread(offset, size);
@@ -2511,13 +2600,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: enable atomic writes if IO stack supports it\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 +3149,12 @@ 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':
+			do_atomic_writes = 1;
+			break;
 		case 'b':
 			simulatedopcount = getnum(optarg, &endp);
 			if (!quiet)
@@ -3475,6 +3568,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
#18 lore raw

Re: [RFC 03/12] ltp/fsx.c: Add atomic writes support to fsx

Quoted 7 lines · Darrick J. Wong
Quoted 6 lines · Ojaswin Mujoo

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

---

<snip>

Quoted 26 lines
Quoted 5 lines
ltp/fsx.c +1 -0
+
 #ifdef HAVE_COPY_FILE_RANGE
 int
 test_copy_range(void)

ltp/fsx.c have_op: · line 2474

Quoted 9 lines
ltp/fsx.c +6 -0
@@ -2385,6 +2466,14 @@ have_op:
 			dowrite(offset, size, 0);
 		break;
 
+	case OP_WRITE_ATOMIC:
+		TRIM_OFF_LEN(offset, size, maxfilelen);
+		if (do_atomic_writes)
+			dowrite(offset, size, RWF_ATOMIC);
+		else
+			dowrite(offset, size, 0);

Er.... shouldn't we skip OP_ATOMIC_WRITE if !do_atomic_writes? There's a whole switch statement further up in test() that does things like:

	case OP_COPY_RANGE:
		if (!copy_range_calls) {
			log5(op, offset, size, offset2, FL_SKIPPED);
			goto out;
		}
		break;
Quoted 3 lines

to break out early.

--D

Got it, I'll make the change Darrick.

Thanks,
ojaswin

#19 lore raw

RFC 4/12 generic/767: Add atomic write test using fio crc check verifier

In reply to Ojaswin Mujoo

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

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc for each data block. If the underlying device supports atomic write then it is guaranteed that we will never have a mix data from two threads writing on the same physical block.

---
 tests/generic/767     | 84 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/767.out |  2 ++
 2 files changed, 86 insertions(+)
 create mode 100755 tests/generic/767
 create mode 100644 tests/generic/767.out
tests/generic/767 new file +84 -0
new file mode 100755
index 00000000..4f80e7b6
@@ -0,0 +1,84 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 767
+#
+# 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
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
+	if (( $1 > $2 )); then
+		echo "$2"
+	else
+		echo "$1"
+	fi
+}
+
+_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")
+blocksize=$(max "$awu_min_write" "$((awu_max_write/2))")
+
+# XFS can have high awu_max_write due to software fallback. Cap it at 64k
+blocksize=$(min "$blocksize" "65536")
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((100 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[aio-dio-aw-verify]
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$blocksize
+fallocate=native
+filename=$SCRATCH_MNT/test-file
+size=$SIZE
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+group_reporting=1
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/767.out new file +2 -0
new file mode 100644
index 00000000..2bf7f989
@@ -0,0 +1,2 @@
+QA output created by 767
+Silence is golden
Signature · 1 line
2.49.0
#20 lore raw

Re: [RFC 04/12] generic/767: Add atomic write test using fio crc check verifier

Quoted 46 lines · Ojaswin Mujoo

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

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc for each data block. If the underlying device supports atomic write then it is guaranteed that we will never have a mix data from two threads writing on the same physical block.

---
 tests/generic/767     | 84 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/767.out |  2 ++
 2 files changed, 86 insertions(+)
 create mode 100755 tests/generic/767
 create mode 100644 tests/generic/767.out
tests/generic/767 new file +24 -0
new file mode 100755
index 00000000..4f80e7b6
@@ -0,0 +1,84 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 767
+#
+# 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
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi

tests/generic/767 line 34

Quoted 10 lines
tests/generic/767 new file +10 -0
+}
+
+function min()
+{
+	if (( $1 > $2 )); then
+		echo "$2"
+	else
+		echo "$1"
+	fi
+}

Should these be common/rc helpers?

Or, since bash is ... uh fun with arguments...

_min() {
	local ret

	for arg in "$@"; do
		if [ -z "$ret" ] || (( $arg < $ret )); then
			ret="$arg"
		fi
	done
	echo $ret
}

and then you can pass as many arguments as you like. The only downside is that you can pass stringly typed crap "_min cow frog" and it still returns "cow". As if.

Quoted 5 lines
tests/generic/767 new file +5 -0
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount
+
+touch "$SCRATCH_MNT/f1"
+awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1")

tests/generic/767 line 50

Quoted 10 lines
tests/generic/767 new file +10 -0
+awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1")
+blocksize=$(max "$awu_min_write" "$((awu_max_write/2))")
+
+# XFS can have high awu_max_write due to software fallback. Cap it at 64k
+blocksize=$(min "$blocksize" "65536")
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))

What program is nproc?

--D

Quoted 46 lines
tests/generic/767 new file +34 -0
+SIZE=$((100 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[aio-dio-aw-verify]
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$blocksize
+fallocate=native
+filename=$SCRATCH_MNT/test-file
+size=$SIZE
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+group_reporting=1
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/767.out new file +2 -0
new file mode 100644
index 00000000..2bf7f989
@@ -0,0 +1,2 @@
+QA output created by 767
+Silence is golden
Signature · 1 line
2.49.0
#21 lore raw

Re: [RFC 04/12] generic/767: Add atomic write test using fio crc check verifier

Quoted 73 lines · Darrick J. Wong
Quoted 46 lines · Ojaswin Mujoo

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

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc for each data block. If the underlying device supports atomic write then it is guaranteed that we will never have a mix data from two threads writing on the same physical block.

---
 tests/generic/767     | 84 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/767.out |  2 ++
 2 files changed, 86 insertions(+)
 create mode 100755 tests/generic/767
 create mode 100644 tests/generic/767.out
tests/generic/767 new file +24 -0
new file mode 100755
index 00000000..4f80e7b6
@@ -0,0 +1,84 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 767
+#
+# 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
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi

tests/generic/767 line 34

Quoted 10 lines
tests/generic/767 new file +10 -0
+}
+
+function min()
+{
+	if (( $1 > $2 )); then
+		echo "$2"
+	else
+		echo "$1"
+	fi
+}

Should these be common/rc helpers?

Or, since bash is ... uh fun with arguments...

_min() {
	local ret

	for arg in "$@"; do
		if [ -z "$ret" ] || (( $arg < $ret )); then
			ret="$arg"
		fi
	done
	echo $ret
}
Quoted 3 lines

and then you can pass as many arguments as you like. The only downside is that you can pass stringly typed crap "_min cow frog" and it still returns "cow". As if.

Yes this makes sense (sans the cow vs frog part :p) . I'll make the change in v2.

Quoted 10 lines
Quoted 10 lines
tests/generic/767 new file +9 -0
+_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")
+blocksize=$(max "$awu_min_write" "$((awu_max_write/2))")
+
+# XFS can have high awu_max_write due to software fallback. Cap it at 64k
Quoted 8 lines

tests/generic/767 line 50

Quoted 6 lines
tests/generic/767 new file +6 -0
+blocksize=$(min "$blocksize" "65536")
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))

What program is nproc?

It returns the number of CPUs so we can scale the load based on CPUs we have. It comes from coreutils so I think the distros should have it.

Thanks,
ojaswin

Quoted 49 lines

--D

Quoted 46 lines
tests/generic/767 new file +34 -0
+SIZE=$((100 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[aio-dio-aw-verify]
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$blocksize
+fallocate=native
+filename=$SCRATCH_MNT/test-file
+size=$SIZE
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+group_reporting=1
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/767.out new file +2 -0
new file mode 100644
index 00000000..2bf7f989
@@ -0,0 +1,2 @@
+QA output created by 767
+Silence is golden
Signature · 1 line
2.49.0
#22 lore raw

Re: [RFC 04/12] generic/767: Add atomic write test using fio crc check verifier

Quoted 99 lines · Ojaswin Mujoo
Quoted 73 lines · Darrick J. Wong
Quoted 46 lines · Ojaswin Mujoo

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

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc for each data block. If the underlying device supports atomic write then it is guaranteed that we will never have a mix data from two threads writing on the same physical block.

---
 tests/generic/767     | 84 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/767.out |  2 ++
 2 files changed, 86 insertions(+)
 create mode 100755 tests/generic/767
 create mode 100644 tests/generic/767.out
tests/generic/767 new file +24 -0
new file mode 100755
index 00000000..4f80e7b6
@@ -0,0 +1,84 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 767
+#
+# 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
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi

tests/generic/767 line 34

Quoted 10 lines
tests/generic/767 new file +10 -0
+}
+
+function min()
+{
+	if (( $1 > $2 )); then
+		echo "$2"
+	else
+		echo "$1"
+	fi
+}

Should these be common/rc helpers?

Or, since bash is ... uh fun with arguments...

_min() {
	local ret

	for arg in "$@"; do
		if [ -z "$ret" ] || (( $arg < $ret )); then
			ret="$arg"
		fi
	done
	echo $ret
}
Quoted 3 lines

and then you can pass as many arguments as you like. The only downside is that you can pass stringly typed crap "_min cow frog" and it still returns "cow". As if.

Yes this makes sense (sans the cow vs frog part :p) . I'll make the change in v2.

Quoted 10 lines
Quoted 10 lines
tests/generic/767 new file +9 -0
+_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")
+blocksize=$(max "$awu_min_write" "$((awu_max_write/2))")
+
+# XFS can have high awu_max_write due to software fallback. Cap it at 64k
Quoted 8 lines

tests/generic/767 line 50

Quoted 6 lines
tests/generic/767 new file +6 -0
+blocksize=$(min "$blocksize" "65536")
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))

What program is nproc?

Quoted 2 lines

It returns the number of CPUs so we can scale the load based on CPUs we have. It comes from coreutils so I think the distros should have it.

Ah, another variant on getconf _NPROCESSORS_ONLN. Though looking at the strace I guess its benefit is that it looks at the cpu affinity mask to compute the number of processors that can actually be used. Hrm, maybe that would be a good cleanup for another day...

--D

Quoted 53 lines

Thanks,
ojaswin

Quoted 49 lines

--D

Quoted 46 lines
tests/generic/767 new file +34 -0
+SIZE=$((100 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[aio-dio-aw-verify]
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$blocksize
+fallocate=native
+filename=$SCRATCH_MNT/test-file
+size=$SIZE
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+group_reporting=1
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/767.out new file +2 -0
new file mode 100644
index 00000000..2bf7f989
@@ -0,0 +1,2 @@
+QA output created by 767
+Silence is golden
Signature · 1 line
2.49.0
#23 lore raw

Re: [RFC 04/12] generic/767: Add atomic write test using fio crc check verifier

In reply to Ojaswin Mujoo
Quoted 7 lines · Ojaswin Mujoo

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

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc for each data block. If the underlying device supports atomic write then it is guaranteed that we will never have a mix data from two threads writing on the same physical block.

Quoted 5 lines
---
 tests/generic/767     | 84 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/767.out |  2 ++

I'd like to recommend using a bigger case number for this RFC test case (and others in this patchset), to help you to rebase on later fstests easily :)

Quoted 50 lines
 2 files changed, 86 insertions(+)
 create mode 100755 tests/generic/767
 create mode 100644 tests/generic/767.out
tests/generic/767 new file +40 -0
new file mode 100755
index 00000000..4f80e7b6
@@ -0,0 +1,84 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 767
+#
+# 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
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
+	if (( $1 > $2 )); then
+		echo "$2"
+	else
+		echo "$1"
+	fi
+}
+
+_scratch_mkfs >> $seqres.full 2>&1
+_scratch_mount
+
+touch "$SCRATCH_MNT/f1"
+awu_min_write=$(_get_atomic_write_unit_min "$SCRATCH_MNT/f1")

tests/generic/767 line 50

Quoted 10 lines
tests/generic/767 new file +10 -0
+awu_max_write=$(_get_atomic_write_unit_max "$SCRATCH_MNT/f1")
+blocksize=$(max "$awu_min_write" "$((awu_max_write/2))")
+
+# XFS can have high awu_max_write due to software fallback. Cap it at 64k
+blocksize=$(min "$blocksize" "65536")
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))

This's the first time I learn about this command :-D

[root@dell-per750-41 xfstests]# nproc
112
[root@dell-per750-41 xfstests]# type -P nproc /usr/bin/nproc
[root@dell-per750-41 xfstests]# rpm -qf type -P nproc coreutils-9.7-3.fc43.x86_64

Thanks,
Zorro

Quoted 46 lines
tests/generic/767 new file +34 -0
+SIZE=$((100 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[aio-dio-aw-verify]
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$blocksize
+fallocate=native
+filename=$SCRATCH_MNT/test-file
+size=$SIZE
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+group_reporting=1
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/767.out new file +2 -0
new file mode 100644
index 00000000..2bf7f989
@@ -0,0 +1,2 @@
+QA output created by 767
+Silence is golden
Signature · 1 line
2.49.0
#24 lore raw

Re: [RFC 04/12] generic/767: Add atomic write test using fio crc check verifier

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

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

This adds atomic write test using fio based on it's crc check verifier. fio adds a crc for each data block. If the underlying device supports atomic write then it is guaranteed that we will never have a mix data from two threads writing on the same physical block.

Quoted 8 lines
Quoted 5 lines
---
 tests/generic/767     | 84 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/767.out |  2 ++

I'd like to recommend using a bigger case number for this RFC test case (and others in this patchset), to help you to rebase on later fstests easily :)

Sure Zorro, I'll make this change in v2. I think g/1226 should be okay as the other atomic writes patchset [1] ends at 1225.

Thanks,
Ojaswin

#25 lore raw

RFC 5/12 generic/769: Add atomic write test using fio verify on file mixed mappings

In reply to Ojaswin Mujoo

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

This tests 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. This forces the filesystem allocator to allocate extents over mixed mapping regions to stress FS block allocators.

---
 tests/generic/769     | 101 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/769.out |   2 +
 2 files changed, 103 insertions(+)
 create mode 100755 tests/generic/769
 create mode 100644 tests/generic/769.out
tests/generic/769 new file +101 -0
new file mode 100755
index 00000000..469d6344
@@ -0,0 +1,101 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 769
+#
+# 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
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+_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)
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((128 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[global]
+ioengine=libaio
+fallocate=none
+filename=$SCRATCH_MNT/test-file
+filesize=$SIZE
+bs=$fsbsize
+direct=1
+verify=0
+group_reporting=1
+
+# Create written extents
+[written_blocks]
+stonewall
+ioengine=libaio
+rw=randwrite
+io_size=$((SIZE/3))
+random_generator=lfsr
+
+# Create unwritten extents
+[unwritten_blocks]
+stonewall
+ioengine=falloc
+rw=randwrite
+io_size=$((SIZE/3))
+random_generator=lfsr
+
+# atomic write to mixed mappings of written/unwritten/holes
+[atomic_write_aio_dio_job]
+stonewall
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$aw_bsize
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+size=$SIZE
+random_generator=lfsr
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/769.out new file +2 -0
new file mode 100644
index 00000000..1512b439
@@ -0,0 +1,2 @@
+QA output created by 769
+Silence is golden
Signature · 1 line
2.49.0
#26 lore raw

Re: [RFC 05/12] generic/769: Add atomic write test using fio verify on file mixed mappings

Quoted 38 lines · Ojaswin Mujoo

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

This tests 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. This forces the filesystem allocator to allocate extents over mixed mapping regions to stress FS block allocators.

---
 tests/generic/769     | 101 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/769.out |   2 +
 2 files changed, 103 insertions(+)
 create mode 100755 tests/generic/769
 create mode 100644 tests/generic/769.out
tests/generic/769 new file +16 -0
new file mode 100755
index 00000000..469d6344
@@ -0,0 +1,101 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 769
+#
+# 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

tests/generic/769 line 26

Quoted 10 lines
tests/generic/769 new file +10 -0
+_require_aio
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}

Yeah, there's that function again.

The rest of the test looks fine.

--D

Quoted 87 lines
tests/generic/769 new file +75 -0
+
+_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)
+
+fio_config=$tmp.fio
+fio_out=$tmp.fio.out
+
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((128 * 1024 * 1024))
+
+cat >$fio_config <<EOF
+[global]
+ioengine=libaio
+fallocate=none
+filename=$SCRATCH_MNT/test-file
+filesize=$SIZE
+bs=$fsbsize
+direct=1
+verify=0
+group_reporting=1
+
+# Create written extents
+[written_blocks]
+stonewall
+ioengine=libaio
+rw=randwrite
+io_size=$((SIZE/3))
+random_generator=lfsr
+
+# Create unwritten extents
+[unwritten_blocks]
+stonewall
+ioengine=falloc
+rw=randwrite
+io_size=$((SIZE/3))
+random_generator=lfsr
+
+# atomic write to mixed mappings of written/unwritten/holes
+[atomic_write_aio_dio_job]
+stonewall
+direct=1
+ioengine=libaio
+rw=randwrite
+bs=$aw_bsize
+iodepth=$FIO_LOAD
+numjobs=$FIO_LOAD
+size=$SIZE
+random_generator=lfsr
+verify_state_save=0
+verify=crc32c
+verify_fatal=1
+verify_dump=0
+verify_backlog=1024
+verify_async=4
+verify_write_sequence=0
+atomic=1
+EOF
+
+_require_fio $fio_config
+
+cat $fio_config >> $seqres.full
+$FIO_PROG $fio_config --output=$fio_out
+cat $fio_out >> $seqres.full
+
+# success, all done
+echo Silence is golden
+status=0
+exit
tests/generic/769.out new file +2 -0
new file mode 100644
index 00000000..1512b439
@@ -0,0 +1,2 @@
+QA output created by 769
+Silence is golden
Signature · 1 line
2.49.0
#27 lore raw

RFC 6/12 generic/770: Add atomic write multi-fsblock O_[D]SYNC tests

In reply to Ojaswin Mujoo

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

---
 tests/generic/770     | 161 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/770.out |   2 +
 2 files changed, 163 insertions(+)
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
tests/generic/770 new file +161 -0
new file mode 100755
index 00000000..2b98b3b3
@@ -0,0 +1,161 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 770
+#
+# 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
+
+_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
+	fi
+}
+
+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 "# Test 1: Do O_DSYNC atomic write on random mixed mapping (10 iterations):" >> $seqres.full
+# Calculate how many blocks (e.g. 4K) fit in awu_max (e.g. 64K)
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+# Run 10 iterations of the test
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		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 preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done
+echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full
+bytes_written=$($XFS_IO_PROG -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" $testfile | \
+                grep wrote | awk -F'[/ ]' '{print $2}')
+test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+_scratch_shutdown -v >> $seqres.full
+_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-2"
+check_data_integrity
+echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 3: Do O_DSYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations):" >> $seqres.full
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Shutdown Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		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 preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+
+	echo "Shutting down filesystem" >> $seqres.full
+	_scratch_shutdown -v >> $seqres.full
+	_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-3"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done
+echo "# Test 3: Do O_SYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations): OK" >> $seqres.full
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
+
tests/generic/770.out new file +2 -0
new file mode 100644
index 00000000..17994ed5
@@ -0,0 +1,2 @@
+QA output created by 770
+Silence is golden
Signature · 1 line
2.49.0
#28 lore raw

Re: [RFC 06/12] generic/770: Add atomic write multi-fsblock O_[D]SYNC tests

Quoted 24 lines · Ojaswin Mujoo

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

---
 tests/generic/770     | 161 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/770.out |   2 +
 2 files changed, 163 insertions(+)
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
tests/generic/770 new file +5 -0
new file mode 100755
index 00000000..2b98b3b3
@@ -0,0 +1,161 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 770

tests/generic/770 line 15

Quoted 10 lines
tests/generic/770 new file +10 -0
+#
+# 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_shutdown?

--D

Quoted 158 lines
tests/generic/770 new file +146 -0
+
+_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
+	fi
+}
+
+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 "# Test 1: Do O_DSYNC atomic write on random mixed mapping (10 iterations):" >> $seqres.full
+# Calculate how many blocks (e.g. 4K) fit in awu_max (e.g. 64K)
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+# Run 10 iterations of the test
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		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 preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done
+echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full
+bytes_written=$($XFS_IO_PROG -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" $testfile | \
+                grep wrote | awk -F'[/ ]' '{print $2}')
+test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+_scratch_shutdown -v >> $seqres.full
+_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-2"
+check_data_integrity
+echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 3: Do O_DSYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations):" >> $seqres.full
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Shutdown Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		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 preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+
+	echo "Shutting down filesystem" >> $seqres.full
+	_scratch_shutdown -v >> $seqres.full
+	_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-3"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done
+echo "# Test 3: Do O_SYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations): OK" >> $seqres.full
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
+
tests/generic/770.out new file +2 -0
new file mode 100644
index 00000000..17994ed5
@@ -0,0 +1,2 @@
+QA output created by 770
+Silence is golden
Signature · 1 line
2.49.0
#29 lore raw

Re: [RFC 06/12] generic/770: Add atomic write multi-fsblock O_[D]SYNC tests

Quoted 38 lines · Darrick J. Wong
Quoted 24 lines · Ojaswin Mujoo

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

---
 tests/generic/770     | 161 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/770.out |   2 +
 2 files changed, 163 insertions(+)
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
tests/generic/770 new file +5 -0
new file mode 100755
index 00000000..2b98b3b3
@@ -0,0 +1,161 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 770

tests/generic/770 line 15

Quoted 10 lines
tests/generic/770 new file +10 -0
+#
+# 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_shutdown?

Quoted 1 line

--D

I'll add that to v2, thanks!

Regards,
ojaswin

#30 lore raw

Re: [RFC 06/12] generic/770: Add atomic write multi-fsblock O_[D]SYNC tests

In reply to Ojaswin Mujoo
Quoted 82 lines · Ojaswin Mujoo

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

---
 tests/generic/770     | 161 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/770.out |   2 +
 2 files changed, 163 insertions(+)
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
tests/generic/770 new file +63 -0
new file mode 100755
index 00000000..2b98b3b3
@@ -0,0 +1,161 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 770
+#
+# 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
+
+_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
+	fi
+}
+
+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 "# Test 1: Do O_DSYNC atomic write on random mixed mapping (10 iterations):" >> $seqres.full
+# Calculate how many blocks (e.g. 4K) fit in awu_max (e.g. 64K)
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+# Run 10 iterations of the test
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		mapping="${mapping}${map}"

tests/generic/770 line 73

Quoted 10 lines
tests/generic/770 new file +10 -0
+
+		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

_require_xfs_io_command falloc

tests/generic/770 line 84

Quoted 11 lines
tests/generic/770 new file +11 -0
+				;;
+		esac
+		off=$((off + blksz))
+	done
+
+	echo "Mixed mapping preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \

_require_xfs_io_command pwrite -A

Quoted 5 lines
tests/generic/770 new file +5 -0
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full

tests/generic/770 line 99

Quoted 10 lines
tests/generic/770 new file +10 -0
+	echo >> $seqres.full
+done
+echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full
+bytes_written=$($XFS_IO_PROG -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" $testfile | \
+                grep wrote | awk -F'[/ ]' '{print $2}')
+test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+_scratch_shutdown -v >> $seqres.full

_require_scratch_shutdown

Quoted 45 lines
tests/generic/770 new file +45 -0
+_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-2"
+check_data_integrity
+echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 3: Do O_DSYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations):" >> $seqres.full
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Shutdown Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		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 preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				  grep wrote | awk -F'[/ ]' '{print $2}')

tests/generic/770 line 154

Quoted 10 lines
tests/generic/770 new file +10 -0
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+
+	echo "Shutting down filesystem" >> $seqres.full
+	_scratch_shutdown -v >> $seqres.full
+	_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-3"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done

Looks like there're two iterations (loop running codes), the code looks much similar, can we move them to one function then call it twice?

Thanks,
Zorro

Quoted 19 lines
tests/generic/770 new file +7 -0
+echo "# Test 3: Do O_SYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations): OK" >> $seqres.full
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
+
tests/generic/770.out new file +2 -0
new file mode 100644
index 00000000..17994ed5
@@ -0,0 +1,2 @@
+QA output created by 770
+Silence is golden
Signature · 1 line
2.49.0
#31 lore raw

Re: [RFC 06/12] generic/770: Add atomic write multi-fsblock O_[D]SYNC tests

Quoted 36 lines · Zorro Lang
Quoted 35 lines · Ojaswin Mujoo

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

---
 tests/generic/770     | 161 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/770.out |   2 +
 2 files changed, 163 insertions(+)
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
tests/generic/770 new file +16 -0
new file mode 100755
index 00000000..2b98b3b3
@@ -0,0 +1,161 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 770
+#
+# 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
+

<snip>

Quoted 5 lines

tests/generic/770 line 73

Quoted 2 lines
tests/generic/770 new file +2 -0
+			"U")
+				$XFS_IO_PROG -c "falloc $off $blksz" $testfile >> /dev/null

_require_xfs_io_command falloc

Quoted 13 lines

tests/generic/770 line 84

Quoted 11 lines
tests/generic/770 new file +11 -0
+				;;
+		esac
+		off=$((off + blksz))
+	done
+
+	echo "Mixed mapping preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \

_require_xfs_io_command pwrite -A

Hey Zorro,

pwrite -A and falloc command are already checked for in _require_atomic_write_test_commands helper used on top.

Quoted 11 lines
Quoted 11 lines
tests/generic/770 new file +9 -0
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done
+echo "# Test 1: Do O_SYNC atomic write on random mixed mapping (10 iterations): OK" >> $seqres.full
+
Quoted 8 lines

tests/generic/770 line 99

Quoted 6 lines
tests/generic/770 new file +6 -0
+echo >> $seqres.full
+echo "# Test 2: Do extending O_SYNC atomic writes: " >> $seqres.full
+bytes_written=$($XFS_IO_PROG -dstc "pwrite -A -V1 -b $awu_max 0 $awu_max" $testfile | \
+                grep wrote | awk -F'[/ ]' '{print $2}')
+test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+_scratch_shutdown -v >> $seqres.full

_require_scratch_shutdown

Thanks, i'll add it.

Quoted 51 lines
Quoted 51 lines
tests/generic/770 new file +50 -0
+_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-2"
+check_data_integrity
+echo "# Test 2: Do extending O_SYNC atomic writes: OK" >> $seqres.full
+
+echo >> $seqres.full
+echo "# Test 3: Do O_DSYNC atomic write on random mixed mapping with sudden fs shutdown (10 iterations):" >> $seqres.full
+num_blocks=$((awu_max / blksz))
+echo "Testing $num_blocks blocks of $blksz size within $awu_max region" >> $seqres.full
+
+operations=("W" "H" "U")
+
+for ((iteration=1; iteration<=10; iteration++)); do
+	echo "=== Mixed Mapping Shutdown Test Iteration $iteration ===" >> $seqres.full
+
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	off=0
+	mapping=""
+
+	for ((i=0; i<num_blocks; i++)); do
+		index=$((RANDOM % ${#operations[@]}))
+		map="${operations[$index]}"
+		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 preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \
+				  grep wrote | awk -F'[/ ]' '{print $2}')
+
+	test $bytes_written -eq $awu_max || echo "atomic write len=$awu_max failed"
+
+	echo "Shutting down filesystem" >> $seqres.full
+	_scratch_shutdown -v >> $seqres.full
Quoted 8 lines

tests/generic/770 line 154

Quoted 5 lines
tests/generic/770 new file +5 -0
+	_scratch_cycle_mount >>$seqres.full 2>&1 || _fail "remount failed for Test-3"
+	check_data_integrity
+	echo "Iteration $iteration completed: OK" >> $seqres.full
+	echo >> $seqres.full
+done

Looks like there're two iterations (loop running codes), the code looks much similar, can we move them to one function then call it twice?

Sure, i'll refactor it in v2.

Thanks,
ojaswin

Quoted 4 lines

Thanks,
Zorro

#32 lore raw

Re: [RFC 06/12] generic/770: Add atomic write multi-fsblock O_[D]SYNC tests

Quoted 59 lines · Ojaswin Mujoo
Quoted 36 lines · Zorro Lang
Quoted 35 lines · Ojaswin Mujoo

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

---
 tests/generic/770     | 161 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/770.out |   2 +
 2 files changed, 163 insertions(+)
 create mode 100755 tests/generic/770
 create mode 100644 tests/generic/770.out
tests/generic/770 new file +16 -0
new file mode 100755
index 00000000..2b98b3b3
@@ -0,0 +1,161 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 770
+#
+# 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
+

<snip>

Quoted 5 lines

tests/generic/770 line 73

Quoted 2 lines
tests/generic/770 new file +2 -0
+			"U")
+				$XFS_IO_PROG -c "falloc $off $blksz" $testfile >> /dev/null

_require_xfs_io_command falloc

Quoted 13 lines

tests/generic/770 line 84

Quoted 11 lines
tests/generic/770 new file +11 -0
+				;;
+		esac
+		off=$((off + blksz))
+	done
+
+	echo "Mixed mapping preparation complete. Full mapping pattern: $mapping" >> $seqres.full
+
+	sync $testfile
+
+	echo "Performing O_DSYNC atomic write over the entire $awu_max region" >> $seqres.full
+	bytes_written=$($XFS_IO_PROG -dc "pwrite -DA -V1 -b $awu_max 0 $awu_max" $testfile | \

_require_xfs_io_command pwrite -A

Quoted 4 lines

Hey Zorro,

pwrite -A and falloc command are already checked for in _require_atomic_write_test_commands helper used on top.

Oh, sure, sorry I didn't notice that :)

Thanks,
Zorro

#33 lore raw

RFC 7/12 generic/771: Stress fsx with atomic writes enabled

In reply to Ojaswin Mujoo

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

---
 tests/generic/771     | 49 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/771.out |  2 ++
 2 files changed, 51 insertions(+)
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
tests/generic/771 new file +49 -0
new file mode 100755
index 00000000..690dfa0a
@@ -0,0 +1,49 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 771
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+# Import common functions.
+. ./common/filter
+
+_require_test
+_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`
+
+# 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)
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full
+status=$?
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#34 lore raw

Re: [RFC 07/12] generic/771: Stress fsx with atomic writes enabled

Quoted 47 lines · Ojaswin Mujoo

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

---
 tests/generic/771     | 49 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/771.out |  2 ++
 2 files changed, 51 insertions(+)
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
tests/generic/771 new file +29 -0
new file mode 100755
index 00000000..690dfa0a
@@ -0,0 +1,49 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 771
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+# Import common functions.
+. ./common/filter
+
+_require_test
+_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`
+

tests/generic/771 line 39

Quoted 10 lines
tests/generic/771 new file +10 -0
+# 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)
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full

Don't we already get fsx stress testing RWF_ATOMIC through generic/521 and generic/522?

Also why is mmap write disabled?

--D

Quoted 22 lines
tests/generic/771 new file +10 -0
+status=$?
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#35 lore raw

Re: [RFC 07/12] generic/771: Stress fsx with atomic writes enabled

Quoted 53 lines · Darrick J. Wong
Quoted 52 lines · Ojaswin Mujoo

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

---
 tests/generic/771     | 49 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/771.out |  2 ++
 2 files changed, 51 insertions(+)
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
tests/generic/771 new file +34 -0
new file mode 100755
index 00000000..690dfa0a
@@ -0,0 +1,49 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 771
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+# Import common functions.
+. ./common/filter
+
+_require_test
+_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`
+
+# 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)
Quoted 8 lines

tests/generic/771 line 39

Quoted 5 lines
tests/generic/771 new file +5 -0
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full

Don't we already get fsx stress testing RWF_ATOMIC through generic/521 and generic/522?

So for RWF_ATOMIC we need to pass the -a flag to fsx which the other tests don't do. This test specifically passes -a to make sure we stress the RWF_ATOMIC code path intermixed with other operations. Further, other tests run FSX on test device whereas for our atomic write tests we generally assume the scratch dev is the one with atomic capabilities.

Quoted 2 lines

Also why is mmap write disabled?

I thought it'd be better to not mix mmap writes with direct writes but I can add it back if that's preferred.

Regards,
ojaswin

Quoted 25 lines

--D

Quoted 22 lines
tests/generic/771 new file +10 -0
+status=$?
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#36 lore raw

Re: [RFC 07/12] generic/771: Stress fsx with atomic writes enabled

Quoted 63 lines · Ojaswin Mujoo
Quoted 53 lines · Darrick J. Wong
Quoted 52 lines · Ojaswin Mujoo

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

---
 tests/generic/771     | 49 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/771.out |  2 ++
 2 files changed, 51 insertions(+)
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
tests/generic/771 new file +34 -0
new file mode 100755
index 00000000..690dfa0a
@@ -0,0 +1,49 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 771
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+# Import common functions.
+. ./common/filter
+
+_require_test
+_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`
+
+# 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)
Quoted 8 lines

tests/generic/771 line 39

Quoted 5 lines
tests/generic/771 new file +5 -0
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full

Don't we already get fsx stress testing RWF_ATOMIC through generic/521 and generic/522?

Quoted 5 lines

So for RWF_ATOMIC we need to pass the -a flag to fsx which the other tests don't do. This test specifically passes -a to make sure we stress the RWF_ATOMIC code path intermixed with other operations. Further, other tests run FSX on test device whereas for our atomic write tests we generally assume the scratch dev is the one with atomic capabilities.

Oh, I overlooked the part where it's not enabled by default if its presence can be detected by fsx. Can we flip the polarity of -a, that way RWF_ATOMIC will get tested with all the other fsx soak tests?

Quoted 4 lines
Quoted 1 line

Also why is mmap write disabled?

I thought it'd be better to not mix mmap writes with direct writes but I can add it back if that's preferred.

It's not unreasonable that some dumb program is going to try that some day, even though we all scream for ice crea^W^W^Wnot to mix the two IO paths.

--D

Quoted 28 lines

Regards,
ojaswin

Quoted 25 lines

--D

Quoted 22 lines
tests/generic/771 new file +10 -0
+status=$?
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#37 lore raw

Re: [RFC 07/12] generic/771: Stress fsx with atomic writes enabled

Quoted 3 lines · Darrick J. Wong
Quoted 2 lines · Ojaswin Mujoo
Quoted 1 line · Darrick J. Wong

On Wed, Jun 11, 2025 at 03:04:50PM +0530, Ojaswin Mujoo wrote:

<...>

Quoted 18 lines
Quoted 12 lines
Quoted 11 lines

tests/generic/771 line 39

Quoted 8 lines
tests/generic/771 new file +8 -0
+# -N numops: total # operations to do
+# -l flen: the upper bound on file size
+# -o oplen: the upper bound on operation size (64k default)
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full

Don't we already get fsx stress testing RWF_ATOMIC through generic/521 and generic/522?

Quoted 5 lines

So for RWF_ATOMIC we need to pass the -a flag to fsx which the other tests don't do. This test specifically passes -a to make sure we stress the RWF_ATOMIC code path intermixed with other operations. Further, other tests run FSX on test device whereas for our atomic write tests we generally assume the scratch dev is the one with atomic capabilities.

Quoted 3 lines

Oh, I overlooked the part where it's not enabled by default if its presence can be detected by fsx. Can we flip the polarity of -a, that way RWF_ATOMIC will get tested with all the other fsx soak tests?

Sure Darrick, I'll do that in next revision.

Quoted 9 lines
Quoted 5 lines
Quoted 2 lines

Also why is mmap write disabled?

I thought it'd be better to not mix mmap writes with direct writes but I can add it back if that's preferred.

It's not unreasonable that some dumb program is going to try that some day, even though we all scream for ice crea^W^W^Wnot to mix the two IO paths.

haha makes sense, i'll enable it as well.

Quoted 31 lines

--D

Quoted 28 lines

Regards,
ojaswin

Quoted 25 lines

--D

Quoted 22 lines
tests/generic/771 new file +10 -0
+status=$?
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#38 lore raw

Re: [RFC 07/12] generic/771: Stress fsx with atomic writes enabled

In reply to Ojaswin Mujoo
Quoted 22 lines · Ojaswin Mujoo

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

---
 tests/generic/771     | 49 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/771.out |  2 ++
 2 files changed, 51 insertions(+)
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
tests/generic/771 new file +4 -0
new file mode 100755
index 00000000..690dfa0a
@@ -0,0 +1,49 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#

tests/generic/771 line 14

Quoted 10 lines
tests/generic/771 new file +10 -0
+# FS QA Test 771
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+# Import common functions.
+. ./common/filter

I think this's useless for this case.

tests/generic/771 line 16

Quoted 2 lines
tests/generic/771 new file +2 -0
+
+_require_test

Do you use TEST_DEV or TEST_MNT ?

tests/generic/771 line 28

Quoted 12 lines
tests/generic/771 new file +12 -0
+_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`

Do you need _require_block_device? Or is nfs, cifs or overlay... good for this test?

tests/generic/771 line 40

Quoted 12 lines
tests/generic/771 new file +12 -0
+
+# 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)
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full
+status=$?

Generally we _exit directly after changing $status.

tests/generic/771 line 45

Quoted 5 lines
tests/generic/771 new file +5 -0
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi

but you don't _exit ...

tests/generic/771 line 48

Quoted 3 lines
tests/generic/771 new file +3 -0
+
+echo "Silence is golden"
+status=0

Then the status will be set to 0 again. So above "status=$?" is useless. You can check "$?" directly, or exit directly if _run_fsx fails.

Thanks,
Zorro

Quoted 13 lines
tests/generic/771 new file +1 -0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#39 lore raw

Re: [RFC 07/12] generic/771: Stress fsx with atomic writes enabled

Quoted 36 lines · Zorro Lang
Quoted 22 lines · Ojaswin Mujoo

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

---
 tests/generic/771     | 49 +++++++++++++++++++++++++++++++++++++++++++
 tests/generic/771.out |  2 ++
 2 files changed, 51 insertions(+)
 create mode 100755 tests/generic/771
 create mode 100644 tests/generic/771.out
tests/generic/771 new file +4 -0
new file mode 100755
index 00000000..690dfa0a
@@ -0,0 +1,49 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#

tests/generic/771 line 14

Quoted 10 lines
tests/generic/771 new file +10 -0
+# FS QA Test 771
+#
+# fuzz fsx with atomic writes
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest rw auto quick atomicwrites
+
+# Import common functions.
+. ./common/filter

I think this's useless for this case.

Quoted 4 lines

tests/generic/771 line 16

Quoted 2 lines
tests/generic/771 new file +2 -0
+
+_require_test

Do you use TEST_DEV or TEST_MNT ?

No we dont, thanks for pointing this out. I'll change that and remove common/filter as wel..

Quoted 15 lines

tests/generic/771 line 28

Quoted 13 lines
tests/generic/771 new file +12 -0
+_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`

Do you need _require_block_device? Or is nfs, cifs or overlay... good for this test?

I think it should be okay to not use _require_block_device since all we care about is the underlying device support atomic write, which we already ensure via _require_scratch_write_atomic.

Quoted 31 lines

tests/generic/771 line 40

Quoted 13 lines
tests/generic/771 new file +12 -0
+
+# 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)
+# -w writebdy: $psize would make writes page aligned (on i386)
+# -Z: O_DIRECT (use -R, -W, -r and -w too)
+# -W: mapped write operations DISabled
+
+_run_fsx_on_file $testfile -N 10000 -a -o $awu_max  -l 500000 -r $bsize -w $bsize -Z -W $FSX_AVOID >> $seqres.full
+status=$?

Generally we _exit directly after changing $status.

tests/generic/771 line 45

Quoted 5 lines
tests/generic/771 new file +5 -0
+
+if [[ "$status" != "0" ]]
+then
+	echo "Somthing went wrong, check $seqres.full"
+fi

but you don't _exit ...

tests/generic/771 line 48

Quoted 3 lines
tests/generic/771 new file +3 -0
+
+echo "Silence is golden"
+status=0

Then the status will be set to 0 again. So above "status=$?" is useless. You can check "$?" directly, or exit directly if _run_fsx fails.

Quoted 2 lines

Thanks,
Zorro

Yes, this seems like a mistake. I will directly _fail in case _run_fsx returns error.

Thanks for the review!
Ojaswin

Quoted 15 lines
Quoted 14 lines
tests/generic/771 new file +1 -0
+exit
tests/generic/771.out new file +2 -0
new file mode 100644
index 00000000..c2345c7b
@@ -0,0 +1,2 @@
+QA output created by 771
+Silence is golden
Signature · 1 line
2.49.0
#40 lore raw

RFC 8/12 generic/772: 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.

---
 tests/generic/772     | 360 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/772.out |   2 +
 2 files changed, 362 insertions(+)
 create mode 100755 tests/generic/772
 create mode 100644 tests/generic/772.out
tests/generic/772 new file +360 -0
new file mode 100755
index 00000000..6af7e74c
@@ -0,0 +1,360 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 0772
+#
+# 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
+
+_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))
+filesize=$(($blksz * 12 * 1024 ))
+
+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
+}
+
+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=$(xxd -s 0 -l $awu_max -p $testfile.exp_old_mixed)
+
+	$XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full
+	expected_data_old_zeroes=$(xxd -s 0 -l $awu_max -p $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=$(xxd -s 0 -l $awu_max -p $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=$(xxd -s 0 -l $awu_max -p $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 data integrity 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)
+		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 "Expected old: "
+			echo "$expected_data_old"
+			echo
+			echo "Expected new: "
+			echo "$expected_data_new"
+			echo
+			echo "Actual contents: "
+			echo "$actual_data"
+
+			return 1
+		fi
+		echo -n "Check at offset $off suceeded! " >> $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
+
+	return 0
+}
+
+# test data integrity for file by shutting down in between atomic writes
+test_data_integrity() {
+	echo >> $seqres.full
+	echo "# Writing atomically to file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown
+
+	kill $awloop_pid
+	wait $awloop_pid
+
+	last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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 happended
+	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 data integrity for file wiht written and unwritten mappings
+test_data_integrity_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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+# test data integrity for file with completely written mappings
+test_data_integrity_writ() {
+	$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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+# test data integrity for file with completely unwritten mappings
+test_data_integrity_unwrit() {
+	$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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+# test data integrity for file with no mappings
+test_data_integrity_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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+test_filesize_integrity() {
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Performing extending atomic writes over file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown
+
+	kill $awloop_pid
+	wait $awloop_pid
+
+	local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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"
+
+	return $?
+}
+
+$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+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 data integrity test for atomic writes over mixed mapping" >> $seqres.full
+	test_data_integrity_mixed
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full
+	test_data_integrity_writ
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full
+	test_data_integrity_unwrit
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full
+	test_data_integrity_hole
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting filesize integrity test for atomic writes" >> $seqres.full
+	test_filesize_integrity
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+done
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/772.out new file +2 -0
new file mode 100644
index 00000000..98c13968
@@ -0,0 +1,2 @@
+QA output created by 772
+Silence is golden
Signature · 1 line
2.49.0
#41 lore raw

Re: [RFC 08/12] generic/772: Add sudden shutdown tests for multi block atomic writes

Quoted 22 lines · Ojaswin Mujoo

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

---
 tests/generic/772     | 360 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/772.out |   2 +
 2 files changed, 362 insertions(+)
 create mode 100755 tests/generic/772
 create mode 100644 tests/generic/772.out
tests/generic/772 new file +4 -0
new file mode 100755
index 00000000..6af7e74c
@@ -0,0 +1,360 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#

tests/generic/772 line 14

Quoted 10 lines
tests/generic/772 new file +10 -0
+# FS QA Test 0772
+#
+# 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_shutdown?

Otherwise seems fine to me...

--D

Quoted 358 lines
tests/generic/772 new file +346 -0
+
+_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))
+filesize=$(($blksz * 12 * 1024 ))
+
+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
+}
+
+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=$(xxd -s 0 -l $awu_max -p $testfile.exp_old_mixed)
+
+	$XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full
+	expected_data_old_zeroes=$(xxd -s 0 -l $awu_max -p $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=$(xxd -s 0 -l $awu_max -p $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=$(xxd -s 0 -l $awu_max -p $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 data integrity 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)
+		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 "Expected old: "
+			echo "$expected_data_old"
+			echo
+			echo "Expected new: "
+			echo "$expected_data_new"
+			echo
+			echo "Actual contents: "
+			echo "$actual_data"
+
+			return 1
+		fi
+		echo -n "Check at offset $off suceeded! " >> $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
+
+	return 0
+}
+
+# test data integrity for file by shutting down in between atomic writes
+test_data_integrity() {
+	echo >> $seqres.full
+	echo "# Writing atomically to file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown
+
+	kill $awloop_pid
+	wait $awloop_pid
+
+	last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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 happended
+	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 data integrity for file wiht written and unwritten mappings
+test_data_integrity_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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+# test data integrity for file with completely written mappings
+test_data_integrity_writ() {
+	$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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+# test data integrity for file with completely unwritten mappings
+test_data_integrity_unwrit() {
+	$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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+# test data integrity for file with no mappings
+test_data_integrity_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_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi
+}
+
+test_filesize_integrity() {
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Performing extending atomic writes over file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown
+
+	kill $awloop_pid
+	wait $awloop_pid
+
+	local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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"
+
+	return $?
+}
+
+$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+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 data integrity test for atomic writes over mixed mapping" >> $seqres.full
+	test_data_integrity_mixed
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full
+	test_data_integrity_writ
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full
+	test_data_integrity_unwrit
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full
+	test_data_integrity_hole
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting filesize integrity test for atomic writes" >> $seqres.full
+	test_filesize_integrity
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+done
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/772.out new file +2 -0
new file mode 100644
index 00000000..98c13968
@@ -0,0 +1,2 @@
+QA output created by 772
+Silence is golden
Signature · 1 line
2.49.0
#42 lore raw

Re: [RFC 08/12] generic/772: Add sudden shutdown tests for multi block atomic writes

Quoted 36 lines · Darrick J. Wong
Quoted 22 lines · Ojaswin Mujoo

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

---
 tests/generic/772     | 360 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/772.out |   2 +
 2 files changed, 362 insertions(+)
 create mode 100755 tests/generic/772
 create mode 100644 tests/generic/772.out
tests/generic/772 new file +4 -0
new file mode 100755
index 00000000..6af7e74c
@@ -0,0 +1,360 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#

tests/generic/772 line 14

Quoted 10 lines
tests/generic/772 new file +10 -0
+# FS QA Test 0772
+#
+# 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_shutdown?

Quoted 3 lines

Otherwise seems fine to me...

--D

Will do, thanks for the reviews!

Regards,
ojaswin

#43 lore raw

Re: [RFC 08/12] generic/772: Add sudden shutdown tests for multi block atomic writes

In reply to Ojaswin Mujoo
Quoted 43 lines · Ojaswin Mujoo

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

---
 tests/generic/772     | 360 ++++++++++++++++++++++++++++++++++++++++++
 tests/generic/772.out |   2 +
 2 files changed, 362 insertions(+)
 create mode 100755 tests/generic/772
 create mode 100644 tests/generic/772.out
tests/generic/772 new file +25 -0
new file mode 100755
index 00000000..6af7e74c
@@ -0,0 +1,360 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 0772
+#
+# 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
+
+_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
+

tests/generic/772 line 35

Quoted 10 lines
tests/generic/772 new file +10 -0
+num_blocks=$((awu_max / blksz))
+filesize=$(($blksz * 12 * 1024 ))
+
+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

_require_xfs_io_command pwrite -A

Quoted 14 lines
tests/generic/772 new file +14 -0
+		echo "Written to offset: $off" >> $tmp.aw
+		off=$((off + $size))
+	done
+}
+
+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")
+

tests/generic/772 line 59

Quoted 10 lines
tests/generic/772 new file +10 -0
+	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

_require_xfs_io_command falloc

tests/generic/772 line 71

Quoted 12 lines
tests/generic/772 new file +12 -0
+			;;
+		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=$(xxd -s 0 -l $awu_max -p $testfile.exp_old_mixed)

"xxd" it's not a necessary running dependence of xfstests, please replace it with common/rc:_hexdump() function or other similar commands in coreutils. Or you have to _notrun if there's not xxd installed.

Quoted 53 lines
tests/generic/772 new file +53 -0
+
+	$XFS_IO_PROG -fc "falloc 0 $awu_max" $testfile.exp_old_zeroes >> $seqres.full
+	expected_data_old_zeroes=$(xxd -s 0 -l $awu_max -p $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=$(xxd -s 0 -l $awu_max -p $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=$(xxd -s 0 -l $awu_max -p $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 data integrity 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)
+		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 "Expected old: "
+			echo "$expected_data_old"
+			echo
+			echo "Expected new: "
+			echo "$expected_data_new"
+			echo
+			echo "Actual contents: "
+			echo "$actual_data"
+
+			return 1
+		fi
+		echo -n "Check at offset $off suceeded! " >> $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

tests/generic/772 line 134

Quoted 10 lines
tests/generic/772 new file +10 -0
+
+	return 0
+}
+
+# test data integrity for file by shutting down in between atomic writes
+test_data_integrity() {
+	echo >> $seqres.full
+	echo "# Writing atomically to file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!

If there's background processes in test case, please make sure it's killed in _cleanup(), e.g.

_cleanup()
{
...

	[ -n "$awloop_pid" ] && kill $awloop_pid
	wait

...
}

tests/generic/772 line 141

Quoted 7 lines
tests/generic/772 new file +7 -0
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown

_require_scratch_shutdown

tests/generic/772 line 144

Quoted 3 lines
tests/generic/772 new file +3 -0
+
+	kill $awloop_pid
+	wait $awloop_pid

unset awloop_pid

tell _cleanup "it's killed".

Quoted 18 lines
tests/generic/772 new file +18 -0
+
+	last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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 happended
+	verify_start=$(( last_offset - (awu_max * 5)))
+	if [[ $verify_start < 0 ]]
+	then
+		verify_start=0
+	fi
+

tests/generic/772 line 172

Quoted 10 lines
tests/generic/772 new file +10 -0
+	verify_end=$(( last_offset + (awu_max * 5)))
+	if [[ "$verify_end" -gt "$filesize" ]]
+	then
+		verify_end=$filesize
+	fi
+}
+
+# test data integrity for file wiht written and unwritten mappings
+test_data_integrity_mixed() {
+	$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full

_require_xfs_io_command truncate

tests/generic/772 line 185

Quoted 13 lines
tests/generic/772 new file +13 -0
+
+	echo >> $seqres.full
+	echo "# Creating testfile with mixed mappings" >> $seqres.full
+	create_mixed_mappings $testfile $filesize
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

If the return value is useful, you'd better to "return 0" clearly. Or the return value of this function will be unclear.

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+# test data integrity for file with completely written mappings
+test_data_integrity_writ() {
+	$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

tests/generic/772 line 204

Quoted 10 lines
tests/generic/772 new file +10 -0
+	sync $testfile
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

Same as above

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+# test data integrity for file with completely unwritten mappings
+test_data_integrity_unwrit() {
+	$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

tests/generic/772 line 223

Quoted 10 lines
tests/generic/772 new file +10 -0
+	sync $testfile
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

Same

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+# test data integrity for file with no mappings
+test_data_integrity_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

tests/generic/772 line 242

Quoted 10 lines
tests/generic/772 new file +10 -0
+	sync $testfile
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

Same

tests/generic/772 line 251

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+test_filesize_integrity() {
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Performing extending atomic writes over file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!

Please use another name, then deal with it in _cleanup, refer to above awloop_pid.

Quoted 93 lines
tests/generic/772 new file +93 -0
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown
+
+	kill $awloop_pid
+	wait $awloop_pid
+
+	local last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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"
+
+	return $?
+}
+
+$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full
+
+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 data integrity test for atomic writes over mixed mapping" >> $seqres.full
+	test_data_integrity_mixed
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over fully written mapping" >> $seqres.full
+	test_data_integrity_writ
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over fully unwritten mapping" >> $seqres.full
+	test_data_integrity_unwrit
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting data integrity test for atomic writes over holes" >> $seqres.full
+	test_data_integrity_hole
+	if [[ "$?" == "1" ]]
+	then
+		status=1

tests/generic/772 line 354

Quoted 10 lines
tests/generic/772 new file +10 -0
+		break
+	fi
+
+	echo >> $seqres.full
+	echo "# Starting filesize integrity test for atomic writes" >> $seqres.full
+	test_filesize_integrity
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break

What are these "status=1" for? You "break" the loop run directly after setting status=1, then the status will be set to 0 at the end of the test.

You can output something to break the golden image (.out file). Or call _exit or _fail to end the test with an error output.

Thanks,
Zorro

Quoted 18 lines
tests/generic/772 new file +6 -0
+	fi
+done
+
+echo "Silence is golden"
+status=0
+exit
tests/generic/772.out new file +2 -0
new file mode 100644
index 00000000..98c13968
@@ -0,0 +1,2 @@
+QA output created by 772
+Silence is golden
Signature · 1 line
2.49.0
#44 lore raw

Re: [RFC 08/12] generic/772: Add sudden shutdown tests for multi block atomic writes

Quoted 39 lines · Zorro Lang

tests/generic/772 line 35

Quoted 8 lines · Ojaswin Mujoo
tests/generic/772 new file +8 -0
+
+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

_require_xfs_io_command pwrite -A

Quoted 14 lines
tests/generic/772 new file +14 -0
+		echo "Written to offset: $off" >> $tmp.aw
+		off=$((off + $size))
+	done
+}
+
+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")
+

tests/generic/772 line 59

Quoted 10 lines
tests/generic/772 new file +10 -0
+	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

_require_xfs_io_command falloc

Quoted 16 lines

tests/generic/772 line 71

Quoted 12 lines
tests/generic/772 new file +12 -0
+			;;
+		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=$(xxd -s 0 -l $awu_max -p $testfile.exp_old_mixed)

"xxd" it's not a necessary running dependence of xfstests, please replace it with common/rc:_hexdump() function or other similar commands in coreutils. Or you have to _notrun if there's not xxd installed.

Hey Zorro, got it I'll switch to using od which is what _hexdump also uses. Won't be using _hexdump helper though since the defaults print the address of byte as well, which is not something I want.

<snip>

Quoted 11 lines

tests/generic/772 line 134

Quoted 7 lines
tests/generic/772 new file +7 -0
+
+# test data integrity for file by shutting down in between atomic writes
+test_data_integrity() {
+	echo >> $seqres.full
+	echo "# Writing atomically to file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!

If there's background processes in test case, please make sure it's killed in _cleanup(), e.g.

Quoted 6 lines

_cleanup()
{
...

	[ -n "$awloop_pid" ] && kill $awloop_pid
	wait

...

Thanks for pointing this out, I'll take care of this.

Quoted 42 lines

}

tests/generic/772 line 141

Quoted 7 lines
tests/generic/772 new file +7 -0
+
+	# Wait for atleast first write to be recorded
+	while [ ! -f "$tmp.aw" ]; do sleep 0.2; done
+
+	echo >> $seqres.full
+	echo "# Shutting down filesystem while write is running" >> $seqres.full
+	_scratch_shutdown

_require_scratch_shutdown

tests/generic/772 line 144

Quoted 3 lines
tests/generic/772 new file +3 -0
+
+	kill $awloop_pid
+	wait $awloop_pid

unset awloop_pid

tell _cleanup "it's killed".

Quoted 22 lines
tests/generic/772 new file +22 -0
+
+	last_offset=$(tail -n 1 $tmp.aw | cut -d" " -f4)
+	cat $tmp.aw >> $seqres.full
+	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 happended
+	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
Quoted 8 lines

tests/generic/772 line 172

Quoted 6 lines
tests/generic/772 new file +6 -0
+	fi
+}
+
+# test data integrity for file wiht written and unwritten mappings
+test_data_integrity_mixed() {
+	$XFS_IO_PROG -fc "truncate 0" $testfile >> $seqres.full

_require_xfs_io_command truncate

Will do.

Quoted 97 lines
Quoted 4 lines
tests/generic/772 new file +3 -0
+
+	echo >> $seqres.full
+	echo "# Creating testfile with mixed mappings" >> $seqres.full

tests/generic/772 line 185

Quoted 10 lines
tests/generic/772 new file +10 -0
+	create_mixed_mappings $testfile $filesize
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mixed" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

If the return value is useful, you'd better to "return 0" clearly. Or the return value of this function will be unclear.

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+# test data integrity for file with completely written mappings
+test_data_integrity_writ() {
+	$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

tests/generic/772 line 204

Quoted 10 lines
tests/generic/772 new file +10 -0
+	sync $testfile
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_mapped" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

Same as above

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+# test data integrity for file with completely unwritten mappings
+test_data_integrity_unwrit() {
+	$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

tests/generic/772 line 223

Quoted 10 lines
tests/generic/772 new file +10 -0
+	sync $testfile
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

Same

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+# test data integrity for file with no mappings
+test_data_integrity_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

tests/generic/772 line 242

Quoted 10 lines
tests/generic/772 new file +10 -0
+	sync $testfile
+
+	test_data_integrity
+
+	verify_data_blocks $verify_start $verify_end "$expected_data_old_zeroes" "$expected_data_new"
+
+	if [[ "$?" == "1" ]]
+	then
+		return 1
+	fi

Same

tests/generic/772 line 251

Quoted 9 lines
tests/generic/772 new file +9 -0
+}
+
+test_filesize_integrity() {
+	$XFS_IO_PROG -c "truncate 0" $testfile >> $seqres.full
+
+	echo >> $seqres.full
+	echo "# Performing extending atomic writes over file in background" >> $seqres.full
+	atomic_write_loop &
+	awloop_pid=$!

Please use another name, then deal with it in _cleanup, refer to above awloop_pid.

<snip>

Quoted 12 lines

tests/generic/772 line 354

Quoted 8 lines
tests/generic/772 new file +8 -0
+
+	echo >> $seqres.full
+	echo "# Starting filesize integrity test for atomic writes" >> $seqres.full
+	test_filesize_integrity
+	if [[ "$?" == "1" ]]
+	then
+		status=1
+		break

What are these "status=1" for? You "break" the loop run directly after setting status=1, then the status will be set to 0 at the end of the test.

Quoted 5 lines

You can output something to break the golden image (.out file). Or call _exit or _fail to end the test with an error output.

Thanks,
Zorro

Ohh right, I don't even know why I didn't just _fail if the check in verify_data_blocks fails. That will save all this stupid return value handling logic I've added. Thanks so much for pointing this out, I'll fix it in next iteration!

Regards,
ojaswin

#45 lore raw

Re: [RFC 08/12] generic/772: Add sudden shutdown tests for multi block atomic writes

In reply to Ojaswin Mujoo
Quoted 2 lines · Ojaswin Mujoo

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

This looks like an interesting test. Can you please consider writing a fuller commit message describing how it works?

And do you verify that the test would fail for a non-atomic writes (before proving that it works for atomic writes)?

I've had a lot of difficulty testing atomic write behavior for system crash/unexpected shutdown, so it would be interesting to see how this works.

In the meantime, I'll try to understand the code...

Thanks,
John

#46 lore raw

Re: [RFC 08/12] generic/772: Add sudden shutdown tests for multi block atomic writes

Quoted 6 lines · John Garry
Quoted 2 lines · Ojaswin Mujoo

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

This looks like an interesting test. Can you please consider writing a fuller commit message describing how it works?

Hey John, sure I'll add a more comprehensive commit message. Basically what we do here is to create a mixed mapping and then do an atomic write on it while shutting the FS down in parallel.

Then we note down the last offset n where write happened before shutdown and check blocks from n - 5 to n + 5 to ensure they either have completely old data or completely new data but not a mix of both.

We repeat this for non mixed mappings like fully written, hole etc but the mixed one is the one most likely to get torn.

Quoted 3 lines

And do you verify that the test would fail for a non-atomic writes (before proving that it works for atomic writes)?

Yes we were reliably able to see torn data, every 20 iterations or so, when we tried this test on a non atomic block device (after modifying the test a bit eg not passing -A to pwrite).

Infact even with an atomic device, we were also able to detect an issue in ext4's early multiblock atomic write implementation where mishandling of the transaction was resulting in torn data.

Quoted 5 lines

I've had a lot of difficulty testing atomic write behavior for system crash/unexpected shutdown, so it would be interesting to see how this works.

In the meantime, I'll try to understand the code...

Sure, one thing to note is that we use FS shutdown and focus mostly on making sure the FS doesn't tear the write.

We might need something similar for block layer as well at some point.

Quoted 3 lines

Thanks,
John

Regards,
ojaswin

#47 lore raw

RFC 9/12 ext4/061: Atomic writes stress test for bigalloc using fio crc verifier

In reply to Ojaswin Mujoo

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

We brute force all possible blocksize & clustersize combinations on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run nproc * $LOAD_FACTOR threads in parallel writing to a single $SCRATCH_MNT/test-file. With atomic writes this test ensures that we never see the mix of data contents from different threads on a given bsrange.

---
 tests/ext4/061     | 107 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/061.out |   2 +
 2 files changed, 109 insertions(+)
 create mode 100755 tests/ext4/061
 create mode 100644 tests/ext4/061.out
tests/ext4/061 new file +107 -0
new file mode 100755
index 00000000..9d656613
@@ -0,0 +1,107 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 061
+#
+# Brute force all possible blocksize clustersize combination on a bigalloc
+# filesystem for stressing atomic write using fio data crc verifier. We run
+# nproc * 2 * $LOAD_FACTOR threads in parallel writing to a single
+# $SCRATCH_MNT/test-file. With fio aio-dio atomic write this test ensures that
+# we should never see the mix of data contents from different threads for any
+# given fio blocksize.
+#
+
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto rw stress atomicwrites
+
+_require_scratch_write_atomic
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
+	if (( $1 < $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+FS_MAX_CLUSTER_SIZE=$((128*1024))
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((100*1024*1024))
+fiobsize=4096
+
+# Calculate fsblocksize and FS_MAX_CLUSTER_SIZE 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)
+fsblocksize=$(max 4096 "$bdev_awu_min")
+FS_MAX_CLUSTER_SIZE=$(min "$FS_MAX_CLUSTER_SIZE" "$bdev_awu_max")
+
+function create_fio_config()
+{
+cat >$fio_config <<EOF
+	[aio-dio-aw-verify]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$fiobsize
+	fallocate=native
+	filename=$SCRATCH_MNT/test-file
+	size=$SIZE
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_dump=0
+	verify_backlog=1024
+	verify_async=4
+	verify_write_sequence=0
+	atomic=1
+EOF
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_config=$tmp.fio
+create_fio_config
+_require_fio $fio_config
+
+for ((fsblocksize=$fsblocksize; fsblocksize <= $(_get_page_size); fsblocksize = $fsblocksize << 1)); do
+	for ((fsclustersize=$fsblocksize; fsclustersize <= $FS_MAX_CLUSTER_SIZE; fsclustersize = $fsclustersize << 1)); do
+		for ((fiobsize = $fsblocksize; fiobsize <= $fsclustersize; fiobsize = $fiobsize << 1)); do
+			MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize"
+			_scratch_mkfs_ext4 "$MKFS_OPTIONS" >> $seqres.full 2>&1 || continue
+			if _try_scratch_mount >> $seqres.full 2>&1; then
+				touch $SCRATCH_MNT/f1
+				echo "== FIO test for fsblocksize=$fsblocksize fsclustersize=$fsclustersize fiobsize=$fiobsize ==" >> $seqres.full
+				fio_config=$tmp.fio
+				fio_out=$tmp.fio.out
+				create_fio_config
+				_require_fio $fio_config
+				cat $fio_config >> $seqres.full
+				$FIO_PROG $fio_config --output=$fio_out
+				ret=$?
+				cat $fio_out >> $seqres.full
+				_scratch_unmount
+				[[ $ret -eq 0 ]] || break;
+			fi
+		done
+	done
+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

Re: [RFC 09/12] ext4/061: Atomic writes stress test for bigalloc using fio crc verifier

Quoted 52 lines · Ojaswin Mujoo

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

We brute force all possible blocksize & clustersize combinations on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run nproc * $LOAD_FACTOR threads in parallel writing to a single $SCRATCH_MNT/test-file. With atomic writes this test ensures that we never see the mix of data contents from different threads on a given bsrange.

---
 tests/ext4/061     | 107 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/061.out |   2 +
 2 files changed, 109 insertions(+)
 create mode 100755 tests/ext4/061
 create mode 100644 tests/ext4/061.out
tests/ext4/061 new file +28 -0
new file mode 100755
index 00000000..9d656613
@@ -0,0 +1,107 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 061
+#
+# Brute force all possible blocksize clustersize combination on a bigalloc
+# filesystem for stressing atomic write using fio data crc verifier. We run
+# nproc * 2 * $LOAD_FACTOR threads in parallel writing to a single
+# $SCRATCH_MNT/test-file. With fio aio-dio atomic write this test ensures that
+# we should never see the mix of data contents from different threads for any
+# given fio blocksize.
+#
+
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto rw stress atomicwrites
+
+_require_scratch_write_atomic
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi

tests/ext4/061 line 38

Quoted 10 lines
tests/ext4/061 new file +10 -0
+}
+
+function min()
+{
+	if (( $1 < $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}

I've seen these two functions many times, please make them to be common helpers.

Quoted 8 lines
tests/ext4/061 new file +8 -0
+
+FS_MAX_CLUSTER_SIZE=$((128*1024))
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((100*1024*1024))
+fiobsize=4096
+
+# Calculate fsblocksize and FS_MAX_CLUSTER_SIZE as per bdev atomic write units.
+bdev_awu_min=$(_get_atomic_write_unit_min $SCRATCH_DEV)

tests/ext4/061 line 56

Quoted 10 lines
tests/ext4/061 new file +10 -0
+bdev_awu_max=$(_get_atomic_write_unit_max $SCRATCH_DEV)
+fsblocksize=$(max 4096 "$bdev_awu_min")
+FS_MAX_CLUSTER_SIZE=$(min "$FS_MAX_CLUSTER_SIZE" "$bdev_awu_max")
+
+function create_fio_config()
+{
+cat >$fio_config <<EOF
+	[aio-dio-aw-verify]
+	direct=1
+	ioengine=libaio

_require_aiodio

Quoted 17 lines
tests/ext4/061 new file +17 -0
+	rw=randwrite
+	bs=$fiobsize
+	fallocate=native
+	filename=$SCRATCH_MNT/test-file
+	size=$SIZE
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_dump=0
+	verify_backlog=1024
+	verify_async=4
+	verify_write_sequence=0
+	atomic=1
+EOF

tests/ext4/061 line 83

Quoted 10 lines
tests/ext4/061 new file +10 -0
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_config=$tmp.fio
+create_fio_config
+_require_fio $fio_config
+
+for ((fsblocksize=$fsblocksize; fsblocksize <= $(_get_page_size); fsblocksize = $fsblocksize << 1)); do
+	for ((fsclustersize=$fsblocksize; fsclustersize <= $FS_MAX_CLUSTER_SIZE; fsclustersize = $fsclustersize << 1)); do
+		for ((fiobsize = $fsblocksize; fiobsize <= $fsclustersize; fiobsize = $fiobsize << 1)); do

Wow, 3 for loops...

tests/ext4/061 line 85

Quoted 2 lines
tests/ext4/061 new file +2 -0
+			MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize"
+			_scratch_mkfs_ext4 "$MKFS_OPTIONS" >> $seqres.full 2>&1 || continue

MKFS_OPTIONS is used in _scratch_mkfs_ext4 by default, you don't need to use it as an argument.

Or do you want to do:

_scratch_mkfs_ext4 "-O bigalloc -b $fsblocksize -C $fsclustersize" ?

tests/ext4/061 line 89

Quoted 4 lines
tests/ext4/061 new file +4 -0
+			if _try_scratch_mount >> $seqres.full 2>&1; then
+				touch $SCRATCH_MNT/f1
+				echo "== FIO test for fsblocksize=$fsblocksize fsclustersize=$fsclustersize fiobsize=$fiobsize ==" >> $seqres.full
+				fio_config=$tmp.fio

Do you change the "$tmp"? If not, you don't need to set fio_config=$tmp.fio everytime, you've set fio_config above this for loop.

tests/ext4/061 line 90

Quoted 1 line
tests/ext4/061 new file +1 -0
+				fio_out=$tmp.fio.out

If you don't change "$tmp", you can set fio_out once, before the for loop running.

tests/ext4/061 line 92

Quoted 2 lines
tests/ext4/061 new file +2 -0
+				create_fio_config
+				_require_fio $fio_config

I think only $fiobsize will be changed in $fio_config at here, so you're trying to check "bs=$fiobsize"? If so, that doesn't make sense, due to _require_fio accepts any "bs" number, except bs <= 0. So you don't need to call _require_fio at here everytime, especially you've called it before the loop running.

Thanks,
Zorro

Quoted 27 lines
tests/ext4/061 new file +15 -0
+				cat $fio_config >> $seqres.full
+				$FIO_PROG $fio_config --output=$fio_out
+				ret=$?
+				cat $fio_out >> $seqres.full
+				_scratch_unmount
+				[[ $ret -eq 0 ]] || break;
+			fi
+		done
+	done
+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
#49 lore raw

Re: [RFC 09/12] ext4/061: Atomic writes stress test for bigalloc using fio crc verifier

Quoted 19 lines · Zorro Lang
Quoted 18 lines · Ojaswin Mujoo

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

We brute force all possible blocksize & clustersize combinations on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run nproc * $LOAD_FACTOR threads in parallel writing to a single $SCRATCH_MNT/test-file. With atomic writes this test ensures that we never see the mix of data contents from different threads on a given bsrange.

---
 tests/ext4/061     | 107 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/061.out |   2 +
 2 files changed, 109 insertions(+)
 create mode 100755 tests/ext4/061
 create mode 100644 tests/ext4/061.out

<snip>

Quoted 11 lines
Quoted 11 lines
tests/ext4/061 new file +11 -0
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
Quoted 8 lines

tests/ext4/061 line 38

Quoted 6 lines
tests/ext4/061 new file +6 -0
+	if (( $1 < $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}

I've seen these two functions many times, please make them to be common helpers.

Yes I will do this in v2.

Quoted 13 lines
Quoted 13 lines
tests/ext4/061 new file +12 -0
+
+FS_MAX_CLUSTER_SIZE=$((128*1024))
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((100*1024*1024))
+fiobsize=4096
+
+# Calculate fsblocksize and FS_MAX_CLUSTER_SIZE 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)
+fsblocksize=$(max 4096 "$bdev_awu_min")
+FS_MAX_CLUSTER_SIZE=$(min "$FS_MAX_CLUSTER_SIZE" "$bdev_awu_max")
+
Quoted 8 lines

tests/ext4/061 line 56

Quoted 6 lines
tests/ext4/061 new file +6 -0
+function create_fio_config()
+{
+cat >$fio_config <<EOF
+	[aio-dio-aw-verify]
+	direct=1
+	ioengine=libaio

_require_aiodio

Got it, thanks

Quoted 22 lines
Quoted 22 lines
tests/ext4/061 new file +21 -0
+	rw=randwrite
+	bs=$fiobsize
+	fallocate=native
+	filename=$SCRATCH_MNT/test-file
+	size=$SIZE
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_dump=0
+	verify_backlog=1024
+	verify_async=4
+	verify_write_sequence=0
+	atomic=1
+EOF
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_config=$tmp.fio
Quoted 8 lines

tests/ext4/061 line 83

Quoted 6 lines
tests/ext4/061 new file +6 -0
+create_fio_config
+_require_fio $fio_config
+
+for ((fsblocksize=$fsblocksize; fsblocksize <= $(_get_page_size); fsblocksize = $fsblocksize << 1)); do
+	for ((fsclustersize=$fsblocksize; fsclustersize <= $FS_MAX_CLUSTER_SIZE; fsclustersize = $fsclustersize << 1)); do
+		for ((fiobsize = $fsblocksize; fiobsize <= $fsclustersize; fiobsize = $fiobsize << 1)); do

Wow, 3 for loops...

Yes :) We want to test all the combinations. Since the IO is less, it usually finishes within a reasonable time (~5 mins)

Quoted 10 lines

tests/ext4/061 line 85

Quoted 3 lines
tests/ext4/061 new file +2 -0
+			MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize"
+			_scratch_mkfs_ext4 "$MKFS_OPTIONS" >> $seqres.full 2>&1 || continue

MKFS_OPTIONS is used in _scratch_mkfs_ext4 by default, you don't need to use it as an argument.

Or do you want to do:

_scratch_mkfs_ext4 "-O bigalloc -b $fsblocksize -C $fsclustersize" ?

Yes we want to explicitly run these options so various block and cluster sizes get tested irrespective of what the user passes in MKFS_OPTIONS.

However, looking at _scratch_mkfs_ext4() > _scratch_do_mkfs() we seem to try mkfs.ext4 $MKFS_OPTIONS $extra_mkfs_options first and if that fails we anyways try mkfs $extra_mkfs_options.

That being said, I'm a bit unsure whether something like:

mkfs.ext4 $MKFS_OPTIONS -O bigalloc -b $fsblocksize -C $fsclustersize

would produce reliable results always so:

MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize" _scratch_mkfs_ext4

seems better approach. Thoughts?

Quoted 14 lines

tests/ext4/061 line 89

Quoted 5 lines
tests/ext4/061 new file +4 -0
+			if _try_scratch_mount >> $seqres.full 2>&1; then
+				touch $SCRATCH_MNT/f1
+				echo "== FIO test for fsblocksize=$fsblocksize fsclustersize=$fsclustersize fiobsize=$fiobsize ==" >> $seqres.full
+				fio_config=$tmp.fio

Do you change the "$tmp"? If not, you don't need to set fio_config=$tmp.fio everytime, you've set fio_config above this for loop.

tests/ext4/061 line 90

Quoted 1 line
tests/ext4/061 new file +1 -0
+				fio_out=$tmp.fio.out

If you don't change "$tmp", you can set fio_out once, before the for loop running.

Quoted 8 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+				create_fio_config
+				_require_fio $fio_config

I think only $fiobsize will be changed in $fio_config at here, so you're trying to check "bs=$fiobsize"? If so, that doesn't make sense, due to _require_fio accepts any "bs" number, except bs <= 0. So you don't need to call _require_fio at here everytime, especially you've called it before the loop running.

Makes sense, I'll clean this up.

Thanks,
ojaswin

Quoted 31 lines

Thanks,
Zorro

Quoted 27 lines
tests/ext4/061 new file +15 -0
+				cat $fio_config >> $seqres.full
+				$FIO_PROG $fio_config --output=$fio_out
+				ret=$?
+				cat $fio_out >> $seqres.full
+				_scratch_unmount
+				[[ $ret -eq 0 ]] || break;
+			fi
+		done
+	done
+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
#50 lore raw

Re: [RFC 09/12] ext4/061: Atomic writes stress test for bigalloc using fio crc verifier

Quoted 122 lines · Ojaswin Mujoo
Quoted 19 lines · Zorro Lang
Quoted 18 lines · Ojaswin Mujoo

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

We brute force all possible blocksize & clustersize combinations on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run nproc * $LOAD_FACTOR threads in parallel writing to a single $SCRATCH_MNT/test-file. With atomic writes this test ensures that we never see the mix of data contents from different threads on a given bsrange.

---
 tests/ext4/061     | 107 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/061.out |   2 +
 2 files changed, 109 insertions(+)
 create mode 100755 tests/ext4/061
 create mode 100644 tests/ext4/061.out

<snip>

Quoted 11 lines
Quoted 11 lines
tests/ext4/061 new file +11 -0
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
Quoted 8 lines

tests/ext4/061 line 38

Quoted 6 lines
tests/ext4/061 new file +6 -0
+	if (( $1 < $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}

I've seen these two functions many times, please make them to be common helpers.

Yes I will do this in v2.

Quoted 13 lines
Quoted 13 lines
tests/ext4/061 new file +12 -0
+
+FS_MAX_CLUSTER_SIZE=$((128*1024))
+FIO_LOAD=$(($(nproc) * 2 * LOAD_FACTOR))
+SIZE=$((100*1024*1024))
+fiobsize=4096
+
+# Calculate fsblocksize and FS_MAX_CLUSTER_SIZE 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)
+fsblocksize=$(max 4096 "$bdev_awu_min")
+FS_MAX_CLUSTER_SIZE=$(min "$FS_MAX_CLUSTER_SIZE" "$bdev_awu_max")
+
Quoted 8 lines

tests/ext4/061 line 56

Quoted 6 lines
tests/ext4/061 new file +6 -0
+function create_fio_config()
+{
+cat >$fio_config <<EOF
+	[aio-dio-aw-verify]
+	direct=1
+	ioengine=libaio

_require_aiodio

Got it, thanks

Quoted 22 lines
Quoted 22 lines
tests/ext4/061 new file +21 -0
+	rw=randwrite
+	bs=$fiobsize
+	fallocate=native
+	filename=$SCRATCH_MNT/test-file
+	size=$SIZE
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_dump=0
+	verify_backlog=1024
+	verify_async=4
+	verify_write_sequence=0
+	atomic=1
+EOF
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_config=$tmp.fio
Quoted 8 lines

tests/ext4/061 line 83

Quoted 6 lines
tests/ext4/061 new file +6 -0
+create_fio_config
+_require_fio $fio_config
+
+for ((fsblocksize=$fsblocksize; fsblocksize <= $(_get_page_size); fsblocksize = $fsblocksize << 1)); do
+	for ((fsclustersize=$fsblocksize; fsclustersize <= $FS_MAX_CLUSTER_SIZE; fsclustersize = $fsclustersize << 1)); do
+		for ((fiobsize = $fsblocksize; fiobsize <= $fsclustersize; fiobsize = $fiobsize << 1)); do

Wow, 3 for loops...

Yes :) We want to test all the combinations. Since the IO is less, it usually finishes within a reasonable time (~5 mins)

Quoted 10 lines

tests/ext4/061 line 85

Quoted 3 lines
tests/ext4/061 new file +2 -0
+			MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize"
+			_scratch_mkfs_ext4 "$MKFS_OPTIONS" >> $seqres.full 2>&1 || continue

MKFS_OPTIONS is used in _scratch_mkfs_ext4 by default, you don't need to use it as an argument.

Or do you want to do:

_scratch_mkfs_ext4 "-O bigalloc -b $fsblocksize -C $fsclustersize" ?

Yes we want to explicitly run these options so various block and cluster sizes get tested irrespective of what the user passes in MKFS_OPTIONS.

However, looking at _scratch_mkfs_ext4() > _scratch_do_mkfs() we seem to try mkfs.ext4 $MKFS_OPTIONS $extra_mkfs_options first and if that fails we anyways try mkfs $extra_mkfs_options.

That being said, I'm a bit unsure whether something like:

Quoted 8 lines

mkfs.ext4 $MKFS_OPTIONS -O bigalloc -b $fsblocksize -C $fsclustersize

would produce reliable results always so:

MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize" _scratch_mkfs_ext4

seems better approach. Thoughts?

Sure, this's good to me:)

Thanks,
Zorro

Quoted 61 lines
Quoted 15 lines

tests/ext4/061 line 89

Quoted 6 lines
tests/ext4/061 new file +4 -0
+			if _try_scratch_mount >> $seqres.full 2>&1; then
+				touch $SCRATCH_MNT/f1
+				echo "== FIO test for fsblocksize=$fsblocksize fsclustersize=$fsclustersize fiobsize=$fiobsize ==" >> $seqres.full
+				fio_config=$tmp.fio

Do you change the "$tmp"? If not, you don't need to set fio_config=$tmp.fio everytime, you've set fio_config above this for loop.

tests/ext4/061 line 90

Quoted 1 line
tests/ext4/061 new file +1 -0
+				fio_out=$tmp.fio.out

If you don't change "$tmp", you can set fio_out once, before the for loop running.

Quoted 8 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+				create_fio_config
+				_require_fio $fio_config

I think only $fiobsize will be changed in $fio_config at here, so you're trying to check "bs=$fiobsize"? If so, that doesn't make sense, due to _require_fio accepts any "bs" number, except bs <= 0. So you don't need to call _require_fio at here everytime, especially you've called it before the loop running.

Makes sense, I'll clean this up.

Thanks,
ojaswin

Quoted 31 lines

Thanks,
Zorro

Quoted 27 lines
tests/ext4/061 new file +15 -0
+				cat $fio_config >> $seqres.full
+				$FIO_PROG $fio_config --output=$fio_out
+				ret=$?
+				cat $fio_out >> $seqres.full
+				_scratch_unmount
+				[[ $ret -eq 0 ]] || break;
+			fi
+		done
+	done
+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
#51 lore raw

RFC 10/12 ext4/062: Atomic writes test for bigalloc using fio crc verifier on multiple files

In reply to Ojaswin Mujoo

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

Brute force all possible blocksize clustersize combination on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run multiple threads in parallel with each job writing to its own file. The parallel jobs running on a constrained filesystem size ensure that we stress the ext4 allocator to allocate contiguous extents.

---
 tests/ext4/062     | 131 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/062.out |   2 +
 2 files changed, 133 insertions(+)
 create mode 100755 tests/ext4/062
 create mode 100644 tests/ext4/062.out
tests/ext4/062 new file +131 -0
new file mode 100755
index 00000000..50803b97
@@ -0,0 +1,131 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 061
+#
+# Brute force all possible blocksize clustersize combination on a bigalloc
+# filesystem for stressing atomic write using fio data crc verifier. We run
+# nproc * $LOAD_FACTOR threads in parallel writing to a single
+# $SCRATCH_MNT/test-file. We also create 8 such parallel jobs to run on
+# a constrained filesystem size to stress the ext4 allocator to allocate
+# contiguous extents.
+#
+
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto rw stress atomicwrites
+
+_require_scratch_write_atomic
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
+	if (( $1 < $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+FSSIZE=$((360*1024*1024))
+FS_MAX_CLUSTER_SIZE=$((128*1024))
+FIO_LOAD=$(($(nproc) * LOAD_FACTOR))
+fiobsize=4096
+
+# Calculate fsblocksize and FS_MAX_CLUSTER_SIZE 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)
+fsblocksize=$(max 4096 "$bdev_awu_min")
+FS_MAX_CLUSTER_SIZE=$(min "$FS_MAX_CLUSTER_SIZE" "$bdev_awu_max")
+
+function create_fio_config()
+{
+cat >$fio_config <<EOF
+	[global]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$fiobsize
+	fallocate=truncate
+	size=$((FSSIZE / 12))
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_dump=0
+	verify_backlog=1024
+	verify_async=4
+	verify_write_sequence=0
+	atomic=1
+
+	[job1]
+	filename=$SCRATCH_MNT/testfile-job1
+
+	[job2]
+	filename=$SCRATCH_MNT/testfile-job2
+
+	[job3]
+	filename=$SCRATCH_MNT/testfile-job3
+
+	[job4]
+	filename=$SCRATCH_MNT/testfile-job4
+
+	[job5]
+	filename=$SCRATCH_MNT/testfile-job5
+
+	[job6]
+	filename=$SCRATCH_MNT/testfile-job6
+
+	[job7]
+	filename=$SCRATCH_MNT/testfile-job7
+
+	[job8]
+	filename=$SCRATCH_MNT/testfile-job8
+
+EOF
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_config=$tmp.fio
+create_fio_config
+_require_fio $fio_config
+
+for ((fsblocksize=$fsblocksize; fsblocksize <= $(_get_page_size); fsblocksize = $fsblocksize << 1)); do
+	for ((fsclustersize=$fsblocksize; fsclustersize <= $FS_MAX_CLUSTER_SIZE; fsclustersize = $fsclustersize << 1)); do
+		for ((fiobsize = $fsblocksize; fiobsize <= $fsclustersize; fiobsize = $fiobsize << 1)); do
+			MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize"
+			_scratch_mkfs_sized "$FSSIZE" >> $seqres.full 2>&1 || continue
+			if _try_scratch_mount >> $seqres.full 2>&1; then
+				touch $SCRATCH_MNT/f1
+				echo "== FIO test for fsblocksize=$fsblocksize fsclustersize=$fsclustersize fiobsize=$fiobsize ==" >> $seqres.full
+				fio_config=$tmp.fio
+				fio_out=$tmp.fio.out
+				create_fio_config
+				_require_fio $fio_config
+				cat $fio_config >> $seqres.full
+				$FIO_PROG $fio_config --output=$fio_out
+				ret=$?
+				cat $fio_out >> $seqres.full
+				_scratch_unmount
+				[[ $ret -eq 0 ]] || break;
+			fi
+		done
+	done
+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
#52 lore raw

Re: [RFC 10/12] ext4/062: Atomic writes test for bigalloc using fio crc verifier on multiple files

Quoted 10 lines · Ojaswin Mujoo

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

Brute force all possible blocksize clustersize combination on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run multiple threads in parallel with each job writing to its own file. The parallel jobs running on a constrained filesystem size ensure that we stress the ext4 allocator to allocate contiguous extents.

RWF_ATOMIC does not guarantee that racing atomic writes and reads are serialised. That is what you are testing here, right?

NVMe and SCSI do guarantee this (serialisation). However, reads in the block layer may be split into multiple requests, even though unlikely.

#53 lore raw

Re: [RFC 10/12] ext4/062: Atomic writes test for bigalloc using fio crc verifier on multiple files

Quoted 9 lines · John Garry
Quoted 8 lines · Ojaswin Mujoo

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

Brute force all possible blocksize clustersize combination on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run multiple threads in parallel with each job writing to its own file. The parallel jobs running on a constrained filesystem size ensure that we stress the ext4 allocator to allocate contiguous extents.

Quoted 8 lines
Quoted 2 lines

RWF_ATOMIC does not guarantee that racing atomic writes and reads are serialised. That is what you are testing here, right?

NVMe and SCSI do guarantee this (serialisation). However, reads in the block layer may be split into multiple requests, even though unlikely.

Hey John,

We are not really testing the serialization here (verify_write_sequence=0) but rather that multiple threads atomically writing to the same file should never tear the write.

In the test, for each job, multiple threads are doing the write on the same file with the same iosize so they should always overwrite each other completely. The verifier then ensures that the whole iosize chunk written matches the checksum, which will only happen if the write is not torn. That way we are able to ensure that even with multiple threads writing the same ranges, we don't break the writes (the sequence doesn't matter as long as it is not breaking)

#54 lore raw

Re: [RFC 10/12] ext4/062: Atomic writes test for bigalloc using fio crc verifier on multiple files

Quoted 25 lines · Ojaswin Mujoo
Quoted 9 lines · John Garry
Quoted 8 lines · Ojaswin Mujoo

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

Brute force all possible blocksize clustersize combination on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run multiple threads in parallel with each job writing to its own file. The parallel jobs running on a constrained filesystem size ensure that we stress the ext4 allocator to allocate contiguous extents.

Quoted 8 lines
Quoted 2 lines

RWF_ATOMIC does not guarantee that racing atomic writes and reads are serialised. That is what you are testing here, right?

NVMe and SCSI do guarantee this (serialisation). However, reads in the block layer may be split into multiple requests, even though unlikely.

Hey John,

We are not really testing the serialization here (verify_write_sequence=0) but rather that multiple threads atomically writing to the same file should never tear the write.

Quoted 7 lines

In the test, for each job, multiple threads are doing the write on the same file with the same iosize so they should always overwrite each other completely. The verifier then ensures that the whole iosize chunk written matches the checksum, which will only happen if the write is not torn. That way we are able to ensure that even with multiple threads writing the same ranges, we don't break the writes (the sequence doesn't matter as long as it is not breaking)

So the threads are overwriting the same data range, right?

If so, as an experiment, try setting /sys/block/DEV/queue/max_sectors_kb lower than the bsize and see what happens...

Thanks,
John

#55 lore raw

Re: [RFC 10/12] ext4/062: Atomic writes test for bigalloc using fio crc verifier on multiple files

Quoted 34 lines · John Garry
Quoted 25 lines · Ojaswin Mujoo
Quoted 9 lines · John Garry
Quoted 8 lines · Ojaswin Mujoo

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

Brute force all possible blocksize clustersize combination on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run multiple threads in parallel with each job writing to its own file. The parallel jobs running on a constrained filesystem size ensure that we stress the ext4 allocator to allocate contiguous extents.

Quoted 8 lines
Quoted 2 lines

RWF_ATOMIC does not guarantee that racing atomic writes and reads are serialised. That is what you are testing here, right?

NVMe and SCSI do guarantee this (serialisation). However, reads in the block layer may be split into multiple requests, even though unlikely.

Hey John,

We are not really testing the serialization here (verify_write_sequence=0) but rather that multiple threads atomically writing to the same file should never tear the write.

Quoted 7 lines

In the test, for each job, multiple threads are doing the write on the same file with the same iosize so they should always overwrite each other completely. The verifier then ensures that the whole iosize chunk written matches the checksum, which will only happen if the write is not torn. That way we are able to ensure that even with multiple threads writing the same ranges, we don't break the writes (the sequence doesn't matter as long as it is not breaking)

Quoted 4 lines

So the threads are overwriting the same data range, right?

If so, as an experiment, try setting /sys/block/DEV/queue/max_sectors_kb lower than the bsize and see what happens...

Okay so I tried this flow:

1. mount ext4 FS
2. max_sectors_kb = 4
3. Do a fio atomic write of 64k (awu_max = 64k in this case)

And I'm able to see checksum issues that means the write is getting torn. I'm not sure of the exact block layer code around max_sectors_kb, but I do see max_sectors_kb -- sets --> max_user_sectors --> max_sectors But then get_max_io_size() ignores max_sectors for atomic writes and uses atomic_write_max_sectors, which should be correctly set.

Hmm.. I must be missing something, what is splitting the bio?

Regards,
ojaswin

Quoted 3 lines

Thanks,
John

#56 lore raw

Re: [RFC 10/12] ext4/062: Atomic writes test for bigalloc using fio crc verifier on multiple files

In reply to Ojaswin Mujoo
Quoted 11 lines · Ojaswin Mujoo

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

Brute force all possible blocksize clustersize combination on a bigalloc filesystem for stressing atomic write using fio data crc verifier. We run multiple threads in parallel with each job writing to its own file. The parallel jobs running on a constrained filesystem size ensure that we stress the ext4 allocator to allocate contiguous extents.

---

Similar review points as patch 9/12.

Quoted 155 lines
 tests/ext4/062     | 131 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/062.out |   2 +
 2 files changed, 133 insertions(+)
 create mode 100755 tests/ext4/062
 create mode 100644 tests/ext4/062.out
tests/ext4/062 new file +131 -0
new file mode 100755
index 00000000..50803b97
@@ -0,0 +1,131 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 061
+#
+# Brute force all possible blocksize clustersize combination on a bigalloc
+# filesystem for stressing atomic write using fio data crc verifier. We run
+# nproc * $LOAD_FACTOR threads in parallel writing to a single
+# $SCRATCH_MNT/test-file. We also create 8 such parallel jobs to run on
+# a constrained filesystem size to stress the ext4 allocator to allocate
+# contiguous extents.
+#
+
+. ./common/preamble
+. ./common/atomicwrites
+
+_begin_fstest auto rw stress atomicwrites
+
+_require_scratch_write_atomic
+
+function max()
+{
+	if (( $1 > $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+function min()
+{
+	if (( $1 < $2 )); then
+		echo "$1"
+	else
+		echo "$2"
+	fi
+}
+
+FSSIZE=$((360*1024*1024))
+FS_MAX_CLUSTER_SIZE=$((128*1024))
+FIO_LOAD=$(($(nproc) * LOAD_FACTOR))
+fiobsize=4096
+
+# Calculate fsblocksize and FS_MAX_CLUSTER_SIZE 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)
+fsblocksize=$(max 4096 "$bdev_awu_min")
+FS_MAX_CLUSTER_SIZE=$(min "$FS_MAX_CLUSTER_SIZE" "$bdev_awu_max")
+
+function create_fio_config()
+{
+cat >$fio_config <<EOF
+	[global]
+	direct=1
+	ioengine=libaio
+	rw=randwrite
+	bs=$fiobsize
+	fallocate=truncate
+	size=$((FSSIZE / 12))
+	iodepth=$FIO_LOAD
+	numjobs=$FIO_LOAD
+	group_reporting=1
+	verify_state_save=0
+	verify=crc32c
+	verify_fatal=1
+	verify_dump=0
+	verify_backlog=1024
+	verify_async=4
+	verify_write_sequence=0
+	atomic=1
+
+	[job1]
+	filename=$SCRATCH_MNT/testfile-job1
+
+	[job2]
+	filename=$SCRATCH_MNT/testfile-job2
+
+	[job3]
+	filename=$SCRATCH_MNT/testfile-job3
+
+	[job4]
+	filename=$SCRATCH_MNT/testfile-job4
+
+	[job5]
+	filename=$SCRATCH_MNT/testfile-job5
+
+	[job6]
+	filename=$SCRATCH_MNT/testfile-job6
+
+	[job7]
+	filename=$SCRATCH_MNT/testfile-job7
+
+	[job8]
+	filename=$SCRATCH_MNT/testfile-job8
+
+EOF
+}
+
+# Let's create a sample fio config to check whether fio supports all options.
+fio_config=$tmp.fio
+create_fio_config
+_require_fio $fio_config
+
+for ((fsblocksize=$fsblocksize; fsblocksize <= $(_get_page_size); fsblocksize = $fsblocksize << 1)); do
+	for ((fsclustersize=$fsblocksize; fsclustersize <= $FS_MAX_CLUSTER_SIZE; fsclustersize = $fsclustersize << 1)); do
+		for ((fiobsize = $fsblocksize; fiobsize <= $fsclustersize; fiobsize = $fiobsize << 1)); do
+			MKFS_OPTIONS="-O bigalloc -b $fsblocksize -C $fsclustersize"
+			_scratch_mkfs_sized "$FSSIZE" >> $seqres.full 2>&1 || continue
+			if _try_scratch_mount >> $seqres.full 2>&1; then
+				touch $SCRATCH_MNT/f1
+				echo "== FIO test for fsblocksize=$fsblocksize fsclustersize=$fsclustersize fiobsize=$fiobsize ==" >> $seqres.full
+				fio_config=$tmp.fio
+				fio_out=$tmp.fio.out
+				create_fio_config
+				_require_fio $fio_config
+				cat $fio_config >> $seqres.full
+				$FIO_PROG $fio_config --output=$fio_out
+				ret=$?
+				cat $fio_out >> $seqres.full
+				_scratch_unmount
+				[[ $ret -eq 0 ]] || break;
+			fi
+		done
+	done
+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
#57 lore raw

RFC 11/12 ext4/063: 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     | 125 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/063.out |   2 +
 2 files changed, 127 insertions(+)
 create mode 100755 tests/ext4/063
 create mode 100644 tests/ext4/063.out
tests/ext4/063 new file +125 -0
new file mode 100755
index 00000000..b4759990
@@ -0,0 +1,125 @@
+#! /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 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
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest auto atomicwrites
+
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+
+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 which bs len extents at alternate offsets. For example,
+	# for 4k bs the tree should look as follows
+	#
+	#                  +---------+---------+
+	#                  | index 1 | index 2 |
+	#                  +-----+---+-----+---+
+	#               +--------+         +-------+
+	#               |                          |
+	#    +----------+--------------+     +-----+-----+
+	#    | ex 1 | ex 2 |... | ex n |     |  ex n + 1 |
+	#    +-------------------------+     +-----------+
+	#    0      2            680          682
+	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 dont merge extents across leaf we will end up with a tree as:
+	#
+	#                  +---------+---------+
+	#                  | index 1 | index 2 |
+	#                  +-----+---+-----+---+
+	#               +--------+         +-------+
+	#               |                          |
+	#    +----------+--------------+     +-----+-----+
+	#    | ex 1 | ex 2 |... | ex n |     | ex merged |
+	#    +-------------------------+     +-----------+
+	#    0      2            680          681  682  684
+	#
+	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=$(xxd -s 0 -l $awu_max -p $testfile.exp)
+
+# We ensure that the data after atomic writes should match the expected data
+actual_data=$(xxd -s $torn_aw_offset -l $awu_max -p $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"
+
+	status=1
+	exit
+fi
+
+echo -n "Data verification at offset $torn_aw_offset suceeded!" >> $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
#58 lore raw

Re: [RFC 11/12] ext4/063: Atomic write test for extent split across leaf nodes

Quoted 58 lines · 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     | 125 +++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/063.out |   2 +
 2 files changed, 127 insertions(+)
 create mode 100755 tests/ext4/063
 create mode 100644 tests/ext4/063.out
tests/ext4/063 new file +36 -0
new file mode 100755
index 00000000..b4759990
@@ -0,0 +1,125 @@
+#! /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 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
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest auto atomicwrites
+
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+
+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 which bs len extents at alternate offsets. For example,
+	# for 4k bs the tree should look as follows
+	#
+	#                  +---------+---------+
+	#                  | index 1 | index 2 |
+	#                  +-----+---+-----+---+
+	#               +--------+         +-------+
+	#               |                          |
+	#    +----------+--------------+     +-----+-----+
+	#    | ex 1 | ex 2 |... | ex n |     |  ex n + 1 |
+	#    +-------------------------+     +-----------+
+	#    0      2            680          682

tests/ext4/063 line 46

Quoted 10 lines
tests/ext4/063 new file +10 -0
+	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

_require_command "$DEBUGFS_PROG" debugfs

Quoted 35 lines
tests/ext4/063 new file +35 -0
+
+	# 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 dont merge extents across leaf we will end up with a tree as:
+	#
+	#                  +---------+---------+
+	#                  | index 1 | index 2 |
+	#                  +-----+---+-----+---+
+	#               +--------+         +-------+
+	#               |                          |
+	#    +----------+--------------+     +-----+-----+
+	#    | ex 1 | ex 2 |... | ex n |     | ex merged |
+	#    +-------------------------+     +-----------+
+	#    0      2            680          681  682  684
+	#
+	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)

tests/ext4/063 line 91

Quoted 10 lines
tests/ext4/063 new file +10 -0
+
+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

_require_xfs_io_command pwrite -A

tests/ext4/063 line 104

Quoted 13 lines
tests/ext4/063 new file +13 -0
+$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=$(xxd -s 0 -l $awu_max -p $testfile.exp)

xxd isn't a necessary running dependence of xfstests, please use common/rc:_hexdump() or other commands in coreutils.

Quoted 7 lines
tests/ext4/063 new file +7 -0
+
+# We ensure that the data after atomic writes should match the expected data
+actual_data=$(xxd -s $torn_aw_offset -l $awu_max -p $testfile)
+if [[ "$actual_data" != "$expected_data" ]]
+then
+	echo "Checksum match failed at off: $torn_aw_offset size: $awu_max"
+	echo

tests/ext4/063 line 119

Quoted 10 lines
tests/ext4/063 new file +8 -0
+	echo "Expected: "
+	echo "$expected_data"
+	echo
+	echo "Actual contents: "
+	echo "$actual_data"
+
 
 
+	status=1
+	exit

_exit 1

Quoted 18 lines
tests/ext4/063 new file +6 -0
+fi
+
+echo -n "Data verification at offset $torn_aw_offset suceeded!" >> $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
#59 lore raw

RFC 12/12 ext4/064: Add atomic write tests for journal credit calculation

In reply to Ojaswin Mujoo

Test atomic writes with journal credit calculation. We take 2 cases here:

1. Atomic writes on single mapping causing tree to collapse into the inode
2. Atomic writes on mixed mapping causing tree to collapse into the inode

This test is inspired by ext4/034.

---
 tests/ext4/064     | 75 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/064.out |  2 ++
 2 files changed, 77 insertions(+)
 create mode 100755 tests/ext4/064
 create mode 100644 tests/ext4/064.out
tests/ext4/064 new file +75 -0
new file mode 100755
index 00000000..12e48ae3
@@ -0,0 +1,75 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 034
+#
+# Test proper credit reservation is done when performing
+# tree collapse during an aotmic write based allocation
+#
+. ./common/preamble
+. ./common/atomicwrites
+_begin_fstest auto quick quota fiemap prealloc atomicwrites
+
+# Import common functions.
+
+
+# Modify as appropriate.
+_exclude_fs ext2
+_exclude_fs ext3
+_require_xfs_io_command "falloc"
+_require_xfs_io_command "fiemap"
+_require_xfs_io_command "syncfs"
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+
+echo "----- Testing with atomi write on non-mixed mapping -----" >> $seqres.full
+
+echo "Format and mount" >> $seqres.full
+_scratch_mkfs  > $seqres.full 2>&1
+_scratch_mount > $seqres.full 2>&1
+
+echo "Create the original file" >> $seqres.full
+touch $SCRATCH_MNT/foobar >> $seqres.full
+
+echo "Create 2 level extent tree (btree) for foobar with a unwritten extent" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "falloc 4k 4k" -c "pwrite 8k 4k" \
+	     -c "pwrite 20k 4k"  -c "pwrite 28k 4k" -c "pwrite 36k 4k" \
+	     -c "fsync" $SCRATCH_MNT/foobar >> $seqres.full
+
+$XFS_IO_PROG -c "fiemap -v" $SCRATCH_MNT/foobar >> $seqres.full
+
+echo "Convert unwritten extent to written and collapse extent tree to inode" >> $seqres.full
+$XFS_IO_PROG -dc "pwrite -A -V1 4k 4k" $SCRATCH_MNT/foobar >> $seqres.full
+
+echo "Create a new file and do fsync to force a jbd2 commit" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "fsync" $SCRATCH_MNT/dummy >> $seqres.full
+
+echo "sync $SCRATCH_MNT to writeback" >> $seqres.full
+$XFS_IO_PROG -c "syncfs" $SCRATCH_MNT >> $seqres.full
+
+echo "----- Testing with atomi write on mixed mapping -----" >> $seqres.full
+
+echo "Create the original file" >> $seqres.full
+touch $SCRATCH_MNT/foobar2 >> $seqres.full
+
+echo "Create 2 level extent tree (btree) for foobar2 with a unwritten extent" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "falloc 4k 4k" -c "pwrite 8k 4k" \
+	     -c "pwrite 20k 4k"  -c "pwrite 28k 4k" -c "pwrite 36k 4k" \
+	     -c "fsync" $SCRATCH_MNT/foobar2 >> $seqres.full
+
+$XFS_IO_PROG -c "fiemap -v" $SCRATCH_MNT/foobar2 >> $seqres.full
+
+echo "Convert unwritten extent to written and collapse extent tree to inode" >> $seqres.full
+$XFS_IO_PROG -dc "pwrite -A -V1 0k 12k" $SCRATCH_MNT/foobar2 >> $seqres.full
+
+echo "Create a new file and do fsync to force a jbd2 commit" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "fsync" $SCRATCH_MNT/dummy2 >> $seqres.full
+
+echo "sync $SCRATCH_MNT to writeback" >> $seqres.full
+$XFS_IO_PROG -c "syncfs" $SCRATCH_MNT >> $seqres.full
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
tests/ext4/064.out new file +2 -0
new file mode 100644
index 00000000..d9076546
@@ -0,0 +1,2 @@
+QA output created by 064
+Silence is golden
Signature · 1 line
2.49.0
#60 lore raw

Re: [RFC 12/12] ext4/064: Add atomic write tests for journal credit calculation

Quoted 36 lines · Ojaswin Mujoo

Test atomic writes with journal credit calculation. We take 2 cases here:

1. Atomic writes on single mapping causing tree to collapse into the inode
2. Atomic writes on mixed mapping causing tree to collapse into the inode

This test is inspired by ext4/034.

---
 tests/ext4/064     | 75 ++++++++++++++++++++++++++++++++++++++++++++++
 tests/ext4/064.out |  2 ++
 2 files changed, 77 insertions(+)
 create mode 100755 tests/ext4/064
 create mode 100644 tests/ext4/064.out
tests/ext4/064 new file +11 -0
new file mode 100755
index 00000000..12e48ae3
@@ -0,0 +1,75 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2025 IBM Corporation. All Rights Reserved.
+#
+# FS QA Test 034
+#
+# Test proper credit reservation is done when performing
+# tree collapse during an aotmic write based allocation
+#
+. ./common/preamble
+. ./common/atomicwrites

tests/ext4/064 line 21

Quoted 10 lines
tests/ext4/064 new file +10 -0
+_begin_fstest auto quick quota fiemap prealloc atomicwrites
+
+# Import common functions.
+
+
+# Modify as appropriate.
+_exclude_fs ext2
+_exclude_fs ext3
+_require_xfs_io_command "falloc"
+_require_xfs_io_command "fiemap"

Great, this case notes the falloc and fiemap commands, and adds fiemap and prealloc test groups. Other test patches refer to this.

Quoted 12 lines
tests/ext4/064 new file +12 -0
+_require_xfs_io_command "syncfs"
+_require_scratch_write_atomic_multi_fsblock
+_require_atomic_write_test_commands
+
+echo "----- Testing with atomi write on non-mixed mapping -----" >> $seqres.full
+
+echo "Format and mount" >> $seqres.full
+_scratch_mkfs  > $seqres.full 2>&1
+_scratch_mount > $seqres.full 2>&1
+
+echo "Create the original file" >> $seqres.full
+touch $SCRATCH_MNT/foobar >> $seqres.full

tests/ext4/064 line 43

Quoted 10 lines
tests/ext4/064 new file +10 -0
+
+echo "Create 2 level extent tree (btree) for foobar with a unwritten extent" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "falloc 4k 4k" -c "pwrite 8k 4k" \
+	     -c "pwrite 20k 4k"  -c "pwrite 28k 4k" -c "pwrite 36k 4k" \
+	     -c "fsync" $SCRATCH_MNT/foobar >> $seqres.full
+
+$XFS_IO_PROG -c "fiemap -v" $SCRATCH_MNT/foobar >> $seqres.full
+
+echo "Convert unwritten extent to written and collapse extent tree to inode" >> $seqres.full
+$XFS_IO_PROG -dc "pwrite -A -V1 4k 4k" $SCRATCH_MNT/foobar >> $seqres.full

_require_xfs_io_command pwrite -A

Quoted 44 lines
tests/ext4/064 new file +32 -0
+
+echo "Create a new file and do fsync to force a jbd2 commit" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "fsync" $SCRATCH_MNT/dummy >> $seqres.full
+
+echo "sync $SCRATCH_MNT to writeback" >> $seqres.full
+$XFS_IO_PROG -c "syncfs" $SCRATCH_MNT >> $seqres.full
+
+echo "----- Testing with atomi write on mixed mapping -----" >> $seqres.full
+
+echo "Create the original file" >> $seqres.full
+touch $SCRATCH_MNT/foobar2 >> $seqres.full
+
+echo "Create 2 level extent tree (btree) for foobar2 with a unwritten extent" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "falloc 4k 4k" -c "pwrite 8k 4k" \
+	     -c "pwrite 20k 4k"  -c "pwrite 28k 4k" -c "pwrite 36k 4k" \
+	     -c "fsync" $SCRATCH_MNT/foobar2 >> $seqres.full
+
+$XFS_IO_PROG -c "fiemap -v" $SCRATCH_MNT/foobar2 >> $seqres.full
+
+echo "Convert unwritten extent to written and collapse extent tree to inode" >> $seqres.full
+$XFS_IO_PROG -dc "pwrite -A -V1 0k 12k" $SCRATCH_MNT/foobar2 >> $seqres.full
+
+echo "Create a new file and do fsync to force a jbd2 commit" >> $seqres.full
+$XFS_IO_PROG -f -c "pwrite 0 4k" -c "fsync" $SCRATCH_MNT/dummy2 >> $seqres.full
+
+echo "sync $SCRATCH_MNT to writeback" >> $seqres.full
+$XFS_IO_PROG -c "syncfs" $SCRATCH_MNT >> $seqres.full
+
+# success, all done
+echo "Silence is golden"
+status=0
+exit
tests/ext4/064.out new file +2 -0
new file mode 100644
index 00000000..d9076546
@@ -0,0 +1,2 @@
+QA output created by 064
+Silence is golden
Signature · 1 line
2.49.0