diff -ruN linux-2.6.22/fs/Kconfig linux-2.6.22-aufs/fs/Kconfig
--- linux-2.6.22/fs/Kconfig	2007-07-18 14:01:55.000000000 +0200
+++ linux-2.6.22-aufs/fs/Kconfig	2007-07-18 14:02:32.000000000 +0200
@@ -1044,6 +1045,123 @@
 	  To compile this file system support as a module, choose M here: the
 	  module will be called ecryptfs.
 
+config AUFS
+	tristate "Another unionfs"
+	help
+	  Aufs is a stackable unification filesystem such as Unionfs,
+	  which unifies several directories and provides a merged single
+	  directory.
+	  In the early days, aufs was entirely re-designed and
+	  re-implemented Unionfs Version 1.x series. After many original
+	  ideas, approaches and improvements, it becomes totally
+	  different from Unionfs while keeping the basic features.
+	  See Unionfs for the basic features.
+
+config AUFS_FAKE_DM
+	bool "Use simplified (fake) nameidata"
+	depends on AUFS
+	default y
+	help
+	  Faking nameidata (VFS internal data), you can get better performance
+	  in some cases.
+
+choice
+	prompt "Maximum number of branches"
+	depends on AUFS
+	default AUFS_BRANCH_MAX_127
+	help
+	  HELP
+config AUFS_BRANCH_MAX_127
+	bool "127"
+	help
+	  HELP
+config AUFS_BRANCH_MAX_511
+	bool "511"
+	help
+	  HELP
+config AUFS_BRANCH_MAX_1023
+	bool "1023"
+	help
+	  HELP
+config AUFS_BRANCH_MAX_32767
+	bool "32767"
+	help
+	  HELP
+endchoice
+
+config AUFS_SYSAUFS
+	bool "Use <sysfs>/fs/aufs"
+	depends on AUFS
+	depends on SYSFS
+	default y
+	help
+	  Aufs creates some files under sysfs for various purposes.
+	  If the number of your branches is large or their path is long
+	  and you meet the limitation of mount(8), /etc/mtab or
+	  /proc/mount, you need to enable this option and set aufs
+	  module parameter brs=1.
+	  See detail in aufs.5.
+
+config AUFS_HINOTIFY
+	bool "Use inotify to detect actions on a branch"
+	depends on AUFS
+	depends on INOTIFY
+	default n
+	help
+	  If you want to modify files on branches directly, eg. bypassing aufs,
+	  and want aufs to detect the changes of them fully, then enable this
+	  option and use 'udba=inotify' mount option.
+	  It will damage the performance.
+	  See detail in aufs.5.
+
+config AUFS_EXPORT
+	bool "NFS-exportable aufs"
+	depends on AUFS
+	depends on (AUFS = y && EXPORTFS = y) || (AUFS = m && EXPORTFS)
+	default n
+	help
+	  If you want to export your mounted aufs, then enable this
+	  option. There are several requirements to export aufs.
+	  See detail in aufs.5.
+
+config AUFS_ROBR
+	bool "Aufs as an readonly branch of another aufs"
+	depends on AUFS
+	default n
+	help
+	  If you want make your aufs to be a part of another aufs, then
+	  enable this option. In other words, you can specify your aufs
+	  path in 'br:' mount option for another aufs, but cannot
+	  specify 'rw' as the branch permission.
+	  It will damage the performance.
+	  See detail in aufs.5.
+
+config AUFS_DEBUG
+	bool "Debug aufs"
+	depends on AUFS
+	default y
+	help
+	  Enable this to compile aufs internal debug code.
+	  The performance will be damaged.
+
+config AUFS_COMPAT
+	bool "Compatibility with Unionfs (obsolete)"
+	depends on AUFS
+	default n
+	help
+	  This makes aufs compatible with unionfs-style mount options and some
+	  behaviours.
+	  The dirs= mount option and =nfsro branch permission flag are always
+	  interpreted as br: mount option and =ro flag respectively. The
+	  'debug', 'delete' and 'imap' mount options are ignored.
+	  If you disable this option, you will get,
+	  - aufs issues a warning about the ignored mount options
+	  - the default branch permission flag is set. RW for the first branch,
+	    and RO for the rests.
+	  - the name of a internal file which represents the directory is
+	    'opaque', becomes '.wh..wh..opq'
+	  - the 'diropq=w' mount option is set by default
+
 config UNION_FS
 	tristate "Union file system (EXPERIMENTAL)"
 	depends on EXPERIMENTAL
diff -ruN linux-2.6.22/fs/Makefile linux-2.6.22-aufs/fs/Makefile
--- linux-2.6.22/fs/Makefile		2007-07-18 20:10:56.000000000 +0200
+++ linux-2.6.22-aufs/fs/Makefile	2007-07-18 20:11:12.000000000 +0200
@@ -121,3 +121,4 @@
 obj-$(CONFIG_OCFS2_FS)		+= ocfs2/
 obj-$(CONFIG_GFS2_FS)		+= gfs2/
 obj-$(CONFIG_UNION_FS)		+= unionfs/
+obj-$(CONFIG_AUFS)		+= aufs/
diff -ruN linux-2.6.22/fs/aufs/Makefile linux-2.6.22-aufs/fs/aufs/Makefile
--- linux-2.6.22/fs/aufs/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/Makefile	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,18 @@
+# AUFS Makefile for the Linux 2.6.16 and later
+# $Id: Makefile,v 1.31 2007/07/15 20:06:55 sfjro Exp $
+
+obj-$(CONFIG_AUFS) += aufs.o
+aufs-y := module.o super.o sbinfo.o xino.o \
+	branch.o cpup.o whout.o plink.o wkq.o dcsub.o vfsub.o \
+	opts.o \
+	dentry.o dinfo.o \
+	file.o f_op.o finfo.o \
+	dir.o vdir.o \
+	inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o iinfo.o \
+	misc.o
+#xattr.o
+aufs-$(CONFIG_AUFS_SYSAUFS) += sysaufs.o
+aufs-$(CONFIG_AUFS_HINOTIFY) += hinotify.o
+aufs-$(CONFIG_AUFS_EXPORT) += export.o
+#aufs-$(CONFIG_DEBUGFS) += dbgfs.o
+aufs-$(CONFIG_AUFS_DEBUG) += debug.o
diff -ruN linux-2.6.22/fs/aufs/aufs.h linux-2.6.22-aufs/fs/aufs/aufs.h
--- linux-2.6.22/fs/aufs/aufs.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/aufs.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: aufs.h,v 1.31 2007/07/15 20:01:55 sfjro Exp $ */
+
+#ifndef __AUFS_H__
+#define __AUFS_H__
+
+#ifdef __KERNEL__
+
+#include <linux/version.h>
+
+/* limited support before 2.6.16, curretly 2.6.15 only. */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16)
+#define timespec_to_ns(ts)	({(long long)(ts)->tv_sec;})
+#define D_CHILD			d_child
+#else
+#define D_CHILD			d_u.d_child
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+#include "debug.h"
+
+#include "branch.h"
+#include "cpup.h"
+#include "dcsub.h"
+#include "dentry.h"
+#include "dir.h"
+#include "file.h"
+#include "hinode.h"
+#include "inode.h"
+#include "misc.h"
+#include "module.h"
+#include "opts.h"
+#include "super.h"
+#include "sysaufs.h"
+#include "vfsub.h"
+#include "whout.h"
+#include "wkq.h"
+//#include "xattr.h"
+
+#define AuUse_ISSUBDIR
+#ifdef CONFIG_AUFS_MODULE
+
+/* call ksize() or not */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22)
+#ifndef CONFIG_AUFS_KSIZE_PATCH
+#define ksize(p)	(0U)
+#endif
+#endif
+
+/* call is_subdir() or not */
+#ifndef CONFIG_AUFS_ISSUBDIR_PATCH
+#undef AuUse_ISSUBDIR
+#endif
+
+#endif /* CONFIG_AUFS_MODULE */
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_H__ */
diff -ruN linux-2.6.22/fs/aufs/branch.c linux-2.6.22-aufs/fs/aufs/branch.c
--- linux-2.6.22/fs/aufs/branch.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/branch.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,962 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: branch.c,v 1.56 2007/07/15 20:02:10 sfjro Exp $ */
+
+#include <linux/loop.h>
+#include <linux/smp_lock.h>
+#include "aufs.h"
+
+static void free_branch(struct aufs_branch *br)
+{
+	TraceEnter();
+
+	if (br->br_xino)
+		fput(br->br_xino);
+	dput(br->br_wh);
+	dput(br->br_plink);
+	if (!au_is_nfs(br->br_mnt->mnt_sb))
+		mntput(br->br_mnt);
+	else {
+		lockdep_off();
+		mntput(br->br_mnt);
+		lockdep_on();
+	}
+	AuDebugOn(br_count(br) || atomic_read(&br->br_wh_running));
+	kfree(br);
+}
+
+/*
+ * frees all branches
+ */
+void free_branches(struct aufs_sbinfo *sbinfo)
+{
+	aufs_bindex_t bmax;
+	struct aufs_branch **br;
+
+	TraceEnter();
+	bmax = sbinfo->si_bend + 1;
+	br = sbinfo->si_branch;
+	while (bmax--)
+		free_branch(*br++);
+}
+
+/*
+ * find the index of a branch which is specified by @br_id.
+ */
+int find_brindex(struct super_block *sb, aufs_bindex_t br_id)
+{
+	aufs_bindex_t bindex, bend;
+
+	TraceEnter();
+
+	bend = sbend(sb);
+	for (bindex = 0; bindex <= bend; bindex++)
+		if (sbr_id(sb, bindex) == br_id)
+			return bindex;
+	return -1;
+}
+
+/*
+ * test if the @br is readonly or not.
+ */
+int br_rdonly(struct aufs_branch *br)
+{
+	return ((br->br_mnt->mnt_sb->s_flags & MS_RDONLY)
+		|| !br_writable(br->br_perm))
+		? -EROFS : 0;
+}
+
+int au_is_rr(struct super_block *h_sb)
+{
+	int ro = 0;
+	const char *type = au_sbtype(h_sb);
+
+#if defined(CONFIG_SQUASHFS_FS) || defined(CONFIG_SQUASHFS_FS_MODULE)
+	ro = !strcmp(type, "squashfs");
+#endif
+#if defined(CONFIG_ISO9660_FS) || defined(CONFIG_ISO9660_FS_MODULE)
+	if (!ro)
+		ro = !strcmp(type, "iso9660");
+#endif
+#if defined(CONFIG_UDF_FS) || defined(CONFIG_UDF_FS_MODULE)
+	if (!ro)
+		ro = !strcmp(type, "udf");
+#endif
+#if defined(CONFIG_CRAMFS) || defined(CONFIG_CRAMFS_MODULE)
+	if (!ro)
+		ro = !strcmp(type, "cramfs");
+#endif
+#if defined(CONFIG_ROMFS_FS) || defined(CONFIG_ROMFS_FS_MODULE)
+	if (!ro)
+		ro = !strcmp(type, "romfs");
+#endif
+	return ro;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * returns writable branch index, otherwise an error.
+ * todo: customizable writable-branch-policy
+ */
+static int find_rw_parent(struct dentry *dentry, aufs_bindex_t bend)
+{
+	int err;
+	aufs_bindex_t bindex, candidate;
+	struct super_block *sb;
+	struct dentry *parent, *hidden_parent;
+
+	err = bend;
+	sb = dentry->d_sb;
+	parent = dget_parent(dentry);
+#if 1 // branch policy
+	hidden_parent = au_h_dptr_i(parent, bend);
+	if (hidden_parent && !br_rdonly(stobr(sb, bend)))
+		goto out; /* success */
+#endif
+
+	candidate = -1;
+	for (bindex = dbstart(parent); bindex <= bend; bindex++) {
+		hidden_parent = au_h_dptr_i(parent, bindex);
+		if (hidden_parent && !br_rdonly(stobr(sb, bindex))) {
+#if 0 // branch policy
+			if (candidate == -1)
+				candidate = bindex;
+			if (!au_test_perm(hidden_parent->d_inode, MAY_WRITE))
+				return bindex;
+#endif
+			err = bindex;
+			goto out; /* success */
+		}
+	}
+#if 0 // branch policy
+	err = candidate;
+	if (candidate != -1)
+		goto out; /* success */
+#endif
+	err = -EROFS;
+
+ out:
+	dput(parent);
+	return err;
+}
+
+int find_rw_br(struct super_block *sb, aufs_bindex_t bend)
+{
+	aufs_bindex_t bindex;
+
+	for (bindex = bend; bindex >= 0; bindex--)
+		if (!br_rdonly(stobr(sb, bindex)))
+			return bindex;
+	return -EROFS;
+}
+
+int find_rw_parent_br(struct dentry *dentry, aufs_bindex_t bend)
+{
+	int err;
+
+	err = find_rw_parent(dentry, bend);
+	if (err >= 0)
+		return err;
+	return find_rw_br(dentry->d_sb, bend);
+}
+
+#if 0
+/*
+ * dir_cpdown/nodir_cpdown(def)
+ * wr_br_policy=dir | branch
+ */
+int au_rw(struct dentry *dentry, aufs_bindex_t bend)
+{
+	int err;
+	struct super_block *sb;
+
+	sb = dentry->d_sb;
+	SiMustAnyLock(sb);
+
+	if (!au_flag_test(sb, AuFlag_DIR_CPDOWN)) {
+		dpages;
+	}
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * test if two hidden_dentries have overlapping branches.
+ */
+static int do_is_overlap(struct super_block *sb, struct dentry *hidden_d1,
+			 struct dentry *hidden_d2)
+{
+	int err;
+
+	LKTRTrace("%.*s, %.*s\n", DLNPair(hidden_d1), DLNPair(hidden_d2));
+
+	err = au_is_subdir(hidden_d1, hidden_d2);
+	TraceErr(err);
+	return err;
+}
+
+#if defined(CONFIG_BLK_DEV_LOOP) || defined(CONFIG_BLK_DEV_LOOP_MODULE)
+static int is_overlap_loopback(struct super_block *sb, struct dentry *hidden_d1,
+			       struct dentry *hidden_d2)
+{
+	struct inode *hidden_inode;
+	struct loop_device *l;
+
+	hidden_inode = hidden_d1->d_inode;
+	if (MAJOR(hidden_inode->i_sb->s_dev) != LOOP_MAJOR)
+		return 0;
+
+	l = hidden_inode->i_sb->s_bdev->bd_disk->private_data;
+	hidden_d1 = l->lo_backing_file->f_dentry;
+	if (unlikely(hidden_d1->d_sb == sb))
+		return 1;
+	return do_is_overlap(sb, hidden_d1, hidden_d2);
+}
+#else
+#define is_overlap_loopback(sb, hidden_d1, hidden_d2) 0
+#endif
+
+static int is_overlap(struct super_block *sb, struct dentry *hidden_d1,
+		      struct dentry *hidden_d2)
+{
+	LKTRTrace("d1 %.*s, d2 %.*s\n", DLNPair(hidden_d1), DLNPair(hidden_d2));
+	if (unlikely(hidden_d1 == hidden_d2))
+		return 1;
+	return do_is_overlap(sb, hidden_d1, hidden_d2)
+		|| do_is_overlap(sb, hidden_d2, hidden_d1)
+		|| is_overlap_loopback(sb, hidden_d1, hidden_d2)
+		|| is_overlap_loopback(sb, hidden_d2, hidden_d1);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int init_br_wh(struct super_block *sb, aufs_bindex_t bindex,
+		      struct aufs_branch *br, int new_perm,
+		      struct dentry *h_root, struct vfsmount *h_mnt)
+{
+	int err, old_perm;
+	struct inode *dir = sb->s_root->d_inode,
+		*h_dir = h_root->d_inode;
+	const int new = (bindex < 0);
+
+	LKTRTrace("b%d, new_perm %d\n", bindex, new_perm);
+
+	if (new)
+		vfsub_i_lock_nested(h_dir, AuLsc_I_PARENT);
+	else
+		hdir_lock(h_dir, dir, bindex);
+
+	br_wh_write_lock(br);
+	old_perm = br->br_perm;
+	br->br_perm = new_perm;
+	err = init_wh(h_root, br, au_do_nfsmnt(h_mnt), sb);
+	br->br_perm = old_perm;
+	br_wh_write_unlock(br);
+
+	if (new)
+		vfsub_i_unlock(h_dir);
+	else
+		hdir_unlock(h_dir, dir, bindex);
+
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * returns a newly allocated branch. @new_nbranch is a number of branches
+ * after adding a branch.
+ */
+static struct aufs_branch *alloc_addbr(struct super_block *sb, int new_nbranch)
+{
+	struct aufs_branch **branchp, *add_branch;
+	int sz;
+	void *p;
+	struct dentry *root;
+	struct inode *inode;
+	struct aufs_hinode *hinodep;
+	struct aufs_hdentry *hdentryp;
+
+	LKTRTrace("new_nbranch %d\n", new_nbranch);
+	SiMustWriteLock(sb);
+	root = sb->s_root;
+	DiMustWriteLock(root);
+	inode = root->d_inode;
+	IiMustWriteLock(inode);
+
+	add_branch = kmalloc(sizeof(*add_branch), GFP_KERNEL);
+	//if (LktrCond) {kfree(add_branch); add_branch = NULL;}
+	if (unlikely(!add_branch))
+		goto out;
+
+	sz = sizeof(*branchp) * (new_nbranch - 1);
+	if (unlikely(!sz))
+		sz = sizeof(*branchp);
+	p = stosi(sb)->si_branch;
+	branchp = au_kzrealloc(p, sz, sizeof(*branchp) * new_nbranch,
+			       GFP_KERNEL);
+	//if (LktrCond) branchp = NULL;
+	if (unlikely(!branchp))
+		goto out;
+	stosi(sb)->si_branch = branchp;
+
+	sz = sizeof(*hdentryp) * (new_nbranch - 1);
+	if (unlikely(!sz))
+		sz = sizeof(*hdentryp);
+	p = dtodi(root)->di_hdentry;
+	hdentryp = au_kzrealloc(p, sz, sizeof(*hdentryp) * new_nbranch,
+				GFP_KERNEL);
+	//if (LktrCond) hdentryp = NULL;
+	if (unlikely(!hdentryp))
+		goto out;
+	dtodi(root)->di_hdentry = hdentryp;
+
+	sz = sizeof(*hinodep) * (new_nbranch - 1);
+	if (unlikely(!sz))
+		sz = sizeof(*hinodep);
+	p = itoii(inode)->ii_hinode;
+	hinodep = au_kzrealloc(p, sz, sizeof(*hinodep) * new_nbranch,
+			       GFP_KERNEL);
+	//if (LktrCond) hinodep = NULL; // unavailable test
+	if (unlikely(!hinodep))
+		goto out;
+	itoii(inode)->ii_hinode = hinodep;
+	return add_branch; /* success */
+
+ out:
+	kfree(add_branch);
+	TraceErr(-ENOMEM);
+	return ERR_PTR(-ENOMEM);
+}
+
+/*
+ * test if the branch permission is legal or not.
+ */
+static int test_br(struct super_block *sb, struct inode *inode, int brperm,
+		   char *path)
+{
+	int err;
+
+	err = 0;
+	if (unlikely(br_writable(brperm) && IS_RDONLY(inode))) {
+		Err("write permission for readonly fs or inode, %s\n", path);
+		err = -EINVAL;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * retunrs,,,
+ * 0: success, the caller will add it
+ * plus: success, it is already unified, the caller should ignore it
+ * minus: error
+ */
+static int test_add(struct super_block *sb, struct opt_add *add, int remount)
+{
+	int err;
+	struct dentry *root;
+	struct inode *inode, *hidden_inode;
+	aufs_bindex_t bend, bindex;
+
+	LKTRTrace("%s, remo%d\n", add->path, remount);
+
+	root = sb->s_root;
+	bend = sbend(sb);
+	if (unlikely(bend >= 0 && au_find_dbindex(root, add->nd.dentry) >= 0)) {
+		err = 1;
+		if (!remount) {
+			err = -EINVAL;
+			Err("%s duplicated\n", add->path);
+		}
+		goto out;
+	}
+
+	err = -ENOSPC; //-E2BIG;
+	//if (LktrCond) bend = AUFS_BRANCH_MAX;
+	if (unlikely(AUFS_BRANCH_MAX <= add->bindex
+		     || AUFS_BRANCH_MAX - 1 <= bend)) {
+		Err("number of branches exceeded %s\n", add->path);
+		goto out;
+	}
+
+	err = -EDOM;
+	if (unlikely(add->bindex < 0 || bend + 1 < add->bindex)) {
+		Err("bad index %d\n", add->bindex);
+		goto out;
+	}
+
+	inode = add->nd.dentry->d_inode;
+	AuDebugOn(!inode || !S_ISDIR(inode->i_mode));
+	err = -ENOENT;
+	if (unlikely(!inode->i_nlink)) {
+		Err("no existence %s\n", add->path);
+		goto out;
+	}
+
+	err = -EINVAL;
+	if (unlikely(inode->i_sb == sb)) {
+		Err("%s must be outside\n", add->path);
+		goto out;
+	}
+
+#ifndef CONFIG_AUFS_ROBR
+	if (unlikely(au_is_aufs(inode->i_sb)
+		     || !strcmp(au_sbtype(inode->i_sb), "unionfs"))) {
+		Err("nested " AUFS_NAME " %s\n", add->path);
+		goto out;
+	}
+#endif
+
+#ifdef AuNoNfsBranch
+	if (unlikely(au_is_nfs(inode->i_sb))) {
+		Err(AuNoNfsBranchMsg ". %s\n", add->path);
+		goto out;
+	}
+#endif
+
+	err = test_br(sb, add->nd.dentry->d_inode, add->perm, add->path);
+	if (unlikely(err))
+		goto out;
+
+	if (unlikely(bend < 0))
+		return 0; /* success */
+
+	hidden_inode = au_h_dptr(root)->d_inode;
+	if (unlikely(au_flag_test(sb, AuFlag_WARN_PERM)
+		     && ((hidden_inode->i_mode & S_IALLUGO)
+			 != (inode->i_mode & S_IALLUGO)
+			 || hidden_inode->i_uid != inode->i_uid
+			 || hidden_inode->i_gid != inode->i_gid)))
+		Warn("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n",
+		     add->path,
+		     inode->i_uid, inode->i_gid, (inode->i_mode & S_IALLUGO),
+		     hidden_inode->i_uid, hidden_inode->i_gid,
+		     (hidden_inode->i_mode & S_IALLUGO));
+
+	err = -EINVAL;
+	for (bindex = 0; bindex <= bend; bindex++)
+		if (unlikely(is_overlap(sb, add->nd.dentry,
+					au_h_dptr_i(root, bindex)))) {
+			Err("%s is overlapped\n", add->path);
+			goto out;
+		}
+	err = 0;
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+int br_add(struct super_block *sb, struct opt_add *add, int remount)
+{
+	int err, sz;
+	aufs_bindex_t bend, add_bindex;
+	struct dentry *root;
+	struct aufs_iinfo *iinfo;
+	struct aufs_sbinfo *sbinfo;
+	struct aufs_dinfo *dinfo;
+	struct inode *root_inode;
+	unsigned long long maxb;
+	struct aufs_branch **branchp, *add_branch;
+	struct aufs_hdentry *hdentryp;
+	struct aufs_hinode *hinodep;
+
+	LKTRTrace("b%d, %s, 0x%x, %.*s\n", add->bindex, add->path,
+		  add->perm, DLNPair(add->nd.dentry));
+	SiMustWriteLock(sb);
+	root = sb->s_root;
+	DiMustWriteLock(root);
+	root_inode = root->d_inode;
+	IMustLock(root_inode);
+	IiMustWriteLock(root_inode);
+
+	err = test_add(sb, add, remount);
+	if (unlikely(err < 0))
+		goto out;
+	if (unlikely(err))
+		return 0; /* success */
+
+	bend = sbend(sb);
+	add_branch = alloc_addbr(sb, bend + 2);
+	err = PTR_ERR(add_branch);
+	if (IS_ERR(add_branch))
+		goto out;
+
+	err = 0;
+	rw_init_nolock(&add_branch->br_wh_rwsem);
+	add_branch->br_wh = add_branch->br_plink = NULL;
+	if (unlikely(br_writable(add->perm))) {
+		err = init_br_wh(sb, /*bindex*/-1, add_branch, add->perm,
+				 add->nd.dentry, add->nd.mnt);
+		if (unlikely(err)) {
+			kfree(add_branch);
+			goto out;
+		}
+	}
+	add_branch->br_xino = NULL;
+	add_branch->br_mnt = mntget(add->nd.mnt);
+	atomic_set(&add_branch->br_wh_running, 0);
+	add_branch->br_id = new_br_id(sb);
+	add_branch->br_perm = add->perm;
+	atomic_set(&add_branch->br_count, 0);
+	add_branch->br_generation = au_sigen(sb);
+
+	sbinfo = stosi(sb);
+	dinfo = dtodi(root);
+	iinfo = itoii(root_inode);
+
+	add_bindex = add->bindex;
+	sz = sizeof(*(sbinfo->si_branch)) * (bend + 1 - add_bindex);
+	branchp = sbinfo->si_branch + add_bindex;
+	memmove(branchp + 1, branchp, sz);
+	*branchp = add_branch;
+	sz = sizeof(*hdentryp) * (bend + 1 - add_bindex);
+	hdentryp = dinfo->di_hdentry + add_bindex;
+	memmove(hdentryp + 1, hdentryp, sz);
+	hdentryp->hd_dentry = NULL;
+	sz = sizeof(*hinodep) * (bend + 1 - add_bindex);
+	hinodep = iinfo->ii_hinode + add_bindex;
+	memmove(hinodep + 1, hinodep, sz);
+	hinodep->hi_inode = NULL;
+	hinodep->hi_notify = NULL;
+
+	sbinfo->si_bend++;
+	dinfo->di_bend++;
+	iinfo->ii_bend++;
+	if (unlikely(bend < 0)) {
+		sbinfo->si_bend = 0;
+		dinfo->di_bstart = 0;
+		iinfo->ii_bstart = 0;
+	}
+	set_h_dptr(root, add_bindex, dget(add->nd.dentry));
+	set_h_iptr(root_inode, add_bindex, igrab(add->nd.dentry->d_inode), 0);
+	if (!add_bindex)
+		au_cpup_attr_all(root_inode);
+	else
+		au_add_nlink(root_inode, add->nd.dentry->d_inode);
+	maxb = add->nd.dentry->d_sb->s_maxbytes;
+	if (sb->s_maxbytes < maxb)
+		sb->s_maxbytes = maxb;
+
+	if (au_flag_test(sb, AuFlag_XINO)) {
+		struct file *base_file = stobr(sb, 0)->br_xino;
+		if (!add_bindex)
+			base_file = stobr(sb, 1)->br_xino;
+		err = xino_br(sb, add_bindex, base_file, /*do_test*/1);
+		if (unlikely(err)) {
+			AuDebugOn(add_branch->br_xino);
+			/* bad action? */
+			IOErr("err %d, force noxino\n", err);
+			err = -EIO;
+			xino_clr(sb);
+		}
+	}
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#define Verbose(sb, fmt, args...) do { \
+	if (au_flag_test(sb, AuFlag_VERBOSE)) \
+		Info(fmt, ##args); \
+	else \
+		LKTRTrace(fmt, ##args); \
+} while (0)
+
+/*
+ * test if the branch is deletable or not.
+ */
+static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex,
+			    au_gen_t sigen)
+{
+	int err, i, j, ndentry;
+	struct au_dcsub_pages dpages;
+	struct au_dpage *dpage;
+	struct dentry *d;
+	aufs_bindex_t bstart, bend;
+
+	LKTRTrace("b%d, gen%d\n", bindex, sigen);
+	SiMustWriteLock(root->d_sb);
+
+	err = au_dpages_init(&dpages, GFP_KERNEL);
+	if (unlikely(err))
+		goto out;
+	err = au_dcsub_pages(&dpages, root, NULL, NULL);
+	if (unlikely(err))
+		goto out_dpages;
+
+	for (i = 0; !err && i < dpages.ndpage; i++) {
+		dpage = dpages.dpages + i;
+		ndentry = dpage->ndentry;
+		for (j = 0; !err && j < ndentry; j++) {
+			d = dpage->dentries[j];
+			AuDebugOn(!atomic_read(&d->d_count));
+			if (au_digen(d) == sigen)
+				di_read_lock_child(d, AuLock_IR);
+			else {
+				di_write_lock_child(d);
+				err = au_reval_dpath(d, sigen);
+				if (!err)
+					di_downgrade_lock(d, AuLock_IR);
+				else {
+					di_write_unlock(d);
+					break;
+				}
+			}
+
+			bstart = dbstart(d);
+			bend = dbend(d);
+			if (bstart <= bindex
+			    && bindex <= bend
+			    && au_h_dptr_i(d, bindex)
+			    && (!S_ISDIR(d->d_inode->i_mode)
+				|| bstart == bend)) {
+				err = -EBUSY;
+				Verbose(root->d_sb, "busy %.*s\n", DLNPair(d));
+			}
+			di_read_unlock(d, AuLock_IR);
+		}
+	}
+
+ out_dpages:
+	au_dpages_free(&dpages);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int test_inode_busy(struct super_block *sb, aufs_bindex_t bindex,
+			   au_gen_t sigen)
+{
+	int err;
+	struct inode *i;
+	aufs_bindex_t bstart, bend;
+
+	LKTRTrace("b%d, gen%d\n", bindex, sigen);
+	SiMustWriteLock(sb);
+
+	err = 0;
+	list_for_each_entry(i, &sb->s_inodes, i_sb_list) {
+		AuDebugOn(!atomic_read(&i->i_count));
+		if (!list_empty(&i->i_dentry))
+			continue;
+
+		if (au_iigen(i) == sigen)
+			ii_read_lock_child(i);
+		else {
+			ii_write_lock_child(i);
+			err = au_refresh_hinode_self(i);
+			if (!err)
+				ii_downgrade_lock(i);
+			else {
+				ii_write_unlock(i);
+				break;
+			}
+		}
+
+		bstart = ibstart(i);
+		bend = ibend(i);
+		if (bstart <= bindex
+		    && bindex <= bend
+		    && au_h_iptr_i(i, bindex)
+		    && (!S_ISDIR(i->i_mode)
+			|| bstart == bend)) {
+			err = -EBUSY;
+			Verbose(sb, "busy i%lu\n", i->i_ino);
+			//au_debug_on();
+			//DbgInode(i);
+			//au_debug_off();
+			ii_read_unlock(i);
+			break;
+		}
+		ii_read_unlock(i);
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+static int test_children_busy(struct dentry *root, aufs_bindex_t bindex)
+{
+	int err;
+	au_gen_t sigen;
+
+	LKTRTrace("b%d\n", bindex);
+	SiMustWriteLock(root->d_sb);
+	DiMustWriteLock(root);
+	AuDebugOn(!kernel_locked());
+
+	sigen = au_sigen(root->d_sb);
+	DiMustNoWaiters(root);
+	IiMustNoWaiters(root->d_inode);
+	di_write_unlock(root);
+	err = test_dentry_busy(root, bindex, sigen);
+	if (!err)
+		err = test_inode_busy(root->d_sb, bindex, sigen);
+	di_write_lock_child(root); /* aufs_write_lock() calls ..._child() */
+
+	TraceErr(err);
+	return err;
+}
+
+int br_del(struct super_block *sb, struct opt_del *del, int remount)
+{
+	int err, do_wh, rerr;
+	struct dentry *root;
+	struct inode *inode, *hidden_dir;
+	aufs_bindex_t bindex, bend, br_id;
+	struct aufs_sbinfo *sbinfo;
+	struct aufs_dinfo *dinfo;
+	struct aufs_iinfo *iinfo;
+	struct aufs_branch *br;
+
+	//au_debug_on();
+	LKTRTrace("%s, %.*s\n", del->path, DLNPair(del->h_root));
+	SiMustWriteLock(sb);
+	root = sb->s_root;
+	DiMustWriteLock(root);
+	inode = root->d_inode;
+	IiMustWriteLock(inode);
+
+	err = 0;
+	bindex = au_find_dbindex(root, del->h_root);
+	if (unlikely(bindex < 0)) {
+		if (remount)
+			goto out; /* success */
+		err = -ENOENT;
+		Err("%s no such branch\n", del->path);
+		goto out;
+	}
+	LKTRTrace("bindex b%d\n", bindex);
+
+	err = -EBUSY;
+	bend = sbend(sb);
+	br = stobr(sb, bindex);
+	if (unlikely(!bend || br_count(br))) {
+		//au_debug_on();
+		LKTRTrace("bend %d, br_count %d\n", bend, br_count(br));
+		//au_debug_off();
+		goto out;
+	}
+
+	do_wh = 0;
+	hidden_dir = del->h_root->d_inode;
+	if (unlikely(br->br_wh || br->br_plink)) {
+#if 0
+		/* remove whiteout base */
+		err = init_br_wh(sb, bindex, br, AuBr_RO, del->h_root,
+				 br->br_mnt);
+		if (unlikely(err))
+			goto out;
+#else
+		dput(br->br_wh);
+		dput(br->br_plink);
+		br->br_wh = br->br_plink = NULL;
+#endif
+		do_wh = 1;
+	}
+
+	err = test_children_busy(root, bindex);
+	if (unlikely(err)) {
+		if (unlikely(do_wh))
+			goto out_wh;
+		goto out;
+	}
+
+	err = 0;
+	sbinfo = stosi(sb);
+	dinfo = dtodi(root);
+	iinfo = itoii(inode);
+
+	dput(au_h_dptr_i(root, bindex));
+	aufs_hiput(iinfo->ii_hinode + bindex);
+	br_id = br->br_id;
+	free_branch(br);
+
+	//todo: realloc and shrink memeory
+	if (bindex < bend) {
+		const aufs_bindex_t n = bend - bindex;
+		struct aufs_branch **brp;
+		struct aufs_hdentry *hdp;
+		struct aufs_hinode *hip;
+
+		brp = sbinfo->si_branch + bindex;
+		memmove(brp, brp + 1, sizeof(*brp) * n);
+		hdp = dinfo->di_hdentry + bindex;
+		memmove(hdp, hdp + 1, sizeof(*hdp) * n);
+		hip = iinfo->ii_hinode + bindex;
+		memmove(hip, hip + 1, sizeof(*hip) * n);
+	}
+	sbinfo->si_branch[0 + bend] = NULL;
+	dinfo->di_hdentry[0 + bend].hd_dentry = NULL;
+	iinfo->ii_hinode[0 + bend].hi_inode = NULL;
+	iinfo->ii_hinode[0 + bend].hi_notify = NULL;
+
+	sbinfo->si_bend--;
+	dinfo->di_bend--;
+	iinfo->ii_bend--;
+	if (!bindex)
+		au_cpup_attr_all(inode);
+	else
+		au_sub_nlink(inode, del->h_root->d_inode);
+	if (au_flag_test(sb, AuFlag_PLINK))
+		half_refresh_plink(sb, br_id);
+
+	if (sb->s_maxbytes == del->h_root->d_sb->s_maxbytes) {
+		bend--;
+		sb->s_maxbytes = 0;
+		for (bindex = 0; bindex <= bend; bindex++) {
+			unsigned long long maxb;
+			maxb = sbr_sb(sb, bindex)->s_maxbytes;
+			if (sb->s_maxbytes < maxb)
+				sb->s_maxbytes = maxb;
+		}
+	}
+	goto out; /* success */
+
+ out_wh:
+	/* revert */
+	rerr = init_br_wh(sb, bindex, br, br->br_perm, del->h_root, br->br_mnt);
+	if (rerr)
+		Warn("failed re-creating base whiteout, %s. (%d)\n",
+		     del->path, rerr);
+ out:
+	TraceErr(err);
+	//au_debug_off();
+	return err;
+}
+
+static int do_need_sigen_inc(int a, int b)
+{
+	return (br_whable(a) && !br_whable(b));
+}
+
+static int need_sigen_inc(int old, int new)
+{
+	return (do_need_sigen_inc(old, new)
+		|| do_need_sigen_inc(new, old));
+}
+
+int br_mod(struct super_block *sb, struct opt_mod *mod, int remount,
+	   int *do_update)
+{
+	int err;
+	struct dentry *root;
+	aufs_bindex_t bindex;
+	struct aufs_branch *br;
+	struct inode *hidden_dir;
+
+	LKTRTrace("%s, %.*s, 0x%x\n",
+		  mod->path, DLNPair(mod->h_root), mod->perm);
+	SiMustWriteLock(sb);
+	root = sb->s_root;
+	DiMustWriteLock(root);
+	IiMustWriteLock(root->d_inode);
+
+	bindex = au_find_dbindex(root, mod->h_root);
+	if (unlikely(bindex < 0)) {
+		if (remount)
+			return 0; /* success */
+		err = -ENOENT;
+		Err("%s no such branch\n", mod->path);
+		goto out;
+	}
+	LKTRTrace("bindex b%d\n", bindex);
+
+	hidden_dir = mod->h_root->d_inode;
+	err = test_br(sb, hidden_dir, mod->perm, mod->path);
+	if (unlikely(err))
+		goto out;
+
+	br = stobr(sb, bindex);
+	if (unlikely(br->br_perm == mod->perm))
+		return 0; /* success */
+
+	if (br_writable(br->br_perm)) {
+#if 1
+		/* remove whiteout base */
+		//todo: mod->perm?
+		err = init_br_wh(sb, bindex, br, AuBr_RO, mod->h_root,
+				 br->br_mnt);
+		if (unlikely(err))
+			goto out;
+#else
+		dput(br->br_wh);
+		dput(br->br_plink);
+		br->br_wh = br->br_plink = NULL;
+#endif
+
+		if (!br_writable(mod->perm)) {
+			/* rw --> ro, file might be mmapped */
+			struct file *file, *hf;
+
+#if 1 // test here
+			DiMustNoWaiters(root);
+			IiMustNoWaiters(root->d_inode);
+			di_write_unlock(root);
+
+			/*
+			 * no need file_list_lock()
+			 * since BKL (and sbinfo) is locked
+			 */
+			AuDebugOn(!kernel_locked());
+			list_for_each_entry(file, &sb->s_files, f_u.fu_list) {
+				LKTRTrace("%.*s\n", DLNPair(file->f_dentry));
+				if (unlikely(!au_test_aufs_file(file)))
+					continue;
+
+				fi_read_lock(file);
+				if (!S_ISREG(file->f_dentry->d_inode->i_mode)
+				    || !(file->f_mode & FMODE_WRITE)
+				    || fbstart(file) != bindex) {
+					FiMustNoWaiters(file);
+					fi_read_unlock(file);
+					continue;
+				}
+
+				// todo: already flushed?
+				hf = au_h_fptr(file);
+				hf->f_flags = au_file_roflags(hf->f_flags);
+				hf->f_mode &= ~FMODE_WRITE;
+				//put_write_access(hf->f_dentry->d_inode);
+				FiMustNoWaiters(file);
+				fi_read_unlock(file);
+			}
+
+			/* aufs_write_lock() calls ..._child() */
+			di_write_lock_child(root);
+#endif
+		}
+	}
+
+	*do_update |= need_sigen_inc(br->br_perm, mod->perm);
+	br->br_perm = mod->perm;
+
+ out:
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/branch.h linux-2.6.22-aufs/fs/aufs/branch.h
--- linux-2.6.22/fs/aufs/branch.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/branch.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: branch.h,v 1.34 2007/07/15 20:02:19 sfjro Exp $ */
+
+#ifndef __AUFS_BRANCH_H__
+#define __AUFS_BRANCH_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+#include "misc.h"
+#include "super.h"
+
+#define _AuNoNfsBranchMsg "NFS branch is not supported"
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16)
+#define AuNoNfsBranch
+#define AuNoNfsBranchMsg _AuNoNfsBranchMsg
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) \
+	&& !defined(CONFIG_AUFS_LHASH_PATCH)
+#define AuNoNfsBranch
+#define AuNoNfsBranchMsg _AuNoNfsBranchMsg \
+	", try lhash.patch (or lhash-2.6.22.patch) and CONFIG_AUFS_LHASH_PATCH"
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/* protected by superblock rwsem */
+struct aufs_branch {
+	struct file		*br_xino;
+
+	aufs_bindex_t		br_id;
+
+	int			br_perm;
+	struct vfsmount		*br_mnt;
+	atomic_t		br_count;
+
+	/* whiteout base */
+	struct aufs_rwsem	br_wh_rwsem;
+	struct dentry		*br_wh;
+	atomic_t 		br_wh_running;
+
+	/* pseudo-link dir */
+	struct dentry		*br_plink;
+
+	au_gen_t		br_generation;
+};
+
+/* ---------------------------------------------------------------------- */
+
+/* branch permission and attribute */
+enum {
+	AuBr_RW,		/* writable, linkable wh */
+	AuBr_RO,		/* readonly, no wh */
+	AuBr_RR,		/* natively readonly, no wh */
+
+	AuBr_RWNoLinkWH,	/* un-linkable whiteouts */
+
+	AuBr_ROWH,
+	AuBr_RRWH,		/* whiteout-able */
+
+	AuBr_Last
+};
+
+static inline int br_writable(int brperm)
+{
+	return (brperm == AuBr_RW
+		|| brperm == AuBr_RWNoLinkWH);
+}
+
+static inline int br_whable(int brperm)
+{
+	return (brperm == AuBr_RW
+		|| brperm == AuBr_ROWH
+		|| brperm == AuBr_RRWH);
+}
+
+static inline int br_linkable_wh(int brperm)
+{
+	return (brperm == AuBr_RW);
+}
+
+static inline int br_hinotifyable(int brperm)
+{
+#ifdef CONFIG_AUFS_HINOTIFY
+	return (brperm != AuBr_RR
+		&& brperm != AuBr_RRWH);
+#else
+	return 0;
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct aufs_sbinfo;
+void free_branches(struct aufs_sbinfo *sinfo);
+int br_rdonly(struct aufs_branch *br);
+int au_is_rr(struct super_block *h_sb);
+int find_brindex(struct super_block *sb, aufs_bindex_t br_id);
+int find_rw_br(struct super_block *sb, aufs_bindex_t bend);
+int find_rw_parent_br(struct dentry *dentry, aufs_bindex_t bend);
+struct opt_add;
+int br_add(struct super_block *sb, struct opt_add *add, int remount);
+struct opt_del;
+int br_del(struct super_block *sb, struct opt_del *del, int remount);
+struct opt_mod;
+int br_mod(struct super_block *sb, struct opt_mod *mod, int remount,
+	   int *do_update);
+
+/* ---------------------------------------------------------------------- */
+
+static inline int br_count(struct aufs_branch *br)
+{
+	return atomic_read(&br->br_count);
+}
+
+static inline int br_get(struct aufs_branch *br)
+{
+	return atomic_inc_return(&br->br_count);
+}
+
+static inline int br_put(struct aufs_branch *br)
+{
+	return atomic_dec_return(&br->br_count);
+}
+
+static inline au_gen_t au_br_gen(struct aufs_branch *br)
+{
+	return br->br_generation;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* Superblock to branch */
+static inline aufs_bindex_t sbr_id(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return stobr(sb, bindex)->br_id;
+}
+
+static inline
+struct vfsmount *sbr_mnt(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return stobr(sb, bindex)->br_mnt;
+}
+
+static inline
+struct super_block *sbr_sb(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return sbr_mnt(sb, bindex)->mnt_sb;
+}
+
+#if 0
+static inline int sbr_count(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return br_count(stobr(sb, bindex));
+}
+
+static inline void sbr_get(struct super_block *sb, aufs_bindex_t bindex)
+{
+	br_get(stobr(sb, bindex));
+}
+#endif
+
+static inline void sbr_put(struct super_block *sb, aufs_bindex_t bindex)
+{
+	br_put(stobr(sb, bindex));
+}
+
+static inline int sbr_perm(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return stobr(sb, bindex)->br_perm;
+}
+
+static inline int sbr_is_whable(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return br_whable(sbr_perm(sb, bindex));
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_LHASH_PATCH
+static inline struct vfsmount *au_do_nfsmnt(struct vfsmount *h_mnt)
+{
+	if (!au_is_nfs(h_mnt->mnt_sb))
+		return NULL;
+	return h_mnt;
+}
+
+/* it doesn't mntget() */
+static inline
+struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return au_do_nfsmnt(sbr_mnt(sb, bindex));
+}
+#else
+static inline struct vfsmount *au_do_nfsmnt(struct vfsmount *h_mnt)
+{
+	return NULL;
+}
+
+static inline
+struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex)
+{
+	return NULL;
+}
+#endif /* CONFIG_AUFS_LHASH_PATCH */
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * br_wh_read_lock, br_wh_write_lock
+ * br_wh_read_unlock, br_wh_write_unlock, br_wh_downgrade_lock
+ */
+SimpleRwsemFuncs(br_wh, struct aufs_branch *br, br->br_wh_rwsem);
+
+/* to debug easier, do not make them inlined functions */
+#define BrWhMustReadLock(br) do { \
+	/* SiMustAnyLock(sb); */ \
+	RwMustReadLock(&(br)->br_wh_rwsem); \
+} while (0)
+
+#define BrWhMustWriteLock(br) do { \
+	/* SiMustAnyLock(sb); */ \
+	RwMustWriteLock(&(br)->br_wh_rwsem); \
+} while (0)
+
+#define BrWhMustAnyLock(br) do { \
+	/* SiMustAnyLock(sb); */ \
+	RwMustAnyLock(&(br)->br_wh_rwsem); \
+} while (0)
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_BRANCH_H__ */
diff -ruN linux-2.6.22/fs/aufs/cpup.c linux-2.6.22-aufs/fs/aufs/cpup.c
--- linux-2.6.22/fs/aufs/cpup.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/cpup.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,909 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: cpup.c,v 1.45 2007/07/15 20:02:36 sfjro Exp $ */
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#include <linux/uaccess.h>
+#else
+#include <asm/uaccess.h>
+#endif
+#include "aufs.h"
+
+/* violent cpup_attr_*() functions don't care inode lock */
+void au_cpup_attr_timesizes(struct inode *inode)
+{
+	struct inode *hidden_inode;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	//IMustLock(inode);
+	hidden_inode = au_h_iptr(inode);
+	AuDebugOn(!hidden_inode);
+	//IMustLock(!hidden_inode);
+
+	inode->i_atime = hidden_inode->i_atime;
+	inode->i_mtime = hidden_inode->i_mtime;
+	inode->i_ctime = hidden_inode->i_ctime;
+	spin_lock(&inode->i_lock);
+	i_size_write(inode, i_size_read(hidden_inode));
+	inode->i_blocks = hidden_inode->i_blocks;
+	spin_unlock(&inode->i_lock);
+}
+
+void au_cpup_attr_nlink(struct inode *inode)
+{
+	struct inode *h_inode;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	//IMustLock(inode);
+	AuDebugOn(!inode->i_mode);
+
+	h_inode = au_h_iptr(inode);
+	inode->i_nlink = h_inode->i_nlink;
+
+	/*
+	 * fewer nlink makes find(1) noisy, but larger nlink doesn't.
+	 * it may includes whplink directory.
+	 */
+	if (unlikely(S_ISDIR(h_inode->i_mode))) {
+		aufs_bindex_t bindex, bend;
+		bend = ibend(inode);
+		for (bindex = ibstart(inode) + 1; bindex <= bend; bindex++) {
+			h_inode = au_h_iptr_i(inode, bindex);
+			if (h_inode)
+				au_add_nlink(inode, h_inode);
+		}
+	}
+}
+
+void au_cpup_attr_changeable(struct inode *inode)
+{
+	struct inode *hidden_inode;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	//IMustLock(inode);
+	hidden_inode = au_h_iptr(inode);
+	AuDebugOn(!hidden_inode);
+
+	inode->i_mode = hidden_inode->i_mode;
+	inode->i_uid = hidden_inode->i_uid;
+	inode->i_gid = hidden_inode->i_gid;
+	au_cpup_attr_timesizes(inode);
+
+	//??
+	inode->i_flags = hidden_inode->i_flags;
+}
+
+void au_cpup_igen(struct inode *inode, struct inode *h_inode)
+{
+	inode->i_generation = h_inode->i_generation;
+	itoii(inode)->ii_hsb1 = h_inode->i_sb;
+}
+
+void au_cpup_attr_all(struct inode *inode)
+{
+	struct inode *hidden_inode;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	//IMustLock(inode);
+	hidden_inode = au_h_iptr(inode);
+	AuDebugOn(!hidden_inode);
+
+	au_cpup_attr_changeable(inode);
+	if (inode->i_nlink > 0)
+		au_cpup_attr_nlink(inode);
+
+	switch (inode->i_mode & S_IFMT) {
+	case S_IFBLK:
+	case S_IFCHR:
+		inode->i_rdev = hidden_inode->i_rdev;
+	}
+	inode->i_blkbits = hidden_inode->i_blkbits;
+	au_cpup_attr_blksize(inode, hidden_inode);
+	au_cpup_igen(inode, hidden_inode);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* Note: dt_dentry and dt_hidden_dentry are not dget/dput-ed */
+
+/* keep the timestamps of the parent dir when cpup */
+void dtime_store(struct dtime *dt, struct dentry *dentry,
+		 struct dentry *hidden_dentry)
+{
+	struct inode *inode;
+
+	TraceEnter();
+	AuDebugOn(!dentry || !hidden_dentry || !hidden_dentry->d_inode);
+
+	dt->dt_dentry = dentry;
+	dt->dt_h_dentry = hidden_dentry;
+	inode = hidden_dentry->d_inode;
+	dt->dt_atime = inode->i_atime;
+	dt->dt_mtime = inode->i_mtime;
+	//smp_mb();
+}
+
+// todo: remove extra parameter
+void dtime_revert(struct dtime *dt, int h_parent_is_locked)
+{
+	struct iattr attr;
+	int err;
+	struct dentry *dentry;
+
+	LKTRTrace("h_parent locked %d\n", h_parent_is_locked);
+
+	attr.ia_atime = dt->dt_atime;
+	attr.ia_mtime = dt->dt_mtime;
+	attr.ia_valid = ATTR_FORCE | ATTR_MTIME | ATTR_MTIME_SET
+		| ATTR_ATIME | ATTR_ATIME_SET;
+	//smp_mb();
+	dentry = NULL;
+	if (!h_parent_is_locked /* && !IS_ROOT(dt->dt_dentry) */)
+		dentry = dt->dt_dentry;
+	err = vfsub_notify_change(dt->dt_h_dentry, &attr,
+				  need_dlgt(dt->dt_dentry->d_sb));
+	if (unlikely(err))
+		Warn("restoring timestamps failed(%d). ignored\n", err);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int cpup_iattr(struct dentry *hidden_dst, struct dentry *hidden_src,
+		      int dlgt)
+{
+	int err;
+	struct iattr ia;
+	struct inode *hidden_isrc, *hidden_idst;
+
+	LKTRTrace("%.*s\n", DLNPair(hidden_dst));
+	hidden_idst = hidden_dst->d_inode;
+	//IMustLock(hidden_idst);
+	hidden_isrc = hidden_src->d_inode;
+	//IMustLock(hidden_isrc);
+
+	ia.ia_valid = ATTR_FORCE | ATTR_MODE | ATTR_UID | ATTR_GID
+		| ATTR_ATIME | ATTR_MTIME
+		| ATTR_ATIME_SET | ATTR_MTIME_SET;
+	ia.ia_mode = hidden_isrc->i_mode;
+	ia.ia_uid = hidden_isrc->i_uid;
+	ia.ia_gid = hidden_isrc->i_gid;
+	ia.ia_atime = hidden_isrc->i_atime;
+	ia.ia_mtime = hidden_isrc->i_mtime;
+	err = vfsub_notify_change(hidden_dst, &ia, dlgt);
+	//if (LktrCond) err = -1;
+	if (!err)
+		hidden_idst->i_flags = hidden_isrc->i_flags; //??
+
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * to support a sparse file which is opened with O_APPEND,
+ * we need to close the file.
+ */
+static int cpup_regular(struct dentry *dentry, aufs_bindex_t bdst,
+			aufs_bindex_t bsrc, loff_t len)
+{
+	int err, i, sparse;
+	struct super_block *sb;
+	struct inode *hidden_inode;
+	enum {SRC, DST};
+	struct {
+		aufs_bindex_t bindex;
+		unsigned int flags;
+		struct dentry *dentry;
+		struct file *file;
+		void *label, *label_file;
+	} *h, hidden[] = {
+		{
+			.bindex = bsrc,
+			.flags = O_RDONLY | O_NOATIME | O_LARGEFILE,
+			.file = NULL,
+			.label = &&out,
+			.label_file = &&out_src_file
+		},
+		{
+			.bindex = bdst,
+			.flags = O_WRONLY | O_NOATIME | O_LARGEFILE,
+			.file = NULL,
+			.label = &&out_src_file,
+			.label_file = &&out_dst_file
+		}
+	};
+
+	LKTRTrace("dentry %.*s, bdst %d, bsrc %d, len %lld\n",
+		  DLNPair(dentry), bdst, bsrc, len);
+	AuDebugOn(bsrc <= bdst);
+	AuDebugOn(!len);
+	sb = dentry->d_sb;
+	AuDebugOn(test_ro(sb, bdst, dentry->d_inode));
+	/* bsrc branch can be ro/rw. */
+
+	h = hidden;
+	for (i = 0; i < 2; i++, h++) {
+		h->dentry = au_h_dptr_i(dentry, h->bindex);
+		AuDebugOn(!h->dentry);
+		hidden_inode = h->dentry->d_inode;
+		AuDebugOn(!hidden_inode || !S_ISREG(hidden_inode->i_mode));
+		h->file = hidden_open(dentry, h->bindex, h->flags);
+		//if (LktrCond)
+		//{fput(h->file);sbr_put(sb, h->bindex);h->file=ERR_PTR(-1);}
+		err = PTR_ERR(h->file);
+		if (IS_ERR(h->file))
+			goto *h->label;
+		err = -EINVAL;
+		if (unlikely(!h->file->f_op))
+			goto *h->label_file;
+	}
+
+	/* stop updating while we copyup */
+	IMustLock(hidden[SRC].dentry->d_inode);
+	sparse = 0;
+	err = au_copy_file(hidden[DST].file, hidden[SRC].file, len, sb,
+			   &sparse);
+
+#if 0
+	/* sparse file: update i_blocks next time */
+	if (unlikely(!err && sparse))
+		d_drop(dentry);
+#endif
+
+ out_dst_file:
+	fput(hidden[DST].file);
+	sbr_put(sb, hidden[DST].bindex);
+ out_src_file:
+	fput(hidden[SRC].file);
+	sbr_put(sb, hidden[SRC].bindex);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+// unnecessary?
+unsigned int au_flags_cpup(unsigned int init, struct dentry *parent)
+{
+	if (unlikely(parent && IS_ROOT(parent)))
+		init |= CPUP_LOCKED_GHDIR;
+	return init;
+}
+
+/* return with hidden dst inode is locked */
+static int cpup_entry(struct dentry *dentry, aufs_bindex_t bdst,
+		      aufs_bindex_t bsrc, loff_t len, unsigned int flags,
+		      int dlgt)
+{
+	int err, isdir, symlen;
+	struct dentry *hidden_src, *hidden_dst, *hidden_parent, *parent;
+	struct inode *hidden_inode, *hidden_dir, *dir;
+	struct dtime dt;
+	umode_t mode;
+	char *sym;
+	mm_segment_t old_fs;
+	const int do_dt = flags & CPUP_DTIME;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %Ld, flags 0x%x\n",
+		  DLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
+		  flags);
+	sb = dentry->d_sb;
+	AuDebugOn(bdst >= bsrc || test_ro(sb, bdst, NULL));
+	/* bsrc branch can be ro/rw. */
+
+	hidden_src = au_h_dptr_i(dentry, bsrc);
+	AuDebugOn(!hidden_src);
+	hidden_inode = hidden_src->d_inode;
+	AuDebugOn(!hidden_inode);
+
+	/* stop refrencing while we are creating */
+	parent = dget_parent(dentry);
+	dir = parent->d_inode;
+	hidden_dst = au_h_dptr_i(dentry, bdst);
+	AuDebugOn(hidden_dst && hidden_dst->d_inode);
+	hidden_parent = dget_parent(hidden_dst);
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+
+	if (do_dt)
+		dtime_store(&dt, parent, hidden_parent);
+
+	isdir = 0;
+	mode = hidden_inode->i_mode;
+	switch (mode & S_IFMT) {
+	case S_IFREG:
+		/* stop updating while we are referencing */
+		IMustLock(hidden_inode);
+		err = vfsub_create(hidden_dir, hidden_dst, mode | S_IWUSR, NULL,
+				   dlgt);
+		//if (LktrCond) {vfs_unlink(hidden_dir, hidden_dst); err = -1;}
+		if (!err) {
+			loff_t l = i_size_read(hidden_inode);
+			if (len == -1 || l < len)
+				len = l;
+			if (len) {
+				err = cpup_regular(dentry, bdst, bsrc, len);
+				//if (LktrCond) err = -1;
+			}
+			if (unlikely(err)) {
+				int rerr;
+				rerr = vfsub_unlink(hidden_dir, hidden_dst,
+						    dlgt);
+				if (rerr) {
+					IOErr("failed unlinking cpup-ed %.*s"
+					      "(%d, %d)\n",
+					      DLNPair(hidden_dst), err, rerr);
+					err = -EIO;
+				}
+			}
+		}
+		break;
+	case S_IFDIR:
+		isdir = 1;
+		err = vfsub_mkdir(hidden_dir, hidden_dst, mode, dlgt);
+		//if (LktrCond) {vfs_rmdir(hidden_dir, hidden_dst); err = -1;}
+		if (!err) {
+			/* setattr case: dir is not locked */
+			if (0 && ibstart(dir) == bdst)
+				au_cpup_attr_nlink(dir);
+			au_cpup_attr_nlink(dentry->d_inode);
+		}
+		break;
+	case S_IFLNK:
+		err = -ENOMEM;
+		sym = __getname();
+		//if (LktrCond) {__putname(sym); sym = NULL;}
+		if (unlikely(!sym))
+			break;
+		old_fs = get_fs();
+		set_fs(KERNEL_DS);
+		err = symlen = hidden_inode->i_op->readlink
+			(hidden_src, (char __user*)sym, PATH_MAX);
+		//if (LktrCond) err = symlen = -1;
+		set_fs(old_fs);
+		if (symlen > 0) {
+			sym[symlen] = 0;
+			err = vfsub_symlink(hidden_dir, hidden_dst, sym, mode,
+					    dlgt);
+			//if (LktrCond)
+			//{vfs_unlink(hidden_dir, hidden_dst); err = -1;}
+		}
+		__putname(sym);
+		break;
+	case S_IFCHR:
+	case S_IFBLK:
+		AuDebugOn(!capable(CAP_MKNOD));
+		/*FALLTHROUGH*/
+	case S_IFIFO:
+	case S_IFSOCK:
+		err = vfsub_mknod(hidden_dir, hidden_dst, mode,
+				  hidden_inode->i_rdev, dlgt);
+		//if (LktrCond) {vfs_unlink(hidden_dir, hidden_dst); err = -1;}
+		break;
+	default:
+		IOErr("Unknown inode type 0%o\n", mode);
+		err = -EIO;
+	}
+
+	if (do_dt)
+		dtime_revert(&dt, flags & CPUP_LOCKED_GHDIR);
+	dput(parent);
+	dput(hidden_parent);
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * copyup the @dentry from @bsrc to @bdst.
+ * the caller must set the both of hidden dentries.
+ * @len is for trucating when it is -1 copyup the entire file.
+ */
+int cpup_single(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
+		loff_t len, unsigned int flags)
+{
+	int err, rerr, isdir, dlgt;
+	struct dentry *hidden_src, *hidden_dst, *parent, *h_parent;
+	struct inode *dst_inode, *hidden_dir, *inode, *src_inode;
+	struct super_block *sb;
+	aufs_bindex_t old_ibstart;
+	struct dtime dt;
+
+	LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %Ld, flags 0x%x\n",
+		  DLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
+		  flags);
+	sb = dentry->d_sb;
+	AuDebugOn(bsrc <= bdst);
+	hidden_dst = au_h_dptr_i(dentry, bdst);
+	AuDebugOn(!hidden_dst || hidden_dst->d_inode);
+	h_parent = dget_parent(hidden_dst);
+	hidden_dir = h_parent->d_inode;
+	IMustLock(hidden_dir);
+	hidden_src = au_h_dptr_i(dentry, bsrc);
+	AuDebugOn(!hidden_src || !hidden_src->d_inode);
+	inode = dentry->d_inode;
+	IiMustWriteLock(inode);
+
+	dlgt = need_dlgt(sb);
+	dst_inode = au_h_iptr_i(inode, bdst);
+	if (unlikely(dst_inode)) {
+		if (unlikely(!au_flag_test(sb, AuFlag_PLINK))) {
+			err = -EIO;
+			IOErr("i%lu exists on a upper branch "
+			      "but plink is disabled\n", inode->i_ino);
+			goto out;
+		}
+
+		if (dst_inode->i_nlink) {
+			hidden_src = lkup_plink(sb, bdst, inode);
+			err = PTR_ERR(hidden_src);
+			if (IS_ERR(hidden_src))
+				goto out;
+			AuDebugOn(!hidden_src->d_inode);
+			/* vfs_link() does lock the inode */
+			err = vfsub_link(hidden_src, hidden_dir, hidden_dst,
+					 dlgt);
+			dput(hidden_src);
+			goto out;
+		} else
+			//todo: cpup_wh_file
+			/* udba work */
+			au_update_brange(inode, 1);
+	}
+
+	old_ibstart = ibstart(inode);
+	err = cpup_entry(dentry, bdst, bsrc, len, flags, dlgt);
+	if (unlikely(err))
+		goto out;
+	dst_inode = hidden_dst->d_inode;
+	vfsub_i_lock_nested(dst_inode, AuLsc_I_CHILD2);
+
+	//todo: test dlgt
+	err = cpup_iattr(hidden_dst, hidden_src, dlgt);
+	//if (LktrCond) err = -1;
+#if 0 // xattr
+	if (0 && !err)
+		err = cpup_xattrs(hidden_src, hidden_dst);
+#endif
+	isdir = S_ISDIR(dst_inode->i_mode);
+	if (!err) {
+		if (bdst < old_ibstart)
+			set_ibstart(inode, bdst);
+		set_h_iptr(inode, bdst, igrab(dst_inode),
+			   au_hi_flags(inode, isdir));
+		vfsub_i_unlock(dst_inode);
+		src_inode = hidden_src->d_inode;
+		if (!isdir
+		    && src_inode->i_nlink > 1
+		    && au_flag_test(sb, AuFlag_PLINK))
+				append_plink(sb, inode, hidden_dst, bdst);
+		goto out; /* success */
+	}
+
+	/* revert */
+	vfsub_i_unlock(dst_inode);
+	parent = dget_parent(dentry);
+	dtime_store(&dt, parent, h_parent);
+	dput(parent);
+	if (!isdir)
+		rerr = vfsub_unlink(hidden_dir, hidden_dst, dlgt);
+	else
+		rerr = vfsub_rmdir(hidden_dir, hidden_dst, dlgt);
+	//rerr = -1;
+	dtime_revert(&dt, flags & CPUP_LOCKED_GHDIR);
+	if (rerr) {
+		IOErr("failed removing broken entry(%d, %d)\n", err, rerr);
+		err = -EIO;
+	}
+
+ out:
+	dput(h_parent);
+	TraceErr(err);
+	return err;
+}
+
+struct cpup_single_args {
+	int *errp;
+	struct dentry *dentry;
+	aufs_bindex_t bdst, bsrc;
+	loff_t len;
+	unsigned int flags;
+};
+
+static void call_cpup_single(void *args)
+{
+	struct cpup_single_args *a = args;
+	*a->errp = cpup_single(a->dentry, a->bdst, a->bsrc, a->len, a->flags);
+}
+
+int sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+		    aufs_bindex_t bsrc, loff_t len, unsigned int flags)
+{
+	int err, wkq_err;
+	struct dentry *hidden_dentry;
+	umode_t mode;
+
+	LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %Ld, flags 0x%x\n",
+		  DLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
+		  flags);
+
+	hidden_dentry = au_h_dptr_i(dentry, bsrc);
+	mode = hidden_dentry->d_inode->i_mode & S_IFMT;
+	if ((mode != S_IFCHR && mode != S_IFBLK)
+	    || capable(CAP_MKNOD))
+		err = cpup_single(dentry, bdst, bsrc, len, flags);
+	else {
+		struct cpup_single_args args = {
+			.errp	= &err,
+			.dentry	= dentry,
+			.bdst	= bdst,
+			.bsrc	= bsrc,
+			.len	= len,
+			.flags	= flags
+		};
+		wkq_err = au_wkq_wait(call_cpup_single, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * copyup the @dentry from the first active hidden branch to @bdst,
+ * using cpup_single().
+ */
+int cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		unsigned int flags)
+{
+	int err;
+	struct inode *inode;
+	aufs_bindex_t bsrc, bend;
+
+	LKTRTrace("%.*s, bdst %d, len %Ld, flags 0x%x\n",
+		  DLNPair(dentry), bdst, len, flags);
+	inode = dentry->d_inode;
+	AuDebugOn(!S_ISDIR(inode->i_mode) && dbstart(dentry) < bdst);
+
+	bend = dbend(dentry);
+	for (bsrc = bdst + 1; bsrc <= bend; bsrc++)
+		if (au_h_dptr_i(dentry, bsrc))
+			break;
+	AuDebugOn(!au_h_dptr_i(dentry, bsrc));
+
+	err = lkup_neg(dentry, bdst);
+	//err = -1;
+	if (!err) {
+		err = cpup_single(dentry, bdst, bsrc, len, flags);
+		if (!err)
+			return 0; /* success */
+
+		/* revert */
+		set_h_dptr(dentry, bdst, NULL);
+		set_dbstart(dentry, bsrc);
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+struct cpup_simple_args {
+	int *errp;
+	struct dentry *dentry;
+	aufs_bindex_t bdst;
+	loff_t len;
+	unsigned int flags;
+};
+
+static void call_cpup_simple(void *args)
+{
+	struct cpup_simple_args *a = args;
+	*a->errp = cpup_simple(a->dentry, a->bdst, a->len, a->flags);
+}
+
+int sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		    unsigned int flags)
+{
+	int err, do_sio, dlgt, wkq_err;
+	struct dentry *parent;
+	struct inode *hidden_dir, *dir;
+
+	LKTRTrace("%.*s, b%d, len %Ld, flags 0x%x\n",
+		  DLNPair(dentry), bdst, len, flags);
+
+	parent = dget_parent(dentry);
+	dir = parent->d_inode;
+	hidden_dir = au_h_iptr_i(dir, bdst);
+	dlgt = need_dlgt(dir->i_sb);
+	do_sio = au_test_perm(hidden_dir, MAY_EXEC | MAY_WRITE, dlgt);
+	if (!do_sio) {
+		umode_t mode = dentry->d_inode->i_mode & S_IFMT;
+		do_sio = ((mode == S_IFCHR || mode == S_IFBLK)
+			  && !capable(CAP_MKNOD));
+	}
+	if (!do_sio)
+		err = cpup_simple(dentry, bdst, len, flags);
+	else {
+		struct cpup_simple_args args = {
+			.errp	= &err,
+			.dentry	= dentry,
+			.bdst	= bdst,
+			.len	= len,
+			.flags	= flags
+		};
+		wkq_err = au_wkq_wait(call_cpup_simple, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+
+	dput(parent);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * copyup the deleted file for writing.
+ */
+int cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+	    struct file *file)
+{
+	int err;
+	struct dentry *parent, *h_parent, *wh_dentry, *h_dentry_bdst,
+		*h_dentry_bstart;
+	struct inode *h_dir;
+	struct super_block *sb;
+	struct lkup_args lkup;
+	struct dtime dt;
+	struct aufs_dinfo *dinfo;
+	aufs_bindex_t bstart;
+
+	LKTRTrace("%.*s, bdst %d, len %Lu\n", DLNPair(dentry), bdst, len);
+	AuDebugOn(S_ISDIR(dentry->d_inode->i_mode)
+		  || (file && !(file->f_mode & FMODE_WRITE)));
+	DiMustWriteLock(dentry);
+
+	parent = dget_parent(dentry);
+	IiMustAnyLock(parent->d_inode);
+	h_parent = au_h_dptr_i(parent, bdst);
+	AuDebugOn(!h_parent);
+	h_dir = h_parent->d_inode;
+	AuDebugOn(!h_dir);
+	IMustLock(h_dir);
+
+	sb = parent->d_sb;
+	lkup.nfsmnt = au_nfsmnt(sb, bdst);
+	lkup.dlgt = need_dlgt(sb);
+	wh_dentry = lkup_whtmp(h_parent, &dentry->d_name, &lkup);
+	//if (LktrCond) {dput(wh_dentry); wh_dentry = ERR_PTR(-1);}
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	dtime_store(&dt, parent, h_parent);
+	dinfo = dtodi(dentry);
+	bstart = dinfo->di_bstart;
+	h_dentry_bdst = dinfo->di_hdentry[0 + bdst].hd_dentry;
+	dinfo->di_bstart = bdst;
+	dinfo->di_hdentry[0 + bdst].hd_dentry = wh_dentry;
+	h_dentry_bstart = dinfo->di_hdentry[0 + bstart].hd_dentry;
+	if (file)
+		dinfo->di_hdentry[0 + bstart].hd_dentry
+			= au_h_fptr(file)->f_dentry;
+	err = cpup_single(dentry, bdst, bstart, len,
+			  au_flags_cpup(!CPUP_DTIME, parent));
+	//if (LktrCond) err = -1;
+	if (!err && file) {
+		err = au_reopen_nondir(file);
+		//err = -1;
+		dinfo->di_hdentry[0 + bstart].hd_dentry = h_dentry_bstart;
+	}
+	dinfo->di_hdentry[0 + bdst].hd_dentry = h_dentry_bdst;
+	dinfo->di_bstart = bstart;
+	if (unlikely(err))
+		goto out_wh;
+
+	AuDebugOn(!d_unhashed(dentry));
+	/* dget first to force sillyrename on nfs */
+	dget(wh_dentry);
+	err = vfsub_unlink(h_dir, wh_dentry, lkup.dlgt);
+	//if (LktrCond) err = -1;
+	if (unlikely(err)) {
+		IOErr("failed remove copied-up tmp file %.*s(%d)\n",
+		      DLNPair(wh_dentry), err);
+		err = -EIO;
+	}
+	dtime_revert(&dt, !CPUP_LOCKED_GHDIR);
+	set_hi_wh(dentry->d_inode, bdst, wh_dentry);
+
+ out_wh:
+	dput(wh_dentry);
+ out:
+	dput(parent);
+	TraceErr(err);
+	//au_debug_off();
+	return err;
+}
+
+struct cpup_wh_args {
+	int *errp;
+	struct dentry *dentry;
+	aufs_bindex_t bdst;
+	loff_t len;
+	struct file *file;
+};
+
+static void call_cpup_wh(void *args)
+{
+	struct cpup_wh_args *a = args;
+	*a->errp = cpup_wh(a->dentry, a->bdst, a->len, a->file);
+}
+
+int sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		struct file *file)
+{
+	int err, wkq_err;
+	struct dentry *parent;
+	struct inode *dir, *h_dir;
+
+	TraceEnter();
+	parent = dget_parent(dentry);
+	dir = parent->d_inode;
+	IiMustAnyLock(dir);
+	h_dir = au_h_iptr_i(dir, bdst);
+	IMustLock(h_dir);
+
+	if (!au_test_perm(h_dir, MAY_EXEC | MAY_WRITE, need_dlgt(dentry->d_sb)))
+		err = cpup_wh(dentry, bdst, len, file);
+	else {
+		struct cpup_wh_args args = {
+			.errp	= &err,
+			.dentry	= dentry,
+			.bdst	= bdst,
+			.len	= len,
+			.file	= file
+		};
+		wkq_err = au_wkq_wait(call_cpup_wh, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+	dput(parent);
+
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+//todo: dcsub
+/* cf. revalidate function in file.c */
+int cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked)
+{
+	int err;
+	struct super_block *sb;
+	struct dentry *d, *parent, *hidden_parent;
+	unsigned int udba;
+
+	LKTRTrace("%.*s, b%d, parent i%lu, locked %p\n",
+		  DLNPair(dentry), bdst, parent_ino(dentry), locked);
+	sb = dentry->d_sb;
+	AuDebugOn(test_ro(sb, bdst, NULL));
+	parent = dentry->d_parent;
+	IiMustWriteLock(parent->d_inode);
+	if (unlikely(IS_ROOT(parent)))
+		return 0;
+	if (locked) {
+		DiMustAnyLock(locked);
+		IiMustAnyLock(locked->d_inode);
+	}
+
+	/* slow loop, keep it simple and stupid */
+	err = 0;
+	udba = au_flag_test_udba_inotify(sb);
+	while (1) {
+		parent = dentry->d_parent; // dget_parent()
+		hidden_parent = au_h_dptr_i(parent, bdst);
+		if (hidden_parent)
+			return 0; /* success */
+
+		/* find top dir which is needed to cpup */
+		do {
+			d = parent;
+			parent = d->d_parent; // dget_parent()
+			if (parent != locked)
+				di_read_lock_parent3(parent, !AuLock_IR);
+			hidden_parent = au_h_dptr_i(parent, bdst);
+			if (parent != locked)
+				di_read_unlock(parent, !AuLock_IR);
+		} while (!hidden_parent);
+
+		if (d != dentry->d_parent)
+			di_write_lock_child3(d);
+
+		/* somebody else might create while we were sleeping */
+		if (!au_h_dptr_i(d, bdst) || !au_h_dptr_i(d, bdst)->d_inode) {
+			struct inode *h_dir = hidden_parent->d_inode,
+				*dir = parent->d_inode,
+				*h_gdir, *gdir;
+
+			if (au_h_dptr_i(d, bdst))
+				au_update_dbstart(d);
+			//AuDebugOn(dbstart(d) <= bdst);
+			if (parent != locked)
+				di_read_lock_parent3(parent, AuLock_IR);
+			h_gdir = gdir = NULL;
+			if (unlikely(udba && !IS_ROOT(parent))) {
+				gdir = parent->d_parent->d_inode;
+				h_gdir = hidden_parent->d_parent->d_inode;
+				hgdir_lock(h_gdir, gdir, bdst);
+			}
+			hdir_lock(h_dir, dir, bdst);
+			err = sio_cpup_simple(d, bdst, -1,
+					      au_flags_cpup(CPUP_DTIME,
+							    parent));
+			//if (LktrCond) err = -1;
+			hdir_unlock(h_dir, dir, bdst);
+			if (unlikely(gdir))
+				hdir_unlock(h_gdir, gdir, bdst);
+			if (parent != locked)
+				di_read_unlock(parent, AuLock_IR);
+		}
+
+		if (d != dentry->d_parent)
+			di_write_unlock(d);
+		if (unlikely(err))
+			break;
+	}
+
+// out:
+	TraceErr(err);
+	return err;
+}
+
+int test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
+		       struct dentry *locked)
+{
+	int err;
+	struct dentry *parent;
+	struct inode *dir;
+
+	parent = dentry->d_parent;
+	dir = parent->d_inode;
+	LKTRTrace("%.*s, b%d, parent i%lu, locked %p\n",
+		  DLNPair(dentry), bdst, dir->i_ino, locked);
+	DiMustReadLock(parent);
+	IiMustReadLock(dir);
+
+	if (au_h_iptr_i(dir, bdst))
+		return 0;
+
+	err = 0;
+	di_read_unlock(parent, AuLock_IR);
+	di_write_lock_parent(parent);
+	if (au_h_iptr_i(dir, bdst))
+		goto out;
+
+	err = cpup_dirs(dentry, bdst, locked);
+
+ out:
+	di_downgrade_lock(parent, AuLock_IR);
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/cpup.h linux-2.6.22-aufs/fs/aufs/cpup.h
--- linux-2.6.22/fs/aufs/cpup.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/cpup.h	2007-07-18 13:53:00.000000000 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: cpup.h,v 1.18 2007/07/09 05:42:26 sfjro Exp $ */
+
+#ifndef __AUFS_CPUP_H__
+#define __AUFS_CPUP_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+
+static inline
+void au_cpup_attr_blksize(struct inode *inode, struct inode *h_inode)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19)
+	inode->i_blksize = h_inode->i_blksize;
+#endif
+}
+
+void au_cpup_attr_timesizes(struct inode *inode);
+void au_cpup_attr_nlink(struct inode *inode);
+void au_cpup_attr_changeable(struct inode *inode);
+void au_cpup_igen(struct inode *inode, struct inode *h_inode);
+void au_cpup_attr_all(struct inode *inode);
+
+#define CPUP_DTIME		1	/* do dtime_store/revert */
+// todo: remove this
+#define CPUP_LOCKED_GHDIR	2	/* grand parent hidden dir is locked */
+unsigned int au_flags_cpup(unsigned int init, struct dentry *parent);
+
+int cpup_single(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
+		loff_t len, unsigned int flags);
+int sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+		    aufs_bindex_t bsrc, loff_t len, unsigned int flags);
+int cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		unsigned int flags);
+int sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		    unsigned int flags);
+int cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+	    struct file *file);
+int sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		struct file *file);
+
+int cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked);
+int test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
+		       struct dentry *locked);
+
+/* keep timestamps when copyup */
+struct dtime {
+	struct dentry *dt_dentry, *dt_h_dentry;
+	struct timespec dt_atime, dt_mtime;
+};
+void dtime_store(struct dtime *dt, struct dentry *dentry,
+		 struct dentry *h_dentry);
+void dtime_revert(struct dtime *dt, int h_parent_is_locked);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_CPUP_H__ */
diff -ruN linux-2.6.22/fs/aufs/dcsub.c linux-2.6.22-aufs/fs/aufs/dcsub.c
--- linux-2.6.22/fs/aufs/dcsub.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dcsub.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: dcsub.c,v 1.5 2007/06/04 02:15:32 sfjro Exp $ */
+
+#include "aufs.h"
+
+static void au_dpage_free(struct au_dpage *dpage)
+{
+	int i;
+
+	TraceEnter();
+	AuDebugOn(!dpage);
+
+	for (i = 0; i < dpage->ndentry; i++)
+		dput(dpage->dentries[i]);
+	free_page((unsigned long)dpage->dentries);
+}
+
+int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp)
+{
+	int err;
+	void *p;
+
+	TraceEnter();
+
+	err = -ENOMEM;
+	dpages->dpages = kmalloc(sizeof(*dpages->dpages), gfp);
+	if (unlikely(!dpages->dpages))
+		goto out;
+	p = (void*)__get_free_page(gfp);
+	if (unlikely(!p))
+		goto out_dpages;
+	dpages->dpages[0].ndentry = 0;
+	dpages->dpages[0].dentries = p;
+	dpages->ndpage = 1;
+	return 0; /* success */
+
+ out_dpages:
+	kfree(dpages->dpages);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+void au_dpages_free(struct au_dcsub_pages *dpages)
+{
+	int i;
+
+	TraceEnter();
+
+	for (i = 0; i < dpages->ndpage; i++)
+		au_dpage_free(dpages->dpages + i);
+	kfree(dpages->dpages);
+}
+
+static int au_dpages_append(struct au_dcsub_pages *dpages,
+			    struct dentry *dentry, gfp_t gfp)
+{
+	int err, sz;
+	struct au_dpage *dpage;
+	void *p;
+
+	//TraceEnter();
+
+	dpage = dpages->dpages + dpages->ndpage - 1;
+	AuDebugOn(!dpage);
+	sz = PAGE_SIZE / sizeof(dentry);
+	if (unlikely(dpage->ndentry >= sz)) {
+		LKTRLabel(new dpage);
+		err = -ENOMEM;
+		sz = dpages->ndpage * sizeof(*dpages->dpages);
+		p = au_kzrealloc(dpages->dpages, sz,
+				 sz + sizeof(*dpages->dpages), gfp);
+		if (unlikely(!p))
+			goto out;
+		dpage = dpages->dpages + dpages->ndpage;
+		p = (void*)__get_free_page(gfp);
+		if (unlikely(!p))
+			goto out;
+		dpage->ndentry = 0;
+		dpage->dentries = p;
+		dpages->ndpage++;
+	}
+
+	dpage->dentries[dpage->ndentry++] = dget(dentry);
+	return 0; /* success */
+
+ out:
+	//TraceErr(err);
+	return err;
+}
+
+int au_dcsub_pages(struct au_dcsub_pages *dpages, struct dentry *root,
+		   au_dpages_test test, void *arg)
+{
+	int err;
+	struct dentry *this_parent = root;
+	struct list_head *next;
+	struct super_block *sb = root->d_sb;
+
+	TraceEnter();
+
+	err = 0;
+	spin_lock(&dcache_lock);
+ repeat:
+	next = this_parent->d_subdirs.next;
+ resume:
+	if (this_parent->d_sb == sb
+	    && !IS_ROOT(this_parent)
+	    && atomic_read(&this_parent->d_count)
+	    && this_parent->d_inode
+	    && (!test || test(this_parent, arg))) {
+		err = au_dpages_append(dpages, this_parent, GFP_ATOMIC);
+		if (unlikely(err))
+			goto out;
+	}
+
+	while (next != &this_parent->d_subdirs) {
+		struct list_head *tmp = next;
+		struct dentry *dentry = list_entry(tmp, struct dentry, D_CHILD);
+		next = tmp->next;
+		if (unlikely(/*d_unhashed(dentry) || */!dentry->d_inode))
+			continue;
+		if (!list_empty(&dentry->d_subdirs)) {
+			this_parent = dentry;
+			goto repeat;
+		}
+		if (dentry->d_sb == sb
+		    && atomic_read(&dentry->d_count)
+		    && (!test || test(dentry, arg))) {
+			err = au_dpages_append(dpages, dentry, GFP_ATOMIC);
+			if (unlikely(err))
+				goto out;
+		}
+	}
+
+	if (this_parent != root) {
+		next = this_parent->D_CHILD.next;
+		this_parent = this_parent->d_parent;
+		goto resume;
+	}
+ out:
+	spin_unlock(&dcache_lock);
+#if 0
+	if (!err) {
+		int i, j;
+		j = 0;
+		for (i = 0; i < dpages->ndpage; i++) {
+			if ((dpages->dpages + i)->ndentry)
+				Dbg("%d: %d\n",
+				    i, (dpages->dpages + i)->ndentry);
+			j += (dpages->dpages + i)->ndentry;
+		}
+		if (j)
+			Dbg("ndpage %d, %d\n", dpages->ndpage, j);
+	}
+#endif
+	TraceErr(err);
+	return err;
+}
+
+int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry,
+		       int do_include, au_dpages_test test, void *arg)
+{
+	int err;
+
+	TraceEnter();
+
+	err = 0;
+	spin_lock(&dcache_lock);
+	if (do_include && (!test || test(dentry, arg))) {
+		err = au_dpages_append(dpages, dentry, GFP_ATOMIC);
+		if (unlikely(err))
+			goto out;
+	}
+	while (!IS_ROOT(dentry)) {
+		dentry = dentry->d_parent;
+		if (!test || test(dentry, arg)) {
+			err = au_dpages_append(dpages, dentry, GFP_ATOMIC);
+			if (unlikely(err))
+				break;
+		}
+	}
+
+ out:
+	spin_unlock(&dcache_lock);
+
+	TraceErr(err);
+	return err;
+}
+
+int au_is_subdir(struct dentry *d1, struct dentry *d2)
+{
+	int err;
+#ifndef AuUse_ISSUBDIR
+	int i, j;
+	struct au_dcsub_pages dpages;
+	struct au_dpage *dpage;
+	struct dentry **dentries;
+#endif
+
+	LKTRTrace("%.*s, %.*s\n", DLNPair(d1), DLNPair(d2));
+
+#ifdef AuUse_ISSUBDIR
+	spin_lock(&dcache_lock);
+	err = is_subdir(d1, d2);
+	spin_unlock(&dcache_lock);
+#else
+	err = au_dpages_init(&dpages, GFP_KERNEL);
+	if (unlikely(err))
+		goto out;
+	err = au_dcsub_pages_rev(&dpages, d1, /*do_include*/1, NULL, NULL);
+	if (unlikely(err))
+		goto out_dpages;
+
+	for (i = dpages.ndpage - 1; !err && i >= 0; i--) {
+		dpage = dpages.dpages + i;
+		dentries = dpage->dentries;
+		for (j = dpage->ndentry - 1; !err && j >= 0; j--) {
+			struct dentry *d;
+			d = dentries[j];
+			//Dbg("d %.*s\n", DLNPair(d));
+			err = (d == d2);
+		}
+	}
+
+ out_dpages:
+	au_dpages_free(&dpages);
+ out:
+#endif
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/dcsub.h linux-2.6.22-aufs/fs/aufs/dcsub.h
--- linux-2.6.22/fs/aufs/dcsub.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dcsub.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: dcsub.h,v 1.4 2007/06/04 02:15:32 sfjro Exp $ */
+
+#ifndef __AUFS_DCSUB_H__
+#define __AUFS_DCSUB_H__
+
+#ifdef __KERNEL__
+
+#include <linux/dcache.h>
+
+struct au_dpage {
+	int ndentry;
+	struct dentry **dentries;
+};
+
+struct au_dcsub_pages {
+	int ndpage;
+	struct au_dpage *dpages;
+};
+
+/* ---------------------------------------------------------------------- */
+
+int au_dpages_init(struct au_dcsub_pages *dpages, gfp_t gfp);
+void au_dpages_free(struct au_dcsub_pages *dpages);
+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 do_include, au_dpages_test test, void *arg);
+int au_is_subdir(struct dentry *d1, struct dentry *d2);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DCSUB_H__ */
diff -ruN linux-2.6.22/fs/aufs/debug.c linux-2.6.22-aufs/fs/aufs/debug.c
--- linux-2.6.22/fs/aufs/debug.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/debug.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: debug.c,v 1.32 2007/07/15 20:02:51 sfjro Exp $ */
+
+#include <linux/sysrq.h>
+#include "aufs.h"
+
+atomic_t aufs_cond = ATOMIC_INIT(0);
+
+static char *au_plevel = KERN_DEBUG;
+#if defined(CONFIG_LKTR) || defined(CONFIG_LKTR_MODULE)
+#define dpri(fmt, arg...) do { \
+	if (LktrCond) \
+		printk("%s" fmt, au_plevel, ##arg); \
+} while (0)
+#else
+#define dpri(fmt, arg...)	printk("%s" fmt, au_plevel, ##arg)
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+void au_dpri_whlist(struct aufs_nhash *whlist)
+{
+	int i;
+	struct hlist_head *head;
+	struct aufs_wh *tpos;
+	struct hlist_node *pos;
+
+	for (i = 0; i < AuSize_NHASH; i++) {
+		head = whlist->heads + i;
+		hlist_for_each_entry(tpos, pos, head, wh_hash)
+			dpri("b%d, %.*s, %d\n",
+			     tpos->wh_bindex,
+			     tpos->wh_str.len, tpos->wh_str.name,
+			     tpos->wh_str.len);
+	}
+}
+
+void au_dpri_vdir(struct aufs_vdir *vdir)
+{
+	int i;
+	union aufs_deblk_p p;
+	unsigned char *o;
+
+	if (!vdir || IS_ERR(vdir)) {
+		dpri("err %ld\n", PTR_ERR(vdir));
+		return;
+	}
+
+	dpri("nblk %d, deblk %p, last{%d, %p}, ver %lu\n",
+	     vdir->vd_nblk, vdir->vd_deblk,
+	     vdir->vd_last.i, vdir->vd_last.p.p, vdir->vd_version);
+	for (i = 0; i < vdir->vd_nblk; i++) {
+		p.deblk = vdir->vd_deblk[i];
+		o = p.p;
+		dpri("[%d]: %p\n", i, o);
+#if 0 // verbose
+		int j;
+		for (j = 0; j < 8; j++) {
+			dpri("%p(+%d) {%02x %02x %02x %02x %02x %02x %02x %02x "
+			     "%02x %02x %02x %02x %02x %02x %02x %02x}\n",
+			     p.p, p.p - o,
+			     p.p[0], p.p[1], p.p[2], p.p[3],
+			     p.p[4], p.p[5], p.p[6], p.p[7],
+			     p.p[8], p.p[9], p.p[10], p.p[11],
+			     p.p[12], p.p[13], p.p[14], p.p[15]);
+			p.p += 16;
+		}
+#endif
+	}
+}
+
+static int do_pri_inode(aufs_bindex_t bindex, struct inode *inode,
+			struct dentry *wh)
+{
+	char *n = NULL;
+	int l = 0, ntfy = 0;
+
+	if (!inode || IS_ERR(inode)) {
+		dpri("i%d: err %ld\n", bindex, PTR_ERR(inode));
+		return -1;
+	}
+
+	/* the type of i_blocks depends upon CONFIG_LSF */
+	BUILD_BUG_ON(sizeof(inode->i_blocks) != sizeof(unsigned long)
+		     && sizeof(inode->i_blocks) != sizeof(u64));
+	if (wh) {
+		n = (void*)wh->d_name.name;
+		l = wh->d_name.len;
+	}
+#ifdef CONFIG_INOTIFY
+	ntfy = !list_empty(&inode->inotify_watches);
+#endif
+	dpri("i%d: i%lu, %s, cnt %d, nl %u, 0%o, ntfy %d, sz %Lu, blk %Lu,"
+	     " ct %Ld, np %lu, st 0x%lx, g %x%s%.*s\n",
+	     bindex,
+	     inode->i_ino, inode->i_sb ? au_sbtype(inode->i_sb) : "??",
+	     atomic_read(&inode->i_count), inode->i_nlink, inode->i_mode,
+	     ntfy,
+	     i_size_read(inode), (u64)inode->i_blocks,
+	     timespec_to_ns(&inode->i_ctime) & 0x0ffff,
+	     inode->i_mapping ? inode->i_mapping->nrpages : 0,
+	     inode->i_state, inode->i_generation,
+	     l ? ", wh " : "", l, n);
+	return 0;
+}
+
+void au_dpri_inode(struct inode *inode)
+{
+	struct aufs_iinfo *iinfo;
+	aufs_bindex_t bindex;
+	int err;
+
+	err = do_pri_inode(-1, inode, NULL);
+	if (err || !au_is_aufs(inode->i_sb))
+		return;
+
+	iinfo = itoii(inode);
+	if (!iinfo)
+		return;
+	dpri("i-1: bstart %d, bend %d, gen %d\n",
+	     iinfo->ii_bstart, iinfo->ii_bend, au_iigen(inode));
+	if (iinfo->ii_bstart < 0)
+		return;
+	for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend; bindex++)
+		do_pri_inode(bindex, iinfo->ii_hinode[0 + bindex].hi_inode,
+			     iinfo->ii_hinode[0 + bindex].hi_whdentry);
+}
+
+static int do_pri_dentry(aufs_bindex_t bindex, struct dentry *dentry)
+{
+	struct dentry *wh = NULL;
+
+	if (!dentry || IS_ERR(dentry)) {
+		dpri("d%d: err %ld\n", bindex, PTR_ERR(dentry));
+		return -1;
+	}
+	dpri("d%d: %.*s/%.*s, %s, cnt %d, flags 0x%x\n",
+	     bindex,
+	     DLNPair(dentry->d_parent), DLNPair(dentry),
+	     dentry->d_sb ? au_sbtype(dentry->d_sb) : "??",
+	     atomic_read(&dentry->d_count), dentry->d_flags);
+	if (bindex >= 0 && dentry->d_inode && au_is_aufs(dentry->d_sb)) {
+		struct aufs_iinfo *iinfo = itoii(dentry->d_inode);
+		if (iinfo)
+			wh = iinfo->ii_hinode[0 + bindex].hi_whdentry;
+	}
+	do_pri_inode(bindex, dentry->d_inode, wh);
+	return 0;
+}
+
+void au_dpri_dentry(struct dentry *dentry)
+{
+	struct aufs_dinfo *dinfo;
+	aufs_bindex_t bindex;
+	int err;
+
+	err = do_pri_dentry(-1, dentry);
+	if (err || !au_is_aufs(dentry->d_sb))
+		return;
+
+	dinfo = dtodi(dentry);
+	if (!dinfo)
+		return;
+	dpri("d-1: bstart %d, bend %d, bwh %d, bdiropq %d, gen %d\n",
+	     dinfo->di_bstart, dinfo->di_bend,
+	     dinfo->di_bwh, dinfo->di_bdiropq, au_digen(dentry));
+	if (dinfo->di_bstart < 0)
+		return;
+	for (bindex = dinfo->di_bstart; bindex <= dinfo->di_bend; bindex++)
+		do_pri_dentry(bindex, dinfo->di_hdentry[0 + bindex].hd_dentry);
+}
+
+static int do_pri_file(aufs_bindex_t bindex, struct file *file)
+{
+	char a[32];
+
+	if (!file || IS_ERR(file)) {
+		dpri("f%d: err %ld\n", bindex, PTR_ERR(file));
+		return -1;
+	}
+	a[0] = 0;
+	if (bindex < 0 && au_is_aufs(file->f_dentry->d_sb) && ftofi(file))
+		snprintf(a, sizeof(a), ", mmapped %d", au_is_mmapped(file));
+	dpri("f%d: mode 0x%x, flags 0%o, cnt %d, pos %Lu%s\n",
+	     bindex, file->f_mode, file->f_flags, file_count(file),
+	     file->f_pos, a);
+	do_pri_dentry(bindex, file->f_dentry);
+	return 0;
+}
+
+void au_dpri_file(struct file *file)
+{
+	struct aufs_finfo *finfo;
+	aufs_bindex_t bindex;
+	int err;
+
+	err = do_pri_file(-1, file);
+	if (err || !file->f_dentry || !au_is_aufs(file->f_dentry->d_sb))
+		return;
+
+	finfo = ftofi(file);
+	if (!finfo)
+		return;
+	if (finfo->fi_bstart < 0)
+		return;
+	for (bindex = finfo->fi_bstart; bindex <= finfo->fi_bend; bindex++) {
+		struct aufs_hfile *hf;
+		//dpri("bindex %d\n", bindex);
+		hf = finfo->fi_hfile + bindex;
+		do_pri_file(bindex, hf ? hf->hf_file : NULL);
+	}
+}
+
+static int do_pri_br(aufs_bindex_t bindex, struct aufs_branch *br)
+{
+	struct vfsmount *mnt;
+	struct super_block *sb;
+
+	if (!br || IS_ERR(br)
+	    || !(mnt = br->br_mnt) || IS_ERR(mnt)
+	    || !(sb = mnt->mnt_sb) || IS_ERR(sb)) {
+		dpri("s%d: err %ld\n", bindex, PTR_ERR(br));
+		return -1;
+	}
+
+	dpri("s%d: {perm 0x%x, cnt %d}, "
+	     "%s, flags 0x%lx, cnt(BIAS) %d, active %d, xino %p %p\n",
+	     bindex, br->br_perm, br_count(br),
+	     au_sbtype(sb), sb->s_flags, sb->s_count - S_BIAS,
+	     atomic_read(&sb->s_active), br->br_xino,
+	     br->br_xino ? br->br_xino->f_dentry : NULL);
+	return 0;
+}
+
+void au_dpri_sb(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+	aufs_bindex_t bindex;
+	int err;
+	struct vfsmount mnt = {.mnt_sb = sb};
+	struct aufs_branch fake = {
+		.br_perm = 0,
+		.br_mnt = &mnt,
+		.br_count = ATOMIC_INIT(0),
+		.br_xino = NULL
+	};
+
+	atomic_set(&fake.br_count, 0);
+	smp_mb(); /* atomic_set */
+	err = do_pri_br(-1, &fake);
+	dpri("dev 0x%x\n", sb->s_dev);
+	if (err || !au_is_aufs(sb))
+		return;
+
+	sbinfo = stosi(sb);
+	if (!sbinfo)
+		return;
+	for (bindex = 0; bindex <= sbinfo->si_bend; bindex++) {
+		//dpri("bindex %d\n", bindex);
+		do_pri_br(bindex, sbinfo->si_branch[0 + bindex]);
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_dbg_sleep(int sec)
+{
+	static DECLARE_WAIT_QUEUE_HEAD(wq);
+	wait_event_timeout(wq, 0, sec * HZ);
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_MAGIC_SYSRQ
+static void sysrq_sb(struct super_block *sb)
+{
+	char *plevel;
+	struct inode *i;
+
+	plevel = au_plevel;
+	au_plevel = KERN_WARNING;
+	au_debug_on();
+
+	dpri(AUFS_NAME ": superblock\n");
+	au_dpri_sb(sb);
+	dpri(AUFS_NAME ": root dentry\n");
+	au_dpri_dentry(sb->s_root);
+#if 1
+	dpri(AUFS_NAME ": root inode\n");
+	au_dpri_inode(sb->s_root->d_inode);
+#endif
+#if 1
+	dpri(AUFS_NAME ": isolated inode\n");
+	list_for_each_entry(i, &sb->s_inodes, i_sb_list)
+		if (list_empty(&i->i_dentry))
+			au_dpri_inode(i);
+#endif
+
+	au_plevel = plevel;
+	au_debug_off();
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+static void au_sysrq(int key, struct tty_struct *tty)
+#else
+static void au_sysrq(int key, struct pt_regs *regs, struct tty_struct *tty)
+#endif
+{
+	au_each_sb(sysrq_sb, /*do_lock*/0);
+}
+
+static struct sysrq_key_op au_sysrq_op = {
+	.handler	= au_sysrq,
+	.help_msg	= "Aufs",
+	.action_msg	= "Aufs",
+	.enable_mask	= SYSRQ_ENABLE_DUMP //??
+};
+
+/* ---------------------------------------------------------------------- */
+
+int __init au_sysrq_init(void)
+{
+	int err;
+	char key;
+
+	err = -1;
+	key = *aufs_sysrq_key;
+	if ('a' <= key && key <= 'z')
+		err = register_sysrq_key(key, &au_sysrq_op);
+	if (unlikely(err))
+		Err("err %d, sysrq=%c\n", err, key);
+	return err;
+}
+
+void au_sysrq_fin(void)
+{
+	int err;
+	err = unregister_sysrq_key(*aufs_sysrq_key, &au_sysrq_op);
+	if (unlikely(err))
+		Err("err %d (ignored)\n", err);
+}
+#endif
diff -ruN linux-2.6.22/fs/aufs/debug.h linux-2.6.22-aufs/fs/aufs/debug.h
--- linux-2.6.22/fs/aufs/debug.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/debug.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: debug.h,v 1.37 2007/07/15 20:03:00 sfjro Exp $ */
+
+#ifndef __AUFS_DEBUG_H__
+#define __AUFS_DEBUG_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+
+#define MtxMustLock(mtx)	AuDebugOn(!mutex_is_locked(mtx))
+
+#ifdef CONFIG_AUFS_DEBUG
+/* sparse warns about pointer */
+#define AuDebugOn(a)		BUG_ON(!!(a))
+extern atomic_t aufs_cond;
+#define au_debug_on()		atomic_inc_return(&aufs_cond)
+#define au_debug_off()		atomic_dec_return(&aufs_cond)
+#define au_is_debug()		atomic_read(&aufs_cond)
+#else
+#define AuDebugOn(a)		do {} while (0)
+#define au_debug_on()		do {} while (0)
+#define au_debug_off()		do {} while (0)
+#define au_is_debug()		0
+#endif /* CONFIG_AUFS_DEBUG */
+
+#if defined(CONFIG_AUFS_DEBUG) && defined(CONFIG_MAGIC_SYSRQ)
+int __init au_sysrq_init(void);
+void au_sysrq_fin(void);
+#else
+#define au_sysrq_init()		0
+#define au_sysrq_fin()		do {} while (0)
+#endif /* CONFIG_AUFS_DEBUG && CONFIG_MAGIC_SYSRQ */
+
+/* ---------------------------------------------------------------------- */
+
+/* debug print */
+#if defined(CONFIG_LKTR) || defined(CONFIG_LKTR_MODULE)
+#include <linux/lktr.h>
+#ifdef CONFIG_AUFS_DEBUG
+#undef LktrCond
+#define LktrCond	unlikely(au_is_debug() || (lktr_cond && lktr_cond()))
+#endif
+#else
+#define LktrCond			au_is_debug()
+#define LKTRDumpVma(pre, vma, suf)	do {} while (0)
+#define LKTRDumpStack()			do {} while (0)
+#define LKTRTrace(fmt, args...) do { \
+	if (LktrCond) \
+		Dbg(fmt, ##args); \
+} while (0)
+#define LKTRLabel(label)		LKTRTrace("%s\n", #label)
+#endif /* CONFIG_LKTR */
+
+#define TraceErr(e) do { \
+	if (unlikely((e) < 0)) \
+		LKTRTrace("err %d\n", (int)(e)); \
+} while (0)
+
+#define TraceErrPtr(p) do { \
+	if (IS_ERR(p)) \
+		LKTRTrace("err %ld\n", PTR_ERR(p)); \
+} while (0)
+
+#define TraceEnter()	LKTRLabel(enter)
+
+/* dirty macros for debug print, use with "%.*s" and caution */
+#define LNPair(qstr)		(qstr)->len,(qstr)->name
+#define DLNPair(d)		LNPair(&(d)->d_name)
+
+/* ---------------------------------------------------------------------- */
+
+#define Dpri(lvl, fmt, arg...) \
+	printk(lvl AUFS_NAME " %s:%d:%s[%d]: " fmt, \
+	       __func__, __LINE__, current->comm, current->pid, ##arg)
+#define Dbg(fmt, arg...)	Dpri(KERN_DEBUG, fmt, ##arg)
+#define Info(fmt, arg...)	Dpri(KERN_INFO, fmt, ##arg)
+#define Warn(fmt, arg...)	Dpri(KERN_WARNING, fmt, ##arg)
+#define Err(fmt, arg...)	Dpri(KERN_ERR, fmt, ##arg)
+#define IOErr(fmt, arg...)	Err("I/O Error, " fmt, ##arg)
+#define IOErrWhck(fmt, arg...)	Err("I/O Error, try whck. " fmt, ##arg)
+#define Warn1(fmt, arg...) do { \
+	static unsigned char c; \
+	if (!c++) Warn(fmt, ##arg); \
+} while (0)
+
+#define Err1(fmt, arg...) do { \
+	static unsigned char c; \
+	if (!c++) Err(fmt, ##arg); \
+} while (0)
+
+#define IOErr1(fmt, arg...) do { \
+	static unsigned char c; \
+	if (!c++) IOErr(fmt, ##arg); \
+} while (0)
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_DEBUG
+struct aufs_nhash;
+void au_dpri_whlist(struct aufs_nhash *whlist);
+struct aufs_vdir;
+void au_dpri_vdir(struct aufs_vdir *vdir);
+void au_dpri_inode(struct inode *inode);
+void au_dpri_dentry(struct dentry *dentry);
+void au_dpri_file(struct file *filp);
+void au_dpri_sb(struct super_block *sb);
+void au_dbg_sleep(int sec);
+#define DbgWhlist(w) do { \
+	LKTRTrace(#w "\n"); \
+	au_dpri_whlist(w); \
+} while (0)
+
+#define DbgVdir(v) do { \
+	LKTRTrace(#v "\n"); \
+	au_dpri_vdir(v); \
+} while (0)
+
+#define DbgInode(i) do { \
+	LKTRTrace(#i "\n"); \
+	au_dpri_inode(i); \
+} while (0)
+
+#define DbgDentry(d) do { \
+	LKTRTrace(#d "\n"); \
+	au_dpri_dentry(d); \
+} while (0)
+
+#define DbgFile(f) do { \
+	LKTRTrace(#f "\n"); \
+	au_dpri_file(f); \
+} while (0)
+
+#define DbgSb(sb) do { \
+	LKTRTrace(#sb "\n"); \
+	au_dpri_sb(sb); \
+} while (0)
+
+#define DbgSleep(sec) do { \
+	Dbg("sleep %d sec\n", sec); \
+	au_dbg_sleep(sec); \
+} while (0)
+#else
+#define DbgWhlist(w)		do {} while (0)
+#define DbgVdir(v)		do {} while (0)
+#define DbgInode(i)		do {} while (0)
+#define DbgDentry(d)		do {} while (0)
+#define DbgFile(f)		do {} while (0)
+#define DbgSb(sb)		do {} while (0)
+#define DbgSleep(sec)		do {} while (0)
+#endif
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DEBUG_H__ */
diff -ruN linux-2.6.22/fs/aufs/dentry.c linux-2.6.22-aufs/fs/aufs/dentry.c
--- linux-2.6.22/fs/aufs/dentry.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dentry.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,981 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: dentry.c,v 1.49 2007/07/15 20:03:11 sfjro Exp $ */
+
+//#include <linux/fs.h>
+//#include <linux/namei.h>
+#include "aufs.h"
+
+#ifdef CONFIG_AUFS_LHASH_PATCH
+
+#ifdef CONFIG_AUFS_DLGT
+struct lookup_hash_args {
+	struct dentry **errp;
+	struct qstr *name;
+	struct dentry *base;
+	struct nameidata *nd;
+};
+
+static void call_lookup_hash(void *args)
+{
+	struct lookup_hash_args *a = args;
+	*a->errp = __lookup_hash(a->name, a->base, a->nd);
+}
+#endif /* CONFIG_AUFS_DLGT */
+
+static struct dentry *lkup_hash(const char *name, struct dentry *parent,
+				int len, struct lkup_args *lkup)
+{
+	struct dentry *dentry;
+	char *p;
+	unsigned long hash;
+	struct qstr this;
+	unsigned int c;
+	struct nameidata tmp_nd;
+
+	dentry = ERR_PTR(-EACCES);
+	this.name = name;
+	this.len = len;
+	if (unlikely(!len))
+		goto out;
+
+	p = (void*)name;
+	hash = init_name_hash();
+	while (len--) {
+		c = *p++;
+		if (unlikely(c == '/' || c == '\0'))
+			goto out;
+		hash = partial_name_hash(c, hash);
+	}
+	this.hash = end_name_hash(hash);
+
+	memset(&tmp_nd, 0, sizeof(tmp_nd));
+	tmp_nd.dentry = dget(parent);
+	tmp_nd.mnt = mntget(lkup->nfsmnt);
+#ifndef CONFIG_AUFS_DLGT
+	dentry = __lookup_hash(&this, parent, &tmp_nd);
+#else
+	if (!lkup->dlgt)
+		dentry = __lookup_hash(&this, parent, &tmp_nd);
+	else {
+		int wkq_err;
+		struct lookup_hash_args args = {
+			.errp	= &dentry,
+			.name	= &this,
+			.base	= parent,
+			.nd	= &tmp_nd
+		};
+		wkq_err = au_wkq_wait(call_lookup_hash, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			dentry = ERR_PTR(wkq_err);
+	}
+#endif
+	path_release(&tmp_nd);
+
+ out:
+	TraceErrPtr(dentry);
+	return dentry;
+}
+#elif defined(CONFIG_AUFS_DLGT)
+static struct dentry *lkup_hash(const char *name, struct dentry *parent,
+				int len, struct lkup_args *lkup)
+{
+	return ERR_PTR(-ENOSYS);
+}
+#endif
+
+#ifdef CONFIG_AUFS_DLGT
+struct lookup_one_len_args {
+	struct dentry **errp;
+	const char *name;
+	struct dentry *parent;
+	int len;
+};
+
+static void call_lookup_one_len(void *args)
+{
+	struct lookup_one_len_args *a = args;
+	*a->errp = lookup_one_len(a->name, a->parent, a->len);
+}
+#endif /* CONFIG_AUFS_DLGT */
+
+#if defined(CONFIG_AUFS_LHASH_PATCH) || defined(CONFIG_AUFS_DLGT)
+/* cf. lookup_one_len() in linux/fs/namei.c */
+struct dentry *lkup_one(const char *name, struct dentry *parent, int len,
+			struct lkup_args *lkup)
+{
+	struct dentry *dentry;
+
+	LKTRTrace("%.*s/%.*s, lkup{%d, %d}\n",
+		  DLNPair(parent), len, name, !!lkup->nfsmnt, lkup->dlgt);
+
+	if (!lkup->nfsmnt) {
+#ifndef CONFIG_AUFS_DLGT
+		dentry = lookup_one_len(name, parent, len);
+#else
+		if (!lkup->dlgt)
+			dentry = lookup_one_len(name, parent, len);
+		else {
+			int wkq_err;
+			struct lookup_one_len_args args = {
+				.errp	= &dentry,
+				.name	= name,
+				.parent	= parent,
+				.len	= len
+			};
+			wkq_err = au_wkq_wait(call_lookup_one_len, &args,
+					      /*dlgt*/1);
+			if (unlikely(wkq_err))
+				dentry = ERR_PTR(wkq_err);
+		}
+#endif
+	} else
+		dentry = lkup_hash(name, parent, len, lkup);
+
+	TraceErrPtr(dentry);
+	return dentry;
+}
+#endif
+
+struct lkup_one_args {
+	struct dentry **errp;
+	const char *name;
+	struct dentry *parent;
+	int len;
+	struct lkup_args *lkup;
+};
+
+static void call_lkup_one(void *args)
+{
+	struct lkup_one_args *a = args;
+	*a->errp = lkup_one(a->name, a->parent, a->len, a->lkup);
+}
+
+/*
+ * returns positive/negative dentry, NULL or an error.
+ * NULL means whiteout-ed or not-found.
+ */
+static struct dentry *do_lookup(struct dentry *hidden_parent,
+				struct dentry *dentry, aufs_bindex_t bindex,
+				struct qstr *wh_name, int allow_neg,
+				mode_t type, int dlgt)
+{
+	struct dentry *hidden_dentry;
+	int wh_found, wh_able, opq;
+	struct inode *hidden_dir, *hidden_inode;
+	struct qstr *name;
+	struct super_block *sb;
+	struct lkup_args lkup = {.dlgt = dlgt};
+
+	LKTRTrace("%.*s/%.*s, b%d, allow_neg %d, type 0%o, dlgt %d\n",
+		  DLNPair(hidden_parent), DLNPair(dentry), bindex, allow_neg,
+		  type, dlgt);
+	AuDebugOn(IS_ROOT(dentry));
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+
+	wh_found = 0;
+	sb = dentry->d_sb;
+	wh_able = sbr_is_whable(sb, bindex);
+	lkup.nfsmnt = au_nfsmnt(sb, bindex);
+	name = &dentry->d_name;
+	if (unlikely(wh_able)) {
+#ifdef CONFIG_AUFS_ROBR
+		if (strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))
+			wh_found = is_wh(hidden_parent, wh_name, /*try_sio*/0,
+					 &lkup);
+		else
+			wh_found = -EPERM;
+#else
+		wh_found = is_wh(hidden_parent, wh_name, /*try_sio*/0, &lkup);
+#endif
+	}
+	//if (LktrCond) wh_found = -1;
+	hidden_dentry = ERR_PTR(wh_found);
+	if (!wh_found)
+		goto real_lookup;
+	if (unlikely(wh_found < 0))
+		goto out;
+
+	/* We found a whiteout */
+	//set_dbend(dentry, bindex);
+	set_dbwh(dentry, bindex);
+	if (!allow_neg)
+		return NULL; /* success */
+
+ real_lookup:
+	/* do not superio. */
+	hidden_dentry = lkup_one(name->name, hidden_parent, name->len, &lkup);
+	//if (LktrCond) {dput(hidden_dentry); hidden_dentry = ERR_PTR(-1);}
+	if (IS_ERR(hidden_dentry))
+		goto out;
+	AuDebugOn(d_unhashed(hidden_dentry));
+	hidden_inode = hidden_dentry->d_inode;
+	if (!hidden_inode) {
+		if (!allow_neg)
+			goto out_neg;
+	} else if (wh_found
+		   || (type && type != (hidden_inode->i_mode & S_IFMT)))
+		goto out_neg;
+
+	if (dbend(dentry) <= bindex)
+		set_dbend(dentry, bindex);
+	if (dbstart(dentry) < 0 || bindex < dbstart(dentry))
+		set_dbstart(dentry, bindex);
+	set_h_dptr(dentry, bindex, hidden_dentry);
+
+	if (!hidden_inode || !S_ISDIR(hidden_inode->i_mode) || !wh_able)
+		return hidden_dentry; /* success */
+
+	vfsub_i_lock_nested(hidden_inode, AuLsc_I_CHILD);
+	opq = is_diropq(hidden_dentry, &lkup);
+	//if (LktrCond) opq = -1;
+	vfsub_i_unlock(hidden_inode);
+	if (opq > 0)
+		set_dbdiropq(dentry, bindex);
+	else if (unlikely(opq < 0)) {
+		set_h_dptr(dentry, bindex, NULL);
+		hidden_dentry = ERR_PTR(opq);
+	}
+	goto out;
+
+ out_neg:
+	dput(hidden_dentry);
+	hidden_dentry = NULL;
+ out:
+	TraceErrPtr(hidden_dentry);
+	return hidden_dentry;
+}
+
+/*
+ * returns the number of hidden positive dentries,
+ * otherwise an error.
+ */
+int lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type)
+{
+	int npositive, err, allow_neg, dlgt;
+	struct dentry *parent;
+	aufs_bindex_t bindex, btail;
+	const struct qstr *name = &dentry->d_name;
+	struct qstr whname;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, b%d, type 0%o\n", LNPair(name), bstart, type);
+	AuDebugOn(bstart < 0 || IS_ROOT(dentry));
+	/* dir may not be locked */
+	parent = dget_parent(dentry);
+
+#ifndef CONFIG_AUFS_ROBR
+	err = -EPERM;
+	if (unlikely(!strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)))
+		goto out;
+#endif
+
+	err = au_alloc_whname(name->name, name->len, &whname);
+	//if (LktrCond) {au_free_whname(&whname); err = -1;}
+	if (unlikely(err))
+		goto out;
+
+	sb = dentry->d_sb;
+	dlgt = need_dlgt(sb);
+	allow_neg = !type;
+	npositive = 0;
+	btail = dbtaildir(parent);
+	for (bindex = bstart; bindex <= btail; bindex++) {
+		struct dentry *hidden_parent, *hidden_dentry;
+		struct inode *hidden_inode;
+		struct inode *hidden_dir;
+
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (hidden_dentry) {
+			if (hidden_dentry->d_inode)
+				npositive++;
+			if (type != S_IFDIR)
+				break;
+			continue;
+		}
+		hidden_parent = au_h_dptr_i(parent, bindex);
+		if (!hidden_parent)
+			continue;
+		hidden_dir = hidden_parent->d_inode;
+		if (!hidden_dir || !S_ISDIR(hidden_dir->i_mode))
+			continue;
+
+		vfsub_i_lock_nested(hidden_dir, AuLsc_I_PARENT);
+		hidden_dentry = do_lookup(hidden_parent, dentry, bindex,
+					  &whname, allow_neg, type, dlgt);
+		// do not dput for testing
+		//if (LktrCond) {hidden_dentry = ERR_PTR(-1);}
+		vfsub_i_unlock(hidden_dir);
+		err = PTR_ERR(hidden_dentry);
+		if (IS_ERR(hidden_dentry))
+			goto out_wh;
+		allow_neg = 0;
+
+		if (dbwh(dentry) >= 0)
+			break;
+		if (!hidden_dentry)
+			continue;
+		hidden_inode = hidden_dentry->d_inode;
+		if (!hidden_inode)
+			continue;
+		npositive++;
+		if (!type)
+			type = hidden_inode->i_mode & S_IFMT;
+		if (type != S_IFDIR)
+			break;
+		else if (dbdiropq(dentry) >= 0)
+			break;
+	}
+
+	if (npositive) {
+		LKTRLabel(positive);
+		au_update_dbstart(dentry);
+	}
+	err = npositive;
+
+ out_wh:
+	au_free_whname(&whname);
+ out:
+	dput(parent);
+	TraceErr(err);
+	return err;
+}
+
+struct dentry *sio_lkup_one(const char *name, struct dentry *parent, int len,
+			    struct lkup_args *lkup)
+{
+	struct dentry *dentry;
+	int wkq_err;
+
+	LKTRTrace("%.*s/%.*s\n", DLNPair(parent), len, name);
+	IMustLock(parent->d_inode);
+
+	if (!au_test_perm(parent->d_inode, MAY_EXEC, lkup->dlgt))
+		dentry = lkup_one(name, parent, len, lkup);
+	else {
+		// ugly
+		int dlgt = lkup->dlgt;
+		struct lkup_one_args args = {
+			.errp	= &dentry,
+			.name	= name,
+			.parent	= parent,
+			.len	= len,
+			.lkup	= lkup
+		};
+
+		lkup->dlgt = 0;
+		wkq_err = au_wkq_wait(call_lkup_one, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			dentry = ERR_PTR(wkq_err);
+		lkup->dlgt = dlgt;
+	}
+
+	TraceErrPtr(dentry);
+	return dentry;
+}
+
+/*
+ * lookup @dentry on @bindex which should be negative.
+ */
+int lkup_neg(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	int err;
+	struct dentry *parent, *hidden_parent, *hidden_dentry;
+	struct inode *hidden_dir;
+	struct lkup_args lkup;
+
+	LKTRTrace("%.*s, b%d\n", DLNPair(dentry), bindex);
+	/* dir may not be locked */
+	parent = dget_parent(dentry);
+	AuDebugOn(!parent || !parent->d_inode
+		  || !S_ISDIR(parent->d_inode->i_mode));
+	hidden_parent = au_h_dptr_i(parent, bindex);
+	AuDebugOn(!hidden_parent);
+	hidden_dir = hidden_parent->d_inode;
+	AuDebugOn(!hidden_dir || !S_ISDIR(hidden_dir->i_mode));
+	IMustLock(hidden_dir);
+
+	lkup.nfsmnt = au_nfsmnt(dentry->d_sb, bindex);
+	lkup.dlgt = need_dlgt(dentry->d_sb);
+	hidden_dentry = sio_lkup_one(dentry->d_name.name, hidden_parent,
+				     dentry->d_name.len, &lkup);
+	//if (LktrCond) {dput(hidden_dentry); hidden_dentry = ERR_PTR(-1);}
+	err = PTR_ERR(hidden_dentry);
+	if (IS_ERR(hidden_dentry))
+		goto out;
+	if (unlikely(hidden_dentry->d_inode)) {
+		err = -EIO;
+		IOErr("b%d %.*s should be negative.%s\n",
+		      bindex, DLNPair(hidden_dentry),
+		      au_flag_test_udba_inotify(dentry->d_sb) ? "" :
+		      " Try udba=inotify.");
+		dput(hidden_dentry);
+		goto out;
+	}
+
+	if (bindex < dbstart(dentry))
+		set_dbstart(dentry, bindex);
+	if (dbend(dentry) < bindex)
+		set_dbend(dentry, bindex);
+	set_h_dptr(dentry, bindex, hidden_dentry);
+	err = 0;
+
+ out:
+	dput(parent);
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * returns the number of found hidden positive dentries,
+ * otherwise an error.
+ */
+int au_refresh_hdentry(struct dentry *dentry, mode_t type)
+{
+	int npositive, new_sz;
+	struct aufs_dinfo *dinfo;
+	struct super_block *sb;
+	struct dentry *parent;
+	aufs_bindex_t bindex, parent_bend, parent_bstart, bwh, bdiropq, bend;
+	struct aufs_hdentry *p;
+	//struct nameidata nd;
+
+	LKTRTrace("%.*s, type 0%o\n", DLNPair(dentry), type);
+	DiMustWriteLock(dentry);
+	sb = dentry->d_sb;
+	AuDebugOn(IS_ROOT(dentry));
+	parent = dget_parent(dentry);
+	AuDebugOn(au_digen(parent) != au_sigen(sb));
+
+	npositive = -ENOMEM;
+	new_sz = sizeof(*dinfo->di_hdentry) * (sbend(sb) + 1);
+	dinfo = dtodi(dentry);
+	p = au_kzrealloc(dinfo->di_hdentry, sizeof(*p) * (dinfo->di_bend + 1),
+			 new_sz, GFP_KERNEL);
+	//p = NULL;
+	if (unlikely(!p))
+		goto out;
+	dinfo->di_hdentry = p;
+
+	bend = dinfo->di_bend;
+	bwh = dinfo->di_bwh;
+	bdiropq = dinfo->di_bdiropq;
+	p += dinfo->di_bstart;
+	for (bindex = dinfo->di_bstart; bindex <= bend; bindex++, p++) {
+		struct dentry *hd, *hdp;
+		struct aufs_hdentry tmp, *q;
+		aufs_bindex_t new_bindex;
+
+		hd = p->hd_dentry;
+		if (!hd)
+			continue;
+		hdp = dget_parent(hd);
+		if (hdp == au_h_dptr_i(parent, bindex)) {
+			dput(hdp);
+			continue;
+		}
+
+		new_bindex = au_find_dbindex(parent, hdp);
+		dput(hdp);
+		AuDebugOn(new_bindex == bindex);
+		if (dinfo->di_bwh == bindex)
+			bwh = new_bindex;
+		if (dinfo->di_bdiropq == bindex)
+			bdiropq = new_bindex;
+		if (new_bindex < 0) { // test here
+			hdput(p);
+			p->hd_dentry = NULL;
+			continue;
+		}
+		/* swap two hidden dentries, and loop again */
+		q = dinfo->di_hdentry + new_bindex;
+		tmp = *q;
+		*q = *p;
+		*p = tmp;
+		if (tmp.hd_dentry) {
+			bindex--;
+			p--;
+		}
+	}
+
+	// test here
+	dinfo->di_bwh = -1;
+	if (unlikely(bwh >= 0 && bwh <= sbend(sb) && sbr_is_whable(sb, bwh)))
+		dinfo->di_bwh = bwh;
+	dinfo->di_bdiropq = -1;
+	if (unlikely(bdiropq >= 0 && bdiropq <= sbend(sb)
+		     && sbr_is_whable(sb, bdiropq)))
+		dinfo->di_bdiropq = bdiropq;
+	parent_bend = dbend(parent);
+	p = dinfo->di_hdentry;
+	for (bindex = 0; bindex <= parent_bend; bindex++, p++)
+		if (p->hd_dentry) {
+			dinfo->di_bstart = bindex;
+			break;
+		}
+	p = dinfo->di_hdentry + parent_bend;
+	//for (bindex = parent_bend; bindex > dinfo->di_bstart; bindex--, p--)
+	for (bindex = parent_bend; bindex >= 0; bindex--, p--)
+		if (p->hd_dentry) {
+			dinfo->di_bend = bindex;
+			break;
+		}
+
+	npositive = 0;
+	parent_bstart = dbstart(parent);
+	if (type != S_IFDIR && dinfo->di_bstart == parent_bstart)
+		goto out_dgen; /* success */
+
+#if 0
+	nd.last_type = LAST_ROOT;
+	nd.flags = LOOKUP_FOLLOW;
+	nd.depth = 0;
+	nd.mnt = mntget(??);
+	nd.dentry = dget(parent);
+#endif
+	npositive = lkup_dentry(dentry, parent_bstart, type);
+	//if (LktrCond) npositive = -1;
+	if (npositive < 0)
+		goto out;
+	if (unlikely(dinfo->di_bwh >= 0 && dinfo->di_bwh <= dinfo->di_bstart))
+		d_drop(dentry);
+
+ out_dgen:
+	au_update_digen(dentry);
+ out:
+	dput(parent);
+	TraceErr(npositive);
+	return npositive;
+}
+
+static int h_d_revalidate(struct dentry *dentry, struct nameidata *nd,
+			  int do_udba)
+{
+	int err, plus, locked, unhashed, is_root, h_plus, is_nfs;
+	struct nameidata fake_nd, *p;
+	aufs_bindex_t bindex, btail, bstart, ibs, ibe;
+	struct super_block *sb;
+	struct inode *inode, *first, *h_inode, *h_cached_inode;
+	umode_t mode, h_mode;
+	struct dentry *h_dentry;
+	int (*reval)(struct dentry *, struct nameidata *);
+	struct qstr *name;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	inode = dentry->d_inode;
+	AuDebugOn(inode && au_digen(dentry) != au_iigen(inode));
+
+	err = 0;
+	sb = dentry->d_sb;
+	plus = 0;
+	mode = 0;
+	first = NULL;
+	ibs = ibe = -1;
+	unhashed = d_unhashed(dentry);
+	is_root = IS_ROOT(dentry);
+	name = &dentry->d_name;
+
+	/*
+	 * Theoretically, REVAL test should be unnecessary in case of INOTIFY.
+	 * But inotify 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);
+		first = au_h_iptr(inode);
+		ibs = ibstart(inode);
+		ibe = ibend(inode);
+	}
+
+	btail = bstart = dbstart(dentry);
+	if (inode && S_ISDIR(inode->i_mode))
+		btail = dbtaildir(dentry);
+	locked = 0;
+	if (nd) {
+		fake_nd = *nd;
+#ifndef CONFIG_AUFS_FAKE_DM
+		if (dentry != nd->dentry) {
+			di_read_lock_parent(nd->dentry, 0);
+			locked = 1;
+		}
+#endif
+	}
+	for (bindex = bstart; bindex <= btail; bindex++) {
+		h_dentry = au_h_dptr_i(dentry, bindex);
+		if (unlikely(!h_dentry))
+			continue;
+		if (unlikely(do_udba
+			     && !is_root
+			     && (unhashed != d_unhashed(h_dentry)
+#if 1
+				 || name->len != h_dentry->d_name.len
+				 || memcmp(name->name, h_dentry->d_name.name,
+					   name->len)
+#endif
+				     ))) {
+			LKTRTrace("unhash 0x%x 0x%x, %.*s %.*s\n",
+				  unhashed, d_unhashed(h_dentry),
+				  DLNPair(dentry), DLNPair(h_dentry));
+			goto err;
+		}
+
+		reval = NULL;
+		if (h_dentry->d_op)
+			reval = h_dentry->d_op->d_revalidate;
+		if (unlikely(reval)) {
+			//LKTRLabel(hidden reval);
+			p = fake_dm(&fake_nd, nd, sb, bindex);
+			AuDebugOn(IS_ERR(p));
+			err = !reval(h_dentry, p);
+			fake_dm_release(p);
+			if (unlikely(err)) {
+				//Dbg("here\n");
+				goto err;
+			}
+		}
+
+		if (unlikely(!do_udba))
+			continue;
+
+		/* UDBA tests */
+		h_inode = h_dentry->d_inode;
+		if (unlikely(!!inode != !!h_inode)) {
+			//Dbg("here\n");
+			goto err;
+		}
+
+		h_plus = plus;
+		h_mode = mode;
+		h_cached_inode = h_inode;
+		is_nfs = 0;
+		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_i(inode, bindex);
+			//is_nfs = au_is_nfs(h_cached_inode->i_sb);
+		}
+
+		LKTRTrace("{%d, 0%o, %d}, h{%d, 0%o, %d}\n",
+			  plus, mode, !!h_cached_inode,
+			  h_plus, h_mode, !!h_inode);
+		if (unlikely(plus != h_plus || mode != h_mode
+			     || (h_cached_inode != h_inode /* && !is_nfs */))) {
+			//Dbg("here\n");
+			goto err;
+		}
+		continue;
+
+	err:
+		err = -EINVAL;
+		break;
+	}
+#ifndef CONFIG_AUFS_FAKE_DM
+	if (unlikely(locked))
+		di_read_unlock(nd->dentry, 0);
+#endif
+
+#if 0
+	// some filesystem uses CURRENT_TIME_SEC instead of CURRENT_TIME.
+	// NFS may stop IN_DELETE because of DCACHE_NFSFS_RENAMED.
+#if 0
+		     && (!timespec_equal(&inode->i_ctime, &first->i_ctime)
+			 || !timespec_equal(&inode->i_atime, &first->i_atime))
+#endif
+	if (unlikely(!err && udba && first))
+		au_cpup_attr_all(inode);
+#endif
+
+	TraceErr(err);
+	return err;
+}
+
+static int simple_reval_dpath(struct dentry *dentry, au_gen_t sgen)
+{
+	int err;
+	mode_t type;
+	struct dentry *parent;
+	struct inode *inode;
+
+	LKTRTrace("%.*s, sgen %d\n", DLNPair(dentry), sgen);
+	SiMustAnyLock(dentry->d_sb);
+	DiMustWriteLock(dentry);
+	inode = dentry->d_inode;
+	AuDebugOn(!inode);
+
+	if (au_digen(dentry) == sgen)
+		return 0;
+
+	parent = dget_parent(dentry);
+	di_read_lock_parent(parent, AuLock_IR);
+	AuDebugOn(au_digen(parent) != sgen);
+#ifdef CONFIG_AUFS_DEBUG
+	{
+		int i, j;
+		struct au_dcsub_pages dpages;
+		struct au_dpage *dpage;
+		struct dentry **dentries;
+
+		err = au_dpages_init(&dpages, GFP_KERNEL);
+		AuDebugOn(err);
+		err = au_dcsub_pages_rev(&dpages, parent, /*do_include*/1, NULL,
+					 NULL);
+		AuDebugOn(err);
+		for (i = dpages.ndpage - 1; !err && i >= 0; i--) {
+			dpage = dpages.dpages + i;
+			dentries = dpage->dentries;
+			for (j = dpage->ndentry - 1; !err && j >= 0; j--)
+				AuDebugOn(au_digen(dentries[j]) != sgen);
+		}
+		au_dpages_free(&dpages);
+	}
+#endif
+	type = (inode->i_mode & S_IFMT);
+	/* returns a number of positive dentries */
+	err = au_refresh_hdentry(dentry, type);
+	if (err >= 0)
+		err = au_refresh_hinode(inode, dentry);
+	di_read_unlock(parent, AuLock_IR);
+	dput(parent);
+	TraceErr(err);
+	return err;
+}
+
+int au_reval_dpath(struct dentry *dentry, au_gen_t sgen)
+{
+	int err;
+	struct dentry *d, *parent;
+	struct inode *inode;
+
+	LKTRTrace("%.*s, sgen %d\n", DLNPair(dentry), sgen);
+	AuDebugOn(!dentry->d_inode);
+	DiMustWriteLock(dentry);
+
+	if (!stosi(dentry->d_sb)->si_failed_refresh_dirs)
+		return simple_reval_dpath(dentry, sgen);
+
+	/* slow loop, keep it simple and stupid */
+	/* cf: cpup_dirs() */
+	err = 0;
+	while (au_digen(dentry) != sgen) {
+		d = dentry;
+		while (1) {
+			parent = d->d_parent; // dget_parent()
+			if (au_digen(parent) == sgen)
+				break;
+			d = parent;
+		}
+
+		inode = d->d_inode;
+		if (d != dentry) {
+			//vfsub_i_lock(inode);
+			di_write_lock_child(d);
+		}
+
+		/* someone might update our dentry while we were sleeping */
+		if (au_digen(d) != sgen) {
+			di_read_lock_parent(parent, AuLock_IR);
+			/* returns a number of positive dentries */
+			err = au_refresh_hdentry(d, inode->i_mode & S_IFMT);
+			//err = -1;
+			if (err >= 0)
+				err = au_refresh_hinode(inode, d);
+			//err = -1;
+			di_read_unlock(parent, AuLock_IR);
+		}
+
+		if (d != dentry) {
+			di_write_unlock(d);
+			//vfsub_i_unlock(inode);
+		}
+		if (unlikely(err))
+			break;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * THIS IS A BOOLEAN FUNCTION: returns 1 if valid, 0 otherwise.
+ * nfsd passes NULL as nameidata.
+ */
+static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
+{
+	int valid, err, do_udba;
+	struct super_block *sb;
+	au_gen_t sgen;
+	struct inode *inode;
+
+	LKTRTrace("dentry %.*s\n", DLNPair(dentry));
+	if (nd && nd->dentry)
+		LKTRTrace("nd %.*s\n", DLNPair(nd->dentry));
+	//dir case: AuDebugOn(dentry->d_parent != nd->dentry);
+	//remove failure case:AuDebugOn(!IS_ROOT(dentry) && d_unhashed(dentry));
+	AuDebugOn(!dentry->d_fsdata);
+
+	err = -EINVAL;
+	inode = dentry->d_inode;
+	sb = dentry->d_sb;
+	si_read_lock(sb, AuLock_FLUSH);
+
+	sgen = au_sigen(sb);
+	if (au_digen(dentry) == sgen)
+		di_read_lock_child(dentry, !AuLock_IR);
+	else {
+		AuDebugOn(IS_ROOT(dentry));
+#ifdef ForceInotify
+		Dbg("UDBA or digen, %.*s\n", DLNPair(dentry));
+#endif
+		//di_read_lock_child(dentry, AuLock_IR);err = -EINVAL; goto out;
+		//vfsub_i_lock(inode);
+		di_write_lock_child(dentry);
+		if (inode)
+			err = au_reval_dpath(dentry, sgen);
+		//err = -1;
+		di_downgrade_lock(dentry, AuLock_IR);
+		//vfsub_i_unlock(inode);
+		if (unlikely(err))
+			goto out;
+		ii_read_unlock(inode);
+		AuDebugOn(au_iigen(inode) != sgen);
+	}
+
+	if (inode) {
+		if (au_iigen(inode) == sgen)
+			ii_read_lock_child(inode);
+		else {
+			AuDebugOn(IS_ROOT(dentry));
+#ifdef ForceInotify
+			Dbg("UDBA or survived, %.*s\n", DLNPair(dentry));
+			//au_debug_on();
+			//DbgInode(inode);
+			//au_debug_off();
+#endif
+			//ii_read_lock_child(inode);err = -EINVAL; goto out;
+			ii_write_lock_child(inode);
+			err = au_refresh_hinode(inode, dentry);
+			ii_downgrade_lock(inode);
+			if (unlikely(err))
+				goto out;
+			AuDebugOn(au_iigen(inode) != sgen);
+		}
+	}
+
+#if 0 // fix it
+	/* parent dir i_nlink is not updated in the case of setattr */
+	if (S_ISDIR(inode->i_mode)) {
+		vfsub_i_lock(inode);
+		ii_write_lock(inode);
+		au_cpup_attr_nlink(inode);
+		ii_write_unlock(inode);
+		vfsub_i_unlock(inode);
+	}
+#endif
+
+	err = -EINVAL;
+	do_udba = !au_flag_test(sb, AuFlag_UDBA_NONE);
+	if (do_udba && inode && ibstart(inode) >= 0
+	    && au_test_higen(inode, au_h_iptr(inode)))
+		goto out;
+	err = h_d_revalidate(dentry, nd, do_udba);
+	//err = -1;
+
+ out:
+	aufs_read_unlock(dentry, AuLock_IR);
+	TraceErr(err);
+	valid = !err;
+	if (!valid)
+		LKTRTrace("%.*s invalid\n", DLNPair(dentry));
+	return valid;
+}
+
+static void aufs_d_release(struct dentry *dentry)
+{
+	struct aufs_dinfo *dinfo;
+	aufs_bindex_t bend, bindex;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	AuDebugOn(!d_unhashed(dentry));
+
+	dinfo = dentry->d_fsdata;
+	if (unlikely(!dinfo))
+		return;
+
+	/* dentry may not be revalidated */
+	bindex = dinfo->di_bstart;
+	if (bindex >= 0) {
+		struct aufs_hdentry *p;
+		bend = dinfo->di_bend;
+		AuDebugOn(bend < bindex);
+		p = dinfo->di_hdentry + bindex;
+		while (bindex++ <= bend) {
+			if (p->hd_dentry)
+				hdput(p);
+			p++;
+		}
+	}
+	kfree(dinfo->di_hdentry);
+	cache_free_dinfo(dinfo);
+}
+
+#if 0
+/* never use this, especially in case of nfs server */
+static int aufs_d_delete(struct dentry *dentry)
+{
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	/* unhash_it */
+	return 1;
+}
+#endif
+
+#if 0
+/* it may be called at remount time, too */
+static void aufs_d_iput(struct dentry *dentry, struct inode *inode)
+{
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, i%lu\n", DLNPair(dentry), inode->i_ino);
+
+	sb = dentry->d_sb;
+#if 0
+	si_read_lock(sb, !AuLock_FLUSH);
+	if (unlikely(au_flag_test(sb, AuFlag_PLINK)
+		     && au_is_plinked(sb, inode))) {
+		ii_write_lock(inode);
+		au_update_brange(inode, 1);
+		ii_write_unlock(inode);
+	}
+	si_read_unlock(sb);
+#endif
+	iput(inode);
+}
+#endif
+
+struct dentry_operations aufs_dop = {
+	.d_revalidate	= aufs_d_revalidate,
+	.d_release	= aufs_d_release,
+	//.d_delete	= aufs_d_delete
+	//.d_iput		= aufs_d_iput
+};
diff -ruN linux-2.6.22/fs/aufs/dentry.h linux-2.6.22-aufs/fs/aufs/dentry.h
--- linux-2.6.22/fs/aufs/dentry.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dentry.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: dentry.h,v 1.29 2007/07/15 20:03:28 sfjro Exp $ */
+
+#ifndef __AUFS_DENTRY_H__
+#define __AUFS_DENTRY_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/aufs_type.h>
+#include "misc.h"
+
+struct aufs_hdentry {
+	struct dentry	*hd_dentry;
+};
+
+struct aufs_dinfo {
+	atomic_t		di_generation;
+
+	struct aufs_rwsem	di_rwsem;
+	aufs_bindex_t		di_bstart, di_bend, di_bwh, di_bdiropq;
+	struct aufs_hdentry	*di_hdentry;
+};
+
+struct lkup_args {
+	struct vfsmount *nfsmnt;
+	int dlgt;
+	//struct super_block *sb;
+};
+
+/* ---------------------------------------------------------------------- */
+
+/* dentry.c */
+#if defined(CONFIG_AUFS_LHASH_PATCH) || defined(CONFIG_AUFS_DLGT)
+struct dentry *lkup_one(const char *name, struct dentry *parent, int len,
+			struct lkup_args *lkup);
+#else
+static inline
+struct dentry *lkup_one(const char *name, struct dentry *parent, int len,
+			struct lkup_args *lkup)
+{
+	return lookup_one_len(name, parent, len);
+}
+#endif
+
+extern struct dentry_operations aufs_dop;
+struct dentry *sio_lkup_one(const char *name, struct dentry *parent, int len,
+			    struct lkup_args *lkup);
+int lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type);
+int lkup_neg(struct dentry *dentry, aufs_bindex_t bindex);
+int au_refresh_hdentry(struct dentry *dentry, mode_t type);
+int au_reval_dpath(struct dentry *dentry, au_gen_t sgen);
+
+/* dinfo.c */
+int au_alloc_dinfo(struct dentry *dentry);
+struct aufs_dinfo *dtodi(struct dentry *dentry);
+
+void di_read_lock(struct dentry *d, int flags, unsigned int lsc);
+void di_read_unlock(struct dentry *d, int flags);
+void di_downgrade_lock(struct dentry *d, int flags);
+void di_write_lock(struct dentry *d, unsigned int lsc);
+void di_write_unlock(struct dentry *d);
+void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir);
+void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir);
+void di_write_unlock2(struct dentry *d1, struct dentry *d2);
+
+aufs_bindex_t dbstart(struct dentry *dentry);
+aufs_bindex_t dbend(struct dentry *dentry);
+aufs_bindex_t dbwh(struct dentry *dentry);
+aufs_bindex_t dbdiropq(struct dentry *dentry);
+struct dentry *au_h_dptr_i(struct dentry *dentry, aufs_bindex_t bindex);
+struct dentry *au_h_dptr(struct dentry *dentry);
+
+aufs_bindex_t dbtail(struct dentry *dentry);
+aufs_bindex_t dbtaildir(struct dentry *dentry);
+aufs_bindex_t dbtail_generic(struct dentry *dentry);
+
+void set_dbstart(struct dentry *dentry, aufs_bindex_t bindex);
+void set_dbend(struct dentry *dentry, aufs_bindex_t bindex);
+void set_dbwh(struct dentry *dentry, aufs_bindex_t bindex);
+void set_dbdiropq(struct dentry *dentry, aufs_bindex_t bindex);
+void hdput(struct aufs_hdentry *hdentry);
+void set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex,
+		struct dentry *h_dentry);
+
+void au_update_digen(struct dentry *dentry);
+void au_update_dbstart(struct dentry *dentry);
+int au_find_dbindex(struct dentry *dentry, struct dentry *h_dentry);
+
+/* ---------------------------------------------------------------------- */
+
+static inline au_gen_t au_digen(struct dentry *d)
+{
+	return atomic_read(&dtodi(d)->di_generation);
+}
+
+#ifdef CONFIG_AUFS_HINOTIFY
+static inline au_gen_t au_digen_dec(struct dentry *d)
+{
+	return atomic_dec_return(&dtodi(d)->di_generation);
+}
+#endif /* CONFIG_AUFS_HINOTIFY */
+
+/* ---------------------------------------------------------------------- */
+
+/* lock subclass for dinfo */
+enum {
+	AuLsc_DI_CHILD,		/* child first */
+	AuLsc_DI_CHILD2,	/* rename(2), link(2), and cpup at hinotify */
+	AuLsc_DI_CHILD3,	/* copyup dirs */
+	AuLsc_DI_PARENT,
+	AuLsc_DI_PARENT2,
+	AuLsc_DI_PARENT3
+};
+
+/*
+ * di_read_lock_child, di_write_lock_child,
+ * di_read_lock_child2, di_write_lock_child2,
+ * di_read_lock_child3, di_write_lock_child3,
+ * di_read_lock_parent, di_write_lock_parent,
+ * di_read_lock_parent2, di_write_lock_parent2,
+ * di_read_lock_parent3, di_write_lock_parent3,
+ */
+#define ReadLockFunc(name, lsc) \
+static inline void di_read_lock_##name(struct dentry *d, int flags) \
+{di_read_lock(d, flags, AuLsc_DI_##lsc);}
+
+#define WriteLockFunc(name, lsc) \
+static inline void di_write_lock_##name(struct dentry *d) \
+{di_write_lock(d, AuLsc_DI_##lsc);}
+
+#define RWLockFuncs(name, lsc) \
+	ReadLockFunc(name, lsc) \
+	WriteLockFunc(name, lsc)
+
+RWLockFuncs(child, CHILD);
+RWLockFuncs(child2, CHILD2);
+RWLockFuncs(child3, CHILD3);
+RWLockFuncs(parent, PARENT);
+RWLockFuncs(parent2, PARENT2);
+RWLockFuncs(parent3, PARENT3);
+
+#undef ReadLockFunc
+#undef WriteLockFunc
+#undef RWLockFunc
+
+/* to debug easier, do not make them inlined functions */
+#define DiMustReadLock(d) do { \
+	SiMustAnyLock((d)->d_sb); \
+	RwMustReadLock(&dtodi(d)->di_rwsem); \
+} while (0)
+
+#define DiMustWriteLock(d) do { \
+	SiMustAnyLock((d)->d_sb); \
+	RwMustWriteLock(&dtodi(d)->di_rwsem); \
+} while (0)
+
+#define DiMustAnyLock(d) do { \
+	SiMustAnyLock((d)->d_sb); \
+	RwMustAnyLock(&dtodi(d)->di_rwsem); \
+} while (0)
+
+#define DiMustNoWaiters(d)	RwMustNoWaiters(&dtodi(d)->di_rwsem)
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DENTRY_H__ */
diff -ruN linux-2.6.22/fs/aufs/dinfo.c linux-2.6.22-aufs/fs/aufs/dinfo.c
--- linux-2.6.22/fs/aufs/dinfo.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dinfo.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: dinfo.c,v 1.29 2007/07/09 05:43:06 sfjro Exp $ */
+
+#include "aufs.h"
+
+int au_alloc_dinfo(struct dentry *dentry)
+{
+	struct aufs_dinfo *dinfo;
+	struct super_block *sb;
+	int nbr;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	AuDebugOn(dentry->d_fsdata);
+
+	dinfo = cache_alloc_dinfo();
+	//if (LktrCond) {cache_free_dinfo(dinfo); dinfo = NULL;}
+	if (dinfo) {
+		sb = dentry->d_sb;
+		nbr = sbend(sb) + 1;
+		if (unlikely(nbr <= 0))
+			nbr = 1;
+		dinfo->di_hdentry = kcalloc(nbr, sizeof(*dinfo->di_hdentry),
+					    GFP_KERNEL);
+		//if (LktrCond)
+		//{kfree(dinfo->di_hdentry); dinfo->di_hdentry = NULL;}
+		if (dinfo->di_hdentry) {
+			rw_init_wlock_nested(&dinfo->di_rwsem, AuLsc_DI_PARENT);
+			dinfo->di_bstart = dinfo->di_bend = -1;
+			dinfo->di_bwh = dinfo->di_bdiropq = -1;
+			atomic_set(&dinfo->di_generation, au_sigen(sb));
+
+			dentry->d_fsdata = dinfo;
+			dentry->d_op = &aufs_dop;
+			return 0; /* success */
+		}
+		cache_free_dinfo(dinfo);
+	}
+	TraceErr(-ENOMEM);
+	return -ENOMEM;
+}
+
+struct aufs_dinfo *dtodi(struct dentry *dentry)
+{
+	struct aufs_dinfo *dinfo = dentry->d_fsdata;
+	AuDebugOn(!dinfo
+		 || !dinfo->di_hdentry
+		 /* || stosi(dentry->d_sb)->si_bend < dinfo->di_bend */
+		 || dinfo->di_bend < dinfo->di_bstart
+		 /* dbwh can be outside of this range */
+		 || (0 <= dinfo->di_bdiropq
+		     && (dinfo->di_bdiropq < dinfo->di_bstart
+			 /* || dinfo->di_bend < dinfo->di_bdiropq */))
+		);
+	return dinfo;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void do_ii_write_lock(struct inode *inode, unsigned int lsc)
+{
+	switch (lsc) {
+	case AuLsc_DI_CHILD:
+		ii_write_lock_child(inode);
+		break;
+	case AuLsc_DI_CHILD2:
+		ii_write_lock_child2(inode);
+		break;
+	case AuLsc_DI_CHILD3:
+		ii_write_lock_child3(inode);
+		break;
+	case AuLsc_DI_PARENT:
+		//AuDebugOn(!S_ISDIR(inode->i_mode));
+		ii_write_lock_parent(inode);
+		break;
+	case AuLsc_DI_PARENT2:
+		//AuDebugOn(!S_ISDIR(inode->i_mode));
+		ii_write_lock_parent2(inode);
+		break;
+	case AuLsc_DI_PARENT3:
+		//AuDebugOn(!S_ISDIR(inode->i_mode));
+		ii_write_lock_parent3(inode);
+		break;
+	default:
+		BUG();
+	}
+}
+
+static void do_ii_read_lock(struct inode *inode, unsigned int lsc)
+{
+	switch (lsc) {
+	case AuLsc_DI_CHILD:
+		ii_read_lock_child(inode);
+		break;
+	case AuLsc_DI_CHILD2:
+		ii_read_lock_child2(inode);
+		break;
+	case AuLsc_DI_CHILD3:
+		ii_read_lock_child3(inode);
+		break;
+	case AuLsc_DI_PARENT:
+		//AuDebugOn(!S_ISDIR(inode->i_mode));
+		ii_read_lock_parent(inode);
+		break;
+	case AuLsc_DI_PARENT2:
+		//AuDebugOn(!S_ISDIR(inode->i_mode));
+		ii_read_lock_parent2(inode);
+		break;
+	case AuLsc_DI_PARENT3:
+		//AuDebugOn(!S_ISDIR(inode->i_mode));
+		ii_read_lock_parent3(inode);
+		break;
+	default:
+		BUG();
+	}
+}
+
+void di_read_lock(struct dentry *d, int flags, unsigned int lsc)
+{
+	SiMustAnyLock(d->d_sb);
+	// todo: always nested?
+	rw_read_lock_nested(&dtodi(d)->di_rwsem, lsc);
+	if (d->d_inode) {
+		if (flags & AuLock_IW)
+			do_ii_write_lock(d->d_inode, lsc);
+		else if (flags & AuLock_IR)
+			do_ii_read_lock(d->d_inode, lsc);
+	}
+}
+
+void di_read_unlock(struct dentry *d, int flags)
+{
+	SiMustAnyLock(d->d_sb);
+	if (d->d_inode) {
+		if (flags & AuLock_IW)
+			ii_write_unlock(d->d_inode);
+		else if (flags & AuLock_IR)
+			ii_read_unlock(d->d_inode);
+	}
+	rw_read_unlock(&dtodi(d)->di_rwsem);
+}
+
+void di_downgrade_lock(struct dentry *d, int flags)
+{
+	SiMustAnyLock(d->d_sb);
+	rw_dgrade_lock(&dtodi(d)->di_rwsem);
+	if (d->d_inode && (flags & AuLock_IR))
+		ii_downgrade_lock(d->d_inode);
+}
+
+void di_write_lock(struct dentry *d, unsigned int lsc)
+{
+	SiMustAnyLock(d->d_sb);
+	// todo: always nested?
+	rw_write_lock_nested(&dtodi(d)->di_rwsem, lsc);
+	if (d->d_inode)
+		do_ii_write_lock(d->d_inode, lsc);
+}
+
+void di_write_unlock(struct dentry *d)
+{
+	SiMustAnyLock(d->d_sb);
+	if (d->d_inode)
+		ii_write_unlock(d->d_inode);
+	rw_write_unlock(&dtodi(d)->di_rwsem);
+}
+
+void di_write_lock2_child(struct dentry *d1, struct dentry *d2, int isdir)
+{
+	TraceEnter();
+	AuDebugOn(d1 == d2
+		  || d1->d_inode == d2->d_inode
+		  || d1->d_sb != d2->d_sb);
+
+	if (isdir && au_is_subdir(d1, d2)) {
+		di_write_lock_child(d1);
+		di_write_lock_child2(d2);
+	} else {
+		/* there should be no races */
+		di_write_lock_child(d2);
+		di_write_lock_child2(d1);
+	}
+}
+
+void di_write_lock2_parent(struct dentry *d1, struct dentry *d2, int isdir)
+{
+	TraceEnter();
+	AuDebugOn(d1 == d2
+		  || d1->d_inode == d2->d_inode
+		  || d1->d_sb != d2->d_sb);
+
+	if (isdir && au_is_subdir(d1, d2)) {
+		di_write_lock_parent(d1);
+		di_write_lock_parent2(d2);
+	} else {
+		/* there should be no races */
+		di_write_lock_parent(d2);
+		di_write_lock_parent2(d1);
+	}
+}
+
+void di_write_unlock2(struct dentry *d1, struct dentry *d2)
+{
+	di_write_unlock(d1);
+	if (d1->d_inode == d2->d_inode)
+		rw_write_unlock(&dtodi(d2)->di_rwsem);
+	else
+		di_write_unlock(d2);
+}
+
+/* ---------------------------------------------------------------------- */
+
+aufs_bindex_t dbstart(struct dentry *dentry)
+{
+	DiMustAnyLock(dentry);
+	return dtodi(dentry)->di_bstart;
+}
+
+aufs_bindex_t dbend(struct dentry *dentry)
+{
+	DiMustAnyLock(dentry);
+	return dtodi(dentry)->di_bend;
+}
+
+aufs_bindex_t dbwh(struct dentry *dentry)
+{
+	DiMustAnyLock(dentry);
+	return dtodi(dentry)->di_bwh;
+}
+
+aufs_bindex_t dbdiropq(struct dentry *dentry)
+{
+	DiMustAnyLock(dentry);
+	AuDebugOn(dentry->d_inode
+		  && dentry->d_inode->i_mode
+		  && !S_ISDIR(dentry->d_inode->i_mode));
+	return dtodi(dentry)->di_bdiropq;
+}
+
+struct dentry *au_h_dptr_i(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	struct dentry *d;
+
+	DiMustAnyLock(dentry);
+	if (dbstart(dentry) < 0 || bindex < dbstart(dentry))
+		return NULL;
+	AuDebugOn(bindex < 0
+		  /* || bindex > sbend(dentry->d_sb) */);
+	d = dtodi(dentry)->di_hdentry[0 + bindex].hd_dentry;
+	AuDebugOn(d && (atomic_read(&d->d_count) <= 0));
+	return d;
+}
+
+struct dentry *au_h_dptr(struct dentry *dentry)
+{
+	return au_h_dptr_i(dentry, dbstart(dentry));
+}
+
+aufs_bindex_t dbtail(struct dentry *dentry)
+{
+	aufs_bindex_t bend, bwh;
+
+	bend = dbend(dentry);
+	if (0 <= bend) {
+		bwh = dbwh(dentry);
+		//AuDebugOn(bend < bwh);
+		if (!bwh)
+			return bwh;
+		if (0 < bwh && bwh < bend)
+			return bwh - 1;
+	}
+	return bend;
+}
+
+aufs_bindex_t dbtaildir(struct dentry *dentry)
+{
+	aufs_bindex_t bend, bopq;
+
+	AuDebugOn(dentry->d_inode
+		  && dentry->d_inode->i_mode
+		  && !S_ISDIR(dentry->d_inode->i_mode));
+
+	bend = dbtail(dentry);
+	if (0 <= bend) {
+		bopq = dbdiropq(dentry);
+		AuDebugOn(bend < bopq);
+		if (0 <= bopq && bopq < bend)
+			bend = bopq;
+	}
+	return bend;
+}
+
+aufs_bindex_t dbtail_generic(struct dentry *dentry)
+{
+	struct inode *inode;
+
+	inode = dentry->d_inode;
+	if (inode && S_ISDIR(inode->i_mode))
+		return dbtaildir(dentry);
+	else
+		return dbtail(dentry);
+}
+
+/* ---------------------------------------------------------------------- */
+
+// hard/soft set
+void set_dbstart(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	DiMustWriteLock(dentry);
+	AuDebugOn(sbend(dentry->d_sb) < bindex);
+	/* */
+	dtodi(dentry)->di_bstart = bindex;
+}
+
+void set_dbend(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	DiMustWriteLock(dentry);
+	AuDebugOn(sbend(dentry->d_sb) < bindex
+		  || bindex < dbstart(dentry));
+	dtodi(dentry)->di_bend = bindex;
+}
+
+void set_dbwh(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	DiMustWriteLock(dentry);
+	AuDebugOn(sbend(dentry->d_sb) < bindex);
+	/* dbwh can be outside of bstart - bend range */
+	dtodi(dentry)->di_bwh = bindex;
+}
+
+void set_dbdiropq(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	DiMustWriteLock(dentry);
+	AuDebugOn(sbend(dentry->d_sb) < bindex);
+	AuDebugOn((bindex >= 0
+		   && (bindex < dbstart(dentry) || dbend(dentry) < bindex))
+		  || (dentry->d_inode
+		      && dentry->d_inode->i_mode
+		      && !S_ISDIR(dentry->d_inode->i_mode)));
+	dtodi(dentry)->di_bdiropq = bindex;
+}
+
+void hdput(struct aufs_hdentry *hd)
+{
+	dput(hd->hd_dentry);
+}
+
+void set_h_dptr(struct dentry *dentry, aufs_bindex_t bindex,
+		struct dentry *h_dentry)
+{
+	struct aufs_hdentry *hd = dtodi(dentry)->di_hdentry + bindex;
+	DiMustWriteLock(dentry);
+	AuDebugOn(bindex < dtodi(dentry)->di_bstart
+		  || bindex > dtodi(dentry)->di_bend
+		  || (h_dentry && atomic_read(&h_dentry->d_count) <= 0)
+		  || (h_dentry && hd->hd_dentry)
+		);
+	if (hd->hd_dentry)
+		hdput(hd);
+	hd->hd_dentry = h_dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_update_digen(struct dentry *dentry)
+{
+	//DiMustWriteLock(dentry);
+	AuDebugOn(!dentry->d_sb);
+	atomic_set(&dtodi(dentry)->di_generation, au_sigen(dentry->d_sb));
+	//smp_mb();
+}
+
+void au_update_dbstart(struct dentry *dentry)
+{
+	aufs_bindex_t bindex, bstart = dbstart(dentry), bend = dbend(dentry);
+	struct dentry *hidden_dentry;
+
+	DiMustWriteLock(dentry);
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (!hidden_dentry)
+			continue;
+		if (hidden_dentry->d_inode) {
+			set_dbstart(dentry, bindex);
+			return;
+		}
+		set_h_dptr(dentry, bindex, NULL);
+	}
+	//set_dbstart(dentry, -1);
+	//set_dbend(dentry, -1);
+}
+
+int au_find_dbindex(struct dentry *dentry, struct dentry *hidden_dentry)
+{
+	aufs_bindex_t bindex, bend;
+
+	bend = dbend(dentry);
+	for (bindex = dbstart(dentry); bindex <= bend; bindex++)
+		if (au_h_dptr_i(dentry, bindex) == hidden_dentry)
+			return bindex;
+	return -1;
+}
diff -ruN linux-2.6.22/fs/aufs/dir.c linux-2.6.22-aufs/fs/aufs/dir.c
--- linux-2.6.22/fs/aufs/dir.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dir.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,535 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: dir.c,v 1.42 2007/07/15 20:03:45 sfjro Exp $ */
+
+#include "aufs.h"
+
+static int reopen_dir(struct file *file)
+{
+	int err;
+	struct dentry *dentry, *hidden_dentry;
+	aufs_bindex_t bindex, btail, bstart;
+	struct file *hidden_file;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	AuDebugOn(!S_ISDIR(dentry->d_inode->i_mode));
+
+	/* open all hidden dirs */
+	bstart = dbstart(dentry);
+#if 1
+	for (bindex = fbstart(file); bindex < bstart; bindex++)
+		set_h_fptr(file, bindex, NULL);
+#endif
+	set_fbstart(file, bstart);
+	btail = dbtaildir(dentry);
+#if 1
+	for (bindex = fbend(file); btail < bindex; bindex--)
+		set_h_fptr(file, bindex, NULL);
+#endif
+	set_fbend(file, btail);
+	for (bindex = bstart; bindex <= btail; bindex++) {
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (!hidden_dentry)
+			continue;
+		hidden_file = au_h_fptr_i(file, bindex);
+		if (hidden_file) {
+			AuDebugOn(hidden_file->f_dentry != hidden_dentry);
+			continue;
+		}
+
+		hidden_file = hidden_open(dentry, bindex, file->f_flags);
+		// unavailable
+		//if (LktrCond) {fput(hidden_file);
+		//br_put(stobr(dentry->d_sb, bindex));hidden_file=ERR_PTR(-1);}
+		err = PTR_ERR(hidden_file);
+		if (IS_ERR(hidden_file))
+			goto out; // close all?
+		//cpup_file_flags(hidden_file, file);
+		set_h_fptr(file, bindex, hidden_file);
+	}
+	err = 0;
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int do_open_dir(struct file *file, int flags)
+{
+	int err;
+	aufs_bindex_t bindex, btail;
+	struct dentry *dentry, *hidden_dentry;
+	struct file *hidden_file;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, 0x%x\n", DLNPair(dentry), flags);
+	AuDebugOn(!dentry->d_inode || !S_ISDIR(dentry->d_inode->i_mode));
+
+	err = 0;
+	set_fvdir_cache(file, NULL);
+	file->f_version = dentry->d_inode->i_version;
+	bindex = dbstart(dentry);
+	set_fbstart(file, bindex);
+	btail = dbtaildir(dentry);
+	set_fbend(file, btail);
+	for (; !err && bindex <= btail; bindex++) {
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (!hidden_dentry)
+			continue;
+
+		hidden_file = hidden_open(dentry, bindex, flags);
+		//if (LktrCond) {fput(hidden_file);
+		//br_put(stobr(dentry->d_sb, bindex));hidden_file=ERR_PTR(-1);}
+		if (!IS_ERR(hidden_file)) {
+			set_h_fptr(file, bindex, hidden_file);
+			continue;
+		}
+		err = PTR_ERR(hidden_file);
+	}
+	if (!err)
+		return 0; /* success */
+
+	/* close all */
+	for (bindex = fbstart(file); !err && bindex <= btail; bindex++)
+		set_h_fptr(file, bindex, NULL);
+	set_fbstart(file, -1);
+	set_fbend(file, -1);
+	return err;
+}
+
+static int aufs_open_dir(struct inode *inode, struct file *file)
+{
+	return au_do_open(inode, file, do_open_dir);
+}
+
+static int aufs_release_dir(struct inode *inode, struct file *file)
+{
+	struct aufs_vdir *vdir_cache;
+	struct super_block *sb;
+
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, DLNPair(file->f_dentry));
+
+	sb = file->f_dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	fi_write_lock(file);
+	vdir_cache = fvdir_cache(file);
+	if (vdir_cache)
+		free_vdir(vdir_cache);
+	fi_write_unlock(file);
+	au_fin_finfo(file);
+	si_read_unlock(sb);
+	return 0;
+}
+
+static int fsync_dir(struct dentry *dentry, int datasync)
+{
+	int err;
+	struct inode *inode;
+	struct super_block *sb;
+	aufs_bindex_t bend, bindex;
+
+	LKTRTrace("%.*s, %d\n", DLNPair(dentry), datasync);
+	DiMustAnyLock(dentry);
+	sb = dentry->d_sb;
+	SiMustAnyLock(sb);
+	inode = dentry->d_inode;
+	IMustLock(inode);
+	IiMustAnyLock(inode);
+
+	err = 0;
+	bend = dbend(dentry);
+	for (bindex = dbstart(dentry); !err && bindex <= bend; bindex++) {
+		struct dentry *h_dentry;
+		struct inode *h_inode;
+		struct file_operations *fop;
+
+		if (test_ro(sb, bindex, inode))
+			continue;
+		h_dentry = au_h_dptr_i(dentry, bindex);
+		if (!h_dentry)
+			continue;
+		h_inode = h_dentry->d_inode;
+		if (!h_inode)
+			continue;
+
+		/* cf. fs/nsfd/vfs.c and fs/nfsd/nfs4recover.c */
+		//hdir_lock(h_inode, inode, bindex);
+		vfsub_i_lock(h_inode);
+		fop = (void*)h_inode->i_fop;
+		err = filemap_fdatawrite(h_inode->i_mapping);
+		if (!err && fop && fop->fsync)
+			err = fop->fsync(NULL, h_dentry, datasync);
+		if (!err)
+			err = filemap_fdatawrite(h_inode->i_mapping);
+		//hdir_unlock(h_inode, inode, bindex);
+		vfsub_i_unlock(h_inode);
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * @file may be NULL
+ */
+static int aufs_fsync_dir(struct file *file, struct dentry *dentry,
+			  int datasync)
+{
+	int err;
+	struct inode *inode;
+	struct file *hidden_file;
+	struct super_block *sb;
+	aufs_bindex_t bend, bindex;
+
+	LKTRTrace("%.*s, %d\n", DLNPair(dentry), datasync);
+	inode = dentry->d_inode;
+	IMustLock(inode);
+
+	err = 0;
+	sb = dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	if (file) {
+		err = au_reval_and_lock_finfo(file, reopen_dir, /*wlock*/1,
+					      /*locked*/1);
+		//err = -1;
+		if (unlikely(err))
+			goto out;
+	} else
+		di_read_lock_child(dentry, !AuLock_IW);
+
+	ii_write_lock_child(inode);
+	if (file) {
+		bend = fbend(file);
+		for (bindex = fbstart(file); !err && bindex <= bend; bindex++) {
+			hidden_file = au_h_fptr_i(file, bindex);
+			if (!hidden_file || test_ro(sb, bindex, inode))
+				continue;
+
+			err = -EINVAL;
+			if (hidden_file->f_op && hidden_file->f_op->fsync) {
+				// todo: try do_fsync() in fs/sync.c
+#if 0
+				AuDebugOn(hidden_file->f_dentry->d_inode
+					  != au_h_iptr_i(inode, bindex));
+				hdir_lock(hidden_file->f_dentry->d_inode, inode,
+					  bindex);
+#else
+				vfsub_i_lock(hidden_file->f_dentry->d_inode);
+#endif
+				err = hidden_file->f_op->fsync
+					(hidden_file, hidden_file->f_dentry,
+					 datasync);
+				//err = -1;
+#if 0
+				hdir_unlock(hidden_file->f_dentry->d_inode,
+					    inode, bindex);
+#else
+				vfsub_i_unlock(hidden_file->f_dentry->d_inode);
+#endif
+			}
+		}
+	} else
+		err = fsync_dir(dentry, datasync);
+	au_cpup_attr_timesizes(inode);
+	ii_write_unlock(inode);
+	if (file)
+		fi_write_unlock(file);
+	else
+		di_read_unlock(dentry, !AuLock_IW);
+
+ out:
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int aufs_readdir(struct file *file, void *dirent, filldir_t filldir)
+{
+	int err;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct super_block *sb;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, pos %Ld\n", DLNPair(dentry), file->f_pos);
+	inode = dentry->d_inode;
+	IMustLock(inode);
+
+	au_nfsd_lockdep_off();
+	sb = dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, reopen_dir, /*wlock*/1,
+				      /*locked*/1);
+	if (unlikely(err))
+		goto out;
+
+	ii_write_lock_child(inode);
+	err = au_init_vdir(file);
+	if (unlikely(err)) {
+		ii_write_unlock(inode);
+		goto out_unlock;
+	}
+	//DbgVdir(fvdir_cache(file));// goto out_unlock;
+
+	/* nfsd filldir calls lookup_one_len(). */
+	ii_downgrade_lock(inode);
+	err = au_fill_de(file, dirent, filldir);
+	//DbgVdir(fvdir_cache(file));// goto out_unlock;
+
+	inode->i_atime = au_h_iptr(inode)->i_atime;
+	ii_read_unlock(inode);
+
+ out_unlock:
+	fi_write_unlock(file);
+ out:
+	si_read_unlock(sb);
+	au_nfsd_lockdep_on();
+#if 0 // debug
+	if (LktrCond)
+		igrab(inode);
+#endif
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct test_empty_arg {
+	struct aufs_nhash *whlist;
+	int whonly;
+	aufs_bindex_t bindex;
+	int err, called;
+};
+
+static int test_empty_cb(void *__arg, const char *__name, int namelen,
+			 loff_t offset, au_filldir_ino_t ino,
+			 unsigned int d_type)
+{
+	struct test_empty_arg *arg = __arg;
+	char *name = (void*)__name;
+
+	LKTRTrace("%.*s\n", namelen, name);
+
+	arg->err = 0;
+	arg->called++;
+	//smp_mb();
+	if (name[0] == '.'
+	    && (namelen == 1 || (name[1] == '.' && namelen == 2)))
+		return 0; /* success */
+
+	if (namelen <= AUFS_WH_PFX_LEN
+	    || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
+		if (arg->whonly && !test_known_wh(arg->whlist, name, namelen))
+			arg->err = -ENOTEMPTY;
+		goto out;
+	}
+
+	name += AUFS_WH_PFX_LEN;
+	namelen -= AUFS_WH_PFX_LEN;
+	if (!test_known_wh(arg->whlist, name, namelen))
+		arg->err = append_wh(arg->whlist, name, namelen, arg->bindex);
+
+ out:
+	//smp_mb();
+	TraceErr(arg->err);
+	return arg->err;
+}
+
+static int do_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
+{
+	int err, dlgt;
+	struct file *hidden_file;
+
+	LKTRTrace("%.*s, {%p, %d, %d}\n",
+		  DLNPair(dentry), arg->whlist, arg->whonly, arg->bindex);
+
+	hidden_file = hidden_open(dentry, arg->bindex,
+				  O_RDONLY | O_NONBLOCK | O_DIRECTORY
+				  | O_LARGEFILE);
+	err = PTR_ERR(hidden_file);
+	if (IS_ERR(hidden_file))
+		goto out;
+
+	dlgt = need_dlgt(dentry->d_sb);
+	//hidden_file->f_pos = 0;
+	do {
+		arg->err = 0;
+		arg->called = 0;
+		//smp_mb();
+		err = vfsub_readdir(hidden_file, test_empty_cb, arg, dlgt);
+		if (err >= 0)
+			err = arg->err;
+	} while (!err && arg->called);
+	fput(hidden_file);
+	sbr_put(dentry->d_sb, arg->bindex);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+struct do_test_empty_args {
+	int *errp;
+	struct dentry *dentry;
+	struct test_empty_arg *arg;
+};
+
+static void call_do_test_empty(void *args)
+{
+	struct do_test_empty_args *a = args;
+	*a->errp = do_test_empty(a->dentry, a->arg);
+}
+
+static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
+{
+	int err, wkq_err;
+	struct dentry *hidden_dentry;
+	struct inode *hidden_inode;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	hidden_dentry = au_h_dptr_i(dentry, arg->bindex);
+	AuDebugOn(!hidden_dentry);
+	hidden_inode = hidden_dentry->d_inode;
+	AuDebugOn(!hidden_inode || !S_ISDIR(hidden_inode->i_mode));
+
+	vfsub_i_lock_nested(hidden_inode, AuLsc_I_CHILD);
+	err = au_test_perm(hidden_inode, MAY_EXEC | MAY_READ,
+			   need_dlgt(dentry->d_sb));
+	vfsub_i_unlock(hidden_inode);
+	if (!err)
+		err = do_test_empty(dentry, arg);
+	else {
+		struct do_test_empty_args args = {
+			.errp	= &err,
+			.dentry	= dentry,
+			.arg	= arg
+		};
+		wkq_err = au_wkq_wait(call_do_test_empty, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+int au_test_empty_lower(struct dentry *dentry)
+{
+	int err;
+	struct inode *inode;
+	struct test_empty_arg arg;
+	struct aufs_nhash *whlist;
+	aufs_bindex_t bindex, bstart, btail;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	inode = dentry->d_inode;
+	AuDebugOn(!inode || !S_ISDIR(inode->i_mode));
+
+	whlist = nhash_new(GFP_KERNEL);
+	err = PTR_ERR(whlist);
+	if (IS_ERR(whlist))
+		goto out;
+
+	bstart = dbstart(dentry);
+	arg.whlist = whlist;
+	arg.whonly = 0;
+	arg.bindex = bstart;
+	err = do_test_empty(dentry, &arg);
+	if (unlikely(err))
+		goto out_whlist;
+
+	arg.whonly = 1;
+	btail = dbtaildir(dentry);
+	for (bindex = bstart + 1; !err && bindex <= btail; bindex++) {
+		struct dentry *hidden_dentry;
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (hidden_dentry && hidden_dentry->d_inode) {
+			AuDebugOn(!S_ISDIR(hidden_dentry->d_inode->i_mode));
+			arg.bindex = bindex;
+			err = do_test_empty(dentry, &arg);
+		}
+	}
+
+ out_whlist:
+	nhash_del(whlist);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+int test_empty(struct dentry *dentry, struct aufs_nhash *whlist)
+{
+	int err;
+	struct inode *inode;
+	struct test_empty_arg arg;
+	aufs_bindex_t bindex, btail;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	inode = dentry->d_inode;
+	AuDebugOn(!inode || !S_ISDIR(inode->i_mode));
+
+	err = 0;
+	arg.whlist = whlist;
+	arg.whonly = 1;
+	btail = dbtaildir(dentry);
+	for (bindex = dbstart(dentry); !err && bindex <= btail; bindex++) {
+		struct dentry *hidden_dentry;
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (hidden_dentry && hidden_dentry->d_inode) {
+			AuDebugOn(!S_ISDIR(hidden_dentry->d_inode->i_mode));
+			arg.bindex = bindex;
+			err = sio_test_empty(dentry, &arg);
+		}
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_add_nlink(struct inode *dir, struct inode *h_dir)
+{
+	AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode));
+	dir->i_nlink += h_dir->i_nlink - 2;
+	if (unlikely(h_dir->i_nlink < 2))
+		dir->i_nlink += 2;
+}
+
+void au_sub_nlink(struct inode *dir, struct inode *h_dir)
+{
+	AuDebugOn(!S_ISDIR(dir->i_mode) || !S_ISDIR(h_dir->i_mode));
+	dir->i_nlink -= h_dir->i_nlink - 2;
+	if (unlikely(h_dir->i_nlink < 2))
+		dir->i_nlink -= 2;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct file_operations aufs_dir_fop = {
+	.read		= generic_read_dir,
+	.readdir	= aufs_readdir,
+	.open		= aufs_open_dir,
+	.release	= aufs_release_dir,
+	.flush		= aufs_flush,
+	.fsync		= aufs_fsync_dir,
+};
diff -ruN linux-2.6.22/fs/aufs/dir.h linux-2.6.22-aufs/fs/aufs/dir.h
--- linux-2.6.22/fs/aufs/dir.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/dir.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: dir.h,v 1.21 2007/07/09 05:43:27 sfjro Exp $ */
+
+#ifndef __AUFS_DIR_H__
+#define __AUFS_DIR_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+typedef u64	au_filldir_ino_t;
+#else
+typedef ino_t	au_filldir_ino_t;
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/* need to be faster and smaller */
+
+#define AuSize_DEBLK	512 // todo: changeable
+#define AuSize_NHASH	32 // todo: changeable
+#if AuSize_DEBLK < NAME_MAX || PAGE_SIZE < AuSize_DEBLK
+#error invalid size AuSize_DEBLK
+#endif
+
+typedef char aufs_deblk_t[AuSize_DEBLK];
+
+struct aufs_nhash {
+	struct hlist_head heads[AuSize_NHASH];
+};
+
+struct aufs_destr {
+	unsigned char	len;
+	char		name[0];
+} __attribute__ ((packed));
+
+struct aufs_dehstr {
+	struct hlist_node hash;
+	struct aufs_destr *str;
+};
+
+struct aufs_de {
+	ino_t			de_ino;
+	unsigned char		de_type;
+	/* caution: packed */
+	struct aufs_destr	de_str;
+} __attribute__ ((packed));
+
+struct aufs_wh {
+	struct hlist_node	wh_hash;
+	aufs_bindex_t		wh_bindex;
+	struct aufs_destr	wh_str;
+} __attribute__ ((packed));
+
+union aufs_deblk_p {
+	unsigned char	*p;
+	aufs_deblk_t	*deblk;
+	struct aufs_de	*de;
+};
+
+struct aufs_vdir {
+	aufs_deblk_t	**vd_deblk;
+	int		vd_nblk;
+	struct {
+		int			i;
+		union aufs_deblk_p	p;
+	} vd_last;
+
+	unsigned long	vd_version;
+	unsigned long	vd_jiffy;
+};
+
+/* ---------------------------------------------------------------------- */
+
+/* dir.c */
+extern struct file_operations aufs_dir_fop;
+int au_test_empty_lower(struct dentry *dentry);
+int test_empty(struct dentry *dentry, struct aufs_nhash *whlist);
+void au_add_nlink(struct inode *dir, struct inode *h_dir);
+void au_sub_nlink(struct inode *dir, struct inode *h_dir);
+
+/* vdir.c */
+struct aufs_nhash *nhash_new(gfp_t gfp);
+void nhash_del(struct aufs_nhash *nhash);
+void nhash_init(struct aufs_nhash *nhash);
+void nhash_move(struct aufs_nhash *dst, struct aufs_nhash *src);
+void nhash_fin(struct aufs_nhash *nhash);
+int is_longer_wh(struct aufs_nhash *whlist, aufs_bindex_t btgt, int limit);
+int test_known_wh(struct aufs_nhash *whlist, char *name, int namelen);
+int append_wh(struct aufs_nhash *whlist, char *name, int namelen,
+	      aufs_bindex_t bindex);
+void free_vdir(struct aufs_vdir *vdir);
+int au_init_vdir(struct file *file);
+int au_fill_de(struct file *file, void *dirent, filldir_t filldir);
+
+/* ---------------------------------------------------------------------- */
+
+static inline
+unsigned int au_name_hash(const unsigned char *name, unsigned int len)
+{
+	return (full_name_hash(name, len) % AuSize_NHASH);
+}
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DIR_H__ */
diff -ruN linux-2.6.22/fs/aufs/export.c linux-2.6.22-aufs/fs/aufs/export.c
--- linux-2.6.22/fs/aufs/export.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/export.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: export.c,v 1.11 2007/07/15 20:03:59 sfjro Exp $ */
+
+#include "aufs.h"
+
+extern struct export_operations export_op_default;
+#define CALL(ops, func)	(((ops)->func) ? ((ops)->func) : export_op_default.func)
+#define is_anon(d)	((d)->d_flags & DCACHE_DISCONNECTED)
+
+union conv {
+#if BITS_PER_LONG == 32
+	__u32 a[1];
+#else
+	__u32 a[2];
+#endif
+	ino_t ino;
+};
+
+static ino_t decode_ino(__u32 *a)
+{
+	union conv u;
+	u.a[0] = a[0];
+#if BITS_PER_LONG == 64
+	u.a[1] = a[1];
+#endif
+	return u.ino;
+}
+
+static void encode_ino(__u32 *a, ino_t ino)
+{
+	union conv u;
+	u.ino = ino;
+	a[0] = u.a[0];
+#if BITS_PER_LONG == 64
+	a[1] = u.a[1];
+#endif
+}
+
+/* NFS file handle */
+enum {
+	/* support 64bit inode number */
+	/* but untested */
+	Fh_br_id,
+	Fh_sigen,
+	Fh_ino1,
+#if BITS_PER_LONG == 64
+	Fh_ino2,
+#endif
+	Fh_dir_ino1,
+#if BITS_PER_LONG == 64
+	Fh_dir_ino2,
+#endif
+	Fh_h_ino1,
+#if BITS_PER_LONG == 64
+	Fh_h_ino2,
+#endif
+	Fh_h_igen,
+	Fh_h_type,
+	Fh_tail,
+
+	Fh_ino = Fh_ino1,
+	Fh_dir_ino = Fh_dir_ino1,
+	Fh_h_ino = Fh_h_ino1,
+};
+
+/* ---------------------------------------------------------------------- */
+
+static struct dentry *decode_by_ino(struct super_block *sb, ino_t ino,
+				    ino_t dir_ino)
+{
+	struct dentry *dentry, *parent;
+	struct inode *inode;
+
+	LKTRTrace("i%lu, diri%lu\n", ino, dir_ino);
+
+	dentry = NULL;
+	inode = ilookup(sb, ino);
+	if (unlikely(!inode))
+		goto out;
+
+	dentry = ERR_PTR(-ESTALE);
+	if (unlikely(is_bad_inode(inode)))
+		goto out_iput;
+
+	dentry = NULL;
+	if (!S_ISDIR(inode->i_mode)) {
+		struct dentry *d;
+		spin_lock(&dcache_lock);
+		list_for_each_entry(d, &inode->i_dentry, d_alias)
+			if (!is_anon(d)
+			    && d->d_parent->d_inode->i_ino == dir_ino) {
+				dentry = dget_locked(d);
+				break;
+			}
+		spin_unlock(&dcache_lock);
+	} else {
+		dentry = d_find_alias(inode);
+		if (dentry && !is_anon(dentry)) {
+			parent = dget_parent(dentry);
+			if (parent->d_inode->i_ino == dir_ino) {
+				dput(parent);
+				goto out_iput; /* success */
+			}
+			dput(parent);
+		}
+
+		dput(dentry);
+		dentry = NULL;
+	}
+
+ out_iput:
+	iput(inode);
+ out:
+	TraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct find_name_by_ino {
+	int called, found;
+	ino_t ino;
+	char *name;
+	int namelen;
+};
+
+static int
+find_name_by_ino(void *arg, const char *name, int namelen, loff_t offset,
+		 au_filldir_ino_t ino, unsigned int d_type)
+{
+	struct find_name_by_ino *a = arg;
+
+	a->called++;
+	if (a->ino != ino)
+		return 0;
+
+	memcpy(a->name, name, namelen);
+	a->namelen = namelen;
+	a->found = 1;
+	return 1;
+}
+
+static struct dentry *decode_by_dir_ino(struct super_block *sb, ino_t ino,
+					ino_t dir_ino)
+{
+	struct dentry *dentry, *parent;
+	struct inode *dir;
+	struct find_name_by_ino arg;
+	struct file *file;
+	int err;
+
+	LKTRTrace("i%lu, diri%lu\n", ino, dir_ino);
+
+	dentry = NULL;
+	dir = ilookup(sb, dir_ino);
+	if (unlikely(!dir))
+		goto out;
+
+	dentry = ERR_PTR(-ESTALE);
+	if (unlikely(is_bad_inode(dir)))
+		goto out_iput;
+
+	dentry = NULL;
+	parent = d_find_alias(dir);
+	if (parent) {
+		if (unlikely(is_anon(parent))) {
+			dput(parent);
+			goto out_iput;
+		}
+	} else
+		goto out_iput;
+
+	file = dentry_open(parent, NULL, au_dir_roflags);
+	dentry = (void*)file;
+	if (IS_ERR(file))
+		goto out_iput;
+
+	dentry = ERR_PTR(-ENOMEM);
+	arg.name = __getname();
+	if (unlikely(!arg.name))
+		goto out_fput;
+	arg.ino = ino;
+	arg.found = 0;
+
+	do {
+		arg.called = 0;
+		//smp_mb();
+		err = vfsub_readdir(file, find_name_by_ino, &arg, /*dlgt*/0);
+	} while (!err && !arg.found && arg.called);
+	dentry = ERR_PTR(err);
+	if (arg.found) {
+		/* do not call lkup_one(), nor dlgt */
+		vfsub_i_lock(dir);
+		dentry = lookup_one_len(arg.name, parent, arg.namelen);
+		vfsub_i_unlock(dir);
+		TraceErrPtr(dentry);
+	}
+
+	//out_putname:
+	__putname(arg.name);
+ out_fput:
+	fput(file);
+ out_iput:
+	iput(dir);
+ out:
+	TraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct append_name {
+	int found, called, len;
+	char *h_path;
+	ino_t h_ino;
+};
+
+static int append_name(void *arg, const char *name, int len, loff_t pos,
+		       au_filldir_ino_t ino, unsigned int d_type)
+{
+	struct append_name *a = arg;
+	char *p;
+
+	a->called++;
+	if (ino != a->h_ino)
+		return 0;
+
+	AuDebugOn(len == 1 && *name == '.');
+	AuDebugOn(len == 2 && name[0] == '.' && name[1] == '.');
+	a->len = strlen(a->h_path);
+	memmove(a->h_path - a->len - 1, a->h_path, a->len);
+	a->h_path -= a->len + 1;
+	p = a->h_path + a->len;
+	*p++ = '/';
+	memcpy(p, name, a->len);
+	a->len += 1 + len;
+	a->found++;
+	return 1;
+}
+
+static int h_acceptable(void *expv, struct dentry *dentry)
+{
+	return 1;
+}
+
+static struct dentry*
+decode_by_path(struct super_block *sb, aufs_bindex_t bindex, __u32 *fh,
+	       int fh_len, void *context)
+{
+	struct dentry *dentry, *h_parent, *root, *h_root;
+	struct super_block *h_sb;
+	char *path, *p;
+	struct vfsmount *h_mnt;
+	struct append_name arg;
+	int len, err;
+	struct file *h_file;
+	struct nameidata nd;
+	struct aufs_branch *br;
+
+	LKTRTrace("b%d\n", bindex);
+	SiMustAnyLock(sb);
+
+	br = stobr(sb, bindex);
+	/* br_get(br); */
+	h_mnt = br->br_mnt;
+	h_sb = h_mnt->mnt_sb;
+	LKTRTrace("%s, h_decode_fh\n", au_sbtype(h_sb));
+	h_parent = CALL(h_sb->s_export_op, decode_fh)
+		(h_sb, fh + Fh_tail, fh_len - Fh_tail, fh[Fh_h_type],
+		 h_acceptable, /*context*/NULL);
+	dentry = h_parent;
+	if (unlikely(!h_parent || IS_ERR(h_parent))) {
+		Warn1("%s decode_fh failed\n", au_sbtype(h_sb));
+		goto out;
+	}
+	dentry = NULL;
+	if (unlikely(is_anon(h_parent))) {
+		Warn1("%s decode_fh returned a disconnected dentry\n",
+		      au_sbtype(h_sb));
+		dput(h_parent);
+		goto out;
+	}
+
+	dentry = ERR_PTR(-ENOMEM);
+	path = __getname();
+	if (unlikely(!path)) {
+		dput(h_parent);
+		goto out;
+	}
+
+	root = sb->s_root;
+	di_read_lock_parent(root, !AuLock_IR);
+	h_root = au_h_dptr_i(root, bindex);
+	di_read_unlock(root, !AuLock_IR);
+	arg.h_path = d_path(h_root, h_mnt, path, PATH_MAX);
+	dentry = (void*)arg.h_path;
+	if (unlikely(!arg.h_path || IS_ERR(arg.h_path)))
+		goto out_putname;
+	len = strlen(arg.h_path);
+	arg.h_path = d_path(h_parent, h_mnt, path, PATH_MAX);
+	dentry = (void*)arg.h_path;
+	if (unlikely(!arg.h_path || IS_ERR(arg.h_path)))
+		goto out_putname;
+	LKTRTrace("%s\n", arg.h_path);
+	if (len != 1)
+		arg.h_path += len;
+	LKTRTrace("%s\n", arg.h_path);
+
+	/* cf. fs/exportfs/expfs.c */
+	h_file = dentry_open(h_parent, NULL, au_dir_roflags);
+	dentry = (void*)h_file;
+	if (IS_ERR(h_file))
+		goto out_putname;
+
+	arg.found = 0;
+	arg.h_ino = decode_ino(fh + Fh_h_ino);
+	do {
+		arg.called = 0;
+		err = vfsub_readdir(h_file, append_name, &arg, /*dlgt*/0);
+	} while (!err && !arg.found && arg.called);
+	LKTRTrace("%s, %d\n", arg.h_path, arg.len);
+
+	p = d_path(root, stosi(sb)->si_mnt, path, PATH_MAX - arg.len - 2);
+	dentry = (void*)p;
+	if (unlikely(!p || IS_ERR(p)))
+		goto out_fput;
+	p[strlen(p)] = '/';
+	LKTRTrace("%s\n", p);
+
+	err = path_lookup(p, LOOKUP_FOLLOW, &nd);
+	dentry = ERR_PTR(err);
+	if (!err) {
+		dentry = dget(nd.dentry);
+		if (unlikely(is_anon(dentry))) {
+			dput(dentry);
+			dentry = ERR_PTR(-ESTALE);
+		}
+		path_release(&nd);
+	}
+
+ out_fput:
+	fput(h_file);
+ out_putname:
+	__putname(path);
+ out:
+	/* br_put(br); */
+	TraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct dentry*
+aufs_decode_fh(struct super_block *sb, __u32 *fh, int fh_len, int fh_type,
+	       int (*acceptable)(void *context, struct dentry *de),
+	       void *context)
+{
+	struct dentry *dentry;
+	ino_t ino, dir_ino;
+	aufs_bindex_t bindex, br_id;
+	struct inode *inode, *h_inode;
+	au_gen_t sigen;
+
+	//au_debug_on();
+	LKTRTrace("%d, fh{i%u, br_id %u, sigen %u, hi%u}\n",
+		  fh_type, fh[Fh_ino], fh[Fh_br_id], fh[Fh_sigen], fh[Fh_h_ino]);
+	AuDebugOn(fh_len < Fh_tail);
+
+	si_read_lock(sb, !AuLock_FLUSH);
+	lockdep_off();
+
+	/* branch id may be wrapped around */
+	dentry = ERR_PTR(-ESTALE);
+	br_id = fh[Fh_br_id];
+	sigen = fh[Fh_sigen];
+	bindex = find_brindex(sb, br_id);
+	if (unlikely(bindex < 0 || sigen + AUFS_BRANCH_MAX <= au_sigen(sb)))
+		goto out;
+
+	/* is this inode still cached? */
+	ino = decode_ino(fh + Fh_ino);
+	dir_ino = decode_ino(fh + Fh_dir_ino);
+	dentry = decode_by_ino(sb, ino, dir_ino);
+	if (IS_ERR(dentry))
+		goto out;
+	if (dentry)
+		goto accept;
+
+	/* is the parent dir cached? */
+	dentry = decode_by_dir_ino(sb, ino, dir_ino);
+	if (IS_ERR(dentry))
+		goto out;
+	if (dentry)
+		goto accept;
+
+	/* lookup path */
+	dentry = decode_by_path(sb, bindex, fh, fh_len, context);
+	if (IS_ERR(dentry))
+		goto out;
+	if (unlikely(!dentry))
+		goto out_stale;
+	if (unlikely(dentry->d_inode->i_ino != ino))
+		goto out_dput;
+
+ accept:
+	inode = dentry->d_inode;
+	h_inode = NULL;
+	ii_read_lock_child(inode);
+	if (ibstart(inode) <= bindex && bindex <= ibend(inode))
+		h_inode = au_h_iptr_i(inode, bindex);
+	ii_read_unlock(inode);
+	if (h_inode
+	    && h_inode->i_generation == fh[Fh_h_igen]
+	    && acceptable(context, dentry))
+		goto out; /* success */
+ out_dput:
+	dput(dentry);
+ out_stale:
+	dentry = ERR_PTR(-ESTALE);
+ out:
+	lockdep_on();
+	si_read_unlock(sb);
+	TraceErrPtr(dentry);
+	//au_debug_off();
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int aufs_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len,
+			  int connectable)
+{
+	int err;
+	struct super_block *sb, *h_sb;
+	struct inode *inode, *h_inode, *dir;
+	aufs_bindex_t bindex;
+	union conv u;
+	struct dentry *parent, *h_parent;
+
+	//au_debug_on();
+	BUILD_BUG_ON(sizeof(u.ino) != sizeof(u.a));
+	LKTRTrace("%.*s, max %d, conn %d\n",
+		  DLNPair(dentry), *max_len, connectable);
+	AuDebugOn(is_anon(dentry));
+	inode = dentry->d_inode;
+	AuDebugOn(!inode);
+	parent = dget_parent(dentry);
+	AuDebugOn(is_anon(parent));
+
+	err = -ENOSPC;
+	if (unlikely(*max_len <= Fh_tail)) {
+		Warn1("NFSv2 client (max_len %d)?\n", *max_len);
+		goto out;
+	}
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, AuLock_FLUSH);
+	di_read_lock_child(dentry, AuLock_IR);
+	di_read_lock_parent(parent, AuLock_IR);
+#ifdef CONFIG_AUFS_DEBUG
+	if (unlikely(!au_flag_test(sb, AuFlag_XINO)))
+		Warn1("NFS-exporting requires xino\n");
+#if 0
+	if (unlikely(au_flag_test_udba_inotify(sb)))
+		Warn1("udba=inotify is not recommended when exporting\n");
+#endif
+#endif
+
+	err = -EPERM;
+	bindex = ibstart(inode);
+	h_sb = sbr_sb(sb, bindex);
+	if (unlikely(!h_sb->s_export_op)) {
+		Err1("%s branch is not exportable\n", au_sbtype(h_sb));
+		goto out_unlock;
+	}
+
+#ifdef CONFIG_AUFS_ROBR
+	if (unlikely(au_is_aufs(h_sb))) {
+		Err1("aufs branch is not supported\n");
+		goto out_unlock;
+	}
+#endif
+
+	/* doesn't support pseudo-link */
+	if (unlikely(bindex < dbstart(dentry)
+		     || dbend(dentry) < bindex
+		     || !au_h_dptr_i(dentry, bindex))) {
+		Err("%.*s/%.*s, b%d, pseudo-link?\n",
+		    DLNPair(parent), DLNPair(dentry), bindex);
+		goto out_unlock;
+	}
+
+	fh[Fh_br_id] = sbr_id(sb, bindex);
+	fh[Fh_sigen] = au_sigen(sb);
+	encode_ino(fh + Fh_ino, inode->i_ino);
+	dir = parent->d_inode;
+	encode_ino(fh + Fh_dir_ino, dir->i_ino);
+	h_inode = au_h_iptr(inode);
+	encode_ino(fh + Fh_h_ino, h_inode->i_ino);
+	fh[Fh_h_igen] = h_inode->i_generation;
+
+	/* it should be set at exporting time */
+	if (unlikely(!h_sb->s_export_op->find_exported_dentry)) {
+		Warn("set default find_exported_dentry for %s\n",
+		     au_sbtype(h_sb));
+		h_sb->s_export_op->find_exported_dentry = find_exported_dentry;
+	}
+
+	*max_len -= Fh_tail;
+	//LKTRTrace("Fh_tail %d, max_len %d\n", Fh_tail, *max_len);
+	h_parent = au_h_dptr_i(parent, bindex);
+	AuDebugOn(is_anon(h_parent));
+	err = fh[Fh_h_type] = CALL(h_sb->s_export_op, encode_fh)
+		(h_parent, fh + Fh_tail, max_len, connectable);
+	*max_len += Fh_tail;
+	if (err != 255)
+		err = 2; //??
+	else
+		Warn1("%s encode_fh failed\n", au_sbtype(h_sb));
+
+ out_unlock:
+	di_read_unlock(parent, AuLock_IR);
+	aufs_read_unlock(dentry, AuLock_IR);
+ out:
+	dput(parent);
+	TraceErr(err);
+	//au_debug_off();
+	if (unlikely(err < 0))
+		err = 255;
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct export_operations aufs_export_op = {
+	.decode_fh = aufs_decode_fh,
+	.encode_fh = aufs_encode_fh
+};
diff -ruN linux-2.6.22/fs/aufs/f_op.c linux-2.6.22-aufs/fs/aufs/f_op.c
--- linux-2.6.22/fs/aufs/f_op.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/f_op.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: f_op.c,v 1.31 2007/07/15 20:02:36 sfjro Exp $ */
+
+#include <linux/fsnotify.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/security.h>
+#include <linux/version.h>
+#include "aufs.h"
+
+/* common function to regular file and dir */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#define FlushArgs	hidden_file, id
+int aufs_flush(struct file *file, fl_owner_t id)
+#else
+#define FlushArgs	hidden_file
+int aufs_flush(struct file *file)
+#endif
+{
+	int err;
+	struct dentry *dentry;
+	aufs_bindex_t bindex, bend;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+
+	// aufs_read_lock_file()
+	si_read_lock(dentry->d_sb, !AuLock_FLUSH);
+	fi_read_lock(file);
+	di_read_lock_child(dentry, !AuLock_IR);
+
+	err = 0;
+	bend = fbend(file);
+	for (bindex = fbstart(file); !err && bindex <= bend; bindex++) {
+		struct file *hidden_file;
+		hidden_file = au_h_fptr_i(file, bindex);
+		if (hidden_file && hidden_file->f_op
+		    && hidden_file->f_op->flush)
+			err = hidden_file->f_op->flush(FlushArgs);
+	}
+
+	di_read_unlock(dentry, !AuLock_IR);
+	fi_read_unlock(file);
+	si_read_unlock(dentry->d_sb);
+	TraceErr(err);
+	return err;
+}
+#undef FlushArgs
+
+/* ---------------------------------------------------------------------- */
+
+static int do_open_nondir(struct file *file, int flags)
+{
+	int err;
+	aufs_bindex_t bindex;
+	struct super_block *sb;
+	struct file *hidden_file;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct aufs_finfo *finfo;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, flags 0%o\n", DLNPair(dentry), flags);
+	FiMustWriteLock(file);
+	inode = dentry->d_inode;
+	AuDebugOn(!inode || S_ISDIR(inode->i_mode));
+
+	err = 0;
+	finfo = ftofi(file);
+	finfo->fi_h_vm_ops = NULL;
+	sb = dentry->d_sb;
+	bindex = dbstart(dentry);
+	AuDebugOn(!au_h_dptr(dentry)->d_inode);
+	/* O_TRUNC is processed already */
+	BUG_ON(test_ro(sb, bindex, inode) && (flags & O_TRUNC));
+
+	hidden_file = hidden_open(dentry, bindex, flags);
+	//if (LktrCond) {fput(hidden_file); br_put(stobr(dentry->d_sb, bindex));
+	//hidden_file = ERR_PTR(-1);}
+	if (!IS_ERR(hidden_file)) {
+		set_fbstart(file, bindex);
+		set_fbend(file, bindex);
+		set_h_fptr(file, bindex, hidden_file);
+		return 0; /* success */
+	}
+	err = PTR_ERR(hidden_file);
+	TraceErr(err);
+	return err;
+}
+
+static int aufs_open_nondir(struct inode *inode, struct file *file)
+{
+	return au_do_open(inode, file, do_open_nondir);
+}
+
+static int aufs_release_nondir(struct inode *inode, struct file *file)
+{
+	struct super_block *sb = file->f_dentry->d_sb;
+
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, DLNPair(file->f_dentry));
+
+	si_read_lock(sb, !AuLock_FLUSH);
+	au_fin_finfo(file);
+	si_read_unlock(sb);
+	return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static ssize_t aufs_read(struct file *file, char __user *buf, size_t count,
+			 loff_t *ppos)
+{
+	ssize_t err;
+	struct dentry *dentry;
+	struct file *hidden_file;
+	struct super_block *sb;
+	struct inode *h_inode;
+	int dlgt;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, cnt %lu, pos %Ld\n",
+		  DLNPair(dentry), (unsigned long)count, *ppos);
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/0,
+				      /*locked*/0);
+	//if (LktrCond) {fi_read_unlock(file); err = -1;}
+	if (unlikely(err))
+		goto out;
+
+	/* support LSM and notify */
+	dlgt = need_dlgt(sb);
+	hidden_file = au_h_fptr(file);
+	h_inode = hidden_file->f_dentry->d_inode;
+	if (!au_flag_test_udba_inotify(sb))
+		err = vfsub_read_u(hidden_file, buf, count, ppos, dlgt);
+	else {
+		struct dentry *parent = dget_parent(dentry),
+			*h_parent = dget_parent(hidden_file->f_dentry);
+		struct inode *dir = parent->d_inode,
+			*h_dir = h_parent->d_inode;
+		aufs_bindex_t bstart = fbstart(file);
+		hdir_lock(h_dir, dir, bstart);
+		err = vfsub_read_u(hidden_file, buf, count, ppos, dlgt);
+		hdir_unlock(h_dir, dir, bstart);
+		dput(parent);
+		dput(h_parent);
+	}
+	memcpy(&file->f_ra, &hidden_file->f_ra, sizeof(file->f_ra)); //??
+	dentry->d_inode->i_atime = hidden_file->f_dentry->d_inode->i_atime;
+
+	fi_read_unlock(file);
+ out:
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+static ssize_t aufs_write(struct file *file, const char __user *__buf,
+			  size_t count, loff_t *ppos)
+{
+	ssize_t err;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct super_block *sb;
+	struct file *hidden_file;
+	char __user *buf = (char __user*)__buf;
+	struct inode *h_inode;
+	int dlgt;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, cnt %lu, pos %Ld\n",
+		  DLNPair(dentry), (unsigned long)count, *ppos);
+
+	inode = dentry->d_inode;
+	vfsub_i_lock(inode);
+	sb = dentry->d_sb;
+	si_read_lock(sb, AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/1,
+				      /*locked*/1);
+	//if (LktrCond) {fi_write_unlock(file); err = -1;}
+	if (unlikely(err))
+		goto out;
+	err = au_ready_to_write(file, -1);
+	//if (LktrCond) err = -1;
+	if (unlikely(err))
+		goto out_unlock;
+
+	/* support LSM and notify */
+	dlgt = need_dlgt(sb);
+	hidden_file = au_h_fptr(file);
+	h_inode = hidden_file->f_dentry->d_inode;
+	if (!au_flag_test_udba_inotify(sb))
+		err = vfsub_write_u(hidden_file, buf, count, ppos, dlgt);
+	else {
+		struct dentry *parent = dget_parent(dentry),
+			*h_parent = dget_parent(hidden_file->f_dentry);
+		struct inode *dir = parent->d_inode,
+			*h_dir = h_parent->d_inode;
+		aufs_bindex_t bstart = fbstart(file);
+		hdir_lock(h_dir, dir, bstart);
+		err = vfsub_write_u(hidden_file, buf, count, ppos, dlgt);
+		hdir_unlock(h_dir, dir, bstart);
+		dput(parent);
+		dput(h_parent);
+	}
+	ii_write_lock_child(inode);
+	au_cpup_attr_timesizes(inode);
+	ii_write_unlock(inode);
+
+ out_unlock:
+	fi_write_unlock(file);
+ out:
+	si_read_unlock(sb);
+	vfsub_i_unlock(inode);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_ROBR
+struct lvma {
+	struct list_head list;
+	struct vm_area_struct *vma;
+};
+
+static struct file *safe_file(struct vm_area_struct *vma)
+{
+	struct file *file = vma->vm_file;
+	struct super_block *sb = file->f_dentry->d_sb;
+	struct lvma *lvma, *entry;
+	struct aufs_sbinfo *sbinfo;
+	int found, warn;
+
+	TraceEnter();
+	AuDebugOn(!au_is_aufs(sb));
+
+	warn = 0;
+	found = 0;
+	sbinfo = stosi(sb);
+	spin_lock(&sbinfo->si_lvma_lock);
+	list_for_each_entry(entry, &sbinfo->si_lvma, list) {
+		found = (entry->vma == vma);
+		if (unlikely(found))
+			break;
+	}
+	if (!found) {
+		lvma = kmalloc(sizeof(*lvma), GFP_ATOMIC);
+		if (lvma) {
+			lvma->vma = vma;
+			list_add(&lvma->list, &sbinfo->si_lvma);
+		} else {
+			warn = 1;
+			file = NULL;
+		}
+	} else
+		file = NULL;
+	spin_unlock(&sbinfo->si_lvma_lock);
+
+	if (unlikely(warn))
+		Warn1("no memory for lvma\n");
+	return file;
+}
+
+static void reset_file(struct vm_area_struct *vma, struct file *file)
+{
+	struct super_block *sb = file->f_dentry->d_sb;
+	struct lvma *entry, *found;
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+	AuDebugOn(!au_is_aufs(sb));
+
+	vma->vm_file = file;
+
+	found = NULL;
+	sbinfo = stosi(sb);
+	spin_lock(&sbinfo->si_lvma_lock);
+	list_for_each_entry(entry, &sbinfo->si_lvma, list)
+		if (entry->vma == vma) {
+			found = entry;
+			break;
+		}
+	AuDebugOn(!found);
+	list_del(&found->list);
+	spin_unlock(&sbinfo->si_lvma_lock);
+	kfree(found);
+}
+
+#else
+
+static struct file *safe_file(struct vm_area_struct *vma)
+{
+	struct file *file;
+
+	file = vma->vm_file;
+	if (file->private_data && au_is_aufs(file->f_dentry->d_sb))
+		return file;
+	return NULL;
+}
+
+static void reset_file(struct vm_area_struct *vma, struct file *file)
+{
+	vma->vm_file = file;
+	smp_mb(); /* flush vm_file */
+}
+#endif /* CONFIG_AUFS_ROBR */
+
+static struct page *aufs_nopage(struct vm_area_struct *vma, unsigned long addr,
+				int *type)
+{
+	struct page *page;
+	struct dentry *dentry;
+	struct file *file, *hidden_file;
+	struct inode *inode;
+	static DECLARE_WAIT_QUEUE_HEAD(wq);
+	struct aufs_finfo *finfo;
+
+	TraceEnter();
+	AuDebugOn(!vma || !vma->vm_file);
+	wait_event(wq, (file = safe_file(vma)));
+	AuDebugOn(!au_is_aufs(file->f_dentry->d_sb));
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, addr %lx\n", DLNPair(dentry), addr);
+	inode = dentry->d_inode;
+	AuDebugOn(!S_ISREG(inode->i_mode));
+
+	/* do not revalidate, nor lock */
+	finfo = ftofi(file);
+	hidden_file = finfo->fi_hfile[0 + finfo->fi_bstart].hf_file;
+	AuDebugOn(!hidden_file || !au_is_mmapped(file));
+	vma->vm_file = hidden_file;
+	//smp_mb();
+	page = finfo->fi_h_vm_ops->nopage(vma, addr, type);
+	reset_file(vma, file);
+#if 0 //def CONFIG_SMP
+	//wake_up_nr(&wq, online_cpu - 1);
+	wake_up_all(&wq);
+#else
+	wake_up(&wq);
+#endif
+	if (!IS_ERR(page)) {
+		//page->mapping = file->f_mapping;
+		//get_page(page);
+		//file->f_mapping = hidden_file->f_mapping;
+		//touch_atime(NULL, dentry);
+		//inode->i_atime = hidden_file->f_dentry->d_inode->i_atime;
+	}
+	TraceErrPtr(page);
+	return page;
+}
+
+static int aufs_populate(struct vm_area_struct *vma, unsigned long addr,
+			 unsigned long len, pgprot_t prot, unsigned long pgoff,
+			 int nonblock)
+{
+	Err("please report me this application\n");
+	BUG();
+	return ftofi(vma->vm_file)->fi_h_vm_ops->populate
+		(vma, addr, len, prot, pgoff, nonblock);
+}
+
+static struct vm_operations_struct aufs_vm_ops = {
+	//.open		= aufs_vmaopen,
+	//.close		= aufs_vmaclose,
+	.nopage		= aufs_nopage,
+	.populate	= aufs_populate,
+	//page_mkwrite(struct vm_area_struct *vma, struct page *page)
+};
+
+/* ---------------------------------------------------------------------- */
+
+static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	int err, wlock, mmapped;
+	struct dentry *dentry;
+	struct super_block *sb;
+	struct file *h_file;
+	struct vm_operations_struct *vm_ops;
+	unsigned long flags;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, %lx, len %lu\n",
+		  DLNPair(dentry), vma->vm_start, vma->vm_end - vma->vm_start);
+	AuDebugOn(!S_ISREG(dentry->d_inode->i_mode));
+	AuDebugOn(down_write_trylock(&vma->vm_mm->mmap_sem));
+
+	mmapped = au_is_mmapped(file);
+	wlock = 0;
+	if (file->f_mode & FMODE_WRITE) {
+		flags = VM_SHARED | VM_WRITE;
+		wlock = ((flags & vma->vm_flags) == flags);
+	}
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir,
+				      wlock | !mmapped, /*locked*/0);
+	//err = -1;
+	if (unlikely(err))
+		goto out;
+
+	if (wlock) {
+		err = au_ready_to_write(file, -1);
+		//err = -1;
+		if (unlikely(err))
+			goto out_unlock;
+	}
+
+	h_file = au_h_fptr(file);
+	vm_ops = ftofi(file)->fi_h_vm_ops;
+	if (unlikely(!mmapped)) {
+		/* nfs uses some locks */
+		lockdep_off();
+		err = h_file->f_op->mmap(h_file, vma);
+		lockdep_on();
+		if (unlikely(err))
+			goto out_unlock;
+		vm_ops = vma->vm_ops;
+		AuDebugOn(!vm_ops);
+		err = do_munmap(current->mm, vma->vm_start,
+				vma->vm_end - vma->vm_start);
+		if (unlikely(err)) {
+			IOErr("failed internal unmapping %.*s, %d\n",
+			      DLNPair(h_file->f_dentry), err);
+			err = -EIO;
+			goto out_unlock;
+		}
+	}
+	AuDebugOn(!vm_ops);
+
+	err = generic_file_mmap(file, vma);
+	if (!err) {
+		file_accessed(h_file);
+		dentry->d_inode->i_atime = h_file->f_dentry->d_inode->i_atime;
+		vma->vm_ops = &aufs_vm_ops;
+		if (unlikely(!mmapped))
+			ftofi(file)->fi_h_vm_ops = vm_ops;
+	}
+
+ out_unlock:
+	if (!wlock && mmapped)
+		fi_read_unlock(file);
+	else
+		fi_write_unlock(file);
+ out:
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+// todo: try do_sendfile() in fs/read_write.c
+static ssize_t aufs_sendfile(struct file *file, loff_t *ppos,
+			     size_t count, read_actor_t actor, void *target)
+{
+	ssize_t err;
+	struct file *h_file;
+	const char c = current->comm[4];
+	/* true if a kernel thread named 'loop[0-9].*' accesses a file */
+	const int loopback = (current->mm == NULL
+			      && '0' <= c && c <= '9'
+			      && strncmp(current->comm, "loop", 4) == 0);
+	struct dentry *dentry;
+	struct super_block *sb;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, pos %Ld, cnt %lu, loopback %d\n",
+		  DLNPair(dentry), *ppos, (unsigned long)count, loopback);
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/0,
+				      /*locked*/0);
+	if (unlikely(err))
+		goto out;
+
+	err = -EINVAL;
+	h_file = au_h_fptr(file);
+	if (h_file->f_op && h_file->f_op->sendfile) {
+		if (/* unlikely */(loopback)) {
+			file->f_mapping = h_file->f_mapping;
+			smp_mb(); /* unnecessary? */
+		}
+		/* nfs uses some locks */
+		lockdep_off();
+		err = h_file->f_op->sendfile
+			(h_file, ppos, count, actor, target);
+		lockdep_on();
+		dentry->d_inode->i_atime = h_file->f_dentry->d_inode->i_atime;
+	}
+	fi_read_unlock(file);
+
+ out:
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* copied from linux/fs/select.h, must match */
+#define DEFAULT_POLLMASK (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM)
+
+static unsigned int aufs_poll(struct file *file, poll_table *wait)
+{
+	unsigned int mask;
+	struct file *hidden_file;
+	int err;
+	struct dentry *dentry;
+	struct super_block *sb;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, wait %p\n", DLNPair(dentry), wait);
+	AuDebugOn(S_ISDIR(dentry->d_inode->i_mode));
+
+	/* We should pretend an error happend. */
+	mask = POLLERR /* | POLLIN | POLLOUT */;
+	sb = dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/0,
+				      /*locked*/0);
+	//err = -1;
+	if (unlikely(err))
+		goto out;
+
+	/* it is not an error of hidden_file has no operation */
+	mask = DEFAULT_POLLMASK;
+	hidden_file = au_h_fptr(file);
+	if (hidden_file->f_op && hidden_file->f_op->poll)
+		mask = hidden_file->f_op->poll(hidden_file, wait);
+	fi_read_unlock(file);
+
+ out:
+	si_read_unlock(sb);
+	TraceErr((int)mask);
+	return mask;
+}
+
+static int aufs_fsync_nondir(struct file *file, struct dentry *dentry,
+			     int datasync)
+{
+	int err, my_lock;
+	struct inode *inode;
+	struct file *hidden_file;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, %d\n", DLNPair(dentry), datasync);
+	inode = dentry->d_inode;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17)
+	IMustLock(inode);
+	my_lock = 0;
+#else
+	/* before 2.6.17,
+	 * msync(2) calls me without locking i_sem/i_mutex, but fsync(2).
+	 */
+	my_lock = !vfsub_i_trylock(inode);
+#endif
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	err = 0; //-EBADF; // posix?
+	if (unlikely(!(file->f_mode & FMODE_WRITE)))
+		goto out;
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/1,
+				      /*locked*/1);
+	//err = -1;
+	if (unlikely(err))
+		goto out;
+	err = au_ready_to_write(file, -1);
+	//err = -1;
+	if (unlikely(err))
+		goto out_unlock;
+
+	err = -EINVAL;
+	hidden_file = au_h_fptr(file);
+	if (hidden_file->f_op && hidden_file->f_op->fsync) {
+		// todo: apparmor thread?
+		//file->f_mapping->host->i_mutex
+		ii_write_lock_child(inode);
+		vfsub_i_lock_nested(hidden_file->f_dentry->d_inode,
+				    AuLsc_I_CHILD);
+		err = hidden_file->f_op->fsync
+			(hidden_file, hidden_file->f_dentry, datasync);
+		//err = -1;
+		au_cpup_attr_timesizes(inode);
+		vfsub_i_unlock(hidden_file->f_dentry->d_inode);
+		ii_write_unlock(inode);
+	}
+
+ out_unlock:
+	fi_write_unlock(file);
+ out:
+	if (unlikely(my_lock))
+		vfsub_i_unlock(inode);
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+static int aufs_fasync(int fd, struct file *file, int flag)
+{
+	int err;
+	struct file *hidden_file;
+	struct dentry *dentry;
+	struct super_block *sb;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, %d\n", DLNPair(dentry), flag);
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/0,
+				      /*locked*/0);
+	//err = -1;
+	if (unlikely(err))
+		goto out;
+
+	hidden_file = au_h_fptr(file);
+	if (hidden_file->f_op && hidden_file->f_op->fasync)
+		err = hidden_file->f_op->fasync(fd, hidden_file, flag);
+	fi_read_unlock(file);
+
+ out:
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct file_operations aufs_file_fop = {
+	.read		= aufs_read,
+	.write		= aufs_write,
+	.poll		= aufs_poll,
+	.mmap		= aufs_mmap,
+	.open		= aufs_open_nondir,
+	.flush		= aufs_flush,
+	.release	= aufs_release_nondir,
+	.fsync		= aufs_fsync_nondir,
+	.fasync		= aufs_fasync,
+	.sendfile	= aufs_sendfile,
+};
diff -ruN linux-2.6.22/fs/aufs/file.c linux-2.6.22-aufs/fs/aufs/file.c
--- linux-2.6.22/fs/aufs/file.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/file.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: file.c,v 1.50 2007/07/15 20:04:08 sfjro Exp $ */
+
+//#include <linux/fsnotify.h>
+#include <linux/pagemap.h>
+//#include <linux/poll.h>
+//#include <linux/security.h>
+#include "aufs.h"
+
+/* drop flags for writing */
+unsigned int au_file_roflags(unsigned int flags)
+{
+	flags &= ~(O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC);
+	flags |= O_RDONLY | O_NOATIME;
+	return flags;
+}
+
+/* common functions to regular file and dir */
+struct file *hidden_open(struct dentry *dentry, aufs_bindex_t bindex, int flags)
+{
+	struct dentry *hidden_dentry;
+	struct inode *hidden_inode;
+	struct super_block *sb;
+	struct vfsmount *hidden_mnt;
+	struct file *hidden_file;
+	struct aufs_branch *br;
+	loff_t old_size;
+	int udba;
+
+	LKTRTrace("%.*s, b%d, flags 0%o\n", DLNPair(dentry), bindex, flags);
+	AuDebugOn(!dentry);
+	hidden_dentry = au_h_dptr_i(dentry, bindex);
+	AuDebugOn(!hidden_dentry);
+	hidden_inode = hidden_dentry->d_inode;
+	AuDebugOn(!hidden_inode);
+
+	sb = dentry->d_sb;
+	udba = au_flag_test_udba_inotify(sb);
+	if (unlikely(udba)) {
+		// test here?
+	}
+
+	br = stobr(sb, bindex);
+	br_get(br);
+	/* drop flags for writing */
+	if (test_ro(sb, bindex, dentry->d_inode))
+		flags = au_file_roflags(flags);
+	flags &= ~O_CREAT;
+	spin_lock(&hidden_inode->i_lock);
+	old_size = i_size_read(hidden_inode);
+	spin_unlock(&hidden_inode->i_lock);
+
+	//DbgSleep(3);
+
+	dget(hidden_dentry);
+	hidden_mnt = mntget(br->br_mnt);
+	hidden_file = dentry_open(hidden_dentry, hidden_mnt, flags);
+	//if (LktrCond) {fput(hidden_file); hidden_file = ERR_PTR(-1);}
+
+	if (!IS_ERR(hidden_file)) {
+#if 0 // remove this
+		if (/* old_size && */ (flags & O_TRUNC)) {
+			au_direval_dec(dentry);
+			if (!IS_ROOT(dentry))
+				au_direval_dec(dentry->d_parent);
+		}
+#endif
+		return hidden_file;
+	}
+
+	br_put(br);
+	TraceErrPtr(hidden_file);
+	return hidden_file;
+}
+
+static int do_coo(struct dentry *dentry, aufs_bindex_t bstart)
+{
+	int err;
+	struct dentry *parent, *h_parent, *h_dentry;
+	aufs_bindex_t bcpup;
+	struct inode *h_dir, *h_inode, *dir;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	AuDebugOn(IS_ROOT(dentry));
+	DiMustWriteLock(dentry);
+
+	parent = dget_parent(dentry);
+	di_write_lock_parent(parent);
+	bcpup = err = find_rw_parent_br(dentry, bstart);
+	//bcpup = err = find_rw_br(sb, bstart);
+	if (unlikely(err < 0)) {
+		err = 0; /* stop copyup, it is not an error */
+		goto out;
+	}
+	err = 0;
+
+	h_parent = au_h_dptr_i(parent, bcpup);
+	if (!h_parent) {
+		err = cpup_dirs(dentry, bcpup, NULL);
+		if (unlikely(err))
+			goto out;
+		h_parent = au_h_dptr_i(parent, bcpup);
+	}
+
+	h_dir = h_parent->d_inode;
+	h_dentry = au_h_dptr_i(dentry, bstart);
+	h_inode = h_dentry->d_inode;
+	dir = parent->d_inode;
+	hdir_lock(h_dir, dir, bcpup);
+	vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD);
+	AuDebugOn(au_h_dptr_i(dentry, bcpup));
+	err = sio_cpup_simple(dentry, bcpup, -1,
+			      au_flags_cpup(CPUP_DTIME, parent));
+	TraceErr(err);
+	vfsub_i_unlock(h_inode);
+	hdir_unlock(h_dir, dir, bcpup);
+
+ out:
+	di_write_unlock(parent);
+	dput(parent);
+	TraceErr(err);
+	return err;
+}
+
+int au_do_open(struct inode *inode, struct file *file,
+	       int (*open)(struct file *file, int flags))
+{
+	int err, coo;
+	struct dentry *dentry;
+	struct super_block *sb;
+	aufs_bindex_t bstart;
+	//struct inode *h_dir, *dir;
+
+	dentry = file->f_dentry;
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, DLNPair(dentry));
+
+	sb = dentry->d_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+	coo = 0;
+	switch (au_flag_test_coo(sb)) {
+	case AuFlag_COO_LEAF:
+		coo = !S_ISDIR(inode->i_mode);
+		break;
+	case AuFlag_COO_ALL:
+		coo = 1;
+		break;
+	}
+	err = au_init_finfo(file);
+	//if (LktrCond) {fi_write_unlock(file); fin_finfo(file); err = -1;}
+	if (unlikely(err))
+		goto out;
+
+	if (!coo) {
+		di_read_lock_child(dentry, AuLock_IR);
+		bstart = dbstart(dentry);
+	} else {
+		di_write_lock_child(dentry);
+		bstart = dbstart(dentry);
+		if (test_ro(sb, bstart, dentry->d_inode)) {
+			err = do_coo(dentry, bstart);
+			if (err) {
+				di_write_unlock(dentry);
+				goto out_finfo;
+			}
+			bstart = dbstart(dentry);
+		}
+		di_downgrade_lock(dentry, AuLock_IR);
+	}
+
+	// todo: remove this extra locks
+#if 0
+	dir = dentry->d_parent->d_inode;
+	if (!IS_ROOT(dentry))
+		ii_read_lock_parent(dir);
+	h_dir = au_h_iptr_i(dir, bstart);
+	hdir_lock(h_dir, dir, bstart);
+#endif
+	err = open(file, file->f_flags);
+	//if (LktrCond) err = -1;
+#if 0
+	hdir_unlock(h_dir, dir, bstart);
+	if (!IS_ROOT(dentry))
+		ii_read_unlock(dir);
+#endif
+	di_read_unlock(dentry, AuLock_IR);
+
+ out_finfo:
+	fi_write_unlock(file);
+	if (unlikely(err))
+		au_fin_finfo(file);
+	//DbgFile(file);
+ out:
+	si_read_unlock(sb);
+	TraceErr(err);
+	return err;
+}
+
+int au_reopen_nondir(struct file *file)
+{
+	int err;
+	struct dentry *dentry;
+	aufs_bindex_t bstart, bindex, bend;
+	struct file *hidden_file, *h_file_tmp;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	AuDebugOn(S_ISDIR(dentry->d_inode->i_mode)
+		  || !au_h_dptr(dentry)->d_inode);
+
+	h_file_tmp = NULL;
+	bstart = dbstart(dentry);
+	if (fbstart(file) == bstart) {
+		hidden_file = au_h_fptr(file);
+		if (file->f_mode == hidden_file->f_mode)
+			return 0; /* success */
+		h_file_tmp = hidden_file;
+		get_file(h_file_tmp);
+		set_h_fptr(file, bstart, NULL);
+	}
+	AuDebugOn(fbstart(file) < bstart
+		  || ftofi(file)->fi_hfile[0 + bstart].hf_file);
+
+	hidden_file = hidden_open(dentry, bstart, file->f_flags & ~O_TRUNC);
+	//if (LktrCond) {fput(hidden_file); br_put(stobr(dentry->d_sb, bstart));
+	//hidden_file = ERR_PTR(-1);}
+	err = PTR_ERR(hidden_file);
+	if (IS_ERR(hidden_file))
+		goto out; // close all?
+	err = 0;
+	//cpup_file_flags(hidden_file, file);
+	set_fbstart(file, bstart);
+	set_h_fptr(file, bstart, hidden_file);
+	memcpy(&hidden_file->f_ra, &file->f_ra, sizeof(file->f_ra)); //??
+
+	/* close lower files */
+	bend = fbend(file);
+	for (bindex = bstart + 1; bindex <= bend; bindex++)
+		set_h_fptr(file, bindex, NULL);
+	set_fbend(file, bstart);
+
+ out:
+	if (h_file_tmp)
+		fput(h_file_tmp);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * prepare the @file for writing.
+ */
+int au_ready_to_write(struct file *file, loff_t len)
+{
+	int err;
+	struct dentry *dentry, *parent, *h_dentry, *h_parent, *hi_wh,
+		*old_h_dentry;
+	struct inode *h_inode, *h_dir, *inode, *dir;
+	struct super_block *sb;
+	aufs_bindex_t bstart, bcpup, old_bstart;
+	struct aufs_dinfo *dinfo;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, len %Ld\n", DLNPair(dentry), len);
+	FiMustWriteLock(file);
+
+	sb = dentry->d_sb;
+	bstart = fbstart(file);
+	AuDebugOn(ftobr(file, bstart) != stobr(sb, bstart));
+
+	inode = dentry->d_inode;
+	ii_read_lock_child(inode);
+	LKTRTrace("rdonly %d, bstart %d\n", test_ro(sb, bstart, inode), bstart);
+	err = test_ro(sb, bstart, inode);
+	ii_read_unlock(inode);
+	if (!err && (au_h_fptr(file)->f_mode & FMODE_WRITE))
+		return 0;
+
+	/* need to cpup */
+	parent = dget_parent(dentry);
+	di_write_lock_child(dentry);
+	di_write_lock_parent(parent);
+	bcpup = err = find_rw_parent_br(dentry, bstart);
+	//bcpup = err = find_rw_br(sb, bstart);
+	if (unlikely(err < 0))
+		goto out_unlock;
+	err = 0;
+
+	h_parent = au_h_dptr_i(parent, bcpup);
+	if (!h_parent) {
+		err = cpup_dirs(dentry, bcpup, NULL);
+		//if (LktrCond) err = -1;
+		if (unlikely(err))
+			goto out_unlock;
+		h_parent = au_h_dptr_i(parent, bcpup);
+	}
+
+	h_dir = h_parent->d_inode;
+	h_dentry = au_h_fptr(file)->f_dentry;
+	h_inode = h_dentry->d_inode;
+	dir = parent->d_inode;
+	hdir_lock(h_dir, dir, bcpup);
+	vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD);
+	if (d_unhashed(dentry) /* || d_unhashed(h_dentry) */
+	    /* || !h_inode->i_nlink */) {
+		hi_wh = au_hi_wh(inode, bcpup);
+		if (!hi_wh)
+			err = sio_cpup_wh(dentry, bcpup, len, file);
+		else {
+			/* already copied-up after unlink */
+			dinfo = dtodi(dentry);
+			old_bstart = dinfo->di_bstart;
+			dinfo->di_bstart = bcpup;
+			old_h_dentry = dinfo->di_hdentry[0 + bcpup].hd_dentry;
+			dinfo->di_hdentry[0 + bcpup].hd_dentry = hi_wh;
+			err = au_reopen_nondir(file);
+			dinfo->di_hdentry[0 + bcpup].hd_dentry = old_h_dentry;
+			dinfo->di_bstart = old_bstart;
+		}
+		//if (LktrCond) err = -1;
+		TraceErr(err);
+	} else {
+		if (!au_h_dptr_i(dentry, bcpup))
+			err = sio_cpup_simple(dentry, bcpup, len,
+					      au_flags_cpup(CPUP_DTIME,
+							    parent));
+		//if (LktrCond) err = -1;
+		TraceErr(err);
+		if (!err)
+			err = au_reopen_nondir(file);
+		//if (LktrCond) err = -1;
+		TraceErr(err);
+	}
+	vfsub_i_unlock(h_inode);
+	hdir_unlock(h_dir, dir, bcpup);
+
+ out_unlock:
+	di_write_unlock(parent);
+	di_write_unlock(dentry);
+	dput(parent);
+	TraceErr(err);
+	return err;
+
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int refresh_file_by_inode(struct file *file, int *need_reopen)
+{
+	int err;
+	struct aufs_finfo *finfo;
+	struct dentry *dentry, *parent, *old_h_dentry, *hi_wh;
+	struct inode *inode, *dir, *h_dir;
+	aufs_bindex_t bstart, new_bstart, old_bstart;
+	struct super_block *sb;
+	struct aufs_dinfo *dinfo;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	FiMustWriteLock(file);
+
+	err = 0;
+	finfo = ftofi(file);
+	inode = dentry->d_inode;
+	sb = dentry->d_sb;
+ again:
+	bstart = ibstart(inode);
+	if (bstart == finfo->fi_bstart)
+		goto out;
+
+	new_bstart = bstart;
+	//parent = dget_parent(dentry);
+	parent = dentry->d_parent;
+	dir = parent->d_inode;
+	if (test_ro(sb, bstart, inode)) {
+		di_read_lock_parent(parent, !AuLock_IR);
+		new_bstart = err = find_rw_parent_br(dentry, bstart);
+		//bstart = err = find_rw_br(sb, bstart);
+		di_read_unlock(parent, !AuLock_IR);
+		//todo: err = -1;
+		if (unlikely(err < 0))
+			goto out;
+	}
+	di_read_unlock(dentry, AuLock_IR);
+	di_write_lock_child(dentry);
+	if (bstart != ibstart(inode)) { // todo
+		/* someone changed our inode while we were sleeping */
+		di_downgrade_lock(dentry, AuLock_IR);
+		err = 0;
+		goto again;
+	}
+	di_read_lock_parent(parent, AuLock_IR);
+	bstart = new_bstart;
+
+	hi_wh = au_hi_wh(inode, bstart);
+	if (au_flag_test(sb, AuFlag_PLINK)
+	    && au_is_plinked(sb, inode)
+	    && !d_unhashed(dentry)) {
+		err = test_and_cpup_dirs(dentry, bstart, NULL);
+
+		/* always superio. */
+#if 1
+		h_dir = au_h_dptr_i(parent, bstart)->d_inode;
+		hdir_lock(h_dir, dir, bstart);
+		err = sio_cpup_simple(dentry, bstart, -1,
+				      au_flags_cpup(CPUP_DTIME, parent));
+		hdir_unlock(h_dir, dir, bstart);
+#else
+		if (!is_au_wkq(current)) {
+			int wkq_err;
+			struct cpup_pseudo_link_args args = {
+				.errp		= &err,
+				.dentry		= dentry,
+				.bdst		= bstart,
+				.do_lock	= 1
+			};
+			wkq_err = au_wkq_wait(call_cpup_pseudo_link, &args);
+			if (unlikely(wkq_err))
+				err = wkq_err;
+		} else
+			err = cpup_pseudo_link(dentry, bstart, /*do_lock*/1);
+#endif
+	} else if (hi_wh) {
+		/* already copied-up after unlink */
+		dinfo = dtodi(dentry);
+		old_bstart = dinfo->di_bstart;
+		dinfo->di_bstart = bstart;
+		old_h_dentry = dinfo->di_hdentry[0 + bstart].hd_dentry;
+		dinfo->di_hdentry[0 + bstart].hd_dentry = hi_wh;
+		err = au_reopen_nondir(file);
+		dinfo->di_hdentry[0 + bstart].hd_dentry = old_h_dentry;
+		dinfo->di_bstart = old_bstart;
+		*need_reopen = 0;
+	}
+	di_read_unlock(parent, AuLock_IR);
+	di_downgrade_lock(dentry, AuLock_IR);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * after branch manipulating, refresh the file.
+ */
+static int refresh_file(struct file *file, int (*reopen)(struct file *file))
+{
+	int err, new_sz, need_reopen;
+	struct dentry *dentry;
+	aufs_bindex_t bend, bindex, bstart, brid;
+	struct aufs_hfile *p;
+	struct aufs_finfo *finfo;
+	struct super_block *sb;
+	struct inode *inode;
+	struct file *hidden_file;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	FiMustWriteLock(file);
+	DiMustReadLock(dentry);
+	inode = dentry->d_inode;
+	IiMustReadLock(inode);
+
+	err = -ENOMEM;
+	sb = dentry->d_sb;
+	finfo = ftofi(file);
+	bstart = finfo->fi_bstart;
+	bend = finfo->fi_bstart;
+	new_sz = sizeof(*finfo->fi_hfile) * (sbend(sb) + 1);
+	p = au_kzrealloc(finfo->fi_hfile, sizeof(*p) * (finfo->fi_bend + 1),
+			 new_sz, GFP_KERNEL);
+	//p = NULL;
+	if (unlikely(!p))
+		goto out;
+	finfo->fi_hfile = p;
+	hidden_file = p[0 + bstart].hf_file;
+
+	p = finfo->fi_hfile + finfo->fi_bstart;
+	brid = p->hf_br->br_id;
+	bend = finfo->fi_bend;
+	for (bindex = finfo->fi_bstart; bindex <= bend; bindex++, p++) {
+		struct aufs_hfile tmp, *q;
+		aufs_bindex_t new_bindex;
+
+		if (!p->hf_file)
+			continue;
+		new_bindex = find_bindex(sb, p->hf_br);
+		if (new_bindex == bindex)
+			continue;
+		if (new_bindex < 0) { // test here
+			set_h_fptr(file, bindex, NULL);
+			continue;
+		}
+
+		/* swap two hidden inode, and loop again */
+		q = finfo->fi_hfile + new_bindex;
+		tmp = *q;
+		*q = *p;
+		*p = tmp;
+		if (tmp.hf_file) {
+			bindex--;
+			p--;
+		}
+	}
+	{
+		aufs_bindex_t s = finfo->fi_bstart, e = finfo->fi_bend;
+		finfo->fi_bstart = 0;
+		finfo->fi_bend = sbend(sb);
+		finfo->fi_bstart = s;
+		finfo->fi_bend = e;
+	}
+
+	p = finfo->fi_hfile;
+	if (!au_is_mmapped(file) && !d_unhashed(dentry)) {
+		bend = sbend(sb);
+		for (finfo->fi_bstart = 0; finfo->fi_bstart <= bend;
+		     finfo->fi_bstart++, p++)
+			if (p->hf_file) {
+				if (p->hf_file->f_dentry
+				    && p->hf_file->f_dentry->d_inode)
+					break;
+				else
+					au_hfput(p);
+			}
+	} else {
+		bend = find_brindex(sb, brid);
+		//LKTRTrace("%d\n", bend);
+		for (finfo->fi_bstart = 0; finfo->fi_bstart < bend;
+		     finfo->fi_bstart++, p++)
+			if (p->hf_file)
+				au_hfput(p);
+		//LKTRTrace("%d\n", finfo->fi_bstart);
+		bend = sbend(sb);
+	}
+
+	p = finfo->fi_hfile + bend;
+	for (finfo->fi_bend = bend; finfo->fi_bend >= finfo->fi_bstart;
+	     finfo->fi_bend--, p--)
+		if (p->hf_file) {
+			if (p->hf_file->f_dentry
+			    && p->hf_file->f_dentry->d_inode)
+				break;
+			else
+				au_hfput(p);
+		}
+	AuDebugOn(finfo->fi_bend < finfo->fi_bstart);
+	//DbgFile(file);
+	//DbgDentry(file->f_dentry);
+
+	err = 0;
+	need_reopen = 1;
+	if (!au_is_mmapped(file))
+		err = refresh_file_by_inode(file, &need_reopen);
+	if (!err && need_reopen && !d_unhashed(dentry))
+		err = reopen(file);
+		//err = -1;
+	if (!err) {
+		au_update_figen(file);
+		//DbgFile(file);
+		return 0; /* success */
+	}
+
+	/* error, close all hidden files */
+	bend = fbend(file);
+	for (bindex = fbstart(file); bindex <= bend; bindex++)
+		set_h_fptr(file, bindex, NULL);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* common function to regular file and dir */
+int au_reval_and_lock_finfo(struct file *file, int (*reopen)(struct file *file),
+			    int wlock, int locked)
+{
+	int err, pseudo_link;
+	struct dentry *dentry;
+	struct super_block *sb;
+	aufs_bindex_t bstart;
+	au_gen_t sgen, fgen;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, w %d, l %d\n", DLNPair(dentry), wlock, locked);
+	sb = dentry->d_sb;
+	SiMustAnyLock(sb);
+
+	err = 0;
+	sgen = au_sigen(sb);
+	fi_write_lock(file);
+	fgen = au_figen(file);
+	di_read_lock_child(dentry, AuLock_IR);
+	bstart = dbstart(dentry);
+	pseudo_link = (bstart != ibstart(dentry->d_inode));
+	di_read_unlock(dentry, AuLock_IR);
+	if (sgen == fgen && !pseudo_link && fbstart(file) == bstart) {
+		if (!wlock)
+			fi_downgrade_lock(file);
+		return 0; /* success */
+	}
+
+	LKTRTrace("sgen %d, fgen %d\n", sgen, fgen);
+	if (sgen != au_digen(dentry)) {
+		/*
+		 * d_path() and path_lookup() is a simple and good approach
+		 * to revalidate. but si_rwsem in DEBUG_RWSEM will cause a
+		 * deadlock. removed the code.
+		 */
+		di_write_lock_child(dentry);
+		err = au_reval_dpath(dentry, sgen);
+		//if (LktrCond) err = -1;
+		di_write_unlock(dentry);
+		if (unlikely(err < 0))
+			goto out;
+		AuDebugOn(au_digen(dentry) != sgen);
+	}
+
+	di_read_lock_child(dentry, AuLock_IR);
+	err = refresh_file(file, reopen/* , au_flag_test(sb, AuFlag_REFROF) */);
+	//if (LktrCond) err = -1;
+	di_read_unlock(dentry, AuLock_IR);
+	if (!err) {
+		if (!wlock)
+			fi_downgrade_lock(file);
+	} else
+		fi_write_unlock(file);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* cf. aufs_nopage() */
+/* for madvise(2) */
+static int aufs_readpage(struct file *file, struct page *page)
+{
+	TraceEnter();
+	unlock_page(page);
+	return 0;
+}
+
+/* they will never be called. */
+#ifdef CONFIG_AUFS_DEBUG
+static int aufs_prepare_write(struct file *file, struct page *page,
+			      unsigned from, unsigned to)
+{BUG();return 0;}
+static int aufs_commit_write(struct file *file, struct page *page,
+			     unsigned from, unsigned to)
+{BUG();return 0;}
+static int aufs_writepage(struct page *page, struct writeback_control *wbc)
+{BUG();return 0;}
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17)
+static void aufs_sync_page(struct page *page)
+{BUG();}
+#else
+static int aufs_sync_page(struct page *page)
+{BUG(); return 0;}
+#endif
+
+#if 0 // comment
+static int aufs_writepages(struct address_space *mapping,
+			   struct writeback_control *wbc)
+{BUG();return 0;}
+static int aufs_readpages(struct file *filp, struct address_space *mapping,
+			  struct list_head *pages, unsigned nr_pages)
+{BUG();return 0;}
+static sector_t aufs_bmap(struct address_space *mapping, sector_t block)
+{BUG();return 0;}
+#endif
+
+static int aufs_set_page_dirty(struct page *page)
+{BUG();return 0;}
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 17)
+static void aufs_invalidatepage (struct page *page, unsigned long offset)
+{BUG();}
+#else
+static int aufs_invalidatepage (struct page *page, unsigned long offset)
+{BUG(); return 0;}
+#endif
+static int aufs_releasepage (struct page *page, gfp_t gfp)
+{BUG();return 0;}
+static ssize_t aufs_direct_IO(int rw, struct kiocb *iocb,
+			      const struct iovec *iov, loff_t offset,
+			      unsigned long nr_segs)
+{BUG();return 0;}
+static struct page *aufs_get_xip_page(struct address_space *mapping,
+				      sector_t offset, int create)
+{BUG();return NULL;}
+//static int aufs_migratepage (struct page *newpage, struct page *page)
+//{BUG();return 0;}
+#endif
+
+struct address_space_operations aufs_aop = {
+	.readpage	= aufs_readpage,
+#ifdef CONFIG_AUFS_DEBUG
+	.writepage	= aufs_writepage,
+	.sync_page	= aufs_sync_page,
+	//.writepages	= aufs_writepages,
+	.set_page_dirty	= aufs_set_page_dirty,
+	//.readpages	= aufs_readpages,
+	.prepare_write	= aufs_prepare_write,
+	.commit_write	= aufs_commit_write,
+	//.bmap		= aufs_bmap,
+	.invalidatepage	= aufs_invalidatepage,
+	.releasepage	= aufs_releasepage,
+	.direct_IO	= aufs_direct_IO,
+	.get_xip_page	= aufs_get_xip_page,
+	//.migratepage	= aufs_migratepage
+#endif
+};
diff -ruN linux-2.6.22/fs/aufs/file.h linux-2.6.22-aufs/fs/aufs/file.h
--- linux-2.6.22/fs/aufs/file.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/file.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: file.h,v 1.29 2007/07/15 20:03:28 sfjro Exp $ */
+
+#ifndef __AUFS_FILE_H__
+#define __AUFS_FILE_H__
+
+#ifdef __KERNEL__
+
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+#include "misc.h"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+/* SEEK_xxx are defined in linux/fs.h */
+#else
+enum {SEEK_SET, SEEK_CUR, SEEK_END};
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+struct aufs_branch;
+struct aufs_hfile {
+	struct file		*hf_file;
+	struct aufs_branch	*hf_br;
+};
+
+struct aufs_vdir;
+struct aufs_finfo {
+	atomic_t		fi_generation;
+
+	struct aufs_rwsem	fi_rwsem;
+	struct aufs_hfile	*fi_hfile;
+	aufs_bindex_t		fi_bstart, fi_bend;
+
+	union {
+		struct vm_operations_struct	*fi_h_vm_ops;
+		struct aufs_vdir		*fi_vdir_cache;
+	};
+};
+
+/* ---------------------------------------------------------------------- */
+
+/* file.c */
+extern struct address_space_operations aufs_aop;
+unsigned int au_file_roflags(unsigned int flags);
+struct file *hidden_open(struct dentry *dentry, aufs_bindex_t bindex,
+			 int flags);
+int au_do_open(struct inode *inode, struct file *file,
+	       int (*open)(struct file *file, int flags));
+int au_reopen_nondir(struct file *file);
+int au_ready_to_write(struct file *file, loff_t len);
+int au_reval_and_lock_finfo(struct file *file, int (*reopen)(struct file *file),
+			    int wlock, int locked);
+
+/* f_op.c */
+extern struct file_operations aufs_file_fop;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+int aufs_flush(struct file *file, fl_owner_t id);
+#else
+int aufs_flush(struct file *file);
+#endif
+
+/* finfo.c */
+struct aufs_finfo *ftofi(struct file *file);
+aufs_bindex_t fbstart(struct file *file);
+aufs_bindex_t fbend(struct file *file);
+struct aufs_vdir *fvdir_cache(struct file *file);
+struct aufs_branch *ftobr(struct file *file, aufs_bindex_t bindex);
+struct file *au_h_fptr_i(struct file *file, aufs_bindex_t bindex);
+struct file *au_h_fptr(struct file *file);
+
+void set_fbstart(struct file *file, aufs_bindex_t bindex);
+void set_fbend(struct file *file, aufs_bindex_t bindex);
+void set_fvdir_cache(struct file *file, struct aufs_vdir *vdir_cache);
+void au_hfput(struct aufs_hfile *hf);
+void set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *h_file);
+void au_update_figen(struct file *file);
+
+void au_fin_finfo(struct file *file);
+int au_init_finfo(struct file *file);
+
+/* ---------------------------------------------------------------------- */
+
+static inline au_gen_t au_figen(struct file *f)
+{
+	return atomic_read(&ftofi(f)->fi_generation);
+}
+
+static inline int au_is_mmapped(struct file *f)
+{
+	return !!(ftofi(f)->fi_h_vm_ops);
+}
+
+static inline int au_test_aufs_file(struct file *f)
+{
+	return !(f->f_dentry->d_inode->i_mode
+		 & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK));
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * fi_read_lock, fi_write_lock,
+ * fi_read_unlock, fi_write_unlock, fi_downgrade_lock
+ */
+SimpleRwsemFuncs(fi, struct file *f, ftofi(f)->fi_rwsem);
+
+/* to debug easier, do not make them inlined functions */
+#define FiMustReadLock(f) do { \
+	SiMustAnyLock((f)->f_dentry->d_sb); \
+	RwMustReadLock(&ftofi(f)->fi_rwsem); \
+} while (0)
+
+#define FiMustWriteLock(f) do { \
+	SiMustAnyLock((f)->f_dentry->d_sb); \
+	RwMustWriteLock(&ftofi(f)->fi_rwsem); \
+} while (0)
+
+#define FiMustAnyLock(f) do { \
+	SiMustAnyLock((f)->f_dentry->d_sb); \
+	RwMustAnyLock(&ftofi(f)->fi_rwsem); \
+} while (0)
+
+#define FiMustNoWaiters(f)	RwMustNoWaiters(&ftofi(f)->fi_rwsem)
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_FILE_H__ */
diff -ruN linux-2.6.22/fs/aufs/finfo.c linux-2.6.22-aufs/fs/aufs/finfo.c
--- linux-2.6.22/fs/aufs/finfo.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/finfo.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: finfo.c,v 1.26 2007/07/02 05:12:15 sfjro Exp $ */
+
+#include "aufs.h"
+
+struct aufs_finfo *ftofi(struct file *file)
+{
+	struct aufs_finfo *finfo = file->private_data;
+	AuDebugOn(!finfo
+		  || !finfo->fi_hfile
+		  || (0 < finfo->fi_bend
+		      && (/* stosi(file->f_dentry->d_sb)->si_bend
+			     < finfo->fi_bend
+			     || */ finfo->fi_bend < finfo->fi_bstart)));
+	return finfo;
+}
+
+// hard/soft set
+aufs_bindex_t fbstart(struct file *file)
+{
+	FiMustAnyLock(file);
+	return ftofi(file)->fi_bstart;
+}
+
+aufs_bindex_t fbend(struct file *file)
+{
+	FiMustAnyLock(file);
+	return ftofi(file)->fi_bend;
+}
+
+struct aufs_vdir *fvdir_cache(struct file *file)
+{
+	FiMustAnyLock(file);
+	return ftofi(file)->fi_vdir_cache;
+}
+
+struct aufs_branch *ftobr(struct file *file, aufs_bindex_t bindex)
+{
+	struct aufs_finfo *finfo = ftofi(file);
+	struct aufs_hfile *hf;
+
+	FiMustAnyLock(file);
+	AuDebugOn(!finfo
+		  || finfo->fi_bstart < 0
+		  || bindex < finfo->fi_bstart
+		  || finfo->fi_bend < bindex);
+	hf = finfo->fi_hfile + bindex;
+	AuDebugOn(hf->hf_br && br_count(hf->hf_br) <= 0);
+	return hf->hf_br;
+}
+
+struct file *au_h_fptr_i(struct file *file, aufs_bindex_t bindex)
+{
+	struct aufs_finfo *finfo = ftofi(file);
+	struct aufs_hfile *hf;
+
+	FiMustAnyLock(file);
+	AuDebugOn(!finfo
+		  || finfo->fi_bstart < 0
+		  || bindex < finfo->fi_bstart
+		  || finfo->fi_bend < bindex);
+	hf = finfo->fi_hfile + bindex;
+	AuDebugOn(hf->hf_file
+		  && file_count(hf->hf_file) <= 0
+		  && br_count(hf->hf_br) <= 0);
+	return hf->hf_file;
+}
+
+struct file *au_h_fptr(struct file *file)
+{
+	return au_h_fptr_i(file, fbstart(file));
+}
+
+void set_fbstart(struct file *file, aufs_bindex_t bindex)
+{
+	FiMustWriteLock(file);
+	AuDebugOn(sbend(file->f_dentry->d_sb) < bindex);
+	ftofi(file)->fi_bstart = bindex;
+}
+
+void set_fbend(struct file *file, aufs_bindex_t bindex)
+{
+	FiMustWriteLock(file);
+	AuDebugOn(sbend(file->f_dentry->d_sb) < bindex
+		  || bindex < fbstart(file));
+	ftofi(file)->fi_bend = bindex;
+}
+
+void set_fvdir_cache(struct file *file, struct aufs_vdir *vdir_cache)
+{
+	FiMustWriteLock(file);
+	AuDebugOn(!S_ISDIR(file->f_dentry->d_inode->i_mode)
+		  || (ftofi(file)->fi_vdir_cache && vdir_cache));
+	ftofi(file)->fi_vdir_cache = vdir_cache;
+}
+
+void au_hfput(struct aufs_hfile *hf)
+{
+	fput(hf->hf_file);
+	hf->hf_file = NULL;
+	AuDebugOn(!hf->hf_br);
+	br_put(hf->hf_br);
+	hf->hf_br = NULL;
+}
+
+void set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *val)
+{
+	struct aufs_finfo *finfo = ftofi(file);
+	struct aufs_hfile *hf;
+
+	FiMustWriteLock(file);
+	AuDebugOn(!finfo
+		  || finfo->fi_bstart < 0
+		  || bindex < finfo->fi_bstart
+		  || finfo->fi_bend < bindex);
+	AuDebugOn(val && file_count(val) <= 0);
+	hf = finfo->fi_hfile + bindex;
+	AuDebugOn(val && hf->hf_file);
+	if (hf->hf_file)
+		au_hfput(hf);
+	if (val) {
+		hf->hf_file = val;
+		hf->hf_br = stobr(file->f_dentry->d_sb, bindex);
+	}
+}
+
+void au_update_figen(struct file *file)
+{
+	atomic_set(&ftofi(file)->fi_generation, au_digen(file->f_dentry));
+	//smp_mb();
+}
+
+void au_fin_finfo(struct file *file)
+{
+	struct aufs_finfo *finfo;
+	struct dentry *dentry;
+	aufs_bindex_t bindex, bend;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	SiMustAnyLock(dentry->d_sb);
+
+	fi_write_lock(file);
+	bend = fbend(file);
+	bindex = fbstart(file);
+	if (bindex >= 0)
+		for (; bindex <= bend; bindex++)
+			set_h_fptr(file, bindex, NULL);
+
+	finfo = ftofi(file);
+#ifdef CONFIG_AUFS_DEBUG
+	if (finfo->fi_bstart >= 0) {
+		bend = fbend(file);
+		for (bindex = finfo->fi_bstart; bindex <= bend; bindex++) {
+			struct aufs_hfile *hf;
+			hf = finfo->fi_hfile + bindex;
+			AuDebugOn(hf->hf_file || hf->hf_br);
+		}
+	}
+#endif
+
+	kfree(finfo->fi_hfile);
+	fi_write_unlock(file);
+	cache_free_finfo(finfo);
+	//file->private_data = NULL;
+}
+
+int au_init_finfo(struct file *file)
+{
+	struct aufs_finfo *finfo;
+	struct dentry *dentry;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	AuDebugOn(!dentry->d_inode);
+
+	finfo = cache_alloc_finfo();
+	if (finfo) {
+		finfo->fi_hfile = kcalloc(sbend(dentry->d_sb) + 1,
+					  sizeof(*finfo->fi_hfile), GFP_KERNEL);
+		if (finfo->fi_hfile) {
+			rw_init_wlock(&finfo->fi_rwsem);
+			finfo->fi_bstart = -1;
+			finfo->fi_bend = -1;
+			atomic_set(&finfo->fi_generation, au_digen(dentry));
+
+			file->private_data = finfo;
+			return 0; /* success */
+		}
+		cache_free_finfo(finfo);
+	}
+
+	TraceErr(-ENOMEM);
+	return -ENOMEM;
+}
diff -ruN linux-2.6.22/fs/aufs/hinode.h linux-2.6.22-aufs/fs/aufs/hinode.h
--- linux-2.6.22/fs/aufs/hinode.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/hinode.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: hinode.h,v 1.2 2007/07/15 20:04:22 sfjro Exp $ */
+
+#ifndef __AUFS_HINODE_H__
+#define __AUFS_HINODE_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/inotify.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+#include "inode.h"
+#include "vfsub.h"
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#else
+struct inotify_watch {};
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+struct aufs_hinotify {
+	struct inotify_watch	hin_watch;
+	struct inode		*hin_aufs_inode;	/* no get/put */
+};
+
+struct aufs_hinode {
+	struct inode		*hi_inode;
+	aufs_bindex_t		hi_id;
+	struct aufs_hinotify	*hi_notify;
+
+	/* reference to the copied-up whiteout with get/put */
+	struct dentry		*hi_whdentry;
+};
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_HINOTIFY
+/* hinotify.c */
+int alloc_hinotify(struct aufs_hinode *hinode, struct inode *inode,
+		   struct inode *h_inode);
+void do_free_hinotify(struct aufs_hinode *hinode);
+void do_hdir_lock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex,
+		  unsigned int lsc);
+void hdir_unlock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex);
+void hdir_lock_rename(struct dentry **h_parents, struct inode **dirs,
+		      aufs_bindex_t bindex, int issamedir);
+void hdir_unlock_rename(struct dentry **h_parents, struct inode **dirs,
+			aufs_bindex_t bindex, int issamedir);
+void au_reset_hinotify(struct inode *inode, unsigned int flags);
+
+int __init au_inotify_init(void);
+void au_inotify_fin(void);
+#else
+static inline
+int alloc_hinotify(struct aufs_hinode *hinode, struct inode *inode,
+		   struct inode *h_inode)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void do_free_hinotify(struct aufs_hinode *hinode)
+{
+	/* nothing */
+}
+
+static inline
+void do_hdir_lock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex,
+		  unsigned int lsc)
+{
+	vfsub_i_lock_nested(h_dir, lsc);
+}
+
+static inline
+void hdir_unlock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex)
+{
+	vfsub_i_unlock(h_dir);
+}
+
+static inline
+void hdir_lock_rename(struct dentry **h_parents, struct inode **dirs,
+		      aufs_bindex_t bindex, int issamedir)
+{
+	vfsub_lock_rename(h_parents[0], h_parents[1]);
+}
+
+static inline
+void hdir_unlock_rename(struct dentry **h_parents, struct inode **dirs,
+			aufs_bindex_t bindex, int issamedir)
+{
+	vfsub_unlock_rename(h_parents[0], h_parents[1]);
+}
+
+static inline void au_reset_hinotify(struct inode *inode, unsigned int flags)
+{
+	/* nothing */
+}
+
+#define au_inotify_init()	0
+#define au_inotify_fin()	do {} while (0)
+#endif /* CONFIG_AUFS_HINOTIFY */
+
+static inline void free_hinotify(struct inode *inode, aufs_bindex_t bindex)
+{
+	do_free_hinotify(itoii(inode)->ii_hinode + bindex);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * hgdir_lock, hdir_lock, hdir2_lock
+ */
+#define LockFunc(name, lsc) \
+static inline \
+void name##_lock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex) \
+{do_hdir_lock(h_dir, dir, bindex, AuLsc_I_##lsc);}
+
+LockFunc(hgdir, GPARENT);
+LockFunc(hdir, PARENT);
+LockFunc(hdir2, PARENT2);
+
+#undef LockFunc
+
+/* ---------------------------------------------------------------------- */
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_HINODE_H__ */
diff -ruN linux-2.6.22/fs/aufs/hinotify.c linux-2.6.22-aufs/fs/aufs/hinotify.c
--- linux-2.6.22/fs/aufs/hinotify.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/hinotify.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,830 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: hinotify.c,v 1.27 2007/07/15 20:06:55 sfjro Exp $ */
+
+#include "aufs.h"
+
+static struct inotify_handle *in_handle;
+static const __u32 in_mask = (IN_MOVE | IN_DELETE | IN_CREATE /* | IN_ACCESS */
+			      | IN_MODIFY | IN_ATTRIB
+			      | IN_DELETE_SELF | IN_MOVE_SELF);
+
+int alloc_hinotify(struct aufs_hinode *hinode, struct inode *inode,
+		   struct inode *hidden_inode)
+{
+	int err;
+	struct aufs_hinotify *hin;
+	s32 wd;
+
+	LKTRTrace("i%lu, hi%lu\n", inode->i_ino, hidden_inode->i_ino);
+
+	err = -ENOMEM;
+	hin = cache_alloc_hinotify();
+	if (hin) {
+		AuDebugOn(hinode->hi_notify);
+		hinode->hi_notify = hin;
+		hin->hin_aufs_inode = inode;
+		inotify_init_watch(&hin->hin_watch);
+		wd = inotify_add_watch(in_handle, &hin->hin_watch, hidden_inode,
+				       in_mask);
+		if (wd >= 0)
+			return 0; /* success */
+
+		err = wd;
+		put_inotify_watch(&hin->hin_watch);
+		cache_free_hinotify(hin);
+		hinode->hi_notify = NULL;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+void do_free_hinotify(struct aufs_hinode *hinode)
+{
+	int err;
+	struct aufs_hinotify *hin;
+
+	TraceEnter();
+
+	hin = hinode->hi_notify;
+	if (hin) {
+		err = 0;
+		if (atomic_read(&hin->hin_watch.count))
+			err = inotify_rm_watch(in_handle, &hin->hin_watch);
+
+		if (!err) {
+			cache_free_hinotify(hin);
+			hinode->hi_notify = NULL;
+		} else
+			IOErr1("failed inotify_rm_watch() %d\n", err);
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void ctl_hinotify(struct aufs_hinode *hinode, const __u32 mask)
+{
+	struct inode *hi;
+	struct inotify_watch *watch;
+
+	hi = hinode->hi_inode;
+	LKTRTrace("hi%lu, sb %p, 0x%x\n", hi->i_ino, hi->i_sb, mask);
+	IMustLock(hi);
+	if (!hinode->hi_notify)
+		return;
+
+	watch = &hinode->hi_notify->hin_watch;
+#if 0
+	{
+		u32 wd;
+		wd = inotify_find_update_watch(in_handle, hi, mask);
+		TraceErr(wd);
+		/* ignore an err; */
+	}
+#else
+	watch->mask = mask;
+	smp_mb(); /* flush mask */
+#endif
+	LKTRTrace("watch %p, mask %u\n", watch, watch->mask);
+}
+
+#define suspend_hinotify(hi)	ctl_hinotify(hi, 0)
+#define resume_hinotify(hi)	ctl_hinotify(hi, in_mask)
+
+void do_hdir_lock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex,
+		  unsigned int lsc)
+{
+	struct aufs_hinode *hinode;
+
+	LKTRTrace("i%lu, b%d, lsc %d\n", dir->i_ino, bindex, lsc);
+	AuDebugOn(!S_ISDIR(dir->i_mode));
+	hinode = itoii(dir)->ii_hinode + bindex;
+	AuDebugOn(h_dir != hinode->hi_inode);
+
+	vfsub_i_lock_nested(h_dir, lsc);
+	suspend_hinotify(hinode);
+}
+
+void hdir_unlock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex)
+{
+	struct aufs_hinode *hinode;
+
+	LKTRTrace("i%lu, b%d\n", dir->i_ino, bindex);
+	AuDebugOn(!S_ISDIR(dir->i_mode));
+	hinode = itoii(dir)->ii_hinode + bindex;
+	AuDebugOn(h_dir != hinode->hi_inode);
+
+	resume_hinotify(hinode);
+	vfsub_i_unlock(h_dir);
+}
+
+void hdir_lock_rename(struct dentry **h_parents, struct inode **dirs,
+		      aufs_bindex_t bindex, int issamedir)
+{
+	struct aufs_hinode *hinode;
+
+	LKTRTrace("%.*s, %.*s\n", DLNPair(h_parents[0]), DLNPair(h_parents[1]));
+
+	vfsub_lock_rename(h_parents[0], h_parents[1]);
+	hinode = itoii(dirs[0])->ii_hinode + bindex;
+	AuDebugOn(h_parents[0]->d_inode != hinode->hi_inode);
+	suspend_hinotify(hinode);
+	if (issamedir)
+		return;
+	hinode = itoii(dirs[1])->ii_hinode + bindex;
+	AuDebugOn(h_parents[1]->d_inode != hinode->hi_inode);
+	suspend_hinotify(hinode);
+}
+
+void hdir_unlock_rename(struct dentry **h_parents, struct inode **dirs,
+			aufs_bindex_t bindex, int issamedir)
+{
+	struct aufs_hinode *hinode;
+
+	LKTRTrace("%.*s, %.*s\n", DLNPair(h_parents[0]), DLNPair(h_parents[1]));
+
+	hinode = itoii(dirs[0])->ii_hinode + bindex;
+	AuDebugOn(h_parents[0]->d_inode != hinode->hi_inode);
+	resume_hinotify(hinode);
+	if (!issamedir) {
+		hinode = itoii(dirs[1])->ii_hinode + bindex;
+		AuDebugOn(h_parents[1]->d_inode != hinode->hi_inode);
+		resume_hinotify(hinode);
+	}
+	vfsub_unlock_rename(h_parents[0], h_parents[1]);
+}
+
+void au_reset_hinotify(struct inode *inode, unsigned int flags)
+{
+	aufs_bindex_t bindex, bend;
+	struct inode *hi;
+	struct dentry *iwhdentry;
+
+	LKTRTrace("i%lu, 0x%x\n", inode->i_ino, flags);
+
+	bend = ibend(inode);
+	for (bindex = ibstart(inode); bindex <= bend; bindex++) {
+		hi = au_h_iptr_i(inode, bindex);
+		if (hi) {
+			//vfsub_i_lock_nested(hi, AuLsc_I_CHILD);
+			iwhdentry = au_hi_wh(inode, bindex);
+			if (unlikely(iwhdentry))
+				dget(iwhdentry);
+			igrab(hi);
+			set_h_iptr(inode, bindex, NULL, 0);
+			set_h_iptr(inode, bindex, igrab(hi),
+				   flags & ~AUFS_HI_XINO);
+			iput(hi);
+			dput(iwhdentry);
+			//vfsub_i_unlock(hi);
+		}
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_DEBUG
+static char *in_name(u32 mask)
+{
+#define test_ret(flag)	if (mask & flag) return #flag;
+	test_ret(IN_ACCESS);
+	test_ret(IN_MODIFY);
+	test_ret(IN_ATTRIB);
+	test_ret(IN_CLOSE_WRITE);
+	test_ret(IN_CLOSE_NOWRITE);
+	test_ret(IN_OPEN);
+	test_ret(IN_MOVED_FROM);
+	test_ret(IN_MOVED_TO);
+	test_ret(IN_CREATE);
+	test_ret(IN_DELETE);
+	test_ret(IN_DELETE_SELF);
+	test_ret(IN_MOVE_SELF);
+	test_ret(IN_UNMOUNT);
+	test_ret(IN_Q_OVERFLOW);
+	test_ret(IN_IGNORED);
+	return "";
+#undef test_ret
+}
+#else
+#define in_name(m) "??"
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen,
+					   struct inode *dir)
+{
+	struct dentry *dentry, *d, *parent;
+	struct qstr *dname;
+
+	LKTRTrace("%.*s, dir%lu\n", nlen, name, dir->i_ino);
+
+	parent = d_find_alias(dir);
+	if (!parent)
+		return NULL;
+
+	dentry = NULL;
+	spin_lock(&dcache_lock);
+	list_for_each_entry(d, &parent->d_subdirs, d_u.d_child) {
+		LKTRTrace("%.*s\n", DLNPair(d));
+		dname = &d->d_name;
+		if (dname->len != nlen
+		    || memcmp(dname->name, name, nlen)
+		    || !atomic_read(&d->d_count))
+			continue;
+
+		dentry = dget(d);
+		break;
+	}
+	spin_unlock(&dcache_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;
+	struct xino xino;
+	int err;
+
+	LKTRTrace("b%d, hi%lu\n", bindex, h_ino);
+	AuDebugOn(!au_flag_test(sb, AuFlag_XINO));
+
+	inode = NULL;
+	err = xino_read(sb, bindex, h_ino, &xino);
+	if (!err && xino.ino)
+		inode = ilookup(sb, xino.ino);
+	if (!inode)
+		goto out;
+	if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
+		Warn("wrong root branch\n");
+		iput(inode);
+		inode = NULL;
+		goto out;
+	}
+
+	ii_write_lock_child(inode);
+#if 0
+	if (au_iigen(inode) == au_sigen(sb))
+		goto out; /* success */
+
+	err = au_refresh_hinode_self(inode);
+	if (!err)
+		goto out; /* success */
+
+	IOErr1("err %d ignored, but ino will be broken\n", err);
+	ii_write_unlock(inode);
+	iput(inode);
+	inode = NULL;
+#endif
+
+ out:
+	return inode;
+}
+
+static int hin_xino(struct inode *inode, struct inode *h_inode)
+{
+	int err;
+	aufs_bindex_t bindex, bend, bfound;
+
+	LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino);
+
+	err = 0;
+	if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
+		Warn("branch root dir was changed\n");
+		goto out;
+	}
+
+	bfound = -1;
+	bend = ibend(inode);
+	for (bindex = ibstart(inode); bindex <= bend; bindex++) {
+		if (au_h_iptr_i(inode, bindex) == h_inode) {
+			bfound = bindex;
+			break;
+		}
+	}
+	if (bfound < 0)
+		goto out;
+
+	err = xino_write0(inode->i_sb, bindex, h_inode->i_ino, 0);
+	/* ignore this error */
+	/* bad action? */
+
+	/* children inode number will be broken */
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int hin_iigen(struct inode *inode)
+{
+	LKTRTrace("i%lu\n", inode->i_ino);
+
+	if (inode->i_ino != AUFS_ROOT_INO)
+		au_iigen_dec(inode);
+	else
+		Warn("branch root dir was changed\n");
+	return 0;
+}
+
+static int hin_digen_tree(struct dentry *dentry, int dec_iigen)
+{
+	int err, i, j, ndentry;
+	struct au_dcsub_pages dpages;
+	struct au_dpage *dpage;
+	struct dentry **dentries;
+
+	LKTRTrace("%.*s, iigen %d\n", DLNPair(dentry), dec_iigen);
+
+	err = au_dpages_init(&dpages, GFP_KERNEL);
+	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];
+			LKTRTrace("%.*s\n", DLNPair(d));
+			if (IS_ROOT(d))
+				continue;
+			d_drop(d);
+			au_digen_dec(d);
+			if (dec_iigen && d->d_inode) {
+				//reset children xino? cached children only?
+				hin_iigen(d->d_inode);
+			}
+		}
+	}
+
+ out_dpages:
+	au_dpages_free(&dpages);
+
+	/* discard children */
+	dentry_unhash(dentry);
+	dput(dentry);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * return 0 if processed.
+ */
+static int hin_digen_by_inode(char *name, unsigned int nlen,
+			      struct inode *inode, int dec_iigen)
+{
+	int err;
+	struct dentry *d;
+	struct qstr *dname;
+
+	LKTRTrace("%.*s, i%lu, iigen %d\n",
+		  nlen, name, inode->i_ino, dec_iigen);
+
+	err = 1;
+	if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
+		Warn("branch root dir was changed\n");
+		err = 0;
+		goto out;
+	}
+
+	if (!S_ISDIR(inode->i_mode)) {
+		AuDebugOn(!name);
+		spin_lock(&dcache_lock);
+		list_for_each_entry(d, &inode->i_dentry, d_alias) {
+			dname = &d->d_name;
+			if (dname->len != nlen
+			    && memcmp(dname->name, name, nlen))
+				continue;
+			err = 0;
+			spin_lock(&d->d_lock);
+			__d_drop(d);
+			au_digen_dec(d);
+			spin_unlock(&d->d_lock);
+			break;
+		}
+		spin_unlock(&dcache_lock);
+	} else {
+		d = d_find_alias(inode);
+		if (d) {
+			dname = &d->d_name;
+			if (dname->len == nlen
+			    && !memcmp(dname->name, name, nlen))
+				err = hin_digen_tree(d, dec_iigen);
+			dput(d);
+		}
+	}
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int hin_digen_by_name(struct dentry *dentry, int dec_iigen)
+{
+	struct inode *inode;
+
+	LKTRTrace("%.*s, iigen %d\n", DLNPair(dentry), dec_iigen);
+
+	if (IS_ROOT(dentry)) {
+		Warn("branch root dir was changed\n");
+		return 0;
+	}
+
+	inode = dentry->d_inode;
+	if (!inode) {
+		d_drop(dentry);
+		au_digen_dec(dentry);
+	} else if (unlikely(inode->i_ino == AUFS_ROOT_INO))
+		Warn("branch root dir was changed\n");
+	else {
+		if (!S_ISDIR(inode->i_mode)) {
+			au_digen_dec(dentry);
+			d_drop(dentry);
+		} else
+			hin_digen_tree(dentry, dec_iigen);
+	}
+
+	return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+union hin_job {
+	unsigned int flags;
+	struct {
+		unsigned int xino0:1;
+		unsigned int iigen:1;
+		unsigned int digen:1;
+		unsigned int dirent:1;
+		unsigned int attr:1;
+	};
+};
+
+struct hin_job_args {
+	union hin_job *jobs;
+	struct inode *inode, *h_inode, *dir, *h_dir;
+	struct dentry *dentry;
+	char *h_name;
+	int h_nlen;
+};
+
+static int hin_job(struct hin_job_args *a)
+{
+	/* reset xino */
+	if (a->inode && a->jobs->xino0)
+		hin_xino(a->inode, a->h_inode);
+	/* ignore this error */
+
+	/* make the generation obsolete */
+	if (a->jobs->iigen && a->inode)
+		hin_iigen(a->inode);
+	/* ignore this error */
+
+	if (a->jobs->digen) {
+		int err;
+		err = -1;
+		if (a->inode)
+			err = hin_digen_by_inode(a->h_name, a->h_nlen, a->inode,
+						 a->jobs->iigen);
+		if (err && a->dentry)
+			hin_digen_by_name(a->dentry, a->jobs->iigen);
+		/* ignore this error */
+	}
+
+	/* make dir entries obsolete */
+	if (a->jobs->dirent) {
+		struct aufs_vdir *vdir;
+		vdir = ivdir(a->inode);
+		if (vdir)
+			vdir->vd_jiffy = 0;
+		IMustLock(a->inode);
+		a->inode->i_version++;
+	}
+
+	/* update the attr */
+	if (a->jobs->attr
+	    && a->inode
+	    && au_h_iptr(a->inode) == a->h_inode)
+		au_cpup_attr_all(a->inode);
+	return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+enum {CHILD, PARENT};
+struct postproc_args {
+	struct inode *h_dir, *dir, *h_child_inode;
+	u32 mask;
+	union hin_job jobs[2];
+	unsigned int h_child_nlen;
+	char h_child_name[];
+};
+
+static void postproc(void *_args)
+{
+	struct postproc_args *a = _args;
+	struct super_block *sb;
+	aufs_bindex_t bindex, bend, bfound;
+	int xino, err;
+	struct inode *inode;
+	ino_t h_ino;
+	struct hin_job_args args;
+	struct dentry *dentry;
+
+	//au_debug_on();
+	LKTRTrace("mask 0x%x %s, i%lu, hi%lu, hci%lu\n",
+		  a->mask, in_name(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;
+	vfsub_i_lock(a->dir);
+	sb = a->dir->i_sb;
+	si_read_lock(sb, !AuLock_FLUSH);
+
+	ii_read_lock_parent(a->dir);
+	bfound = -1;
+	bend = ibend(a->dir);
+	for (bindex = ibstart(a->dir); bindex <= bend; bindex++)
+		if (au_h_iptr_i(a->dir, bindex) == a->h_dir) {
+			bfound = bindex;
+			break;
+		}
+	ii_read_unlock(a->dir);
+	if (unlikely(bfound < 0))
+		goto out;
+
+	xino = !!au_flag_test(sb, AuFlag_XINO);
+	h_ino = 0;
+	if (a->h_child_inode)
+		h_ino = a->h_child_inode->i_ino;
+
+	if (a->h_child_nlen && a->jobs[CHILD].digen)
+		dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen,
+					      a->dir);
+	if (dentry)
+		inode = dentry->d_inode;
+	if (xino && !inode && h_ino
+	    && (a->jobs[CHILD].xino0
+		|| a->jobs[CHILD].iigen
+		|| a->jobs[CHILD].digen
+		|| a->jobs[CHILD].attr))
+		inode = lookup_wlock_by_ino(sb, bfound, h_ino);
+
+	args.jobs = a->jobs + 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 = hin_job(&args);
+	if (dentry) {
+		di_write_unlock(dentry);
+		dput(dentry);
+	} else if (inode) {
+		ii_write_unlock(inode);
+		iput(inode);
+	}
+
+	ii_write_lock_parent(a->dir);
+	args.jobs = a->jobs + 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 = hin_job(&args);
+	ii_write_unlock(a->dir);
+
+ out:
+	si_read_unlock(sb);
+	vfsub_i_unlock(a->dir);
+	au_nwt_dec(&stosi(sb)->si_nowait);
+
+	iput(a->h_child_inode);
+	iput(a->h_dir);
+	iput(a->dir);
+	kfree(a);
+	//au_debug_off();
+}
+
+static void aufs_inotify(struct inotify_watch *watch, u32 wd, u32 mask,
+			 u32 cookie, const char *h_child_name,
+			 struct inode *h_child_inode)
+{
+	struct aufs_hinotify *hinotify;
+	struct postproc_args *args;
+	int len, wkq_err, isdir, isroot, wh;
+	char *p;
+	struct inode *dir;
+	union hin_job jobs[2];
+	struct super_block *sb;
+
+	LKTRTrace("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s, hi%lu\n",
+		  watch->inode->i_ino, wd, mask, in_name(mask), cookie,
+		  h_child_name ? h_child_name : "",
+		  h_child_inode ? h_child_inode->i_ino : 0);
+#if 0 //defined(ForceInotify) || defined(DbgInotify)
+	Dbg("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s, hi%lu\n",
+		  watch->inode->i_ino, wd, mask, in_name(mask), cookie,
+		  h_child_name ? h_child_name : "",
+		  h_child_inode ? h_child_inode->i_ino : 0);
+#endif
+	/* if IN_UNMOUNT happens, there must be another bug */
+	if (mask & (IN_IGNORED | IN_UNMOUNT)) {
+		//WARN_ON(watch->inode->i_ino == 15);
+		put_inotify_watch(watch);
+		return;
+	}
+
+#if 0//def DbgInotify
+	Dbg("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s, hi%lu\n",
+		  watch->inode->i_ino, wd, mask, in_name(mask), cookie,
+		  h_child_name ? h_child_name : "",
+		  h_child_inode ? h_child_inode->i_ino : 0);
+	WARN_ON(1);
+#endif
+
+	isdir = 0;
+	if (h_child_inode)
+		isdir = !!S_ISDIR(h_child_inode->i_mode);
+	hinotify = container_of(watch, struct aufs_hinotify, hin_watch);
+	AuDebugOn(!hinotify || !hinotify->hin_aufs_inode);
+	dir = hinotify->hin_aufs_inode;
+	isroot = (dir->i_ino == AUFS_ROOT_INO);
+	wh = len = 0;
+	if (h_child_name) {
+		len = strlen(h_child_name);
+		if (unlikely(!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;
+		}
+	}
+
+	jobs[CHILD].flags = jobs[PARENT].flags = 0;
+	switch (mask & IN_ALL_EVENTS) {
+	case IN_MODIFY:
+		/*FALLTHROUGH*/
+	case IN_ATTRIB:
+		if (h_child_inode) {
+			if (!wh)
+				jobs[CHILD].attr = 1;
+		} else
+			jobs[PARENT].attr = 1;
+		break;
+
+		/* IN_MOVED_FROM is the first event in rename(2) */
+	case IN_MOVED_FROM:
+	case IN_MOVED_TO:
+		AuDebugOn(!h_child_name || !h_child_inode);
+		jobs[CHILD].iigen = 1;
+		jobs[CHILD].attr = 1;
+		jobs[CHILD].xino0 = 1;//!!isdir;
+		jobs[CHILD].digen = 1;
+		jobs[PARENT].attr = 1;
+		jobs[PARENT].dirent = 1;
+		break;
+
+	case IN_CREATE:
+		AuDebugOn(!h_child_name || !h_child_inode);
+		jobs[PARENT].attr = 1;
+		jobs[PARENT].dirent = 1;
+		jobs[CHILD].digen = 1;
+		jobs[CHILD].iigen = 1;
+		/* hard link */
+		jobs[CHILD].attr = (!isdir && h_child_inode->i_nlink > 1);
+		break;
+
+	case IN_DELETE:
+		/*
+		 * aufs never be able to get this child inode.
+		 * revalidation should be in d_revalide()
+		 * by checking i_nlink, i_generation or d_unhashed().
+		 */
+		AuDebugOn(!h_child_name);
+		jobs[PARENT].attr = 1;
+		jobs[PARENT].dirent = 1;
+		jobs[CHILD].iigen = 1;
+		jobs[CHILD].digen = 1;
+		break;
+
+	case IN_DELETE_SELF:
+		jobs[PARENT].iigen = !isroot;
+		/*FALLTHROUGH*/
+
+	case IN_MOVE_SELF:
+		AuDebugOn(h_child_name || h_child_inode);
+		if (unlikely(isroot)) {
+			Warn("root branch was moved\n");
+			return;
+		}
+#if 1
+		return;
+#else
+		jobs[PARENT].xino0 = !isroot;
+		jobs[PARENT].iigen = !isroot;
+		jobs[PARENT].digen = !isroot;
+		jobs[PARENT].attr = !isroot;
+		jobs[PARENT].dirent = !isroot;
+		break;
+#endif
+	case IN_ACCESS:
+	default:
+		AuDebugOn(1);
+	}
+
+#if 0 //def DbgInotify
+	WARN_ON(1);
+#endif
+
+	if (wh)
+		h_child_inode = NULL;
+
+	/* iput() and kfree() will be called in postproc() */
+	args = kmalloc(sizeof(*args) + len + 1, GFP_KERNEL);
+	if (unlikely(!args)) {
+		Err1("no memory\n");
+		return;
+	}
+	memcpy(args->jobs, jobs, sizeof(jobs));
+	args->mask = mask;
+	args->dir = igrab(dir);
+	args->h_dir = igrab(watch->inode);
+	if (h_child_inode)
+		igrab(h_child_inode);
+	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 + 1);
+	}
+
+	sb = dir->i_sb;
+	au_nwt_inc(&stosi(sb)->si_nowait);
+	wkq_err = au_wkq_nowait(postproc, args, sb, /*dlgt*/0);
+	if (unlikely(wkq_err)) {
+		Err("wkq %d\n", wkq_err);
+		au_nwt_dec(&stosi(sb)->si_nowait);
+	}
+}
+
+static void aufs_inotify_destroy(struct inotify_watch *watch)
+{
+	return;
+}
+
+static struct inotify_operations aufs_inotify_ops = {
+	.handle_event	= aufs_inotify,
+	.destroy_watch	= aufs_inotify_destroy
+};
+
+/* ---------------------------------------------------------------------- */
+
+int __init au_inotify_init(void)
+{
+	in_handle = inotify_init(&aufs_inotify_ops);
+	if (!IS_ERR(in_handle))
+		return 0;
+	TraceErrPtr(in_handle);
+	return PTR_ERR(in_handle);
+}
+
+void au_inotify_fin(void)
+{
+	inotify_destroy(in_handle);
+}
diff -ruN linux-2.6.22/fs/aufs/i_op.c linux-2.6.22-aufs/fs/aufs/i_op.c
--- linux-2.6.22/fs/aufs/i_op.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/i_op.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,629 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: i_op.c,v 1.37 2007/07/15 20:04:34 sfjro Exp $ */
+
+//#include <linux/fs.h>
+//#include <linux/namei.h>
+#include <linux/security.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#include <linux/uaccess.h>
+#else
+#include <asm/uaccess.h>
+#endif
+#include "aufs.h"
+
+#ifdef CONFIG_AUFS_DLGT
+struct security_inode_permission_args {
+	int *errp;
+	struct inode *h_inode;
+	int mask;
+	struct nameidata *fake_nd;
+};
+
+static void call_security_inode_permission(void *args)
+{
+	struct security_inode_permission_args *a = args;
+	LKTRTrace("fsuid %d\n", current->fsuid);
+	*a->errp = security_inode_permission(a->h_inode, a->mask, a->fake_nd);
+}
+#endif
+
+static int hidden_permission(struct inode *hidden_inode, int mask,
+			     struct nameidata *fake_nd, int brperm, int dlgt)
+{
+	int err, submask;
+	const int write_mask = (mask & (MAY_WRITE | MAY_APPEND));
+
+	LKTRTrace("ino %lu, mask 0x%x, brperm 0x%x\n",
+		  hidden_inode->i_ino, mask, brperm);
+
+	err = -EACCES;
+	if (unlikely(write_mask && IS_IMMUTABLE(hidden_inode)))
+		goto out;
+
+	/* skip hidden fs test in the case of write to ro branch */
+	submask = mask & ~MAY_APPEND;
+	if (unlikely((write_mask && !br_writable(brperm))
+		     || !hidden_inode->i_op
+		     || !hidden_inode->i_op->permission)) {
+		//LKTRLabel(generic_permission);
+		err = generic_permission(hidden_inode, submask, NULL);
+	} else {
+		//LKTRLabel(h_inode->permission);
+		err = hidden_inode->i_op->permission(hidden_inode, submask,
+						     fake_nd);
+		TraceErr(err);
+	}
+
+#if 1
+	if (!err) {
+#ifndef CONFIG_AUFS_DLGT
+		err = security_inode_permission(hidden_inode, mask, fake_nd);
+#else
+		if (!dlgt)
+			err = security_inode_permission(hidden_inode, mask,
+							fake_nd);
+		else {
+			int wkq_err;
+			struct security_inode_permission_args args = {
+				.errp		= &err,
+				.h_inode	= hidden_inode,
+				.mask		= mask,
+				.fake_nd	= fake_nd
+			};
+			wkq_err = au_wkq_wait(call_security_inode_permission,
+					      &args, /*dlgt*/1);
+			if (unlikely(wkq_err))
+				err = wkq_err;
+		}
+#endif
+	}
+#endif
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int silly_lock(struct inode *inode, struct nameidata *nd)
+{
+	int locked = 0;
+	struct super_block *sb = inode->i_sb;
+
+	LKTRTrace("i%lu, nd %p\n", inode->i_ino, nd);
+
+#ifdef CONFIG_AUFS_FAKE_DM
+	si_read_lock(sb, !AuLock_FLUSH);
+	ii_read_lock_child(inode);
+#else
+	if (!nd || !nd->dentry) {
+		si_read_lock(sb, !AuLock_FLUSH);
+		ii_read_lock_child(inode);
+	} else if (nd->dentry->d_inode != inode) {
+		locked = 1;
+		/* lock child first, then parent */
+		si_read_lock(sb, !AuLock_FLUSH);
+		ii_read_lock_child(inode);
+		di_read_lock_parent(nd->dentry, 0);
+	} else {
+		locked = 2;
+		aufs_read_lock(nd->dentry, AuLock_IR);
+	}
+#endif
+	return locked;
+}
+
+static void silly_unlock(int locked, struct inode *inode, struct nameidata *nd)
+{
+	struct super_block *sb = inode->i_sb;
+
+	LKTRTrace("locked %d, i%lu, nd %p\n", locked, inode->i_ino, nd);
+
+#ifdef CONFIG_AUFS_FAKE_DM
+	ii_read_unlock(inode);
+	si_read_unlock(sb);
+#else
+	switch (locked) {
+	case 0:
+		ii_read_unlock(inode);
+		si_read_unlock(sb);
+		break;
+	case 1:
+		di_read_unlock(nd->dentry, 0);
+		ii_read_unlock(inode);
+		si_read_unlock(sb);
+		break;
+	case 2:
+		aufs_read_unlock(nd->dentry, AuLock_IR);
+		break;
+	default:
+		BUG();
+	}
+#endif
+}
+
+static int aufs_permission(struct inode *inode, int mask, struct nameidata *nd)
+{
+	int err, locked, dlgt;
+	aufs_bindex_t bindex, bend;
+	struct inode *hidden_inode;
+	struct super_block *sb;
+	struct nameidata fake_nd, *p;
+	const int write_mask = (mask & (MAY_WRITE | MAY_APPEND));
+	const int nondir = !S_ISDIR(inode->i_mode);
+
+	LKTRTrace("ino %lu, mask 0x%x, nondir %d, write_mask %d, "
+		  "nd %d{%d, %d}\n",
+		  inode->i_ino, mask, nondir, write_mask,
+		  !!nd, nd ? !!nd->dentry : 0, nd ? !!nd->mnt : 0);
+
+	sb = inode->i_sb;
+	locked = silly_lock(inode, nd);
+	dlgt = need_dlgt(sb);
+
+	if (nd)
+		fake_nd = *nd;
+	if (/* unlikely */(nondir || write_mask)) {
+		hidden_inode = au_h_iptr(inode);
+		AuDebugOn(!hidden_inode
+			  || ((hidden_inode->i_mode & S_IFMT)
+			      != (inode->i_mode & S_IFMT)));
+		err = 0;
+		bindex = ibstart(inode);
+		p = fake_dm(&fake_nd, nd, sb, bindex);
+		/* actual test will be delegated to LSM */
+		if (IS_ERR(p))
+			AuDebugOn(PTR_ERR(p) != -ENOENT);
+		else {
+			err = hidden_permission(hidden_inode, mask, p,
+						sbr_perm(sb, bindex), dlgt);
+			fake_dm_release(p);
+		}
+		if (write_mask && !err) {
+			err = find_rw_br(sb, bindex);
+			if (err >= 0)
+				err = 0;
+		}
+		goto out;
+	}
+
+	/* non-write to dir */
+	err = 0;
+	bend = ibend(inode);
+	for (bindex = ibstart(inode); !err && bindex <= bend; bindex++) {
+		hidden_inode = au_h_iptr_i(inode, bindex);
+		if (!hidden_inode)
+			continue;
+		AuDebugOn(!S_ISDIR(hidden_inode->i_mode));
+
+		p = fake_dm(&fake_nd, nd, sb, bindex);
+		/* actual test will be delegated to LSM */
+		if (IS_ERR(p))
+			AuDebugOn(PTR_ERR(p) != -ENOENT);
+		else {
+			err = hidden_permission(hidden_inode, mask, p,
+						sbr_perm(sb, bindex), dlgt);
+			fake_dm_release(p);
+		}
+	}
+
+ out:
+	silly_unlock(locked, inode, nd);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry,
+				  struct nameidata *nd)
+{
+	struct dentry *ret, *parent;
+	int err, npositive;
+	struct inode *inode;
+
+	LKTRTrace("dir %lu, %.*s\n", dir->i_ino, DLNPair(dentry));
+	AuDebugOn(IS_ROOT(dentry));
+	IMustLock(dir);
+
+	/* nd can be NULL */
+	parent = dentry->d_parent;
+	aufs_read_lock(parent, !AuLock_FLUSH);
+	err = au_alloc_dinfo(dentry);
+	//if (LktrCond) err = -1;
+	ret = ERR_PTR(err);
+	if (unlikely(err))
+		goto out;
+
+	err = npositive = lkup_dentry(dentry, dbstart(parent), /*type*/0);
+	//err = -1;
+	ret = ERR_PTR(err);
+	if (unlikely(err < 0))
+		goto out_unlock;
+	inode = NULL;
+	if (npositive) {
+		inode = au_new_inode(dentry);
+		ret = (void*)inode;
+	}
+	if (!IS_ERR(inode)) {
+#if 1
+		/* d_splice_alias() also supports d_add() */
+		ret = d_splice_alias(inode, dentry);
+		if (unlikely(IS_ERR(ret) && inode))
+			ii_write_unlock(inode);
+#else
+		d_add(dentry, inode);
+#endif
+	}
+
+ out_unlock:
+	di_write_unlock(dentry);
+ out:
+	aufs_read_unlock(parent, !AuLock_IR);
+	TraceErrPtr(ret);
+	return ret;
+}
+
+/* ---------------------------------------------------------------------- */
+
+//todo: simplify
+/*
+ * decide the branch and the parent dir where we will create a new entry.
+ * returns new bindex or an error.
+ * copyup the parent dir if needed.
+ */
+int wr_dir(struct dentry *dentry, int add_entry, struct dentry *src_dentry,
+	   aufs_bindex_t force_btgt, int do_lock_srcdir)
+{
+	int err;
+	aufs_bindex_t bcpup, bstart, src_bstart;
+	struct dentry *hidden_parent;
+	struct super_block *sb;
+	struct dentry *parent, *src_parent = NULL;
+	struct inode *dir, *src_dir = NULL;
+
+	LKTRTrace("%.*s, add %d, src %p, force %d, lock_srcdir %d\n",
+		  DLNPair(dentry), add_entry, src_dentry, force_btgt,
+		  do_lock_srcdir);
+
+	sb = dentry->d_sb;
+	parent = dget_parent(dentry);
+	bcpup = bstart = dbstart(dentry);
+	if (force_btgt < 0) {
+		if (src_dentry) {
+			src_bstart = dbstart(src_dentry);
+			if (src_bstart < bstart)
+				bcpup = src_bstart;
+		}
+		if (test_ro(sb, bcpup, dentry->d_inode)) {
+			if (!add_entry)
+				di_read_lock_parent(parent, !AuLock_IR);
+			bcpup = err = find_rw_parent_br(dentry, bcpup);
+			//bcpup = err = find_rw_br(sb, bcpup);
+			if (!add_entry)
+				di_read_unlock(parent, !AuLock_IR);
+			//err = -1;
+			if (unlikely(err < 0))
+				goto out;
+		}
+	} else {
+		AuDebugOn(bstart <= force_btgt
+			  || test_ro(sb, force_btgt, dentry->d_inode));
+		bcpup = force_btgt;
+	}
+	LKTRTrace("bstart %d, bcpup %d\n", bstart, bcpup);
+
+	err = bcpup;
+	if (bcpup == bstart)
+		goto out; /* success */
+
+	/* copyup the new parent into the branch we process */
+	hidden_parent = dget_parent(au_h_dptr(dentry));
+	if (src_dentry) {
+		src_parent = dget_parent(src_dentry);
+		src_dir = src_parent->d_inode;
+		if (do_lock_srcdir)
+			di_write_lock_parent2(src_parent);
+	}
+
+	dir = parent->d_inode;
+	if (add_entry) {
+		au_update_dbstart(dentry);
+		IMustLock(dir);
+		DiMustWriteLock(parent);
+		IiMustWriteLock(dir);
+	} else
+		di_write_lock_parent(parent);
+
+	err = 0;
+	if (!au_h_dptr_i(parent, bcpup))
+		err = cpup_dirs(dentry, bcpup, src_parent);
+	//err = -1;
+	if (!err && add_entry) {
+		dput(hidden_parent);
+		hidden_parent = dget(au_h_dptr_i(parent, bcpup));
+		AuDebugOn(!hidden_parent || !hidden_parent->d_inode);
+		vfsub_i_lock_nested(hidden_parent->d_inode, AuLsc_I_PARENT);
+		err = lkup_neg(dentry, bcpup);
+		//err = -1;
+		vfsub_i_unlock(hidden_parent->d_inode);
+	}
+	dput(hidden_parent);
+
+	if (!add_entry)
+		di_write_unlock(parent);
+	if (do_lock_srcdir)
+		di_write_unlock(src_parent);
+	dput(src_parent);
+	if (!err)
+		err = bcpup; /* success */
+	//err = -EPERM;
+ out:
+	dput(parent);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
+{
+	int err, isdir;
+	aufs_bindex_t bstart, bcpup;
+	struct inode *h_inode, *inode, *dir, *h_dir, *gh_dir, *gdir;
+	struct dentry *h_dentry, *parent, *hi_wh;
+	unsigned int udba;
+
+	LKTRTrace("%.*s, ia_valid 0x%x\n", DLNPair(dentry), ia->ia_valid);
+	inode = dentry->d_inode;
+	IMustLock(inode);
+
+	aufs_read_lock(dentry, AuLock_DW);
+	bstart = dbstart(dentry);
+	bcpup = err = wr_dir(dentry, /*add*/0, /*src_dentry*/NULL,
+			     /*force_btgt*/-1, /*do_lock_srcdir*/0);
+	//err = -1;
+	if (unlikely(err < 0))
+		goto out;
+
+	/* crazy udba locks */
+	udba = au_flag_test_udba_inotify(dentry->d_sb);
+	parent = NULL;
+	gdir = gh_dir = dir = h_dir = NULL;
+	if ((udba || bstart != bcpup) && !IS_ROOT(dentry)) {
+		parent = dentry->d_parent; // dget_parent()
+		dir = parent->d_inode;
+		di_read_lock_parent(parent, AuLock_IR);
+		h_dir = au_h_iptr_i(dir, bcpup);
+	}
+	if (parent) {
+		if (unlikely(udba && !IS_ROOT(parent))) {
+			gdir = parent->d_parent->d_inode;  // dget_parent()
+			ii_read_lock_parent2(gdir);
+			gh_dir = au_h_iptr_i(gdir, bcpup);
+			hgdir_lock(gh_dir, gdir, bcpup);
+		}
+		hdir_lock(h_dir, dir, bcpup);
+	}
+
+	isdir = S_ISDIR(inode->i_mode);
+	h_dentry = au_h_dptr(dentry);
+	h_inode = h_dentry->d_inode;
+	AuDebugOn(!h_inode);
+
+#define HiLock(bindex) \
+	do { \
+		if (!isdir) \
+			vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD); \
+		else \
+			hdir2_lock(h_inode, inode, bindex); \
+	} while (0)
+#define HiUnlock(bindex) \
+	do { \
+		if (!isdir) \
+			vfsub_i_unlock(h_inode); \
+		else \
+			hdir_unlock(h_inode, inode, bindex); \
+	} while (0)
+
+	if (bstart != bcpup) {
+		loff_t size = -1;
+		unsigned int flags = au_flags_cpup(CPUP_DTIME, parent);
+
+		if ((ia->ia_valid & ATTR_SIZE)
+		    && ia->ia_size < i_size_read(inode)) {
+			size = ia->ia_size;
+			ia->ia_valid &= ~ATTR_SIZE;
+		}
+		hi_wh = NULL;
+		HiLock(bstart);
+		if (!d_unhashed(dentry))
+			err = sio_cpup_simple(dentry, bcpup, size, flags);
+		else {
+			hi_wh = au_hi_wh(inode, bcpup);
+			if (!hi_wh) {
+				err = sio_cpup_wh(dentry, bcpup, size,
+						  /*file*/NULL);
+				if (!err)
+					hi_wh = au_hi_wh(inode, bcpup);
+			}
+		}
+
+		//err = -1;
+		HiUnlock(bstart);
+		if (unlikely(err || !ia->ia_valid))
+			goto out_unlock;
+
+		if (!hi_wh)
+			h_dentry = au_h_dptr(dentry);
+		else
+			h_dentry = hi_wh; /* do not dget here */
+		h_inode = h_dentry->d_inode;
+		AuDebugOn(!h_inode);
+	}
+
+	HiLock(bcpup);
+	err = vfsub_notify_change(h_dentry, ia, need_dlgt(dentry->d_sb));
+	//err = -1;
+	if (!err)
+		au_cpup_attr_changeable(inode);
+	HiUnlock(bcpup);
+#undef HiLock
+#undef HiUnlock
+
+ out_unlock:
+	if (parent) {
+		hdir_unlock(h_dir, dir, bcpup);
+		di_read_unlock(parent, AuLock_IR);
+	}
+	if (unlikely(gdir)) {
+		hdir_unlock(gh_dir, gdir, bcpup);
+		ii_read_unlock(gdir);
+	}
+ out:
+	aufs_read_unlock(dentry, AuLock_DW);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int hidden_readlink(struct dentry *dentry, int bindex,
+			   char __user * buf, int bufsiz)
+{
+	struct super_block *sb;
+	struct dentry *hidden_dentry;
+
+	hidden_dentry = au_h_dptr_i(dentry, bindex);
+	if (unlikely(!hidden_dentry->d_inode->i_op
+		     || !hidden_dentry->d_inode->i_op->readlink))
+		return -EINVAL;
+
+	sb = dentry->d_sb;
+	if (!test_ro(sb, bindex, dentry->d_inode)) {
+		touch_atime(sbr_mnt(sb, bindex), hidden_dentry);
+		dentry->d_inode->i_atime = hidden_dentry->d_inode->i_atime;
+	}
+	return hidden_dentry->d_inode->i_op->readlink
+		(hidden_dentry, buf, bufsiz);
+}
+
+static int aufs_readlink(struct dentry *dentry, char __user * buf, int bufsiz)
+{
+	int err;
+
+	LKTRTrace("%.*s, %d\n", DLNPair(dentry), bufsiz);
+
+	aufs_read_lock(dentry, AuLock_IR);
+	err = hidden_readlink(dentry, dbstart(dentry), buf, bufsiz);
+	//err = -1;
+	aufs_read_unlock(dentry, AuLock_IR);
+	TraceErr(err);
+	return err;
+}
+
+static void *aufs_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+	int err;
+	char *buf;
+	mm_segment_t old_fs;
+
+	LKTRTrace("%.*s, nd %.*s\n", DLNPair(dentry), DLNPair(nd->dentry));
+
+	err = -ENOMEM;
+	buf = __getname();
+	//buf = NULL;
+	if (unlikely(!buf))
+		goto out;
+
+	aufs_read_lock(dentry, AuLock_IR);
+	old_fs = get_fs();
+	set_fs(KERNEL_DS);
+	err = hidden_readlink(dentry, dbstart(dentry), (char __user *)buf,
+			      PATH_MAX);
+	//err = -1;
+	set_fs(old_fs);
+	aufs_read_unlock(dentry, AuLock_IR);
+
+	if (err >= 0) {
+		buf[err] = 0;
+		/* will be freed by put_link */
+		nd_set_link(nd, buf);
+		return NULL; /* success */
+	}
+	__putname(buf);
+
+ out:
+	path_release(nd);
+	TraceErr(err);
+	return ERR_PTR(err);
+}
+
+static void aufs_put_link(struct dentry *dentry, struct nameidata *nd,
+			  void *cookie)
+{
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	__putname(nd_get_link(nd));
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct inode_operations aufs_symlink_iop = {
+	.permission	= aufs_permission,
+	.setattr	= aufs_setattr,
+
+	.readlink	= aufs_readlink,
+	.follow_link	= aufs_follow_link,
+	.put_link	= aufs_put_link
+};
+
+struct inode_operations aufs_dir_iop = {
+	.create		= aufs_create,
+	.lookup		= aufs_lookup,
+	.link		= aufs_link,
+	.unlink		= aufs_unlink,
+	.symlink	= aufs_symlink,
+	.mkdir		= aufs_mkdir,
+	.rmdir		= aufs_rmdir,
+	.mknod		= aufs_mknod,
+	.rename		= aufs_rename,
+
+	.permission	= aufs_permission,
+	.setattr	= aufs_setattr,
+
+#if 0 // xattr
+	.setxattr	= aufs_setxattr,
+	.getxattr	= aufs_getxattr,
+	.listxattr	= aufs_listxattr,
+	.removexattr	= aufs_removexattr
+#endif
+};
+
+struct inode_operations aufs_iop = {
+	.permission	= aufs_permission,
+	.setattr	= aufs_setattr,
+
+#if 0 // xattr
+	.setxattr	= aufs_setxattr,
+	.getxattr	= aufs_getxattr,
+	.listxattr	= aufs_listxattr,
+	.removexattr	= aufs_removexattr
+#endif
+};
diff -ruN linux-2.6.22/fs/aufs/i_op_add.c linux-2.6.22-aufs/fs/aufs/i_op_add.c
--- linux-2.6.22/fs/aufs/i_op_add.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/i_op_add.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: i_op_add.c,v 1.44 2007/07/15 20:03:45 sfjro Exp $ */
+
+#include "aufs.h"
+
+/*
+ * final procedure of adding a new entry, except link(2).
+ * remove whiteout, instantiate, copyup the parent dir's times and size
+ * and update version.
+ * if it failed, re-create the removed whiteout.
+ */
+static int epilog(struct dentry *wh_dentry, struct dentry *dentry)
+{
+	int err, rerr;
+	aufs_bindex_t bwh;
+	struct inode *inode, *dir;
+	struct dentry *wh;
+	struct lkup_args lkup;
+
+	LKTRTrace("wh %p, %.*s\n", wh_dentry, DLNPair(dentry));
+
+	lkup.dlgt = need_dlgt(dentry->d_sb);
+	bwh = -1;
+	if (wh_dentry) {
+		dir = wh_dentry->d_parent->d_inode;
+		IMustLock(dir);
+		bwh = dbwh(dentry);
+		err = au_unlink_wh_dentry(dir, wh_dentry, dentry, lkup.dlgt);
+		//err = -1;
+		if (unlikely(err))
+			goto out;
+	}
+
+	inode = au_new_inode(dentry);
+	//inode = ERR_PTR(-1);
+	if (!IS_ERR(inode)) {
+		d_instantiate(dentry, inode);
+		dir = dentry->d_parent->d_inode;
+		IMustLock(dir);
+		/* or always cpup dir mtime? */
+		if (ibstart(dir) == dbstart(dentry))
+			au_cpup_attr_timesizes(dir);
+		dir->i_version++;
+		return 0; /* success */
+	}
+
+	err = PTR_ERR(inode);
+	if (!wh_dentry)
+		goto out;
+
+	/* revert */
+	lkup.nfsmnt = au_nfsmnt(dentry->d_sb, bwh);
+	wh = simple_create_wh(dentry, bwh, wh_dentry->d_parent, &lkup);
+	//wh = ERR_PTR(-1);
+	rerr = PTR_ERR(wh);
+	if (!IS_ERR(wh)) {
+		dput(wh);
+		goto out;
+	}
+	IOErr("%.*s reverting whiteout failed(%d, %d)\n",
+	      DLNPair(dentry), err, rerr);
+	err = -EIO;
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * initial procedure of adding a new entry.
+ * prepare writable branch and the parent dir, lock it,
+ * lookup whiteout for the new entry.
+ */
+static struct dentry *
+lock_hdir_lkup_wh(struct dentry *dentry, struct dtime *dt,
+		  struct dentry *src_dentry, int do_lock_srcdir)
+{
+	struct dentry *wh_dentry, *parent, *hidden_parent;
+	int err;
+	aufs_bindex_t bstart, bcpup;
+	struct inode *dir, *h_dir;
+	struct lkup_args lkup;
+
+	LKTRTrace("%.*s, src %p\n", DLNPair(dentry), src_dentry);
+
+	parent = dentry->d_parent;
+	IMustLock(parent->d_inode);
+	bstart = dbstart(dentry);
+	bcpup = err = wr_dir(dentry, 1, src_dentry, -1, do_lock_srcdir);
+	//err = -1;
+	wh_dentry = ERR_PTR(err);
+	if (unlikely(err < 0))
+		goto out;
+
+	dir = parent->d_inode;
+	hidden_parent = au_h_dptr_i(parent, bcpup);
+	h_dir = hidden_parent->d_inode;
+	hdir_lock(h_dir, dir, bcpup);
+	if (dt)
+		dtime_store(dt, parent, hidden_parent);
+	wh_dentry = NULL;
+	if (/* bcpup != bstart || */ bcpup != dbwh(dentry))
+		goto out; /* success */
+
+	lkup.nfsmnt = au_nfsmnt(parent->d_sb, bcpup);
+	lkup.dlgt = need_dlgt(parent->d_sb);
+	wh_dentry = lkup_wh(hidden_parent, &dentry->d_name, &lkup);
+	//wh_dentry = ERR_PTR(-1);
+	if (IS_ERR(wh_dentry))
+		hdir_unlock(h_dir, dir, bcpup);
+
+ out:
+	TraceErrPtr(wh_dentry);
+	return wh_dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+enum {Mknod, Symlink, Creat};
+struct simple_arg {
+	int type;
+	union {
+		struct {
+			int mode;
+			struct nameidata *nd;
+		} c;
+		struct {
+			const char *symname;
+		} s;
+		struct {
+			int mode;
+			dev_t dev;
+		} m;
+	} u;
+};
+
+static int add_simple(struct inode *dir, struct dentry *dentry,
+		      struct simple_arg *arg)
+{
+	int err, dlgt;
+	struct dentry *hidden_dentry, *hidden_parent, *wh_dentry, *parent;
+	struct inode *hidden_dir;
+	struct dtime dt;
+
+	LKTRTrace("type %d, %.*s\n", arg->type, DLNPair(dentry));
+	IMustLock(dir);
+
+	aufs_read_lock(dentry, AuLock_DW);
+	parent = dentry->d_parent;
+	di_write_lock_parent(parent);
+	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL,
+				      /*do_lock_srcdir*/0);
+	//wh_dentry = ERR_PTR(-1);
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	hidden_dentry = au_h_dptr(dentry);
+	hidden_parent = hidden_dentry->d_parent;
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+	dlgt = need_dlgt(dir->i_sb);
+
+#if 1 // partial testing
+	switch (arg->type) {
+	case Creat:
+#if 0
+		if (arg->u.c.nd) {
+			struct nameidata fake_nd;
+			fake_nd = *arg->u.c.nd;
+			fake_nd.dentry = dget(hidden_parent);
+			fake_nd.mnt = sbr_mnt(dentry->d_sb, dbstart(dentry));
+			mntget(fake_nd.mnt);
+			err = vfsub_create(hidden_dir, hidden_dentry,
+					   arg->u.c.mode, &fake_nd, dlgt);
+			path_release(&fake_nd);
+		} else
+#endif
+			err = vfsub_create(hidden_dir, hidden_dentry,
+					   arg->u.c.mode, NULL, dlgt);
+		break;
+	case Symlink:
+		err = vfsub_symlink(hidden_dir, hidden_dentry,
+				    arg->u.s.symname, S_IALLUGO, dlgt);
+		break;
+	case Mknod:
+		err = vfsub_mknod(hidden_dir, hidden_dentry,
+				  arg->u.m.mode, arg->u.m.dev, dlgt);
+		break;
+	default:
+		BUG();
+	}
+#else
+	err = -1;
+#endif
+	if (!err)
+		err = epilog(wh_dentry, dentry);
+	//err = -1;
+
+	/* revert */
+	if (unlikely(err && hidden_dentry->d_inode)) {
+		int rerr;
+		rerr = vfsub_unlink(hidden_dir, hidden_dentry, dlgt);
+		//rerr = -1;
+		if (rerr) {
+			IOErr("%.*s revert failure(%d, %d)\n",
+			      DLNPair(dentry), err, rerr);
+			err = -EIO;
+		}
+		dtime_revert(&dt, !CPUP_LOCKED_GHDIR);
+		d_drop(dentry);
+	}
+
+	hdir_unlock(hidden_dir, dir, dbstart(dentry));
+	dput(wh_dentry);
+
+ out:
+	if (unlikely(err)) {
+		au_update_dbstart(dentry);
+		d_drop(dentry);
+	}
+	di_write_unlock(parent);
+	aufs_read_unlock(dentry, AuLock_DW);
+	TraceErr(err);
+	return err;
+}
+
+int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
+{
+	struct simple_arg arg = {
+		.type = Mknod,
+		.u.m = {.mode = mode, .dev = dev}
+	};
+	return add_simple(dir, dentry, &arg);
+}
+
+int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
+{
+	struct simple_arg arg = {
+		.type = Symlink,
+		.u.s.symname = symname
+	};
+	return add_simple(dir, dentry, &arg);
+}
+
+int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
+		struct nameidata *nd)
+{
+	struct simple_arg arg = {
+		.type = Creat,
+		.u.c = {.mode = mode, .nd = nd}
+	};
+	return add_simple(dir, dentry, &arg);
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct link_arg {
+	aufs_bindex_t bdst, bsrc;
+	int issamedir, dlgt;
+	struct dentry *src_parent, *parent, *hidden_dentry;
+	struct inode *hidden_dir, *inode;
+};
+
+static int cpup_before_link(struct dentry *src_dentry, struct inode *dir,
+			    struct link_arg *a)
+{
+	int err;
+	unsigned int flags;
+	struct inode *hi, *hdir = NULL, *src_dir;
+
+	TraceEnter();
+
+	err = 0;
+	flags = au_flags_cpup(CPUP_DTIME, a->parent);
+	src_dir = a->src_parent->d_inode;
+	if (!a->issamedir) {
+		// todo: dead lock?
+		di_read_lock_parent2(a->src_parent, AuLock_IR);
+		/* this temporary unlock/lock is safe */
+		hdir_unlock(a->hidden_dir, dir, a->bdst);
+		err = test_and_cpup_dirs(src_dentry, a->bdst, a->parent);
+		//err = -1;
+		if (!err) {
+			hdir = au_h_iptr_i(src_dir, a->bdst);
+			hdir_lock(hdir, src_dir, a->bdst);
+			flags = au_flags_cpup(CPUP_DTIME, a->src_parent);
+		}
+	}
+
+	if (!err) {
+		hi = au_h_dptr(src_dentry)->d_inode;
+		vfsub_i_lock_nested(hi, AuLsc_I_CHILD);
+		err = sio_cpup_simple(src_dentry, a->bdst, -1, flags);
+		//err = -1;
+		vfsub_i_unlock(hi);
+	}
+
+	if (!a->issamedir) {
+		if (hdir)
+			hdir_unlock(hdir, src_dir, a->bdst);
+		hdir_lock(a->hidden_dir, dir, a->bdst);
+		di_read_unlock(a->src_parent, AuLock_IR);
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+static int cpup_or_link(struct dentry *src_dentry, struct link_arg *a)
+{
+	int err;
+	struct inode *inode, *h_inode, *h_dst_inode;
+	struct dentry *h_dentry;
+	aufs_bindex_t bstart;
+	struct super_block *sb;
+
+	TraceEnter();
+
+	sb = src_dentry->d_sb;
+	inode = src_dentry->d_inode;
+	h_dentry = au_h_dptr(src_dentry);
+	h_inode = h_dentry->d_inode;
+	bstart = ibstart(inode);
+	h_dst_inode = NULL;
+	if (bstart <= a->bdst)
+		h_dst_inode = au_h_iptr_i(inode, a->bdst);
+
+	if (!h_dst_inode || !h_dst_inode->i_nlink) {
+		/* copyup src_dentry as the name of dentry. */
+		set_dbstart(src_dentry, a->bdst);
+		set_h_dptr(src_dentry, a->bdst, dget(a->hidden_dentry));
+		vfsub_i_lock_nested(h_inode, AuLsc_I_CHILD);
+		err = sio_cpup_single(src_dentry, a->bdst, a->bsrc, -1,
+				      au_flags_cpup(!CPUP_DTIME, a->parent));
+		//err = -1;
+		vfsub_i_unlock(h_inode);
+		set_h_dptr(src_dentry, a->bdst, NULL);
+		set_dbstart(src_dentry, a->bsrc);
+	} else {
+		/* the inode of src_dentry already exists on a.bdst branch */
+		h_dentry = d_find_alias(h_dst_inode);
+		if (h_dentry) {
+			err = vfsub_link(h_dentry, a->hidden_dir,
+					 a->hidden_dentry, a->dlgt);
+			dput(h_dentry);
+		} else {
+			IOErr("no dentry found for i%lu on b%d\n",
+			      h_dst_inode->i_ino, a->bdst);
+			err = -EIO;
+		}
+	}
+
+	if (!err)
+		append_plink(sb, a->inode, a->hidden_dentry, a->bdst);
+
+	TraceErr(err);
+	return err;
+}
+
+int aufs_link(struct dentry *src_dentry, struct inode *dir,
+	      struct dentry *dentry)
+{
+	int err, rerr;
+	struct dentry *hidden_parent, *wh_dentry, *hidden_src_dentry;
+	struct dtime dt;
+	struct link_arg a;
+	struct super_block *sb;
+
+	LKTRTrace("src %.*s, i%lu, dst %.*s\n",
+		  DLNPair(src_dentry), dir->i_ino, DLNPair(dentry));
+	IMustLock(dir);
+	IMustLock(src_dentry->d_inode);
+	AuDebugOn(S_ISDIR(src_dentry->d_inode->i_mode));
+
+	aufs_read_and_write_lock2(dentry, src_dentry, /*flags*/0);
+	a.src_parent = dget_parent(src_dentry);
+	a.parent = dentry->d_parent;
+	a.issamedir = (a.src_parent == a.parent);
+	di_write_lock_parent(a.parent);
+	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, !a.issamedir);
+	//wh_dentry = ERR_PTR(-1);
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	a.inode = src_dentry->d_inode;
+	a.hidden_dentry = au_h_dptr(dentry);
+	hidden_parent = a.hidden_dentry->d_parent;
+	a.hidden_dir = hidden_parent->d_inode;
+	IMustLock(a.hidden_dir);
+
+	err = 0;
+	sb = dentry->d_sb;
+	a.dlgt = need_dlgt(sb);
+
+	//todo: minor optimize, their sb may be same while their bindex differs.
+	a.bsrc = dbstart(src_dentry);
+	a.bdst = dbstart(dentry);
+	hidden_src_dentry = au_h_dptr(src_dentry);
+	if (unlikely(!au_flag_test(sb, AuFlag_PLINK))) {
+		/*
+		 * copyup src_dentry to the branch we process,
+		 * and then link(2) to it.
+		 * gave up 'pseudo link by cpup' approach,
+		 * since nlink may be one and some applications will not work.
+		 */
+		if (a.bdst < a.bsrc
+		    /* && hidden_src_dentry->d_sb != a.hidden_dentry->d_sb */)
+			err = cpup_before_link(src_dentry, dir, &a);
+		if (!err) {
+			hidden_src_dentry = au_h_dptr(src_dentry);
+			err = vfsub_link(hidden_src_dentry, a.hidden_dir,
+					 a.hidden_dentry, a.dlgt);
+			//err = -1;
+		}
+	} else {
+		if (a.bdst < a.bsrc
+		    /* && hidden_src_dentry->d_sb != a.hidden_dentry->d_sb */)
+			err = cpup_or_link(src_dentry, &a);
+		else {
+			hidden_src_dentry = au_h_dptr(src_dentry);
+			err = vfsub_link(hidden_src_dentry, a.hidden_dir,
+					 a.hidden_dentry, a.dlgt);
+			//err = -1;
+		}
+	}
+	if (unlikely(err))
+		goto out_unlock;
+	if (wh_dentry) {
+		err = au_unlink_wh_dentry(a.hidden_dir, wh_dentry, dentry,
+					  a.dlgt);
+		//err = -1;
+		if (unlikely(err))
+			goto out_revert;
+	}
+
+	dir->i_version++;
+	if (ibstart(dir) == dbstart(dentry))
+		au_cpup_attr_timesizes(dir);
+	if (!d_unhashed(a.hidden_dentry)
+	    /* || hidden_old_inode->i_nlink <= nlink */
+	    /* || SB_NFS(hidden_src_dentry->d_sb) */) {
+		dentry->d_inode = igrab(a.inode);
+		d_instantiate(dentry, a.inode);
+		a.inode->i_nlink++;
+		a.inode->i_ctime = dir->i_ctime;
+	} else
+		/* nfs case (< 2.6.15) */
+		d_drop(dentry);
+#if 0
+	au_debug_on();
+	DbgInode(a.inode);
+	au_debug_off();
+	{
+		aufs_bindex_t i;
+		for (i = ibstart(a.inode); i <= ibend(a.inode); i++) {
+			struct xino xino;
+			struct inode *hi;
+			hi = au_h_iptr_i(a.inode, i);
+			if (hi) {
+				xino_read(sb, i, hi->i_ino, &xino);
+				Dbg("hi%lu, i%lu\n", hi->i_ino, xino.ino);
+			}
+		}
+	}
+#endif
+	goto out_unlock; /* success */
+
+ out_revert:
+#if 0 // remove
+	if (d_unhashed(a.hidden_dentry)) {
+		/* hardlink on nfs (< 2.6.15) */
+		struct dentry *d;
+		const struct qstr *name = &a.hidden_dentry->d_name;
+		AuDebugOn(a.hidden_dentry->d_parent->d_inode != a.hidden_dir);
+		/* do not superio. */
+		d = lkup_one(name->name, a.hidden_dentry->d_parent, name->len,
+			     au_nfsmnt(sb, a.bdst)??, need_dlgt(sb));
+		rerr = PTR_ERR(d);
+		if (IS_ERR(d))
+			goto out_rerr;
+		dput(a.hidden_dentry);
+		a.hidden_dentry = d;
+		AuDebugOn(!d->d_inode);
+	}
+#endif
+	rerr = vfsub_unlink(a.hidden_dir, a.hidden_dentry, a.dlgt);
+	//rerr = -1;
+	if (!rerr)
+		goto out_dt;
+// out_rerr:
+	IOErr("%.*s reverting failed(%d, %d)\n", DLNPair(dentry), err, rerr);
+	err = -EIO;
+ out_dt:
+	d_drop(dentry);
+	dtime_revert(&dt, !CPUP_LOCKED_GHDIR);
+ out_unlock:
+	hdir_unlock(a.hidden_dir, dir, a.bdst);
+	dput(wh_dentry);
+ out:
+	if (unlikely(err)) {
+		au_update_dbstart(dentry);
+		d_drop(dentry);
+	}
+	di_write_unlock(a.parent);
+	dput(a.src_parent);
+	aufs_read_and_write_unlock2(dentry, src_dentry);
+	TraceErr(err);
+	return err;
+}
+
+int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	int err, rerr, diropq, dlgt;
+	struct dentry *hidden_dentry, *hidden_parent, *wh_dentry, *parent,
+		*opq_dentry;
+	struct inode *hidden_dir, *hidden_inode;
+	struct dtime dt;
+	aufs_bindex_t bindex;
+	struct super_block *sb;
+
+	LKTRTrace("i%lu, %.*s, mode 0%o\n", dir->i_ino, DLNPair(dentry), mode);
+	IMustLock(dir);
+
+	aufs_read_lock(dentry, AuLock_DW);
+	parent = dentry->d_parent;
+	di_write_lock_parent(parent);
+	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL,
+				      /*do_lock_srcdir*/0);
+	//wh_dentry = ERR_PTR(-1);
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	sb = dentry->d_sb;
+	bindex = dbstart(dentry);
+	hidden_dentry = au_h_dptr(dentry);
+	hidden_parent = hidden_dentry->d_parent;
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+	dlgt = need_dlgt(sb);
+
+	err = vfsub_mkdir(hidden_dir, hidden_dentry, mode, dlgt);
+	//err = -1;
+	if (unlikely(err))
+		goto out_unlock;
+	hidden_inode = hidden_dentry->d_inode;
+
+	/* make the dir opaque */
+	diropq = 0;
+	if (unlikely(wh_dentry || au_flag_test(sb, AuFlag_ALWAYS_DIROPQ))) {
+		vfsub_i_lock_nested(hidden_inode, AuLsc_I_CHILD);
+		opq_dentry = create_diropq(dentry, bindex, dlgt);
+		//opq_dentry = ERR_PTR(-1);
+		vfsub_i_unlock(hidden_inode);
+		err = PTR_ERR(opq_dentry);
+		if (IS_ERR(opq_dentry))
+			goto out_dir;
+		dput(opq_dentry);
+		diropq = 1;
+	}
+
+	err = epilog(wh_dentry, dentry);
+	//err = -1;
+	if (!err) {
+		dir->i_nlink++;
+		goto out_unlock; /* success */
+	}
+
+	/* revert */
+	if (unlikely(diropq)) {
+		LKTRLabel(revert opq);
+		vfsub_i_lock_nested(hidden_inode, AuLsc_I_CHILD);
+		rerr = remove_diropq(dentry, bindex, dlgt);
+		//rerr = -1;
+		vfsub_i_unlock(hidden_inode);
+		if (rerr) {
+			IOErr("%.*s reverting diropq failed(%d, %d)\n",
+			      DLNPair(dentry), err, rerr);
+			err = -EIO;
+		}
+	}
+
+ out_dir:
+	LKTRLabel(revert dir);
+	rerr = vfsub_rmdir(hidden_dir, hidden_dentry, dlgt);
+	//rerr = -1;
+	if (rerr) {
+		IOErr("%.*s reverting dir failed(%d, %d)\n",
+		      DLNPair(dentry), err, rerr);
+		err = -EIO;
+	}
+	d_drop(dentry);
+	dtime_revert(&dt, /*fake flag*/CPUP_LOCKED_GHDIR);
+ out_unlock:
+	hdir_unlock(hidden_dir, dir, bindex);
+	dput(wh_dentry);
+ out:
+	if (unlikely(err)) {
+		au_update_dbstart(dentry);
+		d_drop(dentry);
+	}
+	di_write_unlock(parent);
+	aufs_read_unlock(dentry, AuLock_DW);
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/i_op_del.c linux-2.6.22-aufs/fs/aufs/i_op_del.c
--- linux-2.6.22/fs/aufs/i_op_del.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/i_op_del.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: i_op_del.c,v 1.44 2007/07/15 20:04:44 sfjro Exp $ */
+
+#include "aufs.h"
+
+/* returns,
+ * 0: wh is unnecessary
+ * plus: wh is necessary
+ * minus: error
+ */
+int wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup,
+		   struct dentry *locked)
+{
+	int need_wh, err;
+	aufs_bindex_t bstart;
+	struct dentry *hidden_dentry;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, isdir %d, *bcpup %d, locked %p\n",
+		  DLNPair(dentry), isdir, *bcpup, locked);
+	sb = dentry->d_sb;
+
+	bstart = dbstart(dentry);
+	LKTRTrace("bcpup %d, bstart %d\n", *bcpup, bstart);
+	hidden_dentry = au_h_dptr(dentry);
+	if (*bcpup < 0) {
+		*bcpup = bstart;
+		if (test_ro(sb, bstart, dentry->d_inode)) {
+			*bcpup = err = find_rw_parent_br(dentry, bstart);
+			//*bcpup = err = find_rw_br(sb, bstart);
+			//err = -1;
+			if (unlikely(err < 0))
+				goto out;
+		}
+	} else
+		AuDebugOn(bstart < *bcpup
+			  || test_ro(sb, *bcpup, dentry->d_inode));
+	LKTRTrace("bcpup %d, bstart %d\n", *bcpup, bstart);
+
+	if (*bcpup != bstart) {
+		err = cpup_dirs(dentry, *bcpup, locked);
+		//err = -1;
+		if (unlikely(err))
+			goto out;
+		need_wh = 1;
+	} else {
+		//struct nameidata nd;
+		aufs_bindex_t old_bend, new_bend, bdiropq = -1;
+		old_bend = dbend(dentry);
+		if (isdir) {
+			bdiropq = dbdiropq(dentry);
+			set_dbdiropq(dentry, -1);
+		}
+		err = need_wh = lkup_dentry(dentry, bstart + 1, /*type*/0);
+		//err = -1;
+		if (isdir)
+			set_dbdiropq(dentry, bdiropq);
+		if (unlikely(err < 0))
+			goto out;
+		new_bend = dbend(dentry);
+		if (!need_wh && old_bend != new_bend) {
+			set_h_dptr(dentry, new_bend, NULL);
+			set_dbend(dentry, old_bend);
+#if 0
+		} else if (!au_h_dptr_i(dentry, new_bend)->d_inode) {
+			LKTRTrace("negative\n");
+			set_h_dptr(dentry, new_bend, NULL);
+			set_dbend(dentry, old_bend);
+			need_wh = 0;
+#endif
+		}
+	}
+	LKTRTrace("need_wh %d\n", need_wh);
+	err = need_wh;
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static struct dentry *
+lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup,
+		    struct dtime *dt)
+{
+	struct dentry *wh_dentry;
+	int err, need_wh;
+	struct dentry *hidden_parent, *parent;
+	struct inode *dir, *h_dir;
+	struct lkup_args lkup;
+
+	LKTRTrace("%.*s, isdir %d\n", DLNPair(dentry), isdir);
+
+	err = need_wh = wr_dir_need_wh(dentry, isdir, bcpup, NULL);
+	//err = -1;
+	wh_dentry = ERR_PTR(err);
+	if (unlikely(err < 0))
+		goto out;
+
+	parent = dentry->d_parent;
+	dir = parent->d_inode;
+	IMustLock(dir);
+	hidden_parent = au_h_dptr_i(parent, *bcpup);
+	h_dir = hidden_parent->d_inode;
+	hdir_lock(h_dir, dir, *bcpup);
+	dtime_store(dt, parent, hidden_parent);
+	wh_dentry = NULL;
+	if (!need_wh)
+		goto out; /* success, no need to create whiteout */
+
+	lkup.nfsmnt = au_nfsmnt(dentry->d_sb, *bcpup);
+	lkup.dlgt = need_dlgt(dentry->d_sb);
+	wh_dentry = simple_create_wh(dentry, *bcpup, hidden_parent, &lkup);
+	//wh_dentry = ERR_PTR(-1);
+	if (!IS_ERR(wh_dentry))
+		goto out; /* success */
+	/* returns with the parent is locked and wh_dentry is DGETed */
+
+	hdir_unlock(h_dir, dir, *bcpup);
+
+ out:
+	TraceErrPtr(wh_dentry);
+	return wh_dentry;
+}
+
+static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex,
+			   struct aufs_nhash *whlist, struct inode *dir)
+{
+	int rmdir_later, err;
+	struct dentry *hidden_dentry;
+	struct inode *inode, *h_inode;
+
+	LKTRTrace("%.*s, b%d\n", DLNPair(dentry), bindex);
+
+	inode = h_inode = NULL;
+	if (unlikely(au_flag_test_udba_inotify(dentry->d_sb))) {
+		inode = dentry->d_inode;
+		h_inode = au_h_iptr_i(inode, bindex);
+		hdir2_lock(h_inode, inode, bindex);
+	}
+	err = rename_whtmp(dentry, bindex);
+	if (unlikely(inode))
+		hdir_unlock(h_inode, inode, bindex);
+	//err = -1;
+	if (unlikely(err))
+		goto out;
+
+	hidden_dentry = au_h_dptr_i(dentry, bindex);
+	if (!au_is_nfs(hidden_dentry->d_sb)) {
+		const int dirwh = stosi(dentry->d_sb)->si_dirwh;
+		rmdir_later = (dirwh <= 1);
+		if (!rmdir_later)
+			rmdir_later = is_longer_wh(whlist, bindex, dirwh);
+		if (rmdir_later)
+			return rmdir_later;
+	}
+
+	err = rmdir_whtmp(hidden_dentry, whlist, bindex, dir, dentry->d_inode);
+	//err = -1;
+	if (unlikely(err)) {
+		IOErr("rmdir %.*s, b%d failed, %d. ignored\n",
+		      DLNPair(hidden_dentry), bindex, err);
+		err = 0;
+	}
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static void epilog(struct inode *dir, struct dentry *dentry,
+		   aufs_bindex_t bindex)
+{
+	//todo: unnecessary?
+	d_drop(dentry);
+	dentry->d_inode->i_ctime = dir->i_ctime;
+
+	if (atomic_read(&dentry->d_count) == 1) {
+		set_h_dptr(dentry, dbstart(dentry), NULL);
+		au_update_dbstart(dentry);
+	}
+	if (ibstart(dir) == bindex)
+		au_cpup_attr_timesizes(dir);
+	dir->i_version++;
+}
+
+static int do_revert(int err, struct dentry *wh_dentry, struct dentry *dentry,
+		     aufs_bindex_t bwh, struct dtime *dt, int dlgt)
+{
+	int rerr;
+	struct inode *dir;
+
+	dir = wh_dentry->d_parent->d_inode;
+	IMustLock(dir);
+	rerr = au_unlink_wh_dentry(dir, wh_dentry, dentry, dlgt);
+	//rerr = -1;
+	if (!rerr) {
+		set_dbwh(dentry, bwh);
+		dtime_revert(dt, !CPUP_LOCKED_GHDIR);
+		return 0;
+	}
+
+	IOErr("%.*s reverting whiteout failed(%d, %d)\n",
+	      DLNPair(dentry), err, rerr);
+	return -EIO;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int aufs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	int err, dlgt;
+	struct inode *inode, *hidden_dir;
+	struct dentry *parent, *wh_dentry, *hidden_dentry;
+	struct dtime dt;
+	aufs_bindex_t bwh, bindex, bstart;
+	struct super_block *sb;
+
+	LKTRTrace("i%lu, %.*s\n", dir->i_ino, DLNPair(dentry));
+	IMustLock(dir);
+	inode = dentry->d_inode;
+	if (unlikely(!inode))
+		return -ENOENT; /* possible? */
+	IMustLock(inode);
+
+	aufs_read_lock(dentry, AuLock_DW);
+	parent = dentry->d_parent;
+	di_write_lock_parent(parent);
+
+	bstart = dbstart(dentry);
+	bwh = dbwh(dentry);
+	bindex = -1;
+	wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt);
+	//wh_dentry = ERR_PTR(-1);
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	sb = dir->i_sb;
+	dlgt = need_dlgt(sb);
+	hidden_dentry = au_h_dptr(dentry);
+	dget(hidden_dentry);
+
+	if (bindex == bstart) {
+		hidden_dir = hidden_dentry->d_parent->d_inode;
+		IMustLock(hidden_dir);
+		err = vfsub_unlink(hidden_dir, hidden_dentry, dlgt);
+		//err = -1;
+	} else {
+		AuDebugOn(!wh_dentry
+			  || wh_dentry->d_parent != au_h_dptr_i(parent,
+								bindex));
+		hidden_dir = wh_dentry->d_parent->d_inode;
+		IMustLock(hidden_dir);
+		err = 0;
+	}
+
+	if (!err) {
+		inode->i_nlink--;
+#if 0
+		//todo: update plink
+		if (unlikely(!inode->i_nlink
+			     && au_is_plinked(sb, inode)
+			     /* && atomic_read(&inode->i_count) == 2) */)) {
+			au_debug_on();
+			DbgInode(inode);
+			au_debug_off();
+		}
+#endif
+		epilog(dir, dentry, bindex);
+		goto out_unlock; /* success */
+	}
+
+	/* revert */
+	if (wh_dentry) {
+		int rerr;
+		rerr = do_revert(err, wh_dentry, dentry, bwh, &dt, dlgt);
+		if (rerr)
+			err = rerr;
+	}
+
+ out_unlock:
+	hdir_unlock(hidden_dir, dir, bindex);
+	dput(wh_dentry);
+	dput(hidden_dentry);
+ out:
+	di_write_unlock(parent);
+	aufs_read_unlock(dentry, AuLock_DW);
+	TraceErr(err);
+	return err;
+}
+
+int aufs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	int err, rmdir_later;
+	struct inode *inode, *hidden_dir;
+	struct dentry *parent, *wh_dentry, *hidden_dentry;
+	struct dtime dt;
+	aufs_bindex_t bwh, bindex, bstart;
+	struct rmdir_whtmp_args *args;
+	struct aufs_nhash *whlist;
+	struct super_block *sb;
+
+	LKTRTrace("i%lu, %.*s\n", dir->i_ino, DLNPair(dentry));
+	IMustLock(dir);
+	inode = dentry->d_inode;
+	if (unlikely(!inode))
+		return -ENOENT; /* possible? */
+	IMustLock(inode);
+
+	whlist = nhash_new(GFP_KERNEL);
+	err = PTR_ERR(whlist);
+	if (IS_ERR(whlist))
+		goto out;
+
+	err = -ENOMEM;
+	args = kmalloc(sizeof(*args), GFP_KERNEL);
+	//args = NULL;
+	if (unlikely(!args))
+		goto out_whlist;
+
+	aufs_read_lock(dentry, AuLock_DW);
+	parent = dentry->d_parent;
+	di_write_lock_parent(parent);
+	err = test_empty(dentry, whlist);
+	//err = -1;
+	if (unlikely(err))
+		goto out_args;
+
+	bstart = dbstart(dentry);
+	bwh = dbwh(dentry);
+	bindex = -1;
+	wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/ 1, &bindex, &dt);
+	//wh_dentry = ERR_PTR(-1);
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out_args;
+
+	hidden_dentry = au_h_dptr(dentry);
+	dget(hidden_dentry);
+
+	rmdir_later = 0;
+	if (bindex == bstart) {
+		hidden_dir = hidden_dentry->d_parent->d_inode;
+		IMustLock(hidden_dir);
+		err = renwh_and_rmdir(dentry, bstart, whlist, dir);
+		//err = -1;
+		if (err > 0) {
+			rmdir_later = err;
+			err = 0;
+		}
+	} else {
+		AuDebugOn(!wh_dentry
+			  || wh_dentry->d_parent != au_h_dptr_i(parent,
+								bindex));
+		hidden_dir = wh_dentry->d_parent->d_inode;
+		IMustLock(hidden_dir);
+		err = 0;
+	}
+
+	sb = dentry->d_sb;
+	if (!err) {
+		//aufs_bindex_t bi, bend;
+
+		au_reset_hinotify(inode, /*flags*/0);
+		inode->i_nlink = 0;
+		set_dbdiropq(dentry, -1);
+		epilog(dir, dentry, bindex);
+
+		if (rmdir_later) {
+			kick_rmdir_whtmp(hidden_dentry, whlist, bstart, dir,
+					 inode, args);
+			args = NULL;
+		}
+
+		goto out_unlock; /* success */
+	}
+
+	/* revert */
+	LKTRLabel(revert);
+	if (wh_dentry) {
+		int rerr;
+		rerr = do_revert(err, wh_dentry, dentry, bwh, &dt,
+				 need_dlgt(sb));
+		if (rerr)
+			err = rerr;
+	}
+
+ out_unlock:
+	hdir_unlock(hidden_dir, dir, bindex);
+	dput(wh_dentry);
+	dput(hidden_dentry);
+ out_args:
+	di_write_unlock(parent);
+	aufs_read_unlock(dentry, AuLock_DW);
+	kfree(args);
+ out_whlist:
+	nhash_del(whlist);
+ out:
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/i_op_ren.c linux-2.6.22-aufs/fs/aufs/i_op_ren.c
--- linux-2.6.22/fs/aufs/i_op_ren.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/i_op_ren.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: i_op_ren.c,v 1.48 2007/07/15 20:02:36 sfjro Exp $ */
+
+#include "aufs.h"
+
+enum {SRC, DST};
+struct rename_args {
+	struct dentry *hidden_dentry[2], *parent[2], *hidden_parent[2];
+	struct aufs_nhash whlist;
+	aufs_bindex_t btgt, bstart[2];
+	struct super_block *sb;
+
+	unsigned int isdir:1;
+	unsigned int issamedir:1;
+	unsigned int whsrc:1;
+	unsigned int whdst:1;
+	unsigned int dlgt:1;
+} __attribute__((aligned(sizeof(long))));
+
+static int do_rename(struct inode *src_dir, struct dentry *src_dentry,
+		     struct inode *dir, struct dentry *dentry,
+		     struct rename_args *a)
+{
+	int err, need_diropq, bycpup, rerr;
+	struct rmdir_whtmp_args *thargs;
+	struct dentry *wh_dentry[2], *hidden_dst, *hg_parent;
+	struct inode *hidden_dir[2];
+	aufs_bindex_t bindex, bend;
+	unsigned int flags;
+	struct lkup_args lkup = {.dlgt = a->dlgt};
+
+	LKTRTrace("%.*s/%.*s, %.*s/%.*s, "
+		  "hd{%p, %p}, hp{%p, %p}, wh %p, btgt %d, bstart{%d, %d}, "
+		  "flags{%d, %d, %d, %d}\n",
+		  DLNPair(a->parent[SRC]), DLNPair(src_dentry),
+		  DLNPair(a->parent[DST]), DLNPair(dentry),
+		  a->hidden_dentry[SRC], a->hidden_dentry[DST],
+		  a->hidden_parent[SRC], a->hidden_parent[DST],
+		  &a->whlist, a->btgt,
+		  a->bstart[SRC], a->bstart[DST],
+		  a->isdir, a->issamedir, a->whsrc, a->whdst);
+	hidden_dir[SRC] = a->hidden_parent[SRC]->d_inode;
+	hidden_dir[DST] = a->hidden_parent[DST]->d_inode;
+	IMustLock(hidden_dir[SRC]);
+	IMustLock(hidden_dir[DST]);
+
+	/* prepare workqueue args */
+	hidden_dst = NULL;
+	thargs = NULL;
+	if (a->isdir && a->hidden_dentry[DST]->d_inode) {
+		err = -ENOMEM;
+		thargs = kmalloc(sizeof(*thargs), GFP_KERNEL);
+		//thargs = NULL;
+		if (unlikely(!thargs))
+			goto out;
+		hidden_dst = dget(a->hidden_dentry[DST]);
+	}
+
+	wh_dentry[SRC] = wh_dentry[DST] = NULL;
+	lkup.nfsmnt = au_nfsmnt(a->sb, a->btgt);
+	/* create whiteout for src_dentry */
+	if (a->whsrc) {
+		wh_dentry[SRC] = simple_create_wh(src_dentry, a->btgt,
+						  a->hidden_parent[SRC], &lkup);
+		//wh_dentry[SRC] = ERR_PTR(-1);
+		err = PTR_ERR(wh_dentry[SRC]);
+		if (IS_ERR(wh_dentry[SRC]))
+			goto out_thargs;
+	}
+
+	/* lookup whiteout for dentry */
+	if (a->whdst) {
+		struct dentry *d;
+		d = lkup_wh(a->hidden_parent[DST], &dentry->d_name, &lkup);
+		//d = ERR_PTR(-1);
+		err = PTR_ERR(d);
+		if (IS_ERR(d))
+			goto out_whsrc;
+		if (!d->d_inode)
+			dput(d);
+		else
+			wh_dentry[DST] = d;
+	}
+
+	/* rename dentry to tmpwh */
+	if (thargs) {
+		err = rename_whtmp(dentry, a->btgt);
+		//err = -1;
+		if (unlikely(err))
+			goto out_whdst;
+		set_h_dptr(dentry, a->btgt, NULL);
+		err = lkup_neg(dentry, a->btgt);
+		//err = -1;
+		if (unlikely(err))
+			goto out_whtmp;
+		a->hidden_dentry[DST] = au_h_dptr_i(dentry, a->btgt);
+	}
+
+	/* cpup src */
+	if (a->hidden_dentry[DST]->d_inode && a->bstart[SRC] != a->btgt) {
+		flags = au_flags_cpup(!CPUP_DTIME, a->parent[SRC]);
+		hg_parent = a->hidden_parent[SRC]->d_parent;
+		if (!(flags & CPUP_LOCKED_GHDIR)
+		    && hg_parent == a->hidden_parent[DST])
+			flags |= CPUP_LOCKED_GHDIR;
+
+		vfsub_i_lock_nested(a->hidden_dentry[SRC]->d_inode,
+				    AuLsc_I_CHILD);
+		err = sio_cpup_simple(src_dentry, a->btgt, -1, flags);
+		//err = -1; // untested dir
+		vfsub_i_unlock(a->hidden_dentry[SRC]->d_inode);
+		if (unlikely(err))
+			goto out_whtmp;
+	}
+
+	/* rename by vfs_rename or cpup */
+	need_diropq = a->isdir
+		&& (wh_dentry[DST]
+		    || dbdiropq(dentry) == a->btgt
+		    /* hide the lower to keep xino */
+		    || a->btgt < dbend(dentry)
+		    || au_flag_test(a->sb, AuFlag_ALWAYS_DIROPQ));
+	bycpup = 0;
+	if (dbstart(src_dentry) == a->btgt) {
+		if (need_diropq && dbdiropq(src_dentry) == a->btgt)
+			need_diropq = 0;
+		/* this will fire IN_MOVE_SELF */
+		err = vfsub_rename(hidden_dir[SRC], au_h_dptr(src_dentry),
+				   hidden_dir[DST], a->hidden_dentry[DST],
+				   a->dlgt);
+#if 0
+		if (unlikely(!err
+			     && a->isdir
+			     && au_flag_test_udba_inotify(a->sb)))
+			au_iigen_inc(src_dentry->d_inode);
+#endif
+
+		//err = -1;
+	} else {
+		bycpup = 1;
+		flags = au_flags_cpup(!CPUP_DTIME, a->parent[DST]);
+		hg_parent = a->hidden_parent[DST]->d_parent;
+		if (!(flags & CPUP_LOCKED_GHDIR)
+		    && hg_parent == a->hidden_parent[SRC])
+			flags |= CPUP_LOCKED_GHDIR;
+
+		vfsub_i_lock_nested(a->hidden_dentry[SRC]->d_inode,
+				    AuLsc_I_CHILD);
+		set_dbstart(src_dentry, a->btgt);
+		set_h_dptr(src_dentry, a->btgt, dget(a->hidden_dentry[DST]));
+		err = sio_cpup_single(src_dentry, a->btgt, a->bstart[SRC], -1,
+				      flags);
+		//err = -1; // untested dir
+		if (unlikely(err)) {
+			set_h_dptr(src_dentry, a->btgt, NULL);
+			set_dbstart(src_dentry, a->bstart[SRC]);
+		}
+		vfsub_i_unlock(a->hidden_dentry[SRC]->d_inode);
+	}
+	if (unlikely(err))
+		goto out_whtmp;
+
+	/* make dir opaque */
+	if (need_diropq) {
+		struct dentry *diropq;
+		struct inode *h_inode;
+
+		h_inode = au_h_dptr_i(src_dentry, a->btgt)->d_inode;
+		hdir_lock(h_inode, src_dentry->d_inode, a->btgt);
+		diropq = create_diropq(src_dentry, a->btgt, a->dlgt);
+		//diropq = ERR_PTR(-1);
+		hdir_unlock(h_inode, src_dentry->d_inode, a->btgt);
+		err = PTR_ERR(diropq);
+		if (IS_ERR(diropq))
+			goto out_rename;
+		dput(diropq);
+	}
+
+	/* remove whiteout for dentry */
+	if (wh_dentry[DST]) {
+		err = au_unlink_wh_dentry(hidden_dir[DST], wh_dentry[DST],
+					  dentry, a->dlgt);
+		//err = -1;
+		if (unlikely(err))
+			goto out_diropq;
+	}
+
+	/* remove whtmp */
+	if (thargs) {
+		if (au_is_nfs(hidden_dst->d_sb)
+		    || !is_longer_wh(&a->whlist, a->btgt,
+				     stosi(a->sb)->si_dirwh)) {
+			err = rmdir_whtmp(hidden_dst, &a->whlist, a->btgt, dir,
+					  dentry->d_inode);
+			if (unlikely(err))
+				Warn("failed removing whtmp dir %.*s (%d), "
+				     "ignored.\n", DLNPair(hidden_dst), err);
+		} else {
+			kick_rmdir_whtmp(hidden_dst, &a->whlist, a->btgt, dir,
+					 dentry->d_inode, thargs);
+			dput(hidden_dst);
+			thargs = NULL;
+		}
+	}
+	err = 0;
+	goto out_success;
+
+#define RevertFailure(fmt, args...) do { \
+		IOErrWhck("revert failure: " fmt " (%d, %d)\n", \
+			##args, err, rerr); \
+		err = -EIO; \
+	} while (0)
+
+ out_diropq:
+	if (need_diropq) {
+		struct inode *h_inode;
+
+		h_inode = au_h_dptr_i(src_dentry, a->btgt)->d_inode;
+		/* i_lock simply since inotify is not set to h_inode. */
+		vfsub_i_lock_nested(h_inode, AuLsc_I_PARENT);
+		//hdir_lock(h_inode, src_dentry->d_inode, a->btgt);
+		rerr = remove_diropq(src_dentry, a->btgt, a->dlgt);
+		//rerr = -1;
+		//hdir_unlock(h_inode, src_dentry->d_inode, a->btgt);
+		vfsub_i_unlock(h_inode);
+		if (rerr)
+			RevertFailure("remove diropq %.*s",
+				      DLNPair(src_dentry));
+	}
+ out_rename:
+	if (!bycpup) {
+		struct dentry *d;
+		struct qstr *name = &src_dentry->d_name;
+		d = lkup_one(name->name, a->hidden_parent[SRC], name->len,
+			     &lkup);
+		//d = ERR_PTR(-1);
+		rerr = PTR_ERR(d);
+		if (IS_ERR(d)) {
+			RevertFailure("lkup_one %.*s", DLNPair(src_dentry));
+			goto out_whtmp;
+		}
+		AuDebugOn(d->d_inode);
+		rerr = vfsub_rename
+			(hidden_dir[DST], au_h_dptr_i(src_dentry, a->btgt),
+			 hidden_dir[SRC], d, a->dlgt);
+		//rerr = -1;
+		d_drop(d);
+		dput(d);
+		//set_h_dptr(src_dentry, a->btgt, NULL);
+		if (rerr)
+			RevertFailure("rename %.*s", DLNPair(src_dentry));
+	} else {
+		rerr = vfsub_unlink(hidden_dir[DST], a->hidden_dentry[DST],
+				    a->dlgt);
+		//rerr = -1;
+		set_h_dptr(src_dentry, a->btgt, NULL);
+		set_dbstart(src_dentry, a->bstart[SRC]);
+		if (rerr)
+			RevertFailure("unlink %.*s",
+				      DLNPair(a->hidden_dentry[DST]));
+	}
+ out_whtmp:
+	if (thargs) {
+		struct dentry *d;
+		struct qstr *name = &dentry->d_name;
+		LKTRLabel(here);
+		d = lkup_one(name->name, a->hidden_parent[DST], name->len,
+			     &lkup);
+		//d = ERR_PTR(-1);
+		rerr = PTR_ERR(d);
+		if (IS_ERR(d)) {
+			RevertFailure("lookup %.*s", LNPair(name));
+			goto out_whdst;
+		}
+		if (d->d_inode) {
+			d_drop(d);
+			dput(d);
+			goto out_whdst;
+		}
+		AuDebugOn(d->d_inode);
+		rerr = vfsub_rename(hidden_dir[DST], hidden_dst,
+				    hidden_dir[DST], d, a->dlgt);
+		//rerr = -1;
+		d_drop(d);
+		dput(d);
+		if (rerr) {
+			RevertFailure("rename %.*s", DLNPair(hidden_dst));
+			goto out_whdst;
+		}
+		set_h_dptr(dentry, a->btgt, NULL);
+		set_h_dptr(dentry, a->btgt, dget(hidden_dst));
+	}
+ out_whdst:
+	dput(wh_dentry[DST]);
+	wh_dentry[DST] = NULL;
+ out_whsrc:
+	if (wh_dentry[SRC]) {
+		LKTRLabel(here);
+		rerr = au_unlink_wh_dentry(hidden_dir[SRC], wh_dentry[SRC],
+					   src_dentry, a->dlgt);
+		//rerr = -1;
+		if (rerr)
+			RevertFailure("unlink %.*s", DLNPair(wh_dentry[SRC]));
+	}
+#undef RevertFailure
+	d_drop(src_dentry);
+	bend = dbend(src_dentry);
+	for (bindex = dbstart(src_dentry); bindex <= bend; bindex++) {
+		struct dentry *hd;
+		hd = au_h_dptr_i(src_dentry, bindex);
+		if (hd)
+			d_drop(hd);
+	}
+	d_drop(dentry);
+	bend = dbend(dentry);
+	for (bindex = dbstart(dentry); bindex <= bend; bindex++) {
+		struct dentry *hd;
+		hd = au_h_dptr_i(dentry, bindex);
+		if (hd)
+			d_drop(hd);
+	}
+	au_update_dbstart(dentry);
+	if (thargs)
+		d_drop(hidden_dst);
+ out_success:
+	dput(wh_dentry[SRC]);
+	dput(wh_dentry[DST]);
+ out_thargs:
+	if (thargs) {
+		dput(hidden_dst);
+		kfree(thargs);
+	}
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * test if @dentry dir can be rename destination or not.
+ * success means, it is a logically empty dir.
+ */
+static int may_rename_dstdir(struct dentry *dentry, aufs_bindex_t btgt,
+			     struct aufs_nhash *whlist)
+{
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+
+	return test_empty(dentry, whlist);
+}
+
+/*
+ * test if @dentry dir can be rename source or not.
+ * if it can, return 0 and @children is filled.
+ * success means,
+ * - or, it is a logically empty dir.
+ * - or, it exists on writable branch and has no children including whiteouts
+ *       on the lower branch.
+ */
+static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt)
+{
+	int err;
+	aufs_bindex_t bstart;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+
+	bstart = dbstart(dentry);
+	if (bstart != btgt) {
+		struct aufs_nhash *whlist;
+
+		whlist = nhash_new(GFP_KERNEL);
+		err = PTR_ERR(whlist);
+		if (IS_ERR(whlist))
+			goto out;
+		err = test_empty(dentry, whlist);
+		nhash_del(whlist);
+		goto out;
+	}
+
+	if (bstart == dbtaildir(dentry))
+		return 0; /* success */
+
+	err = au_test_empty_lower(dentry);
+
+ out:
+	if (/* unlikely */(err == -ENOTEMPTY)) {
+		Warn1("renaming dir who has child(ren) on multiple branches,"
+		      " is not supported\n");
+		err = -EXDEV;
+	}
+	TraceErr(err);
+	return err;
+}
+
+int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
+		struct inode *dir, struct dentry *dentry)
+{
+	int err, do_dt_dstdir, flags;
+	aufs_bindex_t bend, bindex;
+	struct inode *inode[2], *dirs[2];
+	enum {PARENT, CHILD};
+	/* reduce stack space */
+	struct {
+		struct rename_args a;
+		struct dtime dt[2][2];
+	} *p;
+
+	LKTRTrace("i%lu, %.*s, i%lu, %.*s\n",
+		  src_dir->i_ino, DLNPair(src_dentry),
+		  dir->i_ino, DLNPair(dentry));
+	IMustLock(src_dir);
+	IMustLock(dir);
+	inode[DST] = dentry->d_inode;
+	if (inode[DST]) {
+		IMustLock(inode[DST]);
+		igrab(inode[DST]);
+	}
+
+	err = -ENOMEM;
+	BUILD_BUG_ON(sizeof(*p) > PAGE_SIZE);
+	p = kmalloc(sizeof(*p), GFP_KERNEL);
+	if (unlikely(!p))
+		goto out;
+
+	err = -ENOTDIR;
+	p->a.sb = src_dentry->d_sb;
+	inode[SRC] = src_dentry->d_inode;
+	p->a.isdir = !!S_ISDIR(inode[SRC]->i_mode);
+	if (unlikely(p->a.isdir && inode[DST]
+		     && !S_ISDIR(inode[DST]->i_mode)))
+		goto out_free;
+
+	flags = 0;
+	if (p->a.isdir)
+		flags = AuLock_DIR;
+	aufs_read_and_write_lock2(dentry, src_dentry, flags);
+	p->a.dlgt = !!need_dlgt(p->a.sb);
+	p->a.parent[SRC] = p->a.parent[DST] = dentry->d_parent;
+	p->a.issamedir = (src_dir == dir);
+	if (p->a.issamedir)
+		di_write_lock_parent(p->a.parent[DST]);
+	else {
+		p->a.parent[SRC] = src_dentry->d_parent;
+		di_write_lock2_parent(p->a.parent[SRC], p->a.parent[DST],
+				      /*isdir*/1);
+	}
+
+	/* which branch we process */
+	p->a.bstart[DST] = dbstart(dentry);
+	p->a.btgt = err = wr_dir(dentry, 1, src_dentry, /*force_btgt*/-1,
+			      /*do_lock_srcdir*/0);
+	if (unlikely(err < 0))
+		goto out_unlock;
+
+	/* are they available to be renamed */
+	err = 0;
+	nhash_init(&p->a.whlist);
+	if (p->a.isdir && inode[DST]) {
+		set_dbstart(dentry, p->a.bstart[DST]);
+		err = may_rename_dstdir(dentry, p->a.btgt, &p->a.whlist);
+		set_dbstart(dentry, p->a.btgt);
+	}
+	p->a.hidden_dentry[DST] = au_h_dptr(dentry);
+	if (unlikely(err))
+		goto out_unlock;
+	//todo: minor optimize, their sb may be same while their bindex differs.
+	p->a.bstart[SRC] = dbstart(src_dentry);
+	p->a.hidden_dentry[SRC] = au_h_dptr(src_dentry);
+	if (p->a.isdir) {
+		err = may_rename_srcdir(src_dentry, p->a.btgt);
+		if (unlikely(err))
+			goto out_children;
+	}
+
+	/* prepare the writable parent dir on the same branch */
+	err = wr_dir_need_wh(src_dentry, p->a.isdir, &p->a.btgt,
+			     p->a.issamedir ? NULL : p->a.parent[DST]);
+	if (unlikely(err < 0))
+		goto out_children;
+	p->a.whsrc = !!err;
+	p->a.whdst = (p->a.bstart[DST] == p->a.btgt);
+	if (!p->a.whdst) {
+		err = cpup_dirs(dentry, p->a.btgt,
+				p->a.issamedir ? NULL : p->a.parent[SRC]);
+		if (unlikely(err))
+			goto out_children;
+	}
+
+	p->a.hidden_parent[SRC] = au_h_dptr_i(p->a.parent[SRC], p->a.btgt);
+	p->a.hidden_parent[DST] = au_h_dptr_i(p->a.parent[DST], p->a.btgt);
+	dirs[0] = src_dir;
+	dirs[1] = dir;
+	hdir_lock_rename(p->a.hidden_parent, dirs, p->a.btgt, p->a.issamedir);
+
+	/* store timestamps to be revertible */
+	dtime_store(p->dt[PARENT] + SRC, p->a.parent[SRC],
+		    p->a.hidden_parent[SRC]);
+	if (!p->a.issamedir)
+		dtime_store(p->dt[PARENT] + DST, p->a.parent[DST],
+			    p->a.hidden_parent[DST]);
+	do_dt_dstdir = 0;
+	if (p->a.isdir) {
+		dtime_store(p->dt[CHILD] + SRC, src_dentry,
+			    p->a.hidden_dentry[SRC]);
+		if (p->a.hidden_dentry[DST]->d_inode) {
+			do_dt_dstdir = 1;
+			dtime_store(p->dt[CHILD] + DST, dentry,
+				    p->a.hidden_dentry[DST]);
+		}
+	}
+
+	err = do_rename(src_dir, src_dentry, dir, dentry, &p->a);
+	if (unlikely(err))
+		goto out_dt;
+	hdir_unlock_rename(p->a.hidden_parent, dirs, p->a.btgt, p->a.issamedir);
+
+	/* update dir attributes */
+	dir->i_version++;
+	if (unlikely(p->a.isdir)) {
+		/* is this updating defined in POSIX? */
+		//vfsub_i_lock(inode[SRC]);
+		au_cpup_attr_timesizes(inode[SRC]);
+		//vfsub_i_unlock(inode[SRC]);
+
+		au_cpup_attr_nlink(dir);
+		if (unlikely(inode[DST])) {
+			inode[DST]->i_nlink--;
+			if (unlikely(p->a.isdir))
+				inode[DST]->i_nlink = 0;
+			au_cpup_attr_timesizes(inode[DST]);
+		}
+	}
+	if (ibstart(dir) == p->a.btgt)
+		au_cpup_attr_timesizes(dir);
+
+	if (!p->a.issamedir) {
+		src_dir->i_version++;
+		if (unlikely(p->a.isdir))
+			au_cpup_attr_nlink(src_dir);
+		if (ibstart(src_dir) == p->a.btgt)
+			au_cpup_attr_timesizes(src_dir);
+	}
+
+#if 0
+	d_drop(src_dentry);
+#else
+	/* dput/iput all lower dentries */
+	set_dbwh(src_dentry, -1);
+	bend = dbend(src_dentry);
+	for (bindex = p->a.btgt + 1; bindex <= bend; bindex++) {
+		struct dentry *hd;
+		hd = au_h_dptr_i(src_dentry, bindex);
+		if (hd)
+			set_h_dptr(src_dentry, bindex, NULL);
+	}
+	set_dbend(src_dentry, p->a.btgt);
+
+	bend = ibend(inode[SRC]);
+	for (bindex = p->a.btgt + 1; bindex <= bend; bindex++) {
+		struct inode *hi;
+		hi = au_h_iptr_i(inode[SRC], bindex);
+		if (hi) {
+			//Dbg("hi%lu, i%lu\n", hi->i_ino, 0LU);
+			xino_write0(p->a.sb, bindex, hi->i_ino, 0);
+			/* ignore this error */
+			set_h_iptr(inode[SRC], bindex, NULL, 0);
+		}
+	}
+	set_ibend(inode[SRC], p->a.btgt);
+#endif
+
+#if 0
+	if (unlikely(inode[DST])) {
+		struct inode *h_i;
+
+		bend = ibend(inode[DST]);
+		for (bindex = ibstart(inode[DST]); bindex <= bend; bindex++) {
+			h_i = au_h_iptr_i(inode[DST], bindex);
+			if (h_i)
+				xino_write0(p->a.sb, bindex, h_i->i_ino, 0);
+			/* ignore this error */
+			/* bad action? */
+		}
+	}
+#endif
+
+	goto out_children; /* success */
+
+ out_dt:
+	dtime_revert(p->dt[PARENT] + SRC,
+		     p->a.hidden_parent[SRC]->d_parent
+		     == p->a.hidden_parent[DST]);
+	if (!p->a.issamedir)
+		dtime_revert(p->dt[PARENT] + DST,
+			     p->a.hidden_parent[DST]->d_parent
+			     == p->a.hidden_parent[SRC]);
+	if (p->a.isdir && err != -EIO) {
+		struct dentry *hd;
+
+		hd = p->dt[CHILD][SRC].dt_h_dentry;
+		vfsub_i_lock_nested(hd->d_inode, AuLsc_I_CHILD);
+		dtime_revert(p->dt[CHILD] + SRC, 1);
+		vfsub_i_unlock(hd->d_inode);
+		if (do_dt_dstdir) {
+			hd = p->dt[CHILD][DST].dt_h_dentry;
+			vfsub_i_lock_nested(hd->d_inode, AuLsc_I_CHILD);
+			dtime_revert(p->dt[CHILD] + DST, 1);
+			vfsub_i_unlock(hd->d_inode);
+		}
+	}
+	hdir_unlock_rename(p->a.hidden_parent, dirs, p->a.btgt, p->a.issamedir);
+ out_children:
+	nhash_fin(&p->a.whlist);
+ out_unlock:
+	//if (unlikely(err /* && p->a.isdir */)) {
+	if (unlikely(err && p->a.isdir)) {
+		au_update_dbstart(dentry);
+		d_drop(dentry);
+	}
+	if (p->a.issamedir)
+		di_write_unlock(p->a.parent[DST]);
+	else
+		di_write_unlock2(p->a.parent[SRC], p->a.parent[DST]);
+	aufs_read_and_write_unlock2(dentry, src_dentry);
+ out_free:
+	kfree(p);
+ out:
+	iput(inode[DST]);
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/iinfo.c linux-2.6.22-aufs/fs/aufs/iinfo.c
--- linux-2.6.22/fs/aufs/iinfo.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/iinfo.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: iinfo.c,v 1.38 2007/07/15 20:04:47 sfjro Exp $ */
+
+#include "aufs.h"
+
+struct aufs_iinfo *itoii(struct inode *inode)
+{
+	struct aufs_iinfo *iinfo;
+
+	iinfo = &(container_of(inode, struct aufs_icntnr, vfs_inode)->iinfo);
+	/* bad_inode case */
+	if (unlikely(!iinfo->ii_hinode))
+		return NULL;
+	AuDebugOn(!iinfo->ii_hinode
+		  /* || stosi(inode->i_sb)->si_bend < iinfo->ii_bend */
+		  || iinfo->ii_bend < iinfo->ii_bstart);
+	return iinfo;
+}
+
+aufs_bindex_t ibstart(struct inode *inode)
+{
+	IiMustAnyLock(inode);
+	return itoii(inode)->ii_bstart;
+}
+
+aufs_bindex_t ibend(struct inode *inode)
+{
+	IiMustAnyLock(inode);
+	return itoii(inode)->ii_bend;
+}
+
+struct aufs_vdir *ivdir(struct inode *inode)
+{
+	IiMustAnyLock(inode);
+	AuDebugOn(!S_ISDIR(inode->i_mode));
+	return itoii(inode)->ii_vdir;
+}
+
+struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex)
+{
+	struct aufs_hinode *hinode;
+
+	IiMustAnyLock(inode);
+
+	hinode = itoii(inode)->ii_hinode + bindex;
+	return hinode->hi_whdentry;
+}
+
+struct inode *au_h_iptr_i(struct inode *inode, aufs_bindex_t bindex)
+{
+	struct inode *hidden_inode;
+
+	IiMustAnyLock(inode);
+	AuDebugOn(bindex < 0 || ibend(inode) < bindex);
+	hidden_inode = itoii(inode)->ii_hinode[0 + bindex].hi_inode;
+	AuDebugOn(hidden_inode && atomic_read(&hidden_inode->i_count) <= 0);
+	return hidden_inode;
+}
+
+struct inode *au_h_iptr(struct inode *inode)
+{
+	return au_h_iptr_i(inode, ibstart(inode));
+}
+
+aufs_bindex_t itoid_index(struct inode *inode, aufs_bindex_t bindex)
+{
+	IiMustAnyLock(inode);
+	AuDebugOn(bindex < 0
+		  || ibend(inode) < bindex
+		  || !itoii(inode)->ii_hinode[0 + bindex].hi_inode);
+	return itoii(inode)->ii_hinode[0 + bindex].hi_id;
+}
+
+// hard/soft set
+void set_ibstart(struct inode *inode, aufs_bindex_t bindex)
+{
+	struct aufs_iinfo *iinfo = itoii(inode);
+	struct inode *h_inode;
+
+	IiMustWriteLock(inode);
+	AuDebugOn(sbend(inode->i_sb) < bindex);
+	iinfo->ii_bstart = bindex;
+	h_inode = iinfo->ii_hinode[bindex + 0].hi_inode;
+	if (h_inode)
+		au_cpup_igen(inode, h_inode);
+}
+
+void set_ibend(struct inode *inode, aufs_bindex_t bindex)
+{
+	IiMustWriteLock(inode);
+	AuDebugOn(sbend(inode->i_sb) < bindex
+		  || bindex < ibstart(inode));
+	itoii(inode)->ii_bend = bindex;
+}
+
+void set_ivdir(struct inode *inode, struct aufs_vdir *vdir)
+{
+	IiMustWriteLock(inode);
+	AuDebugOn(!S_ISDIR(inode->i_mode)
+		  || (itoii(inode)->ii_vdir && vdir));
+	itoii(inode)->ii_vdir = vdir;
+}
+
+void aufs_hiput(struct aufs_hinode *hinode)
+{
+	if (unlikely(hinode->hi_notify))
+		do_free_hinotify(hinode);
+	dput(hinode->hi_whdentry);
+	iput(hinode->hi_inode);
+}
+
+unsigned int au_hi_flags(struct inode *inode, int isdir)
+{
+	unsigned int flags;
+	struct super_block *sb = inode->i_sb;
+
+	flags = 0;
+	if (au_flag_test(sb, AuFlag_XINO))
+		flags = AUFS_HI_XINO;
+	if (unlikely(isdir && au_flag_test_udba_inotify(sb)))
+		flags |= AUFS_HI_NOTIFY;
+	return flags;
+}
+
+void set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
+		struct inode *h_inode, unsigned int flags)
+{
+	struct aufs_hinode *hinode;
+	struct inode *hi;
+	struct aufs_iinfo *iinfo = itoii(inode);
+
+	LKTRTrace("i%lu, b%d, hi%lu, flags 0x%x\n",
+		  inode->i_ino, bindex, h_inode ? h_inode->i_ino : 0, flags);
+	IiMustWriteLock(inode);
+	hinode = iinfo->ii_hinode + bindex;
+	hi = hinode->hi_inode;
+	AuDebugOn(bindex < ibstart(inode) || ibend(inode) < bindex
+		  || (h_inode && atomic_read(&h_inode->i_count) <= 0)
+		  || (h_inode && hi));
+
+	if (hi) {
+#if 0
+		if (unlikely(!hi->i_nlink && (flags & AUFS_HI_XINO))) {
+			AuDebugOn(sbr_id(sb, bindex) != hinode->hi_id);
+			xino_write0(inode->i_sb, bindex, hi->i_ino, 0);
+			/* ignore this error */
+			/* bad action? */
+		}
+#endif
+		aufs_hiput(hinode);
+	}
+	hinode->hi_inode = h_inode;
+	if (h_inode) {
+		int err;
+		struct super_block *sb = inode->i_sb;
+
+		if (bindex == iinfo->ii_bstart)
+			au_cpup_igen(inode, h_inode);
+		hinode->hi_id = sbr_id(sb, bindex);
+		if (flags & AUFS_HI_XINO) {
+			struct xino xino = {
+				.ino	= inode->i_ino,
+				//.h_gen	= h_inode->i_generation
+			};
+			err = xino_write(sb, bindex, h_inode->i_ino, &xino);
+			if (unlikely(err)) {
+				/* bad action? */
+				IOErr1("failed xino_write() %d, force noxino\n",
+				       err);
+				au_flag_clr(sb, AuFlag_XINO);
+			}
+		}
+		if ((flags & AUFS_HI_NOTIFY)
+		    && br_hinotifyable(sbr_perm(sb, bindex))) {
+			err = alloc_hinotify(hinode, inode, h_inode);
+			if (unlikely(err))
+				IOErr1("alloc_hinotify() %d\n", err);
+			else
+				AuDebugOn(!hinode->hi_notify);
+		}
+	}
+}
+
+void set_hi_wh(struct inode *inode, aufs_bindex_t bindex, struct dentry *h_wh)
+{
+	struct aufs_hinode *hinode;
+
+	IiMustWriteLock(inode);
+	hinode = itoii(inode)->ii_hinode + bindex;
+	AuDebugOn(hinode->hi_whdentry);
+	hinode->hi_whdentry = h_wh;
+}
+
+void au_update_iigen(struct inode *inode)
+{
+	//IiMustWriteLock(inode);
+	AuDebugOn(!inode->i_sb);
+	atomic_set(&itoii(inode)->ii_generation, au_sigen(inode->i_sb));
+	//smp_mb();
+}
+
+/* it may be called at remount time, too */
+void au_update_brange(struct inode *inode, int do_put_zero)
+{
+	struct aufs_iinfo *iinfo;
+
+	LKTRTrace("i%lu, %d\n", inode->i_ino, do_put_zero);
+	IiMustWriteLock(inode);
+
+	iinfo = itoii(inode);
+	if (unlikely(!iinfo) || iinfo->ii_bstart < 0)
+		return;
+
+	if (do_put_zero) {
+		aufs_bindex_t bindex;
+		for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend;
+		     bindex++) {
+			struct inode *h_i;
+			h_i = iinfo->ii_hinode[0 + bindex].hi_inode;
+			if (h_i && !h_i->i_nlink)
+				set_h_iptr(inode, bindex, NULL, 0);
+		}
+	}
+
+	iinfo->ii_bstart = -1;
+	while (++iinfo->ii_bstart <= iinfo->ii_bend)
+		if (iinfo->ii_hinode[0 + iinfo->ii_bstart].hi_inode)
+			break;
+	if (iinfo->ii_bstart > iinfo->ii_bend) {
+		iinfo->ii_bend = iinfo->ii_bstart = -1;
+		return;
+	}
+
+	iinfo->ii_bend++;
+	while (0 <= --iinfo->ii_bend)
+		if (iinfo->ii_hinode[0 + iinfo->ii_bend].hi_inode)
+			break;
+	AuDebugOn(iinfo->ii_bstart > iinfo->ii_bend || iinfo->ii_bend < 0);
+}
+
+/* ---------------------------------------------------------------------- */
+
+int au_iinfo_init(struct inode *inode)
+{
+	struct aufs_iinfo *iinfo;
+	struct super_block *sb;
+	int nbr, i;
+
+	sb = inode->i_sb;
+	AuDebugOn(!sb);
+	iinfo = &(container_of(inode, struct aufs_icntnr, vfs_inode)->iinfo);
+	AuDebugOn(iinfo->ii_hinode);
+	nbr = sbend(sb) + 1;
+	if (unlikely(nbr <= 0))
+		nbr = 1;
+	iinfo->ii_hinode = kcalloc(nbr, sizeof(*iinfo->ii_hinode), GFP_KERNEL);
+	//iinfo->ii_hinode = NULL;
+	if (iinfo->ii_hinode) {
+		for (i = 0; i < nbr; i++)
+			iinfo->ii_hinode[i].hi_id = -1;
+		atomic_set(&iinfo->ii_generation, au_sigen(sb));
+		rw_init_nolock(&iinfo->ii_rwsem);
+		iinfo->ii_bstart = -1;
+		iinfo->ii_bend = -1;
+		iinfo->ii_vdir = NULL;
+		return 0;
+	}
+	return -ENOMEM;
+}
+
+static int au_iinfo_write0(struct super_block *sb, struct aufs_hinode *hinode,
+			   ino_t ino)
+{
+	int err, locked;
+	aufs_bindex_t bindex;
+
+	err = 0;
+ 	locked = si_read_trylock(sb, !AuLock_FLUSH); // crucio!
+	bindex = find_brindex(sb, hinode->hi_id);
+	if (bindex >= 0)
+		err = xino_write0(sb, bindex, hinode->hi_inode->i_ino, ino);
+	/* error action? */
+	if (locked)
+		si_read_unlock(sb);
+	return err;
+}
+
+void au_iinfo_fin(struct inode *inode)
+{
+	struct aufs_iinfo *iinfo;
+	aufs_bindex_t bend;
+	struct aufs_hinode *hi;
+	struct super_block *sb;
+	int unlinked;
+	ino_t ino;
+
+	iinfo = itoii(inode);
+	/* bad_inode case */
+	if (unlikely(!iinfo))
+		return;
+
+	if (unlikely(iinfo->ii_vdir))
+		free_vdir(iinfo->ii_vdir);
+
+	if (iinfo->ii_bstart >= 0) {
+		sb = inode->i_sb;
+		unlinked = !inode->i_nlink;
+		ino = 0;
+		if (unlikely(unlinked))
+			ino = inode->i_ino;
+		hi = iinfo->ii_hinode + iinfo->ii_bstart;
+		bend = iinfo->ii_bend;
+		while (iinfo->ii_bstart++ <= bend) {
+			if (hi->hi_inode) {
+				if (unlikely(unlinked
+					     || !hi->hi_inode->i_nlink)) {
+					au_iinfo_write0(sb, hi, ino);
+					/* ignore this error */
+					ino = 0;
+				}
+				aufs_hiput(hi);
+			}
+			hi++;
+		}
+		//iinfo->ii_bstart = iinfo->ii_bend = -1;
+	}
+
+	kfree(iinfo->ii_hinode);
+	//iinfo->ii_hinode = NULL;
+}
diff -ruN linux-2.6.22/fs/aufs/inode.c linux-2.6.22-aufs/fs/aufs/inode.c
--- linux-2.6.22/fs/aufs/inode.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/inode.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: inode.c,v 1.29 2007/07/15 20:06:55 sfjro Exp $ */
+
+#include "aufs.h"
+
+int au_refresh_hinode_self(struct inode *inode)
+{
+	int err, new_sz, update;
+	struct inode *first;
+	struct aufs_hinode *p, *q, tmp;
+	struct super_block *sb;
+	struct aufs_iinfo *iinfo;
+	aufs_bindex_t bindex, bend, new_bindex;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	IiMustWriteLock(inode);
+
+	err = -ENOMEM;
+	update = 0;
+	sb = inode->i_sb;
+	bend = sbend(sb);
+	new_sz = sizeof(*iinfo->ii_hinode) * (bend + 1);
+	iinfo = itoii(inode);
+	p = au_kzrealloc(iinfo->ii_hinode, sizeof(*p) * (iinfo->ii_bend + 1),
+			 new_sz, GFP_KERNEL);
+	//p = NULL;
+	if (unlikely(!p))
+		goto out;
+
+	iinfo->ii_hinode = p;
+	p = iinfo->ii_hinode + iinfo->ii_bstart;
+	first = p->hi_inode;
+	err = 0;
+	for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend;
+	     bindex++, p++) {
+		if (unlikely(!p->hi_inode))
+			continue;
+
+		new_bindex = find_brindex(sb, p->hi_id);
+		if (new_bindex == bindex)
+			continue;
+		if (new_bindex < 0) {
+			update++;
+			aufs_hiput(p);
+			p->hi_inode = NULL;
+			continue;
+		}
+
+		if (new_bindex < iinfo->ii_bstart)
+			iinfo->ii_bstart = new_bindex;
+		if (iinfo->ii_bend < new_bindex)
+			iinfo->ii_bend = new_bindex;
+		/* swap two hidden inode, and loop again */
+		q = iinfo->ii_hinode + new_bindex;
+		tmp = *q;
+		*q = *p;
+		*p = tmp;
+		if (tmp.hi_inode) {
+			bindex--;
+			p--;
+		}
+	}
+	au_update_brange(inode, /*do_put_zero*/0);
+
+	if (unlikely(err))
+		goto out;
+
+	if (1 || first != au_h_iptr(inode))
+		au_cpup_attr_all(inode);
+	if (update && S_ISDIR(inode->i_mode))
+		inode->i_version++;
+	au_update_iigen(inode);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
+{
+	int err, update, isdir;
+	struct inode *first;
+	struct aufs_hinode *p;
+	struct super_block *sb;
+	struct aufs_iinfo *iinfo;
+	aufs_bindex_t bindex, bend;
+	unsigned int flags;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	IiMustWriteLock(inode);
+
+	err = au_refresh_hinode_self(inode);
+	if (unlikely(err))
+		goto out;
+
+	sb = dentry->d_sb;
+	bend = sbend(sb);
+	iinfo = itoii(inode);
+	update = 0;
+	p = iinfo->ii_hinode + iinfo->ii_bstart;
+	first = p->hi_inode;
+	isdir = S_ISDIR(inode->i_mode);
+	flags = au_hi_flags(inode, isdir);
+	bend = dbend(dentry);
+	for (bindex = dbstart(dentry); bindex <= bend; bindex++) {
+		struct inode *hi;
+		struct dentry *hd;
+
+		hd = au_h_dptr_i(dentry, bindex);
+		if (!hd || !hd->d_inode)
+			continue;
+
+		if (iinfo->ii_bstart <= bindex && bindex <= iinfo->ii_bend) {
+			hi = au_h_iptr_i(inode, bindex);
+			if (hi) {
+				if (hi == hd->d_inode)
+					continue;
+				err = -ESTALE;
+				break;
+			}
+		}
+		if (bindex < iinfo->ii_bstart)
+			iinfo->ii_bstart = bindex;
+		if (iinfo->ii_bend < bindex)
+			iinfo->ii_bend = bindex;
+		set_h_iptr(inode, bindex, igrab(hd->d_inode), flags);
+		update++;
+	}
+	au_update_brange(inode, /*do_put_zero*/0);
+
+	if (unlikely(err))
+		goto out;
+
+	if (1 || first != au_h_iptr(inode))
+		au_cpup_attr_all(inode);
+	if (update && isdir)
+		inode->i_version++;
+	au_update_iigen(inode);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int set_inode(struct inode *inode, struct dentry *dentry)
+{
+	int err, isdir;
+	struct dentry *hidden_dentry;
+	struct inode *hidden_inode;
+	umode_t mode;
+	aufs_bindex_t bindex, bstart, btail;
+	struct aufs_iinfo *iinfo;
+	unsigned int flags;
+
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, DLNPair(dentry));
+	AuDebugOn(!(inode->i_state & I_NEW));
+	IiMustWriteLock(inode);
+	hidden_dentry = au_h_dptr(dentry);
+	AuDebugOn(!hidden_dentry);
+	hidden_inode = hidden_dentry->d_inode;
+	AuDebugOn(!hidden_inode);
+
+	err = 0;
+	isdir = 0;
+	bstart = dbstart(dentry);
+	mode = hidden_inode->i_mode;
+	switch (mode & S_IFMT) {
+	case S_IFREG:
+		btail = dbtail(dentry);
+		break;
+	case S_IFDIR:
+		isdir = 1;
+		btail = dbtaildir(dentry);
+		inode->i_op = &aufs_dir_iop;
+		inode->i_fop = &aufs_dir_fop;
+		break;
+	case S_IFLNK:
+		btail = dbtail(dentry);
+		inode->i_op = &aufs_symlink_iop;
+		break;
+	case S_IFBLK:
+	case S_IFCHR:
+	case S_IFIFO:
+	case S_IFSOCK:
+		btail = dbtail(dentry);
+		init_special_inode(inode, mode, hidden_inode->i_rdev);
+		break;
+	default:
+		IOErr("Unknown file type 0%o\n", mode);
+		err = -EIO;
+		goto out;
+	}
+
+	flags = au_hi_flags(inode, isdir);
+	iinfo = itoii(inode);
+	iinfo->ii_bstart = bstart;
+	iinfo->ii_bend = btail;
+	for (bindex = bstart; bindex <= btail; bindex++) {
+		hidden_dentry = au_h_dptr_i(dentry, bindex);
+		if (!hidden_dentry)
+			continue;
+		AuDebugOn(!hidden_dentry->d_inode);
+		set_h_iptr(inode, bindex, igrab(hidden_dentry->d_inode), flags);
+	}
+	au_cpup_attr_all(inode);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* successful returns with iinfo write_locked */
+//todo: return with unlocked?
+static int reval_inode(struct inode *inode, struct dentry *dentry, int *matched)
+{
+	int err;
+	struct inode *h_inode, *h_dinode;
+	aufs_bindex_t bindex, bend;
+	//const int udba = !au_flag_test(inode->i_sb, AuFlag_UDBA_NONE);
+
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, DLNPair(dentry));
+
+	*matched = 0;
+
+	/*
+	 * before this function, if aufs got any iinfo lock, it must be only
+	 * one, the parent dir.
+	 * it can happen by UDBA and the obsoleted inode number.
+	 */
+	err = -EIO;
+	if (unlikely(inode->i_ino == parent_ino(dentry)))
+		goto out;
+
+	h_dinode = au_h_dptr(dentry)->d_inode;
+	vfsub_i_lock_nested(inode, AuLsc_I_CHILD);
+	ii_write_lock_new(inode);
+	bend = ibend(inode);
+	for (bindex = ibstart(inode); bindex <= bend; bindex++) {
+		h_inode = au_h_iptr_i(inode, bindex);
+		if (h_inode && h_inode == h_dinode) {
+			//&& (ibs != bstart || !au_test_higen(inode, h_inode)));
+			*matched = 1;
+			err = 0;
+			if (unlikely(au_iigen(inode) != au_digen(dentry)))
+				err = au_refresh_hinode(inode, dentry);
+			break;
+		}
+	}
+	vfsub_i_unlock(inode);
+	if (unlikely(err))
+		ii_write_unlock(inode);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* successful returns with iinfo write_locked */
+//todo: return with unlocked?
+struct inode *au_new_inode(struct dentry *dentry)
+{
+	struct inode *inode, *h_inode;
+	struct dentry *h_dentry;
+	ino_t h_ino;
+	struct super_block *sb;
+	int err, match;
+	aufs_bindex_t bstart;
+	struct xino xino;
+
+	LKTRTrace("%.*s\n", DLNPair(dentry));
+	sb = dentry->d_sb;
+	h_dentry = au_h_dptr(dentry);
+	AuDebugOn(!h_dentry);
+	h_inode = h_dentry->d_inode;
+	AuDebugOn(!h_inode);
+
+	bstart = dbstart(dentry);
+	h_ino = h_inode->i_ino;
+	err = xino_read(sb, bstart, h_ino, &xino);
+	//err = -1;
+	inode = ERR_PTR(err);
+	if (unlikely(err))
+		goto out;
+ new_ino:
+	if (!xino.ino) {
+		xino.ino = xino_new_ino(sb);
+		if (!xino.ino) {
+			inode = ERR_PTR(-EIO);
+			goto out;
+		}
+	}
+
+	LKTRTrace("i%lu\n", xino.ino);
+	err = -ENOMEM;
+	inode = iget_locked(sb, xino.ino);
+	if (unlikely(!inode))
+		goto out;
+	err = PTR_ERR(inode);
+	if (IS_ERR(inode))
+		goto out;
+	err = -ENOMEM;
+	if (unlikely(is_bad_inode(inode)))
+		goto out_iput;
+
+	LKTRTrace("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW));
+	if (inode->i_state & I_NEW) {
+		sb->s_op->read_inode(inode);
+		if (!is_bad_inode(inode)) {
+			ii_write_lock_new(inode);
+			err = set_inode(inode, dentry);
+			//err = -1;
+		}
+		unlock_new_inode(inode);
+		if (!err)
+			goto out; /* success */
+		ii_write_unlock(inode);
+		goto out_iput;
+	} else {
+		err = reval_inode(inode, dentry, &match);
+		if (!err)
+			goto out; /* success */
+		else if (match)
+			goto out_iput;
+	}
+
+	if (unlikely(au_is_unique_ino(h_dentry, h_ino)))
+		Warn1("Un-notified UDBA or repeatedly renamed dir,"
+		      " b%d, %s, %.*s, hi%lu, i%lu.\n",
+		      bstart, au_sbtype(h_dentry->d_sb), DLNPair(dentry), h_ino,
+		      xino.ino);
+#if 0
+	{
+		static unsigned char c;
+		if (!c++) {
+			struct dentry *d = d_find_alias(inode);
+			au_debug_on();
+			DbgDentry(dentry);
+			DbgInode(inode);
+			if (d) {
+				DbgDentry(d);
+				dput(d);
+			}
+			au_debug_off();
+		}
+	}
+#endif
+	xino.ino = 0;
+	err = xino_write0(sb, bstart, h_ino, 0);
+	if (!err) {
+		iput(inode);
+		goto new_ino;
+	}
+	/* force noxino? */
+
+ out_iput:
+	iput(inode);
+	inode = ERR_PTR(err);
+ out:
+	TraceErrPtr(inode);
+	return inode;
+}
diff -ruN linux-2.6.22/fs/aufs/inode.h linux-2.6.22-aufs/fs/aufs/inode.h
--- linux-2.6.22/fs/aufs/inode.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/inode.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: inode.h,v 1.39 2007/07/15 20:03:28 sfjro Exp $ */
+
+#ifndef __AUFS_INODE_H__
+#define __AUFS_INODE_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+#include "misc.h"
+
+struct aufs_hinode;
+struct aufs_vdir;
+struct aufs_iinfo {
+	atomic_t		ii_generation;
+	struct super_block	*ii_hsb1;	/* no get/put */
+
+	struct aufs_rwsem	ii_rwsem;
+	aufs_bindex_t		ii_bstart, ii_bend;
+	struct aufs_hinode	*ii_hinode;
+	struct aufs_vdir	*ii_vdir;
+};
+
+struct aufs_icntnr {
+	struct aufs_iinfo iinfo;
+	struct inode vfs_inode;
+};
+
+/* ---------------------------------------------------------------------- */
+
+/* inode.c */
+int au_refresh_hinode_self(struct inode *inode);
+int au_refresh_hinode(struct inode *inode, struct dentry *dentry);
+struct inode *au_new_inode(struct dentry *dentry);
+
+/* i_op.c */
+extern struct inode_operations aufs_iop, aufs_symlink_iop, aufs_dir_iop;
+int wr_dir(struct dentry *dentry, int negative, struct dentry *src_dentry,
+	   aufs_bindex_t force_btgt, int do_lock_srcdir);
+
+/* i_op_add.c */
+int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev);
+int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname);
+int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
+		struct nameidata *nd);
+int aufs_link(struct dentry *src_dentry, struct inode *dir,
+	      struct dentry *dentry);
+int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode);
+
+/* i_op_del.c */
+int wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup,
+		   struct dentry *locked);
+int aufs_unlink(struct inode *dir, struct dentry *dentry);
+int aufs_rmdir(struct inode *dir, struct dentry *dentry);
+
+/* i_op_ren.c */
+int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
+		struct inode *dir, struct dentry *dentry);
+
+/* iinfo.c */
+struct aufs_iinfo *itoii(struct inode *inode);
+aufs_bindex_t ibstart(struct inode *inode);
+aufs_bindex_t ibend(struct inode *inode);
+struct aufs_vdir *ivdir(struct inode *inode);
+struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex);
+struct inode *au_h_iptr_i(struct inode *inode, aufs_bindex_t bindex);
+struct inode *au_h_iptr(struct inode *inode);
+aufs_bindex_t itoid_index(struct inode *inode, aufs_bindex_t bindex);
+
+void set_ibstart(struct inode *inode, aufs_bindex_t bindex);
+void set_ibend(struct inode *inode, aufs_bindex_t bindex);
+void set_ivdir(struct inode *inode, struct aufs_vdir *vdir);
+void set_hi_wh(struct inode *inode, aufs_bindex_t bindex, struct dentry *h_wh);
+void aufs_hiput(struct aufs_hinode *hinode);
+#define AUFS_HI_XINO	1
+#define AUFS_HI_NOTIFY	2
+unsigned int au_hi_flags(struct inode *inode, int isdir);
+void set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
+		struct inode *h_inode, unsigned int flags);
+void au_update_iigen(struct inode *inode);
+void au_update_brange(struct inode *inode, int do_put_zero);
+
+int au_iinfo_init(struct inode *inode);
+void au_iinfo_fin(struct inode *inode);
+
+/* plink.c */
+#ifdef CONFIG_AUFS_DEBUG
+void au_list_plink(struct super_block *sb);
+#else
+static inline void au_list_plink(struct super_block *sb)
+{
+	/* nothing */
+}
+#endif
+int au_is_plinked(struct super_block *sb, struct inode *inode);
+struct dentry *lkup_plink(struct super_block *sb, aufs_bindex_t bindex,
+			  struct inode *inode);
+void append_plink(struct super_block *sb, struct inode *inode,
+		  struct dentry *h_dentry, aufs_bindex_t bindex);
+void au_put_plink(struct super_block *sb);
+void half_refresh_plink(struct super_block *sb, aufs_bindex_t br_id);
+
+/* ---------------------------------------------------------------------- */
+
+/* tiny test for inode number */
+/* tmpfs generation is too rough */
+static inline int au_test_higen(struct inode *inode, struct inode *h_inode)
+{
+	//IiMustAnyLock(inode);
+	return !(itoii(inode)->ii_hsb1 == h_inode->i_sb
+		 && inode->i_generation == h_inode->i_generation);
+}
+
+static inline au_gen_t au_iigen(struct inode *inode)
+{
+	return atomic_read(&itoii(inode)->ii_generation);
+}
+
+static inline au_gen_t au_iigen_inc(struct inode *inode)
+{
+	//Dbg("i%lu\n", inode->i_ino);
+	return atomic_inc_return(&itoii(inode)->ii_generation);
+}
+
+static inline au_gen_t au_iigen_dec(struct inode *inode)
+{
+	//Dbg("i%lu\n", inode->i_ino);
+	return atomic_dec_return(&itoii(inode)->ii_generation);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* lock subclass for iinfo */
+enum {
+	AuLsc_II_CHILD,		/* child first */
+	AuLsc_II_CHILD2,	/* rename(2), link(2), and cpup at hinotify */
+	AuLsc_II_CHILD3,	/* copyup dirs */
+	AuLsc_II_PARENT,
+	AuLsc_II_PARENT2,
+	AuLsc_II_PARENT3,
+	AuLsc_II_NEW		/* new inode */
+};
+
+/*
+ * ii_read_lock_child, ii_write_lock_child,
+ * ii_read_lock_child2, ii_write_lock_child2,
+ * ii_read_lock_child3, ii_write_lock_child3,
+ * ii_read_lock_parent, ii_write_lock_parent,
+ * ii_read_lock_parent2, ii_write_lock_parent2,
+ * ii_read_lock_parent3, ii_write_lock_parent3,
+ * ii_read_lock_new, ii_write_lock_new
+ */
+#define ReadLockFunc(name, lsc) \
+static inline void ii_read_lock_##name(struct inode *i) \
+{rw_read_lock_nested(&itoii(i)->ii_rwsem, AuLsc_II_##lsc);}
+
+#define WriteLockFunc(name, lsc) \
+static inline void ii_write_lock_##name(struct inode *i) \
+{rw_write_lock_nested(&itoii(i)->ii_rwsem, AuLsc_II_##lsc);}
+
+#define RWLockFuncs(name, lsc) \
+	ReadLockFunc(name, lsc) \
+	WriteLockFunc(name, lsc)
+
+RWLockFuncs(child, CHILD);
+RWLockFuncs(child2, CHILD2);
+RWLockFuncs(child3, CHILD3);
+RWLockFuncs(parent, PARENT);
+RWLockFuncs(parent2, PARENT2);
+RWLockFuncs(parent3, PARENT3);
+RWLockFuncs(new, NEW);
+
+#undef ReadLockFunc
+#undef WriteLockFunc
+#undef RWLockFunc
+
+/*
+ * ii_read_unlock, ii_write_unlock, ii_downgrade_lock
+ */
+SimpleUnlockRwsemFuncs(ii, struct inode *i, itoii(i)->ii_rwsem);
+
+/* to debug easier, do not make them inlined functions */
+#define IiMustReadLock(i) do { \
+	SiMustAnyLock((i)->i_sb); \
+	RwMustReadLock(&itoii(i)->ii_rwsem); \
+} while (0)
+
+#define IiMustWriteLock(i) do { \
+	SiMustAnyLock((i)->i_sb); \
+	RwMustWriteLock(&itoii(i)->ii_rwsem); \
+} while (0)
+
+#define IiMustAnyLock(i) do { \
+	SiMustAnyLock((i)->i_sb); \
+	RwMustAnyLock(&itoii(i)->ii_rwsem); \
+} while (0)
+
+#define IiMustNoWaiters(i)	RwMustNoWaiters(&itoii(i)->ii_rwsem)
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_INODE_H__ */
diff -ruN linux-2.6.22/fs/aufs/misc.c linux-2.6.22-aufs/fs/aufs/misc.c
--- linux-2.6.22/fs/aufs/misc.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/misc.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: misc.c,v 1.35 2007/07/15 20:04:57 sfjro Exp $ */
+
+//#include <linux/fs.h>
+//#include <linux/namei.h>
+//#include <linux/mm.h>
+//#include <asm/uaccess.h>
+#include "aufs.h"
+
+void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp)
+{
+	void *q;
+
+	LKTRTrace("p %p, nused %d, sz %d\n", p, nused, new_sz);
+	AuDebugOn(new_sz <= 0);
+	if (new_sz <= nused)
+		return p;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
+	q = krealloc(p, new_sz, gfp);
+	if (q)
+		memset(q + nused, 0, new_sz - nused);
+	return q;
+#else
+	LKTRTrace("ksize %d\n", ksize(p));
+	if (new_sz <= ksize(p)) {
+		memset(p + nused, 0, new_sz - nused);
+		return p;
+	}
+
+	q = kmalloc(new_sz, gfp);
+	//q = NULL;
+	if (unlikely(!q))
+		return NULL;
+	memcpy(q, p, nused);
+	memset(q + nused, 0, new_sz - nused);
+	//smp_mb();
+	kfree(p);
+	return q;
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+// todo: make it inline
+struct nameidata *fake_dm(struct nameidata *fake_nd, struct nameidata *nd,
+			  struct super_block *sb, aufs_bindex_t bindex)
+{
+	LKTRTrace("nd %p, b%d\n", nd, bindex);
+
+	if (!nd)
+		return NULL;
+
+	fake_nd->dentry = NULL;
+	fake_nd->mnt = NULL;
+
+#ifndef CONFIG_AUFS_FAKE_DM
+	DiMustAnyLock(nd->dentry);
+
+	if (bindex <= dbend(nd->dentry))
+		fake_nd->dentry = au_h_dptr_i(nd->dentry, bindex);
+	if (fake_nd->dentry) {
+		dget(fake_nd->dentry);
+		fake_nd->mnt = sbr_mnt(sb, bindex);
+		AuDebugOn(!fake_nd->mnt);
+		mntget(fake_nd->mnt);
+	} else
+		fake_nd = ERR_PTR(-ENOENT);
+#endif
+
+	TraceErrPtr(fake_nd);
+	return fake_nd;
+}
+
+void fake_dm_release(struct nameidata *fake_nd)
+{
+#ifndef CONFIG_AUFS_FAKE_DM
+	if (fake_nd) {
+		mntput(fake_nd->mnt);
+		dput(fake_nd->dentry);
+	}
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+//todo: remove sparse
+int au_copy_file(struct file *dst, struct file *src, loff_t len,
+		 struct super_block *sb, int *sparse)
+{
+	int err, all_zero, dlgt;
+	unsigned long blksize;
+	char *buf;
+	/* reduce stack space */
+	struct iattr *ia;
+
+	LKTRTrace("%.*s, %.*s\n",
+		  DLNPair(dst->f_dentry), DLNPair(src->f_dentry));
+	AuDebugOn(!(dst->f_mode & FMODE_WRITE));
+#ifdef CONFIG_AUFS_DEBUG
+	{
+		struct dentry *parent;
+		parent = dget_parent(dst->f_dentry);
+		IMustLock(parent->d_inode);
+		dput(parent);
+	}
+#endif
+
+	err = -ENOMEM;
+	blksize = dst->f_dentry->d_sb->s_blocksize;
+	if (!blksize || PAGE_SIZE < blksize)
+		blksize = PAGE_SIZE;
+	LKTRTrace("blksize %lu\n", blksize);
+	buf = kmalloc(blksize, GFP_KERNEL);
+	//buf = NULL;
+	if (unlikely(!buf))
+		goto out;
+	ia = kmalloc(sizeof(*ia), GFP_KERNEL);
+	if (unlikely(!ia))
+		goto out_buf;
+
+	dlgt = need_dlgt(sb);
+	err = all_zero = 0;
+	dst->f_pos = src->f_pos = 0;
+	while (len) {
+		size_t sz, rbytes, wbytes, i;
+		char *p;
+
+		LKTRTrace("len %lld\n", len);
+		sz = blksize;
+		if (len < blksize)
+			sz = len;
+
+		/* support LSM and notify */
+		rbytes = 0;
+		while (!rbytes || err == -EAGAIN || err == -EINTR)
+			err = rbytes = vfsub_read_k(src, buf, sz, &src->f_pos,
+						    dlgt);
+		if (unlikely(err < 0))
+			break;
+
+		all_zero = 0;
+		if (len >= rbytes && rbytes == blksize) {
+			all_zero = 1;
+			p = buf;
+			for (i = 0; all_zero && i < rbytes; i++)
+				all_zero = !*p++;
+		}
+		if (!all_zero) {
+			wbytes = rbytes;
+			p = buf;
+			while (wbytes) {
+				size_t b;
+				/* support LSM and notify */
+				err = b = vfsub_write_k(dst, p, wbytes,
+							&dst->f_pos, dlgt);
+				if (unlikely(err == -EAGAIN || err == -EINTR))
+					continue;
+				if (unlikely(err < 0))
+					break;
+				wbytes -= b;
+				p += b;
+			}
+		} else {
+			loff_t res;
+			LKTRLabel(hole);
+			*sparse = 1;
+			err = res = vfsub_llseek(dst, rbytes, SEEK_CUR);
+			if (unlikely(res < 0))
+				break;
+		}
+		len -= rbytes;
+		err = 0;
+	}
+
+	/* the last block may be a hole */
+	if (unlikely(!err && all_zero)) {
+		struct dentry *h_d = dst->f_dentry;
+		struct inode *h_i = h_d->d_inode;
+
+		LKTRLabel(last hole);
+		do {
+			err = vfsub_write_k(dst, "\0", 1, &dst->f_pos, dlgt);
+		} while (err == -EAGAIN || err == -EINTR);
+		if (err == 1) {
+			ia->ia_size = dst->f_pos;
+			ia->ia_valid = ATTR_SIZE | ATTR_FILE;
+			ia->ia_file = dst;
+			vfsub_i_lock_nested(h_i, AuLsc_I_CHILD2);
+			err = vfsub_notify_change(h_d, ia, dlgt);
+			vfsub_i_unlock(h_i);
+		}
+	}
+
+	kfree(ia);
+ out_buf:
+	kfree(buf);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int test_ro(struct super_block *sb, aufs_bindex_t bindex, struct inode *inode)
+{
+	int err;
+
+	err = br_rdonly(stobr(sb, bindex));
+	if (!err && inode) {
+		struct inode *hi = au_h_iptr_i(inode, bindex);
+		if (hi)
+			err = IS_IMMUTABLE(hi) ? -EROFS : 0;
+	}
+	return err;
+}
+
+int au_test_perm(struct inode *hidden_inode, int mask, int dlgt)
+{
+	if (!current->fsuid)
+		return 0;
+	if (unlikely(au_is_nfs(hidden_inode->i_sb)
+		     && (mask & MAY_WRITE)
+		     && S_ISDIR(hidden_inode->i_mode)))
+		mask |= MAY_READ; /* force permission check */
+	return vfsub_permission(hidden_inode, mask, NULL, dlgt);
+}
diff -ruN linux-2.6.22/fs/aufs/misc.h linux-2.6.22-aufs/fs/aufs/misc.h
--- linux-2.6.22/fs/aufs/misc.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/misc.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: misc.h,v 1.31 2007/07/15 20:03:28 sfjro Exp $ */
+
+#ifndef __AUFS_MISC_H__
+#define __AUFS_MISC_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/sched.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
+#define I_MUTEX_QUOTA			0
+#define lockdep_off()			do {} while (0)
+#define lockdep_on()			do {} while (0)
+#define mutex_lock_nested(mtx, lsc)	mutex_lock(mtx)
+#define down_write_nested(rw, lsc)	down_write(rw)
+#define down_read_nested(rw, lsc)	down_read(rw)
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+typedef unsigned int au_gen_t;
+/* see linux/include/linux/jiffies.h */
+#define AuGenYounger(a, b)	((int)(b) - (int)(a) < 0)
+#define AuGenOlder(a, b)	AufsGenYounger(b, a)
+
+/* ---------------------------------------------------------------------- */
+
+struct aufs_rwsem {
+	struct rw_semaphore	rwsem;
+#ifdef CONFIG_AUFS_DEBUG
+	atomic_t		rcnt;
+#endif
+};
+
+#ifdef CONFIG_AUFS_DEBUG
+#define DbgRcntInit(rw) do { \
+	atomic_set(&(rw)->rcnt, 0); \
+	smp_mb(); /* atomic_set */ \
+} while (0)
+
+#define DbgRcntInc(rw)		atomic_inc_return(&(rw)->rcnt)
+#define DbgRcntDec(rw)		WARN_ON(atomic_dec_return(&(rw)->rcnt) < 0)
+#else
+#define DbgRcntInit(rw)		do {} while (0)
+#define DbgRcntInc(rw)		do {} while (0)
+#define DbgRcntDec(rw)		do {} while (0)
+#endif
+
+static inline void rw_init_nolock(struct aufs_rwsem *rw)
+{
+	DbgRcntInit(rw);
+	init_rwsem(&rw->rwsem);
+}
+
+static inline void rw_init_wlock(struct aufs_rwsem *rw)
+{
+	rw_init_nolock(rw);
+	down_write(&rw->rwsem);
+}
+
+static inline void rw_init_wlock_nested(struct aufs_rwsem *rw, unsigned int lsc)
+{
+	rw_init_nolock(rw);
+	down_write_nested(&rw->rwsem, lsc);
+}
+
+static inline void rw_read_lock(struct aufs_rwsem *rw)
+{
+	down_read(&rw->rwsem);
+	DbgRcntInc(rw);
+}
+
+static inline void rw_read_lock_nested(struct aufs_rwsem *rw, unsigned int lsc)
+{
+	down_read_nested(&rw->rwsem, lsc);
+	DbgRcntInc(rw);
+}
+
+static inline void rw_read_unlock(struct aufs_rwsem *rw)
+{
+	DbgRcntDec(rw);
+	up_read(&rw->rwsem);
+}
+
+static inline void rw_dgrade_lock(struct aufs_rwsem *rw)
+{
+	DbgRcntInc(rw);
+	downgrade_write(&rw->rwsem);
+}
+
+static inline void rw_write_lock(struct aufs_rwsem *rw)
+{
+	down_write(&rw->rwsem);
+}
+
+static inline void rw_write_lock_nested(struct aufs_rwsem *rw, unsigned int lsc)
+{
+	down_write_nested(&rw->rwsem, lsc);
+}
+
+static inline void rw_write_unlock(struct aufs_rwsem *rw)
+{
+	up_write(&rw->rwsem);
+}
+
+/* why is not _nested version defined */
+static inline int rw_read_trylock(struct aufs_rwsem *rw)
+{
+	int ret = down_read_trylock(&rw->rwsem);
+	if (ret)
+		DbgRcntInc(rw);
+	return ret;
+}
+
+static inline int rw_write_trylock(struct aufs_rwsem *rw)
+{
+	return down_write_trylock(&rw->rwsem);
+}
+
+#undef DbgRcntInit
+#undef DbgRcntInc
+#undef DbgRcntDec
+
+/* to debug easier, do not make them inlined functions */
+#define RwMustNoWaiters(rw)	AuDebugOn(!list_empty(&(rw)->rwsem.wait_list))
+#define RwMustAnyLock(rw)	AuDebugOn(down_write_trylock(&(rw)->rwsem))
+#ifdef CONFIG_AUFS_DEBUG
+#define RwMustReadLock(rw) do { \
+	RwMustAnyLock(rw); \
+	AuDebugOn(!atomic_read(&(rw)->rcnt)); \
+} while (0)
+
+#define RwMustWriteLock(rw) do { \
+	RwMustAnyLock(rw); \
+	AuDebugOn(atomic_read(&(rw)->rcnt)); \
+} while (0)
+#else
+#define RwMustReadLock(rw)	RwMustAnyLock(rw)
+#define RwMustWriteLock(rw)	RwMustAnyLock(rw)
+#endif
+
+#define SimpleLockRwsemFuncs(prefix, param, rwsem) \
+static inline void prefix##_read_lock(param) {rw_read_lock(&(rwsem));} \
+static inline void prefix##_write_lock(param) {rw_write_lock(&(rwsem));} \
+static inline int prefix##_read_trylock(param) {return rw_read_trylock(&(rwsem));} \
+static inline int prefix##_write_trylock(param) {return rw_write_trylock(&(rwsem));}
+//static inline void prefix##_read_trylock_nested(param, lsc)
+//{rw_read_trylock_nested(&(rwsem, lsc));}
+//static inline void prefix##_write_trylock_nestd(param, lsc)
+//{rw_write_trylock_nested(&(rwsem), nested);}
+
+#define SimpleUnlockRwsemFuncs(prefix, param, rwsem) \
+static inline void prefix##_read_unlock(param) {rw_read_unlock(&(rwsem));} \
+static inline void prefix##_write_unlock(param) {rw_write_unlock(&(rwsem));} \
+static inline void prefix##_downgrade_lock(param) {rw_dgrade_lock(&(rwsem));}
+
+#define SimpleRwsemFuncs(prefix, param, rwsem) \
+	SimpleLockRwsemFuncs(prefix, param, rwsem) \
+	SimpleUnlockRwsemFuncs(prefix, param, rwsem)
+
+/* ---------------------------------------------------------------------- */
+
+void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp);
+struct nameidata *fake_dm(struct nameidata *fake_nd, struct nameidata *nd,
+			  struct super_block *sb, aufs_bindex_t bindex);
+void fake_dm_release(struct nameidata *fake_nd);
+int au_copy_file(struct file *dst, struct file *src, loff_t len,
+		 struct super_block *sb, int *sparse);
+int test_ro(struct super_block *sb, aufs_bindex_t bindex, struct inode *inode);
+int au_test_perm(struct inode *h_inode, int mask, int dlgt);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_MISC_H__ */
diff -ruN linux-2.6.22/fs/aufs/module.c linux-2.6.22-aufs/fs/aufs/module.c
--- linux-2.6.22/fs/aufs/module.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/module.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: module.c,v 1.17 2007/07/15 20:05:15 sfjro Exp $ */
+
+//#include <linux/init.h>
+#include <linux/module.h>
+#include "aufs.h"
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * aufs caches
+ */
+struct kmem_cache *aufs_cachep[AuCache_Last];
+static int __init create_cache(void)
+{
+#define Cache(type) kmem_cache_create(#type, sizeof(struct type), 0, \
+				      SLAB_RECLAIM_ACCOUNT, NULL, NULL)
+
+	if ((aufs_cachep[AuCache_DINFO] = Cache(aufs_dinfo))
+	    && (aufs_cachep[AuCache_ICNTNR] = Cache(aufs_icntnr))
+	    && (aufs_cachep[AuCache_FINFO] = Cache(aufs_finfo))
+	    //&& (aufs_cachep[AuCache_FINFO] = NULL)
+	    && (aufs_cachep[AuCache_VDIR] = Cache(aufs_vdir))
+	    && (aufs_cachep[AuCache_DEHSTR] = Cache(aufs_dehstr))
+	    && (aufs_cachep[AuCache_HINOTIFY] = Cache(aufs_hinotify)))
+		return 0;
+	return -ENOMEM;
+
+#undef Cache
+}
+
+static void destroy_cache(void)
+{
+	int i;
+	for (i = 0; i < AuCache_Last; i++)
+		if (aufs_cachep[i])
+			kmem_cache_destroy(aufs_cachep[i]);
+}
+
+/* ---------------------------------------------------------------------- */
+
+char au_esc_chars[0x20 + 3]; /* 0x01-0x20, backslash, del, and NULL */
+int au_dir_roflags;
+
+#ifdef DbgDlgt
+#include <linux/security.h>
+#include "dbg_dlgt.c"
+#else
+#define dbg_dlgt_init()	0
+#define dbg_dlgt_fin()	do {} while (0)
+#endif
+
+/*
+ * functions for module interface.
+ */
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Junjiro Okajima");
+MODULE_DESCRIPTION(AUFS_NAME " -- Another unionfs");
+MODULE_VERSION(AUFS_VERSION);
+
+/* it should be 'byte', but param_set_byte() prints by "%c" */
+short aufs_nwkq = AUFS_NWKQ_DEF;
+MODULE_PARM_DESC(nwkq, "the number of workqueue thread, " AUFS_WKQ_NAME);
+module_param_named(nwkq, aufs_nwkq, short, S_IRUGO);
+
+int sysaufs_brs = 0;
+MODULE_PARM_DESC(brs, "use <sysfs>/fs/aufs/brs");
+module_param_named(brs, sysaufs_brs, int, S_IRUGO);
+
+char *aufs_sysrq_key = "a";
+#ifdef CONFIG_MAGIC_SYSRQ
+MODULE_PARM_DESC(sysrq, "MagicSysRq key for " AUFS_NAME);
+module_param_named(sysrq, aufs_sysrq_key, charp, S_IRUGO);
+#endif
+
+static int __init aufs_init(void)
+{
+	int err, i;
+	char *p;
+
+#ifdef CONFIG_AUFS_DEBUG
+	{
+		aufs_bindex_t bindex = -1;
+		AuDebugOn(bindex >= 0);
+	}
+	{
+		struct aufs_destr destr;
+		destr.len = -1;
+		AuDebugOn(destr.len < NAME_MAX);
+	}
+
+#ifdef CONFIG_4KSTACKS
+	Warn("CONFIG_4KSTACKS is defined.\n");
+#endif
+#if 0 // verbose debug
+	{
+		union {
+			struct aufs_branch *br;
+			struct aufs_dinfo *di;
+			struct aufs_finfo *fi;
+			struct aufs_iinfo *ii;
+			struct aufs_hinode *hi;
+			struct aufs_sbinfo *si;
+			struct aufs_destr *destr;
+			struct aufs_de *de;
+			struct aufs_wh *wh;
+			struct aufs_vdir *vd;
+		} u;
+
+		printk("br{"
+		       "xino %d, readf %d, writef %d, "
+		       "id %d, perm %d, mnt %d, count %d, "
+		       "wh_sem %d, wh %d, run %d} %d\n",
+		       offsetof(typeof(*u.br), br_xino),
+		       offsetof(typeof(*u.br), br_xino_read),
+		       offsetof(typeof(*u.br), br_xino_write),
+		       offsetof(typeof(*u.br), br_id),
+		       offsetof(typeof(*u.br), br_perm),
+		       offsetof(typeof(*u.br), br_mnt),
+		       offsetof(typeof(*u.br), br_count),
+		       offsetof(typeof(*u.br), br_wh_rwsem),
+		       offsetof(typeof(*u.br), br_wh),
+		       offsetof(typeof(*u.br), br_wh_running),
+		       sizeof(*u.br));
+		printk("di{gen %d, rwsem %d, bstart %d, bend %d, bwh %d, "
+		       "bdiropq %d, hdentry %d} %d\n",
+		       offsetof(typeof(*u.di), di_generation),
+		       offsetof(typeof(*u.di), di_rwsem),
+		       offsetof(typeof(*u.di), di_bstart),
+		       offsetof(typeof(*u.di), di_bend),
+		       offsetof(typeof(*u.di), di_bwh),
+		       offsetof(typeof(*u.di), di_bdiropq),
+		       offsetof(typeof(*u.di), di_hdentry),
+		       sizeof(*u.di));
+		printk("fi{gen %d, rwsem %d, hfile %d, bstart %d, bend %d, "
+		       "h_vm_ops %d, vdir_cach %d} %d\n",
+		       offsetof(typeof(*u.fi), fi_generation),
+		       offsetof(typeof(*u.fi), fi_rwsem),
+		       offsetof(typeof(*u.fi), fi_hfile),
+		       offsetof(typeof(*u.fi), fi_bstart),
+		       offsetof(typeof(*u.fi), fi_bend),
+		       offsetof(typeof(*u.fi), fi_h_vm_ops),
+		       offsetof(typeof(*u.fi), fi_vdir_cache),
+		       sizeof(*u.fi));
+		printk("ii{rwsem %d, bstart %d, bend %d, hinode %d, vdir %d} "
+		       "%d\n",
+		       offsetof(typeof(*u.ii), ii_rwsem),
+		       offsetof(typeof(*u.ii), ii_bstart),
+		       offsetof(typeof(*u.ii), ii_bend),
+		       offsetof(typeof(*u.ii), ii_hinode),
+		       offsetof(typeof(*u.ii), ii_vdir),
+		       sizeof(*u.ii));
+		printk("hi{inode %d, id %d, notify %d} %d\n",
+		       offsetof(typeof(*u.hi), hi_inode),
+		       offsetof(typeof(*u.hi), hi_id),
+		       offsetof(typeof(*u.hi), hi_notify),
+		       sizeof(*u.hi));
+		printk("si{rwsem %d, gen %d, "
+		       "failed_refresh %d, "
+		       "bend %d, last id %d, br %d, "
+		       "flags %d, "
+		       "xino %d, "
+		       "rdcache %d, "
+		       "dirwh %d, "
+		       "pl_lock %d, pl %d, "
+		       "} %d\n",
+		       offsetof(typeof(*u.si), si_rwsem),
+		       offsetof(typeof(*u.si), si_generation),
+		       -1,//offsetof(typeof(*u.si), si_failed_refresh_dirs),
+		       offsetof(typeof(*u.si), si_bend),
+		       offsetof(typeof(*u.si), si_last_br_id),
+		       offsetof(typeof(*u.si), si_branch),
+		       offsetof(typeof(*u.si), si_flags),
+		       offsetof(typeof(*u.si), si_xino),
+		       offsetof(typeof(*u.si), si_rdcache),
+		       offsetof(typeof(*u.si), si_dirwh),
+		       offsetof(typeof(*u.si), si_plink_lock),
+		       offsetof(typeof(*u.si), si_plink),
+		       sizeof(*u.si));
+		printk("destr{len %d, name %d} %d\n",
+		       offsetof(typeof(*u.destr), len),
+		       offsetof(typeof(*u.destr), name),
+		       sizeof(*u.destr));
+		printk("de{ino %d, type %d, str %d} %d\n",
+		       offsetof(typeof(*u.de), de_ino),
+		       offsetof(typeof(*u.de), de_type),
+		       offsetof(typeof(*u.de), de_str),
+		       sizeof(*u.de));
+		printk("wh{hash %d, bindex %d, str %d} %d\n",
+		       offsetof(typeof(*u.wh), wh_hash),
+		       offsetof(typeof(*u.wh), wh_bindex),
+		       offsetof(typeof(*u.wh), wh_str),
+		       sizeof(*u.wh));
+		printk("vd{deblk %d, nblk %d, last %d, ver %d, jiffy %d} %d\n",
+		       offsetof(typeof(*u.vd), vd_deblk),
+		       offsetof(typeof(*u.vd), vd_nblk),
+		       offsetof(typeof(*u.vd), vd_last),
+		       offsetof(typeof(*u.vd), vd_version),
+		       offsetof(typeof(*u.vd), vd_jiffy),
+		       sizeof(*u.vd));
+	}
+#endif
+#endif
+
+	p = au_esc_chars;
+	for (i = 1; i <= ' '; i++)
+		*p++ = i;
+	*p++ = '\\';
+	*p++ = '\x7f';
+	*p = 0;
+
+	au_dir_roflags = au_file_roflags(O_DIRECTORY | O_LARGEFILE);
+#ifndef CONFIG_AUFS_SYSAUFS
+	sysaufs_brs = 0;
+#endif
+
+	err = -EINVAL;
+	if (unlikely(aufs_nwkq <= 0))
+		goto out;
+	err = create_cache();
+	if (unlikely(err))
+		goto out;
+	err = sysaufs_init();
+	if (unlikely(err))
+		goto out_cache;
+	err = au_wkq_init();
+	if (unlikely(err))
+		goto out_sysaufs;
+	err = au_inotify_init();
+	if (unlikely(err))
+		goto out_wkq;
+	err = au_sysrq_init();
+	if (unlikely(err))
+		goto out_inotify;
+	err = dbg_dlgt_init();
+	if (unlikely(err))
+		goto out_sysrq;
+	err = register_filesystem(&aufs_fs_type);
+	if (unlikely(err))
+		goto out_dlgt;
+	printk(KERN_INFO AUFS_NAME " " AUFS_VERSION "\n");
+	return 0; /* success */
+
+ out_dlgt:
+	dbg_dlgt_fin();
+ out_sysrq:
+	au_sysrq_fin();
+ out_inotify:
+	au_inotify_fin();
+ out_wkq:
+	au_wkq_fin();
+ out_sysaufs:
+	sysaufs_fin();
+ out_cache:
+	destroy_cache();
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static void __exit aufs_exit(void)
+{
+	unregister_filesystem(&aufs_fs_type);
+	dbg_dlgt_fin();
+	au_sysrq_fin();
+	au_inotify_fin();
+	au_wkq_fin();
+	sysaufs_fin();
+	destroy_cache();
+}
+
+module_init(aufs_init);
+module_exit(aufs_exit);
+
+/* ---------------------------------------------------------------------- */
+
+/* fake Kconfig */
+#if 1
+#ifdef CONFIG_AUFS_HINOTIFY
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
+#error CONFIG_AUFS_HINOTIFY is supported in linux-2.6.18 and later.
+#endif
+#ifndef CONFIG_INOTIFY
+#error enable CONFIG_INOTIFY to use CONFIG_AUFS_HINOTIFY.
+#endif
+#endif
+
+#if AUFS_BRANCH_MAX > 511 && BITS_PER_LONG == 64 && PAGE_SIZE == 4096
+#warning For 4k pagesize and 64bit environment, \
+	CONFIG_AUFS_BRANCH_MAX_511 or smaller is recommended.
+#endif
+
+#ifdef CONFIG_AUFS_SYSAUFS
+#ifndef CONFIG_SYSFS
+#error CONFIG_AUFS_SYSAUFS requires CONFIG_SYSFS.
+#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
+#error CONFIG_AUFS_SYSAUFS requires linux-2.6.18 and later.
+#endif
+#endif
+
+#ifdef CONFIG_AUFS_EXPORT
+#if !defined(CONFIG_EXPORTFS) && !defined(CONFIG_EXPORTFS_MODULE)
+#error CONFIG_AUFS_EXPORT requires CONFIG_EXPORTFS
+#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 18)
+#error CONFIG_AUFS_EXPORT requires linux-2.6.18 and later.
+#endif
+#if defined(CONFIG_EXPORTFS_MODULE) && defined(CONFIG_AUFS)
+#error need CONFIG_EXPORTFS=y to link aufs statically with CONFIG_AUFS_EXPORT
+#endif
+#endif
+
+#if defined(CONFIG_AUFS_KSIZE_PATCH) \
+	&& LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
+#warning KSIZE_PATCH is unnecessary linux-2.6.22 and later.
+#endif
+
+#ifdef CONFIG_DEBUG_PROVE_LOCKING
+#if MAX_LOCKDEP_SUBCLASSES < AuLsc_I_End
+#warning lockdep will not work since aufs uses deeper locks.
+#endif
+#endif
+
+#ifdef CONFIG_AUFS_COMPAT
+#warning CONFIG_AUFS_COMPAT will be removed in the near future.
+#endif
+
+#endif
diff -ruN linux-2.6.22/fs/aufs/module.h linux-2.6.22-aufs/fs/aufs/module.h
--- linux-2.6.22/fs/aufs/module.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/module.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: module.h,v 1.10 2007/07/15 20:05:25 sfjro Exp $ */
+
+#ifndef __AUFS_MODULE_H__
+#define __AUFS_MODULE_H__
+
+#ifdef __KERNEL__
+
+#include <linux/slab.h>
+
+/* ---------------------------------------------------------------------- */
+
+/* module parameters */
+extern short aufs_nwkq;
+extern int sysaufs_brs;
+extern char *aufs_sysrq_key;
+
+/* ---------------------------------------------------------------------- */
+
+extern char au_esc_chars[];
+extern int au_dir_roflags;
+
+/* kmem cache */
+enum {AuCache_DINFO, AuCache_ICNTNR, AuCache_FINFO, AuCache_VDIR,
+      AuCache_DEHSTR, AuCache_HINOTIFY, AuCache_Last};
+extern struct kmem_cache *aufs_cachep[];
+
+#define CacheFuncs(name, index) \
+static inline void *cache_alloc_##name(void) \
+{return kmem_cache_alloc(aufs_cachep[index], GFP_KERNEL);} \
+static inline void cache_free_##name(void *p) \
+{kmem_cache_free(aufs_cachep[index], p);}
+
+CacheFuncs(dinfo, AuCache_DINFO);
+CacheFuncs(icntnr, AuCache_ICNTNR);
+CacheFuncs(finfo, AuCache_FINFO);
+CacheFuncs(vdir, AuCache_VDIR);
+CacheFuncs(dehstr, AuCache_DEHSTR);
+CacheFuncs(hinotify, AuCache_HINOTIFY);
+
+#undef CacheFuncs
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_MODULE_H__ */
diff -ruN linux-2.6.22/fs/aufs/opts.c linux-2.6.22-aufs/fs/aufs/opts.c
--- linux-2.6.22/fs/aufs/opts.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/opts.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,1141 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: opts.c,v 1.41 2007/07/15 20:05:40 sfjro Exp $ */
+
+#include <linux/types.h> /* a distribution requires */
+#include <linux/parser.h>
+#include "aufs.h"
+
+enum {
+	Opt_br,
+	Opt_add, Opt_del, Opt_mod, Opt_append, Opt_prepend,
+	Opt_idel, Opt_imod,
+	Opt_dirwh, Opt_rdcache, Opt_deblk, Opt_nhash, Opt_rendir,
+	Opt_xino, Opt_zxino, Opt_noxino,
+	Opt_trunc_xib, Opt_notrunc_xib,
+	Opt_plink, Opt_noplink, Opt_list_plink, Opt_clean_plink,
+	Opt_udba,
+	Opt_diropq_a, Opt_diropq_w,
+	Opt_warn_perm, Opt_nowarn_perm,
+	Opt_findrw_dir, Opt_findrw_br,
+	Opt_coo,
+	Opt_dlgt, Opt_nodlgt,
+	Opt_refrof, Opt_norefrof,
+	Opt_verbose, Opt_noverbose,
+	Opt_tail, Opt_ignore, Opt_err
+};
+
+static match_table_t options = {
+	{Opt_br, "br=%s"},
+	{Opt_br, "br:%s"},
+
+	{Opt_add, "add=%d:%s"},
+	{Opt_add, "add:%d:%s"},
+	{Opt_add, "ins=%d:%s"},
+	{Opt_add, "ins:%d:%s"},
+	{Opt_append, "append=%s"},
+	{Opt_append, "append:%s"},
+	{Opt_prepend, "prepend=%s"},
+	{Opt_prepend, "prepend:%s"},
+
+	{Opt_del, "del=%s"},
+	{Opt_del, "del:%s"},
+	//{Opt_idel, "idel:%d"},
+	{Opt_mod, "mod=%s"},
+	{Opt_mod, "mod:%s"},
+	{Opt_imod, "imod:%d:%s"},
+
+	{Opt_dirwh, "dirwh=%d"},
+	{Opt_dirwh, "dirwh:%d"},
+
+	{Opt_xino, "xino=%s"},
+	{Opt_xino, "xino:%s"},
+	{Opt_noxino, "noxino"},
+	//{Opt_zxino, "zxino=%s"},
+	{Opt_trunc_xib, "trunc_xib"},
+	{Opt_notrunc_xib, "notrunc_xib"},
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
+	{Opt_plink, "plink"},
+	{Opt_noplink, "noplink"},
+#ifdef CONFIG_AUFS_DEBUG
+	{Opt_list_plink, "list_plink"},
+#endif
+	{Opt_clean_plink, "clean_plink"},
+#endif
+
+	{Opt_udba, "udba=%s"},
+
+	{Opt_diropq_a, "diropq=always"},
+	{Opt_diropq_a, "diropq=a"},
+	{Opt_diropq_w, "diropq=whiteouted"},
+	{Opt_diropq_w, "diropq=w"},
+
+	{Opt_warn_perm, "warn_perm"},
+	{Opt_nowarn_perm, "nowarn_perm"},
+
+#ifdef CONFIG_AUFS_DLGT
+	{Opt_dlgt, "dlgt"},
+	{Opt_nodlgt, "nodlgt"},
+#endif
+
+	{Opt_rendir, "rendir=%d"},
+	{Opt_rendir, "rendir:%d"},
+
+	{Opt_refrof, "refrof"},
+	{Opt_norefrof, "norefrof"},
+
+	{Opt_verbose, "verbose"},
+	{Opt_verbose, "v"},
+	{Opt_noverbose, "noverbose"},
+	{Opt_noverbose, "quiet"},
+	{Opt_noverbose, "q"},
+	{Opt_noverbose, "silent"},
+
+	{Opt_rdcache, "rdcache=%d"},
+	{Opt_rdcache, "rdcache:%d"},
+
+	{Opt_coo, "coo=%s"},
+#if 0
+	{Opt_findrw_dir, "findrw=dir"},
+	{Opt_findrw_br, "findrw=br"},
+
+	{Opt_deblk, "deblk=%d"},
+	{Opt_deblk, "deblk:%d"},
+	{Opt_nhash, "nhash=%d"},
+	{Opt_nhash, "nhash:%d"},
+#endif
+
+	{Opt_br, "dirs=%s"},
+	{Opt_ignore, "debug=%d"},
+	{Opt_ignore, "delete=whiteout"},
+	{Opt_ignore, "delete=all"},
+	{Opt_ignore, "imap=%s"},
+
+	{Opt_err, NULL}
+};
+
+/* ---------------------------------------------------------------------- */
+
+#define RW		"rw"
+#define RO		"ro"
+#define WH		"wh"
+#define RR		"rr"
+#define NoLinkWH	"nolwh"
+
+static match_table_t brperms = {
+	{AuBr_RR, RR},
+	{AuBr_RO, RO},
+	{AuBr_RW, RW},
+
+	{AuBr_RRWH, RR "+" WH},
+	{AuBr_ROWH, RO "+" WH},
+	{AuBr_RWNoLinkWH, RW "+" NoLinkWH},
+
+	{AuBr_ROWH, "nfsro"},
+	{AuBr_RO, NULL}
+};
+
+static int br_perm_val(char *perm)
+{
+	int val;
+	substring_t args[MAX_OPT_ARGS];
+
+	AuDebugOn(!perm || !*perm);
+	LKTRTrace("perm %s\n", perm);
+	val = match_token(perm, brperms, args);
+	TraceErr(val);
+	return val;
+}
+
+int br_perm_str(char *p, unsigned int len, int brperm)
+{
+	struct match_token *bp = brperms;
+
+	LKTRTrace("len %d, 0x%x\n", len, brperm);
+
+	while (bp->pattern) {
+		if (bp->token == brperm) {
+			if (strlen(bp->pattern) < len) {
+				strcpy(p, bp->pattern);
+				return 0;
+			} else
+				return -E2BIG;
+		}
+		bp++;
+	}
+
+	return -EIO;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static match_table_t udbalevel = {
+	{AuFlag_UDBA_REVAL, "reval"},
+#ifdef CONFIG_AUFS_HINOTIFY
+	{AuFlag_UDBA_INOTIFY, "inotify"},
+#endif
+	{AuFlag_UDBA_NONE, "none"},
+	{-1, NULL}
+};
+
+static int udba_val(char *str)
+{
+	substring_t args[MAX_OPT_ARGS];
+	return match_token(str, udbalevel, args);
+}
+
+au_parser_pattern_t udba_str(int udba)
+{
+	struct match_token *p = udbalevel;
+	while (p->pattern) {
+		if (p->token == udba)
+			return p->pattern;
+		p++;
+	}
+	BUG();
+	return "??";
+}
+
+void udba_set(struct super_block *sb, unsigned int flg)
+{
+	au_flag_clr(sb, AuMask_UDBA);
+	au_flag_set(sb, flg);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static match_table_t coolevel = {
+	{AuFlag_COO_LEAF, "leaf"},
+	{AuFlag_COO_ALL, "all"},
+	{AuFlag_COO_NONE, "none"},
+	{-1, NULL}
+};
+
+static int coo_val(char *str)
+{
+	substring_t args[MAX_OPT_ARGS];
+	return match_token(str, coolevel, args);
+}
+
+au_parser_pattern_t coo_str(int coo)
+{
+	struct match_token *p = coolevel;
+	while (p->pattern) {
+		if (p->token == coo)
+			return p->pattern;
+		p++;
+	}
+	BUG();
+	return "??";
+}
+static void coo_set(struct super_block *sb, unsigned int flg)
+{
+	au_flag_clr(sb, AuMask_COO);
+	au_flag_set(sb, flg);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static const int lkup_dirflags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
+
+#ifdef CONFIG_AUFS_DEBUG
+static void dump_opts(struct opts *opts)
+{
+	/* reduce stack space */
+	union {
+		struct opt_add *add;
+		struct opt_del *del;
+		struct opt_mod *mod;
+		struct opt_xino *xino;
+	} u;
+	struct opt *opt;
+
+	TraceEnter();
+
+	opt = opts->opt;
+	while (/* opt < opts_tail && */ opt->type != Opt_tail) {
+		switch (opt->type) {
+		case Opt_add:
+			u.add = &opt->add;
+			LKTRTrace("add {b%d, %s, 0x%x, %p}\n",
+				  u.add->bindex, u.add->path, u.add->perm,
+				  u.add->nd.dentry);
+			break;
+		case Opt_del:
+		case Opt_idel:
+			u.del = &opt->del;
+			LKTRTrace("del {%s, %p}\n", u.del->path, u.del->h_root);
+			break;
+		case Opt_mod:
+		case Opt_imod:
+			u.mod = &opt->mod;
+			LKTRTrace("mod {%s, 0x%x, %p}\n",
+				  u.mod->path, u.mod->perm, u.mod->h_root);
+			break;
+		case Opt_append:
+			u.add = &opt->add;
+			LKTRTrace("append {b%d, %s, 0x%x, %p}\n",
+				  u.add->bindex, u.add->path, u.add->perm,
+				  u.add->nd.dentry);
+			break;
+		case Opt_prepend:
+			u.add = &opt->add;
+			LKTRTrace("prepend {b%d, %s, 0x%x, %p}\n",
+				  u.add->bindex, u.add->path, u.add->perm,
+				  u.add->nd.dentry);
+			break;
+		case Opt_dirwh:
+			LKTRTrace("dirwh %d\n", opt->dirwh);
+			break;
+		case Opt_rdcache:
+			LKTRTrace("rdcache %d\n", opt->rdcache);
+			break;
+		case Opt_xino:
+			u.xino = &opt->xino;
+			LKTRTrace("xino {%s %.*s}\n",
+				  u.xino->path,
+				  DLNPair(u.xino->file->f_dentry));
+			break;
+		case Opt_noxino:
+			LKTRLabel(noxino);
+			break;
+		case Opt_trunc_xib:
+			LKTRLabel(trunc_xib);
+			break;
+		case Opt_notrunc_xib:
+			LKTRLabel(notrunc_xib);
+			break;
+		case Opt_plink:
+			LKTRLabel(plink);
+			break;
+		case Opt_noplink:
+			LKTRLabel(noplink);
+			break;
+		case Opt_list_plink:
+			LKTRLabel(list_plink);
+			break;
+		case Opt_clean_plink:
+			LKTRLabel(clean_plink);
+			break;
+		case Opt_udba:
+			LKTRTrace("udba %d, %s\n",
+				  opt->udba, udba_str(opt->udba));
+			break;
+		case Opt_diropq_a:
+			LKTRLabel(diropq_a);
+			break;
+		case Opt_diropq_w:
+			LKTRLabel(diropq_w);
+			break;
+		case Opt_warn_perm:
+			LKTRLabel(warn_perm);
+			break;
+		case Opt_nowarn_perm:
+			LKTRLabel(nowarn_perm);
+			break;
+		case Opt_dlgt:
+			LKTRLabel(dlgt);
+			break;
+		case Opt_nodlgt:
+			LKTRLabel(nodlgt);
+			break;
+		case Opt_refrof:
+			LKTRLabel(refrof);
+			break;
+		case Opt_norefrof:
+			LKTRLabel(norefrof);
+			break;
+		case Opt_verbose:
+			LKTRLabel(verbose);
+			break;
+		case Opt_noverbose:
+			LKTRLabel(noverbose);
+			break;
+		case Opt_coo:
+			LKTRTrace("coo %d, %s\n", opt->coo, coo_str(opt->coo));
+			break;
+		default:
+			BUG();
+		}
+		opt++;
+	}
+}
+#else
+#define dump_opts(opts) do {} while (0)
+#endif
+
+void au_opts_free(struct opts *opts)
+{
+	struct opt *opt;
+
+	TraceEnter();
+
+	opt = opts->opt;
+	while (opt->type != Opt_tail) {
+		switch (opt->type) {
+		case Opt_add:
+		case Opt_append:
+		case Opt_prepend:
+			path_release(&opt->add.nd);
+			break;
+		case Opt_del:
+		case Opt_idel:
+			dput(opt->del.h_root);
+			break;
+		case Opt_mod:
+		case Opt_imod:
+			dput(opt->mod.h_root);
+			break;
+		case Opt_xino:
+			fput(opt->xino.file);
+			break;
+		}
+		opt++;
+	}
+}
+
+static int opt_add(struct opt *opt, char *opt_str, struct super_block *sb,
+		   aufs_bindex_t bindex)
+{
+	int err;
+	struct opt_add *add = &opt->add;
+	char *p;
+
+	LKTRTrace("%s, b%d\n", opt_str, bindex);
+
+	add->bindex = bindex;
+	add->perm = AuBr_Last;
+	add->path = opt_str;
+	p = strchr(opt_str, '=');
+	if (unlikely(p)) {
+		*p++ = 0;
+		if (*p)
+			add->perm = br_perm_val(p);
+	}
+
+	/* LSM may detect it */
+	/* do not superio. */
+	err = path_lookup(add->path, lkup_dirflags, &add->nd);
+	//err = -1;
+	if (!err) {
+		if (!p /* && add->perm == AuBr_Last */) {
+			add->perm = AuBr_RO;
+			if (au_is_rr(add->nd.dentry->d_sb))
+				add->perm = AuBr_RR;
+			if (!bindex && !(sb->s_flags & MS_RDONLY))
+				add->perm = AuBr_RW;
+#ifdef CONFIG_AUFS_COMPAT
+			add->perm = AuBr_RW;
+#endif
+		}
+		opt->type = Opt_add;
+		goto out;
+	}
+	Err("lookup failed %s (%d)\n", add->path, err);
+	err = -EINVAL;
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* called without aufs lock */
+int au_opts_parse(struct super_block *sb, char *str, struct opts *opts)
+{
+	int err, n, token, skipped;
+	struct dentry *root;
+	struct opt *opt, *opt_tail;
+	char *opt_str, *p;
+	substring_t args[MAX_OPT_ARGS];
+	aufs_bindex_t bindex;
+	struct nameidata nd;
+	union {
+		struct opt_del *del;
+		struct opt_mod *mod;
+		struct opt_xino *xino;
+	} u;
+	struct file *file;
+
+
+	LKTRTrace("%s, nopts %d\n", str, opts->max_opt);
+
+	root = sb->s_root;
+	err = 0;
+	bindex = 0;
+	opt = opts->opt;
+	opt_tail = opt + opts->max_opt - 1;
+	opt->type = Opt_tail;
+	while (!err && (opt_str = strsep(&str, ",")) && *opt_str) {
+		err = -EINVAL;
+		token = match_token(opt_str, options, args);
+		LKTRTrace("%s, token %d, args[0]{%p, %p}\n",
+			  opt_str, token, args[0].from, args[0].to);
+
+		skipped = 0;
+		switch (token) {
+		case Opt_br:
+			err = 0;
+			while (!err && (opt_str = strsep(&args[0].from, ":"))
+			       && *opt_str) {
+				err = opt_add(opt, opt_str, sb, bindex++);
+				//if (LktrCond) err = -1;
+				if (unlikely(!err && ++opt > opt_tail)) {
+					err = -E2BIG;
+					break;
+				}
+				opt->type = Opt_tail;
+				skipped = 1;
+			}
+			break;
+		case Opt_add:
+			if (unlikely(match_int(&args[0], &n))) {
+				Err("bad integer in %s\n", opt_str);
+				break;
+			}
+			bindex = n;
+			err = opt_add(opt, args[1].from, sb, bindex);
+			break;
+		case Opt_append:
+			err = opt_add(opt, args[0].from, sb, /*dummy bindex*/1);
+			if (!err)
+				opt->type = token;
+			break;
+		case Opt_prepend:
+			err = opt_add(opt, args[0].from, sb, /*bindex*/0);
+			if (!err)
+				opt->type = token;
+			break;
+		case Opt_del:
+			u.del = &opt->del;
+			u.del->path = args[0].from;
+			LKTRTrace("del path %s\n", u.del->path);
+			/* LSM may detect it */
+			/* do not superio. */
+			err = path_lookup(u.del->path, lkup_dirflags, &nd);
+			if (unlikely(err)) {
+				Err("lookup failed %s (%d)\n",
+				    u.del->path, err);
+				break;
+			}
+			u.del->h_root = dget(nd.dentry);
+			path_release(&nd);
+			opt->type = token;
+			break;
+#if 0
+		case Opt_idel:
+			u.del = &opt->del;
+			u.del->path = "(indexed)";
+			if (unlikely(match_int(&args[0], &n))) {
+				Err("bad integer in %s\n", opt_str);
+				break;
+			}
+			bindex = n;
+			aufs_read_lock(root, AuLock_FLUSH);
+			if (bindex < 0 || sbend(sb) < bindex) {
+				Err("out of bounds, %d\n", bindex);
+				aufs_read_unlock(root, !AuLock_IR);
+				break;
+			}
+			err = 0;
+			u.del->h_root = dget(au_h_dptr_i(root, bindex));
+			opt->type = token;
+			aufs_read_unlock(root, !AuLock_IR);
+			break;
+#endif
+		case Opt_mod:
+			u.mod = &opt->mod;
+			u.mod->path = args[0].from;
+			p = strchr(u.mod->path, '=');
+			if (unlikely(!p)) {
+				Err("no permssion %s\n", opt_str);
+				break;
+			}
+			*p++ = 0;
+			u.mod->perm = br_perm_val(p);
+			LKTRTrace("mod path %s, perm 0x%x, %s\n",
+				  u.mod->path, u.mod->perm, p);
+			/* LSM may detect it */
+			/* do not superio. */
+			err = path_lookup(u.mod->path, lkup_dirflags, &nd);
+			if (unlikely(err)) {
+				Err("lookup failed %s (%d)\n",
+				    u.mod->path, err);
+				break;
+			}
+			u.mod->h_root = dget(nd.dentry);
+			path_release(&nd);
+			opt->type = token;
+			break;
+#ifdef IMOD
+		case Opt_imod:
+			u.mod = &opt->mod;
+			u.mod->path = "(indexed)";
+			if (unlikely(match_int(&args[0], &n))) {
+				Err("bad integer in %s\n", opt_str);
+				break;
+			}
+			bindex = n;
+			aufs_read_lock(root, AuLock_FLUSH);
+			if (bindex < 0 || sbend(sb) < bindex) {
+				Err("out of bounds, %d\n", bindex);
+				aufs_read_unlock(root, !AuLock_IR);
+				break;
+			}
+			u.mod->perm = br_perm_val(args[1].from);
+			LKTRTrace("mod path %s, perm 0x%x, %s\n",
+				  u.mod->path, u.mod->perm, args[1].from);
+			err = 0;
+			u.mod->h_root = dget(au_h_dptr_i(root, bindex));
+			opt->type = token;
+			aufs_read_unlock(root, !AuLock_IR);
+			break;
+#endif
+		case Opt_xino:
+			u.xino = &opt->xino;
+			file = xino_create(sb, args[0].from, /*silent*/0,
+					   /*parent*/NULL);
+			err = PTR_ERR(file);
+			if (IS_ERR(file))
+				break;
+			err = -EINVAL;
+			if (unlikely(file->f_dentry->d_sb == sb)) {
+				fput(file);
+				Err("%s must be outside\n", args[0].from);
+				break;
+			}
+			err = 0;
+			u.xino->file = file;
+			u.xino->path = args[0].from;
+			opt->type = token;
+			break;
+
+		case Opt_dirwh:
+			if (unlikely(match_int(&args[0], &opt->dirwh)))
+				break;
+			err = 0;
+			opt->type = token;
+			break;
+
+		case Opt_rdcache:
+			if (unlikely(match_int(&args[0], &opt->rdcache)))
+				break;
+			err = 0;
+			opt->type = token;
+			break;
+
+		case Opt_noxino:
+		case Opt_trunc_xib:
+		case Opt_notrunc_xib:
+		case Opt_plink:
+		case Opt_noplink:
+		case Opt_list_plink:
+		case Opt_clean_plink:
+		case Opt_diropq_a:
+		case Opt_diropq_w:
+		case Opt_warn_perm:
+		case Opt_nowarn_perm:
+		case Opt_dlgt:
+		case Opt_nodlgt:
+		case Opt_refrof:
+		case Opt_norefrof:
+		case Opt_verbose:
+		case Opt_noverbose:
+			err = 0;
+			opt->type = token;
+			break;
+
+		case Opt_udba:
+			opt->udba = udba_val(args[0].from);
+			if (opt->udba >= 0) {
+				err = 0;
+				opt->type = token;
+			}
+			break;
+
+		case Opt_coo:
+			opt->coo = coo_val(args[0].from);
+			if (opt->coo >= 0) {
+				err = 0;
+				opt->type = token;
+			}
+			break;
+
+		case Opt_ignore:
+#ifndef CONFIG_AUFS_COMPAT
+			Warn("ignored %s\n", opt_str);
+#endif
+			skipped = 1;
+			err = 0;
+			break;
+		case Opt_err:
+			Err("unknown option %s\n", opt_str);
+			break;
+		}
+
+		if (!err && !skipped) {
+			if (unlikely(++opt > opt_tail)) {
+				err = -E2BIG;
+				opt--;
+				opt->type = Opt_tail;
+				break;
+			}
+			opt->type = Opt_tail;
+		}
+	}
+
+	dump_opts(opts);
+	if (unlikely(err))
+		au_opts_free(opts);
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * returns,
+ * plus: processed without an error
+ * zero: unprocessed
+ */
+static int au_opt_simple(struct super_block *sb, struct opt *opt,
+			 struct opts *opts)
+{
+	int err;
+	struct aufs_sbinfo *sbinfo = stosi(sb);
+
+	TraceEnter();
+
+	err = 1; /* handled */
+	switch (opt->type) {
+	case Opt_udba:
+		udba_set(sb, opt->udba);
+		opts->given |= opt->udba;
+		break;
+
+	case Opt_plink:
+		au_flag_set(sb, AuFlag_PLINK);
+		opts->given |= AuFlag_PLINK;
+		break;
+	case Opt_noplink:
+		if (au_flag_test(sb, AuFlag_PLINK))
+			au_put_plink(sb);
+		au_flag_clr(sb, AuFlag_PLINK);
+		opts->given |= AuFlag_PLINK;
+		break;
+	case Opt_list_plink:
+		if (au_flag_test(sb, AuFlag_PLINK))
+			au_list_plink(sb);
+		break;
+	case Opt_clean_plink:
+		if (au_flag_test(sb, AuFlag_PLINK))
+			au_put_plink(sb);
+		break;
+
+	case Opt_diropq_a:
+		au_flag_set(sb, AuFlag_ALWAYS_DIROPQ);
+		opts->given |= AuFlag_ALWAYS_DIROPQ;
+		break;
+	case Opt_diropq_w:
+		au_flag_clr(sb, AuFlag_ALWAYS_DIROPQ);
+		opts->given |= AuFlag_ALWAYS_DIROPQ;
+		break;
+
+	case Opt_dlgt:
+		au_flag_set(sb, AuFlag_DLGT);
+		opts->given |= AuFlag_DLGT;
+		break;
+	case Opt_nodlgt:
+		au_flag_clr(sb, AuFlag_DLGT);
+		opts->given |= AuFlag_DLGT;
+		break;
+
+	case Opt_warn_perm:
+		au_flag_set(sb, AuFlag_WARN_PERM);
+		opts->given |= AuFlag_WARN_PERM;
+		break;
+	case Opt_nowarn_perm:
+		au_flag_clr(sb, AuFlag_WARN_PERM);
+		opts->given |= AuFlag_WARN_PERM;
+		break;
+
+	case Opt_refrof:
+		au_flag_set(sb, AuFlag_REFROF);
+		opts->given |= AuFlag_REFROF;
+		break;
+	case Opt_norefrof:
+		au_flag_clr(sb, AuFlag_REFROF);
+		coo_set(sb, AuFlag_COO_LEAF);
+		opts->given |= AuFlag_REFROF;
+		break;
+
+	case Opt_verbose:
+		au_flag_set(sb, AuFlag_VERBOSE);
+		opts->given |= AuFlag_VERBOSE;
+		break;
+	case Opt_noverbose:
+		au_flag_clr(sb, AuFlag_VERBOSE);
+		opts->given |= AuFlag_VERBOSE;
+		break;
+
+	case Opt_coo:
+		coo_set(sb, opt->coo);
+		opts->given |= opt->coo;
+		break;
+
+	case Opt_dirwh:
+		sbinfo->si_dirwh = opt->dirwh;
+		break;
+
+	case Opt_rdcache:
+		sbinfo->si_rdcache = opt->rdcache * HZ;
+		break;
+
+	case Opt_trunc_xib:
+		opts->trunc_xib = 1;
+		break;
+	case Opt_notrunc_xib:
+		opts->trunc_xib = 0;
+		break;
+
+	default:
+		err = 0;
+		break;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * returns tri-state.
+ * plus: processed without an error
+ * zero: unprocessed
+ * minus: error
+ */
+static int au_opt_br(struct super_block *sb, struct opt *opt, struct opts *opts)
+{
+	int err, do_refresh;
+
+	TraceEnter();
+
+	err = 0;
+	switch (opt->type) {
+	case Opt_append:
+		opt->add.bindex = sbend(sb) + 1;
+		if (unlikely(opt->add.bindex < 0))
+			opt->add.bindex = 0;
+		goto add;
+	case Opt_prepend:
+		opt->add.bindex = 0;
+	add:
+	case Opt_add:
+		err = br_add(sb, &opt->add, opts->remount);
+		if (!err) {
+			err = 1;
+			opts->refresh_dir = 1;
+			if (unlikely(br_whable(opt->add.perm)))
+				opts->refresh_nondir = 1;
+		}
+		break;
+
+	case Opt_del:
+	case Opt_idel:
+		err = br_del(sb, &opt->del, opts->remount);
+		if (!err) {
+			err = 1;
+			opts->trunc_xib
+				= opts->refresh_dir
+				= opts->refresh_nondir
+				= 1;
+		}
+		break;
+
+	case Opt_mod:
+	case Opt_imod:
+		err = br_mod(sb, &opt->mod, opts->remount, &do_refresh);
+		if (!err) {
+			err = 1;
+			if (unlikely(do_refresh))
+				opts->refresh_dir = opts->refresh_nondir = 1;
+		}
+		break;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+static int au_opt_xino(struct super_block *sb, struct opt *opt,
+		       struct opt_xino **opt_xino, struct opts *opts)
+{
+	int err;
+
+	TraceEnter();
+
+	err = 0;
+	switch (opt->type) {
+	case Opt_xino:
+		err = xino_set(sb, &opt->xino, opts->remount);
+		if (!err)
+			*opt_xino = &opt->xino;
+		break;
+	case Opt_noxino:
+		xino_clr(sb);
+		break;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+static int verify_opts(struct super_block *sb, unsigned int pending,
+		       int remount)
+
+{
+	int err;
+	aufs_bindex_t bindex, bend;
+	struct aufs_branch *br;
+	struct dentry *root;
+	struct inode *dir;
+	unsigned int do_plink;
+
+	TraceEnter();
+
+	if (unlikely(!(sb->s_flags & MS_RDONLY)
+		     && !br_writable(sbr_perm(sb, 0))))
+		Warn("first branch should be rw\n");
+
+	if (unlikely((au_flag_test_udba_inotify(sb)
+		      || (pending & AuFlag_UDBA_INOTIFY))
+		     && !au_flag_test(sb, AuFlag_XINO)))
+		Warn("udba=inotify requires xino\n");
+
+	err = 0;
+	root = sb->s_root;
+	dir = sb->s_root->d_inode;
+	do_plink = au_flag_test(sb, AuFlag_PLINK);
+	bend = sbend(sb);
+	for (bindex = 0; !err && bindex <= bend; bindex++) {
+		struct inode *h_dir;
+		int skip;
+
+		skip = 0;
+		h_dir = au_h_iptr_i(dir, bindex);
+		br = stobr(sb, bindex);
+		br_wh_read_lock(br);
+		switch (br->br_perm) {
+		case AuBr_RR:
+		case AuBr_RO:
+		case AuBr_RRWH:
+		case AuBr_ROWH:
+			skip = (!br->br_wh && !br->br_plink);
+			break;
+
+		case AuBr_RWNoLinkWH:
+			skip = !br->br_wh;
+			if (skip) {
+				if (do_plink)
+					skip = !!br->br_plink;
+				else
+					skip = !br->br_plink;
+			}
+			break;
+
+		case AuBr_RW:
+			skip = !!br->br_wh;
+			if (skip) {
+				if (do_plink)
+					skip = !!br->br_plink;
+				else
+					skip = !br->br_plink;
+			}
+			break;
+
+		default:
+			BUG();
+		}
+		br_wh_read_unlock(br);
+
+		if (skip)
+			continue;
+
+		hdir_lock(h_dir, dir, bindex);
+		br_wh_write_lock(br);
+		err = init_wh(au_h_dptr_i(root, bindex), br,
+			      au_nfsmnt(sb, bindex), sb);
+		br_wh_write_unlock(br);
+		hdir_unlock(h_dir, dir, bindex);
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+int au_opts_mount(struct super_block *sb, struct opts *opts)
+{
+	int err;
+	struct inode *dir;
+	struct opt *opt;
+	unsigned int flags;
+	struct opt_xino *opt_xino;
+	aufs_bindex_t bend;
+
+	TraceEnter();
+	SiMustWriteLock(sb);
+	DiMustWriteLock(sb->s_root);
+	dir = sb->s_root->d_inode;
+	IiMustWriteLock(dir);
+
+	err = 0;
+	opt_xino = NULL;
+	opt = opts->opt;
+	while (err >= 0 && opt->type != Opt_tail)
+		err = au_opt_simple(sb, opt++, opts);
+	if (err > 0)
+		err = 0;
+	else if (unlikely(err < 0))
+		goto out;
+
+	/* disable them temporary */
+	flags = au_flag_test(sb, AuFlag_XINO | AuMask_UDBA | AuFlag_DLGT);
+	au_flag_clr(sb, AuFlag_XINO | AuFlag_DLGT);
+	udba_set(sb, AuFlag_UDBA_REVAL);
+
+	opt = opts->opt;
+	while (err >= 0 && opt->type != Opt_tail)
+		err = au_opt_br(sb, opt++, opts);
+	if (err > 0)
+		err = 0;
+	else if (unlikely(err < 0))
+		goto out;
+
+	bend = sbend(sb);
+	if (unlikely(bend < 0)) {
+		err = -EINVAL;
+		Err("no branches\n");
+		goto out;
+	}
+
+	if (flags & AuFlag_XINO)
+		au_flag_set(sb, AuFlag_XINO);
+	opt = opts->opt;
+	while (!err && opt->type != Opt_tail)
+		err = au_opt_xino(sb, opt++, &opt_xino, opts);
+	if (unlikely(err))
+		goto out;
+
+	//todo: test this error case.
+	err = verify_opts(sb, flags & (AuMask_UDBA | AuFlag_DLGT),
+			  /*remount*/0);
+	AuDebugOn(err);
+	if (unlikely(err))
+		goto out;
+
+	/* enable xino */
+	if (au_flag_test(sb, AuFlag_XINO) && !opt_xino) {
+		struct opt_xino xino;
+
+		xino.file = xino_def(sb);
+		err = PTR_ERR(xino.file);
+		if (IS_ERR(xino.file))
+			goto out;
+
+		err = xino_set(sb, &xino, /*remount*/0);
+		fput(xino.file);
+		if (unlikely(err))
+			goto out;
+	}
+
+	/* restore hinotify */
+	udba_set(sb, flags & AuMask_UDBA);
+	if (flags & AuFlag_UDBA_INOTIFY)
+		au_reset_hinotify(dir, au_hi_flags(dir, 1) & ~AUFS_HI_XINO);
+
+	/* restore dlgt */
+	if (flags & AuFlag_DLGT)
+		au_flag_set(sb, AuFlag_DLGT);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+int au_opts_remount(struct super_block *sb, struct opts *opts)
+{
+	int err, rerr;
+	struct inode *dir;
+	struct opt_xino *opt_xino;
+	struct opt *opt;
+	unsigned int dlgt;
+
+	TraceEnter();
+	SiMustWriteLock(sb);
+	DiMustWriteLock(sb->s_root);
+	dir = sb->s_root->d_inode;
+	IiMustWriteLock(dir);
+	//AuDebugOn(au_flag_test_udba_inotify(sb));
+
+	err = 0;
+	dlgt = au_flag_test(sb, AuFlag_DLGT);
+	opt_xino = NULL;
+	opt = opts->opt;
+	while (err >= 0 && opt->type != Opt_tail) {
+		err = au_opt_simple(sb, opt, opts);
+
+		/* disable it temporary */
+		dlgt = au_flag_test(sb, AuFlag_DLGT);
+		au_flag_clr(sb, AuFlag_DLGT);
+
+		if (!err)
+			err = au_opt_br(sb, opt, opts);
+		if (!err)
+			err = au_opt_xino(sb, opt, &opt_xino, opts);
+
+		/* restore it */
+		au_flag_set(sb, dlgt);
+		opt++;
+	}
+	if (err > 0)
+		err = 0;
+	TraceErr(err);
+
+	/* go on even err */
+
+	//todo: test this error case.
+	au_flag_clr(sb, AuFlag_DLGT);
+	rerr = verify_opts(sb, dlgt, /*remount*/1);
+	au_flag_set(sb, dlgt);
+	if (unlikely(rerr && !err))
+		err = rerr;
+
+	if (unlikely(opts->trunc_xib)) {
+		rerr = xib_trunc(sb);
+		if (unlikely(rerr && !err))
+			err = rerr;
+	}
+
+	/* they are handled by the caller */
+	if (!opts->refresh_dir)
+		opts->refresh_dir = !!((opts->given & AuMask_UDBA)
+				       || au_flag_test(sb, AuFlag_XINO));
+
+	LKTRTrace("status {%d, %d, %d}\n", opts->refresh_dir,
+		  opts->refresh_nondir, opts->trunc_xib);
+	TraceErr(err);
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/opts.h linux-2.6.22-aufs/fs/aufs/opts.h
--- linux-2.6.22/fs/aufs/opts.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/opts.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: opts.h,v 1.18 2007/07/15 20:05:48 sfjro Exp $ */
+
+#ifndef __AUFS_OPTS_H__
+#define __AUFS_OPTS_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
+typedef const char *au_parser_pattern_t;
+#else
+typedef char *au_parser_pattern_t;
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+struct opt_add {
+	aufs_bindex_t		bindex;
+	char			*path;
+	int			perm;
+	struct nameidata	nd;
+};
+
+struct opt_del {
+	char		*path;
+	struct dentry	*h_root;
+};
+
+struct opt_mod {
+	char		*path;
+	int		perm;
+	struct dentry	*h_root;
+};
+
+struct opt_xino {
+	char		*path;
+	struct file	*file;
+};
+
+struct opt {
+	int type;
+	union {
+		struct opt_xino	xino;
+		struct opt_add	add;
+		struct opt_del	del;
+		struct opt_mod	mod;
+		int		dirwh;
+		int		rdcache;
+		int		deblk;
+		int		nhash;
+		int		udba;
+		int		coo;
+	};
+};
+
+struct opts {
+	struct opt	*opt;
+	int		max_opt;
+
+	unsigned int given;
+	struct {
+		unsigned int remount:1;
+
+		unsigned int refresh_dir:1;
+		unsigned int refresh_nondir:1;
+		unsigned int trunc_xib:1;
+	};
+};
+
+/* ---------------------------------------------------------------------- */
+
+int br_perm_str(char *p, unsigned int len, int brperm);
+au_parser_pattern_t udba_str(int udba);
+void udba_set(struct super_block *sb, unsigned int flg);
+au_parser_pattern_t coo_str(int coo);
+
+void au_opts_free(struct opts *opts);
+int au_opts_parse(struct super_block *sb, char *str, struct opts *opts);
+int au_opts_mount(struct super_block *sb, struct opts *opts);
+int au_opts_remount(struct super_block *sb, struct opts *opts);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_OPTS_H__ */
diff -ruN linux-2.6.22/fs/aufs/plink.c linux-2.6.22-aufs/fs/aufs/plink.c
--- linux-2.6.22/fs/aufs/plink.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/plink.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: plink.c,v 1.9 2007/07/15 20:03:45 sfjro Exp $ */
+
+#include "aufs.h"
+
+struct pseudo_link {
+	struct list_head list;
+	struct inode *inode;
+};
+
+#ifdef CONFIG_AUFS_DEBUG
+void au_list_plink(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+	struct list_head *plink_list;
+	struct pseudo_link *plink;
+
+	TraceEnter();
+	SiMustAnyLock(sb);
+	AuDebugOn(!au_flag_test(sb, AuFlag_PLINK));
+
+	sbinfo = stosi(sb);
+	plink_list = &sbinfo->si_plink;
+	spin_lock(&sbinfo->si_plink_lock);
+	list_for_each_entry(plink, plink_list, list)
+		Dbg("%lu\n", plink->inode->i_ino);
+	spin_unlock(&sbinfo->si_plink_lock);
+}
+#endif
+
+int au_is_plinked(struct super_block *sb, struct inode *inode)
+{
+	int found;
+	struct aufs_sbinfo *sbinfo;
+	struct list_head *plink_list;
+	struct pseudo_link *plink;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	SiMustAnyLock(sb);
+	AuDebugOn(!au_flag_test(sb, AuFlag_PLINK));
+
+	found = 0;
+	sbinfo = stosi(sb);
+	plink_list = &sbinfo->si_plink;
+	spin_lock(&sbinfo->si_plink_lock);
+	list_for_each_entry(plink, plink_list, list)
+		if (plink->inode == inode) {
+			found = 1;
+			break;
+		}
+	spin_unlock(&sbinfo->si_plink_lock);
+	return found;
+}
+
+/* 20 is max digits length of ulong 64 */
+#define PLINK_NAME_LEN	((20 + 1) * 2)
+
+static int plink_name(char *name, int len, struct inode *inode,
+		      aufs_bindex_t bindex)
+{
+	int rlen;
+	struct inode *h_inode;
+
+	LKTRTrace("i%lu, b%d\n", inode->i_ino, bindex);
+	AuDebugOn(len != PLINK_NAME_LEN);
+	h_inode = au_h_iptr_i(inode, bindex);
+	AuDebugOn(!h_inode);
+	rlen = snprintf(name, len, "%lu.%lu", inode->i_ino, h_inode->i_ino);
+	AuDebugOn(rlen >= len);
+	return rlen;
+}
+
+struct dentry *lkup_plink(struct super_block *sb, aufs_bindex_t bindex,
+			  struct inode *inode)
+{
+	struct dentry *h_dentry, *h_parent;
+	struct aufs_branch *br;
+	struct inode *h_dir;
+	char tgtname[PLINK_NAME_LEN];
+	int len;
+	struct lkup_args lkup;
+
+	LKTRTrace("b%d, i%lu\n", bindex, inode->i_ino);
+	br = stobr(sb, bindex);
+	h_parent = br->br_plink;
+	AuDebugOn(!h_parent);
+	h_dir = h_parent->d_inode;
+	AuDebugOn(!h_dir);
+
+	len = plink_name(tgtname, sizeof(tgtname), inode, bindex);
+
+	/* always superio. */
+	lkup.nfsmnt = au_do_nfsmnt(br->br_mnt);
+	lkup.dlgt = need_dlgt(sb);
+	vfsub_i_lock_nested(h_dir, AuLsc_I_CHILD2);
+	h_dentry = sio_lkup_one(tgtname, h_parent, len, &lkup);
+	vfsub_i_unlock(h_dir);
+	return h_dentry;
+}
+
+static int do_whplink(char *tgt, int len, struct dentry *h_parent,
+		      struct dentry *h_dentry, struct vfsmount *nfsmnt,
+		      struct super_block *sb)
+{
+	int err;
+	struct dentry *h_tgt;
+	struct inode *h_dir;
+	struct lkup_args lkup = {
+		.nfsmnt = nfsmnt,
+		.dlgt	= need_dlgt(sb)
+	};
+
+	h_tgt = lkup_one(tgt, h_parent, len, &lkup);
+	err = PTR_ERR(h_tgt);
+	if (IS_ERR(h_tgt))
+		goto out;
+
+	err = 0;
+	h_dir = h_parent->d_inode;
+	if (unlikely(h_tgt->d_inode && h_tgt->d_inode != h_dentry->d_inode))
+		err = vfsub_unlink(h_dir, h_tgt, lkup.dlgt);
+	if (!err && !h_tgt->d_inode) {
+		err = vfsub_link(h_dentry, h_dir, h_tgt, lkup.dlgt);
+		//inode->i_nlink++;
+	}
+	dput(h_tgt);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+struct do_whplink_args {
+	int *errp;
+	char *tgt;
+	int len;
+	struct dentry *h_parent;
+	struct dentry *h_dentry;
+	struct vfsmount *nfsmnt;
+	struct super_block *sb;
+};
+
+static void call_do_whplink(void *args)
+{
+	struct do_whplink_args *a = args;
+	*a->errp = do_whplink(a->tgt, a->len, a->h_parent, a->h_dentry,
+			      a->nfsmnt, a->sb);
+}
+
+static int whplink(struct dentry *h_dentry, struct inode *inode,
+		   aufs_bindex_t bindex, struct super_block *sb)
+{
+	int err, len, wkq_err;
+	struct aufs_branch *br;
+	struct dentry *h_parent;
+	struct inode *h_dir;
+	char tgtname[PLINK_NAME_LEN];
+
+	LKTRTrace("%.*s\n", DLNPair(h_dentry));
+	br = stobr(inode->i_sb, bindex);
+	h_parent = br->br_plink;
+	AuDebugOn(!h_parent);
+	h_dir = h_parent->d_inode;
+	AuDebugOn(!h_dir);
+
+	len = plink_name(tgtname, sizeof(tgtname), inode, bindex);
+
+	/* always superio. */
+	vfsub_i_lock_nested(h_dir, AuLsc_I_CHILD2);
+	if (!is_au_wkq(current)) {
+		struct do_whplink_args args = {
+			.errp		= &err,
+			.tgt		= tgtname,
+			.len		= len,
+			.h_parent	= h_parent,
+			.h_dentry	= h_dentry,
+			.nfsmnt		= au_do_nfsmnt(br->br_mnt),
+			.sb		= sb
+		};
+		wkq_err = au_wkq_wait(call_do_whplink, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	} else
+		err = do_whplink(tgtname, len, h_parent, h_dentry,
+				 au_do_nfsmnt(br->br_mnt), sb);
+	vfsub_i_unlock(h_dir);
+
+	TraceErr(err);
+	return err;
+}
+
+void append_plink(struct super_block *sb, struct inode *inode,
+		  struct dentry *h_dentry, aufs_bindex_t bindex)
+{
+	struct aufs_sbinfo *sbinfo;
+	struct list_head *plink_list;
+	struct pseudo_link *plink;
+	int found, err, cnt;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+	SiMustAnyLock(sb);
+	AuDebugOn(!au_flag_test(sb, AuFlag_PLINK));
+
+	cnt = 0;
+	found = 0;
+	sbinfo = stosi(sb);
+	plink_list = &sbinfo->si_plink;
+	spin_lock(&sbinfo->si_plink_lock);
+	list_for_each_entry(plink, plink_list, list) {
+		cnt++;
+		if (plink->inode == inode) {
+			found = 1;
+			break;
+		}
+	}
+
+	err = 0;
+	if (!found) {
+		plink = kmalloc(sizeof(*plink), GFP_ATOMIC);
+		if (plink) {
+			plink->inode = igrab(inode);
+			list_add(&plink->list, plink_list);
+			cnt++;
+		} else
+			err = -ENOMEM;
+	}
+	spin_unlock(&sbinfo->si_plink_lock);
+
+	if (!err)
+		err = whplink(h_dentry, inode, bindex, sb);
+
+	if (unlikely(cnt > 100))
+		Warn1("unexpectedly many pseudo links, %d\n", cnt);
+	if (unlikely(err))
+		Warn("err %d, damaged pseudo link. ignored.\n", err);
+}
+
+static void do_put_plink(struct pseudo_link *plink, int do_del)
+{
+	TraceEnter();
+
+	iput(plink->inode);
+	if (do_del)
+		list_del(&plink->list);
+	kfree(plink);
+}
+
+void au_put_plink(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+	struct list_head *plink_list;
+	struct pseudo_link *plink, *tmp;
+
+	TraceEnter();
+	SiMustWriteLock(sb);
+	AuDebugOn(!au_flag_test(sb, AuFlag_PLINK));
+
+	sbinfo = stosi(sb);
+	plink_list = &sbinfo->si_plink;
+	//spin_lock(&sbinfo->si_plink_lock);
+	list_for_each_entry_safe(plink, tmp, plink_list, list)
+		do_put_plink(plink, 0);
+	INIT_LIST_HEAD(plink_list);
+	//spin_unlock(&sbinfo->si_plink_lock);
+}
+
+void half_refresh_plink(struct super_block *sb, aufs_bindex_t br_id)
+{
+	struct aufs_sbinfo *sbinfo;
+	struct list_head *plink_list;
+	struct pseudo_link *plink, *tmp;
+	struct inode *inode;
+	aufs_bindex_t bstart, bend, bindex;
+	int do_put;
+
+	TraceEnter();
+	SiMustWriteLock(sb);
+	AuDebugOn(!au_flag_test(sb, AuFlag_PLINK));
+
+	sbinfo = stosi(sb);
+	plink_list = &sbinfo->si_plink;
+	//spin_lock(&sbinfo->si_plink_lock);
+	list_for_each_entry_safe(plink, tmp, plink_list, list) {
+		do_put = 0;
+		inode = igrab(plink->inode);
+		ii_write_lock_child(inode);
+		bstart = ibstart(inode);
+		bend = ibend(inode);
+		if (bstart >= 0) {
+			for (bindex = bstart; bindex <= bend; bindex++) {
+				if (!au_h_iptr_i(inode, bindex)
+				    || itoid_index(inode, bindex) != br_id)
+					continue;
+				set_h_iptr(inode, bindex, NULL, 0);
+				do_put = 1;
+				break;
+			}
+		} else
+			do_put_plink(plink, 1);
+
+		if (do_put) {
+			for (bindex = bstart; bindex <= bend; bindex++)
+				if (au_h_iptr_i(inode, bindex)) {
+					do_put = 0;
+					break;
+				}
+			if (do_put)
+				do_put_plink(plink, 1);
+		}
+		ii_write_unlock(inode);
+		iput(inode);
+	}
+	//spin_unlock(&sbinfo->si_plink_lock);
+}
diff -ruN linux-2.6.22/fs/aufs/sbinfo.c linux-2.6.22-aufs/fs/aufs/sbinfo.c
--- linux-2.6.22/fs/aufs/sbinfo.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/sbinfo.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: sbinfo.c,v 1.39 2007/07/15 20:03:28 sfjro Exp $ */
+
+#include "aufs.h"
+
+struct aufs_sbinfo *stosi(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+	sbinfo = sb->s_fs_info;
+	//AuDebugOn(sbinfo->si_bend < 0);
+	return sbinfo;
+}
+
+aufs_bindex_t sbend(struct super_block *sb)
+{
+	SiMustAnyLock(sb);
+	return stosi(sb)->si_bend;
+}
+
+struct aufs_branch *stobr(struct super_block *sb, aufs_bindex_t bindex)
+{
+	SiMustAnyLock(sb);
+	AuDebugOn(bindex < 0 || sbend(sb) < bindex
+		  || !stosi(sb)->si_branch[0 + bindex]);
+	return stosi(sb)->si_branch[0 + bindex];
+}
+
+au_gen_t au_sigen(struct super_block *sb)
+{
+	SiMustAnyLock(sb);
+	return stosi(sb)->si_generation;
+}
+
+au_gen_t au_sigen_inc(struct super_block *sb)
+{
+	au_gen_t gen;
+
+	SiMustWriteLock(sb);
+	gen = ++stosi(sb)->si_generation;
+	au_update_digen(sb->s_root);
+	au_update_iigen(sb->s_root->d_inode);
+	sb->s_root->d_inode->i_version++;
+	return gen;
+}
+
+int find_bindex(struct super_block *sb, struct aufs_branch *br)
+{
+	aufs_bindex_t bindex, bend;
+
+	bend = sbend(sb);
+	for (bindex = 0; bindex <= bend; bindex++)
+		if (stobr(sb, bindex) == br)
+			return bindex;
+	return -1;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* dentry and super_block lock. call at entry point */
+void aufs_read_lock(struct dentry *dentry, int flags)
+{
+	si_read_lock(dentry->d_sb, flags);
+	if (flags & AuLock_DW)
+		di_write_lock_child(dentry);
+	else
+		di_read_lock_child(dentry, flags);
+}
+
+void aufs_read_unlock(struct dentry *dentry, int flags)
+{
+	if (flags & AuLock_DW)
+		di_write_unlock(dentry);
+	else
+		di_read_unlock(dentry, flags);
+	si_read_unlock(dentry->d_sb);
+}
+
+void aufs_write_lock(struct dentry *dentry)
+{
+	si_write_lock(dentry->d_sb);
+	di_write_lock_child(dentry);
+}
+
+void aufs_write_unlock(struct dentry *dentry)
+{
+	di_write_unlock(dentry);
+	si_write_unlock(dentry->d_sb);
+}
+
+void aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int flags)
+{
+	AuDebugOn(d1 == d2 || d1->d_sb != d2->d_sb);
+	si_read_lock(d1->d_sb, flags);
+	di_write_lock2_child(d1, d2, flags);
+}
+
+void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2)
+{
+	AuDebugOn(d1 == d2 || d1->d_sb != d2->d_sb);
+	di_write_unlock2(d1, d2);
+	si_read_unlock(d1->d_sb);
+}
+
+/* ---------------------------------------------------------------------- */
+
+aufs_bindex_t new_br_id(struct super_block *sb)
+{
+	aufs_bindex_t br_id;
+
+	TraceEnter();
+	SiMustWriteLock(sb);
+
+	while (1) {
+		br_id = ++stosi(sb)->si_last_br_id;
+		if (br_id && find_brindex(sb, br_id) < 0)
+			return br_id;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_SYSAUFS
+static int make_xino(struct seq_file *seq, struct sysaufs_args *args,
+		      int *do_size)
+{
+	int err;
+	struct super_block *sb = args->sb;
+	aufs_bindex_t bindex, bend;
+	struct file *xf;
+	struct inode *xi;
+
+	TraceEnter();
+	AuDebugOn(args->index != SysaufsSb_XINO);
+	SiMustReadLock(sb);
+
+	err = 0;
+	*do_size = 0;
+	if (unlikely(!au_flag_test(sb, AuFlag_XINO))) {
+#ifdef CONFIG_AUFS_DEBUG
+		AuDebugOn(stosi(sb)->si_xib);
+		bend = sbend(sb);
+		for (bindex = 0; !err && bindex <= bend; bindex++)
+			AuDebugOn(stobr(sb, bindex)->br_xino);
+#endif
+		return err;
+	}
+
+	xf = stosi(sb)->si_xib;
+	xi = xf->f_dentry->d_inode;
+	err = seq_printf(seq, "%Lux%d %Ld\n", (u64)xi->i_blocks,
+			 1 << xi->i_blkbits, i_size_read(xi));
+
+	bend = sbend(sb);
+	for (bindex = 0; !err && bindex <= bend; bindex++) {
+		xf = stobr(sb, bindex)->br_xino;
+		xi = xf->f_dentry->d_inode;
+		err = seq_printf(seq, "%d: %d, %Lux%d %Ld\n",
+				 bindex, file_count(xf),
+				 (u64)xi->i_blocks, 1 << xi->i_blkbits,
+				 i_size_read(xi));
+	}
+	TraceErr(err);
+	return err;
+}
+
+sysaufs_op au_si_ops[] = {
+	[SysaufsSb_XINO] = make_xino
+};
+#endif
diff -ruN linux-2.6.22/fs/aufs/super.c linux-2.6.22-aufs/fs/aufs/super.c
--- linux-2.6.22/fs/aufs/super.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/super.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,942 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: super.c,v 1.58 2007/07/15 20:06:03 sfjro Exp $ */
+
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/smp_lock.h>
+#include <linux/statfs.h>
+
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
+#include <linux/mnt_namespace.h>
+typedef struct mnt_namespace au_mnt_ns_t;
+#define au_nsproxy(tsk)	(tsk)->nsproxy
+#define au_mnt_ns(tsk)	(tsk)->nsproxy->mnt_ns
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
+#include <linux/namespace.h>
+typedef struct namespace au_mnt_ns_t;
+#define au_nsproxy(tsk)	(tsk)->nsproxy
+#define au_mnt_ns(tsk)	(tsk)->nsproxy->namespace
+#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#include <linux/namespace.h>
+typedef struct namespace au_mnt_ns_t;
+#define au_nsproxy(tsk)	(tsk)->namespace
+#define au_mnt_ns(tsk)	(tsk)->namespace
+#endif
+
+#include "aufs.h"
+
+/*
+ * super_operations
+ */
+static struct inode *aufs_alloc_inode(struct super_block *sb)
+{
+	struct aufs_icntnr *c;
+
+	TraceEnter();
+
+	c = cache_alloc_icntnr();
+	//if (LktrCond) {cache_free_icntnr(c); c = NULL;}
+	if (c) {
+		inode_init_once(&c->vfs_inode);
+		c->vfs_inode.i_version = 1; //sigen(sb);
+		c->iinfo.ii_hinode = NULL;
+		return &c->vfs_inode;
+	}
+	return NULL;
+}
+
+static void aufs_destroy_inode(struct inode *inode)
+{
+	LKTRTrace("i%lu\n", inode->i_ino);
+	au_iinfo_fin(inode);
+	cache_free_icntnr(container_of(inode, struct aufs_icntnr, vfs_inode));
+}
+
+//todo: how about merge with alloc_inode()?
+static void aufs_read_inode(struct inode *inode)
+{
+	int err;
+
+	LKTRTrace("i%lu\n", inode->i_ino);
+
+	err = au_iinfo_init(inode);
+	//if (LktrCond) err = -1;
+	if (!err) {
+		inode->i_version++;
+		inode->i_op = &aufs_iop;
+		inode->i_fop = &aufs_file_fop;
+		inode->i_mapping->a_ops = &aufs_aop;
+		return; /* success */
+	}
+
+	LKTRTrace("intializing inode info failed(%d)\n", err);
+	make_bad_inode(inode);
+}
+
+int au_show_brs(struct seq_file *seq, struct super_block *sb)
+{
+	int err;
+	aufs_bindex_t bindex, bend;
+	char a[16];
+	struct dentry *root;
+
+	TraceEnter();
+	SiMustAnyLock(sb);
+	root = sb->s_root;
+	DiMustAnyLock(root);
+
+	err = 0;
+	bend = sbend(sb);
+	for (bindex = 0; !err && bindex <= bend; bindex++) {
+		err = br_perm_str(a, sizeof(a), sbr_perm(sb, bindex));
+		if (!err)
+			err = seq_path(seq, sbr_mnt(sb, bindex),
+				       au_h_dptr_i(root, bindex), au_esc_chars);
+		if (err > 0)
+			err = seq_printf(seq, "=%s", a);
+		if (!err && bindex != bend)
+			err = seq_putc(seq, ':');
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+static int aufs_show_options(struct seq_file *m, struct vfsmount *mnt)
+{
+	int err, n;
+	struct super_block *sb;
+	struct aufs_sbinfo *sbinfo;
+	struct dentry *root;
+	struct file *xino;
+
+	TraceEnter();
+
+	sb = mnt->mnt_sb;
+	root = sb->s_root;
+	aufs_read_lock(root, !AuLock_IR);
+	if (au_flag_test(sb, AuFlag_XINO)) {
+		err = seq_puts(m, ",xino=");
+		if (unlikely(err))
+			goto out;
+		xino = stobr(sb, 0)->br_xino;
+		err = seq_path(m, xino->f_vfsmnt, xino->f_dentry, au_esc_chars);
+		if (unlikely(err <= 0))
+			goto out;
+		err = 0;
+
+#define Deleted "\\040(deleted)"
+		m->count -= sizeof(Deleted) - 1;
+		AuDebugOn(memcmp(m->buf + m->count, Deleted,
+				 sizeof(Deleted) - 1));
+#undef Deleted
+	} else
+		err = seq_puts(m, ",noxino");
+
+	n = au_flag_test(sb, AuFlag_PLINK);
+	if (unlikely(!err && (AuDefFlags & AuFlag_PLINK) != n))
+		err = seq_printf(m, ",%splink", n ? "" : "no");
+	n = au_flag_test_udba(sb);
+	if (unlikely(!err && (AuDefFlags & AuMask_UDBA) != n))
+		err = seq_printf(m, ",udba=%s", udba_str(n));
+	n = au_flag_test(sb, AuFlag_ALWAYS_DIROPQ);
+	if (unlikely(!err && (AuDefFlags & AuFlag_ALWAYS_DIROPQ) != n))
+		err = seq_printf(m, ",diropq=%c", n ? 'a' : 'w');
+	n = au_flag_test(sb, AuFlag_DLGT);
+	if (unlikely(!err && (AuDefFlags & AuFlag_DLGT) != n))
+		err = seq_printf(m, ",%sdlgt", n ? "" : "no");
+	n = au_flag_test(sb, AuFlag_WARN_PERM);
+	if (unlikely(!err && (AuDefFlags & AuFlag_WARN_PERM) != n))
+		err = seq_printf(m, ",%swarn_perm", n ? "" : "no");
+	n = au_flag_test(sb, AuFlag_REFROF);
+	if (unlikely(!err && (AuDefFlags & AuFlag_REFROF) != n))
+		err = seq_printf(m, ",%srefrof", n ? "" : "no");
+	n = au_flag_test(sb, AuFlag_VERBOSE);
+	if (unlikely(!err && (AuDefFlags & AuFlag_VERBOSE) != n))
+		err = seq_printf(m, ",%sverbose", n ? "" : "no");
+
+	sbinfo = stosi(sb);
+	n = sbinfo->si_dirwh;
+	if (unlikely(!err && n != AUFS_DIRWH_DEF))
+		err = seq_printf(m, ",dirwh=%d", n);
+	n = sbinfo->si_rdcache / HZ;
+	if (unlikely(!err && n != AUFS_RDCACHE_DEF))
+		err = seq_printf(m, ",rdcache=%d", n);
+	n = au_flag_test_coo(sb);
+	if (unlikely(!err && (AuDefFlags & AuMask_COO) != n))
+		err = seq_printf(m, ",coo=%s", coo_str(n));
+
+	if (!err && !sysaufs_brs) {
+#ifdef CONFIG_AUFS_COMPAT
+		err = seq_puts(m, ",dirs=");
+#else
+		err = seq_puts(m, ",br:");
+#endif
+		if (!err)
+			err = au_show_brs(m, sb);
+	}
+
+ out:
+	aufs_read_unlock(root, !AuLock_IR);
+	TraceErr(err);
+	if (err)
+		err = -E2BIG;
+	TraceErr(err);
+	return err;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#define StatfsLock(d)	aufs_read_lock((d)->d_sb->s_root, 0)
+#define StatfsUnlock(d)	aufs_read_unlock((d)->d_sb->s_root, 0)
+#define StatfsArg(d)	au_h_dptr((d)->d_sb->s_root)
+#define StatfsHInode(d)	(StatfsArg(d)->d_inode)
+#define StatfsSb(d)	((d)->d_sb)
+static int aufs_statfs(struct dentry *arg, struct kstatfs *buf)
+#else
+#define StatfsLock(s)	si_read_lock(s, !AuLock_FLUSH)
+#define StatfsUnlock(s)	si_read_unlock(s)
+#define StatfsArg(s)	sbr_sb(s, 0)
+#define StatfsHInode(s)	(StatfsArg(s)->s_root->d_inode)
+#define StatfsSb(s)	(s)
+static int aufs_statfs(struct super_block *arg, struct kstatfs *buf)
+#endif
+{
+	int err;
+
+	TraceEnter();
+
+	StatfsLock(arg);
+	err = vfsub_statfs(StatfsArg(arg), buf, need_dlgt(StatfsSb(arg)));
+	//if (LktrCond) err = -1;
+	StatfsUnlock(arg);
+	if (!err) {
+		/* buf->f_type = AUFS_SUPER_MAGIC; */
+		buf->f_type = 0;
+		buf->f_namelen -= AUFS_WH_PFX_LEN;
+		memset(&buf->f_fsid, 0, sizeof(buf->f_fsid));
+	}
+	/* buf->f_bsize = buf->f_blocks = buf->f_bfree = buf->f_bavail = -1; */
+
+	TraceErr(err);
+	return err;
+}
+
+static void au_update_mnt(struct vfsmount *mnt)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+	struct vfsmount *pos;
+	struct super_block *sb = mnt->mnt_sb;
+	struct dentry *root = sb->s_root;
+	struct aufs_sbinfo *sbinfo = stosi(sb);
+	au_mnt_ns_t *ns;
+
+	TraceEnter();
+	AuDebugOn(!kernel_locked());
+
+	if (sbinfo->si_mnt != mnt
+	    || atomic_read(&sb->s_active) == 1
+	    || !au_nsproxy(current))
+		return;
+
+	/* no get/put */
+	ns = au_mnt_ns(current);
+	AuDebugOn(!ns);
+	sbinfo->si_mnt = NULL;
+	list_for_each_entry(pos, &ns->list, mnt_list)
+		if (pos != mnt && pos->mnt_sb->s_root == root) {
+			sbinfo->si_mnt = pos;
+			break;
+		}
+	AuDebugOn(!sbinfo->si_mnt);
+#endif
+}
+
+#define UmountBeginHasMnt \
+	(LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18) \
+	|| defined(UbuntuEdgy17Umount18))
+
+#if UmountBeginHasMnt
+#define UmountBeginSb(mnt)	(mnt)->mnt_sb
+#define UmountBeginMnt(mnt)	(mnt)
+static void aufs_umount_begin(struct vfsmount *arg, int flags)
+#else
+#define UmountBeginSb(sb)	sb
+#define UmountBeginMnt(sb)	NULL
+static void aufs_umount_begin(struct super_block *arg)
+#endif
+{
+	struct super_block *sb = UmountBeginSb(arg);
+	struct vfsmount *mnt = UmountBeginMnt(arg);
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+
+	sbinfo = stosi(sb);
+	if (unlikely(!sbinfo))
+		return;
+
+	si_write_lock(sb);
+	if (au_flag_test(sb, AuFlag_PLINK))
+		au_put_plink(sb);
+#if 0
+	if (unlikely(au_flag_test(sb, AuFlag_UDBA_INOTIFY)))
+		shrink_dcache_sb(sb);
+#endif
+	au_update_mnt(mnt);
+	si_write_unlock(sb);
+}
+
+static void free_sbinfo(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+	sbinfo = stosi(sb);
+	AuDebugOn(!sbinfo
+		  || !list_empty(&sbinfo->si_plink)
+		);
+
+	si_write_lock(sb);
+	xino_clr(sb);
+	free_branches(sbinfo);
+	kfree(sbinfo->si_branch);
+	si_write_unlock(sb);
+	kfree(sbinfo);
+}
+
+/* final actions when unmounting a file system */
+static void aufs_put_super(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+
+	sbinfo = stosi(sb);
+	if (unlikely(!sbinfo))
+		return;
+
+	sysaufs_del(sbinfo);
+
+#if !UmountBeginHasMnt
+	/* umount_begin() may not be called. */
+	aufs_umount_begin(sb);
+#endif
+	free_sbinfo(sb);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * refresh dentry and inode at remount time.
+ */
+static int do_refresh(struct dentry *dentry, mode_t type,
+		      unsigned int dir_flags)
+{
+	int err;
+	struct dentry *parent;
+	struct inode *inode;
+
+	LKTRTrace("%.*s, 0%o\n", DLNPair(dentry), type);
+	inode = dentry->d_inode;
+	AuDebugOn(!inode);
+
+	di_write_lock_child(dentry);
+	parent = dget_parent(dentry);
+	di_read_lock_parent(parent, AuLock_IR);
+	/* returns a number of positive dentries */
+	err = au_refresh_hdentry(dentry, type);
+	//err = -1;
+	if (err >= 0) {
+		err = au_refresh_hinode(inode, dentry);
+		//err = -1;
+		if (unlikely(!err && type == S_IFDIR))
+			au_reset_hinotify(inode, dir_flags);
+	}
+	if (unlikely(err))
+		Err("unrecoverable error %d, %.*s\n", err, DLNPair(dentry));
+	di_read_unlock(parent, AuLock_IR);
+	dput(parent);
+	di_write_unlock(dentry);
+
+	TraceErr(err);
+	return err;
+}
+
+static int test_dir(struct dentry *dentry, void *arg)
+{
+	return S_ISDIR(dentry->d_inode->i_mode);
+}
+
+static int refresh_dir(struct dentry *root, au_gen_t sgen)
+{
+	int err, i, j, ndentry, e;
+	const unsigned int flags = au_hi_flags(root->d_inode, /*isdir*/1);
+	struct au_dcsub_pages dpages;
+	struct au_dpage *dpage;
+	struct dentry **dentries;
+	struct inode *inode;
+
+	LKTRTrace("sgen %d\n", sgen);
+	SiMustWriteLock(root->d_sb);
+	AuDebugOn(au_digen(root) != sgen
+		  || !kernel_locked());
+
+	err = 0;
+	list_for_each_entry(inode, &root->d_sb->s_inodes, i_sb_list)
+		if (unlikely(S_ISDIR(inode->i_mode)
+			     && au_iigen(inode) != sgen)) {
+			ii_write_lock_child(inode);
+			e = au_refresh_hinode_self(inode);
+			//e = -1;
+			ii_write_unlock(inode);
+			if (unlikely(e)) {
+				LKTRTrace("e %d, i%lu\n", e, inode->i_ino);
+				if (!err)
+					err = e;
+				/* go on even if err */
+			}
+		}
+
+	e = au_dpages_init(&dpages, GFP_KERNEL);
+	if (unlikely(e)) {
+		if (!err)
+			err = e;
+		goto out;
+	}
+	e = au_dcsub_pages(&dpages, root, test_dir, NULL);
+	if (unlikely(e)) {
+		if (!err)
+			err = e;
+		goto out_dpages;
+	}
+
+	e = 0;
+	for (i = 0; !e && i < dpages.ndpage; i++) {
+		dpage = dpages.dpages + i;
+		dentries = dpage->dentries;
+		ndentry = dpage->ndentry;
+		for (j = 0; !e && j < ndentry; j++) {
+			struct dentry *d;
+			d = dentries[j];
+#ifdef CONFIG_AUFS_DEBUG
+			{
+				struct dentry *parent;
+				parent = dget_parent(d);
+				AuDebugOn(!S_ISDIR(d->d_inode->i_mode)
+					  || IS_ROOT(d)
+					  || au_digen(parent) != sgen);
+				dput(parent);
+			}
+#endif
+			if (au_digen(d) != sgen) {
+				e = do_refresh(d, S_IFDIR, flags);
+				//e = -1;
+				if (unlikely(e && !err))
+					err = e;
+				/* break on err */
+			}
+		}
+	}
+
+ out_dpages:
+	au_dpages_free(&dpages);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int test_nondir(struct dentry *dentry, void *arg)
+{
+	return !S_ISDIR(dentry->d_inode->i_mode);
+}
+
+static int refresh_nondir(struct dentry *root, au_gen_t sgen, int do_dentry)
+{
+	int err, i, j, ndentry, e;
+	struct au_dcsub_pages dpages;
+	struct au_dpage *dpage;
+	struct dentry **dentries;
+	struct inode *inode;
+
+	LKTRTrace("sgen %d\n", sgen);
+	SiMustWriteLock(root->d_sb);
+	AuDebugOn(au_digen(root) != sgen
+		  || !kernel_locked());
+
+	err = 0;
+	list_for_each_entry(inode, &root->d_sb->s_inodes, i_sb_list)
+		if (unlikely(!S_ISDIR(inode->i_mode)
+			     && au_iigen(inode) != sgen)) {
+			ii_write_lock_child(inode);
+			e = au_refresh_hinode_self(inode);
+			//e = -1;
+			ii_write_unlock(inode);
+			if (unlikely(e)) {
+				LKTRTrace("e %d, i%lu\n", e, inode->i_ino);
+				if (!err)
+					err = e;
+				/* go on even if err */
+			}
+		}
+
+	if (unlikely(!do_dentry))
+		goto out;
+
+	e = au_dpages_init(&dpages, GFP_KERNEL);
+	if (unlikely(e)) {
+		if (!err)
+			err = e;
+		goto out;
+	}
+	e = au_dcsub_pages(&dpages, root, test_nondir, NULL);
+	if (unlikely(e)) {
+		if (!err)
+			err = e;
+		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];
+#ifdef CONFIG_AUFS_DEBUG
+			{
+				struct dentry *parent;
+				parent = dget_parent(d);
+				AuDebugOn(S_ISDIR(d->d_inode->i_mode)
+					  || au_digen(parent) != sgen);
+				dput(parent);
+			}
+#endif
+			inode = d->d_inode;
+			if (inode && au_digen(d) != sgen) {
+				e = do_refresh(d, inode->i_mode & S_IFMT, 0);
+				//e = -1;
+				if (unlikely(e && !err))
+					err = e;
+				/* go on even err */
+			}
+		}
+	}
+
+ out_dpages:
+	au_dpages_free(&dpages);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* stop extra interpretation of errno in mount(8), and strange error messages */
+static int cvt_err(int err)
+{
+	TraceErr(err);
+
+	switch (err) {
+	case -ENOENT:
+	case -ENOTDIR:
+	case -EEXIST:
+	case -EIO:
+		err = -EINVAL;
+	}
+	return err;
+}
+
+/* protected by s_umount */
+static int aufs_remount_fs(struct super_block *sb, int *flags, char *data)
+{
+	int err;
+	struct dentry *root;
+	struct inode *inode;
+	struct opts opts;
+	unsigned int dlgt;
+	struct aufs_sbinfo *sbinfo;
+
+	//au_debug_on();
+	LKTRTrace("flags 0x%x, data %s, len %lu\n",
+		  *flags, data ? data : "NULL",
+		  (unsigned long)(data ? strlen(data) : 0));
+
+	err = 0;
+	if (unlikely(!data || !*data))
+		goto out; /* success */
+
+	err = -ENOMEM;
+	memset(&opts, 0, sizeof(opts));
+	opts.opt = (void*)__get_free_page(GFP_KERNEL);
+	//if (LktrCond) {free_page((unsigned long)opts.opt); opts.opt = NULL;}
+	if (unlikely(!opts.opt))
+		goto out;
+	opts.max_opt = PAGE_SIZE / sizeof(*opts.opt);
+	opts.remount = 1;
+
+	/* parse it before aufs lock */
+	err = au_opts_parse(sb, data, &opts);
+	//if (LktrCond) {au_free_opts(&opts); err = -1;}
+	if (unlikely(err))
+		goto out_opts;
+
+	sbinfo = stosi(sb);
+	root = sb->s_root;
+	inode = root->d_inode;
+	vfsub_i_lock(inode);
+	aufs_write_lock(root);
+
+	//DbgSleep(3);
+
+	/* au_do_opts() may return an error */
+	err = au_opts_remount(sb, &opts);
+	//if (LktrCond) err = -1;
+	au_opts_free(&opts);
+
+	if (opts.refresh_dir || opts.refresh_nondir) {
+		int rerr;
+		au_gen_t sigen;
+
+		dlgt = au_flag_test(sb, AuFlag_DLGT);
+		au_flag_clr(sb, AuFlag_DLGT);
+		au_sigen_inc(sb);
+		au_reset_hinotify(inode, au_hi_flags(inode, /*isdir*/1));
+		sigen = au_sigen(sb);
+		sbinfo->si_failed_refresh_dirs = 0;
+
+		DiMustNoWaiters(root);
+		IiMustNoWaiters(root->d_inode);
+		di_write_unlock(root);
+
+		rerr = refresh_dir(root, sigen);
+		if (unlikely(rerr)) {
+			sbinfo->si_failed_refresh_dirs = 1;
+			Warn("Refreshing directories failed, ignores (%d)\n",
+			     rerr);
+		}
+
+		if (unlikely(opts.refresh_nondir)) {
+			//au_debug_on();
+			rerr = refresh_nondir(root, sigen, !rerr);
+			if (unlikely(rerr))
+				Warn("Refreshing non-directories failed,"
+				     " ignores (%d)\n", rerr);
+			//au_debug_off();
+		}
+
+		/* aufs_write_lock() calls ..._child() */
+		di_write_lock_child(root);
+
+		au_cpup_attr_all(inode);
+		au_flag_set(sb, dlgt);
+	}
+
+	aufs_write_unlock(root);
+	vfsub_i_unlock(inode);
+	if (opts.refresh_dir)
+		sysaufs_notify_remount();
+
+ out_opts:
+	free_page((unsigned long)opts.opt);
+ out:
+	err = cvt_err(err);
+	TraceErr(err);
+	//au_debug_off();
+	return err;
+}
+
+static struct super_operations aufs_sop = {
+	.alloc_inode	= aufs_alloc_inode,
+	.destroy_inode	= aufs_destroy_inode,
+	.read_inode	= aufs_read_inode,
+	//.dirty_inode	= aufs_dirty_inode,
+	//.write_inode	= aufs_write_inode,
+	//void (*put_inode) (struct inode *);
+	.drop_inode	= generic_delete_inode,
+	//.delete_inode	= aufs_delete_inode,
+	//.clear_inode	= aufs_clear_inode,
+
+	.show_options	= aufs_show_options,
+	.statfs		= aufs_statfs,
+
+	.put_super	= aufs_put_super,
+	//void (*write_super) (struct super_block *);
+	//int (*sync_fs)(struct super_block *sb, int wait);
+	//void (*write_super_lockfs) (struct super_block *);
+	//void (*unlockfs) (struct super_block *);
+	.remount_fs	= aufs_remount_fs,
+	/* depends upon umount flags. also use put_super() (< 2.6.18) */
+	.umount_begin	= aufs_umount_begin
+};
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * at first mount time.
+ */
+
+static int alloc_sbinfo(struct super_block *sb)
+{
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+
+	sbinfo = kmalloc(sizeof(*sbinfo), GFP_KERNEL);
+	//if (LktrCond) {kfree(sbinfo); sbinfo = NULL;}
+	if (unlikely(!sbinfo))
+		goto out;
+	sbinfo->si_branch = kzalloc(sizeof(*sbinfo->si_branch), GFP_KERNEL);
+	//if (LktrCond) {kfree(sbinfo->si_branch); sbinfo->si_branch = NULL;}
+	if (unlikely(!sbinfo->si_branch))
+		goto out_sbinfo;
+
+	rw_init_wlock(&sbinfo->si_rwsem);
+	sbinfo->si_generation = 0;
+	//sbinfo->si_generation = INT_MAX - 2;
+	sbinfo->si_failed_refresh_dirs = 0;
+	sbinfo->si_bend = -1;
+	sbinfo->si_last_br_id = 0;
+	sbinfo->si_flags = AuDefFlags;
+
+	sbinfo->si_xread = NULL;
+	sbinfo->si_xwrite = NULL;
+	sbinfo->si_xib = NULL;
+	mutex_init(&sbinfo->si_xib_mtx);
+	sbinfo->si_xib_buf = NULL;
+	/* leave si_xib_last_pindex and si_xib_next_bit */
+
+	au_nwt_init(&sbinfo->si_nowait);
+
+	sbinfo->si_rdcache = AUFS_RDCACHE_DEF * HZ;
+	sbinfo->si_dirwh = AUFS_DIRWH_DEF;
+
+	spin_lock_init(&sbinfo->si_plink_lock);
+	INIT_LIST_HEAD(&sbinfo->si_plink);
+
+	/* leave syaufs members, si_list, si_mnt and si_sysaufs. */
+
+	init_lvma(sbinfo);
+	sb->s_fs_info = sbinfo;
+
+#ifdef ForceInotify
+	udba_set(sb, AuFlag_UDBA_INOTIFY);
+#endif
+#ifdef ForceDlgt
+	au_flag_set(sb, AuFlag_DLGT);
+#endif
+#ifdef ForceNoPlink
+	au_flag_clr(sb, AuFlag_PLINK);
+#endif
+#ifdef ForceNoXino
+	au_flag_clr(sb, AuFlag_XINO);
+#endif
+#ifdef ForceNoRefrof
+	au_flag_clr(sb, AuFlag_REFROF);
+#endif
+	return 0; /* success */
+
+// out_branch:
+	kfree(sbinfo->si_branch);
+ out_sbinfo:
+	kfree(sbinfo);
+ out:
+	TraceErr(-ENOMEM);
+	return -ENOMEM;
+}
+
+static int alloc_root(struct super_block *sb)
+{
+	int err;
+	struct inode *inode;
+	struct dentry *root;
+
+	TraceEnter();
+
+	err = -ENOMEM;
+	inode = iget(sb, AUFS_ROOT_INO);
+	//if (LktrCond) {iput(inode); inode = NULL;}
+	if (unlikely(!inode))
+		goto out;
+	err = PTR_ERR(inode);
+	if (IS_ERR(inode))
+		goto out;
+	err = -ENOMEM;
+	if (unlikely(is_bad_inode(inode)))
+		goto out_iput;
+
+	inode->i_mode = S_IFDIR;
+	root = d_alloc_root(inode);
+	//if (LktrCond) {igrab(inode); dput(root); root = NULL;}
+	if (unlikely(!root))
+		goto out_iput;
+	err = PTR_ERR(root);
+	if (IS_ERR(root))
+		goto out_iput;
+
+	err = au_alloc_dinfo(root);
+	//if (LktrCond){rw_write_unlock(&dtodi(root)->di_rwsem);err=-1;}
+	if (!err) {
+		sb->s_root = root;
+		return 0; /* success */
+	}
+	dput(root);
+	goto out; /* do not iput */
+
+ out_iput:
+	iput(inode);
+ out:
+	TraceErr(err);
+	return err;
+
+}
+
+static int aufs_fill_super(struct super_block *sb, void *raw_data, int silent)
+{
+	int err;
+	struct dentry *root;
+	struct inode *inode;
+	struct opts opts;
+	char *arg = raw_data;
+
+	//au_debug_on();
+	if (unlikely(!arg || !*arg)) {
+		err = -EINVAL;
+		Err("no arg\n");
+		goto out;
+	}
+	LKTRTrace("%s, silent %d\n", arg, silent);
+
+	err = -ENOMEM;
+	memset(&opts, 0, sizeof(opts));
+	opts.opt = (void*)__get_free_page(GFP_KERNEL);
+	//if (LktrCond) {free_page((unsigned long)opts.opt); opts.opt = NULL;}
+	if (unlikely(!opts.opt))
+		goto out;
+	opts.max_opt = PAGE_SIZE / sizeof(*opts.opt);
+
+	err = alloc_sbinfo(sb);
+	//if (LktrCond) {si_write_unlock(sb);free_sbinfo(sb);err=-1;}
+	if (unlikely(err))
+		goto out_opts;
+	SiMustWriteLock(sb);
+	/* all timestamps always follow the ones on the branch */
+	sb->s_flags |= MS_NOATIME | MS_NODIRATIME;
+	sb->s_op = &aufs_sop;
+	au_init_export_op(sb);
+
+	err = alloc_root(sb);
+	//if (LktrCond) {rw_write_unlock(&dtodi(sb->s_root)->di_rwsem);
+	//dput(sb->s_root);sb->s_root=NULL;err=-1;}
+	if (unlikely(err)) {
+		AuDebugOn(sb->s_root);
+		si_write_unlock(sb);
+		goto out_info;
+	}
+	root = sb->s_root;
+	DiMustWriteLock(root);
+	inode = root->d_inode;
+	inode->i_nlink = 2;
+
+	/*
+	 * actually we can parse options regardless aufs lock here.
+	 * but at remount time, parsing must be done before aufs lock.
+	 * so we follow the same rule.
+	 */
+	ii_write_lock_parent(inode);
+	aufs_write_unlock(root);
+	err = au_opts_parse(sb, arg, &opts);
+	//if (LktrCond) {au_opts_free(&opts); err = -1;}
+	if (unlikely(err))
+		goto out_root;
+
+	/* lock vfs_inode first, then aufs. */
+	vfsub_i_lock(inode);
+	inode->i_op = &aufs_dir_iop;
+	inode->i_fop = &aufs_dir_fop;
+	aufs_write_lock(root);
+
+	sb->s_maxbytes = 0;
+	err = au_opts_mount(sb, &opts);
+	//if (LktrCond) err = -1;
+	au_opts_free(&opts);
+	if (unlikely(err))
+		goto out_unlock;
+	AuDebugOn(!sb->s_maxbytes);
+
+	//DbgDentry(root);
+	aufs_write_unlock(root);
+	vfsub_i_unlock(inode);
+	//DbgSb(sb);
+	goto out_opts; /* success */
+
+ out_unlock:
+	aufs_write_unlock(root);
+	vfsub_i_unlock(inode);
+ out_root:
+	dput(root);
+	sb->s_root = NULL;
+ out_info:
+	free_sbinfo(sb);
+	sb->s_fs_info = NULL;
+ out_opts:
+	free_page((unsigned long)opts.opt);
+ out:
+	TraceErr(err);
+	err = cvt_err(err);
+	TraceErr(err);
+	//au_debug_off();
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+static int aufs_get_sb(struct file_system_type *fs_type, int flags,
+		       const char *dev_name, void *raw_data,
+		       struct vfsmount *mnt)
+{
+	int err;
+
+	/* all timestamps always follow the ones on the branch */
+	/* mnt->mnt_flags |= MNT_NOATIME | MNT_NODIRATIME; */
+	err = get_sb_nodev(fs_type, flags, raw_data, aufs_fill_super, mnt);
+	if (!err) {
+		struct aufs_sbinfo *sbinfo = stosi(mnt->mnt_sb);
+		sbinfo->si_mnt = mnt;
+		sysaufs_add(sbinfo);
+	}
+	return err;
+}
+#else
+static struct super_block *aufs_get_sb(struct file_system_type *fs_type,
+				       int flags, const char *dev_name,
+				       void *raw_data)
+{
+	return get_sb_nodev(fs_type, flags, raw_data, aufs_fill_super);
+}
+#endif
+
+struct file_system_type aufs_fs_type = {
+	.name		= AUFS_FSTYPE,
+	.fs_flags	= FS_REVAL_DOT, /* for UDBA and NFS branch */
+	.get_sb		= aufs_get_sb,
+	.kill_sb	= generic_shutdown_super,
+	/* no need to __module_get() and module_put(). */
+	.owner		= THIS_MODULE,
+};
diff -ruN linux-2.6.22/fs/aufs/super.h linux-2.6.22-aufs/fs/aufs/super.h
--- linux-2.6.22/fs/aufs/super.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/super.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: super.h,v 1.52 2007/07/15 20:06:11 sfjro Exp $ */
+
+#ifndef __AUFS_SUPER_H__
+#define __AUFS_SUPER_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/version.h>
+#include <linux/aufs_type.h>
+#include "hinode.h"
+#include "misc.h"
+#include "sysaufs.h"
+#include "wkq.h"
+
+typedef ssize_t (*readf_t)(struct file*, char __user*, size_t, loff_t*);
+typedef ssize_t (*writef_t)(struct file*, const char __user*, size_t, loff_t*);
+
+#ifdef CONFIG_AUFS_SYSAUFS
+/* entries under sysfs per mount-point */
+enum {SysaufsSb_XINO, /* SysaufsSb_PLINK, SysaufsSb_files, */ SysaufsSb_Last};
+struct sysaufs_sbinfo {
+	au_subsys_t		subsys;
+	struct sysaufs_entry	array[SysaufsSb_Last];
+};
+extern sysaufs_op au_si_ops[];
+#else
+struct sysaufs_sbinfo {};
+#endif
+
+struct aufs_sbinfo {
+	/* nowait tasks in the system-wide workqueue */
+	struct au_nowait_tasks	si_nowait;
+
+	struct aufs_rwsem	si_rwsem;
+
+	/* branch management */
+	au_gen_t		si_generation;
+
+	/*
+	 * set true when refresh_dirs() at remount time failed.
+	 * then try refreshing dirs at access time again.
+	 * if it is false, refreshing dirs at access time is unnecesary
+	 */
+	unsigned int		si_failed_refresh_dirs:1;
+
+	aufs_bindex_t		si_bend;
+	aufs_bindex_t		si_last_br_id;
+	struct aufs_branch	**si_branch;
+
+	/* mount flags */
+	unsigned int		si_flags;
+
+	/* external inode number (bitmap and translation table) */
+	readf_t			si_xread;
+	writef_t		si_xwrite;
+	struct file		*si_xib;
+	struct mutex		si_xib_mtx; /* protect xib members */
+	unsigned long		*si_xib_buf;
+	int			si_xib_last_pindex;
+	int			si_xib_next_bit;
+
+	/* readdir cache time, max, in HZ */
+	unsigned long		si_rdcache;
+
+	/*
+	 * If the number of whiteouts are larger than si_dirwh, leave all of
+	 * them after rename_whtmp to reduce the cost of rmdir(2).
+	 * future fsck.aufs or kernel thread will remove them later.
+	 * Otherwise, remove all whiteouts and the dir in rmdir(2).
+	 */
+	unsigned int		si_dirwh;
+
+	/*
+	 * rename(2) a directory with all children.
+	 */
+	int			si_rendir;
+
+	/* pseudo_link list */ // dirty
+	spinlock_t		si_plink_lock;
+	struct list_head	si_plink;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+	/* super_blocks list is not exported */
+	struct list_head	si_list;
+	struct vfsmount		*si_mnt;	/* no get/put */
+#endif
+
+	/* sysfs */
+	struct sysaufs_sbinfo	si_sysaufs;
+
+#ifdef CONFIG_AUFS_ROBR
+	/* locked vma list for mmap() */ // very dirty
+	spinlock_t		si_lvma_lock;
+	struct list_head	si_lvma;
+#endif
+};
+
+/* an entry in a xino file */
+struct xino {
+	ino_t ino;
+	//__u32 h_gen;
+} __attribute__ ((packed));
+
+//#define AuXino_INVALID_HGEN	(-1)
+
+/* ---------------------------------------------------------------------- */
+
+/* Mount flags */
+#define AuFlag_XINO		1
+#define AuFlag_ZXINO		(1 << 1)
+#define AuFlag_PLINK		(1 << 2)
+#define AuFlag_UDBA_NONE	(1 << 3)
+#define AuFlag_UDBA_REVAL	(1 << 4)
+#define AuFlag_UDBA_INOTIFY	(1 << 5)
+#define AuFlag_WARN_PERM	(1 << 6)
+#define AuFlag_COO_NONE		(1 << 7)
+#define AuFlag_COO_LEAF		(1 << 8)
+#define AuFlag_COO_ALL		(1 << 9)
+#define AuFlag_ALWAYS_DIROPQ	(1 << 10)
+#define AuFlag_DLGT		(1 << 11)
+#define AuFlag_REFROF		(1 << 12)
+#define AuFlag_VERBOSE		(1 << 13)
+
+#define AuMask_UDBA		(AuFlag_UDBA_NONE | AuFlag_UDBA_REVAL \
+				 | AuFlag_UDBA_INOTIFY)
+#define AuMask_COO		(AuFlag_COO_NONE | AuFlag_COO_LEAF \
+				 | AuFlag_COO_ALL)
+
+#ifdef CONFIG_AUFS_COMPAT
+#define AuDefFlag_DIROPQ	AuFlag_ALWAYS_DIROPQ
+#else
+#define AuDefFlag_DIROPQ	0
+#endif
+
+#define AuDefFlags_COMM		(AuFlag_XINO | AuFlag_UDBA_REVAL \
+				| AuFlag_WARN_PERM | AuFlag_COO_NONE \
+				| AuDefFlag_DIROPQ | AuFlag_REFROF)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
+#define AuDefFlags		(AuDefFlags_COMM | AuFlag_PLINK)
+#else
+#define AuDefFlags		AuDefFlags_COMM
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/* flags for si_read_lock()/aufs_read_lock()/di_read_lock() */
+#define AuLock_DW		1
+#define AuLock_IR		(1 << 1)
+#define AuLock_IW		(1 << 2)
+#define AuLock_FLUSH		(1 << 3)
+#define AuLock_DIR		(1 << 4)
+
+/* ---------------------------------------------------------------------- */
+
+/* super.c */
+int au_show_brs(struct seq_file *seq, struct super_block *sb);
+extern struct file_system_type aufs_fs_type;
+
+/* xino.c */
+int xib_trunc(struct super_block *sb);
+
+struct file *xino_create(struct super_block *sb, char *fname, int silent,
+			 struct dentry *parent);
+ino_t xino_new_ino(struct super_block *sb);
+int xino_write0(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
+		ino_t ino);
+int xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
+	       struct xino *xino);
+int xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
+	      struct xino *xino);
+int xino_br(struct super_block *sb, aufs_bindex_t bindex,
+	    struct file *base_file, int do_test);
+
+struct opt_xino;
+int xino_set(struct super_block *sb, struct opt_xino *xino, int remount);
+void xino_clr(struct super_block *sb);
+struct file *xino_def(struct super_block *sb);
+
+/* sbinfo.c */
+struct aufs_sbinfo *stosi(struct super_block *sb);
+aufs_bindex_t sbend(struct super_block *sb);
+struct aufs_branch *stobr(struct super_block *sb, aufs_bindex_t bindex);
+au_gen_t au_sigen(struct super_block *sb);
+au_gen_t au_sigen_inc(struct super_block *sb);
+int find_bindex(struct super_block *sb, struct aufs_branch *br);
+
+void aufs_read_lock(struct dentry *dentry, int flags);
+void aufs_read_unlock(struct dentry *dentry, int flags);
+void aufs_write_lock(struct dentry *dentry);
+void aufs_write_unlock(struct dentry *dentry);
+void aufs_read_and_write_lock2(struct dentry *d1, struct dentry *d2, int isdir);
+void aufs_read_and_write_unlock2(struct dentry *d1, struct dentry *d2);
+
+aufs_bindex_t new_br_id(struct super_block *sb);
+
+/* ---------------------------------------------------------------------- */
+
+static inline const char *au_sbtype(struct super_block *sb)
+{
+	return sb->s_type->name;
+}
+
+static inline int au_is_aufs(struct super_block *sb)
+{
+	return !strcmp(au_sbtype(sb), AUFS_FSTYPE);
+}
+
+static inline int au_is_nfs(struct super_block *sb)
+{
+#if defined(CONFIG_NFS_FS) || defined(CONFIG_NFS_FS_MODULE)
+	return !strcmp(au_sbtype(sb), "nfs");
+#else
+	return 0;
+#endif
+}
+
+static inline int au_is_remote(struct super_block *sb)
+{
+	return au_is_nfs(sb);
+}
+
+/* temporary support for i#1 in cramfs */
+static inline int au_is_unique_ino(struct dentry *h_dentry, ino_t h_ino)
+{
+#if defined(CONFIG_CRAMFS) || defined(CONFIG_CRAMFS_MODULE)
+	if (unlikely(!strcmp(au_sbtype(h_dentry->d_sb), "cramfs")))
+		return (h_ino != 1);
+#endif
+	return 1;
+}
+
+#ifdef CONFIG_AUFS_EXPORT
+extern struct export_operations aufs_export_op;
+static inline void au_init_export_op(struct super_block *sb)
+{
+	sb->s_export_op = &aufs_export_op;
+}
+
+static inline int au_is_nfsd(struct task_struct *tsk)
+{
+	return (!tsk->mm && !strcmp(tsk->comm, "nfsd"));
+}
+
+static inline void au_nfsd_lockdep_off(void)
+{
+	if (au_is_nfsd(current))
+		lockdep_off();
+}
+
+static inline void au_nfsd_lockdep_on(void)
+{
+	if (au_is_nfsd(current))
+		lockdep_on();
+}
+#else
+static inline int au_is_nfsd(struct task_struct *tsk)
+{
+	return 0;
+}
+static inline void au_init_export_op(struct super_block *sb)
+{
+	/* nothing */
+}
+#define au_nfsd_lockdep_off()	do {} while (0)
+#define au_nfsd_lockdep_on()	do {} while (0)
+#endif /* CONFIG_AUFS_EXPORT */
+
+static inline void init_lvma(struct aufs_sbinfo *sbinfo)
+{
+#ifdef CONFIG_AUFS_ROBR
+	spin_lock_init(&sbinfo->si_lvma_lock);
+	INIT_LIST_HEAD(&sbinfo->si_lvma);
+#else
+	/* nothing */
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* limited support before 2.6.18 */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+static inline void au_mntget(struct super_block *sb)
+{
+	mntget(stosi(sb)->si_mnt);
+}
+
+static inline void au_mntput(struct super_block *sb)
+{
+	mntput(stosi(sb)->si_mnt);
+}
+#else
+static inline void au_mntget(struct super_block *sb)
+{
+	/* empty */
+}
+
+static inline void au_mntput(struct super_block *sb)
+{
+	/* empty */
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static inline void au_flag_set(struct super_block *sb, unsigned int flag)
+{
+	//SiMustWriteLock(sb);
+	stosi(sb)->si_flags |= flag;
+}
+
+static inline void au_flag_clr(struct super_block *sb, unsigned int flag)
+{
+	//SiMustWriteLock(sb);
+	stosi(sb)->si_flags &= ~flag;
+}
+
+static inline
+unsigned int au_flag_test(struct super_block *sb, unsigned int flag)
+{
+	//SiMustAnyLock(sb);
+	return stosi(sb)->si_flags & flag;
+}
+
+static inline unsigned int au_flag_test_udba(struct super_block *sb)
+{
+	return au_flag_test(sb, AuMask_UDBA);
+}
+
+static inline unsigned int au_flag_test_udba_inotify(struct super_block *sb)
+{
+#ifdef CONFIG_AUFS_HINOTIFY
+	return au_flag_test(sb, AuFlag_UDBA_INOTIFY);
+#else
+	return 0;
+#endif
+}
+
+static inline unsigned int au_flag_test_coo(struct super_block *sb)
+{
+	return au_flag_test(sb, AuMask_COO);
+}
+
+static inline int need_dlgt(struct super_block *sb)
+{
+#ifdef CONFIG_AUFS_DLGT
+	return (au_flag_test(sb, AuFlag_DLGT) && !is_au_wkq(current));
+#else
+	return 0;
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* lock superblock. mainly for entry point functions */
+/*
+ * si_noflush_read_lock, si_noflush_write_lock,
+ * si_read_unlock, si_write_unlock, si_downgrade_lock
+ */
+SimpleLockRwsemFuncs(si_noflush, struct super_block *sb, stosi(sb)->si_rwsem);
+SimpleUnlockRwsemFuncs(si, struct super_block *sb, stosi(sb)->si_rwsem);
+
+static inline void si_read_lock(struct super_block *sb, int flags)
+{
+	if (unlikely(flags & AuLock_FLUSH))
+		au_nwt_flush(&stosi(sb)->si_nowait);
+	si_noflush_read_lock(sb);
+}
+
+static inline void si_write_lock(struct super_block *sb)
+{
+	au_nwt_flush(&stosi(sb)->si_nowait);
+	si_noflush_write_lock(sb);
+}
+
+static inline int si_read_trylock(struct super_block *sb, int flags)
+{
+	if (unlikely(flags & AuLock_FLUSH))
+		au_nwt_flush(&stosi(sb)->si_nowait);
+	return si_noflush_read_trylock(sb);
+}
+
+static inline int si_write_trylock(struct super_block *sb)
+{
+	au_nwt_flush(&stosi(sb)->si_nowait);
+	return si_noflush_write_trylock(sb);
+}
+
+/* to debug easier, do not make them inlined functions */
+#define SiMustReadLock(sb)	RwMustReadLock(&stosi(sb)->si_rwsem)
+#define SiMustWriteLock(sb)	RwMustWriteLock(&stosi(sb)->si_rwsem)
+#define SiMustAnyLock(sb)	RwMustAnyLock(&stosi(sb)->si_rwsem)
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_SUPER_H__ */
diff -ruN linux-2.6.22/fs/aufs/sysaufs.c linux-2.6.22-aufs/fs/aufs/sysaufs.c
--- linux-2.6.22/fs/aufs/sysaufs.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/sysaufs.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: sysaufs.c,v 1.12 2007/07/09 05:46:54 sfjro Exp $ */
+
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/sysfs.h>
+#include "aufs.h"
+
+/* ---------------------------------------------------------------------- */
+
+/* super_blocks list is not exported */
+static DEFINE_MUTEX(aufs_sbilist_mtx);
+static LIST_HEAD(aufs_sbilist);
+
+/* ---------------------------------------------------------------------- */
+
+typedef ssize_t (*rwfunc_t)(struct kobject *kobj, char *buf, loff_t offset,
+			    size_t sz, struct sysaufs_args *args);
+static ssize_t sysaufs_read(struct kobject *kobj, char *buf, loff_t offset,
+			    size_t sz, struct sysaufs_args *args);
+static ssize_t sysaufs_free_write(struct kobject *kobj, char *buf, loff_t
+			    offset, size_t sz, struct sysaufs_args *args);
+
+#define GFunc(name, _index, func) \
+static ssize_t name(struct kobject *kobj, char *buf, loff_t offset, size_t sz) \
+{ \
+	struct sysaufs_args args = { \
+		.index	= (_index), \
+		.mtx	= &aufs_sbilist_mtx, \
+		.sb	= NULL \
+	}; \
+	return func(kobj, buf, offset, sz, &args); \
+}
+
+#define GFuncs(name, _index) \
+	GFunc(read_##name, _index, sysaufs_read) \
+	GFunc(write_##name, _index, sysaufs_free_write)
+
+static struct super_block *find_sb_lock(struct kobject *kobj)
+{
+	struct super_block *sb;
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+	MtxMustLock(&aufs_sbilist_mtx);
+
+	sb = NULL;
+	list_for_each_entry(sbinfo, &aufs_sbilist, si_list) {
+		if (&au_subsys_to_kset(sbinfo->si_sysaufs.subsys).kobj != kobj)
+			continue;
+		sb = sbinfo->si_mnt->mnt_sb;
+		si_read_lock(sb, !AuLock_FLUSH);
+		break;
+	}
+	return sb;
+}
+
+static ssize_t sb_func(struct kobject *kobj, char *buf, loff_t offset,
+		       size_t sz, struct sysaufs_args *args, rwfunc_t func)
+{
+	ssize_t err;
+
+	err = -ENOENT;
+	mutex_lock(&aufs_sbilist_mtx);
+	args->sb = find_sb_lock(kobj);
+	if (args->sb) {
+		err = func(kobj, buf, offset, sz, args);
+		si_read_unlock(args->sb);
+	}
+	mutex_unlock(&aufs_sbilist_mtx);
+	return err;
+}
+
+#define SbFunc(name, _index, func) \
+static ssize_t name(struct kobject *kobj, char *buf, loff_t offset, size_t sz) \
+{ \
+	struct sysaufs_args args = { \
+		.index	= (_index), \
+		.mtx	= NULL \
+	}; \
+	return sb_func(kobj, buf, offset, sz, &args, func); \
+}
+
+#define SbFuncs(name, index) \
+	SbFunc(read_##name, index, sysaufs_read) \
+	SbFunc(write_##name, index, sysaufs_free_write)
+
+static decl_subsys(aufs, NULL, NULL);
+enum {Brs, Stat, Config, _Last};
+static struct sysaufs_entry g_array[_Last];
+/*
+ * read_brs, write_brs,
+ * read_stat, write_stat,
+ * read_config, write_config
+ */
+GFuncs(brs, Brs);
+GFuncs(stat, Stat);
+GFuncs(config, Config);
+
+/*
+ * read_xino, write_xino
+ */
+SbFuncs(xino, SysaufsSb_XINO);
+
+#define SetEntry(e, _name, init_size, _ops) \
+	do { \
+		(e)->attr.attr.name = #_name; \
+		(e)->attr.attr.owner = THIS_MODULE; \
+		(e)->attr.attr.mode = S_IRUGO | S_IWUSR; \
+		(e)->attr.read = read_##_name; \
+		(e)->attr.write = write_##_name; \
+		(e)->allocated = init_size; \
+		(e)->err = -1; \
+		(e)->ops = _ops; \
+	} while (0)
+
+#define Priv(e)		(e)->attr.private
+#define Allocated(e)	(e)->allocated
+#define Len(e)		(e)->attr.size
+#define Name(e)		attr_name((e)->attr)
+
+/* ---------------------------------------------------------------------- */
+
+static void free_entry(struct sysaufs_entry *e)
+{
+	MtxMustLock(&aufs_sbilist_mtx);
+	AuDebugOn(!Priv(e));
+
+	if (Allocated(e) > 0)
+		kfree(Priv(e));
+	else
+		free_pages((unsigned long)Priv(e), -Allocated(e));
+	Priv(e) = NULL;
+	Len(e) = 0;
+}
+
+static void free_entries(void)
+{
+	static int a[] = {Brs, -1};
+	int *p = a;
+
+	MtxMustLock(&aufs_sbilist_mtx);
+
+	while (*p >= 0) {
+		if (Priv(g_array + *p))
+			free_entry(g_array + *p);
+		p++;
+	}
+}
+
+static int alloc_entry(struct sysaufs_entry *e)
+{
+	MtxMustLock(&aufs_sbilist_mtx);
+	AuDebugOn(Priv(e));
+
+	if (Allocated(e) > 0)
+		Priv(e) = kmalloc(Allocated(e), GFP_KERNEL);
+	else
+		Priv(e) = (void*)__get_free_pages(GFP_KERNEL, -Allocated(e));
+	if (Priv(e))
+		return 0;
+	return -ENOMEM;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void unreg(au_subsys_t *subsys, struct sysaufs_entry *a, int n,
+		  au_subsys_t *parent)
+{
+	int i;
+
+	TraceEnter();
+
+	for (i = 0; i < n; i++, a++)
+		if (!a->err) {
+			sysfs_remove_bin_file
+				(&au_subsys_to_kset(*subsys).kobj, &a->attr);
+			if (Priv(a))
+				free_entry(a);
+		}
+
+	subsystem_unregister(subsys);
+	subsys_put(parent);
+}
+
+static int reg(au_subsys_t *subsys, struct sysaufs_entry *a, int n,
+	       au_subsys_t *parent)
+{
+	int err, i;
+
+	TraceEnter();
+
+	subsys_get(parent);
+	kobj_set_kset_s(&au_subsys_to_kset(*subsys), *parent);
+	err = subsystem_register(subsys);
+	if (unlikely(err))
+		goto out;
+
+	for (i = 0; !err && i < n; i++)
+		err = a[i].err = sysfs_create_bin_file
+			(&au_subsys_to_kset(*subsys).kobj, &a[i].attr);
+	if (unlikely(err))
+		unreg(subsys, a, n, parent);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#define SbSetEntry(index, name, init_size) \
+	SetEntry(sa->array + index, name, init_size, au_si_ops);
+
+void sysaufs_add(struct aufs_sbinfo *sbinfo)
+{
+	int err;
+	struct sysaufs_sbinfo *sa = &sbinfo->si_sysaufs;
+
+	TraceEnter();
+
+	mutex_lock(&aufs_sbilist_mtx);
+	list_add_tail(&sbinfo->si_list, &aufs_sbilist);
+	free_entries();
+
+	memset(sa, 0, sizeof(*sa));
+	SbSetEntry(SysaufsSb_XINO, xino, 128);
+	err = kobject_set_name(&au_subsys_to_kset(sa->subsys).kobj, "%p",
+			       sbinfo->si_mnt->mnt_sb);
+	if (!err)
+		err = reg(&sa->subsys, sa->array, ARRAY_SIZE(sa->array),
+			  &aufs_subsys);
+	if (unlikely(err))
+		Warn("failed adding sysfs (%d)\n", err);
+
+	mutex_unlock(&aufs_sbilist_mtx);
+}
+
+void sysaufs_del(struct aufs_sbinfo *sbinfo)
+{
+	struct sysaufs_sbinfo *sa = &sbinfo->si_sysaufs;
+
+	TraceEnter();
+
+	mutex_lock(&aufs_sbilist_mtx);
+	unreg(&sa->subsys, sa->array, ARRAY_SIZE(sa->array), &aufs_subsys);
+	list_del(&sbinfo->si_list);
+	free_entries();
+	mutex_unlock(&aufs_sbilist_mtx);
+}
+
+void sysaufs_notify_remount(void)
+{
+	mutex_lock(&aufs_sbilist_mtx);
+	free_entries();
+	mutex_unlock(&aufs_sbilist_mtx);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int make_brs(struct seq_file *seq, struct sysaufs_args *args,
+		    int *do_size)
+{
+	int err;
+	struct aufs_sbinfo *sbinfo;
+
+	TraceEnter();
+	MtxMustLock(&aufs_sbilist_mtx);
+	AuDebugOn(args->index != Brs);
+
+	err = 0;
+	list_for_each_entry(sbinfo, &aufs_sbilist, si_list) {
+		struct super_block *sb;
+		struct dentry *root;
+		struct vfsmount *mnt;
+
+		sb = sbinfo->si_mnt->mnt_sb;
+		root = sb->s_root;
+		aufs_read_lock(root, !AuLock_IR);
+		mnt = sbinfo->si_mnt;
+		err = seq_escape
+			(seq, mnt->mnt_devname ? mnt->mnt_devname : "none",
+			 au_esc_chars);
+		if (!err)
+			err = seq_putc(seq, ' ');
+		if (!err)
+			err = seq_path(seq, mnt, root, au_esc_chars);
+		if (err > 0)
+			err = seq_printf(seq, " %p br:", sb);
+		if (!err)
+			err = au_show_brs(seq, sb);
+		aufs_read_unlock(root, !AuLock_IR);
+		if (!err)
+			err = seq_putc(seq, '\n');
+		else
+			break;
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+static int make_config(struct seq_file *seq, struct sysaufs_args *args,
+		       int *do_size)
+{
+	int err;
+
+	TraceEnter();
+	AuDebugOn(args->index != Config);
+
+#ifdef CONFIG_AUFS
+	err = seq_puts(seq, "CONFIG_AUFS=y\n");
+#else
+	err = seq_puts(seq, "CONFIG_AUFS=m\n");
+#endif
+
+#define puts(m, v) \
+	if (!err) \
+		err = seq_puts(seq, "CONFIG_AUFS_" #m "=" #v "\n")
+#define puts_bool(m)	puts(m, y)
+#define puts_mod(m)	puts(m, m)
+
+#ifdef CONFIG_AUFS_FAKE_DM
+	puts_bool(FAKE_DM);
+#endif
+#ifdef CONFIG_AUFS_BRANCH_MAX_127
+	puts_bool(BRANCH_MAX_127);
+#elif defined(CONFIG_AUFS_BRANCH_MAX_511)
+	puts_bool(BRANCH_MAX_511);
+#elif defined(CONFIG_AUFS_BRANCH_MAX_1023)
+	puts_bool(BRANCH_MAX_1023);
+#elif defined(CONFIG_AUFS_BRANCH_MAX_32767)
+	puts_bool(BRANCH_MAX_32767);
+#endif
+	puts_bool(SYSAUFS);
+#ifdef CONFIG_AUFS_HINOTIFY
+	puts_bool(HINOTIFY);
+#endif
+#ifdef CONFIG_AUFS_EXPORT
+	puts_bool(EXPORT);
+#endif
+#ifdef CONFIG_AUFS_ROBR
+	puts_bool(ROBR);
+#endif
+#ifdef CONFIG_AUFS_DLGT
+	puts_bool(DLGT);
+#endif
+#ifdef CONFIG_AUFS_LHASH_PATCH
+	puts_bool(LHASH_PATCH);
+#endif
+#ifdef CONFIG_AUFS_KSIZE_PATCH
+	puts_bool(KSIZE_PATCH);
+#endif
+#ifdef CONFIG_AUFS_ISSUBDIR_PATCH
+	puts_bool(CONFIG_AUFS_ISSUBDIR_PATCH);
+#endif
+#ifdef CONFIG_AUFS_DEBUG
+	puts_bool(DEBUG);
+#endif
+#ifdef CONFIG_AUFS_COMPAT
+	puts_bool(COMPAT);
+#endif
+
+#undef puts_bool
+#undef puts
+
+	TraceErr(err);
+	return err;
+}
+
+static int make_stat(struct seq_file *seq, struct sysaufs_args *args,
+		     int *do_size)
+{
+	int err, i;
+
+	TraceEnter();
+	AuDebugOn(args->index != Stat);
+
+	*do_size = 0;
+	err = seq_puts(seq, "wkq max_busy:");
+	for (i = 0; !err && i < aufs_nwkq; i++)
+		err = seq_printf(seq, " %u", au_wkq[i].max_busy);
+	if (!err)
+		err = seq_printf(seq, ", %u(generic)\n",
+				 au_wkq[aufs_nwkq].max_busy);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int make(struct sysaufs_entry *e, struct sysaufs_args *args,
+		int *do_size)
+{
+	int err;
+	struct seq_file *seq;
+
+	TraceEnter();
+	AuDebugOn(Priv(e));
+	MtxMustLock(&aufs_sbilist_mtx);
+
+	err = -ENOMEM;
+	seq = kzalloc(sizeof(*seq), GFP_KERNEL);
+	if (unlikely(!seq))
+		goto out;
+
+	Len(e) = 0;
+	while (1) {
+		err = alloc_entry(e);
+		if (unlikely(err))
+			break;
+
+		//mutex_init(&seq.lock);
+		seq->buf = Priv(e);
+		seq->count = 0;
+		seq->size = Allocated(e);
+		if (unlikely(Allocated(e) <= 0))
+			seq->size = PAGE_SIZE << -Allocated(e);
+
+		err = e->ops[args->index](seq, args, do_size);
+		if (!err) {
+			Len(e) = seq->count;
+			break; /* success */
+		}
+
+		free_entry(e);
+		if (Allocated(e) > 0) {
+			Allocated(e) <<= 1;
+			if (unlikely(Allocated(e) >= (int)PAGE_SIZE))
+				Allocated(e) = 0;
+		} else
+			Allocated(e)--;
+	}
+	kfree(seq);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* why does sysfs pass my parent kobject? */
+static struct dentry *find_me(struct dentry *parent, struct sysaufs_entry *e)
+{
+#if 1
+	struct dentry *dentry;
+	const char *name = Name(e);
+	const unsigned int len = strlen(name);
+
+	spin_lock(&dcache_lock);
+	list_for_each_entry(dentry, &parent->d_subdirs, D_CHILD) {
+		if (len == dentry->d_name.len
+		    && !strcmp(dentry->d_name.name, name)) {
+			spin_unlock(&dcache_lock);
+			return dentry;
+		}
+	}
+	spin_unlock(&dcache_lock);
+#endif
+	return NULL;
+}
+
+static ssize_t sysaufs_read(struct kobject *kobj, char *buf, loff_t offset,
+			    size_t sz, struct sysaufs_args *args)
+{
+	ssize_t err;
+	loff_t len;
+	struct dentry *d;
+	struct sysaufs_entry *e;
+	int do_size;
+
+	LKTRTrace("{%d, %p}, offset %Ld, sz %lu\n",
+		  args->index, args->sb, offset, (unsigned long)sz);
+
+	if (unlikely(!sz))
+		return 0;
+
+	err = 0;
+	d = NULL;
+	e = g_array + args->index;
+	if (args->sb)
+		e = stosi(args->sb)->si_sysaufs.array + args->index;
+
+	do_size = 1;
+	if (args->mtx)
+		mutex_lock(args->mtx);
+	if (unlikely(!Priv(e))) {
+		err = make(e, args, &do_size);
+		AuDebugOn(Len(e) > INT_MAX);
+		if (do_size) {
+			d = find_me(kobj->dentry, e);
+			if (d)
+				i_size_write(d->d_inode, Len(e));
+		}
+	}
+
+	if (!err) {
+		err = len = Len(e) - offset;
+		LKTRTrace("%Ld\n", len);
+		if (len > 0) {
+			if (len > sz)
+				err = sz;
+			memcpy(buf, Priv(e) + offset, err);
+		}
+
+		if (!do_size)
+			free_entry(e);
+	}
+	if (args->mtx)
+		mutex_unlock(args->mtx);
+
+	TraceErr(err);
+	return err;
+}
+
+static ssize_t sysaufs_free_write(struct kobject *kobj, char *buf,
+				  loff_t offset, size_t sz,
+				  struct sysaufs_args *args)
+{
+	struct dentry *d;
+	int allocated, len;
+	struct sysaufs_entry *e;
+
+	LKTRTrace("{%d, %p}\n", args->index, args->sb);
+
+	e = g_array + args->index;
+	if (args->sb)
+		e = stosi(args->sb)->si_sysaufs.array + args->index;
+
+	if (args->mtx)
+		mutex_lock(args->mtx);
+	if (Priv(e)) {
+		allocated = Allocated(e);
+		if (unlikely(allocated <= 0))
+			allocated = PAGE_SIZE << -allocated;
+		allocated >>= 1;
+		len = Len(e);
+
+		free_entry(e);
+		if (unlikely(len <= allocated)) {
+			if (Allocated(e) >= 0)
+				Allocated(e) = allocated;
+			else
+				Allocated(e)++;
+		}
+
+		d = find_me(kobj->dentry, e);
+		if (d && i_size_read(d->d_inode))
+			i_size_write(d->d_inode, 0);
+	}
+	if (args->mtx)
+		mutex_unlock(args->mtx);
+
+	return sz;
+}
+
+static sysaufs_op g_ops[] = {
+	[Brs]		= make_brs,
+	[Stat]		= make_stat,
+	[Config]	= make_config
+};
+
+/* ---------------------------------------------------------------------- */
+
+#define GSetEntry(index, name, init_size) \
+	SetEntry(g_array + index, name, init_size, g_ops)
+
+int __init sysaufs_init(void)
+{
+	int err;
+
+	GSetEntry(Brs, brs, 128);
+	GSetEntry(Stat, stat, 32);
+	GSetEntry(Config, config, 256);
+	err = reg(&aufs_subsys, g_array, ARRAY_SIZE(g_array), &fs_subsys);
+	TraceErr(err);
+	return err;
+}
+
+void __exit sysaufs_fin(void)
+{
+	mutex_lock(&aufs_sbilist_mtx);
+	unreg(&aufs_subsys, g_array, ARRAY_SIZE(g_array), &fs_subsys);
+	mutex_unlock(&aufs_sbilist_mtx);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_each_sb(each_sb_cb_t cb, int do_lock)
+{
+	struct aufs_sbinfo *sbinfo;
+
+	if (do_lock)
+		mutex_lock(&aufs_sbilist_mtx);
+	list_for_each_entry(sbinfo, &aufs_sbilist, si_list)
+		cb(sbinfo->si_mnt->mnt_sb);
+	if (do_lock)
+		mutex_unlock(&aufs_sbilist_mtx);
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef DbgDlgt
+int is_branch(struct super_block *h_sb)
+{
+	int found = 0;
+	struct aufs_sbinfo *sbinfo;
+
+	mutex_lock(&aufs_sbilist_mtx);
+	list_for_each_entry(sbinfo, &aufs_sbilist, si_list) {
+		aufs_bindex_t bindex, bend;
+		struct super_block *sb;
+
+		sb = sbinfo->si_mnt->mnt_sb;
+		si_read_lock(sb);
+		bend = sbend(sb);
+		for (bindex = 0; !found && bindex <= bend; bindex++)
+			found = (h_sb == sbr_sb(sb, bindex));
+		si_read_unlock(sb);
+	}
+	mutex_unlock(&aufs_sbilist_mtx);
+	return found;
+}
+#endif
diff -ruN linux-2.6.22/fs/aufs/sysaufs.h linux-2.6.22-aufs/fs/aufs/sysaufs.h
--- linux-2.6.22/fs/aufs/sysaufs.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/sysaufs.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: sysaufs.h,v 1.10 2007/07/15 20:05:25 sfjro Exp $ */
+
+#ifndef __SYSAUFS_H__
+#define __SYSAUFS_H__
+
+#ifdef __KERNEL__
+
+#include <linux/seq_file.h>
+#include <linux/sysfs.h>
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
+typedef struct kset au_subsys_t;
+#define au_subsys_to_kset(subsys) (subsys)
+#else
+typedef struct subsystem au_subsys_t;
+#define au_subsys_to_kset(subsys) ((subsys).kset)
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/* arguments for an entry under sysfs */
+struct sysaufs_args {
+	int index;
+	struct mutex *mtx; /* template for sysaufs_read */
+	struct super_block *sb;
+};
+
+typedef int (*sysaufs_op)(struct seq_file *seq, struct sysaufs_args *args,
+			  int *do_size);
+
+/* an entry under sysfs */
+struct sysaufs_entry {
+	struct bin_attribute attr;
+	int allocated;	/* zero and minus means pages, otherwise bytes */
+	int err;
+	sysaufs_op *ops;
+};
+
+/* ---------------------------------------------------------------------- */
+
+struct aufs_sbinfo;
+typedef void (*each_sb_cb_t)(struct super_block *sb);
+#ifdef CONFIG_AUFS_SYSAUFS
+void sysaufs_add(struct aufs_sbinfo *sbinfo);
+void sysaufs_del(struct aufs_sbinfo *sbinfo);
+int __init sysaufs_init(void);
+void sysaufs_fin(void);
+void sysaufs_notify_remount(void);
+
+void au_each_sb(each_sb_cb_t cb, int do_lock);
+
+#else
+
+static inline void sysaufs_add(struct aufs_sbinfo *sbinfo)
+{
+	/* nothing */
+}
+
+static inline void sysaufs_del(struct aufs_sbinfo *sbinfo)
+{
+	/* nothing */
+}
+
+#define sysaufs_init()			0
+#define sysaufs_fin()			do {} while (0)
+#define sysaufs_notify_remount()	do {} while (0)
+
+static inline void au_each_sb(each_sb_cb_t cb, int do_lock)
+{
+	/* empty */
+}
+#endif /* CONFIG_AUFS_SYSAUFS */
+
+#endif /* __KERNEL__ */
+#endif /* __SYSAUFS_H__ */
diff -ruN linux-2.6.22/fs/aufs/vdir.c linux-2.6.22-aufs/fs/aufs/vdir.c
--- linux-2.6.22/fs/aufs/vdir.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/vdir.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: vdir.c,v 1.27 2007/07/09 05:47:06 sfjro Exp $ */
+
+#include "aufs.h"
+
+static int calc_size(int namelen)
+{
+	int sz;
+
+	sz = sizeof(struct aufs_de) + namelen;
+	if (sizeof(ino_t) == sizeof(long)) {
+		const int mask = sizeof(ino_t) - 1;
+		if (sz & mask) {
+			sz += sizeof(ino_t);
+			sz &= ~mask;
+		}
+	} else {
+#if 0 // remove
+		BUG();
+		/* this block will be discarded by optimizer. */
+		int m;
+		m = sz % sizeof(ino_t);
+		if (m)
+			sz += sizeof(ino_t) - m;
+#endif
+	}
+
+	AuDebugOn(sz % sizeof(ino_t));
+	return sz;
+}
+
+static int set_deblk_end(union aufs_deblk_p *p, union aufs_deblk_p *deblk_end)
+{
+	if (calc_size(0) <= deblk_end->p - p->p) {
+		p->de->de_str.len = 0;
+		//smp_mb();
+		return 0;
+	}
+	return -1; /* error */
+}
+
+/* returns true or false */
+static int is_deblk_end(union aufs_deblk_p *p, union aufs_deblk_p *deblk_end)
+{
+	if (calc_size(0) <= deblk_end->p - p->p)
+		return !p->de->de_str.len;
+	return 1;
+}
+
+static aufs_deblk_t *last_deblk(struct aufs_vdir *vdir)
+{
+	return vdir->vd_deblk[vdir->vd_nblk - 1];
+}
+
+void nhash_init(struct aufs_nhash *nhash)
+{
+	int i;
+	for (i = 0; i < AuSize_NHASH; i++)
+		INIT_HLIST_HEAD(nhash->heads + i);
+}
+
+struct aufs_nhash *nhash_new(gfp_t gfp)
+{
+	struct aufs_nhash *nhash;
+
+	nhash = kmalloc(sizeof(*nhash), gfp);
+	if (nhash) {
+		nhash_init(nhash);
+		return nhash;
+	}
+	return ERR_PTR(-ENOMEM);
+}
+
+void nhash_del(struct aufs_nhash *nhash)
+{
+	nhash_fin(nhash);
+	kfree(nhash);
+}
+
+void nhash_move(struct aufs_nhash *dst, struct aufs_nhash *src)
+{
+	int i;
+
+	TraceEnter();
+
+	//DbgWhlist(src);
+	*dst = *src;
+	for (i = 0; i < AuSize_NHASH; i++) {
+		struct hlist_head *h;
+		h = dst->heads + i;
+		if (h->first)
+			h->first->pprev = &h->first;
+		INIT_HLIST_HEAD(src->heads + i);
+	}
+	//DbgWhlist(src);
+	//DbgWhlist(dst);
+	//smp_mb();
+}
+
+/* ---------------------------------------------------------------------- */
+
+void nhash_fin(struct aufs_nhash *whlist)
+{
+	int i;
+	struct hlist_head *head;
+	struct aufs_wh *tpos;
+	struct hlist_node *pos, *n;
+
+	TraceEnter();
+
+	for (i = 0; i < AuSize_NHASH; i++) {
+		head = whlist->heads + i;
+		hlist_for_each_entry_safe(tpos, pos, n, head, wh_hash) {
+			//hlist_del(pos);
+			kfree(tpos);
+		}
+	}
+}
+
+int is_longer_wh(struct aufs_nhash *whlist, aufs_bindex_t btgt, int limit)
+{
+	int n, i;
+	struct hlist_head *head;
+	struct aufs_wh *tpos;
+	struct hlist_node *pos;
+
+	LKTRTrace("limit %d\n", limit);
+	//return 1;
+
+	n = 0;
+	for (i = 0; i < AuSize_NHASH; i++) {
+		head = whlist->heads + i;
+		hlist_for_each_entry(tpos, pos, head, wh_hash)
+			if (tpos->wh_bindex == btgt && ++n > limit)
+				return 1;
+	}
+	return 0;
+}
+
+/* returns found(true) or not */
+int test_known_wh(struct aufs_nhash *whlist, char *name, int namelen)
+{
+	struct hlist_head *head;
+	struct aufs_wh *tpos;
+	struct hlist_node *pos;
+	struct aufs_destr *str;
+
+	LKTRTrace("%.*s\n", namelen, name);
+
+	head = whlist->heads + au_name_hash(name, namelen);
+	hlist_for_each_entry(tpos, pos, head, wh_hash) {
+		str = &tpos->wh_str;
+		LKTRTrace("%.*s\n", str->len, str->name);
+		if (str->len == namelen && !memcmp(str->name, name, namelen))
+			return 1;
+	}
+	return 0;
+}
+
+int append_wh(struct aufs_nhash *whlist, char *name, int namelen,
+	      aufs_bindex_t bindex)
+{
+	int err;
+	struct aufs_destr *str;
+	struct aufs_wh *wh;
+
+	LKTRTrace("%.*s\n", namelen, name);
+
+	err = -ENOMEM;
+	wh = kmalloc(sizeof(*wh) + namelen, GFP_KERNEL);
+	if (unlikely(!wh))
+		goto out;
+	err = 0;
+	wh->wh_bindex = bindex;
+	str = &wh->wh_str;
+	str->len = namelen;
+	memcpy(str->name, name, namelen);
+	hlist_add_head(&wh->wh_hash,
+		       whlist->heads + au_name_hash(name, namelen));
+	//smp_mb();
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void free_vdir(struct aufs_vdir *vdir)
+{
+	aufs_deblk_t **deblk;
+
+	TraceEnter();
+
+	deblk = vdir->vd_deblk;
+	while (vdir->vd_nblk--) {
+		kfree(*deblk);
+		deblk++;
+	}
+	kfree(vdir->vd_deblk);
+	cache_free_vdir(vdir);
+}
+
+static int append_deblk(struct aufs_vdir *vdir)
+{
+	int err, sz, i;
+	aufs_deblk_t **o;
+	union aufs_deblk_p p, deblk_end;
+
+	TraceEnter();
+
+	err = -ENOMEM;
+	sz = sizeof(*o) * vdir->vd_nblk;
+	o = au_kzrealloc(vdir->vd_deblk, sz, sz + sizeof(*o), GFP_KERNEL);
+	if (unlikely(!o))
+		goto out;
+	vdir->vd_deblk = o;
+	p.deblk = kmalloc(sizeof(*p.deblk), GFP_KERNEL);
+	if (p.deblk) {
+		i = vdir->vd_nblk++;
+		vdir->vd_deblk[i] = p.deblk;
+		vdir->vd_last.i = i;
+		vdir->vd_last.p.p = p.p;
+		deblk_end.deblk = p.deblk + 1;
+		err = set_deblk_end(&p, &deblk_end);
+		AuDebugOn(err);
+	}
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static struct aufs_vdir *alloc_vdir(void)
+{
+	struct aufs_vdir *vdir;
+	int err;
+
+	TraceEnter();
+
+	err = -ENOMEM;
+	vdir = cache_alloc_vdir();
+	if (unlikely(!vdir))
+		goto out;
+	vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_KERNEL);
+	if (unlikely(!vdir->vd_deblk))
+		goto out_free;
+
+	vdir->vd_nblk = 0;
+	vdir->vd_version = 0;
+	vdir->vd_jiffy = 0;
+	err = append_deblk(vdir);
+	if (!err)
+		return vdir; /* success */
+
+	kfree(vdir->vd_deblk);
+
+ out_free:
+	cache_free_vdir(vdir);
+ out:
+	vdir = ERR_PTR(err);
+	TraceErrPtr(vdir);
+	return vdir;
+}
+
+static int reinit_vdir(struct aufs_vdir *vdir)
+{
+	int err;
+	union aufs_deblk_p p, deblk_end;
+
+	TraceEnter();
+
+	while (vdir->vd_nblk > 1) {
+		kfree(vdir->vd_deblk[vdir->vd_nblk - 1]);
+		vdir->vd_deblk[vdir->vd_nblk - 1] = NULL;
+		vdir->vd_nblk--;
+	}
+	p.deblk = vdir->vd_deblk[0];
+	deblk_end.deblk = p.deblk + 1;
+	err = set_deblk_end(&p, &deblk_end);
+	AuDebugOn(err);
+	vdir->vd_version = 0;
+	vdir->vd_jiffy = 0;
+	vdir->vd_last.i = 0;
+	vdir->vd_last.p.deblk = vdir->vd_deblk[0];
+	//smp_mb();
+	//DbgVdir(vdir);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void free_dehlist(struct aufs_nhash *dehlist)
+{
+	int i;
+	struct hlist_head *head;
+	struct aufs_dehstr *tpos;
+	struct hlist_node *pos, *n;
+
+	TraceEnter();
+
+	for (i = 0; i < AuSize_NHASH; i++) {
+		head = dehlist->heads + i;
+		hlist_for_each_entry_safe(tpos, pos, n, head, hash) {
+			//hlist_del(pos);
+			cache_free_dehstr(tpos);
+		}
+	}
+}
+
+/* returns found(true) or not */
+static int test_known(struct aufs_nhash *delist, char *name, int namelen)
+{
+	struct hlist_head *head;
+	struct aufs_dehstr *tpos;
+	struct hlist_node *pos;
+	struct aufs_destr *str;
+
+	LKTRTrace("%.*s\n", namelen, name);
+
+	head = delist->heads + au_name_hash(name, namelen);
+	hlist_for_each_entry(tpos, pos, head, hash) {
+		str = tpos->str;
+		LKTRTrace("%.*s\n", str->len, str->name);
+		if (str->len == namelen && !memcmp(str->name, name, namelen))
+			return 1;
+	}
+	return 0;
+
+}
+
+static int append_de(struct aufs_vdir *vdir, char *name, int namelen, ino_t ino,
+		     unsigned int d_type, struct aufs_nhash *delist)
+{
+	int err, sz;
+	union aufs_deblk_p p, *room, deblk_end;
+	struct aufs_dehstr *dehstr;
+
+	LKTRTrace("%.*s %d, i%lu, dt%u\n", namelen, name, namelen, ino, d_type);
+
+	p.deblk = last_deblk(vdir);
+	deblk_end.deblk = p.deblk + 1;
+	room = &vdir->vd_last.p;
+	AuDebugOn(room->p < p.p || deblk_end.p <= room->p
+		  || !is_deblk_end(room, &deblk_end));
+
+	sz = calc_size(namelen);
+	if (unlikely(sz > deblk_end.p - room->p)) {
+		err = append_deblk(vdir);
+		if (unlikely(err))
+			goto out;
+		p.deblk = last_deblk(vdir);
+		deblk_end.deblk = p.deblk + 1;
+		//smp_mb();
+		AuDebugOn(room->p != p.p);
+	}
+
+	err = -ENOMEM;
+	dehstr = cache_alloc_dehstr();
+	if (unlikely(!dehstr))
+		goto out;
+	dehstr->str = &room->de->de_str;
+	hlist_add_head(&dehstr->hash,
+		       delist->heads + au_name_hash(name, namelen));
+
+	room->de->de_ino = ino;
+	room->de->de_type = d_type;
+	room->de->de_str.len = namelen;
+	memcpy(room->de->de_str.name, name, namelen);
+
+	err = 0;
+	room->p += sz;
+	if (unlikely(set_deblk_end(room, &deblk_end)))
+		err = append_deblk(vdir);
+	//smp_mb();
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct fillvdir_arg {
+	struct file		*file;
+	struct aufs_vdir 	*vdir;
+	struct aufs_nhash	*delist;
+	struct aufs_nhash	*whlist;
+	aufs_bindex_t		bindex;
+	int			err;
+	int			called;
+};
+
+static int fillvdir(void *__arg, const char *__name, int namelen, loff_t offset,
+		    au_filldir_ino_t h_ino, unsigned int d_type)
+{
+	struct fillvdir_arg *arg = __arg;
+	char *name = (void*)__name;
+	aufs_bindex_t bindex, bend;
+	struct xino xino;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, namelen %d, i%Lu, dt%u\n",
+		  namelen, name, namelen, (u64)h_ino, d_type);
+
+	sb = arg->file->f_dentry->d_sb;
+	bend = arg->bindex;
+	arg->err = 0;
+	arg->called++;
+	//smp_mb();
+	if (namelen <= AUFS_WH_PFX_LEN
+	    || memcmp(name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
+		for (bindex = 0; bindex < bend; bindex++)
+			if (test_known(arg->delist + bindex, name, namelen)
+			    || test_known_wh(arg->whlist + bindex, name,
+					     namelen))
+				goto out; /* already exists or whiteouted */
+
+		arg->err = xino_read(sb, bend, h_ino, &xino);
+		if (!arg->err && !xino.ino) {
+			//struct inode *h_inode;
+			xino.ino = xino_new_ino(sb);
+			if (unlikely(!xino.ino))
+				arg->err = -EIO;
+#if 0
+			//xino.h_gen = AuXino_INVALID_HGEN;
+			h_inode = ilookup(sbr_sb(sb, bend), h_ino);
+			if (h_inode) {
+				if (!is_bad_inode(h_inode)) {
+					xino.h_gen = h_inode->i_generation;
+					WARN_ON(xino.h_gen
+						== AuXino_INVALID_HGEN);
+				}
+				iput(h_inode);
+			}
+#endif
+			arg->err = xino_write(sb, bend, h_ino, &xino);
+		}
+		if (!arg->err)
+			arg->err = append_de(arg->vdir, name, namelen, xino.ino,
+					     d_type, arg->delist + bend);
+	} else {
+		name += AUFS_WH_PFX_LEN;
+		namelen -= AUFS_WH_PFX_LEN;
+		for (bindex = 0; bindex < bend; bindex++)
+			if (test_known_wh(arg->whlist + bend, name, namelen))
+				goto out; /* already whiteouted */
+		arg->err = append_wh(arg->whlist + bend, name, namelen, bend);
+	}
+
+ out:
+	if (!arg->err)
+		arg->vdir->vd_jiffy = jiffies;
+	//smp_mb();
+	TraceErr(arg->err);
+	return arg->err;
+}
+
+static int read_vdir(struct file *file, int may_read)
+{
+	int err, do_read, dlgt;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct aufs_vdir *vdir, *allocated;
+	unsigned long expire;
+	struct fillvdir_arg arg;
+	aufs_bindex_t bindex, bend, bstart;
+	struct super_block *sb;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, may %d\n", DLNPair(dentry), may_read);
+	FiMustWriteLock(file);
+	inode = dentry->d_inode;
+	IMustLock(inode);
+	IiMustWriteLock(inode);
+	AuDebugOn(!S_ISDIR(inode->i_mode));
+
+	err = 0;
+	allocated = NULL;
+	do_read = 0;
+	sb = inode->i_sb;
+	expire = stosi(sb)->si_rdcache;
+	vdir = ivdir(inode);
+#if 0
+	Dbg("vdir %p, fvdir %p, i_ver %lu, vd_ver %lu, jiffy %lu, vd_jiffy %lu,"
+	    " expire %lu\n",
+	    vdir, fvdir_cache(file), inode->i_version,
+	    vdir ? vdir->vd_version : 0LU, jiffies,
+	    vdir ? vdir->vd_jiffy + expire : 0LU, expire);
+#endif
+	if (!vdir) {
+		AuDebugOn(fvdir_cache(file));
+		do_read = 1;
+		vdir = alloc_vdir();
+		err = PTR_ERR(vdir);
+		if (IS_ERR(vdir))
+			goto out;
+		err = 0;
+		allocated = vdir;
+	} else if (may_read
+		   && (inode->i_version != vdir->vd_version
+		       || time_after(jiffies, vdir->vd_jiffy + expire))) {
+		LKTRTrace("iver %lu, vdver %lu, exp %lu\n",
+			  inode->i_version, vdir->vd_version,
+			  vdir->vd_jiffy + expire);
+		do_read = 1;
+		err = reinit_vdir(vdir);
+		if (unlikely(err))
+			goto out;
+	}
+	//DbgVdir(vdir); goto out;
+
+#if 0
+	Dbg("do_read %d\n", do_read);
+#endif
+	if (!do_read)
+		return 0; /* success */
+
+	err = -ENOMEM;
+	bend = fbend(file);
+	arg.delist = kmalloc(sizeof(*arg.delist) * (bend + 1), GFP_KERNEL);
+	if (unlikely(!arg.delist))
+		goto out_vdir;
+	arg.whlist = kmalloc(sizeof(*arg.whlist) * (bend + 1), GFP_KERNEL);
+	if (unlikely(!arg.whlist))
+		goto out_delist;
+	err = 0;
+	for (bindex = 0; bindex <= bend; bindex++) {
+		nhash_init(arg.delist + bindex);
+		nhash_init(arg.whlist + bindex);
+	}
+
+	dlgt = need_dlgt(sb);
+	arg.file = file;
+	arg.vdir = vdir;
+	bstart = fbstart(file);
+	for (bindex = bstart; !err && bindex <= bend; bindex++) {
+		struct file *hf;
+		loff_t offset;
+
+		hf = au_h_fptr_i(file, bindex);
+		if (!hf)
+			continue;
+
+		err = offset = vfsub_llseek(hf, 0, SEEK_SET);
+		if (unlikely(offset))
+			break;
+		arg.bindex = bindex;
+		do {
+			arg.err = 0;
+			arg.called = 0;
+			//smp_mb();
+			err = vfsub_readdir(hf, fillvdir, &arg, dlgt);
+			if (err >= 0)
+				err = arg.err;
+		} while (!err && arg.called);
+	}
+
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		free_dehlist(arg.delist + bindex);
+		nhash_fin(arg.whlist + bindex);
+	}
+	kfree(arg.whlist);
+
+ out_delist:
+	kfree(arg.delist);
+ out_vdir:
+	if (!err) {
+		//file->f_pos = 0;
+		vdir->vd_version = inode->i_version;
+		vdir->vd_last.i = 0;
+		vdir->vd_last.p.deblk = vdir->vd_deblk[0];
+		if (allocated)
+			set_ivdir(inode, allocated);
+	} else if (allocated)
+		free_vdir(allocated);
+	//DbgVdir(vdir); goto out;
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+static int copy_vdir(struct aufs_vdir *tgt, struct aufs_vdir *src)
+{
+	int err, i, rerr, n;
+
+	TraceEnter();
+	AuDebugOn(tgt->vd_nblk != 1);
+	//DbgVdir(tgt);
+
+	err = -ENOMEM;
+	if (tgt->vd_nblk < src->vd_nblk) {
+		aufs_deblk_t **p;
+		p = au_kzrealloc(tgt->vd_deblk, sizeof(*p) * tgt->vd_nblk,
+				 sizeof(*p) * src->vd_nblk, GFP_KERNEL);
+		if (unlikely(!p))
+			goto out;
+		tgt->vd_deblk = p;
+	}
+
+	n = tgt->vd_nblk = src->vd_nblk;
+	memcpy(tgt->vd_deblk[0], src->vd_deblk[0], AuSize_DEBLK);
+	//tgt->vd_last.i = 0;
+	//tgt->vd_last.p.deblk = tgt->vd_deblk[0];
+	tgt->vd_version = src->vd_version;
+	tgt->vd_jiffy = src->vd_jiffy;
+
+	for (i = 1; i < n; i++) {
+		tgt->vd_deblk[i] = kmalloc(AuSize_DEBLK, GFP_KERNEL);
+		if (tgt->vd_deblk[i])
+			memcpy(tgt->vd_deblk[i], src->vd_deblk[i],
+			       AuSize_DEBLK);
+		else
+			goto out;
+	}
+	//smp_mb();
+	//DbgVdir(tgt);
+	return 0; /* success */
+
+ out:
+	rerr = reinit_vdir(tgt);
+	BUG_ON(rerr);
+	TraceErr(err);
+	return err;
+}
+
+int au_init_vdir(struct file *file)
+{
+	int err;
+	struct dentry *dentry;
+	struct inode *inode;
+	struct aufs_vdir *vdir_cache, *allocated;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, pos %Ld\n", DLNPair(dentry), file->f_pos);
+	FiMustWriteLock(file);
+	inode = dentry->d_inode;
+	IiMustWriteLock(inode);
+	AuDebugOn(!S_ISDIR(inode->i_mode));
+
+	err = read_vdir(file, !file->f_pos);
+	if (unlikely(err))
+		goto out;
+	//DbgVdir(ivdir(inode)); goto out;
+
+	allocated = NULL;
+	vdir_cache = fvdir_cache(file);
+	if (!vdir_cache) {
+		vdir_cache = alloc_vdir();
+		err = PTR_ERR(vdir_cache);
+		if (IS_ERR(vdir_cache))
+			goto out;
+		allocated = vdir_cache;
+	} else if (!file->f_pos && vdir_cache->vd_version != file->f_version) {
+		err = reinit_vdir(vdir_cache);
+		if (unlikely(err))
+			goto out;
+	} else
+		return 0; /* success */
+	//err = 0; DbgVdir(vdir_cache); goto out;
+
+	err = copy_vdir(vdir_cache, ivdir(inode));
+	if (!err) {
+		file->f_version = inode->i_version;
+		if (allocated)
+			set_fvdir_cache(file, allocated);
+	} else if (allocated)
+		free_vdir(allocated);
+
+ out:
+	//au_debug_on();
+	TraceErr(err);
+	//au_debug_off();
+	return err;
+}
+
+static loff_t calc_offset(struct aufs_vdir *vdir)
+{
+	loff_t offset;
+	union aufs_deblk_p p;
+
+	p.deblk = vdir->vd_deblk[vdir->vd_last.i];
+	offset = vdir->vd_last.p.p - p.p;
+	offset += sizeof(*p.deblk) * vdir->vd_last.i;
+	return offset;
+}
+
+/* returns true or false */
+static int seek_vdir(struct file *file)
+{
+	int valid, i, n;
+	struct dentry *dentry;
+	struct aufs_vdir *vdir_cache;
+	loff_t offset;
+	union aufs_deblk_p p, deblk_end;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, pos %Ld\n", DLNPair(dentry), file->f_pos);
+	vdir_cache = fvdir_cache(file);
+	AuDebugOn(!vdir_cache);
+	//DbgVdir(vdir_cache);
+
+	valid = 1;
+	offset = calc_offset(vdir_cache);
+	LKTRTrace("offset %Ld\n", offset);
+	if (file->f_pos == offset)
+		goto out;
+
+	vdir_cache->vd_last.i = 0;
+	vdir_cache->vd_last.p.deblk = vdir_cache->vd_deblk[0];
+	if (!file->f_pos)
+		goto out;
+
+	valid = 0;
+	i = file->f_pos / AuSize_DEBLK;
+	LKTRTrace("i %d\n", i);
+	if (i >= vdir_cache->vd_nblk)
+		goto out;
+
+	n = vdir_cache->vd_nblk;
+	for (; i < n; i++) {
+		p.deblk = vdir_cache->vd_deblk[i];
+		deblk_end.deblk = p.deblk + 1;
+		offset = i;
+		offset *= AuSize_DEBLK;
+		while (!is_deblk_end(&p, &deblk_end) && offset < file->f_pos) {
+			int l;
+			l = calc_size(p.de->de_str.len);
+			offset += l;
+			p.p += l;
+		}
+		if (!is_deblk_end(&p, &deblk_end)) {
+			valid = 1;
+			vdir_cache->vd_last.i = i;
+			vdir_cache->vd_last.p = p;
+			break;
+		}
+	}
+
+ out:
+	//smp_mb();
+	TraceErr(!valid);
+	return valid;
+}
+
+int au_fill_de(struct file *file, void *dirent, filldir_t filldir)
+{
+	int err, l;
+	struct dentry *dentry;
+	struct aufs_vdir *vdir_cache;
+	struct aufs_de *de;
+	union aufs_deblk_p deblk_end;
+
+	dentry = file->f_dentry;
+	LKTRTrace("%.*s, pos %Ld\n", DLNPair(dentry), file->f_pos);
+	vdir_cache = fvdir_cache(file);
+	AuDebugOn(!vdir_cache);
+	//DbgVdir(vdir_cache);
+
+	if (!seek_vdir(file))
+		return 0;
+
+	while (1) {
+		deblk_end.deblk
+			= vdir_cache->vd_deblk[vdir_cache->vd_last.i] + 1;
+		while (!is_deblk_end(&vdir_cache->vd_last.p, &deblk_end)) {
+			de = vdir_cache->vd_last.p.de;
+			LKTRTrace("%.*s, off%Ld, i%lu, dt%d\n",
+				  de->de_str.len, de->de_str.name,
+				  file->f_pos, de->de_ino, de->de_type);
+			err = filldir(dirent, de->de_str.name, de->de_str.len,
+				      file->f_pos, de->de_ino, de->de_type);
+			if (unlikely(err)) {
+				TraceErr(err);
+				//return err;
+				//todo: ignore the error caused by udba.
+				return 0;
+			}
+
+			l = calc_size(de->de_str.len);
+			vdir_cache->vd_last.p.p += l;
+			file->f_pos += l;
+		}
+		if (vdir_cache->vd_last.i < vdir_cache->vd_nblk - 1) {
+			vdir_cache->vd_last.i++;
+			vdir_cache->vd_last.p.deblk
+				= vdir_cache->vd_deblk[vdir_cache->vd_last.i];
+			file->f_pos = sizeof(*vdir_cache->vd_last.p.deblk)
+				* vdir_cache->vd_last.i;
+			continue;
+		}
+		break;
+	}
+
+	//smp_mb();
+	return 0;
+}
diff -ruN linux-2.6.22/fs/aufs/vfsub.c linux-2.6.22-aufs/fs/aufs/vfsub.c
--- linux-2.6.22/fs/aufs/vfsub.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/vfsub.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,705 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: vfsub.c,v 1.10 2007/07/02 05:07:38 sfjro Exp $ */
+// I'm going to slightly mad
+
+#include "aufs.h"
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_DLGT
+struct permission_args {
+	int *errp;
+	struct inode *inode;
+	int mask;
+	struct nameidata *nd;
+};
+
+static void call_permission(void *args)
+{
+	struct permission_args *a = args;
+	*a->errp = do_vfsub_permission(a->inode, a->mask, a->nd);
+}
+
+int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd,
+		     int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_permission(inode, mask, nd);
+	else {
+		int err, wkq_err;
+		struct permission_args args = {
+			.errp	= &err,
+			.inode	= inode,
+			.mask	= mask,
+			.nd	= nd
+		};
+		wkq_err = au_wkq_wait(call_permission, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct create_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *dentry;
+	int mode;
+	struct nameidata *nd;
+};
+
+static void call_create(void *args)
+{
+	struct create_args *a = args;
+	*a->errp = do_vfsub_create(a->dir, a->dentry, a->mode, a->nd);
+}
+
+int vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
+		 struct nameidata *nd, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_create(dir, dentry, mode, nd);
+	else {
+		int err, wkq_err;
+		struct create_args args = {
+			.errp	= &err,
+			.dir	= dir,
+			.dentry	= dentry,
+			.mode	= mode,
+			.nd	= nd
+		};
+		wkq_err = au_wkq_wait(call_create, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct symlink_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *dentry;
+	const char *symname;
+	int mode;
+};
+
+static void call_symlink(void *args)
+{
+	struct symlink_args *a = args;
+	*a->errp = do_vfsub_symlink(a->dir, a->dentry, a->symname, a->mode);
+}
+
+int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname,
+		  int mode, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_symlink(dir, dentry, symname, mode);
+	else {
+		int err, wkq_err;
+		struct symlink_args args = {
+			.errp		= &err,
+			.dir		= dir,
+			.dentry		= dentry,
+			.symname	= symname,
+			.mode		= mode
+		};
+		wkq_err = au_wkq_wait(call_symlink, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct mknod_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *dentry;
+	int mode;
+	dev_t dev;
+};
+
+static void call_mknod(void *args)
+{
+	struct mknod_args *a = args;
+	*a->errp = do_vfsub_mknod(a->dir, a->dentry, a->mode, a->dev);
+}
+
+int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev,
+		int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_mknod(dir, dentry, mode, dev);
+	else {
+		int err, wkq_err;
+		struct mknod_args args = {
+			.errp	= &err,
+			.dir	= dir,
+			.dentry	= dentry,
+			.mode	= mode,
+			.dev	= dev
+		};
+		wkq_err = au_wkq_wait(call_mknod, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct mkdir_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *dentry;
+	int mode;
+};
+
+static void call_mkdir(void *args)
+{
+	struct mkdir_args *a = args;
+	*a->errp = do_vfsub_mkdir(a->dir, a->dentry, a->mode);
+}
+
+int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_mkdir(dir, dentry, mode);
+	else {
+		int err, wkq_err;
+		struct mkdir_args args = {
+			.errp	= &err,
+			.dir	= dir,
+			.dentry	= dentry,
+			.mode	= mode
+		};
+		wkq_err = au_wkq_wait(call_mkdir, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct link_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *src_dentry, *dentry;
+};
+
+static void call_link(void *args)
+{
+	struct link_args *a = args;
+	*a->errp = do_vfsub_link(a->src_dentry, a->dir, a->dentry);
+}
+
+int vfsub_link(struct dentry *src_dentry, struct inode *dir,
+	       struct dentry *dentry, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_link(src_dentry, dir, dentry);
+	else {
+		int err, wkq_err;
+		struct link_args args = {
+			.errp		= &err,
+			.src_dentry	= src_dentry,
+			.dir		= dir,
+			.dentry		= dentry
+		};
+		wkq_err = au_wkq_wait(call_link, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct rename_args {
+	int *errp;
+	struct inode *src_dir, *dir;
+	struct dentry *src_dentry, *dentry;
+};
+
+static void call_rename(void *args)
+{
+	struct rename_args *a = args;
+	*a->errp = do_vfsub_rename(a->src_dir, a->src_dentry, a->dir,
+				   a->dentry);
+}
+
+int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
+		 struct inode *dir, struct dentry *dentry, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_rename(src_dir, src_dentry, dir, dentry);
+	else {
+		int err, wkq_err;
+		struct rename_args args = {
+			.errp		= &err,
+			.src_dir	= src_dir,
+			.src_dentry	= src_dentry,
+			.dir		= dir,
+			.dentry		= dentry
+		};
+		wkq_err = au_wkq_wait(call_rename, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct rmdir_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *dentry;
+};
+
+static void call_rmdir(void *args)
+{
+	struct rmdir_args *a = args;
+	*a->errp = do_vfsub_rmdir(a->dir, a->dentry);
+}
+
+int vfsub_rmdir(struct inode *dir, struct dentry *dentry, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_rmdir(dir, dentry);
+	else {
+		int err, wkq_err;
+		struct rmdir_args args = {
+			.errp	= &err,
+			.dir	= dir,
+			.dentry	= dentry
+		};
+		wkq_err = au_wkq_wait(call_rmdir, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct read_args {
+	ssize_t *errp;
+	struct file *file;
+	union {
+		void *kbuf;
+		char __user *ubuf;
+	};
+	size_t count;
+	loff_t *ppos;
+};
+
+static void call_read_k(void *args)
+{
+	struct read_args *a = args;
+	LKTRTrace("%.*s, cnt %lu, pos %Ld\n",
+		  DLNPair(a->file->f_dentry), (unsigned long)a->count,
+		  *a->ppos);
+	*a->errp = do_vfsub_read_k(a->file, a->kbuf, a->count, a->ppos);
+}
+
+ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
+		     loff_t *ppos, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_read_u(file, ubuf, count, ppos);
+	else {
+		int wkq_err;
+		ssize_t err, read;
+		struct read_args args = {
+			.errp	= &err,
+			.file	= file,
+			.count	= count,
+			.ppos	= ppos
+		};
+
+		if (unlikely(!count))
+			return 0;
+
+		/*
+		 * workaround an application bug.
+		 * generally, read(2) or write(2) may return the value shorter
+		 * than requested. But many applications don't support it,
+		 * for example bash.
+		 */
+		err = -ENOMEM;
+		if (args.count > PAGE_SIZE)
+			args.count = PAGE_SIZE;
+		args.kbuf = kmalloc(args.count, GFP_KERNEL);
+		if (unlikely(!args.kbuf))
+			goto out;
+
+		read = 0;
+		do {
+			wkq_err = au_wkq_wait(call_read_k, &args, /*dlgt*/1);
+			if (unlikely(wkq_err))
+				err = wkq_err;
+			if (unlikely(err > 0
+				     && copy_to_user(ubuf, args.kbuf, err))) {
+				err = -EFAULT;
+				goto out_free;
+			} else if (!err)
+				break;
+			else if (unlikely(err < 0))
+				goto out_free;
+			count -= err;
+			/* do not read too much because of file i/o pointer */
+			if (unlikely(count < args.count))
+				args.count = count;
+			ubuf += err;
+			read += err;
+		} while (count);
+		smp_mb(); /* flush ubuf */
+		err = read;
+
+	out_free:
+		kfree(args.kbuf);
+	out:
+		return err;
+	}
+}
+
+ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, loff_t *ppos,
+		     int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_read_k(file, kbuf, count, ppos);
+	else {
+		ssize_t err;
+		int wkq_err;
+		struct read_args args = {
+			.errp	= &err,
+			.file	= file,
+			.count	= count,
+			.ppos	= ppos
+		};
+		args.kbuf = kbuf;
+		wkq_err = au_wkq_wait(call_read_k, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct write_args {
+	ssize_t *errp;
+	struct file *file;
+	union {
+		void *kbuf;
+		const char __user *ubuf;
+	};
+	void *buf;
+	size_t count;
+	loff_t *ppos;
+};
+
+static void call_write_k(void *args)
+{
+	struct write_args *a = args;
+	LKTRTrace("%.*s, cnt %lu, pos %Ld\n",
+		  DLNPair(a->file->f_dentry), (unsigned long)a->count,
+		  *a->ppos);
+	*a->errp = do_vfsub_write_k(a->file, a->kbuf, a->count, a->ppos);
+}
+
+ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
+		      loff_t *ppos, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_write_u(file, ubuf, count, ppos);
+	else {
+		ssize_t err, written;
+		int wkq_err;
+		struct write_args args = {
+			.errp	= &err,
+			.file	= file,
+			.count	= count,
+			.ppos	= ppos
+		};
+
+		if (unlikely(!count))
+			return 0;
+
+		/*
+		 * workaround an application bug.
+		 * generally, read(2) or write(2) may return the value shorter
+		 * than requested. But many applications don't support it,
+		 * for example bash.
+		 */
+		err = -ENOMEM;
+		if (args.count > PAGE_SIZE)
+			args.count = PAGE_SIZE;
+		args.kbuf = kmalloc(args.count, GFP_KERNEL);
+		if (unlikely(!args.kbuf))
+			goto out;
+
+		written = 0;
+		do {
+			if (unlikely(copy_from_user(args.kbuf, ubuf,
+						    args.count))) {
+				err = -EFAULT;
+				goto out_free;
+			}
+
+			wkq_err = au_wkq_wait(call_write_k, &args, /*dlgt*/1);
+			if (unlikely(wkq_err))
+				err = wkq_err;
+			if (err > 0) {
+				count -= err;
+				if (count < args.count)
+					args.count = count;
+				ubuf += err;
+				written += err;
+			} else if (!err)
+				break;
+			else if (unlikely(err < 0))
+				goto out_free;
+		} while (count);
+		err = written;
+
+	out_free:
+		kfree(args.kbuf);
+	out:
+		return err;
+	}
+}
+
+ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos,
+		      int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_write_k(file, kbuf, count, ppos);
+	else {
+		ssize_t err;
+		int wkq_err;
+		struct write_args args = {
+			.errp	= &err,
+			.file	= file,
+			.count	= count,
+			.ppos	= ppos
+		};
+		args.kbuf = kbuf;
+		wkq_err = au_wkq_wait(call_write_k, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct readdir_args {
+	int *errp;
+	struct file *file;
+	filldir_t filldir;
+	void *arg;
+};
+
+static void call_readdir(void *args)
+{
+	struct readdir_args *a = args;
+	*a->errp = do_vfsub_readdir(a->file, a->filldir, a->arg);
+}
+
+int vfsub_readdir(struct file *file, filldir_t filldir, void *arg, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_readdir(file, filldir, arg);
+	else {
+		int err, wkq_err;
+		struct readdir_args args = {
+			.errp		= &err,
+			.file		= file,
+			.filldir	= filldir,
+			.arg		= arg
+		};
+		wkq_err = au_wkq_wait(call_readdir, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+#endif /* CONFIG_AUFS_DLGT */
+
+/* ---------------------------------------------------------------------- */
+
+struct notify_change_args {
+	int *errp;
+	struct dentry *h_dentry;
+	struct iattr *ia;
+};
+
+static void call_notify_change(void *args)
+{
+	struct notify_change_args *a = args;
+	struct inode *h_inode;
+
+	LKTRTrace("%.*s, ia_valid 0x%x\n",
+		  DLNPair(a->h_dentry), a->ia->ia_valid);
+	h_inode = a->h_dentry->d_inode;
+	IMustLock(h_inode);
+
+	*a->errp = -EPERM;
+	if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) {
+		lockdep_off();
+		*a->errp = notify_change(a->h_dentry, a->ia);
+		lockdep_on();
+	}
+	TraceErr(*a->errp);
+}
+
+int vfsub_notify_change(struct dentry *dentry, struct iattr *ia, int dlgt)
+{
+	int err;
+	struct notify_change_args args = {
+		.errp		= &err,
+		.h_dentry	= dentry,
+		.ia		= ia
+	};
+
+#ifndef CONFIG_AUFS_DLGT
+	call_notify_change(&args);
+#else
+	if (!dlgt)
+		call_notify_change(&args);
+	else {
+		int wkq_err;
+		wkq_err = au_wkq_wait(call_notify_change, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+#endif
+
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct unlink_args {
+	int *errp;
+	struct inode *dir;
+	struct dentry *dentry;
+};
+
+static void call_unlink(void *args)
+{
+	struct unlink_args *a = args;
+	struct inode *h_inode;
+	const int stop_sillyrename = (au_is_nfs(a->dentry->d_sb)
+				      && atomic_read(&a->dentry->d_count) == 1);
+
+	LKTRTrace("%.*s, stop_silly %d, cnt %d\n",
+		  DLNPair(a->dentry), stop_sillyrename,
+		  atomic_read(&a->dentry->d_count));
+	IMustLock(a->dir);
+
+	if (!stop_sillyrename)
+		dget(a->dentry);
+	h_inode = a->dentry->d_inode;
+	if (h_inode)
+		atomic_inc_return(&h_inode->i_count);
+#if 0 // partial testing
+	{
+		struct qstr *name = &a->dentry->d_name;
+		if (name->len == sizeof(AUFS_XINO_FNAME) - 1
+		    && !strncmp(name->name, AUFS_XINO_FNAME, name->len)) {
+			lockdep_off();
+			*a->errp = do_vfsub_unlink(a->dir, a->dentry);
+			lockdep_on();
+		} else
+			err = -1;
+	}
+#else
+	/* vfs_unlink() locks inode */
+	lockdep_off();
+	*a->errp = do_vfsub_unlink(a->dir, a->dentry);
+	lockdep_on();
+#endif
+
+	if (!stop_sillyrename)
+		dput(a->dentry);
+	if (h_inode)
+		iput(h_inode);
+
+	TraceErr(*a->errp);
+}
+
+/*
+ * @dir: must be locked.
+ * @dentry: target dentry.
+ */
+int vfsub_unlink(struct inode *dir, struct dentry *dentry, int dlgt)
+{
+	int err;
+	struct unlink_args args = {
+		.errp	= &err,
+		.dir	= dir,
+		.dentry	= dentry
+	};
+
+	if (!dlgt)
+		call_unlink(&args);
+	else {
+		int wkq_err;
+		wkq_err = au_wkq_wait(call_unlink, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct statfs_args {
+	int *errp;
+	void *arg;
+	struct kstatfs *buf;
+};
+
+static void call_statfs(void *args)
+{
+	struct statfs_args *a = args;
+	*a->errp = vfs_statfs(a->arg, a->buf);
+}
+
+int vfsub_statfs(void *arg, struct kstatfs *buf, int dlgt)
+{
+	int err;
+	struct statfs_args args = {
+		.errp	= &err,
+		.arg	= arg,
+		.buf	= buf
+	};
+
+#ifndef CONFIG_AUFS_DLGT
+	call_statfs(&args);
+#else
+	if (!dlgt)
+		call_statfs(&args);
+	else {
+		int wkq_err;
+		wkq_err = au_wkq_wait(call_statfs, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+#endif
+	return err;
+}
diff -ruN linux-2.6.22/fs/aufs/vfsub.h linux-2.6.22-aufs/fs/aufs/vfsub.h
--- linux-2.6.22/fs/aufs/vfsub.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/vfsub.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2007 Junjiro 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
+ */
+
+/* $Id: vfsub.h,v 1.13 2007/07/15 20:06:18 sfjro Exp $ */
+
+#ifndef __AUFS_VFSUB_H__
+#define __AUFS_VFSUB_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/version.h>
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
+#include <linux/uaccess.h>
+#else
+#include <asm/uaccess.h>
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/* lock subclass for hidden inode */
+/* default MAX_LOCKDEP_SUBCLASSES(8) is not enough */
+// todo: reduce it by dcsub.
+enum {
+	AuLsc_I_Begin = I_MUTEX_QUOTA,
+	AuLsc_I_GPARENT,	/* setattr with inotify */
+	AuLsc_I_PARENT,		/* hidden inode, parent first */
+	AuLsc_I_CHILD,
+	AuLsc_I_PARENT2,	/* copyup dirs */
+	AuLsc_I_CHILD2,
+	AuLsc_I_End
+};
+
+/* simple abstraction */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 16)
+static inline void vfsub_i_lock(struct inode *i)
+{
+	down(&i->i_sem);
+}
+
+static inline void vfsub_i_lock_nested(struct inode *i, unsigned lsc)
+{
+	vfsub_i_lock(i);
+}
+
+static inline void vfsub_i_unlock(struct inode *i)
+{
+	up(&i->i_sem);
+}
+
+static inline int vfsub_i_trylock(struct inode *i)
+{
+	return down_trylock(&i->i_sem);
+}
+
+#define IMustLock(i)	AuDebugOn(!down_trylock(&(i)->i_sem))
+#else
+static inline void vfsub_i_lock(struct inode *i)
+{
+	mutex_lock(&i->i_mutex);
+}
+
+static inline void vfsub_i_lock_nested(struct inode *i, unsigned lsc)
+{
+	mutex_lock_nested(&i->i_mutex, lsc);
+}
+
+static inline void vfsub_i_unlock(struct inode *i)
+{
+	mutex_unlock(&i->i_mutex);
+}
+
+static inline int vfsub_i_trylock(struct inode *i)
+{
+	return mutex_trylock(&i->i_mutex);
+}
+
+#define IMustLock(i)	MtxMustLock(&(i)->i_mutex)
+#endif /* LINUX_VERSION_CODE */
+
+/* ---------------------------------------------------------------------- */
+
+/* simple abstractions, for future use */
+static inline
+int do_vfsub_permission(struct inode *inode, int mask, struct nameidata *nd)
+{
+	LKTRTrace("i%lu, mask 0x%x, nd %p\n", inode->i_ino, mask, nd);
+	return permission(inode, mask, nd);
+}
+
+static inline
+struct file *vfsub_filp_open(const char *path, int oflags, int mode)
+{
+	struct file *err;
+
+	LKTRTrace("%s\n", path);
+
+	lockdep_off();
+	err = filp_open(path, oflags, mode);
+	lockdep_on();
+	return err;
+}
+
+static inline
+int vfsub_path_lookup(const char *name, unsigned int flags,
+		      struct nameidata *nd)
+{
+	int err;
+
+	LKTRTrace("%s\n", name);
+
+	/* lockdep_off(); */
+	err = path_lookup(name, flags, nd);
+	/* lockdep_on(); */
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static inline
+int do_vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
+		    struct nameidata *nd)
+{
+	LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, DLNPair(dentry), mode);
+	return vfs_create(dir, dentry, mode, nd);
+}
+
+static inline
+int do_vfsub_symlink(struct inode *dir, struct dentry *dentry,
+		     const char *symname, int mode)
+{
+	LKTRTrace("i%lu, %.*s, %s, 0x%x\n",
+		  dir->i_ino, DLNPair(dentry), symname, mode);
+	return vfs_symlink(dir, dentry, symname, mode);
+}
+
+static inline
+int do_vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode,
+		   dev_t dev)
+{
+	LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, DLNPair(dentry), mode);
+	return vfs_mknod(dir, dentry, mode, dev);
+}
+
+static inline
+int do_vfsub_link(struct dentry *src_dentry, struct inode *dir,
+		  struct dentry *dentry)
+{
+	int err;
+
+	LKTRTrace("%.*s, i%lu, %.*s\n",
+		  DLNPair(src_dentry), dir->i_ino, DLNPair(dentry));
+
+	lockdep_off();
+	err = vfs_link(src_dentry, dir, dentry);
+	lockdep_on();
+	return err;
+}
+
+static inline
+int do_vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
+		    struct inode *dir, struct dentry *dentry)
+{
+	int err;
+
+	LKTRTrace("i%lu, %.*s, i%lu, %.*s\n",
+		  src_dir->i_ino, DLNPair(src_dentry),
+		  dir->i_ino, DLNPair(dentry));
+
+	lockdep_off();
+	err = vfs_rename(src_dir, src_dentry, dir, dentry);
+	lockdep_on();
+	return err;
+}
+
+static inline
+int do_vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, DLNPair(dentry), mode);
+	return vfs_mkdir(dir, dentry, mode);
+}
+
+static inline int do_vfsub_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	int err;
+
+	LKTRTrace("i%lu, %.*s\n", dir->i_ino, DLNPair(dentry));
+
+	lockdep_off();
+	err = vfs_rmdir(dir, dentry);
+	lockdep_on();
+	return err;
+}
+
+static inline int do_vfsub_unlink(struct inode *dir, struct dentry *dentry)
+{
+	int err;
+
+	LKTRTrace("i%lu, %.*s\n", dir->i_ino, DLNPair(dentry));
+
+	lockdep_off();
+	err = vfs_unlink(dir, dentry);
+	lockdep_on();
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static inline
+ssize_t do_vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
+			loff_t *ppos)
+{
+	ssize_t err;
+
+	LKTRTrace("%.*s, cnt %lu, pos %Ld\n",
+		  DLNPair(file->f_dentry), (unsigned long)count, *ppos);
+
+	/* nfs uses some locks */
+	lockdep_off();
+	err = vfs_read(file, ubuf, count, ppos);
+	lockdep_on();
+	return err;
+}
+
+// kernel_read() ??
+static inline
+ssize_t do_vfsub_read_k(struct file *file, void *kbuf, size_t count,
+			loff_t *ppos)
+{
+	ssize_t err;
+	mm_segment_t oldfs;
+
+	oldfs = get_fs();
+	set_fs(KERNEL_DS);
+	err = do_vfsub_read_u(file, (char __user*)kbuf, count, ppos);
+	set_fs(oldfs);
+	return err;
+}
+
+static inline
+ssize_t do_vfsub_write_u(struct file *file, const char __user *ubuf,
+			 size_t count, loff_t *ppos)
+{
+	ssize_t err;
+
+	LKTRTrace("%.*s, cnt %lu, pos %Ld\n",
+		  DLNPair(file->f_dentry), (unsigned long)count, *ppos);
+
+	lockdep_off();
+	err = vfs_write(file, ubuf, count, ppos);
+	lockdep_on();
+	return err;
+}
+
+static inline
+ssize_t do_vfsub_write_k(struct file *file, void *kbuf, size_t count,
+			 loff_t *ppos)
+{
+	ssize_t err;
+	mm_segment_t oldfs;
+
+	oldfs = get_fs();
+	set_fs(KERNEL_DS);
+	err = do_vfsub_write_u(file, (const char __user*)kbuf, count, ppos);
+	set_fs(oldfs);
+	return err;
+}
+
+static inline
+int do_vfsub_readdir(struct file *file, filldir_t filldir, void *arg)
+{
+	int err;
+
+	LKTRTrace("%.*s\n", DLNPair(file->f_dentry));
+
+	lockdep_off();
+	err = vfs_readdir(file, filldir, arg);
+	lockdep_on();
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static inline loff_t vfsub_llseek(struct file *file, loff_t offset, int origin)
+{
+	loff_t err;
+
+	LKTRTrace("%.*s\n", DLNPair(file->f_dentry));
+
+	lockdep_off();
+	err = vfs_llseek(file, offset, origin);
+	lockdep_on();
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_DLGT
+int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd,
+		     int dlgt);
+
+int vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
+		 struct nameidata *nd, int dlgt);
+int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname,
+		  int mode, int dlgt);
+int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev,
+		int dlgt);
+int vfsub_link(struct dentry *src_dentry, struct inode *dir,
+	       struct dentry *dentry, int dlgt);
+int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
+		 struct inode *dir, struct dentry *dentry, int dlgt);
+int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode, int dlgt);
+int vfsub_rmdir(struct inode *dir, struct dentry *dentry, int dlgt);
+
+ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
+		     loff_t *ppos, int dlgt);
+ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, loff_t *ppos,
+		     int dlgt);
+ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
+		      loff_t *ppos, int dlgt);
+ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos,
+		      int dlgt);
+int vfsub_readdir(struct file *file, filldir_t filldir, void *arg, int dlgt);
+
+#else
+
+static inline
+int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd,
+		     int dlgt)
+{
+	return do_vfsub_permission(inode, mask, nd);
+}
+
+static inline
+int vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
+		 struct nameidata *nd, int dlgt)
+{
+	return do_vfsub_create(dir, dentry, mode, nd);
+}
+
+static inline
+int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname,
+		  int mode, int dlgt)
+{
+	return do_vfsub_symlink(dir, dentry, symname, mode);
+}
+
+static inline
+int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev,
+		int dlgt)
+{
+	return do_vfsub_mknod(dir, dentry, mode, dev);
+}
+
+static inline
+int vfsub_link(struct dentry *src_dentry, struct inode *dir,
+	       struct dentry *dentry, int dlgt)
+{
+	return do_vfsub_link(src_dentry, dir, dentry);
+}
+
+static inline
+int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
+		 struct inode *dir, struct dentry *dentry, int dlgt)
+{
+	return do_vfsub_rename(src_dir, src_dentry, dir, dentry);
+}
+
+static inline
+int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode,
+		int dlgt)
+{
+	return do_vfsub_mkdir(dir, dentry, mode);
+}
+
+static inline
+int vfsub_rmdir(struct inode *dir, struct dentry *dentry, int dlgt)
+{
+	return do_vfsub_rmdir(dir, dentry);
+}
+
+static inline
+ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
+		     loff_t *ppos, int dlgt)
+{
+	return do_vfsub_read_u(file, ubuf, count, ppos);
+}
+
+static inline
+ssize_t vfsub_read_k(struct file *file, void *kbuf, size_t count, loff_t *ppos,
+		     int dlgt)
+{
+	return do_vfsub_read_k(file, kbuf, count, ppos);
+}
+
+static inline
+ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
+		      loff_t *ppos, int dlgt)
+{
+	return do_vfsub_write_u(file, ubuf, count, ppos);
+}
+
+static inline
+ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count, loff_t *ppos,
+		      int dlgt)
+{
+	return do_vfsub_write_k(file, kbuf, count, ppos);
+}
+
+static inline
+int vfsub_readdir(struct file *file, filldir_t filldir, void *arg, int dlgt)
+{
+	return do_vfsub_readdir(file, filldir, arg);
+}
+#endif /* CONFIG_AUFS_DLGT */
+
+/* ---------------------------------------------------------------------- */
+
+static inline
+struct dentry *vfsub_lock_rename(struct dentry *d1, struct dentry *d2)
+{
+	struct dentry *d;
+
+	lockdep_off();
+	d = lock_rename(d1, d2);
+	lockdep_on();
+	return d;
+}
+
+static inline void vfsub_unlock_rename(struct dentry *d1, struct dentry *d2)
+{
+	lockdep_off();
+	unlock_rename(d1, d2);
+	lockdep_on();
+}
+
+/* ---------------------------------------------------------------------- */
+
+int vfsub_notify_change(struct dentry *dentry, struct iattr *ia, int dlgt);
+int vfsub_unlink(struct inode *dir, struct dentry *dentry, int dlgt);
+int vfsub_statfs(void *arg, struct kstatfs *buf, int dlgt);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_VFSUB_H__ */
diff -ruN linux-2.6.22/fs/aufs/whout.c linux-2.6.22-aufs/fs/aufs/whout.c
--- linux-2.6.22/fs/aufs/whout.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/whout.c	2007-07-18 13:53:00.000000000 +0200
@@ -0,0 +1,976 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: whout.c,v 1.20 2007/07/09 05:47:31 sfjro Exp $ */
+
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/random.h>
+#include <linux/security.h>
+#include "aufs.h"
+
+#define WH_MASK			S_IRUGO
+
+/* If a directory contains this file, then it is opaque.  We start with the
+ * .wh. flag so that it is blocked by lookup.
+ */
+static struct qstr diropq_name = {
+	.name = AUFS_WH_DIROPQ,
+	.len = sizeof(AUFS_WH_DIROPQ) - 1
+};
+
+/*
+ * generate whiteout name, which is NOT terminated by NULL.
+ * @name: original d_name.name
+ * @len: original d_name.len
+ * @wh: whiteout qstr
+ * returns zero when succeeds, otherwise error.
+ * succeeded value as wh->name should be freed by au_free_whname().
+ */
+int au_alloc_whname(const char *name, int len, struct qstr *wh)
+{
+	char *p;
+
+	AuDebugOn(!name || !len || !wh);
+
+	if (unlikely(len > PATH_MAX - AUFS_WH_PFX_LEN))
+		return -ENAMETOOLONG;
+
+	wh->len = len + AUFS_WH_PFX_LEN;
+	wh->name = p = kmalloc(wh->len, GFP_KERNEL);
+	//if (LktrCond) {kfree(p); wh->name = p = NULL;}
+	if (p) {
+		memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
+		memcpy(p + AUFS_WH_PFX_LEN, name, len);
+		//smp_mb();
+		return 0;
+	}
+	return -ENOMEM;
+}
+
+void au_free_whname(struct qstr *wh)
+{
+	AuDebugOn(!wh || !wh->name);
+	kfree(wh->name);
+#ifdef CONFIG_AUFS_DEBUG
+	wh->name = NULL;
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * test if the @wh_name exists under @hidden_parent.
+ * @try_sio specifies the necessary of super-io.
+ */
+int is_wh(struct dentry *hidden_parent, struct qstr *wh_name, int try_sio,
+	  struct lkup_args *lkup)
+{
+	int err;
+	struct dentry *wh_dentry;
+	struct inode *hidden_dir;
+
+	LKTRTrace("%.*s/%.*s, lkup{%p, %d}\n", DLNPair(hidden_parent),
+		  wh_name->len, wh_name->name, lkup->nfsmnt, lkup->dlgt);
+	hidden_dir = hidden_parent->d_inode;
+	AuDebugOn(!S_ISDIR(hidden_dir->i_mode));
+	IMustLock(hidden_dir);
+
+	if (!try_sio)
+		wh_dentry = lkup_one(wh_name->name, hidden_parent,
+				     wh_name->len, lkup);
+	else
+		wh_dentry = sio_lkup_one(wh_name->name, hidden_parent,
+					 wh_name->len, lkup);
+	//if (LktrCond) {dput(wh_dentry); wh_dentry = ERR_PTR(-1);}
+	err = PTR_ERR(wh_dentry);
+	if (IS_ERR(wh_dentry))
+		goto out;
+
+	err = 0;
+	if (!wh_dentry->d_inode)
+		goto out_wh; /* success */
+
+	err = 1;
+	if (S_ISREG(wh_dentry->d_inode->i_mode))
+		goto out_wh; /* success */
+
+	err = -EIO;
+	IOErr("%.*s Invalid whiteout entry type 0%o.\n",
+	      DLNPair(wh_dentry), wh_dentry->d_inode->i_mode);
+
+ out_wh:
+	dput(wh_dentry);
+ out:
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * test if the @hidden_dentry sets opaque or not.
+ */
+int is_diropq(struct dentry *hidden_dentry, struct lkup_args *lkup)
+{
+	int err;
+	struct inode *hidden_dir;
+
+	LKTRTrace("dentry %.*s\n", DLNPair(hidden_dentry));
+	hidden_dir = hidden_dentry->d_inode;
+	AuDebugOn(!S_ISDIR(hidden_dir->i_mode));
+	IMustLock(hidden_dir);
+
+	err = is_wh(hidden_dentry, &diropq_name, /*try_sio*/1, lkup);
+	TraceErr(err);
+	return err;
+}
+
+/*
+ * returns a negative dentry whose name is unique and temporary.
+ */
+struct dentry *lkup_whtmp(struct dentry *hidden_parent, struct qstr *prefix,
+			  struct lkup_args *lkup)
+{
+#define HEX_LEN 4
+	struct dentry *dentry;
+	int len, i;
+	char defname[AUFS_WH_PFX_LEN * 2 + DNAME_INLINE_LEN_MIN + 1
+		     + HEX_LEN + 1], *name, *p;
+	static unsigned char cnt;
+
+	LKTRTrace("hp %.*s, prefix %.*s\n",
+		  DLNPair(hidden_parent), prefix->len, prefix->name);
+	AuDebugOn(!hidden_parent->d_inode);
+	IMustLock(hidden_parent->d_inode);
+
+	name = defname;
+	len = sizeof(defname) - DNAME_INLINE_LEN_MIN + prefix->len - 1;
+	if (unlikely(prefix->len > DNAME_INLINE_LEN_MIN)) {
+		dentry = ERR_PTR(-ENAMETOOLONG);
+		if (unlikely(len >= PATH_MAX))
+			goto out;
+		dentry = ERR_PTR(-ENOMEM);
+		name = kmalloc(len + 1, GFP_KERNEL);
+		//if (LktrCond) {kfree(name); name = NULL;}
+		if (unlikely(!name))
+			goto out;
+	}
+
+	/* doubly whiteout-ed */
+	memcpy(name, AUFS_WH_PFX AUFS_WH_PFX, AUFS_WH_PFX_LEN * 2);
+	p = name + AUFS_WH_PFX_LEN * 2;
+	memcpy(p, prefix->name, prefix->len);
+	p += prefix->len;
+	*p++ = '.';
+	AuDebugOn(name + len + 1 - p <= HEX_LEN);
+
+	for (i = 0; i < 3; i++) {
+		sprintf(p, "%.*d", HEX_LEN, cnt++);
+		dentry = sio_lkup_one(name, hidden_parent, len, lkup);
+		//if (LktrCond) {dput(dentry); dentry = ERR_PTR(-1);}
+		if (IS_ERR(dentry) || !dentry->d_inode)
+			goto out_name;
+		dput(dentry);
+	}
+	/* Warn("could not get random name\n"); */
+	dentry = ERR_PTR(-EEXIST);
+	Dbg("%.*s\n", len, name);
+	BUG();
+
+ out_name:
+	if (unlikely(name != defname))
+		kfree(name);
+ out:
+	TraceErrPtr(dentry);
+	return dentry;
+#undef HEX_LEN
+}
+
+/*
+ * rename the @dentry of @bindex to the whiteouted temporary name.
+ */
+int rename_whtmp(struct dentry *dentry, aufs_bindex_t bindex)
+{
+	int err;
+	struct inode *hidden_dir;
+	struct dentry *hidden_dentry, *hidden_parent, *tmp_dentry;
+	struct super_block *sb;
+	struct lkup_args lkup;
+
+	LKTRTrace("%.*s, b%d\n", DLNPair(dentry), bindex);
+	hidden_dentry = au_h_dptr_i(dentry, bindex);
+	AuDebugOn(!hidden_dentry || !hidden_dentry->d_inode);
+	hidden_parent = hidden_dentry->d_parent;
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+
+	sb = dentry->d_sb;
+	lkup.nfsmnt = au_nfsmnt(sb, bindex);
+	lkup.dlgt = need_dlgt(sb);
+	tmp_dentry = lkup_whtmp(hidden_parent, &hidden_dentry->d_name, &lkup);
+	//if (LktrCond) {dput(tmp_dentry); tmp_dentry = ERR_PTR(-1);}
+	err = PTR_ERR(tmp_dentry);
+	if (!IS_ERR(tmp_dentry)) {
+		/* under the same dir, no need to lock_rename() */
+		err = vfsub_rename(hidden_dir, hidden_dentry,
+				   hidden_dir, tmp_dentry, lkup.dlgt);
+		//if (LktrCond) err = -1; //unavailable
+		TraceErr(err);
+		dput(tmp_dentry);
+	}
+
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int do_unlink_wh(struct inode *h_dir, struct dentry *wh_dentry, int dlgt)
+{
+	LKTRTrace("hi%lu, wh %.*s\n", h_dir->i_ino, DLNPair(wh_dentry));
+	AuDebugOn(!wh_dentry->d_inode
+		  || !S_ISREG(wh_dentry->d_inode->i_mode));
+	IMustLock(h_dir);
+
+	/*
+	 * forces superio when the dir has a sticky bit.
+	 * this may be a violation of unix fs semantics.
+	 */
+	if (unlikely(!dlgt
+		     && (h_dir->i_mode & S_ISVTX)
+		     && wh_dentry->d_inode->i_uid != current->fsuid))
+		dlgt = 1;
+
+	return vfsub_unlink(h_dir, wh_dentry, dlgt);
+}
+
+int au_unlink_wh_dentry(struct inode *hidden_dir, struct dentry *wh_dentry,
+			struct dentry *dentry, int dlgt)
+{
+	int err;
+
+	LKTRTrace("hi%lu, wh %.*s, d %p\n", hidden_dir->i_ino,
+		  DLNPair(wh_dentry), dentry);
+	AuDebugOn((dentry && dbwh(dentry) < 0)
+		  || !wh_dentry->d_inode
+		  || !S_ISREG(wh_dentry->d_inode->i_mode));
+	IMustLock(hidden_dir);
+
+	err = do_unlink_wh(hidden_dir, wh_dentry, dlgt);
+	//if (LktrCond) err = -1; // unavailable
+	if (!err && dentry)
+		set_dbwh(dentry, -1);
+
+	TraceErr(err);
+	return err;
+}
+
+static int unlink_wh_name(struct dentry *hidden_parent, struct qstr *wh,
+			  struct lkup_args *lkup)
+{
+	int err;
+	struct inode *hidden_dir;
+	struct dentry *hidden_dentry;
+
+	LKTRTrace("%.*s/%.*s\n", DLNPair(hidden_parent), LNPair(wh));
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+
+	/* au_test_perm() is already done */
+	hidden_dentry = lkup_one(wh->name, hidden_parent, wh->len, lkup);
+	//if (LktrCond) {dput(hidden_dentry); hidden_dentry = ERR_PTR(-1);}
+	if (!IS_ERR(hidden_dentry)) {
+		err = 0;
+		if (hidden_dentry->d_inode)
+			err = do_unlink_wh(hidden_dir, hidden_dentry,
+					   lkup->dlgt);
+		dput(hidden_dentry);
+	} else
+		err = PTR_ERR(hidden_dentry);
+
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void clean_wh(struct inode *h_dir, struct dentry *wh)
+{
+	TraceEnter();
+	if (wh->d_inode) {
+		int err = vfsub_unlink(h_dir, wh, /*dlgt*/0);
+		if (unlikely(err))
+			Warn("failed unlink %.*s (%d), ignored.\n",
+			     DLNPair(wh), err);
+	}
+}
+
+static void clean_plink(struct inode *h_dir, struct dentry *plink)
+{
+	TraceEnter();
+	if (plink->d_inode) {
+		int err = vfsub_rmdir(h_dir, plink, /*dlgt*/0);
+		if (unlikely(err))
+			Warn("failed rmdir %.*s (%d), ignored.\n",
+			     DLNPair(plink), err);
+	}
+}
+
+static int test_linkable(struct inode *h_dir)
+{
+	if (h_dir->i_op && h_dir->i_op->link)
+		return 0;
+	return -ENOSYS;
+}
+
+static int plink_dir(struct inode *h_dir, struct dentry *plink)
+{
+	int err;
+
+	err = -EEXIST;
+	if (!plink->d_inode) {
+		int mode = S_IRWXU;
+		if (unlikely(au_is_nfs(plink->d_sb)))
+			mode |= S_IXUGO;
+		err = vfsub_mkdir(h_dir, plink, mode, /*dlgt*/0);
+	} else if (S_ISDIR(plink->d_inode->i_mode))
+		err = 0;
+	else
+		Err("unknown %.*s exists\n", DLNPair(plink));
+
+	return err;
+}
+
+/*
+ * initialize the whiteout base file/dir for @br.
+ */
+int init_wh(struct dentry *h_root, struct aufs_branch *br,
+	    struct vfsmount *nfsmnt, struct super_block *sb)
+{
+	int err;
+	struct dentry *wh, *plink;
+	struct inode *h_dir;
+	static struct qstr base_name[] = {
+		{.name = AUFS_WH_BASENAME, .len = sizeof(AUFS_WH_BASENAME) - 1},
+		{.name = AUFS_WH_PLINKDIR, .len = sizeof(AUFS_WH_PLINKDIR) - 1}
+	};
+	struct lkup_args lkup = {
+		.nfsmnt	= nfsmnt,
+		.dlgt	= 0 /* always no dlgt */
+	};
+	const int do_plink = au_flag_test(sb, AuFlag_PLINK);
+
+	LKTRTrace("nfsmnt %p\n", nfsmnt);
+	BrWhMustWriteLock(br);
+	SiMustWriteLock(sb);
+	h_dir = h_root->d_inode;
+	IMustLock(h_dir);
+
+	/* doubly whiteouted */
+	wh = lkup_wh(h_root, base_name + 0, &lkup);
+	//if (LktrCond) {dput(wh); wh = ERR_PTR(-1);}
+	err = PTR_ERR(wh);
+	if (IS_ERR(wh))
+		goto out;
+	AuDebugOn(br->br_wh && br->br_wh != wh);
+
+	plink = lkup_wh(h_root, base_name + 1, &lkup);
+	err = PTR_ERR(plink);
+	if (IS_ERR(plink))
+		goto out_dput_wh;
+	AuDebugOn(br->br_plink && br->br_plink != plink);
+
+	dput(br->br_wh);
+	dput(br->br_plink);
+	br->br_wh = br->br_plink = NULL;
+
+	err = 0;
+	switch (br->br_perm) {
+	case AuBr_RR:
+	case AuBr_RO:
+	case AuBr_RRWH:
+	case AuBr_ROWH:
+		clean_wh(h_dir, wh);
+		clean_plink(h_dir, plink);
+		break;
+
+	case AuBr_RWNoLinkWH:
+		clean_wh(h_dir, wh);
+		if (do_plink) {
+			err = test_linkable(h_dir);
+			if (unlikely(err))
+				goto out_nolink;
+
+			err = plink_dir(h_dir, plink);
+			if (unlikely(err))
+				goto out_err;
+			br->br_plink = dget(plink);
+		} else
+			clean_plink(h_dir, plink);
+		break;
+
+	case AuBr_RW:
+		/*
+		 * for the moment, aufs supports the branch filesystem
+		 * which does not support link(2).
+		 * testing on FAT which does not support i_op->setattr() fully
+		 * either, copyup failed.
+		 * finally, such filesystem will not be used as the writable
+		 * branch.
+		 */
+		err = test_linkable(h_dir);
+		if (unlikely(err))
+			goto out_nolink;
+
+		err = -EEXIST;
+		if (!wh->d_inode)
+			err = vfsub_create(h_dir, wh, WH_MASK, NULL, /*dlgt*/0);
+		else if (S_ISREG(wh->d_inode->i_mode))
+			err = 0;
+		else
+			Err("unknown %.*s/%.*s exists\n",
+			    DLNPair(h_root), DLNPair(wh));
+		if (unlikely(err))
+			goto out_err;
+
+		if (do_plink) {
+			err = plink_dir(h_dir, plink);
+			if (unlikely(err))
+				goto out_err;
+			br->br_plink = dget(plink);
+		} else
+			clean_plink(h_dir, plink);
+		br->br_wh = dget(wh);
+		break;
+
+	default:
+		BUG();
+	}
+
+ out_dput:
+	dput(plink);
+ out_dput_wh:
+	dput(wh);
+ out:
+	TraceErr(err);
+	return err;
+ out_nolink:
+	Err("%.*s doesn't support link(2), use noplink and rw+nolwh\n",
+	    DLNPair(h_root));
+	goto out_dput;
+ out_err:
+	Err("an error(%d) on the writable branch %.*s(%s)\n",
+	    err, DLNPair(h_root), au_sbtype(h_root->d_sb));
+	goto out_dput;
+}
+
+struct reinit_br_wh {
+	struct super_block *sb;
+	struct aufs_branch *br;
+};
+
+static void reinit_br_wh(void *arg)
+{
+	int err;
+	struct reinit_br_wh *a = arg;
+	struct inode *hidden_dir, *dir;
+	struct dentry *hidden_root;
+	aufs_bindex_t bindex;
+
+	TraceEnter();
+	AuDebugOn(!a->br->br_wh || !a->br->br_wh->d_inode || current->fsuid);
+
+	err = 0;
+	/* aufs big lock */
+	si_write_lock(a->sb);
+	if (unlikely(!br_writable(a->br->br_perm)))
+		goto out;
+	bindex = find_brindex(a->sb, a->br->br_id);
+	if (unlikely(bindex < 0))
+		goto out;
+
+	dir = a->sb->s_root->d_inode;
+	hidden_root = dget_parent(a->br->br_wh);
+	hidden_dir = hidden_root->d_inode;
+	AuDebugOn(!hidden_dir->i_op || !hidden_dir->i_op->link);
+	hdir_lock(hidden_dir, dir, bindex);
+	br_wh_write_lock(a->br);
+	err = vfsub_unlink(hidden_dir, a->br->br_wh, /*dlgt*/0);
+	//if (LktrCond) err = -1;
+	dput(a->br->br_wh);
+	a->br->br_wh = NULL;
+	if (!err)
+		err = init_wh(hidden_root, a->br, au_do_nfsmnt(a->br->br_mnt),
+			      a->sb);
+	br_wh_write_unlock(a->br);
+	hdir_unlock(hidden_dir, dir, bindex);
+	dput(hidden_root);
+
+ out:
+	atomic_dec_return(&a->br->br_wh_running);
+	br_put(a->br);
+	si_write_unlock(a->sb);
+	kfree(arg);
+	if (unlikely(err))
+		IOErr("err %d\n", err);
+}
+
+static void kick_reinit_br_wh(struct super_block *sb, struct aufs_branch *br)
+{
+	int do_dec, wkq_err;
+	struct reinit_br_wh *arg;
+
+	do_dec = 1;
+	if (atomic_inc_return(&br->br_wh_running) != 1)
+		goto out;
+
+	/* ignore ENOMEM */
+	arg = kmalloc(sizeof(*arg), GFP_KERNEL);
+	if (arg) {
+		/*
+		 * dec(wh_running), kfree(arg) and br_put()
+		 * in reinit function
+		 */
+		arg->sb = sb;
+		arg->br = br;
+		br_get(br);
+		wkq_err = au_wkq_nowait(reinit_br_wh, arg, sb, /*dlgt*/0);
+		if (unlikely(wkq_err)) {
+			atomic_dec_return(&br->br_wh_running);
+			br_put(br);
+			kfree(arg);
+		}
+		do_dec = 0;
+	}
+
+ out:
+	if (do_dec)
+		atomic_dec_return(&br->br_wh_running);
+}
+
+/*
+ * create the whiteoute @wh.
+ */
+static int link_or_create_wh(struct dentry *wh, struct super_block *sb,
+			     aufs_bindex_t bindex)
+{
+	int err, dlgt;
+	struct aufs_branch *br;
+	struct dentry *hidden_parent;
+	struct inode *hidden_dir;
+
+	LKTRTrace("%.*s\n", DLNPair(wh));
+	SiMustReadLock(sb);
+	hidden_parent = wh->d_parent;
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+
+	dlgt = need_dlgt(sb);
+	br = stobr(sb, bindex);
+	br_wh_read_lock(br);
+	if (br->br_wh) {
+		err = vfsub_link(br->br_wh, hidden_dir, wh, dlgt);
+		if (!err || err != -EMLINK)
+			goto out;
+
+		/* link count full. re-initialize br_wh. */
+		kick_reinit_br_wh(sb, br);
+	}
+
+	/* return this error in this context */
+	err = vfsub_create(hidden_dir, wh, WH_MASK, NULL, dlgt);
+
+ out:
+	br_wh_read_unlock(br);
+	TraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * create or remove the diropq.
+ */
+static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex,
+				struct au_diropq_flags *flags)
+{
+	struct dentry *opq_dentry, *hidden_dentry;
+	struct inode *hidden_dir;
+	int err;
+	struct super_block *sb;
+	struct lkup_args lkup;
+
+	LKTRTrace("%.*s, bindex %d, do_create %d\n", DLNPair(dentry),
+		  bindex, flags->create);
+	hidden_dentry = au_h_dptr_i(dentry, bindex);
+	AuDebugOn(!hidden_dentry);
+	hidden_dir = hidden_dentry->d_inode;
+	AuDebugOn(!hidden_dir || !S_ISDIR(hidden_dir->i_mode));
+	IMustLock(hidden_dir);
+
+	/* already checked by au_test_perm(). */
+	sb = dentry->d_sb;
+	lkup.nfsmnt = au_nfsmnt(sb, bindex);
+	lkup.dlgt = flags->dlgt;
+	opq_dentry = lkup_one(diropq_name.name, hidden_dentry, diropq_name.len,
+			      &lkup);
+	//if (LktrCond) {dput(opq_dentry); opq_dentry = ERR_PTR(-1);}
+	if (IS_ERR(opq_dentry))
+		goto out;
+
+	if (flags->create) {
+		AuDebugOn(opq_dentry->d_inode);
+		err = link_or_create_wh(opq_dentry, sb, bindex);
+		//if (LktrCond) {vfs_unlink(hidden_dir, opq_dentry); err = -1;}
+		if (!err) {
+			set_dbdiropq(dentry, bindex);
+			goto out; /* success */
+		}
+	} else {
+		AuDebugOn(/* !S_ISDIR(dentry->d_inode->i_mode)
+			   * ||  */!opq_dentry->d_inode);
+		err = do_unlink_wh(hidden_dir, opq_dentry, lkup.dlgt);
+		//if (LktrCond) err = -1;
+		if (!err)
+			set_dbdiropq(dentry, -1);
+	}
+	dput(opq_dentry);
+	opq_dentry = ERR_PTR(err);
+
+ out:
+	TraceErrPtr(opq_dentry);
+	return opq_dentry;
+}
+
+struct do_diropq_args {
+	struct dentry **errp;
+	struct dentry *dentry;
+	aufs_bindex_t bindex;
+	struct au_diropq_flags *flags;
+};
+
+static void call_do_diropq(void *args)
+{
+	struct do_diropq_args *a = args;
+	*a->errp = do_diropq(a->dentry, a->bindex, a->flags);
+}
+
+struct dentry *sio_diropq(struct dentry *dentry, aufs_bindex_t bindex,
+			  struct au_diropq_flags *flags)
+{
+	struct dentry *diropq, *hidden_dentry;
+
+	LKTRTrace("%.*s, bindex %d, do_create %d\n",
+		  DLNPair(dentry), bindex, flags->create);
+
+	hidden_dentry = au_h_dptr_i(dentry, bindex);
+	if (!au_test_perm(hidden_dentry->d_inode, MAY_EXEC | MAY_WRITE,
+			  flags->dlgt))
+		diropq = do_diropq(dentry, bindex, flags);
+	else {
+		int wkq_err;
+		struct do_diropq_args args = {
+			.errp		= &diropq,
+			.dentry		= dentry,
+			.bindex		= bindex,
+			.flags		= flags
+		};
+		wkq_err = au_wkq_wait(call_do_diropq, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			diropq = ERR_PTR(wkq_err);
+	}
+
+	TraceErrPtr(diropq);
+	return diropq;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * lookup whiteout dentry.
+ * @hidden_parent: hidden parent dentry which must exist and be locked
+ * @base_name: name of dentry which will be whiteouted
+ * returns dentry for whiteout.
+ */
+struct dentry *lkup_wh(struct dentry *hidden_parent, struct qstr *base_name,
+		       struct lkup_args *lkup)
+{
+	int err;
+	struct qstr wh_name;
+	struct dentry *wh_dentry;
+
+	LKTRTrace("%.*s/%.*s\n", DLNPair(hidden_parent), LNPair(base_name));
+	IMustLock(hidden_parent->d_inode);
+
+	err = au_alloc_whname(base_name->name, base_name->len, &wh_name);
+	//if (LktrCond) {au_free_whname(&wh_name); err = -1;}
+	wh_dentry = ERR_PTR(err);
+	if (!err) {
+		/* do not superio. */
+		wh_dentry = lkup_one(wh_name.name, hidden_parent, wh_name.len,
+				     lkup);
+		au_free_whname(&wh_name);
+	}
+	TraceErrPtr(wh_dentry);
+	return wh_dentry;
+}
+
+/*
+ * link/create a whiteout for @dentry on @bindex.
+ */
+struct dentry *simple_create_wh(struct dentry *dentry, aufs_bindex_t bindex,
+				struct dentry *hidden_parent,
+				struct lkup_args *lkup)
+{
+	struct dentry *wh_dentry;
+	int err;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s/%.*s on b%d\n", DLNPair(hidden_parent),
+		  DLNPair(dentry), bindex);
+
+	sb = dentry->d_sb;
+	wh_dentry = lkup_wh(hidden_parent, &dentry->d_name, lkup);
+	//au_nfsmnt(sb, bindex), need_dlgt(sb));
+	//if (LktrCond) {dput(wh_dentry); wh_dentry = ERR_PTR(-1);}
+	if (!IS_ERR(wh_dentry) && !wh_dentry->d_inode) {
+		IMustLock(hidden_parent->d_inode);
+		err = link_or_create_wh(wh_dentry, sb, bindex);
+		if (!err)
+			set_dbwh(dentry, bindex);
+		else {
+			dput(wh_dentry);
+			wh_dentry = ERR_PTR(err);
+		}
+	}
+
+	TraceErrPtr(wh_dentry);
+	return wh_dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* Delete all whiteouts in this directory in branch bindex. */
+static int del_wh_children(struct aufs_nhash *whlist,
+			   struct dentry *hidden_parent, aufs_bindex_t bindex,
+			   struct lkup_args *lkup)
+{
+	int err, i;
+	struct qstr wh_name;
+	char *p;
+	struct inode *hidden_dir;
+	struct hlist_head *head;
+	struct aufs_wh *tpos;
+	struct hlist_node *pos;
+	struct aufs_destr *str;
+
+	LKTRTrace("%.*s\n", DLNPair(hidden_parent));
+	hidden_dir = hidden_parent->d_inode;
+	IMustLock(hidden_dir);
+	AuDebugOn(IS_RDONLY(hidden_dir));
+	//SiMustReadLock(??);
+
+	err = -ENOMEM;
+	wh_name.name = p = __getname();
+	//if (LktrCond) {__putname(p); wh_name.name = p = NULL;}
+	if (unlikely(!wh_name.name))
+		goto out;
+	memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
+	p += AUFS_WH_PFX_LEN;
+
+	/* already checked by au_test_perm(). */
+	err = 0;
+	for (i = 0; !err && i < AuSize_NHASH; i++) {
+		head = whlist->heads + i;
+		hlist_for_each_entry(tpos, pos, head, wh_hash) {
+			if (tpos->wh_bindex != bindex)
+				continue;
+			str = &tpos->wh_str;
+			if (str->len + AUFS_WH_PFX_LEN <= PATH_MAX) {
+				memcpy(p, str->name, str->len);
+				wh_name.len = AUFS_WH_PFX_LEN + str->len;
+				err = unlink_wh_name(hidden_parent, &wh_name,
+						     lkup);
+				//if (LktrCond) err = -1;
+				if (!err)
+					continue;
+				break;
+			}
+			IOErr("whiteout name too long %.*s\n",
+			      str->len, str->name);
+			err = -EIO;
+			break;
+		}
+	}
+	__putname(wh_name.name);
+
+ out:
+	TraceErr(err);
+	return err;
+}
+
+struct del_wh_children_args {
+	int *errp;
+	struct aufs_nhash *whlist;
+	struct dentry *hidden_parent;
+	aufs_bindex_t bindex;
+	struct lkup_args *lkup;
+};
+
+static void call_del_wh_children(void *args)
+{
+	struct del_wh_children_args *a = args;
+	*a->errp = del_wh_children(a->whlist, a->hidden_parent, a->bindex,
+				   a->lkup);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * rmdir the whiteouted temporary named dir @hidden_dentry.
+ * @whlist: whiteouted children.
+ */
+int rmdir_whtmp(struct dentry *hidden_dentry, struct aufs_nhash *whlist,
+		aufs_bindex_t bindex, struct inode *dir, struct inode *inode)
+{
+	int err;
+	struct inode *hidden_inode, *hidden_dir;
+	struct lkup_args lkup;
+	struct super_block *sb;
+
+	LKTRTrace("hd %.*s, b%d, i%lu\n",
+		  DLNPair(hidden_dentry), bindex, dir->i_ino);
+	IMustLock(dir);
+	IiMustAnyLock(dir);
+	hidden_dir = hidden_dentry->d_parent->d_inode;
+	IMustLock(hidden_dir);
+
+	sb = inode->i_sb;
+	lkup.nfsmnt = au_nfsmnt(sb, bindex);
+	lkup.dlgt = need_dlgt(sb);
+	hidden_inode = hidden_dentry->d_inode;
+	AuDebugOn(hidden_inode != au_h_iptr_i(inode, bindex));
+	hdir2_lock(hidden_inode, inode, bindex);
+	if (!au_test_perm(hidden_inode, MAY_EXEC | MAY_WRITE, lkup.dlgt))
+		err = del_wh_children(whlist, hidden_dentry, bindex, &lkup);
+	else {
+		int wkq_err;
+		/* ugly */
+		int dlgt = lkup.dlgt;
+		struct del_wh_children_args args = {
+			.errp		= &err,
+			.whlist		= whlist,
+			.hidden_parent	= hidden_dentry,
+			.bindex		= bindex,
+			.lkup		= &lkup
+		};
+
+		lkup.dlgt = 0;
+		wkq_err = au_wkq_wait(call_del_wh_children, &args, /*dlgt*/0);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		lkup.dlgt = dlgt;
+	}
+	hdir_unlock(hidden_inode, inode, bindex);
+
+	if (!err) {
+		err = vfsub_rmdir(hidden_dir, hidden_dentry, lkup.dlgt);
+		//d_drop(hidden_dentry);
+		//if (LktrCond) err = -1;
+	}
+
+	if (!err) {
+		if (ibstart(dir) == bindex) {
+			au_cpup_attr_timesizes(dir);
+			//au_cpup_attr_nlink(dir);
+			dir->i_nlink--;
+		}
+		return 0; /* success */
+	}
+
+	Warn("failed removing %.*s(%d), ignored\n",
+	     DLNPair(hidden_dentry), err);
+	return err;
+}
+
+static void rmdir_whtmp_free_args(struct rmdir_whtmp_args *args)
+{
+	dput(args->h_dentry);
+	nhash_fin(&args->whlist);
+	iput(args->inode);
+	vfsub_i_unlock(args->dir);
+	iput(args->dir);
+	kfree(args);
+}
+
+static void do_rmdir_whtmp(void *args)
+{
+	int err;
+	struct rmdir_whtmp_args *a = args;
+	struct super_block *sb;
+
+	LKTRTrace("%.*s, b%d, dir i%lu\n",
+		  DLNPair(a->h_dentry), a->bindex, a->dir->i_ino);
+
+	vfsub_i_lock(a->dir);
+	sb = a->dir->i_sb;
+	//DbgSleep(3);
+	si_read_lock(sb, !AuLock_FLUSH);
+	err = test_ro(sb, a->bindex, NULL);
+	if (!err) {
+		struct dentry *h_parent = dget_parent(a->h_dentry);
+		struct inode *hidden_dir = h_parent->d_inode;
+
+		ii_write_lock_child(a->inode);
+		ii_write_lock_parent(a->dir);
+		hdir_lock(hidden_dir, a->dir, a->bindex);
+		err = rmdir_whtmp(a->h_dentry, &a->whlist, a->bindex,
+				  a->dir, a->inode);
+		hdir_unlock(hidden_dir, a->dir, a->bindex);
+		ii_write_unlock(a->dir);
+		ii_write_unlock(a->inode);
+		dput(h_parent);
+	}
+	si_read_unlock(sb);
+	rmdir_whtmp_free_args(a);
+	if (unlikely(err))
+		IOErr("err %d\n", err);
+}
+
+void kick_rmdir_whtmp(struct dentry *h_dentry, struct aufs_nhash *whlist,
+		      aufs_bindex_t bindex, struct inode *dir,
+		      struct inode *inode, struct rmdir_whtmp_args *args)
+{
+	int wkq_err;
+
+	LKTRTrace("%.*s\n", DLNPair(h_dentry));
+	IMustLock(dir);
+
+	/* all post-process will be done in do_rmdir_whtmp(). */
+	args->h_dentry = dget(h_dentry);
+	nhash_init(&args->whlist);
+	nhash_move(&args->whlist, whlist);
+	args->bindex = bindex;
+	args->dir = igrab(dir);
+	args->inode = igrab(inode);
+	wkq_err = au_wkq_nowait(do_rmdir_whtmp, args, dir->i_sb, /*dlgt*/0);
+	if (unlikely(wkq_err)) {
+		Warn("rmdir error %.*s (%d), ignored\n",
+		     DLNPair(h_dentry), wkq_err);
+		rmdir_whtmp_free_args(args);
+	}
+}
diff -ruN linux-2.6.22/fs/aufs/whout.h linux-2.6.22-aufs/fs/aufs/whout.h
--- linux-2.6.22/fs/aufs/whout.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/whout.h	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: whout.h,v 1.10 2007/07/09 05:47:39 sfjro Exp $ */
+
+#ifndef __AUFS_WHOUT_H__
+#define __AUFS_WHOUT_H__
+
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
+#include <linux/aufs_type.h>
+
+int au_alloc_whname(const char *name, int len, struct qstr *wh);
+void au_free_whname(struct qstr *wh);
+
+struct lkup_args;
+int is_wh(struct dentry *h_parent, struct qstr *wh_name, int try_sio,
+	  struct lkup_args *lkup);
+int is_diropq(struct dentry *h_dentry, struct lkup_args *lkup);
+
+struct dentry *lkup_whtmp(struct dentry *h_parent, struct qstr *prefix,
+			  struct lkup_args *lkup);
+int rename_whtmp(struct dentry *dentry, aufs_bindex_t bindex);
+int au_unlink_wh_dentry(struct inode *h_dir, struct dentry *wh_dentry,
+			struct dentry *dentry, int dlgt);
+
+struct aufs_branch;
+int init_wh(struct dentry *h_parent, struct aufs_branch *br,
+	    struct vfsmount *nfsmnt, struct super_block *sb);
+
+struct au_diropq_flags {
+	unsigned int create:1;
+	unsigned int dlgt:1;
+};
+struct dentry *sio_diropq(struct dentry *dentry, aufs_bindex_t bindex,
+			  struct au_diropq_flags *flags);
+
+struct dentry *lkup_wh(struct dentry *h_parent, struct qstr *base_name,
+		       struct lkup_args *lkup);
+struct dentry *simple_create_wh(struct dentry *dentry, aufs_bindex_t bindex,
+				struct dentry *h_parent,
+				struct lkup_args *lkup);
+
+/* real rmdir the whiteout-ed dir */
+struct rmdir_whtmp_args {
+	struct dentry *h_dentry;
+	struct aufs_nhash whlist;
+	aufs_bindex_t bindex;
+	struct inode *dir, *inode;
+};
+
+struct aufs_nhash;
+int rmdir_whtmp(struct dentry *h_dentry, struct aufs_nhash *whlist,
+		aufs_bindex_t bindex, struct inode *dir, struct inode *inode);
+void kick_rmdir_whtmp(struct dentry *h_dentry, struct aufs_nhash *whlist,
+		      aufs_bindex_t bindex, struct inode *dir,
+		      struct inode *inode, struct rmdir_whtmp_args *args);
+
+/* ---------------------------------------------------------------------- */
+
+static inline
+struct dentry *create_diropq(struct dentry *dentry, aufs_bindex_t bindex,
+			     int dlgt)
+{
+	struct au_diropq_flags flags = {
+		.create	= 1,
+		.dlgt	= !!dlgt
+	};
+	return sio_diropq(dentry, bindex, &flags);
+}
+
+static inline
+int remove_diropq(struct dentry *dentry, aufs_bindex_t bindex, int dlgt)
+{
+	struct au_diropq_flags flags = {
+		.create	= 0,
+		.dlgt	= !!dlgt
+	};
+	return PTR_ERR(sio_diropq(dentry, bindex, &flags));
+}
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_WHOUT_H__ */
diff -ruN linux-2.6.22/fs/aufs/wkq.c linux-2.6.22-aufs/fs/aufs/wkq.c
--- linux-2.6.22/fs/aufs/wkq.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.22-aufs/fs/aufs/wkq.c	2007-07-18 13:53:01.000000000 +0200
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2005, 2006, 2007 Junjiro 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
+ */
+
+/* $Id: wkq.c,v 1.20 2007/07/09 05:47:47 sfjro Exp $ */
+
+#include <linux/module.h>
+#include "aufs.h"
+
+struct au_wkq *au_wkq;
+
+struct au_cred {
+#ifdef CONFIG_AUFS_DLGT
+	uid_t fsuid;
+	gid_t fsgid;
+	kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
+	//unsigned keep_capabilities:1;
+	//struct user_struct *user;
+	//struct fs_struct *fs;
+	//struct nsproxy *nsproxy;
+#endif
+};
+
+struct au_wkinfo {
+	struct work_struct wk;
+	struct super_block *sb;
+
+	unsigned int wait:1;
+	unsigned int dlgt:1;
+	struct au_cred cred;
+
+	au_wkq_func_t func;
+	void *args;
+
+	atomic_t *busyp;
+	struct completion *comp;
+};
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_DLGT
+static void cred_store(struct au_cred *cred)
+{
+	cred->fsuid = current->fsuid;
+	cred->fsgid = current->fsgid;
+	cred->cap_effective = current->cap_effective;
+	cred->cap_inheritable = current->cap_inheritable;
+	cred->cap_permitted = current->cap_permitted;
+}
+
+static void cred_revert(struct au_cred *cred)
+{
+	AuDebugOn(!is_au_wkq(current));
+	current->fsuid = cred->fsuid;
+	current->fsgid = cred->fsgid;
+	current->cap_effective = cred->cap_effective;
+	current->cap_inheritable = cred->cap_inheritable;
+	current->cap_permitted = cred->cap_permitted;
+}
+
+static void cred_switch(struct au_cred *old, struct au_cred *new)
+{
+	cred_store(old);
+	cred_revert(new);
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static void update_busy(struct au_wkq *wkq, struct au_wkinfo *wkinfo)
+{
+#ifdef CONFIG_AUFS_SYSAUFS
+	unsigned int new, old;
+
+	do {
+		new = atomic_read(wkinfo->busyp);
+		old = wkq->max_busy;
+		if (new <= old)
+			break;
+	} while (cmpxchg(&wkq->max_busy, old, new) == old);
+#endif
+}
+
+static int enqueue(struct au_wkq *wkq, struct au_wkinfo *wkinfo)
+{
+	TraceEnter();
+	wkinfo->busyp = &wkq->busy;
+	update_busy(wkq, wkinfo);
+	if (wkinfo->wait)
+		return !queue_work(wkq->q, &wkinfo->wk);
+	else
+		return !schedule_work(&wkinfo->wk);
+}
+
+static void do_wkq(struct au_wkinfo *wkinfo)
+{
+	unsigned int idle, n;
+	int i, idle_idx;
+
+	TraceEnter();
+
+	while (1) {
+		if (wkinfo->wait) {
+			idle_idx = 0;
+			idle = UINT_MAX;
+			for (i = 0; i < aufs_nwkq; i++) {
+				n = atomic_inc_return(&au_wkq[i].busy);
+				if (n == 1 && !enqueue(au_wkq + i, wkinfo))
+					return; /* success */
+
+				if (n < idle) {
+					idle_idx = i;
+					idle = n;
+				}
+				atomic_dec_return(&au_wkq[i].busy);
+			}
+		} else
+			idle_idx = aufs_nwkq;
+
+		atomic_inc_return(&au_wkq[idle_idx].busy);
+		if (!enqueue(au_wkq + idle_idx, wkinfo))
+			return; /* success */
+
+		/* impossible? */
+		Warn1("failed to queue_work()\n");
+		yield();
+	}
+}
+
+static AuWkqFunc(wkq_func, wk)
+{
+	struct au_wkinfo *wkinfo = container_of(wk, struct au_wkinfo, wk);
+	struct aufs_sbinfo *sbinfo;
+
+	LKTRTrace("wkinfo{%u, %u, %p, %p, %p}\n",
+		  wkinfo->wait, wkinfo->dlgt, wkinfo->func, wkinfo->busyp,
+		  wkinfo->comp);
+#ifdef CONFIG_AUFS_DLGT
+	if (!wkinfo->dlgt)
+		wkinfo->func(wkinfo->args);
+	else {
+		struct au_cred cred;
+		cred_switch(&cred, &wkinfo->cred);
+		wkinfo->func(wkinfo->args);
+		cred_revert(&cred);
+	}
+#else
+	wkinfo->func(wkinfo->args);
+#endif
+	atomic_dec_return(wkinfo->busyp);
+	if (wkinfo->wait)
+		complete(wkinfo->comp);
+	else {
+		sbinfo = stosi(wkinfo->sb);
+#if 0
+		if (atomic_dec_and_test(&sbinfo->si_wkq_nowait))
+			wake_up_all(&sbinfo->si_wkq_nowait_wq);
+#endif
+		au_mntput(wkinfo->sb);
+		module_put(THIS_MODULE);
+		kfree(wkinfo);
+	}
+}
+
+int au_wkq_run(au_wkq_func_t func, void *args, struct super_block *sb,
+	       struct au_wkq_flags *flags)
+{
+	int err;
+	//int queued;
+	//struct workqueue_struct *wkq;
+	struct aufs_sbinfo *sbinfo;
+	DECLARE_COMPLETION_ONSTACK(comp);
+	struct au_wkinfo _wkinfo = {
+		//.sb	= sb,
+		.wait	= 1,
+		.dlgt	= flags->dlgt,
+		.func	= func,
+		.args	= args,
+		.comp	= &comp
+	}, *wkinfo = &_wkinfo;
+
+	LKTRTrace("dlgt %d, do_wait %d\n", flags->dlgt, flags->wait);
+	AuDebu