generic: test MADV_POPULATE_READ with IO errors
[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 . ./common/preamble
20 _begin_fstest auto shutdown metadata atime
21
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)
25
26 restore_expiration_settings()
27 {
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
31 }
32
33 # Enable continuous writeback of dirty inodes, so that we don't have to wait
34 # for the typical 30 seconds default.
35 __expire_inodes()
36 {
37         echo 1 > /proc/sys/vm/dirty_expire_centisecs
38         echo 1 > /proc/sys/vm/dirty_writeback_centisecs
39 }
40
41 # Trigger and wait for writeback of any dirty inodes (not dirtytime inodes).
42 expire_inodes()
43 {
44         __expire_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.
47         sleep 2
48         restore_expiration_settings
49 }
50
51 # Trigger and wait for writeback of any dirtytime inodes.
52 expire_timestamps()
53 {
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
58
59         # Enable continuous writeback of dirty inodes.
60         __expire_inodes
61
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.
64         sleep 2
65         restore_expiration_settings
66 }
67
68 # Override the default cleanup function.
69 _cleanup()
70 {
71         restore_expiration_settings
72         rm -f $tmp.*
73 }
74
75 . ./common/filter
76
77 _supported_fs generic
78 # This test uses the shutdown command, so it has to use the scratch filesystem
79 # rather than the test filesystem.
80 _require_scratch
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.
90
91 _scratch_mkfs &>> $seqres.full
92 _scratch_mount
93
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
97
98 # Get the specified timestamp of $file in nanoseconds since the epoch.
99 get_timestamp()
100 {
101         local timestamp_type=$1
102
103         local arg
104         case $timestamp_type in
105         atime)  arg=X ;;
106         mtime)  arg=Y ;;
107         ctime)  arg=Z ;;
108         *)      _fail "Unhandled timestamp_type: $timestamp_type" ;;
109         esac
110         stat -c "%.9${arg}" $file | tr -d '.'
111 }
112
113 do_test()
114 {
115         local timestamp_type=$1
116         local persist_method=$2
117
118         echo -e "\n# Testing that lazytime $timestamp_type update is persisted by $persist_method"
119
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
125         else
126                 _scratch_cycle_mount lazytime
127         fi
128
129         # Update the specified timestamp on the file.
130         local orig_time=$(get_timestamp $timestamp_type)
131         sleep 0.1
132         case $timestamp_type in
133         atime)
134                 # Read from the file to update its atime.
135                 cat $file > /dev/null
136                 ;;
137         mtime)
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.
143                 #
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
149                 ;;
150         ctime)
151                 # It isn't possible to update just ctime, so use 'touch -a'
152                 # to update both atime and ctime.
153                 touch -a $file
154                 ;;
155         esac
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)"
159         fi
160
161         # Do something that should cause the timestamp to be persisted.
162         case $persist_method in
163         other_inode_change)
164                 # Make a non-timestamp-related change to the inode.
165                 chmod 777 $file
166                 if [[ $timestamp_type == ctime ]]; then
167                         # The inode change will have updated ctime again.
168                         expected_time=$(get_timestamp ctime)
169                 fi
170                 # The inode may have been marked dirty but not actually written
171                 # yet.  Expire it by tweaking the VM settings and waiting.
172                 expire_inodes
173                 ;;
174         sync)
175                 # Execute the sync() system call.
176                 sync
177                 ;;
178         fsync)
179                 # Execute the fsync() system call on the file.
180                 $XFS_IO_PROG -r $file -c fsync
181                 ;;
182         syncfs)
183                 # Execute the syncfs() system call on the filesystem.
184                 $XFS_IO_PROG $SCRATCH_MNT -c syncfs
185                 ;;
186         eviction)
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
192                 expire_inodes
193                 ;;
194         expiration)
195                 # Expire the lazy timestamps via dirtytime_expire_seconds.
196                 expire_timestamps
197                 ;;
198         *)
199                 _fail "Unhandled persist_method: $persist_method"
200         esac
201
202         # Use the shutdown ioctl to abort the filesystem.
203         #
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.
206         _scratch_shutdown -f
207
208         # Now remount the filesystem and verify that the timestamp really got
209         # updated as expected.
210         _scratch_cycle_mount
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)"
217         fi
218 }
219
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
227 done
228
229 # success, all done
230 status=0
231 exit