vfs: rename: use common code for dir and non-dir
There's actually very little difference between vfs_rename_dir() and vfs_rename_other() so move both inline into vfs_rename() which still stays reasonably readable. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz> Reviewed-by: J. Bruce Fields <bfields@redhat.com>
This commit is contained in:
		
							parent
							
								
									de22a4c372
								
							
						
					
					
						commit
						bc27027a73
					
				
							
								
								
									
										211
									
								
								fs/namei.c
									
									
									
									
									
								
							
							
						
						
									
										211
									
								
								fs/namei.c
									
									
									
									
									
								
							| @ -3973,7 +3973,27 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname | |||||||
| 	return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0); | 	return sys_linkat(AT_FDCWD, oldname, AT_FDCWD, newname, 0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /*
 | /**
 | ||||||
|  |  * vfs_rename - rename a filesystem object | ||||||
|  |  * @old_dir:	parent of source | ||||||
|  |  * @old_dentry:	source | ||||||
|  |  * @new_dir:	parent of destination | ||||||
|  |  * @new_dentry:	destination | ||||||
|  |  * @delegated_inode: returns an inode needing a delegation break | ||||||
|  |  * | ||||||
|  |  * The caller must hold multiple mutexes--see lock_rename()). | ||||||
|  |  * | ||||||
|  |  * If vfs_rename discovers a delegation in need of breaking at either | ||||||
|  |  * the source or destination, it will return -EWOULDBLOCK and return a | ||||||
|  |  * reference to the inode in delegated_inode.  The caller should then | ||||||
|  |  * break the delegation and retry.  Because breaking a delegation may | ||||||
|  |  * take a long time, the caller should drop all locks before doing | ||||||
|  |  * so. | ||||||
|  |  * | ||||||
|  |  * Alternatively, a caller may pass NULL for delegated_inode.  This may | ||||||
|  |  * be appropriate for callers that expect the underlying filesystem not | ||||||
|  |  * to be NFS exported. | ||||||
|  |  * | ||||||
|  * The worst of all namespace operations - renaming directory. "Perverted" |  * The worst of all namespace operations - renaming directory. "Perverted" | ||||||
|  * doesn't even start to describe it. Somebody in UCB had a heck of a trip... |  * doesn't even start to describe it. Somebody in UCB had a heck of a trip... | ||||||
|  * Problems: |  * Problems: | ||||||
| @ -4001,137 +4021,24 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname | |||||||
|  *	   ->i_mutex on parents, which works but leads to some truly excessive |  *	   ->i_mutex on parents, which works but leads to some truly excessive | ||||||
|  *	   locking]. |  *	   locking]. | ||||||
|  */ |  */ | ||||||
| static int vfs_rename_dir(struct inode *old_dir, struct dentry *old_dentry, |  | ||||||
| 			  struct inode *new_dir, struct dentry *new_dentry) |  | ||||||
| { |  | ||||||
| 	int error = 0; |  | ||||||
| 	struct inode *target = new_dentry->d_inode; |  | ||||||
| 	unsigned max_links = new_dir->i_sb->s_max_links; |  | ||||||
| 
 |  | ||||||
| 	/*
 |  | ||||||
| 	 * If we are going to change the parent - check write permissions, |  | ||||||
| 	 * we'll need to flip '..'. |  | ||||||
| 	 */ |  | ||||||
| 	if (new_dir != old_dir) { |  | ||||||
| 		error = inode_permission(old_dentry->d_inode, MAY_WRITE); |  | ||||||
| 		if (error) |  | ||||||
| 			return error; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry); |  | ||||||
| 	if (error) |  | ||||||
| 		return error; |  | ||||||
| 
 |  | ||||||
| 	dget(new_dentry); |  | ||||||
| 	if (target) |  | ||||||
| 		mutex_lock(&target->i_mutex); |  | ||||||
| 
 |  | ||||||
| 	error = -EBUSY; |  | ||||||
| 	if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) |  | ||||||
| 		goto out; |  | ||||||
| 
 |  | ||||||
| 	error = -EMLINK; |  | ||||||
| 	if (max_links && !target && new_dir != old_dir && |  | ||||||
| 	    new_dir->i_nlink >= max_links) |  | ||||||
| 		goto out; |  | ||||||
| 
 |  | ||||||
| 	if (target) |  | ||||||
| 		shrink_dcache_parent(new_dentry); |  | ||||||
| 	error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); |  | ||||||
| 	if (error) |  | ||||||
| 		goto out; |  | ||||||
| 
 |  | ||||||
| 	if (target) { |  | ||||||
| 		target->i_flags |= S_DEAD; |  | ||||||
| 		dont_mount(new_dentry); |  | ||||||
| 	} |  | ||||||
| 	if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) |  | ||||||
| 		d_move(old_dentry, new_dentry); |  | ||||||
| out: |  | ||||||
| 	if (target) |  | ||||||
| 		mutex_unlock(&target->i_mutex); |  | ||||||
| 	dput(new_dentry); |  | ||||||
| 	return error; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry, |  | ||||||
| 			    struct inode *new_dir, struct dentry *new_dentry, |  | ||||||
| 			    struct inode **delegated_inode) |  | ||||||
| { |  | ||||||
| 	struct inode *target = new_dentry->d_inode; |  | ||||||
| 	struct inode *source = old_dentry->d_inode; |  | ||||||
| 	int error; |  | ||||||
| 
 |  | ||||||
| 	error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry); |  | ||||||
| 	if (error) |  | ||||||
| 		return error; |  | ||||||
| 
 |  | ||||||
| 	dget(new_dentry); |  | ||||||
| 	lock_two_nondirectories(source, target); |  | ||||||
| 
 |  | ||||||
| 	error = -EBUSY; |  | ||||||
| 	if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry)) |  | ||||||
| 		goto out; |  | ||||||
| 
 |  | ||||||
| 	error = try_break_deleg(source, delegated_inode); |  | ||||||
| 	if (error) |  | ||||||
| 		goto out; |  | ||||||
| 	if (target) { |  | ||||||
| 		error = try_break_deleg(target, delegated_inode); |  | ||||||
| 		if (error) |  | ||||||
| 			goto out; |  | ||||||
| 	} |  | ||||||
| 	error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); |  | ||||||
| 	if (error) |  | ||||||
| 		goto out; |  | ||||||
| 
 |  | ||||||
| 	if (target) |  | ||||||
| 		dont_mount(new_dentry); |  | ||||||
| 	if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) |  | ||||||
| 		d_move(old_dentry, new_dentry); |  | ||||||
| out: |  | ||||||
| 	unlock_two_nondirectories(source, target); |  | ||||||
| 	dput(new_dentry); |  | ||||||
| 	return error; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /**
 |  | ||||||
|  * vfs_rename - rename a filesystem object |  | ||||||
|  * @old_dir:	parent of source |  | ||||||
|  * @old_dentry:	source |  | ||||||
|  * @new_dir:	parent of destination |  | ||||||
|  * @new_dentry:	destination |  | ||||||
|  * @delegated_inode: returns an inode needing a delegation break |  | ||||||
|  * |  | ||||||
|  * The caller must hold multiple mutexes--see lock_rename()). |  | ||||||
|  * |  | ||||||
|  * If vfs_rename discovers a delegation in need of breaking at either |  | ||||||
|  * the source or destination, it will return -EWOULDBLOCK and return a |  | ||||||
|  * reference to the inode in delegated_inode.  The caller should then |  | ||||||
|  * break the delegation and retry.  Because breaking a delegation may |  | ||||||
|  * take a long time, the caller should drop all locks before doing |  | ||||||
|  * so. |  | ||||||
|  * |  | ||||||
|  * Alternatively, a caller may pass NULL for delegated_inode.  This may |  | ||||||
|  * be appropriate for callers that expect the underlying filesystem not |  | ||||||
|  * to be NFS exported. |  | ||||||
|  */ |  | ||||||
| int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | ||||||
| 	       struct inode *new_dir, struct dentry *new_dentry, | 	       struct inode *new_dir, struct dentry *new_dentry, | ||||||
| 	       struct inode **delegated_inode) | 	       struct inode **delegated_inode) | ||||||
| { | { | ||||||
| 	int error; | 	int error; | ||||||
| 	int is_dir = d_is_dir(old_dentry); | 	bool is_dir = d_is_dir(old_dentry); | ||||||
| 	const unsigned char *old_name; | 	const unsigned char *old_name; | ||||||
|  | 	struct inode *source = old_dentry->d_inode; | ||||||
|  | 	struct inode *target = new_dentry->d_inode; | ||||||
| 
 | 
 | ||||||
| 	if (old_dentry->d_inode == new_dentry->d_inode) | 	if (source == target) | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
| 	error = may_delete(old_dir, old_dentry, is_dir); | 	error = may_delete(old_dir, old_dentry, is_dir); | ||||||
| 	if (error) | 	if (error) | ||||||
| 		return error; | 		return error; | ||||||
| 
 | 
 | ||||||
| 	if (!new_dentry->d_inode) | 	if (!target) | ||||||
| 		error = may_create(new_dir, new_dentry); | 		error = may_create(new_dir, new_dentry); | ||||||
| 	else | 	else | ||||||
| 		error = may_delete(new_dir, new_dentry, is_dir); | 		error = may_delete(new_dir, new_dentry, is_dir); | ||||||
| @ -4141,15 +4048,71 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, | |||||||
| 	if (!old_dir->i_op->rename) | 	if (!old_dir->i_op->rename) | ||||||
| 		return -EPERM; | 		return -EPERM; | ||||||
| 
 | 
 | ||||||
| 	old_name = fsnotify_oldname_init(old_dentry->d_name.name); | 	/*
 | ||||||
|  | 	 * If we are going to change the parent - check write permissions, | ||||||
|  | 	 * we'll need to flip '..'. | ||||||
|  | 	 */ | ||||||
|  | 	if (is_dir && new_dir != old_dir) { | ||||||
|  | 		error = inode_permission(source, MAY_WRITE); | ||||||
|  | 		if (error) | ||||||
|  | 			return error; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|  | 	error = security_inode_rename(old_dir, old_dentry, new_dir, new_dentry); | ||||||
|  | 	if (error) | ||||||
|  | 		return error; | ||||||
|  | 
 | ||||||
|  | 	old_name = fsnotify_oldname_init(old_dentry->d_name.name); | ||||||
|  | 	dget(new_dentry); | ||||||
|  | 	if (!is_dir) | ||||||
|  | 		lock_two_nondirectories(source, target); | ||||||
|  | 	else if (target) | ||||||
|  | 		mutex_lock(&target->i_mutex); | ||||||
|  | 
 | ||||||
|  | 	error = -EBUSY; | ||||||
|  | 	if (d_mountpoint(old_dentry) || d_mountpoint(new_dentry)) | ||||||
|  | 		goto out; | ||||||
|  | 
 | ||||||
|  | 	if (is_dir) { | ||||||
|  | 		unsigned max_links = new_dir->i_sb->s_max_links; | ||||||
|  | 
 | ||||||
|  | 		error = -EMLINK; | ||||||
|  | 		if (max_links && !target && new_dir != old_dir && | ||||||
|  | 		    new_dir->i_nlink >= max_links) | ||||||
|  | 			goto out; | ||||||
|  | 
 | ||||||
|  | 		if (target) | ||||||
|  | 			shrink_dcache_parent(new_dentry); | ||||||
|  | 	} else { | ||||||
|  | 		error = try_break_deleg(source, delegated_inode); | ||||||
|  | 		if (error) | ||||||
|  | 			goto out; | ||||||
|  | 		if (target) { | ||||||
|  | 			error = try_break_deleg(target, delegated_inode); | ||||||
|  | 			if (error) | ||||||
|  | 				goto out; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry); | ||||||
|  | 	if (error) | ||||||
|  | 		goto out; | ||||||
|  | 
 | ||||||
|  | 	if (target) { | ||||||
| 		if (is_dir) | 		if (is_dir) | ||||||
| 		error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry); | 			target->i_flags |= S_DEAD; | ||||||
| 	else | 		dont_mount(new_dentry); | ||||||
| 		error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode); | 	} | ||||||
|  | 	if (!(old_dir->i_sb->s_type->fs_flags & FS_RENAME_DOES_D_MOVE)) | ||||||
|  | 		d_move(old_dentry, new_dentry); | ||||||
|  | out: | ||||||
|  | 	if (!is_dir) | ||||||
|  | 		unlock_two_nondirectories(source, target); | ||||||
|  | 	else if (target) | ||||||
|  | 		mutex_unlock(&target->i_mutex); | ||||||
|  | 	dput(new_dentry); | ||||||
| 	if (!error) | 	if (!error) | ||||||
| 		fsnotify_move(old_dir, new_dir, old_name, is_dir, | 		fsnotify_move(old_dir, new_dir, old_name, is_dir, | ||||||
| 			      new_dentry->d_inode, old_dentry); | 			      target, old_dentry); | ||||||
| 	fsnotify_oldname_free(old_name); | 	fsnotify_oldname_free(old_name); | ||||||
| 
 | 
 | ||||||
| 	return error; | 	return error; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user