lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Tue, 29 Jul 2008 22:43:37 -0400
From:	Erez Zadok <ezk@...sunysb.edu>
To:	akpm@...ux-foundation.org
Cc:	linux-kernel@...r.kernel.org, linux-fsdevel@...r.kernel.org,
	viro@...iv.linux.org.uk, hch@...radead.org, miklos@...redi.hu,
	Erez Zadok <ezk@...sunysb.edu>
Subject: [PATCH 07/19] Unionfs: overhaul whiteout code

Move all whiteout functions and helpers into a separate file, replace all
embedded whiteout code with calls to helpers.  Cleanup and consolidate the
code.  This will make it easier to replace the whiteout code with a
Linux-native whiteout implementation (once available).

Signed-off-by: Erez Zadok <ezk@...sunysb.edu>
---
 fs/unionfs/Makefile    |    2 +-
 fs/unionfs/dirfops.c   |   22 +-
 fs/unionfs/dirhelper.c |  124 +----------
 fs/unionfs/inode.c     |  183 ++-------------
 fs/unionfs/lookup.c    |   85 +-------
 fs/unionfs/rename.c    |  149 ++-----------
 fs/unionfs/sioq.c      |   18 --
 fs/unionfs/subr.c      |  161 --------------
 fs/unionfs/super.c     |    2 +-
 fs/unionfs/union.h     |   40 ++---
 fs/unionfs/whiteout.c  |  577 ++++++++++++++++++++++++++++++++++++++++++++++++
 11 files changed, 655 insertions(+), 708 deletions(-)
 create mode 100644 fs/unionfs/whiteout.c

diff --git a/fs/unionfs/Makefile b/fs/unionfs/Makefile
index 17ca4a7..0dc28c1 100644
--- a/fs/unionfs/Makefile
+++ b/fs/unionfs/Makefile
@@ -2,7 +2,7 @@ obj-$(CONFIG_UNION_FS) += unionfs.o
 
 unionfs-y := subr.o dentry.o file.o inode.o main.o super.o \
 	rdstate.o copyup.o dirhelper.o rename.o unlink.o \
-	lookup.o commonfops.o dirfops.o sioq.o mmap.o
+	lookup.o commonfops.o dirfops.o sioq.o mmap.o whiteout.o
 
 unionfs-$(CONFIG_UNION_FS_XATTR) += xattr.o
 
diff --git a/fs/unionfs/dirfops.c b/fs/unionfs/dirfops.c
index 8272fb6..e35afa4 100644
--- a/fs/unionfs/dirfops.c
+++ b/fs/unionfs/dirfops.c
@@ -36,37 +36,33 @@ struct unionfs_getdents_callback {
 };
 
 /* based on generic filldir in fs/readir.c */
-static int unionfs_filldir(void *dirent, const char *name, int namelen,
+static int unionfs_filldir(void *dirent, const char *oname, int namelen,
 			   loff_t offset, u64 ino, unsigned int d_type)
 {
 	struct unionfs_getdents_callback *buf = dirent;
 	struct filldir_node *found = NULL;
 	int err = 0;
-	int is_wh_entry = 0;
+	int is_whiteout;
+	char *name = (char *) oname;
 
 	buf->filldir_called++;
 
-	if ((namelen > UNIONFS_WHLEN) &&
-	    !strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) {
-		name += UNIONFS_WHLEN;
-		namelen -= UNIONFS_WHLEN;
-		is_wh_entry = 1;
-	}
+	is_whiteout = is_whiteout_name(&name, &namelen);
 
-	found = find_filldir_node(buf->rdstate, name, namelen, is_wh_entry);
+	found = find_filldir_node(buf->rdstate, name, namelen, is_whiteout);
 
 	if (found) {
 		/*
 		 * If we had non-whiteout entry in dir cache, then mark it
 		 * as a whiteout and but leave it in the dir cache.
 		 */
-		if (is_wh_entry && !found->whiteout)
-			found->whiteout = is_wh_entry;
+		if (is_whiteout && !found->whiteout)
+			found->whiteout = is_whiteout;
 		goto out;
 	}
 
 	/* if 'name' isn't a whiteout, filldir it. */
-	if (!is_wh_entry) {
+	if (!is_whiteout) {
 		off_t pos = rdstate2offset(buf->rdstate);
 		u64 unionfs_ino = ino;
 
@@ -85,7 +81,7 @@ static int unionfs_filldir(void *dirent, const char *name, int namelen,
 	}
 	buf->entries_written++;
 	err = add_filldir_node(buf->rdstate, name, namelen,
-			       buf->rdstate->bindex, is_wh_entry);
+			       buf->rdstate->bindex, is_whiteout);
 	if (err)
 		buf->filldir_error = err;
 
diff --git a/fs/unionfs/dirhelper.c b/fs/unionfs/dirhelper.c
index 4b73bb6..302a4a1 100644
--- a/fs/unionfs/dirhelper.c
+++ b/fs/unionfs/dirhelper.c
@@ -18,112 +18,6 @@
 
 #include "union.h"
 
-/*
- * Delete all of the whiteouts in a given directory for rmdir.
- *
- * lower directory inode should be locked
- */
-int do_delete_whiteouts(struct dentry *dentry, int bindex,
-			struct unionfs_dir_state *namelist)
-{
-	int err = 0;
-	struct dentry *lower_dir_dentry = NULL;
-	struct dentry *lower_dentry;
-	char *name = NULL, *p;
-	struct inode *lower_dir;
-	int i;
-	struct list_head *pos;
-	struct filldir_node *cursor;
-
-	/* Find out lower parent dentry */
-	lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-	BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
-	lower_dir = lower_dir_dentry->d_inode;
-	BUG_ON(!S_ISDIR(lower_dir->i_mode));
-
-	err = -ENOMEM;
-	name = __getname();
-	if (unlikely(!name))
-		goto out;
-	strcpy(name, UNIONFS_WHPFX);
-	p = name + UNIONFS_WHLEN;
-
-	err = 0;
-	for (i = 0; !err && i < namelist->size; i++) {
-		list_for_each(pos, &namelist->list[i]) {
-			cursor =
-				list_entry(pos, struct filldir_node,
-					   file_list);
-			/* Only operate on whiteouts in this branch. */
-			if (cursor->bindex != bindex)
-				continue;
-			if (!cursor->whiteout)
-				continue;
-
-			strcpy(p, cursor->name);
-			lower_dentry =
-				lookup_one_len(name, lower_dir_dentry,
-					       cursor->namelen +
-					       UNIONFS_WHLEN);
-			if (IS_ERR(lower_dentry)) {
-				err = PTR_ERR(lower_dentry);
-				break;
-			}
-			if (lower_dentry->d_inode)
-				err = vfs_unlink(lower_dir, lower_dentry);
-			dput(lower_dentry);
-			if (err)
-				break;
-		}
-	}
-
-	__putname(name);
-
-	/* After all of the removals, we should copy the attributes once. */
-	fsstack_copy_attr_times(dentry->d_inode, lower_dir_dentry->d_inode);
-
-out:
-	return err;
-}
-
-/* delete whiteouts in a dir (for rmdir operation) using sioq if necessary */
-int delete_whiteouts(struct dentry *dentry, int bindex,
-		     struct unionfs_dir_state *namelist)
-{
-	int err;
-	struct super_block *sb;
-	struct dentry *lower_dir_dentry;
-	struct inode *lower_dir;
-	struct sioq_args args;
-
-	sb = dentry->d_sb;
-
-	BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
-	BUG_ON(bindex < dbstart(dentry));
-	BUG_ON(bindex > dbend(dentry));
-	err = is_robranch_super(sb, bindex);
-	if (err)
-		goto out;
-
-	lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-	BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
-	lower_dir = lower_dir_dentry->d_inode;
-	BUG_ON(!S_ISDIR(lower_dir->i_mode));
-
-	if (!permission(lower_dir, MAY_WRITE | MAY_EXEC, NULL)) {
-		err = do_delete_whiteouts(dentry, bindex, namelist);
-	} else {
-		args.deletewh.namelist = namelist;
-		args.deletewh.dentry = dentry;
-		args.deletewh.bindex = bindex;
-		run_sioq(__delete_whiteouts, &args);
-		err = args.err;
-	}
-
-out:
-	return err;
-}
-
 #define RD_NONE 0
 #define RD_CHECK_EMPTY 1
 /* The callback structure for check_empty. */
@@ -135,13 +29,14 @@ struct unionfs_rdutil_callback {
 };
 
 /* This filldir function makes sure only whiteouts exist within a directory. */
-static int readdir_util_callback(void *dirent, const char *name, int namelen,
+static int readdir_util_callback(void *dirent, const char *oname, int namelen,
 				 loff_t offset, u64 ino, unsigned int d_type)
 {
 	int err = 0;
 	struct unionfs_rdutil_callback *buf = dirent;
-	int whiteout = 0;
+	int is_whiteout;
 	struct filldir_node *found;
+	char *name = (char *) oname;
 
 	buf->filldir_called = 1;
 
@@ -149,14 +44,9 @@ static int readdir_util_callback(void *dirent, const char *name, int namelen,
 			       (name[1] == '.' && namelen == 2)))
 		goto out;
 
-	if (namelen > UNIONFS_WHLEN &&
-	    !strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN)) {
-		namelen -= UNIONFS_WHLEN;
-		name += UNIONFS_WHLEN;
-		whiteout = 1;
-	}
+	is_whiteout = is_whiteout_name(&name, &namelen);
 
-	found = find_filldir_node(buf->rdstate, name, namelen, whiteout);
+	found = find_filldir_node(buf->rdstate, name, namelen, is_whiteout);
 	/* If it was found in the table there was a previous whiteout. */
 	if (found)
 		goto out;
@@ -166,11 +56,11 @@ static int readdir_util_callback(void *dirent, const char *name, int namelen,
 	 * empty.
 	 */
 	err = -ENOTEMPTY;
-	if ((buf->mode == RD_CHECK_EMPTY) && !whiteout)
+	if ((buf->mode == RD_CHECK_EMPTY) && !is_whiteout)
 		goto out;
 
 	err = add_filldir_node(buf->rdstate, name, namelen,
-			       buf->rdstate->bindex, whiteout);
+			       buf->rdstate->bindex, is_whiteout);
 
 out:
 	buf->err = err;
diff --git a/fs/unionfs/inode.c b/fs/unionfs/inode.c
index 8b4da54..bfebc0c 100644
--- a/fs/unionfs/inode.c
+++ b/fs/unionfs/inode.c
@@ -19,79 +19,6 @@
 #include "union.h"
 
 /*
- * Helper function when creating new objects (create, symlink, and mknod).
- * Checks to see if there's a whiteout in @lower_dentry's parent directory,
- * whose name is taken from @dentry.  Then tries to remove that whiteout, if
- * found.
- *
- * Return 0 if no whiteout was found, or if one was found and successfully
- * removed (a zero tells the caller that @lower_dentry belongs to a good
- * branch to create the new object in).  Return -ERRNO if an error occurred
- * during whiteout lookup or in trying to unlink the whiteout.
- */
-static int check_for_whiteout(struct dentry *dentry,
-			      struct dentry *lower_dentry)
-{
-	int err = 0;
-	struct dentry *wh_dentry = NULL;
-	struct dentry *lower_dir_dentry;
-	char *name = NULL;
-
-	/*
-	 * check if whiteout exists in this branch, i.e. lookup .wh.foo
-	 * first.
-	 */
-	name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
-	if (unlikely(IS_ERR(name))) {
-		err = PTR_ERR(name);
-		goto out;
-	}
-
-	wh_dentry = lookup_one_len(name, lower_dentry->d_parent,
-				   dentry->d_name.len + UNIONFS_WHLEN);
-	if (IS_ERR(wh_dentry)) {
-		err = PTR_ERR(wh_dentry);
-		wh_dentry = NULL;
-		goto out;
-	}
-
-	if (!wh_dentry->d_inode) /* no whiteout exists */
-		goto out;
-
-	/* .wh.foo has been found, so let's unlink it */
-	lower_dir_dentry = lock_parent_wh(wh_dentry);
-	/* see Documentation/filesystems/unionfs/issues.txt */
-	lockdep_off();
-	err = vfs_unlink(lower_dir_dentry->d_inode, wh_dentry);
-	lockdep_on();
-	unlock_dir(lower_dir_dentry);
-
-	/*
-	 * Whiteouts are special files and should be deleted no matter what
-	 * (as if they never existed), in order to allow this create
-	 * operation to succeed.  This is especially important in sticky
-	 * directories: a whiteout may have been created by one user, but
-	 * the newly created file may be created by another user.
-	 * Therefore, in order to maintain Unix semantics, if the vfs_unlink
-	 * above failed, then we have to try to directly unlink the
-	 * whiteout.  Note: in the ODF version of unionfs, whiteout are
-	 * handled much more cleanly.
-	 */
-	if (err == -EPERM) {
-		struct inode *inode = lower_dir_dentry->d_inode;
-		err = inode->i_op->unlink(inode, wh_dentry);
-	}
-	if (err)
-		printk(KERN_ERR "unionfs: could not "
-		       "unlink whiteout, err = %d\n", err);
-
-out:
-	dput(wh_dentry);
-	kfree(name);
-	return err;
-}
-
-/*
  * Find a writeable branch to create new object in.  Checks all writeble
  * branches of the parent inode, from istart to iend order; if none are
  * suitable, also tries branch 0 (which may require a copyup).
@@ -125,7 +52,9 @@ begin:
 		 * check for whiteouts in writeable branch, and remove them
 		 * if necessary.
 		 */
-		err = check_for_whiteout(dentry, lower_dentry);
+		err = check_unlink_whiteout(dentry, lower_dentry, bindex);
+		if (err > 0)	/* ignore if whiteout found and removed */
+			err = 0;
 		if (err)
 			continue;
 		/* if get here, we can write to the branch */
@@ -302,7 +231,6 @@ static int unionfs_link(struct dentry *old_dentry, struct inode *dir,
 	struct dentry *lower_old_dentry = NULL;
 	struct dentry *lower_new_dentry = NULL;
 	struct dentry *lower_dir_dentry = NULL;
-	struct dentry *whiteout_dentry;
 	char *name = NULL;
 
 	unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
@@ -320,48 +248,20 @@ static int unionfs_link(struct dentry *old_dentry, struct inode *dir,
 
 	lower_new_dentry = unionfs_lower_dentry(new_dentry);
 
-	/*
-	 * check if whiteout exists in the branch of new dentry, i.e. lookup
-	 * .wh.foo first. If present, delete it
-	 */
-	name = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len);
-	if (unlikely(IS_ERR(name))) {
-		err = PTR_ERR(name);
-		goto out;
-	}
-
-	whiteout_dentry = lookup_one_len(name, lower_new_dentry->d_parent,
-					 new_dentry->d_name.len +
-					 UNIONFS_WHLEN);
-	if (IS_ERR(whiteout_dentry)) {
-		err = PTR_ERR(whiteout_dentry);
-		goto out;
-	}
-
-	if (!whiteout_dentry->d_inode) {
-		dput(whiteout_dentry);
-		whiteout_dentry = NULL;
-	} else {
-		/* found a .wh.foo entry, unlink it and then call vfs_link() */
-		lower_dir_dentry = lock_parent_wh(whiteout_dentry);
-		err = is_robranch_super(new_dentry->d_sb, dbstart(new_dentry));
-		if (!err) {
-			/* see Documentation/filesystems/unionfs/issues.txt */
-			lockdep_off();
-			err = vfs_unlink(lower_dir_dentry->d_inode,
-					 whiteout_dentry);
-			lockdep_on();
-		}
-
+	/* check for a whiteout in new dentry branch, and delete it */
+	err = check_unlink_whiteout(new_dentry, lower_new_dentry,
+				    dbstart(new_dentry));
+	if (err > 0) {	       /* whiteout found and removed successfully */
+		lower_dir_dentry = dget_parent(lower_new_dentry);
 		fsstack_copy_attr_times(dir, lower_dir_dentry->d_inode);
+		dput(lower_dir_dentry);
 		dir->i_nlink = unionfs_get_nlinks(dir);
-		unlock_dir(lower_dir_dentry);
-		lower_dir_dentry = NULL;
-		dput(whiteout_dentry);
-		if (err)
-			goto out;
+		err = 0;
 	}
+	if (err)
+		goto out;
 
+	/* check if parent hierachy is needed, then link in same branch */
 	if (dbstart(old_dentry) != dbstart(new_dentry)) {
 		lower_new_dentry = create_parents(dir, new_dentry,
 						  new_dentry->d_name.name,
@@ -531,12 +431,10 @@ out:
 static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
 {
 	int err = 0;
-	struct dentry *lower_dentry = NULL, *whiteout_dentry = NULL;
+	struct dentry *lower_dentry = NULL;
 	struct dentry *lower_parent_dentry = NULL;
 	int bindex = 0, bstart;
 	char *name = NULL;
-	int whiteout_unlinked = 0;
-	struct sioq_args args;
 	int valid;
 
 	unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
@@ -558,51 +456,18 @@ static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
 
 	lower_dentry = unionfs_lower_dentry(dentry);
 
-	/*
-	 * check if whiteout exists in this branch, i.e. lookup .wh.foo
-	 * first.
-	 */
-	name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
-	if (unlikely(IS_ERR(name))) {
-		err = PTR_ERR(name);
-		goto out;
-	}
-
-	whiteout_dentry = lookup_one_len(name, lower_dentry->d_parent,
-					 dentry->d_name.len + UNIONFS_WHLEN);
-	if (IS_ERR(whiteout_dentry)) {
-		err = PTR_ERR(whiteout_dentry);
-		goto out;
-	}
-
-	if (!whiteout_dentry->d_inode) {
-		dput(whiteout_dentry);
-		whiteout_dentry = NULL;
-	} else {
-		lower_parent_dentry = lock_parent_wh(whiteout_dentry);
-
-		/* found a.wh.foo entry, remove it then do vfs_mkdir */
-		err = is_robranch_super(dentry->d_sb, bstart);
-		if (!err) {
-			args.unlink.parent = lower_parent_dentry->d_inode;
-			args.unlink.dentry = whiteout_dentry;
-			run_sioq(__unionfs_unlink, &args);
-			err = args.err;
-		}
-		dput(whiteout_dentry);
-
-		unlock_dir(lower_parent_dentry);
-
-		if (err) {
-			/* exit if the error returned was NOT -EROFS */
-			if (!IS_COPYUP_ERR(err))
-				goto out;
-			bstart--;
-		} else {
-			whiteout_unlinked = 1;
-		}
+	/* check for a whiteout in new dentry branch, and delete it */
+	err = check_unlink_whiteout(dentry, lower_dentry, bstart);
+	if (err > 0)	       /* whiteout found and removed successfully */
+		err = 0;
+	if (err) {
+		/* exit if the error returned was NOT -EROFS */
+		if (!IS_COPYUP_ERR(err))
+			goto out;
+		bstart--;
 	}
 
+	/* check if copyup's needed, and mkdir */
 	for (bindex = bstart; bindex >= 0; bindex--) {
 		int i;
 		int bend = dbend(dentry);
diff --git a/fs/unionfs/lookup.c b/fs/unionfs/lookup.c
index 1ba7103..0f62087 100644
--- a/fs/unionfs/lookup.c
+++ b/fs/unionfs/lookup.c
@@ -20,58 +20,6 @@
 
 static int realloc_dentry_private_data(struct dentry *dentry);
 
-/* is the filename valid == !(whiteout for a file or opaque dir marker) */
-static int is_validname(const char *name)
-{
-	if (!strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN))
-		return 0;
-	if (!strncmp(name, UNIONFS_DIR_OPAQUE_NAME,
-		     sizeof(UNIONFS_DIR_OPAQUE_NAME) - 1))
-		return 0;
-	return 1;
-}
-
-/* The rest of these are utility functions for lookup. */
-static noinline_for_stack int is_opaque_dir(struct dentry *dentry, int bindex)
-{
-	int err = 0;
-	struct dentry *lower_dentry;
-	struct dentry *wh_lower_dentry;
-	struct inode *lower_inode;
-	struct sioq_args args;
-
-	lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-	lower_inode = lower_dentry->d_inode;
-
-	BUG_ON(!S_ISDIR(lower_inode->i_mode));
-
-	mutex_lock(&lower_inode->i_mutex);
-
-	if (!permission(lower_inode, MAY_EXEC, NULL)) {
-		wh_lower_dentry =
-			lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
-				       sizeof(UNIONFS_DIR_OPAQUE) - 1);
-	} else {
-		args.is_opaque.dentry = lower_dentry;
-		run_sioq(__is_opaque_dir, &args);
-		wh_lower_dentry = args.ret;
-	}
-
-	mutex_unlock(&lower_inode->i_mutex);
-
-	if (IS_ERR(wh_lower_dentry)) {
-		err = PTR_ERR(wh_lower_dentry);
-		goto out;
-	}
-
-	/* This is an opaque dir iff wh_lower_dentry is positive */
-	err = !!wh_lower_dentry->d_inode;
-
-	dput(wh_lower_dentry);
-out:
-	return err;
-}
-
 /*
  * Main (and complex) driver function for Unionfs's lookup
  *
@@ -99,7 +47,6 @@ struct dentry *unionfs_lookup_backend(struct dentry *dentry,
 	struct dentry *first_lower_dentry = NULL;
 	struct vfsmount *first_lower_mnt = NULL;
 	int opaque;
-	char *whname = NULL;
 	const char *name;
 	int namelen;
 
@@ -184,42 +131,19 @@ struct dentry *unionfs_lookup_backend(struct dentry *dentry,
 		if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode))
 			continue;
 
-		/* Reuse the whiteout name because its value doesn't change. */
-		if (!whname) {
-			whname = alloc_whname(name, namelen);
-			if (unlikely(IS_ERR(whname))) {
-				err = PTR_ERR(whname);
-				goto out_free;
-			}
-		}
-
-		/* check if whiteout exists in this branch: lookup .wh.foo */
-		wh_lower_dentry = lookup_one_len(whname, lower_dir_dentry,
-						 namelen + UNIONFS_WHLEN);
+		/* check for whiteouts: stop lookup if found */
+		wh_lower_dentry = lookup_whiteout(name, lower_dir_dentry);
 		if (IS_ERR(wh_lower_dentry)) {
 			dput(first_lower_dentry);
 			unionfs_mntput(first_dentry, first_dentry_offset);
 			err = PTR_ERR(wh_lower_dentry);
 			goto out_free;
 		}
-
 		if (wh_lower_dentry->d_inode) {
-			/* We found a whiteout so let's give up. */
-			if (S_ISREG(wh_lower_dentry->d_inode->i_mode)) {
-				dbend(dentry) = dbopaque(dentry) = bindex;
-				dput(wh_lower_dentry);
-				break;
-			}
-			err = -EIO;
-			printk(KERN_ERR "unionfs: EIO: invalid whiteout "
-			       "entry type %d\n",
-			       wh_lower_dentry->d_inode->i_mode);
+			dbend(dentry) = dbopaque(dentry) = bindex;
 			dput(wh_lower_dentry);
-			dput(first_lower_dentry);
-			unionfs_mntput(first_dentry, first_dentry_offset);
-			goto out_free;
+			break;
 		}
-
 		dput(wh_lower_dentry);
 		wh_lower_dentry = NULL;
 
@@ -424,7 +348,6 @@ out:
 			dput(first_lower_dentry);
 		}
 	}
-	kfree(whname);
 	dput(parent_dentry);
 	if (err && (lookupmode == INTERPOSE_LOOKUP))
 		unionfs_unlock_dentry(dentry);
diff --git a/fs/unionfs/rename.c b/fs/unionfs/rename.c
index 5b3f1a3..85f96ee 100644
--- a/fs/unionfs/rename.c
+++ b/fs/unionfs/rename.c
@@ -62,17 +62,14 @@ out:
 
 static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 			    struct inode *new_dir, struct dentry *new_dentry,
-			    int bindex, struct dentry **wh_old)
+			    int bindex)
 {
 	int err = 0;
 	struct dentry *lower_old_dentry;
 	struct dentry *lower_new_dentry;
 	struct dentry *lower_old_dir_dentry;
 	struct dentry *lower_new_dir_dentry;
-	struct dentry *lower_wh_dentry;
-	struct dentry *lower_wh_dir_dentry;
 	struct dentry *trap;
-	char *wh_name = NULL;
 
 	lower_new_dentry = unionfs_lower_dentry_idx(new_dentry, bindex);
 	lower_old_dentry = unionfs_lower_dentry_idx(old_dentry, bindex);
@@ -93,46 +90,14 @@ static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 		}
 	}
 
-	wh_name = alloc_whname(new_dentry->d_name.name,
-			       new_dentry->d_name.len);
-	if (unlikely(IS_ERR(wh_name))) {
-		err = PTR_ERR(wh_name);
-		goto out;
-	}
-
-	lower_wh_dentry = lookup_one_len(wh_name, lower_new_dentry->d_parent,
-					 new_dentry->d_name.len +
-					 UNIONFS_WHLEN);
-	if (IS_ERR(lower_wh_dentry)) {
-		err = PTR_ERR(lower_wh_dentry);
+	/* check for and remove whiteout, if any */
+	err = check_unlink_whiteout(new_dentry, lower_new_dentry, bindex);
+	if (err > 0) /* ignore if whiteout found and successfully removed */
+		err = 0;
+	if (err)
 		goto out;
-	}
-
-	if (lower_wh_dentry->d_inode) {
-		/* get rid of the whiteout that is existing */
-		if (lower_new_dentry->d_inode) {
-			printk(KERN_ERR "unionfs: both a whiteout and a "
-			       "dentry exist when doing a rename!\n");
-			err = -EIO;
-
-			dput(lower_wh_dentry);
-			goto out;
-		}
-
-		lower_wh_dir_dentry = lock_parent_wh(lower_wh_dentry);
-		err = is_robranch_super(old_dentry->d_sb, bindex);
-		if (!err)
-			err = vfs_unlink(lower_wh_dir_dentry->d_inode,
-					 lower_wh_dentry);
-
-		dput(lower_wh_dentry);
-		unlock_dir(lower_wh_dir_dentry);
-		if (err)
-			goto out;
-	} else {
-		dput(lower_wh_dentry);
-	}
 
+	/* check of old_dentry branch is writable */
 	err = is_robranch_super(old_dentry->d_sb, bindex);
 	if (err)
 		goto out;
@@ -142,28 +107,6 @@ static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	lower_old_dir_dentry = dget_parent(lower_old_dentry);
 	lower_new_dir_dentry = dget_parent(lower_new_dentry);
 
-	/*
-	 * ready to whiteout for old_dentry. caller will create the actual
-	 * whiteout, and must dput(*wh_old)
-	 */
-	if (wh_old) {
-		char *whname;
-		whname = alloc_whname(old_dentry->d_name.name,
-				      old_dentry->d_name.len);
-		err = PTR_ERR(whname);
-		if (unlikely(IS_ERR(whname)))
-			goto out_dput;
-		*wh_old = lookup_one_len(whname, lower_old_dir_dentry,
-					 old_dentry->d_name.len +
-					 UNIONFS_WHLEN);
-		kfree(whname);
-		err = PTR_ERR(*wh_old);
-		if (IS_ERR(*wh_old)) {
-			*wh_old = NULL;
-			goto out_dput;
-		}
-	}
-
 	/* see Documentation/filesystems/unionfs/issues.txt */
 	lockdep_off();
 	trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
@@ -188,7 +131,6 @@ out_err_unlock:
 	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
 	lockdep_on();
 
-out_dput:
 	dput(lower_old_dir_dentry);
 	dput(lower_new_dir_dentry);
 	dput(lower_old_dentry);
@@ -203,8 +145,6 @@ out:
 			dbend(new_dentry) = bindex;
 	}
 
-	kfree(wh_name);
-
 	return err;
 }
 
@@ -227,7 +167,6 @@ static int do_unionfs_rename(struct inode *old_dir,
 	int local_err = 0;
 	int eio = 0;
 	int revert = 0;
-	struct dentry *wh_old = NULL;
 
 	old_bstart = dbstart(old_dentry);
 	bwh_old = old_bstart;
@@ -239,7 +178,7 @@ static int do_unionfs_rename(struct inode *old_dir,
 
 	/* Rename source to destination. */
 	err = __unionfs_rename(old_dir, old_dentry, new_dir, new_dentry,
-			       old_bstart, &wh_old);
+			       old_bstart);
 	if (err) {
 		if (!IS_COPYUP_ERR(err))
 			goto out;
@@ -282,7 +221,6 @@ static int do_unionfs_rename(struct inode *old_dir,
 		} else if (IS_COPYUP_ERR(err)) {
 			do_copyup = bindex - 1;
 		} else if (revert) {
-			dput(wh_old);
 			goto revert;
 		}
 	}
@@ -301,11 +239,10 @@ static int do_unionfs_rename(struct inode *old_dir,
 			/* if copyup failed, try next branch to the left */
 			if (err)
 				continue;
-			dput(wh_old);
 			bwh_old = bindex;
 			err = __unionfs_rename(old_dir, old_dentry,
 					       new_dir, new_dentry,
-					       bindex, &wh_old);
+					       bindex);
 			break;
 		}
 	}
@@ -323,38 +260,22 @@ static int do_unionfs_rename(struct inode *old_dir,
 	 * (2) We did a copy_up
 	 */
 	if ((old_bstart != old_bend) || (do_copyup != -1)) {
-		struct dentry *lower_parent;
-		struct nameidata nd;
-		if (!wh_old || wh_old->d_inode || bwh_old < 0) {
-			printk(KERN_ERR "unionfs: rename error "
-			       "(wh_old=%p/%p bwh_old=%d)\n", wh_old,
-			       (wh_old ? wh_old->d_inode : NULL), bwh_old);
+		if (bwh_old < 0) {
+			printk(KERN_ERR "unionfs: rename error (bwh_old=%d)\n",
+			       bwh_old);
 			err = -EIO;
 			goto out;
 		}
-		err = init_lower_nd(&nd, LOOKUP_CREATE);
-		if (unlikely(err < 0))
-			goto out;
-		lower_parent = lock_parent_wh(wh_old);
-		local_err = vfs_create(lower_parent->d_inode, wh_old, S_IRUGO,
-				       &nd);
-		unlock_dir(lower_parent);
-		if (!local_err) {
-			dbopaque(old_dentry) = bwh_old;
-		} else {
-			/*
-			 * we can't fix anything now, so we cop-out and use
-			 * -EIO.
-			 */
+		err = create_whiteout(old_dentry, bwh_old);
+		if (err) {
+			/* can't fix anything now, so we exit with -EIO */
 			printk(KERN_ERR "unionfs: can't create a whiteout for "
-			       "the source in rename!\n");
+			       "%s in rename!\n", old_dentry->d_name.name);
 			err = -EIO;
 		}
-		release_lower_nd(&nd, local_err);
 	}
 
 out:
-	dput(wh_old);
 	return err;
 
 revert:
@@ -391,7 +312,7 @@ revert:
 	}
 
 	local_err = __unionfs_rename(new_dir, new_dentry,
-				     old_dir, old_dentry, old_bstart, NULL);
+				     old_dir, old_dentry, old_bstart);
 
 	/* If we can't fix it, then we cop-out with -EIO. */
 	if (local_err) {
@@ -412,40 +333,6 @@ revert_out:
 	return err;
 }
 
-static struct dentry *lookup_whiteout(struct dentry *dentry)
-{
-	char *whname;
-	int bindex = -1, bstart = -1, bend = -1;
-	struct dentry *parent, *lower_parent, *wh_dentry;
-
-	whname = alloc_whname(dentry->d_name.name, dentry->d_name.len);
-	if (unlikely(IS_ERR(whname)))
-		return (void *)whname;
-
-	parent = dget_parent(dentry);
-	unionfs_lock_dentry(parent, UNIONFS_DMUTEX_WHITEOUT);
-	bstart = dbstart(parent);
-	bend = dbend(parent);
-	wh_dentry = ERR_PTR(-ENOENT);
-	for (bindex = bstart; bindex <= bend; bindex++) {
-		lower_parent = unionfs_lower_dentry_idx(parent, bindex);
-		if (!lower_parent)
-			continue;
-		wh_dentry = lookup_one_len(whname, lower_parent,
-					   dentry->d_name.len + UNIONFS_WHLEN);
-		if (IS_ERR(wh_dentry))
-			continue;
-		if (wh_dentry->d_inode)
-			break;
-		dput(wh_dentry);
-		wh_dentry = ERR_PTR(-ENOENT);
-	}
-	unionfs_unlock_dentry(parent);
-	dput(parent);
-	kfree(whname);
-	return wh_dentry;
-}
-
 /*
  * We can't copyup a directory, because it may involve huge numbers of
  * children, etc.  Doing that in the kernel would be bad, so instead we
@@ -511,7 +398,7 @@ int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	 * if new_dentry is already lower because of whiteout,
 	 * simply override it even if the whited-out dir is not empty.
 	 */
-	wh_dentry = lookup_whiteout(new_dentry);
+	wh_dentry = find_first_whiteout(new_dentry);
 	if (!IS_ERR(wh_dentry)) {
 		dput(wh_dentry);
 	} else if (new_dentry->d_inode) {
diff --git a/fs/unionfs/sioq.c b/fs/unionfs/sioq.c
index 2a8c88e..0ea8436 100644
--- a/fs/unionfs/sioq.c
+++ b/fs/unionfs/sioq.c
@@ -99,21 +99,3 @@ void __unionfs_unlink(struct work_struct *work)
 	args->err = vfs_unlink(u->parent, u->dentry);
 	complete(&args->comp);
 }
-
-void __delete_whiteouts(struct work_struct *work)
-{
-	struct sioq_args *args = container_of(work, struct sioq_args, work);
-	struct deletewh_args *d = &args->deletewh;
-
-	args->err = do_delete_whiteouts(d->dentry, d->bindex, d->namelist);
-	complete(&args->comp);
-}
-
-void __is_opaque_dir(struct work_struct *work)
-{
-	struct sioq_args *args = container_of(work, struct sioq_args, work);
-
-	args->ret = lookup_one_len(UNIONFS_DIR_OPAQUE, args->is_opaque.dentry,
-				   sizeof(UNIONFS_DIR_OPAQUE) - 1);
-	complete(&args->comp);
-}
diff --git a/fs/unionfs/subr.c b/fs/unionfs/subr.c
index 1f1db3d..dda2745 100644
--- a/fs/unionfs/subr.c
+++ b/fs/unionfs/subr.c
@@ -19,152 +19,6 @@
 #include "union.h"
 
 /*
- * Pass an unionfs dentry and an index.  It will try to create a whiteout
- * for the filename in dentry, and will try in branch 'index'.  On error,
- * it will proceed to a branch to the left.
- */
-int create_whiteout(struct dentry *dentry, int start)
-{
-	int bstart, bend, bindex;
-	struct dentry *lower_dir_dentry;
-	struct dentry *lower_dentry;
-	struct dentry *lower_wh_dentry;
-	struct nameidata nd;
-	char *name = NULL;
-	int err = -EINVAL;
-
-	verify_locked(dentry);
-
-	bstart = dbstart(dentry);
-	bend = dbend(dentry);
-
-	/* create dentry's whiteout equivalent */
-	name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
-	if (unlikely(IS_ERR(name))) {
-		err = PTR_ERR(name);
-		goto out;
-	}
-
-	for (bindex = start; bindex >= 0; bindex--) {
-		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-
-		if (!lower_dentry) {
-			/*
-			 * if lower dentry is not present, create the
-			 * entire lower dentry directory structure and go
-			 * ahead.  Since we want to just create whiteout, we
-			 * only want the parent dentry, and hence get rid of
-			 * this dentry.
-			 */
-			lower_dentry = create_parents(dentry->d_inode,
-						      dentry,
-						      dentry->d_name.name,
-						      bindex);
-			if (!lower_dentry || IS_ERR(lower_dentry)) {
-				int ret = PTR_ERR(lower_dentry);
-				if (!IS_COPYUP_ERR(ret))
-					printk(KERN_ERR
-					       "unionfs: create_parents for "
-					       "whiteout failed: bindex=%d "
-					       "err=%d\n", bindex, ret);
-				continue;
-			}
-		}
-
-		lower_wh_dentry =
-			lookup_one_len(name, lower_dentry->d_parent,
-				       dentry->d_name.len + UNIONFS_WHLEN);
-		if (IS_ERR(lower_wh_dentry))
-			continue;
-
-		/*
-		 * The whiteout already exists. This used to be impossible,
-		 * but now is possible because of opaqueness.
-		 */
-		if (lower_wh_dentry->d_inode) {
-			dput(lower_wh_dentry);
-			err = 0;
-			goto out;
-		}
-
-		err = init_lower_nd(&nd, LOOKUP_CREATE);
-		if (unlikely(err < 0))
-			goto out;
-		lower_dir_dentry = lock_parent_wh(lower_wh_dentry);
-		err = is_robranch_super(dentry->d_sb, bindex);
-		if (!err)
-			err = vfs_create(lower_dir_dentry->d_inode,
-					 lower_wh_dentry,
-					 ~current->fs->umask & S_IRWXUGO,
-					 &nd);
-		unlock_dir(lower_dir_dentry);
-		dput(lower_wh_dentry);
-		release_lower_nd(&nd, err);
-
-		if (!err || !IS_COPYUP_ERR(err))
-			break;
-	}
-
-	/* set dbopaque so that lookup will not proceed after this branch */
-	if (!err)
-		dbopaque(dentry) = bindex;
-
-out:
-	kfree(name);
-	return err;
-}
-
-int make_dir_opaque(struct dentry *dentry, int bindex)
-{
-	int err = 0;
-	struct dentry *lower_dentry, *diropq;
-	struct inode *lower_dir;
-	struct nameidata nd;
-	kernel_cap_t orig_cap;
-
-	/*
-	 * Opaque directory whiteout markers are special files (like regular
-	 * whiteouts), and should appear to the users as if they don't
-	 * exist.  They should be created/deleted regardless of directory
-	 * search/create permissions, but only for the duration of this
-	 * creation of the .wh.__dir_opaque: file.  Note, this does not
-	 * circumvent normal ->permission).
-	 */
-	orig_cap = current->cap_effective;
-	cap_raise(current->cap_effective, CAP_DAC_READ_SEARCH);
-	cap_raise(current->cap_effective, CAP_DAC_OVERRIDE);
-
-	lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
-	lower_dir = lower_dentry->d_inode;
-	BUG_ON(!S_ISDIR(dentry->d_inode->i_mode) ||
-	       !S_ISDIR(lower_dir->i_mode));
-
-	mutex_lock(&lower_dir->i_mutex);
-	diropq = lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
-				sizeof(UNIONFS_DIR_OPAQUE) - 1);
-	if (IS_ERR(diropq)) {
-		err = PTR_ERR(diropq);
-		goto out;
-	}
-
-	err = init_lower_nd(&nd, LOOKUP_CREATE);
-	if (unlikely(err < 0))
-		goto out;
-	if (!diropq->d_inode)
-		err = vfs_create(lower_dir, diropq, S_IRUGO, &nd);
-	if (!err)
-		dbopaque(dentry) = bindex;
-	release_lower_nd(&nd, err);
-
-	dput(diropq);
-
-out:
-	mutex_unlock(&lower_dir->i_mutex);
-	current->cap_effective = orig_cap;
-	return err;
-}
-
-/*
  * returns the right n_link value based on the inode type
  */
 int unionfs_get_nlinks(const struct inode *inode)
@@ -184,21 +38,6 @@ int unionfs_get_nlinks(const struct inode *inode)
 	return 1;
 }
 
-/* construct whiteout filename */
-char *alloc_whname(const char *name, int len)
-{
-	char *buf;
-
-	buf = kmalloc(len + UNIONFS_WHLEN + 1, GFP_KERNEL);
-	if (unlikely(!buf))
-		return ERR_PTR(-ENOMEM);
-
-	strcpy(buf, UNIONFS_WHPFX);
-	strlcat(buf, name, len + UNIONFS_WHLEN + 1);
-
-	return buf;
-}
-
 /* copy a/m/ctime from the lower branch with the newest times */
 void unionfs_copy_attr_times(struct inode *upper)
 {
diff --git a/fs/unionfs/super.c b/fs/unionfs/super.c
index 9715529..3b6b65a 100644
--- a/fs/unionfs/super.c
+++ b/fs/unionfs/super.c
@@ -173,7 +173,7 @@ static int unionfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 	 *
 	 * XXX: this restriction goes away with ODF.
 	 */
-	buf->f_namelen -= UNIONFS_WHLEN;
+	unionfs_set_max_namelen(&buf->f_namelen);
 
 	/*
 	 * reset two fields to avoid confusing user-land.
diff --git a/fs/unionfs/union.h b/fs/unionfs/union.h
index f5cf09b..9271b3b 100644
--- a/fs/unionfs/union.h
+++ b/fs/unionfs/union.h
@@ -306,18 +306,10 @@ extern void release_lower_nd(struct nameidata *nd, int err);
 /* replicates the directory structure up to given dentry in given branch */
 extern struct dentry *create_parents(struct inode *dir, struct dentry *dentry,
 				     const char *name, int bindex);
-extern int make_dir_opaque(struct dentry *dir, int bindex);
 
 /* partial lookup */
 extern int unionfs_partial_lookup(struct dentry *dentry);
 
-/*
- * Pass an unionfs dentry and an index and it will try to create a whiteout
- * in branch 'index'.
- *
- * On error, it will proceed to a branch to the left
- */
-extern int create_whiteout(struct dentry *dentry, int start);
 /* copies a file from dbstart to newbindex branch */
 extern int copyup_file(struct inode *dir, struct file *file, int bstart,
 		       int newbindex, loff_t size);
@@ -332,18 +324,25 @@ extern int copyup_dentry(struct inode *dir, struct dentry *dentry,
 extern void unionfs_postcopyup_setmnt(struct dentry *dentry);
 extern void unionfs_postcopyup_release(struct dentry *dentry);
 
-extern int remove_whiteouts(struct dentry *dentry,
-			    struct dentry *lower_dentry, int bindex);
-
-extern int do_delete_whiteouts(struct dentry *dentry, int bindex,
-			       struct unionfs_dir_state *namelist);
-
 /* Is this directory empty: 0 if it is empty, -ENOTEMPTY if not. */
 extern int check_empty(struct dentry *dentry,
 		       struct unionfs_dir_state **namelist);
-/* Delete whiteouts from this directory in branch bindex. */
+/* whiteout and opaque directory helpers */
+extern char *alloc_whname(const char *name, int len);
+extern bool is_whiteout_name(char **namep, int *namelenp);
+extern bool is_validname(const char *name);
+extern struct dentry *lookup_whiteout(const char *name,
+				      struct dentry *lower_parent);
+extern struct dentry *find_first_whiteout(struct dentry *dentry);
+extern int unlink_whiteout(struct dentry *wh_dentry);
+extern int check_unlink_whiteout(struct dentry *dentry,
+				 struct dentry *lower_dentry, int bindex);
+extern int create_whiteout(struct dentry *dentry, int start);
 extern int delete_whiteouts(struct dentry *dentry, int bindex,
 			    struct unionfs_dir_state *namelist);
+extern int is_opaque_dir(struct dentry *dentry, int bindex);
+extern int make_dir_opaque(struct dentry *dir, int bindex);
+extern void unionfs_set_max_namelen(long *namelen);
 
 extern void unionfs_reinterpose(struct dentry *this_dentry);
 extern struct super_block *unionfs_duplicate_super(struct super_block *sb);
@@ -476,20 +475,9 @@ static inline int is_robranch(const struct dentry *dentry)
 	return is_robranch_idx(dentry, index);
 }
 
-/* What do we use for whiteouts. */
-#define UNIONFS_WHPFX ".wh."
-#define UNIONFS_WHLEN 4
-/*
- * If a directory contains this file, then it is opaque.  We start with the
- * .wh. flag so that it is blocked by lookup.
- */
-#define UNIONFS_DIR_OPAQUE_NAME "__dir_opaque"
-#define UNIONFS_DIR_OPAQUE UNIONFS_WHPFX UNIONFS_DIR_OPAQUE_NAME
-
 /*
  * EXTERNALS:
  */
-extern char *alloc_whname(const char *name, int len);
 extern int check_branch(struct nameidata *nd);
 extern int parse_branch_mode(const char *name, int *perms);
 
diff --git a/fs/unionfs/whiteout.c b/fs/unionfs/whiteout.c
new file mode 100644
index 0000000..b1768ed
--- /dev/null
+++ b/fs/unionfs/whiteout.c
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2003-2007 Erez Zadok
+ * Copyright (c) 2003-2006 Charles P. Wright
+ * Copyright (c) 2005-2007 Josef 'Jeff' Sipek
+ * Copyright (c) 2005-2006 Junjiro Okajima
+ * Copyright (c) 2005      Arun M. Krishnakumar
+ * Copyright (c) 2004-2006 David P. Quigley
+ * Copyright (c) 2003-2004 Mohammad Nayyer Zubair
+ * Copyright (c) 2003      Puja Gupta
+ * Copyright (c) 2003      Harikesavan Krishnan
+ * Copyright (c) 2003-2007 Stony Brook University
+ * Copyright (c) 2003-2007 The Research Foundation of SUNY
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include "union.h"
+
+/*
+ * whiteout and opaque directory helpers
+ */
+
+/* What do we use for whiteouts. */
+#define UNIONFS_WHPFX ".wh."
+#define UNIONFS_WHLEN 4
+/*
+ * If a directory contains this file, then it is opaque.  We start with the
+ * .wh. flag so that it is blocked by lookup.
+ */
+#define UNIONFS_DIR_OPAQUE_NAME "__dir_opaque"
+#define UNIONFS_DIR_OPAQUE UNIONFS_WHPFX UNIONFS_DIR_OPAQUE_NAME
+
+/* construct whiteout filename */
+char *alloc_whname(const char *name, int len)
+{
+	char *buf;
+
+	buf = kmalloc(len + UNIONFS_WHLEN + 1, GFP_KERNEL);
+	if (unlikely(!buf))
+		return ERR_PTR(-ENOMEM);
+
+	strcpy(buf, UNIONFS_WHPFX);
+	strlcat(buf, name, len + UNIONFS_WHLEN + 1);
+
+	return buf;
+}
+
+/*
+ * XXX: this can be inline or CPP macro, but is here to keep all whiteout
+ * code in one place.
+ */
+void unionfs_set_max_namelen(long *namelen)
+{
+	*namelen -= UNIONFS_WHLEN;
+}
+
+/* check if @namep is a whiteout, update @namep and @namelenp accordingly */
+bool is_whiteout_name(char **namep, int *namelenp)
+{
+	if (*namelenp > UNIONFS_WHLEN &&
+	    !strncmp(*namep, UNIONFS_WHPFX, UNIONFS_WHLEN)) {
+		*namep += UNIONFS_WHLEN;
+		*namelenp -= UNIONFS_WHLEN;
+		return true;
+	}
+	return false;
+}
+
+/* is the filename valid == !(whiteout for a file or opaque dir marker) */
+bool is_validname(const char *name)
+{
+	if (!strncmp(name, UNIONFS_WHPFX, UNIONFS_WHLEN))
+		return false;
+	if (!strncmp(name, UNIONFS_DIR_OPAQUE_NAME,
+		     sizeof(UNIONFS_DIR_OPAQUE_NAME) - 1))
+		return false;
+	return true;
+}
+
+/*
+ * Look for a whiteout @name in @lower_parent directory.  If error, return
+ * ERR_PTR.  Caller must dput() the returned dentry if not an error.
+ *
+ * XXX: some callers can reuse the whname allocated buffer to avoid repeated
+ * free then re-malloc calls.  Need to provide a different API for those
+ * callers.
+ */
+struct dentry *lookup_whiteout(const char *name, struct dentry *lower_parent)
+{
+	char *whname = NULL;
+	int err = 0, namelen;
+	struct dentry *wh_dentry = NULL;
+
+	namelen = strlen(name);
+	whname = alloc_whname(name, namelen);
+	if (unlikely(IS_ERR(whname))) {
+		err = PTR_ERR(whname);
+		goto out;
+	}
+
+	/* check if whiteout exists in this branch: lookup .wh.foo */
+	wh_dentry = lookup_one_len(whname, lower_parent, strlen(whname));
+	if (IS_ERR(wh_dentry)) {
+		err = PTR_ERR(wh_dentry);
+		goto out;
+	}
+
+	/* check if negative dentry (ENOENT) */
+	if (!wh_dentry->d_inode)
+		goto out;
+
+	/* whiteout found: check if valid type */
+	if (!S_ISREG(wh_dentry->d_inode->i_mode)) {
+		printk(KERN_ERR "unionfs: invalid whiteout %s entry type %d\n",
+		       whname, wh_dentry->d_inode->i_mode);
+		dput(wh_dentry);
+		err = -EIO;
+		goto out;
+	}
+
+out:
+	kfree(whname);
+	if (err)
+		wh_dentry = ERR_PTR(err);
+	return wh_dentry;
+}
+
+/* find and return first whiteout in parent directory, else ENOENT */
+struct dentry *find_first_whiteout(struct dentry *dentry)
+{
+	int bindex, bstart, bend;
+	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);
+
+	for (bindex = bstart; bindex <= bend; bindex++) {
+		lower_parent = unionfs_lower_dentry_idx(parent, bindex);
+		if (!lower_parent)
+			continue;
+		wh_dentry = lookup_whiteout(dentry->d_name.name, lower_parent);
+		if (IS_ERR(wh_dentry))
+			continue;
+		if (wh_dentry->d_inode)
+			break;
+		dput(wh_dentry);
+		wh_dentry = ERR_PTR(-ENOENT);
+	}
+	unionfs_unlock_dentry(parent);
+	dput(parent);
+
+	return wh_dentry;
+}
+
+/*
+ * Unlink a whiteout dentry.  Returns 0 or -errno.  Caller must hold and
+ * release dentry reference.
+ */
+int unlink_whiteout(struct dentry *wh_dentry)
+{
+	int err;
+	struct dentry *lower_dir_dentry;
+
+	/* dget and lock parent dentry */
+	lower_dir_dentry = lock_parent_wh(wh_dentry);
+
+	/* see Documentation/filesystems/unionfs/issues.txt */
+	lockdep_off();
+	err = vfs_unlink(lower_dir_dentry->d_inode, wh_dentry);
+	lockdep_on();
+	unlock_dir(lower_dir_dentry);
+
+	/*
+	 * Whiteouts are special files and should be deleted no matter what
+	 * (as if they never existed), in order to allow this create
+	 * operation to succeed.  This is especially important in sticky
+	 * directories: a whiteout may have been created by one user, but
+	 * the newly created file may be created by another user.
+	 * Therefore, in order to maintain Unix semantics, if the vfs_unlink
+	 * above failed, then we have to try to directly unlink the
+	 * whiteout.  Note: in the ODF version of unionfs, whiteout are
+	 * handled much more cleanly.
+	 */
+	if (err == -EPERM) {
+		struct inode *inode = lower_dir_dentry->d_inode;
+		err = inode->i_op->unlink(inode, wh_dentry);
+	}
+	if (err)
+		printk(KERN_ERR "unionfs: could not unlink whiteout %s, "
+		       "err = %d\n", wh_dentry->d_name.name, err);
+
+	return err;
+
+}
+
+/*
+ * Helper function when creating new objects (create, symlink, mknod, etc.).
+ * Checks to see if there's a whiteout in @lower_dentry's parent directory,
+ * whose name is taken from @dentry.  Then tries to remove that whiteout, if
+ * found.  If <dentry,bindex> is a branch marked readonly, return -EROFS.
+ * If it finds both a regular file and a whiteout, return -EIO (this should
+ * never happen).
+ *
+ * Return 0 if no whiteout was found.  Return 1 if one was found and
+ * successfully removed.  Therefore a value >= 0 tells the caller that
+ * @lower_dentry belongs to a good branch to create the new object in).
+ * Return -ERRNO if an error occurred during whiteout lookup or in trying to
+ * unlink the whiteout.
+ */
+int check_unlink_whiteout(struct dentry *dentry, struct dentry *lower_dentry,
+			  int bindex)
+{
+	int err;
+	struct dentry *wh_dentry = NULL;
+	struct dentry *lower_dir_dentry = NULL;
+
+	/* look for whiteout dentry first */
+	lower_dir_dentry = dget_parent(lower_dentry);
+	wh_dentry = lookup_whiteout(dentry->d_name.name, lower_dir_dentry);
+	dput(lower_dir_dentry);
+	if (IS_ERR(wh_dentry)) {
+		err = PTR_ERR(wh_dentry);
+		goto out;
+	}
+
+	if (!wh_dentry->d_inode) { /* no whiteout exists*/
+		err = 0;
+		goto out_dput;
+	}
+
+	/* check if regular file and whiteout were both found */
+	if (unlikely(lower_dentry->d_inode)) {
+		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);
+		goto out_dput;
+	}
+
+	/* check if branch is writeable */
+	err = is_robranch_super(dentry->d_sb, bindex);
+	if (err)
+		goto out_dput;
+
+	/* .wh.foo has been found, so let's unlink it */
+	err = unlink_whiteout(wh_dentry);
+	if (!err)
+		err = 1; /* a whiteout was found and successfully removed */
+out_dput:
+	dput(wh_dentry);
+out:
+	return err;
+}
+
+/*
+ * Pass an unionfs dentry and an index.  It will try to create a whiteout
+ * for the filename in dentry, and will try in branch 'index'.  On error,
+ * it will proceed to a branch to the left.
+ */
+int create_whiteout(struct dentry *dentry, int start)
+{
+	int bstart, bend, bindex;
+	struct dentry *lower_dir_dentry;
+	struct dentry *lower_dentry;
+	struct dentry *lower_wh_dentry;
+	struct nameidata nd;
+	char *name = NULL;
+	int err = -EINVAL;
+
+	verify_locked(dentry);
+
+	bstart = dbstart(dentry);
+	bend = dbend(dentry);
+
+	/* create dentry's whiteout equivalent */
+	name = alloc_whname(dentry->d_name.name, dentry->d_name.len);
+	if (unlikely(IS_ERR(name))) {
+		err = PTR_ERR(name);
+		goto out;
+	}
+
+	for (bindex = start; bindex >= 0; bindex--) {
+		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+
+		if (!lower_dentry) {
+			/*
+			 * if lower dentry is not present, create the
+			 * entire lower dentry directory structure and go
+			 * ahead.  Since we want to just create whiteout, we
+			 * only want the parent dentry, and hence get rid of
+			 * this dentry.
+			 */
+			lower_dentry = create_parents(dentry->d_inode,
+						      dentry,
+						      dentry->d_name.name,
+						      bindex);
+			if (!lower_dentry || IS_ERR(lower_dentry)) {
+				int ret = PTR_ERR(lower_dentry);
+				if (!IS_COPYUP_ERR(ret))
+					printk(KERN_ERR
+					       "unionfs: create_parents for "
+					       "whiteout failed: bindex=%d "
+					       "err=%d\n", bindex, ret);
+				continue;
+			}
+		}
+
+		lower_wh_dentry =
+			lookup_one_len(name, lower_dentry->d_parent,
+				       dentry->d_name.len + UNIONFS_WHLEN);
+		if (IS_ERR(lower_wh_dentry))
+			continue;
+
+		/*
+		 * The whiteout already exists. This used to be impossible,
+		 * but now is possible because of opaqueness.
+		 */
+		if (lower_wh_dentry->d_inode) {
+			dput(lower_wh_dentry);
+			err = 0;
+			goto out;
+		}
+
+		err = init_lower_nd(&nd, LOOKUP_CREATE);
+		if (unlikely(err < 0))
+			goto out;
+		lower_dir_dentry = lock_parent_wh(lower_wh_dentry);
+		err = is_robranch_super(dentry->d_sb, bindex);
+		if (!err)
+			err = vfs_create(lower_dir_dentry->d_inode,
+					 lower_wh_dentry,
+					 ~current->fs->umask & S_IRUGO,
+					 &nd);
+		unlock_dir(lower_dir_dentry);
+		dput(lower_wh_dentry);
+		release_lower_nd(&nd, err);
+
+		if (!err || !IS_COPYUP_ERR(err))
+			break;
+	}
+
+	/* set dbopaque so that lookup will not proceed after this branch */
+	if (!err)
+		dbopaque(dentry) = bindex;
+
+out:
+	kfree(name);
+	return err;
+}
+
+/*
+ * Delete all of the whiteouts in a given directory for rmdir.
+ *
+ * lower directory inode should be locked
+ */
+static int do_delete_whiteouts(struct dentry *dentry, int bindex,
+			       struct unionfs_dir_state *namelist)
+{
+	int err = 0;
+	struct dentry *lower_dir_dentry = NULL;
+	struct dentry *lower_dentry;
+	char *name = NULL, *p;
+	struct inode *lower_dir;
+	int i;
+	struct list_head *pos;
+	struct filldir_node *cursor;
+
+	/* Find out lower parent dentry */
+	lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+	BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
+	lower_dir = lower_dir_dentry->d_inode;
+	BUG_ON(!S_ISDIR(lower_dir->i_mode));
+
+	err = -ENOMEM;
+	name = __getname();
+	if (unlikely(!name))
+		goto out;
+	strcpy(name, UNIONFS_WHPFX);
+	p = name + UNIONFS_WHLEN;
+
+	err = 0;
+	for (i = 0; !err && i < namelist->size; i++) {
+		list_for_each(pos, &namelist->list[i]) {
+			cursor =
+				list_entry(pos, struct filldir_node,
+					   file_list);
+			/* Only operate on whiteouts in this branch. */
+			if (cursor->bindex != bindex)
+				continue;
+			if (!cursor->whiteout)
+				continue;
+
+			strlcpy(p, cursor->name, PATH_MAX - UNIONFS_WHLEN);
+			lower_dentry =
+				lookup_one_len(name, lower_dir_dentry,
+					       cursor->namelen +
+					       UNIONFS_WHLEN);
+			if (IS_ERR(lower_dentry)) {
+				err = PTR_ERR(lower_dentry);
+				break;
+			}
+			if (lower_dentry->d_inode)
+				err = vfs_unlink(lower_dir, lower_dentry);
+			dput(lower_dentry);
+			if (err)
+				break;
+		}
+	}
+
+	__putname(name);
+
+	/* After all of the removals, we should copy the attributes once. */
+	fsstack_copy_attr_times(dentry->d_inode, lower_dir_dentry->d_inode);
+
+out:
+	return err;
+}
+
+
+void __delete_whiteouts(struct work_struct *work)
+{
+	struct sioq_args *args = container_of(work, struct sioq_args, work);
+	struct deletewh_args *d = &args->deletewh;
+
+	args->err = do_delete_whiteouts(d->dentry, d->bindex, d->namelist);
+	complete(&args->comp);
+}
+
+/* delete whiteouts in a dir (for rmdir operation) using sioq if necessary */
+int delete_whiteouts(struct dentry *dentry, int bindex,
+		     struct unionfs_dir_state *namelist)
+{
+	int err;
+	struct super_block *sb;
+	struct dentry *lower_dir_dentry;
+	struct inode *lower_dir;
+	struct sioq_args args;
+
+	sb = dentry->d_sb;
+
+	BUG_ON(!S_ISDIR(dentry->d_inode->i_mode));
+	BUG_ON(bindex < dbstart(dentry));
+	BUG_ON(bindex > dbend(dentry));
+	err = is_robranch_super(sb, bindex);
+	if (err)
+		goto out;
+
+	lower_dir_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+	BUG_ON(!S_ISDIR(lower_dir_dentry->d_inode->i_mode));
+	lower_dir = lower_dir_dentry->d_inode;
+	BUG_ON(!S_ISDIR(lower_dir->i_mode));
+
+	if (!permission(lower_dir, MAY_WRITE | MAY_EXEC, NULL)) {
+		err = do_delete_whiteouts(dentry, bindex, namelist);
+	} else {
+		args.deletewh.namelist = namelist;
+		args.deletewh.dentry = dentry;
+		args.deletewh.bindex = bindex;
+		run_sioq(__delete_whiteouts, &args);
+		err = args.err;
+	}
+
+out:
+	return err;
+}
+
+/****************************************************************************
+ * Opaque directory helpers                                                 *
+ ****************************************************************************/
+
+/*
+ * is_opaque_dir: returns 0 if it is NOT an opaque dir, 1 if it is, and
+ * -errno if an error occurred trying to figure this out.
+ */
+int is_opaque_dir(struct dentry *dentry, int bindex)
+{
+	int err = 0;
+	struct dentry *lower_dentry;
+	struct dentry *wh_lower_dentry;
+	struct inode *lower_inode;
+	struct sioq_args args;
+
+	lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+	lower_inode = lower_dentry->d_inode;
+
+	BUG_ON(!S_ISDIR(lower_inode->i_mode));
+
+	mutex_lock(&lower_inode->i_mutex);
+
+	if (!permission(lower_inode, MAY_EXEC, NULL)) {
+		wh_lower_dentry =
+			lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
+				       sizeof(UNIONFS_DIR_OPAQUE) - 1);
+	} else {
+		args.is_opaque.dentry = lower_dentry;
+		run_sioq(__is_opaque_dir, &args);
+		wh_lower_dentry = args.ret;
+	}
+
+	mutex_unlock(&lower_inode->i_mutex);
+
+	if (IS_ERR(wh_lower_dentry)) {
+		err = PTR_ERR(wh_lower_dentry);
+		goto out;
+	}
+
+	/* This is an opaque dir iff wh_lower_dentry is positive */
+	err = !!wh_lower_dentry->d_inode;
+
+	dput(wh_lower_dentry);
+out:
+	return err;
+}
+
+void __is_opaque_dir(struct work_struct *work)
+{
+	struct sioq_args *args = container_of(work, struct sioq_args, work);
+
+	args->ret = lookup_one_len(UNIONFS_DIR_OPAQUE, args->is_opaque.dentry,
+				   sizeof(UNIONFS_DIR_OPAQUE) - 1);
+	complete(&args->comp);
+}
+
+int make_dir_opaque(struct dentry *dentry, int bindex)
+{
+	int err = 0;
+	struct dentry *lower_dentry, *diropq;
+	struct inode *lower_dir;
+	struct nameidata nd;
+	kernel_cap_t orig_cap;
+
+	/*
+	 * Opaque directory whiteout markers are special files (like regular
+	 * whiteouts), and should appear to the users as if they don't
+	 * exist.  They should be created/deleted regardless of directory
+	 * search/create permissions, but only for the duration of this
+	 * creation of the .wh.__dir_opaque: file.  Note, this does not
+	 * circumvent normal ->permission).
+	 */
+	orig_cap = current->cap_effective;
+	cap_raise(current->cap_effective, CAP_DAC_READ_SEARCH);
+	cap_raise(current->cap_effective, CAP_DAC_OVERRIDE);
+
+	lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
+	lower_dir = lower_dentry->d_inode;
+	BUG_ON(!S_ISDIR(dentry->d_inode->i_mode) ||
+	       !S_ISDIR(lower_dir->i_mode));
+
+	mutex_lock(&lower_dir->i_mutex);
+	diropq = lookup_one_len(UNIONFS_DIR_OPAQUE, lower_dentry,
+				sizeof(UNIONFS_DIR_OPAQUE) - 1);
+	if (IS_ERR(diropq)) {
+		err = PTR_ERR(diropq);
+		goto out;
+	}
+
+	err = init_lower_nd(&nd, LOOKUP_CREATE);
+	if (unlikely(err < 0))
+		goto out;
+	if (!diropq->d_inode)
+		err = vfs_create(lower_dir, diropq, S_IRUGO, &nd);
+	if (!err)
+		dbopaque(dentry) = bindex;
+	release_lower_nd(&nd, err);
+
+	dput(diropq);
+
+out:
+	mutex_unlock(&lower_dir->i_mutex);
+	current->cap_effective = orig_cap;
+	return err;
+}
-- 
1.5.2.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ