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

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
...@@ -1285,22 +1285,24 @@ cifs_do_rename(int xid, struct dentry *from_dentry, const char *fromPath, ...@@ -1285,22 +1285,24 @@ cifs_do_rename(int xid, struct dentry *from_dentry, const char *fromPath,
return rc; return rc;
} }
int cifs_rename(struct inode *source_inode, struct dentry *source_direntry, int cifs_rename(struct inode *source_dir, struct dentry *source_dentry,
struct inode *target_inode, struct dentry *target_direntry) struct inode *target_dir, struct dentry *target_dentry)
{ {
char *fromName = NULL; char *fromName = NULL;
char *toName = NULL; char *toName = NULL;
struct cifs_sb_info *cifs_sb_source; struct cifs_sb_info *cifs_sb_source;
struct cifs_sb_info *cifs_sb_target; 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_source = NULL;
FILE_UNIX_BASIC_INFO *info_buf_target; FILE_UNIX_BASIC_INFO *info_buf_target;
int xid; __u16 dstfid;
int rc; int xid, rc, tmprc, oplock = 0;
bool delete_already_pending;
cifs_sb_target = CIFS_SB(target_inode->i_sb); cifs_sb_target = CIFS_SB(target_dir->i_sb);
cifs_sb_source = CIFS_SB(source_inode->i_sb); cifs_sb_source = CIFS_SB(source_dir->i_sb);
pTcon = cifs_sb_source->tcon; tcon = cifs_sb_source->tcon;
xid = GetXid(); xid = GetXid();
...@@ -1308,7 +1310,7 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry, ...@@ -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. * BB: this might be allowed if same server, but different share.
* Consider adding support for this * Consider adding support for this
*/ */
if (pTcon != cifs_sb_target->tcon) { if (tcon != cifs_sb_target->tcon) {
rc = -EXDEV; rc = -EXDEV;
goto cifs_rename_exit; goto cifs_rename_exit;
} }
...@@ -1317,65 +1319,133 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry, ...@@ -1317,65 +1319,133 @@ int cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
* we already have the rename sem so we do not need to * we already have the rename sem so we do not need to
* grab it again here to protect the path integrity * 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) { if (fromName == NULL) {
rc = -ENOMEM; rc = -ENOMEM;
goto cifs_rename_exit; goto cifs_rename_exit;
} }
toName = build_path_from_dentry(target_direntry); toName = build_path_from_dentry(target_dentry);
if (toName == NULL) { if (toName == NULL) {
rc = -ENOMEM; rc = -ENOMEM;
goto cifs_rename_exit; goto cifs_rename_exit;
} }
rc = cifs_do_rename(xid, source_direntry, fromName, rc = cifs_do_rename(xid, source_dentry, fromName,
target_direntry, toName); target_dentry, toName);
if (rc == -EEXIST) { if (rc == -EEXIST && tcon->unix_ext) {
if (pTcon->unix_ext) { /*
/* * Are src and dst hardlinks of same inode? We can
* Are src and dst hardlinks of same inode? We can * only tell with unix extensions enabled
* only tell with unix extensions enabled */
*/ info_buf_source =
info_buf_source = kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO),
kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO), GFP_KERNEL);
GFP_KERNEL); if (info_buf_source == NULL) {
if (info_buf_source == NULL) rc = -ENOMEM;
goto unlink_target; goto cifs_rename_exit;
}
info_buf_target = info_buf_source + 1;
rc = CIFSSMBUnixQPathInfo(xid, pTcon, fromName, info_buf_target = info_buf_source + 1;
info_buf_source, rc = CIFSSMBUnixQPathInfo(xid, tcon, fromName,
cifs_sb_source->local_nls, info_buf_source,
cifs_sb_source->mnt_cifs_flags & cifs_sb_source->local_nls,
CIFS_MOUNT_MAP_SPECIAL_CHR); cifs_sb_source->mnt_cifs_flags &
if (rc != 0) CIFS_MOUNT_MAP_SPECIAL_CHR);
goto unlink_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 == rc = CIFSSMBUnixQPathInfo(xid, tcon,
info_buf_target->UniqueId)) toName, info_buf_target,
/* same file, POSIX says that this is a noop */ cifs_sb_target->local_nls,
goto cifs_rename_exit; /* remap based on source sb */
} /* else ... BB we could add the same check for Windows by cifs_sb_source->mnt_cifs_flags &
CIFS_MOUNT_MAP_SPECIAL_CHR);
if (rc == 0 && (info_buf_source->UniqueId ==
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 */ checking the UniqueId via FILE_INTERNAL_INFO */
if ((rc == -EACCES) || (rc == -EEXIST)) {
unlink_target: 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);
/*
* This hack is for broken samba servers, remove this
* once more fixed ones are in the field.
*/
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;
/* /*
* we either can not tell the files are hardlinked (as with * after this point, we can't bother with error handling on
* Windows servers) or files are not hardlinked. Delete the * the undo's. This is best effort since we can't do anything
* target manually before renaming to follow POSIX rather than * about failures here.
* Windows semantics
*/ */
cifs_unlink(target_inode, target_direntry); if (!delete_already_pending) {
rc = cifs_do_rename(xid, source_direntry, fromName, tmprc = CIFSSMBSetFileDisposition(xid, tcon,
target_direntry, toName); 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: cifs_rename_exit:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment