overlayfs is about to stop syncing the whole upper filesystem on syncfs(2) and unmount. With fsync=relaxed it only writes the upper inodes that were modified through the overlay, so it has to know about all of them. Modify an overlay in many ways: new files and directories, data and metadata copy up, stores through a shared mapping, sendfile, copy_range, an O_TMPFILE, a short and a long symlink, hard links, renames, whiteouts, a file written before the inode caches are dropped. Then sync or unmount the overlay, shut down the base filesystem without flushing its log, cycle the mounts and check that nothing has changed. Do that with the default mount options, with every fsync mode that syncs and with redirect_dir=on,metacopy=on. Options that the kernel does not know are skipped. Signed-off-by: Josef Bacik --- v1: https://lore.kernel.org/r/773faf2a275e0a51bd38cf68f492856c.josef@toxicpanda.com v1->v2: - Added a symlink that's too long to be stored in the inode. (Christian) This is for "ovl: only sync what was written through the overlay" [1], Amir suggested a test like this in his review. It passes without the series on ext4 and xfs, there it skips fsync=relaxed and fsync=on, and it passes with the series. With the series and the function that marks the overlay inode dirty turned into a no-op it fails in the default, fsync=relaxed and metacopy rounds, the file data is gone. [1] https://lore.kernel.org/r/20261106-b4-ovl-containerized-sync-v4-0-6ddde0b7dd8a@toxicpanda.com tests/overlay/118 | 182 ++++++++++++++++++++++++++++++++++++++++++ tests/overlay/118.out | 2 + 2 files changed, 184 insertions(+) create mode 100755 tests/overlay/118 create mode 100644 tests/overlay/118.out diff --git a/tests/overlay/118 b/tests/overlay/118 new file mode 100755 index 00000000..f9c98eff --- /dev/null +++ b/tests/overlay/118 @@ -0,0 +1,182 @@ +#! /bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2026 Josef Bacik. All Rights Reserved. +# +# FS QA Test 118 +# +# Test that syncfs(2) on an overlay, and unmounting one, persist everything +# that was modified through it. +# +# overlayfs used to sync the whole upper filesystem for that. With +# fsync=relaxed it only writes the upper inodes that were modified through this +# overlay, so it has to know about all of them. +# +# Modify the overlay in every way we can think of, sync it, shut down the base +# filesystem without flushing its log, cycle the mounts and check that nothing +# has changed. +# +. ./common/preamble +_begin_fstest auto quick shutdown copyup whiteout mmap redirect metacopy + +. ./common/attr + +_require_scratch +_require_scratch_shutdown +_require_metadata_journaling $SCRATCH_DEV +_require_xfs_io_command "syncfs" +_require_xfs_io_command "sendfile" +_require_xfs_io_command "copy_range" +_require_xfs_io_command "-T" +_require_xfs_io_command "flink" +_require_attrs +_require_chown +_require_mknod +# These never sync +_exclude_scratch_mount_option "volatile" "fsync=off" + +lowerdir=$OVL_BASE_SCRATCH_MNT/$OVL_LOWER + +create_lower() +{ + local f + + # This leaves the base filesystem mounted + _scratch_mkfs >> $seqres.full 2>&1 + + mkdir -p $lowerdir/dir $lowerdir/rmdir $lowerdir/mvdir + for f in append truncate mmap chmod chown chown_append xattr link \ + rename remove dir/append rmdir/file mvdir/file; do + $XFS_IO_PROG -f -c "pwrite -S 0x6c 0 256k" $lowerdir/$f \ + >> $seqres.full + done + + # These were not written through the overlay, so it does not have to + # sync them. + $XFS_IO_PROG -c "syncfs" $OVL_BASE_SCRATCH_MNT +} + +modify() +{ + local m=$SCRATCH_MNT + + # New files and directories + $XFS_IO_PROG -f -c "pwrite -S 0x61 0 1m" $m/new >> $seqres.full + mkdir $m/newdir + $XFS_IO_PROG -f -c "pwrite -S 0x62 0 64k" $m/newdir/new >> $seqres.full + $XFS_IO_PROG -f -c "pwrite -S 0x63 0 64k" $m/dir/new >> $seqres.full + ln -s new $m/symlink + # Too long to be stored in the inode + ln -s $(printf 'a%.0s' $(seq 1000)) $m/longsymlink + mkfifo $m/fifo + + # Copy up, then change the data + $XFS_IO_PROG -c "pwrite -S 0x64 256k 64k" $m/append >> $seqres.full + $XFS_IO_PROG -c "pwrite -S 0x65 256k 64k" $m/dir/append >> $seqres.full + $XFS_IO_PROG -c "truncate 4k" $m/truncate + + # Stores through a shared mapping, also after a syncfs, no msync + $XFS_IO_PROG -c "mmap -rw 0 256k" -c "mwrite -S 0x66 0 64k" \ + -c "syncfs" -c "mwrite -S 0x67 64k 64k" $m/mmap >> $seqres.full + + # The other ways to write data + $XFS_IO_PROG -f -c "sendfile -i $m/link 0 256k" $m/sendfile \ + >> $seqres.full + $XFS_IO_PROG -f -c "copy_range -l 256k $m/link" $m/copy_range + $XFS_IO_PROG -T -c "pwrite -S 0x69 0 64k" -c "flink $m/tmpfile" $m \ + >> $seqres.full + + # Copy up, then change the metadata + chmod 640 $m/chmod + chown 1234:1234 $m/chown + $SETFATTR_PROG -n user.test -v value $m/xattr + + # With metacopy=on the first one only copies up the metadata + chown 1234:1234 $m/chown_append + $XFS_IO_PROG -c "pwrite -S 0x6a 256k 64k" $m/chown_append \ + >> $seqres.full + + # Names + ln $m/link $m/link2 + mv $m/rename $m/renamed + # Without redirect_dir=on mv gets EXDEV and copies the directory + mv $m/mvdir $m/mvdir2 + rm $m/remove + rm -r $m/rmdir + + # An overlay inode that nobody holds on to + $XFS_IO_PROG -f -c "pwrite -S 0x68 0 1m" $m/evicted >> $seqres.full + echo 2 > /proc/sys/vm/drop_caches +} + +# No inode numbers, no timestamps, and no sizes and link counts of directories +snapshot() +{ + local f + + cd $SCRATCH_MNT + find . | LC_ALL=C sort | while read -r f; do + if [ -L "$f" ]; then + echo "$f $(stat -c '%F %u:%g' "$f") $(readlink "$f")" + elif [ -f "$f" ]; then + echo "$f $(stat -c '%F %a %u:%g %h %s' "$f")" \ + "$(md5sum < "$f")" + else + echo "$f $(stat -c '%F %a %u:%g' "$f")" + fi + done + _getfattr --absolute-names -n user.test xattr 2>&1 + cd - > /dev/null +} + +# [mount options] +do_test() +{ + local how=$1 + shift + local label="$how${*:+ $*}" + + echo "=== $label" >> $seqres.full + + create_lower + if [ $# -eq 0 ]; then + _scratch_mount + elif ! _try_scratch_mount $* >> $seqres.full 2>&1; then + # Not every kernel knows every option + echo "cannot mount" >> $seqres.full + _unmount $OVL_BASE_SCRATCH_MNT + return + fi + + modify + snapshot > $tmp.before + + if [ $how = syncfs ]; then + $XFS_IO_PROG -c "syncfs" $SCRATCH_MNT + _scratch_shutdown | tee -a $seqres.full + _scratch_unmount + else + # Only the overlay, the base filesystem stays mounted + _unmount $SCRATCH_MNT + _scratch_shutdown | tee -a $seqres.full + _unmount $OVL_BASE_SCRATCH_MNT + fi + + _scratch_mount $* + snapshot > $tmp.after + cat $tmp.after >> $seqres.full + diff -u $tmp.before $tmp.after | sed -e "s/^/$label: /" + _scratch_unmount +} + +for how in syncfs umount; do + do_test $how + for mode in relaxed on strict; do + do_test $how -o fsync=$mode + done + do_test $how -o redirect_dir=on,metacopy=on +done + +# success, all done +echo "Silence is golden" +status=0 +exit diff --git a/tests/overlay/118.out b/tests/overlay/118.out new file mode 100644 index 00000000..3daed864 --- /dev/null +++ b/tests/overlay/118.out @@ -0,0 +1,2 @@ +QA output created by 118 +Silence is golden -- 2.55.0