Exemplo n.º 1
0
/* The rest of these are utility functions for lookup. */
static int is_opaque_dir(struct dentry *dentry, int bindex)
{
	int err = 0;
	struct dentry *hidden_dentry;
	struct dentry *wh_hidden_dentry;
	struct inode *hidden_inode;
	uid_t saved_uid = current->fsuid;
	gid_t saved_gid = current->fsgid;

	print_entry_location();

	hidden_dentry = dtohd_index(dentry, bindex);
	hidden_inode = hidden_dentry->d_inode;

	BUG_ON(!S_ISDIR(hidden_inode->i_mode));

	current->fsuid = hidden_inode->i_uid;
	current->fsgid = hidden_inode->i_gid;
	wh_hidden_dentry = LOOKUP_ONE_LEN(UNIONFS_DIR_OPAQUE, hidden_dentry,
					  sizeof(UNIONFS_DIR_OPAQUE) - 1);
	current->fsuid = saved_uid;
	current->fsgid = saved_gid;
	if (IS_ERR(wh_hidden_dentry)) {
		err = PTR_ERR(wh_hidden_dentry);
		fist_dprint(1, "LOOKUP_ONE_LEN returned: %d\n", err);
		goto out;
	}
	if (wh_hidden_dentry->d_inode)
		err = 1;
	DPUT(wh_hidden_dentry);
      out:
	print_exit_status(err);
	return err;
}
Exemplo n.º 2
0
static struct dentry *lookup_whiteout(struct dentry *dentry)
{
	char *whname;
	int bindex = -1, bstart = -1, bend = -1;
	struct dentry *parent, *hidden_parent, *wh_dentry;

	whname = alloc_whname(dentry->d_name.name, dentry->d_name.len);
	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);
		if (!hidden_parent)
			continue;
		wh_dentry =
		    LOOKUP_ONE_LEN(whname, hidden_parent,
				   dentry->d_name.len + 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);
	KFREE(whname);
	return wh_dentry;
}
Exemplo n.º 3
0
/* This function replicates the directory structure upto given dentry
 * in the bindex branch.  */
struct dentry *create_parents_named(struct inode *dir, struct dentry *dentry,
				    const char *name, int bindex)
{
	int err;
	struct dentry *child_dentry;
	struct dentry *parent_dentry;
	struct dentry *hidden_parent_dentry = NULL;
	struct dentry *hidden_dentry = NULL;
	const char *childname;
	unsigned int childnamelen;

	int old_kmalloc_size;
	int kmalloc_size;
	int num_dentry;
	int count;

	int old_bstart;
	int old_bend;
	struct dentry **path = NULL;
	struct dentry **tmp_path;

	print_entry_location();

	verify_locked(dentry);

	/* There is no sense allocating any less than the minimum. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
	kmalloc_size = malloc_sizes[0].cs_size;
#else
	kmalloc_size = 32;
#endif
	num_dentry = kmalloc_size / sizeof(struct dentry *);

	if ((err = is_robranch_super(dir->i_sb, bindex))) {
		hidden_dentry = ERR_PTR(err);
		goto out;
	}

	fist_print_dentry("IN: create_parents_named", dentry);
	fist_dprint(8, "name = %s\n", name);

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

	path = (struct dentry **)KMALLOC(kmalloc_size, GFP_KERNEL);
	memset(path, 0, kmalloc_size);

	/* assume the negative dentry of unionfs as the parent dentry */
	parent_dentry = dentry;

	count = 0;
	/* This loop finds the first parent that exists in the given branch.
	 * We start building the directory structure from there.  At the end
	 * of the loop, the following should hold:
	 *      child_dentry is the first nonexistent child
	 *      parent_dentry is the first existent parent
	 *      path[0] is the = deepest child
	 *      path[count] is the first child to create
	 */
	do {
		child_dentry = parent_dentry;

		/* find the parent directory dentry in unionfs */
		parent_dentry = child_dentry->d_parent;
		lock_dentry(parent_dentry);

		/* find out the hidden_parent_dentry in the given branch */
		hidden_parent_dentry = dtohd_index(parent_dentry, bindex);

		/* store the child dentry */
		path[count++] = child_dentry;
		if (count == num_dentry) {
			old_kmalloc_size = kmalloc_size;
			kmalloc_size *= 2;
			num_dentry = kmalloc_size / sizeof(struct dentry *);

			tmp_path =
			    (struct dentry **)KMALLOC(kmalloc_size, GFP_KERNEL);
			if (!tmp_path) {
				hidden_dentry = ERR_PTR(-ENOMEM);
				goto out;
			}
			memset(tmp_path, 0, kmalloc_size);
			memcpy(tmp_path, path, old_kmalloc_size);
			KFREE(path);
			path = tmp_path;
			tmp_path = NULL;
		}

	} while (!hidden_parent_dentry);
	count--;

	/* This is basically while(child_dentry != dentry).  This loop is
	 * horrible to follow and should be replaced with cleaner code. */
	while (1) {
		PASSERT(child_dentry);
		PASSERT(parent_dentry);
		PASSERT(parent_dentry->d_inode);

		// get hidden parent dir in the current branch
		hidden_parent_dentry = dtohd_index(parent_dentry, bindex);
		unlock_dentry(parent_dentry);
		PASSERT(hidden_parent_dentry);
		PASSERT(hidden_parent_dentry->d_inode);

		// init the values to lookup
		childname = child_dentry->d_name.name;
		childnamelen = child_dentry->d_name.len;

		if (child_dentry != dentry) {
			// lookup child in the underlying file system
			hidden_dentry =
			    LOOKUP_ONE_LEN(childname, hidden_parent_dentry,
					   childnamelen);
			if (IS_ERR(hidden_dentry))
				goto out;
		} else {
			int loop_start;
			int loop_end;
			int new_bstart = -1;
			int new_bend = -1;
			int i;

			/* is the name a whiteout of the childname ? */
			//lookup the whiteout child in the underlying file system
			hidden_dentry =
			    LOOKUP_ONE_LEN(name, hidden_parent_dentry,
					   strlen(name));
			if (IS_ERR(hidden_dentry))
				goto out;

			/* Replace the current dentry (if any) with the new one. */
			DPUT(dtohd_index(dentry, bindex));
			set_dtohd_index(dentry, bindex, hidden_dentry);

			loop_start =
			    (old_bstart < bindex) ? old_bstart : bindex;
			loop_end = (old_bend > bindex) ? old_bend : bindex;

			/* This loop sets the bstart and bend for the new
			 * dentry by traversing from left to right.
			 * It also dputs all negative dentries except
			 * bindex (the newly looked dentry
			 */
			for (i = loop_start; i <= loop_end; i++) {
				if (!dtohd_index(dentry, i))
					continue;

				if (i == bindex) {
					new_bend = i;
					if (new_bstart < 0)
						new_bstart = i;
					continue;
				}

				if (!dtohd_index(dentry, i)->d_inode) {
					DPUT(dtohd_index(dentry, i));
					set_dtohd_index(dentry, i, NULL);
				} else {
					if (new_bstart < 0)
						new_bstart = i;
					new_bend = i;
				}
			}

			if (new_bstart < 0)
				new_bstart = bindex;
			if (new_bend < 0)
				new_bend = bindex;
			set_dbstart(dentry, new_bstart);
			set_dbend(dentry, new_bend);
			break;
		}

		if (hidden_dentry->d_inode) {
			/* since this already exists we dput to avoid
			 * multiple references on the same dentry */
			DPUT(hidden_dentry);
		} else {
			uid_t saved_uid = current->fsuid;
			gid_t saved_gid = current->fsgid;

			/* its a negative dentry, create a new dir */
			hidden_parent_dentry = lock_parent(hidden_dentry);
			current->fsuid = hidden_parent_dentry->d_inode->i_uid;
			current->fsgid = hidden_parent_dentry->d_inode->i_gid;
			err = vfs_mkdir(hidden_parent_dentry->d_inode,
					hidden_dentry, S_IRWXUGO);
			current->fsuid = saved_uid;
			current->fsgid = saved_gid;
			unlock_dir(hidden_parent_dentry);
			if (err || !hidden_dentry->d_inode) {
				DPUT(hidden_dentry);
				hidden_dentry = ERR_PTR(err);
				goto out;
			}
			err =
			    copyup_permissions(dir->i_sb, child_dentry,
					       hidden_dentry);
			if (err) {
				DPUT(hidden_dentry);
				hidden_dentry = ERR_PTR(err);
				goto out;
			}
			set_itohi_index(child_dentry->d_inode, bindex,
					igrab(hidden_dentry->d_inode));
			if (ibstart(child_dentry->d_inode) > bindex)
				ibstart(child_dentry->d_inode) = bindex;
			if (ibend(child_dentry->d_inode) < bindex)
				ibend(child_dentry->d_inode) = bindex;

			set_dtohd_index(child_dentry, bindex, hidden_dentry);
			if (dbstart(child_dentry) > bindex)
				set_dbstart(child_dentry, bindex);
			if (dbend(child_dentry) < bindex)
				set_dbend(child_dentry, bindex);
		}

		parent_dentry = child_dentry;
		child_dentry = path[--count];
	}
      out:
	KFREE(path);
	fist_print_dentry("OUT: create_parents_named", dentry);
	print_exit_pointer(hidden_dentry);
	return hidden_dentry;
}
Exemplo n.º 4
0
static int unionfs_unlink_whiteout(struct inode *dir, struct dentry *dentry)
{
	int err = 0;
	struct dentry *hidden_old_dentry;
	struct dentry *hidden_wh_dentry = NULL;
	struct dentry *hidden_old_dir_dentry, *hidden_new_dir_dentry;
	char *name = NULL;
	struct iattr newattrs;

	print_entry_location();
	fist_print_dentry("IN unionfs_unlink_whiteout: ", dentry);

	/* create whiteout, get the leftmost underlying dentry and rename it */
	hidden_old_dentry = dtohd(dentry);

	/* lookup .wh.foo first, MUST NOT EXIST */
	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';

	hidden_wh_dentry =
	    LOOKUP_ONE_LEN(name, hidden_old_dentry->d_parent,
			   dentry->d_name.len + 4);
	if (IS_ERR(hidden_wh_dentry)) {
		err = PTR_ERR(hidden_wh_dentry);
		goto out;
	}
	ASSERT(hidden_wh_dentry->d_inode == NULL);

	DGET(hidden_old_dentry);

	hidden_old_dir_dentry = GET_PARENT(hidden_old_dentry);
	hidden_new_dir_dentry = GET_PARENT(hidden_wh_dentry);
	double_lock(hidden_old_dir_dentry, hidden_new_dir_dentry);

	if (!(err = is_robranch(dentry))) {
		if (S_ISREG(hidden_old_dentry->d_inode->i_mode)) {
			err = vfs_rename(hidden_old_dir_dentry->d_inode,
					 hidden_old_dentry,
					 hidden_new_dir_dentry->d_inode,
					 hidden_wh_dentry);
		} else {
			err = vfs_unlink(hidden_old_dir_dentry->d_inode,
					 hidden_old_dentry);
			if (!err)
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
				err = vfs_create(hidden_new_dir_dentry->d_inode,
						 hidden_wh_dentry, 0666, NULL);
#else
				err = vfs_create(hidden_new_dir_dentry->d_inode,
						 hidden_wh_dentry, 0666);
#endif
		}
	}

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

	if (!err) {
		hidden_wh_dentry = LOOKUP_ONE_LEN(name,
						  hidden_old_dentry->d_parent,
						  dentry->d_name.len + 4);
		if (IS_ERR(hidden_wh_dentry)) {
			err = PTR_ERR(hidden_wh_dentry);
			goto out;
		}
		PASSERT(hidden_wh_dentry->d_inode);
		down(&hidden_wh_dentry->d_inode->i_sem);
		newattrs.ia_valid = ATTR_CTIME;
		if (hidden_wh_dentry->d_inode->i_size != 0) {
			newattrs.ia_valid |= ATTR_SIZE;
			newattrs.ia_size = 0;
		}
		/* We discard this error, because the entry is whited out
		 * even if we fail here. */
		notify_change(hidden_wh_dentry, &newattrs);
		up(&hidden_wh_dentry->d_inode->i_sem);
		DPUT(hidden_wh_dentry);
	}

	if (err) {
		if (dbstart(dentry) == 0)
			goto out;
		/* exit if the error returned was NOT -EROFS */
		if (!IS_COPYUP_ERR(err))
			goto out;
		err = create_whiteout(dentry, dbstart(dentry) - 1);
	} else {
		fist_copy_attr_all(dir, hidden_new_dir_dentry->d_inode);
	}

      out:
	KFREE(name);
	print_exit_status(err);
	return err;
}
Exemplo n.º 5
0
static int do_rename(struct inode *old_dir, struct dentry *old_dentry,
		     struct inode *new_dir, struct dentry *new_dentry,
		     int bindex, struct dentry **wh_old)
{
	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);

	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 = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len);
	if (IS_ERR(wh_name)) {
		err = PTR_ERR(wh_name);
		goto out;
	}

	hidden_wh_dentry =
	    LOOKUP_ONE_LEN(wh_name, hidden_new_dentry->d_parent,
			   new_dentry->d_name.len + WHLEN);
	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);

	lock_rename(hidden_old_dir_dentry, hidden_new_dir_dentry);

	err = is_robranch_super(old_dentry->d_sb, bindex);
	if (err)
		goto out_unlock;

	/* 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 (IS_ERR(whname))
			goto out_unlock;
		*wh_old = LOOKUP_ONE_LEN(whname, hidden_old_dir_dentry,
					 old_dentry->d_name.len + WHLEN);
		KFREE(whname);
		err = PTR_ERR(*wh_old);
		if (IS_ERR(*wh_old)) {
			*wh_old = NULL;
			goto out_unlock;
		}
	}

		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);

      out_unlock:
	unlock_rename(hidden_old_dir_dentry, hidden_new_dir_dentry);

	DPUT(hidden_old_dir_dentry);
	DPUT(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;
}
Exemplo n.º 6
0
struct dentry *unionfs_lookup_backend(struct dentry *dentry, int lookupmode)
{
	int err = 0;
	struct dentry *hidden_dentry = NULL;
	struct dentry *wh_hidden_dentry = NULL;
	struct dentry *hidden_dir_dentry = NULL;
	struct dentry *parent_dentry = NULL;
	int bindex, bstart, bend, bopaque;
	int dentry_count = 0;	/* Number of positive dentries. */
	int first_dentry_offset = -1;
	struct dentry *first_hidden_dentry = NULL;
	int locked_parent = 0;
	int locked_child = 0;

	int opaque;
	char *whname = NULL;
	const char *name;
	int namelen;

	print_entry("mode = %d", lookupmode);

	/* We should already have a lock on this dentry in the case of a
	 * partial lookup, or a revalidation. Otherwise it is returned from
	 * new_dentry_private_data already locked.  */
	if (lookupmode == INTERPOSE_PARTIAL || lookupmode == INTERPOSE_REVAL
	    || lookupmode == INTERPOSE_REVAL_NEG) {
		verify_locked(dentry);
	} else {
		BUG_ON(dtopd_nocheck(dentry) != NULL);
		locked_child = 1;
	}
	if (lookupmode != INTERPOSE_PARTIAL)
		if ((err = new_dentry_private_data(dentry)))
			goto out;
	/* must initialize dentry operations */
	dentry->d_op = &unionfs_dops;

	parent_dentry = GET_PARENT(dentry);
	/* We never partial lookup the root directory. */
	if (parent_dentry != dentry) {
		lock_dentry(parent_dentry);
		locked_parent = 1;
	} else {
		DPUT(parent_dentry);
		parent_dentry = NULL;
		goto out;
	}

	fist_print_dentry("IN unionfs_lookup (parent)", parent_dentry);
	fist_print_dentry("IN unionfs_lookup (child)", dentry);

	name = dentry->d_name.name;
	namelen = dentry->d_name.len;

	/* No dentries should get created for possible whiteout names. */
	if (!is_validname(name)) {
		err = -EPERM;
		goto out_free;
	}

	/* Now start the actual lookup procedure. */
	bstart = dbstart(parent_dentry);
	bend = dbend(parent_dentry);
	bopaque = dbopaque(parent_dentry);
	BUG_ON(bstart < 0);

	/* It would be ideal if we could convert partial lookups to only have
	 * to do this work when they really need to.  It could probably improve
	 * performance quite a bit, and maybe simplify the rest of the code. */
	if (lookupmode == INTERPOSE_PARTIAL) {
		bstart++;
		if ((bopaque != -1) && (bopaque < bend))
			bend = bopaque;
	}

	fist_dprint(8, "bstart = %d, bend = %d\n", bstart, bend);
	for (bindex = bstart; bindex <= bend; bindex++) {
		hidden_dentry = dtohd_index(dentry, bindex);
		if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry)
			continue;
		BUG_ON(hidden_dentry != NULL);

		hidden_dir_dentry = dtohd_index(parent_dentry, bindex);

		/* if the parent hidden dentry does not exist skip this */
		if (!(hidden_dir_dentry && hidden_dir_dentry->d_inode))
			continue;

		/* also skip it if the parent isn't a directory. */
		if (!S_ISDIR(hidden_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 (IS_ERR(whname)) {
				err = PTR_ERR(whname);
				goto out_free;
			}
		}

		/* check if whiteout exists in this branch: lookup .wh.foo */
		wh_hidden_dentry = LOOKUP_ONE_LEN(whname, hidden_dir_dentry,
						  namelen + WHLEN);
		if (IS_ERR(wh_hidden_dentry)) {
			DPUT(first_hidden_dentry);
			err = PTR_ERR(wh_hidden_dentry);
			goto out_free;
		}

		if (wh_hidden_dentry->d_inode) {
			/* We found a whiteout so lets give up. */
			fist_dprint(8, "whiteout found in %d\n", bindex);
			if (S_ISREG(wh_hidden_dentry->d_inode->i_mode)) {
				set_dbend(dentry, bindex);
				set_dbopaque(dentry, bindex);
				DPUT(wh_hidden_dentry);
				break;
			}
			err = -EIO;
			printk(KERN_NOTICE "EIO: Invalid whiteout entry type"
			       " %d.\n", wh_hidden_dentry->d_inode->i_mode);
			DPUT(wh_hidden_dentry);
			DPUT(first_hidden_dentry);
			goto out_free;
		}

		DPUT(wh_hidden_dentry);
		wh_hidden_dentry = NULL;

		/* Now do regular lookup; lookup foo */
		hidden_dentry = LOOKUP_ONE_LEN(name, hidden_dir_dentry,
					       namelen);
		fist_print_generic_dentry("hidden result", hidden_dentry);
		if (IS_ERR(hidden_dentry)) {
			DPUT(first_hidden_dentry);
			err = PTR_ERR(hidden_dentry);
			goto out_free;
		}

		/* Store the first negative dentry specially, because if they
		 * are all negative we need this for future creates. */
		if (!hidden_dentry->d_inode) {
			if (!first_hidden_dentry && (dbstart(dentry) == -1)) {
				first_hidden_dentry = hidden_dentry;
				first_dentry_offset = bindex;
			} else {
				DPUT(hidden_dentry);
			}
			continue;
		}

		/* number of positive dentries */
		dentry_count++;

		/* store underlying dentry */
		if (dbstart(dentry) == -1)
			set_dbstart(dentry, bindex);
		set_dtohd_index(dentry, bindex, hidden_dentry);
		set_dbend(dentry, bindex);

		/* update parent directory's atime with the bindex */
		fist_copy_attr_atime(parent_dentry->d_inode,
				     hidden_dir_dentry->d_inode);

		/* We terminate file lookups here. */
		if (!S_ISDIR(hidden_dentry->d_inode->i_mode)) {
			if (lookupmode == INTERPOSE_PARTIAL)
				continue;
			if (dentry_count == 1)
				goto out_positive;
			/* This can only happen with mixed D-*-F-* */
			BUG_ON(!S_ISDIR(dtohd(dentry)->d_inode->i_mode));
			continue;
		}

		opaque = is_opaque_dir(dentry, bindex);
		if (opaque < 0) {
			DPUT(first_hidden_dentry);
			err = opaque;
			goto out_free;
		}
		if (opaque) {
			set_dbend(dentry, bindex);
			set_dbopaque(dentry, bindex);
			break;
		}
	}

	if (dentry_count)
		goto out_positive;
	else
		goto out_negative;

      out_negative:
	if (lookupmode == INTERPOSE_PARTIAL)
		goto out;

	/* If we've only got negative dentries, then use the leftmost one. */
	if (lookupmode == INTERPOSE_REVAL) {
		if (dentry->d_inode) {
			itopd(dentry->d_inode)->uii_stale = 1;
		}
		goto out;
	}
	/* This should only happen if we found a whiteout. */
	if (first_dentry_offset == -1) {
		first_hidden_dentry = LOOKUP_ONE_LEN(name, hidden_dir_dentry,
						     namelen);
		first_dentry_offset = bindex;
		if (IS_ERR(first_hidden_dentry)) {
			err = PTR_ERR(first_hidden_dentry);
			goto out;
		}
	}
	set_dtohd_index(dentry, first_dentry_offset, first_hidden_dentry);
	set_dbstart(dentry, first_dentry_offset);
	set_dbend(dentry, first_dentry_offset);

	if (lookupmode == INTERPOSE_REVAL_NEG)
		BUG_ON(dentry->d_inode != NULL);
	else
		d_add(dentry, NULL);
	goto out;

/* This part of the code is for positive dentries. */
      out_positive:
	BUG_ON(dentry_count <= 0);

	/* If we're holding onto the first negative dentry throw it out. */
	DPUT(first_hidden_dentry);

	/* Partial lookups need to reinterpose, or throw away older negs. */
	if (lookupmode == INTERPOSE_PARTIAL) {
		if (dentry->d_inode) {
			unionfs_reinterpose(dentry);
			goto out;
		}

		/* This somehow turned positive, so it is as if we had a
		 * negative revalidation.  */
		lookupmode = INTERPOSE_REVAL_NEG;

		update_bstart(dentry);
		bstart = dbstart(dentry);
		bend = dbend(dentry);
	}

	err = unionfs_interpose(dentry, dentry->d_sb, lookupmode);
	if (err)
		goto out_drop;

	fist_checkinode(dentry->d_inode, "unionfs_lookup OUT: child");
	fist_checkinode(parent_dentry->d_inode, "unionfs_lookup OUT: dir");
	goto out;

      out_drop:
	d_drop(dentry);

      out_free:
	/* should dput all the underlying dentries on error condition */
	bstart = dbstart(dentry);
	if (bstart >= 0) {
		bend = dbend(dentry);
		for (bindex = bstart; bindex <= bend; bindex++)
			DPUT(dtohd_index(dentry, bindex));
	}
	KFREE(dtohd_ptr(dentry));
	dtohd_ptr(dentry) = NULL;
	set_dbstart(dentry, -1);
	set_dbend(dentry, -1);

      out:
	if (!err && dtopd(dentry)) {
		BUG_ON(dbend(dentry) > dtopd(dentry)->udi_bcount);
		BUG_ON(dbend(dentry) > sbmax(dentry->d_sb));
		BUG_ON(dbstart(dentry) < 0);
	}
	KFREE(whname);
	fist_print_dentry("OUT unionfs_lookup (parent)", parent_dentry);
	fist_print_dentry("OUT unionfs_lookup (child)", dentry);
	if (locked_parent)
		unlock_dentry(parent_dentry);
	DPUT(parent_dentry);
	if (locked_child)
		unlock_dentry(dentry);
	print_exit_status(err);
	return ERR_PTR(err);
}
Exemplo n.º 7
0
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 = alloc_whname(dentry->d_name.name, dentry->d_name.len);
	if (IS_ERR(name)) {
		err = PTR_ERR(name);
		goto out;
	}

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 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 .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;
			}
		}

		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;
}
Exemplo n.º 8
0
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 = alloc_whname(dentry->d_name.name, dentry->d_name.len);
	if (IS_ERR(name)) {
		err = PTR_ERR(name);
		goto out;
	}

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 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 {
		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;
			}
		}

		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;
}
Exemplo n.º 9
0
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 = alloc_whname(dentry->d_name.name, dentry->d_name.len);
	if (IS_ERR(name)) {
		err = PTR_ERR(name);
		goto out;
	}

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

	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 | ATTR_ATIME
		    | ATTR_MTIME | ATTR_UID | ATTR_GID | ATTR_FORCE
		    | ATTR_KILL_SUID | ATTR_KILL_SGID;

		newattrs.ia_mode = mode & ~current->fs->umask;
		newattrs.ia_uid = current->fsuid;
		newattrs.ia_gid = current->fsgid;

		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);

		if (err)
			printk(KERN_WARNING
			       "unionfs: %s:%d: notify_change failed: %d, ignoring..\n",
			       __FILE__, __LINE__, err);

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

		hidden_dir_dentry = GET_PARENT(whiteout_dentry);
		lock_rename(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);
		}

		unlock_rename(hidden_dir_dentry, hidden_dir_dentry);
		DPUT(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. */
			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;
			}
		}

		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;
}
Exemplo n.º 10
0
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 = alloc_whname(dentry->d_name.name, dentry->d_name.len);
	if (IS_ERR(name)) {
		err = PTR_ERR(name);
		goto out;
	}

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_dentry->d_parent,
			   dentry->d_name.len + 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_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;
			}
		}

		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;
}
Exemplo n.º 11
0
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 = alloc_whname(new_dentry->d_name.name, new_dentry->d_name.len);
	if (IS_ERR(name)) {
		err = PTR_ERR(name);
		goto out;
	}

	whiteout_dentry =
	    LOOKUP_ONE_LEN(name, hidden_new_dentry->d_parent,
			   new_dentry->d_name.len + 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() */
		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);

	BUG_ON(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);
	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;
}