// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2019 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 .
*/
/*
* mount options/flags
*/
#include
#include /* a distribution requires */
#include
#include "aufs.h"
/* ---------------------------------------------------------------------- */
enum {
Opt_br,
Opt_add, Opt_del, Opt_mod, Opt_append, Opt_prepend,
Opt_idel, Opt_imod,
Opt_dirwh, Opt_rdcache, Opt_rdblk, Opt_rdhash,
Opt_rdblk_def, Opt_rdhash_def,
Opt_xino, Opt_noxino,
Opt_trunc_xino, Opt_trunc_xino_v, Opt_notrunc_xino,
Opt_trunc_xino_path, Opt_itrunc_xino,
Opt_trunc_xib, Opt_notrunc_xib,
Opt_shwh, Opt_noshwh,
Opt_plink, Opt_noplink, Opt_list_plink,
Opt_udba,
Opt_dio, Opt_nodio,
Opt_diropq_a, Opt_diropq_w,
Opt_warn_perm, Opt_nowarn_perm,
Opt_wbr_copyup, Opt_wbr_create,
Opt_fhsm_sec,
Opt_verbose, Opt_noverbose,
Opt_sum, Opt_nosum, Opt_wsum,
Opt_dirperm1, Opt_nodirperm1,
Opt_dirren, Opt_nodirren,
Opt_acl, Opt_noacl,
Opt_tail, Opt_ignore, Opt_ignore_silent, Opt_err
};
static match_table_t options = {
{Opt_br, "br=%s"},
{Opt_br, "br:%s"},
{Opt_add, "add=%d:%s"},
{Opt_add, "add:%d:%s"},
{Opt_add, "ins=%d:%s"},
{Opt_add, "ins:%d:%s"},
{Opt_append, "append=%s"},
{Opt_append, "append:%s"},
{Opt_prepend, "prepend=%s"},
{Opt_prepend, "prepend:%s"},
{Opt_del, "del=%s"},
{Opt_del, "del:%s"},
/* {Opt_idel, "idel:%d"}, */
{Opt_mod, "mod=%s"},
{Opt_mod, "mod:%s"},
/* {Opt_imod, "imod:%d:%s"}, */
{Opt_dirwh, "dirwh=%d"},
{Opt_xino, "xino=%s"},
{Opt_noxino, "noxino"},
{Opt_trunc_xino, "trunc_xino"},
{Opt_trunc_xino_v, "trunc_xino_v=%d:%d"},
{Opt_notrunc_xino, "notrunc_xino"},
{Opt_trunc_xino_path, "trunc_xino=%s"},
{Opt_itrunc_xino, "itrunc_xino=%d"},
/* {Opt_zxino, "zxino=%s"}, */
{Opt_trunc_xib, "trunc_xib"},
{Opt_notrunc_xib, "notrunc_xib"},
#ifdef CONFIG_PROC_FS
{Opt_plink, "plink"},
#else
{Opt_ignore_silent, "plink"},
#endif
{Opt_noplink, "noplink"},
#ifdef CONFIG_AUFS_DEBUG
{Opt_list_plink, "list_plink"},
#endif
{Opt_udba, "udba=%s"},
{Opt_dio, "dio"},
{Opt_nodio, "nodio"},
#ifdef CONFIG_AUFS_DIRREN
{Opt_dirren, "dirren"},
{Opt_nodirren, "nodirren"},
#else
{Opt_ignore, "dirren"},
{Opt_ignore_silent, "nodirren"},
#endif
#ifdef CONFIG_AUFS_FHSM
{Opt_fhsm_sec, "fhsm_sec=%d"},
#else
{Opt_ignore, "fhsm_sec=%d"},
#endif
{Opt_diropq_a, "diropq=always"},
{Opt_diropq_a, "diropq=a"},
{Opt_diropq_w, "diropq=whiteouted"},
{Opt_diropq_w, "diropq=w"},
{Opt_warn_perm, "warn_perm"},
{Opt_nowarn_perm, "nowarn_perm"},
/* keep them temporary */
{Opt_ignore_silent, "nodlgt"},
{Opt_ignore, "clean_plink"},
#ifdef CONFIG_AUFS_SHWH
{Opt_shwh, "shwh"},
#endif
{Opt_noshwh, "noshwh"},
{Opt_dirperm1, "dirperm1"},
{Opt_nodirperm1, "nodirperm1"},
{Opt_verbose, "verbose"},
{Opt_verbose, "v"},
{Opt_noverbose, "noverbose"},
{Opt_noverbose, "quiet"},
{Opt_noverbose, "q"},
{Opt_noverbose, "silent"},
{Opt_sum, "sum"},
{Opt_nosum, "nosum"},
{Opt_wsum, "wsum"},
{Opt_rdcache, "rdcache=%d"},
{Opt_rdblk, "rdblk=%d"},
{Opt_rdblk_def, "rdblk=def"},
{Opt_rdhash, "rdhash=%d"},
{Opt_rdhash_def, "rdhash=def"},
{Opt_wbr_create, "create=%s"},
{Opt_wbr_create, "create_policy=%s"},
{Opt_wbr_copyup, "cpup=%s"},
{Opt_wbr_copyup, "copyup=%s"},
{Opt_wbr_copyup, "copyup_policy=%s"},
/* generic VFS flag */
#ifdef CONFIG_FS_POSIX_ACL
{Opt_acl, "acl"},
{Opt_noacl, "noacl"},
#else
{Opt_ignore, "acl"},
{Opt_ignore_silent, "noacl"},
#endif
/* internal use for the scripts */
{Opt_ignore_silent, "si=%s"},
{Opt_br, "dirs=%s"},
{Opt_ignore, "debug=%d"},
{Opt_ignore, "delete=whiteout"},
{Opt_ignore, "delete=all"},
{Opt_ignore, "imap=%s"},
/* temporary workaround, due to old mount(8)? */
{Opt_ignore_silent, "relatime"},
{Opt_err, NULL}
};
/* ---------------------------------------------------------------------- */
static const char *au_parser_pattern(int val, match_table_t tbl)
{
struct match_token *p;
p = tbl;
while (p->pattern) {
if (p->token == val)
return p->pattern;
p++;
}
BUG();
return "??";
}
static const char *au_optstr(int *val, match_table_t tbl)
{
struct match_token *p;
int v;
v = *val;
if (!v)
goto out;
p = tbl;
while (p->pattern) {
if (p->token
&& (v & p->token) == p->token) {
*val &= ~p->token;
return p->pattern;
}
p++;
}
out:
return NULL;
}
/* ---------------------------------------------------------------------- */
static match_table_t brperm = {
{AuBrPerm_RO, AUFS_BRPERM_RO},
{AuBrPerm_RR, AUFS_BRPERM_RR},
{AuBrPerm_RW, AUFS_BRPERM_RW},
{0, NULL}
};
static match_table_t brattr = {
/* general */
{AuBrAttr_COO_REG, AUFS_BRATTR_COO_REG},
{AuBrAttr_COO_ALL, AUFS_BRATTR_COO_ALL},
/* 'unpin' attrib is meaningless since linux-3.18-rc1 */
{AuBrAttr_UNPIN, AUFS_BRATTR_UNPIN},
#ifdef CONFIG_AUFS_FHSM
{AuBrAttr_FHSM, AUFS_BRATTR_FHSM},
#endif
#ifdef CONFIG_AUFS_XATTR
{AuBrAttr_ICEX, AUFS_BRATTR_ICEX},
{AuBrAttr_ICEX_SEC, AUFS_BRATTR_ICEX_SEC},
{AuBrAttr_ICEX_SYS, AUFS_BRATTR_ICEX_SYS},
{AuBrAttr_ICEX_TR, AUFS_BRATTR_ICEX_TR},
{AuBrAttr_ICEX_USR, AUFS_BRATTR_ICEX_USR},
{AuBrAttr_ICEX_OTH, AUFS_BRATTR_ICEX_OTH},
#endif
/* ro/rr branch */
{AuBrRAttr_WH, AUFS_BRRATTR_WH},
/* rw branch */
{AuBrWAttr_MOO, AUFS_BRWATTR_MOO},
{AuBrWAttr_NoLinkWH, AUFS_BRWATTR_NLWH},
{0, NULL}
};
static int br_attr_val(char *str, match_table_t table, substring_t args[])
{
int attr, v;
char *p;
attr = 0;
do {
p = strchr(str, '+');
if (p)
*p = 0;
v = match_token(str, table, args);
if (v) {
if (v & AuBrAttr_CMOO_Mask)
attr &= ~AuBrAttr_CMOO_Mask;
attr |= v;
} else {
if (p)
*p = '+';
pr_warn("ignored branch attribute %s\n", str);
break;
}
if (p)
str = p + 1;
} while (p);
return attr;
}
static int au_do_optstr_br_attr(au_br_perm_str_t *str, int perm)
{
int sz;
const char *p;
char *q;
q = str->a;
*q = 0;
p = au_optstr(&perm, brattr);
if (p) {
sz = strlen(p);
memcpy(q, p, sz + 1);
q += sz;
} else
goto out;
do {
p = au_optstr(&perm, brattr);
if (p) {
*q++ = '+';
sz = strlen(p);
memcpy(q, p, sz + 1);
q += sz;
}
} while (p);
out:
return q - str->a;
}
static int noinline_for_stack br_perm_val(char *perm)
{
int val, bad, sz;
char *p;
substring_t args[MAX_OPT_ARGS];
au_br_perm_str_t attr;
p = strchr(perm, '+');
if (p)
*p = 0;
val = match_token(perm, brperm, args);
if (!val) {
if (p)
*p = '+';
pr_warn("ignored branch permission %s\n", perm);
val = AuBrPerm_RO;
goto out;
}
if (!p)
goto out;
val |= br_attr_val(p + 1, brattr, args);
bad = 0;
switch (val & AuBrPerm_Mask) {
case AuBrPerm_RO:
case AuBrPerm_RR:
bad = val & AuBrWAttr_Mask;
val &= ~AuBrWAttr_Mask;
break;
case AuBrPerm_RW:
bad = val & AuBrRAttr_Mask;
val &= ~AuBrRAttr_Mask;
break;
}
/*
* 'unpin' attrib becomes meaningless since linux-3.18-rc1, but aufs
* does not treat it as an error, just warning.
* this is a tiny guard for the user operation.
*/
if (val & AuBrAttr_UNPIN) {
bad |= AuBrAttr_UNPIN;
val &= ~AuBrAttr_UNPIN;
}
if (unlikely(bad)) {
sz = au_do_optstr_br_attr(&attr, bad);
AuDebugOn(!sz);
pr_warn("ignored branch attribute %s\n", attr.a);
}
out:
return val;
}
void au_optstr_br_perm(au_br_perm_str_t *str, int perm)
{
au_br_perm_str_t attr;
const char *p;
char *q;
int sz;
q = str->a;
p = au_optstr(&perm, brperm);
AuDebugOn(!p || !*p);
sz = strlen(p);
memcpy(q, p, sz + 1);
q += sz;
sz = au_do_optstr_br_attr(&attr, perm);
if (sz) {
*q++ = '+';
memcpy(q, attr.a, sz + 1);
}
AuDebugOn(strlen(str->a) >= sizeof(str->a));
}
/* ---------------------------------------------------------------------- */
static match_table_t udbalevel = {
{AuOpt_UDBA_REVAL, "reval"},
{AuOpt_UDBA_NONE, "none"},
#ifdef CONFIG_AUFS_HNOTIFY
{AuOpt_UDBA_HNOTIFY, "notify"}, /* abstraction */
#ifdef CONFIG_AUFS_HFSNOTIFY
{AuOpt_UDBA_HNOTIFY, "fsnotify"},
#endif
#endif
{-1, NULL}
};
static int noinline_for_stack udba_val(char *str)
{
substring_t args[MAX_OPT_ARGS];
return match_token(str, udbalevel, args);
}
const char *au_optstr_udba(int udba)
{
return au_parser_pattern(udba, udbalevel);
}
/* ---------------------------------------------------------------------- */
static match_table_t au_wbr_create_policy = {
{AuWbrCreate_TDP, "tdp"},
{AuWbrCreate_TDP, "top-down-parent"},
{AuWbrCreate_RR, "rr"},
{AuWbrCreate_RR, "round-robin"},
{AuWbrCreate_MFS, "mfs"},
{AuWbrCreate_MFS, "most-free-space"},
{AuWbrCreate_MFSV, "mfs:%d"},
{AuWbrCreate_MFSV, "most-free-space:%d"},
/* top-down regardless the parent, and then mfs */
{AuWbrCreate_TDMFS, "tdmfs:%d"},
{AuWbrCreate_TDMFSV, "tdmfs:%d:%d"},
{AuWbrCreate_MFSRR, "mfsrr:%d"},
{AuWbrCreate_MFSRRV, "mfsrr:%d:%d"},
{AuWbrCreate_PMFS, "pmfs"},
{AuWbrCreate_PMFSV, "pmfs:%d"},
{AuWbrCreate_PMFSRR, "pmfsrr:%d"},
{AuWbrCreate_PMFSRRV, "pmfsrr:%d:%d"},
{-1, NULL}
};
static int au_wbr_mfs_wmark(substring_t *arg, char *str,
struct au_opt_wbr_create *create)
{
int err;
unsigned long long ull;
err = 0;
if (!match_u64(arg, &ull))
create->mfsrr_watermark = ull;
else {
pr_err("bad integer in %s\n", str);
err = -EINVAL;
}
return err;
}
static int au_wbr_mfs_sec(substring_t *arg, char *str,
struct au_opt_wbr_create *create)
{
int n, err;
err = 0;
if (!match_int(arg, &n) && 0 <= n && n <= AUFS_MFS_MAX_SEC)
create->mfs_second = n;
else {
pr_err("bad integer in %s\n", str);
err = -EINVAL;
}
return err;
}
static int noinline_for_stack
au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
{
int err, e;
substring_t args[MAX_OPT_ARGS];
err = match_token(str, au_wbr_create_policy, args);
create->wbr_create = err;
switch (err) {
case AuWbrCreate_MFSRRV:
case AuWbrCreate_TDMFSV:
case AuWbrCreate_PMFSRRV:
e = au_wbr_mfs_wmark(&args[0], str, create);
if (!e)
e = au_wbr_mfs_sec(&args[1], str, create);
if (unlikely(e))
err = e;
break;
case AuWbrCreate_MFSRR:
case AuWbrCreate_TDMFS:
case AuWbrCreate_PMFSRR:
e = au_wbr_mfs_wmark(&args[0], str, create);
if (unlikely(e)) {
err = e;
break;
}
/*FALLTHROUGH*/
case AuWbrCreate_MFS:
case AuWbrCreate_PMFS:
create->mfs_second = AUFS_MFS_DEF_SEC;
break;
case AuWbrCreate_MFSV:
case AuWbrCreate_PMFSV:
e = au_wbr_mfs_sec(&args[0], str, create);
if (unlikely(e))
err = e;
break;
}
return err;
}
const char *au_optstr_wbr_create(int wbr_create)
{
return au_parser_pattern(wbr_create, au_wbr_create_policy);
}
static match_table_t au_wbr_copyup_policy = {
{AuWbrCopyup_TDP, "tdp"},
{AuWbrCopyup_TDP, "top-down-parent"},
{AuWbrCopyup_BUP, "bup"},
{AuWbrCopyup_BUP, "bottom-up-parent"},
{AuWbrCopyup_BU, "bu"},
{AuWbrCopyup_BU, "bottom-up"},
{-1, NULL}
};
static int noinline_for_stack au_wbr_copyup_val(char *str)
{
substring_t args[MAX_OPT_ARGS];
return match_token(str, au_wbr_copyup_policy, args);
}
const char *au_optstr_wbr_copyup(int wbr_copyup)
{
return au_parser_pattern(wbr_copyup, au_wbr_copyup_policy);
}
/* ---------------------------------------------------------------------- */
static const int lkup_dirflags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
static void dump_opts(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:
case Opt_idel:
u.del = &opt->del;
AuDbg("del {%s, %p}\n",
u.del->pathname, u.del->h_path.dentry);
break;
case Opt_mod:
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 %u\n", opt->rdblk);
break;
case Opt_rdblk_def:
AuDbg("rdblk_def\n");
break;
case Opt_rdhash:
AuDbg("rdhash %u\n", opt->rdhash);
break;
case Opt_rdhash_def:
AuDbg("rdhash_def\n");
break;
case Opt_xino:
u.xino = &opt->xino;
AuDbg("xino {%s %pD}\n", u.xino->path, u.xino->file);
break;
case Opt_trunc_xino:
AuLabel(trunc_xino);
break;
case Opt_notrunc_xino:
AuLabel(notrunc_xino);
break;
case Opt_trunc_xino_path:
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_trunc_xib:
AuLabel(trunc_xib);
break;
case Opt_notrunc_xib:
AuLabel(notrunc_xib);
break;
case Opt_shwh:
AuLabel(shwh);
break;
case Opt_noshwh:
AuLabel(noshwh);
break;
case Opt_dirperm1:
AuLabel(dirperm1);
break;
case Opt_nodirperm1:
AuLabel(nodirperm1);
break;
case Opt_plink:
AuLabel(plink);
break;
case Opt_noplink:
AuLabel(noplink);
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_dio:
AuLabel(dio);
break;
case Opt_nodio:
AuLabel(nodio);
break;
case Opt_diropq_a:
AuLabel(diropq_a);
break;
case Opt_diropq_w:
AuLabel(diropq_w);
break;
case Opt_warn_perm:
AuLabel(warn_perm);
break;
case Opt_nowarn_perm:
AuLabel(nowarn_perm);
break;
case Opt_verbose:
AuLabel(verbose);
break;
case Opt_noverbose:
AuLabel(noverbose);
break;
case Opt_sum:
AuLabel(sum);
break;
case Opt_nosum:
AuLabel(nosum);
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:
case AuWbrCreate_PMFSV:
AuDbg("%d sec\n", u.create->mfs_second);
break;
case AuWbrCreate_MFSRR:
case AuWbrCreate_TDMFS:
AuDbg("%llu watermark\n",
u.create->mfsrr_watermark);
break;
case AuWbrCreate_MFSRRV:
case AuWbrCreate_TDMFSV:
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;
case Opt_dirren:
AuLabel(dirren);
break;
case Opt_nodirren:
AuLabel(nodirren);
break;
case Opt_acl:
AuLabel(acl);
break;
case Opt_noacl:
AuLabel(noacl);
break;
default:
BUG();
}
opt++;
}
#endif
}
void au_opts_free(struct au_opts *opts)
{
struct au_opt *opt;
opt = opts->opt;
while (opt->type != Opt_tail) {
switch (opt->type) {
case Opt_add:
case Opt_append:
case Opt_prepend:
path_put(&opt->add.path);
break;
case Opt_del:
case Opt_idel:
path_put(&opt->del.h_path);
break;
case Opt_mod:
case Opt_imod:
dput(opt->mod.h_root);
break;
case Opt_xino:
fput(opt->xino.file);
break;
}
opt++;
}
}
static int opt_add(struct au_opt *opt, char *opt_str, unsigned long sb_flags,
aufs_bindex_t bindex)
{
int err;
struct au_opt_add *add = &opt->add;
char *p;
add->bindex = bindex;
add->perm = AuBrPerm_RO;
add->pathname = opt_str;
p = strchr(opt_str, '=');
if (p) {
*p++ = 0;
if (*p)
add->perm = br_perm_val(p);
}
err = vfsub_kern_path(add->pathname, lkup_dirflags, &add->path);
if (!err) {
if (!p) {
add->perm = AuBrPerm_RO;
if (au_test_fs_rr(add->path.dentry->d_sb))
add->perm = AuBrPerm_RR;
else if (!bindex && !(sb_flags & SB_RDONLY))
add->perm = AuBrPerm_RW;
}
opt->type = Opt_add;
goto out;
}
pr_err("lookup failed %s (%d)\n", add->pathname, err);
err = -EINVAL;
out:
return err;
}
static int au_opts_parse_del(struct au_opt_del *del, substring_t args[])
{
int err;
del->pathname = args[0].from;
AuDbg("del path %s\n", del->pathname);
err = vfsub_kern_path(del->pathname, lkup_dirflags, &del->h_path);
if (unlikely(err))
pr_err("lookup failed %s (%d)\n", del->pathname, err);
return err;
}
#if 0 /* reserved for future use */
static int au_opts_parse_idel(struct super_block *sb, aufs_bindex_t bindex,
struct au_opt_del *del, substring_t args[])
{
int err;
struct dentry *root;
err = -EINVAL;
root = sb->s_root;
aufs_read_lock(root, AuLock_FLUSH);
if (bindex < 0 || au_sbbot(sb) < bindex) {
pr_err("out of bounds, %d\n", 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);
return err;
}
#endif
static int noinline_for_stack
au_opts_parse_mod(struct au_opt_mod *mod, substring_t args[])
{
int err;
struct path path;
char *p;
err = -EINVAL;
mod->path = args[0].from;
p = strchr(mod->path, '=');
if (unlikely(!p)) {
pr_err("no permission %s\n", args[0].from);
goto out;
}
*p++ = 0;
err = vfsub_kern_path(mod->path, lkup_dirflags, &path);
if (unlikely(err)) {
pr_err("lookup failed %s (%d)\n", mod->path, err);
goto out;
}
mod->perm = br_perm_val(p);
AuDbg("mod path %s, perm 0x%x, %s\n", mod->path, mod->perm, p);
mod->h_root = dget(path.dentry);
path_put(&path);
out:
return err;
}
#if 0 /* reserved for future use */
static int au_opts_parse_imod(struct super_block *sb, aufs_bindex_t bindex,
struct au_opt_mod *mod, substring_t args[])
{
int err;
struct dentry *root;
err = -EINVAL;
root = sb->s_root;
aufs_read_lock(root, AuLock_FLUSH);
if (bindex < 0 || au_sbbot(sb) < bindex) {
pr_err("out of bounds, %d\n", bindex);
goto out;
}
err = 0;
mod->perm = br_perm_val(args[1].from);
AuDbg("mod path %s, perm 0x%x, %s\n",
mod->path, mod->perm, args[1].from);
mod->h_root = dget(au_h_dptr(root, bindex));
out:
aufs_read_unlock(root, !AuLock_IR);
return err;
}
#endif
static int au_opts_parse_xino(struct super_block *sb, struct au_opt_xino *xino,
substring_t args[])
{
int err;
struct file *file;
file = au_xino_create(sb, args[0].from, /*silent*/0, /*wbrtop*/0);
err = PTR_ERR(file);
if (IS_ERR(file))
goto out;
err = -EINVAL;
if (unlikely(file->f_path.dentry->d_sb == sb)) {
fput(file);
pr_err("%s must be outside\n", args[0].from);
goto out;
}
err = 0;
xino->file = file;
xino->path = args[0].from;
out:
return err;
}
static int noinline_for_stack
au_opts_parse_xino_itrunc_path(struct super_block *sb,
struct au_opt_xino_itrunc *xino_itrunc,
substring_t args[])
{
int err;
aufs_bindex_t bbot, bindex;
struct path path;
struct dentry *root;
err = vfsub_kern_path(args[0].from, lkup_dirflags, &path);
if (unlikely(err)) {
pr_err("lookup failed %s (%d)\n", args[0].from, err);
goto out;
}
xino_itrunc->bindex = -1;
root = sb->s_root;
aufs_read_lock(root, AuLock_FLUSH);
bbot = au_sbbot(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)) {
pr_err("no such branch %s\n", args[0].from);
err = -EINVAL;
}
out:
return err;
}
/* called without aufs lock */
int au_opts_parse(struct super_block *sb, char *str, struct au_opts *opts)
{
int err, n, token;
aufs_bindex_t bindex;
unsigned char skipped;
struct dentry *root;
struct au_opt *opt, *opt_tail;
char *opt_str;
/* reduce the stack space */
union {
struct au_opt_xino_itrunc *xino_itrunc;
struct au_opt_wbr_create *create;
} u;
struct {
substring_t args[MAX_OPT_ARGS];
} *a;
err = -ENOMEM;
a = kmalloc(sizeof(*a), GFP_NOFS);
if (unlikely(!a))
goto out;
root = sb->s_root;
err = 0;
bindex = 0;
opt = opts->opt;
opt_tail = opt + opts->max_opt - 1;
opt->type = Opt_tail;
while (!err && (opt_str = strsep(&str, ",")) && *opt_str) {
err = -EINVAL;
skipped = 0;
token = match_token(opt_str, options, a->args);
switch (token) {
case Opt_br:
err = 0;
while (!err && (opt_str = strsep(&a->args[0].from, ":"))
&& *opt_str) {
err = opt_add(opt, opt_str, opts->sb_flags,
bindex++);
if (unlikely(!err && ++opt > opt_tail)) {
err = -E2BIG;
break;
}
opt->type = Opt_tail;
skipped = 1;
}
break;
case Opt_add:
if (unlikely(match_int(&a->args[0], &n))) {
pr_err("bad integer in %s\n", opt_str);
break;
}
bindex = n;
err = opt_add(opt, a->args[1].from, opts->sb_flags,
bindex);
if (!err)
opt->type = token;
break;
case Opt_append:
err = opt_add(opt, a->args[0].from, opts->sb_flags,
/*dummy bindex*/1);
if (!err)
opt->type = token;
break;
case Opt_prepend:
err = opt_add(opt, a->args[0].from, opts->sb_flags,
/*bindex*/0);
if (!err)
opt->type = token;
break;
case Opt_del:
err = au_opts_parse_del(&opt->del, a->args);
if (!err)
opt->type = token;
break;
#if 0 /* reserved for future use */
case Opt_idel:
del->pathname = "(indexed)";
if (unlikely(match_int(&args[0], &n))) {
pr_err("bad integer in %s\n", opt_str);
break;
}
err = au_opts_parse_idel(sb, n, &opt->del, a->args);
if (!err)
opt->type = token;
break;
#endif
case Opt_mod:
err = au_opts_parse_mod(&opt->mod, a->args);
if (!err)
opt->type = token;
break;
#ifdef IMOD /* reserved for future use */
case Opt_imod:
u.mod->path = "(indexed)";
if (unlikely(match_int(&a->args[0], &n))) {
pr_err("bad integer in %s\n", opt_str);
break;
}
err = au_opts_parse_imod(sb, n, &opt->mod, a->args);
if (!err)
opt->type = token;
break;
#endif
case Opt_xino:
err = au_opts_parse_xino(sb, &opt->xino, a->args);
if (!err)
opt->type = token;
break;
case Opt_trunc_xino_path:
err = au_opts_parse_xino_itrunc_path
(sb, &opt->xino_itrunc, a->args);
if (!err)
opt->type = token;
break;
case Opt_itrunc_xino:
u.xino_itrunc = &opt->xino_itrunc;
if (unlikely(match_int(&a->args[0], &n))) {
pr_err("bad integer in %s\n", opt_str);
break;
}
u.xino_itrunc->bindex = n;
aufs_read_lock(root, AuLock_FLUSH);
if (n < 0 || au_sbbot(sb) < n) {
pr_err("out of bounds, %d\n", n);
aufs_read_unlock(root, !AuLock_IR);
break;
}
aufs_read_unlock(root, !AuLock_IR);
err = 0;
opt->type = token;
break;
case Opt_dirwh:
if (unlikely(match_int(&a->args[0], &opt->dirwh)))
break;
err = 0;
opt->type = token;
break;
case Opt_rdcache:
if (unlikely(match_int(&a->args[0], &n))) {
pr_err("bad integer in %s\n", opt_str);
break;
}
if (unlikely(n > AUFS_RDCACHE_MAX)) {
pr_err("rdcache must be smaller than %d\n",
AUFS_RDCACHE_MAX);
break;
}
opt->rdcache = n;
err = 0;
opt->type = token;
break;
case Opt_rdblk:
if (unlikely(match_int(&a->args[0], &n)
|| n < 0
|| n > KMALLOC_MAX_SIZE)) {
pr_err("bad integer in %s\n", opt_str);
break;
}
if (unlikely(n && n < NAME_MAX)) {
pr_err("rdblk must be larger than %d\n",
NAME_MAX);
break;
}
opt->rdblk = n;
err = 0;
opt->type = token;
break;
case Opt_rdhash:
if (unlikely(match_int(&a->args[0], &n)
|| n < 0
|| n * sizeof(struct hlist_head)
> KMALLOC_MAX_SIZE)) {
pr_err("bad integer in %s\n", opt_str);
break;
}
opt->rdhash = n;
err = 0;
opt->type = token;
break;
case Opt_trunc_xino:
case Opt_notrunc_xino:
case Opt_noxino:
case Opt_trunc_xib:
case Opt_notrunc_xib:
case Opt_shwh:
case Opt_noshwh:
case Opt_dirperm1:
case Opt_nodirperm1:
case Opt_plink:
case Opt_noplink:
case Opt_list_plink:
case Opt_dio:
case Opt_nodio:
case Opt_diropq_a:
case Opt_diropq_w:
case Opt_warn_perm:
case Opt_nowarn_perm:
case Opt_verbose:
case Opt_noverbose:
case Opt_sum:
case Opt_nosum:
case Opt_wsum:
case Opt_rdblk_def:
case Opt_rdhash_def:
case Opt_dirren:
case Opt_nodirren:
case Opt_acl:
case Opt_noacl:
err = 0;
opt->type = token;
break;
case Opt_udba:
opt->udba = udba_val(a->args[0].from);
if (opt->udba >= 0) {
err = 0;
opt->type = token;
} else
pr_err("wrong value, %s\n", opt_str);
break;
case Opt_wbr_create:
u.create = &opt->wbr_create;
u.create->wbr_create
= au_wbr_create_val(a->args[0].from, u.create);
if (u.create->wbr_create >= 0) {
err = 0;
opt->type = token;
} else
pr_err("wrong value, %s\n", opt_str);
break;
case Opt_wbr_copyup:
opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from);
if (opt->wbr_copyup >= 0) {
err = 0;
opt->type = token;
} else
pr_err("wrong value, %s\n", opt_str);
break;
case Opt_fhsm_sec:
if (unlikely(match_int(&a->args[0], &n)
|| n < 0)) {
pr_err("bad integer in %s\n", opt_str);
break;
}
if (sysaufs_brs) {
opt->fhsm_second = n;
opt->type = token;
} else
pr_warn("ignored %s\n", opt_str);
err = 0;
break;
case Opt_ignore:
pr_warn("ignored %s\n", opt_str);
/*FALLTHROUGH*/
case Opt_ignore_silent:
skipped = 1;
err = 0;
break;
case Opt_err:
pr_err("unknown option %s\n", opt_str);
break;
}
if (!err && !skipped) {
if (unlikely(++opt > opt_tail)) {
err = -E2BIG;
opt--;
opt->type = Opt_tail;
break;
}
opt->type = Opt_tail;
}
}
au_kfree_rcu(a);
dump_opts(opts);
if (unlikely(err))
au_opts_free(opts);
out:
return err;
}
static int au_opt_wbr_create(struct super_block *sb,
struct au_opt_wbr_create *create)
{
int err;
struct au_sbinfo *sbinfo;
SiMustWriteLock(sb);
err = 1; /* handled */
sbinfo = au_sbi(sb);
if (sbinfo->si_wbr_create_ops->fin) {
err = sbinfo->si_wbr_create_ops->fin(sb);
if (!err)
err = 1;
}
sbinfo->si_wbr_create = create->wbr_create;
sbinfo->si_wbr_create_ops = au_wbr_create_ops + create->wbr_create;
switch (create->wbr_create) {
case AuWbrCreate_MFSRRV:
case AuWbrCreate_MFSRR:
case AuWbrCreate_TDMFS:
case AuWbrCreate_TDMFSV:
case AuWbrCreate_PMFSRR:
case AuWbrCreate_PMFSRRV:
sbinfo->si_wbr_mfs.mfsrr_watermark = create->mfsrr_watermark;
/*FALLTHROUGH*/
case AuWbrCreate_MFS:
case AuWbrCreate_MFSV:
case AuWbrCreate_PMFS:
case AuWbrCreate_PMFSV:
sbinfo->si_wbr_mfs.mfs_expire
= msecs_to_jiffies(create->mfs_second * MSEC_PER_SEC);
break;
}
if (sbinfo->si_wbr_create_ops->init)
sbinfo->si_wbr_create_ops->init(sb); /* ignore */
return err;
}
/*
* returns,
* plus: processed without an error
* zero: unprocessed
*/
static int au_opt_simple(struct super_block *sb, struct au_opt *opt,
struct au_opts *opts)
{
int err;
struct au_sbinfo *sbinfo;
SiMustWriteLock(sb);
err = 1; /* handled */
sbinfo = au_sbi(sb);
switch (opt->type) {
case Opt_udba:
sbinfo->si_mntflags &= ~AuOptMask_UDBA;
sbinfo->si_mntflags |= opt->udba;
opts->given_udba |= opt->udba;
break;
case Opt_plink:
au_opt_set(sbinfo->si_mntflags, PLINK);
break;
case Opt_noplink:
if (au_opt_test(sbinfo->si_mntflags, PLINK))
au_plink_put(sb, /*verbose*/1);
au_opt_clr(sbinfo->si_mntflags, PLINK);
break;
case Opt_list_plink:
if (au_opt_test(sbinfo->si_mntflags, PLINK))
au_plink_list(sb);
break;
case Opt_dio:
au_opt_set(sbinfo->si_mntflags, DIO);
au_fset_opts(opts->flags, REFRESH_DYAOP);
break;
case Opt_nodio:
au_opt_clr(sbinfo->si_mntflags, DIO);
au_fset_opts(opts->flags, REFRESH_DYAOP);
break;
case Opt_fhsm_sec:
au_fhsm_set(sbinfo, opt->fhsm_second);
break;
case Opt_diropq_a:
au_opt_set(sbinfo->si_mntflags, ALWAYS_DIROPQ);
break;
case Opt_diropq_w:
au_opt_clr(sbinfo->si_mntflags, ALWAYS_DIROPQ);
break;
case Opt_warn_perm:
au_opt_set(sbinfo->si_mntflags, WARN_PERM);
break;
case Opt_nowarn_perm:
au_opt_clr(sbinfo->si_mntflags, WARN_PERM);
break;
case Opt_verbose:
au_opt_set(sbinfo->si_mntflags, VERBOSE);
break;
case Opt_noverbose:
au_opt_clr(sbinfo->si_mntflags, VERBOSE);
break;
case Opt_sum:
au_opt_set(sbinfo->si_mntflags, SUM);
break;
case Opt_wsum:
au_opt_clr(sbinfo->si_mntflags, SUM);
au_opt_set(sbinfo->si_mntflags, SUM_W);
case Opt_nosum:
au_opt_clr(sbinfo->si_mntflags, SUM);
au_opt_clr(sbinfo->si_mntflags, SUM_W);
break;
case Opt_wbr_create:
err = au_opt_wbr_create(sb, &opt->wbr_create);
break;
case Opt_wbr_copyup:
sbinfo->si_wbr_copyup = opt->wbr_copyup;
sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + opt->wbr_copyup;
break;
case Opt_dirwh:
sbinfo->si_dirwh = opt->dirwh;
break;
case Opt_rdcache:
sbinfo->si_rdcache
= msecs_to_jiffies(opt->rdcache * MSEC_PER_SEC);
break;
case Opt_rdblk:
sbinfo->si_rdblk = opt->rdblk;
break;
case Opt_rdblk_def:
sbinfo->si_rdblk = AUFS_RDBLK_DEF;
break;
case Opt_rdhash:
sbinfo->si_rdhash = opt->rdhash;
break;
case Opt_rdhash_def:
sbinfo->si_rdhash = AUFS_RDHASH_DEF;
break;
case Opt_shwh:
au_opt_set(sbinfo->si_mntflags, SHWH);
break;
case Opt_noshwh:
au_opt_clr(sbinfo->si_mntflags, SHWH);
break;
case Opt_dirperm1:
au_opt_set(sbinfo->si_mntflags, DIRPERM1);
break;
case Opt_nodirperm1:
au_opt_clr(sbinfo->si_mntflags, DIRPERM1);
break;
case Opt_trunc_xino:
au_opt_set(sbinfo->si_mntflags, TRUNC_XINO);
break;
case Opt_notrunc_xino:
au_opt_clr(sbinfo->si_mntflags, TRUNC_XINO);
break;
case Opt_trunc_xino_path:
case Opt_itrunc_xino:
err = au_xino_trunc(sb, opt->xino_itrunc.bindex,
/*idx_begin*/0);
if (!err)
err = 1;
break;
case Opt_trunc_xib:
au_fset_opts(opts->flags, TRUNC_XIB);
break;
case Opt_notrunc_xib:
au_fclr_opts(opts->flags, TRUNC_XIB);
break;
case Opt_dirren:
err = 1;
if (!au_opt_test(sbinfo->si_mntflags, DIRREN)) {
err = au_dr_opt_set(sb);
if (!err)
err = 1;
}
if (err == 1)
au_opt_set(sbinfo->si_mntflags, DIRREN);
break;
case Opt_nodirren:
err = 1;
if (au_opt_test(sbinfo->si_mntflags, DIRREN)) {
err = au_dr_opt_clr(sb, au_ftest_opts(opts->flags,
DR_FLUSHED));
if (!err)
err = 1;
}
if (err == 1)
au_opt_clr(sbinfo->si_mntflags, DIRREN);
break;
case Opt_acl:
sb->s_flags |= SB_POSIXACL;
break;
case Opt_noacl:
sb->s_flags &= ~SB_POSIXACL;
break;
default:
err = 0;
break;
}
return err;
}
/*
* returns tri-state.
* plus: processed without an error
* zero: unprocessed
* minus: error
*/
static int au_opt_br(struct super_block *sb, struct au_opt *opt,
struct au_opts *opts)
{
int err, do_refresh;
err = 0;
switch (opt->type) {
case Opt_append:
opt->add.bindex = au_sbbot(sb) + 1;
if (opt->add.bindex < 0)
opt->add.bindex = 0;
goto add;
case Opt_prepend:
opt->add.bindex = 0;
add: /* indented label */
case Opt_add:
err = au_br_add(sb, &opt->add,
au_ftest_opts(opts->flags, REMOUNT));
if (!err) {
err = 1;
au_fset_opts(opts->flags, REFRESH);
}
break;
case Opt_del:
case Opt_idel:
err = au_br_del(sb, &opt->del,
au_ftest_opts(opts->flags, REMOUNT));
if (!err) {
err = 1;
au_fset_opts(opts->flags, TRUNC_XIB);
au_fset_opts(opts->flags, REFRESH);
}
break;
case Opt_mod:
case Opt_imod:
err = au_br_mod(sb, &opt->mod,
au_ftest_opts(opts->flags, REMOUNT),
&do_refresh);
if (!err) {
err = 1;
if (do_refresh)
au_fset_opts(opts->flags, REFRESH);
}
break;
}
return err;
}
static int au_opt_xino(struct super_block *sb, struct au_opt *opt,
struct au_opt_xino **opt_xino,
struct au_opts *opts)
{
int err;
err = 0;
switch (opt->type) {
case Opt_xino:
err = au_xino_set(sb, &opt->xino,
!!au_ftest_opts(opts->flags, REMOUNT));
if (unlikely(err))
break;
*opt_xino = &opt->xino;
break;
case Opt_noxino:
au_xino_clr(sb);
*opt_xino = (void *)-1;
break;
}
return err;
}
int au_opts_verify(struct super_block *sb, unsigned long sb_flags,
unsigned int pending)
{
int err, fhsm;
aufs_bindex_t bindex, bbot;
unsigned char do_plink, skip, do_free, can_no_dreval;
struct au_branch *br;
struct au_wbr *wbr;
struct dentry *root, *dentry;
struct inode *dir, *h_dir;
struct au_sbinfo *sbinfo;
struct au_hinode *hdir;
SiMustAnyLock(sb);
sbinfo = au_sbi(sb);
AuDebugOn(!(sbinfo->si_mntflags & AuOptMask_UDBA));
if (!(sb_flags & SB_RDONLY)) {
if (unlikely(!au_br_writable(au_sbr_perm(sb, 0))))
pr_warn("first branch should be rw\n");
if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH)))
pr_warn_once("shwh should be used with ro\n");
}
if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY)
&& !au_opt_test(sbinfo->si_mntflags, XINO))
pr_warn_once("udba=*notify requires xino\n");
if (au_opt_test(sbinfo->si_mntflags, DIRPERM1))
pr_warn_once("dirperm1 breaks the protection"
" by the permission bits on the lower branch\n");
err = 0;
fhsm = 0;
root = sb->s_root;
dir = d_inode(root);
do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK);
can_no_dreval = !!au_opt_test((sbinfo->si_mntflags | pending),
UDBA_NONE);
bbot = au_sbbot(sb);
for (bindex = 0; !err && bindex <= bbot; bindex++) {
skip = 0;
h_dir = au_h_iptr(dir, bindex);
br = au_sbr(sb, bindex);
if ((br->br_perm & AuBrAttr_ICEX)
&& !h_dir->i_op->listxattr)
br->br_perm &= ~AuBrAttr_ICEX;
#if 0
if ((br->br_perm & AuBrAttr_ICEX_SEC)
&& (au_br_sb(br)->s_flags & SB_NOSEC))
br->br_perm &= ~AuBrAttr_ICEX_SEC;
#endif
do_free = 0;
wbr = br->br_wbr;
if (wbr)
wbr_wh_read_lock(wbr);
if (!au_br_writable(br->br_perm)) {
do_free = !!wbr;
skip = (!wbr
|| (!wbr->wbr_whbase
&& !wbr->wbr_plink
&& !wbr->wbr_orph));
} else if (!au_br_wh_linkable(br->br_perm)) {
/* skip = (!br->br_whbase && !br->br_orph); */
skip = (!wbr || !wbr->wbr_whbase);
if (skip && wbr) {
if (do_plink)
skip = !!wbr->wbr_plink;
else
skip = !wbr->wbr_plink;
}
} else {
/* skip = (br->br_whbase && br->br_ohph); */
skip = (wbr && wbr->wbr_whbase);
if (skip) {
if (do_plink)
skip = !!wbr->wbr_plink;
else
skip = !wbr->wbr_plink;
}
}
if (wbr)
wbr_wh_read_unlock(wbr);
if (can_no_dreval) {
dentry = br->br_path.dentry;
spin_lock(&dentry->d_lock);
if (dentry->d_flags &
(DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE))
can_no_dreval = 0;
spin_unlock(&dentry->d_lock);
}
if (au_br_fhsm(br->br_perm)) {
fhsm++;
AuDebugOn(!br->br_fhsm);
}
if (skip)
continue;
hdir = au_hi(dir, bindex);
au_hn_inode_lock_nested(hdir, AuLsc_I_PARENT);
if (wbr)
wbr_wh_write_lock(wbr);
err = au_wh_init(br, sb);
if (wbr)
wbr_wh_write_unlock(wbr);
au_hn_inode_unlock(hdir);
if (!err && do_free) {
au_kfree_rcu(wbr);
br->br_wbr = NULL;
}
}
if (can_no_dreval)
au_fset_si(sbinfo, NO_DREVAL);
else
au_fclr_si(sbinfo, NO_DREVAL);
if (fhsm >= 2) {
au_fset_si(sbinfo, FHSM);
for (bindex = bbot; bindex >= 0; bindex--) {
br = au_sbr(sb, bindex);
if (au_br_fhsm(br->br_perm)) {
au_fhsm_set_bottom(sb, bindex);
break;
}
}
} else {
au_fclr_si(sbinfo, FHSM);
au_fhsm_set_bottom(sb, -1);
}
return err;
}
int au_opts_mount(struct super_block *sb, struct au_opts *opts)
{
int err;
unsigned int tmp;
aufs_bindex_t bindex, bbot;
struct au_opt *opt;
struct au_opt_xino *opt_xino, xino;
struct au_sbinfo *sbinfo;
struct au_branch *br;
struct inode *dir;
SiMustWriteLock(sb);
err = 0;
opt_xino = NULL;
opt = opts->opt;
while (err >= 0 && opt->type != Opt_tail)
err = au_opt_simple(sb, opt++, opts);
if (err > 0)
err = 0;
else if (unlikely(err < 0))
goto out;
/* disable xino and udba temporary */
sbinfo = au_sbi(sb);
tmp = sbinfo->si_mntflags;
au_opt_clr(sbinfo->si_mntflags, XINO);
au_opt_set_udba(sbinfo->si_mntflags, UDBA_REVAL);
opt = opts->opt;
while (err >= 0 && opt->type != Opt_tail)
err = au_opt_br(sb, opt++, opts);
if (err > 0)
err = 0;
else if (unlikely(err < 0))
goto out;
bbot = au_sbbot(sb);
if (unlikely(bbot < 0)) {
err = -EINVAL;
pr_err("no branches\n");
goto out;
}
if (au_opt_test(tmp, XINO))
au_opt_set(sbinfo->si_mntflags, XINO);
opt = opts->opt;
while (!err && opt->type != Opt_tail)
err = au_opt_xino(sb, opt++, &opt_xino, opts);
if (unlikely(err))
goto out;
err = au_opts_verify(sb, sb->s_flags, tmp);
if (unlikely(err))
goto out;
/* restore xino */
if (au_opt_test(tmp, XINO) && !opt_xino) {
xino.file = au_xino_def(sb);
err = PTR_ERR(xino.file);
if (IS_ERR(xino.file))
goto out;
err = au_xino_set(sb, &xino, /*remount*/0);
fput(xino.file);
if (unlikely(err))
goto out;
}
/* restore udba */
tmp &= AuOptMask_UDBA;
sbinfo->si_mntflags &= ~AuOptMask_UDBA;
sbinfo->si_mntflags |= tmp;
bbot = au_sbbot(sb);
for (bindex = 0; bindex <= bbot; bindex++) {
br = au_sbr(sb, bindex);
err = au_hnotify_reset_br(tmp, br, br->br_perm);
if (unlikely(err))
AuIOErr("hnotify failed on br %d, %d, ignored\n",
bindex, err);
/* go on even if err */
}
if (au_opt_test(tmp, UDBA_HNOTIFY)) {
dir = d_inode(sb->s_root);
au_hn_reset(dir, au_hi_flags(dir, /*isdir*/1) & ~AuHi_XINO);
}
out:
return err;
}
int au_opts_remount(struct super_block *sb, struct au_opts *opts)
{
int err, rerr;
unsigned char no_dreval;
struct inode *dir;
struct au_opt_xino *opt_xino;
struct au_opt *opt;
struct au_sbinfo *sbinfo;
SiMustWriteLock(sb);
err = au_dr_opt_flush(sb);
if (unlikely(err))
goto out;
au_fset_opts(opts->flags, DR_FLUSHED);
dir = d_inode(sb->s_root);
sbinfo = au_sbi(sb);
opt_xino = NULL;
opt = opts->opt;
while (err >= 0 && opt->type != Opt_tail) {
err = au_opt_simple(sb, opt, opts);
if (!err)
err = au_opt_br(sb, opt, opts);
if (!err)
err = au_opt_xino(sb, opt, &opt_xino, opts);
opt++;
}
if (err > 0)
err = 0;
AuTraceErr(err);
/* go on even err */
no_dreval = !!au_ftest_si(sbinfo, NO_DREVAL);
rerr = au_opts_verify(sb, opts->sb_flags, /*pending*/0);
if (unlikely(rerr && !err))
err = rerr;
if (no_dreval != !!au_ftest_si(sbinfo, NO_DREVAL))
au_fset_opts(opts->flags, REFRESH_IDOP);
if (au_ftest_opts(opts->flags, TRUNC_XIB)) {
rerr = au_xib_trunc(sb);
if (unlikely(rerr && !err))
err = rerr;
}
/* will be handled by the caller */
if (!au_ftest_opts(opts->flags, REFRESH)
&& (opts->given_udba
|| au_opt_test(sbinfo->si_mntflags, XINO)
|| au_ftest_opts(opts->flags, REFRESH_IDOP)
))
au_fset_opts(opts->flags, REFRESH);
AuDbg("status 0x%x\n", opts->flags);
out:
return err;
}
/* ---------------------------------------------------------------------- */
unsigned int au_opt_udba(struct super_block *sb)
{
return au_mntflags(sb) & AuOptMask_UDBA;
}