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:
Miklos Szeredi 2024-05-02 20:35:57 +02:00
parent ed30a4a51b
commit 9a87907de3
7 changed files with 165 additions and 25 deletions

View File

@ -52,6 +52,29 @@ struct file *backing_file_open(const struct path *user_path, int flags,
} }
EXPORT_SYMBOL_GPL(backing_file_open); 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 backing_aio {
struct kiocb iocb; struct kiocb iocb;
refcount_t ref; refcount_t ref;

View File

@ -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_symlinkat(struct filename *from, int newdfd, struct filename *to);
int do_linkat(int olddfd, struct filename *old, int newdfd, int do_linkat(int olddfd, struct filename *old, int newdfd,
struct filename *new, int flags); struct filename *new, int flags);
int vfs_tmpfile(struct mnt_idmap *idmap,
const struct path *parentpath,
struct file *file, umode_t mode);
/* /*
* namespace.c * namespace.c

View File

@ -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 * On non-idmapped mounts or if permission checking is to be performed on the
* raw inode simply pass @nop_mnt_idmap. * 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, const struct path *parentpath,
struct file *file, umode_t mode) struct file *file, umode_t mode)
{ {
struct dentry *child; struct dentry *child;
struct inode *dir = d_inode(parentpath->dentry); struct inode *dir = d_inode(parentpath->dentry);

View File

@ -14,6 +14,7 @@
#include <linux/posix_acl_xattr.h> #include <linux/posix_acl_xattr.h>
#include <linux/atomic.h> #include <linux/atomic.h>
#include <linux/ratelimit.h> #include <linux/ratelimit.h>
#include <linux/backing-file.h>
#include "overlayfs.h" #include "overlayfs.h"
static unsigned short ovl_redirect_max = 256; 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. * may not use to instantiate the new dentry.
*/ */
static int ovl_instantiate(struct dentry *dentry, struct inode *inode, 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 = { struct ovl_inode_params oip = {
.upperdentry = newdentry, .upperdentry = newdentry,
.newinode = inode, .newinode = inode,
}; };
ovl_dir_modified(dentry->d_parent, false);
ovl_dentry_set_upper_alias(dentry); ovl_dentry_set_upper_alias(dentry);
ovl_dentry_init_reval(dentry, newdentry, NULL); ovl_dentry_init_reval(dentry, newdentry, NULL);
@ -295,6 +295,9 @@ static int ovl_instantiate(struct dentry *dentry, struct inode *inode,
inc_nlink(inode); inc_nlink(inode);
} }
if (tmpfile)
d_mark_tmpfile(tmpfile, inode);
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
if (inode != oip.newinode) { if (inode != oip.newinode) {
pr_warn_ratelimited("newly created inode found in cache (%pd2)\n", 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); 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) if (err)
goto out_cleanup; goto out_cleanup;
out_unlock: out_unlock:
@ -529,7 +533,8 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
if (err) if (err)
goto out_cleanup; 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) { if (err) {
ovl_cleanup(ofs, udir, newdentry); ovl_cleanup(ofs, udir, newdentry);
dput(newdentry); dput(newdentry);
@ -551,12 +556,35 @@ out_cleanup:
goto out_dput; 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, static int ovl_create_or_link(struct dentry *dentry, struct inode *inode,
struct ovl_cattr *attr, bool origin) struct ovl_cattr *attr, bool origin)
{ {
int err; int err;
const struct cred *old_cred; const struct cred *old_cred;
struct cred *override_cred;
struct dentry *parent = dentry->d_parent; struct dentry *parent = dentry->d_parent;
old_cred = ovl_override_creds(dentry->d_sb); 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) { if (!attr->hardlink) {
err = -ENOMEM;
override_cred = prepare_creds();
if (!override_cred)
goto out_revert_creds;
/* /*
* In the creation cases(create, mkdir, mknod, symlink), * In the creation cases(create, mkdir, mknod, symlink),
* ovl should transfer current's fs{u,g}id to underlying * 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 * create a new inode, so just use the ovl mounter's
* fs{u,g}id. * fs{u,g}id.
*/ */
override_cred->fsuid = inode->i_uid; err = ovl_setup_cred_for_create(dentry, inode, attr->mode, old_cred);
override_cred->fsgid = inode->i_gid; if (err)
err = security_dentry_create_files_as(dentry,
attr->mode, &dentry->d_name, old_cred,
override_cred);
if (err) {
put_cred(override_cred);
goto out_revert_creds; goto out_revert_creds;
}
put_cred(override_creds(override_cred));
put_cred(override_cred);
} }
if (!ovl_dentry_is_whiteout(dentry)) if (!ovl_dentry_is_whiteout(dentry))
@ -1290,6 +1306,100 @@ out:
return err; 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 = { const struct inode_operations ovl_dir_inode_operations = {
.lookup = ovl_lookup, .lookup = ovl_lookup,
.mkdir = ovl_mkdir, .mkdir = ovl_mkdir,
@ -1310,4 +1420,5 @@ const struct inode_operations ovl_dir_inode_operations = {
.update_time = ovl_update_time, .update_time = ovl_update_time,
.fileattr_get = ovl_fileattr_get, .fileattr_get = ovl_fileattr_get,
.fileattr_set = ovl_fileattr_set, .fileattr_set = ovl_fileattr_set,
.tmpfile = ovl_tmpfile,
}; };

View File

@ -24,9 +24,6 @@ static char ovl_whatisit(struct inode *inode, struct inode *realinode)
return 'm'; return 'm';
} }
/* No atime modification on underlying */
#define OVL_OPEN_FLAGS (O_NOATIME)
static struct file *ovl_open_realfile(const struct file *file, static struct file *ovl_open_realfile(const struct file *file,
const struct path *realpath) const struct path *realpath)
{ {

View File

@ -175,6 +175,9 @@ static inline int ovl_metadata_digest_size(const struct ovl_metacopy *metacopy)
return (int)metacopy->len - OVL_METACOPY_MIN_SIZE; 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]; extern const char *const ovl_xattr_table[][2];
static inline const char *ovl_xattr(struct ovl_fs *ofs, enum ovl_xattr ox) static inline const char *ovl_xattr(struct ovl_fs *ofs, enum ovl_xattr ox)
{ {

View File

@ -22,6 +22,9 @@ struct backing_file_ctx {
struct file *backing_file_open(const struct path *user_path, int flags, struct file *backing_file_open(const struct path *user_path, int flags,
const struct path *real_path, const struct path *real_path,
const struct cred *cred); 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, ssize_t backing_file_read_iter(struct file *file, struct iov_iter *iter,
struct kiocb *iocb, int flags, struct kiocb *iocb, int flags,
struct backing_file_ctx *ctx); struct backing_file_ctx *ctx);