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 seqres=$RESULT_DIR/$seq
21 echo "QA output created by $seq"
25 status=1 # failure is the default!
26 trap "_cleanup; exit \$status" 0 1 2 3 15
28 DIRTY_EXPIRE_CENTISECS_ORIG=$(</proc/sys/vm/dirty_expire_centisecs)
29 DIRTY_WRITEBACK_CENTISECS_ORIG=$(</proc/sys/vm/dirty_writeback_centisecs)
30 DIRTYTIME_EXPIRE_SECONDS_ORIG=$(</proc/sys/vm/dirtytime_expire_seconds)
32 restore_expiration_settings()
34 echo "$DIRTY_EXPIRE_CENTISECS_ORIG" > /proc/sys/vm/dirty_expire_centisecs
35 echo "$DIRTY_WRITEBACK_CENTISECS_ORIG" > /proc/sys/vm/dirty_writeback_centisecs
36 echo "$DIRTYTIME_EXPIRE_SECONDS_ORIG" > /proc/sys/vm/dirtytime_expire_seconds
39 # Enable continuous writeback of dirty inodes, so that we don't have to wait
40 # for the typical 30 seconds default.
43 echo 1 > /proc/sys/vm/dirty_expire_centisecs
44 echo 1 > /proc/sys/vm/dirty_writeback_centisecs
47 # Trigger and wait for writeback of any dirty inodes (not dirtytime inodes).
51 # Userspace doesn't have direct visibility into when inodes are dirty,
52 # so the best we can do is sleep for a couple seconds.
54 restore_expiration_settings
57 # Trigger and wait for writeback of any dirtytime inodes.
60 # Enable immediate expiration of lazytime timestamps, so that we don't
61 # have to wait for the typical 24 hours default. This should quickly
62 # turn dirtytime inodes into regular dirty inodes.
63 echo 1 > /proc/sys/vm/dirtytime_expire_seconds
65 # Enable continuous writeback of dirty inodes.
68 # Userspace doesn't have direct visibility into when inodes are dirty,
69 # so the best we can do is sleep for a couple seconds.
71 restore_expiration_settings
76 restore_expiration_settings
86 # This test uses the shutdown command, so it has to use the scratch filesystem
87 # rather than the test filesystem.
89 _require_scratch_shutdown
90 _require_xfs_io_command "pwrite"
91 _require_xfs_io_command "fsync"
92 _require_xfs_io_command "syncfs"
93 # Note that this test doesn't have to check that the filesystem supports
94 # "lazytime", since "lazytime" is a VFS-level option, and at worst it just will
95 # be ignored. This test will still pass if lazytime is ignored, as it only
96 # tests that timestamp updates are persisted when they should be; it doesn't
97 # test that timestamp updates aren't persisted when they shouldn't be.
99 _scratch_mkfs &>> $seqres.full
102 # Create the test file for which we'll update and check the timestamps.
103 file=$SCRATCH_MNT/file
104 $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null
106 # Get the specified timestamp of $file in nanoseconds since the epoch.
109 local timestamp_type=$1
112 case $timestamp_type in
116 *) _fail "Unhandled timestamp_type: $timestamp_type" ;;
118 stat -c "%.9${arg}" $file | tr -d '.'
123 local timestamp_type=$1
124 local persist_method=$2
126 echo -e "\n# Testing that lazytime $timestamp_type update is persisted by $persist_method"
128 # Mount the filesystem with lazytime. If atime is being tested, then
129 # also use strictatime, since otherwise the filesystem may default to
130 # relatime and not do the atime updates.
131 if [[ $timestamp_type == atime ]]; then
132 _scratch_cycle_mount lazytime,strictatime
134 _scratch_cycle_mount lazytime
137 # Update the specified timestamp on the file.
138 local orig_time=$(get_timestamp $timestamp_type)
140 case $timestamp_type in
142 # Read from the file to update its atime.
143 cat $file > /dev/null
146 # Write to the file to update its mtime. Make sure to not write
147 # past the end of the file, as that would change i_size, which
148 # would be an inode change which would cause the timestamp to
149 # always be written -- thus making the test not detect bugs
150 # where the timestamp doesn't get written.
152 # Also do the write twice, since XFS updates i_version the first
153 # time, which likewise causes mtime to be written. We want the
154 # last thing done to just update mtime.
155 $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null
156 $XFS_IO_PROG -f $file -c "pwrite 0 100" > /dev/null
159 # It isn't possible to update just ctime, so use 'touch -a'
160 # to update both atime and ctime.
164 local expected_time=$(get_timestamp $timestamp_type)
165 if (( expected_time <= orig_time )); then
166 echo "FAIL: $timestamp_type didn't increase after updating it (in-memory)"
169 # Do something that should cause the timestamp to be persisted.
170 case $persist_method in
172 # Make a non-timestamp-related change to the inode.
174 if [[ $timestamp_type == ctime ]]; then
175 # The inode change will have updated ctime again.
176 expected_time=$(get_timestamp ctime)
178 # The inode may have been marked dirty but not actually written
179 # yet. Expire it by tweaking the VM settings and waiting.
183 # Execute the sync() system call.
187 # Execute the fsync() system call on the file.
188 $XFS_IO_PROG -r $file -c fsync
191 # Execute the syncfs() system call on the filesystem.
192 $XFS_IO_PROG $SCRATCH_MNT -c syncfs
195 # Evict the inode from memory. In theory, drop_caches should do
196 # the trick by itself. But that actually just dirties the
197 # inodes that need a lazytime update. So we still need to wait
198 # for inode writeback too.
199 echo 2 > /proc/sys/vm/drop_caches
203 # Expire the lazy timestamps via dirtytime_expire_seconds.
207 _fail "Unhandled persist_method: $persist_method"
210 # Use the shutdown ioctl to abort the filesystem.
212 # The timestamp might have just been written to the log and not *fully*
213 # persisted yet, so use -f to ensure the log gets flushed.
216 # Now remount the filesystem and verify that the timestamp really got
217 # updated as expected.
219 local ondisk_time=$(get_timestamp $timestamp_type)
220 if (( ondisk_time != expected_time )); then
221 # Fail the test by printing unexpected output rather than by
222 # calling _fail(), since we can still run the other test cases.
223 echo "FAIL: lazytime $timestamp_type wasn't persisted by $persist_method"
224 echo "ondisk_time ($ondisk_time) != expected_time ($expected_time)"
228 for timestamp_type in atime mtime ctime; do
229 do_test $timestamp_type other_inode_change
230 do_test $timestamp_type sync
231 do_test $timestamp_type fsync
232 do_test $timestamp_type syncfs
233 do_test $timestamp_type eviction
234 do_test $timestamp_type expiration