// SPDX-License-Identifier: GPL-2.0-only
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/namei.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
#include <linux/posix_acl_xattr.h>
#include <linux/seq_file.h>
#include <linux/xattr.h>
#include "overlayfs.h"
#include "params.h"
static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR);
module_param_named(redirect_dir, ovl_redirect_dir_def, bool , 0644 );
MODULE_PARM_DESC(redirect_dir,
"Default to on or off for the redirect_dir feature" );
static bool ovl_redirect_always_follow =
IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW);
module_param_named(redirect_always_follow, ovl_redirect_always_follow,
bool , 0644 );
MODULE_PARM_DESC(redirect_always_follow,
"Follow redirects even if redirect_dir feature is turned off" );
static bool ovl_xino_auto_def = IS_ENABLED(CONFIG_OVERLAY_FS_XINO_AUTO);
module_param_named(xino_auto, ovl_xino_auto_def, bool , 0644 );
MODULE_PARM_DESC(xino_auto,
"Auto enable xino feature" );
static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX);
module_param_named(index, ovl_index_def, bool , 0644 );
MODULE_PARM_DESC(index,
"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(nfs_export,
"Default to on or off for the NFS export feature" );
static bool ovl_metacopy_def = IS_ENABLED(CONFIG_OVERLAY_FS_METACOPY);
module_param_named(metacopy, ovl_metacopy_def, bool , 0644 );
MODULE_PARM_DESC(metacopy,
"Default to on or off for the metadata only copy up feature" );
enum ovl_opt {
Opt_lowerdir,
Opt_lowerdir_add,
Opt_datadir_add,
Opt_upperdir,
Opt_workdir,
Opt_default_permissions,
Opt_redirect_dir,
Opt_index,
Opt_uuid,
Opt_nfs_export,
Opt_userxattr,
Opt_xino,
Opt_metacopy,
Opt_verity,
Opt_volatile,
Opt_override_creds,
};
static const struct constant_table ovl_parameter_bool[] = {
{ "on" , true },
{ "off" , false },
{}
};
static const struct constant_table ovl_parameter_uuid[] = {
{ "off" , OVL_UUID_OFF },
{ "null" , OVL_UUID_NULL },
{ "auto" , OVL_UUID_AUTO },
{ "on" , OVL_UUID_ON },
{}
};
static const char *ovl_uuid_mode(struct ovl_config *config)
{
return ovl_parameter_uuid[config->uuid].name;
}
static int ovl_uuid_def(void )
{
return OVL_UUID_AUTO;
}
static const struct constant_table ovl_parameter_xino[] = {
{ "off" , OVL_XINO_OFF },
{ "auto" , OVL_XINO_AUTO },
{ "on" , OVL_XINO_ON },
{}
};
const char *ovl_xino_mode(struct ovl_config *config)
{
return ovl_parameter_xino[config->xino].name;
}
static int ovl_xino_def(void )
{
return ovl_xino_auto_def ? OVL_XINO_AUTO : OVL_XINO_OFF;
}
const struct constant_table ovl_parameter_redirect_dir[] = {
{ "off" , OVL_REDIRECT_OFF },
{ "follow" , OVL_REDIRECT_FOLLOW },
{ "nofollow" , OVL_REDIRECT_NOFOLLOW },
{ "on" , OVL_REDIRECT_ON },
{}
};
static const char *ovl_redirect_mode(struct ovl_config *config)
{
return ovl_parameter_redirect_dir[config->redirect_mode].name;
}
static int ovl_redirect_mode_def(void )
{
return ovl_redirect_dir_def ? OVL_REDIRECT_ON :
ovl_redirect_always_follow ? OVL_REDIRECT_FOLLOW :
OVL_REDIRECT_NOFOLLOW;
}
static const struct constant_table ovl_parameter_verity[] = {
{ "off" , OVL_VERITY_OFF },
{ "on" , OVL_VERITY_ON },
{ "require" , OVL_VERITY_REQUIRE },
{}
};
static const char *ovl_verity_mode(struct ovl_config *config)
{
return ovl_parameter_verity[config->verity_mode].name;
}
static int ovl_verity_mode_def(void )
{
return OVL_VERITY_OFF;
}
const struct fs_parameter_spec ovl_parameter_spec[] = {
fsparam_string_empty("lowerdir" , Opt_lowerdir),
fsparam_file_or_string("lowerdir+" , Opt_lowerdir_add),
fsparam_file_or_string("datadir+" , Opt_datadir_add),
fsparam_file_or_string("upperdir" , Opt_upperdir),
fsparam_file_or_string("workdir" , Opt_workdir),
fsparam_flag("default_permissions" , Opt_default_permissions),
fsparam_enum("redirect_dir" , Opt_redirect_dir, ovl_parameter_redirect_dir),
fsparam_enum("index" , Opt_index, ovl_parameter_bool),
fsparam_enum("uuid" , Opt_uuid, ovl_parameter_uuid),
fsparam_enum("nfs_export" , Opt_nfs_export, ovl_parameter_bool),
fsparam_flag("userxattr" , Opt_userxattr),
fsparam_enum("xino" , Opt_xino, ovl_parameter_xino),
fsparam_enum("metacopy" , Opt_metacopy, ovl_parameter_bool),
fsparam_enum("verity" , Opt_verity, ovl_parameter_verity),
fsparam_flag("volatile" , Opt_volatile),
fsparam_flag_no("override_creds" , Opt_override_creds),
{}
};
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)
{
ssize_t nr_layers = 1 , nr_colons = 0 ;
char *s, *d;
for (s = d = str;; s++, d++) {
if (*s == '\\' ) {
/* keep esc chars in split lowerdir */
*d++ = *s++;
} else if (*s == ':' ) {
bool next_colon = (*(s + 1 ) == ':' );
nr_colons++;
if (nr_colons == 2 && next_colon) {
pr_err("only single ':' or double '::' sequences of unescaped colons in lowerdir mount option allowed.\n" );
return -EINVAL;
}
/* count layers, not colons */
if (!next_colon)
nr_layers++;
*d = '\0' ;
continue ;
}
*d = *s;
if (!*s) {
/* trailing colons */
if (nr_colons) {
pr_err("unescaped trailing colons in lowerdir mount option.\n" );
return -EINVAL;
}
break ;
}
nr_colons = 0 ;
}
return nr_layers;
}
static int ovl_mount_dir_noesc(const char *name, struct path *path)
{
int err = -EINVAL;
if (!*name) {
pr_err("empty lowerdir\n" );
goto out;
}
err = kern_path(name, LOOKUP_FOLLOW, path);
if (err) {
pr_err("failed to resolve '%s': %i\n" , name, err);
goto out;
}
return 0 ;
out:
return err;
}
static void ovl_unescape(char *s)
{
char *d = s;
for (;; s++, d++) {
if (*s == '\\' )
s++;
*d = *s;
if (!*s)
break ;
}
}
static int ovl_mount_dir(const char *name, struct path *path)
{
int err = -ENOMEM;
char *tmp = kstrdup(name, GFP_KERNEL);
if (tmp) {
ovl_unescape(tmp);
err = ovl_mount_dir_noesc(tmp, path);
kfree(tmp);
}
return err;
}
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 (!d_is_dir(path->dentry))
return invalfc(fc, "%s is not a directory" , name);
/*
* Allow filesystems that are case - folding capable but deny composing
* ovl stack from case - folded directories .
*/
if (ovl_dentry_casefolded(path->dentry))
return invalfc(fc, "case-insensitive directory on %s not supported" , name);
if (ovl_dentry_weird(path->dentry))
return invalfc(fc, "filesystem on %s not supported" , name);
/*
* Check whether upper path is read - only here to report failures
* early . Don ' t forget to recheck when the superblock is created
* as the mount attributes could change .
*/
if (upper) {
if (path->dentry->d_flags & DCACHE_OP_REAL)
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:
swap(config->workdir, *pname);
swap(ctx->work, *path);
break ;
case Opt_upperdir:
swap(config->upperdir, *pname);
swap(ctx->upper, *path);
break ;
case Opt_datadir_add:
ctx->nr_data++;
fallthrough;
case Opt_lowerdir:
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 );
}
}
static inline bool is_upper_layer(enum ovl_opt layer)
{
return layer == Opt_upperdir || layer == Opt_workdir;
}
/* Handle non-file descriptor-based layer options that require path lookup. */
static inline int ovl_kern_path(const char *layer_name, struct path *layer_path,
enum ovl_opt layer)
{
int err;
switch (layer) {
case Opt_upperdir:
fallthrough;
case Opt_workdir:
fallthrough;
case Opt_lowerdir:
err = ovl_mount_dir(layer_name, layer_path);
break ;
case Opt_lowerdir_add:
fallthrough;
case Opt_datadir_add:
err = ovl_mount_dir_noesc(layer_name, layer_path);
break ;
default :
WARN_ON_ONCE(true );
err = -EINVAL;
}
return err;
}
static int ovl_do_parse_layer(struct fs_context *fc, const char *layer_name,
struct path *layer_path, enum ovl_opt layer)
{
char *name __free(kfree) = kstrdup(layer_name, GFP_KERNEL);
bool upper;
int err = 0 ;
if (!name)
return -ENOMEM;
upper = is_upper_layer(layer);
err = ovl_mount_dir_check(fc, layer_path, layer, name, upper);
if (err)
return err;
if (!upper) {
err = ovl_ctx_realloc_lower(fc);
if (err)
return err;
}
/* Store the user provided path string in ctx to show in mountinfo */
ovl_add_layer(fc, layer, layer_path, &name);
return err;
}
static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
enum ovl_opt layer)
{
struct path layer_path __free(path_put) = {};
int err = 0 ;
switch (param->type) {
case fs_value_is_string:
err = ovl_kern_path(param->string, &layer_path, layer);
if (err)
return err;
err = ovl_do_parse_layer(fc, param->string, &layer_path, layer);
break ;
case fs_value_is_file: {
char *buf __free(kfree);
char *layer_name;
buf = kmalloc(PATH_MAX, GFP_KERNEL_ACCOUNT);
if (!buf)
return -ENOMEM;
layer_path = param->file->f_path;
path_get(&layer_path);
layer_name = d_path(&layer_path, buf, PATH_MAX);
if (IS_ERR(layer_name))
return PTR_ERR(layer_name);
err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer);
break ;
}
default :
WARN_ON_ONCE(true );
err = -EINVAL;
}
return err;
}
static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx)
{
struct ovl_fs_context_layer *l = ctx->lower;
// Reset old user provided lowerdir string
kfree(ctx->lowerdir_all);
ctx->lowerdir_all = NULL;
for (size_t nr = 0 ; nr < ctx->nr; nr++, l++) {
path_put(&l->path);
kfree(l->name);
l->name = NULL;
}
ctx->nr = 0 ;
ctx->nr_data = 0 ;
}
/*
* Parse lowerdir = mount option :
*
* e . g . : lowerdir = / lower1 : / lower2 : / lower3 : : / data1 : : / data2
* Set " / lower1 " , " / lower2 " , and " / lower3 " as lower layers and
* " / data1 " and " / data2 " as data lower layers . Any existing lower
* layers are replaced .
*/
static int ovl_parse_param_lowerdir(const char *name, struct fs_context *fc)
{
int err;
struct ovl_fs_context *ctx = fc->fs_private;
char *dup = NULL, *iter;
ssize_t nr_lower, nr;
bool data_layer = false ;
/*
* Ensure we ' re backwards compatible with mount ( 2 )
* by allowing relative paths .
*/
/* drop all existing lower layers */
ovl_reset_lowerdirs(ctx);
if (!*name)
return 0 ;
if (*name == ':' ) {
pr_err("cannot append lower layer\n" );
return -EINVAL;
}
// Store user provided lowerdir string to show in mount options
ctx->lowerdir_all = kstrdup(name, GFP_KERNEL);
if (!ctx->lowerdir_all)
return -ENOMEM;
dup = kstrdup(name, GFP_KERNEL);
if (!dup)
return -ENOMEM;
err = -EINVAL;
nr_lower = ovl_parse_param_split_lowerdirs(dup);
if (nr_lower < 0 )
goto out_err;
if (nr_lower > OVL_MAX_STACK) {
pr_err("too many lower directories, limit is %d\n" , OVL_MAX_STACK);
goto out_err;
}
iter = dup;
for (nr = 0 ; nr < nr_lower; nr++) {
struct path path __free(path_put) = {};
err = ovl_kern_path(iter, &path, Opt_lowerdir);
if (err)
goto out_err;
err = ovl_do_parse_layer(fc, iter, &path, Opt_lowerdir);
if (err)
goto out_err;
if (data_layer)
ctx->nr_data++;
/* Calling strchr() again would overrun. */
if (ctx->nr == nr_lower)
break ;
err = -EINVAL;
iter = strchr(iter, '\0' ) + 1 ;
if (*iter) {
/*
* This is a regular layer so we require that
* there are no data layers .
*/
if (ctx->nr_data > 0 ) {
pr_err("regular lower layers cannot follow data lower layers\n" );
goto out_err;
}
data_layer = false ;
continue ;
}
/* This is a data lower layer. */
data_layer = true ;
iter++;
}
kfree(dup);
return 0 ;
out_err:
kfree(dup);
/* Intentionally don't realloc to a smaller size. */
return err;
}
static int ovl_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
int err = 0 ;
struct fs_parse_result result;
struct ovl_fs *ofs = fc->s_fs_info;
struct ovl_config *config = &ofs->config;
struct ovl_fs_context *ctx = fc->fs_private;
int opt;
if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE) {
/*
* On remount overlayfs has always ignored all mount
* options no matter if malformed or not so for
* backwards compatibility we do the same here .
*/
if (fc->oldapi)
return 0 ;
/*
* Give us the freedom to allow changing mount options
* with the new mount api in the future . So instead of
* silently ignoring everything we report a proper
* error . This is only visible for users of the new
* mount api .
*/
return invalfc(fc, "No changes allowed in reconfigure" );
}
opt = fs_parse(fc, ovl_parameter_spec, param, &result);
if (opt < 0 )
return opt;
switch (opt) {
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);
break ;
case Opt_default_permissions:
config->default_permissions = true ;
break ;
case Opt_redirect_dir:
config->redirect_mode = result.uint_32;
if (config->redirect_mode == OVL_REDIRECT_OFF) {
config->redirect_mode = ovl_redirect_always_follow ?
OVL_REDIRECT_FOLLOW :
OVL_REDIRECT_NOFOLLOW;
}
ctx->set.redirect = true ;
break ;
case Opt_index:
config->index = result.uint_32;
ctx->set.index = true ;
break ;
case Opt_uuid:
config->uuid = result.uint_32;
break ;
case Opt_nfs_export:
config->nfs_export = result.uint_32;
ctx->set.nfs_export = true ;
break ;
case Opt_xino:
config->xino = result.uint_32;
break ;
case Opt_metacopy:
config->metacopy = result.uint_32;
ctx->set.metacopy = true ;
break ;
case Opt_verity:
config->verity_mode = result.uint_32;
break ;
case Opt_volatile:
config->ovl_volatile = true ;
break ;
case Opt_userxattr:
config->userxattr = true ;
break ;
case Opt_override_creds: {
const struct cred *cred = NULL;
if (result.negated) {
swap(cred, ofs->creator_cred);
put_cred(cred);
break ;
}
if (!current_in_userns(fc->user_ns)) {
err = -EINVAL;
break ;
}
cred = prepare_creds();
if (cred)
swap(cred, ofs->creator_cred);
else
err = -ENOMEM;
put_cred(cred);
break ;
}
default :
pr_err("unrecognized mount option \" %s\" or missing value\n" ,
param->key);
return -EINVAL;
}
return err;
}
static int ovl_get_tree(struct fs_context *fc)
{
return get_tree_nodev(fc, ovl_fill_super);
}
static inline void ovl_fs_context_free(struct ovl_fs_context *ctx)
{
ovl_reset_lowerdirs(ctx);
path_put(&ctx->upper);
path_put(&ctx->work);
kfree(ctx->lower);
kfree(ctx);
}
static void ovl_free(struct fs_context *fc)
{
struct ovl_fs *ofs = fc->s_fs_info;
struct ovl_fs_context *ctx = fc->fs_private;
/*
* ofs is stored in the fs_context when it is initialized .
* ofs is transferred to the superblock on a successful mount ,
* but if an error occurs before the transfer we have to free
* it here .
*/
if (ofs)
ovl_free_fs(ofs);
if (ctx)
ovl_fs_context_free(ctx);
}
static int ovl_reconfigure(struct fs_context *fc)
{
struct super_block *sb = fc->root->d_sb;
struct ovl_fs *ofs = OVL_FS(sb);
struct super_block *upper_sb;
int ret = 0 ;
if (!(fc->sb_flags & SB_RDONLY) && ovl_force_readonly(ofs))
return -EROFS;
if (fc->sb_flags & SB_RDONLY && !sb_rdonly(sb)) {
upper_sb = ovl_upper_mnt(ofs)->mnt_sb;
if (ovl_should_sync(ofs)) {
down_read(&upper_sb->s_umount);
ret = sync_filesystem(upper_sb);
up_read(&upper_sb->s_umount);
}
}
return ret;
}
static const struct fs_context_operations ovl_context_ops = {
.parse_monolithic = ovl_parse_monolithic,
.parse_param = ovl_parse_param,
.get_tree = ovl_get_tree,
.reconfigure = ovl_reconfigure,
.free = ovl_free,
};
/*
* This is called during fsopen ( ) and will record the user namespace of
* the caller in fc - > user_ns since we ' ve raised FS_USERNS_MOUNT . We ' ll
* need it when we actually create the superblock to verify that the
* process creating the superblock is in the same user namespace as
* process that called fsopen ( ) .
*/
int ovl_init_fs_context(struct fs_context *fc)
{
struct ovl_fs_context *ctx;
struct ovl_fs *ofs;
ctx = kzalloc(sizeof (*ctx), GFP_KERNEL_ACCOUNT);
if (!ctx)
return -ENOMEM;
/*
* By default we allocate for three lower layers . It ' s likely
* that it ' ll cover most users .
*/
ctx->lower = kmalloc_array(3 , sizeof (*ctx->lower), GFP_KERNEL_ACCOUNT);
if (!ctx->lower)
goto out_err;
ctx->capacity = 3 ;
ofs = kzalloc(sizeof (struct ovl_fs), GFP_KERNEL);
if (!ofs)
goto out_err;
ofs->config.redirect_mode = ovl_redirect_mode_def();
ofs->config.index = ovl_index_def;
ofs->config.uuid = ovl_uuid_def();
ofs->config.nfs_export = ovl_nfs_export_def;
ofs->config.xino = ovl_xino_def();
ofs->config.metacopy = ovl_metacopy_def;
fc->s_fs_info = ofs;
fc->fs_private = ctx;
fc->ops = &ovl_context_ops;
mutex_init(&ofs->whiteout_lock);
return 0 ;
out_err:
ovl_fs_context_free(ctx);
return -ENOMEM;
}
void ovl_free_fs(struct ovl_fs *ofs)
{
struct vfsmount **mounts;
unsigned i;
iput(ofs->workbasedir_trap);
iput(ofs->workdir_trap);
dput(ofs->whiteout);
dput(ofs->workdir);
if (ofs->workdir_locked)
ovl_inuse_unlock(ofs->workbasedir);
dput(ofs->workbasedir);
if (ofs->upperdir_locked)
ovl_inuse_unlock(ovl_upper_mnt(ofs)->mnt_root);
/* Reuse ofs->config.lowerdirs as a vfsmount array before freeing it */
mounts = (struct vfsmount **) ofs->config.lowerdirs;
for (i = 0 ; i < ofs->numlayer; i++) {
iput(ofs->layers[i].trap);
kfree(ofs->config.lowerdirs[i]);
mounts[i] = ofs->layers[i].mnt;
}
kern_unmount_array(mounts, ofs->numlayer);
kfree(ofs->layers);
for (i = 0 ; i < ofs->numfs; i++)
free_anon_bdev(ofs->fs[i].pseudo_dev);
kfree(ofs->fs);
kfree(ofs->config.lowerdirs);
kfree(ofs->config.upperdir);
kfree(ofs->config.workdir);
if (ofs->creator_cred)
put_cred(ofs->creator_cred);
kfree(ofs);
}
int ovl_fs_params_verify(const struct ovl_fs_context *ctx,
struct ovl_config *config)
{
struct ovl_opt_set set = ctx->set;
/* Workdir/index are useless in non-upper mount */
if (!config->upperdir) {
if (config->workdir) {
pr_info("option \" workdir=%s\" is useless in a non-upper mount, ignore\n" ,
config->workdir);
kfree(config->workdir);
config->workdir = NULL;
}
if (config->index && set.index) {
pr_info("option \" index=on\" is useless in a non-upper mount, ignore\n" );
set.index = false ;
}
config->index = false ;
}
if (!config->upperdir && config->ovl_volatile) {
pr_info("option \" volatile \" is meaningless in a non-upper mount, ignoring it.\n" );
config->ovl_volatile = false ;
}
if (!config->upperdir && config->uuid == OVL_UUID_ON) {
pr_info("option \" uuid=on\" requires an upper fs, falling back to uuid=null.\n" );
config->uuid = OVL_UUID_NULL;
}
/*
* This is to make the logic below simpler . It doesn ' t make any other
* difference , since redirect_dir = on is only used for upper .
*/
if (!config->upperdir && config->redirect_mode == OVL_REDIRECT_FOLLOW)
config->redirect_mode = OVL_REDIRECT_ON;
/* metacopy -> redirect_dir dependency */
if (config->metacopy && config->redirect_mode != OVL_REDIRECT_ON) {
if (set.metacopy && set.redirect) {
pr_err("conflicting options: metacopy=on,redirect_dir=%s\n" ,
ovl_redirect_mode(config));
return -EINVAL;
}
if (set.redirect) {
/*
* There was an explicit redirect_dir = . . . that resulted
* in this conflict .
*/
pr_info("disabling metacopy due to redirect_dir=%s\n" ,
ovl_redirect_mode(config));
config->metacopy = false ;
} else {
/* Automatically enable redirect otherwise. */
config->redirect_mode = OVL_REDIRECT_ON;
}
}
/* Resolve nfs_export -> index dependency */
if (config->nfs_export && !config->index) {
if (!config->upperdir &&
config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
pr_info("NFS export requires \" redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n" );
config->nfs_export = false ;
} else if (set.nfs_export && set.index) {
pr_err("conflicting options: nfs_export=on,index=off\n" );
return -EINVAL;
} else if (set.index) {
/*
* There was an explicit index = off that resulted
* in this conflict .
*/
pr_info("disabling nfs_export due to index=off\n" );
config->nfs_export = false ;
} else {
/* Automatically enable index otherwise. */
config->index = true ;
}
}
/* Resolve nfs_export -> !metacopy && !verity dependency */
if (config->nfs_export && config->metacopy) {
if (set.nfs_export && set.metacopy) {
pr_err("conflicting options: nfs_export=on,metacopy=on\n" );
return -EINVAL;
}
if (set.metacopy) {
/*
* There was an explicit metacopy = on that resulted
* in this conflict .
*/
pr_info("disabling nfs_export due to metacopy=on\n" );
config->nfs_export = false ;
} else if (config->verity_mode) {
/*
* There was an explicit verity = . . that resulted
* in this conflict .
*/
pr_info("disabling nfs_export due to verity=%s\n" ,
ovl_verity_mode(config));
config->nfs_export = false ;
} else {
/*
* There was an explicit nfs_export = on that resulted
* in this conflict .
*/
pr_info("disabling metacopy due to nfs_export=on\n" );
config->metacopy = false ;
}
}
/* Resolve userxattr -> !redirect && !metacopy dependency */
if (config->userxattr) {
if (set.redirect &&
config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
pr_err("conflicting options: userxattr,redirect_dir=%s\n" ,
ovl_redirect_mode(config));
return -EINVAL;
}
if (config->metacopy && set.metacopy) {
pr_err("conflicting options: userxattr,metacopy=on\n" );
return -EINVAL;
}
/*
* Silently disable default setting of redirect and metacopy .
* This shall be the default in the future as well : these
* options must be explicitly enabled if used together with
* userxattr .
*/
config->redirect_mode = OVL_REDIRECT_NOFOLLOW;
config->metacopy = false ;
}
/*
* Fail if we don ' t have trusted xattr capability and a feature was
* explicitly requested that requires them .
*/
if (!config->userxattr && !capable(CAP_SYS_ADMIN)) {
if (set.redirect &&
config->redirect_mode != OVL_REDIRECT_NOFOLLOW) {
pr_err("redirect_dir requires permission to access trusted xattrs\n" );
return -EPERM;
}
if (config->metacopy && set.metacopy) {
pr_err("metacopy requires permission to access trusted xattrs\n" );
return -EPERM;
}
if (config->verity_mode) {
pr_err("verity requires permission to access trusted xattrs\n" );
return -EPERM;
}
if (ctx->nr_data > 0 ) {
pr_err("lower data-only dirs require permission to access trusted xattrs\n" );
return -EPERM;
}
/*
* Other xattr - dependent features should be disabled without
* great disturbance to the user in ovl_make_workdir ( ) .
*/
}
return 0 ;
}
/**
* ovl_show_options
* @ m : the seq_file handle
* @ dentry : The dentry to query
*
* Prints the mount options for a given superblock .
* Returns zero ; does not fail .
*/
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 .
*/
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);
}
if (ofs->config.default_permissions)
seq_puts(m, ",default_permissions" );
if (ofs->config.redirect_mode != ovl_redirect_mode_def())
seq_printf(m, ",redirect_dir=%s" ,
ovl_redirect_mode(&ofs->config));
if (ofs->config.index != ovl_index_def)
seq_printf(m, ",index=%s" , str_on_off(ofs->config.index));
if (ofs->config.uuid != ovl_uuid_def())
seq_printf(m, ",uuid=%s" , ovl_uuid_mode(&ofs->config));
if (ofs->config.nfs_export != ovl_nfs_export_def)
seq_printf(m, ",nfs_export=%s" ,
str_on_off(ofs->config.nfs_export));
if (ofs->config.xino != ovl_xino_def() && !ovl_same_fs(ofs))
seq_printf(m, ",xino=%s" , ovl_xino_mode(&ofs->config));
if (ofs->config.metacopy != ovl_metacopy_def)
seq_printf(m, ",metacopy=%s" , str_on_off(ofs->config.metacopy));
if (ofs->config.ovl_volatile)
seq_puts(m, ",volatile" );
if (ofs->config.userxattr)
seq_puts(m, ",userxattr" );
if (ofs->config.verity_mode != ovl_verity_mode_def())
seq_printf(m, ",verity=%s" ,
ovl_verity_mode(&ofs->config));
return 0 ;
}
Messung V0.5 in Prozent C=97 H=94 G=95
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland