forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1154 lines
27 KiB
1154 lines
27 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (C) 2005-2020 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 <http://www.gnu.org/licenses/>. |
|
*/ |
|
|
|
/* |
|
* lookup and dentry operations |
|
*/ |
|
|
|
#include <linux/iversion.h> |
|
#include <linux/namei.h> |
|
#include "aufs.h" |
|
|
|
/* |
|
* 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 au_do_lookup_args *args) |
|
{ |
|
struct dentry *h_dentry; |
|
struct inode *h_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); |
|
const unsigned char ignore_perm = !!au_ftest_lkup(args->flags, |
|
IGNORE_PERM); |
|
|
|
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, &args->whname, ignore_perm); |
|
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_dbbot(dentry, bindex); */ |
|
au_set_dbwh(dentry, bindex); |
|
if (!allow_neg) |
|
return NULL; /* success */ |
|
|
|
real_lookup: |
|
if (!ignore_perm) |
|
h_dentry = vfsub_lkup_one(args->name, h_parent); |
|
else |
|
h_dentry = au_sio_lkup_one(args->name, h_parent); |
|
if (IS_ERR(h_dentry)) { |
|
if (PTR_ERR(h_dentry) == -ENAMETOOLONG |
|
&& !allow_neg) |
|
h_dentry = NULL; |
|
goto out; |
|
} |
|
|
|
h_inode = d_inode(h_dentry); |
|
if (d_is_negative(h_dentry)) { |
|
if (!allow_neg) |
|
goto out_neg; |
|
} else if (wh_found |
|
|| (args->type && args->type != (h_inode->i_mode & S_IFMT))) |
|
goto out_neg; |
|
else if (au_ftest_lkup(args->flags, DIRREN) |
|
/* && h_inode */ |
|
&& !au_dr_lkup_h_ino(args, bindex, h_inode->i_ino)) { |
|
AuDbg("b%d %pd ignored hi%llu\n", bindex, h_dentry, |
|
(unsigned long long)h_inode->i_ino); |
|
goto out_neg; |
|
} |
|
|
|
if (au_dbbot(dentry) <= bindex) |
|
au_set_dbbot(dentry, bindex); |
|
if (au_dbtop(dentry) < 0 || bindex < au_dbtop(dentry)) |
|
au_set_dbtop(dentry, bindex); |
|
au_set_h_dptr(dentry, bindex, h_dentry); |
|
|
|
if (!d_is_dir(h_dentry) |
|
|| !wh_able |
|
|| (d_really_is_positive(dentry) && !d_is_dir(dentry))) |
|
goto out; /* success */ |
|
|
|
inode_lock_shared_nested(h_inode, AuLsc_I_CHILD); |
|
opq = au_diropq_test(h_dentry); |
|
inode_unlock_shared(h_inode); |
|
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 btop, |
|
unsigned int flags) |
|
{ |
|
int npositive, err; |
|
aufs_bindex_t bindex, btail, bdiropq; |
|
unsigned char isdir, dirperm1, dirren; |
|
struct au_do_lookup_args args = { |
|
.flags = flags, |
|
.name = &dentry->d_name |
|
}; |
|
struct dentry *parent; |
|
struct super_block *sb; |
|
|
|
sb = dentry->d_sb; |
|
err = au_test_shwh(sb, args.name); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
err = au_wh_name_alloc(&args.whname, args.name); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
isdir = !!d_is_dir(dentry); |
|
dirperm1 = !!au_opt_test(au_mntflags(sb), DIRPERM1); |
|
dirren = !!au_opt_test(au_mntflags(sb), DIRREN); |
|
if (dirren) |
|
au_fset_lkup(args.flags, DIRREN); |
|
|
|
npositive = 0; |
|
parent = dget_parent(dentry); |
|
btail = au_dbtaildir(parent); |
|
for (bindex = btop; bindex <= btail; bindex++) { |
|
struct dentry *h_parent, *h_dentry; |
|
struct inode *h_inode, *h_dir; |
|
struct au_branch *br; |
|
|
|
h_dentry = au_h_dptr(dentry, bindex); |
|
if (h_dentry) { |
|
if (d_is_positive(h_dentry)) |
|
npositive++; |
|
break; |
|
} |
|
h_parent = au_h_dptr(parent, bindex); |
|
if (!h_parent || !d_is_dir(h_parent)) |
|
continue; |
|
|
|
if (dirren) { |
|
/* if the inum matches, then use the prepared name */ |
|
err = au_dr_lkup_name(&args, bindex); |
|
if (unlikely(err)) |
|
goto out_parent; |
|
} |
|
|
|
h_dir = d_inode(h_parent); |
|
inode_lock_shared_nested(h_dir, AuLsc_I_PARENT); |
|
h_dentry = au_do_lookup(h_parent, dentry, bindex, &args); |
|
inode_unlock_shared(h_dir); |
|
err = PTR_ERR(h_dentry); |
|
if (IS_ERR(h_dentry)) |
|
goto out_parent; |
|
if (h_dentry) |
|
au_fclr_lkup(args.flags, ALLOW_NEG); |
|
if (dirperm1) |
|
au_fset_lkup(args.flags, IGNORE_PERM); |
|
|
|
if (au_dbwh(dentry) == bindex) |
|
break; |
|
if (!h_dentry) |
|
continue; |
|
if (d_is_negative(h_dentry)) |
|
continue; |
|
h_inode = d_inode(h_dentry); |
|
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; |
|
} |
|
br = au_sbr(sb, bindex); |
|
if (dirren |
|
&& au_dr_hino_test_add(&br->br_dirren, h_inode->i_ino, |
|
/*add_ent*/NULL)) { |
|
/* prepare next name to lookup */ |
|
err = au_dr_lkup(&args, dentry, bindex); |
|
if (unlikely(err)) |
|
goto out_parent; |
|
} |
|
} |
|
|
|
if (npositive) { |
|
AuLabel(positive); |
|
au_update_dbtop(dentry); |
|
} |
|
err = npositive; |
|
if (unlikely(!au_opt_test(au_mntflags(sb), UDBA_NONE) |
|
&& au_dbtop(dentry) < 0)) { |
|
err = -EIO; |
|
AuIOErr("both of real entry and whiteout found, %pd, err %d\n", |
|
dentry, err); |
|
} |
|
|
|
out_parent: |
|
dput(parent); |
|
au_kfree_try_rcu(args.whname.name); |
|
if (dirren) |
|
au_dr_lkup_fin(&args); |
|
out: |
|
return err; |
|
} |
|
|
|
struct dentry *au_sio_lkup_one(struct qstr *name, struct dentry *parent) |
|
{ |
|
struct dentry *dentry; |
|
int wkq_err; |
|
|
|
if (!au_test_h_perm_sio(d_inode(parent), MAY_EXEC)) |
|
dentry = vfsub_lkup_one(name, parent); |
|
else { |
|
struct vfsub_lkup_one_args args = { |
|
.errp = &dentry, |
|
.name = name, |
|
.parent = parent |
|
}; |
|
|
|
wkq_err = au_wkq_wait(vfsub_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 wh) |
|
{ |
|
int err; |
|
struct dentry *parent, *h_parent, *h_dentry; |
|
struct au_branch *br; |
|
|
|
parent = dget_parent(dentry); |
|
h_parent = au_h_dptr(parent, bindex); |
|
br = au_sbr(dentry->d_sb, bindex); |
|
if (wh) |
|
h_dentry = au_whtmp_lkup(h_parent, br, &dentry->d_name); |
|
else |
|
h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent); |
|
err = PTR_ERR(h_dentry); |
|
if (IS_ERR(h_dentry)) |
|
goto out; |
|
if (unlikely(d_is_positive(h_dentry))) { |
|
err = -EIO; |
|
AuIOErr("%pd should be negative on b%d.\n", h_dentry, bindex); |
|
dput(h_dentry); |
|
goto out; |
|
} |
|
|
|
err = 0; |
|
if (bindex < au_dbtop(dentry)) |
|
au_set_dbtop(dentry, bindex); |
|
if (au_dbbot(dentry) < bindex) |
|
au_set_dbbot(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; */ |
|
kuid_t i_uid; |
|
kgid_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 = inode_query_iversion(h_inode); |
|
/* |
|
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 */ |
|
|| !uid_eq(ia->i_uid, h_inode->i_uid) |
|
|| !gid_eq(ia->i_gid, h_inode->i_gid) |
|
|| !inode_eq_iversion(h_inode, ia->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 = NULL; |
|
if (d_is_positive(h_dentry)) { |
|
h_inode = d_inode(h_dentry); |
|
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 = vfsub_lkup_one(&h_dentry->d_name, h_parent); |
|
err = PTR_ERR(h_d); |
|
if (IS_ERR(h_d)) |
|
goto out; |
|
|
|
err = 0; |
|
if (unlikely(h_d != h_dentry |
|
|| d_inode(h_d) != 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 = (d_inode(h_dentry->d_parent) != 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, bbot, 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); |
|
bbot = dinfo->di_bbot; |
|
bwh = dinfo->di_bwh; |
|
bdiropq = dinfo->di_bdiropq; |
|
bindex = dinfo->di_btop; |
|
p = au_hdentry(dinfo, bindex); |
|
for (; bindex <= bbot; 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 = au_hdentry(dinfo, new_bindex); |
|
tmp = *q; |
|
*q = *p; |
|
*p = tmp; |
|
if (tmp.hd_dentry) { |
|
bindex--; |
|
p--; |
|
} |
|
} |
|
|
|
dinfo->di_bwh = -1; |
|
if (bwh >= 0 && bwh <= au_sbbot(sb) && au_sbr_whable(sb, bwh)) |
|
dinfo->di_bwh = bwh; |
|
|
|
dinfo->di_bdiropq = -1; |
|
if (bdiropq >= 0 |
|
&& bdiropq <= au_sbbot(sb) |
|
&& au_sbr_whable(sb, bdiropq)) |
|
dinfo->di_bdiropq = bdiropq; |
|
|
|
err = -EIO; |
|
dinfo->di_btop = -1; |
|
dinfo->di_bbot = -1; |
|
bbot = au_dbbot(parent); |
|
bindex = 0; |
|
p = au_hdentry(dinfo, bindex); |
|
for (; bindex <= bbot; bindex++, p++) |
|
if (p->hd_dentry) { |
|
dinfo->di_btop = bindex; |
|
break; |
|
} |
|
|
|
if (dinfo->di_btop >= 0) { |
|
bindex = bbot; |
|
p = au_hdentry(dinfo, bindex); |
|
for (; bindex >= 0; bindex--, p--) |
|
if (p->hd_dentry) { |
|
dinfo->di_bbot = bindex; |
|
err = 0; |
|
break; |
|
} |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static void au_do_hide(struct dentry *dentry) |
|
{ |
|
struct inode *inode; |
|
|
|
if (d_really_is_positive(dentry)) { |
|
inode = d_inode(dentry); |
|
if (!d_is_dir(dentry)) { |
|
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; |
|
|
|
AuDbgDentry(dentry); |
|
if (d_is_dir(dentry)) { |
|
/* shrink_dcache_parent(dentry); */ |
|
err = au_hide_children(dentry); |
|
if (unlikely(err)) |
|
AuIOErr("%pd, failed hiding children, ignored %d\n", |
|
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, bbot; |
|
struct { |
|
struct dentry *dentry; |
|
struct inode *inode; |
|
mode_t mode; |
|
} orig_h, tmp_h = { |
|
.dentry = NULL |
|
}; |
|
struct au_hdentry *hd; |
|
struct inode *inode, *h_inode; |
|
struct dentry *h_dentry; |
|
|
|
err = 0; |
|
AuDebugOn(dinfo->di_btop < 0); |
|
orig_h.mode = 0; |
|
orig_h.dentry = au_hdentry(dinfo, dinfo->di_btop)->hd_dentry; |
|
orig_h.inode = NULL; |
|
if (d_is_positive(orig_h.dentry)) { |
|
orig_h.inode = d_inode(orig_h.dentry); |
|
orig_h.mode = orig_h.inode->i_mode & S_IFMT; |
|
} |
|
if (tmp->di_btop >= 0) { |
|
tmp_h.dentry = au_hdentry(tmp, tmp->di_btop)->hd_dentry; |
|
if (d_is_positive(tmp_h.dentry)) { |
|
tmp_h.inode = d_inode(tmp_h.dentry); |
|
tmp_h.mode = tmp_h.inode->i_mode & S_IFMT; |
|
} |
|
} |
|
|
|
inode = NULL; |
|
if (d_really_is_positive(dentry)) |
|
inode = d_inode(dentry); |
|
if (!orig_h.inode) { |
|
AuDbg("negative originally\n"); |
|
if (inode) { |
|
au_hide(dentry); |
|
goto out; |
|
} |
|
AuDebugOn(inode); |
|
AuDebugOn(dinfo->di_btop != dinfo->di_bbot); |
|
AuDebugOn(dinfo->di_bdiropq != -1); |
|
|
|
if (!tmp_h.inode) { |
|
AuDbg("negative --> negative\n"); |
|
/* should have only one negative lower */ |
|
if (tmp->di_btop >= 0 |
|
&& tmp->di_btop < dinfo->di_btop) { |
|
AuDebugOn(tmp->di_btop != tmp->di_bbot); |
|
AuDebugOn(dinfo->di_btop != dinfo->di_bbot); |
|
au_set_h_dptr(dentry, dinfo->di_btop, NULL); |
|
au_di_cp(dinfo, tmp); |
|
hd = au_hdentry(tmp, tmp->di_btop); |
|
au_set_h_dptr(dentry, tmp->di_btop, |
|
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_btop) |
|
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_btop > tmp->di_btop) { |
|
/* |
|
* 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_btop > tmp->di_btop) |
|
dinfo->di_btop = tmp->di_btop; |
|
if (dinfo->di_bbot < tmp->di_bbot) |
|
dinfo->di_bbot = tmp->di_bbot; |
|
dinfo->di_bwh = tmp->di_bwh; |
|
dinfo->di_bdiropq = tmp->di_bdiropq; |
|
bbot = dinfo->di_bbot; |
|
bindex = tmp->di_btop; |
|
hd = au_hdentry(tmp, bindex); |
|
for (; bindex <= bbot; bindex++, hd++) { |
|
if (au_h_dptr(dentry, bindex)) |
|
continue; |
|
h_dentry = hd->hd_dentry; |
|
if (!h_dentry) |
|
continue; |
|
AuDebugOn(d_is_negative(h_dentry)); |
|
h_inode = d_inode(h_dentry); |
|
AuDebugOn(orig_h.mode |
|
!= (h_inode->i_mode |
|
& S_IFMT)); |
|
au_set_h_dptr(dentry, bindex, |
|
dget(h_dentry)); |
|
} |
|
if (inode) |
|
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; |
|
} |
|
|
|
void au_refresh_dop(struct dentry *dentry, int force_reval) |
|
{ |
|
const struct dentry_operations *dop |
|
= force_reval ? &aufs_dop : dentry->d_sb->s_d_op; |
|
static const unsigned int mask |
|
= DCACHE_OP_REVALIDATE | DCACHE_OP_WEAK_REVALIDATE; |
|
|
|
BUILD_BUG_ON(sizeof(mask) != sizeof(dentry->d_flags)); |
|
|
|
if (dentry->d_op == dop) |
|
return; |
|
|
|
AuDbg("%pd\n", dentry); |
|
spin_lock(&dentry->d_lock); |
|
if (dop == &aufs_dop) |
|
dentry->d_flags |= mask; |
|
else |
|
dentry->d_flags &= ~mask; |
|
dentry->d_op = dop; |
|
spin_unlock(&dentry->d_lock); |
|
} |
|
|
|
int au_refresh_dentry(struct dentry *dentry, struct dentry *parent) |
|
{ |
|
int err, ebrange, nbr; |
|
unsigned int sigen; |
|
struct au_dinfo *dinfo, *tmp; |
|
struct super_block *sb; |
|
struct inode *inode; |
|
|
|
DiMustWriteLock(dentry); |
|
AuDebugOn(IS_ROOT(dentry)); |
|
AuDebugOn(d_really_is_negative(parent)); |
|
|
|
sb = dentry->d_sb; |
|
sigen = au_sigen(sb); |
|
err = au_digen_test(parent, sigen); |
|
if (unlikely(err)) |
|
goto out; |
|
|
|
nbr = au_sbbot(sb) + 1; |
|
dinfo = au_di(dentry); |
|
err = au_di_realloc(dinfo, nbr, /*may_shrink*/0); |
|
if (unlikely(err)) |
|
goto out; |
|
ebrange = au_dbrange_test(dentry); |
|
if (!ebrange) |
|
ebrange = au_do_refresh_hdentry(dentry, parent); |
|
|
|
if (d_unhashed(dentry) || ebrange /* || dinfo->di_tmpfile */) { |
|
AuDebugOn(au_dbtop(dentry) < 0 && au_dbbot(dentry) >= 0); |
|
if (d_really_is_positive(dentry)) { |
|
inode = d_inode(dentry); |
|
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, /*btop*/0, AuLkup_ALLOW_NEG); |
|
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_di_realloc(dinfo, nbr, /*may_shrink*/1); /* harmless if 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 %pd, %d\n", dentry, err); |
|
AuDbgDentry(dentry); |
|
} |
|
AuTraceErr(err); |
|
return err; |
|
} |
|
|
|
static int au_do_h_d_reval(struct dentry *h_dentry, unsigned int flags, |
|
struct dentry *dentry, aufs_bindex_t bindex) |
|
{ |
|
int err, valid; |
|
|
|
err = 0; |
|
if (!(h_dentry->d_flags & DCACHE_OP_REVALIDATE)) |
|
goto out; |
|
|
|
AuDbg("b%d\n", bindex); |
|
/* |
|
* gave up supporting LOOKUP_CREATE/OPEN for lower fs, |
|
* due to whiteout and branch permission. |
|
*/ |
|
flags &= ~(/*LOOKUP_PARENT |*/ LOOKUP_OPEN | LOOKUP_CREATE |
|
| LOOKUP_FOLLOW | LOOKUP_EXCL); |
|
/* it may return tri-state */ |
|
valid = h_dentry->d_op->d_revalidate(h_dentry, flags); |
|
|
|
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, |
|
unsigned int flags, int do_udba, int dirren) |
|
{ |
|
int err; |
|
umode_t mode, h_mode; |
|
aufs_bindex_t bindex, btail, btop, ibs, ibe; |
|
unsigned char plus, unhashed, is_root, h_plus, h_nfs, tmpfile; |
|
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; |
|
tmpfile = au_di(dentry)->di_tmpfile; |
|
|
|
/* |
|
* 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 |
|
* Let's do REVAL test too. |
|
*/ |
|
if (do_udba && inode) { |
|
mode = (inode->i_mode & S_IFMT); |
|
plus = (inode->i_nlink > 0); |
|
ibs = au_ibtop(inode); |
|
ibe = au_ibbot(inode); |
|
} |
|
|
|
btop = au_dbtop(dentry); |
|
btail = btop; |
|
if (inode && S_ISDIR(inode->i_mode)) |
|
btail = au_dbtaildir(dentry); |
|
for (bindex = btop; bindex <= btail; bindex++) { |
|
h_dentry = au_h_dptr(dentry, bindex); |
|
if (!h_dentry) |
|
continue; |
|
|
|
AuDbg("b%d, %pd\n", bindex, h_dentry); |
|
h_nfs = !!au_test_nfs(h_dentry->d_sb); |
|
spin_lock(&h_dentry->d_lock); |
|
h_name = &h_dentry->d_name; |
|
if (unlikely(do_udba |
|
&& !is_root |
|
&& ((!h_nfs |
|
&& (unhashed != !!d_unhashed(h_dentry) |
|
|| (!tmpfile && !dirren |
|
&& !au_qstreq(name, h_name)) |
|
)) |
|
|| (h_nfs |
|
&& !(flags & LOOKUP_OPEN) |
|
&& (h_dentry->d_flags |
|
& DCACHE_NFSFS_RENAMED))) |
|
)) { |
|
int h_unhashed; |
|
|
|
h_unhashed = d_unhashed(h_dentry); |
|
spin_unlock(&h_dentry->d_lock); |
|
AuDbg("unhash 0x%x 0x%x, %pd %pd\n", |
|
unhashed, h_unhashed, dentry, h_dentry); |
|
goto err; |
|
} |
|
spin_unlock(&h_dentry->d_lock); |
|
|
|
err = au_do_h_d_reval(h_dentry, flags, dentry, bindex); |
|
if (unlikely(err)) |
|
/* do not goto err, to keep the errno */ |
|
break; |
|
|
|
/* todo: plink too? */ |
|
if (!do_udba) |
|
continue; |
|
|
|
/* UDBA tests */ |
|
if (unlikely(!!inode != d_is_positive(h_dentry))) |
|
goto err; |
|
|
|
h_inode = NULL; |
|
if (d_is_positive(h_dentry)) |
|
h_inode = d_inode(h_dentry); |
|
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 (!h_nfs) { |
|
if (unlikely(plus != h_plus && !tmpfile)) |
|
goto err; |
|
} else { |
|
if (unlikely(!(h_dentry->d_flags & DCACHE_NFSFS_RENAMED) |
|
&& !is_root |
|
&& !IS_ROOT(h_dentry) |
|
&& unhashed != d_unhashed(h_dentry))) |
|
goto err; |
|
} |
|
if (unlikely(mode != h_mode |
|
|| h_cached_inode != h_inode)) |
|
goto err; |
|
continue; |
|
|
|
err: |
|
err = -EINVAL; |
|
break; |
|
} |
|
|
|
AuTraceErr(err); |
|
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; |
|
|
|
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; |
|
} |
|
|
|
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, unsigned int flags) |
|
{ |
|
int valid, err; |
|
unsigned int sigen; |
|
unsigned char do_udba, dirren; |
|
struct super_block *sb; |
|
struct inode *inode; |
|
|
|
/* todo: support rcu-walk? */ |
|
if (flags & LOOKUP_RCU) |
|
return -ECHILD; |
|
|
|
valid = 0; |
|
if (unlikely(!au_di(dentry))) |
|
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; |
|
} |
|
inode = NULL; |
|
if (d_really_is_positive(dentry)) |
|
inode = d_inode(dentry); |
|
if (unlikely(inode && au_is_bad_inode(inode))) { |
|
err = -EINVAL; |
|
AuTraceErr(err); |
|
goto out_dgrade; |
|
} |
|
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 (!(flags & (LOOKUP_OPEN | LOOKUP_EMPTY)) |
|
&& inode |
|
&& !(inode->i_state && I_LINKABLE) |
|
&& (IS_DEADDIR(inode) || !inode->i_nlink)) { |
|
AuTraceErr(err); |
|
goto out_inval; |
|
} |
|
|
|
do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE); |
|
if (do_udba && inode) { |
|
aufs_bindex_t btop = au_ibtop(inode); |
|
struct inode *h_inode; |
|
|
|
if (btop >= 0) { |
|
h_inode = au_h_iptr(inode, btop); |
|
if (h_inode && au_test_higen(inode, h_inode)) { |
|
AuTraceErr(err); |
|
goto out_inval; |
|
} |
|
} |
|
} |
|
|
|
dirren = !!au_opt_test(au_mntflags(sb), DIRREN); |
|
err = h_d_revalidate(dentry, inode, flags, do_udba, dirren); |
|
if (unlikely(!err && do_udba && au_dbtop(dentry) < 0)) { |
|
err = -EIO; |
|
AuDbg("both of real entry and whiteout found, %p, err %d\n", |
|
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("%pd invalid, %d\n", 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_weak_revalidate = aufs_d_revalidate, |
|
.d_release = aufs_d_release |
|
}; |
|
|
|
/* aufs_dop without d_revalidate */ |
|
const struct dentry_operations aufs_dop_noreval = { |
|
.d_release = aufs_d_release |
|
};
|
|
|