2 # SPDX-License-Identifier: GPL-2.0-only
3 # Copyright 2021 Google LLC
7 # Test that when the lazytime mount option is enabled, updates to atime, mtime,
8 # and ctime are persisted in (at least) the four cases when they should be:
10 # - The inode needs to be updated for some change unrelated to file timestamps
11 # - Userspace calls fsync(), syncfs(), or sync()
12 # - The inode is evicted from memory
13 # - More than dirtytime_expire_seconds have elapsed
15 # This is in part a regression test for kernel commit 1e249cb5b7fc
16 # ("fs: fix lazytime expiration handling in __writeback_single_inode()").
17 # This test failed on XFS without that commit.
20 _begin_fstest auto shutdown metadata atime
22 DIRTY_EXPIRE_CENTISECS_ORIG=$(</proc/sys/vm/dirty_expire_centisecs)
23 DIRTY_WRITEBACK_CENTISECS_ORIG=$(</proc/sys/vm/dirty_writeback_centisecs)
24 DIRTYTIME_EXPIRE_SECONDS_ORIG=$(</proc/sys/vm/dirtytime_expire_seconds)
26 restore_expiration_settings()
28 echo "$DIRTY_EXPIRE_CENTISECS_ORIG" > /proc/sys/vm/dirty_expire_centisecs
29 echo "$DIRTY_WRITEBACK_CENTISECS_ORIG" > /proc/sys/vm/dirty_writeback_centisecs
30 echo "$DIRTYTIME_EXPIRE_SECONDS_ORIG" > /proc/sys/vm/dirtytime_expire_seconds
33 # Enable continuous writeback of dirty inodes, so that we don't have to wait
34 # for the typical 30 seconds default.
37 echo 1 > /proc/sys/vm/dirty_expire_centisecs
38 echo 1 > /proc/sys/vm/dirty_writeback_centisecs
41 # Trigger and wait for writeback of any dirty inodes (not dirtytime inodes).
45 # Userspace doesn't have direct visibility into when inodes are dirty,
46 # so the best we can do is sleep for a couple seconds.
48 restore_expiration_settings
51 # Trigger and wait for writeback of any dirtytime inodes.
54 # Enable immediate expiration of lazytime timestamps, so that we don't
55 # have to wait for the typical 24 hours default. This should quickly
56 # turn dirtytime inodes into regular dirty inodes.
57 echo 1 > /proc/sys/vm/dirtytime_expire_seconds
59 # Enable continuous writeback of dirty inodes.
62 # Userspace doesn't have direct visibility into when inodes are dirty,
63 # so the best we can do is sleep for a couple seconds.
65 restore_expiration_settings
68 # Override the default cleanup function.
71 restore_expiration_settings
78 # This test uses the shutdown command, so it has to use the scratch filesystem
79 # rather than the test filesystem.
81 _require_scratch_shutdown
82 _require_xfs_io_command "pwrite"
83 _require_xfs_io_command "fsync"
84 _require_xfs_io_command "syncfs"
85 # Note that this test doesn't have to check that the filesystem supports
86 # "lazytime", since "lazytime" is a VFS-level option, and at worst it just will
87 # be ignored. This test will still pass if lazytime is ignored, as it only
88 # tests that timestamp updates are persisted when they should be; it doesn't
89 # test that timestamp updates aren't persisted when they shouldn't be.
91 _scratch_mkfs &>> $seqres.full
94 # Create the test file for which we'll update and check the timestamps.
95 file=$SCRATCH_MNT/file
96 $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null
98 # Get the specified timestamp of $file in nanoseconds since the epoch.
101 local timestamp_type=$1
104 case $timestamp_type in
108 *) _fail "Unhandled timestamp_type: $timestamp_type" ;;
110 stat -c "%.9${arg}" $file | tr -d '.'
115 local timestamp_type=$1
116 local persist_method=$2
118 echo -e "\n# Testing that lazytime $timestamp_type update is persisted by $persist_method"
120 # Mount the filesystem with lazytime. If atime is being tested, then
121 # also use strictatime, since otherwise the filesystem may default to
122 # relatime and not do the atime updates.
123 if [[ $timestamp_type == atime ]]; then
124 _scratch_cycle_mount lazytime,strictatime
126 _scratch_cycle_mount lazytime
129 # Update the specified timestamp on the file.
130 local orig_time=$(get_timestamp $timestamp_type)
132 case $timestamp_type in
134 # Read from the file to update its atime.
135 cat $file > /dev/null
138 # Write to the file to update its mtime. Make sure to not write
139 # past the end of the file, as that would change i_size, which
140 # would be an inode change which would cause the timestamp to
141 # always be written -- thus making the test not detect bugs
142 # where the timestamp doesn't get written.
144 # Also do the write twice, since XFS updates i_version the first
145 # time, which likewise causes mtime to be written. We want the
146 # last thing done to just update mtime.
147 $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null
148 $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null
151 # It isn't possible to update just ctime, so use 'touch -a'
152 # to update both atime and ctime.
156 local expected_time=$(get_timestamp $timestamp_type)
157 if (( expected_time <= orig_time )); then
158 echo "FAIL: $timestamp_type didn't increase after updating it (in-memory)"
161 # Do something that should cause the timestamp to be persisted.
162 case $persist_method in
164 # Make a non-timestamp-related change to the inode.
166 if [[ $timestamp_type == ctime ]]; then
167 # The inode change will have updated ctime again.
168 expected_time=$(get_timestamp ctime)
170 # The inode may have been marked dirty but not actually written
171 # yet. Expire it by tweaking the VM settings and waiting.
175 # Execute the sync() system call.
179 # Execute the fsync() system call on the file.
180 $XFS_IO_PROG -r $file -c fsync
183 # Execute the syncfs() system call on the filesystem.
184 $XFS_IO_PROG $SCRATCH_MNT -c syncfs
187 # Evict the inode from memory. In theory, drop_caches should do
188 # the trick by itself. But that actually just dirties the
189 # inodes that need a lazytime update. So we still need to wait
190 # for inode writeback too.
191 echo 2 > /proc/sys/vm/drop_caches
195 # Expire the lazy timestamps via dirtytime_expire_seconds.
199 _fail "Unhandled persist_method: $persist_method"
202 # Use the shutdown ioctl to abort the filesystem.
204 # The timestamp might have just been written to the log and not *fully*
205 # persisted yet, so use -f to ensure the log gets flushed.
208 # Now remount the filesystem and verify that the timestamp really got
209 # updated as expected.
211 local ondisk_time=$(get_timestamp $timestamp_type)
212 if (( ondisk_time != expected_time )); then
213 # Fail the test by printing unexpected output rather than by
214 # calling _fail(), since we can still run the other test cases.
215 echo "FAIL: lazytime $timestamp_type wasn't persisted by $persist_method"
216 echo "ondisk_time ($ondisk_time) != expected_time ($expected_time)"
220 for timestamp_type in atime mtime ctime; do
221 do_test $timestamp_type other_inode_change
222 do_test $timestamp_type sync
223 do_test $timestamp_type fsync
224 do_test $timestamp_type syncfs
225 do_test $timestamp_type eviction
226 do_test $timestamp_type expiration