| #! /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. |
| # |
| . ./common/preamble |
| _begin_fstest auto shutdown metadata atime |
| |
| DIRTY_EXPIRE_CENTISECS_ORIG=$(</proc/sys/vm/dirty_expire_centisecs) |
| DIRTY_WRITEBACK_CENTISECS_ORIG=$(</proc/sys/vm/dirty_writeback_centisecs) |
| DIRTYTIME_EXPIRE_SECONDS_ORIG=$(</proc/sys/vm/dirtytime_expire_seconds) |
| |
| restore_expiration_settings() |
| { |
| echo "$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 |
| } |
| |
| # Override the default cleanup function. |
| _cleanup() |
| { |
| restore_expiration_settings |
| rm -f $tmp.* |
| } |
| |
| . ./common/filter |
| |
| # 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" |
| |
| # exfat does not support last metadata change timestamp |
| [ "${FSTYP}" == "exfat" -a "${timestamp_type}" == "ctime" ] && return 0 |
| |
| # 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 |
| |
| # exfat's timestamp for access_time has double seconds granularity |
| [ "${FSTYP}" == "exfat" -a "${timestamp_type}" == "atime" ] && sleep 2 |
| |
| 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 |