From f681eb1d5c02c9e79775e10363057d034c720efc Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 5 Jun 2017 22:44:49 +0300 Subject: ovl: fix nlink leak in ovl_rename() This patch fixes an overlay inode nlink leak in the case where ovl_rename() renames over a non-dir. This is not so critical, because overlay inode doesn't rely on nlink dropping to zero for inode deletion. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'fs') diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a63a71656e9b..fcfa7de12ad5 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -1046,6 +1046,13 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (cleanup_whiteout) ovl_cleanup(old_upperdir->d_inode, newdentry); + if (overwrite && d_inode(new)) { + if (new_is_dir) + clear_nlink(d_inode(new)); + else + drop_nlink(d_inode(new)); + } + ovl_dentry_version_inc(old->d_parent); ovl_dentry_version_inc(new->d_parent); -- cgit From 13cf199d0088b77ab08a9594df2e73e775317ed2 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Mon, 12 Jun 2017 09:54:40 +0300 Subject: ovl: allocate an ovl_inode struct We need some more space to store overlay inode data in memory, so allocate overlay inodes from a slab of struct ovl_inode. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/ovl_entry.h | 9 ++++++++ fs/overlayfs/super.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 34bc4a9f5c61..553727df886c 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -58,3 +58,12 @@ static inline struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe) { return lockless_dereference(oe->__upperdentry); } + +struct ovl_inode { + struct inode vfs_inode; +}; + +static inline struct ovl_inode *OVL_I(struct inode *inode) +{ + return container_of(inode, struct ovl_inode, vfs_inode); +} diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 4882ffb37bae..ed916018fe1a 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -165,6 +165,27 @@ static const struct dentry_operations ovl_reval_dentry_operations = { .d_weak_revalidate = ovl_dentry_weak_revalidate, }; +static struct kmem_cache *ovl_inode_cachep; + +static struct inode *ovl_alloc_inode(struct super_block *sb) +{ + struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); + + return &oi->vfs_inode; +} + +static void ovl_i_callback(struct rcu_head *head) +{ + struct inode *inode = container_of(head, struct inode, i_rcu); + + kmem_cache_free(ovl_inode_cachep, OVL_I(inode)); +} + +static void ovl_destroy_inode(struct inode *inode) +{ + call_rcu(&inode->i_rcu, ovl_i_callback); +} + static void ovl_put_super(struct super_block *sb) { struct ovl_fs *ufs = sb->s_fs_info; @@ -263,12 +284,14 @@ static int ovl_remount(struct super_block *sb, int *flags, char *data) } static const struct super_operations ovl_super_operations = { + .alloc_inode = ovl_alloc_inode, + .destroy_inode = ovl_destroy_inode, + .drop_inode = generic_delete_inode, .put_super = ovl_put_super, .sync_fs = ovl_sync_fs, .statfs = ovl_statfs, .show_options = ovl_show_options, .remount_fs = ovl_remount, - .drop_inode = generic_delete_inode, }; enum { @@ -1038,14 +1061,43 @@ static struct file_system_type ovl_fs_type = { }; MODULE_ALIAS_FS("overlay"); +static void ovl_inode_init_once(void *foo) +{ + struct ovl_inode *oi = foo; + + inode_init_once(&oi->vfs_inode); +} + static int __init ovl_init(void) { - return register_filesystem(&ovl_fs_type); + int err; + + ovl_inode_cachep = kmem_cache_create("ovl_inode", + sizeof(struct ovl_inode), 0, + (SLAB_RECLAIM_ACCOUNT| + SLAB_MEM_SPREAD|SLAB_ACCOUNT), + ovl_inode_init_once); + if (ovl_inode_cachep == NULL) + return -ENOMEM; + + err = register_filesystem(&ovl_fs_type); + if (err) + kmem_cache_destroy(ovl_inode_cachep); + + return err; } static void __exit ovl_exit(void) { unregister_filesystem(&ovl_fs_type); + + /* + * Make sure all delayed rcu free inodes are flushed before we + * destroy cache. + */ + rcu_barrier(); + kmem_cache_destroy(ovl_inode_cachep); + } module_init(ovl_init); -- cgit From e6d2ebddbc5205635a021a910f2f0e93bc2aa534 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: simplify getting inode Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 26 ++++++++++++++++++-------- fs/overlayfs/namei.c | 30 +++++++++--------------------- fs/overlayfs/overlayfs.h | 5 ++--- fs/overlayfs/super.c | 5 +---- fs/overlayfs/util.c | 7 ++++++- 5 files changed, 36 insertions(+), 37 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index d613e2c41242..22c677040b35 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -462,18 +462,28 @@ static int ovl_inode_set(struct inode *inode, void *data) return 0; } -struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode) - +struct inode *ovl_get_inode(struct dentry *dentry) { + struct dentry *upperdentry = ovl_dentry_upper(dentry); + struct inode *realinode = d_inode(ovl_dentry_real(dentry)); struct inode *inode; - inode = iget5_locked(sb, (unsigned long) realinode, - ovl_inode_test, ovl_inode_set, realinode); - if (inode && inode->i_state & I_NEW) { - ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); + if (upperdentry && !d_is_dir(upperdentry)) { + inode = iget5_locked(dentry->d_sb, (unsigned long) realinode, + ovl_inode_test, ovl_inode_set, realinode); + if (!inode || !(inode->i_state & I_NEW)) + goto out; + set_nlink(inode, realinode->i_nlink); - unlock_new_inode(inode); + } else { + inode = new_inode(dentry->d_sb); + if (!inode) + goto out; } - + ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); + ovl_inode_init(inode, dentry); + if (inode->i_state & I_NEW) + unlock_new_inode(inode); +out: return inode; } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index de0d4f742f36..0072ca5d5dac 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -433,41 +433,29 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (!oe) goto out_put; - if (upperdentry || ctr) { - struct dentry *realdentry; - struct inode *realinode; - - realdentry = upperdentry ? upperdentry : stack[0].dentry; - realinode = d_inode(realdentry); + oe->opaque = upperopaque; + oe->impure = upperimpure; + oe->redirect = upperredirect; + oe->__upperdentry = upperdentry; + memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); + dentry->d_fsdata = oe; + if (upperdentry || ctr) { err = -ENOMEM; - if (upperdentry && !d_is_dir(upperdentry)) { - inode = ovl_get_inode(dentry->d_sb, realinode); - } else { - inode = ovl_new_inode(dentry->d_sb, realinode->i_mode, - realinode->i_rdev); - if (inode) - ovl_inode_init(inode, realinode, !!upperdentry); - } + inode = ovl_get_inode(dentry); if (!inode) goto out_free_oe; - ovl_copyattr(realdentry->d_inode, inode); } revert_creds(old_cred); - oe->opaque = upperopaque; - oe->impure = upperimpure; - oe->redirect = upperredirect; - oe->__upperdentry = upperdentry; - memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); kfree(stack); kfree(d.redirect); - dentry->d_fsdata = oe; d_add(dentry, inode); return NULL; out_free_oe: + dentry->d_fsdata = NULL; kfree(oe); out_put: for (i = 0; i < ctr; i++) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 10863b4105fa..3af33d3166e2 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -211,8 +211,7 @@ bool ovl_redirect_dir(struct super_block *sb); const char *ovl_dentry_get_redirect(struct dentry *dentry); void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect); void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); -void ovl_inode_init(struct inode *inode, struct inode *realinode, - bool is_upper); +void ovl_inode_init(struct inode *inode, struct dentry *dentry); void ovl_inode_update(struct inode *inode, struct inode *upperinode); void ovl_dentry_version_inc(struct dentry *dentry); u64 ovl_dentry_version_get(struct dentry *dentry); @@ -262,7 +261,7 @@ int ovl_update_time(struct inode *inode, struct timespec *ts, int flags); bool ovl_is_private_xattr(const char *name); struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev); -struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode); +struct inode *ovl_get_inode(struct dentry *dentry); static inline void ovl_copyattr(struct inode *from, struct inode *to) { to->i_uid = from->i_uid; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ed916018fe1a..ec1b40816483 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -757,7 +757,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) struct path upperpath = { }; struct path workpath = { }; struct dentry *root_dentry; - struct inode *realinode; struct ovl_entry *oe; struct ovl_fs *ufs; struct path *stack = NULL; @@ -1009,9 +1008,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) root_dentry->d_fsdata = oe; - realinode = d_inode(ovl_dentry_real(root_dentry)); - ovl_inode_init(d_inode(root_dentry), realinode, !!upperpath.dentry); - ovl_copyattr(realinode, d_inode(root_dentry)); + ovl_inode_init(d_inode(root_dentry), root_dentry); sb->s_root = root_dentry; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 809048913889..f4847eff3284 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -230,10 +230,15 @@ void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) oe->__upperdentry = upperdentry; } -void ovl_inode_init(struct inode *inode, struct inode *realinode, bool is_upper) +void ovl_inode_init(struct inode *inode, struct dentry *dentry) { + struct inode *realinode = d_inode(ovl_dentry_real(dentry)); + bool is_upper = ovl_dentry_upper(dentry); + WRITE_ONCE(inode->i_private, (unsigned long) realinode | (is_upper ? OVL_ISUPPER_MASK : 0)); + + ovl_copyattr(realinode, inode); } void ovl_inode_update(struct inode *inode, struct inode *upperinode) -- cgit From 25b7713afe50963e70f98c1c964f60baf1e7e373 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: use i_private only as a key Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 4 ++-- fs/overlayfs/overlayfs.h | 13 +------------ fs/overlayfs/ovl_entry.h | 2 ++ fs/overlayfs/super.c | 3 +++ fs/overlayfs/util.c | 35 +++++++++++++++++++++++++++++------ 5 files changed, 37 insertions(+), 20 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 22c677040b35..4c30d44905ef 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -453,12 +453,12 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev) static int ovl_inode_test(struct inode *inode, void *data) { - return ovl_inode_real(inode, NULL) == data; + return inode->i_private == data; } static int ovl_inode_set(struct inode *inode, void *data) { - inode->i_private = (void *) (((unsigned long) data) | OVL_ISUPPER_MASK); + inode->i_private = data; return 0; } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 3af33d3166e2..6e6600ae1d54 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -60,8 +60,6 @@ struct ovl_fh { u8 fid[0]; /* file identifier */ } __packed; -#define OVL_ISUPPER_MASK 1UL - static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) { int err = vfs_rmdir(dir, dentry); @@ -175,16 +173,6 @@ static inline struct dentry *ovl_do_tmpfile(struct dentry *dentry, umode_t mode) return ret; } -static inline struct inode *ovl_inode_real(struct inode *inode, bool *is_upper) -{ - unsigned long x = (unsigned long) READ_ONCE(inode->i_private); - - if (is_upper) - *is_upper = x & OVL_ISUPPER_MASK; - - return (struct inode *) (x & ~OVL_ISUPPER_MASK); -} - /* util.c */ int ovl_want_write(struct dentry *dentry); void ovl_drop_write(struct dentry *dentry); @@ -201,6 +189,7 @@ enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); +struct inode *ovl_inode_real(struct inode *inode, bool *is_upper); struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); bool ovl_dentry_is_opaque(struct dentry *dentry); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 553727df886c..b8c213891e84 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -61,6 +61,8 @@ static inline struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe) struct ovl_inode { struct inode vfs_inode; + struct inode *upper; + struct inode *lower; }; static inline struct ovl_inode *OVL_I(struct inode *inode) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index ec1b40816483..c166c1d76890 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -171,6 +171,9 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) { struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); + oi->upper = NULL; + oi->lower = NULL; + return &oi->vfs_inode; } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index f4847eff3284..fc7b1447435b 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -155,6 +155,22 @@ struct dentry *ovl_dentry_real(struct dentry *dentry) return realdentry; } +struct inode *ovl_inode_real(struct inode *inode, bool *is_upper) +{ + struct inode *realinode = lockless_dereference(OVL_I(inode)->upper); + bool isup = false; + + if (!realinode) + realinode = OVL_I(inode)->lower; + else + isup = true; + + if (is_upper) + *is_upper = isup; + + return realinode; +} + struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; @@ -233,10 +249,11 @@ void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) void ovl_inode_init(struct inode *inode, struct dentry *dentry) { struct inode *realinode = d_inode(ovl_dentry_real(dentry)); - bool is_upper = ovl_dentry_upper(dentry); - WRITE_ONCE(inode->i_private, (unsigned long) realinode | - (is_upper ? OVL_ISUPPER_MASK : 0)); + if (ovl_dentry_upper(dentry)) + OVL_I(inode)->upper = realinode; + else + OVL_I(inode)->lower = realinode; ovl_copyattr(realinode, inode); } @@ -245,10 +262,16 @@ void ovl_inode_update(struct inode *inode, struct inode *upperinode) { WARN_ON(!upperinode); WARN_ON(!inode_unhashed(inode)); - WRITE_ONCE(inode->i_private, - (unsigned long) upperinode | OVL_ISUPPER_MASK); - if (!S_ISDIR(upperinode->i_mode)) + /* + * Make sure upperinode is consistent before making it visible to + * ovl_inode_real(); + */ + smp_wmb(); + OVL_I(inode)->upper = upperinode; + if (!S_ISDIR(upperinode->i_mode)) { + inode->i_private = upperinode; __insert_inode_hash(inode, (unsigned long) upperinode); + } } void ovl_dentry_version_inc(struct dentry *dentry) -- cgit From 9020df37207867272e590a416c2fb3da0e5383c6 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: compare inodes When checking for consistency in directory operations (unlink, rename, etc.) match inodes not dentries. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index fcfa7de12ad5..59e0dc9897fb 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -611,6 +611,11 @@ out: return err; } +static bool ovl_matches_upper(struct dentry *dentry, struct dentry *upper) +{ + return d_inode(ovl_dentry_upper(dentry)) == d_inode(upper); +} + static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir) { struct dentry *workdir = ovl_workdir(dentry); @@ -646,7 +651,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir) err = -ESTALE; if ((opaquedir && upper != opaquedir) || (!opaquedir && ovl_dentry_upper(dentry) && - upper != ovl_dentry_upper(dentry))) { + !ovl_matches_upper(dentry, upper))) { goto out_dput_upper; } @@ -707,7 +712,7 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) err = -ESTALE; if ((opaquedir && upper != opaquedir) || - (!opaquedir && upper != ovl_dentry_upper(dentry))) + (!opaquedir && !ovl_matches_upper(dentry, upper))) goto out_dput_upper; if (is_dir) @@ -985,7 +990,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, goto out_unlock; err = -ESTALE; - if (olddentry != ovl_dentry_upper(old)) + if (!ovl_matches_upper(old, olddentry)) goto out_dput_old; newdentry = lookup_one_len(new->d_name.name, new_upperdir, @@ -1003,7 +1008,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, if (newdentry != opaquedir) goto out_dput; } else { - if (newdentry != ovl_dentry_upper(new)) + if (!ovl_matches_upper(new, newdentry)) goto out_dput; } } else { -- cgit From 09d8b586731bf589655c2ac971532c14cf272b63 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: move __upperdentry to ovl_inode Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 3 +- fs/overlayfs/dir.c | 8 ++--- fs/overlayfs/inode.c | 26 +++++++++----- fs/overlayfs/namei.c | 7 ++-- fs/overlayfs/overlayfs.h | 12 ++++--- fs/overlayfs/ovl_entry.h | 13 ++++--- fs/overlayfs/super.c | 15 ++++---- fs/overlayfs/util.c | 89 +++++++++++++++++------------------------------- 8 files changed, 79 insertions(+), 94 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index e5869f91b3ab..87289b9a152c 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -417,8 +417,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, goto out_cleanup; newdentry = dget(tmpfile ? upper : temp); - ovl_dentry_update(dentry, newdentry); - ovl_inode_update(d_inode(dentry), d_inode(newdentry)); + ovl_inode_update(d_inode(dentry), newdentry); /* Restore timestamps on parent (best effort) */ ovl_set_timestamps(upperdir, pstat); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 59e0dc9897fb..d0d6292e069a 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -154,12 +154,12 @@ static void ovl_instantiate(struct dentry *dentry, struct inode *inode, struct dentry *newdentry, bool hardlink) { ovl_dentry_version_inc(dentry->d_parent); - ovl_dentry_update(dentry, newdentry); if (!hardlink) { - ovl_inode_update(inode, d_inode(newdentry)); + ovl_inode_update(inode, newdentry); ovl_copyattr(newdentry->d_inode, inode); } else { - WARN_ON(ovl_inode_real(inode, NULL) != d_inode(newdentry)); + WARN_ON(ovl_inode_real(inode) != d_inode(newdentry)); + dput(newdentry); inc_nlink(inode); } d_instantiate(dentry, inode); @@ -1003,7 +1003,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, new_opaque = ovl_dentry_is_opaque(new); err = -ESTALE; - if (ovl_dentry_upper(new)) { + if (d_inode(new) && ovl_dentry_upper(new)) { if (opaquedir) { if (newdentry != opaquedir) goto out_dput; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 4c30d44905ef..4654c03dd508 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -134,8 +134,8 @@ out: int ovl_permission(struct inode *inode, int mask) { - bool is_upper; - struct inode *realinode = ovl_inode_real(inode, &is_upper); + struct inode *upperinode = ovl_inode_upper(inode); + struct inode *realinode = upperinode ?: ovl_inode_lower(inode); const struct cred *old_cred; int err; @@ -154,7 +154,8 @@ int ovl_permission(struct inode *inode, int mask) return err; old_cred = ovl_override_creds(inode->i_sb); - if (!is_upper && !special_file(realinode->i_mode) && mask & MAY_WRITE) { + if (!upperinode && + !special_file(realinode->i_mode) && mask & MAY_WRITE) { mask &= ~(MAY_WRITE | MAY_APPEND); /* Make sure mounter can read file for copy up later */ mask |= MAY_READ; @@ -286,7 +287,7 @@ ssize_t ovl_listxattr(struct dentry *dentry, char *list, size_t size) struct posix_acl *ovl_get_acl(struct inode *inode, int type) { - struct inode *realinode = ovl_inode_real(inode, NULL); + struct inode *realinode = ovl_inode_real(inode); const struct cred *old_cred; struct posix_acl *acl; @@ -462,17 +463,24 @@ static int ovl_inode_set(struct inode *inode, void *data) return 0; } -struct inode *ovl_get_inode(struct dentry *dentry) +struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) { - struct dentry *upperdentry = ovl_dentry_upper(dentry); - struct inode *realinode = d_inode(ovl_dentry_real(dentry)); + struct dentry *lowerdentry = ovl_dentry_lower(dentry); + struct inode *realinode = upperdentry ? d_inode(upperdentry) : NULL; struct inode *inode; + if (!realinode) + realinode = d_inode(lowerdentry); + if (upperdentry && !d_is_dir(upperdentry)) { inode = iget5_locked(dentry->d_sb, (unsigned long) realinode, ovl_inode_test, ovl_inode_set, realinode); - if (!inode || !(inode->i_state & I_NEW)) + if (!inode) goto out; + if (!(inode->i_state & I_NEW)) { + dput(upperdentry); + goto out; + } set_nlink(inode, realinode->i_nlink); } else { @@ -481,7 +489,7 @@ struct inode *ovl_get_inode(struct dentry *dentry) goto out; } ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); - ovl_inode_init(inode, dentry); + ovl_inode_init(inode, upperdentry, lowerdentry); if (inode->i_state & I_NEW) unlock_new_inode(inode); out: diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 0072ca5d5dac..1f873e2e8a79 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -359,7 +359,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, return ERR_PTR(-ENAMETOOLONG); old_cred = ovl_override_creds(dentry->d_sb); - upperdir = ovl_upperdentry_dereference(poe); + upperdir = ovl_dentry_upper(dentry->d_parent); if (upperdir) { err = ovl_lookup_layer(upperdir, &d, &upperdentry); if (err) @@ -436,13 +436,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, oe->opaque = upperopaque; oe->impure = upperimpure; oe->redirect = upperredirect; - oe->__upperdentry = upperdentry; memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); dentry->d_fsdata = oe; if (upperdentry || ctr) { err = -ENOMEM; - inode = ovl_get_inode(dentry); + inode = ovl_get_inode(dentry, upperdentry); if (!inode) goto out_free_oe; } @@ -487,7 +486,7 @@ bool ovl_lower_positive(struct dentry *dentry) return oe->opaque; /* Negative upper -> positive lower */ - if (!oe->__upperdentry) + if (!ovl_dentry_upper(dentry)) return true; /* Positive upper -> have to look up lower to see whether it exists */ diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 6e6600ae1d54..83607f883ace 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -189,7 +189,9 @@ enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); -struct inode *ovl_inode_real(struct inode *inode, bool *is_upper); +struct inode *ovl_inode_upper(struct inode *inode); +struct inode *ovl_inode_lower(struct inode *inode); +struct inode *ovl_inode_real(struct inode *inode); struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); bool ovl_dentry_is_opaque(struct dentry *dentry); @@ -199,9 +201,9 @@ void ovl_dentry_set_opaque(struct dentry *dentry); bool ovl_redirect_dir(struct super_block *sb); const char *ovl_dentry_get_redirect(struct dentry *dentry); void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect); -void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); -void ovl_inode_init(struct inode *inode, struct dentry *dentry); -void ovl_inode_update(struct inode *inode, struct inode *upperinode); +void ovl_inode_init(struct inode *inode, struct dentry *upperdentry, + struct dentry *lowerdentry); +void ovl_inode_update(struct inode *inode, struct dentry *upperdentry); void ovl_dentry_version_inc(struct dentry *dentry); u64 ovl_dentry_version_get(struct dentry *dentry); bool ovl_is_whiteout(struct dentry *dentry); @@ -250,7 +252,7 @@ int ovl_update_time(struct inode *inode, struct timespec *ts, int flags); bool ovl_is_private_xattr(const char *name); struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev); -struct inode *ovl_get_inode(struct dentry *dentry); +struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry); static inline void ovl_copyattr(struct inode *from, struct inode *to) { to->i_uid = from->i_uid; diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index b8c213891e84..ddd937490f0d 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -36,7 +36,6 @@ struct ovl_fs { /* private information held for every overlayfs dentry */ struct ovl_entry { - struct dentry *__upperdentry; struct ovl_dir_cache *cache; union { struct { @@ -54,14 +53,9 @@ struct ovl_entry { struct ovl_entry *ovl_alloc_entry(unsigned int numlower); -static inline struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe) -{ - return lockless_dereference(oe->__upperdentry); -} - struct ovl_inode { struct inode vfs_inode; - struct inode *upper; + struct dentry *__upperdentry; struct inode *lower; }; @@ -69,3 +63,8 @@ static inline struct ovl_inode *OVL_I(struct inode *inode) { return container_of(inode, struct ovl_inode, vfs_inode); } + +static inline struct dentry *ovl_upperdentry_dereference(struct ovl_inode *oi) +{ + return lockless_dereference(oi->__upperdentry); +} diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index c166c1d76890..1b865716110a 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -41,7 +41,6 @@ static void ovl_dentry_release(struct dentry *dentry) if (oe) { unsigned int i; - dput(oe->__upperdentry); kfree(oe->redirect); for (i = 0; i < oe->numlower; i++) dput(oe->lowerstack[i].dentry); @@ -171,7 +170,7 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) { struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); - oi->upper = NULL; + oi->__upperdentry = NULL; oi->lower = NULL; return &oi->vfs_inode; @@ -186,6 +185,10 @@ static void ovl_i_callback(struct rcu_head *head) static void ovl_destroy_inode(struct inode *inode) { + struct ovl_inode *oi = OVL_I(inode); + + dput(oi->__upperdentry); + call_rcu(&inode->i_rcu, ovl_i_callback); } @@ -636,7 +639,7 @@ ovl_posix_acl_xattr_set(const struct xattr_handler *handler, size_t size, int flags) { struct dentry *workdir = ovl_workdir(dentry); - struct inode *realinode = ovl_inode_real(inode, NULL); + struct inode *realinode = ovl_inode_real(inode); struct posix_acl *acl = NULL; int err; @@ -678,7 +681,7 @@ ovl_posix_acl_xattr_set(const struct xattr_handler *handler, err = ovl_xattr_set(dentry, handler->name, value, size, flags); if (!err) - ovl_copyattr(ovl_inode_real(inode, NULL), inode); + ovl_copyattr(ovl_inode_real(inode), inode); return err; @@ -1000,7 +1003,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) kfree(lowertmp); if (upperpath.dentry) { - oe->__upperdentry = upperpath.dentry; oe->impure = ovl_is_impuredir(upperpath.dentry); } for (i = 0; i < numlower; i++) { @@ -1011,7 +1013,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) root_dentry->d_fsdata = oe; - ovl_inode_init(d_inode(root_dentry), root_dentry); + ovl_inode_init(d_inode(root_dentry), upperpath.dentry, + ovl_dentry_lower(root_dentry)); sb->s_root = root_dentry; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index fc7b1447435b..e5d9e8daa55c 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -78,7 +78,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) struct ovl_entry *oe = dentry->d_fsdata; enum ovl_path_type type = 0; - if (oe->__upperdentry) { + if (ovl_dentry_upper(dentry)) { type = __OVL_PATH_UPPER; /* @@ -99,10 +99,9 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) void ovl_path_upper(struct dentry *dentry, struct path *path) { struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - struct ovl_entry *oe = dentry->d_fsdata; path->mnt = ofs->upper_mnt; - path->dentry = ovl_upperdentry_dereference(oe); + path->dentry = ovl_dentry_upper(dentry); } void ovl_path_lower(struct dentry *dentry, struct path *path) @@ -126,51 +125,39 @@ enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path) struct dentry *ovl_dentry_upper(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; - - return ovl_upperdentry_dereference(oe); -} - -static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe) -{ - return oe->numlower ? oe->lowerstack[0].dentry : NULL; + return ovl_upperdentry_dereference(OVL_I(d_inode(dentry))); } struct dentry *ovl_dentry_lower(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; - return __ovl_dentry_lower(oe); + return oe->numlower ? oe->lowerstack[0].dentry : NULL; } struct dentry *ovl_dentry_real(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; - struct dentry *realdentry; - - realdentry = ovl_upperdentry_dereference(oe); - if (!realdentry) - realdentry = __ovl_dentry_lower(oe); - - return realdentry; + return ovl_dentry_upper(dentry) ?: ovl_dentry_lower(dentry); } -struct inode *ovl_inode_real(struct inode *inode, bool *is_upper) +struct inode *ovl_inode_upper(struct inode *inode) { - struct inode *realinode = lockless_dereference(OVL_I(inode)->upper); - bool isup = false; + struct dentry *upperdentry = ovl_upperdentry_dereference(OVL_I(inode)); - if (!realinode) - realinode = OVL_I(inode)->lower; - else - isup = true; + return upperdentry ? d_inode(upperdentry) : NULL; +} - if (is_upper) - *is_upper = isup; +struct inode *ovl_inode_lower(struct inode *inode) +{ + return OVL_I(inode)->lower; +} - return realinode; +struct inode *ovl_inode_real(struct inode *inode) +{ + return ovl_inode_upper(inode) ?: ovl_inode_lower(inode); } + struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; @@ -232,42 +219,30 @@ void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect) oe->redirect = redirect; } -void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry) +void ovl_inode_init(struct inode *inode, struct dentry *upperdentry, + struct dentry *lowerdentry) { - struct ovl_entry *oe = dentry->d_fsdata; + if (upperdentry) + OVL_I(inode)->__upperdentry = upperdentry; + if (lowerdentry) + OVL_I(inode)->lower = d_inode(lowerdentry); - WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); - WARN_ON(oe->__upperdentry); - /* - * Make sure upperdentry is consistent before making it visible to - * ovl_upperdentry_dereference(). - */ - smp_wmb(); - oe->__upperdentry = upperdentry; + ovl_copyattr(d_inode(upperdentry ?: lowerdentry), inode); } -void ovl_inode_init(struct inode *inode, struct dentry *dentry) +void ovl_inode_update(struct inode *inode, struct dentry *upperdentry) { - struct inode *realinode = d_inode(ovl_dentry_real(dentry)); + struct inode *upperinode = d_inode(upperdentry); - if (ovl_dentry_upper(dentry)) - OVL_I(inode)->upper = realinode; - else - OVL_I(inode)->lower = realinode; - - ovl_copyattr(realinode, inode); -} - -void ovl_inode_update(struct inode *inode, struct inode *upperinode) -{ - WARN_ON(!upperinode); WARN_ON(!inode_unhashed(inode)); + WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); + WARN_ON(OVL_I(inode)->__upperdentry); + /* - * Make sure upperinode is consistent before making it visible to - * ovl_inode_real(); + * Make sure upperdentry is consistent before making it visible */ smp_wmb(); - OVL_I(inode)->upper = upperinode; + OVL_I(inode)->__upperdentry = upperdentry; if (!S_ISDIR(upperinode->i_mode)) { inode->i_private = upperinode; __insert_inode_hash(inode, (unsigned long) upperinode); @@ -311,7 +286,7 @@ int ovl_copy_up_start(struct dentry *dentry) spin_lock(&ofs->copyup_wq.lock); err = wait_event_interruptible_locked(ofs->copyup_wq, !oe->copying); if (!err) { - if (oe->__upperdentry) + if (ovl_dentry_upper(dentry)) err = 1; /* Already copied up */ else oe->copying = true; -- cgit From cf31c46347e8e54cb53d66255ae3eea045b0a60c Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: move redirect to ovl_inode Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 3 ++- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/super.c | 3 ++- fs/overlayfs/util.c | 10 ++++------ 4 files changed, 9 insertions(+), 9 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 1f873e2e8a79..1b6092b80330 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -435,7 +435,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, oe->opaque = upperopaque; oe->impure = upperimpure; - oe->redirect = upperredirect; memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); dentry->d_fsdata = oe; @@ -444,6 +443,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, inode = ovl_get_inode(dentry, upperdentry); if (!inode) goto out_free_oe; + + OVL_I(inode)->redirect = upperredirect; } revert_creds(old_cred); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index ddd937490f0d..477d21738656 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -40,7 +40,6 @@ struct ovl_entry { union { struct { u64 version; - const char *redirect; bool opaque; bool impure; bool copying; @@ -54,6 +53,7 @@ struct ovl_entry { struct ovl_entry *ovl_alloc_entry(unsigned int numlower); struct ovl_inode { + const char *redirect; struct inode vfs_inode; struct dentry *__upperdentry; struct inode *lower; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 1b865716110a..7346518846c5 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -41,7 +41,6 @@ static void ovl_dentry_release(struct dentry *dentry) if (oe) { unsigned int i; - kfree(oe->redirect); for (i = 0; i < oe->numlower; i++) dput(oe->lowerstack[i].dentry); kfree_rcu(oe, rcu); @@ -170,6 +169,7 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) { struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); + oi->redirect = NULL; oi->__upperdentry = NULL; oi->lower = NULL; @@ -188,6 +188,7 @@ static void ovl_destroy_inode(struct inode *inode) struct ovl_inode *oi = OVL_I(inode); dput(oi->__upperdentry); + kfree(oi->redirect); call_rcu(&inode->i_rcu, ovl_i_callback); } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index e5d9e8daa55c..be0670f1addf 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -206,17 +206,15 @@ bool ovl_redirect_dir(struct super_block *sb) const char *ovl_dentry_get_redirect(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; - - return oe->redirect; + return OVL_I(d_inode(dentry))->redirect; } void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect) { - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_inode *oi = OVL_I(d_inode(dentry)); - kfree(oe->redirect); - oe->redirect = redirect; + kfree(oi->redirect); + oi->redirect = redirect; } void ovl_inode_init(struct inode *inode, struct dentry *upperdentry, -- cgit From 13c72075ac9f5a5cf3f61c85adaafffe48a6f797 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: move impure to ovl_inode Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 4 ++++ fs/overlayfs/namei.c | 4 ---- fs/overlayfs/overlayfs.h | 7 ++++++- fs/overlayfs/ovl_entry.h | 2 +- fs/overlayfs/super.c | 4 +++- fs/overlayfs/util.c | 22 ++++++++++++---------- 6 files changed, 26 insertions(+), 17 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 4654c03dd508..23d64d51f331 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -490,6 +490,10 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) } ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); ovl_inode_init(inode, upperdentry, lowerdentry); + + if (upperdentry && ovl_is_impuredir(upperdentry)) + ovl_set_flag(OVL_IMPURE, inode); + if (inode->i_state & I_NEW) unlock_new_inode(inode); out: diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 1b6092b80330..277a55c8299a 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -341,7 +341,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int ctr = 0; struct inode *inode = NULL; bool upperopaque = false; - bool upperimpure = false; char *upperredirect = NULL; struct dentry *this; unsigned int i; @@ -386,8 +385,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, poe = roe; } upperopaque = d.opaque; - if (upperdentry && d.is_dir) - upperimpure = ovl_is_impuredir(upperdentry); } if (!d.stop && poe->numlower) { @@ -434,7 +431,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, goto out_put; oe->opaque = upperopaque; - oe->impure = upperimpure; memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); dentry->d_fsdata = oe; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 83607f883ace..b1be3d39ac9d 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -26,6 +26,10 @@ enum ovl_path_type { #define OVL_XATTR_ORIGIN OVL_XATTR_PREFIX "origin" #define OVL_XATTR_IMPURE OVL_XATTR_PREFIX "impure" +enum ovl_flag { + OVL_IMPURE, +}; + /* * The tuple (fh,uuid) is a universal unique identifier for a copy up origin, * where: @@ -195,7 +199,6 @@ struct inode *ovl_inode_real(struct inode *inode); struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry); void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); bool ovl_dentry_is_opaque(struct dentry *dentry); -bool ovl_dentry_is_impure(struct dentry *dentry); bool ovl_dentry_is_whiteout(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry); bool ovl_redirect_dir(struct super_block *sb); @@ -215,6 +218,8 @@ int ovl_check_setxattr(struct dentry *dentry, struct dentry *upperdentry, const char *name, const void *value, size_t size, int xerr); int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry); +void ovl_set_flag(unsigned long flag, struct inode *inode); +bool ovl_test_flag(unsigned long flag, struct inode *inode); static inline bool ovl_is_impuredir(struct dentry *dentry) { diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 477d21738656..50dfa4826152 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -41,7 +41,6 @@ struct ovl_entry { struct { u64 version; bool opaque; - bool impure; bool copying; }; struct rcu_head rcu; @@ -54,6 +53,7 @@ struct ovl_entry *ovl_alloc_entry(unsigned int numlower); struct ovl_inode { const char *redirect; + unsigned long flags; struct inode vfs_inode; struct dentry *__upperdentry; struct inode *lower; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 7346518846c5..7c7b946b063f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -170,6 +170,7 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); oi->redirect = NULL; + oi->flags = 0; oi->__upperdentry = NULL; oi->lower = NULL; @@ -1004,7 +1005,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) kfree(lowertmp); if (upperpath.dentry) { - oe->impure = ovl_is_impuredir(upperpath.dentry); + if (ovl_is_impuredir(upperpath.dentry)) + ovl_set_flag(OVL_IMPURE, d_inode(root_dentry)); } for (i = 0; i < numlower; i++) { oe->lowerstack[i].dentry = stack[i].dentry; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index be0670f1addf..2fc4c22707aa 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -178,13 +178,6 @@ bool ovl_dentry_is_opaque(struct dentry *dentry) return oe->opaque; } -bool ovl_dentry_is_impure(struct dentry *dentry) -{ - struct ovl_entry *oe = dentry->d_fsdata; - - return oe->impure; -} - bool ovl_dentry_is_whiteout(struct dentry *dentry) { return !dentry->d_inode && ovl_dentry_is_opaque(dentry); @@ -344,9 +337,8 @@ int ovl_check_setxattr(struct dentry *dentry, struct dentry *upperdentry, int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry) { int err; - struct ovl_entry *oe = dentry->d_fsdata; - if (oe->impure) + if (ovl_test_flag(OVL_IMPURE, d_inode(dentry))) return 0; /* @@ -356,7 +348,17 @@ int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry) err = ovl_check_setxattr(dentry, upperdentry, OVL_XATTR_IMPURE, "y", 1, 0); if (!err) - oe->impure = true; + ovl_set_flag(OVL_IMPURE, d_inode(dentry)); return err; } + +void ovl_set_flag(unsigned long flag, struct inode *inode) +{ + set_bit(flag, &OVL_I(inode)->flags); +} + +bool ovl_test_flag(unsigned long flag, struct inode *inode) +{ + return test_bit(flag, &OVL_I(inode)->flags); +} -- cgit From a015dafcaf5b0316654a39bc598a76804595af90 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:51 +0300 Subject: ovl: use ovl_inode mutex to synchronize concurrent copy up Use the new ovl_inode mutex to synchonize concurrent copy up instead of the super block copy up workqueue. Moving the synchronization object from the overlay dentry to the overlay inode is needed for synchonizing concurrent copy up of lower hardlinks to the same upper inode. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/ovl_entry.h | 5 +++-- fs/overlayfs/super.c | 3 ++- fs/overlayfs/util.c | 23 ++++++----------------- 3 files changed, 11 insertions(+), 20 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 50dfa4826152..d8f514a474ca 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -29,7 +29,6 @@ struct ovl_fs { const struct cred *creator_cred; bool tmpfile; bool noxattr; - wait_queue_head_t copyup_wq; /* sb common to all layers */ struct super_block *same_sb; }; @@ -41,7 +40,6 @@ struct ovl_entry { struct { u64 version; bool opaque; - bool copying; }; struct rcu_head rcu; }; @@ -57,6 +55,9 @@ struct ovl_inode { struct inode vfs_inode; struct dentry *__upperdentry; struct inode *lower; + + /* synchronize copy up and more */ + struct mutex lock; }; static inline struct ovl_inode *OVL_I(struct inode *inode) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 7c7b946b063f..b0d539af1fad 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -173,6 +173,7 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) oi->flags = 0; oi->__upperdentry = NULL; oi->lower = NULL; + mutex_init(&oi->lock); return &oi->vfs_inode; } @@ -190,6 +191,7 @@ static void ovl_destroy_inode(struct inode *inode) dput(oi->__upperdentry); kfree(oi->redirect); + mutex_destroy(&oi->lock); call_rcu(&inode->i_rcu, ovl_i_callback); } @@ -782,7 +784,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!ufs) goto out; - init_waitqueue_head(&ufs->copyup_wq); ufs->config.redirect_dir = ovl_redirect_dir_def; err = ovl_parse_opt((char *) data, &ufs->config); if (err) diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 2fc4c22707aa..a0baaa7e224c 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -270,32 +270,21 @@ struct file *ovl_path_open(struct path *path, int flags) int ovl_copy_up_start(struct dentry *dentry) { - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - struct ovl_entry *oe = dentry->d_fsdata; + struct ovl_inode *oi = OVL_I(d_inode(dentry)); int err; - spin_lock(&ofs->copyup_wq.lock); - err = wait_event_interruptible_locked(ofs->copyup_wq, !oe->copying); - if (!err) { - if (ovl_dentry_upper(dentry)) - err = 1; /* Already copied up */ - else - oe->copying = true; + err = mutex_lock_interruptible(&oi->lock); + if (!err && ovl_dentry_upper(dentry)) { + err = 1; /* Already copied up */ + mutex_unlock(&oi->lock); } - spin_unlock(&ofs->copyup_wq.lock); return err; } void ovl_copy_up_end(struct dentry *dentry) { - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; - struct ovl_entry *oe = dentry->d_fsdata; - - spin_lock(&ofs->copyup_wq.lock); - oe->copying = false; - wake_up_locked(&ofs->copyup_wq); - spin_unlock(&ofs->copyup_wq.lock); + mutex_unlock(&OVL_I(d_inode(dentry))->lock); } bool ovl_check_dir_xattr(struct dentry *dentry, const char *name) -- cgit From 04a01ac7ed3c5cd718713ef6341249a143c96b10 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:16 +0200 Subject: ovl: move cache and version to ovl_inode Signed-off-by: Miklos Szeredi --- fs/overlayfs/ovl_entry.h | 8 +++----- fs/overlayfs/super.c | 2 ++ fs/overlayfs/util.c | 20 ++++++++------------ 3 files changed, 13 insertions(+), 17 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index d8f514a474ca..6a90a48c3589 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -35,12 +35,8 @@ struct ovl_fs { /* private information held for every overlayfs dentry */ struct ovl_entry { - struct ovl_dir_cache *cache; union { - struct { - u64 version; - bool opaque; - }; + bool opaque; struct rcu_head rcu; }; unsigned numlower; @@ -50,7 +46,9 @@ struct ovl_entry { struct ovl_entry *ovl_alloc_entry(unsigned int numlower); struct ovl_inode { + struct ovl_dir_cache *cache; const char *redirect; + u64 version; unsigned long flags; struct inode vfs_inode; struct dentry *__upperdentry; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index b0d539af1fad..e0a51ea773ec 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -169,7 +169,9 @@ static struct inode *ovl_alloc_inode(struct super_block *sb) { struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL); + oi->cache = NULL; oi->redirect = NULL; + oi->version = 0; oi->flags = 0; oi->__upperdentry = NULL; oi->lower = NULL; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index a0baaa7e224c..f093fcf2b4bd 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -160,16 +160,12 @@ struct inode *ovl_inode_real(struct inode *inode) struct ovl_dir_cache *ovl_dir_cache(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; - - return oe->cache; + return OVL_I(d_inode(dentry))->cache; } void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache) { - struct ovl_entry *oe = dentry->d_fsdata; - - oe->cache = cache; + OVL_I(d_inode(dentry))->cache = cache; } bool ovl_dentry_is_opaque(struct dentry *dentry) @@ -242,18 +238,18 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry) void ovl_dentry_version_inc(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct inode *inode = d_inode(dentry); - WARN_ON(!inode_is_locked(dentry->d_inode)); - oe->version++; + WARN_ON(!inode_is_locked(inode)); + OVL_I(inode)->version++; } u64 ovl_dentry_version_get(struct dentry *dentry) { - struct ovl_entry *oe = dentry->d_fsdata; + struct inode *inode = d_inode(dentry); - WARN_ON(!inode_is_locked(dentry->d_inode)); - return oe->version; + WARN_ON(!inode_is_locked(inode)); + return OVL_I(inode)->version; } bool ovl_is_whiteout(struct dentry *dentry) -- cgit From ad0af7104dadccd55cd2b390271677fac142650f Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:32 +0300 Subject: vfs: introduce inode 'inuse' lock Added an i_state flag I_INUSE and helpers to set/clear/test the bit. The 'inuse' lock is an 'advisory' inode lock, that can be used to extend exclusive create protection beyond parent->i_mutex lock among cooperating users. This is going to be used by overlayfs to get exclusive ownership on upper and work dirs among overlayfs mounts. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/overlayfs.h | 2 ++ fs/overlayfs/util.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) (limited to 'fs') diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index b1be3d39ac9d..5e958427463d 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -220,6 +220,8 @@ int ovl_check_setxattr(struct dentry *dentry, struct dentry *upperdentry, int ovl_set_impure(struct dentry *dentry, struct dentry *upperdentry); void ovl_set_flag(unsigned long flag, struct inode *inode); bool ovl_test_flag(unsigned long flag, struct inode *inode); +bool ovl_inuse_trylock(struct dentry *dentry); +void ovl_inuse_unlock(struct dentry *dentry); static inline bool ovl_is_impuredir(struct dentry *dentry) { diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index f093fcf2b4bd..adccd74162d6 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -347,3 +347,34 @@ bool ovl_test_flag(unsigned long flag, struct inode *inode) { return test_bit(flag, &OVL_I(inode)->flags); } + +/** + * Caller must hold a reference to inode to prevent it from being freed while + * it is marked inuse. + */ +bool ovl_inuse_trylock(struct dentry *dentry) +{ + struct inode *inode = d_inode(dentry); + bool locked = false; + + spin_lock(&inode->i_lock); + if (!(inode->i_state & I_OVL_INUSE)) { + inode->i_state |= I_OVL_INUSE; + locked = true; + } + spin_unlock(&inode->i_lock); + + return locked; +} + +void ovl_inuse_unlock(struct dentry *dentry) +{ + if (dentry) { + struct inode *inode = d_inode(dentry); + + spin_lock(&inode->i_lock); + WARN_ON(!(inode->i_state & I_OVL_INUSE)); + inode->i_state &= ~I_OVL_INUSE; + spin_unlock(&inode->i_lock); + } +} -- cgit From 2cac0c00a6cdcc9121de150ed531f652396d1544 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:33 +0300 Subject: ovl: get exclusive ownership on upper/work dirs Bad things can happen if several concurrent overlay mounts try to use the same upperdir/workdir path. Try to get the 'inuse' advisory lock on upperdir and workdir. Fail mount if another overlay mount instance or another user holds the 'inuse' lock on these directories. Note that this provides no protection for concurrent overlay mount that use overlapping (i.e. descendant) upper/work dirs. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/ovl_entry.h | 3 +++ fs/overlayfs/super.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 6a90a48c3589..5b5a32116424 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -21,6 +21,9 @@ struct ovl_fs { struct vfsmount *upper_mnt; unsigned numlower; struct vfsmount **lower_mnt; + /* workbasedir is the path at workdir= mount option */ + struct dentry *workbasedir; + /* workdir is the 'work' directory under workbasedir */ struct dentry *workdir; long namelen; /* pathnames of lower and upper dirs, for show_options */ diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e0a51ea773ec..b31637727021 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -204,6 +204,10 @@ static void ovl_put_super(struct super_block *sb) unsigned i; dput(ufs->workdir); + ovl_inuse_unlock(ufs->workbasedir); + dput(ufs->workbasedir); + if (ufs->upper_mnt) + ovl_inuse_unlock(ufs->upper_mnt->mnt_root); mntput(ufs->upper_mnt); for (i = 0; i < ufs->numlower; i++) mntput(ufs->lower_mnt[i]); @@ -821,9 +825,15 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (err) goto out_put_upperpath; + err = -EBUSY; + if (!ovl_inuse_trylock(upperpath.dentry)) { + pr_err("overlayfs: upperdir is in-use by another mount\n"); + goto out_put_upperpath; + } + err = ovl_mount_dir(ufs->config.workdir, &workpath); if (err) - goto out_put_upperpath; + goto out_unlock_upperdentry; err = -EINVAL; if (upperpath.mnt != workpath.mnt) { @@ -834,12 +844,20 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) pr_err("overlayfs: workdir and upperdir must be separate subtrees\n"); goto out_put_workpath; } + + err = -EBUSY; + if (!ovl_inuse_trylock(workpath.dentry)) { + pr_err("overlayfs: workdir is in-use by another mount\n"); + goto out_put_workpath; + } + + ufs->workbasedir = workpath.dentry; sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth; } err = -ENOMEM; lowertmp = kstrdup(ufs->config.lowerdir, GFP_KERNEL); if (!lowertmp) - goto out_put_workpath; + goto out_unlock_workdentry; err = -EINVAL; stacklen = ovl_split_lowerdirs(lowertmp); @@ -882,6 +900,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) pr_err("overlayfs: failed to clone upperpath\n"); goto out_put_lowerpath; } + /* Don't inherit atime flags */ ufs->upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME); @@ -1004,7 +1023,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) mntput(upperpath.mnt); for (i = 0; i < numlower; i++) mntput(stack[i].mnt); - path_put(&workpath); + mntput(workpath.mnt); kfree(lowertmp); if (upperpath.dentry) { @@ -1043,8 +1062,12 @@ out_put_lowerpath: kfree(stack); out_free_lowertmp: kfree(lowertmp); +out_unlock_workdentry: + ovl_inuse_unlock(workpath.dentry); out_put_workpath: path_put(&workpath); +out_unlock_upperdentry: + ovl_inuse_unlock(upperpath.dentry); out_put_upperpath: path_put(&upperpath); out_free_config: -- cgit From f7d3daca7c79d9b77e61f50f718b257b71d07498 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:34 +0300 Subject: ovl: relax same fs constrain for ovl_check_origin() For the case of all layers not on the same fs, try to decode the copy up origin file handle on any of the lower layers. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/namei.c | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 277a55c8299a..6485beddaa1f 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -272,28 +272,24 @@ static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d, static int ovl_check_origin(struct dentry *dentry, struct dentry *upperdentry, struct path **stackp, unsigned int *ctrp) { - struct super_block *same_sb = ovl_same_sb(dentry->d_sb); struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata; struct vfsmount *mnt; - struct dentry *origin; + struct dentry *origin = NULL; + int i; - if (!same_sb || !roe->numlower) - return 0; - /* - * Since all layers are on the same fs, we use the first layer for - * decoding the file handle. We may get a disconnected dentry, - * which is fine, because we only need to hold the origin inode in - * cache and use its inode number. We may even get a connected dentry, - * that is not under the first layer's root. That is also fine for - * using it's inode number - it's the same as if we held a reference - * to a dentry in first layer that was moved under us. - */ - mnt = roe->lowerstack[0].mnt; - - origin = ovl_get_origin(upperdentry, mnt); - if (IS_ERR_OR_NULL(origin)) - return PTR_ERR(origin); + for (i = 0; i < roe->numlower; i++) { + mnt = roe->lowerstack[i].mnt; + origin = ovl_get_origin(upperdentry, mnt); + if (IS_ERR(origin)) + return PTR_ERR(origin); + + if (origin) + break; + } + + if (!origin) + return 0; BUG_ON(*stackp || *ctrp); *stackp = kmalloc(sizeof(struct path), GFP_TEMPORARY); @@ -371,6 +367,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } if (upperdentry && !d.is_dir) { BUG_ON(!d.stop || d.redirect); + /* + * Lookup copy up origin by decoding origin file handle. + * We may get a disconnected dentry, which is fine, + * because we only need to hold the origin inode in + * cache and use its inode number. We may even get a + * connected dentry, that is not under any of the lower + * layers root. That is also fine for using it's inode + * number - it's the same as if we held a reference + * to a dentry in lower layer that was moved under us. + */ err = ovl_check_origin(dentry, upperdentry, &stack, &ctr); if (err) -- cgit From 6b8aa129dcbe0e9825109b35c4b967f984e8fb13 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:35 +0300 Subject: ovl: generalize ovl_create_workdir() Pass in the subdir name to create and specify if subdir is persistent or if it should be cleaned up on every mount. Move fallback to readonly mount on failure to create dir and print of error message into the helper. This function is going to be used for creating the persistent 'index' dir under workbasedir. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index b31637727021..fea7bd496f2e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -418,22 +418,27 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) #define OVL_WORKDIR_NAME "work" -static struct dentry *ovl_workdir_create(struct vfsmount *mnt, - struct dentry *dentry) +static struct dentry *ovl_workdir_create(struct super_block *sb, + struct ovl_fs *ufs, + struct dentry *dentry, + const char *name, bool persist) { struct inode *dir = dentry->d_inode; + struct vfsmount *mnt = ufs->upper_mnt; struct dentry *work; int err; bool retried = false; + bool locked = false; err = mnt_want_write(mnt); if (err) - return ERR_PTR(err); + goto out_err; inode_lock_nested(dir, I_MUTEX_PARENT); + locked = true; + retry: - work = lookup_one_len(OVL_WORKDIR_NAME, dentry, - strlen(OVL_WORKDIR_NAME)); + work = lookup_one_len(name, dentry, strlen(name)); if (!IS_ERR(work)) { struct iattr attr = { @@ -446,6 +451,9 @@ retry: if (retried) goto out_dput; + if (persist) + goto out_unlock; + retried = true; ovl_workdir_cleanup(dir, mnt, work, 0); dput(work); @@ -485,16 +493,24 @@ retry: inode_unlock(work->d_inode); if (err) goto out_dput; + } else { + err = PTR_ERR(work); + goto out_err; } out_unlock: - inode_unlock(dir); mnt_drop_write(mnt); + if (locked) + inode_unlock(dir); return work; out_dput: dput(work); - work = ERR_PTR(err); +out_err: + pr_warn("overlayfs: failed to create directory %s/%s (errno: %i); mounting read-only\n", + ufs->config.workdir, name, -err); + sb->s_flags |= MS_RDONLY; + work = NULL; goto out_unlock; } @@ -906,15 +922,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) sb->s_time_gran = ufs->upper_mnt->mnt_sb->s_time_gran; - ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); - err = PTR_ERR(ufs->workdir); - if (IS_ERR(ufs->workdir)) { - pr_warn("overlayfs: failed to create directory %s/%s (errno: %i); mounting read-only\n", - ufs->config.workdir, OVL_WORKDIR_NAME, -err); - sb->s_flags |= MS_RDONLY; - ufs->workdir = NULL; - } - + ufs->workdir = ovl_workdir_create(sb, ufs, workpath.dentry, + OVL_WORKDIR_NAME, false); /* * Upper should support d_type, else whiteouts are visible. * Given workdir and upper are on same fs, we can do -- cgit From 02bcd1577400b0b2eab806ccb9f72d6b5ec7bcca Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:36 +0300 Subject: ovl: introduce the inodes index dir feature Create the index dir on mount. The index dir will contain hardlinks to upper inodes, named after the hex representation of their origin lower inodes. The index dir is going to be used to prevent breaking lower hardlinks on copy up and to implement overlayfs NFS export. Because the feature is not fully backward compat, enabling the feature is opt-in by config/module/mount option. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/Kconfig | 20 +++++++++++++++ fs/overlayfs/copy_up.c | 9 +++---- fs/overlayfs/overlayfs.h | 2 ++ fs/overlayfs/ovl_entry.h | 3 +++ fs/overlayfs/super.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++-- fs/overlayfs/util.c | 15 +++++++++++ 6 files changed, 108 insertions(+), 7 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig index c0c9683934b7..cbfc196e5dc5 100644 --- a/fs/overlayfs/Kconfig +++ b/fs/overlayfs/Kconfig @@ -23,3 +23,23 @@ config OVERLAY_FS_REDIRECT_DIR Note, that redirects are not backward compatible. That is, mounting an overlay which has redirects on a kernel that doesn't support this feature will have unexpected results. + +config OVERLAY_FS_INDEX + bool "Overlayfs: turn on inodes index feature by default" + depends on OVERLAY_FS + help + If this config option is enabled then overlay filesystems will use + the inodes index dir to map lower inodes to upper inodes by default. + In this case it is still possible to turn off index globally with the + "index=off" module option or on a filesystem instance basis with the + "index=off" mount option. + + The inodes index feature prevents breaking of lower hardlinks on copy + up. + + Note, that the inodes index feature is read-only backward compatible. + That is, mounting an overlay which has an index dir on a kernel that + doesn't support this feature read-only, will not have any negative + outcomes. However, mounting the same overlay with an old kernel + read-write and then mounting it again with a new kernel, will have + unexpected results. diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 87289b9a152c..f9f51cce3c18 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -233,12 +233,13 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) return err; } -static struct ovl_fh *ovl_encode_fh(struct dentry *lower, uuid_t *uuid) +static struct ovl_fh *ovl_encode_fh(struct dentry *lower) { struct ovl_fh *fh; int fh_type, fh_len, dwords; void *buf; int buflen = MAX_HANDLE_SZ; + uuid_t *uuid = &lower->d_sb->s_uuid; buf = kmalloc(buflen, GFP_TEMPORARY); if (!buf) @@ -283,7 +284,6 @@ out: static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, struct dentry *upper) { - struct super_block *sb = lower->d_sb; const struct ovl_fh *fh = NULL; int err; @@ -292,9 +292,8 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, * so we can use the overlay.origin xattr to distignuish between a copy * up and a pure upper inode. */ - if (sb->s_export_op && sb->s_export_op->fh_to_dentry && - !uuid_is_null(&sb->s_uuid)) { - fh = ovl_encode_fh(lower, &sb->s_uuid); + if (ovl_can_decode_fh(lower->d_sb)) { + fh = ovl_encode_fh(lower); if (IS_ERR(fh)) return PTR_ERR(fh); } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 5e958427463d..4e7a74e99d3c 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -183,6 +183,8 @@ void ovl_drop_write(struct dentry *dentry); struct dentry *ovl_workdir(struct dentry *dentry); const struct cred *ovl_override_creds(struct super_block *sb); struct super_block *ovl_same_sb(struct super_block *sb); +bool ovl_can_decode_fh(struct super_block *sb); +struct dentry *ovl_indexdir(struct super_block *sb); struct ovl_entry *ovl_alloc_entry(unsigned int numlower); bool ovl_dentry_remote(struct dentry *dentry); bool ovl_dentry_weird(struct dentry *dentry); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 5b5a32116424..9642ec64467b 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -14,6 +14,7 @@ struct ovl_config { char *workdir; bool default_permissions; bool redirect_dir; + bool index; }; /* private information held for overlayfs's superblock */ @@ -25,6 +26,8 @@ struct ovl_fs { struct dentry *workbasedir; /* workdir is the 'work' directory under workbasedir */ struct dentry *workdir; + /* index directory listing overlay inodes by origin file handle */ + struct dentry *indexdir; long namelen; /* pathnames of lower and upper dirs, for show_options */ struct ovl_config config; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index fea7bd496f2e..fa83b3245124 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -34,6 +34,11 @@ module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644); MODULE_PARM_DESC(ovl_redirect_dir_def, "Default to on or off for the redirect_dir feature"); +static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX); +module_param_named(index, ovl_index_def, bool, 0644); +MODULE_PARM_DESC(ovl_index_def, + "Default to on or off for the inodes index feature"); + static void ovl_dentry_release(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; @@ -203,6 +208,7 @@ static void ovl_put_super(struct super_block *sb) struct ovl_fs *ufs = sb->s_fs_info; unsigned i; + dput(ufs->indexdir); dput(ufs->workdir); ovl_inuse_unlock(ufs->workbasedir); dput(ufs->workbasedir); @@ -265,6 +271,12 @@ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf) return err; } +/* Will this overlay be forced to mount/remount ro? */ +static bool ovl_force_readonly(struct ovl_fs *ufs) +{ + return (!ufs->upper_mnt || !ufs->workdir); +} + /** * ovl_show_options * @@ -286,6 +298,9 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) if (ufs->config.redirect_dir != ovl_redirect_dir_def) seq_printf(m, ",redirect_dir=%s", ufs->config.redirect_dir ? "on" : "off"); + if (ufs->config.index != ovl_index_def) + seq_printf(m, ",index=%s", + ufs->config.index ? "on" : "off"); return 0; } @@ -293,7 +308,7 @@ static int ovl_remount(struct super_block *sb, int *flags, char *data) { struct ovl_fs *ufs = sb->s_fs_info; - if (!(*flags & MS_RDONLY) && (!ufs->upper_mnt || !ufs->workdir)) + if (!(*flags & MS_RDONLY) && ovl_force_readonly(ufs)) return -EROFS; return 0; @@ -317,6 +332,8 @@ enum { OPT_DEFAULT_PERMISSIONS, OPT_REDIRECT_DIR_ON, OPT_REDIRECT_DIR_OFF, + OPT_INDEX_ON, + OPT_INDEX_OFF, OPT_ERR, }; @@ -327,6 +344,8 @@ static const match_table_t ovl_tokens = { {OPT_DEFAULT_PERMISSIONS, "default_permissions"}, {OPT_REDIRECT_DIR_ON, "redirect_dir=on"}, {OPT_REDIRECT_DIR_OFF, "redirect_dir=off"}, + {OPT_INDEX_ON, "index=on"}, + {OPT_INDEX_OFF, "index=off"}, {OPT_ERR, NULL} }; @@ -399,6 +418,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) config->redirect_dir = false; break; + case OPT_INDEX_ON: + config->index = true; + break; + + case OPT_INDEX_OFF: + config->index = false; + break; + default: pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p); return -EINVAL; @@ -417,6 +444,7 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) } #define OVL_WORKDIR_NAME "work" +#define OVL_INDEXDIR_NAME "index" static struct dentry *ovl_workdir_create(struct super_block *sb, struct ovl_fs *ufs, @@ -610,6 +638,15 @@ static int ovl_lower_dir(const char *name, struct path *path, if (ovl_dentry_remote(path->dentry)) *remote = true; + /* + * The inodes index feature needs to encode and decode file + * handles, so it requires that all layers support them. + */ + if (ofs->config.index && !ovl_can_decode_fh(path->dentry->d_sb)) { + ofs->config.index = false; + pr_warn("overlayfs: fs on '%s' does not support file handles, falling back to index=off.\n", name); + } + return 0; out_put: @@ -807,6 +844,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out; ufs->config.redirect_dir = ovl_redirect_dir_def; + ufs->config.index = ovl_index_def; err = ovl_parse_opt((char *) data, &ufs->config); if (err) goto out_free_config; @@ -965,6 +1003,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) } else { vfs_removexattr(ufs->workdir, OVL_XATTR_OPAQUE); } + + /* Check if upper/work fs supports file handles */ + if (ufs->config.index && + !ovl_can_decode_fh(ufs->workdir->d_sb)) { + ufs->config.index = false; + pr_warn("overlayfs: upper fs does not support file handles, falling back to index=off.\n"); + } } } @@ -1002,6 +1047,21 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) else if (ufs->upper_mnt->mnt_sb != ufs->same_sb) ufs->same_sb = NULL; + if (!(ovl_force_readonly(ufs)) && ufs->config.index) { + ufs->indexdir = ovl_workdir_create(sb, ufs, workpath.dentry, + OVL_INDEXDIR_NAME, true); + err = PTR_ERR(ufs->indexdir); + if (IS_ERR(ufs->indexdir)) + goto out_put_lower_mnt; + + if (!ufs->indexdir) + pr_warn("overlayfs: try deleting index dir or mounting with '-o index=off' to disable inodes index.\n"); + } + + /* Show index=off/on in /proc/mounts for any of the reasons above */ + if (!ufs->indexdir) + ufs->config.index = false; + if (remote) sb->s_d_op = &ovl_reval_dentry_operations; else @@ -1009,7 +1069,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ufs->creator_cred = cred = prepare_creds(); if (!cred) - goto out_put_lower_mnt; + goto out_put_indexdir; /* Never override disk quota limits or use reserved space */ cap_lower(cred->cap_effective, CAP_SYS_RESOURCE); @@ -1058,6 +1118,8 @@ out_free_oe: kfree(oe); out_put_cred: put_cred(ufs->creator_cred); +out_put_indexdir: + dput(ufs->indexdir); out_put_lower_mnt: for (i = 0; i < ufs->numlower; i++) mntput(ufs->lower_mnt[i]); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index adccd74162d6..90b50b8e75ab 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include "overlayfs.h" #include "ovl_entry.h" @@ -47,6 +49,19 @@ struct super_block *ovl_same_sb(struct super_block *sb) return ofs->same_sb; } +bool ovl_can_decode_fh(struct super_block *sb) +{ + return (sb->s_export_op && sb->s_export_op->fh_to_dentry && + !uuid_is_null(&sb->s_uuid)); +} + +struct dentry *ovl_indexdir(struct super_block *sb) +{ + struct ovl_fs *ofs = sb->s_fs_info; + + return ofs->indexdir; +} + struct ovl_entry *ovl_alloc_entry(unsigned int numlower) { size_t size = offsetof(struct ovl_entry, lowerstack[numlower]); -- cgit From 8b88a2e6403638b56556ed5b1c60d9318eefea9c Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:37 +0300 Subject: ovl: verify upper root dir matches lower root dir When inodes index feature is enabled, verify that the file handle stored in upper root dir matches the lower root dir or fail to mount. If upper root dir has no stored file handle, encode and store the lower root dir file handle in overlay.origin xattr. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 2 +- fs/overlayfs/namei.c | 103 ++++++++++++++++++++++++++++++++++++++++------- fs/overlayfs/overlayfs.h | 3 ++ fs/overlayfs/super.c | 8 ++++ 4 files changed, 101 insertions(+), 15 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index f9f51cce3c18..42807cb57da0 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -233,7 +233,7 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) return err; } -static struct ovl_fh *ovl_encode_fh(struct dentry *lower) +struct ovl_fh *ovl_encode_fh(struct dentry *lower) { struct ovl_fh *fh; int fh_type, fh_len, dwords; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 6485beddaa1f..197b53d34861 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -88,13 +88,10 @@ static int ovl_acceptable(void *ctx, struct dentry *dentry) return 1; } -static struct dentry *ovl_get_origin(struct dentry *dentry, - struct vfsmount *mnt) +static struct ovl_fh *ovl_get_origin_fh(struct dentry *dentry) { int res; struct ovl_fh *fh = NULL; - struct dentry *origin = NULL; - int bytes; res = vfs_getxattr(dentry, OVL_XATTR_ORIGIN, NULL, 0); if (res < 0) { @@ -106,7 +103,7 @@ static struct dentry *ovl_get_origin(struct dentry *dentry, if (res == 0) return NULL; - fh = kzalloc(res, GFP_TEMPORARY); + fh = kzalloc(res, GFP_TEMPORARY); if (!fh) return ERR_PTR(-ENOMEM); @@ -129,7 +126,29 @@ static struct dentry *ovl_get_origin(struct dentry *dentry, (fh->flags & OVL_FH_FLAG_BIG_ENDIAN) != OVL_FH_FLAG_CPU_ENDIAN) goto out; - bytes = (fh->len - offsetof(struct ovl_fh, fid)); + return fh; + +out: + kfree(fh); + return NULL; + +fail: + pr_warn_ratelimited("overlayfs: failed to get origin (%i)\n", res); + goto out; +invalid: + pr_warn_ratelimited("overlayfs: invalid origin (%*phN)\n", res, fh); + goto out; +} + +static struct dentry *ovl_get_origin(struct dentry *dentry, + struct vfsmount *mnt) +{ + struct dentry *origin = NULL; + struct ovl_fh *fh = ovl_get_origin_fh(dentry); + int bytes; + + if (IS_ERR_OR_NULL(fh)) + return (struct dentry *)fh; /* * Make sure that the stored uuid matches the uuid of the lower @@ -138,6 +157,7 @@ static struct dentry *ovl_get_origin(struct dentry *dentry, if (!uuid_equal(&fh->uuid, &mnt->mnt_sb->s_uuid)) goto out; + bytes = (fh->len - offsetof(struct ovl_fh, fid)); origin = exportfs_decode_fh(mnt, (struct fid *)fh->fid, bytes >> 2, (int)fh->type, ovl_acceptable, NULL); @@ -149,21 +169,17 @@ static struct dentry *ovl_get_origin(struct dentry *dentry, } if (ovl_dentry_weird(origin) || - ((d_inode(origin)->i_mode ^ d_inode(dentry)->i_mode) & S_IFMT)) { - dput(origin); - origin = NULL; + ((d_inode(origin)->i_mode ^ d_inode(dentry)->i_mode) & S_IFMT)) goto invalid; - } out: kfree(fh); return origin; -fail: - pr_warn_ratelimited("overlayfs: failed to get origin (%i)\n", res); - goto out; invalid: - pr_warn_ratelimited("overlayfs: invalid origin (%*phN)\n", res, fh); + pr_warn_ratelimited("overlayfs: invalid origin (%pd2)\n", origin); + dput(origin); + origin = NULL; goto out; } @@ -303,6 +319,65 @@ static int ovl_check_origin(struct dentry *dentry, struct dentry *upperdentry, return 0; } +/* + * Verify that @fh matches the origin file handle stored in OVL_XATTR_ORIGIN. + * Return 0 on match, -ESTALE on mismatch, < 0 on error. + */ +static int ovl_verify_origin_fh(struct dentry *dentry, const struct ovl_fh *fh) +{ + struct ovl_fh *ofh = ovl_get_origin_fh(dentry); + int err = 0; + + if (!ofh) + return -ENODATA; + + if (IS_ERR(ofh)) + return PTR_ERR(ofh); + + if (fh->len != ofh->len || memcmp(fh, ofh, fh->len)) + err = -ESTALE; + + kfree(ofh); + return err; +} + +/* + * Verify that an inode matches the origin file handle stored in upper inode. + * + * If @set is true and there is no stored file handle, encode and store origin + * file handle in OVL_XATTR_ORIGIN. + * + * Return 0 on match, -ESTALE on mismatch, < 0 on error. + */ +int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, + struct dentry *origin, bool set) +{ + struct inode *inode; + struct ovl_fh *fh; + int err; + + fh = ovl_encode_fh(origin); + err = PTR_ERR(fh); + if (IS_ERR(fh)) + goto fail; + + err = ovl_verify_origin_fh(dentry, fh); + if (set && err == -ENODATA) + err = ovl_do_setxattr(dentry, OVL_XATTR_ORIGIN, fh, fh->len, 0); + if (err) + goto fail; + +out: + kfree(fh); + return err; + +fail: + inode = d_inode(origin); + pr_warn_ratelimited("overlayfs: failed to verify origin (%pd2, ino=%lu, err=%i)\n", + origin, inode ? inode->i_ino : 0, err); + goto out; +} + /* * Returns next layer in stack starting from top. * Returns -1 if this is the last layer. diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 4e7a74e99d3c..38ac84cba6ea 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -232,6 +232,8 @@ static inline bool ovl_is_impuredir(struct dentry *dentry) /* namei.c */ +int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, + struct dentry *origin, bool set); int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); bool ovl_lower_positive(struct dentry *dentry); @@ -290,3 +292,4 @@ int ovl_copy_up(struct dentry *dentry); int ovl_copy_up_flags(struct dentry *dentry, int flags); int ovl_copy_xattr(struct dentry *old, struct dentry *new); int ovl_set_attr(struct dentry *upper, struct kstat *stat); +struct ovl_fh *ovl_encode_fh(struct dentry *lower); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index fa83b3245124..bfdcff0f3168 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1048,6 +1048,14 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ufs->same_sb = NULL; if (!(ovl_force_readonly(ufs)) && ufs->config.index) { + /* Verify lower root is upper root origin */ + err = ovl_verify_origin(upperpath.dentry, ufs->lower_mnt[0], + stack[0].dentry, true); + if (err) { + pr_err("overlayfs: failed to verify upper root origin\n"); + goto out_put_lower_mnt; + } + ufs->indexdir = ovl_workdir_create(sb, ufs, workpath.dentry, OVL_INDEXDIR_NAME, true); err = PTR_ERR(ufs->indexdir); -- cgit From 54fb347e836faadaed2a5617fb4dd4a4597d0490 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:38 +0300 Subject: ovl: verify index dir matches upper dir An index dir contains persistent hardlinks to files in upper dir. Therefore, we must never mount an existing index dir with a differnt upper dir. Store the upper root dir file handle in index dir inode when index dir is created and verify the file handle before using an existing index dir on mount. Add an 'is_upper' flag to the overlay file handle encoding and set it when encoding the upper root file handle. This is not critical for index dir verification, but it is good practice towards a standard overlayfs file handle format for NFS export. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 12 ++++++++++-- fs/overlayfs/namei.c | 4 ++-- fs/overlayfs/overlayfs.h | 6 ++++-- fs/overlayfs/super.c | 13 +++++++++++-- 4 files changed, 27 insertions(+), 8 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 42807cb57da0..5e8fd99557e1 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -233,7 +233,7 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) return err; } -struct ovl_fh *ovl_encode_fh(struct dentry *lower) +struct ovl_fh *ovl_encode_fh(struct dentry *lower, bool is_upper) { struct ovl_fh *fh; int fh_type, fh_len, dwords; @@ -272,6 +272,14 @@ struct ovl_fh *ovl_encode_fh(struct dentry *lower) fh->magic = OVL_FH_MAGIC; fh->type = fh_type; fh->flags = OVL_FH_FLAG_CPU_ENDIAN; + /* + * When we will want to decode an overlay dentry from this handle + * and all layers are on the same fs, if we get a disconncted real + * dentry when we decode fid, the only way to tell if we should assign + * it to upperdentry or to lowerstack is by checking this flag. + */ + if (is_upper) + fh->flags |= OVL_FH_FLAG_PATH_UPPER; fh->len = fh_len; fh->uuid = *uuid; memcpy(fh->fid, buf, buflen); @@ -293,7 +301,7 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, * up and a pure upper inode. */ if (ovl_can_decode_fh(lower->d_sb)) { - fh = ovl_encode_fh(lower); + fh = ovl_encode_fh(lower, false); if (IS_ERR(fh)) return PTR_ERR(fh); } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 197b53d34861..0c816e9aa50c 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -350,13 +350,13 @@ static int ovl_verify_origin_fh(struct dentry *dentry, const struct ovl_fh *fh) * Return 0 on match, -ESTALE on mismatch, < 0 on error. */ int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, - struct dentry *origin, bool set) + struct dentry *origin, bool is_upper, bool set) { struct inode *inode; struct ovl_fh *fh; int err; - fh = ovl_encode_fh(origin); + fh = ovl_encode_fh(origin, is_upper); err = PTR_ERR(fh); if (IS_ERR(fh)) goto fail; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 38ac84cba6ea..58bbd135a7b3 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -42,6 +42,8 @@ enum ovl_flag { /* CPU byte order required for fid decoding: */ #define OVL_FH_FLAG_BIG_ENDIAN (1 << 0) #define OVL_FH_FLAG_ANY_ENDIAN (1 << 1) +/* Is the real inode encoded in fid an upper inode? */ +#define OVL_FH_FLAG_PATH_UPPER (1 << 2) #define OVL_FH_FLAG_ALL (OVL_FH_FLAG_BIG_ENDIAN | OVL_FH_FLAG_ANY_ENDIAN) @@ -233,7 +235,7 @@ static inline bool ovl_is_impuredir(struct dentry *dentry) /* namei.c */ int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, - struct dentry *origin, bool set); + struct dentry *origin, bool is_upper, bool set); int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); bool ovl_lower_positive(struct dentry *dentry); @@ -292,4 +294,4 @@ int ovl_copy_up(struct dentry *dentry); int ovl_copy_up_flags(struct dentry *dentry, int flags); int ovl_copy_xattr(struct dentry *old, struct dentry *new); int ovl_set_attr(struct dentry *upper, struct kstat *stat); -struct ovl_fh *ovl_encode_fh(struct dentry *lower); +struct ovl_fh *ovl_encode_fh(struct dentry *lower, bool is_upper); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index bfdcff0f3168..a313af25dac2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1050,7 +1050,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (!(ovl_force_readonly(ufs)) && ufs->config.index) { /* Verify lower root is upper root origin */ err = ovl_verify_origin(upperpath.dentry, ufs->lower_mnt[0], - stack[0].dentry, true); + stack[0].dentry, false, true); if (err) { pr_err("overlayfs: failed to verify upper root origin\n"); goto out_put_lower_mnt; @@ -1062,8 +1062,17 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (IS_ERR(ufs->indexdir)) goto out_put_lower_mnt; - if (!ufs->indexdir) + if (ufs->indexdir) { + /* Verify upper root is index dir origin */ + err = ovl_verify_origin(ufs->indexdir, ufs->upper_mnt, + upperpath.dentry, true, true); + if (err) + pr_err("overlayfs: failed to verify index dir origin\n"); + } + if (err || !ufs->indexdir) pr_warn("overlayfs: try deleting index dir or mounting with '-o index=off' to disable inodes index.\n"); + if (err) + goto out_put_indexdir; } /* Show index=off/on in /proc/mounts for any of the reasons above */ -- cgit From 359f392ca53e9122cafa5fc103545558b0b85d54 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:41 +0300 Subject: ovl: lookup index entry for copy up origin When inodes index feature is enabled, lookup in indexdir for the index entry of lower real inode or copy up origin inode. The index entry name is the hex representation of the lower inode file handle. If the index dentry in negative, then either no lower aliases have been copied up yet, or aliases have been copied up in older kernels and are not indexed. If the index dentry for a copy up origin inode is positive, but points to an inode different than the upper inode, then either the upper inode has been copied up and not indexed or it was indexed, but since then index dir was cleared. Either way, that index cannot be used to indentify the overlay inode. If a positive dentry that matches the upper inode was found, then it is safe to use the copy up origin st_ino for upper hardlinks, because all indexed upper hardlinks are represented by the same overlay inode as the copy up origin. Set the INDEX type flag on an indexed upper dentry. A non-upper dentry may also have a positive index from copy up of another lower hardlink. This situation will be handled by following patches. Index lookup is going to be used to prevent breaking hardlinks on copy up. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 8 +++- fs/overlayfs/namei.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++ fs/overlayfs/overlayfs.h | 2 + 3 files changed, 116 insertions(+), 2 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 23d64d51f331..35bb956af8e8 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -96,11 +96,15 @@ int ovl_getattr(const struct path *path, struct kstat *stat, WARN_ON_ONCE(stat->dev != lowerstat.dev); /* - * Lower hardlinks are broken on copy up to different + * Lower hardlinks may be broken on copy up to different * upper files, so we cannot use the lower origin st_ino * for those different files, even for the same fs case. + * With inodes index enabled, it is safe to use st_ino + * of an indexed hardlinked origin. The index validates + * that the upper hardlink is not broken. */ - if (is_dir || lowerstat.nlink == 1) + if (is_dir || lowerstat.nlink == 1 || + ovl_test_flag(OVL_INDEX, d_inode(dentry))) stat->ino = lowerstat.ino; } stat->dev = dentry->d_sb->s_dev; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 0c816e9aa50c..3bec4cb39967 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -378,6 +378,94 @@ fail: goto out; } +/* + * Lookup in indexdir for the index entry of a lower real inode or a copy up + * origin inode. The index entry name is the hex representation of the lower + * inode file handle. + * + * If the index dentry in negative, then either no lower aliases have been + * copied up yet, or aliases have been copied up in older kernels and are + * not indexed. + * + * If the index dentry for a copy up origin inode is positive, but points + * to an inode different than the upper inode, then either the upper inode + * has been copied up and not indexed or it was indexed, but since then + * index dir was cleared. Either way, that index cannot be used to indentify + * the overlay inode. + */ +int ovl_get_index_name(struct dentry *origin, struct qstr *name) +{ + int err; + struct ovl_fh *fh; + char *n, *s; + + fh = ovl_encode_fh(origin, false); + if (IS_ERR(fh)) + return PTR_ERR(fh); + + err = -ENOMEM; + n = kzalloc(fh->len * 2, GFP_TEMPORARY); + if (n) { + s = bin2hex(n, fh, fh->len); + *name = (struct qstr) QSTR_INIT(n, s - n); + err = 0; + } + kfree(fh); + + return err; + +} + +static struct dentry *ovl_lookup_index(struct dentry *dentry, + struct dentry *upper, + struct dentry *origin) +{ + struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + struct dentry *index; + struct inode *inode; + struct qstr name; + int err; + + err = ovl_get_index_name(origin, &name); + if (err) + return ERR_PTR(err); + + index = lookup_one_len_unlocked(name.name, ofs->indexdir, name.len); + if (IS_ERR(index)) { + pr_warn_ratelimited("overlayfs: failed inode index lookup (ino=%lu, key=%*s, err=%i);\n" + "overlayfs: mount with '-o index=off' to disable inodes index.\n", + d_inode(origin)->i_ino, name.len, name.name, + err); + goto out; + } + + if (d_is_negative(index)) { + if (upper && d_inode(origin)->i_nlink > 1) { + pr_warn_ratelimited("overlayfs: hard link with origin but no index (ino=%lu).\n", + d_inode(origin)->i_ino); + goto fail; + } + + dput(index); + index = NULL; + } else if (upper && d_inode(index) != d_inode(upper)) { + inode = d_inode(index); + pr_warn_ratelimited("overlayfs: wrong index found (index ino: %lu, upper ino: %lu).\n", + d_inode(index)->i_ino, + d_inode(upper)->i_ino); + goto fail; + } + +out: + kfree(name.name); + return index; + +fail: + dput(index); + index = ERR_PTR(-EIO); + goto out; +} + /* * Returns next layer in stack starting from top. * Returns -1 if this is the last layer. @@ -409,6 +497,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata; struct path *stack = NULL; struct dentry *upperdir, *upperdentry = NULL; + struct dentry *index = NULL; unsigned int ctr = 0; struct inode *inode = NULL; bool upperopaque = false; @@ -506,6 +595,18 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } + /* Lookup index by lower inode and verify it matches upper inode */ + if (ctr && !d.is_dir && ovl_indexdir(dentry->d_sb)) { + struct dentry *origin = stack[0].dentry; + + index = ovl_lookup_index(dentry, upperdentry, origin); + if (IS_ERR(index)) { + err = PTR_ERR(index); + index = NULL; + goto out_put; + } + } + oe = ovl_alloc_entry(ctr); err = -ENOMEM; if (!oe) @@ -515,6 +616,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); dentry->d_fsdata = oe; + if (index && !upperdentry) + upperdentry = dget(index); + if (upperdentry || ctr) { err = -ENOMEM; inode = ovl_get_inode(dentry, upperdentry); @@ -522,9 +626,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, goto out_free_oe; OVL_I(inode)->redirect = upperredirect; + if (index) + ovl_set_flag(OVL_INDEX, inode); } revert_creds(old_cred); + dput(index); kfree(stack); kfree(d.redirect); d_add(dentry, inode); @@ -535,6 +642,7 @@ out_free_oe: dentry->d_fsdata = NULL; kfree(oe); out_put: + dput(index); for (i = 0; i < ctr; i++) dput(stack[i].dentry); kfree(stack); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 58bbd135a7b3..437a0301e1b6 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -28,6 +28,7 @@ enum ovl_path_type { enum ovl_flag { OVL_IMPURE, + OVL_INDEX, }; /* @@ -236,6 +237,7 @@ static inline bool ovl_is_impuredir(struct dentry *dentry) /* namei.c */ int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, struct dentry *origin, bool is_upper, bool set); +int ovl_get_index_name(struct dentry *origin, struct qstr *name); int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); bool ovl_lower_positive(struct dentry *dentry); -- cgit From 415543d5c64fe490b4b6a7e21c3ea2f1310c442f Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 15:28:42 +0300 Subject: ovl: cleanup bad and stale index entries on mount Bad index entries are entries whose name does not match the origin file handle stored in trusted.overlay.origin xattr. Bad index entries could be a result of a system power off in the middle of copy up. Stale index entries are entries whose origin file handle is stale. Stale index entries could be a result of copying layers or removing lower entries while the overlay is not mounted. The case of copying layers should be detected earlier by the verification of upper root dir origin and index dir origin. Both bad and stale index entries are detected and removed on mount. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 4 ++- fs/overlayfs/namei.c | 74 ++++++++++++++++++++++++++++++++++++++++++------ fs/overlayfs/overlayfs.h | 6 +++- fs/overlayfs/readdir.c | 50 ++++++++++++++++++++++++++++++++ fs/overlayfs/super.c | 6 ++++ 5 files changed, 130 insertions(+), 10 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index d0d6292e069a..a072c27e03bc 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -24,7 +24,7 @@ module_param_named(redirect_max, ovl_redirect_max, ushort, 0644); MODULE_PARM_DESC(ovl_redirect_max, "Maximum length of absolute redirect xattr value"); -void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) +int ovl_cleanup(struct inode *wdir, struct dentry *wdentry) { int err; @@ -39,6 +39,8 @@ void ovl_cleanup(struct inode *wdir, struct dentry *wdentry) pr_err("overlayfs: cleanup of '%pd2' failed (%i)\n", wdentry, err); } + + return err; } struct dentry *ovl_lookup_temp(struct dentry *workdir) diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 3bec4cb39967..4df37e805eb7 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -285,17 +285,17 @@ static int ovl_lookup_layer(struct dentry *base, struct ovl_lookup_data *d, } -static int ovl_check_origin(struct dentry *dentry, struct dentry *upperdentry, +static int ovl_check_origin(struct dentry *upperdentry, + struct path *lowerstack, unsigned int numlower, struct path **stackp, unsigned int *ctrp) { - struct ovl_entry *roe = dentry->d_sb->s_root->d_fsdata; struct vfsmount *mnt; struct dentry *origin = NULL; int i; - for (i = 0; i < roe->numlower; i++) { - mnt = roe->lowerstack[i].mnt; + for (i = 0; i < numlower; i++) { + mnt = lowerstack[i].mnt; origin = ovl_get_origin(upperdentry, mnt); if (IS_ERR(origin)) return PTR_ERR(origin); @@ -307,8 +307,9 @@ static int ovl_check_origin(struct dentry *dentry, struct dentry *upperdentry, if (!origin) return 0; - BUG_ON(*stackp || *ctrp); - *stackp = kmalloc(sizeof(struct path), GFP_TEMPORARY); + BUG_ON(*ctrp); + if (!*stackp) + *stackp = kmalloc(sizeof(struct path), GFP_TEMPORARY); if (!*stackp) { dput(origin); return -ENOMEM; @@ -378,6 +379,63 @@ fail: goto out; } +/* + * Verify that an index entry name matches the origin file handle stored in + * OVL_XATTR_ORIGIN and that origin file handle can be decoded to lower path. + * Return 0 on match, -ESTALE on mismatch or stale origin, < 0 on error. + */ +int ovl_verify_index(struct dentry *index, struct path *lowerstack, + unsigned int numlower) +{ + struct ovl_fh *fh = NULL; + size_t len; + struct path origin = { }; + struct path *stack = &origin; + unsigned int ctr = 0; + int err; + + if (!d_inode(index)) + return 0; + + err = -EISDIR; + if (d_is_dir(index)) + goto fail; + + err = -EINVAL; + if (index->d_name.len < sizeof(struct ovl_fh)*2) + goto fail; + + err = -ENOMEM; + len = index->d_name.len / 2; + fh = kzalloc(len, GFP_TEMPORARY); + if (!fh) + goto fail; + + err = -EINVAL; + if (hex2bin((u8 *)fh, index->d_name.name, len) || len != fh->len) + goto fail; + + err = ovl_verify_origin_fh(index, fh); + if (err) + goto fail; + + err = ovl_check_origin(index, lowerstack, numlower, &stack, &ctr); + if (!err && !ctr) + err = -ESTALE; + if (err) + goto fail; + + dput(origin.dentry); +out: + kfree(fh); + return err; + +fail: + pr_warn_ratelimited("overlayfs: failed to verify index (%pd2, err=%i)\n", + index, err); + goto out; +} + /* * Lookup in indexdir for the index entry of a lower real inode or a copy up * origin inode. The index entry name is the hex representation of the lower @@ -541,8 +599,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, * number - it's the same as if we held a reference * to a dentry in lower layer that was moved under us. */ - err = ovl_check_origin(dentry, upperdentry, - &stack, &ctr); + err = ovl_check_origin(upperdentry, roe->lowerstack, + roe->numlower, &stack, &ctr); if (err) goto out; } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 437a0301e1b6..f3e49cf34517 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -237,6 +237,8 @@ static inline bool ovl_is_impuredir(struct dentry *dentry) /* namei.c */ int ovl_verify_origin(struct dentry *dentry, struct vfsmount *mnt, struct dentry *origin, bool is_upper, bool set); +int ovl_verify_index(struct dentry *index, struct path *lowerstack, + unsigned int numlower); int ovl_get_index_name(struct dentry *origin, struct qstr *name); int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); @@ -250,6 +252,8 @@ void ovl_cache_free(struct list_head *list); int ovl_check_d_type_supported(struct path *realpath); void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, struct dentry *dentry, int level); +int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt, + struct path *lowerstack, unsigned int numlower); /* inode.c */ int ovl_setattr(struct dentry *dentry, struct iattr *attr); @@ -289,7 +293,7 @@ struct cattr { int ovl_create_real(struct inode *dir, struct dentry *newdentry, struct cattr *attr, struct dentry *hardlink, bool debug); -void ovl_cleanup(struct inode *dir, struct dentry *dentry); +int ovl_cleanup(struct inode *dir, struct dentry *dentry); /* copy_up.c */ int ovl_copy_up(struct dentry *dentry); diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index f241b4ee3d8a..0298463cf9c3 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -667,3 +667,53 @@ void ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, ovl_cleanup(dir, dentry); } } + +int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt, + struct path *lowerstack, unsigned int numlower) +{ + int err; + struct inode *dir = dentry->d_inode; + struct path path = { .mnt = mnt, .dentry = dentry }; + LIST_HEAD(list); + struct ovl_cache_entry *p; + struct ovl_readdir_data rdd = { + .ctx.actor = ovl_fill_merge, + .dentry = NULL, + .list = &list, + .root = RB_ROOT, + .is_lowest = false, + }; + + err = ovl_dir_read(&path, &rdd); + if (err) + goto out; + + inode_lock_nested(dir, I_MUTEX_PARENT); + list_for_each_entry(p, &list, l_node) { + struct dentry *index; + + if (p->name[0] == '.') { + if (p->len == 1) + continue; + if (p->len == 2 && p->name[1] == '.') + continue; + } + index = lookup_one_len(p->name, dentry, p->len); + if (IS_ERR(index)) { + err = PTR_ERR(index); + break; + } + if (ovl_verify_index(index, lowerstack, numlower)) { + err = ovl_cleanup(dir, index); + if (err) + break; + } + dput(index); + } + inode_unlock(dir); +out: + ovl_cache_free(&list); + if (err) + pr_err("overlayfs: failed index dir cleanup (%i)\n", err); + return err; +} diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index a313af25dac2..791581c370f5 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1068,6 +1068,12 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) upperpath.dentry, true, true); if (err) pr_err("overlayfs: failed to verify index dir origin\n"); + + /* Cleanup bad/stale index entries */ + if (!err) + err = ovl_indexdir_cleanup(ufs->indexdir, + ufs->upper_mnt, + stack, numlower); } if (err || !ufs->indexdir) pr_warn("overlayfs: try deleting index dir or mounting with '-o index=off' to disable inodes index.\n"); -- cgit From b9ac5c274b8c9d642567022c0e319bca4db31956 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:17 +0200 Subject: ovl: hash overlay non-dir inodes by copy up origin Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 46 +++++++++++++++++++++++++++++++++++++++++----- fs/overlayfs/namei.c | 4 ++-- fs/overlayfs/util.c | 3 +-- 3 files changed, 44 insertions(+), 9 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 35bb956af8e8..d9fe07defca3 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -467,6 +467,25 @@ static int ovl_inode_set(struct inode *inode, void *data) return 0; } +static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry, + struct dentry *upperdentry) +{ + struct inode *lowerinode = lowerdentry ? d_inode(lowerdentry) : NULL; + + /* Lower (origin) inode must match, even if NULL */ + if (ovl_inode_lower(inode) != lowerinode) + return false; + + /* + * Allow non-NULL __upperdentry in inode even if upperdentry is NULL. + * This happens when finding a lower alias for a copied up hard link. + */ + if (upperdentry && ovl_inode_upper(inode) != d_inode(upperdentry)) + return false; + + return true; +} + struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) { struct dentry *lowerdentry = ovl_dentry_lower(dentry); @@ -476,12 +495,25 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) if (!realinode) realinode = d_inode(lowerdentry); - if (upperdentry && !d_is_dir(upperdentry)) { - inode = iget5_locked(dentry->d_sb, (unsigned long) realinode, - ovl_inode_test, ovl_inode_set, realinode); + if (!S_ISDIR(realinode->i_mode) && + (upperdentry || (lowerdentry && ovl_indexdir(dentry->d_sb)))) { + struct inode *key = d_inode(lowerdentry ?: upperdentry); + + inode = iget5_locked(dentry->d_sb, (unsigned long) key, + ovl_inode_test, ovl_inode_set, key); if (!inode) - goto out; + goto out_nomem; if (!(inode->i_state & I_NEW)) { + /* + * Verify that the underlying files stored in the inode + * match those in the dentry. + */ + if (!ovl_verify_inode(inode, lowerdentry, upperdentry)) { + iput(inode); + inode = ERR_PTR(-ESTALE); + goto out; + } + dput(upperdentry); goto out; } @@ -490,7 +522,7 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) } else { inode = new_inode(dentry->d_sb); if (!inode) - goto out; + goto out_nomem; } ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); ovl_inode_init(inode, upperdentry, lowerdentry); @@ -502,4 +534,8 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) unlock_new_inode(inode); out: return inode; + +out_nomem: + inode = ERR_PTR(-ENOMEM); + goto out; } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 4df37e805eb7..f7fb0c919419 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -678,9 +678,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, upperdentry = dget(index); if (upperdentry || ctr) { - err = -ENOMEM; inode = ovl_get_inode(dentry, upperdentry); - if (!inode) + err = PTR_ERR(inode); + if (IS_ERR(inode)) goto out_free_oe; OVL_I(inode)->redirect = upperredirect; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 90b50b8e75ab..22ed51f80e58 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -236,7 +236,6 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry) { struct inode *upperinode = d_inode(upperdentry); - WARN_ON(!inode_unhashed(inode)); WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); WARN_ON(OVL_I(inode)->__upperdentry); @@ -245,7 +244,7 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry) */ smp_wmb(); OVL_I(inode)->__upperdentry = upperdentry; - if (!S_ISDIR(upperinode->i_mode)) { + if (!S_ISDIR(upperinode->i_mode) && inode_unhashed(inode)) { inode->i_private = upperinode; __insert_inode_hash(inode, (unsigned long) upperinode); } -- cgit From 15932c415b3ed20bd1c1e05d071b4ad498656280 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 16 May 2017 01:26:49 +0300 Subject: ovl: defer upper dir lock to tempfile link On copy up of regular file using an O_TMPFILE, lock upper dir only before linking the tempfile in place. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 67 ++++++++++++++++++++++++++++---------------------- fs/overlayfs/util.c | 1 - 2 files changed, 38 insertions(+), 30 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 5e8fd99557e1..28711af7f9db 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -316,6 +316,35 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, return err; } +static int ovl_install_temp(struct dentry *workdir, struct dentry *upperdir, + struct dentry *dentry, + struct dentry *temp, struct kstat *pstat, + bool tmpfile, struct dentry **newdentry) +{ + int err; + struct dentry *upper; + struct inode *udir = d_inode(upperdir); + + upper = lookup_one_len(dentry->d_name.name, upperdir, + dentry->d_name.len); + if (IS_ERR(upper)) + return PTR_ERR(upper); + + if (tmpfile) + err = ovl_do_link(temp, udir, upper, true); + else + err = ovl_do_rename(d_inode(workdir), temp, udir, upper, 0); + + /* Restore timestamps on parent (best effort) */ + if (!err) { + ovl_set_timestamps(upperdir, pstat); + *newdentry = dget(tmpfile ? upper : temp); + } + dput(upper); + + return err; +} + static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, struct dentry *dentry, struct path *lowerpath, struct kstat *stat, const char *link, @@ -324,7 +353,6 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, struct inode *wdir = workdir->d_inode; struct inode *udir = upperdir->d_inode; struct dentry *newdentry = NULL; - struct dentry *upper = NULL; struct dentry *temp = NULL; int err; const struct cred *old_creds = NULL; @@ -371,16 +399,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, BUG_ON(upperpath.dentry != NULL); upperpath.dentry = temp; - if (tmpfile) { - inode_unlock(udir); - err = ovl_copy_up_data(lowerpath, &upperpath, - stat->size); - inode_lock_nested(udir, I_MUTEX_PARENT); - } else { - err = ovl_copy_up_data(lowerpath, &upperpath, - stat->size); - } - + err = ovl_copy_up_data(lowerpath, &upperpath, stat->size); if (err) goto out_cleanup; } @@ -408,29 +427,21 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, goto out_cleanup; } - upper = lookup_one_len(dentry->d_name.name, upperdir, - dentry->d_name.len); - if (IS_ERR(upper)) { - err = PTR_ERR(upper); - upper = NULL; - goto out_cleanup; + if (tmpfile) { + inode_lock_nested(udir, I_MUTEX_PARENT); + err = ovl_install_temp(workdir, upperdir, dentry, temp, pstat, + tmpfile, &newdentry); + inode_unlock(udir); + } else { + err = ovl_install_temp(workdir, upperdir, dentry, temp, pstat, + tmpfile, &newdentry); } - - if (tmpfile) - err = ovl_do_link(temp, udir, upper, true); - else - err = ovl_do_rename(wdir, temp, udir, upper, 0); if (err) goto out_cleanup; - newdentry = dget(tmpfile ? upper : temp); ovl_inode_update(d_inode(dentry), newdentry); - - /* Restore timestamps on parent (best effort) */ - ovl_set_timestamps(upperdir, pstat); out: dput(temp); - dput(upper); return err; out_cleanup: @@ -496,10 +507,8 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, goto out_done; } - inode_lock_nested(upperdir->d_inode, I_MUTEX_PARENT); err = ovl_copy_up_locked(workdir, upperdir, dentry, lowerpath, stat, link, &pstat, true); - inode_unlock(upperdir->d_inode); ovl_copy_up_end(dentry); goto out_done; } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 22ed51f80e58..c80b4bf1e64f 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -236,7 +236,6 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry) { struct inode *upperinode = d_inode(upperdentry); - WARN_ON(!inode_is_locked(upperdentry->d_parent->d_inode)); WARN_ON(OVL_I(inode)->__upperdentry); /* -- cgit From 7d90b853f932874f0b348858fddbd41f022179ee Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:18 +0200 Subject: ovl: extract helper to get temp file in copy up Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 59 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 18 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 28711af7f9db..a7941ab80c9b 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -345,16 +345,13 @@ static int ovl_install_temp(struct dentry *workdir, struct dentry *upperdir, return err; } -static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, - struct dentry *dentry, struct path *lowerpath, - struct kstat *stat, const char *link, - struct kstat *pstat, bool tmpfile) +static int ovl_get_tmpfile(struct dentry *workdir, struct dentry *upperdir, + struct dentry *dentry, + struct kstat *stat, const char *link, bool tmpfile, + struct dentry **tempp) { - struct inode *wdir = workdir->d_inode; - struct inode *udir = upperdir->d_inode; - struct dentry *newdentry = NULL; - struct dentry *temp = NULL; int err; + struct dentry *temp; const struct cred *old_creds = NULL; struct cred *new_creds = NULL; struct cattr cattr = { @@ -371,24 +368,50 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, if (new_creds) old_creds = override_creds(new_creds); - if (tmpfile) + if (tmpfile) { temp = ovl_do_tmpfile(upperdir, stat->mode); - else + if (IS_ERR(temp)) + goto temp_err; + } else { temp = ovl_lookup_temp(workdir); - err = 0; - if (IS_ERR(temp)) { - err = PTR_ERR(temp); - temp = NULL; + if (IS_ERR(temp)) + goto temp_err; + + err = ovl_create_real(d_inode(workdir), temp, &cattr, + NULL, true); + if (err) { + dput(temp); + goto out; + } } - - if (!err && !tmpfile) - err = ovl_create_real(wdir, temp, &cattr, NULL, true); - + err = 0; + *tempp = temp; +out: if (new_creds) { revert_creds(old_creds); put_cred(new_creds); } + return err; + +temp_err: + err = PTR_ERR(temp); + goto out; +} + +static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, + struct dentry *dentry, struct path *lowerpath, + struct kstat *stat, const char *link, + struct kstat *pstat, bool tmpfile) +{ + struct inode *wdir = workdir->d_inode; + struct inode *udir = upperdir->d_inode; + struct dentry *newdentry = NULL; + struct dentry *temp = NULL; + int err; + + err = ovl_get_tmpfile(workdir, upperdir, dentry, stat, link, tmpfile, + &temp); if (err) goto out; -- cgit From 02209d10709c18d552c2494df74117db09a18e05 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Fri, 19 May 2017 15:16:21 +0300 Subject: ovl: factor out ovl_copy_up_inode() helper Factor out helper for copying lower inode data and metadata to temp upper inode, that is common to copy up using O_TMPFILE and workdir. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 46 +++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 17 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index a7941ab80c9b..81b9a44916a0 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -399,22 +399,11 @@ temp_err: goto out; } -static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, - struct dentry *dentry, struct path *lowerpath, - struct kstat *stat, const char *link, - struct kstat *pstat, bool tmpfile) +static int ovl_copy_up_inode(struct dentry *dentry, struct dentry *temp, + struct path *lowerpath, struct kstat *stat) { - struct inode *wdir = workdir->d_inode; - struct inode *udir = upperdir->d_inode; - struct dentry *newdentry = NULL; - struct dentry *temp = NULL; int err; - err = ovl_get_tmpfile(workdir, upperdir, dentry, stat, link, tmpfile, - &temp); - if (err) - goto out; - if (S_ISREG(stat->mode)) { struct path upperpath; @@ -424,18 +413,18 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, err = ovl_copy_up_data(lowerpath, &upperpath, stat->size); if (err) - goto out_cleanup; + return err; } err = ovl_copy_xattr(lowerpath->dentry, temp); if (err) - goto out_cleanup; + return err; inode_lock(temp->d_inode); err = ovl_set_attr(temp, stat); inode_unlock(temp->d_inode); if (err) - goto out_cleanup; + return err; /* * Store identifier of lower inode in upper inode xattr to @@ -447,9 +436,32 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, if (S_ISDIR(stat->mode) || stat->nlink == 1) { err = ovl_set_origin(dentry, lowerpath->dentry, temp); if (err) - goto out_cleanup; + return err; } + return 0; +} + +static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, + struct dentry *dentry, struct path *lowerpath, + struct kstat *stat, const char *link, + struct kstat *pstat, bool tmpfile) +{ + struct inode *wdir = workdir->d_inode; + struct inode *udir = upperdir->d_inode; + struct dentry *newdentry = NULL; + struct dentry *temp = NULL; + int err; + + err = ovl_get_tmpfile(workdir, upperdir, dentry, stat, link, tmpfile, + &temp); + if (err) + goto out; + + err = ovl_copy_up_inode(dentry, temp, lowerpath, stat); + if (err) + goto out_cleanup; + if (tmpfile) { inode_lock_nested(udir, I_MUTEX_PARENT); err = ovl_install_temp(workdir, upperdir, dentry, temp, pstat, -- cgit From 7ab8b1763fd84ff4e7263ed7f5c728e4cb3f364a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:18 +0200 Subject: ovl: base tmpfile in workdir too Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 81b9a44916a0..1264f2434047 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -345,8 +345,7 @@ static int ovl_install_temp(struct dentry *workdir, struct dentry *upperdir, return err; } -static int ovl_get_tmpfile(struct dentry *workdir, struct dentry *upperdir, - struct dentry *dentry, +static int ovl_get_tmpfile(struct dentry *workdir, struct dentry *dentry, struct kstat *stat, const char *link, bool tmpfile, struct dentry **tempp) { @@ -369,7 +368,7 @@ static int ovl_get_tmpfile(struct dentry *workdir, struct dentry *upperdir, old_creds = override_creds(new_creds); if (tmpfile) { - temp = ovl_do_tmpfile(upperdir, stat->mode); + temp = ovl_do_tmpfile(workdir, stat->mode); if (IS_ERR(temp)) goto temp_err; } else { @@ -453,8 +452,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, struct dentry *temp = NULL; int err; - err = ovl_get_tmpfile(workdir, upperdir, dentry, stat, link, tmpfile, - &temp); + err = ovl_get_tmpfile(workdir, dentry, stat, link, tmpfile, &temp); if (err) goto out; -- cgit From 23f0ab13eaa69b4a351184cbec448be2aad3a3a9 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:18 +0200 Subject: ovl: use struct copy_up_ctx as function argument This cleans up functions with too many arguments. Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 160 ++++++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 82 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 1264f2434047..8f9e26e91386 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -316,38 +316,45 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, return err; } -static int ovl_install_temp(struct dentry *workdir, struct dentry *upperdir, - struct dentry *dentry, - struct dentry *temp, struct kstat *pstat, - bool tmpfile, struct dentry **newdentry) +struct ovl_copy_up_ctx { + struct dentry *dentry; + struct path lowerpath; + struct kstat stat; + struct kstat pstat; + const char *link; + struct dentry *upperdir; + struct dentry *workdir; + bool tmpfile; +}; + +static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, + struct dentry **newdentry) { int err; struct dentry *upper; - struct inode *udir = d_inode(upperdir); + struct inode *udir = d_inode(c->upperdir); - upper = lookup_one_len(dentry->d_name.name, upperdir, - dentry->d_name.len); + upper = lookup_one_len(c->dentry->d_name.name, c->upperdir, + c->dentry->d_name.len); if (IS_ERR(upper)) return PTR_ERR(upper); - if (tmpfile) + if (c->tmpfile) err = ovl_do_link(temp, udir, upper, true); else - err = ovl_do_rename(d_inode(workdir), temp, udir, upper, 0); + err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0); /* Restore timestamps on parent (best effort) */ if (!err) { - ovl_set_timestamps(upperdir, pstat); - *newdentry = dget(tmpfile ? upper : temp); + ovl_set_timestamps(c->upperdir, &c->pstat); + *newdentry = dget(c->tmpfile ? upper : temp); } dput(upper); return err; } -static int ovl_get_tmpfile(struct dentry *workdir, struct dentry *dentry, - struct kstat *stat, const char *link, bool tmpfile, - struct dentry **tempp) +static int ovl_get_tmpfile(struct ovl_copy_up_ctx *c, struct dentry **tempp) { int err; struct dentry *temp; @@ -355,28 +362,28 @@ static int ovl_get_tmpfile(struct dentry *workdir, struct dentry *dentry, struct cred *new_creds = NULL; struct cattr cattr = { /* Can't properly set mode on creation because of the umask */ - .mode = stat->mode & S_IFMT, - .rdev = stat->rdev, - .link = link + .mode = c->stat.mode & S_IFMT, + .rdev = c->stat.rdev, + .link = c->link }; - err = security_inode_copy_up(dentry, &new_creds); + err = security_inode_copy_up(c->dentry, &new_creds); if (err < 0) goto out; if (new_creds) old_creds = override_creds(new_creds); - if (tmpfile) { - temp = ovl_do_tmpfile(workdir, stat->mode); + if (c->tmpfile) { + temp = ovl_do_tmpfile(c->workdir, c->stat.mode); if (IS_ERR(temp)) goto temp_err; } else { - temp = ovl_lookup_temp(workdir); + temp = ovl_lookup_temp(c->workdir); if (IS_ERR(temp)) goto temp_err; - err = ovl_create_real(d_inode(workdir), temp, &cattr, + err = ovl_create_real(d_inode(c->workdir), temp, &cattr, NULL, true); if (err) { dput(temp); @@ -398,29 +405,28 @@ temp_err: goto out; } -static int ovl_copy_up_inode(struct dentry *dentry, struct dentry *temp, - struct path *lowerpath, struct kstat *stat) +static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp) { int err; - if (S_ISREG(stat->mode)) { + if (S_ISREG(c->stat.mode)) { struct path upperpath; - ovl_path_upper(dentry, &upperpath); + ovl_path_upper(c->dentry, &upperpath); BUG_ON(upperpath.dentry != NULL); upperpath.dentry = temp; - err = ovl_copy_up_data(lowerpath, &upperpath, stat->size); + err = ovl_copy_up_data(&c->lowerpath, &upperpath, c->stat.size); if (err) return err; } - err = ovl_copy_xattr(lowerpath->dentry, temp); + err = ovl_copy_xattr(c->lowerpath.dentry, temp); if (err) return err; inode_lock(temp->d_inode); - err = ovl_set_attr(temp, stat); + err = ovl_set_attr(temp, &c->stat); inode_unlock(temp->d_inode); if (err) return err; @@ -432,8 +438,8 @@ static int ovl_copy_up_inode(struct dentry *dentry, struct dentry *temp, * Don't set origin when we are breaking the association with a lower * hard link. */ - if (S_ISDIR(stat->mode) || stat->nlink == 1) { - err = ovl_set_origin(dentry, lowerpath->dentry, temp); + if (S_ISDIR(c->stat.mode) || c->stat.nlink == 1) { + err = ovl_set_origin(c->dentry, c->lowerpath.dentry, temp); if (err) return err; } @@ -441,45 +447,39 @@ static int ovl_copy_up_inode(struct dentry *dentry, struct dentry *temp, return 0; } -static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, - struct dentry *dentry, struct path *lowerpath, - struct kstat *stat, const char *link, - struct kstat *pstat, bool tmpfile) +static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) { - struct inode *wdir = workdir->d_inode; - struct inode *udir = upperdir->d_inode; + struct inode *udir = c->upperdir->d_inode; struct dentry *newdentry = NULL; struct dentry *temp = NULL; int err; - err = ovl_get_tmpfile(workdir, dentry, stat, link, tmpfile, &temp); + err = ovl_get_tmpfile(c, &temp); if (err) goto out; - err = ovl_copy_up_inode(dentry, temp, lowerpath, stat); + err = ovl_copy_up_inode(c, temp); if (err) goto out_cleanup; - if (tmpfile) { + if (c->tmpfile) { inode_lock_nested(udir, I_MUTEX_PARENT); - err = ovl_install_temp(workdir, upperdir, dentry, temp, pstat, - tmpfile, &newdentry); + err = ovl_install_temp(c, temp, &newdentry); inode_unlock(udir); } else { - err = ovl_install_temp(workdir, upperdir, dentry, temp, pstat, - tmpfile, &newdentry); + err = ovl_install_temp(c, temp, &newdentry); } if (err) goto out_cleanup; - ovl_inode_update(d_inode(dentry), newdentry); + ovl_inode_update(d_inode(c->dentry), newdentry); out: dput(temp); return err; out_cleanup: - if (!tmpfile) - ovl_cleanup(wdir, temp); + if (!c->tmpfile) + ovl_cleanup(d_inode(c->workdir), temp); goto out; } @@ -492,75 +492,70 @@ out_cleanup: * is possible that the copy up will lock the old parent. At that point * the file will have already been copied up anyway. */ -static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, - struct path *lowerpath, struct kstat *stat) +static int ovl_copy_up_one(struct dentry *parent, struct ovl_copy_up_ctx *c) { DEFINE_DELAYED_CALL(done); - struct dentry *workdir = ovl_workdir(dentry); int err; - struct kstat pstat; struct path parentpath; - struct dentry *lowerdentry = lowerpath->dentry; - struct dentry *upperdir; - const char *link = NULL; - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; + struct dentry *lowerdentry = c->lowerpath.dentry; + struct ovl_fs *ofs = c->dentry->d_sb->s_fs_info; - if (WARN_ON(!workdir)) + c->workdir = ovl_workdir(c->dentry); + if (WARN_ON(!c->workdir)) return -EROFS; ovl_do_check_copy_up(lowerdentry); ovl_path_upper(parent, &parentpath); - upperdir = parentpath.dentry; + c->upperdir = parentpath.dentry; /* Mark parent "impure" because it may now contain non-pure upper */ - err = ovl_set_impure(parent, upperdir); + err = ovl_set_impure(parent, c->upperdir); if (err) return err; - err = vfs_getattr(&parentpath, &pstat, + err = vfs_getattr(&parentpath, &c->pstat, STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT); if (err) return err; - if (S_ISLNK(stat->mode)) { - link = vfs_get_link(lowerdentry, &done); - if (IS_ERR(link)) - return PTR_ERR(link); + if (S_ISLNK(c->stat.mode)) { + c->link = vfs_get_link(lowerdentry, &done); + if (IS_ERR(c->link)) + return PTR_ERR(c->link); } /* Should we copyup with O_TMPFILE or with workdir? */ - if (S_ISREG(stat->mode) && ofs->tmpfile) { - err = ovl_copy_up_start(dentry); + if (S_ISREG(c->stat.mode) && ofs->tmpfile) { + err = ovl_copy_up_start(c->dentry); /* err < 0: interrupted, err > 0: raced with another copy-up */ if (unlikely(err)) { - pr_debug("ovl_copy_up_start(%pd2) = %i\n", dentry, err); + pr_debug("ovl_copy_up_start(%pd2) = %i\n", c->dentry, + err); if (err > 0) err = 0; goto out_done; } - - err = ovl_copy_up_locked(workdir, upperdir, dentry, lowerpath, - stat, link, &pstat, true); - ovl_copy_up_end(dentry); + c->tmpfile = true; + err = ovl_copy_up_locked(c); + ovl_copy_up_end(c->dentry); goto out_done; } err = -EIO; - if (lock_rename(workdir, upperdir) != NULL) { + if (lock_rename(c->workdir, c->upperdir) != NULL) { pr_err("overlayfs: failed to lock workdir+upperdir\n"); goto out_unlock; } - if (ovl_dentry_upper(dentry)) { + if (ovl_dentry_upper(c->dentry)) { /* Raced with another copy-up? Nothing to do, then... */ err = 0; goto out_unlock; } - err = ovl_copy_up_locked(workdir, upperdir, dentry, lowerpath, - stat, link, &pstat, false); + err = ovl_copy_up_locked(c); out_unlock: - unlock_rename(workdir, upperdir); + unlock_rename(c->workdir, c->upperdir); out_done: do_delayed_call(&done); @@ -575,8 +570,7 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags) while (!err) { struct dentry *next; struct dentry *parent; - struct path lowerpath; - struct kstat stat; + struct ovl_copy_up_ctx ctx = { }; enum ovl_path_type type = ovl_path_type(dentry); if (OVL_TYPE_UPPER(type)) @@ -595,14 +589,16 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags) next = parent; } - ovl_path_lower(next, &lowerpath); - err = vfs_getattr(&lowerpath, &stat, + ovl_path_lower(next, &ctx.lowerpath); + err = vfs_getattr(&ctx.lowerpath, &ctx.stat, STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT); /* maybe truncate regular file. this has no effect on dirs */ if (flags & O_TRUNC) - stat.size = 0; - if (!err) - err = ovl_copy_up_one(parent, next, &lowerpath, &stat); + ctx.stat.size = 0; + if (!err) { + ctx.dentry = next; + err = ovl_copy_up_one(parent, &ctx); + } dput(parent); dput(next); -- cgit From 55acc6618259c8ff0a400a131f0f4b613e96010a Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:18 +0200 Subject: ovl: add flag for upper in ovl_entry For rename, we need to ensure that an upper alias exists for hard links before attempting the operation. Introduce a flag in ovl_entry to track the state of the upper alias. Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 1 + fs/overlayfs/dir.c | 1 + fs/overlayfs/namei.c | 4 +++- fs/overlayfs/overlayfs.h | 2 ++ fs/overlayfs/ovl_entry.h | 5 ++++- fs/overlayfs/super.c | 1 + fs/overlayfs/util.c | 19 +++++++++++++++++++ 7 files changed, 31 insertions(+), 2 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 8f9e26e91386..58c06bd58a96 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -472,6 +472,7 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) if (err) goto out_cleanup; + ovl_dentry_set_upper_alias(c->dentry); ovl_inode_update(d_inode(c->dentry), newdentry); out: dput(temp); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index a072c27e03bc..8b2b23181b19 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -156,6 +156,7 @@ static void ovl_instantiate(struct dentry *dentry, struct inode *inode, struct dentry *newdentry, bool hardlink) { ovl_dentry_version_inc(dentry->d_parent); + ovl_dentry_set_upper_alias(dentry); if (!hardlink) { ovl_inode_update(inode, newdentry); ovl_copyattr(newdentry->d_inode, inode); diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index f7fb0c919419..2d8b6292fe21 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -674,7 +674,9 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); dentry->d_fsdata = oe; - if (index && !upperdentry) + if (upperdentry) + ovl_dentry_set_upper_alias(dentry); + else if (index) upperdentry = dget(index); if (upperdentry || ctr) { diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index f3e49cf34517..751b36a5c22f 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -206,6 +206,8 @@ void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache); bool ovl_dentry_is_opaque(struct dentry *dentry); bool ovl_dentry_is_whiteout(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry); +bool ovl_dentry_has_upper_alias(struct dentry *dentry); +void ovl_dentry_set_upper_alias(struct dentry *dentry); bool ovl_redirect_dir(struct super_block *sb); const char *ovl_dentry_get_redirect(struct dentry *dentry); void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect); diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h index 9642ec64467b..878a750986dd 100644 --- a/fs/overlayfs/ovl_entry.h +++ b/fs/overlayfs/ovl_entry.h @@ -42,7 +42,10 @@ struct ovl_fs { /* private information held for every overlayfs dentry */ struct ovl_entry { union { - bool opaque; + struct { + unsigned long has_upper; + bool opaque; + }; struct rcu_head rcu; }; unsigned numlower; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 791581c370f5..f29ee08cf99f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1119,6 +1119,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) kfree(lowertmp); if (upperpath.dentry) { + oe->has_upper = true; if (ovl_is_impuredir(upperpath.dentry)) ovl_set_flag(OVL_IMPURE, d_inode(root_dentry)); } diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index c80b4bf1e64f..38fa75228c66 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -201,6 +201,25 @@ void ovl_dentry_set_opaque(struct dentry *dentry) oe->opaque = true; } +/* + * For hard links it's possible for ovl_dentry_upper() to return positive, while + * there's no actual upper alias for the inode. Copy up code needs to know + * about the existence of the upper alias, so it can't use ovl_dentry_upper(). + */ +bool ovl_dentry_has_upper_alias(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + return oe->has_upper; +} + +void ovl_dentry_set_upper_alias(struct dentry *dentry) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + oe->has_upper = true; +} + bool ovl_redirect_dir(struct super_block *sb) { struct ovl_fs *ofs = sb->s_fs_info; -- cgit From a6fb235a448b8eb731fd6d4de2c5c6269677cf5b Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:18 +0200 Subject: ovl: rearrange copy up Split up and rearrange copy up functions to make them better readable. Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 86 +++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 36 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 58c06bd58a96..15668d3bbbc4 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -317,6 +317,7 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, } struct ovl_copy_up_ctx { + struct dentry *parent; struct dentry *dentry; struct path lowerpath; struct kstat stat; @@ -493,39 +494,16 @@ out_cleanup: * is possible that the copy up will lock the old parent. At that point * the file will have already been copied up anyway. */ -static int ovl_copy_up_one(struct dentry *parent, struct ovl_copy_up_ctx *c) +static int ovl_do_copy_up(struct ovl_copy_up_ctx *c) { - DEFINE_DELAYED_CALL(done); int err; - struct path parentpath; - struct dentry *lowerdentry = c->lowerpath.dentry; struct ovl_fs *ofs = c->dentry->d_sb->s_fs_info; - c->workdir = ovl_workdir(c->dentry); - if (WARN_ON(!c->workdir)) - return -EROFS; - - ovl_do_check_copy_up(lowerdentry); - - ovl_path_upper(parent, &parentpath); - c->upperdir = parentpath.dentry; - /* Mark parent "impure" because it may now contain non-pure upper */ - err = ovl_set_impure(parent, c->upperdir); - if (err) - return err; - - err = vfs_getattr(&parentpath, &c->pstat, - STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT); + err = ovl_set_impure(c->parent, c->upperdir); if (err) return err; - if (S_ISLNK(c->stat.mode)) { - c->link = vfs_get_link(lowerdentry, &done); - if (IS_ERR(c->link)) - return PTR_ERR(c->link); - } - /* Should we copyup with O_TMPFILE or with workdir? */ if (S_ISREG(c->stat.mode) && ofs->tmpfile) { err = ovl_copy_up_start(c->dentry); @@ -558,6 +536,52 @@ static int ovl_copy_up_one(struct dentry *parent, struct ovl_copy_up_ctx *c) out_unlock: unlock_rename(c->workdir, c->upperdir); out_done: + + return err; +} + +static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, + int flags) +{ + int err; + DEFINE_DELAYED_CALL(done); + struct path parentpath; + struct ovl_copy_up_ctx ctx = { + .parent = parent, + .dentry = dentry, + .workdir = ovl_workdir(dentry), + }; + + if (WARN_ON(!ctx.workdir)) + return -EROFS; + + ovl_path_lower(dentry, &ctx.lowerpath); + err = vfs_getattr(&ctx.lowerpath, &ctx.stat, + STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT); + if (err) + return err; + + ovl_path_upper(parent, &parentpath); + ctx.upperdir = parentpath.dentry; + + err = vfs_getattr(&parentpath, &ctx.pstat, + STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT); + if (err) + return err; + + /* maybe truncate regular file. this has no effect on dirs */ + if (flags & O_TRUNC) + ctx.stat.size = 0; + + if (S_ISLNK(ctx.stat.mode)) { + ctx.link = vfs_get_link(ctx.lowerpath.dentry, &done); + if (IS_ERR(ctx.link)) + return PTR_ERR(ctx.link); + } + ovl_do_check_copy_up(ctx.lowerpath.dentry); + + err = ovl_do_copy_up(&ctx); + do_delayed_call(&done); return err; @@ -571,7 +595,6 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags) while (!err) { struct dentry *next; struct dentry *parent; - struct ovl_copy_up_ctx ctx = { }; enum ovl_path_type type = ovl_path_type(dentry); if (OVL_TYPE_UPPER(type)) @@ -590,16 +613,7 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags) next = parent; } - ovl_path_lower(next, &ctx.lowerpath); - err = vfs_getattr(&ctx.lowerpath, &ctx.stat, - STATX_BASIC_STATS, AT_STATX_SYNC_AS_STAT); - /* maybe truncate regular file. this has no effect on dirs */ - if (flags & O_TRUNC) - ctx.stat.size = 0; - if (!err) { - ctx.dentry = next; - err = ovl_copy_up_one(parent, &ctx); - } + err = ovl_copy_up_one(parent, next, flags); dput(parent); dput(next); -- cgit From fd210b7d67ee3768bf1ad3e07d55797d4b45fcc1 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 4 Jul 2017 22:03:18 +0200 Subject: ovl: move copy up lock out Move ovl_copy_up_start()/ovl_copy_up_end() out so that it's used for both tempfile and workdir copy ups. Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 15668d3bbbc4..0d9de353f42b 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -506,37 +506,18 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c) /* Should we copyup with O_TMPFILE or with workdir? */ if (S_ISREG(c->stat.mode) && ofs->tmpfile) { - err = ovl_copy_up_start(c->dentry); - /* err < 0: interrupted, err > 0: raced with another copy-up */ - if (unlikely(err)) { - pr_debug("ovl_copy_up_start(%pd2) = %i\n", c->dentry, - err); - if (err > 0) - err = 0; - goto out_done; - } c->tmpfile = true; - err = ovl_copy_up_locked(c); - ovl_copy_up_end(c->dentry); - goto out_done; + return ovl_copy_up_locked(c); } err = -EIO; if (lock_rename(c->workdir, c->upperdir) != NULL) { pr_err("overlayfs: failed to lock workdir+upperdir\n"); - goto out_unlock; - } - if (ovl_dentry_upper(c->dentry)) { - /* Raced with another copy-up? Nothing to do, then... */ - err = 0; - goto out_unlock; + } else { + err = ovl_copy_up_locked(c); + unlock_rename(c->workdir, c->upperdir); } - err = ovl_copy_up_locked(c); -out_unlock: - unlock_rename(c->workdir, c->upperdir); -out_done: - return err; } @@ -580,8 +561,15 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, } ovl_do_check_copy_up(ctx.lowerpath.dentry); - err = ovl_do_copy_up(&ctx); - + err = ovl_copy_up_start(dentry); + /* err < 0: interrupted, err > 0: raced with another copy-up */ + if (unlikely(err)) { + if (err > 0) + err = 0; + } else { + err = ovl_do_copy_up(&ctx); + ovl_copy_up_end(dentry); + } do_delayed_call(&done); return err; -- cgit From 59be09712ab98a3060f13e31343c7abb9bc4583d Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 20 Jun 2017 15:25:46 +0300 Subject: ovl: implement index dir copy up Implement a copy up method for non-dir objects using index dir to prevent breaking lower hardlinks on copy up. This method requires that the inodes index dir feature was enabled and that all underlying fs support file handle encoding/decoding. On the first lower hardlink copy up, upper file is created in index dir, named after the hex representation of the lower origin inode file handle. On the second lower hardlink copy up, upper file is found in index dir, by the same lower handle key. On either case, the upper indexed inode is then linked to the copy up upper path. The index entry remains linked for future lower hardlink copy up and for lower to upper inode map, that is needed for exporting overlayfs to NFS. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 125 ++++++++++++++++++++++++++++++++++++++----------- fs/overlayfs/inode.c | 13 ++--- fs/overlayfs/util.c | 2 +- 3 files changed, 103 insertions(+), 37 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 0d9de353f42b..9f5a47338e59 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -316,6 +316,29 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, return err; } +static int ovl_link_up(struct dentry *parent, struct dentry *dentry) +{ + int err; + struct dentry *upper; + struct dentry *upperdir = ovl_dentry_upper(parent); + struct inode *udir = d_inode(upperdir); + + inode_lock_nested(udir, I_MUTEX_PARENT); + upper = lookup_one_len(dentry->d_name.name, upperdir, + dentry->d_name.len); + err = PTR_ERR(upper); + if (!IS_ERR(upper)) { + err = ovl_do_link(ovl_dentry_upper(dentry), udir, upper, true); + dput(upper); + + if (!err) + ovl_dentry_set_upper_alias(dentry); + } + inode_unlock(udir); + + return err; +} + struct ovl_copy_up_ctx { struct dentry *parent; struct dentry *dentry; @@ -323,9 +346,11 @@ struct ovl_copy_up_ctx { struct kstat stat; struct kstat pstat; const char *link; - struct dentry *upperdir; + struct dentry *destdir; + struct qstr destname; struct dentry *workdir; bool tmpfile; + bool origin; }; static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, @@ -333,10 +358,9 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, { int err; struct dentry *upper; - struct inode *udir = d_inode(c->upperdir); + struct inode *udir = d_inode(c->destdir); - upper = lookup_one_len(c->dentry->d_name.name, c->upperdir, - c->dentry->d_name.len); + upper = lookup_one_len(c->destname.name, c->destdir, c->destname.len); if (IS_ERR(upper)) return PTR_ERR(upper); @@ -345,11 +369,8 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, else err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0); - /* Restore timestamps on parent (best effort) */ - if (!err) { - ovl_set_timestamps(c->upperdir, &c->pstat); + if (!err) *newdentry = dget(c->tmpfile ? upper : temp); - } dput(upper); return err; @@ -439,7 +460,7 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp) * Don't set origin when we are breaking the association with a lower * hard link. */ - if (S_ISDIR(c->stat.mode) || c->stat.nlink == 1) { + if (c->origin) { err = ovl_set_origin(c->dentry, c->lowerpath.dentry, temp); if (err) return err; @@ -450,7 +471,7 @@ static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp) static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) { - struct inode *udir = c->upperdir->d_inode; + struct inode *udir = c->destdir->d_inode; struct dentry *newdentry = NULL; struct dentry *temp = NULL; int err; @@ -473,7 +494,6 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) if (err) goto out_cleanup; - ovl_dentry_set_upper_alias(c->dentry); ovl_inode_update(d_inode(c->dentry), newdentry); out: dput(temp); @@ -498,24 +518,57 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c) { int err; struct ovl_fs *ofs = c->dentry->d_sb->s_fs_info; + bool indexed = false; - /* Mark parent "impure" because it may now contain non-pure upper */ - err = ovl_set_impure(c->parent, c->upperdir); - if (err) - return err; + if (ovl_indexdir(c->dentry->d_sb) && !S_ISDIR(c->stat.mode) && + c->stat.nlink > 1) + indexed = true; + + if (S_ISDIR(c->stat.mode) || c->stat.nlink == 1 || indexed) + c->origin = true; + + if (indexed) { + c->destdir = ovl_indexdir(c->dentry->d_sb); + err = ovl_get_index_name(c->lowerpath.dentry, &c->destname); + if (err) + return err; + } else { + /* + * Mark parent "impure" because it may now contain non-pure + * upper + */ + err = ovl_set_impure(c->parent, c->destdir); + if (err) + return err; + } /* Should we copyup with O_TMPFILE or with workdir? */ if (S_ISREG(c->stat.mode) && ofs->tmpfile) { c->tmpfile = true; - return ovl_copy_up_locked(c); + err = ovl_copy_up_locked(c); + } else { + err = -EIO; + if (lock_rename(c->workdir, c->destdir) != NULL) { + pr_err("overlayfs: failed to lock workdir+upperdir\n"); + } else { + err = ovl_copy_up_locked(c); + unlock_rename(c->workdir, c->destdir); + } } - err = -EIO; - if (lock_rename(c->workdir, c->upperdir) != NULL) { - pr_err("overlayfs: failed to lock workdir+upperdir\n"); - } else { - err = ovl_copy_up_locked(c); - unlock_rename(c->workdir, c->upperdir); + if (indexed) { + if (!err) + ovl_set_flag(OVL_INDEX, d_inode(c->dentry)); + kfree(c->destname.name); + } else if (!err) { + struct inode *udir = d_inode(c->destdir); + + /* Restore timestamps on parent (best effort) */ + inode_lock(udir); + ovl_set_timestamps(c->destdir, &c->pstat); + inode_unlock(udir); + + ovl_dentry_set_upper_alias(c->dentry); } return err; @@ -543,7 +596,8 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, return err; ovl_path_upper(parent, &parentpath); - ctx.upperdir = parentpath.dentry; + ctx.destdir = parentpath.dentry; + ctx.destname = dentry->d_name; err = vfs_getattr(&parentpath, &ctx.pstat, STATX_ATIME | STATX_MTIME, AT_STATX_SYNC_AS_STAT); @@ -567,7 +621,10 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, if (err > 0) err = 0; } else { - err = ovl_do_copy_up(&ctx); + if (!ovl_dentry_upper(dentry)) + err = ovl_do_copy_up(&ctx); + if (!err && !ovl_dentry_has_upper_alias(dentry)) + err = ovl_link_up(parent, dentry); ovl_copy_up_end(dentry); } do_delayed_call(&done); @@ -583,9 +640,22 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags) while (!err) { struct dentry *next; struct dentry *parent; - enum ovl_path_type type = ovl_path_type(dentry); - if (OVL_TYPE_UPPER(type)) + /* + * Check if copy-up has happened as well as for upper alias (in + * case of hard links) is there. + * + * Both checks are lockless: + * - false negatives: will recheck under oi->lock + * - false positives: + * + ovl_dentry_upper() uses memory barriers to ensure the + * upper dentry is up-to-date + * + ovl_dentry_has_upper_alias() relies on locking of + * upper parent i_rwsem to prevent reordering copy-up + * with rename. + */ + if (ovl_dentry_upper(dentry) && + ovl_dentry_has_upper_alias(dentry)) break; next = dget(dentry); @@ -593,8 +663,7 @@ int ovl_copy_up_flags(struct dentry *dentry, int flags) for (;;) { parent = dget_parent(next); - type = ovl_path_type(parent); - if (OVL_TYPE_UPPER(type)) + if (ovl_dentry_upper(parent)) break; dput(next); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index d9fe07defca3..44d262a0a77e 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -305,13 +305,13 @@ struct posix_acl *ovl_get_acl(struct inode *inode, int type) return acl; } -static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type, - struct dentry *realdentry) +static bool ovl_open_need_copy_up(struct dentry *dentry, int flags) { - if (OVL_TYPE_UPPER(type)) + if (ovl_dentry_upper(dentry) && + ovl_dentry_has_upper_alias(dentry)) return false; - if (special_file(realdentry->d_inode->i_mode)) + if (special_file(d_inode(dentry)->i_mode)) return false; if (!(OPEN_FMODE(flags) & FMODE_WRITE) && !(flags & O_TRUNC)) @@ -323,11 +323,8 @@ static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type, int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags) { int err = 0; - struct path realpath; - enum ovl_path_type type; - type = ovl_path_real(dentry, &realpath); - if (ovl_open_need_copy_up(file_flags, type, realpath.dentry)) { + if (ovl_open_need_copy_up(dentry, file_flags)) { err = ovl_want_write(dentry); if (!err) { err = ovl_copy_up_flags(dentry, file_flags); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 38fa75228c66..a290be449b8b 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -302,7 +302,7 @@ int ovl_copy_up_start(struct dentry *dentry) int err; err = mutex_lock_interruptible(&oi->lock); - if (!err && ovl_dentry_upper(dentry)) { + if (!err && ovl_dentry_has_upper_alias(dentry)) { err = 1; /* Already copied up */ mutex_unlock(&oi->lock); } -- cgit From 5f8415d6b87ecb4ebf1bbd02c538694ebb7fb57c Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 20 Jun 2017 15:35:14 +0300 Subject: ovl: persistent overlay inode nlink for indexed inodes With inodes index enabled, an overlay inode nlink counts the union of upper and non-covered lower hardlinks. During the lifetime of a non-pure upper inode, the following nlink modifying operations can happen: 1. Lower hardlink copy up 2. Upper hardlink created, unlinked or renamed over 3. Lower hardlink whiteout or renamed over For the first, copy up case, the union nlink does not change, whether the operation succeeds or fails, but the upper inode nlink may change. Therefore, before copy up, we store the union nlink value relative to the lower inode nlink in the index inode xattr trusted.overlay.nlink. For the second, upper hardlink case, the union nlink should be incremented or decremented IFF the operation succeeds, aligned with nlink change of the upper inode. Therefore, before link/unlink/rename, we store the union nlink value relative to the upper inode nlink in the index inode. For the last, lower cover up case, we simplify things by preceding the whiteout or cover up with copy up. This makes sure that there is an index upper inode where the nlink xattr can be stored before the copied up upper entry is unlink. Return the overlay inode nlinks for indexed upper inodes on stat(2). Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 5 +++ fs/overlayfs/dir.c | 19 +++++++- fs/overlayfs/inode.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++- fs/overlayfs/overlayfs.h | 5 +++ fs/overlayfs/util.c | 66 ++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 3 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 9f5a47338e59..f193976560de 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -323,6 +323,10 @@ static int ovl_link_up(struct dentry *parent, struct dentry *dentry) struct dentry *upperdir = ovl_dentry_upper(parent); struct inode *udir = d_inode(upperdir); + err = ovl_set_nlink_lower(dentry); + if (err) + return err; + inode_lock_nested(udir, I_MUTEX_PARENT); upper = lookup_one_len(dentry->d_name.name, upperdir, dentry->d_name.len); @@ -335,6 +339,7 @@ static int ovl_link_up(struct dentry *parent, struct dentry *dentry) ovl_dentry_set_upper_alias(dentry); } inode_unlock(udir); + ovl_set_nlink_upper(dentry); return err; } diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 8b2b23181b19..641d9ee97f91 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -591,6 +591,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir, struct dentry *new) { int err; + bool locked = false; struct inode *inode; err = ovl_want_write(old); @@ -601,6 +602,10 @@ static int ovl_link(struct dentry *old, struct inode *newdir, if (err) goto out_drop_write; + err = ovl_nlink_start(old, &locked); + if (err) + goto out_drop_write; + inode = d_inode(old); ihold(inode); @@ -608,6 +613,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir, if (err) iput(inode); + ovl_nlink_end(old, locked); out_drop_write: ovl_drop_write(old); out: @@ -743,8 +749,8 @@ out: static int ovl_do_remove(struct dentry *dentry, bool is_dir) { - enum ovl_path_type type; int err; + bool locked = false; const struct cred *old_cred; err = ovl_want_write(dentry); @@ -755,7 +761,9 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) if (err) goto out_drop_write; - type = ovl_path_type(dentry); + err = ovl_nlink_start(dentry, &locked); + if (err) + goto out_drop_write; old_cred = ovl_override_creds(dentry->d_sb); if (!ovl_lower_positive(dentry)) @@ -769,6 +777,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) else drop_nlink(dentry->d_inode); } + ovl_nlink_end(dentry, locked); out_drop_write: ovl_drop_write(dentry); out: @@ -891,6 +900,7 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, unsigned int flags) { int err; + bool locked = false; struct dentry *old_upperdir; struct dentry *new_upperdir; struct dentry *olddentry; @@ -934,6 +944,10 @@ static int ovl_rename(struct inode *olddir, struct dentry *old, err = ovl_copy_up(new); if (err) goto out_drop_write; + } else { + err = ovl_nlink_start(new, &locked); + if (err) + goto out_drop_write; } old_cred = ovl_override_creds(old->d_sb); @@ -1072,6 +1086,7 @@ out_unlock: unlock_rename(new_upperdir, old_upperdir); out_revert_creds: revert_creds(old_cred); + ovl_nlink_end(new, locked); out_drop_write: ovl_drop_write(old); out: diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 44d262a0a77e..196a4e5450c0 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "overlayfs.h" int ovl_setattr(struct dentry *dentry, struct iattr *attr) @@ -130,6 +131,15 @@ int ovl_getattr(const struct path *path, struct kstat *stat, if (is_dir && OVL_TYPE_MERGE(type)) stat->nlink = 1; + /* + * Return the overlay inode nlinks for indexed upper inodes. + * Overlay inode nlink counts the union of the upper hardlinks + * and non-covered lower hardlinks. It does not include the upper + * index hardlink. + */ + if (!is_dir && ovl_test_flag(OVL_INDEX, d_inode(dentry))) + stat->nlink = dentry->d_inode->i_nlink; + out: revert_creds(old_cred); @@ -442,6 +452,103 @@ static void ovl_fill_inode(struct inode *inode, umode_t mode, dev_t rdev) } } +/* + * With inodes index enabled, an overlay inode nlink counts the union of upper + * hardlinks and non-covered lower hardlinks. During the lifetime of a non-pure + * upper inode, the following nlink modifying operations can happen: + * + * 1. Lower hardlink copy up + * 2. Upper hardlink created, unlinked or renamed over + * 3. Lower hardlink whiteout or renamed over + * + * For the first, copy up case, the union nlink does not change, whether the + * operation succeeds or fails, but the upper inode nlink may change. + * Therefore, before copy up, we store the union nlink value relative to the + * lower inode nlink in the index inode xattr trusted.overlay.nlink. + * + * For the second, upper hardlink case, the union nlink should be incremented + * or decremented IFF the operation succeeds, aligned with nlink change of the + * upper inode. Therefore, before link/unlink/rename, we store the union nlink + * value relative to the upper inode nlink in the index inode. + * + * For the last, lower cover up case, we simplify things by preceding the + * whiteout or cover up with copy up. This makes sure that there is an index + * upper inode where the nlink xattr can be stored before the copied up upper + * entry is unlink. + */ +#define OVL_NLINK_ADD_UPPER (1 << 0) + +/* + * On-disk format for indexed nlink: + * + * nlink relative to the upper inode - "U[+-]NUM" + * nlink relative to the lower inode - "L[+-]NUM" + */ + +static int ovl_set_nlink_common(struct dentry *dentry, + struct dentry *realdentry, const char *format) +{ + struct inode *inode = d_inode(dentry); + struct inode *realinode = d_inode(realdentry); + char buf[13]; + int len; + + len = snprintf(buf, sizeof(buf), format, + (int) (inode->i_nlink - realinode->i_nlink)); + + return ovl_do_setxattr(ovl_dentry_upper(dentry), + OVL_XATTR_NLINK, buf, len, 0); +} + +int ovl_set_nlink_upper(struct dentry *dentry) +{ + return ovl_set_nlink_common(dentry, ovl_dentry_upper(dentry), "U%+i"); +} + +int ovl_set_nlink_lower(struct dentry *dentry) +{ + return ovl_set_nlink_common(dentry, ovl_dentry_lower(dentry), "L%+i"); +} + +static unsigned int ovl_get_nlink(struct dentry *lowerdentry, + struct dentry *upperdentry, + unsigned int fallback) +{ + int nlink_diff; + int nlink; + char buf[13]; + int err; + + if (!lowerdentry || !upperdentry || d_inode(lowerdentry)->i_nlink == 1) + return fallback; + + err = vfs_getxattr(upperdentry, OVL_XATTR_NLINK, &buf, sizeof(buf) - 1); + if (err < 0) + goto fail; + + buf[err] = '\0'; + if ((buf[0] != 'L' && buf[0] != 'U') || + (buf[1] != '+' && buf[1] != '-')) + goto fail; + + err = kstrtoint(buf + 1, 10, &nlink_diff); + if (err < 0) + goto fail; + + nlink = d_inode(buf[0] == 'L' ? lowerdentry : upperdentry)->i_nlink; + nlink += nlink_diff; + + if (nlink <= 0) + goto fail; + + return nlink; + +fail: + pr_warn_ratelimited("overlayfs: failed to get index nlink (%pd2, err=%i)\n", + upperdentry, err); + return fallback; +} + struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev) { struct inode *inode; @@ -495,6 +602,7 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) if (!S_ISDIR(realinode->i_mode) && (upperdentry || (lowerdentry && ovl_indexdir(dentry->d_sb)))) { struct inode *key = d_inode(lowerdentry ?: upperdentry); + unsigned int nlink; inode = iget5_locked(dentry->d_sb, (unsigned long) key, ovl_inode_test, ovl_inode_set, key); @@ -515,7 +623,9 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry) goto out; } - set_nlink(inode, realinode->i_nlink); + nlink = ovl_get_nlink(lowerdentry, upperdentry, + realinode->i_nlink); + set_nlink(inode, nlink); } else { inode = new_inode(dentry->d_sb); if (!inode) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 751b36a5c22f..c1321ab38224 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -25,6 +25,7 @@ enum ovl_path_type { #define OVL_XATTR_REDIRECT OVL_XATTR_PREFIX "redirect" #define OVL_XATTR_ORIGIN OVL_XATTR_PREFIX "origin" #define OVL_XATTR_IMPURE OVL_XATTR_PREFIX "impure" +#define OVL_XATTR_NLINK OVL_XATTR_PREFIX "nlink" enum ovl_flag { OVL_IMPURE, @@ -229,6 +230,8 @@ void ovl_set_flag(unsigned long flag, struct inode *inode); bool ovl_test_flag(unsigned long flag, struct inode *inode); bool ovl_inuse_trylock(struct dentry *dentry); void ovl_inuse_unlock(struct dentry *dentry); +int ovl_nlink_start(struct dentry *dentry, bool *locked); +void ovl_nlink_end(struct dentry *dentry, bool locked); static inline bool ovl_is_impuredir(struct dentry *dentry) { @@ -258,6 +261,8 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt, struct path *lowerstack, unsigned int numlower); /* inode.c */ +int ovl_set_nlink_upper(struct dentry *dentry); +int ovl_set_nlink_lower(struct dentry *dentry); int ovl_setattr(struct dentry *dentry, struct iattr *attr); int ovl_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index a290be449b8b..04d5018e728e 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -410,3 +410,69 @@ void ovl_inuse_unlock(struct dentry *dentry) spin_unlock(&inode->i_lock); } } + +/* + * Operations that change overlay inode and upper inode nlink need to be + * synchronized with copy up for persistent nlink accounting. + */ +int ovl_nlink_start(struct dentry *dentry, bool *locked) +{ + struct ovl_inode *oi = OVL_I(d_inode(dentry)); + const struct cred *old_cred; + int err; + + if (!d_inode(dentry) || d_is_dir(dentry)) + return 0; + + /* + * With inodes index is enabled, we store the union overlay nlink + * in an xattr on the index inode. When whiting out lower hardlinks + * we need to decrement the overlay persistent nlink, but before the + * first copy up, we have no upper index inode to store the xattr. + * + * As a workaround, before whiteout/rename over of a lower hardlink, + * copy up to create the upper index. Creating the upper index will + * initialize the overlay nlink, so it could be dropped if unlink + * or rename succeeds. + * + * TODO: implement metadata only index copy up when called with + * ovl_copy_up_flags(dentry, O_PATH). + */ + if (ovl_indexdir(dentry->d_sb) && !ovl_dentry_has_upper_alias(dentry) && + d_inode(ovl_dentry_lower(dentry))->i_nlink > 1) { + err = ovl_copy_up(dentry); + if (err) + return err; + } + + err = mutex_lock_interruptible(&oi->lock); + if (err) + return err; + + if (!ovl_test_flag(OVL_INDEX, d_inode(dentry))) + goto out; + + old_cred = ovl_override_creds(dentry->d_sb); + /* + * The overlay inode nlink should be incremented/decremented IFF the + * upper operation succeeds, along with nlink change of upper inode. + * Therefore, before link/unlink/rename, we store the union nlink + * value relative to the upper inode nlink in an upper inode xattr. + */ + err = ovl_set_nlink_upper(dentry); + revert_creds(old_cred); + +out: + if (err) + mutex_unlock(&oi->lock); + else + *locked = true; + + return err; +} + +void ovl_nlink_end(struct dentry *dentry, bool locked) +{ + if (locked) + mutex_unlock(&OVL_I(d_inode(dentry))->lock); +} -- cgit From caf70cb2ba5dff85ea90f494a30075af92df13b0 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Wed, 21 Jun 2017 13:46:12 +0300 Subject: ovl: cleanup orphan index entries index entry should live only as long as there are upper or lower hardlinks. Cleanup orphan index entries on mount and when dropping the last overlay inode nlink. When about to cleanup or link up to orphan index and the index inode nlink > 1, admit that something went wrong and adjust overlay nlink to index inode nlink - 1 to prevent it from dropping below zero. This could happen when adding lower hardlinks underneath a mounted overlay and then trying to unlink them. Signed-off-by: Amir Goldstein Signed-off-by: Miklos Szeredi --- fs/overlayfs/inode.c | 6 ++--- fs/overlayfs/namei.c | 5 ++++ fs/overlayfs/overlayfs.h | 3 +++ fs/overlayfs/super.c | 2 +- fs/overlayfs/util.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 77 insertions(+), 5 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 196a4e5450c0..69f4fc26ee39 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -510,9 +510,9 @@ int ovl_set_nlink_lower(struct dentry *dentry) return ovl_set_nlink_common(dentry, ovl_dentry_lower(dentry), "L%+i"); } -static unsigned int ovl_get_nlink(struct dentry *lowerdentry, - struct dentry *upperdentry, - unsigned int fallback) +unsigned int ovl_get_nlink(struct dentry *lowerdentry, + struct dentry *upperdentry, + unsigned int fallback) { int nlink_diff; int nlink; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 2d8b6292fe21..9bc0e580a5b3 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -425,6 +425,11 @@ int ovl_verify_index(struct dentry *index, struct path *lowerstack, if (err) goto fail; + /* Check if index is orphan and don't warn before cleaning it */ + if (d_inode(index)->i_nlink == 1 && + ovl_get_nlink(index, origin.dentry, 0) == 0) + err = -ENOENT; + dput(origin.dentry); out: kfree(fh); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index c1321ab38224..60d26605e039 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -263,6 +263,9 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt, /* inode.c */ int ovl_set_nlink_upper(struct dentry *dentry); int ovl_set_nlink_lower(struct dentry *dentry); +unsigned int ovl_get_nlink(struct dentry *lowerdentry, + struct dentry *upperdentry, + unsigned int fallback); int ovl_setattr(struct dentry *dentry, struct iattr *attr); int ovl_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index f29ee08cf99f..44dc2d6ffe0f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1069,7 +1069,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (err) pr_err("overlayfs: failed to verify index dir origin\n"); - /* Cleanup bad/stale index entries */ + /* Cleanup bad/stale/orphan index entries */ if (!err) err = ovl_indexdir_cleanup(ufs->indexdir, ufs->upper_mnt, diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 04d5018e728e..c492ba75c659 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include "overlayfs.h" #include "ovl_entry.h" @@ -411,6 +413,58 @@ void ovl_inuse_unlock(struct dentry *dentry) } } +/* Called must hold OVL_I(inode)->oi_lock */ +static void ovl_cleanup_index(struct dentry *dentry) +{ + struct inode *dir = ovl_indexdir(dentry->d_sb)->d_inode; + struct dentry *lowerdentry = ovl_dentry_lower(dentry); + struct dentry *upperdentry = ovl_dentry_upper(dentry); + struct dentry *index = NULL; + struct inode *inode; + struct qstr name; + int err; + + err = ovl_get_index_name(lowerdentry, &name); + if (err) + goto fail; + + inode = d_inode(upperdentry); + if (inode->i_nlink != 1) { + pr_warn_ratelimited("overlayfs: cleanup linked index (%pd2, ino=%lu, nlink=%u)\n", + upperdentry, inode->i_ino, inode->i_nlink); + /* + * We either have a bug with persistent union nlink or a lower + * hardlink was added while overlay is mounted. Adding a lower + * hardlink and then unlinking all overlay hardlinks would drop + * overlay nlink to zero before all upper inodes are unlinked. + * As a safety measure, when that situation is detected, set + * the overlay nlink to the index inode nlink minus one for the + * index entry itself. + */ + set_nlink(d_inode(dentry), inode->i_nlink - 1); + ovl_set_nlink_upper(dentry); + goto out; + } + + inode_lock_nested(dir, I_MUTEX_PARENT); + /* TODO: whiteout instead of cleanup to block future open by handle */ + index = lookup_one_len(name.name, ovl_indexdir(dentry->d_sb), name.len); + err = PTR_ERR(index); + if (!IS_ERR(index)) + err = ovl_cleanup(dir, index); + inode_unlock(dir); + if (err) + goto fail; + +out: + dput(index); + return; + +fail: + pr_err("overlayfs: cleanup index of '%pd2' failed (%i)\n", dentry, err); + goto out; +} + /* * Operations that change overlay inode and upper inode nlink need to be * synchronized with copy up for persistent nlink accounting. @@ -473,6 +527,16 @@ out: void ovl_nlink_end(struct dentry *dentry, bool locked) { - if (locked) + if (locked) { + if (ovl_test_flag(OVL_INDEX, d_inode(dentry)) && + d_inode(dentry)->i_nlink == 0) { + const struct cred *old_cred; + + old_cred = ovl_override_creds(dentry->d_sb); + ovl_cleanup_index(dentry); + revert_creds(old_cred); + } + mutex_unlock(&OVL_I(d_inode(dentry))->lock); + } } -- cgit From f4439de118283159ff165e52036134a278ebf990 Mon Sep 17 00:00:00 2001 From: Amir Goldstein Date: Tue, 4 Jul 2017 22:04:06 +0300 Subject: ovl: mark parent impure and restore timestamp on ovl_link_up() Signed-off-by: Amir Goldstein --- fs/overlayfs/copy_up.c | 57 +++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 24 deletions(-) (limited to 'fs') diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index f193976560de..acb6f97deb97 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -316,48 +316,57 @@ static int ovl_set_origin(struct dentry *dentry, struct dentry *lower, return err; } -static int ovl_link_up(struct dentry *parent, struct dentry *dentry) +struct ovl_copy_up_ctx { + struct dentry *parent; + struct dentry *dentry; + struct path lowerpath; + struct kstat stat; + struct kstat pstat; + const char *link; + struct dentry *destdir; + struct qstr destname; + struct dentry *workdir; + bool tmpfile; + bool origin; +}; + +static int ovl_link_up(struct ovl_copy_up_ctx *c) { int err; struct dentry *upper; - struct dentry *upperdir = ovl_dentry_upper(parent); + struct dentry *upperdir = ovl_dentry_upper(c->parent); struct inode *udir = d_inode(upperdir); - err = ovl_set_nlink_lower(dentry); + /* Mark parent "impure" because it may now contain non-pure upper */ + err = ovl_set_impure(c->parent, upperdir); + if (err) + return err; + + err = ovl_set_nlink_lower(c->dentry); if (err) return err; inode_lock_nested(udir, I_MUTEX_PARENT); - upper = lookup_one_len(dentry->d_name.name, upperdir, - dentry->d_name.len); + upper = lookup_one_len(c->dentry->d_name.name, upperdir, + c->dentry->d_name.len); err = PTR_ERR(upper); if (!IS_ERR(upper)) { - err = ovl_do_link(ovl_dentry_upper(dentry), udir, upper, true); + err = ovl_do_link(ovl_dentry_upper(c->dentry), udir, upper, + true); dput(upper); - if (!err) - ovl_dentry_set_upper_alias(dentry); + if (!err) { + /* Restore timestamps on parent (best effort) */ + ovl_set_timestamps(upperdir, &c->pstat); + ovl_dentry_set_upper_alias(c->dentry); + } } inode_unlock(udir); - ovl_set_nlink_upper(dentry); + ovl_set_nlink_upper(c->dentry); return err; } -struct ovl_copy_up_ctx { - struct dentry *parent; - struct dentry *dentry; - struct path lowerpath; - struct kstat stat; - struct kstat pstat; - const char *link; - struct dentry *destdir; - struct qstr destname; - struct dentry *workdir; - bool tmpfile; - bool origin; -}; - static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, struct dentry **newdentry) { @@ -629,7 +638,7 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, if (!ovl_dentry_upper(dentry)) err = ovl_do_copy_up(&ctx); if (!err && !ovl_dentry_has_upper_alias(dentry)) - err = ovl_link_up(parent, dentry); + err = ovl_link_up(&ctx); ovl_copy_up_end(dentry); } do_delayed_call(&done); -- cgit