mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 05:32:00 +00:00
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: Miklos Szeredi <mszeredi@redhat.com> Reviewed-by: Amir Goldstein <amir73il@gmail.com>
This commit is contained in:
parent
ed30a4a51b
commit
9a87907de3
@ -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;
|
||||
|
@ -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
|
||||
|
@ -3668,9 +3668,9 @@ 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,
|
||||
const struct path *parentpath,
|
||||
struct file *file, umode_t mode)
|
||||
int vfs_tmpfile(struct mnt_idmap *idmap,
|
||||
const struct path *parentpath,
|
||||
struct file *file, umode_t mode)
|
||||
{
|
||||
struct dentry *child;
|
||||
struct inode *dir = d_inode(parentpath->dentry);
|
||||
|
@ -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 @@ out_cleanup:
|
||||
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,17 +613,9 @@ 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))
|
||||
@ -1290,6 +1306,100 @@ out:
|
||||
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,
|
||||
};
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -175,6 +175,9 @@ static inline int ovl_metadata_digest_size(const struct ovl_metacopy *metacopy)
|
||||
return (int)metacopy->len - OVL_METACOPY_MIN_SIZE;
|
||||
}
|
||||
|
||||
/* No atime modification on underlying */
|
||||
#define OVL_OPEN_FLAGS (O_NOATIME)
|
||||
|
||||
extern const char *const ovl_xattr_table[][2];
|
||||
static inline const char *ovl_xattr(struct ovl_fs *ofs, enum ovl_xattr ox)
|
||||
{
|
||||
|
@ -22,6 +22,9 @@ struct backing_file_ctx {
|
||||
struct file *backing_file_open(const struct path *user_path, int flags,
|
||||
const struct path *real_path,
|
||||
const struct cred *cred);
|
||||
struct file *backing_tmpfile_open(const struct path *user_path, int flags,
|
||||
const struct path *real_parentpath,
|
||||
umode_t mode, const struct cred *cred);
|
||||
ssize_t backing_file_read_iter(struct file *file, struct iov_iter *iter,
|
||||
struct kiocb *iocb, int flags,
|
||||
struct backing_file_ctx *ctx);
|
||||
|
Loading…
Reference in New Issue
Block a user