blob: 7cac1edcb68469d05258fba4c7364b1aefded5f1 [file] [log] [blame]
/*
* Copyright (C) 2005-2013 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* mount options/flags
*/
#include <linux/namei.h>
#include <linux/types.h> /* a distribution requires */
#include <linux/parser.h>
#include "aufs.h"
/* ---------------------------------------------------------------------- */
enum {
Opt_br,
Opt_add, Opt_del, Opt_mod, Opt_reorder, Opt_append, Opt_prepend,
Opt_idel, Opt_imod, Opt_ireorder,
Opt_dirwh, Opt_rdcache, Opt_rdblk, Opt_rdhash, Opt_rendir,
Opt_rdblk_def, Opt_rdhash_def,
Opt_xino, Opt_zxino, 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_lock, Opt_unlock, */
Opt_cmd, Opt_cmd_args,
Opt_diropq_a, Opt_diropq_w,
Opt_warn_perm, Opt_nowarn_perm,
Opt_wbr_copyup, Opt_wbr_create,
Opt_refrof, Opt_norefrof,
Opt_verbose, Opt_noverbose,
Opt_sum, Opt_nosum, Opt_wsum,
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"},
{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, "coo=%s"},
{Opt_ignore_silent, "nodlgt"},
{Opt_ignore_silent, "nodirperm1"},
{Opt_ignore_silent, "clean_plink"},
#ifdef CONFIG_AUFS_SHWH
{Opt_shwh, "shwh"},
#endif
{Opt_noshwh, "noshwh"},
{Opt_rendir, "rendir=%d"},
{Opt_refrof, "refrof"},
{Opt_norefrof, "norefrof"},
{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"},
/* 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, struct match_token *token)
{
while (token->pattern) {
if (token->token == val)
return token->pattern;
token++;
}
BUG();
return "??";
}
/* ---------------------------------------------------------------------- */
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 = {
{AuBrAttr_UNPIN, AUFS_BRATTR_UNPIN},
{AuBrRAttr_WH, AUFS_BRRATTR_WH},
{AuBrWAttr_NoLinkWH, AUFS_BRWATTR_NLWH},
{0, NULL}
};
#define AuBrStr_LONGEST AUFS_BRPERM_RW \
"+" AUFS_BRATTR_UNPIN \
"+" AUFS_BRWATTR_NLWH
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)
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 noinline_for_stack br_perm_val(char *perm)
{
int val;
char *p, *q;
substring_t args[MAX_OPT_ARGS];
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;
p++;
while (1) {
q = strchr(p, '+');
if (q)
*q = 0;
val |= br_attr_val(p, brattr, args);
if (q) {
*q = '+';
p = q + 1;
} else
break;
}
switch (val & AuBrPerm_Mask) {
case AuBrPerm_RO:
case AuBrPerm_RR:
if (unlikely(val & AuBrWAttr_NoLinkWH)) {
pr_warn("ignored branch attribute %s\n",
AUFS_BRWATTR_NLWH);
val &= ~AuBrWAttr_NoLinkWH;
}
break;
case AuBrPerm_RW:
if (unlikely(val & AuBrRAttr_WH)) {
pr_warn("ignored branch attribute %s\n",
AUFS_BRRATTR_WH);
val &= ~AuBrRAttr_WH;
}
break;
}
out:
return val;
}
/* Caller should free the return value */
char *au_optstr_br_perm(int brperm)
{
char *p, a[sizeof(AuBrStr_LONGEST)];
int sz;
#define SetPerm(str) do { \
sz = sizeof(str); \
memcpy(a, str, sz); \
p = a + sz - 1; \
} while (0)
#define AppendAttr(flag, str) do { \
if (brperm & flag) { \
sz = sizeof(str); \
*p++ = '+'; \
memcpy(p, str, sz); \
p += sz - 1; \
} \
} while (0)
switch (brperm & AuBrPerm_Mask) {
case AuBrPerm_RO:
SetPerm(AUFS_BRPERM_RO);
break;
case AuBrPerm_RR:
SetPerm(AUFS_BRPERM_RR);
break;
case AuBrPerm_RW:
SetPerm(AUFS_BRPERM_RW);
break;
default:
AuDebugOn(1);
}
AppendAttr(AuBrAttr_UNPIN, AUFS_BRATTR_UNPIN);
AppendAttr(AuBrRAttr_WH, AUFS_BRRATTR_WH);
AppendAttr(AuBrWAttr_NoLinkWH, AUFS_BRWATTR_NLWH);
AuDebugOn(strlen(a) >= sizeof(a));
return kstrdup(a, GFP_NOFS);
#undef SetPerm
#undef AppendAttr
}
/* ---------------------------------------------------------------------- */
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, (void *)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"},
{AuWbrCreate_MFSRR, "mfsrr:%d"},
{AuWbrCreate_MFSRRV, "mfsrr:%d:%d"},
{AuWbrCreate_PMFS, "pmfs"},
{AuWbrCreate_PMFSV, "pmfs:%d"},
{-1, NULL}
};
/*
* cf. linux/lib/parser.c and cmdline.c
* gave up calling memparse() since it uses simple_strtoull() instead of
* kstrto...().
*/
static int noinline_for_stack
au_match_ull(substring_t *s, unsigned long long *result)
{
int err;
unsigned int len;
char a[32];
err = -ERANGE;
len = s->to - s->from;
if (len + 1 <= sizeof(a)) {
memcpy(a, s->from, len);
a[len] = '\0';
err = kstrtoull(a, 0, result);
}
return err;
}
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 (!au_match_ull(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:
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:
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, (void *)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, (void *)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 %.*s}\n",
u.xino->path,
AuDLNPair(u.xino->file->f_dentry));
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_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_refrof:
AuLabel(refrof);
break;
case Opt_norefrof:
AuLabel(norefrof);
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:
AuDbg("%llu watermark\n",
u.create->mfsrr_watermark);
break;
case AuWbrCreate_MFSRRV:
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;
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 & MS_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_sbend(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 permssion %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_sbend(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);
err = PTR_ERR(file);
if (IS_ERR(file))
goto out;
err = -EINVAL;
if (unlikely(file->f_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 bend, 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);
bend = au_sbend(sb);
for (bindex = 0; bindex <= bend; 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_sbend(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_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_refrof:
case Opt_norefrof:
case Opt_verbose:
case Opt_noverbose:
case Opt_sum:
case Opt_nosum:
case Opt_wsum:
case Opt_rdblk_def:
case Opt_rdhash_def:
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_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;
}
}
kfree(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:
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_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_refrof:
au_opt_set(sbinfo->si_mntflags, REFROF);
break;
case Opt_norefrof:
au_opt_clr(sbinfo->si_mntflags, REFROF);
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_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);
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;
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_sbend(sb) + 1;
if (opt->add.bindex < 0)
opt->add.bindex = 0;
goto add;
case Opt_prepend:
opt->add.bindex = 0;
add:
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;
aufs_bindex_t bend, bindex;
struct dentry *root, *parent, *h_root;
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;
au_xino_brid_set(sb, -1);
/* safe d_parent access */
parent = opt->xino.file->f_dentry->d_parent;
root = sb->s_root;
bend = au_sbend(sb);
for (bindex = 0; bindex <= bend; bindex++) {
h_root = au_h_dptr(root, bindex);
if (h_root == parent) {
au_xino_brid_set(sb, au_sbr_id(sb, bindex));
break;
}
}
break;
case Opt_noxino:
au_xino_clr(sb);
au_xino_brid_set(sb, -1);
*opt_xino = (void *)-1;
break;
}
return err;
}
int au_opts_verify(struct super_block *sb, unsigned long sb_flags,
unsigned int pending)
{
int err;
aufs_bindex_t bindex, bend;
unsigned char do_plink, skip, do_free;
struct au_branch *br;
struct au_wbr *wbr;
struct dentry *root;
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 & MS_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("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("udba=*notify requires xino\n");
err = 0;
root = sb->s_root;
dir = root->d_inode;
do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK);
bend = au_sbend(sb);
for (bindex = 0; !err && bindex <= bend; bindex++) {
skip = 0;
h_dir = au_h_iptr(dir, bindex);
br = au_sbr(sb, bindex);
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 (skip)
continue;
hdir = au_hi(dir, bindex);
au_hn_imtx_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_imtx_unlock(hdir);
if (!err && do_free) {
kfree(wbr);
br->br_wbr = NULL;
}
}
return err;
}
int au_opts_mount(struct super_block *sb, struct au_opts *opts)
{
int err;
unsigned int tmp;
aufs_bindex_t bindex, bend;
struct au_opt *opt;
struct au_opt_xino *opt_xino, xino;
struct au_sbinfo *sbinfo;
struct au_branch *br;
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;
bend = au_sbend(sb);
if (unlikely(bend < 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;
bend = au_sbend(sb);
for (bindex = 0; bindex <= bend; 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)) {
struct inode *dir = sb->s_root->d_inode;
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;
struct inode *dir;
struct au_opt_xino *opt_xino;
struct au_opt *opt;
struct au_sbinfo *sbinfo;
SiMustWriteLock(sb);
dir = sb->s_root->d_inode;
sbinfo = au_sbi(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)
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 */
rerr = au_opts_verify(sb, opts->sb_flags, /*pending*/0);
if (unlikely(rerr && !err))
err = rerr;
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_fset_opts(opts->flags, REFRESH);
AuDbg("status 0x%x\n", opts->flags);
return err;
}
/* ---------------------------------------------------------------------- */
unsigned int au_opt_udba(struct super_block *sb)
{
return au_mntflags(sb) & AuOptMask_UDBA;
}