Commit f168f109 authored by Amir Goldstein's avatar Amir Goldstein Committed by Miklos Szeredi

ovl: add support for "nfs_export" configuration

Introduce the "nfs_export" config, module and mount options.

The NFS export feature depends on the "index" feature and enables two
implicit overlayfs features: "index_all" and "verify_lower".
The "index_all" feature creates an index on copy up of every file and
directory. The "verify_lower" feature uses the full index to detect
overlay filesystems inconsistencies on lookup, like redirect from
multiple upper dirs to the same lower dir.

NFS export can be enabled for non-upper mount with no index. However,
because lower layer redirects cannot be verified with the index, enabling
NFS export support on an overlay with no upper layer requires turning off
redirect follow (e.g. "redirect_dir=nofollow").

The full index may incur some overhead on mount time, especially when
verifying that lower directory file handles are not stale.

NFS export support, full index and consistency verification will be
implemented by following patches.
Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarMiklos Szeredi <mszeredi@redhat.com>
parent 60b86642
...@@ -190,6 +190,24 @@ Mount options: ...@@ -190,6 +190,24 @@ Mount options:
Redirects are not created and not followed (equivalent to "redirect_dir=off" Redirects are not created and not followed (equivalent to "redirect_dir=off"
if "redirect_always_follow" feature is not enabled). if "redirect_always_follow" feature is not enabled).
When the NFS export feature is enabled, every copied up directory is
indexed by the file handle of the lower inode and a file handle of the
upper directory is stored in a "trusted.overlay.upper" extended attribute
on the index entry. On lookup of a merged directory, if the upper
directory does not match the file handle stores in the index, that is an
indication that multiple upper directories may be redirected to the same
lower directory. In that case, lookup returns an error and warns about
a possible inconsistency.
Because lower layer redirects cannot be verified with the index, enabling
NFS export support on an overlay filesystem with no upper layer requires
turning off redirect follow (e.g. "redirect_dir=nofollow").
When the NFS export feature is enabled, all directory index entries are
verified on mount time to check that upper file handles are not stale.
This verification may cause significant overhead in some cases.
Non-directories Non-directories
--------------- ---------------
...@@ -299,6 +317,23 @@ filesystem are not allowed. If the underlying filesystem is changed, ...@@ -299,6 +317,23 @@ filesystem are not allowed. If the underlying filesystem is changed,
the behavior of the overlay is undefined, though it will not result in the behavior of the overlay is undefined, though it will not result in
a crash or deadlock. a crash or deadlock.
When the overlay NFS export feature is enabled, overlay filesystems
behavior on offline changes of the underlying lower layer is different
than the behavior when NFS export is disabled.
On every copy_up, an NFS file handle of the lower inode, along with the
UUID of the lower filesystem, are encoded and stored in an extended
attribute "trusted.overlay.origin" on the upper inode.
When the NFS export feature is enabled, a lookup of a merged directory,
that found a lower directory at the lookup path or at the path pointed
to by the "trusted.overlay.redirect" extended attribute, will verify
that the found lower directory file handle and lower filesystem UUID
match the origin file handle that was stored at copy_up time. If a
found lower directory does not match the stored origin, that directory
will not be merged with the upper directory.
Testsuite Testsuite
--------- ---------
......
...@@ -50,3 +50,25 @@ config OVERLAY_FS_INDEX ...@@ -50,3 +50,25 @@ config OVERLAY_FS_INDEX
Note, that the inodes index feature is not backward compatible. Note, that the inodes index feature is not backward compatible.
That is, mounting an overlay which has an inodes index on a kernel That is, mounting an overlay which has an inodes index on a kernel
that doesn't support this feature will have unexpected results. that doesn't support this feature will have unexpected results.
config OVERLAY_FS_NFS_EXPORT
bool "Overlayfs: turn on NFS export feature by default"
depends on OVERLAY_FS
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.
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.
The NFS export feature creates an index on copy up of every file and
directory. This full index is used to detect overlay filesystems
inconsistencies on lookup, like redirect from multiple upper dirs to
the same lower dir. The full index may incur some overhead on mount
time, especially when verifying that directory file handles are not
stale.
Note, that the NFS export feature is not backward compatible.
That is, mounting an overlay which has a full index on a kernel
that doesn't support this feature will have unexpected results.
...@@ -194,6 +194,8 @@ const struct cred *ovl_override_creds(struct super_block *sb); ...@@ -194,6 +194,8 @@ const struct cred *ovl_override_creds(struct super_block *sb);
struct super_block *ovl_same_sb(struct super_block *sb); struct super_block *ovl_same_sb(struct super_block *sb);
bool ovl_can_decode_fh(struct super_block *sb); bool ovl_can_decode_fh(struct super_block *sb);
struct dentry *ovl_indexdir(struct super_block *sb); struct dentry *ovl_indexdir(struct super_block *sb);
bool ovl_index_all(struct super_block *sb);
bool ovl_verify_lower(struct super_block *sb);
struct ovl_entry *ovl_alloc_entry(unsigned int numlower); struct ovl_entry *ovl_alloc_entry(unsigned int numlower);
bool ovl_dentry_remote(struct dentry *dentry); bool ovl_dentry_remote(struct dentry *dentry);
bool ovl_dentry_weird(struct dentry *dentry); bool ovl_dentry_weird(struct dentry *dentry);
......
...@@ -17,6 +17,7 @@ struct ovl_config { ...@@ -17,6 +17,7 @@ struct ovl_config {
bool redirect_follow; bool redirect_follow;
const char *redirect_mode; const char *redirect_mode;
bool index; bool index;
bool nfs_export;
}; };
struct ovl_layer { struct ovl_layer {
......
...@@ -45,6 +45,11 @@ module_param_named(index, ovl_index_def, bool, 0644); ...@@ -45,6 +45,11 @@ module_param_named(index, ovl_index_def, bool, 0644);
MODULE_PARM_DESC(ovl_index_def, MODULE_PARM_DESC(ovl_index_def,
"Default to on or off for the inodes index feature"); "Default to on or off for the inodes index feature");
static bool ovl_nfs_export_def = IS_ENABLED(CONFIG_OVERLAY_FS_NFS_EXPORT);
module_param_named(nfs_export, ovl_nfs_export_def, bool, 0644);
MODULE_PARM_DESC(ovl_nfs_export_def,
"Default to on or off for the NFS export feature");
static void ovl_entry_stack_free(struct ovl_entry *oe) static void ovl_entry_stack_free(struct ovl_entry *oe)
{ {
unsigned int i; unsigned int i;
...@@ -342,6 +347,9 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) ...@@ -342,6 +347,9 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
seq_printf(m, ",redirect_dir=%s", ofs->config.redirect_mode); seq_printf(m, ",redirect_dir=%s", ofs->config.redirect_mode);
if (ofs->config.index != ovl_index_def) if (ofs->config.index != ovl_index_def)
seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off"); seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off");
if (ofs->config.nfs_export != ovl_nfs_export_def)
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
"on" : "off");
return 0; return 0;
} }
...@@ -374,6 +382,8 @@ enum { ...@@ -374,6 +382,8 @@ enum {
OPT_REDIRECT_DIR, OPT_REDIRECT_DIR,
OPT_INDEX_ON, OPT_INDEX_ON,
OPT_INDEX_OFF, OPT_INDEX_OFF,
OPT_NFS_EXPORT_ON,
OPT_NFS_EXPORT_OFF,
OPT_ERR, OPT_ERR,
}; };
...@@ -385,6 +395,8 @@ static const match_table_t ovl_tokens = { ...@@ -385,6 +395,8 @@ static const match_table_t ovl_tokens = {
{OPT_REDIRECT_DIR, "redirect_dir=%s"}, {OPT_REDIRECT_DIR, "redirect_dir=%s"},
{OPT_INDEX_ON, "index=on"}, {OPT_INDEX_ON, "index=on"},
{OPT_INDEX_OFF, "index=off"}, {OPT_INDEX_OFF, "index=off"},
{OPT_NFS_EXPORT_ON, "nfs_export=on"},
{OPT_NFS_EXPORT_OFF, "nfs_export=off"},
{OPT_ERR, NULL} {OPT_ERR, NULL}
}; };
...@@ -491,6 +503,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config) ...@@ -491,6 +503,14 @@ static int ovl_parse_opt(char *opt, struct ovl_config *config)
config->index = false; config->index = false;
break; break;
case OPT_NFS_EXPORT_ON:
config->nfs_export = true;
break;
case OPT_NFS_EXPORT_OFF:
config->nfs_export = false;
break;
default: default:
pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p); pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
return -EINVAL; return -EINVAL;
...@@ -696,13 +716,16 @@ static int ovl_lower_dir(const char *name, struct path *path, ...@@ -696,13 +716,16 @@ static int ovl_lower_dir(const char *name, struct path *path,
*remote = true; *remote = true;
/* /*
* The inodes index feature needs to encode and decode file * The inodes index feature and NFS export need to encode and decode
* handles, so it requires that all layers support them. * file handles, so they require that all layers support them.
*/ */
if (ofs->config.index && ofs->config.upperdir && if ((ofs->config.nfs_export ||
(ofs->config.index && ofs->config.upperdir)) &&
!ovl_can_decode_fh(path->dentry->d_sb)) { !ovl_can_decode_fh(path->dentry->d_sb)) {
ofs->config.index = false; ofs->config.index = false;
pr_warn("overlayfs: fs on '%s' does not support file handles, falling back to index=off.\n", name); ofs->config.nfs_export = false;
pr_warn("overlayfs: fs on '%s' does not support file handles, falling back to index=off,nfs_export=off.\n",
name);
} }
return 0; return 0;
...@@ -983,6 +1006,12 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath) ...@@ -983,6 +1006,12 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
pr_warn("overlayfs: upper fs does not support file handles, falling back to index=off.\n"); pr_warn("overlayfs: upper fs does not support file handles, falling back to index=off.\n");
} }
/* NFS export of r/w mount depends on index */
if (ofs->config.nfs_export && !ofs->config.index) {
pr_warn("overlayfs: NFS export requires \"index=on\", falling back to nfs_export=off.\n");
ofs->config.nfs_export = false;
}
out: out:
mnt_drop_write(mnt); mnt_drop_write(mnt);
return err; return err;
...@@ -1141,6 +1170,10 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, ...@@ -1141,6 +1170,10 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
} else if (!ofs->config.upperdir && stacklen == 1) { } else if (!ofs->config.upperdir && stacklen == 1) {
pr_err("overlayfs: at least 2 lowerdir are needed while upperdir nonexistent\n"); pr_err("overlayfs: at least 2 lowerdir are needed while upperdir nonexistent\n");
goto out_err; goto out_err;
} else if (!ofs->config.upperdir && ofs->config.nfs_export &&
ofs->config.redirect_follow) {
pr_warn("overlayfs: NFS export requires \"redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n");
ofs->config.nfs_export = false;
} }
err = -ENOMEM; err = -ENOMEM;
...@@ -1217,6 +1250,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ...@@ -1217,6 +1250,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
goto out_err; goto out_err;
ofs->config.index = ovl_index_def; ofs->config.index = ovl_index_def;
ofs->config.nfs_export = ovl_nfs_export_def;
err = ovl_parse_opt((char *) data, &ofs->config); err = ovl_parse_opt((char *) data, &ofs->config);
if (err) if (err)
goto out_err; goto out_err;
...@@ -1277,8 +1311,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ...@@ -1277,8 +1311,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
} }
/* Show index=off in /proc/mounts for forced r/o mount */ /* Show index=off in /proc/mounts for forced r/o mount */
if (!ofs->indexdir) if (!ofs->indexdir) {
ofs->config.index = false; ofs->config.index = false;
if (ofs->upper_mnt && ofs->config.nfs_export) {
pr_warn("overlayfs: NFS export requires an index dir, falling back to nfs_export=off.\n");
ofs->config.nfs_export = false;
}
}
/* Never override disk quota limits or use reserved space */ /* Never override disk quota limits or use reserved space */
cap_lower(cred->cap_effective, CAP_SYS_RESOURCE); cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);
......
...@@ -63,6 +63,22 @@ struct dentry *ovl_indexdir(struct super_block *sb) ...@@ -63,6 +63,22 @@ struct dentry *ovl_indexdir(struct super_block *sb)
return ofs->indexdir; return ofs->indexdir;
} }
/* Index all files on copy up. For now only enabled for NFS export */
bool ovl_index_all(struct super_block *sb)
{
struct ovl_fs *ofs = sb->s_fs_info;
return ofs->config.nfs_export && ofs->config.index;
}
/* Verify lower origin on lookup. For now only enabled for NFS export */
bool ovl_verify_lower(struct super_block *sb)
{
struct ovl_fs *ofs = sb->s_fs_info;
return ofs->config.nfs_export && ofs->config.index;
}
struct ovl_entry *ovl_alloc_entry(unsigned int numlower) struct ovl_entry *ovl_alloc_entry(unsigned int numlower)
{ {
size_t size = offsetof(struct ovl_entry, lowerstack[numlower]); size_t size = offsetof(struct ovl_entry, lowerstack[numlower]);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment