From: 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 { _