Skip to contentSkip to conversation

non-recursive pathname resolution

RFC PATCH Superseded Al Viro 11 participants 299 messages May 5, 2015 – May 19, 2015 1 message not in the archive Page 2 of 3

Subsystems (29)
lore
Share & tools
lore
author maintainer
#136 lore raw

PATCH v3 29/110 new ->follow_link() and ->put_link() calling conventions

In reply to Al Viro

From: Al Viro <[email protected]>

a) instead of storing the symlink body (via nd_set_link()) and returning an opaque pointer later passed to ->put_link(), ->follow_link() _stores_ that opaque pointer (into void * passed by address by caller) and returns the symlink body. Returning ERR_PTR() on error, NULL on jump (procfs magic symlinks) and pointer to symlink body for normal symlinks. Stored pointer is ignored in all cases except the last one.

Storing NULL for opaque pointer (or not storing it at all) means no call of ->put_link().

b) the body used to be passed to ->put_link() implicitly (via nameidata). Now only the opaque pointer is. In the cases when we used the symlink body to free stuff, ->follow_link() now should store it as opaque pointer in addition to returning it.

---
 Documentation/filesystems/Locking             |  4 +-
 Documentation/filesystems/vfs.txt             |  4 +-
 drivers/staging/lustre/lustre/llite/symlink.c | 11 ++---
 fs/9p/vfs_inode.c                             | 13 +++---
 fs/9p/vfs_inode_dotl.c                        |  7 ++-
 fs/autofs4/symlink.c                          |  5 +-
 fs/befs/linuxvfs.c                            | 35 +++++++-------
 fs/cifs/cifsfs.h                              |  2 +-
 fs/cifs/link.c                                | 28 ++++++------
 fs/configfs/symlink.c                         | 28 +++++-------
 fs/ecryptfs/inode.c                           |  8 ++--
 fs/ext4/symlink.c                             |  9 ++--
 fs/f2fs/namei.c                               | 18 +++-----
 fs/fuse/dir.c                                 | 19 ++------
 fs/gfs2/inode.c                               | 10 ++--
 fs/hostfs/hostfs_kern.c                       | 15 +++---
 fs/hppfs/hppfs.c                              |  9 ++--
 fs/kernfs/symlink.c                           | 22 ++++-----
 fs/libfs.c                                    | 12 ++---
 fs/namei.c                                    | 66 +++++++++------------------
 fs/nfs/symlink.c                              | 19 +++-----
 fs/overlayfs/inode.c                          | 18 ++++----
 fs/proc/base.c                                |  2 +-
 fs/proc/inode.c                               |  9 ++--
 fs/proc/namespaces.c                          |  2 +-
 fs/proc/self.c                                | 24 +++++-----
 fs/proc/thread_self.c                         | 22 ++++-----
 fs/xfs/xfs_iops.c                             | 10 ++--
 include/linux/fs.h                            | 12 ++---
 include/linux/namei.h                         |  2 -
 mm/shmem.c                                    | 23 +++++-----
 31 files changed, 195 insertions(+), 273 deletions(-)
Documentation/filesystems/Locking +2 -2
index 0a926e2..7fa6c4a 100644
@@ -50,8 +50,8 @@ 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 *, struct nameidata *);
-	void (*put_link) (struct dentry *, struct nameidata *, void *);
+	const char *(*follow_link) (struct dentry *, void **, struct nameidata *);
+	void (*put_link) (struct dentry *, void *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, unsigned int);
 	int (*get_acl)(struct inode *, int);
Documentation/filesystems/vfs.txt +2 -2
index 5d833b3..1c6b03a 100644
@@ -350,8 +350,8 @@ 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 *, struct nameidata *);
-        void (*put_link) (struct dentry *, struct nameidata *, void *);
+	const char *(*follow_link) (struct dentry *, void **, struct nameidata *);
+	void (*put_link) (struct dentry *, void *);
 	int (*permission) (struct inode *, int);
 	int (*get_acl)(struct inode *, int);
 	int (*setattr) (struct dentry *, struct iattr *);
drivers/staging/lustre/lustre/llite/symlink.c +5 -6
index 3711e67..e488cb3 100644
@@ -118,7 +118,7 @@ failed:
 	return rc;
 }
 
-static void *ll_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *ll_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct inode *inode = d_inode(dentry);
 	struct ptlrpc_request *request = NULL;
@@ -140,18 +140,17 @@ static void *ll_follow_link(struct dentry *dentry, struct nameidata *nd)
 	}
 	if (rc) {
 		ptlrpc_req_finished(request);
-		request = NULL;
-		symname = ERR_PTR(rc);
+		return ERR_PTR(rc);
 	}
 
-	nd_set_link(nd, symname);
 	/* symname may contain a pointer to the request message buffer,
 	 * we delay request releasing until ll_put_link then.
 	 */
-	return request;
+	*cookie = request;
+	return symname;
 }
 
-static void ll_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
+static void ll_put_link(struct dentry *dentry, void *cookie)
 {
 	ptlrpc_req_finished(cookie);
 }
fs/9p/vfs_inode.c +7 -6
index 0ba1171..7cc70a3 100644
@@ -1230,11 +1230,12 @@ ino_t v9fs_qid2ino(struct p9_qid *qid)
  *
  */
 
-static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *v9fs_vfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry);
 	struct p9_fid *fid = v9fs_fid_lookup(dentry);
 	struct p9_wstat *st;
+	char *res;
 
 	p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
 
@@ -1253,14 +1254,14 @@ static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd)
 		kfree(st);
 		return ERR_PTR(-EINVAL);
 	}
-	if (strlen(st->extension) >= PATH_MAX)
-		st->extension[PATH_MAX - 1] = '\0';
-
-	nd_set_link(nd, st->extension);
+	res = st->extension;
 	st->extension = NULL;
+	if (strlen(res) >= PATH_MAX)
+		res[PATH_MAX - 1] = '\0';
+
 	p9stat_free(st);
 	kfree(st);
-	return NULL;
+	return *cookie = res;
 }
 
 /**
fs/9p/vfs_inode_dotl.c +3 -4
index bc2a91f..ae062ff 100644
@@ -909,8 +909,8 @@ error:
  *
  */
 
-static void *
-v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct nameidata *nd)
+static const char *
+v9fs_vfs_follow_link_dotl(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct p9_fid *fid = v9fs_fid_lookup(dentry);
 	char *target;
@@ -923,8 +923,7 @@ v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct nameidata *nd)
 	retval = p9_client_readlink(fid, &target);
 	if (retval)
 		return ERR_PTR(retval);
-	nd_set_link(nd, target);
-	return NULL;
+	return *cookie = target;
 }
 
 int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode)
fs/autofs4/symlink.c +2 -3
index de58cc7..9c6a077 100644
@@ -12,14 +12,13 @@
 
 #include "autofs_i.h"
 
-static void *autofs4_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *autofs4_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	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(nd, d_inode(dentry)->i_private);
-	return NULL;
+	return d_inode(dentry)->i_private;
 }
 
 const struct inode_operations autofs4_symlink_inode_operations = {
fs/befs/linuxvfs.c +16 -19
index 172e306..3a1aefb 100644
@@ -42,7 +42,7 @@ 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 *, struct nameidata *);
+static const char *befs_follow_link(struct dentry *, void **, struct nameidata *nd);
 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,
@@ -463,8 +463,8 @@ befs_destroy_inodecache(void)
  * The data stream become link name. Unless the LONG_SYMLINK
  * flag is set.
  */
-static void *
-befs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *
+befs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct super_block *sb = dentry->d_sb;
 	struct befs_inode_info *befs_ino = BEFS_I(d_inode(dentry));
@@ -474,23 +474,20 @@ befs_follow_link(struct dentry *dentry, struct nameidata *nd)
 
 	if (len == 0) {
 		befs_error(sb, "Long symlink with illegal length");
-		link = ERR_PTR(-EIO);
-	} else {
-		befs_debug(sb, "Follow long symlink");
-
-		link = kmalloc(len, GFP_NOFS);
-		if (!link) {
-			link = ERR_PTR(-ENOMEM);
-		} else if (befs_read_lsymlink(sb, data, link, len) != len) {
-			kfree(link);
-			befs_error(sb, "Failed to read entire long symlink");
-			link = ERR_PTR(-EIO);
-		} else {
-			link[len - 1] = '\0';
-		}
+		return ERR_PTR(-EIO);
 	}
-	nd_set_link(nd, link);
-	return NULL;
+	befs_debug(sb, "Follow long symlink");
+
+	link = kmalloc(len, GFP_NOFS);
+	if (!link)
+		return ERR_PTR(-ENOMEM);
+	if (befs_read_lsymlink(sb, data, link, len) != len) {
+		kfree(link);
+		befs_error(sb, "Failed to read entire long symlink");
+		return ERR_PTR(-EIO);
+	}
+	link[len - 1] = '\0';
+	return *cookie = link;
 }
 
 /*
fs/cifs/cifsfs.h +1 -1
index 252f5c1..61012da 100644
@@ -120,7 +120,7 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
 #endif
 
 /* Functions related to symlinks */
-extern void *cifs_follow_link(struct dentry *direntry, struct nameidata *nd);
+extern const char *cifs_follow_link(struct dentry *direntry, void **cookie, struct nameidata *nd);
 extern int cifs_readlink(struct dentry *direntry, char __user *buffer,
 			 int buflen);
 extern int cifs_symlink(struct inode *inode, struct dentry *direntry,
fs/cifs/link.c +13 -15
index 252e672..4a439c2 100644
@@ -626,8 +626,8 @@ cifs_hl_exit:
 	return rc;
 }
 
-void *
-cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
+const char *
+cifs_follow_link(struct dentry *direntry, void **cookie, struct nameidata *nd)
 {
 	struct inode *inode = d_inode(direntry);
 	int rc = -ENOMEM;
@@ -643,16 +643,18 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
 
 	tlink = cifs_sb_tlink(cifs_sb);
 	if (IS_ERR(tlink)) {
-		rc = PTR_ERR(tlink);
-		tlink = NULL;
-		goto out;
+		free_xid(xid);
+		return ERR_CAST(tlink);
 	}
 	tcon = tlink_tcon(tlink);
 	server = tcon->ses->server;
 
 	full_path = build_path_from_dentry(direntry);
-	if (!full_path)
-		goto out;
+	if (!full_path) {
+		free_xid(xid);
+		cifs_put_tlink(tlink);
+		return ERR_PTR(-ENOMEM);
+	}
 
 	cifs_dbg(FYI, "Full path: %s inode = 0x%p\n", full_path, inode);
 
@@ -670,17 +672,13 @@ cifs_follow_link(struct dentry *direntry, struct nameidata *nd)
 						&target_path, cifs_sb);
 
 	kfree(full_path);
-out:
+	free_xid(xid);
+	cifs_put_tlink(tlink);
 	if (rc != 0) {
 		kfree(target_path);
-		target_path = ERR_PTR(rc);
+		return ERR_PTR(rc);
 	}
-
-	free_xid(xid);
-	if (tlink)
-		cifs_put_tlink(tlink);
-	nd_set_link(nd, target_path);
-	return NULL;
+	return *cookie = target_path;
 }
 
 int
fs/configfs/symlink.c +12 -16
index cc9f254..fac8e85 100644
@@ -279,30 +279,26 @@ static int configfs_getlink(struct dentry *dentry, char * path)
 
 }
 
-static void *configfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *configfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
-	int error = -ENOMEM;
 	unsigned long page = get_zeroed_page(GFP_KERNEL);
+	int error;
 
-	if (page) {
-		error = configfs_getlink(dentry, (char *)page);
-		if (!error) {
-			nd_set_link(nd, (char *)page);
-			return (void *)page;
-		}
+	if (!page)
+		return ERR_PTR(-ENOMEM);
+
+	error = configfs_getlink(dentry, (char *)page);
+	if (!error) {
+		return *cookie = (void *)page;
 	}
 
-	nd_set_link(nd, ERR_PTR(error));
-	return NULL;
+	free_page(page);
+	return ERR_PTR(error);
 }
 
-static void configfs_put_link(struct dentry *dentry, struct nameidata *nd,
-			      void *cookie)
+static void configfs_put_link(struct dentry *dentry, void *cookie)
 {
-	if (cookie) {
-		unsigned long page = (unsigned long)cookie;
-		free_page(page);
-	}
+	free_page((unsigned long)cookie);
 }
 
 const struct inode_operations configfs_symlink_inode_operations = {
fs/ecryptfs/inode.c +3 -5
index fc850b5..cdb9d6c 100644
@@ -675,18 +675,16 @@ out:
 	return rc ? ERR_PTR(rc) : buf;
 }
 
-static void *ecryptfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *ecryptfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	size_t len;
 	char *buf = ecryptfs_readlink_lower(dentry, &len);
 	if (IS_ERR(buf))
-		goto out;
+		return buf;
 	fsstack_copy_attr_atime(d_inode(dentry),
 				d_inode(ecryptfs_dentry_to_lower(dentry)));
 	buf[len] = '\0';
-out:
-	nd_set_link(nd, buf);
-	return NULL;
+	return *cookie = buf;
 }
 
 /**
fs/ext4/symlink.c +4 -5
index 4264fb1..afec475 100644
@@ -23,7 +23,7 @@
 #include "xattr.h"
 
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
-static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *ext4_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
@@ -37,7 +37,7 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd)
 
 	ctx = ext4_get_fname_crypto_ctx(inode, inode->i_sb->s_blocksize);
 	if (IS_ERR(ctx))
-		return ctx;
+		return ERR_CAST(ctx);
 
 	if (ext4_inode_is_fast_symlink(inode)) {
 		caddr = (char *) EXT4_I(inode)->i_data;
@@ -46,7 +46,7 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd)
 		cpage = read_mapping_page(inode->i_mapping, 0, NULL);
 		if (IS_ERR(cpage)) {
 			ext4_put_fname_crypto_ctx(&ctx);
-			return cpage;
+			return ERR_CAST(cpage);
 		}
 		caddr = kmap(cpage);
 		caddr[size] = 0;
@@ -77,13 +77,12 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd)
 	/* Null-terminate the name */
 	if (res <= plen)
 		paddr[res] = '\0';
-	nd_set_link(nd, paddr);
 	ext4_put_fname_crypto_ctx(&ctx);
 	if (cpage) {
 		kunmap(cpage);
 		page_cache_release(cpage);
 	}
-	return NULL;
+	return *cookie = paddr;
 errout:
 	ext4_put_fname_crypto_ctx(&ctx);
 	if (cpage) {
fs/f2fs/namei.c +7 -11
index 658e807..d294793 100644
@@ -296,19 +296,15 @@ fail:
 	return err;
 }
 
-static void *f2fs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *f2fs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
-	struct page *page = page_follow_link_light(dentry, nd);
-
-	if (IS_ERR_OR_NULL(page))
-		return page;
-
-	/* this is broken symlink case */
-	if (*nd_get_link(nd) == 0) {
-		page_put_link(dentry, nd, page);
-		return ERR_PTR(-ENOENT);
+	const char *link = page_follow_link_light(dentry, cookie, nd);
+	if (!IS_ERR(link) && !*link) {
+		/* this is broken symlink case */
+		page_put_link(dentry, *cookie);
+		link = ERR_PTR(-ENOENT);
 	}
-	return page;
+	return link;
 }
 
 static int f2fs_symlink(struct inode *dir, struct dentry *dentry,
fs/fuse/dir.c +4 -15
index 0572bca..f9cb260 100644
@@ -1365,7 +1365,7 @@ static int fuse_readdir(struct file *file, struct dir_context *ctx)
 	return err;
 }
 
-static char *read_link(struct dentry *dentry)
+static const char *fuse_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct inode *inode = d_inode(dentry);
 	struct fuse_conn *fc = get_fuse_conn(inode);
@@ -1389,26 +1389,15 @@ static char *read_link(struct dentry *dentry)
 		link = ERR_PTR(ret);
 	} else {
 		link[ret] = '\0';
+		*cookie = link;
 	}
 	fuse_invalidate_atime(inode);
 	return link;
 }
 
-static void free_link(char *link)
+static void fuse_put_link(struct dentry *dentry, void *cookie)
 {
-	if (!IS_ERR(link))
-		free_page((unsigned long) link);
-}
-
-static void *fuse_follow_link(struct dentry *dentry, struct nameidata *nd)
-{
-	nd_set_link(nd, read_link(dentry));
-	return NULL;
-}
-
-static void fuse_put_link(struct dentry *dentry, struct nameidata *nd, void *c)
-{
-	free_link(nd_get_link(nd));
+	free_page((unsigned long) cookie);
 }
 
 static int fuse_dir_open(struct inode *inode, struct file *file)
fs/gfs2/inode.c +5 -5
index 1b3ca7a..f59390a 100644
@@ -1548,7 +1548,7 @@ out:
  * Returns: 0 on success or error code
  */
 
-static void *gfs2_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *gfs2_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct gfs2_inode *ip = GFS2_I(d_inode(dentry));
 	struct gfs2_holder i_gh;
@@ -1561,8 +1561,7 @@ static void *gfs2_follow_link(struct dentry *dentry, struct nameidata *nd)
 	error = gfs2_glock_nq(&i_gh);
 	if (error) {
 		gfs2_holder_uninit(&i_gh);
-		nd_set_link(nd, ERR_PTR(error));
-		return NULL;
+		return ERR_PTR(error);
 	}
 
 	size = (unsigned int)i_size_read(&ip->i_inode);
@@ -1586,8 +1585,9 @@ static void *gfs2_follow_link(struct dentry *dentry, struct nameidata *nd)
 	brelse(dibh);
 out:
 	gfs2_glock_dq_uninit(&i_gh);
-	nd_set_link(nd, buf);
-	return NULL;
+	if (!IS_ERR(buf))
+		*cookie = buf;
+	return buf;
 }
 
 /**
fs/hostfs/hostfs_kern.c +6 -9
index ef26317..f650ed6 100644
@@ -892,7 +892,7 @@ static const struct inode_operations hostfs_dir_iops = {
 	.setattr	= hostfs_setattr,
 };
 
-static void *hostfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *hostfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	char *link = __getname();
 	if (link) {
@@ -906,21 +906,18 @@ static void *hostfs_follow_link(struct dentry *dentry, struct nameidata *nd)
 		}
 		if (err < 0) {
 			__putname(link);
-			link = ERR_PTR(err);
+			return ERR_PTR(err);
 		}
 	} else {
-		link = ERR_PTR(-ENOMEM);
+		return ERR_PTR(-ENOMEM);
 	}
 
-	nd_set_link(nd, link);
-	return NULL;
+	return *cookie = link;
 }
 
-static void hostfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
+static void hostfs_put_link(struct dentry *dentry, void *cookie)
 {
-	char *s = nd_get_link(nd);
-	if (!IS_ERR(s))
-		__putname(s);
+	__putname(cookie);
 }
 
 static const struct inode_operations hostfs_link_iops = {
fs/hppfs/hppfs.c +4 -5
index fa2bd53..b8f24d3 100644
@@ -642,20 +642,19 @@ static int hppfs_readlink(struct dentry *dentry, char __user *buffer,
 						    buflen);
 }
 
-static void *hppfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *hppfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct dentry *proc_dentry = HPPFS_I(d_inode(dentry))->proc_dentry;
 
-	return d_inode(proc_dentry)->i_op->follow_link(proc_dentry, nd);
+	return d_inode(proc_dentry)->i_op->follow_link(proc_dentry, cookie, nd);
 }
 
-static void hppfs_put_link(struct dentry *dentry, struct nameidata *nd,
-			   void *cookie)
+static void hppfs_put_link(struct dentry *dentry, void *cookie)
 {
 	struct dentry *proc_dentry = HPPFS_I(d_inode(dentry))->proc_dentry;
 
 	if (d_inode(proc_dentry)->i_op->put_link)
-		d_inode(proc_dentry)->i_op->put_link(proc_dentry, nd, cookie);
+		d_inode(proc_dentry)->i_op->put_link(proc_dentry, cookie);
 }
 
 static const struct inode_operations hppfs_dir_iops = {
fs/kernfs/symlink.c +10 -12
index 8a19889..3c7e799 100644
@@ -112,25 +112,23 @@ static int kernfs_getlink(struct dentry *dentry, char *path)
 	return error;
 }
 
-static void *kernfs_iop_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	int error = -ENOMEM;
 	unsigned long page = get_zeroed_page(GFP_KERNEL);
-	if (page) {
-		error = kernfs_getlink(dentry, (char *) page);
-		if (error < 0)
-			free_page((unsigned long)page);
+	if (!page)
+		return ERR_PTR(-ENOMEM);
+	error = kernfs_getlink(dentry, (char *)page);
+	if (unlikely(error < 0)) {
+		free_page((unsigned long)page);
+		return ERR_PTR(error);
 	}
-	nd_set_link(nd, error ? ERR_PTR(error) : (char *)page);
-	return NULL;
+	return *cookie = (char *)page;
 }
 
-static void kernfs_iop_put_link(struct dentry *dentry, struct nameidata *nd,
-				void *cookie)
+static void kernfs_iop_put_link(struct dentry *dentry, void *cookie)
 {
-	char *page = nd_get_link(nd);
-	if (!IS_ERR(page))
-		free_page((unsigned long)page);
+	free_page((unsigned long)cookie);
 }
 
 const struct inode_operations kernfs_symlink_iops = {
fs/libfs.c +4 -8
index 72e4e01..0c83fde 100644
@@ -1024,12 +1024,9 @@ int noop_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 }
 EXPORT_SYMBOL(noop_fsync);
 
-void kfree_put_link(struct dentry *dentry, struct nameidata *nd,
-				void *cookie)
+void kfree_put_link(struct dentry *dentry, void *cookie)
 {
-	char *s = nd_get_link(nd);
-	if (!IS_ERR(s))
-		kfree(s);
+	kfree(cookie);
 }
 EXPORT_SYMBOL(kfree_put_link);
 
@@ -1094,10 +1091,9 @@ simple_nosetlease(struct file *filp, long arg, struct file_lock **flp,
 }
 EXPORT_SYMBOL(simple_nosetlease);
 
-void *simple_follow_link(struct dentry *dentry, struct nameidata *nd)
+const char *simple_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
-	nd_set_link(nd, d_inode(dentry)->i_link);
-	return NULL;
+	return d_inode(dentry)->i_link;
 }
 EXPORT_SYMBOL(simple_follow_link);
 
fs/namei.c +21 -45

131 lines not on this page · raw message

fs/nfs/symlink.c +6 -13
index 2d56200..c992b20 100644
@@ -20,7 +20,6 @@
 #include <linux/stat.h>
 #include <linux/mm.h>
 #include <linux/string.h>
-#include <linux/namei.h>
 
 /* Symlink caching in the page cache is even more simplistic
  * and straight-forward than readdir caching.
@@ -43,7 +42,7 @@ error:
 	return -EIO;
 }
 
-static void *nfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *nfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct inode *inode = d_inode(dentry);
 	struct page *page;
@@ -51,19 +50,13 @@ static void *nfs_follow_link(struct dentry *dentry, struct nameidata *nd)
 
 	err = ERR_PTR(nfs_revalidate_mapping(inode, inode->i_mapping));
 	if (err)
-		goto read_failed;
+		return err;
 	page = read_cache_page(&inode->i_data, 0,
 				(filler_t *)nfs_symlink_filler, inode);
-	if (IS_ERR(page)) {
-		err = page;
-		goto read_failed;
-	}
-	nd_set_link(nd, kmap(page));
-	return page;
-
-read_failed:
-	nd_set_link(nd, err);
-	return NULL;
+	if (IS_ERR(page))
+		return ERR_CAST(page);
+	*cookie = page;
+	return kmap(page);
 }
 
 /*
fs/overlayfs/inode.c +10 -8
index 1b4b9c5e..235ad42 100644
@@ -140,12 +140,12 @@ struct ovl_link_data {
 	void *cookie;
 };
 
-static void *ovl_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *ovl_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
-	void *ret;
 	struct dentry *realdentry;
 	struct inode *realinode;
 	struct ovl_link_data *data = NULL;
+	const char *ret;
 
 	realdentry = ovl_dentry_real(dentry);
 	realinode = realdentry->d_inode;
@@ -160,19 +160,21 @@ static void *ovl_follow_link(struct dentry *dentry, struct nameidata *nd)
 		data->realdentry = realdentry;
 	}
 
-	ret = realinode->i_op->follow_link(realdentry, nd);
-	if (IS_ERR(ret)) {
+	ret = realinode->i_op->follow_link(realdentry, cookie, nd);
+	if (IS_ERR_OR_NULL(ret)) {
 		kfree(data);
 		return ret;
 	}
 
 	if (data)
-		data->cookie = ret;
+		data->cookie = *cookie;
 
-	return data;
+	*cookie = data;
+
+	return ret;
 }
 
-static void ovl_put_link(struct dentry *dentry, struct nameidata *nd, void *c)
+static void ovl_put_link(struct dentry *dentry, void *c)
 {
 	struct inode *realinode;
 	struct ovl_link_data *data = c;
@@ -181,7 +183,7 @@ static void ovl_put_link(struct dentry *dentry, struct nameidata *nd, void *c)
 		return;
 
 	realinode = data->realdentry->d_inode;
-	realinode->i_op->put_link(data->realdentry, nd, data->cookie);
+	realinode->i_op->put_link(data->realdentry, data->cookie);
 	kfree(data);
 }
 
fs/proc/base.c +1 -1
index 093ca14..52652f8 100644
@@ -1380,7 +1380,7 @@ static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
 		return -ENOENT;
 }
 
-static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *proc_pid_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct inode *inode = d_inode(dentry);
 	struct path path;
fs/proc/inode.c +4 -5
index 8272aab..acd51d7 100644
@@ -23,7 +23,6 @@
 #include <linux/slab.h>
 #include <linux/mount.h>
 #include <linux/magic.h>
-#include <linux/namei.h>
 
 #include <asm/uaccess.h>
 
@@ -394,16 +393,16 @@ static const struct file_operations proc_reg_file_ops_no_compat = {
 };
 #endif
 
-static void *proc_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *proc_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct proc_dir_entry *pde = PDE(d_inode(dentry));
 	if (unlikely(!use_pde(pde)))
 		return ERR_PTR(-EINVAL);
-	nd_set_link(nd, pde->data);
-	return pde;
+	*cookie = pde;
+	return pde->data;
 }
 
-static void proc_put_link(struct dentry *dentry, struct nameidata *nd, void *p)
+static void proc_put_link(struct dentry *dentry, void *p)
 {
 	unuse_pde(p);
 }
fs/proc/namespaces.c +1 -1
index e512642..10d24dd 100644
@@ -30,7 +30,7 @@ static const struct proc_ns_operations *ns_entries[] = {
 	&mntns_operations,
 };
 
-static void *proc_ns_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *proc_ns_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct inode *inode = d_inode(dentry);
 	const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
fs/proc/self.c +11 -13
index 6195b4a..ad33394 100644
@@ -1,5 +1,4 @@
 #include <linux/sched.h>
-#include <linux/namei.h>
 #include <linux/slab.h>
 #include <linux/pid_namespace.h>
 #include "internal.h"
@@ -19,21 +18,20 @@ 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, struct nameidata *nd)
+static const char *proc_self_follow_link(struct dentry *dentry, void **cookie, 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 (tgid) {
-		/* 11 for max length of signed int in decimal + NULL term */
-		name = kmalloc(12, GFP_KERNEL);
-		if (!name)
-			name = ERR_PTR(-ENOMEM);
-		else
-			sprintf(name, "%d", tgid);
-	}
-	nd_set_link(nd, name);
-	return NULL;
+	char *name;
+
+	if (!tgid)
+		return ERR_PTR(-ENOENT);
+	/* 11 for max length of signed int in decimal + NULL term */
+	name = kmalloc(12, GFP_KERNEL);
+	if (!name)
+		return ERR_PTR(-ENOMEM);
+	sprintf(name, "%d", tgid);
+	return *cookie = name;
 }
 
 static const struct inode_operations proc_self_inode_operations = {
fs/proc/thread_self.c +10 -12
index a837199..85c96e0 100644
@@ -1,5 +1,4 @@
 #include <linux/sched.h>
-#include <linux/namei.h>
 #include <linux/slab.h>
 #include <linux/pid_namespace.h>
 #include "internal.h"
@@ -20,21 +19,20 @@ 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, struct nameidata *nd)
+static const char *proc_thread_self_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	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);
-	char *name = ERR_PTR(-ENOENT);
-	if (pid) {
-		name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
-		if (!name)
-			name = ERR_PTR(-ENOMEM);
-		else
-			sprintf(name, "%d/task/%d", tgid, pid);
-	}
-	nd_set_link(nd, name);
-	return NULL;
+	char *name;
+
+	if (!pid)
+		return ERR_PTR(-ENOENT);
+	name = kmalloc(PROC_NUMBUF + 6 + PROC_NUMBUF, GFP_KERNEL);
+	if (!name)
+		return ERR_PTR(-ENOMEM);
+	sprintf(name, "%d/task/%d", tgid, pid);
+	return *cookie = name;
 }
 
 static const struct inode_operations proc_thread_self_inode_operations = {
fs/xfs/xfs_iops.c +4 -6
index f4cd720..26c4dcb 100644
@@ -41,7 +41,6 @@
 
 #include <linux/capability.h>
 #include <linux/xattr.h>
-#include <linux/namei.h>
 #include <linux/posix_acl.h>
 #include <linux/security.h>
 #include <linux/fiemap.h>
@@ -414,9 +413,10 @@ xfs_vn_rename(
  * we need to be very careful about how much stack we use.
  * uio is kmalloced for this reason...
  */
-STATIC void *
+STATIC const char *
 xfs_vn_follow_link(
 	struct dentry		*dentry,
+	void			**cookie,
 	struct nameidata	*nd)
 {
 	char			*link;
@@ -430,14 +430,12 @@ xfs_vn_follow_link(
 	if (unlikely(error))
 		goto out_kfree;
 
-	nd_set_link(nd, link);
-	return NULL;
+	return *cookie = link;
 
  out_kfree:
 	kfree(link);
  out_err:
-	nd_set_link(nd, ERR_PTR(error));
-	return NULL;
+	return ERR_PTR(error);
 }
 
 STATIC int
include/linux/fs.h +6 -6
index 0ac758f..9ab9341 100644
@@ -1608,12 +1608,12 @@ struct file_operations {
 
 struct inode_operations {
 	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
-	void * (*follow_link) (struct dentry *, struct nameidata *);
+	const char * (*follow_link) (struct dentry *, void **, struct nameidata *);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
 	int (*readlink) (struct dentry *, char __user *,int);
-	void (*put_link) (struct dentry *, struct nameidata *, void *);
+	void (*put_link) (struct dentry *, void *);
 
 	int (*create) (struct inode *,struct dentry *, umode_t, bool);
 	int (*link) (struct dentry *,struct inode *,struct dentry *);
@@ -2705,13 +2705,13 @@ 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 *, struct nameidata *);
-extern void page_put_link(struct dentry *, struct nameidata *, void *);
+extern const char *page_follow_link_light(struct dentry *, void **, struct nameidata *);
+extern void page_put_link(struct dentry *, void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
 extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
-extern void kfree_put_link(struct dentry *, struct nameidata *, void *);
+extern void kfree_put_link(struct dentry *, void *);
 extern int generic_readlink(struct dentry *, char __user *, int);
 extern void generic_fillattr(struct inode *, struct kstat *);
 int vfs_getattr_nosec(struct path *path, struct kstat *stat);
@@ -2722,7 +2722,7 @@ void __inode_sub_bytes(struct inode *inode, loff_t bytes);
 void inode_sub_bytes(struct inode *inode, loff_t bytes);
 loff_t inode_get_bytes(struct inode *inode);
 void inode_set_bytes(struct inode *inode, loff_t bytes);
-void *simple_follow_link(struct dentry *, struct nameidata *);
+const char *simple_follow_link(struct dentry *, void **, struct nameidata *);
 extern const struct inode_operations simple_symlink_inode_operations;
 
 extern int iterate_dir(struct file *, struct dir_context *);
include/linux/namei.h +0 -2
index c899077..a5d5bed 100644
@@ -71,8 +71,6 @@ extern struct dentry *lock_rename(struct dentry *, struct dentry *);
 extern void unlock_rename(struct dentry *, struct dentry *);
 
 extern void nd_jump_link(struct nameidata *nd, struct path *path);
-extern void nd_set_link(struct nameidata *nd, char *path);
-extern char *nd_get_link(struct nameidata *nd);
 
 static inline void nd_terminate_link(void *name, size_t len, size_t maxlen)
 {
mm/shmem.c +11 -12
index 7f6e2f8..d1693dc 100644
@@ -2475,24 +2475,23 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	return 0;
 }
 
-static void *shmem_follow_link(struct dentry *dentry, struct nameidata *nd)
+static const char *shmem_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
 {
 	struct page *page = NULL;
 	int error = shmem_getpage(d_inode(dentry), 0, &page, SGP_READ, NULL);
-	nd_set_link(nd, error ? ERR_PTR(error) : kmap(page));
-	if (page)
-		unlock_page(page);
-	return page;
+	if (error)
+		return ERR_PTR(error);
+	unlock_page(page);
+	*cookie = page;
+	return kmap(page);
 }
 
-static void shmem_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
+static void shmem_put_link(struct dentry *dentry, void *cookie)
 {
-	if (!IS_ERR(nd_get_link(nd))) {
-		struct page *page = cookie;
-		kunmap(page);
-		mark_page_accessed(page);
-		page_cache_release(page);
-	}
+	struct page *page = cookie;
+	kunmap(page);
+	mark_page_accessed(page);
+	page_cache_release(page);
 }
 
 #ifdef CONFIG_TMPFS_XATTR
Signature · 1 line
2.1.4
author maintainer
#137 lore raw

PATCH v3 30/110 namei.c: separate the parts of follow_link() that find the link body

In reply to Al Viro

From: Al Viro <[email protected]>

Split a piece of fs/namei.c:follow_link() that does obtaining the link body into a separate function. follow_link() itself is converted to calling get_link() and then doing the body traversal (if any).

The next step will expand follow_link() call in link_path_walk() and this helps to keep the size down...

---
 fs/namei.c | 65 ++++++++++++++++++++++++++++++++++----------------------------
 1 file changed, 36 insertions(+), 29 deletions(-)
fs/namei.c +36 -29
index aeca448..a1ba556 100644
@@ -836,21 +836,22 @@ static int may_linkat(struct path *link)
 	return -EPERM;
 }
 
-static __always_inline int
-follow_link(struct path *link, struct nameidata *nd, void **p)
+static __always_inline const char *
+get_link(struct path *link, struct nameidata *nd, void **p)
 {
 	struct dentry *dentry = link->dentry;
+	struct inode *inode = dentry->d_inode;
 	int error;
-	const char *s;
+	const char *res;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 
 	if (link->mnt == nd->path.mnt)
 		mntget(link->mnt);
 
-	error = -ELOOP;
+	res = ERR_PTR(-ELOOP);
 	if (unlikely(current->total_link_count >= 40))
-		goto out_put_nd_path;
+		goto out;
 
 	cond_resched();
 	current->total_link_count++;
@@ -858,37 +859,43 @@ follow_link(struct path *link, struct nameidata *nd, void **p)
 	touch_atime(link);
 
 	error = security_inode_follow_link(dentry);
+	res = ERR_PTR(error);
 	if (error)
-		goto out_put_nd_path;
+		goto out;
 
 	nd->last_type = LAST_BIND;
 	*p = NULL;
-	s = dentry->d_inode->i_op->follow_link(dentry, p, nd);
-	error = PTR_ERR(s);
-	if (IS_ERR(s))
-		goto out_put_nd_path;
-
-	error = 0;
-	if (s) {
-		if (*s == '/') {
-			if (!nd->root.mnt)
-				set_root(nd);
-			path_put(&nd->path);
-			nd->path = nd->root;
-			path_get(&nd->root);
-			nd->flags |= LOOKUP_JUMPED;
-		}
-		nd->inode = nd->path.dentry->d_inode;
-		error = link_path_walk(s, nd);
-		if (unlikely(error))
-			put_link(nd, link, *p);
+	res = inode->i_op->follow_link(dentry, p, nd);
+	if (IS_ERR(res)) {
+out:
+		path_put(&nd->path);
+		path_put(link);
 	}
+	return res;
+}
 
-	return error;
+static __always_inline int
+follow_link(struct path *link, struct nameidata *nd, void **p)
+{
+	const char *s = get_link(link, nd, p);
+	int error;
 
-out_put_nd_path:
-	path_put(&nd->path);
-	path_put(link);
+	if (unlikely(IS_ERR(s)))
+		return PTR_ERR(s);
+	if (unlikely(!s))
+		return 0;
+	if (*s == '/') {
+		if (!nd->root.mnt)
+			set_root(nd);
+		path_put(&nd->path);
+		nd->path = nd->root;
+		path_get(&nd->root);
+		nd->flags |= LOOKUP_JUMPED;
+	}
+	nd->inode = nd->path.dentry->d_inode;
+	error = link_path_walk(s, nd);
+	if (unlikely(error))
+		put_link(nd, link, *p);
 	return error;
 }
 
Signature · 1 line
2.1.4
author maintainer
#138 lore raw

PATCH v3 31/110 namei: don't bother with ->follow_link() if ->i_link is set

In reply to Al Viro

From: Al Viro <[email protected]>

with new calling conventions it's trivial

Conflicts:
fs/namei.c

---
 fs/namei.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)
fs/namei.c +13 -7
index a1ba556..2ffb4af 100644
@@ -865,11 +865,14 @@ get_link(struct path *link, struct nameidata *nd, void **p)
 
 	nd->last_type = LAST_BIND;
 	*p = NULL;
-	res = inode->i_op->follow_link(dentry, p, nd);
-	if (IS_ERR(res)) {
+	res = inode->i_link;
+	if (!res) {
+		res = inode->i_op->follow_link(dentry, p, nd);
+		if (IS_ERR(res)) {
 out:
-		path_put(&nd->path);
-		path_put(link);
+			path_put(&nd->path);
+			path_put(link);
+		}
 	}
 	return res;
 }
@@ -4418,11 +4421,14 @@ EXPORT_SYMBOL(readlink_copy);
 int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 {
 	void *cookie;
-	const char *link = dentry->d_inode->i_op->follow_link(dentry, &cookie, NULL);
+	const char *link = dentry->d_inode->i_link;
 	int res;
 
-	if (IS_ERR(link))
-		return PTR_ERR(link);
+	if (!link) {
+		link = dentry->d_inode->i_op->follow_link(dentry, &cookie, NULL);
+		if (IS_ERR(link))
+			return PTR_ERR(link);
+	}
 	res = readlink_copy(buffer, buflen, link);
 	if (cookie && dentry->d_inode->i_op->put_link)
 		dentry->d_inode->i_op->put_link(dentry, cookie);
Signature · 1 line
2.1.4
author maintainer
#139 lore raw

PATCH v3 32/110 namei: introduce nameidata->link

In reply to Al Viro

From: Al Viro <[email protected]>

shares space with nameidata->next, walk_component() et.al. store the struct path of symlink instead of returning it into a variable passed by caller.

---
 fs/namei.c | 62 ++++++++++++++++++++++++++++++++++----------------------------
 1 file changed, 34 insertions(+), 28 deletions(-)
fs/namei.c +34 -28
index 2ffb4af..06c3e4a 100644
@@ -494,7 +494,10 @@ EXPORT_SYMBOL(path_put);
 
 struct nameidata {
 	struct path	path;
-	struct qstr	last;
+	union {
+		struct qstr	last;
+		struct path	link;
+	};
 	struct path	root;
 	struct inode	*inode; /* path.dentry.d_inode */
 	unsigned int	flags;
@@ -1559,8 +1562,9 @@ static inline int should_follow_link(struct dentry *dentry, int follow)
 	return unlikely(d_is_symlink(dentry)) ? follow : 0;
 }
 
-static int walk_component(struct nameidata *nd, struct path *path, int follow)
+static int walk_component(struct nameidata *nd, int follow)
 {
+	struct path path;
 	struct inode *inode;
 	int err;
 	/*
@@ -1570,38 +1574,39 @@ static int walk_component(struct nameidata *nd, struct path *path, int follow)
 	 */
 	if (unlikely(nd->last_type != LAST_NORM))
 		return handle_dots(nd, nd->last_type);
-	err = lookup_fast(nd, path, &inode);
+	err = lookup_fast(nd, &path, &inode);
 	if (unlikely(err)) {
 		if (err < 0)
 			goto out_err;
 
-		err = lookup_slow(nd, path);
+		err = lookup_slow(nd, &path);
 		if (err < 0)
 			goto out_err;
 
-		inode = path->dentry->d_inode;
+		inode = path.dentry->d_inode;
 		err = -ENOENT;
-		if (d_is_negative(path->dentry))
+		if (d_is_negative(path.dentry))
 			goto out_path_put;
 	}
 
-	if (should_follow_link(path->dentry, follow)) {
+	if (should_follow_link(path.dentry, follow)) {
 		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(nd->path.mnt != path->mnt ||
-				     unlazy_walk(nd, path->dentry))) {
+			if (unlikely(nd->path.mnt != path.mnt ||
+				     unlazy_walk(nd, path.dentry))) {
 				err = -ECHILD;
 				goto out_err;
 			}
 		}
-		BUG_ON(inode != path->dentry->d_inode);
+		BUG_ON(inode != path.dentry->d_inode);
+		nd->link = path;
 		return 1;
 	}
-	path_to_nameidata(path, nd);
+	path_to_nameidata(&path, nd);
 	nd->inode = inode;
 	return 0;
 
 out_path_put:
-	path_to_nameidata(path, nd);
+	path_to_nameidata(&path, nd);
 out_err:
 	terminate_walk(nd);
 	return err;
@@ -1614,12 +1619,12 @@ out_err:
  * Without that kind of total limit, nasty chains of consecutive
  * symlinks can cause almost arbitrarily long lookups.
  */
-static inline int nested_symlink(struct path *path, struct nameidata *nd)
+static inline int nested_symlink(struct nameidata *nd)
 {
 	int res;
 
 	if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
-		path_put_conditional(path, nd);
+		path_put_conditional(&nd->link, nd);
 		path_put(&nd->path);
 		return -ELOOP;
 	}
@@ -1629,13 +1634,13 @@ static inline int nested_symlink(struct path *path, struct nameidata *nd)
 	current->link_count++;
 
 	do {
-		struct path link = *path;
+		struct path link = nd->link;
 		void *cookie;
 
 		res = follow_link(&link, nd, &cookie);
 		if (res)
 			break;
-		res = walk_component(nd, path, LOOKUP_FOLLOW);
+		res = walk_component(nd, LOOKUP_FOLLOW);
 		put_link(nd, &link, cookie);
 	} while (res > 0);
 
@@ -1770,7 +1775,6 @@ static inline u64 hash_name(const char *name)
  */
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
-	struct path next;
 	int err;
 	
 	while (*name=='/')
@@ -1830,17 +1834,17 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 		if (!*name)
 			return 0;
 
-		err = walk_component(nd, &next, LOOKUP_FOLLOW);
+		err = walk_component(nd, LOOKUP_FOLLOW);
 		if (err < 0)
 			return err;
 
 		if (err) {
-			err = nested_symlink(&next, nd);
+			err = nested_symlink(nd);
 			if (err)
 				return err;
 		}
 		if (!d_can_lookup(nd->path.dentry)) {
-			err = -ENOTDIR; 
+			err = -ENOTDIR;
 			break;
 		}
 	}
@@ -1960,20 +1964,19 @@ static void path_cleanup(struct nameidata *nd)
 		fput(nd->base);
 }
 
-static inline int lookup_last(struct nameidata *nd, struct path *path)
+static inline int lookup_last(struct nameidata *nd)
 {
 	if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	nd->flags &= ~LOOKUP_PARENT;
-	return walk_component(nd, path, nd->flags & LOOKUP_FOLLOW);
+	return walk_component(nd, nd->flags & LOOKUP_FOLLOW);
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
 static int path_lookupat(int dfd, const struct filename *name,
 				unsigned int flags, struct nameidata *nd)
 {
-	struct path path;
 	int err;
 
 	/*
@@ -1992,10 +1995,10 @@ static int path_lookupat(int dfd, const struct filename *name,
 	 */
 	err = path_init(dfd, name, flags, nd);
 	if (!err && !(flags & LOOKUP_PARENT)) {
-		err = lookup_last(nd, &path);
+		err = lookup_last(nd);
 		while (err > 0) {
 			void *cookie;
-			struct path link = path;
+			struct path link = nd->link;
 			err = may_follow_link(&link, nd);
 			if (unlikely(err))
 				break;
@@ -2003,7 +2006,7 @@ static int path_lookupat(int dfd, const struct filename *name,
 			err = follow_link(&link, nd, &cookie);
 			if (err)
 				break;
-			err = lookup_last(nd, &path);
+			err = lookup_last(nd);
 			put_link(nd, &link, cookie);
 		}
 	}
@@ -2312,8 +2315,10 @@ done:
 	}
 	path->dentry = dentry;
 	path->mnt = nd->path.mnt;
-	if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW))
+	if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW)) {
+		nd->link = *path;
 		return 1;
+	}
 	mntget(path->mnt);
 	follow_mount(path);
 	error = 0;
@@ -3040,6 +3045,7 @@ finish_lookup:
 			}
 		}
 		BUG_ON(inode != path->dentry->d_inode);
+		nd->link = *path;
 		return 1;
 	}
 
@@ -3228,7 +3234,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 
 	error = do_last(nd, &path, file, op, &opened, pathname);
 	while (unlikely(error > 0)) { /* trailing symlink */
-		struct path link = path;
+		struct path link = nd->link;
 		void *cookie;
 		error = may_follow_link(&link, nd);
 		if (unlikely(error))
Signature · 1 line
2.1.4
author maintainer
#140 lore raw

PATCH v3 33/110 do_last: move path there from caller's stack frame

In reply to Al Viro

From: Al Viro <[email protected]>

We used to need it to feed to follow_link(). No more...

---
 fs/namei.c | 50 +++++++++++++++++++++++++-------------------------
 1 file changed, 25 insertions(+), 25 deletions(-)
fs/namei.c +25 -25
index 06c3e4a..8582907 100644
@@ -2911,7 +2911,7 @@ out_dput:
 /*
  * Handle the last step of open()
  */
-static int do_last(struct nameidata *nd, struct path *path,
+static int do_last(struct nameidata *nd,
 		   struct file *file, const struct open_flags *op,
 		   int *opened, struct filename *name)
 {
@@ -2922,6 +2922,7 @@ static int do_last(struct nameidata *nd, struct path *path,
 	int acc_mode = op->acc_mode;
 	struct inode *inode;
 	struct path save_parent = { .dentry = NULL, .mnt = NULL };
+	struct path path;
 	bool retried = false;
 	int error;
 
@@ -2939,7 +2940,7 @@ static int do_last(struct nameidata *nd, struct path *path,
 		if (nd->last.name[nd->last.len])
 			nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 		/* we _can_ be in RCU mode here */
-		error = lookup_fast(nd, path, &inode);
+		error = lookup_fast(nd, &path, &inode);
 		if (likely(!error))
 			goto finish_lookup;
 
@@ -2977,7 +2978,7 @@ retry_lookup:
 		 */
 	}
 	mutex_lock(&dir->d_inode->i_mutex);
-	error = lookup_open(nd, path, file, op, got_write, opened);
+	error = lookup_open(nd, &path, file, op, got_write, opened);
 	mutex_unlock(&dir->d_inode->i_mutex);
 
 	if (error <= 0) {
@@ -2997,15 +2998,15 @@ retry_lookup:
 		open_flag &= ~O_TRUNC;
 		will_truncate = false;
 		acc_mode = MAY_OPEN;
-		path_to_nameidata(path, nd);
+		path_to_nameidata(&path, nd);
 		goto finish_open_created;
 	}
 
 	/*
 	 * create/update audit record if it already exists.
 	 */
-	if (d_is_positive(path->dentry))
-		audit_inode(name, path->dentry, 0);
+	if (d_is_positive(path.dentry))
+		audit_inode(name, path.dentry, 0);
 
 	/*
 	 * If atomic_open() acquired write access it is dropped now due to
@@ -3021,7 +3022,7 @@ retry_lookup:
 	if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))
 		goto exit_dput;
 
-	error = follow_managed(path, nd->flags);
+	error = follow_managed(&path, nd->flags);
 	if (error < 0)
 		goto exit_dput;
 
@@ -3029,38 +3030,38 @@ retry_lookup:
 		nd->flags |= LOOKUP_JUMPED;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
-	inode = path->dentry->d_inode;
+	inode = path.dentry->d_inode;
 	error = -ENOENT;
-	if (d_is_negative(path->dentry)) {
-		path_to_nameidata(path, nd);
+	if (d_is_negative(path.dentry)) {
+		path_to_nameidata(&path, nd);
 		goto out;
 	}
 finish_lookup:
-	if (should_follow_link(path->dentry, nd->flags & LOOKUP_FOLLOW)) {
+	if (should_follow_link(path.dentry, nd->flags & LOOKUP_FOLLOW)) {
 		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(nd->path.mnt != path->mnt ||
-				     unlazy_walk(nd, path->dentry))) {
+			if (unlikely(nd->path.mnt != path.mnt ||
+				     unlazy_walk(nd, path.dentry))) {
 				error = -ECHILD;
 				goto out;
 			}
 		}
-		BUG_ON(inode != path->dentry->d_inode);
-		nd->link = *path;
+		BUG_ON(inode != path.dentry->d_inode);
+		nd->link = path;
 		return 1;
 	}
 
-	if (unlikely(d_is_symlink(path->dentry)) && !(open_flag & O_PATH)) {
-		path_to_nameidata(path, nd);
+	if (unlikely(d_is_symlink(path.dentry)) && !(open_flag & O_PATH)) {
+		path_to_nameidata(&path, nd);
 		error = -ELOOP;
 		goto out;
 	}
 
-	if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path->mnt) {
-		path_to_nameidata(path, nd);
+	if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path.mnt) {
+		path_to_nameidata(&path, nd);
 	} else {
 		save_parent.dentry = nd->path.dentry;
-		save_parent.mnt = mntget(path->mnt);
-		nd->path.dentry = path->dentry;
+		save_parent.mnt = mntget(path.mnt);
+		nd->path.dentry = path.dentry;
 
 	}
 	nd->inode = inode;
@@ -3122,7 +3123,7 @@ out:
 	return error;
 
 exit_dput:
-	path_put_conditional(path, nd);
+	path_put_conditional(&path, nd);
 	goto out;
 exit_fput:
 	fput(file);
@@ -3213,7 +3214,6 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		struct nameidata *nd, const struct open_flags *op, int flags)
 {
 	struct file *file;
-	struct path path;
 	int opened = 0;
 	int error;
 
@@ -3232,7 +3232,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 	if (unlikely(error))
 		goto out;
 
-	error = do_last(nd, &path, file, op, &opened, pathname);
+	error = do_last(nd, file, op, &opened, pathname);
 	while (unlikely(error > 0)) { /* trailing symlink */
 		struct path link = nd->link;
 		void *cookie;
@@ -3244,7 +3244,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		error = follow_link(&link, nd, &cookie);
 		if (unlikely(error))
 			break;
-		error = do_last(nd, &path, file, op, &opened, pathname);
+		error = do_last(nd, file, op, &opened, pathname);
 		put_link(nd, &link, cookie);
 	}
 out:
Signature · 1 line
2.1.4
author maintainer
#141 lore raw

PATCH v3 34/110 namei: expand nested_symlink() in its only caller

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 61 +++++++++++++++++++++++--------------------------------------
 1 file changed, 23 insertions(+), 38 deletions(-)
fs/namei.c +23 -38
index 8582907..7fcda91 100644
@@ -1613,43 +1613,6 @@ out_err:
 }
 
 /*
- * This limits recursive symlink follows to 8, while
- * limiting consecutive symlinks to 40.
- *
- * Without that kind of total limit, nasty chains of consecutive
- * symlinks can cause almost arbitrarily long lookups.
- */
-static inline int nested_symlink(struct nameidata *nd)
-{
-	int res;
-
-	if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
-		path_put_conditional(&nd->link, nd);
-		path_put(&nd->path);
-		return -ELOOP;
-	}
-	BUG_ON(nd->depth >= MAX_NESTED_LINKS);
-
-	nd->depth++;
-	current->link_count++;
-
-	do {
-		struct path link = nd->link;
-		void *cookie;
-
-		res = follow_link(&link, nd, &cookie);
-		if (res)
-			break;
-		res = walk_component(nd, LOOKUP_FOLLOW);
-		put_link(nd, &link, cookie);
-	} while (res > 0);
-
-	current->link_count--;
-	nd->depth--;
-	return res;
-}
-
-/*
  * We can do the critical dentry name comparison and hashing
  * operations one word at a time, but we are limited to:
  *
@@ -1839,7 +1802,29 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			return err;
 
 		if (err) {
-			err = nested_symlink(nd);
+			if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
+				path_put_conditional(&nd->link, nd);
+				path_put(&nd->path);
+				return -ELOOP;
+			}
+			BUG_ON(nd->depth >= MAX_NESTED_LINKS);
+
+			nd->depth++;
+			current->link_count++;
+
+			do {
+				struct path link = nd->link;
+				void *cookie;
+
+				err = follow_link(&link, nd, &cookie);
+				if (err)
+					break;
+				err = walk_component(nd, LOOKUP_FOLLOW);
+				put_link(nd, &link, cookie);
+			} while (err > 0);
+
+			current->link_count--;
+			nd->depth--;
 			if (err)
 				return err;
 		}
Signature · 1 line
2.1.4
author maintainer
#142 lore raw

PATCH v3 35/110 namei: expand the call of follow_link() in link_path_walk()

In reply to Al Viro

From: Al Viro <[email protected]>

... and strip __always_inline from follow_link() - remaining callers don't need that.

Now link_path_walk() recursion is a direct one.

---
 fs/namei.c | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)
fs/namei.c +22 -4
index 7fcda91..2354d4f 100644
@@ -880,8 +880,7 @@ out:
 	return res;
 }
 
-static __always_inline int
-follow_link(struct path *link, struct nameidata *nd, void **p)
+static int follow_link(struct path *link, struct nameidata *nd, void **p)
 {
 	const char *s = get_link(link, nd, p);
 	int error;
@@ -1815,10 +1814,29 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			do {
 				struct path link = nd->link;
 				void *cookie;
+				const char *s = get_link(&link, nd, &cookie);
 
-				err = follow_link(&link, nd, &cookie);
-				if (err)
+				if (unlikely(IS_ERR(s))) {
+					err = PTR_ERR(s);
 					break;
+				}
+				err = 0;
+				if (likely(s)) {
+					if (*s == '/') {
+						if (!nd->root.mnt)
+							set_root(nd);
+						path_put(&nd->path);
+						nd->path = nd->root;
+						path_get(&nd->root);
+						nd->flags |= LOOKUP_JUMPED;
+					}
+					nd->inode = nd->path.dentry->d_inode;
+					err = link_path_walk(s, nd);
+					if (unlikely(err)) {
+						put_link(nd, &link, cookie);
+						break;
+					}
+				}
 				err = walk_component(nd, LOOKUP_FOLLOW);
 				put_link(nd, &link, cookie);
 			} while (err > 0);
Signature · 1 line
2.1.4
author maintainer
#143 lore raw

PATCH v3 36/110 namei: move the calls of may_follow_link() into follow_link()

In reply to Al Viro

From: Al Viro <[email protected]>

All remaining callers of the former are preceded by the latter

---
 fs/namei.c | 21 ++++++---------------
 1 file changed, 6 insertions(+), 15 deletions(-)
fs/namei.c +6 -15
index 2354d4f..f7659ee 100644
@@ -882,9 +882,12 @@ out:
 
 static int follow_link(struct path *link, struct nameidata *nd, void **p)
 {
-	const char *s = get_link(link, nd, p);
-	int error;
-
+	const char *s;
+	int error = may_follow_link(link, nd);
+	if (unlikely(error))
+		return error;
+	nd->flags |= LOOKUP_PARENT;
+	s = get_link(link, nd, p);
 	if (unlikely(IS_ERR(s)))
 		return PTR_ERR(s);
 	if (unlikely(!s))
@@ -2002,10 +2005,6 @@ static int path_lookupat(int dfd, const struct filename *name,
 		while (err > 0) {
 			void *cookie;
 			struct path link = nd->link;
-			err = may_follow_link(&link, nd);
-			if (unlikely(err))
-				break;
-			nd->flags |= LOOKUP_PARENT;
 			err = follow_link(&link, nd, &cookie);
 			if (err)
 				break;
@@ -2352,10 +2351,6 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 	while (err > 0) {
 		void *cookie;
 		struct path link = *path;
-		err = may_follow_link(&link, nd);
-		if (unlikely(err))
-			break;
-		nd->flags |= LOOKUP_PARENT;
 		err = follow_link(&link, nd, &cookie);
 		if (err)
 			break;
@@ -3239,10 +3234,6 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 	while (unlikely(error > 0)) { /* trailing symlink */
 		struct path link = nd->link;
 		void *cookie;
-		error = may_follow_link(&link, nd);
-		if (unlikely(error))
-			break;
-		nd->flags |= LOOKUP_PARENT;
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
 		error = follow_link(&link, nd, &cookie);
 		if (unlikely(error))
Signature · 1 line
2.1.4
author maintainer
#144 lore raw

PATCH v3 37/110 namei: rename follow_link to trailing_symlink, move it down

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 62 ++++++++++++++++++++++++++++++--------------------------------
 1 file changed, 30 insertions(+), 32 deletions(-)
fs/namei.c +30 -32
index f7659ee..d729ef7 100644
@@ -668,8 +668,6 @@ static __always_inline void set_root(struct nameidata *nd)
 	get_fs_root(current->fs, &nd->root);
 }
 
-static int link_path_walk(const char *, struct nameidata *);
-
 static __always_inline unsigned set_root_rcu(struct nameidata *nd)
 {
 	struct fs_struct *fs = current->fs;
@@ -880,33 +878,6 @@ out:
 	return res;
 }
 
-static int follow_link(struct path *link, struct nameidata *nd, void **p)
-{
-	const char *s;
-	int error = may_follow_link(link, nd);
-	if (unlikely(error))
-		return error;
-	nd->flags |= LOOKUP_PARENT;
-	s = get_link(link, nd, p);
-	if (unlikely(IS_ERR(s)))
-		return PTR_ERR(s);
-	if (unlikely(!s))
-		return 0;
-	if (*s == '/') {
-		if (!nd->root.mnt)
-			set_root(nd);
-		path_put(&nd->path);
-		nd->path = nd->root;
-		path_get(&nd->root);
-		nd->flags |= LOOKUP_JUMPED;
-	}
-	nd->inode = nd->path.dentry->d_inode;
-	error = link_path_walk(s, nd);
-	if (unlikely(error))
-		put_link(nd, link, *p);
-	return error;
-}
-
 static int follow_up_rcu(struct path *path)
 {
 	struct mount *mnt = real_mount(path->mnt);
@@ -1970,6 +1941,33 @@ static void path_cleanup(struct nameidata *nd)
 		fput(nd->base);
 }
 
+static int trailing_symlink(struct path *link, struct nameidata *nd, void **p)
+{
+	const char *s;
+	int error = may_follow_link(link, nd);
+	if (unlikely(error))
+		return error;
+	nd->flags |= LOOKUP_PARENT;
+	s = get_link(link, nd, p);
+	if (unlikely(IS_ERR(s)))
+		return PTR_ERR(s);
+	if (unlikely(!s))
+		return 0;
+	if (*s == '/') {
+		if (!nd->root.mnt)
+			set_root(nd);
+		path_put(&nd->path);
+		nd->path = nd->root;
+		path_get(&nd->root);
+		nd->flags |= LOOKUP_JUMPED;
+	}
+	nd->inode = nd->path.dentry->d_inode;
+	error = link_path_walk(s, nd);
+	if (unlikely(error))
+		put_link(nd, link, *p);
+	return error;
+}
+
 static inline int lookup_last(struct nameidata *nd)
 {
 	if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
@@ -2005,7 +2003,7 @@ static int path_lookupat(int dfd, const struct filename *name,
 		while (err > 0) {
 			void *cookie;
 			struct path link = nd->link;
-			err = follow_link(&link, nd, &cookie);
+			err = trailing_symlink(&link, nd, &cookie);
 			if (err)
 				break;
 			err = lookup_last(nd);
@@ -2351,7 +2349,7 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 	while (err > 0) {
 		void *cookie;
 		struct path link = *path;
-		err = follow_link(&link, nd, &cookie);
+		err = trailing_symlink(&link, nd, &cookie);
 		if (err)
 			break;
 		err = mountpoint_last(nd, path);
@@ -3235,7 +3233,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		struct path link = nd->link;
 		void *cookie;
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
-		error = follow_link(&link, nd, &cookie);
+		error = trailing_symlink(&link, nd, &cookie);
 		if (unlikely(error))
 			break;
 		error = do_last(nd, file, op, &opened, pathname);
Signature · 1 line
2.1.4
author maintainer
#145 lore raw

PATCH v3 38/110 link_path_walk: handle get_link() returning ERR_PTR() immediately

In reply to Al Viro

From: Al Viro <[email protected]>

If we get ERR_PTR() from get_link(), we are guaranteed to get err != 0 when we break out of do-while, so we are going to hit if (err) return err; shortly after it. Pull that into the if (IS_ERR(s)) body.

---
 fs/namei.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
fs/namei.c +3 -1
index d729ef7..9937470 100644
@@ -1792,7 +1792,9 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 
 				if (unlikely(IS_ERR(s))) {
 					err = PTR_ERR(s);
-					break;
+					current->link_count--;
+					nd->depth--;
+					return err;
 				}
 				err = 0;
 				if (likely(s)) {
Signature · 1 line
2.1.4
author maintainer
#146 lore raw

PATCH v3 39/110 link_path_walk: don't bother with walk_component() after jumping link

In reply to Al Viro

From: Al Viro <[email protected]>

... it does nothing if nd->last_type is LAST_BIND.

---
 fs/namei.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
fs/namei.c +7 -3
index 9937470..ee083f9 100644
@@ -1797,7 +1797,11 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 					return err;
 				}
 				err = 0;
-				if (likely(s)) {
+				if (unlikely(!s)) {
+					/* jumped */
+					put_link(nd, &link, cookie);
+					break;
+				} else {
 					if (*s == '/') {
 						if (!nd->root.mnt)
 							set_root(nd);
@@ -1812,9 +1816,9 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 						put_link(nd, &link, cookie);
 						break;
 					}
+					err = walk_component(nd, LOOKUP_FOLLOW);
+					put_link(nd, &link, cookie);
 				}
-				err = walk_component(nd, LOOKUP_FOLLOW);
-				put_link(nd, &link, cookie);
 			} while (err > 0);
 
 			current->link_count--;
Signature · 1 line
2.1.4
author maintainer
#147 lore raw

PATCH v3 40/110 link_path_walk: turn inner loop into explicit goto

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 61 ++++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 32 insertions(+), 29 deletions(-)
fs/namei.c +32 -29
index ee083f9..6a0dd07 100644
@@ -1775,6 +1775,10 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			return err;
 
 		if (err) {
+			struct path link;
+			void *cookie;
+			const char *s;
+
 			if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
 				path_put_conditional(&nd->link, nd);
 				path_put(&nd->path);
@@ -1785,41 +1789,40 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			nd->depth++;
 			current->link_count++;
 
-			do {
-				struct path link = nd->link;
-				void *cookie;
-				const char *s = get_link(&link, nd, &cookie);
-
-				if (unlikely(IS_ERR(s))) {
-					err = PTR_ERR(s);
-					current->link_count--;
-					nd->depth--;
-					return err;
+loop:	/* will be gone very soon */
+			link = nd->link;
+			s = get_link(&link, nd, &cookie);
+
+			if (unlikely(IS_ERR(s))) {
+				err = PTR_ERR(s);
+				current->link_count--;
+				nd->depth--;
+				return err;
+			}
+			err = 0;
+			if (unlikely(!s)) {
+				/* jumped */
+				put_link(nd, &link, cookie);
+			} else {
+				if (*s == '/') {
+					if (!nd->root.mnt)
+						set_root(nd);
+					path_put(&nd->path);
+					nd->path = nd->root;
+					path_get(&nd->root);
+					nd->flags |= LOOKUP_JUMPED;
 				}
-				err = 0;
-				if (unlikely(!s)) {
-					/* jumped */
+				nd->inode = nd->path.dentry->d_inode;
+				err = link_path_walk(s, nd);
+				if (unlikely(err)) {
 					put_link(nd, &link, cookie);
-					break;
 				} else {
-					if (*s == '/') {
-						if (!nd->root.mnt)
-							set_root(nd);
-						path_put(&nd->path);
-						nd->path = nd->root;
-						path_get(&nd->root);
-						nd->flags |= LOOKUP_JUMPED;
-					}
-					nd->inode = nd->path.dentry->d_inode;
-					err = link_path_walk(s, nd);
-					if (unlikely(err)) {
-						put_link(nd, &link, cookie);
-						break;
-					}
 					err = walk_component(nd, LOOKUP_FOLLOW);
 					put_link(nd, &link, cookie);
+					if (err > 0)
+						goto loop;
 				}
-			} while (err > 0);
+			}
 
 			current->link_count--;
 			nd->depth--;
Signature · 1 line
2.1.4
author maintainer
#148 lore raw

PATCH v3 41/110 link_path_walk: massage a bit more

In reply to Al Viro

From: Al Viro <[email protected]>

Pull the block after the if-else in the end of what used to be do-while body into all branches there. We are almost done with the massage...

---
 fs/namei.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)
fs/namei.c +13 -6
index 6a0dd07..b7ba718 100644
@@ -1803,6 +1803,8 @@ loop:	/* will be gone very soon */
 			if (unlikely(!s)) {
 				/* jumped */
 				put_link(nd, &link, cookie);
+				current->link_count--;
+				nd->depth--;
 			} else {
 				if (*s == '/') {
 					if (!nd->root.mnt)
@@ -1816,18 +1818,23 @@ loop:	/* will be gone very soon */
 				err = link_path_walk(s, nd);
 				if (unlikely(err)) {
 					put_link(nd, &link, cookie);
+					current->link_count--;
+					nd->depth--;
+					return err;
 				} else {
 					err = walk_component(nd, LOOKUP_FOLLOW);
 					put_link(nd, &link, cookie);
-					if (err > 0)
+					current->link_count--;
+					nd->depth--;
+					if (err < 0)
+						return err;
+					if (err > 0) {
+						current->link_count++;
+						nd->depth++;
 						goto loop;
+					}
 				}
 			}
-
-			current->link_count--;
-			nd->depth--;
-			if (err)
-				return err;
 		}
 		if (!d_can_lookup(nd->path.dentry)) {
 			err = -ENOTDIR;
Signature · 1 line
2.1.4
author maintainer
#149 lore raw

PATCH v3 42/110 link_path_walk: get rid of duplication

In reply to Al Viro

From: Al Viro <[email protected]>

What we do after the second walk_component() + put_link() + depth decrement in there is exactly equivalent to what's done right after the first walk_component(). Easy to verify and not at all surprising, seeing that there we have just walked the last component of nested symlink.

---
 fs/namei.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)
fs/namei.c +2 -8
index b7ba718..9f45d33 100644
@@ -1771,6 +1771,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			return 0;
 
 		err = walk_component(nd, LOOKUP_FOLLOW);
+Walked:
 		if (err < 0)
 			return err;
 
@@ -1789,7 +1790,6 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			nd->depth++;
 			current->link_count++;
 
-loop:	/* will be gone very soon */
 			link = nd->link;
 			s = get_link(&link, nd, &cookie);
 
@@ -1826,13 +1826,7 @@ loop:	/* will be gone very soon */
 					put_link(nd, &link, cookie);
 					current->link_count--;
 					nd->depth--;
-					if (err < 0)
-						return err;
-					if (err > 0) {
-						current->link_count++;
-						nd->depth++;
-						goto loop;
-					}
+					goto Walked;
 				}
 			}
 		}
Signature · 1 line
2.1.4
author maintainer
#150 lore raw

PATCH v3 43/110 link_path_walk: final preparations to killing recursion

In reply to Al Viro

From: Al Viro <[email protected]>

reduce the number of returns in there - turn all places where it returns zero into goto OK and places where it returns non-zero into goto Err. The only non-trivial detail is that all breaks in the loop are guaranteed to be with non-zero err.

---
 fs/namei.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
fs/namei.c +11 -7
index 9f45d33..b469ce2 100644
@@ -1716,7 +1716,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 	while (*name=='/')
 		name++;
 	if (!*name)
-		return 0;
+		goto OK;
 
 	/* At this point we know we have a real path component. */
 	for(;;) {
@@ -1759,7 +1759,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 
 		name += hashlen_len(hash_len);
 		if (!*name)
-			return 0;
+			goto OK;
 		/*
 		 * If it wasn't NUL, we know it was '/'. Skip that
 		 * slash, and continue until no more slashes.
@@ -1768,12 +1768,12 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 			name++;
 		} while (unlikely(*name == '/'));
 		if (!*name)
-			return 0;
+			goto OK;
 
 		err = walk_component(nd, LOOKUP_FOLLOW);
 Walked:
 		if (err < 0)
-			return err;
+			goto Err;
 
 		if (err) {
 			struct path link;
@@ -1783,7 +1783,8 @@ Walked:
 			if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
 				path_put_conditional(&nd->link, nd);
 				path_put(&nd->path);
-				return -ELOOP;
+				err = -ELOOP;
+				goto Err;
 			}
 			BUG_ON(nd->depth >= MAX_NESTED_LINKS);
 
@@ -1797,7 +1798,7 @@ Walked:
 				err = PTR_ERR(s);
 				current->link_count--;
 				nd->depth--;
-				return err;
+				goto Err;
 			}
 			err = 0;
 			if (unlikely(!s)) {
@@ -1820,7 +1821,7 @@ Walked:
 					put_link(nd, &link, cookie);
 					current->link_count--;
 					nd->depth--;
-					return err;
+					goto Err;
 				} else {
 					err = walk_component(nd, LOOKUP_FOLLOW);
 					put_link(nd, &link, cookie);
@@ -1836,7 +1837,10 @@ Walked:
 		}
 	}
 	terminate_walk(nd);
+Err:
 	return err;
+OK:
+	return 0;
 }
 
 static int path_init(int dfd, const struct filename *name, unsigned int flags,
Signature · 1 line
2.1.4
author maintainer
#151 lore raw

PATCH v3 44/110 link_path_walk: kill the recursion

In reply to Al Viro

From: Al Viro <[email protected]>

absolutely straightforward now - the only variables we need to preserve across the recursive call are name, link and cookie, and recursion depth is limited (and can is equal to nd->depth). So arrange an array of triples to hold instances of those and be done with that.

---
 fs/namei.c | 40 +++++++++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 11 deletions(-)
fs/namei.c +29 -11
index b469ce2..9844bb2 100644
@@ -1711,8 +1711,14 @@ static inline u64 hash_name(const char *name)
  */
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
+	struct saved {
+		struct path link;
+		void *cookie;
+		const char *name;
+	} stack[MAX_NESTED_LINKS], *last = stack + nd->depth - 1;
 	int err;
-	
+
+start:
 	while (*name=='/')
 		name++;
 	if (!*name)
@@ -1776,8 +1782,6 @@ Walked:
 			goto Err;
 
 		if (err) {
-			struct path link;
-			void *cookie;
 			const char *s;
 
 			if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
@@ -1790,22 +1794,25 @@ Walked:
 
 			nd->depth++;
 			current->link_count++;
+			last++;
 
-			link = nd->link;
-			s = get_link(&link, nd, &cookie);
+			last->link = nd->link;
+			s = get_link(&last->link, nd, &last->cookie);
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
 				current->link_count--;
 				nd->depth--;
+				last--;
 				goto Err;
 			}
 			err = 0;
 			if (unlikely(!s)) {
 				/* jumped */
-				put_link(nd, &link, cookie);
+				put_link(nd, &last->link, last->cookie);
 				current->link_count--;
 				nd->depth--;
+				last--;
 			} else {
 				if (*s == '/') {
 					if (!nd->root.mnt)
@@ -1816,17 +1823,24 @@ Walked:
 					nd->flags |= LOOKUP_JUMPED;
 				}
 				nd->inode = nd->path.dentry->d_inode;
-				err = link_path_walk(s, nd);
+				last->name = name;
+				name = s;
+				goto start;
+
+back:
+				name = last->name;
 				if (unlikely(err)) {
-					put_link(nd, &link, cookie);
+					put_link(nd, &last->link, last->cookie);
 					current->link_count--;
 					nd->depth--;
+					last--;
 					goto Err;
 				} else {
 					err = walk_component(nd, LOOKUP_FOLLOW);
-					put_link(nd, &link, cookie);
+					put_link(nd, &last->link, last->cookie);
 					current->link_count--;
 					nd->depth--;
+					last--;
 					goto Walked;
 				}
 			}
@@ -1838,9 +1852,13 @@ Walked:
 	}
 	terminate_walk(nd);
 Err:
-	return err;
+	if (likely(!nd->depth))
+		return err;
+	goto back;
 OK:
-	return 0;
+	if (likely(!nd->depth))
+		return 0;
+	goto back;
 }
 
 static int path_init(int dfd, const struct filename *name, unsigned int flags,
Signature · 1 line
2.1.4
author maintainer
#152 lore raw

PATCH v3 45/110 link_path_walk: split "return from recursive call" path

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 40 +++++++++++++++++-----------------------
 1 file changed, 17 insertions(+), 23 deletions(-)
fs/namei.c +17 -23
index 9844bb2..5b0edd3 100644
@@ -1826,23 +1826,6 @@ Walked:
 				last->name = name;
 				name = s;
 				goto start;
-
-back:
-				name = last->name;
-				if (unlikely(err)) {
-					put_link(nd, &last->link, last->cookie);
-					current->link_count--;
-					nd->depth--;
-					last--;
-					goto Err;
-				} else {
-					err = walk_component(nd, LOOKUP_FOLLOW);
-					put_link(nd, &last->link, last->cookie);
-					current->link_count--;
-					nd->depth--;
-					last--;
-					goto Walked;
-				}
 			}
 		}
 		if (!d_can_lookup(nd->path.dentry)) {
@@ -1852,13 +1835,24 @@ back:
 	}
 	terminate_walk(nd);
 Err:
-	if (likely(!nd->depth))
-		return err;
-	goto back;
+	while (unlikely(nd->depth)) {
+		put_link(nd, &last->link, last->cookie);
+		current->link_count--;
+		nd->depth--;
+		last--;
+	}
+	return err;
 OK:
-	if (likely(!nd->depth))
-		return 0;
-	goto back;
+	if (unlikely(nd->depth)) {
+		name = last->name;
+		err = walk_component(nd, LOOKUP_FOLLOW);
+		put_link(nd, &last->link, last->cookie);
+		current->link_count--;
+		nd->depth--;
+		last--;
+		goto Walked;
+	}
+	return 0;
 }
 
 static int path_init(int dfd, const struct filename *name, unsigned int flags,
Signature · 1 line
2.1.4
author maintainer
#153 lore raw

PATCH v3 46/110 link_path_walk: cleanup - turn goto start; into continue;

In reply to Al Viro

From: Al Viro <[email protected]>

Deal with skipping leading slashes before what used to be the recursive call. That way we can get rid of that goto completely.

---
 fs/namei.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
fs/namei.c +6 -3
index 5b0edd3..c105107 100644
@@ -1718,11 +1718,10 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 	} stack[MAX_NESTED_LINKS], *last = stack + nd->depth - 1;
 	int err;
 
-start:
 	while (*name=='/')
 		name++;
 	if (!*name)
-		goto OK;
+		return 0;
 
 	/* At this point we know we have a real path component. */
 	for(;;) {
@@ -1821,11 +1820,15 @@ Walked:
 					nd->path = nd->root;
 					path_get(&nd->root);
 					nd->flags |= LOOKUP_JUMPED;
+					while (unlikely(*++s == '/'))
+						;
 				}
 				nd->inode = nd->path.dentry->d_inode;
 				last->name = name;
+				if (!*s)
+					goto OK;
 				name = s;
-				goto start;
+				continue;
 			}
 		}
 		if (!d_can_lookup(nd->path.dentry)) {
Signature · 1 line
2.1.4
author maintainer
#154 lore raw

PATCH v3 47/110 namei: move link/cookie pairs into nameidata

In reply to Al Viro

From: Al Viro <[email protected]>

Array of MAX_NESTED_LINKS + 1 elements put into nameidata; what used to be a local array in link_path_walk() occupies entries 1 .. MAX_NESTED_LINKS in it, link and cookie from the trailing symlink handling loops - entry 0.

This is _not_ the final arrangement; just an easily verified incremental step.

---
 fs/namei.c | 35 ++++++++++++++++++-----------------
 1 file changed, 18 insertions(+), 17 deletions(-)
fs/namei.c +18 -17
index c105107..d4b238a 100644
@@ -505,6 +505,11 @@ struct nameidata {
 	int		last_type;
 	unsigned	depth;
 	struct file	*base;
+	struct saved {
+		struct path link;
+		void *cookie;
+		const char *name;
+	} stack[MAX_NESTED_LINKS + 1];
 };
 
 /*
@@ -1711,11 +1716,7 @@ static inline u64 hash_name(const char *name)
  */
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
-	struct saved {
-		struct path link;
-		void *cookie;
-		const char *name;
-	} stack[MAX_NESTED_LINKS], *last = stack + nd->depth - 1;
+	struct saved *last = nd->stack;
 	int err;
 
 	while (*name=='/')
@@ -2030,13 +2031,13 @@ static int path_lookupat(int dfd, const struct filename *name,
 	if (!err && !(flags & LOOKUP_PARENT)) {
 		err = lookup_last(nd);
 		while (err > 0) {
-			void *cookie;
-			struct path link = nd->link;
-			err = trailing_symlink(&link, nd, &cookie);
+			nd->stack[0].link = nd->link;
+			err = trailing_symlink(&nd->stack[0].link,
+						nd, &nd->stack[0].cookie);
 			if (err)
 				break;
 			err = lookup_last(nd);
-			put_link(nd, &link, cookie);
+			put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
 		}
 	}
 
@@ -2376,13 +2377,13 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 
 	err = mountpoint_last(nd, path);
 	while (err > 0) {
-		void *cookie;
-		struct path link = *path;
-		err = trailing_symlink(&link, nd, &cookie);
+		nd->stack[0].link = nd->link;
+		err = trailing_symlink(&nd->stack[0].link,
+					nd, &nd->stack[0].cookie);
 		if (err)
 			break;
 		err = mountpoint_last(nd, path);
-		put_link(nd, &link, cookie);
+		put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
 	}
 out:
 	path_cleanup(nd);
@@ -3259,14 +3260,14 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 
 	error = do_last(nd, file, op, &opened, pathname);
 	while (unlikely(error > 0)) { /* trailing symlink */
-		struct path link = nd->link;
-		void *cookie;
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
-		error = trailing_symlink(&link, nd, &cookie);
+		nd->stack[0].link = nd->link;
+		error= trailing_symlink(&nd->stack[0].link,
+					nd, &nd->stack[0].cookie);
 		if (unlikely(error))
 			break;
 		error = do_last(nd, file, op, &opened, pathname);
-		put_link(nd, &link, cookie);
+		put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
 	}
 out:
 	path_cleanup(nd);
Signature · 1 line
2.1.4
author maintainer
#155 lore raw

PATCH v3 48/110 namei: trim redundant arguments of trailing_symlink()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 21 ++++++++-------------
 1 file changed, 8 insertions(+), 13 deletions(-)
fs/namei.c +8 -13
index d4b238a..8c4f2af 100644
@@ -1971,14 +1971,15 @@ static void path_cleanup(struct nameidata *nd)
 		fput(nd->base);
 }
 
-static int trailing_symlink(struct path *link, struct nameidata *nd, void **p)
+static int trailing_symlink(struct nameidata *nd)
 {
 	const char *s;
-	int error = may_follow_link(link, nd);
+	int error = may_follow_link(&nd->link, nd);
 	if (unlikely(error))
 		return error;
 	nd->flags |= LOOKUP_PARENT;
-	s = get_link(link, nd, p);
+	nd->stack[0].link = nd->link;
+	s = get_link(&nd->stack[0].link, nd, &nd->stack[0].cookie);
 	if (unlikely(IS_ERR(s)))
 		return PTR_ERR(s);
 	if (unlikely(!s))
@@ -1994,7 +1995,7 @@ static int trailing_symlink(struct path *link, struct nameidata *nd, void **p)
 	nd->inode = nd->path.dentry->d_inode;
 	error = link_path_walk(s, nd);
 	if (unlikely(error))
-		put_link(nd, link, *p);
+		put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
 	return error;
 }
 
@@ -2031,9 +2032,7 @@ static int path_lookupat(int dfd, const struct filename *name,
 	if (!err && !(flags & LOOKUP_PARENT)) {
 		err = lookup_last(nd);
 		while (err > 0) {
-			nd->stack[0].link = nd->link;
-			err = trailing_symlink(&nd->stack[0].link,
-						nd, &nd->stack[0].cookie);
+			err = trailing_symlink(nd);
 			if (err)
 				break;
 			err = lookup_last(nd);
@@ -2377,9 +2376,7 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 
 	err = mountpoint_last(nd, path);
 	while (err > 0) {
-		nd->stack[0].link = nd->link;
-		err = trailing_symlink(&nd->stack[0].link,
-					nd, &nd->stack[0].cookie);
+		err = trailing_symlink(nd);
 		if (err)
 			break;
 		err = mountpoint_last(nd, path);
@@ -3261,9 +3258,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 	error = do_last(nd, file, op, &opened, pathname);
 	while (unlikely(error > 0)) { /* trailing symlink */
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
-		nd->stack[0].link = nd->link;
-		error= trailing_symlink(&nd->stack[0].link,
-					nd, &nd->stack[0].cookie);
+		error = trailing_symlink(nd);
 		if (unlikely(error))
 			break;
 		error = do_last(nd, file, op, &opened, pathname);
Signature · 1 line
2.1.4
author maintainer
#156 lore raw

PATCH v3 49/110 namei: trim redundant arguments of fs/namei.c:put_link()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)
fs/namei.c +13 -12
index 8c4f2af..1ac3217 100644
@@ -718,12 +718,13 @@ void nd_jump_link(struct nameidata *nd, struct path *path)
 	nd->flags |= LOOKUP_JUMPED;
 }
 
-static inline void put_link(struct nameidata *nd, struct path *link, void *cookie)
+static inline void put_link(struct nameidata *nd)
 {
-	struct inode *inode = link->dentry->d_inode;
-	if (cookie && inode->i_op->put_link)
-		inode->i_op->put_link(link->dentry, cookie);
-	path_put(link);
+	struct saved *last = nd->stack + nd->depth;
+	struct inode *inode = last->link.dentry->d_inode;
+	if (last->cookie && inode->i_op->put_link)
+		inode->i_op->put_link(last->link.dentry, last->cookie);
+	path_put(&last->link);
 }
 
 int sysctl_protected_symlinks __read_mostly = 0;
@@ -1809,7 +1810,7 @@ Walked:
 			err = 0;
 			if (unlikely(!s)) {
 				/* jumped */
-				put_link(nd, &last->link, last->cookie);
+				put_link(nd);
 				current->link_count--;
 				nd->depth--;
 				last--;
@@ -1840,7 +1841,7 @@ Walked:
 	terminate_walk(nd);
 Err:
 	while (unlikely(nd->depth)) {
-		put_link(nd, &last->link, last->cookie);
+		put_link(nd);
 		current->link_count--;
 		nd->depth--;
 		last--;
@@ -1850,7 +1851,7 @@ OK:
 	if (unlikely(nd->depth)) {
 		name = last->name;
 		err = walk_component(nd, LOOKUP_FOLLOW);
-		put_link(nd, &last->link, last->cookie);
+		put_link(nd);
 		current->link_count--;
 		nd->depth--;
 		last--;
@@ -1995,7 +1996,7 @@ static int trailing_symlink(struct nameidata *nd)
 	nd->inode = nd->path.dentry->d_inode;
 	error = link_path_walk(s, nd);
 	if (unlikely(error))
-		put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
+		put_link(nd);
 	return error;
 }
 
@@ -2036,7 +2037,7 @@ static int path_lookupat(int dfd, const struct filename *name,
 			if (err)
 				break;
 			err = lookup_last(nd);
-			put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
+			put_link(nd);
 		}
 	}
 
@@ -2380,7 +2381,7 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 		if (err)
 			break;
 		err = mountpoint_last(nd, path);
-		put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
+		put_link(nd);
 	}
 out:
 	path_cleanup(nd);
@@ -3262,7 +3263,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		if (unlikely(error))
 			break;
 		error = do_last(nd, file, op, &opened, pathname);
-		put_link(nd, &nd->stack[0].link, nd->stack[0].cookie);
+		put_link(nd);
 	}
 out:
 	path_cleanup(nd);
Signature · 1 line
2.1.4
author maintainer
#157 lore raw

PATCH v3 50/110 namei: trim the arguments of get_link()

In reply to Al Viro

From: Al Viro <[email protected]>

same story as the previous commit

---
 fs/namei.c | 45 +++++++++++++++++++++------------------------
 1 file changed, 21 insertions(+), 24 deletions(-)
fs/namei.c +21 -24
index 1ac3217..e5715a5 100644
@@ -843,27 +843,33 @@ static int may_linkat(struct path *link)
 	return -EPERM;
 }
 
-static __always_inline const char *
-get_link(struct path *link, struct nameidata *nd, void **p)
+static __always_inline
+const char *get_link(struct nameidata *nd)
 {
-	struct dentry *dentry = link->dentry;
+	struct saved *last = nd->stack + nd->depth;
+	struct dentry *dentry = nd->link.dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 	const char *res;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 
-	if (link->mnt == nd->path.mnt)
-		mntget(link->mnt);
+	if (nd->link.mnt == nd->path.mnt)
+		mntget(nd->link.mnt);
 
-	res = ERR_PTR(-ELOOP);
-	if (unlikely(current->total_link_count >= 40))
-		goto out;
+	if (unlikely(current->total_link_count >= 40)) {
+		path_put(&nd->path);
+		path_put(&nd->link);
+		return ERR_PTR(-ELOOP);
+	}
+
+	last->link = nd->link;
+	last->cookie = NULL;
 
 	cond_resched();
 	current->total_link_count++;
 
-	touch_atime(link);
+	touch_atime(&last->link);
 
 	error = security_inode_follow_link(dentry);
 	res = ERR_PTR(error);
@@ -871,14 +877,13 @@ get_link(struct path *link, struct nameidata *nd, void **p)
 		goto out;
 
 	nd->last_type = LAST_BIND;
-	*p = NULL;
 	res = inode->i_link;
 	if (!res) {
-		res = inode->i_op->follow_link(dentry, p, nd);
+		res = inode->i_op->follow_link(dentry, &last->cookie, nd);
 		if (IS_ERR(res)) {
 out:
 			path_put(&nd->path);
-			path_put(link);
+			path_put(&last->link);
 		}
 	}
 	return res;
@@ -1717,7 +1722,6 @@ static inline u64 hash_name(const char *name)
  */
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
-	struct saved *last = nd->stack;
 	int err;
 
 	while (*name=='/')
@@ -1795,16 +1799,13 @@ Walked:
 
 			nd->depth++;
 			current->link_count++;
-			last++;
 
-			last->link = nd->link;
-			s = get_link(&last->link, nd, &last->cookie);
+			s = get_link(nd);
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
 				current->link_count--;
 				nd->depth--;
-				last--;
 				goto Err;
 			}
 			err = 0;
@@ -1813,7 +1814,6 @@ Walked:
 				put_link(nd);
 				current->link_count--;
 				nd->depth--;
-				last--;
 			} else {
 				if (*s == '/') {
 					if (!nd->root.mnt)
@@ -1826,7 +1826,7 @@ Walked:
 						;
 				}
 				nd->inode = nd->path.dentry->d_inode;
-				last->name = name;
+				nd->stack[nd->depth].name = name;
 				if (!*s)
 					goto OK;
 				name = s;
@@ -1844,17 +1844,15 @@ Err:
 		put_link(nd);
 		current->link_count--;
 		nd->depth--;
-		last--;
 	}
 	return err;
 OK:
 	if (unlikely(nd->depth)) {
-		name = last->name;
+		name = nd->stack[nd->depth].name;
 		err = walk_component(nd, LOOKUP_FOLLOW);
 		put_link(nd);
 		current->link_count--;
 		nd->depth--;
-		last--;
 		goto Walked;
 	}
 	return 0;
@@ -1979,8 +1977,7 @@ static int trailing_symlink(struct nameidata *nd)
 	if (unlikely(error))
 		return error;
 	nd->flags |= LOOKUP_PARENT;
-	nd->stack[0].link = nd->link;
-	s = get_link(&nd->stack[0].link, nd, &nd->stack[0].cookie);
+	s = get_link(nd);
 	if (unlikely(IS_ERR(s)))
 		return PTR_ERR(s);
 	if (unlikely(!s))
Signature · 1 line
2.1.4
author maintainer
#158 lore raw

PATCH v3 51/110 namei: remove restrictions on nesting depth

In reply to Al Viro

From: Al Viro <[email protected]>

The only restriction is that on the total amount of symlinks crossed; how they are nested does not matter

---
 fs/namei.c            | 66 ++++++++++++++++++++++++++++++++++++++++-----------
 include/linux/namei.h |  2 ++
 2 files changed, 54 insertions(+), 14 deletions(-)
fs/namei.c +52 -14
index e5715a5..1ae34cd 100644
@@ -492,6 +492,7 @@ void path_put(const struct path *path)
 }
 EXPORT_SYMBOL(path_put);
 
+#define EMBEDDED_LEVELS 2
 struct nameidata {
 	struct path	path;
 	union {
@@ -509,9 +510,42 @@ struct nameidata {
 		struct path link;
 		void *cookie;
 		const char *name;
-	} stack[MAX_NESTED_LINKS + 1];
+	} *stack, internal[EMBEDDED_LEVELS];
 };
 
+static void set_nameidata(struct nameidata *nd)
+{
+	nd->stack = nd->internal;
+}
+
+static void restore_nameidata(struct nameidata *nd)
+{
+	if (nd->stack != nd->internal) {
+		kfree(nd->stack);
+		nd->stack = nd->internal;
+	}
+}
+
+static int __nd_alloc_stack(struct nameidata *nd)
+{
+	struct saved *p = kmalloc((MAXSYMLINKS + 1) * sizeof(struct saved),
+				  GFP_KERNEL);
+	if (unlikely(!p))
+		return -ENOMEM;
+	memcpy(p, nd->internal, sizeof(nd->internal));
+	nd->stack = p;
+	return 0;
+}
+
+static inline int nd_alloc_stack(struct nameidata *nd)
+{
+	if (likely(nd->depth != EMBEDDED_LEVELS - 1))
+		return 0;
+	if (likely(nd->stack != nd->internal))
+		return 0;
+	return __nd_alloc_stack(nd);
+}
+
 /*
  * Path walking has 2 modes, rcu-walk and ref-walk (see
  * Documentation/filesystems/path-lookup.txt).  In situations when we can't
@@ -857,7 +891,7 @@ const char *get_link(struct nameidata *nd)
 	if (nd->link.mnt == nd->path.mnt)
 		mntget(nd->link.mnt);
 
-	if (unlikely(current->total_link_count >= 40)) {
+	if (unlikely(current->total_link_count >= MAXSYMLINKS)) {
 		path_put(&nd->path);
 		path_put(&nd->link);
 		return ERR_PTR(-ELOOP);
@@ -1789,22 +1823,18 @@ Walked:
 		if (err) {
 			const char *s;
 
-			if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
-				path_put_conditional(&nd->link, nd);
-				path_put(&nd->path);
-				err = -ELOOP;
-				goto Err;
+			err = nd_alloc_stack(nd);
+			if (unlikely(err)) {
+				path_to_nameidata(&nd->link, nd);
+				break;
 			}
-			BUG_ON(nd->depth >= MAX_NESTED_LINKS);
 
 			nd->depth++;
-			current->link_count++;
 
 			s = get_link(nd);
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
-				current->link_count--;
 				nd->depth--;
 				goto Err;
 			}
@@ -1812,7 +1842,6 @@ Walked:
 			if (unlikely(!s)) {
 				/* jumped */
 				put_link(nd);
-				current->link_count--;
 				nd->depth--;
 			} else {
 				if (*s == '/') {
@@ -1842,7 +1871,6 @@ Walked:
 Err:
 	while (unlikely(nd->depth)) {
 		put_link(nd);
-		current->link_count--;
 		nd->depth--;
 	}
 	return err;
@@ -1851,7 +1879,6 @@ OK:
 		name = nd->stack[nd->depth].name;
 		err = walk_component(nd, LOOKUP_FOLLOW);
 		put_link(nd);
-		current->link_count--;
 		nd->depth--;
 		goto Walked;
 	}
@@ -2055,7 +2082,11 @@ static int path_lookupat(int dfd, const struct filename *name,
 static int filename_lookup(int dfd, struct filename *name,
 				unsigned int flags, struct nameidata *nd)
 {
-	int retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
+	int retval;
+
+	set_nameidata(nd);
+	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
+
 	if (unlikely(retval == -ECHILD))
 		retval = path_lookupat(dfd, name, flags, nd);
 	if (unlikely(retval == -ESTALE))
@@ -2063,6 +2094,7 @@ static int filename_lookup(int dfd, struct filename *name,
 
 	if (likely(!retval))
 		audit_inode(name, nd->path.dentry, flags & LOOKUP_PARENT);
+	restore_nameidata(nd);
 	return retval;
 }
 
@@ -2393,6 +2425,7 @@ filename_mountpoint(int dfd, struct filename *name, struct path *path,
 	int error;
 	if (IS_ERR(name))
 		return PTR_ERR(name);
+	set_nameidata(&nd);
 	error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_RCU);
 	if (unlikely(error == -ECHILD))
 		error = path_mountpoint(dfd, name, path, &nd, flags);
@@ -2400,6 +2433,7 @@ filename_mountpoint(int dfd, struct filename *name, struct path *path,
 		error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_REVAL);
 	if (likely(!error))
 		audit_inode(name, path->dentry, 0);
+	restore_nameidata(&nd);
 	putname(name);
 	return error;
 }
@@ -3288,11 +3322,13 @@ struct file *do_filp_open(int dfd, struct filename *pathname,
 	int flags = op->lookup_flags;
 	struct file *filp;
 
+	set_nameidata(&nd);
 	filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
 	if (unlikely(filp == ERR_PTR(-ECHILD)))
 		filp = path_openat(dfd, pathname, &nd, op, flags);
 	if (unlikely(filp == ERR_PTR(-ESTALE)))
 		filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
+	restore_nameidata(&nd);
 	return filp;
 }
 
@@ -3306,6 +3342,7 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 
 	nd.root.mnt = mnt;
 	nd.root.dentry = dentry;
+	set_nameidata(&nd);
 
 	if (d_is_symlink(dentry) && op->intent & LOOKUP_OPEN)
 		return ERR_PTR(-ELOOP);
@@ -3319,6 +3356,7 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 		file = path_openat(-1, filename, &nd, op, flags);
 	if (unlikely(file == ERR_PTR(-ESTALE)))
 		file = path_openat(-1, filename, &nd, op, flags | LOOKUP_REVAL);
+	restore_nameidata(&nd);
 	putname(filename);
 	return file;
 }
include/linux/namei.h +2 -0
index a5d5bed..3a6cc96 100644
@@ -11,6 +11,8 @@ struct nameidata;
 
 enum { MAX_NESTED_LINKS = 8 };
 
+#define MAXSYMLINKS 40
+
 /*
  * Type of the last component on LOOKUP_PARENT
  */
Signature · 1 line
2.1.4
author maintainer
#159 lore raw

PATCH v3 52/110 link_path_walk: nd->depth massage, part 1

In reply to Al Viro

From: Al Viro <[email protected]>

nd->stack[0] is unused until the handling of trailing symlinks and we want to get rid of that. Having fucked that transformation up several times, I went for bloody pedantic series of provably equivalent transformations. Sorry.

Step 1: keep nd->depth higher by one in link_path_walk() - increment upon entry, decrement on exits, adjust the arithmetics inside and surround the calls of functions that care about nd->depth value (nd_alloc_stack(), get_link(), put_link()) with decrement/increment pairs.

---
 fs/namei.c | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)
fs/namei.c +17 -5
index 1ae34cd..e408f4d 100644
@@ -1763,6 +1763,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 	if (!*name)
 		return 0;
 
+	nd->depth++;
 	/* At this point we know we have a real path component. */
 	for(;;) {
 		u64 hash_len;
@@ -1823,15 +1824,18 @@ Walked:
 		if (err) {
 			const char *s;
 
+			nd->depth--;
 			err = nd_alloc_stack(nd);
+			nd->depth++;
 			if (unlikely(err)) {
 				path_to_nameidata(&nd->link, nd);
 				break;
 			}
 
 			nd->depth++;
-
+			nd->depth--;
 			s = get_link(nd);
+			nd->depth++;
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
@@ -1841,7 +1845,9 @@ Walked:
 			err = 0;
 			if (unlikely(!s)) {
 				/* jumped */
+				nd->depth--;
 				put_link(nd);
+				nd->depth++;
 				nd->depth--;
 			} else {
 				if (*s == '/') {
@@ -1855,7 +1861,7 @@ Walked:
 						;
 				}
 				nd->inode = nd->path.dentry->d_inode;
-				nd->stack[nd->depth].name = name;
+				nd->stack[nd->depth - 1].name = name;
 				if (!*s)
 					goto OK;
 				name = s;
@@ -1869,19 +1875,25 @@ Walked:
 	}
 	terminate_walk(nd);
 Err:
-	while (unlikely(nd->depth)) {
+	while (unlikely(nd->depth > 1)) {
+		nd->depth--;
 		put_link(nd);
+		nd->depth++;
 		nd->depth--;
 	}
+	nd->depth--;
 	return err;
 OK:
-	if (unlikely(nd->depth)) {
-		name = nd->stack[nd->depth].name;
+	if (unlikely(nd->depth > 1)) {
+		name = nd->stack[nd->depth - 1].name;
 		err = walk_component(nd, LOOKUP_FOLLOW);
+		nd->depth--;
 		put_link(nd);
+		nd->depth++;
 		nd->depth--;
 		goto Walked;
 	}
+	nd->depth--;
 	return 0;
 }
 
Signature · 1 line
2.1.4
author maintainer
#160 lore raw

PATCH v3 53/110 link_path_walk: nd->depth massage, part 2

In reply to Al Viro

From: Al Viro <[email protected]>

collapse adjacent increment/decrement pairs.

---
 fs/namei.c | 8 --------
 1 file changed, 8 deletions(-)
fs/namei.c +0 -8
index e408f4d..a403425 100644
@@ -1832,8 +1832,6 @@ Walked:
 				break;
 			}
 
-			nd->depth++;
-			nd->depth--;
 			s = get_link(nd);
 			nd->depth++;
 
@@ -1847,8 +1845,6 @@ Walked:
 				/* jumped */
 				nd->depth--;
 				put_link(nd);
-				nd->depth++;
-				nd->depth--;
 			} else {
 				if (*s == '/') {
 					if (!nd->root.mnt)
@@ -1878,8 +1874,6 @@ Err:
 	while (unlikely(nd->depth > 1)) {
 		nd->depth--;
 		put_link(nd);
-		nd->depth++;
-		nd->depth--;
 	}
 	nd->depth--;
 	return err;
@@ -1889,8 +1883,6 @@ OK:
 		err = walk_component(nd, LOOKUP_FOLLOW);
 		nd->depth--;
 		put_link(nd);
-		nd->depth++;
-		nd->depth--;
 		goto Walked;
 	}
 	nd->depth--;
Signature · 1 line
2.1.4
author maintainer
#161 lore raw

PATCH v3 54/110 link_path_walk: nd->depth massage, part 3

In reply to Al Viro

From: Al Viro <[email protected]>

remove decrement/increment surrounding nd_alloc_stack(), adjust the test in it.

---
 fs/namei.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
fs/namei.c +1 -3
index a403425..3df4731 100644
@@ -539,7 +539,7 @@ static int __nd_alloc_stack(struct nameidata *nd)
 
 static inline int nd_alloc_stack(struct nameidata *nd)
 {
-	if (likely(nd->depth != EMBEDDED_LEVELS - 1))
+	if (likely(nd->depth != EMBEDDED_LEVELS))
 		return 0;
 	if (likely(nd->stack != nd->internal))
 		return 0;
@@ -1824,9 +1824,7 @@ Walked:
 		if (err) {
 			const char *s;
 
-			nd->depth--;
 			err = nd_alloc_stack(nd);
-			nd->depth++;
 			if (unlikely(err)) {
 				path_to_nameidata(&nd->link, nd);
 				break;
Signature · 1 line
2.1.4
author maintainer
#162 lore raw

PATCH v3 55/110 link_path_walk: nd->depth massage, part 4

In reply to Al Viro

From: Al Viro <[email protected]>

lift increment/decrement into link_path_walk() callers.

---
 fs/namei.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)
fs/namei.c +6 -4
index 3df4731..cf5009b 100644
@@ -1763,7 +1763,6 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 	if (!*name)
 		return 0;
 
-	nd->depth++;
 	/* At this point we know we have a real path component. */
 	for(;;) {
 		u64 hash_len;
@@ -1873,7 +1872,6 @@ Err:
 		nd->depth--;
 		put_link(nd);
 	}
-	nd->depth--;
 	return err;
 OK:
 	if (unlikely(nd->depth > 1)) {
@@ -1883,7 +1881,6 @@ OK:
 		put_link(nd);
 		goto Walked;
 	}
-	nd->depth--;
 	return 0;
 }
 
@@ -1986,7 +1983,10 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 	return -ECHILD;
 done:
 	current->total_link_count = 0;
-	return link_path_walk(s, nd);
+	nd->depth++;
+	retval = link_path_walk(s, nd);
+	nd->depth--;
+	return retval;
 }
 
 static void path_cleanup(struct nameidata *nd)
@@ -2020,7 +2020,9 @@ static int trailing_symlink(struct nameidata *nd)
 		nd->flags |= LOOKUP_JUMPED;
 	}
 	nd->inode = nd->path.dentry->d_inode;
+	nd->depth++;
 	error = link_path_walk(s, nd);
+	nd->depth--;
 	if (unlikely(error))
 		put_link(nd);
 	return error;
Signature · 1 line
2.1.4
author maintainer
#163 lore raw

PATCH v3 56/110 trailing_symlink: nd->depth massage, part 5

In reply to Al Viro

From: Al Viro <[email protected]>

move increment of ->depth to the point where we'd discovered that get_link() has not returned an error, adjust exits accordingly.

---
 fs/namei.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)
fs/namei.c +10 -5
index cf5009b..5753f46 100644
@@ -2009,8 +2009,11 @@ static int trailing_symlink(struct nameidata *nd)
 	s = get_link(nd);
 	if (unlikely(IS_ERR(s)))
 		return PTR_ERR(s);
-	if (unlikely(!s))
+	nd->depth++;
+	if (unlikely(!s)) {
+		nd->depth--;
 		return 0;
+	}
 	if (*s == '/') {
 		if (!nd->root.mnt)
 			set_root(nd);
@@ -2020,12 +2023,14 @@ static int trailing_symlink(struct nameidata *nd)
 		nd->flags |= LOOKUP_JUMPED;
 	}
 	nd->inode = nd->path.dentry->d_inode;
-	nd->depth++;
 	error = link_path_walk(s, nd);
-	nd->depth--;
-	if (unlikely(error))
+	if (unlikely(error)) {
+		nd->depth--;
 		put_link(nd);
-	return error;
+		return error;
+	}
+	nd->depth--;
+	return 0;
 }
 
 static inline int lookup_last(struct nameidata *nd)
Signature · 1 line
2.1.4
author maintainer
#164 lore raw

PATCH v3 57/110 get_link: nd->depth massage, part 6

In reply to Al Viro

From: Al Viro <[email protected]>

make get_link() increment nd->depth on successful exit

---
 fs/namei.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
fs/namei.c +2 -3
index 5753f46..93b5f73 100644
@@ -918,8 +918,10 @@ const char *get_link(struct nameidata *nd)
 out:
 			path_put(&nd->path);
 			path_put(&last->link);
+			return res;
 		}
 	}
+	nd->depth++;
 	return res;
 }
 
@@ -1830,11 +1832,9 @@ Walked:
 			}
 
 			s = get_link(nd);
-			nd->depth++;
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
-				nd->depth--;
 				goto Err;
 			}
 			err = 0;
@@ -2009,7 +2009,6 @@ static int trailing_symlink(struct nameidata *nd)
 	s = get_link(nd);
 	if (unlikely(IS_ERR(s)))
 		return PTR_ERR(s);
-	nd->depth++;
 	if (unlikely(!s)) {
 		nd->depth--;
 		return 0;
Signature · 1 line
2.1.4
author maintainer
#165 lore raw

PATCH v3 58/110 trailing_symlink: nd->depth massage, part 7

In reply to Al Viro

From: Al Viro <[email protected]>

move decrement of nd->depth on successful returns into the callers.

---
 fs/namei.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
fs/namei.c +4 -4
index 93b5f73..9df1c7a 100644
@@ -2009,10 +2009,8 @@ static int trailing_symlink(struct nameidata *nd)
 	s = get_link(nd);
 	if (unlikely(IS_ERR(s)))
 		return PTR_ERR(s);
-	if (unlikely(!s)) {
-		nd->depth--;
+	if (unlikely(!s))
 		return 0;
-	}
 	if (*s == '/') {
 		if (!nd->root.mnt)
 			set_root(nd);
@@ -2028,7 +2026,6 @@ static int trailing_symlink(struct nameidata *nd)
 		put_link(nd);
 		return error;
 	}
-	nd->depth--;
 	return 0;
 }
 
@@ -2069,6 +2066,7 @@ static int path_lookupat(int dfd, const struct filename *name,
 			if (err)
 				break;
 			err = lookup_last(nd);
+			nd->depth--;
 			put_link(nd);
 		}
 	}
@@ -2418,6 +2416,7 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 		if (err)
 			break;
 		err = mountpoint_last(nd, path);
+		nd->depth--;
 		put_link(nd);
 	}
 out:
@@ -3302,6 +3301,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		if (unlikely(error))
 			break;
 		error = do_last(nd, file, op, &opened, pathname);
+		nd->depth--;
 		put_link(nd);
 	}
 out:
Signature · 1 line
2.1.4
author maintainer
#166 lore raw

PATCH v3 59/110 put_link: nd->depth massage, part 8

In reply to Al Viro

From: Al Viro <[email protected]>

all calls are preceded by decrement of nd->depth; move it into put_link() itself.

---
 fs/namei.c | 18 ++++--------------
 1 file changed, 4 insertions(+), 14 deletions(-)
fs/namei.c +4 -14
index 9df1c7a..1f61955 100644
@@ -754,7 +754,7 @@ void nd_jump_link(struct nameidata *nd, struct path *path)
 
 static inline void put_link(struct nameidata *nd)
 {
-	struct saved *last = nd->stack + nd->depth;
+	struct saved *last = nd->stack + --nd->depth;
 	struct inode *inode = last->link.dentry->d_inode;
 	if (last->cookie && inode->i_op->put_link)
 		inode->i_op->put_link(last->link.dentry, last->cookie);
@@ -1840,7 +1840,6 @@ Walked:
 			err = 0;
 			if (unlikely(!s)) {
 				/* jumped */
-				nd->depth--;
 				put_link(nd);
 			} else {
 				if (*s == '/') {
@@ -1868,16 +1867,13 @@ Walked:
 	}
 	terminate_walk(nd);
 Err:
-	while (unlikely(nd->depth > 1)) {
-		nd->depth--;
+	while (unlikely(nd->depth > 1))
 		put_link(nd);
-	}
 	return err;
 OK:
 	if (unlikely(nd->depth > 1)) {
 		name = nd->stack[nd->depth - 1].name;
 		err = walk_component(nd, LOOKUP_FOLLOW);
-		nd->depth--;
 		put_link(nd);
 		goto Walked;
 	}
@@ -2021,12 +2017,9 @@ static int trailing_symlink(struct nameidata *nd)
 	}
 	nd->inode = nd->path.dentry->d_inode;
 	error = link_path_walk(s, nd);
-	if (unlikely(error)) {
-		nd->depth--;
+	if (unlikely(error))
 		put_link(nd);
-		return error;
-	}
-	return 0;
+	return error;
 }
 
 static inline int lookup_last(struct nameidata *nd)
@@ -2066,7 +2059,6 @@ static int path_lookupat(int dfd, const struct filename *name,
 			if (err)
 				break;
 			err = lookup_last(nd);
-			nd->depth--;
 			put_link(nd);
 		}
 	}
@@ -2416,7 +2408,6 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 		if (err)
 			break;
 		err = mountpoint_last(nd, path);
-		nd->depth--;
 		put_link(nd);
 	}
 out:
@@ -3301,7 +3292,6 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		if (unlikely(error))
 			break;
 		error = do_last(nd, file, op, &opened, pathname);
-		nd->depth--;
 		put_link(nd);
 	}
 out:
Signature · 1 line
2.1.4
author maintainer
#167 lore raw

PATCH v3 60/110 link_path_walk: nd->depth massage, part 9

In reply to Al Viro

From: Al Viro <[email protected]>

Make link_path_walk() work with any value of nd->depth on entry - memorize it and use it in tests instead of comparing with 1. Don't bother with increment/decrement in path_init().

---
 fs/namei.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
fs/namei.c +4 -6
index 1f61955..bc6d67e 100644
@@ -1759,6 +1759,7 @@ static inline u64 hash_name(const char *name)
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
 	int err;
+	int orig_depth = nd->depth;
 
 	while (*name=='/')
 		name++;
@@ -1867,11 +1868,11 @@ Walked:
 	}
 	terminate_walk(nd);
 Err:
-	while (unlikely(nd->depth > 1))
+	while (unlikely(nd->depth > orig_depth))
 		put_link(nd);
 	return err;
 OK:
-	if (unlikely(nd->depth > 1)) {
+	if (unlikely(nd->depth > orig_depth)) {
 		name = nd->stack[nd->depth - 1].name;
 		err = walk_component(nd, LOOKUP_FOLLOW);
 		put_link(nd);
@@ -1979,10 +1980,7 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 	return -ECHILD;
 done:
 	current->total_link_count = 0;
-	nd->depth++;
-	retval = link_path_walk(s, nd);
-	nd->depth--;
-	return retval;
+	return link_path_walk(s, nd);
 }
 
 static void path_cleanup(struct nameidata *nd)
Signature · 1 line
2.1.4
author maintainer
#168 lore raw

PATCH v3 61/110 link_path_walk: nd->depth massage, part 10

In reply to Al Viro

From: Al Viro <[email protected]>

Get rid of orig_depth checks in OK: logics. If nd->depth is zero, we had been called from path_init() and we are done. If it is greater than 1, we are not done, whether we'd been called from path_init() or trailing_symlink(). And in case when it's 1, we might have been called from path_init() and reached the end of nested symlink (in which case nd->stack[0].name will point to the rest of pathname and we are not done) or from trailing_symlink(), in which case we are done.

Just have trailing_symlink() leave NULL in nd->stack[0].name and use that to discriminate between those cases.

---
 fs/namei.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
fs/namei.c +10 -7
index bc6d67e..6febe25 100644
@@ -1872,13 +1872,15 @@ Err:
 		put_link(nd);
 	return err;
 OK:
-	if (unlikely(nd->depth > orig_depth)) {
-		name = nd->stack[nd->depth - 1].name;
-		err = walk_component(nd, LOOKUP_FOLLOW);
-		put_link(nd);
-		goto Walked;
-	}
-	return 0;
+	if (!nd->depth)		/* called from path_init(), done */
+		return 0;
+	name = nd->stack[nd->depth - 1].name;
+	if (!name)		/* called from trailing_symlink(), done */
+		return 0;
+
+	err = walk_component(nd, LOOKUP_FOLLOW);
+	put_link(nd);
+	goto Walked;
 }
 
 static int path_init(int dfd, const struct filename *name, unsigned int flags,
@@ -2014,6 +2016,7 @@ static int trailing_symlink(struct nameidata *nd)
 		nd->flags |= LOOKUP_JUMPED;
 	}
 	nd->inode = nd->path.dentry->d_inode;
+	nd->stack[0].name = NULL;
 	error = link_path_walk(s, nd);
 	if (unlikely(error))
 		put_link(nd);
Signature · 1 line
2.1.4
author maintainer
#169 lore raw

PATCH v3 62/110 link_path_walk: end of nd->depth massage

In reply to Al Viro

From: Al Viro <[email protected]>

get rid of orig_depth - we only use it on error exit to tell whether to stop doing put_link() when depth reaches 0 (call from path_init()) or when it reaches 1 (call from trailing_symlink()). However, in the latter case the caller would immediately follow with one more put_link(). Just keep doing it until the depth reaches zero (and simplify trailing_symlink() as the result).

---
 fs/namei.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)
fs/namei.c +2 -6
index 6febe25..d12b16c 100644
@@ -1759,7 +1759,6 @@ static inline u64 hash_name(const char *name)
 static int link_path_walk(const char *name, struct nameidata *nd)
 {
 	int err;
-	int orig_depth = nd->depth;
 
 	while (*name=='/')
 		name++;
@@ -1868,7 +1867,7 @@ Walked:
 	}
 	terminate_walk(nd);
 Err:
-	while (unlikely(nd->depth > orig_depth))
+	while (unlikely(nd->depth))
 		put_link(nd);
 	return err;
 OK:
@@ -2017,10 +2016,7 @@ static int trailing_symlink(struct nameidata *nd)
 	}
 	nd->inode = nd->path.dentry->d_inode;
 	nd->stack[0].name = NULL;
-	error = link_path_walk(s, nd);
-	if (unlikely(error))
-		put_link(nd);
-	return error;
+	return link_path_walk(s, nd);
 }
 
 static inline int lookup_last(struct nameidata *nd)
Signature · 1 line
2.1.4
author maintainer
#170 lore raw

PATCH v3 63/110 namei: we never need more than MAXSYMLINKS entries in nd->stack

In reply to Al Viro

From: Al Viro <[email protected]>

The only reason why we needed one more was that purely nested MAXSYMLINKS symlinks could lead to path_init() using that many entries in addition to nd->stack[0] which it left unused.

That can't happen now - path_init() starts with entry 0 (and trailing_symlink() is called only when we'd already encountered one symlink, so no more than MAXSYMLINKS-1 are left).

---
 fs/namei.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namei.c +1 -1
index d12b16c..b939f48 100644
@@ -528,7 +528,7 @@ static void restore_nameidata(struct nameidata *nd)
 
 static int __nd_alloc_stack(struct nameidata *nd)
 {
-	struct saved *p = kmalloc((MAXSYMLINKS + 1) * sizeof(struct saved),
+	struct saved *p = kmalloc(MAXSYMLINKS * sizeof(struct saved),
 				  GFP_KERNEL);
 	if (unlikely(!p))
 		return -ENOMEM;
Signature · 1 line
2.1.4
author maintainer
#171 lore raw

PATCH v3 64/110 namei: lift (open-coded) terminate_walk() in follow_dotdot_rcu() into callers

In reply to Al Viro

From: Al Viro <[email protected]>

follow_dotdot_rcu() does an equivalent of terminate_walk() on failure; shifting it into callers makes for simpler rules and those callers already have terminate_walk() on other failure exits.

---
 fs/namei.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)
fs/namei.c +10 -9
index b939f48..25cd935 100644
@@ -1230,10 +1230,6 @@ static int follow_dotdot_rcu(struct nameidata *nd)
 	return 0;
 
 failed:
-	nd->flags &= ~LOOKUP_RCU;
-	if (!(nd->flags & LOOKUP_ROOT))
-		nd->root.mnt = NULL;
-	rcu_read_unlock();
 	return -ECHILD;
 }
 
@@ -1551,8 +1547,7 @@ static inline int handle_dots(struct nameidata *nd, int type)
 {
 	if (type == LAST_DOTDOT) {
 		if (nd->flags & LOOKUP_RCU) {
-			if (follow_dotdot_rcu(nd))
-				return -ECHILD;
+			return follow_dotdot_rcu(nd);
 		} else
 			follow_dotdot(nd);
 	}
@@ -1592,8 +1587,12 @@ static int walk_component(struct nameidata *nd, int follow)
 	 * to be able to know about the current root directory and
 	 * parent relationships.
 	 */
-	if (unlikely(nd->last_type != LAST_NORM))
-		return handle_dots(nd, nd->last_type);
+	if (unlikely(nd->last_type != LAST_NORM)) {
+		err = handle_dots(nd, nd->last_type);
+		if (err)
+			goto out_err;
+		return 0;
+	}
 	err = lookup_fast(nd, &path, &inode);
 	if (unlikely(err)) {
 		if (err < 0)
@@ -2981,8 +2980,10 @@ static int do_last(struct nameidata *nd,
 
 	if (nd->last_type != LAST_NORM) {
 		error = handle_dots(nd, nd->last_type);
-		if (error)
+		if (unlikely(error)) {
+			terminate_walk(nd);
 			return error;
+		}
 		goto finish_open;
 	}
 
Signature · 1 line
2.1.4
author maintainer
#172 lore raw

PATCH v3 65/110 lift terminate_walk() into callers of walk_component()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 25 +++++++++++--------------
 1 file changed, 11 insertions(+), 14 deletions(-)
fs/namei.c +11 -14
index 25cd935..d99eaac 100644
@@ -1587,20 +1587,16 @@ static int walk_component(struct nameidata *nd, int follow)
 	 * to be able to know about the current root directory and
 	 * parent relationships.
 	 */
-	if (unlikely(nd->last_type != LAST_NORM)) {
-		err = handle_dots(nd, nd->last_type);
-		if (err)
-			goto out_err;
-		return 0;
-	}
+	if (unlikely(nd->last_type != LAST_NORM))
+		return handle_dots(nd, nd->last_type);
 	err = lookup_fast(nd, &path, &inode);
 	if (unlikely(err)) {
 		if (err < 0)
-			goto out_err;
+			return err;
 
 		err = lookup_slow(nd, &path);
 		if (err < 0)
-			goto out_err;
+			return err;
 
 		inode = path.dentry->d_inode;
 		err = -ENOENT;
@@ -1612,8 +1608,7 @@ static int walk_component(struct nameidata *nd, int follow)
 		if (nd->flags & LOOKUP_RCU) {
 			if (unlikely(nd->path.mnt != path.mnt ||
 				     unlazy_walk(nd, path.dentry))) {
-				err = -ECHILD;
-				goto out_err;
+				return -ECHILD;
 			}
 		}
 		BUG_ON(inode != path.dentry->d_inode);
@@ -1626,8 +1621,6 @@ static int walk_component(struct nameidata *nd, int follow)
 
 out_path_put:
 	path_to_nameidata(&path, nd);
-out_err:
-	terminate_walk(nd);
 	return err;
 }
 
@@ -1819,7 +1812,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 		err = walk_component(nd, LOOKUP_FOLLOW);
 Walked:
 		if (err < 0)
-			goto Err;
+			break;
 
 		if (err) {
 			const char *s;
@@ -2020,11 +2013,15 @@ static int trailing_symlink(struct nameidata *nd)
 
 static inline int lookup_last(struct nameidata *nd)
 {
+	int err;
 	if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	nd->flags &= ~LOOKUP_PARENT;
-	return walk_component(nd, nd->flags & LOOKUP_FOLLOW);
+	err = walk_component(nd, nd->flags & LOOKUP_FOLLOW);
+	if (err < 0)
+		terminate_walk(nd);
+	return err;
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
Signature · 1 line
2.1.4
author maintainer
#173 lore raw

PATCH v3 66/110 namei: lift (open-coded) terminate_walk() into callers of get_link()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)
fs/namei.c +4 -5
index d99eaac..db21e04 100644
@@ -892,7 +892,6 @@ const char *get_link(struct nameidata *nd)
 		mntget(nd->link.mnt);
 
 	if (unlikely(current->total_link_count >= MAXSYMLINKS)) {
-		path_put(&nd->path);
 		path_put(&nd->link);
 		return ERR_PTR(-ELOOP);
 	}
@@ -916,7 +915,6 @@ const char *get_link(struct nameidata *nd)
 		res = inode->i_op->follow_link(dentry, &last->cookie, nd);
 		if (IS_ERR(res)) {
 out:
-			path_put(&nd->path);
 			path_put(&last->link);
 			return res;
 		}
@@ -1827,7 +1825,7 @@ Walked:
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
-				goto Err;
+				break;
 			}
 			err = 0;
 			if (unlikely(!s)) {
@@ -1858,7 +1856,6 @@ Walked:
 		}
 	}
 	terminate_walk(nd);
-Err:
 	while (unlikely(nd->depth))
 		put_link(nd);
 	return err;
@@ -1994,8 +1991,10 @@ static int trailing_symlink(struct nameidata *nd)
 		return error;
 	nd->flags |= LOOKUP_PARENT;
 	s = get_link(nd);
-	if (unlikely(IS_ERR(s)))
+	if (unlikely(IS_ERR(s))) {
+		terminate_walk(nd);
 		return PTR_ERR(s);
+	}
 	if (unlikely(!s))
 		return 0;
 	if (*s == '/') {
Signature · 1 line
2.1.4
author maintainer
#174 lore raw

PATCH v3 67/110 namei: take put_link() into {lookup,mountpoint,do}_last()

In reply to Al Viro

From: Al Viro <[email protected]>

rationale: we'll need to have terminate_walk() do put_link() on everything, which will mean that in some cases ..._last() will do put_link() anyway. Easier to have them do it in all cases.

---
 fs/namei.c | 34 +++++++++++++++++++++-------------
 1 file changed, 21 insertions(+), 13 deletions(-)
fs/namei.c +21 -13
index db21e04..26466fb 100644
@@ -2018,6 +2018,8 @@ static inline int lookup_last(struct nameidata *nd)
 
 	nd->flags &= ~LOOKUP_PARENT;
 	err = walk_component(nd, nd->flags & LOOKUP_FOLLOW);
+	if (nd->depth)
+		put_link(nd);
 	if (err < 0)
 		terminate_walk(nd);
 	return err;
@@ -2045,13 +2047,10 @@ static int path_lookupat(int dfd, const struct filename *name,
 	 */
 	err = path_init(dfd, name, flags, nd);
 	if (!err && !(flags & LOOKUP_PARENT)) {
-		err = lookup_last(nd);
-		while (err > 0) {
+		while ((err = lookup_last(nd)) > 0) {
 			err = trailing_symlink(nd);
 			if (err)
 				break;
-			err = lookup_last(nd);
-			put_link(nd);
 		}
 	}
 
@@ -2362,6 +2361,8 @@ done:
 		dput(dentry);
 		goto out;
 	}
+	if (nd->depth)
+		put_link(nd);
 	path->dentry = dentry;
 	path->mnt = nd->path.mnt;
 	if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW)) {
@@ -2373,6 +2374,8 @@ done:
 	error = 0;
 out:
 	terminate_walk(nd);
+	if (nd->depth)
+		put_link(nd);
 	return error;
 }
 
@@ -2394,13 +2397,10 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 	if (unlikely(err))
 		goto out;
 
-	err = mountpoint_last(nd, path);
-	while (err > 0) {
+	while ((err = mountpoint_last(nd, path)) > 0) {
 		err = trailing_symlink(nd);
 		if (err)
 			break;
-		err = mountpoint_last(nd, path);
-		put_link(nd);
 	}
 out:
 	path_cleanup(nd);
@@ -2978,6 +2978,8 @@ static int do_last(struct nameidata *nd,
 		error = handle_dots(nd, nd->last_type);
 		if (unlikely(error)) {
 			terminate_walk(nd);
+			if (nd->depth)
+				put_link(nd);
 			return error;
 		}
 		goto finish_open;
@@ -3003,8 +3005,11 @@ static int do_last(struct nameidata *nd,
 		 * about to look up
 		 */
 		error = complete_walk(nd);
-		if (error)
+		if (error) {
+			if (nd->depth)
+				put_link(nd);
 			return error;
+		}
 
 		audit_inode(name, dir, LOOKUP_PARENT);
 		error = -EISDIR;
@@ -3093,6 +3098,8 @@ finish_lookup:
 			}
 		}
 		BUG_ON(inode != path.dentry->d_inode);
+		if (nd->depth)
+			put_link(nd);
 		nd->link = path;
 		return 1;
 	}
@@ -3116,6 +3123,8 @@ finish_lookup:
 finish_open:
 	error = complete_walk(nd);
 	if (error) {
+		if (nd->depth)
+			put_link(nd);
 		path_put(&save_parent);
 		return error;
 	}
@@ -3167,6 +3176,8 @@ out:
 		mnt_drop_write(nd->path.mnt);
 	path_put(&save_parent);
 	terminate_walk(nd);
+	if (nd->depth)
+		put_link(nd);
 	return error;
 
 exit_dput:
@@ -3279,14 +3290,11 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 	if (unlikely(error))
 		goto out;
 
-	error = do_last(nd, file, op, &opened, pathname);
-	while (unlikely(error > 0)) { /* trailing symlink */
+	while ((error = do_last(nd, file, op, &opened, pathname)) > 0) {
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
 		error = trailing_symlink(nd);
 		if (unlikely(error))
 			break;
-		error = do_last(nd, file, op, &opened, pathname);
-		put_link(nd);
 	}
 out:
 	path_cleanup(nd);
Signature · 1 line
2.1.4
author maintainer
#175 lore raw

PATCH v3 68/110 namei: have terminate_walk() do put_link() on everything left

In reply to Al Viro

From: Al Viro <[email protected]>

All callers of terminate_walk() are followed by more or less open-coded eqiuvalent of "do put_link() on everything left in nd->stack". Better done in terminate_walk() itself, and when we go for RCU symlink traversal we'll have to do it there anyway.

---
 fs/namei.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)
fs/namei.c +2 -8
index 26466fb..31da717 100644
@@ -1562,6 +1562,8 @@ static void terminate_walk(struct nameidata *nd)
 			nd->root.mnt = NULL;
 		rcu_read_unlock();
 	}
+	while (unlikely(nd->depth))
+		put_link(nd);
 }
 
 /*
@@ -1856,8 +1858,6 @@ Walked:
 		}
 	}
 	terminate_walk(nd);
-	while (unlikely(nd->depth))
-		put_link(nd);
 	return err;
 OK:
 	if (!nd->depth)		/* called from path_init(), done */
@@ -2374,8 +2374,6 @@ done:
 	error = 0;
 out:
 	terminate_walk(nd);
-	if (nd->depth)
-		put_link(nd);
 	return error;
 }
 
@@ -2978,8 +2976,6 @@ static int do_last(struct nameidata *nd,
 		error = handle_dots(nd, nd->last_type);
 		if (unlikely(error)) {
 			terminate_walk(nd);
-			if (nd->depth)
-				put_link(nd);
 			return error;
 		}
 		goto finish_open;
@@ -3176,8 +3172,6 @@ out:
 		mnt_drop_write(nd->path.mnt);
 	path_put(&save_parent);
 	terminate_walk(nd);
-	if (nd->depth)
-		put_link(nd);
 	return error;
 
 exit_dput:
Signature · 1 line
2.1.4
author maintainer
#176 lore raw

PATCH v3 69/110 link_path_walk: move the OK: inside the loop

In reply to Al Viro

From: Al Viro <[email protected]>

fewer labels that way; in particular, resuming after the end of nested symlink is straight-line.

---
 fs/namei.c | 30 +++++++++++++++---------------
 1 file changed, 15 insertions(+), 15 deletions(-)
fs/namei.c +15 -15
index 31da717..19e5c8a 100644
@@ -1806,11 +1806,21 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 		do {
 			name++;
 		} while (unlikely(*name == '/'));
-		if (!*name)
-			goto OK;
-
-		err = walk_component(nd, LOOKUP_FOLLOW);
-Walked:
+		if (unlikely(!*name)) {
+OK:
+			/* called from path_init(), done */
+			if (!nd->depth)
+				return 0;
+			name = nd->stack[nd->depth - 1].name;
+			/* called from trailing_symlink(), done */
+			if (!name)
+				return 0;
+			/* last component of nested symlink */
+			err = walk_component(nd, LOOKUP_FOLLOW);
+			put_link(nd);
+		} else {
+			err = walk_component(nd, LOOKUP_FOLLOW);
+		}
 		if (err < 0)
 			break;
 
@@ -1859,16 +1869,6 @@ Walked:
 	}
 	terminate_walk(nd);
 	return err;
-OK:
-	if (!nd->depth)		/* called from path_init(), done */
-		return 0;
-	name = nd->stack[nd->depth - 1].name;
-	if (!name)		/* called from trailing_symlink(), done */
-		return 0;
-
-	err = walk_component(nd, LOOKUP_FOLLOW);
-	put_link(nd);
-	goto Walked;
 }
 
 static int path_init(int dfd, const struct filename *name, unsigned int flags,
Signature · 1 line
2.1.4
author maintainer
#177 lore raw

PATCH v3 70/110 namei: new calling conventions for walk_component()

In reply to Al Viro

From: Al Viro <[email protected]>

instead of a single flag (!= 0 => we want to follow symlinks) pass two bits - WALK_GET (want to follow symlinks) and WALK_PUT (put_link() once we are done looking at the name). The latter matters only for success exits - on failure the caller will discard everything anyway.

Suggestions for better variant are welcome; what this thing aims for is making sure that pending put_link() is done *before* walk_component() decides to pick a symlink up, rather than between picking it up and acting upon it. See the next commit for payoff.

---
 fs/namei.c | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)
fs/namei.c +20 -10
index 19e5c8a..1c9af925 100644
@@ -1577,7 +1577,9 @@ static inline int should_follow_link(struct dentry *dentry, int follow)
 	return unlikely(d_is_symlink(dentry)) ? follow : 0;
 }
 
-static int walk_component(struct nameidata *nd, int follow)
+enum {WALK_GET = 1, WALK_PUT = 2};
+
+static int walk_component(struct nameidata *nd, int flags)
 {
 	struct path path;
 	struct inode *inode;
@@ -1587,8 +1589,12 @@ static int walk_component(struct nameidata *nd, int follow)
 	 * to be able to know about the current root directory and
 	 * parent relationships.
 	 */
-	if (unlikely(nd->last_type != LAST_NORM))
-		return handle_dots(nd, nd->last_type);
+	if (unlikely(nd->last_type != LAST_NORM)) {
+		err = handle_dots(nd, nd->last_type);
+		if (flags & WALK_PUT)
+			put_link(nd);
+		return err;
+	}
 	err = lookup_fast(nd, &path, &inode);
 	if (unlikely(err)) {
 		if (err < 0)
@@ -1604,7 +1610,9 @@ static int walk_component(struct nameidata *nd, int follow)
 			goto out_path_put;
 	}
 
-	if (should_follow_link(path.dentry, follow)) {
+	if (flags & WALK_PUT)
+		put_link(nd);
+	if (should_follow_link(path.dentry, flags & WALK_GET)) {
 		if (nd->flags & LOOKUP_RCU) {
 			if (unlikely(nd->path.mnt != path.mnt ||
 				     unlazy_walk(nd, path.dentry))) {
@@ -1816,10 +1824,9 @@ OK:
 			if (!name)
 				return 0;
 			/* last component of nested symlink */
-			err = walk_component(nd, LOOKUP_FOLLOW);
-			put_link(nd);
+			err = walk_component(nd, WALK_GET | WALK_PUT);
 		} else {
-			err = walk_component(nd, LOOKUP_FOLLOW);
+			err = walk_component(nd, WALK_GET);
 		}
 		if (err < 0)
 			break;
@@ -2017,9 +2024,12 @@ static inline int lookup_last(struct nameidata *nd)
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	nd->flags &= ~LOOKUP_PARENT;
-	err = walk_component(nd, nd->flags & LOOKUP_FOLLOW);
-	if (nd->depth)
-		put_link(nd);
+	err = walk_component(nd,
+			nd->flags & LOOKUP_FOLLOW
+				? nd->depth
+					? WALK_PUT | WALK_GET
+					: WALK_GET
+				: 0);
 	if (err < 0)
 		terminate_walk(nd);
 	return err;
Signature · 1 line
2.1.4
author maintainer
#178 lore raw

PATCH v3 71/110 namei: make should_follow_link() store the link in nd->link

In reply to Al Viro

From: Al Viro <[email protected]>

... if it decides to follow, that is.

---
 fs/namei.c | 62 +++++++++++++++++++++++++++++++++-----------------------------
 1 file changed, 33 insertions(+), 29 deletions(-)
fs/namei.c +33 -29
index 1c9af925..1b4bc1b 100644
@@ -1566,15 +1566,31 @@ static void terminate_walk(struct nameidata *nd)
 		put_link(nd);
 }
 
+static int pick_link(struct nameidata *nd, struct path *link)
+{
+	if (nd->flags & LOOKUP_RCU) {
+		if (unlikely(nd->path.mnt != link->mnt ||
+			     unlazy_walk(nd, link->dentry))) {
+			return -ECHILD;
+		}
+	}
+	nd->link = *link;
+	return 1;
+}
+
 /*
  * Do we need to follow links? We _really_ want to be able
  * to do this check without having to look at inode->i_op,
  * so we keep a cache of "no, this doesn't need follow_link"
  * for the common case.
  */
-static inline int should_follow_link(struct dentry *dentry, int follow)
+static inline int should_follow_link(struct nameidata *nd, struct path *link, int follow)
 {
-	return unlikely(d_is_symlink(dentry)) ? follow : 0;
+	if (likely(!d_is_symlink(link->dentry)))
+		return 0;
+	if (!follow)
+		return 0;
+	return pick_link(nd, link);
 }
 
 enum {WALK_GET = 1, WALK_PUT = 2};
@@ -1612,17 +1628,9 @@ static int walk_component(struct nameidata *nd, int flags)
 
 	if (flags & WALK_PUT)
 		put_link(nd);
-	if (should_follow_link(path.dentry, flags & WALK_GET)) {
-		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(nd->path.mnt != path.mnt ||
-				     unlazy_walk(nd, path.dentry))) {
-				return -ECHILD;
-			}
-		}
-		BUG_ON(inode != path.dentry->d_inode);
-		nd->link = path;
-		return 1;
-	}
+	err = should_follow_link(nd, &path, flags & WALK_GET);
+	if (unlikely(err))
+		return err;
 	path_to_nameidata(&path, nd);
 	nd->inode = inode;
 	return 0;
@@ -2375,9 +2383,11 @@ done:
 		put_link(nd);
 	path->dentry = dentry;
 	path->mnt = nd->path.mnt;
-	if (should_follow_link(dentry, nd->flags & LOOKUP_FOLLOW)) {
-		nd->link = *path;
-		return 1;
+	error = should_follow_link(nd, path, nd->flags & LOOKUP_FOLLOW);
+	if (unlikely(error)) {
+		if (error < 0)
+			goto out;
+		return error;
 	}
 	mntget(path->mnt);
 	follow_mount(path);
@@ -3095,19 +3105,13 @@ retry_lookup:
 		goto out;
 	}
 finish_lookup:
-	if (should_follow_link(path.dentry, nd->flags & LOOKUP_FOLLOW)) {
-		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(nd->path.mnt != path.mnt ||
-				     unlazy_walk(nd, path.dentry))) {
-				error = -ECHILD;
-				goto out;
-			}
-		}
-		BUG_ON(inode != path.dentry->d_inode);
-		if (nd->depth)
-			put_link(nd);
-		nd->link = path;
-		return 1;
+	if (nd->depth)
+		put_link(nd);
+	error = should_follow_link(nd, &path, nd->flags & LOOKUP_FOLLOW);
+	if (unlikely(error)) {
+		if (error < 0)
+			goto out;
+		return error;
 	}
 
 	if (unlikely(d_is_symlink(path.dentry)) && !(open_flag & O_PATH)) {
Signature · 1 line
2.1.4
author maintainer
#179 lore raw

PATCH v3 72/110 namei: move link count check and stack allocation into pick_link()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 27 ++++++++++++---------------
 1 file changed, 12 insertions(+), 15 deletions(-)
fs/namei.c +12 -15
index 1b4bc1b..046a703 100644
@@ -891,16 +891,10 @@ const char *get_link(struct nameidata *nd)
 	if (nd->link.mnt == nd->path.mnt)
 		mntget(nd->link.mnt);
 
-	if (unlikely(current->total_link_count >= MAXSYMLINKS)) {
-		path_put(&nd->link);
-		return ERR_PTR(-ELOOP);
-	}
-
 	last->link = nd->link;
 	last->cookie = NULL;
 
 	cond_resched();
-	current->total_link_count++;
 
 	touch_atime(&last->link);
 
@@ -1568,12 +1562,23 @@ static void terminate_walk(struct nameidata *nd)
 
 static int pick_link(struct nameidata *nd, struct path *link)
 {
+	int error;
+	if (unlikely(current->total_link_count++ >= MAXSYMLINKS)) {
+		path_to_nameidata(link, nd);
+		return -ELOOP;
+	}
 	if (nd->flags & LOOKUP_RCU) {
 		if (unlikely(nd->path.mnt != link->mnt ||
 			     unlazy_walk(nd, link->dentry))) {
 			return -ECHILD;
 		}
 	}
+	error = nd_alloc_stack(nd);
+	if (unlikely(error)) {
+		path_to_nameidata(link, nd);
+		return error;
+	}
+
 	nd->link = *link;
 	return 1;
 }
@@ -1840,15 +1845,7 @@ OK:
 			break;
 
 		if (err) {
-			const char *s;
-
-			err = nd_alloc_stack(nd);
-			if (unlikely(err)) {
-				path_to_nameidata(&nd->link, nd);
-				break;
-			}
-
-			s = get_link(nd);
+			const char *s = get_link(nd);
 
 			if (unlikely(IS_ERR(s))) {
 				err = PTR_ERR(s);
Signature · 1 line
2.1.4
author maintainer
#180 lore raw

PATCH v3 73/110 lustre: rip the private symlink nesting limit out

In reply to Al Viro

From: Al Viro <[email protected]>

---
 drivers/staging/lustre/lustre/llite/symlink.c | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)
drivers/staging/lustre/lustre/llite/symlink.c +3 -12
index e488cb3..da6d9d1 100644
@@ -126,18 +126,9 @@ static const char *ll_follow_link(struct dentry *dentry, void **cookie, struct n
 	char *symname = NULL;
 
 	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.
-	 * For 8k stacks we need to limit it to 7 for local servers. */
-	if (THREAD_SIZE < 8192 && current->link_count >= 6) {
-		rc = -ELOOP;
-	} else if (THREAD_SIZE == 8192 && current->link_count >= 8) {
-		rc = -ELOOP;
-	} else {
-		ll_inode_size_lock(inode);
-		rc = ll_readlink_internal(inode, &request, &symname);
-		ll_inode_size_unlock(inode);
-	}
+	ll_inode_size_lock(inode);
+	rc = ll_readlink_internal(inode, &request, &symname);
+	ll_inode_size_unlock(inode);
 	if (rc) {
 		ptlrpc_req_finished(request);
 		return ERR_PTR(rc);
Signature · 1 line
2.1.4
author maintainer
#181 lore raw

PATCH v3 74/110 VFS: replace {, total_}link_count in task_struct with pointer to nameidata

In reply to Al Viro

From: NeilBrown <[email protected]>

task_struct currently contains two ad-hoc members for use by the VFS:
link_count and total_link_count. These are only interesting to fs/namei.c, so exposing them explicitly is poor layering. Incidentally, link_count isn't used anymore, so it can just die.

This patches replaces those with a single pointer to 'struct nameidata'. This structure represents the current filename lookup of which there can only be one per process, and is a natural place to store total_link_count.

This will allow the current "nameidata" argument to all follow_link operations to be removed as current->nameidata can be used instead in the _very_ few instances that care about it at all.

As there are occasional circumstances where pathname lookup can recurse, such as through kern_path_locked, we always save and old current->nameidata (if there is one) when setting a new value, and make sure any active link_counts are preserved.

follow_mount and follow_automount now get a 'struct nameidata *' rather than 'int flags' so that they can directly access total_link_count, rather than going through 'current'.

---
 fs/namei.c            | 70 ++++++++++++++++++++++++++++-----------------------
 include/linux/sched.h |  2 +-
 2 files changed, 40 insertions(+), 32 deletions(-)
fs/namei.c +39 -31
index 046a703..699e093 100644
@@ -505,6 +505,7 @@ struct nameidata {
 	unsigned	seq, m_seq;
 	int		last_type;
 	unsigned	depth;
+	int		total_link_count;
 	struct file	*base;
 	struct saved {
 		struct path link;
@@ -513,16 +514,25 @@ struct nameidata {
 	} *stack, internal[EMBEDDED_LEVELS];
 };
 
-static void set_nameidata(struct nameidata *nd)
+static struct nameidata *set_nameidata(struct nameidata *p)
 {
-	nd->stack = nd->internal;
+	struct nameidata *old = current->nameidata;
+	p->stack = p->internal;
+	p->total_link_count = old ? old->total_link_count : 0;
+	current->nameidata = p;
+	return old;
 }
 
-static void restore_nameidata(struct nameidata *nd)
+static void restore_nameidata(struct nameidata *old)
 {
-	if (nd->stack != nd->internal) {
-		kfree(nd->stack);
-		nd->stack = nd->internal;
+	struct nameidata *now = current->nameidata;
+
+	current->nameidata = old;
+	if (old)
+		old->total_link_count = now->total_link_count;
+	if (now->stack != now->internal) {
+		kfree(now->stack);
+		now->stack = now->internal;
 	}
 }
 
@@ -970,7 +980,7 @@ EXPORT_SYMBOL(follow_up);
  * - return -EISDIR to tell follow_managed() to stop and return the path we
  *   were called with.
  */
-static int follow_automount(struct path *path, unsigned flags,
+static int follow_automount(struct path *path, struct nameidata *nd,
 			    bool *need_mntput)
 {
 	struct vfsmount *mnt;
@@ -990,13 +1000,13 @@ static int follow_automount(struct path *path, unsigned flags,
 	 * as being automount points.  These will need the attentions
 	 * of the daemon to instantiate them before they can be used.
 	 */
-	if (!(flags & (LOOKUP_PARENT | LOOKUP_DIRECTORY |
-		     LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_AUTOMOUNT)) &&
+	if (!(nd->flags & (LOOKUP_PARENT | LOOKUP_DIRECTORY |
+			   LOOKUP_OPEN | LOOKUP_CREATE | LOOKUP_AUTOMOUNT)) &&
 	    path->dentry->d_inode)
 		return -EISDIR;
 
-	current->total_link_count++;
-	if (current->total_link_count >= 40)
+	nd->total_link_count++;
+	if (nd->total_link_count >= 40)
 		return -ELOOP;
 
 	mnt = path->dentry->d_op->d_automount(path);
@@ -1010,7 +1020,7 @@ static int follow_automount(struct path *path, unsigned flags,
 		 * the path being looked up; if it wasn't then the remainder of
 		 * the path is inaccessible and we should say so.
 		 */
-		if (PTR_ERR(mnt) == -EISDIR && (flags & LOOKUP_PARENT))
+		if (PTR_ERR(mnt) == -EISDIR && (nd->flags & LOOKUP_PARENT))
 			return -EREMOTE;
 		return PTR_ERR(mnt);
 	}
@@ -1050,7 +1060,7 @@ static int follow_automount(struct path *path, unsigned flags,
  *
  * Serialization is taken care of in namespace.c
  */
-static int follow_managed(struct path *path, unsigned flags)
+static int follow_managed(struct path *path, struct nameidata *nd)
 {
 	struct vfsmount *mnt = path->mnt; /* held by caller, must be left alone */
 	unsigned managed;
@@ -1094,7 +1104,7 @@ static int follow_managed(struct path *path, unsigned flags)
 
 		/* Handle an automount point */
 		if (managed & DCACHE_NEED_AUTOMOUNT) {
-			ret = follow_automount(path, flags, &need_mntput);
+			ret = follow_automount(path, nd, &need_mntput);
 			if (ret < 0)
 				break;
 			continue;
@@ -1483,7 +1493,7 @@ unlazy:
 	}
 	path->mnt = mnt;
 	path->dentry = dentry;
-	err = follow_managed(path, nd->flags);
+	err = follow_managed(path, nd);
 	if (unlikely(err < 0)) {
 		path_put_conditional(path, nd);
 		return err;
@@ -1513,7 +1523,7 @@ static int lookup_slow(struct nameidata *nd, struct path *path)
 		return PTR_ERR(dentry);
 	path->mnt = nd->path.mnt;
 	path->dentry = dentry;
-	err = follow_managed(path, nd->flags);
+	err = follow_managed(path, nd);
 	if (unlikely(err < 0)) {
 		path_put_conditional(path, nd);
 		return err;
@@ -1563,7 +1573,7 @@ static void terminate_walk(struct nameidata *nd)
 static int pick_link(struct nameidata *nd, struct path *link)
 {
 	int error;
-	if (unlikely(current->total_link_count++ >= MAXSYMLINKS)) {
+	if (unlikely(nd->total_link_count++ >= MAXSYMLINKS)) {
 		path_to_nameidata(link, nd);
 		return -ELOOP;
 	}
@@ -1981,7 +1991,7 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 	rcu_read_unlock();
 	return -ECHILD;
 done:
-	current->total_link_count = 0;
+	nd->total_link_count = 0;
 	return link_path_walk(s, nd);
 }
 
@@ -2087,10 +2097,9 @@ static int filename_lookup(int dfd, struct filename *name,
 				unsigned int flags, struct nameidata *nd)
 {
 	int retval;
+	struct nameidata *saved_nd = set_nameidata(nd);
 
-	set_nameidata(nd);
 	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
-
 	if (unlikely(retval == -ECHILD))
 		retval = path_lookupat(dfd, name, flags, nd);
 	if (unlikely(retval == -ESTALE))
@@ -2098,7 +2107,7 @@ static int filename_lookup(int dfd, struct filename *name,
 
 	if (likely(!retval))
 		audit_inode(name, nd->path.dentry, flags & LOOKUP_PARENT);
-	restore_nameidata(nd);
+	restore_nameidata(saved_nd);
 	return retval;
 }
 
@@ -2426,11 +2435,11 @@ static int
 filename_mountpoint(int dfd, struct filename *name, struct path *path,
 			unsigned int flags)
 {
-	struct nameidata nd;
+	struct nameidata nd, *saved;
 	int error;
 	if (IS_ERR(name))
 		return PTR_ERR(name);
-	set_nameidata(&nd);
+	saved = set_nameidata(&nd);
 	error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_RCU);
 	if (unlikely(error == -ECHILD))
 		error = path_mountpoint(dfd, name, path, &nd, flags);
@@ -2438,7 +2447,7 @@ filename_mountpoint(int dfd, struct filename *name, struct path *path,
 		error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_REVAL);
 	if (likely(!error))
 		audit_inode(name, path->dentry, 0);
-	restore_nameidata(&nd);
+	restore_nameidata(saved);
 	putname(name);
 	return error;
 }
@@ -3087,7 +3096,7 @@ retry_lookup:
 	if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))
 		goto exit_dput;
 
-	error = follow_managed(&path, nd->flags);
+	error = follow_managed(&path, nd);
 	if (error < 0)
 		goto exit_dput;
 
@@ -3323,31 +3332,29 @@ out2:
 struct file *do_filp_open(int dfd, struct filename *pathname,
 		const struct open_flags *op)
 {
-	struct nameidata nd;
+	struct nameidata nd, *saved_nd = set_nameidata(&nd);
 	int flags = op->lookup_flags;
 	struct file *filp;
 
-	set_nameidata(&nd);
 	filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
 	if (unlikely(filp == ERR_PTR(-ECHILD)))
 		filp = path_openat(dfd, pathname, &nd, op, flags);
 	if (unlikely(filp == ERR_PTR(-ESTALE)))
 		filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
-	restore_nameidata(&nd);
+	restore_nameidata(saved_nd);
 	return filp;
 }
 
 struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 		const char *name, const struct open_flags *op)
 {
-	struct nameidata nd;
+	struct nameidata nd, *saved_nd;
 	struct file *file;
 	struct filename *filename;
 	int flags = op->lookup_flags | LOOKUP_ROOT;
 
 	nd.root.mnt = mnt;
 	nd.root.dentry = dentry;
-	set_nameidata(&nd);
 
 	if (d_is_symlink(dentry) && op->intent & LOOKUP_OPEN)
 		return ERR_PTR(-ELOOP);
@@ -3356,12 +3363,13 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 	if (unlikely(IS_ERR(filename)))
 		return ERR_CAST(filename);
 
+	saved_nd = set_nameidata(&nd);
 	file = path_openat(-1, filename, &nd, op, flags | LOOKUP_RCU);
 	if (unlikely(file == ERR_PTR(-ECHILD)))
 		file = path_openat(-1, filename, &nd, op, flags);
 	if (unlikely(file == ERR_PTR(-ESTALE)))
 		file = path_openat(-1, filename, &nd, op, flags | LOOKUP_REVAL);
-	restore_nameidata(&nd);
+	restore_nameidata(saved_nd);
 	putname(filename);
 	return file;
 }
include/linux/sched.h +1 -1
index 26a2e61..f6c9b69 100644
@@ -1461,7 +1461,7 @@ struct task_struct {
 				       it with task_lock())
 				     - initialized normally by setup_new_exec */
 /* file system info */
-	int link_count, total_link_count;
+	struct nameidata *nameidata;
 #ifdef CONFIG_SYSVIPC
 /* ipc stuff */
 	struct sysv_sem sysvsem;
Signature · 1 line
2.1.4
author maintainer
#182 lore raw

Re: [PATCH v3 074/110] VFS: replace {, total_}link_count in task_struct with pointer to nameidata

Quoted 17 lines · Al Viro

From: NeilBrown <[email protected]>

task_struct currently contains two ad-hoc members for use by the VFS:
link_count and total_link_count. These are only interesting to fs/namei.c, so exposing them explicitly is poor layering. Incidentally, link_count isn't used anymore, so it can just die.

This patches replaces those with a single pointer to 'struct nameidata'. This structure represents the current filename lookup of which there can only be one per process, and is a natural place to store total_link_count.

This will allow the current "nameidata" argument to all follow_link operations to be removed as current->nameidata can be used instead in the _very_ few instances that care about it at all.

Quoted 8 lines

As there are occasional circumstances where pathname lookup can recurse, such as through kern_path_locked, we always save and old current->nameidata (if there is one) when setting a new value, and make sure any active link_counts are preserved.

follow_mount and follow_automount now get a 'struct nameidata *' rather than 'int flags' so that they can directly access total_link_count, rather than going through 'current'.

FWIW, the mainline semantics for total_link_count is bogus. Look: we set it to zero in path_init() (since _way_ back - in fact, 2.4.12 had something like that in path_walk()). Now, consider what happens when something like e.g. NFS referral point crossing does pathname resolution (recursion there is limited to "no more than once" by referral loop prevention logics in NFS code). We get the damn thing quietly reset to zero, no matter how many symlinks had already been crossed.

IMO we should drop that assignment in path_init() and let the logics in set_nameidata/restore_nameidata do the right thing. The current semantics is obviously wrong; we should either count the symlink traversals during a referral resolution towards the limit or just limit those to 40 independently from the symlink traversals outside of referral resolution, but resetting the count to zero as soon as we'd hit the referral is clearly bogus.

I would prefer the "let's count them towards the limit" variant. Objections?

author maintainer
#183 lore raw

PATCH v3 75/110 namei: simplify the callers of follow_managed()

In reply to Al Viro

From: Al Viro <[email protected]>

now that it gets nameidata, no reason to have setting LOOKUP_JUMPED on mountpoint crossing and calling path_put_conditional() on failures done in every caller.

---
 fs/namei.c | 32 ++++++++++----------------------
 1 file changed, 10 insertions(+), 22 deletions(-)
fs/namei.c +10 -22
index 699e093..b57400c 100644
@@ -1118,7 +1118,11 @@ static int follow_managed(struct path *path, struct nameidata *nd)
 		mntput(path->mnt);
 	if (ret == -EISDIR)
 		ret = 0;
-	return ret < 0 ? ret : need_mntput;
+	if (need_mntput)
+		nd->flags |= LOOKUP_JUMPED;
+	if (unlikely(ret < 0))
+		path_put_conditional(path, nd);
+	return ret;
 }
 
 int follow_down_one(struct path *path)
@@ -1494,14 +1498,9 @@ unlazy:
 	path->mnt = mnt;
 	path->dentry = dentry;
 	err = follow_managed(path, nd);
-	if (unlikely(err < 0)) {
-		path_put_conditional(path, nd);
-		return err;
-	}
-	if (err)
-		nd->flags |= LOOKUP_JUMPED;
-	*inode = path->dentry->d_inode;
-	return 0;
+	if (likely(!err))
+		*inode = path->dentry->d_inode;
+	return err;
 
 need_lookup:
 	return 1;
@@ -1511,7 +1510,6 @@ need_lookup:
 static int lookup_slow(struct nameidata *nd, struct path *path)
 {
 	struct dentry *dentry, *parent;
-	int err;
 
 	parent = nd->path.dentry;
 	BUG_ON(nd->inode != parent->d_inode);
@@ -1523,14 +1521,7 @@ static int lookup_slow(struct nameidata *nd, struct path *path)
 		return PTR_ERR(dentry);
 	path->mnt = nd->path.mnt;
 	path->dentry = dentry;
-	err = follow_managed(path, nd);
-	if (unlikely(err < 0)) {
-		path_put_conditional(path, nd);
-		return err;
-	}
-	if (err)
-		nd->flags |= LOOKUP_JUMPED;
-	return 0;
+	return follow_managed(path, nd);
 }
 
 static inline int may_lookup(struct nameidata *nd)
@@ -3098,10 +3089,7 @@ retry_lookup:
 
 	error = follow_managed(&path, nd);
 	if (error < 0)
-		goto exit_dput;
-
-	if (error)
-		nd->flags |= LOOKUP_JUMPED;
+		goto out;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 	inode = path.dentry->d_inode;
Signature · 1 line
2.1.4
author maintainer
#184 lore raw

PATCH v3 76/110 don't pass nameidata to ->follow_link()

In reply to Al Viro

From: Al Viro <[email protected]>

its only use is getting passed to nd_jump_link(), which can obtain it from current->nameidata

---
 Documentation/filesystems/Locking             |  2 +-
 Documentation/filesystems/vfs.txt             |  2 +-
 drivers/staging/lustre/lustre/llite/symlink.c |  2 +-
 fs/9p/vfs_inode.c                             |  2 +-
 fs/9p/vfs_inode_dotl.c                        |  2 +-
 fs/autofs4/symlink.c                          |  2 +-
 fs/befs/linuxvfs.c                            |  4 ++--
 fs/cifs/cifsfs.h                              |  2 +-
 fs/cifs/link.c                                |  2 +-
 fs/configfs/symlink.c                         |  2 +-
 fs/ecryptfs/inode.c                           |  2 +-
 fs/ext4/symlink.c                             |  2 +-
 fs/f2fs/namei.c                               |  4 ++--
 fs/fuse/dir.c                                 |  2 +-
 fs/gfs2/inode.c                               |  2 +-
 fs/hostfs/hostfs_kern.c                       |  2 +-
 fs/hppfs/hppfs.c                              |  4 ++--
 fs/kernfs/symlink.c                           |  2 +-
 fs/libfs.c                                    |  2 +-
 fs/namei.c                                    | 11 ++++++-----
 fs/nfs/symlink.c                              |  2 +-
 fs/overlayfs/inode.c                          |  4 ++--
 fs/proc/base.c                                |  4 ++--
 fs/proc/inode.c                               |  2 +-
 fs/proc/namespaces.c                          |  4 ++--
 fs/proc/self.c                                |  2 +-
 fs/proc/thread_self.c                         |  2 +-
 fs/xfs/xfs_iops.c                             |  3 +--
 include/linux/fs.h                            |  6 +++---
 include/linux/namei.h                         |  2 +-
 mm/shmem.c                                    |  2 +-
 31 files changed, 44 insertions(+), 44 deletions(-)
Documentation/filesystems/Locking +1 -1
index 7fa6c4a..5b5b4f5 100644
@@ -50,7 +50,7 @@ prototypes:
 	int (*rename2) (struct inode *, struct dentry *,
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
-	const char *(*follow_link) (struct dentry *, void **, struct nameidata *);
+	const char *(*follow_link) (struct dentry *, void **);
 	void (*put_link) (struct dentry *, void *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, unsigned int);
Documentation/filesystems/vfs.txt +1 -1
index 1c6b03a..0dec8c8 100644
@@ -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);
-	const char *(*follow_link) (struct dentry *, void **, struct nameidata *);
+	const char *(*follow_link) (struct dentry *, void **);
 	void (*put_link) (struct dentry *, void *);
 	int (*permission) (struct inode *, int);
 	int (*get_acl)(struct inode *, int);
drivers/staging/lustre/lustre/llite/symlink.c +1 -1
index da6d9d1..f3be3bf 100644
@@ -118,7 +118,7 @@ failed:
 	return rc;
 }
 
-static const char *ll_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *ll_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct inode *inode = d_inode(dentry);
 	struct ptlrpc_request *request = NULL;
fs/9p/vfs_inode.c +1 -1
index 7cc70a3..271f51a 100644
@@ -1230,7 +1230,7 @@ ino_t v9fs_qid2ino(struct p9_qid *qid)
  *
  */
 
-static const char *v9fs_vfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *v9fs_vfs_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry);
 	struct p9_fid *fid = v9fs_fid_lookup(dentry);
fs/9p/vfs_inode_dotl.c +1 -1
index ae062ff..16658ed 100644
@@ -910,7 +910,7 @@ error:
  */
 
 static const char *
-v9fs_vfs_follow_link_dotl(struct dentry *dentry, void **cookie, struct nameidata *nd)
+v9fs_vfs_follow_link_dotl(struct dentry *dentry, void **cookie)
 {
 	struct p9_fid *fid = v9fs_fid_lookup(dentry);
 	char *target;
fs/autofs4/symlink.c +1 -1
index 9c6a077..da0c334 100644
@@ -12,7 +12,7 @@
 
 #include "autofs_i.h"
 
-static const char *autofs4_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *autofs4_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct autofs_sb_info *sbi = autofs4_sbi(dentry->d_sb);
 	struct autofs_info *ino = autofs4_dentry_ino(dentry);
fs/befs/linuxvfs.c +2 -2
index 3a1aefb..46aedac 100644
@@ -42,7 +42,7 @@ 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 const char *befs_follow_link(struct dentry *, void **, struct nameidata *nd);
+static const char *befs_follow_link(struct dentry *, void **);
 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,
@@ -464,7 +464,7 @@ befs_destroy_inodecache(void)
  * flag is set.
  */
 static const char *
-befs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+befs_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct super_block *sb = dentry->d_sb;
 	struct befs_inode_info *befs_ino = BEFS_I(d_inode(dentry));
fs/cifs/cifsfs.h +1 -1
index 61012da..a782b22 100644
@@ -120,7 +120,7 @@ extern struct vfsmount *cifs_dfs_d_automount(struct path *path);
 #endif
 
 /* Functions related to symlinks */
-extern const char *cifs_follow_link(struct dentry *direntry, void **cookie, struct nameidata *nd);
+extern const char *cifs_follow_link(struct dentry *direntry, void **cookie);
 extern int cifs_readlink(struct dentry *direntry, char __user *buffer,
 			 int buflen);
 extern int cifs_symlink(struct inode *inode, struct dentry *direntry,
fs/cifs/link.c +1 -1
index 4a439c2..546f86a 100644
@@ -627,7 +627,7 @@ cifs_hl_exit:
 }
 
 const char *
-cifs_follow_link(struct dentry *direntry, void **cookie, struct nameidata *nd)
+cifs_follow_link(struct dentry *direntry, void **cookie)
 {
 	struct inode *inode = d_inode(direntry);
 	int rc = -ENOMEM;
fs/configfs/symlink.c +1 -1
index fac8e85..0ace756 100644
@@ -279,7 +279,7 @@ static int configfs_getlink(struct dentry *dentry, char * path)
 
 }
 
-static const char *configfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
 {
 	unsigned long page = get_zeroed_page(GFP_KERNEL);
 	int error;
fs/ecryptfs/inode.c +1 -1
index cdb9d6c..73d20ae 100644
@@ -675,7 +675,7 @@ out:
 	return rc ? ERR_PTR(rc) : buf;
 }
 
-static const char *ecryptfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *ecryptfs_follow_link(struct dentry *dentry, void **cookie)
 {
 	size_t len;
 	char *buf = ecryptfs_readlink_lower(dentry, &len);
fs/ext4/symlink.c +1 -1
index afec475..ba5bd18 100644
@@ -23,7 +23,7 @@
 #include "xattr.h"
 
 #ifdef CONFIG_EXT4_FS_ENCRYPTION
-static const char *ext4_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *ext4_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct page *cpage = NULL;
 	char *caddr, *paddr = NULL;
fs/f2fs/namei.c +2 -2
index d294793..cd05a7c 100644
@@ -296,9 +296,9 @@ fail:
 	return err;
 }
 
-static const char *f2fs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *f2fs_follow_link(struct dentry *dentry, void **cookie)
 {
-	const char *link = page_follow_link_light(dentry, cookie, nd);
+	const char *link = page_follow_link_light(dentry, cookie);
 	if (!IS_ERR(link) && !*link) {
 		/* this is broken symlink case */
 		page_put_link(dentry, *cookie);
fs/fuse/dir.c +1 -1
index f9cb260..d5cdef8 100644
@@ -1365,7 +1365,7 @@ static int fuse_readdir(struct file *file, struct dir_context *ctx)
 	return err;
 }
 
-static const char *fuse_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *fuse_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct inode *inode = d_inode(dentry);
 	struct fuse_conn *fc = get_fuse_conn(inode);
fs/gfs2/inode.c +1 -1
index f59390a..3a1461d 100644
@@ -1548,7 +1548,7 @@ out:
  * Returns: 0 on success or error code
  */
 
-static const char *gfs2_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *gfs2_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct gfs2_inode *ip = GFS2_I(d_inode(dentry));
 	struct gfs2_holder i_gh;
fs/hostfs/hostfs_kern.c +1 -1
index f650ed6..7b6ed7a 100644
@@ -892,7 +892,7 @@ static const struct inode_operations hostfs_dir_iops = {
 	.setattr	= hostfs_setattr,
 };
 
-static const char *hostfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *hostfs_follow_link(struct dentry *dentry, void **cookie)
 {
 	char *link = __getname();
 	if (link) {
fs/hppfs/hppfs.c +2 -2
index b8f24d3..15a774e 100644
@@ -642,11 +642,11 @@ static int hppfs_readlink(struct dentry *dentry, char __user *buffer,
 						    buflen);
 }
 
-static const char *hppfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *hppfs_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct dentry *proc_dentry = HPPFS_I(d_inode(dentry))->proc_dentry;
 
-	return d_inode(proc_dentry)->i_op->follow_link(proc_dentry, cookie, nd);
+	return d_inode(proc_dentry)->i_op->follow_link(proc_dentry, cookie);
 }
 
 static void hppfs_put_link(struct dentry *dentry, void *cookie)
fs/kernfs/symlink.c +1 -1
index 3c7e799..366c5a1 100644
@@ -112,7 +112,7 @@ static int kernfs_getlink(struct dentry *dentry, char *path)
 	return error;
 }
 
-static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie)
 {
 	int error = -ENOMEM;
 	unsigned long page = get_zeroed_page(GFP_KERNEL);
fs/libfs.c +1 -1
index 0c83fde..c5f3373 100644
@@ -1091,7 +1091,7 @@ simple_nosetlease(struct file *filp, long arg, struct file_lock **flp,
 }
 EXPORT_SYMBOL(simple_nosetlease);
 
-const char *simple_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+const char *simple_follow_link(struct dentry *dentry, void **cookie)
 {
 	return d_inode(dentry)->i_link;
 }
fs/namei.c +6 -5
index b57400c..f311f03 100644
@@ -753,8 +753,9 @@ static inline void path_to_nameidata(const struct path *path,
  * Helper to directly jump to a known parsed path from ->follow_link,
  * caller must have taken a reference to path beforehand.
  */
-void nd_jump_link(struct nameidata *nd, struct path *path)
+void nd_jump_link(struct path *path)
 {
+	struct nameidata *nd = current->nameidata;
 	path_put(&nd->path);
 
 	nd->path = *path;
@@ -916,7 +917,7 @@ const char *get_link(struct nameidata *nd)
 	nd->last_type = LAST_BIND;
 	res = inode->i_link;
 	if (!res) {
-		res = inode->i_op->follow_link(dentry, &last->cookie, nd);
+		res = inode->i_op->follow_link(dentry, &last->cookie);
 		if (IS_ERR(res)) {
 out:
 			path_put(&last->link);
@@ -4485,12 +4486,12 @@ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 	int res;
 
 	if (!link) {
-		link = dentry->d_inode->i_op->follow_link(dentry, &cookie, NULL);
+		link = dentry->d_inode->i_op->follow_link(dentry, &cookie);
 		if (IS_ERR(link))
 			return PTR_ERR(link);
 	}
 	res = readlink_copy(buffer, buflen, link);
-	if (cookie && dentry->d_inode->i_op->put_link)
+	if (dentry->d_inode->i_op->put_link)
 		dentry->d_inode->i_op->put_link(dentry, cookie);
 	return res;
 }
@@ -4523,7 +4524,7 @@ int page_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 }
 EXPORT_SYMBOL(page_readlink);
 
-const char *page_follow_link_light(struct dentry *dentry, void **cookie, struct nameidata *nd)
+const char *page_follow_link_light(struct dentry *dentry, void **cookie)
 {
 	struct page *page = NULL;
 	char *res = page_getlink(dentry, &page);
fs/nfs/symlink.c +1 -1
index c992b20..b6de433 100644
@@ -42,7 +42,7 @@ error:
 	return -EIO;
 }
 
-static const char *nfs_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *nfs_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct inode *inode = d_inode(dentry);
 	struct page *page;
fs/overlayfs/inode.c +2 -2
index 235ad42..9986833 100644
@@ -140,7 +140,7 @@ struct ovl_link_data {
 	void *cookie;
 };
 
-static const char *ovl_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *ovl_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct dentry *realdentry;
 	struct inode *realinode;
@@ -160,7 +160,7 @@ static const char *ovl_follow_link(struct dentry *dentry, void **cookie, struct
 		data->realdentry = realdentry;
 	}
 
-	ret = realinode->i_op->follow_link(realdentry, cookie, nd);
+	ret = realinode->i_op->follow_link(realdentry, cookie);
 	if (IS_ERR_OR_NULL(ret)) {
 		kfree(data);
 		return ret;
fs/proc/base.c +2 -2
index 52652f8..286a422 100644
@@ -1380,7 +1380,7 @@ static int proc_exe_link(struct dentry *dentry, struct path *exe_path)
 		return -ENOENT;
 }
 
-static const char *proc_pid_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *proc_pid_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct inode *inode = d_inode(dentry);
 	struct path path;
@@ -1394,7 +1394,7 @@ static const char *proc_pid_follow_link(struct dentry *dentry, void **cookie, st
 	if (error)
 		goto out;
 
-	nd_jump_link(nd, &path);
+	nd_jump_link(&path);
 	return NULL;
 out:
 	return ERR_PTR(error);
fs/proc/inode.c +1 -1
index acd51d7..eb35874 100644
@@ -393,7 +393,7 @@ static const struct file_operations proc_reg_file_ops_no_compat = {
 };
 #endif
 
-static const char *proc_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *proc_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct proc_dir_entry *pde = PDE(d_inode(dentry));
 	if (unlikely(!use_pde(pde)))
fs/proc/namespaces.c +2 -2
index 10d24dd..f6e8354 100644
@@ -30,7 +30,7 @@ static const struct proc_ns_operations *ns_entries[] = {
 	&mntns_operations,
 };
 
-static const char *proc_ns_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *proc_ns_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct inode *inode = d_inode(dentry);
 	const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
@@ -45,7 +45,7 @@ static const char *proc_ns_follow_link(struct dentry *dentry, void **cookie, str
 	if (ptrace_may_access(task, PTRACE_MODE_READ)) {
 		error = ns_get_path(&ns_path, task, ns_ops);
 		if (!error)
-			nd_jump_link(nd, &ns_path);
+			nd_jump_link(&ns_path);
 	}
 	put_task_struct(task);
 	return error;
fs/proc/self.c +1 -1
index ad33394..113b8d0 100644
@@ -18,7 +18,7 @@ static int proc_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static const char *proc_self_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *proc_self_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
fs/proc/thread_self.c +1 -1
index 85c96e0..947b0f4 100644
@@ -19,7 +19,7 @@ static int proc_thread_self_readlink(struct dentry *dentry, char __user *buffer,
 	return readlink_copy(buffer, buflen, tmp);
 }
 
-static const char *proc_thread_self_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *proc_thread_self_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct pid_namespace *ns = dentry->d_sb->s_fs_info;
 	pid_t tgid = task_tgid_nr_ns(current, ns);
fs/xfs/xfs_iops.c +1 -2
index 26c4dcb..7f51f39 100644
@@ -416,8 +416,7 @@ xfs_vn_rename(
 STATIC const char *
 xfs_vn_follow_link(
 	struct dentry		*dentry,
-	void			**cookie,
-	struct nameidata	*nd)
+	void			**cookie)
 {
 	char			*link;
 	int			error = -ENOMEM;
include/linux/fs.h +3 -3
index 9ab9341..ed7c9f2 100644
@@ -1608,7 +1608,7 @@ struct file_operations {
 
 struct inode_operations {
 	struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
-	const char * (*follow_link) (struct dentry *, void **, struct nameidata *);
+	const char * (*follow_link) (struct dentry *, void **);
 	int (*permission) (struct inode *, int);
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
@@ -2705,7 +2705,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 const char *page_follow_link_light(struct dentry *, void **, struct nameidata *);
+extern const char *page_follow_link_light(struct dentry *, void **);
 extern void page_put_link(struct dentry *, void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
@@ -2722,7 +2722,7 @@ void __inode_sub_bytes(struct inode *inode, loff_t bytes);
 void inode_sub_bytes(struct inode *inode, loff_t bytes);
 loff_t inode_get_bytes(struct inode *inode);
 void inode_set_bytes(struct inode *inode, loff_t bytes);
-const char *simple_follow_link(struct dentry *, void **, struct nameidata *);
+const char *simple_follow_link(struct dentry *, void **);
 extern const struct inode_operations simple_symlink_inode_operations;
 
 extern int iterate_dir(struct file *, struct dir_context *);
include/linux/namei.h +1 -1
index 3a6cc96..d756304 100644
@@ -72,7 +72,7 @@ extern int follow_up(struct path *);
 extern struct dentry *lock_rename(struct dentry *, struct dentry *);
 extern void unlock_rename(struct dentry *, struct dentry *);
 
-extern void nd_jump_link(struct nameidata *nd, struct path *path);
+extern void nd_jump_link(struct path *path);
 
 static inline void nd_terminate_link(void *name, size_t len, size_t maxlen)
 {
mm/shmem.c +1 -1
index d1693dc..e026822 100644
@@ -2475,7 +2475,7 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s
 	return 0;
 }
 
-static const char *shmem_follow_link(struct dentry *dentry, void **cookie, struct nameidata *nd)
+static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
 {
 	struct page *page = NULL;
 	int error = shmem_getpage(d_inode(dentry), 0, &page, SGP_READ, NULL);
Signature · 1 line
2.1.4
author maintainer
#185 lore raw

PATCH v3 77/110 namei: simplify failure exits in get_link()

In reply to Al Viro

From: Al Viro <[email protected]>

when cookie is NULL, put_link() is equivalent to path_put(), so as soon as we'd set last->cookie to NULL, we can bump nd->depth and let the normal logics in terminate_walk() to take care of cleanups.

---
 fs/namei.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)
fs/namei.c +4 -8
index f311f03..678aeef 100644
@@ -904,27 +904,23 @@ const char *get_link(struct nameidata *nd)
 
 	last->link = nd->link;
 	last->cookie = NULL;
+	nd->depth++;
 
 	cond_resched();
 
 	touch_atime(&last->link);
 
 	error = security_inode_follow_link(dentry);
-	res = ERR_PTR(error);
 	if (error)
-		goto out;
+		return ERR_PTR(error);
 
 	nd->last_type = LAST_BIND;
 	res = inode->i_link;
 	if (!res) {
 		res = inode->i_op->follow_link(dentry, &last->cookie);
-		if (IS_ERR(res)) {
-out:
-			path_put(&last->link);
-			return res;
-		}
+		if (IS_ERR_OR_NULL(res))
+			last->cookie = NULL;
 	}
-	nd->depth++;
 	return res;
 }
 
Signature · 1 line
2.1.4
author maintainer
#186 lore raw

PATCH v3 78/110 namei: simpler treatment of symlinks with nothing other that / in the body

In reply to Al Viro

From: Al Viro <[email protected]>

Instead of saving name and branching to OK:, where we'll immediately restore it, and call walk_component() with WALK_PUT|WALK_GET and nd->last_type being LAST_BIND, which is equivalent to put_link(nd), err = 0, we can just treat that the same way we'd treat procfs-style "jump" symlinks - do put_link(nd) and move on.

---
 fs/namei.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)
fs/namei.c +7 -5
index 678aeef..c5eb77a 100644
@@ -1865,11 +1865,13 @@ OK:
 						;
 				}
 				nd->inode = nd->path.dentry->d_inode;
-				nd->stack[nd->depth - 1].name = name;
-				if (!*s)
-					goto OK;
-				name = s;
-				continue;
+				if (unlikely(!*s)) {
+					put_link(nd);
+				} else {
+					nd->stack[nd->depth - 1].name = name;
+					name = s;
+					continue;
+				}
 			}
 		}
 		if (!d_can_lookup(nd->path.dentry)) {
Signature · 1 line
2.1.4
author maintainer
#187 lore raw

PATCH v3 79/110 namei: take the treatment of absolute symlinks to get_link()

In reply to Al Viro

From: Al Viro <[email protected]>

rather than letting the callers handle the jump-to-root part of semantics, do it right in get_link() and return the rest of the body for the caller to deal with - at that point it's treated the same way as relative symlinks would be. And return NULL when there's no "rest of the body" - those are treated the same as pure jump symlink would be.

---
 fs/namei.c | 49 ++++++++++++++++++++-----------------------------
 1 file changed, 20 insertions(+), 29 deletions(-)
fs/namei.c +20 -29
index c5eb77a..c6ff9da 100644
@@ -918,9 +918,24 @@ const char *get_link(struct nameidata *nd)
 	res = inode->i_link;
 	if (!res) {
 		res = inode->i_op->follow_link(dentry, &last->cookie);
-		if (IS_ERR_OR_NULL(res))
+		if (IS_ERR_OR_NULL(res)) {
 			last->cookie = NULL;
+			return res;
+		}
+	}
+	if (*res == '/') {
+		if (!nd->root.mnt)
+			set_root(nd);
+		path_put(&nd->path);
+		nd->path = nd->root;
+		path_get(&nd->root);
+		nd->inode = nd->path.dentry->d_inode;
+		nd->flags |= LOOKUP_JUMPED;
+		while (unlikely(*++res == '/'))
+			;
 	}
+	if (!*res)
+		res = NULL;
 	return res;
 }
 
@@ -1854,24 +1869,9 @@ OK:
 				/* jumped */
 				put_link(nd);
 			} else {
-				if (*s == '/') {
-					if (!nd->root.mnt)
-						set_root(nd);
-					path_put(&nd->path);
-					nd->path = nd->root;
-					path_get(&nd->root);
-					nd->flags |= LOOKUP_JUMPED;
-					while (unlikely(*++s == '/'))
-						;
-				}
-				nd->inode = nd->path.dentry->d_inode;
-				if (unlikely(!*s)) {
-					put_link(nd);
-				} else {
-					nd->stack[nd->depth - 1].name = name;
-					name = s;
-					continue;
-				}
+				nd->stack[nd->depth - 1].name = name;
+				name = s;
+				continue;
 			}
 		}
 		if (!d_can_lookup(nd->path.dentry)) {
@@ -2002,6 +2002,7 @@ static int trailing_symlink(struct nameidata *nd)
 	if (unlikely(error))
 		return error;
 	nd->flags |= LOOKUP_PARENT;
+	nd->stack[0].name = NULL;
 	s = get_link(nd);
 	if (unlikely(IS_ERR(s))) {
 		terminate_walk(nd);
@@ -2009,16 +2010,6 @@ static int trailing_symlink(struct nameidata *nd)
 	}
 	if (unlikely(!s))
 		return 0;
-	if (*s == '/') {
-		if (!nd->root.mnt)
-			set_root(nd);
-		path_put(&nd->path);
-		nd->path = nd->root;
-		path_get(&nd->root);
-		nd->flags |= LOOKUP_JUMPED;
-	}
-	nd->inode = nd->path.dentry->d_inode;
-	nd->stack[0].name = NULL;
 	return link_path_walk(s, nd);
 }
 
Signature · 1 line
2.1.4
author maintainer
#188 lore raw

PATCH v3 80/110 namei: fold put_link() into the failure case of complete_walk()

In reply to Al Viro

From: Al Viro <[email protected]>

... and don't open-code unlazy_walk() in there - the only reason for that is to avoid verfication of cached nd->root, which is trivially avoided by discarding said cached nd->root first.

---
 fs/namei.c | 29 ++++++-----------------------
 1 file changed, 6 insertions(+), 23 deletions(-)
fs/namei.c +6 -23
index c6ff9da..55283fe 100644
@@ -567,6 +567,8 @@ static inline int nd_alloc_stack(struct nameidata *nd)
  * to restart the path walk from the beginning in ref-walk mode.
  */
 
+static void terminate_walk(struct nameidata *nd);
+
 /**
  * unlazy_walk - try to switch to ref-walk mode.
  * @nd: nameidata pathwalk data
@@ -673,26 +675,12 @@ static int complete_walk(struct nameidata *nd)
 	int status;
 
 	if (nd->flags & LOOKUP_RCU) {
-		nd->flags &= ~LOOKUP_RCU;
 		if (!(nd->flags & LOOKUP_ROOT))
 			nd->root.mnt = NULL;
-
-		if (!legitimize_mnt(nd->path.mnt, nd->m_seq)) {
-			rcu_read_unlock();
-			return -ECHILD;
-		}
-		if (unlikely(!lockref_get_not_dead(&dentry->d_lockref))) {
-			rcu_read_unlock();
-			mntput(nd->path.mnt);
-			return -ECHILD;
-		}
-		if (read_seqcount_retry(&dentry->d_seq, nd->seq)) {
-			rcu_read_unlock();
-			dput(dentry);
-			mntput(nd->path.mnt);
+		if (unlikely(unlazy_walk(nd, NULL))) {
+			terminate_walk(nd);
 			return -ECHILD;
 		}
-		rcu_read_unlock();
 	}
 
 	if (likely(!(nd->flags & LOOKUP_JUMPED)))
@@ -708,7 +696,7 @@ static int complete_walk(struct nameidata *nd)
 	if (!status)
 		status = -ESTALE;
 
-	path_put(&nd->path);
+	terminate_walk(nd);
 	return status;
 }
 
@@ -3008,11 +2996,8 @@ static int do_last(struct nameidata *nd,
 		 * about to look up
 		 */
 		error = complete_walk(nd);
-		if (error) {
-			if (nd->depth)
-				put_link(nd);
+		if (error)
 			return error;
-		}
 
 		audit_inode(name, dir, LOOKUP_PARENT);
 		error = -EISDIR;
@@ -3117,8 +3102,6 @@ finish_lookup:
 finish_open:
 	error = complete_walk(nd);
 	if (error) {
-		if (nd->depth)
-			put_link(nd);
 		path_put(&save_parent);
 		return error;
 	}
Signature · 1 line
2.1.4
author maintainer
#189 lore raw

PATCH v3 81/110 namei: move bumping the refcount of link->mnt into pick_link()

In reply to Al Viro

From: Al Viro <[email protected]>

update the failure cleanup in may_follow_link() to match that.

---
 fs/namei.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)
fs/namei.c +4 -5
index 55283fe..05efcc0 100644
@@ -802,7 +802,7 @@ static inline int may_follow_link(struct path *link, struct nameidata *nd)
 		return 0;
 
 	audit_log_link_denied("follow_link", link);
-	path_put_conditional(link, nd);
+	path_put(link);
 	path_put(&nd->path);
 	return -EACCES;
 }
@@ -887,9 +887,6 @@ const char *get_link(struct nameidata *nd)
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 
-	if (nd->link.mnt == nd->path.mnt)
-		mntget(nd->link.mnt);
-
 	last->link = nd->link;
 	last->cookie = NULL;
 	nd->depth++;
@@ -1574,9 +1571,11 @@ static int pick_link(struct nameidata *nd, struct path *link)
 			return -ECHILD;
 		}
 	}
+	if (link->mnt == nd->path.mnt)
+		mntget(link->mnt);
 	error = nd_alloc_stack(nd);
 	if (unlikely(error)) {
-		path_to_nameidata(link, nd);
+		path_put(link);
 		return error;
 	}
 
Signature · 1 line
2.1.4
author maintainer
#190 lore raw

PATCH v3 82/110 may_follow_link(): trim arguments

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)
fs/namei.c +5 -6
index 05efcc0..82cb1bc 100644
@@ -765,7 +765,6 @@ int sysctl_protected_hardlinks __read_mostly = 0;
 
 /**
  * may_follow_link - Check symlink following for unsafe situations
- * @link: The path of the symlink
  * @nd: nameidata pathwalk data
  *
  * In the case of the sysctl_protected_symlinks sysctl being enabled,
@@ -779,7 +778,7 @@ int sysctl_protected_hardlinks __read_mostly = 0;
  *
  * Returns 0 if following the symlink is allowed, -ve on error.
  */
-static inline int may_follow_link(struct path *link, struct nameidata *nd)
+static inline int may_follow_link(struct nameidata *nd)
 {
 	const struct inode *inode;
 	const struct inode *parent;
@@ -788,7 +787,7 @@ static inline int may_follow_link(struct path *link, struct nameidata *nd)
 		return 0;
 
 	/* Allowed if owner and follower match. */
-	inode = link->dentry->d_inode;
+	inode = nd->link.dentry->d_inode;
 	if (uid_eq(current_cred()->fsuid, inode->i_uid))
 		return 0;
 
@@ -801,8 +800,8 @@ static inline int may_follow_link(struct path *link, struct nameidata *nd)
 	if (uid_eq(parent->i_uid, inode->i_uid))
 		return 0;
 
-	audit_log_link_denied("follow_link", link);
-	path_put(link);
+	audit_log_link_denied("follow_link", &nd->link);
+	path_put(&nd->link);
 	path_put(&nd->path);
 	return -EACCES;
 }
@@ -1985,7 +1984,7 @@ static void path_cleanup(struct nameidata *nd)
 static int trailing_symlink(struct nameidata *nd)
 {
 	const char *s;
-	int error = may_follow_link(&nd->link, nd);
+	int error = may_follow_link(nd);
 	if (unlikely(error))
 		return error;
 	nd->flags |= LOOKUP_PARENT;
Signature · 1 line
2.1.4
author maintainer
#191 lore raw

PATCH v3 83/110 namei: kill nd->link

In reply to Al Viro

From: Al Viro <[email protected]>

Just store it in nd->stack[nd->depth].link right in pick_link(). Now that we make sure of stack expansion in pick_link(), we can do so...

---
 fs/namei.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)
fs/namei.c +8 -10
index 82cb1bc..277ca86 100644
@@ -495,10 +495,7 @@ EXPORT_SYMBOL(path_put);
 #define EMBEDDED_LEVELS 2
 struct nameidata {
 	struct path	path;
-	union {
-		struct qstr	last;
-		struct path	link;
-	};
+	struct qstr	last;
 	struct path	root;
 	struct inode	*inode; /* path.dentry.d_inode */
 	unsigned int	flags;
@@ -787,7 +784,7 @@ static inline int may_follow_link(struct nameidata *nd)
 		return 0;
 
 	/* Allowed if owner and follower match. */
-	inode = nd->link.dentry->d_inode;
+	inode = nd->stack[0].link.dentry->d_inode;
 	if (uid_eq(current_cred()->fsuid, inode->i_uid))
 		return 0;
 
@@ -800,8 +797,8 @@ static inline int may_follow_link(struct nameidata *nd)
 	if (uid_eq(parent->i_uid, inode->i_uid))
 		return 0;
 
-	audit_log_link_denied("follow_link", &nd->link);
-	path_put(&nd->link);
+	audit_log_link_denied("follow_link", &nd->stack[0].link);
+	path_put(&nd->stack[0].link);
 	path_put(&nd->path);
 	return -EACCES;
 }
@@ -879,14 +876,13 @@ static __always_inline
 const char *get_link(struct nameidata *nd)
 {
 	struct saved *last = nd->stack + nd->depth;
-	struct dentry *dentry = nd->link.dentry;
+	struct dentry *dentry = last->link.dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
 	const char *res;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 
-	last->link = nd->link;
 	last->cookie = NULL;
 	nd->depth++;
 
@@ -1560,6 +1556,7 @@ static void terminate_walk(struct nameidata *nd)
 static int pick_link(struct nameidata *nd, struct path *link)
 {
 	int error;
+	struct saved *last;
 	if (unlikely(nd->total_link_count++ >= MAXSYMLINKS)) {
 		path_to_nameidata(link, nd);
 		return -ELOOP;
@@ -1578,7 +1575,8 @@ static int pick_link(struct nameidata *nd, struct path *link)
 		return error;
 	}
 
-	nd->link = *link;
+	last = nd->stack + nd->depth;
+	last->link = *link;
 	return 1;
 }
 
Signature · 1 line
2.1.4
author maintainer
#192 lore raw

PATCH v3 84/110 namei: take increment of nd->depth into pick_link()

In reply to Al Viro

From: Al Viro <[email protected]>

Makes the situation much more regular - we avoid a strange state when the element just after the top of stack is used to store struct path of symlink, but isn't counted in nd->depth. This is much more regular, so the normal failure exits, etc., work fine.

---
 fs/namei.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)
fs/namei.c +4 -7
index 277ca86..6d4692d 100644
@@ -798,8 +798,7 @@ static inline int may_follow_link(struct nameidata *nd)
 		return 0;
 
 	audit_log_link_denied("follow_link", &nd->stack[0].link);
-	path_put(&nd->stack[0].link);
-	path_put(&nd->path);
+	terminate_walk(nd);
 	return -EACCES;
 }
 
@@ -875,7 +874,7 @@ static int may_linkat(struct path *link)
 static __always_inline
 const char *get_link(struct nameidata *nd)
 {
-	struct saved *last = nd->stack + nd->depth;
+	struct saved *last = nd->stack + nd->depth - 1;
 	struct dentry *dentry = last->link.dentry;
 	struct inode *inode = dentry->d_inode;
 	int error;
@@ -883,9 +882,6 @@ const char *get_link(struct nameidata *nd)
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 
-	last->cookie = NULL;
-	nd->depth++;
-
 	cond_resched();
 
 	touch_atime(&last->link);
@@ -1575,8 +1571,9 @@ static int pick_link(struct nameidata *nd, struct path *link)
 		return error;
 	}
 
-	last = nd->stack + nd->depth;
+	last = nd->stack + nd->depth++;
 	last->link = *link;
+	last->cookie = NULL;
 	return 1;
 }
 
Signature · 1 line
2.1.4
author maintainer
#193 lore raw

PATCH v3 85/110 namei: may_follow_link() - lift terminate_walk() on failures into caller

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
fs/namei.c +3 -2
index 6d4692d..51e2214 100644
@@ -798,7 +798,6 @@ static inline int may_follow_link(struct nameidata *nd)
 		return 0;
 
 	audit_log_link_denied("follow_link", &nd->stack[0].link);
-	terminate_walk(nd);
 	return -EACCES;
 }
 
@@ -1980,8 +1979,10 @@ static int trailing_symlink(struct nameidata *nd)
 {
 	const char *s;
 	int error = may_follow_link(nd);
-	if (unlikely(error))
+	if (unlikely(error)) {
+		terminate_walk(nd);
 		return error;
+	}
 	nd->flags |= LOOKUP_PARENT;
 	nd->stack[0].name = NULL;
 	s = get_link(nd);
Signature · 1 line
2.1.4
author maintainer
#194 lore raw

PATCH v3 86/110 namei: split off filename_lookupat() with LOOKUP_PARENT

In reply to Al Viro

From: Al Viro <[email protected]>

new functions: filename_parentat() and path_parentat() resp.

---
 fs/namei.c | 37 +++++++++++++++++++++++++++++++++----
 1 file changed, 33 insertions(+), 4 deletions(-)
fs/namei.c +33 -4
index 51e2214..6f95bc9 100644
@@ -2034,7 +2034,7 @@ static int path_lookupat(int dfd, const struct filename *name,
 	 * be able to complete).
 	 */
 	err = path_init(dfd, name, flags, nd);
-	if (!err && !(flags & LOOKUP_PARENT)) {
+	if (!err) {
 		while ((err = lookup_last(nd)) > 0) {
 			err = trailing_symlink(nd);
 			if (err)
@@ -2074,6 +2074,35 @@ static int filename_lookup(int dfd, struct filename *name,
 	return retval;
 }
 
+/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
+static int path_parentat(int dfd, const struct filename *name,
+				unsigned int flags, struct nameidata *nd)
+{
+	int err = path_init(dfd, name, flags | LOOKUP_PARENT, nd);
+	if (!err)
+		err = complete_walk(nd);
+	path_cleanup(nd);
+	return err;
+}
+
+static int filename_parentat(int dfd, struct filename *name,
+				unsigned int flags, struct nameidata *nd)
+{
+	int retval;
+	struct nameidata *saved_nd = set_nameidata(nd);
+
+	retval = path_parentat(dfd, name, flags | LOOKUP_RCU, nd);
+	if (unlikely(retval == -ECHILD))
+		retval = path_parentat(dfd, name, flags, nd);
+	if (unlikely(retval == -ESTALE))
+		retval = path_parentat(dfd, name, flags | LOOKUP_REVAL, nd);
+
+	if (likely(!retval))
+		audit_inode(name, nd->path.dentry, LOOKUP_PARENT);
+	restore_nameidata(saved_nd);
+	return retval;
+}
+
 /* does lookup, returns the object with parent locked */
 struct dentry *kern_path_locked(const char *name, struct path *path)
 {
@@ -2085,7 +2114,7 @@ struct dentry *kern_path_locked(const char *name, struct path *path)
 	if (IS_ERR(filename))
 		return ERR_CAST(filename);
 
-	err = filename_lookup(AT_FDCWD, filename, LOOKUP_PARENT, &nd);
+	err = filename_parentat(AT_FDCWD, filename, 0, &nd);
 	if (err) {
 		d = ERR_PTR(err);
 		goto out;
@@ -2255,7 +2284,7 @@ user_path_parent(int dfd, const char __user *path,
 	if (IS_ERR(s))
 		return s;
 
-	error = filename_lookup(dfd, s, flags | LOOKUP_PARENT, &nd);
+	error = filename_parentat(dfd, s, flags, &nd);
 	if (error) {
 		putname(s);
 		return ERR_PTR(error);
@@ -3344,7 +3373,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 */
 	lookup_flags &= LOOKUP_REVAL;
 
-	error = filename_lookup(dfd, name, LOOKUP_PARENT|lookup_flags, &nd);
+	error = filename_parentat(dfd, name, lookup_flags, &nd);
 	if (error)
 		return ERR_PTR(error);
 
Signature · 1 line
2.1.4
author maintainer
#195 lore raw

PATCH v3 87/110 namei: get rid of nameidata->base

In reply to Al Viro

From: Al Viro <[email protected]>

we can do fdput() under rcu_read_lock() just fine; all we need to take care of is fetching nd->inode value first.

---
 fs/namei.c | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)
fs/namei.c +5 -8
index 6f95bc9..497d5f4 100644
@@ -503,7 +503,6 @@ struct nameidata {
 	int		last_type;
 	unsigned	depth;
 	int		total_link_count;
-	struct file	*base;
 	struct saved {
 		struct path link;
 		void *cookie;
@@ -1872,7 +1871,6 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 	nd->last_type = LAST_ROOT; /* if there are only slashes... */
 	nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
 	nd->depth = 0;
-	nd->base = NULL;
 	if (flags & LOOKUP_ROOT) {
 		struct dentry *root = nd->root.dentry;
 		struct inode *inode = root->d_inode;
@@ -1941,14 +1939,15 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 
 		nd->path = f.file->f_path;
 		if (flags & LOOKUP_RCU) {
-			if (f.flags & FDPUT_FPUT)
-				nd->base = f.file;
-			nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq);
 			rcu_read_lock();
+			nd->inode = nd->path.dentry->d_inode;
+			nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
 		} else {
 			path_get(&nd->path);
-			fdput(f);
+			nd->inode = nd->path.dentry->d_inode;
 		}
+		fdput(f);
+		goto done;
 	}
 
 	nd->inode = nd->path.dentry->d_inode;
@@ -1971,8 +1970,6 @@ static void path_cleanup(struct nameidata *nd)
 		path_put(&nd->root);
 		nd->root.mnt = NULL;
 	}
-	if (unlikely(nd->base))
-		fput(nd->base);
 }
 
 static int trailing_symlink(struct nameidata *nd)
Signature · 1 line
2.1.4
author maintainer
#196 lore raw

PATCH v3 88/110 namei: path_init() calling conventions change

In reply to Al Viro

From: Al Viro <[email protected]>

* lift link_path_walk() into callers; moving it down into path_init() had been a mistake. Stack footprint, among other things...
* do _not_ call path_cleanup() after path_init() failure; on all failure exits out of it we have nothing for path_cleanup() to do
* have path_init() return pathname or ERR_PTR(-E...)

---
 fs/namei.c | 69 +++++++++++++++++++++++++++++++-------------------------------
 1 file changed, 35 insertions(+), 34 deletions(-)
fs/namei.c +35 -34
index 497d5f4..06c7120 100644
@@ -1821,11 +1821,11 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 		} while (unlikely(*name == '/'));
 		if (unlikely(!*name)) {
 OK:
-			/* called from path_init(), done */
+			/* pathname body, done */
 			if (!nd->depth)
 				return 0;
 			name = nd->stack[nd->depth - 1].name;
-			/* called from trailing_symlink(), done */
+			/* trailing symlink, done */
 			if (!name)
 				return 0;
 			/* last component of nested symlink */
@@ -1862,8 +1862,8 @@ OK:
 	return err;
 }
 
-static int path_init(int dfd, const struct filename *name, unsigned int flags,
-		     struct nameidata *nd)
+static const char *path_init(int dfd, const struct filename *name,
+			     unsigned int flags, struct nameidata *nd)
 {
 	int retval = 0;
 	const char *s = name->name;
@@ -1871,15 +1871,16 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 	nd->last_type = LAST_ROOT; /* if there are only slashes... */
 	nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
 	nd->depth = 0;
+	nd->total_link_count = 0;
 	if (flags & LOOKUP_ROOT) {
 		struct dentry *root = nd->root.dentry;
 		struct inode *inode = root->d_inode;
 		if (*s) {
 			if (!d_can_lookup(root))
-				return -ENOTDIR;
+				return ERR_PTR(-ENOTDIR);
 			retval = inode_permission(inode, MAY_EXEC);
 			if (retval)
-				return retval;
+				return ERR_PTR(retval);
 		}
 		nd->path = nd->root;
 		nd->inode = inode;
@@ -1890,7 +1891,7 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 		} else {
 			path_get(&nd->path);
 		}
-		goto done;
+		return s;
 	}
 
 	nd->root.mnt = NULL;
@@ -1926,14 +1927,14 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 		struct dentry *dentry;
 
 		if (!f.file)
-			return -EBADF;
+			return ERR_PTR(-EBADF);
 
 		dentry = f.file->f_path.dentry;
 
 		if (*s) {
 			if (!d_can_lookup(dentry)) {
 				fdput(f);
-				return -ENOTDIR;
+				return ERR_PTR(-ENOTDIR);
 			}
 		}
 
@@ -1947,21 +1948,18 @@ static int path_init(int dfd, const struct filename *name, unsigned int flags,
 			nd->inode = nd->path.dentry->d_inode;
 		}
 		fdput(f);
-		goto done;
+		return s;
 	}
 
 	nd->inode = nd->path.dentry->d_inode;
 	if (!(flags & LOOKUP_RCU))
-		goto done;
+		return s;
 	if (likely(!read_seqcount_retry(&nd->path.dentry->d_seq, nd->seq)))
-		goto done;
+		return s;
 	if (!(nd->flags & LOOKUP_ROOT))
 		nd->root.mnt = NULL;
 	rcu_read_unlock();
-	return -ECHILD;
-done:
-	nd->total_link_count = 0;
-	return link_path_walk(s, nd);
+	return ERR_PTR(-ECHILD);
 }
 
 static void path_cleanup(struct nameidata *nd)
@@ -2014,23 +2012,12 @@ static inline int lookup_last(struct nameidata *nd)
 static int path_lookupat(int dfd, const struct filename *name,
 				unsigned int flags, struct nameidata *nd)
 {
+	const char *s = path_init(dfd, name, flags, nd);
 	int err;
 
-	/*
-	 * Path walking is largely split up into 2 different synchronisation
-	 * schemes, rcu-walk and ref-walk (explained in
-	 * Documentation/filesystems/path-lookup.txt). These share much of the
-	 * path walk code, but some things particularly setup, cleanup, and
-	 * following mounts are sufficiently divergent that functions are
-	 * duplicated. Typically there is a function foo(), and its RCU
-	 * analogue, foo_rcu().
-	 *
-	 * -ECHILD is the error number of choice (just to avoid clashes) that
-	 * is returned if some aspect of an rcu-walk fails. Such an error must
-	 * be handled by restarting a traditional ref-walk (which will always
-	 * be able to complete).
-	 */
-	err = path_init(dfd, name, flags, nd);
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+	err = link_path_walk(s, nd);
 	if (!err) {
 		while ((err = lookup_last(nd)) > 0) {
 			err = trailing_symlink(nd);
@@ -2075,7 +2062,11 @@ static int filename_lookup(int dfd, struct filename *name,
 static int path_parentat(int dfd, const struct filename *name,
 				unsigned int flags, struct nameidata *nd)
 {
-	int err = path_init(dfd, name, flags | LOOKUP_PARENT, nd);
+	const char *s = path_init(dfd, name, flags, nd);
+	int err;
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+	err = link_path_walk(s, nd);
 	if (!err)
 		err = complete_walk(nd);
 	path_cleanup(nd);
@@ -2406,7 +2397,11 @@ static int
 path_mountpoint(int dfd, const struct filename *name, struct path *path,
 		struct nameidata *nd, unsigned int flags)
 {
-	int err = path_init(dfd, name, flags, nd);
+	const char *s = path_init(dfd, name, flags, nd);
+	int err;
+	if (IS_ERR(s))
+		return PTR_ERR(s);
+	err = link_path_walk(s, nd);
 	if (unlikely(err))
 		goto out;
 
@@ -3266,6 +3261,7 @@ out:
 static struct file *path_openat(int dfd, struct filename *pathname,
 		struct nameidata *nd, const struct open_flags *op, int flags)
 {
+	const char *s;
 	struct file *file;
 	int opened = 0;
 	int error;
@@ -3281,7 +3277,12 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		goto out2;
 	}
 
-	error = path_init(dfd, pathname, flags, nd);
+	s = path_init(dfd, pathname, flags, nd);
+	if (IS_ERR(s)) {
+		put_filp(file);
+		return ERR_CAST(s);
+	}
+	error = link_path_walk(s, nd);
 	if (unlikely(error))
 		goto out;
 
Signature · 1 line
2.1.4
author maintainer
#197 lore raw

PATCH v3 89/110 namei: lift link_path_walk() call out of trailing_symlink()

In reply to Al Viro

From: Al Viro <[email protected]>

Make trailing_symlink() return the pathname to traverse or ERR_PTR(-E...). A subtle point is that for "magic" symlinks it returns "" now - that leads to link_path_walk("", nd), which is immediately returning 0 and we are back to the treatment of the last component, at whereever the damn thing has left us.

Reduces the stack footprint - link_path_walk() called on more shallow stack now.

---
 fs/namei.c | 50 +++++++++++++++++++++++---------------------------
 1 file changed, 23 insertions(+), 27 deletions(-)
fs/namei.c +23 -27
index 06c7120..46f4266 100644
@@ -1970,24 +1970,24 @@ static void path_cleanup(struct nameidata *nd)
 	}
 }
 
-static int trailing_symlink(struct nameidata *nd)
+static const char *trailing_symlink(struct nameidata *nd)
 {
 	const char *s;
 	int error = may_follow_link(nd);
 	if (unlikely(error)) {
 		terminate_walk(nd);
-		return error;
+		return ERR_PTR(error);
 	}
 	nd->flags |= LOOKUP_PARENT;
 	nd->stack[0].name = NULL;
 	s = get_link(nd);
 	if (unlikely(IS_ERR(s))) {
 		terminate_walk(nd);
-		return PTR_ERR(s);
+		return s;
 	}
 	if (unlikely(!s))
-		return 0;
-	return link_path_walk(s, nd);
+		s = "";
+	return s;
 }
 
 static inline int lookup_last(struct nameidata *nd)
@@ -2017,12 +2017,12 @@ static int path_lookupat(int dfd, const struct filename *name,
 
 	if (IS_ERR(s))
 		return PTR_ERR(s);
-	err = link_path_walk(s, nd);
-	if (!err) {
-		while ((err = lookup_last(nd)) > 0) {
-			err = trailing_symlink(nd);
-			if (err)
-				break;
+	while (!(err = link_path_walk(s, nd))
+		&& ((err = lookup_last(nd)) > 0)) {
+		s = trailing_symlink(nd);
+		if (IS_ERR(s)) {
+			err = PTR_ERR(s);
+			break;
 		}
 	}
 
@@ -2401,16 +2401,14 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 	int err;
 	if (IS_ERR(s))
 		return PTR_ERR(s);
-	err = link_path_walk(s, nd);
-	if (unlikely(err))
-		goto out;
-
-	while ((err = mountpoint_last(nd, path)) > 0) {
-		err = trailing_symlink(nd);
-		if (err)
+	while (!(err = link_path_walk(s, nd)) &&
+		(err = mountpoint_last(nd, path)) > 0) {
+		s = trailing_symlink(nd);
+		if (IS_ERR(s)) {
+			err = PTR_ERR(s);
 			break;
+		}
 	}
-out:
 	path_cleanup(nd);
 	return err;
 }
@@ -3282,17 +3280,15 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		put_filp(file);
 		return ERR_CAST(s);
 	}
-	error = link_path_walk(s, nd);
-	if (unlikely(error))
-		goto out;
-
-	while ((error = do_last(nd, file, op, &opened, pathname)) > 0) {
+	while (!(error = link_path_walk(s, nd)) &&
+		(error = do_last(nd, file, op, &opened, pathname)) > 0) {
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
-		error = trailing_symlink(nd);
-		if (unlikely(error))
+		s = trailing_symlink(nd);
+		if (IS_ERR(s)) {
+			error = PTR_ERR(s);
 			break;
+		}
 	}
-out:
 	path_cleanup(nd);
 out2:
 	if (!(opened & FILE_OPENED)) {
Signature · 1 line
2.1.4
author maintainer
#198 lore raw

PATCH v3 90/110 namei: lift terminate_walk() all the way up

In reply to Al Viro

From: Al Viro <[email protected]>

Lift it from link_path_walk(), trailing_symlink(), lookup_last(), mountpoint_last(), complete_walk() and do_last(). A _lot_ of those suckers merge.

---
 fs/namei.c | 106 ++++++++++++++++++++-----------------------------------------
 1 file changed, 34 insertions(+), 72 deletions(-)
fs/namei.c +34 -72
index 46f4266..27c3859 100644
@@ -563,8 +563,6 @@ static inline int nd_alloc_stack(struct nameidata *nd)
  * to restart the path walk from the beginning in ref-walk mode.
  */
 
-static void terminate_walk(struct nameidata *nd);
-
 /**
  * unlazy_walk - try to switch to ref-walk mode.
  * @nd: nameidata pathwalk data
@@ -673,10 +671,8 @@ static int complete_walk(struct nameidata *nd)
 	if (nd->flags & LOOKUP_RCU) {
 		if (!(nd->flags & LOOKUP_ROOT))
 			nd->root.mnt = NULL;
-		if (unlikely(unlazy_walk(nd, NULL))) {
-			terminate_walk(nd);
+		if (unlikely(unlazy_walk(nd, NULL)))
 			return -ECHILD;
-		}
 	}
 
 	if (likely(!(nd->flags & LOOKUP_JUMPED)))
@@ -692,7 +688,6 @@ static int complete_walk(struct nameidata *nd)
 	if (!status)
 		status = -ESTALE;
 
-	terminate_walk(nd);
 	return status;
 }
 
@@ -1858,7 +1853,6 @@ OK:
 			break;
 		}
 	}
-	terminate_walk(nd);
 	return err;
 }
 
@@ -1974,38 +1968,26 @@ static const char *trailing_symlink(struct nameidata *nd)
 {
 	const char *s;
 	int error = may_follow_link(nd);
-	if (unlikely(error)) {
-		terminate_walk(nd);
+	if (unlikely(error))
 		return ERR_PTR(error);
-	}
 	nd->flags |= LOOKUP_PARENT;
 	nd->stack[0].name = NULL;
 	s = get_link(nd);
-	if (unlikely(IS_ERR(s))) {
-		terminate_walk(nd);
-		return s;
-	}
-	if (unlikely(!s))
-		s = "";
-	return s;
+	return s ? s : "";
 }
 
 static inline int lookup_last(struct nameidata *nd)
 {
-	int err;
 	if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	nd->flags &= ~LOOKUP_PARENT;
-	err = walk_component(nd,
+	return walk_component(nd,
 			nd->flags & LOOKUP_FOLLOW
 				? nd->depth
 					? WALK_PUT | WALK_GET
 					: WALK_GET
 				: 0);
-	if (err < 0)
-		terminate_walk(nd);
-	return err;
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
@@ -2025,16 +2007,14 @@ static int path_lookupat(int dfd, const struct filename *name,
 			break;
 		}
 	}
-
 	if (!err)
 		err = complete_walk(nd);
 
-	if (!err && nd->flags & LOOKUP_DIRECTORY) {
-		if (!d_can_lookup(nd->path.dentry)) {
-			path_put(&nd->path);
+	if (!err && nd->flags & LOOKUP_DIRECTORY)
+		if (!d_can_lookup(nd->path.dentry))
 			err = -ENOTDIR;
-		}
-	}
+	if (err)
+		terminate_walk(nd);
 
 	path_cleanup(nd);
 	return err;
@@ -2069,6 +2049,8 @@ static int path_parentat(int dfd, const struct filename *name,
 	err = link_path_walk(s, nd);
 	if (!err)
 		err = complete_walk(nd);
+	if (err)
+		terminate_walk(nd);
 	path_cleanup(nd);
 	return err;
 }
@@ -2320,10 +2302,8 @@ mountpoint_last(struct nameidata *nd, struct path *path)
 
 	/* If we're in rcuwalk, drop out of it to handle last component */
 	if (nd->flags & LOOKUP_RCU) {
-		if (unlazy_walk(nd, NULL)) {
-			error = -ECHILD;
-			goto out;
-		}
+		if (unlazy_walk(nd, NULL))
+			return -ECHILD;
 	}
 
 	nd->flags &= ~LOOKUP_PARENT;
@@ -2331,7 +2311,7 @@ mountpoint_last(struct nameidata *nd, struct path *path)
 	if (unlikely(nd->last_type != LAST_NORM)) {
 		error = handle_dots(nd, nd->last_type);
 		if (error)
-			goto out;
+			return error;
 		dentry = dget(nd->path.dentry);
 		goto done;
 	}
@@ -2346,41 +2326,32 @@ mountpoint_last(struct nameidata *nd, struct path *path)
 		 */
 		dentry = d_alloc(dir, &nd->last);
 		if (!dentry) {
-			error = -ENOMEM;
 			mutex_unlock(&dir->d_inode->i_mutex);
-			goto out;
+			return -ENOMEM;
 		}
 		dentry = lookup_real(dir->d_inode, dentry, nd->flags);
-		error = PTR_ERR(dentry);
 		if (IS_ERR(dentry)) {
 			mutex_unlock(&dir->d_inode->i_mutex);
-			goto out;
+			return PTR_ERR(dentry);
 		}
 	}
 	mutex_unlock(&dir->d_inode->i_mutex);
 
 done:
 	if (d_is_negative(dentry)) {
-		error = -ENOENT;
 		dput(dentry);
-		goto out;
+		return -ENOENT;
 	}
 	if (nd->depth)
 		put_link(nd);
 	path->dentry = dentry;
 	path->mnt = nd->path.mnt;
 	error = should_follow_link(nd, path, nd->flags & LOOKUP_FOLLOW);
-	if (unlikely(error)) {
-		if (error < 0)
-			goto out;
+	if (unlikely(error))
 		return error;
-	}
 	mntget(path->mnt);
 	follow_mount(path);
-	error = 0;
-out:
-	terminate_walk(nd);
-	return error;
+	return 0;
 }
 
 /**
@@ -2409,6 +2380,7 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 			break;
 		}
 	}
+	terminate_walk(nd);
 	path_cleanup(nd);
 	return err;
 }
@@ -2982,10 +2954,8 @@ static int do_last(struct nameidata *nd,
 
 	if (nd->last_type != LAST_NORM) {
 		error = handle_dots(nd, nd->last_type);
-		if (unlikely(error)) {
-			terminate_walk(nd);
+		if (unlikely(error))
 			return error;
-		}
 		goto finish_open;
 	}
 
@@ -2998,7 +2968,7 @@ static int do_last(struct nameidata *nd,
 			goto finish_lookup;
 
 		if (error < 0)
-			goto out;
+			return error;
 
 		BUG_ON(nd->inode != dir->d_inode);
 	} else {
@@ -3013,10 +2983,9 @@ static int do_last(struct nameidata *nd,
 			return error;
 
 		audit_inode(name, dir, LOOKUP_PARENT);
-		error = -EISDIR;
 		/* trailing slashes? */
-		if (nd->last.name[nd->last.len])
-			goto out;
+		if (unlikely(nd->last.name[nd->last.len]))
+			return -EISDIR;
 	}
 
 retry_lookup:
@@ -3071,35 +3040,31 @@ retry_lookup:
 		got_write = false;
 	}
 
-	error = -EEXIST;
-	if ((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))
-		goto exit_dput;
+	if (unlikely((open_flag & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT))) {
+		path_to_nameidata(&path, nd);
+		return -EEXIST;
+	}
 
 	error = follow_managed(&path, nd);
-	if (error < 0)
-		goto out;
+	if (unlikely(error < 0))
+		return error;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 	inode = path.dentry->d_inode;
-	error = -ENOENT;
-	if (d_is_negative(path.dentry)) {
+	if (unlikely(d_is_negative(path.dentry))) {
 		path_to_nameidata(&path, nd);
-		goto out;
+		return -ENOENT;
 	}
 finish_lookup:
 	if (nd->depth)
 		put_link(nd);
 	error = should_follow_link(nd, &path, nd->flags & LOOKUP_FOLLOW);
-	if (unlikely(error)) {
-		if (error < 0)
-			goto out;
+	if (unlikely(error))
 		return error;
-	}
 
 	if (unlikely(d_is_symlink(path.dentry)) && !(open_flag & O_PATH)) {
 		path_to_nameidata(&path, nd);
-		error = -ELOOP;
-		goto out;
+		return -ELOOP;
 	}
 
 	if ((nd->flags & LOOKUP_RCU) || nd->path.mnt != path.mnt) {
@@ -3165,12 +3130,8 @@ out:
 	if (got_write)
 		mnt_drop_write(nd->path.mnt);
 	path_put(&save_parent);
-	terminate_walk(nd);
 	return error;
 
-exit_dput:
-	path_put_conditional(&path, nd);
-	goto out;
 exit_fput:
 	fput(file);
 	goto out;
@@ -3289,6 +3250,7 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 			break;
 		}
 	}
+	terminate_walk(nd);
 	path_cleanup(nd);
 out2:
 	if (!(opened & FILE_OPENED)) {
Signature · 1 line
2.1.4
author maintainer
#199 lore raw

PATCH v3 91/110 link_path_walk: use explicit returns for failure exits

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)
fs/namei.c +7 -12
index 27c3859..756e150 100644
@@ -1772,7 +1772,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 
 		err = may_lookup(nd);
  		if (err)
-			break;
+			return err;
 
 		hash_len = hash_name(name);
 
@@ -1794,7 +1794,7 @@ static int link_path_walk(const char *name, struct nameidata *nd)
 				struct qstr this = { { .hash_len = hash_len }, .name = name };
 				err = parent->d_op->d_hash(parent, &this);
 				if (err < 0)
-					break;
+					return err;
 				hash_len = this.hash_len;
 				name = this.name;
 			}
@@ -1829,15 +1829,13 @@ OK:
 			err = walk_component(nd, WALK_GET);
 		}
 		if (err < 0)
-			break;
+			return err;
 
 		if (err) {
 			const char *s = get_link(nd);
 
-			if (unlikely(IS_ERR(s))) {
-				err = PTR_ERR(s);
-				break;
-			}
+			if (unlikely(IS_ERR(s)))
+				return PTR_ERR(s);
 			err = 0;
 			if (unlikely(!s)) {
 				/* jumped */
@@ -1848,12 +1846,9 @@ OK:
 				continue;
 			}
 		}
-		if (!d_can_lookup(nd->path.dentry)) {
-			err = -ENOTDIR;
-			break;
-		}
+		if (unlikely(!d_can_lookup(nd->path.dentry)))
+			return -ENOTDIR;
 	}
-	return err;
 }
 
 static const char *path_init(int dfd, const struct filename *name,
Signature · 1 line
2.1.4
author maintainer
#200 lore raw

PATCH v3 92/110 namei: explicitly pass seq number to unlazy_walk() when dentry != NULL

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)
fs/namei.c +8 -7
index 756e150..97315df 100644
@@ -567,13 +567,14 @@ static inline int nd_alloc_stack(struct nameidata *nd)
  * unlazy_walk - try to switch to ref-walk mode.
  * @nd: nameidata pathwalk data
  * @dentry: child of nd->path.dentry or NULL
+ * @seq: seq number to check dentry against
  * Returns: 0 on success, -ECHILD on failure
  *
  * unlazy_walk attempts to legitimize the current nd->path, nd->root and dentry
  * for ref-walk mode.  @dentry must be a path found by a do_lookup call on
  * @nd or NULL.  Must be called from rcu-walk context.
  */
-static int unlazy_walk(struct nameidata *nd, struct dentry *dentry)
+static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
 {
 	struct fs_struct *fs = current->fs;
 	struct dentry *parent = nd->path.dentry;
@@ -615,7 +616,7 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry)
 	} else {
 		if (!lockref_get_not_dead(&dentry->d_lockref))
 			goto out;
-		if (read_seqcount_retry(&dentry->d_seq, nd->seq))
+		if (read_seqcount_retry(&dentry->d_seq, seq))
 			goto drop_dentry;
 	}
 
@@ -671,7 +672,7 @@ static int complete_walk(struct nameidata *nd)
 	if (nd->flags & LOOKUP_RCU) {
 		if (!(nd->flags & LOOKUP_ROOT))
 			nd->root.mnt = NULL;
-		if (unlikely(unlazy_walk(nd, NULL)))
+		if (unlikely(unlazy_walk(nd, NULL, 0)))
 			return -ECHILD;
 	}
 
@@ -1451,7 +1452,7 @@ static int lookup_fast(struct nameidata *nd,
 		if (likely(__follow_mount_rcu(nd, path, inode)))
 			return 0;
 unlazy:
-		if (unlazy_walk(nd, dentry))
+		if (unlazy_walk(nd, dentry, nd->seq))
 			return -ECHILD;
 	} else {
 		dentry = __d_lookup(parent, &nd->last);
@@ -1511,7 +1512,7 @@ static inline int may_lookup(struct nameidata *nd)
 		int err = inode_permission(nd->inode, MAY_EXEC|MAY_NOT_BLOCK);
 		if (err != -ECHILD)
 			return err;
-		if (unlazy_walk(nd, NULL))
+		if (unlazy_walk(nd, NULL, 0))
 			return -ECHILD;
 	}
 	return inode_permission(nd->inode, MAY_EXEC);
@@ -1552,7 +1553,7 @@ static int pick_link(struct nameidata *nd, struct path *link)
 	}
 	if (nd->flags & LOOKUP_RCU) {
 		if (unlikely(nd->path.mnt != link->mnt ||
-			     unlazy_walk(nd, link->dentry))) {
+			     unlazy_walk(nd, link->dentry, nd->seq))) {
 			return -ECHILD;
 		}
 	}
@@ -2297,7 +2298,7 @@ mountpoint_last(struct nameidata *nd, struct path *path)
 
 	/* If we're in rcuwalk, drop out of it to handle last component */
 	if (nd->flags & LOOKUP_RCU) {
-		if (unlazy_walk(nd, NULL))
+		if (unlazy_walk(nd, NULL, 0))
 			return -ECHILD;
 	}
 
Signature · 1 line
2.1.4
author maintainer
#201 lore raw

PATCH v3 93/110 namei: don't mangle nd->seq in lookup_fast()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 38 +++++++++++++++++++++++---------------
 1 file changed, 23 insertions(+), 15 deletions(-)
fs/namei.c +23 -15
index 97315df..e321450 100644
@@ -1134,7 +1134,7 @@ static inline int managed_dentry_rcu(struct dentry *dentry)
  * we meet a managed dentry that would need blocking.
  */
 static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
-			       struct inode **inode)
+			       struct inode **inode, unsigned *seqp)
 {
 	for (;;) {
 		struct mount *mounted;
@@ -1161,7 +1161,7 @@ static bool __follow_mount_rcu(struct nameidata *nd, struct path *path,
 		path->mnt = &mounted->mnt;
 		path->dentry = mounted->mnt.mnt_root;
 		nd->flags |= LOOKUP_JUMPED;
-		nd->seq = read_seqcount_begin(&path->dentry->d_seq);
+		*seqp = read_seqcount_begin(&path->dentry->d_seq);
 		/*
 		 * Update the inode too. We don't need to re-check the
 		 * dentry sequence number here after this d_inode read,
@@ -1397,7 +1397,8 @@ static struct dentry *__lookup_hash(struct qstr *name,
  *  It _is_ time-critical.
  */
 static int lookup_fast(struct nameidata *nd,
-		       struct path *path, struct inode **inode)
+		       struct path *path, struct inode **inode,
+		       unsigned *seqp)
 {
 	struct vfsmount *mnt = nd->path.mnt;
 	struct dentry *dentry, *parent = nd->path.dentry;
@@ -1437,8 +1438,8 @@ static int lookup_fast(struct nameidata *nd,
 		 */
 		if (__read_seqcount_retry(&parent->d_seq, nd->seq))
 			return -ECHILD;
-		nd->seq = seq;
 
+		*seqp = seq;
 		if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
 			status = d_revalidate(dentry, nd->flags);
 			if (unlikely(status <= 0)) {
@@ -1449,10 +1450,10 @@ static int lookup_fast(struct nameidata *nd,
 		}
 		path->mnt = mnt;
 		path->dentry = dentry;
-		if (likely(__follow_mount_rcu(nd, path, inode)))
+		if (likely(__follow_mount_rcu(nd, path, inode, seqp)))
 			return 0;
 unlazy:
-		if (unlazy_walk(nd, dentry, nd->seq))
+		if (unlazy_walk(nd, dentry, seq))
 			return -ECHILD;
 	} else {
 		dentry = __d_lookup(parent, &nd->last);
@@ -1543,7 +1544,7 @@ static void terminate_walk(struct nameidata *nd)
 		put_link(nd);
 }
 
-static int pick_link(struct nameidata *nd, struct path *link)
+static int pick_link(struct nameidata *nd, struct path *link, unsigned seq)
 {
 	int error;
 	struct saved *last;
@@ -1553,7 +1554,7 @@ static int pick_link(struct nameidata *nd, struct path *link)
 	}
 	if (nd->flags & LOOKUP_RCU) {
 		if (unlikely(nd->path.mnt != link->mnt ||
-			     unlazy_walk(nd, link->dentry, nd->seq))) {
+			     unlazy_walk(nd, link->dentry, seq))) {
 			return -ECHILD;
 		}
 	}
@@ -1577,13 +1578,14 @@ static int pick_link(struct nameidata *nd, struct path *link)
  * so we keep a cache of "no, this doesn't need follow_link"
  * for the common case.
  */
-static inline int should_follow_link(struct nameidata *nd, struct path *link, int follow)
+static inline int should_follow_link(struct nameidata *nd, struct path *link,
+				     int follow, unsigned seq)
 {
 	if (likely(!d_is_symlink(link->dentry)))
 		return 0;
 	if (!follow)
 		return 0;
-	return pick_link(nd, link);
+	return pick_link(nd, link, seq);
 }
 
 enum {WALK_GET = 1, WALK_PUT = 2};
@@ -1592,6 +1594,7 @@ static int walk_component(struct nameidata *nd, int flags)
 {
 	struct path path;
 	struct inode *inode;
+	unsigned seq;
 	int err;
 	/*
 	 * "." and ".." are special - ".." especially so because it has
@@ -1604,7 +1607,7 @@ static int walk_component(struct nameidata *nd, int flags)
 			put_link(nd);
 		return err;
 	}
-	err = lookup_fast(nd, &path, &inode);
+	err = lookup_fast(nd, &path, &inode, &seq);
 	if (unlikely(err)) {
 		if (err < 0)
 			return err;
@@ -1614,6 +1617,7 @@ static int walk_component(struct nameidata *nd, int flags)
 			return err;
 
 		inode = path.dentry->d_inode;
+		seq = 0;	/* we are already out of RCU mode */
 		err = -ENOENT;
 		if (d_is_negative(path.dentry))
 			goto out_path_put;
@@ -1621,11 +1625,12 @@ static int walk_component(struct nameidata *nd, int flags)
 
 	if (flags & WALK_PUT)
 		put_link(nd);
-	err = should_follow_link(nd, &path, flags & WALK_GET);
+	err = should_follow_link(nd, &path, flags & WALK_GET, seq);
 	if (unlikely(err))
 		return err;
 	path_to_nameidata(&path, nd);
 	nd->inode = inode;
+	nd->seq = seq;
 	return 0;
 
 out_path_put:
@@ -2342,7 +2347,7 @@ done:
 		put_link(nd);
 	path->dentry = dentry;
 	path->mnt = nd->path.mnt;
-	error = should_follow_link(nd, path, nd->flags & LOOKUP_FOLLOW);
+	error = should_follow_link(nd, path, nd->flags & LOOKUP_FOLLOW, 0);
 	if (unlikely(error))
 		return error;
 	mntget(path->mnt);
@@ -2939,6 +2944,7 @@ static int do_last(struct nameidata *nd,
 	bool will_truncate = (open_flag & O_TRUNC) != 0;
 	bool got_write = false;
 	int acc_mode = op->acc_mode;
+	unsigned seq;
 	struct inode *inode;
 	struct path save_parent = { .dentry = NULL, .mnt = NULL };
 	struct path path;
@@ -2959,7 +2965,7 @@ static int do_last(struct nameidata *nd,
 		if (nd->last.name[nd->last.len])
 			nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 		/* we _can_ be in RCU mode here */
-		error = lookup_fast(nd, &path, &inode);
+		error = lookup_fast(nd, &path, &inode, &seq);
 		if (likely(!error))
 			goto finish_lookup;
 
@@ -3047,6 +3053,7 @@ retry_lookup:
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
 	inode = path.dentry->d_inode;
+	seq = 0;	/* out of RCU mode, so the value doesn't matter */
 	if (unlikely(d_is_negative(path.dentry))) {
 		path_to_nameidata(&path, nd);
 		return -ENOENT;
@@ -3054,7 +3061,7 @@ retry_lookup:
 finish_lookup:
 	if (nd->depth)
 		put_link(nd);
-	error = should_follow_link(nd, &path, nd->flags & LOOKUP_FOLLOW);
+	error = should_follow_link(nd, &path, nd->flags & LOOKUP_FOLLOW, seq);
 	if (unlikely(error))
 		return error;
 
@@ -3072,6 +3079,7 @@ finish_lookup:
 
 	}
 	nd->inode = inode;
+	nd->seq = seq;
 	/* Why this, you ask?  _Now_ we might have grown LOOKUP_JUMPED... */
 finish_open:
 	error = complete_walk(nd);
Signature · 1 line
2.1.4
author maintainer
#202 lore raw

PATCH v3 94/110 namei: store inode in nd->stack[]

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
fs/namei.c +5 -3
index e321450..366b0f3 100644
@@ -507,6 +507,7 @@ struct nameidata {
 		struct path link;
 		void *cookie;
 		const char *name;
+		struct inode *inode;
 	} *stack, internal[EMBEDDED_LEVELS];
 };
 
@@ -746,7 +747,7 @@ void nd_jump_link(struct path *path)
 static inline void put_link(struct nameidata *nd)
 {
 	struct saved *last = nd->stack + --nd->depth;
-	struct inode *inode = last->link.dentry->d_inode;
+	struct inode *inode = last->inode;
 	if (last->cookie && inode->i_op->put_link)
 		inode->i_op->put_link(last->link.dentry, last->cookie);
 	path_put(&last->link);
@@ -779,7 +780,7 @@ static inline int may_follow_link(struct nameidata *nd)
 		return 0;
 
 	/* Allowed if owner and follower match. */
-	inode = nd->stack[0].link.dentry->d_inode;
+	inode = nd->stack[0].inode;
 	if (uid_eq(current_cred()->fsuid, inode->i_uid))
 		return 0;
 
@@ -870,7 +871,7 @@ const char *get_link(struct nameidata *nd)
 {
 	struct saved *last = nd->stack + nd->depth - 1;
 	struct dentry *dentry = last->link.dentry;
-	struct inode *inode = dentry->d_inode;
+	struct inode *inode = last->inode;
 	int error;
 	const char *res;
 
@@ -1569,6 +1570,7 @@ static int pick_link(struct nameidata *nd, struct path *link, unsigned seq)
 	last = nd->stack + nd->depth++;
 	last->link = *link;
 	last->cookie = NULL;
+	last->inode = d_backing_inode(link->dentry);
 	return 1;
 }
 
Signature · 1 line
2.1.4
author maintainer
#203 lore raw

PATCH v3 95/110 VFS: Handle lower layer dentry/inode in pathwalk

In reply to Al Viro

From: David Howells <[email protected]>

Make use of d_backing_inode() in pathwalk to gain access to an inode or dentry that's on a lower layer.

---
 fs/namei.c | 10 +++++-----
 fs/open.c  |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)
fs/namei.c +5 -5
index 366b0f3..bcacb31 100644
@@ -1423,7 +1423,7 @@ static int lookup_fast(struct nameidata *nd,
 		 * This sequence count validates that the inode matches
 		 * the dentry name information from lookup.
 		 */
-		*inode = dentry->d_inode;
+		*inode = d_backing_inode(dentry);
 		negative = d_is_negative(dentry);
 		if (read_seqcount_retry(&dentry->d_seq, seq))
 			return -ECHILD;
@@ -1483,7 +1483,7 @@ unlazy:
 	path->dentry = dentry;
 	err = follow_managed(path, nd);
 	if (likely(!err))
-		*inode = path->dentry->d_inode;
+		*inode = d_backing_inode(path->dentry);
 	return err;
 
 need_lookup:
@@ -1618,7 +1618,7 @@ static int walk_component(struct nameidata *nd, int flags)
 		if (err < 0)
 			return err;
 
-		inode = path.dentry->d_inode;
+		inode = d_backing_inode(path.dentry);
 		seq = 0;	/* we are already out of RCU mode */
 		err = -ENOENT;
 		if (d_is_negative(path.dentry))
@@ -2471,7 +2471,7 @@ EXPORT_SYMBOL(__check_sticky);
  */
 static int may_delete(struct inode *dir, struct dentry *victim, bool isdir)
 {
-	struct inode *inode = victim->d_inode;
+	struct inode *inode = d_backing_inode(victim);
 	int error;
 
 	if (d_is_negative(victim))
@@ -3054,7 +3054,7 @@ retry_lookup:
 		return error;
 
 	BUG_ON(nd->flags & LOOKUP_RCU);
-	inode = path.dentry->d_inode;
+	inode = d_backing_inode(path.dentry);
 	seq = 0;	/* out of RCU mode, so the value doesn't matter */
 	if (unlikely(d_is_negative(path.dentry))) {
 		path_to_nameidata(&path, nd);
fs/open.c +1 -1
index 98e5a52..e0250bd 100644
@@ -367,7 +367,7 @@ retry:
 	if (res)
 		goto out;
 
-	inode = path.dentry->d_inode;
+	inode = d_backing_inode(path.dentry);
 
 	if ((mode & MAY_EXEC) && S_ISREG(inode->i_mode)) {
 		/*
Signature · 1 line
2.1.4
author maintainer
#204 lore raw

PATCH v3 96/110 namei: pick_link() callers already have inode

In reply to Al Viro

From: Al Viro <[email protected]>

no need to refetch (and once we move unlazy out of there, recheck ->d_seq).

---
 fs/namei.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
fs/namei.c +11 -7
index bcacb31..33b655d 100644
@@ -1545,7 +1545,8 @@ static void terminate_walk(struct nameidata *nd)
 		put_link(nd);
 }
 
-static int pick_link(struct nameidata *nd, struct path *link, unsigned seq)
+static int pick_link(struct nameidata *nd, struct path *link,
+		     struct inode *inode, unsigned seq)
 {
 	int error;
 	struct saved *last;
@@ -1570,7 +1571,7 @@ static int pick_link(struct nameidata *nd, struct path *link, unsigned seq)
 	last = nd->stack + nd->depth++;
 	last->link = *link;
 	last->cookie = NULL;
-	last->inode = d_backing_inode(link->dentry);
+	last->inode = inode;
 	return 1;
 }
 
@@ -1581,13 +1582,14 @@ static int pick_link(struct nameidata *nd, struct path *link, unsigned seq)
  * for the common case.
  */
 static inline int should_follow_link(struct nameidata *nd, struct path *link,
-				     int follow, unsigned seq)
+				     int follow,
+				     struct inode *inode, unsigned seq)
 {
 	if (likely(!d_is_symlink(link->dentry)))
 		return 0;
 	if (!follow)
 		return 0;
-	return pick_link(nd, link, seq);
+	return pick_link(nd, link, inode, seq);
 }
 
 enum {WALK_GET = 1, WALK_PUT = 2};
@@ -1627,7 +1629,7 @@ static int walk_component(struct nameidata *nd, int flags)
 
 	if (flags & WALK_PUT)
 		put_link(nd);
-	err = should_follow_link(nd, &path, flags & WALK_GET, seq);
+	err = should_follow_link(nd, &path, flags & WALK_GET, inode, seq);
 	if (unlikely(err))
 		return err;
 	path_to_nameidata(&path, nd);
@@ -2349,7 +2351,8 @@ done:
 		put_link(nd);
 	path->dentry = dentry;
 	path->mnt = nd->path.mnt;
-	error = should_follow_link(nd, path, nd->flags & LOOKUP_FOLLOW, 0);
+	error = should_follow_link(nd, path, nd->flags & LOOKUP_FOLLOW,
+				   d_backing_inode(dentry), 0);
 	if (unlikely(error))
 		return error;
 	mntget(path->mnt);
@@ -3063,7 +3066,8 @@ retry_lookup:
 finish_lookup:
 	if (nd->depth)
 		put_link(nd);
-	error = should_follow_link(nd, &path, nd->flags & LOOKUP_FOLLOW, seq);
+	error = should_follow_link(nd, &path, nd->flags & LOOKUP_FOLLOW,
+				   inode, seq);
 	if (unlikely(error))
 		return error;
 
Signature · 1 line
2.1.4
author maintainer
#205 lore raw

PATCH v3 97/110 security/selinux: pass 'flags' arg to avc_audit() and avc_has_perm_flags()

In reply to Al Viro

From: NeilBrown <[email protected]>

This allows MAY_NOT_BLOCK to be passed, in RCU-walk mode, through the new avc_has_perm_flags() to avc_audit() and thence the slow_avc_audit.

---
 security/selinux/avc.c         | 18 +++++++++++++++++-
 security/selinux/hooks.c       |  2 +-
 security/selinux/include/avc.h |  9 +++++++--
 3 files changed, 25 insertions(+), 4 deletions(-)
security/selinux/avc.c +17 -1
index 3c17dda..0b122b1 100644
@@ -761,7 +761,23 @@ int avc_has_perm(u32 ssid, u32 tsid, u16 tclass,
 
 	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
 
-	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata);
+	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, 0);
+	if (rc2)
+		return rc2;
+	return rc;
+}
+
+int avc_has_perm_flags(u32 ssid, u32 tsid, u16 tclass,
+		       u32 requested, struct common_audit_data *auditdata,
+		       int flags)
+{
+	struct av_decision avd;
+	int rc, rc2;
+
+	rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
+
+	rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc,
+			auditdata, flags);
 	if (rc2)
 		return rc2;
 	return rc;
security/selinux/hooks.c +1 -1
index 8016229..d56a829 100644
@@ -1564,7 +1564,7 @@ static int cred_has_capability(const struct cred *cred,
 
 	rc = avc_has_perm_noaudit(sid, sid, sclass, av, 0, &avd);
 	if (audit == SECURITY_CAP_AUDIT) {
-		int rc2 = avc_audit(sid, sid, sclass, av, &avd, rc, &ad);
+		int rc2 = avc_audit(sid, sid, sclass, av, &avd, rc, &ad, 0);
 		if (rc2)
 			return rc2;
 	}
security/selinux/include/avc.h +7 -2
index ddf8eec..5973c32 100644
@@ -130,7 +130,8 @@ static inline int avc_audit(u32 ssid, u32 tsid,
 			    u16 tclass, u32 requested,
 			    struct av_decision *avd,
 			    int result,
-			    struct common_audit_data *a)
+			    struct common_audit_data *a,
+			    int flags)
 {
 	u32 audited, denied;
 	audited = avc_audit_required(requested, avd, result, 0, &denied);
@@ -138,7 +139,7 @@ static inline int avc_audit(u32 ssid, u32 tsid,
 		return 0;
 	return slow_avc_audit(ssid, tsid, tclass,
 			      requested, audited, denied, result,
-			      a, 0);
+			      a, flags);
 }
 
 #define AVC_STRICT 1 /* Ignore permissive mode. */
@@ -150,6 +151,10 @@ int avc_has_perm_noaudit(u32 ssid, u32 tsid,
 int avc_has_perm(u32 ssid, u32 tsid,
 		 u16 tclass, u32 requested,
 		 struct common_audit_data *auditdata);
+int avc_has_perm_flags(u32 ssid, u32 tsid,
+		       u16 tclass, u32 requested,
+		       struct common_audit_data *auditdata,
+		       int flags);
 
 u32 avc_policy_seqno(void);
 
Signature · 1 line
2.1.4
author maintainer
#206 lore raw

PATCH v3 98/110 security: make inode_follow_link RCU-walk aware

In reply to Al Viro

From: NeilBrown <[email protected]>

inode_follow_link now takes an inode and rcu flag as well as the dentry.

inode is used in preference to d_backing_inode(dentry), particularly in RCU-walk mode.

selinux_inode_follow_link() gets dentry_has_perm() and inode_has_perm() open-coded into it so that it can call avc_has_perm_flags() in way that is safe if LOOKUP_RCU is set.

Calling avc_has_perm_flags() with rcu_read_lock() held means that when avc_has_perm_noaudit calls avc_compute_av(), the attempt to rcu_read_unlock() before calling security_compute_av() will not actually drop the RCU read-lock.

However as security_compute_av() is completely in a read_lock()ed region, it should be safe with the RCU read-lock held.

---
 fs/namei.c               |  5 +++--
 include/linux/security.h | 12 +++++++++---
 security/capability.c    |  3 ++-
 security/security.c      |  7 ++++---
 security/selinux/hooks.c | 16 ++++++++++++++--
 5 files changed, 32 insertions(+), 11 deletions(-)
fs/namei.c +3 -2
index 33b655d..0fa7af2 100644
@@ -881,8 +881,9 @@ const char *get_link(struct nameidata *nd)
 
 	touch_atime(&last->link);
 
-	error = security_inode_follow_link(dentry);
-	if (error)
+	error = security_inode_follow_link(dentry, inode,
+					   nd->flags & LOOKUP_RCU);
+	if (unlikely(error))
 		return ERR_PTR(error);
 
 	nd->last_type = LAST_BIND;
include/linux/security.h +9 -3
index 62a6620..52febde 100644
@@ -476,6 +476,8 @@ static inline void security_free_mnt_opts(struct security_mnt_opts *opts)
  * @inode_follow_link:
  *	Check permission to follow a symbolic link when looking up a pathname.
  *	@dentry contains the dentry structure for the link.
+ *	@inode contains the inode, which itself is not stable in RCU-walk
+ *	@rcu indicates whether we are in RCU-walk mode.
  *	Return 0 if permission is granted.
  * @inode_permission:
  *	Check permission before accessing an inode.  This hook is called by the
@@ -1551,7 +1553,8 @@ struct security_operations {
 	int (*inode_rename) (struct inode *old_dir, struct dentry *old_dentry,
 			     struct inode *new_dir, struct dentry *new_dentry);
 	int (*inode_readlink) (struct dentry *dentry);
-	int (*inode_follow_link) (struct dentry *dentry);
+	int (*inode_follow_link) (struct dentry *dentry, struct inode *inode,
+				  bool rcu);
 	int (*inode_permission) (struct inode *inode, int mask);
 	int (*inode_setattr)	(struct dentry *dentry, struct iattr *attr);
 	int (*inode_getattr) (const struct path *path);
@@ -1837,7 +1840,8 @@ int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry,
 			  struct inode *new_dir, struct dentry *new_dentry,
 			  unsigned int flags);
 int security_inode_readlink(struct dentry *dentry);
-int security_inode_follow_link(struct dentry *dentry);
+int security_inode_follow_link(struct dentry *dentry, struct inode *inode,
+			       bool rcu);
 int security_inode_permission(struct inode *inode, int mask);
 int security_inode_setattr(struct dentry *dentry, struct iattr *attr);
 int security_inode_getattr(const struct path *path);
@@ -2239,7 +2243,9 @@ static inline int security_inode_readlink(struct dentry *dentry)
 	return 0;
 }
 
-static inline int security_inode_follow_link(struct dentry *dentry)
+static inline int security_inode_follow_link(struct dentry *dentry,
+					     struct inode *inode,
+					     bool rcu)
 {
 	return 0;
 }
security/capability.c +2 -1
index fae99db..7d3f38f 100644
@@ -209,7 +209,8 @@ static int cap_inode_readlink(struct dentry *dentry)
 	return 0;
 }
 
-static int cap_inode_follow_link(struct dentry *dentry)
+static int cap_inode_follow_link(struct dentry *dentry, struct inode *inode,
+				 bool rcu)
 {
 	return 0;
 }
security/security.c +4 -3
index d7c30b0..04c8fec 100644
@@ -581,11 +581,12 @@ int security_inode_readlink(struct dentry *dentry)
 	return security_ops->inode_readlink(dentry);
 }
 
-int security_inode_follow_link(struct dentry *dentry)
+int security_inode_follow_link(struct dentry *dentry, struct inode *inode,
+			       bool rcu)
 {
-	if (unlikely(IS_PRIVATE(d_backing_inode(dentry))))
+	if (unlikely(IS_PRIVATE(inode)))
 		return 0;
-	return security_ops->inode_follow_link(dentry);
+	return security_ops->inode_follow_link(dentry, inode, rcu);
 }
 
 int security_inode_permission(struct inode *inode, int mask)
security/selinux/hooks.c +14 -2
index d56a829..ffa5a64 100644
@@ -2861,11 +2861,23 @@ static int selinux_inode_readlink(struct dentry *dentry)
 	return dentry_has_perm(cred, dentry, FILE__READ);
 }
 
-static int selinux_inode_follow_link(struct dentry *dentry)
+static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode,
+				     bool rcu)
 {
 	const struct cred *cred = current_cred();
+	struct common_audit_data ad;
+	struct inode_security_struct *isec;
+	u32 sid;
 
-	return dentry_has_perm(cred, dentry, FILE__READ);
+	validate_creds(cred);
+
+	ad.type = LSM_AUDIT_DATA_DENTRY;
+	ad.u.dentry = dentry;
+	sid = cred_sid(cred);
+	isec = inode->i_security;
+
+	return avc_has_perm_flags(sid, isec->sid, isec->sclass, FILE__READ, &ad,
+				  rcu ? MAY_NOT_BLOCK : 0);
 }
 
 static noinline int audit_inode_permission(struct inode *inode,
Signature · 1 line
2.1.4
author maintainer
#207 lore raw

PATCH v3 99/110 switch ->put_link() from dentry to inode

In reply to Al Viro

From: Al Viro <[email protected]>

only one instance looks at that argument at all; that sole exception wants inode rather than dentry.

---
 Documentation/filesystems/Locking             |  2 +-
 Documentation/filesystems/vfs.txt             |  2 +-
 drivers/staging/lustre/lustre/llite/symlink.c |  2 +-
 fs/configfs/symlink.c                         |  2 +-
 fs/f2fs/namei.c                               |  2 +-
 fs/fuse/dir.c                                 |  2 +-
 fs/hostfs/hostfs_kern.c                       |  2 +-
 fs/hppfs/hppfs.c                              |  8 ++++----
 fs/kernfs/symlink.c                           |  2 +-
 fs/libfs.c                                    |  2 +-
 fs/namei.c                                    | 13 +++++++------
 fs/overlayfs/inode.c                          |  4 ++--
 fs/proc/inode.c                               |  2 +-
 include/linux/fs.h                            |  6 +++---
 mm/shmem.c                                    |  2 +-
 15 files changed, 27 insertions(+), 26 deletions(-)
Documentation/filesystems/Locking +1 -1
index 5b5b4f5..6a34a0f 100644
@@ -51,7 +51,7 @@ prototypes:
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
 	const char *(*follow_link) (struct dentry *, void **);
-	void (*put_link) (struct dentry *, void *);
+	void (*put_link) (struct inode *, void *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, unsigned int);
 	int (*get_acl)(struct inode *, int);
Documentation/filesystems/vfs.txt +1 -1
index 0dec8c8..542d935 100644
@@ -351,7 +351,7 @@ struct inode_operations {
 			struct inode *, struct dentry *, unsigned int);
 	int (*readlink) (struct dentry *, char __user *,int);
 	const char *(*follow_link) (struct dentry *, void **);
-	void (*put_link) (struct dentry *, void *);
+	void (*put_link) (struct inode *, void *);
 	int (*permission) (struct inode *, int);
 	int (*get_acl)(struct inode *, int);
 	int (*setattr) (struct dentry *, struct iattr *);
drivers/staging/lustre/lustre/llite/symlink.c +1 -1
index f3be3bf..69b2036 100644
@@ -141,7 +141,7 @@ static const char *ll_follow_link(struct dentry *dentry, void **cookie)
 	return symname;
 }
 
-static void ll_put_link(struct dentry *dentry, void *cookie)
+static void ll_put_link(struct inode *unused, void *cookie)
 {
 	ptlrpc_req_finished(cookie);
 }
fs/configfs/symlink.c +1 -1
index 0ace756..bc464c2 100644
@@ -296,7 +296,7 @@ static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
 	return ERR_PTR(error);
 }
 
-static void configfs_put_link(struct dentry *dentry, void *cookie)
+static void configfs_put_link(struct inode *unused, void *cookie)
 {
 	free_page((unsigned long)cookie);
 }
fs/f2fs/namei.c +1 -1
index cd05a7c..71765d0 100644
@@ -301,7 +301,7 @@ static const char *f2fs_follow_link(struct dentry *dentry, void **cookie)
 	const char *link = page_follow_link_light(dentry, cookie);
 	if (!IS_ERR(link) && !*link) {
 		/* this is broken symlink case */
-		page_put_link(dentry, *cookie);
+		page_put_link(NULL, *cookie);
 		link = ERR_PTR(-ENOENT);
 	}
 	return link;
fs/fuse/dir.c +1 -1
index d5cdef8..9e704c1 100644
@@ -1395,7 +1395,7 @@ static const char *fuse_follow_link(struct dentry *dentry, void **cookie)
 	return link;
 }
 
-static void fuse_put_link(struct dentry *dentry, void *cookie)
+static void fuse_put_link(struct inode *unused, void *cookie)
 {
 	free_page((unsigned long) cookie);
 }
fs/hostfs/hostfs_kern.c +1 -1
index 7b6ed7a..4a437ab 100644
@@ -915,7 +915,7 @@ static const char *hostfs_follow_link(struct dentry *dentry, void **cookie)
 	return *cookie = link;
 }
 
-static void hostfs_put_link(struct dentry *dentry, void *cookie)
+static void hostfs_put_link(struct inode *unused, void *cookie)
 {
 	__putname(cookie);
 }
fs/hppfs/hppfs.c +4 -4
index 15a774e..2867837 100644
@@ -649,12 +649,12 @@ static const char *hppfs_follow_link(struct dentry *dentry, void **cookie)
 	return d_inode(proc_dentry)->i_op->follow_link(proc_dentry, cookie);
 }
 
-static void hppfs_put_link(struct dentry *dentry, void *cookie)
+static void hppfs_put_link(struct inode *inode, void *cookie)
 {
-	struct dentry *proc_dentry = HPPFS_I(d_inode(dentry))->proc_dentry;
+	struct inode *proc_inode = d_inode(HPPFS_I(inode)->proc_dentry);
 
-	if (d_inode(proc_dentry)->i_op->put_link)
-		d_inode(proc_dentry)->i_op->put_link(proc_dentry, cookie);
+	if (proc_inode->i_op->put_link)
+		proc_inode->i_op->put_link(proc_inode, cookie);
 }
 
 static const struct inode_operations hppfs_dir_iops = {
fs/kernfs/symlink.c +1 -1
index 366c5a1..f6aa2e5 100644
@@ -126,7 +126,7 @@ static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie)
 	return *cookie = (char *)page;
 }
 
-static void kernfs_iop_put_link(struct dentry *dentry, void *cookie)
+static void kernfs_iop_put_link(struct inode *unused, void *cookie)
 {
 	free_page((unsigned long)cookie);
 }
fs/libfs.c +1 -1
index c5f3373..01c337b 100644
@@ -1024,7 +1024,7 @@ int noop_fsync(struct file *file, loff_t start, loff_t end, int datasync)
 }
 EXPORT_SYMBOL(noop_fsync);
 
-void kfree_put_link(struct dentry *dentry, void *cookie)
+void kfree_put_link(struct inode *unused, void *cookie)
 {
 	kfree(cookie);
 }
fs/namei.c +7 -6
index 0fa7af2..4303404 100644
@@ -749,7 +749,7 @@ static inline void put_link(struct nameidata *nd)
 	struct saved *last = nd->stack + --nd->depth;
 	struct inode *inode = last->inode;
 	if (last->cookie && inode->i_op->put_link)
-		inode->i_op->put_link(last->link.dentry, last->cookie);
+		inode->i_op->put_link(inode, last->cookie);
 	path_put(&last->link);
 }
 
@@ -4444,17 +4444,18 @@ EXPORT_SYMBOL(readlink_copy);
 int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen)
 {
 	void *cookie;
-	const char *link = dentry->d_inode->i_link;
+	struct inode *inode = d_inode(dentry);
+	const char *link = inode->i_link;
 	int res;
 
 	if (!link) {
-		link = dentry->d_inode->i_op->follow_link(dentry, &cookie);
+		link = inode->i_op->follow_link(dentry, &cookie);
 		if (IS_ERR(link))
 			return PTR_ERR(link);
 	}
 	res = readlink_copy(buffer, buflen, link);
-	if (dentry->d_inode->i_op->put_link)
-		dentry->d_inode->i_op->put_link(dentry, cookie);
+	if (inode->i_op->put_link)
+		inode->i_op->put_link(inode, cookie);
 	return res;
 }
 EXPORT_SYMBOL(generic_readlink);
@@ -4496,7 +4497,7 @@ const char *page_follow_link_light(struct dentry *dentry, void **cookie)
 }
 EXPORT_SYMBOL(page_follow_link_light);
 
-void page_put_link(struct dentry *dentry, void *cookie)
+void page_put_link(struct inode *unused, void *cookie)
 {
 	struct page *page = cookie;
 	kunmap(page);
fs/overlayfs/inode.c +2 -2
index 9986833..308379b 100644
@@ -174,7 +174,7 @@ static const char *ovl_follow_link(struct dentry *dentry, void **cookie)
 	return ret;
 }
 
-static void ovl_put_link(struct dentry *dentry, void *c)
+static void ovl_put_link(struct inode *unused, void *c)
 {
 	struct inode *realinode;
 	struct ovl_link_data *data = c;
@@ -183,7 +183,7 @@ static void ovl_put_link(struct dentry *dentry, void *c)
 		return;
 
 	realinode = data->realdentry->d_inode;
-	realinode->i_op->put_link(data->realdentry, data->cookie);
+	realinode->i_op->put_link(realinode, data->cookie);
 	kfree(data);
 }
 
fs/proc/inode.c +1 -1
index eb35874..afe232b 100644
@@ -402,7 +402,7 @@ static const char *proc_follow_link(struct dentry *dentry, void **cookie)
 	return pde->data;
 }
 
-static void proc_put_link(struct dentry *dentry, void *p)
+static void proc_put_link(struct inode *unused, void *p)
 {
 	unuse_pde(p);
 }
include/linux/fs.h +3 -3
index ed7c9f2..f21e332 100644
@@ -1613,7 +1613,7 @@ struct inode_operations {
 	struct posix_acl * (*get_acl)(struct inode *, int);
 
 	int (*readlink) (struct dentry *, char __user *,int);
-	void (*put_link) (struct dentry *, void *);
+	void (*put_link) (struct inode *, void *);
 
 	int (*create) (struct inode *,struct dentry *, umode_t, bool);
 	int (*link) (struct dentry *,struct inode *,struct dentry *);
@@ -2706,12 +2706,12 @@ 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 const char *page_follow_link_light(struct dentry *, void **);
-extern void page_put_link(struct dentry *, void *);
+extern void page_put_link(struct inode *, void *);
 extern int __page_symlink(struct inode *inode, const char *symname, int len,
 		int nofs);
 extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
-extern void kfree_put_link(struct dentry *, void *);
+extern void kfree_put_link(struct inode *, void *);
 extern int generic_readlink(struct dentry *, char __user *, int);
 extern void generic_fillattr(struct inode *, struct kstat *);
 int vfs_getattr_nosec(struct path *path, struct kstat *stat);
mm/shmem.c +1 -1
index e026822..a59087e 100644
@@ -2486,7 +2486,7 @@ static const char *shmem_follow_link(struct dentry *dentry, void **cookie)
 	return kmap(page);
 }
 
-static void shmem_put_link(struct dentry *dentry, void *cookie)
+static void shmem_put_link(struct inode *unused, void *cookie)
 {
 	struct page *page = cookie;
 	kunmap(page);
Signature · 1 line
2.1.4
author maintainer
#208 lore raw

PATCH v3 100/110 new helper: free_page_put_link()

In reply to Al Viro

From: Al Viro <[email protected]>

similar to kfree_put_link()

---
 fs/configfs/symlink.c | 7 +------
 fs/fuse/dir.c         | 7 +------
 fs/kernfs/symlink.c   | 7 +------
 fs/libfs.c            | 6 ++++++
 include/linux/fs.h    | 1 +
 5 files changed, 10 insertions(+), 18 deletions(-)
fs/configfs/symlink.c +1 -6
index bc464c2..ec5c832 100644
@@ -296,15 +296,10 @@ static const char *configfs_follow_link(struct dentry *dentry, void **cookie)
 	return ERR_PTR(error);
 }
 
-static void configfs_put_link(struct inode *unused, void *cookie)
-{
-	free_page((unsigned long)cookie);
-}
-
 const struct inode_operations configfs_symlink_inode_operations = {
 	.follow_link = configfs_follow_link,
 	.readlink = generic_readlink,
-	.put_link = configfs_put_link,
+	.put_link = free_page_put_link,
 	.setattr = configfs_setattr,
 };
 
fs/fuse/dir.c +1 -6
index 9e704c1..5e2e087 100644
@@ -1395,11 +1395,6 @@ static const char *fuse_follow_link(struct dentry *dentry, void **cookie)
 	return link;
 }
 
-static void fuse_put_link(struct inode *unused, void *cookie)
-{
-	free_page((unsigned long) cookie);
-}
-
 static int fuse_dir_open(struct inode *inode, struct file *file)
 {
 	return fuse_open_common(inode, file, true);
@@ -1915,7 +1910,7 @@ static const struct inode_operations fuse_common_inode_operations = {
 static const struct inode_operations fuse_symlink_inode_operations = {
 	.setattr	= fuse_setattr,
 	.follow_link	= fuse_follow_link,
-	.put_link	= fuse_put_link,
+	.put_link	= free_page_put_link,
 	.readlink	= generic_readlink,
 	.getattr	= fuse_getattr,
 	.setxattr	= fuse_setxattr,
fs/kernfs/symlink.c +1 -6
index f6aa2e5..db27252 100644
@@ -126,11 +126,6 @@ static const char *kernfs_iop_follow_link(struct dentry *dentry, void **cookie)
 	return *cookie = (char *)page;
 }
 
-static void kernfs_iop_put_link(struct inode *unused, void *cookie)
-{
-	free_page((unsigned long)cookie);
-}
-
 const struct inode_operations kernfs_symlink_iops = {
 	.setxattr	= kernfs_iop_setxattr,
 	.removexattr	= kernfs_iop_removexattr,
@@ -138,7 +133,7 @@ const struct inode_operations kernfs_symlink_iops = {
 	.listxattr	= kernfs_iop_listxattr,
 	.readlink	= generic_readlink,
 	.follow_link	= kernfs_iop_follow_link,
-	.put_link	= kernfs_iop_put_link,
+	.put_link	= free_page_put_link,
 	.setattr	= kernfs_iop_setattr,
 	.getattr	= kernfs_iop_getattr,
 	.permission	= kernfs_iop_permission,
fs/libfs.c +6 -0
index 01c337b..65e1fec 100644
@@ -1030,6 +1030,12 @@ void kfree_put_link(struct inode *unused, void *cookie)
 }
 EXPORT_SYMBOL(kfree_put_link);
 
+void free_page_put_link(struct inode *unused, void *cookie)
+{
+	free_page((unsigned long) cookie);
+}
+EXPORT_SYMBOL(free_page_put_link);
+
 /*
  * nop .set_page_dirty method so that people can use .page_mkwrite on
  * anon inodes.
include/linux/fs.h +1 -0
index f21e332..8f73851 100644
@@ -2712,6 +2712,7 @@ extern int __page_symlink(struct inode *inode, const char *symname, int len,
 extern int page_symlink(struct inode *inode, const char *symname, int len);
 extern const struct inode_operations page_symlink_inode_operations;
 extern void kfree_put_link(struct inode *, void *);
+extern void free_page_put_link(struct inode *, void *);
 extern int generic_readlink(struct dentry *, char __user *, int);
 extern void generic_fillattr(struct inode *, struct kstat *);
 int vfs_getattr_nosec(struct path *path, struct kstat *stat);
Signature · 1 line
2.1.4
author maintainer
#209 lore raw

PATCH v3 101/110 namei: make put_link() RCU-safe

In reply to Al Viro

From: Al Viro <[email protected]>

very simple - just make path_put() conditional on !RCU. Note that right now it doesn't get called in RCU mode - we leave it before getting anything into stack.

---
 fs/namei.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
fs/namei.c +2 -1
index 4303404..998c3c2 100644
@@ -750,7 +750,8 @@ static inline void put_link(struct nameidata *nd)
 	struct inode *inode = last->inode;
 	if (last->cookie && inode->i_op->put_link)
 		inode->i_op->put_link(inode, last->cookie);
-	path_put(&last->link);
+	if (!(nd->flags & LOOKUP_RCU))
+		path_put(&last->link);
 }
 
 int sysctl_protected_symlinks __read_mostly = 0;
Signature · 1 line
2.1.4
author maintainer
#210 lore raw

PATCH v3 102/110 namei: make may_follow_link() safe in RCU mode

In reply to Al Viro

From: Al Viro <[email protected]>

We *can't* call that audit garbage in RCU mode - it's doing a weird mix of allocations (GFP_NOFS, immediately followed by GFP_KERNEL) and I'm not touching that... thing again.

So if this security sclero^Whardening feature gets triggered when we are in RCU mode, tough - we'll fail with -ECHILD and have everything restarted in non-RCU mode. Only to hit the same test and fail, this time with EACCES and with (oh, rapture) an audit spew produced.

---
 fs/namei.c | 3 +++
 1 file changed, 3 insertions(+)
fs/namei.c +3 -0
index 998c3c2..20bf494 100644
@@ -794,6 +794,9 @@ static inline int may_follow_link(struct nameidata *nd)
 	if (uid_eq(parent->i_uid, inode->i_uid))
 		return 0;
 
+	if (nd->flags & LOOKUP_RCU)
+		return -ECHILD;
+
 	audit_log_link_denied("follow_link", &nd->stack[0].link);
 	return -EACCES;
 }
Signature · 1 line
2.1.4
author maintainer
#211 lore raw

PATCH v3 103/110 new helper: __legitimize_mnt()

In reply to Al Viro

From: Al Viro <[email protected]>

same as legitimize_mnt(), except that it does *not* drop and regain rcu_read_lock; return values are

0  =>  grabbed a reference, we are fine
1  =>  failed, just go away
-1 =>  failed, go away and mntput(bastard) when outside of rcu_read_lock
---
 fs/mount.h     |  1 +
 fs/namespace.c | 27 +++++++++++++++++++--------
 2 files changed, 20 insertions(+), 8 deletions(-)
fs/mount.h +1 -0
index 6a61c2b..b5b8082 100644
@@ -88,6 +88,7 @@ static inline int is_mounted(struct vfsmount *mnt)
 extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
 extern struct mount *__lookup_mnt_last(struct vfsmount *, struct dentry *);
 
+extern int __legitimize_mnt(struct vfsmount *, unsigned);
 extern bool legitimize_mnt(struct vfsmount *, unsigned);
 
 extern void __detach_mounts(struct dentry *dentry);
fs/namespace.c +19 -8
index 1b9e111..9c1c43d 100644
@@ -590,24 +590,35 @@ static void delayed_free_vfsmnt(struct rcu_head *head)
 }
 
 /* call under rcu_read_lock */
-bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
+int __legitimize_mnt(struct vfsmount *bastard, unsigned seq)
 {
 	struct mount *mnt;
 	if (read_seqretry(&mount_lock, seq))
-		return false;
+		return 1;
 	if (bastard == NULL)
-		return true;
+		return 0;
 	mnt = real_mount(bastard);
 	mnt_add_count(mnt, 1);
 	if (likely(!read_seqretry(&mount_lock, seq)))
-		return true;
+		return 0;
 	if (bastard->mnt_flags & MNT_SYNC_UMOUNT) {
 		mnt_add_count(mnt, -1);
-		return false;
+		return 1;
+	}
+	return -1;
+}
+
+/* call under rcu_read_lock */
+bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
+{
+	int res = __legitimize_mnt(bastard, seq);
+	if (likely(!res))
+		return true;
+	if (unlikely(res < 0)) {
+		rcu_read_unlock();
+		mntput(bastard);
+		rcu_read_lock();
 	}
-	rcu_read_unlock();
-	mntput(bastard);
-	rcu_read_lock();
 	return false;
 }
 
Signature · 1 line
2.1.4
author maintainer
#212 lore raw

PATCH v3 104/110 namei: store seq numbers in nd->stack[]

In reply to Al Viro

From: Al Viro <[email protected]>

we'll need them for unlazy_walk()

---
 fs/namei.c | 2 ++
 1 file changed, 2 insertions(+)
fs/namei.c +2 -0
index 20bf494..92bf031 100644
@@ -508,6 +508,7 @@ struct nameidata {
 		void *cookie;
 		const char *name;
 		struct inode *inode;
+		unsigned seq;
 	} *stack, internal[EMBEDDED_LEVELS];
 };
 
@@ -1577,6 +1578,7 @@ static int pick_link(struct nameidata *nd, struct path *link,
 	last->link = *link;
 	last->cookie = NULL;
 	last->inode = inode;
+	last->seq = seq;
 	return 1;
 }
 
Signature · 1 line
2.1.4
author maintainer
#213 lore raw

PATCH v3 105/110 namei: make unlazy_walk and terminate_walk handle nd->stack, add unlazy_link

In reply to Al Viro

From: Al Viro <[email protected]>

We are almost done - primitives for leaving RCU mode are aware of nd->stack now, a new primitive for going to non-RCU mode when we have a symlink on hands added.

The thing we are heavily relying upon is that *any* unlazy failure will be shortly followed by terminate_walk(), with no access to nameidata in between. So it's enough to leave the things in a state terminate_walk() would cope with.

---
 fs/namei.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 88 insertions(+), 17 deletions(-)
fs/namei.c +88 -17
index 92bf031..090214b 100644
@@ -554,6 +554,50 @@ static inline int nd_alloc_stack(struct nameidata *nd)
 	return __nd_alloc_stack(nd);
 }
 
+static void drop_links(struct nameidata *nd)
+{
+	int i = nd->depth;
+	while (i--) {
+		struct saved *last = nd->stack + i;
+		struct inode *inode = last->inode;
+		if (last->cookie && inode->i_op->put_link) {
+			inode->i_op->put_link(inode, last->cookie);
+			last->cookie = NULL;
+		}
+	}
+}
+
+static bool legitimize_path(struct nameidata *nd,
+			    struct path *path, unsigned seq)
+{
+	int res = __legitimize_mnt(path->mnt, nd->m_seq);
+	if (unlikely(res)) {
+		if (res > 0)
+			path->mnt = NULL;
+		path->dentry = NULL;
+		return false;
+	}
+	if (unlikely(!lockref_get_not_dead(&path->dentry->d_lockref))) {
+		path->dentry = NULL;
+		return false;
+	}
+	return !read_seqcount_retry(&path->dentry->d_seq, seq);
+}
+
+static bool legitimize_links(struct nameidata *nd)
+{
+	int i;
+	for (i = 0; i < nd->depth; i++) {
+		struct saved *last = nd->stack + i;
+		if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
+			drop_links(nd);
+			nd->depth = i;
+			return false;
+		}
+	}
+	return true;
+}
+
 /*
  * Path walking has 2 modes, rcu-walk and ref-walk (see
  * Documentation/filesystems/path-lookup.txt).  In situations when we can't
@@ -575,25 +619,33 @@ static inline int nd_alloc_stack(struct nameidata *nd)
  * unlazy_walk attempts to legitimize the current nd->path, nd->root and dentry
  * for ref-walk mode.  @dentry must be a path found by a do_lookup call on
  * @nd or NULL.  Must be called from rcu-walk context.
+ * Nothing should touch nameidata between unlazy_walk() failure and
+ * terminate_walk().
  */
 static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
 {
 	struct fs_struct *fs = current->fs;
 	struct dentry *parent = nd->path.dentry;
+	int res;
 
 	BUG_ON(!(nd->flags & LOOKUP_RCU));
 
-	/*
-	 * After legitimizing the bastards, terminate_walk()
-	 * will do the right thing for non-RCU mode, and all our
-	 * subsequent exit cases should rcu_read_unlock()
-	 * before returning.  Do vfsmount first; if dentry
-	 * can't be legitimized, just set nd->path.dentry to NULL
-	 * and rely on dput(NULL) being a no-op.
-	 */
-	if (!legitimize_mnt(nd->path.mnt, nd->m_seq))
-		return -ECHILD;
 	nd->flags &= ~LOOKUP_RCU;
+	if (unlikely(!legitimize_links(nd))) {
+		rcu_read_unlock();
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+		goto drop_root_mnt;
+	}
+	res = __legitimize_mnt(nd->path.mnt, nd->m_seq);
+	if (unlikely(res)) {
+		rcu_read_unlock();
+		if (res < 0)
+			mntput(nd->path.mnt);
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+		goto drop_root_mnt;
+	}
 
 	if (!lockref_get_not_dead(&parent->d_lockref)) {
 		nd->path.dentry = NULL;	
@@ -651,6 +703,23 @@ drop_root_mnt:
 	return -ECHILD;
 }
 
+static int unlazy_link(struct nameidata *nd, struct path *link, unsigned seq)
+{
+	if (unlikely(!legitimize_path(nd, link, seq))) {
+		drop_links(nd);
+		rcu_read_unlock();
+		nd->flags &= ~LOOKUP_RCU;
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+		if (!(nd->flags & LOOKUP_ROOT))
+			nd->root.mnt = NULL;
+	} else if (likely(unlazy_walk(nd, NULL, 0)) == 0) {
+		return 0;
+	}
+	path_put(link);
+	return -ECHILD;
+}
+
 static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
 {
 	return dentry->d_op->d_revalidate(dentry, flags);
@@ -1539,16 +1608,19 @@ static inline int handle_dots(struct nameidata *nd, int type)
 
 static void terminate_walk(struct nameidata *nd)
 {
+	drop_links(nd);
 	if (!(nd->flags & LOOKUP_RCU)) {
+		int i;
 		path_put(&nd->path);
+		for (i = 0; i < nd->depth; i++)
+			path_put(&nd->stack[i].link);
 	} else {
 		nd->flags &= ~LOOKUP_RCU;
 		if (!(nd->flags & LOOKUP_ROOT))
 			nd->root.mnt = NULL;
 		rcu_read_unlock();
 	}
-	while (unlikely(nd->depth))
-		put_link(nd);
+	nd->depth = 0;
 }
 
 static int pick_link(struct nameidata *nd, struct path *link,
@@ -1561,13 +1633,12 @@ static int pick_link(struct nameidata *nd, struct path *link,
 		return -ELOOP;
 	}
 	if (nd->flags & LOOKUP_RCU) {
-		if (unlikely(nd->path.mnt != link->mnt ||
-			     unlazy_walk(nd, link->dentry, seq))) {
+		if (unlikely(unlazy_link(nd, link, seq)))
 			return -ECHILD;
-		}
+	} else {
+		if (link->mnt == nd->path.mnt)
+			mntget(link->mnt);
 	}
-	if (link->mnt == nd->path.mnt)
-		mntget(link->mnt);
 	error = nd_alloc_stack(nd);
 	if (unlikely(error)) {
 		path_put(link);
Signature · 1 line
2.1.4
author maintainer
#214 lore raw

Re: [PATCH v3 105/110] namei: make unlazy_walk and terminate_walk handle nd->stack, add unlazy_link

fs/namei.c static inline int nd_alloc_stack(struct nameidata *nd) · line 594

Quoted 8 lines · Al Viro
fs/namei.c +8 -0
+static bool legitimize_links(struct nameidata *nd)
+{
+	int i;
+	for (i = 0; i < nd->depth; i++) {
+		struct saved *last = nd->stack + i;
+		if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
+			drop_links(nd);
+			nd->depth = i;

Broken, actually - it should be i + 1. What happens is that we attempt to grab references on nd->stack[...].link; if everything succeeds, we'd won. If legitimizing nd->stack[i].link fails (e.g. ->d_seq has changed on us), we
* put_link everything in stack and clear nd->stack[...].cookie, making
sure that nobody will call ->put_link() on it later.
* leave the things for terminate_walk() so that it would do
path_put() on everything we have grabbed and ignored everything we hadn't even got around to.

But this failed legitimize_path() requires path_put() - we *can't* block there (we wouldn't be able to do ->put_link() afterwards if we did), so we just zero what we didn't grab and leave what we had for subsequent path_put(). Which may be anything from "nothing" (mount_lock has been touched) to "both vfsmount and dentry" (->d_seq mismatch).

So we need to set nd->depth to i + 1 here, not i. As it is, we are risking a vfsmount (and possibly dentry) leak. Fixed and force-pushed...

author maintainer
#215 lore raw

Re: [PATCH v3 105/110] namei: make unlazy_walk and terminate_walk handle nd->stack, add unlazy_link

Quoted 20 lines · Al Viro

Review

Quoted 8 lines · Al Viro
(quoted diff) +8 -0
+static bool legitimize_links(struct nameidata *nd)
+{
+	int i;
+	for (i = 0; i < nd->depth; i++) {
+		struct saved *last = nd->stack + i;
+		if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
+			drop_links(nd);
+			nd->depth = i;

Broken, actually - it should be i + 1. What happens is that we attempt to grab references on nd->stack[...].link; if everything succeeds, we'd won. If legitimizing nd->stack[i].link fails (e.g. ->d_seq has changed on us), we
* put_link everything in stack and clear nd->stack[...].cookie, making
sure that nobody will call ->put_link() on it later.
* leave the things for terminate_walk() so that it would do
path_put() on everything we have grabbed and ignored everything we hadn't even got around to.

Quoted 8 lines

But this failed legitimize_path() requires path_put() - we *can't* block there (we wouldn't be able to do ->put_link() afterwards if we did), so we just zero what we didn't grab and leave what we had for subsequent path_put(). Which may be anything from "nothing" (mount_lock has been touched) to "both vfsmount and dentry" (->d_seq mismatch).

So we need to set nd->depth to i + 1 here, not i. As it is, we are risking a vfsmount (and possibly dentry) leak. Fixed and force-pushed...

FWIW, below is a better replacement; tested and force-pushed. And seeing that we just got nd->root_seq, I wonder if we really need messing with current->fs there - something like

	if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
		if (unlikely(!legitimize_path(&nd->root, nd->root_seq))) {
			rcu_read_unlock();
			dput(dentry);
			return -ECHILD;
		}
	}

should do better than playing with fs->lock, etc. we do right now...

fs/namei.c +99 -38
index 92bf031..6db14f2 100644
@@ -554,6 +554,68 @@ static inline int nd_alloc_stack(struct nameidata *nd)
 	return __nd_alloc_stack(nd);
 }
 
+static void drop_links(struct nameidata *nd)
+{
+	int i = nd->depth;
+	while (i--) {
+		struct saved *last = nd->stack + i;
+		struct inode *inode = last->inode;
+		if (last->cookie && inode->i_op->put_link) {
+			inode->i_op->put_link(inode, last->cookie);
+			last->cookie = NULL;
+		}
+	}
+}
+
+static void terminate_walk(struct nameidata *nd)
+{
+	drop_links(nd);
+	if (!(nd->flags & LOOKUP_RCU)) {
+		int i;
+		path_put(&nd->path);
+		for (i = 0; i < nd->depth; i++)
+			path_put(&nd->stack[i].link);
+	} else {
+		nd->flags &= ~LOOKUP_RCU;
+		if (!(nd->flags & LOOKUP_ROOT))
+			nd->root.mnt = NULL;
+		rcu_read_unlock();
+	}
+	nd->depth = 0;
+}
+
+/* path_put is needed afterwards regardless of success or failure */
+static bool legitimize_path(struct nameidata *nd,
+			    struct path *path, unsigned seq)
+{
+	int res = __legitimize_mnt(path->mnt, nd->m_seq);
+	if (unlikely(res)) {
+		if (res > 0)
+			path->mnt = NULL;
+		path->dentry = NULL;
+		return false;
+	}
+	if (unlikely(!lockref_get_not_dead(&path->dentry->d_lockref))) {
+		path->dentry = NULL;
+		return false;
+	}
+	return !read_seqcount_retry(&path->dentry->d_seq, seq);
+}
+
+static bool legitimize_links(struct nameidata *nd)
+{
+	int i;
+	for (i = 0; i < nd->depth; i++) {
+		struct saved *last = nd->stack + i;
+		if (unlikely(!legitimize_path(nd, &last->link, last->seq))) {
+			drop_links(nd);
+			nd->depth = i + 1;
+			return false;
+		}
+	}
+	return true;
+}
+
 /*
  * Path walking has 2 modes, rcu-walk and ref-walk (see
  * Documentation/filesystems/path-lookup.txt).  In situations when we can't
@@ -575,6 +637,8 @@ static inline int nd_alloc_stack(struct nameidata *nd)
  * unlazy_walk attempts to legitimize the current nd->path, nd->root and dentry
  * for ref-walk mode.  @dentry must be a path found by a do_lookup call on
  * @nd or NULL.  Must be called from rcu-walk context.
+ * Nothing should touch nameidata between unlazy_walk() failure and
+ * terminate_walk().
  */
 static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
 {
@@ -583,22 +647,13 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq
 
 	BUG_ON(!(nd->flags & LOOKUP_RCU));
 
-	/*
-	 * After legitimizing the bastards, terminate_walk()
-	 * will do the right thing for non-RCU mode, and all our
-	 * subsequent exit cases should rcu_read_unlock()
-	 * before returning.  Do vfsmount first; if dentry
-	 * can't be legitimized, just set nd->path.dentry to NULL
-	 * and rely on dput(NULL) being a no-op.
-	 */
-	if (!legitimize_mnt(nd->path.mnt, nd->m_seq))
-		return -ECHILD;
 	nd->flags &= ~LOOKUP_RCU;
-
-	if (!lockref_get_not_dead(&parent->d_lockref)) {
-		nd->path.dentry = NULL;	
-		goto out;
-	}
+	if (unlikely(!legitimize_links(nd)))
+		goto out2;
+	if (unlikely(!legitimize_mnt(nd->path.mnt, nd->m_seq)))
+		goto out2;
+	if (unlikely(!lockref_get_not_dead(&parent->d_lockref)))
+		goto out1;
 
 	/*
 	 * For a negative lookup, the lookup sequence point is the parents
@@ -628,8 +683,10 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq
 	 */
 	if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
 		spin_lock(&fs->lock);
-		if (nd->root.mnt != fs->root.mnt || nd->root.dentry != fs->root.dentry)
-			goto unlock_and_drop_dentry;
+		if (unlikely(!path_equal(&nd->root, &fs->root))) {
+			spin_unlock(&fs->lock);
+			goto drop_dentry;
+		}
 		path_get(&nd->root);
 		spin_unlock(&fs->lock);
 	}
@@ -637,12 +694,14 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq
 	rcu_read_unlock();
 	return 0;
 
-unlock_and_drop_dentry:
-	spin_unlock(&fs->lock);
 drop_dentry:
 	rcu_read_unlock();
 	dput(dentry);
 	goto drop_root_mnt;
+out2:
+	nd->path.mnt = NULL;
+out1:
+	nd->path.dentry = NULL;
 out:
 	rcu_read_unlock();
 drop_root_mnt:
@@ -651,6 +710,23 @@ drop_root_mnt:
 	return -ECHILD;
 }
 
+static int unlazy_link(struct nameidata *nd, struct path *link, unsigned seq)
+{
+	if (unlikely(!legitimize_path(nd, link, seq))) {
+		drop_links(nd);
+		rcu_read_unlock();
+		nd->flags &= ~LOOKUP_RCU;
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+		if (!(nd->flags & LOOKUP_ROOT))
+			nd->root.mnt = NULL;
+	} else if (likely(unlazy_walk(nd, NULL, 0)) == 0) {
+		return 0;
+	}
+	path_put(link);
+	return -ECHILD;
+}
+
 static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
 {
 	return dentry->d_op->d_revalidate(dentry, flags);
@@ -1537,20 +1613,6 @@ static inline int handle_dots(struct nameidata *nd, int type)
 	return 0;
 }
 
-static void terminate_walk(struct nameidata *nd)
-{
-	if (!(nd->flags & LOOKUP_RCU)) {
-		path_put(&nd->path);
-	} else {
-		nd->flags &= ~LOOKUP_RCU;
-		if (!(nd->flags & LOOKUP_ROOT))
-			nd->root.mnt = NULL;
-		rcu_read_unlock();
-	}
-	while (unlikely(nd->depth))
-		put_link(nd);
-}
-
 static int pick_link(struct nameidata *nd, struct path *link,
 		     struct inode *inode, unsigned seq)
 {
@@ -1561,13 +1623,12 @@ static int pick_link(struct nameidata *nd, struct path *link,
 		return -ELOOP;
 	}
 	if (nd->flags & LOOKUP_RCU) {
-		if (unlikely(nd->path.mnt != link->mnt ||
-			     unlazy_walk(nd, link->dentry, seq))) {
+		if (unlikely(unlazy_link(nd, link, seq)))
 			return -ECHILD;
-		}
+	} else {
+		if (link->mnt == nd->path.mnt)
+			mntget(link->mnt);
 	}
-	if (link->mnt == nd->path.mnt)
-		mntget(link->mnt);
 	error = nd_alloc_stack(nd);
 	if (unlikely(error)) {
 		path_put(link);
author maintainer
#216 lore raw

Re: [PATCH v3 105/110] namei: make unlazy_walk and terminate_walk handle nd->stack, add unlazy_link

fs/namei.c drop_root_mnt: · line 722

Quoted 10 lines · Al Viro
fs/namei.c +10 -0
+static int unlazy_link(struct nameidata *nd, struct path *link, unsigned seq)
+{
+	if (unlikely(!legitimize_path(nd, link, seq))) {
+		drop_links(nd);
+		rcu_read_unlock();
+		nd->flags &= ~LOOKUP_RCU;
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+		if (!(nd->flags & LOOKUP_ROOT))
+			nd->root.mnt = NULL;

... and nd->depth should be set to 0, to avoid bogus path_put() on the stuff in nd->stack[...].link when we get to terminate_walk(). Fixed and folded.

Quoted 53 lines
fs/namei.c +11 -20
+	} else if (likely(unlazy_walk(nd, NULL, 0)) == 0) {
+		return 0;
+	}
+	path_put(link);
+	return -ECHILD;
+}
+
 static inline int d_revalidate(struct dentry *dentry, unsigned int flags)
 {
 	return dentry->d_op->d_revalidate(dentry, flags);
@@ -1537,20 +1613,6 @@ static inline int handle_dots(struct nameidata *nd, int type)
 	return 0;
 }
 
-static void terminate_walk(struct nameidata *nd)
-{
-	if (!(nd->flags & LOOKUP_RCU)) {
-		path_put(&nd->path);
-	} else {
-		nd->flags &= ~LOOKUP_RCU;
-		if (!(nd->flags & LOOKUP_ROOT))
-			nd->root.mnt = NULL;
-		rcu_read_unlock();
-	}
-	while (unlikely(nd->depth))
-		put_link(nd);
-}
-
 static int pick_link(struct nameidata *nd, struct path *link,
 		     struct inode *inode, unsigned seq)
 {
@@ -1561,13 +1623,12 @@ static int pick_link(struct nameidata *nd, struct path *link,
 		return -ELOOP;
 	}
 	if (nd->flags & LOOKUP_RCU) {
-		if (unlikely(nd->path.mnt != link->mnt ||
-			     unlazy_walk(nd, link->dentry, seq))) {
+		if (unlikely(unlazy_link(nd, link, seq)))
 			return -ECHILD;
-		}
+	} else {
+		if (link->mnt == nd->path.mnt)
+			mntget(link->mnt);
 	}
-	if (link->mnt == nd->path.mnt)
-		mntget(link->mnt);
 	error = nd_alloc_stack(nd);
 	if (unlikely(error)) {
 		path_put(link);
--

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

author maintainer
#217 lore raw

PATCH v3 106/110 namei: don't unlazy until get_link()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 37 ++++++++++++++++++++++++++-----------
 1 file changed, 26 insertions(+), 11 deletions(-)
fs/namei.c +26 -11
index 090214b..4d6065d 100644
@@ -536,10 +536,19 @@ static void restore_nameidata(struct nameidata *old)
 
 static int __nd_alloc_stack(struct nameidata *nd)
 {
-	struct saved *p = kmalloc(MAXSYMLINKS * sizeof(struct saved),
+	struct saved *p;
+
+	if (nd->flags & LOOKUP_RCU) {
+		p= kmalloc(MAXSYMLINKS * sizeof(struct saved),
+				  GFP_ATOMIC);
+		if (unlikely(!p))
+			return -ECHILD;
+	} else {
+		p= kmalloc(MAXSYMLINKS * sizeof(struct saved),
 				  GFP_KERNEL);
-	if (unlikely(!p))
-		return -ENOMEM;
+		if (unlikely(!p))
+			return -ENOMEM;
+	}
 	memcpy(p, nd->internal, sizeof(nd->internal));
 	nd->stack = p;
 	return 0;
@@ -949,8 +958,10 @@ const char *get_link(struct nameidata *nd)
 	int error;
 	const char *res;
 
-	BUG_ON(nd->flags & LOOKUP_RCU);
-
+	if (nd->flags & LOOKUP_RCU) {
+		if (unlikely(unlazy_walk(nd, NULL, 0)))
+			return ERR_PTR(-ECHILD);
+	}
 	cond_resched();
 
 	touch_atime(&last->link);
@@ -1632,17 +1643,21 @@ static int pick_link(struct nameidata *nd, struct path *link,
 		path_to_nameidata(link, nd);
 		return -ELOOP;
 	}
-	if (nd->flags & LOOKUP_RCU) {
-		if (unlikely(unlazy_link(nd, link, seq)))
-			return -ECHILD;
-	} else {
+	if (!(nd->flags & LOOKUP_RCU)) {
 		if (link->mnt == nd->path.mnt)
 			mntget(link->mnt);
 	}
 	error = nd_alloc_stack(nd);
 	if (unlikely(error)) {
-		path_put(link);
-		return error;
+		if (error == -ECHILD) {
+			if (unlikely(unlazy_link(nd, link, seq)))
+				return -ECHILD;
+			error = nd_alloc_stack(nd);
+		}
+		if (error) {
+			path_put(link);
+			return error;
+		}
 	}
 
 	last = nd->stack + nd->depth++;
Signature · 1 line
2.1.4
author maintainer
#218 lore raw

PATCH v3 107/110 VFS/namei: make the use of touch_atime() in get_link() RCU-safe.

In reply to Al Viro

From: NeilBrown <[email protected]>

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.

---
 fs/inode.c         | 30 +++++++++++++++++++++---------
 fs/namei.c         | 12 +++++++++---
 include/linux/fs.h |  1 +
 3 files changed, 31 insertions(+), 12 deletions(-)
fs/inode.c +21 -9
index 952fb48..e8d6268 100644
@@ -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:
fs/namei.c +9 -3
index 4d6065d..9ef8c50 100644
@@ -958,13 +958,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);
+	} 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);
+	_cond_resched();
+	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);
include/linux/fs.h +1 -0
index 8f73851..1426c43 100644
@@ -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)
 {
Signature · 1 line
2.1.4
author maintainer
#219 lore raw

PATCH v3 108/110 enable passing fast relative symlinks without dropping out of RCU mode

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
fs/namei.c +8 -4
index 9ef8c50..1771143 100644
@@ -967,10 +967,6 @@ const char *get_link(struct nameidata *nd)
 	}
 
 	_cond_resched();
-	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);
@@ -980,6 +976,10 @@ const char *get_link(struct nameidata *nd)
 	nd->last_type = LAST_BIND;
 	res = inode->i_link;
 	if (!res) {
+		if (nd->flags & LOOKUP_RCU) {
+			if (unlikely(unlazy_walk(nd, NULL, 0)))
+				return ERR_PTR(-ECHILD);
+		}
 		res = inode->i_op->follow_link(dentry, &last->cookie);
 		if (IS_ERR_OR_NULL(res)) {
 			last->cookie = NULL;
@@ -987,6 +987,10 @@ const char *get_link(struct nameidata *nd)
 		}
 	}
 	if (*res == '/') {
+		if (nd->flags & LOOKUP_RCU) {
+			if (unlikely(unlazy_walk(nd, NULL, 0)))
+				return ERR_PTR(-ECHILD);
+		}
 		if (!nd->root.mnt)
 			set_root(nd);
 		path_put(&nd->path);
Signature · 1 line
2.1.4
author maintainer
#220 lore raw

PATCH v3 109/110 namei: handle absolute symlinks without dropping out of RCU mode

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 30 +++++++++++++++++++-----------
 1 file changed, 19 insertions(+), 11 deletions(-)
fs/namei.c +19 -11
index 1771143..fc8df8b 100644
@@ -499,7 +499,7 @@ struct nameidata {
 	struct path	root;
 	struct inode	*inode; /* path.dentry.d_inode */
 	unsigned int	flags;
-	unsigned	seq, m_seq;
+	unsigned	seq, m_seq, root_seq;
 	int		last_type;
 	unsigned	depth;
 	int		total_link_count;
@@ -780,14 +780,14 @@ static __always_inline void set_root(struct nameidata *nd)
 static __always_inline unsigned set_root_rcu(struct nameidata *nd)
 {
 	struct fs_struct *fs = current->fs;
-	unsigned seq, res;
+	unsigned seq;
 
 	do {
 		seq = read_seqcount_begin(&fs->seq);
 		nd->root = fs->root;
-		res = __read_seqcount_begin(&nd->root.dentry->d_seq);
+		nd->root_seq = __read_seqcount_begin(&nd->root.dentry->d_seq);
 	} while (read_seqcount_retry(&fs->seq, seq));
-	return res;
+	return nd->root_seq;
 }
 
 static void path_put_conditional(struct path *path, struct nameidata *nd)
@@ -988,15 +988,23 @@ const char *get_link(struct nameidata *nd)
 	}
 	if (*res == '/') {
 		if (nd->flags & LOOKUP_RCU) {
-			if (unlikely(unlazy_walk(nd, NULL, 0)))
+			struct dentry *d;
+			if (!nd->root.mnt)
+				set_root_rcu(nd);
+			nd->path = nd->root;
+			d = nd->path.dentry;
+			nd->inode = d->d_inode;
+			nd->seq = nd->root_seq;
+			if (unlikely(read_seqcount_retry(&d->d_seq, nd->seq)))
 				return ERR_PTR(-ECHILD);
+		} else {
+			if (!nd->root.mnt)
+				set_root(nd);
+			path_put(&nd->path);
+			nd->path = nd->root;
+			path_get(&nd->root);
+			nd->inode = nd->path.dentry->d_inode;
 		}
-		if (!nd->root.mnt)
-			set_root(nd);
-		path_put(&nd->path);
-		nd->path = nd->root;
-		path_get(&nd->root);
-		nd->inode = nd->path.dentry->d_inode;
 		nd->flags |= LOOKUP_JUMPED;
 		while (unlikely(*++res == '/'))
 			;
Signature · 1 line
2.1.4
author maintainer
#221 lore raw

Re: [PATCH v3 109/110] namei: handle absolute symlinks without dropping out of RCU mode

Quoted 11 lines · Al Viro
fs/namei.c +1 -1
@@ -499,7 +499,7 @@ struct nameidata {
 	struct path	root;
 	struct inode	*inode; /* path.dentry.d_inode */
 	unsigned int	flags;
-	unsigned	seq, m_seq;
+	unsigned	seq, m_seq, root_seq;
 	int		last_type;
 	unsigned	depth;
 	int		total_link_count;
@@ -780,14 +780,14 @@ static __always_inline void set_root(struct nameidata *nd)
 static __always_inline unsigned set_root_rcu(struct nameidata *nd)

fs/namei.c static __always_inline void set_root(struct nameidata *nd) · line 788

Quoted 10 lines
fs/namei.c +2 -2
 {
 	struct fs_struct *fs = current->fs;
-	unsigned seq, res;
+	unsigned seq;
 
 	do {
 		seq = read_seqcount_begin(&fs->seq);
 		nd->root = fs->root;
-		res = __read_seqcount_begin(&nd->root.dentry->d_seq);
+		nd->root_seq = __read_seqcount_begin(&nd->root.dentry->d_seq);

nd->root_seq is also needed in LOOKUP_ROOT | LOOKUP_RCU case. Fixed and folded.

author maintainer
#222 lore raw

PATCH v3 110/110 update Documentation/filesystems/ regarding the follow_link/put_link changes

In reply to Al Viro

From: Al Viro <[email protected]>

---
 Documentation/filesystems/porting | 17 +++++++++++++++++
 Documentation/filesystems/vfs.txt | 18 ++++++++++--------
 2 files changed, 27 insertions(+), 8 deletions(-)
Documentation/filesystems/porting +17 -0
index e69274d..3eae250 100644
@@ -483,3 +483,20 @@ in your dentry operations instead.
 --
 [mandatory]
 	->aio_read/->aio_write are gone.  Use ->read_iter/->write_iter.
+---
+[recommended]
+	for embedded ("fast") symlinks just set inode->i_link to wherever the
+	symlink body is and use simple_follow_link() as ->follow_link().
+--
+[mandatory]
+	calling conventions for ->follow_link() have changed.  Instead of returning
+	cookie and using nd_set_link() to store the body to traverse, we return
+	the body to traverse and store the cookie using explicit void ** argument.
+	nameidata isn't passed at all - nd_jump_link() doesn't need it and
+	nd_[gs]et_link() is gone.
+--
+[mandatory]
+	calling conventions for ->put_link() have changed.  It gets inode instead of
+	dentry,  it does not get nameidata at all and it gets called only when cookie
+	is non-NULL.  Note that link body isn't available anymore, so if you need it,
+	store it as cookie.
Documentation/filesystems/vfs.txt +10 -8
index 542d935..b403b29 100644
@@ -436,16 +436,18 @@ otherwise noted.
 
   follow_link: called by the VFS to follow a symbolic link to the
 	inode it points to.  Only required if you want to support
-	symbolic links.  This method returns a void pointer cookie
-	that is passed to put_link().
+	symbolic links.  This method returns the symlink body
+	to traverse (and possibly resets the current position with
+	nd_jump_link()).  If the body won't go away until the inode
+	is gone, nothing else is needed; if it needs to be otherwise
+	pinned, the data needed to release whatever we'd grabbed
+	is to be stored in void * variable passed by address to
+	follow_link() instance.
 
   put_link: called by the VFS to release resources allocated by
-  	follow_link().  The cookie returned by follow_link() is passed
-  	to this method as the last parameter.  It is used by
-  	filesystems such as NFS where page cache is not stable
-  	(i.e. page that was installed when the symbolic link walk
-  	started might not be in the page cache at the end of the
-  	walk).
+	follow_link().  The cookie stored by follow_link() is passed
+	to this method as the last parameter; only called when
+	cookie isn't NULL.
 
   permission: called by the VFS to check for access rights on a POSIX-like
   	filesystem.
Signature · 1 line
2.1.4
author maintainer
#223 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to Al Viro
	More on top of the current vfs.git#for-next (== the posted patchset

with a couple of fixes): more fs/namei.c reorganization and stack footprint reduction (below 1Kb now). One interesting piece of that is that we don't touch current->fs->lock anymore - unlazy_walk() used to, but now we can get rid of that.

FWIW, at that point I'm starting to seriously look into a primitive
that would take the usual dfd+name+flags and (path x inode x bool -> int) callback (since we don't have closures, it'd have to be
int filename_apply(int dfd, struct filename *name, unsigned flags,

		   int (*act)(struct path *path,
			      struct inode *inode,
			      bool may_block,
			      void *ctx),
		   void *ctx);

) with lookup done and if it ends up at something positive, act() called for it. If we end up reaching the very end in RCU mode, act() gets called with false as the third argument, _without_ dropping rcu_read_lock() or grabbing references. It may return -ECHILD, in which case we'll unlazy and call it again with may_block being true; if it does *not* return -ECHILD, we'll check for mount lock and d_seq still being valid. If they are, we are done, if not - restart the lookup from scratch in non-lazy mode.

Basically, that's your "could we get stat(2) without ever dirtying
anything shared?" thing, except that it's might be applicable to some of the getxattr(), statfs(), access(), listxattr() and readlink() as well. The obstacles for stat() are
* ->d_weak_revalidate() needs to be taught about being called in
RCU mode. Not a problem - flags are already passed and one of two instances is already checking for LOOKUP_RCU (what with being ->d_revalidate() at the same time). That one applies to all of them, not just stat().
* Linux S&M with its usual habit of sticking hooks into every
orifice out there (and if there hadn't been one, the hook still goes in, of course). In this case it's not just selinux, as with follow_link - apparmor, tomoyo and smack are also there. selinux one looks like it could be made to work if given an inode and may_block in addition to struct path; the rest... no idea.
* telling ->getattr() that we are in RCU mode. And giving it
inode, of course. As the first approximation, we could live with just the "if ->getattr isn't NULL, chicken out and return -ECHILD", but e.g. ext4, btrfs and xfs have non-NULL ->getattr(). In this case I wonder if adding a new method wouldn't be the right thing...

Overall, it seems to be doable, and with the results of massage
already done to fs/namei.c the PITA promises to be fairly limited. How generic do we really want it? I mean, is e.g. access(2) (faccessat(2)) worth bothering with? Or getxattr(2), for that matter... Comments?

Anyway, additional pieces of the series follow:

namei: unlazy_walk() doesn't need to mess with current->fs anymore
lustre: kill unused macro (LOOKUP_CONTINUE)
lustre: kill unused helper
get rid of assorted nameidata-related debris
[Neil's] Documentation: remove outdated information from automount-support.txt
namei: be careful with mountpoint crossings in follow_dotdot_rcu()
namei: uninline set_root{,_rcu}()
namei: pass the struct path to store the result down into path_lookupat()
namei: move putname() call into filename_lookup()
namei: shift nameidata inside filename_lookup()
namei: make filename_lookup() reject ERR_PTR() passed as name
namei: shift nameidata down into filename_parentat()
namei: saner calling conventions for filename_create()
namei: saner calling conventions for filename_parentat()
namei: fold path_cleanup() into terminate_walk()
namei: stash dfd and name into nameidata
namei: trim do_last() arguments
inline user_path_parent()
inline user_path_create()
namei: move saved_nd pointer into struct nameidata
turn user_{path_at,path,lpath,path_dir}() into static inlines

author maintainer
#224 lore raw

PATCH 1/21 namei: unlazy_walk() doesn't need to mess with current->fs anymore

From: Al Viro <[email protected]>

now that we have ->root_seq, legitimize_path(&nd->root, nd->root_seq) will do just fine...

---
 fs/namei.c | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)
fs/namei.c +4 -7
index 3ee1d65..e736722 100644
@@ -651,7 +651,6 @@ static bool legitimize_links(struct nameidata *nd)
  */
 static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq)
 {
-	struct fs_struct *fs = current->fs;
 	struct dentry *parent = nd->path.dentry;
 
 	BUG_ON(!(nd->flags & LOOKUP_RCU));
@@ -691,13 +690,11 @@ static int unlazy_walk(struct nameidata *nd, struct dentry *dentry, unsigned seq
 	 * still valid and get it if required.
 	 */
 	if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
-		spin_lock(&fs->lock);
-		if (unlikely(!path_equal(&nd->root, &fs->root))) {
-			spin_unlock(&fs->lock);
-			goto drop_dentry;
+		if (unlikely(!legitimize_path(nd, &nd->root, nd->root_seq))) {
+			rcu_read_unlock();
+			dput(dentry);
+			return -ECHILD;
 		}
-		path_get(&nd->root);
-		spin_unlock(&fs->lock);
 	}
 
 	rcu_read_unlock();
Signature · 1 line
2.1.4
author maintainer
#225 lore raw

PATCH 2/21 lustre: kill unused macro (LOOKUP_CONTINUE)

In reply to Al Viro

From: Al Viro <[email protected]>

---
 drivers/staging/lustre/lustre/llite/llite_internal.h | 6 ------
 1 file changed, 6 deletions(-)
drivers/staging/lustre/lustre/llite/llite_internal.h +0 -6
index 5f918e3..528af90 100644
@@ -57,12 +57,6 @@
 #define VM_FAULT_RETRY 0
 #endif
 
-/* Kernel 3.1 kills LOOKUP_CONTINUE, LOOKUP_PARENT is equivalent to it.
- * seem kernel commit 49084c3bb2055c401f3493c13edae14d49128ca0 */
-#ifndef LOOKUP_CONTINUE
-#define LOOKUP_CONTINUE LOOKUP_PARENT
-#endif
-
 /** Only used on client-side for indicating the tail of dir hash/offset. */
 #define LL_DIR_END_OFF	  0x7fffffffffffffffULL
 #define LL_DIR_END_OFF_32BIT    0x7fffffffUL
Signature · 1 line
2.1.4
author maintainer
#226 lore raw

PATCH 3/21 lustre: kill unused helper

In reply to Al Viro

From: Al Viro <[email protected]>

---
 .../staging/lustre/lustre/include/linux/lustre_compat25.h | 15 ---------------
 1 file changed, 15 deletions(-)
drivers/staging/lustre/lustre/include/linux/lustre_compat25.h +0 -15
index 3925db1..513c81f 100644
@@ -189,22 +189,7 @@ static inline int ll_quota_off(struct super_block *sb, int off, int remount)
 #endif
 
 
-
-/*
- * After 3.1, kernel's nameidata.intent.open.flags is different
- * with lustre's lookup_intent.it_flags, as lustre's it_flags'
- * lower bits equal to FMODE_xxx while kernel doesn't transliterate
- * lower bits of nameidata.intent.open.flags to FMODE_xxx.
- * */
 #include <linux/version.h>
-static inline int ll_namei_to_lookup_intent_flag(int flag)
-{
-#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 1, 0)
-	flag = (flag & ~O_ACCMODE) | OPEN_FMODE(flag);
-#endif
-	return flag;
-}
-
 #include <linux/fs.h>
 
 # define ll_umode_t	umode_t
Signature · 1 line
2.1.4
author maintainer
#227 lore raw

PATCH 4/21 get rid of assorted nameidata-related debris

In reply to Al Viro

From: Al Viro <[email protected]>

pointless forward declarations, stale comments

---
 fs/9p/vfs_inode.c      | 3 +--
 fs/9p/vfs_inode_dotl.c | 3 +--
 fs/ecryptfs/inode.c    | 3 +--
 fs/ntfs/namei.c        | 2 +-
 include/linux/fs.h     | 1 -
 include/linux/namei.h  | 1 -
 include/linux/sched.h  | 1 +
 7 files changed, 5 insertions(+), 9 deletions(-)
fs/9p/vfs_inode.c +1 -2
index 271f51a..510040b 100644
@@ -1226,8 +1226,7 @@ ino_t v9fs_qid2ino(struct p9_qid *qid)
 /**
  * v9fs_vfs_follow_link - follow a symlink path
  * @dentry: dentry for symlink
- * @nd: nameidata
- *
+ * @cookie: place to pass the data to put_link()
  */
 
 static const char *v9fs_vfs_follow_link(struct dentry *dentry, void **cookie)
fs/9p/vfs_inode_dotl.c +1 -2
index 16658ed..09e4433 100644
@@ -905,8 +905,7 @@ error:
 /**
  * v9fs_vfs_follow_link_dotl - follow a symlink path
  * @dentry: dentry for symlink
- * @nd: nameidata
- *
+ * @cookie: place to pass the data to put_link()
  */
 
 static const char *
fs/ecryptfs/inode.c +1 -2
index 73d20ae..3c4db11 100644
@@ -170,7 +170,6 @@ out_unlock:
  * @directory_inode: inode of the new file's dentry's parent in ecryptfs
  * @ecryptfs_dentry: New file's dentry in ecryptfs
  * @mode: The mode of the new file
- * @nd: nameidata of ecryptfs' parent's dentry & vfsmount
  *
  * Creates the underlying file and the eCryptfs inode which will link to
  * it. It will also update the eCryptfs directory inode to mimic the
@@ -384,7 +383,7 @@ static int ecryptfs_lookup_interpose(struct dentry *dentry,
  * ecryptfs_lookup
  * @ecryptfs_dir_inode: The eCryptfs directory inode
  * @ecryptfs_dentry: The eCryptfs dentry that we are looking up
- * @ecryptfs_nd: nameidata; may be NULL
+ * @flags: lookup flags
  *
  * Find a file on disk. If the file does not exist, then we'll add it to the
  * dentry cache and continue on to read it from the disk.
fs/ntfs/namei.c +1 -1
index 0f35b80..443abec 100644
@@ -35,7 +35,7 @@
  * ntfs_lookup - find the inode represented by a dentry in a directory inode
  * @dir_ino:	directory inode in which to look for the inode
  * @dent:	dentry representing the inode to look for
- * @nd:		lookup nameidata
+ * @flags:	lookup flags
  *
  * In short, ntfs_lookup() looks for the inode represented by the dentry @dent
  * in the directory inode @dir_ino and if found attaches the inode to the
include/linux/fs.h +0 -1
index 1426c43..b577e80 100644
@@ -38,7 +38,6 @@ struct backing_dev_info;
 struct export_operations;
 struct hd_geometry;
 struct iovec;
-struct nameidata;
 struct kiocb;
 struct kobject;
 struct pipe_inode_info;
include/linux/namei.h +0 -1
index d756304..1208e48 100644
@@ -7,7 +7,6 @@
 #include <linux/path.h>
 
 struct vfsmount;
-struct nameidata;
 
 enum { MAX_NESTED_LINKS = 8 };
 
include/linux/sched.h +1 -0
index f6c9b69..a1158c9 100644
@@ -132,6 +132,7 @@ struct fs_struct;
 struct perf_event_context;
 struct blk_plug;
 struct filename;
+struct nameidata;
 
 #define VMACACHE_BITS 2
 #define VMACACHE_SIZE (1U << VMACACHE_BITS)
Signature · 1 line
2.1.4
author maintainer
#228 lore raw

PATCH 5/21 Documentation: remove outdated information from automount-support.txt

In reply to Al Viro

From: NeilBrown <[email protected]>

The guidelines for adding automount support to a filesystem in filesystems/automount-support.txt is out or date. filesystems/autofs4.txt contains more current text, so replace the out-of-date content with a reference to that.

---
 Documentation/filesystems/automount-support.txt | 51 +++++++------------------
 1 file changed, 13 insertions(+), 38 deletions(-)
Documentation/filesystems/automount-support.txt +13 -38
index 7cac200..7eb762e 100644
@@ -1,41 +1,15 @@
-Support is available for filesystems that wish to do automounting support (such
-as kAFS which can be found in fs/afs/). This facility includes allowing
-in-kernel mounts to be performed and mountpoint degradation to be
-requested. The latter can also be requested by userspace.
+Support is available for filesystems that wish to do automounting
+support (such as kAFS which can be found in fs/afs/ and NFS in
+fs/nfs/). This facility includes allowing in-kernel mounts to be
+performed and mountpoint degradation to be requested. The latter can
+also be requested by userspace.
 
 
 ======================
 IN-KERNEL AUTOMOUNTING
 ======================
 
-A filesystem can now mount another filesystem on one of its directories by the
-following procedure:
-
- (1) Give the directory a follow_link() operation.
-
-     When the directory is accessed, the follow_link op will be called, and
-     it will be provided with the location of the mountpoint in the nameidata
-     structure (vfsmount and dentry).
-
- (2) Have the follow_link() op do the following steps:
-
-     (a) Call vfs_kern_mount() to call the appropriate filesystem to set up a
-         superblock and gain a vfsmount structure representing it.
-
-     (b) Copy the nameidata provided as an argument and substitute the dentry
-	 argument into it the copy.
-
-     (c) Call do_add_mount() to install the new vfsmount into the namespace's
-	 mountpoint tree, thus making it accessible to userspace. Use the
-	 nameidata set up in (b) as the destination.
-
-	 If the mountpoint will be automatically expired, then do_add_mount()
-	 should also be given the location of an expiration list (see further
-	 down).
-
-     (d) Release the path in the nameidata argument and substitute in the new
-	 vfsmount and its root dentry. The ref counts on these will need
-	 incrementing.
+See section "Mount Traps" of  Documentation/filesystems/autofs4.txt
 
 Then from userspace, you can just do something like:
 
@@ -61,17 +35,18 @@ AUTOMATIC MOUNTPOINT EXPIRY
 ===========================
 
 Automatic expiration of mountpoints is easy, provided you've mounted the
-mountpoint to be expired in the automounting procedure outlined above.
+mountpoint to be expired in the automounting procedure outlined separately.
 
 To do expiration, you need to follow these steps:
 
- (3) Create at least one list off which the vfsmounts to be expired can be
-     hung. Access to this list will be governed by the vfsmount_lock.
+ (1) Create at least one list off which the vfsmounts to be expired can be
+     hung.
 
- (4) In step (2c) above, the call to do_add_mount() should be provided with a
-     pointer to this list. It will hang the vfsmount off of it if it succeeds.
+ (2) When a new mountpoint is created in the ->d_automount method, add
+     the mnt to the list using mnt_set_expiry()
+             mnt_set_expiry(newmnt, &afs_vfsmounts);
 
- (5) When you want mountpoints to be expired, call mark_mounts_for_expiry()
+ (3) When you want mountpoints to be expired, call mark_mounts_for_expiry()
      with a pointer to this list. This will process the list, marking every
      vfsmount thereon for potential expiry on the next call.
 
Signature · 1 line
2.1.4
author maintainer
#229 lore raw

PATCH 6/21 namei: be careful with mountpoint crossings in follow_dotdot_rcu()

In reply to Al Viro

From: Al Viro <[email protected]>

Otherwise we are risking a hard error where nonlazy restart would be the right thing to do; it's a very narrow race with mount --move and most of the time it ends up being completely harmless, but it's possible to construct a case when we'll get a bogus hard error instead of falling back to non-lazy walk...

For one thing, when crossing _into_ overmount of parent we need to check for mount_lock bumps when we get NULL from __lookup_mnt() as well.

For another, and less exotically, we need to make sure that the data fetched in follow_up_rcu() had been consistent. ->mnt_mountpoint is pinned for as long as it is a mountpoint, but we need to check mount_lock after fetching to verify that.

---
 fs/namei.c | 51 +++++++++++++++++++++------------------------------
 1 file changed, 21 insertions(+), 30 deletions(-)
fs/namei.c +21 -30
index e736722..e907e4b 100644
@@ -1017,21 +1017,6 @@ const char *get_link(struct nameidata *nd)
 	return res;
 }
 
-static int follow_up_rcu(struct path *path)
-{
-	struct mount *mnt = real_mount(path->mnt);
-	struct mount *parent;
-	struct dentry *mountpoint;
-
-	parent = mnt->mnt_parent;
-	if (&parent->mnt == path->mnt)
-		return 0;
-	mountpoint = mnt->mnt_mountpoint;
-	path->dentry = mountpoint;
-	path->mnt = &parent->mnt;
-	return 1;
-}
-
 /*
  * follow_up - Find the mountpoint of path's vfsmount
  *
@@ -1288,10 +1273,8 @@ static int follow_dotdot_rcu(struct nameidata *nd)
 		set_root_rcu(nd);
 
 	while (1) {
-		if (nd->path.dentry == nd->root.dentry &&
-		    nd->path.mnt == nd->root.mnt) {
+		if (path_equal(&nd->path, &nd->root))
 			break;
-		}
 		if (nd->path.dentry != nd->path.mnt->mnt_root) {
 			struct dentry *old = nd->path.dentry;
 			struct dentry *parent = old->d_parent;
@@ -1299,34 +1282,42 @@ static int follow_dotdot_rcu(struct nameidata *nd)
 
 			inode = parent->d_inode;
 			seq = read_seqcount_begin(&parent->d_seq);
-			if (read_seqcount_retry(&old->d_seq, nd->seq))
-				goto failed;
+			if (unlikely(read_seqcount_retry(&old->d_seq, nd->seq)))
+				return -ECHILD;
 			nd->path.dentry = parent;
 			nd->seq = seq;
 			break;
+		} else {
+			struct mount *mnt = real_mount(nd->path.mnt);
+			struct mount *mparent = mnt->mnt_parent;
+			struct dentry *mountpoint = mnt->mnt_mountpoint;
+			struct inode *inode2 = mountpoint->d_inode;
+			unsigned seq = read_seqcount_begin(&mountpoint->d_seq);
+			if (unlikely(read_seqretry(&mount_lock, nd->m_seq)))
+				return -ECHILD;
+			if (&mparent->mnt == nd->path.mnt)
+				break;
+			/* we know that mountpoint was pinned */
+			nd->path.dentry = mountpoint;
+			nd->path.mnt = &mparent->mnt;
+			inode = inode2;
+			nd->seq = seq;
 		}
-		if (!follow_up_rcu(&nd->path))
-			break;
-		inode = nd->path.dentry->d_inode;
-		nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
 	}
-	while (d_mountpoint(nd->path.dentry)) {
+	while (unlikely(d_mountpoint(nd->path.dentry))) {
 		struct mount *mounted;
 		mounted = __lookup_mnt(nd->path.mnt, nd->path.dentry);
+		if (unlikely(read_seqretry(&mount_lock, nd->m_seq)))
+			return -ECHILD;
 		if (!mounted)
 			break;
 		nd->path.mnt = &mounted->mnt;
 		nd->path.dentry = mounted->mnt.mnt_root;
 		inode = nd->path.dentry->d_inode;
 		nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
-		if (read_seqretry(&mount_lock, nd->m_seq))
-			goto failed;
 	}
 	nd->inode = inode;
 	return 0;
-
-failed:
-	return -ECHILD;
 }
 
 /*
Signature · 1 line
2.1.4
author maintainer
#230 lore raw

PATCH 7/21 namei: uninline set_root{,_rcu}()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
fs/namei.c +2 -2
index e907e4b..ffa2e13 100644
@@ -776,12 +776,12 @@ static int complete_walk(struct nameidata *nd)
 	return status;
 }
 
-static __always_inline void set_root(struct nameidata *nd)
+static void set_root(struct nameidata *nd)
 {
 	get_fs_root(current->fs, &nd->root);
 }
 
-static __always_inline unsigned set_root_rcu(struct nameidata *nd)
+static unsigned set_root_rcu(struct nameidata *nd)
 {
 	struct fs_struct *fs = current->fs;
 	unsigned seq;
Signature · 1 line
2.1.4
author maintainer
#231 lore raw

PATCH 8/21 namei: pass the struct path to store the result down into path_lookupat()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 72 +++++++++++++++++++++++++++++---------------------------------
 1 file changed, 34 insertions(+), 38 deletions(-)
fs/namei.c +34 -38
index ffa2e13..937ec33 100644
@@ -2084,8 +2084,8 @@ static inline int lookup_last(struct nameidata *nd)
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-static int path_lookupat(int dfd, const struct filename *name,
-				unsigned int flags, struct nameidata *nd)
+static int path_lookupat(int dfd, const struct filename *name, unsigned flags,
+			 struct nameidata *nd, struct path *path)
 {
 	const char *s = path_init(dfd, name, flags, nd);
 	int err;
@@ -2106,27 +2106,31 @@ static int path_lookupat(int dfd, const struct filename *name,
 	if (!err && nd->flags & LOOKUP_DIRECTORY)
 		if (!d_can_lookup(nd->path.dentry))
 			err = -ENOTDIR;
-	if (err)
-		terminate_walk(nd);
-
+	if (!err) {
+		*path = nd->path;
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+	}
+	terminate_walk(nd);
 	path_cleanup(nd);
 	return err;
 }
 
-static int filename_lookup(int dfd, struct filename *name,
-				unsigned int flags, struct nameidata *nd)
+static int filename_lookup(int dfd, struct filename *name, unsigned flags,
+			   struct nameidata *nd, struct path *path)
 {
 	int retval;
 	struct nameidata *saved_nd = set_nameidata(nd);
 
-	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd);
+	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd, path);
 	if (unlikely(retval == -ECHILD))
-		retval = path_lookupat(dfd, name, flags, nd);
+		retval = path_lookupat(dfd, name, flags, nd, path);
 	if (unlikely(retval == -ESTALE))
-		retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL, nd);
+		retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL,
+				       nd, path); 
 
 	if (likely(!retval))
-		audit_inode(name, nd->path.dentry, flags & LOOKUP_PARENT);
+		audit_inode(name, path->dentry, flags & LOOKUP_PARENT);
 	restore_nameidata(saved_nd);
 	return retval;
 }
@@ -2207,10 +2211,8 @@ int kern_path(const char *name, unsigned int flags, struct path *path)
 	int res = PTR_ERR(filename);
 
 	if (!IS_ERR(filename)) {
-		res = filename_lookup(AT_FDCWD, filename, flags, &nd);
+		res = filename_lookup(AT_FDCWD, filename, flags, &nd, path);
 		putname(filename);
-		if (!res)
-			*path = nd.path;
 	}
 	return res;
 }
@@ -2239,9 +2241,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 		nd.root.dentry = dentry;
 		nd.root.mnt = mnt;
 		err = filename_lookup(AT_FDCWD, filename,
-				      flags | LOOKUP_ROOT, &nd);
-		if (!err)
-			*path = nd.path;
+				      flags | LOOKUP_ROOT, &nd, path);
 		putname(filename);
 	}
 	return err;
@@ -2309,10 +2309,8 @@ int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 
 		BUG_ON(flags & LOOKUP_PARENT);
 
-		err = filename_lookup(dfd, tmp, flags, &nd);
+		err = filename_lookup(dfd, tmp, flags, &nd, path);
 		putname(tmp);
-		if (!err)
-			*path = nd.path;
 	}
 	return err;
 }
@@ -3259,44 +3257,42 @@ static int do_tmpfile(int dfd, struct filename *pathname,
 		struct file *file, int *opened)
 {
 	static const struct qstr name = QSTR_INIT("/", 1);
-	struct dentry *dentry, *child;
+	struct dentry *child;
 	struct inode *dir;
+	struct path path;
 	int error = path_lookupat(dfd, pathname,
-				  flags | LOOKUP_DIRECTORY, nd);
+				  flags | LOOKUP_DIRECTORY, nd, &path);
 	if (unlikely(error))
 		return error;
-	error = mnt_want_write(nd->path.mnt);
+	error = mnt_want_write(path.mnt);
 	if (unlikely(error))
 		goto out;
+	dir = path.dentry->d_inode;
 	/* we want directory to be writable */
-	error = inode_permission(nd->inode, MAY_WRITE | MAY_EXEC);
+	error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
 	if (error)
 		goto out2;
-	dentry = nd->path.dentry;
-	dir = dentry->d_inode;
 	if (!dir->i_op->tmpfile) {
 		error = -EOPNOTSUPP;
 		goto out2;
 	}
-	child = d_alloc(dentry, &name);
+	child = d_alloc(path.dentry, &name);
 	if (unlikely(!child)) {
 		error = -ENOMEM;
 		goto out2;
 	}
-	nd->flags &= ~LOOKUP_DIRECTORY;
-	nd->flags |= op->intent;
-	dput(nd->path.dentry);
-	nd->path.dentry = child;
-	error = dir->i_op->tmpfile(dir, nd->path.dentry, op->mode);
+	dput(path.dentry);
+	path.dentry = child;
+	error = dir->i_op->tmpfile(dir, child, op->mode);
 	if (error)
 		goto out2;
-	audit_inode(pathname, nd->path.dentry, 0);
+	audit_inode(pathname, child, 0);
 	/* Don't check for other permissions, the inode was just created */
-	error = may_open(&nd->path, MAY_OPEN, op->open_flag);
+	error = may_open(&path, MAY_OPEN, op->open_flag);
 	if (error)
 		goto out2;
-	file->f_path.mnt = nd->path.mnt;
-	error = finish_open(file, nd->path.dentry, NULL, opened);
+	file->f_path.mnt = path.mnt;
+	error = finish_open(file, child, NULL, opened);
 	if (error)
 		goto out2;
 	error = open_check_o_direct(file);
@@ -3309,9 +3305,9 @@ static int do_tmpfile(int dfd, struct filename *pathname,
 		spin_unlock(&inode->i_lock);
 	}
 out2:
-	mnt_drop_write(nd->path.mnt);
+	mnt_drop_write(path.mnt);
 out:
-	path_put(&nd->path);
+	path_put(&path);
 	return error;
 }
 
Signature · 1 line
2.1.4
author maintainer
#232 lore raw

PATCH 9/21 namei: move putname() call into filename_lookup()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 38 +++++++++++++++-----------------------
 1 file changed, 15 insertions(+), 23 deletions(-)
fs/namei.c +15 -23
index 937ec33..17350c0 100644
@@ -2132,6 +2132,7 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 	if (likely(!retval))
 		audit_inode(name, path->dentry, flags & LOOKUP_PARENT);
 	restore_nameidata(saved_nd);
+	putname(name);
 	return retval;
 }
 
@@ -2208,13 +2209,9 @@ int kern_path(const char *name, unsigned int flags, struct path *path)
 {
 	struct nameidata nd;
 	struct filename *filename = getname_kernel(name);
-	int res = PTR_ERR(filename);
-
-	if (!IS_ERR(filename)) {
-		res = filename_lookup(AT_FDCWD, filename, flags, &nd, path);
-		putname(filename);
-	}
-	return res;
+	if (IS_ERR(filename))
+		return PTR_ERR(filename);
+	return filename_lookup(AT_FDCWD, filename, flags, &nd, path);
 }
 EXPORT_SYMBOL(kern_path);
 
@@ -2230,21 +2227,19 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 		    const char *name, unsigned int flags,
 		    struct path *path)
 {
+	struct nameidata nd;
 	struct filename *filename = getname_kernel(name);
-	int err = PTR_ERR(filename);
 
 	BUG_ON(flags & LOOKUP_PARENT);
 
+	if (IS_ERR(filename))
+		return PTR_ERR(filename);
+
+	nd.root.dentry = dentry;
+	nd.root.mnt = mnt;
 	/* the first argument of filename_lookup() is ignored with LOOKUP_ROOT */
-	if (!IS_ERR(filename)) {
-		struct nameidata nd;
-		nd.root.dentry = dentry;
-		nd.root.mnt = mnt;
-		err = filename_lookup(AT_FDCWD, filename,
+	return filename_lookup(AT_FDCWD, filename,
 				      flags | LOOKUP_ROOT, &nd, path);
-		putname(filename);
-	}
-	return err;
 }
 EXPORT_SYMBOL(vfs_path_lookup);
 
@@ -2304,15 +2299,12 @@ int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 {
 	struct nameidata nd;
 	struct filename *tmp = getname_flags(name, flags, empty);
-	int err = PTR_ERR(tmp);
-	if (!IS_ERR(tmp)) {
+	if (IS_ERR(tmp))
+		return PTR_ERR(tmp);
 
-		BUG_ON(flags & LOOKUP_PARENT);
+	BUG_ON(flags & LOOKUP_PARENT);
 
-		err = filename_lookup(dfd, tmp, flags, &nd, path);
-		putname(tmp);
-	}
-	return err;
+	return filename_lookup(dfd, tmp, flags, &nd, path);
 }
 
 int user_path_at(int dfd, const char __user *name, unsigned flags,
Signature · 1 line
2.1.4
author maintainer
#233 lore raw

PATCH 10/21 namei: shift nameidata inside filename_lookup()

In reply to Al Viro

From: Al Viro <[email protected]>

pass root instead; non-NULL => copy to nd.root and set LOOKUP_ROOT in flags

---
 fs/namei.c | 30 ++++++++++++++----------------
 1 file changed, 14 insertions(+), 16 deletions(-)
fs/namei.c +14 -16
index 17350c0..abf65b0 100644
@@ -2117,17 +2117,20 @@ static int path_lookupat(int dfd, const struct filename *name, unsigned flags,
 }
 
 static int filename_lookup(int dfd, struct filename *name, unsigned flags,
-			   struct nameidata *nd, struct path *path)
+			   struct path *path, struct path *root)
 {
 	int retval;
-	struct nameidata *saved_nd = set_nameidata(nd);
-
-	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, nd, path);
+	struct nameidata nd, *saved_nd = set_nameidata(&nd);
+	if (unlikely(root)) {
+		nd.root = *root;
+		flags |= LOOKUP_ROOT;
+	}
+	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, &nd, path);
 	if (unlikely(retval == -ECHILD))
-		retval = path_lookupat(dfd, name, flags, nd, path);
+		retval = path_lookupat(dfd, name, flags, &nd, path);
 	if (unlikely(retval == -ESTALE))
 		retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL,
-				       nd, path); 
+				       &nd, path);
 
 	if (likely(!retval))
 		audit_inode(name, path->dentry, flags & LOOKUP_PARENT);
@@ -2207,11 +2210,10 @@ out:
 
 int kern_path(const char *name, unsigned int flags, struct path *path)
 {
-	struct nameidata nd;
 	struct filename *filename = getname_kernel(name);
 	if (IS_ERR(filename))
 		return PTR_ERR(filename);
-	return filename_lookup(AT_FDCWD, filename, flags, &nd, path);
+	return filename_lookup(AT_FDCWD, filename, flags, path, NULL);
 }
 EXPORT_SYMBOL(kern_path);
 
@@ -2227,7 +2229,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 		    const char *name, unsigned int flags,
 		    struct path *path)
 {
-	struct nameidata nd;
+	struct path root = {.mnt = mnt, .dentry = dentry};
 	struct filename *filename = getname_kernel(name);
 
 	BUG_ON(flags & LOOKUP_PARENT);
@@ -2235,11 +2237,8 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 	if (IS_ERR(filename))
 		return PTR_ERR(filename);
 
-	nd.root.dentry = dentry;
-	nd.root.mnt = mnt;
-	/* the first argument of filename_lookup() is ignored with LOOKUP_ROOT */
-	return filename_lookup(AT_FDCWD, filename,
-				      flags | LOOKUP_ROOT, &nd, path);
+	/* the first argument of filename_lookup() is ignored with root */
+	return filename_lookup(AT_FDCWD, filename, flags , path, &root);
 }
 EXPORT_SYMBOL(vfs_path_lookup);
 
@@ -2297,14 +2296,13 @@ EXPORT_SYMBOL(lookup_one_len);
 int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 		 struct path *path, int *empty)
 {
-	struct nameidata nd;
 	struct filename *tmp = getname_flags(name, flags, empty);
 	if (IS_ERR(tmp))
 		return PTR_ERR(tmp);
 
 	BUG_ON(flags & LOOKUP_PARENT);
 
-	return filename_lookup(dfd, tmp, flags, &nd, path);
+	return filename_lookup(dfd, tmp, flags, path, NULL);
 }
 
 int user_path_at(int dfd, const char __user *name, unsigned flags,
Signature · 1 line
2.1.4
author maintainer
#234 lore raw

PATCH 11/21 namei: make filename_lookup() reject ERR_PTR() passed as name

In reply to Al Viro

From: Al Viro <[email protected]>

makes for much easier life in callers

---
 fs/namei.c | 30 ++++++++++--------------------
 1 file changed, 10 insertions(+), 20 deletions(-)
fs/namei.c +10 -20
index abf65b0..6607a05 100644
@@ -2120,7 +2120,10 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 			   struct path *path, struct path *root)
 {
 	int retval;
-	struct nameidata nd, *saved_nd = set_nameidata(&nd);
+	struct nameidata nd, *saved_nd;
+	if (IS_ERR(name))
+		return PTR_ERR(name);
+	saved_nd = set_nameidata(&nd);
 	if (unlikely(root)) {
 		nd.root = *root;
 		flags |= LOOKUP_ROOT;
@@ -2210,10 +2213,8 @@ out:
 
 int kern_path(const char *name, unsigned int flags, struct path *path)
 {
-	struct filename *filename = getname_kernel(name);
-	if (IS_ERR(filename))
-		return PTR_ERR(filename);
-	return filename_lookup(AT_FDCWD, filename, flags, path, NULL);
+	return filename_lookup(AT_FDCWD, getname_kernel(name),
+			       flags, path, NULL);
 }
 EXPORT_SYMBOL(kern_path);
 
@@ -2230,15 +2231,9 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
 		    struct path *path)
 {
 	struct path root = {.mnt = mnt, .dentry = dentry};
-	struct filename *filename = getname_kernel(name);
-
-	BUG_ON(flags & LOOKUP_PARENT);
-
-	if (IS_ERR(filename))
-		return PTR_ERR(filename);
-
 	/* the first argument of filename_lookup() is ignored with root */
-	return filename_lookup(AT_FDCWD, filename, flags , path, &root);
+	return filename_lookup(AT_FDCWD, getname_kernel(name),
+			       flags , path, &root);
 }
 EXPORT_SYMBOL(vfs_path_lookup);
 
@@ -2296,13 +2291,8 @@ EXPORT_SYMBOL(lookup_one_len);
 int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 		 struct path *path, int *empty)
 {
-	struct filename *tmp = getname_flags(name, flags, empty);
-	if (IS_ERR(tmp))
-		return PTR_ERR(tmp);
-
-	BUG_ON(flags & LOOKUP_PARENT);
-
-	return filename_lookup(dfd, tmp, flags, path, NULL);
+	return filename_lookup(dfd, getname_flags(name, flags, empty),
+			       flags, path, NULL);
 }
 
 int user_path_at(int dfd, const char __user *name, unsigned flags,
Signature · 1 line
2.1.4
author maintainer
#235 lore raw

PATCH 12/21 namei: shift nameidata down into filename_parentat()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 84 ++++++++++++++++++++++++++++++++------------------------------
 1 file changed, 43 insertions(+), 41 deletions(-)
fs/namei.c +43 -41
index 6607a05..dbd1a34 100644
@@ -2144,7 +2144,8 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
 static int path_parentat(int dfd, const struct filename *name,
-				unsigned int flags, struct nameidata *nd)
+				unsigned int flags, struct nameidata *nd,
+				struct path *parent)
 {
 	const char *s = path_init(dfd, name, flags, nd);
 	int err;
@@ -2153,26 +2154,34 @@ static int path_parentat(int dfd, const struct filename *name,
 	err = link_path_walk(s, nd);
 	if (!err)
 		err = complete_walk(nd);
-	if (err)
-		terminate_walk(nd);
+	if (!err) {
+		*parent = nd->path;
+		nd->path.mnt = NULL;
+		nd->path.dentry = NULL;
+	}
+	terminate_walk(nd);
 	path_cleanup(nd);
 	return err;
 }
 
 static int filename_parentat(int dfd, struct filename *name,
-				unsigned int flags, struct nameidata *nd)
+				unsigned int flags, struct path *parent,
+				struct qstr *last, int *type)
 {
 	int retval;
-	struct nameidata *saved_nd = set_nameidata(nd);
+	struct nameidata nd, *saved_nd = set_nameidata(&nd);
 
-	retval = path_parentat(dfd, name, flags | LOOKUP_RCU, nd);
+	retval = path_parentat(dfd, name, flags | LOOKUP_RCU, &nd, parent);
 	if (unlikely(retval == -ECHILD))
-		retval = path_parentat(dfd, name, flags, nd);
+		retval = path_parentat(dfd, name, flags, &nd, parent);
 	if (unlikely(retval == -ESTALE))
-		retval = path_parentat(dfd, name, flags | LOOKUP_REVAL, nd);
-
-	if (likely(!retval))
-		audit_inode(name, nd->path.dentry, LOOKUP_PARENT);
+		retval = path_parentat(dfd, name, flags | LOOKUP_REVAL,
+					&nd, parent);
+	if (likely(!retval)) {
+		*last = nd.last;
+		*type = nd.last_type;
+		audit_inode(name, parent->dentry, LOOKUP_PARENT);
+	}
 	restore_nameidata(saved_nd);
 	return retval;
 }
@@ -2181,31 +2190,30 @@ static int filename_parentat(int dfd, struct filename *name,
 struct dentry *kern_path_locked(const char *name, struct path *path)
 {
 	struct filename *filename = getname_kernel(name);
-	struct nameidata nd;
+	struct qstr last;
+	int type;
 	struct dentry *d;
 	int err;
 
 	if (IS_ERR(filename))
 		return ERR_CAST(filename);
 
-	err = filename_parentat(AT_FDCWD, filename, 0, &nd);
+	err = filename_parentat(AT_FDCWD, filename, 0, path, &last, &type);
 	if (err) {
 		d = ERR_PTR(err);
 		goto out;
 	}
-	if (nd.last_type != LAST_NORM) {
-		path_put(&nd.path);
+	if (type != LAST_NORM) {
+		path_put(path);
 		d = ERR_PTR(-EINVAL);
 		goto out;
 	}
-	mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
-	d = __lookup_hash(&nd.last, nd.path.dentry, 0);
+	mutex_lock_nested(&path->dentry->d_inode->i_mutex, I_MUTEX_PARENT);
+	d = __lookup_hash(&last, path->dentry, 0);
 	if (IS_ERR(d)) {
-		mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
-		path_put(&nd.path);
-		goto out;
+		mutex_unlock(&path->dentry->d_inode->i_mutex);
+		path_put(path);
 	}
-	*path = nd.path;
 out:
 	putname(filename);
 	return d;
@@ -2315,7 +2323,6 @@ user_path_parent(int dfd, const char __user *path,
 		 int *type,
 		 unsigned int flags)
 {
-	struct nameidata nd;
 	struct filename *s = getname(path);
 	int error;
 
@@ -2325,15 +2332,11 @@ user_path_parent(int dfd, const char __user *path,
 	if (IS_ERR(s))
 		return s;
 
-	error = filename_parentat(dfd, s, flags, &nd);
+	error = filename_parentat(dfd, s, flags, parent, last, type);
 	if (error) {
 		putname(s);
-		return ERR_PTR(error);
+		s = ERR_PTR(error);
 	}
-	*parent = nd.path;
-	*last = nd.last;
-	*type = nd.last_type;
-
 	return s;
 }
 
@@ -3392,7 +3395,8 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 				struct path *path, unsigned int lookup_flags)
 {
 	struct dentry *dentry = ERR_PTR(-EEXIST);
-	struct nameidata nd;
+	struct qstr last;
+	int type;
 	int err2;
 	int error;
 	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
@@ -3403,7 +3407,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 */
 	lookup_flags &= LOOKUP_REVAL;
 
-	error = filename_parentat(dfd, name, lookup_flags, &nd);
+	error = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
 	if (error)
 		return ERR_PTR(error);
 
@@ -3411,18 +3415,17 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 * Yucky last component or no last component at all?
 	 * (foo/., foo/.., /////)
 	 */
-	if (nd.last_type != LAST_NORM)
+	if (type != LAST_NORM)
 		goto out;
-	nd.flags &= ~LOOKUP_PARENT;
-	nd.flags |= LOOKUP_CREATE | LOOKUP_EXCL;
 
 	/* don't fail immediately if it's r/o, at least try to report other errors */
-	err2 = mnt_want_write(nd.path.mnt);
+	err2 = mnt_want_write(path->mnt);
 	/*
 	 * Do the final lookup.
 	 */
-	mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
-	dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags);
+	lookup_flags |= LOOKUP_CREATE | LOOKUP_EXCL;
+	mutex_lock_nested(&path->dentry->d_inode->i_mutex, I_MUTEX_PARENT);
+	dentry = __lookup_hash(&last, path->dentry, lookup_flags);
 	if (IS_ERR(dentry))
 		goto unlock;
 
@@ -3436,7 +3439,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 * all is fine. Let's be bastards - you had / on the end, you've
 	 * been asking for (non-existent) directory. -ENOENT for you.
 	 */
-	if (unlikely(!is_dir && nd.last.name[nd.last.len])) {
+	if (unlikely(!is_dir && last.name[last.len])) {
 		error = -ENOENT;
 		goto fail;
 	}
@@ -3444,17 +3447,16 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 		error = err2;
 		goto fail;
 	}
-	*path = nd.path;
 	return dentry;
 fail:
 	dput(dentry);
 	dentry = ERR_PTR(error);
 unlock:
-	mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+	mutex_unlock(&path->dentry->d_inode->i_mutex);
 	if (!err2)
-		mnt_drop_write(nd.path.mnt);
+		mnt_drop_write(path->mnt);
 out:
-	path_put(&nd.path);
+	path_put(path);
 	return dentry;
 }
 
Signature · 1 line
2.1.4
author maintainer
#236 lore raw

PATCH 13/21 namei: saner calling conventions for filename_create()

In reply to Al Viro

From: Al Viro <[email protected]>

a) make it reject ERR_PTR() for name
b) make it putname(name) upon return in all other cases.

seriously simplifies the callers...

---
 fs/namei.c | 26 ++++++++++----------------
 1 file changed, 10 insertions(+), 16 deletions(-)
fs/namei.c +10 -16
index dbd1a34..4588e82 100644
@@ -3401,6 +3401,8 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	int error;
 	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
 
+	if (IS_ERR(name))
+		return ERR_CAST(name);
 	/*
 	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
 	 * other flags passed in are ignored!
@@ -3408,8 +3410,10 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	lookup_flags &= LOOKUP_REVAL;
 
 	error = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
-	if (error)
+	if (error) {
+		putname(name);
 		return ERR_PTR(error);
+	}
 
 	/*
 	 * Yucky last component or no last component at all?
@@ -3447,6 +3451,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 		error = err2;
 		goto fail;
 	}
+	putname(name);
 	return dentry;
 fail:
 	dput(dentry);
@@ -3457,20 +3462,15 @@ unlock:
 		mnt_drop_write(path->mnt);
 out:
 	path_put(path);
+	putname(name);
 	return dentry;
 }
 
 struct dentry *kern_path_create(int dfd, const char *pathname,
 				struct path *path, unsigned int lookup_flags)
 {
-	struct filename *filename = getname_kernel(pathname);
-	struct dentry *res;
-
-	if (IS_ERR(filename))
-		return ERR_CAST(filename);
-	res = filename_create(dfd, filename, path, lookup_flags);
-	putname(filename);
-	return res;
+	return filename_create(dfd, getname_kernel(pathname),
+				path, lookup_flags);
 }
 EXPORT_SYMBOL(kern_path_create);
 
@@ -3486,13 +3486,7 @@ EXPORT_SYMBOL(done_path_create);
 struct dentry *user_path_create(int dfd, const char __user *pathname,
 				struct path *path, unsigned int lookup_flags)
 {
-	struct filename *tmp = getname(pathname);
-	struct dentry *res;
-	if (IS_ERR(tmp))
-		return ERR_CAST(tmp);
-	res = filename_create(dfd, tmp, path, lookup_flags);
-	putname(tmp);
-	return res;
+	return filename_create(dfd, getname(pathname), path, lookup_flags);
 }
 EXPORT_SYMBOL(user_path_create);
 
Signature · 1 line
2.1.4
author maintainer
#237 lore raw

PATCH 14/21 namei: saner calling conventions for filename_parentat()

In reply to Al Viro

From: Al Viro <[email protected]>

a) make it reject ERR_PTR() for name
b) make it putname(name) on all other failure exits
c) make it return name on success

again, simplifies the callers

---
 fs/namei.c | 60 ++++++++++++++++++++++--------------------------------------
 1 file changed, 22 insertions(+), 38 deletions(-)
fs/namei.c +22 -38
index 4588e82..bd45300 100644
@@ -2164,13 +2164,16 @@ static int path_parentat(int dfd, const struct filename *name,
 	return err;
 }
 
-static int filename_parentat(int dfd, struct filename *name,
+static struct filename *filename_parentat(int dfd, struct filename *name,
 				unsigned int flags, struct path *parent,
 				struct qstr *last, int *type)
 {
 	int retval;
-	struct nameidata nd, *saved_nd = set_nameidata(&nd);
+	struct nameidata nd, *saved_nd;
 
+	if (IS_ERR(name))
+		return name;
+	saved_nd = set_nameidata(&nd);
 	retval = path_parentat(dfd, name, flags | LOOKUP_RCU, &nd, parent);
 	if (unlikely(retval == -ECHILD))
 		retval = path_parentat(dfd, name, flags, &nd, parent);
@@ -2181,32 +2184,30 @@ static int filename_parentat(int dfd, struct filename *name,
 		*last = nd.last;
 		*type = nd.last_type;
 		audit_inode(name, parent->dentry, LOOKUP_PARENT);
+	} else {
+		putname(name);
+		name = ERR_PTR(retval);
 	}
 	restore_nameidata(saved_nd);
-	return retval;
+	return name;
 }
 
 /* does lookup, returns the object with parent locked */
 struct dentry *kern_path_locked(const char *name, struct path *path)
 {
-	struct filename *filename = getname_kernel(name);
+	struct filename *filename;
+	struct dentry *d;
 	struct qstr last;
 	int type;
-	struct dentry *d;
-	int err;
 
+	filename = filename_parentat(AT_FDCWD, getname_kernel(name), 0, path,
+				    &last, &type);
 	if (IS_ERR(filename))
 		return ERR_CAST(filename);
-
-	err = filename_parentat(AT_FDCWD, filename, 0, path, &last, &type);
-	if (err) {
-		d = ERR_PTR(err);
-		goto out;
-	}
-	if (type != LAST_NORM) {
+	if (unlikely(type != LAST_NORM)) {
 		path_put(path);
-		d = ERR_PTR(-EINVAL);
-		goto out;
+		putname(filename);
+		return ERR_PTR(-EINVAL);
 	}
 	mutex_lock_nested(&path->dentry->d_inode->i_mutex, I_MUTEX_PARENT);
 	d = __lookup_hash(&last, path->dentry, 0);
@@ -2214,7 +2215,6 @@ struct dentry *kern_path_locked(const char *name, struct path *path)
 		mutex_unlock(&path->dentry->d_inode->i_mutex);
 		path_put(path);
 	}
-out:
 	putname(filename);
 	return d;
 }
@@ -2323,21 +2323,9 @@ user_path_parent(int dfd, const char __user *path,
 		 int *type,
 		 unsigned int flags)
 {
-	struct filename *s = getname(path);
-	int error;
-
 	/* only LOOKUP_REVAL is allowed in extra flags */
-	flags &= LOOKUP_REVAL;
-
-	if (IS_ERR(s))
-		return s;
-
-	error = filename_parentat(dfd, s, flags, parent, last, type);
-	if (error) {
-		putname(s);
-		s = ERR_PTR(error);
-	}
-	return s;
+	return filename_parentat(dfd, getname(path), flags & LOOKUP_REVAL,
+				 parent, last, type);
 }
 
 /**
@@ -3401,25 +3389,21 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	int error;
 	bool is_dir = (lookup_flags & LOOKUP_DIRECTORY);
 
-	if (IS_ERR(name))
-		return ERR_CAST(name);
 	/*
 	 * Note that only LOOKUP_REVAL and LOOKUP_DIRECTORY matter here. Any
 	 * other flags passed in are ignored!
 	 */
 	lookup_flags &= LOOKUP_REVAL;
 
-	error = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
-	if (error) {
-		putname(name);
-		return ERR_PTR(error);
-	}
+	name = filename_parentat(dfd, name, lookup_flags, path, &last, &type);
+	if (IS_ERR(name))
+		return ERR_CAST(name);
 
 	/*
 	 * Yucky last component or no last component at all?
 	 * (foo/., foo/.., /////)
 	 */
-	if (type != LAST_NORM)
+	if (unlikely(type != LAST_NORM))
 		goto out;
 
 	/* don't fail immediately if it's r/o, at least try to report other errors */
Signature · 1 line
2.1.4
author maintainer
#238 lore raw

PATCH 15/21 namei: fold path_cleanup() into terminate_walk()

In reply to Al Viro

From: Al Viro <[email protected]>

they are always called next to each other; moreover, terminate_walk() is more symmetrical that way.

---
 fs/namei.c | 16 ++++------------
 1 file changed, 4 insertions(+), 12 deletions(-)
fs/namei.c +4 -12
index bd45300..59a4662 100644
@@ -584,6 +584,10 @@ static void terminate_walk(struct nameidata *nd)
 		path_put(&nd->path);
 		for (i = 0; i < nd->depth; i++)
 			path_put(&nd->stack[i].link);
+		if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
+			path_put(&nd->root);
+			nd->root.mnt = NULL;
+		}
 	} else {
 		nd->flags &= ~LOOKUP_RCU;
 		if (!(nd->flags & LOOKUP_ROOT))
@@ -2049,14 +2053,6 @@ static const char *path_init(int dfd, const struct filename *name,
 	return ERR_PTR(-ECHILD);
 }
 
-static void path_cleanup(struct nameidata *nd)
-{
-	if (nd->root.mnt && !(nd->flags & LOOKUP_ROOT)) {
-		path_put(&nd->root);
-		nd->root.mnt = NULL;
-	}
-}
-
 static const char *trailing_symlink(struct nameidata *nd)
 {
 	const char *s;
@@ -2112,7 +2108,6 @@ static int path_lookupat(int dfd, const struct filename *name, unsigned flags,
 		nd->path.dentry = NULL;
 	}
 	terminate_walk(nd);
-	path_cleanup(nd);
 	return err;
 }
 
@@ -2160,7 +2155,6 @@ static int path_parentat(int dfd, const struct filename *name,
 		nd->path.dentry = NULL;
 	}
 	terminate_walk(nd);
-	path_cleanup(nd);
 	return err;
 }
 
@@ -2444,7 +2438,6 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path,
 		}
 	}
 	terminate_walk(nd);
-	path_cleanup(nd);
 	return err;
 }
 
@@ -3316,7 +3309,6 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 		}
 	}
 	terminate_walk(nd);
-	path_cleanup(nd);
 out2:
 	if (!(opened & FILE_OPENED)) {
 		BUG_ON(!error);
Signature · 1 line
2.1.4
author maintainer
#239 lore raw

PATCH 16/21 namei: stash dfd and name into nameidata

In reply to Al Viro

From: Al Viro <[email protected]>

fewer arguments to pass around...

---
 fs/namei.c | 96 ++++++++++++++++++++++++++++++--------------------------------
 1 file changed, 46 insertions(+), 50 deletions(-)
fs/namei.c +46 -50
index 59a4662..0c39113 100644
@@ -498,6 +498,8 @@ struct nameidata {
 	struct qstr	last;
 	struct path	root;
 	struct inode	*inode; /* path.dentry.d_inode */
+	struct filename	*name;
+	int		dfd;
 	unsigned int	flags;
 	unsigned	seq, m_seq, root_seq;
 	int		last_type;
@@ -512,10 +514,13 @@ struct nameidata {
 	} *stack, internal[EMBEDDED_LEVELS];
 };
 
-static struct nameidata *set_nameidata(struct nameidata *p)
+static struct nameidata *set_nameidata(struct nameidata *p, int dfd,
+					struct filename *name)
 {
 	struct nameidata *old = current->nameidata;
 	p->stack = p->internal;
+	p->dfd = dfd;
+	p->name = name;
 	p->total_link_count = old ? old->total_link_count : 0;
 	current->nameidata = p;
 	return old;
@@ -1953,11 +1958,10 @@ OK:
 	}
 }
 
-static const char *path_init(int dfd, const struct filename *name,
-			     unsigned int flags, struct nameidata *nd)
+static const char *path_init(struct nameidata *nd, unsigned flags)
 {
 	int retval = 0;
-	const char *s = name->name;
+	const char *s = nd->name->name;
 
 	nd->last_type = LAST_ROOT; /* if there are only slashes... */
 	nd->flags = flags | LOOKUP_JUMPED | LOOKUP_PARENT;
@@ -1997,7 +2001,7 @@ static const char *path_init(int dfd, const struct filename *name,
 			path_get(&nd->root);
 		}
 		nd->path = nd->root;
-	} else if (dfd == AT_FDCWD) {
+	} else if (nd->dfd == AT_FDCWD) {
 		if (flags & LOOKUP_RCU) {
 			struct fs_struct *fs = current->fs;
 			unsigned seq;
@@ -2014,7 +2018,7 @@ static const char *path_init(int dfd, const struct filename *name,
 		}
 	} else {
 		/* Caller must check execute permissions on the starting path component */
-		struct fd f = fdget_raw(dfd);
+		struct fd f = fdget_raw(nd->dfd);
 		struct dentry *dentry;
 
 		if (!f.file)
@@ -2080,10 +2084,9 @@ static inline int lookup_last(struct nameidata *nd)
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-static int path_lookupat(int dfd, const struct filename *name, unsigned flags,
-			 struct nameidata *nd, struct path *path)
+static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path)
 {
-	const char *s = path_init(dfd, name, flags, nd);
+	const char *s = path_init(nd, flags);
 	int err;
 
 	if (IS_ERR(s))
@@ -2118,17 +2121,16 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 	struct nameidata nd, *saved_nd;
 	if (IS_ERR(name))
 		return PTR_ERR(name);
-	saved_nd = set_nameidata(&nd);
+	saved_nd = set_nameidata(&nd, dfd, name);
 	if (unlikely(root)) {
 		nd.root = *root;
 		flags |= LOOKUP_ROOT;
 	}
-	retval = path_lookupat(dfd, name, flags | LOOKUP_RCU, &nd, path);
+	retval = path_lookupat(&nd, flags | LOOKUP_RCU, path);
 	if (unlikely(retval == -ECHILD))
-		retval = path_lookupat(dfd, name, flags, &nd, path);
+		retval = path_lookupat(&nd, flags, path);
 	if (unlikely(retval == -ESTALE))
-		retval = path_lookupat(dfd, name, flags | LOOKUP_REVAL,
-				       &nd, path);
+		retval = path_lookupat(&nd, flags | LOOKUP_REVAL, path);
 
 	if (likely(!retval))
 		audit_inode(name, path->dentry, flags & LOOKUP_PARENT);
@@ -2138,11 +2140,10 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 }
 
 /* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-static int path_parentat(int dfd, const struct filename *name,
-				unsigned int flags, struct nameidata *nd,
+static int path_parentat(struct nameidata *nd, unsigned flags,
 				struct path *parent)
 {
-	const char *s = path_init(dfd, name, flags, nd);
+	const char *s = path_init(nd, flags);
 	int err;
 	if (IS_ERR(s))
 		return PTR_ERR(s);
@@ -2167,13 +2168,12 @@ static struct filename *filename_parentat(int dfd, struct filename *name,
 
 	if (IS_ERR(name))
 		return name;
-	saved_nd = set_nameidata(&nd);
-	retval = path_parentat(dfd, name, flags | LOOKUP_RCU, &nd, parent);
+	saved_nd = set_nameidata(&nd, dfd, name);
+	retval = path_parentat(&nd, flags | LOOKUP_RCU, parent);
 	if (unlikely(retval == -ECHILD))
-		retval = path_parentat(dfd, name, flags, &nd, parent);
+		retval = path_parentat(&nd, flags, parent);
 	if (unlikely(retval == -ESTALE))
-		retval = path_parentat(dfd, name, flags | LOOKUP_REVAL,
-					&nd, parent);
+		retval = path_parentat(&nd, flags | LOOKUP_REVAL, parent);
 	if (likely(!retval)) {
 		*last = nd.last;
 		*type = nd.last_type;
@@ -2413,19 +2413,17 @@ done:
 
 /**
  * path_mountpoint - look up a path to be umounted
- * @dfd:	directory file descriptor to start walk from
- * @name:	full pathname to walk
- * @path:	pointer to container for result
+ * @nameidata:	lookup context
  * @flags:	lookup flags
+ * @path:	pointer to container for result
  *
  * Look up the given name, but don't attempt to revalidate the last component.
  * Returns 0 and "path" will be valid on success; Returns error otherwise.
  */
 static int
-path_mountpoint(int dfd, const struct filename *name, struct path *path,
-		struct nameidata *nd, unsigned int flags)
+path_mountpoint(struct nameidata *nd, unsigned flags, struct path *path)
 {
-	const char *s = path_init(dfd, name, flags, nd);
+	const char *s = path_init(nd, flags);
 	int err;
 	if (IS_ERR(s))
 		return PTR_ERR(s);
@@ -2449,12 +2447,12 @@ filename_mountpoint(int dfd, struct filename *name, struct path *path,
 	int error;
 	if (IS_ERR(name))
 		return PTR_ERR(name);
-	saved = set_nameidata(&nd);
-	error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_RCU);
+	saved = set_nameidata(&nd, dfd, name);
+	error = path_mountpoint(&nd, flags | LOOKUP_RCU, path);
 	if (unlikely(error == -ECHILD))
-		error = path_mountpoint(dfd, name, path, &nd, flags);
+		error = path_mountpoint(&nd, flags, path);
 	if (unlikely(error == -ESTALE))
-		error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_REVAL);
+		error = path_mountpoint(&nd, flags | LOOKUP_REVAL, path);
 	if (likely(!error))
 		audit_inode(name, path->dentry, 0);
 	restore_nameidata(saved);
@@ -3215,8 +3213,7 @@ stale_open:
 	goto retry_lookup;
 }
 
-static int do_tmpfile(int dfd, struct filename *pathname,
-		struct nameidata *nd, int flags,
+static int do_tmpfile(struct nameidata *nd, unsigned flags,
 		const struct open_flags *op,
 		struct file *file, int *opened)
 {
@@ -3224,8 +3221,7 @@ static int do_tmpfile(int dfd, struct filename *pathname,
 	struct dentry *child;
 	struct inode *dir;
 	struct path path;
-	int error = path_lookupat(dfd, pathname,
-				  flags | LOOKUP_DIRECTORY, nd, &path);
+	int error = path_lookupat(nd, flags | LOOKUP_DIRECTORY, &path);
 	if (unlikely(error))
 		return error;
 	error = mnt_want_write(path.mnt);
@@ -3250,7 +3246,7 @@ static int do_tmpfile(int dfd, struct filename *pathname,
 	error = dir->i_op->tmpfile(dir, child, op->mode);
 	if (error)
 		goto out2;
-	audit_inode(pathname, child, 0);
+	audit_inode(nd->name, child, 0);
 	/* Don't check for other permissions, the inode was just created */
 	error = may_open(&path, MAY_OPEN, op->open_flag);
 	if (error)
@@ -3275,8 +3271,8 @@ out:
 	return error;
 }
 
-static struct file *path_openat(int dfd, struct filename *pathname,
-		struct nameidata *nd, const struct open_flags *op, int flags)
+static struct file *path_openat(struct nameidata *nd,
+			const struct open_flags *op, unsigned flags)
 {
 	const char *s;
 	struct file *file;
@@ -3290,17 +3286,17 @@ static struct file *path_openat(int dfd, struct filename *pathname,
 	file->f_flags = op->open_flag;
 
 	if (unlikely(file->f_flags & __O_TMPFILE)) {
-		error = do_tmpfile(dfd, pathname, nd, flags, op, file, &opened);
+		error = do_tmpfile(nd, flags, op, file, &opened);
 		goto out2;
 	}
 
-	s = path_init(dfd, pathname, flags, nd);
+	s = path_init(nd, flags);
 	if (IS_ERR(s)) {
 		put_filp(file);
 		return ERR_CAST(s);
 	}
 	while (!(error = link_path_walk(s, nd)) &&
-		(error = do_last(nd, file, op, &opened, pathname)) > 0) {
+		(error = do_last(nd, file, op, &opened, nd->name)) > 0) {
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
 		s = trailing_symlink(nd);
 		if (IS_ERR(s)) {
@@ -3329,15 +3325,15 @@ out2:
 struct file *do_filp_open(int dfd, struct filename *pathname,
 		const struct open_flags *op)
 {
-	struct nameidata nd, *saved_nd = set_nameidata(&nd);
+	struct nameidata nd, *saved_nd = set_nameidata(&nd, dfd, pathname);
 	int flags = op->lookup_flags;
 	struct file *filp;
 
-	filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
+	filp = path_openat(&nd, op, flags | LOOKUP_RCU);
 	if (unlikely(filp == ERR_PTR(-ECHILD)))
-		filp = path_openat(dfd, pathname, &nd, op, flags);
+		filp = path_openat(&nd, op, flags);
 	if (unlikely(filp == ERR_PTR(-ESTALE)))
-		filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_REVAL);
+		filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
 	restore_nameidata(saved_nd);
 	return filp;
 }
@@ -3360,12 +3356,12 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 	if (unlikely(IS_ERR(filename)))
 		return ERR_CAST(filename);
 
-	saved_nd = set_nameidata(&nd);
-	file = path_openat(-1, filename, &nd, op, flags | LOOKUP_RCU);
+	saved_nd = set_nameidata(&nd, -1, filename);
+	file = path_openat(&nd, op, flags | LOOKUP_RCU);
 	if (unlikely(file == ERR_PTR(-ECHILD)))
-		file = path_openat(-1, filename, &nd, op, flags);
+		file = path_openat(&nd, op, flags);
 	if (unlikely(file == ERR_PTR(-ESTALE)))
-		file = path_openat(-1, filename, &nd, op, flags | LOOKUP_REVAL);
+		file = path_openat(&nd, op, flags | LOOKUP_REVAL);
 	restore_nameidata(saved_nd);
 	putname(filename);
 	return file;
Signature · 1 line
2.1.4
author maintainer
#240 lore raw

PATCH 17/21 namei: trim do_last() arguments

In reply to Al Viro

From: Al Viro <[email protected]>

now that struct filename is stashed in nameidata we have no need to pass it in

---
 fs/namei.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
fs/namei.c +6 -6
index 0c39113..9259fa9 100644
@@ -2990,7 +2990,7 @@ out_dput:
  */
 static int do_last(struct nameidata *nd,
 		   struct file *file, const struct open_flags *op,
-		   int *opened, struct filename *name)
+		   int *opened)
 {
 	struct dentry *dir = nd->path.dentry;
 	int open_flag = op->open_flag;
@@ -3037,7 +3037,7 @@ static int do_last(struct nameidata *nd,
 		if (error)
 			return error;
 
-		audit_inode(name, dir, LOOKUP_PARENT);
+		audit_inode(nd->name, dir, LOOKUP_PARENT);
 		/* trailing slashes? */
 		if (unlikely(nd->last.name[nd->last.len]))
 			return -EISDIR;
@@ -3066,7 +3066,7 @@ retry_lookup:
 		    !S_ISREG(file_inode(file)->i_mode))
 			will_truncate = false;
 
-		audit_inode(name, file->f_path.dentry, 0);
+		audit_inode(nd->name, file->f_path.dentry, 0);
 		goto opened;
 	}
 
@@ -3083,7 +3083,7 @@ retry_lookup:
 	 * create/update audit record if it already exists.
 	 */
 	if (d_is_positive(path.dentry))
-		audit_inode(name, path.dentry, 0);
+		audit_inode(nd->name, path.dentry, 0);
 
 	/*
 	 * If atomic_open() acquired write access it is dropped now due to
@@ -3141,7 +3141,7 @@ finish_open:
 		path_put(&save_parent);
 		return error;
 	}
-	audit_inode(name, nd->path.dentry, 0);
+	audit_inode(nd->name, nd->path.dentry, 0);
 	error = -EISDIR;
 	if ((open_flag & O_CREAT) && d_is_dir(nd->path.dentry))
 		goto out;
@@ -3296,7 +3296,7 @@ static struct file *path_openat(struct nameidata *nd,
 		return ERR_CAST(s);
 	}
 	while (!(error = link_path_walk(s, nd)) &&
-		(error = do_last(nd, file, op, &opened, nd->name)) > 0) {
+		(error = do_last(nd, file, op, &opened)) > 0) {
 		nd->flags &= ~(LOOKUP_OPEN|LOOKUP_CREATE|LOOKUP_EXCL);
 		s = trailing_symlink(nd);
 		if (IS_ERR(s)) {
Signature · 1 line
2.1.4
author maintainer
#241 lore raw

PATCH 18/21 inline user_path_parent()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namei.c +1 -1
index 9259fa9..995b56e 100644
@@ -2310,7 +2310,7 @@ EXPORT_SYMBOL(user_path_at);
  *     allocated by getname. So we must hold the reference to it until all
  *     path-walking is complete.
  */
-static struct filename *
+static inline struct filename *
 user_path_parent(int dfd, const char __user *path,
 		 struct path *parent,
 		 struct qstr *last,
Signature · 1 line
2.1.4
author maintainer
#242 lore raw

PATCH 19/21 inline user_path_create()

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
fs/namei.c +1 -1
index 995b56e..23dd9e8 100644
@@ -3455,7 +3455,7 @@ void done_path_create(struct path *path, struct dentry *dentry)
 }
 EXPORT_SYMBOL(done_path_create);
 
-struct dentry *user_path_create(int dfd, const char __user *pathname,
+inline struct dentry *user_path_create(int dfd, const char __user *pathname,
 				struct path *path, unsigned int lookup_flags)
 {
 	return filename_create(dfd, getname(pathname), path, lookup_flags);
Signature · 1 line
2.1.4
author maintainer
#243 lore raw

PATCH 20/21 namei: move saved_nd pointer into struct nameidata

In reply to Al Viro

From: Al Viro <[email protected]>

these guys are always declared next to each other; might as well put the former (pointer to previous instance) into the latter and simplify the calling conventions for {set,restore}_nameidata()

---
 fs/namei.c | 46 ++++++++++++++++++++++++----------------------
 1 file changed, 24 insertions(+), 22 deletions(-)
fs/namei.c +24 -22
index 23dd9e8..dead60d 100644
@@ -498,10 +498,8 @@ struct nameidata {
 	struct qstr	last;
 	struct path	root;
 	struct inode	*inode; /* path.dentry.d_inode */
-	struct filename	*name;
-	int		dfd;
 	unsigned int	flags;
-	unsigned	seq, m_seq, root_seq;
+	unsigned	seq, m_seq;
 	int		last_type;
 	unsigned	depth;
 	int		total_link_count;
@@ -512,23 +510,26 @@ struct nameidata {
 		struct inode *inode;
 		unsigned seq;
 	} *stack, internal[EMBEDDED_LEVELS];
+	struct filename	*name;
+	struct nameidata *saved;
+	unsigned	root_seq;
+	int		dfd;
 };
 
-static struct nameidata *set_nameidata(struct nameidata *p, int dfd,
-					struct filename *name)
+static void set_nameidata(struct nameidata *p, int dfd, struct filename *name)
 {
 	struct nameidata *old = current->nameidata;
 	p->stack = p->internal;
 	p->dfd = dfd;
 	p->name = name;
 	p->total_link_count = old ? old->total_link_count : 0;
+	p->saved = old;
 	current->nameidata = p;
-	return old;
 }
 
-static void restore_nameidata(struct nameidata *old)
+static void restore_nameidata(void)
 {
-	struct nameidata *now = current->nameidata;
+	struct nameidata *now = current->nameidata, *old = now->saved;
 
 	current->nameidata = old;
 	if (old)
@@ -2118,14 +2119,14 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 			   struct path *path, struct path *root)
 {
 	int retval;
-	struct nameidata nd, *saved_nd;
+	struct nameidata nd;
 	if (IS_ERR(name))
 		return PTR_ERR(name);
-	saved_nd = set_nameidata(&nd, dfd, name);
 	if (unlikely(root)) {
 		nd.root = *root;
 		flags |= LOOKUP_ROOT;
 	}
+	set_nameidata(&nd, dfd, name);
 	retval = path_lookupat(&nd, flags | LOOKUP_RCU, path);
 	if (unlikely(retval == -ECHILD))
 		retval = path_lookupat(&nd, flags, path);
@@ -2134,7 +2135,7 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 
 	if (likely(!retval))
 		audit_inode(name, path->dentry, flags & LOOKUP_PARENT);
-	restore_nameidata(saved_nd);
+	restore_nameidata();
 	putname(name);
 	return retval;
 }
@@ -2164,11 +2165,11 @@ static struct filename *filename_parentat(int dfd, struct filename *name,
 				struct qstr *last, int *type)
 {
 	int retval;
-	struct nameidata nd, *saved_nd;
+	struct nameidata nd;
 
 	if (IS_ERR(name))
 		return name;
-	saved_nd = set_nameidata(&nd, dfd, name);
+	set_nameidata(&nd, dfd, name);
 	retval = path_parentat(&nd, flags | LOOKUP_RCU, parent);
 	if (unlikely(retval == -ECHILD))
 		retval = path_parentat(&nd, flags, parent);
@@ -2182,7 +2183,7 @@ static struct filename *filename_parentat(int dfd, struct filename *name,
 		putname(name);
 		name = ERR_PTR(retval);
 	}
-	restore_nameidata(saved_nd);
+	restore_nameidata();
 	return name;
 }
 
@@ -2443,11 +2444,11 @@ static int
 filename_mountpoint(int dfd, struct filename *name, struct path *path,
 			unsigned int flags)
 {
-	struct nameidata nd, *saved;
+	struct nameidata nd;
 	int error;
 	if (IS_ERR(name))
 		return PTR_ERR(name);
-	saved = set_nameidata(&nd, dfd, name);
+	set_nameidata(&nd, dfd, name);
 	error = path_mountpoint(&nd, flags | LOOKUP_RCU, path);
 	if (unlikely(error == -ECHILD))
 		error = path_mountpoint(&nd, flags, path);
@@ -2455,7 +2456,7 @@ filename_mountpoint(int dfd, struct filename *name, struct path *path,
 		error = path_mountpoint(&nd, flags | LOOKUP_REVAL, path);
 	if (likely(!error))
 		audit_inode(name, path->dentry, 0);
-	restore_nameidata(saved);
+	restore_nameidata();
 	putname(name);
 	return error;
 }
@@ -3325,23 +3326,24 @@ out2:
 struct file *do_filp_open(int dfd, struct filename *pathname,
 		const struct open_flags *op)
 {
-	struct nameidata nd, *saved_nd = set_nameidata(&nd, dfd, pathname);
+	struct nameidata nd;
 	int flags = op->lookup_flags;
 	struct file *filp;
 
+	set_nameidata(&nd, dfd, pathname);
 	filp = path_openat(&nd, op, flags | LOOKUP_RCU);
 	if (unlikely(filp == ERR_PTR(-ECHILD)))
 		filp = path_openat(&nd, op, flags);
 	if (unlikely(filp == ERR_PTR(-ESTALE)))
 		filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
-	restore_nameidata(saved_nd);
+	restore_nameidata();
 	return filp;
 }
 
 struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 		const char *name, const struct open_flags *op)
 {
-	struct nameidata nd, *saved_nd;
+	struct nameidata nd;
 	struct file *file;
 	struct filename *filename;
 	int flags = op->lookup_flags | LOOKUP_ROOT;
@@ -3356,13 +3358,13 @@ struct file *do_file_open_root(struct dentry *dentry, struct vfsmount *mnt,
 	if (unlikely(IS_ERR(filename)))
 		return ERR_CAST(filename);
 
-	saved_nd = set_nameidata(&nd, -1, filename);
+	set_nameidata(&nd, -1, filename);
 	file = path_openat(&nd, op, flags | LOOKUP_RCU);
 	if (unlikely(file == ERR_PTR(-ECHILD)))
 		file = path_openat(&nd, op, flags);
 	if (unlikely(file == ERR_PTR(-ESTALE)))
 		file = path_openat(&nd, op, flags | LOOKUP_REVAL);
-	restore_nameidata(saved_nd);
+	restore_nameidata();
 	putname(filename);
 	return file;
 }
Signature · 1 line
2.1.4
author maintainer
#244 lore raw

PATCH 21/21 turn user_{path_at,path,lpath,path_dir}() into static inlines

In reply to Al Viro

From: Al Viro <[email protected]>

---
 fs/namei.c            |  8 +-------
 include/linux/namei.h | 34 ++++++++++++++++++++++++----------
 2 files changed, 25 insertions(+), 17 deletions(-)
fs/namei.c +1 -7
index dead60d..8ca1fbd 100644
@@ -2297,13 +2297,7 @@ int user_path_at_empty(int dfd, const char __user *name, unsigned flags,
 	return filename_lookup(dfd, getname_flags(name, flags, empty),
 			       flags, path, NULL);
 }
-
-int user_path_at(int dfd, const char __user *name, unsigned flags,
-		 struct path *path)
-{
-	return user_path_at_empty(dfd, name, flags, path, NULL);
-}
-EXPORT_SYMBOL(user_path_at);
+EXPORT_SYMBOL(user_path_at_empty);
 
 /*
  * NB: most callers don't do anything directly with the reference to the
include/linux/namei.h +24 -10
index 1208e48..d8c6334 100644
@@ -1,12 +1,10 @@
 #ifndef _LINUX_NAMEI_H
 #define _LINUX_NAMEI_H
 
-#include <linux/dcache.h>
-#include <linux/errno.h>
-#include <linux/linkage.h>
+#include <linux/kernel.h>
 #include <linux/path.h>
-
-struct vfsmount;
+#include <linux/fcntl.h>
+#include <linux/errno.h>
 
 enum { MAX_NESTED_LINKS = 8 };
 
@@ -46,13 +44,29 @@ enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, LAST_BIND};
 #define LOOKUP_ROOT		0x2000
 #define LOOKUP_EMPTY		0x4000
 
-extern int user_path_at(int, const char __user *, unsigned, struct path *);
 extern int user_path_at_empty(int, const char __user *, unsigned, struct path *, int *empty);
 
-#define user_path(name, path) user_path_at(AT_FDCWD, name, LOOKUP_FOLLOW, path)
-#define user_lpath(name, path) user_path_at(AT_FDCWD, name, 0, path)
-#define user_path_dir(name, path) \
-	user_path_at(AT_FDCWD, name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, path)
+static inline int user_path_at(int dfd, const char __user *name, unsigned flags,
+		 struct path *path)
+{
+	return user_path_at_empty(dfd, name, flags, path, NULL);
+}
+
+static inline int user_path(const char __user *name, struct path *path)
+{
+	return user_path_at_empty(AT_FDCWD, name, LOOKUP_FOLLOW, path, NULL);
+}
+
+static inline int user_lpath(const char __user *name, struct path *path)
+{
+	return user_path_at_empty(AT_FDCWD, name, 0, path, NULL);
+}
+
+static inline int user_path_dir(const char __user *name, struct path *path)
+{
+	return user_path_at_empty(AT_FDCWD, name,
+				  LOOKUP_FOLLOW | LOOKUP_DIRECTORY, path, NULL);
+}
 
 extern int kern_path(const char *, unsigned, struct path *);
 
Signature · 1 line
2.1.4
#245 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to Al Viro
Quoted 5 lines · Al Viro
        More on top of the current vfs.git#for-next (== the posted patchset

with a couple of fixes): more fs/namei.c reorganization and stack footprint reduction (below 1Kb now). One interesting piece of that is that we don't touch current->fs->lock anymore - unlazy_walk() used to, but now we can get rid of that.

Ok. I don't see anything wrong here, but I have to admit that I'm also at the point where I go "maybe this area should calm down a bit", and where I'd prefer to not see more long patch-series. Even if most of the patches seem to be fairly mechanical code movement and cleanup and preparation, mistakes happen, and I just get worried.

So I think the series is good, but in particular if you're planning on some more core changes (ie your "act on filename" callback thing), I would really prefer that we stop at this point for the 4.2 window, and make sure it's all stable.

And then your callback thing could be for 4.3.

That said, I'm not entirely convinced about a callback approach for stat() and friends. I suspect only stat() is really critical enough to warrant the whole "let's do it all in RCU mode", and if there's only one case, then there's no need for the (*act) indirection - might as well hardcode it.

But feel free to convince me. Again, I'd really prefer that to be after the current work has been in a stable release and a well tested base, though.

Linus

author maintainer
#246 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 13 lines · Linus Torvalds
Quoted 5 lines · Al Viro
        More on top of the current vfs.git#for-next (== the posted patchset

with a couple of fixes): more fs/namei.c reorganization and stack footprint reduction (below 1Kb now). One interesting piece of that is that we don't touch current->fs->lock anymore - unlazy_walk() used to, but now we can get rid of that.

Ok. I don't see anything wrong here, but I have to admit that I'm also at the point where I go "maybe this area should calm down a bit", and where I'd prefer to not see more long patch-series. Even if most of the patches seem to be fairly mechanical code movement and cleanup and preparation, mistakes happen, and I just get worried.

Quoted 4 lines

So I think the series is good, but in particular if you're planning on some more core changes (ie your "act on filename" callback thing), I would really prefer that we stop at this point for the 4.2 window, and make sure it's all stable.

*nod*

It had been a fun three weeks, but at that point all low-hanging fruits are gone. There is more stuff visible there (lazy stat(), offloading automounts via schedule_work(), perhaps RCU handling of non-fast symlinks), but that'll take a lot more serious plotting[1] before it gets to the stage when it can be implemented. In particular, automounts will require discussing what exactly in the process' state is used for those - both with autofs/NFS/AFS/CIFS folks and with Eric (what netns should be used when we are crossing an NFSv4 referral point? Should it come from the NFS mount we'd found the referral on, or from the process that has run across it? There'd been a series from Ian around the interplay of autofs with namespaces, and IIRC it stepped into similar-sounding areas; it'll need to be looked into, etc.)

I doubt that we'll get it sorted out before 4.2, and this kind of stuff *does* need exposure in -testing, as well as public review, so such continuations are clear 4.3 fodder. I hope to get it figured out by the time 4.3-rc1 comes, so that it could be dealt with early in the next cycle, but for now I'm planning to pull the current #link_path_walk into #for-next and let it soak there. Especially since there are other pending piles of joy elsewhere - handling of d_move()-messed vfsmounts, for starters ;-/

Quoted 7 lines

And then your callback thing could be for 4.3.

That said, I'm not entirely convinced about a callback approach for stat() and friends. I suspect only stat() is really critical enough to warrant the whole "let's do it all in RCU mode", and if there's only one case, then there's no need for the (*act) indirection - might as well hardcode it.

Maybe... I'd like to see the profiles, TBH - especially getxattr() and access() frequency on various loads. Sure, make(1) and cc(1) really care about stat() very much, but I wouldn't be surprised if something like httpd or samba would be hitting getxattr() a lot...

Quoted 3 lines

But feel free to convince me. Again, I'd really prefer that to be after the current work has been in a stable release and a well tested base, though.

No arguments here...

[1] as in "which shitpiles are we likely to step into on the way there and how far would detours take us"

#247 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 5 lines · Al Viro

Maybe... I'd like to see the profiles, TBH - especially getxattr() and access() frequency on various loads. Sure, make(1) and cc(1) really care about stat() very much, but I wouldn't be surprised if something like httpd or samba would be hitting getxattr() a lot...

So I haven't seen samba profiles in ages, but iirc we have more serious problems than trying to speed up basic filename lookup.

At least long long ago, inode semaphore contention was a big deal, largely due to readdir(). And readdir() itself, for that matter - we have no good vfs-level readdir caching, so it all ends up serialized on the inode semaphore, and it all goes all the way into the filesystem to get the readdir data. And at least for ext4, readdir() is slow anyway, because it doesn't use the page cache, it uses that good old buffer cache, because of how ext4 does metadata journaling etc.

Having readdir() caching at the VFS layer would likely be a really good thing, but it's hard. It *might* be worth looking at the nfs4 code to see if we could possibly move some of that code into the vfs layer, but the answer is likely "no", or at least "that's incredibly painful".

Also, I *think* samba ends up basically doing most of the pathname lookups from its own user-level cache, because of how it needs to avoid symlinks and do the whole crazy case insensitive pathname thing. I have this very dim memory that that's one reason samba ends up being so readdir-intensive. But I might be wrong, it's been many years since I talked to anybody about samba, and I don't run it myself.

But yes, that would likely be a very worthy profile target. I suspect we've beaten the "make does lots of stat() calls" horse pretty much to death.

Linus

#248 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 7 lines · Linus Torvalds
Quoted 5 lines · Al Viro

Maybe... I'd like to see the profiles, TBH - especially getxattr() and access() frequency on various loads. Sure, make(1) and cc(1) really care about stat() very much, but I wouldn't be surprised if something like httpd or samba would be hitting getxattr() a lot...

Quoted 5 lines

So I haven't seen samba profiles in ages, but iirc we have more serious problems than trying to speed up basic filename lookup.

At least long long ago, inode semaphore contention was a big deal, largely due to readdir().

It still is - it's the prime reason people still need to create hashed directory structures so that they can get concurrency in directory operations. IMO, concurrency in directory operations is a more important problem to solve than worrying about readdir speed; in large filesystems readdir and lookup are IO bound operations and so everything serialises on the IO as it's done with the i_mutex held....

Quoted 7 lines

And readdir() itself, for that matter - we have no good vfs-level readdir caching, so it all ends up serialized on the inode semaphore, and it all goes all the way into the filesystem to get the readdir data. And at least for ext4, readdir()
is slow anyway, because it doesn't use the page cache, it uses that good old buffer cache, because of how ext4 does metadata journaling etc.

IIRC, ext4 readdir is not slow because of the use of the buffer cache, it's slow because of the way it hashes dirents across blocks on disk. i.e. it has locality issues, not a caching problem.

Quoted 5 lines

Having readdir() caching at the VFS layer would likely be a really good thing, but it's hard. It *might* be worth looking at the nfs4 code to see if we could possibly move some of that code into the vfs layer, but the answer is likely "no", or at least "that's incredibly painful".

Maybe I'm missing something - what operation would be sped up by caching readdir data? Are you trying to optimise the ->lookup that tends to follow readdir by caching individual dirents? Or something else?

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
reviewer
#249 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 9 lines · Dave Chinner
Quoted 7 lines · Linus Torvalds

And readdir() itself, for that matter - we have no good vfs-level readdir caching, so it all ends up serialized on the inode semaphore, and it all goes all the way into the filesystem to get the readdir data. And at least for ext4, readdir()
is slow anyway, because it doesn't use the page cache, it uses that good old buffer cache, because of how ext4 does metadata journaling etc.

Quoted 3 lines

IIRC, ext4 readdir is not slow because of the use of the buffer cache, it's slow because of the way it hashes dirents across blocks on disk. i.e. it has locality issues, not a caching problem.

For ext4 readdir is just a linear read of the directory. Linus is right
we store directory blocks in buffer cache but we do our own readahead on directory blocks so I don't think much slowness comes from that. One thing that is slowing us down is that we don't do preallocation for directories so they often end up being fragmented a lot.

The locality problem you are probably referring to is that readdir on ext4 returns directory entries in hash order. That is different from the ordering by inode number which is optimal for the following cache-cold stat / unlink / whatever you want to do with inodes. This causes big performance issues e.g. if you do rm -rf on large directory hierarchy. But you don't see that often these days as lots of utilities have learned to workaround ext4 problems by sorting directory entries by inode number before doing anything with them.

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
#250 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to Dave Chinner
Quoted 4 lines · Dave Chinner

IIRC, ext4 readdir is not slow because of the use of the buffer cache, it's slow because of the way it hashes dirents across blocks on disk. i.e. it has locality issues, not a caching problem.

No, you're just worrying about IO. Natural for a filesystem guy, but a lot of loads cache really well, and IO isn't an issue. Yes, there's a bad cold-cache case, but that's not when you get inode semaphore contention. You get contention when you have lots of concurrent accesses to the same directory, and then the data is all nice and hot in the caches. But readdir() _still_ sucks donkey ass by the bucket-load for that case.

And that's the case I'm talking about. Using the buffer cache for readdir() is a complete disaster, because it means that

(a) you have to go down to the filesystem, wasting CPU resources, and
more importantly, going into code that by definition hasn't been optimized as well and cannot ever be, because it's not common code that everybody sees.

(b) you have to look up the physical block number, wasting even
*more* CPU resources, because the buffer heads are physically indexed

(c) you then use the buffer head lookup, which itself isn't horrible,
but it's not as well optimized as the page cache is.

(d) and because we call into the filesystem, not only is the code not
getting as much attention as the vfs layer, we generally can't trust filesystem guys to get locking right (because 90% of the filesystems don't get the attention they need even _without_ locking, and the 10% that does is maintained by people who worry mainly about IO). So the VFS layer has no real choice except to use a big-hammer "lock the whole damn directory" approach.

End result: readdir() wastes a *lot* of time on stupid stuff (just that physical block number lookup is generally more expensive than readdir itself should be), and it does so with excessive locking, serializing everything.

Both readdir() and path component lookup are technically read operations, so why the hell do we use a mutex, rather than just get a read-write lock for reading? Yeah, it's that (d) above. I might trust xfs and ext4 to get their internal exclusions for allocations etc right when called concurrently for the same directory. But the others?

I saw you talk about how the aio IO paths are "better" than the regular page cache paths just a few days ago (when talking about persistent memory). You're completely and utterly out to lunch, *especially* with things like persistent memory, where the IO paths wouldn't even *exist*, because things never get out of the cache. And that out to lunch on this comes from your total fixation with IO. The page cache is one studly mf in the normal cases when things are cached, BUT YOU NEVER EVEN SEE THAT. Why? Because your filesystem code never gets called for it, and the page cache ends up having almost perfect behavior. It scales perfectly, and it scales with good performance.

I understand where you are coming from, but caching really really works. You ignore that, because you don't see those things, and the caching case never affects you.

The readdir path? It sucks. And it sucks exactly because it's done in the filesystem, and not in some VFS caches that we could actually make go fast. We can't cache it well.

Basically, in computer science, pretty much all performance work is about caching. And readdir is the one area where the VFS layer doesn't do well, falls on its face and punts back to the filesystem.

Linus

#251 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 3 lines · Linus Torvalds

Basically, in computer science, pretty much all performance work is about caching.

Credit where credit is due. Terje "almost all programming can be viewed as an exercise in caching" Mathisen.

Linus

#252 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to Linus Torvalds
Quoted 10 lines · Linus Torvalds
Quoted 4 lines · Dave Chinner

IIRC, ext4 readdir is not slow because of the use of the buffer cache, it's slow because of the way it hashes dirents across blocks on disk. i.e. it has locality issues, not a caching problem.

No, you're just worrying about IO. Natural for a filesystem guy, but a lot of loads cache really well, and IO isn't an issue. Yes, there's a bad cold-cache case, but that's not when you get inode semaphore contention.

Right, because it's cold cache performance that everyone complains about. e.g. Workloads like gluster, ceph, fileservers, openstack (e.g. swift) etc are all mostly cold cache directory workloads with *extremely high* concurrency. Nobody is complaining about cached readdir performance - concurrency in cold cache directory operations is what everyone has been asking me for.

In case you missed it, recently the Ceph developers have been talking about storing file handles in a userspace database and then using open_by_handle_at() so they can avoid the pain of cold cache directory lookup overhead (see the O_NOMTIME thread). We have a serious cold cache lookup problem on directories when people are looking to bypass the directory structure entirely....

[snip a bunch of rhetoric lacking in technical merit]

Quoted 4 lines

End result: readdir() wastes a *lot* of time on stupid stuff (just that physical block number lookup is generally more expensive than readdir itself should be), and it does so with excessive locking, serializing everything.

The most overhead in readdir is calling filldir over and over again for every dirent to copy it into the user buffer. The overhead is not from looking up the buffer in the cache.

So, I just created close to a million dirents in a directory, and ran the xfs_io readdir command on it (look, a readdir performance measurement tool!). I used a ram disk to take IO out of the picture for the first read, the system has E5-4620 0 @ 2.20GHz CPUs, and I dropped caches to ensure that there was no cached metadata:

$ sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
$ sudo xfs_io -c readdir /mnt/scratch
read 29545648 bytes from offset 0
28 MiB, 923327 ops, 0.0000 sec (111.011 MiB/sec and 3637694.9201 ops/sec)
$ sudo xfs_io -c readdir /mnt/scratch
read 29545648 bytes from offset 0
28 MiB, 923327 ops, 0.0000 sec (189.864 MiB/sec and 6221628.5056 ops/sec)
$ sudo xfs_io -c readdir /mnt/scratch
read 29545648 bytes from offset 0
28 MiB, 923327 ops, 0.0000 sec (190.156 MiB/sec and 6231201.6629 ops/sec)
$

Reading, decoding and copying dirents at 190MB/s? That's roughly 6 million dirents/second being pulled from cache, and it's doing roughly 4 million/second cold cache. That's not slow at all.

What *noticable* performance gains are there to be had here for the average user? Anything that takes less than a second or two to complete is not going to be noticable to a user, and most people don't have 8-10 million inodes in a directory....

So, what did the profile look like?

  10.07%  [kernel]  [k] __xfs_dir3_data_check
   9.92%  [kernel]  [k] copy_user_generic_string
   7.44%  [kernel]  [k] xfs_dir_ino_validate
   6.83%  [kernel]  [k] filldir
   5.43%  [kernel]  [k] xfs_dir2_leaf_getdents
   4.56%  [kernel]  [k] kallsyms_expand_symbol.constprop.1
   4.38%  [kernel]  [k] _raw_spin_unlock_irqrestore
   4.26%  [kernel]  [k] _raw_spin_unlock_irq
   4.02%  [kernel]  [k] __memcpy
   3.02%  [kernel]  [k] format_decode
   2.36%  [kernel]  [k] xfs_dir2_data_entsize
   2.28%  [kernel]  [k] vsnprintf
   1.99%  [kernel]  [k] __do_softirq
   1.93%  [kernel]  [k] xfs_dir2_data_get_ftype
   1.88%  [kernel]  [k] number.isra.14
   1.84%  [kernel]  [k] _xfs_buf_find
   1.82%  [kernel]  [k] ___might_sleep
   1.61%  [kernel]  [k] strnlen
   1.49%  [kernel]  [k] queue_work_on
   1.48%  [kernel]  [k] string.isra.4
   1.21%  [kernel]  [k] __might_sleep

Oh, I'm running CONFIG_XFS_DEBUG=y, so internal runtime consistency checks consume most of the CPU (__xfs_dir3_data_check, xfs_dir_ino_validate). IOWs, real world readdir performance will be much, much faster than I've demonstrated.

Other than that, the most CPU is spent on copying dirents into the user buffer (copy_user_generic_string), passing dirents to the user buffer (filldir) and extracting dirents from the on-disk buffer (xfs_dir2_leaf_getdents). The we have lock contention, ramdisk IO (memcpy), some vsnprintf stuff (includes format_decode, probably debug code) and some more dirent information extraction functions.

it's not until we get to _xfs_buf_find() do we see a buffer cache lookup function, and that's actually comsuming less CPU than the __might_sleep/____might_sleep() debug annotations. That puts it in persepective just how little overhead readdir buffer caching actually has compared to everything else.

IOWs, these numbers indicate that readdir caching overhead has no real impact on the performance of hot cache readdir operations.

So, back to the question I asked that you didn't answer: exactly what are you proposing to cache in the VFS readdir cache? Without knowing that, I can't make any sane comment on about technical merit of your proposal....

Quoted 6 lines

Both readdir() and path component lookup are technically read operations, so why the hell do we use a mutex, rather than just get a read-write lock for reading? Yeah, it's that (d) above. I might trust xfs and ext4 to get their internal exclusions for allocations etc right when called concurrently for the same directory. But the others?

They just use a write lock for everything and *nothing changes* - this is a simple problem to solve.

The argument "filesystem developers are stupid" is not a compelling argument against changing locking. You're just being insulting, even though you probably don't realise it.

[snip more rhetoric about the page cache being the only solution]

Quoted 4 lines

Basically, in computer science, pretty much all performance work is about caching. And readdir is the one area where the VFS layer doesn't do well, falls on its face and punts back to the filesystem.

Caching is used to hide the problems of the lower layers. If the lower layers don't have a problem, then another layer of caching is not necessary.

Linus, what you haven't put together is a clear statement of the problem another layer of readdir caching is going to solve. What workload is having problems? Where are the profiles demonstrating that readdir caching is the issue, or the solution to the issue you are seeing? We know about plenty of workloads where directory access concurrency is a real problem, but I'm not seeing the problem you are trying to address...

Cheers,

Dave.

Signature · 2 lines
Dave Chinner
[email protected]
author maintainer
#253 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 7 lines · Dave Chinner
Quoted 6 lines

Both readdir() and path component lookup are technically read operations, so why the hell do we use a mutex, rather than just get a read-write lock for reading? Yeah, it's that (d) above. I might trust xfs and ext4 to get their internal exclusions for allocations etc right when called concurrently for the same directory. But the others?

Quoted 6 lines

They just use a write lock for everything and *nothing changes* - this is a simple problem to solve.

The argument "filesystem developers are stupid" is not a compelling argument against changing locking. You're just being insulting, even though you probably don't realise it.

Er... Remember the clusterfuck around the ->i_size and alignment checks on XFS DIO writes? Just this cycle. Correctness of XFS locking is nothing to boast about - it *is* convoluted as hell and you guys are not superhuman enough to reliably spot the problems in that nest of horrors. Nobody is.

PS: I've no idea whether I'm being insulting or not and frankly, I don't give a damn; unlike Linus I hadn't signed off on the "code of conflict" nonsense. Anyone who feels like complaining is quite welcome to it.

author maintainer
#254 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 9 lines · Al Viro

Er... Remember the clusterfuck around the ->i_size and alignment checks on XFS DIO writes? Just this cycle. Correctness of XFS locking is nothing to boast about - it *is* convoluted as hell and you guys are not superhuman enough to reliably spot the problems in that nest of horrors. Nobody is.

PS: I've no idea whether I'm being insulting or not and frankly, I don't give a damn; unlike Linus I hadn't signed off on the "code of conflict" nonsense. Anyone who feels like complaining is quite welcome to it.

PPS: everyone is also quite welcome to coming up with a sane locking scheme, and unlike complaints about insulting tone, _that_ won't be ignored. Folks, I'm absolutely serious - if anyone has a good candidate (hell, any candidate), post it on fsdevel and let's discuss it. I'll do my best to poke holes in such and if we end up with something working, I'll be bloody happy.

#255 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to Dave Chinner
Quoted 3 lines · Dave Chinner

Right, because it's cold cache performance that everyone complains about.

People really do complain about the hot-cache one too.

Did you read the description of the sample benchmark that Jeremy described Windows sales people for using?

That kind of thing is actually not that unusual, and they can be big sales tools.

We went through similar things with "mindcraft", then netbench/dbench. People will run those benchmarks with enough memory (and often tune things like dirty thresholds etc) explicitly to get rid of the IO component for benchmarking reasons.

And often they are just nasty marketing benchmarks and not very meaningful. The "geekbench of filesystem testing", if you will. Fair enough. But those kinds of things have also been very useful in making performance better, because the "real" filesystem benchmarks are usually too nasty to actually run on reasonable machines. So the fake/bad ones are often good at showing things that don't scale well (despite being 100% just CPU-bound) because they show some bottleneck.

And sometimes fixing that bottleneck for the non-IO case ends up helping the IO case too.

So the one samba profile I remember seeing was probably from early dbench, I'm pretty sure it was Tridge that showed it as a stress-case for samba on Linux. So we're talking a decade ago, I really can't claim I remember the details, but I do remember it being readdir() being 100% CPU-bound. Or rather, it *would* have been 100% CPU-bound, but due to the inode semaphore (and back then it was i_sem, I think, now it's i_mutex) it was actually spending most of the time sleeping/scheduling due to inode semaphore contention. So rather than scaling perfectly with CPU's, it just took basically one CPU.

Now, samba has probably changed enormously, and maybe it's not a big deal. But I don't think our filesystem locking has changed at all, because quite frankly, nobody else seems to see it. It tends to be a fileserving thing (the Lustre comment kind of feeds into that).

So it might be interesting to have a simple benchmark that people can run. WITHOUT the IO load. Because really, IO isn't that interesting to most of us, especially when we then don't even have IO subsystems that do much parallelism..

I wrote my own (really really stupid) concurrent stat() test just to get good profiles of where the real problems are. It's nasty - it's literally just MAX_THREADS pthread that loop on doing stat() on a list of files for ten seconds, and then it reports the total number of loops. But that stupid thing was actually ridiculously useful, not because the load is meaningful, but because it ended up showing that we had horribly fragile behavior when we had contention on the dentry lock.

(That got fixed, although it still ends up sucking when we fall out of RCU mode - but with Al's upcoming patches that should hopefully be really really unusual rather than "every time we see a symlink" etc)

Linus

#256 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to Dave Chinner
Quoted 15 lines · Dave Chinner
Quoted 7 lines · Linus Torvalds
Quoted 5 lines · Al Viro

Maybe... I'd like to see the profiles, TBH - especially getxattr() and access() frequency on various loads. Sure, make(1) and cc(1) really care about stat() very much, but I wouldn't be surprised if something like httpd or samba would be hitting getxattr() a lot...

Quoted 5 lines

So I haven't seen samba profiles in ages, but iirc we have more serious problems than trying to speed up basic filename lookup.

At least long long ago, inode semaphore contention was a big deal, largely due to readdir().

Quoted 7 lines

It still is - it's the prime reason people still need to create hashed directory structures so that they can get concurrency in directory operations. IMO, concurrency in directory operations is a more important problem to solve than worrying about readdir speed; in large filesystems readdir and lookup are IO bound operations and so everything serialises on the IO as it's done with the i_mutex held....

We've had a patch[*] to add ext4 parallel directory operations in Lustre for a few years, that adds separate locks for each internal tree and leaf block instead of using i_mutex, so it scales as the size of the directory grows. This definitely improved many-threaded directory create/lookup/unlink performance (rename still uses a single lock).

Since it is used only directly on the server within the kernel there is no VFS interface for it. The last time that I brought VFS integration up with Al a year or two ago he thought the complexity was very high and not worth the effort, but maybe with increasing thread counts on clients it is time to look into it?

Cheers, Andreas

[*] For anyone interested, the latest version of the ext4 patch is at:
http://git.whamcloud.com/fs/lustre-release.git/blob/ac8a7f3d22ef007bcf62bfb4c6ed747cec6e874e:/ldiskfs/kernel_patches/patches/rhel7/ext4-pdirop.patch

Quoted 18 lines
Quoted 7 lines

And readdir() itself, for that matter - we have no good vfs-level readdir caching, so it all ends up serialized on the inode semaphore, and it all goes all the way into the filesystem to get the readdir data. And at least for ext4, readdir()
is slow anyway, because it doesn't use the page cache, it uses that good old buffer cache, because of how ext4 does metadata journaling etc.

IIRC, ext4 readdir is not slow because of the use of the buffer cache, it's slow because of the way it hashes dirents across blocks on disk. i.e. it has locality issues, not a caching problem.

Quoted 5 lines

Having readdir() caching at the VFS layer would likely be a really good thing, but it's hard. It *might* be worth looking at the nfs4 code to see if we could possibly move some of that code into the vfs layer, but the answer is likely "no", or at least "that's incredibly painful".

Quoted 15 lines

Maybe I'm missing something - what operation would be sped up by caching readdir data? Are you trying to optimise the ->lookup that tends to follow readdir by caching individual dirents? Or something else?

Cheers,

Dave.

Signature · 6 lines
Dave Chinner
[email protected]
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Cheers, Andreas

#257 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 24 lines · On Fri, 15 May 2015 15:15:48 -0600 Andreas Dilger <[email protected]> wrote:
Quoted 15 lines · Dave Chinner
Quoted 7 lines · Linus Torvalds
Quoted 5 lines · Al Viro

Maybe... I'd like to see the profiles, TBH - especially getxattr() and access() frequency on various loads. Sure, make(1) and cc(1) really care about stat() very much, but I wouldn't be surprised if something like httpd or samba would be hitting getxattr() a lot...

Quoted 5 lines

So I haven't seen samba profiles in ages, but iirc we have more serious problems than trying to speed up basic filename lookup.

At least long long ago, inode semaphore contention was a big deal, largely due to readdir().

Quoted 7 lines

It still is - it's the prime reason people still need to create hashed directory structures so that they can get concurrency in directory operations. IMO, concurrency in directory operations is a more important problem to solve than worrying about readdir speed; in large filesystems readdir and lookup are IO bound operations and so everything serialises on the IO as it's done with the i_mutex held....

Quoted 5 lines

We've had a patch[*] to add ext4 parallel directory operations in Lustre for a few years, that adds separate locks for each internal tree and leaf block instead of using i_mutex, so it scales as the size of the directory grows. This definitely improved many-threaded directory create/lookup/unlink performance (rename still uses a single lock).

.. and I've been wondering what to do about i_mutex and NFS. I've had customer reports of slowness in creating files that seems to be due to i_mutex on the directory being held over the whole 'create' RPC, so only one of those can be in flight at the one time.
"make -j" on a large source directory can easily want to create lots of "*.o" files at "the same time".

And NFS doesn't need i_mutex at all because the server will provide the needed guarantees.

NeilBrown

author maintainer
#258 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 9 lines · NeilBrown

.. and I've been wondering what to do about i_mutex and NFS. I've had customer reports of slowness in creating files that seems to be due to i_mutex on the directory being held over the whole 'create' RPC, so only one of those can be in flight at the one time.
"make -j" on a large source directory can easily want to create lots of "*.o" files at "the same time".

And NFS doesn't need i_mutex at all because the server will provide the needed guarantees.

Directory i_mutex is used for a lot more than serialization of fs methods on said directory - a lot of dcache handling relies upon having it held around adding dentries/moving them around/making them negative/etc.

Server can't do a damn thing about those, obviously. Neither can it do anything about multiple lookups on the same name in the same directory, just because several processes have arrived at the same time with dcache cold. And no, caching dentry before the end of lookup isn't a good idea either - you'll get tons of messy corner cases.

If anyone has a usable finer-grained locking scheme, I would _love_ to see it. All I'd seen from Lustre folks in that area was bringing Stross' mythos to mind - both as in "reduction of NP to P would be handy for analysis" and "looking into that thing feels like an excellent way of inviting the gibbering monstrosities from beyond the spacetime to chew on your cortex".

#259 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to NeilBrown
Quoted 10 lines · NeilBrown

.. and I've been wondering what to do about i_mutex and NFS. I've had customer reports of slowness in creating files that seems to be due to i_mutex on the directory being held over the whole 'create' RPC, so only one of those can be in flight at the one time.
"make -j" on a large source directory can easily want to create lots of "*.o" files at "the same time".

And NFS doesn't need i_mutex at all because the server will provide the needed guarantees.

So i_mutex on a directory is probably the nastiest lock we have in the fs layer.

It's used for several different half-related things:

- serialize filename creation/deletion

This is partly for the benefit of the filesystem itself (and not
helpful for NFS, as you note), but it's also very much about making sure we have uniqueness guarantees at the VFS layer too.

So even with NFS, it's not just "the server provides the needed
guarantees", because some of the guarantees are really client-local.

For example, simply that we only ever have one single dentry for a
particular name, and that we only ever have one active lookup per dentry. Those things happen independently of - and before - the server even sees the operation.

So the whole local directory tree consistency ends up depending on this.

- readdir(). This is mostly to make it hard for filesystems to do the
wrong thing when there is concurrent file creation.

I suspect readdir could fairly easily push the i_mutex down from the caller and into the filesystem, and then filesystems might narrow down the use (or even get rid of it). The initial patch might even be automated with coccinelle. However, rather few loads actually have a lot of readdir() activity, and samba is probably the only major one. I've seen benchmarks where it matters, but they are rare (and I haven't seen one in literally years).

So the readdir case could probably be at least relaxed fairly easily. But the thing that tends to hurt on more loads is, as you note, the filename lookup/creation/movement case. And that's much harder to fix.

Al, do you have any ideas? Personally, I've wanted to make I_mutex a rwsem for a long time, but right now pretty much everything uses it for exclusion. For example, filename lookup is clearly just reading the directory, so it should take a rwsem for reading, right? No. Not the way it is done now. Filename lookup wants the directory inode exclusively because that guarantees that we create just one dentry and call the filesystem ->lookup only once on that dentry.

Again, there tend to be no simple benchmarks or loads that people care about that show this. Most of the time it's fairly hard to see.

Linus

#260 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 22 lines · On Fri, 15 May 2015 17:45:56 -0700 Linus Torvalds…
Quoted 10 lines · NeilBrown

.. and I've been wondering what to do about i_mutex and NFS. I've had customer reports of slowness in creating files that seems to be due to i_mutex on the directory being held over the whole 'create' RPC, so only one of those can be in flight at the one time.
"make -j" on a large source directory can easily want to create lots of "*.o" files at "the same time".

And NFS doesn't need i_mutex at all because the server will provide the needed guarantees.

So i_mutex on a directory is probably the nastiest lock we have in the fs layer.

It's used for several different half-related things:

- serialize filename creation/deletion

This is partly for the benefit of the filesystem itself (and not
helpful for NFS, as you note), but it's also very much about making sure we have uniqueness guarantees at the VFS layer too.

Quoted 7 lines

So even with NFS, it's not just "the server provides the needed
guarantees", because some of the guarantees are really client-local.

For example, simply that we only ever have one single dentry for a
particular name, and that we only ever have one active lookup per dentry. Those things happen independently of - and before - the server even sees the operation.

But surely those things can be managed with a spinlock.

I think a big part of the problem is that the VFS tries to control filesystems rather than provide services to them.
If NFS was in control it might:
- ask the dcache to look up a name and get back a dentry: positive, negative or not-validated.
- if positive, NFS returns an error, or uses the inode - depending on operation.
- otherwise send a 'create' request to the server. At this point it holds references to dentries for directory and target, but no locks.
- If an error is returned, just drop references and return.
- On successful create, turn the filehandle into an inode and then ask the dcache to link the inode with the target dentry.
If the dentry is still negative or not-validated, this is trivial. If it is positive and already has the right inode, again trivial. If it has the wrong inode, you certainly have an interesting problem, but one that is specific to NFS (or similar filesystems) and one that is up to NFS to solve, not up to the VFS to avoid.
If the syscall doesn't need to return an 'fd', then just drop the references and report success.
If an 'fd' is required, then create a 'deleted' dentry attached to the original parent. As 'mkdir' doesn't return an fd, that should be safe (not that all the fuss about directory dentries having exactly one parent is particularly relevant to NFS)

I'm not convinced that serialising 'lookup' calls is vital. If two threads find a 'not-validated' dentry, and both try to look up the inode, they will both ultimately get the same struct_inode from the icache, and will both succeed in connecting it to the dentry. Obviously it would be better to avoid two concurrent NFS "LOOKUP" requests, but that is a problem for NFS to solve. I suspect that using d_fsdata to point to a pending LOOKUP request would allow the "second" thread to wait for that request to finish. Other filesystems would take a completely different approach.

But with the VFS trying to be in control and trying to accommodate the needs of wildly different filesystems, I imagine it might not be so easy.

Quoted 5 lines

So the whole local directory tree consistency ends up depending on this.

- readdir(). This is mostly to make it hard for filesystems to do the
wrong thing when there is concurrent file creation.

and makes it impossible for them to do the right thing too?

Quoted 21 lines

I suspect readdir could fairly easily push the i_mutex down from the caller and into the filesystem, and then filesystems might narrow down the use (or even get rid of it). The initial patch might even be automated with coccinelle. However, rather few loads actually have a lot of readdir() activity, and samba is probably the only major one. I've seen benchmarks where it matters, but they are rare (and I haven't seen one in literally years).

So the readdir case could probably be at least relaxed fairly easily. But the thing that tends to hurt on more loads is, as you note, the filename lookup/creation/movement case. And that's much harder to fix.

Al, do you have any ideas? Personally, I've wanted to make I_mutex a rwsem for a long time, but right now pretty much everything uses it for exclusion. For example, filename lookup is clearly just reading the directory, so it should take a rwsem for reading, right? No. Not the way it is done now. Filename lookup wants the directory inode exclusively because that guarantees that we create just one dentry and call the filesystem ->lookup only once on that dentry.

Quoted 3 lines

Again, there tend to be no simple benchmarks or loads that people care about that show this. Most of the time it's fairly hard to see.

Which "this"? Readdir? I haven't come across one (NFS READDIR issues are all about when to use READDIR_PLUS and when not to).
Or create-contention-on-i_mutex? The load my customer had was 'make -j60' on a many-cpu machine. I'm not sure if all the .o files were in the one directory or if some logging was being recorded to multiple files in that one directory, but the serialization of file-creation over NFS was measurable. It wasn't horribly bad, but it was noticeably suboptimal. I think I recommended creating whatever-it-was in multiple directories.

NeilBrown

#261 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 7 lines · NeilBrown
Quoted 5 lines

For example, simply that we only ever have one single dentry for a
particular name, and that we only ever have one active lookup per dentry. Those things happen independently of - and before - the server even sees the operation.

But surely those things can be managed with a spinlock.

Some of them could. But no, not in general. Exactly because of that "only one lookup of this particular dentry". The lookup sleeps, so when another thread tries to look up the same dentry, it needs to hit a sleeping lock. And since at that point we don't have the new dentry yet (much less the inode we're looking up), it is in the only place we *do* have: the inode of the directory we're looking up.

Now, maybe we could solve it with a new sleeping lock in the dentry itself. Maybe we could allocate the new dentry early, add it to the directory the usual way, but mark it as being "not ready" (so that d_lookup() wouldn't use it). And have the sleeping lock be a new sleeping lock in the dentry.

But that would be a very big change. And I suspect Al has tons of reasons why it would have various problems. We do rely on the i_mutex a fair amount..

Quoted 2 lines

I think a big part of the problem is that the VFS tries to control filesystems rather than provide services to them.

Well, most of the time we do aim to provice services (eg the page cache generally works that way).

But the dentry layer really is pretty important. And it's a *huge* performance win. Yes, yes, you don't see it when it works, you only see the bad cases, and there is no way in hell we can let the low-level filesystem control the dentry tree.

Also, remember: we do have something like 65 different filesystems. It's a *big* deal to make them all work "well enough". Making the locking rules unambiguous is pretty primary.

Making one particular filesystem perform well is important to _you_, but in the big picture...

Quoted 4 lines
Quoted 2 lines

- readdir(). This is mostly to make it hard for filesystems to do the
wrong thing when there is concurrent file creation.

and makes it impossible for them to do the right thing too?

Well, possibly. As mentioned, the readdir case might actually be pretty trivial, but it just hasn't come up often.

I've literally only ever seen it for samba. I'd take a tested patch. Hint, hint.

Quoted 7 lines
Quoted 2 lines

Again, there tend to be no simple benchmarks or loads that people care about that show this. Most of the time it's fairly hard to see.

Which "this"? Readdir? I haven't come across one (NFS READDIR issues are all about when to use READDIR_PLUS and when not to).
Or create-contention-on-i_mutex? The load my customer had was 'make -j60' on a many-cpu machine.

Both/either. The problem is that the "make -j60" case seems to depend on the filesystem having bad latency - it certainly doesn't show up on a local filesystem. Maybe that could be simulated some way..

Linus

author maintainer
#262 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 5 lines · Linus Torvalds

Now, maybe we could solve it with a new sleeping lock in the dentry itself. Maybe we could allocate the new dentry early, add it to the directory the usual way, but mark it as being "not ready" (so that d_lookup() wouldn't use it). And have the sleeping lock be a new sleeping lock in the dentry.

See upthread. It might be doable (provided that we turn ->i_mutex into rwsem, to keep the exclusion with directory _modifiers_), but it'll need a really non-trivial code review of a bunch of filesystems, especially ones that want to play with the list of children like ceph does. And things like sillyrename and dcache-populating readdir instances, albeit not as scary as ceph. And then there's lustre...

#263 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 7 lines · Al Viro

See upthread. It might be doable (provided that we turn ->i_mutex into rwsem, to keep the exclusion with directory _modifiers_), but it'll need a really non-trivial code review of a bunch of filesystems, especially ones that want to play with the list of children like ceph does. And things like sillyrename and dcache-populating readdir instances, albeit not as scary as ceph. And then there's lustre...

Yup.

I don't think it's viable if we can't do it gradually, and leave filesystems with the option to basically keep the existing locking. Because most won't care that deeply anyway, and some have complications like ceph.

But we might be able to do *some* changes that wouldn't be that noticeable. For example, something like

- phase 1:

Turn i_mutex into an rwsem, change all users to take it for writing

This part should be pretty much a semantic no-op.

- phase 2:

For filesystems that say that they are ok with, make lookup_slow()
(and *only* lookup_slow for now) instead take the rwsem for reading, but in addition to that, take a hashed mutex.

By "hashed mutex", I mean having a smallish table of mutexes (say, 1024), and just creating a hash based on the name-hash and the parent pointer. That way we can avoid all the issues with adding a new lock to the dentry itself, or having to allocate a new child dentry just for the lock. It *could* cause some cross-directory serialization due to hash collisions, but that shouldn't be noticeable if the hash is of a reasonable size and quality.

That would allow lookups (and _only_ lookups) to happen in parallel, but the hashed mutex would mean that you'd serialize the "same name in same directory" case. And we'd require filesystems to say "I can support this concurrent lookup model".

There might be a "phase 3" and so on where we could expand this to slightly more than just lookup_slow(), but I suspect that even doing it *just* there would already catch the bulk of issues. And requiring filesystems to sign up for it means that we can ignore any ugly cases.

I dunno. The above _sounds_ fairly safe and easy because of how it limits the impact. But I might be missing something.

Linus

author maintainer
#264 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 11 lines · Linus Torvalds

For filesystems that say that they are ok with, make lookup_slow()
(and *only* lookup_slow for now) instead take the rwsem for reading, but in addition to that, take a hashed mutex.

By "hashed mutex", I mean having a smallish table of mutexes (say, 1024), and just creating a hash based on the name-hash and the parent pointer. That way we can avoid all the issues with adding a new lock to the dentry itself, or having to allocate a new child dentry just for the lock. It *could* cause some cross-directory serialization due to hash collisions, but that shouldn't be noticeable if the hash is of a reasonable size and quality.

What for? All we need is a flag, waitqueue and being woken
up when the flag gets cleared. So let's just use the queue of parent's ->i_mutex and explicitly kick it when removing dentry flag. We *are* holding a reference on parent (we need that to hold that sucker shared, after all), so it's not going away under us...

I'm all for gradual transformations, but in this case I suspect
that doing it on per-fs basis isn't the best way to do it; gradual massage of code using dcache lookups or walking the lists of children in filesystems (fortunately, it's fairly rare these days, and we only need to care about the code checking if such a beast is hashed; d_alloc() already places new dentry on the list of children) would seem to be a better approach. We'd also need to audit fs/dcache.c tree-walking-related code itself, but that's much more limited.

A message that is not in the archive (sent off-list or not yet received): CA+55aFzOcE7e=sOckb56KTs+WGutnL2_uum=pLD6Y+73t=EwOQ@mail.gmail.com

author maintainer
#266 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 11 lines · Linus Torvalds
Quoted 3 lines · Al Viro

What for? All we need is a flag, waitqueue and being woken
up when the flag gets cleared.

You need to have the flag somewhere.

The child dentry doesn't exist yet.

That's the point of the hashed entry. It approximates the not-yet-existing child dentry that we have *not* added to the parent until after lookup.

Point, but... A lot of our problems comes from the fact that ->i_mutex doubles as protection against the addition to the list of children, on top of protection of directory itself. What if we do the following:
have the normal case of __lookup_hash() (and other callers of lookup_real())
* allocate dentry, marked "in-lookup"
* do dcache lookup, likely to come up empty, _without_ touching
potential matches' d_lock, i.e. based on __d_lookup_rcu() (under rcu_read_lock(), with rename_lock loop around it). Hold parent's ->d_lock while walking the chain, grab refcount in the unlikely case the match had been found. If nothing's found *and* rename_lock hadn't been touched, insert the new dentry into hash and list of children before dropping ->d_lock.
* call ->lookup() (still under ->i_mutex, shared)
* clear "in-lookup" bit on _original_ dentry (we might very well
have returned a different one)
* kick the wait queue of parent's ->i_mutex

I'll need to think about that after I get some sleep, but it smells like that could be feasible. Of course, that assumes we'll be able to cope with hashed-but-currently-in-lookup dentries, but I think it might be doable with some massage...

#267 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

On Fri, May 15, 2015 at 9:31 PM, Al Viro <[email protected]> wrote:
=>

Quoted 3 lines

Point, but... A lot of our problems comes from the fact that ->i_mutex doubles as protection against the addition to the list of children, on top of protection of directory itself.

Yeah, ok, we'd need to change that too. Maybe just make it use d_lock..

But yes, I like your alternative:

Quoted 5 lines

What if we do the following:
have the normal case of __lookup_hash() (and other callers of lookup_real())
* allocate dentry, marked "in-lookup"
* do dcache lookup, likely to come up empty, _without_ touching
potential matches' d_lock, i.e. based on __d_lookup_rcu() (under

Quoted 8 lines

rcu_read_lock(), with rename_lock loop around it). Hold parent's ->d_lock while walking the chain, grab refcount in the unlikely case the match had been found. If nothing's found *and* rename_lock hadn't been touched, insert the new dentry into hash and list of children before dropping ->d_lock.
* call ->lookup() (still under ->i_mutex, shared)
* clear "in-lookup" bit on _original_ dentry (we might very well
have returned a different one)
* kick the wait queue of parent's ->i_mutex

I agree, that should work too, and might be somewhat advantageous. And we do have that extra dentry, since we pass it down (for the name) to lookup anyway. We'd just hash it and have that magical state.

Anyway, just grepping for "i_mutex" made me almost cry.

So phase 1 should probably be to not even touch i_mutex, but just add a new abstraction layer to get rid of the direct lock accesses. That will make things easier down the line.

The attached patch is huge, but it's all automated, and shouldn't change any semantics at all - except to make it much easier to change the locking details later. What do you think?

There are still a lot of "i_mutex" references in comments (several of them clearly just mindless search-and-replace from when it used to be a semaphore, when we _didn't_ do this cleanup: look for "down" mentions ;) and there's a few scattered actual uses for initialization and for two cases of 'mutex_lock_killable()' that I didn't bother to make a wrapper for etc. But this should make it much easier to change things eventually if we want to (ie we could turn it all into a rwsem with a rather small patch for testing)

Linus

author maintainer
#268 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

In reply to NeilBrown
Quoted 4 lines · NeilBrown

But surely those things can be managed with a spinlock.

I think a big part of the problem is that the VFS tries to control filesystems rather than provide services to them.

What with being the thing syscalls talk to for sending the requests to filesystems... Do you really want to push the pathname resolution into fs code? You've looked at it lately, right?

Quoted 8 lines

I'm not convinced that serialising 'lookup' calls is vital. If two threads find a 'not-validated' dentry, and both try to look up the inode, they will both ultimately get the same struct_inode from the icache, and will both succeed in connecting it to the dentry. Obviously it would be better to avoid two concurrent NFS "LOOKUP" requests, but that is a problem for NFS to solve. I suspect that using d_fsdata to point to a pending LOOKUP request would allow the "second" thread to wait for that request to finish. Other filesystems would take a completely different approach.

See upthread regarding multiple negative dentries with the same name and fun consequences thereof. There might be _NO_ inode. At all. dcache has a large negative component and without it you'd get really fucked on NFS as soon as you try to compile anything. Shitloads of headers, looked up in a lot of directories. Most of the lookups ending up negative. We really do need that stuff...

#269 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 9 lines · On Sat, 16 May 2015 02:47:18 +0100 Al Viro <[email protected]> wrote:
Quoted 4 lines · NeilBrown

But surely those things can be managed with a spinlock.

I think a big part of the problem is that the VFS tries to control filesystems rather than provide services to them.

What with being the thing syscalls talk to for sending the requests to filesystems... Do you really want to push the pathname resolution into fs code? You've looked at it lately, right?

Yes, I've looked lately :-)
I think that all of RCU-walk, and probably some of REF-walk should happen before the filesystem gets to see anything.
But once you hit a non-positive dentry or the parent of the target name, I'd rather hand over the the FS.

NFSv4 has the ability to look up multiple components in a single LOOKUP call. VFS doesn't give it a chance to try because it wants to go step-by-step, and wants each entry in the cache to have an inode etc.

The earlier the filesystem gets control, the less completely-general the VFS needs to be.

Quoted 10 lines
Quoted 9 lines

I'm not convinced that serialising 'lookup' calls is vital. If two threads find a 'not-validated' dentry, and both try to look up the inode, they will both ultimately get the same struct_inode from the icache, and will both succeed in connecting it to the dentry. Obviously it would be better to avoid two concurrent NFS "LOOKUP" requests, but that is a problem for NFS to solve. I suspect that using d_fsdata to point to a pending LOOKUP request would allow the "second" thread to wait for that request to finish. Other filesystems would take a completely different approach.

Quoted 6 lines

See upthread regarding multiple negative dentries with the same name and fun consequences thereof. There might be _NO_ inode. At all. dcache has a large negative component and without it you'd get really fucked on NFS as soon as you try to compile anything. Shitloads of headers, looked up in a lot of directories. Most of the lookups ending up negative. We really do need that stuff...

Of course negative dentries are important and having multiple would be unfortunate. I don't suggest that for a moment.
I'm suggesting three different states for a dentry: positive, negative, don't know. "don't know" is a new state that isn't currently allowed.

While a filesystem is performing 'lookup', doing its own locking or not, the dentry would be "don't know". Anything that needed to know would block somewhere in the filesystem code on whatever lock or waitqueue or whatever that the filesystem developer felt as appropriate. On i_mutex if generic_foo() was in use.

If NFSv4 did a multi-component lookup, the intermediate dentries would be "don't know" even while they had children. For local filesystems, that sort of thing would never happen. For NFS - which has to allow for random changes on the server anyway - it is just part of the game.

NeilBrown

author maintainer
#270 lore raw

Re: [RFC][PATCHSET v3] non-recursive pathname resolution & RCU symlinks

Quoted 5 lines · NeilBrown

Yes, I've looked lately :-)
I think that all of RCU-walk, and probably some of REF-walk should happen before the filesystem gets to see anything.
But once you hit a non-positive dentry or the parent of the target name, I'd rather hand over the the FS.

... and be ready to get it back when the sucker runs into a symlink. Unless you want to handle _those_ in NFS somehow (including an absolute one starting with /sys/, etc.).

Quoted 3 lines

NFSv4 has the ability to look up multiple components in a single LOOKUP call. VFS doesn't give it a chance to try because it wants to go step-by-step, and wants each entry in the cache to have an inode etc.

Do tell, how do we deal with .. afterwards if we leave the intermediate ones without inodes? We _could_ feed multi-component requests to filesystems (and NFSv4 isn't the first one to handle that - 9p had been there a lot earlier), but then you get to
* populate all of them with inodes
* be damn careful to avoid multiple dentries for the same directory
inode
Look, creating those suckers isn't the worst part; you need to be ready for e.g. mount(2) or pathname resolution playing with the ones you'd created. It's not fs-private data structure; pathname resolution might very well span many filesystem types.

Worse, you get to deal with several multi-component requests jumping into fs at the same place. With responses arriving a bit afterwards, and guess what? Those requests happen to share bits and pieces of prefixes. Oh, and one of them is a rename. Dealing with just the final components isn't a problem; you'll need to deal with directory tree in all its fscking glory. In a way that wouldn't be in too incestous relationship with the pathwalking logics in VFS and, by that proxy, such in all other fs types.

In particular, "unknown" for intermediate nodes is a recipe for really nasty mess. If the path can rejoin the known universe several components later... <shudder>

Dealing with multi-component lookups isn't impossible and might be a good idea, but only if all intermediates are populated. What information does NFSv4 multi-component lookup give you? 9p one gives an array of FIDs, one per component, and that is best used as multi-component revalidate on hot dcache...