-diff -uprN -x .git linux-2.6.31/Documentation/ABI/testing/debugfs-aufs aufs2-2.6.git/Documentation/ABI/testing/debugfs-aufs
---- linux-2.6.31/Documentation/ABI/testing/debugfs-aufs 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/ABI/testing/debugfs-aufs 2009-09-14 14:52:35.032396516 +0000
-@@ -0,0 +1,40 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/Documentation/ABI/testing/debugfs-aufs linux-2.6.34/Documentation/ABI/testing/debugfs-aufs
+--- linux-2.6.34.org/Documentation/ABI/testing/debugfs-aufs 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/Documentation/ABI/testing/debugfs-aufs 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,37 @@
+What: /debug/aufs/si_<id>/
+Date: March 2009
+Contact: J. R. Okajima <hooanon05@yahoo.co.jp>
+ It shows the consumed blocks by xib (External Inode Number
+ Bitmap), its block size and file size.
+ When the aufs mount option 'noxino' is specified, it
-+ will be empty. About XINO files, see
-+ Documentation/filesystems/aufs/aufs.5 in detail.
++ will be empty. About XINO files, see the aufs manual.
+
+What: /debug/aufs/si_<id>/xino0, xino1 ... xinoN
+Date: March 2009
+ Translation Table), its link count, block size and file
+ size.
+ When the aufs mount option 'noxino' is specified, it
-+ will be empty. About XINO files, see
-+ Documentation/filesystems/aufs/aufs.5 in detail.
++ will be empty. About XINO files, see the aufs manual.
+
+What: /debug/aufs/si_<id>/xigen
+Date: March 2009
+ If CONFIG_AUFS_EXPORT is disabled, this entry will not
+ be created.
+ When the aufs mount option 'noxino' is specified, it
-+ will be empty. About XINO files, see
-+ Documentation/filesystems/aufs/aufs.5 in detail.
-diff -uprN -x .git linux-2.6.31/Documentation/ABI/testing/sysfs-aufs aufs2-2.6.git/Documentation/ABI/testing/sysfs-aufs
---- linux-2.6.31/Documentation/ABI/testing/sysfs-aufs 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/ABI/testing/sysfs-aufs 2009-09-14 14:52:35.032396516 +0000
-@@ -0,0 +1,25 @@
++ will be empty. About XINO files, see the aufs manual.
+diff -urN --exclude '*.orig' linux-2.6.34.org/Documentation/ABI/testing/sysfs-aufs linux-2.6.34/Documentation/ABI/testing/sysfs-aufs
+--- linux-2.6.34.org/Documentation/ABI/testing/sysfs-aufs 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/Documentation/ABI/testing/sysfs-aufs 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,24 @@
+What: /sys/fs/aufs/si_<id>/
+Date: March 2009
+Contact: J. R. Okajima <hooanon05@yahoo.co.jp>
+ Bitmap, Translation Table and Generation Table) file
+ even if it is the default path.
+ When the aufs mount option 'noxino' is specified, it
-+ will be empty. About XINO files, see
-+ Documentation/filesystems/aufs/aufs.5 in detail.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/README aufs2-2.6.git/Documentation/filesystems/aufs/README
---- linux-2.6.31/Documentation/filesystems/aufs/README 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/README 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,342 @@
-+
-+Aufs2 -- advanced multi layered unification filesystem version 2
-+http://aufs.sf.net
-+Junjiro R. Okajima
-+
-+
-+0. Introduction
-+----------------------------------------
-+In the early days, aufs was entirely re-designed and re-implemented
-+Unionfs Version 1.x series. After many original ideas, approaches,
-+improvements and implementations, it becomes totally different from
-+Unionfs while keeping the basic features.
-+Recently, Unionfs Version 2.x series begin taking some of the same
-+approaches to aufs1's.
-+Unionfs is being developed by Professor Erez Zadok at Stony Brook
-+University and his team.
-+
-+This version of AUFS, aufs2 has several purposes.
-+- to be reviewed easily and widely.
-+- to make the source files simpler and smaller by dropping several
-+ original features.
-+
-+Through this work, I found some bad things in aufs1 source code and
-+fixed them. Some of the dropped features will be reverted in the future,
-+but not all I'm afraid.
-+Aufs2 supports linux-2.6.27 and later. If you want older kernel version
-+support, try aufs1 from CVS on SourceForge.
-+
-+Note: it becomes clear that "Aufs was rejected. Let's give it up."
-+According to Christoph Hellwig, linux rejects all union-type filesystems
-+but UnionMount.
-+<http://marc.info/?l=linux-kernel&m=123938533724484&w=2>
-+
-+
-+1. Features
-+----------------------------------------
-+- unite several directories into a single virtual filesystem. The member
-+ directory is called as a branch.
-+- you can specify the permission flags to the branch, which are 'readonly',
-+ 'readwrite' and 'whiteout-able.'
-+- by upper writable branch, internal copyup and whiteout, files/dirs on
-+ readonly branch are modifiable logically.
-+- dynamic branch manipulation, add, del.
-+- etc...
-+
-+Also there are many enhancements in aufs1, such as:
-+- readdir(3) in userspace.
-+- keep inode number by external inode number table
-+- keep the timestamps of file/dir in internal copyup operation
-+- seekable directory, supporting NFS readdir.
-+- support mmap(2) including /proc/PID/exe symlink, without page-copy
-+- whiteout is hardlinked in order to reduce the consumption of inodes
-+ on branch
-+- do not copyup, nor create a whiteout when it is unnecessary
-+- revert a single systemcall when an error occurs in aufs
-+- remount interface instead of ioctl
-+- maintain /etc/mtab by an external command, /sbin/mount.aufs.
-+- loopback mounted filesystem as a branch
-+- kernel thread for removing the dir who has a plenty of whiteouts
-+- support copyup sparse file (a file which has a 'hole' in it)
-+- default permission flags for branches
-+- selectable permission flags for ro branch, whether whiteout can
-+ exist or not
-+- export via NFS.
-+- support <sysfs>/fs/aufs and <debugfs>/aufs.
-+- support multiple writable branches, some policies to select one
-+ among multiple writable branches.
-+- a new semantics for link(2) and rename(2) to support multiple
-+ writable branches.
-+- no glibc changes are required.
-+- pseudo hardlink (hardlink over branches)
-+- allow a direct access manually to a file on branch, e.g. bypassing aufs.
-+ including NFS or remote filesystem branch.
-+- and more...
-+
-+Currently these features are dropped temporary from this version, aufs2.
-+See design/08plan.txt in detail.
-+- test only the highest one for the directory permission (dirperm1)
-+- show whiteout mode (shwh)
-+- copyup on open (coo=)
-+- nested mount, i.e. aufs as readonly no-whiteout branch of another aufs
-+ (robr)
-+- statistics of aufs thread (/sys/fs/aufs/stat)
-+- delegation mode (dlgt)
-+ a delegation of the internal branch access to support task I/O
-+ accounting, which also supports Linux Security Modules (LSM) mainly
-+ for Suse AppArmor.
-+- intent.open/create (file open in a single lookup)
-+
-+Features or just an idea in the future (see also design/*.txt),
-+- reorder the branch index without del/re-add.
-+- permanent xino files for NFSD
-+- an option for refreshing the opened files after add/del branches
-+- 'move' policy for copy-up between two writable branches, after
-+ checking free space.
-+- O_DIRECT
-+- light version, without branch manipulation. (unnecessary?)
-+- copyup in userspace
-+- inotify in userspace
-+- readv/writev
-+- xattr, acl
-+
-+
-+2. Download
-+----------------------------------------
-+Kindly one of aufs user, the Center for Scientific Computing and Free
-+Software (C3SL), Federal University of Parana offered me a public GIT
-+tree space.
-+
-+There are three GIT trees, aufs2-2.6, aufs2-standalone and aufs2-util.
-+While the aufs2-util is always necessary, you need either of aufs2-2.6
-+or aufs2-standalone.
-+
-+The aufs2-2.6 tree includes the whole linux-2.6 GIT tree,
-+git://git.kernel.org/.../torvalds/linux-2.6.git.
-+And you cannot select CONFIG_AUFS_FS=m for this version, eg. you cannot
-+build aufs2 as an externel kernel module.
-+If you already have linux-2.6 GIT tree, you may want to pull and merge
-+the "aufs2" branch from this tree.
-+
-+On the other hand, the aufs2-standalone tree has only aufs2 source files
-+and a necessary patch, and you can select CONFIG_AUFS_FS=m. In other
-+words, the aufs2-standalone tree is generated from aufs2-2.6 tree by,
-+- extract new files and modifications.
-+- generate some patch files from modifications.
-+- generate a ChangeLog file from git-log.
-+- commit the files newly and no log messages. this is not git-pull.
-+
-+Both of aufs2-2.6 and aufs2-standalone trees have a branch whose name is
-+in form of "aufs2-xx" where "xx" represents the linux kernel version,
-+"linux-2.6.xx".
-+
-+o aufs2-2.6 tree
-+$ git clone --reference /your/linux-2.6/git/tree \
-+ http://git.c3sl.ufpr.br/pub/scm/aufs/aufs2-2.6.git \
-+ aufs2-2.6.git
-+- if you don't have linux-2.6 GIT tree, then remove "--reference ..."
-+$ cd aufs2-2.6.git
-+$ git checkout origin/aufs2-xx # for instance, aufs2-27 for linux-2.6.27
-+ # aufs2 (no -xx) for the latest -rc version.
-+
-+o aufs2-standalone tree
-+$ git clone http://git.c3sl.ufpr.br/pub/scm/aufs/aufs2-standalone.git \
-+ aufs2-standalone.git
-+$ cd aufs2-standalone.git
-+$ git checkout origin/aufs2-xx # for instance, aufs2-27 for linux-2.6.27
-+ # aufs2 (no -xx) for the latest -rc version.
-+
-+o aufs2-util tree
-+$ git clone http://git.c3sl.ufpr.br/pub/scm/aufs/aufs2-util.git \
-+ aufs2-util.git
-+$ cd aufs2-util.git
-+- no particular tag/branch currently.
-+
-+o for advanced users
-+$ git clone git://git.kernel.org/.../torvalds/linux-2.6.git linux-2.6.git
-+ It will take very long time.
-+
-+$ cd linux-2.6.git
-+$ git remote add aufs2 http://git.c3sl.ufpr.br/pub/scm/aufs/aufs2-2.6.git
-+$ git checkout -b aufs2-27 v2.6.27
-+$ git pull aufs2 aufs2-27
-+ It may take long time again.
-+ Once pulling completes, you've got linux-2.6.27 and aufs2 for it in a
-+ branch named aufs2-27, and you can configure and build it.
-+
-+Or
-+
-+$ git checkout -t -b aufs2 master
-+$ git pull aufs2 aufs2
-+ then you've got the latest linux kernel and the latest aufs2 in a
-+ branch named aufs2, and you can configure and build it.
-+ But aufs is released once a week, so you may meet a compilation error
-+ due to mismatching between the mainline and aufs2.
-+
-+Or you may want build linux-2.6.xx.yy instead of linux-2.6.xx, then here
-+is an approach using linux-2.6-stable GIT tree.
-+
-+$ cd linux-2.6.git/..
-+$ git clone -q --reference ./linux-2.6.git git://git.kernel.org/.../linux-2.6-stable.git \
-+ linux-2.6-stable.git
-+ It will take very long time.
-+
-+$ cd linux-2.6-stable.git
-+$ git remote add aufs2 http://git.c3sl.ufpr.br/pub/scm/aufs/aufs2-2.6.git
-+$ git checkout -b aufs2-27.1 v2.6.27.1
-+$ git pull aufs2 aufs2-27
-+ then you've got linux-2.6.27.1 and aufs2 for 2.6.27 in a branch named
-+ aufs2-27.1, and you can configure and build it.
-+ But the changes made by v2.6.xx.yy may conflict with aufs2-xx, since
-+ aufs2-xx is for v2.6.xx only. In this case, you may find some patchces
-+ for v2.6.xx.yy in aufs2-standalone.git#aufs2-xx branch if someone else
-+ have ever requested me to support v2.6.xx.yy and I did it.
-+
-+You can also check what was changed by pulling aufs2.
-+$ git diff v2.6.27.1..aufs2-27.1
-+
-+If you want to check the changed files other than fs/aufs, then try this.
-+$ git diff v2.6.27.1..aufs2-27.1 |
-+> awk '
-+> /^diff / {new=1}
-+> /^diff.*aufs/ {new=0}
-+> new {print}
-+> '
-+
-+
-+3. Configuration and Compilation
-+----------------------------------------
-+For aufs2-2.6 tree,
-+- enable CONFIG_EXPERIMENTAL and CONFIG_AUFS_FS.
-+- set other aufs configurations if necessary.
-+
-+For aufs2-standalone tree,
-+There are several ways to build.
-+
-+You may feel why aufs2-standalone.patch needs to export so many kernel
-+symbols. Because you selected aufs2-standalone tree instead of aufs2-2.6
-+tree. The number of necessary symbols to export essentially is zero.
-+All other symbols are for the external module.
-+If you don't like aufs2-standalone.patch, then try aufs2-2.6 tree.
-+
-+1.
-+- apply ./aufs2-kbuild.patch to your kernel source files.
-+- apply ./aufs2-base.patch too.
-+- apply ./aufs2-standalone.patch too, if you have a plan to set
-+ CONFIG_AUFS_FS=m. otherwise you don't need ./aufs2-standalone.patch.
-+- copy ./{Documentation,fs,include} files to your kernel source tree.
-+- enable CONFIG_EXPERIMENTAL and CONFIG_AUFS_FS, you can select either
-+ =m or =y.
-+- and build your kernel as usual.
-+- install it and reboot your system.
-+
-+2.
-+- module only (CONFIG_AUFS_FS=m).
-+- apply ./aufs2-base.patch to your kernel source files.
-+- apply ./aufs2-standalone.patch too.
-+- build your kernel and reboot.
-+- edit ./config.mk and set other aufs configurations if necessary.
-+ Note: You should read ./fs/aufs/Kconfig carefully which describes
-+ every aufs configurations.
-+- build the module by simple "make".
-+- you can specify ${KDIR} make variable which points to your kernel
-+ source tree.
-+- copy the build ./aufs.ko to /lib/modules/..., and run depmod -a (or
-+ reboot simply).
-+- no need to apply aufs2-kbuild.patch, nor copying source files to your
-+ kernel source tree.
-+
-+And then,
-+- read README in aufs2-util, build and install it
-+- if you want to use readdir(3) in userspace, then run
-+ "make install_ulib" too. And refer to the aufs manual in detail.
-+
-+
-+4. Usage
-+----------------------------------------
-+At first, make sure aufs2-util are installed, and please read the aufs
-+manual, aufs.5 in aufs2-util.git tree.
-+$ man -l aufs.5
-+
-+And then,
-+$ mkdir /tmp/rw /tmp/aufs
-+# mount -t aufs -o br=/tmp/rw:${HOME} none /tmp/aufs
-+
-+Here is another example. The result is equivalent.
-+# mount -t aufs -o br=/tmp/rw=rw:${HOME}=ro none /tmp/aufs
-+ Or
-+# mount -t aufs -o br:/tmp/rw none /tmp/aufs
-+# mount -o remount,append:${HOME} /tmp/aufs
-+
-+Then, you can see whole tree of your home dir through /tmp/aufs. If
-+you modify a file under /tmp/aufs, the one on your home directory is
-+not affected, instead the same named file will be newly created under
-+/tmp/rw. And all of your modification to a file will be applied to
-+the one under /tmp/rw. This is called the file based Copy on Write
-+(COW) method.
-+Aufs mount options are described in aufs.5.
-+
-+Additionally, there are some sample usages of aufs which are a
-+diskless system with network booting, and LiveCD over NFS.
-+See sample dir in CVS tree on SourceForge.
-+
-+
-+5. Contact
-+----------------------------------------
-+When you have any problems or strange behaviour in aufs, please let me
-+know with:
-+- /proc/mounts (instead of the output of mount(8))
-+- /sys/module/aufs/*
-+- /sys/fs/aufs/* (if you have them)
-+- /debug/aufs/* (if you have them)
-+- linux kernel version
-+ if your kernel is not plain, for example modified by distributor,
-+ the url where i can download its source is necessary too.
-+- aufs version which was printed at loading the module or booting the
-+ system, instead of the date you downloaded.
-+- configuration (define/undefine CONFIG_AUFS_xxx)
-+- kernel configuration or /proc/config.gz (if you have it)
-+- behaviour which you think to be incorrect
-+- actual operation, reproducible one is better
-+- mailto: aufs-users at lists.sourceforge.net
-+
-+Usually, I don't watch the Public Areas(Bugs, Support Requests, Patches,
-+and Feature Requests) on SourceForge. Please join and write to
-+aufs-users ML.
-+
-+
-+6. Acknowledgements
-+----------------------------------------
-+Thanks to everyone who have tried and are using aufs, whoever
-+have reported a bug or any feedback.
-+
-+Especially donors:
-+Tomas Matejicek(slax.org) made a donation (much more than once).
-+Dai Itasaka made a donation (2007/8).
-+Chuck Smith made a donation (2008/4, 10 and 12).
-+Henk Schoneveld made a donation (2008/9).
-+Chih-Wei Huang, ASUS, CTC donated Eee PC 4G (2008/10).
-+Francois Dupoux made a donation (2008/11).
-+Bruno Cesar Ribas and Luis Carlos Erpen de Bona, C3SL serves public
-+aufs2 GIT tree (2009/2).
-+William Grant made a donation (2009/3).
-+Patrick Lane made a donation (2009/4).
-+The Mail Archive (mail-archive.com) made donations (2009/5).
-+Nippy Networks (Ed Wildgoose) a donation (2009/7).
-+
-+Thank you very much.
-+Donations are always, including future donations, very important and
-+helpful for me to keep on developing aufs.
-+
-+
-+7.
-+----------------------------------------
-+If you are an experienced user, no explanation is needed. Aufs is
-+just a linux filesystem.
-+
-+
-+Enjoy!
-+
-+# Local variables: ;
-+# mode: text;
-+# End: ;
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/01intro.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/01intro.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/01intro.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/01intro.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,137 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+Introduction
-+----------------------------------------
-+
-+aufs [ei ju: ef es] | [a u f s]
-+1. abbrev. for "advanced multi-layered unification filesystem".
-+2. abbrev. for "another unionfs".
-+3. abbrev. for "auf das" in German which means "on the" in English.
-+ Ex. "Butter aufs Brot"(G) means "butter onto bread"(E).
-+ But "Filesystem aufs Filesystem" is hard to understand.
-+
-+AUFS is a filesystem with features:
-+- multi layered stackable unification filesystem, the member directory
-+ is called as a branch.
-+- branch permission and attribute, 'readonly', 'real-readonly',
-+ 'readwrite', 'whiteout-able', 'link-able whiteout' and their
-+ combination.
-+- internal "file copy-on-write".
-+- logical deletion, whiteout.
-+- dynamic branch manipulation, adding, deleting and changing permission.
-+- allow bypassing aufs, user's direct branch access.
-+- external inode number translation table and bitmap which maintains the
-+ persistent aufs inode number.
-+- seekable directory, including NFS readdir.
-+- file mapping, mmap and sharing pages.
-+- pseudo-link, hardlink over branches.
-+- loopback mounted filesystem as a branch.
-+- several policies to select one among multiple writable branches.
-+- revert a single systemcall when an error occurs in aufs.
-+- and more...
-+
-+
-+Multi Layered Stackable Unification Filesystem
-+----------------------------------------------------------------------
-+Most people already knows what it is.
-+It is a filesystem which unifies several directories and provides a
-+merged single directory. When users access a file, the access will be
-+passed/re-directed/converted (sorry, I am not sure which English word is
-+correct) to the real file on the member filesystem. The member
-+filesystem is called 'lower filesystem' or 'branch' and has a mode
-+'readonly' and 'readwrite.' And the deletion for a file on the lower
-+readonly branch is handled by creating 'whiteout' on the upper writable
-+branch.
-+
-+On LKML, there have been discussions about UnionMount (Jan Blunck and
-+Bharata B Rao) and Unionfs (Erez Zadok). They took different approaches
-+to implement the merged-view.
-+The former tries putting it into VFS, and the latter implements as a
-+separate filesystem.
-+(If I misunderstand about these implementations, please let me know and
-+I shall correct it. Because it is a long time ago when I read their
-+source files last time).
-+UnionMount's approach will be able to small, but may be hard to share
-+branches between several UnionMount since the whiteout in it is
-+implemented in the inode on branch filesystem and always
-+shared. According to Bharata's post, readdir does not seems to be
-+finished yet.
-+Unionfs has a longer history. When I started implementing a stacking filesystem
-+(Aug 2005), it already existed. It has virtual super_block, inode,
-+dentry and file objects and they have an array pointing lower same kind
-+objects. After contributing many patches for Unionfs, I re-started my
-+project AUFS (Jun 2006).
-+
-+In AUFS, the structure of filesystem resembles to Unionfs, but I
-+implemented my own ideas, approaches and enhancements and it became
-+totally different one.
-+
-+
-+Several characters/aspects of aufs
-+----------------------------------------------------------------------
-+
-+Aufs has several characters or aspects.
-+1. a filesystem, callee of VFS helper
-+2. sub-VFS, caller of VFS helper for branches
-+3. a virtual filesystem which maintains persistent inode number
-+4. reader/writer of files on branches such like an application
-+
-+1. Caller of VFS Helper
-+As an ordinary linux filesystem, aufs is a callee of VFS. For instance,
-+unlink(2) from an application reaches sys_unlink() kernel function and
-+then vfs_unlink() is called. vfs_unlink() is one of VFS helper and it
-+calls filesystem specific unlink operation. Actually aufs implements the
-+unlink operation but it behaves like a redirector.
-+
-+2. Caller of VFS Helper for Branches
-+aufs_unlink() passes the unlink request to the branch filesystem as if
-+it were called from VFS. So the called unlink operation of the branch
-+filesystem acts as usual. As a caller of VFS helper, aufs should handle
-+every necessary pre/post operation for the branch filesystem.
-+- acquire the lock for the parent dir on a branch
-+- lookup in a branch
-+- revalidate dentry on a branch
-+- mnt_want_write() for a branch
-+- vfs_unlink() for a branch
-+- mnt_drop_write() for a branch
-+- release the lock on a branch
-+
-+3. Persistent Inode Number
-+One of the most important issue for a filesystem is to maintain inode
-+numbers. This is particularly important to support exporting a
-+filesystem via NFS. Aufs is a virtual filesystem which doesn't have a
-+backend block device for its own. But some storage is necessary to
-+maintain inode number. It may be a large space and may not suit to keep
-+in memory. Aufs rents some space from its first writable branch
-+filesystem (by default) and creates file(s) on it. These files are
-+created by aufs internally and removed soon (currently) keeping opened.
-+Note: Because these files are removed, they are totally gone after
-+ unmounting aufs. It means the inode numbers are not persistent
-+ across unmount or reboot. I have a plan to make them really
-+ persistent which will be important for aufs on NFS server.
-+
-+4. Read/Write Files Internally (copy-on-write)
-+Because a branch can be readonly, when you write a file on it, aufs will
-+"copy-up" it to the upper writable branch internally. And then write the
-+originally requested thing to the file. Generally kernel doesn't
-+open/read/write file actively. In aufs, even a single write may cause a
-+internal "file copy". This behaviour is very similar to cp(1) command.
-+
-+Some people may think it is better to pass such work to user space
-+helper, instead of doing in kernel space. Actually I am still thinking
-+about it. But currently I have implemented it in kernel space.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/02struct.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/02struct.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/02struct.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/02struct.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,218 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+Basic Aufs Internal Structure
-+
-+Superblock/Inode/Dentry/File Objects
-+----------------------------------------------------------------------
-+As like an ordinary filesystem, aufs has its own
-+superblock/inode/dentry/file objects. All these objects have a
-+dynamically allocated array and store the same kind of pointers to the
-+lower filesystem, branch.
-+For example, when you build a union with one readwrite branch and one
-+readonly, mounted /au, /rw and /ro respectively.
-+- /au = /rw + /ro
-+- /ro/fileA exists but /rw/fileA
-+
-+Aufs lookup operation finds /ro/fileA and gets dentry for that. These
-+pointers are stored in a aufs dentry. The array in aufs dentry will be,
-+- [0] = NULL
-+- [1] = /ro/fileA
-+
-+This style of an array is essentially same to the aufs
-+superblock/inode/dentry/file objects.
-+
-+Because aufs supports manipulating branches, ie. add/delete/change
-+dynamically, these objects has its own generation. When branches are
-+changed, the generation in aufs superblock is incremented. And a
-+generation in other object are compared when it is accessed.
-+When a generation in other objects are obsoleted, aufs refreshes the
-+internal array.
-+
-+
-+Superblock
-+----------------------------------------------------------------------
-+Additionally aufs superblock has some data for policies to select one
-+among multiple writable branches, XIB files, pseudo-links and kobject.
-+See below in detail.
-+About the policies which supports copy-down a directory, see policy.txt
-+too.
-+
-+
-+Branch and XINO(External Inode Number Translation Table)
-+----------------------------------------------------------------------
-+Every branch has its own xino (external inode number translation table)
-+file. The xino file is created and unlinked by aufs internally. When two
-+members of a union exist on the same filesystem, they share the single
-+xino file.
-+The struct of a xino file is simple, just a sequence of aufs inode
-+numbers which is indexed by the lower inode number.
-+In the above sample, assume the inode number of /ro/fileA is i111 and
-+aufs assigns the inode number i999 for fileA. Then aufs writes 999 as
-+4(8) bytes at 111 * 4(8) bytes offset in the xino file.
-+
-+When the inode numbers are not contiguous, the xino file will be sparse
-+which has a hole in it and doesn't consume as much disk space as it
-+might appear. If your branch filesystem consumes disk space for such
-+holes, then you should specify 'xino=' option at mounting aufs.
-+
-+Also a writable branch has three kinds of "whiteout bases". All these
-+are existed when the branch is joined to aufs and the names are
-+whiteout-ed doubly, so that users will never see their names in aufs
-+hierarchy.
-+1. a regular file which will be linked to all whiteouts.
-+2. a directory to store a pseudo-link.
-+3. a directory to store an "orphan-ed" file temporary.
-+
-+1. Whiteout Base
-+ When you remove a file on a readonly branch, aufs handles it as a
-+ logical deletion and creates a whiteout on the upper writable branch
-+ as a hardlink of this file in order not to consume inode on the
-+ writable branch.
-+2. Pseudo-link Dir
-+ See below, Pseudo-link.
-+3. Step-Parent Dir
-+ When "fileC" exists on the lower readonly branch only and it is
-+ opened and removed with its parent dir, and then user writes
-+ something into it, then aufs copies-up fileC to this
-+ directory. Because there is no other dir to store fileC. After
-+ creating a file under this dir, the file is unlinked.
-+
-+Because aufs supports manipulating branches, ie. add/delete/change
-+dynamically, a branch has its own id. When the branch order changes, aufs
-+finds the new index by searching the branch id.
-+
-+
-+Pseudo-link
-+----------------------------------------------------------------------
-+Assume "fileA" exists on the lower readonly branch only and it is
-+hardlinked to "fileB" on the branch. When you write something to fileA,
-+aufs copies-up it to the upper writable branch. Additionally aufs
-+creates a hardlink under the Pseudo-link Directory of the writable
-+branch. The inode of a pseudo-link is kept in aufs super_block as a
-+simple list. If fileB is read after unlinking fileA, aufs returns
-+filedata from the pseudo-link instead of the lower readonly
-+branch. Because the pseudo-link is based upon the inode, to keep the
-+inode number by xino (see above) is important.
-+
-+All the hardlinks under the Pseudo-link Directory of the writable branch
-+should be restored in a proper location later. Aufs provides a utility
-+to do this. The userspace helpers executed at remounting and unmounting
-+aufs by default.
-+
-+
-+XIB(external inode number bitmap)
-+----------------------------------------------------------------------
-+Addition to the xino file per a branch, aufs has an external inode number
-+bitmap in a superblock object. It is also a file such like a xino file.
-+It is a simple bitmap to mark whether the aufs inode number is in-use or
-+not.
-+To reduce the file I/O, aufs prepares a single memory page to cache xib.
-+
-+Aufs implements a feature to truncate/refresh both of xino and xib to
-+reduce the number of consumed disk blocks for these files.
-+
-+
-+Virtual or Vertical Dir
-+----------------------------------------------------------------------
-+In order to support multiple layers (branches), aufs readdir operation
-+constructs a virtual dir block on memory. For readdir, aufs calls
-+vfs_readdir() internally for each dir on branches, merges their entries
-+with eliminating the whiteout-ed ones, and sets it to file (dir)
-+object. So the file object has its entry list until it is closed. The
-+entry list will be updated when the file position is zero and becomes
-+old. This decision is made in aufs automatically.
-+
-+The dynamically allocated memory block for the name of entries has a
-+unit of 512 bytes (by default) and stores the names contiguously (no
-+padding). Another block for each entry is handled by kmem_cache too.
-+During building dir blocks, aufs creates hash list and judging whether
-+the entry is whiteouted by its upper branch or already listed.
-+
-+Some people may call it can be a security hole or invite DoS attack
-+since the opened and once readdir-ed dir (file object) holds its entry
-+list and becomes a pressure for system memory. But I'd say it is similar
-+to files under /proc or /sys. The virtual files in them also holds a
-+memory page (generally) while they are opened. When an idea to reduce
-+memory for them is introduced, it will be applied to aufs too.
-+For those who really hate this situation, I've developed readdir(3)
-+library which operates this merging in userspace. You just need to set
-+LD_PRELOAD environment variable, and aufs will not consume no memory in
-+kernel space for readdir(3).
-+
-+
-+Workqueue
-+----------------------------------------------------------------------
-+Aufs sometimes requires privilege access to a branch. For instance,
-+in copy-up/down operation. When a user process is going to make changes
-+to a file which exists in the lower readonly branch only, and the mode
-+of one of ancestor directories may not be writable by a user
-+process. Here aufs copy-up the file with its ancestors and they may
-+require privilege to set its owner/group/mode/etc.
-+This is a typical case of a application character of aufs (see
-+Introduction).
-+
-+Aufs uses workqueue synchronously for this case. It creates its own
-+workqueue. The workqueue is a kernel thread and has privilege. Aufs
-+passes the request to call mkdir or write (for example), and wait for
-+its completion. This approach solves a problem of a signal handler
-+simply.
-+If aufs didn't adopt the workqueue and changed the privilege of the
-+process, and if the mkdir/write call arises SIGXFSZ or other signal,
-+then the user process might gain a privilege or the generated core file
-+was owned by a superuser. But I have a plan to switch to a new
-+credential approach which will be introduced in linux-2.6.29.
-+
-+Also aufs uses the system global workqueue ("events" kernel thread) too
-+for asynchronous tasks, such like handling inotify, re-creating a
-+whiteout base and etc. This is unrelated to a privilege.
-+Most of aufs operation tries acquiring a rw_semaphore for aufs
-+superblock at the beginning, at the same time waits for the completion
-+of all queued asynchronous tasks.
-+
-+
-+Whiteout
-+----------------------------------------------------------------------
-+The whiteout in aufs is very similar to Unionfs's. That is represented
-+by its filename. UnionMount takes an approach of a file mode, but I am
-+afraid several utilities (find(1) or something) will have to support it.
-+
-+Basically the whiteout represents "logical deletion" which stops aufs to
-+lookup further, but also it represents "dir is opaque" which also stop
-+lookup.
-+
-+In aufs, rmdir(2) and rename(2) for dir uses whiteout alternatively.
-+In order to make several functions in a single systemcall to be
-+revertible, aufs adopts an approach to rename a directory to a temporary
-+unique whiteouted name.
-+For example, in rename(2) dir where the target dir already existed, aufs
-+renames the target dir to a temporary unique whiteouted name before the
-+actual rename on a branch and then handles other actions (make it opaque,
-+update the attributes, etc). If an error happens in these actions, aufs
-+simply renames the whiteouted name back and returns an error. If all are
-+succeeded, aufs registers a function to remove the whiteouted unique
-+temporary name completely and asynchronously to the system global
-+workqueue.
-+
-+
-+Copy-up
-+----------------------------------------------------------------------
-+It is a well-known feature or concept.
-+When user modifies a file on a readonly branch, aufs operate "copy-up"
-+internally and makes change to the new file on the upper writable branch.
-+When the trigger systemcall does not update the timestamps of the parent
-+dir, aufs reverts it after copy-up.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/03lookup.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/03lookup.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/03lookup.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/03lookup.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,104 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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 in a Branch
-+----------------------------------------------------------------------
-+Since aufs has a character of sub-VFS (see Introduction), it operates
-+lookup for branches as VFS does. It may be a heavy work. Generally
-+speaking struct nameidata is a bigger structure and includes many
-+information. But almost all lookup operation in aufs is the simplest
-+case, ie. lookup only an entry directly connected to its parent. Digging
-+down the directory hierarchy is unnecessary.
-+
-+VFS has a function lookup_one_len() for that use, but it is not usable
-+for a branch filesystem which requires struct nameidata. So aufs
-+implements a simple lookup wrapper function. When a branch filesystem
-+allows NULL as nameidata, it calls lookup_one_len(). Otherwise it builds
-+a simplest nameidata and calls lookup_hash().
-+Here aufs applies "a principle in NFSD", ie. if the filesystem supports
-+NFS-export, then it has to support NULL as a nameidata parameter for
-+->create(), ->lookup() and ->d_revalidate(). So the lookup wrapper in
-+aufs tests if ->s_export_op in the branch is NULL or not.
-+
-+When a branch is a remote filesystem, aufs trusts its ->d_revalidate().
-+For d_revalidate, aufs implements three levels of revalidate tests. See
-+"Revalidate Dentry and UDBA" in detail.
-+
-+
-+Loopback Mount
-+----------------------------------------------------------------------
-+Basically aufs supports any type of filesystem and block device for a
-+branch (actually there are some exceptions). But it is prohibited to add
-+a loopback mounted one whose backend file exists in a filesystem which is
-+already added to aufs. The reason is to protect aufs from a recursive
-+lookup. If it was allowed, the aufs lookup operation might re-enter a
-+lookup for the loopback mounted branch in the same context, and will
-+cause a deadlock.
-+
-+
-+Revalidate Dentry and UDBA (User's Direct Branch Access)
-+----------------------------------------------------------------------
-+Generally VFS helpers re-validate a dentry as a part of lookup.
-+0. digging down the directory hierarchy.
-+1. lock the parent dir by its i_mutex.
-+2. lookup the final (child) entry.
-+3. revalidate it.
-+4. call the actual operation (create, unlink, etc.)
-+5. unlock the parent dir
-+
-+If the filesystem implements its ->d_revalidate() (step 3), then it is
-+called. Actually aufs implements it and checks the dentry on a branch is
-+still valid.
-+But it is not enough. Because aufs has to release the lock for the
-+parent dir on a branch at the end of ->lookup() (step 2) and
-+->d_revalidate() (step 3) while the i_mutex of the aufs dir is still
-+held by VFS.
-+If the file on a branch is changed directly, eg. bypassing aufs, after
-+aufs released the lock, then the subsequent operation may cause
-+something unpleasant result.
-+
-+This situation is a result of VFS architecture, ->lookup() and
-+->d_revalidate() is separated. But I never say it is wrong. It is a good
-+design from VFS's point of view. It is just not suitable for sub-VFS
-+character in aufs.
-+
-+Aufs supports such case by three level of revalidation which is
-+selectable by user.
-+1. Simple Revalidate
-+ Addition to the native flow in VFS's, confirm the child-parent
-+ relationship on the branch just after locking the parent dir on the
-+ branch in the "actual operation" (step 4). When this validation
-+ fails, aufs returns EBUSY. ->d_revalidate() (step 3) in aufs still
-+ checks the validation of the dentry on branches.
-+2. Monitor Changes Internally by Inotify
-+ Addition to above, in the "actual operation" (step 4) aufs re-lookup
-+ the dentry on the branch, and returns EBUSY if it finds different
-+ dentry.
-+ Additionally, aufs sets the inotify watch for every dir on branches
-+ during it is in cache. When the event is notified, aufs registers a
-+ function to kernel 'events' thread by schedule_work(). And the
-+ function sets some special status to the cached aufs dentry and inode
-+ private data. If they are not cached, then aufs has nothing to
-+ do. When the same file is accessed through aufs (step 0-3) later,
-+ aufs will detect the status and refresh all necessary data.
-+ In this mode, aufs has to ignore the event which is fired by aufs
-+ itself.
-+3. No Extra Validation
-+ This is the simplest test and doesn't add any additional revalidation
-+ test, and skip therevalidatin in step 4. It is useful and improves
-+ aufs performance when system surely hide the aufs branches from user,
-+ by over-mounting something (or another method).
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/04branch.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/04branch.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/04branch.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/04branch.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,76 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+Branch Manipulation
-+
-+Since aufs supports dynamic branch manipulation, ie. add/remove a branch
-+and changing its permission/attribute, there are a lot of works to do.
-+
-+
-+Add a Branch
-+----------------------------------------------------------------------
-+o Confirm the adding dir exists outside of aufs, including loopback
-+ mount.
-+- and other various attributes...
-+o Initialize the xino file and whiteout bases if necessary.
-+ See struct.txt.
-+
-+o Check the owner/group/mode of the directory
-+ When the owner/group/mode of the adding directory differs from the
-+ existing branch, aufs issues a warning because it may impose a
-+ security risk.
-+ For example, when a upper writable branch has a world writable empty
-+ top directory, a malicious user can create any files on the writable
-+ branch directly, like copy-up and modify manually. If something like
-+ /etc/{passwd,shadow} exists on the lower readonly branch but the upper
-+ writable branch, and the writable branch is world-writable, then a
-+ malicious guy may create /etc/passwd on the writable branch directly
-+ and the infected file will be valid in aufs.
-+ I am afraid it can be a security issue, but nothing to do except
-+ producing a warning.
-+
-+
-+Delete a Branch
-+----------------------------------------------------------------------
-+o Confirm the deleting branch is not busy
-+ To be general, there is one merit to adopt "remount" interface to
-+ manipulate branches. It is to discard caches. At deleting a branch,
-+ aufs checks the still cached (and connected) dentries and inodes. If
-+ there are any, then they are all in-use. An inode without its
-+ corresponding dentry can be alive alone (for example, inotify case).
-+
-+ For the cached one, aufs checks whether the same named entry exists on
-+ other branches.
-+ If the cached one is a directory, because aufs provides a merged view
-+ to users, as long as one dir is left on any branch aufs can show the
-+ dir to users. In this case, the branch can be removed from aufs.
-+ Otherwise aufs rejects deleting the branch.
-+
-+ If any file on the deleting branch is opened by aufs, then aufs
-+ rejects deleting.
-+
-+
-+Modify the Permission of a Branch
-+----------------------------------------------------------------------
-+o Re-initialize or remove the xino file and whiteout bases if necessary.
-+ See struct.txt.
-+
-+o rw --> ro: Confirm the modifying branch is not busy
-+ Aufs rejects the request if any of these conditions are true.
-+ - a file on the branch is mmap-ed.
-+ - a regular file on the branch is opened for write and there is no
-+ same named entry on the upper branch.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/05wbr_policy.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/05wbr_policy.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/05wbr_policy.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/05wbr_policy.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,65 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+Policies to Select One among Multiple Writable Branches
-+----------------------------------------------------------------------
-+When the number of writable branch is more than one, aufs has to decide
-+the target branch for file creation or copy-up. By default, the highest
-+writable branch which has the parent (or ancestor) dir of the target
-+file is chosen (top-down-parent policy).
-+By user's request, aufs implements some other policies to select the
-+writable branch, for file creation two policies, round-robin and
-+most-free-space policies. For copy-up three policies, top-down-parent,
-+bottom-up-parent and bottom-up policies.
-+
-+As expected, the round-robin policy selects the branch in circular. When
-+you have two writable branches and creates 10 new files, 5 files will be
-+created for each branch. mkdir(2) systemcall is an exception. When you
-+create 10 new directories, all will be created on the same branch.
-+And the most-free-space policy selects the one which has most free
-+space among the writable branches. The amount of free space will be
-+checked by aufs internally, and users can specify its time interval.
-+
-+The policies for copy-up is more simple,
-+top-down-parent is equivalent to the same named on in create policy,
-+bottom-up-parent selects the writable branch where the parent dir
-+exists and the nearest upper one from the copyup-source,
-+bottom-up selects the nearest upper writable branch from the
-+copyup-source, regardless the existence of the parent dir.
-+
-+There are some rules or exceptions to apply these policies.
-+- If there is a readonly branch above the policy-selected branch and
-+ the parent dir is marked as opaque (a variation of whiteout), or the
-+ target (creating) file is whiteout-ed on the upper readonly branch,
-+ then the result of the policy is ignored and the target file will be
-+ created on the nearest upper writable branch than the readonly branch.
-+- If there is a writable branch above the policy-selected branch and
-+ the parent dir is marked as opaque or the target file is whiteouted
-+ on the branch, then the result of the policy is ignored and the target
-+ file will be created on the highest one among the upper writable
-+ branches who has diropq or whiteout. In case of whiteout, aufs removes
-+ it as usual.
-+- link(2) and rename(2) systemcalls are exceptions in every policy.
-+ They try selecting the branch where the source exists as possible
-+ since copyup a large file will take long time. If it can't be,
-+ ie. the branch where the source exists is readonly, then they will
-+ follow the copyup policy.
-+- There is an exception for rename(2) when the target exists.
-+ If the rename target exists, aufs compares the index of the branches
-+ where the source and the target exists and selects the higher
-+ one. If the selected branch is readonly, then aufs follows the
-+ copyup policy.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/06fmode_exec.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/06fmode_exec.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/06fmode_exec.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/06fmode_exec.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,33 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+FMODE_EXEC and deny_write()
-+----------------------------------------------------------------------
-+Generally Unix prevents an executing file from writing its filedata.
-+In linux it is implemented by deny_write() and allow_write().
-+When a file is executed by exec() family, open_exec() (and sys_uselib())
-+they opens the file and calls deny_write(). If the file is aufs's virtual
-+one, it has no meaning. The file which deny_write() is really necessary
-+is the file on a branch. But the FMODE_EXEC flag is not passed to
-+->open() operation. So aufs adopt a dirty trick.
-+
-+- in order to get FMODE_EXEC, aufs ->lookup() and ->d_revalidate() set
-+ nd->intent.open.file->private_data to nd->intent.open.flags temporary.
-+- in aufs ->open(), when FMODE_EXEC is set in file->private_data, it
-+ calls deny_write() for the file on a branch.
-+- when the aufs file is released, allow_write() for the file on a branch
-+ is called.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/07mmap.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/07mmap.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/07mmap.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/07mmap.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,53 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+mmap(2) -- File Memory Mapping
-+----------------------------------------------------------------------
-+In aufs, the file-mapped pages are shared between the file on a branch
-+and the virtual one in aufs by overriding vm_operation, particularly
-+->fault().
-+
-+In aufs_mmap(),
-+- get and store vm_ops of the real file on a branch.
-+- map the file of aufs by generic_file_mmap() and set aufs's vm
-+ operations.
-+
-+In aufs_fault(),
-+- get the file of aufs from the passed vma, sleep if needed.
-+- get the real file on a branch from the aufs file.
-+- a race may happen. for instance a multithreaded library. so some lock
-+ is implemented.
-+- call ->fault() in the previously stored vm_ops with setting the
-+ real file on a branch to vm_file.
-+- restore vm_file and wake_up if someone else got sleep.
-+
-+When a branch is added to or deleted from aufs, the same-named file may
-+unveil and its contents will be replaced by the new one when a process
-+read(2) through previously opened file.
-+(Some users may not want to refresh the filedata. For such users, I
-+have a plan to implement a mount option 'refrof' which decides to
-+refresh the opened files or not. See plan.txt too.)
-+In this case, an already mapped file will not be updated since the
-+contents are a part of a process already and it should not be changed by
-+aufs branch manipulation. (Even if MAP_SHARED is specified, currently).
-+Of course, in case of the deleting branch has a busy file, it cannot be
-+deleted from the union.
-+
-+In Unionfs, it took an approach which the memory pages mapped to
-+filedata are copied from the lower (real) file into the Unionfs's
-+virtual one and handles it by address_space operations. Recently Unionfs
-+changed it to this approach which aufs adopted since Jul 2006.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/08export.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/08export.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/08export.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/08export.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,59 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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 Aufs via NFS
-+----------------------------------------------------------------------
-+Here is an approach.
-+- like xino/xib, add a new file 'xigen' which stores aufs inode
-+ generation.
-+- iget_locked(): initialize aufs inode generation for a new inode, and
-+ store it in xigen file.
-+- destroy_inode(): increment aufs inode generation and store it in xigen
-+ file. it is necessary even if it is not unlinked, because any data of
-+ inode may be changed by UDBA.
-+- encode_fh(): for a root dir, simply return FILEID_ROOT. otherwise
-+ build file handle by
-+ + branch id (4 bytes)
-+ + superblock generation (4 bytes)
-+ + inode number (4 or 8 bytes)
-+ + parent dir inode number (4 or 8 bytes)
-+ + inode generation (4 bytes))
-+ + return value of exportfs_encode_fh() for the parent on a branch (4
-+ bytes)
-+ + file handle for a branch (by exportfs_encode_fh())
-+- fh_to_dentry():
-+ + find the index of a branch from its id in handle, and check it is
-+ still exist in aufs.
-+ + 1st level: get the inode number from handle and search it in cache.
-+ + 2nd level: if not found, get the parent inode number from handle and
-+ search it in cache. and then open the parent dir, find the matching
-+ inode number by vfs_readdir() and get its name, and call
-+ lookup_one_len() for the target dentry.
-+ + 3rd level: if the parent dir is not cached, call
-+ exportfs_decode_fh() for a branch and get the parent on a branch,
-+ build a pathname of it, convert it a pathname in aufs, call
-+ path_lookup(). now aufs gets a parent dir dentry, then handle it as
-+ the 2nd level.
-+ + to open the dir, aufs needs struct vfsmount. aufs keeps vfsmount
-+ for every branch, but not itself. to get this, (currently) aufs
-+ searches in current->nsproxy->mnt_ns list. it may not be a good
-+ idea, but I didn't get other approach.
-+ + test the generation of the gotten inode.
-+- every inode operation: they may get EBUSY due to UDBA. in this case,
-+ convert it into ESTALE for NFSD.
-+- readdir(): call lockdep_on/off() because filldir in NFSD calls
-+ lookup_one_len(), vfs_getattr(), encode_fh() and others.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/09shwh.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/09shwh.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/09shwh.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/09shwh.txt 2009-09-21 21:48:58.761610020 +0000
-@@ -0,0 +1,53 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+Show Whiteout Mode (shwh)
-+----------------------------------------------------------------------
-+Generally aufs hides the name of whiteouts. But in some cases, to show
-+them is very useful for users. For instance, creating a new middle layer
-+(branch) by merging existing layers.
-+
-+(borrowing aufs1 HOW-TO from a user, Michael Towers)
-+When you have three branches,
-+- Bottom: 'system', squashfs (underlying base system), read-only
-+- Middle: 'mods', squashfs, read-only
-+- Top: 'overlay', ram (tmpfs), read-write
-+
-+The top layer is loaded at boot time and saved at shutdown, to preserve
-+the changes made to the system during the session.
-+When larger changes have been made, or smaller changes have accumulated,
-+the size of the saved top layer data grows. At this point, it would be
-+nice to be able to merge the two overlay branches ('mods' and 'overlay')
-+and rewrite the 'mods' squashfs, clearing the top layer and thus
-+restoring save and load speed.
-+
-+This merging is simplified by the use of another aufs mount, of just the
-+two overlay branches using the 'shwh' option.
-+# mount -t aufs -o ro,shwh,br:/livesys/overlay=ro+wh:/livesys/mods=rr+wh \
-+ aufs /livesys/merge_union
-+
-+A merged view of these two branches is then available at
-+/livesys/merge_union, and the new feature is that the whiteouts are
-+visible!
-+Note that in 'shwh' mode the aufs mount must be 'ro', which will disable
-+writing to all branches. Also the default mode for all branches is 'ro'.
-+It is now possible to save the combined contents of the two overlay
-+branches to a new squashfs, e.g.:
-+# mksquashfs /livesys/merge_union /path/to/newmods.squash
-+
-+This new squashfs archive can be stored on the boot device and the
-+initramfs will use it to replace the old one at the next boot.
-diff -uprN -x .git linux-2.6.31/Documentation/filesystems/aufs/design/99plan.txt aufs2-2.6.git/Documentation/filesystems/aufs/design/99plan.txt
---- linux-2.6.31/Documentation/filesystems/aufs/design/99plan.txt 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/Documentation/filesystems/aufs/design/99plan.txt 2009-09-21 21:48:58.765776628 +0000
-@@ -0,0 +1,96 @@
-+
-+# Copyright (C) 2005-2009 Junjiro R. Okajima
-+#
-+# This program 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
-+
-+Plan
-+
-+Restoring some features which was implemented in aufs1.
-+They were dropped in aufs2 in order to make source files simpler and
-+easier to be reviewed.
-+
-+
-+Test Only the Highest One for the Directory Permission (dirperm1 option)
-+----------------------------------------------------------------------
-+Let's try case study.
-+- aufs has two branches, upper readwrite and lower readonly.
-+ /au = /rw + /ro
-+- "dirA" exists under /ro, but /rw. and its mode is 0700.
-+- user invoked "chmod a+rx /au/dirA"
-+- then "dirA" becomes world readable?
-+
-+In this case, /ro/dirA is still 0700 since it exists in readonly branch,
-+or it may be a natively readonly filesystem. If aufs respects the lower
-+branch, it should not respond readdir request from other users. But user
-+allowed it by chmod. Should really aufs rejects showing the entries
-+under /ro/dirA?
-+
-+To be honest, I don't have a best solution for this case. So I
-+implemented 'dirperm1' and 'nodirperm1' option in aufs1, and leave it to
-+users.
-+When dirperm1 is specified, aufs checks only the highest one for the
-+directory permission, and shows the entries. Otherwise, as usual, checks
-+every dir existing on all branches and rejects the request.
-+
-+As a side effect, dirperm1 option improves the performance of aufs
-+because the number of permission check is reduced.
-+
-+
-+Being Another Aufs's Readonly Branch (robr)
-+----------------------------------------------------------------------
-+Aufs1 allows aufs to be another aufs's readonly branch.
-+This feature was developed by a user's request. But it may not be used
-+currecnly.
-+
-+
-+Copy-up on Open (coo=)
-+----------------------------------------------------------------------
-+By default the internal copy-up is executed when it is really necessary.
-+It is not done when a file is opened for writing, but when write(2) is
-+done. Users who have many (over 100) branches want to know and analyse
-+when and what file is copied-up. To insert a new upper branch which
-+contains such files only may improve the performance of aufs.
-+
-+Aufs1 implemented "coo=none | leaf | all" option.
-+
-+
-+Refresh the Opened File (refrof)
-+----------------------------------------------------------------------
-+This option is implemented in aufs1 but incomplete.
-+
-+When user reads from a file, he expects to get its latest filedata
-+generally. If the file is removed and a new same named file is created,
-+the content he gets is unchanged, ie. the unlinked filedata.
-+
-+Let's try case study again.
-+- aufs has two branches.
-+ /au = /rw + /ro
-+- "fileA" exists under /ro, but /rw.
-+- user opened "/au/fileA".
-+- he or someone else inserts a branch (/new) between /rw and /ro.
-+ /au = /rw + /new + /ro
-+- the new branch has "fileA".
-+- user reads from the opened "fileA"
-+- which filedata should aufs return, from /ro or /new?
-+
-+Some people says it has to be "from /ro" and it is a semantics of Unix.
-+The others say it should be "from /new" because the file is not removed
-+and it is equivalent to the case of someone else modifies the file.
-+
-+Here again I don't have a best and final answer. I got an idea to
-+implement 'refrof' and 'norefrof' option. When 'refrof' (REFResh the
-+Opened File) is specified (by default), aufs returns the filedata from
-+/new.
-+Otherwise from /new.
-diff -uprN -x .git linux-2.6.31/fs/Kconfig aufs2-2.6.git/fs/Kconfig
---- linux-2.6.31/fs/Kconfig 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/fs/Kconfig 2009-09-21 21:49:23.315008102 +0000
-@@ -187,6 +187,7 @@ source "fs/sysv/Kconfig"
- source "fs/ufs/Kconfig"
- source "fs/exofs/Kconfig"
- source "fs/nilfs2/Kconfig"
-+source "fs/aufs/Kconfig"
-
- endif # MISC_FILESYSTEMS
-
-diff -uprN -x .git linux-2.6.31/fs/Makefile aufs2-2.6.git/fs/Makefile
---- linux-2.6.31/fs/Makefile 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/fs/Makefile 2009-09-14 14:52:35.065948532 +0000
-@@ -124,3 +124,4 @@ obj-$(CONFIG_OCFS2_FS) += ocfs2/
- obj-$(CONFIG_BTRFS_FS) += btrfs/
- obj-$(CONFIG_GFS2_FS) += gfs2/
- obj-$(CONFIG_EXOFS_FS) += exofs/
-+obj-$(CONFIG_AUFS_FS) += aufs/
-diff -uprN -x .git linux-2.6.31/fs/aufs/Kconfig aufs2-2.6.git/fs/aufs/Kconfig
---- linux-2.6.31/fs/aufs/Kconfig 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/Kconfig 2009-09-21 21:49:23.374524295 +0000
-@@ -0,0 +1,140 @@
-+config AUFS_FS
-+ bool "Aufs (Advanced multi layered unification filesystem) support"
-+ depends on EXPERIMENTAL
-+ help
-+ Aufs is a stackable unification filesystem such as Unionfs,
-+ which unifies several directories and provides a merged single
-+ directory.
-+ In the early days, aufs was entirely re-designed and
-+ re-implemented Unionfs Version 1.x series. Introducing many
-+ original ideas, approaches and improvements, it becomes totally
-+ different from Unionfs while keeping the basic features.
-+
-+if AUFS_FS
-+choice
-+ prompt "Maximum number of branches"
-+ default AUFS_BRANCH_MAX_127
-+ help
-+ Specifies the maximum number of branches (or member directories)
-+ in a single aufs. The larger value consumes more system
-+ resources and has a minor impact to performance.
-+config AUFS_BRANCH_MAX_127
-+ bool "127"
-+ help
-+ Specifies the maximum number of branches (or member directories)
-+ in a single aufs. The larger value consumes more system
-+ resources and has a minor impact to performance.
-+config AUFS_BRANCH_MAX_511
-+ bool "511"
-+ help
-+ Specifies the maximum number of branches (or member directories)
-+ in a single aufs. The larger value consumes more system
-+ resources and has a minor impact to performance.
-+config AUFS_BRANCH_MAX_1023
-+ bool "1023"
-+ help
-+ Specifies the maximum number of branches (or member directories)
-+ in a single aufs. The larger value consumes more system
-+ resources and has a minor impact to performance.
-+config AUFS_BRANCH_MAX_32767
-+ bool "32767"
-+ help
-+ Specifies the maximum number of branches (or member directories)
-+ in a single aufs. The larger value consumes more system
-+ resources and has a minor impact to performance.
-+endchoice
-+
-+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.
-+
-+config AUFS_EXPORT
-+ bool "NFS-exportable aufs"
-+ depends on EXPORTFS = y
-+ help
-+ If you want to export your mounted aufs via NFS, then enable this
-+ option. There are several requirements for this configuration.
-+ See detail in aufs.5.
-+
-+config AUFS_RDU
-+ bool "Readdir in userspace"
-+ help
-+ If you have millions of files under a single aufs directory, and
-+ meet the out of memory, then enable this option and set
-+ environment variables for your readdir(3).
-+ See detail in aufs.5.
-+
-+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_BR_RAMFS
-+ bool "Ramfs (initramfs/rootfs) as an aufs branch"
-+ help
-+ If you want to use ramfs as an aufs branch fs, then enable this
-+ option. Generally tmpfs is recommended.
-+ Aufs prohibited them to be a branch fs by default, because
-+ initramfs becomes unusable after switch_root or something
-+ generally. If you sets initramfs as an aufs branch and boot your
-+ system by switch_root, you will meet a problem easily since the
-+ files in initramfs may be inaccessible.
-+ Unless you are going to use ramfs as an aufs branch fs without
-+ switch_root or something, leave it N.
-+
-+config AUFS_BR_FUSE
-+ bool "Fuse fs as an aufs branch"
-+ depends on FUSE_FS
-+ select AUFS_POLL
-+ help
-+ If you want to use fuse-based userspace filesystem as an aufs
-+ branch fs, then enable this option.
-+ It implements the internal poll(2) operation which is
-+ implemented by fuse only (curretnly).
-+
-+config AUFS_DEBUG
-+ bool "Debug aufs"
-+ 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_BDEV_LOOP
-+ bool
-+ depends on BLK_DEV_LOOP
-+ default y
-+ help
-+ Automatic configuration for internal use.
-+ Convert =[ym] into =y.
-+
-+config AUFS_INO_T_64
-+ bool
-+ depends on AUFS_EXPORT
-+ depends on 64BIT && !(ALPHA || S390)
-+ default y
-+ help
-+ Automatic configuration for internal use.
-+ /* typedef unsigned long/int __kernel_ino_t */
-+ /* alpha and s390x are int */
-+
-+config AUFS_POLL
-+ bool
-+ help
-+ Automatic configuration for internal use.
-+endif
-diff -uprN -x .git linux-2.6.31/fs/aufs/Makefile aufs2-2.6.git/fs/aufs/Makefile
---- linux-2.6.31/fs/aufs/Makefile 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/Makefile 2009-09-21 21:49:23.374524295 +0000
-@@ -0,0 +1,23 @@
-+
-+include ${srctree}/${src}/magic.mk
-+
-+obj-$(CONFIG_AUFS_FS) += aufs.o
-+aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
-+ wkq.o vfsub.o dcsub.o \
-+ cpup.o whout.o plink.o wbr_policy.o \
-+ dinfo.o dentry.o \
-+ finfo.o file.o f_op.o \
-+ dir.o vdir.o \
-+ iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \
-+ ioctl.o
-+
-+# all are boolean
-+aufs-$(CONFIG_SYSFS) += sysfs.o
-+aufs-$(CONFIG_DEBUG_FS) += dbgaufs.o
-+aufs-$(CONFIG_AUFS_BDEV_LOOP) += loop.o
-+aufs-$(CONFIG_AUFS_HINOTIFY) += hinotify.o
-+aufs-$(CONFIG_AUFS_EXPORT) += export.o
-+aufs-$(CONFIG_AUFS_POLL) += poll.o
-+aufs-$(CONFIG_AUFS_RDU) += rdu.o
-+aufs-$(CONFIG_AUFS_DEBUG) += debug.o
-+aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o
-diff -uprN -x .git linux-2.6.31/fs/aufs/aufs.h aufs2-2.6.git/fs/aufs/aufs.h
---- linux-2.6.31/fs/aufs/aufs.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/aufs.h 2009-09-21 21:49:23.374524295 +0000
-@@ -0,0 +1,51 @@
++ will be empty. About XINO files, see the aufs manual.
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/aufs.h linux-2.6.34/fs/aufs/aufs.h
+--- linux-2.6.34.org/fs/aufs/aufs.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/aufs.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,61 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+#ifdef __KERNEL__
+
++#define AuStub(type, name, body, ...) \
++ static inline type name(__VA_ARGS__) { body; }
++
++#define AuStubVoid(name, ...) \
++ AuStub(void, name, , __VA_ARGS__)
++#define AuStubInt0(name, ...) \
++ AuStub(int, name, return 0, __VA_ARGS__)
++
+#include "debug.h"
+
+#include "branch.h"
+#include "dbgaufs.h"
+#include "dentry.h"
+#include "dir.h"
++#include "dynop.h"
+#include "file.h"
+#include "fstype.h"
+#include "inode.h"
+#include "loop.h"
+#include "module.h"
++/* never include ./mtx.h */
+#include "opts.h"
+#include "rwsem.h"
+#include "spl.h"
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/branch.c aufs2-2.6.git/fs/aufs/branch.c
---- linux-2.6.31/fs/aufs/branch.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/branch.c 2009-09-21 21:49:23.374524295 +0000
-@@ -0,0 +1,970 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/branch.c linux-2.6.34/fs/aufs/branch.c
+--- linux-2.6.34.org/fs/aufs/branch.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/branch.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,1007 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+#include <linux/file.h>
++#include <linux/statfs.h>
+#include "aufs.h"
+
+/*
+{
+ int i;
+ struct au_wbr *wbr;
++ struct au_dykey **key;
+
+ if (br->br_xino.xi_file)
+ fput(br->br_xino.xi_file);
+ AuRwDestroy(&wbr->wbr_wh_rwsem);
+ }
+
++ key = br->br_dykey;
++ for (i = 0; i < AuBrDynOp; i++, key++)
++ if (*key)
++ au_dy_put(*key);
++ else
++ break;
++
+ /* some filesystems acquire extra lock */
-+ lockdep_off();
++ /* lockdep_off(); */
+ mntput(br->br_mnt);
-+ lockdep_on();
++ /* lockdep_on(); */
+
+ kfree(wbr);
+ kfree(br);
+{
+ if (unlikely(h_d1 == h_d2))
+ return 1;
-+ return !!au_test_subdir(h_d1, h_d2)
-+ || !!au_test_subdir(h_d2, h_d1)
++ return au_test_subdir(h_d1, h_d2)
++ || au_test_subdir(h_d2, h_d1)
+ || au_test_loopback_overlap(sb, h_d1, h_d2)
+ || au_test_loopback_overlap(sb, h_d2, h_d1);
+}
+{
+ struct au_branch *add_branch;
+ struct dentry *root;
++ int err;
+
++ err = -ENOMEM;
+ root = sb->s_root;
+ add_branch = kmalloc(sizeof(*add_branch), GFP_NOFS);
+ if (unlikely(!add_branch))
+ goto out_br;
+ }
+
-+ if (unlikely(au_sbr_realloc(au_sbi(sb), new_nbranch)
-+ || au_di_realloc(au_di(root), new_nbranch)
-+ || au_ii_realloc(au_ii(root->d_inode), new_nbranch)))
-+ goto out_wbr;
-+ return add_branch; /* success */
++ err = au_sbr_realloc(au_sbi(sb), new_nbranch);
++ if (!err)
++ err = au_di_realloc(au_di(root), new_nbranch);
++ if (!err)
++ err = au_ii_realloc(au_ii(root->d_inode), new_nbranch);
++ if (!err)
++ return add_branch; /* success */
+
-+ out_wbr:
+ kfree(add_branch->br_wbr);
++
+ out_br:
+ kfree(add_branch);
+ out:
-+ return ERR_PTR(-ENOMEM);
++ return ERR_PTR(err);
+}
+
+/*
+{
+ int err;
+
-+ err = 0;
-+ if (unlikely(au_br_writable(brperm) && IS_RDONLY(inode))) {
-+ AuErr("write permission for readonly mount or inode, %s\n",
-+ path);
-+ err = -EINVAL;
-+ }
++ err = (au_br_writable(brperm) && IS_RDONLY(inode));
++ if (!err)
++ goto out;
+
++ err = -EINVAL;
++ pr_err("write permission for readonly mount or inode, %s\n", path);
++
++ out:
+ return err;
+}
+
+ err = 1;
+ if (!remount) {
+ err = -EINVAL;
-+ AuErr("%s duplicated\n", add->pathname);
++ pr_err("%s duplicated\n", add->pathname);
+ }
+ goto out;
+ }
+ err = -ENOSPC; /* -E2BIG; */
+ if (unlikely(AUFS_BRANCH_MAX <= add->bindex
+ || AUFS_BRANCH_MAX - 1 <= bend)) {
-+ AuErr("number of branches exceeded %s\n", add->pathname);
++ pr_err("number of branches exceeded %s\n", add->pathname);
+ goto out;
+ }
+
+ err = -EDOM;
+ if (unlikely(add->bindex < 0 || bend + 1 < add->bindex)) {
-+ AuErr("bad index %d\n", add->bindex);
++ pr_err("bad index %d\n", add->bindex);
+ goto out;
+ }
+
+ inode = add->path.dentry->d_inode;
+ err = -ENOENT;
+ if (unlikely(!inode->i_nlink)) {
-+ AuErr("no existence %s\n", add->pathname);
++ pr_err("no existence %s\n", add->pathname);
+ goto out;
+ }
+
+ err = -EINVAL;
+ if (unlikely(inode->i_sb == sb)) {
-+ AuErr("%s must be outside\n", add->pathname);
++ pr_err("%s must be outside\n", add->pathname);
+ goto out;
+ }
+
+ if (unlikely(au_test_fs_unsuppoted(inode->i_sb))) {
-+ AuErr("unsupported filesystem, %s (%s)\n",
-+ add->pathname, au_sbtype(inode->i_sb));
++ pr_err("unsupported filesystem, %s (%s)\n",
++ add->pathname, au_sbtype(inode->i_sb));
+ goto out;
+ }
+
+ for (bindex = 0; bindex <= bend; bindex++)
+ if (unlikely(test_overlap(sb, add->path.dentry,
+ au_h_dptr(root, bindex)))) {
-+ AuErr("%s is overlapped\n", add->pathname);
++ pr_err("%s is overlapped\n", add->pathname);
+ goto out;
+ }
+
+ if ((h_inode->i_mode & S_IALLUGO) != (inode->i_mode & S_IALLUGO)
+ || h_inode->i_uid != inode->i_uid
+ || h_inode->i_gid != inode->i_gid)
-+ AuWarn("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n",
-+ add->pathname,
-+ inode->i_uid, inode->i_gid,
-+ (inode->i_mode & S_IALLUGO),
-+ h_inode->i_uid, h_inode->i_gid,
-+ (h_inode->i_mode & S_IALLUGO));
++ pr_warning("uid/gid/perm %s %u/%u/0%o, %u/%u/0%o\n",
++ add->pathname,
++ inode->i_uid, inode->i_gid,
++ (inode->i_mode & S_IALLUGO),
++ h_inode->i_uid, h_inode->i_gid,
++ (h_inode->i_mode & S_IALLUGO));
+ }
+
+ out:
+ bindex = au_br_index(sb, br->br_id);
+ if (0 <= bindex) {
+ hdir = au_hi(sb->s_root->d_inode, bindex);
-+ au_hin_imtx_lock_nested(hdir, AuLsc_I_PARENT);
++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT);
+ } else {
+ h_mtx = &h_root->d_inode->i_mutex;
+ mutex_lock_nested(h_mtx, AuLsc_I_PARENT);
+ wbr_wh_write_unlock(wbr);
+ }
+ if (hdir)
-+ au_hin_imtx_unlock(hdir);
++ au_hn_imtx_unlock(hdir);
+ else
+ mutex_unlock(h_mtx);
+ br->br_perm = old_perm;
+ int perm, struct path *path)
+{
+ int err;
++ struct kstatfs kst;
+ struct au_wbr *wbr;
++ struct dentry *h_dentry;
+
+ wbr = br->br_wbr;
+ au_rw_init(&wbr->wbr_wh_rwsem);
+ atomic_set(&wbr->wbr_wh_running, 0);
+ wbr->wbr_bytes = 0;
+
-+ err = au_br_init_wh(sb, br, perm, path->dentry);
++ /*
++ * a limit for rmdir/rename a dir
++ * cf. AUFS_MAX_NAMELEN in include/linux/aufs_type.h
++ */
++ h_dentry = path->dentry;
++ err = vfs_statfs(h_dentry, &kst);
++ if (unlikely(err))
++ goto out;
++ err = -EINVAL;
++ if (kst.f_namelen >= NAME_MAX)
++ err = au_br_init_wh(sb, br, perm, h_dentry);
++ else
++ pr_err("%.*s(%s), unsupported namelen %ld\n",
++ AuDLNPair(h_dentry), au_sbtype(h_dentry->d_sb),
++ kst.f_namelen);
+
++ out:
+ return err;
+}
+
+ mutex_init(&br->br_xino.xi_nondir_mtx);
+ br->br_perm = add->perm;
+ br->br_mnt = add->path.mnt; /* set first, mntget() later */
++ spin_lock_init(&br->br_dykey_lock);
++ memset(br->br_dykey, 0, sizeof(br->br_dykey));
+ atomic_set(&br->br_count, 0);
+ br->br_xino_upper = AUFS_XINO_TRUNC_INIT;
+ atomic_set(&br->br_xino_running, 0);
+ hip = iinfo->ii_hinode + bindex;
+ memmove(hip + 1, hip, sizeof(*hip) * amount);
+ hip->hi_inode = NULL;
-+ au_hin_init(hip, NULL);
++ au_hn_init(hip);
+ iinfo->ii_bend++;
+ if (unlikely(bend < 0))
+ iinfo->ii_bstart = 0;
+
+ root = sb->s_root;
+ root_inode = root->d_inode;
-+ au_plink_block_maintain(sb);
++ au_plink_maint_block(sb);
+ bend = au_sbend(sb);
+ amount = bend + 1 - bindex;
+ au_br_do_add_brp(au_sbi(sb), bindex, br, bend, amount);
+ au_add_nlink(root_inode, h_dentry->d_inode);
+
+ /*
-+ * this test/set prevents aufs from handling unnecesary inotify events
++ * this test/set prevents aufs from handling unnecesary notify events
+ * of xino files, in a case of re-adding a writable branch which was
+ * once detached from aufs.
+ */
+ */
+
+/* to show the line number, do not make it inlined function */
-+#define AuVerbose(do_info, fmt, args...) do { \
++#define AuVerbose(do_info, fmt, ...) do { \
+ if (do_info) \
-+ AuInfo(fmt, ##args); \
++ pr_info(fmt, ##__VA_ARGS__); \
+} while (0)
+
+/*
+ p = krealloc(sbinfo->si_branch, sizeof(*p) * bend, GFP_NOFS);
+ if (p)
+ sbinfo->si_branch = p;
++ /* harmless error */
+}
+
+static void au_br_do_del_hdp(struct au_dinfo *dinfo, const aufs_bindex_t bindex,
+
+ AuRwMustWriteLock(&dinfo->di_rwsem);
+
-+ hdp = dinfo->di_hdentry + bindex;
++ hdp = dinfo->di_hdentry;
+ if (bindex < bend)
-+ memmove(hdp, hdp + 1, sizeof(*hdp) * (bend - bindex));
-+ dinfo->di_hdentry[0 + bend].hd_dentry = NULL;
++ memmove(hdp + bindex, hdp + bindex + 1,
++ sizeof(*hdp) * (bend - bindex));
++ hdp[0 + bend].hd_dentry = NULL;
+ dinfo->di_bend--;
+
-+ p = krealloc(dinfo->di_hdentry, sizeof(*p) * bend, GFP_NOFS);
++ p = krealloc(hdp, sizeof(*p) * bend, GFP_NOFS);
+ if (p)
+ dinfo->di_hdentry = p;
++ /* harmless error */
+}
+
+static void au_br_do_del_hip(struct au_iinfo *iinfo, const aufs_bindex_t bindex,
+ if (bindex < bend)
+ memmove(hip, hip + 1, sizeof(*hip) * (bend - bindex));
+ iinfo->ii_hinode[0 + bend].hi_inode = NULL;
-+ au_hin_init(iinfo->ii_hinode + bend, NULL);
++ au_hn_init(iinfo->ii_hinode + bend);
+ iinfo->ii_bend--;
+
+ p = krealloc(iinfo->ii_hinode, sizeof(*p) * bend, GFP_NOFS);
+ if (p)
+ iinfo->ii_hinode = p;
++ /* harmless error */
+}
+
+static void au_br_do_del(struct super_block *sb, aufs_bindex_t bindex,
+
+ root = sb->s_root;
+ inode = root->d_inode;
-+ au_plink_block_maintain(sb);
++ au_plink_maint_block(sb);
+ sbinfo = au_sbi(sb);
+ bend = sbinfo->si_bend;
+
+ if (remount)
+ goto out; /* success */
+ err = -ENOENT;
-+ AuErr("%s no such branch\n", del->pathname);
++ pr_err("%s no such branch\n", del->pathname);
+ goto out;
+ }
+ AuDbg("bindex b%d\n", bindex);
+ /* revert */
+ rerr = au_br_init_wh(sb, br, br->br_perm, del->h_path.dentry);
+ if (rerr)
-+ AuWarn("failed re-creating base whiteout, %s. (%d)\n",
-+ del->pathname, rerr);
++ pr_warning("failed re-creating base whiteout, %s. (%d)\n",
++ del->pathname, rerr);
+ out:
+ return err;
+}
+{
+#ifdef CONFIG_IMA
+ /* since it doesn't support mark_files_ro() */
-+ AuWarn("RW -> RO makes IMA to produce wrong message");
++ pr_warning("RW -> RO makes IMA to produce wrong message");
+#endif
+}
+
+ continue;
+ }
+
-+ hf = au_h_fptr(file, bstart);
++ hf = au_hf_top(file);
+ FiMustNoWaiters(file);
+ fi_read_unlock(file);
+
+ struct au_branch *br;
+
+ root = sb->s_root;
-+ au_plink_block_maintain(sb);
++ au_plink_maint_block(sb);
+ bindex = au_find_dbindex(root, mod->h_root);
+ if (bindex < 0) {
+ if (remount)
+ return 0; /* success */
+ err = -ENOENT;
-+ AuErr("%s no such branch\n", mod->path);
++ pr_err("%s no such branch\n", mod->path);
+ goto out;
+ }
+ AuDbg("bindex b%d\n", bindex);
+ out:
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/branch.h aufs2-2.6.git/fs/aufs/branch.h
---- linux-2.6.31/fs/aufs/branch.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/branch.h 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,219 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/branch.h linux-2.6.34/fs/aufs/branch.h
+--- linux-2.6.34.org/fs/aufs/branch.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/branch.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,225 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/aufs_type.h>
++#include "dynop.h"
+#include "rwsem.h"
+#include "super.h"
+
+struct au_wbr {
+ struct au_rwsem wbr_wh_rwsem;
+ struct dentry *wbr_wh[AuBrWh_Last];
-+ atomic_t wbr_wh_running;
++ 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_orph wbr_wh[AuBrWh_ORPH] /* dir for orphans */
+ unsigned long long wbr_bytes;
+};
+
++/* ext2 has 3 types of operations at least, ext3 has 4 */
++#define AuBrDynOp (AuDyLast * 4)
++
+/* protected by superblock rwsem */
+struct au_branch {
+ struct au_xino_file br_xino;
+
+ int br_perm;
+ struct vfsmount *br_mnt;
++ spinlock_t br_dykey_lock;
++ struct au_dykey *br_dykey[AuBrDynOp];
+ atomic_t br_count;
+
+ struct au_wbr *br_wbr;
+ ? -EROFS : 0;
+}
+
-+static inline int au_br_hinotifyable(int brperm __maybe_unused)
++static inline int au_br_hnotifyable(int brperm __maybe_unused)
+{
-+#ifdef CONFIG_AUFS_HINOTIFY
++#ifdef CONFIG_AUFS_HNOTIFY
+ return brperm != AuBrPerm_RR && brperm != AuBrPerm_RRWH;
+#else
+ return 0;
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_BRANCH_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/cpup.c aufs2-2.6.git/fs/aufs/cpup.c
---- linux-2.6.31/fs/aufs/cpup.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/cpup.c 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,1048 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/conf.mk linux-2.6.34/fs/aufs/conf.mk
+--- linux-2.6.34.org/fs/aufs/conf.mk 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/conf.mk 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,33 @@
++
++AuConfStr = CONFIG_AUFS_FS=${CONFIG_AUFS_FS}
++
++define AuConf
++ifdef ${1}
++AuConfStr += ${1}=${${1}}
++endif
++endef
++
++$(foreach i, BRANCH_MAX_127 BRANCH_MAX_511 BRANCH_MAX_1023 BRANCH_MAX_32767 \
++ HNOTIFY HFSNOTIFY HINOTIFY \
++ EXPORT INO_T_64 \
++ RDU \
++ SP_IATTR \
++ SHWH \
++ BR_RAMFS \
++ BR_FUSE POLL \
++ BR_HFSPLUS \
++ BDEV_LOOP \
++ DEBUG MAGIC_SYSRQ, \
++ $(eval $(call AuConf,CONFIG_AUFS_${i})))
++
++AuConfName = ${obj}/conf.str
++${AuConfName}.tmp: FORCE
++ @echo ${AuConfStr} | tr ' ' '\n' | sed -e 's/^/"/' -e 's/$$/\\n"/' > $@
++${AuConfName}: ${AuConfName}.tmp
++ @diff -q $< $@ > /dev/null 2>&1 || { \
++ echo ' GEN ' $@; \
++ cp -p $< $@; \
++ }
++FORCE:
++clean-files += ${AuConfName} ${AuConfName}.tmp
++${obj}/sysfs.o: ${AuConfName}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/cpup.c linux-2.6.34/fs/aufs/cpup.c
+--- linux-2.6.34.org/fs/aufs/cpup.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/cpup.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,1057 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+ h_inode = au_h_iptr(inode, au_ibstart(inode));
+ fsstack_copy_attr_times(inode, h_inode);
-+ vfsub_copy_inode_size(inode, h_inode);
++ fsstack_copy_inode_size(inode, h_inode);
+}
+
+void au_cpup_attr_nlink(struct inode *inode, int force)
+
+ err = vfsub_notify_change(&dt->dt_h_path, &attr);
+ if (unlikely(err))
-+ AuWarn("restoring timestamps failed(%d). ignored\n", err);
++ pr_warning("restoring timestamps failed(%d). ignored\n", err);
+}
+
+/* ---------------------------------------------------------------------- */
+ * we need to close the file.
+ */
+static int au_cp_regular(struct dentry *dentry, aufs_bindex_t bdst,
-+ aufs_bindex_t bsrc, loff_t len)
++ aufs_bindex_t bsrc, loff_t len)
+{
+ int err, i;
+ enum { SRC, DST };
+ goto out;
+
+ err = -ENOMEM;
-+ sym = __getname();
++ sym = __getname_gfp(GFP_NOFS);
+ if (unlikely(!sym))
+ goto out;
+
+ } else
+ /* todo: cpup_wh_file? */
+ /* udba work */
-+ au_update_brange(inode, 1);
++ au_update_ibrange(inode, /*do_put_zero*/1);
+ }
+
+ old_ibstart = au_ibstart(inode);
+ err = cpup_iattr(dentry, bdst, h_src);
+ isdir = S_ISDIR(dst_inode->i_mode);
+ if (!err) {
-+ if (bdst < old_ibstart)
++ if (bdst < old_ibstart) {
++ if (S_ISREG(inode->i_mode)) {
++ err = au_dy_iaop(inode, bdst, dst_inode);
++ if (unlikely(err))
++ goto out_rev;
++ }
+ au_set_ibstart(inode, bdst);
++ }
+ au_set_h_iptr(inode, bdst, au_igrab(dst_inode),
+ au_hi_flags(inode, isdir));
+ mutex_unlock(&dst_inode->i_mutex);
+ }
+
+ /* revert */
++out_rev:
+ h_path.dentry = h_parent;
+ mutex_unlock(&dst_inode->i_mutex);
+ au_dtime_store(&dt, dst_parent, &h_path);
+ aufs_bindex_t bstart;
+ struct au_dinfo *dinfo;
+ struct dentry *h_d_dst, *h_d_start;
++ struct au_hdentry *hdp;
+
+ dinfo = au_di(dentry);
+ AuRwMustWriteLock(&dinfo->di_rwsem);
+
+ bstart = dinfo->di_bstart;
-+ h_d_dst = dinfo->di_hdentry[0 + bdst].hd_dentry;
++ hdp = dinfo->di_hdentry;
++ h_d_dst = hdp[0 + bdst].hd_dentry;
+ dinfo->di_bstart = bdst;
-+ dinfo->di_hdentry[0 + bdst].hd_dentry = wh_dentry;
-+ h_d_start = dinfo->di_hdentry[0 + bstart].hd_dentry;
++ hdp[0 + bdst].hd_dentry = wh_dentry;
++ h_d_start = hdp[0 + bstart].hd_dentry;
+ if (file)
-+ dinfo->di_hdentry[0 + bstart].hd_dentry
-+ = au_h_fptr(file, au_fbstart(file))->f_dentry;
++ hdp[0 + bstart].hd_dentry = au_hf_top(file)->f_dentry;
+ err = au_cpup_single(dentry, bdst, bstart, len, !AuCpup_DTIME,
+ /*h_parent*/NULL);
+ if (!err && file) {
+ err = au_reopen_nondir(file);
-+ dinfo->di_hdentry[0 + bstart].hd_dentry = h_d_start;
++ hdp[0 + bstart].hd_dentry = h_d_start;
+ }
-+ dinfo->di_hdentry[0 + bdst].hd_dentry = h_d_dst;
++ hdp[0 + bdst].hd_dentry = h_d_dst;
+ dinfo->di_bstart = bstart;
+
+ return err;
+
+ dget(wh_dentry);
+ h_path.dentry = wh_dentry;
-+ err = vfsub_unlink(h_parent->d_inode, &h_path, /*force*/0);
++ if (!S_ISDIR(wh_dentry->d_inode->i_mode))
++ err = vfsub_unlink(h_parent->d_inode, &h_path, /*force*/0);
++ else
++ err = vfsub_rmdir(h_parent->d_inode, &h_path);
+ if (unlikely(err)) {
+ AuIOErr("failed remove copied-up tmp file %.*s(%d)\n",
+ AuDLNPair(wh_dentry), err);
+ h_orph = wbr->wbr_orph;
+
+ h_parent = dget(au_h_dptr(parent, bdst));
-+ au_set_h_dptr(parent, bdst, NULL);
+ au_set_h_dptr(parent, bdst, dget(h_orph));
+ h_tmpdir = h_orph->d_inode;
-+ au_set_h_iptr(dir, bdst, NULL, 0);
+ au_set_h_iptr(dir, bdst, au_igrab(h_tmpdir), /*flags*/0);
+
+ /* this temporary unlock is safe */
+ if (file)
-+ h_dentry = au_h_fptr(file, au_fbstart(file))->f_dentry;
++ h_dentry = au_hf_top(file)->f_dentry;
+ else
+ h_dentry = au_h_dptr(dentry, au_dbstart(dentry));
+ h_inode = h_dentry->d_inode;
+ mutex_unlock(&h_inode->i_mutex);
+ mutex_lock_nested(&h_tmpdir->i_mutex, AuLsc_I_PARENT3);
+ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
++ /* todo: au_h_open_pre()? */
+ }
+
+ if (!au_test_h_perm_sio(h_tmpdir, MAY_EXEC | MAY_WRITE))
+
+ if (h_orph) {
+ mutex_unlock(&h_tmpdir->i_mutex);
-+ au_set_h_iptr(dir, bdst, NULL, 0);
++ /* todo: au_h_open_post()? */
+ 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);
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/cpup.h aufs2-2.6.git/fs/aufs/cpup.h
---- linux-2.6.31/fs/aufs/cpup.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/cpup.h 2009-09-21 21:49:23.377863284 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/cpup.h linux-2.6.34/fs/aufs/cpup.h
+--- linux-2.6.34.org/fs/aufs/cpup.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/cpup.h 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,81 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_CPUP_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/dbgaufs.c aufs2-2.6.git/fs/aufs/dbgaufs.c
---- linux-2.6.31/fs/aufs/dbgaufs.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dbgaufs.c 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,331 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dbgaufs.c linux-2.6.34/fs/aufs/dbgaufs.c
+--- linux-2.6.34.org/fs/aufs/dbgaufs.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dbgaufs.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,334 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+}
+
+static const struct file_operations dbgaufs_xib_fop = {
++ .owner = THIS_MODULE,
+ .open = dbgaufs_xib_open,
+ .release = dbgaufs_xi_release,
+ .read = dbgaufs_xi_read
+}
+
+static const struct file_operations dbgaufs_xino_fop = {
++ .owner = THIS_MODULE,
+ .open = dbgaufs_xino_open,
+ .release = dbgaufs_xi_release,
+ .read = dbgaufs_xi_read
+}
+
+static const struct file_operations dbgaufs_xigen_fop = {
++ .owner = THIS_MODULE,
+ .open = dbgaufs_xigen_open,
+ .release = dbgaufs_xi_release,
+ .read = dbgaufs_xi_read
+ err = 0;
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/dbgaufs.h aufs2-2.6.git/fs/aufs/dbgaufs.h
---- linux-2.6.31/fs/aufs/dbgaufs.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dbgaufs.h 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,79 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dbgaufs.h linux-2.6.34/fs/aufs/dbgaufs.h
+--- linux-2.6.34.org/fs/aufs/dbgaufs.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dbgaufs.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,52 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+int dbgaufs_si_init(struct au_sbinfo *sbinfo);
+void dbgaufs_fin(void);
+int __init dbgaufs_init(void);
-+
+#else
-+
-+static inline
-+void dbgaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex)
-+{
-+ /* empty */
-+}
-+
-+static inline
-+void dbgaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex)
-+{
-+ /* empty */
-+}
-+
-+static inline
-+void dbgaufs_si_fin(struct au_sbinfo *sbinfo)
-+{
-+ /* empty */
-+}
-+
-+static inline
-+int dbgaufs_si_init(struct au_sbinfo *sbinfo)
-+{
-+ return 0;
-+}
-+
-+#define dbgaufs_fin() do {} while (0)
-+
-+static inline
-+int __init dbgaufs_init(void)
-+{
-+ return 0;
-+}
++AuStubVoid(dbgaufs_brs_del, struct super_block *sb, aufs_bindex_t bindex)
++AuStubVoid(dbgaufs_brs_add, struct super_block *sb, aufs_bindex_t bindex)
++AuStubVoid(dbgaufs_si_fin, struct au_sbinfo *sbinfo)
++AuStubInt0(dbgaufs_si_init, struct au_sbinfo *sbinfo)
++AuStubVoid(dbgaufs_fin, void)
++AuStubInt0(__init dbgaufs_init, void)
+#endif /* CONFIG_DEBUG_FS */
+
+#endif /* __KERNEL__ */
+#endif /* __DBGAUFS_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/dcsub.c aufs2-2.6.git/fs/aufs/dcsub.c
---- linux-2.6.31/fs/aufs/dcsub.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dcsub.c 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,223 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dcsub.c linux-2.6.34/fs/aufs/dcsub.c
+--- linux-2.6.34.org/fs/aufs/dcsub.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dcsub.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,200 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ return err;
+}
+
-+struct dentry *au_test_subdir(struct dentry *d1, struct dentry *d2)
++int au_test_subdir(struct dentry *d1, struct dentry *d2)
+{
-+ struct dentry *trap, **dentries;
-+ int err, i, j;
-+ struct au_dcsub_pages dpages;
-+ struct au_dpage *dpage;
++ struct path path[2] = {
++ {
++ .dentry = d1
++ },
++ {
++ .dentry = d2
++ }
++ };
+
-+ 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;
-+
-+ trap = d1;
-+ for (i = 0; !err && i < dpages.ndpage; i++) {
-+ dpage = dpages.dpages + i;
-+ dentries = dpage->dentries;
-+ 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:
-+ return trap;
++ return path_is_under(path + 0, path + 1);
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/dcsub.h aufs2-2.6.git/fs/aufs/dcsub.h
---- linux-2.6.31/fs/aufs/dcsub.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dcsub.h 2009-09-21 21:49:23.377863284 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dcsub.h linux-2.6.34/fs/aufs/dcsub.h
+--- linux-2.6.34.org/fs/aufs/dcsub.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dcsub.h 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,54 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ 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);
-+struct dentry *au_test_subdir(struct dentry *d1, struct dentry *d2);
++int au_test_subdir(struct dentry *d1, struct dentry *d2);
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DCSUB_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/debug.c aufs2-2.6.git/fs/aufs/debug.c
---- linux-2.6.31/fs/aufs/debug.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/debug.c 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,431 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/debug.c linux-2.6.34/fs/aufs/debug.c
+--- linux-2.6.34.org/fs/aufs/debug.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/debug.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,425 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+module_param_named(debug, aufs_debug, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+char *au_plevel = KERN_DEBUG;
-+#define dpri(fmt, arg...) do { \
++#define dpri(fmt, ...) do { \
+ if (au_debug_test()) \
-+ printk("%s" fmt, au_plevel, ##arg); \
++ printk("%s" fmt, au_plevel, ##__VA_ARGS__); \
+} while (0)
+
+/* ---------------------------------------------------------------------- */
+ struct au_dinfo *dinfo;
+ aufs_bindex_t bindex;
+ int err;
++ struct au_hdentry *hdp;
+
+ err = do_pri_dentry(-1, dentry);
+ if (err || !au_test_aufs(dentry->d_sb))
+ dinfo->di_bwh, dinfo->di_bdiropq, au_digen(dentry));
+ if (dinfo->di_bstart < 0)
+ return;
++ hdp = dinfo->di_hdentry;
+ for (bindex = dinfo->di_bstart; bindex <= dinfo->di_bend; bindex++)
-+ do_pri_dentry(bindex, dinfo->di_hdentry[0 + bindex].hd_dentry);
++ do_pri_dentry(bindex, hdp[0 + bindex].hd_dentry);
+}
+
+static int do_pri_file(aufs_bindex_t bindex, struct file *file)
+ && file->f_dentry
+ && au_test_aufs(file->f_dentry->d_sb)
+ && au_fi(file))
-+ snprintf(a, sizeof(a), ", mmapped %d", au_test_mmapped(file));
++ snprintf(a, sizeof(a), ", mmapped %d",
++ !!au_fi(file)->fi_hvmop);
+ 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);
+void au_dpri_file(struct file *file)
+{
+ struct au_finfo *finfo;
++ struct au_fidir *fidir;
++ struct au_hfile *hfile;
+ aufs_bindex_t bindex;
+ int err;
+
+ finfo = au_fi(file);
+ if (!finfo)
+ return;
-+ if (finfo->fi_bstart < 0)
++ if (finfo->fi_btop < 0)
+ return;
-+ for (bindex = finfo->fi_bstart; bindex <= finfo->fi_bend; bindex++) {
-+ struct au_hfile *hf;
-+
-+ hf = finfo->fi_hfile + bindex;
-+ do_pri_file(bindex, hf ? hf->hf_file : NULL);
-+ }
++ fidir = finfo->fi_hdir;
++ if (!fidir)
++ do_pri_file(finfo->fi_btop, finfo->fi_htop.hf_file);
++ else
++ for (bindex = finfo->fi_btop; bindex <= fidir->fd_bbot;
++ bindex++) {
++ hfile = fidir->fd_hfile + bindex;
++ do_pri_file(bindex, hfile ? hfile->hf_file : NULL);
++ }
+}
+
+static int do_pri_br(aufs_bindex_t bindex, struct au_branch *br)
+ au_dpages_free(&dpages);
+}
+
-+void au_dbg_verify_hf(struct au_finfo *finfo)
-+{
-+ struct au_hfile *hf;
-+ aufs_bindex_t bend, bindex;
-+
-+ if (finfo->fi_bstart >= 0) {
-+ bend = finfo->fi_bend;
-+ for (bindex = finfo->fi_bstart; bindex <= bend; bindex++) {
-+ hf = finfo->fi_hfile + bindex;
-+ AuDebugOn(hf->hf_file || hf->hf_br);
-+ }
-+ }
-+}
-+
+void au_dbg_verify_kthread(void)
+{
+ if (au_test_wkq(current)) {
+#ifdef AuForceNoRefrof
+ au_opt_clr(sbinfo->si_mntflags, REFROF);
+#endif
-+#ifdef AuForceHinotify
-+ au_opt_set_udba(sbinfo->si_mntflags, UDBA_HINOTIFY);
++#ifdef AuForceHnotify
++ au_opt_set_udba(sbinfo->si_mntflags, UDBA_HNOTIFY);
+#endif
+#ifdef AuForceRd0
+ sbinfo->si_rdblk = 0;
+ AuDebugOn(destr.len < NAME_MAX);
+
+#ifdef CONFIG_4KSTACKS
-+ AuWarn("CONFIG_4KSTACKS is defined.\n");
++ pr_warning("CONFIG_4KSTACKS is defined.\n");
+#endif
+
+#ifdef AuForceNoBrs
+
+ return 0;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/debug.h aufs2-2.6.git/fs/aufs/debug.h
---- linux-2.6.31/fs/aufs/debug.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/debug.h 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,261 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/debug.h linux-2.6.34/fs/aufs/debug.h
+--- linux-2.6.34.org/fs/aufs/debug.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/debug.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,243 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#include <linux/bug.h>
+/* #include <linux/err.h> */
+#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/kallsyms.h>
+/* #include <linux/kernel.h> */
+#include <linux/delay.h>
+/* #include <linux/kd.h> */
+#include <linux/sysrq.h>
+#include <linux/aufs_type.h>
+
++#include <asm/system.h>
++
+#ifdef CONFIG_AUFS_DEBUG
+#define AuDebugOn(a) BUG_ON(a)
+
+}
+#else
+#define AuDebugOn(a) do {} while (0)
-+#define au_debug() do {} while (0)
-+static inline int au_debug_test(void)
-+{
-+ return 0;
-+}
++AuStubVoid(au_debug, int n)
++AuStubInt0(au_debug_test, void)
+#endif /* CONFIG_AUFS_DEBUG */
+
+/* ---------------------------------------------------------------------- */
+
+/* debug print */
+
-+#define AuDpri(lvl, fmt, arg...) \
-+ printk(lvl AUFS_NAME " %s:%d:%s[%d]: " fmt, \
-+ __func__, __LINE__, current->comm, current->pid, ##arg)
-+#define AuDbg(fmt, arg...) do { \
++#define AuDbg(fmt, ...) do { \
+ if (au_debug_test()) \
-+ AuDpri(KERN_DEBUG, "DEBUG: " fmt, ##arg); \
++ pr_debug("DEBUG: " fmt, ##__VA_ARGS__); \
+} while (0)
-+#define AuLabel(l) AuDbg(#l "\n")
-+#define AuInfo(fmt, arg...) AuDpri(KERN_INFO, fmt, ##arg)
-+#define AuWarn(fmt, arg...) AuDpri(KERN_WARNING, fmt, ##arg)
-+#define AuErr(fmt, arg...) AuDpri(KERN_ERR, fmt, ##arg)
-+#define AuIOErr(fmt, arg...) AuErr("I/O Error, " fmt, ##arg)
-+#define AuWarn1(fmt, arg...) do { \
++#define AuLabel(l) AuDbg(#l "\n")
++#define AuIOErr(fmt, ...) pr_err("I/O Error, " fmt, ##__VA_ARGS__)
++#define AuWarn1(fmt, ...) do { \
+ static unsigned char _c; \
+ if (!_c++) \
-+ AuWarn(fmt, ##arg); \
++ pr_warning(fmt, ##__VA_ARGS__); \
+} while (0)
+
-+#define AuErr1(fmt, arg...) do { \
++#define AuErr1(fmt, ...) do { \
+ static unsigned char _c; \
+ if (!_c++) \
-+ AuErr(fmt, ##arg); \
++ pr_err(fmt, ##__VA_ARGS__); \
+} while (0)
+
-+#define AuIOErr1(fmt, arg...) do { \
++#define AuIOErr1(fmt, ...) do { \
+ static unsigned char _c; \
+ if (!_c++) \
-+ AuIOErr(fmt, ##arg); \
++ AuIOErr(fmt, ##__VA_ARGS__); \
+} while (0)
+
+#define AuUnsupportMsg "This operation is not supported." \
+ " Please report this application to aufs-users ML."
-+#define AuUnsupport(fmt, args...) do { \
-+ AuErr(AuUnsupportMsg "\n" fmt, ##args); \
++#define AuUnsupport(fmt, ...) do { \
++ pr_err(AuUnsupportMsg "\n" fmt, ##__VA_ARGS__); \
+ dump_stack(); \
+} while (0)
+
+void au_dbg_verify_dir_parent(struct dentry *dentry, unsigned int sigen);
+void au_dbg_verify_nondir_parent(struct dentry *dentry, unsigned int sigen);
+void au_dbg_verify_gen(struct dentry *parent, unsigned int sigen);
-+void au_dbg_verify_hf(struct au_finfo *finfo);
+void au_dbg_verify_kthread(void);
+
+int __init au_debug_init(void);
+ AuDbg("ia_valid 0x%x\n", (ia)->ia_valid); \
+ au_dbg_iattr(ia); \
+} while (0)
++
++#define AuDbgSym(addr) do { \
++ char sym[KSYM_SYMBOL_LEN]; \
++ sprint_symbol(sym, (unsigned long)addr); \
++ AuDbg("%s\n", sym); \
++} while (0)
++
++#define AuInfoSym(addr) do { \
++ char sym[KSYM_SYMBOL_LEN]; \
++ sprint_symbol(sym, (unsigned long)addr); \
++ AuInfo("%s\n", sym); \
++} while (0)
+#else
-+static inline void au_dbg_verify_dir_parent(struct dentry *dentry,
-+ unsigned int sigen)
-+{
-+ /* empty */
-+}
-+static inline void au_dbg_verify_nondir_parent(struct dentry *dentry,
-+ unsigned int sigen)
-+{
-+ /* empty */
-+}
-+static inline void au_dbg_verify_gen(struct dentry *parent, unsigned int sigen)
-+{
-+ /* empty */
-+}
-+static inline void au_dbg_verify_hf(struct au_finfo *finfo)
-+{
-+ /* empty */
-+}
-+static inline void au_dbg_verify_kthread(void)
-+{
-+ /* empty */
-+}
++AuStubVoid(au_dbg_verify_dir_parent, struct dentry *dentry, unsigned int sigen)
++AuStubVoid(au_dbg_verify_nondir_parent, struct dentry *dentry,
++ unsigned int sigen)
++AuStubVoid(au_dbg_verify_gen, struct dentry *parent, unsigned int sigen)
++AuStubVoid(au_dbg_verify_kthread, void)
++AuStubInt0(__init au_debug_init, void)
++AuStubVoid(au_debug_sbinfo_init, struct au_sbinfo *sbinfo)
+
-+static inline int au_debug_init(void)
-+{
-+ return 0;
-+}
-+static inline void au_debug_sbinfo_init(struct au_sbinfo *sbinfo)
-+{
-+ /* empty */
-+}
+#define AuDbgWhlist(w) do {} while (0)
+#define AuDbgVdir(v) do {} while (0)
+#define AuDbgInode(i) do {} while (0)
+#define AuDbgSleep(sec) do {} while (0)
+#define AuDbgSleepJiffy(jiffy) do {} while (0)
+#define AuDbgIAttr(ia) do {} while (0)
++#define AuDbgSym(addr) do {} while (0)
++#define AuInfoSym(addr) do {} while (0)
+#endif /* CONFIG_AUFS_DEBUG */
+
+/* ---------------------------------------------------------------------- */
+ handle_sysrq('w', vc_cons[fg_console].d->vc_tty); \
+} while (0)
+#else
-+#define au_dbg_blocked() do {} while (0)
++AuStubVoid(au_dbg_blocked, void)
+#endif
+
+#else
-+static inline int au_sysrq_init(void)
-+{
-+ return 0;
-+}
-+#define au_sysrq_fin() do {} while (0)
-+#define au_dbg_blocked() do {} while (0)
++AuStubInt0(__init au_sysrq_init, void)
++AuStubVoid(au_sysrq_fin, void)
++AuStubVoid(au_dbg_blocked, void)
+#endif /* CONFIG_AUFS_MAGIC_SYSRQ */
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DEBUG_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/dentry.c aufs2-2.6.git/fs/aufs/dentry.c
---- linux-2.6.31/fs/aufs/dentry.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dentry.c 2009-09-21 21:49:23.377863284 +0000
-@@ -0,0 +1,879 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dentry.c linux-2.6.34/fs/aufs/dentry.c
+--- linux-2.6.34.org/fs/aufs/dentry.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dentry.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,850 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ path_put(&h_nd.path);
+ }
+
++ AuTraceErrPtr(h_dentry);
+ return h_dentry;
+}
+
+{
+ struct dentry *h_dentry;
+ struct inode *h_inode, *inode;
-+ struct qstr *name;
+ struct au_branch *br;
+ int wh_found, opq;
+ unsigned char wh_able;
+ const unsigned char allow_neg = !!au_ftest_lkup(args->flags, ALLOW_NEG);
+
-+ name = &dentry->d_name;
+ wh_found = 0;
+ br = au_sbr(dentry->d_sb, bindex);
+ wh_able = !!au_br_whable(br->br_perm);
+ return NULL; /* success */
+
+ real_lookup:
-+ h_dentry = au_lkup_one(name, h_parent, br, args->nd);
++ h_dentry = au_lkup_one(&dentry->d_name, h_parent, br, args->nd);
+ if (IS_ERR(h_dentry))
+ goto out;
+
+ struct dentry *parent;
+ struct inode *inode;
+
-+ parent = dget_parent(dentry);
+ err = au_test_shwh(dentry->d_sb, name);
+ if (unlikely(err))
+ goto out;
+ au_fset_lkup(args.flags, ALLOW_NEG);
+
+ npositive = 0;
++ parent = dget_parent(dentry);
+ btail = au_dbtaildir(parent);
+ for (bindex = bstart; bindex <= btail; bindex++) {
+ struct dentry *h_parent, *h_dentry;
+ mutex_unlock(&h_dir->i_mutex);
+ err = PTR_ERR(h_dentry);
+ if (IS_ERR(h_dentry))
-+ goto out_wh;
++ goto out_parent;
+ au_fclr_lkup(args.flags, ALLOW_NEG);
+
+ if (au_dbwh(dentry) >= 0)
+ /* both of real entry and whiteout found */
+ err = -EIO;
+
-+ out_wh:
++ out_parent:
++ dput(parent);
+ kfree(whname.name);
+ out:
-+ dput(parent);
+ return err;
+}
+
+{
+ int err;
+ struct dentry *parent, *h_parent, *h_dentry;
-+ struct qstr *name;
+
-+ name = &dentry->d_name;
+ parent = dget_parent(dentry);
+ h_parent = au_h_dptr(parent, bindex);
-+ h_dentry = au_sio_lkup_one(name, h_parent,
++ h_dentry = au_sio_lkup_one(&dentry->d_name, h_parent,
+ au_sbr(dentry->d_sb, bindex));
+ err = PTR_ERR(h_dentry);
+ if (IS_ERR(h_dentry))
+ goto out;
+ }
+
++ err = 0;
+ if (bindex < au_dbstart(dentry))
+ au_set_dbstart(dentry, bindex);
+ if (au_dbend(dentry) < bindex)
+ au_set_dbend(dentry, bindex);
+ au_set_h_dptr(dentry, bindex, h_dentry);
-+ err = 0;
+
+ out:
+ dput(parent);
+ if (udba == AuOpt_UDBA_REVAL) {
+ IMustLock(h_dir);
+ err = (h_dentry->d_parent->d_inode != h_dir);
-+ } else if (udba == AuOpt_UDBA_HINOTIFY)
++ } else if (udba == AuOpt_UDBA_HNOTIFY)
+ err = au_h_verify_dentry(h_dentry, h_parent, br);
+
+ return err;
+ umode_t mode, h_mode;
+ aufs_bindex_t bindex, btail, bstart, ibs, ibe;
+ unsigned char plus, unhashed, is_root, h_plus;
-+ struct inode *first, *h_inode, *h_cached_inode;
++ struct inode *h_inode, *h_cached_inode;
+ struct dentry *h_dentry;
+ struct qstr *name, *h_name;
+
+ err = 0;
+ plus = 0;
+ mode = 0;
-+ first = NULL;
+ ibs = -1;
+ ibe = -1;
+ unhashed = !!d_unhashed(dentry);
+ if (do_udba && inode) {
+ mode = (inode->i_mode & S_IFMT);
+ plus = (inode->i_nlink > 0);
-+ first = au_h_iptr(inode, au_ibstart(inode));
+ ibs = au_ibstart(inode);
+ ibe = au_ibend(inode);
+ }
+ err = au_reval_dpath(dentry, sigen);
+ if (unlikely(err))
+ goto out_dgrade;
-+ AuDebugOn(au_digen(dentry) != sigen);
+ }
+ if (inode && au_iigen(inode) != sigen) {
+ AuDebugOn(IS_ROOT(dentry));
+ err = au_refresh_hinode(inode, dentry);
+ if (unlikely(err))
+ goto out_dgrade;
-+ AuDebugOn(au_iigen(inode) != sigen);
+ }
+ di_downgrade_lock(dentry, AuLock_IR);
+
-+ AuDebugOn(au_digen(dentry) != sigen);
-+ AuDebugOn(inode && au_iigen(inode) != sigen);
+ err = -EINVAL;
+ do_udba = !au_opt_test(au_mntflags(sb), UDBA_NONE);
+ if (do_udba && inode) {
+
+static void aufs_d_release(struct dentry *dentry)
+{
-+ struct au_dinfo *dinfo;
-+ aufs_bindex_t bend, bindex;
-+
-+ dinfo = dentry->d_fsdata;
-+ if (!dinfo)
-+ return;
-+
-+ /* dentry may not be revalidated */
-+ bindex = dinfo->di_bstart;
-+ if (bindex >= 0) {
-+ struct au_hdentry *p;
-+
-+ bend = dinfo->di_bend;
-+ p = dinfo->di_hdentry + bindex;
-+ while (bindex++ <= bend) {
-+ if (p->hd_dentry)
-+ au_hdput(p);
-+ p++;
-+ }
++ if (dentry->d_fsdata) {
++ au_di_fin(dentry);
++ au_hn_di_reinit(dentry);
+ }
-+ kfree(dinfo->di_hdentry);
-+ AuRwDestroy(&dinfo->di_rwsem);
-+ au_cache_free_dinfo(dinfo);
-+ au_hin_di_reinit(dentry);
+}
+
-+struct dentry_operations aufs_dop = {
++const struct dentry_operations aufs_dop = {
+ .d_revalidate = aufs_d_revalidate,
+ .d_release = aufs_d_release
+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/dentry.h aufs2-2.6.git/fs/aufs/dentry.h
---- linux-2.6.31/fs/aufs/dentry.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dentry.h 2009-09-21 21:49:23.399892755 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dentry.h linux-2.6.34/fs/aufs/dentry.h
+--- linux-2.6.34.org/fs/aufs/dentry.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dentry.h 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,231 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ struct au_rwsem di_rwsem;
+ aufs_bindex_t di_bstart, di_bend, di_bwh, di_bdiropq;
+ struct au_hdentry *di_hdentry;
-+};
++} ____cacheline_aligned_in_smp;
+
+/* ---------------------------------------------------------------------- */
+
+/* dentry.c */
-+extern struct dentry_operations aufs_dop;
++extern const struct dentry_operations aufs_dop;
+struct au_branch;
+struct dentry *au_lkup_one(struct qstr *name, struct dentry *h_parent,
+ struct au_branch *br, struct nameidata *nd);
+int au_reval_dpath(struct dentry *dentry, unsigned int sigen);
+
+/* dinfo.c */
-+int au_alloc_dinfo(struct dentry *dentry);
++void au_di_init_once(void *_di);
++int au_di_init(struct dentry *dentry);
++void au_di_fin(struct dentry *dentry);
+int au_di_realloc(struct au_dinfo *dinfo, int nbr);
+
+void di_read_lock(struct dentry *d, int flags, unsigned int lsc);
+/* lock subclass for dinfo */
+enum {
+ AuLsc_DI_CHILD, /* child first */
-+ AuLsc_DI_CHILD2, /* rename(2), link(2), and cpup at hinotify */
++ AuLsc_DI_CHILD2, /* rename(2), link(2), and cpup at hnotify */
+ AuLsc_DI_CHILD3, /* copyup dirs */
+ AuLsc_DI_PARENT,
+ AuLsc_DI_PARENT2,
+
+static inline void au_hdput(struct au_hdentry *hd)
+{
-+ dput(hd->hd_dentry);
++ if (hd)
++ dput(hd->hd_dentry);
+}
+
+static inline aufs_bindex_t au_dbstart(struct dentry *dentry)
+
+/* ---------------------------------------------------------------------- */
+
-+#ifdef CONFIG_AUFS_HINOTIFY
++#ifdef CONFIG_AUFS_HNOTIFY
+static inline void au_digen_dec(struct dentry *d)
+{
+ atomic_dec_return(&au_di(d)->di_generation);
+}
+
-+static inline void au_hin_di_reinit(struct dentry *dentry)
++static inline void au_hn_di_reinit(struct dentry *dentry)
+{
+ dentry->d_fsdata = NULL;
+}
+#else
-+static inline void au_hin_di_reinit(struct dentry *dentry __maybe_unused)
-+{
-+ /* empty */
-+}
-+#endif /* CONFIG_AUFS_HINOTIFY */
++AuStubVoid(au_hn_di_reinit, struct dentry *dentry __maybe_unused)
++#endif /* CONFIG_AUFS_HNOTIFY */
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DENTRY_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/dinfo.c aufs2-2.6.git/fs/aufs/dinfo.c
---- linux-2.6.31/fs/aufs/dinfo.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dinfo.c 2009-09-21 21:49:23.399892755 +0000
-@@ -0,0 +1,367 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dinfo.c linux-2.6.34/fs/aufs/dinfo.c
+--- linux-2.6.34.org/fs/aufs/dinfo.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dinfo.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,395 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+#include "aufs.h"
+
-+int au_alloc_dinfo(struct dentry *dentry)
++void au_di_init_once(void *_di)
++{
++ struct au_dinfo *di = _di;
++
++ au_rw_init(&di->di_rwsem);
++}
++
++int au_di_init(struct dentry *dentry)
+{
+ struct au_dinfo *dinfo;
+ struct super_block *sb;
+
+ atomic_set(&dinfo->di_generation, au_sigen(sb));
+ /* smp_mb(); */ /* atomic_set */
-+ au_rw_init_wlock_nested(&dinfo->di_rwsem, AuLsc_DI_CHILD);
++ au_rw_write_lock_nested(&dinfo->di_rwsem, AuLsc_DI_CHILD);
+ dinfo->di_bstart = -1;
+ dinfo->di_bend = -1;
+ dinfo->di_bwh = -1;
+ return -ENOMEM;
+}
+
++void au_di_fin(struct dentry *dentry)
++{
++ struct au_dinfo *di;
++ struct au_hdentry *p;
++ aufs_bindex_t bend, bindex;
++
++ /* dentry may not be revalidated */
++ di = dentry->d_fsdata;
++ bindex = di->di_bstart;
++ if (bindex >= 0) {
++ bend = di->di_bend;
++ p = di->di_hdentry + bindex;
++ while (bindex++ <= bend)
++ au_hdput(p++);
++ }
++ kfree(di->di_hdentry);
++ AuRwDestroy(&di->di_rwsem);
++ au_cache_free_dinfo(di);
++}
++
+int au_di_realloc(struct au_dinfo *dinfo, int nbr)
+{
+ int err, sz;
+
+ DiMustWriteLock(dentry);
+
-+ if (hd->hd_dentry)
-+ au_hdput(hd);
++ au_hdput(hd);
+ hd->hd_dentry = h_dentry;
+}
+
+{
+ struct au_dinfo *dinfo;
+ struct dentry *h_d;
++ struct au_hdentry *hdp;
+
+ DiMustWriteLock(dentry);
+
+ if (!dinfo || dinfo->di_bstart < 0)
+ return;
+
++ hdp = dinfo->di_hdentry;
+ if (do_put_zero) {
+ aufs_bindex_t bindex, bend;
+
+ bend = dinfo->di_bend;
+ for (bindex = dinfo->di_bstart; bindex <= bend; bindex++) {
-+ h_d = dinfo->di_hdentry[0 + bindex].hd_dentry;
++ h_d = hdp[0 + bindex].hd_dentry;
+ if (h_d && !h_d->d_inode)
+ au_set_h_dptr(dentry, bindex, NULL);
+ }
+
+ dinfo->di_bstart = -1;
+ while (++dinfo->di_bstart <= dinfo->di_bend)
-+ if (dinfo->di_hdentry[0 + dinfo->di_bstart].hd_dentry)
++ if (hdp[0 + dinfo->di_bstart].hd_dentry)
+ break;
+ if (dinfo->di_bstart > dinfo->di_bend) {
+ dinfo->di_bstart = -1;
+
+ dinfo->di_bend++;
+ while (0 <= --dinfo->di_bend)
-+ if (dinfo->di_hdentry[0 + dinfo->di_bend].hd_dentry)
++ if (hdp[0 + dinfo->di_bend].hd_dentry)
+ break;
+ AuDebugOn(dinfo->di_bstart > dinfo->di_bend || dinfo->di_bend < 0);
+}
+ return bindex;
+ return -1;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/dir.c aufs2-2.6.git/fs/aufs/dir.c
---- linux-2.6.31/fs/aufs/dir.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dir.c 2009-09-21 21:49:23.399892755 +0000
-@@ -0,0 +1,593 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dir.c linux-2.6.34/fs/aufs/dir.c
+--- linux-2.6.34.org/fs/aufs/dir.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dir.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,637 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ AuDebugOn(!file->f_dentry->d_inode);
+ AuDebugOn(!S_ISDIR(file->f_dentry->d_inode->i_mode));
+
-+ bend = au_fbend(file);
++ bend = au_fbend_dir(file);
+ for (bindex = au_fbstart(file);
+ bindex <= bend && sz < KMALLOC_MAX_SIZE;
+ bindex++) {
-+ h_file = au_h_fptr(file, bindex);
++ h_file = au_hf_dir(file, bindex);
+ if (h_file
+ && h_file->f_dentry
+ && h_file->f_dentry->d_inode)
+ au_set_fbstart(file, bstart);
+
+ btail = au_dbtaildir(dentry);
-+ for (bindex = au_fbend(file); btail < bindex; bindex--)
++ for (bindex = au_fbend_dir(file); btail < bindex; bindex--)
+ au_set_h_fptr(file, bindex, NULL);
-+ au_set_fbend(file, btail);
++ au_set_fbend_dir(file, btail);
+
-+ spin_lock(&file->f_lock);
-+ flags = file->f_flags;
-+ spin_unlock(&file->f_lock);
++ flags = vfsub_file_flags(file);
+ for (bindex = bstart; bindex <= btail; bindex++) {
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (!h_dentry)
+ continue;
-+ h_file = au_h_fptr(file, bindex);
++ h_file = au_hf_dir(file, bindex);
+ if (h_file)
+ continue;
+
+
+ err = 0;
+ dentry = file->f_dentry;
-+ au_set_fvdir_cache(file, NULL);
-+ au_fi(file)->fi_maintain_plink = 0;
+ file->f_version = dentry->d_inode->i_version;
+ bindex = au_dbstart(dentry);
+ au_set_fbstart(file, bindex);
+ btail = au_dbtaildir(dentry);
-+ au_set_fbend(file, btail);
++ au_set_fbend_dir(file, btail);
+ for (; !err && bindex <= btail; bindex++) {
+ h_dentry = au_h_dptr(dentry, bindex);
+ if (!h_dentry)
+ 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);
++ au_set_fbend_dir(file, -1);
++
+ return err;
+}
+
+static int aufs_open_dir(struct inode *inode __maybe_unused,
+ struct file *file)
+{
-+ return au_do_open(file, do_open_dir);
++ int err;
++ struct super_block *sb;
++ struct au_fidir *fidir;
++
++ err = -ENOMEM;
++ sb = file->f_dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ fidir = au_fidir_alloc(inode->i_sb);
++ if (fidir) {
++ err = au_do_open(file, do_open_dir, fidir);
++ if (unlikely(err))
++ kfree(fidir);
++ }
++ si_read_unlock(sb);
++ return err;
+}
+
+static int aufs_release_dir(struct inode *inode __maybe_unused,
+{
+ struct au_vdir *vdir_cache;
+ struct super_block *sb;
-+ struct au_sbinfo *sbinfo;
++ struct au_finfo *finfo;
++ struct au_fidir *fidir;
++ aufs_bindex_t bindex, bend;
+
++ au_plink_maint_leave(file);
+ sb = file->f_dentry->d_sb;
-+ si_noflush_read_lock(sb);
-+ fi_write_lock(file);
-+ vdir_cache = au_fvdir_cache(file);
-+ if (vdir_cache)
-+ au_vdir_free(vdir_cache);
-+ if (au_fi(file)->fi_maintain_plink) {
-+ sbinfo = au_sbi(sb);
-+ /* clear the flag without write-lock */
-+ sbinfo->au_si_status &= ~AuSi_MAINTAIN_PLINK;
-+ smp_mb();
-+ wake_up_all(&sbinfo->si_plink_wq);
++ finfo = au_fi(file);
++ fidir = finfo->fi_hdir;
++ if (fidir) {
++ vdir_cache = fidir->fd_vdir_cache; /* lock-free */
++ if (vdir_cache)
++ au_vdir_free(vdir_cache);
++
++ bindex = finfo->fi_btop;
++ if (bindex >= 0) {
++ /*
++ * calls fput() instead of filp_close(),
++ * since no dnotify or lock for the lower file.
++ */
++ bend = fidir->fd_bbot;
++ for (; bindex <= bend; bindex++)
++ au_set_h_fptr(file, bindex, NULL);
++ }
++ kfree(fidir);
++ finfo->fi_hdir = NULL;
+ }
-+ fi_write_unlock(file);
+ au_finfo_fin(file);
-+ si_read_unlock(sb);
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
++static int au_do_flush_dir(struct file *file, fl_owner_t id)
++{
++ int err;
++ aufs_bindex_t bindex, bend;
++ struct file *h_file;
++
++ err = 0;
++ bend = au_fbend_dir(file);
++ for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) {
++ h_file = au_hf_dir(file, bindex);
++ if (h_file)
++ err = vfsub_flush(h_file, id);
++ }
++ return err;
++}
++
++static int aufs_flush_dir(struct file *file, fl_owner_t id)
++{
++ return au_do_flush(file, id, au_do_flush_dir);
++}
++
++/* ---------------------------------------------------------------------- */
++
+static int au_do_fsync_dir_no_file(struct dentry *dentry, int datasync)
+{
+ int err;
+
+ sb = file->f_dentry->d_sb;
+ inode = file->f_dentry->d_inode;
-+ bend = au_fbend(file);
++ bend = au_fbend_dir(file);
+ for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) {
-+ h_file = au_h_fptr(file, bindex);
++ h_file = au_hf_dir(file, bindex);
+ if (!h_file || au_test_ro(sb, bindex, inode))
+ continue;
+
+
+ di_read_unlock(dentry, AuLock_IR);
+ si_read_unlock(sb);
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = au_vdir_fill_de(file, dirent, filldir);
-+ lockdep_on();
++ /* lockdep_on(); */
+ fsstack_copy_attr_atime(inode, h_inode);
+ fi_write_unlock(file);
+
+/* ---------------------------------------------------------------------- */
+
+const struct file_operations aufs_dir_fop = {
++ .owner = THIS_MODULE,
+ .read = generic_read_dir,
+ .readdir = aufs_readdir,
+ .unlocked_ioctl = aufs_ioctl_dir,
+ .open = aufs_open_dir,
+ .release = aufs_release_dir,
-+ .flush = aufs_flush,
++ .flush = aufs_flush_dir,
+ .fsync = aufs_fsync_dir
+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/dir.h aufs2-2.6.git/fs/aufs/dir.h
---- linux-2.6.31/fs/aufs/dir.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/dir.h 2009-09-21 21:49:23.399892755 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dir.h linux-2.6.34/fs/aufs/dir.h
+--- linux-2.6.34.org/fs/aufs/dir.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dir.h 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,127 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+struct au_vdir_dehstr {
+ struct hlist_node hash;
+ struct au_vdir_destr *str;
-+};
++} ____cacheline_aligned_in_smp;
+
+struct au_vdir_de {
+ ino_t de_ino;
+ unsigned long vd_version;
+ unsigned int vd_deblk_sz;
+ unsigned long vd_jiffy;
-+};
++} ____cacheline_aligned_in_smp;
+
+/* ---------------------------------------------------------------------- */
+
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_DIR_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/export.c aufs2-2.6.git/fs/aufs/export.c
---- linux-2.6.31/fs/aufs/export.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/export.c 2009-09-21 21:49:23.399892755 +0000
-@@ -0,0 +1,746 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dynop.c linux-2.6.34/fs/aufs/dynop.c
+--- linux-2.6.34.org/fs/aufs/dynop.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dynop.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,404 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * export via nfs
++ * dynamically customizable operations for regular files
+ */
+
-+#include <linux/exportfs.h>
-+#include <linux/file.h>
-+#include <linux/mnt_namespace.h>
-+#include <linux/namei.h>
-+#include <linux/nsproxy.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;
-+};
++#define DyPrSym(key) AuDbgSym(key->dk_op.dy_hop)
+
-+static ino_t decode_ino(__u32 *a)
++/*
++ * How large will these lists be?
++ * Usually just a few elements, 20-30 at most for each, I guess.
++ */
++static struct au_splhead dynop[AuDyLast];
++
++static struct au_dykey *dy_gfind_get(struct au_splhead *spl, const void *h_op)
+{
-+ union conv u;
++ struct au_dykey *key, *tmp;
++ struct list_head *head;
+
-+ 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;
++ key = NULL;
++ head = &spl->head;
++ rcu_read_lock();
++ list_for_each_entry_rcu(tmp, head, dk_list)
++ if (tmp->dk_op.dy_hop == h_op) {
++ key = tmp;
++ kref_get(&key->dk_kref);
++ break;
++ }
++ rcu_read_unlock();
++
++ return key;
+}
+
-+static void encode_ino(__u32 *a, ino_t ino)
++static struct au_dykey *dy_bradd(struct au_branch *br, struct au_dykey *key)
+{
-+ union conv u;
++ struct au_dykey **k, *found;
++ const void *h_op = key->dk_op.dy_hop;
++ int i;
+
-+ u.ino = ino;
-+ a[0] = u.a[0];
-+#ifdef CONFIG_AUFS_INO_T_64
-+ a[1] = u.a[1];
-+#endif
++ found = NULL;
++ k = br->br_dykey;
++ for (i = 0; i < AuBrDynOp; i++)
++ if (k[i]) {
++ if (k[i]->dk_op.dy_hop == h_op) {
++ found = k[i];
++ break;
++ }
++ } else
++ break;
++ if (!found) {
++ spin_lock(&br->br_dykey_lock);
++ for (; i < AuBrDynOp; i++)
++ if (k[i]) {
++ if (k[i]->dk_op.dy_hop == h_op) {
++ found = k[i];
++ break;
++ }
++ } else {
++ k[i] = key;
++ break;
++ }
++ spin_unlock(&br->br_dykey_lock);
++ BUG_ON(i == AuBrDynOp); /* expand the array */
++ }
++
++ return found;
+}
+
-+/* 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,
++/* kref_get() if @key is already added */
++static struct au_dykey *dy_gadd(struct au_splhead *spl, struct au_dykey *key)
++{
++ struct au_dykey *tmp, *found;
++ struct list_head *head;
++ const void *h_op = key->dk_op.dy_hop;
+
-+ Fh_ino = Fh_ino1,
-+ Fh_dir_ino = Fh_dir_ino1
-+};
++ found = NULL;
++ head = &spl->head;
++ spin_lock(&spl->spin);
++ list_for_each_entry(tmp, head, dk_list)
++ if (tmp->dk_op.dy_hop == h_op) {
++ kref_get(&tmp->dk_kref);
++ found = tmp;
++ break;
++ }
++ if (!found)
++ list_add_rcu(&key->dk_list, head);
++ spin_unlock(&spl->spin);
+
-+static int au_test_anon(struct dentry *dentry)
++ if (!found)
++ DyPrSym(key);
++ return found;
++}
++
++static void dy_free_rcu(struct rcu_head *rcu)
+{
-+ return !!(dentry->d_flags & DCACHE_DISCONNECTED);
++ struct au_dykey *key;
++
++ key = container_of(rcu, struct au_dykey, dk_rcu);
++ DyPrSym(key);
++ kfree(key);
+}
+
-+/* ---------------------------------------------------------------------- */
-+/* inode generation external table */
++static void dy_free(struct kref *kref)
++{
++ struct au_dykey *key;
++ struct au_splhead *spl;
+
-+int au_xigen_inc(struct inode *inode)
++ key = container_of(kref, struct au_dykey, dk_kref);
++ spl = dynop + key->dk_op.dy_type;
++ au_spl_del_rcu(&key->dk_list, spl);
++ call_rcu(&key->dk_rcu, dy_free_rcu);
++}
++
++void au_dy_put(struct au_dykey *key)
+{
-+ int err;
-+ loff_t pos;
-+ ssize_t sz;
-+ __u32 igen;
-+ struct super_block *sb;
-+ struct au_sbinfo *sbinfo;
++ kref_put(&key->dk_kref, dy_free);
++}
+
-+ err = 0;
-+ sb = inode->i_sb;
-+ sbinfo = au_sbi(sb);
++/* ---------------------------------------------------------------------- */
++
++#define DyDbgSize(cnt, op) AuDebugOn(cnt != sizeof(op)/sizeof(void *))
++
++#ifdef CONFIG_AUFS_DEBUG
++#define DyDbgDeclare(cnt) unsigned int cnt = 0
++#define DyDbgInc(cnt) cnt++
++#else
++#define DyDbgDeclare(cnt) do {} while (0)
++#define DyDbgInc(cnt) do {} while (0)
++#endif
++
++#define DySet(func, dst, src, h_op, h_sb) do { \
++ DyDbgInc(cnt); \
++ if (h_op->func) { \
++ if (src.func) \
++ dst.func = src.func; \
++ else \
++ AuDbg("%s %s\n", au_sbtype(h_sb), #func); \
++ } \
++} while (0)
++
++#define DySetForce(func, dst, src) do { \
++ AuDebugOn(!src.func); \
++ DyDbgInc(cnt); \
++ dst.func = src.func; \
++} while (0)
++
++#define DySetAop(func) \
++ DySet(func, dyaop->da_op, aufs_aop, h_aop, h_sb)
++#define DySetAopForce(func) \
++ DySetForce(func, dyaop->da_op, aufs_aop)
++
++static void dy_aop(struct au_dykey *key, const void *h_op,
++ struct super_block *h_sb __maybe_unused)
++{
++ struct au_dyaop *dyaop = (void *)key;
++ const struct address_space_operations *h_aop = h_op;
++ DyDbgDeclare(cnt);
++
++ AuDbg("%s\n", au_sbtype(h_sb));
++
++ DySetAop(writepage);
++ DySetAopForce(readpage); /* force */
++ DySetAop(sync_page);
++ DySetAop(writepages);
++ DySetAop(set_page_dirty);
++ DySetAop(readpages);
++ DySetAop(write_begin);
++ DySetAop(write_end);
++ DySetAop(bmap);
++ DySetAop(invalidatepage);
++ DySetAop(releasepage);
++ /* these two will be changed according to an aufs mount option */
++ DySetAop(direct_IO);
++ DySetAop(get_xip_mem);
++ DySetAop(migratepage);
++ DySetAop(launder_page);
++ DySetAop(is_partially_uptodate);
++ DySetAop(error_remove_page);
++
++ DyDbgSize(cnt, *h_aop);
++ dyaop->da_get_xip_mem = h_aop->get_xip_mem;
++}
++
++#define DySetVmop(func) \
++ DySet(func, dyvmop->dv_op, aufs_vm_ops, h_vmop, h_sb)
++#define DySetVmopForce(func) \
++ DySetForce(func, dyvmop->dv_op, aufs_vm_ops)
++
++static void dy_vmop(struct au_dykey *key, const void *h_op,
++ struct super_block *h_sb __maybe_unused)
++{
++ struct au_dyvmop *dyvmop = (void *)key;
++ const struct vm_operations_struct *h_vmop = h_op;
++ DyDbgDeclare(cnt);
++
++ AuDbg("%s\n", au_sbtype(h_sb));
++
++ DySetVmop(open);
++ DySetVmop(close);
++ DySetVmop(fault);
++ DySetVmop(page_mkwrite);
++ DySetVmop(access);
++#ifdef CONFIG_NUMA
++ DySetVmop(set_policy);
++ DySetVmop(get_policy);
++ DySetVmop(migrate);
++#endif
++
++ DyDbgSize(cnt, *h_vmop);
++}
++
++/* ---------------------------------------------------------------------- */
++
++static void dy_bug(struct kref *kref)
++{
++ BUG();
++}
++
++static struct au_dykey *dy_get(struct au_dynop *op, struct au_branch *br)
++{
++ struct au_dykey *key, *old;
++ struct au_splhead *spl;
++ static const struct {
++ unsigned int sz;
++ void (*set_op)(struct au_dykey *key, const void *h_op,
++ struct super_block *h_sb __maybe_unused);
++ } a[] = {
++ [AuDy_AOP] = {
++ .sz = sizeof(struct au_dyaop),
++ .set_op = dy_aop
++ },
++ [AuDy_VMOP] = {
++ .sz = sizeof(struct au_dyvmop),
++ .set_op = dy_vmop
++ }
++ }, *p;
++
++ spl = dynop + op->dy_type;
++ key = dy_gfind_get(spl, op->dy_hop);
++ if (key)
++ goto out_add; /* success */
++
++ p = a + op->dy_type;
++ key = kzalloc(p->sz, GFP_NOFS);
++ if (unlikely(!key)) {
++ key = ERR_PTR(-ENOMEM);
++ goto out;
++ }
++
++ key->dk_op.dy_hop = op->dy_hop;
++ kref_init(&key->dk_kref);
++ p->set_op(key, op->dy_hop, br->br_mnt->mnt_sb);
++ old = dy_gadd(spl, key);
++ if (old) {
++ kfree(key);
++ key = old;
++ }
++
++out_add:
++ old = dy_bradd(br, key);
++ if (old)
++ /* its ref-count should never be zero here */
++ kref_put(&key->dk_kref, dy_bug);
++out:
++ return key;
++}
++
++/* ---------------------------------------------------------------------- */
++/*
++ * Aufs prohibits O_DIRECT by defaut even if the branch supports it.
++ * This behaviour is neccessary to return an error from open(O_DIRECT) instead
++ * of the succeeding I/O. The dio mount option enables O_DIRECT and makes
++ * open(O_DIRECT) always succeed, but the succeeding I/O may return an error.
++ * See the aufs manual in detail.
++ *
++ * To keep this behaviour, aufs has to set NULL to ->get_xip_mem too, and the
++ * performance of fadvise() and madvise() may be affected.
++ */
++static void dy_adx(struct au_dyaop *dyaop, int do_dx)
++{
++ if (!do_dx) {
++ dyaop->da_op.direct_IO = NULL;
++ dyaop->da_op.get_xip_mem = NULL;
++ } else {
++ dyaop->da_op.direct_IO = aufs_aop.direct_IO;
++ dyaop->da_op.get_xip_mem = aufs_aop.get_xip_mem;
++ if (!dyaop->da_get_xip_mem)
++ dyaop->da_op.get_xip_mem = NULL;
++ }
++}
++
++static struct au_dyaop *dy_aget(struct au_branch *br,
++ const struct address_space_operations *h_aop,
++ int do_dx)
++{
++ struct au_dyaop *dyaop;
++ struct au_dynop op;
++
++ op.dy_type = AuDy_AOP;
++ op.dy_haop = h_aop;
++ dyaop = (void *)dy_get(&op, br);
++ if (IS_ERR(dyaop))
++ goto out;
++ dy_adx(dyaop, do_dx);
++
++out:
++ return dyaop;
++}
++
++int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex,
++ struct inode *h_inode)
++{
++ int err, do_dx;
++ struct super_block *sb;
++ struct au_branch *br;
++ struct au_dyaop *dyaop;
++
++ AuDebugOn(!S_ISREG(h_inode->i_mode));
++ IiMustWriteLock(inode);
++
++ sb = inode->i_sb;
++ br = au_sbr(sb, bindex);
++ do_dx = !!au_opt_test(au_mntflags(sb), DIO);
++ dyaop = dy_aget(br, h_inode->i_mapping->a_ops, do_dx);
++ err = PTR_ERR(dyaop);
++ if (IS_ERR(dyaop))
++ /* unnecessary to call dy_fput() */
++ goto out;
++
++ err = 0;
++ inode->i_mapping->a_ops = &dyaop->da_op;
++
++out:
++ return err;
++}
++
++void au_dy_arefresh(int do_dx)
++{
++ struct au_splhead *spl;
++ struct list_head *head;
++ struct au_dykey *key;
++
++ spl = dynop + AuDy_AOP;
++ head = &spl->head;
++ spin_lock(&spl->spin);
++ list_for_each_entry(key, head, dk_list)
++ dy_adx((void *)key, do_dx);
++ spin_unlock(&spl->spin);
++}
++
++const struct vm_operations_struct *
++au_dy_vmop(struct file *file, struct au_branch *br,
++ const struct vm_operations_struct *h_vmop)
++{
++ struct au_dyvmop *dyvmop;
++ struct au_dynop op;
++
++ op.dy_type = AuDy_VMOP;
++ op.dy_hvmop = h_vmop;
++ dyvmop = (void *)dy_get(&op, br);
++ if (IS_ERR(dyvmop))
++ return (void *)dyvmop;
++ return &dyvmop->dv_op;
++}
++
++/* ---------------------------------------------------------------------- */
++
++void __init au_dy_init(void)
++{
++ int i;
++
++ /* make sure that 'struct au_dykey *' can be any type */
++ BUILD_BUG_ON(offsetof(struct au_dyaop, da_key));
++ BUILD_BUG_ON(offsetof(struct au_dyvmop, dv_key));
++
++ for (i = 0; i < AuDyLast; i++)
++ au_spl_init(dynop + i);
++}
++
++void au_dy_fin(void)
++{
++ int i;
++
++ for (i = 0; i < AuDyLast; i++)
++ WARN_ON(!list_empty(&dynop[i].head));
++}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/dynop.h linux-2.6.34/fs/aufs/dynop.h
+--- linux-2.6.34.org/fs/aufs/dynop.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/dynop.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,109 @@
++/*
++ * Copyright (C) 2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * dynamically customizable operations (for regular files only)
++ */
++
++#ifndef __AUFS_DYNOP_H__
++#define __AUFS_DYNOP_H__
++
++#ifdef __KERNEL__
++
++#include <linux/fs.h>
++#include <linux/mm.h>
++#include <linux/rcupdate.h>
++#include <linux/aufs_type.h>
++#include "inode.h"
++
++enum {AuDy_AOP, AuDy_VMOP, AuDyLast};
++
++struct au_dynop {
++ int dy_type;
++ union {
++ const void *dy_hop;
++ const struct address_space_operations *dy_haop;
++ const struct vm_operations_struct *dy_hvmop;
++ };
++};
++
++struct au_dykey {
++ union {
++ struct list_head dk_list;
++ struct rcu_head dk_rcu;
++ };
++ struct au_dynop dk_op;
++
++ /*
++ * during I am in the branch local array, kref is gotten. when the
++ * branch is removed, kref is put.
++ */
++ struct kref dk_kref;
++};
++
++/* stop unioning since their sizes are very different from each other */
++struct au_dyaop {
++ struct au_dykey da_key;
++ struct address_space_operations da_op; /* not const */
++ int (*da_get_xip_mem)(struct address_space *, pgoff_t, int,
++ void **, unsigned long *);
++};
++
++struct au_dyvmop {
++ struct au_dykey dv_key;
++ struct vm_operations_struct dv_op; /* not const */
++};
++
++/* ---------------------------------------------------------------------- */
++
++/* dynop.c */
++struct au_branch;
++void au_dy_put(struct au_dykey *key);
++int au_dy_iaop(struct inode *inode, aufs_bindex_t bindex,
++ struct inode *h_inode);
++void au_dy_arefresh(int do_dio);
++const struct vm_operations_struct *
++au_dy_vmop(struct file *file, struct au_branch *br,
++ const struct vm_operations_struct *h_vmop);
++
++void __init au_dy_init(void);
++void au_dy_fin(void);
++
++/* ---------------------------------------------------------------------- */
++
++/*
++ * Is it safe to replace a_ops during the inode/file is in operation?
++ * Yes, I hope so.
++ */
++static inline int au_dy_irefresh(struct inode *inode)
++{
++ int err;
++ aufs_bindex_t bstart;
++ struct inode *h_inode;
++
++ err = 0;
++ if (S_ISREG(inode->i_mode)) {
++ bstart = au_ibstart(inode);
++ h_inode = au_h_iptr(inode, bstart);
++ err = au_dy_iaop(inode, bstart, h_inode);
++ }
++ return err;
++}
++
++#endif /* __KERNEL__ */
++#endif /* __AUFS_DYNOP_H__ */
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/export.c linux-2.6.34/fs/aufs/export.c
+--- linux-2.6.34.org/fs/aufs/export.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/export.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,801 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * export via nfs
++ */
++
++#include <linux/exportfs.h>
++#include <linux/file.h>
++#include <linux/mnt_namespace.h>
++#include <linux/namei.h>
++#include <linux/nsproxy.h>
++#include <linux/random.h>
++#include <linux/writeback.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;
++
++ err = 0;
++ sb = inode->i_sb;
++ sbinfo = au_sbi(sb);
+ /*
+ * temporary workaround for escaping from SiMustAnyLock() in
+ * au_mntflags(), since this function is called from au_iinfo_fin().
+
+/* todo: dirty? */
+/* if exportfs_decode_fh() passed vfsmount*, we could be happy */
++
++struct au_compare_mnt_args {
++ /* input */
++ struct super_block *sb;
++
++ /* output */
++ struct vfsmount *mnt;
++};
++
++static int au_compare_mnt(struct vfsmount *mnt, void *arg)
++{
++ struct au_compare_mnt_args *a = arg;
++
++ if (mnt->mnt_sb != a->sb)
++ return 0;
++ a->mnt = mntget(mnt);
++ return 1;
++}
++
+static struct vfsmount *au_mnt_get(struct super_block *sb)
+{
++ int err;
++ struct au_compare_mnt_args args = {
++ .sb = sb
++ };
+ struct mnt_namespace *ns;
-+ struct vfsmount *pos, *mnt;
+
-+ spin_lock(&vfsmount_lock);
+ /* 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 = mntget(pos);
-+ break;
-+ }
-+ spin_unlock(&vfsmount_lock);
-+ AuDebugOn(!mnt);
-+
-+ return mnt;
++ err = iterate_mounts(au_compare_mnt, &args, ns->root);
++ AuDebugOn(!err);
++ AuDebugOn(!args.mnt);
++ return args.mnt;
+}
+
+struct au_nfsd_si_lock {
-+ const unsigned int sigen;
-+ const aufs_bindex_t br_id;
++ unsigned int sigen;
++ aufs_bindex_t br_id;
+ unsigned char force_lock;
+};
+
+ parent = path->dentry;
+ if (nsi_lock)
+ si_read_unlock(parent->d_sb);
-+ path_get(path);
-+ file = vfsub_dentry_open(path, au_dir_roflags, current_cred());
++ file = vfsub_dentry_open(path, au_dir_roflags);
+ dentry = (void *)file;
+ if (IS_ERR(file))
+ goto out;
+
+ dentry = ERR_PTR(-ENOMEM);
-+ arg.name = __getname();
++ arg.name = __getname_gfp(GFP_NOFS);
+ if (unlikely(!arg.name))
+ goto out_file;
+ arg.ino = ino;
+ ino_t ino, dir_ino;
+ aufs_bindex_t bindex;
+ struct au_nfsd_si_lock nsi_lock = {
-+ .sigen = fh[Fh_sigen],
-+ .br_id = fh[Fh_br_id],
+ .force_lock = 0
+ };
+
-+ AuDebugOn(fh_len < Fh_tail);
-+
+ dentry = ERR_PTR(-ESTALE);
++ /* it should never happen, but the file handle is unreliable */
++ if (unlikely(fh_len < Fh_tail))
++ goto out;
++ nsi_lock.sigen = fh[Fh_sigen];
++ nsi_lock.br_id = fh[Fh_br_id];
++
+ /* branch id may be wrapped around */
+ bindex = si_nfsd_read_lock(sb, &nsi_lock);
+ if (unlikely(bindex < 0))
+
+ /* is this inode still cached? */
+ ino = decode_ino(fh + Fh_ino);
-+ AuDebugOn(ino == AUFS_ROOT_INO);
++ /* it should never happen */
++ if (unlikely(ino == AUFS_ROOT_INO))
++ goto out;
++
+ dir_ino = decode_ino(fh + Fh_dir_ino);
+ dentry = decode_by_ino(sb, ino, dir_ino);
+ if (IS_ERR(dentry))
+
+/* ---------------------------------------------------------------------- */
+
++static int aufs_commit_metadata(struct inode *inode)
++{
++ int err;
++ aufs_bindex_t bindex;
++ struct super_block *sb;
++ struct inode *h_inode;
++ int (*f)(struct inode *inode);
++
++ sb = inode->i_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ ii_write_lock_child(inode);
++ bindex = au_ibstart(inode);
++ AuDebugOn(bindex < 0);
++ h_inode = au_h_iptr(inode, bindex);
++
++ f = h_inode->i_sb->s_export_op->commit_metadata;
++ if (f)
++ err = f(h_inode);
++ else {
++ struct writeback_control wbc = {
++ .sync_mode = WB_SYNC_ALL,
++ .nr_to_write = 0 /* metadata only */
++ };
++
++ err = sync_inode(h_inode, &wbc);
++ }
++
++ au_cpup_attr_timesizes(inode);
++ ii_write_unlock(inode);
++ si_read_unlock(sb);
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
+static struct export_operations aufs_export_op = {
-+ .fh_to_dentry = aufs_fh_to_dentry,
++ .fh_to_dentry = aufs_fh_to_dentry,
+ /* .fh_to_parent = aufs_fh_to_parent, */
-+ .encode_fh = aufs_encode_fh
++ .encode_fh = aufs_encode_fh,
++ .commit_metadata = aufs_commit_metadata
+};
+
+void au_export_init(struct super_block *sb)
+ BUILD_BUG_ON(sizeof(u) != sizeof(int));
+ atomic_set(&sbinfo->si_xigen_next, u);
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/f_op.c aufs2-2.6.git/fs/aufs/f_op.c
---- linux-2.6.31/fs/aufs/f_op.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/f_op.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,826 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/file.c linux-2.6.34/fs/aufs/file.c
+--- linux-2.6.34.org/fs/aufs/file.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/file.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,652 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * file and vm operations
++ * handling file/dir, and address_space operation
+ */
+
+#include <linux/file.h>
-+#include <linux/fs_stack.h>
-+#include <linux/ima.h>
-+#include <linux/mman.h>
-+#include <linux/mm.h>
-+#include <linux/security.h>
++#include <linux/fsnotify.h>
++#include <linux/namei.h>
++#include <linux/pagemap.h>
+#include "aufs.h"
+
-+/* common function to regular file and dir */
-+int aufs_flush(struct file *file, fl_owner_t id)
++/* drop flags for writing */
++unsigned int au_file_roflags(unsigned int flags)
++{
++ flags &= ~(O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC);
++ flags |= O_RDONLY | O_NOATIME;
++ return flags;
++}
++
++/* common functions to regular file and dir */
++struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
++ struct file *file)
+{
-+ int err;
-+ aufs_bindex_t bindex, bend;
-+ struct dentry *dentry;
+ struct file *h_file;
++ struct dentry *h_dentry;
++ struct inode *h_inode;
++ struct super_block *sb;
++ struct au_branch *br;
++ struct path h_path;
++ int err, exec_flag;
+
-+ dentry = file->f_dentry;
-+ si_noflush_read_lock(dentry->d_sb);
-+ fi_read_lock(file);
-+ di_read_lock_child(dentry, AuLock_IW);
++ /* a race condition can happen between open and unlink/rmdir */
++ h_file = ERR_PTR(-ENOENT);
++ h_dentry = au_h_dptr(dentry, bindex);
++ if (au_test_nfsd(current) && !h_dentry)
++ goto out;
++ h_inode = h_dentry->d_inode;
++ if (au_test_nfsd(current) && !h_inode)
++ goto out;
++ if (unlikely((!d_unhashed(dentry) && d_unhashed(h_dentry))
++ || !h_inode))
++ goto out;
+
-+ err = 0;
-+ bend = au_fbend(file);
-+ for (bindex = au_fbstart(file); !err && bindex <= bend; bindex++) {
-+ h_file = au_h_fptr(file, bindex);
-+ if (!h_file || !h_file->f_op || !h_file->f_op->flush)
-+ continue;
++ sb = dentry->d_sb;
++ br = au_sbr(sb, bindex);
++ h_file = ERR_PTR(-EACCES);
++ exec_flag = flags & vfsub_fmode_to_uint(FMODE_EXEC);
++ if (exec_flag && (br->br_mnt->mnt_flags & MNT_NOEXEC))
++ goto out;
+
-+ err = h_file->f_op->flush(h_file, id);
-+ if (!err)
-+ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL);
-+ /*ignore*/
++ /* drop flags for writing */
++ if (au_test_ro(sb, bindex, dentry->d_inode))
++ flags = au_file_roflags(flags);
++ flags &= ~O_CREAT;
++ atomic_inc(&br->br_count);
++ h_path.dentry = h_dentry;
++ h_path.mnt = br->br_mnt;
++ if (!au_special_file(h_inode->i_mode))
++ h_file = vfsub_dentry_open(&h_path, flags);
++ else {
++ /* this block depends upon the configuration */
++ di_read_unlock(dentry, AuLock_IR);
++ fi_write_unlock(file);
++ si_read_unlock(sb);
++ h_file = vfsub_dentry_open(&h_path, flags);
++ si_noflush_read_lock(sb);
++ fi_write_lock(file);
++ di_read_lock_child(dentry, AuLock_IR);
+ }
-+ au_cpup_attr_timesizes(dentry->d_inode);
++ if (IS_ERR(h_file))
++ goto out_br;
+
-+ di_read_unlock(dentry, AuLock_IW);
-+ fi_read_unlock(file);
-+ si_read_unlock(dentry->d_sb);
-+ return err;
-+}
++ if (exec_flag) {
++ err = deny_write_access(h_file);
++ if (unlikely(err)) {
++ fput(h_file);
++ h_file = ERR_PTR(err);
++ goto out_br;
++ }
++ }
++ fsnotify_open(h_dentry);
++ goto out; /* success */
+
-+/* ---------------------------------------------------------------------- */
++ out_br:
++ atomic_dec(&br->br_count);
++ out:
++ return h_file;
++}
+
-+static int do_open_nondir(struct file *file, int flags)
++int au_do_open(struct file *file, int (*open)(struct file *file, int flags),
++ struct au_fidir *fidir)
+{
+ int err;
-+ aufs_bindex_t bindex;
-+ struct file *h_file;
+ struct dentry *dentry;
-+ struct au_finfo *finfo;
+
-+ FiMustWriteLock(file);
++ err = au_finfo_init(file, fidir);
++ if (unlikely(err))
++ goto out;
+
-+ err = 0;
+ dentry = file->f_dentry;
-+ finfo = au_fi(file);
-+ finfo->fi_h_vm_ops = NULL;
-+ finfo->fi_vm_ops = NULL;
-+ bindex = au_dbstart(dentry);
-+ /* O_TRUNC is processed already */
-+ BUG_ON(au_test_ro(dentry->d_sb, bindex, dentry->d_inode)
-+ && (flags & O_TRUNC));
++ di_read_lock_child(dentry, AuLock_IR);
++ err = open(file, vfsub_file_flags(file));
++ di_read_unlock(dentry, AuLock_IR);
+
-+ h_file = au_h_open(dentry, bindex, flags, 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; */
++ fi_write_unlock(file);
++ if (unlikely(err)) {
++ au_fi(file)->fi_hdir = NULL;
++ au_finfo_fin(file);
+ }
++
++ out:
+ return err;
+}
+
-+static int aufs_open_nondir(struct inode *inode __maybe_unused,
-+ struct file *file)
++int au_reopen_nondir(struct file *file)
+{
-+ return au_do_open(file, do_open_nondir);
-+}
++ int err;
++ aufs_bindex_t bstart;
++ struct dentry *dentry;
++ struct file *h_file, *h_file_tmp;
+
-+static int aufs_release_nondir(struct inode *inode __maybe_unused,
-+ struct file *file)
-+{
-+ struct super_block *sb = file->f_dentry->d_sb;
++ dentry = file->f_dentry;
++ AuDebugOn(au_special_file(dentry->d_inode->i_mode));
++ bstart = au_dbstart(dentry);
++ h_file_tmp = NULL;
++ if (au_fbstart(file) == bstart) {
++ h_file = au_hf_top(file);
++ if (file->f_mode == h_file->f_mode)
++ return 0; /* success */
++ h_file_tmp = h_file;
++ get_file(h_file_tmp);
++ au_set_h_fptr(file, bstart, NULL);
++ }
++ AuDebugOn(au_fi(file)->fi_hdir);
++ AuDebugOn(au_fbstart(file) < bstart);
+
-+ si_noflush_read_lock(sb);
-+ kfree(au_fi(file)->fi_vm_ops);
-+ au_finfo_fin(file);
-+ si_read_unlock(sb);
-+ return 0;
++ h_file = au_h_open(dentry, bstart, vfsub_file_flags(file) & ~O_TRUNC,
++ file);
++ err = PTR_ERR(h_file);
++ if (IS_ERR(h_file))
++ goto out; /* todo: close all? */
++
++ err = 0;
++ au_set_fbstart(file, bstart);
++ au_set_h_fptr(file, bstart, h_file);
++ au_update_figen(file);
++ /* todo: necessary? */
++ /* file->f_ra = h_file->f_ra; */
++
++ out:
++ if (h_file_tmp)
++ fput(h_file_tmp);
++ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+static ssize_t aufs_read(struct file *file, char __user *buf, size_t count,
-+ loff_t *ppos)
++static int au_reopen_wh(struct file *file, aufs_bindex_t btgt,
++ struct dentry *hi_wh)
+{
-+ ssize_t err;
-+ struct dentry *dentry;
-+ struct file *h_file;
-+ struct super_block *sb;
++ int err;
++ aufs_bindex_t bstart;
++ struct au_dinfo *dinfo;
++ struct dentry *h_dentry;
++ struct au_hdentry *hdp;
+
-+ dentry = file->f_dentry;
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
-+ if (unlikely(err))
-+ goto out;
++ dinfo = au_di(file->f_dentry);
++ AuRwMustWriteLock(&dinfo->di_rwsem);
+
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ err = vfsub_read_u(h_file, buf, count, ppos);
-+ /* todo: necessary? */
-+ /* file->f_ra = h_file->f_ra; */
-+ fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
++ bstart = dinfo->di_bstart;
++ dinfo->di_bstart = btgt;
++ hdp = dinfo->di_hdentry;
++ h_dentry = hdp[0 + btgt].hd_dentry;
++ hdp[0 + btgt].hd_dentry = hi_wh;
++ err = au_reopen_nondir(file);
++ hdp[0 + btgt].hd_dentry = h_dentry;
++ dinfo->di_bstart = bstart;
+
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_read_unlock(file);
-+ out:
-+ si_read_unlock(sb);
+ return err;
+}
+
-+static ssize_t aufs_write(struct file *file, const char __user *ubuf,
-+ size_t count, loff_t *ppos)
++static int au_ready_to_write_wh(struct file *file, loff_t len,
++ aufs_bindex_t bcpup)
+{
-+ ssize_t err;
-+ aufs_bindex_t bstart;
-+ struct au_pin pin;
-+ struct dentry *dentry;
++ int err;
+ struct inode *inode;
-+ struct super_block *sb;
-+ struct file *h_file;
-+ char __user *buf = (char __user *)ubuf;
++ struct dentry *dentry, *hi_wh;
+
+ dentry = file->f_dentry;
-+ sb = dentry->d_sb;
++ au_update_dbstart(dentry);
+ inode = dentry->d_inode;
-+ mutex_lock(&inode->i_mutex);
-+ si_read_lock(sb, AuLock_FLUSH);
-+
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
-+ if (unlikely(err))
-+ goto out;
-+
-+ err = au_ready_to_write(file, -1, &pin);
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ goto out_unlock;
++ hi_wh = au_hi_wh(inode, bcpup);
++ if (!hi_wh)
++ err = au_sio_cpup_wh(dentry, bcpup, len, file);
++ else
++ /* already copied-up after unlink */
++ err = au_reopen_wh(file, bcpup, hi_wh);
+
-+ bstart = au_fbstart(file);
-+ h_file = au_h_fptr(file, bstart);
-+ au_unpin(&pin);
-+ err = vfsub_write_u(h_file, buf, count, ppos);
-+ au_cpup_attr_timesizes(inode);
-+ inode->i_mode = h_file->f_dentry->d_inode->i_mode;
++ if (!err
++ && inode->i_nlink > 1
++ && au_opt_test(au_mntflags(dentry->d_sb), PLINK))
++ au_plink_append(inode, bcpup, au_h_dptr(dentry, bcpup));
+
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_write_unlock(file);
-+ out:
-+ si_read_unlock(sb);
-+ mutex_unlock(&inode->i_mutex);
+ return err;
+}
+
-+static ssize_t aufs_aio_read(struct kiocb *kio, const struct iovec *iov,
-+ unsigned long nv, loff_t pos)
++/*
++ * prepare the @file for writing.
++ */
++int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin)
+{
-+ ssize_t err;
-+ struct file *file, *h_file;
-+ struct dentry *dentry;
++ int err;
++ aufs_bindex_t bstart, bcpup;
++ struct dentry *dentry, *parent, *h_dentry;
++ struct inode *h_inode, *inode;
+ struct super_block *sb;
++ struct file *h_file;
+
-+ file = kio->ki_filp;
+ dentry = file->f_dentry;
+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
-+ if (unlikely(err))
++ inode = dentry->d_inode;
++ AuDebugOn(au_special_file(inode->i_mode));
++ bstart = au_fbstart(file);
++ err = au_test_ro(sb, bstart, inode);
++ if (!err && (au_hf_top(file)->f_mode & FMODE_WRITE)) {
++ err = au_pin(pin, dentry, bstart, AuOpt_UDBA_NONE, /*flags*/0);
+ goto out;
++ }
+
-+ err = -ENOSYS;
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ if (h_file->f_op && h_file->f_op->aio_read) {
-+ err = security_file_permission(h_file, MAY_READ);
++ /* need to cpup */
++ parent = dget_parent(dentry);
++ di_write_lock_parent(parent);
++ err = AuWbrCopyup(au_sbi(sb), dentry);
++ bcpup = err;
++ if (unlikely(err < 0))
++ goto out_dgrade;
++ err = 0;
++
++ if (!au_h_dptr(parent, bcpup)) {
++ err = au_cpup_dirs(dentry, bcpup);
+ if (unlikely(err))
-+ goto out_unlock;
-+ if (!is_sync_kiocb(kio)) {
-+ get_file(h_file);
-+ fput(file);
-+ }
-+ kio->ki_filp = h_file;
-+ err = h_file->f_op->aio_read(kio, iov, nv, pos);
-+ /* todo: necessary? */
-+ /* file->f_ra = h_file->f_ra; */
-+ fsstack_copy_attr_atime(dentry->d_inode,
-+ h_file->f_dentry->d_inode);
-+ } else
-+ /* currently there is no such fs */
-+ WARN_ON_ONCE(h_file->f_op && h_file->f_op->read);
++ goto out_dgrade;
++ }
++
++ err = au_pin(pin, dentry, bcpup, AuOpt_UDBA_NONE,
++ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ if (unlikely(err))
++ goto out_dgrade;
++
++ h_dentry = au_hf_top(file)->f_dentry;
++ h_inode = h_dentry->d_inode;
++ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
++ h_file = au_h_open_pre(dentry, bstart);
++ if (IS_ERR(h_file)) {
++ err = PTR_ERR(h_file);
++ h_file = NULL;
++ } else if (d_unhashed(dentry) /* || d_unhashed(h_dentry) */
++ /* || !h_inode->i_nlink */) {
++ err = au_ready_to_write_wh(file, len, bcpup);
++ 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);
++ if (!err)
++ err = au_reopen_nondir(file);
++ }
++ mutex_unlock(&h_inode->i_mutex);
++ au_h_open_post(dentry, bstart, h_file);
++
++ if (!err) {
++ 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_read_unlock(dentry, AuLock_IR);
-+ fi_read_unlock(file);
++ di_read_unlock(parent, AuLock_IR);
++ out_dput:
++ dput(parent);
+ out:
-+ si_read_unlock(sb);
+ return err;
+}
+
-+static ssize_t aufs_aio_write(struct kiocb *kio, const struct iovec *iov,
-+ unsigned long nv, loff_t pos)
++/* ---------------------------------------------------------------------- */
++
++int au_do_flush(struct file *file, fl_owner_t id,
++ int (*flush)(struct file *file, fl_owner_t id))
+{
-+ ssize_t err;
-+ aufs_bindex_t bstart;
-+ struct au_pin pin;
++ int err;
+ struct dentry *dentry;
-+ struct inode *inode;
+ struct super_block *sb;
-+ struct file *file, *h_file;
++ struct inode *inode;
+
-+ file = kio->ki_filp;
+ dentry = file->f_dentry;
+ sb = dentry->d_sb;
+ inode = dentry->d_inode;
-+ mutex_lock(&inode->i_mutex);
-+ si_read_lock(sb, AuLock_FLUSH);
-+
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
-+ if (unlikely(err))
-+ goto out;
-+
-+ err = au_ready_to_write(file, -1, &pin);
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ goto out_unlock;
++ si_noflush_read_lock(sb);
++ fi_read_lock(file);
++ di_read_lock_child(dentry, AuLock_IW);
+
-+ err = -ENOSYS;
-+ bstart = au_fbstart(file);
-+ h_file = au_h_fptr(file, bstart);
-+ au_unpin(&pin);
-+ if (h_file->f_op && h_file->f_op->aio_write) {
-+ err = security_file_permission(h_file, MAY_WRITE);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ if (!is_sync_kiocb(kio)) {
-+ get_file(h_file);
-+ fput(file);
-+ }
-+ kio->ki_filp = h_file;
-+ err = h_file->f_op->aio_write(kio, iov, nv, pos);
-+ au_cpup_attr_timesizes(inode);
-+ inode->i_mode = h_file->f_dentry->d_inode->i_mode;
-+ } else
-+ /* currently there is no such fs */
-+ WARN_ON_ONCE(h_file->f_op && h_file->f_op->write);
++ err = flush(file, id);
++ au_cpup_attr_timesizes(inode);
+
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_write_unlock(file);
-+ out:
++ di_read_unlock(dentry, AuLock_IW);
++ fi_read_unlock(file);
+ si_read_unlock(sb);
-+ mutex_unlock(&inode->i_mutex);
+ return err;
+}
+
-+static ssize_t aufs_splice_read(struct file *file, loff_t *ppos,
-+ struct pipe_inode_info *pipe, size_t len,
-+ unsigned int flags)
++/* ---------------------------------------------------------------------- */
++
++static int au_file_refresh_by_inode(struct file *file, int *need_reopen)
+{
-+ ssize_t err;
-+ struct file *h_file;
-+ struct dentry *dentry;
++ int err;
++ aufs_bindex_t bstart;
++ struct au_pin pin;
++ struct au_finfo *finfo;
++ struct dentry *dentry, *parent, *hi_wh;
++ struct inode *inode;
+ struct super_block *sb;
+
++ FiMustWriteLock(file);
++
++ err = 0;
++ finfo = au_fi(file);
+ dentry = file->f_dentry;
+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
-+ if (unlikely(err))
++ inode = dentry->d_inode;
++ bstart = au_ibstart(inode);
++ if (bstart == finfo->fi_btop)
+ goto out;
+
-+ err = -EINVAL;
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ if (au_test_loopback_kthread()) {
-+ file->f_mapping = h_file->f_mapping;
-+ smp_mb(); /* unnecessary? */
++ parent = dget_parent(dentry);
++ if (au_test_ro(sb, bstart, inode)) {
++ di_read_lock_parent(parent, !AuLock_IR);
++ err = AuWbrCopyup(au_sbi(sb), dentry);
++ bstart = err;
++ di_read_unlock(parent, !AuLock_IR);
++ if (unlikely(err < 0))
++ goto out_parent;
++ err = 0;
+ }
-+ err = vfsub_splice_to(h_file, ppos, pipe, len, flags);
-+ /* 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);
++ di_read_lock_parent(parent, AuLock_IR);
++ hi_wh = au_hi_wh(inode, bstart);
++ if (au_opt_test(au_mntflags(sb), PLINK)
++ && au_plink_test(inode)
++ && !d_unhashed(dentry)) {
++ err = au_test_and_cpup_dirs(dentry, bstart);
++ if (unlikely(err))
++ goto out_unlock;
++
++ /* always superio. */
++ err = au_pin(&pin, dentry, bstart, AuOpt_UDBA_NONE,
++ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ if (!err)
++ err = au_sio_cpup_simple(dentry, bstart, -1,
++ AuCpup_DTIME);
++ au_unpin(&pin);
++ } else if (hi_wh) {
++ /* already copied-up after unlink */
++ err = au_reopen_wh(file, bstart, hi_wh);
++ *need_reopen = 0;
++ }
+
++ out_unlock:
++ di_read_unlock(parent, AuLock_IR);
++ out_parent:
++ dput(parent);
+ out:
-+ si_read_unlock(sb);
+ return err;
+}
+
-+static ssize_t
-+aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos,
-+ size_t len, unsigned int flags)
++static void au_do_refresh_dir(struct file *file)
+{
-+ ssize_t err;
-+ struct au_pin pin;
-+ struct dentry *dentry;
-+ struct inode *inode;
++ aufs_bindex_t bindex, bend, new_bindex, brid;
++ struct au_hfile *p, tmp, *q;
++ struct au_finfo *finfo;
+ struct super_block *sb;
-+ struct file *h_file;
++ struct au_fidir *fidir;
+
-+ dentry = file->f_dentry;
-+ inode = dentry->d_inode;
-+ mutex_lock(&inode->i_mutex);
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
++ FiMustWriteLock(file);
+
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
-+ if (unlikely(err))
-+ goto out;
++ sb = file->f_dentry->d_sb;
++ finfo = au_fi(file);
++ fidir = finfo->fi_hdir;
++ AuDebugOn(!fidir);
++ p = fidir->fd_hfile + finfo->fi_btop;
++ brid = p->hf_br->br_id;
++ bend = fidir->fd_bbot;
++ for (bindex = finfo->fi_btop; bindex <= bend; bindex++, p++) {
++ if (!p->hf_file)
++ continue;
+
-+ err = au_ready_to_write(file, -1, &pin);
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ goto out_unlock;
-+
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ au_unpin(&pin);
-+ err = vfsub_splice_from(pipe, h_file, ppos, len, flags);
-+ au_cpup_attr_timesizes(inode);
-+ inode->i_mode = h_file->f_dentry->d_inode->i_mode;
-+
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_write_unlock(file);
-+ out:
-+ si_read_unlock(sb);
-+ mutex_unlock(&inode->i_mutex);
-+ return err;
-+}
-+
-+/* ---------------------------------------------------------------------- */
++ new_bindex = au_br_index(sb, p->hf_br->br_id);
++ if (new_bindex == bindex)
++ continue;
++ if (new_bindex < 0) {
++ au_set_h_fptr(file, bindex, NULL);
++ continue;
++ }
+
-+static struct file *au_safe_file(struct vm_area_struct *vma)
-+{
-+ struct file *file;
++ /* swap two lower inode, and loop again */
++ q = fidir->fd_hfile + new_bindex;
++ tmp = *q;
++ *q = *p;
++ *p = tmp;
++ if (tmp.hf_file) {
++ bindex--;
++ p--;
++ }
++ }
+
-+ file = vma->vm_file;
-+ if (file->private_data && au_test_aufs(file->f_dentry->d_sb))
-+ return file;
-+ return NULL;
-+}
++ p = fidir->fd_hfile;
++ if (!au_test_mmapped(file) && !d_unhashed(file->f_dentry)) {
++ bend = au_sbend(sb);
++ for (finfo->fi_btop = 0; finfo->fi_btop <= bend;
++ finfo->fi_btop++, p++)
++ if (p->hf_file) {
++ if (p->hf_file->f_dentry
++ && p->hf_file->f_dentry->d_inode)
++ break;
++ else
++ au_hfput(p, file);
++ }
++ } else {
++ bend = au_br_index(sb, brid);
++ for (finfo->fi_btop = 0; finfo->fi_btop < bend;
++ finfo->fi_btop++, p++)
++ if (p->hf_file)
++ au_hfput(p, file);
++ bend = au_sbend(sb);
++ }
+
-+static void au_reset_file(struct vm_area_struct *vma, struct file *file)
-+{
-+ vma->vm_file = file;
-+ /* smp_mb(); */ /* flush vm_file */
++ p = fidir->fd_hfile + bend;
++ for (fidir->fd_bbot = bend; fidir->fd_bbot >= finfo->fi_btop;
++ fidir->fd_bbot--, p--)
++ if (p->hf_file) {
++ if (p->hf_file->f_dentry
++ && p->hf_file->f_dentry->d_inode)
++ break;
++ else
++ au_hfput(p, file);
++ }
++ AuDebugOn(fidir->fd_bbot < finfo->fi_btop);
+}
+
-+static int aufs_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
++/*
++ * after branch manipulating, refresh the file.
++ */
++static int refresh_file(struct file *file, int (*reopen)(struct file *file))
+{
-+ int err;
-+ static DECLARE_WAIT_QUEUE_HEAD(wq);
-+ struct file *file, *h_file;
++ int err, need_reopen;
++ aufs_bindex_t bend, bindex;
++ struct dentry *dentry;
+ struct au_finfo *finfo;
++ struct au_hfile *hfile;
+
-+ /* todo: non-robr mode, user vm_file as it is? */
-+ wait_event(wq, (file = au_safe_file(vma)));
-+
-+ /* do not revalidate, no si lock */
++ dentry = file->f_dentry;
+ finfo = au_fi(file);
-+ h_file = finfo->fi_hfile[0 + finfo->fi_bstart].hf_file;
-+ AuDebugOn(!h_file || !finfo->fi_h_vm_ops);
++ if (!finfo->fi_hdir) {
++ hfile = &finfo->fi_htop;
++ AuDebugOn(!hfile->hf_file);
++ bindex = au_br_index(dentry->d_sb, hfile->hf_br->br_id);
++ AuDebugOn(bindex < 0);
++ if (bindex != finfo->fi_btop)
++ au_set_fbstart(file, bindex);
++ } else {
++ err = au_fidir_realloc(finfo, au_sbend(dentry->d_sb) + 1);
++ if (unlikely(err))
++ goto out;
++ au_do_refresh_dir(file);
++ }
+
-+ mutex_lock(&finfo->fi_vm_mtx);
-+ vma->vm_file = h_file;
-+ err = finfo->fi_h_vm_ops->fault(vma, vmf);
-+ /* todo: necessary? */
-+ /* file->f_ra = h_file->f_ra; */
-+ au_reset_file(vma, file);
-+ mutex_unlock(&finfo->fi_vm_mtx);
-+#if 0 /* def CONFIG_SMP */
-+ /* wake_up_nr(&wq, online_cpu - 1); */
-+ wake_up_all(&wq);
-+#else
-+ wake_up(&wq);
-+#endif
++ err = 0;
++ need_reopen = 1;
++ if (!au_test_mmapped(file))
++ err = au_file_refresh_by_inode(file, &need_reopen);
++ if (!err && need_reopen && !d_unhashed(dentry))
++ err = reopen(file);
++ if (!err) {
++ au_update_figen(file);
++ goto out; /* success */
++ }
++
++ /* error, close all lower files */
++ if (finfo->fi_hdir) {
++ bend = au_fbend_dir(file);
++ for (bindex = au_fbstart(file); bindex <= bend; bindex++)
++ au_set_h_fptr(file, bindex, NULL);
++ }
+
++ out:
+ return err;
+}
+
-+static int aufs_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
++/* common function to regular file and dir */
++int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file),
++ int wlock)
+{
+ int err;
-+ static DECLARE_WAIT_QUEUE_HEAD(wq);
-+ struct file *file, *h_file;
-+ struct au_finfo *finfo;
++ unsigned int sigen, figen;
++ aufs_bindex_t bstart;
++ unsigned char pseudo_link;
++ struct dentry *dentry;
++ struct inode *inode;
+
-+ wait_event(wq, (file = au_safe_file(vma)));
++ err = 0;
++ dentry = file->f_dentry;
++ inode = dentry->d_inode;
++ AuDebugOn(au_special_file(inode->i_mode));
++ sigen = au_sigen(dentry->d_sb);
++ fi_write_lock(file);
++ figen = au_figen(file);
++ di_write_lock_child(dentry);
++ bstart = au_dbstart(dentry);
++ pseudo_link = (bstart != au_ibstart(inode));
++ if (sigen == figen && !pseudo_link && au_fbstart(file) == bstart) {
++ if (!wlock) {
++ di_downgrade_lock(dentry, AuLock_IR);
++ fi_downgrade_lock(file);
++ }
++ goto out; /* success */
++ }
+
-+ finfo = au_fi(file);
-+ h_file = finfo->fi_hfile[0 + finfo->fi_bstart].hf_file;
-+ AuDebugOn(!h_file || !finfo->fi_h_vm_ops);
++ AuDbg("sigen %d, figen %d\n", sigen, figen);
++ if (sigen != au_digen(dentry)
++ || sigen != au_iigen(inode)) {
++ err = au_reval_dpath(dentry, sigen);
++ if (unlikely(err < 0))
++ goto out;
++ AuDebugOn(au_digen(dentry) != sigen
++ || au_iigen(inode) != sigen);
++ }
+
-+ mutex_lock(&finfo->fi_vm_mtx);
-+ vma->vm_file = h_file;
-+ err = finfo->fi_h_vm_ops->page_mkwrite(vma, vmf);
-+ au_reset_file(vma, file);
-+ mutex_unlock(&finfo->fi_vm_mtx);
-+ wake_up(&wq);
++ err = refresh_file(file, reopen);
++ if (!err) {
++ if (!wlock) {
++ di_downgrade_lock(dentry, AuLock_IR);
++ fi_downgrade_lock(file);
++ }
++ } else {
++ di_write_unlock(dentry);
++ fi_write_unlock(file);
++ }
+
++ out:
+ return err;
+}
+
-+static void aufs_vm_close(struct vm_area_struct *vma)
++/* ---------------------------------------------------------------------- */
++
++/* cf. aufs_nopage() */
++/* for madvise(2) */
++static int aufs_readpage(struct file *file __maybe_unused, struct page *page)
+{
-+ static DECLARE_WAIT_QUEUE_HEAD(wq);
-+ struct file *file, *h_file;
-+ struct au_finfo *finfo;
++ unlock_page(page);
++ return 0;
++}
+
-+ wait_event(wq, (file = au_safe_file(vma)));
++/* it will never be called, but necessary to support O_DIRECT */
++static ssize_t aufs_direct_IO(int rw, struct kiocb *iocb,
++ const struct iovec *iov, loff_t offset,
++ unsigned long nr_segs)
++{ BUG(); return 0; }
+
-+ finfo = au_fi(file);
-+ h_file = finfo->fi_hfile[0 + finfo->fi_bstart].hf_file;
-+ AuDebugOn(!h_file || !finfo->fi_h_vm_ops);
++/*
++ * it will never be called, but madvise and fadvise behaves differently
++ * when get_xip_mem is defined
++ */
++static int aufs_get_xip_mem(struct address_space *mapping, pgoff_t pgoff,
++ int create, void **kmem, unsigned long *pfn)
++{ BUG(); return 0; }
+
-+ mutex_lock(&finfo->fi_vm_mtx);
-+ vma->vm_file = h_file;
-+ finfo->fi_h_vm_ops->close(vma);
-+ au_reset_file(vma, file);
-+ mutex_unlock(&finfo->fi_vm_mtx);
-+ wake_up(&wq);
-+}
++/* they will never be called. */
++#ifdef CONFIG_AUFS_DEBUG
++static int aufs_write_begin(struct file *file, struct address_space *mapping,
++ loff_t pos, unsigned len, unsigned flags,
++ struct page **pagep, void **fsdata)
++{ AuUnsupport(); return 0; }
++static int aufs_write_end(struct file *file, struct address_space *mapping,
++ loff_t pos, unsigned len, unsigned copied,
++ struct page *page, void *fsdata)
++{ AuUnsupport(); return 0; }
++static int aufs_writepage(struct page *page, struct writeback_control *wbc)
++{ AuUnsupport(); return 0; }
++static void aufs_sync_page(struct page *page)
++{ AuUnsupport(); }
+
-+static struct vm_operations_struct aufs_vm_ops = {
-+ /* .close and .page_mkwrite are not set by default */
-+ .fault = aufs_fault,
++static int aufs_set_page_dirty(struct page *page)
++{ AuUnsupport(); return 0; }
++static void aufs_invalidatepage(struct page *page, unsigned long offset)
++{ AuUnsupport(); }
++static int aufs_releasepage(struct page *page, gfp_t gfp)
++{ AuUnsupport(); return 0; }
++static int aufs_migratepage(struct address_space *mapping, struct page *newpage,
++ struct page *page)
++{ AuUnsupport(); return 0; }
++static int aufs_launder_page(struct page *page)
++{ AuUnsupport(); return 0; }
++static int aufs_is_partially_uptodate(struct page *page,
++ read_descriptor_t *desc,
++ unsigned long from)
++{ AuUnsupport(); return 0; }
++static int aufs_error_remove_page(struct address_space *mapping,
++ struct page *page)
++{ AuUnsupport(); return 0; }
++#endif /* CONFIG_AUFS_DEBUG */
++
++const struct address_space_operations aufs_aop = {
++ .readpage = aufs_readpage,
++ .direct_IO = aufs_direct_IO,
++ .get_xip_mem = aufs_get_xip_mem,
++#ifdef CONFIG_AUFS_DEBUG
++ .writepage = aufs_writepage,
++ .sync_page = aufs_sync_page,
++ /* no writepages, because of writepage */
++ .set_page_dirty = aufs_set_page_dirty,
++ /* no readpages, because of readpage */
++ .write_begin = aufs_write_begin,
++ .write_end = aufs_write_end,
++ /* no bmap, no block device */
++ .invalidatepage = aufs_invalidatepage,
++ .releasepage = aufs_releasepage,
++ .migratepage = aufs_migratepage,
++ .launder_page = aufs_launder_page,
++ .is_partially_uptodate = aufs_is_partially_uptodate,
++ .error_remove_page = aufs_error_remove_page
++#endif /* CONFIG_AUFS_DEBUG */
+};
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/file.h linux-2.6.34/fs/aufs/file.h
+--- linux-2.6.34.org/fs/aufs/file.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/file.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,234 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
+
-+/* ---------------------------------------------------------------------- */
++/*
++ * file operations
++ */
+
-+static unsigned long au_prot_conv(unsigned long flags)
-+{
-+ unsigned long prot;
++#ifndef __AUFS_FILE_H__
++#define __AUFS_FILE_H__
+
-+ prot = 0;
-+ if (flags & VM_READ)
-+ prot |= PROT_READ;
-+ if (flags & VM_WRITE)
-+ prot |= PROT_WRITE;
-+ if (flags & VM_EXEC)
-+ prot |= PROT_EXEC;
-+ return prot;
-+}
++#ifdef __KERNEL__
+
-+static struct vm_operations_struct *au_vm_ops(struct file *h_file,
-+ struct vm_area_struct *vma)
-+{
-+ struct vm_operations_struct *vm_ops;
-+ int err;
++#include <linux/fs.h>
++#include <linux/poll.h>
++#include <linux/aufs_type.h>
++#include "rwsem.h"
+
-+ vm_ops = ERR_PTR(-ENODEV);
-+ if (!h_file->f_op || !h_file->f_op->mmap)
-+ goto out;
-+
-+ err = ima_file_mmap(h_file, au_prot_conv(vma->vm_flags));
-+ vm_ops = ERR_PTR(err);
-+ if (err)
-+ goto out;
-+
-+ err = h_file->f_op->mmap(h_file, vma);
-+ vm_ops = ERR_PTR(err);
-+ if (unlikely(err))
-+ goto out;
-+
-+ vm_ops = vma->vm_ops;
-+ err = do_munmap(current->mm, vma->vm_start,
-+ vma->vm_end - vma->vm_start);
-+ if (unlikely(err)) {
-+ AuIOErr("failed internal unmapping %.*s, %d\n",
-+ AuDLNPair(h_file->f_dentry), err);
-+ vm_ops = ERR_PTR(-EIO);
-+ }
++struct au_branch;
++struct au_hfile {
++ struct file *hf_file;
++ struct au_branch *hf_br;
++};
+
-+ out:
-+ return vm_ops;
-+}
++struct au_vdir;
++struct au_fidir {
++ aufs_bindex_t fd_bbot;
++ aufs_bindex_t fd_nent;
++ struct au_vdir *fd_vdir_cache;
++ struct au_hfile fd_hfile[];
++};
+
-+static int au_custom_vm_ops(struct au_finfo *finfo, struct vm_area_struct *vma)
++static inline int au_fidir_sz(int nent)
+{
-+ int err;
-+ struct vm_operations_struct *h_ops;
-+
-+ AuRwMustAnyLock(&finfo->fi_rwsem);
++ AuDebugOn(nent < 0);
++ return sizeof(struct au_fidir) + sizeof(struct au_hfile) * nent;
++}
+
-+ err = 0;
-+ h_ops = finfo->fi_h_vm_ops;
-+ AuDebugOn(!h_ops);
-+ if ((!h_ops->page_mkwrite && !h_ops->close)
-+ || finfo->fi_vm_ops)
-+ goto out;
++struct au_finfo {
++ atomic_t fi_generation;
+
-+ err = -ENOMEM;
-+ finfo->fi_vm_ops = kmemdup(&aufs_vm_ops, sizeof(aufs_vm_ops), GFP_NOFS);
-+ if (unlikely(!finfo->fi_vm_ops))
-+ goto out;
++ struct au_rwsem fi_rwsem;
++ aufs_bindex_t fi_btop;
++
++ /* do not union them */
++ struct { /* for non-dir */
++ struct au_hfile fi_htop;
++ struct vm_operations_struct *fi_hvmop;
++ struct mutex fi_vm_mtx;
++ struct mutex fi_mmap;
++ };
++ struct au_fidir *fi_hdir; /* for dir only */
++} ____cacheline_aligned_in_smp;
+
-+ err = 0;
-+ if (h_ops->page_mkwrite)
-+ finfo->fi_vm_ops->page_mkwrite = aufs_page_mkwrite;
-+ if (h_ops->close)
-+ finfo->fi_vm_ops->close = aufs_vm_close;
++/* ---------------------------------------------------------------------- */
+
-+ vma->vm_ops = finfo->fi_vm_ops;
++/* file.c */
++extern const struct address_space_operations aufs_aop;
++unsigned int au_file_roflags(unsigned int flags);
++struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
++ struct file *file);
++int au_do_open(struct file *file, int (*open)(struct file *file, int flags),
++ struct au_fidir *fidir);
++int au_reopen_nondir(struct file *file);
++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 au_do_flush(struct file *file, fl_owner_t id,
++ int (*flush)(struct file *file, fl_owner_t id));
+
-+ out:
-+ return err;
-+}
++/* poll.c */
++#ifdef CONFIG_AUFS_POLL
++unsigned int aufs_poll(struct file *file, poll_table *wait);
++#endif
+
-+static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
++#ifdef CONFIG_AUFS_BR_HFSPLUS
++/* hfsplus.c */
++struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex);
++void au_h_open_post(struct dentry *dentry, aufs_bindex_t bindex,
++ struct file *h_file);
++#else
++static inline
++struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex)
+{
-+ int err;
-+ unsigned char wlock, mmapped;
-+ struct dentry *dentry;
-+ struct super_block *sb;
-+ struct file *h_file;
-+ struct vm_operations_struct *vm_ops;
++ return NULL;
++}
+
-+ dentry = file->f_dentry;
-+ wlock = !!(file->f_mode & FMODE_WRITE) && (vma->vm_flags & VM_SHARED);
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
-+ if (unlikely(err))
-+ goto out;
++AuStubVoid(au_h_open_post, struct dentry *dentry, aufs_bindex_t bindex,
++ struct file *h_file);
++#endif
+
-+ mmapped = !!au_test_mmapped(file);
-+ if (wlock) {
-+ struct au_pin pin;
++/* f_op.c */
++extern const struct file_operations aufs_file_fop;
++extern const struct vm_operations_struct aufs_vm_ops;
++int au_do_open_nondir(struct file *file, int flags);
++int aufs_release_nondir(struct inode *inode __maybe_unused, struct file *file);
++
++#ifdef CONFIG_AUFS_SP_IATTR
++/* f_op_sp.c */
++int au_special_file(umode_t mode);
++void au_init_special_fop(struct inode *inode, umode_t mode, dev_t rdev);
++#else
++AuStubInt0(au_special_file, umode_t mode)
++static inline void au_init_special_fop(struct inode *inode, umode_t mode,
++ dev_t rdev)
++{
++ init_special_inode(inode, mode, rdev);
++}
++#endif
+
-+ err = au_ready_to_write(file, -1, &pin);
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ au_unpin(&pin);
-+ } else
-+ di_downgrade_lock(dentry, AuLock_IR);
++/* finfo.c */
++void au_hfput(struct au_hfile *hf, struct file *file);
++void au_set_h_fptr(struct file *file, aufs_bindex_t bindex,
++ struct file *h_file);
+
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ if (!mmapped && au_test_fs_bad_mapping(h_file->f_dentry->d_sb)) {
-+ /*
-+ * by this assignment, f_mapping will differs from aufs inode
-+ * i_mapping.
-+ * if someone else mixes the use of f_dentry->d_inode and
-+ * f_mapping->host, then a problem may arise.
-+ */
-+ file->f_mapping = h_file->f_mapping;
-+ }
++void au_update_figen(struct file *file);
++void au_fi_mmap_lock(struct file *file);
++void au_fi_mmap_unlock(struct file *file);
++struct au_fidir *au_fidir_alloc(struct super_block *sb);
++int au_fidir_realloc(struct au_finfo *finfo, int nbr);
+
-+ vm_ops = NULL;
-+ if (!mmapped) {
-+ vm_ops = au_vm_ops(h_file, vma);
-+ err = PTR_ERR(vm_ops);
-+ if (IS_ERR(vm_ops))
-+ goto out_unlock;
-+ }
++void au_fi_init_once(void *_fi);
++void au_finfo_fin(struct file *file);
++int au_finfo_init(struct file *file, struct au_fidir *fidir);
+
-+ /*
-+ * unnecessary to handle MAP_DENYWRITE and deny_write_access()?
-+ * currently MAP_DENYWRITE from userspace is ignored, but elf loader
-+ * sets it. when FMODE_EXEC is set (by open_exec() or sys_uselib()),
-+ * both of the aufs file and the lower file is deny_write_access()-ed.
-+ * finally I hope we can skip handlling MAP_DENYWRITE here.
-+ */
-+ err = generic_file_mmap(file, vma);
-+ if (unlikely(err))
-+ goto out_unlock;
++/* ioctl.c */
++long aufs_ioctl_nondir(struct file *file, unsigned int cmd, unsigned long arg);
+
-+ vma->vm_ops = &aufs_vm_ops;
-+ if (!mmapped) {
-+ struct au_finfo *finfo = au_fi(file);
++/* ---------------------------------------------------------------------- */
+
-+ finfo->fi_h_vm_ops = vm_ops;
-+ mutex_init(&finfo->fi_vm_mtx);
-+ }
++static inline struct au_finfo *au_fi(struct file *file)
++{
++ return file->private_data;
++}
+
-+ err = au_custom_vm_ops(au_fi(file), vma);
-+ if (unlikely(err))
-+ goto out_unlock;
++/* ---------------------------------------------------------------------- */
+
-+ vfsub_file_accessed(h_file);
-+ fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
++/*
++ * fi_read_lock, fi_write_lock,
++ * fi_read_unlock, fi_write_unlock, fi_downgrade_lock
++ */
++AuSimpleRwsemFuncs(fi, struct file *f, &au_fi(f)->fi_rwsem);
+
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_write_unlock(file);
-+ out:
-+ si_read_unlock(sb);
-+ return err;
-+}
++#define FiMustNoWaiters(f) AuRwMustNoWaiters(&au_fi(f)->fi_rwsem)
++#define FiMustAnyLock(f) AuRwMustAnyLock(&au_fi(f)->fi_rwsem)
++#define FiMustWriteLock(f) AuRwMustWriteLock(&au_fi(f)->fi_rwsem)
+
+/* ---------------------------------------------------------------------- */
+
-+static int aufs_fsync_nondir(struct file *file, struct dentry *dentry,
-+ int datasync)
++/* todo: hard/soft set? */
++static inline aufs_bindex_t au_fbstart(struct file *file)
+{
-+ int err;
-+ struct au_pin pin;
-+ struct inode *inode;
-+ struct file *h_file;
-+ struct super_block *sb;
++ FiMustAnyLock(file);
++ return au_fi(file)->fi_btop;
++}
+
-+ inode = dentry->d_inode;
-+ IMustLock(file->f_mapping->host);
-+ if (inode != file->f_mapping->host) {
-+ mutex_unlock(&file->f_mapping->host->i_mutex);
-+ mutex_lock(&inode->i_mutex);
-+ }
-+ IMustLock(inode);
++static inline aufs_bindex_t au_fbend_dir(struct file *file)
++{
++ FiMustAnyLock(file);
++ AuDebugOn(!au_fi(file)->fi_hdir);
++ return au_fi(file)->fi_hdir->fd_bbot;
++}
+
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
++static inline struct au_vdir *au_fvdir_cache(struct file *file)
++{
++ FiMustAnyLock(file);
++ AuDebugOn(!au_fi(file)->fi_hdir);
++ return au_fi(file)->fi_hdir->fd_vdir_cache;
++}
+
-+ err = 0; /* -EBADF; */ /* posix? */
-+ if (unlikely(!(file->f_mode & FMODE_WRITE)))
-+ goto out;
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
-+ if (unlikely(err))
-+ goto out;
++static inline void au_set_fbstart(struct file *file, aufs_bindex_t bindex)
++{
++ FiMustWriteLock(file);
++ au_fi(file)->fi_btop = bindex;
++}
+
-+ err = au_ready_to_write(file, -1, &pin);
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ au_unpin(&pin);
++static inline void au_set_fbend_dir(struct file *file, aufs_bindex_t bindex)
++{
++ FiMustWriteLock(file);
++ AuDebugOn(!au_fi(file)->fi_hdir);
++ au_fi(file)->fi_hdir->fd_bbot = bindex;
++}
+
-+ err = -EINVAL;
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ if (h_file->f_op && h_file->f_op->fsync) {
-+ struct dentry *h_d;
-+ struct mutex *h_mtx;
++static inline void au_set_fvdir_cache(struct file *file,
++ struct au_vdir *vdir_cache)
++{
++ FiMustWriteLock(file);
++ AuDebugOn(!au_fi(file)->fi_hdir);
++ au_fi(file)->fi_hdir->fd_vdir_cache = vdir_cache;
++}
+
-+ /*
-+ * no filemap_fdatawrite() since aufs file has no its own
-+ * mapping, but dir.
-+ */
-+ h_d = h_file->f_dentry;
-+ h_mtx = &h_d->d_inode->i_mutex;
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ err = h_file->f_op->fsync(h_file, h_d, datasync);
-+ if (!err)
-+ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL);
-+ /*ignore*/
-+ au_cpup_attr_timesizes(inode);
-+ mutex_unlock(h_mtx);
-+ }
++static inline struct file *au_hf_top(struct file *file)
++{
++ FiMustAnyLock(file);
++ AuDebugOn(au_fi(file)->fi_hdir);
++ return au_fi(file)->fi_htop.hf_file;
++}
+
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_write_unlock(file);
-+ out:
-+ si_read_unlock(sb);
-+ if (inode != file->f_mapping->host) {
-+ mutex_unlock(&inode->i_mutex);
-+ mutex_lock(&file->f_mapping->host->i_mutex);
-+ }
-+ return err;
++static inline struct file *au_hf_dir(struct file *file, aufs_bindex_t bindex)
++{
++ FiMustAnyLock(file);
++ AuDebugOn(!au_fi(file)->fi_hdir);
++ return au_fi(file)->fi_hdir->fd_hfile[0 + bindex].hf_file;
+}
+
-+/* no one supports this operation, currently */
-+#if 0
-+static int aufs_aio_fsync_nondir(struct kiocb *kio, int datasync)
++/* todo: memory barrier? */
++static inline unsigned int au_figen(struct file *f)
+{
-+ int err;
-+ struct au_pin pin;
-+ struct dentry *dentry;
-+ struct inode *inode;
-+ struct file *file, *h_file;
-+ struct super_block *sb;
++ return atomic_read(&au_fi(f)->fi_generation);
++}
+
-+ file = kio->ki_filp;
-+ dentry = file->f_dentry;
-+ inode = dentry->d_inode;
-+ mutex_lock(&inode->i_mutex);
++static inline int au_test_mmapped(struct file *f)
++{
++ FiMustAnyLock(f);
++ return !!(au_fi(f)->fi_hvmop);
++}
+
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
++#endif /* __KERNEL__ */
++#endif /* __AUFS_FILE_H__ */
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/finfo.c linux-2.6.34/fs/aufs/finfo.c
+--- linux-2.6.34.org/fs/aufs/finfo.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/finfo.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,167 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
+
-+ err = 0; /* -EBADF; */ /* posix? */
-+ if (unlikely(!(file->f_mode & FMODE_WRITE)))
-+ goto out;
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
-+ if (unlikely(err))
-+ goto out;
++/*
++ * file private data
++ */
+
-+ err = au_ready_to_write(file, -1, &pin);
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ au_unpin(&pin);
++#include <linux/file.h>
++#include "aufs.h"
+
-+ err = -ENOSYS;
-+ h_file = au_h_fptr(file, au_fbstart(file));
-+ if (h_file->f_op && h_file->f_op->aio_fsync) {
-+ struct dentry *h_d;
-+ struct mutex *h_mtx;
++void au_hfput(struct au_hfile *hf, struct file *file)
++{
++ /* todo: direct access f_flags */
++ if (vfsub_file_flags(file) & vfsub_fmode_to_uint(FMODE_EXEC))
++ allow_write_access(hf->hf_file);
++ fput(hf->hf_file);
++ hf->hf_file = NULL;
++ atomic_dec_return(&hf->hf_br->br_count);
++ hf->hf_br = NULL;
++}
+
-+ h_d = h_file->f_dentry;
-+ h_mtx = &h_d->d_inode->i_mutex;
-+ if (!is_sync_kiocb(kio)) {
-+ get_file(h_file);
-+ fput(file);
-+ }
-+ kio->ki_filp = h_file;
-+ err = h_file->f_op->aio_fsync(kio, datasync);
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ if (!err)
-+ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL);
-+ /*ignore*/
-+ au_cpup_attr_timesizes(inode);
-+ mutex_unlock(h_mtx);
++void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *val)
++{
++ struct au_finfo *finfo = au_fi(file);
++ struct au_hfile *hf;
++ struct au_fidir *fidir;
++
++ fidir = finfo->fi_hdir;
++ if (!fidir) {
++ AuDebugOn(finfo->fi_btop != bindex);
++ hf = &finfo->fi_htop;
++ } else
++ hf = fidir->fd_hfile + bindex;
++
++ if (hf && hf->hf_file)
++ au_hfput(hf, file);
++ if (val) {
++ FiMustWriteLock(file);
++ hf->hf_file = val;
++ hf->hf_br = au_sbr(file->f_dentry->d_sb, bindex);
+ }
++}
+
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ fi_write_unlock(file);
-+ out:
-+ si_read_unlock(sb);
-+ mutex_unlock(&inode->i_mutex);
-+ return err;
++void au_update_figen(struct file *file)
++{
++ atomic_set(&au_fi(file)->fi_generation, au_digen(file->f_dentry));
++ /* smp_mb(); */ /* atomic_set */
+}
-+#endif
+
-+static int aufs_fasync(int fd, struct file *file, int flag)
++/* ---------------------------------------------------------------------- */
++
++void au_fi_mmap_lock(struct file *file)
+{
-+ int err;
-+ struct file *h_file;
-+ struct dentry *dentry;
-+ struct super_block *sb;
++ FiMustWriteLock(file);
++ lockdep_off();
++ mutex_lock(&au_fi(file)->fi_mmap);
++ lockdep_on();
++}
+
-+ dentry = file->f_dentry;
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
-+ if (unlikely(err))
-+ goto out;
++void au_fi_mmap_unlock(struct file *file)
++{
++ lockdep_off();
++ mutex_unlock(&au_fi(file)->fi_mmap);
++ lockdep_on();
++}
+
-+ 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);
++struct au_fidir *au_fidir_alloc(struct super_block *sb)
++{
++ struct au_fidir *fidir;
++ int nbr;
++
++ nbr = au_sbend(sb) + 1;
++ if (nbr < 2)
++ nbr = 2; /* initial allocate for 2 branches */
++ fidir = kzalloc(au_fidir_sz(nbr), GFP_NOFS);
++ if (fidir) {
++ fidir->fd_bbot = -1;
++ fidir->fd_nent = nbr;
++ fidir->fd_vdir_cache = NULL;
++ }
++
++ return fidir;
++}
++
++int au_fidir_realloc(struct au_finfo *finfo, int nbr)
++{
++ int err;
++ struct au_fidir *fidir, *p;
++
++ AuRwMustWriteLock(&finfo->fi_rwsem);
++ fidir = finfo->fi_hdir;
++ AuDebugOn(!fidir);
++
++ err = -ENOMEM;
++ p = au_kzrealloc(fidir, au_fidir_sz(fidir->fd_nent), au_fidir_sz(nbr),
++ GFP_NOFS);
++ if (p) {
++ p->fd_nent = nbr;
++ finfo->fi_hdir = p;
++ err = 0;
++ }
+
-+ out:
-+ si_read_unlock(sb);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+/* no one supports this operation, currently */
-+#if 0
-+static ssize_t aufs_sendpage(struct file *file, struct page *page, int offset,
-+ size_t len, loff_t *pos , int more)
++void au_finfo_fin(struct file *file)
+{
++ struct au_finfo *finfo;
++
++ finfo = au_fi(file);
++ AuDebugOn(finfo->fi_hdir);
++ AuRwDestroy(&finfo->fi_rwsem);
++ au_cache_free_finfo(finfo);
+}
-+#endif
+
-+/* ---------------------------------------------------------------------- */
++void au_fi_init_once(void *_fi)
++{
++ struct au_finfo *fi = _fi;
+
-+const struct file_operations aufs_file_fop = {
-+ /*
-+ * while generic_file_llseek/_unlocked() don't use BKL,
-+ * don't use it since it operates file->f_mapping->host.
-+ * in aufs, it may be a real file and may confuse users by UDBA.
-+ */
-+ /* .llseek = generic_file_llseek, */
++ au_rw_init(&fi->fi_rwsem);
++ mutex_init(&fi->fi_vm_mtx);
++ mutex_init(&fi->fi_mmap);
++}
+
-+ .read = aufs_read,
-+ .write = aufs_write,
-+ .aio_read = aufs_aio_read,
-+ .aio_write = aufs_aio_write,
-+#ifdef CONFIG_AUFS_POLL
-+ .poll = aufs_poll,
-+#endif
-+ .mmap = aufs_mmap,
-+ .open = aufs_open_nondir,
-+ .flush = aufs_flush,
-+ .release = aufs_release_nondir,
-+ .fsync = aufs_fsync_nondir,
-+ /* .aio_fsync = aufs_aio_fsync_nondir, */
-+ .fasync = aufs_fasync,
-+ /* .sendpage = aufs_sendpage, */
-+ .splice_write = aufs_splice_write,
-+ .splice_read = aufs_splice_read,
-+#if 0
-+ .aio_splice_write = aufs_aio_splice_write,
-+ .aio_splice_read = aufs_aio_splice_read
-+#endif
-+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/file.c aufs2-2.6.git/fs/aufs/file.c
---- linux-2.6.31/fs/aufs/file.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/file.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,568 @@
++int au_finfo_init(struct file *file, struct au_fidir *fidir)
++{
++ int err;
++ struct au_finfo *finfo;
++ struct dentry *dentry;
++
++ err = -ENOMEM;
++ dentry = file->f_dentry;
++ finfo = au_cache_alloc_finfo();
++ if (unlikely(!finfo))
++ goto out;
++
++ err = 0;
++ au_rw_write_lock(&finfo->fi_rwsem);
++ finfo->fi_btop = -1;
++ finfo->fi_hdir = fidir;
++ atomic_set(&finfo->fi_generation, au_digen(dentry));
++ /* smp_mb(); */ /* atomic_set */
++
++ file->private_data = finfo;
++
++out:
++ return err;
++}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/f_op.c linux-2.6.34/fs/aufs/f_op.c
+--- linux-2.6.34.org/fs/aufs/f_op.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/f_op.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,884 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * handling file/dir, and address_space operation
++ * file and vm operations
+ */
+
+#include <linux/file.h>
-+#include <linux/fsnotify.h>
-+#include <linux/namei.h>
-+#include <linux/pagemap.h>
++#include <linux/fs_stack.h>
++#include <linux/mman.h>
++#include <linux/mm.h>
++#include <linux/security.h>
+#include "aufs.h"
+
-+/* drop flags for writing */
-+unsigned int au_file_roflags(unsigned int flags)
++int au_do_open_nondir(struct file *file, int flags)
+{
-+ flags &= ~(O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC);
-+ flags |= O_RDONLY | O_NOATIME;
-+ return flags;
++ int err;
++ aufs_bindex_t bindex;
++ struct file *h_file;
++ struct dentry *dentry;
++ struct au_finfo *finfo;
++
++ FiMustWriteLock(file);
++
++ err = 0;
++ dentry = file->f_dentry;
++ finfo = au_fi(file);
++ memset(&finfo->fi_htop, 0, sizeof(finfo->fi_htop));
++ finfo->fi_hvmop = NULL;
++ bindex = au_dbstart(dentry);
++ h_file = au_h_open(dentry, bindex, flags, file);
++ if (IS_ERR(h_file))
++ err = PTR_ERR(h_file);
++ else {
++ au_set_fbstart(file, bindex);
++ au_set_h_fptr(file, bindex, h_file);
++ au_update_figen(file);
++ /* todo: necessary? */
++ /* file->f_ra = h_file->f_ra; */
++ }
++ return err;
+}
+
-+/* common functions to regular file and dir */
-+struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
-+ struct file *file)
++static int aufs_open_nondir(struct inode *inode __maybe_unused,
++ struct file *file)
+{
-+ struct file *h_file;
-+ struct dentry *h_dentry;
-+ struct inode *h_inode;
++ int err;
+ struct super_block *sb;
-+ struct au_branch *br;
-+ int err, exec_flag;
-+ struct path h_path;
+
-+ /* a race condition can happen between open and unlink/rmdir */
-+ h_file = ERR_PTR(-ENOENT);
-+ h_dentry = au_h_dptr(dentry, bindex);
-+ if (au_test_nfsd(current) && !h_dentry)
-+ goto out;
-+ h_inode = h_dentry->d_inode;
-+ if (au_test_nfsd(current) && !h_inode)
-+ goto out;
-+ if (unlikely((!d_unhashed(dentry) && d_unhashed(h_dentry))
-+ || !h_inode))
-+ goto out;
++ AuDbg("%.*s, f_ flags 0x%x, f_mode 0x%x\n",
++ AuDLNPair(file->f_dentry), vfsub_file_flags(file),
++ file->f_mode);
+
-+ sb = dentry->d_sb;
-+ br = au_sbr(sb, bindex);
-+ h_file = ERR_PTR(-EACCES);
-+ exec_flag = flags & vfsub_fmode_to_uint(FMODE_EXEC);
-+ if (exec_flag && (br->br_mnt->mnt_flags & MNT_NOEXEC))
-+ goto out;
++ sb = file->f_dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ err = au_do_open(file, au_do_open_nondir, /*fidir*/NULL);
++ si_read_unlock(sb);
++ return err;
++}
+
-+ /* drop flags for writing */
-+ if (au_test_ro(sb, bindex, dentry->d_inode))
-+ flags = au_file_roflags(flags);
-+ flags &= ~O_CREAT;
-+ atomic_inc(&br->br_count);
-+ h_path.dentry = h_dentry;
-+ h_path.mnt = br->br_mnt;
-+ path_get(&h_path);
-+ h_file = vfsub_dentry_open(&h_path, flags, current_cred());
-+ if (IS_ERR(h_file))
-+ goto out_br;
++int aufs_release_nondir(struct inode *inode __maybe_unused, struct file *file)
++{
++ struct au_finfo *finfo;
++ aufs_bindex_t bindex;
+
-+ if (exec_flag) {
-+ err = deny_write_access(h_file);
-+ if (unlikely(err)) {
-+ fput(h_file);
-+ h_file = ERR_PTR(err);
-+ goto out_br;
-+ }
-+ }
-+ fsnotify_open(h_dentry);
-+ goto out; /* success */
++ finfo = au_fi(file);
++ bindex = finfo->fi_btop;
++ if (bindex >= 0)
++ au_set_h_fptr(file, bindex, NULL);
+
-+ out_br:
-+ atomic_dec(&br->br_count);
-+ out:
-+ return h_file;
++ au_finfo_fin(file);
++ return 0;
+}
+
-+int au_do_open(struct file *file, int (*open)(struct file *file, int flags))
++/* ---------------------------------------------------------------------- */
++
++static int au_do_flush_nondir(struct file *file, fl_owner_t id)
+{
+ int err;
-+ unsigned int flags;
++ struct file *h_file;
++
++ err = 0;
++ h_file = au_hf_top(file);
++ if (h_file)
++ err = vfsub_flush(h_file, id);
++ return err;
++}
++
++static int aufs_flush_nondir(struct file *file, fl_owner_t id)
++{
++ return au_do_flush(file, id, au_do_flush_nondir);
++}
++
++/* ---------------------------------------------------------------------- */
++
++static ssize_t aufs_read(struct file *file, char __user *buf, size_t count,
++ loff_t *ppos)
++{
++ ssize_t err;
+ struct dentry *dentry;
++ struct file *h_file;
+ struct super_block *sb;
+
+ dentry = file->f_dentry;
+ sb = dentry->d_sb;
+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_finfo_init(file);
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
+ if (unlikely(err))
+ goto out;
+
-+ di_read_lock_child(dentry, AuLock_IR);
-+ spin_lock(&file->f_lock);
-+ flags = file->f_flags;
-+ spin_unlock(&file->f_lock);
-+ err = open(file, flags);
-+ di_read_unlock(dentry, AuLock_IR);
++ h_file = au_hf_top(file);
++ err = vfsub_read_u(h_file, buf, count, ppos);
++ /* todo: necessary? */
++ /* file->f_ra = h_file->f_ra; */
++ fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
+
-+ fi_write_unlock(file);
-+ if (unlikely(err))
-+ au_finfo_fin(file);
++ di_read_unlock(dentry, AuLock_IR);
++ fi_read_unlock(file);
+ out:
+ si_read_unlock(sb);
+ return err;
+}
+
-+int au_reopen_nondir(struct file *file)
++static ssize_t aufs_write(struct file *file, const char __user *ubuf,
++ size_t count, loff_t *ppos)
+{
-+ int err;
-+ unsigned int flags;
-+ aufs_bindex_t bstart, bindex, bend;
++ ssize_t err;
++ struct au_pin pin;
+ struct dentry *dentry;
-+ struct file *h_file, *h_file_tmp;
++ struct inode *inode;
++ struct super_block *sb;
++ struct file *h_file;
++ char __user *buf = (char __user *)ubuf;
+
+ dentry = file->f_dentry;
-+ bstart = au_dbstart(dentry);
-+ h_file_tmp = NULL;
-+ if (au_fbstart(file) == bstart) {
-+ h_file = au_h_fptr(file, bstart);
-+ if (file->f_mode == h_file->f_mode)
-+ return 0; /* success */
-+ h_file_tmp = h_file;
-+ get_file(h_file_tmp);
-+ au_set_h_fptr(file, bstart, NULL);
-+ }
-+ AuDebugOn(au_fbstart(file) < bstart
-+ || au_fi(file)->fi_hfile[0 + bstart].hf_file);
++ sb = dentry->d_sb;
++ inode = dentry->d_inode;
++ mutex_lock(&inode->i_mutex);
++ si_read_lock(sb, AuLock_FLUSH);
+
-+ spin_lock(&file->f_lock);
-+ flags = file->f_flags & ~O_TRUNC;
-+ spin_unlock(&file->f_lock);
-+ h_file = au_h_open(dentry, bstart, flags, file);
-+ err = PTR_ERR(h_file);
-+ if (IS_ERR(h_file))
-+ goto out; /* todo: close all? */
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
++ goto out;
+
-+ err = 0;
-+ au_set_fbstart(file, bstart);
-+ au_set_h_fptr(file, bstart, h_file);
-+ au_update_figen(file);
-+ /* todo: necessary? */
-+ /* file->f_ra = h_file->f_ra; */
++ err = au_ready_to_write(file, -1, &pin);
++ di_downgrade_lock(dentry, AuLock_IR);
++ if (unlikely(err))
++ goto out_unlock;
+
-+ /* close lower files */
-+ bend = au_fbend(file);
-+ for (bindex = bstart + 1; bindex <= bend; bindex++)
-+ au_set_h_fptr(file, bindex, NULL);
-+ au_set_fbend(file, bstart);
++ h_file = au_hf_top(file);
++ au_unpin(&pin);
++ err = vfsub_write_u(h_file, buf, count, ppos);
++ au_cpup_attr_timesizes(inode);
++ inode->i_mode = h_file->f_dentry->d_inode->i_mode;
+
++ out_unlock:
++ di_read_unlock(dentry, AuLock_IR);
++ fi_write_unlock(file);
+ out:
-+ if (h_file_tmp)
-+ fput(h_file_tmp);
++ si_read_unlock(sb);
++ mutex_unlock(&inode->i_mutex);
+ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+static int au_reopen_wh(struct file *file, aufs_bindex_t btgt,
-+ struct dentry *hi_wh)
++static ssize_t au_do_aio(struct file *h_file, int rw, struct kiocb *kio,
++ const struct iovec *iov, unsigned long nv, loff_t pos)
+{
-+ int err;
-+ aufs_bindex_t bstart;
-+ struct au_dinfo *dinfo;
-+ struct dentry *h_dentry;
++ ssize_t err;
++ struct file *file;
++ ssize_t (*func)(struct kiocb *, const struct iovec *, unsigned long,
++ loff_t);
+
-+ dinfo = au_di(file->f_dentry);
-+ AuRwMustWriteLock(&dinfo->di_rwsem);
++ err = security_file_permission(h_file, rw);
++ if (unlikely(err))
++ goto out;
+
-+ bstart = dinfo->di_bstart;
-+ dinfo->di_bstart = btgt;
-+ h_dentry = dinfo->di_hdentry[0 + btgt].hd_dentry;
-+ dinfo->di_hdentry[0 + btgt].hd_dentry = hi_wh;
-+ err = au_reopen_nondir(file);
-+ dinfo->di_hdentry[0 + btgt].hd_dentry = h_dentry;
-+ dinfo->di_bstart = bstart;
++ err = -ENOSYS;
++ func = NULL;
++ if (rw == MAY_READ)
++ func = h_file->f_op->aio_read;
++ else if (rw == MAY_WRITE)
++ func = h_file->f_op->aio_write;
++ if (func) {
++ file = kio->ki_filp;
++ kio->ki_filp = h_file;
++ err = func(kio, iov, nv, pos);
++ kio->ki_filp = file;
++ } else
++ /* currently there is no such fs */
++ WARN_ON_ONCE(1);
+
++ out:
+ return err;
+}
+
-+static int au_ready_to_write_wh(struct file *file, loff_t len,
-+ aufs_bindex_t bcpup)
++static ssize_t aufs_aio_read(struct kiocb *kio, const struct iovec *iov,
++ unsigned long nv, loff_t pos)
+{
-+ int err;
-+ struct inode *inode;
-+ struct dentry *dentry, *hi_wh;
++ ssize_t err;
++ struct file *file, *h_file;
++ struct dentry *dentry;
+ struct super_block *sb;
+
++ file = kio->ki_filp;
+ dentry = file->f_dentry;
-+ inode = dentry->d_inode;
-+ hi_wh = au_hi_wh(inode, bcpup);
-+ if (!hi_wh)
-+ err = au_sio_cpup_wh(dentry, bcpup, len, file);
-+ else
-+ /* already copied-up after unlink */
-+ err = au_reopen_wh(file, bcpup, hi_wh);
-+
+ sb = dentry->d_sb;
-+ if (!err && inode->i_nlink > 1 && au_opt_test(au_mntflags(sb), PLINK))
-+ au_plink_append(inode, bcpup, au_h_dptr(dentry, bcpup));
++ si_read_lock(sb, AuLock_FLUSH);
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
++ if (unlikely(err))
++ goto out;
++
++ h_file = au_hf_top(file);
++ err = au_do_aio(h_file, MAY_READ, kio, iov, nv, pos);
++ /* todo: necessary? */
++ /* 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);
+ return err;
+}
+
-+/*
-+ * prepare the @file for writing.
-+ */
-+int au_ready_to_write(struct file *file, loff_t len, struct au_pin *pin)
++static ssize_t aufs_aio_write(struct kiocb *kio, const struct iovec *iov,
++ unsigned long nv, loff_t pos)
+{
-+ int err;
-+ aufs_bindex_t bstart, bcpup;
-+ struct dentry *dentry, *parent, *h_dentry;
-+ struct inode *h_inode, *inode;
++ ssize_t err;
++ struct au_pin pin;
++ struct dentry *dentry;
++ struct inode *inode;
+ struct super_block *sb;
++ struct file *file, *h_file;
+
++ file = kio->ki_filp;
+ dentry = file->f_dentry;
+ sb = dentry->d_sb;
-+ bstart = au_fbstart(file);
+ inode = dentry->d_inode;
-+ err = au_test_ro(sb, bstart, inode);
-+ if (!err && (au_h_fptr(file, bstart)->f_mode & FMODE_WRITE)) {
-+ err = au_pin(pin, dentry, bstart, AuOpt_UDBA_NONE, /*flags*/0);
++ mutex_lock(&inode->i_mutex);
++ si_read_lock(sb, AuLock_FLUSH);
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
+ goto out;
-+ }
-+
-+ /* need to cpup */
-+ parent = dget_parent(dentry);
-+ di_write_lock_parent(parent);
-+ err = AuWbrCopyup(au_sbi(sb), dentry);
-+ bcpup = err;
-+ if (unlikely(err < 0))
-+ goto out_dgrade;
-+ err = 0;
-+
-+ if (!au_h_dptr(parent, bcpup)) {
-+ err = au_cpup_dirs(dentry, bcpup);
-+ if (unlikely(err))
-+ goto out_dgrade;
-+ }
+
-+ err = au_pin(pin, dentry, bcpup, AuOpt_UDBA_NONE,
-+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ err = au_ready_to_write(file, -1, &pin);
++ di_downgrade_lock(dentry, AuLock_IR);
+ if (unlikely(err))
-+ goto out_dgrade;
-+
-+ h_dentry = au_h_fptr(file, bstart)->f_dentry;
-+ h_inode = h_dentry->d_inode;
-+ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
-+ if (d_unhashed(dentry) /* || d_unhashed(h_dentry) */
-+ /* || !h_inode->i_nlink */) {
-+ err = au_ready_to_write_wh(file, len, bcpup);
-+ 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);
-+ if (!err)
-+ err = au_reopen_nondir(file);
-+ }
-+ mutex_unlock(&h_inode->i_mutex);
++ goto out_unlock;
+
-+ if (!err) {
-+ au_pin_set_parent_lflag(pin, /*lflag*/0);
-+ goto out_dput; /* success */
-+ }
-+ au_unpin(pin);
-+ goto out_unlock;
++ au_unpin(&pin);
++ h_file = au_hf_top(file);
++ err = au_do_aio(h_file, MAY_WRITE, kio, iov, nv, pos);
++ au_cpup_attr_timesizes(inode);
++ inode->i_mode = h_file->f_dentry->d_inode->i_mode;
+
-+ out_dgrade:
-+ di_downgrade_lock(parent, AuLock_IR);
+ out_unlock:
-+ di_read_unlock(parent, AuLock_IR);
-+ out_dput:
-+ dput(parent);
++ di_read_unlock(dentry, AuLock_IR);
++ fi_write_unlock(file);
+ out:
++ si_read_unlock(sb);
++ mutex_unlock(&inode->i_mutex);
+ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+static int au_file_refresh_by_inode(struct file *file, int *need_reopen)
++static ssize_t aufs_splice_read(struct file *file, loff_t *ppos,
++ struct pipe_inode_info *pipe, size_t len,
++ unsigned int flags)
+{
-+ int err;
-+ aufs_bindex_t bstart;
-+ struct au_pin pin;
-+ struct au_finfo *finfo;
-+ struct dentry *dentry, *parent, *hi_wh;
-+ struct inode *inode;
++ ssize_t err;
++ struct file *h_file;
++ struct dentry *dentry;
+ struct super_block *sb;
+
-+ FiMustWriteLock(file);
-+
-+ err = 0;
-+ finfo = au_fi(file);
+ dentry = file->f_dentry;
+ sb = dentry->d_sb;
-+ inode = dentry->d_inode;
-+ bstart = au_ibstart(inode);
-+ if (bstart == finfo->fi_bstart)
++ si_read_lock(sb, AuLock_FLUSH);
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
++ if (unlikely(err))
+ goto out;
+
-+ parent = dget_parent(dentry);
-+ if (au_test_ro(sb, bstart, inode)) {
-+ di_read_lock_parent(parent, !AuLock_IR);
-+ err = AuWbrCopyup(au_sbi(sb), dentry);
-+ bstart = err;
-+ di_read_unlock(parent, !AuLock_IR);
-+ if (unlikely(err < 0))
-+ goto out_parent;
-+ err = 0;
++ err = -EINVAL;
++ h_file = au_hf_top(file);
++ if (au_test_loopback_kthread()) {
++ file->f_mapping = h_file->f_mapping;
++ smp_mb(); /* unnecessary? */
+ }
++ err = vfsub_splice_to(h_file, ppos, pipe, len, flags);
++ /* todo: necessasry? */
++ /* file->f_ra = h_file->f_ra; */
++ fsstack_copy_attr_atime(dentry->d_inode, h_file->f_dentry->d_inode);
+
-+ di_read_lock_parent(parent, AuLock_IR);
-+ hi_wh = au_hi_wh(inode, bstart);
-+ if (au_opt_test(au_mntflags(sb), PLINK)
-+ && au_plink_test(inode)
-+ && !d_unhashed(dentry)) {
-+ err = au_test_and_cpup_dirs(dentry, bstart);
-+ if (unlikely(err))
-+ goto out_unlock;
-+
-+ /* always superio. */
-+ err = au_pin(&pin, dentry, bstart, AuOpt_UDBA_NONE,
-+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
-+ if (!err)
-+ err = au_sio_cpup_simple(dentry, bstart, -1,
-+ AuCpup_DTIME);
-+ au_unpin(&pin);
-+ } else if (hi_wh) {
-+ /* already copied-up after unlink */
-+ err = au_reopen_wh(file, bstart, hi_wh);
-+ *need_reopen = 0;
-+ }
++ di_read_unlock(dentry, AuLock_IR);
++ fi_read_unlock(file);
+
-+ out_unlock:
-+ di_read_unlock(parent, AuLock_IR);
-+ out_parent:
-+ dput(parent);
+ out:
++ si_read_unlock(sb);
+ return err;
+}
+
-+static void au_do_refresh_file(struct file *file)
++static ssize_t
++aufs_splice_write(struct pipe_inode_info *pipe, struct file *file, loff_t *ppos,
++ size_t len, unsigned int flags)
+{
-+ aufs_bindex_t bindex, bend, new_bindex, brid;
-+ struct au_hfile *p, tmp, *q;
-+ struct au_finfo *finfo;
++ ssize_t err;
++ struct au_pin pin;
++ struct dentry *dentry;
++ struct inode *inode;
+ struct super_block *sb;
++ struct file *h_file;
+
-+ FiMustWriteLock(file);
++ dentry = file->f_dentry;
++ inode = dentry->d_inode;
++ mutex_lock(&inode->i_mutex);
++ sb = dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
+
-+ sb = file->f_dentry->d_sb;
-+ finfo = au_fi(file);
-+ p = finfo->fi_hfile + finfo->fi_bstart;
-+ brid = p->hf_br->br_id;
-+ bend = finfo->fi_bend;
-+ for (bindex = finfo->fi_bstart; bindex <= bend; bindex++, p++) {
-+ if (!p->hf_file)
-+ continue;
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
++ goto out;
+
-+ new_bindex = au_br_index(sb, p->hf_br->br_id);
-+ if (new_bindex == bindex)
-+ continue;
-+ if (new_bindex < 0) {
-+ au_set_h_fptr(file, bindex, NULL);
-+ continue;
-+ }
++ err = au_ready_to_write(file, -1, &pin);
++ di_downgrade_lock(dentry, AuLock_IR);
++ if (unlikely(err))
++ goto out_unlock;
+
-+ /* swap two lower inode, and loop again */
-+ q = finfo->fi_hfile + new_bindex;
-+ tmp = *q;
-+ *q = *p;
-+ *p = tmp;
-+ if (tmp.hf_file) {
-+ bindex--;
-+ p--;
-+ }
-+ }
++ h_file = au_hf_top(file);
++ au_unpin(&pin);
++ err = vfsub_splice_from(pipe, h_file, ppos, len, flags);
++ au_cpup_attr_timesizes(inode);
++ inode->i_mode = h_file->f_dentry->d_inode->i_mode;
+
-+ p = finfo->fi_hfile;
-+ if (!au_test_mmapped(file) && !d_unhashed(file->f_dentry)) {
-+ bend = au_sbend(sb);
-+ for (finfo->fi_bstart = 0; finfo->fi_bstart <= bend;
-+ finfo->fi_bstart++, p++)
-+ if (p->hf_file) {
-+ if (p->hf_file->f_dentry
-+ && p->hf_file->f_dentry->d_inode)
-+ break;
-+ else
-+ au_hfput(p, file);
-+ }
-+ } else {
-+ bend = au_br_index(sb, brid);
-+ for (finfo->fi_bstart = 0; finfo->fi_bstart < bend;
-+ finfo->fi_bstart++, p++)
-+ if (p->hf_file)
-+ au_hfput(p, file);
-+ bend = au_sbend(sb);
-+ }
++ out_unlock:
++ di_read_unlock(dentry, AuLock_IR);
++ fi_write_unlock(file);
++ out:
++ si_read_unlock(sb);
++ mutex_unlock(&inode->i_mutex);
++ return err;
++}
+
-+ p = finfo->fi_hfile + bend;
-+ for (finfo->fi_bend = bend; finfo->fi_bend >= finfo->fi_bstart;
-+ finfo->fi_bend--, p--)
-+ if (p->hf_file) {
-+ if (p->hf_file->f_dentry
-+ && p->hf_file->f_dentry->d_inode)
-+ break;
-+ else
-+ au_hfput(p, file);
-+ }
-+ AuDebugOn(finfo->fi_bend < finfo->fi_bstart);
++/* ---------------------------------------------------------------------- */
++
++static struct file *au_safe_file(struct vm_area_struct *vma)
++{
++ struct file *file;
++
++ file = vma->vm_file;
++ if (file->private_data && au_test_aufs(file->f_dentry->d_sb))
++ return file;
++ return NULL;
+}
+
-+/*
-+ * after branch manipulating, refresh the file.
-+ */
-+static int refresh_file(struct file *file, int (*reopen)(struct file *file))
++static void au_reset_file(struct vm_area_struct *vma, struct file *file)
+{
-+ int err, need_reopen;
-+ struct dentry *dentry;
-+ aufs_bindex_t bend, bindex;
++ vma->vm_file = file;
++ /* smp_mb(); */ /* flush vm_file */
++}
+
-+ dentry = file->f_dentry;
-+ err = au_fi_realloc(au_fi(file), au_sbend(dentry->d_sb) + 1);
-+ if (unlikely(err))
-+ goto out;
-+ au_do_refresh_file(file);
++static int aufs_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
++{
++ int err;
++ static DECLARE_WAIT_QUEUE_HEAD(wq);
++ struct file *file, *h_file;
++ struct au_finfo *finfo;
+
-+ err = 0;
-+ need_reopen = 1;
-+ if (!au_test_mmapped(file))
-+ err = au_file_refresh_by_inode(file, &need_reopen);
-+ if (!err && need_reopen && !d_unhashed(dentry))
-+ err = reopen(file);
-+ if (!err) {
-+ au_update_figen(file);
-+ return 0; /* success */
-+ }
++ /* todo: non-robr mode, user vm_file as it is? */
++ wait_event(wq, (file = au_safe_file(vma)));
+
-+ /* error, close all lower files */
-+ bend = au_fbend(file);
-+ for (bindex = au_fbstart(file); bindex <= bend; bindex++)
-+ au_set_h_fptr(file, bindex, NULL);
++ /* do not revalidate, no si lock */
++ finfo = au_fi(file);
++ AuDebugOn(finfo->fi_hdir);
++ h_file = finfo->fi_htop.hf_file;
++ AuDebugOn(!h_file || !finfo->fi_hvmop);
++
++ mutex_lock(&finfo->fi_vm_mtx);
++ vma->vm_file = h_file;
++ err = finfo->fi_hvmop->fault(vma, vmf);
++ /* todo: necessary? */
++ /* file->f_ra = h_file->f_ra; */
++ au_reset_file(vma, file);
++ mutex_unlock(&finfo->fi_vm_mtx);
++#if 0 /* def CONFIG_SMP */
++ /* wake_up_nr(&wq, online_cpu - 1); */
++ wake_up_all(&wq);
++#else
++ wake_up(&wq);
++#endif
+
-+ out:
+ return err;
+}
+
-+/* common function to regular file and dir */
-+int au_reval_and_lock_fdi(struct file *file, int (*reopen)(struct file *file),
-+ int wlock)
++static int aufs_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ int err;
-+ unsigned int sigen, figen;
-+ aufs_bindex_t bstart;
-+ unsigned char pseudo_link;
-+ struct dentry *dentry;
++ static DECLARE_WAIT_QUEUE_HEAD(wq);
++ struct file *file, *h_file;
++ struct au_finfo *finfo;
+
-+ err = 0;
-+ dentry = file->f_dentry;
-+ sigen = au_sigen(dentry->d_sb);
-+ fi_write_lock(file);
-+ figen = au_figen(file);
-+ di_write_lock_child(dentry);
-+ bstart = au_dbstart(dentry);
-+ pseudo_link = (bstart != au_ibstart(dentry->d_inode));
-+ if (sigen == figen && !pseudo_link && au_fbstart(file) == bstart) {
-+ if (!wlock) {
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ fi_downgrade_lock(file);
-+ }
-+ goto out; /* success */
-+ }
++ wait_event(wq, (file = au_safe_file(vma)));
+
-+ AuDbg("sigen %d, figen %d\n", sigen, figen);
-+ if (sigen != au_digen(dentry)
-+ || sigen != au_iigen(dentry->d_inode)) {
-+ err = au_reval_dpath(dentry, sigen);
-+ if (unlikely(err < 0))
-+ goto out;
-+ AuDebugOn(au_digen(dentry) != sigen
-+ || au_iigen(dentry->d_inode) != sigen);
-+ }
++ finfo = au_fi(file);
++ AuDebugOn(finfo->fi_hdir);
++ h_file = finfo->fi_htop.hf_file;
++ AuDebugOn(!h_file || !finfo->fi_hvmop);
+
-+ err = refresh_file(file, reopen);
-+ if (!err) {
-+ if (!wlock) {
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ fi_downgrade_lock(file);
-+ }
-+ } else {
-+ di_write_unlock(dentry);
-+ fi_write_unlock(file);
-+ }
++ mutex_lock(&finfo->fi_vm_mtx);
++ vma->vm_file = h_file;
++ err = finfo->fi_hvmop->page_mkwrite(vma, vmf);
++ au_reset_file(vma, file);
++ mutex_unlock(&finfo->fi_vm_mtx);
++ wake_up(&wq);
+
-+ out:
+ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+/* cf. aufs_nopage() */
-+/* for madvise(2) */
-+static int aufs_readpage(struct file *file __maybe_unused, struct page *page)
++static void aufs_vm_close(struct vm_area_struct *vma)
+{
-+ unlock_page(page);
-+ return 0;
-+}
++ static DECLARE_WAIT_QUEUE_HEAD(wq);
++ struct file *file, *h_file;
++ struct au_finfo *finfo;
+
-+/* they will never be called. */
-+#ifdef CONFIG_AUFS_DEBUG
-+static int aufs_write_begin(struct file *file, struct address_space *mapping,
-+ loff_t pos, unsigned len, unsigned flags,
-+ struct page **pagep, void **fsdata)
-+{ AuUnsupport(); return 0; }
-+static int aufs_write_end(struct file *file, struct address_space *mapping,
-+ loff_t pos, unsigned len, unsigned copied,
-+ struct page *page, void *fsdata)
-+{ AuUnsupport(); return 0; }
-+static int aufs_writepage(struct page *page, struct writeback_control *wbc)
-+{ AuUnsupport(); return 0; }
-+static void aufs_sync_page(struct page *page)
-+{ AuUnsupport(); }
-+
-+static int aufs_set_page_dirty(struct page *page)
-+{ AuUnsupport(); return 0; }
-+static void aufs_invalidatepage(struct page *page, unsigned long offset)
-+{ AuUnsupport(); }
-+static int aufs_releasepage(struct page *page, gfp_t gfp)
-+{ AuUnsupport(); return 0; }
-+static ssize_t aufs_direct_IO(int rw, struct kiocb *iocb,
-+ const struct iovec *iov, loff_t offset,
-+ unsigned long nr_segs)
-+{ AuUnsupport(); return 0; }
-+#endif /* CONFIG_AUFS_DEBUG */
-+
-+struct address_space_operations aufs_aop = {
-+ .readpage = aufs_readpage,
-+#ifdef CONFIG_AUFS_DEBUG
-+ .writepage = aufs_writepage,
-+ .sync_page = aufs_sync_page,
-+ .set_page_dirty = aufs_set_page_dirty,
-+ .write_begin = aufs_write_begin,
-+ .write_end = aufs_write_end,
-+ .invalidatepage = aufs_invalidatepage,
-+ .releasepage = aufs_releasepage,
-+ .direct_IO = aufs_direct_IO,
-+#endif /* CONFIG_AUFS_DEBUG */
-+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/file.h aufs2-2.6.git/fs/aufs/file.h
---- linux-2.6.31/fs/aufs/file.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/file.h 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,175 @@
-+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
-+ *
-+ * This program, aufs is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation; either version 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-+ */
-+
-+/*
-+ * file operations
-+ */
-+
-+#ifndef __AUFS_FILE_H__
-+#define __AUFS_FILE_H__
-+
-+#ifdef __KERNEL__
-+
-+#include <linux/fs.h>
-+#include <linux/poll.h>
-+#include <linux/aufs_type.h>
-+#include "rwsem.h"
-+
-+struct au_branch;
-+struct au_hfile {
-+ struct file *hf_file;
-+ struct au_branch *hf_br;
-+};
-+
-+struct au_vdir;
-+struct au_finfo {
-+ atomic_t fi_generation;
++ wait_event(wq, (file = au_safe_file(vma)));
+
-+ struct au_rwsem fi_rwsem;
-+ struct au_hfile *fi_hfile;
-+ aufs_bindex_t fi_bstart, fi_bend;
++ finfo = au_fi(file);
++ AuDebugOn(finfo->fi_hdir);
++ h_file = finfo->fi_htop.hf_file;
++ AuDebugOn(!h_file || !finfo->fi_hvmop);
+
-+ union {
-+ /* non-dir only */
-+ struct {
-+ struct vm_operations_struct *fi_h_vm_ops;
-+ struct vm_operations_struct *fi_vm_ops;
-+ struct mutex fi_vm_mtx;
-+ };
++ mutex_lock(&finfo->fi_vm_mtx);
++ vma->vm_file = h_file;
++ finfo->fi_hvmop->close(vma);
++ au_reset_file(vma, file);
++ mutex_unlock(&finfo->fi_vm_mtx);
++ wake_up(&wq);
++}
+
-+ /* dir only */
-+ struct {
-+ struct au_vdir *fi_vdir_cache;
-+ int fi_maintain_plink;
-+ };
-+ };
++const struct vm_operations_struct aufs_vm_ops = {
++ .close = aufs_vm_close,
++ .fault = aufs_fault,
++ .page_mkwrite = aufs_page_mkwrite
+};
+
+/* ---------------------------------------------------------------------- */
+
-+/* file.c */
-+extern struct address_space_operations aufs_aop;
-+unsigned int au_file_roflags(unsigned int flags);
-+struct file *au_h_open(struct dentry *dentry, aufs_bindex_t bindex, int flags,
-+ struct file *file);
-+int au_do_open(struct file *file, int (*open)(struct file *file, int flags));
-+int au_reopen_nondir(struct file *file);
-+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);
-+
-+/* poll.c */
-+#ifdef CONFIG_AUFS_POLL
-+unsigned int aufs_poll(struct file *file, poll_table *wait);
-+#endif
-+
-+/* f_op.c */
-+extern const struct file_operations aufs_file_fop;
-+int aufs_flush(struct file *file, fl_owner_t id);
-+
-+/* finfo.c */
-+void au_hfput(struct au_hfile *hf, struct file *file);
-+void au_set_h_fptr(struct file *file, aufs_bindex_t bindex,
-+ struct file *h_file);
-+
-+void au_update_figen(struct file *file);
-+
-+void au_finfo_fin(struct file *file);
-+int au_finfo_init(struct file *file);
-+int au_fi_realloc(struct au_finfo *finfo, int nbr);
-+
-+/* ---------------------------------------------------------------------- */
++/* cf. linux/include/linux/mman.h: calc_vm_prot_bits() */
++#define AuConv_VM_PROT(f, b) _calc_vm_trans(f, VM_##b, PROT_##b)
+
-+static inline struct au_finfo *au_fi(struct file *file)
++static unsigned long au_arch_prot_conv(unsigned long flags)
+{
-+ return file->private_data;
++ /* currently ppc64 only */
++#ifdef CONFIG_PPC64
++ /* cf. linux/arch/powerpc/include/asm/mman.h */
++ AuDebugOn(arch_calc_vm_prot_bits(-1) != VM_SAO);
++ return AuConv_VM_PROT(flags, SAO);
++#else
++ AuDebugOn(arch_calc_vm_prot_bits(-1));
++ return 0;
++#endif
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+/*
-+ * fi_read_lock, fi_write_lock,
-+ * fi_read_unlock, fi_write_unlock, fi_downgrade_lock
-+ */
-+AuSimpleRwsemFuncs(fi, struct file *f, &au_fi(f)->fi_rwsem);
-+
-+#define FiMustNoWaiters(f) AuRwMustNoWaiters(&au_fi(f)->fi_rwsem)
-+#define FiMustAnyLock(f) AuRwMustAnyLock(&au_fi(f)->fi_rwsem)
-+#define FiMustWriteLock(f) AuRwMustWriteLock(&au_fi(f)->fi_rwsem)
-+
-+/* ---------------------------------------------------------------------- */
-+
-+/* todo: hard/soft set? */
-+static inline aufs_bindex_t au_fbstart(struct file *file)
++static unsigned long au_prot_conv(unsigned long flags)
+{
-+ FiMustAnyLock(file);
-+ return au_fi(file)->fi_bstart;
++ return AuConv_VM_PROT(flags, READ)
++ | AuConv_VM_PROT(flags, WRITE)
++ | AuConv_VM_PROT(flags, EXEC)
++ | au_arch_prot_conv(flags);
+}
+
-+static inline aufs_bindex_t au_fbend(struct file *file)
-+{
-+ FiMustAnyLock(file);
-+ return au_fi(file)->fi_bend;
-+}
++/* cf. linux/include/linux/mman.h: calc_vm_flag_bits() */
++#define AuConv_VM_MAP(f, b) _calc_vm_trans(f, VM_##b, MAP_##b)
+
-+static inline struct au_vdir *au_fvdir_cache(struct file *file)
++static unsigned long au_flag_conv(unsigned long flags)
+{
-+ FiMustAnyLock(file);
-+ return au_fi(file)->fi_vdir_cache;
++ return AuConv_VM_MAP(flags, GROWSDOWN)
++ | AuConv_VM_MAP(flags, DENYWRITE)
++ | AuConv_VM_MAP(flags, EXECUTABLE)
++ | AuConv_VM_MAP(flags, LOCKED);
+}
+
-+static inline void au_set_fbstart(struct file *file, aufs_bindex_t bindex)
++static struct vm_operations_struct *
++au_hvmop(struct file *h_file, struct vm_area_struct *vma, unsigned long *flags)
+{
-+ FiMustWriteLock(file);
-+ au_fi(file)->fi_bstart = bindex;
-+}
++ struct vm_operations_struct *h_vmop;
++ unsigned long prot;
++ int err;
+
-+static inline void au_set_fbend(struct file *file, aufs_bindex_t bindex)
-+{
-+ FiMustWriteLock(file);
-+ au_fi(file)->fi_bend = bindex;
-+}
++ h_vmop = ERR_PTR(-ENODEV);
++ if (!h_file->f_op || !h_file->f_op->mmap)
++ goto out;
+
-+static inline void au_set_fvdir_cache(struct file *file,
-+ struct au_vdir *vdir_cache)
-+{
-+ FiMustWriteLock(file);
-+ au_fi(file)->fi_vdir_cache = vdir_cache;
-+}
++ prot = au_prot_conv(vma->vm_flags);
++ err = security_file_mmap(h_file, /*reqprot*/prot, prot,
++ au_flag_conv(vma->vm_flags), vma->vm_start, 0);
++ h_vmop = ERR_PTR(err);
++ if (unlikely(err))
++ goto out;
+
-+static inline struct file *au_h_fptr(struct file *file, aufs_bindex_t bindex)
-+{
-+ FiMustAnyLock(file);
-+ return au_fi(file)->fi_hfile[0 + bindex].hf_file;
-+}
++ err = h_file->f_op->mmap(h_file, vma);
++ h_vmop = ERR_PTR(err);
++ if (unlikely(err))
++ goto out;
+
-+/* todo: memory barrier? */
-+static inline unsigned int au_figen(struct file *f)
-+{
-+ return atomic_read(&au_fi(f)->fi_generation);
-+}
++ /* oops, it became 'const' */
++ h_vmop = (struct vm_operations_struct *)vma->vm_ops;
++ *flags = vma->vm_flags;
++ err = do_munmap(current->mm, vma->vm_start,
++ vma->vm_end - vma->vm_start);
++ if (unlikely(err)) {
++ AuIOErr("failed internal unmapping %.*s, %d\n",
++ AuDLNPair(h_file->f_dentry), err);
++ h_vmop = ERR_PTR(-EIO);
++ }
+
-+static inline int au_test_mmapped(struct file *f)
-+{
-+ /* FiMustAnyLock(f); */
-+ return !!(au_fi(f)->fi_h_vm_ops);
++ out:
++ return h_vmop;
+}
+
-+#endif /* __KERNEL__ */
-+#endif /* __AUFS_FILE_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/finfo.c aufs2-2.6.git/fs/aufs/finfo.c
---- linux-2.6.31/fs/aufs/finfo.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/finfo.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,128 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
-+ *
-+ * This program, aufs is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation; either version 2 of the License, or
-+ * (at your option) any later version.
++ * This is another ugly approach to keep the lock order, particularly
++ * mm->mmap_sem and aufs rwsem. The previous approach was reverted and you can
++ * find it in git-log, if you want.
+ *
-+ * 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.
++ * native readdir: i_mutex, copy_to_user, mmap_sem
++ * aufs readdir: i_mutex, rwsem, nested-i_mutex, copy_to_user, mmap_sem
+ *
-+ * 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
++ * Before aufs_mmap() mmap_sem is acquired already, but aufs_mmap() has to
++ * acquire aufs rwsem. It introduces a circular locking dependency.
++ * To address this problem, aufs_mmap() delegates the part which requires aufs
++ * rwsem to its internal workqueue.
+ */
+
-+/*
-+ * file private data
-+ */
++/* very ugly approach */
++#include "mtx.h"
+
-+#include <linux/file.h>
-+#include "aufs.h"
++struct au_mmap_pre_args {
++ /* input */
++ struct file *file;
++ struct vm_area_struct *vma;
+
-+void au_hfput(struct au_hfile *hf, struct file *file)
-+{
-+ if (file->f_flags & vfsub_fmode_to_uint(FMODE_EXEC))
-+ allow_write_access(hf->hf_file);
-+ fput(hf->hf_file);
-+ hf->hf_file = NULL;
-+ atomic_dec_return(&hf->hf_br->br_count);
-+ hf->hf_br = NULL;
-+}
++ /* output */
++ int *errp;
++ struct file *h_file;
++ struct au_branch *br;
++ int mmapped;
++};
+
-+void au_set_h_fptr(struct file *file, aufs_bindex_t bindex, struct file *val)
++static int au_mmap_pre(struct file *file, struct vm_area_struct *vma,
++ struct file **h_file, struct au_branch **br,
++ int *mmapped)
+{
-+ struct au_finfo *finfo = au_fi(file);
-+ struct au_hfile *hf;
++ int err;
++ aufs_bindex_t bstart;
++ const unsigned char wlock
++ = !!(file->f_mode & FMODE_WRITE) && (vma->vm_flags & VM_SHARED);
++ struct dentry *dentry;
++ struct super_block *sb;
+
-+ hf = finfo->fi_hfile + bindex;
-+ if (hf->hf_file)
-+ au_hfput(hf, file);
-+ if (val) {
-+ hf->hf_file = val;
-+ hf->hf_br = au_sbr(file->f_dentry->d_sb, bindex);
-+ }
++ dentry = file->f_dentry;
++ sb = dentry->d_sb;
++ si_read_lock(sb, !AuLock_FLUSH);
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
++ goto out;
++
++ *mmapped = !!au_test_mmapped(file);
++ if (wlock) {
++ struct au_pin pin;
++
++ err = au_ready_to_write(file, -1, &pin);
++ di_write_unlock(dentry);
++ if (unlikely(err))
++ goto out_unlock;
++ au_unpin(&pin);
++ } else
++ di_write_unlock(dentry);
++ bstart = au_fbstart(file);
++ *br = au_sbr(sb, bstart);
++ *h_file = au_hf_top(file);
++ get_file(*h_file);
++ au_fi_mmap_lock(file);
++
++out_unlock:
++ fi_write_unlock(file);
++out:
++ si_read_unlock(sb);
++ return err;
+}
+
-+void au_update_figen(struct file *file)
++static void au_call_mmap_pre(void *args)
+{
-+ atomic_set(&au_fi(file)->fi_generation, au_digen(file->f_dentry));
-+ /* smp_mb(); */ /* atomic_set */
++ struct au_mmap_pre_args *a = args;
++ *a->errp = au_mmap_pre(a->file, a->vma, &a->h_file, &a->br,
++ &a->mmapped);
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+void au_finfo_fin(struct file *file)
++static int aufs_mmap(struct file *file, struct vm_area_struct *vma)
+{
++ int err, wkq_err;
++ unsigned long h_vmflags;
+ struct au_finfo *finfo;
-+ aufs_bindex_t bindex, bend;
-+
-+ fi_write_lock(file);
-+ bend = au_fbend(file);
-+ bindex = au_fbstart(file);
-+ if (bindex >= 0)
++ struct dentry *h_dentry;
++ struct vm_operations_struct *h_vmop, *vmop;
++ struct au_mmap_pre_args args = {
++ .file = file,
++ .vma = vma,
++ .errp = &err
++ };
++
++ wkq_err = au_wkq_wait(au_call_mmap_pre, &args);
++ if (unlikely(wkq_err))
++ err = wkq_err;
++ if (unlikely(err))
++ goto out;
++ finfo = au_fi(file);
++ mutex_set_owner(&finfo->fi_mmap);
++
++ h_dentry = args.h_file->f_dentry;
++ if (!args.mmapped && au_test_fs_bad_mapping(h_dentry->d_sb)) {
+ /*
-+ * calls fput() instead of filp_close(),
-+ * since no dnotify or lock for the lower file.
++ * by this assignment, f_mapping will differs from aufs inode
++ * i_mapping.
++ * if someone else mixes the use of f_dentry->d_inode and
++ * f_mapping->host, then a problem may arise.
+ */
-+ for (; bindex <= bend; bindex++)
-+ au_set_h_fptr(file, bindex, NULL);
++ file->f_mapping = args.h_file->f_mapping;
++ }
+
-+ finfo = au_fi(file);
-+ au_dbg_verify_hf(finfo);
-+ kfree(finfo->fi_hfile);
++ /* always try this internal mmap to get vma flags */
++ h_vmflags = 0; /* gcc warning */
++ h_vmop = au_hvmop(args.h_file, vma, &h_vmflags);
++ err = PTR_ERR(h_vmop);
++ if (IS_ERR(h_vmop))
++ goto out_unlock;
++ AuDebugOn(args.mmapped && h_vmop != finfo->fi_hvmop);
++
++ vmop = (void *)au_dy_vmop(file, args.br, h_vmop);
++ err = PTR_ERR(vmop);
++ if (IS_ERR(vmop))
++ goto out_unlock;
++
++ /*
++ * unnecessary to handle MAP_DENYWRITE and deny_write_access()?
++ * currently MAP_DENYWRITE from userspace is ignored, but elf loader
++ * sets it. when FMODE_EXEC is set (by open_exec() or sys_uselib()),
++ * both of the aufs file and the lower file is deny_write_access()-ed.
++ * finally I hope we can skip handlling MAP_DENYWRITE here.
++ */
++ err = generic_file_mmap(file, vma);
++ if (unlikely(err))
++ goto out_unlock;
++
++ vma->vm_ops = vmop;
++ vma->vm_flags = h_vmflags;
++ if (!args.mmapped)
++ finfo->fi_hvmop = h_vmop;
++
++ vfsub_file_accessed(args.h_file);
++ /* update without lock, I don't think it a problem */
++ fsstack_copy_attr_atime(file->f_dentry->d_inode, h_dentry->d_inode);
++
++ out_unlock:
++ au_fi_mmap_unlock(file);
++ fput(args.h_file);
++ out:
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++static int aufs_fsync_nondir(struct file *file, struct dentry *dentry,
++ int datasync)
++{
++ int err;
++ struct au_pin pin;
++ struct inode *inode;
++ struct file *h_file;
++ struct super_block *sb;
++
++ inode = dentry->d_inode;
++ IMustLock(file->f_mapping->host);
++ if (inode != file->f_mapping->host) {
++ mutex_unlock(&file->f_mapping->host->i_mutex);
++ mutex_lock(&inode->i_mutex);
++ }
++ IMustLock(inode);
++
++ sb = dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++
++ err = 0; /* -EBADF; */ /* posix? */
++ if (unlikely(!(file->f_mode & FMODE_WRITE)))
++ goto out;
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
++ goto out;
++
++ 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_hf_top(file);
++ if (h_file->f_op && h_file->f_op->fsync) {
++ struct dentry *h_d;
++ struct mutex *h_mtx;
++
++ /*
++ * no filemap_fdatawrite() since aufs file has no its own
++ * mapping, but dir.
++ */
++ h_d = h_file->f_dentry;
++ h_mtx = &h_d->d_inode->i_mutex;
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ err = h_file->f_op->fsync(h_file, h_d, datasync);
++ if (!err)
++ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL);
++ /*ignore*/
++ au_cpup_attr_timesizes(inode);
++ mutex_unlock(h_mtx);
++ }
++
++ out_unlock:
++ di_read_unlock(dentry, AuLock_IR);
+ fi_write_unlock(file);
-+ AuRwDestroy(&finfo->fi_rwsem);
-+ au_cache_free_finfo(finfo);
++ out:
++ si_read_unlock(sb);
++ if (inode != file->f_mapping->host) {
++ mutex_unlock(&inode->i_mutex);
++ mutex_lock(&file->f_mapping->host->i_mutex);
++ }
++ return err;
+}
+
-+int au_finfo_init(struct file *file)
++/* no one supports this operation, currently */
++#if 0
++static int aufs_aio_fsync_nondir(struct kiocb *kio, int datasync)
+{
-+ struct au_finfo *finfo;
++ int err;
++ struct au_pin pin;
+ struct dentry *dentry;
++ struct inode *inode;
++ struct file *file, *h_file;
++ struct super_block *sb;
+
++ file = kio->ki_filp;
+ dentry = file->f_dentry;
-+ finfo = au_cache_alloc_finfo();
-+ if (unlikely(!finfo))
++ inode = dentry->d_inode;
++ mutex_lock(&inode->i_mutex);
++
++ sb = dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++
++ err = 0; /* -EBADF; */ /* posix? */
++ if (unlikely(!(file->f_mode & FMODE_WRITE)))
++ goto out;
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
+ goto out;
+
-+ finfo->fi_hfile = kcalloc(au_sbend(dentry->d_sb) + 1,
-+ sizeof(*finfo->fi_hfile), GFP_NOFS);
-+ if (unlikely(!finfo->fi_hfile))
-+ goto out_finfo;
++ err = au_ready_to_write(file, -1, &pin);
++ di_downgrade_lock(dentry, AuLock_IR);
++ if (unlikely(err))
++ goto out_unlock;
++ au_unpin(&pin);
+
-+ au_rw_init_wlock(&finfo->fi_rwsem);
-+ finfo->fi_bstart = -1;
-+ finfo->fi_bend = -1;
-+ atomic_set(&finfo->fi_generation, au_digen(dentry));
-+ /* smp_mb(); */ /* atomic_set */
++ err = -ENOSYS;
++ h_file = au_hf_top(file);
++ if (h_file->f_op && h_file->f_op->aio_fsync) {
++ struct dentry *h_d;
++ struct mutex *h_mtx;
+
-+ file->private_data = finfo;
-+ return 0; /* success */
++ h_d = h_file->f_dentry;
++ h_mtx = &h_d->d_inode->i_mutex;
++ if (!is_sync_kiocb(kio)) {
++ get_file(h_file);
++ fput(file);
++ }
++ kio->ki_filp = h_file;
++ err = h_file->f_op->aio_fsync(kio, datasync);
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ if (!err)
++ vfsub_update_h_iattr(&h_file->f_path, /*did*/NULL);
++ /*ignore*/
++ au_cpup_attr_timesizes(inode);
++ mutex_unlock(h_mtx);
++ }
+
-+ out_finfo:
-+ au_cache_free_finfo(finfo);
++ out_unlock:
++ di_read_unlock(dentry, AuLock_IR);
++ fi_write_unlock(file);
+ out:
-+ return -ENOMEM;
++ si_read_unlock(sb);
++ mutex_unlock(&inode->i_mutex);
++ return err;
+}
++#endif
+
-+int au_fi_realloc(struct au_finfo *finfo, int nbr)
++static int aufs_fasync(int fd, struct file *file, int flag)
+{
-+ int err, sz;
-+ struct au_hfile *hfp;
++ int err;
++ struct file *h_file;
++ struct dentry *dentry;
++ struct super_block *sb;
+
-+ err = -ENOMEM;
-+ sz = sizeof(*hfp) * (finfo->fi_bend + 1);
-+ if (!sz)
-+ sz = sizeof(*hfp);
-+ hfp = au_kzrealloc(finfo->fi_hfile, sz, sizeof(*hfp) * nbr, GFP_NOFS);
-+ if (hfp) {
-+ finfo->fi_hfile = hfp;
-+ err = 0;
-+ }
++ dentry = file->f_dentry;
++ sb = dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/0);
++ if (unlikely(err))
++ goto out;
++
++ h_file = au_hf_top(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:
++ si_read_unlock(sb);
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/fstype.h aufs2-2.6.git/fs/aufs/fstype.h
---- linux-2.6.31/fs/aufs/fstype.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/fstype.h 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,485 @@
++
++/* ---------------------------------------------------------------------- */
++
++/* no one supports this operation, currently */
++#if 0
++static ssize_t aufs_sendpage(struct file *file, struct page *page, int offset,
++ size_t len, loff_t *pos , int more)
++{
++}
++#endif
++
++/* ---------------------------------------------------------------------- */
++
++const struct file_operations aufs_file_fop = {
++ .owner = THIS_MODULE,
++ /*
++ * while generic_file_llseek/_unlocked() don't use BKL,
++ * don't use it since it operates file->f_mapping->host.
++ * in aufs, it may be a real file and may confuse users by UDBA.
++ */
++ /* .llseek = generic_file_llseek, */
++
++ .read = aufs_read,
++ .write = aufs_write,
++ .aio_read = aufs_aio_read,
++ .aio_write = aufs_aio_write,
++#ifdef CONFIG_AUFS_POLL
++ .poll = aufs_poll,
++#endif
++ .unlocked_ioctl = aufs_ioctl_nondir,
++ .mmap = aufs_mmap,
++ .open = aufs_open_nondir,
++ .flush = aufs_flush_nondir,
++ .release = aufs_release_nondir,
++ .fsync = aufs_fsync_nondir,
++ /* .aio_fsync = aufs_aio_fsync_nondir, */
++ .fasync = aufs_fasync,
++ /* .sendpage = aufs_sendpage, */
++ .splice_write = aufs_splice_write,
++ .splice_read = aufs_splice_read,
++#if 0
++ .aio_splice_write = aufs_aio_splice_write,
++ .aio_splice_read = aufs_aio_splice_read
++#endif
++};
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/f_op_sp.c linux-2.6.34/fs/aufs/f_op_sp.c
+--- linux-2.6.34.org/fs/aufs/f_op_sp.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/f_op_sp.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,299 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * judging filesystem type
++ * file operations for special files.
++ * while they exist in aufs virtually,
++ * their file I/O is handled out of aufs.
+ */
+
-+#ifndef __AUFS_FSTYPE_H__
-+#define __AUFS_FSTYPE_H__
-+
-+#ifdef __KERNEL__
-+
-+#include <linux/cramfs_fs.h>
-+#include <linux/fs.h>
-+#include <linux/magic.h>
-+#include <linux/romfs_fs.h>
-+#include <linux/aufs_type.h>
++#include <linux/fs_stack.h>
++#include "aufs.h"
+
-+static inline int au_test_aufs(struct super_block *sb)
++static ssize_t aufs_aio_read_sp(struct kiocb *kio, const struct iovec *iov,
++ unsigned long nv, loff_t pos)
+{
-+ return sb->s_magic == AUFS_SUPER_MAGIC;
-+}
++ ssize_t err;
++ aufs_bindex_t bstart;
++ unsigned char wbr;
++ struct file *file, *h_file;
++ struct super_block *sb;
+
-+static inline const char *au_sbtype(struct super_block *sb)
-+{
-+ return sb->s_type->name;
-+}
++ file = kio->ki_filp;
++ sb = file->f_dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ fi_read_lock(file);
++ bstart = au_fbstart(file);
++ h_file = au_hf_top(file);
++ fi_read_unlock(file);
++ wbr = !!au_br_writable(au_sbr(sb, bstart)->br_perm);
++ si_read_unlock(sb);
++
++ /* do not change the file in kio */
++ AuDebugOn(!h_file->f_op || !h_file->f_op->aio_read);
++ err = h_file->f_op->aio_read(kio, iov, nv, pos);
++ if (err > 0 && wbr)
++ file_accessed(h_file);
++
++ return err;
++}
++
++static ssize_t aufs_aio_write_sp(struct kiocb *kio, const struct iovec *iov,
++ unsigned long nv, loff_t pos)
++{
++ ssize_t err;
++ aufs_bindex_t bstart;
++ unsigned char wbr;
++ struct super_block *sb;
++ struct file *file, *h_file;
++
++ file = kio->ki_filp;
++ sb = file->f_dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ fi_read_lock(file);
++ bstart = au_fbstart(file);
++ h_file = au_hf_top(file);
++ fi_read_unlock(file);
++ wbr = !!au_br_writable(au_sbr(sb, bstart)->br_perm);
++ si_read_unlock(sb);
++
++ /* do not change the file in kio */
++ AuDebugOn(!h_file->f_op || !h_file->f_op->aio_write);
++ err = h_file->f_op->aio_write(kio, iov, nv, pos);
++ if (err > 0 && wbr)
++ file_update_time(h_file);
++
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++static int aufs_release_sp(struct inode *inode, struct file *file)
++{
++ int err;
++ struct file *h_file;
++
++ fi_read_lock(file);
++ h_file = au_hf_top(file);
++ fi_read_unlock(file);
++ /* close this fifo in aufs */
++ err = h_file->f_op->release(inode, file); /* ignore */
++ aufs_release_nondir(inode, file); /* ignore */
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++/* currently, support only FIFO */
++enum {AuSp_FIFO, AuSp_FIFO_R, AuSp_FIFO_W, AuSp_FIFO_RW,
++ /* AuSp_SOCK, AuSp_CHR, AuSp_BLK, */
++ AuSp_Last};
++static int aufs_open_sp(struct inode *inode, struct file *file);
++static struct au_sp_fop {
++ int done;
++ struct file_operations fop; /* not 'const' */
++ spinlock_t spin;
++} au_sp_fop[AuSp_Last] = {
++ [AuSp_FIFO] = {
++ .fop = {
++ .owner = THIS_MODULE,
++ .open = aufs_open_sp
++ }
++ }
++};
++
++static void au_init_fop_sp(struct file *file)
++{
++ struct au_sp_fop *p;
++ int i;
++ struct file *h_file;
++
++ p = au_sp_fop;
++ if (unlikely(!p->done)) {
++ /* initialize first time only */
++ static DEFINE_SPINLOCK(spin);
++
++ spin_lock(&spin);
++ if (!p->done) {
++ BUILD_BUG_ON(sizeof(au_sp_fop)/sizeof(*au_sp_fop)
++ != AuSp_Last);
++ for (i = 0; i < AuSp_Last; i++)
++ spin_lock_init(&p[i].spin);
++ p->done = 1;
++ }
++ spin_unlock(&spin);
++ }
++
++ switch (file->f_mode & (FMODE_READ | FMODE_WRITE)) {
++ case FMODE_READ:
++ i = AuSp_FIFO_R;
++ break;
++ case FMODE_WRITE:
++ i = AuSp_FIFO_W;
++ break;
++ case FMODE_READ | FMODE_WRITE:
++ i = AuSp_FIFO_RW;
++ break;
++ default:
++ BUG();
++ }
++
++ p += i;
++ if (unlikely(!p->done)) {
++ /* initialize first time only */
++ h_file = au_hf_top(file);
++ spin_lock(&p->spin);
++ if (!p->done) {
++ p->fop = *h_file->f_op;
++ p->fop.owner = THIS_MODULE;
++ if (p->fop.aio_read)
++ p->fop.aio_read = aufs_aio_read_sp;
++ if (p->fop.aio_write)
++ p->fop.aio_write = aufs_aio_write_sp;
++ p->fop.release = aufs_release_sp;
++ p->done = 1;
++ }
++ spin_unlock(&p->spin);
++ }
++ file->f_op = &p->fop;
++}
++
++static int au_cpup_sp(struct dentry *dentry)
++{
++ int err;
++ aufs_bindex_t bcpup;
++ struct au_pin pin;
++ struct au_wr_dir_args wr_dir_args = {
++ .force_btgt = -1,
++ .flags = 0
++ };
++
++ AuDbg("%.*s\n", AuDLNPair(dentry));
++
++ di_read_unlock(dentry, AuLock_IR);
++ di_write_lock_child(dentry);
++ err = au_wr_dir(dentry, /*src_dentry*/NULL, &wr_dir_args);
++ if (unlikely(err < 0))
++ goto out;
++ bcpup = err;
++ err = 0;
++ if (bcpup == au_dbstart(dentry))
++ goto out; /* success */
++
++ err = au_pin(&pin, dentry, bcpup, au_opt_udba(dentry->d_sb),
++ AuPin_MNT_WRITE);
++ if (!err) {
++ err = au_sio_cpup_simple(dentry, bcpup, -1, AuCpup_DTIME);
++ au_unpin(&pin);
++ }
++
++ out:
++ di_downgrade_lock(dentry, AuLock_IR);
++ return err;
++}
++
++static int au_do_open_sp(struct file *file, int flags)
++{
++ int err;
++ struct dentry *dentry;
++ struct super_block *sb;
++ struct file *h_file;
++ struct inode *h_inode;
++
++ dentry = file->f_dentry;
++ AuDbg("%.*s\n", AuDLNPair(dentry));
++
++ /*
++ * try copying-up.
++ * operate on the ro branch is not an error.
++ */
++ au_cpup_sp(dentry); /* ignore */
++
++ /* prepare h_file */
++ err = au_do_open_nondir(file, vfsub_file_flags(file));
++ if (unlikely(err))
++ goto out;
++
++ sb = dentry->d_sb;
++ h_file = au_hf_top(file);
++ h_inode = h_file->f_dentry->d_inode;
++ di_read_unlock(dentry, AuLock_IR);
++ fi_write_unlock(file);
++ si_read_unlock(sb);
++ /* open this fifo in aufs */
++ err = h_inode->i_fop->open(file->f_dentry->d_inode, file);
++ si_noflush_read_lock(sb);
++ fi_write_lock(file);
++ di_read_lock_child(dentry, AuLock_IR);
++ if (!err)
++ au_init_fop_sp(file);
++ else
++ au_finfo_fin(file);
++
++ out:
++ return err;
++}
++
++static int aufs_open_sp(struct inode *inode, struct file *file)
++{
++ int err;
++ struct super_block *sb;
++
++ sb = file->f_dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ err = au_do_open(file, au_do_open_sp, /*fidir*/NULL);
++ si_read_unlock(sb);
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++void au_init_special_fop(struct inode *inode, umode_t mode, dev_t rdev)
++{
++ init_special_inode(inode, mode, rdev);
++
++ switch (mode & S_IFMT) {
++ case S_IFIFO:
++ inode->i_fop = &au_sp_fop[AuSp_FIFO].fop;
++ /*FALLTHROUGH*/
++ case S_IFCHR:
++ case S_IFBLK:
++ case S_IFSOCK:
++ break;
++ default:
++ AuDebugOn(1);
++ }
++}
++
++int au_special_file(umode_t mode)
++{
++ int ret;
++
++ ret = 0;
++ switch (mode & S_IFMT) {
++ case S_IFIFO:
++#if 0
++ case S_IFCHR:
++ case S_IFBLK:
++ case S_IFSOCK:
++#endif
++ ret = 1;
++ }
++
++ return ret;
++}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/fstype.h linux-2.6.34/fs/aufs/fstype.h
+--- linux-2.6.34.org/fs/aufs/fstype.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/fstype.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,497 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * judging filesystem type
++ */
++
++#ifndef __AUFS_FSTYPE_H__
++#define __AUFS_FSTYPE_H__
++
++#ifdef __KERNEL__
++
++#include <linux/fs.h>
++#include <linux/magic.h>
++#include <linux/romfs_fs.h>
++#include <linux/aufs_type.h>
++
++static inline int au_test_aufs(struct super_block *sb)
++{
++ return sb->s_magic == AUFS_SUPER_MAGIC;
++}
++
++static inline const char *au_sbtype(struct super_block *sb)
++{
++ return sb->s_type->name;
++}
+
+static inline int au_test_iso9660(struct super_block *sb __maybe_unused)
+{
+#endif
+}
+
++static inline int au_test_hfsplus(struct super_block *sb __maybe_unused)
++{
++#if defined(CONFIG_HFSPLUS_FS) || defined(CONFIG_HFSPLUS_FS_MODULE)
++ return sb->s_magic == HFSPLUS_SUPER_MAGIC;
++#else
++ return 0;
++#endif
++}
++
+/* ---------------------------------------------------------------------- */
+/*
+ * they can't be an aufs branch.
+static inline int au_test_fs_bad_iattr_size(struct super_block *sb)
+{
+ return au_test_xfs(sb)
++ || au_test_btrfs(sb)
++ || au_test_ubifs(sb)
++ || au_test_hfsplus(sb) /* maintained, but incorrect */
+ /* || au_test_ext4(sb) */ /* untested */
+ /* || au_test_ocfs2(sb) */ /* untested */
+ /* || au_test_ocfs2_dlmfs(sb) */ /* untested */
+ /* || au_test_sysv(sb) */ /* untested */
-+ /* || au_test_ubifs(sb) */ /* untested */
+ /* || au_test_minix(sb) */ /* untested */
+ ;
+}
+#ifdef CONFIG_AUFS_BR_RAMFS
+ || au_test_ramfs(sb)
+#endif
-+ || au_test_ubifs(sb);
++ || au_test_ubifs(sb)
++ || au_test_btrfs(sb)
++ || au_test_hfsplus(sb);
+}
+
+/*
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_FSTYPE_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/hinotify.c aufs2-2.6.git/fs/aufs/hinotify.c
---- linux-2.6.31/fs/aufs/hinotify.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/hinotify.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,755 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/hfsnotify.c linux-2.6.34/fs/aufs/hfsnotify.c
+--- linux-2.6.34.org/fs/aufs/hfsnotify.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/hfsnotify.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,230 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * inotify for the lower directories
++ * fsnotify for the lower directories
+ */
+
+#include "aufs.h"
+
-+static const __u32 AuHinMask = (IN_MOVE | IN_DELETE | IN_CREATE);
-+static struct inotify_handle *au_hin_handle;
-+
-+AuCacheFuncs(hinotify, HINOTIFY);
++/* FS_IN_IGNORED is unnecessary */
++static const __u32 AuHfsnMask = (FS_MOVED_TO | FS_MOVED_FROM | FS_DELETE
++ | FS_CREATE | FS_EVENT_ON_CHILD);
++static struct fsnotify_group *au_hfsn_group;
+
-+int au_hin_alloc(struct au_hinode *hinode, struct inode *inode,
-+ struct inode *h_inode)
++static void au_hfsn_free_mark(struct fsnotify_mark_entry *entry)
+{
-+ int err;
-+ struct au_hinotify *hin;
-+ s32 wd;
-+
-+ err = -ENOMEM;
-+ hin = au_cache_alloc_hinotify();
-+ if (hin) {
-+ AuDebugOn(hinode->hi_notify);
-+ hinode->hi_notify = hin;
-+ hin->hin_aufs_inode = inode;
-+
-+ inotify_init_watch(&hin->hin_watch);
-+ wd = inotify_add_watch(au_hin_handle, &hin->hin_watch, h_inode,
-+ AuHinMask);
-+ if (wd >= 0)
-+ return 0; /* success */
++#if 0
++ struct au_hnotify *hn = container_of(entry, struct au_hnotify,
++ hn_entry);
++ au_cache_free_hnotify(hn);
++#endif
++ AuDbg("here\n");
++}
+
-+ err = wd;
-+ put_inotify_watch(&hin->hin_watch);
-+ au_cache_free_hinotify(hin);
-+ hinode->hi_notify = NULL;
-+ }
++static int au_hfsn_alloc(struct au_hnotify *hn, struct inode *h_inode)
++{
++ struct fsnotify_mark_entry *entry;
+
-+ return err;
++ entry = &hn->hn_entry;
++ fsnotify_init_mark(entry, au_hfsn_free_mark);
++ entry->mask = AuHfsnMask;
++ return fsnotify_add_mark(entry, au_hfsn_group, h_inode);
+}
+
-+void au_hin_free(struct au_hinode *hinode)
++static void au_hfsn_free(struct au_hnotify *hn)
+{
-+ int err;
-+ struct au_hinotify *hin;
++ struct fsnotify_mark_entry *entry;
+
-+ hin = hinode->hi_notify;
-+ if (hin) {
-+ err = 0;
-+ if (atomic_read(&hin->hin_watch.count))
-+ err = inotify_rm_watch(au_hin_handle, &hin->hin_watch);
-+ if (unlikely(err))
-+ /* it means the watch is already removed */
-+ AuWarn("failed inotify_rm_watch() %d\n", err);
-+ au_cache_free_hinotify(hin);
-+ hinode->hi_notify = NULL;
-+ }
++ entry = &hn->hn_entry;
++ fsnotify_destroy_mark_by_entry(entry);
++ fsnotify_put_mark(entry);
+}
+
+/* ---------------------------------------------------------------------- */
+
-+void au_hin_ctl(struct au_hinode *hinode, int do_set)
++static void au_hfsn_ctl(struct au_hinode *hinode, int do_set)
+{
-+ struct inode *h_inode;
-+ struct inotify_watch *watch;
-+
-+ if (!hinode->hi_notify)
-+ return;
-+
-+ h_inode = hinode->hi_inode;
-+ IMustLock(h_inode);
++ struct fsnotify_mark_entry *entry;
+
-+ /* todo: try inotify_find_update_watch()? */
-+ watch = &hinode->hi_notify->hin_watch;
-+ mutex_lock(&h_inode->inotify_mutex);
-+ /* mutex_lock(&watch->ih->mutex); */
++ entry = &hinode->hi_notify->hn_entry;
++ spin_lock(&entry->lock);
+ if (do_set) {
-+ AuDebugOn(watch->mask & AuHinMask);
-+ watch->mask |= AuHinMask;
++ AuDebugOn(entry->mask & AuHfsnMask);
++ entry->mask |= AuHfsnMask;
+ } else {
-+ AuDebugOn(!(watch->mask & AuHinMask));
-+ watch->mask &= ~AuHinMask;
++ AuDebugOn(!(entry->mask & AuHfsnMask));
++ entry->mask &= ~AuHfsnMask;
+ }
-+ /* mutex_unlock(&watch->ih->mutex); */
-+ mutex_unlock(&h_inode->inotify_mutex);
++ spin_unlock(&entry->lock);
++ /* fsnotify_recalc_inode_mask(hinode->hi_inode); */
+}
+
-+void au_reset_hinotify(struct inode *inode, unsigned int flags)
-+{
-+ aufs_bindex_t bindex, bend;
-+ struct inode *hi;
-+ struct dentry *iwhdentry;
++/* ---------------------------------------------------------------------- */
+
-+ bend = au_ibend(inode);
-+ for (bindex = au_ibstart(inode); bindex <= bend; bindex++) {
-+ hi = au_h_iptr(inode, bindex);
-+ if (!hi)
-+ continue;
-+
-+ /* mutex_lock_nested(&hi->i_mutex, AuLsc_I_CHILD); */
-+ iwhdentry = au_hi_wh(inode, bindex);
-+ if (iwhdentry)
-+ dget(iwhdentry);
-+ au_igrab(hi);
-+ au_set_h_iptr(inode, bindex, NULL, 0);
-+ au_set_h_iptr(inode, bindex, au_igrab(hi),
-+ flags & ~AuHi_XINO);
-+ iput(hi);
-+ dput(iwhdentry);
-+ /* mutex_unlock(&hi->i_mutex); */
-+ }
++/* #define AuDbgHnotify */
++#ifdef AuDbgHnotify
++static char *au_hfsn_name(u32 mask)
++{
++#ifdef CONFIG_AUFS_DEBUG
++#define test_ret(flag) if (mask & flag) \
++ return #flag;
++ test_ret(FS_ACCESS);
++ test_ret(FS_MODIFY);
++ test_ret(FS_ATTRIB);
++ test_ret(FS_CLOSE_WRITE);
++ test_ret(FS_CLOSE_NOWRITE);
++ test_ret(FS_OPEN);
++ test_ret(FS_MOVED_FROM);
++ test_ret(FS_MOVED_TO);
++ test_ret(FS_CREATE);
++ test_ret(FS_DELETE);
++ test_ret(FS_DELETE_SELF);
++ test_ret(FS_MOVE_SELF);
++ test_ret(FS_UNMOUNT);
++ test_ret(FS_Q_OVERFLOW);
++ test_ret(FS_IN_IGNORED);
++ test_ret(FS_IN_ISDIR);
++ test_ret(FS_IN_ONESHOT);
++ test_ret(FS_EVENT_ON_CHILD);
++ return "";
++#undef test_ret
++#else
++ return "??";
++#endif
+}
++#endif
+
+/* ---------------------------------------------------------------------- */
+
-+static int hin_xino(struct inode *inode, struct inode *h_inode)
++static int au_hfsn_handle_event(struct fsnotify_group *group,
++ struct fsnotify_event *event)
+{
+ int err;
-+ aufs_bindex_t bindex, bend, bfound, bstart;
-+ struct inode *h_i;
++ struct au_hnotify *hnotify;
++ struct inode *h_dir, *h_inode;
++ __u32 mask;
++ struct fsnotify_mark_entry *entry;
++ struct qstr h_child_qstr = {
++ .name = event->file_name,
++ .len = event->name_len
++ };
++
++ AuDebugOn(event->data_type != FSNOTIFY_EVENT_INODE);
+
+ err = 0;
-+ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
-+ AuWarn("branch root dir was changed\n");
++ /* if IN_UNMOUNT happens, there must be another bug */
++ mask = event->mask;
++ AuDebugOn(mask & FS_UNMOUNT);
++ if (mask & (IN_IGNORED | IN_UNMOUNT))
+ 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;
++ h_dir = event->to_tell;
++ h_inode = event->inode;
++#ifdef AuDbgHnotify
++ au_debug(1);
++ if (1 || h_child_qstr.len != sizeof(AUFS_XINO_FNAME) - 1
++ || strncmp(h_child_qstr.name, AUFS_XINO_FNAME, h_child_qstr.len)) {
++ AuDbg("i%lu, mask 0x%x %s, hcname %.*s, hi%lu\n",
++ h_dir->i_ino, mask, au_hfsn_name(mask),
++ AuLNPair(&h_child_qstr), h_inode ? h_inode->i_ino : 0);
++ /* WARN_ON(1); */
+ }
++ au_debug(0);
+#endif
-+ for (bindex = bstart; bindex <= bend; bindex++) {
-+ if (au_h_iptr(inode, bindex) == h_inode) {
-+ bfound = bindex;
-+ break;
-+ }
++
++ spin_lock(&h_dir->i_lock);
++ entry = fsnotify_find_mark_entry(group, h_dir);
++ spin_unlock(&h_dir->i_lock);
++ if (entry) {
++ hnotify = container_of(entry, struct au_hnotify, hn_entry);
++ err = au_hnotify(h_dir, hnotify, mask, &h_child_qstr, h_inode);
++ fsnotify_put_mark(entry);
+ }
-+ if (bfound < 0)
-+ goto out;
+
-+ for (bindex = bstart; bindex <= bend; bindex++) {
-+ h_i = au_h_iptr(inode, bindex);
-+ if (!h_i)
-+ continue;
++out:
++ return err;
++}
+
-+ err = au_xino_write(inode->i_sb, bindex, h_i->i_ino, /*ino*/0);
-+ /* ignore this error */
-+ /* bad action? */
++/* copied from linux/fs/notify/inotify/inotify_fsnotiry.c */
++/* it should be exported to modules */
++static bool au_hfsn_should_send_event(struct fsnotify_group *group,
++ struct inode *h_inode, __u32 mask)
++{
++ struct fsnotify_mark_entry *entry;
++ bool send;
++
++ spin_lock(&h_inode->i_lock);
++ entry = fsnotify_find_mark_entry(group, h_inode);
++ spin_unlock(&h_inode->i_lock);
++ if (!entry)
++ return false;
++
++ mask = (mask & ~FS_EVENT_ON_CHILD);
++ send = (entry->mask & mask);
++
++ /* find took a reference */
++ fsnotify_put_mark(entry);
++
++ return send;
++}
++
++static struct fsnotify_ops au_hfsn_ops = {
++ .should_send_event = au_hfsn_should_send_event,
++ .handle_event = au_hfsn_handle_event
++};
++
++/* ---------------------------------------------------------------------- */
++
++static int __init au_hfsn_init(void)
++{
++ int err;
++ unsigned int gn;
++ const unsigned int gn_max = 10;
++
++ gn = 0;
++ for (gn = 0; gn < gn_max; gn++) {
++ au_hfsn_group = fsnotify_obtain_group(gn, AuHfsnMask,
++ &au_hfsn_ops);
++ if (au_hfsn_group != ERR_PTR(-EEXIST))
++ break;
+ }
+
-+ /* children inode number will be broken */
++ err = 0;
++ if (IS_ERR(au_hfsn_group)) {
++ pr_err("fsnotify_obtain_group() failed %u times\n", gn_max);
++ err = PTR_ERR(au_hfsn_group);
++ }
+
-+ out:
+ AuTraceErr(err);
+ return err;
+}
+
-+static int hin_gen_tree(struct dentry *dentry)
++static void au_hfsn_fin(void)
+{
-+ int err, i, j, ndentry;
-+ struct au_dcsub_pages dpages;
-+ struct au_dpage *dpage;
-+ struct dentry **dentries;
++ fsnotify_put_group(au_hfsn_group);
++}
+
-+ 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;
++const struct au_hnotify_op au_hnotify_op = {
++ .ctl = au_hfsn_ctl,
++ .alloc = au_hfsn_alloc,
++ .free = au_hfsn_free,
+
-+ 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;
++ .fin = au_hfsn_fin,
++ .init = au_hfsn_init
++};
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/hfsplus.c linux-2.6.34/fs/aufs/hfsplus.c
+--- linux-2.6.34.org/fs/aufs/hfsplus.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/hfsplus.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,58 @@
++/*
++ * Copyright (C) 2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
+
-+ d = dentries[j];
-+ if (IS_ROOT(d))
-+ continue;
++/*
++ * special support for filesystems which aqucires an inode mutex
++ * at final closing a file, eg, hfsplus.
++ *
++ * This trick is very simple and stupid, just to open the file before really
++ * neceeary open to tell hfsplus that this is not the final closing.
++ * The caller should call au_h_open_pre() after acquiring the inode mutex,
++ * and au_h_open_post() after releasing it.
++ */
+
-+ d_drop(d);
-+ au_digen_dec(d);
-+ if (d->d_inode)
-+ /* todo: reset children xino?
-+ cached children only? */
-+ au_iigen_dec(d->d_inode);
-+ }
-+ }
++#include <linux/file.h>
++#include "aufs.h"
+
-+ out_dpages:
-+ au_dpages_free(&dpages);
++struct file *au_h_open_pre(struct dentry *dentry, aufs_bindex_t bindex)
++{
++ struct file *h_file;
++ struct dentry *h_dentry;
+
-+ /* discard children */
-+ dentry_unhash(dentry);
-+ dput(dentry);
-+ out:
-+ return err;
++ h_dentry = au_h_dptr(dentry, bindex);
++ AuDebugOn(!h_dentry);
++ AuDebugOn(!h_dentry->d_inode);
++ IMustLock(h_dentry->d_inode);
++
++ h_file = NULL;
++ if (au_test_hfsplus(h_dentry->d_sb)
++ && S_ISREG(h_dentry->d_inode->i_mode))
++ h_file = au_h_open(dentry, bindex,
++ O_RDONLY | O_NOATIME | O_LARGEFILE,
++ /*file*/NULL);
++ return h_file;
+}
+
++void au_h_open_post(struct dentry *dentry, aufs_bindex_t bindex,
++ struct file *h_file)
++{
++ if (h_file) {
++ fput(h_file);
++ au_sbr_put(dentry->d_sb, bindex);
++ }
++}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/hinotify.c linux-2.6.34/fs/aufs/hinotify.c
+--- linux-2.6.34.org/fs/aufs/hinotify.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/hinotify.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,227 @@
+/*
-+ * return 0 if processed.
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * inotify for the lower directories (deprecated)
+ */
-+static int hin_gen_by_inode(char *name, unsigned int nlen, struct inode *inode,
-+ const unsigned int isdir)
++
++#include "aufs.h"
++
++static const __u32 AuHinMask = (IN_MOVE | IN_DELETE | IN_CREATE);
++static struct inotify_handle *au_hin_handle;
++
++/* ---------------------------------------------------------------------- */
++
++static int au_hin_alloc(struct au_hnotify *hn, struct inode *h_inode)
+{
+ int err;
-+ struct dentry *d;
-+ struct qstr *dname;
++ s32 wd;
++ struct inotify_watch *watch;
+
-+ err = 1;
-+ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
-+ AuWarn("branch root dir was changed\n");
-+ err = 0;
++ err = -EEXIST;
++ wd = inotify_find_watch(au_hin_handle, h_inode, &watch);
++ if (wd >= 0) {
++ put_inotify_watch(watch);
+ 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);
++ err = 0;
++ inotify_init_watch(&hn->hn_watch);
++ wd = inotify_add_watch(au_hin_handle, &hn->hn_watch, h_inode,
++ AuHinMask);
++ if (unlikely(wd < 0)) {
++ err = wd;
++ put_inotify_watch(&hn->hn_watch);
+ }
+
-+ out:
-+ AuTraceErr(err);
++out:
+ return err;
+}
+
-+static int hin_gen_by_name(struct dentry *dentry, const unsigned int isdir)
++static void au_hin_free(struct au_hnotify *hn)
+{
+ int err;
-+ struct inode *inode;
-+
-+ 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;
++ if (atomic_read(&hn->hn_watch.count))
++ err = inotify_rm_watch(au_hin_handle, &hn->hn_watch);
++ if (unlikely(err))
++ /* it means the watch is already removed */
++ pr_warning("failed inotify_rm_watch() %d\n", err);
+}
+
+/* ---------------------------------------------------------------------- */
+
-+/* hinotify job flags */
-+#define AuHinJob_XINO0 1
-+#define AuHinJob_GEN (1 << 1)
-+#define AuHinJob_DIRENT (1 << 2)
-+#define AuHinJob_ISDIR (1 << 3)
-+#define AuHinJob_TRYXINO0 (1 << 4)
-+#define AuHinJob_MNTPNT (1 << 5)
-+#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)
++static void au_hin_ctl(struct au_hinode *hinode, int do_set)
+{
-+ 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 */
++ struct inode *h_inode;
++ struct inotify_watch *watch;
+
-+ 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);
-+ }
++ h_inode = hinode->hi_inode;
++ IMustLock(h_inode);
+
-+ /* 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;
-+
-+ vdir = au_ivdir(a->inode);
-+ if (vdir)
-+ vdir->vd_jiffy = 0;
-+ /* IMustLock(a->inode); */
-+ /* a->inode->i_version++; */
++ /* todo: try inotify_find_update_watch()? */
++ watch = &hinode->hi_notify->hn_watch;
++ mutex_lock(&h_inode->inotify_mutex);
++ /* mutex_lock(&watch->ih->mutex); */
++ if (do_set) {
++ AuDebugOn(watch->mask & AuHinMask);
++ watch->mask |= AuHinMask;
++ } else {
++ AuDebugOn(!(watch->mask & AuHinMask));
++ watch->mask &= ~AuHinMask;
+ }
-+
-+ /* 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;
++ /* mutex_unlock(&watch->ih->mutex); */
++ mutex_unlock(&h_inode->inotify_mutex);
+}
+
+/* ---------------------------------------------------------------------- */
+
++#ifdef AuDbgHnotify
+static char *in_name(u32 mask)
+{
+#ifdef CONFIG_AUFS_DEBUG
+ return "??";
+#endif
+}
++#endif
+
-+static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen,
-+ struct inode *dir)
-+{
-+ struct dentry *dentry, *d, *parent;
-+ struct qstr *dname;
-+
-+ parent = d_find_alias(dir);
-+ if (!parent)
-+ return NULL;
-+
-+ dentry = NULL;
-+ spin_lock(&dcache_lock);
-+ list_for_each_entry(d, &parent->d_subdirs, d_u.d_child) {
-+ /* AuDbg("%.*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)
-+ di_write_lock_child(dentry);
-+
-+ return dentry;
-+}
-+
-+static struct inode *lookup_wlock_by_ino(struct super_block *sb,
-+ aufs_bindex_t bindex, ino_t h_ino)
-+{
-+ struct inode *inode;
-+ ino_t ino;
-+ int err;
-+
-+ inode = NULL;
-+ err = au_xino_read(sb, bindex, h_ino, &ino);
-+ if (!err && ino)
-+ inode = ilookup(sb, ino);
-+ if (!inode)
-+ goto out;
-+
-+ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
-+ AuWarn("wrong root branch\n");
-+ iput(inode);
-+ inode = NULL;
-+ goto out;
-+ }
-+
-+ ii_write_lock_child(inode);
-+
-+ out:
-+ return inode;
-+}
-+
-+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);
-+ AuDbg("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(au_mntflags(sb), XINO);
-+ h_ino = 0;
-+ if (a->h_child_inode)
-+ h_ino = a->h_child_inode->i_ino;
-+
-+ if (a->h_child_nlen
-+ && (au_ftest_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))) {
-+ 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 u32 au_hin_conv_mask(u32 mask)
++{
++ u32 conv;
++
++ conv = 0;
++#define do_conv(flag) conv |= (mask & IN_ ## flag) ? FS_ ## flag : 0
++ do_conv(ACCESS);
++ do_conv(MODIFY);
++ do_conv(ATTRIB);
++ do_conv(CLOSE_WRITE);
++ do_conv(CLOSE_NOWRITE);
++ do_conv(OPEN);
++ do_conv(MOVED_FROM);
++ do_conv(MOVED_TO);
++ do_conv(CREATE);
++ do_conv(DELETE);
++ do_conv(DELETE_SELF);
++ do_conv(MOVE_SELF);
++ do_conv(UNMOUNT);
++ do_conv(Q_OVERFLOW);
++#undef do_conv
++#define do_conv(flag) conv |= (mask & IN_ ## flag) ? FS_IN_ ## flag : 0
++ do_conv(IGNORED);
++ /* do_conv(ISDIR); */
++ /* do_conv(ONESHOT); */
++#undef do_conv
++
++ return conv;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
+static void aufs_inotify(struct inotify_watch *watch, u32 wd __maybe_unused,
+ u32 mask, u32 cookie __maybe_unused,
+ 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];
++ struct au_hnotify *hnotify;
++ struct qstr h_child_qstr = {
++ .name = h_child_name
++ };
+
+ /* if IN_UNMOUNT happens, there must be another bug */
+ AuDebugOn(mask & IN_UNMOUNT);
+ put_inotify_watch(watch);
+ return;
+ }
-+#ifdef AuDbgHinotify
++
++#ifdef AuDbgHnotify
+ au_debug(1);
+ if (1 || !h_child_name || strcmp(h_child_name, AUFS_XINO_FNAME)) {
+ AuDbg("i%lu, wd %d, mask 0x%x %s, cookie 0x%x, hcname %s,"
+ au_debug(0);
+#endif
+
-+ hinotify = container_of(watch, struct au_hinotify, hin_watch);
-+ AuDebugOn(!hinotify || !hinotify->hin_aufs_inode);
-+ dir = igrab(hinotify->hin_aufs_inode);
-+ if (!dir)
-+ return;
++ if (h_child_name)
++ h_child_qstr.len = strlen(h_child_name);
++ hnotify = container_of(watch, struct au_hnotify, hn_watch);
++ mask = au_hin_conv_mask(mask);
++ au_hnotify(watch->inode, hnotify, mask, &h_child_qstr, h_child_inode);
++}
+
-+ 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;
-+ }
-+ }
++static void aufs_inotify_destroy(struct inotify_watch *watch __maybe_unused)
++{
++ return;
++}
+
-+ 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_MOVED_FROM:
-+ case IN_MOVED_TO:
-+ AuDebugOn(!h_child_name || !h_child_inode);
-+ au_fset_hinjob(flags[CHILD], GEN);
-+ au_fset_hinjob(flags[CHILD], XINO0);
-+ au_fset_hinjob(flags[CHILD], MNTPNT);
-+ au_fset_hinjob(flags[PARENT], DIRENT);
-+ break;
++static struct inotify_operations aufs_inotify_ops = {
++ .handle_event = aufs_inotify,
++ .destroy_watch = aufs_inotify_destroy
++};
+
-+ case IN_CREATE:
-+ AuDebugOn(!h_child_name || !h_child_inode);
-+ au_fset_hinjob(flags[PARENT], DIRENT);
-+ au_fset_hinjob(flags[CHILD], GEN);
-+ 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], DIRENT);
-+ au_fset_hinjob(flags[CHILD], GEN);
-+ au_fset_hinjob(flags[CHILD], TRYXINO0);
-+ au_fset_hinjob(flags[CHILD], MNTPNT);
-+ break;
++static int __init au_hin_init(void)
++{
++ int err;
+
-+ default:
-+ AuDebugOn(1);
-+ }
++ err = 0;
++ au_hin_handle = inotify_init(&aufs_inotify_ops);
++ if (IS_ERR(au_hin_handle))
++ err = PTR_ERR(au_hin_handle);
+
-+ if (wh)
-+ h_child_inode = NULL;
++ AuTraceErr(err);
++ return err;
++}
+
-+ /* 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);
-+ lockdep_on();
-+ if (unlikely(wkq_err))
-+ AuErr("wkq %d\n", wkq_err);
-+}
-+
-+static void aufs_inotify_destroy(struct inotify_watch *watch __maybe_unused)
-+{
-+ return;
-+}
-+
-+static struct inotify_operations aufs_inotify_ops = {
-+ .handle_event = aufs_inotify,
-+ .destroy_watch = aufs_inotify_destroy
-+};
-+
-+/* ---------------------------------------------------------------------- */
-+
-+static void au_hin_destroy_cache(void)
++static void au_hin_fin(void)
+{
-+ kmem_cache_destroy(au_cachep[AuCache_HINOTIFY]);
-+ au_cachep[AuCache_HINOTIFY] = NULL;
++ inotify_destroy(au_hin_handle);
+}
+
-+int __init au_hinotify_init(void)
-+{
-+ int err;
-+
-+ err = -ENOMEM;
-+ au_cachep[AuCache_HINOTIFY] = AuCache(au_hinotify);
-+ if (au_cachep[AuCache_HINOTIFY]) {
-+ err = 0;
-+ au_hin_handle = inotify_init(&aufs_inotify_ops);
-+ if (IS_ERR(au_hin_handle)) {
-+ err = PTR_ERR(au_hin_handle);
-+ au_hin_destroy_cache();
-+ }
-+ }
-+ AuTraceErr(err);
-+ return err;
-+}
++const struct au_hnotify_op au_hnotify_op = {
++ .ctl = au_hin_ctl,
++ .alloc = au_hin_alloc,
++ .free = au_hin_free,
+
-+void au_hinotify_fin(void)
-+{
-+ inotify_destroy(au_hin_handle);
-+ if (au_cachep[AuCache_HINOTIFY])
-+ au_hin_destroy_cache();
-+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/i_op.c aufs2-2.6.git/fs/aufs/i_op.c
---- linux-2.6.31/fs/aufs/i_op.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/i_op.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,883 @@
++ .fin = au_hin_fin,
++ .init = au_hin_init
++};
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/hnotify.c linux-2.6.34/fs/aufs/hnotify.c
+--- linux-2.6.34.org/fs/aufs/hnotify.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/hnotify.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,671 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * inode operations (except add/del/rename)
++ * abstraction to notify the direct changes on lower directories
+ */
+
-+#include <linux/device_cgroup.h>
-+#include <linux/fs_stack.h>
-+#include <linux/mm.h>
-+#include <linux/namei.h>
-+#include <linux/security.h>
-+#include <linux/uaccess.h>
+#include "aufs.h"
+
-+static int h_permission(struct inode *h_inode, int mask,
-+ struct vfsmount *h_mnt, int brperm)
++int au_hn_alloc(struct au_hinode *hinode, struct inode *inode,
++ struct inode *h_inode)
+{
+ int err;
-+ const unsigned char write_mask = !!(mask & (MAY_WRITE | MAY_APPEND));
-+
-+ err = -EACCES;
-+ if ((write_mask && IS_IMMUTABLE(h_inode))
-+ || ((mask & MAY_EXEC)
-+ && S_ISREG(h_inode->i_mode)
-+ && ((h_mnt->mnt_flags & MNT_NOEXEC)
-+ || !(h_inode->i_mode & S_IXUGO))))
-+ goto out;
-+
-+ /*
-+ * - skip the lower 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.
-+ */
-+ 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->permission) {
-+ /* AuLabel(generic_permission); */
-+ err = generic_permission(h_inode, mask, NULL);
-+ } else {
-+ /* AuLabel(h_inode->permission); */
-+ err = h_inode->i_op->permission(h_inode, mask);
-+ AuTraceErr(err);
-+ }
-+
-+ if (!err)
-+ err = devcgroup_inode_permission(h_inode, mask);
-+ if (!err)
-+ err = security_inode_permission
-+ (h_inode, mask & (MAY_READ | MAY_WRITE | MAY_EXEC
-+ | MAY_APPEND));
++ struct au_hnotify *hn;
+
-+#if 0
-+ if (!err) {
-+ /* todo: do we need to call ima_path_check()? */
-+ struct path h_path = {
-+ .dentry =
-+ .mnt = h_mnt
-+ };
-+ err = ima_path_check(&h_path,
-+ mask & (MAY_READ | MAY_WRITE | MAY_EXEC),
-+ IMA_COUNT_LEAVE);
++ err = -ENOMEM;
++ hn = au_cache_alloc_hnotify();
++ if (hn) {
++ hn->hn_aufs_inode = inode;
++ err = au_hnotify_op.alloc(hn, h_inode);
++ if (!err)
++ hinode->hi_notify = hn;
++ else {
++ au_cache_free_hnotify(hn);
++ /*
++ * The upper dir was removed by udba, but the same named
++ * dir left. In this case, aufs assignes a new inode
++ * number and set the monitor again.
++ * For the lower dir, the old monitnor is still left.
++ */
++ if (err == -EEXIST)
++ err = 0;
++ }
+ }
-+#endif
+
-+ out:
+ return err;
+}
+
-+static int aufs_permission(struct inode *inode, int mask)
++void au_hn_free(struct au_hinode *hinode)
+{
-+ int err;
-+ aufs_bindex_t bindex, bend;
-+ const unsigned char isdir = !!S_ISDIR(inode->i_mode);
-+ const unsigned char write_mask = !!(mask & (MAY_WRITE | MAY_APPEND));
-+ struct inode *h_inode;
-+ struct super_block *sb;
-+ struct au_branch *br;
++ struct au_hnotify *hn;
+
-+ sb = inode->i_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ ii_read_lock_child(inode);
++ hn = hinode->hi_notify;
++ if (hn) {
++ au_hnotify_op.free(hn);
++ au_cache_free_hnotify(hn);
++ hinode->hi_notify = NULL;
++ }
++}
+
-+ if (!isdir || write_mask) {
-+ err = au_busy_or_stale();
-+ h_inode = au_h_iptr(inode, au_ibstart(inode));
-+ if (unlikely(!h_inode
-+ || (h_inode->i_mode & S_IFMT)
-+ != (inode->i_mode & S_IFMT)))
-+ goto out;
++/* ---------------------------------------------------------------------- */
+
-+ err = 0;
-+ bindex = au_ibstart(inode);
-+ br = au_sbr(sb, bindex);
-+ err = h_permission(h_inode, mask, br->br_mnt, br->br_perm);
-+ 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;
-+ }
++void au_hn_ctl(struct au_hinode *hinode, int do_set)
++{
++ if (hinode->hi_notify)
++ au_hnotify_op.ctl(hinode, do_set);
++}
++
++void au_hn_reset(struct inode *inode, unsigned int flags)
++{
++ aufs_bindex_t bindex, bend;
++ struct inode *hi;
++ struct dentry *iwhdentry;
+
-+ /* 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) {
-+ err = au_busy_or_stale();
-+ if (unlikely(!S_ISDIR(h_inode->i_mode)))
-+ break;
++ for (bindex = au_ibstart(inode); bindex <= bend; bindex++) {
++ hi = au_h_iptr(inode, bindex);
++ if (!hi)
++ continue;
+
-+ br = au_sbr(sb, bindex);
-+ err = h_permission(h_inode, mask, br->br_mnt,
-+ br->br_perm);
-+ }
++ /* mutex_lock_nested(&hi->i_mutex, AuLsc_I_CHILD); */
++ iwhdentry = au_hi_wh(inode, bindex);
++ if (iwhdentry)
++ dget(iwhdentry);
++ au_igrab(hi);
++ au_set_h_iptr(inode, bindex, NULL, 0);
++ au_set_h_iptr(inode, bindex, au_igrab(hi),
++ flags & ~AuHi_XINO);
++ iput(hi);
++ dput(iwhdentry);
++ /* mutex_unlock(&hi->i_mutex); */
+ }
-+
-+ out:
-+ ii_read_unlock(inode);
-+ si_read_unlock(sb);
-+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry,
-+ struct nameidata *nd)
++static int hn_xino(struct inode *inode, struct inode *h_inode)
+{
-+ struct dentry *ret, *parent;
-+ struct inode *inode, *h_inode;
-+ struct mutex *mtx;
-+ struct super_block *sb;
-+ int err, npositive;
-+ aufs_bindex_t bstart;
++ int err;
++ aufs_bindex_t bindex, bend, bfound, bstart;
++ struct inode *h_i;
+
-+ IMustLock(dir);
++ err = 0;
++ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
++ pr_warning("branch root dir was changed\n");
++ goto out;
++ }
+
-+ sb = dir->i_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ err = au_alloc_dinfo(dentry);
-+ ret = ERR_PTR(err);
-+ if (unlikely(err))
++ 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;
+
-+ 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, nd);
-+ di_read_unlock(parent, AuLock_IR);
-+ err = npositive;
-+ ret = ERR_PTR(err);
-+ if (unlikely(err < 0))
-+ goto out_unlock;
++ for (bindex = bstart; bindex <= bend; bindex++) {
++ h_i = au_h_iptr(inode, bindex);
++ if (!h_i)
++ continue;
+
-+ inode = NULL;
-+ if (npositive) {
-+ bstart = au_dbstart(dentry);
-+ h_inode = au_h_dptr(dentry, bstart)->d_inode;
-+ 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, /*must_new*/0);
-+ mutex_unlock(mtx);
-+ } else
-+ inode = au_new_inode(dentry, /*must_new*/0);
-+ ret = (void *)inode;
++ err = au_xino_write(inode->i_sb, bindex, h_i->i_ino, /*ino*/0);
++ /* ignore this error */
++ /* bad action? */
+ }
-+ if (IS_ERR(inode))
-+ goto out_unlock;
+
-+ ret = d_splice_alias(inode, dentry);
-+ if (unlikely(IS_ERR(ret) && inode))
-+ ii_write_unlock(inode);
++ /* children inode number will be broken */
+
-+ out_unlock:
-+ di_write_unlock(dentry);
+ out:
-+ si_read_unlock(sb);
-+ return ret;
++ AuTraceErr(err);
++ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+static int au_wr_dir_cpup(struct dentry *dentry, struct dentry *parent,
-+ const unsigned char add_entry, aufs_bindex_t bcpup,
-+ aufs_bindex_t bstart)
++static int hn_gen_tree(struct dentry *dentry)
+{
-+ int err;
-+ struct dentry *h_parent;
-+ struct inode *h_dir;
++ int err, i, j, ndentry;
++ struct au_dcsub_pages dpages;
++ struct au_dpage *dpage;
++ struct dentry **dentries;
+
-+ if (add_entry) {
-+ au_update_dbstart(dentry);
-+ IMustLock(parent->d_inode);
-+ } else
-+ di_write_lock_parent(parent);
++ 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;
+
-+ err = 0;
-+ if (!au_h_dptr(parent, bcpup)) {
-+ if (bstart < bcpup)
-+ err = au_cpdown_dirs(dentry, bcpup);
-+ else
-+ err = au_cpup_dirs(dentry, bcpup);
-+ }
-+ if (!err && add_entry) {
-+ h_parent = au_h_dptr(parent, bcpup);
-+ h_dir = h_parent->d_inode;
-+ mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
-+ err = au_lkup_neg(dentry, bcpup);
-+ /* todo: no unlock here */
-+ mutex_unlock(&h_dir->i_mutex);
-+ if (bstart < bcpup && au_dbstart(dentry) < 0) {
-+ au_set_dbstart(dentry, 0);
-+ au_update_dbrange(dentry, /*do_put_zero*/0);
++ for (i = 0; i < dpages.ndpage; i++) {
++ dpage = dpages.dpages + i;
++ dentries = dpage->dentries;
++ ndentry = dpage->ndentry;
++ for (j = 0; j < ndentry; j++) {
++ struct dentry *d;
++
++ d = dentries[j];
++ if (IS_ROOT(d))
++ continue;
++
++ d_drop(d);
++ au_digen_dec(d);
++ if (d->d_inode)
++ /* todo: reset children xino?
++ cached children only? */
++ au_iigen_dec(d->d_inode);
+ }
+ }
+
-+ if (!add_entry)
-+ di_write_unlock(parent);
-+ if (!err)
-+ err = bcpup; /* success */
++ out_dpages:
++ au_dpages_free(&dpages);
+
++ /* discard children */
++ dentry_unhash(dentry);
++ dput(dentry);
++ out:
+ return err;
+}
+
+/*
-+ * decide the branch and the parent dir where we will create a new entry.
-+ * returns new bindex or an error.
-+ * copyup the parent dir if needed.
++ * return 0 if processed.
+ */
-+int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
-+ struct au_wr_dir_args *args)
++static int hn_gen_by_inode(char *name, unsigned int nlen, struct inode *inode,
++ const unsigned int isdir)
+{
+ int err;
-+ aufs_bindex_t bcpup, bstart, src_bstart;
-+ const unsigned char add_entry = !!au_ftest_wrdir(args->flags,
-+ ADD_ENTRY);
-+ struct super_block *sb;
-+ struct dentry *parent;
-+ struct au_sbinfo *sbinfo;
++ struct dentry *d;
++ struct qstr *dname;
+
-+ sb = dentry->d_sb;
-+ sbinfo = au_sbi(sb);
-+ parent = dget_parent(dentry);
-+ bstart = au_dbstart(dentry);
-+ bcpup = bstart;
-+ if (args->force_btgt < 0) {
-+ if (src_dentry) {
-+ src_bstart = au_dbstart(src_dentry);
-+ if (src_bstart < bstart)
-+ bcpup = src_bstart;
-+ } else if (add_entry) {
-+ err = AuWbrCreate(sbinfo, dentry,
-+ au_ftest_wrdir(args->flags, ISDIR));
-+ bcpup = err;
-+ }
++ err = 1;
++ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
++ pr_warning("branch root dir was changed\n");
++ err = 0;
++ goto out;
++ }
+
-+ if (bcpup < 0 || au_test_ro(sb, bcpup, dentry->d_inode)) {
-+ if (add_entry)
-+ err = AuWbrCopyup(sbinfo, dentry);
-+ else {
-+ if (!IS_ROOT(dentry)) {
-+ di_read_lock_parent(parent, !AuLock_IR);
-+ err = AuWbrCopyup(sbinfo, dentry);
-+ di_read_unlock(parent, !AuLock_IR);
-+ } else
-+ err = AuWbrCopyup(sbinfo, dentry);
-+ }
-+ bcpup = err;
-+ if (unlikely(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 {
-+ bcpup = args->force_btgt;
-+ AuDebugOn(au_test_ro(sb, bcpup, dentry->d_inode));
-+ }
-+ AuDbg("bstart %d, bcpup %d\n", bstart, bcpup);
-+ if (bstart < bcpup)
-+ au_update_dbrange(dentry, /*do_put_zero*/1);
-+
-+ err = bcpup;
-+ if (bcpup == bstart)
-+ goto out; /* success */
++ au_fset_si(au_sbi(inode->i_sb), FAILED_REFRESH_DIRS);
++ d = d_find_alias(inode);
++ if (!d) {
++ au_iigen_dec(inode);
++ goto out;
++ }
+
-+ /* copyup the new parent into the branch we process */
-+ err = au_wr_dir_cpup(dentry, parent, add_entry, bcpup, bstart);
++ dname = &d->d_name;
++ if (dname->len == nlen && !memcmp(dname->name, name, nlen))
++ err = hn_gen_tree(d);
++ dput(d);
++ }
+
+ out:
-+ dput(parent);
++ AuTraceErr(err);
+ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+struct dentry *au_pinned_h_parent(struct au_pin *pin)
++static int hn_gen_by_name(struct dentry *dentry, const unsigned int isdir)
+{
-+ if (pin && pin->parent)
-+ return au_h_dptr(pin->parent, pin->bindex);
-+ return NULL;
-+}
++ int err;
++ struct inode *inode;
+
-+void au_unpin(struct au_pin *p)
-+{
-+ if (au_ftest_pin(p->flags, MNT_WRITE))
-+ mnt_drop_write(p->h_mnt);
-+ if (!p->hdir)
-+ return;
++ inode = dentry->d_inode;
++ if (IS_ROOT(dentry)
++ /* || (inode && inode->i_ino == AUFS_ROOT_INO) */
++ ) {
++ pr_warning("branch root dir was changed\n");
++ return 0;
++ }
+
-+ au_hin_imtx_unlock(p->hdir);
-+ if (!au_ftest_pin(p->flags, DI_LOCKED))
-+ di_read_unlock(p->parent, AuLock_IR);
-+ iput(p->hdir->hi_inode);
-+ dput(p->parent);
-+ p->parent = NULL;
-+ p->hdir = NULL;
-+ p->h_mnt = NULL;
++ 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 = hn_gen_tree(dentry);
++ }
++
++ AuTraceErr(err);
++ return err;
+}
+
-+int au_do_pin(struct au_pin *p)
-+{
-+ int err;
-+ struct super_block *sb;
-+ struct dentry *h_dentry, *h_parent;
-+ struct au_branch *br;
-+ struct inode *h_dir;
++/* ---------------------------------------------------------------------- */
+
-+ err = 0;
-+ sb = p->dentry->d_sb;
-+ br = au_sbr(sb, p->bindex);
-+ if (IS_ROOT(p->dentry)) {
-+ if (au_ftest_pin(p->flags, MNT_WRITE)) {
-+ p->h_mnt = br->br_mnt;
-+ err = mnt_want_write(p->h_mnt);
-+ if (unlikely(err)) {
-+ au_fclr_pin(p->flags, MNT_WRITE);
-+ goto out_err;
-+ }
-+ }
-+ goto out;
-+ }
++/* hnotify job flags */
++#define AuHnJob_XINO0 1
++#define AuHnJob_GEN (1 << 1)
++#define AuHnJob_DIRENT (1 << 2)
++#define AuHnJob_ISDIR (1 << 3)
++#define AuHnJob_TRYXINO0 (1 << 4)
++#define AuHnJob_MNTPNT (1 << 5)
++#define au_ftest_hnjob(flags, name) ((flags) & AuHnJob_##name)
++#define au_fset_hnjob(flags, name) { (flags) |= AuHnJob_##name; }
++#define au_fclr_hnjob(flags, name) { (flags) &= ~AuHnJob_##name; }
+
-+ h_dentry = NULL;
-+ if (p->bindex <= au_dbend(p->dentry))
-+ h_dentry = au_h_dptr(p->dentry, p->bindex);
++enum {
++ AuHn_CHILD,
++ AuHn_PARENT,
++ AuHnLast
++};
+
-+ p->parent = dget_parent(p->dentry);
-+ if (!au_ftest_pin(p->flags, DI_LOCKED))
-+ di_read_lock(p->parent, AuLock_IR, p->lsc_di);
++struct au_hnotify_args {
++ struct inode *h_dir, *dir, *h_child_inode;
++ u32 mask;
++ unsigned int flags[AuHnLast];
++ unsigned int h_child_nlen;
++ char h_child_name[];
++};
+
-+ h_dir = NULL;
-+ h_parent = au_h_dptr(p->parent, p->bindex);
-+ p->hdir = au_hi(p->parent->d_inode, p->bindex);
-+ if (p->hdir)
-+ h_dir = p->hdir->hi_inode;
++struct hn_job_args {
++ unsigned int flags;
++ struct inode *inode, *h_inode, *dir, *h_dir;
++ struct dentry *dentry;
++ char *h_name;
++ int h_nlen;
++};
+
-+ /* udba case */
-+ if (unlikely(!p->hdir || !h_dir)) {
-+ if (!au_ftest_pin(p->flags, DI_LOCKED))
-+ di_read_unlock(p->parent, AuLock_IR);
-+ dput(p->parent);
-+ p->parent = NULL;
-+ goto out_err;
-+ }
++static int hn_job(struct hn_job_args *a)
++{
++ const unsigned int isdir = au_ftest_hnjob(a->flags, ISDIR);
+
-+ au_igrab(h_dir);
-+ au_hin_imtx_lock_nested(p->hdir, p->lsc_hi);
++ /* reset xino */
++ if (au_ftest_hnjob(a->flags, XINO0) && a->inode)
++ hn_xino(a->inode, a->h_inode); /* ignore this error */
+
-+ if (unlikely(p->hdir->hi_inode != h_parent->d_inode)) {
-+ err = -EBUSY;
-+ goto out_unpin;
-+ }
-+ if (h_dentry) {
-+ err = au_h_verify(h_dentry, p->udba, h_dir, h_parent, br);
-+ if (unlikely(err)) {
-+ au_fclr_pin(p->flags, MNT_WRITE);
-+ goto out_unpin;
-+ }
++ if (au_ftest_hnjob(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)
++ hn_xino(a->inode, a->h_inode); /* ignore this error */
++ mutex_unlock(&a->h_inode->i_mutex);
+ }
+
-+ if (au_ftest_pin(p->flags, MNT_WRITE)) {
-+ p->h_mnt = br->br_mnt;
-+ err = mnt_want_write(p->h_mnt);
-+ if (unlikely(err)) {
-+ au_fclr_pin(p->flags, MNT_WRITE);
-+ goto out_unpin;
-+ }
++ /* make the generation obsolete */
++ if (au_ftest_hnjob(a->flags, GEN)) {
++ int err = -1;
++ if (a->inode)
++ err = hn_gen_by_inode(a->h_name, a->h_nlen, a->inode,
++ isdir);
++ if (err && a->dentry)
++ hn_gen_by_name(a->dentry, isdir);
++ /* ignore this error */
+ }
-+ goto out; /* success */
+
-+ out_unpin:
-+ au_unpin(p);
-+ out_err:
-+ AuErr("err %d\n", err);
-+ err = au_busy_or_stale();
-+ out:
-+ return err;
-+}
++ /* make dir entries obsolete */
++ if (au_ftest_hnjob(a->flags, DIRENT) && a->inode) {
++ struct au_vdir *vdir;
+
-+void au_pin_init(struct au_pin *p, struct dentry *dentry,
-+ aufs_bindex_t bindex, int lsc_di, int lsc_hi,
-+ unsigned int udba, unsigned char flags)
-+{
-+ p->dentry = dentry;
-+ p->udba = udba;
-+ p->lsc_di = lsc_di;
-+ p->lsc_hi = lsc_hi;
-+ p->flags = flags;
-+ p->bindex = bindex;
++ vdir = au_ivdir(a->inode);
++ if (vdir)
++ vdir->vd_jiffy = 0;
++ /* IMustLock(a->inode); */
++ /* a->inode->i_version++; */
++ }
+
-+ p->parent = NULL;
-+ p->hdir = NULL;
-+ p->h_mnt = NULL;
-+}
++ /* can do nothing but warn */
++ if (au_ftest_hnjob(a->flags, MNTPNT)
++ && a->dentry
++ && d_mountpoint(a->dentry))
++ pr_warning("mount-point %.*s is removed or renamed\n",
++ AuDLNPair(a->dentry));
+
-+int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex,
-+ unsigned int udba, unsigned char flags)
-+{
-+ au_pin_init(pin, dentry, bindex, AuLsc_DI_PARENT, AuLsc_I_PARENT2,
-+ udba, flags);
-+ return au_do_pin(pin);
++ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+#define AuIcpup_DID_CPUP 1
-+#define au_ftest_icpup(flags, name) ((flags) & AuIcpup_##name)
-+#define au_fset_icpup(flags, name) { (flags) |= AuIcpup_##name; }
-+#define au_fclr_icpup(flags, name) { (flags) &= ~AuIcpup_##name; }
-+
-+struct au_icpup_args {
-+ unsigned char flags;
-+ unsigned char pin_flags;
-+ aufs_bindex_t btgt;
-+ struct au_pin pin;
-+ struct path h_path;
-+ struct inode *h_inode;
-+};
-+
-+static int au_lock_and_icpup(struct dentry *dentry, struct iattr *ia,
-+ struct au_icpup_args *a)
++static struct dentry *lookup_wlock_by_name(char *name, unsigned int nlen,
++ struct inode *dir)
+{
-+ int err;
-+ unsigned int udba;
-+ loff_t sz;
-+ aufs_bindex_t bstart;
-+ struct dentry *hi_wh, *parent;
-+ struct inode *inode;
-+ struct au_wr_dir_args wr_dir_args = {
-+ .force_btgt = -1,
-+ .flags = 0
-+ };
-+
-+ di_write_lock_child(dentry);
-+ bstart = au_dbstart(dentry);
-+ inode = dentry->d_inode;
-+ if (S_ISDIR(inode->i_mode))
-+ au_fset_wrdir(wr_dir_args.flags, ISDIR);
-+ /* plink or hi_wh() case */
-+ 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;
-+ a->btgt = err;
-+ if (err != bstart)
-+ au_fset_icpup(a->flags, DID_CPUP);
++ struct dentry *dentry, *d, *parent;
++ struct qstr *dname;
+
-+ err = 0;
-+ a->pin_flags = AuPin_MNT_WRITE;
-+ parent = NULL;
-+ if (!IS_ROOT(dentry)) {
-+ au_fset_pin(a->pin_flags, DI_LOCKED);
-+ parent = dget_parent(dentry);
-+ di_write_lock_parent(parent);
-+ }
++ parent = d_find_alias(dir);
++ if (!parent)
++ return NULL;
+
-+ udba = au_opt_udba(dentry->d_sb);
-+ if (d_unhashed(dentry) || (ia->ia_valid & ATTR_FILE))
-+ udba = AuOpt_UDBA_NONE;
-+ err = au_pin(&a->pin, dentry, a->btgt, udba, a->pin_flags);
-+ if (unlikely(err)) {
-+ if (parent) {
-+ di_write_unlock(parent);
-+ dput(parent);
++ dentry = NULL;
++ spin_lock(&dcache_lock);
++ list_for_each_entry(d, &parent->d_subdirs, d_u.d_child) {
++ /* AuDbg("%.*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;
+ }
-+ goto out_dentry;
-+ }
-+ a->h_path.dentry = au_h_dptr(dentry, bstart);
-+ a->h_inode = a->h_path.dentry->d_inode;
-+ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
-+ sz = -1;
-+ if ((ia->ia_valid & ATTR_SIZE) && ia->ia_size < i_size_read(a->h_inode))
-+ sz = ia->ia_size;
+
-+ hi_wh = NULL;
-+ if (au_ftest_icpup(a->flags, DID_CPUP) && d_unhashed(dentry)) {
-+ hi_wh = au_hi_wh(inode, a->btgt);
-+ if (!hi_wh) {
-+ err = au_sio_cpup_wh(dentry, a->btgt, sz, /*file*/NULL);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ hi_wh = au_hi_wh(inode, a->btgt);
-+ /* todo: revalidate hi_wh? */
-+ }
++ dentry = dget(d);
++ break;
+ }
++ spin_unlock(&dcache_lock);
++ dput(parent);
+
-+ if (parent) {
-+ au_pin_set_parent_lflag(&a->pin, /*lflag*/0);
-+ di_downgrade_lock(parent, AuLock_IR);
-+ dput(parent);
-+ }
-+ if (!au_ftest_icpup(a->flags, DID_CPUP))
-+ goto out; /* success */
++ if (dentry)
++ di_write_lock_child(dentry);
+
-+ if (!d_unhashed(dentry)) {
-+ err = au_sio_cpup_simple(dentry, a->btgt, sz, AuCpup_DTIME);
-+ if (!err)
-+ a->h_path.dentry = au_h_dptr(dentry, a->btgt);
-+ } else if (!hi_wh)
-+ a->h_path.dentry = au_h_dptr(dentry, a->btgt);
-+ else
-+ a->h_path.dentry = hi_wh; /* do not dget here */
++ return dentry;
++}
+
-+ out_unlock:
-+ mutex_unlock(&a->h_inode->i_mutex);
-+ a->h_inode = a->h_path.dentry->d_inode;
-+ if (!err) {
-+ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
-+ goto out; /* success */
++static struct inode *lookup_wlock_by_ino(struct super_block *sb,
++ aufs_bindex_t bindex, ino_t h_ino)
++{
++ struct inode *inode;
++ ino_t ino;
++ int err;
++
++ inode = NULL;
++ err = au_xino_read(sb, bindex, h_ino, &ino);
++ if (!err && ino)
++ inode = ilookup(sb, ino);
++ if (!inode)
++ goto out;
++
++ if (unlikely(inode->i_ino == AUFS_ROOT_INO)) {
++ pr_warning("wrong root branch\n");
++ iput(inode);
++ inode = NULL;
++ goto out;
+ }
+
-+ au_unpin(&a->pin);
++ ii_write_lock_child(inode);
+
-+ out_dentry:
-+ di_write_unlock(dentry);
+ out:
-+ return err;
++ return inode;
+}
+
-+static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
++static void au_hn_bh(void *_args)
+{
++ struct au_hnotify_args *a = _args;
++ struct super_block *sb;
++ aufs_bindex_t bindex, bend, bfound;
++ unsigned char xino, try_iput;
+ int err;
+ struct inode *inode;
-+ struct super_block *sb;
-+ struct file *file;
-+ struct au_icpup_args *a;
++ ino_t h_ino;
++ struct hn_job_args args;
++ struct dentry *dentry;
++ struct au_sbinfo *sbinfo;
+
-+ err = -ENOMEM;
-+ a = kzalloc(sizeof(*a), GFP_NOFS);
-+ if (unlikely(!a))
-+ goto out;
++ AuDebugOn(!_args);
++ AuDebugOn(!a->h_dir);
++ AuDebugOn(!a->dir);
++ AuDebugOn(!a->mask);
++ AuDbg("mask 0x%x, i%lu, hi%lu, hci%lu\n",
++ a->mask, a->dir->i_ino, a->h_dir->i_ino,
++ a->h_child_inode ? a->h_child_inode->i_ino : 0);
+
-+ inode = dentry->d_inode;
-+ IMustLock(inode);
-+ sb = dentry->d_sb;
-+ si_read_lock(sb, AuLock_FLUSH);
++ 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);
+
-+ file = NULL;
-+ 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));
-+ }
++ 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;
+
-+ if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
-+ ia->ia_valid &= ~ATTR_MODE;
++ xino = !!au_opt_test(au_mntflags(sb), XINO);
++ h_ino = 0;
++ if (a->h_child_inode)
++ h_ino = a->h_child_inode->i_ino;
+
-+ err = au_lock_and_icpup(dentry, ia, a);
-+ if (unlikely(err < 0))
-+ goto out_si;
-+ if (au_ftest_icpup(a->flags, DID_CPUP)) {
-+ ia->ia_file = NULL;
-+ ia->ia_valid &= ~ATTR_FILE;
-+ }
++ if (a->h_child_nlen
++ && (au_ftest_hnjob(a->flags[AuHn_CHILD], GEN)
++ || au_ftest_hnjob(a->flags[AuHn_CHILD], MNTPNT)))
++ dentry = lookup_wlock_by_name(a->h_child_name, a->h_child_nlen,
++ a->dir);
++ try_iput = 0;
++ if (dentry)
++ inode = dentry->d_inode;
++ if (xino && !inode && h_ino
++ && (au_ftest_hnjob(a->flags[AuHn_CHILD], XINO0)
++ || au_ftest_hnjob(a->flags[AuHn_CHILD], TRYXINO0)
++ || au_ftest_hnjob(a->flags[AuHn_CHILD], GEN))) {
++ inode = lookup_wlock_by_ino(sb, bfound, h_ino);
++ try_iput = 1;
++ }
+
-+ a->h_path.mnt = au_sbr_mnt(sb, a->btgt);
-+ if (ia->ia_valid & ATTR_SIZE) {
-+ struct file *f;
++ args.flags = a->flags[AuHn_CHILD];
++ args.dentry = dentry;
++ args.inode = inode;
++ args.h_inode = a->h_child_inode;
++ args.dir = a->dir;
++ args.h_dir = a->h_dir;
++ args.h_name = a->h_child_name;
++ args.h_nlen = a->h_child_nlen;
++ err = hn_job(&args);
++ if (dentry) {
++ if (dentry->d_fsdata)
++ di_write_unlock(dentry);
++ dput(dentry);
++ }
++ if (inode && try_iput) {
++ ii_write_unlock(inode);
++ iput(inode);
++ }
+
-+ if (ia->ia_size < i_size_read(inode)) {
-+ /* unmap only */
-+ err = vmtruncate(inode, ia->ia_size);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ }
++ ii_write_lock_parent(a->dir);
++ args.flags = a->flags[AuHn_PARENT];
++ args.dentry = NULL;
++ args.inode = a->dir;
++ args.h_inode = a->h_dir;
++ args.dir = NULL;
++ args.h_dir = NULL;
++ args.h_name = NULL;
++ args.h_nlen = 0;
++ err = hn_job(&args);
++ ii_write_unlock(a->dir);
+
-+ f = NULL;
-+ if (ia->ia_valid & ATTR_FILE)
-+ f = ia->ia_file;
-+ mutex_unlock(&a->h_inode->i_mutex);
-+ err = vfsub_trunc(&a->h_path, ia->ia_size, ia->ia_valid, f);
-+ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
-+ } else
-+ err = vfsub_notify_change(&a->h_path, ia);
-+ if (!err)
-+ au_cpup_attr_changeable(inode);
++ out:
++ au_nwt_done(&sbinfo->si_nowait);
++ si_write_unlock(sb);
+
-+ out_unlock:
-+ mutex_unlock(&a->h_inode->i_mutex);
-+ au_unpin(&a->pin);
-+ di_write_unlock(dentry);
-+ out_si:
-+ if (file) {
-+ fi_write_unlock(file);
-+ ia->ia_file = file;
-+ ia->ia_valid |= ATTR_FILE;
-+ }
-+ si_read_unlock(sb);
++ iput(a->h_child_inode);
++ iput(a->h_dir);
++ iput(a->dir);
+ kfree(a);
-+ out:
-+ return err;
+}
+
-+static int au_getattr_lock_reval(struct dentry *dentry, unsigned int sigen)
++/* ---------------------------------------------------------------------- */
++
++int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask,
++ struct qstr *h_child_qstr, struct inode *h_child_inode)
+{
-+ int err;
-+ struct inode *inode;
-+ struct dentry *parent;
++ int err, len;
++ unsigned int flags[AuHnLast];
++ unsigned char isdir, isroot, wh;
++ struct inode *dir;
++ struct au_hnotify_args *args;
++ char *p, *h_child_name;
+
+ err = 0;
-+ inode = dentry->d_inode;
-+ di_write_lock_child(dentry);
-+ if (au_digen(dentry) != sigen || au_iigen(inode) != sigen) {
-+ parent = dget_parent(dentry);
-+ di_read_lock_parent(parent, AuLock_IR);
-+ /* returns a number of positive dentries */
-+ err = au_refresh_hdentry(dentry, inode->i_mode & S_IFMT);
-+ if (err >= 0)
-+ err = au_refresh_hinode(inode, dentry);
-+ di_read_unlock(parent, AuLock_IR);
-+ dput(parent);
-+ }
-+ di_downgrade_lock(dentry, AuLock_IR);
-+ if (unlikely(err))
-+ di_read_unlock(dentry, AuLock_IR);
-+
-+ AuTraceErr(err);
-+ return err;
-+}
-+
-+static void au_refresh_iattr(struct inode *inode, struct kstat *st,
-+ unsigned int nlink)
-+{
-+ inode->i_mode = st->mode;
-+ inode->i_uid = st->uid;
-+ inode->i_gid = st->gid;
-+ inode->i_atime = st->atime;
-+ inode->i_mtime = st->mtime;
-+ inode->i_ctime = st->ctime;
++ AuDebugOn(!hnotify || !hnotify->hn_aufs_inode);
++ dir = igrab(hnotify->hn_aufs_inode);
++ if (!dir)
++ goto out;
+
-+ au_cpup_attr_nlink(inode, /*force*/0);
-+ if (S_ISDIR(inode->i_mode)) {
-+ inode->i_nlink -= nlink;
-+ inode->i_nlink += st->nlink;
++ isroot = (dir->i_ino == AUFS_ROOT_INO);
++ wh = 0;
++ h_child_name = (void *)h_child_qstr->name;
++ len = h_child_qstr->len;
++ if (h_child_name) {
++ if (len > AUFS_WH_PFX_LEN
++ && !memcmp(h_child_name, AUFS_WH_PFX, AUFS_WH_PFX_LEN)) {
++ h_child_name += AUFS_WH_PFX_LEN;
++ len -= AUFS_WH_PFX_LEN;
++ wh = 1;
++ }
+ }
+
-+ spin_lock(&inode->i_lock);
-+ inode->i_blocks = st->blocks;
-+ i_size_write(inode, st->size);
-+ spin_unlock(&inode->i_lock);
-+}
-+
-+static int aufs_getattr(struct vfsmount *mnt __maybe_unused,
-+ struct dentry *dentry, struct kstat *st)
-+{
-+ int err;
-+ unsigned int mnt_flags;
-+ aufs_bindex_t bindex;
-+ unsigned char udba_none, positive;
-+ struct super_block *sb, *h_sb;
-+ struct inode *inode;
-+ struct vfsmount *h_mnt;
-+ struct dentry *h_dentry;
++ isdir = 0;
++ if (h_child_inode)
++ isdir = !!S_ISDIR(h_child_inode->i_mode);
++ flags[AuHn_PARENT] = AuHnJob_ISDIR;
++ flags[AuHn_CHILD] = 0;
++ if (isdir)
++ flags[AuHn_CHILD] = AuHnJob_ISDIR;
++ au_fset_hnjob(flags[AuHn_PARENT], DIRENT);
++ au_fset_hnjob(flags[AuHn_CHILD], GEN);
++ switch (mask & FS_EVENTS_POSS_ON_CHILD) {
++ case FS_MOVED_FROM:
++ case FS_MOVED_TO:
++ au_fset_hnjob(flags[AuHn_CHILD], XINO0);
++ au_fset_hnjob(flags[AuHn_CHILD], MNTPNT);
++ /*FALLTHROUGH*/
++ case FS_CREATE:
++ AuDebugOn(!h_child_name || !h_child_inode);
++ break;
+
-+ err = 0;
-+ sb = dentry->d_sb;
-+ inode = dentry->d_inode;
-+ si_read_lock(sb, AuLock_FLUSH);
-+ mnt_flags = au_mntflags(sb);
-+ udba_none = !!au_opt_test(mnt_flags, UDBA_NONE);
++ case FS_DELETE:
++ /*
++ * aufs never be able to get this child inode.
++ * revalidation should be in d_revalidate()
++ * by checking i_nlink, i_generation or d_unhashed().
++ */
++ AuDebugOn(!h_child_name);
++ au_fset_hnjob(flags[AuHn_CHILD], TRYXINO0);
++ au_fset_hnjob(flags[AuHn_CHILD], MNTPNT);
++ break;
+
-+ /* support fstat(2) */
-+ if (!d_unhashed(dentry) && !udba_none) {
-+ unsigned int sigen = au_sigen(sb);
-+ if (au_digen(dentry) == sigen && au_iigen(inode) == sigen)
-+ di_read_lock_child(dentry, AuLock_IR);
-+ else {
-+ AuDebugOn(IS_ROOT(dentry));
-+ err = au_getattr_lock_reval(dentry, sigen);
-+ if (unlikely(err))
-+ goto out;
-+ }
-+ } else
-+ di_read_lock_child(dentry, AuLock_IR);
++ default:
++ AuDebugOn(1);
++ }
+
-+ bindex = au_ibstart(inode);
-+ h_mnt = au_sbr_mnt(sb, bindex);
-+ h_sb = h_mnt->mnt_sb;
-+ if (!au_test_fs_bad_iattr(h_sb) && udba_none)
-+ goto out_fill; /* success */
++ if (wh)
++ h_child_inode = NULL;
+
-+ h_dentry = NULL;
-+ if (au_dbstart(dentry) == bindex)
-+ h_dentry = dget(au_h_dptr(dentry, bindex));
-+ else if (au_opt_test(mnt_flags, PLINK) && au_plink_test(inode)) {
-+ h_dentry = au_plink_lkup(inode, bindex);
-+ if (IS_ERR(h_dentry))
-+ goto out_fill; /* pretending success */
++ err = -ENOMEM;
++ /* iput() and kfree() will be called in au_hnotify() */
++ /*
++ * 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);
++ goto out;
++ }
++ args->flags[AuHn_PARENT] = flags[AuHn_PARENT];
++ args->flags[AuHn_CHILD] = flags[AuHn_CHILD];
++ args->mask = mask;
++ args->dir = dir;
++ args->h_dir = igrab(h_dir);
++ if (h_child_inode)
++ h_child_inode = igrab(h_child_inode); /* can be NULL */
++ args->h_child_inode = h_child_inode;
++ args->h_child_nlen = len;
++ if (len) {
++ p = (void *)args;
++ p += sizeof(*args);
++ memcpy(p, h_child_name, len);
++ p[len] = 0;
+ }
-+ /* illegally overlapped or something */
-+ if (unlikely(!h_dentry))
-+ goto out_fill; /* pretending success */
+
-+ positive = !!h_dentry->d_inode;
-+ if (positive)
-+ err = vfs_getattr(h_mnt, h_dentry, st);
-+ dput(h_dentry);
-+ if (!err) {
-+ if (positive)
-+ au_refresh_iattr(inode, st, h_dentry->d_inode->i_nlink);
-+ goto out_fill; /* success */
++ /* lockdep_off(); */
++ err = au_wkq_nowait(au_hn_bh, args, dir->i_sb);
++ /* lockdep_on(); */
++ if (unlikely(err)) {
++ pr_err("wkq %d\n", err);
++ iput(args->h_child_inode);
++ iput(args->h_dir);
++ iput(args->dir);
++ kfree(args);
+ }
-+ goto out_unlock;
+
-+ out_fill:
-+ generic_fillattr(inode, st);
-+ out_unlock:
-+ di_read_unlock(dentry, AuLock_IR);
-+ out:
-+ si_read_unlock(sb);
++out:
+ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
++static void au_hn_destroy_cache(void)
++{
++ kmem_cache_destroy(au_cachep[AuCache_HNOTIFY]);
++ au_cachep[AuCache_HNOTIFY] = NULL;
++}
+
-+static int h_readlink(struct dentry *dentry, int bindex, char __user *buf,
-+ int bufsiz)
++int __init au_hnotify_init(void)
+{
+ int err;
-+ struct super_block *sb;
-+ struct dentry *h_dentry;
-+
-+ err = -EINVAL;
-+ h_dentry = au_h_dptr(dentry, bindex);
-+ if (unlikely(/* !h_dentry
-+ || !h_dentry->d_inode
-+ || !h_dentry->d_inode->i_op
-+ || */ !h_dentry->d_inode->i_op->readlink))
-+ goto out;
-+
-+ err = security_inode_readlink(h_dentry);
-+ if (unlikely(err))
-+ goto out;
+
-+ sb = dentry->d_sb;
-+ if (!au_test_ro(sb, bindex, dentry->d_inode)) {
-+ vfsub_touch_atime(au_sbr_mnt(sb, bindex), h_dentry);
-+ fsstack_copy_attr_atime(dentry->d_inode, h_dentry->d_inode);
++ err = -ENOMEM;
++ au_cachep[AuCache_HNOTIFY] = AuCache(au_hnotify);
++ if (au_cachep[AuCache_HNOTIFY]) {
++ err = au_hnotify_op.init();
++ if (unlikely(err))
++ au_hn_destroy_cache();
+ }
-+ err = h_dentry->d_inode->i_op->readlink(h_dentry, buf, bufsiz);
-+
-+ out:
-+ return err;
-+}
-+
-+static int aufs_readlink(struct dentry *dentry, char __user *buf, int bufsiz)
-+{
-+ int err;
-+
-+ aufs_read_lock(dentry, AuLock_IR);
-+ err = h_readlink(dentry, au_dbstart(dentry), buf, bufsiz);
-+ aufs_read_unlock(dentry, AuLock_IR);
-+
-+ return err;
-+}
-+
-+static void *aufs_follow_link(struct dentry *dentry, struct nameidata *nd)
-+{
-+ int err;
-+ char *buf;
-+ mm_segment_t old_fs;
-+
-+ err = -ENOMEM;
-+ buf = __getname();
-+ if (unlikely(!buf))
-+ goto out;
-+
-+ aufs_read_lock(dentry, AuLock_IR);
-+ old_fs = get_fs();
-+ set_fs(KERNEL_DS);
-+ err = h_readlink(dentry, au_dbstart(dentry), (char __user *)buf,
-+ PATH_MAX);
-+ set_fs(old_fs);
-+ aufs_read_unlock(dentry, AuLock_IR);
-+
-+ if (err >= 0) {
-+ buf[err] = 0;
-+ /* will be freed by put_link */
-+ nd_set_link(nd, buf);
-+ return NULL; /* success */
-+ }
-+ __putname(buf);
-+
-+ out:
-+ path_put(&nd->path);
+ AuTraceErr(err);
-+ return ERR_PTR(err);
-+}
-+
-+static void aufs_put_link(struct dentry *dentry __maybe_unused,
-+ struct nameidata *nd, void *cookie __maybe_unused)
-+{
-+ __putname(nd_get_link(nd));
++ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+static void aufs_truncate_range(struct inode *inode __maybe_unused,
-+ loff_t start __maybe_unused,
-+ loff_t end __maybe_unused)
++void au_hnotify_fin(void)
+{
-+ AuUnsupport();
++ au_hnotify_op.fin();
++ /* cf. au_cache_fin() */
++ if (au_cachep[AuCache_HNOTIFY])
++ au_hn_destroy_cache();
+}
-+
-+/* ---------------------------------------------------------------------- */
-+
-+struct inode_operations aufs_symlink_iop = {
-+ .permission = aufs_permission,
-+ .setattr = aufs_setattr,
-+ .getattr = aufs_getattr,
-+ .readlink = aufs_readlink,
-+ .follow_link = aufs_follow_link,
-+ .put_link = aufs_put_link
-+};
-+
-+struct inode_operations aufs_dir_iop = {
-+ .create = aufs_create,
-+ .lookup = aufs_lookup,
-+ .link = aufs_link,
-+ .unlink = aufs_unlink,
-+ .symlink = aufs_symlink,
-+ .mkdir = aufs_mkdir,
-+ .rmdir = aufs_rmdir,
-+ .mknod = aufs_mknod,
-+ .rename = aufs_rename,
-+
-+ .permission = aufs_permission,
-+ .setattr = aufs_setattr,
-+ .getattr = aufs_getattr
-+};
-+
-+struct inode_operations aufs_iop = {
-+ .permission = aufs_permission,
-+ .setattr = aufs_setattr,
-+ .getattr = aufs_getattr,
-+ .truncate_range = aufs_truncate_range
-+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/i_op_add.c aufs2-2.6.git/fs/aufs/i_op_add.c
---- linux-2.6.31/fs/aufs/i_op_add.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/i_op_add.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,649 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/iinfo.c linux-2.6.34/fs/aufs/iinfo.c
+--- linux-2.6.34.org/fs/aufs/iinfo.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/iinfo.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,277 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * inode operations (add entry)
++ * inode private data
+ */
+
+#include "aufs.h"
+
-+/*
-+ * final procedure of adding a new entry, except link(2).
-+ * remove whiteout, instantiate, copyup the parent dir's times and size
-+ * and update version.
-+ * if it failed, re-create the removed whiteout.
-+ */
-+static int epilog(struct inode *dir, aufs_bindex_t bindex,
-+ struct dentry *wh_dentry, struct dentry *dentry)
++struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex)
+{
-+ int err, rerr;
-+ aufs_bindex_t bwh;
-+ struct path h_path;
-+ struct inode *inode, *h_dir;
-+ struct dentry *wh;
++ struct inode *h_inode;
+
-+ 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);
-+ h_path.dentry = wh_dentry;
-+ h_path.mnt = au_sbr_mnt(dir->i_sb, bindex);
-+ err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path,
-+ dentry);
-+ if (unlikely(err))
-+ goto out;
-+ }
++ IiMustAnyLock(inode);
+
-+ inode = au_new_inode(dentry, /*must_new*/1);
-+ if (!IS_ERR(inode)) {
-+ d_instantiate(dentry, inode);
-+ dir = dentry->d_parent->d_inode; /* dir inode is locked */
-+ IMustLock(dir);
-+ if (au_ibstart(dir) == au_dbstart(dentry))
-+ au_cpup_attr_timesizes(dir);
-+ dir->i_version++;
-+ return 0; /* success */
-+ }
++ h_inode = au_ii(inode)->ii_hinode[0 + bindex].hi_inode;
++ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
++ return h_inode;
++}
+
-+ err = PTR_ERR(inode);
-+ if (!wh_dentry)
-+ goto out;
++/* todo: hard/soft set? */
++void au_hiput(struct au_hinode *hinode)
++{
++ au_hn_free(hinode);
++ dput(hinode->hi_whdentry);
++ iput(hinode->hi_inode);
++}
+
-+ /* revert */
-+ /* dir inode is locked */
-+ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent);
-+ rerr = PTR_ERR(wh);
-+ if (IS_ERR(wh)) {
-+ AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
-+ AuDLNPair(dentry), err, rerr);
-+ err = -EIO;
-+ } else
-+ dput(wh);
++unsigned int au_hi_flags(struct inode *inode, int isdir)
++{
++ unsigned int flags;
++ const unsigned int mnt_flags = au_mntflags(inode->i_sb);
+
-+ out:
-+ return err;
++ flags = 0;
++ if (au_opt_test(mnt_flags, XINO))
++ au_fset_hi(flags, XINO);
++ if (isdir && au_opt_test(mnt_flags, UDBA_HNOTIFY))
++ au_fset_hi(flags, HNOTIFY);
++ return flags;
+}
+
-+/*
-+ * simple tests for the adding inode operations.
-+ * following the checks in vfs, plus the parent-child relationship.
-+ */
-+int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
-+ struct dentry *h_parent, int isdir)
++void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
++ struct inode *h_inode, unsigned int flags)
+{
-+ int err;
-+ umode_t h_mode;
-+ struct dentry *h_dentry;
-+ struct inode *h_inode;
++ struct au_hinode *hinode;
++ struct inode *hi;
++ struct au_iinfo *iinfo = au_ii(inode);
+
-+ h_dentry = au_h_dptr(dentry, bindex);
-+ h_inode = h_dentry->d_inode;
-+ if (!dentry->d_inode) {
-+ err = -EEXIST;
-+ if (unlikely(h_inode))
-+ goto out;
-+ } else {
-+ /* rename(2) case */
-+ err = -EIO;
-+ if (unlikely(!h_inode || !h_inode->i_nlink))
-+ goto out;
++ IiMustWriteLock(inode);
+
-+ h_mode = h_inode->i_mode;
-+ if (!isdir) {
-+ err = -EISDIR;
-+ if (unlikely(S_ISDIR(h_mode)))
-+ goto out;
-+ } else if (unlikely(!S_ISDIR(h_mode))) {
-+ err = -ENOTDIR;
-+ goto out;
++ hinode = iinfo->ii_hinode + bindex;
++ hi = hinode->hi_inode;
++ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
++
++ if (hi)
++ au_hiput(hinode);
++ hinode->hi_inode = h_inode;
++ if (h_inode) {
++ int err;
++ struct super_block *sb = inode->i_sb;
++ struct au_branch *br;
++
++ if (bindex == iinfo->ii_bstart)
++ au_cpup_igen(inode, h_inode);
++ br = au_sbr(sb, bindex);
++ hinode->hi_id = br->br_id;
++ if (au_ftest_hi(flags, XINO)) {
++ err = au_xino_write(sb, bindex, h_inode->i_ino,
++ inode->i_ino);
++ if (unlikely(err))
++ AuIOErr1("failed au_xino_write() %d\n", err);
++ }
++
++ if (au_ftest_hi(flags, HNOTIFY)
++ && au_br_hnotifyable(br->br_perm)) {
++ err = au_hn_alloc(hinode, inode, h_inode);
++ if (unlikely(err))
++ AuIOErr1("au_hn_alloc() %d\n", err);
+ }
+ }
++}
+
-+ err = -EIO;
-+ /* expected parent dir is locked */
-+ if (unlikely(h_parent != h_dentry->d_parent))
-+ goto out;
-+ err = 0;
++void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex,
++ struct dentry *h_wh)
++{
++ struct au_hinode *hinode;
+
-+ out:
-+ return err;
++ IiMustWriteLock(inode);
++
++ hinode = au_ii(inode)->ii_hinode + bindex;
++ AuDebugOn(hinode->hi_whdentry);
++ hinode->hi_whdentry = h_wh;
+}
+
-+/*
-+ * initial procedure of adding a new entry.
-+ * prepare writable branch and the parent dir, lock it,
-+ * and lookup whiteout for the new entry.
-+ */
-+static struct dentry*
-+lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
-+ struct dentry *src_dentry, struct au_pin *pin,
-+ struct au_wr_dir_args *wr_dir_args)
++void au_update_iigen(struct inode *inode)
+{
-+ struct dentry *wh_dentry, *h_parent;
-+ struct super_block *sb;
-+ struct au_branch *br;
-+ int err;
-+ unsigned int udba;
-+ aufs_bindex_t bcpup;
++ atomic_set(&au_ii(inode)->ii_generation, au_sigen(inode->i_sb));
++ /* smp_mb(); */ /* atomic_set */
++}
+
-+ err = au_wr_dir(dentry, src_dentry, wr_dir_args);
-+ bcpup = err;
-+ wh_dentry = ERR_PTR(err);
-+ if (unlikely(err < 0))
-+ goto out;
++/* it may be called at remount time, too */
++void au_update_ibrange(struct inode *inode, int do_put_zero)
++{
++ struct au_iinfo *iinfo;
+
-+ sb = dentry->d_sb;
-+ udba = au_opt_udba(sb);
-+ err = au_pin(pin, dentry, bcpup, udba,
-+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
-+ wh_dentry = ERR_PTR(err);
-+ if (unlikely(err))
-+ goto out;
++ iinfo = au_ii(inode);
++ if (!iinfo || iinfo->ii_bstart < 0)
++ return;
+
-+ h_parent = au_pinned_h_parent(pin);
-+ if (udba != AuOpt_UDBA_NONE
-+ && au_dbstart(dentry) == bcpup) {
-+ err = au_may_add(dentry, bcpup, h_parent,
-+ au_ftest_wrdir(wr_dir_args->flags, ISDIR));
-+ wh_dentry = ERR_PTR(err);
-+ if (unlikely(err))
-+ goto out_unpin;
-+ }
++ IiMustWriteLock(inode);
+
-+ br = au_sbr(sb, bcpup);
-+ if (dt) {
-+ struct path tmp = {
-+ .dentry = h_parent,
-+ .mnt = br->br_mnt
-+ };
-+ au_dtime_store(dt, au_pinned_parent(pin), &tmp);
-+ }
++ if (do_put_zero) {
++ aufs_bindex_t bindex;
+
-+ wh_dentry = NULL;
-+ if (bcpup != au_dbwh(dentry))
-+ goto out; /* success */
++ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend;
++ bindex++) {
++ struct inode *h_i;
+
-+ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br);
++ h_i = iinfo->ii_hinode[0 + bindex].hi_inode;
++ if (h_i && !h_i->i_nlink)
++ au_set_h_iptr(inode, bindex, NULL, 0);
++ }
++ }
+
-+ out_unpin:
-+ if (IS_ERR(wh_dentry))
-+ au_unpin(pin);
-+ out:
-+ return wh_dentry;
++ iinfo->ii_bstart = -1;
++ while (++iinfo->ii_bstart <= iinfo->ii_bend)
++ if (iinfo->ii_hinode[0 + iinfo->ii_bstart].hi_inode)
++ break;
++ if (iinfo->ii_bstart > iinfo->ii_bend) {
++ iinfo->ii_bstart = -1;
++ iinfo->ii_bend = -1;
++ return;
++ }
++
++ iinfo->ii_bend++;
++ while (0 <= --iinfo->ii_bend)
++ if (iinfo->ii_hinode[0 + iinfo->ii_bend].hi_inode)
++ break;
++ AuDebugOn(iinfo->ii_bstart > iinfo->ii_bend || iinfo->ii_bend < 0);
+}
+
+/* ---------------------------------------------------------------------- */
+
-+enum { Mknod, Symlink, Creat };
-+struct simple_arg {
-+ int type;
-+ union {
-+ struct {
-+ int mode;
-+ struct nameidata *nd;
-+ } c;
-+ struct {
-+ const char *symname;
-+ } s;
-+ struct {
-+ int mode;
-+ dev_t dev;
-+ } m;
-+ } u;
-+};
-+
-+static int add_simple(struct inode *dir, struct dentry *dentry,
-+ struct simple_arg *arg)
++void au_icntnr_init_once(void *_c)
+{
-+ int err;
-+ aufs_bindex_t bstart;
-+ unsigned char created;
-+ struct au_dtime dt;
-+ struct au_pin pin;
-+ struct path h_path;
-+ struct dentry *wh_dentry, *parent;
-+ struct inode *h_dir;
-+ struct au_wr_dir_args wr_dir_args = {
-+ .force_btgt = -1,
-+ .flags = AuWrDir_ADD_ENTRY
-+ };
++ struct au_icntnr *c = _c;
++ struct au_iinfo *iinfo = &c->iinfo;
+
-+ IMustLock(dir);
++ au_rw_init(&iinfo->ii_rwsem);
++ inode_init_once(&c->vfs_inode);
++}
+
-+ parent = dentry->d_parent; /* dir inode is locked */
-+ aufs_read_lock(dentry, AuLock_DW);
-+ di_write_lock_parent(parent);
-+ 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;
++int au_iinfo_init(struct inode *inode)
++{
++ struct au_iinfo *iinfo;
++ struct super_block *sb;
++ int nbr, i;
+
-+ bstart = au_dbstart(dentry);
-+ h_path.dentry = au_h_dptr(dentry, bstart);
-+ h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart);
-+ h_dir = au_pinned_h_dir(&pin);
-+ switch (arg->type) {
-+ case Creat:
-+ err = vfsub_create(h_dir, &h_path, arg->u.c.mode);
-+ break;
-+ case Symlink:
-+ err = vfsub_symlink(h_dir, &h_path, arg->u.s.symname);
-+ break;
-+ case Mknod:
-+ err = vfsub_mknod(h_dir, &h_path, arg->u.m.mode, arg->u.m.dev);
-+ break;
-+ default:
-+ BUG();
-+ }
-+ created = !err;
-+ if (!err)
-+ err = epilog(dir, bstart, wh_dentry, dentry);
++ sb = inode->i_sb;
++ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo);
++ nbr = au_sbend(sb) + 1;
++ if (unlikely(nbr <= 0))
++ nbr = 1;
++ 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;
+
-+ /* revert */
-+ if (unlikely(created && err && h_path.dentry->d_inode)) {
-+ int rerr;
-+ rerr = vfsub_unlink(h_dir, &h_path, /*force*/0);
-+ if (rerr) {
-+ AuIOErr("%.*s revert failure(%d, %d)\n",
-+ AuDLNPair(dentry), err, rerr);
-+ err = -EIO;
-+ }
-+ au_dtime_revert(&dt);
-+ d_drop(dentry);
++ atomic_set(&iinfo->ii_generation, au_sigen(sb));
++ /* smp_mb(); */ /* atomic_set */
++ iinfo->ii_bstart = -1;
++ iinfo->ii_bend = -1;
++ iinfo->ii_vdir = NULL;
++ return 0;
+ }
++ return -ENOMEM;
++}
+
-+ au_unpin(&pin);
-+ dput(wh_dentry);
++int au_ii_realloc(struct au_iinfo *iinfo, int nbr)
++{
++ int err, sz;
++ struct au_hinode *hip;
+
-+ out:
-+ if (unlikely(err)) {
-+ au_update_dbstart(dentry);
-+ d_drop(dentry);
++ AuRwMustWriteLock(&iinfo->ii_rwsem);
++
++ err = -ENOMEM;
++ sz = sizeof(*hip) * (iinfo->ii_bend + 1);
++ if (!sz)
++ sz = sizeof(*hip);
++ hip = au_kzrealloc(iinfo->ii_hinode, sz, sizeof(*hip) * nbr, GFP_NOFS);
++ if (hip) {
++ iinfo->ii_hinode = hip;
++ err = 0;
+ }
-+ di_write_unlock(parent);
-+ aufs_read_unlock(dentry, AuLock_DW);
++
+ return err;
+}
+
-+int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
++static int au_iinfo_write0(struct super_block *sb, struct au_hinode *hinode,
++ ino_t ino)
+{
-+ struct simple_arg arg = {
-+ .type = Mknod,
-+ .u.m = {
-+ .mode = mode,
-+ .dev = dev
-+ }
-+ };
-+ return add_simple(dir, dentry, &arg);
-+}
++ int err;
++ aufs_bindex_t bindex;
++ unsigned char locked;
+
-+int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
-+{
-+ struct simple_arg arg = {
-+ .type = Symlink,
-+ .u.s.symname = symname
-+ };
-+ return add_simple(dir, dentry, &arg);
++ err = 0;
++ locked = !!si_noflush_read_trylock(sb);
++ bindex = au_br_index(sb, hinode->hi_id);
++ if (bindex >= 0)
++ err = au_xino_write0(sb, bindex, hinode->hi_inode->i_ino, ino);
++ /* error action? */
++ if (locked)
++ si_read_unlock(sb);
++ return err;
+}
+
-+int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
-+ struct nameidata *nd)
++void au_iinfo_fin(struct inode *inode)
+{
-+ struct simple_arg arg = {
-+ .type = Creat,
-+ .u.c = {
-+ .mode = mode,
-+ .nd = nd
-+ }
-+ };
-+ return add_simple(dir, dentry, &arg);
-+}
-+
-+/* ---------------------------------------------------------------------- */
++ ino_t ino;
++ aufs_bindex_t bend;
++ unsigned char unlinked = !inode->i_nlink;
++ struct au_iinfo *iinfo;
++ struct au_hinode *hi;
++ struct super_block *sb;
+
-+struct au_link_args {
-+ aufs_bindex_t bdst, bsrc;
-+ struct au_pin pin;
-+ struct path h_path;
-+ struct dentry *src_parent, *parent;
-+};
++ if (unlinked) {
++ int err = au_xigen_inc(inode);
++ if (unlikely(err))
++ AuWarn1("failed resetting i_generation, %d\n", err);
++ }
+
-+static int au_cpup_before_link(struct dentry *src_dentry,
-+ struct au_link_args *a)
-+{
-+ int err;
-+ struct dentry *h_src_dentry;
-+ struct mutex *h_mtx;
++ iinfo = au_ii(inode);
++ /* bad_inode case */
++ if (!iinfo)
++ return;
+
-+ di_read_lock_parent(a->src_parent, AuLock_IR);
-+ err = au_test_and_cpup_dirs(src_dentry, a->bdst);
-+ if (unlikely(err))
-+ goto out;
++ if (iinfo->ii_vdir)
++ au_vdir_free(iinfo->ii_vdir);
+
-+ h_src_dentry = au_h_dptr(src_dentry, a->bsrc);
-+ h_mtx = &h_src_dentry->d_inode->i_mutex;
-+ err = au_pin(&a->pin, src_dentry, a->bdst,
-+ au_opt_udba(src_dentry->d_sb),
-+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
-+ if (unlikely(err))
-+ goto out;
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ err = au_sio_cpup_simple(src_dentry, a->bdst, -1,
-+ AuCpup_DTIME /* | AuCpup_KEEPLINO */);
-+ mutex_unlock(h_mtx);
-+ au_unpin(&a->pin);
++ if (iinfo->ii_bstart >= 0) {
++ sb = inode->i_sb;
++ ino = 0;
++ if (unlinked)
++ ino = inode->i_ino;
++ hi = iinfo->ii_hinode + iinfo->ii_bstart;
++ bend = iinfo->ii_bend;
++ while (iinfo->ii_bstart++ <= bend) {
++ if (hi->hi_inode) {
++ if (unlinked || !hi->hi_inode->i_nlink) {
++ au_iinfo_write0(sb, hi, ino);
++ /* ignore this error */
++ ino = 0;
++ }
++ au_hiput(hi);
++ }
++ hi++;
++ }
++ }
+
-+ out:
-+ di_read_unlock(a->src_parent, AuLock_IR);
-+ return err;
++ kfree(iinfo->ii_hinode);
++ AuRwDestroy(&iinfo->ii_rwsem);
+}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/inode.c linux-2.6.34/fs/aufs/inode.c
+--- linux-2.6.34.org/fs/aufs/inode.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/inode.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,416 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
+
-+static int au_cpup_or_link(struct dentry *src_dentry, struct au_link_args *a)
-+{
-+ int err;
-+ unsigned char plink;
-+ struct inode *h_inode, *inode;
-+ struct dentry *h_src_dentry;
-+ struct super_block *sb;
-+
-+ plink = 0;
-+ h_inode = NULL;
-+ sb = src_dentry->d_sb;
-+ inode = src_dentry->d_inode;
-+ if (au_ibstart(inode) <= a->bdst)
-+ h_inode = au_h_iptr(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_path.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_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_src_dentry = d_find_alias(h_inode);
-+ if (!h_src_dentry && au_plink_test(inode)) {
-+ plink = 1;
-+ h_src_dentry = au_plink_lkup(inode, a->bdst);
-+ err = PTR_ERR(h_src_dentry);
-+ if (IS_ERR(h_src_dentry))
-+ goto out;
++/*
++ * inode functions
++ */
+
-+ if (unlikely(!h_src_dentry->d_inode)) {
-+ dput(h_src_dentry);
-+ h_src_dentry = NULL;
-+ }
++#include "aufs.h"
+
-+ }
-+ if (h_src_dentry) {
-+ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
-+ &a->h_path);
-+ dput(h_src_dentry);
-+ } else {
-+ AuIOErr("no dentry found for hi%lu on b%d\n",
-+ h_inode->i_ino, a->bdst);
-+ err = -EIO;
-+ }
++struct inode *au_igrab(struct inode *inode)
++{
++ if (inode) {
++ AuDebugOn(!atomic_read(&inode->i_count));
++ atomic_inc_return(&inode->i_count);
+ }
++ return inode;
++}
+
-+ if (!err && !plink)
-+ au_plink_append(inode, a->bdst, a->h_path.dentry);
-+
-+out:
-+ return err;
++static void au_refresh_hinode_attr(struct inode *inode, int do_version)
++{
++ au_cpup_attr_all(inode, /*force*/0);
++ au_update_iigen(inode);
++ if (do_version)
++ inode->i_version++;
+}
+
-+int aufs_link(struct dentry *src_dentry, struct inode *dir,
-+ struct dentry *dentry)
++int au_refresh_hinode_self(struct inode *inode, int do_attr)
+{
-+ int err, rerr;
-+ struct au_dtime dt;
-+ struct au_link_args *a;
-+ struct dentry *wh_dentry, *h_src_dentry;
-+ struct inode *inode;
++ int err, e;
++ aufs_bindex_t bindex, new_bindex;
++ unsigned char update;
++ struct au_hinode *p, *q, tmp;
+ struct super_block *sb;
-+ struct au_wr_dir_args wr_dir_args = {
-+ /* .force_btgt = -1, */
-+ .flags = AuWrDir_ADD_ENTRY
-+ };
-+
-+ IMustLock(dir);
-+ inode = src_dentry->d_inode;
-+ IMustLock(inode);
++ struct au_iinfo *iinfo;
+
-+ err = -ENOENT;
-+ if (unlikely(!inode->i_nlink))
-+ goto out;
++ IiMustWriteLock(inode);
+
-+ err = -ENOMEM;
-+ a = kzalloc(sizeof(*a), GFP_NOFS);
-+ if (unlikely(!a))
++ update = 0;
++ sb = inode->i_sb;
++ iinfo = au_ii(inode);
++ err = au_ii_realloc(iinfo, au_sbend(sb) + 1);
++ if (unlikely(err))
+ goto out;
+
-+ 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);
++ p = iinfo->ii_hinode + iinfo->ii_bstart;
++ err = 0;
++ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend;
++ bindex++, p++) {
++ if (!p->hi_inode)
++ continue;
+
-+ 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, &a->pin,
-+ &wr_dir_args);
-+ err = PTR_ERR(wh_dentry);
-+ if (IS_ERR(wh_dentry))
-+ goto out_unlock;
++ new_bindex = au_br_index(sb, p->hi_id);
++ if (new_bindex == bindex)
++ continue;
+
-+ err = 0;
-+ sb = dentry->d_sb;
-+ a->bdst = au_dbstart(dentry);
-+ a->h_path.dentry = au_h_dptr(dentry, a->bdst);
-+ a->h_path.mnt = au_sbr_mnt(sb, a->bdst);
-+ a->bsrc = au_dbstart(src_dentry);
-+ if (au_opt_test(au_mntflags(sb), PLINK)) {
-+ if (a->bdst < a->bsrc
-+ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */)
-+ err = au_cpup_or_link(src_dentry, a);
-+ else {
-+ h_src_dentry = au_h_dptr(src_dentry, a->bdst);
-+ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
-+ &a->h_path);
-+ }
-+ } else {
-+ /*
-+ * copyup src_dentry to the branch we process,
-+ * and then link(2) to it.
-+ */
-+ if (a->bdst < a->bsrc
-+ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) {
-+ au_unpin(&a->pin);
-+ di_write_unlock(a->parent);
-+ err = au_cpup_before_link(src_dentry, a);
-+ di_write_lock_parent(a->parent);
-+ if (!err)
-+ err = au_pin(&a->pin, dentry, a->bdst,
-+ au_opt_udba(sb),
-+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
-+ if (unlikely(err))
-+ goto out_wh;
++ if (new_bindex < 0) {
++ update = 1;
++ au_hiput(p);
++ p->hi_inode = NULL;
++ continue;
+ }
-+ if (!err) {
-+ h_src_dentry = au_h_dptr(src_dentry, a->bdst);
-+ err = -ENOENT;
-+ if (h_src_dentry && h_src_dentry->d_inode)
-+ err = vfsub_link(h_src_dentry,
-+ au_pinned_h_dir(&a->pin),
-+ &a->h_path);
++
++ if (new_bindex < iinfo->ii_bstart)
++ iinfo->ii_bstart = new_bindex;
++ if (iinfo->ii_bend < new_bindex)
++ iinfo->ii_bend = new_bindex;
++ /* swap two lower inode, and loop again */
++ q = iinfo->ii_hinode + new_bindex;
++ tmp = *q;
++ *q = *p;
++ *p = tmp;
++ if (tmp.hi_inode) {
++ bindex--;
++ p--;
+ }
+ }
-+ if (unlikely(err))
-+ goto out_unpin;
++ au_update_ibrange(inode, /*do_put_zero*/0);
++ e = au_dy_irefresh(inode);
++ if (unlikely(e && !err))
++ err = e;
++ if (do_attr)
++ au_refresh_hinode_attr(inode, update && S_ISDIR(inode->i_mode));
+
-+ if (wh_dentry) {
-+ a->h_path.dentry = wh_dentry;
-+ err = au_wh_unlink_dentry(au_pinned_h_dir(&a->pin), &a->h_path,
-+ dentry);
-+ if (unlikely(err))
-+ goto out_revert;
-+ }
++ out:
++ return err;
++}
+
-+ dir->i_version++;
-+ if (au_ibstart(dir) == au_dbstart(dentry))
-+ au_cpup_attr_timesizes(dir);
-+ inc_nlink(inode);
-+ inode->i_ctime = dir->i_ctime;
-+ if (!d_unhashed(a->h_path.dentry))
-+ d_instantiate(dentry, au_igrab(inode));
-+ else
-+ /* some filesystem calls d_drop() */
-+ d_drop(dentry);
-+ goto out_unpin; /* success */
++int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
++{
++ int err, e;
++ unsigned int flags;
++ aufs_bindex_t bindex, bend;
++ unsigned char isdir, update;
++ struct au_hinode *p;
++ struct au_iinfo *iinfo;
+
-+ out_revert:
-+ rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), &a->h_path, /*force*/0);
-+ if (!rerr)
-+ goto out_dt;
-+ AuIOErr("%.*s reverting failed(%d, %d)\n",
-+ AuDLNPair(dentry), err, rerr);
-+ err = -EIO;
-+ out_dt:
-+ d_drop(dentry);
-+ au_dtime_revert(&dt);
-+ out_unpin:
-+ au_unpin(&a->pin);
-+ out_wh:
-+ dput(wh_dentry);
-+ out_unlock:
-+ if (unlikely(err)) {
-+ au_update_dbstart(dentry);
-+ d_drop(dentry);
++ err = au_refresh_hinode_self(inode, /*do_attr*/0);
++ if (unlikely(err))
++ goto out;
++
++ update = 0;
++ iinfo = au_ii(inode);
++ p = iinfo->ii_hinode + iinfo->ii_bstart;
++ isdir = S_ISDIR(inode->i_mode);
++ flags = au_hi_flags(inode, isdir);
++ bend = au_dbend(dentry);
++ for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) {
++ struct inode *h_i;
++ struct dentry *h_d;
++
++ h_d = au_h_dptr(dentry, bindex);
++ if (!h_d || !h_d->d_inode)
++ continue;
++
++ if (iinfo->ii_bstart <= bindex && bindex <= iinfo->ii_bend) {
++ h_i = au_h_iptr(inode, bindex);
++ if (h_i) {
++ if (h_i == h_d->d_inode)
++ continue;
++ err = -EIO;
++ break;
++ }
++ }
++ if (bindex < iinfo->ii_bstart)
++ iinfo->ii_bstart = bindex;
++ if (iinfo->ii_bend < bindex)
++ iinfo->ii_bend = bindex;
++ au_set_h_iptr(inode, bindex, au_igrab(h_d->d_inode), flags);
++ update = 1;
+ }
-+ di_write_unlock(a->parent);
-+ dput(a->src_parent);
-+ aufs_read_and_write_unlock2(dentry, src_dentry);
-+ kfree(a);
++ au_update_ibrange(inode, /*do_put_zero*/0);
++ e = au_dy_irefresh(inode);
++ if (unlikely(e && !err))
++ err = e;
++ au_refresh_hinode_attr(inode, update && isdir);
++
+ out:
++ AuTraceErr(err);
+ return err;
+}
+
-+int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
++static int set_inode(struct inode *inode, struct dentry *dentry)
+{
-+ int err, rerr;
-+ aufs_bindex_t bindex;
-+ unsigned char diropq;
-+ struct path h_path;
-+ struct dentry *wh_dentry, *parent, *opq_dentry;
-+ struct mutex *h_mtx;
-+ struct super_block *sb;
-+ struct {
-+ struct au_pin pin;
-+ struct au_dtime dt;
-+ } *a; /* reduce the stack usage */
-+ struct au_wr_dir_args wr_dir_args = {
-+ .force_btgt = -1,
-+ .flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR
-+ };
++ int err;
++ unsigned int flags;
++ umode_t mode;
++ aufs_bindex_t bindex, bstart, btail;
++ unsigned char isdir;
++ struct dentry *h_dentry;
++ struct inode *h_inode;
++ struct au_iinfo *iinfo;
+
-+ IMustLock(dir);
++ IiMustWriteLock(inode);
+
-+ err = -ENOMEM;
-+ a = kmalloc(sizeof(*a), GFP_NOFS);
-+ if (unlikely(!a))
++ err = 0;
++ isdir = 0;
++ bstart = au_dbstart(dentry);
++ h_inode = au_h_dptr(dentry, bstart)->d_inode;
++ mode = h_inode->i_mode;
++ switch (mode & S_IFMT) {
++ case S_IFREG:
++ btail = au_dbtail(dentry);
++ inode->i_op = &aufs_iop;
++ inode->i_fop = &aufs_file_fop;
++ err = au_dy_iaop(inode, bstart, h_inode);
++ if (unlikely(err))
++ goto out;
++ break;
++ case S_IFDIR:
++ isdir = 1;
++ btail = au_dbtaildir(dentry);
++ inode->i_op = &aufs_dir_iop;
++ inode->i_fop = &aufs_dir_fop;
++ break;
++ case S_IFLNK:
++ btail = au_dbtail(dentry);
++ inode->i_op = &aufs_symlink_iop;
++ break;
++ case S_IFBLK:
++ case S_IFCHR:
++ case S_IFIFO:
++ case S_IFSOCK:
++ btail = au_dbtail(dentry);
++ inode->i_op = &aufs_iop;
++ au_init_special_fop(inode, mode, h_inode->i_rdev);
++ break;
++ default:
++ AuIOErr("Unknown file type 0%o\n", mode);
++ err = -EIO;
+ goto out;
++ }
+
-+ aufs_read_lock(dentry, AuLock_DW);
-+ parent = dentry->d_parent; /* dir inode is locked */
-+ di_write_lock_parent(parent);
-+ wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL,
-+ &a->pin, &wr_dir_args);
-+ err = PTR_ERR(wh_dentry);
-+ if (IS_ERR(wh_dentry))
-+ goto out_free;
++ /* do not set hnotify for whiteouted dirs (SHWH mode) */
++ flags = au_hi_flags(inode, isdir);
++ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)
++ && au_ftest_hi(flags, HNOTIFY)
++ && dentry->d_name.len > AUFS_WH_PFX_LEN
++ && !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))
++ au_fclr_hi(flags, HNOTIFY);
++ iinfo = au_ii(inode);
++ iinfo->ii_bstart = bstart;
++ iinfo->ii_bend = btail;
++ for (bindex = bstart; bindex <= btail; bindex++) {
++ h_dentry = au_h_dptr(dentry, bindex);
++ if (h_dentry)
++ au_set_h_iptr(inode, bindex,
++ au_igrab(h_dentry->d_inode), flags);
++ }
++ au_cpup_attr_all(inode, /*force*/1);
+
-+ sb = dentry->d_sb;
-+ bindex = au_dbstart(dentry);
-+ h_path.dentry = au_h_dptr(dentry, bindex);
-+ h_path.mnt = au_sbr_mnt(sb, bindex);
-+ err = vfsub_mkdir(au_pinned_h_dir(&a->pin), &h_path, mode);
-+ if (unlikely(err))
-+ goto out_unlock;
++ out:
++ return err;
++}
+
-+ /* make the dir opaque */
-+ diropq = 0;
-+ h_mtx = &h_path.dentry->d_inode->i_mutex;
-+ if (wh_dentry
-+ || au_opt_test(au_mntflags(sb), ALWAYS_DIROPQ)) {
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ opq_dentry = au_diropq_create(dentry, bindex);
-+ mutex_unlock(h_mtx);
-+ err = PTR_ERR(opq_dentry);
-+ if (IS_ERR(opq_dentry))
-+ goto out_dir;
-+ dput(opq_dentry);
-+ diropq = 1;
-+ }
++/* successful returns with iinfo write_locked */
++static int reval_inode(struct inode *inode, struct dentry *dentry, int *matched)
++{
++ int err;
++ aufs_bindex_t bindex, bend;
++ struct inode *h_inode, *h_dinode;
+
-+ err = epilog(dir, bindex, wh_dentry, dentry);
-+ if (!err) {
-+ inc_nlink(dir);
-+ goto out_unlock; /* success */
-+ }
++ *matched = 0;
+
-+ /* revert */
-+ if (diropq) {
-+ AuLabel(revert opq);
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ rerr = au_diropq_remove(dentry, bindex);
-+ mutex_unlock(h_mtx);
-+ if (rerr) {
-+ AuIOErr("%.*s reverting diropq failed(%d, %d)\n",
-+ AuDLNPair(dentry), err, rerr);
-+ err = -EIO;
++ /*
++ * before this function, if aufs got any iinfo lock, it must be only
++ * one, the parent dir.
++ * it can happen by UDBA and the obsoleted inode number.
++ */
++ err = -EIO;
++ if (unlikely(inode->i_ino == parent_ino(dentry)))
++ goto out;
++
++ err = 0;
++ ii_write_lock_new_child(inode);
++ h_dinode = au_h_dptr(dentry, au_dbstart(dentry))->d_inode;
++ bend = au_ibend(inode);
++ for (bindex = au_ibstart(inode); bindex <= bend; bindex++) {
++ h_inode = au_h_iptr(inode, bindex);
++ if (h_inode && h_inode == h_dinode) {
++ *matched = 1;
++ err = 0;
++ if (au_iigen(inode) != au_digen(dentry))
++ err = au_refresh_hinode(inode, dentry);
++ break;
+ }
+ }
+
-+ out_dir:
-+ AuLabel(revert dir);
-+ rerr = vfsub_rmdir(au_pinned_h_dir(&a->pin), &h_path);
-+ if (rerr) {
-+ AuIOErr("%.*s reverting dir failed(%d, %d)\n",
-+ AuDLNPair(dentry), err, rerr);
-+ err = -EIO;
-+ }
-+ d_drop(dentry);
-+ au_dtime_revert(&a->dt);
-+ out_unlock:
-+ au_unpin(&a->pin);
-+ dput(wh_dentry);
-+ out_free:
-+ if (unlikely(err)) {
-+ au_update_dbstart(dentry);
-+ d_drop(dentry);
-+ }
-+ di_write_unlock(parent);
-+ aufs_read_unlock(dentry, AuLock_DW);
-+ kfree(a);
++ if (unlikely(err))
++ ii_write_unlock(inode);
+ out:
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/i_op_del.c aufs2-2.6.git/fs/aufs/i_op_del.c
---- linux-2.6.31/fs/aufs/i_op_del.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/i_op_del.c 2009-09-21 21:49:23.401607657 +0000
-@@ -0,0 +1,468 @@
-+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
-+ *
-+ * This program, aufs is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation; either version 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-+ */
-+
-+/*
-+ * inode operations (del entry)
-+ */
+
-+#include "aufs.h"
-+
-+/*
-+ * decide if a new whiteout for @dentry is necessary or not.
-+ * when it is necessary, prepare the parent dir for the upper branch whose
-+ * branch index is @bcpup for creation. the actual creation of the whiteout will
-+ * be done by caller.
-+ * return value:
-+ * 0: wh is unnecessary
-+ * plus: wh is necessary
-+ * minus: error
-+ */
-+int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup)
++int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
++ unsigned int d_type, ino_t *ino)
+{
-+ int need_wh, err;
-+ aufs_bindex_t bstart;
-+ struct super_block *sb;
++ int err;
++ struct mutex *mtx;
++ const int isdir = (d_type == DT_DIR);
+
-+ sb = dentry->d_sb;
-+ bstart = au_dbstart(dentry);
-+ if (*bcpup < 0) {
-+ *bcpup = bstart;
-+ if (au_test_ro(sb, bstart, dentry->d_inode)) {
-+ err = AuWbrCopyup(au_sbi(sb), dentry);
-+ *bcpup = err;
-+ if (unlikely(err < 0))
-+ goto out;
-+ }
-+ } else
-+ AuDebugOn(bstart < *bcpup
-+ || au_test_ro(sb, *bcpup, dentry->d_inode));
-+ AuDbg("bcpup %d, bstart %d\n", *bcpup, bstart);
++ /* 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, ino);
++ if (unlikely(err))
++ goto out;
+
-+ if (*bcpup != bstart) {
-+ err = au_cpup_dirs(dentry, *bcpup);
-+ if (unlikely(err))
++ if (!*ino) {
++ err = -EIO;
++ *ino = au_xino_new_ino(sb);
++ if (unlikely(!*ino))
+ goto out;
-+ need_wh = 1;
-+ } else {
-+ aufs_bindex_t old_bend, new_bend, bdiropq = -1;
-+
-+ old_bend = au_dbend(dentry);
-+ if (isdir) {
-+ bdiropq = au_dbdiropq(dentry);
-+ au_set_dbdiropq(dentry, -1);
-+ }
-+ need_wh = au_lkup_dentry(dentry, bstart + 1, /*type*/0,
-+ /*nd*/NULL);
-+ err = need_wh;
-+ if (isdir)
-+ au_set_dbdiropq(dentry, bdiropq);
-+ if (unlikely(err < 0))
++ err = au_xino_write(sb, bindex, h_ino, *ino);
++ if (unlikely(err))
+ goto out;
-+ new_bend = au_dbend(dentry);
-+ if (!need_wh && old_bend != new_bend) {
-+ au_set_h_dptr(dentry, new_bend, NULL);
-+ au_set_dbend(dentry, old_bend);
-+ }
+ }
-+ AuDbg("need_wh %d\n", need_wh);
-+ err = need_wh;
+
+ out:
++ if (!isdir)
++ mutex_unlock(mtx);
+ return err;
+}
+
-+/*
-+ * simple tests for the del-entry operations.
-+ * following the checks in vfs, plus the parent-child relationship.
-+ */
-+int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
-+ struct dentry *h_parent, int isdir)
++/* successful returns with iinfo write_locked */
++/* todo: return with unlocked? */
++struct inode *au_new_inode(struct dentry *dentry, int must_new)
+{
-+ int err;
-+ umode_t h_mode;
-+ struct dentry *h_dentry, *h_latest;
-+ struct inode *h_inode;
-+
-+ h_dentry = au_h_dptr(dentry, bindex);
-+ h_inode = h_dentry->d_inode;
-+ if (dentry->d_inode) {
-+ err = -ENOENT;
-+ if (unlikely(!h_inode || !h_inode->i_nlink))
-+ goto out;
++ struct inode *inode;
++ struct dentry *h_dentry;
++ struct super_block *sb;
++ ino_t h_ino, ino;
++ int err, match;
++ aufs_bindex_t bstart;
+
-+ h_mode = h_inode->i_mode;
-+ if (!isdir) {
-+ err = -EISDIR;
-+ if (unlikely(S_ISDIR(h_mode)))
-+ goto out;
-+ } else if (unlikely(!S_ISDIR(h_mode))) {
-+ err = -ENOTDIR;
++ sb = dentry->d_sb;
++ bstart = au_dbstart(dentry);
++ h_dentry = au_h_dptr(dentry, bstart);
++ h_ino = h_dentry->d_inode->i_ino;
++ err = au_xino_read(sb, bstart, h_ino, &ino);
++ inode = ERR_PTR(err);
++ if (unlikely(err))
++ goto out;
++ new_ino:
++ if (!ino) {
++ ino = au_xino_new_ino(sb);
++ if (unlikely(!ino)) {
++ inode = ERR_PTR(-EIO);
+ goto out;
+ }
-+ } else {
-+ /* rename(2) case */
-+ err = -EIO;
-+ if (unlikely(h_inode))
-+ goto out;
+ }
+
-+ err = -ENOENT;
-+ /* expected parent dir is locked */
-+ if (unlikely(h_parent != h_dentry->d_parent))
++ AuDbg("i%lu\n", (unsigned long)ino);
++ inode = au_iget_locked(sb, ino);
++ err = PTR_ERR(inode);
++ if (IS_ERR(inode))
+ goto out;
-+ err = 0;
+
-+ /*
-+ * rmdir a dir may break the consistency on some filesystem.
-+ * let's try heavy test.
-+ */
-+ err = -EACCES;
-+ if (unlikely(au_test_h_perm(h_parent->d_inode, MAY_EXEC | MAY_WRITE)))
-+ goto out;
++ AuDbg("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW));
++ if (inode->i_state & I_NEW) {
++ ii_write_lock_new_child(inode);
++ err = set_inode(inode, dentry);
++ if (!err) {
++ unlock_new_inode(inode);
++ goto out; /* success */
++ }
+
-+ h_latest = au_sio_lkup_one(&dentry->d_name, h_parent,
-+ au_sbr(dentry->d_sb, bindex));
-+ err = -EIO;
-+ if (IS_ERR(h_latest))
-+ goto out;
-+ if (h_latest == h_dentry)
-+ err = 0;
-+ dput(h_latest);
++ ii_write_unlock(inode);
++ iget_failed(inode);
++ goto out_err;
++ } else if (!must_new) {
++ err = reval_inode(inode, dentry, &match);
++ if (!err)
++ goto out; /* success */
++ else if (match)
++ goto out_iput;
++ }
++
++ if (unlikely(au_test_fs_unique_ino(h_dentry->d_inode)))
++ AuWarn1("Warning: 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),
++ (unsigned long)h_ino, (unsigned long)ino);
++ ino = 0;
++ err = au_xino_write(sb, bstart, h_ino, /*ino*/0);
++ if (!err) {
++ iput(inode);
++ goto new_ino;
++ }
+
++ out_iput:
++ iput(inode);
++ out_err:
++ inode = ERR_PTR(err);
+ out:
-+ return err;
++ return inode;
+}
+
-+/*
-+ * decide the branch where we operate for @dentry. the branch index will be set
-+ * @rbcpup. after diciding it, 'pin' it and store the timestamps of the parent
-+ * dir for reverting.
-+ * when a new whiteout is necessary, create it.
-+ */
-+static struct dentry*
-+lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
-+ struct au_dtime *dt, struct au_pin *pin)
-+{
-+ struct dentry *wh_dentry;
-+ struct super_block *sb;
-+ struct path h_path;
-+ int err, need_wh;
-+ unsigned int udba;
-+ aufs_bindex_t bcpup;
++/* ---------------------------------------------------------------------- */
+
-+ need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup);
-+ wh_dentry = ERR_PTR(need_wh);
-+ if (unlikely(need_wh < 0))
-+ goto out;
++int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
++ struct inode *inode)
++{
++ int err;
+
-+ sb = dentry->d_sb;
-+ udba = au_opt_udba(sb);
-+ bcpup = *rbcpup;
-+ err = au_pin(pin, dentry, bcpup, udba,
-+ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
-+ wh_dentry = ERR_PTR(err);
-+ if (unlikely(err))
-+ goto out;
++ err = au_br_rdonly(au_sbr(sb, bindex));
+
-+ h_path.dentry = au_pinned_h_parent(pin);
-+ if (udba != AuOpt_UDBA_NONE
-+ && au_dbstart(dentry) == bcpup) {
-+ err = au_may_del(dentry, bcpup, h_path.dentry, isdir);
-+ wh_dentry = ERR_PTR(err);
-+ if (unlikely(err))
-+ goto out_unpin;
++ /* pseudo-link after flushed may happen out of bounds */
++ if (!err
++ && inode
++ && au_ibstart(inode) <= bindex
++ && bindex <= au_ibend(inode)) {
++ /*
++ * permission check is unnecessary since vfsub routine
++ * will be called later
++ */
++ struct inode *hi = au_h_iptr(inode, bindex);
++ if (hi)
++ err = IS_IMMUTABLE(hi) ? -EROFS : 0;
+ }
+
-+ h_path.mnt = au_sbr_mnt(sb, bcpup);
-+ au_dtime_store(dt, au_pinned_parent(pin), &h_path);
-+ wh_dentry = NULL;
-+ if (!need_wh)
-+ goto out; /* success, no need to create whiteout */
++ return err;
++}
+
-+ wh_dentry = au_wh_create(dentry, bcpup, h_path.dentry);
-+ if (!IS_ERR(wh_dentry))
-+ goto out; /* success */
-+ /* returns with the parent is locked and wh_dentry is dget-ed */
++int au_test_h_perm(struct inode *h_inode, int mask)
++{
++ if (!current_fsuid())
++ return 0;
++ return inode_permission(h_inode, mask);
++}
+
-+ out_unpin:
-+ au_unpin(pin);
-+ out:
-+ return wh_dentry;
++int au_test_h_perm_sio(struct inode *h_inode, int mask)
++{
++ if (au_test_nfs(h_inode->i_sb)
++ && (mask & MAY_WRITE)
++ && S_ISDIR(h_inode->i_mode))
++ mask |= MAY_READ; /* force permission check */
++ return au_test_h_perm(h_inode, mask);
+}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/inode.h linux-2.6.34/fs/aufs/inode.h
+--- linux-2.6.34.org/fs/aufs/inode.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/inode.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,498 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
+
+/*
-+ * when removing a dir, rename it to a unique temporary whiteout-ed name first
-+ * in order to be revertible and save time for removing many child whiteouts
-+ * under the dir.
-+ * returns 1 when there are too many child whiteout and caller should remove
-+ * them asynchronously. returns 0 when the number of children is enough small to
-+ * remove now or the branch fs is a remote fs.
-+ * otherwise return an error.
++ * inode operations
+ */
-+static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex,
-+ struct au_nhash *whlist, struct inode *dir)
-+{
-+ int rmdir_later, err, dirwh;
-+ struct dentry *h_dentry;
-+ struct super_block *sb;
+
-+ sb = dentry->d_sb;
-+ SiMustAnyLock(sb);
-+ h_dentry = au_h_dptr(dentry, bindex);
-+ err = au_whtmp_ren(h_dentry, au_sbr(sb, bindex));
-+ if (unlikely(err))
-+ goto out;
++#ifndef __AUFS_INODE_H__
++#define __AUFS_INODE_H__
+
-+ /* stop monitoring */
-+ au_hin_free(au_hi(dentry->d_inode, bindex));
++#ifdef __KERNEL__
+
-+ if (!au_test_fs_remote(h_dentry->d_sb)) {
-+ dirwh = au_sbi(sb)->si_dirwh;
-+ rmdir_later = (dirwh <= 1);
-+ if (!rmdir_later)
-+ rmdir_later = au_nhash_test_longer_wh(whlist, bindex,
-+ dirwh);
-+ if (rmdir_later)
-+ return rmdir_later;
-+ }
++#include <linux/fs.h>
++#include <linux/fsnotify.h>
++#include <linux/aufs_type.h>
++#include "rwsem.h"
+
-+ 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);
-+ err = 0;
-+ }
++struct vfsmount;
+
-+ out:
-+ return err;
-+}
++struct au_hnotify {
++#ifdef CONFIG_AUFS_HNOTIFY
++#ifdef CONFIG_AUFS_HFSNOTIFY
++ struct fsnotify_mark_entry hn_entry;
++#else
++ struct inotify_watch hn_watch;
++#endif
++ struct inode *hn_aufs_inode; /* no get/put */
++#endif
++} ____cacheline_aligned_in_smp;
+
-+/*
-+ * final procedure for deleting a entry.
-+ * maintain dentry and iattr.
-+ */
-+static void epilog(struct inode *dir, struct dentry *dentry,
-+ aufs_bindex_t bindex)
-+{
-+ struct inode *inode;
++struct au_hinode {
++ struct inode *hi_inode;
++ aufs_bindex_t hi_id;
++#ifdef CONFIG_AUFS_HNOTIFY
++ struct au_hnotify *hi_notify;
++#endif
+
-+ inode = dentry->d_inode;
-+ d_drop(dentry);
-+ inode->i_ctime = dir->i_ctime;
++ /* reference to the copied-up whiteout with get/put */
++ struct dentry *hi_whdentry;
++};
+
-+ if (atomic_read(&dentry->d_count) == 1) {
-+ au_set_h_dptr(dentry, au_dbstart(dentry), NULL);
-+ au_update_dbstart(dentry);
-+ }
-+ if (au_ibstart(dir) == bindex)
-+ au_cpup_attr_timesizes(dir);
-+ dir->i_version++;
-+}
++struct au_vdir;
++struct au_iinfo {
++ atomic_t ii_generation;
++ struct super_block *ii_hsb1; /* no get/put */
+
-+/*
-+ * when an error happened, remove the created whiteout and revert everything.
-+ */
-+static int do_revert(int err, struct inode *dir, aufs_bindex_t bwh,
-+ struct dentry *wh_dentry, struct dentry *dentry,
-+ struct au_dtime *dt)
-+{
-+ int rerr;
-+ struct path h_path = {
-+ .dentry = wh_dentry,
-+ .mnt = au_sbr_mnt(dir->i_sb, bwh)
-+ };
++ 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;
++};
+
-+ rerr = au_wh_unlink_dentry(au_h_iptr(dir, bwh), &h_path, dentry);
-+ if (!rerr) {
-+ au_set_dbwh(dentry, bwh);
-+ au_dtime_revert(dt);
-+ return 0;
-+ }
++struct au_icntnr {
++ struct au_iinfo iinfo;
++ struct inode vfs_inode;
++} ____cacheline_aligned_in_smp;
+
-+ AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
-+ AuDLNPair(dentry), err, rerr);
-+ return -EIO;
-+}
++/* au_pin flags */
++#define AuPin_DI_LOCKED 1
++#define AuPin_MNT_WRITE (1 << 1)
++#define au_ftest_pin(flags, name) ((flags) & AuPin_##name)
++#define au_fset_pin(flags, name) { (flags) |= AuPin_##name; }
++#define au_fclr_pin(flags, name) { (flags) &= ~AuPin_##name; }
++
++struct au_pin {
++ /* input */
++ struct dentry *dentry;
++ unsigned int udba;
++ unsigned char lsc_di, lsc_hi, flags;
++ aufs_bindex_t bindex;
++
++ /* output */
++ struct dentry *parent;
++ struct au_hinode *hdir;
++ struct vfsmount *h_mnt;
++};
+
+/* ---------------------------------------------------------------------- */
+
-+int aufs_unlink(struct inode *dir, struct dentry *dentry)
++static inline struct au_iinfo *au_ii(struct inode *inode)
+{
-+ int err;
-+ aufs_bindex_t bwh, bindex, bstart;
-+ struct au_dtime dt;
-+ struct au_pin pin;
-+ struct path h_path;
-+ struct inode *inode, *h_dir;
-+ struct dentry *parent, *wh_dentry;
++ struct au_iinfo *iinfo;
+
-+ IMustLock(dir);
-+ inode = dentry->d_inode;
-+ if (unlikely(!inode))
-+ return -ENOENT; /* possible? */
-+ IMustLock(inode);
++ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo);
++ if (iinfo->ii_hinode)
++ return iinfo;
++ return NULL; /* debugging bad_inode case */
++}
+
-+ aufs_read_lock(dentry, AuLock_DW);
-+ parent = dentry->d_parent; /* dir inode is locked */
-+ di_write_lock_parent(parent);
++/* ---------------------------------------------------------------------- */
+
-+ bstart = au_dbstart(dentry);
-+ bwh = au_dbwh(dentry);
-+ bindex = -1;
-+ wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt, &pin);
-+ err = PTR_ERR(wh_dentry);
-+ if (IS_ERR(wh_dentry))
-+ goto out;
++/* inode.c */
++struct inode *au_igrab(struct inode *inode);
++int au_refresh_hinode_self(struct inode *inode, int do_attr);
++int au_refresh_hinode(struct inode *inode, struct dentry *dentry);
++int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
++ unsigned int d_type, ino_t *ino);
++struct inode *au_new_inode(struct dentry *dentry, int must_new);
++int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
++ struct inode *inode);
++int au_test_h_perm(struct inode *h_inode, int mask);
++int au_test_h_perm_sio(struct inode *h_inode, int mask);
+
-+ h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart);
-+ h_path.dentry = au_h_dptr(dentry, bstart);
-+ dget(h_path.dentry);
-+ if (bindex == bstart) {
-+ h_dir = au_pinned_h_dir(&pin);
-+ err = vfsub_unlink(h_dir, &h_path, /*force*/0);
-+ } else {
-+ /* dir inode is locked */
-+ h_dir = wh_dentry->d_parent->d_inode;
-+ IMustLock(h_dir);
-+ err = 0;
-+ }
++static inline int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex,
++ ino_t h_ino, unsigned int d_type, ino_t *ino)
++{
++#ifdef CONFIG_AUFS_SHWH
++ return au_ino(sb, bindex, h_ino, d_type, ino);
++#else
++ return 0;
++#endif
++}
+
-+ if (!err) {
-+ drop_nlink(inode);
-+ epilog(dir, dentry, bindex);
++/* i_op.c */
++extern struct inode_operations aufs_iop, aufs_symlink_iop, aufs_dir_iop;
+
-+ /* update target timestamps */
-+ if (bindex == bstart) {
-+ vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/
-+ inode->i_ctime = h_path.dentry->d_inode->i_ctime;
-+ } else
-+ /* todo: this timestamp may be reverted later */
-+ inode->i_ctime = h_dir->i_ctime;
-+ goto out_unlock; /* success */
-+ }
++/* au_wr_dir flags */
++#define AuWrDir_ADD_ENTRY 1
++#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; }
+
-+ /* revert */
-+ if (wh_dentry) {
-+ int rerr;
-+
-+ rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt);
-+ if (rerr)
-+ err = rerr;
-+ }
++struct au_wr_dir_args {
++ aufs_bindex_t force_btgt;
++ unsigned char flags;
++};
++int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
++ struct au_wr_dir_args *args);
+
-+ out_unlock:
-+ au_unpin(&pin);
-+ dput(wh_dentry);
-+ dput(h_path.dentry);
-+ out:
-+ di_write_unlock(parent);
-+ aufs_read_unlock(dentry, AuLock_DW);
-+ return err;
-+}
++struct dentry *au_pinned_h_parent(struct au_pin *pin);
++void au_pin_init(struct au_pin *pin, struct dentry *dentry,
++ aufs_bindex_t bindex, int lsc_di, int lsc_hi,
++ unsigned int udba, unsigned char flags);
++int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex,
++ unsigned int udba, unsigned char flags) __must_check;
++int au_do_pin(struct au_pin *pin) __must_check;
++void au_unpin(struct au_pin *pin);
+
-+int aufs_rmdir(struct inode *dir, struct dentry *dentry)
-+{
-+ int err, rmdir_later;
-+ aufs_bindex_t bwh, bindex, bstart;
-+ struct au_dtime dt;
-+ struct au_pin pin;
-+ struct inode *inode;
-+ struct dentry *parent, *wh_dentry, *h_dentry;
-+ struct au_whtmp_rmdir *args;
++/* i_op_add.c */
++int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
++ struct dentry *h_parent, int isdir);
++int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev);
++int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname);
++int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
++ struct nameidata *nd);
++int aufs_link(struct dentry *src_dentry, struct inode *dir,
++ struct dentry *dentry);
++int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode);
+
-+ IMustLock(dir);
-+ inode = dentry->d_inode;
-+ err = -ENOENT; /* possible? */
-+ if (unlikely(!inode))
-+ goto out;
-+ IMustLock(inode);
++/* i_op_del.c */
++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);
++int aufs_unlink(struct inode *dir, struct dentry *dentry);
++int aufs_rmdir(struct inode *dir, struct dentry *dentry);
+
-+ aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH);
-+ err = -ENOMEM;
-+ args = au_whtmp_rmdir_alloc(dir->i_sb, GFP_NOFS);
-+ if (unlikely(!args))
-+ goto out_unlock;
++/* i_op_ren.c */
++int au_wbr(struct dentry *dentry, aufs_bindex_t btgt);
++int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
++ struct inode *dir, struct dentry *dentry);
+
-+ parent = dentry->d_parent; /* dir inode is locked */
-+ di_write_lock_parent(parent);
-+ err = au_test_empty(dentry, &args->whlist);
-+ if (unlikely(err))
-+ goto out_args;
++/* iinfo.c */
++struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex);
++void au_hiput(struct au_hinode *hinode);
++void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex,
++ struct dentry *h_wh);
++unsigned int au_hi_flags(struct inode *inode, int isdir);
+
-+ bstart = au_dbstart(dentry);
-+ bwh = au_dbwh(dentry);
-+ bindex = -1;
-+ 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;
++/* hinode flags */
++#define AuHi_XINO 1
++#define AuHi_HNOTIFY (1 << 1)
++#define au_ftest_hi(flags, name) ((flags) & AuHi_##name)
++#define au_fset_hi(flags, name) { (flags) |= AuHi_##name; }
++#define au_fclr_hi(flags, name) { (flags) &= ~AuHi_##name; }
+
-+ h_dentry = au_h_dptr(dentry, bstart);
-+ dget(h_dentry);
-+ rmdir_later = 0;
-+ if (bindex == bstart) {
-+ err = renwh_and_rmdir(dentry, bstart, &args->whlist, dir);
-+ if (err > 0) {
-+ rmdir_later = err;
-+ err = 0;
-+ }
-+ } else {
-+ /* stop monitoring */
-+ au_hin_free(au_hi(inode, bstart));
++#ifndef CONFIG_AUFS_HNOTIFY
++#undef AuHi_HNOTIFY
++#define AuHi_HNOTIFY 0
++#endif
+
-+ /* dir inode is locked */
-+ IMustLock(wh_dentry->d_parent->d_inode);
-+ err = 0;
-+ }
++void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
++ struct inode *h_inode, unsigned int flags);
+
-+ if (!err) {
-+ clear_nlink(inode);
-+ au_set_dbdiropq(dentry, -1);
-+ epilog(dir, dentry, bindex);
++void au_update_iigen(struct inode *inode);
++void au_update_ibrange(struct inode *inode, int do_put_zero);
+
-+ if (rmdir_later) {
-+ au_whtmp_kick_rmdir(dir, bstart, h_dentry, args);
-+ args = NULL;
-+ }
++void au_icntnr_init_once(void *_c);
++int au_iinfo_init(struct inode *inode);
++void au_iinfo_fin(struct inode *inode);
++int au_ii_realloc(struct au_iinfo *iinfo, int nbr);
+
-+ goto out_unpin; /* success */
-+ }
++/* plink.c */
++void au_plink_maint_block(struct super_block *sb);
++void au_plink_maint_leave(struct file *file);
++#ifdef CONFIG_AUFS_DEBUG
++void au_plink_list(struct super_block *sb);
++#else
++AuStubVoid(au_plink_list, struct super_block *sb)
++#endif
++int au_plink_test(struct inode *inode);
++struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex);
++void au_plink_append(struct inode *inode, aufs_bindex_t bindex,
++ struct dentry *h_dentry);
++void au_plink_put(struct super_block *sb);
++void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id);
++long au_plink_ioctl(struct file *file, unsigned int cmd);
+
-+ /* revert */
-+ AuLabel(revert);
-+ if (wh_dentry) {
-+ int rerr;
++/* ---------------------------------------------------------------------- */
+
-+ rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt);
-+ if (rerr)
-+ err = rerr;
-+ }
++/* lock subclass for iinfo */
++enum {
++ AuLsc_II_CHILD, /* child first */
++ AuLsc_II_CHILD2, /* rename(2), link(2), and cpup at hnotify */
++ AuLsc_II_CHILD3, /* copyup dirs */
++ AuLsc_II_PARENT, /* see AuLsc_I_PARENT in vfsub.h */
++ AuLsc_II_PARENT2,
++ AuLsc_II_PARENT3, /* copyup dirs */
++ AuLsc_II_NEW_CHILD
++};
+
-+ out_unpin:
-+ au_unpin(&pin);
-+ dput(wh_dentry);
-+ dput(h_dentry);
-+ out_args:
-+ di_write_unlock(parent);
-+ if (args)
-+ au_whtmp_rmdir_free(args);
-+ out_unlock:
-+ aufs_read_unlock(dentry, AuLock_DW);
-+ out:
-+ return err;
-+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/i_op_ren.c aufs2-2.6.git/fs/aufs/i_op_ren.c
---- linux-2.6.31/fs/aufs/i_op_ren.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/i_op_ren.c 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,957 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
-+ *
-+ * This program, aufs is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation; either version 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ * ii_read_lock_child, ii_write_lock_child,
++ * ii_read_lock_child2, ii_write_lock_child2,
++ * ii_read_lock_child3, ii_write_lock_child3,
++ * ii_read_lock_parent, ii_write_lock_parent,
++ * ii_read_lock_parent2, ii_write_lock_parent2,
++ * ii_read_lock_parent3, ii_write_lock_parent3,
++ * ii_read_lock_new_child, ii_write_lock_new_child,
+ */
++#define AuReadLockFunc(name, lsc) \
++static inline void ii_read_lock_##name(struct inode *i) \
++{ \
++ au_rw_read_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \
++}
++
++#define AuWriteLockFunc(name, lsc) \
++static inline void ii_write_lock_##name(struct inode *i) \
++{ \
++ au_rw_write_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \
++}
++
++#define AuRWLockFuncs(name, lsc) \
++ AuReadLockFunc(name, lsc) \
++ AuWriteLockFunc(name, lsc)
++
++AuRWLockFuncs(child, CHILD);
++AuRWLockFuncs(child2, CHILD2);
++AuRWLockFuncs(child3, CHILD3);
++AuRWLockFuncs(parent, PARENT);
++AuRWLockFuncs(parent2, PARENT2);
++AuRWLockFuncs(parent3, PARENT3);
++AuRWLockFuncs(new_child, NEW_CHILD);
++
++#undef AuReadLockFunc
++#undef AuWriteLockFunc
++#undef AuRWLockFuncs
+
+/*
-+ * inode operation (rename entry)
-+ * todo: this is crazy monster
++ * ii_read_unlock, ii_write_unlock, ii_downgrade_lock
+ */
++AuSimpleUnlockRwsemFuncs(ii, struct inode *i, &au_ii(i)->ii_rwsem);
+
-+#include "aufs.h"
++#define IiMustNoWaiters(i) AuRwMustNoWaiters(&au_ii(i)->ii_rwsem)
++#define IiMustAnyLock(i) AuRwMustAnyLock(&au_ii(i)->ii_rwsem)
++#define IiMustWriteLock(i) AuRwMustWriteLock(&au_ii(i)->ii_rwsem)
+
-+enum { AuSRC, AuDST, AuSrcDst };
-+enum { AuPARENT, AuCHILD, AuParentChild };
++/* ---------------------------------------------------------------------- */
+
-+#define AuRen_ISDIR 1
-+#define AuRen_ISSAMEDIR (1 << 1)
-+#define AuRen_WHSRC (1 << 2)
-+#define AuRen_WHDST (1 << 3)
-+#define AuRen_MNT_WRITE (1 << 4)
-+#define AuRen_DT_DSTDIR (1 << 5)
-+#define AuRen_DIROPQ (1 << 6)
-+#define AuRen_CPUP (1 << 7)
-+#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; }
++static inline unsigned int au_iigen(struct inode *inode)
++{
++ return atomic_read(&au_ii(inode)->ii_generation);
++}
+
-+struct au_ren_args {
-+ struct {
-+ struct dentry *dentry, *h_dentry, *parent, *h_parent,
-+ *wh_dentry;
-+ struct inode *dir, *inode;
-+ struct au_hinode *hdir;
-+ struct au_dtime dt[AuParentChild];
-+ aufs_bindex_t bstart;
-+ } sd[AuSrcDst];
++/* tiny test for inode number */
++/* tmpfs generation is too rough */
++static inline int au_test_higen(struct inode *inode, struct inode *h_inode)
++{
++ struct au_iinfo *iinfo;
+
-+#define src_dentry sd[AuSRC].dentry
-+#define src_dir sd[AuSRC].dir
-+#define src_inode sd[AuSRC].inode
-+#define src_h_dentry sd[AuSRC].h_dentry
-+#define src_parent sd[AuSRC].parent
-+#define src_h_parent sd[AuSRC].h_parent
-+#define src_wh_dentry sd[AuSRC].wh_dentry
-+#define src_hdir sd[AuSRC].hdir
-+#define src_h_dir sd[AuSRC].hdir->hi_inode
-+#define src_dt sd[AuSRC].dt
-+#define src_bstart sd[AuSRC].bstart
++ iinfo = au_ii(inode);
++ AuRwMustAnyLock(&iinfo->ii_rwsem);
++ return !(iinfo->ii_hsb1 == h_inode->i_sb
++ && iinfo->ii_higen == h_inode->i_generation);
++}
+
-+#define dst_dentry sd[AuDST].dentry
-+#define dst_dir sd[AuDST].dir
-+#define dst_inode sd[AuDST].inode
-+#define dst_h_dentry sd[AuDST].h_dentry
-+#define dst_parent sd[AuDST].parent
-+#define dst_h_parent sd[AuDST].h_parent
-+#define dst_wh_dentry sd[AuDST].wh_dentry
-+#define dst_hdir sd[AuDST].hdir
-+#define dst_h_dir sd[AuDST].hdir->hi_inode
-+#define dst_dt sd[AuDST].dt
-+#define dst_bstart sd[AuDST].bstart
++static inline void au_iigen_dec(struct inode *inode)
++{
++#ifdef CONFIG_AUFS_HNOTIFY
++ atomic_dec_return(&au_ii(inode)->ii_generation);
++#endif
++}
+
-+ struct dentry *h_trap;
-+ struct au_branch *br;
-+ struct au_hinode *src_hinode;
-+ struct path h_path;
-+ struct au_nhash whlist;
-+ aufs_bindex_t btgt;
++/* ---------------------------------------------------------------------- */
+
-+ unsigned int flags;
++static inline aufs_bindex_t au_ii_br_id(struct inode *inode,
++ aufs_bindex_t bindex)
++{
++ IiMustAnyLock(inode);
++ return au_ii(inode)->ii_hinode[0 + bindex].hi_id;
++}
+
-+ struct au_whtmp_rmdir *thargs;
-+ struct dentry *h_dst;
-+};
++static inline aufs_bindex_t au_ibstart(struct inode *inode)
++{
++ IiMustAnyLock(inode);
++ return au_ii(inode)->ii_bstart;
++}
+
-+/* ---------------------------------------------------------------------- */
++static inline aufs_bindex_t au_ibend(struct inode *inode)
++{
++ IiMustAnyLock(inode);
++ return au_ii(inode)->ii_bend;
++}
+
-+/*
-+ * functions for reverting.
-+ * when an error happened in a single rename systemcall, we should revert
-+ * everything as if nothing happend.
-+ * we don't need to revert the copied-up/down the parent dir since they are
-+ * harmless.
-+ */
++static inline struct au_vdir *au_ivdir(struct inode *inode)
++{
++ IiMustAnyLock(inode);
++ return au_ii(inode)->ii_vdir;
++}
+
-+#define RevertFailure(fmt, args...) do { \
-+ AuIOErr("revert failure: " fmt " (%d, %d)\n", \
-+ ##args, err, rerr); \
-+ err = -EIO; \
-+} while (0)
++static inline struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex)
++{
++ IiMustAnyLock(inode);
++ return au_ii(inode)->ii_hinode[0 + bindex].hi_whdentry;
++}
+
-+static void au_ren_rev_diropq(int err, struct au_ren_args *a)
++static inline void au_set_ibstart(struct inode *inode, aufs_bindex_t bindex)
+{
-+ int rerr;
++ IiMustWriteLock(inode);
++ au_ii(inode)->ii_bstart = bindex;
++}
+
-+ au_hin_imtx_lock_nested(a->src_hinode, AuLsc_I_CHILD);
-+ rerr = au_diropq_remove(a->src_dentry, a->btgt);
-+ au_hin_imtx_unlock(a->src_hinode);
-+ if (rerr)
-+ RevertFailure("remove diropq %.*s", AuDLNPair(a->src_dentry));
++static inline void au_set_ibend(struct inode *inode, aufs_bindex_t bindex)
++{
++ IiMustWriteLock(inode);
++ au_ii(inode)->ii_bend = bindex;
+}
+
++static inline void au_set_ivdir(struct inode *inode, struct au_vdir *vdir)
++{
++ IiMustWriteLock(inode);
++ au_ii(inode)->ii_vdir = vdir;
++}
+
-+static void au_ren_rev_rename(int err, struct au_ren_args *a)
++static inline struct au_hinode *au_hi(struct inode *inode, aufs_bindex_t bindex)
+{
-+ int rerr;
++ IiMustAnyLock(inode);
++ return au_ii(inode)->ii_hinode + bindex;
++}
+
-+ a->h_path.dentry = au_lkup_one(&a->src_dentry->d_name, a->src_h_parent,
-+ a->br, /*nd*/NULL);
-+ rerr = PTR_ERR(a->h_path.dentry);
-+ if (IS_ERR(a->h_path.dentry)) {
-+ RevertFailure("au_lkup_one %.*s", AuDLNPair(a->src_dentry));
-+ return;
-+ }
++/* ---------------------------------------------------------------------- */
+
-+ rerr = vfsub_rename(a->dst_h_dir,
-+ au_h_dptr(a->src_dentry, a->btgt),
-+ a->src_h_dir, &a->h_path);
-+ d_drop(a->h_path.dentry);
-+ dput(a->h_path.dentry);
-+ /* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */
-+ if (rerr)
-+ RevertFailure("rename %.*s", AuDLNPair(a->src_dentry));
++static inline struct dentry *au_pinned_parent(struct au_pin *pin)
++{
++ if (pin)
++ return pin->parent;
++ return NULL;
+}
+
-+static void au_ren_rev_cpup(int err, struct au_ren_args *a)
++static inline struct inode *au_pinned_h_dir(struct au_pin *pin)
+{
-+ int rerr;
-+
-+ a->h_path.dentry = a->dst_h_dentry;
-+ rerr = vfsub_unlink(a->dst_h_dir, &a->h_path, /*force*/0);
-+ au_set_h_dptr(a->src_dentry, a->btgt, NULL);
-+ au_set_dbstart(a->src_dentry, a->src_bstart);
-+ if (rerr)
-+ RevertFailure("unlink %.*s", AuDLNPair(a->dst_h_dentry));
++ if (pin && pin->hdir)
++ return pin->hdir->hi_inode;
++ return NULL;
+}
+
++static inline struct au_hinode *au_pinned_hdir(struct au_pin *pin)
++{
++ if (pin)
++ return pin->hdir;
++ return NULL;
++}
+
-+static void au_ren_rev_whtmp(int err, struct au_ren_args *a)
++static inline void au_pin_set_dentry(struct au_pin *pin, struct dentry *dentry)
+{
-+ int rerr;
++ if (pin)
++ pin->dentry = dentry;
++}
+
-+ a->h_path.dentry = au_lkup_one(&a->dst_dentry->d_name, a->dst_h_parent,
-+ a->br, /*nd*/NULL);
-+ rerr = PTR_ERR(a->h_path.dentry);
-+ if (IS_ERR(a->h_path.dentry)) {
-+ RevertFailure("lookup %.*s", AuDLNPair(a->dst_dentry));
-+ return;
++static inline void au_pin_set_parent_lflag(struct au_pin *pin,
++ unsigned char lflag)
++{
++ if (pin) {
++ /* dirty macros require brackets */
++ if (lflag) {
++ au_fset_pin(pin->flags, DI_LOCKED);
++ } else {
++ au_fclr_pin(pin->flags, DI_LOCKED);
++ }
+ }
-+ if (a->h_path.dentry->d_inode) {
-+ d_drop(a->h_path.dentry);
-+ dput(a->h_path.dentry);
-+ return;
++}
++
++static inline void au_pin_set_parent(struct au_pin *pin, struct dentry *parent)
++{
++ if (pin) {
++ dput(pin->parent);
++ pin->parent = dget(parent);
+ }
++}
+
-+ rerr = vfsub_rename(a->dst_h_dir, a->h_dst, a->dst_h_dir, &a->h_path);
-+ d_drop(a->h_path.dentry);
-+ dput(a->h_path.dentry);
-+ if (!rerr) {
-+ au_set_h_dptr(a->dst_dentry, a->btgt, NULL);
-+ au_set_h_dptr(a->dst_dentry, a->btgt, dget(a->h_dst));
-+ } else
-+ RevertFailure("rename %.*s", AuDLNPair(a->h_dst));
++/* ---------------------------------------------------------------------- */
++
++#ifdef CONFIG_AUFS_HNOTIFY
++struct au_hnotify_op {
++ void (*ctl)(struct au_hinode *hinode, int do_set);
++ int (*alloc)(struct au_hnotify *hn, struct inode *h_inode);
++ void (*free)(struct au_hnotify *hn);
++
++ void (*fin)(void);
++ int (*init)(void);
++};
++
++/* hnotify.c */
++int au_hn_alloc(struct au_hinode *hinode, struct inode *inode,
++ struct inode *h_inode);
++void au_hn_free(struct au_hinode *hinode);
++void au_hn_ctl(struct au_hinode *hinode, int do_set);
++void au_hn_reset(struct inode *inode, unsigned int flags);
++int au_hnotify(struct inode *h_dir, struct au_hnotify *hnotify, u32 mask,
++ struct qstr *h_child_qstr, struct inode *h_child_inode);
++int __init au_hnotify_init(void);
++void au_hnotify_fin(void);
++
++/* hinotify.c */
++extern const struct au_hnotify_op au_hnotify_op;
++
++static inline
++void au_hn_init(struct au_hinode *hinode)
++{
++ hinode->hi_notify = NULL;
+}
+
-+static void au_ren_rev_whsrc(int err, struct au_ren_args *a)
++#else
++static inline
++int au_hn_alloc(struct au_hinode *hinode __maybe_unused,
++ struct inode *inode __maybe_unused,
++ struct inode *h_inode __maybe_unused)
+{
-+ int rerr;
++ return -EOPNOTSUPP;
++}
+
-+ a->h_path.dentry = a->src_wh_dentry;
-+ rerr = au_wh_unlink_dentry(a->src_h_dir, &a->h_path, a->src_dentry);
-+ if (rerr)
-+ RevertFailure("unlink %.*s", AuDLNPair(a->src_wh_dentry));
++AuStubVoid(au_hn_free, struct au_hinode *hinode __maybe_unused)
++AuStubVoid(au_hn_ctl, struct au_hinode *hinode __maybe_unused,
++ int do_set __maybe_unused)
++AuStubVoid(au_hn_reset, struct inode *inode __maybe_unused,
++ unsigned int flags __maybe_unused)
++AuStubInt0(__init au_hnotify_init, void)
++AuStubVoid(au_hnotify_fin, void)
++AuStubVoid(au_hn_init, struct au_hinode *hinode __maybe_unused)
++#endif /* CONFIG_AUFS_HNOTIFY */
++
++static inline void au_hn_suspend(struct au_hinode *hdir)
++{
++ au_hn_ctl(hdir, /*do_set*/0);
+}
+
-+static void au_ren_rev_drop(struct au_ren_args *a)
++static inline void au_hn_resume(struct au_hinode *hdir)
+{
-+ struct dentry *d, *h_d;
-+ int i;
-+ aufs_bindex_t bend, bindex;
++ au_hn_ctl(hdir, /*do_set*/1);
++}
+
-+ for (i = 0; i < AuSrcDst; i++) {
-+ d = a->sd[i].dentry;
-+ d_drop(d);
-+ bend = au_dbend(d);
-+ for (bindex = au_dbstart(d); bindex <= bend; bindex++) {
-+ h_d = au_h_dptr(d, bindex);
-+ if (h_d)
-+ d_drop(h_d);
-+ }
-+ }
++static inline void au_hn_imtx_lock(struct au_hinode *hdir)
++{
++ mutex_lock(&hdir->hi_inode->i_mutex);
++ au_hn_suspend(hdir);
++}
+
-+ au_update_dbstart(a->dst_dentry);
-+ if (a->thargs)
-+ d_drop(a->h_dst);
++static inline void au_hn_imtx_lock_nested(struct au_hinode *hdir,
++ unsigned int sc __maybe_unused)
++{
++ mutex_lock_nested(&hdir->hi_inode->i_mutex, sc);
++ au_hn_suspend(hdir);
+}
-+#undef RevertFailure
+
-+/* ---------------------------------------------------------------------- */
++static inline void au_hn_imtx_unlock(struct au_hinode *hdir)
++{
++ au_hn_resume(hdir);
++ mutex_unlock(&hdir->hi_inode->i_mutex);
++}
++
++#endif /* __KERNEL__ */
++#endif /* __AUFS_INODE_H__ */
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/ioctl.c linux-2.6.34/fs/aufs/ioctl.c
+--- linux-2.6.34.org/fs/aufs/ioctl.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/ioctl.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,126 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * ioctl
++ * plink-management and readdir in userspace.
++ * assist the pathconf(3) wrapper library.
++ */
++
++#include <linux/file.h>
++#include "aufs.h"
++
++static int au_wbr_fd(struct path *path)
++{
++ int err, fd;
++ aufs_bindex_t wbi, bindex, bend;
++ struct file *h_file;
++ struct super_block *sb;
++ struct dentry *root;
++ struct au_branch *wbr;
++
++ err = get_unused_fd();
++ if (unlikely(err < 0))
++ goto out;
++ fd = err;
++
++ wbi = 0;
++ sb = path->dentry->d_sb;
++ root = sb->s_root;
++ aufs_read_lock(root, AuLock_IR);
++ wbr = au_sbr(sb, wbi);
++ if (!(path->mnt->mnt_flags & MNT_READONLY)
++ && !au_br_writable(wbr->br_perm)) {
++ bend = au_sbend(sb);
++ for (bindex = 1; bindex <= bend; bindex++) {
++ wbr = au_sbr(sb, bindex);
++ if (au_br_writable(wbr->br_perm)) {
++ wbi = bindex;
++ break;
++ }
++ }
++ wbr = au_sbr(sb, wbi);
++ }
++ AuDbg("wbi %d\n", wbi);
++ h_file = au_h_open(root, wbi, O_RDONLY | O_DIRECTORY | O_LARGEFILE,
++ NULL);
++ aufs_read_unlock(root, AuLock_IR);
++ err = PTR_ERR(h_file);
++ if (IS_ERR(h_file))
++ goto out_fd;
++
++ atomic_dec(&wbr->br_count); /* cf. au_h_open() */
++ fd_install(fd, h_file);
++ err = fd;
++ goto out; /* success */
++
++ out_fd:
++ put_unused_fd(fd);
++ out:
++ return err;
++}
++
++/* ---------------------------------------------------------------------- */
++
++long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg)
++{
++ long err;
++
++ switch (cmd) {
++ case AUFS_CTL_PLINK_MAINT:
++ case AUFS_CTL_PLINK_CLEAN:
++ err = au_plink_ioctl(file, cmd);
++ break;
++
++ case AUFS_CTL_RDU:
++ case AUFS_CTL_RDU_INO:
++ err = au_rdu_ioctl(file, cmd, arg);
++ break;
++
++ case AUFS_CTL_WBR_FD:
++ err = au_wbr_fd(&file->f_path);
++ break;
++
++ default:
++ /* do not call the lower */
++ AuDbg("0x%x\n", cmd);
++ err = -ENOTTY;
++ }
++
++ AuTraceErr(err);
++ return err;
++}
++
++long aufs_ioctl_nondir(struct file *file, unsigned int cmd, unsigned long arg)
++{
++ long err;
++
++ switch (cmd) {
++ case AUFS_CTL_WBR_FD:
++ err = au_wbr_fd(&file->f_path);
++ break;
++
++ default:
++ /* do not call the lower */
++ AuDbg("0x%x\n", cmd);
++ err = -ENOTTY;
++ }
++
++ AuTraceErr(err);
++ return err;
++}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/i_op_add.c linux-2.6.34/fs/aufs/i_op_add.c
+--- linux-2.6.34.org/fs/aufs/i_op_add.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/i_op_add.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,672 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * inode operations (add entry)
++ */
++
++#include "aufs.h"
++
++/*
++ * final procedure of adding a new entry, except link(2).
++ * remove whiteout, instantiate, copyup the parent dir's times and size
++ * and update version.
++ * if it failed, re-create the removed whiteout.
++ */
++static int epilog(struct inode *dir, aufs_bindex_t bindex,
++ struct dentry *wh_dentry, struct dentry *dentry)
++{
++ int err, rerr;
++ aufs_bindex_t bwh;
++ struct path h_path;
++ struct inode *inode, *h_dir;
++ struct dentry *wh;
++
++ 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);
++ h_path.dentry = wh_dentry;
++ h_path.mnt = au_sbr_mnt(dir->i_sb, bindex);
++ err = au_wh_unlink_dentry(au_h_iptr(dir, bindex), &h_path,
++ dentry);
++ if (unlikely(err))
++ goto out;
++ }
++
++ inode = au_new_inode(dentry, /*must_new*/1);
++ if (!IS_ERR(inode)) {
++ d_instantiate(dentry, inode);
++ dir = dentry->d_parent->d_inode; /* dir inode is locked */
++ IMustLock(dir);
++ if (au_ibstart(dir) == au_dbstart(dentry))
++ au_cpup_attr_timesizes(dir);
++ dir->i_version++;
++ return 0; /* success */
++ }
++
++ err = PTR_ERR(inode);
++ if (!wh_dentry)
++ goto out;
++
++ /* revert */
++ /* dir inode is locked */
++ wh = au_wh_create(dentry, bwh, wh_dentry->d_parent);
++ rerr = PTR_ERR(wh);
++ if (IS_ERR(wh)) {
++ AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
++ AuDLNPair(dentry), err, rerr);
++ err = -EIO;
++ } else
++ dput(wh);
++
++ out:
++ return err;
++}
++
++/*
++ * simple tests for the adding inode operations.
++ * following the checks in vfs, plus the parent-child relationship.
++ */
++int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
++ struct dentry *h_parent, int isdir)
++{
++ int err;
++ umode_t h_mode;
++ struct dentry *h_dentry;
++ struct inode *h_inode;
++
++ err = -ENAMETOOLONG;
++ if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
++ goto out;
++
++ h_dentry = au_h_dptr(dentry, bindex);
++ h_inode = h_dentry->d_inode;
++ if (!dentry->d_inode) {
++ err = -EEXIST;
++ if (unlikely(h_inode))
++ goto out;
++ } else {
++ /* rename(2) case */
++ err = -EIO;
++ if (unlikely(!h_inode || !h_inode->i_nlink))
++ goto out;
++
++ h_mode = h_inode->i_mode;
++ if (!isdir) {
++ err = -EISDIR;
++ if (unlikely(S_ISDIR(h_mode)))
++ goto out;
++ } else if (unlikely(!S_ISDIR(h_mode))) {
++ err = -ENOTDIR;
++ goto out;
++ }
++ }
++
++ err = 0;
++ /* expected parent dir is locked */
++ if (unlikely(h_parent != h_dentry->d_parent))
++ err = -EIO;
++
++ out:
++ AuTraceErr(err);
++ return err;
++}
++
++/*
++ * initial procedure of adding a new entry.
++ * prepare writable branch and the parent dir, lock it,
++ * and lookup whiteout for the new entry.
++ */
++static struct dentry*
++lock_hdir_lkup_wh(struct dentry *dentry, struct au_dtime *dt,
++ struct dentry *src_dentry, struct au_pin *pin,
++ struct au_wr_dir_args *wr_dir_args)
++{
++ struct dentry *wh_dentry, *h_parent;
++ struct super_block *sb;
++ struct au_branch *br;
++ int err;
++ unsigned int udba;
++ aufs_bindex_t bcpup;
++
++ AuDbg("%.*s\n", AuDLNPair(dentry));
++
++ err = au_wr_dir(dentry, src_dentry, wr_dir_args);
++ bcpup = err;
++ wh_dentry = ERR_PTR(err);
++ if (unlikely(err < 0))
++ goto out;
++
++ sb = dentry->d_sb;
++ udba = au_opt_udba(sb);
++ err = au_pin(pin, dentry, bcpup, udba,
++ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ wh_dentry = ERR_PTR(err);
++ if (unlikely(err))
++ goto out;
++
++ h_parent = au_pinned_h_parent(pin);
++ if (udba != AuOpt_UDBA_NONE
++ && au_dbstart(dentry) == bcpup)
++ err = au_may_add(dentry, bcpup, h_parent,
++ au_ftest_wrdir(wr_dir_args->flags, ISDIR));
++ else if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
++ err = -ENAMETOOLONG;
++ wh_dentry = ERR_PTR(err);
++ if (unlikely(err))
++ goto out_unpin;
++
++ br = au_sbr(sb, bcpup);
++ if (dt) {
++ struct path tmp = {
++ .dentry = h_parent,
++ .mnt = br->br_mnt
++ };
++ au_dtime_store(dt, au_pinned_parent(pin), &tmp);
++ }
++
++ wh_dentry = NULL;
++ if (bcpup != au_dbwh(dentry))
++ goto out; /* success */
++
++ wh_dentry = au_wh_lkup(h_parent, &dentry->d_name, br);
++
++ out_unpin:
++ if (IS_ERR(wh_dentry))
++ au_unpin(pin);
++ out:
++ return wh_dentry;
++}
++
++/* ---------------------------------------------------------------------- */
++
++enum { Mknod, Symlink, Creat };
++struct simple_arg {
++ int type;
++ union {
++ struct {
++ int mode;
++ struct nameidata *nd;
++ } c;
++ struct {
++ const char *symname;
++ } s;
++ struct {
++ int mode;
++ dev_t dev;
++ } m;
++ } u;
++};
++
++static int add_simple(struct inode *dir, struct dentry *dentry,
++ struct simple_arg *arg)
++{
++ int err;
++ aufs_bindex_t bstart;
++ unsigned char created;
++ struct au_dtime dt;
++ struct au_pin pin;
++ struct path h_path;
++ struct dentry *wh_dentry, *parent;
++ struct inode *h_dir;
++ struct au_wr_dir_args wr_dir_args = {
++ .force_btgt = -1,
++ .flags = AuWrDir_ADD_ENTRY
++ };
++
++ AuDbg("%.*s\n", AuDLNPair(dentry));
++ IMustLock(dir);
++
++ parent = dentry->d_parent; /* dir inode is locked */
++ aufs_read_lock(dentry, AuLock_DW);
++ di_write_lock_parent(parent);
++ 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;
++
++ bstart = au_dbstart(dentry);
++ h_path.dentry = au_h_dptr(dentry, bstart);
++ h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart);
++ h_dir = au_pinned_h_dir(&pin);
++ switch (arg->type) {
++ case Creat:
++ err = vfsub_create(h_dir, &h_path, arg->u.c.mode);
++ break;
++ case Symlink:
++ err = vfsub_symlink(h_dir, &h_path, arg->u.s.symname);
++ break;
++ case Mknod:
++ err = vfsub_mknod(h_dir, &h_path, arg->u.m.mode, arg->u.m.dev);
++ break;
++ default:
++ BUG();
++ }
++ created = !err;
++ if (!err)
++ err = epilog(dir, bstart, wh_dentry, dentry);
++
++ /* revert */
++ if (unlikely(created && err && h_path.dentry->d_inode)) {
++ int rerr;
++ rerr = vfsub_unlink(h_dir, &h_path, /*force*/0);
++ if (rerr) {
++ AuIOErr("%.*s revert failure(%d, %d)\n",
++ AuDLNPair(dentry), err, rerr);
++ err = -EIO;
++ }
++ au_dtime_revert(&dt);
++ d_drop(dentry);
++ }
++
++ au_unpin(&pin);
++ dput(wh_dentry);
++
++ out:
++ if (unlikely(err)) {
++ au_update_dbstart(dentry);
++ d_drop(dentry);
++ }
++ di_write_unlock(parent);
++ aufs_read_unlock(dentry, AuLock_DW);
++ return err;
++}
++
++int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
++{
++ struct simple_arg arg = {
++ .type = Mknod,
++ .u.m = {
++ .mode = mode,
++ .dev = dev
++ }
++ };
++ return add_simple(dir, dentry, &arg);
++}
++
++int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname)
++{
++ struct simple_arg arg = {
++ .type = Symlink,
++ .u.s.symname = symname
++ };
++ return add_simple(dir, dentry, &arg);
++}
++
++int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
++ struct nameidata *nd)
++{
++ struct simple_arg arg = {
++ .type = Creat,
++ .u.c = {
++ .mode = mode,
++ .nd = nd
++ }
++ };
++ return add_simple(dir, dentry, &arg);
++}
++
++/* ---------------------------------------------------------------------- */
++
++struct au_link_args {
++ aufs_bindex_t bdst, bsrc;
++ struct au_pin pin;
++ struct path h_path;
++ struct dentry *src_parent, *parent;
++};
++
++static int au_cpup_before_link(struct dentry *src_dentry,
++ struct au_link_args *a)
++{
++ int err;
++ struct dentry *h_src_dentry;
++ struct mutex *h_mtx;
++ struct file *h_file;
++
++ di_read_lock_parent(a->src_parent, AuLock_IR);
++ err = au_test_and_cpup_dirs(src_dentry, a->bdst);
++ if (unlikely(err))
++ goto out;
++
++ h_src_dentry = au_h_dptr(src_dentry, a->bsrc);
++ h_mtx = &h_src_dentry->d_inode->i_mutex;
++ err = au_pin(&a->pin, src_dentry, a->bdst,
++ au_opt_udba(src_dentry->d_sb),
++ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ if (unlikely(err))
++ goto out;
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ h_file = au_h_open_pre(src_dentry, a->bsrc);
++ if (IS_ERR(h_file)) {
++ err = PTR_ERR(h_file);
++ h_file = NULL;
++ } else
++ err = au_sio_cpup_simple(src_dentry, a->bdst, a->bsrc,
++ AuCpup_DTIME /* | AuCpup_KEEPLINO */);
++ mutex_unlock(h_mtx);
++ au_h_open_post(src_dentry, a->bsrc, h_file);
++ au_unpin(&a->pin);
++
++ out:
++ di_read_unlock(a->src_parent, AuLock_IR);
++ return err;
++}
++
++static int au_cpup_or_link(struct dentry *src_dentry, struct au_link_args *a)
++{
++ int err;
++ unsigned char plink;
++ struct inode *h_inode, *inode;
++ struct dentry *h_src_dentry;
++ struct super_block *sb;
++ struct file *h_file;
++
++ plink = 0;
++ h_inode = NULL;
++ sb = src_dentry->d_sb;
++ inode = src_dentry->d_inode;
++ if (au_ibstart(inode) <= a->bdst)
++ h_inode = au_h_iptr(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_path.dentry));
++ h_inode = au_h_dptr(src_dentry, a->bsrc)->d_inode;
++ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
++ h_file = au_h_open_pre(src_dentry, a->bsrc);
++ if (IS_ERR(h_file)) {
++ err = PTR_ERR(h_file);
++ h_file = NULL;
++ } else
++ err = au_sio_cpup_single(src_dentry, a->bdst, a->bsrc,
++ -1, AuCpup_KEEPLINO,
++ a->parent);
++ mutex_unlock(&h_inode->i_mutex);
++ au_h_open_post(src_dentry, a->bsrc, h_file);
++ 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_src_dentry = d_find_alias(h_inode);
++ if (!h_src_dentry && au_plink_test(inode)) {
++ plink = 1;
++ h_src_dentry = au_plink_lkup(inode, a->bdst);
++ err = PTR_ERR(h_src_dentry);
++ if (IS_ERR(h_src_dentry))
++ goto out;
++
++ if (unlikely(!h_src_dentry->d_inode)) {
++ dput(h_src_dentry);
++ h_src_dentry = NULL;
++ }
++
++ }
++ if (h_src_dentry) {
++ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
++ &a->h_path);
++ dput(h_src_dentry);
++ } else {
++ AuIOErr("no dentry found for hi%lu on b%d\n",
++ h_inode->i_ino, a->bdst);
++ err = -EIO;
++ }
++ }
++
++ if (!err && !plink)
++ au_plink_append(inode, a->bdst, a->h_path.dentry);
++
++out:
++ return err;
++}
++
++int aufs_link(struct dentry *src_dentry, struct inode *dir,
++ struct dentry *dentry)
++{
++ int err, rerr;
++ struct au_dtime dt;
++ struct au_link_args *a;
++ struct dentry *wh_dentry, *h_src_dentry;
++ struct inode *inode;
++ struct super_block *sb;
++ struct au_wr_dir_args wr_dir_args = {
++ /* .force_btgt = -1, */
++ .flags = AuWrDir_ADD_ENTRY
++ };
++
++ IMustLock(dir);
++ inode = src_dentry->d_inode;
++ IMustLock(inode);
++
++ err = -ENOENT;
++ if (unlikely(!inode->i_nlink))
++ goto out;
++
++ err = -ENOMEM;
++ a = kzalloc(sizeof(*a), GFP_NOFS);
++ if (unlikely(!a))
++ goto out;
++
++ 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);
++ wr_dir_args.force_btgt = au_wbr(dentry, wr_dir_args.force_btgt);
++ 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_unlock;
++
++ err = 0;
++ sb = dentry->d_sb;
++ a->bdst = au_dbstart(dentry);
++ a->h_path.dentry = au_h_dptr(dentry, a->bdst);
++ a->h_path.mnt = au_sbr_mnt(sb, a->bdst);
++ a->bsrc = au_dbstart(src_dentry);
++ if (au_opt_test(au_mntflags(sb), PLINK)) {
++ if (a->bdst < a->bsrc
++ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */)
++ err = au_cpup_or_link(src_dentry, a);
++ else {
++ h_src_dentry = au_h_dptr(src_dentry, a->bdst);
++ err = vfsub_link(h_src_dentry, au_pinned_h_dir(&a->pin),
++ &a->h_path);
++ }
++ } else {
++ /*
++ * copyup src_dentry to the branch we process,
++ * and then link(2) to it.
++ */
++ if (a->bdst < a->bsrc
++ /* && h_src_dentry->d_sb != a->h_path.dentry->d_sb */) {
++ au_unpin(&a->pin);
++ di_write_unlock(a->parent);
++ err = au_cpup_before_link(src_dentry, a);
++ di_write_lock_parent(a->parent);
++ if (!err)
++ err = au_pin(&a->pin, dentry, a->bdst,
++ au_opt_udba(sb),
++ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ if (unlikely(err))
++ goto out_wh;
++ }
++ if (!err) {
++ h_src_dentry = au_h_dptr(src_dentry, a->bdst);
++ err = -ENOENT;
++ if (h_src_dentry && h_src_dentry->d_inode)
++ err = vfsub_link(h_src_dentry,
++ au_pinned_h_dir(&a->pin),
++ &a->h_path);
++ }
++ }
++ if (unlikely(err))
++ goto out_unpin;
++
++ if (wh_dentry) {
++ a->h_path.dentry = wh_dentry;
++ err = au_wh_unlink_dentry(au_pinned_h_dir(&a->pin), &a->h_path,
++ dentry);
++ if (unlikely(err))
++ goto out_revert;
++ }
++
++ dir->i_version++;
++ if (au_ibstart(dir) == au_dbstart(dentry))
++ au_cpup_attr_timesizes(dir);
++ inc_nlink(inode);
++ inode->i_ctime = dir->i_ctime;
++ if (!d_unhashed(a->h_path.dentry))
++ d_instantiate(dentry, au_igrab(inode));
++ else
++ /* some filesystem calls d_drop() */
++ d_drop(dentry);
++ goto out_unpin; /* success */
++
++ out_revert:
++ rerr = vfsub_unlink(au_pinned_h_dir(&a->pin), &a->h_path, /*force*/0);
++ if (!rerr)
++ goto out_dt;
++ AuIOErr("%.*s reverting failed(%d, %d)\n",
++ AuDLNPair(dentry), err, rerr);
++ err = -EIO;
++ out_dt:
++ d_drop(dentry);
++ au_dtime_revert(&dt);
++ out_unpin:
++ au_unpin(&a->pin);
++ out_wh:
++ dput(wh_dentry);
++ out_unlock:
++ if (unlikely(err)) {
++ au_update_dbstart(dentry);
++ d_drop(dentry);
++ }
++ di_write_unlock(a->parent);
++ dput(a->src_parent);
++ aufs_read_and_write_unlock2(dentry, src_dentry);
++ kfree(a);
++ out:
++ return err;
++}
++
++int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
++{
++ int err, rerr;
++ aufs_bindex_t bindex;
++ unsigned char diropq;
++ struct path h_path;
++ struct dentry *wh_dentry, *parent, *opq_dentry;
++ struct mutex *h_mtx;
++ struct super_block *sb;
++ struct {
++ struct au_pin pin;
++ struct au_dtime dt;
++ } *a; /* reduce the stack usage */
++ struct au_wr_dir_args wr_dir_args = {
++ .force_btgt = -1,
++ .flags = AuWrDir_ADD_ENTRY | AuWrDir_ISDIR
++ };
++
++ IMustLock(dir);
++
++ err = -ENOMEM;
++ a = kmalloc(sizeof(*a), GFP_NOFS);
++ if (unlikely(!a))
++ goto out;
++
++ aufs_read_lock(dentry, AuLock_DW);
++ parent = dentry->d_parent; /* dir inode is locked */
++ di_write_lock_parent(parent);
++ wh_dentry = lock_hdir_lkup_wh(dentry, &a->dt, /*src_dentry*/NULL,
++ &a->pin, &wr_dir_args);
++ err = PTR_ERR(wh_dentry);
++ if (IS_ERR(wh_dentry))
++ goto out_free;
++
++ sb = dentry->d_sb;
++ bindex = au_dbstart(dentry);
++ h_path.dentry = au_h_dptr(dentry, bindex);
++ h_path.mnt = au_sbr_mnt(sb, bindex);
++ err = vfsub_mkdir(au_pinned_h_dir(&a->pin), &h_path, mode);
++ if (unlikely(err))
++ goto out_unlock;
++
++ /* make the dir opaque */
++ diropq = 0;
++ h_mtx = &h_path.dentry->d_inode->i_mutex;
++ if (wh_dentry
++ || au_opt_test(au_mntflags(sb), ALWAYS_DIROPQ)) {
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ opq_dentry = au_diropq_create(dentry, bindex);
++ mutex_unlock(h_mtx);
++ err = PTR_ERR(opq_dentry);
++ if (IS_ERR(opq_dentry))
++ goto out_dir;
++ dput(opq_dentry);
++ diropq = 1;
++ }
++
++ err = epilog(dir, bindex, wh_dentry, dentry);
++ if (!err) {
++ inc_nlink(dir);
++ goto out_unlock; /* success */
++ }
++
++ /* revert */
++ if (diropq) {
++ AuLabel(revert opq);
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ rerr = au_diropq_remove(dentry, bindex);
++ mutex_unlock(h_mtx);
++ if (rerr) {
++ AuIOErr("%.*s reverting diropq failed(%d, %d)\n",
++ AuDLNPair(dentry), err, rerr);
++ err = -EIO;
++ }
++ }
++
++ out_dir:
++ AuLabel(revert dir);
++ rerr = vfsub_rmdir(au_pinned_h_dir(&a->pin), &h_path);
++ if (rerr) {
++ AuIOErr("%.*s reverting dir failed(%d, %d)\n",
++ AuDLNPair(dentry), err, rerr);
++ err = -EIO;
++ }
++ d_drop(dentry);
++ au_dtime_revert(&a->dt);
++ out_unlock:
++ au_unpin(&a->pin);
++ dput(wh_dentry);
++ out_free:
++ if (unlikely(err)) {
++ au_update_dbstart(dentry);
++ d_drop(dentry);
++ }
++ di_write_unlock(parent);
++ aufs_read_unlock(dentry, AuLock_DW);
++ kfree(a);
++ out:
++ return err;
++}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/i_op.c linux-2.6.34/fs/aufs/i_op.c
+--- linux-2.6.34.org/fs/aufs/i_op.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/i_op.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,924 @@
++/*
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
+
+/*
-+ * when we have to copyup the renaming entry, do it with the rename-target name
-+ * in order to minimize the cost (the later actual rename is unnecessary).
-+ * otherwise rename it on the target branch.
++ * inode operations (except add/del/rename)
+ */
-+static int au_ren_or_cpup(struct au_ren_args *a)
++
++#include <linux/device_cgroup.h>
++#include <linux/fs_stack.h>
++#include <linux/mm.h>
++#include <linux/namei.h>
++#include <linux/security.h>
++#include <linux/uaccess.h>
++#include "aufs.h"
++
++static int h_permission(struct inode *h_inode, int mask,
++ struct vfsmount *h_mnt, int brperm)
+{
+ int err;
-+ struct dentry *d;
++ const unsigned char write_mask = !!(mask & (MAY_WRITE | MAY_APPEND));
+
-+ d = a->src_dentry;
-+ if (au_dbstart(d) == a->btgt) {
-+ a->h_path.dentry = a->dst_h_dentry;
-+ if (au_ftest_ren(a->flags, DIROPQ)
-+ && au_dbdiropq(d) == a->btgt)
-+ au_fclr_ren(a->flags, DIROPQ);
-+ AuDebugOn(au_dbstart(d) != a->btgt);
-+ err = vfsub_rename(a->src_h_dir, au_h_dptr(d, a->btgt),
-+ a->dst_h_dir, &a->h_path);
++ err = -EACCES;
++ if ((write_mask && IS_IMMUTABLE(h_inode))
++ || ((mask & MAY_EXEC)
++ && S_ISREG(h_inode->i_mode)
++ && ((h_mnt->mnt_flags & MNT_NOEXEC)
++ || !(h_inode->i_mode & S_IXUGO))))
++ goto out;
++
++ /*
++ * - skip the lower 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.
++ */
++ 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->permission) {
++ /* AuLabel(generic_permission); */
++ err = generic_permission(h_inode, mask,
++ h_inode->i_op->check_acl);
+ } else {
-+ struct mutex *h_mtx = &a->src_h_dentry->d_inode->i_mutex;
++ /* AuLabel(h_inode->permission); */
++ err = h_inode->i_op->permission(h_inode, mask);
++ AuTraceErr(err);
++ }
+
-+ au_fset_ren(a->flags, CPUP);
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ au_set_dbstart(d, a->btgt);
-+ au_set_h_dptr(d, a->btgt, dget(a->dst_h_dentry));
-+ err = au_sio_cpup_single(d, a->btgt, a->src_bstart, -1,
-+ !AuCpup_DTIME, a->dst_parent);
-+ if (unlikely(err)) {
-+ au_set_h_dptr(d, a->btgt, NULL);
-+ au_set_dbstart(d, a->src_bstart);
-+ }
-+ mutex_unlock(h_mtx);
++ if (!err)
++ err = devcgroup_inode_permission(h_inode, mask);
++ if (!err) {
++ mask &= (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND);
++ err = security_inode_permission(h_inode, mask);
++ }
++
++#if 0
++ if (!err) {
++ /* todo: do we need to call ima_path_check()? */
++ struct path h_path = {
++ .dentry =
++ .mnt = h_mnt
++ };
++ err = ima_path_check(&h_path,
++ mask & (MAY_READ | MAY_WRITE | MAY_EXEC),
++ IMA_COUNT_LEAVE);
+ }
++#endif
+
++ out:
+ return err;
+}
+
-+/* cf. aufs_rmdir() */
-+static int au_ren_del_whtmp(struct au_ren_args *a)
++static int aufs_permission(struct inode *inode, int mask)
+{
+ int err;
-+ struct inode *dir;
++ aufs_bindex_t bindex, bend;
++ const unsigned char isdir = !!S_ISDIR(inode->i_mode),
++ write_mask = !!(mask & (MAY_WRITE | MAY_APPEND));
++ struct inode *h_inode;
++ struct super_block *sb;
++ struct au_branch *br;
+
-+ dir = a->dst_dir;
-+ SiMustAnyLock(dir->i_sb);
-+ if (!au_nhash_test_longer_wh(&a->whlist, a->btgt,
-+ au_sbi(dir->i_sb)->si_dirwh)
-+ || au_test_fs_remote(a->h_dst->d_sb)) {
-+ err = au_whtmp_rmdir(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_nhash_wh_free(&a->thargs->whlist);
-+ a->thargs->whlist = a->whlist;
-+ a->whlist.nh_num = 0;
-+ au_whtmp_kick_rmdir(dir, a->btgt, a->h_dst, a->thargs);
-+ dput(a->h_dst);
-+ a->thargs = NULL;
-+ }
++ sb = inode->i_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ ii_read_lock_child(inode);
+
-+ return 0;
-+}
++ if (!isdir || write_mask) {
++ err = au_busy_or_stale();
++ h_inode = au_h_iptr(inode, au_ibstart(inode));
++ if (unlikely(!h_inode
++ || (h_inode->i_mode & S_IFMT)
++ != (inode->i_mode & S_IFMT)))
++ goto out;
+
-+/* make it 'opaque' dir. */
-+static int au_ren_diropq(struct au_ren_args *a)
-+{
-+ int err;
-+ struct dentry *diropq;
++ err = 0;
++ bindex = au_ibstart(inode);
++ br = au_sbr(sb, bindex);
++ err = h_permission(h_inode, mask, br->br_mnt, br->br_perm);
++ if (write_mask
++ && !err
++ && !special_file(h_inode->i_mode)) {
++ /* 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;
++ }
+
++ /* non-write to dir */
+ err = 0;
-+ a->src_hinode = au_hi(a->src_inode, a->btgt);
-+ au_hin_imtx_lock_nested(a->src_hinode, AuLsc_I_CHILD);
-+ diropq = au_diropq_create(a->src_dentry, a->btgt);
-+ au_hin_imtx_unlock(a->src_hinode);
-+ if (IS_ERR(diropq))
-+ err = PTR_ERR(diropq);
-+ dput(diropq);
++ bend = au_ibend(inode);
++ for (bindex = au_ibstart(inode); !err && bindex <= bend; bindex++) {
++ h_inode = au_h_iptr(inode, bindex);
++ if (h_inode) {
++ err = au_busy_or_stale();
++ if (unlikely(!S_ISDIR(h_inode->i_mode)))
++ break;
++
++ br = au_sbr(sb, bindex);
++ err = h_permission(h_inode, mask, br->br_mnt,
++ br->br_perm);
++ }
++ }
+
++ out:
++ ii_read_unlock(inode);
++ si_read_unlock(sb);
+ return err;
+}
+
-+static int do_rename(struct au_ren_args *a)
-+{
-+ int err;
-+ struct dentry *d, *h_d;
++/* ---------------------------------------------------------------------- */
+
-+ /* prepare workqueue args for asynchronous rmdir */
-+ h_d = a->dst_h_dentry;
-+ if (au_ftest_ren(a->flags, ISDIR) && h_d->d_inode) {
-+ err = -ENOMEM;
-+ a->thargs = au_whtmp_rmdir_alloc(a->src_dentry->d_sb, GFP_NOFS);
-+ if (unlikely(!a->thargs))
-+ goto out;
-+ a->h_dst = dget(h_d);
-+ }
++static struct dentry *aufs_lookup(struct inode *dir, struct dentry *dentry,
++ struct nameidata *nd)
++{
++ struct dentry *ret, *parent;
++ struct inode *inode, *h_inode;
++ struct mutex *mtx;
++ struct super_block *sb;
++ int err, npositive;
++ aufs_bindex_t bstart;
+
-+ /* create whiteout for src_dentry */
-+ if (au_ftest_ren(a->flags, WHSRC)) {
-+ a->src_wh_dentry
-+ = au_wh_create(a->src_dentry, a->btgt, a->src_h_parent);
-+ err = PTR_ERR(a->src_wh_dentry);
-+ if (IS_ERR(a->src_wh_dentry))
-+ goto out_thargs;
-+ }
++ IMustLock(dir);
+
-+ /* lookup whiteout for dentry */
-+ if (au_ftest_ren(a->flags, WHDST)) {
-+ h_d = au_wh_lkup(a->dst_h_parent, &a->dst_dentry->d_name,
-+ a->br);
-+ err = PTR_ERR(h_d);
-+ if (IS_ERR(h_d))
-+ goto out_whsrc;
-+ if (!h_d->d_inode)
-+ dput(h_d);
-+ else
-+ a->dst_wh_dentry = h_d;
-+ }
++ sb = dir->i_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ ret = ERR_PTR(-ENAMETOOLONG);
++ if (unlikely(dentry->d_name.len > AUFS_MAX_NAMELEN))
++ goto out;
++ err = au_di_init(dentry);
++ ret = ERR_PTR(err);
++ if (unlikely(err))
++ goto out;
+
-+ /* rename dentry to tmpwh */
-+ if (a->thargs) {
-+ err = au_whtmp_ren(a->dst_h_dentry, a->br);
-+ if (unlikely(err))
-+ goto out_whdst;
++ 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, nd);
++ di_read_unlock(parent, AuLock_IR);
++ err = npositive;
++ ret = ERR_PTR(err);
++ if (unlikely(err < 0))
++ goto out_unlock;
+
-+ d = a->dst_dentry;
-+ au_set_h_dptr(d, a->btgt, NULL);
-+ err = au_lkup_neg(d, a->btgt);
-+ if (unlikely(err))
-+ goto out_whtmp;
-+ a->dst_h_dentry = au_h_dptr(d, a->btgt);
++ inode = NULL;
++ if (npositive) {
++ bstart = au_dbstart(dentry);
++ h_inode = au_h_dptr(dentry, bstart)->d_inode;
++ 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, /*must_new*/0);
++ mutex_unlock(mtx);
++ } else
++ inode = au_new_inode(dentry, /*must_new*/0);
++ ret = (void *)inode;
+ }
++ if (IS_ERR(inode))
++ goto out_unlock;
++
++ ret = d_splice_alias(inode, dentry);
++ if (unlikely(IS_ERR(ret) && inode))
++ ii_write_unlock(inode);
+
-+ /* cpup src */
-+ if (a->dst_h_dentry->d_inode && a->src_bstart != a->btgt) {
-+ struct mutex *h_mtx = &a->src_h_dentry->d_inode->i_mutex;
++ out_unlock:
++ di_write_unlock(dentry);
++ out:
++ si_read_unlock(sb);
++ return ret;
++}
+
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ err = au_sio_cpup_simple(a->src_dentry, a->btgt, -1,
-+ !AuCpup_DTIME);
-+ mutex_unlock(h_mtx);
-+ if (unlikely(err))
-+ goto out_whtmp;
-+ }
++/* ---------------------------------------------------------------------- */
+
-+ /* rename by vfs_rename or cpup */
-+ d = a->dst_dentry;
-+ if (au_ftest_ren(a->flags, ISDIR)
-+ && (a->dst_wh_dentry
-+ || au_dbdiropq(d) == a->btgt
-+ /* hide the lower to keep xino */
-+ || a->btgt < au_dbend(d)
-+ || au_opt_test(au_mntflags(d->d_sb), ALWAYS_DIROPQ)))
-+ au_fset_ren(a->flags, DIROPQ);
-+ err = au_ren_or_cpup(a);
-+ if (unlikely(err))
-+ /* leave the copied-up one */
-+ goto out_whtmp;
++static int au_wr_dir_cpup(struct dentry *dentry, struct dentry *parent,
++ const unsigned char add_entry, aufs_bindex_t bcpup,
++ aufs_bindex_t bstart)
++{
++ int err;
++ struct dentry *h_parent;
++ struct inode *h_dir;
+
-+ /* make dir opaque */
-+ if (au_ftest_ren(a->flags, DIROPQ)) {
-+ err = au_ren_diropq(a);
-+ if (unlikely(err))
-+ goto out_rename;
++ if (add_entry) {
++ au_update_dbstart(dentry);
++ IMustLock(parent->d_inode);
++ } else
++ di_write_lock_parent(parent);
++
++ err = 0;
++ if (!au_h_dptr(parent, bcpup)) {
++ if (bstart < bcpup)
++ err = au_cpdown_dirs(dentry, bcpup);
++ else
++ err = au_cpup_dirs(dentry, bcpup);
++ }
++ if (!err && add_entry) {
++ h_parent = au_h_dptr(parent, bcpup);
++ h_dir = h_parent->d_inode;
++ mutex_lock_nested(&h_dir->i_mutex, AuLsc_I_PARENT);
++ err = au_lkup_neg(dentry, bcpup);
++ /* todo: no unlock here */
++ mutex_unlock(&h_dir->i_mutex);
++ if (bstart < bcpup && au_dbstart(dentry) < 0) {
++ au_set_dbstart(dentry, 0);
++ au_update_dbrange(dentry, /*do_put_zero*/0);
++ }
+ }
+
-+ /* update target timestamps */
-+ AuDebugOn(au_dbstart(a->src_dentry) != a->btgt);
-+ a->h_path.dentry = au_h_dptr(a->src_dentry, a->btgt);
-+ vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/
-+ a->src_inode->i_ctime = a->h_path.dentry->d_inode->i_ctime;
++ if (!add_entry)
++ di_write_unlock(parent);
++ if (!err)
++ err = bcpup; /* success */
+
-+ /* remove whiteout for dentry */
-+ if (a->dst_wh_dentry) {
-+ a->h_path.dentry = a->dst_wh_dentry;
-+ err = au_wh_unlink_dentry(a->dst_h_dir, &a->h_path,
-+ a->dst_dentry);
-+ if (unlikely(err))
-+ goto out_diropq;
-+ }
++ return err;
++}
+
-+ /* remove whtmp */
-+ if (a->thargs)
-+ au_ren_del_whtmp(a); /* ignore this error */
++/*
++ * decide the branch and the parent dir where we will create a new entry.
++ * returns new bindex or an error.
++ * copyup the parent dir if needed.
++ */
++int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
++ struct au_wr_dir_args *args)
++{
++ int err;
++ aufs_bindex_t bcpup, bstart, src_bstart;
++ const unsigned char add_entry = !!au_ftest_wrdir(args->flags,
++ ADD_ENTRY);
++ struct super_block *sb;
++ struct dentry *parent;
++ struct au_sbinfo *sbinfo;
+
-+ err = 0;
-+ goto out_success;
++ sb = dentry->d_sb;
++ sbinfo = au_sbi(sb);
++ parent = dget_parent(dentry);
++ bstart = au_dbstart(dentry);
++ bcpup = bstart;
++ if (args->force_btgt < 0) {
++ if (src_dentry) {
++ src_bstart = au_dbstart(src_dentry);
++ if (src_bstart < bstart)
++ bcpup = src_bstart;
++ } else if (add_entry) {
++ err = AuWbrCreate(sbinfo, dentry,
++ au_ftest_wrdir(args->flags, ISDIR));
++ bcpup = err;
++ }
+
-+ out_diropq:
-+ if (au_ftest_ren(a->flags, DIROPQ))
-+ au_ren_rev_diropq(err, a);
-+ out_rename:
-+ if (!au_ftest_ren(a->flags, CPUP))
-+ au_ren_rev_rename(err, a);
-+ else
-+ au_ren_rev_cpup(err, a);
-+ out_whtmp:
-+ if (a->thargs)
-+ au_ren_rev_whtmp(err, a);
-+ out_whdst:
-+ dput(a->dst_wh_dentry);
-+ a->dst_wh_dentry = NULL;
-+ out_whsrc:
-+ if (a->src_wh_dentry)
-+ au_ren_rev_whsrc(err, a);
-+ au_ren_rev_drop(a);
-+ out_success:
-+ dput(a->src_wh_dentry);
-+ dput(a->dst_wh_dentry);
-+ out_thargs:
-+ if (a->thargs) {
-+ dput(a->h_dst);
-+ au_whtmp_rmdir_free(a->thargs);
-+ a->thargs = NULL;
++ if (bcpup < 0 || au_test_ro(sb, bcpup, dentry->d_inode)) {
++ if (add_entry)
++ err = AuWbrCopyup(sbinfo, dentry);
++ else {
++ if (!IS_ROOT(dentry)) {
++ di_read_lock_parent(parent, !AuLock_IR);
++ err = AuWbrCopyup(sbinfo, dentry);
++ di_read_unlock(parent, !AuLock_IR);
++ } else
++ err = AuWbrCopyup(sbinfo, dentry);
++ }
++ bcpup = err;
++ if (unlikely(err < 0))
++ goto out;
++ }
++ } else {
++ bcpup = args->force_btgt;
++ AuDebugOn(au_test_ro(sb, bcpup, dentry->d_inode));
+ }
++ AuDbg("bstart %d, bcpup %d\n", bstart, bcpup);
++ err = bcpup;
++ if (bcpup == bstart)
++ goto out; /* success */
++ else if (bstart < bcpup)
++ au_update_dbrange(dentry, /*do_put_zero*/1);
++
++ /* copyup the new parent into the branch we process */
++ err = au_wr_dir_cpup(dentry, parent, add_entry, bcpup, bstart);
++
+ out:
++ dput(parent);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+/*
-+ * test if @dentry dir can be rename destination or not.
-+ * success means, it is a logically empty dir.
-+ */
-+static int may_rename_dstdir(struct dentry *dentry, struct au_nhash *whlist)
++struct dentry *au_pinned_h_parent(struct au_pin *pin)
+{
-+ return au_test_empty(dentry, whlist);
++ if (pin && pin->parent)
++ return au_h_dptr(pin->parent, pin->bindex);
++ return NULL;
+}
+
-+/*
-+ * test if @dentry dir can be rename source or not.
-+ * if it can, return 0 and @children is filled.
-+ * success means,
-+ * - it is a logically empty dir.
-+ * - or, it exists on writable branch and has no children including whiteouts
-+ * on the lower branch.
-+ */
-+static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt)
++void au_unpin(struct au_pin *p)
+{
-+ int err;
-+ unsigned int rdhash;
-+ aufs_bindex_t bstart;
++ if (au_ftest_pin(p->flags, MNT_WRITE))
++ mnt_drop_write(p->h_mnt);
++ if (!p->hdir)
++ return;
+
-+ bstart = au_dbstart(dentry);
-+ if (bstart != btgt) {
-+ struct au_nhash whlist;
++ au_hn_imtx_unlock(p->hdir);
++ if (!au_ftest_pin(p->flags, DI_LOCKED))
++ di_read_unlock(p->parent, AuLock_IR);
++ iput(p->hdir->hi_inode);
++ dput(p->parent);
++ p->parent = NULL;
++ p->hdir = NULL;
++ p->h_mnt = NULL;
++}
+
-+ SiMustAnyLock(dentry->d_sb);
-+ rdhash = au_sbi(dentry->d_sb)->si_rdhash;
-+ if (!rdhash)
-+ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL,
-+ dentry));
-+ err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS);
-+ if (unlikely(err))
-+ goto out;
-+ err = au_test_empty(dentry, &whlist);
-+ au_nhash_wh_free(&whlist);
++int au_do_pin(struct au_pin *p)
++{
++ int err;
++ struct super_block *sb;
++ struct dentry *h_dentry, *h_parent;
++ struct au_branch *br;
++ struct inode *h_dir;
++
++ err = 0;
++ sb = p->dentry->d_sb;
++ br = au_sbr(sb, p->bindex);
++ if (IS_ROOT(p->dentry)) {
++ if (au_ftest_pin(p->flags, MNT_WRITE)) {
++ p->h_mnt = br->br_mnt;
++ err = mnt_want_write(p->h_mnt);
++ if (unlikely(err)) {
++ au_fclr_pin(p->flags, MNT_WRITE);
++ goto out_err;
++ }
++ }
+ goto out;
+ }
+
-+ if (bstart == au_dbtaildir(dentry))
-+ return 0; /* success */
-+
-+ err = au_test_empty_lower(dentry);
++ h_dentry = NULL;
++ if (p->bindex <= au_dbend(p->dentry))
++ h_dentry = au_h_dptr(p->dentry, p->bindex);
+
-+ out:
-+ if (err == -ENOTEMPTY) {
-+ AuWarn1("renaming dir who has child(ren) on multiple branches,"
-+ " is not supported\n");
-+ err = -EXDEV;
-+ }
-+ return err;
-+}
++ p->parent = dget_parent(p->dentry);
++ if (!au_ftest_pin(p->flags, DI_LOCKED))
++ di_read_lock(p->parent, AuLock_IR, p->lsc_di);
+
-+/* side effect: sets whlist and h_dentry */
-+static int au_ren_may_dir(struct au_ren_args *a)
-+{
-+ int err;
-+ unsigned int rdhash;
-+ struct dentry *d;
++ h_dir = NULL;
++ h_parent = au_h_dptr(p->parent, p->bindex);
++ p->hdir = au_hi(p->parent->d_inode, p->bindex);
++ if (p->hdir)
++ h_dir = p->hdir->hi_inode;
+
-+ d = a->dst_dentry;
-+ SiMustAnyLock(d->d_sb);
++ /* udba case */
++ if (unlikely(!p->hdir || !h_dir)) {
++ if (!au_ftest_pin(p->flags, DI_LOCKED))
++ di_read_unlock(p->parent, AuLock_IR);
++ dput(p->parent);
++ p->parent = NULL;
++ goto out_err;
++ }
+
-+ err = 0;
-+ if (au_ftest_ren(a->flags, ISDIR) && a->dst_inode) {
-+ rdhash = au_sbi(d->d_sb)->si_rdhash;
-+ if (!rdhash)
-+ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, d));
-+ err = au_nhash_alloc(&a->whlist, rdhash, GFP_NOFS);
-+ if (unlikely(err))
-+ goto out;
++ au_igrab(h_dir);
++ au_hn_imtx_lock_nested(p->hdir, p->lsc_hi);
+
-+ au_set_dbstart(d, a->dst_bstart);
-+ err = may_rename_dstdir(d, &a->whlist);
-+ au_set_dbstart(d, a->btgt);
++ if (unlikely(p->hdir->hi_inode != h_parent->d_inode)) {
++ err = -EBUSY;
++ goto out_unpin;
++ }
++ if (h_dentry) {
++ err = au_h_verify(h_dentry, p->udba, h_dir, h_parent, br);
++ if (unlikely(err)) {
++ au_fclr_pin(p->flags, MNT_WRITE);
++ goto out_unpin;
++ }
+ }
-+ a->dst_h_dentry = au_h_dptr(d, au_dbstart(d));
-+ if (unlikely(err))
-+ goto out;
+
-+ d = a->src_dentry;
-+ a->src_h_dentry = au_h_dptr(d, au_dbstart(d));
-+ if (au_ftest_ren(a->flags, ISDIR)) {
-+ err = may_rename_srcdir(d, a->btgt);
++ if (au_ftest_pin(p->flags, MNT_WRITE)) {
++ p->h_mnt = br->br_mnt;
++ err = mnt_want_write(p->h_mnt);
+ if (unlikely(err)) {
-+ au_nhash_wh_free(&a->whlist);
-+ a->whlist.nh_num = 0;
++ au_fclr_pin(p->flags, MNT_WRITE);
++ goto out_unpin;
+ }
+ }
++ goto out; /* success */
++
++ out_unpin:
++ au_unpin(p);
++ out_err:
++ pr_err("err %d\n", err);
++ err = au_busy_or_stale();
+ out:
+ return err;
+}
+
++void au_pin_init(struct au_pin *p, struct dentry *dentry,
++ aufs_bindex_t bindex, int lsc_di, int lsc_hi,
++ unsigned int udba, unsigned char flags)
++{
++ p->dentry = dentry;
++ p->udba = udba;
++ p->lsc_di = lsc_di;
++ p->lsc_hi = lsc_hi;
++ p->flags = flags;
++ p->bindex = bindex;
++
++ p->parent = NULL;
++ p->hdir = NULL;
++ p->h_mnt = NULL;
++}
++
++int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex,
++ unsigned int udba, unsigned char flags)
++{
++ au_pin_init(pin, dentry, bindex, AuLsc_DI_PARENT, AuLsc_I_PARENT2,
++ udba, flags);
++ return au_do_pin(pin);
++}
++
+/* ---------------------------------------------------------------------- */
+
+/*
-+ * simple tests for rename.
-+ * following the checks in vfs, plus the parent-child relationship.
++ * ->setattr() and ->getattr() are called in various cases.
++ * chmod, stat: dentry is revalidated.
++ * fchmod, fstat: file and dentry are not revalidated, additionally they may be
++ * unhashed.
++ * for ->setattr(), ia->ia_file is passed from ftruncate only.
+ */
-+static int au_may_ren(struct au_ren_args *a)
++static int au_reval_for_attr(struct dentry *dentry, unsigned int sigen)
+{
-+ int err, isdir;
-+ struct inode *h_inode;
-+
-+ if (a->src_bstart == a->btgt) {
-+ err = au_may_del(a->src_dentry, a->btgt, a->src_h_parent,
-+ au_ftest_ren(a->flags, ISDIR));
-+ if (unlikely(err))
-+ goto out;
-+ err = -EINVAL;
-+ if (unlikely(a->src_h_dentry == a->h_trap))
-+ goto out;
-+ }
++ int err;
++ struct inode *inode;
++ struct dentry *parent;
+
+ err = 0;
-+ if (a->dst_bstart != a->btgt)
-+ goto out;
-+
-+ err = -EIO;
-+ h_inode = a->dst_h_dentry->d_inode;
-+ isdir = !!au_ftest_ren(a->flags, ISDIR);
-+ if (!a->dst_dentry->d_inode) {
-+ if (unlikely(h_inode))
-+ goto out;
-+ err = au_may_add(a->dst_dentry, a->btgt, a->dst_h_parent,
-+ isdir);
-+ } else {
-+ if (unlikely(!h_inode || !h_inode->i_nlink))
-+ goto out;
-+ err = au_may_del(a->dst_dentry, a->btgt, a->dst_h_parent,
-+ isdir);
-+ if (unlikely(err))
-+ goto out;
-+ err = -ENOTEMPTY;
-+ if (unlikely(a->dst_h_dentry == a->h_trap))
-+ goto out;
-+ err = 0;
++ inode = dentry->d_inode;
++ if (au_digen(dentry) != sigen || au_iigen(inode) != sigen) {
++ parent = dget_parent(dentry);
++ di_read_lock_parent(parent, AuLock_IR);
++ /* returns a number of positive dentries */
++ err = au_refresh_hdentry(dentry, inode->i_mode & S_IFMT);
++ if (err >= 0)
++ err = au_refresh_hinode(inode, dentry);
++ di_read_unlock(parent, AuLock_IR);
++ dput(parent);
+ }
+
-+ out:
-+ if (unlikely(err == -ENOENT || err == -EEXIST))
-+ err = -EIO;
++ AuTraceErr(err);
+ 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()
-+ * + lower src_dir and dir by vfsub_lock_rename()
-+ * + verify the every relationships between child and parent. if any
-+ * of them failed, unlock all and return -EBUSY.
-+ */
-+static void au_ren_unlock(struct au_ren_args *a)
-+{
-+ struct super_block *sb;
++#define AuIcpup_DID_CPUP 1
++#define au_ftest_icpup(flags, name) ((flags) & AuIcpup_##name)
++#define au_fset_icpup(flags, name) { (flags) |= AuIcpup_##name; }
++#define au_fclr_icpup(flags, name) { (flags) &= ~AuIcpup_##name; }
+
-+ sb = a->dst_dentry->d_sb;
-+ if (au_ftest_ren(a->flags, MNT_WRITE))
-+ mnt_drop_write(a->br->br_mnt);
-+ vfsub_unlock_rename(a->src_h_parent, a->src_hdir,
-+ a->dst_h_parent, a->dst_hdir);
-+}
++struct au_icpup_args {
++ unsigned char flags;
++ unsigned char pin_flags;
++ aufs_bindex_t btgt;
++ unsigned int udba;
++ struct au_pin pin;
++ struct path h_path;
++ struct inode *h_inode;
++};
+
-+static int au_ren_lock(struct au_ren_args *a)
++static int au_pin_and_icpup(struct dentry *dentry, struct iattr *ia,
++ struct au_icpup_args *a)
+{
+ int err;
-+ unsigned int udba;
++ loff_t sz;
++ aufs_bindex_t bstart;
++ struct dentry *hi_wh, *parent;
++ struct inode *inode;
++ struct file *h_file;
++ struct au_wr_dir_args wr_dir_args = {
++ .force_btgt = -1,
++ .flags = 0
++ };
++
++ bstart = au_dbstart(dentry);
++ inode = dentry->d_inode;
++ if (S_ISDIR(inode->i_mode))
++ au_fset_wrdir(wr_dir_args.flags, ISDIR);
++ /* plink or hi_wh() case */
++ 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;
++ a->btgt = err;
++ if (err != bstart)
++ au_fset_icpup(a->flags, DID_CPUP);
++
++ err = 0;
++ a->pin_flags = AuPin_MNT_WRITE;
++ parent = NULL;
++ if (!IS_ROOT(dentry)) {
++ au_fset_pin(a->pin_flags, DI_LOCKED);
++ parent = dget_parent(dentry);
++ di_write_lock_parent(parent);
++ }
++
++ err = au_pin(&a->pin, dentry, a->btgt, a->udba, a->pin_flags);
++ if (unlikely(err))
++ goto out_parent;
++
++ a->h_path.dentry = au_h_dptr(dentry, bstart);
++ a->h_inode = a->h_path.dentry->d_inode;
++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
++ sz = -1;
++ if ((ia->ia_valid & ATTR_SIZE) && ia->ia_size < i_size_read(a->h_inode))
++ sz = ia->ia_size;
++
++ h_file = NULL;
++ hi_wh = NULL;
++ if (au_ftest_icpup(a->flags, DID_CPUP) && d_unhashed(dentry)) {
++ hi_wh = au_hi_wh(inode, a->btgt);
++ if (!hi_wh) {
++ err = au_sio_cpup_wh(dentry, a->btgt, sz, /*file*/NULL);
++ if (unlikely(err))
++ goto out_unlock;
++ 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);
++ parent = NULL;
++ }
++ if (!au_ftest_icpup(a->flags, DID_CPUP))
++ goto out; /* success */
++
++ if (!d_unhashed(dentry)) {
++ h_file = au_h_open_pre(dentry, bstart);
++ if (IS_ERR(h_file)) {
++ err = PTR_ERR(h_file);
++ h_file = NULL;
++ } else
++ err = au_sio_cpup_simple(dentry, a->btgt, sz,
++ AuCpup_DTIME);
++ if (!err)
++ a->h_path.dentry = au_h_dptr(dentry, a->btgt);
++ } else if (!hi_wh)
++ a->h_path.dentry = au_h_dptr(dentry, a->btgt);
++ else
++ a->h_path.dentry = hi_wh; /* do not dget here */
+
-+ err = 0;
-+ a->src_h_parent = au_h_dptr(a->src_parent, a->btgt);
-+ a->src_hdir = au_hi(a->src_dir, a->btgt);
-+ a->dst_h_parent = au_h_dptr(a->dst_parent, a->btgt);
-+ a->dst_hdir = au_hi(a->dst_dir, a->btgt);
-+ a->h_trap = vfsub_lock_rename(a->src_h_parent, a->src_hdir,
-+ a->dst_h_parent, a->dst_hdir);
-+ udba = au_opt_udba(a->src_dentry->d_sb);
-+ if (unlikely(a->src_hdir->hi_inode != a->src_h_parent->d_inode
-+ || a->dst_hdir->hi_inode != a->dst_h_parent->d_inode))
-+ err = au_busy_or_stale();
-+ if (!err && au_dbstart(a->src_dentry) == a->btgt)
-+ err = au_h_verify(a->src_h_dentry, udba,
-+ a->src_h_parent->d_inode, a->src_h_parent,
-+ a->br);
-+ if (!err && au_dbstart(a->dst_dentry) == a->btgt)
-+ err = au_h_verify(a->dst_h_dentry, udba,
-+ a->dst_h_parent->d_inode, a->dst_h_parent,
-+ a->br);
++ out_unlock:
++ mutex_unlock(&a->h_inode->i_mutex);
++ au_h_open_post(dentry, bstart, h_file);
++ a->h_inode = a->h_path.dentry->d_inode;
+ if (!err) {
-+ err = mnt_want_write(a->br->br_mnt);
-+ if (unlikely(err))
-+ goto out_unlock;
-+ au_fset_ren(a->flags, MNT_WRITE);
++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
+ goto out; /* success */
+ }
+
-+ err = au_busy_or_stale();
-+
-+ out_unlock:
-+ au_ren_unlock(a);
++ au_unpin(&a->pin);
++ out_parent:
++ if (parent) {
++ di_write_unlock(parent);
++ dput(parent);
++ }
+ out:
+ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+static void au_ren_refresh_dir(struct au_ren_args *a)
++static int aufs_setattr(struct dentry *dentry, struct iattr *ia)
+{
-+ struct inode *dir;
-+
-+ dir = a->dst_dir;
-+ dir->i_version++;
-+ if (au_ftest_ren(a->flags, ISDIR)) {
-+ /* is this updating defined in POSIX? */
-+ au_cpup_attr_timesizes(a->src_inode);
-+ au_cpup_attr_nlink(dir, /*force*/1);
-+ if (a->dst_inode) {
-+ clear_nlink(a->dst_inode);
-+ au_cpup_attr_timesizes(a->dst_inode);
-+ }
-+ }
-+ if (au_ibstart(dir) == a->btgt)
-+ au_cpup_attr_timesizes(dir);
++ int err;
++ struct inode *inode;
++ struct super_block *sb;
++ struct file *file;
++ struct au_icpup_args *a;
+
-+ if (au_ftest_ren(a->flags, ISSAMEDIR))
-+ return;
++ inode = dentry->d_inode;
++ IMustLock(inode);
+
-+ dir = a->src_dir;
-+ dir->i_version++;
-+ if (au_ftest_ren(a->flags, ISDIR))
-+ au_cpup_attr_nlink(dir, /*force*/1);
-+ if (au_ibstart(dir) == a->btgt)
-+ au_cpup_attr_timesizes(dir);
-+}
++ err = -ENOMEM;
++ a = kzalloc(sizeof(*a), GFP_NOFS);
++ if (unlikely(!a))
++ goto out;
+
-+static void au_ren_refresh(struct au_ren_args *a)
-+{
-+ aufs_bindex_t bend, bindex;
-+ struct dentry *d, *h_d;
-+ struct inode *i, *h_i;
-+ struct super_block *sb;
++ if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
++ ia->ia_valid &= ~ATTR_MODE;
+
-+ d = a->src_dentry;
-+ au_set_dbwh(d, -1);
-+ bend = au_dbend(d);
-+ for (bindex = a->btgt + 1; bindex <= bend; bindex++) {
-+ h_d = au_h_dptr(d, bindex);
-+ if (h_d)
-+ au_set_h_dptr(d, bindex, NULL);
++ file = NULL;
++ sb = dentry->d_sb;
++ si_read_lock(sb, AuLock_FLUSH);
++ if (ia->ia_valid & ATTR_FILE) {
++ /* currently ftruncate(2) only */
++ AuDebugOn(!S_ISREG(inode->i_mode));
++ file = ia->ia_file;
++ err = au_reval_and_lock_fdi(file, au_reopen_nondir, /*wlock*/1);
++ if (unlikely(err))
++ goto out_si;
++ ia->ia_file = au_hf_top(file);
++ a->udba = AuOpt_UDBA_NONE;
++ } else {
++ /* fchmod() doesn't pass ia_file */
++ a->udba = au_opt_udba(sb);
++ if (d_unhashed(dentry))
++ a->udba = AuOpt_UDBA_NONE;
++ di_write_lock_child(dentry);
++ if (a->udba != AuOpt_UDBA_NONE) {
++ AuDebugOn(IS_ROOT(dentry));
++ err = au_reval_for_attr(dentry, au_sigen(sb));
++ if (unlikely(err))
++ goto out_dentry;
++ }
+ }
-+ au_set_dbend(d, a->btgt);
-+
-+ sb = d->d_sb;
-+ i = a->src_inode;
-+ if (au_opt_test(au_mntflags(sb), PLINK) && au_plink_test(i))
-+ return; /* success */
+
-+ bend = au_ibend(i);
-+ for (bindex = a->btgt + 1; bindex <= bend; bindex++) {
-+ h_i = au_h_iptr(i, bindex);
-+ if (h_i) {
-+ au_xino_write(sb, bindex, h_i->i_ino, /*ino*/0);
-+ /* ignore this error */
-+ au_set_h_iptr(i, bindex, NULL, 0);
-+ }
++ err = au_pin_and_icpup(dentry, ia, a);
++ if (unlikely(err < 0))
++ goto out_dentry;
++ if (au_ftest_icpup(a->flags, DID_CPUP)) {
++ ia->ia_file = NULL;
++ ia->ia_valid &= ~ATTR_FILE;
+ }
-+ au_set_ibend(i, a->btgt);
-+}
+
-+/* ---------------------------------------------------------------------- */
++ a->h_path.mnt = au_sbr_mnt(sb, a->btgt);
++ if ((ia->ia_valid & (ATTR_MODE | ATTR_CTIME))
++ == (ATTR_MODE | ATTR_CTIME)) {
++ err = security_path_chmod(a->h_path.dentry, a->h_path.mnt,
++ ia->ia_mode);
++ if (unlikely(err))
++ goto out_unlock;
++ } else if ((ia->ia_valid & (ATTR_UID | ATTR_GID))
++ && (ia->ia_valid & ATTR_CTIME)) {
++ err = security_path_chown(&a->h_path, ia->ia_uid, ia->ia_gid);
++ if (unlikely(err))
++ goto out_unlock;
++ }
+
-+/* mainly for link(2) and rename(2) */
-+int au_wbr(struct dentry *dentry, aufs_bindex_t btgt)
-+{
-+ aufs_bindex_t bdiropq, bwh;
-+ struct dentry *parent;
-+ struct au_branch *br;
++ if (ia->ia_valid & ATTR_SIZE) {
++ struct file *f;
+
-+ parent = dentry->d_parent;
-+ IMustLock(parent->d_inode); /* dir is locked */
++ if (ia->ia_size < i_size_read(inode)) {
++ /* unmap only */
++ err = vmtruncate(inode, ia->ia_size);
++ if (unlikely(err))
++ goto out_unlock;
++ }
+
-+ bdiropq = au_dbdiropq(parent);
-+ bwh = au_dbwh(dentry);
-+ br = au_sbr(dentry->d_sb, btgt);
-+ if (au_br_rdonly(br)
-+ || (0 <= bdiropq && bdiropq < btgt)
-+ || (0 <= bwh && bwh < btgt))
-+ btgt = -1;
++ f = NULL;
++ if (ia->ia_valid & ATTR_FILE)
++ f = ia->ia_file;
++ mutex_unlock(&a->h_inode->i_mutex);
++ err = vfsub_trunc(&a->h_path, ia->ia_size, ia->ia_valid, f);
++ mutex_lock_nested(&a->h_inode->i_mutex, AuLsc_I_CHILD);
++ } else
++ err = vfsub_notify_change(&a->h_path, ia);
++ if (!err)
++ au_cpup_attr_changeable(inode);
+
-+ AuDbg("btgt %d\n", btgt);
-+ return btgt;
++ out_unlock:
++ mutex_unlock(&a->h_inode->i_mutex);
++ au_unpin(&a->pin);
++ out_dentry:
++ di_write_unlock(dentry);
++ if (file) {
++ fi_write_unlock(file);
++ ia->ia_file = file;
++ ia->ia_valid |= ATTR_FILE;
++ }
++ out_si:
++ si_read_unlock(sb);
++ kfree(a);
++ out:
++ AuTraceErr(err);
++ return err;
+}
+
-+/* sets src_bstart, dst_bstart and btgt */
-+static int au_ren_wbr(struct au_ren_args *a)
++static void au_refresh_iattr(struct inode *inode, struct kstat *st,
++ unsigned int nlink)
+{
-+ int err;
-+ struct au_wr_dir_args wr_dir_args = {
-+ /* .force_btgt = -1, */
-+ .flags = AuWrDir_ADD_ENTRY
-+ };
++ inode->i_mode = st->mode;
++ inode->i_uid = st->uid;
++ inode->i_gid = st->gid;
++ inode->i_atime = st->atime;
++ inode->i_mtime = st->mtime;
++ inode->i_ctime = st->ctime;
+
-+ a->src_bstart = au_dbstart(a->src_dentry);
-+ a->dst_bstart = au_dbstart(a->dst_dentry);
-+ if (au_ftest_ren(a->flags, ISDIR))
-+ au_fset_wrdir(wr_dir_args.flags, ISDIR);
-+ wr_dir_args.force_btgt = a->src_bstart;
-+ if (a->dst_inode && a->dst_bstart < a->src_bstart)
-+ wr_dir_args.force_btgt = a->dst_bstart;
-+ wr_dir_args.force_btgt = au_wbr(a->dst_dentry, wr_dir_args.force_btgt);
-+ err = au_wr_dir(a->dst_dentry, a->src_dentry, &wr_dir_args);
-+ a->btgt = err;
++ au_cpup_attr_nlink(inode, /*force*/0);
++ if (S_ISDIR(inode->i_mode)) {
++ inode->i_nlink -= nlink;
++ inode->i_nlink += st->nlink;
++ }
+
-+ return err;
++ spin_lock(&inode->i_lock);
++ inode->i_blocks = st->blocks;
++ i_size_write(inode, st->size);
++ spin_unlock(&inode->i_lock);
+}
+
-+static void au_ren_dt(struct au_ren_args *a)
++static int aufs_getattr(struct vfsmount *mnt __maybe_unused,
++ struct dentry *dentry, struct kstat *st)
+{
-+ a->h_path.dentry = a->src_h_parent;
-+ au_dtime_store(a->src_dt + AuPARENT, a->src_parent, &a->h_path);
-+ if (!au_ftest_ren(a->flags, ISSAMEDIR)) {
-+ a->h_path.dentry = a->dst_h_parent;
-+ au_dtime_store(a->dst_dt + AuPARENT, a->dst_parent, &a->h_path);
-+ }
-+
-+ au_fclr_ren(a->flags, DT_DSTDIR);
-+ if (!au_ftest_ren(a->flags, ISDIR))
-+ return;
-+
-+ a->h_path.dentry = a->src_h_dentry;
-+ au_dtime_store(a->src_dt + AuCHILD, a->src_dentry, &a->h_path);
-+ if (a->dst_h_dentry->d_inode) {
-+ au_fset_ren(a->flags, DT_DSTDIR);
-+ a->h_path.dentry = a->dst_h_dentry;
-+ au_dtime_store(a->dst_dt + AuCHILD, a->dst_dentry, &a->h_path);
-+ }
-+}
++ int err;
++ unsigned int mnt_flags;
++ aufs_bindex_t bindex;
++ unsigned char udba_none, positive;
++ struct super_block *sb, *h_sb;
++ struct inode *inode;
++ struct vfsmount *h_mnt;
++ struct dentry *h_dentry;
+
-+static void au_ren_rev_dt(int err, struct au_ren_args *a)
-+{
-+ struct dentry *h_d;
-+ struct mutex *h_mtx;
++ err = 0;
++ sb = dentry->d_sb;
++ inode = dentry->d_inode;
++ si_read_lock(sb, AuLock_FLUSH);
++ mnt_flags = au_mntflags(sb);
++ udba_none = !!au_opt_test(mnt_flags, UDBA_NONE);
+
-+ au_dtime_revert(a->src_dt + AuPARENT);
-+ if (!au_ftest_ren(a->flags, ISSAMEDIR))
-+ au_dtime_revert(a->dst_dt + AuPARENT);
++ /* support fstat(2) */
++ if (!d_unhashed(dentry) && !udba_none) {
++ unsigned int sigen = au_sigen(sb);
++ if (au_digen(dentry) == sigen && au_iigen(inode) == sigen)
++ di_read_lock_child(dentry, AuLock_IR);
++ else {
++ AuDebugOn(IS_ROOT(dentry));
++ di_write_lock_child(dentry);
++ err = au_reval_for_attr(dentry, sigen);
++ di_downgrade_lock(dentry, AuLock_IR);
++ if (unlikely(err))
++ goto out;
++ }
++ } else
++ di_read_lock_child(dentry, AuLock_IR);
+
-+ if (au_ftest_ren(a->flags, ISDIR) && err != -EIO) {
-+ h_d = a->src_dt[AuCHILD].dt_h_path.dentry;
-+ h_mtx = &h_d->d_inode->i_mutex;
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ au_dtime_revert(a->src_dt + AuCHILD);
-+ mutex_unlock(h_mtx);
++ bindex = au_ibstart(inode);
++ h_mnt = au_sbr_mnt(sb, bindex);
++ h_sb = h_mnt->mnt_sb;
++ if (!au_test_fs_bad_iattr(h_sb) && udba_none)
++ goto out_fill; /* success */
+
-+ if (au_ftest_ren(a->flags, DT_DSTDIR)) {
-+ h_d = a->dst_dt[AuCHILD].dt_h_path.dentry;
-+ h_mtx = &h_d->d_inode->i_mutex;
-+ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
-+ au_dtime_revert(a->dst_dt + AuCHILD);
-+ mutex_unlock(h_mtx);
-+ }
++ h_dentry = NULL;
++ if (au_dbstart(dentry) == bindex)
++ h_dentry = dget(au_h_dptr(dentry, bindex));
++ else if (au_opt_test(mnt_flags, PLINK) && au_plink_test(inode)) {
++ h_dentry = au_plink_lkup(inode, bindex);
++ if (IS_ERR(h_dentry))
++ goto out_fill; /* pretending success */
++ }
++ /* illegally overlapped or something */
++ if (unlikely(!h_dentry))
++ goto out_fill; /* pretending success */
++
++ positive = !!h_dentry->d_inode;
++ if (positive)
++ err = vfs_getattr(h_mnt, h_dentry, st);
++ dput(h_dentry);
++ if (!err) {
++ if (positive)
++ au_refresh_iattr(inode, st, h_dentry->d_inode->i_nlink);
++ goto out_fill; /* success */
+ }
++ goto out;
++
++ out_fill:
++ generic_fillattr(inode, st);
++ out:
++ di_read_unlock(dentry, AuLock_IR);
++ si_read_unlock(sb);
++ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+int aufs_rename(struct inode *_src_dir, struct dentry *_src_dentry,
-+ struct inode *_dst_dir, struct dentry *_dst_dentry)
++static int h_readlink(struct dentry *dentry, int bindex, char __user *buf,
++ int bufsiz)
+{
+ int err;
-+ /* reduce stack space */
-+ struct au_ren_args *a;
++ struct super_block *sb;
++ struct dentry *h_dentry;
+
-+ IMustLock(_src_dir);
-+ IMustLock(_dst_dir);
++ err = -EINVAL;
++ h_dentry = au_h_dptr(dentry, bindex);
++ if (unlikely(!h_dentry->d_inode->i_op->readlink))
++ goto out;
+
-+ err = -ENOMEM;
-+ BUILD_BUG_ON(sizeof(*a) > PAGE_SIZE);
-+ a = kzalloc(sizeof(*a), GFP_NOFS);
-+ if (unlikely(!a))
++ err = security_inode_readlink(h_dentry);
++ if (unlikely(err))
+ goto out;
+
-+ a->src_dir = _src_dir;
-+ a->src_dentry = _src_dentry;
-+ a->src_inode = a->src_dentry->d_inode;
-+ a->src_parent = a->src_dentry->d_parent; /* dir inode is locked */
-+ a->dst_dir = _dst_dir;
-+ a->dst_dentry = _dst_dentry;
-+ a->dst_inode = a->dst_dentry->d_inode;
-+ a->dst_parent = a->dst_dentry->d_parent; /* dir inode is locked */
-+ if (a->dst_inode) {
-+ IMustLock(a->dst_inode);
-+ au_igrab(a->dst_inode);
++ sb = dentry->d_sb;
++ if (!au_test_ro(sb, bindex, dentry->d_inode)) {
++ vfsub_touch_atime(au_sbr_mnt(sb, bindex), h_dentry);
++ fsstack_copy_attr_atime(dentry->d_inode, h_dentry->d_inode);
+ }
++ err = h_dentry->d_inode->i_op->readlink(h_dentry, buf, bufsiz);
+
-+ err = -ENOTDIR;
-+ if (S_ISDIR(a->src_inode->i_mode)) {
-+ au_fset_ren(a->flags, ISDIR);
-+ if (unlikely(a->dst_inode && !S_ISDIR(a->dst_inode->i_mode)))
-+ goto out_free;
-+ aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry,
-+ AuLock_DIR | AuLock_FLUSH);
-+ } else
-+ aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry,
-+ AuLock_FLUSH);
++ out:
++ return err;
++}
+
-+ au_fset_ren(a->flags, ISSAMEDIR); /* temporary */
-+ di_write_lock_parent(a->dst_parent);
++static int aufs_readlink(struct dentry *dentry, char __user *buf, int bufsiz)
++{
++ int err;
+
-+ /* which branch we process */
-+ err = au_ren_wbr(a);
-+ if (unlikely(err < 0))
-+ goto out_unlock;
-+ a->br = au_sbr(a->dst_dentry->d_sb, a->btgt);
-+ a->h_path.mnt = a->br->br_mnt;
++ aufs_read_lock(dentry, AuLock_IR);
++ err = h_readlink(dentry, au_dbstart(dentry), buf, bufsiz);
++ aufs_read_unlock(dentry, AuLock_IR);
+
-+ /* are they available to be renamed */
-+ err = au_ren_may_dir(a);
-+ if (unlikely(err))
-+ goto out_children;
++ return err;
++}
+
-+ /* prepare the writable parent dir on the same branch */
-+ if (a->dst_bstart == a->btgt) {
-+ au_fset_ren(a->flags, WHDST);
-+ } else {
-+ err = au_cpup_dirs(a->dst_dentry, a->btgt);
-+ if (unlikely(err))
-+ goto out_children;
-+ }
++static void *aufs_follow_link(struct dentry *dentry, struct nameidata *nd)
++{
++ int err;
++ char *buf;
++ mm_segment_t old_fs;
+
-+ if (a->src_dir != a->dst_dir) {
-+ /*
-+ * this temporary unlock is safe,
-+ * because both dir->i_mutex are locked.
-+ */
-+ di_write_unlock(a->dst_parent);
-+ di_write_lock_parent(a->src_parent);
-+ err = au_wr_dir_need_wh(a->src_dentry,
-+ au_ftest_ren(a->flags, ISDIR),
-+ &a->btgt);
-+ di_write_unlock(a->src_parent);
-+ di_write_lock2_parent(a->src_parent, a->dst_parent, /*isdir*/1);
-+ au_fclr_ren(a->flags, ISSAMEDIR);
-+ } else
-+ err = au_wr_dir_need_wh(a->src_dentry,
-+ au_ftest_ren(a->flags, ISDIR),
-+ &a->btgt);
-+ if (unlikely(err < 0))
-+ goto out_children;
-+ if (err)
-+ au_fset_ren(a->flags, WHSRC);
++ err = -ENOMEM;
++ buf = __getname_gfp(GFP_NOFS);
++ if (unlikely(!buf))
++ goto out;
+
-+ /* lock them all */
-+ err = au_ren_lock(a);
-+ if (unlikely(err))
-+ goto out_children;
++ aufs_read_lock(dentry, AuLock_IR);
++ old_fs = get_fs();
++ set_fs(KERNEL_DS);
++ err = h_readlink(dentry, au_dbstart(dentry), (char __user *)buf,
++ PATH_MAX);
++ set_fs(old_fs);
++ aufs_read_unlock(dentry, AuLock_IR);
+
-+ if (!au_opt_test(au_mntflags(a->dst_dir->i_sb), UDBA_NONE)) {
-+ err = au_may_ren(a);
-+ if (unlikely(err))
-+ goto out_hdir;
++ if (err >= 0) {
++ buf[err] = 0;
++ /* will be freed by put_link */
++ nd_set_link(nd, buf);
++ return NULL; /* success */
+ }
++ __putname(buf);
+
-+ /* store timestamps to be revertible */
-+ au_ren_dt(a);
++ out:
++ path_put(&nd->path);
++ AuTraceErr(err);
++ return ERR_PTR(err);
++}
+
-+ /* here we go */
-+ err = do_rename(a);
-+ if (unlikely(err))
-+ goto out_dt;
++static void aufs_put_link(struct dentry *dentry __maybe_unused,
++ struct nameidata *nd, void *cookie __maybe_unused)
++{
++ __putname(nd_get_link(nd));
++}
+
-+ /* update dir attributes */
-+ au_ren_refresh_dir(a);
++/* ---------------------------------------------------------------------- */
+
-+ /* dput/iput all lower dentries */
-+ au_ren_refresh(a);
++static void aufs_truncate_range(struct inode *inode __maybe_unused,
++ loff_t start __maybe_unused,
++ loff_t end __maybe_unused)
++{
++ AuUnsupport();
++}
+
-+ goto out_hdir; /* success */
++/* ---------------------------------------------------------------------- */
+
-+ out_dt:
-+ au_ren_rev_dt(err, a);
-+ out_hdir:
-+ au_ren_unlock(a);
-+ out_children:
-+ au_nhash_wh_free(&a->whlist);
-+ out_unlock:
-+ if (unlikely(err && au_ftest_ren(a->flags, ISDIR))) {
-+ au_update_dbstart(a->dst_dentry);
-+ d_drop(a->dst_dentry);
-+ }
-+ if (!err)
-+ d_move(a->src_dentry, a->dst_dentry);
-+ if (au_ftest_ren(a->flags, ISSAMEDIR))
-+ di_write_unlock(a->dst_parent);
-+ else
-+ di_write_unlock2(a->src_parent, a->dst_parent);
-+ aufs_read_and_write_unlock2(a->dst_dentry, a->src_dentry);
-+ out_free:
-+ iput(a->dst_inode);
-+ if (a->thargs)
-+ au_whtmp_rmdir_free(a->thargs);
-+ kfree(a);
-+ out:
-+ return err;
-+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/iinfo.c aufs2-2.6.git/fs/aufs/iinfo.c
---- linux-2.6.31/fs/aufs/iinfo.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/iinfo.c 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,283 @@
++struct inode_operations aufs_symlink_iop = {
++ .permission = aufs_permission,
++ .setattr = aufs_setattr,
++ .getattr = aufs_getattr,
++ .readlink = aufs_readlink,
++ .follow_link = aufs_follow_link,
++ .put_link = aufs_put_link
++};
++
++struct inode_operations aufs_dir_iop = {
++ .create = aufs_create,
++ .lookup = aufs_lookup,
++ .link = aufs_link,
++ .unlink = aufs_unlink,
++ .symlink = aufs_symlink,
++ .mkdir = aufs_mkdir,
++ .rmdir = aufs_rmdir,
++ .mknod = aufs_mknod,
++ .rename = aufs_rename,
++
++ .permission = aufs_permission,
++ .setattr = aufs_setattr,
++ .getattr = aufs_getattr
++};
++
++struct inode_operations aufs_iop = {
++ .permission = aufs_permission,
++ .setattr = aufs_setattr,
++ .getattr = aufs_getattr,
++ .truncate_range = aufs_truncate_range
++};
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/i_op_del.c linux-2.6.34/fs/aufs/i_op_del.c
+--- linux-2.6.34.org/fs/aufs/i_op_del.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/i_op_del.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,472 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * inode private data
++ * inode operations (del entry)
+ */
+
+#include "aufs.h"
+
-+struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex)
++/*
++ * decide if a new whiteout for @dentry is necessary or not.
++ * when it is necessary, prepare the parent dir for the upper branch whose
++ * branch index is @bcpup for creation. the actual creation of the whiteout will
++ * be done by caller.
++ * return value:
++ * 0: wh is unnecessary
++ * plus: wh is necessary
++ * minus: error
++ */
++int au_wr_dir_need_wh(struct dentry *dentry, int isdir, aufs_bindex_t *bcpup)
+{
-+ struct inode *h_inode;
++ int need_wh, err;
++ aufs_bindex_t bstart;
++ struct super_block *sb;
+
-+ IiMustAnyLock(inode);
++ sb = dentry->d_sb;
++ bstart = au_dbstart(dentry);
++ if (*bcpup < 0) {
++ *bcpup = bstart;
++ if (au_test_ro(sb, bstart, dentry->d_inode)) {
++ err = AuWbrCopyup(au_sbi(sb), dentry);
++ *bcpup = err;
++ if (unlikely(err < 0))
++ goto out;
++ }
++ } else
++ AuDebugOn(bstart < *bcpup
++ || au_test_ro(sb, *bcpup, dentry->d_inode));
++ AuDbg("bcpup %d, bstart %d\n", *bcpup, bstart);
+
-+ h_inode = au_ii(inode)->ii_hinode[0 + bindex].hi_inode;
-+ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
-+ return h_inode;
++ if (*bcpup != bstart) {
++ err = au_cpup_dirs(dentry, *bcpup);
++ if (unlikely(err))
++ goto out;
++ need_wh = 1;
++ } else {
++ aufs_bindex_t old_bend, new_bend, bdiropq = -1;
++
++ old_bend = au_dbend(dentry);
++ if (isdir) {
++ bdiropq = au_dbdiropq(dentry);
++ au_set_dbdiropq(dentry, -1);
++ }
++ need_wh = au_lkup_dentry(dentry, bstart + 1, /*type*/0,
++ /*nd*/NULL);
++ err = need_wh;
++ if (isdir)
++ au_set_dbdiropq(dentry, bdiropq);
++ if (unlikely(err < 0))
++ goto out;
++ new_bend = au_dbend(dentry);
++ if (!need_wh && old_bend != new_bend) {
++ au_set_h_dptr(dentry, new_bend, NULL);
++ au_set_dbend(dentry, old_bend);
++ }
++ }
++ AuDbg("need_wh %d\n", need_wh);
++ err = need_wh;
++
++ out:
++ return err;
+}
+
-+/* todo: hard/soft set? */
-+void au_set_ibstart(struct inode *inode, aufs_bindex_t bindex)
++/*
++ * simple tests for the del-entry operations.
++ * following the checks in vfs, plus the parent-child relationship.
++ */
++int au_may_del(struct dentry *dentry, aufs_bindex_t bindex,
++ struct dentry *h_parent, int isdir)
+{
-+ struct au_iinfo *iinfo = au_ii(inode);
++ int err;
++ umode_t h_mode;
++ struct dentry *h_dentry, *h_latest;
+ struct inode *h_inode;
+
-+ IiMustWriteLock(inode);
++ h_dentry = au_h_dptr(dentry, bindex);
++ h_inode = h_dentry->d_inode;
++ if (dentry->d_inode) {
++ err = -ENOENT;
++ if (unlikely(!h_inode || !h_inode->i_nlink))
++ goto out;
+
-+ iinfo->ii_bstart = bindex;
-+ h_inode = iinfo->ii_hinode[bindex + 0].hi_inode;
-+ if (h_inode)
-+ au_cpup_igen(inode, h_inode);
-+}
++ h_mode = h_inode->i_mode;
++ if (!isdir) {
++ err = -EISDIR;
++ if (unlikely(S_ISDIR(h_mode)))
++ goto out;
++ } else if (unlikely(!S_ISDIR(h_mode))) {
++ err = -ENOTDIR;
++ goto out;
++ }
++ } else {
++ /* rename(2) case */
++ err = -EIO;
++ if (unlikely(h_inode))
++ goto out;
++ }
+
-+void au_hiput(struct au_hinode *hinode)
-+{
-+ au_hin_free(hinode);
-+ dput(hinode->hi_whdentry);
-+ iput(hinode->hi_inode);
++ err = -ENOENT;
++ /* expected parent dir is locked */
++ if (unlikely(h_parent != h_dentry->d_parent))
++ goto out;
++ err = 0;
++
++ /*
++ * rmdir a dir may break the consistency on some filesystem.
++ * let's try heavy test.
++ */
++ err = -EACCES;
++ if (unlikely(au_test_h_perm(h_parent->d_inode, MAY_EXEC | MAY_WRITE)))
++ goto out;
++
++ h_latest = au_sio_lkup_one(&dentry->d_name, h_parent,
++ au_sbr(dentry->d_sb, bindex));
++ err = -EIO;
++ if (IS_ERR(h_latest))
++ goto out;
++ if (h_latest == h_dentry)
++ err = 0;
++ dput(h_latest);
++
++ out:
++ return err;
+}
+
-+unsigned int au_hi_flags(struct inode *inode, int isdir)
++/*
++ * decide the branch where we operate for @dentry. the branch index will be set
++ * @rbcpup. after diciding it, 'pin' it and store the timestamps of the parent
++ * dir for reverting.
++ * when a new whiteout is necessary, create it.
++ */
++static struct dentry*
++lock_hdir_create_wh(struct dentry *dentry, int isdir, aufs_bindex_t *rbcpup,
++ struct au_dtime *dt, struct au_pin *pin)
+{
-+ unsigned int flags;
-+ const unsigned int mnt_flags = au_mntflags(inode->i_sb);
++ struct dentry *wh_dentry;
++ struct super_block *sb;
++ struct path h_path;
++ int err, need_wh;
++ unsigned int udba;
++ aufs_bindex_t bcpup;
+
-+ flags = 0;
-+ if (au_opt_test(mnt_flags, XINO))
-+ au_fset_hi(flags, XINO);
-+ if (isdir && au_opt_test(mnt_flags, UDBA_HINOTIFY))
-+ au_fset_hi(flags, HINOTIFY);
-+ return flags;
++ need_wh = au_wr_dir_need_wh(dentry, isdir, rbcpup);
++ wh_dentry = ERR_PTR(need_wh);
++ if (unlikely(need_wh < 0))
++ goto out;
++
++ sb = dentry->d_sb;
++ udba = au_opt_udba(sb);
++ bcpup = *rbcpup;
++ err = au_pin(pin, dentry, bcpup, udba,
++ AuPin_DI_LOCKED | AuPin_MNT_WRITE);
++ wh_dentry = ERR_PTR(err);
++ if (unlikely(err))
++ goto out;
++
++ h_path.dentry = au_pinned_h_parent(pin);
++ if (udba != AuOpt_UDBA_NONE
++ && au_dbstart(dentry) == bcpup) {
++ err = au_may_del(dentry, bcpup, h_path.dentry, isdir);
++ wh_dentry = ERR_PTR(err);
++ if (unlikely(err))
++ goto out_unpin;
++ }
++
++ h_path.mnt = au_sbr_mnt(sb, bcpup);
++ au_dtime_store(dt, au_pinned_parent(pin), &h_path);
++ wh_dentry = NULL;
++ if (!need_wh)
++ goto out; /* success, no need to create whiteout */
++
++ wh_dentry = au_wh_create(dentry, bcpup, h_path.dentry);
++ if (IS_ERR(wh_dentry))
++ goto out_unpin;
++
++ /* returns with the parent is locked and wh_dentry is dget-ed */
++ goto out; /* success */
++
++ out_unpin:
++ au_unpin(pin);
++ out:
++ return wh_dentry;
+}
+
-+void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
-+ struct inode *h_inode, unsigned int flags)
++/*
++ * when removing a dir, rename it to a unique temporary whiteout-ed name first
++ * in order to be revertible and save time for removing many child whiteouts
++ * under the dir.
++ * returns 1 when there are too many child whiteout and caller should remove
++ * them asynchronously. returns 0 when the number of children is enough small to
++ * remove now or the branch fs is a remote fs.
++ * otherwise return an error.
++ */
++static int renwh_and_rmdir(struct dentry *dentry, aufs_bindex_t bindex,
++ struct au_nhash *whlist, struct inode *dir)
+{
-+ struct au_hinode *hinode;
-+ struct inode *hi;
-+ struct au_iinfo *iinfo = au_ii(inode);
++ int rmdir_later, err, dirwh;
++ struct dentry *h_dentry;
++ struct super_block *sb;
++
++ sb = dentry->d_sb;
++ SiMustAnyLock(sb);
++ h_dentry = au_h_dptr(dentry, bindex);
++ err = au_whtmp_ren(h_dentry, au_sbr(sb, bindex));
++ if (unlikely(err))
++ goto out;
++
++ /* stop monitoring */
++ au_hn_free(au_hi(dentry->d_inode, bindex));
++
++ if (!au_test_fs_remote(h_dentry->d_sb)) {
++ dirwh = au_sbi(sb)->si_dirwh;
++ rmdir_later = (dirwh <= 1);
++ if (!rmdir_later)
++ rmdir_later = au_nhash_test_longer_wh(whlist, bindex,
++ dirwh);
++ if (rmdir_later)
++ return rmdir_later;
++ }
+
-+ IiMustWriteLock(inode);
++ 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);
++ err = 0;
++ }
+
-+ hinode = iinfo->ii_hinode + bindex;
-+ hi = hinode->hi_inode;
-+ AuDebugOn(h_inode && atomic_read(&h_inode->i_count) <= 0);
-+ AuDebugOn(h_inode && hi);
++ out:
++ AuTraceErr(err);
++ return err;
++}
+
-+ if (hi)
-+ au_hiput(hinode);
-+ hinode->hi_inode = h_inode;
-+ if (h_inode) {
-+ int err;
-+ struct super_block *sb = inode->i_sb;
-+ struct au_branch *br;
++/*
++ * final procedure for deleting a entry.
++ * maintain dentry and iattr.
++ */
++static void epilog(struct inode *dir, struct dentry *dentry,
++ aufs_bindex_t bindex)
++{
++ struct inode *inode;
+
-+ if (bindex == iinfo->ii_bstart)
-+ au_cpup_igen(inode, h_inode);
-+ br = au_sbr(sb, bindex);
-+ hinode->hi_id = br->br_id;
-+ if (au_ftest_hi(flags, XINO)) {
-+ err = au_xino_write(sb, bindex, h_inode->i_ino,
-+ inode->i_ino);
-+ if (unlikely(err))
-+ AuIOErr1("failed au_xino_write() %d\n", err);
-+ }
++ inode = dentry->d_inode;
++ d_drop(dentry);
++ inode->i_ctime = dir->i_ctime;
+
-+ if (au_ftest_hi(flags, HINOTIFY)
-+ && au_br_hinotifyable(br->br_perm)) {
-+ err = au_hin_alloc(hinode, inode, h_inode);
-+ if (unlikely(err))
-+ AuIOErr1("au_hin_alloc() %d\n", err);
-+ }
++ if (atomic_read(&dentry->d_count) == 1) {
++ au_set_h_dptr(dentry, au_dbstart(dentry), NULL);
++ au_update_dbstart(dentry);
+ }
++ if (au_ibstart(dir) == bindex)
++ au_cpup_attr_timesizes(dir);
++ dir->i_version++;
+}
+
-+void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex,
-+ struct dentry *h_wh)
++/*
++ * when an error happened, remove the created whiteout and revert everything.
++ */
++static int do_revert(int err, struct inode *dir, aufs_bindex_t bwh,
++ struct dentry *wh_dentry, struct dentry *dentry,
++ struct au_dtime *dt)
+{
-+ struct au_hinode *hinode;
++ int rerr;
++ struct path h_path = {
++ .dentry = wh_dentry,
++ .mnt = au_sbr_mnt(dir->i_sb, bwh)
++ };
+
-+ IiMustWriteLock(inode);
++ rerr = au_wh_unlink_dentry(au_h_iptr(dir, bwh), &h_path, dentry);
++ if (!rerr) {
++ au_set_dbwh(dentry, bwh);
++ au_dtime_revert(dt);
++ return 0;
++ }
+
-+ hinode = au_ii(inode)->ii_hinode + bindex;
-+ AuDebugOn(hinode->hi_whdentry);
-+ hinode->hi_whdentry = h_wh;
++ AuIOErr("%.*s reverting whiteout failed(%d, %d)\n",
++ AuDLNPair(dentry), err, rerr);
++ return -EIO;
+}
+
-+void au_update_iigen(struct inode *inode)
-+{
-+ atomic_set(&au_ii(inode)->ii_generation, au_sigen(inode->i_sb));
-+ /* smp_mb(); */ /* atomic_set */
-+}
++/* ---------------------------------------------------------------------- */
+
-+/* it may be called at remount time, too */
-+void au_update_brange(struct inode *inode, int do_put_zero)
++int aufs_unlink(struct inode *dir, struct dentry *dentry)
+{
-+ struct au_iinfo *iinfo;
++ int err;
++ aufs_bindex_t bwh, bindex, bstart;
++ struct au_dtime dt;
++ struct au_pin pin;
++ struct path h_path;
++ struct inode *inode, *h_dir;
++ struct dentry *parent, *wh_dentry;
+
-+ iinfo = au_ii(inode);
-+ if (!iinfo || iinfo->ii_bstart < 0)
-+ return;
++ IMustLock(dir);
++ inode = dentry->d_inode;
++ if (unlikely(!inode))
++ return -ENOENT; /* possible? */
++ IMustLock(inode);
+
-+ IiMustWriteLock(inode);
++ aufs_read_lock(dentry, AuLock_DW);
++ parent = dentry->d_parent; /* dir inode is locked */
++ di_write_lock_parent(parent);
+
-+ if (do_put_zero) {
-+ aufs_bindex_t bindex;
++ bstart = au_dbstart(dentry);
++ bwh = au_dbwh(dentry);
++ bindex = -1;
++ wh_dentry = lock_hdir_create_wh(dentry, /*isdir*/0, &bindex, &dt, &pin);
++ err = PTR_ERR(wh_dentry);
++ if (IS_ERR(wh_dentry))
++ goto out;
+
-+ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend;
-+ bindex++) {
-+ struct inode *h_i;
++ h_path.mnt = au_sbr_mnt(dentry->d_sb, bstart);
++ h_path.dentry = au_h_dptr(dentry, bstart);
++ dget(h_path.dentry);
++ if (bindex == bstart) {
++ h_dir = au_pinned_h_dir(&pin);
++ err = vfsub_unlink(h_dir, &h_path, /*force*/0);
++ } else {
++ /* dir inode is locked */
++ h_dir = wh_dentry->d_parent->d_inode;
++ IMustLock(h_dir);
++ err = 0;
++ }
+
-+ h_i = iinfo->ii_hinode[0 + bindex].hi_inode;
-+ if (h_i && !h_i->i_nlink)
-+ au_set_h_iptr(inode, bindex, NULL, 0);
-+ }
++ if (!err) {
++ drop_nlink(inode);
++ epilog(dir, dentry, bindex);
++
++ /* update target timestamps */
++ if (bindex == bstart) {
++ vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/
++ inode->i_ctime = h_path.dentry->d_inode->i_ctime;
++ } else
++ /* todo: this timestamp may be reverted later */
++ inode->i_ctime = h_dir->i_ctime;
++ goto out_unlock; /* success */
+ }
+
-+ iinfo->ii_bstart = -1;
-+ while (++iinfo->ii_bstart <= iinfo->ii_bend)
-+ if (iinfo->ii_hinode[0 + iinfo->ii_bstart].hi_inode)
-+ break;
-+ if (iinfo->ii_bstart > iinfo->ii_bend) {
-+ iinfo->ii_bstart = -1;
-+ iinfo->ii_bend = -1;
-+ return;
++ /* revert */
++ if (wh_dentry) {
++ int rerr;
++
++ rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt);
++ if (rerr)
++ err = rerr;
+ }
+
-+ iinfo->ii_bend++;
-+ while (0 <= --iinfo->ii_bend)
-+ if (iinfo->ii_hinode[0 + iinfo->ii_bend].hi_inode)
-+ break;
-+ AuDebugOn(iinfo->ii_bstart > iinfo->ii_bend || iinfo->ii_bend < 0);
++ out_unlock:
++ au_unpin(&pin);
++ dput(wh_dentry);
++ dput(h_path.dentry);
++ out:
++ di_write_unlock(parent);
++ aufs_read_unlock(dentry, AuLock_DW);
++ return err;
+}
+
-+/* ---------------------------------------------------------------------- */
-+
-+int au_iinfo_init(struct inode *inode)
++int aufs_rmdir(struct inode *dir, struct dentry *dentry)
+{
-+ struct au_iinfo *iinfo;
-+ struct super_block *sb;
-+ int nbr, i;
++ int err, rmdir_later;
++ aufs_bindex_t bwh, bindex, bstart;
++ struct au_dtime dt;
++ struct au_pin pin;
++ struct inode *inode;
++ struct dentry *parent, *wh_dentry, *h_dentry;
++ struct au_whtmp_rmdir *args;
+
-+ sb = inode->i_sb;
-+ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo);
-+ nbr = au_sbend(sb) + 1;
-+ if (unlikely(nbr <= 0))
-+ nbr = 1;
-+ 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;
++ IMustLock(dir);
++ inode = dentry->d_inode;
++ err = -ENOENT; /* possible? */
++ if (unlikely(!inode))
++ goto out;
++ IMustLock(inode);
+
-+ atomic_set(&iinfo->ii_generation, au_sigen(sb));
-+ /* smp_mb(); */ /* atomic_set */
-+ au_rw_init(&iinfo->ii_rwsem);
-+ iinfo->ii_bstart = -1;
-+ iinfo->ii_bend = -1;
-+ iinfo->ii_vdir = NULL;
-+ return 0;
-+ }
-+ return -ENOMEM;
-+}
++ aufs_read_lock(dentry, AuLock_DW | AuLock_FLUSH);
++ err = -ENOMEM;
++ args = au_whtmp_rmdir_alloc(dir->i_sb, GFP_NOFS);
++ if (unlikely(!args))
++ goto out_unlock;
+
-+int au_ii_realloc(struct au_iinfo *iinfo, int nbr)
-+{
-+ int err, sz;
-+ struct au_hinode *hip;
++ parent = dentry->d_parent; /* dir inode is locked */
++ di_write_lock_parent(parent);
++ err = au_test_empty(dentry, &args->whlist);
++ if (unlikely(err))
++ goto out_args;
+
-+ AuRwMustWriteLock(&iinfo->ii_rwsem);
++ bstart = au_dbstart(dentry);
++ bwh = au_dbwh(dentry);
++ bindex = -1;
++ 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;
+
-+ err = -ENOMEM;
-+ sz = sizeof(*hip) * (iinfo->ii_bend + 1);
-+ if (!sz)
-+ sz = sizeof(*hip);
-+ hip = au_kzrealloc(iinfo->ii_hinode, sz, sizeof(*hip) * nbr, GFP_NOFS);
-+ if (hip) {
-+ iinfo->ii_hinode = hip;
++ h_dentry = au_h_dptr(dentry, bstart);
++ dget(h_dentry);
++ rmdir_later = 0;
++ if (bindex == bstart) {
++ err = renwh_and_rmdir(dentry, bstart, &args->whlist, dir);
++ if (err > 0) {
++ rmdir_later = err;
++ err = 0;
++ }
++ } else {
++ /* stop monitoring */
++ au_hn_free(au_hi(inode, bstart));
++
++ /* dir inode is locked */
++ IMustLock(wh_dentry->d_parent->d_inode);
+ err = 0;
+ }
+
-+ return err;
-+}
-+
-+static int au_iinfo_write0(struct super_block *sb, struct au_hinode *hinode,
-+ ino_t ino)
-+{
-+ int err;
-+ aufs_bindex_t bindex;
-+ unsigned char locked;
-+
-+ err = 0;
-+ locked = !!si_noflush_read_trylock(sb);
-+ bindex = au_br_index(sb, hinode->hi_id);
-+ if (bindex >= 0)
-+ err = au_xino_write0(sb, bindex, hinode->hi_inode->i_ino, ino);
-+ /* error action? */
-+ if (locked)
-+ si_read_unlock(sb);
-+ return err;
-+}
++ if (!err) {
++ clear_nlink(inode);
++ au_set_dbdiropq(dentry, -1);
++ epilog(dir, dentry, bindex);
+
-+void au_iinfo_fin(struct inode *inode)
-+{
-+ ino_t ino;
-+ aufs_bindex_t bend;
-+ unsigned char unlinked = !inode->i_nlink;
-+ struct au_iinfo *iinfo;
-+ struct au_hinode *hi;
-+ struct super_block *sb;
++ if (rmdir_later) {
++ au_whtmp_kick_rmdir(dir, bstart, h_dentry, args);
++ args = NULL;
++ }
+
-+ if (unlinked) {
-+ int err = au_xigen_inc(inode);
-+ if (unlikely(err))
-+ AuWarn1("failed resetting i_generation, %d\n", err);
++ goto out_unpin; /* success */
+ }
+
-+ iinfo = au_ii(inode);
-+ /* bad_inode case */
-+ if (!iinfo)
-+ return;
-+
-+ if (iinfo->ii_vdir)
-+ au_vdir_free(iinfo->ii_vdir);
++ /* revert */
++ AuLabel(revert);
++ if (wh_dentry) {
++ int rerr;
+
-+ if (iinfo->ii_bstart >= 0) {
-+ sb = inode->i_sb;
-+ ino = 0;
-+ if (unlinked)
-+ ino = inode->i_ino;
-+ hi = iinfo->ii_hinode + iinfo->ii_bstart;
-+ bend = iinfo->ii_bend;
-+ while (iinfo->ii_bstart++ <= bend) {
-+ if (hi->hi_inode) {
-+ if (unlinked || !hi->hi_inode->i_nlink) {
-+ au_iinfo_write0(sb, hi, ino);
-+ /* ignore this error */
-+ ino = 0;
-+ }
-+ au_hiput(hi);
-+ }
-+ hi++;
-+ }
++ rerr = do_revert(err, dir, bwh, wh_dentry, dentry, &dt);
++ if (rerr)
++ err = rerr;
+ }
+
-+ kfree(iinfo->ii_hinode);
-+ AuRwDestroy(&iinfo->ii_rwsem);
++ out_unpin:
++ au_unpin(&pin);
++ dput(wh_dentry);
++ dput(h_dentry);
++ out_args:
++ di_write_unlock(parent);
++ if (args)
++ au_whtmp_rmdir_free(args);
++ out_unlock:
++ aufs_read_unlock(dentry, AuLock_DW);
++ out:
++ AuTraceErr(err);
++ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/inode.c aufs2-2.6.git/fs/aufs/inode.c
---- linux-2.6.31/fs/aufs/inode.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/inode.c 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,414 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/i_op_ren.c linux-2.6.34/fs/aufs/i_op_ren.c
+--- linux-2.6.34.org/fs/aufs/i_op_ren.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/i_op_ren.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,977 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ */
+
+/*
-+ * inode functions
++ * inode operation (rename entry)
++ * todo: this is crazy monster
+ */
+
+#include "aufs.h"
+
-+struct inode *au_igrab(struct inode *inode)
-+{
-+ if (inode) {
-+ AuDebugOn(!atomic_read(&inode->i_count));
-+ atomic_inc_return(&inode->i_count);
-+ }
-+ return inode;
-+}
-+
-+static void au_refresh_hinode_attr(struct inode *inode, int do_version)
-+{
-+ au_cpup_attr_all(inode, /*force*/0);
-+ au_update_iigen(inode);
-+ if (do_version)
-+ inode->i_version++;
-+}
-+
-+int au_refresh_hinode_self(struct inode *inode, int do_attr)
-+{
-+ int err;
-+ aufs_bindex_t bindex, new_bindex;
-+ unsigned char update;
-+ struct inode *first;
-+ struct au_hinode *p, *q, tmp;
-+ struct super_block *sb;
-+ struct au_iinfo *iinfo;
-+
-+ IiMustWriteLock(inode);
-+
-+ update = 0;
-+ sb = inode->i_sb;
-+ iinfo = au_ii(inode);
-+ err = au_ii_realloc(iinfo, au_sbend(sb) + 1);
-+ if (unlikely(err))
-+ goto out;
-+
-+ p = iinfo->ii_hinode + iinfo->ii_bstart;
-+ first = p->hi_inode;
-+ err = 0;
-+ for (bindex = iinfo->ii_bstart; bindex <= iinfo->ii_bend;
-+ bindex++, p++) {
-+ if (!p->hi_inode)
-+ continue;
-+
-+ new_bindex = au_br_index(sb, p->hi_id);
-+ if (new_bindex == bindex)
-+ continue;
-+
-+ if (new_bindex < 0) {
-+ update++;
-+ au_hiput(p);
-+ p->hi_inode = NULL;
-+ continue;
-+ }
-+
-+ if (new_bindex < iinfo->ii_bstart)
-+ iinfo->ii_bstart = new_bindex;
-+ if (iinfo->ii_bend < new_bindex)
-+ iinfo->ii_bend = new_bindex;
-+ /* swap two lower inode, and loop again */
-+ q = iinfo->ii_hinode + new_bindex;
-+ tmp = *q;
-+ *q = *p;
-+ *p = tmp;
-+ if (tmp.hi_inode) {
-+ bindex--;
-+ p--;
-+ }
-+ }
-+ au_update_brange(inode, /*do_put_zero*/0);
-+ if (do_attr)
-+ au_refresh_hinode_attr(inode, update && S_ISDIR(inode->i_mode));
-+
-+ out:
-+ return err;
-+}
-+
-+int au_refresh_hinode(struct inode *inode, struct dentry *dentry)
-+{
-+ int err, update;
-+ unsigned int flags;
-+ aufs_bindex_t bindex, bend;
-+ unsigned char isdir;
-+ struct inode *first;
-+ struct au_hinode *p;
-+ struct au_iinfo *iinfo;
-+
-+ err = au_refresh_hinode_self(inode, /*do_attr*/0);
-+ if (unlikely(err))
-+ goto out;
-+
-+ update = 0;
-+ iinfo = au_ii(inode);
-+ p = iinfo->ii_hinode + iinfo->ii_bstart;
-+ first = p->hi_inode;
-+ isdir = S_ISDIR(inode->i_mode);
-+ flags = au_hi_flags(inode, isdir);
-+ bend = au_dbend(dentry);
-+ for (bindex = au_dbstart(dentry); bindex <= bend; bindex++) {
-+ struct inode *h_i;
-+ struct dentry *h_d;
-+
-+ h_d = au_h_dptr(dentry, bindex);
-+ if (!h_d || !h_d->d_inode)
-+ continue;
++enum { AuSRC, AuDST, AuSrcDst };
++enum { AuPARENT, AuCHILD, AuParentChild };
+
-+ if (iinfo->ii_bstart <= bindex && bindex <= iinfo->ii_bend) {
-+ h_i = au_h_iptr(inode, bindex);
-+ if (h_i) {
-+ if (h_i == h_d->d_inode)
-+ continue;
-+ err = -EIO;
-+ break;
-+ }
-+ }
-+ if (bindex < iinfo->ii_bstart)
-+ iinfo->ii_bstart = bindex;
-+ if (iinfo->ii_bend < bindex)
-+ iinfo->ii_bend = bindex;
-+ au_set_h_iptr(inode, bindex, au_igrab(h_d->d_inode), flags);
-+ update = 1;
-+ }
-+ au_update_brange(inode, /*do_put_zero*/0);
++#define AuRen_ISDIR 1
++#define AuRen_ISSAMEDIR (1 << 1)
++#define AuRen_WHSRC (1 << 2)
++#define AuRen_WHDST (1 << 3)
++#define AuRen_MNT_WRITE (1 << 4)
++#define AuRen_DT_DSTDIR (1 << 5)
++#define AuRen_DIROPQ (1 << 6)
++#define AuRen_CPUP (1 << 7)
++#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; }
+
-+ if (unlikely(err))
-+ goto out;
++struct au_ren_args {
++ struct {
++ struct dentry *dentry, *h_dentry, *parent, *h_parent,
++ *wh_dentry;
++ struct inode *dir, *inode;
++ struct au_hinode *hdir;
++ struct au_dtime dt[AuParentChild];
++ aufs_bindex_t bstart;
++ } sd[AuSrcDst];
+
-+ au_refresh_hinode_attr(inode, update && isdir);
++#define src_dentry sd[AuSRC].dentry
++#define src_dir sd[AuSRC].dir
++#define src_inode sd[AuSRC].inode
++#define src_h_dentry sd[AuSRC].h_dentry
++#define src_parent sd[AuSRC].parent
++#define src_h_parent sd[AuSRC].h_parent
++#define src_wh_dentry sd[AuSRC].wh_dentry
++#define src_hdir sd[AuSRC].hdir
++#define src_h_dir sd[AuSRC].hdir->hi_inode
++#define src_dt sd[AuSRC].dt
++#define src_bstart sd[AuSRC].bstart
+
-+ out:
-+ AuTraceErr(err);
-+ return err;
-+}
++#define dst_dentry sd[AuDST].dentry
++#define dst_dir sd[AuDST].dir
++#define dst_inode sd[AuDST].inode
++#define dst_h_dentry sd[AuDST].h_dentry
++#define dst_parent sd[AuDST].parent
++#define dst_h_parent sd[AuDST].h_parent
++#define dst_wh_dentry sd[AuDST].wh_dentry
++#define dst_hdir sd[AuDST].hdir
++#define dst_h_dir sd[AuDST].hdir->hi_inode
++#define dst_dt sd[AuDST].dt
++#define dst_bstart sd[AuDST].bstart
++
++ struct dentry *h_trap;
++ struct au_branch *br;
++ struct au_hinode *src_hinode;
++ struct path h_path;
++ struct au_nhash whlist;
++ aufs_bindex_t btgt;
+
-+static int set_inode(struct inode *inode, struct dentry *dentry)
-+{
-+ int err;
+ unsigned int flags;
-+ umode_t mode;
-+ aufs_bindex_t bindex, bstart, btail;
-+ unsigned char isdir;
-+ struct dentry *h_dentry;
-+ struct inode *h_inode;
-+ struct au_iinfo *iinfo;
+
-+ IiMustWriteLock(inode);
++ struct au_whtmp_rmdir *thargs;
++ struct dentry *h_dst;
++};
+
-+ err = 0;
-+ isdir = 0;
-+ bstart = au_dbstart(dentry);
-+ h_inode = au_h_dptr(dentry, bstart)->d_inode;
-+ mode = h_inode->i_mode;
-+ 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;
-+ btail = au_dbtaildir(dentry);
-+ inode->i_op = &aufs_dir_iop;
-+ inode->i_fop = &aufs_dir_fop;
-+ break;
-+ case S_IFLNK:
-+ btail = au_dbtail(dentry);
-+ inode->i_op = &aufs_symlink_iop;
-+ break;
-+ case S_IFBLK:
-+ case S_IFCHR:
-+ case S_IFIFO:
-+ case S_IFSOCK:
-+ btail = au_dbtail(dentry);
-+ inode->i_op = &aufs_iop;
-+ init_special_inode(inode, mode, h_inode->i_rdev);
-+ break;
-+ default:
-+ AuIOErr("Unknown file type 0%o\n", mode);
-+ err = -EIO;
-+ goto out;
-+ }
++/* ---------------------------------------------------------------------- */
+
-+ /* do not set inotify for whiteouted dirs (SHWH mode) */
-+ flags = au_hi_flags(inode, isdir);
-+ if (au_opt_test(au_mntflags(dentry->d_sb), SHWH)
-+ && au_ftest_hi(flags, HINOTIFY)
-+ && dentry->d_name.len > AUFS_WH_PFX_LEN
-+ && !memcmp(dentry->d_name.name, AUFS_WH_PFX, AUFS_WH_PFX_LEN))
-+ au_fclr_hi(flags, HINOTIFY);
-+ iinfo = au_ii(inode);
-+ iinfo->ii_bstart = bstart;
-+ iinfo->ii_bend = btail;
-+ for (bindex = bstart; bindex <= btail; bindex++) {
-+ h_dentry = au_h_dptr(dentry, bindex);
-+ if (h_dentry)
-+ au_set_h_iptr(inode, bindex,
-+ au_igrab(h_dentry->d_inode), flags);
-+ }
-+ au_cpup_attr_all(inode, /*force*/1);
++/*
++ * functions for reverting.
++ * when an error happened in a single rename systemcall, we should revert
++ * everything as if nothing happend.
++ * we don't need to revert the copied-up/down the parent dir since they are
++ * harmless.
++ */
+
-+ out:
-+ return err;
-+}
++#define RevertFailure(fmt, ...) do { \
++ AuIOErr("revert failure: " fmt " (%d, %d)\n", \
++ ##__VA_ARGS__, err, rerr); \
++ err = -EIO; \
++} while (0)
+
-+/* successful returns with iinfo write_locked */
-+static int reval_inode(struct inode *inode, struct dentry *dentry, int *matched)
++static void au_ren_rev_diropq(int err, struct au_ren_args *a)
+{
-+ int err;
-+ aufs_bindex_t bindex, bend;
-+ struct inode *h_inode, *h_dinode;
++ int rerr;
+
-+ *matched = 0;
++ au_hn_imtx_lock_nested(a->src_hinode, AuLsc_I_CHILD);
++ rerr = au_diropq_remove(a->src_dentry, a->btgt);
++ au_hn_imtx_unlock(a->src_hinode);
++ if (rerr)
++ RevertFailure("remove diropq %.*s", AuDLNPair(a->src_dentry));
++}
+
-+ /*
-+ * before this function, if aufs got any iinfo lock, it must be only
-+ * one, the parent dir.
-+ * it can happen by UDBA and the obsoleted inode number.
-+ */
-+ err = -EIO;
-+ if (unlikely(inode->i_ino == parent_ino(dentry)))
-+ goto out;
++static void au_ren_rev_rename(int err, struct au_ren_args *a)
++{
++ int rerr;
+
-+ err = 0;
-+ ii_write_lock_new_child(inode);
-+ h_dinode = au_h_dptr(dentry, au_dbstart(dentry))->d_inode;
-+ bend = au_ibend(inode);
-+ for (bindex = au_ibstart(inode); bindex <= bend; bindex++) {
-+ h_inode = au_h_iptr(inode, bindex);
-+ if (h_inode && h_inode == h_dinode) {
-+ *matched = 1;
-+ err = 0;
-+ if (au_iigen(inode) != au_digen(dentry))
-+ err = au_refresh_hinode(inode, dentry);
-+ break;
-+ }
++ a->h_path.dentry = au_lkup_one(&a->src_dentry->d_name, a->src_h_parent,
++ a->br, /*nd*/NULL);
++ rerr = PTR_ERR(a->h_path.dentry);
++ if (IS_ERR(a->h_path.dentry)) {
++ RevertFailure("au_lkup_one %.*s", AuDLNPair(a->src_dentry));
++ return;
+ }
+
-+ if (unlikely(err))
-+ ii_write_unlock(inode);
-+ out:
-+ return err;
++ rerr = vfsub_rename(a->dst_h_dir,
++ au_h_dptr(a->src_dentry, a->btgt),
++ a->src_h_dir, &a->h_path);
++ d_drop(a->h_path.dentry);
++ dput(a->h_path.dentry);
++ /* au_set_h_dptr(a->src_dentry, a->btgt, NULL); */
++ if (rerr)
++ RevertFailure("rename %.*s", AuDLNPair(a->src_dentry));
+}
+
-+int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
-+ unsigned int d_type, ino_t *ino)
++static void au_ren_rev_cpup(int err, struct au_ren_args *a)
+{
-+ int err;
-+ 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, ino);
-+ if (unlikely(err))
-+ goto out;
-+
-+ if (!*ino) {
-+ err = -EIO;
-+ *ino = au_xino_new_ino(sb);
-+ if (unlikely(!*ino))
-+ goto out;
-+ err = au_xino_write(sb, bindex, h_ino, *ino);
-+ if (unlikely(err))
-+ goto out;
-+ }
++ int rerr;
+
-+ out:
-+ if (!isdir)
-+ mutex_unlock(mtx);
-+ return err;
++ a->h_path.dentry = a->dst_h_dentry;
++ rerr = vfsub_unlink(a->dst_h_dir, &a->h_path, /*force*/0);
++ au_set_h_dptr(a->src_dentry, a->btgt, NULL);
++ au_set_dbstart(a->src_dentry, a->src_bstart);
++ if (rerr)
++ RevertFailure("unlink %.*s", AuDLNPair(a->dst_h_dentry));
+}
+
-+/* successful returns with iinfo write_locked */
-+/* todo: return with unlocked? */
-+struct inode *au_new_inode(struct dentry *dentry, int must_new)
++static void au_ren_rev_whtmp(int err, struct au_ren_args *a)
+{
-+ struct inode *inode;
-+ struct dentry *h_dentry;
-+ struct super_block *sb;
-+ ino_t h_ino, ino;
-+ int err, match;
-+ aufs_bindex_t bstart;
++ int rerr;
+
-+ sb = dentry->d_sb;
-+ bstart = au_dbstart(dentry);
-+ h_dentry = au_h_dptr(dentry, bstart);
-+ h_ino = h_dentry->d_inode->i_ino;
-+ err = au_xino_read(sb, bstart, h_ino, &ino);
-+ inode = ERR_PTR(err);
-+ if (unlikely(err))
-+ goto out;
-+ new_ino:
-+ if (!ino) {
-+ ino = au_xino_new_ino(sb);
-+ if (unlikely(!ino)) {
-+ inode = ERR_PTR(-EIO);
-+ goto out;
-+ }
++ a->h_path.dentry = au_lkup_one(&a->dst_dentry->d_name, a->dst_h_parent,
++ a->br, /*nd*/NULL);
++ rerr = PTR_ERR(a->h_path.dentry);
++ if (IS_ERR(a->h_path.dentry)) {
++ RevertFailure("lookup %.*s", AuDLNPair(a->dst_dentry));
++ return;
++ }
++ if (a->h_path.dentry->d_inode) {
++ d_drop(a->h_path.dentry);
++ dput(a->h_path.dentry);
++ return;
+ }
+
-+ AuDbg("i%lu\n", (unsigned long)ino);
-+ inode = au_iget_locked(sb, ino);
-+ err = PTR_ERR(inode);
-+ if (IS_ERR(inode))
-+ goto out;
++ rerr = vfsub_rename(a->dst_h_dir, a->h_dst, a->dst_h_dir, &a->h_path);
++ d_drop(a->h_path.dentry);
++ dput(a->h_path.dentry);
++ if (!rerr)
++ au_set_h_dptr(a->dst_dentry, a->btgt, dget(a->h_dst));
++ else
++ RevertFailure("rename %.*s", AuDLNPair(a->h_dst));
++}
+
-+ AuDbg("%lx, new %d\n", inode->i_state, !!(inode->i_state & I_NEW));
-+ if (inode->i_state & I_NEW) {
-+ ii_write_lock_new_child(inode);
-+ err = set_inode(inode, dentry);
-+ unlock_new_inode(inode);
-+ if (!err)
-+ goto out; /* success */
++static void au_ren_rev_whsrc(int err, struct au_ren_args *a)
++{
++ int rerr;
+
-+ iget_failed(inode);
-+ ii_write_unlock(inode);
-+ goto out_iput;
-+ } else if (!must_new) {
-+ err = reval_inode(inode, dentry, &match);
-+ if (!err)
-+ goto out; /* success */
-+ else if (match)
-+ goto out_iput;
-+ }
++ a->h_path.dentry = a->src_wh_dentry;
++ rerr = au_wh_unlink_dentry(a->src_h_dir, &a->h_path, a->src_dentry);
++ if (rerr)
++ RevertFailure("unlink %.*s", AuDLNPair(a->src_wh_dentry));
++}
+
-+ if (unlikely(au_test_fs_unique_ino(h_dentry->d_inode)))
-+ AuWarn1("Warning: 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),
-+ (unsigned long)h_ino, (unsigned long)ino);
-+ ino = 0;
-+ err = au_xino_write(sb, bstart, h_ino, /*ino*/0);
-+ if (!err) {
-+ iput(inode);
-+ goto new_ino;
++static void au_ren_rev_drop(struct au_ren_args *a)
++{
++ struct dentry *d, *h_d;
++ int i;
++ aufs_bindex_t bend, bindex;
++
++ for (i = 0; i < AuSrcDst; i++) {
++ d = a->sd[i].dentry;
++ d_drop(d);
++ bend = au_dbend(d);
++ for (bindex = au_dbstart(d); bindex <= bend; bindex++) {
++ h_d = au_h_dptr(d, bindex);
++ if (h_d)
++ d_drop(h_d);
++ }
+ }
+
-+ out_iput:
-+ iput(inode);
-+ inode = ERR_PTR(err);
-+ out:
-+ return inode;
++ au_update_dbstart(a->dst_dentry);
++ if (a->thargs)
++ d_drop(a->h_dst);
+}
++#undef RevertFailure
+
+/* ---------------------------------------------------------------------- */
+
-+int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
-+ struct inode *inode)
++/*
++ * when we have to copyup the renaming entry, do it with the rename-target name
++ * in order to minimize the cost (the later actual rename is unnecessary).
++ * otherwise rename it on the target branch.
++ */
++static int au_ren_or_cpup(struct au_ren_args *a)
+{
+ int err;
++ struct dentry *d;
+
-+ err = au_br_rdonly(au_sbr(sb, bindex));
++ d = a->src_dentry;
++ if (au_dbstart(d) == a->btgt) {
++ a->h_path.dentry = a->dst_h_dentry;
++ if (au_ftest_ren(a->flags, DIROPQ)
++ && au_dbdiropq(d) == a->btgt)
++ au_fclr_ren(a->flags, DIROPQ);
++ AuDebugOn(au_dbstart(d) != a->btgt);
++ err = vfsub_rename(a->src_h_dir, au_h_dptr(d, a->btgt),
++ a->dst_h_dir, &a->h_path);
++ } else {
++ struct mutex *h_mtx = &a->src_h_dentry->d_inode->i_mutex;
++ struct file *h_file;
+
-+ /* pseudo-link after flushed may happen out of bounds */
-+ if (!err
-+ && inode
-+ && au_ibstart(inode) <= bindex
-+ && bindex <= au_ibend(inode)) {
-+ /*
-+ * permission check is unnecessary since vfsub routine
-+ * will be called later
-+ */
-+ struct inode *hi = au_h_iptr(inode, bindex);
-+ if (hi)
-+ err = IS_IMMUTABLE(hi) ? -EROFS : 0;
++ au_fset_ren(a->flags, CPUP);
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ au_set_dbstart(d, a->btgt);
++ au_set_h_dptr(d, a->btgt, dget(a->dst_h_dentry));
++ h_file = au_h_open_pre(d, a->src_bstart);
++ if (IS_ERR(h_file)) {
++ err = PTR_ERR(h_file);
++ h_file = NULL;
++ } else
++ err = au_sio_cpup_single(d, a->btgt, a->src_bstart, -1,
++ !AuCpup_DTIME, a->dst_parent);
++ mutex_unlock(h_mtx);
++ au_h_open_post(d, a->src_bstart, h_file);
++ if (!err) {
++ d = a->dst_dentry;
++ au_set_h_dptr(d, a->btgt, NULL);
++ au_update_dbstart(d);
++ } else {
++ au_set_h_dptr(d, a->btgt, NULL);
++ au_set_dbstart(d, a->src_bstart);
++ }
+ }
+
+ return err;
+}
+
-+int au_test_h_perm(struct inode *h_inode, int mask)
++/* cf. aufs_rmdir() */
++static int au_ren_del_whtmp(struct au_ren_args *a)
+{
-+ if (!current_fsuid())
-+ return 0;
-+ return inode_permission(h_inode, mask);
-+}
++ int err;
++ struct inode *dir;
+
-+int au_test_h_perm_sio(struct inode *h_inode, int mask)
-+{
-+ if (au_test_nfs(h_inode->i_sb)
-+ && (mask & MAY_WRITE)
-+ && S_ISDIR(h_inode->i_mode))
-+ mask |= MAY_READ; /* force permission check */
-+ return au_test_h_perm(h_inode, mask);
++ dir = a->dst_dir;
++ SiMustAnyLock(dir->i_sb);
++ if (!au_nhash_test_longer_wh(&a->whlist, a->btgt,
++ au_sbi(dir->i_sb)->si_dirwh)
++ || au_test_fs_remote(a->h_dst->d_sb)) {
++ err = au_whtmp_rmdir(dir, a->btgt, a->h_dst, &a->whlist);
++ if (unlikely(err))
++ pr_warning("failed removing whtmp dir %.*s (%d), "
++ "ignored.\n", AuDLNPair(a->h_dst), err);
++ } else {
++ au_nhash_wh_free(&a->thargs->whlist);
++ a->thargs->whlist = a->whlist;
++ a->whlist.nh_num = 0;
++ au_whtmp_kick_rmdir(dir, a->btgt, a->h_dst, a->thargs);
++ dput(a->h_dst);
++ a->thargs = NULL;
++ }
++
++ return 0;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/inode.h aufs2-2.6.git/fs/aufs/inode.h
---- linux-2.6.31/fs/aufs/inode.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/inode.h 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,497 @@
-+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
-+ *
-+ * This program, aufs is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation; either version 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-+ */
+
-+/*
-+ * inode operations
-+ */
++/* make it 'opaque' dir. */
++static int au_ren_diropq(struct au_ren_args *a)
++{
++ int err;
++ struct dentry *diropq;
+
-+#ifndef __AUFS_INODE_H__
-+#define __AUFS_INODE_H__
++ err = 0;
++ a->src_hinode = au_hi(a->src_inode, a->btgt);
++ au_hn_imtx_lock_nested(a->src_hinode, AuLsc_I_CHILD);
++ diropq = au_diropq_create(a->src_dentry, a->btgt);
++ au_hn_imtx_unlock(a->src_hinode);
++ if (IS_ERR(diropq))
++ err = PTR_ERR(diropq);
++ dput(diropq);
+
-+#ifdef __KERNEL__
++ return err;
++}
+
-+#include <linux/fs.h>
-+#include <linux/inotify.h>
-+#include <linux/aufs_type.h>
-+#include "rwsem.h"
++static int do_rename(struct au_ren_args *a)
++{
++ int err;
++ struct dentry *d, *h_d;
+
-+struct vfsmount;
++ /* prepare workqueue args for asynchronous rmdir */
++ h_d = a->dst_h_dentry;
++ if (au_ftest_ren(a->flags, ISDIR) && h_d->d_inode) {
++ err = -ENOMEM;
++ a->thargs = au_whtmp_rmdir_alloc(a->src_dentry->d_sb, GFP_NOFS);
++ if (unlikely(!a->thargs))
++ goto out;
++ a->h_dst = dget(h_d);
++ }
+
-+struct au_hinotify {
-+#ifdef CONFIG_AUFS_HINOTIFY
-+ struct inotify_watch hin_watch;
-+ struct inode *hin_aufs_inode; /* no get/put */
-+#endif
-+};
++ /* create whiteout for src_dentry */
++ if (au_ftest_ren(a->flags, WHSRC)) {
++ a->src_wh_dentry
++ = au_wh_create(a->src_dentry, a->btgt, a->src_h_parent);
++ err = PTR_ERR(a->src_wh_dentry);
++ if (IS_ERR(a->src_wh_dentry))
++ goto out_thargs;
++ }
+
-+struct au_hinode {
-+ struct inode *hi_inode;
-+ aufs_bindex_t hi_id;
-+#ifdef CONFIG_AUFS_HINOTIFY
-+ struct au_hinotify *hi_notify;
-+#endif
++ /* lookup whiteout for dentry */
++ if (au_ftest_ren(a->flags, WHDST)) {
++ h_d = au_wh_lkup(a->dst_h_parent, &a->dst_dentry->d_name,
++ a->br);
++ err = PTR_ERR(h_d);
++ if (IS_ERR(h_d))
++ goto out_whsrc;
++ if (!h_d->d_inode)
++ dput(h_d);
++ else
++ a->dst_wh_dentry = h_d;
++ }
+
-+ /* reference to the copied-up whiteout with get/put */
-+ struct dentry *hi_whdentry;
-+};
++ /* rename dentry to tmpwh */
++ if (a->thargs) {
++ err = au_whtmp_ren(a->dst_h_dentry, a->br);
++ if (unlikely(err))
++ goto out_whdst;
+
-+struct au_vdir;
-+struct au_iinfo {
-+ atomic_t ii_generation;
-+ struct super_block *ii_hsb1; /* no get/put */
++ d = a->dst_dentry;
++ au_set_h_dptr(d, a->btgt, NULL);
++ err = au_lkup_neg(d, a->btgt);
++ if (unlikely(err))
++ goto out_whtmp;
++ a->dst_h_dentry = au_h_dptr(d, a->btgt);
++ }
+
-+ 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;
-+};
++ /* cpup src */
++ if (a->dst_h_dentry->d_inode && a->src_bstart != a->btgt) {
++ struct mutex *h_mtx = &a->src_h_dentry->d_inode->i_mutex;
++ struct file *h_file;
+
-+struct au_icntnr {
-+ struct au_iinfo iinfo;
-+ struct inode vfs_inode;
-+};
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ AuDebugOn(au_dbstart(a->src_dentry) != a->src_bstart);
++ h_file = au_h_open_pre(a->src_dentry, a->src_bstart);
++ if (IS_ERR(h_file)) {
++ err = PTR_ERR(h_file);
++ h_file = NULL;
++ } else
++ err = au_sio_cpup_simple(a->src_dentry, a->btgt, -1,
++ !AuCpup_DTIME);
++ mutex_unlock(h_mtx);
++ au_h_open_post(a->src_dentry, a->src_bstart, h_file);
++ if (unlikely(err))
++ goto out_whtmp;
++ }
+
-+/* au_pin flags */
-+#define AuPin_DI_LOCKED 1
-+#define AuPin_MNT_WRITE (1 << 1)
-+#define au_ftest_pin(flags, name) ((flags) & AuPin_##name)
-+#define au_fset_pin(flags, name) { (flags) |= AuPin_##name; }
-+#define au_fclr_pin(flags, name) { (flags) &= ~AuPin_##name; }
++ /* rename by vfs_rename or cpup */
++ d = a->dst_dentry;
++ if (au_ftest_ren(a->flags, ISDIR)
++ && (a->dst_wh_dentry
++ || au_dbdiropq(d) == a->btgt
++ /* hide the lower to keep xino */
++ || a->btgt < au_dbend(d)
++ || au_opt_test(au_mntflags(d->d_sb), ALWAYS_DIROPQ)))
++ au_fset_ren(a->flags, DIROPQ);
++ err = au_ren_or_cpup(a);
++ if (unlikely(err))
++ /* leave the copied-up one */
++ goto out_whtmp;
+
-+struct au_pin {
-+ /* input */
-+ struct dentry *dentry;
-+ unsigned int udba;
-+ unsigned char lsc_di, lsc_hi, flags;
-+ aufs_bindex_t bindex;
++ /* make dir opaque */
++ if (au_ftest_ren(a->flags, DIROPQ)) {
++ err = au_ren_diropq(a);
++ if (unlikely(err))
++ goto out_rename;
++ }
+
-+ /* output */
-+ struct dentry *parent;
-+ struct au_hinode *hdir;
-+ struct vfsmount *h_mnt;
-+};
++ /* update target timestamps */
++ AuDebugOn(au_dbstart(a->src_dentry) != a->btgt);
++ a->h_path.dentry = au_h_dptr(a->src_dentry, a->btgt);
++ vfsub_update_h_iattr(&a->h_path, /*did*/NULL); /*ignore*/
++ a->src_inode->i_ctime = a->h_path.dentry->d_inode->i_ctime;
+
-+/* ---------------------------------------------------------------------- */
++ /* remove whiteout for dentry */
++ if (a->dst_wh_dentry) {
++ a->h_path.dentry = a->dst_wh_dentry;
++ err = au_wh_unlink_dentry(a->dst_h_dir, &a->h_path,
++ a->dst_dentry);
++ if (unlikely(err))
++ goto out_diropq;
++ }
+
-+static inline struct au_iinfo *au_ii(struct inode *inode)
-+{
-+ struct au_iinfo *iinfo;
++ /* remove whtmp */
++ if (a->thargs)
++ au_ren_del_whtmp(a); /* ignore this error */
+
-+ iinfo = &(container_of(inode, struct au_icntnr, vfs_inode)->iinfo);
-+ if (iinfo->ii_hinode)
-+ return iinfo;
-+ return NULL; /* debugging bad_inode case */
++ err = 0;
++ goto out_success;
++
++ out_diropq:
++ if (au_ftest_ren(a->flags, DIROPQ))
++ au_ren_rev_diropq(err, a);
++ out_rename:
++ if (!au_ftest_ren(a->flags, CPUP))
++ au_ren_rev_rename(err, a);
++ else
++ au_ren_rev_cpup(err, a);
++ out_whtmp:
++ if (a->thargs)
++ au_ren_rev_whtmp(err, a);
++ out_whdst:
++ dput(a->dst_wh_dentry);
++ a->dst_wh_dentry = NULL;
++ out_whsrc:
++ if (a->src_wh_dentry)
++ au_ren_rev_whsrc(err, a);
++ au_ren_rev_drop(a);
++ out_success:
++ dput(a->src_wh_dentry);
++ dput(a->dst_wh_dentry);
++ out_thargs:
++ if (a->thargs) {
++ dput(a->h_dst);
++ au_whtmp_rmdir_free(a->thargs);
++ a->thargs = NULL;
++ }
++ out:
++ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+/* inode.c */
-+struct inode *au_igrab(struct inode *inode);
-+int au_refresh_hinode_self(struct inode *inode, int do_attr);
-+int au_refresh_hinode(struct inode *inode, struct dentry *dentry);
-+int au_ino(struct super_block *sb, aufs_bindex_t bindex, ino_t h_ino,
-+ unsigned int d_type, ino_t *ino);
-+struct inode *au_new_inode(struct dentry *dentry, int must_new);
-+int au_test_ro(struct super_block *sb, aufs_bindex_t bindex,
-+ struct inode *inode);
-+int au_test_h_perm(struct inode *h_inode, int mask);
-+int au_test_h_perm_sio(struct inode *h_inode, int mask);
-+
-+static inline int au_wh_ino(struct super_block *sb, aufs_bindex_t bindex,
-+ ino_t h_ino, unsigned int d_type, ino_t *ino)
++/*
++ * test if @dentry dir can be rename destination or not.
++ * success means, it is a logically empty dir.
++ */
++static int may_rename_dstdir(struct dentry *dentry, struct au_nhash *whlist)
+{
-+#ifdef CONFIG_AUFS_SHWH
-+ return au_ino(sb, bindex, h_ino, d_type, ino);
-+#else
-+ return 0;
-+#endif
++ return au_test_empty(dentry, whlist);
+}
+
-+/* i_op.c */
-+extern struct inode_operations aufs_iop, aufs_symlink_iop, aufs_dir_iop;
-+
-+/* au_wr_dir flags */
-+#define AuWrDir_ADD_ENTRY 1
-+#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 char flags;
-+};
-+int au_wr_dir(struct dentry *dentry, struct dentry *src_dentry,
-+ struct au_wr_dir_args *args);
-+
-+struct dentry *au_pinned_h_parent(struct au_pin *pin);
-+void au_pin_init(struct au_pin *pin, struct dentry *dentry,
-+ aufs_bindex_t bindex, int lsc_di, int lsc_hi,
-+ unsigned int udba, unsigned char flags);
-+int au_pin(struct au_pin *pin, struct dentry *dentry, aufs_bindex_t bindex,
-+ unsigned int udba, unsigned char flags) __must_check;
-+int au_do_pin(struct au_pin *pin) __must_check;
-+void au_unpin(struct au_pin *pin);
-+
-+/* i_op_add.c */
-+int au_may_add(struct dentry *dentry, aufs_bindex_t bindex,
-+ struct dentry *h_parent, int isdir);
-+int aufs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev);
-+int aufs_symlink(struct inode *dir, struct dentry *dentry, const char *symname);
-+int aufs_create(struct inode *dir, struct dentry *dentry, int mode,
-+ struct nameidata *nd);
-+int aufs_link(struct dentry *src_dentry, struct inode *dir,
-+ struct dentry *dentry);
-+int aufs_mkdir(struct inode *dir, struct dentry *dentry, int mode);
-+
-+/* i_op_del.c */
-+int 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);
-+int aufs_unlink(struct inode *dir, struct dentry *dentry);
-+int aufs_rmdir(struct inode *dir, struct dentry *dentry);
-+
-+/* i_op_ren.c */
-+int au_wbr(struct dentry *dentry, aufs_bindex_t btgt);
-+int aufs_rename(struct inode *src_dir, struct dentry *src_dentry,
-+ struct inode *dir, struct dentry *dentry);
-+
-+/* iinfo.c */
-+struct inode *au_h_iptr(struct inode *inode, aufs_bindex_t bindex);
-+void au_hiput(struct au_hinode *hinode);
-+void au_set_ibstart(struct inode *inode, aufs_bindex_t bindex);
-+void au_set_hi_wh(struct inode *inode, aufs_bindex_t bindex,
-+ struct dentry *h_wh);
-+unsigned int au_hi_flags(struct inode *inode, int isdir);
++/*
++ * test if @dentry dir can be rename source or not.
++ * if it can, return 0 and @children is filled.
++ * success means,
++ * - it is a logically empty dir.
++ * - or, it exists on writable branch and has no children including whiteouts
++ * on the lower branch.
++ */
++static int may_rename_srcdir(struct dentry *dentry, aufs_bindex_t btgt)
++{
++ int err;
++ unsigned int rdhash;
++ aufs_bindex_t bstart;
+
-+/* hinode flags */
-+#define AuHi_XINO 1
-+#define AuHi_HINOTIFY (1 << 1)
-+#define au_ftest_hi(flags, name) ((flags) & AuHi_##name)
-+#define au_fset_hi(flags, name) { (flags) |= AuHi_##name; }
-+#define au_fclr_hi(flags, name) { (flags) &= ~AuHi_##name; }
++ bstart = au_dbstart(dentry);
++ if (bstart != btgt) {
++ struct au_nhash whlist;
+
-+#ifndef CONFIG_AUFS_HINOTIFY
-+#undef AuHi_HINOTIFY
-+#define AuHi_HINOTIFY 0
-+#endif
++ SiMustAnyLock(dentry->d_sb);
++ rdhash = au_sbi(dentry->d_sb)->si_rdhash;
++ if (!rdhash)
++ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL,
++ dentry));
++ err = au_nhash_alloc(&whlist, rdhash, GFP_NOFS);
++ if (unlikely(err))
++ goto out;
++ err = au_test_empty(dentry, &whlist);
++ au_nhash_wh_free(&whlist);
++ goto out;
++ }
+
-+void au_set_h_iptr(struct inode *inode, aufs_bindex_t bindex,
-+ struct inode *h_inode, unsigned int flags);
++ if (bstart == au_dbtaildir(dentry))
++ return 0; /* success */
+
-+void au_update_iigen(struct inode *inode);
-+void au_update_brange(struct inode *inode, int do_put_zero);
++ err = au_test_empty_lower(dentry);
+
-+int au_iinfo_init(struct inode *inode);
-+void au_iinfo_fin(struct inode *inode);
-+int au_ii_realloc(struct au_iinfo *iinfo, int nbr);
++ out:
++ if (err == -ENOTEMPTY) {
++ AuWarn1("renaming dir who has child(ren) on multiple branches,"
++ " is not supported\n");
++ err = -EXDEV;
++ }
++ return err;
++}
+
-+/* plink.c */
-+void au_plink_block_maintain(struct super_block *sb);
-+#ifdef CONFIG_AUFS_DEBUG
-+void au_plink_list(struct super_block *sb);
-+#else
-+static inline void au_plink_list(struct super_block *sb)
++/* side effect: sets whlist and h_dentry */
++static int au_ren_may_dir(struct au_ren_args *a)
+{
-+ /* nothing */
-+}
-+#endif
-+int au_plink_test(struct inode *inode);
-+struct dentry *au_plink_lkup(struct inode *inode, aufs_bindex_t bindex);
-+void au_plink_append(struct inode *inode, aufs_bindex_t bindex,
-+ struct dentry *h_dentry);
-+void au_plink_put(struct super_block *sb);
-+void au_plink_half_refresh(struct super_block *sb, aufs_bindex_t br_id);
-+long au_plink_ioctl(struct file *file, unsigned int cmd);
++ int err;
++ unsigned int rdhash;
++ struct dentry *d;
+
-+/* ---------------------------------------------------------------------- */
++ d = a->dst_dentry;
++ SiMustAnyLock(d->d_sb);
+
-+/* lock subclass for iinfo */
-+enum {
-+ AuLsc_II_CHILD, /* child first */
-+ AuLsc_II_CHILD2, /* rename(2), link(2), and cpup at hinotify */
-+ AuLsc_II_CHILD3, /* copyup dirs */
-+ AuLsc_II_PARENT, /* see AuLsc_I_PARENT in vfsub.h */
-+ AuLsc_II_PARENT2,
-+ AuLsc_II_PARENT3, /* copyup dirs */
-+ AuLsc_II_NEW_CHILD
-+};
++ err = 0;
++ if (au_ftest_ren(a->flags, ISDIR) && a->dst_inode) {
++ rdhash = au_sbi(d->d_sb)->si_rdhash;
++ if (!rdhash)
++ rdhash = au_rdhash_est(au_dir_size(/*file*/NULL, d));
++ err = au_nhash_alloc(&a->whlist, rdhash, GFP_NOFS);
++ if (unlikely(err))
++ goto out;
+
-+/*
-+ * ii_read_lock_child, ii_write_lock_child,
-+ * ii_read_lock_child2, ii_write_lock_child2,
-+ * ii_read_lock_child3, ii_write_lock_child3,
-+ * ii_read_lock_parent, ii_write_lock_parent,
-+ * ii_read_lock_parent2, ii_write_lock_parent2,
-+ * ii_read_lock_parent3, ii_write_lock_parent3,
-+ * ii_read_lock_new_child, ii_write_lock_new_child,
-+ */
-+#define AuReadLockFunc(name, lsc) \
-+static inline void ii_read_lock_##name(struct inode *i) \
-+{ \
-+ au_rw_read_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \
++ au_set_dbstart(d, a->dst_bstart);
++ err = may_rename_dstdir(d, &a->whlist);
++ au_set_dbstart(d, a->btgt);
++ }
++ a->dst_h_dentry = au_h_dptr(d, au_dbstart(d));
++ if (unlikely(err))
++ goto out;
++
++ d = a->src_dentry;
++ a->src_h_dentry = au_h_dptr(d, au_dbstart(d));
++ if (au_ftest_ren(a->flags, ISDIR)) {
++ err = may_rename_srcdir(d, a->btgt);
++ if (unlikely(err)) {
++ au_nhash_wh_free(&a->whlist);
++ a->whlist.nh_num = 0;
++ }
++ }
++ out:
++ return err;
+}
+
-+#define AuWriteLockFunc(name, lsc) \
-+static inline void ii_write_lock_##name(struct inode *i) \
-+{ \
-+ au_rw_write_lock_nested(&au_ii(i)->ii_rwsem, AuLsc_II_##lsc); \
-+}
++/* ---------------------------------------------------------------------- */
+
-+#define AuRWLockFuncs(name, lsc) \
-+ AuReadLockFunc(name, lsc) \
-+ AuWriteLockFunc(name, lsc)
++/*
++ * simple tests for rename.
++ * following the checks in vfs, plus the parent-child relationship.
++ */
++static int au_may_ren(struct au_ren_args *a)
++{
++ int err, isdir;
++ struct inode *h_inode;
+
-+AuRWLockFuncs(child, CHILD);
-+AuRWLockFuncs(child2, CHILD2);
-+AuRWLockFuncs(child3, CHILD3);
-+AuRWLockFuncs(parent, PARENT);
-+AuRWLockFuncs(parent2, PARENT2);
-+AuRWLockFuncs(parent3, PARENT3);
-+AuRWLockFuncs(new_child, NEW_CHILD);
++ if (a->src_bstart == a->btgt) {
++ err = au_may_del(a->src_dentry, a->btgt, a->src_h_parent,
++ au_ftest_ren(a->flags, ISDIR));
++ if (unlikely(err))
++ goto out;
++ err = -EINVAL;
++ if (unlikely(a->src_h_dentry == a->h_trap))
++ goto out;
++ }
+
-+#undef AuReadLockFunc
-+#undef AuWriteLockFunc
-+#undef AuRWLockFuncs
++ err = 0;
++ if (a->dst_bstart != a->btgt)
++ goto out;
+
-+/*
-+ * ii_read_unlock, ii_write_unlock, ii_downgrade_lock
-+ */
-+AuSimpleUnlockRwsemFuncs(ii, struct inode *i, &au_ii(i)->ii_rwsem);
++ err = -EIO;
++ h_inode = a->dst_h_dentry->d_inode;
++ isdir = !!au_ftest_ren(a->flags, ISDIR);
++ if (!a->dst_dentry->d_inode) {
++ if (unlikely(h_inode))
++ goto out;
++ err = au_may_add(a->dst_dentry, a->btgt, a->dst_h_parent,
++ isdir);
++ } else {
++ if (unlikely(!h_inode || !h_inode->i_nlink))
++ goto out;
++ err = au_may_del(a->dst_dentry, a->btgt, a->dst_h_parent,
++ isdir);
++ if (unlikely(err))
++ goto out;
++ err = -ENOTEMPTY;
++ if (unlikely(a->dst_h_dentry == a->h_trap))
++ goto out;
++ err = 0;
++ }
+
-+#define IiMustNoWaiters(i) AuRwMustNoWaiters(&au_ii(i)->ii_rwsem)
-+#define IiMustAnyLock(i) AuRwMustAnyLock(&au_ii(i)->ii_rwsem)
-+#define IiMustWriteLock(i) AuRwMustWriteLock(&au_ii(i)->ii_rwsem)
++ out:
++ if (unlikely(err == -ENOENT || err == -EEXIST))
++ err = -EIO;
++ AuTraceErr(err);
++ return err;
++}
+
+/* ---------------------------------------------------------------------- */
+
-+static inline unsigned int au_iigen(struct inode *inode)
++/*
++ * 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()
++ * + lower src_dir and dir by vfsub_lock_rename()
++ * + verify the every relationships between child and parent. if any
++ * of them failed, unlock all and return -EBUSY.
++ */
++static void au_ren_unlock(struct au_ren_args *a)
+{
-+ return atomic_read(&au_ii(inode)->ii_generation);
++ struct super_block *sb;
++
++ sb = a->dst_dentry->d_sb;
++ if (au_ftest_ren(a->flags, MNT_WRITE))
++ mnt_drop_write(a->br->br_mnt);
++ vfsub_unlock_rename(a->src_h_parent, a->src_hdir,
++ a->dst_h_parent, a->dst_hdir);
+}
+
-+/* tiny test for inode number */
-+/* tmpfs generation is too rough */
-+static inline int au_test_higen(struct inode *inode, struct inode *h_inode)
++static int au_ren_lock(struct au_ren_args *a)
+{
-+ struct au_iinfo *iinfo;
++ int err;
++ unsigned int udba;
+
-+ iinfo = au_ii(inode);
-+ AuRwMustAnyLock(&iinfo->ii_rwsem);
-+ return !(iinfo->ii_hsb1 == h_inode->i_sb
-+ && iinfo->ii_higen == h_inode->i_generation);
++ err = 0;
++ a->src_h_parent = au_h_dptr(a->src_parent, a->btgt);
++ a->src_hdir = au_hi(a->src_dir, a->btgt);
++ a->dst_h_parent = au_h_dptr(a->dst_parent, a->btgt);
++ a->dst_hdir = au_hi(a->dst_dir, a->btgt);
++ a->h_trap = vfsub_lock_rename(a->src_h_parent, a->src_hdir,
++ a->dst_h_parent, a->dst_hdir);
++ udba = au_opt_udba(a->src_dentry->d_sb);
++ if (unlikely(a->src_hdir->hi_inode != a->src_h_parent->d_inode
++ || a->dst_hdir->hi_inode != a->dst_h_parent->d_inode))
++ err = au_busy_or_stale();
++ if (!err && au_dbstart(a->src_dentry) == a->btgt)
++ err = au_h_verify(a->src_h_dentry, udba,
++ a->src_h_parent->d_inode, a->src_h_parent,
++ a->br);
++ if (!err && au_dbstart(a->dst_dentry) == a->btgt)
++ err = au_h_verify(a->dst_h_dentry, udba,
++ a->dst_h_parent->d_inode, a->dst_h_parent,
++ a->br);
++ if (!err) {
++ err = mnt_want_write(a->br->br_mnt);
++ if (unlikely(err))
++ goto out_unlock;
++ au_fset_ren(a->flags, MNT_WRITE);
++ goto out; /* success */
++ }
++
++ err = au_busy_or_stale();
++
++ out_unlock:
++ au_ren_unlock(a);
++ out:
++ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
-+static inline aufs_bindex_t au_ii_br_id(struct inode *inode,
-+ aufs_bindex_t bindex)
++static void au_ren_refresh_dir(struct au_ren_args *a)
+{
-+ IiMustAnyLock(inode);
-+ return au_ii(inode)->ii_hinode[0 + bindex].hi_id;
-+}
++ struct inode *dir;
+
-+static inline aufs_bindex_t au_ibstart(struct inode *inode)
-+{
-+ IiMustAnyLock(inode);
-+ return au_ii(inode)->ii_bstart;
-+}
++ dir = a->dst_dir;
++ dir->i_version++;
++ if (au_ftest_ren(a->flags, ISDIR)) {
++ /* is this updating defined in POSIX? */
++ au_cpup_attr_timesizes(a->src_inode);
++ au_cpup_attr_nlink(dir, /*force*/1);
++ if (a->dst_inode) {
++ clear_nlink(a->dst_inode);
++ au_cpup_attr_timesizes(a->dst_inode);
++ }
++ }
++ if (au_ibstart(dir) == a->btgt)
++ au_cpup_attr_timesizes(dir);
+
-+static inline aufs_bindex_t au_ibend(struct inode *inode)
-+{
-+ IiMustAnyLock(inode);
-+ return au_ii(inode)->ii_bend;
-+}
++ if (au_ftest_ren(a->flags, ISSAMEDIR))
++ return;
+
-+static inline struct au_vdir *au_ivdir(struct inode *inode)
-+{
-+ IiMustAnyLock(inode);
-+ return au_ii(inode)->ii_vdir;
++ dir = a->src_dir;
++ dir->i_version++;
++ if (au_ftest_ren(a->flags, ISDIR))
++ au_cpup_attr_nlink(dir, /*force*/1);
++ if (au_ibstart(dir) == a->btgt)
++ au_cpup_attr_timesizes(dir);
+}
+
-+static inline struct dentry *au_hi_wh(struct inode *inode, aufs_bindex_t bindex)
++static void au_ren_refresh(struct au_ren_args *a)
+{
-+ IiMustAnyLock(inode);
-+ return au_ii(inode)->ii_hinode[0 + bindex].hi_whdentry;
-+}
++ aufs_bindex_t bend, bindex;
++ struct dentry *d, *h_d;
++ struct inode *i, *h_i;
++ struct super_block *sb;
+
-+static inline void au_set_ibend(struct inode *inode, aufs_bindex_t bindex)
-+{
-+ IiMustWriteLock(inode);
-+ au_ii(inode)->ii_bend = bindex;
++ d = a->src_dentry;
++ au_set_dbwh(d, -1);
++ bend = au_dbend(d);
++ for (bindex = a->btgt + 1; bindex <= bend; bindex++) {
++ h_d = au_h_dptr(d, bindex);
++ if (h_d)
++ au_set_h_dptr(d, bindex, NULL);
++ }
++ au_set_dbend(d, a->btgt);
++
++ sb = d->d_sb;
++ i = a->src_inode;
++ if (au_opt_test(au_mntflags(sb), PLINK) && au_plink_test(i))
++ return; /* success */
++
++ bend = au_ibend(i);
++ for (bindex = a->btgt + 1; bindex <= bend; bindex++) {
++ h_i = au_h_iptr(i, bindex);
++ if (h_i) {
++ au_xino_write(sb, bindex, h_i->i_ino, /*ino*/0);
++ /* ignore this error */
++ au_set_h_iptr(i, bindex, NULL, 0);
++ }
++ }
++ au_set_ibend(i, a->btgt);
+}
+
-+static inline void au_set_ivdir(struct inode *inode, struct au_vdir *vdir)
++/* ---------------------------------------------------------------------- */
++
++/* mainly for link(2) and rename(2) */
++int au_wbr(struct dentry *dentry, aufs_bindex_t btgt)
+{
-+ IiMustWriteLock(inode);
-+ au_ii(inode)->ii_vdir = vdir;
++ aufs_bindex_t bdiropq, bwh;
++ struct dentry *parent;
++ struct au_branch *br;
++
++ parent = dentry->d_parent;
++ IMustLock(parent->d_inode); /* dir is locked */
++
++ bdiropq = au_dbdiropq(parent);
++ bwh = au_dbwh(dentry);
++ br = au_sbr(dentry->d_sb, btgt);
++ if (au_br_rdonly(br)
++ || (0 <= bdiropq && bdiropq < btgt)
++ || (0 <= bwh && bwh < btgt))
++ btgt = -1;
++
++ AuDbg("btgt %d\n", btgt);
++ return btgt;
+}
+
-+static inline struct au_hinode *au_hi(struct inode *inode, aufs_bindex_t bindex)
++/* sets src_bstart, dst_bstart and btgt */
++static int au_ren_wbr(struct au_ren_args *a)
+{
-+ IiMustAnyLock(inode);
-+ return au_ii(inode)->ii_hinode + bindex;
-+}
++ int err;
++ struct au_wr_dir_args wr_dir_args = {
++ /* .force_btgt = -1, */
++ .flags = AuWrDir_ADD_ENTRY
++ };
+
-+/* ---------------------------------------------------------------------- */
++ a->src_bstart = au_dbstart(a->src_dentry);
++ a->dst_bstart = au_dbstart(a->dst_dentry);
++ if (au_ftest_ren(a->flags, ISDIR))
++ au_fset_wrdir(wr_dir_args.flags, ISDIR);
++ wr_dir_args.force_btgt = a->src_bstart;
++ if (a->dst_inode && a->dst_bstart < a->src_bstart)
++ wr_dir_args.force_btgt = a->dst_bstart;
++ wr_dir_args.force_btgt = au_wbr(a->dst_dentry, wr_dir_args.force_btgt);
++ err = au_wr_dir(a->dst_dentry, a->src_dentry, &wr_dir_args);
++ a->btgt = err;
+
-+static inline struct dentry *au_pinned_parent(struct au_pin *pin)
-+{
-+ if (pin)
-+ return pin->parent;
-+ return NULL;
++ return err;
+}
+
-+static inline struct inode *au_pinned_h_dir(struct au_pin *pin)
++static void au_ren_dt(struct au_ren_args *a)
+{
-+ if (pin && pin->hdir)
-+ return pin->hdir->hi_inode;
-+ return NULL;
-+}
++ a->h_path.dentry = a->src_h_parent;
++ au_dtime_store(a->src_dt + AuPARENT, a->src_parent, &a->h_path);
++ if (!au_ftest_ren(a->flags, ISSAMEDIR)) {
++ a->h_path.dentry = a->dst_h_parent;
++ au_dtime_store(a->dst_dt + AuPARENT, a->dst_parent, &a->h_path);
++ }
+
-+static inline struct au_hinode *au_pinned_hdir(struct au_pin *pin)
-+{
-+ if (pin)
-+ return pin->hdir;
-+ return NULL;
-+}
++ au_fclr_ren(a->flags, DT_DSTDIR);
++ if (!au_ftest_ren(a->flags, ISDIR))
++ return;
+
-+static inline void au_pin_set_dentry(struct au_pin *pin, struct dentry *dentry)
-+{
-+ if (pin)
-+ pin->dentry = dentry;
++ a->h_path.dentry = a->src_h_dentry;
++ au_dtime_store(a->src_dt + AuCHILD, a->src_dentry, &a->h_path);
++ if (a->dst_h_dentry->d_inode) {
++ au_fset_ren(a->flags, DT_DSTDIR);
++ a->h_path.dentry = a->dst_h_dentry;
++ au_dtime_store(a->dst_dt + AuCHILD, a->dst_dentry, &a->h_path);
++ }
+}
+
-+static inline void au_pin_set_parent_lflag(struct au_pin *pin,
-+ unsigned char lflag)
++static void au_ren_rev_dt(int err, struct au_ren_args *a)
+{
-+ if (pin) {
-+ /* dirty macros require brackets */
-+ if (lflag) {
-+ au_fset_pin(pin->flags, DI_LOCKED);
-+ } else {
-+ au_fclr_pin(pin->flags, DI_LOCKED);
++ struct dentry *h_d;
++ struct mutex *h_mtx;
++
++ au_dtime_revert(a->src_dt + AuPARENT);
++ if (!au_ftest_ren(a->flags, ISSAMEDIR))
++ au_dtime_revert(a->dst_dt + AuPARENT);
++
++ if (au_ftest_ren(a->flags, ISDIR) && err != -EIO) {
++ h_d = a->src_dt[AuCHILD].dt_h_path.dentry;
++ h_mtx = &h_d->d_inode->i_mutex;
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ au_dtime_revert(a->src_dt + AuCHILD);
++ mutex_unlock(h_mtx);
++
++ if (au_ftest_ren(a->flags, DT_DSTDIR)) {
++ h_d = a->dst_dt[AuCHILD].dt_h_path.dentry;
++ h_mtx = &h_d->d_inode->i_mutex;
++ mutex_lock_nested(h_mtx, AuLsc_I_CHILD);
++ au_dtime_revert(a->dst_dt + AuCHILD);
++ mutex_unlock(h_mtx);
+ }
+ }
+}
+
-+static inline void au_pin_set_parent(struct au_pin *pin, struct dentry *parent)
++/* ---------------------------------------------------------------------- */
++
++int aufs_rename(struct inode *_src_dir, struct dentry *_src_dentry,
++ struct inode *_dst_dir, struct dentry *_dst_dentry)
+{
-+ if (pin) {
-+ dput(pin->parent);
-+ pin->parent = dget(parent);
++ int err;
++ /* reduce stack space */
++ struct au_ren_args *a;
++
++ AuDbg("%.*s, %.*s\n", AuDLNPair(_src_dentry), AuDLNPair(_dst_dentry));
++ IMustLock(_src_dir);
++ IMustLock(_dst_dir);
++
++ err = -ENOMEM;
++ BUILD_BUG_ON(sizeof(*a) > PAGE_SIZE);
++ a = kzalloc(sizeof(*a), GFP_NOFS);
++ if (unlikely(!a))
++ goto out;
++
++ a->src_dir = _src_dir;
++ a->src_dentry = _src_dentry;
++ a->src_inode = a->src_dentry->d_inode;
++ a->src_parent = a->src_dentry->d_parent; /* dir inode is locked */
++ a->dst_dir = _dst_dir;
++ a->dst_dentry = _dst_dentry;
++ a->dst_inode = a->dst_dentry->d_inode;
++ a->dst_parent = a->dst_dentry->d_parent; /* dir inode is locked */
++ if (a->dst_inode) {
++ IMustLock(a->dst_inode);
++ au_igrab(a->dst_inode);
+ }
-+}
+
-+/* ---------------------------------------------------------------------- */
++ err = -ENOTDIR;
++ if (S_ISDIR(a->src_inode->i_mode)) {
++ au_fset_ren(a->flags, ISDIR);
++ if (unlikely(a->dst_inode && !S_ISDIR(a->dst_inode->i_mode)))
++ goto out_free;
++ aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry,
++ AuLock_DIR | AuLock_FLUSH);
++ } else
++ aufs_read_and_write_lock2(a->dst_dentry, a->src_dentry,
++ AuLock_FLUSH);
+
-+#ifdef CONFIG_AUFS_HINOTIFY
-+/* hinotify.c */
-+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_hin_ctl(struct au_hinode *hinode, int do_set);
-+void au_reset_hinotify(struct inode *inode, unsigned int flags);
++ au_fset_ren(a->flags, ISSAMEDIR); /* temporary */
++ di_write_lock_parent(a->dst_parent);
+
-+int __init au_hinotify_init(void);
-+void au_hinotify_fin(void);
++ /* which branch we process */
++ err = au_ren_wbr(a);
++ if (unlikely(err < 0))
++ goto out_unlock;
++ a->br = au_sbr(a->dst_dentry->d_sb, a->btgt);
++ a->h_path.mnt = a->br->br_mnt;
+
-+static inline
-+void au_hin_init(struct au_hinode *hinode, struct au_hinotify *val)
-+{
-+ hinode->hi_notify = val;
-+}
++ /* are they available to be renamed */
++ err = au_ren_may_dir(a);
++ if (unlikely(err))
++ goto out_children;
+
-+static inline void au_iigen_dec(struct inode *inode)
-+{
-+ atomic_dec_return(&au_ii(inode)->ii_generation);
-+}
++ /* prepare the writable parent dir on the same branch */
++ if (a->dst_bstart == a->btgt) {
++ au_fset_ren(a->flags, WHDST);
++ } else {
++ err = au_cpup_dirs(a->dst_dentry, a->btgt);
++ if (unlikely(err))
++ goto out_children;
++ }
+
-+#else
-+static inline
-+int au_hin_alloc(struct au_hinode *hinode __maybe_unused,
-+ struct inode *inode __maybe_unused,
-+ struct inode *h_inode __maybe_unused)
-+{
-+ return -EOPNOTSUPP;
-+}
++ if (a->src_dir != a->dst_dir) {
++ /*
++ * this temporary unlock is safe,
++ * because both dir->i_mutex are locked.
++ */
++ di_write_unlock(a->dst_parent);
++ di_write_lock_parent(a->src_parent);
++ err = au_wr_dir_need_wh(a->src_dentry,
++ au_ftest_ren(a->flags, ISDIR),
++ &a->btgt);
++ di_write_unlock(a->src_parent);
++ di_write_lock2_parent(a->src_parent, a->dst_parent, /*isdir*/1);
++ au_fclr_ren(a->flags, ISSAMEDIR);
++ } else
++ err = au_wr_dir_need_wh(a->src_dentry,
++ au_ftest_ren(a->flags, ISDIR),
++ &a->btgt);
++ if (unlikely(err < 0))
++ goto out_children;
++ if (err)
++ au_fset_ren(a->flags, WHSRC);
+
-+static inline void au_hin_free(struct au_hinode *hinode __maybe_unused)
-+{
-+ /* nothing */
-+}
++ /* lock them all */
++ err = au_ren_lock(a);
++ if (unlikely(err))
++ goto out_children;
+
-+static inline void au_hin_ctl(struct au_hinode *hinode __maybe_unused,
-+ int do_set __maybe_unused)
-+{
-+ /* nothing */
-+}
++ if (!au_opt_test(au_mntflags(a->dst_dir->i_sb), UDBA_NONE))
++ err = au_may_ren(a);
++ else if (unlikely(a->dst_dentry->d_name.len > AUFS_MAX_NAMELEN))
++ err = -ENAMETOOLONG;
++ if (unlikely(err))
++ goto out_hdir;
+
-+static inline void au_reset_hinotify(struct inode *inode __maybe_unused,
-+ unsigned int flags __maybe_unused)
-+{
-+ /* nothing */
-+}
++ /* store timestamps to be revertible */
++ au_ren_dt(a);
+
-+static inline int au_hinotify_init(void)
-+{
-+ return 0;
++ /* here we go */
++ err = do_rename(a);
++ if (unlikely(err))
++ goto out_dt;
++
++ /* update dir attributes */
++ au_ren_refresh_dir(a);
++
++ /* dput/iput all lower dentries */
++ au_ren_refresh(a);
++
++ goto out_hdir; /* success */
++
++ out_dt:
++ au_ren_rev_dt(err, a);
++ out_hdir:
++ au_ren_unlock(a);
++ out_children:
++ au_nhash_wh_free(&a->whlist);
++ out_unlock:
++ if (unlikely(err && au_ftest_ren(a->flags, ISDIR))) {
++ au_update_dbstart(a->dst_dentry);
++ d_drop(a->dst_dentry);
++ }
++ if (!err)
++ d_move(a->src_dentry, a->dst_dentry);
++ if (au_ftest_ren(a->flags, ISSAMEDIR))
++ di_write_unlock(a->dst_parent);
++ else
++ di_write_unlock2(a->src_parent, a->dst_parent);
++ aufs_read_and_write_unlock2(a->dst_dentry, a->src_dentry);
++ out_free:
++ iput(a->dst_inode);
++ if (a->thargs)
++ au_whtmp_rmdir_free(a->thargs);
++ kfree(a);
++ out:
++ AuTraceErr(err);
++ return err;
+}
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/Kconfig linux-2.6.34/fs/aufs/Kconfig
+--- linux-2.6.34.org/fs/aufs/Kconfig 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/Kconfig 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,175 @@
++config AUFS_FS
++ tristate "Aufs (Advanced multi layered unification filesystem) support"
++ depends on EXPERIMENTAL
++ help
++ Aufs is a stackable unification filesystem such as Unionfs,
++ which unifies several directories and provides a merged single
++ directory.
++ In the early days, aufs was entirely re-designed and
++ re-implemented Unionfs Version 1.x series. Introducing many
++ original ideas, approaches and improvements, it becomes totally
++ different from Unionfs while keeping the basic features.
+
-+#define au_hinotify_fin() do {} while (0)
++if AUFS_FS
++choice
++ prompt "Maximum number of branches"
++ default AUFS_BRANCH_MAX_127
++ help
++ Specifies the maximum number of branches (or member directories)
++ in a single aufs. The larger value consumes more system
++ resources and has a minor impact to performance.
++config AUFS_BRANCH_MAX_127
++ bool "127"
++ help
++ Specifies the maximum number of branches (or member directories)
++ in a single aufs. The larger value consumes more system
++ resources and has a minor impact to performance.
++config AUFS_BRANCH_MAX_511
++ bool "511"
++ help
++ Specifies the maximum number of branches (or member directories)
++ in a single aufs. The larger value consumes more system
++ resources and has a minor impact to performance.
++config AUFS_BRANCH_MAX_1023
++ bool "1023"
++ help
++ Specifies the maximum number of branches (or member directories)
++ in a single aufs. The larger value consumes more system
++ resources and has a minor impact to performance.
++config AUFS_BRANCH_MAX_32767
++ bool "32767"
++ help
++ Specifies the maximum number of branches (or member directories)
++ in a single aufs. The larger value consumes more system
++ resources and has a minor impact to performance.
++endchoice
+
-+static inline
-+void au_hin_init(struct au_hinode *hinode __maybe_unused,
-+ struct au_hinotify *val __maybe_unused)
-+{
-+ /* empty */
-+}
-+#endif /* CONFIG_AUFS_HINOTIFY */
++config AUFS_HNOTIFY
++ bool "Detect direct branch access (bypassing aufs)"
++ 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=notify' mount option.
++ It will have a negative impact to the performance.
++ See detail in aufs.5.
+
-+static inline void au_hin_suspend(struct au_hinode *hdir)
-+{
-+ au_hin_ctl(hdir, /*do_set*/0);
-+}
+
-+static inline void au_hin_resume(struct au_hinode *hdir)
-+{
-+ au_hin_ctl(hdir, /*do_set*/1);
-+}
++choice
++ prompt "method" if AUFS_HNOTIFY
++ default AUFS_HFSNOTIFY
++config AUFS_HFSNOTIFY
++ bool "fsnotify"
++ select FSNOTIFY
++config AUFS_HINOTIFY
++ bool "inotify (DEPRECATED)"
++ depends on INOTIFY
++endchoice
+
-+static inline void au_hin_imtx_lock(struct au_hinode *hdir)
-+{
-+ mutex_lock(&hdir->hi_inode->i_mutex);
-+ au_hin_suspend(hdir);
-+}
++config AUFS_EXPORT
++ bool "NFS-exportable aufs"
++ depends on (AUFS_FS = y && EXPORTFS = y) || (AUFS_FS = m && EXPORTFS)
++ help
++ If you want to export your mounted aufs via NFS, then enable this
++ option. There are several requirements for this configuration.
++ See detail in aufs.5.
+
-+static inline void au_hin_imtx_lock_nested(struct au_hinode *hdir,
-+ unsigned int sc __maybe_unused)
-+{
-+ mutex_lock_nested(&hdir->hi_inode->i_mutex, sc);
-+ au_hin_suspend(hdir);
-+}
++config AUFS_INO_T_64
++ bool
++ depends on AUFS_EXPORT
++ depends on 64BIT && !(ALPHA || S390)
++ default y
++ help
++ Automatic configuration for internal use.
++ /* typedef unsigned long/int __kernel_ino_t */
++ /* alpha and s390x are int */
+
-+static inline void au_hin_imtx_unlock(struct au_hinode *hdir)
-+{
-+ au_hin_resume(hdir);
-+ mutex_unlock(&hdir->hi_inode->i_mutex);
-+}
++config AUFS_RDU
++ bool "Readdir in userspace"
++ help
++ Aufs has two methods to provide a merged view for a directory,
++ by a user-space library and by kernel-space natively. The latter
++ is always enabled but sometimes large and slow.
++ If you enable this option, install the library in aufs2-util
++ package, and set some environment variables for your readdir(3),
++ then the work will be handled in user-space which generally
++ shows better performance in most cases.
++ See detail in aufs.5.
+
-+#endif /* __KERNEL__ */
-+#endif /* __AUFS_INODE_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/ioctl.c aufs2-2.6.git/fs/aufs/ioctl.c
---- linux-2.6.31/fs/aufs/ioctl.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/ioctl.c 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,47 @@
-+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
-+ *
-+ * This program, aufs is free software; you can redistribute it and/or modify
-+ * it under the terms of the GNU General Public License as published by
-+ * the Free Software Foundation; either version 2 of the License, or
-+ * (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful,
-+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-+ * GNU General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-+ */
++config AUFS_SP_IATTR
++ bool "Respect the attributes (mtime/ctime mainly) of special files"
++ help
++ When you write something to a special file, some attributes of it
++ (mtime/ctime mainly) may be updated. Generally such updates are
++ less important (actually some device drivers and NFS ignore
++ it). But some applications (such like test program) requires
++ such updates. If you need these updates, then enable this
++ configuration which introduces some overhead.
++ Currently this configuration handles FIFO only.
+
-+/*
-+ * ioctl
-+ * plink-management and readdir in userspace.
-+ */
++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.
+
-+#include "aufs.h"
++config AUFS_BR_RAMFS
++ bool "Ramfs (initramfs/rootfs) as an aufs branch"
++ help
++ If you want to use ramfs as an aufs branch fs, then enable this
++ option. Generally tmpfs is recommended.
++ Aufs prohibited them to be a branch fs by default, because
++ initramfs becomes unusable after switch_root or something
++ generally. If you sets initramfs as an aufs branch and boot your
++ system by switch_root, you will meet a problem easily since the
++ files in initramfs may be inaccessible.
++ Unless you are going to use ramfs as an aufs branch fs without
++ switch_root or something, leave it N.
+
-+long aufs_ioctl_dir(struct file *file, unsigned int cmd, unsigned long arg)
-+{
-+ long err;
++config AUFS_BR_FUSE
++ bool "Fuse fs as an aufs branch"
++ depends on FUSE_FS
++ select AUFS_POLL
++ help
++ If you want to use fuse-based userspace filesystem as an aufs
++ branch fs, then enable this option.
++ It implements the internal poll(2) operation which is
++ implemented by fuse only (curretnly).
+
-+ switch (cmd) {
-+ case AUFS_CTL_PLINK_MAINT:
-+ case AUFS_CTL_PLINK_CLEAN:
-+ err = au_plink_ioctl(file, cmd);
-+ break;
++config AUFS_POLL
++ bool
++ help
++ Automatic configuration for internal use.
+
-+ case AUFS_CTL_RDU:
-+ case AUFS_CTL_RDU_INO:
-+ err = au_rdu_ioctl(file, cmd, arg);
-+ break;
++config AUFS_BR_HFSPLUS
++ bool "Hfsplus as an aufs branch"
++ depends on HFSPLUS_FS
++ default y
++ help
++ If you want to use hfsplus fs as an aufs branch fs, then enable
++ this option. This option introduces a small overhead at
++ copying-up a file on hfsplus.
+
-+ default:
-+ err = -EINVAL;
-+ }
++config AUFS_BDEV_LOOP
++ bool
++ depends on BLK_DEV_LOOP
++ default y
++ help
++ Automatic configuration for internal use.
++ Convert =[ym] into =y.
+
-+ AuTraceErr(err);
-+ return err;
-+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/loop.c aufs2-2.6.git/fs/aufs/loop.c
---- linux-2.6.31/fs/aufs/loop.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/loop.c 2009-09-21 21:49:23.404940801 +0000
++config AUFS_DEBUG
++ bool "Debug aufs"
++ 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.
++endif
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/loop.c linux-2.6.34/fs/aufs/loop.c
+--- linux-2.6.34.org/fs/aufs/loop.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/loop.c 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,55 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ /* h_d1 can be local NFS. in this case aufs cannot detect the loop */
+ if (unlikely(h_d1->d_sb == sb))
+ return 1;
-+ return !!au_test_subdir(h_d1, h_d2);
++ return au_test_subdir(h_d1, h_d2);
+}
+
+/* true if a kernel thread named 'loop[0-9].*' accesses a file */
+ && '0' <= c && c <= '9'
+ && strncmp(current->comm, "loop", 4) == 0;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/loop.h aufs2-2.6.git/fs/aufs/loop.h
---- linux-2.6.31/fs/aufs/loop.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/loop.h 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,51 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/loop.h linux-2.6.34/fs/aufs/loop.h
+--- linux-2.6.34.org/fs/aufs/loop.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/loop.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,43 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ struct dentry *h_d2);
+int au_test_loopback_kthread(void);
+#else
-+static inline
-+int au_test_loopback_overlap(struct super_block *sb, struct dentry *h_d1,
-+ struct dentry *h_d2)
-+{
-+ return 0;
-+}
-+
-+static inline int au_test_loopback_kthread(void)
-+{
-+ return 0;
-+}
++AuStubInt0(au_test_loopback_overlap, struct super_block *sb,
++ struct dentry *h_d1, struct dentry *h_d2)
++AuStubInt0(au_test_loopback_kthread, void)
+#endif /* BLK_DEV_LOOP */
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_LOOP_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/magic.mk aufs2-2.6.git/fs/aufs/magic.mk
---- linux-2.6.31/fs/aufs/magic.mk 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/magic.mk 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,52 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/magic.mk linux-2.6.34/fs/aufs/magic.mk
+--- linux-2.6.34.org/fs/aufs/magic.mk 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/magic.mk 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,54 @@
+
+# defined in ${srctree}/fs/fuse/inode.c
+# tristate
+ccflags-y += -DDLMFS_MAGIC=0x76a9f425
+endif
+
-+# defined in ${srctree}/fs/ramfs/inode.c
-+# always true
-+ccflags-y += -DRAMFS_MAGIC=0x858458f6
-+
+# defined in ${srctree}/fs/cifs/cifsfs.c
+# tristate
+ifdef CONFIG_CIFS_FS
+ifdef CONFIG_UBIFS_FS
+ccflags-y += -DUBIFS_SUPER_MAGIC=0x24051905
+endif
-diff -uprN -x .git linux-2.6.31/fs/aufs/module.c aufs2-2.6.git/fs/aufs/module.c
---- linux-2.6.31/fs/aufs/module.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/module.c 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,173 @@
++
++# defined in ${srctree}/fs/hfsplus/hfsplus_raw.h
++# tristate
++ifdef CONFIG_HFSPLUS_FS
++ccflags-y += -DHFSPLUS_SUPER_MAGIC=0x482b
++endif
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/Makefile linux-2.6.34/fs/aufs/Makefile
+--- linux-2.6.34.org/fs/aufs/Makefile 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/Makefile 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,38 @@
++
++include ${src}/magic.mk
++ifeq (${CONFIG_AUFS_FS},m)
++include ${src}/conf.mk
++endif
++-include ${src}/priv_def.mk
++
++# cf. include/linux/kernel.h
++# enable pr_debug
++ccflags-y += -DDEBUG
++ccflags-y += -D'pr_fmt(fmt)="aufs %s:%d:%s[%d]: " fmt, \
++ __func__, __LINE__, current->comm, current->pid'
++
++obj-$(CONFIG_AUFS_FS) += aufs.o
++aufs-y := module.o sbinfo.o super.o branch.o xino.o sysaufs.o opts.o \
++ wkq.o vfsub.o dcsub.o \
++ cpup.o whout.o plink.o wbr_policy.o \
++ dinfo.o dentry.o \
++ dynop.o \
++ finfo.o file.o f_op.o \
++ dir.o vdir.o \
++ iinfo.o inode.o i_op.o i_op_add.o i_op_del.o i_op_ren.o \
++ ioctl.o
++
++# all are boolean
++aufs-$(CONFIG_SYSFS) += sysfs.o
++aufs-$(CONFIG_DEBUG_FS) += dbgaufs.o
++aufs-$(CONFIG_AUFS_BDEV_LOOP) += loop.o
++aufs-$(CONFIG_AUFS_HNOTIFY) += hnotify.o
++aufs-$(CONFIG_AUFS_HFSNOTIFY) += hfsnotify.o
++aufs-$(CONFIG_AUFS_HINOTIFY) += hinotify.o
++aufs-$(CONFIG_AUFS_EXPORT) += export.o
++aufs-$(CONFIG_AUFS_POLL) += poll.o
++aufs-$(CONFIG_AUFS_RDU) += rdu.o
++aufs-$(CONFIG_AUFS_SP_IATTR) += f_op_sp.o
++aufs-$(CONFIG_AUFS_BR_HFSPLUS) += hfsplus.o
++aufs-$(CONFIG_AUFS_DEBUG) += debug.o
++aufs-$(CONFIG_AUFS_MAGIC_SYSRQ) += sysrq.o
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/module.c linux-2.6.34/fs/aufs/module.c
+--- linux-2.6.34.org/fs/aufs/module.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/module.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,171 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+struct kmem_cache *au_cachep[AuCache_Last];
+static int __init au_cache_init(void)
+{
-+ au_cachep[AuCache_DINFO] = AuCache(au_dinfo);
++ au_cachep[AuCache_DINFO] = AuCacheCtor(au_dinfo, au_di_init_once);
+ if (au_cachep[AuCache_DINFO])
-+ au_cachep[AuCache_ICNTNR] = AuCache(au_icntnr);
++ au_cachep[AuCache_ICNTNR] = AuCacheCtor(au_icntnr,
++ au_icntnr_init_once);
+ if (au_cachep[AuCache_ICNTNR])
-+ au_cachep[AuCache_FINFO] = AuCache(au_finfo);
++ au_cachep[AuCache_FINFO] = AuCacheCtor(au_finfo,
++ au_fi_init_once);
+ if (au_cachep[AuCache_FINFO])
+ au_cachep[AuCache_VDIR] = AuCache(au_vdir);
+ if (au_cachep[AuCache_VDIR])
+static void au_cache_fin(void)
+{
+ int i;
++
++ /* including AuCache_HNOTIFY */
+ for (i = 0; i < AuCache_Last; i++)
+ if (au_cachep[i]) {
+ kmem_cache_destroy(au_cachep[i]);
+ " -- Advanced multi layered unification filesystem");
+MODULE_VERSION(AUFS_VERSION);
+
-+/* it should be 'byte', but param_set_byte() prints it by "%c" */
-+short aufs_nwkq = AUFS_NWKQ_DEF;
-+MODULE_PARM_DESC(nwkq, "the number of workqueue thread, " AUFS_WKQ_NAME);
-+module_param_named(nwkq, aufs_nwkq, short, S_IRUGO);
-+
+/* this module parameter has no meaning when SYSFS is disabled */
+int sysaufs_brs = 1;
+MODULE_PARM_DESC(brs, "use <sysfs>/fs/aufs/si_*/brN");
+
+ sysaufs_brs_init();
+ au_debug_init();
-+
-+ err = -EINVAL;
-+ if (unlikely(aufs_nwkq <= 0))
-+ goto out;
-+
++ au_dy_init();
+ err = sysaufs_init();
+ if (unlikely(err))
+ goto out;
+ err = au_wkq_init();
+ if (unlikely(err))
+ goto out_sysaufs;
-+ err = au_hinotify_init();
++ err = au_hnotify_init();
+ if (unlikely(err))
+ goto out_wkq;
+ err = au_sysrq_init();
+ err = register_filesystem(&aufs_fs_type);
+ if (unlikely(err))
+ goto out_cache;
-+ pr_info(AUFS_NAME " " AUFS_VERSION "\n");
++ /* since we define pr_fmt, call printk directly */
++ printk(KERN_INFO AUFS_NAME " " AUFS_VERSION "\n");
+ goto out; /* success */
+
+ out_cache:
+ out_sysrq:
+ au_sysrq_fin();
+ out_hin:
-+ au_hinotify_fin();
++ au_hnotify_fin();
+ out_wkq:
+ au_wkq_fin();
+ out_sysaufs:
+ sysaufs_fin();
++ au_dy_fin();
+ out:
+ return err;
+}
+ unregister_filesystem(&aufs_fs_type);
+ au_cache_fin();
+ au_sysrq_fin();
-+ au_hinotify_fin();
++ au_hnotify_fin();
+ au_wkq_fin();
+ sysaufs_fin();
++ au_dy_fin();
+}
+
+module_init(aufs_init);
+module_exit(aufs_exit);
-diff -uprN -x .git linux-2.6.31/fs/aufs/module.h aufs2-2.6.git/fs/aufs/module.h
---- linux-2.6.31/fs/aufs/module.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/module.h 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,78 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/module.h linux-2.6.34/fs/aufs/module.h
+--- linux-2.6.34.org/fs/aufs/module.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/module.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,82 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+struct seq_file;
+
+/* module parameters */
-+extern short aufs_nwkq;
+extern int sysaufs_brs;
+
+/* ---------------------------------------------------------------------- */
+ AuCache_FINFO,
+ AuCache_VDIR,
+ AuCache_DEHSTR,
-+#ifdef CONFIG_AUFS_HINOTIFY
-+ AuCache_HINOTIFY,
++#ifdef CONFIG_AUFS_HNOTIFY
++ AuCache_HNOTIFY,
+#endif
+ AuCache_Last
+};
+
-+#define AuCache(type) KMEM_CACHE(type, SLAB_RECLAIM_ACCOUNT)
++#define AuCacheFlags (SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD)
++#define AuCache(type) KMEM_CACHE(type, AuCacheFlags)
++#define AuCacheCtor(type, ctor) \
++ kmem_cache_create(#type, sizeof(struct type), \
++ __alignof__(struct type), AuCacheFlags, ctor)
+
+extern struct kmem_cache *au_cachep[];
+
+#define AuCacheFuncs(name, index) \
-+static inline void *au_cache_alloc_##name(void) \
++static inline struct au_##name *au_cache_alloc_##name(void) \
+{ return kmem_cache_alloc(au_cachep[AuCache_##index], GFP_NOFS); } \
-+static inline void au_cache_free_##name(void *p) \
++static inline void au_cache_free_##name(struct au_##name *p) \
+{ kmem_cache_free(au_cachep[AuCache_##index], p); }
+
+AuCacheFuncs(dinfo, DINFO);
+AuCacheFuncs(icntnr, ICNTNR);
+AuCacheFuncs(finfo, FINFO);
+AuCacheFuncs(vdir, VDIR);
-+AuCacheFuncs(dehstr, DEHSTR);
++AuCacheFuncs(vdir_dehstr, DEHSTR);
++#ifdef CONFIG_AUFS_HNOTIFY
++AuCacheFuncs(hnotify, HNOTIFY);
++#endif
+
-+/* ---------------------------------------------------------------------- */
++#endif /* __KERNEL__ */
++#endif /* __AUFS_MODULE_H__ */
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/mtx.h linux-2.6.34/fs/aufs/mtx.h
+--- linux-2.6.34.org/fs/aufs/mtx.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/mtx.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,48 @@
++/*
++ * Copyright (C) 2010 Junjiro R. Okajima
++ *
++ * This program, aufs is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
++ */
++
++/*
++ * very ugly approach for aufs_mmap()
++ * never include this file from other than f_op.c.
++ * see f_op.c in detail.
++ */
++
++#ifndef __AUFS_MTX_H__
++#define __AUFS_MTX_H__
++
++#ifdef __KERNEL__
++
++/* copied from ../kernel/mutex{,-debug}.h */
++struct mutex;
++struct thread_info;
++#ifdef CONFIG_DEBUG_MUTEXES
++static inline void mutex_set_owner(struct mutex *lock)
++{
++ lock->owner = current_thread_info();
++}
++#else
++static inline void mutex_set_owner(struct mutex *lock)
++{
++#ifdef CONFIG_SMP
++ lock->owner = current_thread_info();
++#endif
++}
++#endif
+
+#endif /* __KERNEL__ */
-+#endif /* __AUFS_MODULE_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/opts.c aufs2-2.6.git/fs/aufs/opts.c
---- linux-2.6.31/fs/aufs/opts.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/opts.c 2009-09-21 21:49:23.404940801 +0000
-@@ -0,0 +1,1546 @@
++#endif /* __AUFS_MTX_H__ */
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/opts.c linux-2.6.34/fs/aufs/opts.c
+--- linux-2.6.34.org/fs/aufs/opts.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/opts.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,1585 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ Opt_shwh, Opt_noshwh,
+ Opt_plink, Opt_noplink, Opt_list_plink,
+ Opt_udba,
++ Opt_dio, Opt_nodio,
+ /* Opt_lock, Opt_unlock, */
+ Opt_cmd, Opt_cmd_args,
+ Opt_diropq_a, Opt_diropq_w,
+
+ {Opt_udba, "udba=%s"},
+
++ {Opt_dio, "dio"},
++ {Opt_nodio, "nodio"},
++
+ {Opt_diropq_a, "diropq=always"},
+ {Opt_diropq_a, "diropq=a"},
+ {Opt_diropq_w, "diropq=whiteouted"},
+ {AuBrPerm_RO, NULL}
+};
+
-+static int br_perm_val(char *perm)
++static int noinline_for_stack br_perm_val(char *perm)
+{
+ int val;
+ substring_t args[MAX_OPT_ARGS];
+static match_table_t udbalevel = {
+ {AuOpt_UDBA_REVAL, "reval"},
+ {AuOpt_UDBA_NONE, "none"},
-+#ifdef CONFIG_AUFS_HINOTIFY
-+ {AuOpt_UDBA_HINOTIFY, "inotify"},
++#ifdef CONFIG_AUFS_HNOTIFY
++ {AuOpt_UDBA_HNOTIFY, "notify"}, /* abstraction */
++#ifdef CONFIG_AUFS_HFSNOTIFY
++ {AuOpt_UDBA_HNOTIFY, "fsnotify"},
++#else
++ {AuOpt_UDBA_HNOTIFY, "inotify"},
++#endif
+#endif
+ {-1, NULL}
+};
+
-+static int udba_val(char *str)
++static void au_warn_inotify(int val, char *str)
++{
++#ifdef CONFIG_AUFS_HINOTIFY
++ if (val == AuOpt_UDBA_HNOTIFY
++ && !strcmp(str, "inotify"))
++ AuWarn1("udba=inotify is deprecated, use udba=notify\n");
++#endif
++}
++
++static int noinline_for_stack udba_val(char *str)
+{
++ int val;
+ substring_t args[MAX_OPT_ARGS];
+
-+ return match_token(str, udbalevel, args);
++ val = match_token(str, udbalevel, args);
++ au_warn_inotify(val, str);
++ return val;
+}
+
+const char *au_optstr_udba(int udba)
+ * gave up calling memparse() since it uses simple_strtoull() instead of
+ * strict_...().
+ */
-+static int au_match_ull(substring_t *s, unsigned long long *result)
++static int noinline_for_stack
++au_match_ull(substring_t *s, unsigned long long *result)
+{
+ int err;
+ unsigned int len;
+ if (!au_match_ull(arg, &ull))
+ create->mfsrr_watermark = ull;
+ else {
-+ AuErr("bad integer in %s\n", str);
++ pr_err("bad integer in %s\n", str);
+ err = -EINVAL;
+ }
+
+ if (!match_int(arg, &n) && 0 <= n)
+ create->mfs_second = n;
+ else {
-+ AuErr("bad integer in %s\n", str);
++ pr_err("bad integer in %s\n", str);
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
-+static int au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
++static int noinline_for_stack
++au_wbr_create_val(char *str, struct au_opt_wbr_create *create)
+{
+ int err, e;
+ substring_t args[MAX_OPT_ARGS];
+ {-1, NULL}
+};
+
-+static int au_wbr_copyup_val(char *str)
++static int noinline_for_stack au_wbr_copyup_val(char *str)
+{
+ substring_t args[MAX_OPT_ARGS];
+
+ AuDbg("udba %d, %s\n",
+ opt->udba, au_optstr_udba(opt->udba));
+ break;
++ case Opt_dio:
++ AuLabel(dio);
++ break;
++ case Opt_nodio:
++ AuLabel(nodio);
++ break;
+ case Opt_diropq_a:
+ AuLabel(diropq_a);
+ break;
+ opt->type = Opt_add;
+ goto out;
+ }
-+ AuErr("lookup failed %s (%d)\n", add->pathname, err);
++ pr_err("lookup failed %s (%d)\n", add->pathname, err);
+ err = -EINVAL;
+
+ out:
+
+ err = vfsub_kern_path(del->pathname, lkup_dirflags, &del->h_path);
+ if (unlikely(err))
-+ AuErr("lookup failed %s (%d)\n", del->pathname, err);
++ pr_err("lookup failed %s (%d)\n", del->pathname, err);
+
+ return err;
+}
+ root = sb->s_root;
+ aufs_read_lock(root, AuLock_FLUSH);
+ if (bindex < 0 || au_sbend(sb) < bindex) {
-+ AuErr("out of bounds, %d\n", bindex);
++ pr_err("out of bounds, %d\n", bindex);
+ goto out;
+ }
+
+}
+#endif
+
-+static int au_opts_parse_mod(struct au_opt_mod *mod, substring_t args[])
++static int noinline_for_stack
++au_opts_parse_mod(struct au_opt_mod *mod, substring_t args[])
+{
+ int err;
+ struct path path;
+ mod->path = args[0].from;
+ p = strchr(mod->path, '=');
+ if (unlikely(!p)) {
-+ AuErr("no permssion %s\n", args[0].from);
++ pr_err("no permssion %s\n", args[0].from);
+ goto out;
+ }
+
+ *p++ = 0;
+ err = vfsub_kern_path(mod->path, lkup_dirflags, &path);
+ if (unlikely(err)) {
-+ AuErr("lookup failed %s (%d)\n", mod->path, err);
++ pr_err("lookup failed %s (%d)\n", mod->path, err);
+ goto out;
+ }
+
+ root = sb->s_root;
+ aufs_read_lock(root, AuLock_FLUSH);
+ if (bindex < 0 || au_sbend(sb) < bindex) {
-+ AuErr("out of bounds, %d\n", bindex);
++ pr_err("out of bounds, %d\n", bindex);
+ goto out;
+ }
+
+ err = -EINVAL;
+ if (unlikely(file->f_dentry->d_sb == sb)) {
+ fput(file);
-+ AuErr("%s must be outside\n", args[0].from);
++ pr_err("%s must be outside\n", args[0].from);
+ goto out;
+ }
+
+ return err;
+}
+
-+static
-+int au_opts_parse_xino_itrunc_path(struct super_block *sb,
-+ struct au_opt_xino_itrunc *xino_itrunc,
-+ substring_t args[])
++static int noinline_for_stack
++au_opts_parse_xino_itrunc_path(struct super_block *sb,
++ struct au_opt_xino_itrunc *xino_itrunc,
++ substring_t args[])
+{
+ int err;
+ aufs_bindex_t bend, bindex;
+
+ err = vfsub_kern_path(args[0].from, lkup_dirflags, &path);
+ if (unlikely(err)) {
-+ AuErr("lookup failed %s (%d)\n", args[0].from, err);
++ pr_err("lookup failed %s (%d)\n", args[0].from, err);
+ goto out;
+ }
+
+ path_put(&path);
+
+ if (unlikely(xino_itrunc->bindex < 0)) {
-+ AuErr("no such branch %s\n", args[0].from);
++ pr_err("no such branch %s\n", args[0].from);
+ err = -EINVAL;
+ }
+
+ break;
+ case Opt_add:
+ if (unlikely(match_int(&a->args[0], &n))) {
-+ AuErr("bad integer in %s\n", opt_str);
++ pr_err("bad integer in %s\n", opt_str);
+ break;
+ }
+ bindex = n;
+ case Opt_idel:
+ del->pathname = "(indexed)";
+ if (unlikely(match_int(&args[0], &n))) {
-+ AuErr("bad integer in %s\n", opt_str);
++ pr_err("bad integer in %s\n", opt_str);
+ break;
+ }
+ err = au_opts_parse_idel(sb, n, &opt->del, a->args);
+ case Opt_imod:
+ u.mod->path = "(indexed)";
+ if (unlikely(match_int(&a->args[0], &n))) {
-+ AuErr("bad integer in %s\n", opt_str);
++ pr_err("bad integer in %s\n", opt_str);
+ break;
+ }
+ err = au_opts_parse_imod(sb, n, &opt->mod, a->args);
+ case Opt_itrunc_xino:
+ u.xino_itrunc = &opt->xino_itrunc;
+ if (unlikely(match_int(&a->args[0], &n))) {
-+ AuErr("bad integer in %s\n", opt_str);
++ pr_err("bad integer in %s\n", opt_str);
+ break;
+ }
+ u.xino_itrunc->bindex = n;
+ aufs_read_lock(root, AuLock_FLUSH);
+ if (n < 0 || au_sbend(sb) < n) {
-+ AuErr("out of bounds, %d\n", n);
++ pr_err("out of bounds, %d\n", n);
+ aufs_read_unlock(root, !AuLock_IR);
+ break;
+ }
+ if (unlikely(match_int(&a->args[0], &n)
+ || n < 0
+ || n > KMALLOC_MAX_SIZE)) {
-+ AuErr("bad integer in %s\n", opt_str);
++ pr_err("bad integer in %s\n", opt_str);
+ break;
+ }
+ if (unlikely(n && n < NAME_MAX)) {
-+ AuErr("rdblk must be larger than %d\n",
-+ NAME_MAX);
++ pr_err("rdblk must be larger than %d\n",
++ NAME_MAX);
+ break;
+ }
+ opt->rdblk = n;
+ || n < 0
+ || n * sizeof(struct hlist_head)
+ > KMALLOC_MAX_SIZE)) {
-+ AuErr("bad integer in %s\n", opt_str);
++ pr_err("bad integer in %s\n", opt_str);
+ break;
+ }
+ opt->rdhash = n;
+ case Opt_plink:
+ case Opt_noplink:
+ case Opt_list_plink:
++ case Opt_dio:
++ case Opt_nodio:
+ case Opt_diropq_a:
+ case Opt_diropq_w:
+ case Opt_warn_perm:
+ err = 0;
+ opt->type = token;
+ } else
-+ AuErr("wrong value, %s\n", opt_str);
++ pr_err("wrong value, %s\n", opt_str);
+ break;
+
+ case Opt_wbr_create:
+ err = 0;
+ opt->type = token;
+ } else
-+ AuErr("wrong value, %s\n", opt_str);
++ pr_err("wrong value, %s\n", opt_str);
+ break;
+ case Opt_wbr_copyup:
+ opt->wbr_copyup = au_wbr_copyup_val(a->args[0].from);
+ err = 0;
+ opt->type = token;
+ } else
-+ AuErr("wrong value, %s\n", opt_str);
++ pr_err("wrong value, %s\n", opt_str);
+ break;
+
+ case Opt_ignore:
-+ AuWarn("ignored %s\n", opt_str);
++ pr_warning("ignored %s\n", opt_str);
+ /*FALLTHROUGH*/
+ case Opt_ignore_silent:
+ skipped = 1;
+ err = 0;
+ break;
+ case Opt_err:
-+ AuErr("unknown option %s\n", opt_str);
++ pr_err("unknown option %s\n", opt_str);
+ break;
+ }
+
+ au_plink_list(sb);
+ break;
+
++ case Opt_dio:
++ au_opt_set(sbinfo->si_mntflags, DIO);
++ au_fset_opts(opts->flags, REFRESH_DYAOP);
++ break;
++ case Opt_nodio:
++ au_opt_clr(sbinfo->si_mntflags, DIO);
++ au_fset_opts(opts->flags, REFRESH_DYAOP);
++ break;
++
+ case Opt_diropq_a:
+ au_opt_set(sbinfo->si_mntflags, ALWAYS_DIROPQ);
+ break;
+ if (!err) {
+ err = 1;
+ au_fset_opts(opts->flags, REFRESH_DIR);
-+ if (au_br_whable(opt->add.perm))
-+ au_fset_opts(opts->flags, REFRESH_NONDIR);
++ au_fset_opts(opts->flags, REFRESH_NONDIR);
+ }
+ break;
+
+
+ if (!(sb_flags & MS_RDONLY)) {
+ if (unlikely(!au_br_writable(au_sbr_perm(sb, 0))))
-+ AuWarn("first branch should be rw\n");
++ pr_warning("first branch should be rw\n");
+ if (unlikely(au_opt_test(sbinfo->si_mntflags, SHWH)))
-+ AuWarn("shwh should be used with ro\n");
++ pr_warning("shwh should be used with ro\n");
+ }
+
-+ if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HINOTIFY)
++ if (au_opt_test((sbinfo->si_mntflags | pending), UDBA_HNOTIFY)
+ && !au_opt_test(sbinfo->si_mntflags, XINO))
-+ AuWarn("udba=inotify requires xino\n");
++ pr_warning("udba=*notify requires xino\n");
+
+ err = 0;
+ root = sb->s_root;
-+ dir = sb->s_root->d_inode;
++ dir = root->d_inode;
+ do_plink = !!au_opt_test(sbinfo->si_mntflags, PLINK);
+ bend = au_sbend(sb);
+ for (bindex = 0; !err && bindex <= bend; bindex++) {
+ continue;
+
+ hdir = au_hi(dir, bindex);
-+ au_hin_imtx_lock_nested(hdir, AuLsc_I_PARENT);
++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT);
+ if (wbr)
+ wbr_wh_write_lock(wbr);
+ err = au_wh_init(au_h_dptr(root, bindex), br, sb);
+ if (wbr)
+ wbr_wh_write_unlock(wbr);
-+ au_hin_imtx_unlock(hdir);
++ au_hn_imtx_unlock(hdir);
+
+ if (!err && do_free) {
+ kfree(wbr);
+ bend = au_sbend(sb);
+ if (unlikely(bend < 0)) {
+ err = -EINVAL;
-+ AuErr("no branches\n");
++ pr_err("no branches\n");
+ goto out;
+ }
+
+ /* restore udba */
+ sbinfo->si_mntflags &= ~AuOptMask_UDBA;
+ sbinfo->si_mntflags |= (tmp & AuOptMask_UDBA);
-+ if (au_opt_test(tmp, UDBA_HINOTIFY)) {
++ if (au_opt_test(tmp, UDBA_HNOTIFY)) {
+ struct inode *dir = sb->s_root->d_inode;
-+ au_reset_hinotify(dir,
-+ au_hi_flags(dir, /*isdir*/1) & ~AuHi_XINO);
++ au_hn_reset(dir, au_hi_flags(dir, /*isdir*/1) & ~AuHi_XINO);
+ }
+
+ out:
+{
+ return au_mntflags(sb) & AuOptMask_UDBA;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/opts.h aufs2-2.6.git/fs/aufs/opts.h
---- linux-2.6.31/fs/aufs/opts.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/opts.h 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,196 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/opts.h linux-2.6.34/fs/aufs/opts.h
+--- linux-2.6.34.org/fs/aufs/opts.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/opts.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,198 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#define AuOpt_TRUNC_XINO (1 << 1) /* truncate xino files */
+#define AuOpt_UDBA_NONE (1 << 2) /* users direct branch access */
+#define AuOpt_UDBA_REVAL (1 << 3)
-+#define AuOpt_UDBA_HINOTIFY (1 << 4)
++#define AuOpt_UDBA_HNOTIFY (1 << 4)
+#define AuOpt_SHWH (1 << 5) /* show whiteout */
+#define AuOpt_PLINK (1 << 6) /* pseudo-link */
+#define AuOpt_DIRPERM1 (1 << 7) /* unimplemented */
+#define AuOpt_SUM_W (1 << 11) /* unimplemented */
+#define AuOpt_WARN_PERM (1 << 12) /* warn when add-branch */
+#define AuOpt_VERBOSE (1 << 13) /* busy inode when del-branch */
++#define AuOpt_DIO (1 << 14) /* direct io */
+
-+#ifndef CONFIG_AUFS_HINOTIFY
-+#undef AuOpt_UDBA_HINOTIFY
-+#define AuOpt_UDBA_HINOTIFY 0
++#ifndef CONFIG_AUFS_HNOTIFY
++#undef AuOpt_UDBA_HNOTIFY
++#define AuOpt_UDBA_HNOTIFY 0
+#endif
+#ifndef CONFIG_AUFS_SHWH
+#undef AuOpt_SHWH
+ | AuOpt_WARN_PERM)
+#define AuOptMask_UDBA (AuOpt_UDBA_NONE \
+ | AuOpt_UDBA_REVAL \
-+ | AuOpt_UDBA_HINOTIFY)
++ | AuOpt_UDBA_HNOTIFY)
+
+#define au_opt_test(flags, name) (flags & AuOpt_##name)
+#define au_opt_set(flags, name) do { \
+#define AuOpts_REFRESH_DIR (1 << 1)
+#define AuOpts_REFRESH_NONDIR (1 << 2)
+#define AuOpts_TRUNC_XIB (1 << 3)
++#define AuOpts_REFRESH_DYAOP (1 << 4)
+#define au_ftest_opts(flags, name) ((flags) & AuOpts_##name)
+#define au_fset_opts(flags, name) { (flags) |= AuOpts_##name; }
+#define au_fclr_opts(flags, name) { (flags) &= ~AuOpts_##name; }
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_OPTS_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/plink.c aufs2-2.6.git/fs/aufs/plink.c
---- linux-2.6.31/fs/aufs/plink.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/plink.c 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,396 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/plink.c linux-2.6.34/fs/aufs/plink.c
+--- linux-2.6.34.org/fs/aufs/plink.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/plink.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,453 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * during a user process maintains the pseudo-links,
+ * prohibit adding a new plink and branch manipulation.
+ */
-+void au_plink_block_maintain(struct super_block *sb)
++void au_plink_maint_block(struct super_block *sb)
+{
+ struct au_sbinfo *sbi = au_sbi(sb);
+
+ SiMustAnyLock(sb);
+
+ /* gave up wake_up_bit() */
-+ wait_event(sbi->si_plink_wq, !au_ftest_si(sbi, MAINTAIN_PLINK));
++ wait_event(sbi->si_plink_wq, !sbi->si_plink_maint);
++}
++
++void au_plink_maint_leave(struct file *file)
++{
++ struct au_sbinfo *sbinfo;
++ int iam;
++
++ AuDebugOn(atomic_long_read(&file->f_count));
++
++ sbinfo = au_sbi(file->f_dentry->d_sb);
++ spin_lock(&sbinfo->si_plink_maint_lock);
++ iam = (sbinfo->si_plink_maint == file);
++ if (iam)
++ sbinfo->si_plink_maint = NULL;
++ spin_unlock(&sbinfo->si_plink_maint_lock);
++ if (iam)
++ wake_up_all(&sbinfo->si_plink_wq);
++}
++
++static int au_plink_maint_enter(struct file *file)
++{
++ int err;
++ struct super_block *sb;
++ struct au_sbinfo *sbinfo;
++
++ err = 0;
++ sb = file->f_dentry->d_sb;
++ sbinfo = au_sbi(sb);
++ /* make sure i am the only one in this fs */
++ si_write_lock(sb);
++ /* spin_lock(&sbinfo->si_plink_maint_lock); */
++ if (!sbinfo->si_plink_maint)
++ sbinfo->si_plink_maint = file;
++ else
++ err = -EBUSY;
++ /* spin_unlock(&sbinfo->si_plink_maint_lock); */
++ si_write_unlock(sb);
++
++ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct pseudo_link {
-+ struct list_head list;
++ union {
++ struct list_head list;
++ struct rcu_head rcu;
++ };
+ struct inode *inode;
+};
+
+ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
+
+ plink_list = &sbinfo->si_plink.head;
-+ spin_lock(&sbinfo->si_plink.spin);
-+ list_for_each_entry(plink, plink_list, list)
++ rcu_read_lock();
++ list_for_each_entry_rcu(plink, plink_list, list)
+ AuDbg("%lu\n", plink->inode->i_ino);
-+ spin_unlock(&sbinfo->si_plink.spin);
++ rcu_read_unlock();
+}
+#endif
+
+
+ found = 0;
+ plink_list = &sbinfo->si_plink.head;
-+ spin_lock(&sbinfo->si_plink.spin);
-+ list_for_each_entry(plink, plink_list, list)
++ rcu_read_lock();
++ list_for_each_entry_rcu(plink, plink_list, list)
+ if (plink->inode == inode) {
+ found = 1;
+ break;
+ }
-+ spin_unlock(&sbinfo->si_plink.spin);
++ rcu_read_unlock();
+ return found;
+}
+
+/* free a single plink */
+static void do_put_plink(struct pseudo_link *plink, int do_del)
+{
-+ iput(plink->inode);
+ if (do_del)
+ list_del(&plink->list);
++ iput(plink->inode);
++ kfree(plink);
++}
++
++static void do_put_plink_rcu(struct rcu_head *rcu)
++{
++ struct pseudo_link *plink;
++
++ plink = container_of(rcu, struct pseudo_link, rcu);
++ iput(plink->inode);
+ kfree(plink);
+}
+
+ struct super_block *sb;
+ struct au_sbinfo *sbinfo;
+ struct list_head *plink_list;
-+ struct pseudo_link *plink;
++ struct pseudo_link *plink, *tmp;
+ int found, err, cnt;
+
+ sb = inode->i_sb;
+ sbinfo = au_sbi(sb);
+ AuDebugOn(!au_opt_test(au_mntflags(sb), PLINK));
+
-+ err = 0;
+ cnt = 0;
+ found = 0;
+ plink_list = &sbinfo->si_plink.head;
-+ spin_lock(&sbinfo->si_plink.spin);
-+ list_for_each_entry(plink, plink_list, list) {
++ rcu_read_lock();
++ list_for_each_entry_rcu(plink, plink_list, list) {
+ cnt++;
+ if (plink->inode == inode) {
+ found = 1;
+ break;
+ }
+ }
-+ if (found) {
-+ spin_unlock(&sbinfo->si_plink.spin);
++ rcu_read_unlock();
++ if (found)
+ return;
++
++ tmp = kmalloc(sizeof(*plink), GFP_NOFS);
++ if (tmp)
++ tmp->inode = au_igrab(inode);
++ else {
++ err = -ENOMEM;
++ goto out;
+ }
+
-+ plink = NULL;
-+ if (!found) {
-+ plink = kmalloc(sizeof(*plink), GFP_ATOMIC);
-+ if (plink) {
-+ plink->inode = au_igrab(inode);
-+ list_add(&plink->list, plink_list);
-+ cnt++;
-+ } else
-+ err = -ENOMEM;
++ spin_lock(&sbinfo->si_plink.spin);
++ list_for_each_entry(plink, plink_list, list) {
++ if (plink->inode == inode) {
++ found = 1;
++ break;
++ }
+ }
++ if (!found)
++ list_add_rcu(&tmp->list, plink_list);
+ spin_unlock(&sbinfo->si_plink.spin);
-+
-+ if (!err) {
-+ au_plink_block_maintain(sb);
++ if (!found) {
++ cnt++;
++ WARN_ONCE(cnt > AUFS_PLINK_WARN,
++ "unexpectedly many pseudo links, %d\n", cnt);
++ au_plink_maint_block(sb);
+ err = whplink(h_dentry, inode, bindex, au_sbr(sb, bindex));
++ } else {
++ do_put_plink(tmp, 0);
++ return;
+ }
+
-+ if (unlikely(cnt > AUFS_PLINK_WARN))
-+ AuWarn1("unexpectedly many pseudo links, %d\n", cnt);
++out:
+ if (unlikely(err)) {
-+ AuWarn("err %d, damaged pseudo link.\n", err);
-+ if (!found && plink)
-+ do_put_plink(plink, /*do_del*/1);
++ pr_warning("err %d, damaged pseudo link.\n", err);
++ if (tmp) {
++ au_spl_del_rcu(&tmp->list, &sbinfo->si_plink);
++ call_rcu(&tmp->rcu, do_put_plink_rcu);
++ }
+ }
+}
+
+ * pseudo-link maintenance mode,
+ * cleared by aufs_release_dir()
+ */
-+ si_write_lock(sb);
-+ if (!au_ftest_si(sbinfo, MAINTAIN_PLINK)) {
-+ au_fset_si(sbinfo, MAINTAIN_PLINK);
-+ au_fi(file)->fi_maintain_plink = 1;
-+ } else
-+ err = -EBUSY;
-+ si_write_unlock(sb);
++ err = au_plink_maint_enter(file);
+ break;
+ case AUFS_CTL_PLINK_CLEAN:
+ aufs_write_lock(sb->s_root);
+ aufs_write_unlock(sb->s_root);
+ break;
+ default:
++ /* err = -ENOTTY; */
+ err = -EINVAL;
+ }
+ out:
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/poll.c aufs2-2.6.git/fs/aufs/poll.c
---- linux-2.6.31/fs/aufs/poll.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/poll.c 2009-09-21 21:49:23.408274204 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/poll.c linux-2.6.34/fs/aufs/poll.c
+--- linux-2.6.34.org/fs/aufs/poll.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/poll.c 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,56 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+ /* it is not an error if h_file has no operation */
+ mask = DEFAULT_POLLMASK;
-+ h_file = au_h_fptr(file, au_fbstart(file));
++ h_file = au_hf_top(file);
+ if (h_file->f_op && h_file->f_op->poll)
+ mask = h_file->f_op->poll(h_file, wait);
+
+ AuTraceErr((int)mask);
+ return mask;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/rdu.c aufs2-2.6.git/fs/aufs/rdu.c
---- linux-2.6.31/fs/aufs/rdu.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/rdu.c 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,331 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/rdu.c linux-2.6.34/fs/aufs/rdu.c
+--- linux-2.6.34.org/fs/aufs/rdu.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/rdu.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,330 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * readdir in userspace.
+ */
+
++#include <linux/fs_stack.h>
+#include <linux/security.h>
+#include <linux/uaccess.h>
+#include <linux/aufs_type.h>
+ ent.bindex = rdu->cookie.bindex;
+ ent.type = d_type;
+ ent.nlen = nlen;
++ if (unlikely(nlen > AUFS_MAX_NAMELEN))
++ ent.type = DT_UNKNOWN;
+
+ err = -EFAULT;
+ if (copy_to_user(arg->ent.e, &ent, sizeof(ent)))
+ bend = au_fbstart(file);
+ if (cookie->bindex < bend)
+ cookie->bindex = bend;
-+ bend = au_fbend(file);
++ bend = au_fbend_dir(file);
+ /* AuDbg("b%d, b%d\n", cookie->bindex, bend); */
+ for (; !err && cookie->bindex <= bend;
+ cookie->bindex++, cookie->h_pos = 0) {
-+ h_file = au_h_fptr(file, cookie->bindex);
++ h_file = au_hf_dir(file, cookie->bindex);
+ if (!h_file)
+ continue;
+
+ sb = file->f_dentry->d_sb;
+ si_read_lock(sb, AuLock_FLUSH);
+ while (nent-- > 0) {
-+ err = !access_ok(VERIFY_WRITE, u->e, sizeof(ent));
-+ if (unlikely(err)) {
-+ err = -EFAULT;
-+ AuTraceErr(err);
-+ break;
-+ }
-+
+ err = copy_from_user(&ent, u->e, sizeof(ent));
++ if (!err)
++ err = !access_ok(VERIFY_WRITE, &u->e->ino, sizeof(ino));
+ if (unlikely(err)) {
+ err = -EFAULT;
+ AuTraceErr(err);
+ break;
+
+ default:
++ /* err = -ENOTTY; */
+ err = -EINVAL;
+ }
+
+ AuTraceErr(err);
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/rwsem.h aufs2-2.6.git/fs/aufs/rwsem.h
---- linux-2.6.31/fs/aufs/rwsem.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/rwsem.h 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,186 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/rwsem.h linux-2.6.34/fs/aufs/rwsem.h
+--- linux-2.6.34.org/fs/aufs/rwsem.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/rwsem.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,187 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#ifdef __KERNEL__
+
+#include <linux/rwsem.h>
++#include "debug.h"
+
+struct au_rwsem {
+ struct rw_semaphore rwsem;
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_RWSEM_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/sbinfo.c aufs2-2.6.git/fs/aufs/sbinfo.c
---- linux-2.6.31/fs/aufs/sbinfo.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/sbinfo.c 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,208 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/sbinfo.c linux-2.6.34/fs/aufs/sbinfo.c
+--- linux-2.6.34.org/fs/aufs/sbinfo.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/sbinfo.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,202 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+ sbinfo = container_of(kobj, struct au_sbinfo, si_kobj);
+ AuDebugOn(!list_empty(&sbinfo->si_plink.head));
++ AuDebugOn(sbinfo->si_plink_maint);
+
+ sb = sbinfo->si_sb;
+ si_write_lock(sb);
+ struct au_sbinfo *sbinfo;
+
+ err = -ENOMEM;
-+ sbinfo = kmalloc(sizeof(*sbinfo), GFP_NOFS);
++ sbinfo = kzalloc(sizeof(*sbinfo), GFP_NOFS);
+ if (unlikely(!sbinfo))
+ goto out;
+
+ if (unlikely(!sbinfo->si_branch))
+ goto out_sbinfo;
+
-+ memset(&sbinfo->si_kobj, 0, sizeof(sbinfo->si_kobj));
+ err = sysaufs_si_init(sbinfo);
+ if (unlikely(err))
+ goto out_br;
+
+ au_nwt_init(&sbinfo->si_nowait);
+ au_rw_init_wlock(&sbinfo->si_rwsem);
-+ sbinfo->si_generation = 0;
-+ sbinfo->au_si_status = 0;
+ sbinfo->si_bend = -1;
-+ sbinfo->si_last_br_id = 0;
+
+ sbinfo->si_wbr_copyup = AuWbrCopyup_Def;
+ sbinfo->si_wbr_create = AuWbrCreate_Def;
-+ sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + AuWbrCopyup_Def;
-+ sbinfo->si_wbr_create_ops = au_wbr_create_ops + AuWbrCreate_Def;
++ sbinfo->si_wbr_copyup_ops = au_wbr_copyup_ops + sbinfo->si_wbr_copyup;
++ sbinfo->si_wbr_create_ops = au_wbr_create_ops + sbinfo->si_wbr_create;
+
+ sbinfo->si_mntflags = AuOpt_Def;
+
-+ sbinfo->si_xread = NULL;
-+ sbinfo->si_xwrite = NULL;
-+ sbinfo->si_xib = NULL;
+ mutex_init(&sbinfo->si_xib_mtx);
-+ sbinfo->si_xib_buf = NULL;
+ sbinfo->si_xino_brid = -1;
+ /* leave si_xib_last_pindex and si_xib_next_bit */
+
+
+ au_spl_init(&sbinfo->si_plink);
+ init_waitqueue_head(&sbinfo->si_plink_wq);
++ spin_lock_init(&sbinfo->si_plink_maint_lock);
+
+ /* leave other members for sysaufs and si_mnt. */
+ sbinfo->si_sb = sb;
+ di_write_unlock2(d1, d2);
+ si_read_unlock(d1->d_sb);
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/spl.h aufs2-2.6.git/fs/aufs/spl.h
---- linux-2.6.31/fs/aufs/spl.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/spl.h 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,57 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/spl.h linux-2.6.34/fs/aufs/spl.h
+--- linux-2.6.34.org/fs/aufs/spl.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/spl.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,66 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+#include <linux/spinlock.h>
+#include <linux/list.h>
++#include <linux/rculist.h>
+
+struct au_splhead {
+ spinlock_t spin;
+ spin_unlock(&spl->spin);
+}
+
++static inline void au_spl_del_rcu(struct list_head *list,
++ struct au_splhead *spl)
++{
++ spin_lock(&spl->spin);
++ list_del_rcu(list);
++ spin_unlock(&spl->spin);
++}
++
+#endif /* __KERNEL__ */
+#endif /* __AUFS_SPL_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/super.c aufs2-2.6.git/fs/aufs/super.c
---- linux-2.6.31/fs/aufs/super.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/super.c 2009-09-21 21:49:23.408274204 +0000
-@@ -0,0 +1,874 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/super.c linux-2.6.34/fs/aufs/super.c
+--- linux-2.6.34.org/fs/aufs/super.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/super.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,847 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+ c = au_cache_alloc_icntnr();
+ if (c) {
-+ inode_init_once(&c->vfs_inode);
+ c->vfs_inode.i_version = 1; /* sigen(sb); */
+ c->iinfo.ii_hinode = NULL;
+ return &c->vfs_inode;
+ int err;
+ aufs_bindex_t bindex, bend;
+ struct path path;
-+ struct au_hdentry *hd;
++ struct au_hdentry *hdp;
+ struct au_branch *br;
+
+ err = 0;
+ bend = au_sbend(sb);
-+ hd = au_di(sb->s_root)->di_hdentry;
++ hdp = au_di(sb->s_root)->di_hdentry;
+ for (bindex = 0; !err && bindex <= bend; bindex++) {
+ br = au_sbr(sb, bindex);
+ path.mnt = br->br_mnt;
-+ path.dentry = hd[bindex].hd_dentry;
++ path.dentry = hdp[bindex].hd_dentry;
+ err = au_seq_path(seq, &path);
+ if (err > 0)
+ err = seq_printf(seq, "=%s",
+ struct qstr *name;
+ struct file *f;
+ struct dentry *d, *h_root;
++ struct au_hdentry *hdp;
+
+ AuRwMustAnyLock(&sbinfo->si_rwsem);
+
+ brid = au_xino_brid(sb);
+ if (brid >= 0) {
+ bindex = au_br_index(sb, brid);
-+ h_root = au_di(sb->s_root)->di_hdentry[0 + bindex].hd_dentry;
++ hdp = au_di(sb->s_root)->di_hdentry;
++ h_root = hdp[0 + bindex].hd_dentry;
+ }
+ d = f->f_dentry;
+ name = &d->d_name;
+ AuStr(UDBA, udba);
+ AuBool(SHWH, shwh);
+ AuBool(PLINK, plink);
++ AuBool(DIO, dio);
+ /* AuBool(DIRPERM1, dirperm1); */
+ /* AuBool(REFROF, refrof); */
+
+ si_read_unlock(sb);
+ return 0;
+
-+#undef Deleted
+#undef AuBool
+#undef AuStr
++#undef AuUInt
+}
+
+/* ---------------------------------------------------------------------- */
+
+ if (!err) {
+ buf->f_type = AUFS_SUPER_MAGIC;
-+ buf->f_namelen -= AUFS_WH_PFX_LEN;
++ buf->f_namelen = AUFS_MAX_NAMELEN;
+ memset(&buf->f_fsid, 0, sizeof(buf->f_fsid));
+ }
+ /* buf->f_bsize = buf->f_blocks = buf->f_bfree = buf->f_bavail = -1; */
+
+/* ---------------------------------------------------------------------- */
+
-+/* try flushing the lower fs at aufs remount/unmount time */
-+
-+static void au_fsync_br(struct super_block *sb)
-+{
-+ aufs_bindex_t bend, bindex;
-+ int brperm;
-+ struct au_branch *br;
-+ struct super_block *h_sb;
-+
-+ bend = au_sbend(sb);
-+ for (bindex = 0; bindex < bend; bindex++) {
-+ br = au_sbr(sb, bindex);
-+ brperm = br->br_perm;
-+ if (brperm == AuBrPerm_RR || brperm == AuBrPerm_RRWH)
-+ continue;
-+ h_sb = br->br_mnt->mnt_sb;
-+ if (bdev_read_only(h_sb->s_bdev))
-+ continue;
-+
-+ lockdep_off();
-+ down_write(&h_sb->s_umount);
-+ shrink_dcache_sb(h_sb);
-+ sync_filesystem(h_sb);
-+ up_write(&h_sb->s_umount);
-+ lockdep_on();
-+ }
-+}
-+
+/*
+ * this IS NOT for super_operations.
+ * I guess it will be reverted someday.
+ return;
+
+ si_write_lock(sb);
-+ au_fsync_br(sb);
+ if (au_opt_test(au_mntflags(sb), PLINK))
+ au_plink_put(sb);
+ if (sbinfo->si_wbr_create_ops->fin)
+ struct inode *inode = dentry->d_inode;
+ err = au_refresh_hinode(inode, dentry);
+ if (!err && type == S_IFDIR)
-+ au_reset_hinotify(inode, dir_flags);
++ au_hn_reset(inode, dir_flags);
+ }
+ if (unlikely(err))
-+ AuErr("unrecoverable error %d, %.*s\n", err, AuDLNPair(dentry));
++ pr_err("unrecoverable error %d, %.*s\n",
++ err, AuDLNPair(dentry));
+
+ di_read_unlock(parent, AuLock_IR);
+ dput(parent);
+ DiMustNoWaiters(root);
+ inode = root->d_inode;
+ IiMustNoWaiters(inode);
-+ au_reset_hinotify(inode, au_hi_flags(inode, /*isdir*/1));
++ au_hn_reset(inode, au_hi_flags(inode, /*isdir*/1));
+ di_write_unlock(root);
+
+ err = refresh_dir(root, sigen);
+ if (unlikely(err)) {
+ au_fset_si(sbinfo, FAILED_REFRESH_DIRS);
-+ AuWarn("Refreshing directories failed, ignored (%d)\n", err);
++ pr_warning("Refreshing directories failed, ignored (%d)\n",
++ err);
+ }
+
+ if (au_ftest_opts(flags, REFRESH_NONDIR)) {
+ err = refresh_nondir(root, sigen, !err);
+ if (unlikely(err))
-+ AuWarn("Refreshing non-directories failed, ignored"
-+ "(%d)\n", err);
++ pr_warning("Refreshing non-directories failed, ignored"
++ "(%d)\n", err);
+ }
+
+ /* aufs_write_lock() calls ..._child() */
+
+static int aufs_remount_fs(struct super_block *sb, int *flags, char *data)
+{
-+ int err;
++ int err, do_dx;
++ unsigned int mntflags;
+ struct au_opts opts;
+ struct dentry *root;
+ struct inode *inode;
+ if (!data || !*data) {
+ aufs_write_lock(root);
+ err = au_opts_verify(sb, *flags, /*pending*/0);
-+ if (!err)
-+ au_fsync_br(sb);
+ aufs_write_unlock(root);
+ goto out;
+ }
+ inode = root->d_inode;
+ mutex_lock(&inode->i_mutex);
+ aufs_write_lock(root);
-+ au_fsync_br(sb);
+
+ /* au_opts_remount() may return an error */
+ err = au_opts_remount(sb, &opts);
+ || au_ftest_opts(opts.flags, REFRESH_NONDIR))
+ au_remount_refresh(sb, opts.flags);
+
++ if (au_ftest_opts(opts.flags, REFRESH_DYAOP)) {
++ mntflags = au_mntflags(sb);
++ do_dx = !!au_opt_test(mntflags, DIO);
++ au_dy_arefresh(do_dx);
++ }
++
+ aufs_write_unlock(root);
+ mutex_unlock(&inode->i_mutex);
+
+ return err;
+}
+
-+static struct super_operations aufs_sop = {
++static const struct super_operations aufs_sop = {
+ .alloc_inode = aufs_alloc_inode,
+ .destroy_inode = aufs_destroy_inode,
+ .drop_inode = generic_delete_inode,
+ if (IS_ERR(root))
+ goto out_iput;
+
-+ err = au_alloc_dinfo(root);
++ err = au_di_init(root);
+ if (!err) {
+ sb->s_root = root;
+ return 0; /* success */
+
+ if (unlikely(!arg || !*arg)) {
+ err = -EINVAL;
-+ AuErr("no arg\n");
++ pr_err("no arg\n");
+ goto out;
+ }
+
+
+ /* lock vfs_inode first, then aufs. */
+ mutex_lock(&inode->i_mutex);
-+ inode->i_op = &aufs_dir_iop;
-+ inode->i_fop = &aufs_dir_fop;
+ aufs_write_lock(root);
+ err = au_opts_mount(sb, &opts);
+ au_opts_free(&opts);
-+ if (unlikely(err))
-+ goto out_unlock;
+ aufs_write_unlock(root);
+ mutex_unlock(&inode->i_mutex);
-+ goto out_opts; /* success */
++ if (!err)
++ goto out_opts; /* success */
+
-+ out_unlock:
-+ aufs_write_unlock(root);
-+ mutex_unlock(&inode->i_mutex);
+ out_root:
+ dput(root);
+ sb->s_root = NULL;
+ /* no need to __module_get() and module_put(). */
+ .owner = THIS_MODULE,
+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/super.h aufs2-2.6.git/fs/aufs/super.h
---- linux-2.6.31/fs/aufs/super.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/super.h 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,384 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/super.h linux-2.6.34/fs/aufs/super.h
+--- linux-2.6.34.org/fs/aufs/super.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/super.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,361 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ /* pseudo_link list */
+ struct au_splhead si_plink;
+ wait_queue_head_t si_plink_wq;
++ spinlock_t si_plink_maint_lock;
++ struct file *si_plink_maint;
+
+ /*
+ * sysfs and lifetime management.
+ * if it is false, refreshing dirs at access time is unnecesary
+ */
+#define AuSi_FAILED_REFRESH_DIRS 1
-+#define AuSi_MAINTAIN_PLINK (1 << 1) /* ioctl */
+static inline unsigned char au_do_ftest_si(struct au_sbinfo *sbi,
+ unsigned int flag)
+{
+/* ---------------------------------------------------------------------- */
+
+/* policy to select one among writable branches */
-+#define AuWbrCopyup(sbinfo, args...) \
-+ ((sbinfo)->si_wbr_copyup_ops->copyup(args))
-+#define AuWbrCreate(sbinfo, args...) \
-+ ((sbinfo)->si_wbr_create_ops->create(args))
++#define AuWbrCopyup(sbinfo, ...) \
++ ((sbinfo)->si_wbr_copyup_ops->copyup(__VA_ARGS__))
++#define AuWbrCreate(sbinfo, ...) \
++ ((sbinfo)->si_wbr_create_ops->create(__VA_ARGS__))
+
+/* flags for si_read_lock()/aufs_read_lock()/di_read_lock() */
+#define AuLock_DW 1 /* write-lock dentry */
+ return -ESTALE;
+}
+#else
-+static inline void au_export_init(struct super_block *sb)
-+{
-+ /* nothing */
-+}
-+
-+static inline int au_test_nfsd(struct task_struct *tsk)
-+{
-+ return 0;
-+}
-+
-+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 */
-+}
-+
++AuStubVoid(au_export_init, struct super_block *sb)
++AuStubInt0(au_test_nfsd, struct task_struct *tsk)
++AuStubInt0(au_xigen_inc, struct inode *inode)
++AuStubInt0(au_xigen_new, struct inode *inode)
++AuStubInt0(au_xigen_set, struct super_block *sb, struct file *base)
++AuStubVoid(au_xigen_clr, struct super_block *sb)
+static inline int au_busy_or_stale(void)
+{
+ return -EBUSY;
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_SUPER_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/sysaufs.c aufs2-2.6.git/fs/aufs/sysaufs.c
---- linux-2.6.31/fs/aufs/sysaufs.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/sysaufs.c 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,104 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/sysaufs.c linux-2.6.34/fs/aufs/sysaufs.c
+--- linux-2.6.34.org/fs/aufs/sysaufs.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/sysaufs.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,107 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ NULL,
+};
+
-+static struct sysfs_ops au_sbi_ops = {
++static const struct sysfs_ops au_sbi_ops = {
+ .show = sysaufs_si_show
+};
+
+ get_random_bytes(&sysaufs_si_mask, sizeof(sysaufs_si_mask));
+ } while (!sysaufs_si_mask);
+
++ err = -EINVAL;
+ sysaufs_ket = kset_create_and_add(AUFS_NAME, NULL, fs_kobj);
++ if (unlikely(!sysaufs_ket))
++ goto out;
+ err = PTR_ERR(sysaufs_ket);
+ if (IS_ERR(sysaufs_ket))
+ goto out;
+ out:
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/sysaufs.h aufs2-2.6.git/fs/aufs/sysaufs.h
---- linux-2.6.31/fs/aufs/sysaufs.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/sysaufs.h 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,120 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/sysaufs.h linux-2.6.34/fs/aufs/sysaufs.h
+--- linux-2.6.34.org/fs/aufs/sysaufs.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/sysaufs.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,105 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#else
+#define sysaufs_attr_group NULL
+
-+static inline
-+int sysaufs_si_xi_path(struct seq_file *seq, struct super_block *sb)
-+{
-+ return 0;
-+}
++AuStubInt0(sysaufs_si_xi_path, struct seq_file *seq, struct super_block *sb)
+
+static inline
+ssize_t sysaufs_si_show(struct kobject *kobj, struct attribute *attr,
+ return 0;
+}
+
-+static inline void sysaufs_br_init(struct au_branch *br)
-+{
-+ /* empty */
-+}
-+
-+static inline void sysaufs_brs_add(struct super_block *sb, aufs_bindex_t bindex)
-+{
-+ /* nothing */
-+}
-+
-+static inline void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex)
-+{
-+ /* nothing */
-+}
++AuStubVoid(sysaufs_br_init, struct au_branch *br)
++AuStubVoid(sysaufs_brs_add, struct super_block *sb, aufs_bindex_t bindex)
++AuStubVoid(sysaufs_brs_del, struct super_block *sb, aufs_bindex_t bindex)
+
+static inline void sysaufs_brs_init(void)
+{
+
+#endif /* __KERNEL__ */
+#endif /* __SYSAUFS_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/sysfs.c aufs2-2.6.git/fs/aufs/sysfs.c
---- linux-2.6.31/fs/aufs/sysfs.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/sysfs.c 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,224 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/sysfs.c linux-2.6.34/fs/aufs/sysfs.c
+--- linux-2.6.34.org/fs/aufs/sysfs.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/sysfs.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,251 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#include <linux/sysfs.h>
+#include "aufs.h"
+
++#ifdef CONFIG_AUFS_FS_MODULE
++/* this entry violates the "one line per file" policy of sysfs */
++static ssize_t config_show(struct kobject *kobj, struct kobj_attribute *attr,
++ char *buf)
++{
++ ssize_t err;
++ static char *conf =
++/* this file is generated at compiling */
++#include "conf.str"
++ ;
++
++ err = snprintf(buf, PAGE_SIZE, conf);
++ if (unlikely(err >= PAGE_SIZE))
++ err = -EFBIG;
++ return err;
++}
++
++static struct kobj_attribute au_config_attr = __ATTR_RO(config);
++#endif
++
+static struct attribute *au_attr[] = {
++#ifdef CONFIG_AUFS_FS_MODULE
++ &au_config_attr.attr,
++#endif
+ NULL, /* need to NULL terminate the list of attributes */
+};
+
+
+void sysaufs_br_init(struct au_branch *br)
+{
-+ br->br_attr.name = br->br_name;
-+ br->br_attr.mode = S_IRUGO;
-+ br->br_attr.owner = THIS_MODULE;
++ struct attribute *attr = &br->br_attr;
++
++ sysfs_attr_init(attr);
++ attr->name = br->br_name;
++ attr->mode = S_IRUGO;
++ attr->owner = THIS_MODULE;
+}
+
+void sysaufs_brs_del(struct super_block *sb, aufs_bindex_t bindex)
+ "%d", bindex);
+ err = sysfs_create_file(kobj, &br->br_attr);
+ if (unlikely(err))
-+ AuWarn("failed %s under sysfs(%d)\n", br->br_name, err);
++ pr_warning("failed %s under sysfs(%d)\n",
++ br->br_name, err);
+ }
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/sysrq.c aufs2-2.6.git/fs/aufs/sysrq.c
---- linux-2.6.31/fs/aufs/sysrq.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/sysrq.c 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,115 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/sysrq.c linux-2.6.34/fs/aufs/sysrq.c
+--- linux-2.6.34.org/fs/aufs/sysrq.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/sysrq.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,119 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ au_debug(1);
+
+ sbinfo = au_sbi(sb);
-+ pr_warning("si=%lx\n", sysaufs_si_id(sbinfo));
-+ pr_warning(AUFS_NAME ": superblock\n");
++ /* since we define pr_fmt, call printk directly */
++ printk(KERN_WARNING "si=%lx\n", sysaufs_si_id(sbinfo));
++ printk(KERN_WARNING AUFS_NAME ": superblock\n");
+ au_dpri_sb(sb);
-+ pr_warning(AUFS_NAME ": root dentry\n");
++ printk(KERN_WARNING AUFS_NAME ": root dentry\n");
+ au_dpri_dentry(sb->s_root);
-+ pr_warning(AUFS_NAME ": root inode\n");
++ printk(KERN_WARNING AUFS_NAME ": root inode\n");
+ au_dpri_inode(sb->s_root->d_inode);
+#if 0
+ struct inode *i;
-+ pr_warning(AUFS_NAME ": isolated inode\n");
++ printk(KERN_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);
+#endif
-+ pr_warning(AUFS_NAME ": files\n");
-+ list_for_each_entry(file, &sb->s_files, f_u.fu_list)
-+ if (!special_file(file->f_dentry->d_inode->i_mode))
++ printk(KERN_WARNING AUFS_NAME ": files\n");
++ list_for_each_entry(file, &sb->s_files, f_u.fu_list) {
++ umode_t mode;
++ mode = file->f_dentry->d_inode->i_mode;
++ if (!special_file(mode) || au_special_file(mode))
+ au_dpri_file(file);
++ }
+
+ au_plevel = plevel;
+ au_debug(0);
+ if ('a' <= key && key <= 'z')
+ err = register_sysrq_key(key, &au_sysrq_op);
+ if (unlikely(err))
-+ AuErr("err %d, sysrq=%c\n", err, key);
++ pr_err("err %d, sysrq=%c\n", err, key);
+ return err;
+}
+
+ int err;
+ err = unregister_sysrq_key(*aufs_sysrq_key, &au_sysrq_op);
+ if (unlikely(err))
-+ AuErr("err %d (ignored)\n", err);
++ pr_err("err %d (ignored)\n", err);
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/vdir.c aufs2-2.6.git/fs/aufs/vdir.c
---- linux-2.6.31/fs/aufs/vdir.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/vdir.c 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,879 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/vdir.c linux-2.6.34/fs/aufs/vdir.c
+--- linux-2.6.34.org/fs/aufs/vdir.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/vdir.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,884 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+static unsigned int calc_size(int nlen)
+{
-+ BUILD_BUG_ON(sizeof(ino_t) != sizeof(long));
+ return ALIGN(sizeof(struct au_vdir_de) + nlen, sizeof(ino_t));
+}
+
+ n = sz;
+ if (sz < AUFS_RDHASH_DEF)
+ n = AUFS_RDHASH_DEF;
-+ /* AuInfo("n %u\n", n); */
++ /* pr_info("n %u\n", n); */
+ return n;
+}
+
+ n = 0;
+ hlist_for_each(pos, head)
+ n++;
-+ AuInfo("%lu\n", n);
++ pr_info("%lu\n", n);
+#endif
+}
+
+
+ hlist_for_each_entry_safe(tpos, pos, node, head, hash) {
+ /* hlist_del(pos); */
-+ au_cache_free_dehstr(tpos);
++ au_cache_free_vdir_dehstr(tpos);
+ }
+}
+
+ }
+
+ err = -ENOMEM;
-+ dehstr = au_cache_alloc_dehstr();
++ dehstr = au_cache_alloc_vdir_dehstr();
+ if (unlikely(!dehstr))
+ goto out;
+
+ if (!vdir->vd_deblk_sz) {
+ /* estimate the apropriate size for deblk */
+ vdir->vd_deblk_sz = au_dir_size(file, /*dentry*/NULL);
-+ /* AuInfo("vd_deblk_sz %u\n", vdir->vd_deblk_sz); */
++ /* pr_info("vd_deblk_sz %u\n", vdir->vd_deblk_sz); */
+ }
+ vdir->vd_nblk = 0;
+ vdir->vd_version = 0;
+
+ sb = arg->file->f_dentry->d_sb;
+ arg->err = au_ino(sb, arg->bindex, h_ino, d_type, &ino);
-+ if (!arg->err)
++ if (!arg->err) {
++ if (unlikely(nlen > AUFS_MAX_NAMELEN))
++ d_type = DT_UNKNOWN;
+ arg->err = append_de(arg->vdir, name, nlen, ino,
+ d_type, &arg->delist);
++ }
+ } else if (au_ftest_fillvdir(arg->flags, WHABLE)) {
+ name += AUFS_WH_PFX_LEN;
+ nlen -= AUFS_WH_PFX_LEN;
+ if (shwh)
+ arg->err = au_wh_ino(sb, arg->bindex, h_ino, d_type,
+ &ino);
-+ if (!arg->err)
++ if (!arg->err) {
++ if (nlen <= AUFS_MAX_NAMELEN + AUFS_WH_PFX_LEN)
++ d_type = DT_UNKNOWN;
+ arg->err = au_nhash_append_wh
+ (&arg->whlist, name, nlen, ino, d_type,
+ arg->bindex, shwh);
++ }
+ }
+
+ out:
+ AuDebugOn(!au_opt_test(au_mntflags(sb), SHWH));
+
+ err = -ENOMEM;
-+ o = p = __getname();
++ o = p = __getname_gfp(GFP_NOFS);
+ if (unlikely(!p))
+ goto out;
+
+ au_fset_fillvdir(arg->flags, SHWH);
+ }
+ bstart = au_fbstart(file);
-+ bend = au_fbend(file);
++ bend = au_fbend_dir(file);
+ for (bindex = bstart; !err && bindex <= bend; bindex++) {
-+ hf = au_h_fptr(file, bindex);
++ hf = au_hf_dir(file, bindex);
+ if (!hf)
+ continue;
+
+ /* smp_mb(); */
+ return 0;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/vfsub.c aufs2-2.6.git/fs/aufs/vfsub.c
---- linux-2.6.31/fs/aufs/vfsub.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/vfsub.c 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,751 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/vfsub.c linux-2.6.34/fs/aufs/vfsub.c
+--- linux-2.6.34.org/fs/aufs/vfsub.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/vfsub.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,805 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * sub-routines for VFS
+ */
+
++#include <linux/file.h>
+#include <linux/ima.h>
+#include <linux/namei.h>
+#include <linux/security.h>
+
+/* ---------------------------------------------------------------------- */
+
-+struct file *vfsub_dentry_open(struct path *path, int flags,
-+ const struct cred *cred)
++static int au_conv_oflags(int flags)
++{
++ int mask = 0;
++
++#ifdef CONFIG_IMA
++ fmode_t fmode;
++
++ /* mask = MAY_OPEN; */
++ fmode = OPEN_FMODE(flags);
++ if (fmode & FMODE_READ)
++ mask |= MAY_READ;
++ if ((fmode & FMODE_WRITE)
++ || (flags & O_TRUNC))
++ mask |= MAY_WRITE;
++ /*
++ * if (flags & O_APPEND)
++ * mask |= MAY_APPEND;
++ */
++ if (flags & vfsub_fmode_to_uint(FMODE_EXEC))
++ mask |= MAY_EXEC;
++
++ AuDbg("flags 0x%x, mask 0x%x\n", flags, mask);
++#endif
++
++ return mask;
++}
++
++struct file *vfsub_dentry_open(struct path *path, int flags)
+{
+ struct file *file;
++ int err;
+
-+ file = dentry_open(path->dentry, path->mnt, flags, cred);
++ path_get(path);
++ file = dentry_open(path->dentry, path->mnt, flags, current_cred());
+ if (IS_ERR(file))
-+ return file;
-+ /* as NFSD does, just call ima_..._get() simply after dentry_open */
-+ ima_counts_get(file);
++ goto out;
++
++ err = ima_file_check(file, au_conv_oflags(flags));
++ if (unlikely(err)) {
++ fput(file);
++ file = ERR_PTR(err);
++ }
++out:
+ return file;
+}
+
+{
+ struct file *file;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ file = filp_open(path, oflags, mode);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (IS_ERR(file))
+ goto out;
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
+ vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/
+
+ out:
++ AuTraceErrPtr(path.dentry);
+ return path.dentry;
+}
+
+ IMustLock(nd->path.dentry->d_inode);
+
+ path.dentry = lookup_hash(nd);
-+ if (!IS_ERR(path.dentry) && path.dentry->d_inode)
++ if (IS_ERR(path.dentry))
++ goto out;
++ if (path.dentry->d_inode)
+ vfsub_update_h_iattr(&path, /*did*/NULL); /*ignore*/
+
++ out:
++ AuTraceErrPtr(path.dentry);
+ return path.dentry;
+}
+
+ lockdep_off();
+ d = lock_rename(d1, d2);
+ lockdep_on();
-+ au_hin_suspend(hdir1);
++ au_hn_suspend(hdir1);
+ if (hdir1 != hdir2)
-+ au_hin_suspend(hdir2);
++ au_hn_suspend(hdir2);
+
+ return d;
+}
+void vfsub_unlock_rename(struct dentry *d1, struct au_hinode *hdir1,
+ struct dentry *d2, struct au_hinode *hdir2)
+{
-+ au_hin_resume(hdir1);
++ au_hn_resume(hdir1);
+ if (hdir1 != hdir2)
-+ au_hin_resume(hdir2);
++ au_hn_resume(hdir2);
+ lockdep_off();
+ unlock_rename(d1, d2);
+ lockdep_on();
+ if (unlikely(err))
+ goto out;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = vfs_link(src_dentry, dir, path->dentry);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (!err) {
+ struct path tmp = *path;
+ int did;
+ if (unlikely(err))
+ goto out;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = vfs_rename(src_dir, src_dentry, dir, path->dentry);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (!err) {
+ int did;
+
+ if (unlikely(err))
+ goto out;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = vfs_rmdir(dir, path->dentry);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (!err) {
+ struct path tmp = {
+ .dentry = path->dentry->d_parent,
+{
+ ssize_t err;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = vfs_write(file, ubuf, count, ppos);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (err >= 0)
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
+ return err;
+ return err;
+}
+
++int vfsub_flush(struct file *file, fl_owner_t id)
++{
++ int err;
++
++ err = 0;
++ if (file->f_op && file->f_op->flush) {
++ err = file->f_op->flush(file, id);
++ if (!err)
++ vfsub_update_h_iattr(&file->f_path, /*did*/NULL);
++ /*ignore*/
++ }
++ return err;
++}
++
+int vfsub_readdir(struct file *file, filldir_t filldir, void *arg)
+{
+ int err;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = vfs_readdir(file, filldir, arg);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (err >= 0)
+ vfsub_update_h_iattr(&file->f_path, /*did*/NULL); /*ignore*/
+ return err;
+{
+ long err;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = do_splice_to(in, ppos, pipe, len, flags);
-+ lockdep_on();
++ /* lockdep_on(); */
++ file_accessed(in);
+ if (err >= 0)
+ vfsub_update_h_iattr(&in->f_path, /*did*/NULL); /*ignore*/
+ return err;
+{
+ long err;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = do_splice_from(pipe, out, ppos, len, flags);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (err >= 0)
+ vfsub_update_h_iattr(&out->f_path, /*did*/NULL); /*ignore*/
+ return err;
+ err = get_write_access(h_inode);
+ if (err)
+ goto out_mnt;
-+ err = break_lease(h_inode, vfsub_fmode_to_uint(FMODE_WRITE));
++ err = break_lease(h_inode, O_WRONLY);
+ if (err)
+ goto out_inode;
+ }
+ if (!err)
+ err = security_path_truncate(h_path, length, attr);
+ if (!err) {
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = do_truncate(h_path->dentry, length, attr, h_file);
-+ lockdep_on();
++ /* lockdep_on(); */
+ }
+
+ out_inode:
+
+ *a->errp = -EPERM;
+ if (!IS_IMMUTABLE(h_inode) && !IS_APPEND(h_inode)) {
-+ lockdep_off();
++ /* lockdep_off(); */
+ *a->errp = notify_change(a->path->dentry, a->ia);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (!*a->errp)
+ vfsub_update_h_iattr(a->path, /*did*/NULL); /*ignore*/
+ }
+ if (h_inode)
+ atomic_inc(&h_inode->i_count);
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ *a->errp = vfs_unlink(a->dir, d);
-+ lockdep_on();
++ /* lockdep_on(); */
+ if (!*a->errp) {
+ struct path tmp = {
+ .dentry = d->d_parent,
+
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/vfsub.h aufs2-2.6.git/fs/aufs/vfsub.h
---- linux-2.6.31/fs/aufs/vfsub.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/vfsub.h 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,172 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/vfsub.h linux-2.6.34/fs/aufs/vfsub.h
+--- linux-2.6.34.org/fs/aufs/vfsub.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/vfsub.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,176 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#ifdef __KERNEL__
+
+#include <linux/fs.h>
-+#include <linux/fs_stack.h>
+
+/* ---------------------------------------------------------------------- */
+
+
+/* ---------------------------------------------------------------------- */
+
-+static inline void vfsub_copy_inode_size(struct inode *inode,
-+ struct inode *h_inode)
-+{
-+ spin_lock(&inode->i_lock);
-+ fsstack_copy_inode_size(inode, h_inode);
-+ spin_unlock(&inode->i_lock);
-+}
-+
+int vfsub_update_h_iattr(struct path *h_path, int *did);
++struct file *vfsub_dentry_open(struct path *path, int flags);
+struct file *vfsub_filp_open(const char *path, int oflags, int mode);
-+struct file *vfsub_dentry_open(struct path *path, int flags,
-+ const struct cred *cred);
+int vfsub_kern_path(const char *name, unsigned int flags, struct path *path);
+struct dentry *vfsub_lookup_one_len(const char *name, struct dentry *parent,
+ int len);
+int vfsub_mkdir(struct inode *dir, struct path *path, int mode);
+int vfsub_rmdir(struct inode *dir, struct path *path);
+
-+int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode);
-+int vfsub_sio_rmdir(struct inode *dir, struct path *path);
-+int vfsub_sio_notify_change(struct path *path, struct iattr *ia);
-+int vfsub_notify_change(struct path *path, struct iattr *ia);
-+int vfsub_unlink(struct inode *dir, struct path *path, int force);
-+
+/* ---------------------------------------------------------------------- */
+
+ssize_t vfsub_read_u(struct file *file, char __user *ubuf, size_t count,
+ loff_t *ppos);
+ssize_t vfsub_write_k(struct file *file, void *kbuf, size_t count,
+ loff_t *ppos);
++int vfsub_flush(struct file *file, fl_owner_t id);
+int vfsub_readdir(struct file *file, filldir_t filldir, void *arg);
+
-+long vfsub_splice_to(struct file *in, loff_t *ppos,
-+ struct pipe_inode_info *pipe, size_t len,
-+ unsigned int flags);
-+long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
-+ loff_t *ppos, size_t len, unsigned int flags);
-+int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr,
-+ struct file *h_file);
++static inline unsigned int vfsub_file_flags(struct file *file)
++{
++ unsigned int flags;
++
++ spin_lock(&file->f_lock);
++ flags = file->f_flags;
++ spin_unlock(&file->f_lock);
++
++ return flags;
++}
+
+static inline void vfsub_file_accessed(struct file *h_file)
+{
+ vfsub_update_h_iattr(&h_path, /*did*/NULL); /*ignore*/
+}
+
++long vfsub_splice_to(struct file *in, loff_t *ppos,
++ struct pipe_inode_info *pipe, size_t len,
++ unsigned int flags);
++long vfsub_splice_from(struct pipe_inode_info *pipe, struct file *out,
++ loff_t *ppos, size_t len, unsigned int flags);
++int vfsub_trunc(struct path *h_path, loff_t length, unsigned int attr,
++ struct file *h_file);
++
+/* ---------------------------------------------------------------------- */
+
+static inline loff_t vfsub_llseek(struct file *file, loff_t offset, int origin)
+{
+ loff_t err;
+
-+ lockdep_off();
++ /* lockdep_off(); */
+ err = vfs_llseek(file, offset, origin);
-+ lockdep_on();
++ /* lockdep_on(); */
+ return err;
+}
+
+ return u.fm;
+}
+
++/* ---------------------------------------------------------------------- */
++
++int vfsub_sio_mkdir(struct inode *dir, struct path *path, int mode);
++int vfsub_sio_rmdir(struct inode *dir, struct path *path);
++int vfsub_sio_notify_change(struct path *path, struct iattr *ia);
++int vfsub_notify_change(struct path *path, struct iattr *ia);
++int vfsub_unlink(struct inode *dir, struct path *path, int force);
++
+#endif /* __KERNEL__ */
+#endif /* __AUFS_VFSUB_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/wbr_policy.c aufs2-2.6.git/fs/aufs/wbr_policy.c
---- linux-2.6.31/fs/aufs/wbr_policy.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/wbr_policy.c 2009-09-21 21:49:23.411607814 +0000
-@@ -0,0 +1,641 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/wbr_policy.c linux-2.6.34/fs/aufs/wbr_policy.c
+--- linux-2.6.34.org/fs/aufs/wbr_policy.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/wbr_policy.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,696 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ struct dentry *h_parent, void *arg)
+{
+ int err, rerr;
-+ aufs_bindex_t bend, bopq, bstart;
-+ unsigned char parent_opq;
++ aufs_bindex_t bopq, bstart;
+ struct path h_path;
+ struct dentry *parent;
+ struct inode *h_dir, *h_inode, *inode, *dir;
+ goto out_put;
+ au_fset_cpdown(args->flags, MADE_DIR);
+
-+ bend = au_dbend(dentry);
+ bopq = au_dbdiropq(dentry);
+ au_fclr_cpdown(args->flags, WHED);
+ au_fclr_cpdown(args->flags, DIROPQ);
+ au_fset_cpdown(args->flags, WHED);
+ if (!au_ftest_cpdown(args->flags, PARENT_OPQ) && bopq <= bdst)
+ au_fset_cpdown(args->flags, PARENT_OPQ);
-+ parent_opq = (au_ftest_cpdown(args->flags, PARENT_OPQ)
-+ && args->parent == dentry);
+ h_inode = h_path.dentry->d_inode;
+ mutex_lock_nested(&h_inode->i_mutex, AuLsc_I_CHILD);
+ if (au_ftest_cpdown(args->flags, WHED)) {
+
+/* policies for create */
+
++static int au_wbr_nonopq(struct dentry *dentry, aufs_bindex_t bindex)
++{
++ int err, i, j, ndentry;
++ aufs_bindex_t bopq;
++ struct au_dcsub_pages dpages;
++ struct au_dpage *dpage;
++ struct dentry **dentries, *parent, *d;
++
++ err = au_dpages_init(&dpages, GFP_NOFS);
++ if (unlikely(err))
++ goto out;
++ parent = dget_parent(dentry);
++ err = au_dcsub_pages_rev(&dpages, parent, /*do_include*/0, /*test*/NULL,
++ /*arg*/NULL);
++ if (unlikely(err))
++ goto out_free;
++
++ err = bindex;
++ for (i = 0; i < dpages.ndpage; i++) {
++ dpage = dpages.dpages + i;
++ dentries = dpage->dentries;
++ ndentry = dpage->ndentry;
++ for (j = 0; j < ndentry; j++) {
++ d = dentries[j];
++ di_read_lock_parent2(d, !AuLock_IR);
++ bopq = au_dbdiropq(d);
++ di_read_unlock(d, !AuLock_IR);
++ if (bopq >= 0 && bopq < err)
++ err = bopq;
++ }
++ }
++
++out_free:
++ dput(parent);
++ au_dpages_free(&dpages);
++out:
++ return err;
++}
++
+static int au_wbr_bu(struct super_block *sb, aufs_bindex_t bindex)
+{
+ for (; bindex >= 0; bindex--)
+ dput(parent);
+
+ /* bottom up here */
-+ if (unlikely(err < 0))
++ if (unlikely(err < 0)) {
+ err = au_wbr_bu(sb, bstart - 1);
++ if (err >= 0)
++ err = au_wbr_nonopq(dentry, err);
++ }
+
+ out:
+ AuDbg("b%d\n", err);
+ }
+ dput(parent);
+
++ if (err >= 0)
++ err = au_wbr_nonopq(dentry, err);
++
+ if (err >= 0 && au_br_rdonly(au_sbr(dentry->d_sb, err)))
+ err = -1;
+
+ err = -EROFS;
+ }
+
++ if (err >= 0)
++ err = au_wbr_nonopq(dentry, err);
++
+ out:
+ AuDbg("%d\n", err);
+ return err;
+ mutex_unlock(&mfs->mfs_lock);
+ err = mfs->mfs_bindex;
+
++ if (err >= 0)
++ err = au_wbr_nonopq(dentry, err);
++
+ out:
+ AuDbg("b%d\n", err);
+ return err;
+ }
+ }
+
++ if (err >= 0)
++ err = au_wbr_nonopq(dentry, err);
++
+ out_parent:
+ dput(parent);
+ out:
+static int au_wbr_copyup_bu(struct dentry *dentry)
+{
+ int err;
++ aufs_bindex_t bstart;
+
-+ err = au_wbr_bu(dentry->d_sb, au_dbstart(dentry));
++ bstart = au_dbstart(dentry);
++ err = au_wbr_bu(dentry->d_sb, bstart);
++ AuDbg("b%d\n", err);
++ if (err > bstart)
++ err = au_wbr_nonopq(dentry, err);
+
+ AuDbg("b%d\n", err);
+ return err;
+ .fin = au_wbr_create_fin_mfs
+ }
+};
-diff -uprN -x .git linux-2.6.31/fs/aufs/whout.c aufs2-2.6.git/fs/aufs/whout.c
---- linux-2.6.31/fs/aufs/whout.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/whout.c 2009-09-21 21:49:23.411607814 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/whout.c linux-2.6.34/fs/aufs/whout.c
+--- linux-2.6.34.org/fs/aufs/whout.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/whout.c 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,1052 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+{
+ int err;
+ struct dentry *wh_dentry;
-+ struct inode *h_dir;
+
-+ h_dir = h_parent->d_inode;
+ if (!try_sio)
+ wh_dentry = au_lkup_one(wh_name, h_parent, br, /*nd*/NULL);
+ else
+struct dentry *au_whtmp_lkup(struct dentry *h_parent, struct au_branch *br,
+ struct qstr *prefix)
+{
-+#define HEX_LEN 4
+ struct dentry *dentry;
+ int i;
-+ char defname[AUFS_WH_PFX_LEN * 2 + DNAME_INLINE_LEN_MIN + 1
-+ + HEX_LEN + 1], *name, *p;
++ char defname[NAME_MAX - AUFS_MAX_NAMELEN + DNAME_INLINE_LEN_MIN + 1],
++ *name, *p;
+ static unsigned short cnt;
+ struct qstr qs;
+
++ BUILD_BUG_ON(sizeof(cnt) * 2 > AUFS_WH_TMP_LEN);
++
+ name = defname;
+ qs.len = sizeof(defname) - DNAME_INLINE_LEN_MIN + prefix->len - 1;
+ if (unlikely(prefix->len > DNAME_INLINE_LEN_MIN)) {
+ dentry = ERR_PTR(-ENAMETOOLONG);
-+ if (unlikely(qs.len >= PATH_MAX))
++ if (unlikely(qs.len > NAME_MAX))
+ goto out;
+ dentry = ERR_PTR(-ENOMEM);
+ name = kmalloc(qs.len + 1, GFP_NOFS);
+ memcpy(p, prefix->name, prefix->len);
+ p += prefix->len;
+ *p++ = '.';
-+ AuDebugOn(name + qs.len + 1 - p <= HEX_LEN);
++ AuDebugOn(name + qs.len + 1 - p <= AUFS_WH_TMP_LEN);
+
+ qs.name = name;
+ for (i = 0; i < 3; i++) {
-+ sprintf(p, "%.*d", HEX_LEN, cnt++);
++ sprintf(p, "%.*d", AUFS_WH_TMP_LEN, cnt++);
+ dentry = au_sio_lkup_one(&qs, h_parent, br);
+ if (IS_ERR(dentry) || !dentry->d_inode)
+ goto out_name;
+ dput(dentry);
+ }
-+ /* AuWarn("could not get random name\n"); */
++ /* pr_warning("could not get random name\n"); */
+ dentry = ERR_PTR(-EEXIST);
+ AuDbg("%.*s\n", AuLNPair(&qs));
+ BUG();
+ if (name != defname)
+ kfree(name);
+ out:
++ AuTraceErrPtr(dentry);
+ return dentry;
-+#undef HEX_LEN
+}
+
+/*
+ dput(h_path.dentry);
+
+ out:
++ AuTraceErr(err);
+ return err;
+}
+
+ mnt_drop_write(whpath->mnt);
+ }
+ if (unlikely(err))
-+ AuWarn("failed removing %.*s (%d), ignored.\n",
-+ AuDLNPair(whpath->dentry), err);
++ pr_warning("failed removing %.*s (%d), ignored.\n",
++ AuDLNPair(whpath->dentry), err);
+}
+
+static int test_linkable(struct dentry *h_root)
+ if (h_dir->i_op->link)
+ return 0;
+
-+ AuErr("%.*s (%s) doesn't support link(2), use noplink and rw+nolwh\n",
-+ AuDLNPair(h_root), au_sbtype(h_root->d_sb));
++ pr_err("%.*s (%s) doesn't support link(2), use noplink and rw+nolwh\n",
++ AuDLNPair(h_root), au_sbtype(h_root->d_sb));
+ return -ENOSYS;
+}
+
+ } else if (S_ISDIR(path->dentry->d_inode->i_mode))
+ err = 0;
+ else
-+ AuErr("unknown %.*s exists\n", AuDLNPair(path->dentry));
++ pr_err("unknown %.*s exists\n", AuDLNPair(path->dentry));
+
+ return err;
+}
+ } 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(base[AuBrWh_BASE].dentry));
++ pr_err("unknown %.*s/%.*s exists\n",
++ AuDLNPair(h_root), AuDLNPair(base[AuBrWh_BASE].dentry));
+ if (unlikely(err))
+ goto out;
+
+ if (wbr)
+ WbrWhMustWriteLock(wbr);
+
-+ h_dir = h_root->d_inode;
+ for (i = 0; i < AuBrWh_Last; i++) {
+ /* doubly whiteouted */
+ struct dentry *d;
+ }
+
+ err = 0;
-+
+ switch (br->br_perm) {
+ case AuBrPerm_RO:
+ case AuBrPerm_ROWH:
+ case AuBrPerm_RR:
+ case AuBrPerm_RRWH:
++ h_dir = h_root->d_inode;
+ au_wh_init_ro(h_dir, base, &path);
+ break;
+
+ goto out; /* success */
+
+ out_err:
-+ AuErr("an error(%d) on the writable branch %.*s(%s)\n",
-+ err, AuDLNPair(h_root), au_sbtype(h_root->d_sb));
++ pr_err("an error(%d) on the writable branch %.*s(%s)\n",
++ err, AuDLNPair(h_root), au_sbtype(h_root->d_sb));
+ out:
+ for (i = 0; i < AuBrWh_Last; i++)
+ dput(base[i].dentry);
+ hdir = au_hi(dir, bindex);
+ h_root = au_h_dptr(a->sb->s_root, bindex);
+
-+ au_hin_imtx_lock_nested(hdir, AuLsc_I_PARENT);
++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT);
+ wbr_wh_write_lock(wbr);
+ err = au_h_verify(wbr->wbr_whbase, au_opt_udba(a->sb), hdir->hi_inode,
+ h_root, a->br);
+ mnt_drop_write(a->br->br_mnt);
+ }
+ } else {
-+ AuWarn("%.*s is moved, ignored\n", AuDLNPair(wbr->wbr_whbase));
++ pr_warning("%.*s is moved, ignored\n",
++ AuDLNPair(wbr->wbr_whbase));
+ err = 0;
+ }
+ dput(wbr->wbr_whbase);
+ if (!err)
+ err = au_wh_init(h_root, a->br, a->sb);
+ wbr_wh_write_unlock(wbr);
-+ au_hin_imtx_unlock(hdir);
++ au_hn_imtx_unlock(hdir);
+ di_read_unlock(a->sb->s_root, AuLock_IR);
+
+ out:
+ struct au_vdir_destr *str;
+
+ err = -ENOMEM;
-+ p = __getname();
++ p = __getname_gfp(GFP_NOFS);
+ wh_name.name = p;
+ if (unlikely(!wh_name.name))
+ goto out;
+ return 0; /* success */
+ }
+
-+ AuWarn("failed removing %.*s(%d), ignored\n",
-+ AuDLNPair(wh_dentry), err);
++ pr_warning("failed removing %.*s(%d), ignored\n",
++ AuDLNPair(wh_dentry), err);
+ return err;
+}
+
+ h_parent = dget_parent(a->wh_dentry);
+ h_dir = h_parent->d_inode;
+ hdir = au_hi(a->dir, a->bindex);
-+ au_hin_imtx_lock_nested(hdir, AuLsc_I_PARENT);
++ au_hn_imtx_lock_nested(hdir, AuLsc_I_PARENT);
+ err = au_h_verify(a->wh_dentry, au_opt_udba(sb), h_dir, h_parent, br);
+ if (!err) {
+ err = mnt_want_write(br->br_mnt);
+ mnt_drop_write(br->br_mnt);
+ }
+ }
-+ au_hin_imtx_unlock(hdir);
++ au_hn_imtx_unlock(hdir);
+ dput(h_parent);
+ ii_write_unlock(a->dir);
+
+ args->wh_dentry = dget(wh_dentry);
+ wkq_err = au_wkq_nowait(call_rmdir_whtmp, args, dir->i_sb);
+ if (unlikely(wkq_err)) {
-+ AuWarn("rmdir error %.*s (%d), ignored\n",
-+ AuDLNPair(wh_dentry), wkq_err);
++ pr_warning("rmdir error %.*s (%d), ignored\n",
++ AuDLNPair(wh_dentry), wkq_err);
+ au_whtmp_rmdir_free(args);
+ }
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/whout.h aufs2-2.6.git/fs/aufs/whout.h
---- linux-2.6.31/fs/aufs/whout.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/whout.h 2009-09-21 21:49:23.414941217 +0000
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/whout.h linux-2.6.34/fs/aufs/whout.h
+--- linux-2.6.34.org/fs/aufs/whout.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/whout.h 2010-05-31 22:15:32.000000000 +0200
@@ -0,0 +1,87 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_WHOUT_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/wkq.c aufs2-2.6.git/fs/aufs/wkq.c
---- linux-2.6.31/fs/aufs/wkq.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/wkq.c 2009-09-21 21:49:23.414941217 +0000
-@@ -0,0 +1,259 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/wkq.c linux-2.6.34/fs/aufs/wkq.c
+--- linux-2.6.34.org/fs/aufs/wkq.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/wkq.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,183 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#include "aufs.h"
+
+/* internal workqueue named AUFS_WKQ_NAME */
-+static struct au_wkq {
-+ struct workqueue_struct *q;
-+
-+ /* balancing */
-+ atomic_t busy;
-+} *au_wkq;
++static struct workqueue_struct *au_wkq;
+
+struct au_wkinfo {
+ struct work_struct wk;
+ au_wkq_func_t func;
+ void *args;
+
-+ atomic_t *busyp;
+ struct completion *comp;
+};
+
+/* ---------------------------------------------------------------------- */
+
-+static int enqueue(struct au_wkq *wkq, struct au_wkinfo *wkinfo)
-+{
-+ wkinfo->busyp = &wkq->busy;
-+ if (au_ftest_wkq(wkinfo->flags, WAIT))
-+ return !queue_work(wkq->q, &wkinfo->wk);
-+ else
-+ return !schedule_work(&wkinfo->wk);
-+}
-+
-+static void do_wkq(struct au_wkinfo *wkinfo)
-+{
-+ unsigned int idle, n;
-+ int i, idle_idx;
-+
-+ while (1) {
-+ if (au_ftest_wkq(wkinfo->flags, WAIT)) {
-+ idle_idx = 0;
-+ idle = UINT_MAX;
-+ for (i = 0; i < aufs_nwkq; i++) {
-+ n = atomic_inc_return(&au_wkq[i].busy);
-+ if (n == 1 && !enqueue(au_wkq + i, wkinfo))
-+ return; /* success */
-+
-+ if (n < idle) {
-+ idle_idx = i;
-+ idle = n;
-+ }
-+ atomic_dec(&au_wkq[i].busy);
-+ }
-+ } else
-+ idle_idx = aufs_nwkq;
-+
-+ atomic_inc(&au_wkq[idle_idx].busy);
-+ if (!enqueue(au_wkq + idle_idx, wkinfo))
-+ return; /* success */
-+
-+ /* impossible? */
-+ AuWarn1("failed to queue_work()\n");
-+ yield();
-+ }
-+}
-+
+static void wkq_func(struct work_struct *wk)
+{
+ struct au_wkinfo *wkinfo = container_of(wk, struct au_wkinfo, wk);
+
+ wkinfo->func(wkinfo->args);
-+ atomic_dec_return(wkinfo->busyp);
+ if (au_ftest_wkq(wkinfo->flags, WAIT))
+ complete(wkinfo->comp);
+ else {
+}
+#endif /* 4KSTACKS */
+
-+static void au_wkq_run(struct au_wkinfo *wkinfo)
++static void au_wkq_run(struct au_wkinfo *wkinfo, int do_wait)
+{
+ au_dbg_verify_kthread();
-+ INIT_WORK(&wkinfo->wk, wkq_func);
-+ do_wkq(wkinfo);
++ if (do_wait) {
++ INIT_WORK_ON_STACK(&wkinfo->wk, wkq_func);
++ queue_work(au_wkq, &wkinfo->wk);
++ } else {
++ INIT_WORK(&wkinfo->wk, wkq_func);
++ schedule_work(&wkinfo->wk);
++ }
+}
+
+int au_wkq_wait(au_wkq_func_t func, void *args)
+
+ err = au_wkq_comp_alloc(&wkinfo, &comp);
+ if (!err) {
-+ au_wkq_run(&wkinfo);
++ au_wkq_run(&wkinfo, AuWkq_WAIT);
+ /* no timeout, no interrupt */
+ wait_for_completion(wkinfo.comp);
+ au_wkq_comp_free(comp);
++ destroy_work_on_stack(&wkinfo.wk);
+ }
+
+ return err;
+ kobject_get(&au_sbi(sb)->si_kobj);
+ __module_get(THIS_MODULE);
+
-+ au_wkq_run(wkinfo);
++ au_wkq_run(wkinfo, !AuWkq_WAIT);
+ } else {
+ err = -ENOMEM;
+ atomic_dec(&au_sbi(sb)->si_nowait.nw_len);
+void au_nwt_init(struct au_nowait_tasks *nwt)
+{
+ atomic_set(&nwt->nw_len, 0);
-+ /* smp_mb();*/ /* atomic_set */
++ /* smp_mb(); */ /* atomic_set */
+ init_waitqueue_head(&nwt->nw_wq);
+}
+
+void au_wkq_fin(void)
+{
-+ int i;
-+
-+ for (i = 0; i < aufs_nwkq; i++)
-+ if (au_wkq[i].q && !IS_ERR(au_wkq[i].q))
-+ destroy_workqueue(au_wkq[i].q);
-+ kfree(au_wkq);
++ destroy_workqueue(au_wkq);
+}
+
+int __init au_wkq_init(void)
+{
-+ int err, i;
-+ struct au_wkq *nowaitq;
-+
-+ /* '+1' is for accounting of nowait queue */
-+ err = -ENOMEM;
-+ au_wkq = kcalloc(aufs_nwkq + 1, sizeof(*au_wkq), GFP_NOFS);
-+ if (unlikely(!au_wkq))
-+ goto out;
-+
-+ err = 0;
-+ for (i = 0; i < aufs_nwkq; i++) {
-+ 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);
-+ continue;
-+ }
-+
-+ err = PTR_ERR(au_wkq[i].q);
-+ au_wkq_fin();
-+ goto out;
-+ }
-+
-+ /* nowait accounting */
-+ nowaitq = au_wkq + aufs_nwkq;
-+ atomic_set(&nowaitq->busy, 0);
-+ nowaitq->q = NULL;
-+ /* smp_mb(); */ /* atomic_set */
-+
-+ out:
-+ return err;
++ au_wkq = create_workqueue(AUFS_WKQ_NAME);
++ return 0;
+}
-diff -uprN -x .git linux-2.6.31/fs/aufs/wkq.h aufs2-2.6.git/fs/aufs/wkq.h
---- linux-2.6.31/fs/aufs/wkq.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/wkq.h 2009-09-21 21:49:23.414941217 +0000
-@@ -0,0 +1,82 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/wkq.h linux-2.6.34/fs/aufs/wkq.h
+--- linux-2.6.34.org/fs/aufs/wkq.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/wkq.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,84 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+static inline int au_test_wkq(struct task_struct *tsk)
+{
-+ return !tsk->mm && !strcmp(tsk->comm, AUFS_WKQ_NAME);
++ return !tsk->mm
++ && !strncmp(tsk->comm, AUFS_WKQ_NAME "/",
++ sizeof(AUFS_WKQ_NAME));
+}
+
+static inline void au_nwt_done(struct au_nowait_tasks *nwt)
+
+#endif /* __KERNEL__ */
+#endif /* __AUFS_WKQ_H__ */
-diff -uprN -x .git linux-2.6.31/fs/aufs/xino.c aufs2-2.6.git/fs/aufs/xino.c
---- linux-2.6.31/fs/aufs/xino.c 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/fs/aufs/xino.c 2009-09-21 21:49:23.414941217 +0000
-@@ -0,0 +1,1203 @@
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/aufs/xino.c linux-2.6.34/fs/aufs/xino.c
+--- linux-2.6.34.org/fs/aufs/xino.c 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/fs/aufs/xino.c 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,1202 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+
+ oldfs = get_fs();
+ set_fs(KERNEL_DS);
-+ lockdep_off();
++ /* lockdep_off(); */
+ do {
+ /* todo: signal_pending? */
+ err = func(file, (const char __user *)buf, size, pos);
+ } while (err == -EAGAIN || err == -EINTR);
-+ lockdep_on();
++ /* lockdep_on(); */
+ set_fs(oldfs);
+
+#if 0 /* reserved for future use */
+struct file *au_xino_create2(struct file *base_file, struct file *copy_src)
+{
+ struct file *file;
-+ struct dentry *base, *dentry, *parent;
++ struct dentry *base, *parent;
+ struct inode *dir;
+ struct qstr *name;
-+ int err;
+ struct path path;
++ int err;
+
+ base = base_file->f_dentry;
+ parent = base->d_parent; /* dir inode is locked */
+
+ file = ERR_PTR(-EINVAL);
+ name = &base->d_name;
-+ dentry = vfsub_lookup_one_len(name->name, parent, name->len);
-+ if (IS_ERR(dentry)) {
-+ file = (void *)dentry;
-+ AuErr("%.*s lookup err %ld\n", AuLNPair(name), PTR_ERR(dentry));
++ path.dentry = vfsub_lookup_one_len(name->name, parent, name->len);
++ if (IS_ERR(path.dentry)) {
++ file = (void *)path.dentry;
++ pr_err("%.*s lookup err %ld\n",
++ AuLNPair(name), PTR_ERR(path.dentry));
+ goto out;
+ }
+
+ /* no need to mnt_want_write() since we call dentry_open() later */
-+ err = vfs_create(dir, dentry, S_IRUGO | S_IWUGO, NULL);
++ err = vfs_create(dir, path.dentry, S_IRUGO | S_IWUGO, NULL);
+ if (unlikely(err)) {
+ file = ERR_PTR(err);
-+ AuErr("%.*s create err %d\n", AuLNPair(name), err);
++ pr_err("%.*s create err %d\n", AuLNPair(name), err);
+ goto out_dput;
+ }
+
-+ path.dentry = dentry;
+ path.mnt = base_file->f_vfsmnt;
-+ path_get(&path);
-+ file = vfsub_dentry_open(&path, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE,
-+ current_cred());
++ file = vfsub_dentry_open(&path,
++ O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE);
+ if (IS_ERR(file)) {
-+ AuErr("%.*s open err %ld\n", AuLNPair(name), PTR_ERR(file));
++ pr_err("%.*s open err %ld\n", AuLNPair(name), PTR_ERR(file));
+ goto out_dput;
+ }
+
+ err = vfsub_unlink(dir, &file->f_path, /*force*/0);
+ if (unlikely(err)) {
-+ AuErr("%.*s unlink err %d\n", AuLNPair(name), err);
++ pr_err("%.*s unlink err %d\n", AuLNPair(name), err);
+ goto out_fput;
+ }
+
+ err = au_copy_file(file, copy_src,
+ i_size_read(copy_src->f_dentry->d_inode));
+ if (unlikely(err)) {
-+ AuErr("%.*s copy err %d\n", AuLNPair(name), err);
++ pr_err("%.*s copy err %d\n", AuLNPair(name), err);
+ goto out_fput;
+ }
+ }
+ fput(file);
+ file = ERR_PTR(err);
+ out_dput:
-+ dput(dentry);
++ dput(path.dentry);
+ out:
+ return file;
+}
+ bindex = au_br_index(sb, brid);
+ if (bindex >= 0) {
+ ldir->hdir = au_hi(sb->s_root->d_inode, bindex);
-+ au_hin_imtx_lock_nested(ldir->hdir, AuLsc_I_PARENT);
++ au_hn_imtx_lock_nested(ldir->hdir, AuLsc_I_PARENT);
+ } else {
+ ldir->parent = dget_parent(xino->f_dentry);
+ ldir->mtx = &ldir->parent->d_inode->i_mutex;
+static void au_xino_unlock_dir(struct au_xino_lock_dir *ldir)
+{
+ if (ldir->hdir)
-+ au_hin_imtx_unlock(ldir->hdir);
++ au_hn_imtx_unlock(ldir->hdir);
+ else {
+ mutex_unlock(ldir->mtx);
+ dput(ldir->parent);
+
+ ii_read_unlock(dir);
+ if (unlikely(err))
-+ AuWarn("err b%d, (%d)\n", bindex, err);
++ pr_warning("err b%d, (%d)\n", bindex, err);
+ atomic_dec(&br->br_xino_running);
+ atomic_dec(&br->br_count);
+ au_nwt_done(&au_sbi(sb)->si_nowait);
+ if (!wkq_err)
+ return; /* success */
+
-+ AuErr("wkq %d\n", wkq_err);
++ pr_err("wkq %d\n", wkq_err);
+ atomic_dec_return(&br->br_count);
+
+ out_args:
+
+ /*
+ * at mount-time, and the xino file is the default path,
-+ * hinotify is disabled so we have no inotify events to ignore.
++ * hnotify is disabled so we have no notify events to ignore.
+ * when a user specified the xino, we cannot get au_hdir to be ignored.
+ */
+ file = vfsub_filp_open(fname, O_RDWR | O_CREAT | O_EXCL | O_LARGEFILE,
+ S_IRUGO | S_IWUGO);
+ if (IS_ERR(file)) {
+ if (!silent)
-+ AuErr("open %s(%ld)\n", fname, PTR_ERR(file));
++ pr_err("open %s(%ld)\n", fname, PTR_ERR(file));
+ return file;
+ }
+
+ dput(h_parent);
+ if (unlikely(err)) {
+ if (!silent)
-+ AuErr("unlink %s(%d)\n", fname, err);
++ pr_err("unlink %s(%d)\n", fname, err);
+ goto out;
+ }
+
+ d = file->f_dentry;
+ if (unlikely(sb == d->d_sb)) {
+ if (!silent)
-+ AuErr("%s must be outside\n", fname);
++ pr_err("%s must be outside\n", fname);
+ goto out;
+ }
+ if (unlikely(au_test_fs_bad_xino(d->d_sb))) {
+ if (!silent)
-+ AuErr("xino doesn't support %s(%s)\n",
-+ fname, au_sbtype(d->d_sb));
++ pr_err("xino doesn't support %s(%s)\n",
++ fname, au_sbtype(d->d_sb));
+ goto out;
+ }
+ return file; /* success */
+
+ if (bwr >= 0) {
+ file = ERR_PTR(-ENOMEM);
-+ page = __getname();
++ page = __getname_gfp(GFP_NOFS);
+ if (unlikely(!page))
+ goto out;
+ path.mnt = br->br_mnt;
+ goto out;
+ h_sb = file->f_dentry->d_sb;
+ if (unlikely(au_test_fs_bad_xino(h_sb))) {
-+ AuErr("xino doesn't support %s(%s)\n",
-+ AUFS_XINO_DEFPATH, au_sbtype(h_sb));
++ pr_err("xino doesn't support %s(%s)\n",
++ AUFS_XINO_DEFPATH, au_sbtype(h_sb));
+ fput(file);
+ file = ERR_PTR(-EINVAL);
+ }
+ out:
+ return err;
+}
-diff -uprN -x .git linux-2.6.31/fs/namei.c aufs2-2.6.git/fs/namei.c
---- linux-2.6.31/fs/namei.c 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/fs/namei.c 2009-09-21 21:49:25.001190884 +0000
-@@ -1219,7 +1219,7 @@ out:
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/Kconfig linux-2.6.34/fs/Kconfig
+--- linux-2.6.34.org/fs/Kconfig 2010-05-31 22:19:12.649639677 +0200
++++ linux-2.6.34/fs/Kconfig 2010-05-31 22:20:27.150695120 +0200
+@@ -190,6 +190,7 @@
+ source "fs/sysv/Kconfig"
+ source "fs/ufs/Kconfig"
+ source "fs/exofs/Kconfig"
++source "fs/aufs/Kconfig"
+
+ endif # MISC_FILESYSTEMS
+
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/Makefile linux-2.6.34/fs/Makefile
+--- linux-2.6.34.org/fs/Makefile 2010-05-31 22:19:12.699737326 +0200
++++ linux-2.6.34/fs/Makefile 2010-05-31 22:20:27.207546162 +0200
+@@ -127,3 +127,4 @@
+ obj-$(CONFIG_GFS2_FS) += gfs2/
+ obj-$(CONFIG_EXOFS_FS) += exofs/
+ obj-$(CONFIG_CEPH_FS) += ceph/
++obj-$(CONFIG_AUFS_FS) += aufs/
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/namei.c linux-2.6.34/fs/namei.c
+--- linux-2.6.34.org/fs/namei.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/fs/namei.c 2010-05-31 22:20:36.579878021 +0200
+@@ -348,6 +348,7 @@
+
+ return 0;
+ }
++EXPORT_SYMBOL(deny_write_access);
+
+ /**
+ * path_get - get a reference to a path
+@@ -1177,7 +1178,7 @@
* needs parent already locked. Doesn't follow mounts.
* SMP-safe.
*/
{
int err;
-@@ -1229,7 +1229,7 @@ static struct dentry *lookup_hash(struct
+@@ -1186,8 +1187,9 @@
+ return ERR_PTR(err);
return __lookup_hash(&nd->last, nd->path.dentry, nd);
}
++EXPORT_SYMBOL(lookup_hash);
-static int __lookup_one_len(const char *name, struct qstr *this,
+int __lookup_one_len(const char *name, struct qstr *this,
struct dentry *base, int len)
{
unsigned long hash;
-diff -uprN -x .git linux-2.6.31/fs/splice.c aufs2-2.6.git/fs/splice.c
---- linux-2.6.31/fs/splice.c 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/fs/splice.c 2009-09-21 21:49:25.471607719 +0000
-@@ -1057,8 +1057,8 @@ EXPORT_SYMBOL(generic_splice_sendpage);
+@@ -1208,6 +1210,7 @@
+ this->hash = end_name_hash(hash);
+ return 0;
+ }
++EXPORT_SYMBOL(__lookup_one_len);
+
+ /**
+ * lookup_one_len - filesystem helper to lookup single pathname component
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/namespace.c linux-2.6.34/fs/namespace.c
+--- linux-2.6.34.org/fs/namespace.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/fs/namespace.c 2010-05-31 22:20:36.586473532 +0200
+@@ -1282,6 +1282,7 @@
+ }
+ return 0;
+ }
++EXPORT_SYMBOL(iterate_mounts);
+
+ static void cleanup_group_ids(struct vfsmount *mnt, struct vfsmount *end)
+ {
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/notify/group.c linux-2.6.34/fs/notify/group.c
+--- linux-2.6.34.org/fs/notify/group.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/fs/notify/group.c 2010-05-31 22:20:36.589687896 +0200
+@@ -22,6 +22,7 @@
+ #include <linux/srcu.h>
+ #include <linux/rculist.h>
+ #include <linux/wait.h>
++#include <linux/module.h>
+
+ #include <linux/fsnotify_backend.h>
+ #include "fsnotify.h"
+@@ -169,6 +170,7 @@
+ fsnotify_recalc_global_mask();
+ fsnotify_destroy_group(group);
+ }
++EXPORT_SYMBOL(fsnotify_put_group);
+
+ /*
+ * Simply run the fsnotify_groups list and find a group which matches
+@@ -252,3 +254,4 @@
+
+ return group;
+ }
++EXPORT_SYMBOL(fsnotify_obtain_group);
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/notify/inode_mark.c linux-2.6.34/fs/notify/inode_mark.c
+--- linux-2.6.34.org/fs/notify/inode_mark.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/fs/notify/inode_mark.c 2010-05-31 22:20:36.593009117 +0200
+@@ -105,6 +105,7 @@
+ if (atomic_dec_and_test(&entry->refcnt))
+ entry->free_mark(entry);
+ }
++EXPORT_SYMBOL(fsnotify_put_mark);
+
+ /*
+ * Recalculate the mask of events relevant to a given inode locked.
+@@ -215,6 +216,7 @@
+ if (unlikely(atomic_dec_and_test(&group->num_marks)))
+ fsnotify_final_destroy_group(group);
+ }
++EXPORT_SYMBOL(fsnotify_destroy_mark_by_entry);
+
+ /*
+ * Given a group, destroy all of the marks associated with that group.
+@@ -281,6 +283,7 @@
+ }
+ return NULL;
+ }
++EXPORT_SYMBOL(fsnotify_find_mark_entry);
+
+ /*
+ * Nothing fancy, just initialize lists and locks and counters.
+@@ -297,6 +300,7 @@
+ entry->inode = NULL;
+ entry->free_mark = free_mark;
+ }
++EXPORT_SYMBOL(fsnotify_init_mark);
+
+ /*
+ * Attach an initialized mark entry to a given group and inode.
+@@ -352,6 +356,7 @@
+
+ return ret;
+ }
++EXPORT_SYMBOL(fsnotify_add_mark);
+
+ /**
+ * fsnotify_unmount_inodes - an sb is unmounting. handle any watched inodes.
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/open.c linux-2.6.34/fs/open.c
+--- linux-2.6.34.org/fs/open.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/fs/open.c 2010-05-31 22:20:36.593009117 +0200
+@@ -225,6 +225,7 @@
+ mutex_unlock(&dentry->d_inode->i_mutex);
+ return ret;
+ }
++EXPORT_SYMBOL(do_truncate);
+
+ static long do_sys_truncate(const char __user *pathname, loff_t length)
+ {
+diff -urN --exclude '*.orig' linux-2.6.34.org/fs/splice.c linux-2.6.34/fs/splice.c
+--- linux-2.6.34.org/fs/splice.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/fs/splice.c 2010-05-31 22:20:36.593009117 +0200
+@@ -1054,8 +1054,8 @@
/*
* Attempt to initiate a splice from pipe to file.
*/
{
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,
loff_t *, size_t, unsigned int);
-@@ -1084,9 +1084,9 @@ static long do_splice_from(struct pipe_i
+@@ -1078,13 +1078,14 @@
+
+ return splice_write(pipe, out, ppos, len, flags);
+ }
++EXPORT_SYMBOL(do_splice_from);
+
/*
* Attempt to initiate a splice from a file to a pipe.
*/
{
ssize_t (*splice_read)(struct file *, loff_t *,
struct pipe_inode_info *, size_t, unsigned int);
-diff -uprN -x .git linux-2.6.31/include/linux/Kbuild aufs2-2.6.git/include/linux/Kbuild
---- linux-2.6.31/include/linux/Kbuild 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/include/linux/Kbuild 2009-09-21 21:49:26.084940677 +0000
-@@ -34,6 +34,7 @@ header-y += atmppp.h
- header-y += atmsap.h
- header-y += atmsvc.h
- header-y += atm_zatm.h
-+header-y += aufs_type.h
- header-y += auto_fs4.h
- header-y += ax25.h
- header-y += b1lli.h
-diff -uprN -x .git linux-2.6.31/include/linux/aufs_type.h aufs2-2.6.git/include/linux/aufs_type.h
---- linux-2.6.31/include/linux/aufs_type.h 1970-01-01 00:00:00.000000000 +0000
-+++ aufs2-2.6.git/include/linux/aufs_type.h 2009-09-21 21:49:26.101190816 +0000
-@@ -0,0 +1,184 @@
+@@ -1104,6 +1105,7 @@
+
+ return splice_read(in, ppos, pipe, len, flags);
+ }
++EXPORT_SYMBOL(do_splice_to);
+
+ /**
+ * splice_direct_to_actor - splices data directly between two non-pipes
+diff -urN --exclude '*.orig' linux-2.6.34.org/include/linux/aufs_type.h linux-2.6.34/include/linux/aufs_type.h
+--- linux-2.6.34.org/include/linux/aufs_type.h 1970-01-01 01:00:00.000000000 +0100
++++ linux-2.6.34/include/linux/aufs_type.h 2010-05-31 22:15:32.000000000 +0200
+@@ -0,0 +1,198 @@
+/*
-+ * Copyright (C) 2005-2009 Junjiro R. Okajima
++ * Copyright (C) 2005-2010 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+#define __AUFS_TYPE_H__
+
+#include <linux/ioctl.h>
++/* for those who didn't "make headers_install" */
++#ifdef __KERNEL__
++#include <linux/kernel.h>
++#endif
++#include <linux/limits.h>
+#include <linux/types.h>
+
-+#define AUFS_VERSION "2-31"
++#define AUFS_VERSION "2-standalone.tree-34-20100531"
+
+/* todo? move this to linux-2.6.19/include/magic.h */
+#define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's')
+
+#define AUFS_WH_PFX ".wh."
+#define AUFS_WH_PFX_LEN ((int)sizeof(AUFS_WH_PFX) - 1)
++#define AUFS_WH_TMP_LEN 4
++/* a limit for rmdir/rename a dir */
++#define AUFS_MAX_NAMELEN (NAME_MAX \
++ - AUFS_WH_PFX_LEN * 2 /* doubly whiteouted */\
++ - 1 /* dot */\
++ - AUFS_WH_TMP_LEN) /* hex */
+#define AUFS_XINO_FNAME "." AUFS_NAME ".xino"
+#define AUFS_XINO_DEFPATH "/tmp/" AUFS_XINO_FNAME
+#define AUFS_XINO_TRUNC_INIT 64 /* blocks */
+#define AUFS_RDBLK_DEF 512 /* bytes */
+#define AUFS_RDHASH_DEF 32
+#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 */
+
+
+ /* readdir in userspace */
+ AuCtl_RDU,
-+ AuCtl_RDU_INO
++ AuCtl_RDU_INO,
++
++ /* pathconf wrapper */
++ AuCtl_WBR_FD
+};
+
+/* borrowed from linux/include/linux/kernel.h */
+#define AUFS_CTL_PLINK_CLEAN _IO(AuCtlType, AuCtl_PLINK_CLEAN)
+#define AUFS_CTL_RDU _IOWR(AuCtlType, AuCtl_RDU, struct aufs_rdu)
+#define AUFS_CTL_RDU_INO _IOWR(AuCtlType, AuCtl_RDU_INO, struct aufs_rdu)
++#define AUFS_CTL_WBR_FD _IO(AuCtlType, AuCtl_WBR_FD)
+
+#endif /* __AUFS_TYPE_H__ */
-diff -uprN -x .git linux-2.6.31/include/linux/namei.h aufs2-2.6.git/include/linux/namei.h
---- linux-2.6.31/include/linux/namei.h 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/include/linux/namei.h 2009-09-21 21:49:26.484529184 +0000
-@@ -75,6 +75,9 @@ extern struct file *lookup_instantiate_f
- extern struct file *nameidata_to_filp(struct nameidata *nd, int flags);
- extern void release_open_intent(struct nameidata *);
+diff -urN --exclude '*.orig' linux-2.6.34.org/include/linux/Kbuild linux-2.6.34/include/linux/Kbuild
+--- linux-2.6.34.org/include/linux/Kbuild 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/include/linux/Kbuild 2010-05-31 22:20:27.267087305 +0200
+@@ -34,6 +34,7 @@
+ header-y += atmsap.h
+ header-y += atmsvc.h
+ header-y += atm_zatm.h
++header-y += aufs_type.h
+ header-y += auto_fs4.h
+ header-y += ax25.h
+ header-y += b1lli.h
+diff -urN --exclude '*.orig' linux-2.6.34.org/include/linux/namei.h linux-2.6.34/include/linux/namei.h
+--- linux-2.6.34.org/include/linux/namei.h 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/include/linux/namei.h 2010-05-31 22:20:31.663379109 +0200
+@@ -73,6 +73,9 @@
+ extern struct file *lookup_instantiate_filp(struct nameidata *nd, struct dentry *dentry,
+ int (*open)(struct inode *, struct file *));
+extern struct dentry *lookup_hash(struct nameidata *nd);
+extern int __lookup_one_len(const char *name, struct qstr *this,
+ struct dentry *base, int len);
extern struct dentry *lookup_one_len(const char *, struct dentry *, int);
- extern struct dentry *lookup_one_noperm(const char *, struct dentry *);
-diff -uprN -x .git linux-2.6.31/include/linux/splice.h aufs2-2.6.git/include/linux/splice.h
---- linux-2.6.31/include/linux/splice.h 2009-09-09 22:13:59.000000000 +0000
-+++ aufs2-2.6.git/include/linux/splice.h 2009-09-21 21:49:26.544523817 +0000
-@@ -82,4 +82,10 @@ extern ssize_t splice_to_pipe(struct pip
+ extern int follow_down(struct path *);
+diff -urN --exclude '*.orig' linux-2.6.34.org/include/linux/splice.h linux-2.6.34/include/linux/splice.h
+--- linux-2.6.34.org/include/linux/splice.h 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/include/linux/splice.h 2010-05-31 22:20:31.666712433 +0200
+@@ -82,4 +82,10 @@
extern ssize_t splice_direct_to_actor(struct file *, struct splice_desc *,
splice_direct_actor *);
+ unsigned int flags);
+
#endif
+diff -urN --exclude '*.orig' linux-2.6.34.org/security/commoncap.c linux-2.6.34/security/commoncap.c
+--- linux-2.6.34.org/security/commoncap.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/security/commoncap.c 2010-05-31 22:20:36.596333970 +0200
+@@ -951,3 +951,4 @@
+ }
+ return ret;
+ }
++EXPORT_SYMBOL(cap_file_mmap);
+diff -urN --exclude '*.orig' linux-2.6.34.org/security/device_cgroup.c linux-2.6.34/security/device_cgroup.c
+--- linux-2.6.34.org/security/device_cgroup.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/security/device_cgroup.c 2010-05-31 22:20:36.596333970 +0200
+@@ -515,6 +515,7 @@
+
+ return -EPERM;
+ }
++EXPORT_SYMBOL(devcgroup_inode_permission);
+
+ int devcgroup_inode_mknod(int mode, dev_t dev)
+ {
+diff -urN --exclude '*.orig' linux-2.6.34.org/security/security.c linux-2.6.34/security/security.c
+--- linux-2.6.34.org/security/security.c 2010-05-16 23:17:36.000000000 +0200
++++ linux-2.6.34/security/security.c 2010-05-31 22:20:36.596333970 +0200
+@@ -411,6 +411,7 @@
+ return 0;
+ return security_ops->path_mkdir(dir, dentry, mode);
+ }
++EXPORT_SYMBOL(security_path_mkdir);
+
+ int security_path_rmdir(struct path *dir, struct dentry *dentry)
+ {
+@@ -418,6 +419,7 @@
+ return 0;
+ return security_ops->path_rmdir(dir, dentry);
+ }
++EXPORT_SYMBOL(security_path_rmdir);
+
+ int security_path_unlink(struct path *dir, struct dentry *dentry)
+ {
+@@ -425,6 +427,7 @@
+ return 0;
+ return security_ops->path_unlink(dir, dentry);
+ }
++EXPORT_SYMBOL(security_path_unlink);
+
+ int security_path_symlink(struct path *dir, struct dentry *dentry,
+ const char *old_name)
+@@ -433,6 +436,7 @@
+ return 0;
+ return security_ops->path_symlink(dir, dentry, old_name);
+ }
++EXPORT_SYMBOL(security_path_symlink);
+
+ int security_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry)
+@@ -441,6 +445,7 @@
+ return 0;
+ return security_ops->path_link(old_dentry, new_dir, new_dentry);
+ }
++EXPORT_SYMBOL(security_path_link);
+
+ int security_path_rename(struct path *old_dir, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry)
+@@ -451,6 +456,7 @@
+ return security_ops->path_rename(old_dir, old_dentry, new_dir,
+ new_dentry);
+ }
++EXPORT_SYMBOL(security_path_rename);
+
+ int security_path_truncate(struct path *path, loff_t length,
+ unsigned int time_attrs)
+@@ -459,6 +465,7 @@
+ return 0;
+ return security_ops->path_truncate(path, length, time_attrs);
+ }
++EXPORT_SYMBOL(security_path_truncate);
+
+ int security_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
+ mode_t mode)
+@@ -467,6 +474,7 @@
+ return 0;
+ return security_ops->path_chmod(dentry, mnt, mode);
+ }
++EXPORT_SYMBOL(security_path_chmod);
+
+ int security_path_chown(struct path *path, uid_t uid, gid_t gid)
+ {
+@@ -474,6 +482,7 @@
+ return 0;
+ return security_ops->path_chown(path, uid, gid);
+ }
++EXPORT_SYMBOL(security_path_chown);
+
+ int security_path_chroot(struct path *path)
+ {
+@@ -550,6 +559,7 @@
+ return 0;
+ return security_ops->inode_readlink(dentry);
+ }
++EXPORT_SYMBOL(security_inode_readlink);
+
+ int security_inode_follow_link(struct dentry *dentry, struct nameidata *nd)
+ {
+@@ -564,6 +574,7 @@
+ return 0;
+ return security_ops->inode_permission(inode, mask);
+ }
++EXPORT_SYMBOL(security_inode_permission);
+
+ int security_inode_setattr(struct dentry *dentry, struct iattr *attr)
+ {
+@@ -664,6 +675,7 @@
+ {
+ return security_ops->file_permission(file, mask);
+ }
++EXPORT_SYMBOL(security_file_permission);
+
+ int security_file_alloc(struct file *file)
+ {
+@@ -691,6 +703,7 @@
+ return ret;
+ return ima_file_mmap(file, prot);
+ }
++EXPORT_SYMBOL(security_file_mmap);
+
+ int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
+ unsigned long prot)