mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	Merge branch 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
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
This commit is contained in:
		
						commit
						e58bc92783
					
				
					 5 changed files with 148 additions and 27 deletions
				
			
		|  | @ -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; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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; | ||||
| 	}; | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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); | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Linus Torvalds
						Linus Torvalds