// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Junjiro R. Okajima
*
* This program, aufs is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
/*
* fs context, aka new mount api
*/
#include
#include "aufs.h"
struct au_fsctx_opts {
aufs_bindex_t bindex;
unsigned char skipped;
struct au_opt *opt, *opt_tail;
struct super_block *sb;
struct au_sbinfo *sbinfo;
struct au_opts opts;
};
/* stop extra interpretation of errno in mount(8), and strange error messages */
static int cvt_err(int err)
{
AuTraceErr(err);
switch (err) {
case -ENOENT:
case -ENOTDIR:
case -EEXIST:
case -EIO:
err = -EINVAL;
}
return err;
}
static int au_fsctx_reconfigure(struct fs_context *fc)
{
int err, do_dx;
unsigned int mntflags;
struct dentry *root;
struct super_block *sb;
struct inode *inode;
struct au_fsctx_opts *a = fc->fs_private;
AuDbg("fc %p\n", fc);
root = fc->root;
sb = root->d_sb;
err = si_write_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
if (!err) {
di_write_lock_child(root);
err = au_opts_verify(sb, fc->sb_flags, /*pending*/0);
aufs_write_unlock(root);
}
inode = d_inode(root);
inode_lock(inode);
err = si_write_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
if (unlikely(err))
goto out;
di_write_lock_child(root);
/* au_opts_remount() may return an error */
err = au_opts_remount(sb, &a->opts);
if (au_ftest_opts(a->opts.flags, REFRESH))
au_remount_refresh(sb, au_ftest_opts(a->opts.flags,
REFRESH_IDOP));
if (au_ftest_opts(a->opts.flags, REFRESH_DYAOP)) {
mntflags = au_mntflags(sb);
do_dx = !!au_opt_test(mntflags, DIO);
au_dy_arefresh(do_dx);
}
au_fhsm_wrote_all(sb, /*force*/1); /* ?? */
aufs_write_unlock(root);
out:
inode_unlock(inode);
err = cvt_err(err);
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
static int au_fsctx_fill_super(struct super_block *sb, struct fs_context *fc)
{
int err;
struct au_fsctx_opts *a = fc->fs_private;
struct au_sbinfo *sbinfo = a->sbinfo;
struct dentry *root;
struct inode *inode;
sbinfo->si_sb = sb;
sb->s_fs_info = sbinfo;
kobject_get(&sbinfo->si_kobj);
__si_write_lock(sb);
si_pid_set(sb);
au_sbilist_add(sb);
/* all timestamps always follow the ones on the branch */
sb->s_flags |= SB_NOATIME | SB_NODIRATIME;
sb->s_flags |= SB_I_VERSION; /* do we really need this? */
sb->s_op = &aufs_sop;
sb->s_d_op = &aufs_dop;
sb->s_magic = AUFS_SUPER_MAGIC;
sb->s_maxbytes = 0;
sb->s_stack_depth = 1;
au_export_init(sb);
au_xattr_init(sb);
err = au_alloc_root(sb);
if (unlikely(err)) {
si_write_unlock(sb);
goto out;
}
root = sb->s_root;
inode = d_inode(root);
ii_write_lock_parent(inode);
aufs_write_unlock(root);
/* lock vfs_inode first, then aufs. */
inode_lock(inode);
aufs_write_lock(root);
err = au_opts_mount(sb, &a->opts);
AuTraceErr(err);
if (!err && au_ftest_si(sbinfo, NO_DREVAL)) {
sb->s_d_op = &aufs_dop_noreval;
/* infofc(fc, "%ps", sb->s_d_op); */
pr_info("%ps\n", sb->s_d_op);
au_refresh_dop(root, /*force_reval*/0);
sbinfo->si_iop_array = aufs_iop_nogetattr;
au_refresh_iop(inode, /*force_getattr*/0);
}
aufs_write_unlock(root);
inode_unlock(inode);
if (!err)
goto out; /* success */
dput(root);
sb->s_root = NULL;
out:
if (unlikely(err))
kobject_put(&sbinfo->si_kobj);
AuTraceErr(err);
err = cvt_err(err);
AuTraceErr(err);
return err;
}
static int au_fsctx_get_tree(struct fs_context *fc)
{
int err;
AuDbg("fc %p\n", fc);
err = get_tree_nodev(fc, au_fsctx_fill_super);
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
static void au_fsctx_dump(struct au_opts *opts)
{
#ifdef CONFIG_AUFS_DEBUG
/* reduce stack space */
union {
struct au_opt_add *add;
struct au_opt_del *del;
struct au_opt_mod *mod;
struct au_opt_xino *xino;
struct au_opt_xino_itrunc *xino_itrunc;
struct au_opt_wbr_create *create;
} u;
struct au_opt *opt;
opt = opts->opt;
while (opt->type != Opt_tail) {
switch (opt->type) {
case Opt_add:
u.add = &opt->add;
AuDbg("add {b%d, %s, 0x%x, %p}\n",
u.add->bindex, u.add->pathname, u.add->perm,
u.add->path.dentry);
break;
case Opt_del:
fallthrough;
case Opt_idel:
u.del = &opt->del;
AuDbg("del {%s, %p}\n",
u.del->pathname, u.del->h_path.dentry);
break;
case Opt_mod:
fallthrough;
case Opt_imod:
u.mod = &opt->mod;
AuDbg("mod {%s, 0x%x, %p}\n",
u.mod->path, u.mod->perm, u.mod->h_root);
break;
case Opt_append:
u.add = &opt->add;
AuDbg("append {b%d, %s, 0x%x, %p}\n",
u.add->bindex, u.add->pathname, u.add->perm,
u.add->path.dentry);
break;
case Opt_prepend:
u.add = &opt->add;
AuDbg("prepend {b%d, %s, 0x%x, %p}\n",
u.add->bindex, u.add->pathname, u.add->perm,
u.add->path.dentry);
break;
case Opt_dirwh:
AuDbg("dirwh %d\n", opt->dirwh);
break;
case Opt_rdcache:
AuDbg("rdcache %d\n", opt->rdcache);
break;
case Opt_rdblk:
AuDbg("rdblk %d\n", opt->rdblk);
break;
case Opt_rdhash:
AuDbg("rdhash %u\n", opt->rdhash);
break;
case Opt_xino:
u.xino = &opt->xino;
AuDbg("xino {%s %pD}\n", u.xino->path, u.xino->file);
break;
#define au_fsctx_TF(name) \
case Opt_##name: \
if (opt->tf) \
AuLabel(name); \
else \
AuLabel(no##name); \
break;
/* simple true/false flag */
au_fsctx_TF(trunc_xino);
au_fsctx_TF(trunc_xib);
au_fsctx_TF(dirperm1);
au_fsctx_TF(plink);
au_fsctx_TF(shwh);
au_fsctx_TF(dio);
au_fsctx_TF(warn_perm);
au_fsctx_TF(verbose);
au_fsctx_TF(sum);
au_fsctx_TF(dirren);
au_fsctx_TF(acl);
#undef au_fsctx_TF
case Opt_trunc_xino_path:
fallthrough;
case Opt_itrunc_xino:
u.xino_itrunc = &opt->xino_itrunc;
AuDbg("trunc_xino %d\n", u.xino_itrunc->bindex);
break;
case Opt_noxino:
AuLabel(noxino);
break;
case Opt_list_plink:
AuLabel(list_plink);
break;
case Opt_udba:
AuDbg("udba %d, %s\n",
opt->udba, au_optstr_udba(opt->udba));
break;
case Opt_diropq_a:
AuLabel(diropq_a);
break;
case Opt_diropq_w:
AuLabel(diropq_w);
break;
case Opt_wsum:
AuLabel(wsum);
break;
case Opt_wbr_create:
u.create = &opt->wbr_create;
AuDbg("create %d, %s\n", u.create->wbr_create,
au_optstr_wbr_create(u.create->wbr_create));
switch (u.create->wbr_create) {
case AuWbrCreate_MFSV:
fallthrough;
case AuWbrCreate_PMFSV:
AuDbg("%d sec\n", u.create->mfs_second);
break;
case AuWbrCreate_MFSRR:
fallthrough;
case AuWbrCreate_TDMFS:
AuDbg("%llu watermark\n",
u.create->mfsrr_watermark);
break;
case AuWbrCreate_MFSRRV:
fallthrough;
case AuWbrCreate_TDMFSV:
fallthrough;
case AuWbrCreate_PMFSRRV:
AuDbg("%llu watermark, %d sec\n",
u.create->mfsrr_watermark,
u.create->mfs_second);
break;
}
break;
case Opt_wbr_copyup:
AuDbg("copyup %d, %s\n", opt->wbr_copyup,
au_optstr_wbr_copyup(opt->wbr_copyup));
break;
case Opt_fhsm_sec:
AuDbg("fhsm_sec %u\n", opt->fhsm_second);
break;
default:
AuDbg("type %d\n", opt->type);
BUG();
}
opt++;
}
#endif
}
/* ---------------------------------------------------------------------- */
/*
* For conditionally compiled mount options.
* Instead of fsparam_flag_no(), use this macro to distinguish ignore_silent.
*/
#define au_ignore_flag(name, action) \
fsparam_flag(name, action), \
fsparam_flag("no" name, Opt_ignore_silent)
const struct fs_parameter_spec aufs_fsctx_paramspec[] = {
fsparam_string("br", Opt_br),
/* "add=%d:%s" or "ins=%d:%s" */
fsparam_string("add", Opt_add),
fsparam_string("ins", Opt_add),
fsparam_path("append", Opt_append),
fsparam_path("prepend", Opt_prepend),
fsparam_path("del", Opt_del),
/* fsparam_s32("idel", Opt_idel), */
fsparam_path("mod", Opt_mod),
/* fsparam_string("imod", Opt_imod), */
fsparam_s32("dirwh", Opt_dirwh),
fsparam_path("xino", Opt_xino),
fsparam_flag("noxino", Opt_noxino),
fsparam_flag_no("trunc_xino", Opt_trunc_xino),
/* "trunc_xino_v=%d:%d" */
/* fsparam_string("trunc_xino_v", Opt_trunc_xino_v), */
fsparam_path("trunc_xino", Opt_trunc_xino_path),
fsparam_s32("itrunc_xino", Opt_itrunc_xino),
/* fsparam_path("zxino", Opt_zxino), */
fsparam_flag_no("trunc_xib", Opt_trunc_xib),
#ifdef CONFIG_PROC_FS
fsparam_flag_no("plink", Opt_plink),
#else
au_ignore_flag("plink", Opt_ignore),
#endif
#ifdef CONFIG_AUFS_DEBUG
fsparam_flag("list_plink", Opt_list_plink),
#endif
fsparam_string("udba", Opt_udba),
fsparam_flag_no("dio", Opt_dio),
#ifdef CONFIG_AUFS_DIRREN
fsparam_flag_no("dirren", Opt_dirren),
#else
au_ignore_flag("dirren", Opt_ignore),
#endif
#ifdef CONFIG_AUFS_FHSM
fsparam_s32("fhsm_sec", Opt_fhsm_sec),
#else
fsparam_s32("fhsm_sec", Opt_ignore),
#endif
/* always | a | whiteouted | w */
fsparam_string("diropq", Opt_diropq),
fsparam_flag_no("warn_perm", Opt_warn_perm),
#ifdef CONFIG_AUFS_SHWH
fsparam_flag_no("shwh", Opt_shwh),
#else
au_ignore_flag("shwh", Opt_err),
#endif
fsparam_flag_no("dirperm1", Opt_dirperm1),
fsparam_flag_no("verbose", Opt_verbose),
fsparam_flag("v", Opt_verbose),
fsparam_flag("quiet", Opt_noverbose),
fsparam_flag("q", Opt_noverbose),
/* user-space may handle this */
fsparam_flag("silent", Opt_noverbose),
fsparam_flag_no("sum", Opt_sum),
fsparam_flag("wsum", Opt_wsum),
fsparam_s32("rdcache", Opt_rdcache),
/* "def" or s32 */
fsparam_string("rdblk", Opt_rdblk),
/* "def" or s32 */
fsparam_string("rdhash", Opt_rdhash),
fsparam_string("create", Opt_wbr_create),
fsparam_string("create_policy", Opt_wbr_create),
fsparam_string("cpup", Opt_wbr_copyup),
fsparam_string("copyup", Opt_wbr_copyup),
fsparam_string("copyup_policy", Opt_wbr_copyup),
/* generic VFS flag */
#ifdef CONFIG_FS_POSIX_ACL
fsparam_flag_no("acl", Opt_acl),
#else
au_ignore_flag("acl"),
#endif
/* internal use for the scripts */
fsparam_string("si", Opt_ignore_silent),
/* obsoleted, keep them temporary */
fsparam_flag("nodlgt", Opt_ignore_silent),
fsparam_flag("clean_plink", Opt_ignore),
fsparam_string("dirs", Opt_br),
fsparam_u32("debug", Opt_ignore),
/* "whiteout" or "all" */
fsparam_string("delete", Opt_ignore),
fsparam_string("imap", Opt_ignore),
/* temporary workaround, due to old mount(8)? */
fsparam_flag("relatime", Opt_ignore_silent),
{}
};
static int au_fsctx_parse_do_add(struct fs_context *fc, struct au_opt *opt,
char *brspec, size_t speclen,
aufs_bindex_t bindex)
{
int err;
char *p;
AuDbg("brspec %s\n", brspec);
err = -ENOMEM;
if (!speclen)
speclen = strlen(brspec);
/* will be freed by au_fsctx_free() */
p = kmemdup_nul(brspec, speclen, GFP_NOFS);
if (unlikely(!p)) {
errorfc(fc, "failed in %s", brspec);
goto out;
}
err = au_opt_add(opt, p, fc->sb_flags, bindex);
out:
AuTraceErr(err);
return err;
}
static int au_fsctx_parse_br(struct fs_context *fc, char *brspec)
{
int err;
char *p;
struct au_fsctx_opts *a = fc->fs_private;
struct au_opt *opt = a->opt;
aufs_bindex_t bindex = a->bindex;
AuDbg("brspec %s\n", brspec);
err = -EINVAL;
while ((p = strsep(&brspec, ":")) && *p) {
err = au_fsctx_parse_do_add(fc, opt, p, /*len*/0, bindex);
AuTraceErr(err);
if (unlikely(err))
break;
bindex++;
opt++;
if (unlikely(opt > a->opt_tail)) {
err = -E2BIG;
bindex--;
opt--;
break;
}
opt->type = Opt_tail;
a->skipped = 1;
}
a->bindex = bindex;
a->opt = opt;
AuTraceErr(err);
return err;
}
static int au_fsctx_parse_add(struct fs_context *fc, char *addspec)
{
int err, n;
char *p;
struct au_fsctx_opts *a = fc->fs_private;
struct au_opt *opt = a->opt;
err = -EINVAL;
p = strchr(addspec, ':');
if (unlikely(!p)) {
errorfc(fc, "bad arg in %s", addspec);
goto out;
}
*p++ = '\0';
err = kstrtoint(addspec, 0, &n);
if (unlikely(err)) {
errorfc(fc, "bad integer in %s", addspec);
goto out;
}
AuDbg("n %d\n", n);
err = au_fsctx_parse_do_add(fc, opt, p, /*len*/0, n);
out:
AuTraceErr(err);
return err;
}
static int au_fsctx_parse_del(struct fs_context *fc, struct au_opt_del *del,
struct fs_parameter *param)
{
int err;
err = -ENOMEM;
/* will be freed by au_fsctx_free() */
del->pathname = kmemdup_nul(param->string, param->size, GFP_NOFS);
if (unlikely(!del->pathname))
goto out;
AuDbg("del %s\n", del->pathname);
err = vfsub_kern_path(del->pathname, AuOpt_LkupDirFlags, &del->h_path);
if (unlikely(err))
errorfc(fc, "lookup failed %s (%d)", del->pathname, err);
out:
AuTraceErr(err);
return err;
}
#if 0 /* reserved for future use */
static int au_fsctx_parse_idel(struct fs_context *fc, struct au_opt_del *del,
aufs_bindex_t bindex)
{
int err;
struct super_block *sb;
struct dentry *root;
struct au_fsctx_opts *a = fc->fs_private;
sb = a->sb;
AuDebugOn(!sb);
err = -EINVAL;
root = sb->s_root;
aufs_read_lock(root, AuLock_FLUSH);
if (bindex < 0 || au_sbbot(sb) < bindex) {
errorfc(fc, "out of bounds, %d", bindex);
goto out;
}
err = 0;
del->h_path.dentry = dget(au_h_dptr(root, bindex));
del->h_path.mnt = mntget(au_sbr_mnt(sb, bindex));
out:
aufs_read_unlock(root, !AuLock_IR);
AuTraceErr(err);
return err;
}
#endif
static int au_fsctx_parse_mod(struct fs_context *fc, struct au_opt_mod *mod,
struct fs_parameter *param)
{
int err;
struct path path;
char *p;
err = -ENOMEM;
/* will be freed by au_fsctx_free() */
mod->path = kmemdup_nul(param->string, param->size, GFP_NOFS);
if (unlikely(!mod->path))
goto out;
err = -EINVAL;
p = strchr(mod->path, '=');
if (unlikely(!p)) {
errorfc(fc, "no permission %s", mod->path);
goto out;
}
*p++ = 0;
err = vfsub_kern_path(mod->path, AuOpt_LkupDirFlags, &path);
if (unlikely(err)) {
errorfc(fc, "lookup failed %s (%d)", mod->path, err);
goto out;
}
mod->perm = au_br_perm_val(p);
AuDbg("mod path %s, perm 0x%x, %s", mod->path, mod->perm, p);
mod->h_root = dget(path.dentry);
path_put(&path);
out:
AuTraceErr(err);
return err;
}
#if 0 /* reserved for future use */
static int au_fsctx_parse_imod(struct fs_context *fc, struct au_opt_mod *mod,
char *ibrspec)
{
int err, n;
char *p;
struct super_block *sb;
struct dentry *root;
struct au_fsctx_opts *a = fc->fs_private;
sb = a->sb;
AuDebugOn(!sb);
err = -EINVAL;
p = strchr(ibrspec, ':');
if (unlikely(!p)) {
errorfc(fc, "no index, %s", ibrspec);
goto out;
}
*p++ = '\0';
err = kstrtoint(ibrspec, 0, &n);
if (unlikely(err)) {
errorfc(fc, "bad integer in %s", ibrspec);
goto out;
}
AuDbg("n %d\n", n);
root = sb->s_root;
aufs_read_lock(root, AuLock_FLUSH);
if (n < 0 || au_sbbot(sb) < n) {
errorfc(fc, "out of bounds, %d", bindex);
goto out_root;
}
err = 0;
mod->perm = au_br_perm_val(p);
AuDbg("mod path %s, perm 0x%x, %s\n",
mod->path, mod->perm, p);
mod->h_root = dget(au_h_dptr(root, bindex));
out_root:
aufs_read_unlock(root, !AuLock_IR);
out:
AuTraceErr(err);
return err;
}
#endif
static int au_fsctx_parse_xino(struct fs_context *fc,
struct au_opt_xino *xino,
struct fs_parameter *param)
{
int err;
struct au_fsctx_opts *a = fc->fs_private;
err = -ENOMEM;
/* will be freed by au_opts_free() */
xino->path = kmemdup_nul(param->string, param->size, GFP_NOFS);
if (unlikely(!xino->path))
goto out;
AuDbg("path %s\n", xino->path);
xino->file = au_xino_create(a->sb, xino->path, /*silent*/0,
/*wbrtop*/0);
err = PTR_ERR(xino->file);
if (IS_ERR(xino->file)) {
xino->file = NULL;
goto out;
}
err = 0;
if (unlikely(a->sb && xino->file->f_path.dentry->d_sb == a->sb)) {
err = -EINVAL;
errorfc(fc, "%s must be outside", xino->path);
}
out:
AuTraceErr(err);
return err;
}
static
int au_fsctx_parse_xino_itrunc_path(struct fs_context *fc,
struct au_opt_xino_itrunc *xino_itrunc,
char *pathname)
{
int err;
aufs_bindex_t bbot, bindex;
struct path path;
struct dentry *root;
struct au_fsctx_opts *a = fc->fs_private;
AuDebugOn(!a->sb);
err = vfsub_kern_path(pathname, AuOpt_LkupDirFlags, &path);
if (unlikely(err)) {
errorfc(fc, "lookup failed %s (%d)", pathname, err);
goto out;
}
xino_itrunc->bindex = -1;
root = a->sb->s_root;
aufs_read_lock(root, AuLock_FLUSH);
bbot = au_sbbot(a->sb);
for (bindex = 0; bindex <= bbot; bindex++) {
if (au_h_dptr(root, bindex) == path.dentry) {
xino_itrunc->bindex = bindex;
break;
}
}
aufs_read_unlock(root, !AuLock_IR);
path_put(&path);
if (unlikely(xino_itrunc->bindex < 0)) {
err = -EINVAL;
errorfc(fc, "no such branch %s", pathname);
}
out:
AuTraceErr(err);
return err;
}
static int au_fsctx_parse_xino_itrunc(struct fs_context *fc,
struct au_opt_xino_itrunc *xino_itrunc,
unsigned int bindex)
{
int err;
aufs_bindex_t bbot;
struct super_block *sb;
struct au_fsctx_opts *a = fc->fs_private;
sb = a->sb;
AuDebugOn(!sb);
err = 0;
si_noflush_read_lock(sb);
bbot = au_sbbot(sb);
si_read_unlock(sb);
if (bindex <= bbot)
xino_itrunc->bindex = bindex;
else {
err = -EINVAL;
errorfc(fc, "out of bounds, %u", bindex);
}
AuTraceErr(err);
return err;
}
static int au_fsctx_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
int err, token;
struct fs_parse_result result;
struct au_fsctx_opts *a = fc->fs_private;
struct au_opt *opt = a->opt;
AuDbg("fc %p, param {key %s, string %s}\n",
fc, param->key, param->string);
err = fs_parse(fc, aufs_fsctx_paramspec, param, &result);
if (unlikely(err < 0))
goto out;
token = err;
AuDbg("token %d, res{negated %d, uint64 %llu}\n",
token, result.negated, result.uint_64);
err = -EINVAL;
a->skipped = 0;
switch (token) {
case Opt_br:
err = au_fsctx_parse_br(fc, param->string);
break;
case Opt_add:
err = au_fsctx_parse_add(fc, param->string);
break;
case Opt_append:
err = au_fsctx_parse_do_add(fc, opt, param->string, param->size,
/*dummy bindex*/1);
break;
case Opt_prepend:
err = au_fsctx_parse_do_add(fc, opt, param->string, param->size,
/*bindex*/0);
break;
case Opt_del:
err = au_fsctx_parse_del(fc, &opt->del, param);
break;
#if 0 /* reserved for future use */
case Opt_idel:
if (!a->sb) {
err = 0;
a->skipped = 1;
break;
}
del->pathname = "(indexed)";
err = au_opts_parse_idel(fc, &opt->del, result.uint_32);
break;
#endif
case Opt_mod:
err = au_fsctx_parse_mod(fc, &opt->mod, param);
break;
#ifdef IMOD /* reserved for future use */
case Opt_imod:
if (!a->sb) {
err = 0;
a->skipped = 1;
break;
}
u.mod->path = "(indexed)";
err = au_opts_parse_imod(fc, &opt->mod, param->string);
break;
#endif
case Opt_xino:
err = au_fsctx_parse_xino(fc, &opt->xino, param);
break;
case Opt_trunc_xino_path:
if (!a->sb) {
errorfc(fc, "no such branch %s", param->string);
break;
}
err = au_fsctx_parse_xino_itrunc_path(fc, &opt->xino_itrunc,
param->string);
break;
#if 0
case Opt_trunc_xino_v:
if (!a->sb) {
err = 0;
a->skipped = 1;
break;
}
err = au_fsctx_parse_xino_itrunc_path(fc, &opt->xino_itrunc,
param->string);
break;
#endif
case Opt_itrunc_xino:
if (!a->sb) {
errorfc(fc, "out of bounds %s", param->string);
break;
}
err = au_fsctx_parse_xino_itrunc(fc, &opt->xino_itrunc,
result.int_32);
break;
case Opt_dirwh:
err = 0;
opt->dirwh = result.int_32;
break;
case Opt_rdcache:
if (unlikely(result.int_32 > AUFS_RDCACHE_MAX)) {
errorfc(fc, "rdcache must be smaller than %d",
AUFS_RDCACHE_MAX);
break;
}
err = 0;
opt->rdcache = result.int_32;
break;
case Opt_rdblk:
err = 0;
opt->rdblk = AUFS_RDBLK_DEF;
if (!strcmp(param->string, "def"))
break;
err = kstrtoint(param->string, 0, &result.int_32);
if (unlikely(err)) {
errorfc(fc, "bad value in %s", param->key);
break;
}
err = -EINVAL;
if (unlikely(result.int_32 < 0
|| result.int_32 > KMALLOC_MAX_SIZE)) {
errorfc(fc, "bad value in %s", param->key);
break;
}
if (unlikely(result.int_32 && result.int_32 < NAME_MAX)) {
errorfc(fc, "rdblk must be larger than %d", NAME_MAX);
break;
}
err = 0;
opt->rdblk = result.int_32;
break;
case Opt_rdhash:
err = 0;
opt->rdhash = AUFS_RDHASH_DEF;
if (!strcmp(param->string, "def"))
break;
err = kstrtoint(param->string, 0, &result.int_32);
if (unlikely(err)) {
errorfc(fc, "bad value in %s", param->key);
break;
}
/* how about zero? */
if (result.int_32 < 0
|| result.int_32 * sizeof(struct hlist_head)
> KMALLOC_MAX_SIZE) {
err = -EINVAL;
errorfc(fc, "bad integer in %s", param->key);
break;
}
opt->rdhash = result.int_32;
break;
case Opt_diropq:
/*
* As other options, fs/aufs/opts.c can handle these strings by
* match_token(). But "diropq=" is deprecated now and will
* never have other value. So simple strcmp() is enough here.
*/
if (!strcmp(param->string, "a") ||
!strcmp(param->string, "always")) {
err = 0;
opt->type = Opt_diropq_a;
} else if (!strcmp(param->string, "w") ||
!strcmp(param->string, "whiteouted")) {
err = 0;
opt->type = Opt_diropq_w;
} else
errorfc(fc, "unknown value %s", param->string);
break;
case Opt_udba:
opt->udba = au_udba_val(param->string);
if (opt->udba >= 0)
err = 0;
else
errorf(fc, "wrong value, %s", param->string);
break;
case Opt_wbr_create:
opt->wbr_create.wbr_create
= au_wbr_create_val(param->string, &opt->wbr_create);
if (opt->wbr_create.wbr_create >= 0)
err = 0;
else
errorf(fc, "wrong value, %s", param->key);
break;
case Opt_wbr_copyup:
opt->wbr_copyup = au_wbr_copyup_val(param->string);
if (opt->wbr_copyup >= 0)
err = 0;
else
errorfc(fc, "wrong value, %s", param->key);
break;
case Opt_fhsm_sec:
if (unlikely(result.int_32 < 0)) {
errorfc(fc, "bad integer in %s\n", param->key);
break;
}
err = 0;
if (sysaufs_brs)
opt->fhsm_second = result.int_32;
else
warnfc(fc, "ignored %s %s", param->key, param->string);
break;
/* simple true/false flag */
#define au_fsctx_TF(name) \
case Opt_##name: \
err = 0; \
opt->tf = !result.negated; \
break;
au_fsctx_TF(trunc_xino);
au_fsctx_TF(trunc_xib);
au_fsctx_TF(dirperm1);
au_fsctx_TF(plink);
au_fsctx_TF(shwh);
au_fsctx_TF(dio);
au_fsctx_TF(warn_perm);
au_fsctx_TF(verbose);
au_fsctx_TF(sum);
au_fsctx_TF(dirren);
au_fsctx_TF(acl);
#undef au_fsctx_TF
case Opt_noverbose:
err = 0;
opt->type = Opt_verbose;
opt->tf = false;
break;
case Opt_noxino:
fallthrough;
case Opt_list_plink:
fallthrough;
case Opt_wsum:
err = 0;
break;
case Opt_ignore:
warnfc(fc, "ignored %s", param->key);
fallthrough;
case Opt_ignore_silent:
a->skipped = 1;
err = 0;
break;
default:
a->skipped = 1;
err = -ENOPARAM;
break;
}
if (unlikely(err))
goto out;
if (a->skipped)
goto out;
switch (token) {
case Opt_br:
fallthrough;
case Opt_noverbose:
fallthrough;
case Opt_diropq:
break;
default:
opt->type = token;
break;
}
opt++;
if (unlikely(opt > a->opt_tail)) {
err = -E2BIG;
opt--;
}
opt->type = Opt_tail;
a->opt = opt;
out:
return err;
}
/*
* these options accept both 'name=val' and 'name:val' form.
* some accept optional '=' in its value.
* eg. br:/br1=rw:/br2=ro and br=/br1=rw:/br2=ro
*/
static inline unsigned int is_colonopt(char *str)
{
#define do_test(name) \
if (!strncmp(str, name ":", sizeof(name))) \
return sizeof(name) - 1;
do_test("br");
do_test("add");
do_test("ins");
do_test("append");
do_test("prepend");
do_test("del");
/* do_test("idel"); */
do_test("mod");
/* do_test("imod"); */
#undef do_test
return 0;
}
static int au_fsctx_parse_monolithic(struct fs_context *fc, void *data)
{
int err;
unsigned int u;
char *str;
struct au_fsctx_opts *a = fc->fs_private;
str = data;
AuDbg("str %s\n", str);
while (str) {
u = is_colonopt(str);
if (u)
str[u] = '=';
str = strchr(str, ',');
if (!str)
break;
str++;
}
str = data;
AuDbg("str %s\n", str);
err = generic_parse_monolithic(fc, str);
AuTraceErr(err);
au_fsctx_dump(&a->opts);
return err;
}
/* ---------------------------------------------------------------------- */
static void au_fsctx_opts_free(struct au_opts *opts)
{
struct au_opt *opt;
opt = opts->opt;
while (opt->type != Opt_tail) {
switch (opt->type) {
case Opt_add:
fallthrough;
case Opt_append:
fallthrough;
case Opt_prepend:
kfree(opt->add.pathname);
path_put(&opt->add.path);
break;
case Opt_del:
kfree(opt->del.pathname);
fallthrough;
case Opt_idel:
path_put(&opt->del.h_path);
break;
case Opt_mod:
kfree(opt->mod.path);
fallthrough;
case Opt_imod:
dput(opt->mod.h_root);
break;
case Opt_xino:
kfree(opt->xino.path);
fput(opt->xino.file);
break;
}
opt++;
}
}
static void au_fsctx_free(struct fs_context *fc)
{
struct au_fsctx_opts *a = fc->fs_private;
/* fs_type=%p, root=%pD */
AuDbg("fc %p{sb_flags 0x%x, sb_flags_mask 0x%x, purpose %u\n",
fc, fc->sb_flags, fc->sb_flags_mask, fc->purpose);
kobject_put(&a->sbinfo->si_kobj);
au_fsctx_opts_free(&a->opts);
free_page((unsigned long)a->opts.opt);
au_kfree_rcu(a);
}
static const struct fs_context_operations au_fsctx_ops = {
.free = au_fsctx_free,
.parse_param = au_fsctx_parse_param,
.parse_monolithic = au_fsctx_parse_monolithic,
.get_tree = au_fsctx_get_tree,
.reconfigure = au_fsctx_reconfigure
/*
* nfs4 requires ->dup()? No.
* I don't know what is this ->dup() for.
*/
};
int aufs_fsctx_init(struct fs_context *fc)
{
int err;
struct au_fsctx_opts *a;
/* fs_type=%p, root=%pD */
AuDbg("fc %p{sb_flags 0x%x, sb_flags_mask 0x%x, purpose %u\n",
fc, fc->sb_flags, fc->sb_flags_mask, fc->purpose);
/* they will be freed by au_fsctx_free() */
err = -ENOMEM;
a = kzalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
a->bindex = 0;
a->opts.opt = (void *)__get_free_page(GFP_NOFS);
if (unlikely(!a->opts.opt))
goto out_a;
a->opt = a->opts.opt;
a->opt->type = Opt_tail;
a->opts.max_opt = PAGE_SIZE / sizeof(*a->opts.opt);
a->opt_tail = a->opt + a->opts.max_opt - 1;
a->opts.sb_flags = fc->sb_flags;
a->sb = NULL;
if (fc->root) {
AuDebugOn(fc->purpose != FS_CONTEXT_FOR_RECONFIGURE);
a->opts.flags = AuOpts_REMOUNT;
a->sb = fc->root->d_sb;
a->sbinfo = au_sbi(a->sb);
kobject_get(&a->sbinfo->si_kobj);
} else {
a->sbinfo = au_si_alloc(a->sb);
AuDebugOn(!a->sbinfo);
err = PTR_ERR(a->sbinfo);
if (IS_ERR(a->sbinfo))
goto out_opt;
au_rw_write_unlock(&a->sbinfo->si_rwsem);
}
err = 0;
fc->fs_private = a;
fc->ops = &au_fsctx_ops;
goto out; /* success */
out_opt:
free_page((unsigned long)a->opts.opt);
out_a:
au_kfree_rcu(a);
out:
AuTraceErr(err);
return err;
}