In preparation for supporting ->follow_link in RCU-walk, make sure all ->follow_link handers which are not atomic will fail if LOOKUP_RCU is set. Later patches will make some of these handle LOOKUP_RCU more gracefully. This is current achieved by introducing a new function "nd_is_rcu" to check if a nameidata has LOOKUP_RCU set. There must be a better way. Signed-off-by: NeilBrown <neilb@xxxxxxx> --- drivers/staging/lustre/lustre/llite/symlink.c | 3 +++ fs/9p/vfs_inode.c | 6 +++++- fs/9p/vfs_inode_dotl.c | 5 ++++- fs/befs/linuxvfs.c | 2 ++ fs/cifs/link.c | 2 ++ fs/configfs/symlink.c | 7 ++++++- fs/ecryptfs/inode.c | 7 ++++++- fs/fuse/dir.c | 2 ++ fs/gfs2/inode.c | 2 ++ fs/hostfs/hostfs_kern.c | 7 ++++++- fs/kernfs/symlink.c | 7 ++++++- fs/namei.c | 8 ++++++++ fs/nfs/symlink.c | 2 ++ fs/overlayfs/inode.c | 3 +++ fs/proc/base.c | 2 ++ fs/proc/namespaces.c | 3 +++ fs/proc/self.c | 4 ++++ fs/proc/thread_self.c | 4 ++++ fs/xfs/xfs_iops.c | 2 ++ include/linux/fs.h | 1 + mm/shmem.c | 6 +++++- 21 files changed, 78 insertions(+), 7 deletions(-) diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c index 686b6a574cc5..d4ad3925f11f 100644 --- a/drivers/staging/lustre/lustre/llite/symlink.c +++ b/drivers/staging/lustre/lustre/llite/symlink.c @@ -125,6 +125,9 @@ static void *ll_follow_link(struct dentry *dentry, struct nameidata *nd) int rc; char *symname = NULL; + if (nd_is_rcu(nd)) + return PTR_ERR(-ECHILD); + CDEBUG(D_VFSTRACE, "VFS Op\n"); /* Limit the recursive symlink depth to 5 instead of default * 8 links when kernel has 4k stack to prevent stack overflow. diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 3662f1d1d9cf..8aff5d684154 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1281,7 +1281,11 @@ done: static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) { int len = 0; - char *link = __getname(); + char *link; + + if (nd_is_rcu(nd)) + 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 6054c16b8fae..51776a3cc842 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -914,9 +914,12 @@ v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct nameidata *nd) { int retval; struct p9_fid *fid; - char *link = __getname(); + char *link; char *target; + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + link = __getname(); p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); if (!link) { diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index e089f1985fca..bbe8f90924b2 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -477,6 +477,8 @@ befs_follow_link(struct dentry *dentry, struct nameidata *nd) befs_off_t len = data->size; char *link; + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); if (len == 0) { befs_error(sb, "Long symlink with illegal length"); link = ERR_PTR(-EIO); diff --git a/fs/cifs/link.c b/fs/cifs/link.c index 2ec6037f61c7..0dbe1a326632 100644 --- a/fs/cifs/link.c +++ b/fs/cifs/link.c @@ -639,6 +639,8 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd) struct cifs_tcon *tcon; struct TCP_Server_Info *server; + if (nd_is_rcu(nd)) + 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 cc9f2546ea4a..1397342aad5b 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -282,7 +282,12 @@ static int configfs_getlink(struct dentry *dentry, char * path) static void *configfs_follow_link(struct dentry *dentry, struct nameidata *nd) { int error = -ENOMEM; - unsigned long page = get_zeroed_page(GFP_KERNEL); + unsigned long page; + + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + + page = get_zeroed_page(GFP_KERNEL); if (page) { error = configfs_getlink(dentry, (char *)page); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index b08b5187f662..49d3dd96344c 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -678,7 +678,12 @@ out: static void *ecryptfs_follow_link(struct dentry *dentry, struct nameidata *nd) { size_t len; - char *buf = ecryptfs_readlink_lower(dentry, &len); + char *buf; + + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + + buf = ecryptfs_readlink_lower(dentry, &len); if (IS_ERR(buf)) goto out; fsstack_copy_attr_atime(dentry->d_inode, diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 1545b711ddcf..15d326ec5943 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1402,6 +1402,8 @@ static void free_link(char *link) static void *fuse_follow_link(struct dentry *dentry, struct nameidata *nd) { + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); nd_set_link(nd, read_link(dentry)); return NULL; } diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 73c72253faac..21086c7870f1 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1557,6 +1557,8 @@ static void *gfs2_follow_link(struct dentry *dentry, struct nameidata *nd) char *buf; int error; + if (nd_is_rcu(nd)) + 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 fd62cae0fdcb..374d04909538 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -884,7 +884,12 @@ static const struct inode_operations hostfs_dir_iops = { static void *hostfs_follow_link(struct dentry *dentry, struct nameidata *nd) { - char *link = __getname(); + char *link; + + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + + link = __getname(); if (link) { char *path = dentry_name(dentry); int err = -ENOMEM; diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 8a198898e39a..8e5421f386c0 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -115,7 +115,12 @@ static int kernfs_getlink(struct dentry *dentry, char *path) static void *kernfs_iop_follow_link(struct dentry *dentry, struct nameidata *nd) { int error = -ENOMEM; - unsigned long page = get_zeroed_page(GFP_KERNEL); + unsigned long page; + + if (nd_is_rcu(nd)) + 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 c83145af4bfc..de508a3cec42 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4475,6 +4475,8 @@ EXPORT_SYMBOL(page_readlink); void *page_follow_link_light(struct dentry *dentry, struct nameidata *nd) { struct page *page = NULL; + if (nd->flags & LOOKUP_RCU) + return ERR_PTR(-ECHILD); nd_set_link(nd, page_getlink(dentry, &page)); return page; } @@ -4542,3 +4544,9 @@ const struct inode_operations page_symlink_inode_operations = { .put_link = page_put_link, }; EXPORT_SYMBOL(page_symlink_inode_operations); + +int nd_is_rcu(struct nameidata *nd) +{ + return nd->flags & LOOKUP_RCU; +} +EXPORT_SYMBOL(nd_is_rcu); diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c index 05c9e02f4153..c9a2d3cc4619 100644 --- a/fs/nfs/symlink.c +++ b/fs/nfs/symlink.c @@ -49,6 +49,8 @@ static void *nfs_follow_link(struct dentry *dentry, struct nameidata *nd) struct page *page; void *err; + if (nd_is_rcu(nd)) + 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 04f124884687..db370d5d84c4 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" @@ -146,6 +147,8 @@ static void *ovl_follow_link(struct dentry *dentry, struct nameidata *nd) struct dentry *realdentry; struct inode *realinode; + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); realdentry = ovl_dentry_real(dentry); realinode = realdentry->d_inode; diff --git a/fs/proc/base.c b/fs/proc/base.c index 3f3d7aeb0712..6f5dbfe68516 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -1377,6 +1377,8 @@ static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd) struct path path; int error = -EACCES; + if (nd_is_rcu(nd)) + 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/namespaces.c b/fs/proc/namespaces.c index c9eac4563fa8..c89a51401bb5 100644 --- a/fs/proc/namespaces.c +++ b/fs/proc/namespaces.c @@ -38,6 +38,9 @@ static void *proc_ns_follow_link(struct dentry *dentry, struct nameidata *nd) struct path ns_path; void *error = ERR_PTR(-EACCES); + if (nd_is_rcu(nd)) + 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 4348bb8907c2..faf0cf8046f1 100644 --- a/fs/proc/self.c +++ b/fs/proc/self.c @@ -24,6 +24,10 @@ static void *proc_self_follow_link(struct dentry *dentry, struct nameidata *nd) struct pid_namespace *ns = dentry->d_sb->s_fs_info; pid_t tgid = task_tgid_nr_ns(current, ns); char *name = ERR_PTR(-ENOENT); + + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + 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 59075b509df3..954cf6699393 100644 --- a/fs/proc/thread_self.c +++ b/fs/proc/thread_self.c @@ -26,6 +26,10 @@ static void *proc_thread_self_follow_link(struct dentry *dentry, struct nameidat pid_t tgid = task_tgid_nr_ns(current, ns); pid_t pid = task_pid_nr_ns(current, ns); char *name = ERR_PTR(-ENOENT); + + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + if (pid) { name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL); if (!name) diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index e53a90331422..23cea798b777 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -417,6 +417,8 @@ xfs_vn_follow_link( char *link; int error = -ENOMEM; + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); link = kmalloc(MAXPATHLEN+1, GFP_KERNEL); if (!link) goto out_err; diff --git a/include/linux/fs.h b/include/linux/fs.h index b821fa32ba3f..eaef987ae3cf 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2167,6 +2167,7 @@ extern struct filename *getname_flags(const char __user *, int, int *); extern struct filename *getname(const char __user *); extern struct filename *getname_kernel(const char *); extern void putname(struct filename *name); +extern int nd_is_rcu(struct nameidata *nd); enum { FILE_CREATED = 1, diff --git a/mm/shmem.c b/mm/shmem.c index cf2d0ca010bc..fdf6ba18fce3 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -2483,7 +2483,11 @@ static void *shmem_follow_short_symlink(struct dentry *dentry, struct nameidata static void *shmem_follow_link(struct dentry *dentry, struct nameidata *nd) { struct page *page = NULL; - int error = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ, NULL); + int error; + + if (nd_is_rcu(nd)) + return ERR_PTR(-ECHILD); + error = shmem_getpage(dentry->d_inode, 0, &page, SGP_READ, NULL); nd_set_link(nd, error ? ERR_PTR(error) : kmap(page)); if (page) unlock_page(page); -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html