示例#1
0
static int unionfs_setattr(struct dentry *dentry, struct iattr *ia)
{
	int err = 0;
	struct dentry *lower_dentry;
	struct dentry *parent;
	struct inode *inode;
	struct inode *lower_inode;
	int bstart, bend, bindex;
	loff_t size;
	struct iattr lower_ia;

	/* check if user has permission to change inode */
	err = inode_change_ok(dentry->d_inode, ia);
	if (err)
		goto out_err;

	unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
	parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
	unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);

	if (unlikely(!__unionfs_d_revalidate(dentry, parent, false, 0))) {
		err = -ESTALE;
		goto out;
	}

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

	/*
	 * mode change is for clearing setuid/setgid. Allow lower filesystem
	 * to reinterpret it in its own way.
	 */
	if (ia->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
		ia->ia_valid &= ~ATTR_MODE;

	lower_dentry = unionfs_lower_dentry(dentry);
	if (!lower_dentry) { /* should never happen after above revalidate */
		err = -EINVAL;
		goto out;
	}

	/*
	 * Get the lower inode directly from lower dentry, in case ibstart
	 * is -1 (which happens when the file is open but unlinked.
	 */
	lower_inode = lower_dentry->d_inode;

	/* check if user has permission to change lower inode */
	err = inode_change_ok(lower_inode, ia);
	if (err)
		goto out;

	/* copyup if the file is on a read only branch */
	if (is_robranch_super(dentry->d_sb, bstart)
	    || __is_rdonly(lower_inode)) {
		/* check if we have a branch to copy up to */
		if (bstart <= 0) {
			err = -EACCES;
			goto out;
		}

		if (ia->ia_valid & ATTR_SIZE)
			size = ia->ia_size;
		else
			size = i_size_read(inode);
		/* copyup to next available branch */
		for (bindex = bstart - 1; bindex >= 0; bindex--) {
			err = copyup_dentry(parent->d_inode,
					    dentry, bstart, bindex,
					    dentry->d_name.name,
					    dentry->d_name.len,
					    NULL, size);
			if (!err)
				break;
		}
		if (err)
			goto out;
		/* get updated lower_dentry/inode after copyup */
		lower_dentry = unionfs_lower_dentry(dentry);
		lower_inode = unionfs_lower_inode(inode);
		/*
		 * check for whiteouts in writeable branch, and remove them
		 * if necessary.
		 */
		if (lower_dentry) {
			err = check_unlink_whiteout(dentry, lower_dentry,
						    bindex);
			if (err > 0) /* ignore if whiteout found and removed */
				err = 0;
		}
	}

	/*
	 * If shrinking, first truncate upper level to cancel writing dirty
	 * pages beyond the new eof; and also if its' maxbytes is more
	 * limiting (fail with -EFBIG before making any change to the lower
	 * level).  There is no need to vmtruncate the upper level
	 * afterwards in the other cases: we fsstack_copy_inode_size from
	 * the lower level.
	 */
	if (ia->ia_valid & ATTR_SIZE) {
		err = inode_newsize_ok(inode, ia->ia_size);
		if (err)
			goto out;
		truncate_setsize(inode, ia->ia_size);
	}

	/* notify the (possibly copied-up) lower inode */
	/*
	 * Note: we use lower_dentry->d_inode, because lower_inode may be
	 * unlinked (no inode->i_sb and i_ino==0.  This happens if someone
	 * tries to open(), unlink(), then ftruncate() a file.
	 */
	/* prepare our own lower struct iattr (with our own lower file) */
	memcpy(&lower_ia, ia, sizeof(lower_ia));
	if (ia->ia_valid & ATTR_FILE) {
		lower_ia.ia_file = unionfs_lower_file(ia->ia_file);
		BUG_ON(!lower_ia.ia_file); // XXX?
	}

	mutex_lock(&lower_dentry->d_inode->i_mutex);
	err = notify_change(lower_dentry, &lower_ia);
	mutex_unlock(&lower_dentry->d_inode->i_mutex);
	if (err)
		goto out;

	/* get attributes from the first lower inode */
	if (ibstart(inode) >= 0)
		unionfs_copy_attr_all(inode, lower_inode);
	/*
	 * unionfs_copy_attr_all will copy the lower times to our inode if
	 * the lower ones are newer (useful for cache coherency).  However,
	 * ->setattr is the only place in which we may have to copy the
	 * lower inode times absolutely, to support utimes(2).
	 */
	if (ia->ia_valid & ATTR_MTIME_SET)
		inode->i_mtime = lower_inode->i_mtime;
	if (ia->ia_valid & ATTR_CTIME)
		inode->i_ctime = lower_inode->i_ctime;
	if (ia->ia_valid & ATTR_ATIME_SET)
		inode->i_atime = lower_inode->i_atime;
	fsstack_copy_inode_size(inode, lower_inode);

out:
	if (!err)
		unionfs_check_dentry(dentry);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
out_err:
	return err;
}
示例#2
0
static int unionfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
	int err = 0;
	struct dentry *lower_dentry = NULL;
	struct dentry *lower_parent_dentry = NULL;
	struct dentry *parent;
	int bindex = 0, bstart;
	char *name = NULL;
	int valid;

	unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_CHILD);
	parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
	unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);

	valid = __unionfs_d_revalidate(dentry, parent, false, 0);
	if (unlikely(!valid)) {
		err = -ESTALE;	/* same as what real_lookup does */
		goto out;
	}

	bstart = dbstart(dentry);

	lower_dentry = unionfs_lower_dentry(dentry);

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

		if (is_robranch_super(dentry->d_sb, bindex))
			continue;

		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (!lower_dentry) {
			lower_dentry = create_parents(dir, dentry,
						      dentry->d_name.name,
						      bindex);
			if (!lower_dentry || IS_ERR(lower_dentry)) {
				printk(KERN_ERR "unionfs: lower dentry "
				       " NULL for bindex = %d\n", bindex);
				continue;
			}
		}

		lower_parent_dentry = lock_parent(lower_dentry);

		if (IS_ERR(lower_parent_dentry)) {
			err = PTR_ERR(lower_parent_dentry);
			goto out;
		}

		err = vfs_mkdir(lower_parent_dentry->d_inode, lower_dentry,
				mode);

		unlock_dir(lower_parent_dentry);

		/* did the mkdir succeed? */
		if (err)
			break;

		for (i = bindex + 1; i <= bend; i++) {
			/* XXX: use path_put_lowers? */
			if (unionfs_lower_dentry_idx(dentry, i)) {
				dput(unionfs_lower_dentry_idx(dentry, i));
				unionfs_set_lower_dentry_idx(dentry, i, NULL);
			}
		}
		dbend(dentry) = bindex;

		/*
		 * Only INTERPOSE_LOOKUP can return a value other than 0 on
		 * err.
		 */
		err = PTR_ERR(unionfs_interpose(dentry, dir->i_sb, 0));
		if (!err) {
			unionfs_copy_attr_times(dir);
			fsstack_copy_inode_size(dir,
						lower_parent_dentry->d_inode);

			/* update number of links on parent directory */
			set_nlink(dir, unionfs_get_nlinks(dir));
		}

		err = make_dir_opaque(dentry, dbstart(dentry));
		if (err) {
			printk(KERN_ERR "unionfs: mkdir: error creating "
			       ".wh.__dir_opaque: %d\n", err);
			goto out;
		}

		/* we are done! */
		break;
	}

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

	kfree(name);

	if (!err) {
		unionfs_copy_attr_times(dentry->d_inode);
		unionfs_postcopyup_setmnt(dentry);
	}
	unionfs_check_inode(dir);
	unionfs_check_dentry(dentry);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);

	return err;
}
示例#3
0
static int unionfs_link(struct dentry *old_dentry, struct inode *dir,
			struct dentry *new_dentry)
{
	int err = 0;
	struct dentry *lower_old_dentry = NULL;
	struct dentry *lower_new_dentry = NULL;
	struct dentry *lower_dir_dentry = NULL;
	struct dentry *old_parent, *new_parent;
	char *name = NULL;
	bool valid;

	unionfs_read_lock(old_dentry->d_sb, UNIONFS_SMUTEX_CHILD);
	old_parent = dget_parent(old_dentry);
	new_parent = dget_parent(new_dentry);
	unionfs_double_lock_parents(old_parent, new_parent);
	unionfs_double_lock_dentry(old_dentry, new_dentry);

	valid = __unionfs_d_revalidate(old_dentry, old_parent, false, 0);
	if (unlikely(!valid)) {
		err = -ESTALE;
		goto out;
	}
	if (new_dentry->d_inode) {
		valid = __unionfs_d_revalidate(new_dentry, new_parent, false, 0);
		if (unlikely(!valid)) {
			err = -ESTALE;
			goto out;
		}
	}

	lower_new_dentry = unionfs_lower_dentry(new_dentry);

	/* 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);
		set_nlink(dir, unionfs_get_nlinks(dir));
		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,
						  dbstart(old_dentry));
		err = PTR_ERR(lower_new_dentry);
		if (IS_COPYUP_ERR(err))
			goto docopyup;
		if (!lower_new_dentry || IS_ERR(lower_new_dentry))
			goto out;
	}
	lower_new_dentry = unionfs_lower_dentry(new_dentry);
	lower_old_dentry = unionfs_lower_dentry(old_dentry);

	BUG_ON(dbstart(old_dentry) != dbstart(new_dentry));
	lower_dir_dentry = lock_parent(lower_new_dentry);
	err = is_robranch(old_dentry);
	if (!err) {
		/* see Documentation/filesystems/unionfs/issues.txt */
		lockdep_off();
		err = vfs_link(lower_old_dentry, lower_dir_dentry->d_inode,
			       lower_new_dentry);
		lockdep_on();
	}
	unlock_dir(lower_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_parent->d_inode,
					    old_dentry, old_bstart,
					    bindex, old_dentry->d_name.name,
					    old_dentry->d_name.len, NULL,
					    i_size_read(old_dentry->d_inode));
			if (err)
				continue;
			lower_new_dentry =
				create_parents(dir, new_dentry,
					       new_dentry->d_name.name,
					       bindex);
			lower_old_dentry = unionfs_lower_dentry(old_dentry);
			lower_dir_dentry = lock_parent(lower_new_dentry);
			/* see Documentation/filesystems/unionfs/issues.txt */
			lockdep_off();
			/* do vfs_link */
			err = vfs_link(lower_old_dentry,
				       lower_dir_dentry->d_inode,
				       lower_new_dentry);
			lockdep_on();
			unlock_dir(lower_dir_dentry);
			goto check_link;
		}
		goto out;
	}

check_link:
	if (err || !lower_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_add(new_dentry, new_dentry->d_inode);
	unionfs_copy_attr_all(dir, lower_new_dentry->d_parent->d_inode);
	fsstack_copy_inode_size(dir, lower_new_dentry->d_parent->d_inode);

	/* propagate number of hard-links */
	set_nlink(old_dentry->d_inode,
		  unionfs_get_nlinks(old_dentry->d_inode));
	/* new dentry's ctime may have changed due to hard-link counts */
	unionfs_copy_attr_times(new_dentry->d_inode);

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

	kfree(name);
	if (!err)
		unionfs_postcopyup_setmnt(new_dentry);

	unionfs_check_inode(dir);
	unionfs_check_dentry(new_dentry);
	unionfs_check_dentry(old_dentry);

	unionfs_double_unlock_dentry(old_dentry, new_dentry);
	unionfs_double_unlock_parents(old_parent, new_parent);
	dput(new_parent);
	dput(old_parent);
	unionfs_read_unlock(old_dentry->d_sb);

	return err;
}
示例#4
0
/*
 * 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).
 *
 * Return a lower_dentry we can use to create object in, or ERR_PTR.
 */
static struct dentry *find_writeable_branch(struct inode *parent,
					    struct dentry *dentry)
{
	int err = -EINVAL;
	int bindex, istart, iend;
	struct dentry *lower_dentry = NULL;

	istart = ibstart(parent);
	iend = ibend(parent);
	if (istart < 0)
		goto out;

begin:
	for (bindex = istart; bindex <= iend; bindex++) {
		/* skip non-writeable branches */
		err = is_robranch_super(dentry->d_sb, bindex);
		if (err) {
			err = -EROFS;
			continue;
		}
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (!lower_dentry)
			continue;
		/*
		 * check for whiteouts in writeable branch, and remove them
		 * if necessary.
		 */
		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 */
		break;
	}
	/*
	 * If istart wasn't already branch 0, and we got any error, then try
	 * branch 0 (which may require copyup)
	 */
	if (err && istart > 0) {
		istart = iend = 0;
		goto begin;
	}

	/*
	 * If we tried even branch 0, and still got an error, abort.  But if
	 * the error was an EROFS, then we should try to copyup.
	 */
	if (err && err != -EROFS)
		goto out;

	/*
	 * If we get here, then check if copyup needed.  If lower_dentry is
	 * NULL, create the entire dentry directory structure in branch 0.
	 */
	if (!lower_dentry) {
		bindex = 0;
		lower_dentry = create_parents(parent, dentry,
					      dentry->d_name.name, bindex);
		if (IS_ERR(lower_dentry)) {
			err = PTR_ERR(lower_dentry);
			goto out;
		}
	}
	err = 0;		/* all's well */
out:
	if (err)
		return ERR_PTR(err);
	return lower_dentry;
}
示例#5
0
文件: rename.c 项目: robacklin/ts7800
static int __unionfs_rename(struct inode *old_dir, struct dentry *old_dentry,
			    struct dentry *old_parent,
			    struct inode *new_dir, struct dentry *new_dentry,
			    struct dentry *new_parent,
			    int bindex)
{
	int err = 0;
	struct dentry *lower_old_dentry;
	struct dentry *lower_new_dentry;
	struct dentry *lower_old_dir_dentry;
	struct dentry *lower_new_dir_dentry;
	struct dentry *trap;

	lower_new_dentry = unionfs_lower_dentry_idx(new_dentry, bindex);
	lower_old_dentry = unionfs_lower_dentry_idx(old_dentry, bindex);

	if (!lower_new_dentry) {
		lower_new_dentry =
			create_parents(new_parent->d_inode,
				       new_dentry, new_dentry->d_name.name,
				       bindex);
		if (IS_ERR(lower_new_dentry)) {
			err = PTR_ERR(lower_new_dentry);
			if (IS_COPYUP_ERR(err))
				goto out;
			printk(KERN_ERR "unionfs: error creating directory "
			       "tree for rename, bindex=%d err=%d\n",
			       bindex, err);
			goto out;
		}
	}

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

	/* check of old_dentry branch is writable */
	err = is_robranch_super(old_dentry->d_sb, bindex);
	if (err)
		goto out;

	dget(lower_old_dentry);
	dget(lower_new_dentry);
	lower_old_dir_dentry = dget_parent(lower_old_dentry);
	lower_new_dir_dentry = dget_parent(lower_new_dentry);

	/* see Documentation/filesystems/unionfs/issues.txt */
	lockdep_off();
	trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
	/* source should not be ancenstor of target */
	if (trap == lower_old_dentry) {
		err = -EINVAL;
		goto out_err_unlock;
	}
	/* target should not be ancenstor of source */
	if (trap == lower_new_dentry) {
		err = -ENOTEMPTY;
		goto out_err_unlock;
	}
	err = vfs_rename(lower_old_dir_dentry->d_inode, lower_old_dentry,
			 lower_new_dir_dentry->d_inode, lower_new_dentry);
out_err_unlock:
	if (!err) {
		/* update parent dir times */
		fsstack_copy_attr_times(old_dir, lower_old_dir_dentry->d_inode);
		fsstack_copy_attr_times(new_dir, lower_new_dir_dentry->d_inode);
	}
	unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
	lockdep_on();

	dput(lower_old_dir_dentry);
	dput(lower_new_dir_dentry);
	dput(lower_old_dentry);
	dput(lower_new_dentry);

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

	return err;
}