Example #1
0
/* unionfs_open helper function: open a directory */
static int __open_dir(struct inode *inode, struct file *file)
{
	struct dentry *lower_dentry;
	struct file *lower_file;
	int bindex, bstart, bend;
	struct vfsmount *mnt;

	bstart = fbstart(file) = dbstart(file->f_path.dentry);
	bend = fbend(file) = dbend(file->f_path.dentry);

	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_dentry =
			unionfs_lower_dentry_idx(file->f_path.dentry, bindex);
		if (!lower_dentry)
			continue;

		dget(lower_dentry);
		unionfs_mntget(file->f_path.dentry, bindex);
		mnt = unionfs_lower_mnt_idx(file->f_path.dentry, bindex);
		lower_file = dentry_open(lower_dentry, mnt, file->f_flags,
					 current_cred());
		if (IS_ERR(lower_file))
			return PTR_ERR(lower_file);

		unionfs_set_lower_file_idx(file, bindex, lower_file);

		/*
		 * The branchget goes after the open, because otherwise
		 * we would miss the reference on release.
		 */
		branchget(inode->i_sb, bindex);
	}

	return 0;
}
Example #2
0
/* open all lower files for a given file */
static int open_all_files(struct file *file)
{
	int bindex, bstart, bend, err = 0;
	struct file *lower_file;
	struct dentry *lower_dentry;
	struct dentry *dentry = file->f_path.dentry;
	struct super_block *sb = dentry->d_sb;

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

	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (!lower_dentry)
			continue;

		dget(lower_dentry);
		unionfs_mntget(dentry, bindex);
		branchget(sb, bindex);

		lower_file =
			dentry_open(lower_dentry,
				    unionfs_lower_mnt_idx(dentry, bindex),
				    file->f_flags, current_cred());
		if (IS_ERR(lower_file)) {
			branchput(sb, bindex);
			err = PTR_ERR(lower_file);
			goto out;
		} else {
			unionfs_set_lower_file_idx(file, bindex, lower_file);
		}
	}
out:
	return err;
}
Example #3
0
/*
 * Post-copyup helper to ensure we have valid mnts: set lower mnt of
 * dentry+parents to the first parent node that has an mnt.
 */
void unionfs_postcopyup_setmnt(struct dentry *dentry)
{
	struct dentry *parent, *hasone;
	int bindex = dbstart(dentry);

	if (unionfs_lower_mnt_idx(dentry, bindex))
		return;
	hasone = dentry->d_parent;
	/* this loop should stop at root dentry */
	while (!unionfs_lower_mnt_idx(hasone, bindex))
		hasone = hasone->d_parent;
	parent = dentry;
	while (!unionfs_lower_mnt_idx(parent, bindex)) {
		unionfs_set_lower_mnt_idx(parent, bindex,
					  unionfs_mntget(hasone, bindex));
		parent = parent->d_parent;
	}
}
Example #4
0
/*
 * return to user-space the branch indices containing the file in question
 *
 * We use fd_set and therefore we are limited to the number of the branches
 * to FD_SETSIZE, which is currently 1024 - plenty for most people
 */
static int unionfs_ioctl_queryfile(struct file *file, struct dentry *parent,
				   unsigned int cmd, unsigned long arg)
{
	int err = 0;
	fd_set branchlist;
	int bstart = 0, bend = 0, bindex = 0;
	int orig_bstart, orig_bend;
	struct dentry *dentry, *lower_dentry;
	struct vfsmount *mnt;

	dentry = file->f_path.dentry;
	orig_bstart = dbstart(dentry);
	orig_bend = dbend(dentry);
	err = unionfs_partial_lookup(dentry, parent);
	if (err)
		goto out;
	bstart = dbstart(dentry);
	bend = dbend(dentry);

	FD_ZERO(&branchlist);

	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (!lower_dentry)
			continue;
		if (likely(lower_dentry->d_inode))
			FD_SET(bindex, &branchlist);
		/* purge any lower objects after partial_lookup */
		if (bindex < orig_bstart || bindex > orig_bend) {
			dput(lower_dentry);
			unionfs_set_lower_dentry_idx(dentry, bindex, NULL);
			iput(unionfs_lower_inode_idx(dentry->d_inode, bindex));
			unionfs_set_lower_inode_idx(dentry->d_inode, bindex,
						    NULL);
			mnt = unionfs_lower_mnt_idx(dentry, bindex);
			if (!mnt)
				continue;
			unionfs_mntput(dentry, bindex);
			unionfs_set_lower_mnt_idx(dentry, bindex, NULL);
		}
	}
	/* restore original dentry's offsets */
	dbstart(dentry) = orig_bstart;
	dbend(dentry) = orig_bend;
	ibstart(dentry->d_inode) = orig_bstart;
	ibend(dentry->d_inode) = orig_bend;

	err = copy_to_user((void __user *)arg, &branchlist, sizeof(fd_set));
	if (unlikely(err))
		err = -EFAULT;

out:
	return err < 0 ? err : bend;
}
Example #5
0
/* open the highest priority file for a given upper file */
static int open_highest_file(struct file *file, bool willwrite)
{
	int bindex, bstart, bend, err = 0;
	struct file *lower_file;
	struct dentry *lower_dentry;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent = dget_parent(dentry);
	struct inode *parent_inode = parent->d_inode;
	struct super_block *sb = dentry->d_sb;

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

	lower_dentry = unionfs_lower_dentry(dentry);
	if (willwrite && IS_WRITE_FLAG(file->f_flags) && is_robranch(dentry)) {
		for (bindex = bstart - 1; bindex >= 0; bindex--) {
			err = copyup_file(parent_inode, file, bstart, bindex,
					  i_size_read(dentry->d_inode));
			if (!err)
				break;
		}
		atomic_set(&UNIONFS_F(file)->generation,
			   atomic_read(&UNIONFS_I(dentry->d_inode)->
				       generation));
		goto out;
	}

	dget(lower_dentry);
	unionfs_mntget(dentry, bstart);
	lower_file = dentry_open(lower_dentry,
				 unionfs_lower_mnt_idx(dentry, bstart),
				 file->f_flags, current_cred());
	if (IS_ERR(lower_file)) {
		err = PTR_ERR(lower_file);
		goto out;
	}
	branchget(sb, bstart);
	unionfs_set_lower_file(file, lower_file);
	/* Fix up the position. */
	lower_file->f_pos = file->f_pos;

	memcpy(&lower_file->f_ra, &file->f_ra, sizeof(struct file_ra_state));
out:
	dput(parent);
	return err;
}
Example #6
0
static void unionfs_d_release(struct dentry *dentry)
{
	int bindex, bstart, bend;

	/* There is no reason to lock the dentry, because we have the only
	 * reference, but the printing functions verify that we have a lock
	 * on the dentry before calling dbstart, etc.
	 */
	unionfs_lock_dentry(dentry);

	/* this could be a negative dentry, so check first */
	if (!UNIONFS_D(dentry)) {
		printk(KERN_DEBUG "dentry without private data: %.*s",
		       dentry->d_name.len, dentry->d_name.name);
		goto out;
	} else if (dbstart(dentry) < 0) {
		/* this is due to a failed lookup */
		printk(KERN_DEBUG "dentry without hidden dentries : %.*s",
		       dentry->d_name.len, dentry->d_name.name);
		goto out_free;
	}

	/* Release all the hidden dentries */
	bstart = dbstart(dentry);
	bend = dbend(dentry);
	for (bindex = bstart; bindex <= bend; bindex++) {
		dput(unionfs_lower_dentry_idx(dentry, bindex));
		mntput(unionfs_lower_mnt_idx(dentry, bindex));

		unionfs_set_lower_dentry_idx(dentry, bindex, NULL);
		unionfs_set_lower_mnt_idx(dentry, bindex, NULL);
	}
	/* free private data (unionfs_dentry_info) here */
	kfree(UNIONFS_D(dentry)->lower_paths);
	UNIONFS_D(dentry)->lower_paths = NULL;

out_free:
	/* No need to unlock it, because it is disappeared. */
	free_dentry_private_data(UNIONFS_D(dentry));
	dentry->d_fsdata = NULL;	/* just to be safe */

out:
	return;
}
Example #7
0
/*
 * Main (and complex) driver function for Unionfs's lookup
 *
 * Returns: NULL (ok), ERR_PTR if an error occurred, or a non-null non-error
 * PTR if d_splice returned a different dentry.
 *
 * If lookupmode is INTERPOSE_PARTIAL/REVAL/REVAL_NEG, the passed dentry's
 * inode info must be locked.  If lookupmode is INTERPOSE_LOOKUP (i.e., a
 * newly looked-up dentry), then unionfs_lookup_backend will return a locked
 * dentry's info, which the caller must unlock.
 */
struct dentry *unionfs_lookup_full(struct dentry *dentry,
				   struct dentry *parent, int lookupmode)
{
	int err = 0;
	struct dentry *lower_dentry = NULL;
	struct vfsmount *lower_mnt;
	struct vfsmount *lower_dir_mnt;
	struct dentry *wh_lower_dentry = NULL;
	struct dentry *lower_dir_dentry = NULL;
	struct dentry *d_interposed = NULL;
	int bindex, bstart, bend, bopaque;
	int opaque, num_positive = 0;
	const char *name;
	int namelen;
	int pos_start, pos_end;

	/*
	 * 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.
	 */
	verify_locked(dentry);
	verify_locked(parent);

	/* must initialize dentry operations */
	dentry->d_op = &unionfs_dops;

	/* We never partial lookup the root directory. */
	if (IS_ROOT(dentry))
		goto out;

	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);
	bend = dbend(parent);
	bopaque = dbopaque(parent);
	BUG_ON(bstart < 0);

	/* adjust bend to bopaque if needed */
	if ((bopaque >= 0) && (bopaque < bend))
		bend = bopaque;

	/* lookup all possible dentries */
	for (bindex = bstart; bindex <= bend; bindex++) {

		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		lower_mnt = unionfs_lower_mnt_idx(dentry, bindex);

		/* skip if we already have a positive lower dentry */
		if (lower_dentry) {
			if (dbstart(dentry) < 0)
				dbstart(dentry) = bindex;
			if (bindex > dbend(dentry))
				dbend(dentry) = bindex;
			if (lower_dentry->d_inode)
				num_positive++;
			continue;
		}

		lower_dir_dentry =
			unionfs_lower_dentry_idx(parent, bindex);
		/* if the lower dentry's parent does not exist, skip this */
		if (!lower_dir_dentry || !lower_dir_dentry->d_inode)
			continue;

		/* also skip it if the parent isn't a directory. */
		if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode))
			continue; /* XXX: should be BUG_ON */

		/* check for whiteouts: stop lookup if found */
		wh_lower_dentry = lookup_whiteout(name, lower_dir_dentry);
		if (IS_ERR(wh_lower_dentry)) {
			err = PTR_ERR(wh_lower_dentry);
			goto out_free;
		}
		if (wh_lower_dentry->d_inode) {
			dbend(dentry) = dbopaque(dentry) = bindex;
			if (dbstart(dentry) < 0)
				dbstart(dentry) = bindex;
			dput(wh_lower_dentry);
			break;
		}
		dput(wh_lower_dentry);

		/* Now do regular lookup; lookup @name */
		lower_dir_mnt = unionfs_lower_mnt_idx(parent, bindex);
		lower_mnt = NULL; /* XXX: needed? */

		lower_dentry = __lookup_one(lower_dir_dentry, lower_dir_mnt,
					    name, &lower_mnt);

		if (IS_ERR(lower_dentry)) {
			err = PTR_ERR(lower_dentry);
			goto out_free;
		}
		unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry);
		if (!lower_mnt)
			lower_mnt = unionfs_mntget(dentry->d_sb->s_root,
						   bindex);
		unionfs_set_lower_mnt_idx(dentry, bindex, lower_mnt);

		/* adjust dbstart/end */
		if (dbstart(dentry) < 0)
			dbstart(dentry) = bindex;
		if (bindex > dbend(dentry))
			dbend(dentry) = bindex;
		/*
		 * We always store the lower dentries above, and update
		 * dbstart/dbend, even if the whole unionfs dentry is
		 * negative (i.e., no lower inodes).
		 */
		if (!lower_dentry->d_inode)
			continue;
		num_positive++;

		/*
		 * check if we just found an opaque directory, if so, stop
		 * lookups here.
		 */
		if (!S_ISDIR(lower_dentry->d_inode->i_mode))
			continue;
		opaque = is_opaque_dir(dentry, bindex);
		if (opaque < 0) {
			err = opaque;
			goto out_free;
		} else if (opaque) {
			dbend(dentry) = dbopaque(dentry) = bindex;
			break;
		}
		dbend(dentry) = bindex;

		/* update parent directory's atime with the bindex */
		fsstack_copy_attr_atime(parent->d_inode,
					lower_dir_dentry->d_inode);
	}

	/* sanity checks, then decide if to process a negative dentry */
	BUG_ON(dbstart(dentry) < 0 && dbend(dentry) >= 0);
	BUG_ON(dbstart(dentry) >= 0 && dbend(dentry) < 0);

	if (num_positive > 0)
		goto out_positive;

	/*** handle NEGATIVE dentries ***/

	/*
	 * If negative, keep only first lower negative dentry, to save on
	 * memory.
	 */
	if (dbstart(dentry) < dbend(dentry)) {
		path_put_lowers(dentry, dbstart(dentry) + 1,
				dbend(dentry), false);
		dbend(dentry) = dbstart(dentry);
	}
	if (lookupmode == INTERPOSE_PARTIAL)
		goto out;
	if (lookupmode == INTERPOSE_LOOKUP) {
		/*
		 * If all we found was a whiteout in the first available
		 * branch, then create a negative dentry for a possibly new
		 * file to be created.
		 */
		if (dbopaque(dentry) < 0)
			goto out;
		/* XXX: need to get mnt here */
		bindex = dbstart(dentry);
		if (unionfs_lower_dentry_idx(dentry, bindex))
			goto out;
		lower_dir_dentry =
			unionfs_lower_dentry_idx(parent, bindex);
		if (!lower_dir_dentry || !lower_dir_dentry->d_inode)
			goto out;
		if (!S_ISDIR(lower_dir_dentry->d_inode->i_mode))
			goto out; /* XXX: should be BUG_ON */
		/* XXX: do we need to cross bind mounts here? */
		lower_dentry = lookup_one_len(name, lower_dir_dentry, namelen);
		if (IS_ERR(lower_dentry)) {
			err = PTR_ERR(lower_dentry);
			goto out;
		}
		/* XXX: need to mntget/mntput as needed too! */
		unionfs_set_lower_dentry_idx(dentry, bindex, lower_dentry);
		/* XXX: wrong mnt for crossing bind mounts! */
		lower_mnt = unionfs_mntget(dentry->d_sb->s_root, bindex);
		unionfs_set_lower_mnt_idx(dentry, bindex, lower_mnt);

		goto out;
	}

	/* if we're revalidating a positive dentry, don't make it negative */
	if (lookupmode != INTERPOSE_REVAL)
		d_add(dentry, NULL);

	goto out;

out_positive:
	/*** handle POSITIVE dentries ***/

	/*
	 * This unionfs dentry is positive (at least one lower inode
	 * exists), so scan entire dentry from beginning to end, and remove
	 * any negative lower dentries, if any.  Then, update dbstart/dbend
	 * to reflect the start/end of positive dentries.
	 */
	pos_start = pos_end = -1;
	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry,
							bindex);
		if (lower_dentry && lower_dentry->d_inode) {
			if (pos_start < 0)
				pos_start = bindex;
			if (bindex > pos_end)
				pos_end = bindex;
			continue;
		}
		path_put_lowers(dentry, bindex, bindex, false);
	}
	if (pos_start >= 0)
		dbstart(dentry) = pos_start;
	if (pos_end >= 0)
		dbend(dentry) = pos_end;

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

		/*
		 * This dentry was positive, so it is as if we had a
		 * negative revalidation.
		 */
		lookupmode = INTERPOSE_REVAL_NEG;
		update_bstart(dentry);
	}

	/*
	 * Interpose can return a dentry if d_splice returned a different
	 * dentry.
	 */
	d_interposed = unionfs_interpose(dentry, dentry->d_sb, lookupmode);
	if (IS_ERR(d_interposed))
		err = PTR_ERR(d_interposed);
	else if (d_interposed)
		dentry = d_interposed;

	if (!err)
		goto out;
	d_drop(dentry);

out_free:
	/* should dput/mntput all the underlying dentries on error condition */
	if (dbstart(dentry) >= 0)
		path_put_lowers_all(dentry, false);
	/* free lower_paths unconditionally */
	kfree(UNIONFS_D(dentry)->lower_paths);
	UNIONFS_D(dentry)->lower_paths = NULL;

out:
	if (dentry && UNIONFS_D(dentry)) {
		BUG_ON(dbstart(dentry) < 0 && dbend(dentry) >= 0);
		BUG_ON(dbstart(dentry) >= 0 && dbend(dentry) < 0);
	}
	if (d_interposed && UNIONFS_D(d_interposed)) {
		BUG_ON(dbstart(d_interposed) < 0 && dbend(d_interposed) >= 0);
		BUG_ON(dbstart(d_interposed) >= 0 && dbend(d_interposed) < 0);
	}

	if (!err && d_interposed)
		return d_interposed;
	return ERR_PTR(err);
}
Example #8
0
void __unionfs_check_dentry(const struct dentry *dentry,
			    const char *fname, const char *fxn, int line)
{
	int bindex;
	int dstart, dend, istart, iend;
	struct dentry *lower_dentry;
	struct inode *inode, *lower_inode;
	struct super_block *sb;
	struct vfsmount *lower_mnt;
	int printed_caller = 0;
	void *poison_ptr;

	BUG_ON(!dentry);
	sb = dentry->d_sb;
	inode = dentry->d_inode;
	dstart = dbstart(dentry);
	dend = dbend(dentry);
	/* don't check dentry/mnt if no lower branches */
	if (dstart < 0 && dend < 0)
		goto check_inode;
	BUG_ON(dstart > dend);

	if (unlikely((dstart == -1 && dend != -1) ||
		     (dstart != -1 && dend == -1))) {
		PRINT_CALLER(fname, fxn, line);
		pr_debug(" CD0: dentry=%p dstart/end=%d:%d\n",
			 dentry, dstart, dend);
	}
	/*
	 * check for NULL dentries inside the start/end range, or
	 * non-NULL dentries outside the start/end range.
	 */
	for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) {
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (lower_dentry) {
			if (unlikely(bindex < dstart || bindex > dend)) {
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" CD1: dentry/lower=%p:%p(%p) "
					 "bindex=%d dstart/end=%d:%d\n",
					 dentry, lower_dentry,
					 (lower_dentry ? lower_dentry->d_inode :
					  (void *) -1L),
					 bindex, dstart, dend);
			}
		} else {	/* lower_dentry == NULL */
			if (bindex < dstart || bindex > dend)
				continue;
			/*
			 * Directories can have NULL lower inodes in b/t
			 * start/end, but NOT if at the start/end range.
			 * Ignore this rule, however, if this is a NULL
			 * dentry or a deleted dentry.
			 */
			if (unlikely(!d_deleted((struct dentry *) dentry) &&
				     inode &&
				     !(inode && S_ISDIR(inode->i_mode) &&
				       bindex > dstart && bindex < dend))) {
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" CD2: dentry/lower=%p:%p(%p) "
					 "bindex=%d dstart/end=%d:%d\n",
					 dentry, lower_dentry,
					 (lower_dentry ?
					  lower_dentry->d_inode :
					  (void *) -1L),
					 bindex, dstart, dend);
			}
		}
	}

	/* check for vfsmounts same as for dentries */
	for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) {
		lower_mnt = unionfs_lower_mnt_idx(dentry, bindex);
		if (lower_mnt) {
			if (unlikely(bindex < dstart || bindex > dend)) {
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" CM0: dentry/lmnt=%p:%p bindex=%d "
					 "dstart/end=%d:%d\n", dentry,
					 lower_mnt, bindex, dstart, dend);
			}
		} else {	/* lower_mnt == NULL */
			if (bindex < dstart || bindex > dend)
				continue;
			/*
			 * Directories can have NULL lower inodes in b/t
			 * start/end, but NOT if at the start/end range.
			 * Ignore this rule, however, if this is a NULL
			 * dentry.
			 */
			if (unlikely(inode &&
				     !(inode && S_ISDIR(inode->i_mode) &&
				       bindex > dstart && bindex < dend))) {
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" CM1: dentry/lmnt=%p:%p "
					 "bindex=%d dstart/end=%d:%d\n",
					 dentry, lower_mnt, bindex,
					 dstart, dend);
			}
		}
	}

check_inode:
	/* for inodes now */
	if (!inode)
		return;
	istart = ibstart(inode);
	iend = ibend(inode);
	/* don't check inode if no lower branches */
	if (istart < 0 && iend < 0)
		return;
	BUG_ON(istart > iend);
	if (unlikely((istart == -1 && iend != -1) ||
		     (istart != -1 && iend == -1))) {
		PRINT_CALLER(fname, fxn, line);
		pr_debug(" CI0: dentry/inode=%p:%p istart/end=%d:%d\n",
			 dentry, inode, istart, iend);
	}
	if (unlikely(istart != dstart)) {
		PRINT_CALLER(fname, fxn, line);
		pr_debug(" CI1: dentry/inode=%p:%p istart=%d dstart=%d\n",
			 dentry, inode, istart, dstart);
	}
	if (unlikely(iend != dend)) {
		PRINT_CALLER(fname, fxn, line);
		pr_debug(" CI2: dentry/inode=%p:%p iend=%d dend=%d\n",
			 dentry, inode, iend, dend);
	}

	if (!S_ISDIR(inode->i_mode)) {
		if (unlikely(dend != dstart)) {
			PRINT_CALLER(fname, fxn, line);
			pr_debug(" CI3: dentry/inode=%p:%p dstart=%d dend=%d\n",
				 dentry, inode, dstart, dend);
		}
		if (unlikely(iend != istart)) {
			PRINT_CALLER(fname, fxn, line);
			pr_debug(" CI4: dentry/inode=%p:%p istart=%d iend=%d\n",
				 dentry, inode, istart, iend);
		}
	}

	for (bindex = sbstart(sb); bindex < sbmax(sb); bindex++) {
		lower_inode = unionfs_lower_inode_idx(inode, bindex);
		if (lower_inode) {
			memset(&poison_ptr, POISON_INUSE, sizeof(void *));
			if (unlikely(bindex < istart || bindex > iend)) {
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" CI5: dentry/linode=%p:%p bindex=%d "
					 "istart/end=%d:%d\n", dentry,
					 lower_inode, bindex, istart, iend);
			} else if (unlikely(lower_inode == poison_ptr)) {
				/* freed inode! */
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" CI6: dentry/linode=%p:%p bindex=%d "
					 "istart/end=%d:%d\n", dentry,
					 lower_inode, bindex, istart, iend);
			}
			continue;
		}
		/* if we get here, then lower_inode == NULL */
		if (bindex < istart || bindex > iend)
			continue;
		/*
		 * directories can have NULL lower inodes in b/t start/end,
		 * but NOT if at the start/end range.
		 */
		if (unlikely(S_ISDIR(inode->i_mode) &&
			     bindex > istart && bindex < iend))
			continue;
		PRINT_CALLER(fname, fxn, line);
		pr_debug(" CI7: dentry/linode=%p:%p "
			 "bindex=%d istart/end=%d:%d\n",
			 dentry, lower_inode, bindex, istart, iend);
	}

	/*
	 * If it's a directory, then intermediate objects b/t start/end can
	 * be NULL.  But, check that all three are NULL: lower dentry, mnt,
	 * and inode.
	 */
	if (dstart >= 0 && dend >= 0 && S_ISDIR(inode->i_mode))
		for (bindex = dstart+1; bindex < dend; bindex++) {
			lower_inode = unionfs_lower_inode_idx(inode, bindex);
			lower_dentry = unionfs_lower_dentry_idx(dentry,
								bindex);
			lower_mnt = unionfs_lower_mnt_idx(dentry, bindex);
			if (unlikely(!((lower_inode && lower_dentry &&
					lower_mnt) ||
				       (!lower_inode &&
					!lower_dentry && !lower_mnt)))) {
				PRINT_CALLER(fname, fxn, line);
				pr_debug(" Cx: lmnt/ldentry/linode=%p:%p:%p "
					 "bindex=%d dstart/end=%d:%d\n",
					 lower_mnt, lower_dentry, lower_inode,
					 bindex, dstart, dend);
			}
		}
	/* check if lower inode is newer than upper one (it shouldn't) */
	if (unlikely(is_newer_lower(dentry) && !is_negative_lower(dentry))) {
		PRINT_CALLER(fname, fxn, line);
		for (bindex = ibstart(inode); bindex <= ibend(inode);
		     bindex++) {
			lower_inode = unionfs_lower_inode_idx(inode, bindex);
			if (unlikely(!lower_inode))
				continue;
			pr_debug(" CI8: bindex=%d mtime/lmtime=%lu.%lu/%lu.%lu "
				 "ctime/lctime=%lu.%lu/%lu.%lu\n",
				 bindex,
				 inode->i_mtime.tv_sec,
				 inode->i_mtime.tv_nsec,
				 lower_inode->i_mtime.tv_sec,
				 lower_inode->i_mtime.tv_nsec,
				 inode->i_ctime.tv_sec,
				 inode->i_ctime.tv_nsec,
				 lower_inode->i_ctime.tv_sec,
				 lower_inode->i_ctime.tv_nsec);
		}
	}
}
Example #9
0
static int __copyup_reg_data(struct dentry *dentry,
			     struct dentry *new_lower_dentry, int new_bindex,
			     struct dentry *old_lower_dentry, int old_bindex,
			     struct file **copyup_file, loff_t len)
{
	struct super_block *sb = dentry->d_sb;
	struct file *input_file;
	struct file *output_file;
	struct vfsmount *output_mnt;
	mm_segment_t old_fs;
	char *buf = NULL;
	ssize_t read_bytes, write_bytes;
	loff_t size;
	int err = 0;

	/* open old file */
	unionfs_mntget(dentry, old_bindex);
	branchget(sb, old_bindex);
	/* dentry_open calls dput and mntput if it returns an error */
	input_file = dentry_open(old_lower_dentry,
				 unionfs_lower_mnt_idx(dentry, old_bindex),
				 O_RDONLY | O_LARGEFILE);
	if (IS_ERR(input_file)) {
		dput(old_lower_dentry);
		err = PTR_ERR(input_file);
		goto out;
	}
	if (unlikely(!input_file->f_op || !input_file->f_op->read)) {
		err = -EINVAL;
		goto out_close_in;
	}

	/* open new file */
	dget(new_lower_dentry);
	output_mnt = unionfs_mntget(sb->s_root, new_bindex);
	branchget(sb, new_bindex);
	output_file = dentry_open(new_lower_dentry, output_mnt,
				  O_RDWR | O_LARGEFILE);
	if (IS_ERR(output_file)) {
		err = PTR_ERR(output_file);
		goto out_close_in2;
	}
	if (unlikely(!output_file->f_op || !output_file->f_op->write)) {
		err = -EINVAL;
		goto out_close_out;
	}

	/* allocating a buffer */
	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (unlikely(!buf)) {
		err = -ENOMEM;
		goto out_close_out;
	}

	input_file->f_pos = 0;
	output_file->f_pos = 0;

	old_fs = get_fs();
	set_fs(KERNEL_DS);

	size = len;
	err = 0;
	do {
		if (len >= PAGE_SIZE)
			size = PAGE_SIZE;
		else if ((len < PAGE_SIZE) && (len > 0))
			size = len;

		len -= PAGE_SIZE;

		read_bytes =
			input_file->f_op->read(input_file,
					       (char __user *)buf, size,
					       &input_file->f_pos);
		if (read_bytes <= 0) {
			err = read_bytes;
			break;
		}

		/* see Documentation/filesystems/unionfs/issues.txt */
		lockdep_off();
		write_bytes =
			output_file->f_op->write(output_file,
						 (char __user *)buf,
						 read_bytes,
						 &output_file->f_pos);
		lockdep_on();
		if ((write_bytes < 0) || (write_bytes < read_bytes)) {
			err = write_bytes;
			break;
		}
	} while ((read_bytes > 0) && (len > 0));

	set_fs(old_fs);

	kfree(buf);

	if (!err)
		err = output_file->f_op->fsync(output_file,
					       new_lower_dentry, 0);

	if (err)
		goto out_close_out;

	if (copyup_file) {
		*copyup_file = output_file;
		goto out_close_in;
	}

out_close_out:
	fput(output_file);

out_close_in2:
	branchput(sb, new_bindex);

out_close_in:
	fput(input_file);

out:
	branchput(sb, old_bindex);

	return err;
}
Example #10
0
struct dentry *unionfs_lookup_backend(struct dentry *dentry, struct nameidata *nd,
				      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;
	struct vfsmount *first_hidden_mnt = NULL;
	int locked_parent = 0;
	int locked_child = 0;

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

	/* 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(UNIONFS_D(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 = dget_parent(dentry);
	/* We never partial lookup the root directory. */
	if (parent_dentry != dentry) {
		unionfs_lock_dentry(parent_dentry);
		locked_parent = 1;
	} else {
		dput(parent_dentry);
		parent_dentry = NULL;
		goto out;
	}

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

	for (bindex = bstart; bindex <= bend; bindex++) {
		hidden_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		if (lookupmode == INTERPOSE_PARTIAL && hidden_dentry)
			continue;
		BUG_ON(hidden_dentry != NULL);

		hidden_dir_dentry = unionfs_lower_dentry_idx(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 + UNIONFS_WHLEN);
		if (IS_ERR(wh_hidden_dentry)) {
			dput(first_hidden_dentry);
			mntput(first_hidden_mnt);
			err = PTR_ERR(wh_hidden_dentry);
			goto out_free;
		}

		if (wh_hidden_dentry->d_inode) {
			/* We found a whiteout so lets give up. */
			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);
			mntput(first_hidden_mnt);
			goto out_free;
		}

		dput(wh_hidden_dentry);
		wh_hidden_dentry = NULL;

		/* Now do regular lookup; lookup foo */
		nd->dentry = unionfs_lower_dentry_idx(dentry, bindex);
		/* FIXME: fix following line for mount point crossing */
		nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex);

		hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry,
					       namelen, nd);
		if (IS_ERR(hidden_dentry)) {
			dput(first_hidden_dentry);
			mntput(first_hidden_mnt);
			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;
				/* FIXME: following line needs to be changed
				 * to allow mountpoint crossing
				 */
				first_hidden_mnt = mntget(
					unionfs_lower_mnt_idx(parent_dentry,
								bindex));
				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);
		unionfs_set_lower_dentry_idx(dentry, bindex, hidden_dentry);
		/* FIXME: the following line needs to get fixed to allow
		 * mountpoint crossing
		 */
		unionfs_set_lower_mnt_idx(dentry, bindex,
			mntget(unionfs_lower_mnt_idx(parent_dentry, bindex)));
		set_dbend(dentry, bindex);

		/* update parent directory's atime with the bindex */
		fsstack_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(unionfs_lower_dentry(dentry)->d_inode->i_mode));
			continue;
		}

		opaque = is_opaque_dir(dentry, bindex);
		if (opaque < 0) {
			dput(first_hidden_dentry);
			mntput(first_hidden_mnt);
			err = opaque;
			goto out_free;
		} else 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)
			UNIONFS_I(dentry->d_inode)->stale = 1;

		goto out;
	}
	/* This should only happen if we found a whiteout. */
	if (first_dentry_offset == -1) {
		nd->dentry = dentry;
		/* FIXME: fix following line for mount point crossing */
		nd->mnt = unionfs_lower_mnt_idx(parent_dentry, bindex);

		first_hidden_dentry = lookup_one_len_nd(name, hidden_dir_dentry,
						     namelen, nd);
		first_dentry_offset = bindex;
		if (IS_ERR(first_hidden_dentry)) {
			err = PTR_ERR(first_hidden_dentry);
			goto out;
		}
		
		/* FIXME: the following line needs to be changed to allow
		 * mountpoint crossing
		 */
		first_hidden_mnt = mntget(unionfs_lower_mnt_idx(dentry, bindex));
	}
	unionfs_set_lower_dentry_idx(dentry, first_dentry_offset, first_hidden_dentry);
	unionfs_set_lower_mnt_idx(dentry, first_dentry_offset, first_hidden_mnt);
	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 & corresponding
	 * vfsmount - throw it out.
	 */
	dput(first_hidden_dentry);
	mntput(first_hidden_mnt);

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

	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(unionfs_lower_dentry_idx(dentry, bindex));
			mntput(unionfs_lower_mnt_idx(dentry, bindex));
		}
	}
	kfree(UNIONFS_D(dentry)->lower_paths);
	UNIONFS_D(dentry)->lower_paths = NULL;
	set_dbstart(dentry, -1);
	set_dbend(dentry, -1);

out:
	if (!err && UNIONFS_D(dentry)) {
		BUG_ON(dbend(dentry) > UNIONFS_D(dentry)->bcount);
		BUG_ON(dbend(dentry) > sbmax(dentry->d_sb));
		BUG_ON(dbstart(dentry) < 0);
	}
	kfree(whname);
	if (locked_parent)
		unionfs_unlock_dentry(parent_dentry);
	dput(parent_dentry);
	if (locked_child)
		unionfs_unlock_dentry(dentry);
	return ERR_PTR(err);
}
Example #11
0
static int __copyup_reg_data(struct dentry *dentry,
			     struct dentry *new_hidden_dentry, int new_bindex,
			     struct dentry *old_hidden_dentry, int old_bindex,
			     struct file **copyup_file, loff_t len)
{
	struct super_block *sb = dentry->d_sb;
	struct file *input_file;
	struct file *output_file;
	mm_segment_t old_fs;
	char *buf = NULL;
	ssize_t read_bytes, write_bytes;
	loff_t size;
	int err = 0;

	/* open old file */
	mntget(unionfs_lower_mnt_idx(dentry, old_bindex));
	branchget(sb, old_bindex);
	input_file = dentry_open(old_hidden_dentry,
				unionfs_lower_mnt_idx(dentry, old_bindex),
				O_RDONLY | O_LARGEFILE);
	if (IS_ERR(input_file)) {
		dput(old_hidden_dentry);
		err = PTR_ERR(input_file);
		goto out;
	}
	if (!input_file->f_op || !input_file->f_op->read) {
		err = -EINVAL;
		goto out_close_in;
	}

	/* open new file */
	dget(new_hidden_dentry);
	mntget(unionfs_lower_mnt_idx(dentry, new_bindex));
	branchget(sb, new_bindex);
	output_file = dentry_open(new_hidden_dentry,
				unionfs_lower_mnt_idx(dentry, new_bindex),
				O_WRONLY | O_LARGEFILE);
	if (IS_ERR(output_file)) {
		err = PTR_ERR(output_file);
		goto out_close_in2;
	}
	if (!output_file->f_op || !output_file->f_op->write) {
		err = -EINVAL;
		goto out_close_out;
	}

	/* allocating a buffer */
	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (!buf) {
		err = -ENOMEM;
		goto out_close_out;
	}

	input_file->f_pos = 0;
	output_file->f_pos = 0;

	old_fs = get_fs();
	set_fs(KERNEL_DS);

	size = len;
	err = 0;
	do {
		if (len >= PAGE_SIZE)
			size = PAGE_SIZE;
		else if ((len < PAGE_SIZE) && (len > 0))
			size = len;

		len -= PAGE_SIZE;

		read_bytes =
		    input_file->f_op->read(input_file,
					   (char __user *)buf, size,
					   &input_file->f_pos);
		if (read_bytes <= 0) {
			err = read_bytes;
			break;
		}

		write_bytes =
		    output_file->f_op->write(output_file,
					     (char __user *)buf,
					     read_bytes,
					     &output_file->f_pos);
		if ((write_bytes < 0) || (write_bytes < read_bytes)) {
			err = write_bytes;
			break;
		}
	} while ((read_bytes > 0) && (len > 0));

	set_fs(old_fs);

	kfree(buf);

	if (err)
		goto out_close_out;
	if (copyup_file) {
		*copyup_file = output_file;
		goto out_close_in;
	}

out_close_out:
	fput(output_file);

out_close_in2:
	branchput(sb, new_bindex);

out_close_in:
	fput(input_file);

out:
	branchput(sb, old_bindex);

	return err;
}
Example #12
0
/* unionfs_open helper function: open a file */
static int __open_file(struct inode *inode, struct file *file,
		       struct dentry *parent)
{
	struct dentry *lower_dentry;
	struct file *lower_file;
	int lower_flags;
	int bindex, bstart, bend;

	lower_dentry = unionfs_lower_dentry(file->f_path.dentry);
	lower_flags = file->f_flags;

	bstart = fbstart(file) = dbstart(file->f_path.dentry);
	bend = fbend(file) = dbend(file->f_path.dentry);

	/*
	 * check for the permission for lower file.  If the error is
	 * COPYUP_ERR, copyup the file.
	 */
	if (lower_dentry->d_inode && is_robranch(file->f_path.dentry)) {
		/*
		 * if the open will change the file, copy it up otherwise
		 * defer it.
		 */
		if (lower_flags & O_TRUNC) {
			int size = 0;
			int err = -EROFS;

			/* copyup the file */
			for (bindex = bstart - 1; bindex >= 0; bindex--) {
				err = copyup_file(parent->d_inode, file,
						  bstart, bindex, size);
				if (!err) {
					/* only one regular file open */
					fbend(file) = fbstart(file);
					break;
				}
			}
			return err;
		} else {
			/*
			 * turn off writeable flags, to force delayed copyup
			 * by caller.
			 */
			lower_flags &= ~(OPEN_WRITE_FLAGS);
		}
	}

	dget(lower_dentry);

	/*
	 * dentry_open will decrement mnt refcnt if err.
	 * otherwise fput() will do an mntput() for us upon file close.
	 */
	unionfs_mntget(file->f_path.dentry, bstart);
	lower_file =
		dentry_open(lower_dentry,
			    unionfs_lower_mnt_idx(file->f_path.dentry, bstart),
			    lower_flags, current_cred());
	if (IS_ERR(lower_file))
		return PTR_ERR(lower_file);

	unionfs_set_lower_file(file, lower_file);
	branchget(inode->i_sb, bstart);

	return 0;
}