Commit 6b81a214 authored by J. R. Okajima's avatar J. R. Okajima
Browse files

aufs: hnotify 2/3, body



The feature is constructed by two layers. One is generic interface, and
the other is exact implementation. This is rather historical. Originally
aufs implemented this feature based upon 'inotify.' Later 'fsnotify'
made 'inotify' obsolete. During the transition period, these two layers
were introduced to support both of 'inotify' and 'fsnotify.' Currently
only 'fsnotify' is supported, but the layers are kept for the future
use.

This feature is compiled only when CONFIG_AUFS_HNOTIFY is enabled.
See also the document in previous commit.
Signed-off-by: default avatarJ. R. Okajima <hooanon05g@gmail.com>
parent 9dc58c2b
...@@ -22,5 +22,7 @@ aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \ ...@@ -22,5 +22,7 @@ aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
# all are boolean # all are boolean
aufs-$(CONFIG_PROC_FS) += procfs.o plink.o aufs-$(CONFIG_PROC_FS) += procfs.o plink.o
aufs-$(CONFIG_SYSFS) += sysfs.o aufs-$(CONFIG_SYSFS) += sysfs.o
aufs-$(CONFIG_AUFS_HNOTIFY) += hnotify.o
aufs-$(CONFIG_AUFS_HFSNOTIFY) += hfsnotify.o
aufs-$(CONFIG_AUFS_EXPORT) += export.o aufs-$(CONFIG_AUFS_EXPORT) += export.o
aufs-$(CONFIG_AUFS_DEBUG) += debug.o aufs-$(CONFIG_AUFS_DEBUG) += debug.o
...@@ -94,6 +94,61 @@ out: ...@@ -94,6 +94,61 @@ out:
return err; return err;
} }
/* todo: BAD approach */
/* copied from linux/fs/dcache.c */
enum d_walk_ret {
D_WALK_CONTINUE,
D_WALK_QUIT,
D_WALK_NORETRY,
D_WALK_SKIP,
};
extern void d_walk(struct dentry *parent, void *data,
enum d_walk_ret (*enter)(void *, struct dentry *));
struct ac_dpages_arg {
int err;
struct au_dcsub_pages *dpages;
struct super_block *sb;
au_dpages_test test;
void *arg;
};
static enum d_walk_ret au_call_dpages_append(void *_arg, struct dentry *dentry)
{
enum d_walk_ret ret;
struct ac_dpages_arg *arg = _arg;
ret = D_WALK_CONTINUE;
if (dentry->d_sb == arg->sb
&& !IS_ROOT(dentry)
&& au_dcount(dentry) > 0
&& au_di(dentry)
&& (!arg->test || arg->test(dentry, arg->arg))) {
arg->err = au_dpages_append(arg->dpages, dentry, GFP_ATOMIC);
if (unlikely(arg->err))
ret = D_WALK_QUIT;
}
return ret;
}
int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root,
au_dpages_test test, void *arg)
{
struct ac_dpages_arg args = {
.err = 0,
.dpages = dpages,
.sb = root->d_sb,
.test = test,
.arg = arg
};
d_walk(root, &args, au_call_dpages_append);
return args.err;
}
int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry,
int do_include, au_dpages_test test, void *arg) int do_include, au_dpages_test test, void *arg)
{ {
......
...@@ -30,6 +30,8 @@ struct au_dcsub_pages { ...@@ -30,6 +30,8 @@ struct au_dcsub_pages {
int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp); int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp);
void au_dpages_free(struct au_dcsub_pages *dpages); void au_dpages_free(struct au_dcsub_pages *dpages);
typedef int (*au_dpages_test)(struct dentry *dentry, void *arg); typedef int (*au_dpages_test)(struct dentry *dentry, void *arg);
int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root,
au_dpages_test test, void *arg);
int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry, int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry,
int do_include, au_dpages_test test, void *arg); int do_include, au_dpages_test test, void *arg);
int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages, int au_dcsub_pages_rev_aufs(struct au_dcsub_pages *dpages,
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2019 Junjiro R. Okajima
*/
/*
* fsnotify for the lower directories
*/
#include "aufs.h"
/* FS_IN_IGNORED is unnecessary */
static const __u32 AuHfsnMask = (FS_MOVED_TO | FS_MOVED_FROM | FS_DELETE
| FS_CREATE | FS_EVENT_ON_CHILD);
static DECLARE_WAIT_QUEUE_HEAD(au_hfsn_wq);
static __cacheline_aligned_in_smp atomic64_t au_hfsn_ifree = ATOMIC64_INIT(0);
static void au_hfsn_free_mark(struct fsnotify_mark *mark)
{
struct au_hnotify *hn = container_of(mark, struct au_hnotify,
hn_mark);
/* AuDbg("here\n"); */
au_cache_free_hnotify(hn);
smp_mb__before_atomic(); /* for atomic64_dec */
if (atomic64_dec_and_test(&au_hfsn_ifree))
wake_up(&au_hfsn_wq);
}
static int au_hfsn_alloc(struct au_hinode *hinode)
{
int err;
struct au_hnotify *hn;
struct super_block *sb;
struct au_branch *br;
struct fsnotify_mark *mark;
aufs_bindex_t bindex;
hn = hinode->hi_notify;
sb = hn->hn_aufs_inode->i_sb;
bindex = au_br_index(sb, hinode->hi_id);
br = au_sbr(sb, bindex);
AuDebugOn(!br->br_hfsn);
mark = &hn->hn_mark;
fsnotify_init_mark(mark, br->br_hfsn->hfsn_group);
mark->mask = AuHfsnMask;
/*
* by udba rename or rmdir, aufs assign a new inode to the known
* h_inode, so specify 1 to allow dups.
*/
lockdep_off();
err = fsnotify_add_inode_mark(mark, hinode->hi_inode, /*allow_dups*/1);
lockdep_on();
return err;
}
static int au_hfsn_free(struct au_hinode *hinode, struct au_hnotify *hn)
{
struct fsnotify_mark *mark;
unsigned long long ull;
struct fsnotify_group *group;
ull = atomic64_inc_return(&au_hfsn_ifree);
BUG_ON(!ull);
mark = &hn->hn_mark;
spin_lock(&mark->lock);
group = mark->group;
fsnotify_get_group(group);
spin_unlock(&mark->lock);
lockdep_off();
fsnotify_destroy_mark(mark, group);
fsnotify_put_mark(mark);
fsnotify_put_group(group);
lockdep_on();
/* free hn by myself */
return 0;
}
/* ---------------------------------------------------------------------- */
static void au_hfsn_ctl(struct au_hinode *hinode, int do_set)
{
struct fsnotify_mark *mark;
mark = &hinode->hi_notify->hn_mark;
spin_lock(&mark->lock);
if (do_set) {
AuDebugOn(mark->mask & AuHfsnMask);
mark->mask |= AuHfsnMask;
} else {
AuDebugOn(!(mark->mask & AuHfsnMask));
mark->mask &= ~AuHfsnMask;
}
spin_unlock(&mark->lock);
/* fsnotify_recalc_inode_mask(hinode->hi_inode); */
}
/* ---------------------------------------------------------------------- */
/* #define AuDbgHnotify */
#ifdef AuDbgHnotify
static char *au_hfsn_name(u32 mask)
{
#ifdef CONFIG_AUFS_DEBUG
#define test_ret(flag) \
do { \
if (mask & flag) \
return #flag; \
} while (0)
test_ret(FS_ACCESS);
test_ret(FS_MODIFY);
test_ret(FS_ATTRIB);
test_ret(FS_CLOSE_WRITE);
test_ret(FS_CLOSE_NOWRITE);
test_ret(FS_OPEN);
test_ret(FS_MOVED_FROM);
test_ret(FS_MOVED_TO);
test_ret(FS_CREATE);
test_ret(FS_DELETE);
test_ret(FS_DELETE_SELF);
test_ret(FS_MOVE_SELF);
test_ret(FS_UNMOUNT);
test_ret(FS_Q_OVERFLOW);
test_ret(FS_IN_IGNORED);
test_ret(FS_ISDIR);
test_ret(FS_IN_ONESHOT);
test_ret(FS_EVENT_ON_CHILD);
return "";
#undef test_ret
#else
return "??";
#endif
}
#endif
/* ---------------------------------------------------------------------- */
static void au_hfsn_free_group(struct fsnotify_group *group)
{
struct au_br_hfsnotify *hfsn = group->private;
/* AuDbg("here\n"); */
au_kfree_try_rcu(hfsn);
}
static int au_hfsn_handle_event(struct fsnotify_group *group,
struct inode *inode,
u32 mask, const void *data, int data_type,
const unsigned char *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info)
{
int err;
struct au_hnotify *hnotify;
struct inode *h_dir, *h_inode;
struct qstr h_child_qstr = QSTR_INIT(file_name, strlen(file_name));
struct fsnotify_mark *inode_mark;
AuDebugOn(data_type != FSNOTIFY_EVENT_INODE);
err = 0;
/* if FS_UNMOUNT happens, there must be another bug */
AuDebugOn(mask & FS_UNMOUNT);
if (mask & (FS_IN_IGNORED | FS_UNMOUNT))
goto out;
h_dir = inode;
h_inode = NULL;
#ifdef AuDbgHnotify
au_debug_on();
if (1 || h_child_qstr.len != sizeof(AUFS_XINO_FNAME) - 1
|| strncmp(h_child_qstr.name, AUFS_XINO_FNAME, h_child_qstr.len)) {
AuDbg("i%lu, mask 0x%x %s, hcname %.*s, hi%lu\n",
h_dir->i_ino, mask, au_hfsn_name(mask),
AuLNPair(&h_child_qstr), h_inode ? h_inode->i_ino : 0);
/* WARN_ON(1); */
}
au_debug_off();
#endif
inode_mark = fsnotify_iter_inode_mark(iter_info);
AuDebugOn(!inode_mark);
hnotify = container_of(inode_mark, struct au_hnotify, hn_mark);
err = au_hnotify(h_dir, hnotify, mask, &h_child_qstr, h_inode);
out:
return err;
}
static struct fsnotify_ops au_hfsn_ops = {
.handle_event = au_hfsn_handle_event,
.free_group_priv = au_hfsn_free_group,
.free_mark = au_hfsn_free_mark
};
/* ---------------------------------------------------------------------- */
static void au_hfsn_fin_br(struct au_branch *br)
{
struct au_br_hfsnotify *hfsn;
hfsn = br->br_hfsn;
if (hfsn) {
lockdep_off();
fsnotify_put_group(hfsn->hfsn_group);
lockdep_on();
}
}
static int au_hfsn_init_br(struct au_branch *br, int perm)
{
int err;
struct fsnotify_group *group;
struct au_br_hfsnotify *hfsn;
err = 0;
br->br_hfsn = NULL;
if (!au_br_hnotifyable(perm))
goto out;
err = -ENOMEM;
hfsn = kmalloc(sizeof(*hfsn), GFP_NOFS);
if (unlikely(!hfsn))
goto out;
err = 0;
group = fsnotify_alloc_group(&au_hfsn_ops);
if (IS_ERR(group)) {
err = PTR_ERR(group);
pr_err("fsnotify_alloc_group() failed, %d\n", err);
goto out_hfsn;
}
group->private = hfsn;
hfsn->hfsn_group = group;
br->br_hfsn = hfsn;
goto out; /* success */
out_hfsn:
au_kfree_try_rcu(hfsn);
out:
return err;
}
static int au_hfsn_reset_br(unsigned int udba, struct au_branch *br, int perm)
{
int err;
err = 0;
if (!br->br_hfsn)
err = au_hfsn_init_br(br, perm);
return err;
}
/* ---------------------------------------------------------------------- */
static void au_hfsn_fin(void)
{
AuDbg("au_hfsn_ifree %lld\n", (long long)atomic64_read(&au_hfsn_ifree));
wait_event(au_hfsn_wq, !atomic64_read(&au_hfsn_ifree));
}
const struct au_hnotify_op au_hnotify_op = {
.ctl = au_hfsn_ctl,
.alloc = au_hfsn_alloc,
.free = au_hfsn_free,
.fin = au_hfsn_fin,
.reset_br = au_hfsn_reset_br,
.fin_br = au_hfsn_fin_br,
.init_br = au_hfsn_init_br
};
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2005-2019 Junjiro R. Okajima
*/
/*
* abstraction to notify the direct changes on lower directories
*/
/* #include <linux/iversion.h> */
#include "aufs.h"
int au_hn_alloc(struct au_hinode *hinode, struct inode *inode)
{
int err;
struct au_hnotify *hn;
err = -ENOMEM;
hn = au_cache_alloc_hnotify();
if (hn) {
hn->hn_aufs_inode = inode;
hinode->hi_notify = hn;
err = au_hnotify_op.alloc(hinode);
AuTraceErr(err);
if (unlikely(err)) {
hinode->hi_notify = NULL;
au_cache_free_hnotify(hn);
/*
* The upper dir was removed by udba, but the same named
* dir left. In this case, aufs assigns a new inode
* number and set the monitor again.
* For the lower dir, the old monitor is still left.
*/
if (err == -EEXIST)
err = 0;
}
}
AuTraceErr(err);
return err;
}
void au_hn_free(struct au_hinode *hinode)
{
struct au_hnotify *hn;
hn = hinode->hi_notify;
if (hn) {
hinode->hi_notify = NULL;
if (au_hnotify_op.free(hinode, hn))
au_cache_free_hnotify(hn);
}
}
/* ---------------------------------------------------------------------- */
void au_hn_ctl(struct au_hinode *hinode, int do_set)
{
if (hinode->hi_notify)
au_hnotify_op.ctl(hinode, do_set);
}
void au_hn_reset(struct inode *inode, unsigned int flags)
{
aufs_bindex_t bindex, bbot;
struct inode *hi;
struct dentry *iwhdentry;
bbot = au_ibbot(inode);
for (bindex = au_ibtop(inode); bindex <= bbot; bindex++) {
hi = au_h_iptr(inode, bindex);
if (!hi)
continue;
/* inode_lock_nested(hi, AuLsc_I_CHILD); */
iwhdentry = au_hi_wh(inode, bindex);
if (iwhdentry)
dget(iwhdentry);
au_igrab(hi);
au_set_h_iptr(inode, bindex, NULL, 0);
au_set_h_iptr(inode, bindex, au_igrab(hi),
flags & ~AuHi_XINO);
iput(hi);
dput(iwhdentry);
/* inode_unlock(hi); */
}
}
/* ---------------------------------------------------------------------- */
static int hn_xino(struct inode *inode, struct inode *h_inode)
{
int err;
aufs_bindex_t bindex, bbot, bfound, btop;
struct inode *h_i;
err = 0;
if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
pr_warn("branch root dir was changed\n");
goto out;
}
bfound = -1;
bbot = au_ibbot(inode);
btop = au_ibtop(inode);
#if 0 /* reserved for future use */
if (bindex == bbot) {
/* keep this ino in rename case */
goto out;
}
#endif
for (bindex = btop; bindex <= bbot; bindex++)
if (au_h_iptr(inode, bindex) == h_inode) {
bfound = bindex;
break;
}
if (bfound < 0)
goto out;
for (bindex = btop; bindex <= bbot; bindex++) {
h_i = au_h_iptr(inode, bindex);
if (!h_i)
continue;
err = au_xino_write(inode->i_sb, bindex, h_i->i_ino, /*ino*/0);
/* ignore this error */
/* bad action? */
}
/* children inode number will be broken */
out:
AuTraceErr(err);
return err;
}
static int hn_gen_tree(struct dentry *dentry)
{
int err, i, j, ndentry;
struct au_dcsub_pages dpages;
struct au_dpage *dpage;
struct dentry **dentries;
err = au_dpages_init(&dpages, GFP_NOFS);
if (unlikely(err))
goto out;
err = au_dcsub_pages(&dpages, dentry, NULL, NULL);
if (unlikely(err))
goto out_dpages;
for (i = 0; i < dpages.ndpage; i++) {
dpage = dpages.dpages + i;
dentries = dpage->dentries;
ndentry = dpage->ndentry;
for (j = 0; j < ndentry; j++) {
struct dentry *d;
d = dentries[j];
if (IS_ROOT(d))
continue;
au_digen_dec(d);
if (d_really_is_positive(d))
/* todo: reset children xino?
cached children only? */
au_iigen_dec(d_inode(d));
}
}
out_dpages:
au_dpages_free(&dpages);
#if 0
/* discard children */
dentry_unhash(dentry);
dput(dentry);
#endif
out:
return err;
}
/*
* return 0 if processed.
*/
static int hn_gen_by_inode(char *name, unsigned int nlen, struct inode *inode,
const unsigned int isdir)
{
int err;
struct dentry *d;
struct qstr *dname;
err = 1;
if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
pr_warn("branch root dir was changed\n");
err = 0;
goto out;
}
if (!isdir) {
AuDebugOn(!name);
au_iigen_dec(inode);
spin_lock(&inode->i_lock);
hlist_for_each_entry(d, &inode->i_dentry, d_u.d_alias) {
spin_lock(&d->d_lock);
dname = &d->d_name;
if (dname->len != nlen
&& memcmp(dname->name, name, nlen)) {
spin_unlock(&d->d_lock);
continue;
}
err = 0;
au_digen_dec(d);
spin_unlock(&d->d_lock);
break;
}
spin_unlock(&inode->i_lock);
} else {
/* re-commit later */
/* au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIR); */
d = d_find_any_alias(inode);
if (!d) {
au_iigen_dec(inode);
goto out;
}
spin_lock(&d->d_lock);
dname = &d->d_name;
if (dname->len == nlen && !memcmp(dname->name, name, nlen)) {
spin_unlock(&d->d_lock);
err = hn_gen_tree(d);
spin_lock(&d->d_lock);
}
spin_unlock(&d->d_lock);
dput(d);
}
out:
AuTraceErr(err);
return err;
}
static int hn_gen_by_name(struct dentry *dentry, const unsigned int isdir)
{
int err;
if (IS_ROOT(dentry)) {
pr_warn("branch root dir was changed\n");
return 0;
}
err = 0;
if (!isdir) {
au_digen_dec(dentry);
if (d_really_is_positive(dentry))
au_iigen_dec(d_inode(dentry));
} else {
/* re-commit later */
/* au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIR); */
if (d_really_is_positive(dentry))
err = hn_gen_tree(dentry);
}
AuTraceErr(err);
return err;
}
/* ---------------------------------------------------------------------- */
/* hnotify job flags */
#define AuHnJob_XINO0 1
#define AuHnJob_GEN (1 << 1)
#define AuHnJob_DIRENT (1 << 2)
#define AuHnJob_ISDIR (1 << 3)
#define AuHnJob_TRYXINO0 (1 << 4)
#define AuHnJob_MNTPNT (1 << 5)
#define au_ftest_hnjob(flags, name) ((flags) & AuHnJob_##name)
#define au_fset_hnjob(flags, name) \
do { (flags) |= AuHnJob_##name; } while (0)
#define au_fclr_hnjob(flags, name) \
do { (flags) &= ~AuHnJob_##name; } while (0)
enum {
AuHn_CHILD,
AuHn_PARENT,
AuHnLast
};
struct au_hnotify_args {
struct inode *h_dir, *dir, *h_child_inode;
u32 mask;
unsigned int flags[AuHnLast];
unsigned int h_child_nlen;
char h_child_name[];
};
struct hn_job_args {
unsigned int flags;
struct inode *inode, *h_inode, *dir, *h_dir;
struct dentry *dentry;
char *h_name;
int h_nlen;
};
static int hn_job(struct hn_job_args *a)
{
const unsigned int isdir = au_ftest_hnjob(a->flags, ISDIR);
int e;
/* reset xino */
if (au_ftest_hnjob(a->flags, XINO0) && a->inode)
hn_xino(a->inode, a->h_inode); /* ignore this error */
if (au_ftest_hnjob(a->flags, TRYXINO0)
&& a->inode
&& a->h_inode) {
inode_lock_shared_nested(a->h_inode, AuLsc_I_CHILD);
if (!a->h_inode->i_nlink
&& !(a->h_inode->i_state & I_LINKABLE))
hn_xino(a->inode, a->h_inode); /* ignore this error */
inode_unlock_shared(a->h_inode);
}
/* make the generation obsolete */
if (au_ftest_hnjob(a->flags, GEN)) {
e = -1;
if (a->inode)
e = hn_gen_by_inode(a->h_name, a->h_nlen, a->inode,
isdir);
if (e && a->dentry)
hn_gen_by_name(a->dentry, isdir);
/* ignore this error */
}
/* make dir entries obsolete */
#if 0 /* re-commit later */
if (au_ftest_hnjob(a->flags, DIRENT) && a->inode) {
struct au_vdir *vdir;
vdir = au_ivdir(a->inode);
if (vdir)
vdir->vd_jiffy = 0;
/* IMustLock(a->inode); */
/* inode_inc_iversion(a->inode); */
}
#endif
/* can do nothing but warn */
if (au_ftest_hnjob(a->flags, MNTPNT)
&& a->dentry
&& d_mountpoint(a->dentry))
pr_warn("mount-point %pd is removed or renamed\n", a->dentry);
return 0;
}
/* ---------------------------------------------------------------------- */
static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen,
struct inode *dir)
{
struct dentry *dentry, *d, *parent;
struct qstr *dname;
parent = d_find_any_alias(dir);
if (!parent)
return NULL;
dentry = NULL;
spin_lock(&parent->d_lock);
list_for_each_entry(d, &parent->d_subdirs, d_child) {
/* AuDbg("%pd\n", d); */
spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
dname = &d->d_name;
if (dname->len != nlen || memcmp(dname->name, name, nlen))
goto cont_unlock;
if (au_di(d))
au_digen_dec(d);
else
goto cont_unlock;
if (au_dcount(d) > 0) {
dentry = dget_dlock(d);
spin_unlock(&d->d_lock);
break;
}
cont_unlock:
spin_unlock(&d->d_lock);
}
spin_unlock(&parent->d_lock);
dput(parent);
if (dentry)
di_write_lock_child(dentry);
return dentry;
}
static struct inode *lookup_wlock_by_ino(struct super_block *sb,
aufs_bindex_t bindex, ino_t h_ino)
{
struct inode *inode;
ino_t ino;
int err;
inode = NULL;
err = au_xino_read(sb, bindex, h_ino, &ino);
if (!err && ino)
inode = ilookup(sb, ino);
if (!inode)
goto out;
if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
pr_warn("wrong root branch\n");
iput(inode);
inode = NULL;
goto out;
}
ii_write_lock_child(inode);
out:
return inode;
}
static void au_hn_bh(void *_args)
{
struct au_hnotify_args *a = _args;
struct super_block *sb;
aufs_bindex_t bindex, bbot, bfound;
unsigned char xino, try_iput;
int err;
struct inode *inode;
ino_t h_ino;
struct hn_job_args args;
struct dentry *dentry;
struct au_sbinfo *sbinfo;
AuDebugOn(!_args);
AuDebugOn(!a->h_dir);
AuDebugOn(!a->dir);
AuDebugOn(!a->mask);
AuDbg("mask 0x%x, i%lu, hi%lu, hci%lu\n",
a->mask, a->dir->i_ino, a->h_dir->i_ino,
a->h_child_inode ? a->h_child_inode->i_ino : 0);
inode = NULL;
dentry = NULL;
/*
* do not lock a->dir->i_mutex here
* because of d_revalidate() may cause a deadlock.
*/
sb = a->dir->i_sb;
AuDebugOn(!sb);
sbinfo = au_sbi(sb);
AuDebugOn(!sbinfo);
si_write_lock(sb, AuLock_NOPLMW);
ii_read_lock_parent(a->dir);
bfound = -1;
bbot = au_ibbot(a->dir);
for (bindex = au_ibtop(a->dir); bindex <= bbot; bindex++)
if (au_h_iptr(a->dir, bindex) == a->h_dir) {
bfound = bindex;
break;
}
ii_read_unlock(a->dir);
if (unlikely(bfound < 0))
goto out;
xino = !!au_opt_test(au_mntflags(sb), XINO);
h_ino = 0;
if (a->h_child_inode)
h_ino = a->h_child_inode->i_ino;
if (a->h_child_nlen
&& (au_ftest_hnjob(a->flags[AuHn_CHILD], GEN)
|| au_ftest_hnjob(a->flags[AuHn_CHILD], MNTPNT)))
dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen,
a->dir);
try_iput = 0;
if (dentry && d_really_is_positive(dentry))
inode = d_inode(dentry);
if (xino && !inode && h_ino
&& (au_ftest_hnjob(a->flags[AuHn_CHILD], XINO0)
|| au_ftest_hnjob(a->flags[AuHn_CHILD], TRYXINO0)
|| au_ftest_hnjob(a->flags[AuHn_CHILD], GEN))) {
inode = lookup_wlock_by_ino(sb, bfound, h_ino);
try_iput = 1;
}
args.flags = a->flags[AuHn_CHILD];
args.dentry = dentry;
args.inode = inode;
args.h_inode = a->h_child_inode;
args.dir = a->dir;
args.h_dir = a->h_dir;
args.h_name = a->h_child_name;
args.h_nlen = a->h_child_nlen;
err = hn_job(&args);
if (dentry) {
if (au_di(dentry))
di_write_unlock(dentry);
dput(dentry);
}
if (inode && try_iput) {
ii_write_unlock(inode);
iput(inode);
}
ii_write_lock_parent(a->dir);
args.flags = a->flags[AuHn_PARENT];
args.dentry = NULL;
args.inode = a->dir;
args.h_inode = a->h_dir;
args.dir = NULL;
args.h_dir = NULL;
args.h_name = NULL;
args.h_nlen = 0;
err = hn_job(&args);
ii_write_unlock(a->dir);
out:
iput(a->h_child_inode);
iput(a->h_dir);
iput(a->dir);
si_write_unlock(sb);
au_nwt_done(&sbinfo->si_nowait);
au_kfree_rcu(a);
}
/* ---------------------------------------------------------------------- */
int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask,
struct qstr *h_child_qstr, struct inode *h_child_inode)
{
int err, len;
unsigned int flags[AuHnLast], f;
unsigned char isdir, isroot, wh;
struct inode *dir;
struct au_hnotify_args *args;
char *p, *h_child_name;
err = 0;
AuDebugOn(!hnotify || !hnotify->hn_aufs_inode);
dir = igrab(hnotify->hn_aufs_inode);
if (!dir)
goto out;
isroot = (dir->i_ino == AUFS_ROOT_INO);
wh = 0;
h_child_name = (void *)h_child_qstr->name;
len = h_child_qstr->len;
if (h_child_name) {
if (len > AUFS_WH_PFX_LEN
&& !memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
h_child_name += AUFS_WH_PFX_LEN;
len -= AUFS_WH_PFX_LEN;
wh = 1;
}
}
isdir = 0;
if (h_child_inode)
isdir = !!S_ISDIR(h_child_inode->i_mode);
flags[AuHn_PARENT] = AuHnJob_ISDIR;
flags[AuHn_CHILD] = 0;
if (isdir)
flags[AuHn_CHILD] = AuHnJob_ISDIR;
au_fset_hnjob(flags[AuHn_PARENT], DIRENT);
au_fset_hnjob(flags[AuHn_CHILD], GEN);
switch (mask & FS_EVENTS_POSS_ON_CHILD) {
case FS_MOVED_FROM:
case FS_MOVED_TO:
au_fset_hnjob(flags[AuHn_CHILD], XINO0);
au_fset_hnjob(flags[AuHn_CHILD], MNTPNT);
/*FALLTHROUGH*/
case FS_CREATE:
AuDebugOn(!h_child_name);
break;
case FS_DELETE:
/*
* aufs never be able to get this child inode.
* revalidation should be in d_revalidate()
* by checking i_nlink, i_generation or d_unhashed().
*/
AuDebugOn(!h_child_name);
au_fset_hnjob(flags[AuHn_CHILD], TRYXINO0);
au_fset_hnjob(flags[AuHn_CHILD], MNTPNT);
break;
default:
AuDebugOn(1);
}
if (wh)
h_child_inode = NULL;
err = -ENOMEM;
/* iput() and kfree() will be called in au_hnotify() */
args = kmalloc(sizeof(*args) + len + 1, GFP_NOFS);
if (unlikely(!args)) {
AuErr1("no memory\n");
iput(dir);
goto out;
}
args->flags[AuHn_PARENT] = flags[AuHn_PARENT];
args->flags[AuHn_CHILD] = flags[AuHn_CHILD];
args->mask = mask;
args->dir = dir;
args->h_dir = igrab(h_dir);
if (h_child_inode)
h_child_inode = igrab(h_child_inode); /* can be NULL */
args->h_child_inode = h_child_inode;
args->h_child_nlen = len;
if (len) {
p = (void *)args;
p += sizeof(*args);
memcpy(p, h_child_name, len);
p[len] = 0;
}
/* NFS fires the event for silly-renamed one from kworker */
f = 0;
if (!dir->i_nlink
|| (au_test_nfs(h_dir->i_sb) && (mask & FS_DELETE)))
f = AuWkq_NEST;
err = au_wkq_nowait(au_hn_bh, args, dir->i_sb, f);
if (unlikely(err)) {
pr_err("wkq %d\n", err);
iput(args->h_child_inode);
iput(args->h_dir);
iput(args->dir);
au_kfree_rcu(args);
}
out:
return err;
}
/* ---------------------------------------------------------------------- */
int au_hnotify_reset_br(unsigned int udba, struct au_branch *br, int perm)
{
int err;
AuDebugOn(!(udba & AuOptMask_UDBA));
err = 0;
if (au_hnotify_op.reset_br)
err = au_hnotify_op.reset_br(udba, br, perm);
return err;
}
int au_hnotify_init_br(struct au_branch *br, int perm)
{
int err;
err = 0;
if (au_hnotify_op.init_br)
err = au_hnotify_op.init_br(br, perm);
return err;
}
void au_hnotify_fin_br(struct au_branch *br)
{
if (au_hnotify_op.fin_br)
au_hnotify_op.fin_br(br);
}
static void au_hn_destroy_cache(void)
{
kmem_cache_destroy(au_cache[AuCache_HNOTIFY]);
au_cache[AuCache_HNOTIFY] = NULL;
}
int __init au_hnotify_init(void)
{
int err;
err = -ENOMEM;
au_cache[AuCache_HNOTIFY] = AuCache(au_hnotify);
if (au_cache[AuCache_HNOTIFY]) {
err = 0;
if (au_hnotify_op.init)
err = au_hnotify_op.init();
if (unlikely(err))
au_hn_destroy_cache();
}
AuTraceErr(err);
return err;
}
void au_hnotify_fin(void)
{
if (au_hnotify_op.fin)
au_hnotify_op.fin();
/* cf. au_cache_fin() */
if (au_cache[AuCache_HNOTIFY])
au_hn_destroy_cache();
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment