VFS/namei: make the use of touch_atime() in get_link() RCU-safe.
authorNeilBrown <neilb@suse.de>
Mon, 23 Mar 2015 02:37:40 +0000 (13:37 +1100)
committerAl Viro <viro@zeniv.linux.org.uk>
Fri, 15 May 2015 05:06:27 +0000 (01:06 -0400)
touch_atime is not RCU-safe, and so cannot be called on an RCU walk.
However, in situations where RCU-walk makes a difference, the symlink
will likely to accessed much more often than it is useful to update
the atime.

So split out the test of "Does the atime actually need to be updated"
into  atime_needs_update(), and have get_link() unlazy if it finds that
it will need to do that update.

Signed-off-by: NeilBrown <neilb@suse.de>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/inode.c
fs/namei.c
include/linux/fs.h

index 952fb4852e387d765d37bf3cf3305b905f8808a9..e8d62688ed9181e511e2a0e8c6a5f36840cdbe94 100644 (file)
@@ -1585,36 +1585,47 @@ static int update_time(struct inode *inode, struct timespec *time, int flags)
  *     This function automatically handles read only file systems and media,
  *     as well as the "noatime" flag and inode specific "noatime" markers.
  */
-void touch_atime(const struct path *path)
+bool atime_needs_update(const struct path *path, struct inode *inode)
 {
        struct vfsmount *mnt = path->mnt;
-       struct inode *inode = d_inode(path->dentry);
        struct timespec now;
 
        if (inode->i_flags & S_NOATIME)
-               return;
+               return false;
        if (IS_NOATIME(inode))
-               return;
+               return false;
        if ((inode->i_sb->s_flags & MS_NODIRATIME) && S_ISDIR(inode->i_mode))
-               return;
+               return false;
 
        if (mnt->mnt_flags & MNT_NOATIME)
-               return;
+               return false;
        if ((mnt->mnt_flags & MNT_NODIRATIME) && S_ISDIR(inode->i_mode))
-               return;
+               return false;
 
        now = current_fs_time(inode->i_sb);
 
        if (!relatime_need_update(mnt, inode, now))
-               return;
+               return false;
 
        if (timespec_equal(&inode->i_atime, &now))
+               return false;
+
+       return true;
+}
+
+void touch_atime(const struct path *path)
+{
+       struct vfsmount *mnt = path->mnt;
+       struct inode *inode = d_inode(path->dentry);
+       struct timespec now;
+
+       if (!atime_needs_update(path, inode))
                return;
 
        if (!sb_start_write_trylock(inode->i_sb))
                return;
 
-       if (__mnt_want_write(mnt))
+       if (__mnt_want_write(mnt) != 0)
                goto skip_update;
        /*
         * File systems can error out when updating inodes if they need to
@@ -1625,6 +1636,7 @@ void touch_atime(const struct path *path)
         * We may also fail on filesystems that have the ability to make parts
         * of the fs read only, e.g. subvolumes in Btrfs.
         */
+       now = current_fs_time(inode->i_sb);
        update_time(inode, &now, S_ATIME);
        __mnt_drop_write(mnt);
 skip_update:
index 47b20086e9f3401f0f01cae3b18efc74511344af..d9f77ff60b557de0edb575b760781bf1418fc20c 100644 (file)
@@ -966,13 +966,19 @@ const char *get_link(struct nameidata *nd)
        int error;
        const char *res;
 
-       if (nd->flags & LOOKUP_RCU) {
+       if (!(nd->flags & LOOKUP_RCU)) {
+               touch_atime(&last->link);
+               cond_resched();
+       } else if (atime_needs_update(&last->link, inode)) {
                if (unlikely(unlazy_walk(nd, NULL, 0)))
                        return ERR_PTR(-ECHILD);
+               touch_atime(&last->link);
        }
-       cond_resched();
 
-       touch_atime(&last->link);
+       if (nd->flags & LOOKUP_RCU) {
+               if (unlikely(unlazy_walk(nd, NULL, 0)))
+                       return ERR_PTR(-ECHILD);
+       }
 
        error = security_inode_follow_link(dentry, inode,
                                           nd->flags & LOOKUP_RCU);
index 8f738512c8746961b12edb46fb7060bc365b5b73..1426c435d45580fe6aae426ed01175743886ee6a 100644 (file)
@@ -1880,6 +1880,7 @@ enum file_time_flags {
        S_VERSION = 8,
 };
 
+extern bool atime_needs_update(const struct path *, struct inode *);
 extern void touch_atime(const struct path *);
 static inline void file_accessed(struct file *file)
 {