#! /bin/bash # SPDX-License-Identifier: GPL-2.0-only # Copyright 2021 Google LLC # # FS QA Test No. 622 # # Test that when the lazytime mount option is enabled, updates to atime, mtime, # and ctime are persisted in (at least) the four cases when they should be: # # - The inode needs to be updated for some change unrelated to file timestamps # - Userspace calls fsync(), syncfs(), or sync() # - The inode is evicted from memory # - More than dirtytime_expire_seconds have elapsed # # This is in part a regression test for kernel commit 1e249cb5b7fc # ("fs: fix lazytime expiration handling in __writeback_single_inode()"). # This test failed on XFS without that commit. # seq=`basename $0` seqres=$RESULT_DIR/$seq echo "QA output created by $seq" here=`pwd` tmp=/tmp/$$ status=1 # failure is the default! trap "_cleanup; exit \$status" 0 1 2 3 15 DIRTY_EXPIRE_CENTISECS_ORIG=$( /proc/sys/vm/dirty_expire_centisecs echo "$DIRTY_WRITEBACK_CENTISECS_ORIG" > /proc/sys/vm/dirty_writeback_centisecs echo "$DIRTYTIME_EXPIRE_SECONDS_ORIG" > /proc/sys/vm/dirtytime_expire_seconds } # Enable continuous writeback of dirty inodes, so that we don't have to wait # for the typical 30 seconds default. __expire_inodes() { echo 1 > /proc/sys/vm/dirty_expire_centisecs echo 1 > /proc/sys/vm/dirty_writeback_centisecs } # Trigger and wait for writeback of any dirty inodes (not dirtytime inodes). expire_inodes() { __expire_inodes # Userspace doesn't have direct visibility into when inodes are dirty, # so the best we can do is sleep for a couple seconds. sleep 2 restore_expiration_settings } # Trigger and wait for writeback of any dirtytime inodes. expire_timestamps() { # Enable immediate expiration of lazytime timestamps, so that we don't # have to wait for the typical 24 hours default. This should quickly # turn dirtytime inodes into regular dirty inodes. echo 1 > /proc/sys/vm/dirtytime_expire_seconds # Enable continuous writeback of dirty inodes. __expire_inodes # Userspace doesn't have direct visibility into when inodes are dirty, # so the best we can do is sleep for a couple seconds. sleep 2 restore_expiration_settings } _cleanup() { restore_expiration_settings rm -f $tmp.* } . ./common/rc . ./common/filter rm -f $seqres.full _supported_fs generic # This test uses the shutdown command, so it has to use the scratch filesystem # rather than the test filesystem. _require_scratch _require_scratch_shutdown _require_xfs_io_command "pwrite" _require_xfs_io_command "fsync" _require_xfs_io_command "syncfs" # Note that this test doesn't have to check that the filesystem supports # "lazytime", since "lazytime" is a VFS-level option, and at worst it just will # be ignored. This test will still pass if lazytime is ignored, as it only # tests that timestamp updates are persisted when they should be; it doesn't # test that timestamp updates aren't persisted when they shouldn't be. _scratch_mkfs &>> $seqres.full _scratch_mount # Create the test file for which we'll update and check the timestamps. file=$SCRATCH_MNT/file $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null # Get the specified timestamp of $file in nanoseconds since the epoch. get_timestamp() { local timestamp_type=$1 local arg case $timestamp_type in atime) arg=X ;; mtime) arg=Y ;; ctime) arg=Z ;; *) _fail "Unhandled timestamp_type: $timestamp_type" ;; esac stat -c "%.9${arg}" $file | tr -d '.' } do_test() { local timestamp_type=$1 local persist_method=$2 echo -e "\n# Testing that lazytime $timestamp_type update is persisted by $persist_method" # Mount the filesystem with lazytime. If atime is being tested, then # also use strictatime, since otherwise the filesystem may default to # relatime and not do the atime updates. if [[ $timestamp_type == atime ]]; then _scratch_cycle_mount lazytime,strictatime else _scratch_cycle_mount lazytime fi # Update the specified timestamp on the file. local orig_time=$(get_timestamp $timestamp_type) sleep 0.1 case $timestamp_type in atime) # Read from the file to update its atime. cat $file > /dev/null ;; mtime) # Write to the file to update its mtime. Make sure to not write # past the end of the file, as that would change i_size, which # would be an inode change which would cause the timestamp to # always be written -- thus making the test not detect bugs # where the timestamp doesn't get written. # # Also do the write twice, since XFS updates i_version the first # time, which likewise causes mtime to be written. We want the # last thing done to just update mtime. $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null ;; ctime) # It isn't possible to update just ctime, so use 'touch -a' # to update both atime and ctime. touch -a $file ;; esac local expected_time=$(get_timestamp $timestamp_type) if (( expected_time <= orig_time )); then echo "FAIL: $timestamp_type didn't increase after updating it (in-memory)" fi # Do something that should cause the timestamp to be persisted. case $persist_method in other_inode_change) # Make a non-timestamp-related change to the inode. chmod 777 $file if [[ $timestamp_type == ctime ]]; then # The inode change will have updated ctime again. expected_time=$(get_timestamp ctime) fi # The inode may have been marked dirty but not actually written # yet. Expire it by tweaking the VM settings and waiting. expire_inodes ;; sync) # Execute the sync() system call. sync ;; fsync) # Execute the fsync() system call on the file. $XFS_IO_PROG -r $file -c fsync ;; syncfs) # Execute the syncfs() system call on the filesystem. $XFS_IO_PROG $SCRATCH_MNT -c syncfs ;; eviction) # Evict the inode from memory. In theory, drop_caches should do # the trick by itself. But that actually just dirties the # inodes that need a lazytime update. So we still need to wait # for inode writeback too. echo 2 > /proc/sys/vm/drop_caches expire_inodes ;; expiration) # Expire the lazy timestamps via dirtytime_expire_seconds. expire_timestamps ;; *) _fail "Unhandled persist_method: $persist_method" esac # Use the shutdown ioctl to abort the filesystem. # # The timestamp might have just been written to the log and not *fully* # persisted yet, so use -f to ensure the log gets flushed. _scratch_shutdown -f # Now remount the filesystem and verify that the timestamp really got # updated as expected. _scratch_cycle_mount local ondisk_time=$(get_timestamp $timestamp_type) if (( ondisk_time != expected_time )); then # Fail the test by printing unexpected output rather than by # calling _fail(), since we can still run the other test cases. echo "FAIL: lazytime $timestamp_type wasn't persisted by $persist_method" echo "ondisk_time ($ondisk_time) != expected_time ($expected_time)" fi } for timestamp_type in atime mtime ctime; do do_test $timestamp_type other_inode_change do_test $timestamp_type sync do_test $timestamp_type fsync do_test $timestamp_type syncfs do_test $timestamp_type eviction do_test $timestamp_type expiration done # success, all done status=0 exit