+
+For more information, see <http://unionfs.filesystems.org/>.
diff --git a/MAINTAINERS b/MAINTAINERS
-index 56a2f67..26a2176 100644
+index 0a613cb..d811b00 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
-@@ -4093,6 +4093,14 @@ L: linux-kernel@vger.kernel.org
+@@ -4144,6 +4144,14 @@ L: linux-kernel@vger.kernel.org
W: http://www.kernel.dk
S: Maintained
P: Oliver Neukum
M: oliver@neukum.name
diff --git a/fs/Kconfig b/fs/Kconfig
-index 2694648..ee32927 100644
+index abccb5d..3d9310a 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
-@@ -1031,6 +1031,47 @@ config CONFIGFS_FS
+@@ -981,6 +981,47 @@ config CONFIGFS_FS
endmenu
menu "Miscellaneous filesystems"
config ADFS_FS
-@@ -1083,18 +1124,6 @@ config AFFS_FS
+@@ -1033,18 +1074,6 @@ config AFFS_FS
To compile this file system support as a module, choose M here: the
module will be called affs. If unsure, say N.
tristate "Apple Macintosh file system support (EXPERIMENTAL)"
depends on BLOCK && EXPERIMENTAL
diff --git a/fs/Makefile b/fs/Makefile
-index 1e7a11b..dadf53b 100644
+index a1482a5..9bf3915 100644
--- a/fs/Makefile
+++ b/fs/Makefile
-@@ -85,6 +85,7 @@ obj-$(CONFIG_ISO9660_FS) += isofs/
+@@ -86,6 +86,7 @@ obj-$(CONFIG_ISO9660_FS) += isofs/
obj-$(CONFIG_HFSPLUS_FS) += hfsplus/ # Before hfs to find wrapped HFS+
obj-$(CONFIG_HFS_FS) += hfs/
obj-$(CONFIG_ECRYPT_FS) += ecryptfs/
out:
return rc;
diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c
-index c92cc1c..71fcaea 100644
+index 89209f0..d99a83e 100644
--- a/fs/ecryptfs/inode.c
+++ b/fs/ecryptfs/inode.c
-@@ -570,9 +570,9 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+@@ -589,9 +589,9 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry,
lower_new_dir_dentry->d_inode, lower_new_dentry);
if (rc)
goto out_lock;
out_lock:
unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
dput(lower_new_dentry->d_parent);
-@@ -907,7 +907,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia)
+@@ -913,7 +913,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia)
rc = notify_change(lower_dentry, ia);
mutex_unlock(&lower_dentry->d_inode->i_mutex);
out:
}
diff --git a/fs/ecryptfs/main.c b/fs/ecryptfs/main.c
-index d603631..59daba3 100644
+index 448dfd5..db2db5d 100644
--- a/fs/ecryptfs/main.c
+++ b/fs/ecryptfs/main.c
-@@ -211,7 +211,7 @@ int ecryptfs_interpose(struct dentry *lower_dentry, struct dentry *dentry,
+@@ -197,7 +197,7 @@ int ecryptfs_interpose(struct dentry *lower_dentry, struct dentry *dentry,
d_add(dentry, inode);
else
d_instantiate(dentry, inode);
* other metadata */
fsstack_copy_inode_size(inode, lower_inode);
diff --git a/fs/namei.c b/fs/namei.c
-index 01e67dd..b51fde0 100644
+index 4ea63ed..3c8e0d6 100644
--- a/fs/namei.c
+++ b/fs/namei.c
-@@ -404,6 +404,7 @@ void release_open_intent(struct nameidata *nd)
+@@ -392,6 +392,7 @@ void release_open_intent(struct nameidata *nd)
else
fput(nd->intent.open.file);
}
static inline struct dentry *
do_revalidate(struct dentry *dentry, struct nameidata *nd)
diff --git a/fs/splice.c b/fs/splice.c
-index aa5f6f6..4282fdf 100644
+index 1bbc6f4..7de91ce 100644
--- a/fs/splice.c
+++ b/fs/splice.c
-@@ -878,8 +878,8 @@ EXPORT_SYMBOL(generic_splice_sendpage);
+@@ -887,8 +887,8 @@ EXPORT_SYMBOL(generic_splice_sendpage);
/*
* Attempt to initiate a splice from pipe to file.
*/
{
int ret;
-@@ -895,13 +895,14 @@ static long do_splice_from(struct pipe_inode_info *pipe, struct file *out,
+@@ -904,13 +904,14 @@ static long do_splice_from(struct pipe_inode_info *pipe, struct file *out,
return out->f_op->splice_write(pipe, out, ppos, len, flags);
}
{
int ret;
-@@ -917,6 +918,7 @@ static long do_splice_to(struct file *in, loff_t *ppos,
+@@ -926,6 +927,7 @@ static long do_splice_to(struct file *in, loff_t *ppos,
return in->f_op->splice_read(in, ppos, pipe, len, flags);
}
/**
* splice_direct_to_actor - splices data directly between two non-pipes
-@@ -986,7 +988,7 @@ ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd,
+@@ -995,7 +997,7 @@ ssize_t splice_direct_to_actor(struct file *in, struct splice_desc *sd,
size_t read_len;
loff_t pos = sd->pos, prev_pos = pos;
if (unlikely(ret <= 0))
goto out_release;
-@@ -1045,7 +1047,7 @@ static int direct_splice_actor(struct pipe_inode_info *pipe,
+@@ -1054,7 +1056,7 @@ static int direct_splice_actor(struct pipe_inode_info *pipe,
{
struct file *file = sd->u.file;
}
/**
-@@ -1119,7 +1121,7 @@ static long do_splice(struct file *in, loff_t __user *off_in,
+@@ -1128,7 +1130,7 @@ static long do_splice(struct file *in, loff_t __user *off_in,
} else
off = &out->f_pos;
if (off_out && copy_to_user(off_out, off, sizeof(loff_t)))
ret = -EFAULT;
-@@ -1140,7 +1142,7 @@ static long do_splice(struct file *in, loff_t __user *off_in,
+@@ -1149,7 +1151,7 @@ static long do_splice(struct file *in, loff_t __user *off_in,
} else
off = &in->f_pos;
EXPORT_SYMBOL_GPL(fsstack_copy_attr_all);
diff --git a/fs/unionfs/Makefile b/fs/unionfs/Makefile
new file mode 100644
-index 0000000..6614204
+index 0000000..fc98b38
--- /dev/null
+++ b/fs/unionfs/Makefile
@@ -0,0 +1,17 @@
-+UNIONFS_VERSION="2.4 (for 2.6.26)"
++UNIONFS_VERSION="2.5 (for 2.6.27-rc6)"
+
+EXTRA_CFLAGS += -DUNIONFS_VERSION=\"$(UNIONFS_VERSION)\"
+
+endif
diff --git a/fs/unionfs/commonfops.c b/fs/unionfs/commonfops.c
new file mode 100644
-index 0000000..5861970
+index 0000000..ed3604e
--- /dev/null
+++ b/fs/unionfs/commonfops.c
-@@ -0,0 +1,905 @@
+@@ -0,0 +1,879 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * stolen from NFS's silly rename
+ */
+static int copyup_deleted_file(struct file *file, struct dentry *dentry,
-+ int bstart, int bindex)
++ struct dentry *parent, int bstart, int bindex)
+{
+ static unsigned int counter;
+ const int i_inosize = sizeof(dentry->d_inode->i_ino) * 2;
+ } while (tmp_dentry->d_inode != NULL); /* need negative dentry */
+ dput(tmp_dentry);
+
-+ err = copyup_named_file(dentry->d_parent->d_inode, file, name, bstart,
-+ bindex,
++ err = copyup_named_file(parent->d_inode, file, name, bstart, bindex,
+ i_size_read(file->f_path.dentry->d_inode));
+ if (err) {
+ if (unlikely(err == -EEXIST))
+ unionfs_lower_mnt_idx(dentry, bindex),
+ file->f_flags);
+ if (IS_ERR(lower_file)) {
++ branchput(sb, bindex);
+ err = PTR_ERR(lower_file);
+ goto out;
+ } else {
+ struct file *lower_file;
+ struct dentry *lower_dentry;
+ struct dentry *dentry = file->f_path.dentry;
-+ struct inode *parent_inode = dentry->d_parent->d_inode;
++ struct dentry *parent = dget_parent(dentry);
++ struct inode *parent_inode = parent->d_inode;
+ struct super_block *sb = dentry->d_sb;
+
+ bstart = dbstart(dentry);
+
+ memcpy(&lower_file->f_ra, &file->f_ra, sizeof(struct file_ra_state));
+out:
++ dput(parent);
+ return err;
+}
+
+/* perform a delayed copyup of a read-write file on a read-only branch */
-+static int do_delayed_copyup(struct file *file)
++static int do_delayed_copyup(struct file *file, struct dentry *parent)
+{
+ int bindex, bstart, bend, err = 0;
+ struct dentry *dentry = file->f_path.dentry;
-+ struct inode *parent_inode = dentry->d_parent->d_inode;
++ struct inode *parent_inode = parent->d_inode;
+
+ bstart = fbstart(file);
+ bend = fbend(file);
+ bindex,
+ i_size_read(dentry->d_inode));
+ else
-+ err = copyup_deleted_file(file, dentry, bstart,
-+ bindex);
++ err = copyup_deleted_file(file, dentry, parent,
++ bstart, bindex);
+ /* if succeeded, set lower open-file flags and break */
+ if (!err) {
+ struct file *lower_file;
+ * Expects dentry/parent to be locked already, and revalidated.
+ */
+static int __unionfs_file_revalidate(struct file *file, struct dentry *dentry,
++ struct dentry *parent,
+ struct super_block *sb, int sbgen,
+ int dgen, bool willwrite)
+{
+ is_robranch(dentry)) {
+ pr_debug("unionfs: do delay copyup of \"%s\"\n",
+ dentry->d_name.name);
-+ err = do_delayed_copyup(file);
++ err = do_delayed_copyup(file, parent);
+ /* regular files have only one open lower file */
+ if (!err && !S_ISDIR(dentry->d_inode->i_mode))
+ fbend(file) = fbstart(file);
+ if (err) {
+ kfree(UNIONFS_F(file)->lower_files);
+ kfree(UNIONFS_F(file)->saved_branch_ids);
-+ } else {
-+ unionfs_check_file(file);
+ }
+ return err;
+}
+/*
+ * Revalidate the struct file
+ * @file: file to revalidate
++ * @parent: parent dentry (locked by caller)
+ * @willwrite: true if caller may cause changes to the file; false otherwise.
+ * Caller must lock/unlock dentry's branch configuration.
+ */
-+int unionfs_file_revalidate(struct file *file, bool willwrite)
++int unionfs_file_revalidate(struct file *file, struct dentry *parent,
++ bool willwrite)
+{
+ struct super_block *sb;
+ struct dentry *dentry;
+ dentry = file->f_path.dentry;
+ sb = dentry->d_sb;
+ verify_locked(dentry);
++ verify_locked(parent);
+
+ /*
+ * First revalidate the dentry inside struct file,
+ * but not unhashed dentries.
+ */
-+reval_dentry:
+ if (!d_deleted(dentry) &&
-+ !__unionfs_d_revalidate_chain(dentry, NULL, willwrite)) {
++ !__unionfs_d_revalidate(dentry, parent, willwrite)) {
+ err = -ESTALE;
+ goto out;
+ }
+ sbgen = atomic_read(&UNIONFS_SB(sb)->generation);
+ dgen = atomic_read(&UNIONFS_D(dentry)->generation);
+
-+ if (unlikely(sbgen > dgen)) {
-+ pr_debug("unionfs: retry dentry %s revalidation\n",
++ if (unlikely(sbgen > dgen)) { /* XXX: should never happen */
++ pr_debug("unionfs: failed to revalidate dentry (%s)\n",
+ dentry->d_name.name);
-+ schedule();
-+ goto reval_dentry;
-+ }
-+ BUG_ON(sbgen > dgen);
-+
-+ err = __unionfs_file_revalidate(file, dentry, sb,
-+ sbgen, dgen, willwrite);
-+out:
-+ return err;
-+}
-+
-+/* same as unionfs_file_revalidate, but parent dentry must be locked too */
-+int unionfs_file_revalidate_locked(struct file *file, bool willwrite)
-+{
-+ struct super_block *sb;
-+ struct dentry *dentry;
-+ int sbgen, dgen;
-+ int err = 0, valid;
-+
-+ dentry = file->f_path.dentry;
-+ sb = dentry->d_sb;
-+ verify_locked(dentry);
-+ verify_locked(dentry->d_parent);
-+
-+ /* first revalidate (locked) parent, then child */
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
-+ if (unlikely(!valid)) {
-+ err = -ESTALE; /* same as what real_lookup does */
-+ goto out;
-+ }
-+
-+reval_dentry:
-+ if (!d_deleted(dentry) &&
-+ !__unionfs_d_revalidate_one_locked(dentry, NULL, willwrite)) {
+ err = -ESTALE;
+ goto out;
+ }
+
-+ sbgen = atomic_read(&UNIONFS_SB(sb)->generation);
-+ dgen = atomic_read(&UNIONFS_D(dentry)->generation);
-+
-+ if (unlikely(sbgen > dgen)) {
-+ pr_debug("unionfs: retry (locked) dentry %s revalidation\n",
-+ dentry->d_name.name);
-+ schedule();
-+ goto reval_dentry;
-+ }
-+ BUG_ON(sbgen > dgen);
-+
-+ err = __unionfs_file_revalidate(file, dentry, sb,
++ err = __unionfs_file_revalidate(file, dentry, parent, sb,
+ sbgen, dgen, willwrite);
+out:
+ return err;
+}
+
+/* unionfs_open helper function: open a file */
-+static int __open_file(struct inode *inode, struct file *file)
++static int __open_file(struct inode *inode, struct file *file,
++ struct dentry *parent)
+{
+ struct dentry *lower_dentry;
+ struct file *lower_file;
+
+ /* copyup the file */
+ for (bindex = bstart - 1; bindex >= 0; bindex--) {
-+ err = copyup_file(
-+ file->f_path.dentry->d_parent->d_inode,
-+ file, bstart, bindex, size);
++ err = copyup_file(parent->d_inode, file,
++ bstart, bindex, size);
+ if (!err)
+ break;
+ }
+ int err = 0;
+ struct file *lower_file = NULL;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ int bindex = 0, bstart = 0, bend = 0;
+ int size;
+ int valid = 0;
+
+ unionfs_read_lock(inode->i_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ if (dentry != dentry->d_parent)
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
+
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
++ /* don't open unhashed/deleted files */
++ if (d_deleted(dentry)) {
++ err = -ENOENT;
++ goto out_nofree;
++ }
++
++ /* XXX: should I change 'false' below to the 'willwrite' flag? */
++ valid = __unionfs_d_revalidate(dentry, parent, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out_nofree;
+ if (S_ISDIR(inode->i_mode))
+ err = __open_dir(inode, file); /* open a dir */
+ else
-+ err = __open_file(inode, file); /* open a file */
++ err = __open_file(inode, file, parent); /* open a file */
+
+ /* freeing the allocated resources, and fput the opened files */
+ if (err) {
+ unionfs_check_file(file);
+ unionfs_check_inode(inode);
+ }
-+ if (dentry != dentry->d_parent)
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(inode->i_sb);
+ return err;
+}
+ struct unionfs_inode_info *inodeinfo;
+ struct super_block *sb = inode->i_sb;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ int bindex, bstart, bend;
+ int fgen, err = 0;
+
+ unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
+ /*
-+ * Yes, we have to revalidate this file even if it's being released.
-+ * This is important for open-but-unlinked files, as well as mmap
-+ * support.
++ * We try to revalidate, but the VFS ignores return return values
++ * from file->release, so we must always try to succeed here,
++ * including to do the kfree and dput below. So if revalidation
++ * failed, all we can do is print some message and keep going.
+ */
-+ err = unionfs_file_revalidate(file, UNIONFS_F(file)->wrote_to_file);
-+ if (unlikely(err))
-+ goto out;
-+ unionfs_check_file(file);
++ err = unionfs_file_revalidate(file, parent,
++ UNIONFS_F(file)->wrote_to_file);
++ if (!err)
++ unionfs_check_file(file);
+ fileinfo = UNIONFS_F(file);
+ BUG_ON(file->f_path.dentry->d_inode != inode);
+ inodeinfo = UNIONFS_I(inode);
+ }
+ kfree(fileinfo);
+
-+out:
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(sb);
+ return err;
+}
+ * We use fd_set and therefore we are limited to the number of the branches
+ * to FD_SETSIZE, which is currently 1024 - plenty for most people
+ */
-+static int unionfs_ioctl_queryfile(struct file *file, unsigned int cmd,
-+ unsigned long arg)
++static int unionfs_ioctl_queryfile(struct file *file, struct dentry *parent,
++ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ fd_set branchlist;
+ dentry = file->f_path.dentry;
+ orig_bstart = dbstart(dentry);
+ orig_bend = dbend(dentry);
-+ err = unionfs_partial_lookup(dentry);
++ err = unionfs_partial_lookup(dentry, parent);
+ if (err)
+ goto out;
+ bstart = dbstart(dentry);
+{
+ long err;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ err = unionfs_file_revalidate(file, true);
++ err = unionfs_file_revalidate(file, parent, true);
+ if (unlikely(err))
+ goto out;
+
+
+ case UNIONFS_IOCTL_QUERYFILE:
+ /* Return list of branches containing the given file */
-+ err = unionfs_ioctl_queryfile(file, cmd, arg);
++ err = unionfs_ioctl_queryfile(file, parent, cmd, arg);
+ break;
+
+ default:
+out:
+ unionfs_check_file(file);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ int err = 0;
+ struct file *lower_file = NULL;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ int bindex, bstart, bend;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ err = unionfs_file_revalidate(file, UNIONFS_F(file)->wrote_to_file);
++ err = unionfs_file_revalidate(file, parent,
++ UNIONFS_F(file)->wrote_to_file);
+ if (unlikely(err))
+ goto out;
+ unionfs_check_file(file);
+ if (!err)
+ unionfs_check_file(file);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
diff --git a/fs/unionfs/copyup.c b/fs/unionfs/copyup.c
new file mode 100644
-index 0000000..bbd49c8
+index 0000000..ae6ea2b
--- /dev/null
+++ b/fs/unionfs/copyup.c
-@@ -0,0 +1,880 @@
+@@ -0,0 +1,879 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ args.symlink.parent = new_lower_parent_dentry->d_inode;
+ args.symlink.dentry = new_lower_dentry;
+ args.symlink.symbuf = symbuf;
-+ args.symlink.mode = old_mode;
+
+ run_sioq(__unionfs_symlink, &args);
+ err = args.err;
+}
diff --git a/fs/unionfs/dentry.c b/fs/unionfs/dentry.c
new file mode 100644
-index 0000000..7f0c7f7
+index 0000000..583f4a4
--- /dev/null
+++ b/fs/unionfs/dentry.c
-@@ -0,0 +1,570 @@
+@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+}
+
+/*
-+ * Revalidate a single dentry.
-+ * Assume that dentry's info node is locked.
-+ * Assume that parent(s) are all valid already, but
-+ * the child may not yet be valid.
-+ * Returns true if valid, false otherwise.
++ * Purge and invalidate as many data pages of a unionfs inode. This is
++ * called when the lower inode has changed, and we want to force processes
++ * to re-get the new data.
+ */
-+static bool __unionfs_d_revalidate_one(struct dentry *dentry,
-+ struct nameidata *nd)
++static inline void purge_inode_data(struct inode *inode)
++{
++ /* remove all non-private mappings */
++ unmap_mapping_range(inode->i_mapping, 0, 0, 0);
++ /* invalidate as many pages as possible */
++ invalidate_mapping_pages(inode->i_mapping, 0, -1);
++ /*
++ * Don't try to truncate_inode_pages here, because this could lead
++ * to a deadlock between some of address_space ops and dentry
++ * revalidation: the address space op is invoked with a lock on our
++ * own page, and truncate_inode_pages will block on locked pages.
++ */
++}
++
++/*
++ * Revalidate a single file/symlink/special dentry. Assume that info nodes
++ * of the @dentry and its @parent are locked. Assume parent is valid,
++ * otherwise return false (and let's hope the VFS will try to re-lookup this
++ * dentry). Returns true if valid, false otherwise.
++ */
++bool __unionfs_d_revalidate(struct dentry *dentry, struct dentry *parent,
++ bool willwrite)
+{
+ bool valid = true; /* default is valid */
+ struct dentry *lower_dentry;
++ struct dentry *result;
+ int bindex, bstart, bend;
-+ int sbgen, dgen;
++ int sbgen, dgen, pdgen;
+ int positive = 0;
+ int interpose_flag;
-+ struct nameidata lowernd; /* TODO: be gentler to the stack */
-+
-+ if (nd)
-+ memcpy(&lowernd, nd, sizeof(struct nameidata));
-+ else
-+ memset(&lowernd, 0, sizeof(struct nameidata));
+
+ verify_locked(dentry);
-+ verify_locked(dentry->d_parent);
++ verify_locked(parent);
+
-+ sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
+ /* if the dentry is unhashed, do NOT revalidate */
+ if (d_deleted(dentry))
+ goto out;
+
++ dgen = atomic_read(&UNIONFS_D(dentry)->generation);
++
++ if (is_newer_lower(dentry)) {
++ /* root dentry is always valid */
++ if (IS_ROOT(dentry)) {
++ unionfs_copy_attr_times(dentry->d_inode);
++ } else {
++ /*
++ * reset generation number to zero, guaranteed to be
++ * "old"
++ */
++ dgen = 0;
++ atomic_set(&UNIONFS_D(dentry)->generation, dgen);
++ }
++ if (!willwrite)
++ purge_inode_data(dentry->d_inode);
++ }
++
++ sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
++
+ BUG_ON(dbstart(dentry) == -1);
+ if (dentry->d_inode)
+ positive = 1;
-+ dgen = atomic_read(&UNIONFS_D(dentry)->generation);
-+ /*
-+ * If we are working on an unconnected dentry, then there is no
-+ * revalidation to be done, because this file does not exist within
-+ * the namespace, and Unionfs operates on the namespace, not data.
-+ */
-+ if (unlikely(sbgen != dgen)) {
-+ struct dentry *result;
-+ int pdgen;
+
-+ /* The root entry should always be valid */
-+ BUG_ON(IS_ROOT(dentry));
++ /* if our dentry is valid, then validate all lower ones */
++ if (sbgen == dgen)
++ goto validate_lowers;
+
-+ /* We can't work correctly if our parent isn't valid. */
-+ pdgen = atomic_read(&UNIONFS_D(dentry->d_parent)->generation);
-+ BUG_ON(pdgen != sbgen); /* should never happen here */
++ /* The root entry should always be valid */
++ BUG_ON(IS_ROOT(dentry));
+
-+ /* Free the pointers for our inodes and this dentry. */
-+ bstart = dbstart(dentry);
-+ bend = dbend(dentry);
++ /* We can't work correctly if our parent isn't valid. */
++ pdgen = atomic_read(&UNIONFS_D(parent)->generation);
+
-+ /*
-+ * mntput unhashed lower dentries, because those files got
-+ * deleted or rmdir'ed.
-+ */
-+ for (bindex = bstart; bindex <= bend; bindex++) {
-+ lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-+ if (!lower_dentry)
-+ continue;
-+ if (!d_deleted(lower_dentry) &&
-+ !(lower_dentry->d_flags & DCACHE_NFSFS_RENAMED))
-+ continue;
-+ unionfs_mntput(dentry, bindex);
-+ }
++ /* Free the pointers for our inodes and this dentry. */
++ path_put_lowers_all(dentry, false);
+
-+ __dput_lowers(dentry, bstart, bend);
-+ dbstart(dentry) = dbend(dentry) = -1;
++ interpose_flag = INTERPOSE_REVAL_NEG;
++ if (positive) {
++ interpose_flag = INTERPOSE_REVAL;
++ iput_lowers_all(dentry->d_inode, true);
++ }
+
-+ interpose_flag = INTERPOSE_REVAL_NEG;
-+ if (positive) {
-+ interpose_flag = INTERPOSE_REVAL;
-+ iput_lowers_all(dentry->d_inode, true);
-+ }
++ if (realloc_dentry_private_data(dentry) != 0) {
++ valid = false;
++ goto out;
++ }
+
-+ if (realloc_dentry_private_data(dentry) != 0) {
++ result = unionfs_lookup_full(dentry, parent, interpose_flag);
++ if (result) {
++ if (IS_ERR(result)) {
+ valid = false;
+ goto out;
+ }
++ /*
++ * current unionfs_lookup_backend() doesn't return
++ * a valid dentry
++ */
++ dput(dentry);
++ dentry = result;
++ }
+
-+ result = unionfs_lookup_full(dentry, &lowernd, interpose_flag);
-+ if (result) {
-+ if (IS_ERR(result)) {
-+ valid = false;
-+ goto out;
-+ }
-+ /*
-+ * current unionfs_lookup_backend() doesn't return
-+ * a valid dentry
-+ */
-+ dput(dentry);
-+ dentry = result;
-+ }
-+
-+ if (unlikely(positive && is_negative_lower(dentry))) {
-+ make_bad_inode(dentry->d_inode);
-+ d_drop(dentry);
-+ valid = false;
-+ goto out;
-+ }
++ if (unlikely(positive && is_negative_lower(dentry))) {
++ /* call make_bad_inode here ? */
++ d_drop(dentry);
++ valid = false;
+ goto out;
+ }
+
++ /*
++ * if we got here then we have revalidated our dentry and all lower
++ * ones, so we can return safely.
++ */
++ if (!valid) /* lower dentry revalidation failed */
++ goto out;
++
++ /*
++ * If the parent's gen no. matches the superblock's gen no., then
++ * we can update our denty's gen no. If they didn't match, then it
++ * was OK to revalidate this dentry with a stale parent, but we'll
++ * purposely not update our dentry's gen no. (so it can be redone);
++ * and, we'll mark our parent dentry as invalid so it'll force it
++ * (and our dentry) to be revalidated.
++ */
++ if (pdgen == sbgen)
++ atomic_set(&UNIONFS_D(dentry)->generation, sbgen);
++ goto out;
++
++validate_lowers:
++
+ /* The revalidation must occur across all branches */
+ bstart = dbstart(dentry);
+ bend = dbend(dentry);
+ * If we get here, and we copy the meta-data from the lower
+ * inode to our inode, then it is vital that we have already
+ * purged all unionfs-level file data. We do that in the
-+ * caller (__unionfs_d_revalidate_chain) by calling
++ * caller (__unionfs_d_revalidate) by calling
+ * purge_inode_data.
+ */
+ unionfs_copy_attr_all(dentry->d_inode,
+ }
+
+out:
-+ if (valid)
-+ atomic_set(&UNIONFS_D(dentry)->generation, sbgen);
-+
+ return valid;
+}
+
+ return false; /* default: lower is not newer */
+}
+
-+/*
-+ * Purge and invalidate as many data pages of a unionfs inode. This is
-+ * called when the lower inode has changed, and we want to force processes
-+ * to re-get the new data.
-+ */
-+static inline void purge_inode_data(struct inode *inode)
-+{
-+ /* remove all non-private mappings */
-+ unmap_mapping_range(inode->i_mapping, 0, 0, 0);
-+ /* invalidate as many pages as possible */
-+ invalidate_mapping_pages(inode->i_mapping, 0, -1);
-+ /*
-+ * Don't try to truncate_inode_pages here, because this could lead
-+ * to a deadlock between some of address_space ops and dentry
-+ * revalidation: the address space op is invoked with a lock on our
-+ * own page, and truncate_inode_pages will block on locked pages.
-+ */
-+}
-+
-+/*
-+ * Revalidate a single file/symlink/special dentry. Assume that info nodes
-+ * of the dentry and its parent are locked. Assume that parent(s) are all
-+ * valid already, but the child may not yet be valid. Returns true if
-+ * valid, false otherwise.
-+ */
-+bool __unionfs_d_revalidate_one_locked(struct dentry *dentry,
-+ struct nameidata *nd,
-+ bool willwrite)
-+{
-+ bool valid = false; /* default is invalid */
-+ int sbgen, dgen, bindex;
-+
-+ verify_locked(dentry);
-+ verify_locked(dentry->d_parent);
-+
-+ sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
-+ dgen = atomic_read(&UNIONFS_D(dentry)->generation);
-+
-+ if (unlikely(is_newer_lower(dentry))) {
-+ /* root dentry special case as aforementioned */
-+ if (IS_ROOT(dentry)) {
-+ unionfs_copy_attr_times(dentry->d_inode);
-+ } else {
-+ /*
-+ * reset generation number to zero, guaranteed to be
-+ * "old"
-+ */
-+ dgen = 0;
-+ atomic_set(&UNIONFS_D(dentry)->generation, dgen);
-+ }
-+ if (!willwrite)
-+ purge_inode_data(dentry->d_inode);
-+ }
-+ valid = __unionfs_d_revalidate_one(dentry, nd);
-+
-+ /*
-+ * If __unionfs_d_revalidate_one() succeeded above, then it will
-+ * have incremented the refcnt of the mnt's, but also the branch
-+ * indices of the dentry will have been updated (to take into
-+ * account any branch insertions/deletion. So the current
-+ * dbstart/dbend match the current, and new, indices of the mnts
-+ * which __unionfs_d_revalidate_one has incremented. Note: the "if"
-+ * test below does not depend on whether chain_len was 0 or greater.
-+ */
-+ if (!valid || sbgen == dgen)
-+ goto out;
-+ for (bindex = dbstart(dentry); bindex <= dbend(dentry); bindex++)
-+ unionfs_mntput(dentry, bindex);
-+out:
-+ return valid;
-+}
-+
-+/*
-+ * Revalidate a parent chain of dentries, then the actual node.
-+ * Assumes that dentry is locked, but will lock all parents if/when needed.
-+ *
-+ * If 'willwrite' is true, and the lower inode times are not in sync, then
-+ * *don't* purge_inode_data, as it could deadlock if ->write calls us and we
-+ * try to truncate a locked page. Besides, if unionfs is about to write
-+ * data to a file, then there's the data unionfs is about to write is more
-+ * authoritative than what's below, therefore we can safely overwrite the
-+ * lower inode times and data.
-+ */
-+bool __unionfs_d_revalidate_chain(struct dentry *dentry, struct nameidata *nd,
-+ bool willwrite)
-+{
-+ bool valid = false; /* default is invalid */
-+ struct dentry **chain = NULL; /* chain of dentries to reval */
-+ int chain_len = 0;
-+ struct dentry *dtmp;
-+ int sbgen, dgen, i;
-+ int saved_bstart, saved_bend, bindex;
-+
-+ /* find length of chain needed to revalidate */
-+ /* XXX: should I grab some global (dcache?) lock? */
-+ chain_len = 0;
-+ sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
-+ dtmp = dentry->d_parent;
-+ verify_locked(dentry);
-+ if (dentry != dtmp)
-+ unionfs_lock_dentry(dtmp, UNIONFS_DMUTEX_REVAL_PARENT);
-+ dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
-+ /* XXX: should we check if is_newer_lower all the way up? */
-+ if (unlikely(is_newer_lower(dtmp))) {
-+ /*
-+ * Special case: the root dentry's generation number must
-+ * always be valid, but its lower inode times don't have to
-+ * be, so sync up the times only.
-+ */
-+ if (IS_ROOT(dtmp)) {
-+ unionfs_copy_attr_times(dtmp->d_inode);
-+ } else {
-+ /*
-+ * reset generation number to zero, guaranteed to be
-+ * "old"
-+ */
-+ dgen = 0;
-+ atomic_set(&UNIONFS_D(dtmp)->generation, dgen);
-+ }
-+ purge_inode_data(dtmp->d_inode);
-+ }
-+ if (dentry != dtmp)
-+ unionfs_unlock_dentry(dtmp);
-+ while (sbgen != dgen) {
-+ /* The root entry should always be valid */
-+ BUG_ON(IS_ROOT(dtmp));
-+ chain_len++;
-+ dtmp = dtmp->d_parent;
-+ dgen = atomic_read(&UNIONFS_D(dtmp)->generation);
-+ }
-+ if (chain_len == 0)
-+ goto out_this; /* shortcut if parents are OK */
-+
-+ /*
-+ * Allocate array of dentries to reval. We could use linked lists,
-+ * but the number of entries we need to alloc here is often small,
-+ * and short lived, so locality will be better.
-+ */
-+ chain = kzalloc(chain_len * sizeof(struct dentry *), GFP_KERNEL);
-+ if (unlikely(!chain)) {
-+ printk(KERN_CRIT "unionfs: no more memory in %s\n",
-+ __func__);
-+ goto out;
-+ }
-+
-+ /* grab all dentries in chain, in child to parent order */
-+ dtmp = dentry;
-+ for (i = chain_len-1; i >= 0; i--)
-+ dtmp = chain[i] = dget_parent(dtmp);
-+
-+ /*
-+ * call __unionfs_d_revalidate_one() on each dentry, but in parent
-+ * to child order.
-+ */
-+ for (i = 0; i < chain_len; i++) {
-+ unionfs_lock_dentry(chain[i], UNIONFS_DMUTEX_REVAL_CHILD);
-+ if (chain[i] != chain[i]->d_parent)
-+ unionfs_lock_dentry(chain[i]->d_parent,
-+ UNIONFS_DMUTEX_REVAL_PARENT);
-+ saved_bstart = dbstart(chain[i]);
-+ saved_bend = dbend(chain[i]);
-+ sbgen = atomic_read(&UNIONFS_SB(dentry->d_sb)->generation);
-+ dgen = atomic_read(&UNIONFS_D(chain[i])->generation);
-+
-+ valid = __unionfs_d_revalidate_one(chain[i], nd);
-+ /* XXX: is this the correct mntput condition?! */
-+ if (valid && chain_len > 0 &&
-+ sbgen != dgen && chain[i]->d_inode &&
-+ S_ISDIR(chain[i]->d_inode->i_mode)) {
-+ for (bindex = saved_bstart; bindex <= saved_bend;
-+ bindex++)
-+ unionfs_mntput(chain[i], bindex);
-+ }
-+ if (chain[i] != chain[i]->d_parent)
-+ unionfs_unlock_dentry(chain[i]->d_parent);
-+ unionfs_unlock_dentry(chain[i]);
-+
-+ if (unlikely(!valid))
-+ goto out_free;
-+ }
-+
-+
-+out_this:
-+ /* finally, lock this dentry and revalidate it */
-+ verify_locked(dentry); /* verify child is locked */
-+ if (dentry != dentry->d_parent)
-+ unionfs_lock_dentry(dentry->d_parent,
-+ UNIONFS_DMUTEX_REVAL_PARENT);
-+ valid = __unionfs_d_revalidate_one_locked(dentry, nd, willwrite);
-+ if (dentry != dentry->d_parent)
-+ unionfs_unlock_dentry(dentry->d_parent);
-+
-+out_free:
-+ /* unlock/dput all dentries in chain and return status */
-+ if (chain_len > 0) {
-+ for (i = 0; i < chain_len; i++)
-+ dput(chain[i]);
-+ kfree(chain);
-+ }
-+out:
-+ return valid;
-+}
-+
-+static int unionfs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
++static int unionfs_d_revalidate(struct dentry *dentry,
++ struct nameidata *nd_unused)
+{
-+ int err;
++ bool valid = true;
++ int err = 1; /* 1 means valid for the VFS */
++ struct dentry *parent;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
-+
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ err = __unionfs_d_revalidate_chain(dentry, nd, false);
-+ if (likely(err > 0)) { /* true==1: dentry is valid */
++
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (valid) {
+ unionfs_postcopyup_setmnt(dentry);
+ unionfs_check_dentry(dentry);
-+ unionfs_check_nd(nd);
++ } else {
++ d_drop(dentry);
++ err = valid;
+ }
+ unionfs_unlock_dentry(dentry);
-+
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+
+ return err;
+};
diff --git a/fs/unionfs/dirfops.c b/fs/unionfs/dirfops.c
new file mode 100644
-index 0000000..14ca7d3
+index 0000000..63fb419
--- /dev/null
+++ b/fs/unionfs/dirfops.c
-@@ -0,0 +1,292 @@
+@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ int err = 0;
+ struct file *lower_file = NULL;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ struct inode *inode = NULL;
+ struct unionfs_getdents_callback buf;
+ struct unionfs_dir_state *uds;
+ loff_t offset;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ err = unionfs_file_revalidate(file, false);
++ err = unionfs_file_revalidate(file, parent, false);
+ if (unlikely(err))
+ goto out;
+
+ }
+
+out:
++ if (!err)
++ unionfs_check_file(file);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+{
+ struct unionfs_dir_state *rdstate;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ loff_t err;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ err = unionfs_file_revalidate(file, false);
++ err = unionfs_file_revalidate(file, parent, false);
+ if (unlikely(err))
+ goto out;
+
+ }
+
+out:
++ if (!err)
++ unionfs_check_file(file);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+};
diff --git a/fs/unionfs/dirhelper.c b/fs/unionfs/dirhelper.c
new file mode 100644
-index 0000000..d936f03
+index 0000000..aa31e91
--- /dev/null
+++ b/fs/unionfs/dirhelper.c
-@@ -0,0 +1,157 @@
+@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+}
+
+/* Is a directory logically empty? */
-+int check_empty(struct dentry *dentry, struct unionfs_dir_state **namelist)
++int check_empty(struct dentry *dentry, struct dentry *parent,
++ struct unionfs_dir_state **namelist)
+{
+ int err = 0;
+ struct dentry *lower_dentry = NULL;
+
+ BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
+
-+ err = unionfs_partial_lookup(dentry);
++ err = unionfs_partial_lookup(dentry, parent);
+ if (err)
+ goto out;
+
+}
diff --git a/fs/unionfs/fanout.h b/fs/unionfs/fanout.h
new file mode 100644
-index 0000000..4f264de
+index 0000000..69c0921
--- /dev/null
+++ b/fs/unionfs/fanout.h
-@@ -0,0 +1,384 @@
+@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ mutex_unlock(&UNIONFS_D(d)->lock);
+}
+
++static inline struct dentry *unionfs_lock_parent(struct dentry *d,
++ unsigned int subclass)
++{
++ struct dentry *p;
++
++ BUG_ON(!d);
++ p = dget_parent(d);
++ if (p != d)
++ mutex_lock_nested(&UNIONFS_D(p)->lock, subclass);
++ return p;
++}
++
++static inline void unionfs_unlock_parent(struct dentry *d, struct dentry *p)
++{
++ BUG_ON(!d);
++ BUG_ON(!p);
++ if (p != d) {
++ BUG_ON(!mutex_is_locked(&UNIONFS_D(p)->lock));
++ mutex_unlock(&UNIONFS_D(p)->lock);
++ }
++ dput(p);
++}
++
+static inline void verify_locked(struct dentry *d)
+{
+ BUG_ON(!d);
+#endif /* not _FANOUT_H */
diff --git a/fs/unionfs/file.c b/fs/unionfs/file.c
new file mode 100644
-index 0000000..965d071
+index 0000000..3cc6a76
--- /dev/null
+++ b/fs/unionfs/file.c
-@@ -0,0 +1,341 @@
+@@ -0,0 +1,364 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ int err;
+ struct file *lower_file;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ err = unionfs_file_revalidate(file, false);
++
++ err = unionfs_file_revalidate(file, parent, false);
+ if (unlikely(err))
+ goto out;
+
+
+out:
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ int err = 0;
+ struct file *lower_file;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ if (dentry != dentry->d_parent)
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
-+ err = unionfs_file_revalidate_locked(file, true);
++
++ err = unionfs_file_revalidate(file, parent, true);
+ if (unlikely(err))
+ goto out;
+
+ }
+
+out:
-+ if (dentry != dentry->d_parent)
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ bool willwrite;
+ struct file *lower_file;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ struct vm_operations_struct *saved_vm_ops = NULL;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
+ /* This might be deferred to mmap's writepage */
+ willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags);
-+ err = unionfs_file_revalidate(file, willwrite);
++ err = unionfs_file_revalidate(file, parent, willwrite);
+ if (unlikely(err))
+ goto out;
+ unionfs_check_file(file);
+out:
+ if (!err) {
+ /* copyup could cause parent dir times to change */
-+ unionfs_copy_attr_times(dentry->d_parent->d_inode);
++ unionfs_copy_attr_times(parent->d_inode);
+ unionfs_check_file(file);
+ }
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ int bindex, bstart, bend;
+ struct file *lower_file;
+ struct dentry *lower_dentry;
++ struct dentry *parent;
+ struct inode *lower_inode, *inode;
+ int err = -EINVAL;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ err = unionfs_file_revalidate(file, true);
++
++ err = unionfs_file_revalidate(file, parent, true);
+ if (unlikely(err))
+ goto out;
+ unionfs_check_file(file);
+ if (!err)
+ unionfs_check_file(file);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ int bindex, bstart, bend;
+ struct file *lower_file;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+ struct inode *lower_inode, *inode;
+ int err = 0;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ err = unionfs_file_revalidate(file, true);
++
++ err = unionfs_file_revalidate(file, parent, true);
+ if (unlikely(err))
+ goto out;
+ unionfs_check_file(file);
+ if (!err)
+ unionfs_check_file(file);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ ssize_t err;
+ struct file *lower_file;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ err = unionfs_file_revalidate(file, false);
++
++ err = unionfs_file_revalidate(file, parent, false);
+ if (unlikely(err))
+ goto out;
+
+
+out:
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ ssize_t err = 0;
+ struct file *lower_file;
+ struct dentry *dentry = file->f_path.dentry;
++ struct dentry *parent;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ err = unionfs_file_revalidate(file, true);
++
++ err = unionfs_file_revalidate(file, parent, true);
+ if (unlikely(err))
+ goto out;
+
+
+out:
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+};
diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c
new file mode 100644
-index 0000000..a05b412
+index 0000000..800648e
--- /dev/null
+++ b/fs/unionfs/inode.c
-@@ -0,0 +1,992 @@
+@@ -0,0 +1,1035 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ return lower_dentry;
+}
+
-+static int unionfs_create(struct inode *parent, struct dentry *dentry,
-+ int mode, struct nameidata *nd)
++static int unionfs_create(struct inode *dir, struct dentry *dentry,
++ int mode, struct nameidata *nd_unused)
+{
+ int err = 0;
+ struct dentry *lower_dentry = NULL;
+ struct dentry *lower_parent_dentry = NULL;
++ struct dentry *parent;
+ int valid = 0;
+ struct nameidata lower_nd;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
+
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, nd, false);
++ valid = __unionfs_d_revalidate(dentry, parent, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE; /* same as what real_lookup does */
+ goto out;
+ }
+
-+ valid = __unionfs_d_revalidate_one_locked(dentry, nd, false);
-+ /*
-+ * It's only a bug if this dentry was not negative and couldn't be
-+ * revalidated (shouldn't happen).
-+ */
-+ BUG_ON(!valid && dentry->d_inode);
-+
-+ lower_dentry = find_writeable_branch(parent, dentry);
++ lower_dentry = find_writeable_branch(dir, dentry);
+ if (IS_ERR(lower_dentry)) {
+ err = PTR_ERR(lower_dentry);
+ goto out;
+ release_lower_nd(&lower_nd, err);
+
+ if (!err) {
-+ err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
++ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
+ if (!err) {
-+ unionfs_copy_attr_times(parent);
-+ fsstack_copy_inode_size(parent,
++ unionfs_copy_attr_times(dir);
++ fsstack_copy_inode_size(dir,
+ lower_parent_dentry->d_inode);
+ /* update no. of links on parent directory */
-+ parent->i_nlink = unionfs_get_nlinks(parent);
++ dir->i_nlink = unionfs_get_nlinks(dir);
+ }
+ }
+
+out:
+ if (!err) {
+ unionfs_postcopyup_setmnt(dentry);
-+ unionfs_check_inode(parent);
++ unionfs_check_inode(dir);
+ unionfs_check_dentry(dentry);
-+ unionfs_check_nd(nd);
+ }
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ * do NOT want to call __unionfs_d_revalidate_chain because by definition,
+ * we don't have a valid dentry here yet.
+ */
-+static struct dentry *unionfs_lookup(struct inode *parent,
++static struct dentry *unionfs_lookup(struct inode *dir,
+ struct dentry *dentry,
-+ struct nameidata *nd)
++ struct nameidata *nd_unused)
+{
-+ struct path path_save = {NULL, NULL};
-+ struct dentry *ret;
++ struct dentry *ret, *parent;
+ int err = 0;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
-+ if (dentry != dentry->d_parent)
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_ROOT);
-+
-+ /* save the dentry & vfsmnt from namei */
-+ if (nd) {
-+ path_save.dentry = nd->path.dentry;
-+ path_save.mnt = nd->path.mnt;
-+ }
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+
+ /*
-+ * unionfs_lookup_backend returns a locked dentry upon success,
-+ * so we'll have to unlock it below.
++ * As long as we lock/dget the parent, then can skip validating the
++ * parent now; we may have to rebuild this dentry on the next
++ * ->d_revalidate, however.
+ */
+
+ /* allocate dentry private data. We free it in ->d_release */
+ ret = ERR_PTR(err);
+ goto out;
+ }
-+ ret = unionfs_lookup_full(dentry, nd, INTERPOSE_LOOKUP);
+
-+ /* restore the dentry & vfsmnt in namei */
-+ if (nd) {
-+ nd->path.dentry = path_save.dentry;
-+ nd->path.mnt = path_save.mnt;
-+ }
++ ret = unionfs_lookup_full(dentry, parent, INTERPOSE_LOOKUP);
++
+ if (!IS_ERR(ret)) {
+ if (ret)
+ dentry = ret;
+ unionfs_postcopyup_release(dentry);
+ }
+ unionfs_copy_attr_times(dentry->d_inode);
-+ /* parent times may have changed */
-+ unionfs_copy_attr_times(dentry->d_parent->d_inode);
+ }
+
-+ unionfs_check_inode(parent);
-+ if (!IS_ERR(ret)) {
++ unionfs_check_inode(dir);
++ if (!IS_ERR(ret))
+ unionfs_check_dentry(dentry);
-+ unionfs_check_nd(nd);
-+ }
-+ unionfs_unlock_dentry(dentry);
++ unionfs_check_dentry(parent);
++ unionfs_unlock_dentry(dentry); /* locked in new_dentry_private data */
+
+out:
-+ if (dentry != dentry->d_parent) {
-+ unionfs_check_dentry(dentry->d_parent);
-+ unionfs_unlock_dentry(dentry->d_parent);
-+ }
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+
+ return ret;
+ struct dentry *lower_old_dentry = NULL;
+ struct dentry *lower_new_dentry = NULL;
+ struct dentry *lower_dir_dentry = NULL;
++ struct dentry *old_parent, *new_parent;
+ char *name = NULL;
++ bool valid;
+
+ unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
-+ unionfs_double_lock_dentry(new_dentry, old_dentry);
++ old_parent = dget_parent(old_dentry);
++ new_parent = dget_parent(new_dentry);
++ unionfs_double_lock_parents(old_parent, new_parent);
++ unionfs_double_lock_dentry(old_dentry, new_dentry);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(old_dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(old_dentry, old_parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
-+ if (unlikely(new_dentry->d_inode &&
-+ !__unionfs_d_revalidate_chain(new_dentry, NULL, false))) {
-+ err = -ESTALE;
-+ goto out;
++ if (new_dentry->d_inode) {
++ valid = __unionfs_d_revalidate(new_dentry, new_parent, false);
++ if (unlikely(!valid)) {
++ err = -ESTALE;
++ goto out;
++ }
+ }
+
+ lower_new_dentry = unionfs_lower_dentry(new_dentry);
+ int bindex;
+
+ for (bindex = old_bstart - 1; bindex >= 0; bindex--) {
-+ err = copyup_dentry(old_dentry->d_parent->d_inode,
++ err = copyup_dentry(old_parent->d_inode,
+ old_dentry, old_bstart,
+ bindex, old_dentry->d_name.name,
+ old_dentry->d_name.len, NULL,
+ unionfs_check_dentry(new_dentry);
+ unionfs_check_dentry(old_dentry);
+
-+ unionfs_unlock_dentry(new_dentry);
-+ unionfs_unlock_dentry(old_dentry);
++ unionfs_double_unlock_dentry(old_dentry, new_dentry);
++ unionfs_double_unlock_parents(old_parent, new_parent);
++ dput(new_parent);
++ dput(old_parent);
+ unionfs_read_unlock(old_dentry->d_sb);
+
+ return err;
+}
+
-+static int unionfs_symlink(struct inode *parent, struct dentry *dentry,
++static int unionfs_symlink(struct inode *dir, struct dentry *dentry,
+ const char *symname)
+{
+ int err = 0;
+ struct dentry *lower_dentry = NULL;
+ struct dentry *wh_dentry = NULL;
+ struct dentry *lower_parent_dentry = NULL;
++ struct dentry *parent;
+ char *name = NULL;
+ int valid = 0;
+ umode_t mode;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
+
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
++ valid = __unionfs_d_revalidate(dentry, parent, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
-+ if (unlikely(dentry->d_inode &&
-+ !__unionfs_d_revalidate_one_locked(dentry, NULL, false))) {
-+ err = -ESTALE;
-+ goto out;
-+ }
+
+ /*
+ * It's only a bug if this dentry was not negative and couldn't be
+ */
+ BUG_ON(!valid && dentry->d_inode);
+
-+ lower_dentry = find_writeable_branch(parent, dentry);
++ lower_dentry = find_writeable_branch(dir, dentry);
+ if (IS_ERR(lower_dentry)) {
+ err = PTR_ERR(lower_dentry);
+ goto out;
+ }
+
+ mode = S_IALLUGO;
-+ err = vfs_symlink(lower_parent_dentry->d_inode, lower_dentry,
-+ symname, mode);
++ err = vfs_symlink(lower_parent_dentry->d_inode, lower_dentry, symname);
+ if (!err) {
-+ err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
++ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
+ if (!err) {
-+ unionfs_copy_attr_times(parent);
-+ fsstack_copy_inode_size(parent,
++ unionfs_copy_attr_times(dir);
++ fsstack_copy_inode_size(dir,
+ lower_parent_dentry->d_inode);
+ /* update no. of links on parent directory */
-+ parent->i_nlink = unionfs_get_nlinks(parent);
++ dir->i_nlink = unionfs_get_nlinks(dir);
+ }
+ }
+
+
+ if (!err) {
+ unionfs_postcopyup_setmnt(dentry);
-+ unionfs_check_inode(parent);
++ unionfs_check_inode(dir);
+ unionfs_check_dentry(dentry);
+ }
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+
-+static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
++static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+ int err = 0;
+ struct dentry *lower_dentry = NULL;
+ struct dentry *lower_parent_dentry = NULL;
++ struct dentry *parent;
+ int bindex = 0, bstart;
+ char *name = NULL;
+ int valid;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
+
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
++ valid = __unionfs_d_revalidate(dentry, parent, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE; /* same as what real_lookup does */
+ goto out;
+ }
-+ if (unlikely(dentry->d_inode &&
-+ !__unionfs_d_revalidate_one_locked(dentry, NULL, false))) {
-+ err = -ESTALE;
-+ goto out;
-+ }
+
+ bstart = dbstart(dentry);
+
+
+ lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+ if (!lower_dentry) {
-+ lower_dentry = create_parents(parent, dentry,
++ lower_dentry = create_parents(dir, dentry,
+ dentry->d_name.name,
+ bindex);
+ if (!lower_dentry || IS_ERR(lower_dentry)) {
+ if (err)
+ break;
+
-+ for (i = bindex + 1; i < bend; i++) {
++ for (i = bindex + 1; i <= bend; i++) {
++ /* XXX: use path_put_lowers? */
+ if (unionfs_lower_dentry_idx(dentry, i)) {
+ dput(unionfs_lower_dentry_idx(dentry, i));
+ unionfs_set_lower_dentry_idx(dentry, i, NULL);
+ * Only INTERPOSE_LOOKUP can return a value other than 0 on
+ * err.
+ */
-+ err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
++ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
+ if (!err) {
-+ unionfs_copy_attr_times(parent);
-+ fsstack_copy_inode_size(parent,
++ unionfs_copy_attr_times(dir);
++ fsstack_copy_inode_size(dir,
+ lower_parent_dentry->d_inode);
+
+ /* update number of links on parent directory */
-+ parent->i_nlink = unionfs_get_nlinks(parent);
++ dir->i_nlink = unionfs_get_nlinks(dir);
+ }
+
+ err = make_dir_opaque(dentry, dbstart(dentry));
+ unionfs_copy_attr_times(dentry->d_inode);
+ unionfs_postcopyup_setmnt(dentry);
+ }
-+ unionfs_check_inode(parent);
++ unionfs_check_inode(dir);
+ unionfs_check_dentry(dentry);
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+
+ return err;
+}
+
-+static int unionfs_mknod(struct inode *parent, struct dentry *dentry, int mode,
++static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode,
+ dev_t dev)
+{
+ int err = 0;
+ struct dentry *lower_dentry = NULL;
+ struct dentry *wh_dentry = NULL;
+ struct dentry *lower_parent_dentry = NULL;
++ struct dentry *parent;
+ char *name = NULL;
+ int valid = 0;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
+
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
++ valid = __unionfs_d_revalidate(dentry, parent, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
-+ if (unlikely(dentry->d_inode &&
-+ !__unionfs_d_revalidate_one_locked(dentry, NULL, false))) {
-+ err = -ESTALE;
-+ goto out;
-+ }
+
+ /*
+ * It's only a bug if this dentry was not negative and couldn't be
+ */
+ BUG_ON(!valid && dentry->d_inode);
+
-+ lower_dentry = find_writeable_branch(parent, dentry);
++ lower_dentry = find_writeable_branch(dir, dentry);
+ if (IS_ERR(lower_dentry)) {
+ err = PTR_ERR(lower_dentry);
+ goto out;
+
+ err = vfs_mknod(lower_parent_dentry->d_inode, lower_dentry, mode, dev);
+ if (!err) {
-+ err = PTR_ERR(unionfs_interpose(dentry, parent->i_sb, 0));
++ err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
+ if (!err) {
-+ unionfs_copy_attr_times(parent);
-+ fsstack_copy_inode_size(parent,
++ unionfs_copy_attr_times(dir);
++ fsstack_copy_inode_size(dir,
+ lower_parent_dentry->d_inode);
+ /* update no. of links on parent directory */
-+ parent->i_nlink = unionfs_get_nlinks(parent);
++ dir->i_nlink = unionfs_get_nlinks(dir);
+ }
+ }
+
+
+ if (!err) {
+ unionfs_postcopyup_setmnt(dentry);
-+ unionfs_check_inode(parent);
++ unionfs_check_inode(dir);
+ unionfs_check_dentry(dentry);
+ }
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+
-+static int unionfs_readlink(struct dentry *dentry, char __user *buf,
-+ int bufsiz)
++/* requires sb, dentry, and parent to already be locked */
++static int __unionfs_readlink(struct dentry *dentry, char __user *buf,
++ int bufsiz)
+{
+ int err;
+ struct dentry *lower_dentry;
+
-+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
-+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
-+ err = -ESTALE;
-+ goto out;
-+ }
-+
+ lower_dentry = unionfs_lower_dentry(dentry);
+
+ if (!lower_dentry->d_inode->i_op ||
+
+ err = lower_dentry->d_inode->i_op->readlink(lower_dentry,
+ buf, bufsiz);
-+ if (err > 0)
++ if (err >= 0)
+ fsstack_copy_attr_atime(dentry->d_inode,
+ lower_dentry->d_inode);
+
+out:
++ return err;
++}
++
++static int unionfs_readlink(struct dentry *dentry, char __user *buf,
++ int bufsiz)
++{
++ int err;
++ struct dentry *parent;
++
++ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
++ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
++
++ if (unlikely(!__unionfs_d_revalidate(dentry, parent, false))) {
++ err = -ESTALE;
++ goto out;
++ }
++
++ err = __unionfs_readlink(dentry, buf, bufsiz);
++
++out:
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+
+ return err;
+}
+
-+/*
-+ * unionfs_follow_link takes a dentry, but it is simple. It only needs to
-+ * allocate some memory and then call our ->readlink method. Our
-+ * unionfs_readlink *does* lock our dentry and revalidate the dentry.
-+ * Therefore, we do not have to lock our dentry here, to prevent a deadlock;
-+ * nor do we need to revalidate it either. It is safe to not lock our
-+ * dentry here, nor revalidate it, because unionfs_follow_link does not do
-+ * anything (prior to calling ->readlink) which could become inconsistent
-+ * due to branch management. We also don't need to lock our super because
-+ * this function isn't affected by branch-management.
-+ */
+static void *unionfs_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+ char *buf;
+ int len = PAGE_SIZE, err;
+ mm_segment_t old_fs;
++ struct dentry *parent;
++
++ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
++ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
+ /* This is freed by the put_link method assuming a successful call. */
+ buf = kmalloc(len, GFP_KERNEL);
+ /* read the symlink, and then we will follow it */
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
-+ err = dentry->d_inode->i_op->readlink(dentry, (char __user *)buf, len);
++ err = __unionfs_readlink(dentry, buf, len);
+ set_fs(old_fs);
+ if (err < 0) {
+ kfree(buf);
+ err = 0;
+
+out:
-+ if (!err) {
-+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
++ if (err >= 0) {
++ unionfs_check_nd(nd);
+ unionfs_check_dentry(dentry);
-+ unionfs_unlock_dentry(dentry);
+ }
-+ unionfs_check_nd(nd);
++
++ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
++ unionfs_read_unlock(dentry->d_sb);
++
+ return ERR_PTR(err);
+}
+
-+/* FIXME: We may not have to lock here */
++/* this @nd *IS* still used */
+static void unionfs_put_link(struct dentry *dentry, struct nameidata *nd,
+ void *cookie)
+{
-+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ struct dentry *parent;
+
++ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, nd, false)))
++
++ if (unlikely(!__unionfs_d_revalidate(dentry, parent, false)))
+ printk(KERN_ERR
+ "unionfs: put_link failed to revalidate dentry\n");
+
+ unionfs_check_nd(nd);
+ kfree(nd_get_link(nd));
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+}
+
+/*
++ * This is a variant of fs/namei.c:permission() or inode_permission() which
++ * skips over EROFS tests (because we perform copyup on EROFS).
++ */
++static int __inode_permission(struct inode *inode, int mask)
++{
++ int retval;
++
++ /* nobody gets write access to an immutable file */
++ if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode))
++ return -EACCES;
++
++ /* Ordinary permission routines do not understand MAY_APPEND. */
++ if (inode->i_op && inode->i_op->permission) {
++ retval = inode->i_op->permission(inode, mask);
++ if (!retval) {
++ /*
++ * Exec permission on a regular file is denied if none
++ * of the execute bits are set.
++ *
++ * This check should be done by the ->permission()
++ * method.
++ */
++ if ((mask & MAY_EXEC) && S_ISREG(inode->i_mode) &&
++ !(inode->i_mode & S_IXUGO))
++ return -EACCES;
++ }
++ } else {
++ retval = generic_permission(inode, mask, NULL);
++ }
++ if (retval)
++ return retval;
++
++ return security_inode_permission(inode,
++ mask & (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND));
++}
++
++/*
+ * Don't grab the superblock read-lock in unionfs_permission, which prevents
+ * a deadlock with the branch-management "add branch" code (which grabbed
+ * the write lock). It is safe to not grab the read lock here, because even
+ * unionfs_permission, or anything it calls, will use stale branch
+ * information.
+ */
-+static int unionfs_permission(struct inode *inode, int mask,
-+ struct nameidata *nd)
++static int unionfs_permission(struct inode *inode, int mask)
+{
+ struct inode *lower_inode = NULL;
+ int err = 0;
+ int bindex, bstart, bend;
+ const int is_file = !S_ISDIR(inode->i_mode);
+ const int write_mask = (mask & MAY_WRITE) && !(mask & MAY_READ);
++ struct inode *inode_grabbed = igrab(inode);
++ struct dentry *dentry = d_find_alias(inode);
+
-+ if (nd)
-+ unionfs_lock_dentry(nd->path.dentry, UNIONFS_DMUTEX_CHILD);
++ if (dentry)
++ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
+ if (!UNIONFS_I(inode)->lower_inodes) {
+ if (is_file) /* dirs can be unlinked but chdir'ed to */
+ * We check basic permissions, but we ignore any conditions
+ * such as readonly file systems or branches marked as
+ * readonly, because those conditions should lead to a
-+ * copyup taking place later on.
++ * copyup taking place later on. However, if user never had
++ * access to the file, then no copyup could ever take place.
+ */
-+ err = permission(lower_inode, mask, nd);
-+ if (err && bindex > 0) {
++ err = __inode_permission(lower_inode, mask);
++ if (err && err != -EACCES && err != EPERM && bindex > 0) {
+ umode_t mode = lower_inode->i_mode;
-+ if (is_robranch_super(inode->i_sb, bindex) &&
++ if ((is_robranch_super(inode->i_sb, bindex) ||
++ IS_RDONLY(lower_inode)) &&
+ (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode)))
+ err = 0;
+ if (IS_COPYUP_ERR(err))
+
+out:
+ unionfs_check_inode(inode);
-+ unionfs_check_nd(nd);
-+ if (nd)
-+ unionfs_unlock_dentry(nd->path.dentry);
++ if (dentry) {
++ unionfs_unlock_dentry(dentry);
++ dput(dentry);
++ }
++ iput(inode_grabbed);
+ return err;
+}
+
+{
+ int err = 0;
+ struct dentry *lower_dentry;
++ struct dentry *parent;
+ struct inode *inode;
+ struct inode *lower_inode;
+ int bstart, bend, bindex;
+ loff_t size;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ if (unlikely(!__unionfs_d_revalidate(dentry, parent, false))) {
+ err = -ESTALE;
+ goto out;
+ }
+ ia->ia_valid &= ~ATTR_MODE;
+
+ lower_dentry = unionfs_lower_dentry(dentry);
-+ BUG_ON(!lower_dentry); /* should never happen after above revalidate */
++ if (!lower_dentry) { /* should never happen after above revalidate */
++ err = -EINVAL;
++ goto out;
++ }
++ lower_inode = unionfs_lower_inode(inode);
++
++ /* check if user has permission to change lower inode */
++ err = inode_change_ok(lower_inode, ia);
++ if (err)
++ goto out;
+
+ /* copyup if the file is on a read only branch */
+ if (is_robranch_super(dentry->d_sb, bstart)
-+ || IS_RDONLY(lower_dentry->d_inode)) {
++ || IS_RDONLY(lower_inode)) {
+ /* check if we have a branch to copy up to */
+ if (bstart <= 0) {
+ err = -EACCES;
+ size = i_size_read(inode);
+ /* copyup to next available branch */
+ for (bindex = bstart - 1; bindex >= 0; bindex--) {
-+ err = copyup_dentry(dentry->d_parent->d_inode,
++ err = copyup_dentry(parent->d_inode,
+ dentry, bstart, bindex,
+ dentry->d_name.name,
+ dentry->d_name.len,
+ }
+ if (err)
+ goto out;
-+ /* get updated lower_dentry after copyup */
++ /* get updated lower_dentry/inode after copyup */
+ lower_dentry = unionfs_lower_dentry(dentry);
++ lower_inode = unionfs_lower_inode(inode);
+ }
+
-+ lower_inode = unionfs_lower_inode(inode);
-+
+ /*
+ * If shrinking, first truncate upper level to cancel writing dirty
+ * pages beyond the new eof; and also if its' maxbytes is more
+ }
+
+ /* notify the (possibly copied-up) lower inode */
-+ mutex_lock(&lower_dentry->d_inode->i_mutex);
++ mutex_lock(&lower_inode->i_mutex);
+ err = notify_change(lower_dentry, ia);
-+ mutex_unlock(&lower_dentry->d_inode->i_mutex);
++ mutex_unlock(&lower_inode->i_mutex);
+ if (err)
+ goto out;
+
+ if (!err)
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+
+ return err;
+};
diff --git a/fs/unionfs/lookup.c b/fs/unionfs/lookup.c
new file mode 100644
-index 0000000..0ae7f3a
+index 0000000..6877b8c
--- /dev/null
+++ b/fs/unionfs/lookup.c
-@@ -0,0 +1,573 @@
+@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * Returns: 0 (ok), or -ERRNO if an error occurred.
+ * XXX: get rid of _partial_lookup and make callers call _lookup_full directly
+ */
-+int unionfs_partial_lookup(struct dentry *dentry)
++int unionfs_partial_lookup(struct dentry *dentry, struct dentry *parent)
+{
+ struct dentry *tmp;
-+ struct nameidata nd = { .flags = 0 };
+ int err = -ENOSYS;
+
-+ tmp = unionfs_lookup_full(dentry, &nd, INTERPOSE_PARTIAL);
++ tmp = unionfs_lookup_full(dentry, parent, INTERPOSE_PARTIAL);
+
+ if (!tmp) {
+ err = 0;
+ nd->intent.open.file = file;
+#endif /* ALLOC_LOWER_ND_FILE */
+ break;
-+ case LOOKUP_ACCESS:
-+ nd->flags = flags;
-+ break;
+ default:
+ /*
+ * We should never get here, for now.
+ * dentry's info, which the caller must unlock.
+ */
+struct dentry *unionfs_lookup_full(struct dentry *dentry,
-+ struct nameidata *nd_unused, int lookupmode)
++ struct dentry *parent, int lookupmode)
+{
+ int err = 0;
+ struct dentry *lower_dentry = NULL;
+ struct vfsmount *lower_dir_mnt;
+ struct dentry *wh_lower_dentry = NULL;
+ struct dentry *lower_dir_dentry = NULL;
-+ struct dentry *parent_dentry = NULL;
+ struct dentry *d_interposed = NULL;
+ int bindex, bstart, bend, bopaque;
+ int opaque, num_positive = 0;
+ * new_dentry_private_data already locked.
+ */
+ verify_locked(dentry);
++ verify_locked(parent);
+
+ /* must initialize dentry operations */
+ dentry->d_op = &unionfs_dops;
+ /* We never partial lookup the root directory. */
+ if (IS_ROOT(dentry))
+ goto out;
-+ parent_dentry = dget_parent(dentry);
+
+ name = dentry->d_name.name;
+ namelen = dentry->d_name.len;
+ }
+
+ /* Now start the actual lookup procedure. */
-+ bstart = dbstart(parent_dentry);
-+ bend = dbend(parent_dentry);
-+ bopaque = dbopaque(parent_dentry);
++ bstart = dbstart(parent);
++ bend = dbend(parent);
++ bopaque = dbopaque(parent);
+ BUG_ON(bstart < 0);
+
+ /* adjust bend to bopaque if needed */
+ }
+
+ lower_dir_dentry =
-+ unionfs_lower_dentry_idx(parent_dentry, bindex);
++ unionfs_lower_dentry_idx(parent, bindex);
+ /* if the lower dentry's parent does not exist, skip this */
+ if (!lower_dir_dentry || !lower_dir_dentry->d_inode)
+ continue;
+ dput(wh_lower_dentry);
+
+ /* Now do regular lookup; lookup @name */
-+ lower_dir_mnt = unionfs_lower_mnt_idx(parent_dentry, bindex);
++ lower_dir_mnt = unionfs_lower_mnt_idx(parent, bindex);
+ lower_mnt = NULL; /* XXX: needed? */
+
+ lower_dentry = __lookup_one(lower_dir_dentry, lower_dir_mnt,
+ goto out_free;
+ }
+ unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry);
-+ BUG_ON(!lower_mnt);
++ if (!lower_mnt)
++ lower_mnt = unionfs_mntget(dentry->d_sb->s_root,
++ bindex);
+ unionfs_set_lower_mnt_idx(dentry, bindex, lower_mnt);
+
+ /* adjust dbstart/end */
+ dbend(dentry) = bindex;
+
+ /* update parent directory's atime with the bindex */
-+ fsstack_copy_attr_atime(parent_dentry->d_inode,
++ fsstack_copy_attr_atime(parent->d_inode,
+ lower_dir_dentry->d_inode);
+ }
+
+ if (unionfs_lower_dentry_idx(dentry, bindex))
+ goto out;
+ lower_dir_dentry =
-+ unionfs_lower_dentry_idx(parent_dentry, bindex);
++ unionfs_lower_dentry_idx(parent, bindex);
+ if (!lower_dir_dentry || !lower_dir_dentry->d_inode)
+ goto out;
+ if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode))
+ BUG_ON(dbstart(d_interposed) >= 0 && dbend(d_interposed) < 0);
+ }
+
-+ dput(parent_dentry);
+ if (!err && d_interposed)
+ return d_interposed;
+ return ERR_PTR(err);
+}
diff --git a/fs/unionfs/rename.c b/fs/unionfs/rename.c
new file mode 100644
-index 0000000..da7d589
+index 0000000..800d9ee
--- /dev/null
+++ b/fs/unionfs/rename.c
-@@ -0,0 +1,478 @@
+@@ -0,0 +1,515 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * This is a helper function for rename, used when rename ends up with hosed
+ * over dentries and we need to revert.
+ */
-+static int unionfs_refresh_lower_dentry(struct dentry *dentry, int bindex)
++static int unionfs_refresh_lower_dentry(struct dentry *dentry,
++ struct dentry *parent, int bindex)
+{
+ struct dentry *lower_dentry;
+ struct dentry *lower_parent;
+
+ verify_locked(dentry);
+
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_CHILD);
-+ lower_parent = unionfs_lower_dentry_idx(dentry->d_parent, bindex);
-+ unionfs_unlock_dentry(dentry->d_parent);
++ lower_parent = unionfs_lower_dentry_idx(parent, bindex);
+
+ BUG_ON(!S_ISDIR(lower_parent->d_inode->i_mode));
+
+}
+
+static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
++ struct dentry *old_parent,
+ struct inode *new_dir, struct dentry *new_dentry,
++ struct dentry *new_parent,
+ int bindex)
+{
+ int err = 0;
+
+ if (!lower_new_dentry) {
+ lower_new_dentry =
-+ create_parents(new_dentry->d_parent->d_inode,
++ create_parents(new_parent->d_inode,
+ new_dentry, new_dentry->d_name.name,
+ bindex);
+ if (IS_ERR(lower_new_dentry)) {
+ */
+static int do_unionfs_rename(struct inode *old_dir,
+ struct dentry *old_dentry,
++ struct dentry *old_parent,
+ struct inode *new_dir,
-+ struct dentry *new_dentry)
++ struct dentry *new_dentry,
++ struct dentry *new_parent)
+{
+ int err = 0;
+ int bindex, bwh_old;
+ int old_bstart, old_bend;
+ int new_bstart, new_bend;
+ int do_copyup = -1;
-+ struct dentry *parent_dentry;
+ int local_err = 0;
+ int eio = 0;
+ int revert = 0;
+ old_bstart = dbstart(old_dentry);
+ bwh_old = old_bstart;
+ old_bend = dbend(old_dentry);
-+ parent_dentry = old_dentry->d_parent;
+
+ new_bstart = dbstart(new_dentry);
+ new_bend = dbend(new_dentry);
+
+ /* Rename source to destination. */
-+ err = __unionfs_rename(old_dir, old_dentry, new_dir, new_dentry,
++ err = __unionfs_rename(old_dir, old_dentry, old_parent,
++ new_dir, new_dentry, new_parent,
+ old_bstart);
+ if (err) {
+ if (!IS_COPYUP_ERR(err))
+ err = vfs_unlink(unlink_dir_dentry->d_inode,
+ unlink_dentry);
+
-+ fsstack_copy_attr_times(new_dentry->d_parent->d_inode,
++ fsstack_copy_attr_times(new_parent->d_inode,
+ unlink_dir_dentry->d_inode);
+ /* propagate number of hard-links */
-+ new_dentry->d_parent->d_inode->i_nlink =
-+ unionfs_get_nlinks(new_dentry->d_parent->d_inode);
++ new_parent->d_inode->i_nlink =
++ unionfs_get_nlinks(new_parent->d_inode);
+
+ unlock_dir(unlink_dir_dentry);
+ if (!err) {
+ * copyup the file into some left directory, so that
+ * you can rename it
+ */
-+ err = copyup_dentry(old_dentry->d_parent->d_inode,
++ err = copyup_dentry(old_parent->d_inode,
+ old_dentry, old_bstart, bindex,
+ old_dentry->d_name.name,
+ old_dentry->d_name.len, NULL,
+ if (err)
+ continue;
+ bwh_old = bindex;
-+ err = __unionfs_rename(old_dir, old_dentry,
-+ new_dir, new_dentry,
++ err = __unionfs_rename(old_dir, old_dentry, old_parent,
++ new_dir, new_dentry, new_parent,
+ bindex);
+ break;
+ }
+
+revert:
+ /* Do revert here. */
-+ local_err = unionfs_refresh_lower_dentry(new_dentry, old_bstart);
++ local_err = unionfs_refresh_lower_dentry(new_dentry, new_parent,
++ old_bstart);
+ if (local_err) {
+ printk(KERN_ERR "unionfs: revert failed in rename: "
+ "the new refresh failed\n");
+ eio = -EIO;
+ }
+
-+ local_err = unionfs_refresh_lower_dentry(old_dentry, old_bstart);
++ local_err = unionfs_refresh_lower_dentry(old_dentry, old_parent,
++ old_bstart);
+ if (local_err) {
+ printk(KERN_ERR "unionfs: revert failed in rename: "
+ "the old refresh failed\n");
+ goto revert_out;
+ }
+
-+ local_err = __unionfs_rename(new_dir, new_dentry,
-+ old_dir, old_dentry, old_bstart);
++ local_err = __unionfs_rename(new_dir, new_dentry, new_parent,
++ old_dir, old_dentry, old_parent,
++ old_bstart);
+
+ /* If we can't fix it, then we cop-out with -EIO. */
+ if (local_err) {
+ eio = -EIO;
+ }
+
-+ local_err = unionfs_refresh_lower_dentry(new_dentry, bindex);
++ local_err = unionfs_refresh_lower_dentry(new_dentry, new_parent,
++ bindex);
+ if (local_err)
+ eio = -EIO;
-+ local_err = unionfs_refresh_lower_dentry(old_dentry, bindex);
++ local_err = unionfs_refresh_lower_dentry(old_dentry, old_parent,
++ bindex);
+ if (local_err)
+ eio = -EIO;
+
+ * return EXDEV to the user-space utility that caused this, and let the
+ * user-space recurse and ask us to copy up each file separately.
+ */
-+static int may_rename_dir(struct dentry *dentry)
++static int may_rename_dir(struct dentry *dentry, struct dentry *parent)
+{
+ int err, bstart;
+
-+ err = check_empty(dentry, NULL);
++ err = check_empty(dentry, parent, NULL);
+ if (err == -ENOTEMPTY) {
+ if (is_robranch(dentry))
+ return -EXDEV;
+ return 0;
+
+ dbstart(dentry) = bstart + 1;
-+ err = check_empty(dentry, NULL);
++ err = check_empty(dentry, parent, NULL);
+ dbstart(dentry) = bstart;
+ if (err == -ENOTEMPTY)
+ err = -EXDEV;
+ return err;
+}
+
++/*
++ * The locking rules in unionfs_rename are complex. We could use a simpler
++ * superblock-level name-space lock for renames and copy-ups.
++ */
+int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry)
+{
+ int err = 0;
+ struct dentry *wh_dentry;
++ struct dentry *old_parent, *new_parent;
++ int valid = true;
+
+ unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ old_parent = dget_parent(old_dentry);
++ new_parent = dget_parent(new_dentry);
++ /* un/lock parent dentries only if they differ from old/new_dentry */
++ if (old_parent != old_dentry &&
++ old_parent != new_dentry)
++ unionfs_lock_dentry(old_parent, UNIONFS_DMUTEX_REVAL_PARENT);
++ if (new_parent != old_dentry &&
++ new_parent != new_dentry &&
++ new_parent != old_parent)
++ unionfs_lock_dentry(new_parent, UNIONFS_DMUTEX_REVAL_CHILD);
+ unionfs_double_lock_dentry(old_dentry, new_dentry);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(old_dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(old_dentry, old_parent, false);
++ if (!valid) {
+ err = -ESTALE;
+ goto out;
+ }
-+ if (unlikely(!d_deleted(new_dentry) && new_dentry->d_inode &&
-+ !__unionfs_d_revalidate_chain(new_dentry, NULL, false))) {
-+ err = -ESTALE;
-+ goto out;
++ if (!d_deleted(new_dentry) && new_dentry->d_inode) {
++ valid = __unionfs_d_revalidate(new_dentry, new_parent, false);
++ if (!valid) {
++ err = -ESTALE;
++ goto out;
++ }
+ }
+
+ if (!S_ISDIR(old_dentry->d_inode->i_mode))
-+ err = unionfs_partial_lookup(old_dentry);
++ err = unionfs_partial_lookup(old_dentry, old_parent);
+ else
-+ err = may_rename_dir(old_dentry);
++ err = may_rename_dir(old_dentry, old_parent);
+
+ if (err)
+ goto out;
+
-+ err = unionfs_partial_lookup(new_dentry);
++ err = unionfs_partial_lookup(new_dentry, new_parent);
+ if (err)
+ goto out;
+
+ if (S_ISDIR(new_dentry->d_inode->i_mode)) {
+ struct unionfs_dir_state *namelist = NULL;
+ /* check if this unionfs directory is empty or not */
-+ err = check_empty(new_dentry, &namelist);
++ err = check_empty(new_dentry, new_parent, &namelist);
+ if (err)
+ goto out;
+
+ }
+ }
+
-+ err = do_unionfs_rename(old_dir, old_dentry, new_dir, new_dentry);
++ err = do_unionfs_rename(old_dir, old_dentry, old_parent,
++ new_dir, new_dentry, new_parent);
+ if (err)
+ goto out;
+
+out:
+ if (err) /* clear the new_dentry stuff created */
+ d_drop(new_dentry);
-+ unionfs_unlock_dentry(new_dentry);
-+ unionfs_unlock_dentry(old_dentry);
++
++ unionfs_double_unlock_dentry(old_dentry, new_dentry);
++ if (new_parent != old_dentry &&
++ new_parent != new_dentry &&
++ new_parent != old_parent)
++ unionfs_unlock_dentry(new_parent);
++ if (old_parent != old_dentry &&
++ old_parent != new_dentry)
++ unionfs_unlock_dentry(old_parent);
++ dput(new_parent);
++ dput(old_parent);
+ unionfs_read_unlock(old_dentry->d_sb);
++
+ return err;
+}
diff --git a/fs/unionfs/sioq.c b/fs/unionfs/sioq.c
new file mode 100644
-index 0000000..e6f15a0
+index 0000000..dd45e39
--- /dev/null
+++ b/fs/unionfs/sioq.c
@@ -0,0 +1,101 @@
+ struct sioq_args *args = container_of(work, struct sioq_args, work);
+ struct symlink_args *s = &args->symlink;
+
-+ args->err = vfs_symlink(s->parent, s->dentry, s->symbuf, s->mode);
++ args->err = vfs_symlink(s->parent, s->dentry, s->symbuf);
+ complete(&args->comp);
+}
+
+}
diff --git a/fs/unionfs/sioq.h b/fs/unionfs/sioq.h
new file mode 100644
-index 0000000..e072bf7
+index 0000000..679a0df
--- /dev/null
+++ b/fs/unionfs/sioq.h
-@@ -0,0 +1,92 @@
+@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2006-2008 Erez Zadok
+ * Copyright (c) 2006 Charles P. Wright
+ struct inode *parent;
+ struct dentry *dentry;
+ char *symbuf;
-+ umode_t mode;
+};
+
+struct unlink_args {
+}
diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
new file mode 100644
-index 0000000..1f4b3f4
+index 0000000..8115079
--- /dev/null
+++ b/fs/unionfs/super.c
-@@ -0,0 +1,1042 @@
+@@ -0,0 +1,1047 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ bindex, branch_count(sb, bindex));
+ leaks = 1;
+ }
-+ BUG_ON(leaks != 0);
++ WARN_ON(leaks != 0);
+
+ /* decrement lower super references */
+ for (bindex = bstart; bindex <= bend; bindex++) {
+ int err = 0;
+ struct super_block *sb;
+ struct dentry *lower_dentry;
++ struct dentry *parent;
++ bool valid;
+
+ sb = dentry->d_sb;
+
+ unionfs_read_lock(sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+out:
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(sb);
+ return err;
+}
+}
+
+/* unionfs inode cache constructor */
-+static void init_once(struct kmem_cache *cachep, void *obj)
++static void init_once(void *obj)
+{
+ struct unionfs_inode_info *i = obj;
+
+};
diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h
new file mode 100644
-index 0000000..1b9c3f7
+index 0000000..00e6dec
--- /dev/null
+++ b/fs/unionfs/union.h
-@@ -0,0 +1,607 @@
+@@ -0,0 +1,650 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+{
+ BUG_ON(d1 == d2);
+ if (d1 < d2) {
-+ unionfs_lock_dentry(d2, UNIONFS_DMUTEX_CHILD);
+ unionfs_lock_dentry(d1, UNIONFS_DMUTEX_PARENT);
++ unionfs_lock_dentry(d2, UNIONFS_DMUTEX_CHILD);
+ } else {
-+ unionfs_lock_dentry(d1, UNIONFS_DMUTEX_CHILD);
+ unionfs_lock_dentry(d2, UNIONFS_DMUTEX_PARENT);
++ unionfs_lock_dentry(d1, UNIONFS_DMUTEX_CHILD);
++ }
++}
++
++static inline void unionfs_double_unlock_dentry(struct dentry *d1,
++ struct dentry *d2)
++{
++ BUG_ON(d1 == d2);
++ if (d1 < d2) { /* unlock in reverse order than double_lock_dentry */
++ unionfs_unlock_dentry(d1);
++ unionfs_unlock_dentry(d2);
++ } else {
++ unionfs_unlock_dentry(d2);
++ unionfs_unlock_dentry(d1);
++ }
++}
++
++static inline void unionfs_double_lock_parents(struct dentry *p1,
++ struct dentry *p2)
++{
++ if (p1 == p2) {
++ unionfs_lock_dentry(p1, UNIONFS_DMUTEX_REVAL_PARENT);
++ return;
++ }
++ if (p1 < p2) {
++ unionfs_lock_dentry(p1, UNIONFS_DMUTEX_REVAL_PARENT);
++ unionfs_lock_dentry(p2, UNIONFS_DMUTEX_REVAL_CHILD);
++ } else {
++ unionfs_lock_dentry(p2, UNIONFS_DMUTEX_REVAL_PARENT);
++ unionfs_lock_dentry(p1, UNIONFS_DMUTEX_REVAL_CHILD);
++ }
++}
++
++static inline void unionfs_double_unlock_parents(struct dentry *p1,
++ struct dentry *p2)
++{
++ if (p1 == p2) {
++ unionfs_unlock_dentry(p1);
++ return;
++ }
++ if (p1 < p2) { /* unlock in reverse order of double_lock_parents */
++ unionfs_unlock_dentry(p1);
++ unionfs_unlock_dentry(p2);
++ } else {
++ unionfs_unlock_dentry(p2);
++ unionfs_unlock_dentry(p1);
+ }
+}
+
+ const char *name, int bindex);
+
+/* partial lookup */
-+extern int unionfs_partial_lookup(struct dentry *dentry);
++extern int unionfs_partial_lookup(struct dentry *dentry,
++ struct dentry *parent);
+extern struct dentry *unionfs_lookup_full(struct dentry *dentry,
-+ struct nameidata *nd_unused,
++ struct dentry *parent,
+ int lookupmode);
+
+/* copies a file from dbstart to newbindex branch */
+extern void unionfs_postcopyup_release(struct dentry *dentry);
+
+/* Is this directory empty: 0 if it is empty, -ENOTEMPTY if not. */
-+extern int check_empty(struct dentry *dentry,
++extern int check_empty(struct dentry *dentry, struct dentry *parent,
+ struct unionfs_dir_state **namelist);
+/* whiteout and opaque directory helpers */
+extern char *alloc_whname(const char *name, int len);
+extern int unionfs_getlk(struct file *file, struct file_lock *fl);
+
+/* Common file operations. */
-+extern int unionfs_file_revalidate(struct file *file, bool willwrite);
-+extern int unionfs_file_revalidate_locked(struct file *file, bool willwrite);
++extern int unionfs_file_revalidate(struct file *file, struct dentry *parent,
++ bool willwrite);
+extern int unionfs_open(struct inode *inode, struct file *file);
+extern int unionfs_file_release(struct inode *inode, struct file *file);
+extern int unionfs_flush(struct file *file, fl_owner_t id);
+extern int unionfs_unlink(struct inode *dir, struct dentry *dentry);
+extern int unionfs_rmdir(struct inode *dir, struct dentry *dentry);
+
-+extern bool __unionfs_d_revalidate_one_locked(struct dentry *dentry,
-+ struct nameidata *nd,
-+ bool willwrite);
-+extern bool __unionfs_d_revalidate_chain(struct dentry *dentry,
-+ struct nameidata *nd, bool willwrite);
++extern bool __unionfs_d_revalidate(struct dentry *dentry,
++ struct dentry *parent, bool willwrite);
+extern bool is_negative_lower(const struct dentry *dentry);
+extern bool is_newer_lower(const struct dentry *dentry);
+extern void purge_sb_data(struct super_block *sb);
+#endif /* not _UNION_H_ */
diff --git a/fs/unionfs/unlink.c b/fs/unionfs/unlink.c
new file mode 100644
-index 0000000..623f68d
+index 0000000..6634c4b
--- /dev/null
+++ b/fs/unionfs/unlink.c
-@@ -0,0 +1,277 @@
+@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * as as per Documentation/filesystems/unionfs/concepts.txt).
+ *
+ */
-+static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry)
++static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry,
++ struct dentry *parent)
+{
+ struct dentry *lower_dentry;
+ struct dentry *lower_dir_dentry;
+ int bindex;
+ int err = 0;
+
-+ err = unionfs_partial_lookup(dentry);
++ err = unionfs_partial_lookup(dentry, parent);
+ if (err)
+ goto out;
+
+{
+ int err = 0;
+ struct inode *inode = dentry->d_inode;
++ struct dentry *parent;
+ int valid;
+
+ BUG_ON(S_ISDIR(inode->i_mode));
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
-+ unionfs_lock_dentry(dentry->d_parent, UNIONFS_DMUTEX_PARENT);
+
-+ valid = __unionfs_d_revalidate_chain(dentry->d_parent, NULL, false);
-+ if (unlikely(!valid)) {
-+ err = -ESTALE;
-+ goto out;
-+ }
-+ valid = __unionfs_d_revalidate_one_locked(dentry, NULL, false);
++ valid = __unionfs_d_revalidate(dentry, parent, false);
+ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+ unionfs_check_dentry(dentry);
+
-+ err = unionfs_unlink_whiteout(dir, dentry);
++ err = unionfs_unlink_whiteout(dir, dentry, parent);
+ /* call d_drop so the system "forgets" about us */
+ if (!err) {
+ unionfs_postcopyup_release(dentry);
++ unionfs_postcopyup_setmnt(parent);
+ if (inode->i_nlink == 0) /* drop lower inodes */
+ iput_lowers_all(inode, false);
+ d_drop(dentry);
+ unionfs_check_dentry(dentry);
+ unionfs_check_inode(dir);
+ }
-+ unionfs_unlock_dentry(dentry->d_parent);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+{
+ int err = 0;
+ struct unionfs_dir_state *namelist = NULL;
++ struct dentry *parent;
+ int dstart, dend;
++ bool valid;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+ unionfs_check_dentry(dentry);
+
+ /* check if this unionfs directory is empty or not */
-+ err = check_empty(dentry, &namelist);
++ err = check_empty(dentry, parent, &namelist);
+ if (err)
+ goto out;
+
+ d_drop(dentry);
+ /* update our lower vfsmnts, in case a copyup took place */
+ unionfs_postcopyup_setmnt(dentry);
++ unionfs_check_dentry(dentry);
++ unionfs_check_inode(dir);
+ }
+
+ if (namelist)
+ free_rdstate(namelist);
+
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
diff --git a/fs/unionfs/whiteout.c b/fs/unionfs/whiteout.c
new file mode 100644
-index 0000000..94b5241
+index 0000000..0934ac8
--- /dev/null
+++ b/fs/unionfs/whiteout.c
@@ -0,0 +1,577 @@
+ struct dentry *parent, *lower_parent, *wh_dentry;
+
+ parent = dget_parent(dentry);
-+ unionfs_lock_dentry(parent, UNIONFS_DMUTEX_WHITEOUT);
++
+ bstart = dbstart(parent);
+ bend = dbend(parent);
+ wh_dentry = ERR_PTR(-ENOENT);
+ dput(wh_dentry);
+ wh_dentry = ERR_PTR(-ENOENT);
+ }
-+ unionfs_unlock_dentry(parent);
++
+ dput(parent);
+
+ return wh_dentry;
+ err = -EIO;
+ printk(KERN_ERR "unionfs: found both whiteout and regular "
+ "file in directory %s (branch %d)\n",
-+ lower_dentry->d_parent->d_name.name, bindex);
++ lower_dir_dentry->d_name.name, bindex);
+ goto out_dput;
+ }
+
+ lower_dir = lower_dir_dentry->d_inode;
+ BUG_ON(!S_ISDIR(lower_dir->i_mode));
+
-+ if (!permission(lower_dir, MAY_WRITE | MAY_EXEC, NULL)) {
++ if (!inode_permission(lower_dir, MAY_WRITE | MAY_EXEC)) {
+ err = do_delete_whiteouts(dentry, bindex, namelist);
+ } else {
+ args.deletewh.namelist = namelist;
+
+ mutex_lock(&lower_inode->i_mutex);
+
-+ if (!permission(lower_inode, MAY_EXEC, NULL)) {
++ if (!inode_permission(lower_inode, MAY_EXEC)) {
+ wh_lower_dentry =
+ lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
+ sizeof(UNIONFS_DIR_OPAQUE) - 1);
+}
diff --git a/fs/unionfs/xattr.c b/fs/unionfs/xattr.c
new file mode 100644
-index 0000000..93a8fce
+index 0000000..e2215c1
--- /dev/null
+++ b/fs/unionfs/xattr.c
-@@ -0,0 +1,153 @@
+@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2003-2008 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ size_t size)
+{
+ struct dentry *lower_dentry = NULL;
++ struct dentry *parent;
+ int err = -EOPNOTSUPP;
++ bool valid;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+out:
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ const void *value, size_t size, int flags)
+{
+ struct dentry *lower_dentry = NULL;
++ struct dentry *parent;
+ int err = -EOPNOTSUPP;
++ bool valid;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+out:
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+int unionfs_removexattr(struct dentry *dentry, const char *name)
+{
+ struct dentry *lower_dentry = NULL;
++ struct dentry *parent;
+ int err = -EOPNOTSUPP;
++ bool valid;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+out:
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}
+ssize_t unionfs_listxattr(struct dentry *dentry, char *list, size_t size)
+{
+ struct dentry *lower_dentry = NULL;
++ struct dentry *parent;
+ int err = -EOPNOTSUPP;
+ char *encoded_list = NULL;
++ bool valid;
+
+ unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
++ parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
+ unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
+
-+ if (unlikely(!__unionfs_d_revalidate_chain(dentry, NULL, false))) {
++ valid = __unionfs_d_revalidate(dentry, parent, false);
++ if (unlikely(!valid)) {
+ err = -ESTALE;
+ goto out;
+ }
+out:
+ unionfs_check_dentry(dentry);
+ unionfs_unlock_dentry(dentry);
++ unionfs_unlock_parent(dentry, parent);
+ unionfs_read_unlock(dentry->d_sb);
+ return err;
+}