diff mbox

[08/20] VFS: make all ->follow_link handlers aware for LOOKUP_RCU

Message ID 20150323023739.8161.87072.stgit@notabene.brown (mailing list archive)
State New, archived
Headers show

Commit Message

NeilBrown March 23, 2015, 2:37 a.m. UTC
Pass a the inode explicit as an argument, as dentry->d_inode is not
stable during RCU-walk, and also a new 'flags' argument
which may (after further patches) contain LOOKUP_RCU.

->follow_link methods which cannot complete atomically
must return -ECHILD when LOOKUP_RCU is set.
Those which can complete atomically must use 'inode'
rather than 'dentry->d_inode', and must only reference data
structures that are freed using rcu_free().

Later patches will make some of these handle LOOKUP_RCU
more gracefully.

Signed-off-by: NeilBrown <neilb@suse.de>
---
 Documentation/filesystems/Locking             |    2 +-
 Documentation/filesystems/porting             |    9 +++++++++
 Documentation/filesystems/vfs.txt             |    2 +-
 drivers/staging/lustre/lustre/llite/symlink.c |    7 +++++--
 fs/9p/vfs_inode.c                             |   11 +++++++++--
 fs/9p/vfs_inode_dotl.c                        |   10 ++++++++--
 fs/autofs4/symlink.c                          |    6 ++++--
 fs/befs/linuxvfs.c                            |   15 +++++++++------
 fs/ceph/inode.c                               |    5 +++--
 fs/cifs/cifsfs.h                              |    3 ++-
 fs/cifs/link.c                                |    5 +++--
 fs/configfs/symlink.c                         |   10 ++++++++--
 fs/debugfs/file.c                             |    5 +++--
 fs/ecryptfs/inode.c                           |   12 +++++++++---
 fs/exofs/symlink.c                            |    6 +++---
 fs/ext2/symlink.c                             |    5 +++--
 fs/ext3/symlink.c                             |    5 +++--
 fs/ext4/symlink.c                             |    5 +++--
 fs/freevxfs/vxfs_immed.c                      |    9 ++++++---
 fs/fuse/dir.c                                 |    5 ++++-
 fs/gfs2/inode.c                               |    9 +++++++--
 fs/hostfs/hostfs_kern.c                       |   10 ++++++++--
 fs/hppfs/hppfs.c                              |    9 ++++++---
 fs/jffs2/symlink.c                            |    8 +++++---
 fs/jfs/symlink.c                              |    5 +++--
 fs/kernfs/symlink.c                           |   10 ++++++++--
 fs/namei.c                                    |   14 ++++++++++----
 fs/nfs/symlink.c                              |    6 ++++--
 fs/overlayfs/inode.c                          |    8 ++++++--
 fs/proc/base.c                                |    6 ++++--
 fs/proc/inode.c                               |    5 +++--
 fs/proc/namespaces.c                          |    7 +++++--
 fs/proc/self.c                                |   10 ++++++++--
 fs/proc/thread_self.c                         |   13 ++++++++++---
 fs/sysv/symlink.c                             |    5 +++--
 fs/ubifs/file.c                               |    5 +++--
 fs/ufs/symlink.c                              |    6 ++++--
 fs/xfs/xfs_iops.c                             |    8 ++++++--
 include/linux/fs.h                            |    4 ++--
 mm/shmem.c                                    |   14 ++++++++++----
 40 files changed, 211 insertions(+), 88 deletions(-)



--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking
index bbce4914d209..c0289bae848f 100644
--- a/Documentation/filesystems/Locking
+++ b/Documentation/filesystems/Locking
@@ -50,7 +50,7 @@  prototypes:
 	int (*rename2) (struct inode *, struct dentry *,
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
-	void * (*follow_link) (struct dentry *);
+	void * (*follow_link) (struct dentry *, struct inode *, int);
 	void (*put_link) (struct dentry *, char *, void *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, unsigned int);
diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting
index 9996b4631a87..eba8dd0a13e3 100644
--- a/Documentation/filesystems/porting
+++ b/Documentation/filesystems/porting
@@ -481,3 +481,12 @@  in your dentry operations instead.
 	->follow_link() no longer receives 'struct nameidata *'.
 	The nd is now attached to 'current' and nd_set_link()
 	accesses it directly.
+--
+[mandatory]
+	->follow_link now receives 'struct inode *' and 'int flags' which
+	may contain LOOKUP_RCU.  In this case -ECHILD must be
+	returned if the operation cannot be completed under
+	rcu_read_lock() conditions.
+	The passed inode must be used rather than dentry->d_inode,
+	particularly if LOOKUP_RCU is set.
+	If s_fs_info is used, it must be freed using RCU.
diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt
index 11aac530931b..5557e9283d04 100644
--- a/Documentation/filesystems/vfs.txt
+++ b/Documentation/filesystems/vfs.txt
@@ -350,7 +350,7 @@  struct inode_operations {
 	int (*rename2) (struct inode *, struct dentry *,
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
-        void * (*follow_link) (struct dentry *);
+        void * (*follow_link) (struct dentry *, struct inode *, int);
 	void (*put_link) (struct dentry *, char *, void *);
 	int (*permission) (struct inode *, int);
 	int (*get_acl)(struct inode *, int);
diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c
index 63dd1a925c92..44d095c68ce7 100644
--- a/drivers/staging/lustre/lustre/llite/symlink.c
+++ b/drivers/staging/lustre/lustre/llite/symlink.c
@@ -118,14 +118,17 @@  failed:
 	return rc;
 }
 
-static void *ll_follow_link(struct dentry *dentry)
+static void *ll_follow_link(struct dentry *dentry, struct inode *inode,
+			    int flags)
 {
 	unsigned long avail_space;
-	struct inode *inode = dentry->d_inode;
 	struct ptlrpc_request *request = NULL;
 	int rc;
 	char *symname = NULL;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
 	CDEBUG(D_VFSTRACE, "VFS Op\n");
 	/* Limit the recursive symlink depth.
 	 * Previously limited to 5 instead of default 8 links when
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index ebf50c3e132c..112091a186a1 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -1274,13 +1274,20 @@  done:
 /**
  * v9fs_vfs_follow_link - follow a symlink path
  * @dentry: dentry for symlink
+ * @inode:  inode for the symlink
+ * @flags: lookup flags
  *
  */
 
-static void *v9fs_vfs_follow_link(struct dentry *dentry)
+static void *v9fs_vfs_follow_link(struct dentry *dentry, struct inode *inode,
+				  int flags)
 {
 	int len = 0;
-	char *link = __getname();
+	char *link;
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+	link = __getname();
 
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index dc35156aea6a..3971e265f788 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -905,17 +905,23 @@  error:
 /**
  * v9fs_vfs_follow_link_dotl - follow a symlink path
  * @dentry: dentry for symlink
+ * @inode: inode for symlink
+ * @flags: lookup flags
  *
  */
 
 static void *
-v9fs_vfs_follow_link_dotl(struct dentry *dentry)
+v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct inode *inode,
+			  int flags)
 {
 	int retval;
 	struct p9_fid *fid;
-	char *link = __getname();
+	char *link;
 	char *target;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+	link = __getname();
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
 	if (!link) {
diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c
index 37b4b561faa3..e87885a6ef4e 100644
--- a/fs/autofs4/symlink.c
+++ b/fs/autofs4/symlink.c
@@ -12,13 +12,15 @@ 
 
 #include "autofs_i.h"
 
-static void *autofs4_follow_link(struct dentry *dentry)
+static void *autofs4_follow_link(struct dentry *dentry, struct inode *inode,
+				 int flags)
 {
 	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
 	struct autofs_info *ino = autofs4_dentry_ino(dentry);
+
 	if (ino && !autofs4_oz_mode(sbi))
 		ino->last_used = jiffies;
-	nd_set_link(dentry->d_inode->i_private);
+	nd_set_link(inode->i_private);
 	return NULL;
 }
 
diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c
index 339ac02c0e17..1151a6fbc74e 100644
--- a/fs/befs/linuxvfs.c
+++ b/fs/befs/linuxvfs.c
@@ -42,8 +42,8 @@  static struct inode *befs_iget(struct super_block *, unsigned long);
 static struct inode *befs_alloc_inode(struct super_block *sb);
 static void befs_destroy_inode(struct inode *inode);
 static void befs_destroy_inodecache(void);
-static void *befs_follow_link(struct dentry *);
-static void *befs_fast_follow_link(struct dentry *);
+static void *befs_follow_link(struct dentry *, struct inode *, int);
+static void *befs_fast_follow_link(struct dentry *, struct inode *, int);
 static int befs_utf2nls(struct super_block *sb, const char *in, int in_len,
 			char **out, int *out_len);
 static int befs_nls2utf(struct super_block *sb, const char *in, int in_len,
@@ -469,14 +469,17 @@  befs_destroy_inodecache(void)
  * flag is set.
  */
 static void *
-befs_follow_link(struct dentry *dentry)
+befs_follow_link(struct dentry *dentry, struct inode *inode, int flags)
 {
 	struct super_block *sb = dentry->d_sb;
-	befs_inode_info *befs_ino = BEFS_I(dentry->d_inode);
+	befs_inode_info *befs_ino = BEFS_I(inode);
 	befs_data_stream *data = &befs_ino->i_data.ds;
 	befs_off_t len = data->size;
 	char *link;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
 	if (len == 0) {
 		befs_error(sb, "Long symlink with illegal length");
 		link = ERR_PTR(-EIO);
@@ -500,9 +503,9 @@  befs_follow_link(struct dentry *dentry)
 
 
 static void *
-befs_fast_follow_link(struct dentry *dentry)
+befs_fast_follow_link(struct dentry *dentry, struct inode *inode, int flags)
 {
-	befs_inode_info *befs_ino = BEFS_I(dentry->d_inode);
+	befs_inode_info *befs_ino = BEFS_I(inode);
 	nd_set_link(befs_ino->i_data.symlink);
 	return NULL;
 }
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
index f8212d6945de..761b55e73491 100644
--- a/fs/ceph/inode.c
+++ b/fs/ceph/inode.c
@@ -1691,9 +1691,10 @@  retry:
 /*
  * symlinks
  */
-static void *ceph_sym_follow_link(struct dentry *dentry)
+static void *ceph_sym_follow_link(struct dentry *dentry, struct inode *inode,
+				  int flags)
 {
-	struct ceph_inode_info *ci = ceph_inode(dentry->d_inode);
+	struct ceph_inode_info *ci = ceph_inode(inode);
 	nd_set_link(ci->i_symlink);
 	return NULL;
 }
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index e3a6ef52a3e4..e58e685565d8 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -120,7 +120,8 @@  extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
 #endif
 
 /* Functions related to symlinks */
-extern void *cifs_follow_link(struct dentry *direntry);
+extern void *cifs_follow_link(struct dentry *direntry, struct inode *inode,
+			      int flags);
 extern int cifs_readlink(struct dentry *direntry, char __user *buffer,
 			 int buflen);
 extern int cifs_symlink(struct inode *inode, struct dentry *direntry,
diff --git a/fs/cifs/link.c b/fs/cifs/link.c
index ba3562198c33..0d3c14acbdfc 100644
--- a/fs/cifs/link.c
+++ b/fs/cifs/link.c
@@ -627,9 +627,8 @@  cifs_hl_exit:
 }
 
 void *
-cifs_follow_link(struct dentry *direntry)
+cifs_follow_link(struct dentry *direntry, struct inode *inode, int flags)
 {
-	struct inode *inode = direntry->d_inode;
 	int rc = -ENOMEM;
 	unsigned int xid;
 	char *full_path = NULL;
@@ -639,6 +638,8 @@  cifs_follow_link(struct dentry *direntry)
 	struct cifs_tcon *tcon;
 	struct TCP_Server_Info *server;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	xid = get_xid();
 
 	tlink = cifs_sb_tlink(cifs_sb);
diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c
index ff41712ffddd..443b11251b84 100644
--- a/fs/configfs/symlink.c
+++ b/fs/configfs/symlink.c
@@ -279,10 +279,16 @@  static int configfs_getlink(struct dentry *dentry, char * path)
 
 }
 
-static void *configfs_follow_link(struct dentry *dentry)
+static void *configfs_follow_link(struct dentry *dentry, struct inode *inode,
+				  int flags)
 {
 	int error = -ENOMEM;
-	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	unsigned long page;
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
+	page = get_zeroed_page(GFP_KERNEL);
 
 	if (page) {
 		error = configfs_getlink(dentry, (char *)page);
diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c
index eeed1f1fed4f..720dfb983b93 100644
--- a/fs/debugfs/file.c
+++ b/fs/debugfs/file.c
@@ -43,9 +43,10 @@  const struct file_operations debugfs_file_operations = {
 	.llseek =	noop_llseek,
 };
 
-static void *debugfs_follow_link(struct dentry *dentry)
+static void *debugfs_follow_link(struct dentry *dentry, struct inode *inode,
+				 int flags)
 {
-	nd_set_link(dentry->d_inode->i_private);
+	nd_set_link(inode->i_private);
 	return NULL;
 }
 
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
index 680cf30e9135..17c4321e6d40 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
@@ -673,13 +673,19 @@  out:
 	return rc ? ERR_PTR(rc) : buf;
 }
 
-static void *ecryptfs_follow_link(struct dentry *dentry)
+static void *ecryptfs_follow_link(struct dentry *dentry, struct inode *inode,
+				  int flags)
 {
 	size_t len;
-	char *buf = ecryptfs_readlink_lower(dentry, &len);
+	char *buf;
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
+	buf = ecryptfs_readlink_lower(dentry, &len);
 	if (IS_ERR(buf))
 		goto out;
-	fsstack_copy_attr_atime(dentry->d_inode,
+	fsstack_copy_attr_atime(inode,
 				ecryptfs_dentry_to_lower(dentry)->d_inode);
 	buf[len] = '\0';
 out:
diff --git a/fs/exofs/symlink.c b/fs/exofs/symlink.c
index e6d0467a0b5a..c8525b051811 100644
--- a/fs/exofs/symlink.c
+++ b/fs/exofs/symlink.c
@@ -35,10 +35,10 @@ 
 
 #include "exofs.h"
 
-static void *exofs_follow_link(struct dentry *dentry)
+static void *exofs_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags)
 {
-	struct exofs_i_info *oi = exofs_i(dentry->d_inode);
-
+	struct exofs_i_info *oi = exofs_i(inode);
 	nd_set_link((char *)oi->i_data);
 	return NULL;
 }
diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c
index 063852432bd4..eb1820a875c9 100644
--- a/fs/ext2/symlink.c
+++ b/fs/ext2/symlink.c
@@ -21,9 +21,10 @@ 
 #include "xattr.h"
 #include <linux/namei.h>
 
-static void *ext2_follow_link(struct dentry *dentry)
+static void *ext2_follow_link(struct dentry *dentry, struct inode *inode,
+			      int flags)
 {
-	struct ext2_inode_info *ei = EXT2_I(dentry->d_inode);
+	struct ext2_inode_info *ei = EXT2_I(inode);
 	nd_set_link((char *)ei->i_data);
 	return NULL;
 }
diff --git a/fs/ext3/symlink.c b/fs/ext3/symlink.c
index bf8acd9efaae..c048fedc13c4 100644
--- a/fs/ext3/symlink.c
+++ b/fs/ext3/symlink.c
@@ -21,9 +21,10 @@ 
 #include "ext3.h"
 #include "xattr.h"
 
-static void * ext3_follow_link(struct dentry *dentry)
+static void * ext3_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags)
 {
-	struct ext3_inode_info *ei = EXT3_I(dentry->d_inode);
+	struct ext3_inode_info *ei = EXT3_I(inode);
 	nd_set_link((char*)ei->i_data);
 	return NULL;
 }
diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c
index 0015e7f53d0f..da0790514769 100644
--- a/fs/ext4/symlink.c
+++ b/fs/ext4/symlink.c
@@ -23,9 +23,10 @@ 
 #include "ext4.h"
 #include "xattr.h"
 
-static void *ext4_follow_link(struct dentry *dentry)
+static void *ext4_follow_link(struct dentry *dentry, struct inode *inode,
+			      int flags)
 {
-	struct ext4_inode_info *ei = EXT4_I(dentry->d_inode);
+	struct ext4_inode_info *ei = EXT4_I(inode);
 	nd_set_link((char *) ei->i_data);
 	return NULL;
 }
diff --git a/fs/freevxfs/vxfs_immed.c b/fs/freevxfs/vxfs_immed.c
index 058acefeb11c..bc8f83f1ff7d 100644
--- a/fs/freevxfs/vxfs_immed.c
+++ b/fs/freevxfs/vxfs_immed.c
@@ -39,7 +39,7 @@ 
 #include "vxfs_inode.h"
 
 
-static void *	vxfs_immed_follow_link(struct dentry *);
+static void *	vxfs_immed_follow_link(struct dentry *, struct inode *, int);
 
 static int	vxfs_immed_readpage(struct file *, struct page *);
 
@@ -64,6 +64,8 @@  const struct address_space_operations vxfs_immed_aops = {
 /**
  * vxfs_immed_follow_link - follow immed symlink
  * @dp:		dentry for the link
+ * @inode:	inode for the link
+ * @flags:	lookup flags
  *
  * Description:
  *   vxfs_immed_follow_link restarts the pathname lookup with
@@ -73,9 +75,10 @@  const struct address_space_operations vxfs_immed_aops = {
  *   Zero on success, else a negative error code.
  */
 static void *
-vxfs_immed_follow_link(struct dentry *dp)
+vxfs_immed_follow_link(struct dentry *dp, struct inode *inode,
+		       int flags)
 {
-	struct vxfs_inode_info		*vip = VXFS_INO(dp->d_inode);
+	struct vxfs_inode_info		*vip = VXFS_INO(inode);
 	nd_set_link(vip->vii_immed.vi_immed);
 	return NULL;
 }
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 58e632be862b..e950b3c774c5 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1400,8 +1400,11 @@  static void free_link(char *link)
 		free_page((unsigned long) link);
 }
 
-static void *fuse_follow_link(struct dentry *dentry)
+static void *fuse_follow_link(struct dentry *dentry, struct inode *inode,
+			      int flags)
 {
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	nd_set_link(read_link(dentry));
 	return NULL;
 }
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 5b28009ea860..2e4caa76bf97 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1541,21 +1541,26 @@  out:
 /**
  * gfs2_follow_link - Follow a symbolic link
  * @dentry: The dentry of the link
+ * @inode: The inode of the link
+ * @flags: Lookup flags
  *
  * This can handle symlinks of any size.
  *
  * Returns: 0 on success or error code
  */
 
-static void *gfs2_follow_link(struct dentry *dentry)
+static void *gfs2_follow_link(struct dentry *dentry, struct inode *inode,
+			      int flags)
 {
-	struct gfs2_inode *ip = GFS2_I(dentry->d_inode);
+	struct gfs2_inode *ip = GFS2_I(inode);
 	struct gfs2_holder i_gh;
 	struct buffer_head *dibh;
 	unsigned int size;
 	char *buf;
 	int error;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	gfs2_holder_init(ip->i_gl, LM_ST_SHARED, 0, &i_gh);
 	error = gfs2_glock_nq(&i_gh);
 	if (error) {
diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c
index a862634bf98e..f40966f9fdaf 100644
--- a/fs/hostfs/hostfs_kern.c
+++ b/fs/hostfs/hostfs_kern.c
@@ -882,9 +882,15 @@  static const struct inode_operations hostfs_dir_iops = {
 	.setattr	= hostfs_setattr,
 };
 
-static void *hostfs_follow_link(struct dentry *dentry)
+static void *hostfs_follow_link(struct dentry *dentry, struct inode *inode,
+				int flags)
 {
-	char *link = __getname();
+	char *link;
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
+	link = __getname();
 	if (link) {
 		char *path = dentry_name(dentry);
 		int err = -ENOMEM;
diff --git a/fs/hppfs/hppfs.c b/fs/hppfs/hppfs.c
index bf13cf24eec7..b6f68e22d0fb 100644
--- a/fs/hppfs/hppfs.c
+++ b/fs/hppfs/hppfs.c
@@ -642,11 +642,14 @@  static int hppfs_readlink(struct dentry *dentry, char __user *buffer,
 						    buflen);
 }
 
-static void *hppfs_follow_link(struct dentry *dentry)
+static void *hppfs_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags)
 {
-	struct dentry *proc_dentry = HPPFS_I(dentry->d_inode)->proc_dentry;
+	struct dentry *proc_dentry = HPPFS_I(inode)->proc_dentry;
 
-	return proc_dentry->d_inode->i_op->follow_link(proc_dentry);
+	return proc_dentry->d_inode->i_op->follow_link(proc_dentry,
+						       proc_dentry->d_inode,
+						       flags);
 }
 
 static void hppfs_put_link(struct dentry *dentry, char *link,
diff --git a/fs/jffs2/symlink.c b/fs/jffs2/symlink.c
index 6b58d7659fbd..18c00fbd7060 100644
--- a/fs/jffs2/symlink.c
+++ b/fs/jffs2/symlink.c
@@ -16,7 +16,8 @@ 
 #include <linux/namei.h>
 #include "nodelist.h"
 
-static void *jffs2_follow_link(struct dentry *dentry);
+static void *jffs2_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags);
 
 const struct inode_operations jffs2_symlink_inode_operations =
 {
@@ -29,9 +30,10 @@  const struct inode_operations jffs2_symlink_inode_operations =
 	.removexattr =	jffs2_removexattr
 };
 
-static void *jffs2_follow_link(struct dentry *dentry)
+static void *jffs2_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags)
 {
-	struct jffs2_inode_info *f = JFFS2_INODE_INFO(dentry->d_inode);
+	struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode);
 	char *p = (char *)f->target;
 
 	/*
diff --git a/fs/jfs/symlink.c b/fs/jfs/symlink.c
index cefda45b8d3a..610f44fad05e 100644
--- a/fs/jfs/symlink.c
+++ b/fs/jfs/symlink.c
@@ -22,9 +22,10 @@ 
 #include "jfs_inode.h"
 #include "jfs_xattr.h"
 
-static void *jfs_follow_link(struct dentry *dentry)
+static void *jfs_follow_link(struct dentry *dentry, struct inode *inode,
+			     int flags)
 {
-	char *s = JFS_IP(dentry->d_inode)->i_inline;
+	char *s = JFS_IP(inode)->i_inline;
 	nd_set_link(s);
 	return NULL;
 }
diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c
index 63d08ecbb72c..1a40d5e6ac71 100644
--- a/fs/kernfs/symlink.c
+++ b/fs/kernfs/symlink.c
@@ -112,10 +112,16 @@  static int kernfs_getlink(struct dentry *dentry, char *path)
 	return error;
 }
 
-static void *kernfs_iop_follow_link(struct dentry *dentry)
+static void *kernfs_iop_follow_link(struct dentry *dentry, struct inode *inode,
+				    int flags)
 {
 	int error = -ENOMEM;
-	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	unsigned long page;
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
+	page = get_zeroed_page(GFP_KERNEL);
 	if (page) {
 		error = kernfs_getlink(dentry, (char *) page);
 		if (error < 0)
diff --git a/fs/namei.c b/fs/namei.c
index e7fab6886e29..784fca0e6c70 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -876,6 +876,7 @@  static __always_inline int
 follow_link(struct path *link, struct nameidata *nd, void **p)
 {
 	struct dentry *dentry = link->dentry;
+	struct inode *inode = dentry->d_inode;
 	int error;
 	char *s;
 
@@ -894,12 +895,13 @@  follow_link(struct path *link, struct nameidata *nd, void **p)
 	touch_atime(link);
 	nd_set_link(NULL);
 
-	error = security_inode_follow_link(link->dentry);
+	error = security_inode_follow_link(dentry);
 	if (error)
 		goto out_put_nd_path;
 
 	nd->last_type = LAST_BIND;
-	*p = dentry->d_inode->i_op->follow_link(dentry);
+	*p = inode->i_op->follow_link(dentry, inode,
+				      nd->flags & LOOKUP_RCU);
 	error = PTR_ERR(*p);
 	if (IS_ERR(*p))
 		goto out_put_nd_path;
@@ -4464,7 +4466,8 @@  int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 	int res;
 
 	nd.depth = 0;
-	cookie = dentry->d_inode->i_op->follow_link(dentry);
+	cookie = dentry->d_inode->i_op->follow_link(dentry,
+						    dentry->d_inode, 0);
 	if (IS_ERR(cookie))
 		res = PTR_ERR(cookie);
 	else {
@@ -4506,9 +4509,12 @@  int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 }
 EXPORT_SYMBOL(page_readlink);
 
-void *page_follow_link_light(struct dentry *dentry)
+void *page_follow_link_light(struct dentry *dentry, struct inode *inode,
+			     int flags)
 {
 	struct page *page = NULL;
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	nd_set_link(page_getlink(dentry, &page));
 	return page;
 }
diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c
index f3c44616b615..32bbac1bb4bc 100644
--- a/fs/nfs/symlink.c
+++ b/fs/nfs/symlink.c
@@ -43,12 +43,14 @@  error:
 	return -EIO;
 }
 
-static void *nfs_follow_link(struct dentry *dentry)
+static void *nfs_follow_link(struct dentry *dentry, struct inode *inode,
+			     int flags)
 {
-	struct inode *inode = dentry->d_inode;
 	struct page *page;
 	void *err;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping));
 	if (err)
 		goto read_failed;
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index 0de7b87bd025..675efccd5c84 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -8,6 +8,7 @@ 
  */
 
 #include <linux/fs.h>
+#include <linux/namei.h>
 #include <linux/slab.h>
 #include <linux/xattr.h>
 #include "overlayfs.h"
@@ -140,13 +141,16 @@  struct ovl_link_data {
 	void *cookie;
 };
 
-static void *ovl_follow_link(struct dentry *dentry)
+static void *ovl_follow_link(struct dentry *dentry, struct inode *inode,
+			     int flags)
 {
 	void *ret;
 	struct dentry *realdentry;
 	struct inode *realinode;
 	struct ovl_link_data *data = NULL;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	realdentry = ovl_dentry_real(dentry);
 	realinode = realdentry->d_inode;
 
@@ -160,7 +164,7 @@  static void *ovl_follow_link(struct dentry *dentry)
 		data->realdentry = realdentry;
 	}
 
-	ret = realinode->i_op->follow_link(realdentry);
+	ret = realinode->i_op->follow_link(realdentry, realinode, flags);
 	if (IS_ERR(ret)) {
 		kfree(data);
 		return ret;
diff --git a/fs/proc/base.c b/fs/proc/base.c
index a0c0b85aead3..203f8d8ceab1 100644
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -1371,12 +1371,14 @@  static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
 		return -ENOENT;
 }
 
-static void *proc_pid_follow_link(struct dentry *dentry)
+static void *proc_pid_follow_link(struct dentry *dentry, struct inode *inode,
+				  int flags)
 {
-	struct inode *inode = dentry->d_inode;
 	struct path path;
 	int error = -EACCES;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	/* Are we allowed to snoop on the tasks file descriptors? */
 	if (!proc_fd_access_allowed(inode))
 		goto out;
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 7bdaf1040f98..faf2c5400437 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -394,9 +394,10 @@  static const struct file_operations proc_reg_file_ops_no_compat = {
 };
 #endif
 
-static void *proc_follow_link(struct dentry *dentry)
+static void *proc_follow_link(struct dentry *dentry, struct inode *inode,
+			      int flags)
 {
-	struct proc_dir_entry *pde = PDE(dentry->d_inode);
+	struct proc_dir_entry *pde = PDE(inode);
 	if (unlikely(!use_pde(pde)))
 		return ERR_PTR(-EINVAL);
 	nd_set_link(pde->data);
diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c
index 5e3394509c2c..a2578c44edeb 100644
--- a/fs/proc/namespaces.c
+++ b/fs/proc/namespaces.c
@@ -30,14 +30,17 @@  static const struct proc_ns_operations *ns_entries[] = {
 	&mntns_operations,
 };
 
-static void *proc_ns_follow_link(struct dentry *dentry)
+static void *proc_ns_follow_link(struct dentry *dentry, struct inode *inode,
+				 int flags)
 {
-	struct inode *inode = dentry->d_inode;
 	const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
 	struct task_struct *task;
 	struct path ns_path;
 	void *error = ERR_PTR(-EACCES);
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
 	task = get_proc_task(inode);
 	if (!task)
 		return error;
diff --git a/fs/proc/self.c b/fs/proc/self.c
index 639bd0afdc05..7fcb906c250a 100644
--- a/fs/proc/self.c
+++ b/fs/proc/self.c
@@ -19,11 +19,17 @@  static int proc_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static void *proc_self_follow_link(struct dentry *dentry)
+static void *proc_self_follow_link(struct dentry *dentry, struct inode *inode,
+				   int flags)
 {
 	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
-	pid_t tgid = task_tgid_nr_ns(current, ns);
+	pid_t tgid;
 	char *name = ERR_PTR(-ENOENT);
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
+	tgid = task_tgid_nr_ns(current, ns);
 	if (tgid) {
 		/* 11 for max length of signed int in decimal + NULL term */
 		name = kmalloc(12, GFP_KERNEL);
diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c
index 2036b051f53f..7a9af8a6baab 100644
--- a/fs/proc/thread_self.c
+++ b/fs/proc/thread_self.c
@@ -20,12 +20,19 @@  static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static void *proc_thread_self_follow_link(struct dentry *dentry)
+static void *proc_thread_self_follow_link(struct dentry *dentry,
+					  struct inode *inode, int flags)
 {
 	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
-	pid_t tgid = task_tgid_nr_ns(current, ns);
-	pid_t pid = task_pid_nr_ns(current, ns);
+	pid_t tgid;
+	pid_t pid;
 	char *name = ERR_PTR(-ENOENT);
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+
+	tgid = task_tgid_nr_ns(current, ns);
+	pid = task_pid_nr_ns(current, ns);
 	if (pid) {
 		name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
 		if (!name)
diff --git a/fs/sysv/symlink.c b/fs/sysv/symlink.c
index 3f8154e6b27e..bdddd74831ac 100644
--- a/fs/sysv/symlink.c
+++ b/fs/sysv/symlink.c
@@ -8,9 +8,10 @@ 
 #include "sysv.h"
 #include <linux/namei.h>
 
-static void *sysv_follow_link(struct dentry *dentry)
+static void *sysv_follow_link(struct dentry *dentry, struct inode *inode,
+			      int flags)
 {
-	nd_set_link((char *)SYSV_I(dentry->d_inode)->i_data);
+	nd_set_link((char *)SYSV_I(inode)->i_data);
 	return NULL;
 }
 
diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c
index 082958d62096..4872f14a88c1 100644
--- a/fs/ubifs/file.c
+++ b/fs/ubifs/file.c
@@ -1300,9 +1300,10 @@  static void ubifs_invalidatepage(struct page *page, unsigned int offset,
 	ClearPageChecked(page);
 }
 
-static void *ubifs_follow_link(struct dentry *dentry)
+static void *ubifs_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags)
 {
-	struct ubifs_inode *ui = ubifs_inode(dentry->d_inode);
+	struct ubifs_inode *ui = ubifs_inode(inode);
 
 	nd_set_link(ui->data);
 	return NULL;
diff --git a/fs/ufs/symlink.c b/fs/ufs/symlink.c
index a8266ff60b0f..3b690a020627 100644
--- a/fs/ufs/symlink.c
+++ b/fs/ufs/symlink.c
@@ -32,9 +32,11 @@ 
 #include "ufs.h"
 
 
-static void *ufs_follow_link(struct dentry *dentry)
+static void *ufs_follow_link(struct dentry *dentry, struct inode *inode,
+			     int flags)
 {
-	struct ufs_inode_info *p = UFS_I(dentry->d_inode);
+	struct ufs_inode_info *p = UFS_I(inode);
+
 	nd_set_link((char*)p->i_u1.i_symlink);
 	return NULL;
 }
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index ac915d09de29..c2c136ef3a50 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -411,16 +411,20 @@  xfs_vn_rename(
  */
 STATIC void *
 xfs_vn_follow_link(
-	struct dentry		*dentry)
+	struct dentry		*dentry,
+	struct inode		*inode,
+	int			flags)
 {
 	char			*link;
 	int			error = -ENOMEM;
 
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
 	link = kmalloc(MAXPATHLEN+1, GFP_KERNEL);
 	if (!link)
 		goto out_err;
 
-	error = xfs_readlink(XFS_I(dentry->d_inode), link);
+	error = xfs_readlink(XFS_I(inode), link);
 	if (unlikely(error))
 		goto out_kfree;
 
diff --git a/include/linux/fs.h b/include/linux/fs.h
index d78dd3ae1be9..dda92ac8ef41 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1573,7 +1573,7 @@  struct file_operations {
 
 struct inode_operations {
 	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
-	void * (*follow_link) (struct dentry *);
+	void * (*follow_link) (struct dentry *, struct inode *, int);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
@@ -2648,7 +2648,7 @@  extern const struct file_operations generic_ro_fops;
 
 extern int readlink_copy(char __user *, int, const char *);
 extern int page_readlink(struct dentry *, char __user *, int);
-extern void *page_follow_link_light(struct dentry *);
+extern void *page_follow_link_light(struct dentry *, struct inode *, int);
 extern void page_put_link(struct dentry *, char *, void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
diff --git a/mm/shmem.c b/mm/shmem.c
index 910b37f44a2b..1083e8c75536 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2474,16 +2474,22 @@  static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	return 0;
 }
 
-static void *shmem_follow_short_symlink(struct dentry *dentry)
+static void *shmem_follow_short_symlink(struct dentry *dentry,
+					struct inode *inode, int flags)
 {
-	nd_set_link(SHMEM_I(dentry->d_inode)->symlink);
+	nd_set_link(SHMEM_I(inode)->symlink);
 	return NULL;
 }
 
-static void *shmem_follow_link(struct dentry *dentry)
+static void *shmem_follow_link(struct dentry *dentry, struct inode *inode,
+			       int flags)
 {
 	struct page *page = NULL;
-	int error = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ, NULL);
+	int error;
+
+	if (flags & LOOKUP_RCU)
+		return ERR_PTR(-ECHILD);
+	error = shmem_getpage(inode, 0, &page, SGP_READ, NULL);
 	nd_set_link(error ? ERR_PTR(error) : kmap(page));
 	if (page)
 		unlock_page(page);