From: Erez Zadok <ezk@xxxxxxxxxxxxx> Update unionfs_interpose to handle spliced dentries, which is important for NFS exporting. Signed-off-by: Erez Zadok <ezk@xxxxxxxxxxxxx> Signed-off-by: Josef 'Jeff' Sipek <jsipek@xxxxxxxxxxxxx> --- fs/unionfs/inode.c | 40 +++++++++++---- fs/unionfs/lookup.c | 22 +++++++- fs/unionfs/main.c | 138 +++++++++++++++++++++++++++++++++------------------ fs/unionfs/union.h | 4 +- 4 files changed, 141 insertions(+), 63 deletions(-) diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c index 6cec564..a219a40 100644 --- a/fs/unionfs/inode.c +++ b/fs/unionfs/inode.c @@ -152,7 +152,12 @@ static int unionfs_create(struct inode *parent, struct dentry *dentry, wh_dentry); wh_dentry = NULL; - err = unionfs_interpose(dentry, parent->i_sb, 0); + /* + * Only INTERPOSE_LOOKUP can return a value other + * than 0 on err. + */ + err = PTR_ERR(unionfs_interpose(dentry, + parent->i_sb, 0)); goto out; } } @@ -194,11 +199,14 @@ static int unionfs_create(struct inode *parent, struct dentry *dentry, if (!IS_COPYUP_ERR(err)) break; } else { - err = unionfs_interpose(dentry, parent->i_sb, 0); + /* + * Only INTERPOSE_LOOKUP can return a value other + * than 0 on err. + */ + err = PTR_ERR(unionfs_interpose(dentry, + parent->i_sb, 0)); if (!err) { - fsstack_copy_attr_times(parent, - lower_parent_dentry-> - d_inode); + unionfs_copy_attr_times(parent); fsstack_copy_inode_size(parent, lower_parent_dentry-> d_inode); @@ -527,7 +535,12 @@ static int unionfs_symlink(struct inode *dir, struct dentry *dentry, if (!IS_COPYUP_ERR(err)) break; } else { - err = unionfs_interpose(dentry, dir->i_sb, 0); + /* + * Only INTERPOSE_LOOKUP can return a value other + * than 0 on err. + */ + err = PTR_ERR(unionfs_interpose(dentry, + dir->i_sb, 0)); if (!err) { fsstack_copy_attr_times(dir, lower_dir_dentry-> @@ -664,10 +677,13 @@ static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode) } set_dbend(dentry, bindex); - err = unionfs_interpose(dentry, parent->i_sb, 0); + /* + * Only INTERPOSE_LOOKUP can return a value other than 0 on + * err. + */ + err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0)); if (!err) { - fsstack_copy_attr_times(parent, - lower_parent_dentry->d_inode); + unionfs_copy_attr_times(parent); fsstack_copy_inode_size(parent, lower_parent_dentry->d_inode); @@ -795,7 +811,11 @@ static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode, break; } - err = unionfs_interpose(dentry, dir->i_sb, 0); + /* + * Only INTERPOSE_LOOKUP can return a value other than 0 on + * err. + */ + err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0)); if (!err) { fsstack_copy_attr_times(dir, lower_parent_dentry->d_inode); diff --git a/fs/unionfs/lookup.c b/fs/unionfs/lookup.c index 61ee50d..e4e8470 100644 --- a/fs/unionfs/lookup.c +++ b/fs/unionfs/lookup.c @@ -72,7 +72,12 @@ out: return err; } -/* main (and complex) driver function for Unionfs's lookup */ +/* + * Main (and complex) driver function for Unionfs's lookup + * + * Returns: NULL (ok), ERR_PTR if an error occurred, or a non-null non-error + * PTR if d_splice returned a different dentry. + */ struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd, int lookupmode) { @@ -81,6 +86,7 @@ struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct dentry *wh_lower_dentry = NULL; struct dentry *lower_dir_dentry = NULL; struct dentry *parent_dentry = NULL; + struct dentry *d_interposed = NULL; int bindex, bstart, bend, bopaque; int dentry_count = 0; /* Number of positive dentries. */ int first_dentry_offset = -1; /* -1 is uninitialized */ @@ -90,7 +96,6 @@ struct dentry *unionfs_lookup_backend(struct dentry *dentry, int locked_parent = 0; int locked_child = 0; int allocated_new_info = 0; - int opaque; char *whname = NULL; const char *name; @@ -370,7 +375,16 @@ out_positive: bend = dbend(dentry); } - err = unionfs_interpose(dentry, dentry->d_sb, lookupmode); + /* + * Interpose can return a dentry if d_splice returned a different + * dentry. + */ + d_interposed = unionfs_interpose(dentry, dentry->d_sb, lookupmode); + if (IS_ERR(d_interposed)) + err = PTR_ERR(d_interposed); + else if (d_interposed) + dentry = d_interposed; + if (err) goto out_drop; @@ -406,6 +420,8 @@ out: dput(parent_dentry); if (locked_child || (err && allocated_new_info)) unionfs_unlock_dentry(dentry); + if (!err && d_interposed) + return d_interposed; return ERR_PTR(err); } diff --git a/fs/unionfs/main.c b/fs/unionfs/main.c index 689a8fa..bc5c105 100644 --- a/fs/unionfs/main.c +++ b/fs/unionfs/main.c @@ -20,20 +20,73 @@ #include <linux/module.h> #include <linux/moduleparam.h> +static void unionfs_fill_inode(struct dentry *dentry, + struct inode *inode) +{ + struct inode *lower_inode; + struct dentry *lower_dentry; + int bindex, bstart, bend; + + bstart = dbstart(dentry); + bend = dbend(dentry); + + for (bindex = bstart; bindex <= bend; bindex++) { + lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); + if (!lower_dentry) { + unionfs_set_lower_inode_idx(inode, bindex, NULL); + continue; + } + + /* Initialize the lower inode to the new lower inode. */ + if (!lower_dentry->d_inode) + continue; + + unionfs_set_lower_inode_idx(inode, bindex, + igrab(lower_dentry->d_inode)); + } + + ibstart(inode) = dbstart(dentry); + ibend(inode) = dbend(dentry); + + /* Use attributes from the first branch. */ + lower_inode = unionfs_lower_inode(inode); + + /* Use different set of inode ops for symlinks & directories */ + if (S_ISLNK(lower_inode->i_mode)) + inode->i_op = &unionfs_symlink_iops; + else if (S_ISDIR(lower_inode->i_mode)) + inode->i_op = &unionfs_dir_iops; + + /* Use different set of file ops for directories */ + if (S_ISDIR(lower_inode->i_mode)) + inode->i_fop = &unionfs_dir_fops; + + /* properly initialize special inodes */ + if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || + S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) + init_special_inode(inode, lower_inode->i_mode, + lower_inode->i_rdev); + + /* all well, copy inode attributes */ + unionfs_copy_attr_all(inode, lower_inode); + fsstack_copy_inode_size(inode, lower_inode); +} + /* * Connect a unionfs inode dentry/inode with several lower ones. This is * the classic stackable file system "vnode interposition" action. * * @sb: unionfs's super_block */ -int unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) +struct dentry *unionfs_interpose(struct dentry *dentry, struct super_block *sb, + int flag) { - struct inode *lower_inode; - struct dentry *lower_dentry; int err = 0; struct inode *inode; int is_negative_dentry = 1; int bindex, bstart, bend; + int need_fill_inode = 1; + struct dentry *spliced = NULL; verify_locked(dentry); @@ -80,51 +133,12 @@ int unionfs_interpose(struct dentry *dentry, struct super_block *sb, int flag) err = -EACCES; goto out; } - if (atomic_read(&inode->i_count) > 1) goto skip; } - for (bindex = bstart; bindex <= bend; bindex++) { - lower_dentry = unionfs_lower_dentry_idx(dentry, bindex); - if (!lower_dentry) { - unionfs_set_lower_inode_idx(inode, bindex, NULL); - continue; - } - - /* Initialize the lower inode to the new lower inode. */ - if (!lower_dentry->d_inode) - continue; - - unionfs_set_lower_inode_idx(inode, bindex, - igrab(lower_dentry->d_inode)); - } - - ibstart(inode) = dbstart(dentry); - ibend(inode) = dbend(dentry); - - /* Use attributes from the first branch. */ - lower_inode = unionfs_lower_inode(inode); - - /* Use different set of inode ops for symlinks & directories */ - if (S_ISLNK(lower_inode->i_mode)) - inode->i_op = &unionfs_symlink_iops; - else if (S_ISDIR(lower_inode->i_mode)) - inode->i_op = &unionfs_dir_iops; - - /* Use different set of file ops for directories */ - if (S_ISDIR(lower_inode->i_mode)) - inode->i_fop = &unionfs_dir_fops; - - /* properly initialize special inodes */ - if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) || - S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode)) - init_special_inode(inode, lower_inode->i_mode, - lower_inode->i_rdev); - - /* all well, copy inode attributes */ - unionfs_copy_attr_all(inode, lower_inode); - fsstack_copy_inode_size(inode, lower_inode); + need_fill_inode = 0; + unionfs_fill_inode(dentry, inode); skip: /* only (our) lookup wants to do a d_add */ @@ -134,7 +148,28 @@ skip: d_instantiate(dentry, inode); break; case INTERPOSE_LOOKUP: - err = PTR_ERR(d_splice_alias(inode, dentry)); + spliced = d_splice_alias(inode, dentry); + if (IS_ERR(spliced)) + err = PTR_ERR(spliced); + else if (spliced && spliced != dentry) { + /* + * d_splice can return a dentry if it was + * disconnected and had to be moved. We must ensure + * that the private data of the new dentry is + * correct and that the inode info was filled + * properly. Finally we must return this new + * dentry. + */ + spliced->d_op = &unionfs_dops; + spliced->d_fsdata = dentry->d_fsdata; + dentry->d_fsdata = NULL; + dentry = spliced; + if (need_fill_inode) { + need_fill_inode = 0; + unionfs_fill_inode(dentry, inode); + } + goto out_spliced; + } break; case INTERPOSE_REVAL: /* Do nothing. */ @@ -143,9 +178,13 @@ skip: printk(KERN_ERR "unionfs: invalid interpose flag passed!"); BUG(); } + goto out; +out_spliced: + if (!err) + return spliced; out: - return err; + return ERR_PTR(err); } /* like interpose above, but for an already existing dentry */ @@ -623,8 +662,11 @@ static int unionfs_read_super(struct super_block *sb, void *raw_data, /* Set the generation number to one, since this is for the mount. */ atomic_set(&UNIONFS_D(sb->s_root)->generation, 1); - /* call interpose to create the upper level inode */ - err = unionfs_interpose(sb->s_root, sb, 0); + /* + * Call interpose to create the upper level inode. Only + * INTERPOSE_LOOKUP can return a value other than 0 on err. + */ + err = PTR_ERR(unionfs_interpose(sb->s_root, sb, 0)); unionfs_unlock_dentry(sb->s_root); if (!err) goto out; diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h index ec33155..0fa8ae0 100644 --- a/fs/unionfs/union.h +++ b/fs/unionfs/union.h @@ -333,8 +333,8 @@ extern int is_newer_lower(const struct dentry *dentry); #define INTERPOSE_REVAL_NEG 3 #define INTERPOSE_PARTIAL 4 -extern int unionfs_interpose(struct dentry *this_dentry, - struct super_block *sb, int flag); +extern struct dentry *unionfs_interpose(struct dentry *this_dentry, + struct super_block *sb, int flag); #ifdef CONFIG_UNION_FS_XATTR /* Extended attribute functions. */ -- 1.5.2.2.238.g7cbf2f2 - To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html