mirror of
https://github.com/linux-sunxi/meta-sunxi.git
synced 2025-04-08 09:56:47 +02:00
1141 lines
26 KiB
C
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
|
|
};
|