summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--fs/overlayfs/Kconfig20
-rw-r--r--fs/overlayfs/copy_up.c9
-rw-r--r--fs/overlayfs/overlayfs.h2
-rw-r--r--fs/overlayfs/ovl_entry.h3
-rw-r--r--fs/overlayfs/super.c66
-rw-r--r--fs/overlayfs/util.c15
6 files changed, 108 insertions, 7 deletions
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 <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]);