meta-sunxi/recipes-kernel/linux/linux/fs/aufs/dentry.c
2012-10-17 19:21:01 +02:00

1141 lines
26 KiB
C

/*
* Copyright (C) 2005-2012 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
*/
/*
* lookup and dentry operations
*/
#include <linux/namei.h>
#include "aufs.h"
static void au_h_nd(struct nameidata *h_nd, struct nameidata *nd)
{
if (nd) {
*h_nd = *nd;
/*
* gave up supporting LOOKUP_CREATE/OPEN for lower fs,
* due to whiteout and branch permission.
*/
h_nd->flags &= ~(/*LOOKUP_PARENT |*/ LOOKUP_OPEN | LOOKUP_CREATE
| LOOKUP_FOLLOW | LOOKUP_EXCL);
/* unnecessary? */
h_nd->intent.open.file = NULL;
} else
memset(h_nd, 0, sizeof(*h_nd));
}
struct au_lkup_one_args {
struct dentry **errp;
struct qstr *name;
struct dentry *h_parent;
struct au_branch *br;
struct nameidata *nd;
};
struct dentry *au_lkup_one(struct qstr *name, struct dentry *h_parent,
struct au_branch *br, struct nameidata *nd)
{
struct dentry *h_dentry;
int err;
struct nameidata h_nd;
if (au_test_fs_null_nd(h_parent->d_sb))
return vfsub_lookup_one_len(name->name, h_parent, name->len);
au_h_nd(&h_nd, nd);
h_nd.path.dentry = h_parent;
h_nd.path.mnt = br->br_mnt;
err = vfsub_name_hash(name->name, &h_nd.last, name->len);
h_dentry = ERR_PTR(err);
if (!err) {
path_get(&h_nd.path);
h_dentry = vfsub_lookup_hash(&h_nd);
path_put(&h_nd.path);
}
AuTraceErrPtr(h_dentry);
return h_dentry;
}
static void au_call_lkup_one(void *args)
{
struct au_lkup_one_args *a = args;
*a->errp = au_lkup_one(a->name, a->h_parent, a->br, a->nd);
}
#define AuLkup_ALLOW_NEG 1
#define au_ftest_lkup(flags, name) ((flags) & AuLkup_##name)
#define au_fset_lkup(flags, name) \
do { (flags) |= AuLkup_##name; } while (0)
#define au_fclr_lkup(flags, name) \
do { (flags) &= ~AuLkup_##name; } while (0)
struct au_do_lookup_args {
unsigned int flags;
mode_t type;
struct nameidata *nd;
};
/*
* returns positive/negative dentry, NULL or an error.
* NULL means whiteout-ed or not-found.
*/
static struct dentry*
au_do_lookup(struct dentry *h_parent, struct dentry *dentry,
aufs_bindex_t bindex, struct qstr *wh_name,
struct au_do_lookup_args *args)
{
struct dentry *h_dentry;
struct inode *h_inode, *inode;
struct au_branch *br;
int wh_found, opq;
unsigned char wh_able;
const unsigned char allow_neg = !!au_ftest_lkup(args->flags, ALLOW_NEG);
wh_found = 0;
br = au_sbr(dentry->d_sb, bindex);
wh_able = !!au_br_whable(br->br_perm);
if (wh_able)
wh_found = au_wh_test(h_parent, wh_name, br, /*try_sio*/0);
h_dentry = ERR_PTR(wh_found);
if (!wh_found)
goto real_lookup;
if (unlikely(wh_found < 0))
goto out;
/* We found a whiteout */
/* au_set_dbend(dentry, bindex); */
au_set_dbwh(dentry, bindex);
if (!allow_neg)
return NULL; /* success */
real_lookup:
h_dentry = au_lkup_one(&dentry->d_name, h_parent, br, args->nd);
if (IS_ERR(h_dentry))
goto out;
h_inode = h_dentry->d_inode;
if (!h_inode) {
if (!allow_neg)
goto out_neg;
} else if (wh_found
|| (args->type && args->type != (h_inode->i_mode & S_IFMT)))
goto out_neg;
if (au_dbend(dentry) <= bindex)
au_set_dbend(dentry, bindex);
if (au_dbstart(dentry) < 0 || bindex < au_dbstart(dentry))
au_set_dbstart(dentry, bindex);
au_set_h_dptr(dentry, bindex, h_dentry);
inode = dentry->d_inode;
if (!h_inode || !S_ISDIR(h_inode->i_mode) || !wh_able
|| (inode && !S_ISDIR(inode->i_mode)))
goto out; /* success */
mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
opq = au_diropq_test(h_dentry, br);
mutex_unlock(&h_inode->i_mutex);
if (opq > 0)
au_set_dbdiropq(dentry, bindex);
else if (unlikely(opq < 0)) {
au_set_h_dptr(dentry, bindex, NULL);
h_dentry = ERR_PTR(opq);
}
goto out;
out_neg:
dput(h_dentry);
h_dentry = NULL;
out:
return h_dentry;
}
static int au_test_shwh(struct super_block *sb, const struct qstr *name)
{
if (unlikely(!au_opt_test(au_mntflags(sb), SHWH)
&& !strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)))
return -EPERM;
return 0;
}
/*
* returns the number of lower positive dentries,
* otherwise an error.
* can be called at unlinking with @type is zero.
*/
int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type,
struct nameidata *nd)
{
int npositive, err;
aufs_bindex_t bindex, btail, bdiropq;
unsigned char isdir;
struct qstr whname;
struct au_do_lookup_args args = {
.flags = 0,
.type = type,
.nd = nd
};
const struct qstr *name = &dentry->d_name;
struct dentry *parent;
struct inode *inode;
err = au_test_shwh(dentry->d_sb, name);
if (unlikely(err))
goto out;
err = au_wh_name_alloc(&whname, name);
if (unlikely(err))
goto out;
inode = dentry->d_inode;
isdir = !!(inode && S_ISDIR(inode->i_mode));
if (!type)
au_fset_lkup(args.flags, ALLOW_NEG);
npositive = 0;
parent = dget_parent(dentry);
btail = au_dbtaildir(parent);
for (bindex = bstart; bindex <= btail; bindex++) {
struct dentry *h_parent, *h_dentry;
struct inode *h_inode, *h_dir;
h_dentry = au_h_dptr(dentry, bindex);
if (h_dentry) {
if (h_dentry->d_inode)
npositive++;
if (type != S_IFDIR)
break;
continue;
}
h_parent = au_h_dptr(parent, bindex);
if (!h_parent)
continue;
h_dir = h_parent->d_inode;
if (!h_dir || !S_ISDIR(h_dir->i_mode))
continue;
mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
h_dentry = au_do_lookup(h_parent, dentry, bindex, &whname,
&args);
mutex_unlock(&h_dir->i_mutex);
err = PTR_ERR(h_dentry);
if (IS_ERR(h_dentry))
goto out_parent;
au_fclr_lkup(args.flags, ALLOW_NEG);
if (au_dbwh(dentry) >= 0)
break;
if (!h_dentry)
continue;
h_inode = h_dentry->d_inode;
if (!h_inode)
continue;
npositive++;
if (!args.type)
args.type = h_inode->i_mode & S_IFMT;
if (args.type != S_IFDIR)
break;
else if (isdir) {
/* the type of lower may be different */
bdiropq = au_dbdiropq(dentry);
if (bdiropq >= 0 && bdiropq <= bindex)
break;
}
}
if (npositive) {
AuLabel(positive);
au_update_dbstart(dentry);
}
err = npositive;
if (unlikely(!au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE)
&& au_dbstart(dentry) < 0)) {
err = -EIO;
AuIOErr("both of real entry and whiteout found, %.*s, err %d\n",
AuDLNPair(dentry), err);
}
out_parent:
dput(parent);
kfree(whname.name);
out:
return err;
}
struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent,
struct au_branch *br)
{
struct dentry *dentry;
int wkq_err;
if (!au_test_h_perm_sio(parent->d_inode, MAY_EXEC))
dentry = au_lkup_one(name, parent, br, /*nd*/NULL);
else {
struct au_lkup_one_args args = {
.errp = &dentry,
.name = name,
.h_parent = parent,
.br = br,
.nd = NULL
};
wkq_err = au_wkq_wait(au_call_lkup_one, &args);
if (unlikely(wkq_err))
dentry = ERR_PTR(wkq_err);
}
return dentry;
}
/*
* lookup @dentry on @bindex which should be negative.
*/
int au_lkup_neg(struct dentry *dentry, aufs_bindex_t bindex)
{
int err;
struct dentry *parent, *h_parent, *h_dentry;
parent = dget_parent(dentry);
h_parent = au_h_dptr(parent, bindex);
h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent,
au_sbr(dentry->d_sb, bindex));
err = PTR_ERR(h_dentry);
if (IS_ERR(h_dentry))
goto out;
if (unlikely(h_dentry->d_inode)) {
err = -EIO;
AuIOErr("%.*s should be negative on b%d.\n",
AuDLNPair(h_dentry), bindex);
dput(h_dentry);
goto out;
}
err = 0;
if (bindex < au_dbstart(dentry))
au_set_dbstart(dentry, bindex);
if (au_dbend(dentry) < bindex)
au_set_dbend(dentry, bindex);
au_set_h_dptr(dentry, bindex, h_dentry);
out:
dput(parent);
return err;
}
/* ---------------------------------------------------------------------- */
/* subset of struct inode */
struct au_iattr {
unsigned long i_ino;
/* unsigned int i_nlink; */
uid_t i_uid;
gid_t i_gid;
u64 i_version;
/*
loff_t i_size;
blkcnt_t i_blocks;
*/
umode_t i_mode;
};
static void au_iattr_save(struct au_iattr *ia, struct inode *h_inode)
{
ia->i_ino = h_inode->i_ino;
/* ia->i_nlink = h_inode->i_nlink; */
ia->i_uid = h_inode->i_uid;
ia->i_gid = h_inode->i_gid;
ia->i_version = h_inode->i_version;
/*
ia->i_size = h_inode->i_size;
ia->i_blocks = h_inode->i_blocks;
*/
ia->i_mode = (h_inode->i_mode & S_IFMT);
}
static int au_iattr_test(struct au_iattr *ia, struct inode *h_inode)
{
return ia->i_ino != h_inode->i_ino
/* || ia->i_nlink != h_inode->i_nlink */
|| ia->i_uid != h_inode->i_uid
|| ia->i_gid != h_inode->i_gid
|| ia->i_version != h_inode->i_version
/*
|| ia->i_size != h_inode->i_size
|| ia->i_blocks != h_inode->i_blocks
*/
|| ia->i_mode != (h_inode->i_mode & S_IFMT);
}
static int au_h_verify_dentry(struct dentry *h_dentry, struct dentry *h_parent,
struct au_branch *br)
{
int err;
struct au_iattr ia;
struct inode *h_inode;
struct dentry *h_d;
struct super_block *h_sb;
err = 0;
memset(&ia, -1, sizeof(ia));
h_sb = h_dentry->d_sb;
h_inode = h_dentry->d_inode;
if (h_inode)
au_iattr_save(&ia, h_inode);
else if (au_test_nfs(h_sb) || au_test_fuse(h_sb))
/* nfs d_revalidate may return 0 for negative dentry */
/* fuse d_revalidate always return 0 for negative dentry */
goto out;
/* main purpose is namei.c:cached_lookup() and d_revalidate */
h_d = au_lkup_one(&h_dentry->d_name, h_parent, br, /*nd*/NULL);
err = PTR_ERR(h_d);
if (IS_ERR(h_d))
goto out;
err = 0;
if (unlikely(h_d != h_dentry
|| h_d->d_inode != h_inode
|| (h_inode && au_iattr_test(&ia, h_inode))))
err = au_busy_or_stale();
dput(h_d);
out:
AuTraceErr(err);
return err;
}
int au_h_verify(struct dentry *h_dentry, unsigned int udba, struct inode *h_dir,
struct dentry *h_parent, struct au_branch *br)
{
int err;
err = 0;
if (udba == AuOpt_UDBA_REVAL
&& !au_test_fs_remote(h_dentry->d_sb)) {
IMustLock(h_dir);
err = (h_dentry->d_parent->d_inode != h_dir);
} else if (udba != AuOpt_UDBA_NONE)
err = au_h_verify_dentry(h_dentry, h_parent, br);
return err;
}
/* ---------------------------------------------------------------------- */
static int au_do_refresh_hdentry(struct dentry *dentry, struct dentry *parent)
{
int err;
aufs_bindex_t new_bindex, bindex, bend, bwh, bdiropq;
struct au_hdentry tmp, *p, *q;
struct au_dinfo *dinfo;
struct super_block *sb;
DiMustWriteLock(dentry);
sb = dentry->d_sb;
dinfo = au_di(dentry);
bend = dinfo->di_bend;
bwh = dinfo->di_bwh;
bdiropq = dinfo->di_bdiropq;
p = dinfo->di_hdentry + dinfo->di_bstart;
for (bindex = dinfo->di_bstart; bindex <= bend; bindex++, p++) {
if (!p->hd_dentry)
continue;
new_bindex = au_br_index(sb, p->hd_id);
if (new_bindex == bindex)
continue;
if (dinfo->di_bwh == bindex)
bwh = new_bindex;
if (dinfo->di_bdiropq == bindex)
bdiropq = new_bindex;
if (new_bindex < 0) {
au_hdput(p);
p->hd_dentry = NULL;
continue;
}
/* swap two lower dentries, and loop again */
q = dinfo->di_hdentry + new_bindex;
tmp = *q;
*q = *p;
*p = tmp;
if (tmp.hd_dentry) {
bindex--;
p--;
}
}
dinfo->di_bwh = -1;
if (bwh >= 0 && bwh <= au_sbend(sb) && au_sbr_whable(sb, bwh))
dinfo->di_bwh = bwh;
dinfo->di_bdiropq = -1;
if (bdiropq >= 0
&& bdiropq <= au_sbend(sb)
&& au_sbr_whable(sb, bdiropq))
dinfo->di_bdiropq = bdiropq;
err = -EIO;
dinfo->di_bstart = -1;
dinfo->di_bend = -1;
bend = au_dbend(parent);
p = dinfo->di_hdentry;
for (bindex = 0; bindex <= bend; bindex++, p++)
if (p->hd_dentry) {
dinfo->di_bstart = bindex;
break;
}
if (dinfo->di_bstart >= 0) {
p = dinfo->di_hdentry + bend;
for (bindex = bend; bindex >= 0; bindex--, p--)
if (p->hd_dentry) {
dinfo->di_bend = bindex;
err = 0;
break;
}
}
return err;
}
static void au_do_hide(struct dentry *dentry)
{
struct inode *inode;
inode = dentry->d_inode;
if (inode) {
if (!S_ISDIR(inode->i_mode)) {
if (inode->i_nlink && !d_unhashed(dentry))
drop_nlink(inode);
} else {
clear_nlink(inode);
/* stop next lookup */
inode->i_flags |= S_DEAD;
}
smp_mb(); /* necessary? */
}
d_drop(dentry);
}
static int au_hide_children(struct dentry *parent)
{
int err, i, j, ndentry;
struct au_dcsub_pages dpages;
struct au_dpage *dpage;
struct dentry *dentry;
err = au_dpages_init(&dpages, GFP_NOFS);
if (unlikely(err))
goto out;
err = au_dcsub_pages(&dpages, parent, NULL, NULL);
if (unlikely(err))
goto out_dpages;
/* in reverse order */
for (i = dpages.ndpage - 1; i >= 0; i--) {
dpage = dpages.dpages + i;
ndentry = dpage->ndentry;
for (j = ndentry - 1; j >= 0; j--) {
dentry = dpage->dentries[j];
if (dentry != parent)
au_do_hide(dentry);
}
}
out_dpages:
au_dpages_free(&dpages);
out:
return err;
}
static void au_hide(struct dentry *dentry)
{
int err;
struct inode *inode;
AuDbgDentry(dentry);
inode = dentry->d_inode;
if (inode && S_ISDIR(inode->i_mode)) {
/* shrink_dcache_parent(dentry); */
err = au_hide_children(dentry);
if (unlikely(err))
AuIOErr("%.*s, failed hiding children, ignored %d\n",
AuDLNPair(dentry), err);
}
au_do_hide(dentry);
}
/*
* By adding a dirty branch, a cached dentry may be affected in various ways.
*
* a dirty branch is added
* - on the top of layers
* - in the middle of layers
* - to the bottom of layers
*
* on the added branch there exists
* - a whiteout
* - a diropq
* - a same named entry
* + exist
* * negative --> positive
* * positive --> positive
* - type is unchanged
* - type is changed
* + doesn't exist
* * negative --> negative
* * positive --> negative (rejected by au_br_del() for non-dir case)
* - none
*/
static int au_refresh_by_dinfo(struct dentry *dentry, struct au_dinfo *dinfo,
struct au_dinfo *tmp)
{
int err;
aufs_bindex_t bindex, bend;
struct {
struct dentry *dentry;
struct inode *inode;
mode_t mode;
} orig_h, tmp_h;
struct au_hdentry *hd;
struct inode *inode, *h_inode;
struct dentry *h_dentry;
err = 0;
AuDebugOn(dinfo->di_bstart < 0);
orig_h.dentry = dinfo->di_hdentry[dinfo->di_bstart].hd_dentry;
orig_h.inode = orig_h.dentry->d_inode;
orig_h.mode = 0;
if (orig_h.inode)
orig_h.mode = orig_h.inode->i_mode & S_IFMT;
memset(&tmp_h, 0, sizeof(tmp_h));
if (tmp->di_bstart >= 0) {
tmp_h.dentry = tmp->di_hdentry[tmp->di_bstart].hd_dentry;
tmp_h.inode = tmp_h.dentry->d_inode;
if (tmp_h.inode)
tmp_h.mode = tmp_h.inode->i_mode & S_IFMT;
}
inode = dentry->d_inode;
if (!orig_h.inode) {
AuDbg("nagative originally\n");
if (inode) {
au_hide(dentry);
goto out;
}
AuDebugOn(inode);
AuDebugOn(dinfo->di_bstart != dinfo->di_bend);
AuDebugOn(dinfo->di_bdiropq != -1);
if (!tmp_h.inode) {
AuDbg("negative --> negative\n");
/* should have only one negative lower */
if (tmp->di_bstart >= 0
&& tmp->di_bstart < dinfo->di_bstart) {
AuDebugOn(tmp->di_bstart != tmp->di_bend);
AuDebugOn(dinfo->di_bstart != dinfo->di_bend);
au_set_h_dptr(dentry, dinfo->di_bstart, NULL);
au_di_cp(dinfo, tmp);
hd = tmp->di_hdentry + tmp->di_bstart;
au_set_h_dptr(dentry, tmp->di_bstart,
dget(hd->hd_dentry));
}
au_dbg_verify_dinode(dentry);
} else {
AuDbg("negative --> positive\n");
/*
* similar to the behaviour of creating with bypassing
* aufs.
* unhash it in order to force an error in the
* succeeding create operation.
* we should not set S_DEAD here.
*/
d_drop(dentry);
/* au_di_swap(tmp, dinfo); */
au_dbg_verify_dinode(dentry);
}
} else {
AuDbg("positive originally\n");
/* inode may be NULL */
AuDebugOn(inode && (inode->i_mode & S_IFMT) != orig_h.mode);
if (!tmp_h.inode) {
AuDbg("positive --> negative\n");
/* or bypassing aufs */
au_hide(dentry);
if (tmp->di_bwh >= 0 && tmp->di_bwh <= dinfo->di_bstart)
dinfo->di_bwh = tmp->di_bwh;
if (inode)
err = au_refresh_hinode_self(inode);
au_dbg_verify_dinode(dentry);
} else if (orig_h.mode == tmp_h.mode) {
AuDbg("positive --> positive, same type\n");
if (!S_ISDIR(orig_h.mode)
&& dinfo->di_bstart > tmp->di_bstart) {
/*
* similar to the behaviour of removing and
* creating.
*/
au_hide(dentry);
if (inode)
err = au_refresh_hinode_self(inode);
au_dbg_verify_dinode(dentry);
} else {
/* fill empty slots */
if (dinfo->di_bstart > tmp->di_bstart)
dinfo->di_bstart = tmp->di_bstart;
if (dinfo->di_bend < tmp->di_bend)
dinfo->di_bend = tmp->di_bend;
dinfo->di_bwh = tmp->di_bwh;
dinfo->di_bdiropq = tmp->di_bdiropq;
hd = tmp->di_hdentry;
bend = dinfo->di_bend;
for (bindex = tmp->di_bstart; bindex <= bend;
bindex++) {
if (au_h_dptr(dentry, bindex))
continue;
h_dentry = hd[bindex].hd_dentry;
if (!h_dentry)
continue;
h_inode = h_dentry->d_inode;
AuDebugOn(!h_inode);
AuDebugOn(orig_h.mode
!= (h_inode->i_mode
& S_IFMT));
au_set_h_dptr(dentry, bindex,
dget(h_dentry));
}
err = au_refresh_hinode(inode, dentry);
au_dbg_verify_dinode(dentry);
}
} else {
AuDbg("positive --> positive, different type\n");
/* similar to the behaviour of removing and creating */
au_hide(dentry);
if (inode)
err = au_refresh_hinode_self(inode);
au_dbg_verify_dinode(dentry);
}
}
out:
return err;
}
int au_refresh_dentry(struct dentry *dentry, struct dentry *parent)
{
int err, ebrange;
unsigned int sigen;
struct au_dinfo *dinfo, *tmp;
struct super_block *sb;
struct inode *inode;
DiMustWriteLock(dentry);
AuDebugOn(IS_ROOT(dentry));
AuDebugOn(!parent->d_inode);
sb = dentry->d_sb;
inode = dentry->d_inode;
sigen = au_sigen(sb);
err = au_digen_test(parent, sigen);
if (unlikely(err))
goto out;
dinfo = au_di(dentry);
err = au_di_realloc(dinfo, au_sbend(sb) + 1);
if (unlikely(err))
goto out;
ebrange = au_dbrange_test(dentry);
if (!ebrange)
ebrange = au_do_refresh_hdentry(dentry, parent);
if (d_unhashed(dentry) || ebrange) {
AuDebugOn(au_dbstart(dentry) < 0 && au_dbend(dentry) >= 0);
if (inode)
err = au_refresh_hinode_self(inode);
au_dbg_verify_dinode(dentry);
if (!err)
goto out_dgen; /* success */
goto out;
}
/* temporary dinfo */
AuDbgDentry(dentry);
err = -ENOMEM;
tmp = au_di_alloc(sb, AuLsc_DI_TMP);
if (unlikely(!tmp))
goto out;
au_di_swap(tmp, dinfo);
/* returns the number of positive dentries */
/*
* if current working dir is removed, it returns an error.
* but the dentry is legal.
*/
err = au_lkup_dentry(dentry, /*bstart*/0, /*type*/0, /*nd*/NULL);
AuDbgDentry(dentry);
au_di_swap(tmp, dinfo);
if (err == -ENOENT)
err = 0;
if (err >= 0) {
/* compare/refresh by dinfo */
AuDbgDentry(dentry);
err = au_refresh_by_dinfo(dentry, dinfo, tmp);
au_dbg_verify_dinode(dentry);
AuTraceErr(err);
}
au_rw_write_unlock(&tmp->di_rwsem);
au_di_free(tmp);
if (unlikely(err))
goto out;
out_dgen:
au_update_digen(dentry);
out:
if (unlikely(err && !(dentry->d_flags & DCACHE_NFSFS_RENAMED))) {
AuIOErr("failed refreshing %.*s, %d\n",
AuDLNPair(dentry), err);
AuDbgDentry(dentry);
}
AuTraceErr(err);
return err;
}
static noinline_for_stack
int au_do_h_d_reval(struct dentry *h_dentry, struct nameidata *nd,
struct dentry *dentry, aufs_bindex_t bindex)
{
int err, valid;
int (*reval)(struct dentry *, struct nameidata *);
err = 0;
if (!(h_dentry->d_flags & DCACHE_OP_REVALIDATE))
goto out;
reval = h_dentry->d_op->d_revalidate;
AuDbg("b%d\n", bindex);
if (au_test_fs_null_nd(h_dentry->d_sb))
/* it may return tri-state */
valid = reval(h_dentry, NULL);
else {
struct nameidata h_nd;
int locked;
struct dentry *parent;
au_h_nd(&h_nd, nd);
parent = nd->path.dentry;
locked = (nd && nd->path.dentry != dentry);
if (locked)
di_read_lock_parent(parent, AuLock_IR);
BUG_ON(bindex > au_dbend(parent));
h_nd.path.dentry = au_h_dptr(parent, bindex);
BUG_ON(!h_nd.path.dentry);
h_nd.path.mnt = au_sbr(parent->d_sb, bindex)->br_mnt;
path_get(&h_nd.path);
valid = reval(h_dentry, &h_nd);
path_put(&h_nd.path);
if (locked)
di_read_unlock(parent, AuLock_IR);
}
if (unlikely(valid < 0))
err = valid;
else if (!valid)
err = -EINVAL;
out:
AuTraceErr(err);
return err;
}
/* todo: remove this */
static int h_d_revalidate(struct dentry *dentry, struct inode *inode,
struct nameidata *nd, int do_udba)
{
int err;
umode_t mode, h_mode;
aufs_bindex_t bindex, btail, bstart, ibs, ibe;
unsigned char plus, unhashed, is_root, h_plus;
struct inode *h_inode, *h_cached_inode;
struct dentry *h_dentry;
struct qstr *name, *h_name;
err = 0;
plus = 0;
mode = 0;
ibs = -1;
ibe = -1;
unhashed = !!d_unhashed(dentry);
is_root = !!IS_ROOT(dentry);
name = &dentry->d_name;
/*
* Theoretically, REVAL test should be unnecessary in case of
* {FS,I}NOTIFY.
* But {fs,i}notify doesn't fire some necessary events,
* IN_ATTRIB for atime/nlink/pageio
* IN_DELETE for NFS dentry
* Let's do REVAL test too.
*/
if (do_udba && inode) {
mode = (inode->i_mode & S_IFMT);
plus = (inode->i_nlink > 0);
ibs = au_ibstart(inode);
ibe = au_ibend(inode);
}
bstart = au_dbstart(dentry);
btail = bstart;
if (inode && S_ISDIR(inode->i_mode))
btail = au_dbtaildir(dentry);
for (bindex = bstart; bindex <= btail; bindex++) {
h_dentry = au_h_dptr(dentry, bindex);
if (!h_dentry)
continue;
AuDbg("b%d, %.*s\n", bindex, AuDLNPair(h_dentry));
spin_lock(&h_dentry->d_lock);
h_name = &h_dentry->d_name;
if (unlikely(do_udba
&& !is_root
&& (unhashed != !!d_unhashed(h_dentry)
|| name->len != h_name->len
|| memcmp(name->name, h_name->name, name->len))
)) {
AuDbg("unhash 0x%x 0x%x, %.*s %.*s\n",
unhashed, d_unhashed(h_dentry),
AuDLNPair(dentry), AuDLNPair(h_dentry));
spin_unlock(&h_dentry->d_lock);
goto err;
}
spin_unlock(&h_dentry->d_lock);
err = au_do_h_d_reval(h_dentry, nd, dentry, bindex);
if (unlikely(err))
/* do not goto err, to keep the errno */
break;
/* todo: plink too? */
if (!do_udba)
continue;
/* UDBA tests */
h_inode = h_dentry->d_inode;
if (unlikely(!!inode != !!h_inode))
goto err;
h_plus = plus;
h_mode = mode;
h_cached_inode = h_inode;
if (h_inode) {
h_mode = (h_inode->i_mode & S_IFMT);
h_plus = (h_inode->i_nlink > 0);
}
if (inode && ibs <= bindex && bindex <= ibe)
h_cached_inode = au_h_iptr(inode, bindex);
if (unlikely(plus != h_plus
|| mode != h_mode
|| h_cached_inode != h_inode))
goto err;
continue;
err:
err = -EINVAL;
break;
}
return err;
}
/* todo: consolidate with do_refresh() and au_reval_for_attr() */
static int simple_reval_dpath(struct dentry *dentry, unsigned int sigen)
{
int err;
struct dentry *parent;
if (!au_digen_test(dentry, sigen))
return 0;
parent = dget_parent(dentry);
di_read_lock_parent(parent, AuLock_IR);
AuDebugOn(au_digen_test(parent, sigen));
au_dbg_verify_gen(parent, sigen);
err = au_refresh_dentry(dentry, parent);
di_read_unlock(parent, AuLock_IR);
dput(parent);
AuTraceErr(err);
return err;
}
int au_reval_dpath(struct dentry *dentry, unsigned int sigen)
{
int err;
struct dentry *d, *parent;
struct inode *inode;
if (!au_ftest_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR))
return simple_reval_dpath(dentry, sigen);
/* slow loop, keep it simple and stupid */
/* cf: au_cpup_dirs() */
err = 0;
parent = NULL;
while (au_digen_test(dentry, sigen)) {
d = dentry;
while (1) {
dput(parent);
parent = dget_parent(d);
if (!au_digen_test(parent, sigen))
break;
d = parent;
}
inode = d->d_inode;
if (d != dentry)
di_write_lock_child2(d);
/* someone might update our dentry while we were sleeping */
if (au_digen_test(d, sigen)) {
/*
* todo: consolidate with simple_reval_dpath(),
* do_refresh() and au_reval_for_attr().
*/
di_read_lock_parent(parent, AuLock_IR);
err = au_refresh_dentry(d, parent);
di_read_unlock(parent, AuLock_IR);
}
if (d != dentry)
di_write_unlock(d);
dput(parent);
if (unlikely(err))
break;
}
return err;
}
/*
* if valid returns 1, otherwise 0.
*/
static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
{
int valid, err;
unsigned int sigen;
unsigned char do_udba;
struct super_block *sb;
struct inode *inode;
/* todo: support rcu-walk? */
if (nd && (nd->flags & LOOKUP_RCU))
return -ECHILD;
valid = 0;
if (unlikely(!au_di(dentry)))
goto out;
inode = dentry->d_inode;
if (inode && is_bad_inode(inode))
goto out;
valid = 1;
sb = dentry->d_sb;
/*
* todo: very ugly
* i_mutex of parent dir may be held,
* but we should not return 'invalid' due to busy.
*/
err = aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW | AuLock_NOPLM);
if (unlikely(err)) {
valid = err;
AuTraceErr(err);
goto out;
}
if (unlikely(au_dbrange_test(dentry))) {
err = -EINVAL;
AuTraceErr(err);
goto out_dgrade;
}
sigen = au_sigen(sb);
if (au_digen_test(dentry, sigen)) {
AuDebugOn(IS_ROOT(dentry));
err = au_reval_dpath(dentry, sigen);
if (unlikely(err)) {
AuTraceErr(err);
goto out_dgrade;
}
}
di_downgrade_lock(dentry, AuLock_IR);
err = -EINVAL;
if (inode && (IS_DEADDIR(inode) || !inode->i_nlink))
goto out_inval;
do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE);
if (do_udba && inode) {
aufs_bindex_t bstart = au_ibstart(inode);
struct inode *h_inode;
if (bstart >= 0) {
h_inode = au_h_iptr(inode, bstart);
if (h_inode && au_test_higen(inode, h_inode))
goto out_inval;
}
}
err = h_d_revalidate(dentry, inode, nd, do_udba);
if (unlikely(!err && do_udba && au_dbstart(dentry) < 0)) {
err = -EIO;
AuDbg("both of real entry and whiteout found, %.*s, err %d\n",
AuDLNPair(dentry), err);
}
goto out_inval;
out_dgrade:
di_downgrade_lock(dentry, AuLock_IR);
out_inval:
aufs_read_unlock(dentry, AuLock_IR);
AuTraceErr(err);
valid = !err;
out:
if (!valid) {
AuDbg("%.*s invalid, %d\n", AuDLNPair(dentry), valid);
d_drop(dentry);
}
return valid;
}
static void aufs_d_release(struct dentry *dentry)
{
if (au_di(dentry)) {
au_di_fin(dentry);
au_hn_di_reinit(dentry);
}
}
const struct dentry_operations aufs_dop = {
.d_revalidate = aufs_d_revalidate,
.d_release = aufs_d_release
};