// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2021 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 /* a distribution requires */
#include
#include "aufs.h"
/* ---------------------------------------------------------------------- */
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;
}
int au_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}
};
int au_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;
}
int 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}
};
int 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);
}
/* ---------------------------------------------------------------------- */
int au_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 = au_br_perm_val(p);
}
err = vfsub_kern_path(add->pathname, AuOpt_LkupDirFlags, &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_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:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, PLINK);
else {
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:
if (opt->tf) {
au_opt_set(sbinfo->si_mntflags, DIO);
au_fset_opts(opts->flags, REFRESH_DYAOP);
} else {
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:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, WARN_PERM);
else
au_opt_clr(sbinfo->si_mntflags, WARN_PERM);
break;
case Opt_verbose:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, VERBOSE);
else
au_opt_clr(sbinfo->si_mntflags, VERBOSE);
break;
case Opt_sum:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, SUM);
else {
au_opt_clr(sbinfo->si_mntflags, SUM);
au_opt_clr(sbinfo->si_mntflags, SUM_W);
}
break;
case Opt_wsum:
au_opt_clr(sbinfo->si_mntflags, SUM);
au_opt_set(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_rdhash:
sbinfo->si_rdhash = opt->rdhash;
break;
case Opt_shwh:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, SHWH);
else
au_opt_clr(sbinfo->si_mntflags, SHWH);
break;
case Opt_dirperm1:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, DIRPERM1);
else
au_opt_clr(sbinfo->si_mntflags, DIRPERM1);
break;
case Opt_trunc_xino:
if (opt->tf)
au_opt_set(sbinfo->si_mntflags, TRUNC_XINO);
else
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:
if (opt->tf)
au_fset_opts(opts->flags, TRUNC_XIB);
else
au_fclr_opts(opts->flags, TRUNC_XIB);
break;
case Opt_dirren:
err = 1;
if (opt->tf) {
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);
} else {
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:
if (opt->tf)
sb->s_flags |= SB_POSIXACL;
else
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;
/* Always goto add, not fallthrough */
case Opt_prepend:
opt->add.bindex = 0;
fallthrough;
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 (!err)
*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 /* untested */
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;
}