Merge tag 'ovl-fixes-6.6-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs
Pull overlayfs fixes from Amir Goldstein: - Various fixes for regressions due to conversion to new mount api in v6.5 - Disable a new mount option syntax (append lowerdir) that was added in v6.5 because we plan to add a different lowerdir append syntax in v6.7 * tag 'ovl-fixes-6.6-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs: ovl: temporarily disable appending lowedirs ovl: fix regression in showing lowerdir mount option ovl: fix regression in parsing of mount options with escaped comma fs: factor out vfs_parse_monolithic_sep() helper
This commit is contained in:
@@ -339,6 +339,18 @@ The specified lower directories will be stacked beginning from the
|
|||||||
rightmost one and going left. In the above example lower1 will be the
|
rightmost one and going left. In the above example lower1 will be the
|
||||||
top, lower2 the middle and lower3 the bottom layer.
|
top, lower2 the middle and lower3 the bottom layer.
|
||||||
|
|
||||||
|
Note: directory names containing colons can be provided as lower layer by
|
||||||
|
escaping the colons with a single backslash. For example:
|
||||||
|
|
||||||
|
mount -t overlay overlay -olowerdir=/a\:lower\:\:dir /merged
|
||||||
|
|
||||||
|
Since kernel version v6.5, directory names containing colons can also
|
||||||
|
be provided as lower layer using the fsconfig syscall from new mount api:
|
||||||
|
|
||||||
|
fsconfig(fs_fd, FSCONFIG_SET_STRING, "lowerdir", "/a:lower::dir", 0);
|
||||||
|
|
||||||
|
In the latter case, colons in lower layer directory names will be escaped
|
||||||
|
as an octal characters (\072) when displayed in /proc/self/mountinfo.
|
||||||
|
|
||||||
Metadata only copy up
|
Metadata only copy up
|
||||||
---------------------
|
---------------------
|
||||||
|
@@ -192,17 +192,19 @@ int vfs_parse_fs_string(struct fs_context *fc, const char *key,
|
|||||||
EXPORT_SYMBOL(vfs_parse_fs_string);
|
EXPORT_SYMBOL(vfs_parse_fs_string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* generic_parse_monolithic - Parse key[=val][,key[=val]]* mount data
|
* vfs_parse_monolithic_sep - Parse key[=val][,key[=val]]* mount data
|
||||||
* @fc: The superblock configuration to fill in.
|
* @fc: The superblock configuration to fill in.
|
||||||
* @data: The data to parse
|
* @data: The data to parse
|
||||||
|
* @sep: callback for separating next option
|
||||||
*
|
*
|
||||||
* Parse a blob of data that's in key[=val][,key[=val]]* form. This can be
|
* Parse a blob of data that's in key[=val][,key[=val]]* form with a custom
|
||||||
* called from the ->monolithic_mount_data() fs_context operation.
|
* option separator callback.
|
||||||
*
|
*
|
||||||
* Returns 0 on success or the error returned by the ->parse_option() fs_context
|
* Returns 0 on success or the error returned by the ->parse_option() fs_context
|
||||||
* operation on failure.
|
* operation on failure.
|
||||||
*/
|
*/
|
||||||
int generic_parse_monolithic(struct fs_context *fc, void *data)
|
int vfs_parse_monolithic_sep(struct fs_context *fc, void *data,
|
||||||
|
char *(*sep)(char **))
|
||||||
{
|
{
|
||||||
char *options = data, *key;
|
char *options = data, *key;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
@@ -214,7 +216,7 @@ int generic_parse_monolithic(struct fs_context *fc, void *data)
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
while ((key = strsep(&options, ",")) != NULL) {
|
while ((key = sep(&options)) != NULL) {
|
||||||
if (*key) {
|
if (*key) {
|
||||||
size_t v_len = 0;
|
size_t v_len = 0;
|
||||||
char *value = strchr(key, '=');
|
char *value = strchr(key, '=');
|
||||||
@@ -233,6 +235,28 @@ int generic_parse_monolithic(struct fs_context *fc, void *data)
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
EXPORT_SYMBOL(vfs_parse_monolithic_sep);
|
||||||
|
|
||||||
|
static char *vfs_parse_comma_sep(char **s)
|
||||||
|
{
|
||||||
|
return strsep(s, ",");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generic_parse_monolithic - Parse key[=val][,key[=val]]* mount data
|
||||||
|
* @fc: The superblock configuration to fill in.
|
||||||
|
* @data: The data to parse
|
||||||
|
*
|
||||||
|
* Parse a blob of data that's in key[=val][,key[=val]]* form. This can be
|
||||||
|
* called from the ->monolithic_mount_data() fs_context operation.
|
||||||
|
*
|
||||||
|
* Returns 0 on success or the error returned by the ->parse_option() fs_context
|
||||||
|
* operation on failure.
|
||||||
|
*/
|
||||||
|
int generic_parse_monolithic(struct fs_context *fc, void *data)
|
||||||
|
{
|
||||||
|
return vfs_parse_monolithic_sep(fc, data, vfs_parse_comma_sep);
|
||||||
|
}
|
||||||
EXPORT_SYMBOL(generic_parse_monolithic);
|
EXPORT_SYMBOL(generic_parse_monolithic);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -157,6 +157,34 @@ const struct fs_parameter_spec ovl_parameter_spec[] = {
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static char *ovl_next_opt(char **s)
|
||||||
|
{
|
||||||
|
char *sbegin = *s;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
if (sbegin == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
for (p = sbegin; *p; p++) {
|
||||||
|
if (*p == '\\') {
|
||||||
|
p++;
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
} else if (*p == ',') {
|
||||||
|
*p = '\0';
|
||||||
|
*s = p + 1;
|
||||||
|
return sbegin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*s = NULL;
|
||||||
|
return sbegin;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ovl_parse_monolithic(struct fs_context *fc, void *data)
|
||||||
|
{
|
||||||
|
return vfs_parse_monolithic_sep(fc, data, ovl_next_opt);
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t ovl_parse_param_split_lowerdirs(char *str)
|
static ssize_t ovl_parse_param_split_lowerdirs(char *str)
|
||||||
{
|
{
|
||||||
ssize_t nr_layers = 1, nr_colons = 0;
|
ssize_t nr_layers = 1, nr_colons = 0;
|
||||||
@@ -164,7 +192,8 @@ static ssize_t ovl_parse_param_split_lowerdirs(char *str)
|
|||||||
|
|
||||||
for (s = d = str;; s++, d++) {
|
for (s = d = str;; s++, d++) {
|
||||||
if (*s == '\\') {
|
if (*s == '\\') {
|
||||||
s++;
|
/* keep esc chars in split lowerdir */
|
||||||
|
*d++ = *s++;
|
||||||
} else if (*s == ':') {
|
} else if (*s == ':') {
|
||||||
bool next_colon = (*(s + 1) == ':');
|
bool next_colon = (*(s + 1) == ':');
|
||||||
|
|
||||||
@@ -239,7 +268,7 @@ static void ovl_unescape(char *s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ovl_mount_dir(const char *name, struct path *path)
|
static int ovl_mount_dir(const char *name, struct path *path, bool upper)
|
||||||
{
|
{
|
||||||
int err = -ENOMEM;
|
int err = -ENOMEM;
|
||||||
char *tmp = kstrdup(name, GFP_KERNEL);
|
char *tmp = kstrdup(name, GFP_KERNEL);
|
||||||
@@ -248,7 +277,7 @@ static int ovl_mount_dir(const char *name, struct path *path)
|
|||||||
ovl_unescape(tmp);
|
ovl_unescape(tmp);
|
||||||
err = ovl_mount_dir_noesc(tmp, path);
|
err = ovl_mount_dir_noesc(tmp, path);
|
||||||
|
|
||||||
if (!err && path->dentry->d_flags & DCACHE_OP_REAL) {
|
if (!err && upper && path->dentry->d_flags & DCACHE_OP_REAL) {
|
||||||
pr_err("filesystem on '%s' not supported as upperdir\n",
|
pr_err("filesystem on '%s' not supported as upperdir\n",
|
||||||
tmp);
|
tmp);
|
||||||
path_put_init(path);
|
path_put_init(path);
|
||||||
@@ -269,7 +298,7 @@ static int ovl_parse_param_upperdir(const char *name, struct fs_context *fc,
|
|||||||
struct path path;
|
struct path path;
|
||||||
char *dup;
|
char *dup;
|
||||||
|
|
||||||
err = ovl_mount_dir(name, &path);
|
err = ovl_mount_dir(name, &path, true);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
|
||||||
@@ -321,12 +350,6 @@ static void ovl_parse_param_drop_lowerdir(struct ovl_fs_context *ctx)
|
|||||||
* Set "/lower1", "/lower2", and "/lower3" as lower layers and
|
* Set "/lower1", "/lower2", and "/lower3" as lower layers and
|
||||||
* "/data1" and "/data2" as data lower layers. Any existing lower
|
* "/data1" and "/data2" as data lower layers. Any existing lower
|
||||||
* layers are replaced.
|
* layers are replaced.
|
||||||
* (2) lowerdir=:/lower4
|
|
||||||
* Append "/lower4" to current stack of lower layers. This requires
|
|
||||||
* that there already is at least one lower layer configured.
|
|
||||||
* (3) lowerdir=::/lower5
|
|
||||||
* Append data "/lower5" as data lower layer. This requires that
|
|
||||||
* there's at least one regular lower layer present.
|
|
||||||
*/
|
*/
|
||||||
static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
|
static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
|
||||||
{
|
{
|
||||||
@@ -348,51 +371,11 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strncmp(name, "::", 2) == 0) {
|
if (*name == ':') {
|
||||||
/*
|
pr_err("cannot append lower layer");
|
||||||
* This is a data layer.
|
|
||||||
* There must be at least one regular lower layer
|
|
||||||
* specified.
|
|
||||||
*/
|
|
||||||
if (ctx->nr == 0) {
|
|
||||||
pr_err("data lower layers without regular lower layers not allowed");
|
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skip the leading "::". */
|
|
||||||
name += 2;
|
|
||||||
data_layer = true;
|
|
||||||
/*
|
|
||||||
* A data layer is automatically an append as there
|
|
||||||
* must've been at least one regular lower layer.
|
|
||||||
*/
|
|
||||||
append = true;
|
|
||||||
} else if (*name == ':') {
|
|
||||||
/*
|
|
||||||
* This is a regular lower layer.
|
|
||||||
* If users want to append a layer enforce that they
|
|
||||||
* have already specified a first layer before. It's
|
|
||||||
* better to be strict.
|
|
||||||
*/
|
|
||||||
if (ctx->nr == 0) {
|
|
||||||
pr_err("cannot append layer if no previous layer has been specified");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Once a sequence of data layers has started regular
|
|
||||||
* lower layers are forbidden.
|
|
||||||
*/
|
|
||||||
if (ctx->nr_data > 0) {
|
|
||||||
pr_err("regular lower layers cannot follow data lower layers");
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skip the leading ":". */
|
|
||||||
name++;
|
|
||||||
append = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
dup = kstrdup(name, GFP_KERNEL);
|
dup = kstrdup(name, GFP_KERNEL);
|
||||||
if (!dup)
|
if (!dup)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
@@ -472,7 +455,7 @@ static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
|
|||||||
l = &ctx->lower[nr];
|
l = &ctx->lower[nr];
|
||||||
memset(l, 0, sizeof(*l));
|
memset(l, 0, sizeof(*l));
|
||||||
|
|
||||||
err = ovl_mount_dir_noesc(dup_iter, &l->path);
|
err = ovl_mount_dir(dup_iter, &l->path, false);
|
||||||
if (err)
|
if (err)
|
||||||
goto out_put;
|
goto out_put;
|
||||||
|
|
||||||
@@ -682,6 +665,7 @@ static int ovl_reconfigure(struct fs_context *fc)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const struct fs_context_operations ovl_context_ops = {
|
static const struct fs_context_operations ovl_context_ops = {
|
||||||
|
.parse_monolithic = ovl_parse_monolithic,
|
||||||
.parse_param = ovl_parse_param,
|
.parse_param = ovl_parse_param,
|
||||||
.get_tree = ovl_get_tree,
|
.get_tree = ovl_get_tree,
|
||||||
.reconfigure = ovl_reconfigure,
|
.reconfigure = ovl_reconfigure,
|
||||||
@@ -950,16 +934,23 @@ int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
|||||||
struct super_block *sb = dentry->d_sb;
|
struct super_block *sb = dentry->d_sb;
|
||||||
struct ovl_fs *ofs = OVL_FS(sb);
|
struct ovl_fs *ofs = OVL_FS(sb);
|
||||||
size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
|
size_t nr, nr_merged_lower = ofs->numlayer - ofs->numdatalayer;
|
||||||
char **lowerdatadirs = &ofs->config.lowerdirs[nr_merged_lower];
|
|
||||||
|
|
||||||
/* lowerdirs[] starts from offset 1 */
|
/*
|
||||||
seq_printf(m, ",lowerdir=%s", ofs->config.lowerdirs[1]);
|
* lowerdirs[] starts from offset 1, then
|
||||||
/* dump regular lower layers */
|
* >= 0 regular lower layers prefixed with : and
|
||||||
for (nr = 2; nr < nr_merged_lower; nr++)
|
* >= 0 data-only lower layers prefixed with ::
|
||||||
seq_printf(m, ":%s", ofs->config.lowerdirs[nr]);
|
*
|
||||||
/* dump data lower layers */
|
* we need to escase comma and space like seq_show_option() does and
|
||||||
for (nr = 0; nr < ofs->numdatalayer; nr++)
|
* we also need to escape the colon separator from lowerdir paths.
|
||||||
seq_printf(m, "::%s", lowerdatadirs[nr]);
|
*/
|
||||||
|
seq_puts(m, ",lowerdir=");
|
||||||
|
for (nr = 1; nr < ofs->numlayer; nr++) {
|
||||||
|
if (nr > 1)
|
||||||
|
seq_putc(m, ':');
|
||||||
|
if (nr >= nr_merged_lower)
|
||||||
|
seq_putc(m, ':');
|
||||||
|
seq_escape(m, ofs->config.lowerdirs[nr], ":, \t\n\\");
|
||||||
|
}
|
||||||
if (ofs->config.upperdir) {
|
if (ofs->config.upperdir) {
|
||||||
seq_show_option(m, "upperdir", ofs->config.upperdir);
|
seq_show_option(m, "upperdir", ofs->config.upperdir);
|
||||||
seq_show_option(m, "workdir", ofs->config.workdir);
|
seq_show_option(m, "workdir", ofs->config.workdir);
|
||||||
|
@@ -136,6 +136,8 @@ extern struct fs_context *vfs_dup_fs_context(struct fs_context *fc);
|
|||||||
extern int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param);
|
extern int vfs_parse_fs_param(struct fs_context *fc, struct fs_parameter *param);
|
||||||
extern int vfs_parse_fs_string(struct fs_context *fc, const char *key,
|
extern int vfs_parse_fs_string(struct fs_context *fc, const char *key,
|
||||||
const char *value, size_t v_size);
|
const char *value, size_t v_size);
|
||||||
|
int vfs_parse_monolithic_sep(struct fs_context *fc, void *data,
|
||||||
|
char *(*sep)(char **));
|
||||||
extern int generic_parse_monolithic(struct fs_context *fc, void *data);
|
extern int generic_parse_monolithic(struct fs_context *fc, void *data);
|
||||||
extern int vfs_get_tree(struct fs_context *fc);
|
extern int vfs_get_tree(struct fs_context *fc);
|
||||||
extern void put_fs_context(struct fs_context *fc);
|
extern void put_fs_context(struct fs_context *fc);
|
||||||
|
Reference in New Issue
Block a user