summaryrefslogtreecommitdiff
path: root/tests/generic/622
blob: e03fdd714a62f8156ad06383cdc09a23d6f67d96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#! /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

_supported_fs generic
# 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"

	# 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
	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