Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e58bc927 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull overlayfs updates from Miklos Szeredi:
 "Because copy up can take a long time, serialized copy ups could be a
  big performance bottleneck. This update allows concurrent copy up of
  regular files eliminating this potential problem.

  There are also minor fixes"

* 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs:
  ovl: drop CAP_SYS_RESOURCE from saved mounter's credentials
  ovl: properly implement sync_filesystem()
  ovl: concurrent copy up of regular files
  ovl: introduce copy up waitqueue
  ovl: copy up regular file using O_TMPFILE
  ovl: rearrange code in ovl_copy_up_locked()
  ovl: check if upperdir fs supports O_TMPFILE
parents 590dce2d 51f8f3c4
Loading
Loading
Loading
Loading
+66 −25
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <linux/fdtable.h>
#include <linux/ratelimit.h>
#include "overlayfs.h"
#include "ovl_entry.h"

#define OVL_COPY_UP_CHUNK_SIZE (1 << 20)

@@ -233,12 +234,14 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat)

static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
			      struct dentry *dentry, struct path *lowerpath,
			      struct kstat *stat, const char *link)
			      struct kstat *stat, const char *link,
			      struct kstat *pstat, bool tmpfile)
{
	struct inode *wdir = workdir->d_inode;
	struct inode *udir = upperdir->d_inode;
	struct dentry *newdentry = NULL;
	struct dentry *upper = NULL;
	struct dentry *temp = NULL;
	int err;
	const struct cred *old_creds = NULL;
	struct cred *new_creds = NULL;
@@ -249,25 +252,30 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,
		.link = link
	};

	newdentry = ovl_lookup_temp(workdir, dentry);
	err = PTR_ERR(newdentry);
	if (IS_ERR(newdentry))
		goto out;

	upper = lookup_one_len(dentry->d_name.name, upperdir,
			       dentry->d_name.len);
	err = PTR_ERR(upper);
	if (IS_ERR(upper))
		goto out1;
		goto out;

	err = security_inode_copy_up(dentry, &new_creds);
	if (err < 0)
		goto out2;
		goto out1;

	if (new_creds)
		old_creds = override_creds(new_creds);

	err = ovl_create_real(wdir, newdentry, &cattr, NULL, true);
	if (tmpfile)
		temp = ovl_do_tmpfile(upperdir, stat->mode);
	else
		temp = ovl_lookup_temp(workdir, dentry);
	err = PTR_ERR(temp);
	if (IS_ERR(temp))
		goto out1;

	err = 0;
	if (!tmpfile)
		err = ovl_create_real(wdir, temp, &cattr, NULL, true);

	if (new_creds) {
		revert_creds(old_creds);
@@ -282,39 +290,55 @@ static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir,

		ovl_path_upper(dentry, &upperpath);
		BUG_ON(upperpath.dentry != NULL);
		upperpath.dentry = newdentry;
		upperpath.dentry = temp;

		if (tmpfile) {
			inode_unlock(udir);
			err = ovl_copy_up_data(lowerpath, &upperpath,
					       stat->size);
			inode_lock_nested(udir, I_MUTEX_PARENT);
		} else {
			err = ovl_copy_up_data(lowerpath, &upperpath,
					       stat->size);
		}

		err = ovl_copy_up_data(lowerpath, &upperpath, stat->size);
		if (err)
			goto out_cleanup;
	}

	err = ovl_copy_xattr(lowerpath->dentry, newdentry);
	err = ovl_copy_xattr(lowerpath->dentry, temp);
	if (err)
		goto out_cleanup;

	inode_lock(newdentry->d_inode);
	err = ovl_set_attr(newdentry, stat);
	inode_unlock(newdentry->d_inode);
	inode_lock(temp->d_inode);
	err = ovl_set_attr(temp, stat);
	inode_unlock(temp->d_inode);
	if (err)
		goto out_cleanup;

	err = ovl_do_rename(wdir, newdentry, udir, upper, 0);
	if (tmpfile)
		err = ovl_do_link(temp, udir, upper, true);
	else
		err = ovl_do_rename(wdir, temp, udir, upper, 0);
	if (err)
		goto out_cleanup;

	newdentry = dget(tmpfile ? upper : temp);
	ovl_dentry_update(dentry, newdentry);
	ovl_inode_update(d_inode(dentry), d_inode(newdentry));
	newdentry = NULL;

	/* Restore timestamps on parent (best effort) */
	ovl_set_timestamps(upperdir, pstat);
out2:
	dput(upper);
	dput(temp);
out1:
	dput(newdentry);
	dput(upper);
out:
	return err;

out_cleanup:
	ovl_cleanup(wdir, newdentry);
	if (!tmpfile)
		ovl_cleanup(wdir, temp);
	goto out2;
}

@@ -338,6 +362,7 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
	struct dentry *lowerdentry = lowerpath->dentry;
	struct dentry *upperdir;
	const char *link = NULL;
	struct ovl_fs *ofs = dentry->d_sb->s_fs_info;

	if (WARN_ON(!workdir))
		return -EROFS;
@@ -358,6 +383,25 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
			return PTR_ERR(link);
	}

	/* Should we copyup with O_TMPFILE or with workdir? */
	if (S_ISREG(stat->mode) && ofs->tmpfile) {
		err = ovl_copy_up_start(dentry);
		/* err < 0: interrupted, err > 0: raced with another copy-up */
		if (unlikely(err)) {
			pr_debug("ovl_copy_up_start(%pd2) = %i\n", dentry, err);
			if (err > 0)
				err = 0;
			goto out_done;
		}

		inode_lock_nested(upperdir->d_inode, I_MUTEX_PARENT);
		err = ovl_copy_up_locked(workdir, upperdir, dentry, lowerpath,
					 stat, link, &pstat, true);
		inode_unlock(upperdir->d_inode);
		ovl_copy_up_end(dentry);
		goto out_done;
	}

	err = -EIO;
	if (lock_rename(workdir, upperdir) != NULL) {
		pr_err("overlayfs: failed to lock workdir+upperdir\n");
@@ -370,13 +414,10 @@ static int ovl_copy_up_one(struct dentry *parent, struct dentry *dentry,
	}

	err = ovl_copy_up_locked(workdir, upperdir, dentry, lowerpath,
				 stat, link);
	if (!err) {
		/* Restore timestamps on parent (best effort) */
		ovl_set_timestamps(upperdir, &pstat);
	}
				 stat, link, &pstat, false);
out_unlock:
	unlock_rename(workdir, upperdir);
out_done:
	do_delayed_call(&done);

	return err;
+11 −0
Original line number Diff line number Diff line
@@ -127,6 +127,15 @@ static inline int ovl_do_whiteout(struct inode *dir, struct dentry *dentry)
	return err;
}

static inline struct dentry *ovl_do_tmpfile(struct dentry *dentry, umode_t mode)
{
	struct dentry *ret = vfs_tmpfile(dentry, mode, 0);
	int err = IS_ERR(ret) ? PTR_ERR(ret) : 0;

	pr_debug("tmpfile(%pd2, 0%o) = %i\n", dentry, mode, err);
	return ret;
}

static inline struct inode *ovl_inode_real(struct inode *inode, bool *is_upper)
{
	unsigned long x = (unsigned long) READ_ONCE(inode->i_private);
@@ -169,6 +178,8 @@ void ovl_dentry_version_inc(struct dentry *dentry);
u64 ovl_dentry_version_get(struct dentry *dentry);
bool ovl_is_whiteout(struct dentry *dentry);
struct file *ovl_path_open(struct path *path, int flags);
int ovl_copy_up_start(struct dentry *dentry);
void ovl_copy_up_end(struct dentry *dentry);

/* namei.c */
int ovl_path_next(int idx, struct dentry *dentry, struct path *path);
+3 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ struct ovl_fs {
	struct ovl_config config;
	/* creds of process who forced instantiation of super block */
	const struct cred *creator_cred;
	bool tmpfile;
	wait_queue_head_t copyup_wq;
};

/* private information held for every overlayfs dentry */
@@ -38,6 +40,7 @@ struct ovl_entry {
			u64 version;
			const char *redirect;
			bool opaque;
			bool copying;
		};
		struct rcu_head rcu;
	};
+37 −2
Original line number Diff line number Diff line
@@ -161,6 +161,25 @@ static void ovl_put_super(struct super_block *sb)
	kfree(ufs);
}

static int ovl_sync_fs(struct super_block *sb, int wait)
{
	struct ovl_fs *ufs = sb->s_fs_info;
	struct super_block *upper_sb;
	int ret;

	if (!ufs->upper_mnt)
		return 0;
	upper_sb = ufs->upper_mnt->mnt_sb;
	if (!upper_sb->s_op->sync_fs)
		return 0;

	/* real inodes have already been synced by sync_filesystem(ovl_sb) */
	down_read(&upper_sb->s_umount);
	ret = upper_sb->s_op->sync_fs(upper_sb, wait);
	up_read(&upper_sb->s_umount);
	return ret;
}

/**
 * ovl_statfs
 * @sb: The overlayfs super block
@@ -223,6 +242,7 @@ static int ovl_remount(struct super_block *sb, int *flags, char *data)

static const struct super_operations ovl_super_operations = {
	.put_super	= ovl_put_super,
	.sync_fs	= ovl_sync_fs,
	.statfs		= ovl_statfs,
	.show_options	= ovl_show_options,
	.remount_fs	= ovl_remount,
@@ -702,6 +722,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
	unsigned int stacklen = 0;
	unsigned int i;
	bool remote = false;
	struct cred *cred;
	int err;

	err = -ENOMEM;
@@ -709,6 +730,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
	if (!ufs)
		goto out;

	init_waitqueue_head(&ufs->copyup_wq);
	ufs->config.redirect_dir = ovl_redirect_dir_def;
	err = ovl_parse_opt((char *) data, &ufs->config);
	if (err)
@@ -826,6 +848,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
		 * creation of workdir in previous step.
		 */
		if (ufs->workdir) {
			struct dentry *temp;

			err = ovl_check_d_type_supported(&workpath);
			if (err < 0)
				goto out_put_workdir;
@@ -837,6 +861,14 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
			 */
			if (!err)
				pr_warn("overlayfs: upper fs needs to support d_type.\n");

			/* Check if upper/work fs supports O_TMPFILE */
			temp = ovl_do_tmpfile(ufs->workdir, S_IFREG | 0);
			ufs->tmpfile = !IS_ERR(temp);
			if (ufs->tmpfile)
				dput(temp);
			else
				pr_warn("overlayfs: upper fs does not support tmpfile.\n");
		}
	}

@@ -871,10 +903,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
	else
		sb->s_d_op = &ovl_dentry_operations;

	ufs->creator_cred = prepare_creds();
	if (!ufs->creator_cred)
	ufs->creator_cred = cred = prepare_creds();
	if (!cred)
		goto out_put_lower_mnt;

	/* Never override disk quota limits or use reserved space */
	cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);

	err = -ENOMEM;
	oe = ovl_alloc_entry(numlower);
	if (!oe)
+31 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <linux/slab.h>
#include <linux/cred.h>
#include <linux/xattr.h>
#include <linux/sched/signal.h>
#include "overlayfs.h"
#include "ovl_entry.h"

@@ -264,3 +265,33 @@ struct file *ovl_path_open(struct path *path, int flags)
{
	return dentry_open(path, flags | O_NOATIME, current_cred());
}

int ovl_copy_up_start(struct dentry *dentry)
{
	struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
	struct ovl_entry *oe = dentry->d_fsdata;
	int err;

	spin_lock(&ofs->copyup_wq.lock);
	err = wait_event_interruptible_locked(ofs->copyup_wq, !oe->copying);
	if (!err) {
		if (oe->__upperdentry)
			err = 1; /* Already copied up */
		else
			oe->copying = true;
	}
	spin_unlock(&ofs->copyup_wq.lock);

	return err;
}

void ovl_copy_up_end(struct dentry *dentry)
{
	struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
	struct ovl_entry *oe = dentry->d_fsdata;

	spin_lock(&ofs->copyup_wq.lock);
	oe->copying = false;
	wake_up_locked(&ofs->copyup_wq);
	spin_unlock(&ofs->copyup_wq.lock);
}