Commit 14121bdc authored by Jeff Layton's avatar Jeff Layton Committed by Steve French
Browse files

cifs: make cifs_rename handle -EACCES errors



cifs: make cifs_rename handle -EACCES errors

Some servers seem to return -EACCES when attempting to rename one
open file on top of another. Refactor the cifs_rename logic to
attempt to rename the target file out of the way in this situation.

This also fixes the "unlink_target" logic to be undoable if the
subsequent rename fails.

Signed-off-by: default avatarJeff Layton <jlayton@redhat.com>
Signed-off-by: default avatarSteve French <sfrench@us.ibm.com>
parent 41346098
Loading
Loading
Loading
Loading
+122 −52
Original line number Diff line number Diff line
@@ -1285,22 +1285,24 @@ cifs_do_rename(int xid, struct dentry *from_dentry, const char *fromPath,
	return rc;
}

int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
	struct inode *target_inode, struct dentry *target_direntry)
int cifs_rename(struct inode *source_dir, struct dentry *source_dentry,
	struct inode *target_dir, struct dentry *target_dentry)
{
	char *fromName = NULL;
	char *toName = NULL;
	struct cifs_sb_info *cifs_sb_source;
	struct cifs_sb_info *cifs_sb_target;
	struct cifsTconInfo *pTcon;
	struct cifsTconInfo *tcon;
	struct cifsInodeInfo *target_cinode;
	FILE_UNIX_BASIC_INFO *info_buf_source = NULL;
	FILE_UNIX_BASIC_INFO *info_buf_target;
	int xid;
	int rc;
	__u16 dstfid;
	int xid, rc, tmprc, oplock = 0;
	bool delete_already_pending;

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

	xid = GetXid();

@@ -1308,7 +1310,7 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
	 * BB: this might be allowed if same server, but different share.
	 * Consider adding support for this
	 */
	if (pTcon != cifs_sb_target->tcon) {
	if (tcon != cifs_sb_target->tcon) {
		rc = -EXDEV;
		goto cifs_rename_exit;
	}
@@ -1317,23 +1319,22 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
	 * 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);
	fromName = build_path_from_dentry(source_dentry);
	if (fromName == NULL) {
		rc = -ENOMEM;
		goto cifs_rename_exit;
	}

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

	rc = cifs_do_rename(xid, source_direntry, fromName,
			    target_direntry, toName);
	rc = cifs_do_rename(xid, source_dentry, fromName,
			    target_dentry, toName);

	if (rc == -EEXIST) {
		if (pTcon->unix_ext) {
	if (rc == -EEXIST && tcon->unix_ext) {
		/*
		 * Are src and dst hardlinks of same inode? We can
		 * only tell with unix extensions enabled
@@ -1341,11 +1342,13 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
		info_buf_source =
			kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO),
					GFP_KERNEL);
			if (info_buf_source == NULL)
				goto unlink_target;
		if (info_buf_source == NULL) {
			rc = -ENOMEM;
			goto cifs_rename_exit;
		}

		info_buf_target = info_buf_source + 1;
			rc = CIFSSMBUnixQPathInfo(xid, pTcon, fromName,
		rc = CIFSSMBUnixQPathInfo(xid, tcon, fromName,
					info_buf_source,
					cifs_sb_source->local_nls,
					cifs_sb_source->mnt_cifs_flags &
@@ -1353,7 +1356,7 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
		if (rc != 0)
			goto unlink_target;

			rc = CIFSSMBUnixQPathInfo(xid, pTcon,
		rc = CIFSSMBUnixQPathInfo(xid, tcon,
					toName, info_buf_target,
					cifs_sb_target->local_nls,
					/* remap based on source sb */
@@ -1364,18 +1367,85 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
				info_buf_target->UniqueId))
			/* same file, POSIX says that this is a noop */
			goto cifs_rename_exit;

		rc = -EEXIST;
	} /* else ... BB we could add the same check for Windows by
		     checking the UniqueId via FILE_INTERNAL_INFO */

	if ((rc == -EACCES) || (rc == -EEXIST)) {
unlink_target:
		/* don't bother if this is a negative dentry */
		if (!target_dentry->d_inode)
			goto cifs_rename_exit;

		target_cinode = CIFS_I(target_dentry->d_inode);

		/* try to move the target out of the way */
		tmprc = CIFSSMBOpen(xid, tcon, toName, FILE_OPEN, DELETE,
				    CREATE_NOT_DIR, &dstfid, &oplock, NULL,
				    cifs_sb_target->local_nls,
				    cifs_sb_target->mnt_cifs_flags &
					CIFS_MOUNT_MAP_SPECIAL_CHR);
		if (tmprc)
			goto cifs_rename_exit;

		/* rename the file to random name */
		tmprc = CIFSSMBRenameOpenFile(xid, tcon, dstfid, NULL,
					      cifs_sb_target->local_nls,
					      cifs_sb_target->mnt_cifs_flags &
						CIFS_MOUNT_MAP_SPECIAL_CHR);

		if (tmprc)
			goto close_target;

		delete_already_pending = target_cinode->delete_pending;

		if (!delete_already_pending) {
			/* set delete on close */
			tmprc = CIFSSMBSetFileDisposition(xid, tcon,
							  true, dstfid,
							  current->tgid);
			/*
		 * 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
			 * This hack is for broken samba servers, remove this
			 * once more fixed ones are in the field.
			 */
		cifs_unlink(target_inode, target_direntry);
		rc = cifs_do_rename(xid, source_direntry, fromName,
				    target_direntry, toName);
			if (tmprc == -ENOENT)
				delete_already_pending = false;
			else if (tmprc)
				goto undo_target_rename;

			target_cinode->delete_pending = true;
		}


		rc = cifs_do_rename(xid, source_dentry, fromName,
				    target_dentry, toName);

		if (rc == 0)
			goto close_target;

		/*
		 * after this point, we can't bother with error handling on
		 * the undo's. This is best effort since we can't do anything
		 * about failures here.
		 */
		if (!delete_already_pending) {
			tmprc = CIFSSMBSetFileDisposition(xid, tcon,
							  false, dstfid,
							  current->tgid);
			if (tmprc == 0)
				target_cinode->delete_pending = false;
		}

undo_target_rename:
		/* rename failed: undo target rename */
		CIFSSMBRenameOpenFile(xid, tcon, dstfid,
				      target_dentry->d_name.name,
				      cifs_sb_target->local_nls,
				      cifs_sb_target->mnt_cifs_flags &
					CIFS_MOUNT_MAP_SPECIAL_CHR);
close_target:
		CIFSSMBClose(xid, tcon, dstfid);
	}

cifs_rename_exit: