/*
 * Copyright (c) 2003-2005 Erez Zadok
 * Copyright (c) 2003-2005 Charles P. Wright
 * Copyright (c) 2005      Arun M. Krishnakumar
 * Copyright (c) 2005      David P. Quigley
 * Copyright (c) 2003-2004 Mohammad Nayyer Zubair
 * Copyright (c) 2003-2003 Puja Gupta
 * Copyright (c) 2003-2003 Harikesavan Krishnan
 * Copyright (c) 2003-2005 Stony Brook University
 * Copyright (c) 2003-2005 The Research Foundation of State University of New York
 *
 * For specific licensing information, see the COPYING file distributed with
 * this package.
 *
 * This Copyright notice must be kept intact and distributed with all sources.
 */
/*
 *  $Id: rename.c,v 1.21 2005/10/14 18:43:15 arunmk Exp $
 */

#include "fist.h"
#include "unionfs.h"

static int do_rename(struct inode *old_dir, struct dentry *old_dentry,
		     struct inode *new_dir, struct dentry *new_dentry,
		     int bindex)
{
	int err = 0;
	struct dentry *hidden_old_dentry;
	struct dentry *hidden_new_dentry;
	struct dentry *hidden_old_dir_dentry;
	struct dentry *hidden_new_dir_dentry;
	struct dentry *hidden_wh_dentry;
	struct dentry *hidden_wh_dir_dentry;
	char *wh_name = NULL;

	print_entry(" bindex=%d", bindex);

	fist_print_dentry("IN: do_rename, old_dentry", old_dentry);
	fist_print_dentry("IN: do_rename, new_dentry", new_dentry);
	fist_dprint(7, "do_rename for bindex = %d\n", bindex);

	hidden_new_dentry = dtohd_index(new_dentry, bindex);
	hidden_old_dentry = dtohd_index(old_dentry, bindex);
	PASSERT(hidden_old_dentry);

	if (!hidden_new_dentry) {
		hidden_new_dentry =
		    create_parents(new_dentry->d_parent->d_inode, new_dentry,
				   bindex);
		if (IS_ERR(hidden_new_dentry)) {
			fist_dprint(7,
				    "error creating directory tree for rename, bindex = %d\n",
				    bindex);
			err = PTR_ERR(hidden_new_dentry);
			goto out;
		}
	}

	wh_name = KMALLOC(new_dentry->d_name.len + 5, GFP_UNIONFS);
	if (!wh_name) {
		err = -ENOMEM;
		goto out;
	}
	strcpy(wh_name, ".wh.");
	strncat(wh_name, new_dentry->d_name.name, new_dentry->d_name.len);
	wh_name[4 + new_dentry->d_name.len] = '\0';

	hidden_wh_dentry =
	    LOOKUP_ONE_LEN(wh_name, hidden_new_dentry->d_parent,
			   new_dentry->d_name.len + 4);
	if (IS_ERR(hidden_wh_dentry)) {
		err = PTR_ERR(hidden_wh_dentry);
		goto out;
	}

	if (hidden_wh_dentry->d_inode) {
		/* get rid of the whiteout that is existing */
		if (hidden_new_dentry->d_inode) {
			printk(KERN_WARNING
			       "Both a whiteout and a dentry exist when doing a rename!\n");
			err = -EIO;

			DPUT(hidden_wh_dentry);
			goto out;
		}

		hidden_wh_dir_dentry = lock_parent(hidden_wh_dentry);
		if (!(err = is_robranch_super(old_dentry->d_sb, bindex))) {
			err =
			    vfs_unlink(hidden_wh_dir_dentry->d_inode,
				       hidden_wh_dentry);
		}
		DPUT(hidden_wh_dentry);
		unlock_dir(hidden_wh_dir_dentry);
		if (err) {
			goto out;
		}
	} else {
		DPUT(hidden_wh_dentry);
	}

	DGET(hidden_old_dentry);
	hidden_old_dir_dentry = GET_PARENT(hidden_old_dentry);
	hidden_new_dir_dentry = GET_PARENT(hidden_new_dentry);

	double_lock(hidden_old_dir_dentry, hidden_new_dir_dentry);

	if (!(err = is_robranch_super(old_dentry->d_sb, bindex))) {
		PASSERT(hidden_old_dir_dentry->d_inode);
		PASSERT(hidden_old_dentry);
		PASSERT(hidden_old_dentry->d_inode);
		PASSERT(hidden_new_dir_dentry->d_inode);
		PASSERT(hidden_new_dentry);
		fist_print_dentry("NEWBEF", new_dentry);
		fist_print_dentry("OLDBEF", old_dentry);
		err =
		    vfs_rename(hidden_old_dir_dentry->d_inode,
			       hidden_old_dentry,
			       hidden_new_dir_dentry->d_inode,
			       hidden_new_dentry);
		fist_print_dentry("NEWAFT", new_dentry);
		fist_print_dentry("OLDAFT", old_dentry);
	}

	double_unlock(hidden_old_dir_dentry, hidden_new_dir_dentry);
	DPUT(hidden_old_dentry);

      out:
	if (!err) {
		/* Fixup the newdentry. */
		if (bindex < dbstart(new_dentry))
			set_dbstart(new_dentry, bindex);
		else if (bindex > dbend(new_dentry))
			set_dbend(new_dentry, bindex);
	}

	KFREE(wh_name);

	fist_print_dentry("OUT: do_rename, old_dentry", old_dentry);
	fist_print_dentry("OUT: do_rename, new_dentry", new_dentry);

	print_exit_status(err);
	return err;
}

static int unionfs_rename_whiteout(struct inode *old_dir,
				   struct dentry *old_dentry,
				   struct inode *new_dir,
				   struct dentry *new_dentry)
{
	int err = 0;
	int bindex;
	int old_bstart, old_bend;
	int new_bstart, new_bend;
	int do_copyup = -1;
	struct dentry *parent_dentry = NULL;
	int local_err = 0;
	int eio = 0;
	int revert = 0;

	print_entry_location();

	old_bstart = dbstart(old_dentry);
	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 = do_rename(old_dir, old_dentry, new_dir, new_dentry, old_bstart);
	if (err) {
		if (!IS_COPYUP_ERR(err)) {
			goto out;
		}
		do_copyup = old_bstart - 1;
	} else {
		revert = 1;
	}

	/* Unlink all instances of destination that exist to the left of
	 * bstart of source. On error, revert back, goto out.
	 */
	for (bindex = old_bstart - 1; bindex >= new_bstart; bindex--) {
		struct dentry *unlink_dentry;
		struct dentry *unlink_dir_dentry;

		unlink_dentry = dtohd_index(new_dentry, bindex);
		if (!unlink_dentry) {
			continue;
		}

		unlink_dir_dentry = lock_parent(unlink_dentry);
		if (!(err = is_robranch_super(old_dir->i_sb, bindex))) {
			err =
			    vfs_unlink(unlink_dir_dentry->d_inode,
				       unlink_dentry);
		}

		fist_copy_attr_times(new_dentry->d_parent->d_inode,
				     unlink_dir_dentry->d_inode);
		/* propagate number of hard-links */
		new_dentry->d_parent->d_inode->i_nlink =
		    get_nlinks(new_dentry->d_parent->d_inode);

		unlock_dir(unlink_dir_dentry);
		if (!err) {
			if (bindex != new_bstart) {
				DPUT(unlink_dentry);
				set_dtohd_index(new_dentry, bindex, NULL);
			}
		} else if (IS_COPYUP_ERR(err)) {
			do_copyup = bindex - 1;
		} else if (revert) {
			goto revert;
		}
	}

	if (do_copyup != -1) {
		for (bindex = do_copyup; bindex >= 0; bindex--) {
			/* copyup the file into some left directory, so that you can rename it */
			err =
			    copyup_dentry(old_dentry->d_parent->d_inode,
					  old_dentry, old_bstart, bindex, NULL,
					  old_dentry->d_inode->i_size);
			if (!err) {
				parent_dentry = old_dentry->d_parent;
				err =
				    do_rename(old_dir, old_dentry, new_dir,
					      new_dentry, bindex);
			}
		}
	}

	/* Create whiteout for source, only if:
	 * (1) There is more than one underlying instance of source.
	 * (2) We did a copy_up
	 */
	if ((old_bstart != old_bend) || (do_copyup != -1)) {
		int start = (do_copyup == -1) ? old_bstart : do_copyup;
		/* we want to create a whiteout for name in  this parent dentry */
		local_err = create_whiteout(old_dentry, start);
		if (local_err) {
			/* We can't fix anything now, so we cop-out and use -EIO. */
			printk
			    ("<0>We can't create a whiteout for the source in rename!\n");
			err = -EIO;
			goto out;
		}
	}

      out:

	print_exit_status(err);
	return err;

      revert:
	/* Do revert here. */
	local_err = unionfs_refresh_hidden_dentry(new_dentry, old_bstart);
	if (local_err) {
		printk(KERN_WARNING
		       "Revert failed in rename: the new refresh failed.\n");
		eio = -EIO;
	}

	local_err = unionfs_refresh_hidden_dentry(old_dentry, old_bstart);
	if (local_err) {
		printk(KERN_WARNING
		       "Revert failed in rename: the old refresh failed.\n");
		eio = -EIO;
		goto revert_out;
	}

	if (!dtohd_index(new_dentry, bindex)
	    || !dtohd_index(new_dentry, bindex)->d_inode) {
		printk(KERN_WARNING
		       "Revert failed in rename: the object disappeared from under us!\n");
		eio = -EIO;
		goto revert_out;
	}

	if (dtohd_index(old_dentry, bindex)
	    && dtohd_index(old_dentry, bindex)->d_inode) {
		printk(KERN_WARNING
		       "Revert failed in rename: the object was created underneath us!\n");
		eio = -EIO;
		goto revert_out;
	}

	local_err =
	    do_rename(new_dir, new_dentry, old_dir, old_dentry, old_bstart);

	/* If we can't fix it, then we cop-out with -EIO. */
	if (local_err) {
		printk(KERN_WARNING "Revert failed in rename!\n");
		eio = -EIO;
	}

	local_err = unionfs_refresh_hidden_dentry(new_dentry, bindex);
	if (local_err)
		eio = -EIO;
	local_err = unionfs_refresh_hidden_dentry(old_dentry, bindex);
	if (local_err)
		eio = -EIO;

      revert_out:
	if (eio)
		err = eio;
	print_exit_status(err);
	return err;
}

/*
 * The function is nasty, nasty, nasty, but so is rename. :(
 *
 * This psuedo-code describes what the function should do.  Essentially we move
 * from right-to-left, renaming each item.  We skip the leftmost destination
 * (this is so we can always undo the rename if the reverts work), and then the
 * very last thing we do is fix up the leftmost destination (either through
 * renaming it or unlinking it).
 *
 * for i = S_R downto S_L
 *	if (i != L_D && exists(S[i]))
 *		err = rename(S[i], D[i])
 *		if (err == COPYUP)  {
 *			do_whiteout = i - 1;
 *			if (i == S_R) {
 *				do_copyup = i - 1
 *			}
 *		}
 *		else if (err)
 *			goto revert;
 *		else
 *			ok = i;
 *
 *
 * If we get to the leftmost source (S_L) and it is EROFS, we should do copyup
 *
 * if (err == COPYUP) {
 * 	do_copyup = i - 1;
 * }
 *
 * while (i > new_bstart) {
 * 	err = unlink(D[i]);
 *	if (err == COPYUP) {
 *		do_copyup = i - 1;
 *	} else if (err) {
 *		goto revert;
 *	}
 * }
 *
 * if (exists(S[L_D])) {
 *	err = rename(S[L_D], D[L_D]);
 *	if (err = COPYUP) {
 *		if (ok > L_D) {
 *			do_copyup = min(do_copyup, L_D - 1);
 *		}
 *		do_whiteout = min(do_whiteout, L_D - 1);
 *	} else {
 *		goto revert;
 *	}
 * } else {
 *	err = unlink(D[L_D]);
 *	if (err = COPYUP) {
 *		do_copyup = min(do_copyup, L_D - 1);
 *	} else {
 *		goto revert;
 *	}
 * }
 *
 * out:
 *	if (do_whiteout != -1) {
 *		create_whiteout(do_whiteout);
 *	}
 *	if (do_copyup != -1) {
 *		copyup(source to do_copyup)
 *		rename source to destination
 *	}
 *	return err;
 * out_revert:
 *	do the reverting;
 *	return err;
 * }
 */
static int unionfs_rename_all(struct inode *old_dir, struct dentry *old_dentry,
			      struct inode *new_dir, struct dentry *new_dentry)
{
	int old_bstart, old_bend;
	int new_bstart, new_bend;
	struct dentry *parent_dentry = NULL;
	int bindex;
	int err = 0;
	int eio = 0;		/* Used for revert. */
	int isdir;

	/* These variables control error handling. */
	int rename_ok = FD_SETSIZE;	/* The last rename that is ok. */
	int do_copyup = -1;	/* Where we should start copyup. */
	int do_whiteout = -1;	/* Where we should start whiteouts of the source. */
	int clobber;		/* Are we clobbering the destination. */
	fd_set success_mask;
	char *name = NULL;

	print_entry_location();

	old_bstart = dbstart(old_dentry);
	old_bend = dbend(old_dentry);

	parent_dentry = old_dentry->d_parent;
	name = KMALLOC(old_dentry->d_name.len + 1, GFP_UNIONFS);
	strncpy(name, old_dentry->d_name.name, old_dentry->d_name.len + 1);

	new_bstart = dbstart(new_dentry);
	new_bend = dbend(new_dentry);
	ASSERT(new_bstart >= 0);
	ASSERT(old_bstart >= 0);

	/* The failure mask only can deal with FD_SETSIZE entries. */
	ASSERT(old_bend <= FD_SETSIZE);
	ASSERT(new_bend <= FD_SETSIZE);
	FD_ZERO(&success_mask);

	/* Life is simpler if the dentry doesn't exist. */
	clobber = (dtohd_index(new_dentry, new_bstart)->d_inode) ? 1 : 0;
	isdir = S_ISDIR(old_dentry->d_inode->i_mode);

	/* Loop through all the branches from right to left and rename all
	 * instances of old dentry to new dentry, except if they are
	 */
	for (bindex = old_bend; bindex >= old_bstart; bindex--) {
		/* We don't rename if there is no source. */
		if (dtohd_index(old_dentry, bindex) == NULL)
			continue;

		/* we rename the bstart of destination only at the last of
		 * all operations, so that we don't lose it on error
		 */
		if (clobber && (bindex == new_bstart))
			continue;

		/* We shouldn't have a handle on this if there is no inode. */
		PASSERT(dtohd_index(old_dentry, bindex)->d_inode);

		err =
		    do_rename(old_dir, old_dentry, new_dir, new_dentry, bindex);
		if (!err) {
			/* For reverting. */
			FD_SET(bindex, &success_mask);
			/* So we know not to copyup on failures the right */
			rename_ok = bindex;
		} else if (IS_COPYUP_ERR(err)) {
			if (isdir) {
				err = -EXDEV;
				goto revert;
			}
			do_whiteout = bindex - 1;
			if (bindex == old_bstart)
				do_copyup = bindex - 1;
		} else {
			goto revert;
		}
	}

	while (bindex > new_bstart) {
		struct dentry *unlink_dentry;
		struct dentry *unlink_dir_dentry;

		unlink_dentry = dtohd_index(new_dentry, bindex);
		if (!unlink_dentry) {
			bindex--;
			continue;
		}

		unlink_dir_dentry = lock_parent(unlink_dentry);
		if (!(err = is_robranch_super(old_dir->i_sb, bindex)))
			err = vfs_unlink(unlink_dir_dentry->d_inode,
					 unlink_dentry);

		fist_copy_attr_times(new_dentry->d_parent->d_inode,
				     unlink_dir_dentry->d_inode);
		new_dentry->d_parent->d_inode->i_nlink =
		    get_nlinks(new_dentry->d_parent->d_inode);

		unlock_dir(unlink_dir_dentry);

		if (!err) {
			if (bindex != new_bstart) {
				DPUT(unlink_dentry);
				set_dtohd_index(new_dentry, bindex, NULL);
			}
		}

		if (IS_COPYUP_ERR(err)) {
			if (isdir) {
				err = -EXDEV;
				goto revert;
			}
			do_copyup = bindex - 1;
		} else if (err) {
			goto revert;
		}

		bindex--;
	}			// while bindex

	/* Now we need to handle the leftmost of the destination. */
	if (clobber && dtohd_index(old_dentry, new_bstart)) {
		err =
		    do_rename(old_dir, old_dentry, new_dir, new_dentry,
			      new_bstart);
		if (IS_COPYUP_ERR(err)) {
			if (isdir) {
				err = -EXDEV;
				goto revert;
			}
			if (rename_ok > new_bstart) {
				if ((do_copyup == -1)
				    || (new_bstart - 1 < do_copyup))
					do_copyup = new_bstart - 1;
			}
			if ((do_whiteout == -1)
			    || (new_bstart - 1 < do_whiteout)) {
				do_whiteout = new_bstart - 1;
			}
		} else if (err) {
			goto revert;
		}
	} else if (clobber && (new_bstart < old_bstart)) {
		struct dentry *unlink_dentry;
		struct dentry *unlink_dir_dentry;

		unlink_dentry = dtohd_index(new_dentry, new_bstart);
		PASSERT(unlink_dentry);
		PASSERT(unlink_dentry->d_inode);

		unlink_dir_dentry = lock_parent(unlink_dentry);
		if (!(err = is_robranch_super(old_dir->i_sb, new_bstart)))
			err = vfs_unlink(unlink_dir_dentry->d_inode,
					 unlink_dentry);

		fist_copy_attr_times(new_dentry->d_parent->d_inode,
				     unlink_dir_dentry->d_inode);
		new_dentry->d_parent->d_inode->i_nlink =
		    get_nlinks(new_dentry->d_parent->d_inode);

		unlock_dir(unlink_dir_dentry);

		if (IS_COPYUP_ERR(err)) {
			if (isdir) {
				err = -EXDEV;
				goto revert;
			}
			if ((do_copyup == -1) || (new_bstart - 1 < do_copyup)) {
				do_copyup = new_bstart - 1;
			}
		} else if (err) {
			goto revert;
		}
	}

	/* Create a whiteout for the source. */
	if (do_whiteout != -1) {
		ASSERT(do_whiteout >= 0);
		/* create a lookup in the old_dentry's actual parent */
		lock_dentry(parent_dentry);
		err = create_whiteout_parent(parent_dentry, name, do_whiteout);
		unlock_dentry(parent_dentry);
		if (err) {
			/* We can't fix anything now, so we -EIO. */
			printk(KERN_WARNING "We can't create a whiteout for the"
			       "source in rename!\n");
			err = -EIO;
			goto out;
		}
	}

	if (do_copyup != -1) {
		/* We can't copyup a directory, because it may involve huge
		 * numbers of children, etc.  Doing that in the kernel would
		 * be ungood.
		 */

		if (S_ISDIR(old_dentry->d_inode->i_mode)) {
			err = -EXDEV;
			goto out;
		}

		for (bindex = do_copyup; bindex >= 0; bindex--) {
			err =
			    copyup_dentry(old_dentry->d_parent->d_inode,
					  old_dentry, old_bstart, bindex, NULL,
					  old_dentry->d_inode->i_size);
			if (!err)
				err =
				    do_rename(old_dir, old_dentry, new_dir,
					      new_dentry, bindex);
		}
	}

	/* We are at the point where reverting doesn't happen. */
	goto out;

      revert:
	for (bindex = old_bstart; bindex <= old_bend; bindex++) {
		int local_err;

		if (FD_ISSET(bindex, &success_mask)) {
			local_err =
			    unionfs_refresh_hidden_dentry(new_dentry, bindex);
			if (local_err) {
				printk(KERN_WARNING "Revert failed in rename: "
				       "the new refresh failed.\n");
				eio = -EIO;
			}

			local_err =
			    unionfs_refresh_hidden_dentry(old_dentry, bindex);
			if (local_err) {
				printk(KERN_WARNING "Revert failed in rename: "
				       "the old refresh failed.\n");
				eio = -EIO;
				continue;
			}

			if (!dtohd_index(new_dentry, bindex)
			    || !dtohd_index(new_dentry, bindex)->d_inode) {
				printk(KERN_WARNING "Revert failed in rename: "
				       "the object disappeared from under us!\n");
				eio = -EIO;
				continue;
			}

			if (dtohd_index(old_dentry, bindex)
			    && dtohd_index(old_dentry, bindex)->d_inode) {
				printk(KERN_WARNING "Revert failed in rename: "
				       "the object was created underneath us!\n");
				eio = -EIO;
				continue;
			}

			local_err =
			    do_rename(new_dir, new_dentry, old_dir, old_dentry,
				      bindex);

			/* If we can't fix it, then we cop-out with -EIO. */
			if (local_err) {
				printk(KERN_WARNING
				       "Revert failed in rename!\n");
				eio = -EIO;
			}
			local_err =
			    unionfs_refresh_hidden_dentry(new_dentry, bindex);
			if (local_err)
				eio = -EIO;
			local_err =
			    unionfs_refresh_hidden_dentry(old_dentry, bindex);
			if (local_err)
				eio = -EIO;
		}
	}
	if (eio)
		err = eio;

      out:
	KFREE(name);
	print_exit_status(err);
	return err;
}

/*
 * generate whiteout name, which is NOT terminated by NULL.
 * @name: original d_name.name
 * @len: original d_name.len
 * @whlen: length of whname
 * return value is the whname.
 * correctly returned value as whname must be freed later.
 * If an error occurs, returns PTR_ERR.
 */
static inline char *get_whname(const char *name, int len, int *whlen)
{
	char *ret;

	ASSERT(name && len && whlen);
#if 0
	/* TODO: check the full path length with PATH_MAX */
	if (len > PAGE_SIZE - sizeof(".wh."))
		return ERR_PTR(-ENAMETOOLONG);
#endif
	ret = __getname();
	if (!ret)
		return ERR_PTR(-ENOMEM);

	memcpy(ret, ".wh.", sizeof(".wh.") - 1);
	memcpy(ret + sizeof(".wh.") - 1, name, len);
	*whlen = len + sizeof(".wh.") - 1;

	return ret;
}

static inline void put_whname(char *whname)
{
	__putname(whname);
}

static struct dentry *lookup_whiteout(struct dentry *dentry)
{
	char *whname;
	int whlen = 0, bindex = -1, bstart = -1, bend = -1;
	struct dentry *parent, *hidden_parent, *wh_dentry;

	whname = get_whname(dentry->d_name.name, dentry->d_name.len, &whlen);
	if (IS_ERR(whname))
		return (void *)whname;

	parent = GET_PARENT(dentry);
	lock_dentry(parent);
	bstart = dbstart(parent);
	bend = dbend(parent);
	wh_dentry = ERR_PTR(-ENOENT);
	for (bindex = bstart; bindex <= bend; bindex++) {
		hidden_parent = dtohd_index(parent, bindex);
		wh_dentry = LOOKUP_ONE_LEN(whname, hidden_parent, whlen);
		if (IS_ERR(wh_dentry))
			continue;
		if (wh_dentry->d_inode)
			break;
		DPUT(wh_dentry);
		wh_dentry = ERR_PTR(-ENOENT);
	}
	unlock_dentry(parent);
	DPUT(parent);
	put_whname(whname);
	return wh_dentry;
}

int unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
		   struct inode *new_dir, struct dentry *new_dentry)
{
	int err = 0;
	struct dentry *hidden_old_dentry;
	struct dentry *hidden_new_dentry;
	struct dentry *wh_dentry;
	int new_was_wh = 0;

	print_entry_location();

	double_lock_dentry(old_dentry, new_dentry);

	fist_checkinode(old_dir, "unionfs_rename-old_dir");
	fist_checkinode(new_dir, "unionfs_rename-new_dir");
	fist_print_dentry("IN: unionfs_rename, old_dentry", old_dentry);
	fist_print_dentry("IN: unionfs_rename, new_dentry", new_dentry);

	err = unionfs_partial_lookup(old_dentry);
	if (err)
		goto out;
	err = unionfs_partial_lookup(new_dentry);
	if (err)
		goto out;

	hidden_new_dentry = dtohd(new_dentry);
	hidden_old_dentry = dtohd(old_dentry);

	/*
	 * if new_dentry is already hidden because of whiteout,
	 * simply override it even if the whiteouted dir is not empty.
	 */
	wh_dentry = lookup_whiteout(new_dentry);
	if (!IS_ERR(wh_dentry)) {
		new_was_wh = 1;
		DPUT(wh_dentry);
	} else if (new_dentry->d_inode) {
		if (S_ISDIR(old_dentry->d_inode->i_mode) !=
		    S_ISDIR(new_dentry->d_inode->i_mode)) {
			err =
			    S_ISDIR(old_dentry->d_inode->
				    i_mode) ? -ENOTDIR : -EISDIR;
			goto out;
		}

		if (S_ISDIR(old_dentry->d_inode->i_mode)) {
			/* check if this unionfs directory is empty or not */
			err = check_empty(new_dentry, NULL);
			if (err)
				goto out;
			/* Handle the case where we are overwriting directories
			 * that are not really empty because of whiteout or
			 * non-whiteout entries.
			 */
		}
	}

	if (IS_SET(old_dir->i_sb, DELETE_WHITEOUT)) {
		/* create whiteout */
		err = unionfs_rename_whiteout(old_dir, old_dentry, new_dir,
					      new_dentry);
	} else {
		/* delete all. */
		err = unionfs_rename_all(old_dir, old_dentry, new_dir,
					 new_dentry);
	}

      out:
	fist_checkinode(new_dir, "post unionfs_rename-new_dir");
	fist_print_dentry("OUT: unionfs_rename, old_dentry", old_dentry);

	if (err) {
		/* clear the new_dentry stuff created */
		d_drop(new_dentry);
	} else
		fist_print_dentry("OUT: unionfs_rename, new_dentry",
				  new_dentry);

	unlock_dentry(new_dentry);
	unlock_dentry(old_dentry);
	print_exit_status(err);
	return err;
}

/*
 *
 * vim:shiftwidth=8
 * vim:tabstop=8
 *
 * For Emacs:
 * Local variables:
 * c-basic-offset: 8
 * c-comment-only-line-offset: 0
 * c-offsets-alist: ((statement-block-intro . +) (knr-argdecl-intro . 0)
 *              (substatement-open . 0) (label . 0) (statement-cont . +))
 * indent-tabs-mode: t
 * tab-width: 8
 * End:
 */
