From b46c267e4756a88593c4a08de869e70d3907637f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 14 Apr 2015 12:10:34 -0400 Subject: 9p: don't bother with 4K allocation for 24-byte local array... Signed-off-by: Al Viro diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 703342e..cda68f7 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1370,6 +1370,8 @@ v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) return v9fs_vfs_mkspecial(dir, dentry, P9_DMSYMLINK, symname); } +#define U32_MAX_DIGITS 10 + /** * v9fs_vfs_link - create a hardlink * @old_dentry: dentry for file to link to @@ -1383,7 +1385,7 @@ v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) { int retval; - char *name; + char name[1 + U32_MAX_DIGITS + 2]; /* sign + number + \n + \0 */ struct p9_fid *oldfid; p9_debug(P9_DEBUG_VFS, " %lu,%pd,%pd\n", @@ -1393,20 +1395,12 @@ v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, if (IS_ERR(oldfid)) return PTR_ERR(oldfid); - name = __getname(); - if (unlikely(!name)) { - retval = -ENOMEM; - goto clunk_fid; - } - sprintf(name, "%d\n", oldfid->fid); retval = v9fs_vfs_mkspecial(dir, dentry, P9_DMLINK, name); - __putname(name); if (!retval) { v9fs_refresh_inode(oldfid, d_inode(old_dentry)); v9fs_invalidate_inode_attr(dir); } -clunk_fid: p9_client_clunk(oldfid); return retval; } @@ -1425,7 +1419,7 @@ v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rde { struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); int retval; - char *name; + char name[2 + U32_MAX_DIGITS + 1 + U32_MAX_DIGITS + 1]; u32 perm; p9_debug(P9_DEBUG_VFS, " %lu,%pd mode: %hx MAJOR: %u MINOR: %u\n", @@ -1435,26 +1429,16 @@ v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rde if (!new_valid_dev(rdev)) return -EINVAL; - name = __getname(); - if (!name) - return -ENOMEM; /* build extension */ if (S_ISBLK(mode)) sprintf(name, "b %u %u", MAJOR(rdev), MINOR(rdev)); else if (S_ISCHR(mode)) sprintf(name, "c %u %u", MAJOR(rdev), MINOR(rdev)); - else if (S_ISFIFO(mode)) - *name = 0; - else if (S_ISSOCK(mode)) + else *name = 0; - else { - __putname(name); - return -EINVAL; - } perm = unixmode2p9mode(v9ses, mode); retval = v9fs_vfs_mkspecial(dir, dentry, perm, name); - __putname(name); return retval; } -- cgit v0.10.2 From 90e4fc8890daecea72ef73ac8047050e3e8d32db Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 14 Apr 2015 17:42:49 -0400 Subject: 9p: don't bother with __getname() in ->follow_link() We copy there a kmalloc'ed string and proceed to kfree that string immediately after that. Easier to just feed that string to nd_set_link() and _not_ kfree it until ->put_link() (which becomes kfree_put_link() in that case). Signed-off-by: Al Viro diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h index fb9ffcb..0923f2c 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -149,8 +149,6 @@ extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d); extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d); extern int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry); -extern void v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd, - void *p); extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, struct super_block *sb, int new); diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index cda68f7..0ba1171 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1224,103 +1224,46 @@ ino_t v9fs_qid2ino(struct p9_qid *qid) } /** - * v9fs_readlink - read a symlink's location (internal version) + * v9fs_vfs_follow_link - follow a symlink path * @dentry: dentry for symlink - * @buffer: buffer to load symlink location into - * @buflen: length of buffer + * @nd: nameidata * */ -static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen) +static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) { - int retval; - - struct v9fs_session_info *v9ses; - struct p9_fid *fid; + struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry); + struct p9_fid *fid = v9fs_fid_lookup(dentry); struct p9_wstat *st; - p9_debug(P9_DEBUG_VFS, " %pd\n", dentry); - retval = -EPERM; - v9ses = v9fs_dentry2v9ses(dentry); - fid = v9fs_fid_lookup(dentry); + p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); + if (IS_ERR(fid)) - return PTR_ERR(fid); + return ERR_CAST(fid); if (!v9fs_proto_dotu(v9ses)) - return -EBADF; + return ERR_PTR(-EBADF); st = p9_client_stat(fid); if (IS_ERR(st)) - return PTR_ERR(st); + return ERR_CAST(st); if (!(st->mode & P9_DMSYMLINK)) { - retval = -EINVAL; - goto done; + p9stat_free(st); + kfree(st); + return ERR_PTR(-EINVAL); } + if (strlen(st->extension) >= PATH_MAX) + st->extension[PATH_MAX - 1] = '\0'; - /* copy extension buffer into buffer */ - retval = min(strlen(st->extension)+1, (size_t)buflen); - memcpy(buffer, st->extension, retval); - - p9_debug(P9_DEBUG_VFS, "%pd -> %s (%.*s)\n", - dentry, st->extension, buflen, buffer); - -done: + nd_set_link(nd, st->extension); + st->extension = NULL; p9stat_free(st); kfree(st); - return retval; -} - -/** - * v9fs_vfs_follow_link - follow a symlink path - * @dentry: dentry for symlink - * @nd: nameidata - * - */ - -static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - int len = 0; - char *link = __getname(); - - p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); - - if (!link) - link = ERR_PTR(-ENOMEM); - else { - len = v9fs_readlink(dentry, link, PATH_MAX); - - if (len < 0) { - __putname(link); - link = ERR_PTR(len); - } else - link[min(len, PATH_MAX-1)] = 0; - } - nd_set_link(nd, link); - return NULL; } /** - * v9fs_vfs_put_link - release a symlink path - * @dentry: dentry for symlink - * @nd: nameidata - * @p: unused - * - */ - -void -v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd, void *p) -{ - char *s = nd_get_link(nd); - - p9_debug(P9_DEBUG_VFS, " %pd %s\n", - dentry, IS_ERR(s) ? "" : s); - if (!IS_ERR(s)) - __putname(s); -} - -/** * v9fs_vfs_mkspecial - create a special file * @dir: inode to create special file in * @dentry: dentry to create @@ -1514,7 +1457,7 @@ static const struct inode_operations v9fs_file_inode_operations = { static const struct inode_operations v9fs_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = v9fs_vfs_follow_link, - .put_link = v9fs_vfs_put_link, + .put_link = kfree_put_link, .getattr = v9fs_vfs_getattr, .setattr = v9fs_vfs_setattr, }; diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 9861c7c..bc2a91f 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -912,33 +912,18 @@ error: static void * v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct nameidata *nd) { - int retval; - struct p9_fid *fid; - char *link = __getname(); + struct p9_fid *fid = v9fs_fid_lookup(dentry); char *target; + int retval; p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); - if (!link) { - link = ERR_PTR(-ENOMEM); - goto ndset; - } - fid = v9fs_fid_lookup(dentry); - if (IS_ERR(fid)) { - __putname(link); - link = ERR_CAST(fid); - goto ndset; - } + if (IS_ERR(fid)) + return ERR_CAST(fid); retval = p9_client_readlink(fid, &target); - if (!retval) { - strcpy(link, target); - kfree(target); - goto ndset; - } - __putname(link); - link = ERR_PTR(retval); -ndset: - nd_set_link(nd, link); + if (retval) + return ERR_PTR(retval); + nd_set_link(nd, target); return NULL; } @@ -1006,7 +991,7 @@ const struct inode_operations v9fs_file_inode_operations_dotl = { const struct inode_operations v9fs_symlink_inode_operations_dotl = { .readlink = generic_readlink, .follow_link = v9fs_vfs_follow_link_dotl, - .put_link = v9fs_vfs_put_link, + .put_link = kfree_put_link, .getattr = v9fs_vfs_getattr_dotl, .setattr = v9fs_vfs_setattr_dotl, .setxattr = generic_setxattr, -- cgit v0.10.2 From 3188b2955de3d01949ec54eb2c9ff1ecaa0a752d Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:39 +1100 Subject: ovl: rearrange ovl_follow_link to it doesn't need to call ->put_link ovl_follow_link current calls ->put_link on an error path. However ->put_link is about to change in a way that it will be impossible to call it from ovl_follow_link. So rearrange the code to avoid the need for that error path. Specifically: move the kmalloc() call before the ->follow_link() call to the subordinate filesystem. Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 04f1248..1b4b9c5e 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -145,6 +145,7 @@ static void *ovl_follow_link(struct dentry *dentry, struct nameidata *nd) void *ret; struct dentry *realdentry; struct inode *realinode; + struct ovl_link_data *data = NULL; realdentry = ovl_dentry_real(dentry); realinode = realdentry->d_inode; @@ -152,25 +153,23 @@ static void *ovl_follow_link(struct dentry *dentry, struct nameidata *nd) if (WARN_ON(!realinode->i_op->follow_link)) return ERR_PTR(-EPERM); - ret = realinode->i_op->follow_link(realdentry, nd); - if (IS_ERR(ret)) - return ret; - if (realinode->i_op->put_link) { - struct ovl_link_data *data; - data = kmalloc(sizeof(struct ovl_link_data), GFP_KERNEL); - if (!data) { - realinode->i_op->put_link(realdentry, nd, ret); + if (!data) return ERR_PTR(-ENOMEM); - } data->realdentry = realdentry; - data->cookie = ret; + } - return data; - } else { - return NULL; + ret = realinode->i_op->follow_link(realdentry, nd); + if (IS_ERR(ret)) { + kfree(data); + return ret; } + + if (data) + data->cookie = ret; + + return data; } static void ovl_put_link(struct dentry *dentry, struct nameidata *nd, void *c) -- cgit v0.10.2 From a7a67e8a089e25ef48ab01dd34ce82678ef70f11 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 27 Apr 2015 17:51:30 -0400 Subject: ext4: split inode_operations for encrypted symlinks off the rest Signed-off-by: Al Viro diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 009a059..ad358f2 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2847,6 +2847,7 @@ extern int ext4_mpage_readpages(struct address_space *mapping, unsigned nr_pages); /* symlink.c */ +extern const struct inode_operations ext4_encrypted_symlink_inode_operations; extern const struct inode_operations ext4_symlink_inode_operations; extern const struct inode_operations ext4_fast_symlink_inode_operations; diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 55b187c..9f3baa2 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -4213,8 +4213,10 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino) inode->i_op = &ext4_dir_inode_operations; inode->i_fop = &ext4_dir_operations; } else if (S_ISLNK(inode->i_mode)) { - if (ext4_inode_is_fast_symlink(inode) && - !ext4_encrypted_inode(inode)) { + if (ext4_encrypted_inode(inode)) { + inode->i_op = &ext4_encrypted_symlink_inode_operations; + ext4_set_aops(inode); + } else if (ext4_inode_is_fast_symlink(inode)) { inode->i_op = &ext4_fast_symlink_inode_operations; nd_terminate_link(ei->i_data, inode->i_size, sizeof(ei->i_data) - 1); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 814f3be..39f8e65 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3206,10 +3206,12 @@ static int ext4_symlink(struct inode *dir, goto err_drop_inode; sd->len = cpu_to_le16(ostr.len); disk_link.name = (char *) sd; + inode->i_op = &ext4_encrypted_symlink_inode_operations; } if ((disk_link.len > EXT4_N_BLOCKS * 4)) { - inode->i_op = &ext4_symlink_inode_operations; + if (!encryption_required) + inode->i_op = &ext4_symlink_inode_operations; ext4_set_aops(inode); /* * We cannot call page_symlink() with transaction started @@ -3249,9 +3251,8 @@ static int ext4_symlink(struct inode *dir, } else { /* clear the extent format for fast symlink */ ext4_clear_inode_flag(inode, EXT4_INODE_EXTENTS); - inode->i_op = encryption_required ? - &ext4_symlink_inode_operations : - &ext4_fast_symlink_inode_operations; + if (!encryption_required) + inode->i_op = &ext4_fast_symlink_inode_operations; memcpy((char *)&EXT4_I(inode)->i_data, disk_link.name, disk_link.len); inode->i_size = disk_link.len - 1; diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index 187b789..49575ff 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -35,9 +35,6 @@ static void *ext4_follow_link(struct dentry *dentry, struct nameidata *nd) int res; u32 plen, max_size = inode->i_sb->s_blocksize; - if (!ext4_encrypted_inode(inode)) - return page_follow_link_light(dentry, nd); - ctx = ext4_get_fname_crypto_ctx(inode, inode->i_sb->s_blocksize); if (IS_ERR(ctx)) return ctx; @@ -97,18 +94,16 @@ errout: return ERR_PTR(res); } -static void ext4_put_link(struct dentry *dentry, struct nameidata *nd, - void *cookie) -{ - struct page *page = cookie; - - if (!page) { - kfree(nd_get_link(nd)); - } else { - kunmap(page); - page_cache_release(page); - } -} +const struct inode_operations ext4_encrypted_symlink_inode_operations = { + .readlink = generic_readlink, + .follow_link = ext4_follow_link, + .put_link = kfree_put_link, + .setattr = ext4_setattr, + .setxattr = generic_setxattr, + .getxattr = generic_getxattr, + .listxattr = ext4_listxattr, + .removexattr = generic_removexattr, +}; #endif static void *ext4_follow_fast_link(struct dentry *dentry, struct nameidata *nd) @@ -120,13 +115,8 @@ static void *ext4_follow_fast_link(struct dentry *dentry, struct nameidata *nd) const struct inode_operations ext4_symlink_inode_operations = { .readlink = generic_readlink, -#ifdef CONFIG_EXT4_FS_ENCRYPTION - .follow_link = ext4_follow_link, - .put_link = ext4_put_link, -#else .follow_link = page_follow_link_light, .put_link = page_put_link, -#endif .setattr = ext4_setattr, .setxattr = generic_setxattr, .getxattr = generic_getxattr, -- cgit v0.10.2 From 61ba64fc0768879a300599b011c176203bdf27d9 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 09:54:06 -0400 Subject: libfs: simple_follow_link() let "fast" symlinks store the pointer to the body into ->i_link and use simple_follow_link for ->follow_link() Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/fs/inode.c b/fs/inode.c index ea37cd1..952fb48 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -152,6 +152,7 @@ int inode_init_always(struct super_block *sb, struct inode *inode) inode->i_pipe = NULL; inode->i_bdev = NULL; inode->i_cdev = NULL; + inode->i_link = NULL; inode->i_rdev = 0; inode->dirtied_when = 0; diff --git a/fs/libfs.c b/fs/libfs.c index cb1fb4b..72e4e01 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -1093,3 +1093,16 @@ simple_nosetlease(struct file *filp, long arg, struct file_lock **flp, return -EINVAL; } EXPORT_SYMBOL(simple_nosetlease); + +void *simple_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + nd_set_link(nd, d_inode(dentry)->i_link); + return NULL; +} +EXPORT_SYMBOL(simple_follow_link); + +const struct inode_operations simple_symlink_inode_operations = { + .follow_link = simple_follow_link, + .readlink = generic_readlink +}; +EXPORT_SYMBOL(simple_symlink_inode_operations); diff --git a/include/linux/fs.h b/include/linux/fs.h index 35ec87e..0ac758f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -656,6 +656,7 @@ struct inode { struct pipe_inode_info *i_pipe; struct block_device *i_bdev; struct cdev *i_cdev; + char *i_link; }; __u32 i_generation; @@ -2721,6 +2722,8 @@ 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 *); +extern const struct inode_operations simple_symlink_inode_operations; extern int iterate_dir(struct file *, struct dir_context *); -- cgit v0.10.2 From cbe0fa3858cd638d1b540fe1279484bbd71489fb Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:02:46 -0400 Subject: ext2: use simple_follow_link() Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index f460ae3..5c09776 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1403,6 +1403,7 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino) inode->i_mapping->a_ops = &ext2_aops; } else if (S_ISLNK(inode->i_mode)) { if (ext2_inode_is_fast_symlink(inode)) { + inode->i_link = (char *)ei->i_data; inode->i_op = &ext2_fast_symlink_inode_operations; nd_terminate_link(ei->i_data, inode->i_size, sizeof(ei->i_data) - 1); diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 3e074a9..13ec54a 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -189,7 +189,8 @@ static int ext2_symlink (struct inode * dir, struct dentry * dentry, } else { /* fast symlink */ inode->i_op = &ext2_fast_symlink_inode_operations; - memcpy((char*)(EXT2_I(inode)->i_data),symname,l); + inode->i_link = (char*)EXT2_I(inode)->i_data; + memcpy(inode->i_link, symname, l); inode->i_size = l-1; } mark_inode_dirty(inode); diff --git a/fs/ext2/symlink.c b/fs/ext2/symlink.c index 20608f1..ae17179 100644 --- a/fs/ext2/symlink.c +++ b/fs/ext2/symlink.c @@ -19,14 +19,6 @@ #include "ext2.h" #include "xattr.h" -#include - -static void *ext2_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct ext2_inode_info *ei = EXT2_I(d_inode(dentry)); - nd_set_link(nd, (char *)ei->i_data); - return NULL; -} const struct inode_operations ext2_symlink_inode_operations = { .readlink = generic_readlink, @@ -43,7 +35,7 @@ const struct inode_operations ext2_symlink_inode_operations = { const struct inode_operations ext2_fast_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = ext2_follow_link, + .follow_link = simple_follow_link, .setattr = ext2_setattr, #ifdef CONFIG_EXT2_FS_XATTR .setxattr = generic_setxattr, -- cgit v0.10.2 From d0deec19917352333e38cfdafe69e796e5dedfbb Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:06:54 -0400 Subject: befs: switch to simple_follow_link() Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index 7943533..172e306 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -43,7 +43,6 @@ 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 void *befs_fast_follow_link(struct dentry *, struct nameidata *); 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, @@ -80,11 +79,6 @@ static const struct address_space_operations befs_aops = { .bmap = befs_bmap, }; -static const struct inode_operations befs_fast_symlink_inode_operations = { - .readlink = generic_readlink, - .follow_link = befs_fast_follow_link, -}; - static const struct inode_operations befs_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = befs_follow_link, @@ -403,10 +397,12 @@ static struct inode *befs_iget(struct super_block *sb, unsigned long ino) inode->i_op = &befs_dir_inode_operations; inode->i_fop = &befs_dir_operations; } else if (S_ISLNK(inode->i_mode)) { - if (befs_ino->i_flags & BEFS_LONG_SYMLINK) + if (befs_ino->i_flags & BEFS_LONG_SYMLINK) { inode->i_op = &befs_symlink_inode_operations; - else - inode->i_op = &befs_fast_symlink_inode_operations; + } else { + inode->i_link = befs_ino->i_data.symlink; + inode->i_op = &simple_symlink_inode_operations; + } } else { befs_error(sb, "Inode %lu is not a regular file, " "directory or symlink. THAT IS WRONG! BeFS has no " @@ -497,16 +493,6 @@ befs_follow_link(struct dentry *dentry, struct nameidata *nd) return NULL; } - -static void * -befs_fast_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct befs_inode_info *befs_ino = BEFS_I(d_inode(dentry)); - - nd_set_link(nd, befs_ino->i_data.symlink); - return NULL; -} - /* * UTF-8 to NLS charset convert routine * -- cgit v0.10.2 From 115b4205120dd5347858837dcdc17de0750bddef Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:10:02 -0400 Subject: ext3: switch to simple_follow_link() Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/fs/ext3/inode.c b/fs/ext3/inode.c index 2ee2dc4..6c7e546 100644 --- a/fs/ext3/inode.c +++ b/fs/ext3/inode.c @@ -2999,6 +2999,7 @@ struct inode *ext3_iget(struct super_block *sb, unsigned long ino) inode->i_op = &ext3_fast_symlink_inode_operations; nd_terminate_link(ei->i_data, inode->i_size, sizeof(ei->i_data) - 1); + inode->i_link = (char *)ei->i_data; } else { inode->i_op = &ext3_symlink_inode_operations; ext3_set_aops(inode); diff --git a/fs/ext3/namei.c b/fs/ext3/namei.c index 4264b9b..c9e767c 100644 --- a/fs/ext3/namei.c +++ b/fs/ext3/namei.c @@ -2308,7 +2308,8 @@ retry: } } else { inode->i_op = &ext3_fast_symlink_inode_operations; - memcpy((char*)&EXT3_I(inode)->i_data,symname,l); + inode->i_link = (char*)&EXT3_I(inode)->i_data; + memcpy(inode->i_link, symname, l); inode->i_size = l-1; } EXT3_I(inode)->i_disksize = inode->i_size; diff --git a/fs/ext3/symlink.c b/fs/ext3/symlink.c index ea96df3..c08c590 100644 --- a/fs/ext3/symlink.c +++ b/fs/ext3/symlink.c @@ -17,17 +17,9 @@ * ext3 symlink handling code */ -#include #include "ext3.h" #include "xattr.h" -static void * ext3_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct ext3_inode_info *ei = EXT3_I(d_inode(dentry)); - nd_set_link(nd, (char*)ei->i_data); - return NULL; -} - const struct inode_operations ext3_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = page_follow_link_light, @@ -43,7 +35,7 @@ const struct inode_operations ext3_symlink_inode_operations = { const struct inode_operations ext3_fast_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = ext3_follow_link, + .follow_link = simple_follow_link, .setattr = ext3_setattr, #ifdef CONFIG_EXT3_FS_XATTR .setxattr = generic_setxattr, -- cgit v0.10.2 From 75e7566bea0c9b2a257441b66294be94863ef929 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:13:58 -0400 Subject: ext4: switch to simple_follow_link() for fast symlinks only, of course... Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 9f3baa2..066fdd7 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -4217,6 +4217,7 @@ struct inode *ext4_iget(struct super_block *sb, unsigned long ino) inode->i_op = &ext4_encrypted_symlink_inode_operations; ext4_set_aops(inode); } else if (ext4_inode_is_fast_symlink(inode)) { + inode->i_link = (char *)ei->i_data; inode->i_op = &ext4_fast_symlink_inode_operations; nd_terminate_link(ei->i_data, inode->i_size, sizeof(ei->i_data) - 1); diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 39f8e65..5fdb9f6a 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -3251,8 +3251,10 @@ static int ext4_symlink(struct inode *dir, } else { /* clear the extent format for fast symlink */ ext4_clear_inode_flag(inode, EXT4_INODE_EXTENTS); - if (!encryption_required) + if (!encryption_required) { inode->i_op = &ext4_fast_symlink_inode_operations; + inode->i_link = (char *)&EXT4_I(inode)->i_data; + } memcpy((char *)&EXT4_I(inode)->i_data, disk_link.name, disk_link.len); inode->i_size = disk_link.len - 1; diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index 49575ff..4264fb1 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -106,13 +106,6 @@ const struct inode_operations ext4_encrypted_symlink_inode_operations = { }; #endif -static void *ext4_follow_fast_link(struct dentry *dentry, struct nameidata *nd) -{ - struct ext4_inode_info *ei = EXT4_I(d_inode(dentry)); - nd_set_link(nd, (char *) ei->i_data); - return NULL; -} - const struct inode_operations ext4_symlink_inode_operations = { .readlink = generic_readlink, .follow_link = page_follow_link_light, @@ -126,7 +119,7 @@ const struct inode_operations ext4_symlink_inode_operations = { const struct inode_operations ext4_fast_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = ext4_follow_fast_link, + .follow_link = simple_follow_link, .setattr = ext4_setattr, .setxattr = generic_setxattr, .getxattr = generic_getxattr, -- cgit v0.10.2 From a8db149fc97b122ffd14e1a5f6a110124dd721ea Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:21:20 -0400 Subject: jffs2: switch to simple_follow_link() Signed-off-by: Al Viro diff --git a/fs/jffs2/dir.c b/fs/jffs2/dir.c index 1ba5c97..8118002 100644 --- a/fs/jffs2/dir.c +++ b/fs/jffs2/dir.c @@ -354,6 +354,7 @@ static int jffs2_symlink (struct inode *dir_i, struct dentry *dentry, const char ret = -ENOMEM; goto fail; } + inode->i_link = f->target; jffs2_dbg(1, "%s(): symlink's target '%s' cached\n", __func__, (char *)f->target); diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c index fe5ea08..60d86e8 100644 --- a/fs/jffs2/fs.c +++ b/fs/jffs2/fs.c @@ -294,6 +294,7 @@ struct inode *jffs2_iget(struct super_block *sb, unsigned long ino) case S_IFLNK: inode->i_op = &jffs2_symlink_inode_operations; + inode->i_link = f->target; break; case S_IFDIR: diff --git a/fs/jffs2/symlink.c b/fs/jffs2/symlink.c index 1fefa25..8ce2f24 100644 --- a/fs/jffs2/symlink.c +++ b/fs/jffs2/symlink.c @@ -9,58 +9,15 @@ * */ -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include #include "nodelist.h" -static void *jffs2_follow_link(struct dentry *dentry, struct nameidata *nd); - const struct inode_operations jffs2_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = jffs2_follow_link, + .follow_link = simple_follow_link, .setattr = jffs2_setattr, .setxattr = jffs2_setxattr, .getxattr = jffs2_getxattr, .listxattr = jffs2_listxattr, .removexattr = jffs2_removexattr }; - -static void *jffs2_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct jffs2_inode_info *f = JFFS2_INODE_INFO(d_inode(dentry)); - char *p = (char *)f->target; - - /* - * We don't acquire the f->sem mutex here since the only data we - * use is f->target. - * - * 1. If we are here the inode has already built and f->target has - * to point to the target path. - * 2. Nobody uses f->target (if the inode is symlink's inode). The - * exception is inode freeing function which frees f->target. But - * it can't be called while we are here and before VFS has - * stopped using our f->target string which we provide by means of - * nd_set_link() call. - */ - - if (!p) { - pr_err("%s(): can't find symlink target\n", __func__); - p = ERR_PTR(-EIO); - } - jffs2_dbg(1, "%s(): target path is '%s'\n", - __func__, (char *)f->target); - - nd_set_link(nd, p); - - /* - * We will unlock the f->sem mutex but VFS will use the f->target string. This is safe - * since the only way that may cause f->target to be changed is iput() operation. - * But VFS will not use f->target after iput() has been called. - */ - return NULL; -} - -- cgit v0.10.2 From 60380f193ee5a667842a4da4a3e936689bb0e5e6 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:24:43 -0400 Subject: shmem: switch to simple_follow_link() Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/mm/shmem.c b/mm/shmem.c index de98137..7f6e2f8 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -2451,6 +2451,7 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s return -ENOMEM; } inode->i_op = &shmem_short_symlink_operations; + inode->i_link = info->symlink; } else { error = shmem_getpage(inode, 0, &page, SGP_WRITE, NULL); if (error) { @@ -2474,12 +2475,6 @@ static int shmem_symlink(struct inode *dir, struct dentry *dentry, const char *s return 0; } -static void *shmem_follow_short_symlink(struct dentry *dentry, struct nameidata *nd) -{ - nd_set_link(nd, SHMEM_I(d_inode(dentry))->symlink); - return NULL; -} - static void *shmem_follow_link(struct dentry *dentry, struct nameidata *nd) { struct page *page = NULL; @@ -2642,7 +2637,7 @@ static ssize_t shmem_listxattr(struct dentry *dentry, char *buffer, size_t size) static const struct inode_operations shmem_short_symlink_operations = { .readlink = generic_readlink, - .follow_link = shmem_follow_short_symlink, + .follow_link = simple_follow_link, #ifdef CONFIG_TMPFS_XATTR .setxattr = shmem_setxattr, .getxattr = shmem_getxattr, -- cgit v0.10.2 From 5723cb01f0295ace2b029b0737dd6525a2de337f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:27:18 -0400 Subject: debugfs: switch to simple_follow_link() Reviewed-by: Jan Kara Signed-off-by: Al Viro diff --git a/fs/debugfs/file.c b/fs/debugfs/file.c index 830a7e7..284f9aa 100644 --- a/fs/debugfs/file.c +++ b/fs/debugfs/file.c @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -43,17 +42,6 @@ const struct file_operations debugfs_file_operations = { .llseek = noop_llseek, }; -static void *debugfs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - nd_set_link(nd, d_inode(dentry)->i_private); - return NULL; -} - -const struct inode_operations debugfs_link_operations = { - .readlink = generic_readlink, - .follow_link = debugfs_follow_link, -}; - static int debugfs_u8_set(void *data, u64 val) { *(u8 *)data = val; diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c index c1e7ffb..7eaec88 100644 --- a/fs/debugfs/inode.c +++ b/fs/debugfs/inode.c @@ -174,7 +174,7 @@ static void debugfs_evict_inode(struct inode *inode) truncate_inode_pages_final(&inode->i_data); clear_inode(inode); if (S_ISLNK(inode->i_mode)) - kfree(inode->i_private); + kfree(inode->i_link); } static const struct super_operations debugfs_super_operations = { @@ -511,8 +511,8 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent, return failed_creating(dentry); } inode->i_mode = S_IFLNK | S_IRWXUGO; - inode->i_op = &debugfs_link_operations; - inode->i_private = link; + inode->i_op = &simple_symlink_inode_operations; + inode->i_link = link; d_instantiate(dentry, inode); return end_creating(dentry); } diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h index cb25af4..420311b 100644 --- a/include/linux/debugfs.h +++ b/include/linux/debugfs.h @@ -45,7 +45,6 @@ extern struct dentry *arch_debugfs_dir; /* declared over in file.c */ extern const struct file_operations debugfs_file_operations; -extern const struct inode_operations debugfs_link_operations; struct dentry *debugfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, -- cgit v0.10.2 From 4b8061a67f67ebd28d4273b05d1b6ae38f2a019b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:28:56 -0400 Subject: ufs: switch to simple_follow_link() Signed-off-by: Al Viro diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c index be7d42c..99aaf5c 100644 --- a/fs/ufs/inode.c +++ b/fs/ufs/inode.c @@ -572,9 +572,10 @@ static void ufs_set_inode_ops(struct inode *inode) inode->i_fop = &ufs_dir_operations; inode->i_mapping->a_ops = &ufs_aops; } else if (S_ISLNK(inode->i_mode)) { - if (!inode->i_blocks) + if (!inode->i_blocks) { inode->i_op = &ufs_fast_symlink_inode_operations; - else { + inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink; + } else { inode->i_op = &ufs_symlink_inode_operations; inode->i_mapping->a_ops = &ufs_aops; } diff --git a/fs/ufs/namei.c b/fs/ufs/namei.c index e491a93..f773deb 100644 --- a/fs/ufs/namei.c +++ b/fs/ufs/namei.c @@ -144,7 +144,8 @@ static int ufs_symlink (struct inode * dir, struct dentry * dentry, } else { /* fast symlink */ inode->i_op = &ufs_fast_symlink_inode_operations; - memcpy(UFS_I(inode)->i_u1.i_symlink, symname, l); + inode->i_link = (char *)UFS_I(inode)->i_u1.i_symlink; + memcpy(inode->i_link, symname, l); inode->i_size = l-1; } mark_inode_dirty(inode); diff --git a/fs/ufs/symlink.c b/fs/ufs/symlink.c index 5b537e2..874480b 100644 --- a/fs/ufs/symlink.c +++ b/fs/ufs/symlink.c @@ -25,23 +25,12 @@ * ext2 symlink handling code */ -#include -#include - #include "ufs_fs.h" #include "ufs.h" - -static void *ufs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct ufs_inode_info *p = UFS_I(d_inode(dentry)); - nd_set_link(nd, (char*)p->i_u1.i_symlink); - return NULL; -} - const struct inode_operations ufs_fast_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = ufs_follow_link, + .follow_link = simple_follow_link, .setattr = ufs_setattr, }; -- cgit v0.10.2 From 0f301bd3055e86771e7c738ca009afad0964dbd2 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:35:42 -0400 Subject: ubifs: switch to simple_follow_link() Signed-off-by: Al Viro diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 27060fc..5c27c66 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -889,6 +889,7 @@ static int ubifs_symlink(struct inode *dir, struct dentry *dentry, memcpy(ui->data, symname, len); ((char *)ui->data)[len] = '\0'; + inode->i_link = ui->data; /* * The terminating zero byte is not written to the flash media and it * is put just to make later in-memory string processing simpler. Thus, diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index 35efc10..a3dfe2a 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -51,7 +51,6 @@ #include "ubifs.h" #include -#include #include static int read_block(struct inode *inode, void *addr, unsigned int block, @@ -1300,14 +1299,6 @@ static void ubifs_invalidatepage(struct page *page, unsigned int offset, ClearPageChecked(page); } -static void *ubifs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct ubifs_inode *ui = ubifs_inode(d_inode(dentry)); - - nd_set_link(nd, ui->data); - return NULL; -} - int ubifs_fsync(struct file *file, loff_t start, loff_t end, int datasync) { struct inode *inode = file->f_mapping->host; @@ -1570,7 +1561,7 @@ const struct inode_operations ubifs_file_inode_operations = { const struct inode_operations ubifs_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = ubifs_follow_link, + .follow_link = simple_follow_link, .setattr = ubifs_setattr, .getattr = ubifs_getattr, .setxattr = ubifs_setxattr, diff --git a/fs/ubifs/super.c b/fs/ubifs/super.c index 75e6f04..20f5dbd 100644 --- a/fs/ubifs/super.c +++ b/fs/ubifs/super.c @@ -195,6 +195,7 @@ struct inode *ubifs_iget(struct super_block *sb, unsigned long inum) } memcpy(ui->data, ino->data, ui->data_len); ((char *)ui->data)[ui->data_len] = '\0'; + inode->i_link = ui->data; break; case S_IFBLK: case S_IFCHR: -- cgit v0.10.2 From 2e03f3ea7acbee59a89ea73a696b2eb4c3145cde Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:37:09 -0400 Subject: sysv: switch to simple_follow_link() Signed-off-by: Al Viro diff --git a/fs/sysv/Makefile b/fs/sysv/Makefile index 3591f9d..7a75e70 100644 --- a/fs/sysv/Makefile +++ b/fs/sysv/Makefile @@ -5,4 +5,4 @@ obj-$(CONFIG_SYSV_FS) += sysv.o sysv-objs := ialloc.o balloc.o inode.o itree.o file.o dir.o \ - namei.o super.o symlink.o + namei.o super.o diff --git a/fs/sysv/inode.c b/fs/sysv/inode.c index 8895630..590ad92 100644 --- a/fs/sysv/inode.c +++ b/fs/sysv/inode.c @@ -166,8 +166,9 @@ void sysv_set_inode(struct inode *inode, dev_t rdev) inode->i_op = &sysv_symlink_inode_operations; inode->i_mapping->a_ops = &sysv_aops; } else { - inode->i_op = &sysv_fast_symlink_inode_operations; - nd_terminate_link(SYSV_I(inode)->i_data, inode->i_size, + inode->i_op = &simple_symlink_inode_operations; + inode->i_link = (char *)SYSV_I(inode)->i_data; + nd_terminate_link(inode->i_link, inode->i_size, sizeof(SYSV_I(inode)->i_data) - 1); } } else diff --git a/fs/sysv/symlink.c b/fs/sysv/symlink.c deleted file mode 100644 index d3fa0d7..0000000 --- a/fs/sysv/symlink.c +++ /dev/null @@ -1,20 +0,0 @@ -/* - * linux/fs/sysv/symlink.c - * - * Handling of System V filesystem fast symlinks extensions. - * Aug 2001, Christoph Hellwig (hch@infradead.org) - */ - -#include "sysv.h" -#include - -static void *sysv_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - nd_set_link(nd, (char *)SYSV_I(d_inode(dentry))->i_data); - return NULL; -} - -const struct inode_operations sysv_fast_symlink_inode_operations = { - .readlink = generic_readlink, - .follow_link = sysv_follow_link, -}; diff --git a/fs/sysv/sysv.h b/fs/sysv/sysv.h index 69d4889..2c13525 100644 --- a/fs/sysv/sysv.h +++ b/fs/sysv/sysv.h @@ -161,7 +161,6 @@ extern ino_t sysv_inode_by_name(struct dentry *); extern const struct inode_operations sysv_file_inode_operations; extern const struct inode_operations sysv_dir_inode_operations; -extern const struct inode_operations sysv_fast_symlink_inode_operations; extern const struct file_operations sysv_file_operations; extern const struct file_operations sysv_dir_operations; extern const struct address_space_operations sysv_aops; -- cgit v0.10.2 From ad476fedc7805ca7cec12a56e697afe37566e573 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:41:20 -0400 Subject: jfs: switch to simple_follow_link() Reviewed-by: Jan Kara Acked-by: Dave Kleikamp Signed-off-by: Al Viro diff --git a/fs/jfs/inode.c b/fs/jfs/inode.c index 070dc4b..6f1cb2b 100644 --- a/fs/jfs/inode.c +++ b/fs/jfs/inode.c @@ -63,11 +63,12 @@ struct inode *jfs_iget(struct super_block *sb, unsigned long ino) inode->i_mapping->a_ops = &jfs_aops; } else { inode->i_op = &jfs_fast_symlink_inode_operations; + inode->i_link = JFS_IP(inode)->i_inline; /* * The inline data should be null-terminated, but * don't let on-disk corruption crash the kernel */ - JFS_IP(inode)->i_inline[inode->i_size] = '\0'; + inode->i_link[inode->i_size] = '\0'; } } else { inode->i_op = &jfs_file_inode_operations; diff --git a/fs/jfs/namei.c b/fs/jfs/namei.c index 66db7bc..e33be92 100644 --- a/fs/jfs/namei.c +++ b/fs/jfs/namei.c @@ -880,7 +880,6 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry, int ssize; /* source pathname size */ struct btstack btstack; struct inode *ip = d_inode(dentry); - unchar *i_fastsymlink; s64 xlen = 0; int bmask = 0, xsize; s64 xaddr; @@ -946,8 +945,8 @@ static int jfs_symlink(struct inode *dip, struct dentry *dentry, if (ssize <= IDATASIZE) { ip->i_op = &jfs_fast_symlink_inode_operations; - i_fastsymlink = JFS_IP(ip)->i_inline; - memcpy(i_fastsymlink, name, ssize); + ip->i_link = JFS_IP(ip)->i_inline; + memcpy(ip->i_link, name, ssize); ip->i_size = ssize - 1; /* diff --git a/fs/jfs/symlink.c b/fs/jfs/symlink.c index 80f42bc..5929e23 100644 --- a/fs/jfs/symlink.c +++ b/fs/jfs/symlink.c @@ -17,21 +17,13 @@ */ #include -#include #include "jfs_incore.h" #include "jfs_inode.h" #include "jfs_xattr.h" -static void *jfs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - char *s = JFS_IP(d_inode(dentry))->i_inline; - nd_set_link(nd, s); - return NULL; -} - const struct inode_operations jfs_fast_symlink_inode_operations = { .readlink = generic_readlink, - .follow_link = jfs_follow_link, + .follow_link = simple_follow_link, .setattr = jfs_setattr, .setxattr = jfs_setxattr, .getxattr = jfs_getxattr, -- cgit v0.10.2 From df64c082efd1f71d27d942dbfc628877272e6809 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:43:25 -0400 Subject: freevxfs: switch to simple_follow_link() Signed-off-by: Al Viro diff --git a/fs/freevxfs/vxfs_extern.h b/fs/freevxfs/vxfs_extern.h index 881aa3d..e3dcb44 100644 --- a/fs/freevxfs/vxfs_extern.h +++ b/fs/freevxfs/vxfs_extern.h @@ -50,9 +50,6 @@ extern daddr_t vxfs_bmap1(struct inode *, long); /* vxfs_fshead.c */ extern int vxfs_read_fshead(struct super_block *); -/* vxfs_immed.c */ -extern const struct inode_operations vxfs_immed_symlink_iops; - /* vxfs_inode.c */ extern const struct address_space_operations vxfs_immed_aops; extern struct kmem_cache *vxfs_inode_cachep; diff --git a/fs/freevxfs/vxfs_immed.c b/fs/freevxfs/vxfs_immed.c index 8b9229e..cb84f0f 100644 --- a/fs/freevxfs/vxfs_immed.c +++ b/fs/freevxfs/vxfs_immed.c @@ -32,29 +32,15 @@ */ #include #include -#include #include "vxfs.h" #include "vxfs_extern.h" #include "vxfs_inode.h" -static void * vxfs_immed_follow_link(struct dentry *, struct nameidata *); - static int vxfs_immed_readpage(struct file *, struct page *); /* - * Inode operations for immed symlinks. - * - * Unliked all other operations we do not go through the pagecache, - * but do all work directly on the inode. - */ -const struct inode_operations vxfs_immed_symlink_iops = { - .readlink = generic_readlink, - .follow_link = vxfs_immed_follow_link, -}; - -/* * Address space operations for immed files and directories. */ const struct address_space_operations vxfs_immed_aops = { @@ -62,26 +48,6 @@ const struct address_space_operations vxfs_immed_aops = { }; /** - * vxfs_immed_follow_link - follow immed symlink - * @dp: dentry for the link - * @np: pathname lookup data for the current path walk - * - * Description: - * vxfs_immed_follow_link restarts the pathname lookup with - * the data obtained from @dp. - * - * Returns: - * Zero on success, else a negative error code. - */ -static void * -vxfs_immed_follow_link(struct dentry *dp, struct nameidata *np) -{ - struct vxfs_inode_info *vip = VXFS_INO(d_inode(dp)); - nd_set_link(np, vip->vii_immed.vi_immed); - return NULL; -} - -/** * vxfs_immed_readpage - read part of an immed inode into pagecache * @file: file context (unused) * @page: page frame to fill in. diff --git a/fs/freevxfs/vxfs_inode.c b/fs/freevxfs/vxfs_inode.c index 363e3ae..ef73ed6 100644 --- a/fs/freevxfs/vxfs_inode.c +++ b/fs/freevxfs/vxfs_inode.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "vxfs.h" #include "vxfs_inode.h" @@ -327,8 +328,10 @@ vxfs_iget(struct super_block *sbp, ino_t ino) ip->i_op = &page_symlink_inode_operations; ip->i_mapping->a_ops = &vxfs_aops; } else { - ip->i_op = &vxfs_immed_symlink_iops; - vip->vii_immed.vi_immed[ip->i_size] = '\0'; + ip->i_op = &simple_symlink_inode_operations; + ip->i_link = vip->vii_immed.vi_immed; + nd_terminate_link(ip->i_link, ip->i_size, + sizeof(vip->vii_immed.vi_immed) - 1); } } else init_special_inode(ip, ip->i_mode, old_decode_dev(vip->vii_rdev)); -- cgit v0.10.2 From a5ef103daad25f5906700cb0929ce4eac7f72e2e Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:46:42 -0400 Subject: exofs: switch to {simple,page}_symlink_inode_operations ACK-by: Boaz Harrosh Signed-off-by: Al Viro diff --git a/fs/exofs/Kbuild b/fs/exofs/Kbuild index b47c7b8..a364fd0 100644 --- a/fs/exofs/Kbuild +++ b/fs/exofs/Kbuild @@ -16,5 +16,5 @@ libore-y := ore.o ore_raid.o obj-$(CONFIG_ORE) += libore.o -exofs-y := inode.o file.o symlink.o namei.o dir.o super.o sys.o +exofs-y := inode.o file.o namei.o dir.o super.o sys.o obj-$(CONFIG_EXOFS_FS) += exofs.o diff --git a/fs/exofs/exofs.h b/fs/exofs/exofs.h index ad9cac6..2e86086 100644 --- a/fs/exofs/exofs.h +++ b/fs/exofs/exofs.h @@ -207,10 +207,6 @@ extern const struct address_space_operations exofs_aops; extern const struct inode_operations exofs_dir_inode_operations; extern const struct inode_operations exofs_special_inode_operations; -/* symlink.c */ -extern const struct inode_operations exofs_symlink_inode_operations; -extern const struct inode_operations exofs_fast_symlink_inode_operations; - /* exofs_init_comps will initialize an ore_components device array * pointing to a single ore_comp struct, and a round-robin view * of the device table. diff --git a/fs/exofs/inode.c b/fs/exofs/inode.c index 786e4cc..73c64da 100644 --- a/fs/exofs/inode.c +++ b/fs/exofs/inode.c @@ -1222,10 +1222,11 @@ struct inode *exofs_iget(struct super_block *sb, unsigned long ino) inode->i_fop = &exofs_dir_operations; inode->i_mapping->a_ops = &exofs_aops; } else if (S_ISLNK(inode->i_mode)) { - if (exofs_inode_is_fast_symlink(inode)) - inode->i_op = &exofs_fast_symlink_inode_operations; - else { - inode->i_op = &exofs_symlink_inode_operations; + if (exofs_inode_is_fast_symlink(inode)) { + inode->i_op = &simple_symlink_inode_operations; + inode->i_link = (char *)oi->i_data; + } else { + inode->i_op = &page_symlink_inode_operations; inode->i_mapping->a_ops = &exofs_aops; } } else { diff --git a/fs/exofs/namei.c b/fs/exofs/namei.c index 5ae25e4..09a6bb1 100644 --- a/fs/exofs/namei.c +++ b/fs/exofs/namei.c @@ -113,7 +113,7 @@ static int exofs_symlink(struct inode *dir, struct dentry *dentry, oi = exofs_i(inode); if (l > sizeof(oi->i_data)) { /* slow symlink */ - inode->i_op = &exofs_symlink_inode_operations; + inode->i_op = &page_symlink_inode_operations; inode->i_mapping->a_ops = &exofs_aops; memset(oi->i_data, 0, sizeof(oi->i_data)); @@ -122,7 +122,8 @@ static int exofs_symlink(struct inode *dir, struct dentry *dentry, goto out_fail; } else { /* fast symlink */ - inode->i_op = &exofs_fast_symlink_inode_operations; + inode->i_op = &simple_symlink_inode_operations; + inode->i_link = (char *)oi->i_data; memcpy(oi->i_data, symname, l); inode->i_size = l-1; } diff --git a/fs/exofs/symlink.c b/fs/exofs/symlink.c deleted file mode 100644 index 6f6f3a4..0000000 --- a/fs/exofs/symlink.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2005, 2006 - * Avishay Traeger (avishay@gmail.com) - * Copyright (C) 2008, 2009 - * Boaz Harrosh - * - * Copyrights for code taken from ext2: - * Copyright (C) 1992, 1993, 1994, 1995 - * Remy Card (card@masi.ibp.fr) - * Laboratoire MASI - Institut Blaise Pascal - * Universite Pierre et Marie Curie (Paris VI) - * from - * linux/fs/minix/inode.c - * Copyright (C) 1991, 1992 Linus Torvalds - * - * This file is part of exofs. - * - * exofs is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation. Since it is based on ext2, and the only - * valid version of GPL for the Linux kernel is version 2, the only valid - * version of GPL for exofs is version 2. - * - * exofs is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with exofs; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include - -#include "exofs.h" - -static void *exofs_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct exofs_i_info *oi = exofs_i(d_inode(dentry)); - - nd_set_link(nd, (char *)oi->i_data); - return NULL; -} - -const struct inode_operations exofs_symlink_inode_operations = { - .readlink = generic_readlink, - .follow_link = page_follow_link_light, - .put_link = page_put_link, -}; - -const struct inode_operations exofs_fast_symlink_inode_operations = { - .readlink = generic_readlink, - .follow_link = exofs_follow_link, -}; -- cgit v0.10.2 From ac194dccd2395a51b106c9b0aab7310fa6946b0a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 10:50:05 -0400 Subject: ceph: switch to simple_follow_link() Signed-off-by: Al Viro diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index e876e19..571acd8 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -819,6 +818,7 @@ static int fill_inode(struct inode *inode, struct page *locked_page, else kfree(sym); /* lost a race */ } + inode->i_link = ci->i_symlink; break; case S_IFDIR: inode->i_op = &ceph_dir_iops; @@ -1691,16 +1691,9 @@ retry: /* * symlinks */ -static void *ceph_sym_follow_link(struct dentry *dentry, struct nameidata *nd) -{ - struct ceph_inode_info *ci = ceph_inode(d_inode(dentry)); - nd_set_link(nd, ci->i_symlink); - return NULL; -} - static const struct inode_operations ceph_symlink_iops = { .readlink = generic_readlink, - .follow_link = ceph_sym_follow_link, + .follow_link = simple_follow_link, .setattr = ceph_setattr, .getattr = ceph_getattr, .setxattr = ceph_setxattr, -- cgit v0.10.2 From b4272646293b46fb8c7bc9b5176eb94d8141a278 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 21 Apr 2015 10:48:50 -0400 Subject: logfs: fix a pagecache leak for symlinks Signed-off-by: Al Viro diff --git a/fs/logfs/dir.c b/fs/logfs/dir.c index 4cf38f1..f9b45d4 100644 --- a/fs/logfs/dir.c +++ b/fs/logfs/dir.c @@ -779,6 +779,7 @@ fail: const struct inode_operations logfs_symlink_iops = { .readlink = generic_readlink, .follow_link = page_follow_link_light, + .put_link = page_put_link, }; const struct inode_operations logfs_dir_iops = { -- cgit v0.10.2 From 37882db0546c759ff75b561c188539ac96fd0bfe Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:39 +1100 Subject: SECURITY: remove nameidata arg from inode_follow_link. No ->inode_follow_link() methods use the nameidata arg, and it is about to become private to namei.c. So remove from all inode_follow_link() functions. Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index fe30d3b..7f20b40 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -871,7 +871,7 @@ follow_link(struct path *link, struct nameidata *nd, void **p) touch_atime(link); nd_set_link(nd, NULL); - error = security_inode_follow_link(link->dentry, nd); + error = security_inode_follow_link(dentry); if (error) goto out_put_nd_path; diff --git a/include/linux/security.h b/include/linux/security.h index 18264ea..62a6620 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -43,7 +43,6 @@ struct file; struct vfsmount; struct path; struct qstr; -struct nameidata; struct iattr; struct fown_struct; struct file_operations; @@ -477,7 +476,6 @@ 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. - * @nd contains the nameidata structure for the parent directory. * Return 0 if permission is granted. * @inode_permission: * Check permission before accessing an inode. This hook is called by the @@ -1553,7 +1551,7 @@ 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, struct nameidata *nd); + int (*inode_follow_link) (struct dentry *dentry); int (*inode_permission) (struct inode *inode, int mask); int (*inode_setattr) (struct dentry *dentry, struct iattr *attr); int (*inode_getattr) (const struct path *path); @@ -1839,7 +1837,7 @@ 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, struct nameidata *nd); +int security_inode_follow_link(struct dentry *dentry); 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); @@ -2241,8 +2239,7 @@ static inline int security_inode_readlink(struct dentry *dentry) return 0; } -static inline int security_inode_follow_link(struct dentry *dentry, - struct nameidata *nd) +static inline int security_inode_follow_link(struct dentry *dentry) { return 0; } diff --git a/security/capability.c b/security/capability.c index 0d03fcc..fae99db 100644 --- a/security/capability.c +++ b/security/capability.c @@ -209,8 +209,7 @@ static int cap_inode_readlink(struct dentry *dentry) return 0; } -static int cap_inode_follow_link(struct dentry *dentry, - struct nameidata *nameidata) +static int cap_inode_follow_link(struct dentry *dentry) { return 0; } diff --git a/security/security.c b/security/security.c index 8e9b1f4..d7c30b0 100644 --- a/security/security.c +++ b/security/security.c @@ -581,11 +581,11 @@ int security_inode_readlink(struct dentry *dentry) return security_ops->inode_readlink(dentry); } -int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd) +int security_inode_follow_link(struct dentry *dentry) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) return 0; - return security_ops->inode_follow_link(dentry, nd); + return security_ops->inode_follow_link(dentry); } int security_inode_permission(struct inode *inode, int mask) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 7dade28..8016229 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2861,7 +2861,7 @@ 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, struct nameidata *nameidata) +static int selinux_inode_follow_link(struct dentry *dentry) { const struct cred *cred = current_cred(); -- cgit v0.10.2 From 34b128f31c028a28887c6659e90620727a319b16 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 19 Apr 2015 07:48:53 -0400 Subject: uninline walk_component() seriously improves the stack *and* I-cache footprint... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 7f20b40..a77f9ca 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1569,8 +1569,7 @@ static inline int should_follow_link(struct dentry *dentry, int follow) return unlikely(d_is_symlink(dentry)) ? follow : 0; } -static inline int walk_component(struct nameidata *nd, struct path *path, - int follow) +static int walk_component(struct nameidata *nd, struct path *path, int follow) { struct inode *inode; int err; -- cgit v0.10.2 From f488443d1dc50454acd549ddd856421e8f961f98 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 11:27:43 -0400 Subject: namei: take O_NOFOLLOW treatment into do_last() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index a77f9ca..4c1a8bf 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3059,6 +3059,11 @@ finish_lookup: } } BUG_ON(inode != path->dentry->d_inode); + if (!(nd->flags & LOOKUP_FOLLOW)) { + path_put_conditional(path, nd); + path_put(&nd->path); + return -ELOOP; + } return 1; } @@ -3243,12 +3248,6 @@ static struct file *path_openat(int dfd, struct filename *pathname, while (unlikely(error > 0)) { /* trailing symlink */ struct path link = path; void *cookie; - if (!(nd->flags & LOOKUP_FOLLOW)) { - path_put_conditional(&path, nd); - path_put(&nd->path); - error = -ELOOP; - break; - } error = may_follow_link(&link, nd); if (unlikely(error)) break; -- cgit v0.10.2 From fd2805be238947eb819284189205e8de6f6c41c4 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 12:02:25 -0400 Subject: do_last: kill symlink_ok When O_PATH is present, O_CREAT isn't, so symlink_ok is always equal to (open_flags & O_PATH) && !(nd->flags & LOOKUP_FOLLOW). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 4c1a8bf..3e45233 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2931,7 +2931,6 @@ static int do_last(struct nameidata *nd, struct path *path, bool got_write = false; int acc_mode = op->acc_mode; struct inode *inode; - bool symlink_ok = false; struct path save_parent = { .dentry = NULL, .mnt = NULL }; bool retried = false; int error; @@ -2949,8 +2948,6 @@ static int do_last(struct nameidata *nd, struct path *path, if (!(open_flag & O_CREAT)) { if (nd->last.name[nd->last.len]) nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY; - if (open_flag & O_PATH && !(nd->flags & LOOKUP_FOLLOW)) - symlink_ok = true; /* we _can_ be in RCU mode here */ error = lookup_fast(nd, path, &inode); if (likely(!error)) @@ -3050,7 +3047,8 @@ retry_lookup: } finish_lookup: /* we _can_ be in RCU mode here */ - if (should_follow_link(path->dentry, !symlink_ok)) { + if (should_follow_link(path->dentry, + !(open_flag & O_PATH) || (nd->flags & LOOKUP_FOLLOW))) { if (nd->flags & LOOKUP_RCU) { if (unlikely(nd->path.mnt != path->mnt || unlazy_walk(nd, path->dentry))) { -- cgit v0.10.2 From a5cfe2d5e14d4c2dec06b22d18050c443c7537f9 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 12:10:45 -0400 Subject: do_last: regularize the logics around following symlinks With LOOKUP_FOLLOW we unlazy and return 1; without it we either fail with ELOOP or, for O_PATH opens, succeed. No need to mix those cases... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 3e45233..54cbfe7 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3046,9 +3046,7 @@ retry_lookup: goto out; } finish_lookup: - /* we _can_ be in RCU mode here */ - if (should_follow_link(path->dentry, - !(open_flag & O_PATH) || (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))) { @@ -3057,14 +3055,15 @@ finish_lookup: } } BUG_ON(inode != path->dentry->d_inode); - if (!(nd->flags & LOOKUP_FOLLOW)) { - path_put_conditional(path, nd); - path_put(&nd->path); - return -ELOOP; - } return 1; } + 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); } else { -- cgit v0.10.2 From 6a9f40d6104d74b0eaa06cc59e2dcc8f2dde5e22 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 30 Apr 2015 12:25:18 -0400 Subject: namei: get rid of lookup_hash() it's a convenient helper, but we'll want to shift nameidata down the call chain, so it won't be available there... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 54cbfe7..1439aa3 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2128,16 +2128,6 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt, } EXPORT_SYMBOL(vfs_path_lookup); -/* - * Restricted form of lookup. Doesn't follow links, single-component only, - * needs parent already locked. Doesn't follow mounts. - * SMP-safe. - */ -static struct dentry *lookup_hash(struct nameidata *nd) -{ - return __lookup_hash(&nd->last, nd->path.dentry, nd->flags); -} - /** * lookup_one_len - filesystem helper to lookup single pathname component * @name: pathname component to lookup @@ -3351,7 +3341,7 @@ static struct dentry *filename_create(int dfd, struct filename *name, * Do the final lookup. */ mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = lookup_hash(&nd); + dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags); if (IS_ERR(dentry)) goto unlock; @@ -3665,7 +3655,7 @@ retry: goto exit1; mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = lookup_hash(&nd); + dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags); error = PTR_ERR(dentry); if (IS_ERR(dentry)) goto exit2; @@ -3785,7 +3775,7 @@ retry: goto exit1; retry_deleg: mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = lookup_hash(&nd); + dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { /* Why not before? Because we want correct error value */ @@ -4304,7 +4294,7 @@ retry: retry_deleg: trap = lock_rename(new_dir, old_dir); - old_dentry = lookup_hash(&oldnd); + old_dentry = __lookup_hash(&oldnd.last, oldnd.path.dentry, oldnd.flags); error = PTR_ERR(old_dentry); if (IS_ERR(old_dentry)) goto exit3; @@ -4312,7 +4302,7 @@ retry_deleg: error = -ENOENT; if (d_is_negative(old_dentry)) goto exit4; - new_dentry = lookup_hash(&newnd); + new_dentry = __lookup_hash(&newnd.last, newnd.path.dentry, newnd.flags); error = PTR_ERR(new_dentry); if (IS_ERR(new_dentry)) goto exit4; -- cgit v0.10.2 From f5beed755bce1791d926ded9d83640b25e14a617 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 30 Apr 2015 16:09:11 -0400 Subject: name: shift nameidata down into user_path_walk() that avoids having nameidata on stack during the calls of ->rmdir()/->unlink() and *two* of those during the calls of ->rename(). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1439aa3..293300c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2211,9 +2211,13 @@ EXPORT_SYMBOL(user_path_at); * path-walking is complete. */ static struct filename * -user_path_parent(int dfd, const char __user *path, struct nameidata *nd, +user_path_parent(int dfd, const char __user *path, + struct path *parent, + struct qstr *last, + int *type, unsigned int flags) { + struct nameidata nd; struct filename *s = getname(path); int error; @@ -2223,11 +2227,14 @@ user_path_parent(int dfd, const char __user *path, struct nameidata *nd, if (IS_ERR(s)) return s; - error = filename_lookup(dfd, s, flags | LOOKUP_PARENT, nd); + error = filename_lookup(dfd, s, flags | LOOKUP_PARENT, &nd); if (error) { putname(s); return ERR_PTR(error); } + *parent = nd.path; + *last = nd.last; + *type = nd.last_type; return s; } @@ -3630,14 +3637,17 @@ static long do_rmdir(int dfd, const char __user *pathname) int error = 0; struct filename *name; struct dentry *dentry; - struct nameidata nd; + struct path path; + struct qstr last; + int type; unsigned int lookup_flags = 0; retry: - name = user_path_parent(dfd, pathname, &nd, lookup_flags); + name = user_path_parent(dfd, pathname, + &path, &last, &type, lookup_flags); if (IS_ERR(name)) return PTR_ERR(name); - switch(nd.last_type) { + switch (type) { case LAST_DOTDOT: error = -ENOTEMPTY; goto exit1; @@ -3649,13 +3659,12 @@ retry: goto exit1; } - nd.flags &= ~LOOKUP_PARENT; - error = mnt_want_write(nd.path.mnt); + error = mnt_want_write(path.mnt); if (error) goto exit1; - mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags); + mutex_lock_nested(&path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); + dentry = __lookup_hash(&last, path.dentry, lookup_flags); error = PTR_ERR(dentry); if (IS_ERR(dentry)) goto exit2; @@ -3663,17 +3672,17 @@ retry: error = -ENOENT; goto exit3; } - error = security_path_rmdir(&nd.path, dentry); + error = security_path_rmdir(&path, dentry); if (error) goto exit3; - error = vfs_rmdir(nd.path.dentry->d_inode, dentry); + error = vfs_rmdir(path.dentry->d_inode, dentry); exit3: dput(dentry); exit2: - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); - mnt_drop_write(nd.path.mnt); + mutex_unlock(&path.dentry->d_inode->i_mutex); + mnt_drop_write(path.mnt); exit1: - path_put(&nd.path); + path_put(&path); putname(name); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -3756,43 +3765,45 @@ static long do_unlinkat(int dfd, const char __user *pathname) int error; struct filename *name; struct dentry *dentry; - struct nameidata nd; + struct path path; + struct qstr last; + int type; struct inode *inode = NULL; struct inode *delegated_inode = NULL; unsigned int lookup_flags = 0; retry: - name = user_path_parent(dfd, pathname, &nd, lookup_flags); + name = user_path_parent(dfd, pathname, + &path, &last, &type, lookup_flags); if (IS_ERR(name)) return PTR_ERR(name); error = -EISDIR; - if (nd.last_type != LAST_NORM) + if (type != LAST_NORM) goto exit1; - nd.flags &= ~LOOKUP_PARENT; - error = mnt_want_write(nd.path.mnt); + error = mnt_want_write(path.mnt); if (error) goto exit1; retry_deleg: - mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); - dentry = __lookup_hash(&nd.last, nd.path.dentry, nd.flags); + mutex_lock_nested(&path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); + dentry = __lookup_hash(&last, path.dentry, lookup_flags); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { /* Why not before? Because we want correct error value */ - if (nd.last.name[nd.last.len]) + if (last.name[last.len]) goto slashes; inode = dentry->d_inode; if (d_is_negative(dentry)) goto slashes; ihold(inode); - error = security_path_unlink(&nd.path, dentry); + error = security_path_unlink(&path, dentry); if (error) goto exit2; - error = vfs_unlink(nd.path.dentry->d_inode, dentry, &delegated_inode); + error = vfs_unlink(path.dentry->d_inode, dentry, &delegated_inode); exit2: dput(dentry); } - mutex_unlock(&nd.path.dentry->d_inode->i_mutex); + mutex_unlock(&path.dentry->d_inode->i_mutex); if (inode) iput(inode); /* truncate the inode here */ inode = NULL; @@ -3801,9 +3812,9 @@ exit2: if (!error) goto retry_deleg; } - mnt_drop_write(nd.path.mnt); + mnt_drop_write(path.mnt); exit1: - path_put(&nd.path); + path_put(&path); putname(name); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -4233,14 +4244,15 @@ EXPORT_SYMBOL(vfs_rename); SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, int, newdfd, const char __user *, newname, unsigned int, flags) { - struct dentry *old_dir, *new_dir; struct dentry *old_dentry, *new_dentry; struct dentry *trap; - struct nameidata oldnd, newnd; + struct path old_path, new_path; + struct qstr old_last, new_last; + int old_type, new_type; struct inode *delegated_inode = NULL; struct filename *from; struct filename *to; - unsigned int lookup_flags = 0; + unsigned int lookup_flags = 0, target_flags = LOOKUP_RENAME_TARGET; bool should_retry = false; int error; @@ -4254,47 +4266,45 @@ SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname, if ((flags & RENAME_WHITEOUT) && !capable(CAP_MKNOD)) return -EPERM; + if (flags & RENAME_EXCHANGE) + target_flags = 0; + retry: - from = user_path_parent(olddfd, oldname, &oldnd, lookup_flags); + from = user_path_parent(olddfd, oldname, + &old_path, &old_last, &old_type, lookup_flags); if (IS_ERR(from)) { error = PTR_ERR(from); goto exit; } - to = user_path_parent(newdfd, newname, &newnd, lookup_flags); + to = user_path_parent(newdfd, newname, + &new_path, &new_last, &new_type, lookup_flags); if (IS_ERR(to)) { error = PTR_ERR(to); goto exit1; } error = -EXDEV; - if (oldnd.path.mnt != newnd.path.mnt) + if (old_path.mnt != new_path.mnt) goto exit2; - old_dir = oldnd.path.dentry; error = -EBUSY; - if (oldnd.last_type != LAST_NORM) + if (old_type != LAST_NORM) goto exit2; - new_dir = newnd.path.dentry; if (flags & RENAME_NOREPLACE) error = -EEXIST; - if (newnd.last_type != LAST_NORM) + if (new_type != LAST_NORM) goto exit2; - error = mnt_want_write(oldnd.path.mnt); + error = mnt_want_write(old_path.mnt); if (error) goto exit2; - oldnd.flags &= ~LOOKUP_PARENT; - newnd.flags &= ~LOOKUP_PARENT; - if (!(flags & RENAME_EXCHANGE)) - newnd.flags |= LOOKUP_RENAME_TARGET; - retry_deleg: - trap = lock_rename(new_dir, old_dir); + trap = lock_rename(new_path.dentry, old_path.dentry); - old_dentry = __lookup_hash(&oldnd.last, oldnd.path.dentry, oldnd.flags); + old_dentry = __lookup_hash(&old_last, old_path.dentry, lookup_flags); error = PTR_ERR(old_dentry); if (IS_ERR(old_dentry)) goto exit3; @@ -4302,7 +4312,7 @@ retry_deleg: error = -ENOENT; if (d_is_negative(old_dentry)) goto exit4; - new_dentry = __lookup_hash(&newnd.last, newnd.path.dentry, newnd.flags); + new_dentry = __lookup_hash(&new_last, new_path.dentry, lookup_flags | target_flags); error = PTR_ERR(new_dentry); if (IS_ERR(new_dentry)) goto exit4; @@ -4316,16 +4326,16 @@ retry_deleg: if (!d_is_dir(new_dentry)) { error = -ENOTDIR; - if (newnd.last.name[newnd.last.len]) + if (new_last.name[new_last.len]) goto exit5; } } /* unless the source is a directory trailing slashes give -ENOTDIR */ if (!d_is_dir(old_dentry)) { error = -ENOTDIR; - if (oldnd.last.name[oldnd.last.len]) + if (old_last.name[old_last.len]) goto exit5; - if (!(flags & RENAME_EXCHANGE) && newnd.last.name[newnd.last.len]) + if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len]) goto exit5; } /* source should not be ancestor of target */ @@ -4338,32 +4348,32 @@ retry_deleg: if (new_dentry == trap) goto exit5; - error = security_path_rename(&oldnd.path, old_dentry, - &newnd.path, new_dentry, flags); + error = security_path_rename(&old_path, old_dentry, + &new_path, new_dentry, flags); if (error) goto exit5; - error = vfs_rename(old_dir->d_inode, old_dentry, - new_dir->d_inode, new_dentry, + error = vfs_rename(old_path.dentry->d_inode, old_dentry, + new_path.dentry->d_inode, new_dentry, &delegated_inode, flags); exit5: dput(new_dentry); exit4: dput(old_dentry); exit3: - unlock_rename(new_dir, old_dir); + unlock_rename(new_path.dentry, old_path.dentry); if (delegated_inode) { error = break_deleg_wait(&delegated_inode); if (!error) goto retry_deleg; } - mnt_drop_write(oldnd.path.mnt); + mnt_drop_write(old_path.mnt); exit2: if (retry_estale(error, lookup_flags)) should_retry = true; - path_put(&newnd.path); + path_put(&new_path); putname(to); exit1: - path_put(&oldnd.path); + path_put(&old_path); putname(from); if (should_retry) { should_retry = false; -- cgit v0.10.2 From 46afd6f61cc33ae4b3a2aed4bb454d11d4114c27 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 1 May 2015 22:08:30 -0400 Subject: namei: lift nameidata into filename_mountpoint() when we go for on-demand allocation of saved state in link_path_walk(), we'll want nameidata to stay around for all 3 calls of path_mountpoint(). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 293300c..ab2bcbd 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2344,31 +2344,28 @@ out: */ static int path_mountpoint(int dfd, const struct filename *name, struct path *path, - unsigned int flags) + struct nameidata *nd, unsigned int flags) { - struct nameidata nd; - int err; - - err = path_init(dfd, name, flags, &nd); + int err = path_init(dfd, name, flags, nd); if (unlikely(err)) goto out; - err = mountpoint_last(&nd, path); + err = mountpoint_last(nd, path); while (err > 0) { void *cookie; struct path link = *path; - err = may_follow_link(&link, &nd); + err = may_follow_link(&link, nd); if (unlikely(err)) break; - nd.flags |= LOOKUP_PARENT; - err = follow_link(&link, &nd, &cookie); + nd->flags |= LOOKUP_PARENT; + err = follow_link(&link, nd, &cookie); if (err) break; - err = mountpoint_last(&nd, path); - put_link(&nd, &link, cookie); + err = mountpoint_last(nd, path); + put_link(nd, &link, cookie); } out: - path_cleanup(&nd); + path_cleanup(nd); return err; } @@ -2376,14 +2373,15 @@ static int filename_mountpoint(int dfd, struct filename *name, struct path *path, unsigned int flags) { + struct nameidata nd; int error; if (IS_ERR(name)) return PTR_ERR(name); - error = path_mountpoint(dfd, name, path, flags | LOOKUP_RCU); + error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_RCU); if (unlikely(error == -ECHILD)) - error = path_mountpoint(dfd, name, path, flags); + error = path_mountpoint(dfd, name, path, &nd, flags); if (unlikely(error == -ESTALE)) - error = path_mountpoint(dfd, name, path, flags | LOOKUP_REVAL); + error = path_mountpoint(dfd, name, path, &nd, flags | LOOKUP_REVAL); if (likely(!error)) audit_inode(name, path->dentry, 0); putname(name); -- cgit v0.10.2 From 680baacbca69d18a6d7315374ad83d05ac9c0977 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 13:32:22 -0400 Subject: new ->follow_link() and ->put_link() calling conventions 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. Signed-off-by: Al Viro diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index 0a926e2..7fa6c4a 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -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); diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 5d833b3..1c6b03a 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -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 *); diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c index 3711e67..e488cb3 100644 --- a/drivers/staging/lustre/lustre/llite/symlink.c +++ b/drivers/staging/lustre/lustre/llite/symlink.c @@ -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); } diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 0ba1171..7cc70a3 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -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; } /** diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index bc2a91f..ae062ff 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -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) diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c index de58cc7..9c6a077 100644 --- a/fs/autofs4/symlink.c +++ b/fs/autofs4/symlink.c @@ -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 = { diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index 172e306..3a1aefb 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -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; } /* diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 252f5c1..61012da 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -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, diff --git a/fs/cifs/link.c b/fs/cifs/link.c index 252e672..4a439c2 100644 --- a/fs/cifs/link.c +++ b/fs/cifs/link.c @@ -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 diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index cc9f254..fac8e85 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -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 = { diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index fc850b5..cdb9d6c 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -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; } /** diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index 4264fb1..afec475 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -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) { diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 658e807..d294793 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -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, diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 0572bca..f9cb260 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -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) diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 1b3ca7a..f59390a 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -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; } /** diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index ef26317..f650ed6 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -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 = { diff --git a/fs/hppfs/hppfs.c b/fs/hppfs/hppfs.c index fa2bd53..b8f24d3 100644 --- a/fs/hppfs/hppfs.c +++ b/fs/hppfs/hppfs.c @@ -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 = { diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 8a19889..3c7e799 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -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 = { diff --git a/fs/libfs.c b/fs/libfs.c index 72e4e01..0c83fde 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -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); diff --git a/fs/namei.c b/fs/namei.c index ab2bcbd..aeca448 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -502,7 +502,6 @@ struct nameidata { int last_type; unsigned depth; struct file *base; - char *saved_names[MAX_NESTED_LINKS + 1]; }; /* @@ -713,23 +712,11 @@ void nd_jump_link(struct nameidata *nd, struct path *path) nd->flags |= LOOKUP_JUMPED; } -void nd_set_link(struct nameidata *nd, char *path) -{ - nd->saved_names[nd->depth] = path; -} -EXPORT_SYMBOL(nd_set_link); - -char *nd_get_link(struct nameidata *nd) -{ - return nd->saved_names[nd->depth]; -} -EXPORT_SYMBOL(nd_get_link); - static inline void put_link(struct nameidata *nd, struct path *link, void *cookie) { struct inode *inode = link->dentry->d_inode; - if (inode->i_op->put_link) - inode->i_op->put_link(link->dentry, nd, cookie); + if (cookie && inode->i_op->put_link) + inode->i_op->put_link(link->dentry, cookie); path_put(link); } @@ -854,7 +841,7 @@ follow_link(struct path *link, struct nameidata *nd, void **p) { struct dentry *dentry = link->dentry; int error; - char *s; + const char *s; BUG_ON(nd->flags & LOOKUP_RCU); @@ -869,26 +856,20 @@ follow_link(struct path *link, struct nameidata *nd, void **p) current->total_link_count++; touch_atime(link); - nd_set_link(nd, NULL); error = security_inode_follow_link(dentry); if (error) goto out_put_nd_path; nd->last_type = LAST_BIND; - *p = dentry->d_inode->i_op->follow_link(dentry, nd); - error = PTR_ERR(*p); - if (IS_ERR(*p)) + *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; - s = nd_get_link(nd); if (s) { - if (unlikely(IS_ERR(s))) { - path_put(&nd->path); - put_link(nd, link, *p); - return PTR_ERR(s); - } if (*s == '/') { if (!nd->root.mnt) set_root(nd); @@ -906,7 +887,6 @@ follow_link(struct path *link, struct nameidata *nd, void **p) return error; out_put_nd_path: - *p = NULL; path_put(&nd->path); path_put(link); return error; @@ -4430,18 +4410,15 @@ EXPORT_SYMBOL(readlink_copy); */ int generic_readlink(struct dentry *dentry, char __user *buffer, int buflen) { - struct nameidata nd; void *cookie; + const char *link = dentry->d_inode->i_op->follow_link(dentry, &cookie, NULL); int res; - nd.depth = 0; - cookie = dentry->d_inode->i_op->follow_link(dentry, &nd); - if (IS_ERR(cookie)) - return PTR_ERR(cookie); - - res = readlink_copy(buffer, buflen, nd_get_link(&nd)); - if (dentry->d_inode->i_op->put_link) - dentry->d_inode->i_op->put_link(dentry, &nd, cookie); + 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); return res; } EXPORT_SYMBOL(generic_readlink); @@ -4473,22 +4450,21 @@ int page_readlink(struct dentry *dentry, char __user *buffer, int buflen) } EXPORT_SYMBOL(page_readlink); -void *page_follow_link_light(struct dentry *dentry, struct nameidata *nd) +const char *page_follow_link_light(struct dentry *dentry, void **cookie, struct nameidata *nd) { struct page *page = NULL; - nd_set_link(nd, page_getlink(dentry, &page)); - return page; + char *res = page_getlink(dentry, &page); + if (!IS_ERR(res)) + *cookie = page; + return res; } EXPORT_SYMBOL(page_follow_link_light); -void page_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie) +void page_put_link(struct dentry *dentry, void *cookie) { struct page *page = cookie; - - if (page) { - kunmap(page); - page_cache_release(page); - } + kunmap(page); + page_cache_release(page); } EXPORT_SYMBOL(page_put_link); diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c index 2d56200..c992b20 100644 --- a/fs/nfs/symlink.c +++ b/fs/nfs/symlink.c @@ -20,7 +20,6 @@ #include #include #include -#include /* 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); } /* diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 1b4b9c5e..235ad42 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -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); } diff --git a/fs/proc/base.c b/fs/proc/base.c index 093ca14..52652f8 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -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; diff --git a/fs/proc/inode.c b/fs/proc/inode.c index 8272aab..acd51d7 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -23,7 +23,6 @@ #include #include #include -#include #include @@ -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); } diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c index e512642..10d24dd 100644 --- a/fs/proc/namespaces.c +++ b/fs/proc/namespaces.c @@ -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; diff --git a/fs/proc/self.c b/fs/proc/self.c index 6195b4a..ad33394 100644 --- a/fs/proc/self.c +++ b/fs/proc/self.c @@ -1,5 +1,4 @@ #include -#include #include #include #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 = { diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c index a837199..85c96e0 100644 --- a/fs/proc/thread_self.c +++ b/fs/proc/thread_self.c @@ -1,5 +1,4 @@ #include -#include #include #include #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 = { diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index f4cd720..26c4dcb 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -41,7 +41,6 @@ #include #include -#include #include #include #include @@ -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 diff --git a/include/linux/fs.h b/include/linux/fs.h index 0ac758f..9ab9341 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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 *); diff --git a/include/linux/namei.h b/include/linux/namei.h index c899077..a5d5bed 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -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) { diff --git a/mm/shmem.c b/mm/shmem.c index 7f6e2f8..d1693dc 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -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 -- cgit v0.10.2 From 0a959df54b088d38371ebae4b1d7bc3112f6ef62 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 18:23:41 -0400 Subject: namei.c: separate the parts of follow_link() that find the link body 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... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index aeca448..a1ba556 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From d4dee48badbb7ccd740087321518abcc870eda65 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 30 Apr 2015 20:08:02 -0400 Subject: namei: don't bother with ->follow_link() if ->i_link is set with new calling conventions it's trivial Signed-off-by: Al Viro Conflicts: fs/namei.c diff --git a/fs/namei.c b/fs/namei.c index a1ba556..2ffb4af 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From caa8563443539895fc6654b996c68d5e8048b86e Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 17:52:47 -0400 Subject: namei: introduce nameidata->link 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 2ffb4af..06c3e4a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) -- cgit v0.10.2 From 896475d5bd2ee5d094239ff77801089ae25bcbc1 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 18:02:17 -0400 Subject: do_last: move path there from caller's stack frame We used to need it to feed to follow_link(). No more... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 06c3e4a..8582907 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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: -- cgit v0.10.2 From 5a460275ef3c14602040e5dc581a0d8771ce6b43 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 17 Apr 2015 23:44:45 -0400 Subject: namei: expand nested_symlink() in its only caller Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 8582907..7fcda91 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From 172a39a059bb8d84649bcad3e252fe416ad188a8 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 18:45:16 -0400 Subject: namei: expand the call of follow_link() in link_path_walk() ... and strip __always_inline from follow_link() - remaining callers don't need that. Now link_path_walk() recursion is a direct one. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 7fcda91..2354d4f 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 21fef2176e50bf785eaac920d949fcac5e2db124 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 19 Apr 2015 00:16:37 -0400 Subject: namei: move the calls of may_follow_link() into follow_link() All remaining callers of the former are preceded by the latter Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 2354d4f..f7659ee 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) -- cgit v0.10.2 From 95fa25d9f2e4898c62d2732f765c42eac0999285 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 13:46:57 -0400 Subject: namei: rename follow_link to trailing_symlink, move it down Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index f7659ee..d729ef7 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From b0c24c3bdf237182b2f043bfee68bf886b0c9ad3 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 19:10:36 -0400 Subject: link_path_walk: handle get_link() returning ERR_PTR() immediately 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index d729ef7..9937470 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) { -- cgit v0.10.2 From 12b0957800514535165f98efe7714f2a53bbfbb0 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 19:19:01 -0400 Subject: link_path_walk: don't bother with walk_component() after jumping link ... it does nothing if nd->last_type is LAST_BIND. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 9937470..ee083f9 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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--; -- cgit v0.10.2 From d40bcc09ab773d9e3cfbc4a45a6e73a4f2d04a8a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:03:03 -0400 Subject: link_path_walk: turn inner loop into explicit goto Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index ee083f9..6a0dd07 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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--; -- cgit v0.10.2 From 48c8b0c57176ba081c13abdca8334b9d88c4e4d6 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:09:08 -0400 Subject: link_path_walk: massage a bit more 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... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 6a0dd07..b7ba718 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From bb8603f8e166e49f02f865213feee7afddeac9b0 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:14:20 -0400 Subject: link_path_walk: get rid of duplication 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index b7ba718..9f45d33 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } } } -- cgit v0.10.2 From bdf6cbf17923c08dafca70f0231817de1f9d1f30 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:21:40 -0400 Subject: link_path_walk: final preparations to killing recursion 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 9f45d33..b469ce2 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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, -- cgit v0.10.2 From 32cd74685c75fada80c9444cde150434702aba56 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:30:49 -0400 Subject: link_path_walk: kill the recursion 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index b469ce2..9844bb2 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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, -- cgit v0.10.2 From 07681481b865b5dc100f71cc82facaeaa9e69e86 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:40:04 -0400 Subject: link_path_walk: split "return from recursive call" path Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 9844bb2..5b0edd3 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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, -- cgit v0.10.2 From 9e18f10a30e0c49520fbda39751fcae760445b96 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 18 Apr 2015 20:44:34 -0400 Subject: link_path_walk: cleanup - turn goto start; into continue; Deal with skipping leading slashes before what used to be the recursive call. That way we can get rid of that goto completely. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 5b0edd3..c105107 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) { -- cgit v0.10.2 From 697fc6ca666cdb2638211229c8fa3b81eb6f2f1a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 19:38:35 -0400 Subject: namei: move link/cookie pairs into nameidata 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index c105107..d4b238a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 1d8e03d359fd07edc884ffc45d42d60be9d9f098 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 19:48:56 -0400 Subject: namei: trim redundant arguments of trailing_symlink() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index d4b238a..8c4f2af 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From b9ff44293c64bf377e344c4cdc05d774b393cc6f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 20:19:23 -0400 Subject: namei: trim redundant arguments of fs/namei.c:put_link() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 8c4f2af..1ac3217 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 3b2e7f7539bdf5650bf5e13f18c883b7b008d03b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 19 Apr 2015 00:53:50 -0400 Subject: namei: trim the arguments of get_link() same story as the previous commit Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1ac3217..e5715a5 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) -- cgit v0.10.2 From 894bc8c4662ba9daceafe943a5ba0dd407da5cd3 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 07:16:16 -0400 Subject: namei: remove restrictions on nesting depth The only restriction is that on the total amount of symlinks crossed; how they are nested does not matter Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index e5715a5..1ae34cd 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } diff --git a/include/linux/namei.h b/include/linux/namei.h index a5d5bed..3a6cc96 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -11,6 +11,8 @@ struct nameidata; enum { MAX_NESTED_LINKS = 8 }; +#define MAXSYMLINKS 40 + /* * Type of the last component on LOOKUP_PARENT */ -- cgit v0.10.2 From 071bf501379560a7631b8d75ac80165bf82735c4 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 20:01:27 -0400 Subject: link_path_walk: nd->depth massage, part 1 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1ae34cd..e408f4d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From fd4620bbdf618998fd1a7df19e3443a698357e2b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 20:46:54 -0400 Subject: link_path_walk: nd->depth massage, part 2 collapse adjacent increment/decrement pairs. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index e408f4d..a403425 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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--; -- cgit v0.10.2 From da4e0be04d8816bbb9420c1da05ce09f502c5b75 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 20:52:15 -0400 Subject: link_path_walk: nd->depth massage, part 3 remove decrement/increment surrounding nd_alloc_stack(), adjust the test in it. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index a403425..3df4731 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From ef1a3e7b9634cc68740eb1280309ac5e460b95f9 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 20:54:27 -0400 Subject: link_path_walk: nd->depth massage, part 4 lift increment/decrement into link_path_walk() callers. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 3df4731..cf5009b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From f7df08ee05db41c6d2ec498703401197f6b68373 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 20:59:58 -0400 Subject: trailing_symlink: nd->depth massage, part 5 move increment of ->depth to the point where we'd discovered that get_link() has not returned an error, adjust exits accordingly. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index cf5009b..5753f46 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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) -- cgit v0.10.2 From 0fd889d59e12f860358866b6ddeee4bb7011aaad Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:02:40 -0400 Subject: get_link: nd->depth massage, part 6 make get_link() increment nd->depth on successful exit Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 5753f46..93b5f73 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 9ea57b72bf4f8b750e939769ae0aeaa41394ac01 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:04:07 -0400 Subject: trailing_symlink: nd->depth massage, part 7 move decrement of nd->depth on successful returns into the callers. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 93b5f73..9df1c7a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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: -- cgit v0.10.2 From 21c3003d36a8b6c54811bd87eb5f6fc830de9c82 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:06:24 -0400 Subject: put_link: nd->depth massage, part 8 all calls are preceded by decrement of nd->depth; move it into put_link() itself. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 9df1c7a..1f61955 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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: -- cgit v0.10.2 From dc7af8dc05bc46410b978dae14fda8414b65db30 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:16:35 -0400 Subject: link_path_walk: nd->depth massage, part 9 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(). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1f61955..bc6d67e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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) -- cgit v0.10.2 From 939724df56db970bdbaf8abcdc975c51482aba9b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:21:44 -0400 Subject: link_path_walk: nd->depth massage, part 10 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index bc6d67e..6febe25 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 8eff733a45c98f17f254a313859f3b3ed9fc12dc Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:27:36 -0400 Subject: link_path_walk: end of nd->depth massage 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). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 6febe25..d12b16c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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) -- cgit v0.10.2 From e269f2a73f92b40169c4229289587b901a00b244 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 3 May 2015 21:30:27 -0400 Subject: namei: we never need more than MAXSYMLINKS entries in nd->stack 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). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index d12b16c..b939f48 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 70291aecc6aa228c1b3bb36a5f3efdb0af636042 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 07:53:00 -0400 Subject: namei: lift (open-coded) terminate_walk() in follow_dotdot_rcu() into callers 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index b939f48..25cd935 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From f0a9ba7021ce286120b69926a17ba55c11707fae Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 07:59:30 -0400 Subject: lift terminate_walk() into callers of walk_component() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 25cd935..d99eaac 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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. */ -- cgit v0.10.2 From 1bc4b813e8e5bda23a31892712e04905f0c7ffba Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 08:15:36 -0400 Subject: namei: lift (open-coded) terminate_walk() into callers of get_link() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index d99eaac..db21e04 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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 == '/') { -- cgit v0.10.2 From 191d7f73e25c460858bd9467d528b48fdb8cef59 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 08:26:45 -0400 Subject: namei: take put_link() into {lookup,mountpoint,do}_last() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index db21e04..26466fb 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 1543972678e31bcfbc7f15170c12fb601c9f5c90 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 08:34:59 -0400 Subject: namei: have terminate_walk() do put_link() on everything left 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 26466fb..31da717 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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: -- cgit v0.10.2 From 8620c238edbf373aafcc4ee129e76c8e794c5214 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 08:58:35 -0400 Subject: link_path_walk: move the OK: inside the loop fewer labels that way; in particular, resuming after the end of nested symlink is straight-line. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 31da717..19e5c8a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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, -- cgit v0.10.2 From 4693a547cded42807ec468947b4d5287f2cb6aa9 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 17:47:11 -0400 Subject: namei: new calling conventions for walk_component() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 19e5c8a..1c9af925 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From d63ff28f0f693196c0e2c587024d272b6fdbfa6e Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 18:13:23 -0400 Subject: namei: make should_follow_link() store the link in nd->link ... if it decides to follow, that is. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1c9af925..1b4bc1b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) { -- cgit v0.10.2 From 626de99676e1e41fc70fc890776518ba936a58c6 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 4 May 2015 18:26:59 -0400 Subject: namei: move link count check and stack allocation into pick_link() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1b4bc1b..046a703 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 44163f30059e9869451999e77109a37abba8c968 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 17 Apr 2015 23:02:40 -0400 Subject: lustre: rip the private symlink nesting limit out Signed-off-by: Al Viro diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c index e488cb3..da6d9d1 100644 --- a/drivers/staging/lustre/lustre/llite/symlink.c +++ b/drivers/staging/lustre/lustre/llite/symlink.c @@ -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); -- cgit v0.10.2 From 756daf263ea53a8bfc89db26cb92e963953253a1 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:38 +1100 Subject: VFS: replace {, total_}link_count in task_struct with pointer to nameidata 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'. Suggested-by: Al Viro Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 046a703..699e093 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } diff --git a/include/linux/sched.h b/include/linux/sched.h index 26a2e61..f6c9b69 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -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; -- cgit v0.10.2 From 8402752ecf829f67527072151fa680292a519193 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 22 Apr 2015 10:30:08 -0400 Subject: namei: simplify the callers of follow_managed() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 699e093..b57400c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 6e77137b363b8d866ac29c5a0c95e953614fb2d8 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 2 May 2015 13:37:52 -0400 Subject: don't pass nameidata to ->follow_link() its only use is getting passed to nd_jump_link(), which can obtain it from current->nameidata Signed-off-by: Al Viro diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index 7fa6c4a..5b5b4f5 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -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); diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 1c6b03a..0dec8c8 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -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); diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c index da6d9d1..f3be3bf 100644 --- a/drivers/staging/lustre/lustre/llite/symlink.c +++ b/drivers/staging/lustre/lustre/llite/symlink.c @@ -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; diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 7cc70a3..271f51a 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -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); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index ae062ff..16658ed 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -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; diff --git a/fs/autofs4/symlink.c b/fs/autofs4/symlink.c index 9c6a077..da0c334 100644 --- a/fs/autofs4/symlink.c +++ b/fs/autofs4/symlink.c @@ -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); diff --git a/fs/befs/linuxvfs.c b/fs/befs/linuxvfs.c index 3a1aefb..46aedac 100644 --- a/fs/befs/linuxvfs.c +++ b/fs/befs/linuxvfs.c @@ -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)); diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h index 61012da..a782b22 100644 --- a/fs/cifs/cifsfs.h +++ b/fs/cifs/cifsfs.h @@ -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, diff --git a/fs/cifs/link.c b/fs/cifs/link.c index 4a439c2..546f86a 100644 --- a/fs/cifs/link.c +++ b/fs/cifs/link.c @@ -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; diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index fac8e85..0ace756 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -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; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index cdb9d6c..73d20ae 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -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); diff --git a/fs/ext4/symlink.c b/fs/ext4/symlink.c index afec475..ba5bd18 100644 --- a/fs/ext4/symlink.c +++ b/fs/ext4/symlink.c @@ -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; diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index d294793..cd05a7c 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -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); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index f9cb260..d5cdef8 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -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); diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index f59390a..3a1461d 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -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; diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index f650ed6..7b6ed7a 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -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) { diff --git a/fs/hppfs/hppfs.c b/fs/hppfs/hppfs.c index b8f24d3..15a774e 100644 --- a/fs/hppfs/hppfs.c +++ b/fs/hppfs/hppfs.c @@ -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) diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 3c7e799..366c5a1 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -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); diff --git a/fs/libfs.c b/fs/libfs.c index 0c83fde..c5f3373 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -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; } diff --git a/fs/namei.c b/fs/namei.c index b57400c..f311f03 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); diff --git a/fs/nfs/symlink.c b/fs/nfs/symlink.c index c992b20..b6de433 100644 --- a/fs/nfs/symlink.c +++ b/fs/nfs/symlink.c @@ -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; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 235ad42..9986833 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -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; diff --git a/fs/proc/base.c b/fs/proc/base.c index 52652f8..286a422 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -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); diff --git a/fs/proc/inode.c b/fs/proc/inode.c index acd51d7..eb35874 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -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))) diff --git a/fs/proc/namespaces.c b/fs/proc/namespaces.c index 10d24dd..f6e8354 100644 --- a/fs/proc/namespaces.c +++ b/fs/proc/namespaces.c @@ -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; diff --git a/fs/proc/self.c b/fs/proc/self.c index ad33394..113b8d0 100644 --- a/fs/proc/self.c +++ b/fs/proc/self.c @@ -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); diff --git a/fs/proc/thread_self.c b/fs/proc/thread_self.c index 85c96e0..947b0f4 100644 --- a/fs/proc/thread_self.c +++ b/fs/proc/thread_self.c @@ -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); diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 26c4dcb..7f51f39 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -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; diff --git a/include/linux/fs.h b/include/linux/fs.h index 9ab9341..ed7c9f2 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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 *); diff --git a/include/linux/namei.h b/include/linux/namei.h index 3a6cc96..d756304 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -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) { diff --git a/mm/shmem.c b/mm/shmem.c index d1693dc..e026822 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -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); -- cgit v0.10.2 From 6920a4405e6ff7813de4d5a9eb5e0b475e41a06b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 10 May 2015 10:43:46 -0400 Subject: namei: simplify failure exits in get_link() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index f311f03..678aeef 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From 4f697a5e173023a4f566339452a9d6f2cc7bd7dc Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 10 May 2015 10:50:41 -0400 Subject: namei: simpler treatment of symlinks with nothing other that / in the body 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 678aeef..c5eb77a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) { -- cgit v0.10.2 From fab51e8ab25e1ad661ef8da42077de78477fba83 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 10 May 2015 11:01:00 -0400 Subject: namei: take the treatment of absolute symlinks to get_link() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index c5eb77a..c6ff9da 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); } -- cgit v0.10.2 From e8bb73dfb0aad673a3b9650e9af0ba1739a4df01 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 16:28:42 -0400 Subject: namei: fold put_link() into the failure case of complete_walk() ... 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index c6ff9da..55283fe 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From cd179f4468acfda2b7e9e236dc37bba815996421 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 5 May 2015 10:52:35 -0400 Subject: namei: move bumping the refcount of link->mnt into pick_link() update the failure cleanup in may_follow_link() to match that. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 55283fe..05efcc0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From fec2fa24e84a75447341a20d36e808c8d913a81a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 6 May 2015 15:58:18 -0400 Subject: may_follow_link(): trim arguments Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 05efcc0..82cb1bc 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 1cf2665b5bdfc63185fb4a416bff54b14ad30c79 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 6 May 2015 16:01:56 -0400 Subject: namei: kill nd->link 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... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 82cb1bc..277ca86 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From ab10492345d1d629743c7e7d56532f4e5284c2c5 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sun, 10 May 2015 11:50:01 -0400 Subject: namei: take increment of nd->depth into pick_link() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 277ca86..6d4692d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From b5cd3397627ab7e200ee068ca39b9ca2d031f26d Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 16:38:31 -0400 Subject: namei: may_follow_link() - lift terminate_walk() on failures into caller Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 6d4692d..51e2214 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 8bcb77fabd7cbabcad49f58750be8683febee92b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 16:59:20 -0400 Subject: namei: split off filename_lookupat() with LOOKUP_PARENT new functions: filename_parentat() and path_parentat() resp. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 51e2214..6f95bc9 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 34a26b99b78148ff342801e732bf20014c291d03 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 11 May 2015 08:05:05 -0400 Subject: namei: get rid of nameidata->base we can do fdput() under rcu_read_lock() just fine; all we need to take care of is fetching nd->inode value first. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 6f95bc9..497d5f4 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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) -- cgit v0.10.2 From 368ee9ba565d6e13912791b05f3cc1dfa945a62a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 17:19:59 -0400 Subject: namei: path_init() calling conventions change * 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...) Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 497d5f4..06c7120 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 3bdba28b72f5d2e7f3df031b04008b9a6fbdc775 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 17:37:07 -0400 Subject: namei: lift link_path_walk() call out of trailing_symlink() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 06c7120..46f4266 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) { -- cgit v0.10.2 From deb106c632d73c96b6b2b5ca71bacb8aef38fc7b Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 18:05:21 -0400 Subject: namei: lift terminate_walk() all the way up Lift it from link_path_walk(), trailing_symlink(), lookup_last(), mountpoint_last(), complete_walk() and do_last(). A _lot_ of those suckers merge. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 46f4266..27c3859 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) { -- cgit v0.10.2 From 3595e2346cd91c223efddc79353fe7ced81f21bf Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 May 2015 16:54:45 -0400 Subject: link_path_walk: use explicit returns for failure exits Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 27c3859..756e150 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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, -- cgit v0.10.2 From 6e9918b7b3924f7a4cc3faa73c6e15d709ac239f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 5 May 2015 09:26:05 -0400 Subject: namei: explicitly pass seq number to unlazy_walk() when dentry != NULL Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 756e150..97315df 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From 254cf582127b200ea6d4e55c9b4965485c3fe4c8 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 5 May 2015 09:40:46 -0400 Subject: namei: don't mangle nd->seq in lookup_fast() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 97315df..e321450 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); -- cgit v0.10.2 From 237d8b327abb428ff39b49e32ce07fdac468e32f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 7 May 2015 09:21:14 -0400 Subject: namei: store inode in nd->stack[] Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index e321450..366b0f3 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From 63afdfc781e10c6b3ec38274e6163422876caa9a Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 6 May 2015 15:59:00 +0100 Subject: VFS: Handle lower layer dentry/inode in pathwalk Make use of d_backing_inode() in pathwalk to gain access to an inode or dentry that's on a lower layer. Signed-off-by: David Howells diff --git a/fs/namei.c b/fs/namei.c index 366b0f3..bcacb31 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); diff --git a/fs/open.c b/fs/open.c index 98e5a52..e0250bd 100644 --- a/fs/open.c +++ b/fs/open.c @@ -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)) { /* -- cgit v0.10.2 From 181548c05147154605711f3b1cf863267b5b8f7a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 7 May 2015 19:54:34 -0400 Subject: namei: pick_link() callers already have inode no need to refetch (and once we move unlazy out of there, recheck ->d_seq). Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index bcacb31..33b655d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 7b20ea2579238f5e0da4bc93276c1b63c960c9ef Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:39 +1100 Subject: security/selinux: pass 'flags' arg to avc_audit() and avc_has_perm_flags() 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. Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 3c17dda..0b122b1 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -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; diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8016229..d56a829 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -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; } diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index ddf8eec..5973c32 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -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); -- cgit v0.10.2 From bda0be7ad994812960e9f8f2d2757f72cb4a96cb Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:39 +1100 Subject: security: make inode_follow_link RCU-walk aware 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. Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 33b655d..0fa7af2 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; diff --git a/include/linux/security.h b/include/linux/security.h index 62a6620..52febde 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -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; } diff --git a/security/capability.c b/security/capability.c index fae99db..7d3f38f 100644 --- a/security/capability.c +++ b/security/capability.c @@ -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; } diff --git a/security/security.c b/security/security.c index d7c30b0..04c8fec 100644 --- a/security/security.c +++ b/security/security.c @@ -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) diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d56a829..ffa5a64 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -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, -- cgit v0.10.2 From 5f2c4179e129bdc47870a81a65d0aff85aa18293 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 7 May 2015 11:14:26 -0400 Subject: switch ->put_link() from dentry to inode only one instance looks at that argument at all; that sole exception wants inode rather than dentry. Signed-off-by: Al Viro diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index 5b5b4f5..6a34a0f 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -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); diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 0dec8c8..542d935 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -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 *); diff --git a/drivers/staging/lustre/lustre/llite/symlink.c b/drivers/staging/lustre/lustre/llite/symlink.c index f3be3bf..69b2036 100644 --- a/drivers/staging/lustre/lustre/llite/symlink.c +++ b/drivers/staging/lustre/lustre/llite/symlink.c @@ -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); } diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index 0ace756..bc464c2 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -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); } diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index cd05a7c..71765d0 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -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; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index d5cdef8..9e704c1 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -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); } diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 7b6ed7a..4a437ab 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -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); } diff --git a/fs/hppfs/hppfs.c b/fs/hppfs/hppfs.c index 15a774e..2867837 100644 --- a/fs/hppfs/hppfs.c +++ b/fs/hppfs/hppfs.c @@ -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 = { diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index 366c5a1..f6aa2e5 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -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); } diff --git a/fs/libfs.c b/fs/libfs.c index c5f3373..01c337b 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -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); } diff --git a/fs/namei.c b/fs/namei.c index 0fa7af2..4303404 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 9986833..308379b 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -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); } diff --git a/fs/proc/inode.c b/fs/proc/inode.c index eb35874..afe232b 100644 --- a/fs/proc/inode.c +++ b/fs/proc/inode.c @@ -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); } diff --git a/include/linux/fs.h b/include/linux/fs.h index ed7c9f2..f21e332 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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); diff --git a/mm/shmem.c b/mm/shmem.c index e026822..a59087e 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -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); -- cgit v0.10.2 From ecc087ff14352aed52b8e775b4511e7f9cfc64ec Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 7 May 2015 11:19:14 -0400 Subject: new helper: free_page_put_link() similar to kfree_put_link() Signed-off-by: Al Viro diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index bc464c2..ec5c832 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -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, }; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 9e704c1..5e2e087 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -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, diff --git a/fs/kernfs/symlink.c b/fs/kernfs/symlink.c index f6aa2e5..db27252 100644 --- a/fs/kernfs/symlink.c +++ b/fs/kernfs/symlink.c @@ -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, diff --git a/fs/libfs.c b/fs/libfs.c index 01c337b..65e1fec 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -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. diff --git a/include/linux/fs.h b/include/linux/fs.h index f21e332..8f73851 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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); -- cgit v0.10.2 From 6548fae2eca6b66c7257af6663fdbdf5a50745fd Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 7 May 2015 20:32:22 -0400 Subject: namei: make put_link() RCU-safe 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 4303404..998c3c2 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; -- cgit v0.10.2 From 31956502dd2c9432523d01373a9dc0e5931cfa1c Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 7 May 2015 20:37:40 -0400 Subject: namei: make may_follow_link() safe in RCU mode 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 998c3c2..20bf494 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From 294d71ff2f020aa2ef7057a7bd10cf2ec71b5ee3 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 11:43:53 -0400 Subject: new helper: __legitimize_mnt() 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 Signed-off-by: Al Viro diff --git a/fs/mount.h b/fs/mount.h index 6a61c2b..b5b8082 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -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); diff --git a/fs/namespace.c b/fs/namespace.c index 1b9e111..9c1c43d 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -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; } -- cgit v0.10.2 From 0450b2d120ed9e6d4ac7a6eade0ad116f69b88f7 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Fri, 8 May 2015 13:23:53 -0400 Subject: namei: store seq numbers in nd->stack[] we'll need them for unlazy_walk() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 20bf494..92bf031 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; } -- cgit v0.10.2 From 7973387a2f629c6ed0283920da44c6539c69aca0 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 May 2015 12:55:43 -0400 Subject: namei: make unlazy_walk and terminate_walk handle nd->stack, add unlazy_link 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 92bf031..a64df4c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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,24 @@ 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); + nd->depth = 0; + nd->flags &= ~LOOKUP_RCU; + nd->path.mnt = NULL; + nd->path.dentry = NULL; + if (!(nd->flags & LOOKUP_ROOT)) + nd->root.mnt = NULL; + rcu_read_unlock(); + } 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 +1614,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 +1624,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); -- cgit v0.10.2 From bc40aee053be1c2c443e812034f41e1a3cf34752 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 May 2015 13:04:24 -0400 Subject: namei: don't unlazy until get_link() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index a64df4c..47b2008 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; @@ -957,8 +966,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); @@ -1623,17 +1634,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++; -- cgit v0.10.2 From 8fa9dd24667f2d6997ec21341019657342859d31 Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:40 +1100 Subject: VFS/namei: make the use of touch_atime() in get_link() RCU-safe. 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. Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/fs/inode.c b/fs/inode.c index 952fb48..e8d6268 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -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: diff --git a/fs/namei.c b/fs/namei.c index 47b2008..d9f77ff 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -966,13 +966,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); + cond_resched(); + } 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); + 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); diff --git a/include/linux/fs.h b/include/linux/fs.h index 8f73851..1426c43 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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) { -- cgit v0.10.2 From 8c1b456689ac0b27e8e16b35190e89a02fd1f121 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 May 2015 18:15:21 -0400 Subject: enable passing fast relative symlinks without dropping out of RCU mode Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index d9f77ff..bf46e10 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -975,11 +975,6 @@ const char *get_link(struct nameidata *nd) touch_atime(&last->link); } - 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); if (unlikely(error)) @@ -988,6 +983,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; @@ -995,6 +994,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); -- cgit v0.10.2 From 8f47a0167c567de4ef552e26101b4f54a9b8ad48 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 May 2015 19:02:01 -0400 Subject: namei: handle absolute symlinks without dropping out of RCU mode Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index bf46e10..a5ed0d0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; @@ -788,14 +788,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) @@ -995,15 +995,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 == '/')) ; @@ -1979,6 +1987,7 @@ static const char *path_init(int dfd, const struct filename *name, if (flags & LOOKUP_RCU) { rcu_read_lock(); nd->seq = __read_seqcount_begin(&nd->path.dentry->d_seq); + nd->root_seq = nd->seq; nd->m_seq = read_seqbegin(&mount_lock); } else { path_get(&nd->path); -- cgit v0.10.2 From 203bc643db59e2538e9a3f19be1636cdfd2bb2db Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 11 May 2015 08:29:30 -0400 Subject: update Documentation/filesystems/ regarding the follow_link/put_link changes Signed-off-by: Al Viro diff --git a/Documentation/filesystems/porting b/Documentation/filesystems/porting index e69274d..3eae250 100644 --- a/Documentation/filesystems/porting +++ b/Documentation/filesystems/porting @@ -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. diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 542d935..b403b29e 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -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. -- cgit v0.10.2 From 5a8d87e8ed1d6aabdb865fea31e337b2c627dbe0 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 00:10:18 -0400 Subject: namei: unlazy_walk() doesn't need to mess with current->fs anymore now that we have ->root_seq, legitimize_path(&nd->root, nd->root_seq) will do just fine... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index a5ed0d0..0cccb24 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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(); -- cgit v0.10.2 From 539fcc0109346fca363257aa75c5e5a3916bf124 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 23 Feb 2015 00:20:40 -0500 Subject: lustre: kill unused macro (LOOKUP_CONTINUE) Signed-off-by: Al Viro diff --git a/drivers/staging/lustre/lustre/llite/llite_internal.h b/drivers/staging/lustre/lustre/llite/llite_internal.h index 5f918e3..528af90 100644 --- a/drivers/staging/lustre/lustre/llite/llite_internal.h +++ b/drivers/staging/lustre/lustre/llite/llite_internal.h @@ -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 -- cgit v0.10.2 From 322105e2fe20b51587a328e7121dc04ea361b083 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 08:29:13 -0400 Subject: lustre: kill unused helper Signed-off-by: Al Viro diff --git a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h index 3925db1..513c81f 100644 --- a/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h +++ b/drivers/staging/lustre/lustre/include/linux/lustre_compat25.h @@ -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 -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 # define ll_umode_t umode_t -- cgit v0.10.2 From 89076bc31950eee576ecc06460c23466e2d50939 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 08:29:38 -0400 Subject: get rid of assorted nameidata-related debris pointless forward declarations, stale comments Signed-off-by: Al Viro diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 271f51a..510040b 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -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) diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 16658ed..09e44337 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -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 * diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 73d20ae..3c4db11 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -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. diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c index 0f35b80..443abec 100644 --- a/fs/ntfs/namei.c +++ b/fs/ntfs/namei.c @@ -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 diff --git a/include/linux/fs.h b/include/linux/fs.h index 1426c43..b577e80 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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; diff --git a/include/linux/namei.h b/include/linux/namei.h index d756304..1208e48 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -7,7 +7,6 @@ #include struct vfsmount; -struct nameidata; enum { MAX_NESTED_LINKS = 8 }; diff --git a/include/linux/sched.h b/include/linux/sched.h index f6c9b69..a1158c9 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -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) -- cgit v0.10.2 From 99ff6cf0e67ada025ba8054a055862383355ec0e Mon Sep 17 00:00:00 2001 From: NeilBrown Date: Mon, 23 Mar 2015 13:37:38 +1100 Subject: Documentation: remove outdated information from automount-support.txt 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. Signed-off-by: NeilBrown Signed-off-by: Al Viro diff --git a/Documentation/filesystems/automount-support.txt b/Documentation/filesystems/automount-support.txt index 7cac200..7eb762e 100644 --- a/Documentation/filesystems/automount-support.txt +++ b/Documentation/filesystems/automount-support.txt @@ -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. -- cgit v0.10.2 From aed434ada68573549d9f5106cde26b94c48e51c2 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 12:22:47 -0400 Subject: namei: be careful with mountpoint crossings in follow_dotdot_rcu() 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. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 0cccb24..d5f4568 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1018,21 +1018,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 * @@ -1289,10 +1274,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; @@ -1300,34 +1283,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; } /* -- cgit v0.10.2 From 18d8c860112f96b62899819f933912833398cd58 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 16:32:34 -0400 Subject: namei: uninline set_root{,_rcu}() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index d5f4568..177934c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -777,12 +777,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; -- cgit v0.10.2 From 625b6d105446e82e95ddb28d6fd3d25f84f6c479 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 16:36:12 -0400 Subject: namei: pass the struct path to store the result down into path_lookupat() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 177934c..f4c9c07 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2086,8 +2086,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; @@ -2108,27 +2108,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; } @@ -2209,10 +2213,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; } @@ -2241,9 +2243,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; @@ -2311,10 +2311,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; } @@ -3261,44 +3259,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); @@ -3311,9 +3307,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; } -- cgit v0.10.2 From e4bd1c1a95409131ad3948accab98f0d6b8093f0 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 16:40:39 -0400 Subject: namei: move putname() call into filename_lookup() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index f4c9c07..847c61a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2134,6 +2134,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; } @@ -2210,13 +2211,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); @@ -2232,21 +2229,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); @@ -2306,15 +2301,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, -- cgit v0.10.2 From 9ad1aaa61522ffba001a5599623182fe23083a94 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 16:44:39 -0400 Subject: namei: shift nameidata inside filename_lookup() pass root instead; non-NULL => copy to nd.root and set LOOKUP_ROOT in flags Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 847c61a..2999404 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2119,17 +2119,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); @@ -2209,11 +2212,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); @@ -2229,7 +2231,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); @@ -2237,11 +2239,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); @@ -2299,14 +2298,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, -- cgit v0.10.2 From abc9f5beb1ca97b5157914c83c85d67a6b665d74 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 16:53:42 -0400 Subject: namei: make filename_lookup() reject ERR_PTR() passed as name makes for much easier life in callers Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 2999404..a9c593e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2122,7 +2122,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; @@ -2212,10 +2215,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); @@ -2232,15 +2233,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); @@ -2298,13 +2293,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, -- cgit v0.10.2 From 391172c46e6f9d5d03855ff3ae5720d9826f3b59 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Sat, 9 May 2015 11:19:16 -0400 Subject: namei: shift nameidata down into filename_parentat() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index a9c593e..593cf3b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2146,7 +2146,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; @@ -2155,26 +2156,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; } @@ -2183,31 +2192,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; @@ -2317,7 +2325,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; @@ -2327,15 +2334,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; } @@ -3394,7 +3397,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); @@ -3405,7 +3409,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); @@ -3413,18 +3417,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; @@ -3438,7 +3441,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; } @@ -3446,17 +3449,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; } -- cgit v0.10.2 From 181c37b6e4c1bdb061ed0d884c54f9a6e6cdac89 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 17:21:25 -0400 Subject: namei: saner calling conventions for filename_create() a) make it reject ERR_PTR() for name b) make it putname(name) upon return in all other cases. seriously simplifies the callers... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 593cf3b..628b6eb 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3403,6 +3403,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! @@ -3410,8 +3412,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? @@ -3449,6 +3453,7 @@ static struct dentry *filename_create(int dfd, struct filename *name, error = err2; goto fail; } + putname(name); return dentry; fail: dput(dentry); @@ -3459,20 +3464,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); @@ -3488,13 +3488,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); -- cgit v0.10.2 From 5c31b6cedb675199bfd18c08a1223c9b39daedd7 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 17:32:54 -0400 Subject: namei: saner calling conventions for filename_parentat() 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 Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 628b6eb..484b73c 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2166,13 +2166,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); @@ -2183,32 +2186,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); @@ -2216,7 +2217,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; } @@ -2325,21 +2325,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); } /** @@ -3403,25 +3391,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 */ -- cgit v0.10.2 From 102b8af266fbce07b7f8d2396bf2286ba80c93bd Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 17:35:52 -0400 Subject: namei: fold path_cleanup() into terminate_walk() they are always called next to each other; moreover, terminate_walk() is more symmetrical that way. Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 484b73c..14aaf00 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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)) @@ -2051,14 +2055,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; @@ -2114,7 +2110,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; } @@ -2162,7 +2157,6 @@ static int path_parentat(int dfd, const struct filename *name, nd->path.dentry = NULL; } terminate_walk(nd); - path_cleanup(nd); return err; } @@ -2446,7 +2440,6 @@ path_mountpoint(int dfd, const struct filename *name, struct path *path, } } terminate_walk(nd); - path_cleanup(nd); return err; } @@ -3318,7 +3311,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); -- cgit v0.10.2 From c8a53ee5ee4fcab5ee252f61a69609b65e5e74b8 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 18:43:07 -0400 Subject: namei: stash dfd and name into nameidata fewer arguments to pass around... Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 14aaf00..970456f 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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; @@ -1954,11 +1959,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; @@ -1999,7 +2003,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; @@ -2016,7 +2020,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) @@ -2082,10 +2086,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)) @@ -2120,17 +2123,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); @@ -2140,11 +2142,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); @@ -2169,13 +2170,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; @@ -2415,19 +2415,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); @@ -2451,12 +2449,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); @@ -3217,8 +3215,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) { @@ -3226,8 +3223,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); @@ -3252,7 +3248,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) @@ -3277,8 +3273,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; @@ -3292,17 +3288,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)) { @@ -3331,15 +3327,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; } @@ -3362,12 +3358,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; -- cgit v0.10.2 From 76ae2a5ab13c4413dd403c940c16bd0b1e609346 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 12 May 2015 18:44:32 -0400 Subject: namei: trim do_last() arguments now that struct filename is stashed in nameidata we have no need to pass it in Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 970456f..8d45304 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2992,7 +2992,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; @@ -3039,7 +3039,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; @@ -3068,7 +3068,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; } @@ -3085,7 +3085,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 @@ -3143,7 +3143,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; @@ -3298,7 +3298,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)) { -- cgit v0.10.2 From a2ec4a2d5cf35972b0e4dd84f66142bc76bb6cb2 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 13 May 2015 06:57:49 -0400 Subject: inline user_path_parent() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 8d45304..53f832a 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2312,7 +2312,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, -- cgit v0.10.2 From 520ae6874726dbfdf52c779d387b584d95cfed7f Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 13 May 2015 07:00:28 -0400 Subject: inline user_path_create() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 53f832a..22814d0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3457,7 +3457,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); -- cgit v0.10.2 From 9883d1855ecfafc60045a93abcee6c42e0a5f571 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 13 May 2015 07:28:08 -0400 Subject: namei: move saved_nd pointer into struct nameidata 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() Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 22814d0..1a117c0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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) @@ -2120,14 +2121,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); @@ -2136,7 +2137,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; } @@ -2166,11 +2167,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); @@ -2184,7 +2185,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; } @@ -2445,11 +2446,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); @@ -2457,7 +2458,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; } @@ -3327,23 +3328,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; @@ -3358,13 +3360,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; } -- cgit v0.10.2 From b853a16176cf3e02c57e215743015614152c2428 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Wed, 13 May 2015 09:12:02 -0400 Subject: turn user_{path_at,path,lpath,path_dir}() into static inlines Signed-off-by: Al Viro diff --git a/fs/namei.c b/fs/namei.c index 1a117c0..2dad0ea 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2299,13 +2299,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 diff --git a/include/linux/namei.h b/include/linux/namei.h index 1208e48..d8c6334 100644 --- a/include/linux/namei.h +++ b/include/linux/namei.h @@ -1,12 +1,10 @@ #ifndef _LINUX_NAMEI_H #define _LINUX_NAMEI_H -#include -#include -#include +#include #include - -struct vfsmount; +#include +#include 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 *); -- cgit v0.10.2