[PATCH] UBUNTU: add proper aufs source tree from 20080922

Michael Haas laga+ml at laga.ath.cx
Mon Sep 22 22:34:56 UTC 2008


From: root <root at moar.(none)>

Signed-off-by: Michael Haas <laga at laga.ath.cx>
---
 include/linux/aufs_type.h |  114 +++++
 ubuntu/aufs/BOM           |    4 +-
 ubuntu/aufs/Kconfig       |  232 +++++++++-
 ubuntu/aufs/Makefile      |   78 +++-
 ubuntu/aufs/aufs.h        |    7 +-
 ubuntu/aufs/br_fuse.c     |   80 ++++
 ubuntu/aufs/br_nfs.c      |  347 ++++++++++++++
 ubuntu/aufs/br_xfs.c      |   69 +++
 ubuntu/aufs/branch.c      |  397 ++++++++++------
 ubuntu/aufs/branch.h      |  124 +++---
 ubuntu/aufs/cpup.c        |  404 ++++++++++-------
 ubuntu/aufs/cpup.h        |   30 +-
 ubuntu/aufs/dcsub.c       |   27 +-
 ubuntu/aufs/dcsub.h       |    6 +-
 ubuntu/aufs/debug.c       |  105 +++--
 ubuntu/aufs/debug.h       |   35 ++-
 ubuntu/aufs/dentry.c      |  180 +++++---
 ubuntu/aufs/dentry.h      |   19 +-
 ubuntu/aufs/dinfo.c       |   21 +-
 ubuntu/aufs/dir.c         |   50 +--
 ubuntu/aufs/dir.h         |    2 +-
 ubuntu/aufs/dlgt.c        |  113 +++++
 ubuntu/aufs/export.c      |  797 +++++++++++++++++++++++++++++++
 ubuntu/aufs/f_op.c        |  148 ++++---
 ubuntu/aufs/file.c        |  237 +++++-----
 ubuntu/aufs/file.h        |   11 +-
 ubuntu/aufs/finfo.c       |    4 +-
 ubuntu/aufs/hin_or_dlgt.c |  737 +++++++++++++++++++++++++++++
 ubuntu/aufs/hin_or_fuse.c |  120 +++++
 ubuntu/aufs/hinode.h      |  111 ++---
 ubuntu/aufs/hinotify.c    | 1133 +++++++++++++++++++++++++++++++++++++++++++++
 ubuntu/aufs/i_op.c        |  675 +++++++++++++++++++--------
 ubuntu/aufs/i_op_add.c    |  443 +++++++++---------
 ubuntu/aufs/i_op_del.c    |  155 ++++---
 ubuntu/aufs/i_op_ren.c    | 1131 +++++++++++++++++++++++++++++++--------------
 ubuntu/aufs/iinfo.c       |   14 +-
 ubuntu/aufs/inode.c       |   42 ++-
 ubuntu/aufs/inode.h       |  242 +++++++++-
 ubuntu/aufs/misc.c        |  100 +++--
 ubuntu/aufs/misc.h        |   25 +-
 ubuntu/aufs/module.c      |   13 +-
 ubuntu/aufs/module.h      |   11 +-
 ubuntu/aufs/opts.c        |  231 ++++++----
 ubuntu/aufs/opts.h        |   18 +-
 ubuntu/aufs/plink.c       |   28 +-
 ubuntu/aufs/robr.c        |  111 +++++
 ubuntu/aufs/sbinfo.c      |    7 +-
 ubuntu/aufs/super.c       |   90 +++--
 ubuntu/aufs/super.h       |  108 ++++--
 ubuntu/aufs/sysaufs.c     |   35 +-
 ubuntu/aufs/sysaufs.h     |   25 +-
 ubuntu/aufs/sysfs.c       |  107 +++--
 ubuntu/aufs/sysrq.c       |  106 +++++
 ubuntu/aufs/vdir.c        |   78 ++--
 ubuntu/aufs/vfsub.c       |  133 +++---
 ubuntu/aufs/vfsub.h       |  138 +++---
 ubuntu/aufs/wbr_policy.c  |   74 ++--
 ubuntu/aufs/whout.c       |  508 ++++++++++++---------
 ubuntu/aufs/whout.h       |   37 +-
 ubuntu/aufs/wkq.c         |   94 ++++-
 ubuntu/aufs/wkq.h         |   64 +--
 ubuntu/aufs/xino.c        |  195 +++++---
 62 files changed, 8232 insertions(+), 2548 deletions(-)
 create mode 100644 include/linux/aufs_type.h
 create mode 100644 ubuntu/aufs/br_fuse.c
 create mode 100644 ubuntu/aufs/br_nfs.c
 create mode 100644 ubuntu/aufs/br_xfs.c
 create mode 100644 ubuntu/aufs/dlgt.c
 create mode 100644 ubuntu/aufs/export.c
 create mode 100644 ubuntu/aufs/hin_or_dlgt.c
 create mode 100644 ubuntu/aufs/hin_or_fuse.c
 create mode 100644 ubuntu/aufs/hinotify.c
 create mode 100644 ubuntu/aufs/robr.c
 create mode 100644 ubuntu/aufs/sysrq.c

diff --git a/include/linux/aufs_type.h b/include/linux/aufs_type.h
new file mode 100644
index 0000000..9bd59d0
--- /dev/null
+++ b/include/linux/aufs_type.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2005-2008 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_type.h,v 1.124 2008/09/22 03:52:19 sfjro Exp $ */
+
+#include <linux/ioctl.h>
+
+#ifndef __AUFS_TYPE_H__
+#define __AUFS_TYPE_H__
+
+#define AUFS_VERSION	"20080922"
+
+/* move this to linux-2.6.19/include/magic.h */
+#define AUFS_SUPER_MAGIC	('a' << 24 | 'u' << 16 | 'f' << 8 | 's')
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_BRANCH_MAX_127
+/* some environments treat 'char' as 'unsigned char' by default */
+typedef signed char aufs_bindex_t;
+#define AUFS_BRANCH_MAX 127
+#else
+typedef short aufs_bindex_t;
+#ifdef CONFIG_AUFS_BRANCH_MAX_511
+#define AUFS_BRANCH_MAX 511
+#elif defined(CONFIG_AUFS_BRANCH_MAX_1023)
+#define AUFS_BRANCH_MAX 1023
+#elif defined(CONFIG_AUFS_BRANCH_MAX_32767)
+#define AUFS_BRANCH_MAX 32767
+#else
+#error unknown CONFIG_AUFS_BRANCH_MAX value
+#endif
+#endif
+
+#define AUFS_NAME		"aufs"
+#define AUFS_FSTYPE		AUFS_NAME
+
+#define AUFS_ROOT_INO		2
+#define AUFS_FIRST_INO		11
+
+#define AUFS_WH_PFX		".wh."
+#define AUFS_WH_PFX_LEN		((int)sizeof(AUFS_WH_PFX) - 1)
+#define AUFS_XINO_FNAME		"." AUFS_NAME ".xino"
+#define AUFS_XINO_DEFPATH	"/tmp/" AUFS_XINO_FNAME
+#define AUFS_XINO_TRUNC_INIT	64 /* blocks */
+#define AUFS_XINO_TRUNC_STEP	4  /* blocks */
+#define AUFS_DIRWH_DEF		3
+#define AUFS_RDCACHE_DEF	10 /* seconds */
+#define AUFS_WKQ_NAME		AUFS_NAME "d"
+#define AUFS_NWKQ_DEF		4
+#define AUFS_MFS_SECOND_DEF	30 /* seconds */
+#define AUFS_PLINK_WARN		100 /* number of plinks */
+
+#ifdef CONFIG_AUFS_COMPAT
+#define AUFS_DIROPQ_NAME	"__dir_opaque"
+#else
+#define AUFS_DIROPQ_NAME	AUFS_WH_PFX ".opq" /* whiteouted doubly */
+#endif
+#define AUFS_WH_DIROPQ		AUFS_WH_PFX AUFS_DIROPQ_NAME
+
+/* will be whiteouted doubly */
+#define AUFS_WH_BASENAME	AUFS_WH_PFX AUFS_NAME
+#define AUFS_WH_PLINKDIR	AUFS_WH_PFX "plnk"
+#define AUFS_WH_TMPDIR		AUFS_WH_PFX ".tmp"
+
+/* ---------------------------------------------------------------------- */
+
+/* ioctl */
+#if 0 /* reserved for future use */
+enum {
+	AuCtlErr,
+	AuCtlErr_Last
+};
+enum {
+	AuCtl_REFRESH, AuCtl_REFRESHV,
+	AuCtl_FLUSH_PLINK,
+	AuCtl_CPUP,
+	AuCtl_CPDOWN, AuCtl_MVDOWN,
+	AuCtl_DIROPQ
+};
+
+struct aufs_ctl_cp {
+	int bsrc, bdst;
+	int err;
+};
+
+#define AuCtlType		'A'
+#define AUFS_CTL_REFRESH	_IO(AuCtlType, AuCtl_REFRESH)
+#define AUFS_CTL_REFRESHV	_IO(AuCtlType, AuCtl_REFRESHV)
+#define AUFS_CTL_FLUSH_PLINK	_IOR(AuCtlType, AuCtl_FLUSH_PLINK)
+#define AUFS_CTL_CPUP		_IOWR(AuCtlType, AuCtl_CPUP, struct aufs_ctl_cp)
+#define AUFS_CTL_CPDOWN \
+	_IOWR(AuCtlType, AuCtl_CPDOWN, struct aufs_ctl_cp)
+#define AUFS_CTL_MVDOWN \
+	_IOWR(AuCtlType, AuCtl_MVDOWN, struct aufs_ctl_cp)
+#define AUFS_CTL_DIROPQ		_IO(AuCtlType, AuCtl_DIROPQ)
+#endif
+
+#endif /* __AUFS_TYPE_H__ */
diff --git a/ubuntu/aufs/BOM b/ubuntu/aufs/BOM
index 39338a7..efe0677 100644
--- a/ubuntu/aufs/BOM
+++ b/ubuntu/aufs/BOM
@@ -1,3 +1,3 @@
 Downloaded from:	pserver:anonymous at aufs.cvs.sourceforge.net:/cvsroot/aufs
-Current Version:	Mon, 09 Jun 2008 09:49:31 -0400
-Comments:		This is horrible to integrate, but better than unionfs
+Current Version:	Mon, 22 Sep 2008
+Comments:		Unmodified upstream checkout
diff --git a/ubuntu/aufs/Kconfig b/ubuntu/aufs/Kconfig
index 2152ced..7d538d4 100644
--- a/ubuntu/aufs/Kconfig
+++ b/ubuntu/aufs/Kconfig
@@ -1,5 +1,5 @@
 config AUFS
-	bool "Another unionfs"
+	tristate "Another unionfs"
 	help
 	Aufs is a stackable unification filesystem such as Unionfs,
 	which unifies several directories and provides a merged single
@@ -9,11 +9,11 @@ config AUFS
 	ideas, approaches and improvements, it becomes totally
 	different from Unionfs while keeping the basic features.
 	See Unionfs for the basic features.
-
+if AUFS
+comment "These options are for 2.6.27-3-generic"
 choice
 	prompt "Maximum number of branches"
 	default AUFS_BRANCH_MAX_127
-	depends on AUFS
 	help
 	Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance.
 config AUFS_BRANCH_MAX_127
@@ -33,19 +33,233 @@ config AUFS_BRANCH_MAX_32767
 	help
 	Specifies the maximum number of branches (or member directories) in a single aufs. The larger value consumes more system resources and has an impact to performance.
 endchoice
-
 config AUFS_STAT
 	bool "Use <sysfs>/fs/aufs/stat"
-	depends on SYSFS && AUFS
-	default n
+	depends on SYSFS
 	help
 	Shows some statistic data via <sysfs>/fs/aufs/stat.
 	See detail in aufs.5.
-
+comment "SYSFS and AUFS_STAT are disabled"
+	depends on SYSFS = n
+config AUFS_HINOTIFY
+	bool "Use inotify to detect actions on a branch"
+	depends on INOTIFY
+	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 have a negative impact to the performance.
+	See detail in aufs.5.
+comment "INOTIFY and AUFS_HINOTIFY are disabled"
+	depends on INOTIFY = n
+config AUFS_EXPORT
+	bool "NFS-exportable aufs"
+	depends on (AUFS = y && EXPORTFS = y) || (AUFS = m && EXPORTFS)
+	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.
+comment "EXPORTFS and AUFS_EXPORT are disabled"
+	depends on EXPORTFS = n
+comment "AUFS_EXPORT is disabled since EXPORTFS is a module but AUFS"
+	depends on EXPORTFS = m && AUFS = y
+config AUFS_INO_T_64
+	bool
+	depends on 64BIT && !(ALPHA || S390)
+	default y
+config AUFS_ROBR
+	bool "Aufs as an readonly branch of another aufs mount"
+	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 have a negative impact to the performance.
+	See detail in aufs.5.
+config AUFS_DLGT
+	bool "Delegate the internal branch access the kernel thread"
+	help
+	If you want aufs to delegate
+	the internal access to the branches which is made by aufs, to
+	the kernel thread, in order to hide the access issued by your
+	application from your LSM or something or make your
+	application to be traced strictly by the task I/O accounting,
+	then enable this option and use 'dlgt' mount option.
+	It will have a negative impact to the performance.
+	See detail in aufs.5.
+config AUFS_HIN_OR_DLGT
+	bool
+	depends on AUFS_HINOTIFY || AUFS_DLGT
+	default y
+	help
+	Automatic configuration for internal use.
+config AUFS_SHWH
+	bool "Show whiteouts"
+	help
+	If you want to make the whiteouts in aufs visible, then enable
+	this option and specify 'shwh' mount option. Although it may
+	sounds like philosophy or something, but in technically it
+	simply shows the name of whiteout with keeping its behaviour.
+config AUFS_RR_SQUASHFS
+	bool "Make squashfs branch RR (real readonly) by default"
+	default y
+	help
+	If you use squashfs or LZMA patched squashfs as an aufs branch
+	and want to set '=rr' to it by default, then enable this
+	configuration.
+	'rr' stands for real readonly and it optimizes some aspects of
+	'ro.'
+	See detail in aufs.5.
+config AUFS_SEC_PERM_PATCH
+	bool "sec_perm-2.6.24.patch was applied or not"
+	depends on AUFS = m
+	depends on SECURITY
+	help
+	If you build aufs as a module and enabled CONFIG_SECURITY,
+	then you need to apply the patch
+	'CVS_TREE/aufs/patch/sec_perm-2.6.24.patch' to your kernel
+	source, and enable this configuration.
+	The sec_perm-2.6.24.patch exports a kernel function
+	security_inode_permission() to modules.
+comment "SECURITY and AUFS_SEC_PERM_PATCH are disabled"
+	depends on SECURITY = n
+config AUFS_SPLICE_PATCH
+	bool "splice.patch for sendfile(2) and splice(2)"
+	help
+	If you use 'loopback mount' on a fs-image file, or use
+	splice(2) or sendfile(2) systemcall in aufs, then you need to
+	apply the patch 'CVS_TREE/aufs/patch/splice.patch' to your
+	kernel source, and enable this configuration.
+	The splice.patch makes the kernel function do_splice_to/from()
+	global and exports them to modules.
+config AUFS_PUT_FILP_PATCH
+	bool "put_filp.patch for NFS branch"
+	depends on AUFS = m
+	depends on NFS_FS
+	help
+	If you build aufs as a module and use mounted NFS as an aufs
+	branch filesystem, then you need to apply the patch
+	'CVS_TREE/aufs/patch/put_filp.patch' to your kernel source,
+	and enable this configuration.
+	The put_filp.patch exports a kernel function put_filp() to
+	modules.
+comment "NFS_FS and AUFS_PUT_FILP_PATCH are disabled"
+	depends on NFS_FS = n
+config AUFS_LHASH_PATCH
+	bool "lhash.patch for NFS branch"
+	depends on NFS_FS
+	help
+	If you use mounted NFS as an aufs branch filesystem, then you
+	need to apply the patch 'CVS_TREE/aufs/patch/lhash.patch' (or
+	lhash-2.6.22.patch for linux-2.6.22 and later) to your kernel
+	source, and enable this configuration.
+	The patch file makes the kernel function __lookup_hash() global
+	and exports it to modules.
+comment "NFS_FS and AUFS_LHASH_PATCH are disabled"
+	depends on NFS_FS = n
+config AUFS_BR_NFS
+	bool
+	depends on NFS_FS
+	default n if (!AUFS_LHASH_PATCH || !(AUFS = y || AUFS_PUT_FILP_PATCH))
+	default y
+	help
+	Automatic configuration for internal use.
+	When aufs supports NFS branch, enabled automatically.
+config AUFS_BR_XFS
+	bool
+	depends on XFS_FS
+	default y
+	help
+	Automatic configuration for internal use.
+	When aufs supports XFS branch, enabled automatically.
+config AUFS_FSYNC_SUPER_PATCH
+	bool "fsync_super-2.6.xx.patch was applied or not"
+	depends on AUFS = m
+	help
+	If you build aufs as a module and want to flush everything for
+	branch filesystems which are not marked as 'rr' nor 'rr+wh' at
+	umount or remount time, then you need to apply the patch
+	'CVS_TREE/aufs/patch/fsync_super-2.6.16.patch' or
+	'...-2.6.19.patch' to your kernel source, and enable this
+	configuration.
+	It may be helpful at shutdown time in case of your aufs is a
+	root filesystem. But this behaviour will not guarantee the
+	consistency of branch filesystems. To guarantee it, try the
+	approach described in the aufs manual, and do not forget
+	installing auplink script.
+	The fsync_super-2.6.xx.patch does nothing but exports a kernel
+	function fsync_super() to modules.
+config AUFS_DENY_WRITE_ACCESS_PATCH
+	bool "deny_write_access.patch was applied or not"
+	depends on AUFS = m
+	help
+	A security enhancement to deny writing to a running executable
+	which exists on an aufs branch filesystem and executed through
+	aufs. If you applied
+	'CVS_TREE/aufs/patch/deny_write_access.patch' to your kernel
+	and you are compiling aufs as a module, then enable this
+	option.
+	The write_deny_access.patch does nothing but export the
+	function.
+config AUFS_WORKAROUND_FUSE
+	bool "Special handling for FUSE-based filesystem"
+	depends on FUSE_FS
+	help
+	A FUSE-based filesystem may not initialize its inode
+	attributes and the FUSE developer thinks the inode attributes
+	in a positive dentry which is returned by VFS lookup operation
+	are not reliable.
+	If you use a FUSE-based filesystem as an aufs branch, and it
+	customizes the inode attribute on it without overriding
+	fuse_lowlevel_ops.lookup, probably you need to enable this
+	configuration.
+	If you enable this configuration, aufs calls getattr operation
+	in every lookup and revalidate operation for the FUSE-based
+	filesystem branch.
+	It will have a negative impact to the performance even if you do
+	not use a FUSE-based filesystem branch.
+config AUFS_HIN_OR_FUSE
+	bool
+	depends on AUFS_HINOTIFY || AUFS_WORKAROUND_FUSE
+	default y
+	help
+	Automatic configuration for internal use.
 config AUFS_DEBUG
 	bool "Debug aufs"
-	depends on AUFS
-	default n
+	default y
 	help
 	Enable this to compile aufs internal debug code.
 	It will have a negative impact to the performance.
+config AUFS_MAGIC_SYSRQ
+	bool
+	depends on AUFS_DEBUG && MAGIC_SYSRQ
+	default y
+	help
+	Automatic configuration for internal use.
+	When aufs supports Magic SysRq, enabled automatically.
+config AUFS_COMPAT
+	bool "Compatibility with Unionfs (obsolete)"
+	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 AUFS_UNIONFS22_PATCH
+	bool "Unionfs-2.2 or later patch is applied or not (obsolete)"
+	help
+	Unionfs version 2.2 (and later) patch introduces some changes in VFS layer which has an impact to aufs. If you have applied such patch to your kernel, you need to enable this configuration even if you disabled CONFIG_UNIONFS.
+config AUFS_UNIONFS23_PATCH
+	bool "Unionfs-2.3 or later patch is applied or not (obsolete)"
+	select AUFS_SPLICE_PATCH
+	select AUFS_UNIONFS22_PATCH
+	help
+	Unionfs version 2.3 (and later) patch introduces some changes in VFS layer which has an impact to aufs. If you have applied such patch to your kernel, you need to enable this configuration even if you disabled CONFIG_UNIONFS.
+endif
diff --git a/ubuntu/aufs/Makefile b/ubuntu/aufs/Makefile
index 5773312..77ab26a 100644
--- a/ubuntu/aufs/Makefile
+++ b/ubuntu/aufs/Makefile
@@ -1,6 +1,76 @@
+# AUFS Makefile for the Linux 2.6.25 and later
+# $Id: Makefile,v 1.8 2008/08/04 00:32:21 sfjro Exp $
+
+# the environment variables are not inherited since 2.6.23
+ifdef AUFS_EXTRA_CFLAGS
+ccflags-y += ${AUFS_EXTRA_CFLAGS}
+endif
+
+########################################
+
+ifdef CONFIG_AUFS_RR_SQUASHFS
+# cf. squashfs3.2-r2 and sqlzma patch.
+ccflags-y += -DSQUASHFS_MAGIC=0x73717368
+ccflags-y += -DSQUASHFS_MAGIC_SWAP=0x68737173
+ccflags-y += -DSQUASHFS_MAGIC_LZMA=0x71736873
+ccflags-y += -DSQUASHFS_MAGIC_LZMA_SWAP=0x73687371
+endif
+
+# defined in ${srctree}/fs/fuse/inode.c
+ccflags-$(CONFIG_AUFS_WORKAROUND_FUSE) += -DFUSE_SUPER_MAGIC=0x65735546
+
+# defined in ${srctree}/fs/xfs/xfs_sb.h
+# tristate
+ifdef CONFIG_XFS_FS
+ccflags-y += -DXFS_SB_MAGIC=0x58465342
+endif
+
+# defined in ${srctree}/mm/shmem.c in linux-2.6.26 and earlier
+# tristate
+ifdef CONFIG_TMPFS
+ifeq ($(shell test ${SUBLEVEL} -le 26 && echo t),t)
+ccflags-y += -DTMPFS_MAGIC=0x01021994
+endif
+endif
+
+# defined in ${srctree}fs/sysfs/mount.c
+# bool
+ccflags-$(CONFIG_SYSFS) += -DSYSFS_MAGIC=0x62656572
+
+ifndef EXTRAVERSION
+EXTRAVERSION = $(shell echo ${KERNELVERSION} | cut -f3- -d. | cut -f2- -d-)
+endif
+# for -mm tree, support the latest version only
+ifneq ($(strip $(shell echo ${EXTRAVERSION} | fgrep -- mm)),)
+ccflags-y += -DCONFIG_AUFS_UNIONFS22_PATCH -DCONFIG_AUFS_UNIONFS23_PATCH
+endif
+
+-include $(dir $(lastword ${MAKEFILE_LIST}))priv.mk
+#$(warning ${ccflags-y})
+
+########################################
+
 obj-$(CONFIG_AUFS) += aufs.o
+aufs-y := module.o super.o sbinfo.o branch.o xino.o sysaufs.o opts.o \
+	wkq.o vfsub.o dcsub.o \
+	cpup.o whout.o plink.o wbr_policy.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
 
-aufs-objs := module.o super.o sbinfo.o branch.o xino.o sysaufs.o opts.o \
-	wkq.o vfsub.o dcsub.o cpup.o whout.o plink.o wbr_policy.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 sysfs.o debug.o
+aufs-$(CONFIG_SYSFS) += sysfs.o
+aufs-$(CONFIG_AUFS_BR_NFS) += br_nfs.o
+aufs-$(CONFIG_AUFS_BR_XFS) += br_xfs.o
+aufs-$(CONFIG_AUFS_WORKAROUND_FUSE) += br_fuse.o
+aufs-$(CONFIG_AUFS_DLGT) += dlgt.o
+aufs-$(CONFIG_AUFS_HINOTIFY) += hinotify.o
+aufs-$(CONFIG_AUFS_HIN_OR_DLGT) += hin_or_dlgt.o
+aufs-$(CONFIG_AUFS_HIN_OR_FUSE) += hin_or_fuse.o
+aufs-$(CONFIG_AUFS_EXPORT) += export.o
+aufs-$(CONFIG_AUFS_ROBR) += robr.o
+# reserved for future use
+#aufs-$(CONFIG_AUFS_XATTR) += xattr.o
+aufs-$(CONFIG_AUFS_DEBUG) += debug.o
+aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o
diff --git a/ubuntu/aufs/aufs.h b/ubuntu/aufs/aufs.h
index d1aefcd..f01ca72 100644
--- a/ubuntu/aufs/aufs.h
+++ b/ubuntu/aufs/aufs.h
@@ -19,7 +19,7 @@
 /*
  * main header files
  *
- * $Id: aufs.h,v 1.3 2008/05/26 04:04:21 sfjro Exp $
+ * $Id: aufs.h,v 1.4 2008/06/30 03:58:55 sfjro Exp $
  */
 
 #ifndef __AUFS_H__
@@ -53,5 +53,10 @@
 /* reserved for future use */
 /* #include "xattr.h" */
 
+#ifdef AuNoInlineForStack
+#undef noinline_for_stack
+#define noinline_for_stack /* */
+#endif
+
 #endif /* __KERNEL__ */
 #endif /* __AUFS_H__ */
diff --git a/ubuntu/aufs/br_fuse.c b/ubuntu/aufs/br_fuse.c
new file mode 100644
index 0000000..17d504a
--- /dev/null
+++ b/ubuntu/aufs/br_fuse.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * special handling for inode attributes on FUSE branch
+ *
+ * $Id: br_fuse.c,v 1.6 2008/07/27 22:49:28 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+/* h_mnt can be NULL, is it safe? */
+int au_update_fuse_h_inode(struct vfsmount *h_mnt, struct dentry *h_dentry)
+{
+	int err;
+	struct kstat st;
+
+	LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
+
+	err = 0;
+	if (unlikely(h_dentry->d_inode
+		     /* && atomic_read(&h_dentry->d_inode->i_count) */
+		     && au_test_fuse(h_dentry->d_sb))) {
+		err = vfsub_getattr(h_mnt, h_dentry, &st, /*dlgt*/0);
+		if (unlikely(err)) {
+			AuDbg("err %d\n", err);
+			au_debug_on();
+			AuDbgDentry(h_dentry);
+			au_debug_off();
+			WARN_ON(err);
+		}
+	}
+	return err;
+}
+
+#if 0 /* temp */
+/*
+ * This function was born after a discussion with the FUSE developer.
+ * The inode attributes on a filesystem who defines i_op->getattr()
+ * is unreliable since such fs may not maintain the attributes at lookup.
+ * This function doesn't want the result of stat, instead wants the side-effect
+ * which refreshes the attributes.
+ * Hmm, there seems to be no such filesystem except fuse.
+ */
+int vfsub_i_attr(struct vfsmount *mnt, struct dentry *dentry, int dlgt)
+{
+	int err;
+	struct inode *inode;
+	struct inode_operations *op;
+	struct kstat st;
+
+	inode = dentry->d_inode;
+	AuDebugOn(!inode);
+
+	err = 0;
+	op = inode->i_op;
+	if (unlikely(op && op->getattr && !au_test_aufs(dentry->d_sb))) {
+		err = security_inode_getattr(mnt, dentry);
+		if (!err)
+			err = op->getattr(mnt, dentry, &st);
+	}
+	AuTraceErr(err);
+	return err;
+}
+#endif
diff --git a/ubuntu/aufs/br_nfs.c b/ubuntu/aufs/br_nfs.c
new file mode 100644
index 0000000..1b53bdd
--- /dev/null
+++ b/ubuntu/aufs/br_nfs.c
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * lookup functions for NFS branch in linux-2.6.19 and later
+ *
+ * $Id: br_nfs.c,v 1.7 2008/07/21 02:54:22 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+static struct file *au_find_h_intent(struct au_hdentry *hd, struct file *file)
+{
+	struct file *h_file, *hf;
+	struct au_hdintent *hdi, *tmp, *do_free;
+
+	LKTRTrace("%.*s\n", AuDLNPair(hd->hd_dentry));
+
+	h_file = NULL;
+	do_free = NULL;
+	spin_lock(&hd->hd_lock);
+	list_for_each_entry_safe(hdi, tmp, hd->hd_intent_list, hdi_list) {
+		hf = hdi->hdi_file[AuIntent_BRANCH];
+		if (hdi->hdi_file[AuIntent_AUFS] == file
+		    && hf->f_dentry == hd->hd_dentry) {
+			h_file = hf;
+			do_free = hdi;
+			list_del(&hdi->hdi_list);
+			break;
+		}
+	}
+	spin_unlock(&hd->hd_lock);
+	kfree(do_free);
+
+	return h_file;
+}
+
+struct file *au_h_intent(struct dentry *dentry, aufs_bindex_t bindex,
+			 struct file *file)
+{
+	struct file *h_file;
+	struct au_hdentry *hd = au_di(dentry)->di_hdentry + bindex;
+
+	LKTRTrace("%.*s, b%d, f %p\n", AuDLNPair(dentry), bindex, file);
+	DiMustAnyLock(dentry);
+	AuDebugOn(bindex < au_di(dentry)->di_bstart
+		  || bindex > au_di(dentry)->di_bend);
+
+	h_file = NULL;
+	if (!hd->hd_intent_list || !file)
+		return h_file; /* success */
+
+	/* AuDebugOn(au_test_wkq(current)); */
+	h_file = au_find_h_intent(hd, file);
+	return h_file;
+}
+
+static int au_set_h_intent(struct dentry *dentry, aufs_bindex_t bindex,
+			   struct file *file, struct file *h_file)
+{
+	int err;
+	struct au_hdentry *hd = au_di(dentry)->di_hdentry + bindex;
+	struct au_hdintent *hdi;
+	struct file *hf;
+
+	LKTRTrace("%.*s, b%d, f %p\n", AuDLNPair(dentry), bindex, file);
+	/* d_revalidate() holds read_lock */
+	/* DiMustWriteLock(dentry); */
+	AuDebugOn(bindex < au_di(dentry)->di_bstart
+		  || bindex > au_di(dentry)->di_bend
+		  || !file
+		  || !h_file
+		  /* || au_test_wkq(current) */);
+
+	err = -ENOMEM;
+	if (hd->hd_intent_list) {
+		while (1) {
+			hf = au_find_h_intent(hd, file);
+			if (!hf)
+				break;
+			fput(hf);
+			AuWarn("freed hfile %.*s b%d left\n",
+			       AuDLNPair(dentry), bindex);
+		}
+	} else {
+		spin_lock(&hd->hd_lock);
+		if (!hd->hd_intent_list) {
+			hd->hd_intent_list
+				= kmalloc(sizeof(*hd->hd_intent_list),
+					  GFP_ATOMIC);
+			if (unlikely(!hd->hd_intent_list)) {
+				spin_unlock(&hd->hd_lock);
+				goto out;
+			}
+			INIT_LIST_HEAD(hd->hd_intent_list);
+		}
+		spin_unlock(&hd->hd_lock);
+	}
+
+	hdi = kmalloc(sizeof(*hdi), GFP_NOFS);
+	if (unlikely(!hdi))
+		goto out;
+
+	err = 0;
+	/* hdi->hdi_pid = current->pid; */
+	hdi->hdi_file[AuIntent_AUFS] = file;
+	hdi->hdi_file[AuIntent_BRANCH] = h_file;
+	spin_lock(&hd->hd_lock);
+	list_add(&hdi->hdi_list, hd->hd_intent_list);
+	spin_unlock(&hd->hd_lock);
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+int au_br_nfs_h_intent(struct file *nd_file, struct dentry *dentry,
+		       aufs_bindex_t bindex, struct nameidata *nd)
+{
+	int err;
+
+	AuTraceEnter();
+
+	err = 0;
+	if (!nd_file)
+		goto out;
+
+	AuDebugOn(!nd);
+	err = au_set_h_intent(dentry, bindex, nd->intent.open.file, nd_file);
+	if (unlikely(err)) {
+		fput(nd_file);
+		au_set_h_dptr(dentry, bindex, NULL);
+		/* todo: update bstart and bend? */
+	}
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_hintent_put(struct au_hdentry *hd, int do_free)
+{
+	struct au_hdintent *hdi, *tmp;
+	struct file *hf;
+
+	if (unlikely(hd->hd_intent_list)) {
+		/* no spin lock */
+		list_for_each_entry_safe(hdi, tmp, hd->hd_intent_list,
+					 hdi_list) {
+			LKTRTrace("hdi %p\n", hdi);
+			hf = hdi->hdi_file[AuIntent_BRANCH];
+			if (unlikely(hf))
+				fput(hf);
+			/* list_del(&hdi->hdi_list); */
+			kfree(hdi);
+		}
+		if (do_free)
+			kfree(hd->hd_intent_list);
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+int au_fake_intent(/* struct au_ndsub *save,  */struct nameidata *nd,
+		   int perm)
+{
+	int err;
+
+	LKTRTrace("perm %d\n", perm);
+
+	err = 0;
+	nd->intent.open.file = NULL;
+	if (nd->flags & LOOKUP_OPEN) {
+		err = -ENFILE;
+		nd->intent.open.file = get_empty_filp();
+		if (unlikely(!nd->intent.open.file))
+			goto out;
+
+		err = 0;
+		if (!au_br_writable(perm)) {
+			nd->intent.open.flags = FMODE_READ
+				| au_file_roflags(nd->intent.open.flags);
+			nd->flags &= ~LOOKUP_CREATE;
+		}
+	}
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+int au_hin_after_reval(struct nameidata *nd, struct dentry *dentry,
+		       aufs_bindex_t bindex, struct file *file)
+{
+	int err;
+
+	LKTRTrace("nd %p, %.*s, b%d, f %d\n",
+		  nd, AuDLNPair(dentry), bindex, !!file);
+
+	err = 0;
+	if ((nd->flags & LOOKUP_OPEN)
+	    && nd->intent.open.file
+	    && !IS_ERR(nd->intent.open.file)) {
+		if (nd->intent.open.file->f_dentry) {
+			err = au_set_h_intent(dentry, bindex, file,
+					      nd->intent.open.file);
+			if (!err)
+				nd->intent.open.file = NULL;
+		}
+		if (unlikely(nd->intent.open.file))
+			put_filp(nd->intent.open.file);
+	}
+
+	return err;
+}
+
+#ifdef CONFIG_AUFS_DLGT
+struct au_lookup_hash_args {
+	struct dentry **errp;
+	struct qstr *name;
+	struct dentry *base;
+	struct nameidata *nd;
+};
+
+static void au_call_lookup_hash(void *args)
+{
+	struct au_lookup_hash_args *a = args;
+	*a->errp = vfsub__lookup_hash(a->name, a->base, a->nd);
+}
+
+static struct dentry *
+au_lkup_hash_dlgt(struct qstr *this, struct dentry *parent,
+		  struct nameidata *nd, unsigned int flags)
+{
+	struct dentry *dentry;
+	int dirperm1;
+
+	dirperm1 = au_ftest_ndx(flags, DIRPERM1);
+	if (!dirperm1 && !au_ftest_ndx(flags, DLGT))
+		dentry = vfsub__lookup_hash(this, parent, nd);
+	else {
+		int wkq_err;
+		struct au_lookup_hash_args args = {
+			.errp	= &dentry,
+			.name	= this,
+			.base	= parent,
+			.nd	= nd
+		};
+		wkq_err = au_wkq_wait(au_call_lookup_hash, &args,
+				      /*dlgt*/!dirperm1);
+		if (unlikely(wkq_err))
+			dentry = ERR_PTR(wkq_err);
+	}
+
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+#else
+static struct dentry *
+au_lkup_hash_dlgt(struct qstr *this, struct dentry *parent,
+		  struct nameidata *nd, unsigned int flags)
+{
+	return vfsub__lookup_hash(this, parent, nd);
+}
+#endif /* CONFIG_AUFS_DLGT */
+
+struct dentry *au_lkup_hash(const char *name, struct dentry *parent,
+			    int len, struct au_ndx *ndx)
+{
+	struct dentry *dentry;
+	char *p;
+	unsigned long hash;
+	struct qstr this;
+	unsigned int c;
+	struct nameidata tmp_nd, *ndo;
+	int err;
+
+	LKTRTrace("%.*s/%.*s\n", AuDLNPair(parent), len, name);
+
+	/* todo: export and call __lookup_one_len() in fs/namei.c? */
+	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);
+
+	ndo = ndx->nd;
+	if (ndo) {
+		tmp_nd = *ndo;
+		err = au_fake_intent(&tmp_nd, ndx->br->br_perm);
+		dentry = ERR_PTR(err);
+		if (unlikely(err))
+			goto out_intent;
+	} else
+		memset(&tmp_nd, 0, sizeof(tmp_nd));
+
+	tmp_nd.path.dentry = parent;
+	tmp_nd.path.mnt = ndx->nfsmnt;
+	path_get(&tmp_nd.path);
+	dentry = au_lkup_hash_dlgt(&this, parent, &tmp_nd, ndx->flags);
+	if (!IS_ERR(dentry)) {
+		/* why negative dentry for a new dir was unhashed? */
+		if (unlikely(d_unhashed(dentry)))
+			d_rehash(dentry);
+		if (tmp_nd.intent.open.file
+		    && tmp_nd.intent.open.file->f_dentry) {
+			ndx->nd_file = tmp_nd.intent.open.file;
+			tmp_nd.intent.open.file = NULL;
+			/* au_br_get(ndx->br); */
+		}
+	}
+	path_put(&tmp_nd.path);
+
+ out_intent:
+	if (tmp_nd.intent.open.file)
+		put_filp(tmp_nd.intent.open.file);
+ out:
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
diff --git a/ubuntu/aufs/br_xfs.c b/ubuntu/aufs/br_xfs.c
new file mode 100644
index 0000000..c1309cb
--- /dev/null
+++ b/ubuntu/aufs/br_xfs.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * special handling inode attributes on XFS branch in linux-2.6.24 and later
+ *
+ * $Id: br_xfs.c,v 1.3 2008/07/07 01:12:38 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+/* h_mnt can be NULL, is it safe? */
+dev_t au_h_rdev(struct inode *h_inode, struct vfsmount *h_mnt,
+		struct dentry *h_dentry)
+{
+	dev_t rdev;
+	int err;
+	struct kstat st;
+
+	LKTRTrace("hi%lu\n", h_inode->i_ino);
+	if (h_dentry)
+		LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
+
+	rdev = h_inode->i_rdev;
+	if (!rdev || !au_test_xfs(h_inode->i_sb))
+		goto out;
+
+	rdev = 0;
+	if (!h_dentry) {
+		err = 0;
+		h_dentry = d_find_alias(h_inode);
+		if (unlikely(!h_dentry))
+			goto failure;
+		err = PTR_ERR(h_dentry);
+		if (IS_ERR(h_dentry)) {
+			h_dentry = NULL;
+			goto failure;
+		}
+		LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
+	} else
+		dget(h_dentry);
+
+	err = vfsub_getattr(h_mnt, h_dentry, &st, /*dlgt*/0);
+	dput(h_dentry);
+	if (!err) {
+		rdev = st.rdev;
+		goto out; /* success */
+	}
+
+ failure:
+	AuIOErr("failed rdev for XFS inode, hi%lu, %d\n", h_inode->i_ino, err);
+ out:
+	return rdev;
+}
diff --git a/ubuntu/aufs/branch.c b/ubuntu/aufs/branch.c
index 8775eed..e40ebcd 100644
--- a/ubuntu/aufs/branch.c
+++ b/ubuntu/aufs/branch.c
@@ -19,28 +19,29 @@
 /*
  * branch management
  *
- * $Id: branch.c,v 1.7 2008/06/02 02:36:59 sfjro Exp $
+ * $Id: branch.c,v 1.16 2008/09/08 02:39:41 sfjro Exp $
  */
 
 #include <linux/iso_fs.h>
 #include <linux/loop.h>
 #include <linux/romfs_fs.h>
 #include <linux/smp_lock.h>
-
-#ifdef CONFIG_AUFS_RR_SQUASHFS
-#include <linux/squashfs_fs.h>
-#endif
-
 #include "aufs.h"
 
-static void free_branch(struct au_branch *br)
+static void au_br_do_free(struct au_branch *br)
 {
+	int i;
+	struct au_wbr *wbr;
+
 	AuTraceEnter();
 
-	if (br->br_xino)
-		fput(br->br_xino);
-	dput(br->br_wh);
-	dput(br->br_plink);
+	if (br->br_xino.xi_file)
+		fput(br->br_xino.xi_file);
+	wbr = br->br_wbr;
+	if (wbr)
+		for (i = 0; i < AuBrWh_Last; i++)
+			dput(wbr->wbr_wh[i]);
+	/* do not call au_br_nfs_lockdep_off() here */
 	if (!au_test_nfs(br->br_mnt->mnt_sb))
 		mntput(br->br_mnt);
 	else {
@@ -48,7 +49,10 @@ static void free_branch(struct au_branch *br)
 		mntput(br->br_mnt);
 		lockdep_on();
 	}
-	AuDebugOn(au_br_count(br) || atomic_read(&br->br_wh_running));
+	AuDebugOn(au_br_count(br));
+	if (wbr)
+		AuDebugOn(atomic_read(&wbr->wbr_wh_running));
+	kfree(wbr);
 	kfree(br);
 }
 
@@ -64,7 +68,7 @@ void au_br_free(struct au_sbinfo *sbinfo)
 	bmax = sbinfo->si_bend + 1;
 	br = sbinfo->si_branch;
 	while (bmax--)
-		free_branch(*br++);
+		au_br_do_free(*br++);
 }
 
 /*
@@ -125,13 +129,16 @@ int au_test_def_rr(struct super_block *h_sb)
 static int do_test_overlap(struct super_block *sb, struct dentry *h_d1,
 			   struct dentry *h_d2)
 {
-	int err;
+	struct dentry *d;
 
 	LKTRTrace("%.*s, %.*s\n", AuDLNPair(h_d1), AuDLNPair(h_d2));
 
-	err = au_test_subdir(h_d1, h_d2);
-	AuTraceErr(err);
-	return err;
+	d = au_test_subdir(h_d1, h_d2);
+	if (unlikely(d)) {
+		AuDbgDentry(h_d1);
+		AuDbgDentry(h_d2);
+	}
+	return !!d;
 }
 
 static int test_overlap_loopback(struct super_block *sb, struct dentry *h_d1,
@@ -141,12 +148,23 @@ static int test_overlap_loopback(struct super_block *sb, struct dentry *h_d1,
 	struct inode *h_inode;
 	struct loop_device *l;
 
+	LKTRTrace("%.*s, %.*s\n", AuDLNPair(h_d1), AuDLNPair(h_d2));
+	AuDbgDentry(h_d1);
+	AuDbgDentry(h_d2);
+	AuDbgSb(h_d1->d_sb);
+	AuDbgSb(h_d2->d_sb);
+
 	h_inode = h_d1->d_inode;
 	if (MAJOR(h_inode->i_sb->s_dev) != LOOP_MAJOR)
 		return 0;
 
 	l = h_inode->i_sb->s_bdev->bd_disk->private_data;
 	h_d1 = l->lo_backing_file->f_dentry;
+	/* h_d1 can be local NFS. in this case aufs cannot detect the loop */
+	AuDbgDentry(h_d1);
+	AuDbgDentry(h_d2);
+	AuDbgSb(h_d1->d_sb);
+	AuDbgSb(h_d2->d_sb);
 	if (unlikely(h_d1->d_sb == sb))
 		return 1;
 	return do_test_overlap(sb, h_d1, h_d2);
@@ -160,8 +178,11 @@ static int test_overlap(struct super_block *sb, struct dentry *h_d1,
 {
 	LKTRTrace("d1 %.*s, d2 %.*s\n", AuDLNPair(h_d1), AuDLNPair(h_d2));
 
-	if (unlikely(h_d1 == h_d2))
+	if (unlikely(h_d1 == h_d2)) {
+		AuDbgDentry(h_d1);
+		AuDbgDentry(h_d2);
 		return 1;
+	}
 	return do_test_overlap(sb, h_d1, h_d2)
 		|| do_test_overlap(sb, h_d2, h_d1)
 		|| test_overlap_loopback(sb, h_d1, h_d2)
@@ -170,34 +191,35 @@ static int test_overlap(struct super_block *sb, struct dentry *h_d1,
 
 /* ---------------------------------------------------------------------- */
 
-static int init_br_wh(struct super_block *sb, aufs_bindex_t bindex,
-		      struct au_branch *br, int new_perm,
-		      struct dentry *h_root, struct vfsmount *h_mnt)
+static int au_br_init_wh(struct super_block *sb, aufs_bindex_t bindex,
+			 struct au_branch *br, int new_perm,
+			 struct dentry *h_root, struct vfsmount *h_mnt)
 {
 	int err, old_perm;
-	struct inode *dir, *h_dir;
-	const int new = (bindex < 0);
+	struct inode *h_dir;
+	struct au_wbr *wbr;
 
 	LKTRTrace("b%d, new_perm %d\n", bindex, new_perm);
 
-	dir = sb->s_root->d_inode;
+	wbr = br->br_wbr;
 	h_dir = h_root->d_inode;
-	if (new)
-		mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
-	else
-		au_hdir_lock(h_dir, dir, bindex);
-
-	br_wh_write_lock(br);
 	old_perm = br->br_perm;
+	mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
+	if (wbr)
+		wbr_wh_write_lock(wbr);
 	br->br_perm = new_perm;
-	err = au_wh_init(h_root, br, au_do_nfsmnt(h_mnt), sb);
+	err = au_wh_init(h_root, br, au_do_nfsmnt(h_mnt), sb, bindex);
 	br->br_perm = old_perm;
-	br_wh_write_unlock(br);
-
-	if (new)
-		mutex_unlock(&h_dir->i_mutex);
-	else
-		au_hdir_unlock(h_dir, dir, bindex);
+	if (wbr)
+		wbr_wh_write_unlock(wbr);
+	mutex_unlock(&h_dir->i_mutex);
+	if (!err && wbr && !au_br_writable(new_perm)) {
+		AuDebugOn(wbr->wbr_whbase);
+		AuDebugOn(wbr->wbr_plink);
+		AuDebugOn(wbr->wbr_tmp);
+		kfree(wbr);
+		br->br_wbr = NULL;
+	}
 
 	AuTraceErr(err);
 	return err;
@@ -209,7 +231,8 @@ static int init_br_wh(struct super_block *sb, aufs_bindex_t bindex,
  * returns a newly allocated branch. @new_nbranch is a number of branches
  * after adding a branch.
  */
-static struct au_branch *alloc_addbr(struct super_block *sb, int new_nbranch)
+static struct au_branch *alloc_addbr(struct super_block *sb, int new_nbranch,
+				     int perm)
 {
 	struct au_branch **branchp, *add_branch;
 	int sz;
@@ -226,18 +249,24 @@ static struct au_branch *alloc_addbr(struct super_block *sb, int new_nbranch)
 	inode = root->d_inode;
 	IiMustWriteLock(inode);
 
-	add_branch = kmalloc(sizeof(*add_branch), GFP_KERNEL);
+	add_branch = kmalloc(sizeof(*add_branch), GFP_NOFS);
 	if (unlikely(!add_branch))
 		goto out;
+	add_branch->br_wbr = NULL;
+	if (unlikely(au_br_writable(perm))) {
+		add_branch->br_wbr = kmalloc(sizeof(*add_branch->br_wbr),
+					     GFP_NOFS);
+		if (unlikely(!add_branch->br_wbr))
+			goto out_br;
+	}
 
 	sz = sizeof(*branchp) * (new_nbranch - 1);
 	if (unlikely(!sz))
 		sz = sizeof(*branchp);
 	p = au_sbi(sb)->si_branch;
-	branchp = au_kzrealloc(p, sz, sizeof(*branchp) * new_nbranch,
-			       GFP_KERNEL);
+	branchp = au_kzrealloc(p, sz, sizeof(*branchp) * new_nbranch, GFP_NOFS);
 	if (unlikely(!branchp))
-		goto out_br;
+		goto out_wbr;
 	au_sbi(sb)->si_branch = branchp;
 
 	sz = sizeof(*hdentryp) * (new_nbranch - 1);
@@ -245,22 +274,23 @@ static struct au_branch *alloc_addbr(struct super_block *sb, int new_nbranch)
 		sz = sizeof(*hdentryp);
 	p = au_di(root)->di_hdentry;
 	hdentryp = au_kzrealloc(p, sz, sizeof(*hdentryp) * new_nbranch,
-				GFP_KERNEL);
+				GFP_NOFS);
 	if (unlikely(!hdentryp))
-		goto out_br;
+		goto out_wbr;
 	au_di(root)->di_hdentry = hdentryp;
 
 	sz = sizeof(*hinodep) * (new_nbranch - 1);
 	if (unlikely(!sz))
 		sz = sizeof(*hinodep);
 	p = au_ii(inode)->ii_hinode;
-	hinodep = au_kzrealloc(p, sz, sizeof(*hinodep) * new_nbranch,
-			       GFP_KERNEL);
+	hinodep = au_kzrealloc(p, sz, sizeof(*hinodep) * new_nbranch, GFP_NOFS);
 	if (unlikely(!hinodep))
-		goto out_br;
+		goto out_wbr;
 	au_ii(inode)->ii_hinode = hinodep;
 	return add_branch; /* success */
 
+ out_wbr:
+	kfree(add_branch->br_wbr);
  out_br:
 	kfree(add_branch);
  out:
@@ -286,6 +316,15 @@ static int test_br(struct super_block *sb, struct inode *inode, int brperm,
 	return err;
 }
 
+static int au_unsupported_fs(struct super_block *sb)
+{
+	return (sb->s_magic == PROC_SUPER_MAGIC
+#ifdef SYSFS_MAGIC
+		|| sb->s_magic == SYSFS_MAGIC
+#endif
+		|| !strcmp(au_sbtype(sb), "unionfs"));
+}
+
 /*
  * returns:
  * 0: success, the caller will add it
@@ -345,7 +384,7 @@ static int test_add(struct super_block *sb, struct au_opt_add *add, int remount)
 		goto out;
 	}
 
-	if (unlikely(!strcmp(au_sbtype(inode->i_sb), "unionfs"))) {
+	if (unlikely(au_unsupported_fs(inode->i_sb))) {
 		AuErr("unsupported filesystem, %s\n", add->path);
 		goto out;
 	}
@@ -362,6 +401,15 @@ static int test_add(struct super_block *sb, struct au_opt_add *add, int remount)
 	if (bend < 0)
 		return 0; /* success */
 
+	err = -EINVAL;
+	for (bindex = 0; bindex <= bend; bindex++)
+		if (unlikely(test_overlap(sb, add->nd.path.dentry,
+					  au_h_dptr(root, bindex)))) {
+			AuErr("%s is overlapped\n", add->path);
+			goto out;
+		}
+
+	err = 0;
 	h_inode = au_h_dptr(root, 0)->d_inode;
 	if (unlikely(au_opt_test(au_mntflags(sb), WARN_PERM)
 		     && ((h_inode->i_mode & S_IALLUGO)
@@ -374,20 +422,33 @@ static int test_add(struct super_block *sb, struct au_opt_add *add, int remount)
 		       h_inode->i_uid, h_inode->i_gid,
 		       (h_inode->i_mode & S_IALLUGO));
 
-	err = -EINVAL;
-	for (bindex = 0; bindex <= bend; bindex++)
-		if (unlikely(test_overlap(sb, add->nd.path.dentry,
-					  au_h_dptr(root, bindex)))) {
-			AuErr("%s is overlapped\n", add->path);
-			goto out;
-		}
-	err = 0;
-
  out:
 	AuTraceErr(err);
 	return err;
 }
 
+static int au_wbr_init(struct au_branch *br, struct super_block *sb,
+		       int perm, struct path *path)
+{
+	int err;
+	struct au_wbr *wbr;
+
+	AuTraceEnter();
+	wbr = br->br_wbr;
+	AuDebugOn(!wbr);
+
+	au_rw_init_nolock(&wbr->wbr_wh_rwsem);
+	memset(wbr->wbr_wh, 0, sizeof(wbr->wbr_wh));
+	atomic_set(&wbr->wbr_wh_running, 0);
+	wbr->wbr_bytes = 0;
+
+	err = au_br_init_wh(sb, /*bindex*/-1, br, perm,
+			    path->dentry, path->mnt);
+
+	AuTraceErr(err);
+	return err;
+}
+
 static int au_br_init(struct au_branch *br, struct super_block *sb,
 		      struct au_opt_add *add)
 {
@@ -397,24 +458,21 @@ static int au_br_init(struct au_branch *br, struct super_block *sb,
 	AuTraceEnter();
 
 	err = 0;
-	au_rw_init_nolock(&br->br_wh_rwsem);
-	br->br_plink = NULL;
-	br->br_wh = NULL;
 	if (unlikely(au_br_writable(add->perm))) {
-		err = init_br_wh(sb, /*bindex*/-1, br, add->perm,
-				 add->nd.path.dentry, add->nd.path.mnt);
+		err = au_wbr_init(br, sb, add->perm, &add->nd.path);
 		if (unlikely(err))
 			goto out;
 	}
 
-	br->br_xino = NULL;
+	br->br_xino.xi_file = NULL;
+	mutex_init(&br->br_xino.xi_nondir_mtx);
 	br->br_mnt = mntget(add->nd.path.mnt);
 	mnt_flags = au_mntflags(sb);
 	if (au_opt_test(mnt_flags, XINO)) {
 		err = au_xino_br(sb, br, add->nd.path.dentry->d_inode->i_ino,
-				 au_sbr(sb, 0)->br_xino, /*do_test*/1);
+				 au_sbr(sb, 0)->br_xino.xi_file, /*do_test*/1);
 		if (unlikely(err)) {
-			AuDebugOn(br->br_xino);
+			AuDebugOn(br->br_xino.xi_file);
 			goto out;
 		}
 #if 0 /* reserved for future use */
@@ -422,17 +480,15 @@ static int au_br_init(struct au_branch *br, struct super_block *sb,
 		err = au_xinodir_br(sb, br, add->nd.path.dentry->d_inode->i_ino,
 				    /*do_test*/1);
 		if (unlikely(err)) {
-			AuDebugOn(br->br_xino);
+			AuDebugOn(br->br_xino.xi_file);
 			goto out;
 		}
 #endif
 	}
 
-	atomic_set(&br->br_wh_running, 0);
 	br->br_id = au_new_br_id(sb);
 	br->br_perm = add->perm;
 	atomic_set(&br->br_count, 0);
-	br->br_bytes = 0;
 	br->br_xino_upper = AUFS_XINO_TRUNC_INIT;
 	atomic_set(&br->br_xino_running, 0);
 	sysaufs_br_init(br);
@@ -452,7 +508,7 @@ int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount)
 	struct au_iinfo *iinfo;
 	struct au_sbinfo *sbinfo;
 	struct au_dinfo *dinfo;
-	struct inode *root_inode, *inode;
+	struct inode *root_inode;
 	unsigned long long maxb;
 	struct au_branch **branchp, *add_branch;
 	struct au_hdentry *hdentryp;
@@ -475,13 +531,13 @@ int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount)
 		return 0; /* success */
 
 	bend = au_sbend(sb);
-	add_branch = alloc_addbr(sb, bend + 2);
+	add_branch = alloc_addbr(sb, bend + 2, add->perm);
 	err = PTR_ERR(add_branch);
 	if (IS_ERR(add_branch))
 		goto out;
 	err = au_br_init(add_branch, sb, add);
 	if (unlikely(err)) {
-		kfree(add_branch);
+		au_br_do_free(add_branch);
 		goto out;
 	}
 
@@ -513,20 +569,25 @@ int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount)
 		dinfo->di_bstart = 0;
 		iinfo->ii_bstart = 0;
 	}
-	inode = dentry->d_inode;
 	au_set_h_dptr(root, add_bindex, dget(dentry));
-	au_set_h_iptr(root_inode, add_bindex, igrab(inode), 0);
+	au_set_h_iptr(root_inode, add_bindex, au_igrab(dentry->d_inode), 0);
 	if (remount)
 		sysaufs_brs_add(sb, add_bindex);
 
 	if (!add_bindex)
 		au_cpup_attr_all(root_inode);
 	else
-		au_add_nlink(root_inode, inode);
+		au_add_nlink(root_inode, dentry->d_inode);
 	maxb = dentry->d_sb->s_maxbytes;
 	if (sb->s_maxbytes < maxb)
 		sb->s_maxbytes = maxb;
 
+	/* safe d_parent reference */
+	if (!au_xino_def_br(sbinfo)
+	    && add_branch->br_xino.xi_file
+	    && add_branch->br_xino.xi_file->f_dentry->d_parent == dentry)
+		au_xino_def_br_set(add_branch, sbinfo);
+
  out:
 	AuTraceErr(err);
 	return err;
@@ -547,17 +608,18 @@ int au_br_add(struct super_block *sb, struct au_opt_add *add, int remount)
 static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex,
 			    au_gen_t sigen)
 {
-	int err, i, j, ndentry, verbose;
+	int err, i, j, ndentry;
 	struct au_dcsub_pages dpages;
 	struct au_dpage *dpage;
 	struct dentry *d;
 	aufs_bindex_t bstart, bend;
+	unsigned char verbose;
 	struct inode *inode;
 
 	LKTRTrace("b%d, gen%d\n", bindex, sigen);
 	SiMustWriteLock(root->d_sb);
 
-	err = au_dpages_init(&dpages, GFP_TEMPORARY);
+	err = au_dpages_init(&dpages, GFP_NOFS);
 	if (unlikely(err))
 		goto out;
 	err = au_dcsub_pages(&dpages, root, NULL, NULL);
@@ -611,9 +673,10 @@ static int test_dentry_busy(struct dentry *root, aufs_bindex_t bindex,
 static int test_inode_busy(struct super_block *sb, aufs_bindex_t bindex,
 			   au_gen_t sigen)
 {
-	int err, verbose;
+	int err;
 	struct inode *i;
 	aufs_bindex_t bstart, bend;
+	unsigned char verbose;
 
 	LKTRTrace("b%d, gen%d\n", bindex, sigen);
 	SiMustWriteLock(sb);
@@ -682,25 +745,24 @@ static int test_children_busy(struct dentry *root, aufs_bindex_t bindex)
 
 int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
 {
-	int err, do_wh, rerr, verbose;
-	struct dentry *root;
-	struct inode *inode, *hidden_dir;
+	int err, rerr, i;
 	aufs_bindex_t bindex, bend, br_id;
+	unsigned char do_wh, verbose;
 	struct au_sbinfo *sbinfo;
 	struct au_dinfo *dinfo;
 	struct au_iinfo *iinfo;
-	struct au_branch *br;
-	unsigned int mnt_flags;
+	struct au_branch *br, **brp;
+	struct au_wbr *wbr;
+	struct au_hdentry *hdp;
+	struct au_hinode *hip;
 
 	LKTRTrace("%s, %.*s\n", del->path, AuDLNPair(del->h_root));
 	SiMustWriteLock(sb);
-	root = sb->s_root;
-	DiMustWriteLock(root);
-	inode = root->d_inode;
-	IiMustWriteLock(inode);
+	DiMustWriteLock(sb->s_root);
+	IiMustWriteLock(sb->s_root->d_inode);
 
 	err = 0;
-	bindex = au_find_dbindex(root, del->h_root);
+	bindex = au_find_dbindex(sb->s_root, del->h_root);
 	if (bindex < 0) {
 		if (remount)
 			goto out; /* success */
@@ -711,8 +773,7 @@ int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
 	LKTRTrace("bindex b%d\n", bindex);
 
 	err = -EBUSY;
-	mnt_flags = au_mntflags(sb);
-	verbose = au_opt_test(mnt_flags, VERBOSE);
+	verbose = !!au_opt_test(au_mntflags(sb), VERBOSE);
 	bend = au_sbend(sb);
 	if (unlikely(!bend)) {
 		AuVerbose(verbose, "no more branches left\n");
@@ -724,25 +785,24 @@ int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
 		goto out;
 	}
 
-	do_wh = 0;
-	hidden_dir = del->h_root->d_inode;
-	if (br->br_wh || br->br_plink) {
+	wbr = br->br_wbr;
+	do_wh = wbr && (wbr->wbr_whbase || wbr->wbr_plink || wbr->wbr_tmp);
+	if (do_wh) {
 #if 0 /* reserved for future use */
 		/* remove whiteout base */
-		err = init_br_wh(sb, bindex, br, AuBr_RO, del->h_root,
-				 br->br_mnt);
+		err = au_br_init_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_plink = NULL;
-		br->br_wh = NULL;
+		for (i = 0; i < AuBrWh_Last; i++) {
+			dput(wbr->wbr_wh[i]);
+			wbr->wbr_wh[i] = NULL;
+		}
 #endif
-		do_wh = 1;
 	}
 
-	err = test_children_busy(root, bindex);
+	err = test_children_busy(sb->s_root, bindex);
 	if (unlikely(err)) {
 		if (unlikely(do_wh))
 			goto out_wh;
@@ -753,20 +813,17 @@ int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
 	if (remount)
 		sysaufs_brs_del(sb, bindex);
 	sbinfo = au_sbi(sb);
-	dinfo = au_di(root);
-	iinfo = au_ii(inode);
+	dinfo = au_di(sb->s_root);
+	iinfo = au_ii(sb->s_root->d_inode);
 
-	dput(au_h_dptr(root, bindex));
+	dput(au_h_dptr(sb->s_root, bindex));
 	au_hiput(iinfo->ii_hinode + bindex);
 	br_id = br->br_id;
-	free_branch(br);
+	au_br_do_free(br);
 
 	/* todo: realloc and shrink memory? */
 	if (bindex < bend) {
 		const aufs_bindex_t n = bend - bindex;
-		struct au_branch **brp;
-		struct au_hdentry *hdp;
-		struct au_hinode *hip;
 
 		brp = sbinfo->si_branch + bindex;
 		memmove(brp, brp + 1, sizeof(*brp) * n);
@@ -787,10 +844,10 @@ int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
 		sysaufs_brs_add(sb, bindex);
 
 	if (!bindex)
-		au_cpup_attr_all(inode);
+		au_cpup_attr_all(sb->s_root->d_inode);
 	else
-		au_sub_nlink(inode, del->h_root->d_inode);
-	if (au_opt_test(mnt_flags, PLINK))
+		au_sub_nlink(sb->s_root->d_inode, del->h_root->d_inode);
+	if (au_opt_test(au_mntflags(sb), PLINK))
 		au_plink_half_refresh(sb, br_id);
 
 	if (sb->s_maxbytes == del->h_root->d_sb->s_maxbytes) {
@@ -803,11 +860,15 @@ int au_br_del(struct super_block *sb, struct au_opt_del *del, int remount)
 				sb->s_maxbytes = maxb;
 		}
 	}
+
+	if (au_xino_def_br(sbinfo) == br)
+		au_xino_def_br_set(NULL, sbinfo);
 	goto out; /* success */
 
  out_wh:
 	/* revert */
-	rerr = init_br_wh(sb, bindex, br, br->br_perm, del->h_root, br->br_mnt);
+	rerr = au_br_init_wh(sb, bindex, br, br->br_perm, del->h_root,
+			     br->br_mnt);
 	if (rerr)
 		AuWarn("failed re-creating base whiteout, %s. (%d)\n",
 		       del->path, rerr);
@@ -827,6 +888,50 @@ static int need_sigen_inc(int old, int new)
 		|| do_need_sigen_inc(new, old));
 }
 
+static int au_br_mod_files_ro(struct super_block *sb, aufs_bindex_t bindex)
+{
+	int err;
+	struct file *file, *hf;
+
+	AuTraceEnter();
+	SiMustWriteLock(sb);
+
+	/* no need file_list_lock() since sbinfo is locked */
+	err = 0;
+	list_for_each_entry(file, &sb->s_files, f_u.fu_list) {
+		LKTRTrace("%.*s\n", AuDLNPair(file->f_dentry));
+		if (!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)
+		    || au_fbstart(file) != bindex) {
+			FiMustNoWaiters(file);
+			fi_read_unlock(file);
+			continue;
+		}
+
+		if (unlikely(au_test_mmapped(file))) {
+			err = -EBUSY;
+			FiMustNoWaiters(file);
+			fi_read_unlock(file);
+			break;
+		}
+
+		/* todo: already flushed? */
+		hf = au_h_fptr(file, au_fbstart(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);
+	}
+
+	AuTraceErr(err);
+	return err;
+}
+
 int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount,
 	      int *do_update)
 {
@@ -865,66 +970,48 @@ int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount,
 	if (au_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);
+		err = au_br_init_wh(sb, bindex, br, mod->perm, mod->h_root,
+				    br->br_mnt);
 		if (unlikely(err))
 			goto out;
 #else /* reserved for future use */
-		dput(br->br_wh);
-		dput(br->br_plink);
-		br->br_plink = NULL;
-		br->br_wh = NULL;
+		struct au_wbr *wbr;
+		wbr = br->wbr;
+		if (wbr)
+			for (i = 0; i < AuBrWh_Last; i++) {
+				dput(wbr->wbr_wh[i]);
+				wbr->wbr_wh[i] = NULL;
+			}
 #endif
 
 		if (!au_br_writable(mod->perm)) {
 			/* rw --> ro, file might be mmapped */
-			struct file *file, *hf;
 
 #if 1 /* todo: test more? */
 			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", AuDLNPair(file->f_dentry));
-				if (!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)
-				    || au_fbstart(file) != bindex) {
-					FiMustNoWaiters(file);
-					fi_read_unlock(file);
-					continue;
-				}
-
-				if (unlikely(au_test_mmapped(file))) {
-					err = -EBUSY;
-					FiMustNoWaiters(file);
-					fi_read_unlock(file);
-					break;
-				}
-
-				/* todo: already flushed? */
-				hf = au_h_fptr(file, au_fbstart(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);
-			}
-
+			err = au_br_mod_files_ro(sb, bindex);
 			/* aufs_write_lock() calls ..._child() */
 			di_write_lock_child(root);
 #endif
 		}
+	} else if (au_br_writable(mod->perm)) {
+		/* ro --> rw */
+		err = -ENOMEM;
+		br->br_wbr = kmalloc(sizeof(*br->br_wbr), GFP_NOFS);
+		if (br->br_wbr) {
+			struct path path = {
+				.mnt	= br->br_mnt,
+				.dentry	= mod->h_root
+			};
+
+			err = au_wbr_init(br, sb, mod->perm, &path);
+			if (unlikely(err)) {
+				kfree(br->br_wbr);
+				br->br_wbr = NULL;
+			}
+		}
 	}
 
 	if (!err) {
diff --git a/ubuntu/aufs/branch.h b/ubuntu/aufs/branch.h
index 64b44af..bc685ea 100644
--- a/ubuntu/aufs/branch.h
+++ b/ubuntu/aufs/branch.h
@@ -19,7 +19,7 @@
 /*
  * branch filesystems and xino for them
  *
- * $Id: branch.h,v 1.5 2008/06/02 02:36:59 sfjro Exp $
+ * $Id: branch.h,v 1.12 2008/09/15 03:14:03 sfjro Exp $
  */
 
 #ifndef __AUFS_BRANCH_H__
@@ -30,7 +30,7 @@
 #include <linux/fs.h>
 #include <linux/mount.h>
 #include <linux/sysfs.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "misc.h"
 #include "super.h"
 
@@ -48,6 +48,7 @@ struct au_xino_entry {
 /* a xino file */
 struct au_xino_file {
 	struct file		*xi_file;
+	struct mutex		xi_nondir_mtx;
 
 	/* reserved for future use */
 #if 0
@@ -58,21 +59,32 @@ struct au_xino_file {
 	unsigned long long	xi_size;	 /* s_maxbytes */
 
 	/* truncation */
-	u64			xi_upper;	/* watermark in bytes */
-	u64			xi_step;	/* to next watermark in bytes */
-#endif
+	unsigned long long	xi_upper;	/* watermark in bytes */
+	unsigned long long	xi_step;	/* to next watermark in bytes */
 
 	/* truncation */
 	blkcnt_t		xi_upper;	/* watermark in blocks */
 	atomic_t 		xi_running;
+#endif
+};
+
+/* members for writable branch only */
+enum {AuBrWh_BASE, AuBrWh_PLINK, AuBrWh_TMP, AuBrWh_Last};
+struct au_wbr {
+	struct au_rwsem		wbr_wh_rwsem;
+	struct dentry		*wbr_wh[AuBrWh_Last];
+	atomic_t 		wbr_wh_running;
+#define wbr_whbase		wbr_wh[AuBrWh_BASE]	/* whiteout base */
+#define wbr_plink		wbr_wh[AuBrWh_PLINK]	/* pseudo-link dir */
+#define wbr_tmp			wbr_wh[AuBrWh_TMP]	/* temporary dir */
+
+	/* mfs mode */
+	unsigned long long	wbr_bytes;
 };
 
 /* protected by superblock rwsem */
 struct au_branch {
-	struct file		*br_xino;
-#if 0 /* reserved for future use */
-	struct au_xino_file		*br_xino;
-#endif
+	struct au_xino_file	br_xino;
 
 	aufs_bindex_t		br_id;
 
@@ -80,13 +92,7 @@ struct au_branch {
 	struct vfsmount		*br_mnt;
 	atomic_t		br_count;
 
-	/* whiteout base */
-	struct au_rwsem		br_wh_rwsem;
-	struct dentry		*br_wh;
-	atomic_t 		br_wh_running;
-
-	/* pseudo-link dir */
-	struct dentry		*br_plink;
+	struct au_wbr		*br_wbr;
 
 #if 1 /* reserved for future use */
 	/* xino truncation */
@@ -94,9 +100,6 @@ struct au_branch {
 	atomic_t		br_xino_running;
 #endif
 
-	/* mfs mode */
-	u64			br_bytes;
-
 #ifdef CONFIG_SYSFS
 	/* an entry under sysfs per mount-point */
 	char			br_name[8];
@@ -110,41 +113,41 @@ struct au_branch {
 
 /* branch permission and attribute */
 enum {
-	AuBr_RW,		/* writable, linkable wh */
-	AuBr_RO,		/* readonly, no wh */
-	AuBr_RR,		/* natively readonly, no wh */
+	AuBrPerm_RW,		/* writable, linkable wh */
+	AuBrPerm_RO,		/* readonly, no wh */
+	AuBrPerm_RR,		/* natively readonly, no wh */
 
-	AuBr_RWNoLinkWH,	/* un-linkable whiteouts */
+	AuBrPerm_RWNoLinkWH,	/* un-linkable whiteouts */
 
-	AuBr_ROWH,
-	AuBr_RRWH,		/* whiteout-able */
+	AuBrPerm_ROWH,
+	AuBrPerm_RRWH,		/* whiteout-able */
 
-	AuBr_Last
+	AuBrPerm_Last
 };
 
 static inline int au_br_writable(int brperm)
 {
-	return (brperm == AuBr_RW || brperm == AuBr_RWNoLinkWH);
+	return (brperm == AuBrPerm_RW || brperm == AuBrPerm_RWNoLinkWH);
 }
 
 static inline int au_br_whable(int brperm)
 {
-	return (brperm == AuBr_RW
-		|| brperm == AuBr_ROWH
-		|| brperm == AuBr_RRWH);
+	return (brperm == AuBrPerm_RW
+		|| brperm == AuBrPerm_ROWH
+		|| brperm == AuBrPerm_RRWH);
 }
 
 #if 0 /* reserved for future use */
 static inline int au_br_linkable_wh(int brperm)
 {
-	return (brperm == AuBr_RW);
+	return (brperm == AuBrPerm_RW);
 }
 #endif
 
 static inline int au_br_hinotifyable(int brperm)
 {
 #ifdef CONFIG_AUFS_HINOTIFY
-	return (brperm != AuBr_RR && brperm != AuBr_RRWH);
+	return (brperm != AuBrPerm_RR && brperm != AuBrPerm_RRWH);
 #else
 	return 0;
 #endif
@@ -166,10 +169,15 @@ int au_br_mod(struct super_block *sb, struct au_opt_mod *mod, int remount,
 	      int *do_update);
 
 /* xino.c */
+#define Au_LOFF_MAX	((loff_t)LLONG_MAX)
 int au_xib_trunc(struct super_block *sb);
-
-struct file *au_xino_create(struct super_block *sb, char *fname, int silent,
-			    struct dentry *parent);
+ssize_t xino_fread(au_readf_t func, struct file *file, void *buf, size_t size,
+		   loff_t *pos);
+ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size,
+		    loff_t *pos);
+struct file *au_xino_create(struct super_block *sb, char *fname, int silent);
+struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
+			     struct file *copy_src);
 ino_t au_xino_new_ino(struct super_block *sb);
 int au_xino_write0(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 		   ino_t ino);
@@ -305,12 +313,6 @@ static inline int au_test_unique_ino(struct dentry *h_dentry, ino_t h_ino)
 	return 1;
 }
 
-#ifdef CONFIG_AUFS_BR_NFS
-static inline int au_test_unsupported_nfs(struct super_block *h_sb)
-{
-	return 0;
-}
-
 static inline struct vfsmount *au_do_nfsmnt(struct vfsmount *h_mnt)
 {
 	if (!au_test_nfs(h_mnt->mnt_sb))
@@ -318,6 +320,25 @@ static inline struct vfsmount *au_do_nfsmnt(struct vfsmount *h_mnt)
 	return h_mnt;
 }
 
+static inline void au_br_nfs_lockdep_off(struct super_block *sb)
+{
+	if (au_test_nfs(sb))
+		lockdep_off();
+}
+
+static inline void au_br_nfs_lockdep_on(struct super_block *sb)
+{
+	/* hoping this condition will be optimized... */
+	if (au_test_nfs(sb))
+		lockdep_on();
+}
+
+#ifdef CONFIG_AUFS_BR_NFS
+static inline int au_test_unsupported_nfs(struct super_block *h_sb)
+{
+	return 0;
+}
+
 /* it doesn't mntget() */
 static inline
 struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex)
@@ -330,12 +351,7 @@ struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex)
 #else
 static inline int au_test_unsupported_nfs(struct super_block *h_sb)
 {
-	return au_test_nfs(h_sb);
-}
-
-static inline struct vfsmount *au_do_nfsmnt(struct vfsmount *h_mnt)
-{
-	return NULL;
+	return (h_sb->s_magic == NFS_SUPER_MAGIC);
 }
 
 static inline
@@ -355,22 +371,22 @@ struct vfsmount *au_nfsmnt(struct super_block *sb, aufs_bindex_t bindex)
  * br_wh_read_lock, br_wh_write_lock
  * br_wh_read_unlock, br_wh_write_unlock, br_wh_downgrade_lock
  */
-AuSimpleRwsemFuncs(br_wh, struct au_branch *br, br->br_wh_rwsem);
+AuSimpleRwsemFuncs(wbr_wh, struct au_wbr *wbr, wbr->wbr_wh_rwsem);
 
 /* to debug easier, do not make them inlined functions */
-#define BrWhMustReadLock(br) do { \
+#define WbrWhMustReadLock(wbr) do { \
 	/* SiMustAnyLock(sb); */ \
-	AuRwMustReadLock(&(br)->br_wh_rwsem); \
+	AuRwMustReadLock(&(wbr)->wbr_wh_rwsem); \
 } while (0)
 
-#define BrWhMustWriteLock(br) do { \
+#define WbrWhMustWriteLock(wbr) do { \
 	/* SiMustAnyLock(sb); */ \
-	AuRwMustWriteLock(&(br)->br_wh_rwsem); \
+	AuRwMustWriteLock(&(wbr)->wbr_wh_rwsem); \
 } while (0)
 
-#define BrWhMustAnyLock(br) do { \
+#define WbrWhMustAnyLock(br) do { \
 	/* SiMustAnyLock(sb); */ \
-	AuRwMustAnyLock(&(br)->br_wh_rwsem); \
+	AuRwMustAnyLock(&(wbr)->wbr_wh_rwsem); \
 } while (0)
 
 #endif /* __KERNEL__ */
diff --git a/ubuntu/aufs/cpup.c b/ubuntu/aufs/cpup.c
index 9435180..3b5fc65 100644
--- a/ubuntu/aufs/cpup.c
+++ b/ubuntu/aufs/cpup.c
@@ -19,7 +19,7 @@
 /*
  * copy-up functions, see wbr_policy.c for copy-down
  *
- * $Id: cpup.c,v 1.7 2008/06/02 02:37:17 sfjro Exp $
+ * $Id: cpup.c,v 1.17 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include <linux/fs_stack.h>
@@ -88,8 +88,9 @@ void au_cpup_attr_changeable(struct inode *inode)
 
 void au_cpup_igen(struct inode *inode, struct inode *h_inode)
 {
-	inode->i_generation = h_inode->i_generation;
-	au_ii(inode)->ii_hsb1 = h_inode->i_sb;
+	struct au_iinfo *iinfo = au_ii(inode);
+	iinfo->ii_higen = h_inode->i_generation;
+	iinfo->ii_hsb1 = h_inode->i_sb;
 }
 
 void au_cpup_attr_all(struct inode *inode)
@@ -121,19 +122,21 @@ void au_cpup_attr_all(struct inode *inode)
 
 /* keep the timestamps of the parent dir when cpup */
 void au_dtime_store(struct au_dtime *dt, struct dentry *dentry,
-		    struct dentry *h_dentry, struct au_hinode *hdir)
+		    struct dentry *h_dentry, struct au_hinode *hinode,
+		    struct au_hinode *hdir)
 {
-	struct inode *inode;
+	struct inode *h_inode;
 
 	LKTRTrace("%.*s, hdir %d\n", AuDLNPair(dentry), !!hdir);
 	AuDebugOn(!dentry || !h_dentry || !h_dentry->d_inode);
 
 	dt->dt_dentry = dentry;
 	dt->dt_h_dentry = h_dentry;
+	dt->dt_hinode = hinode;
 	dt->dt_hdir = hdir;
-	inode = h_dentry->d_inode;
-	dt->dt_atime = inode->i_atime;
-	dt->dt_mtime = inode->i_mtime;
+	h_inode = h_dentry->d_inode;
+	dt->dt_atime = h_inode->i_atime;
+	dt->dt_mtime = h_inode->i_mtime;
 	/* smp_mb(); */
 }
 
@@ -141,7 +144,7 @@ void au_dtime_revert(struct au_dtime *dt)
 {
 	struct iattr attr;
 	int err;
-	struct au_hin_ignore ign;
+	struct au_hin_ignore ign[2];
 	struct vfsub_args vargs;
 
 	LKTRTrace("%.*s\n", AuDLNPair(dt->dt_dentry));
@@ -151,10 +154,15 @@ void au_dtime_revert(struct au_dtime *dt)
 	attr.ia_valid = ATTR_FORCE | ATTR_MTIME | ATTR_MTIME_SET
 		| ATTR_ATIME | ATTR_ATIME_SET;
 
-	vfsub_args_init(&vargs, &ign,
+	vfsub_args_init(&vargs, ign,
 			au_test_dlgt(au_mntflags(dt->dt_dentry->d_sb)), 0);
-	if (unlikely(dt->dt_hdir))
-		vfsub_ign_hinode(&vargs, IN_ATTRIB, dt->dt_hdir);
+	/*
+	 * IN_ATTRIB should be divided into
+	 * IN_ATTRIB_ATIME, IN_ATTRIB_MTIME ...,
+	 * and define all ORed new IN_ATTRIB macro.
+	 */
+	vfsub_ign_hinode(&vargs, IN_ATTRIB, dt->dt_hinode);
+	vfsub_ign_hinode(&vargs, IN_ATTRIB, dt->dt_hdir);
 	err = vfsub_notify_change(dt->dt_h_dentry, &attr, &vargs);
 	if (unlikely(err))
 		AuWarn("restoring timestamps failed(%d). ignored\n", err);
@@ -162,14 +170,16 @@ void au_dtime_revert(struct au_dtime *dt)
 
 /* ---------------------------------------------------------------------- */
 
-static noinline_for_stack int
-cpup_iattr(struct dentry *h_dst, struct dentry *h_src, int dlgt)
+static noinline_for_stack
+int cpup_iattr(struct dentry *dst, aufs_bindex_t bindex, struct dentry *h_src,
+	       struct au_hinode *hdir, struct vfsub_args *vargs)
 {
 	int err, sbits;
+	struct dentry *h_dst;
 	struct iattr ia;
 	struct inode *h_isrc, *h_idst;
-	struct vfsub_args vargs;
 
+	h_dst = au_h_dptr(dst, bindex);
 	LKTRTrace("%.*s\n", AuDLNPair(h_dst));
 	h_idst = h_dst->d_inode;
 	/* todo? IMustLock(h_idst); */
@@ -186,14 +196,17 @@ cpup_iattr(struct dentry *h_dst, struct dentry *h_src, int dlgt)
 	ia.ia_mtime = h_isrc->i_mtime;
 	sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID));
 
-	vfsub_args_init(&vargs, NULL, dlgt, /*force_unlink*/0);
-	err = vfsub_notify_change(h_dst, &ia, &vargs);
+	vfsub_args_reinit(vargs);
+	vfsub_ign_hinode(vargs, IN_ATTRIB, hdir);
+	err = vfsub_notify_change(h_dst, &ia, vargs);
 
 	/* is this nfs only? */
 	if (!err && sbits && au_test_nfs(h_dst->d_sb)) {
 		ia.ia_valid = ATTR_FORCE | ATTR_MODE;
 		ia.ia_mode = h_isrc->i_mode;
-		err = vfsub_notify_change(h_dst, &ia, &vargs);
+		vfsub_args_reinit(vargs);
+		vfsub_ign_hinode(vargs, IN_ATTRIB, hdir);
+		err = vfsub_notify_change(h_dst, &ia, vargs);
 	}
 
 	/* todo? remove this? */
@@ -208,9 +221,9 @@ cpup_iattr(struct dentry *h_dst, struct dentry *h_src, int dlgt)
  * to support a sparse file which is opened with O_APPEND,
  * we need to close the file.
  */
-static noinline_for_stack int
-cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
-	     loff_t len)
+static noinline_for_stack
+int cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
+		 loff_t len, struct au_hinode *hdir, struct vfsub_args *vargs)
 {
 	int err, i;
 	struct super_block *sb;
@@ -264,7 +277,8 @@ cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
 
 	/* stop updating while we copyup */
 	IMustLock(hidden[SRC].dentry->d_inode);
-	err = au_copy_file(hidden[DST].file, hidden[SRC].file, len, sb);
+	err = au_copy_file(hidden[DST].file, hidden[SRC].file, len, hdir, sb,
+			   vargs);
 
  out_dst_file:
 	fput(hidden[DST].file);
@@ -277,28 +291,28 @@ cpup_regular(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
 	return err;
 }
 
-static noinline_for_stack int
-au_do_cpup_regular(struct dentry *dentry, aufs_bindex_t bdst,
-		   aufs_bindex_t bsrc, loff_t len, struct inode *h_inode,
-		   struct inode *h_dir, struct dentry *h_dst, int dlgt)
+static int au_do_cpup_regular(struct dentry *dentry, aufs_bindex_t bdst,
+			      aufs_bindex_t bsrc, loff_t len,
+			      struct au_hinode *hdir, struct dentry *h_dst,
+			      struct vfsub_args *vargs)
 {
 	int err, rerr;
 	loff_t l;
-	struct vfsub_args vargs;
 
 	AuTraceEnter();
 
 	err = 0;
-	l = i_size_read(h_inode);
+	l = i_size_read(au_h_iptr(dentry->d_inode, bsrc));
 	if (len == -1 || l < len)
 		len = l;
 	if (len)
-		err = cpup_regular(dentry, bdst, bsrc, len);
+		err = cpup_regular(dentry, bdst, bsrc, len, hdir, vargs);
 	if (!err)
 		goto out; /* success */
 
-	vfsub_args_init(&vargs, NULL, dlgt, 0);
-	rerr = vfsub_unlink(h_dir, h_dst, &vargs);
+	vfsub_args_reinit(vargs);
+	vfsub_ign_hinode(vargs, IN_DELETE, hdir);
+	rerr = vfsub_unlink(hdir->hi_inode, h_dst, vargs);
 	if (rerr) {
 		AuIOErr("failed unlinking cpup-ed %.*s(%d, %d)\n",
 			AuDLNPair(h_dst), err, rerr);
@@ -310,9 +324,9 @@ au_do_cpup_regular(struct dentry *dentry, aufs_bindex_t bdst,
 	return err;
 }
 
-static noinline_for_stack int
-au_do_cpup_symlink(struct dentry *h_dst, struct dentry *h_src,
-		   struct inode *h_dir, umode_t mode, int dlgt)
+static int au_do_cpup_symlink(struct dentry *h_dst, struct dentry *h_src,
+			      struct inode *h_dir, umode_t mode,
+			      struct vfsub_args *vargs)
 {
 	int err, symlen;
 	char *sym;
@@ -334,7 +348,7 @@ au_do_cpup_symlink(struct dentry *h_dst, struct dentry *h_src,
 
 	if (symlen > 0) {
 		sym[symlen] = 0;
-		err = vfsub_symlink(h_dir, h_dst, sym, mode, dlgt);
+		err = vfsub_symlink(h_dir, h_dst, sym, mode, vargs);
 	}
 	__putname(sym);
 
@@ -344,19 +358,21 @@ au_do_cpup_symlink(struct dentry *h_dst, struct dentry *h_src,
 }
 
 /* return with hidden dst inode is locked */
-static noinline_for_stack int
-cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
-	   loff_t len, unsigned int flags, int dlgt)
+static noinline_for_stack
+int cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
+	       loff_t len, unsigned int flags, struct dentry *dst_parent,
+	       struct vfsub_args *vargs)
 {
-	int err, isdir, hinotify;
-	struct dentry *h_src, *h_dst, *h_parent, *parent;
+	int err;
+	unsigned char isdir, hinotify;
+	struct dentry *h_src, *h_dst, *h_parent, *gparent;
 	struct inode *h_inode, *h_dir;
 	struct au_dtime dt;
 	umode_t mode;
 	struct super_block *sb;
-	struct au_hinode *hgdir;
-	const int do_dt = au_ftest_cpup(flags, DTIME);
+	struct au_hinode *hgdir, *hdir;
 	unsigned int mnt_flags;
+	const int do_dt = au_ftest_cpup(flags, DTIME);
 
 	LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %lld, dtime %u\n",
 		  AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
@@ -369,52 +385,61 @@ cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
 	AuDebugOn(!h_src);
 	h_inode = h_src->d_inode;
 	AuDebugOn(!h_inode);
+	AuDebugOn(h_inode != au_h_iptr(dentry->d_inode, bsrc));
 
 	/* stop referencing while we are creating */
-	parent = dget_parent(dentry);
 	h_dst = au_h_dptr(dentry, bdst);
 	AuDebugOn(h_dst && h_dst->d_inode);
 	h_parent = h_dst->d_parent; /* dir inode is locked */
 	h_dir = h_parent->d_inode;
 	IMustLock(h_dir);
+	AuDebugOn(h_parent != h_dst->d_parent);
 
+	hdir = NULL;
 	mnt_flags = au_mntflags(sb);
 	hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY);
+	if (unlikely(hinotify)) {
+		hdir = au_hi(dst_parent->d_inode, bdst);
+		AuDebugOn(hdir->hi_inode != h_dir);
+	}
+
 	if (do_dt) {
 		hgdir = NULL;
-		if (unlikely(hinotify && !IS_ROOT(parent))) {
-			struct dentry *gparent;
-			gparent = dget_parent(parent);
+		if (unlikely(hinotify && !IS_ROOT(dst_parent))) {
+			gparent = dget_parent(dst_parent);
 			hgdir = au_hi(gparent->d_inode, bdst);
+			IMustLock(hgdir->hi_inode);
 			dput(gparent);
 		}
-		au_dtime_store(&dt, parent, h_parent, hgdir);
+		au_dtime_store(&dt, dst_parent, h_parent, hdir, hgdir);
 	}
 
 	isdir = 0;
+	vfsub_args_reinit(vargs);
+	vfsub_ign_hinode(vargs, IN_CREATE, hdir);
 	mode = h_inode->i_mode;
 	switch (mode & S_IFMT) {
 	case S_IFREG:
 		/* stop updating while we are referencing */
 		IMustLock(h_inode);
-		err = au_h_create(h_dir, h_dst, mode | S_IWUSR, dlgt, NULL,
+		err = au_h_create(h_dir, h_dst, mode | S_IWUSR, vargs, NULL,
 				  au_nfsmnt(sb, bdst));
 		if (!err)
 			err = au_do_cpup_regular(dentry, bdst, bsrc, len,
-						 h_inode, h_dir, h_dst, dlgt);
+						 hdir, h_dst, vargs);
 		break;
 	case S_IFDIR:
 		isdir = 1;
-		err = vfsub_mkdir(h_dir, h_dst, mode, dlgt);
+		err = vfsub_mkdir(h_dir, h_dst, mode, vargs);
 		if (!err) {
 			/* setattr case: dir is not locked */
-			if (0 && au_ibstart(parent->d_inode) == bdst)
-				au_cpup_attr_nlink(parent->d_inode);
+			if (0 && au_ibstart(dst_parent->d_inode) == bdst)
+				au_cpup_attr_nlink(dst_parent->d_inode);
 			au_cpup_attr_nlink(dentry->d_inode);
 		}
 		break;
 	case S_IFLNK:
-		err = au_do_cpup_symlink(h_dst, h_src, h_dir, mode, dlgt);
+		err = au_do_cpup_symlink(h_dst, h_src, h_dir, mode, vargs);
 		break;
 	case S_IFCHR:
 	case S_IFBLK:
@@ -424,25 +449,25 @@ cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
 	case S_IFSOCK:
 		err = vfsub_mknod(h_dir, h_dst, mode,
 				  au_h_rdev(h_inode, /*h_mnt*/NULL, h_src),
-				  dlgt);
+				  vargs);
 		break;
 	default:
 		AuIOErr("Unknown inode type 0%o\n", mode);
 		err = -EIO;
 	}
 
-	/* todo: should it be always? */
 	if (unlikely(hinotify
 		     && !isdir
 		     && au_opt_test_xino(mnt_flags)
 		     && h_inode->i_nlink == 1
-		     && bdst < bsrc))
+		     //&& dentry->d_inode->i_nlink == 1
+		     && bdst < bsrc
+		     && !au_ftest_cpup(flags, KEEPLINO)))
 		au_xino_write0(sb, bsrc, h_inode->i_ino, /*ino*/0);
 		/* ignore this error */
 
 	if (do_dt)
 		au_dtime_revert(&dt);
-	dput(parent);
 	AuTraceErr(err);
 	return err;
 }
@@ -452,18 +477,19 @@ cpup_entry(struct dentry *dentry, aufs_bindex_t bdst, aufs_bindex_t bsrc,
  * the caller must set the both of hidden dentries.
  * @len is for truncating when it is -1 copyup the entire file.
  */
-int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
-		   aufs_bindex_t bsrc, loff_t len, unsigned int flags)
+static int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
+			  aufs_bindex_t bsrc, loff_t len, unsigned int flags,
+			  struct dentry *dst_parent, struct vfsub_args *vargs)
 {
-	int err, rerr, isdir, dlgt, plink;
-	struct dentry *h_src, *h_dst, *parent, *h_parent;
-	struct inode *dst_inode, *h_dir, *inode;
-	struct super_block *sb;
+	int err, rerr;
+	unsigned int mnt_flags;
 	aufs_bindex_t old_ibstart;
+	unsigned char isdir, plink, hinotify;
 	struct au_dtime dt;
-	struct vfsub_args vargs;
-	struct au_hinode *hgdir;
-	unsigned int mnt_flags;
+	struct dentry *h_src, *h_dst, *h_parent, *gparent;
+	struct inode *dst_inode, *h_dir, *inode;
+	struct super_block *sb;
+	struct au_hinode *hgdir, *hdir;
 
 	LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %lld, flags 0x%x\n",
 		  AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
@@ -479,11 +505,17 @@ int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 	AuDebugOn(!h_src || !h_src->d_inode);
 	inode = dentry->d_inode;
 	IiMustWriteLock(inode);
-	parent = dget_parent(dentry);
+	if (!dst_parent)
+		dst_parent = dget_parent(dentry);
+	else
+		dget(dst_parent);
 
 	mnt_flags = au_mntflags(sb);
-	dlgt = !!au_test_dlgt(mnt_flags);
 	plink = !!au_opt_test(mnt_flags, PLINK);
+	hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY);
+	hdir = NULL;
+	if (unlikely(hinotify))
+		hdir = au_hi(dst_parent->d_inode, bdst);
 	dst_inode = au_h_iptr(inode, bdst);
 	if (unlikely(dst_inode)) {
 		if (unlikely(!plink)) {
@@ -494,12 +526,30 @@ int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 		}
 
 		if (dst_inode->i_nlink) {
+			const int do_dt = au_ftest_cpup(flags, DTIME);
+
 			h_src = au_plink_lkup(sb, bdst, inode);
 			err = PTR_ERR(h_src);
 			if (IS_ERR(h_src))
 				goto out;
 			AuDebugOn(!h_src->d_inode);
-			err = vfsub_link(h_src, h_dir, h_dst, dlgt);
+
+			if (do_dt) {
+				hgdir = NULL;
+				if (unlikely(hinotify && !IS_ROOT(dst_parent))) {
+					gparent = dget_parent(dst_parent);
+					hgdir = au_hi(gparent->d_inode, bdst);
+					IMustLock(hgdir->hi_inode);
+					dput(gparent);
+				}
+				au_dtime_store(&dt, dst_parent, h_parent, hdir,
+					       hgdir);
+			}
+			vfsub_args_reinit(vargs);
+			vfsub_ign_hinode(vargs, IN_CREATE, hdir);
+			err = vfsub_link(h_src, h_dir, h_dst, vargs);
+			if (do_dt)
+				au_dtime_revert(&dt);
 			dput(h_src);
 			goto out;
 		} else
@@ -509,14 +559,14 @@ int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 	}
 
 	old_ibstart = au_ibstart(inode);
-	err = cpup_entry(dentry, bdst, bsrc, len, flags, dlgt);
+	err = cpup_entry(dentry, bdst, bsrc, len, flags, dst_parent, vargs);
 	if (unlikely(err))
 		goto out;
 	dst_inode = h_dst->d_inode;
 	mutex_lock_nested(&dst_inode->i_mutex, AuLsc_I_CHILD2);
 
 	/* todo: test dlgt? */
-	err = cpup_iattr(h_dst, h_src, dlgt);
+	err = cpup_iattr(dentry, bdst, h_src, hdir, vargs);
 #if 0 /* reserved for future use */
 	if (0 && !err)
 		err = cpup_xattrs(h_src, h_dst);
@@ -525,7 +575,7 @@ int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 	if (!err) {
 		if (bdst < old_ibstart)
 			au_set_ibstart(inode, bdst);
-		au_set_h_iptr(inode, bdst, igrab(dst_inode),
+		au_set_h_iptr(inode, bdst, au_igrab(dst_inode),
 			      au_hi_flags(inode, isdir));
 		mutex_unlock(&dst_inode->i_mutex);
 		if (!isdir
@@ -539,18 +589,18 @@ int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 	mutex_unlock(&dst_inode->i_mutex);
 	hgdir = NULL;
 	if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY)
-		     && !IS_ROOT(parent))) {
-		struct dentry *gparent;
-		gparent = dget_parent(parent);
+		     && !IS_ROOT(dst_parent))) {
+		gparent = dget_parent(dst_parent);
 		hgdir = au_hi(gparent->d_inode, bdst);
 		dput(gparent);
 	}
-	au_dtime_store(&dt, parent, h_parent, hgdir);
-	vfsub_args_init(&vargs, NULL, dlgt, 0);
+	au_dtime_store(&dt, dst_parent, h_parent, hdir, hgdir);
+	vfsub_args_reinit(vargs);
+	vfsub_ign_hinode(vargs, IN_DELETE, hdir);
 	if (!isdir)
-		rerr = vfsub_unlink(h_dir, h_dst, &vargs);
+		rerr = vfsub_unlink(h_dir, h_dst, vargs);
 	else
-		rerr = vfsub_rmdir(h_dir, h_dst, &vargs);
+		rerr = vfsub_rmdir(h_dir, h_dst, vargs);
 	au_dtime_revert(&dt);
 	if (rerr) {
 		AuIOErr("failed removing broken entry(%d, %d)\n", err, rerr);
@@ -558,7 +608,7 @@ int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 	}
 
  out:
-	dput(parent);
+	dput(dst_parent);
 	AuTraceErr(err);
 	return err;
 }
@@ -569,31 +619,39 @@ struct au_cpup_single_args {
 	aufs_bindex_t bdst, bsrc;
 	loff_t len;
 	unsigned int flags;
+	struct dentry *dst_parent;
+	struct vfsub_args *vargs;
 };
 
 static void au_call_cpup_single(void *args)
 {
 	struct au_cpup_single_args *a = args;
 	*a->errp = au_cpup_single(a->dentry, a->bdst, a->bsrc, a->len,
-				  a->flags);
+				  a->flags, a->dst_parent, a->vargs);
 }
 
 int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
-		       aufs_bindex_t bsrc, loff_t len, unsigned int flags)
+		       aufs_bindex_t bsrc, loff_t len, unsigned int flags,
+		       struct dentry *dst_parent)
 {
 	int err, wkq_err;
 	struct dentry *h_dentry;
 	umode_t mode;
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
 
 	LKTRTrace("%.*s, i%lu, bdst %d, bsrc %d, len %lld, flags 0x%x\n",
 		  AuDLNPair(dentry), dentry->d_inode->i_ino, bdst, bsrc, len,
 		  flags);
 
+	vfsub_args_init(&vargs, &ign, au_test_dlgt(au_mntflags(dentry->d_sb)),
+			/*force_unlink*/0);
 	h_dentry = au_h_dptr(dentry, bsrc);
 	mode = h_dentry->d_inode->i_mode & S_IFMT;
 	if ((mode != S_IFCHR && mode != S_IFBLK)
 	    || capable(CAP_MKNOD))
-		err = au_cpup_single(dentry, bdst, bsrc, len, flags);
+		err = au_cpup_single(dentry, bdst, bsrc, len, flags,
+				     dst_parent, &vargs);
 	else {
 		struct au_cpup_single_args args = {
 			.errp		= &err,
@@ -601,8 +659,11 @@ int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
 			.bdst		= bdst,
 			.bsrc		= bsrc,
 			.len		= len,
-			.flags		= flags
+			.flags		= flags,
+			.dst_parent	= dst_parent,
+			.vargs		= &vargs
 		};
+		vfsub_fclr(vargs.flags, DLGT);
 		wkq_err = au_wkq_wait(au_call_cpup_single, &args, /*dlgt*/0);
 		if (unlikely(wkq_err))
 			err = wkq_err;
@@ -616,8 +677,8 @@ int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
  * copyup the @dentry from the first active hidden branch to @bdst,
  * using au_cpup_single().
  */
-int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
-		   unsigned int flags)
+static int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+			  unsigned int flags, struct vfsub_args *vargs)
 {
 	int err;
 	struct inode *inode;
@@ -636,7 +697,8 @@ int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 
 	err = au_lkup_neg(dentry, bdst);
 	if (!err) {
-		err = au_cpup_single(dentry, bdst, bsrc, len, flags);
+		err = au_cpup_single(dentry, bdst, bsrc, len, flags, NULL,
+				     vargs);
 		if (!err)
 			return 0; /* success */
 
@@ -655,20 +717,25 @@ struct au_cpup_simple_args {
 	aufs_bindex_t bdst;
 	loff_t len;
 	unsigned int flags;
+	struct vfsub_args *vargs;
 };
 
 static void au_call_cpup_simple(void *args)
 {
 	struct au_cpup_simple_args *a = args;
-	*a->errp = au_cpup_simple(a->dentry, a->bdst, a->len, a->flags);
+	*a->errp = au_cpup_simple(a->dentry, a->bdst, a->len, a->flags,
+				  a->vargs);
 }
 
 int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 		       unsigned int flags)
 {
-	int err, do_sio, dlgt, wkq_err;
+	int err, wkq_err;
+	unsigned char do_sio, dlgt;
 	struct dentry *parent;
 	struct inode *h_dir, *dir;
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
 
 	LKTRTrace("%.*s, b%d, len %lld, flags 0x%x\n",
 		  AuDLNPair(dentry), bdst, len, flags);
@@ -677,7 +744,7 @@ int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 	dir = parent->d_inode;
 	h_dir = au_h_iptr(dir, bdst);
 	dlgt = !!au_test_dlgt(au_mntflags(dir->i_sb));
-	do_sio = au_test_h_perm_sio(h_dir, MAY_EXEC | MAY_WRITE, dlgt);
+	do_sio = !!au_test_h_perm_sio(h_dir, MAY_EXEC | MAY_WRITE, dlgt);
 	if (!do_sio) {
 		/*
 		 * testing CAP_MKNOD is for generic fs,
@@ -689,16 +756,19 @@ int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 			  || ((mode & (S_ISUID | S_ISGID))
 			      && !capable(CAP_FSETID)));
 	}
+	vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0);
 	if (!do_sio)
-		err = au_cpup_simple(dentry, bdst, len, flags);
+		err = au_cpup_simple(dentry, bdst, len, flags, &vargs);
 	else {
 		struct au_cpup_simple_args args = {
 			.errp		= &err,
 			.dentry		= dentry,
 			.bdst		= bdst,
 			.len		= len,
-			.flags		= flags
+			.flags		= flags,
+			.vargs		= &vargs
 		};
+		vfsub_fclr(vargs.flags, DLGT);
 		wkq_err = au_wkq_wait(au_call_cpup_simple, &args, /*dlgt*/0);
 		if (unlikely(wkq_err))
 			err = wkq_err;
@@ -711,9 +781,9 @@ int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 
 /* ---------------------------------------------------------------------- */
 
-static noinline_for_stack int
-au_do_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst,
-	      struct dentry *wh_dentry, struct file *file, loff_t len)
+static int au_do_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst,
+			 struct dentry *wh_dentry, struct file *file,
+			 loff_t len, struct vfsub_args *vargs)
 {
 	int err;
 	struct au_dinfo *dinfo;
@@ -731,7 +801,8 @@ au_do_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst,
 	if (file)
 		dinfo->di_hdentry[0 + bstart].hd_dentry
 			= au_h_fptr(file, au_fbstart(file))->f_dentry;
-	err = au_cpup_single(dentry, bdst, bstart, len, !AuCpup_DTIME);
+	err = au_cpup_single(dentry, bdst, bstart, len, !AuCpup_DTIME,
+			     /*h_parent*/NULL, vargs);
 	if (!err && file) {
 		err = au_reopen_nondir(file);
 		dinfo->di_hdentry[0 + bstart].hd_dentry = h_d_bstart;
@@ -746,16 +817,18 @@ au_do_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst,
 /*
  * copyup the deleted file for writing.
  */
-int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
-	       struct file *file)
+static int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
+		      struct file *file)
 {
-	int err, dlgt;
+	int err;
+	unsigned char dlgt;
 	struct dentry *parent, *h_parent, *wh_dentry;
 	struct super_block *sb;
 	unsigned int mnt_flags;
 	struct au_dtime dt;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
-	struct au_hinode *hgdir;
+	struct au_hinode *hgdir, *hdir;
 	struct au_ndx ndx = {
 		.nd	= NULL,
 		.flags	= 0,
@@ -785,23 +858,28 @@ int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 	if (IS_ERR(wh_dentry))
 		goto out;
 
+	hdir = NULL;
 	hgdir = NULL;
-	if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY)
-		     && !IS_ROOT(parent))) {
-		struct dentry *gparent;
-		gparent = dget_parent(parent);
-		hgdir = au_hi(gparent->d_inode, bdst);
-		dput(gparent);
+	if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY))) {
+		hdir = au_hi(parent->d_inode, bdst);
+		if (!IS_ROOT(parent)) {
+			struct dentry *gparent;
+			gparent = dget_parent(parent);
+			hgdir = au_hi(gparent->d_inode, bdst);
+			dput(gparent);
+		}
 	}
-	au_dtime_store(&dt, parent, h_parent, hgdir);
-	err = au_do_cpup_wh(dentry, bdst, wh_dentry, file, len);
+	au_dtime_store(&dt, parent, h_parent, hdir, hgdir);
+	vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0);
+	err = au_do_cpup_wh(dentry, bdst, wh_dentry, file, len, &vargs);
 	if (unlikely(err))
 		goto out_wh;
 
 	AuDebugOn(!d_unhashed(dentry));
 	/* dget first to force sillyrename on nfs */
 	dget(wh_dentry);
-	vfsub_args_init(&vargs, NULL, dlgt, 0);
+	vfsub_args_reinit(&vargs);
+	vfsub_ign_hinode(&vargs, IN_DELETE, hdir);
 	err = vfsub_unlink(h_parent->d_inode, wh_dentry, &vargs);
 	if (unlikely(err)) {
 		AuIOErr("failed remove copied-up tmp file %.*s(%d)\n",
@@ -837,17 +915,36 @@ int au_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;
+	struct dentry *parent, *h_tmp, *h_parent;
+	struct inode *dir, *h_dir, *h_tmpdir;
+	struct au_wbr *wbr;
 
 	AuTraceEnter();
 	parent = dget_parent(dentry);
 	dir = parent->d_inode;
 	IiMustAnyLock(dir);
-	h_dir = au_h_iptr(dir, bdst);
+
+	h_tmp = NULL;
+	h_parent = NULL;
+	h_dir = au_igrab(au_h_iptr(dir, bdst));
+	h_tmpdir = h_dir;
+	if (unlikely(!h_dir->i_nlink)) {
+		DiMustWriteLock(parent);
+		wbr = au_sbr(dentry->d_sb, bdst)->br_wbr;
+		AuDebugOn(!wbr);
+		h_tmp = wbr->wbr_tmp;
+
+		h_parent = dget(au_h_dptr(parent, bdst));
+		au_set_h_dptr(parent, bdst, NULL);
+		au_set_h_dptr(parent, bdst, dget(h_tmp));
+		h_tmpdir = h_tmp->d_inode;
+		au_set_h_iptr(dir, bdst, NULL, 0);
+		au_set_h_iptr(dir, bdst, au_igrab(h_tmpdir), /*flags*/0);
+		mutex_lock_nested(&h_tmpdir->i_mutex, AuLsc_I_PARENT3);
+	}
 
 	if (!au_test_h_perm_sio
-	    (h_dir, MAY_EXEC | MAY_WRITE,
+	    (h_tmpdir, MAY_EXEC | MAY_WRITE,
 	     au_test_dlgt(au_mntflags(dentry->d_sb))))
 		err = au_cpup_wh(dentry, bdst, len, file);
 	else {
@@ -862,6 +959,16 @@ int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 		if (unlikely(wkq_err))
 			err = wkq_err;
 	}
+
+	/* todo: is this restore safe? */
+	if (unlikely(h_tmp)) {
+		mutex_unlock(&h_tmpdir->i_mutex);
+		au_set_h_iptr(dir, bdst, NULL, 0);
+		au_set_h_iptr(dir, bdst, au_igrab(h_dir), /*flags*/0);
+		au_set_h_dptr(parent, bdst, NULL);
+		au_set_h_dptr(parent, bdst, h_parent);
+	}
+	iput(h_dir);
 	dput(parent);
 
 	AuTraceErr(err);
@@ -876,17 +983,18 @@ int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
  * Since the ancestor directory may be moved/renamed during copy.
  */
 /* cf. revalidate function in file.c */
-int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
+int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst,
 	       int (*cp)(struct dentry *dentry, aufs_bindex_t bdst,
 			 struct dentry *h_parent, void *arg),
 	       void *arg)
 {
 	int err, hinotify;
 	struct super_block *sb;
-	struct dentry *d, *parent, *h_parent, *gparent, *real_parent;
+	struct dentry *d, *parent, *h_parent, *real_parent;
+	struct au_pin pin;
 
-	LKTRTrace("%.*s, b%d, parent i%lu, locked %p\n",
-		  AuDLNPair(dentry), bdst, parent_ino(dentry), locked);
+	LKTRTrace("%.*s, b%d, parent i%lu\n",
+		  AuDLNPair(dentry), bdst, (unsigned long)parent_ino(dentry));
 	sb = dentry->d_sb;
 	AuDebugOn(au_test_ro(sb, bdst, NULL));
 	err = 0;
@@ -894,12 +1002,8 @@ int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
 	IiMustWriteLock(parent->d_inode);
 	if (unlikely(IS_ROOT(parent)))
 		goto out;
-	if (locked) {
-		DiMustAnyLock(locked);
-		IiMustAnyLock(locked->d_inode);
-	}
 
-	/* slow loop, keep it simple and stupid */
+	/* do not use au_dpage */
 	real_parent = parent;
 	hinotify = !!au_opt_test(au_mntflags(sb), UDBA_INOTIFY);
 	while (1) {
@@ -914,12 +1018,9 @@ int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
 			d = parent;
 			dput(parent);
 			parent = dget_parent(d);
-			if (parent != locked) {
-				di_read_lock_parent3(parent, !AuLock_IR);
-				h_parent = au_h_dptr(parent, bdst);
-				di_read_unlock(parent, !AuLock_IR);
-			} else
-				h_parent = au_h_dptr(parent, bdst);
+			di_read_lock_parent3(parent, !AuLock_IR);
+			h_parent = au_h_dptr(parent, bdst);
+			di_read_unlock(parent, !AuLock_IR);
 		} while (!h_parent);
 
 		if (d != real_parent)
@@ -927,32 +1028,17 @@ int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
 
 		/* somebody else might create while we were sleeping */
 		if (!au_h_dptr(d, bdst) || !au_h_dptr(d, bdst)->d_inode) {
-			struct inode *h_dir = h_parent->d_inode,
-				*dir = parent->d_inode;
-
 			if (au_h_dptr(d, bdst))
 				au_update_dbstart(d);
-			if (parent != locked)
-				di_read_lock_parent3(parent, AuLock_IR);
-			gparent = NULL;
-			if (unlikely(hinotify && !IS_ROOT(parent))) {
-				gparent = dget_parent(parent);
-				if (gparent != locked)
-					ii_read_lock_parent4(gparent->d_inode);
-				else {
-					dput(gparent);
-					gparent = NULL;
-				}
-			}
-			au_hdir_lock(h_dir, dir, bdst);
-			err = cp(d, bdst, h_parent, arg);
-			au_hdir_unlock(h_dir, dir, bdst);
-			if (unlikely(gparent)) {
-				ii_read_unlock(gparent->d_inode);
-				dput(gparent);
+
+			au_pin_init(&pin, d, /*di_locked*/0,
+				    AuLsc_DI_PARENT3, AuLsc_I_PARENT2, hinotify);
+			err = au_do_pin(pin.pin + AuPin_PARENT, au_pin_gp(&pin),
+					bdst, hinotify);
+			if (!err) {
+				err = cp(d, bdst, h_parent, arg);
+				au_unpin(&pin);
 			}
-			if (parent != locked)
-				di_read_unlock(parent, AuLock_IR);
 		}
 
 		if (d != real_parent)
@@ -978,19 +1064,17 @@ static int au_cpup_dir(struct dentry *dentry, aufs_bindex_t bdst,
 	return err;
 }
 
-int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
-		 struct dentry *locked)
+int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst)
 {
 	int err;
 
-	err = au_cp_dirs(dentry, bdst, locked, au_cpup_dir, NULL);
+	err = au_cp_dirs(dentry, bdst, au_cpup_dir, NULL);
 
 	AuTraceErr(err);
 	return err;
 }
 
-int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
-			  struct dentry *locked)
+int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst)
 {
 	int err;
 	struct dentry *parent;
@@ -998,8 +1082,8 @@ int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
 
 	parent = dget_parent(dentry);
 	dir = parent->d_inode;
-	LKTRTrace("%.*s, b%d, parent i%lu, locked %p\n",
-		  AuDLNPair(dentry), bdst, dir->i_ino, locked);
+	LKTRTrace("%.*s, b%d, parent i%lu\n",
+		  AuDLNPair(dentry), bdst, dir->i_ino);
 	DiMustReadLock(parent);
 	IiMustReadLock(dir);
 
@@ -1008,10 +1092,10 @@ int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
 		goto out;
 
 	di_read_unlock(parent, AuLock_IR);
-	di_write_lock_parent2(parent);
+	di_write_lock_parent(parent);
 	/* someone else might change our inode while we were sleeping */
 	if (unlikely(!au_h_iptr(dir, bdst)))
-		err = au_cpup_dirs(dentry, bdst, locked);
+		err = au_cpup_dirs(dentry, bdst);
 	di_downgrade_lock(parent, AuLock_IR);
 
  out:
diff --git a/ubuntu/aufs/cpup.h b/ubuntu/aufs/cpup.h
index 5dd1a48..e92ce61 100644
--- a/ubuntu/aufs/cpup.h
+++ b/ubuntu/aufs/cpup.h
@@ -19,7 +19,7 @@
 /*
  * copy-up/down functions
  *
- * $Id: cpup.h,v 1.2 2008/04/21 01:33:43 sfjro Exp $
+ * $Id: cpup.h,v 1.5 2008/09/01 02:54:48 sfjro Exp $
  */
 
 #ifndef __AUFS_CPUP_H__
@@ -28,7 +28,7 @@
 #ifdef __KERNEL__
 
 #include <linux/fs.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 
 void au_cpup_attr_timesizes(struct inode *inode);
 void au_cpup_attr_nlink(struct inode *inode);
@@ -39,43 +39,39 @@ void au_cpup_attr_all(struct inode *inode);
 /* ---------------------------------------------------------------------- */
 
 /* cpup flags */
-#define AuCpup_DTIME	1	/* do dtime_store/revert */
+#define AuCpup_DTIME	1		/* do dtime_store/revert */
+#define AuCpup_KEEPLINO	(1 << 1)	/* do not clear the lower xino,
+					   for link(2) */
 #define au_ftest_cpup(flags, name)	((flags) & AuCpup_##name)
 #define au_fset_cpup(flags, name)	{ (flags) |= AuCpup_##name; }
 #define au_fclr_cpup(flags, name)	{ (flags) &= ~AuCpup_##name; }
 
-int au_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
-		   aufs_bindex_t bsrc, loff_t len, unsigned int flags);
 int au_sio_cpup_single(struct dentry *dentry, aufs_bindex_t bdst,
-		       aufs_bindex_t bsrc, loff_t len, unsigned int flags);
-int au_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
-		   unsigned int flags);
+		       aufs_bindex_t bsrc, loff_t len, unsigned int flags,
+		       struct dentry *dst_parent);
 int au_sio_cpup_simple(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 		       unsigned int flags);
-int au_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
-	       struct file *file);
 int au_sio_cpup_wh(struct dentry *dentry, aufs_bindex_t bdst, loff_t len,
 		   struct file *file);
 
-int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst, struct dentry *locked,
+int au_cp_dirs(struct dentry *dentry, aufs_bindex_t bdst,
 	       int (*cp)(struct dentry *dentry, aufs_bindex_t bdst,
 			 struct dentry *h_parent, void *arg),
 	       void *arg);
-int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
-		 struct dentry *locked);
-int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst,
-			  struct dentry *locked);
+int au_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst);
+int au_test_and_cpup_dirs(struct dentry *dentry, aufs_bindex_t bdst);
 
 /* ---------------------------------------------------------------------- */
 
 /* keep timestamps when copyup */
 struct au_dtime {
 	struct dentry *dt_dentry, *dt_h_dentry;
-	struct au_hinode *dt_hdir;
+	struct au_hinode *dt_hinode, *dt_hdir;
 	struct timespec dt_atime, dt_mtime;
 };
 void au_dtime_store(struct au_dtime *dt, struct dentry *dentry,
-		    struct dentry *h_dentry, struct au_hinode *hdir);
+		    struct dentry *h_dentry, struct au_hinode *hinode,
+		    struct au_hinode *hdir);
 void au_dtime_revert(struct au_dtime *dt);
 
 #endif /* __KERNEL__ */
diff --git a/ubuntu/aufs/dcsub.c b/ubuntu/aufs/dcsub.c
index 049e9cb..275aedf 100644
--- a/ubuntu/aufs/dcsub.c
+++ b/ubuntu/aufs/dcsub.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * sub-routines for dentry cache
  *
- * $Id: dcsub.c,v 1.4 2008/05/26 04:04:22 sfjro Exp $
+ * $Id: dcsub.c,v 1.7 2008/07/21 02:54:22 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -211,36 +211,41 @@ int au_dcsub_pages_rev(struct au_dcsub_pages *dpages, struct dentry *dentry,
 	return err;
 }
 
-int au_test_subdir(struct dentry *d1, struct dentry *d2)
+struct dentry *au_test_subdir(struct dentry *d1, struct dentry *d2)
 {
-	int err;
-	int i, j;
+	struct dentry *trap, **dentries;
+	int err, i, j;
 	struct au_dcsub_pages dpages;
 	struct au_dpage *dpage;
-	struct dentry **dentries;
 
 	LKTRTrace("%.*s, %.*s\n", AuDLNPair(d1), AuDLNPair(d2));
 
-	err = au_dpages_init(&dpages, GFP_TEMPORARY);
+	trap = ERR_PTR(-ENOMEM);
+	err = au_dpages_init(&dpages, GFP_NOFS);
 	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--) {
+	trap = d1;
+	for (i = 0; !err && i < dpages.ndpage; i++) {
 		dpage = dpages.dpages + i;
 		dentries = dpage->dentries;
-		for (j = dpage->ndentry - 1; !err && j >= 0; j--) {
+		for (j = 0; !err && j < dpage->ndentry; j++) {
 			struct dentry *d;
 			d = dentries[j];
 			err = (d == d2);
+			if (!err)
+				trap = d;
 		}
 	}
+	if (!err)
+		trap = NULL;
 
  out_dpages:
 	au_dpages_free(&dpages);
  out:
-	AuTraceErr(err);
-	return err;
+	AuTraceErrPtr(trap);
+	return trap;
 }
diff --git a/ubuntu/aufs/dcsub.h b/ubuntu/aufs/dcsub.h
index e012b94..49e7139 100644
--- a/ubuntu/aufs/dcsub.h
+++ b/ubuntu/aufs/dcsub.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * sub-routines for dentry cache
  *
- * $Id: dcsub.h,v 1.1 2008/04/18 12:18:29 sfjro Exp $
+ * $Id: dcsub.h,v 1.4 2008/07/21 02:54:22 sfjro Exp $
  */
 
 #ifndef __AUFS_DCSUB_H__
@@ -48,7 +48,7 @@ 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_test_subdir(struct dentry *d1, struct dentry *d2);
+struct dentry *au_test_subdir(struct dentry *d1, struct dentry *d2);
 
 #endif /* __KERNEL__ */
 #endif /* __AUFS_DCSUB_H__ */
diff --git a/ubuntu/aufs/debug.c b/ubuntu/aufs/debug.c
index efa5614..e5fa4fe 100644
--- a/ubuntu/aufs/debug.c
+++ b/ubuntu/aufs/debug.c
@@ -19,13 +19,11 @@
 /*
  * debug print functions
  *
- * $Id: debug.c,v 1.4 2008/06/02 02:37:28 sfjro Exp $
+ * $Id: debug.c,v 1.14 2008/09/22 03:52:03 sfjro Exp $
  */
 
 #include "aufs.h"
 
-#ifdef CONFIG_AUFS_DEBUG
-
 atomic_t au_cond = ATOMIC_INIT(0);
 
 char *au_plevel = KERN_DEBUG;
@@ -100,8 +98,8 @@ static int do_pri_inode(aufs_bindex_t bindex, struct inode *inode,
 	     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,
+	     i_size_read(inode), (unsigned long long)inode->i_blocks,
+	     (long long)timespec_to_ns(&inode->i_ctime) & 0x0ffff,
 	     inode->i_mapping ? inode->i_mapping->nrpages : 0,
 	     inode->i_state, inode->i_flags, inode->i_generation,
 	     l ? ", wh " : "", l, n);
@@ -140,7 +138,7 @@ static int do_pri_dentry(aufs_bindex_t bindex, struct dentry *dentry,
 		return -1;
 	}
 	/* do not call dget_parent() here */
-	dpri("d%d: %.*s/%.*s, %s, cnt %d, flags 0x%x, intent %d\n",
+	dpri("d%d: %.*s?/%.*s, %s, cnt %d, flags 0x%x, intent %d\n",
 	     bindex,
 	     AuDLNPair(dentry->d_parent), AuDLNPair(dentry),
 	     dentry->d_sb ? au_sbtype(dentry->d_sb) : "??",
@@ -201,8 +199,8 @@ static int do_pri_file(aufs_bindex_t bindex, struct file *file)
 	    && au_test_aufs(file->f_dentry->d_sb)
 	    && au_fi(file))
 		snprintf(a, sizeof(a), ", mmapped %d", au_test_mmapped(file));
-	dpri("f%d: mode 0x%x, flags 0%o, cnt %d, pos %llu%s\n",
-	     bindex, file->f_mode, file->f_flags, file_count(file),
+	dpri("f%d: mode 0x%x, flags 0%o, cnt %ld, pos %llu%s\n",
+	     bindex, file->f_mode, file->f_flags, (long)file_count(file),
 	     file->f_pos, a);
 	if (file->f_dentry)
 		do_pri_dentry(bindex, file->f_dentry, NULL);
@@ -243,11 +241,13 @@ static int do_pri_br(aufs_bindex_t bindex, struct au_branch *br)
 		return -1;
 	}
 
-	dpri("s%d: {perm 0x%x, cnt %d}, "
-	     "%s, flags 0x%lx, cnt(BIAS) %d, active %d, xino %d\n",
-	     bindex, br->br_perm, au_br_count(br),
-	     au_sbtype(sb), sb->s_flags, sb->s_count - S_BIAS,
-	     atomic_read(&sb->s_active), !!br->br_xino);
+	dpri("s%d: {perm 0x%x, cnt %d, wbr %p}, "
+	     "%s, dev 0x%02x%02x, flags 0x%lx, cnt(BIAS) %d, active %d, "
+	     "xino %d\n",
+	     bindex, br->br_perm, au_br_count(br), br->br_wbr,
+	     au_sbtype(sb), MAJOR(sb->s_dev), MINOR(sb->s_dev),
+	     sb->s_flags, sb->s_count - S_BIAS,
+	     atomic_read(&sb->s_active), !!br->br_xino.xi_file);
 	return 0;
 }
 
@@ -256,17 +256,27 @@ void au_dpri_sb(struct super_block *sb)
 	struct au_sbinfo *sbinfo;
 	aufs_bindex_t bindex;
 	int err;
-	struct vfsmount mnt = { .mnt_sb = sb };
-	struct au_branch fake = {
-		.br_perm = 0,
-		.br_mnt = &mnt,
-		.br_count = ATOMIC_INIT(0),
-		.br_xino = NULL
-	};
-
-	atomic_set(&fake.br_count, 0);
+	/* to reuduce stack size */
+	struct {
+		struct vfsmount mnt;
+		struct au_branch fake;
+	} *a;
+
+	/* this function can be called from magic sysrq */
+	a = kzalloc(sizeof(*a), GFP_ATOMIC);
+	if (unlikely(!a)) {
+		dpri("no memory\n");
+		return;
+	}
+
+	a->mnt.mnt_sb = sb;
+	a->fake.br_perm = 0;
+	a->fake.br_mnt = &a->mnt;
+	a->fake.br_xino.xi_file = NULL;
+	atomic_set(&a->fake.br_count, 0);
 	smp_mb(); /* atomic_set */
-	err = do_pri_br(-1, &fake);
+	err = do_pri_br(-1, &a->fake);
+	kfree(a);
 	dpri("dev 0x%x\n", sb->s_dev);
 	if (err || !au_test_aufs(sb))
 		return;
@@ -274,6 +284,7 @@ void au_dpri_sb(struct super_block *sb)
 	sbinfo = au_sbi(sb);
 	if (!sbinfo)
 		return;
+	dpri("gen %u\n", sbinfo->si_generation);
 	for (bindex = 0; bindex <= sbinfo->si_bend; bindex++)
 		do_pri_br(bindex, sbinfo->si_branch[0 + bindex]);
 }
@@ -286,6 +297,36 @@ void au_dbg_sleep(int sec)
 	wait_event_timeout(wq, 0, sec * HZ);
 }
 
+void au_dbg_sleep_jiffy(int jiffy)
+{
+	static DECLARE_WAIT_QUEUE_HEAD(wq);
+	wait_event_timeout(wq, 0, jiffy);
+}
+
+void au_dbg_iattr(struct iattr *ia)
+{
+#define AuBit(name)	if (ia->ia_valid & ATTR_ ## name) dpri(#name "\n")
+	AuBit(MODE);
+	AuBit(UID);
+	AuBit(GID);
+	AuBit(SIZE);
+	AuBit(ATIME);
+	AuBit(MTIME);
+	AuBit(CTIME);
+	AuBit(ATIME_SET);
+	AuBit(MTIME_SET);
+	AuBit(FORCE);
+	AuBit(ATTR_FLAG);
+	AuBit(KILL_SUID);
+	AuBit(KILL_SGID);
+	AuBit(FILE);
+	AuBit(KILL_PRIV);
+	AuBit(OPEN);
+	AuBit(TIMES_SET);
+#undef	AuBit
+	dpri("ia_file %p\n", ia->ia_file);
+}
+
 /* ---------------------------------------------------------------------- */
 
 void au_debug_sbinfo_init(struct au_sbinfo *sbinfo)
@@ -305,6 +346,9 @@ void au_debug_sbinfo_init(struct au_sbinfo *sbinfo)
 #ifdef ForceNoRefrof
 	au_opt_clr(sbinfo->si_mntflags, REFROF);
 #endif
+#ifdef ForceShwh
+	au_opt_set(sbinfo->si_mntflags, SHWH);
+#endif
 }
 
 int __init au_debug_init(void)
@@ -322,6 +366,10 @@ int __init au_debug_init(void)
 	AuWarn("CONFIG_4KSTACKS is defined.\n");
 #endif
 
+#ifdef ForceBrs
+	sysaufs_brs = 1;
+#endif
+
 #if 0 /* verbose debug */
 	{
 		union {
@@ -340,9 +388,8 @@ int __init au_debug_init(void)
 		pr_info("br{"
 			"xino %d, "
 			"id %d, perm %d, mnt %d, count %d, "
-			"wh_sem %d, wh %d, run %d, plink %d, "
+			"wbr %d, "
 			"xup %d, xrun %d, "
-			"by %d, "
 			"gen %d, "
 			"sa %d} %d\n",
 			offsetof(typeof(*u.br), br_xino),
@@ -350,13 +397,9 @@ int __init au_debug_init(void)
 			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),
-			offsetof(typeof(*u.br), br_plink),
+			offsetof(typeof(*u.br), wbr),
 			offsetof(typeof(*u.br), br_xino_upper),
 			offsetof(typeof(*u.br), br_xino_running),
-			offsetof(typeof(*u.br), br_bytes),
 			offsetof(typeof(*u.br), br_generation),
 			offsetof(typeof(*u.br), br_sabr),
 			sizeof(*u.br));
@@ -467,5 +510,3 @@ int __init au_debug_init(void)
 
 	return 0;
 }
-
-#endif /* CONFIG_AUFS_DEBUG */
diff --git a/ubuntu/aufs/debug.h b/ubuntu/aufs/debug.h
index 5e723a0..eca68ed 100644
--- a/ubuntu/aufs/debug.h
+++ b/ubuntu/aufs/debug.h
@@ -19,7 +19,7 @@
 /*
  * debug print functions
  *
- * $Id: debug.h,v 1.2 2008/04/21 02:00:37 sfjro Exp $
+ * $Id: debug.h,v 1.8 2008/09/22 03:52:03 sfjro Exp $
  */
 
 #ifndef __AUFS_DEBUG_H__
@@ -28,6 +28,9 @@
 #ifdef __KERNEL__
 
 #include <linux/fs.h>
+#include <linux/kd.h>
+#include <linux/vt_kern.h>
+#include <linux/sysrq.h>
 
 /* to debug easier, do not make it an inlined function */
 #define MtxMustLock(mtx)	AuDebugOn(!mutex_is_locked(mtx))
@@ -135,6 +138,11 @@ 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);
+void au_dbg_sleep_jiffy(int jiffy);
+#ifndef ATTR_TIMES_SET
+#define ATTR_TIMES_SET 0
+#endif
+void au_dbg_iattr(struct iattr *ia);
 int __init au_debug_init(void);
 void au_debug_sbinfo_init(struct au_sbinfo *sbinfo);
 #define AuDbgWhlist(w) do { \
@@ -171,6 +179,16 @@ void au_debug_sbinfo_init(struct au_sbinfo *sbinfo);
 	AuDbg("sleep %d sec\n", sec); \
 	au_dbg_sleep(sec); \
 } while (0)
+
+#define AuDbgSleepJiffy(jiffy) do { \
+	AuDbg("sleep %d jiffies\n", jiffy); \
+	au_dbg_sleep_jiffy(jiffy); \
+} while (0)
+
+#define AuDbgIAttr(ia) do { \
+	AuDbg("ia_valid 0x%x\n", (ia)->ia_valid); \
+	au_dbg_iattr(ia); \
+} while (0)
 #else
 static inline int au_debug_init(void)
 {
@@ -187,6 +205,8 @@ static inline void au_debug_sbinfo_init(struct au_sbinfo *sbinfo)
 #define AuDbgFile(f)		do {} while (0)
 #define AuDbgSb(sb)		do {} while (0)
 #define AuDbgSleep(sec)		do {} while (0)
+#define AuDbgSleepJiffy(jiffy)	do {} while (0)
+#define AuDbgIAttr(ia)		do {} while (0)
 #endif /* CONFIG_AUFS_DEBUG */
 
 #ifdef DbgUdbaRace
@@ -198,13 +218,24 @@ static inline void au_debug_sbinfo_init(struct au_sbinfo *sbinfo)
 #ifdef CONFIG_AUFS_MAGIC_SYSRQ
 int __init au_sysrq_init(void);
 void au_sysrq_fin(void);
+
+#ifdef CONFIG_HW_CONSOLE
+#define au_dbg_blocked() do { \
+	WARN_ON(1); \
+	handle_sysrq('w', vc_cons[fg_console].d->vc_tty); \
+} while (0)
+#else
+#define au_dbg_blocked()	do {} while (0)
+#endif
+
 #else
 static inline int au_sysrq_init(void)
 {
 	return 0;
 }
 #define au_sysrq_fin()		do {} while (0)
-#endif
+#define au_dbg_blocked()	do {} while (0)
+#endif /* CONFIG_AUFS_MAGIC_SYSRQ */
 
 #endif /* __KERNEL__ */
 #endif /* __AUFS_DEBUG_H__ */
diff --git a/ubuntu/aufs/dentry.c b/ubuntu/aufs/dentry.c
index 466cd31..4139f2e 100644
--- a/ubuntu/aufs/dentry.c
+++ b/ubuntu/aufs/dentry.c
@@ -19,7 +19,7 @@
 /*
  * lookup and dentry operations
  *
- * $Id: dentry.c,v 1.8 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: dentry.c,v 1.15 2008/09/22 03:52:06 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -90,13 +90,14 @@ struct au_do_lookup_args {
  * returns positive/negative dentry, NULL or an error.
  * NULL means whiteout-ed or not-found.
  */
-static noinline_for_stack
+static /* noinline_for_stack */
 struct dentry *au_do_lookup(struct dentry *h_parent, struct dentry *dentry,
 			    aufs_bindex_t bindex, struct qstr *wh_name,
 			    struct au_do_lookup_args *args)
 {
 	struct dentry *h_dentry;
-	int wh_found, wh_able, opq, err;
+	int err, wh_found, opq;
+	unsigned char wh_able;
 	struct inode *h_dir, *h_inode, *inode;
 	struct qstr *name;
 	struct super_block *sb;
@@ -125,7 +126,7 @@ struct dentry *au_do_lookup(struct dentry *h_parent, struct dentry *dentry,
 		au_fset_ndx(ndx.flags, DIRPERM1);
 	LKTRTrace("nfsmnt %p\n", ndx.nfsmnt);
 	ndx.br = au_sbr(sb, bindex);
-	wh_able = au_br_whable(ndx.br->br_perm);
+	wh_able = !!au_br_whable(ndx.br->br_perm);
 	name = &dentry->d_name;
 	if (unlikely(wh_able))
 		wh_found = au_test_robr_wh(name, h_parent, wh_name,
@@ -208,18 +209,19 @@ struct dentry *au_do_lookup(struct dentry *h_parent, struct dentry *dentry,
 int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type,
 		   struct nameidata *nd)
 {
-	int npositive, err, isdir;
-	struct dentry *parent;
+	int npositive, err;
+	unsigned int mnt_flags;
 	aufs_bindex_t bindex, btail, bdiropq;
-	const struct qstr *name = &dentry->d_name;
+	unsigned char isdir;
 	struct qstr whname;
-	struct super_block *sb;
-	unsigned int mnt_flags;
-	struct inode *inode;
 	struct au_do_lookup_args args = {
 		.type	= type,
 		.nd	= nd
 	};
+	const struct qstr *name = &dentry->d_name;
+	struct dentry *parent;
+	struct super_block *sb;
+	struct inode *inode;
 
 	LKTRTrace("%.*s, b%d, type 0%o\n", AuLNPair(name), bstart, type);
 	AuDebugOn(bstart < 0 || IS_ROOT(dentry));
@@ -238,7 +240,7 @@ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type,
 	sb = dentry->d_sb;
 	mnt_flags = au_mntflags(sb);
 	inode = dentry->d_inode;
-	isdir = (inode && S_ISDIR(inode->i_mode));
+	isdir = !!(inode && S_ISDIR(inode->i_mode));
 	args.flags = 0;
 	if (unlikely(au_test_dlgt(mnt_flags)))
 		au_fset_lkup(args.flags, DLGT);
@@ -301,6 +303,10 @@ int au_lkup_dentry(struct dentry *dentry, aufs_bindex_t bstart, mode_t type,
 		au_update_dbstart(dentry);
 	}
 	err = npositive;
+	if (unlikely(!au_opt_test(mnt_flags, UDBA_NONE)
+		     && au_dbstart(dentry) < 0))
+		/* both of real entry and whiteout found */
+		err = -EIO;
 
  out_wh:
 	au_wh_name_free(&whname);
@@ -430,7 +436,7 @@ int au_refresh_hdentry(struct dentry *dentry, mode_t type)
 	new_sz = sizeof(*dinfo->di_hdentry) * (au_sbend(sb) + 1);
 	dinfo = au_di(dentry);
 	p = au_kzrealloc(dinfo->di_hdentry, sizeof(*p) * (dinfo->di_bend + 1),
-			 new_sz, GFP_KERNEL);
+			 new_sz, GFP_NOFS);
 	if (unlikely(!p))
 		goto out;
 	dinfo->di_hdentry = p;
@@ -535,9 +541,9 @@ static void au_unlock_nd(int locked, struct nameidata *nd)
 }
 
 /* #define TestingFuse */
-static noinline_for_stack int
-au_do_h_d_reval(struct dentry *dentry, aufs_bindex_t bindex,
-		struct nameidata *nd, struct dentry *h_dentry)
+static noinline_for_stack
+int au_do_h_d_reval(struct dentry *dentry, aufs_bindex_t bindex,
+		    struct nameidata *nd, struct dentry *h_dentry)
 {
 	int err, valid, e;
 	int (*reval)(struct dentry *, struct nameidata *);
@@ -568,7 +574,25 @@ au_do_h_d_reval(struct dentry *dentry, aufs_bindex_t bindex,
 	LKTRTrace("b%d\n", bindex);
 
 	/* it may return tri-state */
+#if 1
 	valid = reval(h_dentry, p);
+#else
+	if (p /*&& !IS_ROOT(h_dentry)*/) {
+		struct dentry *h_parent = dget_parent(h_dentry);
+		struct mutex *h_mtx = &h_parent->d_inode->i_mutex;
+#if 0
+		lktr_set_pid(current->pid, LktrArrayPid);
+		AuDbgDentry(p->path.dentry);
+		AuDbgDentry(h_dentry);
+		lktr_clear_pid(current->pid, LktrArrayPid);
+#endif
+		mutex_lock_nested(h_mtx, AuLsc_I_PARENT);
+		valid = reval(h_dentry, p);
+		mutex_unlock(h_mtx);
+		dput(h_parent);
+	} else
+		valid = reval(h_dentry, p);
+#endif
 	if (unlikely(valid < 0))
 		err = valid;
 	else if (!valid)
@@ -596,12 +620,13 @@ au_do_h_d_reval(struct dentry *dentry, aufs_bindex_t bindex,
 	return err;
 }
 
-static noinline_for_stack int
-h_d_revalidate(struct dentry *dentry, struct inode *inode,
-	       struct nameidata *nd, int do_udba)
+static noinline_for_stack
+int h_d_revalidate(struct dentry *dentry, struct inode *inode,
+		   struct nameidata *nd, int do_udba)
 {
-	int err, plus, locked, unhashed, is_root, h_plus;
+	int err;
 	aufs_bindex_t bindex, btail, bstart, ibs, ibe;
+	unsigned char plus, locked, unhashed, is_root, h_plus;
 	struct super_block *sb;
 	struct inode *first, *h_inode, *h_cached_inode;
 	umode_t mode, h_mode;
@@ -618,8 +643,8 @@ h_d_revalidate(struct dentry *dentry, struct inode *inode,
 	first = NULL;
 	ibs = -1;
 	ibe = -1;
-	unhashed = d_unhashed(dentry);
-	is_root = IS_ROOT(dentry);
+	unhashed = !!d_unhashed(dentry);
+	is_root = !!IS_ROOT(dentry);
 	name = &dentry->d_name;
 
 	/*
@@ -641,7 +666,7 @@ h_d_revalidate(struct dentry *dentry, struct inode *inode,
 	btail = bstart;
 	if (inode && S_ISDIR(inode->i_mode))
 		btail = au_dbtaildir(dentry);
-	locked = au_lock_nd(dentry, nd);
+	locked = !!au_lock_nd(dentry, nd);
 	for (bindex = bstart; bindex <= btail; bindex++) {
 		h_dentry = au_h_dptr(dentry, bindex);
 		if (!h_dentry)
@@ -656,7 +681,7 @@ h_d_revalidate(struct dentry *dentry, struct inode *inode,
 
 		if (unlikely(do_udba
 			     && !is_root
-			     && (unhashed != d_unhashed(h_dentry)
+			     && (unhashed != !!d_unhashed(h_dentry)
 				 || name->len != h_dentry->d_name.len
 				 || memcmp(name->name, h_dentry->d_name.name,
 					   name->len)
@@ -672,6 +697,7 @@ h_d_revalidate(struct dentry *dentry, struct inode *inode,
 			/* do not goto err, to keep the errno */
 			break;
 
+		/* todo: plink too? */
 		if (unlikely(!do_udba))
 			continue;
 
@@ -690,9 +716,9 @@ h_d_revalidate(struct dentry *dentry, struct inode *inode,
 		if (inode && ibs <= bindex && bindex <= ibe)
 			h_cached_inode = au_h_iptr(inode, bindex);
 
-		LKTRTrace("{%d, 0%o, %d}, h{%d, 0%o, %d}\n",
-			  plus, mode, !!h_cached_inode,
-			  h_plus, h_mode, !!h_inode);
+		LKTRTrace("{%d, 0%o, %p}, h{%d, 0%o, %p}\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))
@@ -717,8 +743,7 @@ h_d_revalidate(struct dentry *dentry, struct inode *inode,
 	return err;
 }
 
-static noinline_for_stack int
-simple_reval_dpath(struct dentry *dentry, au_gen_t sgen)
+static int simple_reval_dpath(struct dentry *dentry, au_gen_t sgen)
 {
 	int err;
 	mode_t type;
@@ -745,7 +770,7 @@ simple_reval_dpath(struct dentry *dentry, au_gen_t sgen)
 		struct au_dpage *dpage;
 		struct dentry **dentries;
 
-		err = au_dpages_init(&dpages, GFP_TEMPORARY);
+		err = au_dpages_init(&dpages, GFP_NOFS);
 		AuDebugOn(err);
 		err = au_dcsub_pages_rev(&dpages, parent, /*do_include*/1, NULL,
 					 NULL);
@@ -829,11 +854,12 @@ int au_reval_dpath(struct dentry *dentry, au_gen_t sgen)
  */
 static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
 {
-	int valid, err, do_udba;
-	struct super_block *sb;
+	int valid, err;
 	au_gen_t sgen;
-	struct inode *inode;
+	unsigned char do_udba;
 	struct nameidata tmp_nd, *ndp;
+	struct super_block *sb;
+	struct inode *inode;
 
 	LKTRTrace("dentry %.*s\n", AuDLNPair(dentry));
 	if (nd && nd->path.dentry)
@@ -847,45 +873,62 @@ static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
 	AuDebugOn(!dentry->d_fsdata);
 
 	err = -EINVAL;
-	inode = dentry->d_inode;
+#if 0
+	if (d_unhashed(dentry))
+		goto __out;
+#endif
 	sb = dentry->d_sb;
+#if 1
+	aufs_read_lock(dentry, AuLock_FLUSH | AuLock_DW);
+	inode = dentry->d_inode;
+#else
 	si_read_lock(sb, AuLock_FLUSH);
+	//lktr_set_pid(current->pid, LktrArrayPid);
+	AuDbgDentry(dentry);
+	AuDbgInode(dentry->d_inode);
+	au_rw_write_lock_nested(&au_di(dentry)->di_rwsem, AuLsc_DI_CHILD);
+	LKTRLabel(clean);
+	//lktr_clear_pid(current->pid, LktrArrayPid);
+	inode = dentry->d_inode;
+	if (unlikely(inode && (!inode->i_nlink || IS_DEADDIR(inode)))) {
+		AuDbg("here\n");
+		au_rw_write_unlock(&au_di(dentry)->di_rwsem);
+		si_read_unlock(sb);
+		goto __out;
+	}
+	if (inode) {
+		//lktr_set_pid(current->pid, LktrArrayPid);
+		AuDbgDentry(dentry);
+		AuDbgInode(inode);
+		ii_write_lock_child(inode);
+		LKTRLabel(clean);
+		//lktr_clear_pid(current->pid, LktrArrayPid);
+	}
+#endif
 
 	sgen = au_sigen(sb);
-	if (au_digen(dentry) == sgen)
-		di_read_lock_child(dentry, !AuLock_IR);
-	else {
+	if (unlikely(au_digen(dentry) != sgen)) {
 		AuDebugOn(IS_ROOT(dentry));
 #ifdef ForceInotify
 		AuDbg("UDBA or digen, %.*s\n", AuDLNPair(dentry));
 #endif
-		di_write_lock_child(dentry);
 		if (inode)
 			err = au_reval_dpath(dentry, sgen);
-		di_downgrade_lock(dentry, AuLock_IR);
 		if (unlikely(err))
-			goto out;
-		if (inode)
-			ii_read_unlock(inode);
+			goto out_dgrade;
 		AuDebugOn(au_digen(dentry) != sgen);
 	}
-
-	if (inode) {
-		if (au_iigen(inode) == sgen)
-			ii_read_lock_child(inode);
-		else {
-			AuDebugOn(IS_ROOT(dentry));
+	if (unlikely(inode && au_iigen(inode) != sgen)) {
+		AuDebugOn(IS_ROOT(dentry));
 #ifdef ForceInotify
-			AuDbg("UDBA or survived, %.*s\n", AuDLNPair(dentry));
+		AuDbg("UDBA or survived, %.*s\n", AuDLNPair(dentry));
 #endif
-			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);
-		}
+		err = au_refresh_hinode(inode, dentry);
+		if (unlikely(err))
+			goto out_dgrade;
+		AuDebugOn(au_iigen(inode) != sgen);
 	}
+	di_downgrade_lock(dentry, AuLock_IR);
 
 #if 0 /* todo: support it? */
 	/* parent dir i_nlink is not updated in the case of setattr */
@@ -909,15 +952,31 @@ static int aufs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
 			goto out;
 	}
 	ndp = au_dup_nd(au_sbi(sb), &tmp_nd, nd);
+#if 0
+	if (nd) {
+		path = nd->path;
+		nd->path.dentry = au_h_dptr(nd->path.dentry, bindex);
+	if (fake_nd->path.dentry) {
+		fake_nd->path.mnt = au_sbr_mnt(sb, bindex);
+		AuDebugOn(!fake_nd->path.mnt);
+		path_get(&fake_nd->path);
+		nd->path.
+			}
+	}
+#endif
 	err = h_d_revalidate(dentry, inode, ndp, do_udba);
+	if (unlikely(!err && do_udba && au_dbstart(dentry) < 0))
+		/* both of real entry and whiteout found */
+		err = -EIO;
+	goto out;
 
+ out_dgrade:
+	di_downgrade_lock(dentry, AuLock_IR);
  out:
 	au_store_fmode_exec(nd, inode);
 
-	if (inode)
-		ii_read_unlock(inode);
-	di_read_unlock(dentry, !AuLock_IR);
-	si_read_unlock(sb);
+	aufs_read_unlock(dentry, AuLock_IR);
+	//__out:
 	AuTraceErr(err);
 	valid = !err;
 	if (!valid)
@@ -952,6 +1011,9 @@ static void aufs_d_release(struct dentry *dentry)
 	}
 	kfree(dinfo->di_hdentry);
 	au_cache_free_dinfo(dinfo);
+	au_hin_di_reinit(dentry);
+	/* todo: tmp code */
+	//dentry->d_fsdata = NULL;
 }
 
 struct dentry_operations aufs_dop = {
diff --git a/ubuntu/aufs/dentry.h b/ubuntu/aufs/dentry.h
index c97b3d5..43a5b98 100644
--- a/ubuntu/aufs/dentry.h
+++ b/ubuntu/aufs/dentry.h
@@ -19,7 +19,7 @@
 /*
  * lookup and dentry operations
  *
- * $Id: dentry.h,v 1.5 2008/05/26 04:04:23 sfjro Exp $
+ * $Id: dentry.h,v 1.7 2008/09/01 02:54:54 sfjro Exp $
  */
 
 #ifndef __AUFS_DENTRY_H__
@@ -29,7 +29,7 @@
 
 #include <linux/fs.h>
 #include <linux/namei.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "misc.h"
 #include "super.h"
 #include "vfsub.h"
@@ -249,6 +249,16 @@ static inline au_gen_t au_digen_dec(struct dentry *d)
 {
 	return atomic_dec_return(&au_di(d)->di_generation);
 }
+
+static inline void au_hin_di_reinit(struct dentry *d)
+{
+	d->d_fsdata = NULL;
+}
+#else
+static inline void au_hin_di_reinit(struct dentry *d)
+{
+	/* empty */
+}
 #endif /* CONFIG_AUFS_HINOTIFY */
 
 /* ---------------------------------------------------------------------- */
@@ -260,7 +270,8 @@ enum {
 	AuLsc_DI_CHILD3,	/* copyup dirs */
 	AuLsc_DI_PARENT,
 	AuLsc_DI_PARENT2,
-	AuLsc_DI_PARENT3
+	AuLsc_DI_PARENT3,
+	AuLsc_DI_PARENT4
 };
 
 /*
@@ -270,6 +281,7 @@ enum {
  * di_read_lock_parent, di_write_lock_parent,
  * di_read_lock_parent2, di_write_lock_parent2,
  * di_read_lock_parent3, di_write_lock_parent3,
+ * di_read_lock_parent4, di_write_lock_parent4,
  */
 #define AuReadLockFunc(name, lsc) \
 static inline void di_read_lock_##name(struct dentry *d, int flags) \
@@ -289,6 +301,7 @@ AuRWLockFuncs(child3, CHILD3);
 AuRWLockFuncs(parent, PARENT);
 AuRWLockFuncs(parent2, PARENT2);
 AuRWLockFuncs(parent3, PARENT3);
+AuRWLockFuncs(parent4, PARENT4);
 
 #undef AuReadLockFunc
 #undef AuWriteLockFunc
diff --git a/ubuntu/aufs/dinfo.c b/ubuntu/aufs/dinfo.c
index 2985b2f..7857d8a 100644
--- a/ubuntu/aufs/dinfo.c
+++ b/ubuntu/aufs/dinfo.c
@@ -19,7 +19,7 @@
 /*
  * dentry private data
  *
- * $Id: dinfo.c,v 1.4 2008/05/26 04:04:23 sfjro Exp $
+ * $Id: dinfo.c,v 1.7 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -40,13 +40,13 @@ int au_alloc_dinfo(struct dentry *dentry)
 		if (unlikely(nbr <= 0))
 			nbr = 1;
 		dinfo->di_hdentry = kcalloc(nbr, sizeof(*dinfo->di_hdentry),
-					    GFP_KERNEL);
+					    GFP_NOFS);
 		if (dinfo->di_hdentry) {
 			au_h_dentry_init_all(dinfo->di_hdentry, nbr);
 			atomic_set(&dinfo->di_generation, au_sigen(sb));
 			/* smp_mb(); */ /* atomic_set */
 			au_rw_init_wlock_nested(&dinfo->di_rwsem,
-						AuLsc_DI_PARENT);
+						AuLsc_DI_CHILD);
 			dinfo->di_bstart = -1;
 			dinfo->di_bend = -1;
 			dinfo->di_bwh = -1;
@@ -100,6 +100,9 @@ static void do_ii_write_lock(struct inode *inode, unsigned int lsc)
 	case AuLsc_DI_PARENT3:
 		ii_write_lock_parent3(inode);
 		break;
+	case AuLsc_DI_PARENT4:
+		ii_write_lock_parent4(inode);
+		break;
 	default:
 		BUG();
 	}
@@ -126,6 +129,9 @@ static void do_ii_read_lock(struct inode *inode, unsigned int lsc)
 	case AuLsc_DI_PARENT3:
 		ii_read_lock_parent3(inode);
 		break;
+	case AuLsc_DI_PARENT4:
+		ii_read_lock_parent4(inode);
+		break;
 	default:
 		BUG();
 	}
@@ -133,7 +139,9 @@ static void do_ii_read_lock(struct inode *inode, unsigned int lsc)
 
 void di_read_lock(struct dentry *d, int flags, unsigned int lsc)
 {
+	LKTRTrace("%.*s, %u\n", AuDLNPair(d), lsc);
 	SiMustAnyLock(d->d_sb);
+
 	/* todo: always nested? */
 	au_rw_read_lock_nested(&au_di(d)->di_rwsem, lsc);
 	if (d->d_inode) {
@@ -146,7 +154,9 @@ void di_read_lock(struct dentry *d, int flags, unsigned int lsc)
 
 void di_read_unlock(struct dentry *d, int flags)
 {
+	LKTRTrace("%.*s\n", AuDLNPair(d));
 	SiMustAnyLock(d->d_sb);
+
 	if (d->d_inode) {
 		if (au_ftest_lock(flags, IW))
 			ii_write_unlock(d->d_inode);
@@ -159,6 +169,7 @@ void di_read_unlock(struct dentry *d, int flags)
 void di_downgrade_lock(struct dentry *d, int flags)
 {
 	SiMustAnyLock(d->d_sb);
+
 	au_rw_dgrade_lock(&au_di(d)->di_rwsem);
 	if (d->d_inode && au_ftest_lock(flags, IR))
 		ii_downgrade_lock(d->d_inode);
@@ -166,7 +177,9 @@ void di_downgrade_lock(struct dentry *d, int flags)
 
 void di_write_lock(struct dentry *d, unsigned int lsc)
 {
+	LKTRTrace("%.*s, %u\n", AuDLNPair(d), lsc);
 	SiMustAnyLock(d->d_sb);
+
 	/* todo: always nested? */
 	au_rw_write_lock_nested(&au_di(d)->di_rwsem, lsc);
 	if (d->d_inode)
@@ -175,7 +188,9 @@ void di_write_lock(struct dentry *d, unsigned int lsc)
 
 void di_write_unlock(struct dentry *d)
 {
+	LKTRTrace("%.*s\n", AuDLNPair(d));
 	SiMustAnyLock(d->d_sb);
+
 	if (d->d_inode)
 		ii_write_unlock(d->d_inode);
 	au_rw_write_unlock(&au_di(d)->di_rwsem);
diff --git a/ubuntu/aufs/dir.c b/ubuntu/aufs/dir.c
index a7694d1..7300012 100644
--- a/ubuntu/aufs/dir.c
+++ b/ubuntu/aufs/dir.c
@@ -19,7 +19,7 @@
 /*
  * directory operations
  *
- * $Id: dir.c,v 1.6 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: dir.c,v 1.13 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include <linux/fs_stack.h>
@@ -100,11 +100,11 @@ static int do_open_dir(struct file *file, int flags)
 			continue;
 
 		h_file = au_h_open(dentry, bindex, flags, file);
-		if (!IS_ERR(h_file)) {
-			au_set_h_fptr(file, bindex, h_file);
-			continue;
+		if (IS_ERR(h_file)) {
+			err = PTR_ERR(h_file);
+			break;
 		}
-		err = PTR_ERR(h_file);
+		au_set_h_fptr(file, bindex, h_file);
 	}
 	au_update_figen(file);
 	/* todo: necessary? */
@@ -113,7 +113,7 @@ static int do_open_dir(struct file *file, int flags)
 		return 0; /* success */
 
 	/* close all */
-	for (bindex = au_fbstart(file); !err && bindex <= btail; bindex++)
+	for (bindex = au_fbstart(file); bindex <= btail; bindex++)
 		au_set_h_fptr(file, bindex, NULL);
 	au_set_fbstart(file, -1);
 	au_set_fbend(file, -1);
@@ -122,6 +122,8 @@ static int do_open_dir(struct file *file, int flags)
 
 static int aufs_open_dir(struct inode *inode, struct file *file)
 {
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(file->f_dentry));
+
 	return au_do_open(inode, file, do_open_dir);
 }
 
@@ -213,14 +215,13 @@ static int aufs_fsync_dir(struct file *file, struct dentry *dentry,
 	sb = dentry->d_sb;
 	si_noflush_read_lock(sb);
 	if (file) {
-		err = au_reval_and_lock_finfo(file, reopen_dir, /*wlock*/1,
-					      /*locked*/1);
+		err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1,
+					/*locked*/1);
 		if (unlikely(err))
 			goto out;
 	} else
-		di_read_lock_child(dentry, !AuLock_IW);
+		di_write_lock_child(dentry);
 
-	ii_write_lock_child(inode);
 	if (file) {
 		bend = au_fbend(file);
 		for (bindex = au_fbstart(file); !err && bindex <= bend;
@@ -246,11 +247,9 @@ static int aufs_fsync_dir(struct file *file, struct dentry *dentry,
 	} else
 		err = fsync_dir(dentry, datasync);
 	au_cpup_attr_timesizes(inode);
-	ii_write_unlock(inode);
+	di_write_unlock(dentry);
 	if (file)
 		fi_write_unlock(file);
-	else
-		di_read_unlock(dentry, !AuLock_IW);
 
  out:
 	si_read_unlock(sb);
@@ -262,7 +261,7 @@ static int aufs_fsync_dir(struct file *file, struct dentry *dentry,
 
 static int aufs_readdir(struct file *file, void *dirent, filldir_t filldir)
 {
-	int err;
+	int err, iflag;
 	struct dentry *dentry;
 	struct inode *inode;
 	struct super_block *sb;
@@ -275,24 +274,25 @@ static int aufs_readdir(struct file *file, void *dirent, filldir_t filldir)
 	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);
+	err = au_reval_and_lock_fdi(file, reopen_dir, /*wlock*/1, /*locked*/1);
 	if (unlikely(err))
 		goto out;
 
-	ii_write_lock_child(inode);
 	err = au_vdir_init(file);
 	if (unlikely(err)) {
-		ii_write_unlock(inode);
+		di_write_unlock(dentry);
 		goto out_unlock;
 	}
 
 	/* nfsd filldir calls lookup_one_len(). */
-	ii_downgrade_lock(inode);
+	iflag = AuLock_IW;
+	if (unlikely(au_test_nfsd(current)))
+		iflag = AuLock_IR;
+	di_downgrade_lock(dentry, iflag);
 	err = au_vdir_fill_de(file, dirent, filldir);
 
 	fsstack_copy_attr_atime(inode, au_h_iptr(inode, au_ibstart(inode)));
-	ii_read_unlock(inode);
+	di_read_unlock(dentry, iflag);
 
  out_unlock:
 	fi_write_unlock(file);
@@ -387,8 +387,6 @@ static int do_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
 		goto out_put;
 
 	dlgt = au_ftest_testempty(arg->flags, DLGT);
-	/* todo: necessary? */
-	/* h_file->f_pos = 0; */
 	do {
 		arg->err = 0;
 		au_fclr_testempty(arg->flags, CALLED);
@@ -428,7 +426,7 @@ static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
 	h_dentry = au_h_dptr(dentry, arg->bindex);
 	AuDebugOn(!h_dentry);
 	h_inode = h_dentry->d_inode;
-	AuDebugOn(!h_inode || !S_ISDIR(h_inode->i_mode));
+	AuDebugOn(!h_inode);
 
 	mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
 	err = au_test_h_perm_sio(h_inode, MAY_EXEC | MAY_READ,
@@ -443,6 +441,7 @@ static int sio_test_empty(struct dentry *dentry, struct test_empty_arg *arg)
 			.arg	= arg
 		};
 		unsigned int flags = arg->flags;
+
 		au_fclr_testempty(arg->flags, DLGT);
 		au_fclr_testempty(arg->flags, DIRPERM1);
 		wkq_err = au_wkq_wait(call_do_test_empty, &args, /*dlgt*/0);
@@ -468,7 +467,7 @@ int au_test_empty_lower(struct dentry *dentry)
 	inode = dentry->d_inode;
 	AuDebugOn(!inode || !S_ISDIR(inode->i_mode));
 
-	whlist = au_nhash_new(GFP_TEMPORARY);
+	whlist = au_nhash_new(GFP_NOFS);
 	err = PTR_ERR(whlist);
 	if (IS_ERR(whlist))
 		goto out;
@@ -494,7 +493,6 @@ int au_test_empty_lower(struct dentry *dentry)
 		struct dentry *h_dentry;
 		h_dentry = au_h_dptr(dentry, bindex);
 		if (h_dentry && h_dentry->d_inode) {
-			AuDebugOn(!S_ISDIR(h_dentry->d_inode->i_mode));
 			arg.bindex = bindex;
 			err = do_test_empty(dentry, &arg);
 		}
@@ -528,7 +526,6 @@ int au_test_empty(struct dentry *dentry, struct au_nhash *whlist)
 		struct dentry *h_dentry;
 		h_dentry = au_h_dptr(dentry, bindex);
 		if (h_dentry && h_dentry->d_inode) {
-			AuDebugOn(!S_ISDIR(h_dentry->d_inode->i_mode));
 			arg.bindex = bindex;
 			err = sio_test_empty(dentry, &arg);
 		}
@@ -547,5 +544,4 @@ struct file_operations aufs_dir_fop = {
 	.release	= aufs_release_dir,
 	.flush		= aufs_flush,
 	.fsync		= aufs_fsync_dir,
-	.fsetattr	= aufs_fsetattr,
 };
diff --git a/ubuntu/aufs/dir.h b/ubuntu/aufs/dir.h
index 50beaf2..684af9a 100644
--- a/ubuntu/aufs/dir.h
+++ b/ubuntu/aufs/dir.h
@@ -28,7 +28,7 @@
 #ifdef __KERNEL__
 
 #include <linux/fs.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 
 /* ---------------------------------------------------------------------- */
 
diff --git a/ubuntu/aufs/dlgt.c b/ubuntu/aufs/dlgt.c
new file mode 100644
index 0000000..bd52bca
--- /dev/null
+++ b/ubuntu/aufs/dlgt.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * lookup functions in 'delegate' mode
+ *
+ * $Id: dlgt.c,v 1.5 2008/08/11 02:50:34 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+/* ---------------------------------------------------------------------- */
+
+struct au_lookup_one_len_args {
+	struct dentry **errp;
+	const char *name;
+	struct dentry *parent;
+	int len;
+};
+
+static void au_call_lookup_one_len(void *args)
+{
+	struct au_lookup_one_len_args *a = args;
+	*a->errp = vfsub_lookup_one_len(a->name, a->parent, a->len);
+}
+
+struct dentry *au_lkup_one_dlgt(const char *name, struct dentry *parent,
+				int len, unsigned int flags)
+{
+	struct dentry *dentry;
+	int dirperm1;
+
+	LKTRTrace("%.*s/%.*s, 0x%x\n", AuDLNPair(parent), len, name, flags);
+
+	dirperm1 = au_ftest_ndx(flags, DIRPERM1);
+	if (!dirperm1 && !au_ftest_ndx(flags, DLGT))
+		dentry = vfsub_lookup_one_len(name, parent, len);
+	else {
+		int wkq_err;
+		struct au_lookup_one_len_args args = {
+			.errp	= &dentry,
+			.name	= name,
+			.parent	= parent,
+			.len	= len
+		};
+		wkq_err = au_wkq_wait(au_call_lookup_one_len, &args,
+				      /*dlgt*/!dirperm1);
+		if (unlikely(wkq_err))
+			dentry = ERR_PTR(wkq_err);
+	}
+
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+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 = vfsub_security_inode_permission(a->h_inode, a->mask,
+						   a->fake_nd);
+}
+
+int au_security_inode_permission(struct inode *h_inode, int mask,
+				 struct nameidata *fake_nd, int dlgt)
+{
+	int err;
+
+	AuTraceEnter();
+
+	if (!dlgt)
+		err = vfsub_security_inode_permission(h_inode, mask, fake_nd);
+	else {
+		int wkq_err;
+		struct security_inode_permission_args args = {
+			.errp		= &err,
+			.h_inode	= h_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;
+	}
+
+	AuTraceErr(err);
+	return err;
+}
diff --git a/ubuntu/aufs/export.c b/ubuntu/aufs/export.c
new file mode 100644
index 0000000..31d9aa2
--- /dev/null
+++ b/ubuntu/aufs/export.c
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * export via nfs
+ *
+ * $Id: export.c,v 1.15 2008/09/22 03:52:19 sfjro Exp $
+ */
+
+#include <linux/exportfs.h>
+#include <linux/mnt_namespace.h>
+#include <linux/random.h>
+#include "aufs.h"
+
+union conv {
+#ifdef CONFIG_AUFS_INO_T_64
+	__u32 a[2];
+#else
+	__u32 a[1];
+#endif
+	ino_t ino;
+};
+
+static ino_t decode_ino(__u32 *a)
+{
+	union conv u;
+
+	BUILD_BUG_ON(sizeof(u.ino) != sizeof(u.a));
+	u.a[0] = a[0];
+#ifdef CONFIG_AUFS_INO_T_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];
+#ifdef CONFIG_AUFS_INO_T_64
+	a[1] = u.a[1];
+#endif
+}
+
+/* NFS file handle */
+enum {
+	Fh_br_id,
+	Fh_sigen,
+#ifdef CONFIG_AUFS_INO_T_64
+	/* support 64bit inode number */
+	Fh_ino1,
+	Fh_ino2,
+	Fh_dir_ino1,
+	Fh_dir_ino2,
+#else
+	Fh_ino1,
+	Fh_dir_ino1,
+#endif
+	Fh_igen,
+	Fh_h_type,
+	Fh_tail,
+
+	Fh_ino = Fh_ino1,
+	Fh_dir_ino = Fh_dir_ino1
+};
+
+static int au_test_anon(struct dentry *dentry)
+{
+	return !!(dentry->d_flags & DCACHE_DISCONNECTED);
+}
+
+/* ---------------------------------------------------------------------- */
+/* inode generation external table */
+
+int au_xigen_inc(struct inode *inode)
+{
+	int err;
+	loff_t pos;
+	ssize_t sz;
+	__u32 igen;
+	struct super_block *sb;
+	struct au_sbinfo *sbinfo;
+
+	LKTRTrace("i%lu\n", (unsigned long)inode->i_ino);
+
+	err = 0;
+	sb = inode->i_sb;
+	if (unlikely(!au_opt_test_xino(au_mntflags(sb))))
+		goto out;
+
+	pos = inode->i_ino;
+	pos *= sizeof(igen);
+	igen = inode->i_generation + 1;
+	sbinfo = au_sbi(sb);
+	sz = xino_fwrite(sbinfo->si_xwrite, sbinfo->si_xigen, &igen,
+			 sizeof(igen), &pos);
+	if (sz == sizeof(igen))
+		goto out; /* success */
+
+	err = sz;
+	if (unlikely(sz >= 0)) {
+		err = -EIO;
+		AuIOErr("xigen error (%ld)\n", (long)sz);
+	}
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+int au_xigen_new(struct inode *inode)
+{
+	int err;
+	loff_t pos;
+	ssize_t sz;
+	struct super_block *sb;
+	struct au_sbinfo *sbinfo;
+	struct file *file;
+
+	LKTRTrace("i%lu\n", (unsigned long)inode->i_ino);
+
+	err = 0;
+	sb = inode->i_sb;
+	if (unlikely(!au_opt_test_xino(au_mntflags(sb))))
+		goto out;
+
+	err = -EFBIG;
+	pos = inode->i_ino;
+	if (unlikely(Au_LOFF_MAX / sizeof(inode->i_generation) - 1 < pos)) {
+		AuIOErr1("too large i%lld\n", pos);
+		goto out;
+	}
+	pos *= sizeof(inode->i_generation);
+
+	err = 0;
+	sbinfo = au_sbi(sb);
+	file = sbinfo->si_xigen;
+	/* todo: dirty, at mount time */
+	if (unlikely(!file)) {
+		if (inode->i_ino == AUFS_ROOT_INO)
+			goto out;
+		else
+			BUG();
+	}
+
+	if (i_size_read(file->f_dentry->d_inode)
+	    < pos + sizeof(inode->i_generation)) {
+		spin_lock(&sbinfo->si_xigen_lock);
+		inode->i_generation = sbinfo->si_xigen_next++;
+		spin_unlock(&sbinfo->si_xigen_lock);
+		sz = xino_fwrite(sbinfo->si_xwrite, file, &inode->i_generation,
+				 sizeof(inode->i_generation), &pos);
+	} else
+		sz = xino_fread(sbinfo->si_xread, file, &inode->i_generation,
+				sizeof(inode->i_generation), &pos);
+	if (sz == sizeof(inode->i_generation))
+		goto out; /* success */
+
+	err = sz;
+	if (unlikely(sz >= 0)) {
+		err = -EIO;
+		AuIOErr("xigen error (%ld)\n", (long)sz);
+	}
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+int au_xigen_set(struct super_block *sb, struct file *base)
+{
+	int err;
+	struct au_sbinfo *sbinfo;
+	struct file *file;
+
+	LKTRTrace("%.*s\n", AuDLNPair(base->f_dentry));
+	SiMustWriteLock(sb);
+
+	sbinfo = au_sbi(sb);
+	file = au_xino_create2(sb, base, sbinfo->si_xigen);
+	err = PTR_ERR(file);
+	if (IS_ERR(file))
+		goto out;
+	err = 0;
+	if (sbinfo->si_xigen)
+		fput(sbinfo->si_xigen);
+	sbinfo->si_xigen = file;
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+void au_xigen_clr(struct super_block *sb)
+{
+	struct au_sbinfo *sbinfo;
+
+	sbinfo = au_sbi(sb);
+	if (sbinfo->si_xigen) {
+		fput(sbinfo->si_xigen);
+		sbinfo->si_xigen = NULL;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct dentry *decode_by_ino(struct super_block *sb, ino_t ino,
+				    ino_t dir_ino)
+{
+	struct dentry *dentry, *d;
+	struct inode *inode;
+
+	LKTRTrace("i%lu, diri%lu\n",
+		  (unsigned long)ino, (unsigned long)dir_ino);
+
+	dentry = NULL;
+	inode = ilookup(sb, ino);
+	if (unlikely(!inode))
+		goto out;
+
+	dentry = ERR_PTR(-ESTALE);
+	if (unlikely(is_bad_inode(inode) || IS_DEADDIR(inode)))
+		goto out_iput;
+	AuDbgInode(inode);
+
+	dentry = NULL;
+	if (!dir_ino || S_ISDIR(inode->i_mode))
+ 		dentry = d_find_alias(inode);
+	else {
+		spin_lock(&dcache_lock);
+		list_for_each_entry(d, &inode->i_dentry, d_alias)
+			if (!au_test_anon(d)
+			    && d->d_parent->d_inode->i_ino == dir_ino) {
+				dentry = dget_locked(d);
+				break;
+			}
+		spin_unlock(&dcache_lock);
+	}
+	AuDbgDentry(dentry);
+
+ out_iput:
+	iput(inode);
+ out:
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* todo: dirty? */
+/*
+ * when you mntput() for the return value of this function,
+ * you have to store it to your local var.
+ * ie. never mntput si_mntcache directly.
+ */
+static struct vfsmount *au_do_mnt_get(struct super_block *sb)
+{
+	struct mnt_namespace *ns;
+	struct vfsmount *pos, *mnt;
+
+	AuTraceEnter();
+
+	/* vfsmount_lock is not exported */
+	/* no get/put ?? */
+	AuDebugOn(!current->nsproxy);
+	ns = current->nsproxy->mnt_ns;
+	AuDebugOn(!ns);
+	mnt = NULL;
+	/* the order (reverse) will not be a problem */
+	list_for_each_entry(pos, &ns->list, mnt_list)
+		if (pos->mnt_sb == sb) {
+			mnt = pos;
+			break;
+		}
+	AuDebugOn(!mnt);
+
+	return mntget(mnt);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
+static struct vfsmount *au_mnt_get(struct super_block *sb)
+{
+	struct au_sbinfo *sbinfo;
+	struct vfsmount *mnt;
+
+	sbinfo = au_sbi(sb);
+	spin_lock(&sbinfo->si_mntcache_lock);
+	if (sbinfo->si_mntcache)
+		mnt = mntget(sbinfo->si_mntcache);
+	else {
+		sbinfo->si_mntcache = au_do_mnt_get(sb);
+		mnt = sbinfo->si_mntcache;
+	}
+	spin_unlock(&sbinfo->si_mntcache_lock);
+	return mnt;
+}
+#else
+static struct vfsmount *au_mnt_get(struct super_block *sb)
+{
+	return au_do_mnt_get(sb);
+}
+#endif
+
+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,
+		 u64 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 *au_lkup_by_ino(struct path *path, ino_t ino)
+{
+	struct dentry *dentry, *parent;
+	struct file *file;
+	struct inode *dir, *inode;
+	struct find_name_by_ino arg;
+	int err;
+
+	parent = path->dentry;
+	LKTRTrace("%.*s, i%lu\n", AuDLNPair(parent), (unsigned long )ino);
+
+	path_get(path);
+	file = dentry_open(parent, path->mnt, au_dir_roflags);
+	dentry = (void *)file;
+	if (IS_ERR(file))
+		goto out;
+
+	dentry = ERR_PTR(-ENOMEM);
+	arg.name = __getname();
+	if (unlikely(!arg.name))
+		goto out_file;
+	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 (unlikely(err))
+		goto out_name;
+	dentry = ERR_PTR(-ENOENT);
+	if (!arg.found)
+		goto out_name;
+
+	/* do not call au_lkup_one(), nor dlgt */
+	dir = parent->d_inode;
+	mutex_lock(&dir->i_mutex);
+	dentry = vfsub_lookup_one_len(arg.name, parent, arg.namelen);
+	mutex_unlock(&dir->i_mutex);
+	AuTraceErrPtr(dentry);
+	if (IS_ERR(dentry))
+		goto out_name;
+	AuDebugOn(au_test_anon(dentry));
+	inode = dentry->d_inode;
+	if (unlikely(!inode)) {
+		dput(dentry);
+		dentry = ERR_PTR(-ENOENT);
+	}
+
+ out_name:
+	__putname(arg.name);
+ out_file:
+	fput(file);
+ out:
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+
+static /* noinline_for_stack */
+struct dentry *decode_by_dir_ino(struct super_block *sb, ino_t ino,
+				 ino_t dir_ino)
+{
+	struct dentry *dentry, *parent;
+	struct path path;
+
+	LKTRTrace("i%lu, diri%lu\n",
+		  (unsigned long)ino, (unsigned long)dir_ino);
+
+	parent = sb->s_root;
+	if (dir_ino != AUFS_ROOT_INO) {
+		parent = decode_by_ino(sb, dir_ino, 0);
+		AuDbgDentry(parent);
+		dentry = parent;
+		if (unlikely(!parent))
+			goto out;
+		if (IS_ERR(parent))
+			goto out;
+		AuDebugOn(au_test_anon(parent));
+	} else
+		dget(parent);
+
+	path.dentry = parent;
+	path.mnt = au_mnt_get(sb);
+	dentry = au_lkup_by_ino(&path, ino);
+	path_put(&path);
+
+ out:
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int h_acceptable(void *expv, struct dentry *dentry)
+{
+	return 1;
+}
+
+static char *au_build_path(struct dentry *h_parent, struct path *h_rootpath,
+			   char *buf, int len, struct super_block *sb)
+{
+	char *p;
+	int n;
+	struct path path;
+
+	AuTraceEnter();
+
+	p = d_path(h_rootpath, buf, len);
+	if (IS_ERR(p))
+		goto out;
+	n = strlen(p);
+
+	path.mnt = h_rootpath->mnt;
+	path.dentry = h_parent;
+	p = d_path(&path, buf, len);
+	if (IS_ERR(p))
+		goto out;
+	LKTRTrace("%s\n", p);
+	if (n != 1)
+		p += n;
+	LKTRTrace("%p, %s, %ld\n",
+		  p, p, (long)(p - buf));
+
+	path.mnt = au_mnt_get(sb);
+	path.dentry = sb->s_root;
+	p = d_path(&path, buf, len - strlen(p));
+	mntput(path.mnt);
+	if (IS_ERR(p))
+		goto out;
+	if (n != 1)
+		p[strlen(p)] = '/';
+	LKTRTrace("%s\n", p);
+
+ out:
+	AuTraceErrPtr(p);
+	return p;
+}
+
+static noinline_for_stack
+struct dentry *decode_by_path(struct super_block *sb, aufs_bindex_t bindex,
+			      ino_t ino, __u32 *fh, int fh_len)
+{
+	struct dentry *dentry, *h_parent, *root;
+	struct super_block *h_sb;
+	char *pathname, *p;
+	struct vfsmount *h_mnt;
+	struct au_branch *br;
+	int err;
+	struct nameidata nd;
+
+	LKTRTrace("b%d\n", bindex);
+	SiMustAnyLock(sb);
+
+	br = au_sbr(sb, bindex);
+	/* au_br_get(br); */
+	h_mnt = br->br_mnt;
+	h_sb = h_mnt->mnt_sb;
+	LKTRTrace("%s, h_decode_fh\n", au_sbtype(h_sb));
+	/* in linux-2.6.24, it takes struct fid * as file handle */
+	/* todo: call lower fh_to_dentry()? fh_to_parent()? */
+	h_parent = exportfs_decode_fh(h_mnt, (void *)(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))) {
+		AuWarn1("%s decode_fh failed, %ld\n",
+			au_sbtype(h_sb), PTR_ERR(h_parent));
+		goto out;
+	}
+	dentry = NULL;
+	if (unlikely(au_test_anon(h_parent))) {
+		AuWarn1("%s decode_fh returned a disconnected dentry\n",
+			au_sbtype(h_sb));
+		goto out_h_parent;
+	}
+
+	dentry = ERR_PTR(-ENOMEM);
+	pathname = (void *)__get_free_page(GFP_NOFS);
+	if (unlikely(!pathname))
+		goto out_h_parent;
+
+	root = sb->s_root;
+	nd.path.mnt = h_mnt;
+	di_read_lock_parent(root, !AuLock_IR);
+	nd.path.dentry = au_h_dptr(root, bindex);
+	di_read_unlock(root, !AuLock_IR);
+	p = au_build_path(h_parent, &nd.path, pathname, PAGE_SIZE, sb);
+	dentry = (void *)p;
+	if (IS_ERR(p))
+		goto out_pathname;
+
+	LKTRTrace("%s\n", p);
+	err = vfsub_path_lookup(p, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &nd);
+	dentry = ERR_PTR(err);
+	if (unlikely(err))
+		goto out_pathname;
+
+	dentry = ERR_PTR(-ENOENT);
+	AuDebugOn(au_test_anon(nd.path.dentry));
+	if (unlikely(!nd.path.dentry->d_inode))
+		goto out_nd;
+
+	if (ino != nd.path.dentry->d_inode->i_ino)
+		dentry = au_lkup_by_ino(&nd.path, ino);
+	else
+		dentry = dget(nd.path.dentry);
+
+ out_nd:
+	path_put(&nd.path);
+ out_pathname:
+	free_page((unsigned long)pathname);
+ out_h_parent:
+	dput(h_parent);
+ out:
+	/* au_br_put(br); */
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct dentry *
+aufs_fh_to_dentry(struct super_block *sb, struct fid *fid, int fh_len,
+		  int fh_type)
+{
+	struct dentry *dentry;
+	struct inode *inode;
+	__u32 *fh = fid->raw;
+	ino_t ino, dir_ino;
+	aufs_bindex_t bindex, br_id;
+	au_gen_t sigen;
+
+	LKTRTrace("%d, fh{br_id %u, sigen %u, i%u, diri%u, g%u}\n",
+		  fh_type, fh[Fh_br_id], fh[Fh_sigen], fh[Fh_ino],
+		  fh[Fh_dir_ino], fh[Fh_igen]);
+	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 = au_br_index(sb, br_id);
+	LKTRTrace("b%d\n", bindex);
+	if (unlikely(bindex < 0
+		     || (0 && sigen != au_sigen(sb))
+		     || (1 && sigen + AUFS_BRANCH_MAX <= au_sigen(sb))
+		    ))
+		goto out;
+
+	/* is this inode still cached? */
+	ino = decode_ino(fh + Fh_ino);
+	AuDebugOn(ino == AUFS_ROOT_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, ino, fh, fh_len);
+	if (IS_ERR(dentry))
+		goto out;
+	if (unlikely(!dentry))
+		goto out;
+
+ accept:
+	LKTRLabel(accept);
+	inode = dentry->d_inode;
+#if 0
+	/* support branch manupilation and udba on nfs server */
+	sigen = au_sigen(sb);
+	if (unlikely(au_digen(dentry) != sigen
+		     || au_iigen(inode) != sigen)) {
+		int err;
+
+		//lktr_set_pid(current->pid, LktrArrayPid);
+		//au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIRS);
+		di_write_lock_child(dentry);
+		err = au_reval_dpath(dentry, sigen);
+		di_write_unlock(dentry);
+		//lktr_clear_pid(current->pid, LktrArrayPid);
+		if (unlikely(err < 0))
+			goto out_dput;
+	}
+#endif
+
+	if (unlikely(inode->i_generation != fh[Fh_igen])) {
+		LKTRLabel(stale);
+		dput(dentry);
+		dentry = ERR_PTR(-ESTALE);
+	}
+
+ out:
+	LKTRLabel(out);
+	lockdep_on();
+	si_read_unlock(sb);
+	AuTraceErrPtr(dentry);
+	return dentry;
+}
+
+#if 0 /* reserved for future use */
+/* support subtreecheck option */
+static struct dentry *aufs_fh_to_parent(struct super_block *sb, struct fid *fid,
+					int fh_len, int fh_type)
+{
+	struct dentry *parent;
+	__u32 *fh = fid->raw;
+	ino_t dir_ino;
+
+	dir_ino = decode_ino(fh + Fh_dir_ino);
+	parent = decode_by_ino(sb, dir_ino, 0);
+	if (IS_ERR(parent))
+		goto out;
+	if (!parent)
+		parent = decode_by_path(sb, au_br_index(sb, fh[Fh_br_id]),
+					dir_ino, fh, fh_len);
+
+ out:
+	AuTraceErrPtr(parent);
+	return parent;
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static int aufs_encode_fh(struct dentry *dentry, __u32 *fh, int *max_len,
+			  int connectable)
+{
+	int err;
+	aufs_bindex_t bindex, bend;
+	struct super_block *sb, *h_sb;
+	struct inode *inode;
+	struct dentry *parent, *h_parent;
+	struct au_branch *br;
+
+	LKTRTrace("%.*s, max %d, conn %d\n",
+		  AuDLNPair(dentry), *max_len, connectable);
+	AuDebugOn(au_test_anon(dentry));
+
+	parent = NULL;
+	err = -ENOSPC;
+	if (unlikely(*max_len <= Fh_tail)) {
+		AuWarn1("NFSv2 client (max_len %d)?\n", *max_len);
+		goto out;
+	}
+
+	err = FILEID_ROOT;
+	inode = dentry->d_inode;
+	AuDebugOn(!inode);
+	if (inode->i_ino == AUFS_ROOT_INO)
+		goto out;
+
+	err = -EIO;
+	h_parent = NULL;
+	sb = dentry->d_sb;
+	parent = dget_parent(dentry);
+	aufs_read_lock(parent, AuLock_FLUSH | AuLock_IR);
+#ifdef CONFIG_AUFS_DEBUG
+	{
+		unsigned int mnt_flags = au_mntflags(sb);
+
+		if (unlikely(!au_opt_test_xino(mnt_flags)))
+			AuWarn1("NFS-exporting requires xino\n");
+		if (unlikely(0 && !au_opt_test(mnt_flags, UDBA_INOTIFY)))
+			AuWarn1("udba=inotify is recommended "
+				"for NFS-exporting\n");
+	}
+#endif
+
+	bend = au_dbtaildir(parent);
+	for (bindex = au_dbstart(parent); bindex <= bend; bindex++) {
+		h_parent = au_h_dptr(parent, bindex);
+		if (h_parent) {
+			dget(h_parent);
+			break;
+		}
+	}
+	if (unlikely(!h_parent))
+		goto out_unlock;
+	LKTRTrace("b%d\n", bindex);
+
+	err = -EPERM;
+	br = au_sbr(sb, bindex);
+	h_sb = br->br_mnt->mnt_sb;
+	if (unlikely(!h_sb->s_export_op)) {
+		AuErr1("%s branch is not exportable\n", au_sbtype(h_sb));
+		goto out_dput;
+	}
+
+	fh[Fh_br_id] = br->br_id;
+	fh[Fh_sigen] = au_sigen(sb);
+	encode_ino(fh + Fh_ino, inode->i_ino);
+	encode_ino(fh + Fh_dir_ino, parent->d_inode->i_ino);
+	fh[Fh_igen] = inode->i_generation;
+
+	*max_len -= Fh_tail;
+	/* in linux-2.6.24, it takes struct fid * as file handle */
+	fh[Fh_h_type] = exportfs_encode_fh(h_parent, (void *)(fh + Fh_tail),
+					   max_len, connectable);
+	err = fh[Fh_h_type];
+	*max_len += Fh_tail;
+	/* todo: macros? */
+	if (err != 255)
+		err = 99;
+	else
+		AuWarn1("%s encode_fh failed\n", au_sbtype(h_sb));
+
+ out_dput:
+	dput(h_parent);
+ out_unlock:
+	aufs_read_unlock(parent, AuLock_IR);
+	dput(parent);
+ out:
+	AuTraceErr(err);
+	if (unlikely(err < 0))
+		err = 255;
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct export_operations aufs_export_op = {
+	.fh_to_dentry	= aufs_fh_to_dentry,
+	//.fh_to_parent	= aufs_fh_to_parent,
+	.encode_fh	= aufs_encode_fh
+};
+
+void au_export_init(struct super_block *sb)
+{
+	struct au_sbinfo *sbinfo;
+
+	AuTraceEnter();
+	SiMustWriteLock(sb);
+
+	sb->s_export_op = &aufs_export_op;
+	sbinfo = au_sbi(sb);
+	sbinfo->si_xigen = NULL;
+	spin_lock_init(&sbinfo->si_xigen_lock);
+	/* todo: meaningless? */
+	get_random_bytes(&sbinfo->si_xigen_next, sizeof(sbinfo->si_xigen_next));
+	memset(&sbinfo->si_xinodir, 0, sizeof(struct path));
+}
diff --git a/ubuntu/aufs/f_op.c b/ubuntu/aufs/f_op.c
index 7b93a84..de9b088 100644
--- a/ubuntu/aufs/f_op.c
+++ b/ubuntu/aufs/f_op.c
@@ -19,7 +19,7 @@
 /*
  * file and vm operations
  *
- * $Id: f_op.c,v 1.7 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: f_op.c,v 1.11 2008/09/01 02:55:44 sfjro Exp $
  */
 
 #include <linux/fs_stack.h>
@@ -32,6 +32,7 @@ int aufs_flush(struct file *file, fl_owner_t id)
 	int err;
 	struct dentry *dentry;
 	aufs_bindex_t bindex, bend;
+	struct file *h_file;
 
 	dentry = file->f_dentry;
 	LKTRTrace("%.*s\n", AuDLNPair(dentry));
@@ -43,7 +44,6 @@ int aufs_flush(struct file *file, fl_owner_t id)
 	err = 0;
 	bend = au_fbend(file);
 	for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) {
-		struct file *h_file;
 		h_file = au_h_fptr(file, bindex);
 		if (h_file && h_file->f_op && h_file->f_op->flush) {
 			err = h_file->f_op->flush(h_file, id);
@@ -90,22 +90,25 @@ static int do_open_nondir(struct file *file, int flags)
 	BUG_ON(au_test_ro(sb, bindex, inode) && (flags & O_TRUNC));
 
 	h_file = au_h_open(dentry, bindex, flags, file);
-	if (!IS_ERR(h_file)) {
+	if (IS_ERR(h_file))
+		err = PTR_ERR(h_file);
+	else {
 		au_set_fbstart(file, bindex);
 		au_set_fbend(file, bindex);
 		au_set_h_fptr(file, bindex, h_file);
 		au_update_figen(file);
 		/* todo: necessary? */
 		/* file->f_ra = h_file->f_ra; */
-		return 0; /* success */
+		err = 0;
 	}
-	err = PTR_ERR(h_file);
 	AuTraceErr(err);
 	return err;
 }
 
 static int aufs_open_nondir(struct inode *inode, struct file *file)
 {
+	LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(file->f_dentry));
+
 	return au_do_open(inode, file, do_open_nondir);
 }
 
@@ -138,8 +141,8 @@ static ssize_t aufs_read(struct file *file, char __user *buf, size_t count,
 
 	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 = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0,
+				    /*locked*/0);
 	if (unlikely(err))
 		goto out;
 
@@ -152,6 +155,7 @@ static ssize_t aufs_read(struct file *file, char __user *buf, size_t count,
 	/* file->f_ra = h_file->f_ra; */
 	fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
 
+	di_read_unlock(dentry, AuLock_IR);
 	fi_read_unlock(file);
  out:
 	si_read_unlock(sb);
@@ -163,14 +167,17 @@ static ssize_t aufs_write(struct file *file, const char __user *ubuf,
 			  size_t count, loff_t *ppos)
 {
 	ssize_t err;
-	struct dentry *dentry, *parent;
-	struct inode *inode, *dir;
+	struct dentry *dentry;
+	struct inode *inode;
 	struct super_block *sb;
 	unsigned int mnt_flags;
 	struct file *h_file;
 	char __user *buf = (char __user *)ubuf;
 	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
+	aufs_bindex_t bstart;
+	int hinotify;
+	struct au_pin pin;
 
 	dentry = file->f_dentry;
 	LKTRTrace("%.*s, cnt %lu, pos %lld\n",
@@ -180,35 +187,34 @@ static ssize_t aufs_write(struct file *file, const char __user *ubuf,
 	mutex_lock(&inode->i_mutex);
 	sb = dentry->d_sb;
 	si_read_lock(sb, AuLock_FLUSH);
-	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/1,
-				      /*locked*/1);
+	mnt_flags = au_mntflags(sb);
+	hinotify = !!au_opt_test(mnt_flags, UDBA_INOTIFY);
+	vfsub_args_init(&vargs, &ign, au_test_dlgt(mnt_flags), 0);
+
+	err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1,
+				    /*locked*/1);
 	if (unlikely(err))
 		goto out;
-	err = au_ready_to_write(file, -1);
+	err = au_ready_to_write(file, -1, &pin);
+	di_downgrade_lock(dentry, AuLock_IR);
 	if (unlikely(err))
 		goto out_unlock;
 
-	/* support LSM and notify */
-	mnt_flags = au_mntflags(sb);
-	vfsub_args_init(&vargs, &ign, au_test_dlgt(mnt_flags), 0);
-	h_file = au_h_fptr(file, au_fbstart(file));
-	if (!au_opt_test(mnt_flags, UDBA_INOTIFY))
+	bstart = au_fbstart(file);
+	h_file = au_h_fptr(file, bstart);
+	if (!hinotify) {
+		au_unpin(&pin);
 		err = vfsub_write_u(h_file, buf, count, ppos, &vargs);
-	else {
-		parent = dget_parent(dentry);
-		dir = parent->d_inode;
-		ii_read_lock_parent(dir);
+	} else {
 		vfsub_ign_hinode(&vargs, IN_MODIFY,
-				 au_hi(dir, au_fbstart(file)));
+				 au_pinned_hdir(&pin, bstart));
 		err = vfsub_write_u(h_file, buf, count, ppos, &vargs);
-		ii_read_unlock(dir);
-		dput(parent);
+		au_unpin(&pin);
 	}
-	ii_write_lock_child(inode);
 	au_cpup_attr_timesizes(inode);
-	ii_write_unlock(inode);
 
  out_unlock:
+	di_read_unlock(dentry, AuLock_IR);
 	fi_write_unlock(file);
  out:
 	si_read_unlock(sb);
@@ -243,8 +249,8 @@ static ssize_t aufs_splice_read(struct file *file, loff_t *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);
+	err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0,
+				    /*locked*/0);
 	if (unlikely(err))
 		goto out;
 
@@ -260,6 +266,7 @@ static ssize_t aufs_splice_read(struct file *file, loff_t *ppos,
 	/* todo: necessasry? */
 	/* file->f_ra = h_file->f_ra; */
 	fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
+	di_read_unlock(dentry, AuLock_IR);
 	fi_read_unlock(file);
 
  out:
@@ -277,9 +284,10 @@ aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos,
 	struct inode *inode, *h_inode;
 	struct super_block *sb;
 	struct file *h_file;
-	struct au_hin_ignore ign;
+	/* struct au_hin_ignore ign; */
 	struct vfsub_args vargs;
 	unsigned int mnt_flags;
+	struct au_pin pin;
 
 	dentry = file->f_dentry;
 	LKTRTrace("%.*s, len %lu, pos %lld\n",
@@ -289,36 +297,39 @@ aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos,
 	mutex_lock(&inode->i_mutex);
 	sb = dentry->d_sb;
 	si_read_lock(sb, AuLock_FLUSH);
-	err = au_reval_and_lock_finfo(file, au_reopen_nondir, /*wlock*/1,
-				      /*locked*/1);
+	mnt_flags = au_mntflags(sb);
+	vfsub_args_init(&vargs, /*&ign*/NULL, au_test_dlgt(mnt_flags), 0);
+
+	err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1,
+				    /*locked*/1);
 	if (unlikely(err))
 		goto out;
-	err = au_ready_to_write(file, -1);
+	err = au_ready_to_write(file, -1, &pin);
+	di_downgrade_lock(dentry, AuLock_IR);
 	if (unlikely(err))
 		goto out_unlock;
 
 	/* support LSM and notify */
-	mnt_flags = au_mntflags(sb);
-	vfsub_args_init(&vargs, &ign, au_test_dlgt(mnt_flags), 0);
+	/* current vfs_splice_from() doesn't fire up the inotify event */
 	h_file = au_h_fptr(file, au_fbstart(file));
 	h_inode = h_file->f_dentry->d_inode;
-	/* current vfs_splice_from() doesn't fire up the inotify event */
-	if (1 || !au_opt_test(mnt_flags, UDBA_INOTIFY))
+	if (1 || !au_opt_test(mnt_flags, UDBA_INOTIFY)) {
+		au_unpin(&pin);
 		err = vfsub_splice_from(pipe, h_file, ppos, len, flags, &vargs);
+	}
 #if 0 /* reserved for future use */
 	else {
 		struct dentry *parent = dget_parent(dentry);
 		vfsub_ign_hinode(&vargs, IN_MODIFY,
-		au_hi(parent->d_inode, au_fbstart(file));
+				 au_pinned_hdir(&pin, bstart));
 		err = vfsub_splice_from(pipe, h_file, ppos, len, flags, &vargs);
-		dput(parent);
+		au_unpin(&pin);
 	}
 #endif
-	ii_write_lock_child(inode);
 	au_cpup_attr_timesizes(inode);
-	ii_write_unlock(inode);
 
  out_unlock:
+	di_read_unlock(dentry, AuLock_IR);
 	fi_write_unlock(file);
  out:
 	si_read_unlock(sb);
@@ -405,13 +416,9 @@ static struct vm_operations_struct *au_vm_ops(struct file *h_file,
 
 	AuTraceEnter();
 
-	if (!au_test_nfs(h_file->f_vfsmnt->mnt_sb))
-		err = h_file->f_op->mmap(h_file, vma);
-	else {
-		lockdep_off();
-		err = h_file->f_op->mmap(h_file, vma);
-		lockdep_on();
-	}
+	au_br_nfs_lockdep_off(h_file->f_vfsmnt->mnt_sb);
+	err = h_file->f_op->mmap(h_file, vma);
+	au_br_nfs_lockdep_on(h_file->f_vfsmnt->mnt_sb);
 	vm_ops = ERR_PTR(err);
 	if (unlikely(err))
 		goto out;
@@ -431,7 +438,8 @@ static struct vm_operations_struct *au_vm_ops(struct file *h_file,
 
 static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
 {
-	int err, wlock, mmapped;
+	int err;
+	unsigned char wlock, mmapped;
 	struct dentry *dentry;
 	struct super_block *sb;
 	struct file *h_file;
@@ -448,16 +456,21 @@ static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
 	wlock = !!(file->f_mode & FMODE_WRITE);
 	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 = au_reval_and_lock_fdi(file, au_reopen_nondir, wlock | !mmapped,
+				/*locked*/0);
 	if (unlikely(err))
 		goto out;
 
 	if (wlock) {
-		err = au_ready_to_write(file, -1);
+		struct au_pin pin;
+
+		err = au_ready_to_write(file, -1, &pin);
+		di_downgrade_lock(dentry, AuLock_IR);
 		if (unlikely(err))
 			goto out_unlock;
-	}
+		au_unpin(&pin);
+	} else if (!mmapped)
+		di_downgrade_lock(dentry, AuLock_IR);
 
 	h_file = au_h_fptr(file, au_fbstart(file));
 	if (unlikely(au_test_fuse(h_file->f_dentry->d_sb))) {
@@ -500,6 +513,7 @@ static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
 	fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
 
  out_unlock:
+	di_read_unlock(dentry, AuLock_IR);
 	if (!wlock && mmapped)
 		fi_read_unlock(file);
 	else
@@ -528,8 +542,8 @@ static unsigned int aufs_poll(struct file *file, poll_table *wait)
 	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 = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0,
+				    /*locked*/0);
 	if (unlikely(err))
 		goto out;
 
@@ -538,6 +552,7 @@ static unsigned int aufs_poll(struct file *file, poll_table *wait)
 	h_file = au_h_fptr(file, au_fbstart(file));
 	if (h_file->f_op && h_file->f_op->poll)
 		mask = h_file->f_op->poll(h_file, wait);
+	di_read_unlock(dentry, AuLock_IR);
 	fi_read_unlock(file);
 
  out:
@@ -553,6 +568,7 @@ static int aufs_fsync_nondir(struct file *file, struct dentry *dentry,
 	struct inode *inode;
 	struct file *h_file;
 	struct super_block *sb;
+	struct au_pin pin;
 
 	LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), datasync);
 	inode = dentry->d_inode;
@@ -569,30 +585,32 @@ static int aufs_fsync_nondir(struct file *file, struct dentry *dentry,
 	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 = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1,
+				    /*locked*/1);
 	if (unlikely(err))
 		goto out;
-	err = au_ready_to_write(file, -1);
+	err = au_ready_to_write(file, -1, &pin);
+	di_downgrade_lock(dentry, AuLock_IR);
 	if (unlikely(err))
 		goto out_unlock;
+	au_unpin(&pin);
 
 	err = -EINVAL;
 	h_file = au_h_fptr(file, au_fbstart(file));
 	if (h_file->f_op && h_file->f_op->fsync) {
-		ii_write_lock_child(inode);
-		mutex_lock_nested(&h_file->f_dentry->d_inode->i_mutex,
-				  AuLsc_I_CHILD);
+		struct mutex *h_mtx = &h_file->f_dentry->d_inode->i_mutex;
+
+		mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
 		err = h_file->f_op->fsync(h_file, h_file->f_dentry, datasync);
 		if (!err)
 			au_update_fuse_h_inode(h_file->f_vfsmnt,
 					       h_file->f_dentry);
 		au_cpup_attr_timesizes(inode);
-		mutex_unlock(&h_file->f_dentry->d_inode->i_mutex);
-		ii_write_unlock(inode);
+		mutex_unlock(h_mtx);
 	}
 
  out_unlock:
+	di_read_unlock(dentry, AuLock_IR);
 	fi_write_unlock(file);
  out:
 	si_read_unlock(sb);
@@ -616,14 +634,15 @@ static int aufs_fasync(int fd, struct file *file, int 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 = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0,
+				    /*locked*/0);
 	if (unlikely(err))
 		goto out;
 
 	h_file = au_h_fptr(file, au_fbstart(file));
 	if (h_file->f_op && h_file->f_op->fasync)
 		err = h_file->f_op->fasync(fd, h_file, flag);
+	di_read_unlock(dentry, AuLock_IR);
 	fi_read_unlock(file);
 
  out:
@@ -648,5 +667,4 @@ struct file_operations aufs_file_fop = {
 	.splice_write	= aufs_splice_write,
 	.splice_read	= aufs_splice_read,
 #endif
-	.fsetattr	= aufs_fsetattr,
 };
diff --git a/ubuntu/aufs/file.c b/ubuntu/aufs/file.c
index 23710c1..6e26e63 100644
--- a/ubuntu/aufs/file.c
+++ b/ubuntu/aufs/file.c
@@ -19,7 +19,7 @@
 /*
  * handling file/dir, and address_space operation
  *
- * $Id: file.c,v 1.7 2008/06/09 01:11:58 sfjro Exp $
+ * $Id: file.c,v 1.16 2008/09/22 03:52:09 sfjro Exp $
  */
 
 #include <linux/pagemap.h>
@@ -73,25 +73,21 @@ struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
 	struct inode *h_inode;
 	struct super_block *sb;
 	struct au_branch *br;
-	int hinotify, err;
+	int err;
 
 	LKTRTrace("%.*s, b%d, flags 0%o, f %d\n",
 		  AuDLNPair(dentry), bindex, flags, !!file);
-	AuDebugOn(!dentry);
 	h_dentry = au_h_dptr(dentry, bindex);
 	AuDebugOn(!h_dentry);
 	h_inode = h_dentry->d_inode;
-	/* todo: a race can happen, return ENOENT */
-#if 0
+
+	/* a race condition can happen between open and unlink/rmdir */
 	h_file = ERR_PTR(-ENOENT);
-	if (unlikely(!h_inode))
+	if (unlikely((!d_unhashed(dentry) && d_unhashed(h_dentry))
+		     || !h_inode))
 		goto out;
-#else
-	AuDebugOn(!h_inode);
-#endif
 
 	sb = dentry->d_sb;
-	hinotify = !!au_opt_test(au_mntflags(sb), UDBA_INOTIFY);
 	br = au_sbr(sb, bindex);
 	au_br_get(br);
 	/* drop flags for writing */
@@ -117,10 +113,10 @@ struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
 			h_file = ERR_PTR(err);
 		}
 	}
-	if (!IS_ERR(h_file))
-		return h_file;
+	if (IS_ERR(h_file))
+		au_br_put(br);
 
-	au_br_put(br);
+out:
 	AuTraceErrPtr(h_file);
 	return h_file;
 }
@@ -128,10 +124,11 @@ struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
 static int do_coo(struct dentry *dentry, aufs_bindex_t bstart)
 {
 	int err;
-	struct dentry *parent, *h_parent, *h_dentry, *gparent;
+	struct dentry *parent;
 	aufs_bindex_t bcpup;
-	struct inode *h_dir, *h_inode, *dir;
+	struct mutex *h_mtx;
 	struct super_block *sb;
+	struct au_pin pin;
 
 	LKTRTrace("%.*s\n", AuDLNPair(dentry));
 	AuDebugOn(IS_ROOT(dentry));
@@ -144,44 +141,34 @@ static int do_coo(struct dentry *dentry, aufs_bindex_t bstart)
 	bcpup = err;
 	if (err < 0) {
 		err = 0; /* stop copyup, it is not an error */
-		goto out;
+		goto out_dgrade;
 	}
 	err = 0;
 
-	h_parent = au_h_dptr(parent, bcpup);
-	if (!h_parent) {
-		err = au_cpup_dirs(dentry, bcpup, NULL);
+	if (!au_h_dptr(parent, bcpup)) {
+		err = au_cpup_dirs(dentry, bcpup);
 		if (unlikely(err))
-			goto out;
-		h_parent = au_h_dptr(parent, bcpup);
+			goto out_dgrade;
 	}
 
-	h_dir = h_parent->d_inode;
-	h_dentry = au_h_dptr(dentry, bstart);
-	h_inode = h_dentry->d_inode;
-	dir = parent->d_inode;
-	/* todo: meaningless lock if CONFIG_AUFS_DEBUG is disabled. */
-	gparent = NULL;
-	if (unlikely(au_opt_test(au_mntflags(sb), UDBA_INOTIFY)
-		     && !IS_ROOT(parent))) {
-		gparent = dget_parent(parent);
-		ii_read_lock_parent2(gparent->d_inode);
-	}
-	au_hdir_lock(h_dir, dir, bcpup);
-	/* todo: test parent-gparent relationship? */
-	mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
+	di_downgrade_lock(parent, AuLock_IR);
+	err = au_pin(&pin, dentry, bcpup, /*di_locked*/1,
+		     /*do_gp*/au_opt_test(au_mntflags(sb), UDBA_INOTIFY));
+	if (unlikely(err))
+		goto out;
+	h_mtx = &au_h_dptr(dentry, bstart)->d_inode->i_mutex;
+	mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
 	AuDebugOn(au_h_dptr(dentry, bcpup));
 	err = au_sio_cpup_simple(dentry, bcpup, -1, AuCpup_DTIME);
 	AuTraceErr(err);
-	mutex_unlock(&h_inode->i_mutex);
-	au_hdir_unlock(h_dir, dir, bcpup);
-	if (unlikely(gparent)) {
-		ii_read_unlock(gparent->d_inode);
-		dput(gparent);
-	}
+	mutex_unlock(h_mtx);
+	au_unpin(&pin);
+	goto out;
 
+ out_dgrade:
+	di_downgrade_lock(parent, AuLock_IR);
  out:
-	di_write_unlock(parent);
+	di_read_unlock(parent, AuLock_IR);
 	dput(parent);
 	AuTraceErr(err);
 	return err;
@@ -190,10 +177,11 @@ static int do_coo(struct dentry *dentry, aufs_bindex_t bstart)
 int au_do_open(struct inode *inode, struct file *file,
 	       int (*open)(struct file *file, int flags))
 {
-	int err, coo;
+	int err;
 	struct dentry *dentry;
 	struct super_block *sb;
 	aufs_bindex_t bstart;
+	unsigned char coo;
 
 	dentry = file->f_dentry;
 	LKTRTrace("i%lu, %.*s\n", inode->i_ino, AuDLNPair(dentry));
@@ -213,11 +201,9 @@ int au_do_open(struct inode *inode, struct file *file,
 	if (unlikely(err))
 		goto out;
 
-	if (!coo) {
+	if (!coo)
 		di_read_lock_child(dentry, AuLock_IR);
-		/* todo: remove this */
-		bstart = au_dbstart(dentry);
-	} else {
+	else {
 		di_write_lock_child(dentry);
 		bstart = au_dbstart(dentry);
 		if (au_test_ro(sb, bstart, dentry->d_inode)) {
@@ -226,8 +212,6 @@ int au_do_open(struct inode *inode, struct file *file,
 				di_write_unlock(dentry);
 				goto out_finfo;
 			}
-			/* todo: remove this */
-			bstart = au_dbstart(dentry);
 		}
 		di_downgrade_lock(dentry, AuLock_IR);
 	}
@@ -248,13 +232,14 @@ int au_do_open(struct inode *inode, struct file *file,
 int au_reopen_nondir(struct file *file)
 {
 	int err;
-	struct dentry *dentry;
 	aufs_bindex_t bstart, bindex, bend;
+	struct dentry *dentry;
 	struct file *h_file, *h_file_tmp;
 
 	dentry = file->f_dentry;
 	LKTRTrace("%.*s\n", AuDLNPair(dentry));
 	bstart = au_dbstart(dentry);
+	//bstart = au_ibstart(inode);
 	AuDebugOn(S_ISDIR(dentry->d_inode->i_mode)
 		  || !au_h_dptr(dentry, bstart)->d_inode);
 
@@ -301,14 +286,17 @@ static int au_ready_to_write_wh(struct file *file, loff_t len,
 				aufs_bindex_t bcpup)
 {
 	int err;
+	aufs_bindex_t old_bstart;
+	struct inode *inode;
 	struct dentry *dentry, *hi_wh, *old_h_dentry;
 	struct au_dinfo *dinfo;
-	aufs_bindex_t old_bstart;
+	struct super_block *sb;
 
 	AuTraceEnter();
 
 	dentry = file->f_dentry;
-	hi_wh = au_hi_wh(dentry->d_inode, bcpup);
+	inode = dentry->d_inode;
+	hi_wh = au_hi_wh(inode, bcpup);
 	if (!hi_wh)
 		err = au_sio_cpup_wh(dentry, bcpup, len, file);
 	else {
@@ -323,6 +311,10 @@ static int au_ready_to_write_wh(struct file *file, loff_t len,
 		dinfo->di_bstart = old_bstart;
 	}
 
+	sb = dentry->d_sb;
+	if (!err && inode->i_nlink > 1 && au_opt_test(au_mntflags(sb), PLINK))
+		au_plink_append(sb, inode, au_h_dptr(dentry, bcpup), bcpup);
+
 	AuTraceErr(err);
 	return err;
 }
@@ -330,11 +322,11 @@ static int au_ready_to_write_wh(struct file *file, loff_t len,
 /*
  * prepare the @file for writing.
  */
-int au_ready_to_write(struct file *file, loff_t len)
+int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin)
 {
 	int err;
-	struct dentry *dentry, *parent, *h_dentry, *h_parent, *gparent;
-	struct inode *h_inode, *h_dir, *inode, *dir;
+	struct dentry *dentry, *parent, *h_dentry;
+	struct inode *h_inode, *inode;
 	struct super_block *sb;
 	aufs_bindex_t bstart, bcpup;
 
@@ -348,50 +340,45 @@ int au_ready_to_write(struct file *file, loff_t len)
 
 	inode = dentry->d_inode;
 	AuDebugOn(S_ISDIR(inode->i_mode));
-	ii_read_lock_child(inode);
 	LKTRTrace("rdonly %d, bstart %d\n",
 		  au_test_ro(sb, bstart, inode), bstart);
+
 	err = au_test_ro(sb, bstart, inode);
-	ii_read_unlock(inode);
-	if (!err && (au_h_fptr(file, bstart)->f_mode & FMODE_WRITE))
-		return 0;
+	if (!err && (au_h_fptr(file, bstart)->f_mode & FMODE_WRITE)) {
+		err = au_pin(pin, dentry, bstart, /*di_locked*/0, /*dp_gp*/0);
+		goto out;
+	}
 
 	/* need to cpup */
-	di_write_lock_child(dentry);
 	parent = dget_parent(dentry);
 	di_write_lock_parent(parent);
 	err = AuWbrCopyup(au_sbi(sb), dentry);
 	bcpup = err;
 	if (unlikely(err < 0))
-		goto out_unlock;
+		goto out_dgrade;
 	err = 0;
 
-	h_parent = au_h_dptr(parent, bcpup);
-	if (!h_parent) {
-		err = au_cpup_dirs(dentry, bcpup, NULL);
+	if (!au_h_dptr(parent, bcpup)) {
+		err = au_cpup_dirs(dentry, bcpup);
 		if (unlikely(err))
-			goto out_unlock;
-		h_parent = au_h_dptr(parent, bcpup);
+			goto out_dgrade;
 	}
 
-	/* todo: meaningless lock if CONFIG_AUFS_DEBUG is disabled. */
-	gparent = NULL;
-	if (unlikely(au_opt_test(au_mntflags(sb), UDBA_INOTIFY)
-		     && !IS_ROOT(parent))) {
-		gparent = dget_parent(parent);
-		ii_read_lock_parent2(gparent->d_inode);
-	}
-	h_dir = h_parent->d_inode;
-	h_dentry = au_h_fptr(file, au_fbstart(file))->f_dentry;
+	err = au_pin(pin, dentry, bcpup, /*di_locked*/1,
+		     /*dp_gp*/au_opt_test(au_mntflags(sb), UDBA_INOTIFY));
+	if (unlikely(err))
+		goto out_dgrade;
+
+	AuDebugOn(au_fbstart(file) != bstart);
+	h_dentry = au_h_fptr(file, bstart)->f_dentry;
 	h_inode = h_dentry->d_inode;
-	dir = parent->d_inode;
-	au_hdir_lock(h_dir, dir, bcpup);
-	/* todo: test parent-gparent relationship? */
 	mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
 	if (d_unhashed(dentry) /* || d_unhashed(h_dentry) */
-	    /* || !h_inode->i_nlink */)
+	    /* || !h_inode->i_nlink */) {
 		err = au_ready_to_write_wh(file, len, bcpup);
-	else {
+		di_downgrade_lock(parent, AuLock_IR);
+	} else {
+		di_downgrade_lock(parent, AuLock_IR);
 		if (!au_h_dptr(dentry, bcpup))
 			err = au_sio_cpup_simple(dentry, bcpup, len,
 						 AuCpup_DTIME);
@@ -401,32 +388,39 @@ int au_ready_to_write(struct file *file, loff_t len)
 		AuTraceErr(err);
 	}
 	mutex_unlock(&h_inode->i_mutex);
-	au_hdir_unlock(h_dir, dir, bcpup);
-	if (unlikely(gparent)) {
-		ii_read_unlock(gparent->d_inode);
-		dput(gparent);
+
+	if (!err) {
+		au_unpin_gp(pin);
+		au_pin_set_parent_lflag(pin, /*lflag*/0);
+		goto out_dput; /* success */
 	}
+	au_unpin(pin);
+	goto out_unlock;
 
+ out_dgrade:
+	di_downgrade_lock(parent, AuLock_IR);
  out_unlock:
-	di_write_unlock(parent);
-	di_write_unlock(dentry);
+	di_read_unlock(parent, AuLock_IR);
+ out_dput:
 	dput(parent);
+ out:
 	AuTraceErr(err);
 	return err;
-
 }
 
 /* ---------------------------------------------------------------------- */
 
-static int refresh_file_by_inode(struct file *file, int *need_reopen)
+static int au_file_refresh_by_inode(struct file *file, int *need_reopen)
 {
 	int err;
 	struct au_finfo *finfo;
 	struct dentry *dentry, *parent, *old_h_dentry, *hi_wh;
-	struct inode *inode, *dir, *h_dir;
+	struct inode *inode, *dir;
 	aufs_bindex_t bstart, new_bstart, old_bstart;
 	struct super_block *sb;
 	struct au_dinfo *dinfo;
+	unsigned int mnt_flags;
+	struct au_pin pin;
 
 	dentry = file->f_dentry;
 	LKTRTrace("%.*s\n", AuDLNPair(dentry));
@@ -436,6 +430,7 @@ static int refresh_file_by_inode(struct file *file, int *need_reopen)
 	finfo = au_fi(file);
 	inode = dentry->d_inode;
 	sb = dentry->d_sb;
+	mnt_flags = au_mntflags(sb);
  again:
 	bstart = au_ibstart(inode);
 	if (bstart == finfo->fi_bstart)
@@ -450,15 +445,12 @@ static int refresh_file_by_inode(struct file *file, int *need_reopen)
 		new_bstart = err;
 		di_read_unlock(parent, !AuLock_IR);
 		if (unlikely(err < 0))
-			goto out_put;
+			goto out_dput;
 		err = 0;
 	}
-	di_read_unlock(dentry, AuLock_IR);
-	di_write_lock_child(dentry);
 	/* someone else might change our inode while we were sleeping */
 	/* todo: test more? */
 	if (bstart != au_ibstart(inode)) {
-		di_downgrade_lock(dentry, AuLock_IR);
 		err = 0;
 		dput(parent);
 		goto again;
@@ -467,18 +459,21 @@ static int refresh_file_by_inode(struct file *file, int *need_reopen)
 	bstart = new_bstart;
 
 	hi_wh = au_hi_wh(inode, bstart);
-	if (au_opt_test(au_mntflags(sb), PLINK)
+	if (au_opt_test(mnt_flags, PLINK)
 	    && au_plink_test(sb, inode)
 	    && !d_unhashed(dentry)) {
-		err = au_test_and_cpup_dirs(dentry, bstart, NULL);
+		err = au_test_and_cpup_dirs(dentry, bstart);
+		if (unlikely(err))
+			goto out_unlock;
 
 		/* always superio. */
-#if 1 /* reserved for future use */
-		h_dir = au_h_dptr(parent, bstart)->d_inode;
-		au_hdir_lock(h_dir, dir, bstart);
-		err = au_sio_cpup_simple(dentry, bstart, -1, AuCpup_DTIME);
-		au_hdir_unlock(h_dir, dir, bstart);
-#else
+#if 1
+		err = au_pin(&pin, dentry, bstart, /*di_locked*/1,
+			     /*do_gp*/au_opt_test(mnt_flags, UDBA_INOTIFY));
+		if (!err)
+			err = au_sio_cpup_simple(dentry, bstart, -1, AuCpup_DTIME);
+		au_unpin(&pin);
+#else /* reserved for future use */
 		if (!au_test_wkq(current)) {
 			int wkq_err;
 			struct cpup_pseudo_link_args args = {
@@ -505,10 +500,10 @@ static int refresh_file_by_inode(struct file *file, int *need_reopen)
 		dinfo->di_bstart = old_bstart;
 		*need_reopen = 0;
 	}
-	di_read_unlock(parent, AuLock_IR);
-	di_downgrade_lock(dentry, AuLock_IR);
 
- out_put:
+ out_unlock:
+	di_read_unlock(parent, AuLock_IR);
+ out_dput:
 	dput(parent);
  out:
 	AuTraceErr(err);
@@ -531,16 +526,16 @@ static int refresh_file(struct file *file, int (*reopen)(struct file *file))
 	dentry = file->f_dentry;
 	LKTRTrace("%.*s\n", AuDLNPair(dentry));
 	FiMustWriteLock(file);
-	DiMustReadLock(dentry);
+	DiMustAnyLock(dentry);
 	inode = dentry->d_inode;
-	IiMustReadLock(inode);
+	IiMustAnyLock(inode);
 
 	err = -ENOMEM;
 	sb = dentry->d_sb;
 	finfo = au_fi(file);
 	new_sz = sizeof(*finfo->fi_hfile) * (au_sbend(sb) + 1);
 	p = au_kzrealloc(finfo->fi_hfile, sizeof(*p) * (finfo->fi_bend + 1),
-			 new_sz, GFP_KERNEL);
+			 new_sz, GFP_NOFS);
 	if (unlikely(!p))
 		goto out;
 	finfo->fi_hfile = p;
@@ -617,7 +612,7 @@ static int refresh_file(struct file *file, int (*reopen)(struct file *file))
 	err = 0;
 	need_reopen = 1;
 	if (!au_test_mmapped(file))
-		err = refresh_file_by_inode(file, &need_reopen);
+		err = au_file_refresh_by_inode(file, &need_reopen);
 	if (!err && need_reopen && !d_unhashed(dentry))
 		err = reopen(file);
 	if (!err) {
@@ -636,13 +631,14 @@ static int refresh_file(struct file *file, int (*reopen)(struct file *file))
 }
 
 /* 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 au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file),
+			  int wlock, int locked)
 {
-	int err, pseudo_link;
+	int err;
 	struct dentry *dentry;
 	struct super_block *sb;
 	aufs_bindex_t bstart;
+	unsigned char pseudo_link;
 	au_gen_t sgen, fgen;
 
 	dentry = file->f_dentry;
@@ -654,13 +650,14 @@ int au_reval_and_lock_finfo(struct file *file, int (*reopen)(struct file *file),
 	sgen = au_sigen(sb);
 	fi_write_lock(file);
 	fgen = au_figen(file);
-	di_read_lock_child(dentry, AuLock_IR);
+	di_write_lock_child(dentry);
 	bstart = au_dbstart(dentry);
 	pseudo_link = (bstart != au_ibstart(dentry->d_inode));
-	di_read_unlock(dentry, AuLock_IR);
 	if (sgen == fgen && !pseudo_link && au_fbstart(file) == bstart) {
-		if (!wlock)
+		if (!wlock) {
+			di_downgrade_lock(dentry, AuLock_IR);
 			fi_downgrade_lock(file);
+		}
 		goto out; /* success */
 	}
 
@@ -672,24 +669,24 @@ int au_reval_and_lock_finfo(struct file *file, int (*reopen)(struct file *file),
 		 * 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);
-		di_write_unlock(dentry);
 		if (unlikely(err < 0))
 			goto out;
 		AuDebugOn(au_digen(dentry) != sgen
 			  || au_iigen(dentry->d_inode) != sgen);
 	}
 
-	di_read_lock_child(dentry, AuLock_IR);
 	err = refresh_file(file, reopen
 			   /* , au_opt_test(au_mnt_flags(sb), REFROF) */);
-	di_read_unlock(dentry, AuLock_IR);
 	if (!err) {
-		if (!wlock)
+		if (!wlock) {
+			di_downgrade_lock(dentry, AuLock_IR);
 			fi_downgrade_lock(file);
-	} else
+		}
+	} else {
+		di_write_unlock(dentry);
 		fi_write_unlock(file);
+	}
 
  out:
 	AuTraceErr(err);
diff --git a/ubuntu/aufs/file.h b/ubuntu/aufs/file.h
index ac53c74..3dfbe94 100644
--- a/ubuntu/aufs/file.h
+++ b/ubuntu/aufs/file.h
@@ -19,7 +19,7 @@
 /*
  * file operations
  *
- * $Id: file.h,v 1.4 2008/05/26 04:04:24 sfjro Exp $
+ * $Id: file.h,v 1.5 2008/06/30 03:53:43 sfjro Exp $
  */
 
 #ifndef __AUFS_FILE_H__
@@ -30,7 +30,7 @@
 #include <linux/file.h>
 #include <linux/fs.h>
 #include <linux/mm.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "dentry.h"
 #include "misc.h"
 #include "super.h"
@@ -67,9 +67,10 @@ struct file *au_h_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);
+struct au_pin;
+int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin);
+int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file),
+			  int wlock, int locked);
 
 /* f_op.c */
 extern struct file_operations aufs_file_fop;
diff --git a/ubuntu/aufs/finfo.c b/ubuntu/aufs/finfo.c
index 32a69fa..f09875d 100644
--- a/ubuntu/aufs/finfo.c
+++ b/ubuntu/aufs/finfo.c
@@ -19,7 +19,7 @@
 /*
  * file private data
  *
- * $Id: finfo.c,v 1.3 2008/05/26 04:04:24 sfjro Exp $
+ * $Id: finfo.c,v 1.4 2008/06/30 03:50:21 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -150,7 +150,7 @@ int au_finfo_init(struct file *file)
 	finfo = au_cache_alloc_finfo();
 	if (finfo) {
 		finfo->fi_hfile = kcalloc(au_sbend(dentry->d_sb) + 1,
-					  sizeof(*finfo->fi_hfile), GFP_KERNEL);
+					  sizeof(*finfo->fi_hfile), GFP_NOFS);
 		if (finfo->fi_hfile) {
 			au_rw_init_wlock(&finfo->fi_rwsem);
 			finfo->fi_bstart = -1;
diff --git a/ubuntu/aufs/hin_or_dlgt.c b/ubuntu/aufs/hin_or_dlgt.c
new file mode 100644
index 0000000..a4b6ab4
--- /dev/null
+++ b/ubuntu/aufs/hin_or_dlgt.c
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * sub-routines for vfs in hinotify or dlgt mode
+ *
+ * $Id: hin_or_dlgt.c,v 1.6 2008/07/14 00:15:10 sfjro Exp $
+ */
+
+#include <linux/uaccess.h>
+#include "aufs.h"
+
+#if !defined(CONFIG_AUFS_HINOTIFY) && !defined(CONFIG_AUFS_DLGT)
+#error mis-configuraion or Makefile
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+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;
+	struct vfsub_args *vargs;
+};
+
+static void call_create(void *args)
+{
+	struct create_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_create(a->dir, a->dentry, a->mode, a->nd);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
+		 struct nameidata *nd, struct vfsub_args *vargs)
+{
+	int err;
+	struct create_args args = {
+		.errp	= &err,
+		.dir	= dir,
+		.dentry	= dentry,
+		.mode	= mode,
+		.nd	= nd,
+		.vargs	= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_create(&args);
+	else {
+		int wkq_err;
+		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;
+	struct vfsub_args *vargs;
+};
+
+static void call_symlink(void *args)
+{
+	struct symlink_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_symlink(a->dir, a->dentry, a->symname, a->mode);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname,
+		  int mode, struct vfsub_args *vargs)
+{
+	int err;
+	struct symlink_args args = {
+		.errp		= &err,
+		.dir		= dir,
+		.dentry		= dentry,
+		.symname	= symname,
+		.mode		= mode,
+		.vargs		= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_symlink(&args);
+	else {
+		int wkq_err;
+		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;
+	struct vfsub_args *vargs;
+};
+
+static void call_mknod(void *args)
+{
+	struct mknod_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_mknod(a->dir, a->dentry, a->mode, a->dev);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev,
+		struct vfsub_args *vargs)
+{
+	int err;
+	struct mknod_args args = {
+		.errp	= &err,
+		.dir	= dir,
+		.dentry	= dentry,
+		.mode	= mode,
+		.dev	= dev,
+		.vargs	= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_mknod(&args);
+	else {
+		int wkq_err;
+		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;
+	struct vfsub_args *vargs;
+};
+
+static void call_mkdir(void *args)
+{
+	struct mkdir_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_mkdir(a->dir, a->dentry, a->mode);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode,
+		struct vfsub_args *vargs)
+{
+	int err;
+	struct mkdir_args args = {
+		.errp	= &err,
+		.dir	= dir,
+		.dentry	= dentry,
+		.mode	= mode,
+		.vargs	= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_mkdir(&args);
+	else {
+		int wkq_err;
+		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;
+	struct vfsub_args *vargs;
+};
+
+static void call_link(void *args)
+{
+	struct link_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_link(a->src_dentry, a->dir, a->dentry);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_link(struct dentry *src_dentry, struct inode *dir,
+	       struct dentry *dentry, struct vfsub_args *vargs)
+{
+	int err;
+	struct link_args args = {
+		.errp		= &err,
+		.src_dentry	= src_dentry,
+		.dir		= dir,
+		.dentry		= dentry,
+		.vargs		= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_link(&args);
+	else {
+		int wkq_err;
+		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;
+	struct vfsub_args *vargs;
+};
+
+static void call_rename(void *args)
+{
+	struct rename_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_rename(a->src_dir, a->src_dentry, a->dir,
+				   a->dentry);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
+		 struct inode *dir, struct dentry *dentry,
+		 struct vfsub_args *vargs)
+{
+	int err;
+	struct rename_args args = {
+		.errp		= &err,
+		.src_dir	= src_dir,
+		.src_dentry	= src_dentry,
+		.dir		= dir,
+		.dentry		= dentry,
+		.vargs		= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_rename(&args);
+	else {
+		int wkq_err;
+		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;
+	struct vfsub_args *vargs;
+};
+
+static void call_rmdir(void *args)
+{
+	struct rmdir_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_rmdir(a->dir, a->dentry);
+	if (unlikely(*a->errp))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+int vfsub_rmdir(struct inode *dir, struct dentry *dentry,
+		struct vfsub_args *vargs)
+{
+	int err;
+	struct rmdir_args args = {
+		.errp	= &err,
+		.dir	= dir,
+		.dentry	= dentry,
+		.vargs	= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_rmdir(&args);
+	else {
+		int wkq_err;
+		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 %lld\n",
+		  AuDLNPair(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_NOFS);
+		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 (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;
+	};
+	size_t count;
+	loff_t *ppos;
+	struct vfsub_args *vargs;
+};
+
+static void call_write_k(void *args)
+{
+	struct write_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_write_k(a->file, a->kbuf, a->count, a->ppos);
+	if (unlikely(*a->errp <= 0))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
+		      loff_t *ppos, struct vfsub_args *vargs)
+{
+	ssize_t err;
+
+	if (!vfsub_ftest(vargs->flags, DLGT)) {
+		vfsub_ignore(vargs);
+		err = do_vfsub_write_u(file, ubuf, count, ppos);
+		if (unlikely(err <= 0))
+			vfsub_unignore(vargs);
+		au_dbg_hin_list(vargs);
+	} else {
+		ssize_t written;
+		int wkq_err;
+		struct write_args args = {
+			.errp	= &err,
+			.file	= file,
+			.count	= count,
+			.ppos	= ppos,
+			.vargs	= vargs
+		};
+
+		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_NOFS);
+		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,
+		      struct vfsub_args *vargs)
+{
+	ssize_t err;
+	struct write_args args = {
+		.errp	= &err,
+		.file	= file,
+		.count	= count,
+		.ppos	= ppos,
+		.vargs	= vargs
+	};
+
+	args.kbuf = kbuf;
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_write_k(&args);
+	else {
+		int wkq_err;
+		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;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct splice_to_args {
+	long *errp;
+	struct file *in;
+	loff_t *ppos;
+	struct pipe_inode_info *pipe;
+	size_t len;
+	unsigned int flags;
+};
+
+static void call_splice_to(void *args)
+{
+	struct splice_to_args *a = args;
+	*a->errp = do_vfsub_splice_to(a->in, a->ppos, a->pipe, a->len,
+				      a->flags);
+}
+
+long vfsub_splice_to(struct file *in, loff_t *ppos,
+		     struct pipe_inode_info *pipe, size_t len,
+		     unsigned int flags, int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_splice_to(in, ppos, pipe, len, flags);
+	else {
+		long err;
+		int wkq_err;
+		struct splice_to_args args = {
+			.errp	= &err,
+			.in	= in,
+			.ppos	= ppos,
+			.pipe	= pipe,
+			.len	= len,
+			.flags	= flags
+		};
+		wkq_err = au_wkq_wait(call_splice_to, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
+
+struct splice_from_args {
+	long *errp;
+	struct pipe_inode_info *pipe;
+	struct file *out;
+	loff_t *ppos;
+	size_t len;
+	unsigned int flags;
+	struct vfsub_args *vargs;
+};
+
+static void call_splice_from(void *args)
+{
+	struct splice_from_args *a = args;
+	vfsub_ignore(a->vargs);
+	*a->errp = do_vfsub_splice_from(a->pipe, a->out, a->ppos, a->len,
+					a->flags);
+	if (unlikely(*a->errp < 0))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
+}
+
+long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
+		       loff_t *ppos, size_t len, unsigned int flags,
+		       struct vfsub_args *vargs)
+{
+	long err;
+	struct splice_from_args args = {
+		.errp	= &err,
+		.pipe	= pipe,
+		.out	= out,
+		.ppos	= ppos,
+		.len	= len,
+		.flags	= flags,
+		.vargs	= vargs
+	};
+
+	if (!vfsub_ftest(vargs->flags, DLGT))
+		call_splice_from(&args);
+	else {
+		int wkq_err;
+		wkq_err = au_wkq_wait(call_splice_from, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+	}
+	return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct getattr_args {
+	int *errp;
+	struct vfsmount *mnt;
+	struct dentry *dentry;
+	struct kstat *st;
+};
+
+static void call_getattr(void *args)
+{
+	struct getattr_args *a = args;
+	*a->errp = do_vfsub_getattr(a->mnt, a->dentry, a->st);
+}
+
+int vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st,
+		  int dlgt)
+{
+	if (!dlgt)
+		return do_vfsub_getattr(mnt, dentry, st);
+	else {
+		int err, wkq_err;
+		struct getattr_args args = {
+			.errp	= &err,
+			.mnt	= mnt,
+			.dentry	= dentry,
+			.st	= st
+		};
+		wkq_err = au_wkq_wait(call_getattr, &args, /*dlgt*/1);
+		if (unlikely(wkq_err))
+			err = wkq_err;
+		return err;
+	}
+}
diff --git a/ubuntu/aufs/hin_or_fuse.c b/ubuntu/aufs/hin_or_fuse.c
new file mode 100644
index 0000000..82956fa
--- /dev/null
+++ b/ubuntu/aufs/hin_or_fuse.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * inode attributes on FUSE branch or HINOTIFY
+ *
+ * $Id: hin_or_fuse.c,v 1.6 2008/09/15 03:14:30 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+static struct dentry *
+au_h_dget_any(struct dentry *dentry, aufs_bindex_t *bindex)
+{
+	struct dentry *h_dentry;
+	struct inode *inode, *h_inode;
+	struct super_block *sb;
+	aufs_bindex_t ib, db;
+
+	/* must be positive dentry */
+	inode = dentry->d_inode;
+	LKTRTrace("%.*s, i%lu\n", AuDLNPair(dentry), inode->i_ino);
+
+	sb = dentry->d_sb;
+	db = au_dbstart(dentry);
+	ib = au_ibstart(inode);
+	if (db == ib) {
+		*bindex = db;
+		h_dentry = dget(au_h_dptr(dentry, db));
+		if (h_dentry)
+			goto out; /* success */
+	}
+
+	*bindex = ib;
+	h_inode = au_h_iptr(inode, ib);
+	h_dentry = d_find_alias(h_inode);
+	if (h_dentry)
+		goto out; /* success */
+
+#if 0
+	if (au_opt_test(au_mntflags(sb), PLINK)
+	    && au_plink_test(sb, inode)) {
+		h_dentry = au_plink_lkup(sb, ib, inode);
+		if (IS_ERR(h_dentry))
+			goto out;
+		AuDebugOn(!h_dentry->d_inode);
+		goto out; /* success */
+	}
+#endif
+
+	h_dentry = dget(au_hi_wh(inode, ib));
+
+ out:
+	AuTraceErrPtr(h_dentry);
+	return h_dentry;
+}
+
+int aufs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st)
+{
+	int err;
+	unsigned int mnt_flags;
+	aufs_bindex_t bindex;
+	struct inode *inode;
+	struct dentry *h_dentry;
+	struct super_block *sb, *h_sb;
+
+	LKTRTrace("%.*s\n", AuDLNPair(dentry));
+
+	err = 0;
+	inode = dentry->d_inode;
+	sb = dentry->d_sb;
+	aufs_read_lock(dentry, AuLock_FLUSH | AuLock_IR);
+
+	/* todo: nfs branch too? */
+	/* todo: test bit inotify option too? */
+	mnt_flags = au_mntflags(sb);
+	bindex = au_ibstart(inode);
+	h_sb = au_sbr_sb(sb, bindex);
+	if (!au_test_fuse(h_sb)
+	    //&& !au_test_nfs(h_sb) /* todo: fix me */
+	    && (au_iigen(inode) == au_sigen(sb)
+		|| (au_opt_test(mnt_flags, PLINK) && au_plink_test(sb, inode))))
+		goto fill;
+
+	h_dentry = au_h_dget_any(dentry, &bindex);
+	err = PTR_ERR(h_dentry);
+	if (IS_ERR(h_dentry))
+		goto out;
+
+	err = -EIO;
+	if (h_dentry && h_dentry->d_inode)
+		err = vfsub_getattr(au_sbr_mnt(sb, bindex), h_dentry, st,
+				    au_test_dlgt(mnt_flags));
+	dput(h_dentry);
+	if (!err) {
+		au_cpup_attr_all(inode);
+	fill:
+		generic_fillattr(inode, st);
+	}
+
+ out:
+	aufs_read_unlock(dentry, AuLock_IR);
+	AuTraceErr(err);
+	return err;
+}
diff --git a/ubuntu/aufs/hinode.h b/ubuntu/aufs/hinode.h
index ad46630..95ad857 100644
--- a/ubuntu/aufs/hinode.h
+++ b/ubuntu/aufs/hinode.h
@@ -19,7 +19,7 @@
 /*
  * lower (branch filesystem) inode and setting inotify
  *
- * $Id: hinode.h,v 1.4 2008/05/26 04:04:24 sfjro Exp $
+ * $Id: hinode.h,v 1.9 2008/08/25 01:49:59 sfjro Exp $
  */
 
 #ifndef __AUFS_HINODE_H__
@@ -29,7 +29,7 @@
 
 #include <linux/fs.h>
 #include <linux/inotify.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "super.h"
 #include "vfsub.h"
 
@@ -37,11 +37,11 @@
 
 struct au_hinotify {
 #ifdef CONFIG_AUFS_HINOTIFY
+	spinlock_t		hin_ignore_lock;
+	struct list_head	hin_ignore_list;
+
 	struct inotify_watch	hin_watch;
 	struct inode		*hin_aufs_inode;	/* no get/put */
-
-	/* an array of atomic_t X au_hin_nignore */
-	atomic_t		hin_ignore[0];
 #endif
 };
 
@@ -58,7 +58,10 @@ struct au_hinode {
 
 struct au_hin_ignore {
 #ifdef CONFIG_AUFS_HINOTIFY
-	__u32			ign_events;
+	struct list_head	ign_list;
+
+	pid_t			ign_pid;
+	__u32			ign_events, ign_handled;
 	struct au_hinode	*ign_hinode;
 #endif
 };
@@ -66,6 +69,13 @@ struct au_hin_ignore {
 /* ---------------------------------------------------------------------- */
 
 #ifdef CONFIG_AUFS_HINOTIFY
+/* inotify events */
+static const __u32 AuInMask = (IN_MOVE | IN_DELETE | IN_CREATE
+			       /* | IN_ACCESS */
+			       | IN_MODIFY | IN_ATTRIB
+			       /* | IN_DELETE_SELF | IN_MOVE_SELF */
+	);
+
 static inline
 void au_hin_init(struct au_hinode *hinode, struct au_hinotify *val)
 {
@@ -76,23 +86,24 @@ void au_hin_init(struct au_hinode *hinode, struct au_hinotify *val)
 int au_hin_alloc(struct au_hinode *hinode, struct inode *inode,
 		 struct inode *h_inode);
 void au_hin_free(struct au_hinode *hinode);
-void au_do_hdir_lock(struct inode *h_dir, struct inode *dir,
-		     aufs_bindex_t bindex, unsigned int lsc);
-void au_hdir_unlock(struct inode *h_dir, struct inode *dir,
-		    aufs_bindex_t bindex);
-struct dentry *au_hdir_lock_rename(struct dentry **h_parents,
-				   struct inode **dirs, aufs_bindex_t bindex,
-				   int issamedir);
-void au_hdir_unlock_rename(struct dentry **h_parents, struct inode **dirs,
-			   aufs_bindex_t bindex, int issamedir);
+void au_hin_ctl(struct au_hinode *hinode, const __u32 mask);
 void au_reset_hinotify(struct inode *inode, unsigned int flags);
 
-void au_hin_ignore(struct au_hinode *hinode, __u32 events);
-void au_hin_unignore(struct au_hinode *hinode, __u32 events);
+int au_hin_verify_gen(struct dentry *dentry);
 
 int __init au_inotify_init(void);
 void au_inotify_fin(void);
 
+static inline void au_hin_suspend(struct au_hinode *hinode)
+{
+	au_hin_ctl(hinode, 0);
+}
+
+static inline void au_hin_resume(struct au_hinode *hinode)
+{
+	au_hin_ctl(hinode, AuInMask);
+}
+
 #else
 
 static inline
@@ -113,72 +124,52 @@ static inline void au_hin_free(struct au_hinode *hinode)
 	/* nothing */
 }
 
-static inline
-void au_do_hdir_lock(struct inode *h_dir, struct inode *dir,
-		     aufs_bindex_t bindex, unsigned int lsc)
+static inline void au_reset_hinotify(struct inode *inode, unsigned int flags)
 {
-	mutex_lock_nested(&h_dir->i_mutex, lsc);
+	/* nothing */
 }
 
-static inline
-void au_hdir_unlock(struct inode *h_dir, struct inode *dir,
-		    aufs_bindex_t bindex)
+static inline int au_hin_verify_gen(struct dentry *dentry)
 {
-	mutex_unlock(&h_dir->i_mutex);
+	return 0;
 }
 
-static inline
-struct dentry *au_hdir_lock_rename(struct dentry **h_parents,
-				   struct inode **dirs, aufs_bindex_t bindex,
-				   int issamedir)
+static inline int au_inotify_init(void)
 {
-	return vfsub_lock_rename(h_parents[0], h_parents[1]);
+	return 0;
 }
 
-static inline
-void au_hdir_unlock_rename(struct dentry **h_parents, struct inode **dirs,
-			   aufs_bindex_t bindex, int issamedir)
+#define au_inotify_fin()	do {} while (0)
+
+static inline void au_hin_suspend(struct au_hinode *hinode)
 {
-	vfsub_unlock_rename(h_parents[0], h_parents[1]);
+	/* empty */
 }
 
-static inline void au_reset_hinotify(struct inode *inode, unsigned int flags)
+static inline void au_hin_resume(struct au_hinode *hinode)
 {
-	/* nothing */
+	/* empty */
 }
+#endif /* CONFIG_AUFS_HINOTIFY */
 
-static inline void au_hin_ignore(struct au_hinotify *hinotify, __u32 events)
+#if defined(CONFIG_AUFS_HINOTIFY) && defined(CONFIG_AUFS_DEBUG)
+static inline void au_hin_list_del(struct list_head *e)
 {
-	/* nothing */
+	list_del_init(e);
 }
 
-static inline void au_hin_unignore(struct au_hinotify *hinotify, __u32 events)
+void au_dbg_hin_list(struct vfsub_args *vargs);
+#else
+static inline void au_hin_list_del(struct list_head *e)
 {
-	/* nothing */
+	list_del(e);
 }
 
-static inline int au_inotify_init(void)
+static inline void au_dbg_hin_list(struct vfsub_args *vargs)
 {
-	return 0;
+	/* empty */
 }
-
-#define au_inotify_fin()	do {} while (0)
-#endif /* CONFIG_AUFS_HINOTIFY */
-
-/* ---------------------------------------------------------------------- */
-
-/*
- * au_hdir_lock, au_hdir2_lock
- */
-#define AuLockFunc(name, lsc) \
-static inline \
-void name##_lock(struct inode *h_dir, struct inode *dir, aufs_bindex_t bindex) \
-{ au_do_hdir_lock(h_dir, dir, bindex, AuLsc_I_##lsc); }
-
-AuLockFunc(au_hdir, PARENT);
-AuLockFunc(au_hdir2, PARENT2);
-
-#undef AuLockFunc
+#endif /* CONFIG_AUFS_DEBUG */
 
 /* ---------------------------------------------------------------------- */
 
diff --git a/ubuntu/aufs/hinotify.c b/ubuntu/aufs/hinotify.c
new file mode 100644
index 0000000..e4bea52
--- /dev/null
+++ b/ubuntu/aufs/hinotify.c
@@ -0,0 +1,1133 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * internal/hidden inotify handler
+ *
+ * $Id: hinotify.c,v 1.16 2008/09/08 02:39:54 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+/*
+#ifdef DbgInotify
+#define AuDbgHin(args...)	AuDbg(##args)
+#else
+#define AuDbgHin(args...)	do {} while ()
+#endif
+*/
+
+static struct inotify_handle *in_handle;
+
+AuCacheFuncs(hinotify, AuCache_HINOTIFY);
+
+int au_hin_alloc(struct au_hinode *hinode, struct inode *inode,
+		 struct inode *h_inode)
+{
+	int err;
+	struct au_hinotify *hin;
+	s32 wd;
+
+	LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino);
+
+	err = -ENOMEM;
+	hin = au_cache_alloc_hinotify();
+	if (hin) {
+		AuDebugOn(hinode->hi_notify);
+		hinode->hi_notify = hin;
+		spin_lock_init(&hin->hin_ignore_lock);
+		INIT_LIST_HEAD(&hin->hin_ignore_list);
+		hin->hin_aufs_inode = inode;
+
+		inotify_init_watch(&hin->hin_watch);
+		wd = inotify_add_watch(in_handle, &hin->hin_watch, h_inode,
+				       AuInMask);
+		if (wd >= 0)
+			return 0; /* success */
+
+		err = wd;
+		put_inotify_watch(&hin->hin_watch);
+		au_cache_free_hinotify(hin);
+		hinode->hi_notify = NULL;
+	}
+
+	AuTraceErr(err);
+	return err;
+}
+
+void au_hin_free(struct au_hinode *hinode)
+{
+	int err;
+	struct au_hinotify *hin;
+
+	AuTraceEnter();
+
+	hin = hinode->hi_notify;
+	if (unlikely(hin)) {
+		err = 0;
+		if (atomic_read(&hin->hin_watch.count))
+			err = inotify_rm_watch(in_handle, &hin->hin_watch);
+		if (unlikely(err))
+			/* it means the watch is already removed */
+			LKTRTrace("failed inotify_rm_watch() %d\n", err);
+		au_cache_free_hinotify(hin);
+		hinode->hi_notify = NULL;
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_hin_ctl(struct au_hinode *hinode, const __u32 mask)
+{
+	struct inode *h_inode;
+	struct inotify_watch *watch;
+
+	h_inode = hinode->hi_inode;
+	LKTRTrace("hi%lu, sb %p, 0x%x\n", h_inode->i_ino, h_inode->i_sb, mask);
+	IMustLock(h_inode);
+	if (!hinode->hi_notify)
+		return;
+
+	watch = &hinode->hi_notify->hin_watch;
+#if 0 /* reserved for future use */
+	{
+		u32 wd;
+		wd = inotify_find_update_watch(in_handle, h_inode, mask);
+		AuTraceErr(wd);
+		/* ignore an err; */
+	}
+#else
+	/* struct inotify_handle is hidden */
+	mutex_lock(&h_inode->inotify_mutex);
+	/* mutex_lock(&watch->ih->mutex); */
+	watch->mask = mask;
+	/* mutex_unlock(&watch->ih->mutex); */
+	mutex_unlock(&h_inode->inotify_mutex);
+#endif
+	LKTRTrace("watch %p, mask %u\n", watch, watch->mask);
+}
+
+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 = au_ibend(inode);
+	for (bindex = au_ibstart(inode); bindex <= bend; bindex++) {
+		hi = au_h_iptr(inode, bindex);
+		if (hi) {
+			/* mutex_lock_nested(&hi->i_mutex, AuLsc_I_CHILD); */
+			iwhdentry = au_hi_wh(inode, bindex);
+			if (unlikely(iwhdentry))
+				dget(iwhdentry);
+			au_igrab(hi);
+			au_set_h_iptr(inode, bindex, NULL, 0);
+			au_set_h_iptr(inode, bindex, au_igrab(hi),
+				      flags & ~AuHi_XINO);
+			iput(hi);
+			dput(iwhdentry);
+			/* mutex_unlock(&hi->i_mutex); */
+		}
+	}
+}
+
+/* ---------------------------------------------------------------------- */
+
+void au_unpin_gp(struct au_pin *args)
+{
+	struct au_pin1 *gp;
+
+	gp = au_pin_gp(args);
+	AuDebugOn(!gp);
+	if (gp->dentry)
+		LKTRTrace("%.*s\n", AuDLNPair(gp->dentry));
+	else
+		AuTraceEnter();
+
+	au_do_unpin(gp, NULL);
+}
+
+int au_hin_verify_gen(struct dentry *dentry)
+{
+	struct super_block *sb = dentry->d_sb;
+	au_gen_t sigen;
+	struct inode *inode;
+
+	if (!au_opt_test(au_mntflags(sb), UDBA_INOTIFY))
+		return 0;
+
+	sigen = au_sigen(dentry->d_sb);
+	inode = dentry->d_inode;
+	return (au_digen(dentry) != sigen
+		|| (inode && au_iigen(inode) != sigen));
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* cf. fsnotify_change() */
+__u32 vfsub_events_notify_change(struct iattr *ia)
+{
+	__u32 events;
+	const unsigned int amtime = (ATTR_ATIME | ATTR_MTIME);
+
+	events = 0;
+	if ((ia->ia_valid & (ATTR_UID | ATTR_GID | ATTR_MODE))
+	    || (ia->ia_valid & amtime) == amtime)
+		events |= IN_ATTRIB;
+	if ((ia->ia_valid & ATTR_SIZE)
+	    || (ia->ia_valid & amtime) == ATTR_MTIME)
+		events |= IN_MODIFY;
+	return events;
+}
+
+void vfsub_ign_hinode(struct vfsub_args *vargs, __u32 events,
+		      struct au_hinode *hinode)
+{
+	struct au_hinotify *hin;
+	struct super_block *sb;
+	struct au_hin_ignore *ign;
+
+	if (!hinode)
+		return;
+
+	hin = hinode->hi_notify;
+	if (!hin || !hin->hin_watch.mask)
+		return;
+
+	sb = hin->hin_aufs_inode->i_sb;
+	AuDebugOn(!au_opt_test(au_mntflags(sb), UDBA_INOTIFY));
+
+	ign = vargs->ignore + vargs->nignore++;
+	ign->ign_events = events;
+	ign->ign_handled = 0;
+	ign->ign_hinode = hinode;
+
+	{
+		struct inode *h_inode;
+		h_inode = hinode->hi_inode;
+		if (!mutex_is_locked(&h_inode->i_mutex))
+			au_dbg_blocked();
+		IMustLock(h_inode);
+	}
+}
+
+static void au_hin_ignore(struct au_hin_ignore *ign)
+{
+	struct au_hinode *hinode;
+	__u32 events;
+	struct au_hinotify *hin;
+	struct inode *h_inode;
+
+	hinode = ign->ign_hinode;
+	events = ign->ign_events;
+	LKTRTrace("0x%x\n", events);
+	AuDebugOn(!hinode || !events);
+
+	hin = hinode->hi_notify;
+	h_inode = hinode->hi_inode;
+	if (h_inode && hin) {
+		LKTRTrace("hi%lu\n", h_inode->i_ino);
+#ifdef DbgInotify
+		AuDbg("hi%lu, 0x%x\n", h_inode->i_ino, events);
+#endif
+
+		spin_lock(&hin->hin_ignore_lock);
+		list_add(&ign->ign_list, &hin->hin_ignore_list);
+		spin_unlock(&hin->hin_ignore_lock);
+		/* AuDbg("list_add %p, 0x%x\n", ign, events); */
+	}
+#if 1 /* todo: test dlgt */
+	else
+		/*
+		 * it may happen by this scenario.
+		 * - a file and its parent dir exist on two branches
+		 * - a file on the upper branch is opened
+		 * - the parent dir and the file are removed by udba
+		 * - the parent is re-accessed, and new dentry/inode in
+		 *   aufs is generated for it, based upon the one on the lower
+		 *   branch
+		 * - the opened file is re-accessed, re-validated, and it may be
+		 *   re-connected to the new parent dentry
+		 * it means the file in aufs cannot get the actual removed
+		 * parent dir on the branch.
+		 */
+		INIT_LIST_HEAD(&ign->ign_list);
+#endif
+}
+
+static void au_hin_unignore(struct au_hin_ignore *ign)
+{
+	struct au_hinode *hinode;
+	__u32 events;
+	struct au_hinotify *hin;
+	struct inode *h_inode;
+
+	hinode = ign->ign_hinode;
+	events = ign->ign_events;
+	LKTRTrace("0x%x\n", events);
+	/* AuDbg("0x%x\n", events); */
+	AuDebugOn(!hinode || !events);
+
+	hin = hinode->hi_notify;
+	h_inode = hinode->hi_inode;
+	if (unlikely(!h_inode || !hin))
+		return;
+	LKTRTrace("hi%lu\n", h_inode->i_ino);
+#ifdef DbgInotify
+	AuDbg("hi%lu, 0x%x\n", h_inode->i_ino, events);
+#endif
+
+	spin_lock(&hin->hin_ignore_lock);
+	au_hin_list_del(&ign->ign_list);
+	spin_unlock(&hin->hin_ignore_lock);
+	/* AuDbg("list_del %p, 0x%x\n", ign, events); */
+}
+
+static int au_hin_test_ignore(u32 mask, struct au_hinotify *hin)
+{
+	int do_ignore;
+	struct au_hin_ignore *ign, *tmp;
+	u32 events;
+
+	do_ignore = 0;
+	spin_lock(&hin->hin_ignore_lock);
+	list_for_each_entry_safe(ign, tmp, &hin->hin_ignore_list, ign_list) {
+		/* AuDbg("ign %p\n", ign); */
+		if (ign->ign_pid == current->pid) {
+			events = (mask & ign->ign_events);
+			if (events) {
+				do_ignore = 1;
+				ign->ign_handled |= events;
+				if (ign->ign_events == ign->ign_handled) {
+					list_del_init(&ign->ign_list);
+					/*
+					AuDbg("list_del %p, 0x%x\n",
+					      ign, events);
+					*/
+				}
+				break;
+			}
+		}
+	}
+	spin_unlock(&hin->hin_ignore_lock);
+
+	return do_ignore;
+}
+
+void vfsub_ignore(struct vfsub_args *vargs)
+{
+	int n;
+	struct au_hin_ignore *ign;
+	struct super_block *sb;
+	struct au_hinode *hinode;
+	struct inode *h_inode;
+
+	n = vargs->nignore;
+	if (!n)
+		return;
+
+	ign = vargs->ignore;
+	hinode = ign->ign_hinode;
+	sb = hinode->hi_notify->hin_aufs_inode->i_sb;
+	h_inode = hinode->hi_inode;
+	if (unlikely(au_opt_test(au_mntflags(sb), UDBA_INOTIFY))) {
+		if (!mutex_is_locked(&h_inode->i_mutex))
+			au_dbg_blocked();
+		IMustLock(h_inode);
+	}
+	while (n-- > 0) {
+		ign->ign_pid = current->pid;
+		au_hin_ignore(ign++);
+	}
+}
+
+void vfsub_unignore(struct vfsub_args *vargs)
+{
+	int n;
+	struct au_hin_ignore *ign;
+
+	n = vargs->nignore;
+	if (!n)
+		return;
+
+	ign = vargs->ignore;
+	while (n-- > 0)
+		au_hin_unignore(ign++);
+}
+
+#ifdef CONFIG_AUFS_DEBUG
+void au_dbg_hin_list(struct vfsub_args *vargs)
+{
+	int n;
+	struct au_hin_ignore *ign;
+
+	n = vargs->nignore;
+	if (!n)
+		return;
+
+	ign = vargs->ignore;
+	while (n-- > 0) {
+		/* AuDebugOn(!list_empty(&ign++->ign_list)); */
+		if (list_empty(&ign++->ign_list))
+			continue;
+		ign--;
+		AuDbg("%d: pid %d, 0x%x\n",
+		      n + 1, ign->ign_pid, ign->ign_events);
+		ign++;
+		au_dbg_blocked();
+	}
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+static char *in_name(u32 mask)
+{
+#ifdef CONFIG_AUFS_DEBUG
+#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
+	return "??";
+#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", AuDLNPair(d));
+		dname = &d->d_name;
+		if (dname->len != nlen || memcmp(dname->name, name, nlen))
+			continue;
+		if (!atomic_read(&d->d_count) || !d->d_fsdata) {
+			spin_lock(&d->d_lock);
+			__d_drop(d);
+			spin_unlock(&d->d_lock);
+			continue;
+		}
+
+		dentry = dget(d);
+		break;
+	}
+	spin_unlock(&dcache_lock);
+	dput(parent);
+
+	if (dentry) {
+#if 0
+	lktr_set_pid(current->pid, LktrArrayPid);
+	AuDbgDentry(dentry);
+	lktr_clear_pid(current->pid, LktrArrayPid);
+#endif
+		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 au_xino_entry xinoe;
+	int err;
+
+	LKTRTrace("b%d, hi%lu\n", bindex, (unsigned long)h_ino);
+	AuDebugOn(!au_opt_test_xino(au_mntflags(sb)));
+
+	inode = NULL;
+	err = au_xino_read(sb, bindex, h_ino, &xinoe);
+	if (!err && xinoe.ino)
+		inode = ilookup(sb, xinoe.ino);
+	if (!inode)
+		goto out;
+	if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
+		AuWarn("wrong root branch\n");
+		iput(inode);
+		inode = NULL;
+		goto out;
+	}
+
+	ii_write_lock_child(inode);
+
+ out:
+	return inode;
+}
+
+static int hin_xino(struct inode *inode, struct inode *h_inode)
+{
+	int err;
+	aufs_bindex_t bindex, bend, bfound, bstart;
+	struct inode *h_i;
+
+	LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino);
+
+	err = 0;
+	if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
+		AuWarn("branch root dir was changed\n");
+		goto out;
+	}
+
+	bfound = -1;
+	bend = au_ibend(inode);
+	bstart = au_ibstart(inode);
+#if 0 /* reserved for future use */
+	if (bindex == bend) {
+		/* keep this ino in rename case */
+		goto out;
+	}
+#endif
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		if (au_h_iptr(inode, bindex) == h_inode) {
+			bfound = bindex;
+			break;
+		}
+	}
+	if (bfound < 0)
+		goto out;
+
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		h_i = au_h_iptr(inode, bindex);
+		if (h_i)
+			err = au_xino_write0(inode->i_sb, bindex, h_i->i_ino,
+					     0);
+		/* ignore this error */
+		/* bad action? */
+	}
+
+	/* children inode number will be broken */
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+static int hin_gen_tree(struct dentry *dentry)
+{
+	int err, i, j, ndentry;
+	struct au_dcsub_pages dpages;
+	struct au_dpage *dpage;
+	struct dentry **dentries;
+
+	LKTRTrace("%.*s\n", AuDLNPair(dentry));
+
+	err = au_dpages_init(&dpages, GFP_NOFS);
+	if (unlikely(err))
+		goto out;
+	err = au_dcsub_pages(&dpages, dentry, NULL, NULL);
+	if (unlikely(err))
+		goto out_dpages;
+
+	for (i = 0; i < dpages.ndpage; i++) {
+		dpage = dpages.dpages + i;
+		dentries = dpage->dentries;
+		ndentry = dpage->ndentry;
+		for (j = 0; j < ndentry; j++) {
+			struct dentry *d;
+			d = dentries[j];
+			LKTRTrace("%.*s\n", AuDLNPair(d));
+			if (IS_ROOT(d))
+				continue;
+
+			d_drop(d);
+			au_digen_dec(d);
+			if (d->d_inode)
+				/* todo: reset children xino?
+				   cached children only? */
+				au_iigen_dec(d->d_inode);
+		}
+	}
+
+ out_dpages:
+	au_dpages_free(&dpages);
+
+	/* discard children */
+	dentry_unhash(dentry);
+	dput(dentry);
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+/*
+ * return 0 if processed.
+ */
+static int hin_gen_by_inode(char *name, unsigned int nlen, struct inode *inode,
+			    const unsigned int isdir)
+{
+	int err;
+	struct dentry *d;
+	struct qstr *dname;
+
+	LKTRTrace("%.*s, i%lu\n", nlen, name, inode->i_ino);
+
+	err = 1;
+	if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
+		AuWarn("branch root dir was changed\n");
+		err = 0;
+		goto out;
+	}
+
+	if (!isdir) {
+		AuDebugOn(!name);
+		au_iigen_dec(inode);
+		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 {
+		au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIRS);
+		d = d_find_alias(inode);
+		if (!d) {
+			au_iigen_dec(inode);
+			goto out;
+		}
+
+		dname = &d->d_name;
+		if (dname->len == nlen && !memcmp(dname->name, name, nlen))
+			err = hin_gen_tree(d);
+		dput(d);
+	}
+
+ out:
+	AuTraceErr(err);
+	return err;
+}
+
+static int hin_gen_by_name(struct dentry *dentry, const unsigned int isdir)
+{
+	int err;
+	struct inode *inode;
+
+	LKTRTrace("%.*s\n", AuDLNPair(dentry));
+
+	inode = dentry->d_inode;
+	if (IS_ROOT(dentry)
+	    /* || (inode && inode->i_ino == AUFS_ROOT_INO) */
+		) {
+		AuWarn("branch root dir was changed\n");
+		return 0;
+	}
+
+	err = 0;
+	if (!isdir) {
+		d_drop(dentry);
+		au_digen_dec(dentry);
+		if (inode)
+			au_iigen_dec(inode);
+	} else {
+		au_fset_si(au_sbi(dentry->d_sb), FAILED_REFRESH_DIRS);
+		if (inode)
+			err = hin_gen_tree(dentry);
+	}
+
+	AuTraceErr(err);
+	return err;
+}
+
+static void hin_attr(struct inode *inode, struct inode *h_inode)
+{
+	struct dentry *h_dentry;
+
+	LKTRTrace("i%lu, hi%lu\n", inode->i_ino, h_inode->i_ino);
+
+	if (au_h_iptr(inode, au_ibstart(inode)) != h_inode)
+		return;
+
+	h_dentry = d_find_alias(h_inode);
+	if (h_dentry) {
+		au_update_fuse_h_inode(NULL, h_dentry);
+		/* ignore an error*/
+		dput(h_dentry);
+	}
+
+	au_cpup_attr_all(inode);
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* hinotify job flags */
+#define AuHinJob_XINO0	1
+#define AuHinJob_GEN	(1 << 1)
+#define AuHinJob_DIRENT	(1 << 2)
+#define AuHinJob_ATTR	(1 << 3)
+#define AuHinJob_ISDIR	(1 << 4)
+#define AuHinJob_TRYXINO0 (1 << 5)
+#define AuHinJob_MNTPNT	(1 << 6)
+#define au_ftest_hinjob(flags, name)	((flags) & AuHinJob_##name)
+#define au_fset_hinjob(flags, name)	{ (flags) |= AuHinJob_##name; }
+#define au_fclr_hinjob(flags, name)	{ (flags) &= ~AuHinJob_##name; }
+
+struct hin_job_args {
+	unsigned int flags;
+	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)
+{
+	const unsigned int isdir = au_ftest_hinjob(a->flags, ISDIR);
+
+	/* reset xino */
+	if (au_ftest_hinjob(a->flags, XINO0) && a->inode)
+		hin_xino(a->inode, a->h_inode);
+	/* ignore this error */
+
+	if (au_ftest_hinjob(a->flags, TRYXINO0)
+	    && a->inode
+	    && a->h_inode) {
+		mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
+		if (!a->h_inode->i_nlink)
+			hin_xino(a->inode, a->h_inode);
+		/* ignore this error */
+		mutex_unlock(&a->h_inode->i_mutex);
+	}
+
+	/* make the generation obsolete */
+	if (au_ftest_hinjob(a->flags, GEN)) {
+		int err = -1;
+		if (a->inode)
+			err = hin_gen_by_inode(a->h_name, a->h_nlen, a->inode,
+					       isdir);
+		if (err && a->dentry)
+			hin_gen_by_name(a->dentry, isdir);
+		/* ignore this error */
+	}
+
+	/* make dir entries obsolete */
+	if (au_ftest_hinjob(a->flags, DIRENT) && a->inode) {
+		struct au_vdir *vdir;
+		IiMustWriteLock(a->inode);
+		vdir = au_ivdir(a->inode);
+		if (vdir)
+			vdir->vd_jiffy = 0;
+		/* IMustLock(a->inode); */
+		/* a->inode->i_version++; */
+	}
+
+	/* update the attr */
+	if (au_ftest_hinjob(a->flags, ATTR) && a->inode && a->h_inode)
+		hin_attr(a->inode, a->h_inode);
+
+	/* can do nothing but warn */
+	if (au_ftest_hinjob(a->flags, MNTPNT)
+	    && a->dentry
+	    && d_mountpoint(a->dentry))
+		AuWarn("mount-point %.*s is removed or renamed\n",
+		       AuDLNPair(a->dentry));
+
+	return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+enum { CHILD, PARENT };
+struct postproc_args {
+	struct inode *h_dir, *dir, *h_child_inode;
+	u32 mask;
+	unsigned int flags[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;
+	unsigned char xino, try_iput;
+	int err;
+	struct inode *inode;
+	ino_t h_ino;
+	struct hin_job_args args;
+	struct dentry *dentry;
+	struct au_sbinfo *sbinfo;
+
+	AuDebugOn(!_args);
+	AuDebugOn(!a->h_dir);
+	AuDebugOn(!a->dir);
+	AuDebugOn(!a->mask);
+	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;
+	/*
+	 * do not lock a->dir->i_mutex here
+	 * because of d_revalidate() may cause a deadlock.
+	 */
+	sb = a->dir->i_sb;
+	AuDebugOn(!sb);
+	sbinfo = au_sbi(sb);
+	AuDebugOn(!sbinfo);
+	/* big aufs lock */
+	si_noflush_write_lock(sb);
+
+	ii_read_lock_parent(a->dir);
+	bfound = -1;
+	bend = au_ibend(a->dir);
+	for (bindex = au_ibstart(a->dir); bindex <= bend; bindex++)
+		if (au_h_iptr(a->dir, bindex) == a->h_dir) {
+			bfound = bindex;
+			break;
+		}
+	ii_read_unlock(a->dir);
+	if (unlikely(bfound < 0))
+		goto out;
+
+	xino = !!au_opt_test_xino(au_mntflags(sb));
+	h_ino = 0;
+	if (a->h_child_inode)
+		h_ino = a->h_child_inode->i_ino;
+
+	if (a->h_child_nlen
+	    && (au_ftest_hinjob(a->flags[CHILD], GEN)
+		|| au_ftest_hinjob(a->flags[CHILD], MNTPNT)))
+		dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen,
+					      a->dir);
+	try_iput = 0;
+	if (dentry)
+		inode = dentry->d_inode;
+	if (xino && !inode && h_ino
+	    && (au_ftest_hinjob(a->flags[CHILD], XINO0)
+		|| au_ftest_hinjob(a->flags[CHILD], TRYXINO0)
+		|| au_ftest_hinjob(a->flags[CHILD], GEN)
+		|| au_ftest_hinjob(a->flags[CHILD], ATTR))) {
+		inode = lookup_wlock_by_ino(sb, bfound, h_ino);
+		try_iput = 1;
+	    }
+
+	args.flags = a->flags[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) {
+		if (dentry->d_fsdata)
+			di_write_unlock(dentry);
+		dput(dentry);
+	}
+	if (inode && try_iput) {
+		ii_write_unlock(inode);
+		iput(inode);
+	}
+
+	ii_write_lock_parent(a->dir);
+	args.flags = a->flags[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:
+	au_nwt_done(&sbinfo->si_nowait);
+	si_write_unlock(sb);
+
+	iput(a->h_child_inode);
+	iput(a->h_dir);
+	iput(a->dir);
+	kfree(a);
+}
+
+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 au_hinotify *hinotify;
+	struct postproc_args *args;
+	int len, wkq_err;
+	unsigned char isdir, isroot, wh;
+	char *p;
+	struct inode *dir;
+	unsigned int flags[2];
+
+	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 IN_UNMOUNT happens, there must be another bug */
+	if (mask & (IN_IGNORED | IN_UNMOUNT)) {
+		put_inotify_watch(watch);
+		return;
+	}
+
+#ifdef DbgInotify
+	if (!h_child_name || strcmp(h_child_name, AUFS_XINO_FNAME))
+		AuDbg("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
+
+	hinotify = container_of(watch, struct au_hinotify, hin_watch);
+	AuDebugOn(!hinotify || !hinotify->hin_aufs_inode);
+	if (au_hin_test_ignore(mask, hinotify)) {
+#ifdef DbgInotify
+		AuDbg("ignored\n");
+#endif
+		return;
+	}
+#if 0 /* tmp debug */
+	if (h_child_name && !strcmp(h_child_name, AUFS_XINO_FNAME))
+	{
+	AuDbg("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);
+	//au_dbg_blocked();
+	}
+#endif
+
+	dir = igrab(hinotify->hin_aufs_inode);
+	if (!dir)
+		return;
+	isroot = (dir->i_ino == AUFS_ROOT_INO);
+	len = 0;
+	wh = 0;
+	if (h_child_name) {
+		len = strlen(h_child_name);
+		if (!memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
+			h_child_name += AUFS_WH_PFX_LEN;
+			len -= AUFS_WH_PFX_LEN;
+			wh = 1;
+		}
+	}
+
+	isdir = 0;
+	if (h_child_inode)
+		isdir = !!S_ISDIR(h_child_inode->i_mode);
+	flags[PARENT] = AuHinJob_ISDIR;
+	flags[CHILD] = 0;
+	if (isdir)
+		flags[CHILD] = AuHinJob_ISDIR;
+	switch (mask & IN_ALL_EVENTS) {
+	case IN_MODIFY:
+		/*FALLTHROUGH*/
+	case IN_ATTRIB:
+		if (h_child_inode) {
+			if (!wh)
+				au_fset_hinjob(flags[CHILD], ATTR);
+		} else
+			au_fset_hinjob(flags[PARENT], ATTR);
+		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);
+		au_fset_hinjob(flags[CHILD], GEN);
+		au_fset_hinjob(flags[CHILD], ATTR);
+		if (1 || isdir)
+			au_fset_hinjob(flags[CHILD], XINO0);
+		au_fset_hinjob(flags[CHILD], MNTPNT);
+
+		au_fset_hinjob(flags[PARENT], ATTR);
+		au_fset_hinjob(flags[PARENT], DIRENT);
+		break;
+
+	case IN_CREATE:
+		AuDebugOn(!h_child_name || !h_child_inode);
+		au_fset_hinjob(flags[PARENT], ATTR);
+		au_fset_hinjob(flags[PARENT], DIRENT);
+		au_fset_hinjob(flags[CHILD], GEN);
+		/* hard link */
+		if (!isdir && h_child_inode->i_nlink > 1)
+			au_fset_hinjob(flags[CHILD], ATTR);
+		break;
+
+	case IN_DELETE:
+		/*
+		 * aufs never be able to get this child inode.
+		 * revalidation should be in d_revalidate()
+		 * by checking i_nlink, i_generation or d_unhashed().
+		 */
+		AuDebugOn(!h_child_name);
+		au_fset_hinjob(flags[PARENT], ATTR);
+		au_fset_hinjob(flags[PARENT], DIRENT);
+		au_fset_hinjob(flags[CHILD], GEN);
+		au_fset_hinjob(flags[CHILD], TRYXINO0);
+		au_fset_hinjob(flags[CHILD], MNTPNT);
+		break;
+
+	case IN_DELETE_SELF:
+#if 0
+		if (!isroot)
+			au_fset_hinjob(flags[PARENT], GEN);
+		/*FALLTHROUGH*/
+#endif
+
+	case IN_MOVE_SELF:
+#if 0
+		/*
+		 * when an inotify is set to an aufs inode,
+		 * such inode can be isolated and this event can be fired
+		 * solely.
+		 */
+		AuDebugOn(h_child_name || h_child_inode);
+		if (unlikely(isroot)) {
+			AuWarn("root branch was moved\n");
+			iput(dir);
+			return;
+		}
+		au_fset_hinjob(flags[PARENT], XINO0);
+		au_fset_hinjob(flags[PARENT], GEN);
+		au_fset_hinjob(flags[PARENT], ATTR);
+		au_fset_hinjob(flags[PARENT], DIRENT);
+		/* au_fset_hinjob(flags[PARENT], MNTPNT); */
+		break;
+#endif
+
+	case IN_ACCESS:
+	default:
+		AuDebugOn(1);
+	}
+
+	if (wh)
+		h_child_inode = NULL;
+
+	/* iput() and kfree() will be called in postproc() */
+	/*
+	 * inotify_mutex is already acquired and kmalloc/prune_icache may lock
+	 * iprune_mutex. strange.
+	 */
+	lockdep_off();
+	args = kmalloc(sizeof(*args) + len + 1, GFP_NOFS);
+	lockdep_on();
+	if (unlikely(!args)) {
+		AuErr1("no memory\n");
+		iput(dir);
+		return;
+	}
+	args->flags[PARENT] = flags[PARENT];
+	args->flags[CHILD] = flags[CHILD];
+	args->mask = mask;
+	args->dir = dir;
+	args->h_dir = igrab(watch->inode);
+	if (h_child_inode)
+		h_child_inode = igrab(h_child_inode); /* can be NULL */
+	args->h_child_inode = h_child_inode;
+	args->h_child_nlen = len;
+	if (len) {
+		p = (void *)args;
+		p += sizeof(*args);
+		memcpy(p, h_child_name, len + 1);
+	}
+
+	lockdep_off();
+	wkq_err = au_wkq_nowait(postproc, args, dir->i_sb, /*dlgt*/0);
+	lockdep_on();
+	if (unlikely(wkq_err))
+		AuErr("wkq %d\n", wkq_err);
+}
+
+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
+};
+
+/* ---------------------------------------------------------------------- */
+
+static void au_hin_destroy_cache(void)
+{
+	kmem_cache_destroy(au_cachep[AuCache_HINOTIFY]);
+	au_cachep[AuCache_HINOTIFY] = NULL;
+}
+
+int __init au_inotify_init(void)
+{
+	int err;
+
+	err = -ENOMEM;
+	au_cachep[AuCache_HINOTIFY] = AuCache(au_hinotify);
+	if (au_cachep[AuCache_HINOTIFY]) {
+		err = 0;
+		in_handle = inotify_init(&aufs_inotify_ops);
+		if (IS_ERR(in_handle)) {
+			err = PTR_ERR(in_handle);
+			au_hin_destroy_cache();
+		}
+	}
+	AuTraceErr(err);
+	return err;
+}
+
+void au_inotify_fin(void)
+{
+	inotify_destroy(in_handle);
+	if (au_cachep[AuCache_HINOTIFY])
+		au_hin_destroy_cache();
+}
diff --git a/ubuntu/aufs/i_op.c b/ubuntu/aufs/i_op.c
index fd77dae..7843831 100644
--- a/ubuntu/aufs/i_op.c
+++ b/ubuntu/aufs/i_op.c
@@ -19,15 +19,63 @@
 /*
  * inode operations (except add/del/rename)
  *
- * $Id: i_op.c,v 1.8 2008/06/09 01:10:26 sfjro Exp $
+ * $Id: i_op.c,v 1.19 2008/09/08 02:39:57 sfjro Exp $
  */
 
 #include <linux/fs_stack.h>
 #include <linux/uaccess.h>
 #include "aufs.h"
 
+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);
+
+	if (!nd || !nd->path.dentry) {
+		si_read_lock(sb, AuLock_FLUSH);
+		ii_read_lock_child(inode);
+	} else if (nd->path.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->path.dentry, 0);
+	} else {
+		locked = 2;
+		aufs_read_lock(nd->path.dentry, AuLock_FLUSH | AuLock_IR);
+	}
+	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);
+
+	switch (locked) {
+	case 0:
+		ii_read_unlock(inode);
+		si_read_unlock(sb);
+		break;
+	case 1:
+		di_read_unlock(nd->path.dentry, 0);
+		ii_read_unlock(inode);
+		si_read_unlock(sb);
+		break;
+	case 2:
+		aufs_read_unlock(nd->path.dentry, AuLock_IR);
+		break;
+	default:
+		BUG();
+	}
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
 static int h_permission(struct inode *h_inode, int mask,
-			struct nameidata *fake_nd, int brperm, int dlgt)
+			struct vfsmount *h_mnt, int brperm, int dlgt)
 {
 	int err, submask;
 	const int write_mask = (mask & (MAY_WRITE | MAY_APPEND));
@@ -38,8 +86,7 @@ static int h_permission(struct inode *h_inode, int mask,
 	err = -EACCES;
 	if (unlikely((write_mask && IS_IMMUTABLE(h_inode))
 		     || ((mask & MAY_EXEC) && S_ISREG(h_inode->i_mode)
-			 && fake_nd && fake_nd->path.mnt
-			 && (fake_nd->path.mnt->mnt_flags & MNT_NOEXEC))
+			 && (h_mnt->mnt_flags & MNT_NOEXEC))
 		    ))
 		goto out;
 
@@ -64,7 +111,7 @@ static int h_permission(struct inode *h_inode, int mask,
 
 #if 1 /* todo: export? */
 	if (!err)
-		err = au_security_inode_permission(h_inode, mask, fake_nd,
+		err = au_security_inode_permission(h_inode, mask, NULL,
 						   dlgt);
 #endif
 
@@ -73,77 +120,141 @@ static int h_permission(struct inode *h_inode, int mask,
 	return err;
 }
 
-static int silly_lock(struct inode *inode, struct nameidata *nd)
+static int aufs_permission(struct inode *inode, int mask)
 {
-	int locked = 0;
-	struct super_block *sb = inode->i_sb;
+	int err;
+	aufs_bindex_t bindex, bend;
+	unsigned char locked, dlgt;
+	const unsigned char isdir = S_ISDIR(inode->i_mode);
+	struct inode *h_inode;
+	struct super_block *sb;
+	unsigned int mnt_flags;
+	const int write_mask = (mask & (MAY_WRITE | MAY_APPEND));
 
-	LKTRTrace("i%lu, nd %p\n", inode->i_ino, nd);
+	LKTRTrace("ino %lu, mask 0x%x, isdir %d, write_mask %d\n",
+		  inode->i_ino, mask, isdir, write_mask);
 
-	if (!nd || !nd->path.dentry) {
-		si_read_lock(sb, AuLock_FLUSH);
-		ii_read_lock_child(inode);
-	} else if (nd->path.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->path.dentry, 0);
-	} else {
-		locked = 2;
-		aufs_read_lock(nd->path.dentry, AuLock_FLUSH | AuLock_IR);
+	sb = inode->i_sb;
+	locked = silly_lock(inode, NULL);
+	mnt_flags = au_mntflags(sb);
+	dlgt = !!au_test_dlgt(mnt_flags);
+
+	if (/* unlikely */(!isdir || write_mask
+			   || au_test_dirperm1(mnt_flags))) {
+		h_inode = au_h_iptr(inode, au_ibstart(inode));
+		AuDebugOn(!h_inode
+			  || ((h_inode->i_mode & S_IFMT)
+			      != (inode->i_mode & S_IFMT)));
+		err = 0;
+		bindex = au_ibstart(inode);
+		LKTRTrace("b%d\n", bindex);
+		err = h_permission(h_inode, mask, au_sbr_mnt(sb, bindex),
+				   au_sbr_perm(sb, bindex), dlgt);
+
+		if (write_mask && !err) {
+			/* test whether the upper writable branch exists */
+			err = -EROFS;
+			for (; bindex >= 0; bindex--)
+				if (!au_br_rdonly(au_sbr(sb, bindex))) {
+					err = 0;
+					break;
+				}
+		}
+		goto out;
 	}
-	return locked;
+
+	/* non-write to dir */
+	err = 0;
+	bend = au_ibend(inode);
+	for (bindex = au_ibstart(inode); !err && bindex <= bend; bindex++) {
+		h_inode = au_h_iptr(inode, bindex);
+		if (!h_inode)
+			continue;
+		AuDebugOn(!S_ISDIR(h_inode->i_mode));
+
+		LKTRTrace("b%d\n", bindex);
+		err = h_permission(h_inode, mask, au_sbr_mnt(sb, bindex),
+				   au_sbr_perm(sb, bindex), dlgt);
+	}
+
+ out:
+	silly_unlock(locked, inode, NULL);
+	AuTraceErr(err);
+	return err;
 }
+#else
 
-static void silly_unlock(int locked, struct inode *inode, struct nameidata *nd)
+static int h_permission(struct inode *h_inode, int mask,
+			struct nameidata *fake_nd, int brperm, int dlgt)
 {
-	struct super_block *sb = inode->i_sb;
+	int err, submask;
+	const int write_mask = (mask & (MAY_WRITE | MAY_APPEND));
 
-	LKTRTrace("locked %d, i%lu, nd %p\n", locked, inode->i_ino, nd);
+	LKTRTrace("ino %lu, mask 0x%x, brperm 0x%x\n",
+		  h_inode->i_ino, mask, brperm);
 
-	switch (locked) {
-	case 0:
-		ii_read_unlock(inode);
-		si_read_unlock(sb);
-		break;
-	case 1:
-		di_read_unlock(nd->path.dentry, 0);
-		ii_read_unlock(inode);
-		si_read_unlock(sb);
-		break;
-	case 2:
-		aufs_read_unlock(nd->path.dentry, AuLock_FLUSH | AuLock_IR);
-		break;
-	default:
-		BUG();
+	err = -EACCES;
+	if (unlikely((write_mask && IS_IMMUTABLE(h_inode))
+		     || ((mask & MAY_EXEC) && S_ISREG(h_inode->i_mode)
+			 && fake_nd && fake_nd->path.mnt
+			 && (fake_nd->path.mnt->mnt_flags & MNT_NOEXEC))
+		    ))
+		goto out;
+
+	/*
+	 * - skip hidden fs test in the case of write to ro branch.
+	 * - nfs dir permission write check is optimized, but a policy for
+	 *   link/rename requires a real check.
+	 */
+	submask = mask & ~MAY_APPEND;
+	if ((write_mask && !au_br_writable(brperm))
+	    || (au_test_nfs(h_inode->i_sb) && S_ISDIR(h_inode->i_mode)
+		&& write_mask && !(mask & MAY_READ))
+	    || !h_inode->i_op
+	    || !h_inode->i_op->permission) {
+		/* LKTRLabel(generic_permission); */
+		err = generic_permission(h_inode, submask, NULL);
+	} else {
+		/* LKTRLabel(h_inode->permission); */
+		err = h_inode->i_op->permission(h_inode, submask, fake_nd);
+		AuTraceErr(err);
 	}
+
+#if 1 /* todo: export? */
+	if (!err)
+		err = au_security_inode_permission(h_inode, mask, fake_nd,
+						   dlgt);
+#endif
+
+ out:
+	AuTraceErr(err);
+	return err;
 }
 
-static int aufs_permission(struct inode *inode, int mask)
+static int aufs_permission(struct inode *inode, int mask, struct nameidata *nd)
 {
-	int err, locked, dlgt;
+	int err;
 	aufs_bindex_t bindex, bend;
+	unsigned char locked, dlgt, do_nd;
+	const unsigned char isdir = S_ISDIR(inode->i_mode);
 	struct inode *h_inode;
 	struct super_block *sb;
 	unsigned int mnt_flags;
-	struct nameidata fake_nd, *p, *nd = NULL;
+	struct path path;
 	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, "
+	LKTRTrace("ino %lu, mask 0x%x, isdir %d, write_mask %d, "
 		  "nd %d{%d, %d}\n",
-		  inode->i_ino, mask, nondir, write_mask,
+		  inode->i_ino, mask, isdir, write_mask,
 		  !!nd, nd ? !!nd->path.dentry : 0, nd ? !!nd->path.mnt : 0);
 
 	sb = inode->i_sb;
 	locked = silly_lock(inode, nd);
+	do_nd = (nd && locked >= 1);
 	mnt_flags = au_mntflags(sb);
 	dlgt = !!au_test_dlgt(mnt_flags);
 
-	if (nd)
-		fake_nd = *nd;
-	if (/* unlikely */(nondir || write_mask
+	if (/* unlikely */(!isdir || write_mask
 			   || au_test_dirperm1(mnt_flags))) {
 		h_inode = au_h_iptr(inode, au_ibstart(inode));
 		AuDebugOn(!h_inode
@@ -151,16 +262,22 @@ static int aufs_permission(struct inode *inode, int mask)
 			      != (inode->i_mode & S_IFMT)));
 		err = 0;
 		bindex = au_ibstart(inode);
-		p = au_fake_dm(&fake_nd, nd, sb, bindex);
-		/* actual test will be delegated to LSM */
-		if (IS_ERR(p))
-			AuDebugOn(PTR_ERR(p) != -ENOENT);
-		else {
-			LKTRTrace("b%d\n", bindex);
-			err = h_permission(h_inode, mask, p,
+		LKTRTrace("b%d\n", bindex);
+		if (do_nd) {
+			path = nd->path;
+			nd->path.mnt = au_sbr_mnt(sb, bindex);
+			nd->path.dentry = au_h_dptr(nd->path.dentry, bindex);
+			path_get(&nd->path);
+			err = h_permission(h_inode, mask, nd,
+					   au_sbr_perm(sb, bindex), dlgt);
+			path_put(&nd->path);
+			nd->path = path;
+		} else {
+			AuDebugOn(nd && nd->path.mnt);
+			err = h_permission(h_inode, mask, nd,
 					   au_sbr_perm(sb, bindex), dlgt);
-			au_fake_dm_release(p);
 		}
+
 		if (write_mask && !err) {
 			/* test whether the upper writable branch exists */
 			err = -EROFS;
@@ -174,6 +291,12 @@ static int aufs_permission(struct inode *inode, int mask)
 	}
 
 	/* non-write to dir */
+	if (do_nd)
+		path = nd->path;
+	else {
+		path.mnt = NULL;
+		path.dentry = NULL;
+	}
 	err = 0;
 	bend = au_ibend(inode);
 	for (bindex = au_ibstart(inode); !err && bindex <= bend; bindex++) {
@@ -182,23 +305,29 @@ static int aufs_permission(struct inode *inode, int mask)
 			continue;
 		AuDebugOn(!S_ISDIR(h_inode->i_mode));
 
-		p = au_fake_dm(&fake_nd, nd, sb, bindex);
-		/* actual test will be delegated to LSM */
-		if (IS_ERR(p))
-			AuDebugOn(PTR_ERR(p) != -ENOENT);
-		else {
-			LKTRTrace("b%d\n", bindex);
-			err = h_permission(h_inode, mask, p,
+		LKTRTrace("b%d\n", bindex);
+		if (do_nd) {
+			nd->path.mnt = au_sbr_mnt(sb, bindex);
+			nd->path.dentry = au_h_dptr(path.dentry, bindex);
+			path_get(&nd->path);
+			err = h_permission(h_inode, mask, nd,
+					   au_sbr_perm(sb, bindex), dlgt);
+			path_put(&nd->path);
+		} else {
+			AuDebugOn(nd && nd->path.mnt);
+			err = h_permission(h_inode, mask, nd,
 					   au_sbr_perm(sb, bindex), dlgt);
-			au_fake_dm_release(p);
 		}
 	}
+	if (do_nd)
+		nd->path = path;
 
  out:
 	silly_unlock(locked, inode, nd);
 	AuTraceErr(err);
 	return err;
 }
+#endif /* KERNEL_VERSION(2, 6, 27) */
 
 /* ---------------------------------------------------------------------- */
 
@@ -209,42 +338,49 @@ static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry,
 	int err, npositive;
 	struct inode *inode, *h_inode;
 	struct nameidata tmp_nd, *ndp;
+	aufs_bindex_t bstart;
+	struct mutex *mtx;
+	struct super_block *sb;
 
 	LKTRTrace("dir %lu, %.*s, nd{0x%x}\n",
 		  dir->i_ino, AuDLNPair(dentry), nd ? nd->flags : 0);
 	AuDebugOn(IS_ROOT(dentry));
 	IMustLock(dir);
 
-	/* nd can be NULL */
-	parent = dentry->d_parent; /* dir inode is locked */
-	aufs_read_lock(parent, AuLock_FLUSH);
+	sb = dir->i_sb;
+	si_read_lock(sb, AuLock_FLUSH);
 	err = au_alloc_dinfo(dentry);
 	ret = ERR_PTR(err);
 	if (unlikely(err))
 		goto out;
 
-	ndp = au_dup_nd(au_sbi(dir->i_sb), &tmp_nd, nd);
+	/* nd can be NULL */
+	ndp = au_dup_nd(au_sbi(sb), &tmp_nd, nd);
+	parent = dentry->d_parent; /* dir inode is locked */
+	di_read_lock_parent(parent, AuLock_IR);
 	npositive = au_lkup_dentry(dentry, au_dbstart(parent), /*type*/0, ndp);
+	di_read_unlock(parent, AuLock_IR);
 	err = npositive;
 	ret = ERR_PTR(err);
 	if (unlikely(err < 0))
 		goto out_unlock;
+
 	inode = NULL;
 	if (npositive) {
-		/*
-		 * stop 'race'-ing between hardlinks under different parents.
-		 */
-		h_inode = au_h_dptr(dentry, au_dbstart(dentry))->d_inode;
+		bstart = au_dbstart(dentry);
+		h_inode = au_h_dptr(dentry, bstart)->d_inode;
 		AuDebugOn(!h_inode);
-		if (h_inode->i_nlink == 1 || S_ISDIR(h_inode->i_mode))
+		if (!S_ISDIR(h_inode->i_mode)) {
+			/*
+			 * stop 'race'-ing between hardlinks under different
+			 * parents.
+			 */
+			mtx = &au_sbr(sb, bstart)->br_xino.xi_nondir_mtx;
+			mutex_lock(mtx);
 			inode = au_new_inode(dentry);
-		else {
-			/* todo: this lock is too large, try br_xino_nondir mutex */
-			static DEFINE_MUTEX(mtx);
-			mutex_lock(&mtx);
+			mutex_unlock(mtx);
+		} else
 			inode = au_new_inode(dentry);
-			mutex_unlock(&mtx);
-		}
 		ret = (void *)inode;
 	}
 	if (!IS_ERR(inode)) {
@@ -261,7 +397,7 @@ static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry,
  out_unlock:
 	di_write_unlock(dentry);
  out:
-	aufs_read_unlock(parent, !AuLock_IR);
+	si_read_unlock(sb);
 	AuTraceErrPtr(ret);
 	return ret;
 }
@@ -279,15 +415,13 @@ int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
 	int err;
 	aufs_bindex_t bcpup, bstart, src_bstart;
 	struct super_block *sb;
-	struct dentry *parent, *src_parent;
+	struct dentry *parent;
 	struct au_sbinfo *sbinfo;
 	const int add_entry = au_ftest_wrdir(args->flags, ADD_ENTRY);
-	const int lock_srcdir = au_ftest_wrdir(args->flags, LOCK_SRCDIR);
 
 	LKTRTrace("%.*s, src %p, {%d, 0x%x}\n",
 		  AuDLNPair(dentry), src_dentry, args->force_btgt, args->flags);
 
-	src_parent = NULL;
 	sb = dentry->d_sb;
 	sbinfo = au_sbi(sb);
 	parent = dget_parent(dentry);
@@ -329,12 +463,6 @@ int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
 		goto out; /* success */
 
 	/* copyup the new parent into the branch we process */
-	if (src_dentry) {
-		src_parent = dget_parent(src_dentry);
-		if (lock_srcdir)
-			di_write_lock_parent2(src_parent);
-	}
-
 	if (add_entry) {
 		au_update_dbstart(dentry);
 		IMustLock(parent->d_inode);
@@ -346,9 +474,9 @@ int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
 	err = 0;
 	if (!au_h_dptr(parent, bcpup)) {
 		if (bstart < bcpup)
-			err = au_cpdown_dirs(dentry, bcpup, src_parent);
+			err = au_cpdown_dirs(dentry, bcpup);
 		else
-			err = au_cpup_dirs(dentry, bcpup, src_parent);
+			err = au_cpup_dirs(dentry, bcpup);
 	}
 	if (!err && add_entry) {
 		struct dentry *h_parent;
@@ -369,9 +497,6 @@ int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
 
 	if (!add_entry)
 		di_write_unlock(parent);
-	if (lock_srcdir)
-		di_write_unlock(src_parent);
-	dput(src_parent);
 	if (!err)
 		err = bcpup; /* success */
  out:
@@ -383,39 +508,166 @@ int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
 
 /* ---------------------------------------------------------------------- */
 
-static void au_hi_lock(struct inode *h_inode, int isdir, struct inode *inode,
-		       aufs_bindex_t bindex)
+struct dentry *au_do_pinned_h_parent(struct au_pin1 *pin, aufs_bindex_t bindex)
 {
-	if (!isdir)
-		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
-	else
-		au_hdir2_lock(h_inode, inode, bindex);
+	if (pin && pin->parent)
+		return au_h_dptr(pin->parent, bindex);
+	return NULL;
+}
+
+void au_do_unpin(struct au_pin1 *p, struct au_pin1 *gp)
+{
+	LKTRTrace("%p, %p\n", p, gp);
+	AuDebugOn(!p);
+
+	if (unlikely(!p->h_dir))
+		return;
+
+	LKTRTrace("p{%.*s, %d, %d, %d, %d}\n",
+		  AuDLNPair(p->dentry), p->lsc_di, p->lsc_hi,
+		  !!p->parent, !!p->h_dir);
+
+	mutex_unlock(&p->h_dir->i_mutex);
+	if (unlikely(gp))
+		au_do_unpin(gp, NULL);
+	if (!p->di_locked)
+		di_read_unlock(p->parent, AuLock_IR);
+	iput(p->h_dir);
+	dput(p->parent);
+	p->parent = NULL;
+	p->h_dir = NULL;
 }
 
-static void au_hi_unlock(struct inode *h_inode, int isdir, struct inode *inode,
-			 aufs_bindex_t bindex)
+int au_do_pin(struct au_pin1 *p, struct au_pin1 *gp, const aufs_bindex_t bindex,
+	      const int do_gp)
 {
-	if (!isdir)
-		mutex_unlock(&h_inode->i_mutex);
+	int err;
+	struct dentry *h_dentry;
+
+	LKTRTrace("%.*s, %d, b%d, %d\n",
+		  AuDLNPair(p->dentry), !!gp, bindex, do_gp);
+	AuDebugOn(do_gp && !gp);
+	/* AuDebugOn(!do_gp && gp); */
+
+	err = 0;
+	if (unlikely(IS_ROOT(p->dentry)))
+		goto out;
+
+	h_dentry = NULL;
+	if (bindex <= au_dbend(p->dentry))
+		h_dentry = au_h_dptr(p->dentry, bindex);
+
+	p->parent = dget_parent(p->dentry);
+	if (!p->di_locked)
+		di_read_lock(p->parent, AuLock_IR, p->lsc_di);
 	else
-		au_hdir_unlock(h_inode, inode, bindex);
+		DiMustAnyLock(p->parent);
+	AuDebugOn(!p->parent->d_inode);
+	p->h_dir = au_igrab(au_h_iptr(p->parent->d_inode, bindex));
+	/* udba case */
+	if (unlikely(p->do_verify && !p->h_dir)) {
+		err = -EIO;
+		if (!p->di_locked)
+			di_read_unlock(p->parent, AuLock_IR);
+		dput(p->parent);
+		p->parent = NULL;
+		goto out;
+	}
+
+	if (unlikely(do_gp)) {
+		gp->dentry = p->parent;
+		err = au_do_pin(gp, NULL, bindex, 0);
+		if (unlikely(err))
+			gp->dentry = NULL;
+	}
+	mutex_lock_nested(&p->h_dir->i_mutex, p->lsc_hi);
+	if (!err) {
+		/* todo: call d_revalidate() here? */
+		if (!h_dentry
+		    || !p->do_verify
+		    || !au_verify_parent(h_dentry, p->h_dir))
+			goto out; /* success */
+		else {
+			AuWarn1("bypassed %.*s/%.*s?\n",
+				AuDLNPair(p->parent), AuDLNPair(p->dentry));
+			err = -EIO;
+		}
+	}
+
+	AuDbgDentry(p->dentry);
+	AuDbgDentry(h_dentry);
+	AuDbgDentry(p->parent);
+	AuDbgInode(p->h_dir);
+	if (h_dentry)
+		AuDbgDentry(h_dentry->d_parent);
+
+	au_do_unpin(p, gp);
+	if (unlikely(do_gp))
+		gp->dentry = NULL;
+
+ out:
+	AuTraceErr(err);
+	return err;
 }
 
+void au_pin_init(struct au_pin *args, struct dentry *dentry, int di_locked,
+		 int lsc_di, int lsc_hi, int do_gp)
+{
+	struct au_pin1 *p;
+	unsigned char do_verify;
+
+	AuTraceEnter();
+
+	memset(args, 0, sizeof(*args));
+	p = args->pin + AuPin_PARENT;
+	p->dentry = dentry;
+	p->di_locked = di_locked;
+	p->lsc_di = lsc_di;
+	p->lsc_hi = lsc_hi;
+	p->do_verify = !au_opt_test(au_mntflags(dentry->d_sb), UDBA_NONE);
+	if (!do_gp)
+		return;
+
+	do_verify = p->do_verify;
+	p = au_pin_gp(args);
+	if (unlikely(p)) {
+		p->lsc_di = lsc_di + 1; /* child first */
+		p->lsc_hi = lsc_hi - 1; /* parent first */
+		p->do_verify = do_verify;
+	}
+}
+
+int au_pin(struct au_pin *args, struct dentry *dentry, aufs_bindex_t bindex,
+	   int di_locked, int do_gp)
+{
+	LKTRTrace("%.*s, b%d, di_locked %d, do_gp %d\n",
+		  AuDLNPair(dentry), bindex, di_locked, do_gp);
+
+	au_pin_init(args, dentry, di_locked, AuLsc_DI_PARENT, AuLsc_I_PARENT2,
+		    do_gp);
+	return au_do_pin(args->pin + AuPin_PARENT, au_pin_gp(args), bindex, do_gp);
+}
+
+/* ---------------------------------------------------------------------- */
+
 struct au_icpup_args {
 	aufs_bindex_t btgt;
-	unsigned char isdir, did_cpup; /* flags */
-	unsigned char hinotify;
-	struct dentry *parent, *gparent, *h_dentry;
-	struct inode *dir, *gdir, *h_inode, *h_dir;
+	unsigned char isdir, hinotify, did_cpup; /* flags */
+	struct dentry *h_dentry;
+	struct inode *h_inode;
+	struct au_pin pin;
+	struct au_hin_ignore ign[2];
+	struct vfsub_args vargs;
 };
 
+/* todo: refine it */
 static int au_lock_and_icpup(struct dentry *dentry, loff_t sz,
-			     struct au_icpup_args *rargs)
+			     struct au_icpup_args *a)
 {
 	int err;
 	aufs_bindex_t bstart;
 	struct super_block *sb;
-	struct dentry *hi_wh;
+	struct dentry *hi_wh, *parent;
 	struct inode *inode;
 	struct au_wr_dir_args wr_dir_args = {
 		.force_btgt	= -1,
@@ -428,84 +680,90 @@ static int au_lock_and_icpup(struct dentry *dentry, loff_t sz,
 	bstart = au_dbstart(dentry);
 	sb = dentry->d_sb;
 	inode = dentry->d_inode;
-	rargs->isdir = S_ISDIR(inode->i_mode);
-	if (rargs->isdir)
+	a->isdir = !!S_ISDIR(inode->i_mode);
+	if (unlikely(a->isdir))
 		au_fset_wrdir(wr_dir_args.flags, ISDIR);
+	/* plink or hi_wh() */
+	if (bstart != au_ibstart(inode))
+		wr_dir_args.force_btgt = au_ibstart(inode);
 	err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args);
 	if (unlikely(err < 0))
 		goto out_dentry;
-	rargs->btgt = err;
-	rargs->did_cpup = (err != bstart);
+	a->btgt = err;
+	a->did_cpup = (err != bstart);
 	err = 0;
 
 	/* crazy udba locks */
-	rargs->hinotify = !!au_opt_test(au_mntflags(sb), UDBA_INOTIFY);
-	if (unlikely(!IS_ROOT(dentry))) {
-		rargs->parent = dget_parent(dentry);
-		rargs->dir = rargs->parent->d_inode;
-		di_read_lock_parent(rargs->parent, AuLock_IR);
+	a->hinotify = !!au_opt_test(au_mntflags(sb), UDBA_INOTIFY);
+	parent = NULL;
+	if (!IS_ROOT(dentry)) {
+		parent = dget_parent(dentry);
+		di_write_lock_parent(parent);
 	}
-	rargs->h_dentry = au_h_dptr(dentry, au_dbstart(dentry));
-	rargs->h_inode = rargs->h_dentry->d_inode;
-	AuDebugOn(!rargs->h_inode);
-
-	if (!rargs->did_cpup) {
-		au_hi_lock(rargs->h_inode, rargs->isdir, inode, rargs->btgt);
-		/* todo: revalidate the lower dentry? */
-		goto out; /* success */
+	err = au_pin(&a->pin, dentry, a->btgt, /*di_locked*/!!parent,
+		     /*dp_gp*/a->hinotify);
+	if (unlikely(err)) {
+		if (parent) {
+			di_write_unlock(parent);
+			dput(parent);
+		}
+		goto out_dentry;
 	}
-
-	if (unlikely(rargs->hinotify
-		     && rargs->parent
-		     && !IS_ROOT(rargs->parent))) {
-		rargs->gparent = dget_parent(rargs->parent);
-		rargs->gdir = rargs->gparent->d_inode;
-		ii_read_lock_parent2(rargs->gdir);
+	a->h_dentry = au_h_dptr(dentry, bstart);
+	a->h_inode = a->h_dentry->d_inode;
+	AuDebugOn(!a->h_inode);
+	mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
+	if (!a->did_cpup) {
+		au_unpin_gp(&a->pin);
+		if (parent) {
+			au_pin_set_parent_lflag(&a->pin, /*lflag*/0);
+			di_downgrade_lock(parent, AuLock_IR);
+			dput(parent);
+		}
+		goto out; /* success */
 	}
 
 	hi_wh = NULL;
-	rargs->h_dir = au_h_iptr(rargs->dir, rargs->btgt);
-	au_hdir_lock(rargs->h_dir, rargs->dir, rargs->btgt);
-	/* todo: revalidate the lower dentry? */
-	au_hi_lock(rargs->h_inode, rargs->isdir, inode, bstart);
 	if (!d_unhashed(dentry)) {
-		err = au_sio_cpup_simple(dentry, rargs->btgt, sz, AuCpup_DTIME);
+		if (parent) {
+			au_pin_set_parent_lflag(&a->pin, /*lflag*/0);
+			di_downgrade_lock(parent, AuLock_IR);
+			dput(parent);
+		}
+		err = au_sio_cpup_simple(dentry, a->btgt, sz, AuCpup_DTIME);
 		if (!err)
-			rargs->h_dentry = au_h_dptr(dentry, au_dbstart(dentry));
+			a->h_dentry = au_h_dptr(dentry, a->btgt);
 	} else {
-		hi_wh = au_hi_wh(inode, rargs->btgt);
+		hi_wh = au_hi_wh(inode, a->btgt);
 		if (!hi_wh) {
-			err = au_sio_cpup_wh(dentry, rargs->btgt, sz,
+			err = au_sio_cpup_wh(dentry, a->btgt, sz,
 					     /*file*/NULL);
 			if (!err)
-				hi_wh = au_hi_wh(inode, rargs->btgt);
+				hi_wh = au_hi_wh(inode, a->btgt);
 			/* todo: revalidate hi_wh? */
 		}
+		if (parent) {
+			au_pin_set_parent_lflag(&a->pin, /*lflag*/0);
+			di_downgrade_lock(parent, AuLock_IR);
+			dput(parent);
+		}
 		if (!hi_wh)
-			rargs->h_dentry = au_h_dptr(dentry, au_dbstart(dentry));
+			a->h_dentry = au_h_dptr(dentry, a->btgt);
 		else
-			rargs->h_dentry = hi_wh; /* do not dget here */
+			a->h_dentry = hi_wh; /* do not dget here */
 	}
 
-	au_hi_unlock(rargs->h_inode, rargs->isdir, inode, bstart);
-	rargs->h_inode = rargs->h_dentry->d_inode;
-	AuDebugOn(!rargs->h_inode);
-	if (!err)
-		au_hi_lock(rargs->h_inode, rargs->isdir, inode, rargs->btgt);
-	au_hdir_unlock(rargs->h_dir, rargs->dir, rargs->btgt);
-	if (!err)
+	mutex_unlock(&a->h_inode->i_mutex);
+	a->h_inode = a->h_dentry->d_inode;
+	AuDebugOn(!a->h_inode);
+	if (!err) {
+		mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
+		au_unpin_gp(&a->pin);
 		goto out; /* success */
-
-	au_hi_unlock(rargs->h_inode, rargs->isdir, inode, rargs->btgt);
-	if (unlikely(rargs->gdir)) {
-		ii_read_unlock(rargs->gdir);
-		dput(rargs->gparent);
-	}
-	if (unlikely(rargs->dir)) {
-		di_read_unlock(rargs->parent, AuLock_IR);
-		dput(rargs->parent);
 	}
 
+	au_unpin(&a->pin);
+
  out_dentry:
 	di_write_unlock(dentry);
  out:
@@ -513,36 +771,48 @@ static int au_lock_and_icpup(struct dentry *dentry, loff_t sz,
 	return err;
 }
 
-static int aufs_do_setattr(struct dentry *dentry, struct iattr *ia,
-			   struct file *file)
+static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
 {
 	int err;
 	struct inode *inode;
-	struct au_hin_ignore ign;
-	struct vfsub_args vargs;
 	struct super_block *sb;
 	__u32 events;
+	struct file *file;
 	loff_t sz;
-	struct au_icpup_args rargs;
+	struct au_icpup_args *a;
 
-	LKTRTrace("%.*s, ia_valid 0x%x\n", AuDLNPair(dentry), ia->ia_valid);
+	LKTRTrace("%.*s\n", AuDLNPair(dentry));
 	inode = dentry->d_inode;
 	IMustLock(inode);
 
+	err = -ENOMEM;
+	a = kzalloc(sizeof(*a), GFP_NOFS);
+	if (unlikely(!a))
+		goto out;
+
+	file = NULL;
 	sb = dentry->d_sb;
 	si_read_lock(sb, AuLock_FLUSH);
+	vfsub_args_init(&a->vargs, a->ign, au_test_dlgt(au_mntflags(sb)), 0);
 
-	if (file)
+	if (ia->ia_valid & ATTR_FILE) {
+		/* currently ftruncate(2) only */
+		file = ia->ia_file;
 		fi_write_lock(file);
+		ia->ia_file = au_h_fptr(file, au_fbstart(file));
+	}
 
 	sz = -1;
 	if ((ia->ia_valid & ATTR_SIZE)
 	    && ia->ia_size < i_size_read(inode))
 		sz = ia->ia_size;
-	memset(&rargs, 0, sizeof(rargs));
-	err = au_lock_and_icpup(dentry, sz, &rargs);
+	err = au_lock_and_icpup(dentry, sz, a);
 	if (unlikely(err < 0))
-		goto out;
+		goto out_si;
+	if (a->did_cpup) {
+		ia->ia_file = NULL;
+		ia->ia_valid &= ~ATTR_FILE;
+	}
 
 	if ((ia->ia_valid & ATTR_SIZE)
 	    && ia->ia_size < i_size_read(inode)) {
@@ -555,47 +825,37 @@ static int aufs_do_setattr(struct dentry *dentry, struct iattr *ia,
 		ia->ia_valid &= ~ATTR_MODE;
 
 	events = 0;
-	vfsub_args_init(&vargs, &ign, au_test_dlgt(au_mntflags(sb)), 0);
-	if (unlikely(rargs.hinotify && rargs.dir)) {
+	if (unlikely(a->hinotify)) {
 		events = vfsub_events_notify_change(ia);
-		if (events)
-			vfsub_ign_hinode(&vargs, events,
-					 au_hi(rargs.dir, rargs.btgt));
+		if (events) {
+			if (unlikely(a->isdir))
+				vfsub_ign_hinode(&a->vargs, events,
+						 au_hi(inode, a->btgt));
+			vfsub_ign_hinode(&a->vargs, events,
+					 au_pinned_hdir(&a->pin, a->btgt));
+		}
 	}
-	err = vfsub_notify_change(rargs.h_dentry, ia, &vargs);
+	err = vfsub_notify_change(a->h_dentry, ia, &a->vargs);
 	if (!err)
 		au_cpup_attr_changeable(inode);
 
  out_unlock:
-	au_hi_unlock(rargs.h_inode, rargs.isdir, inode, rargs.btgt);
-	if (unlikely(rargs.gdir)) {
-		ii_read_unlock(rargs.gdir);
-		dput(rargs.gparent);
-	}
-	if (unlikely(rargs.dir)) {
-		di_read_unlock(rargs.parent, AuLock_IR);
-		dput(rargs.parent);
-	}
+	mutex_unlock(&a->h_inode->i_mutex);
+	au_unpin(&a->pin);
 	di_write_unlock(dentry);
- out:
-	if (file)
+ out_si:
+	if (file) {
 		fi_write_unlock(file);
-
+		ia->ia_file = file;
+		ia->ia_valid |= ATTR_FILE;
+	}
 	si_read_unlock(sb);
+	kfree(a);
+ out:
 	AuTraceErr(err);
 	return err;
 }
 
-static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
-{
-	return aufs_do_setattr(dentry, ia, NULL);
-}
-
-int aufs_fsetattr(struct file *file, struct iattr *ia)
-{
-	return aufs_do_setattr(file->f_dentry, ia, file);
-}
-
 /* ---------------------------------------------------------------------- */
 
 static int h_readlink(struct dentry *dentry, int bindex, char __user *buf,
@@ -607,8 +867,11 @@ static int h_readlink(struct dentry *dentry, int bindex, char __user *buf,
 	LKTRTrace("%.*s, b%d, %d\n", AuDLNPair(dentry), bindex, bufsiz);
 
 	h_dentry = au_h_dptr(dentry, bindex);
-	if (unlikely(!h_dentry->d_inode->i_op
-		     || !h_dentry->d_inode->i_op->readlink))
+	if (unlikely(/* !h_dentry
+			|| !h_dentry->d_inode
+			|| */
+		    !h_dentry->d_inode->i_op
+		    || !h_dentry->d_inode->i_op->readlink))
 		return -EINVAL;
 
 	sb = dentry->d_sb;
@@ -688,7 +951,7 @@ static void aufs_truncate_range(struct inode *inode, loff_t start, loff_t end)
 struct inode_operations aufs_symlink_iop = {
 	.permission	= aufs_permission,
 	.setattr	= aufs_setattr,
-#ifdef CONFIG_AUFS_WORKAROUND_FUSE
+#ifdef CONFIG_AUFS_HIN_OR_FUSE
 	.getattr	= aufs_getattr,
 #endif
 
@@ -710,7 +973,7 @@ struct inode_operations aufs_dir_iop = {
 
 	.permission	= aufs_permission,
 	.setattr	= aufs_setattr,
-#ifdef CONFIG_AUFS_WORKAROUND_FUSE
+#ifdef CONFIG_AUFS_HIN_OR_FUSE
 	.getattr	= aufs_getattr,
 #endif
 
@@ -725,7 +988,7 @@ struct inode_operations aufs_dir_iop = {
 struct inode_operations aufs_iop = {
 	.permission	= aufs_permission,
 	.setattr	= aufs_setattr,
-#ifdef CONFIG_AUFS_WORKAROUND_FUSE
+#ifdef CONFIG_AUFS_HIN_OR_FUSE
 	.getattr	= aufs_getattr,
 #endif
 
diff --git a/ubuntu/aufs/i_op_add.c b/ubuntu/aufs/i_op_add.c
index a13c3e2..4d01b5b 100644
--- a/ubuntu/aufs/i_op_add.c
+++ b/ubuntu/aufs/i_op_add.c
@@ -19,7 +19,7 @@
 /*
  * inode operations (add entry)
  *
- * $Id: i_op_add.c,v 1.6 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: i_op_add.c,v 1.14 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -30,8 +30,8 @@
  * and update version.
  * if it failed, re-create the removed whiteout.
  */
-static int epilog(struct inode *dir, struct dentry *wh_dentry,
-		  struct dentry *dentry)
+static int epilog(struct inode *dir, aufs_bindex_t bindex,
+		  struct dentry *wh_dentry, struct dentry *dentry)
 {
 	int err, rerr;
 	aufs_bindex_t bwh;
@@ -42,12 +42,14 @@ static int epilog(struct inode *dir, struct dentry *wh_dentry,
 
 	LKTRTrace("wh %p, %.*s\n", wh_dentry, AuDLNPair(dentry));
 
+	sb = dentry->d_sb;
 	bwh = -1;
 	if (wh_dentry) {
 		h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */
 		IMustLock(h_dir);
+		AuDebugOn(au_h_iptr(dir, bindex) != h_dir);
 		bwh = au_dbwh(dentry);
-		err = au_wh_unlink_dentry(h_dir, wh_dentry, dentry, dir,
+		err = au_wh_unlink_dentry(au_hi(dir, bindex), wh_dentry, dentry,
 					  /*dlgt*/0);
 		if (unlikely(err))
 			goto out;
@@ -70,7 +72,6 @@ static int epilog(struct inode *dir, struct dentry *wh_dentry,
 		goto out;
 
 	/* revert */
-	sb = dentry->d_sb;
 	ndx.flags = 0;
 	if (unlikely(au_test_dlgt(au_mntflags(sb))))
 		au_fset_ndx(ndx.flags, DLGT);
@@ -78,15 +79,16 @@ static int epilog(struct inode *dir, struct dentry *wh_dentry,
 	ndx.nd = NULL;
 	/* ndx.br = NULL; */
 	/* dir inode is locked */
-	wh = au_wh_create(dir, dentry, bwh, wh_dentry->d_parent, &ndx);
+	wh = au_wh_create(dentry, bwh, wh_dentry->d_parent, &ndx);
 	rerr = PTR_ERR(wh);
-	if (!IS_ERR(wh)) {
+	if (IS_ERR(wh)) {
+		AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
+			AuDLNPair(dentry), err, rerr);
+		err = -EIO;
+	} else {
+		err = 0;
 		dput(wh);
-		goto out;
 	}
-	AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
-		AuDLNPair(dentry), err, rerr);
-	err = -EIO;
 
  out:
 	AuTraceErr(err);
@@ -170,23 +172,20 @@ int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
  * prepare writable branch and the parent dir, lock it,
  * lookup whiteout for the new entry.
  */
-static struct dentry *
+static struct dentry*
 lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
-		  struct dentry *src_dentry, struct au_wr_dir_args *wr_dir_args)
+		  struct dentry *src_dentry, struct au_pin *pin,
+		  struct au_wr_dir_args *wr_dir_args)
 {
-	struct dentry *wh_dentry, *parent, *h_parent, *gparent;
+	struct dentry *wh_dentry, *h_parent;
 	int err;
 	aufs_bindex_t bstart, bcpup;
-	struct inode *dir, *h_dir, *gdir;
 	struct au_ndx ndx;
 	struct super_block *sb;
-	struct au_hinode *hgdir;
 	unsigned int mnt_flags;
 
 	LKTRTrace("%.*s, src %p\n", AuDLNPair(dentry), src_dentry);
 
-	parent = dentry->d_parent; /* dir inode is locked */
-	IMustLock(parent->d_inode);
 	bstart = au_dbstart(dentry);
 	err = au_wr_dir(dentry, src_dentry, wr_dir_args);
 	bcpup = err;
@@ -194,26 +193,13 @@ lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
 	if (unlikely(err < 0))
 		goto out;
 
-	sb = parent->d_sb;
+	sb = dentry->d_sb;
 	mnt_flags = au_mntflags(sb);
-	/* todo: meaningless lock if CONFIG_AUFS_DEBUG is disabled. */
-	hgdir = NULL;
-	if (unlikely(dt && au_opt_test(mnt_flags, UDBA_INOTIFY)
-		     && !IS_ROOT(parent))) {
-		gparent = dget_parent(parent);
-		gdir = gparent->d_inode;
-		ii_read_lock_parent2(gdir);
-		hgdir = au_hi(gdir, bcpup);
-		ii_read_unlock(gdir);
-		dput(gparent);
-	}
-	dir = parent->d_inode;
-	h_parent = au_h_dptr(parent, bcpup);
-	h_dir = h_parent->d_inode;
-
-	AuDbgSleep_UdbaRace();
-	au_hdir_lock(h_dir, dir, bcpup);
-	/* todo: revalidate the lower dentry? */
+	err = au_pin(pin, dentry, bcpup, /*di_locked*/1,
+		     /*do_gp*/dt && au_opt_test(mnt_flags, UDBA_INOTIFY));
+	wh_dentry = ERR_PTR(err);
+	if (unlikely(err))
+		goto out;
 
 	ndx.nfsmnt = au_nfsmnt(sb, bcpup);
 	ndx.flags = 0;
@@ -223,6 +209,7 @@ lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
 	/* ndx.br = NULL; */
 	/* ndx.nd_file = NULL; */
 
+	h_parent = au_pinned_h_parent(pin, bcpup);
 	if (!au_opt_test(mnt_flags, UDBA_NONE) && au_dbstart(dentry) == bcpup) {
 		struct nameidata nd;
 
@@ -239,22 +226,25 @@ lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
 				 &ndx);
 		wh_dentry = ERR_PTR(err);
 		if (unlikely(err))
-			goto out_dir;
+			goto out_unpin;
 		ndx.nd = NULL;
 		ndx.br = NULL;
 	}
 
 	if (dt)
-		au_dtime_store(dt, parent, h_parent, hgdir);
+		au_dtime_store(dt, au_pinned_parent(pin), h_parent,
+			       au_pinned_hdir(pin, bcpup),
+			       au_pinned_hgdir(pin, bcpup));
+
 	wh_dentry = NULL;
 	if (/* bcpup != bstart || */ bcpup != au_dbwh(dentry))
 		goto out; /* success */
 
 	wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, &ndx);
 
- out_dir:
+ out_unpin:
 	if (IS_ERR(wh_dentry))
-		au_hdir_unlock(h_dir, dir, bcpup);
+		au_unpin(pin);
  out:
 	AuTraceErrPtr(wh_dentry);
 	return wh_dentry;
@@ -283,13 +273,16 @@ struct simple_arg {
 static int add_simple(struct inode *dir, struct dentry *dentry,
 		      struct simple_arg *arg)
 {
-	int err, dlgt, created;
-	struct dentry *h_dentry, *h_parent, *wh_dentry, *parent;
+	int err;
+	struct dentry *h_dentry, *wh_dentry, *parent;
 	struct inode *h_dir;
 	struct au_dtime dt;
 	struct vfsub_args vargs;
 	struct super_block *sb;
 	aufs_bindex_t bstart;
+	unsigned char created;
+	struct au_hin_ignore ign;
+	struct au_pin pin;
 	struct au_wr_dir_args wr_dir_args = {
 		.force_btgt	= -1,
 		.flags		= AuWrDir_ADD_ENTRY
@@ -298,10 +291,12 @@ static int add_simple(struct inode *dir, struct dentry *dentry,
 	LKTRTrace("type %d, %.*s\n", arg->type, AuDLNPair(dentry));
 	IMustLock(dir);
 
-	aufs_read_lock(dentry, AuLock_DW);
+	sb = dir->i_sb;
 	parent = dentry->d_parent; /* dir inode is locked */
+	aufs_read_lock(dentry, AuLock_DW);
+	vfsub_args_init(&vargs, &ign, !!au_test_dlgt(au_mntflags(sb)), 0);
 	di_write_lock_parent(parent);
-	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL,
+	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL, &pin,
 				      &wr_dir_args);
 	err = PTR_ERR(wh_dentry);
 	if (IS_ERR(wh_dentry))
@@ -309,49 +304,48 @@ static int add_simple(struct inode *dir, struct dentry *dentry,
 
 	bstart = au_dbstart(dentry);
 	h_dentry = au_h_dptr(dentry, bstart);
-	h_parent = h_dentry->d_parent; /* dir inode is locked */
-	h_dir = h_parent->d_inode;
-	IMustLock(h_dir);
-	sb = dir->i_sb;
-	dlgt = !!au_test_dlgt(au_mntflags(sb));
+	h_dir = au_pinned_h_dir(&pin);
+	vfsub_ign_hinode(&vargs, IN_CREATE, au_pinned_hdir(&pin, bstart));
 
 	switch (arg->type) {
 	case Creat:
 		AuDebugOn(au_test_nfs(h_dir->i_sb) && !arg->u.c.nd);
-		err = au_h_create(h_dir, h_dentry, arg->u.c.mode, dlgt,
+		err = au_h_create(h_dir, h_dentry, arg->u.c.mode, &vargs,
 				  arg->u.c.nd, au_nfsmnt(sb, bstart));
 		break;
 	case Symlink:
 		err = vfsub_symlink(h_dir, h_dentry, arg->u.s.symname,
-				    S_IALLUGO, dlgt);
+				    S_IALLUGO, &vargs);
 		break;
 	case Mknod:
 		err = vfsub_mknod(h_dir, h_dentry, arg->u.m.mode, arg->u.m.dev,
-				  dlgt);
+				  &vargs);
 		break;
 	default:
 		BUG();
 	}
 	created = !err;
 	if (!err)
-		err = epilog(dir, wh_dentry, dentry);
+		err = epilog(dir, bstart, wh_dentry, dentry);
 
 	/* revert */
 	if (unlikely(created && err && h_dentry->d_inode)) {
 		int rerr;
-		vfsub_args_init(&vargs, NULL, dlgt, 0);
+		vfsub_args_reinit(&vargs);
+		vfsub_ign_hinode(&vargs, IN_DELETE,
+				 au_pinned_hdir(&pin, bstart));
 		rerr = vfsub_unlink(h_dir, h_dentry, &vargs);
 		if (rerr) {
 			AuIOErr("%.*s revert failure(%d, %d)\n",
 				AuDLNPair(dentry), err, rerr);
 			err = -EIO;
 		}
-		/* todo: inotify will be fired to the grand parent dir */
+		/* todo: inotify will be fired to the grand parent dir? */
 		au_dtime_revert(&dt);
 		d_drop(dentry);
 	}
 
-	au_hdir_unlock(h_dir, dir, bstart);
+	au_unpin(&pin);
 	dput(wh_dentry);
 
  out:
@@ -401,128 +395,95 @@ int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
 
 /* ---------------------------------------------------------------------- */
 
-struct link_arg {
+struct au_link_args {
 	aufs_bindex_t bdst, bsrc;
-	int issamedir, dlgt;
-	struct dentry *src_parent, *parent, *h_dentry;
-	struct inode *h_dir, *inode, *dir;
+	struct dentry *h_dentry;
+	struct dentry *src_parent, *parent;
+	struct au_pin pin;
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
+	unsigned int mnt_flags;
 };
 
-static int cpup_before_link(struct dentry *src_dentry, struct inode *dir,
-			    struct link_arg *a)
+static int au_cpup_before_link(struct dentry *src_dentry, struct inode *dir,
+			       struct dentry *dentry, struct au_link_args *a)
 {
 	int err;
-	struct inode *hi, *h_dir, *src_dir, *gdir;
-	struct dentry *gparent;
+	struct mutex *h_mtx;
+	const int hinotify = au_opt_test(a->mnt_flags, UDBA_INOTIFY);
 
-	AuTraceEnter();
-
-	gparent = NULL;
-	gdir = NULL;
-	if (unlikely(au_opt_test(au_mntflags(src_dentry->d_sb), UDBA_INOTIFY)
-		     && !IS_ROOT(a->src_parent))) {
-		gparent = dget_parent(a->src_parent);
-		gdir = gparent->d_inode;
-		if (gdir == dir) {
-			dput(gparent);
-			gparent = NULL;
-		}
-	}
-	src_dir = a->src_parent->d_inode;
-	h_dir = NULL;
-
-	if (!a->issamedir) {
-		/* this temporary unlock/lock is safe */
-		au_hdir_unlock(a->h_dir, dir, a->bdst);
-		di_read_lock_parent2(a->src_parent, AuLock_IR);
-		err = au_test_and_cpup_dirs(src_dentry, a->bdst, a->parent);
-		if (unlikely(err)) {
-			au_hdir_lock(a->h_dir, dir, a->bdst);
-			goto out;
-		}
+	LKTRTrace("src %.*s, i%lu, dst %.*s\n",
+		  AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry));
 
-		/* todo: meaningless lock if CONFIG_AUFS_DEBUG is disabled. */
-		if (unlikely(gparent))
-			ii_read_lock_parent3(gdir);
-		h_dir = au_h_iptr(src_dir, a->bdst);
-		au_hdir_lock(h_dir, src_dir, a->bdst);
-	} else if (unlikely(gparent)) {
-		/* this temporary unlock/lock is safe */
-		au_hdir_unlock(a->h_dir, dir, a->bdst);
-		ii_read_lock_parent3(gdir);
-		au_hdir_lock(a->h_dir, dir, a->bdst);
-	}
-	/* todo: test parent-gparent relationship? */
+	di_read_lock_parent(a->src_parent, AuLock_IR);
+	err = au_test_and_cpup_dirs(src_dentry, a->bdst);
+	if (unlikely(err))
+		goto out;
 
 	AuDebugOn(au_dbstart(src_dentry) != a->bsrc);
-	hi = au_h_dptr(src_dentry, a->bsrc)->d_inode;
-	mutex_lock_nested(&hi->i_mutex, AuLsc_I_CHILD);
-	err = au_sio_cpup_simple(src_dentry, a->bdst, -1, AuCpup_DTIME);
-	mutex_unlock(&hi->i_mutex);
-
-	if (unlikely(gparent)) {
-		ii_read_unlock(gdir);
-		dput(gparent);
-	}
+	h_mtx = &au_h_dptr(src_dentry, a->bsrc)->d_inode->i_mutex;
+	err = au_pin(&a->pin, src_dentry, a->bdst, /*di_locked*/1,
+		     /*do_gp*/hinotify);
+	if (unlikely(err))
+		goto out;
+	mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
+	/* todo: no KEEPLINO because of noplink? */
+	err = au_sio_cpup_simple(src_dentry, a->bdst, -1,
+				 AuCpup_DTIME /* | AuCpup_KEEPLINO */);
+	mutex_unlock(h_mtx);
+	au_unpin(&a->pin);
 
  out:
-	if (h_dir) {
-		au_hdir_unlock(h_dir, src_dir, a->bdst);
-		au_hdir_lock(a->h_dir, dir, a->bdst);
-	}
-	if (!a->issamedir)
-		di_read_unlock(a->src_parent, AuLock_IR);
-
+	di_read_unlock(a->src_parent, AuLock_IR);
 	AuTraceErr(err);
 	return err;
 }
 
-static int cpup_or_link(struct dentry *src_dentry, struct link_arg *a)
+static int au_cpup_or_link(struct dentry *src_dentry, struct au_link_args *a)
 {
 	int err;
-	struct inode *inode, *h_inode, *h_dst_inode;
-	struct dentry *h_dentry;
+	struct inode *h_inode;
 	aufs_bindex_t bstart;
-	struct super_block *sb;
+	struct dentry *h_src_dentry;
 
 	AuTraceEnter();
-
-	sb = src_dentry->d_sb;
-	inode = src_dentry->d_inode;
 	AuDebugOn(au_dbstart(src_dentry) != a->bsrc);
-	h_dentry = au_h_dptr(src_dentry, a->bsrc);
-	h_inode = h_dentry->d_inode;
-	bstart = au_ibstart(inode);
-	h_dst_inode = NULL;
-	if (bstart <= a->bdst)
-		h_dst_inode = au_h_iptr(inode, a->bdst);
 
-	if (!h_dst_inode || !h_dst_inode->i_nlink) {
+	bstart = au_ibstart(src_dentry->d_inode);
+	h_inode = NULL;
+	if (bstart <= a->bdst)
+		h_inode = au_h_iptr(src_dentry->d_inode, a->bdst);
+	if (!h_inode || !h_inode->i_nlink) {
 		/* copyup src_dentry as the name of dentry. */
 		au_set_dbstart(src_dentry, a->bdst);
 		au_set_h_dptr(src_dentry, a->bdst, dget(a->h_dentry));
+		h_inode = au_h_dptr(src_dentry, a->bsrc)->d_inode;
 		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
 		err = au_sio_cpup_single(src_dentry, a->bdst, a->bsrc, -1,
-					 !AuCpup_DTIME);
+					 AuCpup_KEEPLINO, a->parent);
 		mutex_unlock(&h_inode->i_mutex);
 		au_set_h_dptr(src_dentry, a->bdst, NULL);
 		au_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->h_dir,
-					 a->h_dentry, a->dlgt);
-			dput(h_dentry);
+		h_src_dentry = d_find_alias(h_inode);
+		if (h_src_dentry) {
+			/* vfsub_args_reinit(&a->vargs); */
+			vfsub_ign_hinode(&a->vargs, IN_CREATE,
+					 au_pinned_hdir(&a->pin, a->bdst));
+			err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
+					 a->h_dentry, &a->vargs);
+			dput(h_src_dentry);
 		} else {
 			AuIOErr("no dentry found for i%lu on b%d\n",
-				h_dst_inode->i_ino, a->bdst);
+				h_inode->i_ino, a->bdst);
 			err = -EIO;
 		}
 	}
 
 	if (!err)
-		au_plink_append(sb, a->inode, a->h_dentry, a->bdst);
+		au_plink_append(src_dentry->d_sb, src_dentry->d_inode,
+				a->h_dentry, a->bdst);
 
 	AuTraceErr(err);
 	return err;
@@ -532,12 +493,11 @@ int aufs_link(struct dentry *src_dentry, struct inode *dir,
 	      struct dentry *dentry)
 {
 	int err, rerr;
-	struct dentry *h_parent, *wh_dentry, *h_src_dentry;
+	struct dentry *wh_dentry, *h_src_dentry;
+	struct inode *inode;
 	struct au_dtime dt;
-	struct link_arg a;
 	struct super_block *sb;
-	unsigned int mnt_flags;
-	struct vfsub_args vargs;
+	struct au_link_args *a;
 	struct au_wr_dir_args wr_dir_args = {
 		/* .force_btgt	= -1, */
 		.flags		= AuWrDir_ADD_ENTRY
@@ -546,104 +506,121 @@ int aufs_link(struct dentry *src_dentry, struct inode *dir,
 	LKTRTrace("src %.*s, i%lu, dst %.*s\n",
 		  AuDLNPair(src_dentry), dir->i_ino, AuDLNPair(dentry));
 	IMustLock(dir);
-	IMustLock(src_dentry->d_inode);
-	AuDebugOn(S_ISDIR(src_dentry->d_inode->i_mode));
+	inode = src_dentry->d_inode;
+	IMustLock(inode);
+	AuDebugOn(S_ISDIR(inode->i_mode));
+
+	err = -ENOMEM;
+	a = kzalloc(sizeof(*a), GFP_NOFS);
+	if (unlikely(!a))
+		goto out;
 
-	aufs_read_and_write_lock2(dentry, src_dentry, /*flags*/0);
 	sb = dentry->d_sb;
-	a.dir = dir;
-	a.src_parent = dget_parent(src_dentry);
-	a.parent = dentry->d_parent; /* dir inode is locked */
-	a.issamedir = (a.src_parent == a.parent);
-	if (!a.issamedir)
-		au_fset_wrdir(wr_dir_args.flags, LOCK_SRCDIR);
+	a->parent = dentry->d_parent; /* dir inode is locked */
+	aufs_read_and_write_lock2(dentry, src_dentry, /*AuLock_FLUSH*/0);
+	a->src_parent = dget_parent(src_dentry);
 	wr_dir_args.force_btgt = au_dbstart(src_dentry);
-	di_write_lock_parent(a.parent);
+	a->mnt_flags = au_mntflags(sb);
+	vfsub_args_init(&a->vargs, &a->ign, au_test_dlgt(a->mnt_flags), 0);
+
+	di_write_lock_parent(a->parent);
 	wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt);
-	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &wr_dir_args);
+	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, src_dentry, &a->pin,
+				      &wr_dir_args);
 	err = PTR_ERR(wh_dentry);
 	if (IS_ERR(wh_dentry))
-		goto out;
-
-	a.inode = src_dentry->d_inode;
-	a.bdst = au_dbstart(dentry);
-	a.h_dentry = au_h_dptr(dentry, a.bdst);
-	h_parent = a.h_dentry->d_parent; /* dir inode is locked */
-	a.h_dir = h_parent->d_inode;
-	IMustLock(a.h_dir);
-
+		goto out_unlock;
 	err = 0;
-	mnt_flags = au_mntflags(sb);
-	a.dlgt = !!au_test_dlgt(mnt_flags);
+
+	a->bdst = au_dbstart(dentry);
+	a->h_dentry = au_h_dptr(dentry, a->bdst);
 
 	/* todo: minor optimize,
 	   their sb may be same while their bindex differs? */
-	a.bsrc = au_dbstart(src_dentry);
-	h_src_dentry = au_h_dptr(src_dentry, a.bsrc);
-	if (unlikely(!au_opt_test(mnt_flags, PLINK))) {
+	a->bsrc = au_dbstart(src_dentry);
+	if (au_opt_test(a->mnt_flags, PLINK)) {
+		if (a->bdst < a->bsrc
+		    /* && h_src_dentry->d_sb != a->h_dentry->d_sb */)
+			err = au_cpup_or_link(src_dentry, a);
+		else {
+			h_src_dentry = au_h_dptr(src_dentry, a->bdst);
+			AuDebugOn(!h_src_dentry);
+			AuDebugOn(!h_src_dentry->d_inode);
+			vfsub_ign_hinode(&a->vargs, IN_CREATE,
+					 au_pinned_hdir(&a->pin, a->bdst));
+			err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
+					 a->h_dentry, &a->vargs);
+		}
+	} else {
 		/*
 		 * 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
-		    /* && h_src_dentry->d_sb != a.h_dentry->d_sb */)
-			err = cpup_before_link(src_dentry, dir, &a);
-		if (!err) {
-			h_src_dentry = au_h_dptr(src_dentry, a.bdst);
-			err = vfsub_link(h_src_dentry, a.h_dir, a.h_dentry,
-					 a.dlgt);
+		if (a->bdst < a->bsrc
+		    /* && h_src_dentry->d_sb != a->h_dentry->d_sb */) {
+			au_unpin(&a->pin);
+			di_write_unlock(a->parent);
+			err = au_cpup_before_link(src_dentry, dir, dentry, a);
+			if (!err) {
+				di_write_lock_parent(a->parent);
+				err = au_pin
+					(&a->pin, dentry, a->bdst,
+					 /*di_locked*/1,
+					 /*do_gp*/au_opt_test(a->mnt_flags,
+							      UDBA_INOTIFY));
+				if (unlikely(err))
+					goto out_wh;
+			}
 		}
-	} else {
-		if (a.bdst < a.bsrc
-		    /* && h_src_dentry->d_sb != a.h_dentry->d_sb */)
-			err = cpup_or_link(src_dentry, &a);
-		else {
-			h_src_dentry = au_h_dptr(src_dentry, a.bdst);
-			err = vfsub_link(h_src_dentry, a.h_dir, a.h_dentry,
-					 a.dlgt);
+		if (!err) {
+			/* vfsub_args_reinit(&a->vargs); */
+			vfsub_ign_hinode(&a->vargs, IN_CREATE,
+					 au_pinned_hdir(&a->pin, a->bdst));
+			h_src_dentry = au_h_dptr(src_dentry, a->bdst);
+			err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
+					 a->h_dentry, &a->vargs);
 		}
 	}
 	if (unlikely(err))
-		goto out_unlock;
+		goto out_unpin;
+
 	if (wh_dentry) {
-		err = au_wh_unlink_dentry(a.h_dir, wh_dentry, dentry,
-					  dir, /*dlgt*/0);
+		err = au_wh_unlink_dentry(au_pinned_hdir(&a->pin, a->bdst),
+					  wh_dentry, dentry, /*dlgt*/0);
 		if (unlikely(err))
 			goto out_revert;
 	}
 
 #if 0 /* cannot support it */
 	/* fuse has different memory inode for the same inode number */
-	if (unlikely(au_test_fuse(a.h_dentry->d_sb))) {
+	if (unlikely(au_test_fuse(a->h_dentry->d_sb))) {
 		LKTRLabel(here);
-		d_drop(a.h_dentry);
+		d_drop(a->h_dentry);
 		/*d_drop(h_src_dentry);
 		  d_drop(src_dentry);*/
-		inc_nlink(a.inode);
-		a.inode->i_ctime = dir->i_ctime;
+		inc_nlink(a->inode);
+		a->inode->i_ctime = dir->i_ctime;
 	}
 #endif
 
 	dir->i_version++;
 	if (au_ibstart(dir) == au_dbstart(dentry))
 		au_cpup_attr_timesizes(dir);
-	if (!d_unhashed(a.h_dentry)
+	if (!d_unhashed(a->h_dentry)
 	    /* || h_old_inode->i_nlink <= nlink */
 	    /* || SB_NFS(h_src_dentry->d_sb) */) {
-		dentry->d_inode = igrab(a.inode);
-		d_instantiate(dentry, a.inode);
-		inc_nlink(a.inode);
-		a.inode->i_ctime = dir->i_ctime;
+		d_instantiate(dentry, au_igrab(inode));
+		inc_nlink(inode);
+		inode->i_ctime = dir->i_ctime;
 	} else
 		/* nfs case (< 2.6.15) */
 		d_drop(dentry);
-	goto out_unlock; /* success */
+	goto out_unpin; /* success */
 
  out_revert:
-	vfsub_args_init(&vargs, NULL, a.dlgt, 0);
-	rerr = vfsub_unlink(a.h_dir, a.h_dentry, &vargs);
+	vfsub_args_reinit(&a->vargs);
+	vfsub_ign_hinode(&a->vargs, IN_DELETE, au_pinned_hdir(&a->pin, a->bdst));
+	rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), a->h_dentry, &a->vargs);
 	if (!rerr)
 		goto out_dt;
 	AuIOErr("%.*s reverting failed(%d, %d)\n",
@@ -652,31 +629,36 @@ int aufs_link(struct dentry *src_dentry, struct inode *dir,
  out_dt:
 	d_drop(dentry);
 	au_dtime_revert(&dt);
- out_unlock:
-	au_hdir_unlock(a.h_dir, dir, a.bdst);
+ out_unpin:
+	au_unpin(&a->pin);
+ out_wh:
 	dput(wh_dentry);
- out:
+ out_unlock:
 	if (unlikely(err)) {
 		au_update_dbstart(dentry);
 		d_drop(dentry);
 	}
-	di_write_unlock(a.parent);
-	dput(a.src_parent);
+	di_write_unlock(a->parent);
+	dput(a->src_parent);
 	aufs_read_and_write_unlock2(dentry, src_dentry);
+	kfree(a);
+ out:
 	AuTraceErr(err);
 	return err;
 }
 
 int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 {
-	int err, rerr, diropq, dlgt;
-	struct dentry *h_dentry, *h_parent, *wh_dentry, *parent, *opq_dentry;
-	struct inode *h_dir, *h_inode;
+	int err, rerr;
+	struct dentry *h_dentry, *wh_dentry, *parent, *opq_dentry;
+	struct mutex *h_mtx;
 	struct au_dtime dt;
 	aufs_bindex_t bindex;
-	struct super_block *sb;
+	unsigned char diropq, dlgt;
 	unsigned int mnt_flags;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
+	struct au_pin pin;
 	struct au_wr_dir_args wr_dir_args = {
 		.force_btgt	= -1,
 		.flags		= AuWrDir_ADD_ENTRY | AuWrDir_ISDIR
@@ -685,36 +667,40 @@ int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 	LKTRTrace("i%lu, %.*s, mode 0%o\n",
 		  dir->i_ino, AuDLNPair(dentry), mode);
 	IMustLock(dir);
+#if 0
+	if (IS_DEADDIR(dir)) {
+		AuDbg("here\n");
+		return -ENOENT;
+	}
+#endif
 
 	aufs_read_lock(dentry, AuLock_DW);
 	parent = dentry->d_parent; /* dir inode is locked */
+	mnt_flags = au_mntflags(dentry->d_sb);
+	dlgt = !!au_test_dlgt(mnt_flags);
+	vfsub_args_init(&vargs, &ign, dlgt, 0);
+
 	di_write_lock_parent(parent);
-	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL,
+	wh_dentry = lock_hdir_lkup_wh(dentry, &dt, /*src_dentry*/NULL, &pin,
 				      &wr_dir_args);
 	err = PTR_ERR(wh_dentry);
 	if (IS_ERR(wh_dentry))
 		goto out;
 
-	sb = dentry->d_sb;
 	bindex = au_dbstart(dentry);
 	h_dentry = au_h_dptr(dentry, bindex);
-	h_parent = h_dentry->d_parent; /* dir inode is locked */
-	h_dir = h_parent->d_inode;
-	IMustLock(h_dir);
-	mnt_flags = au_mntflags(sb);
-	dlgt = !!au_test_dlgt(mnt_flags);
-
-	err = vfsub_mkdir(h_dir, h_dentry, mode, dlgt);
+	vfsub_ign_hinode(&vargs, IN_CREATE, au_pinned_hdir(&pin, bindex));
+	err = vfsub_mkdir(au_pinned_h_dir(&pin), h_dentry, mode, &vargs);
 	if (unlikely(err))
 		goto out_unlock;
-	h_inode = h_dentry->d_inode;
 
 	/* make the dir opaque */
 	diropq = 0;
+	h_mtx = &h_dentry->d_inode->i_mutex;
 	if (wh_dentry || au_opt_test(mnt_flags, ALWAYS_DIROPQ)) {
-		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
+		mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
 		opq_dentry = au_diropq_create(dentry, bindex, /*dlgt*/0);
-		mutex_unlock(&h_inode->i_mutex);
+		mutex_unlock(h_mtx);
 		err = PTR_ERR(opq_dentry);
 		if (IS_ERR(opq_dentry))
 			goto out_dir;
@@ -722,7 +708,7 @@ int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 		diropq = 1;
 	}
 
-	err = epilog(dir, wh_dentry, dentry);
+	err = epilog(dir, bindex, wh_dentry, dentry);
 	if (!err) {
 		inc_nlink(dir);
 		goto out_unlock; /* success */
@@ -731,9 +717,9 @@ int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 	/* revert */
 	if (diropq) {
 		LKTRLabel(revert opq);
-		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
+		mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
 		rerr = au_diropq_remove(dentry, bindex, dlgt);
-		mutex_unlock(&h_inode->i_mutex);
+		mutex_unlock(h_mtx);
 		if (rerr) {
 			AuIOErr("%.*s reverting diropq failed(%d, %d)\n",
 				AuDLNPair(dentry), err, rerr);
@@ -743,8 +729,9 @@ int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 
  out_dir:
 	LKTRLabel(revert dir);
-	vfsub_args_init(&vargs, NULL, dlgt, 0);
-	rerr = vfsub_rmdir(h_dir, h_dentry, &vargs);
+	vfsub_args_reinit(&vargs);
+	vfsub_ign_hinode(&vargs, IN_DELETE, au_pinned_hdir(&pin, bindex));
+	rerr = vfsub_rmdir(au_pinned_h_dir(&pin), h_dentry, &vargs);
 	if (rerr) {
 		AuIOErr("%.*s reverting dir failed(%d, %d)\n",
 			AuDLNPair(dentry), err, rerr);
@@ -753,7 +740,7 @@ int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 	d_drop(dentry);
 	au_dtime_revert(&dt);
  out_unlock:
-	au_hdir_unlock(h_dir, dir, bindex);
+	au_unpin(&pin);
 	dput(wh_dentry);
  out:
 	if (unlikely(err)) {
diff --git a/ubuntu/aufs/i_op_del.c b/ubuntu/aufs/i_op_del.c
index a3f16c1..aa5e488 100644
--- a/ubuntu/aufs/i_op_del.c
+++ b/ubuntu/aufs/i_op_del.c
@@ -19,7 +19,7 @@
 /*
  * inode operations (del entry)
  *
- * $Id: i_op_del.c,v 1.6 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: i_op_del.c,v 1.12 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -29,16 +29,15 @@
  * plus: wh is necessary
  * minus: error
  */
-int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup,
-		      struct dentry *locked)
+int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup)
 {
 	int need_wh, err;
 	aufs_bindex_t bstart;
 	struct dentry *h_dentry;
 	struct super_block *sb;
 
-	LKTRTrace("%.*s, isdir %d, *bcpup %d, locked %p\n",
-		  AuDLNPair(dentry), isdir, *bcpup, locked);
+	LKTRTrace("%.*s, isdir %d, *bcpup %d\n",
+		  AuDLNPair(dentry), isdir, *bcpup);
 	sb = dentry->d_sb;
 
 	bstart = au_dbstart(dentry);
@@ -58,7 +57,7 @@ int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup,
 	LKTRTrace("bcpup %d, bstart %d\n", *bcpup, bstart);
 
 	if (*bcpup != bstart) {
-		err = au_cpup_dirs(dentry, *bcpup, locked);
+		err = au_cpup_dirs(dentry, *bcpup);
 		if (unlikely(err))
 			goto out;
 		need_wh = 1;
@@ -175,50 +174,33 @@ int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
 
 static struct dentry *
 lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
-		    struct au_dtime *dt)
+		    struct au_dtime *dt, struct au_pin *pin)
 {
 	struct dentry *wh_dentry;
 	int err, need_wh;
-	struct dentry *h_parent, *parent, *gparent;
-	struct inode *dir, *h_dir, *gdir;
+	struct dentry *h_parent;
 	struct au_ndx ndx;
 	struct super_block *sb;
-	struct au_hinode *hgdir;
 	aufs_bindex_t bcpup;
 	unsigned int mnt_flags;
 
 	LKTRTrace("%.*s, isdir %d\n", AuDLNPair(dentry), isdir);
 
-	need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup, NULL);
+	need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup);
 	err = need_wh;
 	wh_dentry = ERR_PTR(err);
 	if (unlikely(err < 0))
 		goto out;
 
-	/* todo: meaningless lock if CONFIG_AUFS_DEBUG is disabled. */
-	hgdir = NULL;
-	bcpup = *rbcpup;
 	sb = dentry->d_sb;
 	mnt_flags = au_mntflags(sb);
-	parent = dentry->d_parent; /* dir inode is locked */
-	if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY)
-		     && !IS_ROOT(parent))) {
-		gparent = dget_parent(parent);
-		gdir = gparent->d_inode;
-		ii_read_lock_parent2(gdir);
-		hgdir = au_hi(gdir, bcpup);
-		ii_read_unlock(gdir);
-		dput(gparent);
-	}
-	dir = parent->d_inode;
-	IMustLock(dir);
-	h_parent = au_h_dptr(parent, bcpup);
-	h_dir = h_parent->d_inode;
-
-	AuDbgSleep_UdbaRace();
-	au_hdir_lock(h_dir, dir, bcpup);
-	/* todo: revalidate the lower dentry? */
-
+	bcpup = *rbcpup;
+	err = au_pin(pin, dentry, bcpup, /*di_locked*/1,
+		     /*do_gp*/au_opt_test(mnt_flags, UDBA_INOTIFY));
+	wh_dentry = ERR_PTR(err);
+	if (unlikely(err))
+		goto out;
+	h_parent = au_pinned_h_parent(pin, bcpup);
 	if (!au_opt_test(mnt_flags, UDBA_NONE) && au_dbstart(dentry) == bcpup) {
 		ndx.nfsmnt = au_nfsmnt(sb, bcpup);
 		ndx.flags = 0;
@@ -230,10 +212,11 @@ lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
 		err = au_may_del(dentry, bcpup, h_parent, isdir, &ndx);
 		wh_dentry = ERR_PTR(err);
 		if (unlikely(err))
-			goto out_dir;
+			goto out_unpin;
 	}
 
-	au_dtime_store(dt, parent, h_parent, hgdir);
+	au_dtime_store(dt, au_pinned_parent(pin), h_parent,
+		       au_pinned_hdir(pin, bcpup), au_pinned_hgdir(pin, bcpup));
 	wh_dentry = NULL;
 	if (!need_wh)
 		goto out; /* success, no need to create whiteout */
@@ -244,13 +227,13 @@ lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
 		au_fset_ndx(ndx.flags, DLGT);
 	ndx.nd = NULL;
 	/* ndx.br = NULL; */
-	wh_dentry = au_wh_create(dir, dentry, bcpup, h_parent, &ndx);
+	wh_dentry = au_wh_create(dentry, bcpup, h_parent, &ndx);
 	if (!IS_ERR(wh_dentry))
 		goto out; /* success */
 	/* returns with the parent is locked and wh_dentry is DGETed */
 
- out_dir:
-	au_hdir_unlock(h_dir, dir, bcpup);
+ out_unpin:
+	au_unpin(pin);
  out:
 	AuTraceErrPtr(wh_dentry);
 	return wh_dentry;
@@ -272,15 +255,19 @@ static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex,
 	if (unlikely(au_opt_test(au_mntflags(sb), UDBA_INOTIFY))) {
 		inode = dentry->d_inode;
 		h_inode = au_h_iptr(inode, bindex);
-		au_hdir2_lock(h_inode, inode, bindex);
+		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
+	}
+	h_dentry = au_h_dptr(dentry, bindex);
+	err = au_whtmp_ren(dir, bindex, h_dentry);
+	if (unlikely(inode)) {
+		/* todo: bad approach? */
+		if (!err)
+			au_hin_suspend(au_hi(inode, bindex));
+		mutex_unlock(&h_inode->i_mutex);
 	}
-	err = au_whtmp_ren(dir, dentry, bindex, /*noself*/1);
-	if (unlikely(inode))
-		au_hdir_unlock(h_inode, inode, bindex);
 	if (unlikely(err))
 		goto out;
 
-	h_dentry = au_h_dptr(dentry, bindex);
 	if (!au_test_nfs(h_dentry->d_sb)) {
 		const int dirwh = au_sbi(sb)->si_dirwh;
 		rmdir_later = (dirwh <= 1);
@@ -291,11 +278,11 @@ static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex,
 			return rmdir_later;
 	}
 
-	err = au_whtmp_rmdir(h_dentry, whlist, bindex, dir, dentry->d_inode,
-			     /*noself*/1);
+	err = au_whtmp_rmdir(dir, bindex, h_dentry, whlist);
 	if (unlikely(err)) {
 		AuIOErr("rmdir %.*s, b%d failed, %d. ignored\n",
 			AuDLNPair(h_dentry), bindex, err);
+		/* we do not revert the inotify watch */
 		err = 0;
 	}
 
@@ -330,15 +317,13 @@ static void epilog(struct inode *dir, struct dentry *dentry,
 #define AuRev_DLGT	0
 #endif
 
-static int do_revert(int err, struct dentry *wh_dentry, struct dentry *dentry,
-		     aufs_bindex_t bwh, struct au_dtime *dt, unsigned int flags)
+static int do_revert(int err, struct inode *dir, aufs_bindex_t bwh,
+		     struct dentry *wh_dentry, struct dentry *dentry,
+		     struct au_dtime *dt, unsigned int flags)
 {
 	int rerr;
-	struct inode *dir;
 
-	dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */
-	IMustLock(dir);
-	rerr = au_wh_unlink_dentry(dir, wh_dentry, dentry, dir,
+	rerr = au_wh_unlink_dentry(au_hi(dir, bwh), wh_dentry, dentry,
 				   au_ftest_rev(flags, DLGT));
 	if (!rerr) {
 		au_set_dbwh(dentry, bwh);
@@ -355,13 +340,16 @@ static int do_revert(int err, struct dentry *wh_dentry, struct dentry *dentry,
 
 int aufs_unlink(struct inode *dir, struct dentry *dentry)
 {
-	int err, dlgt;
+	int err;
 	struct inode *inode, *h_dir;
 	struct dentry *parent, *wh_dentry, *h_dentry;
 	struct au_dtime dt;
 	aufs_bindex_t bwh, bindex, bstart;
+	unsigned char dlgt;
 	struct super_block *sb;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
+	struct au_pin pin;
 
 	LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry));
 	IMustLock(dir);
@@ -377,7 +365,7 @@ int aufs_unlink(struct inode *dir, struct dentry *dentry)
 	bstart = au_dbstart(dentry);
 	bwh = au_dbwh(dentry);
 	bindex = -1;
-	wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt);
+	wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt, &pin);
 	err = PTR_ERR(wh_dentry);
 	if (IS_ERR(wh_dentry))
 		goto out;
@@ -389,9 +377,10 @@ int aufs_unlink(struct inode *dir, struct dentry *dentry)
 	dget(h_dentry);
 
 	if (bindex == bstart) {
-		vfsub_args_init(&vargs, NULL, dlgt, 0);
-		h_dir = h_dentry->d_parent->d_inode; /* dir inode is locked */
-		IMustLock(h_dir);
+		vfsub_args_init(&vargs, &ign, dlgt, 0);
+		vfsub_ign_hinode(&vargs, IN_DELETE, au_pinned_hdir(&pin,
+								   bstart));
+		h_dir = au_pinned_h_dir(&pin);
 		err = vfsub_unlink(h_dir, h_dentry, &vargs);
 	} else {
 		/* dir inode is locked */
@@ -413,6 +402,15 @@ int aufs_unlink(struct inode *dir, struct dentry *dentry)
 			au_debug_off();
 		}
 #endif
+#if 0
+		/*
+		 * although this is not a dir,
+		 * set it here since we need to detect
+		 * the dead inode in d_revalidate().
+		 */
+		if (!inode->i_nlink)
+			inode->i_flags |= S_DEAD;
+#endif
 		epilog(dir, dentry, bindex);
 
 		/* update target timestamps */
@@ -433,13 +431,14 @@ int aufs_unlink(struct inode *dir, struct dentry *dentry)
 		rev_flags = 0;
 		if (unlikely(dlgt))
 			au_fset_rev(rev_flags, DLGT);
-		rerr = do_revert(err, wh_dentry, dentry, bwh, &dt, rev_flags);
+		rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt,
+				 rev_flags);
 		if (rerr)
 			err = rerr;
 	}
 
  out_unlock:
-	au_hdir_unlock(h_dir, dir, bindex);
+	au_unpin(&pin);
 	dput(wh_dentry);
 	dput(h_dentry);
  out:
@@ -452,7 +451,7 @@ int aufs_unlink(struct inode *dir, struct dentry *dentry)
 int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 {
 	int err, rmdir_later;
-	struct inode *inode, *h_dir;
+	struct inode *inode;
 	struct dentry *parent, *wh_dentry, *h_dentry;
 	struct au_dtime dt;
 	aufs_bindex_t bwh, bindex, bstart;
@@ -460,21 +459,34 @@ int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 	struct au_nhash *whlist;
 	struct super_block *sb;
 	unsigned int mnt_flags;
+	struct au_pin pin;
 
 	LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry));
 	IMustLock(dir);
+#if 0
+	err = -ENOENT; /* possible? */
+	if (unlikely(IS_DEADDIR(dir))) {
+		AuDbg("here\n");
+		goto out;
+	}
+#endif
 	inode = dentry->d_inode;
-	if (unlikely(!inode))
-		return -ENOENT; /* possible? */
+	err = -ENOENT; /* possible? */
+	if (unlikely(!inode
+		     //|| IS_DEADDIR(inode) || IS_DEADDIR(dir)
+		    )) {
+		//AuDbg("here\n");
+		goto out;
+	}
 	IMustLock(inode);
 
-	whlist = au_nhash_new(GFP_TEMPORARY);
+	whlist = au_nhash_new(GFP_NOFS);
 	err = PTR_ERR(whlist);
 	if (IS_ERR(whlist))
 		goto out;
 
 	err = -ENOMEM;
-	args = kmalloc(sizeof(*args), GFP_TEMPORARY);
+	args = kmalloc(sizeof(*args), GFP_NOFS);
 	if (unlikely(!args))
 		goto out_whlist;
 
@@ -488,7 +500,7 @@ int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 	bstart = au_dbstart(dentry);
 	bwh = au_dbwh(dentry);
 	bindex = -1;
-	wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &dt);
+	wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/1, &bindex, &dt, &pin);
 	err = PTR_ERR(wh_dentry);
 	if (IS_ERR(wh_dentry))
 		goto out_args;
@@ -499,8 +511,6 @@ int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 
 	rmdir_later = 0;
 	if (bindex == bstart) {
-		h_dir = h_dentry->d_parent->d_inode; /* dir inode is locked */
-		IMustLock(h_dir);
 		err = renwh_and_rmdir(dentry, bstart, whlist, dir);
 		if (err > 0) {
 			rmdir_later = err;
@@ -510,8 +520,7 @@ int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 		/* dir inode is locked */
 		AuDebugOn(!wh_dentry
 			  || wh_dentry->d_parent != au_h_dptr(parent, bindex));
-		h_dir = wh_dentry->d_parent->d_inode;
-		IMustLock(h_dir);
+		IMustLock(wh_dentry->d_parent->d_inode);
 		err = 0;
 	}
 
@@ -522,12 +531,13 @@ int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 			     && rmdir_later))
 			au_reset_hinotify(inode, /*flags*/0);
 		clear_nlink(inode);
+		//inode->i_flags |= S_DEAD;
 		au_set_dbdiropq(dentry, -1);
 		epilog(dir, dentry, bindex);
 
 		if (rmdir_later) {
-			au_whtmp_kick_rmdir(h_dentry, whlist, bstart, dir,
-					    inode, /*noself*/1, args);
+			au_whtmp_kick_rmdir(dir, bstart, h_dentry, whlist,
+					    args);
 			args = NULL;
 		}
 
@@ -543,13 +553,14 @@ int aufs_rmdir(struct inode *dir, struct dentry *dentry)
 		rev_flags = 0;
 		if (unlikely(au_test_dlgt(mnt_flags)))
 			au_fset_rev(rev_flags, DLGT);
-		rerr = do_revert(err, wh_dentry, dentry, bwh, &dt, rev_flags);
+		rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt,
+				 rev_flags);
 		if (rerr)
 			err = rerr;
 	}
 
  out_unlock:
-	au_hdir_unlock(h_dir, dir, bindex);
+	au_unpin(&pin);
 	dput(wh_dentry);
 	dput(h_dentry);
  out_args:
diff --git a/ubuntu/aufs/i_op_ren.c b/ubuntu/aufs/i_op_ren.c
index 0b55a38..4ff15c6 100644
--- a/ubuntu/aufs/i_op_ren.c
+++ b/ubuntu/aufs/i_op_ren.c
@@ -18,9 +18,9 @@
 
 /*
  * inode operation (rename entry)
- * todo: this is monster
+ * todo: this is crazy monster
  *
- * $Id: i_op_ren.c,v 1.6 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: i_op_ren.c,v 1.14 2008/09/22 03:52:12 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -32,6 +32,8 @@ enum { SRC, DST };
 #define AuRen_WHSRC	(1 << 2)
 #define AuRen_WHDST	(1 << 3)
 #define AuRen_DLGT	(1 << 4)
+#define AuRen_VFSLOCK	(1 << 5)
+#define AuRen_PINNED	(1 << 6)
 #define au_ftest_ren(flags, name)	((flags) & AuRen_##name)
 #define au_fset_ren(flags, name)	{ (flags) |= AuRen_##name; }
 #define au_fclr_ren(flags, name)	{ (flags) &= ~AuRen_##name; }
@@ -40,322 +42,419 @@ enum { SRC, DST };
 #define AuRen_DLGT	0
 #endif
 
-struct rename_args {
-	struct dentry *h_dentry[2], *parent[2], *h_parent[2], *h_trap;
+struct au_ren_args {
+	/* original args */
+	struct dentry *src_dentry, *dentry;
+	struct inode *src_dir, *dir;
+
+	struct dentry *h_dentry[2], *h_parent[2], *h_trap, *h_locked[2];
+	/* todo: remove them */
+	struct dentry *parent[2], *gparent[2];
+	struct au_pin pin[2];
 	struct au_nhash whlist;
 	aufs_bindex_t btgt, bstart[2];
+	/* do_rename() only */
+	unsigned char need_diropq, bycpup;
 	struct super_block *sb;
 	unsigned int flags;
 	unsigned int mnt_flags;
-};
+	struct au_ndx ndx;
 
-static noinline_for_stack 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;
+	/* do_rename() only */
+#ifdef CONFIG_AUFS_BR_NFS
+	struct au_hin_ignore ign[3];
+#else
+	struct au_hin_ignore ign[2];
+#endif
+	struct vfsub_args vargs;
 	struct au_whtmp_rmdir_args *thargs;
 	struct dentry *wh_dentry[2], *h_dst, *h_src;
-	struct inode *h_dir[2];
+};
+
+/* ---------------------------------------------------------------------- */
+
+#define RevertFailure(fmt, args...) do { \
+		AuIOErrWhck("revert failure: " fmt " (%d, %d)\n", \
+			    ##args, err, rerr); \
+		err = -EIO; \
+	} while (0)
+
+static noinline_for_stack
+void au_ren_rev_diropq(int err, struct au_ren_args *a)
+{
+	int rerr;
+	struct mutex *h_mtx;
+
+	/* lock inode simply since inotify is not set to h_inode. */
+	h_mtx = &au_h_dptr(a->src_dentry, a->btgt)->d_inode->i_mutex;
+	mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
+	rerr = au_diropq_remove(a->src_dentry, a->btgt,
+				au_ftest_ren(a->flags, DLGT));
+	mutex_unlock(h_mtx);
+	if (rerr)
+		RevertFailure("remove diropq %.*s", AuDLNPair(a->src_dentry));
+}
+
+static noinline_for_stack
+void au_ren_rev_rename(int err, struct au_ren_args *a)
+{
+	int rerr;
+	struct dentry *h_d;
+	struct qstr *name = &a->src_dentry->d_name;
+
+	h_d = au_lkup_one(name->name, a->h_parent[SRC], name->len, &a->ndx);
+	rerr = PTR_ERR(h_d);
+	if (IS_ERR(h_d)) {
+		RevertFailure("au_lkup_one %.*s", AuDLNPair(a->src_dentry));
+		return;
+	}
+
+	AuDebugOn(h_d->d_inode);
+	vfsub_args_reinit(&a->vargs);
+	vfsub_ign_hinode(&a->vargs, IN_MOVED_FROM, au_pinned_hdir(a->pin + DST,
+								  a->btgt));
+	vfsub_ign_hinode(&a->vargs, IN_MOVED_TO, au_pinned_hdir(a->pin + SRC,
+								a->btgt));
+	rerr = vfsub_rename(au_pinned_h_dir(a->pin + DST),
+			    au_h_dptr(a->src_dentry, a->btgt),
+			    au_pinned_h_dir(a->pin + SRC), h_d, &a->vargs);
+	d_drop(h_d);
+	dput(h_d);
+	/* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */
+	if (rerr)
+		RevertFailure("rename %.*s", AuDLNPair(a->src_dentry));
+}
+
+static noinline_for_stack
+void au_ren_rev_cpup(int err, struct au_ren_args *a)
+{
+	int rerr;
+
+	vfsub_args_reinit(&a->vargs);
+	vfsub_ign_hinode(&a->vargs, IN_DELETE, au_pinned_hdir(a->pin + DST,
+							      a->btgt));
+	rerr = vfsub_unlink(au_pinned_h_dir(a->pin + DST), a->h_dentry[DST],
+			    &a->vargs);
+	au_set_h_dptr(a->src_dentry, a->btgt, NULL);
+	au_set_dbstart(a->src_dentry, a->bstart[SRC]);
+	if (rerr)
+		RevertFailure("unlink %.*s", AuDLNPair(a->h_dentry[DST]));
+}
+
+static noinline_for_stack
+void au_ren_rev_whtmp(int err, struct au_ren_args *a)
+{
+	int rerr;
+	struct dentry *h_d;
+	struct mutex *h_mtx;
+	struct qstr *name = &a->dentry->d_name;
+
+	h_d = au_lkup_one(name->name, a->h_parent[DST], name->len, &a->ndx);
+	rerr = PTR_ERR(h_d);
+	if (IS_ERR(h_d)) {
+		RevertFailure("lookup %.*s", AuLNPair(name));
+		return;
+	}
+	if (h_d->d_inode) {
+		d_drop(h_d);
+		dput(h_d);
+		return;
+	}
+
+	h_mtx = &a->h_dst->d_inode->i_mutex;
+	mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
+	au_hin_resume(au_hi(a->src_dentry->d_inode, a->btgt));
+	mutex_unlock(h_mtx);
+	vfsub_args_reinit(&a->vargs);
+	vfsub_ign_hinode(&a->vargs, IN_MOVED_TO | IN_MOVED_FROM,
+			 au_pinned_hdir(a->pin + DST, a->btgt));
+	rerr = vfsub_rename(au_pinned_h_dir(a->pin + DST), a->h_dst,
+			    au_pinned_h_dir(a->pin + DST), h_d, &a->vargs);
+	d_drop(h_d);
+	dput(h_d);
+	if (!rerr) {
+		au_set_h_dptr(a->dentry, a->btgt, NULL);
+		au_set_h_dptr(a->dentry, a->btgt, dget(a->h_dst));
+	} else
+		RevertFailure("rename %.*s", AuDLNPair(a->h_dst));
+}
+
+static noinline_for_stack
+void au_ren_rev_whsrc(int err, struct au_ren_args *a)
+{
+	int rerr;
+
+	rerr = au_wh_unlink_dentry(au_pinned_hdir(a->pin + SRC, a->btgt),
+				   a->wh_dentry[SRC], a->src_dentry, /*dlgt*/0);
+	if (rerr)
+		RevertFailure("unlink %.*s", AuDLNPair(a->wh_dentry[SRC]));
+}
+#undef RevertFailure
+
+/* ---------------------------------------------------------------------- */
+
+static /* noinline_for_stack */
+int au_ren_or_cpup(struct au_ren_args *a)
+{
+	int err;
+
+	AuTraceEnter();
+
+	if (au_dbstart(a->src_dentry) == a->btgt) {
+		if (a->need_diropq && au_dbdiropq(a->src_dentry) == a->btgt)
+			a->need_diropq = 0;
+		vfsub_ign_hinode(&a->vargs, IN_MOVED_FROM,
+				 au_pinned_hdir(a->pin + SRC, a->btgt));
+		vfsub_ign_hinode(&a->vargs, IN_MOVED_TO,
+				 au_pinned_hdir(a->pin + DST, a->btgt));
+		/* nfs_rename() calls d_delete() */
+		if (au_test_nfs(au_pinned_h_dir(a->pin + DST)->i_sb)
+		    && a->h_dentry[DST]->d_inode
+		    && (S_ISDIR(a->h_dentry[DST]->d_inode->i_mode)
+			|| atomic_read(&a->h_dentry[DST]->d_count) <= 2))
+			vfsub_ign_hinode(&a->vargs, IN_DELETE,
+					 au_pinned_hdir(a->pin + DST, a->btgt));
+		AuDebugOn(au_dbstart(a->src_dentry) != a->btgt);
+		err = vfsub_rename(au_pinned_h_dir(a->pin + SRC),
+				   au_h_dptr(a->src_dentry, a->btgt),
+				   au_pinned_h_dir(a->pin + DST),
+				   a->h_dentry[DST], &a->vargs);
+	} else {
+		struct mutex *h_mtx = &a->h_dentry[SRC]->d_inode->i_mutex;
+
+		a->bycpup = 1;
+		mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
+		au_set_dbstart(a->src_dentry, a->btgt);
+		au_set_h_dptr(a->src_dentry, a->btgt, dget(a->h_dentry[DST]));
+		err = au_sio_cpup_single(a->src_dentry, a->btgt, a->bstart[SRC],
+					 -1, !AuCpup_DTIME, a->parent[DST]);
+		if (unlikely(err)) {
+			au_set_h_dptr(a->src_dentry, a->btgt, NULL);
+			au_set_dbstart(a->src_dentry, a->bstart[SRC]);
+		}
+		mutex_unlock(h_mtx);
+	}
+
+	return err;
+}
+
+static /* noinline_for_stack */
+int au_ren_del_whtmp(struct au_ren_args *a)
+{
+	int err;
+
+	AuTraceEnter();
+
+	if (au_test_nfs(a->h_dst->d_sb)
+	    || !au_nhash_test_longer_wh(&a->whlist, a->btgt,
+					au_sbi(a->sb)->si_dirwh)) {
+		err = au_whtmp_rmdir(a->dir, a->btgt, a->h_dst, &a->whlist);
+		if (unlikely(err))
+			AuWarn("failed removing whtmp dir %.*s (%d), "
+			       "ignored.\n", AuDLNPair(a->h_dst), err);
+	} else {
+		au_whtmp_kick_rmdir(a->dir, a->btgt, a->h_dst, &a->whlist,
+				    a->thargs);
+		dput(a->h_dst);
+		a->thargs = NULL;
+	}
+
+	return 0;
+}
+
+static /* noinline_for_stack */
+int au_ren_diropq(struct au_ren_args *a)
+{
+	int err;
+	struct dentry *diropq;
+	struct mutex *h_mtx;
+
+	AuTraceEnter();
+
+	err = 0;
+	h_mtx = &au_h_dptr(a->src_dentry, a->btgt)->d_inode->i_mutex;
+	mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
+	diropq = au_diropq_create(a->src_dentry, a->btgt,
+				  au_ftest_ren(a->flags, DLGT));
+	mutex_unlock(h_mtx);
+	if (IS_ERR(diropq))
+		err = PTR_ERR(diropq);
+	dput(diropq);
+
+	return err;
+}
+
+static /* noinline_for_stack */
+int do_rename(struct au_ren_args *a)
+{
+	int err;
 	aufs_bindex_t bindex, bend;
-	struct au_hin_ignore ign;
-	struct vfsub_args vargs;
-	struct au_ndx ndx = {
-		.flags	= 0,
-		.nd	= NULL,
-		/* .br	= NULL */
-	};
+	struct dentry *h_d;
 
 	LKTRTrace("%.*s/%.*s, %.*s/%.*s, "
 		  "hd{%p, %p}, hp{%p, %p}, wh %p, btgt %d, bstart{%d, %d}, "
 		  "flags 0x%x\n",
-		  AuDLNPair(a->parent[SRC]), AuDLNPair(src_dentry),
-		  AuDLNPair(a->parent[DST]), AuDLNPair(dentry),
+		  AuDLNPair(a->parent[SRC]), AuDLNPair(a->src_dentry),
+		  AuDLNPair(a->parent[DST]), AuDLNPair(a->dentry),
 		  a->h_dentry[SRC], a->h_dentry[DST],
 		  a->h_parent[SRC], a->h_parent[DST],
 		  &a->whlist, a->btgt,
 		  a->bstart[SRC], a->bstart[DST],
 		  a->flags);
 
-	h_dir[SRC] = a->h_parent[SRC]->d_inode;
-	h_dir[DST] = a->h_parent[DST]->d_inode;
-
 	/* prepare workqueue args */
-	h_dst = NULL;
-	thargs = NULL;
 	if (au_ftest_ren(a->flags, ISDIR) && a->h_dentry[DST]->d_inode) {
 		err = -ENOMEM;
-		thargs = kmalloc(sizeof(*thargs), GFP_TEMPORARY);
-		if (unlikely(!thargs))
+		a->thargs = kmalloc(sizeof(*a->thargs), GFP_NOFS);
+		if (unlikely(!a->thargs))
 			goto out;
-		h_dst = dget(a->h_dentry[DST]);
+		a->h_dst = dget(a->h_dentry[DST]);
 	}
 
-	wh_dentry[SRC] = NULL;
-	wh_dentry[DST] = NULL;
-	ndx.nfsmnt = au_nfsmnt(a->sb, a->btgt);
+	a->ndx.nfsmnt = au_nfsmnt(a->sb, a->btgt);
 	if (unlikely(au_ftest_ren(a->flags, DLGT)))
-		au_fset_ndx(ndx.flags, DLGT);
+		au_fset_ndx(a->ndx.flags, DLGT);
 
 	/* create whiteout for src_dentry */
 	if (au_ftest_ren(a->flags, WHSRC)) {
-		wh_dentry[SRC] = au_wh_create(src_dir, src_dentry, a->btgt,
-					      a->h_parent[SRC], &ndx);
-		err = PTR_ERR(wh_dentry[SRC]);
-		if (IS_ERR(wh_dentry[SRC]))
+		a->wh_dentry[SRC] = au_wh_create(a->src_dentry, a->btgt,
+						 a->h_parent[SRC], &a->ndx);
+		err = PTR_ERR(a->wh_dentry[SRC]);
+		if (IS_ERR(a->wh_dentry[SRC]))
 			goto out_thargs;
 	}
 
 	/* lookup whiteout for dentry */
 	if (au_ftest_ren(a->flags, WHDST)) {
-		struct dentry *d;
-
-		d = au_wh_lkup(a->h_parent[DST], &dentry->d_name, &ndx);
-		err = PTR_ERR(d);
-		if (IS_ERR(d))
+		h_d = au_wh_lkup(a->h_parent[DST], &a->dentry->d_name, &a->ndx);
+		err = PTR_ERR(h_d);
+		if (IS_ERR(h_d))
 			goto out_whsrc;
-		if (!d->d_inode)
-			dput(d);
+		if (!h_d->d_inode)
+			dput(h_d);
 		else
-			wh_dentry[DST] = d;
+			a->wh_dentry[DST] = h_d;
 	}
 
 	/* rename dentry to tmpwh */
-	if (thargs) {
-		err = au_whtmp_ren(dir, dentry, a->btgt, /*noself*/0);
+	if (a->thargs) {
+		struct au_hinode *hinode;
+
+		AuDbgDentry(a->h_dentry[DST]);
+		err = au_whtmp_ren(a->dir, a->btgt, a->h_dentry[DST]);
 		if (unlikely(err))
 			goto out_whdst;
-		au_set_h_dptr(dentry, a->btgt, NULL);
-		err = au_lkup_neg(dentry, a->btgt);
+		AuDbgDentry(a->h_dentry[DST]);
+		hinode = au_hi(a->dentry->d_inode, a->btgt);
+		/* todo: bad approach? */
+		mutex_lock_nested(&hinode->hi_inode->i_mutex, AuLsc_I_CHILD);
+		au_hin_suspend(hinode);
+		mutex_unlock(&hinode->hi_inode->i_mutex);
+		au_set_h_dptr(a->dentry, a->btgt, NULL);
+		AuDbgDentry(a->h_dentry[DST]);
+		err = au_lkup_neg(a->dentry, a->btgt);
 		if (unlikely(err))
 			goto out_whtmp;
-		a->h_dentry[DST] = au_h_dptr(dentry, a->btgt);
+		a->h_dentry[DST] = au_h_dptr(a->dentry, a->btgt);
 	}
 
 	/* cpup src */
 	if (a->h_dentry[DST]->d_inode && a->bstart[SRC] != a->btgt) {
-		mutex_lock_nested(&a->h_dentry[SRC]->d_inode->i_mutex,
-				  AuLsc_I_CHILD);
-		err = au_sio_cpup_simple(src_dentry, a->btgt, -1,
+		struct mutex *h_mtx = &a->h_dentry[SRC]->d_inode->i_mutex;
+
+		mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
+		err = au_sio_cpup_simple(a->src_dentry, a->btgt, -1,
 					 !AuCpup_DTIME);
-		mutex_unlock(&a->h_dentry[SRC]->d_inode->i_mutex);
+		mutex_unlock(h_mtx);
 		if (unlikely(err))
 			goto out_whtmp;
 	}
 
 	/* rename by vfs_rename or cpup */
-	need_diropq = au_ftest_ren(a->flags, ISDIR)
-		&& (wh_dentry[DST]
-		    || au_dbdiropq(dentry) == a->btgt
+	a->need_diropq = au_ftest_ren(a->flags, ISDIR)
+		&& (a->wh_dentry[DST]
+		    || au_dbdiropq(a->dentry) == a->btgt
 		    /* hide the lower to keep xino */
-		    || a->btgt < au_dbend(dentry)
+		    || a->btgt < au_dbend(a->dentry)
 		    || au_opt_test(a->mnt_flags, ALWAYS_DIROPQ));
-	bycpup = 0;
-	if (au_dbstart(src_dentry) == a->btgt) {
-		if (need_diropq && au_dbdiropq(src_dentry) == a->btgt)
-			need_diropq = 0;
-		vfsub_args_init(&vargs, &ign, au_ftest_ren(a->flags, DLGT), 0);
-		if (unlikely(au_opt_test(a->mnt_flags, UDBA_INOTIFY)
-			     && au_ftest_ren(a->flags, ISDIR)))
-			vfsub_ign_hinode(&vargs, IN_MOVE_SELF,
-					 au_hi(src_dentry->d_inode, a->btgt));
-		AuDebugOn(au_dbstart(src_dentry) != a->btgt);
-		err = vfsub_rename(h_dir[SRC], au_h_dptr(src_dentry, a->btgt),
-				   h_dir[DST], a->h_dentry[DST], &vargs);
-	} else {
-		bycpup = 1;
-		mutex_lock_nested(&a->h_dentry[SRC]->d_inode->i_mutex,
-				  AuLsc_I_CHILD);
-		au_set_dbstart(src_dentry, a->btgt);
-		au_set_h_dptr(src_dentry, a->btgt, dget(a->h_dentry[DST]));
-		err = au_sio_cpup_single(src_dentry, a->btgt, a->bstart[SRC],
-					 -1, !AuCpup_DTIME);
-		if (unlikely(err)) {
-			au_set_h_dptr(src_dentry, a->btgt, NULL);
-			au_set_dbstart(src_dentry, a->bstart[SRC]);
-		}
-		mutex_unlock(&a->h_dentry[SRC]->d_inode->i_mutex);
-	}
+	a->bycpup = 0;
+	vfsub_args_init(&a->vargs, a->ign, au_ftest_ren(a->flags, DLGT), 0);
+	err = au_ren_or_cpup(a);
 	if (unlikely(err))
 		goto out_whtmp;
 
 	/* make dir opaque */
-	if (need_diropq) {
-		struct dentry *diropq;
-		struct inode *h_inode;
-
-		h_inode = au_h_dptr(src_dentry, a->btgt)->d_inode;
-		au_hdir_lock(h_inode, src_dentry->d_inode, a->btgt);
-		diropq = au_diropq_create(src_dentry, a->btgt,
-					  au_ftest_ren(a->flags, DLGT));
-		au_hdir_unlock(h_inode, src_dentry->d_inode, a->btgt);
-		err = PTR_ERR(diropq);
-		if (IS_ERR(diropq))
+	if (a->need_diropq) {
+		err = au_ren_diropq(a);
+		if (unlikely(err))
 			goto out_rename;
-		dput(diropq);
 	}
 
 	/* update target timestamps */
-	AuDebugOn(au_dbstart(src_dentry) != a->btgt);
-	h_src = au_h_dptr(src_dentry, a->btgt);
-	au_update_fuse_h_inode(NULL, h_src); /*ignore*/
-	/* fsstack_copy_attr_atime(src_dentry->d_inode, h_src->d_inode); */
-	src_dentry->d_inode->i_ctime = h_src->d_inode->i_ctime;
+	AuDebugOn(au_dbstart(a->src_dentry) != a->btgt);
+	a->h_src = au_h_dptr(a->src_dentry, a->btgt);
+	au_update_fuse_h_inode(NULL, a->h_src); /*ignore*/
+	/* fsstack_copy_attr_atime(a->src_dentry->d_inode, a->h_src->d_inode); */
+	a->src_dentry->d_inode->i_ctime = a->h_src->d_inode->i_ctime;
 
 	/* remove whiteout for dentry */
-	if (wh_dentry[DST]) {
-		err = au_wh_unlink_dentry(h_dir[DST], wh_dentry[DST],
-					  dentry, dir, /*dlgt*/0);
+	if (a->wh_dentry[DST]) {
+		err = au_wh_unlink_dentry(au_pinned_hdir(a->pin + DST, a->btgt),
+					  a->wh_dentry[DST], a->dentry,
+					  /*dlgt*/0);
 		if (unlikely(err))
 			goto out_diropq;
 	}
 
 	/* remove whtmp */
-	if (thargs) {
-		if (au_test_nfs(h_dst->d_sb)
-		    || !au_nhash_test_longer_wh(&a->whlist, a->btgt,
-						au_sbi(a->sb)->si_dirwh)) {
-			err = au_whtmp_rmdir(h_dst, &a->whlist, a->btgt, dir,
-					     dentry->d_inode, /*noself*/0);
-			if (unlikely(err))
-				AuWarn("failed removing whtmp dir %.*s (%d), "
-				       "ignored.\n", AuDLNPair(h_dst), err);
-		} else {
-			au_whtmp_kick_rmdir(h_dst, &a->whlist, a->btgt, dir,
-					    dentry->d_inode, /*noself*/0,
-					    thargs);
-			dput(h_dst);
-			thargs = NULL;
-		}
-	}
+	if (a->thargs)
+		/* ignore this error */
+		au_ren_del_whtmp(a);
+
 	err = 0;
 	goto out_success;
 
-#define RevertFailure(fmt, args...) do { \
-		AuIOErrWhck("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(src_dentry, a->btgt)->d_inode;
-		/* lock inode simply since inotify is not set to h_inode. */
-		mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_PARENT);
-		rerr = au_diropq_remove(src_dentry, a->btgt,
-					au_ftest_ren(a->flags, DLGT));
-		mutex_unlock(&h_inode->i_mutex);
-		if (rerr)
-			RevertFailure("remove diropq %.*s",
-				      AuDLNPair(src_dentry));
-	}
+	if (a->need_diropq)
+		au_ren_rev_diropq(err, a);
  out_rename:
-	if (!bycpup) {
-		struct dentry *d;
-		struct qstr *name = &src_dentry->d_name;
-
-		d = au_lkup_one(name->name, a->h_parent[SRC], name->len, &ndx);
-		rerr = PTR_ERR(d);
-		if (IS_ERR(d)) {
-			RevertFailure("au_lkup_one %.*s",
-				      AuDLNPair(src_dentry));
-			goto out_whtmp;
-		}
-		AuDebugOn(d->d_inode);
-		vfsub_args_init(&vargs, &ign, au_ftest_ren(a->flags, DLGT), 0);
-		if (unlikely(au_opt_test(a->mnt_flags, UDBA_INOTIFY)
-			     && au_ftest_ren(a->flags, ISDIR)))
-			vfsub_ign_hinode(&vargs, IN_MOVE_SELF,
-					 au_hi(src_dentry->d_inode, a->btgt));
-		rerr = vfsub_rename(h_dir[DST], au_h_dptr(src_dentry, a->btgt),
-				    h_dir[SRC], d, &vargs);
-		d_drop(d);
-		dput(d);
-		/* au_set_h_dptr(src_dentry, a->btgt, NULL); */
-		if (rerr)
-			RevertFailure("rename %.*s", AuDLNPair(src_dentry));
-	} else {
-		vfsub_args_init(&vargs, NULL, au_ftest_ren(a->flags, DLGT), 0);
-		rerr = vfsub_unlink(h_dir[DST], a->h_dentry[DST], &vargs);
-		au_set_h_dptr(src_dentry, a->btgt, NULL);
-		au_set_dbstart(src_dentry, a->bstart[SRC]);
-		if (rerr)
-			RevertFailure("unlink %.*s",
-				      AuDLNPair(a->h_dentry[DST]));
-	}
+	if (!a->bycpup)
+		au_ren_rev_rename(err, a);
+	else
+		au_ren_rev_cpup(err, a);
  out_whtmp:
-	if (thargs) {
-		struct dentry *d;
-		struct qstr *name = &dentry->d_name;
-
-		d = au_lkup_one(name->name, a->h_parent[DST], name->len, &ndx);
-		rerr = PTR_ERR(d);
-		if (IS_ERR(d)) {
-			RevertFailure("lookup %.*s", AuLNPair(name));
-			goto out_whdst;
-		}
-		if (d->d_inode) {
-			d_drop(d);
-			dput(d);
-			goto out_whdst;
-		}
-		AuDebugOn(d->d_inode);
-		vfsub_args_init(&vargs, &ign, au_ftest_ren(a->flags, DLGT), 0);
-		if (unlikely(0 && au_opt_test(a->mnt_flags, UDBA_INOTIFY)
-			     && au_ftest_ren(a->flags, ISDIR)))
-			vfsub_ign_hinode(&vargs, IN_MOVE_SELF,
-					 au_hi(dentry->d_inode, a->btgt));
-		rerr = vfsub_rename(h_dir[DST], h_dst, h_dir[DST], d, &vargs);
-		d_drop(d);
-		dput(d);
-		if (rerr) {
-			RevertFailure("rename %.*s", AuDLNPair(h_dst));
-			goto out_whdst;
-		}
-		au_set_h_dptr(dentry, a->btgt, NULL);
-		au_set_h_dptr(dentry, a->btgt, dget(h_dst));
-	}
+	if (a->thargs)
+		au_ren_rev_whtmp(err, a);
  out_whdst:
-	dput(wh_dentry[DST]);
-	wh_dentry[DST] = NULL;
+	dput(a->wh_dentry[DST]);
+	a->wh_dentry[DST] = NULL;
  out_whsrc:
-	if (wh_dentry[SRC]) {
-		rerr = au_wh_unlink_dentry(h_dir[SRC], wh_dentry[SRC],
-					   src_dentry, src_dir, /*dlgt*/0);
-		if (rerr)
-			RevertFailure("unlink %.*s", AuDLNPair(wh_dentry[SRC]));
+	if (a->wh_dentry[SRC])
+		au_ren_rev_whsrc(err, a);
+	d_drop(a->src_dentry);
+	bend = au_dbend(a->src_dentry);
+	for (bindex = au_dbstart(a->src_dentry); bindex <= bend; bindex++) {
+		h_d = au_h_dptr(a->src_dentry, bindex);
+		if (h_d)
+			d_drop(h_d);
 	}
-#undef RevertFailure
-	d_drop(src_dentry);
-	bend = au_dbend(src_dentry);
-	for (bindex = au_dbstart(src_dentry); bindex <= bend; bindex++) {
-		struct dentry *hd;
-
-		hd = au_h_dptr(src_dentry, bindex);
-		if (hd)
-			d_drop(hd);
+	d_drop(a->dentry);
+	bend = au_dbend(a->dentry);
+	for (bindex = au_dbstart(a->dentry); bindex <= bend; bindex++) {
+		h_d = au_h_dptr(a->dentry, bindex);
+		if (h_d)
+			d_drop(h_d);
 	}
-	d_drop(dentry);
-	bend = au_dbend(dentry);
-	for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) {
-		struct dentry *hd;
-
-		hd = au_h_dptr(dentry, bindex);
-		if (hd)
-			d_drop(hd);
-	}
-	au_update_dbstart(dentry);
-	if (thargs)
-		d_drop(h_dst);
+	au_update_dbstart(a->dentry);
+	if (a->thargs)
+		d_drop(a->h_dst);
  out_success:
-	dput(wh_dentry[SRC]);
-	dput(wh_dentry[DST]);
+	dput(a->wh_dentry[SRC]);
+	dput(a->wh_dentry[DST]);
  out_thargs:
-	if (thargs) {
-		dput(h_dst);
-		kfree(thargs);
+	if (a->thargs) {
+		dput(a->h_dst);
+		kfree(a->thargs);
 	}
  out:
 	AuTraceErr(err);
@@ -393,7 +492,7 @@ static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt)
 	if (bstart != btgt) {
 		struct au_nhash *whlist;
 
-		whlist = au_nhash_new(GFP_TEMPORARY);
+		whlist = au_nhash_new(GFP_NOFS);
 		err = PTR_ERR(whlist);
 		if (IS_ERR(whlist))
 			goto out;
@@ -410,7 +509,7 @@ static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt)
  out:
 	if (/* unlikely */(err == -ENOTEMPTY)) {
 		AuWarn1("renaming dir who has child(ren) on multiple branches,"
-		      " is not supported\n");
+			" is not supported\n");
 		err = -EXDEV;
 	}
 	AuTraceErr(err);
@@ -438,50 +537,11 @@ int au_wbr(struct dentry *dentry, aufs_bindex_t btgt)
 	return btgt;
 }
 
-/* todo: meaningless lock if CONFIG_AUFS_DEBUG is disabled. */
-static void au_hgdirs(struct au_hinode **hgdir, struct rename_args *a)
-{
-	struct dentry *gparent[2];
-	struct inode *gdir;
-
-	if (!au_opt_test(a->mnt_flags, UDBA_INOTIFY))
-		return;
-
-	gparent[SRC] = NULL;
-	if (!IS_ROOT(a->parent[SRC])) {
-		gparent[SRC] = dget_parent(a->parent[SRC]);
-		gdir = gparent[SRC]->d_inode;
-		if (gparent[SRC] != a->parent[DST]) {
-			ii_read_lock_parent3(gdir);
-			hgdir[SRC] = au_hi(gdir, a->btgt);
-			ii_read_unlock(gdir);
-		} else
-			hgdir[SRC] = au_hi(gdir, a->btgt);
-		dput(gparent[SRC]);
-	}
-
-	if (!au_ftest_ren(a->flags, ISSAMEDIR)
-	    && !IS_ROOT(a->parent[DST])
-	    && a->parent[DST] != gparent[SRC]) {
-		gparent[DST] = dget_parent(a->parent[DST]);
-		gdir = gparent[DST]->d_inode;
-		if (gparent[DST] != a->parent[SRC]) {
-			ii_read_lock_parent3(gdir);
-			hgdir[DST] = au_hi(gdir, a->btgt);
-			ii_read_unlock(gdir);
-		} else
-			hgdir[DST] = au_hi(gdir, a->btgt);
-		dput(gparent[DST]);
-	}
-}
-
 /*
  * simple tests for rename.
  * following the checks in vfs, plus the parent-child relationship.
  */
-static int au_may_ren(struct inode *src_dir, struct dentry *src_dentry,
-		      struct inode *dir, struct dentry *dentry,
-		      struct rename_args *a, struct au_ndx *ndx)
+static int au_may_ren(struct au_ren_args *a)
 {
 	int err;
 	struct inode *h_inode;
@@ -489,8 +549,8 @@ static int au_may_ren(struct inode *src_dir, struct dentry *src_dentry,
 	AuTraceEnter();
 
 	if (a->bstart[SRC] == a->btgt) {
-		err = au_may_del(src_dentry, a->btgt, a->h_parent[SRC],
-				 au_ftest_ren(a->flags, ISDIR), ndx);
+		err = au_may_del(a->src_dentry, a->btgt, a->h_parent[SRC],
+				 au_ftest_ren(a->flags, ISDIR), &a->ndx);
 		if (unlikely(err))
 			goto out;
 		err = -EINVAL;
@@ -504,16 +564,16 @@ static int au_may_ren(struct inode *src_dir, struct dentry *src_dentry,
 
 	err = -EIO;
 	h_inode = a->h_dentry[DST]->d_inode;
-	if (!dentry->d_inode) {
+	if (!a->dentry->d_inode) {
 		if (unlikely(h_inode))
 			goto out;
-		err = au_may_add(dentry, a->btgt, a->h_parent[DST],
-				 au_ftest_ren(a->flags, ISDIR), ndx);
+		err = au_may_add(a->dentry, a->btgt, a->h_parent[DST],
+				 au_ftest_ren(a->flags, ISDIR), &a->ndx);
 	} else {
 		if (unlikely(!h_inode || !h_inode->i_nlink))
 			goto out;
-		err = au_may_del(dentry, a->btgt, a->h_parent[DST],
-				 au_ftest_ren(a->flags, ISDIR), ndx);
+		err = au_may_del(a->dentry, a->btgt, a->h_parent[DST],
+				 au_ftest_ren(a->flags, ISDIR), &a->ndx);
 		if (unlikely(err))
 			goto out;
 		err = -ENOTEMPTY;
@@ -529,17 +589,354 @@ static int au_may_ren(struct inode *src_dir, struct dentry *src_dentry,
 	return err;
 }
 
+/*
+ * locking order
+ * (VFS)
+ * - src_dir and dir by lock_rename()
+ * - inode if exitsts
+ * (aufs)
+ * - lock all
+ *   + src_dentry and dentry by aufs_read_and_write_lock2() which calls,
+ *     + si_read_lock
+ *     + di_write_lock2_child()
+ *       + di_write_lock_child()
+ *	   + ii_write_lock_child()
+ *       + di_write_lock_child2()
+ *	   + ii_write_lock_child2()
+ *     + src_parent and parent
+ *       + di_write_lock_parent()
+ *	   + ii_write_lock_parent()
+ *       + di_write_lock_parent2()
+ *	   + ii_write_lock_parent2()
+ *   + if udab=inotify is specified, lock grand parents (crazy)
+ *     + di_read_lock_gparent()
+ *       + ii_read_lock_gparent()
+ *     + di_read_lock_gparent2()
+ *       + ii_read_lock_gparent2()
+ *     + mutex_lock_gparent()
+ *     + mutex_lock_gparent2()
+ *   + lower src_dir and dir by vfsub_lock_rename()?
+ *   + verify the every relations between child, parent and grand parent. if any
+ *     of them failed, unlock all and return -EBUSY.
+ */
+static void au_ren_pin_init(struct au_pin *first, struct dentry *d1,
+			    struct au_pin *next, struct dentry *d2)
+{
+	AuTraceEnter();
+
+	/* AuLsc_DI_PARENT3 is for higher gparent initially */
+	au_pin_init(first, d1, /*di_locked*/1, AuLsc_DI_PARENT2,
+		    AuLsc_I_PARENT2, /*do_gp*/1);
+	/* AuLsc_DI_PARENT4 is for lower gparent initially */
+	au_pin_init(next, d2, /*di_locked*/1, AuLsc_DI_PARENT3,
+		    AuLsc_I_PARENT4, /*do_gp*/1);
+}
+
+static void au_ren_fake_pin(struct au_ren_args *a)
+{
+	int i;
+	struct au_pin1 *p;
+	struct inode *h_i;
+
+	AuTraceEnter();
+
+	/* they increment the ref counter */
+	for (i = 0; i < 2; i++) {
+		p = a->pin[i].pin + AuPin_PARENT;
+		au_pin_set_parent(a->pin + i, a->parent[i]);
+		dput(a->parent[i]);
+		h_i = a->h_parent[i]->d_inode;
+		au_pin_set_h_dir(a->pin + i, h_i);
+		iput(h_i);
+
+		if (!a->gparent[i]) {
+			au_pin_set_gparent(a->pin + i, NULL);
+			au_pin_set_h_gdir(a->pin + i, NULL);
+		} else {
+			au_pin_set_gparent(a->pin + i, a->gparent[i]);
+			dput(a->gparent[i]);
+			h_i = au_h_iptr(a->gparent[i]->d_inode, a->btgt);
+			au_pin_set_h_gdir(a->pin + i, h_i);
+			iput(h_i);
+		}
+	}
+}
+
+/* crazy */
+/* cf. i_op.c: au_do_pin() */
+static int au_ren_pin4(int higher, int lower, struct au_ren_args *a)
+{
+	int err, i, lsc;
+	struct au_pin *p;
+	struct au_pin1 *p4[4];
+	struct inode *h_dir;
+
+	LKTRTrace("%d, %d\n", higher, lower);
+
+	p = a->pin + higher;
+	p4[0] = au_pin_gp(p); /* highest */
+	p4[1] = p->pin + AuPin_PARENT;
+	p = a->pin + lower;
+	p4[2] = au_pin_gp(p);
+	p4[3] = p->pin + AuPin_PARENT;
+
+	if (a->gparent[higher]) {
+		au_pin_do_set_parent(p4[0], a->gparent[higher]);
+		au_pin_do_set_dentry(p4[0], a->parent[higher]);
+	}
+	au_pin_do_set_parent(p4[1], a->parent[higher]);
+	if (a->gparent[lower]) {
+		au_pin_do_set_parent(p4[2], a->gparent[lower]);
+		au_pin_do_set_dentry(p4[2], a->parent[lower]);
+	}
+	au_pin_do_set_parent(p4[3], a->parent[lower]);
+
+	DiMustWriteLock(p4[3]->parent);
+	di_write_unlock(p4[1]->parent);
+	if (p4[2]->parent)
+		di_read_lock_parent2(p4[2]->parent, AuLock_IR);
+	di_write_lock_parent3(p4[1]->parent);
+	if (p4[0]->parent)
+		di_read_lock_parent4(p4[0]->parent, AuLock_IR);
+
+	lsc = AuLsc_I_PARENT;
+	for (i = 0; i < 4; i++, lsc++) {
+		if (p4[i]->parent) {
+			h_dir = au_h_iptr(p4[i]->parent->d_inode, a->btgt);
+			au_pin_do_set_h_dir(p4[i], h_dir);
+			mutex_lock_nested(&h_dir->i_mutex, lsc);
+		}
+	}
+
+	err = 0;
+	AuTraceErr(err);
+	return err;
+}
+
+static struct dentry *au_ren_pin3(int higher, int lower, struct au_ren_args *a)
+{
+	struct dentry *h_trap;
+	struct au_pin *p;
+	int err;
+
+	LKTRTrace("%d, %d\n", higher, lower);
+
+	p = a->pin + higher;
+	err = au_do_pin(p->pin + AuPin_PARENT, au_pin_gp(p), a->btgt,
+			/*do_gp*/1);
+	h_trap = ERR_PTR(err);
+	if (unlikely(err))
+		goto out;
+	p = a->pin + lower;
+	err = au_do_pin(p->pin + AuPin_PARENT, NULL, a->btgt, /*do_gp*/0);
+	h_trap = ERR_PTR(err);
+	if (unlikely(err)) {
+		au_do_unpin(p->pin + AuPin_PARENT, au_pin_gp(p));
+		goto out;
+	}
+	h_trap = au_pinned_h_parent(p, a->btgt);
+
+ out:
+	AuTraceErrPtr(h_trap);
+	return h_trap;
+}
+
+static struct dentry *au_ren_pin(struct au_ren_args *a)
+{
+	struct dentry *h_trap;
+	struct inode *h_gdir;
+	int err, i, same_gp;
+
+	AuTraceEnter();
+	AuDebugOn(!au_opt_test(a->mnt_flags, UDBA_INOTIFY));
+
+	vfsub_lock_rename_mutex(a->h_dentry[SRC]->d_sb);
+	au_fset_ren(a->flags, VFSLOCK);
+
+	/* gdir is not locked */
+	same_gp = 0;
+	if (!IS_ROOT(a->parent[SRC]))
+		a->gparent[SRC] = dget_parent(a->parent[SRC]);
+	if (!IS_ROOT(a->parent[DST])) {
+		a->gparent[DST] = dget_parent(a->parent[DST]);
+		same_gp = (a->gparent[SRC] == a->gparent[DST]);
+	}
+
+	/*
+	 * patterns
+	 * - gparent[SRC] is parent[DST]
+	 * - parent[SRC] is gparent[DST]
+	 * - gparent[SRC] is gparent[DST]
+	 * - gparent[SRC] is a descendant of parent[DST]
+	 * - parent[SRC] is an ancestor of gparent[DST]
+	 * - not within grand parent range
+	 */
+	err = 0;
+	h_trap = ERR_PTR(-EBUSY);
+	if (a->gparent[SRC] == a->parent[DST]) {
+		LKTRLabel(here);
+		au_ren_pin_init(a->pin + DST, a->dentry, a->pin + SRC,
+				a->src_dentry);
+		h_trap = au_ren_pin3(DST, SRC, a);
+		if (!IS_ERR(h_trap)) {
+			h_gdir = au_pinned_h_dir(a->pin + DST);
+			err = au_verify_parent(a->h_parent[SRC], h_gdir);
+			if (unlikely(err))
+				h_trap = ERR_PTR(-EBUSY);
+		}
+	} else if (a->parent[SRC] == a->gparent[DST] || same_gp) {
+		LKTRLabel(here);
+		au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST,
+				a->dentry);
+		h_trap = au_ren_pin3(SRC, DST, a);
+		if (!IS_ERR(h_trap)) {
+			if (!same_gp)
+				h_gdir = au_pinned_h_dir(a->pin + SRC);
+			else
+				h_gdir = au_pinned_h_gdir(a->pin + SRC);
+			err = au_verify_parent(a->h_parent[DST], h_gdir);
+			if (unlikely(err))
+				h_trap = ERR_PTR(-EBUSY);
+		}
+	} else if (a->gparent[SRC]
+		   && (h_trap = au_test_subdir(a->gparent[SRC],
+					       a->parent[DST]))) {
+		LKTRLabel(here);
+		au_ren_pin_init(a->pin + DST, a->dentry, a->pin + SRC,
+				a->src_dentry);
+		if (a->gparent[DST]) {
+			err = au_ren_pin4(DST, SRC, a);
+			if (unlikely(err))
+				h_trap = ERR_PTR(err);
+		} else {
+			struct dentry *t;
+			t = au_ren_pin3(DST, SRC, a);
+			AuDebugOn(t == h_trap);
+		}
+	} else /* if (a->gparent[DST]
+		  && (h_trap = au_test_subdir(a->gparent[DST],
+		  a->parent[SRC]))) */ {
+		LKTRLabel(here);
+		h_trap = NULL;
+		if (a->gparent[DST])
+			h_trap = au_test_subdir(a->gparent[DST],
+						a->parent[SRC]);
+		au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST,
+				a->dentry);
+		err = au_ren_pin4(SRC, DST, a);
+		if (unlikely(err))
+			h_trap = ERR_PTR(err);
+	}
+	au_fset_ren(a->flags, PINNED);
+
+	if (!IS_ERR(h_trap)) {
+		err = 0;
+		for (i = 0; !err && i < 2; i++) {
+			h_gdir = au_pinned_h_gdir(a->pin + i);
+			if (h_gdir)
+				err = au_verify_parent(a->h_parent[i], h_gdir);
+		}
+		if (unlikely(err))
+			h_trap = ERR_PTR(err);
+	}
+
+	dput(a->gparent[SRC]);
+	dput(a->gparent[DST]);
+	/* memset(a->gparent, 0, sizeof(a->gparent)); */
+	AuTraceErrPtr(h_trap);
+	return h_trap;
+}
+
+static void au_ren_unlock(struct au_ren_args *a)
+{
+	int i;
+
+	AuTraceEnter();
+
+	if (a->h_locked[0])
+		vfsub_unlock_rename(a->h_locked[0], a->h_locked[1]);
+	if (au_ftest_ren(a->flags, PINNED)) {
+		au_unpin(a->pin + SRC);
+		au_unpin(a->pin + DST);
+		memset(a->gparent, 0, sizeof(a->gparent));
+	}
+	if (au_ftest_ren(a->flags, VFSLOCK))
+		vfsub_unlock_rename_mutex(a->h_dentry[SRC]->d_sb);
+	for (i = 0; i < 2; i++)
+		if (unlikely(a->gparent[i])) {
+			di_read_unlock(a->gparent[i], AuLock_IR);
+			dput(a->gparent[i]);
+		}
+}
+
+static int au_ren_lock(struct au_ren_args *a)
+{
+	int err;
+	const int hinotify = au_opt_test(a->mnt_flags, UDBA_INOTIFY);
+
+	AuTraceEnter();
+
+	err = 0;
+	if (!hinotify
+	    || (au_ftest_ren(a->flags, ISSAMEDIR) && IS_ROOT(a->parent[SRC]))) {
+		au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST,
+				a->dentry);
+		LKTRLabel(here);
+		a->h_locked[0] = a->h_parent[SRC];
+		a->h_locked[1] = a->h_parent[DST];
+		a->h_trap = vfsub_lock_rename(a->h_locked[0], a->h_locked[1]);
+		au_ren_fake_pin(a);
+	} else if (au_ftest_ren(a->flags, ISSAMEDIR)
+		   && !IS_ROOT(a->parent[SRC])) {
+		/* this and next block should not be compiled when
+		   hinotify is not enabled */
+		/* irregular/tricky rename lock */
+		LKTRLabel(here);
+		au_ren_pin_init(a->pin + SRC, a->src_dentry, a->pin + DST,
+				a->dentry);
+		a->gparent[SRC] = dget_parent(a->parent[SRC]);
+		di_read_lock_parent2(a->gparent[SRC], AuLock_IR);
+		a->h_locked[0] = a->h_parent[SRC];
+		a->h_locked[1] = dget_parent(a->h_parent[SRC]);
+		a->h_trap = vfsub_lock_rename(a->h_locked[0], a->h_locked[1]);
+		err = au_verify_parent(a->h_parent[SRC],
+				       a->h_locked[1]->d_inode);
+		dput(a->h_locked[1]);
+		if (!err)
+			au_ren_fake_pin(a);
+	} else {
+		/* 3 or 4 dir locks. crazy */
+		LKTRLabel(here);
+		a->h_trap = au_ren_pin(a);
+		if (IS_ERR(a->h_trap))
+			err = PTR_ERR(a->h_trap);
+	}
+
+	if (!err && au_dbstart(a->src_dentry) == a->btgt)
+		err = au_verify_parent(a->h_dentry[SRC],
+				       a->h_parent[SRC]->d_inode);
+	if (!err && au_dbstart(a->dentry) == a->btgt)
+		err = au_verify_parent(a->h_dentry[DST],
+				       a->h_parent[DST]->d_inode);
+	if (unlikely(err)) {
+		err = -EBUSY;
+		au_ren_unlock(a);
+	}
+	AuTraceErr(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;
+	int err;
 	aufs_bindex_t bend, bindex;
-	struct inode *inode[2], *dirs[2];
-	struct au_hinode *hgdir[2], *hdir;
+	unsigned char do_dt_dstdir, hinotify;
+	struct inode *inode[2];
 	enum { PARENT, CHILD };
 	/* reduce stack space */
 	struct {
-		struct rename_args a;
+		struct au_ren_args a;
 		struct au_dtime dt[2][2];
 	} *p;
 	struct au_wr_dir_args wr_dir_args = {
@@ -547,47 +944,48 @@ int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
 		.flags		= AuWrDir_ADD_ENTRY
 	};
 
+	//lktr_set_pid(current->pid, LktrArrayPid);
 	LKTRTrace("i%lu, %.*s, i%lu, %.*s\n",
 		  src_dir->i_ino, AuDLNPair(src_dentry),
 		  dir->i_ino, AuDLNPair(dentry));
+	AuDebugOn(IS_ROOT(src_dentry) || IS_ROOT(dentry));
 	IMustLock(src_dir);
 	IMustLock(dir);
 	inode[DST] = dentry->d_inode;
 	if (inode[DST]) {
 		IMustLock(inode[DST]);
-		igrab(inode[DST]);
+		au_igrab(inode[DST]);
 	}
 
 	err = -ENOMEM;
 	BUILD_BUG_ON(sizeof(*p) > PAGE_SIZE);
-	p = kmalloc(sizeof(*p), GFP_TEMPORARY);
+	p = kzalloc(sizeof(*p), GFP_NOFS);
 	if (unlikely(!p))
 		goto out;
 
 	err = -ENOTDIR;
+	p->a.src_dir = src_dir;
+	p->a.src_dentry = src_dentry;
+	p->a.dir = dir;
+	p->a.dentry = dentry;
 	p->a.sb = src_dentry->d_sb;
 	inode[SRC] = src_dentry->d_inode;
-	flags = 0;
 	p->a.flags = 0;
 	if (S_ISDIR(inode[SRC]->i_mode)) {
-		flags = AuLock_DIR;
 		au_fset_ren(p->a.flags, ISDIR);
 		if (unlikely(inode[DST] && !S_ISDIR(inode[DST]->i_mode)))
 			goto out_free;
-	}
+		aufs_read_and_write_lock2(dentry, src_dentry, AuLock_DIR);
+	} else
+		aufs_read_and_write_lock2(dentry, src_dentry, 0);
 
-	aufs_read_and_write_lock2(dentry, src_dentry, flags);
 	p->a.mnt_flags = au_mntflags(p->a.sb);
 	if (unlikely(au_test_dlgt(p->a.mnt_flags)))
 		au_fset_ren(p->a.flags, DLGT);
 	p->a.parent[SRC] = src_dentry->d_parent; /* dir inode is locked */
 	p->a.parent[DST] = dentry->d_parent; /* dir inode is locked */
-	if (src_dir == dir) {
-		au_fset_ren(p->a.flags, ISSAMEDIR);
-		di_write_lock_parent(p->a.parent[DST]);
-	} else
-		di_write_lock2_parent(p->a.parent[SRC], p->a.parent[DST],
-				      /*isdir*/1);
+	au_fset_ren(p->a.flags, ISSAMEDIR); /* temporary */
+	di_write_lock_parent(p->a.parent[DST]);
 
 	/* which branch we process */
 	p->a.bstart[SRC] = au_dbstart(src_dentry);
@@ -624,79 +1022,84 @@ int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
 	}
 
 	/* prepare the writable parent dir on the same branch */
-	err = au_wr_dir_need_wh(src_dentry, au_ftest_ren(p->a.flags, ISDIR),
-				&p->a.btgt,
-				au_ftest_ren(p->a.flags, ISSAMEDIR)
-				? NULL : p->a.parent[DST]);
-	if (unlikely(err < 0))
-		goto out_children;
-	if (err)
-		au_fset_ren(p->a.flags, WHSRC);
 	if (p->a.bstart[DST] == p->a.btgt) {
 		au_fset_ren(p->a.flags, WHDST);
 	} else {
-		err = au_cpup_dirs(dentry, p->a.btgt,
-				   au_ftest_ren(p->a.flags, ISSAMEDIR)
-				   ? NULL : p->a.parent[SRC]);
+		err = au_cpup_dirs(dentry, p->a.btgt);
 		if (unlikely(err))
 			goto out_children;
 	}
 
-	hgdir[SRC] = NULL;
-	hgdir[DST] = NULL;
-	au_hgdirs(hgdir, &p->a);
+	if (src_dir != dir) {
+		/*
+		 * this temporary unlock is safe,
+		 * because dir->i_mutex is locked.
+		 */
+		di_write_unlock(p->a.parent[DST]);
+		di_write_lock_parent(p->a.parent[SRC]);
+		err = au_wr_dir_need_wh
+			(src_dentry, au_ftest_ren(p->a.flags, ISDIR),
+			 &p->a.btgt);
+		di_write_unlock(p->a.parent[SRC]);
+		di_write_lock2_parent(p->a.parent[SRC], p->a.parent[DST],
+				      /*isdir*/1);
+		au_fclr_ren(p->a.flags, ISSAMEDIR);
+	} else
+		err = au_wr_dir_need_wh
+			(src_dentry, au_ftest_ren(p->a.flags, ISDIR),
+			 &p->a.btgt);
+	if (unlikely(err < 0))
+		goto out_children;
+	if (err)
+		au_fset_ren(p->a.flags, WHSRC);
+
+	hinotify = au_opt_test(p->a.mnt_flags, UDBA_INOTIFY);
 	p->a.h_parent[SRC] = au_h_dptr(p->a.parent[SRC], p->a.btgt);
 	p->a.h_parent[DST] = au_h_dptr(p->a.parent[DST], p->a.btgt);
-	dirs[0] = src_dir;
-	dirs[1] = dir;
-
-	AuDbgSleep_UdbaRace();
-	p->a.h_trap = au_hdir_lock_rename(p->a.h_parent, dirs, p->a.btgt,
-					  au_ftest_ren(p->a.flags, ISSAMEDIR));
-	/* todo: revalidate the lower dentries? */
+	err = au_ren_lock(&p->a);
+	if (unlikely(err))
+		goto out_children;
 
 	if (!au_opt_test(p->a.mnt_flags, UDBA_NONE)) {
-		struct au_ndx ndx = {
-			.nfsmnt	= au_nfsmnt(p->a.sb, p->a.btgt),
-			.flags	= 0,
-			.nd	= NULL,
-			/* .br	= NULL */
-		};
+		p->a.ndx.nfsmnt	= au_nfsmnt(p->a.sb, p->a.btgt);
 		if (unlikely(au_ftest_ren(p->a.flags, DLGT)))
-			au_fset_ndx(ndx.flags, DLGT);
-		err = au_may_ren(src_dir, src_dentry, dir, dentry, &p->a, &ndx);
+			au_fset_ndx(p->a.ndx.flags, DLGT);
+		err = au_may_ren(&p->a);
 		if (unlikely(err))
 			goto out_hdir;
+		memset(&p->a.ndx, 0, sizeof(p->a.ndx));
 	}
 
 	/* store timestamps to be revertible */
 	au_dtime_store(p->dt[PARENT] + SRC, p->a.parent[SRC],
-		       p->a.h_parent[SRC], hgdir[SRC]);
+		       p->a.h_parent[SRC],
+		       au_pinned_hdir(p->a.pin + SRC, p->a.btgt),
+		       au_pinned_hgdir(p->a.pin + SRC, p->a.btgt)
+		       /* hgdir[SRC] */);
 	if (!au_ftest_ren(p->a.flags, ISSAMEDIR))
 		au_dtime_store(p->dt[PARENT] + DST, p->a.parent[DST],
-			       p->a.h_parent[DST], hgdir[DST]);
+			       p->a.h_parent[DST],
+			       au_pinned_hdir(p->a.pin + DST, p->a.btgt),
+			       au_pinned_hgdir(p->a.pin + DST, p->a.btgt)
+			       /* hgdir[DST] */);
 	do_dt_dstdir = 0;
 	if (au_ftest_ren(p->a.flags, ISDIR)) {
-		hdir = NULL;
-		if (unlikely(au_opt_test(p->a.mnt_flags, UDBA_INOTIFY)))
-			hdir = au_hi(p->a.parent[SRC]->d_inode, p->a.btgt);
 		au_dtime_store(p->dt[CHILD] + SRC, src_dentry,
-			       p->a.h_dentry[SRC], hdir);
+			       p->a.h_dentry[SRC], au_hi(inode[SRC], p->a.btgt),
+			       au_pinned_hdir(p->a.pin + SRC, p->a.btgt));
 		if (p->a.h_dentry[DST]->d_inode) {
 			do_dt_dstdir = 1;
-			if (unlikely(au_opt_test(p->a.mnt_flags, UDBA_INOTIFY)))
-				hdir = au_hi(p->a.parent[DST]->d_inode,
-					     p->a.btgt);
 			au_dtime_store(p->dt[CHILD] + DST, dentry,
-				       p->a.h_dentry[DST], hdir);
+				       p->a.h_dentry[DST],
+				       au_hi(inode[DST], p->a.btgt),
+				       au_pinned_hdir(p->a.pin + DST,
+						      p->a.btgt));
 		}
 	}
 
-	err = do_rename(src_dir, src_dentry, dir, dentry, &p->a);
+	err = do_rename(&p->a);
 	if (unlikely(err))
 		goto out_dt;
-	au_hdir_unlock_rename(p->a.h_parent, dirs, p->a.btgt,
-			      au_ftest_ren(p->a.flags, ISSAMEDIR));
 
 	/* update dir attributes */
 	dir->i_version++;
@@ -735,18 +1138,21 @@ int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
 	}
 	au_set_dbend(src_dentry, p->a.btgt);
 
-	bend = au_ibend(inode[SRC]);
-	for (bindex = p->a.btgt + 1; bindex <= bend; bindex++) {
-		struct inode *hi;
-		hi = au_h_iptr(inode[SRC], bindex);
-		if (hi) {
-			au_xino_write0(p->a.sb, bindex, hi->i_ino, 0);
-			/* ignore this error */
-			au_set_h_iptr(inode[SRC], bindex, NULL, 0);
+	if (au_opt_test(p->a.mnt_flags, PLINK)
+	    && !au_plink_test(src_dentry->d_sb, inode[SRC])) {
+		bend = au_ibend(inode[SRC]);
+		for (bindex = p->a.btgt + 1; bindex <= bend; bindex++) {
+			struct inode *hi;
+			hi = au_h_iptr(inode[SRC], bindex);
+			if (hi) {
+				au_xino_write0(p->a.sb, bindex, hi->i_ino, 0);
+				/* ignore this error */
+				au_set_h_iptr(inode[SRC], bindex, NULL, 0);
+			}
 		}
+		au_set_ibend(inode[SRC], p->a.btgt);
 	}
-	au_set_ibend(inode[SRC], p->a.btgt);
-	goto out_children; /* success */
+	goto out_hdir; /* success */
 
  out_dt:
 	au_dtime_revert(p->dt[PARENT] + SRC);
@@ -767,8 +1173,7 @@ int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
 		}
 	}
  out_hdir:
-	au_hdir_unlock_rename(p->a.h_parent, dirs, p->a.btgt,
-			      au_ftest_ren(p->a.flags, ISSAMEDIR));
+	au_ren_unlock(&p->a);
  out_children:
 	au_nhash_fin(&p->a.whlist);
  out_unlock:
@@ -776,6 +1181,21 @@ int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
 		au_update_dbstart(dentry);
 		d_drop(dentry);
 	}
+	if (!err) {
+		d_move(src_dentry, dentry);
+#if 0
+		lktr_set_pid(current->pid, LktrArrayPid);
+		AuDbgDentry(src_dentry);
+		AuDbgDentry(dentry);
+		lktr_clear_pid(current->pid, LktrArrayPid);
+#endif
+#if 0
+		if (inode[DST]) {
+			inode[DST]->i_flags |= S_DEAD;
+			ii_write_unlock(inode[DST]);
+		}
+#endif
+	}
 	if (au_ftest_ren(p->a.flags, ISSAMEDIR))
 		di_write_unlock(p->a.parent[DST]);
 	else
@@ -786,5 +1206,6 @@ int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
  out:
 	iput(inode[DST]);
 	AuTraceErr(err);
+	//lktr_clear_pid(current->pid, LktrArrayPid);
 	return err;
 }
diff --git a/ubuntu/aufs/iinfo.c b/ubuntu/aufs/iinfo.c
index 8ceec38..4a0ac48 100644
--- a/ubuntu/aufs/iinfo.c
+++ b/ubuntu/aufs/iinfo.c
@@ -19,7 +19,7 @@
 /*
  * inode private data
  *
- * $Id: iinfo.c,v 1.4 2008/06/02 02:36:59 sfjro Exp $
+ * $Id: iinfo.c,v 1.8 2008/09/15 03:16:36 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -97,9 +97,9 @@ void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
 	IiMustWriteLock(inode);
 	hinode = iinfo->ii_hinode + bindex;
 	hi = hinode->hi_inode;
-	AuDebugOn(bindex < au_ibstart(inode) || au_ibend(inode) < bindex
-		  || (h_inode && atomic_read(&h_inode->i_count) <= 0)
-		  || (h_inode && hi));
+	AuDebugOn(bindex < au_ibstart(inode) || au_ibend(inode) < bindex);
+	AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
+	AuDebugOn(h_inode && hi);
 
 	if (hi)
 		au_hiput(hinode);
@@ -203,7 +203,7 @@ int au_iinfo_init(struct inode *inode)
 	nbr = au_sbend(sb) + 1;
 	if (unlikely(nbr <= 0))
 		nbr = 1;
-	iinfo->ii_hinode = kcalloc(nbr, sizeof(*iinfo->ii_hinode), GFP_KERNEL);
+	iinfo->ii_hinode = kcalloc(nbr, sizeof(*iinfo->ii_hinode), GFP_NOFS);
 	if (iinfo->ii_hinode) {
 		for (i = 0; i < nbr; i++)
 			iinfo->ii_hinode[i].hi_id = -1;
@@ -225,7 +225,7 @@ static int au_iinfo_write0(struct super_block *sb, struct au_hinode *hinode,
 	aufs_bindex_t bindex;
 
 	err = 0;
-	locked = si_read_trylock(sb, !AuLock_FLUSH); /* crucio! */
+	locked = si_noflush_read_trylock(sb); /* crucio! */
 	bindex = au_br_index(sb, hinode->hi_id);
 	if (bindex >= 0)
 		err = au_xino_write0(sb, bindex, hinode->hi_inode->i_ino, ino);
@@ -239,9 +239,9 @@ void au_iinfo_fin(struct inode *inode)
 {
 	struct au_iinfo *iinfo;
 	aufs_bindex_t bend;
+	unsigned char unlinked;
 	struct au_hinode *hi;
 	struct super_block *sb;
-	int unlinked;
 	ino_t ino;
 
 	iinfo = au_ii(inode);
diff --git a/ubuntu/aufs/inode.c b/ubuntu/aufs/inode.c
index 9589d2c..6229cf2 100644
--- a/ubuntu/aufs/inode.c
+++ b/ubuntu/aufs/inode.c
@@ -19,7 +19,7 @@
 /*
  * inode functions
  *
- * $Id: inode.c,v 1.4 2008/05/26 04:04:25 sfjro Exp $
+ * $Id: inode.c,v 1.13 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -43,7 +43,7 @@ int au_refresh_hinode_self(struct inode *inode)
 	new_sz = sizeof(*iinfo->ii_hinode) * (bend + 1);
 	iinfo = au_ii(inode);
 	p = au_kzrealloc(iinfo->ii_hinode, sizeof(*p) * (iinfo->ii_bend + 1),
-			 new_sz, GFP_KERNEL);
+			 new_sz, GFP_NOFS);
 	if (unlikely(!p))
 		goto out;
 
@@ -98,12 +98,13 @@ int au_refresh_hinode_self(struct inode *inode)
 
 int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
 {
-	int err, update, isdir;
+	int err, update;
 	struct inode *first;
 	struct au_hinode *p;
 	struct super_block *sb;
 	struct au_iinfo *iinfo;
 	aufs_bindex_t bindex, bend;
+	unsigned char isdir;
 	unsigned int flags;
 
 	LKTRTrace("%.*s\n", AuDLNPair(dentry));
@@ -143,7 +144,7 @@ int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
 			iinfo->ii_bstart = bindex;
 		if (iinfo->ii_bend < bindex)
 			iinfo->ii_bend = bindex;
-		au_set_h_iptr(inode, bindex, igrab(hd->d_inode), flags);
+		au_set_h_iptr(inode, bindex, au_igrab(hd->d_inode), flags);
 		update++;
 	}
 	au_update_brange(inode, /*do_put_zero*/0);
@@ -164,11 +165,12 @@ int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
 
 static int set_inode(struct inode *inode, struct dentry *dentry)
 {
-	int err, isdir;
+	int err;
 	struct dentry *h_dentry;
 	struct inode *h_inode;
 	umode_t mode;
 	aufs_bindex_t bindex, bstart, btail;
+	unsigned char isdir;
 	struct au_iinfo *iinfo;
 	unsigned int flags;
 
@@ -187,6 +189,9 @@ static int set_inode(struct inode *inode, struct dentry *dentry)
 	switch (mode & S_IFMT) {
 	case S_IFREG:
 		btail = au_dbtail(dentry);
+		inode->i_op = &aufs_iop;
+		inode->i_fop = &aufs_file_fop;
+		inode->i_mapping->a_ops = &aufs_aop;
 		break;
 	case S_IFDIR:
 		isdir = 1;
@@ -197,6 +202,7 @@ static int set_inode(struct inode *inode, struct dentry *dentry)
 	case S_IFLNK:
 		btail = au_dbtail(dentry);
 		inode->i_op = &aufs_symlink_iop;
+		/* inode->i_fop = &aufs_file_fop; */
 		break;
 	case S_IFBLK:
 	case S_IFCHR:
@@ -212,7 +218,14 @@ static int set_inode(struct inode *inode, struct dentry *dentry)
 		goto out;
 	}
 
+	/* do not set inotify for whiteouted dirs (SHWH mode) */
 	flags = au_hi_flags(inode, isdir);
+	if (unlikely(au_opt_test(au_mntflags(dentry->d_sb), SHWH)
+		     && au_ftest_hi(flags, NOTIFY)
+		     && dentry->d_name.len > AUFS_WH_PFX_LEN
+		     && !memcmp(dentry->d_name.name, AUFS_WH_PFX,
+				AUFS_WH_PFX_LEN)))
+		au_fclr_hi(flags, NOTIFY);
 	iinfo = au_ii(inode);
 	iinfo->ii_bstart = bstart;
 	iinfo->ii_bend = btail;
@@ -221,7 +234,8 @@ static int set_inode(struct inode *inode, struct dentry *dentry)
 		if (!h_dentry)
 			continue;
 		AuDebugOn(!h_dentry->d_inode);
-		au_set_h_iptr(inode, bindex, igrab(h_dentry->d_inode), flags);
+		au_set_h_iptr(inode, bindex, au_igrab(h_dentry->d_inode),
+			      flags);
 	}
 	au_cpup_attr_all(inode);
 
@@ -250,11 +264,13 @@ static int reval_inode(struct inode *inode, struct dentry *dentry, int *matched)
 	err = -EIO;
 	if (unlikely(inode->i_ino == parent_ino(dentry)))
 		goto out;
+	/* todo: test here */
+	//AuDebugOn(IS_DEADDIR(inode));
 
 	err = 0;
 	h_dinode = au_h_dptr(dentry, au_dbstart(dentry))->d_inode;
-	mutex_lock_nested(&inode->i_mutex, AuLsc_I_CHILD);
-	ii_write_lock_new(inode);
+	/* mutex_lock_nested(&inode->i_mutex, AuLsc_I_CHILD); */
+	ii_write_lock_new_child(inode);
 	bend = au_ibend(inode);
 	for (bindex = au_ibstart(inode); bindex <= bend; bindex++) {
 		h_inode = au_h_iptr(inode, bindex);
@@ -270,7 +286,7 @@ static int reval_inode(struct inode *inode, struct dentry *dentry, int *matched)
 	}
 	if (unlikely(err))
 		ii_write_unlock(inode);
-	mutex_unlock(&inode->i_mutex);
+	/* mutex_unlock(&inode->i_mutex); */
 
  out:
 	AuTraceErr(err);
@@ -311,15 +327,17 @@ struct inode *au_new_inode(struct dentry *dentry)
 		}
 	}
 
-	LKTRTrace("i%lu\n", xinoe.ino);
+	LKTRTrace("i%lu\n", (unsigned long)xinoe.ino);
 	inode = au_iget_locked(sb, xinoe.ino);
 	err = PTR_ERR(inode);
 	if (IS_ERR(inode))
 		goto out;
+	/* todo: test here */
+	//AuDebugOn(IS_DEADDIR(inode));
 
 	LKTRTrace("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW));
 	if (inode->i_state & I_NEW) {
-		ii_write_lock_new(inode);
+		ii_write_lock_new_child(inode);
 		err = set_inode(inode, dentry);
 		unlock_new_inode(inode);
 		if (!err)
@@ -340,7 +358,7 @@ struct inode *au_new_inode(struct dentry *dentry)
 		AuWarn1("Un-notified UDBA or repeatedly renamed dir,"
 			" b%d, %s, %.*s, hi%lu, i%lu.\n",
 			bstart, au_sbtype(h_dentry->d_sb), AuDLNPair(dentry),
-			h_ino, xinoe.ino);
+			(unsigned long)h_ino, (unsigned long)xinoe.ino);
 	xinoe.ino = 0;
 	err = au_xino_write0(sb, bstart, h_ino, 0);
 	if (!err) {
diff --git a/ubuntu/aufs/inode.h b/ubuntu/aufs/inode.h
index b8cc04d..c304113 100644
--- a/ubuntu/aufs/inode.h
+++ b/ubuntu/aufs/inode.h
@@ -19,7 +19,7 @@
 /*
  * inode operations
  *
- * $Id: inode.h,v 1.3 2008/05/26 04:04:26 sfjro Exp $
+ * $Id: inode.h,v 1.13 2008/09/15 03:14:44 sfjro Exp $
  */
 
 #ifndef __AUFS_INODE_H__
@@ -30,7 +30,7 @@
 #include <linux/fs.h>
 #include <linux/namei.h>
 #include <linux/security.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "hinode.h"
 #include "misc.h"
 #include "super.h"
@@ -43,6 +43,7 @@ struct au_iinfo {
 
 	struct au_rwsem		ii_rwsem;
 	aufs_bindex_t		ii_bstart, ii_bend;
+	__u32			ii_higen;
 	struct au_hinode	*ii_hinode;
 	struct au_vdir		*ii_vdir;
 };
@@ -52,6 +53,27 @@ struct aufs_icntnr {
 	struct inode vfs_inode;
 };
 
+struct au_pin1 {
+	/* input */
+	struct dentry *dentry;
+	unsigned char di_locked, lsc_di, lsc_hi;
+	/* auto */
+	unsigned char do_verify;
+
+	/* output */
+	struct dentry *parent;
+	struct inode *h_dir;
+};
+
+enum {AuPin_PARENT, AuPin_GPARENT};
+struct au_pin {
+#ifdef CONFIG_AUFS_HINOTIFY
+	struct au_pin1 pin[2];
+#else
+	struct au_pin1 pin[1]; /* no grand parent */
+#endif
+};
+
 /* ---------------------------------------------------------------------- */
 
 /* inode.c */
@@ -65,23 +87,29 @@ int au_test_h_perm_sio(struct inode *h_inode, int mask, int dlgt);
 
 /* i_op.c */
 extern struct inode_operations aufs_iop, aufs_symlink_iop, aufs_dir_iop;
-int aufs_fsetattr(struct file *file, struct iattr *ia);
 
 /* au_wr_dir flags */
 #define AuWrDir_ADD_ENTRY	1
-#define AuWrDir_LOCK_SRCDIR	(1 << 1)
-#define AuWrDir_ISDIR		(1 << 2)
+#define AuWrDir_ISDIR		(1 << 1)
 #define au_ftest_wrdir(flags, name)	((flags) & AuWrDir_##name)
 #define au_fset_wrdir(flags, name)	{ (flags) |= AuWrDir_##name; }
 #define au_fclr_wrdir(flags, name)	{ (flags) &= ~AuWrDir_##name; }
 
 struct au_wr_dir_args {
 	aufs_bindex_t force_btgt;
-	unsigned int flags;
+	unsigned char flags;
 };
 int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
 	      struct au_wr_dir_args *args);
 
+void au_pin_init(struct au_pin *args, struct dentry *dentry, int di_locked,
+		 int lsc_di, int lsc_hi, int do_gp);
+int au_pin(struct au_pin *args, struct dentry *dentry, aufs_bindex_t bindex,
+	   int di_locked, int do_gp) __must_check;
+int au_do_pin(struct au_pin1 *p, struct au_pin1 *gp, const aufs_bindex_t bindex,
+	      const int do_gp) __must_check;
+void au_do_unpin(struct au_pin1 *p, struct au_pin1 *gp);
+
 /* i_op_add.c */
 struct au_ndx;
 int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
@@ -95,8 +123,7 @@ int aufs_link(struct dentry *src_dentry, struct inode *dir,
 int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode);
 
 /* i_op_del.c */
-int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup,
-		      struct dentry *locked);
+int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup);
 int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
 	       struct dentry *h_parent, int isdir, struct au_ndx *ndx);
 int aufs_unlink(struct inode *dir, struct dentry *dentry);
@@ -116,12 +143,12 @@ static inline
 int au_security_inode_permission(struct inode *h_inode, int mask,
 				 struct nameidata *fake_nd, int dlgt)
 {
-	return security_inode_permission(h_inode, mask);
+	return vfsub_security_inode_permission(h_inode, mask, fake_nd);
 }
 #endif /* CONFIG_AUFS_DLGT */
 
-#ifdef CONFIG_AUFS_WORKAROUND_FUSE
-/* br_fuse.c */
+#ifdef CONFIG_AUFS_HIN_OR_FUSE
+/* hin_or_fuse.c */
 int aufs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st);
 #endif
 
@@ -189,11 +216,11 @@ 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_PARENT,	/* see AuLsc_I_PARENT in vfsub.h */
 	AuLsc_II_PARENT2,
 	AuLsc_II_PARENT3,
 	AuLsc_II_PARENT4,
-	AuLsc_II_NEW		/* new inode */
+	AuLsc_II_NEW_CHILD,
 };
 
 /*
@@ -204,7 +231,7 @@ enum {
  * ii_read_lock_parent2, ii_write_lock_parent2,
  * ii_read_lock_parent3, ii_write_lock_parent3,
  * ii_read_lock_parent4, ii_write_lock_parent4,
- * ii_read_lock_new, ii_write_lock_new
+ * ii_read_lock_new_child, ii_write_lock_new_child,
  */
 #define AuReadLockFunc(name, lsc) \
 static inline void ii_read_lock_##name(struct inode *i) \
@@ -225,7 +252,7 @@ AuRWLockFuncs(parent, PARENT);
 AuRWLockFuncs(parent2, PARENT2);
 AuRWLockFuncs(parent3, PARENT3);
 AuRWLockFuncs(parent4, PARENT4);
-AuRWLockFuncs(new, NEW);
+AuRWLockFuncs(new_child, NEW_CHILD);
 
 #undef AuReadLockFunc
 #undef AuWriteLockFunc
@@ -256,6 +283,17 @@ AuSimpleUnlockRwsemFuncs(ii, struct inode *i, au_ii(i)->ii_rwsem);
 
 /* ---------------------------------------------------------------------- */
 
+static inline struct inode *au_igrab(struct inode *inode)
+{
+	if (inode) {
+		AuDebugOn(!atomic_read(&inode->i_count));
+		atomic_inc_return(&inode->i_count);
+	}
+	return inode;
+}
+
+/* ---------------------------------------------------------------------- */
+
 static inline aufs_bindex_t au_ibstart(struct inode *inode)
 {
 	IiMustAnyLock(inode);
@@ -315,9 +353,13 @@ static inline struct au_hinode *au_hi(struct inode *inode, aufs_bindex_t bindex)
 /* tmpfs generation is too rough */
 static inline int au_test_higen(struct inode *inode, struct inode *h_inode)
 {
+	struct au_iinfo *iinfo;
+
 	IiMustAnyLock(inode);
-	return !(au_ii(inode)->ii_hsb1 == h_inode->i_sb
-		 && inode->i_generation == h_inode->i_generation);
+
+	iinfo = au_ii(inode);
+	return !(iinfo->ii_hsb1 == h_inode->i_sb
+		 && iinfo->ii_higen == h_inode->i_generation);
 }
 
 static inline au_gen_t au_iigen(struct inode *inode)
@@ -333,5 +375,171 @@ static inline au_gen_t au_iigen_dec(struct inode *inode)
 }
 #endif
 
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_HINOTIFY
+static inline struct au_pin1 *au_pin_gp(struct au_pin *args)
+{
+	return args->pin + AuPin_GPARENT;
+}
+
+/* hinotify.c */
+void au_unpin_gp(struct au_pin *args);
+
+#else
+
+static inline struct au_pin1 *au_pin_gp(struct au_pin *args)
+{
+	return NULL;
+}
+
+static inline void au_unpin_gp(struct au_pin *args)
+{
+	/* empty */
+}
+#endif /* HINOTIFY */
+
+static inline void au_unpin(struct au_pin *args)
+{
+	au_do_unpin(args->pin + AuPin_PARENT, au_pin_gp(args));
+}
+
+static inline
+struct au_hinode *au_do_pinned_hdir(struct au_pin1 *pin, aufs_bindex_t bindex)
+{
+	if (pin && pin->parent)
+		return au_hi(pin->parent->d_inode, bindex);
+	return NULL;
+}
+
+struct dentry *au_do_pinned_h_parent(struct au_pin1 *pin, aufs_bindex_t bindex);
+
+static inline struct dentry *au_do_pinned_parent(struct au_pin1 *pin)
+{
+	if (pin)
+		return pin->parent;
+	return NULL;
+}
+
+static inline struct inode *au_do_pinned_h_dir(struct au_pin1 *pin)
+{
+	if (pin)
+		return pin->h_dir;
+	return NULL;
+}
+
+static inline
+void au_pin_do_set_dentry(struct au_pin1 *pin, struct dentry *dentry)
+{
+	if (pin)
+		pin->dentry = dentry;
+}
+
+static inline
+void au_pin_do_set_parent(struct au_pin1 *pin, struct dentry *parent)
+{
+	if (pin) {
+		dput(pin->parent);
+		pin->parent = dget(parent);
+	}
+}
+
+static inline void au_pin_do_set_h_dir(struct au_pin1 *pin, struct inode *h_dir)
+{
+	if (pin) {
+		iput(pin->h_dir);
+		pin->h_dir = au_igrab(h_dir);
+	}
+}
+
+static inline
+void au_pin_do_set_parent_lflag(struct au_pin1 *pin, unsigned char lflag)
+{
+	if (pin)
+		pin->di_locked = lflag;
+}
+
+static inline
+struct au_hinode *au_pinned_hdir(struct au_pin *args, aufs_bindex_t bindex)
+{
+	return au_do_pinned_hdir(args->pin + AuPin_PARENT, bindex);
+}
+
+static inline
+struct au_hinode *au_pinned_hgdir(struct au_pin *args, aufs_bindex_t bindex)
+{
+	return au_do_pinned_hdir(au_pin_gp(args), bindex);
+}
+
+static inline
+struct dentry *au_pinned_h_parent(struct au_pin *args, aufs_bindex_t bindex)
+{
+	return au_do_pinned_h_parent(args->pin + AuPin_PARENT, bindex);
+}
+
+#if 0 /* reserved for future use */
+static inline
+struct dentry *au_pinned_h_gparent(struct au_pin *args, aufs_bindex_t bindex)
+{
+	return au_do_pinned_h_parent(au_pin_gp(args), bindex);
+}
+#endif
+
+static inline
+struct dentry *au_pinned_parent(struct au_pin *args)
+{
+	return au_do_pinned_parent(args->pin + AuPin_PARENT);
+}
+
+static inline
+struct dentry *au_pinned_gparent(struct au_pin *args)
+{
+	return au_do_pinned_parent(au_pin_gp(args));
+}
+
+static inline
+struct inode *au_pinned_h_dir(struct au_pin *args)
+{
+	return au_do_pinned_h_dir(args->pin + AuPin_PARENT);
+}
+
+static inline
+struct inode *au_pinned_h_gdir(struct au_pin *args)
+{
+	return au_do_pinned_h_dir(au_pin_gp(args));
+}
+
+static inline void au_pin_set_parent(struct au_pin *args, struct dentry *d)
+{
+	au_pin_do_set_parent(args->pin + AuPin_PARENT, d);
+}
+
+static inline void au_pin_set_gparent(struct au_pin *args, struct dentry *d)
+{
+	au_pin_do_set_parent(au_pin_gp(args), d);
+}
+
+static inline void au_pin_set_h_dir(struct au_pin *args, struct inode *h_dir)
+{
+	au_pin_do_set_h_dir(args->pin + AuPin_PARENT, h_dir);
+}
+
+static inline void au_pin_set_h_gdir(struct au_pin *args, struct inode *h_dir)
+{
+	au_pin_do_set_h_dir(au_pin_gp(args), h_dir);
+}
+
+static inline
+void au_pin_set_parent_lflag(struct au_pin *args, unsigned char lflag)
+{
+	au_pin_do_set_parent_lflag(args->pin + AuPin_PARENT, lflag);
+}
+
+static inline
+void au_pin_set_gparent_lflag(struct au_pin *args, unsigned char lflag)
+{
+	au_pin_do_set_parent_lflag(au_pin_gp(args), lflag);
+}
+
 #endif /* __KERNEL__ */
 #endif /* __AUFS_INODE_H__ */
diff --git a/ubuntu/aufs/misc.c b/ubuntu/aufs/misc.c
index 8423b6e..564e5b5 100644
--- a/ubuntu/aufs/misc.c
+++ b/ubuntu/aufs/misc.c
@@ -17,7 +17,7 @@
  */
 
 /*
- * $Id: misc.c,v 1.8 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: misc.c,v 1.17 2008/09/08 02:40:48 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -94,7 +94,8 @@ void au_fake_dm_release(struct nameidata *fake_nd)
 }
 
 int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode,
-		int dlgt, struct nameidata *nd, struct vfsmount *nfsmnt)
+		struct vfsub_args *vargs, struct nameidata *nd,
+		struct vfsmount *nfsmnt)
 {
 	int err;
 
@@ -103,7 +104,7 @@ int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode,
 
 	err = -ENOSYS;
 	if (!nfsmnt)
-		err = vfsub_create(h_dir, h_dentry, mode, /*nd*/NULL, dlgt);
+		err = vfsub_create(h_dir, h_dentry, mode, /*nd*/NULL, vargs);
 	else {
 		struct nameidata fake_nd;
 
@@ -118,7 +119,7 @@ int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode,
 		fake_nd.intent.open.flags = O_CREAT | FMODE_READ;
 		fake_nd.intent.open.create_mode = mode;
 
-		err = vfsub_create(h_dir, h_dentry, mode, &fake_nd, dlgt);
+		err = vfsub_create(h_dir, h_dentry, mode, &fake_nd, vargs);
 		path_put(&fake_nd.path);
 	}
 
@@ -129,13 +130,13 @@ int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode,
 /* ---------------------------------------------------------------------- */
 
 int au_copy_file(struct file *dst, struct file *src, loff_t len,
-		 struct super_block *sb)
+		 struct au_hinode *hdir, struct super_block *sb,
+		 struct vfsub_args *vargs)
 {
-	int err, all_zero;
+	int err, all_zero, do_kfree;
 	unsigned long blksize;
-	char *buf;
-	struct vfsub_args vargs;
-	/* reduce stack space */
+	char *buf, *zp;
+	/* reduce stack usage */
 	struct iattr *ia;
 
 	LKTRTrace("%.*s, %.*s\n",
@@ -155,24 +156,40 @@ int au_copy_file(struct file *dst, struct file *src, loff_t len,
 	if (!blksize || PAGE_SIZE < blksize)
 		blksize = PAGE_SIZE;
 	LKTRTrace("blksize %lu\n", blksize);
-	buf = kmalloc(blksize, GFP_TEMPORARY);
-	if (unlikely(!buf))
-		goto out;
-	ia = kmalloc(sizeof(*ia), GFP_TEMPORARY);
-	if (unlikely(!ia))
-		goto out_buf;
+	/* todo: use ZERO_PAGE(0) */
+	BUILD_BUG_ON(KMALLOC_MAX_SIZE < 128 << 10);
+	do_kfree = 1;
+	if (blksize <= 64 << 10 && blksize * 2 >= sizeof(*ia)) {
+		buf = kmalloc(blksize * 2, GFP_NOFS);
+		if (unlikely(!buf))
+			goto out;
+		zp = buf + blksize;
+		memset(zp, 0, blksize);
+	} else {
+		BUILD_BUG_ON(PAGE_SIZE * 2 < sizeof(*ia));
+#if 0
+		buf = (void *)__get_free_pages(GFP_NOFS, 1);
+		zp = buf + PAGE_SIZE;
+#endif
+		do_kfree = 0;
+		buf = (void *)__get_free_page(GFP_NOFS);
+		if (unlikely(!buf))
+			goto out;
+		zp = (void *)get_zeroed_page(GFP_NOFS);
+		if (unlikely(!zp))
+			goto out_buf;
+	}
 
 #ifdef CONFIG_AUFS_DEBUG
 	if (len > (1 << 22))
 		AuWarn("copying a large file %lld\n", (long long)len);
 #endif
-	vfsub_args_init(&vargs, NULL, au_test_dlgt(au_mntflags(sb)), 0);
 	err = 0;
 	all_zero = 0;
 	src->f_pos = 0;
 	dst->f_pos = 0;
 	while (len) {
-		size_t sz, rbytes, wbytes, i;
+		size_t sz, rbytes, wbytes;
 		char *p;
 
 		LKTRTrace("len %lld\n", len);
@@ -185,7 +202,7 @@ int au_copy_file(struct file *dst, struct file *src, loff_t len,
 		/* todo: signal_pending? */
 		while (!rbytes || err == -EAGAIN || err == -EINTR) {
 			rbytes = vfsub_read_k(src, buf, sz, &src->f_pos,
-					      vfsub_ftest(vargs.flags, DLGT));
+					      vfsub_ftest(vargs->flags, DLGT));
 			err = rbytes;
 		}
 		if (unlikely(err < 0))
@@ -193,22 +210,20 @@ int au_copy_file(struct file *dst, struct file *src, loff_t len,
 
 		all_zero = 0;
 		if (len >= rbytes && rbytes == blksize) {
-			/* todo: try bitmap or memcmp()/get_zeroed_page()? */
-#if 0 /* todo: is there commonly re-usable zero page? */
-			static const char *zero_page[PAGE_SIZE];
-			all_zero = !memcmp(buf, zero_page, rbytes);
-#else
-			unsigned long *ulp;
-			size_t n;
+#if 1
+			all_zero = !memcmp(buf, zp, rbytes);
+#else /* reserved for future use */
+			unsigned long long *ullp;
+			size_t n, i;
 
 			all_zero = 1;
-			ulp = (void *)buf;
-			n = rbytes / sizeof(*ulp);
+			ullp = (void *)buf;
+			n = rbytes / sizeof(*ullp);
 			i = n;
 			while (n-- > 0 && all_zero)
-				all_zero = !*ulp++;
-			p = (void *)ulp;
-			i *= sizeof(*ulp);
+				all_zero = !*ullp++;
+			p = (void *)ullp;
+			i *= sizeof(*ullp);
 			for (; all_zero && i < rbytes; i++)
 				all_zero = !*p++;
 #endif
@@ -219,8 +234,10 @@ int au_copy_file(struct file *dst, struct file *src, loff_t len,
 			while (wbytes) {
 				size_t b;
 				/* support LSM and notify */
+				vfsub_args_reinit(vargs);
+				vfsub_ign_hinode(vargs, IN_MODIFY, hdir);
 				b = vfsub_write_k(dst, p, wbytes, &dst->f_pos,
-						  &vargs);
+						  vargs);
 				err = b;
 				/* todo: signal_pending? */
 				if (unlikely(err == -EAGAIN || err == -EINTR))
@@ -250,20 +267,31 @@ int au_copy_file(struct file *dst, struct file *src, loff_t len,
 		LKTRLabel(last hole);
 		do {
 			/* todo: signal_pending? */
-			err = vfsub_write_k(dst, "\0", 1, &dst->f_pos, &vargs);
+			vfsub_args_reinit(vargs);
+			vfsub_ign_hinode(vargs, IN_MODIFY, hdir);
+			err = vfsub_write_k(dst, "\0", 1, &dst->f_pos, vargs);
 		} while (err == -EAGAIN || err == -EINTR);
 		if (err == 1) {
+			ia = (void *)buf;
 			ia->ia_size = dst->f_pos;
-			ia->ia_valid = ATTR_SIZE;
+			ia->ia_valid = ATTR_SIZE | ATTR_FILE;
+			ia->ia_file = dst;
+			vfsub_args_reinit(vargs);
+			vfsub_ign_hinode(vargs, vfsub_events_notify_change(ia),
+					 hdir);
 			mutex_lock_nested(&h_i->i_mutex, AuLsc_I_CHILD2);
-			err = vfsub_fnotify_change(h_d, ia, &vargs, dst);
+			err = vfsub_notify_change(h_d, ia, vargs);
 			mutex_unlock(&h_i->i_mutex);
 		}
 	}
+	if (do_kfree)
+		kfree(buf);
+	else
+		free_page((unsigned long)zp);
 
-	kfree(ia);
  out_buf:
-	kfree(buf);
+	if (unlikely(!do_kfree))
+		free_page((unsigned long)buf);
  out:
 	AuTraceErr(err);
 	return err;
diff --git a/ubuntu/aufs/misc.h b/ubuntu/aufs/misc.h
index a8ca047..5751cbe 100644
--- a/ubuntu/aufs/misc.h
+++ b/ubuntu/aufs/misc.h
@@ -17,7 +17,7 @@
  */
 
 /*
- * $Id: misc.h,v 1.3 2008/05/26 04:04:26 sfjro Exp $
+ * $Id: misc.h,v 1.7 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #ifndef __AUFS_MISC_H__
@@ -27,7 +27,7 @@
 
 #include <linux/fs.h>
 #include <linux/namei.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 
 /* ---------------------------------------------------------------------- */
 
@@ -94,12 +94,14 @@ static inline void au_rw_read_unlock(struct au_rwsem *rw)
 {
 	AuDbgRcntDec(rw);
 	up_read(&rw->rwsem);
+	//au_dbg_sleep_jiffy(1 * HZ / HZ);
 }
 
 static inline void au_rw_dgrade_lock(struct au_rwsem *rw)
 {
 	AuDbgRcntInc(rw);
 	downgrade_write(&rw->rwsem);
+	//au_dbg_sleep_jiffy(1 * HZ / HZ);
 }
 
 static inline void au_rw_write_lock(struct au_rwsem *rw)
@@ -116,6 +118,7 @@ static inline void au_rw_write_lock_nested(struct au_rwsem *rw,
 static inline void au_rw_write_unlock(struct au_rwsem *rw)
 {
 	up_write(&rw->rwsem);
+	//au_dbg_sleep_jiffy(1 * HZ / HZ);
 }
 
 /* why is not _nested version defined */
@@ -184,18 +187,32 @@ static inline void prefix##_downgrade_lock(param) \
 
 void *au_kzrealloc(void *p, unsigned int nused, unsigned int new_sz, gfp_t gfp);
 
+struct au_nd_store {
+	unsigned int		flags;
+	struct path		path;
+	struct open_intent	intent;
+};
 struct au_sbinfo;
+void au_nd_store(struct au_nd_store *store, struct nameidata *nd,
+		 struct au_sbinfo *sbinfo);
+void au_nd_revert(struct au_nd_store *store, struct nameidata *nd,
+		  struct au_sbinfo *sbinfo);
+
 struct nameidata *au_dup_nd(struct au_sbinfo *sbinfo, struct nameidata *dst,
 			    struct nameidata *src);
 
 struct nameidata *au_fake_dm(struct nameidata *fake_nd, struct nameidata *nd,
 			     struct super_block *sb, aufs_bindex_t bindex);
 void au_fake_dm_release(struct nameidata *fake_nd);
+struct vfsub_args;
 int au_h_create(struct inode *h_dir, struct dentry *h_dentry, int mode,
-		int dlgt, struct nameidata *nd, struct vfsmount *nfsmnt);
+		struct vfsub_args *vargs, struct nameidata *nd,
+		struct vfsmount *nfsmnt);
 
+struct au_hinode;
 int au_copy_file(struct file *dst, struct file *src, loff_t len,
-		 struct super_block *sb);
+		 struct au_hinode *hdir, struct super_block *sb,
+		 struct vfsub_args *vargs);
 
 #endif /* __KERNEL__ */
 #endif /* __AUFS_MISC_H__ */
diff --git a/ubuntu/aufs/module.c b/ubuntu/aufs/module.c
index 107697b..c517d90 100644
--- a/ubuntu/aufs/module.c
+++ b/ubuntu/aufs/module.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * module global variables and operations
  *
- * $Id: module.c,v 1.6 2008/06/02 02:33:58 sfjro Exp $
+ * $Id: module.c,v 1.10 2008/08/25 01:50:37 sfjro Exp $
  */
 
 #include <linux/module.h>
@@ -89,6 +89,11 @@ static int __init aufs_init(void)
 	char *p;
 
 	au_debug_init();
+#ifdef CONFIG_AUFS_INO_T_64
+	BUILD_BUG_ON(sizeof(ino_t) != sizeof(long long));
+#else
+	BUILD_BUG_ON(sizeof(ino_t) != sizeof(int));
+#endif
 
 	p = au_esc_chars;
 	for (i = 1; i <= ' '; i++)
@@ -199,10 +204,6 @@ module_exit(aufs_exit);
 #endif
 #endif
 
-#ifdef CONFIG_AUFS_FAKE_DM
-#warning CONFIG_AUFS_FAKE_DM is obsoleted in linux-2.6.24 and later.
-#endif
-
 #ifdef CONFIG_AUFS_PUT_FILP_PATCH
 #if !defined(CONFIG_NFS_FS) && !defined(CONFIG_NFS_FS_MODULE)
 #warning CONFIG_AUFS_PUT_FILP_PATCH is unnecessary since CONFIG_NFS_FS is disabled.
diff --git a/ubuntu/aufs/module.h b/ubuntu/aufs/module.h
index d308c17..b2010ab 100644
--- a/ubuntu/aufs/module.h
+++ b/ubuntu/aufs/module.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * module initialization and module-global
  *
- * $Id: module.h,v 1.4 2008/06/02 02:38:50 sfjro Exp $
+ * $Id: module.h,v 1.8 2008/08/25 01:50:37 sfjro Exp $
  */
 
 #ifndef __AUFS_MODULE_H__
@@ -52,15 +52,14 @@ enum {
 extern struct kmem_cache *au_cachep[];
 
 #define AuCacheArgs(type, sz)	(type), (sz), 0, SLAB_RECLAIM_ACCOUNT, NULL
-#define AuCacheX(type, extra) \
-	kmem_cache_create(AuCacheArgs(#type, sizeof(struct type) + extra))
-#define AuCache(type)		AuCacheX(type, 0)
+#define AuCache(type) \
+	kmem_cache_create(AuCacheArgs(#type, sizeof(struct type)))
 
 /* ---------------------------------------------------------------------- */
 
 #define AuCacheFuncs(name, index) \
 static inline void *au_cache_alloc_##name(void) \
-{ return kmem_cache_alloc(au_cachep[index], GFP_KERNEL); } \
+{ return kmem_cache_alloc(au_cachep[index], GFP_NOFS); } \
 static inline void au_cache_free_##name(void *p) \
 { kmem_cache_free(au_cachep[index], p); }
 
diff --git a/ubuntu/aufs/opts.c b/ubuntu/aufs/opts.c
index 6982a49..da8e82a 100644
--- a/ubuntu/aufs/opts.c
+++ b/ubuntu/aufs/opts.c
@@ -19,7 +19,7 @@
 /*
  * mount options/flags
  *
- * $Id: opts.c,v 1.7 2008/06/02 02:39:03 sfjro Exp $
+ * $Id: opts.c,v 1.15 2008/09/01 02:55:31 sfjro Exp $
  */
 
 #include <linux/types.h> /* a distribution requires */
@@ -175,7 +175,7 @@ static match_table_t options = {
 
 /* ---------------------------------------------------------------------- */
 
-static const char *au_parser_pattern(int val, match_table_t token)
+static const char *au_parser_pattern(int val, struct match_token *token)
 {
 	while (token->pattern) {
 		if (token->token == val)
@@ -195,16 +195,16 @@ static const char *au_parser_pattern(int val, match_table_t token)
 #define NoLinkWH	"nolwh"
 
 static match_table_t brperms = {
-	{AuBr_RR, RR},
-	{AuBr_RO, RO},
-	{AuBr_RW, RW},
+	{AuBrPerm_RR, RR},
+	{AuBrPerm_RO, RO},
+	{AuBrPerm_RW, RW},
 
-	{AuBr_RRWH, RR "+" WH},
-	{AuBr_ROWH, RO "+" WH},
-	{AuBr_RWNoLinkWH, RW "+" NoLinkWH},
+	{AuBrPerm_RRWH, RR "+" WH},
+	{AuBrPerm_ROWH, RO "+" WH},
+	{AuBrPerm_RWNoLinkWH, RW "+" NoLinkWH},
 
-	{AuBr_ROWH, "nfsro"},
-	{AuBr_RO, NULL}
+	{AuBrPerm_ROWH, "nfsro"},
+	{AuBrPerm_RO, NULL}
 };
 
 static noinline_for_stack int br_perm_val(char *perm)
@@ -221,7 +221,7 @@ static noinline_for_stack int br_perm_val(char *perm)
 
 const char *au_optstr_br_perm(int brperm)
 {
-	return au_parser_pattern(brperm, brperms);
+	return au_parser_pattern(brperm, (void *)brperms);
 }
 
 /* ---------------------------------------------------------------------- */
@@ -243,7 +243,7 @@ static noinline_for_stack int udba_val(char *str)
 
 const char *au_optstr_udba(int udba)
 {
-	return au_parser_pattern(udba, udbalevel);
+	return au_parser_pattern(udba, (void *)udbalevel);
 }
 
 /* ---------------------------------------------------------------------- */
@@ -263,7 +263,7 @@ static noinline_for_stack int coo_val(char *str)
 
 const char *au_optstr_coo(int coo)
 {
-	return au_parser_pattern(coo, coolevel);
+	return au_parser_pattern(coo, (void *)coolevel);
 }
 
 /* ---------------------------------------------------------------------- */
@@ -293,7 +293,7 @@ static int au_match_ull(substring_t *s, unsigned long long *result, int base)
 	char *buf;
 	int ret;
 
-	buf = kmalloc(s->to - s->from + 1, GFP_KERNEL);
+	buf = kmalloc(s->to - s->from + 1, GFP_NOFS);
 	if (!buf)
 		return -ENOMEM;
 	memcpy(buf, s->from, s->to - s->from);
@@ -310,7 +310,7 @@ static int au_wbr_mfs_wmark(substring_t *arg, char *str,
 			    struct au_opt_wbr_create *create)
 {
 	int err;
-	u64 ull;
+	unsigned long long ull;
 
 	err = 0;
 	if (!au_match_ull(arg, &ull, 0))
@@ -341,8 +341,8 @@ static int au_wbr_mfs_sec(substring_t *arg, char *str,
 	return err;
 }
 
-static noinline_for_stack int
-au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
+static noinline_for_stack
+int au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
 {
 	int err, e;
 	substring_t args[MAX_OPT_ARGS];
@@ -381,7 +381,7 @@ au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
 
 const char *au_optstr_wbr_create(int wbr_create)
 {
-	return au_parser_pattern(wbr_create, au_wbr_create_policy);
+	return au_parser_pattern(wbr_create, (void *)au_wbr_create_policy);
 }
 
 static match_table_t au_wbr_copyup_policy = {
@@ -402,14 +402,14 @@ static noinline_for_stack int au_wbr_copyup_val(char *str)
 
 const char *au_optstr_wbr_copyup(int wbr_copyup)
 {
-	return au_parser_pattern(wbr_copyup, au_wbr_copyup_policy);
+	return au_parser_pattern(wbr_copyup, (void *)au_wbr_copyup_policy);
 }
 
 /* ---------------------------------------------------------------------- */
 
 static const int lkup_dirflags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
-static noinline_for_stack void dump_opts(struct au_opts *opts)
+static void dump_opts(struct au_opts *opts)
 {
 #ifdef CONFIG_AUFS_DEBUG
 	/* reduce stack space */
@@ -634,7 +634,7 @@ static int opt_add(struct au_opt *opt, char *opt_str, struct super_block *sb,
 	LKTRTrace("%s, b%d\n", opt_str, bindex);
 
 	add->bindex = bindex;
-	add->perm = AuBr_Last;
+	add->perm = AuBrPerm_Last;
 	add->path = opt_str;
 	p = strchr(opt_str, '=');
 	if (unlikely(p)) {
@@ -647,14 +647,14 @@ static int opt_add(struct au_opt *opt, char *opt_str, struct super_block *sb,
 	/* do not superio. */
 	err = vfsub_path_lookup(add->path, lkup_dirflags, &add->nd);
 	if (!err) {
-		if (!p /* && add->perm == AuBr_Last */) {
-			add->perm = AuBr_RO;
+		if (!p /* && add->perm == AuBrPerm_Last */) {
+			add->perm = AuBrPerm_RO;
 			if (au_test_def_rr(add->nd.path.dentry->d_sb))
-				add->perm = AuBr_RR;
+				add->perm = AuBrPerm_RR;
 			if (!bindex && !(sb->s_flags & MS_RDONLY))
-				add->perm = AuBr_RW;
+				add->perm = AuBrPerm_RW;
 #ifdef CONFIG_AUFS_COMPAT
-			add->perm = AuBr_RW;
+			add->perm = AuBrPerm_RW;
 #endif
 		}
 		opt->type = Opt_add;
@@ -672,13 +672,12 @@ static int opt_add(struct au_opt *opt, char *opt_str, struct super_block *sb,
 int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 		  struct au_opts *opts)
 {
-	int err, n, token, skipped;
+	int err, n, token;
 	struct dentry *root;
 	struct au_opt *opt, *opt_tail;
 	char *opt_str, *p;
-	substring_t args[MAX_OPT_ARGS];
 	aufs_bindex_t bindex, bend;
-	struct nameidata nd;
+	unsigned char skipped;
 	union {
 		struct au_opt_del *del;
 		struct au_opt_mod *mod;
@@ -688,9 +687,19 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 		struct au_opt_wbr_create *create;
 	} u;
 	struct file *file;
+	/* reduce the stack space */
+	struct {
+		substring_t args[MAX_OPT_ARGS];
+		struct nameidata nd;
+	} *a;
 
 	LKTRTrace("%s, nopts %d\n", str, opts->max_opt);
 
+	err = -ENOMEM;
+	a = kmalloc(sizeof(*a), GFP_NOFS);
+	if (unlikely(!a))
+		goto out;
+
 	root = sb->s_root;
 	err = 0;
 	bindex = 0;
@@ -699,15 +708,15 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 	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);
+		token = match_token(opt_str, options, a->args);
+		LKTRTrace("%s, token %d, a->args[0]{%p, %p}\n",
+			  opt_str, token, a->args[0].from, a->args[0].to);
 
 		skipped = 0;
 		switch (token) {
 		case Opt_br:
 			err = 0;
-			while (!err && (opt_str = strsep(&args[0].from, ":"))
+			while (!err && (opt_str = strsep(&a->args[0].from, ":"))
 			       && *opt_str) {
 				err = opt_add(opt, opt_str, sb, bindex++);
 				if (unlikely(!err && ++opt > opt_tail)) {
@@ -719,45 +728,46 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 			}
 			break;
 		case Opt_add:
-			if (unlikely(match_int(&args[0], &n))) {
+			if (unlikely(match_int(&a->args[0], &n))) {
 				AuErr("bad integer in %s\n", opt_str);
 				break;
 			}
 			bindex = n;
-			err = opt_add(opt, args[1].from, sb, bindex);
+			err = opt_add(opt, a->args[1].from, sb, bindex);
 			break;
 		case Opt_append:
-			err = opt_add(opt, args[0].from, sb, /*dummy bindex*/1);
+			err = opt_add(opt, a->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);
+			err = opt_add(opt, a->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;
+			u.del->path = a->args[0].from;
 			LKTRTrace("del path %s\n", u.del->path);
 			/* LSM may detect it */
 			/* do not superio. */
 			err = vfsub_path_lookup(u.del->path, lkup_dirflags,
-						&nd);
+						&a->nd);
 			if (unlikely(err)) {
 				AuErr("lookup failed %s (%d)\n",
 				      u.del->path, err);
 				break;
 			}
-			u.del->h_root = dget(nd.path.dentry);
-			path_put(&nd.path);
+			u.del->h_root = dget(a->nd.path.dentry);
+			path_put(&a->nd.path);
 			opt->type = token;
 			break;
 #if 0 /* reserved for future use */
 		case Opt_idel:
 			u.del = &opt->del;
 			u.del->path = "(indexed)";
-			if (unlikely(match_int(&args[0], &n))) {
+			if (unlikely(match_int(&a->args[0], &n))) {
 				AuErr("bad integer in %s\n", opt_str);
 				break;
 			}
@@ -776,7 +786,7 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 #endif
 		case Opt_mod:
 			u.mod = &opt->mod;
-			u.mod->path = args[0].from;
+			u.mod->path = a->args[0].from;
 			p = strchr(u.mod->path, '=');
 			if (unlikely(!p)) {
 				AuErr("no permssion %s\n", opt_str);
@@ -789,21 +799,21 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 			/* LSM may detect it */
 			/* do not superio. */
 			err = vfsub_path_lookup(u.mod->path, lkup_dirflags,
-						&nd);
+						&a->nd);
 			if (unlikely(err)) {
 				AuErr("lookup failed %s (%d)\n",
 				      u.mod->path, err);
 				break;
 			}
-			u.mod->h_root = dget(nd.path.dentry);
-			path_put(&nd.path);
+			u.mod->h_root = dget(a->nd.path.dentry);
+			path_put(&a->nd.path);
 			opt->type = token;
 			break;
 #ifdef IMOD /* reserved for future use */
 		case Opt_imod:
 			u.mod = &opt->mod;
 			u.mod->path = "(indexed)";
-			if (unlikely(match_int(&args[0], &n))) {
+			if (unlikely(match_int(&a->args[0], &n))) {
 				AuErr("bad integer in %s\n", opt_str);
 				break;
 			}
@@ -814,9 +824,9 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 				aufs_read_unlock(root, !AuLock_IR);
 				break;
 			}
-			u.mod->perm = br_perm_val(args[1].from);
+			u.mod->perm = br_perm_val(a->args[1].from);
 			LKTRTrace("mod path %s, perm 0x%x, %s\n",
-				  u.mod->path, u.mod->perm, args[1].from);
+				  u.mod->path, u.mod->perm, a->args[1].from);
 			err = 0;
 			u.mod->h_root = dget(au_h_dptr(root, bindex));
 			opt->type = token;
@@ -825,35 +835,34 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 #endif
 		case Opt_xino:
 			u.xino = &opt->xino;
-			file = au_xino_create(sb, args[0].from, /*silent*/0,
-					      /*parent*/NULL);
+			file = au_xino_create(sb, a->args[0].from, /*silent*/0);
 			err = PTR_ERR(file);
 			if (IS_ERR(file))
 				break;
 			err = -EINVAL;
 			if (unlikely(file->f_dentry->d_sb == sb)) {
 				fput(file);
-				AuErr("%s must be outside\n", args[0].from);
+				AuErr("%s must be outside\n", a->args[0].from);
 				break;
 			}
 			err = 0;
 			u.xino->file = file;
-			u.xino->path = args[0].from;
+			u.xino->path = a->args[0].from;
 			opt->type = token;
 			break;
 
 #if 0 /* def CONFIG_AUFS_EXPORT */ /* reserved for futur use */
 		case Opt_xinodir:
 			u.xinodir = &opt->xinodir;
-			u.xinodir->name = args[0].from;
+			u.xinodir->name = a->args[0].from;
 			err = vfsub_path_lookup(u.xinodir->name, lkup_dirflags,
-						&nd);
+						&a->nd);
 			if (unlikely(err)) {
 				AuErr("lookup failed %s (%d)\n",
 				      u.xinodir->name, err);
 				break;
 			}
-			u.xinodir->path = nd.path;
+			u.xinodir->path = a->nd.path;
 			/* do not path_put() */
 			opt->type = token;
 			break;
@@ -861,11 +870,11 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 
 		case Opt_trunc_xino_path:
 			u.xino_itrunc = &opt->xino_itrunc;
-			p = args[0].from;
+			p = a->args[0].from;
 			LKTRTrace("trunc_xino path %s\n", p);
 			/* LSM may detect it */
 			/* do not superio. */
-			err = vfsub_path_lookup(p, lkup_dirflags, &nd);
+			err = vfsub_path_lookup(p, lkup_dirflags, &a->nd);
 			if (unlikely(err)) {
 				AuErr("lookup failed %s (%d)\n", p , err);
 				break;
@@ -874,13 +883,14 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 			aufs_read_lock(root, AuLock_FLUSH);
 			bend = au_sbend(sb);
 			for (bindex = 0; bindex <= bend; bindex++) {
-				if (au_h_dptr(root, bindex) == nd.path.dentry) {
+				if (au_h_dptr(root, bindex)
+				    == a->nd.path.dentry) {
 					u.xino_itrunc->bindex = bindex;
 					break;
 				}
 			}
 			aufs_read_unlock(root, !AuLock_IR);
-			path_put(&nd.path);
+			path_put(&a->nd.path);
 			if (unlikely(u.xino_itrunc->bindex < 0)) {
 				AuErr("no such branch %s\n", p);
 				err = -EINVAL;
@@ -891,7 +901,7 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 
 		case Opt_itrunc_xino:
 			u.xino_itrunc = &opt->xino_itrunc;
-			if (unlikely(match_int(&args[0], &n))) {
+			if (unlikely(match_int(&a->args[0], &n))) {
 				AuErr("bad integer in %s\n", opt_str);
 				break;
 			}
@@ -908,14 +918,14 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 			break;
 
 		case Opt_dirwh:
-			if (unlikely(match_int(&args[0], &opt->dirwh)))
+			if (unlikely(match_int(&a->args[0], &opt->dirwh)))
 				break;
 			err = 0;
 			opt->type = token;
 			break;
 
 		case Opt_rdcache:
-			if (unlikely(match_int(&args[0], &opt->rdcache)))
+			if (unlikely(match_int(&a->args[0], &opt->rdcache)))
 				break;
 			err = 0;
 			opt->type = token;
@@ -956,7 +966,7 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 			break;
 
 		case Opt_udba:
-			opt->udba = udba_val(args[0].from);
+			opt->udba = udba_val(a->args[0].from);
 			if (opt->udba >= 0) {
 				err = 0;
 				opt->type = token;
@@ -967,7 +977,7 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 		case Opt_wbr_create:
 			u.create = &opt->wbr_create;
 			u.create->wbr_create
-				= au_wbr_create_val(args[0].from, u.create);
+				= au_wbr_create_val(a->args[0].from, u.create);
 			if (u.create->wbr_create >= 0) {
 				err = 0;
 				opt->type = token;
@@ -975,7 +985,7 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 				AuErr("wrong value, %s\n", opt_str);
 			break;
 		case Opt_wbr_copyup:
-			opt->wbr_copyup = au_wbr_copyup_val(args[0].from);
+			opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from);
 			if (opt->wbr_copyup >= 0) {
 				err = 0;
 				opt->type = token;
@@ -984,7 +994,7 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 			break;
 
 		case Opt_coo:
-			opt->coo = coo_val(args[0].from);
+			opt->coo = coo_val(a->args[0].from);
 			if (opt->coo >= 0) {
 				err = 0;
 				opt->type = token;
@@ -1016,9 +1026,12 @@ int au_opts_parse(struct super_block *sb, unsigned long flags, char *str,
 		}
 	}
 
+	kfree(a);
 	dump_opts(opts);
 	if (unlikely(err))
 		au_opts_free(opts);
+
+ out:
 	AuTraceErr(err);
 	return err;
 }
@@ -1286,15 +1299,16 @@ static int au_opt_xino(struct super_block *sb, struct au_opt *opt,
 	return err;
 }
 
-static noinline_for_stack int
-verify_opts(struct super_block *sb, unsigned int pending, int remount)
+static int verify_opts(struct super_block *sb, unsigned int pending,
+		       int remount)
 {
 	int err;
 	aufs_bindex_t bindex, bend;
+	unsigned char do_plink, skip, do_free;
 	struct au_branch *br;
+	struct au_wbr *wbr;
 	struct dentry *root;
-	struct inode *dir;
-	unsigned int do_plink;
+	struct inode *dir, *h_dir;
 	unsigned int mnt_flags;
 
 	AuTraceEnter();
@@ -1319,55 +1333,69 @@ verify_opts(struct super_block *sb, unsigned int pending, int remount)
 	do_plink = !!au_opt_test(mnt_flags, PLINK);
 	bend = au_sbend(sb);
 	for (bindex = 0; !err && bindex <= bend; bindex++) {
-		struct inode *h_dir;
-		int skip;
-
 		skip = 0;
 		h_dir = au_h_iptr(dir, bindex);
 		br = au_sbr(sb, bindex);
-		br_wh_read_lock(br);
+		do_free = 0;
+		wbr = br->br_wbr;
+		if (wbr)
+			wbr_wh_read_lock(wbr);
 		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) {
+		case AuBrPerm_RR:
+		case AuBrPerm_RO:
+		case AuBrPerm_RRWH:
+		case AuBrPerm_ROWH:
+			do_free = !!wbr;
+			skip = (!wbr
+				|| (!wbr->wbr_whbase
+				    && !wbr->wbr_plink
+				    && !wbr->wbr_tmp));
+			break;
+
+		case AuBrPerm_RWNoLinkWH:
+			/* skip = (!br->br_whbase && !br->br_tmp); */
+			skip = (!wbr || !wbr->wbr_whbase);
+			if (skip && wbr) {
 				if (do_plink)
-					skip = !!br->br_plink;
+					skip = !!wbr->wbr_plink;
 				else
-					skip = !br->br_plink;
+					skip = !wbr->wbr_plink;
 			}
 			break;
 
-		case AuBr_RW:
-			skip = !!br->br_wh;
+		case AuBrPerm_RW:
+			/* skip = (br->br_whbase && br->br_tmp); */
+			skip = (wbr && wbr->wbr_whbase);
 			if (skip) {
 				if (do_plink)
-					skip = !!br->br_plink;
+					skip = !!wbr->wbr_plink;
 				else
-					skip = !br->br_plink;
+					skip = !wbr->wbr_plink;
 			}
 			break;
 
 		default:
 			BUG();
 		}
-		br_wh_read_unlock(br);
+		if (wbr)
+			wbr_wh_read_unlock(wbr);
 
 		if (skip)
 			continue;
 
-		au_hdir_lock(h_dir, dir, bindex);
-		br_wh_write_lock(br);
+		mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
+		if (wbr)
+			wbr_wh_write_lock(wbr);
 		err = au_wh_init(au_h_dptr(root, bindex), br,
-				 au_nfsmnt(sb, bindex), sb);
-		br_wh_write_unlock(br);
-		au_hdir_unlock(h_dir, dir, bindex);
+				 au_nfsmnt(sb, bindex), sb, bindex);
+		if (wbr)
+			wbr_wh_write_unlock(wbr);
+		mutex_unlock(&h_dir->i_mutex);
+
+		if (!err && do_free) {
+			kfree(wbr);
+			br->br_wbr = NULL;
+		}
 	}
 
 	AuTraceErr(err);
@@ -1379,11 +1407,12 @@ int au_opts_mount(struct super_block *sb, struct au_opts *opts)
 	int err;
 	struct inode *dir;
 	struct au_opt *opt;
-	struct au_opt_xino *opt_xino;
+	struct au_opt_xino *opt_xino, xino;
 	struct au_opt_xinodir *opt_xinodir;
 	aufs_bindex_t bend;
 	struct au_sbinfo *sbinfo;
 	unsigned int tmp;
+	struct au_branch *br;
 
 	AuTraceEnter();
 	SiMustWriteLock(sb);
@@ -1442,17 +1471,17 @@ int au_opts_mount(struct super_block *sb, struct au_opts *opts)
 
 	/* enable xino */
 	if (au_opt_test(tmp, XINO) && !opt_xino) {
-		struct au_opt_xino xino;
-
 		xino.file = au_xino_def(sb);
 		err = PTR_ERR(xino.file);
 		if (IS_ERR(xino.file))
 			goto out;
 
+		br = au_xino_def_br(sbinfo);
 		err = au_xino_set(sb, &xino, /*remount*/0);
 		fput(xino.file);
 		if (unlikely(err))
 			goto out;
+		au_xino_def_br_set(br, sbinfo);
 	}
 
 	/* restore hinotify */
@@ -1477,7 +1506,7 @@ int au_opts_remount(struct super_block *sb, struct au_opts *opts)
 	struct au_opt_xino *opt_xino;
 	struct au_opt_xinodir *opt_xinodir;
 	struct au_opt *opt;
-	unsigned int dlgt;
+	unsigned char dlgt;
 	struct au_sbinfo *sbinfo;
 
 	AuTraceEnter();
diff --git a/ubuntu/aufs/opts.h b/ubuntu/aufs/opts.h
index db57650..5dedbd1 100644
--- a/ubuntu/aufs/opts.h
+++ b/ubuntu/aufs/opts.h
@@ -19,7 +19,7 @@
 /*
  * mount options/flags
  *
- * $Id: opts.h,v 1.4 2008/06/02 02:39:11 sfjro Exp $
+ * $Id: opts.h,v 1.6 2008/08/17 23:03:27 sfjro Exp $
  */
 
 #ifndef __AUFS_OPTS_H__
@@ -29,7 +29,7 @@
 
 #include <linux/fs.h>
 #include <linux/namei.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "wkq.h"
 
 /* ---------------------------------------------------------------------- */
@@ -150,14 +150,14 @@ struct au_opt_xino_itrunc {
 };
 
 struct au_opt_xino_trunc_v {
-	u64		upper;
-	int		step;
+	unsigned long long	upper;
+	int			step;
 };
 
 struct au_opt_wbr_create {
-	int wbr_create;
-	int mfs_second;
-	u64 mfsrr_watermark;
+	int			wbr_create;
+	int			mfs_second;
+	unsigned long long	mfsrr_watermark;
 };
 
 struct au_opt {
@@ -228,12 +228,12 @@ static inline int au_opt_test_xino(unsigned int flags)
 #define au_opt_set_coo(flags, name) do { \
 	(flags) &= ~AuOptMask_COO; \
 	((flags) |= AuOpt_##name); \
-} while(0)
+} while (0)
 
 #define au_opt_set_udba(flags, name) do { \
 	(flags) &= ~AuOptMask_UDBA; \
 	((flags) |= AuOpt_##name); \
-} while(0)
+} while (0)
 
 #define au_opt_clr(flags, name)		{ ((flags) &= ~AuOpt_##name); }
 
diff --git a/ubuntu/aufs/plink.c b/ubuntu/aufs/plink.c
index 3bf4239..b37df8b 100644
--- a/ubuntu/aufs/plink.c
+++ b/ubuntu/aufs/plink.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * pseudo-link
  *
- * $Id: plink.c,v 1.4 2008/06/02 02:38:21 sfjro Exp $
+ * $Id: plink.c,v 1.9 2008/09/01 02:55:35 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -96,6 +96,7 @@ struct dentry *au_plink_lkup(struct super_block *sb, aufs_bindex_t bindex,
 {
 	struct dentry *h_dentry, *h_parent;
 	struct au_branch *br;
+	struct au_wbr *wbr;
 	struct inode *h_dir;
 	char tgtname[PLINK_NAME_LEN];
 	int len;
@@ -107,7 +108,9 @@ struct dentry *au_plink_lkup(struct super_block *sb, aufs_bindex_t bindex,
 
 	LKTRTrace("b%d, i%lu\n", bindex, inode->i_ino);
 	br = au_sbr(sb, bindex);
-	h_parent = br->br_plink;
+	wbr = br->br_wbr;
+	AuDebugOn(!wbr);
+	h_parent = wbr->wbr_plink;
 	AuDebugOn(!h_parent);
 	h_dir = h_parent->d_inode;
 	AuDebugOn(!h_dir);
@@ -137,6 +140,8 @@ static int do_whplink(char *tgt, int len, struct dentry *h_parent,
 		/* .br	= NULL */
 	};
 
+	AuTraceEnter();
+
 	dlgt = !!au_test_dlgt(au_mntflags(sb));
 	if (unlikely(dlgt))
 		au_fset_ndx(ndx.flags, DLGT);
@@ -147,11 +152,12 @@ static int do_whplink(char *tgt, int len, struct dentry *h_parent,
 
 	err = 0;
 	vfsub_args_init(&vargs, NULL, dlgt, 0);
+	/* wh.plink dir is not monitored */
 	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, &vargs);
 	if (!err && !h_tgt->d_inode) {
-		err = vfsub_link(h_dentry, h_dir, h_tgt, dlgt);
+		err = vfsub_link(h_dentry, h_dir, h_tgt, &vargs);
 		/* todo: unnecessary? */
 		/* inc_nlink(inode); */
 	}
@@ -184,13 +190,16 @@ static int whplink(struct dentry *h_dentry, struct inode *inode,
 {
 	int err, len, wkq_err;
 	struct au_branch *br;
+	struct au_wbr *wbr;
 	struct dentry *h_parent;
 	struct inode *h_dir;
 	char tgtname[PLINK_NAME_LEN];
 
 	LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
 	br = au_sbr(inode->i_sb, bindex);
-	h_parent = br->br_plink;
+	wbr = br->br_wbr;
+	AuDebugOn(!wbr);
+	h_parent = wbr->wbr_plink;
 	AuDebugOn(!h_parent);
 	h_dir = h_parent->d_inode;
 	AuDebugOn(!h_dir);
@@ -250,7 +259,7 @@ void au_plink_append(struct super_block *sb, struct inode *inode,
 	if (!found) {
 		plink = kmalloc(sizeof(*plink), GFP_ATOMIC);
 		if (plink) {
-			plink->inode = igrab(inode);
+			plink->inode = au_igrab(inode);
 			list_add(&plink->list, plink_list);
 			cnt++;
 		} else
@@ -258,6 +267,11 @@ void au_plink_append(struct super_block *sb, struct inode *inode,
 	}
 	spin_unlock(&sbinfo->si_plink_lock);
 
+#if 0 /* todo: test here */
+	if (found)
+		return; /* success */
+#endif
+
 	if (!err)
 		err = whplink(h_dentry, inode, bindex, sb);
 
@@ -314,7 +328,7 @@ void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id)
 	/* spin_lock(&sbinfo->si_plink_lock); */
 	list_for_each_entry_safe(plink, tmp, plink_list, list) {
 		do_put = 0;
-		inode = igrab(plink->inode);
+		inode = au_igrab(plink->inode);
 		ii_write_lock_child(inode);
 		bstart = au_ibstart(inode);
 		bend = au_ibend(inode);
diff --git a/ubuntu/aufs/robr.c b/ubuntu/aufs/robr.c
new file mode 100644
index 0000000..0d5d4d5
--- /dev/null
+++ b/ubuntu/aufs/robr.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * 'robr', aufs as readonly branch of another aufs
+ *
+ * $Id: robr.c,v 1.6 2008/07/21 02:53:51 sfjro Exp $
+ */
+
+#include "aufs.h"
+
+/* ---------------------------------------------------------------------- */
+
+int au_test_robr_wh(struct qstr *name, struct dentry *h_parent,
+		    struct qstr *wh_name, int try_sio, struct au_ndx *ndx)
+{
+	if (strncmp(name->name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))
+		return au_wh_test(h_parent, wh_name, try_sio, ndx);
+	return -EPERM;
+}
+
+int au_test_robr_shwh(struct super_block *sb, const struct qstr *name)
+{
+	return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct au_robr_lvma {
+	struct list_head list;
+	struct vm_area_struct *vma;
+};
+
+struct file *au_robr_safe_file(struct vm_area_struct *vma)
+{
+	struct file *file = vma->vm_file;
+	struct super_block *sb = file->f_dentry->d_sb;
+	struct au_robr_lvma *lvma, *entry;
+	struct au_sbinfo *sbinfo;
+	unsigned char found, warn;
+
+	AuTraceEnter();
+	AuDebugOn(!au_test_aufs(sb));
+
+	warn = 0;
+	found = 0;
+	sbinfo = au_sbi(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))
+		AuWarn1("no memory for lvma\n");
+	return file;
+}
+
+void au_robr_reset_file(struct vm_area_struct *vma, struct file *file)
+{
+	struct super_block *sb = file->f_dentry->d_sb;
+	struct au_robr_lvma *entry, *found;
+	struct au_sbinfo *sbinfo;
+
+	AuTraceEnter();
+	AuDebugOn(!au_test_aufs(sb));
+
+	vma->vm_file = file;
+	/* smp_mb(); */ /* flush vm_file */
+
+	found = NULL;
+	sbinfo = au_sbi(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);
+}
diff --git a/ubuntu/aufs/sbinfo.c b/ubuntu/aufs/sbinfo.c
index 6a54005..a72eb04 100644
--- a/ubuntu/aufs/sbinfo.c
+++ b/ubuntu/aufs/sbinfo.c
@@ -19,7 +19,7 @@
 /*
  * superblock private data
  *
- * $Id: sbinfo.c,v 1.8 2008/06/02 02:39:58 sfjro Exp $
+ * $Id: sbinfo.c,v 1.11 2008/07/27 22:49:36 sfjro Exp $
  */
 
 #include <linux/smp_lock.h>
@@ -57,10 +57,10 @@ int au_si_alloc(struct super_block *sb)
 	AuTraceEnter();
 
 	err = -ENOMEM;
-	sbinfo = kmalloc(sizeof(*sbinfo), GFP_KERNEL);
+	sbinfo = kmalloc(sizeof(*sbinfo), GFP_NOFS);
 	if (unlikely(!sbinfo))
 		goto out;
-	sbinfo->si_branch = kzalloc(sizeof(*sbinfo->si_branch), GFP_KERNEL);
+	sbinfo->si_branch = kzalloc(sizeof(*sbinfo->si_branch), GFP_NOFS);
 	if (unlikely(!sbinfo->si_branch))
 		goto out_sbinfo;
 
@@ -87,6 +87,7 @@ int au_si_alloc(struct super_block *sb)
 	sbinfo->si_xib = NULL;
 	mutex_init(&sbinfo->si_xib_mtx);
 	sbinfo->si_xib_buf = NULL;
+	au_xino_def_br_set(NULL, sbinfo);
 	/* leave si_xib_last_pindex and si_xib_next_bit */
 
 	au_nwt_init(&sbinfo->si_nowait);
diff --git a/ubuntu/aufs/super.c b/ubuntu/aufs/super.c
index 2a12367..dcfec6a 100644
--- a/ubuntu/aufs/super.c
+++ b/ubuntu/aufs/super.c
@@ -19,7 +19,7 @@
 /*
  * mount and super_block operations
  *
- * $Id: super.c,v 1.8 2008/06/02 02:40:17 sfjro Exp $
+ * $Id: super.c,v 1.16 2008/09/15 03:14:49 sfjro Exp $
  */
 
 #include <linux/module.h>
@@ -51,7 +51,24 @@ static struct inode *aufs_alloc_inode(struct super_block *sb)
 
 static void aufs_destroy_inode(struct inode *inode)
 {
+	int err;
+
 	LKTRTrace("i%lu\n", inode->i_ino);
+
+	if (!inode->i_nlink) {
+		struct super_block *sb = inode->i_sb;
+		int locked;
+
+		/* in nowait task, sbi is write-locked */
+		/* todo: test kernel thread */
+		locked = si_noflush_read_trylock(sb);
+		err = au_xigen_inc(inode);
+		if (unlikely(err))
+			AuWarn1("failed resetting i_generation, %d\n", err);
+		if (locked)
+			si_read_unlock(sb);
+	}
+
 	au_iinfo_fin(inode);
 	au_cache_free_icntnr(container_of(inode, struct aufs_icntnr,
 					  vfs_inode));
@@ -62,7 +79,7 @@ struct inode *au_iget_locked(struct super_block *sb, ino_t ino)
 	struct inode *inode;
 	int err;
 
-	LKTRTrace("i%lu\n", ino);
+	LKTRTrace("i%lu\n", (unsigned long)ino);
 
 	inode = iget_locked(sb, ino);
 	if (unlikely(!inode)) {
@@ -73,13 +90,12 @@ struct inode *au_iget_locked(struct super_block *sb, ino_t ino)
 	if (unlikely(!(inode->i_state & I_NEW)))
 		goto out;
 
-	err = au_iinfo_init(inode);
-	if (!err) {
+	err = au_xigen_new(inode);
+	if (!err)
+		err = au_iinfo_init(inode);
+	if (!err)
 		inode->i_version++;
-		inode->i_op = &aufs_iop;
-		inode->i_fop = &aufs_file_fop;
-		inode->i_mapping->a_ops = &aufs_aop;
-	} else {
+	else {
 		iget_failed(inode);
 		inode = ERR_PTR(err);
 	}
@@ -133,7 +149,7 @@ static void au_show_wbr_create(struct seq_file *m, int v,
 	case AuWbrCreate_RR:
 	case AuWbrCreate_MFS:
 	case AuWbrCreate_PMFS:
-		seq_printf(m, "%s", pat);
+		seq_printf(m, pat);
 		break;
 	case AuWbrCreate_MFSV:
 		seq_printf(m, /*pat*/"mfs:%lu",
@@ -262,6 +278,7 @@ static int aufs_show_options(struct seq_file *m, struct vfsmount *mnt)
 #undef AuStr_BrOpt
 }
 
+/* todo: in case of round-robin policy, return the sum of all rw branches? */
 static int aufs_statfs(struct dentry *dentry, struct kstatfs *buf)
 {
 	int err;
@@ -296,7 +313,7 @@ static void au_fsync_br(struct super_block *sb)
 	bend = au_sbend(sb);
 	for (bindex = 0; bindex < bend; bindex++) {
 		brperm = au_sbr_perm(sb, bindex);
-		if (brperm == AuBr_RR || brperm == AuBr_RRWH)
+		if (brperm == AuBrPerm_RR || brperm == AuBrPerm_RRWH)
 			continue;
 		h_sb = au_sbr_sb(sb, bindex);
 		if (bdev_read_only(h_sb->s_bdev))
@@ -313,8 +330,17 @@ static void au_fsync_br(struct super_block *sb)
 #endif
 }
 
-static void aufs_umount_begin(struct super_block *sb)
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
+/* this IS NOT for super_operations */
+static void aufs_umount_begin(struct super_block *arg)
+#define AuUmountBeginSb(arg)	(arg)
+#else
+/* this IS for super_operations */
+static void aufs_umount_begin(struct vfsmount *arg, int flags)
+#define AuUmountBeginSb(arg)	(arg)->mnt_sb
+#endif
 {
+	struct super_block *sb = AuUmountBeginSb(arg);
 	struct au_sbinfo *sbinfo;
 
 	AuTraceEnter();
@@ -348,9 +374,9 @@ static void aufs_put_super(struct super_block *sb)
 	sbinfo = au_sbi(sb);
 	if (unlikely(!sbinfo))
 		return;
-
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
 	aufs_umount_begin(sb);
-
+#endif
 	kobject_put(&sbinfo->si_kobj);
 }
 
@@ -399,11 +425,11 @@ static int test_dir(struct dentry *dentry, void *arg)
 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;
+	const unsigned int flags = au_hi_flags(root->d_inode, /*isdir*/1);
 
 	LKTRTrace("sgen %d\n", sgen);
 	SiMustWriteLock(root->d_sb);
@@ -424,7 +450,7 @@ static int refresh_dir(struct dentry *root, au_gen_t sgen)
 			}
 		}
 
-	e = au_dpages_init(&dpages, GFP_TEMPORARY);
+	e = au_dpages_init(&dpages, GFP_NOFS);
 	if (unlikely(e)) {
 		if (!err)
 			err = e;
@@ -505,7 +531,7 @@ static int refresh_nondir(struct dentry *root, au_gen_t sgen, int do_dentry)
 	if (!do_dentry)
 		goto out;
 
-	e = au_dpages_init(&dpages, GFP_TEMPORARY);
+	e = au_dpages_init(&dpages, GFP_NOFS);
 	if (unlikely(e)) {
 		if (!err)
 			err = e;
@@ -569,25 +595,27 @@ static int cvt_err(int err)
 /* protected by s_umount */
 static int aufs_remount_fs(struct super_block *sb, int *flags, char *data)
 {
-	int err;
+	int err, rerr;
+	au_gen_t sigen;
 	struct dentry *root;
 	struct inode *inode;
 	struct au_opts opts;
-	unsigned int dlgt;
 	struct au_sbinfo *sbinfo;
+	unsigned char dlgt;
 
 	LKTRTrace("flags 0x%x, data %s, len %lu\n",
 		  *flags, data ? data : "NULL",
 		  (unsigned long)(data ? strlen(data) : 0));
 
 	au_fsync_br(sb);
+
 	err = 0;
 	if (!data || !*data)
 		goto out; /* success */
 
 	err = -ENOMEM;
 	memset(&opts, 0, sizeof(opts));
-	opts.opt = (void *)__get_free_page(GFP_TEMPORARY);
+	opts.opt = (void *)__get_free_page(GFP_NOFS);
 	if (unlikely(!opts.opt))
 		goto out;
 	opts.max_opt = PAGE_SIZE / sizeof(*opts.opt);
@@ -610,9 +638,6 @@ static int aufs_remount_fs(struct super_block *sb, int *flags, char *data)
 
 	if (au_ftest_opts(opts.flags, REFRESH_DIR)
 	    || au_ftest_opts(opts.flags, REFRESH_NONDIR)) {
-		int rerr;
-		au_gen_t sigen;
-
 		dlgt = !!au_opt_test(sbinfo->si_mntflags, DLGT);
 		au_opt_clr(sbinfo->si_mntflags, DLGT);
 		au_sigen_inc(sb);
@@ -667,7 +692,9 @@ static struct super_operations aufs_sop = {
 
 	.put_super	= aufs_put_super,
 	.remount_fs	= aufs_remount_fs,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
 	.umount_begin	= aufs_umount_begin
+#endif
 };
 
 /* ---------------------------------------------------------------------- */
@@ -685,8 +712,10 @@ static int alloc_root(struct super_block *sb)
 	err = PTR_ERR(inode);
 	if (IS_ERR(inode))
 		goto out;
-	unlock_new_inode(inode);
+	inode->i_op = &aufs_dir_iop;
+	inode->i_fop = &aufs_dir_fop;
 	inode->i_mode = S_IFDIR;
+	unlock_new_inode(inode);
 	root = d_alloc_root(inode);
 	if (unlikely(!root))
 		goto out_iput;
@@ -728,7 +757,7 @@ static int aufs_fill_super(struct super_block *sb, void *raw_data, int silent)
 
 	err = -ENOMEM;
 	memset(&opts, 0, sizeof(opts));
-	opts.opt = (void *)__get_free_page(GFP_TEMPORARY);
+	opts.opt = (void *)__get_free_page(GFP_NOFS);
 	if (unlikely(!opts.opt))
 		goto out;
 	opts.max_opt = PAGE_SIZE / sizeof(*opts.opt);
@@ -741,7 +770,7 @@ static int aufs_fill_super(struct super_block *sb, void *raw_data, int silent)
 	sb->s_flags |= MS_NOATIME | MS_NODIRATIME;
 	sb->s_op = &aufs_sop;
 	sb->s_magic = AUFS_SUPER_MAGIC;
-	au_init_export_op(sb);
+	au_export_init(sb);
 
 	err = alloc_root(sb);
 	if (unlikely(err)) {
@@ -807,15 +836,14 @@ static int aufs_get_sb(struct file_system_type *fs_type, int flags,
 		       struct vfsmount *mnt)
 {
 	int err;
+	struct super_block *sb;
 
 	/* 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 super_block *sb = mnt->mnt_sb;
-		struct au_sbinfo *sbinfo = au_sbi(sb);
-
-		au_mnt_init(sbinfo, mnt);
+		sb = mnt->mnt_sb;
+		au_mnt_init(au_sbi(sb), mnt);
 		si_write_lock(sb);
 		sysaufs_brs_add(sb, 0);
 		si_write_unlock(sb);
@@ -825,7 +853,9 @@ static int aufs_get_sb(struct file_system_type *fs_type, int flags,
 
 struct file_system_type aufs_fs_type = {
 	.name		= AUFS_FSTYPE,
-	.fs_flags	= FS_REVAL_DOT, /* for UDBA and NFS branch */
+	.fs_flags	=
+		FS_RENAME_DOES_D_MOVE	/* a race between rename and others*/
+		| FS_REVAL_DOT,		/* for NFS branch */
 	.get_sb		= aufs_get_sb,
 	.kill_sb	= generic_shutdown_super,
 	/* no need to __module_get() and module_put(). */
diff --git a/ubuntu/aufs/super.h b/ubuntu/aufs/super.h
index ca86c4a..73954bf 100644
--- a/ubuntu/aufs/super.h
+++ b/ubuntu/aufs/super.h
@@ -19,7 +19,7 @@
 /*
  * super_block operations
  *
- * $Id: super.h,v 1.8 2008/06/02 02:39:58 sfjro Exp $
+ * $Id: super.h,v 1.14 2008/09/15 03:14:52 sfjro Exp $
  */
 
 #ifndef __AUFS_SUPER_H__
@@ -31,7 +31,7 @@
 #include <linux/cramfs_fs.h>
 #include <linux/kobject.h>
 #include <linux/magic.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "misc.h"
 #include "wkq.h"
 
@@ -55,8 +55,8 @@ struct au_wbr_mfs {
 	unsigned long	mfs_expire;
 	aufs_bindex_t	mfs_bindex;
 
-	u64		mfsrr_bytes;
-	u64		mfsrr_watermark;
+	unsigned long long	mfsrr_bytes;
+	unsigned long long	mfsrr_watermark;
 };
 
 /* sbinfo status flags */
@@ -116,6 +116,18 @@ struct au_sbinfo {
 	/* reserved for future use */
 	/* unsigned long long	si_xib_limit; */	/* Max xib file size */
 
+#ifdef CONFIG_AUFS_HINOTIFY
+	struct au_branch	*si_xino_def_br;
+#endif
+
+#ifdef CONFIG_AUFS_EXPORT
+	/* i_generation */
+	struct file		*si_xigen;
+	/* todo: atomic_t? */
+	spinlock_t		si_xigen_lock;
+	__u32			si_xigen_next;
+#endif
+
 	/* readdir cache time, max, in HZ */
 	unsigned long		si_rdcache;
 
@@ -137,8 +149,7 @@ struct au_sbinfo {
 	spinlock_t		si_plink_lock;
 	struct list_head	si_plink;
 
-#if defined(CONFIG_AUFS_EXPORT) \
-	&& (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) || !defined(MmTree))
+#if defined(CONFIG_AUFS_EXPORT) && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
 	/* dirty, for export, async ops, and sysfs */
 	spinlock_t		si_mntcache_lock;
 	struct vfsmount		*si_mntcache;	/* no get/put */
@@ -208,13 +219,11 @@ aufs_bindex_t au_new_br_id(struct super_block *sb);
 /* wbr_policy.c */
 extern struct au_wbr_copyup_operations au_wbr_copyup_ops[];
 extern struct au_wbr_create_operations au_wbr_create_ops[];
-int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst,
-		   struct dentry *locked);
+int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst);
 
 /* ---------------------------------------------------------------------- */
 
-#if defined(CONFIG_AUFS_EXPORT) \
-	&& (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) || !defined(MmTree))
+#if defined(CONFIG_AUFS_EXPORT) && LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
 static inline void au_mnt_init(struct au_sbinfo *sbinfo, struct vfsmount *mnt)
 {
 	spin_lock_init(&sbinfo->si_mntcache_lock);
@@ -268,45 +277,60 @@ static inline int au_test_nfs(struct super_block *sb)
 static inline int au_test_fuse(struct super_block *sb)
 {
 #ifdef CONFIG_AUFS_WORKAROUND_FUSE
-#ifdef FUSE_SUPER_MAGIC
 	return (sb->s_magic == FUSE_SUPER_MAGIC);
 #else
-	return !strcmp(au_sbtype(sb), "fuse");
-#endif
-#endif
 	return 0;
+#endif
 }
 
 static inline int au_test_xfs(struct super_block *sb)
 {
 #ifdef CONFIG_AUFS_BR_XFS
-#ifdef XFS_SB_MAGIC
 	return (sb->s_magic == XFS_SB_MAGIC);
 #else
-	return !strcmp(au_sbtype(sb), "xfs");
-#endif
-#endif
 	return 0;
+#endif
 }
 
 static inline int au_test_tmpfs(struct super_block *sb)
 {
 #ifdef CONFIG_TMPFS
-#define TMPFS_MAGIC 0x01021994
 	return (sb->s_magic == TMPFS_MAGIC);
-#endif
+#else
 	return 0;
+#endif
 }
 
 /* ---------------------------------------------------------------------- */
 
-#ifdef CONFIG_AUFS_EXPORT
-extern struct export_operations aufs_export_op;
-static inline void au_init_export_op(struct super_block *sb)
+#ifdef CONFIG_AUFS_HINOTIFY
+static inline void au_xino_def_br_set(struct au_branch *br,
+				      struct au_sbinfo *sbinfo)
+{
+	sbinfo->si_xino_def_br = br;
+}
+
+static inline struct au_branch *au_xino_def_br(struct au_sbinfo *sbinfo)
 {
-	sb->s_export_op = &aufs_export_op;
-	memset(&au_sbi(sb)->si_xinodir, 0, sizeof(struct path));
+	return sbinfo->si_xino_def_br;
 }
+#else
+static inline void au_xino_def_br_set(struct au_branch *br,
+				      struct au_sbinfo *sbinfo)
+{
+	/* empty */
+}
+
+static inline struct au_branch *au_xino_def_br(struct au_sbinfo *sbinfo)
+{
+	return NULL;
+}
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_AUFS_EXPORT
+void au_export_init(struct super_block *sb);
 
 static inline int au_test_nfsd(struct task_struct *tsk)
 {
@@ -329,15 +353,21 @@ static inline void au_export_put(struct au_sbinfo *sbinfo)
 {
 	path_put(&sbinfo->si_xinodir);
 }
+
+int au_xigen_inc(struct inode *inode);
+int au_xigen_new(struct inode *inode);
+int au_xigen_set(struct super_block *sb, struct file *base);
+void au_xigen_clr(struct super_block *sb);
+
 #else
-static inline int au_test_nfsd(struct task_struct *tsk)
+static inline void au_export_init(struct super_block *sb)
 {
-	return 0;
+	/* nothing */
 }
 
-static inline void au_init_export_op(struct super_block *sb)
+static inline int au_test_nfsd(struct task_struct *tsk)
 {
-	/* nothing */
+	return 0;
 }
 
 #define au_nfsd_lockdep_off()	do {} while (0)
@@ -347,6 +377,26 @@ static inline void au_export_put(struct au_sbinfo *sbinfo)
 {
 	/* nothing */
 }
+
+static inline int au_xigen_inc(struct inode *inode)
+{
+	return 0;
+}
+
+static inline int au_xigen_new(struct inode *inode)
+{
+	return 0;
+}
+
+static inline int au_xigen_set(struct super_block *sb, struct file *base)
+{
+	return 0;
+}
+
+static inline void au_xigen_clr(struct super_block *sb)
+{
+	/* empty */
+}
 #endif /* CONFIG_AUFS_EXPORT */
 
 #ifdef CONFIG_AUFS_ROBR
diff --git a/ubuntu/aufs/sysaufs.c b/ubuntu/aufs/sysaufs.c
index 07f7a7d..de90d22 100644
--- a/ubuntu/aufs/sysaufs.c
+++ b/ubuntu/aufs/sysaufs.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -20,7 +20,7 @@
  * sysfs interface and lifetime management
  * they are necessary regardless sysfs is disabled.
  *
- * $Id: sysaufs.c,v 1.7 2008/06/02 02:38:50 sfjro Exp $
+ * $Id: sysaufs.c,v 1.10 2008/09/15 03:14:55 sfjro Exp $
  */
 
 #include <linux/fs.h>
@@ -35,7 +35,7 @@ unsigned long au_si_mask;
 
 /* ---------------------------------------------------------------------- */
 
-struct kset au_kset;
+struct kset *au_kset;
 
 #define AuSbiAttr(_name) { \
 	.attr   = { .name = __stringify(_name), .mode = 0444 },	\
@@ -43,8 +43,14 @@ struct kset au_kset;
 }
 
 static struct au_sbi_attr au_sbi_attr_xino = AuSbiAttr(xino);
+#ifdef CONFIG_AUFS_EXPORT
+static struct au_sbi_attr au_sbi_attr_xigen = AuSbiAttr(xigen);
+#endif
 struct attribute *au_sbi_attrs[] = {
 	&au_sbi_attr_xino.attr,
+#ifdef CONFIG_AUFS_EXPORT
+	&au_sbi_attr_xigen.attr,
+#endif
 	NULL,
 };
 
@@ -55,7 +61,7 @@ static struct sysfs_ops au_sbi_ops = {
 static struct kobj_type au_sbi_ktype = {
 	.release	= au_si_free,
 	.sysfs_ops	= &au_sbi_ops,
-	.default_attrs	= au_sbi_attrs,
+	.default_attrs	= au_sbi_attrs
 };
 
 /* ---------------------------------------------------------------------- */
@@ -64,10 +70,10 @@ int sysaufs_si_init(struct au_sbinfo *sbinfo)
 {
 	int err;
 
-	sbinfo->si_kobj.kset = &au_kset;
+	sbinfo->si_kobj.kset = au_kset;
 	/* some people doesn't like to show a pointer in kernel */
 	err = kobject_init_and_add(&sbinfo->si_kobj, &au_sbi_ktype,
-				   NULL/*&au_kset.kobj*/,
+				   NULL/*&au_kset->kobj*/,
 				   SysaufsSb_PREFIX "%lx",
 				   au_si_mask ^ (unsigned long)sbinfo);
 	AuTraceErr(err);
@@ -79,8 +85,8 @@ int sysaufs_si_init(struct au_sbinfo *sbinfo)
 
 void sysaufs_fin(void)
 {
-	sysfs_remove_group(&au_kset.kobj, au_attr_group);
-	kset_unregister(&au_kset);
+	sysfs_remove_group(&au_kset->kobj, au_attr_group);
+	kset_unregister(au_kset);
 }
 
 int __init sysaufs_init(void)
@@ -89,16 +95,13 @@ int __init sysaufs_init(void)
 
 	get_random_bytes(&au_si_mask, sizeof(au_si_mask));
 
-	sysaufs_brs_init();
-	au_kset.kobj.parent = fs_kobj;
-	kobject_set_name(&au_kset.kobj, AUFS_NAME);
-	au_kset.kobj.ktype = au_ktype;
-	err = kset_register(&au_kset);
-	if (unlikely(err))
+	au_kset = kset_create_and_add(AUFS_NAME, NULL, fs_kobj);
+	err = PTR_ERR(au_kset);
+	if (IS_ERR(au_kset))
 		goto out;
-	err = sysfs_create_group(&au_kset.kobj, au_attr_group);
+	err = sysfs_create_group(&au_kset->kobj, au_attr_group);
 	if (unlikely(err))
-		kset_unregister(&au_kset);
+		kset_unregister(au_kset);
 
  out:
 	AuTraceErr(err);
diff --git a/ubuntu/aufs/sysaufs.h b/ubuntu/aufs/sysaufs.h
index ce2375c..fde73cd 100644
--- a/ubuntu/aufs/sysaufs.h
+++ b/ubuntu/aufs/sysaufs.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * sysfs interface and lifetime management
  *
- * $Id: sysaufs.h,v 1.8 2008/06/02 02:38:50 sfjro Exp $
+ * $Id: sysaufs.h,v 1.11 2008/09/15 03:14:55 sfjro Exp $
  */
 
 #ifndef __SYSAUFS_H__
@@ -43,7 +43,7 @@ struct au_sbi_attr {
 
 /* sysaufs.c */
 extern unsigned long au_si_mask;
-extern struct kset au_kset;
+extern struct kset *au_kset;
 extern struct attribute *au_sbi_attrs[];
 int sysaufs_si_init(struct au_sbinfo *sbinfo);
 int __init sysaufs_init(void);
@@ -58,6 +58,9 @@ extern struct attribute_group *au_attr_group;
 extern struct kobj_type *au_ktype;
 
 int sysaufs_sbi_xino(struct seq_file *seq, struct super_block *sb);
+#ifdef CONFIG_AUFS_EXPORT
+int sysaufs_sbi_xigen(struct seq_file *seq, struct super_block *sb);
+#endif
 int sysaufs_sbi_mntpnt1(struct seq_file *seq, struct super_block *sb);
 ssize_t sysaufs_sbi_show(struct kobject *kobj, struct attribute *attr,
 			 char *buf);
@@ -65,9 +68,6 @@ ssize_t sysaufs_sbi_show(struct kobject *kobj, struct attribute *attr,
 void sysaufs_br_init(struct au_branch *br);
 void sysaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex);
 void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex);
-
-#define sysaufs_brs_init()	do {} while (0)
-
 #else
 #define au_attr_group	NULL
 #define au_ktype	NULL
@@ -78,6 +78,14 @@ int sysaufs_sbi_xino(struct seq_file *seq, struct super_block *sb)
 	return 0;
 }
 
+#ifdef CONFIG_AUFS_EXPORT
+static inline
+int sysaufs_sbi_xigen(struct seq_file *seq, struct super_block *sb)
+{
+	return 0;
+}
+#endif
+
 static inline
 int sysaufs_sbi_mntpnt1(struct seq_file *seq, struct super_block *sb)
 {
@@ -105,11 +113,6 @@ static inline void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex)
 {
 	/* nothing */
 }
-
-static inline void sysaufs_brs_init(void)
-{
-	sysaufs_brs = 0;
-}
 #endif /* CONFIG_SYSFS */
 
 #endif /* __KERNEL__ */
diff --git a/ubuntu/aufs/sysfs.c b/ubuntu/aufs/sysfs.c
index 8502244..fc5b8a4 100644
--- a/ubuntu/aufs/sysfs.c
+++ b/ubuntu/aufs/sysfs.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * sysfs interface
  *
- * $Id: sysfs.c,v 1.6 2008/06/02 02:40:29 sfjro Exp $
+ * $Id: sysfs.c,v 1.13 2008/09/15 03:14:55 sfjro Exp $
  */
 
 #include <linux/fs.h>
@@ -64,6 +64,9 @@ static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr,
 #ifdef CONFIG_AUFS_DLGT
 		conf_bool(DLGT)
 #endif
+#ifdef CONFIG_AUFS_HIN_OR_DLGT
+		conf_bool(HIN_OR_DLGT)
+#endif
 #ifdef CONFIG_AUFS_RR_SQUASHFS
 		conf_bool(RR_SQUASHFS)
 #endif
@@ -79,6 +82,12 @@ static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr,
 #ifdef CONFIG_AUFS_LHASH_PATCH
 		conf_bool(LHASH_PATCH)
 #endif
+#ifdef CONFIG_AUFS_BR_NFS
+		conf_bool(BR_NFS)
+#endif
+#ifdef CONFIG_AUFS_BR_XFS
+		conf_bool(BR_XFS)
+#endif
 #ifdef CONFIG_AUFS_FSYNC_SUPER_PATCH
 		conf_bool(FSYNC_SUPER_PATCH)
 #endif
@@ -91,12 +100,18 @@ static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr,
 #ifdef CONFIG_AUFS_WORKAROUND_FUSE
 		conf_bool(WORKAROUND_FUSE)
 #endif
+#ifdef CONFIG_AUFS_HIN_OR_FUSE
+		conf_bool(HIN_OR_FUSE)
+#endif
 #ifdef CONFIG_AUFS_STAT
 		conf_bool(STAT)
 #endif
 #ifdef CONFIG_AUFS_DEBUG
 		conf_bool(DEBUG)
 #endif
+#ifdef CONFIG_AUFS_MAGIC_SYSRQ
+		conf_bool(MAGIC_SYSRQ)
+#endif
 #ifdef CONFIG_AUFS_COMPAT
 		conf_bool(COMPAT)
 #endif
@@ -106,14 +121,6 @@ static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr,
 #ifdef CONFIG_AUFS_UNIONFS23_PATCH
 		conf_bool(UNIONFS23_PATCH)
 #endif
-
-/* automatic configurations */
-#ifdef CONFIG_AUFS_BR_NFS
-		conf_bool(BR_NFS)
-#endif
-#ifdef CONFIG_AUFS_MAGIC_SYSRQ
-		conf_bool(MAGIC_SYSRQ)
-#endif
 		;
 #undef conf_bool
 
@@ -249,58 +256,71 @@ struct kobj_type *au_ktype = &au_ktype_body;
 
 /* ---------------------------------------------------------------------- */
 
+static int sysaufs_sbi_xi(struct seq_file *seq, struct file *xf, int dlgt,
+			  int print_path)
+{
+	int err;
+	struct kstat st;
+	struct path path;
+
+	err = vfsub_getattr(xf->f_vfsmnt, xf->f_dentry, &st, dlgt);
+	if (!err) {
+		seq_printf(seq, "%llux%lu %lld",
+			   st.blocks, st.blksize, (long long)st.size);
+		if (unlikely(print_path)) {
+			path.dentry = xf->f_dentry;
+			path.mnt = xf->f_vfsmnt;
+			seq_putc(seq, ' ');
+			seq_path(seq, &path, au_esc_chars);
+		}
+		seq_putc(seq, '\n');
+	} else
+		seq_printf(seq, "err %d\n", err);
+
+	AuTraceErr(err);
+	return err;
+}
+
 int sysaufs_sbi_xino(struct seq_file *seq, struct super_block *sb)
 {
-	int err, dlgt, xinodir;
-	struct au_sbinfo *sbinfo;
+	int err;
 	unsigned int mnt_flags;
 	aufs_bindex_t bend, bindex;
-	struct file *xf;
+	unsigned char dlgt, xinodir;
 	struct kstat st;
 	struct path path;
+	struct au_sbinfo *sbinfo;
+	struct file *xf;
 
 	AuTraceEnter();
 
 	sbinfo = au_sbi(sb);
 	mnt_flags = au_mntflags(sb);
-	xinodir = au_opt_test(mnt_flags, XINODIR);
+	xinodir = !!au_opt_test(mnt_flags, XINODIR);
 	if (unlikely(!au_opt_test_xino(mnt_flags))) {
 #ifdef CONFIG_AUFS_DEBUG
 		AuDebugOn(sbinfo->si_xib);
 		bend = au_sbend(sb);
 		for (bindex = 0; bindex <= bend; bindex++)
-			AuDebugOn(au_sbr(sb, bindex)->br_xino);
+			AuDebugOn(au_sbr(sb, bindex)->br_xino.xi_file);
 #endif
 		err = 0;
 		goto out; /* success */
 	}
 
 	dlgt = !!au_test_dlgt(mnt_flags);
-	xf = sbinfo->si_xib;
-	err = vfsub_getattr(xf->f_vfsmnt, xf->f_dentry, &st, dlgt);
-	if (!err) {
-		seq_printf(seq, "%llux%lu %lld",
-			   st.blocks, st.blksize, (long long)st.size);
-		if (unlikely(xinodir)) {
-			path.dentry = xf->f_dentry;
-			path.mnt = xf->f_vfsmnt;
-			seq_putc(seq, ' ');
-			seq_path(seq, &path, au_esc_chars);
-		}
-		seq_putc(seq, '\n');
-	} else
-		seq_printf(seq, "err %d\n", err);
+	err = sysaufs_sbi_xi(seq, sbinfo->si_xib, dlgt, xinodir);
 
 	bend = au_sbend(sb);
 	for (bindex = 0; !err && bindex <= bend; bindex++) {
-		xf = au_sbr(sb, bindex)->br_xino;
+		xf = au_sbr(sb, bindex)->br_xino.xi_file;
 		if (!xf)
 			continue;
 		seq_printf(seq, "%d: ", bindex);
 		err = vfsub_getattr(xf->f_vfsmnt, xf->f_dentry, &st, dlgt);
 		if (!err) {
 			seq_printf(seq, "%ld, %llux%lu %lld",
-				   file_count(xf), st.blocks, st.blksize,
+				   (long)file_count(xf), st.blocks, st.blksize,
 				   (long long)st.size);
 			if (unlikely(xinodir)) {
 				path.dentry = xf->f_dentry;
@@ -318,6 +338,28 @@ int sysaufs_sbi_xino(struct seq_file *seq, struct super_block *sb)
 	return err;
 }
 
+#ifdef CONFIG_AUFS_EXPORT
+int sysaufs_sbi_xigen(struct seq_file *seq, struct super_block *sb)
+{
+	int err;
+	unsigned int mnt_flags;
+	struct au_sbinfo *sbinfo;
+
+	AuTraceEnter();
+
+	err = 0;
+	sbinfo = au_sbi(sb);
+	mnt_flags = au_mntflags(sb);
+	if (au_opt_test_xino(mnt_flags))
+		err = sysaufs_sbi_xi(seq, sbinfo->si_xigen,
+				     !!au_opt_test(mnt_flags, DLGT),
+				     !!au_opt_test(mnt_flags, XINODIR));
+
+	AuTraceErr(err);
+	return err;
+}
+#endif
+
 /*
  * the lifetime of branch is independent from the entry under sysfs.
  * sysfs handles the lifetime of the entry, and never call ->show() after it is
@@ -359,12 +401,11 @@ static struct seq_file *au_seq(char *p, ssize_t len)
 {
 	struct seq_file *seq;
 
-	seq = kzalloc(sizeof(*seq), GFP_TEMPORARY);
+	seq = kzalloc(sizeof(*seq), GFP_NOFS);
 	if (seq) {
 		/* todo: necessary? */
 		/* mutex_init(&seq.lock); */
 		seq->buf = p;
-		seq->count = 0;
 		seq->size = len;
 		return seq; /* success */
 	}
diff --git a/ubuntu/aufs/sysrq.c b/ubuntu/aufs/sysrq.c
new file mode 100644
index 0000000..ee8aaeb
--- /dev/null
+++ b/ubuntu/aufs/sysrq.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2005-2008 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
+ */
+
+/*
+ * magic sysrq hanlder
+ *
+ * $Id: sysrq.c,v 1.10 2008/09/08 02:40:48 sfjro Exp $
+ */
+
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+/* #include <linux/sysrq.h> */
+#include "aufs.h"
+
+static void sysrq_sb(struct super_block *sb)
+{
+	char *plevel;
+	struct inode *i;
+
+	plevel = au_plevel;
+	au_plevel = KERN_WARNING;
+	au_debug_on();
+
+	pr_warning("si=%lx\n", au_si_mask ^ (unsigned long)au_sbi(sb));
+	pr_warning(AUFS_NAME ": superblock\n");
+	au_dpri_sb(sb);
+	pr_warning(AUFS_NAME ": root dentry\n");
+	au_dpri_dentry(sb->s_root);
+	pr_warning(AUFS_NAME ": root inode\n");
+	au_dpri_inode(sb->s_root->d_inode);
+	pr_warning(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);
+
+	au_plevel = plevel;
+	au_debug_off();
+}
+
+/* ---------------------------------------------------------------------- */
+
+/* module parameter */
+static char *aufs_sysrq_key = "a";
+module_param_named(sysrq, aufs_sysrq_key, charp, S_IRUGO);
+MODULE_PARM_DESC(sysrq, "MagicSysRq key for " AUFS_NAME);
+
+static void au_sysrq(int key, struct tty_struct *tty)
+{
+	struct kobject *kobj;
+	struct au_sbinfo *sbinfo;
+
+	/* spin_lock(&au_kset->list_lock); */
+	list_for_each_entry(kobj, &au_kset->list, entry) {
+		sbinfo = container_of(kobj, struct au_sbinfo, si_kobj);
+		sysrq_sb(sbinfo->si_sb);
+	}
+	/* spin_unlock(&au_kset->list_lock); */
+}
+
+static struct sysrq_key_op au_sysrq_op = {
+	.handler	= au_sysrq,
+	.help_msg	= "Aufs",
+	.action_msg	= "Aufs",
+	/* todo: test mask? */
+	.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))
+		AuErr("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))
+		AuErr("err %d (ignored)\n", err);
+}
diff --git a/ubuntu/aufs/vdir.c b/ubuntu/aufs/vdir.c
index e064e08..2ae668f 100644
--- a/ubuntu/aufs/vdir.c
+++ b/ubuntu/aufs/vdir.c
@@ -19,7 +19,7 @@
 /*
  * virtual or vertical directory
  *
- * $Id: vdir.c,v 1.6 2008/06/09 01:11:58 sfjro Exp $
+ * $Id: vdir.c,v 1.11 2008/09/22 03:52:19 sfjro Exp $
  */
 
 #include "aufs.h"
@@ -184,7 +184,7 @@ int au_nhash_append_wh(struct au_nhash *whlist, char *name, int namelen,
 	LKTRTrace("%.*s\n", namelen, name);
 
 	err = -ENOMEM;
-	wh = kmalloc(sizeof(*wh) + namelen, GFP_TEMPORARY);
+	wh = kmalloc(sizeof(*wh) + namelen, GFP_NOFS);
 	if (unlikely(!wh))
 		goto out;
 	err = 0;
@@ -230,11 +230,11 @@ static int append_deblk(struct au_vdir *vdir)
 
 	err = -ENOMEM;
 	sz = sizeof(*o) * vdir->vd_nblk;
-	o = au_kzrealloc(vdir->vd_deblk, sz, sz + sizeof(*o), GFP_KERNEL);
+	o = au_kzrealloc(vdir->vd_deblk, sz, sz + sizeof(*o), GFP_NOFS);
 	if (unlikely(!o))
 		goto out;
 	vdir->vd_deblk = o;
-	p.deblk = kmalloc(sizeof(*p.deblk), GFP_KERNEL);
+	p.deblk = kmalloc(sizeof(*p.deblk), GFP_NOFS);
 	if (p.deblk) {
 		i = vdir->vd_nblk++;
 		vdir->vd_deblk[i] = p.deblk;
@@ -261,7 +261,7 @@ static struct au_vdir *alloc_vdir(void)
 	vdir = au_cache_alloc_vdir();
 	if (unlikely(!vdir))
 		goto out;
-	vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_KERNEL);
+	vdir->vd_deblk = kzalloc(sizeof(*vdir->vd_deblk), GFP_NOFS);
 	if (unlikely(!vdir->vd_deblk))
 		goto out_free;
 
@@ -354,7 +354,8 @@ static int append_de(struct au_vdir *vdir, char *name, int namelen, ino_t ino,
 	union au_vdir_deblk_p p, *room, deblk_end;
 	struct au_vdir_dehstr *dehstr;
 
-	LKTRTrace("%.*s %d, i%lu, dt%u\n", namelen, name, namelen, ino, d_type);
+	LKTRTrace("%.*s %d, i%lu, dt%u\n",
+		  namelen, name, namelen, (unsigned long)ino, d_type);
 
 	p.deblk = last_deblk(vdir);
 	deblk_end.deblk = p.deblk + 1;
@@ -400,15 +401,19 @@ static int append_de(struct au_vdir *vdir, char *name, int namelen, ino_t ino,
 /* ---------------------------------------------------------------------- */
 
 static int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
-		  ino_t *ino)
+		  unsigned int d_type, ino_t *ino)
 {
 	int err;
 	struct au_xino_entry xinoe;
-	/* todo: this lock is too large, try br_xino_nondir mutex by DT_DIR */
-	static DEFINE_MUTEX(mtx);
-
-	/* a race condition for hardlinks */
-	mutex_lock(&mtx);
+	struct mutex *mtx;
+	const int isdir = (d_type == DT_DIR);
+
+	/* prevent hardlinks from race condition */
+	mtx = NULL;
+	if (!isdir) {
+		mtx = &au_sbr(sb, bindex)->br_xino.xi_nondir_mtx;
+		mutex_lock(mtx);
+	}
 	err = au_xino_read(sb, bindex, h_ino, &xinoe);
 	if (unlikely(err))
 		goto out;
@@ -418,6 +423,7 @@ static int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 		xinoe.ino = au_xino_new_ino(sb);
 		if (unlikely(!xinoe.ino))
 			goto out;
+
 #if 0 /* reserved for future use */
 		struct inode *h_inode;
 		xinoe.h_gen = AuXino_INVALID_HGEN;
@@ -438,16 +444,17 @@ static int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 	*ino = xinoe.ino;
 
  out:
-	mutex_unlock(&mtx);
+	if (!isdir)
+		mutex_unlock(mtx);
 	AuTraceErr(err);
 	return err;
 }
 
 static int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
-		     ino_t *ino)
+		     unsigned int d_type, ino_t *ino)
 {
 #ifdef CONFIG_AUFS_SHWH
-	return au_ino(sb, bindex, h_ino, ino);
+	return au_ino(sb, bindex, h_ino, d_type, ino);
 #else
 	return 0;
 #endif
@@ -465,9 +472,9 @@ static int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 
 struct fillvdir_arg {
 	struct file		*file;
-	struct au_vdir 	*vdir;
-	struct au_nhash	*delist;
-	struct au_nhash	*whlist;
+	struct au_vdir		*vdir;
+	struct au_nhash		*delist;
+	struct au_nhash		*whlist;
 	aufs_bindex_t		bindex;
 	unsigned int		flags;
 	int			err;
@@ -483,7 +490,7 @@ static int fillvdir(void *__arg, const char *__name, int namelen, loff_t offset,
 	ino_t ino;
 
 	LKTRTrace("%.*s, namelen %d, i%llu, dt%u\n",
-		  namelen, name, namelen, (u64)h_ino, d_type);
+		  namelen, name, namelen, (unsigned long long)h_ino, d_type);
 
 	sb = arg->file->f_dentry->d_sb;
 	bend = arg->bindex;
@@ -499,7 +506,7 @@ static int fillvdir(void *__arg, const char *__name, int namelen, loff_t offset,
 				goto out; /* already exists or whiteouted */
 
 		ino = 1; /* why does gcc warns? */
-		arg->err = au_ino(sb, bend, h_ino, &ino);
+		arg->err = au_ino(sb, bend, h_ino, d_type, &ino);
 		if (!arg->err)
 			arg->err = append_de(arg->vdir, name, namelen, ino,
 					     d_type, arg->delist + bend);
@@ -513,7 +520,7 @@ static int fillvdir(void *__arg, const char *__name, int namelen, loff_t offset,
 
 		ino = 1; /* dummy */
 		if (unlikely(au_ftest_fillvdir(arg->flags, SHWH)))
-			arg->err = au_wh_ino(sb, bend, h_ino, &ino);
+			arg->err = au_wh_ino(sb, bend, h_ino, d_type, &ino);
 		if (!arg->err)
 			arg->err = au_nhash_append_wh
 				(arg->whlist + bend, name, namelen, ino, d_type,
@@ -584,21 +591,22 @@ static int au_handle_shwh(struct super_block *sb, struct au_vdir *vdir,
 
 static int au_do_read_vdir(struct fillvdir_arg *arg)
 {
-	int err, dlgt, shwh;
+	int err;
+	unsigned int mnt_flags;
+	loff_t offset;
 	aufs_bindex_t bend, bindex, bstart;
+	unsigned char dlgt, shwh;
 	struct super_block *sb;
-	unsigned int mnt_flags;
 	struct file *hf;
-	loff_t offset;
 
 	AuTraceEnter();
 
 	err = -ENOMEM;
 	bend = au_fbend(arg->file);
-	arg->delist = kmalloc(sizeof(*arg->delist) * (bend + 1), GFP_TEMPORARY);
+	arg->delist = kmalloc(sizeof(*arg->delist) * (bend + 1), GFP_NOFS);
 	if (unlikely(!arg->delist))
 		goto out;
-	arg->whlist = kmalloc(sizeof(*arg->whlist) * (bend + 1), GFP_TEMPORARY);
+	arg->whlist = kmalloc(sizeof(*arg->whlist) * (bend + 1), GFP_NOFS);
 	if (unlikely(!arg->whlist))
 		goto out_delist;
 	err = 0;
@@ -656,12 +664,13 @@ static int au_do_read_vdir(struct fillvdir_arg *arg)
 
 static int read_vdir(struct file *file, int may_read)
 {
-	int err, do_read;
+	int err;
+	unsigned long expire;
+	struct fillvdir_arg arg;
+	unsigned char do_read;
 	struct dentry *dentry;
 	struct inode *inode;
 	struct au_vdir *vdir, *allocated;
-	unsigned long expire;
-	struct fillvdir_arg arg;
 	struct super_block *sb;
 
 	dentry = file->f_dentry;
@@ -691,8 +700,8 @@ static int read_vdir(struct file *file, int may_read)
 		   && (inode->i_version != vdir->vd_version
 		       || time_after(jiffies, vdir->vd_jiffy + expire))) {
 		LKTRTrace("iver %llu, vdver %lu, exp %lu\n",
-			  inode->i_version, vdir->vd_version,
-			  vdir->vd_jiffy + expire);
+			  (unsigned long long)inode->i_version,
+			  vdir->vd_version, vdir->vd_jiffy + expire);
 		do_read = 1;
 		err = reinit_vdir(vdir);
 		if (unlikely(err))
@@ -732,7 +741,7 @@ static int copy_vdir(struct au_vdir *tgt, struct au_vdir *src)
 	if (tgt->vd_nblk < src->vd_nblk) {
 		au_vdir_deblk_t **p;
 		p = au_kzrealloc(tgt->vd_deblk, sizeof(*p) * tgt->vd_nblk,
-				 sizeof(*p) * src->vd_nblk, GFP_KERNEL);
+				 sizeof(*p) * src->vd_nblk, GFP_NOFS);
 		if (unlikely(!p))
 			goto out;
 		tgt->vd_deblk = p;
@@ -747,7 +756,7 @@ static int copy_vdir(struct au_vdir *tgt, struct au_vdir *src)
 	tgt->vd_jiffy = src->vd_jiffy;
 
 	for (i = 1; i < n; i++) {
-		tgt->vd_deblk[i] = kmalloc(AuSize_DEBLK, GFP_KERNEL);
+		tgt->vd_deblk[i] = kmalloc(AuSize_DEBLK, GFP_NOFS);
 		if (tgt->vd_deblk[i])
 			memcpy(tgt->vd_deblk[i], src->vd_deblk[i],
 			       AuSize_DEBLK);
@@ -901,7 +910,8 @@ int au_vdir_fill_de(struct file *file, void *dirent, filldir_t filldir)
 			de = vdir_cache->vd_last.p.de;
 			LKTRTrace("%.*s, off%lld, i%lu, dt%d\n",
 				  de->de_str.len, de->de_str.name,
-				  file->f_pos, de->de_ino, de->de_type);
+				  file->f_pos, (unsigned long)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)) {
diff --git a/ubuntu/aufs/vfsub.c b/ubuntu/aufs/vfsub.c
index 64f639d..6902d7e 100644
--- a/ubuntu/aufs/vfsub.c
+++ b/ubuntu/aufs/vfsub.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * sub-routines for VFS
  *
- * $Id: vfsub.c,v 1.6 2008/06/09 01:10:49 sfjro Exp $
+ * $Id: vfsub.c,v 1.11 2008/08/04 00:32:35 sfjro Exp $
  */
 
 #include <linux/uaccess.h>
@@ -126,6 +126,12 @@ int do_vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
 	return err;
 }
 
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
+#define VfsubSymlinkArgs	dir, dentry, symname
+#else
+#define VfsubSymlinkArgs	dir, dentry, symname, mode
+#endif
+
 int do_vfsub_symlink(struct inode *dir, struct dentry *dentry,
 		     const char *symname, int mode)
 {
@@ -135,7 +141,7 @@ int do_vfsub_symlink(struct inode *dir, struct dentry *dentry,
 		  dir->i_ino, AuDLNPair(dentry), symname, mode);
 	IMustLock(dir);
 
-	err = vfs_symlink(dir, dentry, NULL, symname);
+	err = vfs_symlink(VfsubSymlinkArgs);
 	if (!err) {
 		/* dir inode is locked */
 		au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/
@@ -152,7 +158,7 @@ int do_vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode,
 	LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode);
 	IMustLock(dir);
 
-	err = vfs_mknod(dir, dentry, NULL, mode, dev);
+	err = vfs_mknod(dir, dentry, mode, dev);
 	if (!err) {
 		/* dir inode is locked */
 		au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/
@@ -171,7 +177,7 @@ int do_vfsub_link(struct dentry *src_dentry, struct inode *dir,
 	IMustLock(dir);
 
 	lockdep_off();
-	err = vfs_link(src_dentry, NULL, dir, dentry, 0);
+	err = vfs_link(src_dentry, dir, dentry);
 	lockdep_on();
 	if (!err) {
 		LKTRTrace("src_i %p, dst_i %p\n",
@@ -197,7 +203,7 @@ int do_vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
 	IMustLock(src_dir);
 
 	lockdep_off();
-	err = vfs_rename(src_dir, src_dentry, NULL, dir, dentry, 0);
+	err = vfs_rename(src_dir, src_dentry, dir, dentry);
 	lockdep_on();
 	if (!err) {
 		/* dir inode is locked */
@@ -215,7 +221,7 @@ int do_vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode)
 	LKTRTrace("i%lu, %.*s, 0x%x\n", dir->i_ino, AuDLNPair(dentry), mode);
 	IMustLock(dir);
 
-	err = vfs_mkdir(dir, dentry, NULL, mode);
+	err = vfs_mkdir(dir, dentry, mode);
 	if (!err) {
 		/* dir inode is locked */
 		au_update_fuse_h_inode(NULL, dentry->d_parent); /*ignore*/
@@ -232,7 +238,7 @@ int do_vfsub_rmdir(struct inode *dir, struct dentry *dentry)
 	IMustLock(dir);
 
 	lockdep_off();
-	err = vfs_rmdir(dir, dentry, 0);
+	err = vfs_rmdir(dir, dentry);
 	lockdep_on();
 	/* dir inode is locked */
 	if (!err)
@@ -249,7 +255,7 @@ int do_vfsub_unlink(struct inode *dir, struct dentry *dentry)
 
 	/* vfs_unlink() locks inode */
 	lockdep_off();
-	err = vfs_unlink(dir, dentry, 0);
+	err = vfs_unlink(dir, dentry);
 	lockdep_on();
 	/* dir inode is locked */
 	if (!err)
@@ -267,13 +273,10 @@ ssize_t do_vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
 	LKTRTrace("%.*s, cnt %lu, pos %lld\n",
 		  AuDLNPair(file->f_dentry), (unsigned long)count, *ppos);
 
-	if (0 /*!au_test_nfs(file->f_vfsmnt->mnt_sb)*/)
-		err = vfs_read(file, ubuf, count, ppos);
-	else {
-		lockdep_off();
-		err = vfs_read(file, ubuf, count, ppos);
-		lockdep_on();
-	}
+	/* todo: always off, regardless nfs branch? */
+	au_br_nfs_lockdep_off(file->f_vfsmnt->mnt_sb);
+	err = vfs_read(file, ubuf, count, ppos);
+	au_br_nfs_lockdep_on(file->f_vfsmnt->mnt_sb);
 	if (err >= 0)
 		au_update_fuse_h_inode(file->f_vfsmnt, file->f_dentry);
 	/*ignore*/
@@ -381,33 +384,39 @@ struct au_vfsub_mkdir_args {
 	struct inode *dir;
 	struct dentry *dentry;
 	int mode;
-	int dlgt;
+	struct vfsub_args *vargs;
 };
 
 static void au_call_vfsub_mkdir(void *args)
 {
 	struct au_vfsub_mkdir_args *a = args;
-	*a->errp = vfsub_mkdir(a->dir, a->dentry, a->mode, a->dlgt);
+	*a->errp = vfsub_mkdir(a->dir, a->dentry, a->mode, a->vargs);
 }
 
-int vfsub_sio_mkdir(struct inode *dir, struct dentry *dentry, int mode,
+int vfsub_sio_mkdir(struct au_hinode *hdir, struct dentry *dentry, int mode,
 		    int dlgt)
 {
 	int err, do_sio, wkq_err;
+	struct inode *dir = hdir->hi_inode;
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
 
 	LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry));
 
+	vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0);
+	vfsub_ign_hinode(&vargs, IN_CREATE, hdir);
 	do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE, dlgt);
 	if (!do_sio)
-		err = vfsub_mkdir(dir, dentry, mode, dlgt);
+		err = vfsub_mkdir(dir, dentry, mode, &vargs);
 	else {
 		struct au_vfsub_mkdir_args args = {
 			.errp	= &err,
 			.dir	= dir,
 			.dentry	= dentry,
 			.mode	= mode,
-			.dlgt	= 0
+			.vargs	= &vargs
 		};
+		vfsub_fclr(vargs.flags, DLGT);
 		wkq_err = au_wkq_wait(au_call_vfsub_mkdir, &args, /*dlgt*/0);
 		if (unlikely(wkq_err))
 			err = wkq_err;
@@ -430,14 +439,17 @@ static void au_call_vfsub_rmdir(void *args)
 	*a->errp = vfsub_rmdir(a->dir, a->dentry, a->vargs);
 }
 
-int vfsub_sio_rmdir(struct inode *dir, struct dentry *dentry, int dlgt)
+int vfsub_sio_rmdir(struct au_hinode *hdir, struct dentry *dentry, int dlgt)
 {
 	int err, do_sio, wkq_err;
+	struct inode *dir = hdir->hi_inode;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
 
 	LKTRTrace("i%lu, %.*s\n", dir->i_ino, AuDLNPair(dentry));
 
-	vfsub_args_init(&vargs, /*ign*/NULL, dlgt, /*force_unlink*/0);
+	vfsub_args_init(&vargs, &ign, dlgt, /*force_unlink*/0);
+	vfsub_ign_hinode(&vargs, IN_DELETE, hdir);
 	do_sio = au_test_h_perm_sio(dir, MAY_EXEC | MAY_WRITE, dlgt);
 	if (!do_sio)
 		err = vfsub_rmdir(dir, dentry, &vargs);
@@ -458,41 +470,6 @@ int vfsub_sio_rmdir(struct inode *dir, struct dentry *dentry, int dlgt)
 	return err;
 }
 
-struct au_vfsub_notify_change_args {
-	int *errp;
-	struct dentry *dentry;
-	struct iattr *ia;
-	struct vfsub_args *vargs;
-};
-
-static void au_call_vfsub_notify_change(void *args)
-{
-	struct au_vfsub_notify_change_args *a = args;
-	*a->errp = vfsub_notify_change(a->dentry, a->ia, a->vargs);
-}
-
-int vfsub_sio_notify_change(struct dentry *dentry, struct iattr *ia)
-{
-	int err, wkq_err;
-	struct vfsub_args vargs;
-	struct au_vfsub_notify_change_args args = {
-		.errp		= &err,
-		.dentry		= dentry,
-		.ia		= ia,
-		.vargs		= &vargs
-	};
-
-	LKTRTrace("%.*s, 0x%x\n", AuDLNPair(dentry), ia->ia_valid);
-
-	vfsub_args_init(&vargs, /*ign*/NULL, /*dlgt*/0, /*force_unlink*/0);
-	wkq_err = au_wkq_wait(au_call_vfsub_notify_change, &args, /*dlgt*/0);
-	if (unlikely(wkq_err))
-		err = wkq_err;
-
-	AuTraceErr(err);
-	return err;
-}
-
 /* ---------------------------------------------------------------------- */
 
 struct notify_change_args {
@@ -500,7 +477,6 @@ struct notify_change_args {
 	struct dentry *h_dentry;
 	struct iattr *ia;
 	struct vfsub_args *vargs;
-	struct file *file;
 };
 
 static void call_notify_change(void *args)
@@ -517,12 +493,13 @@ static void call_notify_change(void *args)
 	if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) {
 		vfsub_ignore(a->vargs);
 		lockdep_off();
-		*a->errp = fnotify_change(a->h_dentry, NULL, a->ia, a->file);
+		*a->errp = notify_change(a->h_dentry, a->ia);
 		lockdep_on();
 		if (!*a->errp)
 			au_update_fuse_h_inode(NULL, a->h_dentry); /*ignore*/
 		else
 			vfsub_unignore(a->vargs);
+		au_dbg_hin_list(a->vargs);
 	}
 	AuTraceErr(*a->errp);
 }
@@ -548,8 +525,8 @@ static void vfsub_notify_change_dlgt(struct notify_change_args *args,
 }
 #endif
 
-int vfsub_fnotify_change(struct dentry *dentry, struct iattr *ia,
-			struct vfsub_args *vargs, struct file *file)
+int vfsub_notify_change(struct dentry *dentry, struct iattr *ia,
+			struct vfsub_args *vargs)
 {
 	int err;
 	struct notify_change_args args = {
@@ -565,10 +542,32 @@ int vfsub_fnotify_change(struct dentry *dentry, struct iattr *ia,
 	return err;
 }
 
-int vfsub_notify_change(struct dentry *dentry, struct iattr *ia,
-			struct vfsub_args *vargs)
+int vfsub_sio_notify_change(struct au_hinode *hdir, struct dentry *dentry,
+			    struct iattr *ia)
 {
-	return vfsub_fnotify_change(dentry, ia, vargs, NULL);
+	int err, wkq_err;
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
+	__u32 events;
+	struct notify_change_args args = {
+		.errp		= &err,
+		.h_dentry	= dentry,
+		.ia		= ia,
+		.vargs		= &vargs
+	};
+
+	LKTRTrace("%.*s, 0x%x\n", AuDLNPair(dentry), ia->ia_valid);
+
+	vfsub_args_init(&vargs, &ign, /*dlgt*/0, /*force_unlink*/0);
+	events = vfsub_events_notify_change(ia);
+	if (events)
+		vfsub_ign_hinode(&vargs, events, hdir);
+	wkq_err = au_wkq_wait(call_notify_change, &args, /*dlgt*/0);
+	if (unlikely(wkq_err))
+		err = wkq_err;
+
+	AuTraceErr(err);
+	return err;
 }
 
 /* ---------------------------------------------------------------------- */
@@ -596,7 +595,11 @@ static void call_unlink(void *args)
 	h_inode = a->dentry->d_inode;
 	if (h_inode)
 		atomic_inc_return(&h_inode->i_count);
+	vfsub_ignore(a->vargs);
 	*a->errp = do_vfsub_unlink(a->dir, a->dentry);
+	if (unlikely(*a->errp || (a->dentry->d_flags & DCACHE_NFSFS_RENAMED)))
+		vfsub_unignore(a->vargs);
+	au_dbg_hin_list(a->vargs);
 	if (!stop_sillyrename)
 		dput(a->dentry);
 	if (h_inode)
diff --git a/ubuntu/aufs/vfsub.h b/ubuntu/aufs/vfsub.h
index 880bced..35215dc 100644
--- a/ubuntu/aufs/vfsub.h
+++ b/ubuntu/aufs/vfsub.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,7 +19,7 @@
 /*
  * sub-routines for VFS
  *
- * $Id: vfsub.h,v 1.5 2008/06/09 01:11:31 sfjro Exp $
+ * $Id: vfsub.h,v 1.11 2008/08/25 01:51:04 sfjro Exp $
  */
 
 #ifndef __AUFS_VFSUB_H__
@@ -29,9 +29,10 @@
 
 #include <linux/fs.h>
 #include <linux/fs_stack.h>
+#include <linux/inotify.h>
 #include <linux/namei.h>
+#include <linux/security.h>
 #include <linux/splice.h>
-#include <linux/inotify.h>
 
 /* ---------------------------------------------------------------------- */
 
@@ -74,9 +75,6 @@ static inline void vfsub_args_reinit(struct vfsub_args *vargs)
 __u32 vfsub_events_notify_change(struct iattr *ia);
 void vfsub_ign_hinode(struct vfsub_args *vargs, __u32 events,
 		      struct au_hinode *hinode);
-void vfsub_ign_inode(struct vfsub_args *vargs, __u32 events,
-		     struct inode *inode, struct inode *h_inode);
-
 void vfsub_ignore(struct vfsub_args *vargs);
 void vfsub_unignore(struct vfsub_args *vargs);
 #else
@@ -102,12 +100,6 @@ static inline void vfsub_ign_hinode(struct vfsub_args *vargs, __u32 events,
 	/* empty */
 }
 
-static inline void vfsub_ign_inode(struct vfsub_args *vargs, __u32 events,
-				   struct inode *inode, struct inode *h_inode)
-{
-	/* empty */
-}
-
 static inline void vfsub_ignore(struct vfsub_args *vargs)
 {
 	/* empty */
@@ -137,18 +129,34 @@ static inline int au_test_inotify(struct inode *inode)
 
 /* lock subclass for hidden inode */
 /* default MAX_LOCKDEP_SUBCLASSES(8) is not enough */
-/* todo: reduce? */
+/* reduce? gave up. */
 enum {
 	AuLsc_I_Begin = I_MUTEX_QUOTA, /* 4 */
 	AuLsc_I_PARENT,		/* hidden inode, parent first */
-	AuLsc_I_CHILD,
 	AuLsc_I_PARENT2,	/* copyup dirs */
+	AuLsc_I_PARENT3,	/* rename with hinotify */
+	AuLsc_I_PARENT4,	/* ditto */
+	AuLsc_I_CHILD,
 	AuLsc_I_CHILD2,
 	AuLsc_I_End
 };
 
 #define IMustLock(i)	MtxMustLock(&(i)->i_mutex)
 
+static inline void vfsub_lock_rename_mutex(struct super_block *sb)
+{
+	lockdep_off();
+	mutex_lock(&sb->s_vfs_rename_mutex);
+	lockdep_on();
+}
+
+static inline void vfsub_unlock_rename_mutex(struct super_block *sb)
+{
+	lockdep_off();
+	mutex_unlock(&sb->s_vfs_rename_mutex);
+	lockdep_on();
+}
+
 static inline
 struct dentry *vfsub_lock_rename(struct dentry *d1, struct dentry *d2)
 {
@@ -167,6 +175,12 @@ static inline void vfsub_unlock_rename(struct dentry *d1, struct dentry *d2)
 	lockdep_on();
 }
 
+static inline int au_verify_parent(struct dentry *dentry, struct inode *dir)
+{
+	IMustLock(dir);
+	return (/* !dir->i_nlink || */ dentry->d_parent->d_inode != dir);
+}
+
 /* ---------------------------------------------------------------------- */
 
 #ifdef CONFIG_AUFS_WORKAROUND_FUSE
@@ -199,7 +213,22 @@ int do_vfsub_permission(struct inode *inode, int mask, struct nameidata *nd)
 {
 	LKTRTrace("i%lu, mask 0x%x, nd %d\n", inode->i_ino, mask, !!nd);
 	IMustLock(inode);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
 	return inode_permission(inode, mask);
+#else
+	return permission(inode, mask, nd);
+#endif
+}
+
+static inline
+int vfsub_security_inode_permission(struct inode *inode, int mask,
+				    struct nameidata *nd)
+{
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
+	return security_inode_permission(inode, mask);
+#else
+	return security_inode_permission(inode, mask, nd);
+#endif
 }
 
 /* ---------------------------------------------------------------------- */
@@ -246,8 +275,7 @@ int do_vfsub_readdir(struct file *file, filldir_t filldir, void *arg);
 
 /* ---------------------------------------------------------------------- */
 
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) \
-	|| (!defined(MmTree) && !defined(CONFIG_AUFS_UNIONFS22_PATCH))
+#ifndef CONFIG_AUFS_UNIONFS22_PATCH
 static inline void vfsub_copy_inode_size(struct inode *inode,
 					 struct inode *h_inode)
 {
@@ -263,8 +291,7 @@ static inline void vfsub_copy_inode_size(struct inode *inode,
 }
 #endif
 
-#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26) \
-	|| (!defined(MmTree) && !defined(CONFIG_AUFS_UNIONFS23_PATCH))
+#ifndef CONFIG_AUFS_UNIONFS23_PATCH
 #define vfs_splice_to		do_splice_to
 #define vfs_splice_from		do_splice_from
 #endif
@@ -315,23 +342,24 @@ static inline int do_vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry,
 
 /* ---------------------------------------------------------------------- */
 
-#if defined(CONFIG_AUFS_HINOTIFY) || defined(CONFIG_AUFS_DLGT)
+#ifdef CONFIG_AUFS_HIN_OR_DLGT
 /* hin_or_dlgt.c */
 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);
+		 struct nameidata *nd, struct vfsub_args *vargs);
 int vfsub_symlink(struct inode *dir, struct dentry *dentry, const char *symname,
-		  int mode, int dlgt);
+		  int mode, struct vfsub_args *vargs);
 int vfsub_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev,
-		int dlgt);
+		struct vfsub_args *vargs);
 int vfsub_link(struct dentry *src_dentry, struct inode *dir,
-	       struct dentry *dentry, int dlgt);
+	       struct dentry *dentry, struct vfsub_args *vargs);
 int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
 		 struct inode *dir, struct dentry *dentry,
 		 struct vfsub_args *vargs);
-int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode, int dlgt);
+int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode,
+		struct vfsub_args *vargs);
 int vfsub_rmdir(struct inode *dir, struct dentry *dentry,
 		struct vfsub_args *vargs);
 
@@ -364,28 +392,28 @@ int vfsub_permission(struct inode *inode, int mask, struct nameidata *nd,
 
 static inline
 int vfsub_create(struct inode *dir, struct dentry *dentry, int mode,
-		 struct nameidata *nd, int dlgt)
+		 struct nameidata *nd, struct vfsub_args *vargs)
 {
 	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)
+		  int mode, struct vfsub_args *vargs)
 {
 	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)
+		struct vfsub_args *vargs)
 {
 	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)
+	       struct dentry *dentry, struct vfsub_args *vargs)
 {
 	return do_vfsub_link(src_dentry, dir, dentry);
 }
@@ -395,17 +423,12 @@ int vfsub_rename(struct inode *src_dir, struct dentry *src_dentry,
 		 struct inode *dir, struct dentry *dentry,
 		 struct vfsub_args *vargs)
 {
-	int err;
-
-	vfsub_ignore(vargs);
-	err = do_vfsub_rename(src_dir, src_dentry, dir, dentry);
-	if (unlikely(err))
-		vfsub_unignore(vargs);
-	return err;
+	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)
+int vfsub_mkdir(struct inode *dir, struct dentry *dentry, int mode,
+		struct vfsub_args *vargs)
 {
 	return do_vfsub_mkdir(dir, dentry, mode);
 }
@@ -414,13 +437,7 @@ static inline
 int vfsub_rmdir(struct inode *dir, struct dentry *dentry,
 		struct vfsub_args *vargs)
 {
-	int err;
-
-	vfsub_ignore(vargs);
-	err = do_vfsub_rmdir(dir, dentry);
-	if (unlikely(err))
-		vfsub_unignore(vargs);
-	return err;
+	return do_vfsub_rmdir(dir, dentry);
 }
 
 static inline
@@ -441,26 +458,14 @@ static inline
 ssize_t vfsub_write_u(struct file *file, const char __user *ubuf, size_t count,
 		      loff_t *ppos, struct vfsub_args *vargs)
 {
-	int err;
-
-	vfsub_ignore(vargs);
-	err = do_vfsub_write_u(file, ubuf, count, ppos);
-	if (unlikely(err < 0))
-		vfsub_unignore(vargs);
-	return err;
+	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,
 		      struct vfsub_args *vargs)
 {
-	int err;
-
-	vfsub_ignore(vargs);
-	err = do_vfsub_write_k(file, kbuf, count, ppos);
-	if (unlikely(err < 0))
-		vfsub_unignore(vargs);
-	return err;
+	return do_vfsub_write_k(file, kbuf, count, ppos);
 }
 
 static inline
@@ -482,13 +487,7 @@ long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
 		       loff_t *ppos, size_t len, unsigned int flags,
 		       struct vfsub_args *vargs)
 {
-	long err;
-
-	vfsub_ignore(vargs);
-	err = do_vfsub_splice_from(pipe, out, ppos, len, flags);
-	if (unlikely(err < 0))
-		vfsub_unignore(vargs);
-	return err;
+	return do_vfsub_splice_from(pipe, out, ppos, len, flags);
 }
 
 static inline
@@ -497,19 +496,18 @@ int vfsub_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *st,
 {
 	return do_vfsub_getattr(mnt, dentry, st);
 }
-#endif /* CONFIG_AUFS_DLGT || CONFIG_AUFS_HINOTIFY */
+#endif /* HIN_OR_DLGT */
 
 /* ---------------------------------------------------------------------- */
 
-int vfsub_sio_mkdir(struct inode *dir, struct dentry *dentry, int mode,
+int vfsub_sio_mkdir(struct au_hinode *hdir, struct dentry *dentry, int mode,
 		    int dlgt);
-int vfsub_sio_rmdir(struct inode *dir, struct dentry *dentry, int dlgt);
-int vfsub_sio_notify_change(struct dentry *dentry, struct iattr *ia);
+int vfsub_sio_rmdir(struct au_hinode *hdir, struct dentry *dentry, int dlgt);
+int vfsub_sio_notify_change(struct au_hinode *hdir, struct dentry *dentry,
+			    struct iattr *ia);
 
 /* ---------------------------------------------------------------------- */
 
-int vfsub_fnotify_change(struct dentry *dentry, struct iattr *ia,
-			struct vfsub_args *vargs, struct file *file);
 int vfsub_notify_change(struct dentry *dentry, struct iattr *ia,
 			struct vfsub_args *vargs);
 int vfsub_unlink(struct inode *dir, struct dentry *dentry,
diff --git a/ubuntu/aufs/wbr_policy.c b/ubuntu/aufs/wbr_policy.c
index e06adfb..f2aea7c 100644
--- a/ubuntu/aufs/wbr_policy.c
+++ b/ubuntu/aufs/wbr_policy.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2008 Junjiro Okajima
+ * Copyright (C) 2005-2008 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
@@ -19,13 +19,14 @@
 /*
  * policies for selecting one among multiple writable branches
  *
- * $Id: wbr_policy.c,v 1.6 2008/06/09 01:11:08 sfjro Exp $
+ * $Id: wbr_policy.c,v 1.12 2008/09/01 02:55:35 sfjro Exp $
  */
 
 #include <linux/statfs.h>
 #include "aufs.h"
 
-static int au_cpdown_attr(struct dentry *h_dst, struct dentry *h_src)
+static int au_cpdown_attr(struct au_hinode *hdir, struct dentry *h_dst,
+			  struct dentry *h_src)
 {
 	int err, sbits;
 	struct iattr ia;
@@ -43,13 +44,13 @@ static int au_cpdown_attr(struct dentry *h_dst, struct dentry *h_src)
 	ia.ia_gid = h_isrc->i_gid;
 	sbits = !!(ia.ia_mode & (S_ISUID | S_ISGID));
 
-	err = vfsub_sio_notify_change(h_dst, &ia);
+	err = vfsub_sio_notify_change(hdir, h_dst, &ia);
 
 	/* is this nfs only? */
 	if (!err && sbits && au_test_nfs(h_dst->d_sb)) {
 		ia.ia_valid = ATTR_FORCE | ATTR_MODE;
 		ia.ia_mode = h_isrc->i_mode;
-		err = vfsub_sio_notify_change(h_dst, &ia);
+		err = vfsub_sio_notify_change(hdir, h_dst, &ia);
 	}
 
 	/* todo: necessary? */
@@ -68,11 +69,12 @@ struct au_cpdown_dir_args {
 static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 			 struct dentry *h_parent, void *arg)
 {
-	int err, parent_opq, whed, dlgt, do_opq, made_dir, diropq, rerr;
+	int err, rerr;
 	struct au_cpdown_dir_args *args = arg;
 	aufs_bindex_t bend, bopq, bstart;
-	struct dentry *h_dentry, *opq_dentry, *wh_dentry;
-	struct inode *h_dir, *h_inode, *inode;
+	unsigned char parent_opq, whed, dlgt, do_opq, made_dir, diropq;
+	struct dentry *h_dentry, *opq_dentry, *wh_dentry, *parent;
+	struct inode *h_dir, *h_inode, *inode, *dir;
 
 	LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bdst);
 	bstart = au_dbstart(dentry);
@@ -80,8 +82,13 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 		  && bdst <= au_dbend(dentry)
 		  && au_h_dptr(dentry, bdst));
 	AuDebugOn(!h_parent);
+	/* todo: safe? */
+	parent = dget_parent(dentry);
+	dir = parent->d_inode;
+	dput(parent);
 	h_dir = h_parent->d_inode;
 	AuDebugOn(!h_dir);
+	AuDebugOn(h_dir != au_h_iptr(dir, bdst));
 	IMustLock(h_dir);
 
 	err = au_lkup_neg(dentry, bdst);
@@ -89,7 +96,7 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 		goto out;
 	h_dentry = au_h_dptr(dentry, bdst);
 	dlgt = !!au_test_dlgt(au_mntflags(dentry->d_sb));
-	err = vfsub_sio_mkdir(h_dir, h_dentry, S_IRWXU | S_IRUGO | S_IXUGO,
+	err = vfsub_sio_mkdir(au_hi(dir, bdst), h_dentry, S_IRWXU | S_IRUGO | S_IXUGO,
 			      dlgt);
 	if (unlikely(err))
 		goto out_put;
@@ -116,7 +123,7 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 		diropq = 1;
 	}
 
-	err = au_cpdown_attr(h_dentry, au_h_dptr(dentry, bstart));
+	err = au_cpdown_attr(au_hi(dir, bdst), h_dentry, au_h_dptr(dentry, bstart));
 	mutex_unlock(&h_inode->i_mutex);
 	if (unlikely(err))
 		goto out_opq;
@@ -129,8 +136,8 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 			goto out_opq;
 		err = 0;
 		if (wh_dentry->d_inode)
-			err = au_wh_unlink_dentry(h_dir, wh_dentry, dentry,
-						  NULL, dlgt);
+			err = au_wh_unlink_dentry(au_hi(dir, bdst), wh_dentry,
+						  dentry, dlgt);
 		dput(wh_dentry);
 		if (unlikely(err))
 			goto out_opq;
@@ -139,7 +146,7 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 	inode = dentry->d_inode;
 	if (au_ibend(inode) < bdst)
 		au_set_ibend(inode, bdst);
-	au_set_h_iptr(inode, bdst, igrab(h_inode), au_hi_flags(inode, 1));
+	au_set_h_iptr(inode, bdst, au_igrab(h_inode), au_hi_flags(inode, 1));
 	goto out; /* success */
 
 	/* revert */
@@ -157,7 +164,7 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 	}
  out_dir:
 	if (made_dir) {
-		rerr = vfsub_sio_rmdir(h_dir, h_dentry, dlgt);
+		rerr = vfsub_sio_rmdir(au_hi(dir, bdst), h_dentry, dlgt);
 		if (unlikely(rerr)) {
 			AuIOErr("failed removing %.*s b%d (%d)\n",
 				AuDLNPair(dentry), bdst, rerr);
@@ -173,8 +180,7 @@ static int au_cpdown_dir(struct dentry *dentry, aufs_bindex_t bdst,
 	return err;
 }
 
-int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst,
-		   struct dentry *locked)
+int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst)
 {
 	int err;
 	struct au_cpdown_dir_args args = {
@@ -184,7 +190,7 @@ int au_cpdown_dirs(struct dentry *dentry, aufs_bindex_t bdst,
 
 	LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bdst);
 
-	err = au_cp_dirs(dentry, bdst, locked, au_cpdown_dir, &args);
+	err = au_cp_dirs(dentry, bdst, au_cpdown_dir, &args);
 	dput(args.parent);
 
 	AuTraceErr(err);
@@ -206,9 +212,10 @@ static int au_wbr_bu(struct super_block *sb, aufs_bindex_t bindex)
 /* top down parent */
 static int au_wbr_create_tdp(struct dentry *dentry, int isdir)
 {
-	int err, dirperm1;
+	int err;
 	struct super_block *sb;
 	aufs_bindex_t bstart, bindex;
+	unsigned char dirperm1;
 	struct dentry *parent, *h_parent;
 	struct inode *h_dir;
 
@@ -217,8 +224,10 @@ static int au_wbr_create_tdp(struct dentry *dentry, int isdir)
 	sb = dentry->d_sb;
 	dirperm1 = !!au_test_dirperm1(au_mntflags(sb));
 	bstart = au_dbstart(dentry);
+	AuDebugOn(bstart < 0);
 	err = bstart;
-	if (!au_br_rdonly(au_sbr(sb, bstart)))
+	/* todo: can 'err' be an illegal? */
+	if (/* err >= 0 && */ !au_br_rdonly(au_sbr(sb, bstart)))
 		goto out;
 
 	err = -EROFS;
@@ -360,9 +369,10 @@ static void au_mfs(struct dentry *dentry)
 {
 	struct super_block *sb;
 	aufs_bindex_t bindex, bend;
-	int dlgt, err;
+	unsigned char dlgt;
+	int err;
 	struct kstatfs st;
-	u64 b, bavail;
+	unsigned long long b, bavail;
 	void *arg;
 	struct au_branch *br;
 	struct au_wbr_mfs *mfs;
@@ -380,6 +390,7 @@ static void au_mfs(struct dentry *dentry)
 		br = au_sbr(sb, bindex);
 		if (au_br_rdonly(br))
 			continue;
+		AuDebugOn(!br->br_wbr);
 		arg = au_wbr_statfs_arg(br, sb, bindex);
 		if (!arg)
 			continue;
@@ -394,7 +405,7 @@ static void au_mfs(struct dentry *dentry)
 
 		/* when the available size is equal, select lower one */
 		b = st.f_bavail * st.f_bsize;
-		br->br_bytes = b;
+		br->br_wbr->wbr_bytes = b;
 		if (b >= bavail) {
 			bavail = b;
 			mfs->mfs_bindex = bindex;
@@ -493,12 +504,13 @@ static int au_wbr_create_init_mfsrr(struct super_block *sb)
 /* top down parent and most free space */
 static int au_wbr_create_pmfs(struct dentry *dentry, int isdir)
 {
-	int err, e2, dirperm1;
+	int err, e2;
 	struct super_block *sb;
 	struct dentry *parent, *h_parent;
 	aufs_bindex_t bindex, bstart, bend;
+	unsigned char dirperm1;
 	struct au_branch *br;
-	u64 b;
+	unsigned long long b;
 	struct inode *h_dir;
 
 	LKTRTrace("%.*s, %d\n", AuDLNPair(dentry), isdir);
@@ -519,8 +531,9 @@ static int au_wbr_create_pmfs(struct dentry *dentry, int isdir)
 	/* when the available size is equal, select upper one */
 	sb = dentry->d_sb;
 	br = au_sbr(sb, err);
+	AuDebugOn(!br->br_wbr);
 	dirperm1 = !!au_test_dirperm1(au_mntflags(sb));
-	b = br->br_bytes;
+	b = br->br_wbr->wbr_bytes;
 	LKTRTrace("b%d, %llu\n", err, b);
 
 	if (unlikely(dirperm1)) {
@@ -536,8 +549,8 @@ static int au_wbr_create_pmfs(struct dentry *dentry, int isdir)
 			if (!au_br_rdonly(br)
 			    && au_test_h_perm(h_dir, MAY_WRITE | MAY_EXEC,
 					      /*dlgt*/0)
-			    && br->br_bytes > b) {
-				b = br->br_bytes;
+			    && br->br_wbr->wbr_bytes > b) {
+				b = br->br_wbr->wbr_bytes;
 				err = bindex;
 				LKTRTrace("b%d, %llu\n", err, b);
 			}
@@ -551,8 +564,8 @@ static int au_wbr_create_pmfs(struct dentry *dentry, int isdir)
 			continue;
 
 		br = au_sbr(sb, bindex);
-		if (!au_br_rdonly(br) && br->br_bytes > b) {
-			b = br->br_bytes;
+		if (!au_br_rdonly(br) && br->br_wbr->wbr_bytes > b) {
+			b = br->br_wbr->wbr_bytes;
 			err = bindex;
 			LKTRTrace("b%d, %llu\n", err, b);
 		}
@@ -578,9 +591,10 @@ static int au_wbr_copyup_tdp(struct dentry *dentry)
 /* bottom up parent */
 static int au_wbr_copyup_bup(struct dentry *dentry)
 {
-	int err, dirperm1;
+	int err;
 	struct dentry *parent, *h_parent;
 	aufs_bindex_t bindex, bstart;
+	unsigned char dirperm1;
 	struct super_block *sb;
 	struct inode *h_dir;
 
diff --git a/ubuntu/aufs/whout.c b/ubuntu/aufs/whout.c
index dba26b5..8c33c2c 100644
--- a/ubuntu/aufs/whout.c
+++ b/ubuntu/aufs/whout.c
@@ -19,7 +19,7 @@
 /*
  * whiteout for logical deletion and opaque directory
  *
- * $Id: whout.c,v 1.6 2008/06/02 02:38:22 sfjro Exp $
+ * $Id: whout.c,v 1.13 2008/09/08 02:40:12 sfjro Exp $
  */
 
 #include <linux/fs.h>
@@ -54,7 +54,7 @@ int au_wh_name_alloc(const char *name, int len, struct qstr *wh)
 		return -ENAMETOOLONG;
 
 	wh->len = len + AUFS_WH_PFX_LEN;
-	p = kmalloc(wh->len, GFP_KERNEL);
+	p = kmalloc(wh->len, GFP_NOFS);
 	wh->name = p;
 	if (p) {
 		memcpy(p, AUFS_WH_PFX, AUFS_WH_PFX_LEN);
@@ -170,7 +170,7 @@ struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct qstr *prefix,
 		if (unlikely(len >= PATH_MAX))
 			goto out;
 		dentry = ERR_PTR(-ENOMEM);
-		name = kmalloc(len + 1, GFP_KERNEL);
+		name = kmalloc(len + 1, GFP_NOFS);
 		if (unlikely(!name))
 			goto out;
 	}
@@ -207,12 +207,12 @@ struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct qstr *prefix,
 /*
  * rename the @dentry of @bindex to the whiteouted temporary name.
  */
-int au_whtmp_ren(struct inode *dir, struct dentry *dentry, aufs_bindex_t bindex,
-		 int noself)
+int au_whtmp_ren(struct inode *dir, aufs_bindex_t bindex,
+		 struct dentry *h_dentry)
 {
 	int err, dlgt;
 	struct inode *h_dir;
-	struct dentry *h_dentry, *h_parent, *tmp_dentry;
+	struct dentry *h_parent, *tmp_dentry;
 	struct super_block *sb;
 	unsigned int mnt_flags;
 	struct au_hin_ignore ign;
@@ -223,14 +223,13 @@ int au_whtmp_ren(struct inode *dir, struct dentry *dentry, aufs_bindex_t bindex,
 		/* .br	= NULL */
 	};
 
-	LKTRTrace("%.*s, b%d\n", AuDLNPair(dentry), bindex);
-	h_dentry = au_h_dptr(dentry, bindex);
-	AuDebugOn(!h_dentry || !h_dentry->d_inode);
+	LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
+	AuDebugOn(!h_dentry->d_inode);
 	h_parent = h_dentry->d_parent; /* dir inode is locked */
 	h_dir = h_parent->d_inode;
 	IMustLock(h_dir);
 
-	sb = dentry->d_sb;
+	sb = dir->i_sb;
 	mnt_flags = au_mntflags(sb);
 	dlgt = !!au_test_dlgt(mnt_flags);
 	if (unlikely(dlgt))
@@ -238,29 +237,36 @@ int au_whtmp_ren(struct inode *dir, struct dentry *dentry, aufs_bindex_t bindex,
 	ndx.nfsmnt = au_nfsmnt(sb, bindex);
 	tmp_dentry = au_whtmp_lkup(h_parent, &h_dentry->d_name, &ndx);
 	err = PTR_ERR(tmp_dentry);
-	if (!IS_ERR(tmp_dentry)) {
-		/* under the same dir, no need to lock_rename() */
-		vfsub_args_init(&vargs, &ign, dlgt, 0);
-		AuDebugOn(!S_ISDIR(dentry->d_inode->i_mode));
-		if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY) && !noself))
-			vfsub_ign_hinode(&vargs, IN_MOVE_SELF,
-					 au_hi(dentry->d_inode, bindex));
-		err = vfsub_rename(h_dir, h_dentry, h_dir, tmp_dentry, &vargs);
-		AuTraceErr(err);
-		dput(tmp_dentry);
-	}
+	if (IS_ERR(tmp_dentry))
+		goto out;
 
+	/* under the same dir, no need to lock_rename() */
+	vfsub_args_init(&vargs, &ign, dlgt, 0);
+	AuDebugOn(!S_ISDIR(h_dentry->d_inode->i_mode));
+	vfsub_ign_hinode(&vargs, IN_MOVED_FROM | IN_MOVED_TO,
+			 au_hi(dir, bindex));
+	err = vfsub_rename(h_dir, h_dentry, h_dir, tmp_dentry, &vargs);
+	AuTraceErr(err);
+	dput(tmp_dentry);
+
+ out:
 	AuTraceErr(err);
 	return err;
 }
 
 /* ---------------------------------------------------------------------- */
 
-static int do_unlink_wh(struct inode *h_dir, struct dentry *wh_dentry,
-			struct inode *dir, int dlgt)
+static int do_unlink_wh(struct au_hinode *hdir, struct inode *h_dir,
+			struct dentry *wh_dentry, const int dlgt)
 {
+	int err;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
 
+	AuDebugOn(hdir && h_dir);
+	AuDebugOn(!hdir && !h_dir);
+	if (!h_dir)
+		h_dir = hdir->hi_inode;
 	LKTRTrace("hi%lu, wh %.*s\n", h_dir->i_ino, AuDLNPair(wh_dentry));
 	AuDebugOn(!wh_dentry->d_inode || !S_ISREG(wh_dentry->d_inode->i_mode));
 
@@ -268,24 +274,27 @@ static int do_unlink_wh(struct inode *h_dir, struct dentry *wh_dentry,
 	 * forces superio when the dir has a sticky bit.
 	 * this may be a violation of unix fs semantics.
 	 */
-	vfsub_args_init(&vargs, NULL, dlgt,
+	vfsub_args_init(&vargs, &ign, dlgt,
 			(h_dir->i_mode & S_ISVTX)
 			&& wh_dentry->d_inode->i_uid != current->fsuid);
-	return vfsub_unlink(h_dir, wh_dentry, &vargs);
+	vfsub_ign_hinode(&vargs, IN_DELETE, hdir);
+	err = vfsub_unlink(h_dir, wh_dentry, &vargs);
+	AuTraceErr(err);
+	return err;
 }
 
-int au_wh_unlink_dentry(struct inode *h_dir, struct dentry *wh_dentry,
-			struct dentry *dentry, struct inode *dir, int dlgt)
+int au_wh_unlink_dentry(struct au_hinode *hdir, struct dentry *wh_dentry,
+			struct dentry *dentry, int dlgt)
 {
 	int err;
 
-	LKTRTrace("hi%lu, wh %.*s, d %p\n", h_dir->i_ino,
-		  AuDLNPair(wh_dentry), dentry);
+	LKTRTrace("i%lu, wh %.*s, d %p\n",
+		  hdir->hi_inode->i_ino, AuDLNPair(wh_dentry), dentry);
 	AuDebugOn((dentry && au_dbwh(dentry) < 0)
 		  || !wh_dentry->d_inode
 		  || !S_ISREG(wh_dentry->d_inode->i_mode));
 
-	err = do_unlink_wh(h_dir, wh_dentry, dir, dlgt);
+	err = do_unlink_wh(hdir, /*h_dir*/NULL, wh_dentry, dlgt);
 	if (!err && dentry)
 		au_set_dbwh(dentry, -1);
 
@@ -294,25 +303,25 @@ int au_wh_unlink_dentry(struct inode *h_dir, struct dentry *wh_dentry,
 }
 
 static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh,
-			  struct inode *dir, struct au_ndx *ndx)
+			  struct au_ndx *ndx)
 {
 	int err;
-	struct inode *h_dir;
-	struct dentry *h_dentry;
+	struct dentry *wh_dentry;
 
 	LKTRTrace("%.*s/%.*s\n", AuDLNPair(h_parent), AuLNPair(wh));
-	h_dir = h_parent->d_inode;
 
 	/* au_test_h_perm() is already done */
-	h_dentry = au_lkup_one(wh->name, h_parent, wh->len, ndx);
-	if (!IS_ERR(h_dentry)) {
+	wh_dentry = au_lkup_one(wh->name, h_parent, wh->len, ndx);
+	if (IS_ERR(wh_dentry))
+		err = PTR_ERR(wh_dentry);
+	else {
 		err = 0;
-		if (h_dentry->d_inode && S_ISREG(h_dentry->d_inode->i_mode))
-			err = do_unlink_wh(h_dir, h_dentry, dir,
+		if (wh_dentry->d_inode && S_ISREG(wh_dentry->d_inode->i_mode))
+			err = do_unlink_wh(/*hdir*/NULL, h_parent->d_inode,
+					   wh_dentry,
 					   au_ftest_ndx(ndx->flags, DLGT));
-		dput(h_dentry);
-	} else
-		err = PTR_ERR(h_dentry);
+		dput(wh_dentry);
+	}
 
 	AuTraceErr(err);
 	return err;
@@ -320,35 +329,37 @@ static int unlink_wh_name(struct dentry *h_parent, struct qstr *wh,
 
 /* ---------------------------------------------------------------------- */
 
-static void clean_wh(struct inode *h_dir, struct dentry *wh)
+static void clean_wh(struct inode *h_dir, struct dentry *wh,
+		     struct au_hinode *hdir, struct vfsub_args *vargs)
 {
 	int err;
-	struct vfsub_args vargs;
 
 	AuTraceEnter();
 
 	if (wh->d_inode) {
-		vfsub_args_init(&vargs, NULL, 0, 0);
-		err = vfsub_unlink(h_dir, wh, &vargs);
+		vfsub_args_reinit(vargs);
+		vfsub_ign_hinode(vargs, IN_DELETE, hdir);
+		err = vfsub_unlink(h_dir, wh, vargs);
 		if (unlikely(err))
 			AuWarn("failed unlink %.*s (%d), ignored.\n",
 			       AuDLNPair(wh), err);
 	}
 }
 
-static void clean_plink(struct inode *h_dir, struct dentry *plink)
+static void au_whdir_clean(struct inode *h_dir, struct dentry *dentry,
+			   struct au_hinode *hdir, struct vfsub_args *vargs)
 {
 	int err;
-	struct vfsub_args vargs;
 
 	AuTraceEnter();
 
-	if (plink->d_inode) {
-		vfsub_args_init(&vargs, NULL, 0, 0);
-		err = vfsub_rmdir(h_dir, plink, &vargs);
+	if (dentry->d_inode) {
+		vfsub_args_reinit(vargs);
+		vfsub_ign_hinode(vargs, IN_DELETE, hdir);
+		err = vfsub_rmdir(h_dir, dentry, vargs);
 		if (unlikely(err))
 			AuWarn("failed rmdir %.*s (%d), ignored.\n",
-			       AuDLNPair(plink), err);
+			       AuDLNPair(dentry), err);
 	}
 }
 
@@ -359,20 +370,24 @@ static int test_linkable(struct inode *h_dir)
 	return -ENOSYS;
 }
 
-static int plink_dir(struct inode *h_dir, struct dentry *plink)
+/* todo: should this mkdir be done in /sbin/mount.aufs script? */
+static int au_whdir(struct inode *h_dir, struct dentry *dentry,
+		    struct au_hinode *hdir, struct vfsub_args *vargs)
 {
 	int err;
 
 	err = -EEXIST;
-	if (!plink->d_inode) {
+	if (!dentry->d_inode) {
 		int mode = S_IRWXU;
-		if (unlikely(au_test_nfs(plink->d_sb)))
+		if (unlikely(au_test_nfs(dentry->d_sb)))
 			mode |= S_IXUGO;
-		err = vfsub_mkdir(h_dir, plink, mode, /*dlgt*/0);
-	} else if (S_ISDIR(plink->d_inode->i_mode))
+		vfsub_args_reinit(vargs);
+		vfsub_ign_hinode(vargs, IN_CREATE, hdir);
+		err = vfsub_mkdir(h_dir, dentry, mode, vargs);
+	} else if (S_ISDIR(dentry->d_inode->i_mode))
 		err = 0;
 	else
-		AuErr("unknown %.*s exists\n", AuDLNPair(plink));
+		AuErr("unknown %.*s exists\n", AuDLNPair(dentry));
 
 	return err;
 }
@@ -381,19 +396,44 @@ static int plink_dir(struct inode *h_dir, struct dentry *plink)
  * initialize the whiteout base file/dir for @br.
  */
 int au_wh_init(struct dentry *h_root, struct au_branch *br,
-	       struct vfsmount *nfsmnt, struct super_block *sb)
+	       struct vfsmount *nfsmnt, struct super_block *sb,
+	       aufs_bindex_t bindex)
 {
-	int err;
-	struct dentry *wh, *plink;
+	int err, i;
 	struct inode *h_dir;
-	static struct qstr base_name[] = {
-		{
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
+	struct au_hinode *hdir;
+	struct au_wbr *wbr = br->br_wbr;
+	static const struct qstr base_name[] = {
+		[AuBrWh_BASE] = {
 			.name	= AUFS_WH_BASENAME,
 			.len	= sizeof(AUFS_WH_BASENAME) - 1
 		},
-		{
+		[AuBrWh_PLINK] = {
 			.name	= AUFS_WH_PLINKDIR,
 			.len	= sizeof(AUFS_WH_PLINKDIR) - 1
+		},
+		[AuBrWh_TMP] = {
+			.name	= AUFS_WH_TMPDIR,
+			.len	= sizeof(AUFS_WH_TMPDIR) - 1
+		}
+	};
+	struct {
+		const struct qstr *name;
+		struct dentry *dentry;
+	} base[] = {
+		[AuBrWh_BASE] = {
+			.name	= base_name + AuBrWh_BASE,
+			.dentry	= NULL
+		},
+		[AuBrWh_PLINK] = {
+			.name	= base_name + AuBrWh_PLINK,
+			.dentry	= NULL
+		},
+		[AuBrWh_TMP] = {
+			.name	= base_name + AuBrWh_TMP,
+			.dentry	= NULL
 		}
 	};
 	struct au_ndx ndx = {
@@ -402,57 +442,70 @@ int au_wh_init(struct dentry *h_root, struct au_branch *br,
 		.nd	= NULL,
 		/* .br	= NULL */
 	};
-	const int do_plink = au_opt_test(au_mntflags(sb), PLINK);
+	const unsigned int mnt_flags = au_mntflags(sb);
+	const int do_plink = au_opt_test(mnt_flags, PLINK);
+	const int do_hinotify = au_opt_test(mnt_flags, UDBA_INOTIFY);
 
 	LKTRTrace("nfsmnt %p\n", nfsmnt);
-	BrWhMustWriteLock(br);
+	WbrWhMustWriteLock(wbr);
 	SiMustWriteLock(sb);
 	h_dir = h_root->d_inode;
 
-	/* doubly whiteouted */
-	wh = au_wh_lkup(h_root, base_name + 0, &ndx);
-	err = PTR_ERR(wh);
-	if (IS_ERR(wh))
-		goto out;
-	AuDebugOn(br->br_wh && br->br_wh != wh);
-
-	plink = au_wh_lkup(h_root, base_name + 1, &ndx);
-	err = PTR_ERR(plink);
-	if (IS_ERR(plink))
-		goto out_dput_wh;
-	AuDebugOn(br->br_plink && br->br_plink != plink);
+	for (i = 0; i < AuBrWh_Last; i++) {
+		/* doubly whiteouted */
+		base[i].dentry = au_wh_lkup(h_root, (void *)base[i].name, &ndx);
+		err = PTR_ERR(base[i].dentry);
+		if (IS_ERR(base[i].dentry))
+			goto out;
+		AuDebugOn(wbr
+			  && wbr->wbr_wh[i]
+			  && wbr->wbr_wh[i] != base[i].dentry);
+	}
 
-	dput(br->br_wh);
-	dput(br->br_plink);
-	br->br_wh = NULL;
-	br->br_plink = NULL;
+	if (wbr)
+		for (i = 0; i < AuBrWh_Last; i++) {
+			dput(wbr->wbr_wh[i]);
+			wbr->wbr_wh[i] = NULL;
+		}
 
 	err = 0;
+	hdir = NULL;
+	if (unlikely(bindex >= 0 && do_hinotify))
+		hdir = au_hi(sb->s_root->d_inode, bindex);
+	vfsub_args_init(&vargs, &ign, au_test_dlgt(mnt_flags), 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);
+	case AuBrPerm_RR:
+	case AuBrPerm_RO:
+	case AuBrPerm_RRWH:
+	case AuBrPerm_ROWH:
+		clean_wh(h_dir, base[AuBrWh_BASE].dentry, hdir, &vargs);
+		au_whdir_clean(h_dir, base[AuBrWh_PLINK].dentry, hdir, &vargs);
+		au_whdir_clean(h_dir, base[AuBrWh_TMP].dentry, hdir, &vargs);
 		break;
 
-	case AuBr_RWNoLinkWH:
-		clean_wh(h_dir, wh);
+	case AuBrPerm_RWNoLinkWH:
+		clean_wh(h_dir, base[AuBrWh_BASE].dentry, hdir, &vargs);
 		if (do_plink) {
 			err = test_linkable(h_dir);
 			if (unlikely(err))
 				goto out_nolink;
 
-			err = plink_dir(h_dir, plink);
+			err = au_whdir(h_dir, base[AuBrWh_PLINK].dentry, hdir,
+				       &vargs);
 			if (unlikely(err))
 				goto out_err;
-			br->br_plink = dget(plink);
+			wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
 		} else
-			clean_plink(h_dir, plink);
+			au_whdir_clean(h_dir, base[AuBrWh_PLINK].dentry, hdir,
+				       &vargs);
+		err = au_whdir(h_dir, base[AuBrWh_TMP].dentry, hdir, &vargs);
+		if (unlikely(err))
+			goto out_err;
+		wbr->wbr_tmp = dget(base[AuBrWh_TMP].dentry);
 		break;
 
-	case AuBr_RW:
+	case AuBrPerm_RW:
 		/*
 		 * for the moment, aufs supports the branch filesystem
 		 * which does not support link(2).
@@ -466,46 +519,59 @@ int au_wh_init(struct dentry *h_root, struct au_branch *br,
 			goto out_nolink;
 
 		err = -EEXIST;
-		if (!wh->d_inode)
-			err = au_h_create(h_dir, wh, WH_MASK, /*dlgt*/0,
-					  /*nd*/NULL, nfsmnt);
-		else if (S_ISREG(wh->d_inode->i_mode))
+		/*
+		 * todo: should this create be done
+		 * in /sbin/mount.aufs script?
+		 */
+		if (!base[AuBrWh_BASE].dentry->d_inode) {
+			vfsub_args_reinit(&vargs);
+			vfsub_ign_hinode(&vargs, IN_CREATE, hdir);
+			err = au_h_create(h_dir, base[AuBrWh_BASE].dentry,
+					  WH_MASK, &vargs, /*nd*/NULL, nfsmnt);
+		}
+		else if (S_ISREG(base[AuBrWh_BASE].dentry->d_inode->i_mode))
 			err = 0;
 		else
 			AuErr("unknown %.*s/%.*s exists\n",
-			      AuDLNPair(h_root), AuDLNPair(wh));
+			      AuDLNPair(h_root),
+			      AuDLNPair(base[AuBrWh_BASE].dentry));
 		if (unlikely(err))
 			goto out_err;
 
 		if (do_plink) {
-			err = plink_dir(h_dir, plink);
+			err = au_whdir(h_dir, base[AuBrWh_PLINK].dentry, hdir,
+				       &vargs);
 			if (unlikely(err))
 				goto out_err;
-			br->br_plink = dget(plink);
+			wbr->wbr_plink = dget(base[AuBrWh_PLINK].dentry);
 		} else
-			clean_plink(h_dir, plink);
-		br->br_wh = dget(wh);
+			au_whdir_clean(h_dir, base[AuBrWh_PLINK].dentry, hdir,
+				       &vargs);
+		wbr->wbr_whbase = dget(base[AuBrWh_BASE].dentry);
+
+		err = au_whdir(h_dir, base[AuBrWh_TMP].dentry, hdir, &vargs);
+		if (unlikely(err))
+			goto out_err;
+		wbr->wbr_tmp = dget(base[AuBrWh_TMP].dentry);
 		break;
 
 	default:
 		BUG();
 	}
 
- out_dput:
-	dput(plink);
- out_dput_wh:
-	dput(wh);
  out:
+	for (i = 0; i < AuBrWh_Last; i++)
+		dput(base[i].dentry);
 	AuTraceErr(err);
 	return err;
  out_nolink:
 	AuErr("%.*s doesn't support link(2), use noplink and rw+nolwh\n",
 	      AuDLNPair(h_root));
-	goto out_dput;
+	goto out;
  out_err:
 	AuErr("an error(%d) on the writable branch %.*s(%s)\n",
 	      err, AuDLNPair(h_root), au_sbtype(h_root->d_sb));
-	goto out_dput;
+	goto out;
 }
 
 struct reinit_br_wh {
@@ -517,44 +583,59 @@ static void reinit_br_wh(void *arg)
 {
 	int err;
 	struct reinit_br_wh *a = arg;
+	struct au_wbr *wbr;
 	struct inode *h_dir, *dir;
 	struct dentry *h_root;
 	aufs_bindex_t bindex;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
 
 	AuTraceEnter();
-	AuDebugOn(!a->br->br_wh || !a->br->br_wh->d_inode || current->fsuid);
+	AuDebugOn(current->fsuid);
 
 	err = 0;
+	wbr = a->br->br_wbr;
 	/* big aufs lock */
-	si_write_lock(a->sb);
+	si_noflush_write_lock(a->sb);
 	if (unlikely(!au_br_writable(a->br->br_perm)))
 		goto out;
 	bindex = au_br_index(a->sb, a->br->br_id);
 	if (unlikely(bindex < 0))
 		goto out;
 
+	AuDebugOn(!wbr);
+	AuDebugOn(!wbr->wbr_whbase || !wbr->wbr_whbase->d_inode);
+
 	dir = a->sb->s_root->d_inode;
-	h_root = dget_parent(a->br->br_wh);
+	ii_read_lock_parent(dir);
+	h_root = dget_parent(wbr->wbr_whbase);
 	h_dir = h_root->d_inode;
 	AuDebugOn(!h_dir->i_op || !h_dir->i_op->link);
-	vfsub_args_init(&vargs, NULL, /*dlgt*/0, 0);
-	au_hdir_lock(h_dir, dir, bindex);
-	/* todo: revalidate h_wh? */
-	br_wh_write_lock(a->br);
-	err = vfsub_unlink(h_dir, a->br->br_wh, &vargs);
-	dput(a->br->br_wh);
-	a->br->br_wh = NULL;
+	mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
+	wbr_wh_write_lock(wbr);
+	if (!au_verify_parent(wbr->wbr_whbase, h_dir)) {
+		vfsub_args_init(&vargs, &ign, /*dlgt*/0, 0);
+		vfsub_ign_hinode(&vargs, IN_DELETE, au_hi(dir, bindex));
+		err = vfsub_unlink(h_dir, wbr->wbr_whbase, &vargs);
+	} else {
+		AuWarn("%.*s is moved, ignored\n", AuDLNPair(wbr->wbr_whbase));
+		err = 0;
+	}
+	dput(wbr->wbr_whbase);
+	wbr->wbr_whbase = NULL;
 	if (!err)
 		err = au_wh_init(h_root, a->br, au_do_nfsmnt(a->br->br_mnt),
-				 a->sb);
-	br_wh_write_unlock(a->br);
-	au_hdir_unlock(h_dir, dir, bindex);
+				 a->sb, bindex);
+	wbr_wh_write_unlock(wbr);
+	mutex_unlock(&h_dir->i_mutex);
 	dput(h_root);
+	ii_read_unlock(dir);
 
  out:
-	atomic_dec_return(&a->br->br_wh_running);
+	if (wbr)
+		atomic_dec_return(&wbr->wbr_wh_running);
 	au_br_put(a->br);
+	au_nwt_done(&au_sbi(a->sb)->si_nowait);
 	si_write_unlock(a->sb);
 	kfree(arg);
 	if (unlikely(err))
@@ -566,12 +647,15 @@ static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br)
 	int do_dec, wkq_err;
 	struct reinit_br_wh *arg;
 
+	AuTraceEnter();
+	AuDebugOn(!br->br_wbr);
+
 	do_dec = 1;
-	if (atomic_inc_return(&br->br_wh_running) != 1)
+	if (atomic_inc_return(&br->br_wbr->wbr_wh_running) != 1)
 		goto out;
 
 	/* ignore ENOMEM */
-	arg = kmalloc(sizeof(*arg), GFP_TEMPORARY);
+	arg = kmalloc(sizeof(*arg), GFP_NOFS);
 	if (arg) {
 		/*
 		 * dec(wh_running), kfree(arg) and au_br_put()
@@ -582,7 +666,7 @@ static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br)
 		au_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);
+			atomic_dec_return(&br->br_wbr->wbr_wh_running);
 			au_br_put(br);
 			kfree(arg);
 		}
@@ -591,44 +675,54 @@ static void kick_reinit_br_wh(struct super_block *sb, struct au_branch *br)
 
  out:
 	if (do_dec)
-		atomic_dec_return(&br->br_wh_running);
+		atomic_dec_return(&br->br_wbr->wbr_wh_running);
 }
 
 /*
  * create the whiteout @wh.
  */
-static int link_or_create_wh(struct dentry *wh, struct super_block *sb,
-			     aufs_bindex_t bindex, struct inode *dir)
+static int link_or_create_wh(struct super_block *sb, aufs_bindex_t bindex,
+			     struct dentry *wh, struct inode *dir)
 {
 	int err, dlgt;
 	struct au_branch *br;
+	struct au_wbr *wbr;
 	struct dentry *h_parent;
 	struct inode *h_dir;
+	struct au_hin_ignore ign;
+	struct vfsub_args vargs;
 
 	LKTRTrace("%.*s\n", AuDLNPair(wh));
-	SiMustReadLock(sb);
 	h_parent = wh->d_parent; /* dir inode is locked */
 	h_dir = h_parent->d_inode;
 	IMustLock(h_dir);
+	br = au_sbr(sb, bindex);
+	wbr = br->br_wbr;
+	AuDebugOn(!wbr);
 
 	dlgt = !!au_test_dlgt(au_mntflags(sb));
-	br = au_sbr(sb, bindex);
-	br_wh_read_lock(br);
-	if (br->br_wh) {
-		err = vfsub_link(br->br_wh, h_dir, wh, dlgt);
+	wbr_wh_read_lock(wbr);
+	if (wbr->wbr_whbase) {
+		vfsub_args_init(&vargs, &ign, dlgt, 0);
+		if (unlikely(dir))
+			vfsub_ign_hinode(&vargs, IN_CREATE, au_hi(dir, bindex));
+		err = vfsub_link(wbr->wbr_whbase, h_dir, wh, &vargs);
 		if (!err || err != -EMLINK)
 			goto out;
 
-		/* link count full. re-initialize br_wh. */
+		/* link count full. re-initialize br_whbase. */
 		kick_reinit_br_wh(sb, br);
 	}
 
 	/* return this error in this context */
-	err = au_h_create(h_dir, wh, WH_MASK, dlgt, /*nd*/NULL,
+	vfsub_args_init(&vargs, &ign, dlgt, 0);
+	if (unlikely(dir))
+		vfsub_ign_hinode(&vargs, IN_CREATE, au_hi(dir, bindex));
+	err = au_h_create(h_dir, wh, WH_MASK, &vargs, /*nd*/NULL,
 			  au_do_nfsmnt(br->br_mnt));
 
  out:
-	br_wh_read_unlock(br);
+	wbr_wh_read_unlock(wbr);
 	AuTraceErr(err);
 	return err;
 }
@@ -673,7 +767,7 @@ static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex,
 
 	if (au_ftest_diropq(flags, CREATE)) {
 		AuDebugOn(opq_dentry->d_inode);
-		err = link_or_create_wh(opq_dentry, sb, bindex,
+		err = link_or_create_wh(dentry->d_sb, bindex, opq_dentry,
 					dentry->d_inode);
 		if (!err) {
 			au_set_dbdiropq(dentry, bindex);
@@ -682,7 +776,8 @@ static struct dentry *do_diropq(struct dentry *dentry, aufs_bindex_t bindex,
 	} else {
 		AuDebugOn(/* !S_ISDIR(dentry->d_inode->i_mode)
 			   * ||  */!opq_dentry->d_inode);
-		err = do_unlink_wh(h_dir, opq_dentry, dentry->d_inode, dlgt);
+		err = do_unlink_wh(au_hi(dentry->d_inode, bindex),
+				   /*h_dir*/NULL, opq_dentry, dlgt);
 		if (!err)
 			au_set_dbdiropq(dentry, -1);
 	}
@@ -768,11 +863,11 @@ struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
 /*
  * link/create a whiteout for @dentry on @bindex.
  */
-struct dentry *au_wh_create(struct inode *dir, struct dentry *dentry,
-			    aufs_bindex_t bindex, struct dentry *h_parent,
-			    struct au_ndx *ndx)
+struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
+			    struct dentry *h_parent, struct au_ndx *ndx)
 {
 	struct dentry *wh_dentry;
+	struct inode *dir;
 	int err;
 	struct super_block *sb;
 
@@ -782,7 +877,9 @@ struct dentry *au_wh_create(struct inode *dir, struct dentry *dentry,
 	sb = dentry->d_sb;
 	wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, ndx);
 	if (!IS_ERR(wh_dentry) && !wh_dentry->d_inode) {
-		err = link_or_create_wh(wh_dentry, sb, bindex, dir);
+		dir = dentry->d_parent->d_inode; /* dir is locked */
+		IMustLock(dir);
+		err = link_or_create_wh(sb, bindex, wh_dentry, dir);
 		if (!err)
 			au_set_dbwh(dentry, bindex);
 		else {
@@ -798,22 +895,21 @@ struct dentry *au_wh_create(struct inode *dir, struct dentry *dentry,
 /* ---------------------------------------------------------------------- */
 
 /* Delete all whiteouts in this directory on branch bindex. */
-static int del_wh_children(struct au_nhash *whlist, struct dentry *h_parent,
-			   aufs_bindex_t bindex, struct inode *inode,
-			   struct au_ndx *ndx)
+static int del_wh_children(struct dentry *h_dentry, struct au_nhash *whlist,
+			   aufs_bindex_t bindex, struct au_ndx *ndx)
 {
 	int err, i;
 	struct qstr wh_name;
 	char *p;
-	struct inode *h_dir;
+	struct inode *h_inode;
 	struct hlist_head *head;
 	struct au_vdir_wh *tpos;
 	struct hlist_node *pos;
 	struct au_vdir_destr *str;
 
-	LKTRTrace("%.*s\n", AuDLNPair(h_parent));
-	h_dir = h_parent->d_inode;
-	AuDebugOn(IS_RDONLY(h_dir));
+	LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
+	h_inode = h_dentry->d_inode;
+	AuDebugOn(IS_RDONLY(h_inode));
 
 	err = -ENOMEM;
 	p = __getname();
@@ -834,8 +930,7 @@ static int del_wh_children(struct au_nhash *whlist, struct dentry *h_parent,
 			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(h_parent, &wh_name, inode,
-						     ndx);
+				err = unlink_wh_name(h_dentry, &wh_name, ndx);
 				if (!err)
 					continue;
 				break;
@@ -855,18 +950,16 @@ static int del_wh_children(struct au_nhash *whlist, struct dentry *h_parent,
 
 struct del_wh_children_args {
 	int *errp;
+	struct dentry *h_dentry;
 	struct au_nhash *whlist;
-	struct dentry *h_parent;
 	aufs_bindex_t bindex;
-	struct inode *inode;
 	struct au_ndx *ndx;
 };
 
 static void call_del_wh_children(void *args)
 {
 	struct del_wh_children_args *a = args;
-	*a->errp = del_wh_children(a->whlist, a->h_parent, a->bindex,
-				   a->inode, a->ndx);
+	*a->errp = del_wh_children(a->h_dentry, a->whlist, a->bindex, a->ndx);
 }
 
 /* ---------------------------------------------------------------------- */
@@ -875,12 +968,11 @@ static void call_del_wh_children(void *args)
  * rmdir the whiteouted temporary named dir @h_dentry.
  * @whlist: whiteouted children.
  */
-int au_whtmp_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
-		   aufs_bindex_t bindex, struct inode *dir, struct inode *inode,
-		   int noself)
+int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
+		   struct dentry *wh_dentry, struct au_nhash *whlist)
 {
 	int err, dlgt;
-	struct inode *h_inode, *h_dir;
+	struct inode *wh_inode, *h_dir;
 	struct super_block *sb;
 	unsigned int mnt_flags;
 	struct au_hin_ignore ign;
@@ -891,39 +983,37 @@ int au_whtmp_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
 		/* .br	= NULL */
 	};
 
-	LKTRTrace("hd %.*s, b%d, i%lu\n",
-		  AuDLNPair(h_dentry), bindex, dir->i_ino);
-	IMustLock(dir);
+	LKTRTrace("i%lu, %.*s, b%d\n",
+		  dir->i_ino, AuDLNPair(wh_dentry), bindex);
+	/* IMustLock(dir); */
 	IiMustAnyLock(dir);
-	h_dir = h_dentry->d_parent->d_inode; /* dir inode is locked */
+	h_dir = wh_dentry->d_parent->d_inode; /* dir inode is locked */
 	IMustLock(h_dir);
 
-	sb = inode->i_sb;
+	sb = dir->i_sb;
 	mnt_flags = au_mntflags(sb);
 	dlgt = !!au_test_dlgt(mnt_flags);
 	if (unlikely(dlgt))
 		au_fset_ndx(ndx.flags, DLGT);
 	ndx.nfsmnt = au_nfsmnt(sb, bindex);
-	h_inode = h_dentry->d_inode;
-	AuDebugOn(h_inode != au_h_iptr(inode, bindex));
-	au_hdir2_lock(h_inode, inode, bindex);
+	wh_inode = wh_dentry->d_inode;
+	mutex_lock_nested(&wh_inode->i_mutex, AuLsc_I_CHILD);
 
 	/*
 	 * someone else might change some whiteouts while we were sleeping.
 	 * it means this whlist may have an obsoleted entry.
 	 */
-	if (!au_test_h_perm_sio(h_inode, MAY_EXEC | MAY_WRITE, dlgt))
-		err = del_wh_children(whlist, h_dentry, bindex, inode, &ndx);
+	if (!au_test_h_perm_sio(wh_inode, MAY_EXEC | MAY_WRITE, dlgt))
+		err = del_wh_children(wh_dentry, whlist, bindex, &ndx);
 	else {
 		int wkq_err;
 		/* ugly */
 		unsigned int flags = ndx.flags;
 		struct del_wh_children_args args = {
 			.errp		= &err,
+			.h_dentry	= wh_dentry,
 			.whlist		= whlist,
-			.h_parent	= h_dentry,
 			.bindex		= bindex,
-			.inode		= inode,
 			.ndx		= &ndx
 		};
 
@@ -933,14 +1023,12 @@ int au_whtmp_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
 			err = wkq_err;
 		ndx.flags = flags;
 	}
-	au_hdir_unlock(h_inode, inode, bindex);
+	mutex_unlock(&wh_inode->i_mutex);
 
 	if (!err) {
 		vfsub_args_init(&vargs, &ign, dlgt, 0);
-		if (unlikely(au_opt_test(mnt_flags, UDBA_INOTIFY) && !noself))
-			vfsub_ign_hinode(&vargs, IN_DELETE_SELF,
-					 au_hi(inode, bindex));
-		err = vfsub_rmdir(h_dir, h_dentry, &vargs);
+		vfsub_ign_hinode(&vargs, IN_DELETE, au_hi(dir, bindex));
+		err = vfsub_rmdir(h_dir, wh_dentry, &vargs);
 		/* d_drop(h_dentry); */
 	}
 
@@ -953,76 +1041,78 @@ int au_whtmp_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
 		return 0; /* success */
 	}
 
-	AuWarn("failed removing %.*s(%d), ignored\n", AuDLNPair(h_dentry), err);
+	AuWarn("failed removing %.*s(%d), ignored\n",
+	       AuDLNPair(wh_dentry), err);
 	return err;
 }
 
 static void au_whtmp_rmdir_free_args(struct au_whtmp_rmdir_args *args)
 {
-	dput(args->h_dentry);
 	au_nhash_fin(&args->whlist);
-	iput(args->inode);
-	mutex_unlock(&args->dir->i_mutex);
+	dput(args->wh_dentry);
 	iput(args->dir);
 	kfree(args);
 }
 
-static void do_rmdir_whtmp(void *args)
+static void call_rmdir_whtmp(void *args)
 {
 	int err;
 	struct au_whtmp_rmdir_args *a = args;
 	struct super_block *sb;
+	struct dentry *h_parent;
+	struct inode *h_dir;
 
 	LKTRTrace("%.*s, b%d, dir i%lu\n",
-		  AuDLNPair(a->h_dentry), a->bindex, a->dir->i_ino);
+		  AuDLNPair(a->wh_dentry), a->bindex, a->dir->i_ino);
 
-	mutex_lock(&a->dir->i_mutex);
+	/* rmdir by nfsd may cause deadlock with this i_mutex */
+	/* mutex_lock(&a->dir->i_mutex); */
 	sb = a->dir->i_sb;
 	si_noflush_read_lock(sb);
 	err = au_test_ro(sb, a->bindex, NULL);
-	if (!err) {
-		struct dentry *h_parent = dget_parent(a->h_dentry);
-		struct inode *h_dir = h_parent->d_inode;
-
-		ii_write_lock_child(a->inode);
-		ii_write_lock_parent(a->dir);
-		au_hdir_lock(h_dir, a->dir, a->bindex);
-		/* todo: revalidate h_dentry? */
-		err = au_whtmp_rmdir(a->h_dentry, &a->whlist, a->bindex,
-				     a->dir, a->inode, a->noself);
-		au_hdir_unlock(h_dir, a->dir, a->bindex);
-		ii_write_unlock(a->dir);
-		ii_write_unlock(a->inode);
-		dput(h_parent);
-	}
+	if (unlikely(err))
+		goto out;
+
+	err = -EIO;
+	ii_write_lock_parent(a->dir);
+	h_parent = dget_parent(a->wh_dentry);
+	h_dir = h_parent->d_inode;
+	mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
+	if (!au_verify_parent(a->wh_dentry, h_dir))
+		err = au_whtmp_rmdir(a->dir, a->bindex, a->wh_dentry,
+				     &a->whlist);
+	mutex_unlock(&h_dir->i_mutex);
+	dput(h_parent);
+	ii_write_unlock(a->dir);
+
+ out:
+	/* mutex_unlock(&a->dir->i_mutex); */
+	au_nwt_done(&au_sbi(sb)->si_nowait);
 	si_read_unlock(sb);
 	au_whtmp_rmdir_free_args(a);
 	if (unlikely(err))
 		AuIOErr("err %d\n", err);
 }
 
-void au_whtmp_kick_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
-			 aufs_bindex_t bindex, struct inode *dir,
-			 struct inode *inode, int noself,
+void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
+			 struct dentry *wh_dentry, struct au_nhash *whlist,
 			 struct au_whtmp_rmdir_args *args)
 {
 	int wkq_err;
 
-	LKTRTrace("%.*s\n", AuDLNPair(h_dentry));
+	LKTRTrace("%.*s\n", AuDLNPair(wh_dentry));
 	IMustLock(dir);
 
 	/* all post-process will be done in do_rmdir_whtmp(). */
-	args->h_dentry = dget(h_dentry);
+	args->dir = au_igrab(dir);
+	args->bindex = bindex;
+	args->wh_dentry = dget(wh_dentry);
 	au_nhash_init(&args->whlist);
 	au_nhash_move(&args->whlist, whlist);
-	args->bindex = bindex;
-	args->dir = igrab(dir);
-	args->inode = igrab(inode);
-	args->noself = noself;
-	wkq_err = au_wkq_nowait(do_rmdir_whtmp, args, dir->i_sb, /*dlgt*/0);
+	wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, dir->i_sb, /*dlgt*/0);
 	if (unlikely(wkq_err)) {
 		AuWarn("rmdir error %.*s (%d), ignored\n",
-		       AuDLNPair(h_dentry), wkq_err);
+		       AuDLNPair(wh_dentry), wkq_err);
 		au_whtmp_rmdir_free_args(args);
 	}
 }
diff --git a/ubuntu/aufs/whout.h b/ubuntu/aufs/whout.h
index 0ce4395..dad943f 100644
--- a/ubuntu/aufs/whout.h
+++ b/ubuntu/aufs/whout.h
@@ -19,7 +19,7 @@
 /*
  * whiteout for logical deletion and opaque directory
  *
- * $Id: whout.h,v 1.2 2008/04/21 02:00:37 sfjro Exp $
+ * $Id: whout.h,v 1.3 2008/06/30 03:57:35 sfjro Exp $
  */
 
 #ifndef __AUFS_WHOUT_H__
@@ -28,7 +28,7 @@
 #ifdef __KERNEL__
 
 #include <linux/fs.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 #include "dir.h"
 #include "opts.h"
 #include "super.h"
@@ -43,14 +43,15 @@ int au_diropq_test(struct dentry *h_dentry, struct au_ndx *ndx);
 
 struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct qstr *prefix,
 			     struct au_ndx *ndx);
-int au_whtmp_ren(struct inode *dir, struct dentry *dentry, aufs_bindex_t bindex,
-		 int noself);
-int au_wh_unlink_dentry(struct inode *h_dir, struct dentry *wh_dentry,
-			struct dentry *dentry, struct inode *dir, int dlgt);
+int au_whtmp_ren(struct inode *dir, aufs_bindex_t bindex,
+		 struct dentry *h_dentry);
+int au_wh_unlink_dentry(struct au_hinode *dir, struct dentry *wh_dentry,
+			struct dentry *dentry, int dlgt);
 
 struct au_branch;
 int au_wh_init(struct dentry *h_parent, struct au_branch *br,
-	       struct vfsmount *nfsmnt, struct super_block *sb);
+	       struct vfsmount *nfsmnt, struct super_block *sb,
+	       aufs_bindex_t bindex);
 
 /* diropq flags */
 #define AuDiropq_CREATE	1
@@ -68,26 +69,22 @@ struct dentry *au_diropq_sio(struct dentry *dentry, aufs_bindex_t bindex,
 
 struct dentry *au_wh_lkup(struct dentry *h_parent, struct qstr *base_name,
 			  struct au_ndx *ndx);
-struct dentry *au_wh_create(struct inode *dir, struct dentry *dentry,
-			    aufs_bindex_t bindex, struct dentry *h_parent,
-			    struct au_ndx *ndx);
+struct dentry *au_wh_create(struct dentry *dentry, aufs_bindex_t bindex,
+			    struct dentry *h_parent, struct au_ndx *ndx);
 
 /* real rmdir the whiteout-ed dir */
 struct au_whtmp_rmdir_args {
-	struct dentry *h_dentry;
-	struct au_nhash whlist;
+	struct inode *dir;
 	aufs_bindex_t bindex;
-	struct inode *dir, *inode;
-	int noself;
+	struct dentry *wh_dentry;
+	struct au_nhash whlist;
 };
 
 struct au_nhash;
-int au_whtmp_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
-		   aufs_bindex_t bindex, struct inode *dir, struct inode *inode,
-		   int noself);
-void au_whtmp_kick_rmdir(struct dentry *h_dentry, struct au_nhash *whlist,
-			 aufs_bindex_t bindex, struct inode *dir,
-			 struct inode *inode, int noself,
+int au_whtmp_rmdir(struct inode *dir, aufs_bindex_t bindex,
+		   struct dentry *wh_dentry, struct au_nhash *whlist);
+void au_whtmp_kick_rmdir(struct inode *dir, aufs_bindex_t bindex,
+			 struct dentry *wh_dentry, struct au_nhash *whlist,
 			 struct au_whtmp_rmdir_args *args);
 
 /* ---------------------------------------------------------------------- */
diff --git a/ubuntu/aufs/wkq.c b/ubuntu/aufs/wkq.c
index 2ba6bec..31b0c11 100644
--- a/ubuntu/aufs/wkq.c
+++ b/ubuntu/aufs/wkq.c
@@ -19,7 +19,7 @@
 /*
  * workqueue for asynchronous/super-io/delegated operations
  *
- * $Id: wkq.c,v 1.6 2008/06/02 02:35:50 sfjro Exp $
+ * $Id: wkq.c,v 1.13 2008/09/15 03:16:36 sfjro Exp $
  */
 
 #include <linux/module.h>
@@ -134,6 +134,7 @@ static void update_busy(struct au_wkq *wkq, struct au_wkinfo *wkinfo)
 static int enqueue(struct au_wkq *wkq, struct au_wkinfo *wkinfo)
 {
 	AuTraceEnter();
+
 	wkinfo->busyp = &wkq->busy;
 	update_busy(wkq, wkinfo);
 	if (au_ftest_wkq(wkinfo->flags, WAIT))
@@ -195,32 +196,75 @@ static void wkq_func(struct work_struct *wk)
 	}
 }
 
+#if defined(CONFIG_4KSTACKS) || defined(Test4KSTACKS)
+#define AuWkqCompDeclare(name)	struct completion *comp = NULL
+
+static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp)
+{
+	*comp = kmalloc(sizeof(**comp), GFP_NOFS);
+	if (*comp) {
+		init_completion(*comp);
+		wkinfo->comp = *comp;
+		return 0;
+	}
+	return -ENOMEM;
+}
+
+static void au_wkq_comp_free(struct completion *comp)
+{
+	kfree(comp);
+}
+
+#else
+
+#define AuWkqCompDeclare(name) \
+	DECLARE_COMPLETION_ONSTACK(_ ## name); \
+	struct completion *comp = &_ ## name
+
+static int au_wkq_comp_alloc(struct au_wkinfo *wkinfo, struct completion **comp)
+{
+	wkinfo->comp = *comp;
+	return 0;
+}
+
+static void au_wkq_comp_free(struct completion *comp)
+{
+	/* empty */
+}
+#endif /* 4KSTACKS */
+
+/* todo: break to three funcs */
 int au_wkq_run(au_wkq_func_t func, void *args, struct super_block *sb,
 	       unsigned int flags)
 {
-	int err, do_wait;
-	DECLARE_COMPLETION_ONSTACK(comp);
+	int err;
+	AuWkqCompDeclare(comp);
 	struct au_wkinfo _wkinfo = {
 		.flags	= flags,
 		.func	= func,
-		.args	= args,
-		.comp	= &comp
+		.args	= args
 	}, *wkinfo = &_wkinfo;
+	const unsigned char do_wait = au_ftest_wkq(flags, WAIT);
 
 	LKTRTrace("0x%x\n", flags);
-	/* in dlgt mode, inotify will be fired from aufsd */
-	AuDebugOn(au_ftest_wkq(flags, DLGT) && au_test_wkq(current));
+#if 1 /* tmp debug */
+	if (au_test_wkq(current))
+		au_dbg_blocked();
+#endif
+	AuDebugOn(au_test_wkq(current));
 
-	err = 0;
-	do_wait = au_ftest_wkq(flags, WAIT);
-	if (unlikely(!do_wait)) {
+	if (do_wait) {
+		err = au_wkq_comp_alloc(wkinfo, &comp);
+		if (unlikely(err))
+			goto out;
+	} else {
 		AuDebugOn(!sb);
 		/*
 		 * wkq_func() must free this wkinfo.
 		 * it highly depends upon the implementation of workqueue.
 		 */
 		err = -ENOMEM;
-		wkinfo = kmalloc(sizeof(*wkinfo), GFP_TEMPORARY);
+		wkinfo = kmalloc(sizeof(*wkinfo), GFP_NOFS);
 		if (unlikely(!wkinfo))
 			goto out;
 
@@ -237,14 +281,34 @@ int au_wkq_run(au_wkq_func_t func, void *args, struct super_block *sb,
 	INIT_WORK(&wkinfo->wk, wkq_func);
 	dlgt_cred_store(flags, wkinfo);
 	do_wkq(wkinfo);
-	if (do_wait)
+	if (do_wait) {
 		/* no timeout, no interrupt */
 		wait_for_completion(wkinfo->comp);
+		au_wkq_comp_free(comp);
+	}
  out:
 	AuTraceErr(err);
 	return err;
 }
 
+int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb,
+		  int dlgt)
+{
+	int err;
+	unsigned int flags = !AuWkq_WAIT;
+
+	AuTraceEnter();
+
+	if (unlikely(dlgt))
+		au_fset_wkq(flags, DLGT);
+	atomic_inc_return(&au_sbi(sb)->si_nowait.nw_len);
+	err = au_wkq_run(func, args, sb, flags);
+	if (unlikely(err))
+		atomic_dec_return(&au_sbi(sb)->si_nowait.nw_len);
+
+	return err;
+}
+
 /* ---------------------------------------------------------------------- */
 
 void au_wkq_fin(void)
@@ -268,7 +332,7 @@ int __init au_wkq_init(void)
 
 	/* '+1' is for accounting  of nowait queue */
 	err = -ENOMEM;
-	au_wkq = kcalloc(aufs_nwkq + 1, sizeof(*au_wkq), GFP_KERNEL);
+	au_wkq = kcalloc(aufs_nwkq + 1, sizeof(*au_wkq), GFP_NOFS);
 	if (unlikely(!au_wkq))
 		goto out;
 
@@ -277,7 +341,7 @@ int __init au_wkq_init(void)
 		au_wkq[i].q = create_singlethread_workqueue(AUFS_WKQ_NAME);
 		if (au_wkq[i].q && !IS_ERR(au_wkq[i].q)) {
 			atomic_set(&au_wkq[i].busy, 0);
-			au_wkq[i].max_busy = 0;
+			au_wkq_max_busy_init(au_wkq + i);
 			continue;
 		}
 
@@ -289,7 +353,7 @@ int __init au_wkq_init(void)
 	/* nowait accounting */
 	nowaitq = au_wkq + aufs_nwkq;
 	atomic_set(&nowaitq->busy, 0);
-	nowaitq->max_busy = 0;
+	au_wkq_max_busy_init(nowaitq);
 	nowaitq->q = NULL;
 	/* smp_mb(); */ /* atomic_set */
 
diff --git a/ubuntu/aufs/wkq.h b/ubuntu/aufs/wkq.h
index f945689..b2a995c 100644
--- a/ubuntu/aufs/wkq.h
+++ b/ubuntu/aufs/wkq.h
@@ -19,7 +19,7 @@
 /*
  * workqueue for asynchronous/super-io/delegated operations
  *
- * $Id: wkq.h,v 1.4 2008/05/26 04:04:27 sfjro Exp $
+ * $Id: wkq.h,v 1.7 2008/09/15 03:16:36 sfjro Exp $
  */
 
 #ifndef __AUFS_WKQ_H__
@@ -30,7 +30,7 @@
 #include <linux/fs.h>
 #include <linux/sched.h>
 #include <linux/workqueue.h>
-#include <linux/aufs_types.h>
+#include <linux/aufs_type.h>
 
 /* ---------------------------------------------------------------------- */
 
@@ -38,23 +38,21 @@
 struct au_wkq {
 	struct workqueue_struct	*q;
 
-	/* accounting */
+	/* balancing */
 	atomic_t		busy;
-	unsigned int		max_busy; /* todo: STAT only */
+
+	/* accounting */
+#ifdef CONFIG_AUFS_STAT
+	unsigned int		max_busy;
+#endif
 };
 
 /*
  * in the next operation, wait for the 'nowait' tasks in system-wide workqueue
  */
 struct au_nowait_tasks {
-#ifdef CONFIG_AUFS_HINOTIFY
-	/*
-	 * currently, the 'nowait' task which should be waited in the next
-	 * operation is only hinotify.
-	 */
 	atomic_t		nw_len;
 	wait_queue_head_t	nw_wq;
-#endif
 };
 
 /* ---------------------------------------------------------------------- */
@@ -75,6 +73,8 @@ typedef void (*au_wkq_func_t)(void *args);
 
 int au_wkq_run(au_wkq_func_t func, void *args, struct super_block *sb,
 	       unsigned int flags);
+int au_wkq_nowait(au_wkq_func_t func, void *args, struct super_block *sb,
+		  int dlgt);
 int __init au_wkq_init(void);
 void au_wkq_fin(void);
 
@@ -98,16 +98,13 @@ static inline int au_wkq_wait(au_wkq_func_t func, void *args, int dlgt)
 	return au_wkq_run(func, args, /*sb*/NULL, flags);
 }
 
-static inline int au_wkq_nowait(au_wkq_func_t func, void *args,
-				struct super_block *sb, int dlgt)
+static inline void au_wkq_max_busy_init(struct au_wkq *wkq)
 {
-	unsigned int flags = !AuWkq_WAIT;
-	if (unlikely(dlgt))
-		au_fset_wkq(flags, DLGT);
-	return au_wkq_run(func, args, sb, flags);
+#ifdef CONFIG_AUFS_STAT
+	wkq->max_busy = 0;
+#endif
 }
 
-#ifdef CONFIG_AUFS_HINOTIFY
 /* todo: memory barrier? */
 static inline void au_nwt_init(struct au_nowait_tasks *nwt)
 {
@@ -116,14 +113,14 @@ static inline void au_nwt_init(struct au_nowait_tasks *nwt)
 	init_waitqueue_head(&nwt->nw_wq);
 }
 
-static inline int au_nwt_inc(struct au_nowait_tasks *nwt)
+/* todo: make it void */
+static inline int au_nwt_done(struct au_nowait_tasks *nwt)
 {
-	return atomic_inc_return(&nwt->nw_len);
-}
+	int ret;
 
-static inline int au_nwt_dec(struct au_nowait_tasks *nwt)
-{
-	int ret = atomic_dec_return(&nwt->nw_len);
+	AuTraceEnter();
+
+	ret = atomic_dec_return(&nwt->nw_len);
 	if (!ret)
 		wake_up_all(&nwt->nw_wq);
 	return ret;
@@ -134,27 +131,6 @@ static inline int au_nwt_flush(struct au_nowait_tasks *nwt)
 	wait_event(nwt->nw_wq, !atomic_read(&nwt->nw_len));
 	return 0;
 }
-#else
-static inline void au_nwt_init(struct au_nowait_tasks *nwt)
-{
-	/* nothing */
-}
-
-static inline int au_nwt_inc(struct au_nowait_tasks *nwt)
-{
-	return 0;
-}
-
-static inline int au_nwt_dec(struct au_nowait_tasks *nwt)
-{
-	return 0;
-}
-
-static inline int au_nwt_flush(struct au_nowait_tasks *nwt)
-{
-	return 0;
-}
-#endif /* CONFIG_AUFS_HINOTIFY */
 
 #endif /* __KERNEL__ */
 #endif /* __AUFS_WKQ_H__ */
diff --git a/ubuntu/aufs/xino.c b/ubuntu/aufs/xino.c
index df5a72b..beb57a8 100644
--- a/ubuntu/aufs/xino.c
+++ b/ubuntu/aufs/xino.c
@@ -19,7 +19,7 @@
 /*
  * external inode number translation table and bitmap
  *
- * $Id: xino.c,v 1.8 2008/06/02 02:36:59 sfjro Exp $
+ * $Id: xino.c,v 1.16 2008/09/15 03:16:30 sfjro Exp $
  */
 
 #include <linux/uaccess.h>
@@ -27,8 +27,8 @@
 
 /* ---------------------------------------------------------------------- */
 
-static ssize_t xino_fread(au_readf_t func, struct file *file, void *buf,
-			  size_t size, loff_t *pos)
+ssize_t xino_fread(au_readf_t func, struct file *file, void *buf, size_t size,
+		   loff_t *pos)
 {
 	ssize_t err;
 	mm_segment_t oldfs;
@@ -99,8 +99,8 @@ static void call_do_xino_fwrite(void *args)
 	*a->errp = do_xino_fwrite(a->func, a->file, a->buf, a->size, a->pos);
 }
 
-static ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf,
-			   size_t size, loff_t *pos)
+ssize_t xino_fwrite(au_writef_t func, struct file *file, void *buf, size_t size,
+		    loff_t *pos)
 {
 	ssize_t err;
 
@@ -146,29 +146,37 @@ static void xino_do_trunc(void *_args)
 	aufs_bindex_t bindex;
 	int err;
 	struct file *file;
+	struct inode *dir;
+	struct au_sbinfo *sbinfo;
+	struct kobject *kobj;
 
 	err = 0;
 	sb = args->sb;
+	dir = sb->s_root->d_inode;
 	si_noflush_write_lock(sb);
+	ii_read_lock_parent(dir);
 	bindex = au_br_index(sb, args->br->br_id);
 	AuDebugOn(bindex < 0);
 	err = au_xino_trunc(sb, bindex);
 	if (unlikely(err))
 		goto out;
 
-	file = args->br->br_xino;
+	file = args->br->br_xino.xi_file;
 	au_update_fuse_h_inode(args->br->br_mnt, file->f_dentry); /*ignore*/
 	if (file->f_dentry->d_inode->i_blocks >= args->br->br_xino_upper)
 		args->br->br_xino_upper += AUFS_XINO_TRUNC_STEP;
 
  out:
-	si_write_unlock(sb);
+	ii_read_unlock(dir);
 	if (unlikely(err))
 		AuWarn("err b%d, (%d)\n", bindex, err);
-	au_nwt_dec(&au_sbi(sb)->si_nowait);
 	atomic_dec_return(&args->br->br_xino_running);
 	au_br_put(args->br);
-	kobject_put(&au_sbi(sb)->si_kobj);
+	sbinfo = au_sbi(sb);
+	kobj = &sbinfo->si_kobj;
+	au_nwt_done(&sbinfo->si_nowait);
+	si_write_unlock(sb);
+	kobject_put(kobj);
 	kfree(args);
 }
 
@@ -176,7 +184,7 @@ static void xino_try_trunc(struct super_block *sb, struct au_branch *br)
 {
 	struct xino_do_trunc_args *args;
 	struct au_sbinfo *sbinfo;
-	struct file *file = br->br_xino;
+	struct file *file = br->br_xino.xi_file;
 	int wkq_err;
 
 	au_update_fuse_h_inode(br->br_mnt, file->f_dentry); /*ignore*/
@@ -186,7 +194,7 @@ static void xino_try_trunc(struct super_block *sb, struct au_branch *br)
 		goto out;
 
 	/* lock and kfree() will be called in trunc_xino() */
-	args = kmalloc(sizeof(*args), GFP_TEMPORARY);
+	args = kmalloc(sizeof(*args), GFP_NOFS);
 	if (unlikely(!args)) {
 		AuErr1("no memory\n");
 		goto out_args;
@@ -197,13 +205,11 @@ static void xino_try_trunc(struct super_block *sb, struct au_branch *br)
 	au_br_get(br);
 	args->sb = sb;
 	args->br = br;
-	au_nwt_inc(&sbinfo->si_nowait);
 	wkq_err = au_wkq_nowait(xino_do_trunc, args, sb, /*dlgt*/0);
 	if (!wkq_err)
 		return; /* success */
 
 	AuErr("wkq %d\n", wkq_err);
-	au_nwt_dec(&sbinfo->si_nowait);
 	au_br_put(br);
 	kobject_put(&sbinfo->si_kobj);
 
@@ -215,8 +221,6 @@ static void xino_try_trunc(struct super_block *sb, struct au_branch *br)
 
 /* ---------------------------------------------------------------------- */
 
-#define Au_LOFF_MAX	((loff_t)LLONG_MAX)
-
 static int au_xino_do_write(au_writef_t write, struct file *file,
 			    ino_t h_ino, struct au_xino_entry *xinoe)
 {
@@ -227,7 +231,7 @@ static int au_xino_do_write(au_writef_t write, struct file *file,
 
 	pos = h_ino;
 	if (unlikely(Au_LOFF_MAX / sizeof(*xinoe) - 1 < pos)) {
-		AuIOErr1("too large hi%lu\n", h_ino);
+		AuIOErr1("too large hi%lu\n", (unsigned long)h_ino);
 		return -EFBIG;
 	}
 	pos *= sizeof(*xinoe);
@@ -252,7 +256,8 @@ int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 	struct au_branch *br;
 	unsigned int mnt_flags;
 
-	LKTRTrace("b%d, hi%lu, i%lu\n", bindex, h_ino, xinoe->ino);
+	LKTRTrace("b%d, hi%lu, i%lu\n",
+		  bindex, (unsigned long)h_ino, (unsigned long)xinoe->ino);
 	BUILD_BUG_ON(sizeof(long long) != sizeof(Au_LOFF_MAX)
 		     || ((loff_t)-1) > 0);
 
@@ -261,7 +266,7 @@ int au_xino_write(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 		return 0;
 
 	br = au_sbr(sb, bindex);
-	file = br->br_xino;
+	file = br->br_xino.xi_file;
 	AuDebugOn(!file);
 
 	err = au_xino_do_write(au_sbi(sb)->si_xwrite, file, h_ino, xinoe);
@@ -282,6 +287,7 @@ static const int page_bits = (int)PAGE_SIZE * BITS_PER_BYTE;
 static ino_t xib_calc_ino(unsigned long pindex, int bit)
 {
 	ino_t ino;
+
 	AuDebugOn(bit < 0 || page_bits <= bit);
 	ino = AUFS_FIRST_INO + pindex * page_bits + bit;
 	return ino;
@@ -355,7 +361,8 @@ int au_xino_write0(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 		.ino	= 0
 	};
 
-	LKTRTrace("b%d, hi%lu, i%lu\n", bindex, h_ino, ino);
+	LKTRTrace("b%d, hi%lu, i%lu\n",
+		  bindex, (unsigned long)h_ino, (unsigned long)ino);
 
 	if (unlikely(!au_opt_test_xino(au_mntflags(sb))))
 		return 0;
@@ -431,7 +438,7 @@ ino_t au_xino_new_ino(struct super_block *sb)
 	pindex = sbinfo->si_xib_last_pindex;
 	mutex_unlock(&sbinfo->si_xib_mtx);
 	ino = xib_calc_ino(pindex, free_bit);
-	LKTRTrace("i%lu\n", ino);
+	LKTRTrace("i%lu\n", (unsigned long)ino);
 	return ino;
  out_err:
 	mutex_unlock(&sbinfo->si_xib_mtx);
@@ -453,22 +460,22 @@ int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 	ssize_t sz;
 	struct au_sbinfo *sbinfo;
 
-	LKTRTrace("b%d, hi%lu\n", bindex, h_ino);
+	LKTRTrace("b%d, hi%lu\n", bindex, (unsigned long)h_ino);
 
+	xinoe->ino = 0;
 	if (unlikely(!au_opt_test_xino(au_mntflags(sb))))
 		return 0; /* no ino */
 
 	err = 0;
-	xinoe->ino = 0;
 	sbinfo = au_sbi(sb);
 	pos = h_ino;
 	if (unlikely(Au_LOFF_MAX / sizeof(*xinoe) - 1 < pos)) {
-		AuIOErr1("too large hi%lu\n", h_ino);
+		AuIOErr1("too large hi%lu\n", (unsigned long)h_ino);
 		return -EFBIG;
 	}
 	pos *= sizeof(*xinoe);
 
-	file = au_sbr(sb, bindex)->br_xino;
+	file = au_sbr(sb, bindex)->br_xino.xi_file;
 	AuDebugOn(!file);
 	if (i_size_read(file->f_dentry->d_inode) < pos + sizeof(*xinoe))
 		return 0; /* no ino */
@@ -489,8 +496,7 @@ int au_xino_read(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
 
 /* ---------------------------------------------------------------------- */
 
-struct file *au_xino_create(struct super_block *sb, char *fname, int silent,
-			    struct dentry *parent)
+struct file *au_xino_create(struct super_block *sb, char *fname, int silent)
 {
 	struct file *file;
 	int err;
@@ -500,7 +506,12 @@ struct file *au_xino_create(struct super_block *sb, char *fname, int silent,
 
 	LKTRTrace("%s\n", fname);
 
-	/* LSM may detect it */
+	/*
+	 * at mount-time, and the xino file is the default path,
+	 * hinotify is disabled so we have no inotify events to ignore.
+	 * when a user specified the xino, we cannot get au_hdir to be ignored.
+	 */
+	vfsub_args_init(&vargs, /*ign*/NULL, /*dlgt*/0, 0);
 	file = vfsub_filp_open(fname, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE,
 			       S_IRUGO | S_IWUGO);
 	if (IS_ERR(file)) {
@@ -512,7 +523,6 @@ struct file *au_xino_create(struct super_block *sb, char *fname, int silent,
 	/* keep file count */
 	h_parent = dget_parent(file->f_dentry);
 	h_dir = h_parent->d_inode;
-	vfsub_args_init(&vargs, NULL, 0, 0);
 	mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
 	err = vfsub_unlink(h_dir, file->f_dentry, &vargs);
 	mutex_unlock(&h_dir->i_mutex);
@@ -546,8 +556,11 @@ static int is_sb_shared(struct super_block *sb, aufs_bindex_t btgt,
 	aufs_bindex_t bindex;
 	struct super_block *tgt_sb = au_sbr_sb(sb, btgt);
 
-	for (bindex = 0; bindex <= bend; bindex++)
-		if (unlikely(btgt != bindex && tgt_sb == au_sbr_sb(sb, bindex)))
+	for (bindex = 0; bindex < btgt; bindex++)
+		if (unlikely(tgt_sb == au_sbr_sb(sb, bindex)))
+			return bindex;
+	for (bindex++; bindex <= bend; bindex++)
+		if (unlikely(tgt_sb == au_sbr_sb(sb, bindex)))
 			return bindex;
 	return -1;
 }
@@ -555,7 +568,6 @@ static int is_sb_shared(struct super_block *sb, aufs_bindex_t btgt,
 /*
  * create a new xinofile at the same place/path as @base_file.
  */
-static
 struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
 			     struct file *copy_src)
 {
@@ -564,6 +576,10 @@ struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
 	struct dentry *base, *dentry, *parent;
 	struct inode *dir, *inode;
 	struct qstr *name;
+	struct au_hinode *hdir;
+	struct au_branch *br;
+	aufs_bindex_t bindex;
+	struct au_hin_ignore ign;
 	struct vfsub_args vargs;
 	struct au_ndx ndx = {
 		.nfsmnt	= NULL,
@@ -590,7 +606,17 @@ struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
 		AuErr("%.*s lookup err %ld\n", AuLNPair(name), PTR_ERR(dentry));
 		goto out;
 	}
-	err = vfsub_create(dir, dentry, S_IRUGO | S_IWUGO, NULL, /*dlgt*/0);
+
+	hdir = NULL;
+	br = au_xino_def_br(au_sbi(sb));
+	if (br) {
+		bindex = au_find_bindex(sb, br);
+		if (bindex >= 0)
+			hdir = au_hi(sb->s_root->d_inode, bindex);
+	}
+	vfsub_args_init(&vargs, &ign, 0, 0);
+	vfsub_ign_hinode(&vargs, IN_CREATE, hdir);
+	err = vfsub_create(dir, dentry, S_IRUGO | S_IWUGO, NULL, &vargs);
 	if (unlikely(err)) {
 		file = ERR_PTR(err);
 		AuErr("%.*s create err %d\n", AuLNPair(name), err);
@@ -602,7 +628,8 @@ struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
 		AuErr("%.*s open err %ld\n", AuLNPair(name), PTR_ERR(file));
 		goto out_dput;
 	}
-	vfsub_args_init(&vargs, NULL, 0, 0);
+	vfsub_args_reinit(&vargs);
+	vfsub_ign_hinode(&vargs, IN_DELETE, hdir);
 	err = vfsub_unlink(dir, dentry, &vargs);
 	if (unlikely(err)) {
 		AuErr("%.*s unlink err %d\n", AuLNPair(name), err);
@@ -611,7 +638,8 @@ struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
 
 	if (copy_src) {
 		inode = copy_src->f_dentry->d_inode;
-		err = au_copy_file(file, copy_src, i_size_read(inode), sb);
+		err = au_copy_file(file, copy_src, i_size_read(inode),
+				   hdir, sb, &vargs);
 		if (unlikely(err)) {
 			AuErr("%.*s copy err %d\n", AuLNPair(name), err);
 			goto out_fput;
@@ -640,27 +668,27 @@ struct file *au_xino_create2(struct super_block *sb, struct file *base_file,
 int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t h_ino,
 	       struct file *base_file, int do_test)
 {
-	int err, do_create;
+	int err;
 	struct au_branch *shared_br;
-	aufs_bindex_t bshared, bend;
+	aufs_bindex_t bshared, bend, bindex;
+	unsigned char do_create;
 	struct inode *dir;
 	struct au_xino_entry xinoe;
 	struct dentry *parent;
 	struct file *file;
+	struct super_block *tgt_sb;
 
 	LKTRTrace("base_file %p, do_test %d\n", base_file, do_test);
 	SiMustWriteLock(sb);
 	AuDebugOn(!au_opt_test_xino(au_mntflags(sb)));
-	AuDebugOn(br->br_xino);
+	AuDebugOn(br->br_xino.xi_file);
 
 	do_create = 1;
 	bshared = -1;
 	shared_br = NULL;
 	bend = au_sbend(sb);
 	if (do_test) {
-		aufs_bindex_t bindex;
-
-		struct super_block *tgt_sb = br->br_mnt->mnt_sb;
+		tgt_sb = br->br_mnt->mnt_sb;
 		for (bindex = 0; bindex <= bend; bindex++)
 			if (unlikely(tgt_sb == au_sbr_sb(sb, bindex))) {
 				bshared = bindex;
@@ -669,24 +697,23 @@ int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t h_ino,
 	}
 	if (unlikely(bshared >= 0)) {
 		shared_br = au_sbr(sb, bshared);
-		do_create = !shared_br->br_xino;
+		do_create = !shared_br->br_xino.xi_file;
 	}
 
 	if (do_create) {
 		parent = dget_parent(base_file->f_dentry);
 		dir = parent->d_inode;
-
 		mutex_lock_nested(&dir->i_mutex, AuLsc_I_PARENT);
 		file = au_xino_create2(sb, base_file, NULL);
-		err = PTR_ERR(file);
 		mutex_unlock(&dir->i_mutex);
 		dput(parent);
+		err = PTR_ERR(file);
 		if (IS_ERR(file))
 			goto out;
-		br->br_xino = file;
+		br->br_xino.xi_file = file;
 	} else {
-		br->br_xino = shared_br->br_xino;
-		get_file(br->br_xino);
+		br->br_xino.xi_file = shared_br->br_xino.xi_file;
+		get_file(br->br_xino.xi_file);
 	}
 
 	xinoe.ino = AUFS_ROOT_INO;
@@ -694,8 +721,8 @@ int au_xino_br(struct super_block *sb, struct au_branch *br, ino_t h_ino,
 	xinoe.h_gen = h_inode->i_generation;
 	WARN_ON(xinoe.h_gen == AuXino_INVALID_HGEN);
 #endif
-	err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino, h_ino,
-			       &xinoe);
+	err = au_xino_do_write(au_sbi(sb)->si_xwrite, br->br_xino.xi_file,
+			       h_ino, &xinoe);
 	if (!err)
 		return 0; /* success */
 
@@ -763,7 +790,7 @@ static int xib_restore(struct super_block *sb)
 	AuTraceEnter();
 
 	err = -ENOMEM;
-	page = (void *)__get_free_page(GFP_TEMPORARY);
+	page = (void *)__get_free_page(GFP_NOFS);
 	if (unlikely(!page))
 		goto out;
 
@@ -772,7 +799,7 @@ static int xib_restore(struct super_block *sb)
 	for (bindex = 0; !err && bindex <= bend; bindex++)
 		if (!bindex || is_sb_shared(sb, bindex, bindex - 1) < 0)
 			err = do_xib_restore
-				(sb, au_sbr(sb, bindex)->br_xino, page);
+				(sb, au_sbr(sb, bindex)->br_xino.xi_file, page);
 		else
 			LKTRTrace("b%d\n", bindex);
 	free_page((unsigned long)page);
@@ -925,7 +952,7 @@ static int au_xino_set_xib(struct super_block *sb, struct file *base)
 
 	err = -ENOMEM;
 	if (!sbinfo->si_xib_buf)
-		sbinfo->si_xib_buf = (void *)get_zeroed_page(GFP_KERNEL);
+		sbinfo->si_xib_buf = (void *)get_zeroed_page(GFP_NOFS);
 	if (unlikely(!sbinfo->si_xib_buf))
 		goto out_unset;
 
@@ -970,11 +997,11 @@ static void xino_clear_br(struct super_block *sb)
 	bend = au_sbend(sb);
 	for (bindex = 0; bindex <= bend; bindex++) {
 		br = au_sbr(sb, bindex);
-		if (unlikely(!br || !br->br_xino))
+		if (unlikely(!br || !br->br_xino.xi_file))
 			continue;
 
-		fput(br->br_xino);
-		br->br_xino = NULL;
+		fput(br->br_xino.xi_file);
+		br->br_xino.xi_file = NULL;
 	}
 }
 
@@ -995,7 +1022,7 @@ static int au_xino_set_br(struct super_block *sb, struct file *base)
 
 	err = -ENOMEM;
 	bend = au_sbend(sb);
-	fpair = kcalloc(bend + 1, sizeof(*fpair), GFP_TEMPORARY);
+	fpair = kcalloc(bend + 1, sizeof(*fpair), GFP_NOFS);
 	if (unlikely(!fpair))
 		goto out;
 
@@ -1013,8 +1040,8 @@ static int au_xino_set_br(struct super_block *sb, struct file *base)
 
 		if (!p->new) {
 			/* new xino */
-			p->old = br->br_xino;
-			p->new = au_xino_create2(sb, base, br->br_xino);
+			p->old = br->br_xino.xi_file;
+			p->new = au_xino_create2(sb, base, br->br_xino.xi_file);
 			err = PTR_ERR(p->new);
 			if (IS_ERR(p->new)) {
 				p->new = NULL;
@@ -1030,11 +1057,11 @@ static int au_xino_set_br(struct super_block *sb, struct file *base)
 
 	for (bindex = 0, p = fpair; bindex <= bend; bindex++, p++) {
 		br = au_sbr(sb, bindex);
-		AuDebugOn(p->old != br->br_xino);
-		if (br->br_xino)
-			fput(br->br_xino);
+		AuDebugOn(p->old != br->br_xino.xi_file);
+		if (br->br_xino.xi_file)
+			fput(br->br_xino.xi_file);
 		get_file(p->new);
-		br->br_xino = p->new;
+		br->br_xino.xi_file = p->new;
 	}
 
  out_pair:
@@ -1051,13 +1078,18 @@ static int au_xino_set_br(struct super_block *sb, struct file *base)
 
 void au_xino_clr(struct super_block *sb)
 {
+	struct au_sbinfo *sbinfo;
+
 	AuTraceEnter();
 	SiMustWriteLock(sb);
 
+	au_xigen_clr(sb);
 	xino_clear_xib(sb);
 	xino_clear_br(sb);
+	sbinfo = au_sbi(sb);
 	/* lvalue, do not call au_mntflags() */
-	au_opt_clr(au_sbi(sb)->si_mntflags, XINO);
+	au_opt_clr(sbinfo->si_mntflags, XINO);
+	au_xino_def_br_set(NULL, sbinfo);
 }
 
 int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount)
@@ -1093,19 +1125,20 @@ int au_xino_set(struct super_block *sb, struct au_opt_xino *xino, int remount)
 	}
 
 	au_opt_set(sbinfo->si_mntflags, XINO);
+	au_xino_def_br_set(NULL, sbinfo);
 	dir = parent->d_inode;
 	mutex_lock_nested(&dir->i_mutex, AuLsc_I_PARENT);
 	err = au_xino_set_xib(sb, xino->file);
 	if (!err)
+		err = au_xigen_set(sb, xino->file);
+	if (!err)
 		err = au_xino_set_br(sb, xino->file);
 	mutex_unlock(&dir->i_mutex);
 	if (!err)
 		goto out; /* success */
 
 	/* reset all */
-	AuIOErr("failed creating xino, forcing noxino (%d).\n", err);
-	err = -EIO;
-	au_xino_clr(sb);
+	AuIOErr("failed creating xino(%d).\n", err);
 
  out:
 	dput(parent);
@@ -1131,21 +1164,22 @@ int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex)
 	if (unlikely(bindex < 0 || bend < bindex))
 		goto out;
 	br = au_sbr(sb, bindex);
-	if (unlikely(!br->br_xino))
+	if (unlikely(!br->br_xino.xi_file))
 		goto out;
 
-	parent = dget_parent(br->br_xino->f_dentry);
+	parent = dget_parent(br->br_xino.xi_file->f_dentry);
 	dir = parent->d_inode;
 	mutex_lock_nested(&dir->i_mutex, AuLsc_I_PARENT);
-	new_xino = au_xino_create2(sb, br->br_xino, br->br_xino);
+	new_xino = au_xino_create2(sb, br->br_xino.xi_file,
+				   br->br_xino.xi_file);
 	mutex_unlock(&dir->i_mutex);
 	dput(parent);
 	err = PTR_ERR(new_xino);
 	if (IS_ERR(new_xino))
 		goto out;
 	err = 0;
-	fput(br->br_xino);
-	br->br_xino = new_xino;
+	fput(br->br_xino.xi_file);
+	br->br_xino.xi_file = new_xino;
 
 	h_sb = br->br_mnt->mnt_sb;
 	for (bi = 0; bi <= bend; bi++) {
@@ -1155,8 +1189,8 @@ int au_xino_trunc(struct super_block *sb, aufs_bindex_t bindex)
 		if (br->br_mnt->mnt_sb != h_sb)
 			continue;
 
-		fput(br->br_xino);
-		br->br_xino = new_xino;
+		fput(br->br_xino.xi_file);
+		br->br_xino.xi_file = new_xino;
 		get_file(new_xino);
 	}
 
@@ -1176,14 +1210,16 @@ struct file *au_xino_def(struct super_block *sb)
 	aufs_bindex_t bend, bindex, bwr;
 	char *page, *p;
 	struct path path;
+	struct dentry *root;
 
 	AuTraceEnter();
 
+	root = sb->s_root;
 	bend = au_sbend(sb);
 	bwr = -1;
 	for (bindex = 0; bindex <= bend; bindex++)
 		if (au_br_writable(au_sbr_perm(sb, bindex))
-		    && !au_test_nfs(au_h_dptr(sb->s_root, bindex)->d_sb)) {
+		    && !au_test_nfs(au_h_dptr(root, bindex)->d_sb)) {
 			bwr = bindex;
 			break;
 		}
@@ -1194,24 +1230,27 @@ struct file *au_xino_def(struct super_block *sb)
 		if (unlikely(!page))
 			goto out;
 		path.mnt = au_sbr_mnt(sb, bwr);
-		path.dentry = au_h_dptr(sb->s_root, bwr);
+		path.dentry = au_h_dptr(root, bwr);
 		p = d_path(&path, page, PATH_MAX - sizeof(AUFS_XINO_FNAME));
 		file = (void *)p;
-		if (p && !IS_ERR(p)) {
+		if (!IS_ERR(p)) {
 			strcat(p, "/" AUFS_XINO_FNAME);
 			LKTRTrace("%s\n", p);
-			file = au_xino_create(sb, p, /*silent*/0, sb->s_root);
+			file = au_xino_create(sb, p, /*silent*/0);
+			if (!IS_ERR(file))
+				au_xino_def_br_set(au_sbr(sb, bwr), au_sbi(sb));
 		}
 		__putname(page);
 	} else {
-		file = au_xino_create(sb, AUFS_XINO_DEFPATH, /*silent*/0,
-				      /*parent*/NULL);
+		file = au_xino_create(sb, AUFS_XINO_DEFPATH, /*silent*/0);
 		if (unlikely(au_test_nfs(file->f_dentry->d_sb))) {
 			AuErr("xino or noxino option is required "
 			      "since %s is NFS\n", AUFS_XINO_DEFPATH);
 			fput(file);
 			file = ERR_PTR(-EINVAL);
 		}
+		if (!IS_ERR(file))
+			au_xino_def_br_set(NULL, au_sbi(sb));
 	}
 
  out:
-- 
1.5.6.3





More information about the kernel-team mailing list