
From: <viro@parcelfarce.linux.theplanet.co.uk>

I've just found a way to get past the limit on nested symlinks, without
incompatible API changes _and_ with killing code duplication in most of the
readlink/follow_link pairs.  And no, it's not the old ->getlink() crap -
procfs et.al.  are not special-cased there.

Here's how it works:

* ->follow_link() still does what it used to do - replaces vfsmount/dentry
  in the nameidata it got from caller.  However, it can also leave a pathname
  to be resolved by caller.

* we add an array of char * into nameidata; we always work with
  nd->saved_names[current->link_count].  nd_set_link() sets it, nd_get_link()
  returns it.

* callers of ->follow_link() (all two of them) check if ->follow_link() had
  left us something to do.  If it had (return value was zero and nd_get_link()
  is non-NULL), they do __vfs_follow_link() on that name.  Then they call a
  new method (->put_link()) that frees whatever has to be freed, etc.

Note that absolute majority of symlinks have "resolve a pathname" as part of
their ->follow_link(); they can do something else and some don't do that at
all, but having that pathname resolution is very, very common.

With that change we allow them to shift pathname resolution part to caller. 
They don't have to - it's perfectly OK to do all work in ->follow_link(). 
However, leaving the pathname resolution to caller will

a) exclude foo_follow_link() stack frame from the picture

b) kill 2 stack frames - all callers are in fs/namei.c and they can use
   inlined variant of vfs_follow_link().

That reduction of stack use is enough to push the limit on nested symlinks
from 5 to 8 (actually, even beyond that, but since 8 is common for other
Unices it will do fine).

For those who have "pure" ->follow_link() (i.e.  "find a string that would be
symlink contents and say nd_set_link(nd, string)") we also get a common helper
implementing ->readlink() - it just calls ->follow_link() on a dummy
nameidata, calls vfs_readlink() on result of nd_get_link() and does
->put_link().  Using (or not using) it is up to filesystem; it's a helper that
can be used as a ->readlink() for many filesystems, not a reimplementation of
sys_readlink().  However, that's _MANY_ filesystems - practically all of them.


Note that we don't put any crap like "if this is a normal symlink, do this;
otherwise call ->follow_link() and let it do its magic" into callers - all
symlinks are handled the same way.  Which was the main problem with getlink
proposal back then.



This patch:

infrastructure - helpers allowing ->follow_link() to leave a pathname to be
traversed by caller + corresponding code in callers.


---

 25-akpm/fs/namei.c            |   33 ++++++++++++++++++++++++++++++++-
 25-akpm/include/linux/fs.h    |    3 +++
 25-akpm/include/linux/namei.h |    3 +++
 3 files changed, 38 insertions(+), 1 deletion(-)

diff -puN fs/namei.c~SL0-core-RC6-bk5 fs/namei.c
--- 25/fs/namei.c~SL0-core-RC6-bk5	2004-05-22 01:47:17.886146328 -0700
+++ 25-akpm/fs/namei.c	2004-05-22 01:47:17.894145112 -0700
@@ -395,6 +395,21 @@ static struct dentry * real_lookup(struc
 	return result;
 }
 
+inline void nd_set_link(struct nameidata *nd, char *path)
+{
+	nd->saved_names[current->link_count] = path;
+}
+
+inline char *nd_get_link(struct nameidata *nd)
+{
+	return nd->saved_names[current->link_count];
+}
+
+EXPORT_SYMBOL(nd_set_link);
+EXPORT_SYMBOL(nd_get_link);
+
+static inline int __vfs_follow_link(struct nameidata *, const char *);
+
 /*
  * This limits recursive symlink follows to 8, while
  * limiting consecutive symlinks to 40.
@@ -405,7 +420,7 @@ static struct dentry * real_lookup(struc
 static inline int do_follow_link(struct dentry *dentry, struct nameidata *nd)
 {
 	int err = -ELOOP;
-	if (current->link_count >= 5)
+	if (current->link_count >= MAX_NESTED_LINKS)
 		goto loop;
 	if (current->total_link_count >= 40)
 		goto loop;
@@ -416,7 +431,15 @@ static inline int do_follow_link(struct 
 	current->link_count++;
 	current->total_link_count++;
 	touch_atime(nd->mnt, dentry);
+	nd_set_link(nd, NULL);
 	err = dentry->d_inode->i_op->follow_link(dentry, nd);
+	if (!err) {
+		char *s = nd_get_link(nd);
+		if (s)
+			err = __vfs_follow_link(nd, s);
+		if (dentry->d_inode->i_op->put_link)
+			dentry->d_inode->i_op->put_link(dentry, nd);
+	}
 	current->link_count--;
 	return err;
 loop:
@@ -1380,7 +1403,15 @@ do_link:
 	if (error)
 		goto exit_dput;
 	touch_atime(nd->mnt, dentry);
+	nd_set_link(nd, NULL);
 	error = dentry->d_inode->i_op->follow_link(dentry, nd);
+	if (!error) {
+		char *s = nd_get_link(nd);
+		if (s)
+			error = __vfs_follow_link(nd, s);
+		if (dentry->d_inode->i_op->put_link)
+			dentry->d_inode->i_op->put_link(dentry, nd);
+	}
 	dput(dentry);
 	if (error)
 		return error;
diff -puN include/linux/fs.h~SL0-core-RC6-bk5 include/linux/fs.h
--- 25/include/linux/fs.h~SL0-core-RC6-bk5	2004-05-22 01:47:17.887146176 -0700
+++ 25-akpm/include/linux/fs.h	2004-05-22 01:47:17.896144808 -0700
@@ -902,6 +902,7 @@ struct inode_operations {
 			struct inode *, struct dentry *);
 	int (*readlink) (struct dentry *, char __user *,int);
 	int (*follow_link) (struct dentry *, struct nameidata *);
+	void (*put_link) (struct dentry *, struct nameidata *);
 	void (*truncate) (struct inode *);
 	int (*permission) (struct inode *, int, struct nameidata *);
 	int (*setattr) (struct dentry *, struct iattr *);
@@ -1461,6 +1462,8 @@ extern struct file_operations generic_ro
 
 #define special_file(m) (S_ISCHR(m)||S_ISBLK(m)||S_ISFIFO(m)||S_ISSOCK(m))
 
+extern void nd_set_link(struct nameidata *, char *);
+extern char *nd_get_link(struct nameidata *);
 extern int vfs_readlink(struct dentry *, char __user *, int, const char *);
 extern int vfs_follow_link(struct nameidata *, const char *);
 extern int page_readlink(struct dentry *, char __user *, int);
diff -puN include/linux/namei.h~SL0-core-RC6-bk5 include/linux/namei.h
--- 25/include/linux/namei.h~SL0-core-RC6-bk5	2004-05-22 01:47:17.889145872 -0700
+++ 25-akpm/include/linux/namei.h	2004-05-22 01:47:17.897144656 -0700
@@ -10,12 +10,15 @@ struct open_intent {
 	int	create_mode;
 };
 
+enum { MAX_NESTED_LINKS = 5 };
+
 struct nameidata {
 	struct dentry	*dentry;
 	struct vfsmount *mnt;
 	struct qstr	last;
 	unsigned int	flags;
 	int		last_type;
+	char *saved_names[MAX_NESTED_LINKS + 1];
 
 	/* Intent data */
 	union {

_
