diff --git a/include/linux/vfs_compat.h b/include/linux/vfs_compat.h index c01f58508..04a2c2b87 100644 --- a/include/linux/vfs_compat.h +++ b/include/linux/vfs_compat.h @@ -207,6 +207,10 @@ zpl_bdi_destroy(struct super_block *sb) #define SB_MANDLOCK MS_MANDLOCK #endif +#ifndef SB_NOATIME +#define SB_NOATIME MS_NOATIME +#endif + /* * 2.6.38 API change, * LOOKUP_RCU flag introduced to distinguish rcu-walk from ref-walk cases. diff --git a/include/sys/zfs_vfsops.h b/include/sys/zfs_vfsops.h index cad0aaece..42f534f5d 100644 --- a/include/sys/zfs_vfsops.h +++ b/include/sys/zfs_vfsops.h @@ -98,7 +98,6 @@ struct zfsvfs { zfs_case_t z_case; /* case-sense */ boolean_t z_utf8; /* utf8-only */ int z_norm; /* normalization flags */ - boolean_t z_atime; /* enable atimes mount option */ boolean_t z_relatime; /* enable relatime mount option */ boolean_t z_unmounted; /* unmounted */ rrmlock_t z_teardown_lock; diff --git a/include/sys/zfs_znode.h b/include/sys/zfs_znode.h index 2dfcf453a..d4a3ea769 100644 --- a/include/sys/zfs_znode.h +++ b/include/sys/zfs_znode.h @@ -363,6 +363,7 @@ extern int zfs_inode_alloc(struct super_block *, struct inode **ip); extern void zfs_inode_destroy(struct inode *); extern void zfs_inode_update(znode_t *); extern void zfs_mark_inode_dirty(struct inode *); +extern boolean_t zfs_relatime_need_update(const struct inode *); extern void zfs_log_create(zilog_t *zilog, dmu_tx_t *tx, uint64_t txtype, znode_t *dzp, znode_t *zp, char *name, vsecattr_t *, zfs_fuid_info_t *, diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c index 781708ba9..18194a5dc 100644 --- a/module/zfs/zfs_vfsops.c +++ b/module/zfs/zfs_vfsops.c @@ -303,7 +303,21 @@ zfs_sync(struct super_block *sb, int wait, cred_t *cr) static void atime_changed_cb(void *arg, uint64_t newval) { - ((zfsvfs_t *)arg)->z_atime = newval; + zfsvfs_t *zfsvfs = arg; + struct super_block *sb = zfsvfs->z_sb; + + if (sb == NULL) + return; + /* + * Update SB_NOATIME bit in VFS super block. Since atime update is + * determined by atime_needs_update(), atime_needs_update() needs to + * return false if atime is turned off, and not unconditionally return + * false if atime is turned on. + */ + if (newval) + sb->s_flags &= ~SB_NOATIME; + else + sb->s_flags |= SB_NOATIME; } static void diff --git a/module/zfs/zfs_znode.c b/module/zfs/zfs_znode.c index d998e42ab..77eb8bb91 100644 --- a/module/zfs/zfs_znode.c +++ b/module/zfs/zfs_znode.c @@ -1345,16 +1345,39 @@ zfs_zinactive(znode_t *zp) zfs_znode_hold_exit(zfsvfs, zh); } -static inline int -zfs_compare_timespec(struct timespec *t1, struct timespec *t2) +#if defined(HAVE_INODE_TIMESPEC64_TIMES) +#define zfs_compare_timespec timespec64_compare +#else +#define zfs_compare_timespec timespec_compare +#endif + +/* + * Determine whether the znode's atime must be updated. The logic mostly + * duplicates the Linux kernel's relatime_need_update() functionality. + * This function is only called if the underlying filesystem actually has + * atime updates enabled. + */ +boolean_t +zfs_relatime_need_update(const struct inode *ip) { - if (t1->tv_sec < t2->tv_sec) - return (-1); + inode_timespec_t now; - if (t1->tv_sec > t2->tv_sec) - return (1); + gethrestime(&now); + /* + * In relatime mode, only update the atime if the previous atime + * is earlier than either the ctime or mtime or if at least a day + * has passed since the last update of atime. + */ + if (zfs_compare_timespec(&ip->i_mtime, &ip->i_atime) >= 0) + return (B_TRUE); - return (t1->tv_nsec - t2->tv_nsec); + if (zfs_compare_timespec(&ip->i_ctime, &ip->i_atime) >= 0) + return (B_TRUE); + + if ((hrtime_t)now.tv_sec - (hrtime_t)ip->i_atime.tv_sec >= 24*60*60) + return (B_TRUE); + + return (B_FALSE); } /* diff --git a/module/zfs/zpl_file.c b/module/zfs/zpl_file.c index 9c231d950..731836c2c 100644 --- a/module/zfs/zpl_file.c +++ b/module/zfs/zpl_file.c @@ -289,6 +289,8 @@ zpl_iter_read_common(struct kiocb *kiocb, const struct iovec *iovp, { cred_t *cr = CRED(); struct file *filp = kiocb->ki_filp; + struct inode *ip = filp->f_mapping->host; + zfsvfs_t *zfsvfs = ZTOZSB(ITOZ(ip)); ssize_t read; unsigned int f_flags = filp->f_flags; @@ -298,7 +300,20 @@ zpl_iter_read_common(struct kiocb *kiocb, const struct iovec *iovp, nr_segs, &kiocb->ki_pos, seg, f_flags, cr, skip); crfree(cr); - file_accessed(filp); + /* + * If relatime is enabled, call file_accessed() only if + * zfs_relatime_need_update() is true. This is needed since datasets + * with inherited "relatime" property aren't necessarily mounted with + * MNT_RELATIME flag (e.g. after `zfs set relatime=...`), which is what + * relatime test in VFS by relatime_need_update() is based on. + */ + if (!IS_NOATIME(ip) && zfsvfs->z_relatime) { + if (zfs_relatime_need_update(ip)) + file_accessed(filp); + } else { + file_accessed(filp); + } + return (read); } diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 836be089b..746d42a22 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -37,7 +37,8 @@ tests = ['dbufstats_001_pos', 'dbufstats_002_pos'] tags = ['functional', 'arc'] [tests/functional/atime] -tests = ['atime_001_pos', 'atime_002_neg', 'atime_003_pos'] +tests = ['atime_001_pos', 'atime_002_neg', 'atime_003_pos', 'root_atime_off', + 'root_atime_on', 'root_relatime_on'] tags = ['functional', 'atime'] [tests/functional/bootfs] diff --git a/tests/zfs-tests/include/libtest.shlib b/tests/zfs-tests/include/libtest.shlib index ea9448353..57d0880cc 100644 --- a/tests/zfs-tests/include/libtest.shlib +++ b/tests/zfs-tests/include/libtest.shlib @@ -214,6 +214,13 @@ function default_setup log_pass } +function default_setup_no_mountpoint +{ + default_setup_noexit "$1" "$2" "$3" "yes" + + log_pass +} + # # Given a list of disks, setup storage pools and datasets. # @@ -222,6 +229,7 @@ function default_setup_noexit typeset disklist=$1 typeset container=$2 typeset volume=$3 + typeset no_mountpoint=$4 log_note begin default_setup_noexit if is_global_zone; then @@ -238,7 +246,9 @@ function default_setup_noexit mkdir -p $TESTDIR || log_unresolved Could not create $TESTDIR log_must zfs create $TESTPOOL/$TESTFS - log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS + if [[ -z $no_mountpoint ]]; then + log_must zfs set mountpoint=$TESTDIR $TESTPOOL/$TESTFS + fi if [[ -n $container ]]; then rm -rf $TESTDIR1 || \ @@ -249,8 +259,10 @@ function default_setup_noexit log_must zfs create $TESTPOOL/$TESTCTR log_must zfs set canmount=off $TESTPOOL/$TESTCTR log_must zfs create $TESTPOOL/$TESTCTR/$TESTFS1 - log_must zfs set mountpoint=$TESTDIR1 \ - $TESTPOOL/$TESTCTR/$TESTFS1 + if [[ -z $no_mountpoint ]]; then + log_must zfs set mountpoint=$TESTDIR1 \ + $TESTPOOL/$TESTCTR/$TESTFS1 + fi fi if [[ -n $volume ]]; then diff --git a/tests/zfs-tests/tests/functional/atime/Makefile.am b/tests/zfs-tests/tests/functional/atime/Makefile.am index a4e2335a9..63d510b99 100644 --- a/tests/zfs-tests/tests/functional/atime/Makefile.am +++ b/tests/zfs-tests/tests/functional/atime/Makefile.am @@ -4,7 +4,10 @@ dist_pkgdata_SCRIPTS = \ setup.ksh \ atime_001_pos.ksh \ atime_002_neg.ksh \ - atime_003_pos.ksh + atime_003_pos.ksh \ + root_atime_off.ksh \ + root_atime_on.ksh \ + root_relatime_on.ksh dist_pkgdata_DATA = \ atime.cfg \ diff --git a/tests/zfs-tests/tests/functional/atime/atime_common.kshlib b/tests/zfs-tests/tests/functional/atime/atime_common.kshlib index 47de43267..bd6c6dc39 100644 --- a/tests/zfs-tests/tests/functional/atime/atime_common.kshlib +++ b/tests/zfs-tests/tests/functional/atime/atime_common.kshlib @@ -68,13 +68,28 @@ function check_atime_updated function setup_snap_clone { - # Create two file to verify snapshot. - log_must touch $TESTDIR/$TESTFILE + if [ -d "/$TESTPOOL/$TESTFS" ]; then + # No mountpoint case (default). + log_must touch /$TESTPOOL/$TESTFS/$TESTFILE + else + # Create two file to verify snapshot. + log_must touch $TESTDIR/$TESTFILE + fi create_snapshot $TESTPOOL/$TESTFS $TESTSNAP create_clone $TESTPOOL/$TESTFS@$TESTSNAP $TESTPOOL/$TESTCLONE } +function reset_atime +{ + zfs inherit atime $TESTPOOL + zfs inherit atime $TESTPOOL/$TESTFS + zfs inherit atime $TESTPOOL/$TESTCLONE + zfs inherit relatime $TESTPOOL + zfs inherit relatime $TESTPOOL/$TESTFS + zfs inherit relatime $TESTPOOL/$TESTCLONE +} + function cleanup { destroy_clone $TESTPOOL/$TESTCLONE diff --git a/tests/zfs-tests/tests/functional/atime/root_atime_off.ksh b/tests/zfs-tests/tests/functional/atime/root_atime_off.ksh new file mode 100755 index 000000000..2fbf06b13 --- /dev/null +++ b/tests/zfs-tests/tests/functional/atime/root_atime_off.ksh @@ -0,0 +1,74 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2016 by Delphix. All rights reserved. +# Copyright (c) 2019 by Tomohiro Kusumi. All rights reserved. +# + +. $STF_SUITE/tests/functional/atime/atime_common.kshlib + +# +# DESCRIPTION: +# When atime=off, verify the access time for files is not updated when read. +# It is available to pool, fs snapshot and clone. +# +# STRATEGY: +# 1. Create pool, fs. +# 2. Create '$TESTFILE' for fs. +# 3. Create snapshot and clone. +# 4. Setting atime=off on dataset and read '$TESTFILE'. +# 5. Verify the access time is not updated. +# + +verify_runnable "both" + +log_assert "Setting atime=off, the access time for files will not be updated \ + when read." +log_onexit cleanup + +# +# Create $TESTFILE, snapshot and clone. +# Same as 002 except that atime applies to root dataset (ZoL#8675). +# +setup_snap_clone +reset_atime + +for dst in $TESTPOOL/$TESTFS $TESTPOOL/$TESTCLONE $TESTPOOL/$TESTFS@$TESTSNAP +do + typeset mtpt=$(get_prop mountpoint $dst) + + if [[ $dst == $TESTPOOL/$TESTFS@$TESTSNAP ]]; then + mtpt=$(snapshot_mountpoint $dst) + else + log_must zfs set atime=off $(dirname $dst) + fi + + log_mustnot check_atime_updated $mtpt/$TESTFILE +done + +log_pass "Verify the property atime=off passed." diff --git a/tests/zfs-tests/tests/functional/atime/root_atime_on.ksh b/tests/zfs-tests/tests/functional/atime/root_atime_on.ksh new file mode 100755 index 000000000..3976523b0 --- /dev/null +++ b/tests/zfs-tests/tests/functional/atime/root_atime_on.ksh @@ -0,0 +1,78 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2016 by Delphix. All rights reserved. +# Copyright (c) 2019 by Tomohiro Kusumi. All rights reserved. +# + +. $STF_SUITE/tests/functional/atime/atime_common.kshlib + +# +# DESCRIPTION: +# When atime=on, verify the access time for files is updated when read. It +# is available to fs and clone. To snapshot, it is unavailable. +# +# STRATEGY: +# 1. Create pool and fs. +# 2. Create '$TESTFILE' for fs. +# 3. Create snapshot and clone. +# 4. Setting atime=on on datasets except snapshot, and read '$TESTFILE'. +# 5. Expect the access time is updated on datasets except snapshot. +# + +verify_runnable "both" + +log_assert "Setting atime=on, the access time for files is updated when read." +log_onexit cleanup + +# +# Create $TESTFILE, snapshot and clone. +# Same as 001 except that atime/relatime applies to root dataset (ZoL#8675). +# +setup_snap_clone +reset_atime + +for dst in $TESTPOOL/$TESTFS $TESTPOOL/$TESTCLONE $TESTPOOL/$TESTFS@$TESTSNAP +do + typeset mtpt=$(get_prop mountpoint $dst) + + if [[ $dst == $TESTPOOL/$TESTFS@$TESTSNAP ]]; then + mtpt=$(snapshot_mountpoint $dst) + log_mustnot check_atime_updated $mtpt/$TESTFILE + else + if is_linux; then + log_must zfs set relatime=off $(dirname $dst) + fi + + log_must zfs set atime=on $(dirname $dst) + log_must check_atime_updated $mtpt/$TESTFILE + log_must check_atime_updated $mtpt/$TESTFILE + fi +done + +log_pass "Verify the property atime=on passed." diff --git a/tests/zfs-tests/tests/functional/atime/root_relatime_on.ksh b/tests/zfs-tests/tests/functional/atime/root_relatime_on.ksh new file mode 100755 index 000000000..c919e9f29 --- /dev/null +++ b/tests/zfs-tests/tests/functional/atime/root_relatime_on.ksh @@ -0,0 +1,76 @@ +#!/bin/ksh -p +# +# CDDL HEADER START +# +# The contents of this file are subject to the terms of the +# Common Development and Distribution License (the "License"). +# You may not use this file except in compliance with the License. +# +# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE +# or http://www.opensolaris.org/os/licensing. +# See the License for the specific language governing permissions +# and limitations under the License. +# +# When distributing Covered Code, include this CDDL HEADER in each +# file and include the License file at usr/src/OPENSOLARIS.LICENSE. +# If applicable, add the following below this CDDL HEADER, with the +# fields enclosed by brackets "[]" replaced with your own identifying +# information: Portions Copyright [yyyy] [name of copyright owner] +# +# CDDL HEADER END +# + +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +# Copyright (c) 2019 by Tomohiro Kusumi. All rights reserved. +# + +. $STF_SUITE/tests/functional/atime/atime_common.kshlib + +# +# DESCRIPTION: +# When relatime=on, verify the access time for files is updated when first +# read but not on second. +# It is available to fs and clone. To snapshot, it is unavailable. +# +# STRATEGY: +# 1. Create pool and fs. +# 2. Create '$TESTFILE' for fs. +# 3. Create snapshot and clone. +# 4. Setting atime=on and relatime=on on datasets. +# 5. Expect the access time is updated for first read but not on second. +# + +verify_runnable "both" + +log_assert "Setting relatime=on, the access time for files is updated when \ + when read the first time, but not second time." +log_onexit cleanup + +# +# Create $TESTFILE, snapshot and clone. +# Same as 003 except that atime/relatime applies to root dataset (ZoL#8675). +# +setup_snap_clone +reset_atime + +for dst in $TESTPOOL/$TESTFS $TESTPOOL/$TESTCLONE $TESTPOOL/$TESTFS@$TESTSNAP +do + typeset mtpt=$(get_prop mountpoint $dst) + + if [[ $dst == $TESTPOOL/$TESTFS@$TESTSNAP ]]; then + mtpt=$(snapshot_mountpoint $dst) + log_mustnot check_atime_updated $mtpt/$TESTFILE + else + log_must zfs set atime=on $(dirname $dst) + log_must zfs set relatime=on $(dirname $dst) + log_must check_atime_updated $mtpt/$TESTFILE + log_mustnot check_atime_updated $mtpt/$TESTFILE + fi +done + +log_pass "Verify the property relatime=on passed." diff --git a/tests/zfs-tests/tests/functional/atime/setup.ksh b/tests/zfs-tests/tests/functional/atime/setup.ksh index 0c5d38f19..3720e7521 100755 --- a/tests/zfs-tests/tests/functional/atime/setup.ksh +++ b/tests/zfs-tests/tests/functional/atime/setup.ksh @@ -28,4 +28,4 @@ . $STF_SUITE/include/libtest.shlib DISK=${DISKS%% *} -default_setup $DISK +default_setup_no_mountpoint $DISK