summaryrefslogtreecommitdiff
path: root/fs/overlayfs/params.c
diff options
context:
space:
mode:
authorAmir Goldstein <amir73il@gmail.com>2023-10-30 20:34:42 +0200
committerAmir Goldstein <amir73il@gmail.com>2023-10-31 00:13:02 +0200
commit24e16e385f2272b1a9df51337a5c32d28a29c7ad (patch)
treebfe912131d20609891e2cd3cecfb985fe0b6252a /fs/overlayfs/params.c
parent819829f0319a759e8a6ccb7e4f1113f3f9f07aa3 (diff)
downloadlwn-24e16e385f2272b1a9df51337a5c32d28a29c7ad.tar.gz
lwn-24e16e385f2272b1a9df51337a5c32d28a29c7ad.zip
ovl: add support for appending lowerdirs one by one
Add new mount options lowerdir+ and datadir+ that can be used to add layers to lower layers stack one by one. Unlike the legacy lowerdir mount option, special characters (i.e. colons and cammas) are not unescaped with these new mount options. The new mount options can be repeated to compose a large stack of lower layers, but they may not be mixed with the lagacy lowerdir mount option, because for displaying lower layers in mountinfo, we do not want to mix escaped with unescaped lower layers path syntax. Similar to data-only layer rules with the lowerdir mount option, the datadir+ option must follow at least one lowerdir+ option and the lowerdir+ option must not follow the datadir+ option. If the legacy lowerdir mount option follows lowerdir+ and datadir+ mount options, it overrides them. Sepcifically, calling: fsconfig(FSCONFIG_SET_STRING, "lowerdir", "", 0); can be used to reset previously setup lower layers. Suggested-by: Miklos Szeredi <miklos@szeredi.hu> Link: https://lore.kernel.org/r/CAJfpegt7VC94KkRtb1dfHG8+4OzwPBLYqhtc8=QFUxpFJE+=RQ@mail.gmail.com/ Signed-off-by: Amir Goldstein <amir73il@gmail.com>
Diffstat (limited to 'fs/overlayfs/params.c')
-rw-r--r--fs/overlayfs/params.c78
1 files changed, 76 insertions, 2 deletions
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index 88725982b6dd..ddab9ea267d1 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -45,6 +45,8 @@ MODULE_PARM_DESC(metacopy,
enum ovl_opt {
Opt_lowerdir,
+ Opt_lowerdir_add,
+ Opt_datadir_add,
Opt_upperdir,
Opt_workdir,
Opt_default_permissions,
@@ -140,8 +142,11 @@ static int ovl_verity_mode_def(void)
#define fsparam_string_empty(NAME, OPT) \
__fsparam(fs_param_is_string, NAME, OPT, fs_param_can_be_empty, NULL)
+
const struct fs_parameter_spec ovl_parameter_spec[] = {
fsparam_string_empty("lowerdir", Opt_lowerdir),
+ fsparam_string("lowerdir+", Opt_lowerdir_add),
+ fsparam_string("datadir+", Opt_datadir_add),
fsparam_string("upperdir", Opt_upperdir),
fsparam_string("workdir", Opt_workdir),
fsparam_flag("default_permissions", Opt_default_permissions),
@@ -273,12 +278,15 @@ static int ovl_mount_dir(const char *name, struct path *path)
static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path,
enum ovl_opt layer, const char *name, bool upper)
{
+ struct ovl_fs_context *ctx = fc->fs_private;
+
if (ovl_dentry_weird(path->dentry))
return invalfc(fc, "filesystem on %s not supported", name);
if (!d_is_dir(path->dentry))
return invalfc(fc, "%s is not a directory", name);
+
/*
* Check whether upper path is read-only here to report failures
* early. Don't forget to recheck when the superblock is created
@@ -289,16 +297,45 @@ static int ovl_mount_dir_check(struct fs_context *fc, const struct path *path,
return invalfc(fc, "filesystem on %s not supported as upperdir", name);
if (__mnt_is_readonly(path->mnt))
return invalfc(fc, "filesystem on %s is read-only", name);
+ } else {
+ if (ctx->lowerdir_all && layer != Opt_lowerdir)
+ return invalfc(fc, "lowerdir+ and datadir+ cannot follow lowerdir");
+ if (ctx->nr_data && layer == Opt_lowerdir_add)
+ return invalfc(fc, "regular lower layers cannot follow data layers");
+ if (ctx->nr == OVL_MAX_STACK)
+ return invalfc(fc, "too many lower directories, limit is %d",
+ OVL_MAX_STACK);
}
return 0;
}
+static int ovl_ctx_realloc_lower(struct fs_context *fc)
+{
+ struct ovl_fs_context *ctx = fc->fs_private;
+ struct ovl_fs_context_layer *l;
+ size_t nr;
+
+ if (ctx->nr < ctx->capacity)
+ return 0;
+
+ nr = min_t(size_t, max(4096 / sizeof(*l), ctx->capacity * 2),
+ OVL_MAX_STACK);
+ l = krealloc_array(ctx->lower, nr, sizeof(*l), GFP_KERNEL_ACCOUNT);
+ if (!l)
+ return -ENOMEM;
+
+ ctx->lower = l;
+ ctx->capacity = nr;
+ return 0;
+}
+
static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer,
struct path *path, char **pname)
{
struct ovl_fs *ofs = fc->s_fs_info;
struct ovl_config *config = &ofs->config;
struct ovl_fs_context *ctx = fc->fs_private;
+ struct ovl_fs_context_layer *l;
switch (layer) {
case Opt_workdir:
@@ -309,6 +346,16 @@ static void ovl_add_layer(struct fs_context *fc, enum ovl_opt layer,
swap(config->upperdir, *pname);
swap(ctx->upper, *path);
break;
+ case Opt_datadir_add:
+ ctx->nr_data++;
+ fallthrough;
+ case Opt_lowerdir_add:
+ WARN_ON(ctx->nr >= ctx->capacity);
+ l = &ctx->lower[ctx->nr++];
+ memset(l, 0, sizeof(*l));
+ swap(l->name, *pname);
+ swap(l->path, *path);
+ break;
default:
WARN_ON(1);
}
@@ -325,7 +372,10 @@ static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
if (!name)
return -ENOMEM;
- err = ovl_mount_dir(name, &path);
+ if (upper)
+ err = ovl_mount_dir(name, &path);
+ else
+ err = ovl_mount_dir_noesc(name, &path);
if (err)
goto out_free;
@@ -333,6 +383,12 @@ static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
if (err)
goto out_put;
+ if (!upper) {
+ err = ovl_ctx_realloc_lower(fc);
+ if (err)
+ goto out_put;
+ }
+
/* Store the user provided path string in ctx to show in mountinfo */
ovl_add_layer(fc, layer, &path, &name);
@@ -519,6 +575,8 @@ static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param)
case Opt_lowerdir:
err = ovl_parse_param_lowerdir(param->string, fc);
break;
+ case Opt_lowerdir_add:
+ case Opt_datadir_add:
case Opt_upperdir:
case Opt_workdir:
err = ovl_parse_layer(fc, param, opt);
@@ -894,13 +952,29 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
{
struct super_block *sb = dentry->d_sb;
struct ovl_fs *ofs = OVL_FS(sb);
+ size_t nr, nr_merged_lower, nr_lower = 0;
char **lowerdirs = ofs->config.lowerdirs;
/*
* lowerdirs[0] holds the colon separated list that user provided
* with lowerdir mount option.
+ * lowerdirs[1..numlayer] hold the lowerdir paths that were added
+ * using the lowerdir+ and datadir+ mount options.
+ * For now, we do not allow mixing the legacy lowerdir mount option
+ * with the new lowerdir+ and datadir+ mount options.
*/
- seq_show_option(m, "lowerdir", lowerdirs[0]);
+ if (lowerdirs[0]) {
+ seq_show_option(m, "lowerdir", lowerdirs[0]);
+ } else {
+ nr_lower = ofs->numlayer;
+ nr_merged_lower = nr_lower - ofs->numdatalayer;
+ }
+ for (nr = 1; nr < nr_lower; nr++) {
+ if (nr < nr_merged_lower)
+ seq_show_option(m, "lowerdir+", lowerdirs[nr]);
+ else
+ seq_show_option(m, "datadir+", lowerdirs[nr]);
+ }
if (ofs->config.upperdir) {
seq_show_option(m, "upperdir", ofs->config.upperdir);
seq_show_option(m, "workdir", ofs->config.workdir);