[PATCH 1/9] FS: make all ->follow_link handlers aware for LOOKUP_RCU

From: NeilBrown
Date: Thu Mar 05 2015 - 00:22:41 EST


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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/