Esempio n. 1
0
ssize_t unionfs_read(struct file * file, char *buf, size_t count, loff_t * ppos)
{
	int err = -EINVAL;
	struct file *hidden_file = NULL;
	loff_t pos = *ppos;

	print_entry_location();

	if ((err = unionfs_file_revalidate(file, 0)))
		goto out;

	fist_print_file("entering read()", file);

	PASSERT(ftopd(file));
	hidden_file = ftohf(file);
	PASSERT(hidden_file);

	if (!hidden_file->f_op || !hidden_file->f_op->read)
		goto out;

	err = hidden_file->f_op->read(hidden_file, buf, count, &pos);
	*ppos = pos;
	if (err >= 0) {
		/* atime should also be updated for reads of size zero or more */
		fist_copy_attr_atime(file->f_dentry->d_inode,
				     hidden_file->f_dentry->d_inode);
	}
	memcpy(&(file->f_ra), &(hidden_file->f_ra),
	       sizeof(struct file_ra_state));

      out:
	fist_print_file("leaving read()", file);
	print_exit_status(err);
	return err;
}
Esempio n. 2
0
static int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync)
{
	int err;
	struct file *hidden_file = NULL;

	print_entry_location();

	if ((err = unionfs_file_revalidate(file, 1)))
		goto out;

	PASSERT(ftopd(file));
	hidden_file = ftohf(file);

	err = -EINVAL;
	if (!hidden_file->f_op || !hidden_file->f_op->fsync)
		goto out;

	down(&hidden_file->f_dentry->d_inode->i_sem);
	err = hidden_file->f_op->fsync(hidden_file, hidden_file->f_dentry,
				       datasync);
	up(&hidden_file->f_dentry->d_inode->i_sem);

      out:
	print_exit_status(err);
	return err;
}
Esempio n. 3
0
static ssize_t unionfs_write(struct file *file, const char __user *buf,
			     size_t count, loff_t *ppos)
{
	int err = 0;
	struct file *lower_file;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;

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

	err = unionfs_file_revalidate(file, parent, true);
	if (unlikely(err))
		goto out;

	lower_file = unionfs_lower_file(file);
	err = vfs_write(lower_file, buf, count, ppos);
	/* update our inode times+sizes upon a successful lower write */
	if (err >= 0) {
		fsstack_copy_inode_size(dentry->d_inode,
					lower_file->f_path.dentry->d_inode);
		fsstack_copy_attr_times(dentry->d_inode,
					lower_file->f_path.dentry->d_inode);
		UNIONFS_F(file)->wrote_to_file = true; /* for delayed copyup */
		unionfs_check_file(file);
	}

out:
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 4
0
/* FIST-LITE special version of mmap */
static int unionfs_mmap(struct file *file, struct vm_area_struct *vma)
{
	int err = 0;
	struct file *hidden_file = NULL;
	int willwrite;

	print_entry_location();

	/* This might could be deferred to mmap's writepage. */
	willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags);
	if ((err = unionfs_file_revalidate(file, willwrite)))
		goto out;

	PASSERT(ftopd(file));
	hidden_file = ftohf(file);

	err = -ENODEV;
	if (!hidden_file->f_op || !hidden_file->f_op->mmap)
		goto out;

	PASSERT(hidden_file);
	PASSERT(hidden_file->f_op);
	PASSERT(hidden_file->f_op->mmap);

	vma->vm_file = hidden_file;
	err = hidden_file->f_op->mmap(hidden_file, vma);
	get_file(hidden_file);	/* make sure it doesn't get freed on us */
	fput(file);		/* no need to keep extra ref on ours */

      out:
	print_exit_status(err);
	return err;
}
Esempio n. 5
0
static unsigned int unionfs_poll(struct file *file, poll_table * wait)
{
	unsigned int mask = DEFAULT_POLLMASK;
	struct file *hidden_file = NULL;

	print_entry_location();

	if (unionfs_file_revalidate(file, 0)) {
		/* We should pretend an error happend. */
		mask = POLLERR | POLLIN | POLLOUT;
		goto out;
	}

	if (ftopd(file) != NULL)
		hidden_file = ftohf(file);

	if (!hidden_file->f_op || !hidden_file->f_op->poll)
		goto out;

	mask = hidden_file->f_op->poll(hidden_file, wait);

      out:
	print_exit_status(mask);
	return mask;
}
Esempio n. 6
0
static ssize_t unionfs_splice_write(struct pipe_inode_info *pipe,
				    struct file *file, loff_t *ppos,
				    size_t len, unsigned int flags)
{
	ssize_t err = 0;
	struct file *lower_file;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;

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

	err = unionfs_file_revalidate(file, parent, true);
	if (unlikely(err))
		goto out;

	lower_file = unionfs_lower_file(file);
	err = vfs_splice_from(pipe, lower_file, ppos, len, flags);
	/* update our inode times+sizes upon a successful lower write */
	if (err >= 0) {
		fsstack_copy_inode_size(dentry->d_inode,
					lower_file->f_path.dentry->d_inode);
		fsstack_copy_attr_times(dentry->d_inode,
					lower_file->f_path.dentry->d_inode);
		unionfs_check_file(file);
	}

out:
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 7
0
static ssize_t unionfs_read(struct file *file, char __user *buf,
			    size_t count, loff_t *ppos)
{
	int err;
	struct file *lower_file;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;

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

	err = unionfs_file_revalidate(file, parent, false);
	if (unlikely(err))
		goto out;

	lower_file = unionfs_lower_file(file);
	err = vfs_read(lower_file, buf, count, ppos);
	/* update our inode atime upon a successful lower read */
	if (err >= 0) {
		fsstack_copy_attr_atime(dentry->d_inode,
					lower_file->f_path.dentry->d_inode);
		unionfs_check_file(file);
	}

out:
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 8
0
int unionfs_fsync(struct file *file, struct dentry *dentry, int datasync)
{
	int bindex, bstart, bend;
	struct file *lower_file;
	struct dentry *lower_dentry;
	struct dentry *parent;
	struct inode *lower_inode, *inode;
	int err = -EINVAL;

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

	err = unionfs_file_revalidate(file, parent, true);
	if (unlikely(err))
		goto out;
	unionfs_check_file(file);

	bstart = fbstart(file);
	bend = fbend(file);
	if (bstart < 0 || bend < 0)
		goto out;

	inode = dentry->d_inode;
	if (unlikely(!inode)) {
		printk(KERN_ERR
		       "unionfs: null lower inode in unionfs_fsync\n");
		goto out;
	}
	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_inode = unionfs_lower_inode_idx(inode, bindex);
		if (!lower_inode || !lower_inode->i_fop->fsync)
			continue;
		lower_file = unionfs_lower_file_idx(file, bindex);
		lower_dentry = unionfs_lower_dentry_idx(dentry, bindex);
		mutex_lock(&lower_inode->i_mutex);
		err = lower_inode->i_fop->fsync(lower_file,
						lower_dentry,
						datasync);
		if (!err && bindex == bstart)
			fsstack_copy_attr_times(inode, lower_inode);
		mutex_unlock(&lower_inode->i_mutex);
		if (err)
			goto out;
	}

out:
	if (!err)
		unionfs_check_file(file);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 9
0
/* this unionfs_write() does not modify data pages! */
ssize_t unionfs_write(struct file * file, const char *buf, size_t count,
		      loff_t * ppos)
{
	int err = -EINVAL;
	struct file *hidden_file = NULL;
	struct inode *inode;
	struct inode *hidden_inode;
	loff_t pos = *ppos;
	int bstart, bend;

	print_entry_location();

	if ((err = unionfs_file_revalidate(file, 1)))
		goto out;

	inode = file->f_dentry->d_inode;

	bstart = fbstart(file);
	bend = fbend(file);

	ASSERT(bstart != -1);
	PASSERT(ftopd(file));
	PASSERT(ftohf(file));

	hidden_file = ftohf(file);
	hidden_inode = hidden_file->f_dentry->d_inode;

	if (!hidden_file->f_op || !hidden_file->f_op->write)
		goto out;

	/* adjust for append -- seek to the end of the file */
	if (file->f_flags & O_APPEND)
		pos = inode->i_size;

	err = hidden_file->f_op->write(hidden_file, buf, count, &pos);

	/*
	 * copy ctime and mtime from lower layer attributes
	 * atime is unchanged for both layers
	 */
	if (err >= 0)
		fist_copy_attr_times(inode, hidden_inode);

	*ppos = pos;

	/* update this inode's size */
	if (pos > inode->i_size)
		inode->i_size = pos;

      out:
	print_exit_status(err);
	return err;
}
Esempio n. 10
0
static int unionfs_fasync(int fd, struct file *file, int flag)
{
	int err = 0;
	struct file *hidden_file = NULL;

	print_entry_location();

	if ((err = unionfs_file_revalidate(file, 1)))
		goto out;

	hidden_file = ftohf(file);

	if (hidden_file->f_op && hidden_file->f_op->fasync)
		err = hidden_file->f_op->fasync(fd, hidden_file, flag);

      out:
	print_exit_status(err);
	return err;
}
Esempio n. 11
0
long unionfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	long err;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;

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

	err = unionfs_file_revalidate(file, parent, true);
	if (unlikely(err))
		goto out;

	/* check if asked for local commands */
	switch (cmd) {
	case UNIONFS_IOCTL_INCGEN:
		/* Increment the superblock generation count */
		pr_info("unionfs: incgen ioctl deprecated; "
			"use \"-o remount,incgen\"\n");
		err = -ENOSYS;
		break;

	case UNIONFS_IOCTL_QUERYFILE:
		/* Return list of branches containing the given file */
		err = unionfs_ioctl_queryfile(file, parent, cmd, arg);
		break;

	default:
		/* pass the ioctl down */
		err = do_ioctl(file, cmd, arg);
		break;
	}

out:
	unionfs_check_file(file);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 12
0
int unionfs_flush(struct file *file, fl_owner_t id)
{
	int err = 0;
	struct file *lower_file = NULL;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;
	int bindex, bstart, bend;

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

	err = unionfs_file_revalidate(file, parent,
				      UNIONFS_F(file)->wrote_to_file);
	if (unlikely(err))
		goto out;
	unionfs_check_file(file);

	bstart = fbstart(file);
	bend = fbend(file);
	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_file = unionfs_lower_file_idx(file, bindex);

		if (lower_file && lower_file->f_op &&
		    lower_file->f_op->flush) {
			err = lower_file->f_op->flush(lower_file, id);
			if (err)
				goto out;
		}

	}

out:
	if (!err)
		unionfs_check_file(file);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 13
0
static loff_t unionfs_llseek(struct file *file, loff_t offset, int origin)
{
	loff_t err;
	struct file *hidden_file = NULL;

	print_entry_location();

	fist_dprint(6, "unionfs_llseek: file=%p, offset=0x%llx, origin=%d\n",
		    file, offset, origin);

	if ((err = unionfs_file_revalidate(file, 0)))
		goto out;

	PASSERT(ftopd(file));
	hidden_file = ftohf(file);
	PASSERT(hidden_file);
	/* always set hidden position to this one */
	hidden_file->f_pos = file->f_pos;

	memcpy(&(hidden_file->f_ra), &(file->f_ra),
	       sizeof(struct file_ra_state));

	if (hidden_file->f_op && hidden_file->f_op->llseek)
		err = hidden_file->f_op->llseek(hidden_file, offset, origin);
	else
		err = generic_file_llseek(hidden_file, offset, origin);

	if (err < 0)
		goto out;
	if (err != file->f_pos) {
		file->f_pos = err;
		// ION maybe this?
		//      file->f_pos = hidden_file->f_pos;

		file->f_version++;
	}
      out:
	print_exit_status((int)err);
	return err;
}
Esempio n. 14
0
static ssize_t unionfs_sendfile(struct file *file, loff_t * ppos,
				size_t count, read_actor_t actor, void *target)
{
	ssize_t err;
	struct file *hidden_file = NULL;

	print_entry_location();

	if ((err = unionfs_file_revalidate(file, 0)))
		goto out;

	hidden_file = ftohf(file);

	err = -EINVAL;
	if (!hidden_file->f_op || !hidden_file->f_op->sendfile)
		goto out;

	err = hidden_file->f_op->sendfile(hidden_file, ppos, count, actor,
					  target);

      out:
	print_exit_status(err);
	return err;
}
Esempio n. 15
0
static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir)
{
	int err = 0;
	struct file *lower_file = NULL;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;
	struct inode *inode = NULL;
	struct unionfs_getdents_callback buf;
	struct unionfs_dir_state *uds;
	int bend;
	loff_t offset;

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

	err = unionfs_file_revalidate(file, parent, false);
	if (unlikely(err))
		goto out;

	inode = dentry->d_inode;

	uds = UNIONFS_F(file)->rdstate;
	if (!uds) {
		if (file->f_pos == DIREOF) {
			goto out;
		} else if (file->f_pos > 0) {
			uds = find_rdstate(inode, file->f_pos);
			if (unlikely(!uds)) {
				err = -ESTALE;
				goto out;
			}
			UNIONFS_F(file)->rdstate = uds;
		} else {
			init_rdstate(file);
			uds = UNIONFS_F(file)->rdstate;
		}
	}
	bend = fbend(file);

	while (uds->bindex <= bend) {
		lower_file = unionfs_lower_file_idx(file, uds->bindex);
		if (!lower_file) {
			uds->bindex++;
			uds->dirpos = 0;
			continue;
		}

		/* prepare callback buffer */
		buf.filldir_called = 0;
		buf.filldir_error = 0;
		buf.entries_written = 0;
		buf.dirent = dirent;
		buf.filldir = filldir;
		buf.rdstate = uds;
		buf.sb = inode->i_sb;

		/* Read starting from where we last left off. */
		offset = vfs_llseek(lower_file, uds->dirpos, SEEK_SET);
		if (offset < 0) {
			err = offset;
			goto out;
		}
		err = vfs_readdir(lower_file, unionfs_filldir, &buf);

		/* Save the position for when we continue. */
		offset = vfs_llseek(lower_file, 0, SEEK_CUR);
		if (offset < 0) {
			err = offset;
			goto out;
		}
		uds->dirpos = offset;

		/* Copy the atime. */
		fsstack_copy_attr_atime(inode,
					lower_file->f_path.dentry->d_inode);

		if (err < 0)
			goto out;

		if (buf.filldir_error)
			break;

		if (!buf.entries_written) {
			uds->bindex++;
			uds->dirpos = 0;
		}
	}

	if (!buf.filldir_error && uds->bindex >= bend) {
		/* Save the number of hash entries for next time. */
		UNIONFS_I(inode)->hashsize = uds->hashentries;
		free_rdstate(uds);
		UNIONFS_F(file)->rdstate = NULL;
		file->f_pos = DIREOF;
	} else {
		file->f_pos = rdstate2offset(uds);
	}

out:
	if (!err)
		unionfs_check_file(file);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 16
0
/*
 * This is not meant to be a generic repositioning function.  If you do
 * things that aren't supported, then we return EINVAL.
 *
 * What is allowed:
 *  (1) seeking to the same position that you are currently at
 *	This really has no effect, but returns where you are.
 *  (2) seeking to the beginning of the file
 *	This throws out all state, and lets you begin again.
 */
static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
{
	struct unionfs_dir_state *rdstate;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;
	loff_t err;

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

	err = unionfs_file_revalidate(file, parent, false);
	if (unlikely(err))
		goto out;

	rdstate = UNIONFS_F(file)->rdstate;

	/*
	 * we let users seek to their current position, but not anywhere
	 * else.
	 */
	if (!offset) {
		switch (origin) {
		case SEEK_SET:
			if (rdstate) {
				free_rdstate(rdstate);
				UNIONFS_F(file)->rdstate = NULL;
			}
			init_rdstate(file);
			err = 0;
			break;
		case SEEK_CUR:
			err = file->f_pos;
			break;
		case SEEK_END:
			/* Unsupported, because we would break everything.  */
			err = -EINVAL;
			break;
		}
	} else {
		switch (origin) {
		case SEEK_SET:
			if (rdstate) {
				if (offset == rdstate2offset(rdstate))
					err = offset;
				else if (file->f_pos == DIREOF)
					err = DIREOF;
				else
					err = -EINVAL;
			} else {
				struct inode *inode;
				inode = dentry->d_inode;
				rdstate = find_rdstate(inode, offset);
				if (rdstate) {
					UNIONFS_F(file)->rdstate = rdstate;
					err = rdstate->offset;
				} else {
					err = -EINVAL;
				}
			}
			break;
		case SEEK_CUR:
		case SEEK_END:
			/* Unsupported, because we would break everything.  */
			err = -EINVAL;
			break;
		}
	}

out:
	if (!err)
		unionfs_check_file(file);
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}
Esempio n. 17
0
/*
 * release all lower object references & free the file info structure
 *
 * No need to grab sb info's rwsem.
 */
int unionfs_file_release(struct inode *inode, struct file *file)
{
	struct file *lower_file = NULL;
	struct unionfs_file_info *fileinfo;
	struct unionfs_inode_info *inodeinfo;
	struct super_block *sb = inode->i_sb;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;
	int bindex, bstart, bend;
	int fgen, err = 0;

	/*
	 * Since mm/memory.c:might_fault() (under PROVE_LOCKING) was
	 * modified in 2.6.29-rc1 to call might_lock_read on mmap_sem, this
	 * has been causing false positives in file system stacking layers.
	 * In particular, our ->mmap is called after sys_mmap2 already holds
	 * mmap_sem, then we lock our own mutexes; but earlier, it's
	 * possible for lockdep to have locked our mutexes first, and then
	 * we call a lower ->readdir which could call might_fault.  The
	 * different ordering of the locks is what lockdep complains about
	 * -- unnecessarily.  Therefore, we have no choice but to tell
	 * lockdep to temporarily turn off lockdep here.  Note: the comments
	 * inside might_sleep also suggest that it would have been
	 * nicer to only annotate paths that needs that might_lock_read.
	 */
	lockdep_off();
	unionfs_read_lock(sb, UNIONFS_SMUTEX_PARENT);
	parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
	unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);

	/*
	 * We try to revalidate, but the VFS ignores return return values
	 * from file->release, so we must always try to succeed here,
	 * including to do the kfree and dput below.  So if revalidation
	 * failed, all we can do is print some message and keep going.
	 */
	err = unionfs_file_revalidate(file, parent,
				      UNIONFS_F(file)->wrote_to_file);
	if (!err)
		unionfs_check_file(file);
	fileinfo = UNIONFS_F(file);
	BUG_ON(file->f_path.dentry->d_inode != inode);
	inodeinfo = UNIONFS_I(inode);

	/* fput all the lower files */
	fgen = atomic_read(&fileinfo->generation);
	bstart = fbstart(file);
	bend = fbend(file);

	for (bindex = bstart; bindex <= bend; bindex++) {
		lower_file = unionfs_lower_file_idx(file, bindex);

		if (lower_file) {
			unionfs_set_lower_file_idx(file, bindex, NULL);
			fput(lower_file);
			branchput(sb, bindex);
		}

		/* if there are no more refs to the dentry, dput it */
		if (d_deleted(dentry)) {
			dput(unionfs_lower_dentry_idx(dentry, bindex));
			unionfs_set_lower_dentry_idx(dentry, bindex, NULL);
		}
	}

	kfree(fileinfo->lower_files);
	kfree(fileinfo->saved_branch_ids);

	if (fileinfo->rdstate) {
		fileinfo->rdstate->access = jiffies;
		spin_lock(&inodeinfo->rdlock);
		inodeinfo->rdcount++;
		list_add_tail(&fileinfo->rdstate->cache,
			      &inodeinfo->readdircache);
		mark_inode_dirty(inode);
		spin_unlock(&inodeinfo->rdlock);
		fileinfo->rdstate = NULL;
	}
	kfree(fileinfo);

	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(sb);
	lockdep_on();
	return err;
}
Esempio n. 18
0
static int unionfs_mmap(struct file *file, struct vm_area_struct *vma)
{
	int err = 0;
	bool willwrite;
	struct file *lower_file;
	struct dentry *dentry = file->f_path.dentry;
	struct dentry *parent;
	struct vm_operations_struct *saved_vm_ops = NULL;

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

	/* This might be deferred to mmap's writepage */
	willwrite = ((vma->vm_flags | VM_SHARED | VM_WRITE) == vma->vm_flags);
	err = unionfs_file_revalidate(file, parent, willwrite);
	if (unlikely(err))
		goto out;
	unionfs_check_file(file);

	/*
	 * File systems which do not implement ->writepage may use
	 * generic_file_readonly_mmap as their ->mmap op.  If you call
	 * generic_file_readonly_mmap with VM_WRITE, you'd get an -EINVAL.
	 * But we cannot call the lower ->mmap op, so we can't tell that
	 * writeable mappings won't work.  Therefore, our only choice is to
	 * check if the lower file system supports the ->writepage, and if
	 * not, return EINVAL (the same error that
	 * generic_file_readonly_mmap returns in that case).
	 */
	lower_file = unionfs_lower_file(file);
	if (willwrite && !lower_file->f_mapping->a_ops->writepage) {
		err = -EINVAL;
		printk(KERN_ERR "unionfs: branch %d file system does not "
		       "support writeable mmap\n", fbstart(file));
		goto out;
	}

	/*
	 * find and save lower vm_ops.
	 *
	 * XXX: the VFS should have a cleaner way of finding the lower vm_ops
	 */
	if (!UNIONFS_F(file)->lower_vm_ops) {
		err = lower_file->f_op->mmap(lower_file, vma);
		if (err) {
			printk(KERN_ERR "unionfs: lower mmap failed %d\n", err);
			goto out;
		}
		saved_vm_ops = vma->vm_ops;
		err = do_munmap(current->mm, vma->vm_start,
				vma->vm_end - vma->vm_start);
		if (err) {
			printk(KERN_ERR "unionfs: do_munmap failed %d\n", err);
			goto out;
		}
	}

	file->f_mapping->a_ops = &unionfs_dummy_aops;
	err = generic_file_mmap(file, vma);
	file->f_mapping->a_ops = &unionfs_aops;
	if (err) {
		printk(KERN_ERR "unionfs: generic_file_mmap failed %d\n", err);
		goto out;
	}
	vma->vm_ops = &unionfs_vm_ops;
	if (!UNIONFS_F(file)->lower_vm_ops)
		UNIONFS_F(file)->lower_vm_ops = saved_vm_ops;

out:
	if (!err) {
		/* copyup could cause parent dir times to change */
		unionfs_copy_attr_times(parent->d_inode);
		unionfs_check_file(file);
	}
	unionfs_unlock_dentry(dentry);
	unionfs_unlock_parent(dentry, parent);
	unionfs_read_unlock(dentry->d_sb);
	return err;
}