diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-12-16 10:58:12 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-12-16 10:58:12 -0800 |
commit | ff0f962ca3c38239b299a70e7eea27abfbb979c3 (patch) | |
tree | ab37876cf2e8af4900f11f44ac4b96cb0f15e548 /fs/overlayfs | |
parent | 087a76d390cbb8c0d21ea0cb3672ab4a7bb76362 (diff) | |
parent | c3c8699664800a68600f1988302173067eaeaffa (diff) |
Merge branch 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull overlayfs updates from Miklos Szeredi:
"This update contains:
- try to clone on copy-up
- allow renaming a directory
- split source into managable chunks
- misc cleanups and fixes
It does not contain the read-only fd data inconsistency fix, which Al
didn't like. I'll leave that to the next year..."
* 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: (36 commits)
ovl: fix reStructuredText syntax errors in documentation
ovl: fix return value of ovl_fill_super
ovl: clean up kstat usage
ovl: fold ovl_copy_up_truncate() into ovl_copy_up()
ovl: create directories inside merged parent opaque
ovl: opaque cleanup
ovl: show redirect_dir mount option
ovl: allow setting max size of redirect
ovl: allow redirect_dir to default to "on"
ovl: check for emptiness of redirect dir
ovl: redirect on rename-dir
ovl: lookup redirects
ovl: consolidate lookup for underlying layers
ovl: fix nested overlayfs mount
ovl: check namelen
ovl: split super.c
ovl: use d_is_dir()
ovl: simplify lookup
ovl: check lower existence of rename target
ovl: rename: simplify handling of lower/merged directory
...
Diffstat (limited to 'fs/overlayfs')
-rw-r--r-- | fs/overlayfs/Kconfig | 14 | ||||
-rw-r--r-- | fs/overlayfs/Makefile | 2 | ||||
-rw-r--r-- | fs/overlayfs/copy_up.c | 61 | ||||
-rw-r--r-- | fs/overlayfs/dir.c | 375 | ||||
-rw-r--r-- | fs/overlayfs/inode.c | 78 | ||||
-rw-r--r-- | fs/overlayfs/namei.c | 401 | ||||
-rw-r--r-- | fs/overlayfs/overlayfs.h | 62 | ||||
-rw-r--r-- | fs/overlayfs/ovl_entry.h | 53 | ||||
-rw-r--r-- | fs/overlayfs/super.c | 557 | ||||
-rw-r--r-- | fs/overlayfs/util.c | 265 |
10 files changed, 1084 insertions, 784 deletions
diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig index 34355818a2e0..0daac5112f7a 100644 --- a/fs/overlayfs/Kconfig +++ b/fs/overlayfs/Kconfig @@ -8,3 +8,17 @@ config OVERLAY_FS merged with the 'upper' object. For more information see Documentation/filesystems/overlayfs.txt + +config OVERLAY_FS_REDIRECT_DIR + bool "Overlayfs: turn on redirect dir feature by default" + depends on OVERLAY_FS + help + If this config option is enabled then overlay filesystems will use + redirects when renaming directories by default. In this case it is + still possible to turn off redirects globally with the + "redirect_dir=off" module option or on a filesystem instance basis + with the "redirect_dir=off" mount option. + + 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. diff --git a/fs/overlayfs/Makefile b/fs/overlayfs/Makefile index 900daed3e91d..99373bbc1478 100644 --- a/fs/overlayfs/Makefile +++ b/fs/overlayfs/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_OVERLAY_FS) += overlay.o -overlay-objs := super.o inode.o dir.o readdir.o copy_up.o +overlay-objs := super.o namei.o util.o inode.o dir.o readdir.o copy_up.o diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 2838bddb1f91..f57043dace62 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -153,6 +153,13 @@ static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len) goto out_fput; } + /* Try to use clone_file_range to clone up within the same fs */ + error = vfs_clone_file_range(old_file, 0, new_file, 0, len); + if (!error) + goto out; + /* Couldn't clone, so now we try to copy the data */ + error = 0; + /* FIXME: copy up sparse files efficiently */ while (len) { size_t this_len = OVL_COPY_UP_CHUNK_SIZE; @@ -177,7 +184,7 @@ static int ovl_copy_up_data(struct path *old, struct path *new, loff_t len) len -= bytes; } - +out: if (!error) error = vfs_fsync(new_file, 0); fput(new_file); @@ -231,10 +238,15 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, struct inode *udir = upperdir->d_inode; struct dentry *newdentry = NULL; struct dentry *upper = NULL; - umode_t mode = stat->mode; int err; const struct cred *old_creds = NULL; 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 + }; newdentry = ovl_lookup_temp(workdir, dentry); err = PTR_ERR(newdentry); @@ -254,10 +266,7 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, if (new_creds) old_creds = override_creds(new_creds); - /* Can't properly set mode on creation because of the umask */ - stat->mode &= S_IFMT; - err = ovl_create_real(wdir, newdentry, stat, link, NULL, true); - stat->mode = mode; + err = ovl_create_real(wdir, newdentry, &cattr, NULL, true); if (new_creds) { revert_creds(old_creds); @@ -296,12 +305,6 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, ovl_dentry_update(dentry, newdentry); ovl_inode_update(d_inode(dentry), d_inode(newdentry)); newdentry = NULL; - - /* - * Non-directores become opaque when copied up. - */ - if (!S_ISDIR(stat->mode)) - ovl_dentry_set_opaque(dentry, true); out2: dput(upper); out1: @@ -317,20 +320,14 @@ out_cleanup: /* * Copy up a single dentry * - * Directory renames only allowed on "pure upper" (already created on - * upper filesystem, never copied up). Directories which are on lower or - * are merged may not be renamed. For these -EXDEV is returned and - * userspace has to deal with it. This means, when copying up a - * directory we can rely on it and ancestors being stable. - * - * Non-directory renames start with copy up of source if necessary. The - * actual rename will only proceed once the copy up was successful. Copy - * up uses upper parent i_mutex for exclusion. Since rename can change - * d_parent it is possible that the copy up will lock the old parent. At - * that point the file will have already been copied up anyway. + * All renames start with copy up of source if necessary. The actual + * rename will only proceed once the copy up was successful. Copy up uses + * upper parent i_mutex for exclusion. Since rename can change d_parent it + * is possible that the copy up will lock the old parent. At that point + * the file will have already been copied up anyway. */ -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 dentry *dentry, + struct path *lowerpath, struct kstat *stat) { DEFINE_DELAYED_CALL(done); struct dentry *workdir = ovl_workdir(dentry); @@ -339,7 +336,6 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, struct path parentpath; struct dentry *lowerdentry = lowerpath->dentry; struct dentry *upperdir; - struct dentry *upperdentry; const char *link = NULL; if (WARN_ON(!workdir)) @@ -365,8 +361,7 @@ int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry, pr_err("overlayfs: failed to lock workdir+upperdir\n"); goto out_unlock; } - upperdentry = ovl_dentry_upper(dentry); - if (upperdentry) { + if (ovl_dentry_upper(dentry)) { /* Raced with another copy-up? Nothing to do, then... */ err = 0; goto out_unlock; @@ -385,7 +380,7 @@ out_unlock: return err; } -int ovl_copy_up(struct dentry *dentry) +int ovl_copy_up_flags(struct dentry *dentry, int flags) { int err = 0; const struct cred *old_cred = ovl_override_creds(dentry->d_sb); @@ -415,6 +410,9 @@ int ovl_copy_up(struct dentry *dentry) ovl_path_lower(next, &lowerpath); err = vfs_getattr(&lowerpath, &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); @@ -425,3 +423,8 @@ int ovl_copy_up(struct dentry *dentry) return err; } + +int ovl_copy_up(struct dentry *dentry) +{ + return ovl_copy_up_flags(dentry, 0); +} diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 306b6c161840..16e06dd89457 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -12,11 +12,18 @@ #include <linux/xattr.h> #include <linux/security.h> #include <linux/cred.h> +#include <linux/module.h> #include <linux/posix_acl.h> #include <linux/posix_acl_xattr.h> #include <linux/atomic.h> +#include <linux/ratelimit.h> #include "overlayfs.h" +static unsigned short ovl_redirect_max = 256; +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 err; @@ -75,8 +82,7 @@ static struct dentry *ovl_whiteout(struct dentry *workdir, } int ovl_create_real(struct inode *dir, struct dentry *newdentry, - struct kstat *stat, const char *link, - struct dentry *hardlink, bool debug) + struct cattr *attr, struct dentry *hardlink, bool debug) { int err; @@ -86,13 +92,13 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, if (hardlink) { err = ovl_do_link(hardlink, dir, newdentry, debug); } else { - switch (stat->mode & S_IFMT) { + switch (attr->mode & S_IFMT) { case S_IFREG: - err = ovl_do_create(dir, newdentry, stat->mode, debug); + err = ovl_do_create(dir, newdentry, attr->mode, debug); break; case S_IFDIR: - err = ovl_do_mkdir(dir, newdentry, stat->mode, debug); + err = ovl_do_mkdir(dir, newdentry, attr->mode, debug); break; case S_IFCHR: @@ -100,11 +106,11 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, case S_IFIFO: case S_IFSOCK: err = ovl_do_mknod(dir, newdentry, - stat->mode, stat->rdev, debug); + attr->mode, attr->rdev, debug); break; case S_IFLNK: - err = ovl_do_symlink(dir, newdentry, link, debug); + err = ovl_do_symlink(dir, newdentry, attr->link, debug); break; default: @@ -121,20 +127,15 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, return err; } -static int ovl_set_opaque(struct dentry *upperdentry) -{ - return ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); -} - -static void ovl_remove_opaque(struct dentry *upperdentry) +static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry) { int err; - err = ovl_do_removexattr(upperdentry, OVL_XATTR_OPAQUE); - if (err) { - pr_warn("overlayfs: failed to remove opaque from '%s' (%i)\n", - upperdentry->d_name.name, err); - } + err = ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); + if (!err) + ovl_dentry_set_opaque(dentry); + + return err; } static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, @@ -182,9 +183,13 @@ static void ovl_instantiate(struct dentry *dentry, struct inode *inode, d_instantiate(dentry, inode); } +static bool ovl_type_merge(struct dentry *dentry) +{ + return OVL_TYPE_MERGE(ovl_path_type(dentry)); +} + static int ovl_create_upper(struct dentry *dentry, struct inode *inode, - struct kstat *stat, const char *link, - struct dentry *hardlink) + struct cattr *attr, struct dentry *hardlink) { struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct inode *udir = upperdir->d_inode; @@ -192,7 +197,7 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, int err; if (!hardlink && !IS_POSIXACL(udir)) - stat->mode &= ~current_umask(); + attr->mode &= ~current_umask(); inode_lock_nested(udir, I_MUTEX_PARENT); newdentry = lookup_one_len(dentry->d_name.name, upperdir, @@ -200,10 +205,15 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode, err = PTR_ERR(newdentry); if (IS_ERR(newdentry)) goto out_unlock; - err = ovl_create_real(udir, newdentry, stat, link, hardlink, false); + err = ovl_create_real(udir, newdentry, attr, hardlink, false); if (err) goto out_dput; + if (ovl_type_merge(dentry->d_parent)) { + /* 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: @@ -270,7 +280,8 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry, if (IS_ERR(opaquedir)) goto out_unlock; - err = ovl_create_real(wdir, opaquedir, &stat, NULL, NULL, true); + err = ovl_create_real(wdir, opaquedir, + &(struct cattr){.mode = stat.mode}, NULL, true); if (err) goto out_dput; @@ -278,7 +289,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry, if (err) goto out_cleanup; - err = ovl_set_opaque(opaquedir); + err = ovl_set_opaque(dentry, opaquedir); if (err) goto out_cleanup; @@ -370,7 +381,7 @@ out_free: } static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, - struct kstat *stat, const char *link, + struct cattr *cattr, struct dentry *hardlink) { struct dentry *workdir = ovl_workdir(dentry); @@ -387,7 +398,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (!hardlink) { err = posix_acl_create(dentry->d_parent->d_inode, - &stat->mode, &default_acl, &acl); + &cattr->mode, &default_acl, &acl); if (err) return err; } @@ -407,7 +418,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, if (IS_ERR(upper)) goto out_dput; - err = ovl_create_real(wdir, newdentry, stat, link, hardlink, true); + err = ovl_create_real(wdir, newdentry, cattr, hardlink, true); if (err) goto out_dput2; @@ -415,10 +426,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, * mode could have been mutilated due to umask (e.g. sgid directory) */ if (!hardlink && - !S_ISLNK(stat->mode) && newdentry->d_inode->i_mode != stat->mode) { + !S_ISLNK(cattr->mode) && + newdentry->d_inode->i_mode != cattr->mode) { struct iattr attr = { .ia_valid = ATTR_MODE, - .ia_mode = stat->mode, + .ia_mode = cattr->mode, }; inode_lock(newdentry->d_inode); err = notify_change(newdentry, &attr, NULL); @@ -438,8 +450,8 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, goto out_cleanup; } - if (!hardlink && S_ISDIR(stat->mode)) { - err = ovl_set_opaque(newdentry); + if (!hardlink && S_ISDIR(cattr->mode)) { + err = ovl_set_opaque(dentry, newdentry); if (err) goto out_cleanup; @@ -475,8 +487,7 @@ out_cleanup: } static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, - struct kstat *stat, const char *link, - struct dentry *hardlink) + struct cattr *attr, struct dentry *hardlink) { int err; const struct cred *old_cred; @@ -494,7 +505,7 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, override_cred->fsgid = inode->i_gid; if (!hardlink) { err = security_dentry_create_files_as(dentry, - stat->mode, &dentry->d_name, old_cred, + attr->mode, &dentry->d_name, old_cred, override_cred); if (err) { put_cred(override_cred); @@ -504,12 +515,12 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode, put_cred(override_creds(override_cred)); put_cred(override_cred); - if (!ovl_dentry_is_opaque(dentry)) - err = ovl_create_upper(dentry, inode, stat, link, + if (!ovl_dentry_is_whiteout(dentry)) + err = ovl_create_upper(dentry, inode, attr, hardlink); else - err = ovl_create_over_whiteout(dentry, inode, stat, - link, hardlink); + err = ovl_create_over_whiteout(dentry, inode, attr, + hardlink); } out_revert_creds: revert_creds(old_cred); @@ -528,8 +539,9 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, { int err; struct inode *inode; - struct kstat stat = { + struct cattr attr = { .rdev = rdev, + .link = link, }; err = ovl_want_write(dentry); @@ -537,14 +549,14 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, goto out; err = -ENOMEM; - inode = ovl_new_inode(dentry->d_sb, mode); + inode = ovl_new_inode(dentry->d_sb, mode, rdev); if (!inode) goto out_drop_write; inode_init_owner(inode, dentry->d_parent->d_inode, mode); - stat.mode = inode->i_mode; + attr.mode = inode->i_mode; - err = ovl_create_or_link(dentry, inode, &stat, link, NULL); + err = ovl_create_or_link(dentry, inode, &attr, NULL); if (err) iput(inode); @@ -598,7 +610,7 @@ static int ovl_link(struct dentry *old, struct inode *newdir, inode = d_inode(old); ihold(inode); - err = ovl_create_or_link(new, inode, NULL, NULL, ovl_dentry_upper(old)); + err = ovl_create_or_link(new, inode, NULL, ovl_dentry_upper(old)); if (err) iput(inode); @@ -684,8 +696,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct inode *dir = upperdir->d_inode; struct dentry *upper; + struct dentry *opaquedir = NULL; int err; + /* Redirect dir can be !ovl_lower_positive && OVL_TYPE_MERGE */ + if (is_dir && ovl_dentry_get_redirect(dentry)) { + opaquedir = ovl_check_empty_and_clear(dentry); + err = PTR_ERR(opaquedir); + if (IS_ERR(opaquedir)) + goto out; + } + inode_lock_nested(dir, I_MUTEX_PARENT); upper = lookup_one_len(dentry->d_name.name, upperdir, dentry->d_name.len); @@ -694,14 +715,15 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) goto out_unlock; err = -ESTALE; - if (upper == ovl_dentry_upper(dentry)) { - if (is_dir) - err = vfs_rmdir(dir, upper); - else - err = vfs_unlink(dir, upper, NULL); - ovl_dentry_version_inc(dentry->d_parent); - } - dput(upper); + if ((opaquedir && upper != opaquedir) || + (!opaquedir && upper != ovl_dentry_upper(dentry))) + goto out_dput_upper; + + if (is_dir) + err = vfs_rmdir(dir, upper); + else + err = vfs_unlink(dir, upper, NULL); + ovl_dentry_version_inc(dentry->d_parent); /* * Keeping this dentry hashed would mean having to release @@ -711,34 +733,21 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir) */ if (!err) d_drop(dentry); +out_dput_upper: + dput(upper); out_unlock: inode_unlock(dir); - + dput(opaquedir); +out: return err; } -static inline int ovl_check_sticky(struct dentry *dentry) -{ - struct inode *dir = ovl_dentry_real(dentry->d_parent)->d_inode; - struct inode *inode = ovl_dentry_real(dentry)->d_inode; - - if (check_sticky(dir, inode)) - return -EPERM; - - return 0; -} - static int ovl_do_remove(struct dentry *dentry, bool is_dir) { enum ovl_path_type type; int err; const struct cred *old_cred; - - err = ovl_check_sticky(dentry); - if (err) - goto out; - err = ovl_want_write(dentry); if (err) goto out; @@ -750,7 +759,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) type = ovl_path_type(dentry); old_cred = ovl_override_creds(dentry->d_sb); - if (OVL_TYPE_PURE_UPPER(type)) + if (!ovl_lower_positive(dentry)) err = ovl_remove_upper(dentry, is_dir); else err = ovl_remove_and_whiteout(dentry, is_dir); @@ -777,13 +786,114 @@ static int ovl_rmdir(struct inode *dir, struct dentry *dentry) return ovl_do_remove(dentry, true); } -static int ovl_rename2(struct inode *olddir, struct dentry *old, - struct inode *newdir, struct dentry *new, - unsigned int flags) +static bool ovl_type_merge_or_lower(struct dentry *dentry) +{ + enum ovl_path_type type = ovl_path_type(dentry); + + return OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type); +} + +static bool ovl_can_move(struct dentry *dentry) +{ + return ovl_redirect_dir(dentry->d_sb) || + !d_is_dir(dentry) || !ovl_type_merge_or_lower(dentry); +} + +static char *ovl_get_redirect(struct dentry *dentry, bool samedir) +{ + char *buf, *ret; + struct dentry *d, *tmp; + int buflen = ovl_redirect_max + 1; + + if (samedir) { + ret = kstrndup(dentry->d_name.name, dentry->d_name.len, + GFP_KERNEL); + goto out; + } + + buf = ret = kmalloc(buflen, GFP_TEMPORARY); + if (!buf) + goto out; + + buflen--; + buf[buflen] = '\0'; + for (d = dget(dentry); !IS_ROOT(d);) { + const char *name; + int thislen; + + spin_lock(&d->d_lock); + name = ovl_dentry_get_redirect(d); + if (name) { + thislen = strlen(name); + } else { + name = d->d_name.name; + thislen = d->d_name.len; + } + + /* If path is too long, fall back to userspace move */ + if (thislen + (name[0] != '/') > buflen) { + ret = ERR_PTR(-EXDEV); + spin_unlock(&d->d_lock); + goto out_put; + } + + buflen -= thislen; + memcpy(&buf[buflen], name, thislen); + tmp = dget_dlock(d->d_parent); + spin_unlock(&d->d_lock); + + dput(d); + d = tmp; + + /* Absolute redirect: finished */ + if (buf[buflen] == '/') + break; + buflen--; + buf[buflen] = '/'; + } + ret = kstrdup(&buf[buflen], GFP_KERNEL); +out_put: + dput(d); + kfree(buf); +out: + return ret ? ret : ERR_PTR(-ENOMEM); +} + +static int ovl_set_redirect(struct dentry *dentry, bool samedir) +{ + int err; + const char *redirect = ovl_dentry_get_redirect(dentry); + + if (redirect && (samedir || redirect[0] == '/')) + return 0; + + redirect = ovl_get_redirect(dentry, samedir); + if (IS_ERR(redirect)) + return PTR_ERR(redirect); + + err = ovl_do_setxattr(ovl_dentry_upper(dentry), OVL_XATTR_REDIRECT, + redirect, strlen(redirect), 0); + if (!err) { + spin_lock(&dentry->d_lock); + ovl_dentry_set_redirect(dentry, redirect); + spin_unlock(&dentry->d_lock); + } else { + kfree(redirect); + if (err == -EOPNOTSUPP) + ovl_clear_redirect_dir(dentry->d_sb); + else + pr_warn_ratelimited("overlay: failed to set redirect (%i)\n", err); + /* Fall back to userspace copy-up */ + err = -EXDEV; + } + return err; +} + +static int ovl_rename(struct inode *olddir, struct dentry *old, + struct inode *newdir, struct dentry *new, + unsigned int flags) { int err; - enum ovl_path_type old_type; - enum ovl_path_type new_type; struct dentry *old_upperdir; struct dentry *new_upperdir; struct dentry *olddentry; @@ -794,7 +904,8 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, bool cleanup_whiteout = false; bool overwrite = !(flags & RENAME_EXCHANGE); bool is_dir = d_is_dir(old); - bool new_is_dir = false; + bool new_is_dir = d_is_dir(new); + bool samedir = olddir == newdir; struct dentry *opaquedir = NULL; const struct cred *old_cred = NULL; @@ -804,46 +915,12 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, flags &= ~RENAME_NOREPLACE; - err = ovl_check_sticky(old); - if (err) - goto out; - /* Don't copy up directory trees */ - old_type = ovl_path_type(old); err = -EXDEV; - if (OVL_TYPE_MERGE_OR_LOWER(old_type) && is_dir) + if (!ovl_can_move(old)) + goto out; + if (!overwrite && !ovl_can_move(new)) goto out; - - if (new->d_inode) { - err = ovl_check_sticky(new); - if (err) - goto out; - - if (d_is_dir(new)) - new_is_dir = true; - - new_type = ovl_path_type(new); - err = -EXDEV; - if (!overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) - goto out; - - err = 0; - if (!OVL_TYPE_UPPER(new_type) && !OVL_TYPE_UPPER(old_type)) { - if (ovl_dentry_lower(old)->d_inode == - ovl_dentry_lower(new)->d_inode) - goto out; - } - if (OVL_TYPE_UPPER(new_type) && OVL_TYPE_UPPER(old_type)) { - if (ovl_dentry_upper(old)->d_inode == - ovl_dentry_upper(new)->d_inode) - goto out; - } - } else { - if (ovl_dentry_is_opaque(new)) - new_type = __OVL_PATH_UPPER; - else - new_type = __OVL_PATH_UPPER | __OVL_PATH_PURE; - } err = ovl_want_write(old); if (err) @@ -862,12 +939,9 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, goto out_drop_write; } - old_opaque = !OVL_TYPE_PURE_UPPER(old_type); - new_opaque = !OVL_TYPE_PURE_UPPER(new_type); - old_cred = ovl_override_creds(old->d_sb); - if (overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) { + if (overwrite && new_is_dir && ovl_type_merge_or_lower(new)) { opaquedir = ovl_check_empty_and_clear(new); err = PTR_ERR(opaquedir); if (IS_ERR(opaquedir)) { @@ -877,15 +951,15 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, } if (overwrite) { - if (old_opaque) { - if (new->d_inode || !new_opaque) { + if (ovl_lower_positive(old)) { + if (!ovl_dentry_is_whiteout(new)) { /* Whiteout source */ flags |= RENAME_WHITEOUT; } else { /* Switch whiteouts */ flags |= RENAME_EXCHANGE; } - } else if (is_dir && !new->d_inode && new_opaque) { + } else if (is_dir && ovl_dentry_is_whiteout(new)) { flags |= RENAME_EXCHANGE; cleanup_whiteout = true; } @@ -896,7 +970,6 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, trap = lock_rename(new_upperdir, old_upperdir); - olddentry = lookup_one_len(old->d_name.name, old_upperdir, old->d_name.len); err = PTR_ERR(olddentry); @@ -913,6 +986,9 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, if (IS_ERR(newdentry)) goto out_dput_old; + old_opaque = ovl_dentry_is_opaque(old); + new_opaque = ovl_dentry_is_opaque(new); + err = -ESTALE; if (ovl_dentry_upper(new)) { if (opaquedir) { @@ -933,54 +1009,31 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, if (newdentry == trap) goto out_dput; - if (is_dir && !old_opaque && new_opaque) { - err = ovl_set_opaque(olddentry); + if (WARN_ON(olddentry->d_inode == newdentry->d_inode)) + goto out_dput; + + err = 0; + if (is_dir) { + if (ovl_type_merge_or_lower(old)) + err = ovl_set_redirect(old, samedir); + else if (!old_opaque && ovl_type_merge(new->d_parent)) + err = ovl_set_opaque(old, olddentry); if (err) goto out_dput; } - if (!overwrite && new_is_dir && old_opaque && !new_opaque) { - err = ovl_set_opaque(newdentry); + if (!overwrite && new_is_dir) { + if (ovl_type_merge_or_lower(new)) + err = ovl_set_redirect(new, samedir); + else if (!new_opaque && ovl_type_merge(old->d_parent)) + err = ovl_set_opaque(new, newdentry); if (err) goto out_dput; } - if (old_opaque || new_opaque) { - err = ovl_do_rename(old_upperdir->d_inode, olddentry, - new_upperdir->d_inode, newdentry, - flags); - } else { - /* No debug for the plain case */ - BUG_ON(flags & ~RENAME_EXCHANGE); - err = vfs_rename(old_upperdir->d_inode, olddentry, - new_upperdir->d_inode, newdentry, - NULL, flags); - } - - if (err) { - if (is_dir && !old_opaque && new_opaque) - ovl_remove_opaque(olddentry); - if (!overwrite && new_is_dir && old_opaque && !new_opaque) - ovl_remove_opaque(newdentry); + err = ovl_do_rename(old_upperdir->d_inode, olddentry, + new_upperdir->d_inode, newdentry, flags); + if (err) goto out_dput; - } - - if (is_dir && old_opaque && !new_opaque) - ovl_remove_opaque(olddentry); - if (!overwrite && new_is_dir && !old_opaque && new_opaque) - ovl_remove_opaque(newdentry); - - /* - * Old dentry now lives in different location. Dentries in - * lowerstack are stale. We cannot drop them here because - * access to them is lockless. This could be only pure upper - * or opaque directory - numlower is zero. Or upper non-dir - * entry - its pureness is tracked by flag opaque. - */ - if (old_opaque != new_opaque) { - ovl_dentry_set_opaque(old, new_opaque); - if (!overwrite) - ovl_dentry_set_opaque(new, old_opaque); - } if (cleanup_whiteout) ovl_cleanup(old_upperdir->d_inode, newdentry); @@ -1009,7 +1062,7 @@ const struct inode_operations ovl_dir_inode_operations = { .symlink = ovl_symlink, .unlink = ovl_unlink, .rmdir = ovl_rmdir, - .rename = ovl_rename2, + .rename = ovl_rename, .link = ovl_link, .setattr = ovl_setattr, .create = ovl_create, diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 7fb53d055537..1ab8b0dbc237 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -13,34 +13,6 @@ #include <linux/posix_acl.h> #include "overlayfs.h" -static int ovl_copy_up_truncate(struct dentry *dentry) -{ - int err; - struct dentry *parent; - struct kstat stat; - struct path lowerpath; - const struct cred *old_cred; - - parent = dget_parent(dentry); - err = ovl_copy_up(parent); - if (err) - goto out_dput_parent; - - ovl_path_lower(dentry, &lowerpath); - - old_cred = ovl_override_creds(dentry->d_sb); - err = vfs_getattr(&lowerpath, &stat); - if (!err) { - stat.size = 0; - err = ovl_copy_up_one(parent, dentry, &lowerpath, &stat); - } - revert_creds(old_cred); - -out_dput_parent: - dput(parent); - return err; -} - int ovl_setattr(struct dentry *dentry, struct iattr *attr) { int err; @@ -64,27 +36,10 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) if (err) goto out; - if (attr->ia_valid & ATTR_SIZE) { - struct inode *realinode = d_inode(ovl_dentry_real(dentry)); - - err = -ETXTBSY; - if (atomic_read(&realinode->i_writecount) < 0) - goto out_drop_write; - } - err = ovl_copy_up(dentry); if (!err) { - struct inode *winode = NULL; - upperdentry = ovl_dentry_upper(dentry); - if (attr->ia_valid & ATTR_SIZE) { - winode = d_inode(upperdentry); - err = get_write_access(winode); - if (err) - goto out_drop_write; - } - if (attr->ia_valid & (ATTR_KILL_SUID|ATTR_KILL_SGID)) attr->ia_valid &= ~ATTR_MODE; @@ -95,11 +50,7 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) if (!err) ovl_copyattr(upperdentry->d_inode, dentry->d_inode); inode_unlock(upperdentry->d_inode); - - if (winode) - put_write_access(winode); } -out_drop_write: ovl_drop_write(dentry); out: return err; @@ -302,10 +253,7 @@ int ovl_open_maybe_copy_up(struct dentry *dentry, unsigned int file_flags) if (ovl_open_need_copy_up(file_flags, type, realpath.dentry)) { err = ovl_want_write(dentry); if (!err) { - if (file_flags & O_TRUNC) - err = ovl_copy_up_truncate(dentry); - else - err = ovl_copy_up(dentry); + err = ovl_copy_up_flags(dentry, file_flags); ovl_drop_write(dentry); } } @@ -354,7 +302,7 @@ static const struct inode_operations ovl_symlink_inode_operations = { .update_time = ovl_update_time, }; -static void ovl_fill_inode(struct inode *inode, umode_t mode) +static void ovl_fill_inode(struct inode *inode, umode_t mode, dev_t rdev) { inode->i_ino = get_next_ino(); inode->i_mode = mode; @@ -363,8 +311,11 @@ static void ovl_fill_inode(struct inode *inode, umode_t mode) inode->i_acl = inode->i_default_acl = ACL_DONT_CACHE; #endif - mode &= S_IFMT; - switch (mode) { + switch (mode & S_IFMT) { + case S_IFREG: + inode->i_op = &ovl_file_inode_operations; + break; + case S_IFDIR: inode->i_op = &ovl_dir_inode_operations; inode->i_fop = &ovl_dir_operations; @@ -375,26 +326,19 @@ static void ovl_fill_inode(struct inode *inode, umode_t mode) break; default: - WARN(1, "illegal file type: %i\n", mode); - /* Fall through */ - - case S_IFREG: - case S_IFSOCK: - case S_IFBLK: - case S_IFCHR: - case S_IFIFO: inode->i_op = &ovl_file_inode_operations; + init_special_inode(inode, mode, rdev); break; } } -struct inode *ovl_new_inode(struct super_block *sb, umode_t mode) +struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev) { struct inode *inode; inode = new_inode(sb); if (inode) - ovl_fill_inode(inode, mode); + ovl_fill_inode(inode, mode, rdev); return inode; } @@ -418,7 +362,7 @@ struct inode *ovl_get_inode(struct super_block *sb, struct inode *realinode) 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); + ovl_fill_inode(inode, realinode->i_mode, realinode->i_rdev); set_nlink(inode, realinode->i_nlink); unlock_new_inode(inode); |