diff options
-rw-r--r-- | Documentation/filesystems/overlayfs.txt | 7 | ||||
-rw-r--r-- | fs/inode.c | 164 | ||||
-rw-r--r-- | fs/overlayfs/Kconfig | 6 | ||||
-rw-r--r-- | fs/overlayfs/copy_up.c | 83 | ||||
-rw-r--r-- | fs/overlayfs/dir.c | 208 | ||||
-rw-r--r-- | fs/overlayfs/export.c | 8 | ||||
-rw-r--r-- | fs/overlayfs/inode.c | 28 | ||||
-rw-r--r-- | fs/overlayfs/namei.c | 10 | ||||
-rw-r--r-- | fs/overlayfs/overlayfs.h | 65 | ||||
-rw-r--r-- | fs/overlayfs/super.c | 9 | ||||
-rw-r--r-- | include/linux/fs.h | 4 |
11 files changed, 319 insertions, 273 deletions
diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index 961b287ef323..72615a2c0752 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -429,11 +429,12 @@ This verification may cause significant overhead in some cases. Testsuite --------- -There's testsuite developed by David Howells at: +There's a testsuite originally developed by David Howells and currently +maintained by Amir Goldstein at: - git://git.infradead.org/users/dhowells/unionmount-testsuite.git + https://github.com/amir73il/unionmount-testsuite.git Run as root: # cd unionmount-testsuite - # ./run --ov + # ./run --ov --verify diff --git a/fs/inode.c b/fs/inode.c index 3b55391072f3..0df41bb77e0f 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1004,6 +1004,70 @@ void unlock_two_nondirectories(struct inode *inode1, struct inode *inode2) EXPORT_SYMBOL(unlock_two_nondirectories); /** + * inode_insert5 - obtain an inode from a mounted file system + * @inode: pre-allocated inode to use for insert to cache + * @hashval: hash value (usually inode number) to get + * @test: callback used for comparisons between inodes + * @set: callback used to initialize a new struct inode + * @data: opaque data pointer to pass to @test and @set + * + * Search for the inode specified by @hashval and @data in the inode cache, + * and if present it is return it with an increased reference count. This is + * a variant of iget5_locked() for callers that don't want to fail on memory + * allocation of inode. + * + * If the inode is not in cache, insert the pre-allocated inode to cache and + * return it locked, hashed, and with the I_NEW flag set. The file system gets + * to fill it in before unlocking it via unlock_new_inode(). + * + * Note both @test and @set are called with the inode_hash_lock held, so can't + * sleep. + */ +struct inode *inode_insert5(struct inode *inode, unsigned long hashval, + int (*test)(struct inode *, void *), + int (*set)(struct inode *, void *), void *data) +{ + struct hlist_head *head = inode_hashtable + hash(inode->i_sb, hashval); + struct inode *old; + +again: + spin_lock(&inode_hash_lock); + old = find_inode(inode->i_sb, head, test, data); + if (unlikely(old)) { + /* + * Uhhuh, somebody else created the same inode under us. + * Use the old inode instead of the preallocated one. + */ + spin_unlock(&inode_hash_lock); + wait_on_inode(old); + if (unlikely(inode_unhashed(old))) { + iput(old); + goto again; + } + return old; + } + + if (set && unlikely(set(inode, data))) { + inode = NULL; + goto unlock; + } + + /* + * Return the locked inode with I_NEW set, the + * caller is responsible for filling in the contents + */ + spin_lock(&inode->i_lock); + inode->i_state |= I_NEW; + hlist_add_head(&inode->i_hash, head); + spin_unlock(&inode->i_lock); +unlock: + spin_unlock(&inode_hash_lock); + + return inode; +} +EXPORT_SYMBOL(inode_insert5); + +/** * iget5_locked - obtain an inode from a mounted file system * @sb: super block of file system * @hashval: hash value (usually inode number) to get @@ -1027,66 +1091,18 @@ struct inode *iget5_locked(struct super_block *sb, unsigned long hashval, int (*test)(struct inode *, void *), int (*set)(struct inode *, void *), void *data) { - struct hlist_head *head = inode_hashtable + hash(sb, hashval); - struct inode *inode; -again: - spin_lock(&inode_hash_lock); - inode = find_inode(sb, head, test, data); - spin_unlock(&inode_hash_lock); + struct inode *inode = ilookup5(sb, hashval, test, data); - if (inode) { - wait_on_inode(inode); - if (unlikely(inode_unhashed(inode))) { - iput(inode); - goto again; - } - return inode; - } + if (!inode) { + struct inode *new = new_inode(sb); - inode = alloc_inode(sb); - if (inode) { - struct inode *old; - - spin_lock(&inode_hash_lock); - /* We released the lock, so.. */ - old = find_inode(sb, head, test, data); - if (!old) { - if (set(inode, data)) - goto set_failed; - - spin_lock(&inode->i_lock); - inode->i_state = I_NEW; - hlist_add_head(&inode->i_hash, head); - spin_unlock(&inode->i_lock); - inode_sb_list_add(inode); - spin_unlock(&inode_hash_lock); - - /* Return the locked inode with I_NEW set, the - * caller is responsible for filling in the contents - */ - return inode; - } - - /* - * Uhhuh, somebody else created the same inode under - * us. Use the old inode instead of the one we just - * allocated. - */ - spin_unlock(&inode_hash_lock); - destroy_inode(inode); - inode = old; - wait_on_inode(inode); - if (unlikely(inode_unhashed(inode))) { - iput(inode); - goto again; + if (new) { + inode = inode_insert5(new, hashval, test, set, data); + if (unlikely(inode != new)) + iput(new); } } return inode; - -set_failed: - spin_unlock(&inode_hash_lock); - destroy_inode(inode); - return NULL; } EXPORT_SYMBOL(iget5_locked); @@ -1427,43 +1443,13 @@ EXPORT_SYMBOL(insert_inode_locked); int insert_inode_locked4(struct inode *inode, unsigned long hashval, int (*test)(struct inode *, void *), void *data) { - struct super_block *sb = inode->i_sb; - struct hlist_head *head = inode_hashtable + hash(sb, hashval); + struct inode *old = inode_insert5(inode, hashval, test, NULL, data); - while (1) { - struct inode *old = NULL; - - spin_lock(&inode_hash_lock); - hlist_for_each_entry(old, head, i_hash) { - if (old->i_sb != sb) - continue; - if (!test(old, data)) - continue; - spin_lock(&old->i_lock); - if (old->i_state & (I_FREEING|I_WILL_FREE)) { - spin_unlock(&old->i_lock); - continue; - } - break; - } - if (likely(!old)) { - spin_lock(&inode->i_lock); - inode->i_state |= I_NEW; - hlist_add_head(&inode->i_hash, head); - spin_unlock(&inode->i_lock); - spin_unlock(&inode_hash_lock); - return 0; - } - __iget(old); - spin_unlock(&old->i_lock); - spin_unlock(&inode_hash_lock); - wait_on_inode(old); - if (unlikely(!inode_unhashed(old))) { - iput(old); - return -EBUSY; - } + if (old != inode) { iput(old); + return -EBUSY; } + return 0; } EXPORT_SYMBOL(insert_inode_locked4); diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig index 17032631c5cf..9384164253ac 100644 --- a/fs/overlayfs/Kconfig +++ b/fs/overlayfs/Kconfig @@ -11,7 +11,7 @@ config OVERLAY_FS For more information see Documentation/filesystems/overlayfs.txt config OVERLAY_FS_REDIRECT_DIR - bool "Overlayfs: turn on redirect dir feature by default" + bool "Overlayfs: turn on redirect directory feature by default" depends on OVERLAY_FS help If this config option is enabled then overlay filesystems will use @@ -46,7 +46,7 @@ config OVERLAY_FS_INDEX 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. + the index directory 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. @@ -66,7 +66,7 @@ config OVERLAY_FS_NFS_EXPORT depends on OVERLAY_FS_INDEX help If this config option is enabled then overlay filesystems will use - the inodes index dir to decode overlay NFS file handles by default. + the index directory to decode overlay NFS file handles by default. In this case, it is still possible to turn off NFS export support globally with the "nfs_export=off" module option or on a filesystem instance basis with the "nfs_export=off" mount option. diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 8bede0742619..ddaddb4ce4c3 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -365,17 +365,14 @@ static int ovl_create_index(struct dentry *dentry, struct dentry *origin, if (err) return err; - temp = ovl_lookup_temp(indexdir); + temp = ovl_create_temp(indexdir, OVL_CATTR(S_IFDIR | 0)); + err = PTR_ERR(temp); if (IS_ERR(temp)) - goto temp_err; - - err = ovl_do_mkdir(dir, temp, S_IFDIR, true); - if (err) - goto out; + goto free_name; err = ovl_set_upper_fh(upper, temp); if (err) - goto out_cleanup; + goto out; index = lookup_one_len(name.name, indexdir, name.len); if (IS_ERR(index)) { @@ -384,23 +381,13 @@ static int ovl_create_index(struct dentry *dentry, struct dentry *origin, err = ovl_do_rename(dir, temp, dir, index, 0); dput(index); } - - if (err) - goto out_cleanup; - out: + if (err) + ovl_cleanup(dir, temp); dput(temp); +free_name: kfree(name.name); return err; - -temp_err: - err = PTR_ERR(temp); - temp = NULL; - goto out; - -out_cleanup: - ovl_cleanup(dir, temp); - goto out; } struct ovl_copy_up_ctx { @@ -439,8 +426,7 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c) c->dentry->d_name.len); err = PTR_ERR(upper); if (!IS_ERR(upper)) { - err = ovl_do_link(ovl_dentry_upper(c->dentry), udir, upper, - true); + err = ovl_do_link(ovl_dentry_upper(c->dentry), udir, upper); dput(upper); if (!err) { @@ -470,7 +456,7 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, return PTR_ERR(upper); if (c->tmpfile) - err = ovl_do_link(temp, udir, upper, true); + err = ovl_do_link(temp, udir, upper); else err = ovl_do_rename(d_inode(c->workdir), temp, udir, upper, 0); @@ -481,13 +467,13 @@ static int ovl_install_temp(struct ovl_copy_up_ctx *c, struct dentry *temp, return err; } -static int ovl_get_tmpfile(struct ovl_copy_up_ctx *c, struct dentry **tempp) +static struct dentry *ovl_get_tmpfile(struct ovl_copy_up_ctx *c) { int err; struct dentry *temp; const struct cred *old_creds = NULL; struct cred *new_creds = NULL; - struct cattr cattr = { + struct ovl_cattr cattr = { /* Can't properly set mode on creation because of the umask */ .mode = c->stat.mode & S_IFMT, .rdev = c->stat.rdev, @@ -495,41 +481,24 @@ static int ovl_get_tmpfile(struct ovl_copy_up_ctx *c, struct dentry **tempp) }; err = security_inode_copy_up(c->dentry, &new_creds); + temp = ERR_PTR(err); if (err < 0) goto out; if (new_creds) old_creds = override_creds(new_creds); - if (c->tmpfile) { + if (c->tmpfile) temp = ovl_do_tmpfile(c->workdir, c->stat.mode); - if (IS_ERR(temp)) - goto temp_err; - } else { - temp = ovl_lookup_temp(c->workdir); - if (IS_ERR(temp)) - goto temp_err; - - err = ovl_create_real(d_inode(c->workdir), temp, &cattr, - NULL, true); - if (err) { - dput(temp); - goto out; - } - } - err = 0; - *tempp = temp; + else + temp = ovl_create_temp(c->workdir, &cattr); out: if (new_creds) { revert_creds(old_creds); put_cred(new_creds); } - return err; - -temp_err: - err = PTR_ERR(temp); - goto out; + return temp; } static int ovl_copy_up_inode(struct ovl_copy_up_ctx *c, struct dentry *temp) @@ -579,21 +548,21 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) struct inode *udir = c->destdir->d_inode; struct inode *inode; struct dentry *newdentry = NULL; - struct dentry *temp = NULL; + struct dentry *temp; int err; - err = ovl_get_tmpfile(c, &temp); - if (err) - goto out; + temp = ovl_get_tmpfile(c); + if (IS_ERR(temp)) + return PTR_ERR(temp); err = ovl_copy_up_inode(c, temp); if (err) - goto out_cleanup; + goto out; if (S_ISDIR(c->stat.mode) && c->indexed) { err = ovl_create_index(c->dentry, c->lowerpath.dentry, temp); if (err) - goto out_cleanup; + goto out; } if (c->tmpfile) { @@ -604,7 +573,7 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) err = ovl_install_temp(c, temp, &newdentry); } if (err) - goto out_cleanup; + goto out; inode = d_inode(c->dentry); ovl_inode_update(inode, newdentry); @@ -612,13 +581,11 @@ static int ovl_copy_up_locked(struct ovl_copy_up_ctx *c) ovl_set_flag(OVL_WHITEOUTS, inode); out: + if (err && !c->tmpfile) + ovl_cleanup(d_inode(c->workdir), temp); dput(temp); return err; -out_cleanup: - if (!c->tmpfile) - ovl_cleanup(d_inode(c->workdir), temp); - goto out; } /* diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 839709c7803a..f480b1a2cd2e 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -43,7 +43,7 @@ int ovl_cleanup(struct inode *wdir, struct dentry *wdentry) return err; } -struct dentry *ovl_lookup_temp(struct dentry *workdir) +static struct dentry *ovl_lookup_temp(struct dentry *workdir) { struct dentry *temp; char name[20]; @@ -114,36 +114,72 @@ kill_whiteout: goto out; } -int ovl_create_real(struct inode *dir, struct dentry *newdentry, - struct cattr *attr, struct dentry *hardlink, bool debug) +static int ovl_mkdir_real(struct inode *dir, struct dentry **newdentry, + umode_t mode) { int err; + struct dentry *d, *dentry = *newdentry; + err = ovl_do_mkdir(dir, dentry, mode); + if (err) + return err; + + if (likely(!d_unhashed(dentry))) + return 0; + + /* + * vfs_mkdir() may succeed and leave the dentry passed + * to it unhashed and negative. If that happens, try to + * lookup a new hashed and positive dentry. + */ + d = lookup_one_len(dentry->d_name.name, dentry->d_parent, + dentry->d_name.len); + if (IS_ERR(d)) { + pr_warn("overlayfs: failed lookup after mkdir (%pd2, err=%i).\n", + dentry, err); + return PTR_ERR(d); + } + dput(dentry); + *newdentry = d; + + return 0; +} + +struct dentry *ovl_create_real(struct inode *dir, struct dentry *newdentry, + struct ovl_cattr *attr) +{ + int err; + + if (IS_ERR(newdentry)) + return newdentry; + + err = -ESTALE; if (newdentry->d_inode) - return -ESTALE; + goto out; - if (hardlink) { - err = ovl_do_link(hardlink, dir, newdentry, debug); + if (attr->hardlink) { + err = ovl_do_link(attr->hardlink, dir, newdentry); } else { switch (attr->mode & S_IFMT) { case S_IFREG: - err = ovl_do_create(dir, newdentry, attr->mode, debug); + err = ovl_do_create(dir, newdentry, attr->mode); break; case S_IFDIR: - err = ovl_do_mkdir(dir, newdentry, attr->mode, debug); + /* mkdir is special... */ + err = ovl_mkdir_real(dir, &newdentry, attr->mode); break; case S_IFCHR: case S_IFBLK: case S_IFIFO: case S_IFSOCK: - err = ovl_do_mknod(dir, newdentry, - attr->mode, attr->rdev, debug); + err = ovl_do_mknod(dir, newdentry, attr->mode, + attr->rdev); break; case S_IFLNK: - err = ovl_do_symlink(dir, newdentry, attr->link, debug); + err = ovl_do_symlink(dir, newdentry, attr->link); break; default: @@ -155,9 +191,20 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, * Not quite sure if non-instantiated dentry is legal or not. * VFS doesn't seem to care so check and warn here. */ - err = -ENOENT; + err = -EIO; } - return err; +out: + if (err) { + dput(newdentry); + return ERR_PTR(err); + } + return newdentry; +} + +struct dentry *ovl_create_temp(struct dentry *workdir, struct ovl_cattr *attr) +{ + return ovl_create_real(d_inode(workdir), ovl_lookup_temp(workdir), + attr); } static int ovl_set_opaque_xerr(struct dentry *dentry, struct dentry *upper, @@ -182,24 +229,54 @@ static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry) return ovl_set_opaque_xerr(dentry, upperdentry, -EIO); } -/* Common operations required to be done after creation of file on upper */ -static void ovl_instantiate(struct dentry *dentry, struct inode *inode, - struct dentry *newdentry, bool hardlink) +/* + * Common operations required to be done after creation of file on upper. + * If @hardlink is false, then @inode is a pre-allocated inode, we may or + * may not use to instantiate the new dentry. + */ +static int ovl_instantiate(struct dentry *dentry, struct inode *inode, + struct dentry *newdentry, bool hardlink) { + struct ovl_inode_params oip = { + .upperdentry = newdentry, + .newinode = inode, + }; + ovl_dentry_version_inc(dentry->d_parent, false); ovl_dentry_set_upper_alias(dentry); if (!hardlink) { - ovl_inode_update(inode, newdentry); - ovl_copyattr(newdentry->d_inode, inode); + /* + * ovl_obtain_alias() can be called after ovl_create_real() + * and before we get here, so we may get an inode from cache + * with the same real upperdentry that is not the inode we + * pre-allocated. In this case we will use the cached inode + * to instantiate the new dentry. + * + * XXX: if we ever use ovl_obtain_alias() to decode directory + * file handles, need to use ovl_get_inode_locked() and + * d_instantiate_new() here to prevent from creating two + * hashed directory inode aliases. + */ + inode = ovl_get_inode(dentry->d_sb, &oip); + if (WARN_ON(IS_ERR(inode))) + return PTR_ERR(inode); } else { WARN_ON(ovl_inode_real(inode) != d_inode(newdentry)); dput(newdentry); inc_nlink(inode); } + d_instantiate(dentry, inode); + if (inode != oip.newinode) { + pr_warn_ratelimited("overlayfs: newly created inode found in cache (%pd2)\n", + dentry); + } + /* Force lookup of new upper hardlink to find its lower */ if (hardlink) d_drop(dentry); + + return 0; } static bool ovl_type_merge(struct dentry *dentry) @@ -213,38 +290,42 @@ static bool ovl_type_origin(struct dentry *dentry) } static int ovl_create_upper(struct dentry *dentry, struct inode *inode, - struct cattr *attr, struct dentry *hardlink) + struct ovl_cattr *attr) { struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct inode *udir = upperdir->d_inode; struct dentry *newdentry; int err; - if (!hardlink && !IS_POSIXACL(udir)) + if (!attr->hardlink && !IS_POSIXACL(udir)) attr->mode &= ~current_umask(); inode_lock_nested(udir, I_MUTEX_PARENT); - newdentry = lookup_one_len(dentry->d_name.name, upperdir, - dentry->d_name.len); + newdentry = ovl_create_real(udir, + lookup_one_len(dentry->d_name.name, + upperdir, + dentry->d_name.len), + attr); err = PTR_ERR(newdentry); if (IS_ERR(newdentry)) goto out_unlock; - err = ovl_create_real(udir, newdentry, attr, hardlink, false); - if (err) - goto out_dput; if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry)) { /* Setting opaque here is just an optimization, allow to fail */ ovl_set_opaque(dentry, newdentry); } - ovl_instantiate(dentry, inode, newdentry, !!hardlink); - newdentry = NULL; -out_dput: - dput(newdentry); + err = ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink); + if (err) + goto out_cleanup; out_unlock: inode_unlock(udir); return err; + +out_cleanup: + ovl_cleanup(udir, newdentry); + dput(newdentry); + goto out_unlock; } static struct dentry *ovl_clear_empty(struct dentry *dentry, @@ -280,16 +361,11 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry, if (upper->d_parent->d_inode != udir) goto out_unlock; - opaquedir = ovl_lookup_temp(workdir); + opaquedir = ovl_create_temp(workdir, OVL_CATTR(stat.mode)); err = PTR_ERR(opaquedir); if (IS_ERR(opaquedir)) goto out_unlock; - err = ovl_create_real(wdir, opaquedir, - &(struct cattr){.mode = stat.mode}, NULL, true); - if (err) - goto out_dput; - err = ovl_copy_xattr(upper, opaquedir); if (err) goto out_cleanup; @@ -319,7 +395,6 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry, out_cleanup: ovl_cleanup(wdir, opaquedir); -out_dput: dput(opaquedir); out_unlock: unlock_rename(workdir, upperdir); @@ -354,8 +429,7 @@ out_free: } static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, - struct cattr *cattr, - struct dentry *hardlink) + struct ovl_cattr *cattr) { struct dentry *workdir = ovl_workdir(dentry); struct inode *wdir = workdir->d_inode; @@ -365,6 +439,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, struct dentry *newdentry; int err; struct posix_acl *acl, *default_acl; + bool hardlink = !!cattr->hardlink; if (WARN_ON(!workdir)) return -EROFS; @@ -380,20 +455,16 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (err) goto out; - newdentry = ovl_lookup_temp(workdir); - err = PTR_ERR(newdentry); - if (IS_ERR(newdentry)) - goto out_unlock; - upper = lookup_one_len(dentry->d_name.name, upperdir, dentry->d_name.len); err = PTR_ERR(upper); if (IS_ERR(upper)) - goto out_dput; + goto out_unlock; - err = ovl_create_real(wdir, newdentry, cattr, hardlink, true); - if (err) - goto out_dput2; + newdentry = ovl_create_temp(workdir, cattr); + err = PTR_ERR(newdentry); + if (IS_ERR(newdentry)) + goto out_dput; /* * mode could have been mutilated due to umask (e.g. sgid directory) @@ -439,12 +510,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (err) goto out_cleanup; } - ovl_instantiate(dentry, inode, newdentry, !!hardlink); - newdentry = NULL; -out_dput2: - dput(upper); + err = ovl_instantiate(dentry, inode, newdentry, hardlink); + if (err) + goto out_cleanup; out_dput: - dput(newdentry); + dput(upper); out_unlock: unlock_rename(workdir, upperdir); out: @@ -456,12 +526,12 @@ out: out_cleanup: ovl_cleanup(wdir, newdentry); - goto out_dput2; + dput(newdentry); + goto out_dput; } static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, - struct cattr *attr, struct dentry *hardlink, - bool origin) + struct ovl_cattr *attr, bool origin) { int err; const struct cred *old_cred; @@ -489,7 +559,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, if (override_cred) { override_cred->fsuid = inode->i_uid; override_cred->fsgid = inode->i_gid; - if (!hardlink) { + if (!attr->hardlink) { err = security_dentry_create_files_as(dentry, attr->mode, &dentry->d_name, old_cred, override_cred); @@ -502,21 +572,12 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, put_cred(override_cred); if (!ovl_dentry_is_whiteout(dentry)) - err = ovl_create_upper(dentry, inode, attr, - hardlink); + err = ovl_create_upper(dentry, inode, attr); else - err = ovl_create_over_whiteout(dentry, inode, attr, - hardlink); + err = ovl_create_over_whiteout(dentry, inode, attr); } out_revert_creds: revert_creds(old_cred); - if (!err) { - struct inode *realinode = d_inode(ovl_dentry_upper(dentry)); - - WARN_ON(inode->i_mode != realinode->i_mode); - WARN_ON(!uid_eq(inode->i_uid, realinode->i_uid)); - WARN_ON(!gid_eq(inode->i_gid, realinode->i_gid)); - } return err; } @@ -525,7 +586,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, { int err; struct inode *inode; - struct cattr attr = { + struct ovl_cattr attr = { .rdev = rdev, .link = link, }; @@ -534,6 +595,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, if (err) goto out; + /* Preallocate inode to be used by ovl_get_inode() */ err = -ENOMEM; inode = ovl_new_inode(dentry->d_sb, mode, rdev); if (!inode) @@ -542,8 +604,9 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, inode_init_owner(inode, dentry->d_parent->d_inode, mode); attr.mode = inode->i_mode; - err = ovl_create_or_link(dentry, inode, &attr, NULL, false); - if (err) + err = ovl_create_or_link(dentry, inode, &attr, false); + /* Did we end up using the preallocated inode? */ + if (inode != d_inode(dentry)) iput(inode); out_drop_write: @@ -601,8 +664,9 @@ static int ovl_link(struct dentry *old, struct inode *newdir, inode = d_inode(old); ihold(inode); - err = ovl_create_or_link(new, inode, NULL, ovl_dentry_upper(old), - ovl_type_origin(old)); + err = ovl_create_or_link(new, inode, + &(struct ovl_cattr) {.hardlink = ovl_dentry_upper(old)}, + ovl_type_origin(old)); if (err) iput(inode); diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index 425a94672300..9941ece61a14 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -300,12 +300,18 @@ static struct dentry *ovl_obtain_alias(struct super_block *sb, struct dentry *dentry; struct inode *inode; struct ovl_entry *oe; + struct ovl_inode_params oip = { + .lowerpath = lowerpath, + .index = index, + .numlower = !!lower + }; /* We get overlay directory dentries with ovl_lookup_real() */ if (d_is_dir(upper ?: lower)) return ERR_PTR(-EIO); - inode = ovl_get_inode(sb, dget(upper), lowerpath, index, !!lower); + oip.upperdentry = dget(upper); + inode = ovl_get_inode(sb, &oip); if (IS_ERR(inode)) { dput(upper); return ERR_CAST(inode); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 6e3815fb006b..1db5b3b458a1 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -749,15 +749,26 @@ static bool ovl_hash_bylower(struct super_block *sb, struct dentry *upper, return true; } -struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry, - struct ovl_path *lowerpath, struct dentry *index, - unsigned int numlower) +static struct inode *ovl_iget5(struct super_block *sb, struct inode *newinode, + struct inode *key) { + return newinode ? inode_insert5(newinode, (unsigned long) key, + ovl_inode_test, ovl_inode_set, key) : + iget5_locked(sb, (unsigned long) key, + ovl_inode_test, ovl_inode_set, key); +} + +struct inode *ovl_get_inode(struct super_block *sb, + struct ovl_inode_params *oip) +{ + struct dentry *upperdentry = oip->upperdentry; + struct ovl_path *lowerpath = oip->lowerpath; struct inode *realinode = upperdentry ? d_inode(upperdentry) : NULL; struct inode *inode; struct dentry *lowerdentry = lowerpath ? lowerpath->dentry : NULL; - bool bylower = ovl_hash_bylower(sb, upperdentry, lowerdentry, index); - int fsid = bylower ? lowerpath->layer->fsid : 0; + bool bylower = ovl_hash_bylower(sb, upperdentry, lowerdentry, + oip->index); + int fsid = bylower ? oip->lowerpath->layer->fsid : 0; bool is_dir; unsigned long ino = 0; @@ -774,8 +785,7 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry, upperdentry); unsigned int nlink = is_dir ? 1 : realinode->i_nlink; - inode = iget5_locked(sb, (unsigned long) key, - ovl_inode_test, ovl_inode_set, key); + inode = ovl_iget5(sb, oip->newinode, key); if (!inode) goto out_nomem; if (!(inode->i_state & I_NEW)) { @@ -811,12 +821,12 @@ struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry, if (upperdentry && ovl_is_impuredir(upperdentry)) ovl_set_flag(OVL_IMPURE, inode); - if (index) + if (oip->index) ovl_set_flag(OVL_INDEX, inode); /* Check for non-merge dir that may have whiteouts */ if (is_dir) { - if (((upperdentry && lowerdentry) || numlower > 1) || + if (((upperdentry && lowerdentry) || oip->numlower > 1) || ovl_check_origin_xattr(upperdentry ?: lowerdentry)) { ovl_set_flag(OVL_WHITEOUTS, inode); } diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 2dba29eadde6..08801b45df00 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -1004,8 +1004,14 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, upperdentry = dget(index); if (upperdentry || ctr) { - inode = ovl_get_inode(dentry->d_sb, upperdentry, stack, index, - ctr); + struct ovl_inode_params oip = { + .upperdentry = upperdentry, + .lowerpath = stack, + .index = index, + .numlower = ctr, + }; + + inode = ovl_get_inode(dentry->d_sb, &oip); err = PTR_ERR(inode); if (IS_ERR(inode)) goto out_free_oe; diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index e0b7de799f6b..3c5e9f18b0d9 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -86,6 +86,7 @@ struct ovl_fh { static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) { int err = vfs_rmdir(dir, dentry); + pr_debug("rmdir(%pd2) = %i\n", dentry, err); return err; } @@ -93,56 +94,52 @@ static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry) { int err = vfs_unlink(dir, dentry, NULL); + pr_debug("unlink(%pd2) = %i\n", dentry, err); return err; } static inline int ovl_do_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry, bool debug) + struct dentry *new_dentry) { int err = vfs_link(old_dentry, dir, new_dentry, NULL); - if (debug) { - pr_debug("link(%pd2, %pd2) = %i\n", - old_dentry, new_dentry, err); - } + + pr_debug("link(%pd2, %pd2) = %i\n", old_dentry, new_dentry, err); return err; } static inline int ovl_do_create(struct inode *dir, struct dentry *dentry, - umode_t mode, bool debug) + umode_t mode) { int err = vfs_create(dir, dentry, mode, true); - if (debug) - pr_debug("create(%pd2, 0%o) = %i\n", dentry, mode, err); + + pr_debug("create(%pd2, 0%o) = %i\n", dentry, mode, err); return err; } static inline int ovl_do_mkdir(struct inode *dir, struct dentry *dentry, - umode_t mode, bool debug) + umode_t mode) { int err = vfs_mkdir(dir, dentry, mode); - if (debug) - pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, err); + pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, err); return err; } static inline int ovl_do_mknod(struct inode *dir, struct dentry *dentry, - umode_t mode, dev_t dev, bool debug) + umode_t mode, dev_t dev) { int err = vfs_mknod(dir, dentry, mode, dev); - if (debug) { - pr_debug("mknod(%pd2, 0%o, 0%o) = %i\n", - dentry, mode, dev, err); - } + + pr_debug("mknod(%pd2, 0%o, 0%o) = %i\n", dentry, mode, dev, err); return err; } static inline int ovl_do_symlink(struct inode *dir, struct dentry *dentry, - const char *oldname, bool debug) + const char *oldname) { int err = vfs_symlink(dir, dentry, oldname); - if (debug) - pr_debug("symlink(\"%s\", %pd2) = %i\n", oldname, dentry, err); + + pr_debug("symlink(\"%s\", %pd2) = %i\n", oldname, dentry, err); return err; } @@ -168,11 +165,8 @@ static inline int ovl_do_rename(struct inode *olddir, struct dentry *olddentry, { int err; - pr_debug("rename(%pd2, %pd2, 0x%x)\n", - olddentry, newdentry, flags); - + pr_debug("rename(%pd2, %pd2, 0x%x)\n", olddentry, newdentry, flags); err = vfs_rename(olddir, olddentry, newdir, newdentry, NULL, flags); - if (err) { pr_debug("...rename(%pd2, %pd2, ...) = %i\n", olddentry, newdentry, err); @@ -334,12 +328,18 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags); int ovl_update_time(struct inode *inode, struct timespec *ts, int flags); bool ovl_is_private_xattr(const char *name); +struct ovl_inode_params { + struct inode *newinode; + struct dentry *upperdentry; + struct ovl_path *lowerpath; + struct dentry *index; + unsigned int numlower; +}; struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev); struct inode *ovl_lookup_inode(struct super_block *sb, struct dentry *real, bool is_upper); -struct inode *ovl_get_inode(struct super_block *sb, struct dentry *upperdentry, - struct ovl_path *lowerpath, struct dentry *index, - unsigned int numlower); +struct inode *ovl_get_inode(struct super_block *sb, + struct ovl_inode_params *oip); static inline void ovl_copyattr(struct inode *from, struct inode *to) { to->i_uid = from->i_uid; @@ -352,18 +352,21 @@ static inline void ovl_copyattr(struct inode *from, struct inode *to) /* dir.c */ extern const struct inode_operations ovl_dir_inode_operations; -struct dentry *ovl_lookup_temp(struct dentry *workdir); int ovl_cleanup_and_whiteout(struct dentry *workdir, struct inode *dir, struct dentry *dentry); -struct cattr { +struct ovl_cattr { dev_t rdev; umode_t mode; const char *link; + struct dentry *hardlink; }; -int ovl_create_real(struct inode *dir, struct dentry *newdentry, - struct cattr *attr, - struct dentry *hardlink, bool debug); + +#define OVL_CATTR(m) (&(struct ovl_cattr) { .mode = (m) }) + +struct dentry *ovl_create_real(struct inode *dir, struct dentry *newdentry, + struct ovl_cattr *attr); int ovl_cleanup(struct inode *dir, struct dentry *dentry); +struct dentry *ovl_create_temp(struct dentry *workdir, struct ovl_cattr *attr); /* copy_up.c */ int ovl_copy_up(struct dentry *dentry); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e8551c97de51..704b37311467 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -611,11 +611,10 @@ retry: goto retry; } - err = ovl_create_real(dir, work, - &(struct cattr){.mode = S_IFDIR | 0}, - NULL, true); - if (err) - goto out_dput; + work = ovl_create_real(dir, work, OVL_CATTR(attr.ia_mode)); + err = PTR_ERR(work); + if (IS_ERR(work)) + goto out_err; /* * Try to remove POSIX ACL xattrs from workdir. We are good if: diff --git a/include/linux/fs.h b/include/linux/fs.h index 7ef7193488b8..77333ed3a488 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2886,6 +2886,10 @@ extern struct inode *ilookup5(struct super_block *sb, unsigned long hashval, int (*test)(struct inode *, void *), void *data); extern struct inode *ilookup(struct super_block *sb, unsigned long ino); +extern struct inode *inode_insert5(struct inode *inode, unsigned long hashval, + int (*test)(struct inode *, void *), + int (*set)(struct inode *, void *), + void *data); extern struct inode * iget5_locked(struct super_block *, unsigned long, int (*test)(struct inode *, void *), int (*set)(struct inode *, void *), void *); extern struct inode * iget_locked(struct super_block *, unsigned long); extern struct inode *find_inode_nowait(struct super_block *, |