/*
 * 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: inode.c,v 1.237 2005/10/19 20:17:01 cwright Exp $
 */

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

#include <asm/errno.h>

static int unionfs_create(struct inode *parent, struct dentry *dentry,
			  int mode, struct nameidata *nd)
{
	int err = 0;
	struct dentry *hidden_dentry = NULL;
	struct dentry *whiteout_dentry = NULL;
	struct dentry *new_hidden_dentry;
	struct dentry *hidden_parent_dentry = NULL;
	int bindex = 0, bstart;
	char *name = NULL;

	print_entry_location();
	lock_dentry(dentry);
	fist_print_dentry("IN unionfs_create", dentry);

	/* We start out in the leftmost branch. */
	bstart = dbstart(dentry);
	hidden_dentry = dtohd(dentry);

	/* check if whiteout exists in this branch, i.e. lookup .wh.foo first */
	name = KMALLOC(dentry->d_name.len + sizeof(".wh."), GFP_UNIONFS);
	if (!name) {
		err = -ENOMEM;
		goto out;
	}
	strcpy(name, ".wh.");
	strncat(name, dentry->d_name.name, dentry->d_name.len);
	name[4 + dentry->d_name.len] = '\0';

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 4);
	if (IS_ERR(whiteout_dentry)) {
		err = PTR_ERR(whiteout_dentry);
		whiteout_dentry = NULL;
		goto out;
	}
	PASSERT(whiteout_dentry);

	if (whiteout_dentry->d_inode) {
		/* .wh.foo has been found. */
		/* First truncate it and then rename it to foo (hence having
		 * the same overall effect as a normal create.
		 *
		 * XXX: This is not strictly correct.  If we have unlinked the
		 * file and it still has a reference count, then we should
		 * actually unlink the whiteout so that user's data isn't
		 * hosed over.
		 */
		struct dentry *hidden_dir_dentry;
		struct iattr newattrs;

		down(&whiteout_dentry->d_inode->i_sem);
		newattrs.ia_valid = ATTR_CTIME | ATTR_MODE;
		newattrs.ia_mode = mode;
		if (whiteout_dentry->d_inode->i_size != 0) {
			newattrs.ia_valid |= ATTR_SIZE;
			newattrs.ia_size = 0;
		}
		err = notify_change(whiteout_dentry, &newattrs);
		up(&whiteout_dentry->d_inode->i_sem);

		new_hidden_dentry = dtohd(dentry);
		DGET(new_hidden_dentry);

		/* They are in the same directory, but we need to get it twice. */
		hidden_dir_dentry = GET_PARENT(whiteout_dentry);
		GET_PARENT(whiteout_dentry);
		double_lock(hidden_dir_dentry, hidden_dir_dentry);

		if (!(err = is_robranch_super(dentry->d_sb, bstart))) {
			err =
			    vfs_rename(hidden_dir_dentry->d_inode,
				       whiteout_dentry,
				       hidden_dir_dentry->d_inode,
				       new_hidden_dentry);
		}
		if (!err) {
			fist_copy_attr_timesizes(parent,
						 new_hidden_dentry->d_parent->
						 d_inode);
			parent->i_nlink = get_nlinks(parent);
		}

		/* This will dput our double gotten parent. */
		double_unlock(hidden_dir_dentry, hidden_dir_dentry);

		DPUT(new_hidden_dentry);

		if (err) {
			/* exit if the error returned was NOT -EROFS */
			if (!IS_COPYUP_ERR(err))
				goto out;
			/* We were not able to create the file in this branch,
			 * so, we try to create it in one branch to left
			 */
			bstart--;
		} else {
			/* reset the unionfs dentry to point to the .wh.foo entry. */

			/* Discard any old reference. */
			PASSERT(dtohd(dentry));
			DPUT(dtohd(dentry));

			/* Trade one reference to another. */
			set_dtohd_index(dentry, bstart, whiteout_dentry);
			whiteout_dentry = NULL;

			err = unionfs_interpose(dentry, parent->i_sb, 0);
			goto out;
		}
	}

	for (bindex = bstart; bindex >= 0; bindex--) {
		hidden_dentry = dtohd_index(dentry, bindex);
		if (!hidden_dentry) {
			/* if hidden_dentry is NULL, create the entire
			 * dentry directory structure in branch 'bindex'.
			 * hidden_dentry will NOT be null when bindex == bstart
			 * because lookup passed as a negative unionfs dentry
			 * pointing to a lone negative underlying dentry */
			hidden_dentry = create_parents(parent, dentry, bindex);
			if (!hidden_dentry || IS_ERR(hidden_dentry)) {
				if (IS_ERR(hidden_dentry))
					err = PTR_ERR(hidden_dentry);
				continue;
			}
		}

		PASSERT(hidden_dentry);
		fist_checkinode(parent, "unionfs_create");

		hidden_parent_dentry = lock_parent(hidden_dentry);
		if (IS_ERR(hidden_parent_dentry)) {
			err = PTR_ERR(hidden_parent_dentry);
			goto out;
		}
		/* We shouldn't create things in a read-only branch. */
		if (!(err = is_robranch_super(dentry->d_sb, bindex))) {
			//DQ: vfs_create has a different prototype in 2.6
			err = vfs_create(hidden_parent_dentry->d_inode,
					 hidden_dentry, mode, nd);
		}
		if (err || !hidden_dentry->d_inode) {
			unlock_dir(hidden_parent_dentry);

			/* break out of for loop if the error wasn't  -EROFS */
			if (!IS_COPYUP_ERR(err))
				break;
		} else {
			err = unionfs_interpose(dentry, parent->i_sb, 0);
			if (!err) {
				fist_copy_attr_timesizes(parent,
							 hidden_parent_dentry->
							 d_inode);
				/* update number of links on parent directory */
				parent->i_nlink = get_nlinks(parent);
			}
			unlock_dir(hidden_parent_dentry);
			break;
		}
	}

      out:
	DPUT(whiteout_dentry);
	KFREE(name);

	fist_print_dentry("OUT unionfs_create :", dentry);
	unlock_dentry(dentry);
	print_exit_status(err);
	return err;
}

struct dentry *unionfs_lookup(struct inode *parent, struct dentry *dentry,
			      struct nameidata *nd)
{
	/* The locking is done by unionfs_lookup_backend. */
	return unionfs_lookup_backend(dentry, INTERPOSE_LOOKUP);
}

static int unionfs_link(struct dentry *old_dentry, struct inode *dir,
			struct dentry *new_dentry)
{
	int err = 0;
	struct dentry *hidden_old_dentry = NULL;
	struct dentry *hidden_new_dentry = NULL;
	struct dentry *hidden_dir_dentry = NULL;
	struct dentry *whiteout_dentry;
	char *name = NULL;

	print_entry_location();
	double_lock_dentry(new_dentry, old_dentry);

	hidden_new_dentry = dtohd(new_dentry);

	/* check if whiteout exists in the branch of new dentry, i.e. lookup
	 * .wh.foo first. If present, delete it */
	name = KMALLOC(new_dentry->d_name.len + sizeof(".wh."), GFP_UNIONFS);
	if (!name) {
		err = -ENOMEM;
		goto out;
	}
	strcpy(name, ".wh.");
	strncat(name, new_dentry->d_name.name, new_dentry->d_name.len);
	name[4 + new_dentry->d_name.len] = '\0';

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

	PASSERT(whiteout_dentry);

	if (!whiteout_dentry->d_inode) {
		DPUT(whiteout_dentry);
		whiteout_dentry = NULL;
	} else {
		/* found a .wh.foo entry, unlink it and then call vfs_link() */
		hidden_dir_dentry = lock_parent(whiteout_dentry);
		if (!
		    (err =
		     is_robranch_super(new_dentry->d_sb,
				       dbstart(new_dentry)))) {
			err =
			    vfs_unlink(hidden_dir_dentry->d_inode,
				       whiteout_dentry);
		}
		fist_copy_attr_times(dir, hidden_dir_dentry->d_inode);
		dir->i_nlink = get_nlinks(dir);
		unlock_dir(hidden_dir_dentry);
		hidden_dir_dentry = NULL;
		DPUT(whiteout_dentry);
		if (err)
			goto out;
	}

	if (dbstart(old_dentry) != dbstart(new_dentry)) {
		hidden_new_dentry =
		    create_parents(dir, new_dentry, dbstart(old_dentry));
		err = PTR_ERR(hidden_new_dentry);
		if (IS_COPYUP_ERR(err))
			goto docopyup;
		if (!hidden_new_dentry || IS_ERR(hidden_new_dentry))
			goto out;
	}
	hidden_new_dentry = dtohd(new_dentry);
	hidden_old_dentry = dtohd(old_dentry);

	ASSERT(dbstart(old_dentry) == dbstart(new_dentry));
	hidden_dir_dentry = lock_parent(hidden_new_dentry);
	if (!(err = is_robranch(old_dentry)))
		err =
		    vfs_link(hidden_old_dentry, hidden_dir_dentry->d_inode,
			     hidden_new_dentry);
	unlock_dir(hidden_dir_dentry);

      docopyup:
	if (IS_COPYUP_ERR(err)) {
		int old_bstart = dbstart(old_dentry);
		int bindex;

		for (bindex = old_bstart - 1; 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) {
				hidden_new_dentry =
				    create_parents(dir, new_dentry, bindex);
				hidden_old_dentry = dtohd(old_dentry);
				hidden_dir_dentry =
				    lock_parent(hidden_new_dentry);
				/* do vfs_link */
				err =
				    vfs_link(hidden_old_dentry,
					     hidden_dir_dentry->d_inode,
					     hidden_new_dentry);
				unlock_dir(hidden_dir_dentry);
				goto check_link;
			}
		}
		goto out;
	}
      check_link:
	if (err || !hidden_new_dentry->d_inode)
		goto out;

	/* Its a hard link, so use the same inode */
	new_dentry->d_inode = igrab(old_dentry->d_inode);
	PASSERT(new_dentry->d_inode);
	d_instantiate(new_dentry, new_dentry->d_inode);
	fist_copy_attr_all(dir, hidden_new_dentry->d_parent->d_inode);
	/* propagate number of hard-links */
	old_dentry->d_inode->i_nlink = get_nlinks(old_dentry->d_inode);

      out:
	if (!new_dentry->d_inode)
		d_drop(new_dentry);

	KFREE(name);

	unlock_dentry(new_dentry);
	unlock_dentry(old_dentry);

	print_exit_status(err);
	return err;
}

static int unionfs_symlink(struct inode *dir, struct dentry *dentry,
			   const char *symname)
{
	int err = 0;
	struct dentry *hidden_dentry = NULL;
	struct dentry *whiteout_dentry = NULL;
	struct dentry *hidden_dir_dentry = NULL;
	umode_t mode;
	int bindex = 0, bstart;
	char *name = NULL;

	print_entry_location();
	lock_dentry(dentry);
	fist_print_dentry("IN unionfs_symlink", dentry);

	/* We start out in the leftmost branch. */
	bstart = dbstart(dentry);

	hidden_dentry = dtohd(dentry);

	/* check if whiteout exists in this branch, i.e. lookup .wh.foo first. If present, delete it */
	name = KMALLOC(dentry->d_name.len + sizeof(".wh."), GFP_UNIONFS);
	if (!name) {
		err = -ENOMEM;
		goto out;
	}
	strcpy(name, ".wh.");
	strncat(name, dentry->d_name.name, dentry->d_name.len);
	name[4 + dentry->d_name.len] = '\0';

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 4);
	if (IS_ERR(whiteout_dentry)) {
		err = PTR_ERR(whiteout_dentry);
		goto out;
	}

	PASSERT(whiteout_dentry);
	if (!whiteout_dentry->d_inode) {
		DPUT(whiteout_dentry);
		whiteout_dentry = NULL;
	} else {
		/* found a .wh.foo entry, unlink it and then call vfs_symlink() */
		hidden_dir_dentry = lock_parent(whiteout_dentry);

		fist_print_generic_dentry("HDD", hidden_dir_dentry);
		fist_print_generic_dentry("WD", whiteout_dentry);

		if (!(err = is_robranch_super(dentry->d_sb, bstart))) {
			err =
			    vfs_unlink(hidden_dir_dentry->d_inode,
				       whiteout_dentry);
		}
		DPUT(whiteout_dentry);

		fist_copy_attr_times(dir, hidden_dir_dentry->d_inode);
		/* propagate number of hard-links */
		dir->i_nlink = get_nlinks(dir);

		unlock_dir(hidden_dir_dentry);

		if (err) {
			/* exit if the error returned was NOT -EROFS */
			if (!IS_COPYUP_ERR(err))
				goto out;
			/* should now try to create symlink in the another branch */
			bstart--;
		}
	}

	/* deleted whiteout if it was present, now do a normal vfs_symlink() with
	   possible recursive directory creation */
	for (bindex = bstart; bindex >= 0; bindex--) {
		hidden_dentry = dtohd_index(dentry, bindex);
		if (!hidden_dentry) {
			/* if hidden_dentry is NULL, create the entire
			 * dentry directory structure in branch 'bindex'. hidden_dentry will NOT be null when
			 * bindex == bstart because lookup passed as a negative unionfs dentry pointing to a
			 * lone negative underlying dentry */
			hidden_dentry = create_parents(dir, dentry, bindex);
			if (!hidden_dentry || IS_ERR(hidden_dentry)) {
				if (IS_ERR(hidden_dentry)) {
					err = PTR_ERR(hidden_dentry);
				}
				fist_dprint(8,
					    "hidden dentry NULL (or error) for bindex = %d\n",
					    bindex);
				continue;
			}
		}

		PASSERT(hidden_dentry);

		hidden_dir_dentry = lock_parent(hidden_dentry);

		if (!(err = is_robranch_super(dentry->d_sb, bindex))) {
			mode = S_IALLUGO;
			err =
			    vfs_symlink(hidden_dir_dentry->d_inode,
					hidden_dentry, symname, mode);
		}
		unlock_dir(hidden_dir_dentry);

		if (err || !hidden_dentry->d_inode) {
			/* break out of for loop if error returned was NOT -EROFS */
			if (!IS_COPYUP_ERR(err))
				break;
		} else {
			err = unionfs_interpose(dentry, dir->i_sb, 0);
			if (!err) {
				fist_copy_attr_timesizes(dir,
							 hidden_dir_dentry->
							 d_inode);
				/* update number of links on parent directory */
				dir->i_nlink = get_nlinks(dir);
			}
			break;
		}
	}

      out:
	if (!dentry->d_inode)
		d_drop(dentry);

	KFREE(name);
	fist_print_dentry("OUT unionfs_symlink :", dentry);
	unlock_dentry(dentry);
	print_exit_status(err);
	return err;
}

static int unionfs_mkdir(struct inode *parent, struct dentry *dentry, int mode)
{
	int err = 0;
	struct dentry *hidden_dentry = NULL, *whiteout_dentry = NULL;
	struct dentry *hidden_parent_dentry = NULL;
	int bindex = 0, bstart;
	char *name = NULL;
	int whiteout_unlinked = 0;
	uid_t saved_uid = current->fsuid;
	gid_t saved_gid = current->fsgid;

	print_entry_location();
	lock_dentry(dentry);
	fist_print_dentry("IN unionfs_mkdir", dentry);
	bstart = dbstart(dentry);

	hidden_dentry = dtohd(dentry);

	// check if whiteout exists in this branch, i.e. lookup .wh.foo first
	name = KMALLOC(dentry->d_name.len + sizeof(".wh."), GFP_UNIONFS);
	if (!name) {
		err = -ENOMEM;
		goto out;
	}
	strcpy(name, ".wh.");
	strncat(name, dentry->d_name.name, dentry->d_name.len);
	name[4 + dentry->d_name.len] = '\0';

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 4);
	if (IS_ERR(whiteout_dentry)) {
		err = PTR_ERR(whiteout_dentry);
		goto out;
	}
	PASSERT(whiteout_dentry);

	if (!whiteout_dentry->d_inode) {
		DPUT(whiteout_dentry);
		whiteout_dentry = NULL;
	} else {
		hidden_parent_dentry = lock_parent(whiteout_dentry);

		/* Set the uid and gid to trick the fs into allowing us to create
		 * the file */
		current->fsuid = hidden_parent_dentry->d_inode->i_uid;
		current->fsgid = hidden_parent_dentry->d_inode->i_gid;
		//found a.wh.foo entry, remove it then do vfs_mkdir
		if (!(err = is_robranch_super(dentry->d_sb, bstart))) {
			err =
			    vfs_unlink(hidden_parent_dentry->d_inode,
				       whiteout_dentry);
		}
		DPUT(whiteout_dentry);

		current->fsuid = saved_uid;
		current->fsgid = saved_gid;

		unlock_dir(hidden_parent_dentry);

		if (err) {
			/* exit if the error returned was NOT -EROFS */
			if (!IS_COPYUP_ERR(err))
				goto out;
			bstart--;
		} else {
			whiteout_unlinked = 1;
		}
	}

	for (bindex = bstart; bindex >= 0; bindex--) {
		hidden_dentry = dtohd_index(dentry, bindex);
		if (!hidden_dentry) {
			hidden_dentry = create_parents(parent, dentry, bindex);
			if (!hidden_dentry || IS_ERR(hidden_dentry)) {
				fist_dprint(8,
					    "hidden dentry NULL for bindex = %d\n",
					    bindex);
				continue;
			}
		}

		PASSERT(hidden_dentry);

		hidden_parent_dentry = lock_parent(hidden_dentry);
		if (IS_ERR(hidden_parent_dentry)) {
			err = PTR_ERR(hidden_parent_dentry);
			goto out;
		}
		if (!(err = is_robranch_super(dentry->d_sb, bindex))) {
			err =
			    vfs_mkdir(hidden_parent_dentry->d_inode,
				      hidden_dentry, mode);
		}
		unlock_dir(hidden_parent_dentry);

		/* XXX this could potentially return a negative hidden_dentry! */
		if (err || !hidden_dentry->d_inode) {
			/* break out of for loop if error returned was NOT -EROFS */
			if (!IS_COPYUP_ERR(err))
				break;
		} else {
			int i;
			int bend = dbend(dentry);
			for (i = bindex + 1; i < bend; i++) {
				if (dtohd_index(dentry, i)) {
					DPUT(dtohd_index(dentry, i));
					set_dtohd_index(dentry, i, NULL);
				}
			}
			bend = bindex;
			set_dbend(dentry, bend);

			err = unionfs_interpose(dentry, parent->i_sb, 0);
			if (!err) {
				fist_copy_attr_timesizes(parent,
							 hidden_parent_dentry->
							 d_inode);
				/* update number of links on parent directory */
				parent->i_nlink = get_nlinks(parent);
			}
			whiteout_dentry = LOOKUP_ONE_LEN(UNIONFS_DIR_OPAQUE,
							 hidden_dentry,
							 sizeof
							 (UNIONFS_DIR_OPAQUE) -
							 1);
			if (IS_ERR(whiteout_dentry)) {
				err = PTR_ERR(whiteout_dentry);
				goto out;
			}
			down(&hidden_dentry->d_inode->i_sem);
			err = vfs_create(hidden_dentry->d_inode,
					 whiteout_dentry, 0600, NULL);
			up(&hidden_dentry->d_inode->i_sem);
			DPUT(whiteout_dentry);

			if (err) {
				fist_dprint(8,
					    "mkdir: error creating directory override entry: %d\n",
					    err);
				goto out;
			}
			break;
		}
	}

      out:
	if (!dentry->d_inode)
		d_drop(dentry);

	KFREE(name);

	fist_print_dentry("OUT unionfs_mkdir :", dentry);
	unlock_dentry(dentry);
	print_exit_status(err);
	return err;
}

static int unionfs_mknod(struct inode *dir, struct dentry *dentry, int mode,
			 dev_t dev)
{
	int err = 0;
	struct dentry *hidden_dentry = NULL, *whiteout_dentry = NULL;
	struct dentry *hidden_parent_dentry = NULL;
	int bindex = 0, bstart;
	char *name = NULL;
	int whiteout_unlinked = 0;

	print_entry_location();
	lock_dentry(dentry);
	fist_print_dentry("IN unionfs_mknod", dentry);
	bstart = dbstart(dentry);

	hidden_dentry = dtohd(dentry);

	// check if whiteout exists in this branch, i.e. lookup .wh.foo first
	name = KMALLOC(dentry->d_name.len + sizeof(".wh."), GFP_UNIONFS);
	if (!name) {
		err = -ENOMEM;
		goto out;
	}
	strcpy(name, ".wh.");
	strncat(name, dentry->d_name.name, dentry->d_name.len);
	name[4 + dentry->d_name.len] = '\0';

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 4);
	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 .wh.foo, unlink it */
		hidden_parent_dentry = lock_parent(whiteout_dentry);

		//found a.wh.foo entry, remove it then do vfs_mkdir
		if (!(err = is_robranch_super(dentry->d_sb, bstart)))
			err = vfs_unlink(hidden_parent_dentry->d_inode,
					 whiteout_dentry);
		DPUT(whiteout_dentry);

		unlock_dir(hidden_parent_dentry);

		if (err) {
			if (!IS_COPYUP_ERR(err))
				goto out;

			bstart--;
		} else {
			whiteout_unlinked = 1;
		}
	}

	for (bindex = bstart; bindex >= 0; bindex--) {
		hidden_dentry = dtohd_index(dentry, bindex);
		if (!hidden_dentry) {
			hidden_dentry = create_parents(dir, dentry, bindex);
			if (!hidden_dentry || IS_ERR(hidden_dentry)) {
				fist_dprint(8,
					    "hidden dentry NULL for bindex = %d\n",
					    bindex);
				continue;
			}
		}

		PASSERT(hidden_dentry);

		hidden_parent_dentry = lock_parent(hidden_dentry);
		if (IS_ERR(hidden_parent_dentry)) {
			err = PTR_ERR(hidden_parent_dentry);
			goto out;
		}
		if (!(err = is_robranch_super(dentry->d_sb, bindex))) {
			err = vfs_mknod(hidden_parent_dentry->d_inode,
					hidden_dentry, mode, dev);
		}
		/* XXX this could potentially return a negative hidden_dentry! */
		if (err || !hidden_dentry->d_inode) {
			unlock_dir(hidden_parent_dentry);
			/* break out of for, if error was NOT -EROFS */
			if (!IS_COPYUP_ERR(err))
				break;
		} else {
			err = unionfs_interpose(dentry, dir->i_sb, 0);
			if (!err) {
				fist_copy_attr_timesizes(dir,
							 hidden_parent_dentry->
							 d_inode);
				/* update number of links on parent directory */
				dir->i_nlink = get_nlinks(dir);
			}
			unlock_dir(hidden_parent_dentry);

			break;
		}
	}

      out:
	if (!dentry->d_inode)
		d_drop(dentry);

	if (name) {
		KFREE(name);
	}

	fist_print_dentry("OUT unionfs_mknod :", dentry);
	unlock_dentry(dentry);
	print_exit_status(err);
	return err;
}

static int unionfs_readlink(struct dentry *dentry, char *buf, int bufsiz)
{
	int err;
	struct dentry *hidden_dentry;

	print_entry_location();
	lock_dentry(dentry);
	hidden_dentry = dtohd(dentry);
	fist_print_dentry("unionfs_readlink IN", dentry);

	if (!hidden_dentry->d_inode->i_op ||
	    !hidden_dentry->d_inode->i_op->readlink) {
		err = -EINVAL;
		goto out;
	}

	err = hidden_dentry->d_inode->i_op->readlink(hidden_dentry,
						     buf, bufsiz);
	if (err > 0)
		fist_copy_attr_atime(dentry->d_inode, hidden_dentry->d_inode);

      out:
	unlock_dentry(dentry);
	print_exit_status(err);
	return err;
}

/* We don't lock the dentry here, because readlink does the heavy lifting. */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
static int unionfs_follow_link(struct dentry *dentry, struct nameidata *nd)
#else
static void *unionfs_follow_link(struct dentry *dentry, struct nameidata *nd)
#endif
{
	char *buf;
	int len = PAGE_SIZE, err;
	mm_segment_t old_fs;

	print_entry_location();

	/* This is freed by the put_link method assuming a successful call. */
	buf = KMALLOC(len, GFP_UNIONFS);
	if (!buf) {
		err = -ENOMEM;
		goto out;
	}

	/* 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, buf, len);
	set_fs(old_fs);
	if (err < 0) {
		KFREE(buf);
		buf = NULL;
		goto out;
	}
	buf[err] = 0;
	nd_set_link(nd, buf);
	err = 0;

      out:
	print_exit_status(err);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
	return err;
#else
	return ERR_PTR(err);
#endif
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13)
void unionfs_put_link(struct dentry *dentry, struct nameidata *nd)
#else
void unionfs_put_link(struct dentry *dentry, struct nameidata *nd, void *cookie)
#endif
{
	char *link;
	print_entry_location();
	link = nd_get_link(nd);
	KFREE(link);
	print_exit_location();
}

/* Basically copied from the kernel vfs permission(), but we've changed
 * the following: (1) the IS_RDONLY check is skipped, and (2) if you define
 * -DNFS_SECURITY_HOLE, we assume that -EACCESS means that the export is
 * read-only and we should check standard Unix permissions.  This means
 * that NFS ACL checks (or other advanced permission features) are bypassed.
 */
int inode_permission(struct inode *inode, int mask, struct nameidata *nd,
		     int bindex)
{
	int retval, submask;

	if (mask & MAY_WRITE) {
		/* The first branch is allowed to be really readonly. */
		if (bindex == 0) {
			umode_t mode = inode->i_mode;
			if (IS_RDONLY(inode) && (S_ISREG(mode) || S_ISDIR(mode)
						 || S_ISLNK(mode)))
				return -EROFS;
		}
		/*
		 * Nobody gets write access to an immutable file.
		 */
		if (IS_IMMUTABLE(inode))
			return -EACCES;
	}

	/* Ordinary permission routines do not understand MAY_APPEND. */
	submask = mask & ~MAY_APPEND;
	if (inode->i_op && inode->i_op->permission) {
		retval = inode->i_op->permission(inode, submask, nd);
#ifdef NFS_SECURITY_HOLE
#define IS_NFS(inode) (strcmp("nfs", (inode)->i_sb->s_type->s_name))
		if ((retval == -EACCESS) && (submask & MAY_WRITE) &&
		    IS_NFS(inode)) {
			//CHRI retval = generic_permission(inode, submask, NULL);
			retval = vfs_permission(inode, submask);
		}
#endif
	} else {
		//CHRI retval = generic_permission(inode, submask, NULL);
		retval = vfs_permission(inode, submask);
	}
	if (retval)
		return retval;

	return security_inode_permission(inode, mask, nd);
}

static int unionfs_permission(struct inode *inode, int mask,
			      struct nameidata *nd)
{
	struct inode *hidden_inode = NULL;
	int err = 0;
	int bindex, bstart, bend;
	int is_file = 0;

	print_entry_location();

	bstart = ibstart(inode);
	bend = ibend(inode);

	fist_print_inode("IN unionfs_permission", inode);

	/* set if check is for file */
	if (!S_ISDIR(inode->i_mode))
		is_file = 1;

	for (bindex = bstart; bindex <= bend; bindex++) {
		hidden_inode = itohi_index(inode, bindex);
		if (!hidden_inode)
			continue;

		/* check the condition for D-F-D underlying files/directories,
		 * we dont have to check for files, if we are checking for
		 * directories.
		 */
		if (!S_ISDIR(hidden_inode->i_mode) && (!is_file))
			continue;
		/* We use our own special version of permission, such that 
		 * only the first branch returns -EROFS. */
		err = inode_permission(hidden_inode, mask, nd, bindex);
		/* The permissions are an intersection of the overall directory
		 * permissions, so we fail if one fails. */
		if (err)
			goto out;
		/* only the leftmost file matters. */
		if (is_file)
			break;
	}

      out:
	print_exit_status(err);
	return err;
}

static int unionfs_setattr(struct dentry *dentry, struct iattr *ia)
{
	int err = 0;
	struct dentry *hidden_dentry;
	struct inode *inode = NULL;
	struct inode *hidden_inode = NULL;
	int bstart, bend, bindex;
	int i;
	int copyup = 0;
	int locked = 0;

	print_entry_location();
	lock_dentry(dentry);
	bstart = dbstart(dentry);
	bend = dbend(dentry);
	inode = dentry->d_inode;

	for (bindex = bstart; (bindex <= bend) || (bindex == bstart); bindex++) {
		hidden_dentry = dtohd_index(dentry, bindex);
		if (!hidden_dentry)
			continue;
		ASSERT(hidden_dentry->d_inode != NULL);

		/* If the file is on a read only branch */
		if (is_robranch_super(dentry->d_sb, bindex)
		    || IS_RDONLY(hidden_dentry->d_inode)) {
			if (copyup || (bindex != bstart))
				continue;
			/* Only if its the leftmost file, copyup the file */
			for (i = bstart - 1; i >= 0; i--) {
				size_t size = dentry->d_inode->i_size;
				if (ia->ia_valid & ATTR_SIZE)
					size = ia->ia_size;
				err = copyup_dentry(dentry->d_parent->d_inode,
						    dentry, bstart, i, NULL,
						    size);

				if (!err) {
					copyup = 1;
					hidden_dentry = dtohd(dentry);
					break;
				}
				/* if error is in the leftmost f/s, pass it up */
				if (i == 0)
					goto out;
			}

		}
		err = notify_change(hidden_dentry, ia);
		if (err)
			goto out;
		break;
	}

	/* get the size from the first hidden inode */
	hidden_inode = itohi(dentry->d_inode);
	fist_checkinode(inode, "unionfs_setattr");
	fist_copy_attr_all(inode, hidden_inode);

      out:
	if (locked)
		up(&dentry->d_inode->i_sem);
	unlock_dentry(dentry);
	fist_checkinode(inode, "post unionfs_setattr");
	print_exit_status(err);
	return err;
}

struct inode_operations unionfs_symlink_iops = {
	.readlink = unionfs_readlink,
	.permission = unionfs_permission,
	.follow_link = unionfs_follow_link,
	.setattr = unionfs_setattr,
	.put_link = unionfs_put_link,
};

struct inode_operations unionfs_dir_iops = {
	.create = unionfs_create,
	.lookup = unionfs_lookup,
	.link = unionfs_link,
	.unlink = unionfs_unlink,
	.symlink = unionfs_symlink,
	.mkdir = unionfs_mkdir,
	.rmdir = unionfs_rmdir,
	.mknod = unionfs_mknod,
	.rename = unionfs_rename,
	.permission = unionfs_permission,
	.setattr = unionfs_setattr,
// If you have problems with these lines, try defining FIST_SETXATTR_CONSTVOID
#ifdef UNIONFS_XATTR
	.setxattr = unionfs_setxattr,
	.getxattr = unionfs_getxattr,
	.removexattr = unionfs_removexattr,
	.listxattr = unionfs_listxattr,
#endif				/* UNIONFS_XATTR */
};

struct inode_operations unionfs_main_iops = {
	.permission = unionfs_permission,
	.setattr = unionfs_setattr,
// If you have problems with these lines, try defining FIST_SETXATTR_CONSTVOID
# if defined(UNIONFS_XATTR)
	.setxattr = unionfs_setxattr,
	.getxattr = unionfs_getxattr,
	.removexattr = unionfs_removexattr,
	.listxattr = unionfs_listxattr,
# endif
};

/*
 *
 * 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:
 */
