generic: test for lazytime timestamp updates
[xfstests-dev.git] / tests / generic / 622
1 #! /bin/bash
2 # SPDX-License-Identifier: GPL-2.0-only
3 # Copyright 2021 Google LLC
4 #
5 # FS QA Test No. 622
6 #
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:
9 #
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
14 #
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.
18 #
19 seq=`basename $0`
20 seqres=$RESULT_DIR/$seq
21 echo "QA output created by $seq"
22
23 here=`pwd`
24 tmp=/tmp/$$
25 status=1        # failure is the default!
26 trap "_cleanup; exit \$status" 0 1 2 3 15
27
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)
31
32 restore_expiration_settings()
33 {
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
37 }
38
39 # Enable continuous writeback of dirty inodes, so that we don't have to wait
40 # for the typical 30 seconds default.
41 __expire_inodes()
42 {
43         echo 1 > /proc/sys/vm/dirty_expire_centisecs
44         echo 1 > /proc/sys/vm/dirty_writeback_centisecs
45 }
46
47 # Trigger and wait for writeback of any dirty inodes (not dirtytime inodes).
48 expire_inodes()
49 {
50         __expire_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.
53         sleep 2
54         restore_expiration_settings
55 }
56
57 # Trigger and wait for writeback of any dirtytime inodes.
58 expire_timestamps()
59 {
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
64
65         # Enable continuous writeback of dirty inodes.
66         __expire_inodes
67
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.
70         sleep 2
71         restore_expiration_settings
72 }
73
74 _cleanup()
75 {
76         restore_expiration_settings
77         rm -f $tmp.*
78 }
79
80 . ./common/rc
81 . ./common/filter
82
83 rm -f $seqres.full
84
85 _supported_fs generic
86 # This test uses the shutdown command, so it has to use the scratch filesystem
87 # rather than the test filesystem.
88 _require_scratch
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.
98
99 _scratch_mkfs &>> $seqres.full
100 _scratch_mount
101
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
105
106 # Get the specified timestamp of $file in nanoseconds since the epoch.
107 get_timestamp()
108 {
109         local timestamp_type=$1
110
111         local arg
112         case $timestamp_type in
113         atime)  arg=X ;;
114         mtime)  arg=Y ;;
115         ctime)  arg=Z ;;
116         *)      _fail "Unhandled timestamp_type: $timestamp_type" ;;
117         esac
118         stat -c "%.9${arg}" $file | tr -d '.'
119 }
120
121 do_test()
122 {
123         local timestamp_type=$1
124         local persist_method=$2
125
126         echo -e "\n# Testing that lazytime $timestamp_type update is persisted by $persist_method"
127
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
133         else
134                 _scratch_cycle_mount lazytime
135         fi
136
137         # Update the specified timestamp on the file.
138         local orig_time=$(get_timestamp $timestamp_type)
139         sleep 0.1
140         case $timestamp_type in
141         atime)
142                 # Read from the file to update its atime.
143                 cat $file > /dev/null
144                 ;;
145         mtime)
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.
151                 #
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
157                 ;;
158         ctime)
159                 # It isn't possible to update just ctime, so use 'touch -a'
160                 # to update both atime and ctime.
161                 touch -a $file
162                 ;;
163         esac
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)"
167         fi
168
169         # Do something that should cause the timestamp to be persisted.
170         case $persist_method in
171         other_inode_change)
172                 # Make a non-timestamp-related change to the inode.
173                 chmod 777 $file
174                 if [[ $timestamp_type == ctime ]]; then
175                         # The inode change will have updated ctime again.
176                         expected_time=$(get_timestamp ctime)
177                 fi
178                 # The inode may have been marked dirty but not actually written
179                 # yet.  Expire it by tweaking the VM settings and waiting.
180                 expire_inodes
181                 ;;
182         sync)
183                 # Execute the sync() system call.
184                 sync
185                 ;;
186         fsync)
187                 # Execute the fsync() system call on the file.
188                 $XFS_IO_PROG -r $file -c fsync
189                 ;;
190         syncfs)
191                 # Execute the syncfs() system call on the filesystem.
192                 $XFS_IO_PROG $SCRATCH_MNT -c syncfs
193                 ;;
194         eviction)
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
200                 expire_inodes
201                 ;;
202         expiration)
203                 # Expire the lazy timestamps via dirtytime_expire_seconds.
204                 expire_timestamps
205                 ;;
206         *)
207                 _fail "Unhandled persist_method: $persist_method"
208         esac
209
210         # Use the shutdown ioctl to abort the filesystem.
211         #
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.
214         _scratch_shutdown -f
215
216         # Now remount the filesystem and verify that the timestamp really got
217         # updated as expected.
218         _scratch_cycle_mount
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)"
225         fi
226 }
227
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
235 done
236
237 # success, all done
238 status=0
239 exit