summaryrefslogtreecommitdiff
path: root/fs/overlayfs/util.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2018-08-21 18:19:09 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2018-08-21 18:19:09 -0700
commitd9a185f8b49678775ef56ecbdbc7b76970302897 (patch)
tree7ace1b26133e5d796af09e5d71d6531bcb69865c /fs/overlayfs/util.c
parentc22fc16d172fba4d19ffd8f2aa8fe67edba63895 (diff)
parent989974c804574d250ac92d44e220081959ac8ac1 (diff)
Merge tag 'ovl-update-4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull overlayfs updates from Miklos Szeredi: "This contains two new features: - Stack file operations: this allows removal of several hacks from the VFS, proper interaction of read-only open files with copy-up, possibility to implement fs modifying ioctls properly, and others. - Metadata only copy-up: when file is on lower layer and only metadata is modified (except size) then only copy up the metadata and continue to use the data from the lower file" * tag 'ovl-update-4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: (66 commits) ovl: Enable metadata only feature ovl: Do not do metacopy only for ioctl modifying file attr ovl: Do not do metadata only copy-up for truncate operation ovl: add helper to force data copy-up ovl: Check redirect on index as well ovl: Set redirect on upper inode when it is linked ovl: Set redirect on metacopy files upon rename ovl: Do not set dentry type ORIGIN for broken hardlinks ovl: Add an inode flag OVL_CONST_INO ovl: Treat metacopy dentries as type OVL_PATH_MERGE ovl: Check redirects for metacopy files ovl: Move some dir related ovl_lookup_single() code in else block ovl: Do not expose metacopy only dentry from d_real() ovl: Open file with data except for the case of fsync ovl: Add helper ovl_inode_realdata() ovl: Store lower data inode in ovl_inode ovl: Fix ovl_getattr() to get number of blocks from lower ovl: Add helper ovl_dentry_lowerdata() to get lower data dentry ovl: Copy up meta inode data from lowest data inode ovl: Modify ovl_lookup() and friends to lookup metacopy dentry ...
Diffstat (limited to 'fs/overlayfs/util.c')
-rw-r--r--fs/overlayfs/util.c252
1 files changed, 246 insertions, 6 deletions
diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c
index 6f1078028c66..8cfb62cc8672 100644
--- a/fs/overlayfs/util.c
+++ b/fs/overlayfs/util.c
@@ -133,8 +133,10 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry)
* Non-dir dentry can hold lower dentry of its copy up origin.
*/
if (oe->numlower) {
- type |= __OVL_PATH_ORIGIN;
- if (d_is_dir(dentry))
+ if (ovl_test_flag(OVL_CONST_INO, d_inode(dentry)))
+ type |= __OVL_PATH_ORIGIN;
+ if (d_is_dir(dentry) ||
+ !ovl_has_upperdata(d_inode(dentry)))
type |= __OVL_PATH_MERGE;
}
} else {
@@ -164,6 +166,18 @@ void ovl_path_lower(struct dentry *dentry, struct path *path)
}
}
+void ovl_path_lowerdata(struct dentry *dentry, struct path *path)
+{
+ struct ovl_entry *oe = dentry->d_fsdata;
+
+ if (oe->numlower) {
+ path->mnt = oe->lowerstack[oe->numlower - 1].layer->mnt;
+ path->dentry = oe->lowerstack[oe->numlower - 1].dentry;
+ } else {
+ *path = (struct path) { };
+ }
+}
+
enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path)
{
enum ovl_path_type type = ovl_path_type(dentry);
@@ -195,6 +209,19 @@ struct ovl_layer *ovl_layer_lower(struct dentry *dentry)
return oe->numlower ? oe->lowerstack[0].layer : NULL;
}
+/*
+ * ovl_dentry_lower() could return either a data dentry or metacopy dentry
+ * dependig on what is stored in lowerstack[0]. At times we need to find
+ * lower dentry which has data (and not metacopy dentry). This helper
+ * returns the lower data dentry.
+ */
+struct dentry *ovl_dentry_lowerdata(struct dentry *dentry)
+{
+ struct ovl_entry *oe = dentry->d_fsdata;
+
+ return oe->numlower ? oe->lowerstack[oe->numlower - 1].dentry : NULL;
+}
+
struct dentry *ovl_dentry_real(struct dentry *dentry)
{
return ovl_dentry_upper(dentry) ?: ovl_dentry_lower(dentry);
@@ -222,6 +249,26 @@ struct inode *ovl_inode_real(struct inode *inode)
return ovl_inode_upper(inode) ?: ovl_inode_lower(inode);
}
+/* Return inode which contains lower data. Do not return metacopy */
+struct inode *ovl_inode_lowerdata(struct inode *inode)
+{
+ if (WARN_ON(!S_ISREG(inode->i_mode)))
+ return NULL;
+
+ return OVL_I(inode)->lowerdata ?: ovl_inode_lower(inode);
+}
+
+/* Return real inode which contains data. Does not return metacopy inode */
+struct inode *ovl_inode_realdata(struct inode *inode)
+{
+ struct inode *upperinode;
+
+ upperinode = ovl_inode_upper(inode);
+ if (upperinode && ovl_has_upperdata(inode))
+ return upperinode;
+
+ return ovl_inode_lowerdata(inode);
+}
struct ovl_dir_cache *ovl_dir_cache(struct inode *inode)
{
@@ -279,6 +326,62 @@ void ovl_dentry_set_upper_alias(struct dentry *dentry)
ovl_dentry_set_flag(OVL_E_UPPER_ALIAS, dentry);
}
+static bool ovl_should_check_upperdata(struct inode *inode)
+{
+ if (!S_ISREG(inode->i_mode))
+ return false;
+
+ if (!ovl_inode_lower(inode))
+ return false;
+
+ return true;
+}
+
+bool ovl_has_upperdata(struct inode *inode)
+{
+ if (!ovl_should_check_upperdata(inode))
+ return true;
+
+ if (!ovl_test_flag(OVL_UPPERDATA, inode))
+ return false;
+ /*
+ * Pairs with smp_wmb() in ovl_set_upperdata(). Main user of
+ * ovl_has_upperdata() is ovl_copy_up_meta_inode_data(). Make sure
+ * if setting of OVL_UPPERDATA is visible, then effects of writes
+ * before that are visible too.
+ */
+ smp_rmb();
+ return true;
+}
+
+void ovl_set_upperdata(struct inode *inode)
+{
+ /*
+ * Pairs with smp_rmb() in ovl_has_upperdata(). Make sure
+ * if OVL_UPPERDATA flag is visible, then effects of write operations
+ * before it are visible as well.
+ */
+ smp_wmb();
+ ovl_set_flag(OVL_UPPERDATA, inode);
+}
+
+/* Caller should hold ovl_inode->lock */
+bool ovl_dentry_needs_data_copy_up_locked(struct dentry *dentry, int flags)
+{
+ if (!ovl_open_flags_need_copy_up(flags))
+ return false;
+
+ return !ovl_test_flag(OVL_UPPERDATA, d_inode(dentry));
+}
+
+bool ovl_dentry_needs_data_copy_up(struct dentry *dentry, int flags)
+{
+ if (!ovl_open_flags_need_copy_up(flags))
+ return false;
+
+ return !ovl_has_upperdata(d_inode(dentry));
+}
+
bool ovl_redirect_dir(struct super_block *sb)
{
struct ovl_fs *ofs = sb->s_fs_info;
@@ -300,7 +403,7 @@ void ovl_dentry_set_redirect(struct dentry *dentry, const char *redirect)
}
void ovl_inode_init(struct inode *inode, struct dentry *upperdentry,
- struct dentry *lowerdentry)
+ struct dentry *lowerdentry, struct dentry *lowerdata)
{
struct inode *realinode = d_inode(upperdentry ?: lowerdentry);
@@ -308,8 +411,11 @@ void ovl_inode_init(struct inode *inode, struct dentry *upperdentry,
OVL_I(inode)->__upperdentry = upperdentry;
if (lowerdentry)
OVL_I(inode)->lower = igrab(d_inode(lowerdentry));
+ if (lowerdata)
+ OVL_I(inode)->lowerdata = igrab(d_inode(lowerdata));
ovl_copyattr(realinode, inode);
+ ovl_copyflags(realinode, inode);
if (!inode->i_ino)
inode->i_ino = realinode->i_ino;
}
@@ -333,7 +439,7 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry)
}
}
-void ovl_dentry_version_inc(struct dentry *dentry, bool impurity)
+static void ovl_dentry_version_inc(struct dentry *dentry, bool impurity)
{
struct inode *inode = d_inode(dentry);
@@ -348,6 +454,14 @@ void ovl_dentry_version_inc(struct dentry *dentry, bool impurity)
OVL_I(inode)->version++;
}
+void ovl_dir_modified(struct dentry *dentry, bool impurity)
+{
+ /* Copy mtime/ctime */
+ ovl_copyattr(d_inode(ovl_dentry_upper(dentry)), d_inode(dentry));
+
+ ovl_dentry_version_inc(dentry, impurity);
+}
+
u64 ovl_dentry_version_get(struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
@@ -368,13 +482,51 @@ struct file *ovl_path_open(struct path *path, int flags)
return dentry_open(path, flags | O_NOATIME, current_cred());
}
-int ovl_copy_up_start(struct dentry *dentry)
+/* Caller should hold ovl_inode->lock */
+static bool ovl_already_copied_up_locked(struct dentry *dentry, int flags)
+{
+ bool disconnected = dentry->d_flags & DCACHE_DISCONNECTED;
+
+ if (ovl_dentry_upper(dentry) &&
+ (ovl_dentry_has_upper_alias(dentry) || disconnected) &&
+ !ovl_dentry_needs_data_copy_up_locked(dentry, flags))
+ return true;
+
+ return false;
+}
+
+bool ovl_already_copied_up(struct dentry *dentry, int flags)
+{
+ bool disconnected = dentry->d_flags & DCACHE_DISCONNECTED;
+
+ /*
+ * 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) || disconnected) &&
+ !ovl_dentry_needs_data_copy_up(dentry, flags))
+ return true;
+
+ return false;
+}
+
+int ovl_copy_up_start(struct dentry *dentry, int flags)
{
struct ovl_inode *oi = OVL_I(d_inode(dentry));
int err;
err = mutex_lock_interruptible(&oi->lock);
- if (!err && ovl_dentry_has_upper_alias(dentry)) {
+ if (!err && ovl_already_copied_up_locked(dentry, flags)) {
err = 1; /* Already copied up */
mutex_unlock(&oi->lock);
}
@@ -675,3 +827,91 @@ err:
pr_err("overlayfs: failed to lock workdir+upperdir\n");
return -EIO;
}
+
+/* err < 0, 0 if no metacopy xattr, 1 if metacopy xattr found */
+int ovl_check_metacopy_xattr(struct dentry *dentry)
+{
+ int res;
+
+ /* Only regular files can have metacopy xattr */
+ if (!S_ISREG(d_inode(dentry)->i_mode))
+ return 0;
+
+ res = vfs_getxattr(dentry, OVL_XATTR_METACOPY, NULL, 0);
+ if (res < 0) {
+ if (res == -ENODATA || res == -EOPNOTSUPP)
+ return 0;
+ goto out;
+ }
+
+ return 1;
+out:
+ pr_warn_ratelimited("overlayfs: failed to get metacopy (%i)\n", res);
+ return res;
+}
+
+bool ovl_is_metacopy_dentry(struct dentry *dentry)
+{
+ struct ovl_entry *oe = dentry->d_fsdata;
+
+ if (!d_is_reg(dentry))
+ return false;
+
+ if (ovl_dentry_upper(dentry)) {
+ if (!ovl_has_upperdata(d_inode(dentry)))
+ return true;
+ return false;
+ }
+
+ return (oe->numlower > 1);
+}
+
+char *ovl_get_redirect_xattr(struct dentry *dentry, int padding)
+{
+ int res;
+ char *s, *next, *buf = NULL;
+
+ res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, NULL, 0);
+ if (res < 0) {
+ if (res == -ENODATA || res == -EOPNOTSUPP)
+ return NULL;
+ goto fail;
+ }
+
+ buf = kzalloc(res + padding + 1, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ if (res == 0)
+ goto invalid;
+
+ res = vfs_getxattr(dentry, OVL_XATTR_REDIRECT, buf, res);
+ if (res < 0)
+ goto fail;
+ if (res == 0)
+ goto invalid;
+
+ if (buf[0] == '/') {
+ for (s = buf; *s++ == '/'; s = next) {
+ next = strchrnul(s, '/');
+ if (s == next)
+ goto invalid;
+ }
+ } else {
+ if (strchr(buf, '/') != NULL)
+ goto invalid;
+ }
+
+ return buf;
+
+err_free:
+ kfree(buf);
+ return ERR_PTR(res);
+fail:
+ pr_warn_ratelimited("overlayfs: failed to get redirect (%i)\n", res);
+ goto err_free;
+invalid:
+ pr_warn_ratelimited("overlayfs: invalid redirect (%s)\n", buf);
+ res = -EINVAL;
+ goto err_free;
+}