Commit 9a87907d authored by Miklos Szeredi's avatar Miklos Szeredi
Browse files

ovl: implement tmpfile



Combine inode creation with opening a file.

There are six separate objects that are being set up: the backing inode,
dentry and file, and the overlay inode, dentry and file.  Cleanup in case
of an error is a bit of a challenge and is difficult to test, so careful
review is needed.

All tmpfile testcases except generic/509 now run/pass, and no regressions
are observed with full xfstests.

Signed-off-by: default avatarMiklos Szeredi <mszeredi@redhat.com>
Reviewed-by: default avatarAmir Goldstein <amir73il@gmail.com>
parent ed30a4a5
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -52,6 +52,29 @@ struct file *backing_file_open(const struct path *user_path, int flags,
}
EXPORT_SYMBOL_GPL(backing_file_open);

struct file *backing_tmpfile_open(const struct path *user_path, int flags,
				  const struct path *real_parentpath,
				  umode_t mode, const struct cred *cred)
{
	struct mnt_idmap *real_idmap = mnt_idmap(real_parentpath->mnt);
	struct file *f;
	int error;

	f = alloc_empty_backing_file(flags, cred);
	if (IS_ERR(f))
		return f;

	path_get(user_path);
	*backing_file_user_path(f) = *user_path;
	error = vfs_tmpfile(real_idmap, real_parentpath, f, mode);
	if (error) {
		fput(f);
		f = ERR_PTR(error);
	}
	return f;
}
EXPORT_SYMBOL(backing_tmpfile_open);

struct backing_aio {
	struct kiocb iocb;
	refcount_t ref;
+3 −0
Original line number Diff line number Diff line
@@ -62,6 +62,9 @@ int do_mkdirat(int dfd, struct filename *name, umode_t mode);
int do_symlinkat(struct filename *from, int newdfd, struct filename *to);
int do_linkat(int olddfd, struct filename *old, int newdfd,
			struct filename *new, int flags);
int vfs_tmpfile(struct mnt_idmap *idmap,
		const struct path *parentpath,
		struct file *file, umode_t mode);

/*
 * namespace.c
+3 −3
Original line number Diff line number Diff line
@@ -3668,7 +3668,7 @@ static int do_open(struct nameidata *nd,
 * On non-idmapped mounts or if permission checking is to be performed on the
 * raw inode simply pass @nop_mnt_idmap.
 */
static int vfs_tmpfile(struct mnt_idmap *idmap,
int vfs_tmpfile(struct mnt_idmap *idmap,
		const struct path *parentpath,
		struct file *file, umode_t mode)
{
+130 −19
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@
#include <linux/posix_acl_xattr.h>
#include <linux/atomic.h>
#include <linux/ratelimit.h>
#include <linux/backing-file.h>
#include "overlayfs.h"

static unsigned short ovl_redirect_max = 256;
@@ -260,14 +261,13 @@ static int ovl_set_opaque(struct dentry *dentry, struct dentry *upperdentry)
 * may not use to instantiate the new dentry.
 */
static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
			   struct dentry *newdentry, bool hardlink)
			   struct dentry *newdentry, bool hardlink, struct file *tmpfile)
{
	struct ovl_inode_params oip = {
		.upperdentry = newdentry,
		.newinode = inode,
	};

	ovl_dir_modified(dentry->d_parent, false);
	ovl_dentry_set_upper_alias(dentry);
	ovl_dentry_init_reval(dentry, newdentry, NULL);

@@ -295,6 +295,9 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
		inc_nlink(inode);
	}

	if (tmpfile)
		d_mark_tmpfile(tmpfile, inode);

	d_instantiate(dentry, inode);
	if (inode != oip.newinode) {
		pr_warn_ratelimited("newly created inode found in cache (%pd2)\n",
@@ -345,7 +348,8 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
		ovl_set_opaque(dentry, newdentry);
	}

	err = ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink);
	ovl_dir_modified(dentry->d_parent, false);
	err = ovl_instantiate(dentry, inode, newdentry, !!attr->hardlink, NULL);
	if (err)
		goto out_cleanup;
out_unlock:
@@ -529,7 +533,8 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
		if (err)
			goto out_cleanup;
	}
	err = ovl_instantiate(dentry, inode, newdentry, hardlink);
	ovl_dir_modified(dentry->d_parent, false);
	err = ovl_instantiate(dentry, inode, newdentry, hardlink, NULL);
	if (err) {
		ovl_cleanup(ofs, udir, newdentry);
		dput(newdentry);
@@ -551,12 +556,35 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
	goto out_dput;
}

static int ovl_setup_cred_for_create(struct dentry *dentry, struct inode *inode,
				     umode_t mode, const struct cred *old_cred)
{
	int err;
	struct cred *override_cred;

	override_cred = prepare_creds();
	if (!override_cred)
		return -ENOMEM;

	override_cred->fsuid = inode->i_uid;
	override_cred->fsgid = inode->i_gid;
	err = security_dentry_create_files_as(dentry, mode, &dentry->d_name,
					      old_cred, override_cred);
	if (err) {
		put_cred(override_cred);
		return err;
	}
	put_cred(override_creds(override_cred));
	put_cred(override_cred);

	return 0;
}

static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
			      struct ovl_cattr *attr, bool origin)
{
	int err;
	const struct cred *old_cred;
	struct cred *override_cred;
	struct dentry *parent = dentry->d_parent;

	old_cred = ovl_override_creds(dentry->d_sb);
@@ -572,10 +600,6 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
	}

	if (!attr->hardlink) {
		err = -ENOMEM;
		override_cred = prepare_creds();
		if (!override_cred)
			goto out_revert_creds;
		/*
		 * In the creation cases(create, mkdir, mknod, symlink),
		 * ovl should transfer current's fs{u,g}id to underlying
@@ -589,18 +613,10 @@ static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
		 * create a new inode, so just use the ovl mounter's
		 * fs{u,g}id.
		 */
		override_cred->fsuid = inode->i_uid;
		override_cred->fsgid = inode->i_gid;
		err = security_dentry_create_files_as(dentry,
				attr->mode, &dentry->d_name, old_cred,
				override_cred);
		if (err) {
			put_cred(override_cred);
		err = ovl_setup_cred_for_create(dentry, inode, attr->mode, old_cred);
		if (err)
			goto out_revert_creds;
	}
		put_cred(override_creds(override_cred));
		put_cred(override_cred);
	}

	if (!ovl_dentry_is_whiteout(dentry))
		err = ovl_create_upper(dentry, inode, attr);
@@ -1290,6 +1306,100 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
	return err;
}

static int ovl_create_tmpfile(struct file *file, struct dentry *dentry,
			      struct inode *inode, umode_t mode)
{
	const struct cred *old_cred;
	struct path realparentpath;
	struct file *realfile;
	struct dentry *newdentry;
	/* It's okay to set O_NOATIME, since the owner will be current fsuid */
	int flags = file->f_flags | OVL_OPEN_FLAGS;
	int err;

	err = ovl_copy_up(dentry->d_parent);
	if (err)
		return err;

	old_cred = ovl_override_creds(dentry->d_sb);
	err = ovl_setup_cred_for_create(dentry, inode, mode, old_cred);
	if (err)
		goto out_revert_creds;

	ovl_path_upper(dentry->d_parent, &realparentpath);
	realfile = backing_tmpfile_open(&file->f_path, flags, &realparentpath,
					mode, current_cred());
	err = PTR_ERR_OR_ZERO(realfile);
	pr_debug("tmpfile/open(%pd2, 0%o) = %i\n", realparentpath.dentry, mode, err);
	if (err)
		goto out_revert_creds;

	/* ovl_instantiate() consumes the newdentry reference on success */
	newdentry = dget(realfile->f_path.dentry);
	err = ovl_instantiate(dentry, inode, newdentry, false, file);
	if (!err) {
		file->private_data = realfile;
	} else {
		dput(newdentry);
		fput(realfile);
	}
out_revert_creds:
	revert_creds(old_cred);
	return err;
}

static int ovl_dummy_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int ovl_tmpfile(struct mnt_idmap *idmap, struct inode *dir,
		       struct file *file, umode_t mode)
{
	int err;
	struct dentry *dentry = file->f_path.dentry;
	struct inode *inode;

	if (!OVL_FS(dentry->d_sb)->tmpfile)
		return -EOPNOTSUPP;

	err = ovl_want_write(dentry);
	if (err)
		return err;

	err = -ENOMEM;
	inode = ovl_new_inode(dentry->d_sb, mode, 0);
	if (!inode)
		goto drop_write;

	inode_init_owner(&nop_mnt_idmap, inode, dir, mode);
	err = ovl_create_tmpfile(file, dentry, inode, inode->i_mode);
	if (err)
		goto put_inode;

	/*
	 * Check if the preallocated inode was actually used.  Having something
	 * else assigned to the dentry shouldn't happen as that would indicate
	 * that the backing tmpfile "leaked" out of overlayfs.
	 */
	err = -EIO;
	if (WARN_ON(inode != d_inode(dentry)))
		goto put_realfile;

	/* inode reference was transferred to dentry */
	inode = NULL;
	err = finish_open(file, dentry, ovl_dummy_open);
put_realfile:
	/* Without FMODE_OPENED ->release() won't be called on @file */
	if (!(file->f_mode & FMODE_OPENED))
		fput(file->private_data);
put_inode:
	iput(inode);
drop_write:
	ovl_drop_write(dentry);
	return err;
}

const struct inode_operations ovl_dir_inode_operations = {
	.lookup		= ovl_lookup,
	.mkdir		= ovl_mkdir,
@@ -1310,4 +1420,5 @@ const struct inode_operations ovl_dir_inode_operations = {
	.update_time	= ovl_update_time,
	.fileattr_get	= ovl_fileattr_get,
	.fileattr_set	= ovl_fileattr_set,
	.tmpfile	= ovl_tmpfile,
};
+0 −3
Original line number Diff line number Diff line
@@ -24,9 +24,6 @@ static char ovl_whatisit(struct inode *inode, struct inode *realinode)
		return 'm';
}

/* No atime modification on underlying */
#define OVL_OPEN_FLAGS (O_NOATIME)

static struct file *ovl_open_realfile(const struct file *file,
				      const struct path *realpath)
{
Loading