blob: a67931ad877fded53d1a0b75aa2237ba91c683ca [file] [log] [blame]
#! /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
# 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"
# exfat does not support last metadata change timestamp
[ "${FSTYP}" == "exfat" -a "${timestamp_type}" == "ctime" ] && return 0
# 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
# exfat's timestamp for access_time has double seconds granularity
[ "${FSTYP}" == "exfat" -a "${timestamp_type}" == "atime" ] && sleep 2
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