ovl: introduce the inodes index dir feature
authorAmir Goldstein <amir73il@gmail.com>
Wed, 21 Jun 2017 12:28:36 +0000 (15:28 +0300)
committerMiklos Szeredi <mszeredi@redhat.com>
Tue, 4 Jul 2017 20:03:17 +0000 (22:03 +0200)
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 <amir73il@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/overlayfs/Kconfig
fs/overlayfs/copy_up.c
fs/overlayfs/overlayfs.h
fs/overlayfs/ovl_entry.h
fs/overlayfs/super.c
fs/overlayfs/util.c

index c0c9683934b7a7883ab59eb8bbcd412e07385d0b..cbfc196e5dc53b2cb2376524c5955af20bd07ea6 100644 (file)
@@ -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.
index 87289b9a152c87db80663089b9769ae67f9b15ec..f9f51cce3c18176ea118ec834899e84dce0c7cfd 100644 (file)
@@ -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);
        }
index 5e958427463d1d716a5176e039f46fea73a687c0..4e7a74e99d3cb70065fae7b5416178da674df00c 100644 (file)
@@ -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);
index 5b5a3211642425aa25bc753c075c7c68755614e3..9642ec64467b56501d107f953e8835d2ae91f533 100644 (file)
@@ -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;
index fea7bd496f2e53e3fd3dd23a2739a90d72a7edfc..fa83b3245124cee633575cc333585c23506adf45 100644 (file)
@@ -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]);
index adccd74162d6387b74d97c85c1cbd01dcc5b2802..90b50b8e75abcfaea9d4e1234708c10be36f3640 100644 (file)
@@ -12,6 +12,8 @@
 #include <linux/slab.h>
 #include <linux/cred.h>
 #include <linux/xattr.h>
+#include <linux/exportfs.h>
+#include <linux/uuid.h>
 #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]);