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

Commit ee2fd967 authored by Steve French's avatar Steve French
Browse files

[CIFS] fix busy-file renames and refactor cifs_rename logic



Break out the code that does the actual renaming into a separate
function and have cifs_rename call that. That function will attempt a
path based rename first and then do a filehandle based one if it looks
like the source is busy.

The existing logic tried a path based rename first, but if we needed to
remove the destination then it only attempted a filehandle based rename
afterward. Not all servers support renaming by filehandle, so we need to
always attempt path rename first and fall back to filehandle rename if
it doesn't work.

This also fixes renames of open files on windows servers (at least when
the source and destination directories are the same).

CC: Jeff Layton <jlayton@redhat.com>
Signed-off-by: default avatarSteve French <sfrench@us.ibm.com>
parent 6d22f098
Loading
Loading
Loading
Loading
+104 −82
Original line number Diff line number Diff line
@@ -806,8 +806,6 @@ cifs_rename_pending_delete(char *full_path, struct inode *inode, int xid)
	rc = CIFSSMBRenameOpenFile(xid, tcon, netfid, NULL, cifs_sb->local_nls,
				   cifs_sb->mnt_cifs_flags &
					    CIFS_MOUNT_MAP_SPECIAL_CHR);
	if (rc != 0)
		goto out_close;

	/* set DELETE_ON_CLOSE */
	rc = CIFSSMBSetFileDisposition(xid, tcon, true, netfid, current->tgid);
@@ -1180,117 +1178,141 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
	return rc;
}

static int
cifs_do_rename(int xid, struct dentry *from_dentry, const char *fromPath,
		struct dentry *to_dentry, const char *toPath)
{
	struct cifs_sb_info *cifs_sb = CIFS_SB(from_dentry->d_sb);
	struct cifsTconInfo *pTcon = cifs_sb->tcon;
	__u16 srcfid;
	int oplock, rc;

	/* try path-based rename first */
	rc = CIFSSMBRename(xid, pTcon, fromPath, toPath, cifs_sb->local_nls,
			   cifs_sb->mnt_cifs_flags &
				CIFS_MOUNT_MAP_SPECIAL_CHR);

	/*
	 * don't bother with rename by filehandle unless file is busy and
	 * source Note that cross directory moves do not work with
	 * rename by filehandle to various Windows servers.
	 */
	if (rc == 0 || rc != -ETXTBSY)
		return rc;

	/* open the file to be renamed -- we need DELETE perms */
	rc = CIFSSMBOpen(xid, pTcon, fromPath, FILE_OPEN, DELETE,
			 CREATE_NOT_DIR, &srcfid, &oplock, NULL,
			 cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
				CIFS_MOUNT_MAP_SPECIAL_CHR);

	if (rc == 0) {
		rc = CIFSSMBRenameOpenFile(xid, pTcon, srcfid,
				(const char *) to_dentry->d_name.name,
				cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
					CIFS_MOUNT_MAP_SPECIAL_CHR);

		CIFSSMBClose(xid, pTcon, srcfid);
	}

	return rc;
}

int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
	struct inode *target_inode, struct dentry *target_direntry)
{
	char *fromName;
	char *toName;
	char *fromName = NULL;
	char *toName = NULL;
	struct cifs_sb_info *cifs_sb_source;
	struct cifs_sb_info *cifs_sb_target;
	struct cifsTconInfo *pTcon;
	FILE_UNIX_BASIC_INFO *info_buf_source = NULL;
	FILE_UNIX_BASIC_INFO *info_buf_target;
	int xid;
	int rc = 0;

	xid = GetXid();
	int rc;

	cifs_sb_target = CIFS_SB(target_inode->i_sb);
	cifs_sb_source = CIFS_SB(source_inode->i_sb);
	pTcon = cifs_sb_source->tcon;

	xid = GetXid();

	/*
	 * BB: this might be allowed if same server, but different share.
	 * Consider adding support for this
	 */
	if (pTcon != cifs_sb_target->tcon) {
		FreeXid(xid);
		return -EXDEV;	/* BB actually could be allowed if same server,
				   but different share.
				   Might eventually add support for this */
		rc = -EXDEV;
		goto cifs_rename_exit;
	}

	/* we already  have the rename sem so we do not need to grab it again
	   here to protect the path integrity */
	/*
	 * we already have the rename sem so we do not need to
	 * grab it again here to protect the path integrity
	 */
	fromName = build_path_from_dentry(source_direntry);
	if (fromName == NULL) {
		rc = -ENOMEM;
		goto cifs_rename_exit;
	}

	toName = build_path_from_dentry(target_direntry);
	if ((fromName == NULL) || (toName == NULL)) {
	if (toName == NULL) {
		rc = -ENOMEM;
		goto cifs_rename_exit;
	}

	rc = CIFSSMBRename(xid, pTcon, fromName, toName,
			   cifs_sb_source->local_nls,
			   cifs_sb_source->mnt_cifs_flags &
				CIFS_MOUNT_MAP_SPECIAL_CHR);
	if (rc == -EEXIST) {
		/* check if they are the same file because rename of hardlinked
		   files is a noop */
		FILE_UNIX_BASIC_INFO *info_buf_source;
		FILE_UNIX_BASIC_INFO *info_buf_target;
	rc = cifs_do_rename(xid, source_direntry, fromName,
			    target_direntry, toName);

	if (rc == -EEXIST) {
		if (pTcon->unix_ext) {
			/*
			 * Are src and dst hardlinks of same inode? We can
			 * only tell with unix extensions enabled
			 */
			info_buf_source =
			kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO), GFP_KERNEL);
		if (info_buf_source != NULL) {
				kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO),
						GFP_KERNEL);
			if (info_buf_source != NULL)
				goto unlink_target;

			info_buf_target = info_buf_source + 1;
			if (pTcon->unix_ext)
			rc = CIFSSMBUnixQPathInfo(xid, pTcon, fromName,
						info_buf_source,
						cifs_sb_source->local_nls,
						cifs_sb_source->mnt_cifs_flags &
						CIFS_MOUNT_MAP_SPECIAL_CHR);
			/* else rc is still EEXIST so will fall through to
			   unlink the target and retry rename */
			if (rc == 0) {
				rc = CIFSSMBUnixQPathInfo(xid, pTcon, toName,
						info_buf_target,
			if (rc != 0)
				goto unlink_target;

			rc = CIFSSMBUnixQPathInfo(xid, pTcon,
						toName, info_buf_target,
						cifs_sb_target->local_nls,
						/* remap based on source sb */
						cifs_sb_source->mnt_cifs_flags &
						CIFS_MOUNT_MAP_SPECIAL_CHR);
			}
			if ((rc == 0) &&
			    (info_buf_source->UniqueId ==
			     info_buf_target->UniqueId)) {
			/* do not rename since the files are hardlinked which
			   is a noop */
			} else {
			/* we either can not tell the files are hardlinked
			   (as with Windows servers) or files are not
			   hardlinked so delete the target manually before
			   renaming to follow POSIX rather than Windows
			   semantics */
				cifs_unlink(target_inode, target_direntry);
				rc = CIFSSMBRename(xid, pTcon, fromName,
						   toName,
						   cifs_sb_source->local_nls,
						   cifs_sb_source->mnt_cifs_flags
						   & CIFS_MOUNT_MAP_SPECIAL_CHR);
			}
			kfree(info_buf_source);
		} /* if we can not get memory just leave rc as EEXIST */
	}

	if (rc)
		cFYI(1, ("rename rc %d", rc));

	if ((rc == -EIO) || (rc == -EEXIST)) {
		int oplock = 0;
		__u16 netfid;

		/* BB FIXME Is Generic Read correct for rename? */
		/* if renaming directory - we should not say CREATE_NOT_DIR,
		   need to test renaming open directory, also GENERIC_READ
		   might not right be right access to request */
		rc = CIFSSMBOpen(xid, pTcon, fromName, FILE_OPEN, GENERIC_READ,
				 CREATE_NOT_DIR, &netfid, &oplock, NULL,
				 cifs_sb_source->local_nls,
				 cifs_sb_source->mnt_cifs_flags &
					CIFS_MOUNT_MAP_SPECIAL_CHR);
		if (rc == 0) {
			rc = CIFSSMBRenameOpenFile(xid, pTcon, netfid, toName,
					      cifs_sb_source->local_nls,
					      cifs_sb_source->mnt_cifs_flags &
						CIFS_MOUNT_MAP_SPECIAL_CHR);
			CIFSSMBClose(xid, pTcon, netfid);
		}
			if (rc == 0 && (info_buf_source->UniqueId ==
					info_buf_target->UniqueId))
				/* same file, POSIX says that this is a noop */
				goto cifs_rename_exit;
		} /* else ... BB we could add the same check for Windows by
		     checking the UniqueId via FILE_INTERNAL_INFO */
unlink_target:
		/*
		 * we either can not tell the files are hardlinked (as with
		 * Windows servers) or files are not hardlinked. Delete the
		 * target manually before renaming to follow POSIX rather than
		 * Windows semantics
		 */
		cifs_unlink(target_inode, target_direntry);
		rc = cifs_do_rename(xid, source_direntry, fromName,
				    target_direntry, toName);
	}

cifs_rename_exit:
	kfree(info_buf_source);
	kfree(fromName);
	kfree(toName);
	FreeXid(xid);