cifs: make cifs_rename handle -EACCES errors
authorJeff Layton <jlayton@redhat.com>
Mon, 20 Oct 2008 18:45:22 +0000 (14:45 -0400)
committerSteve French <sfrench@us.ibm.com>
Mon, 20 Oct 2008 18:44:13 +0000 (18:44 +0000)
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: Jeff Layton <jlayton@redhat.com>
Signed-off-by: Steve French <sfrench@us.ibm.com>
fs/cifs/inode.c

index 23cca21..8ac4eab 100644 (file)
@@ -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,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
         * 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) {
-                       /*
-                        * 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)
-                               goto unlink_target;
-
-                       info_buf_target = info_buf_source + 1;
-                       rc = CIFSSMBUnixQPathInfo(xid, pTcon, fromName,
-                                               info_buf_source,
-                                               cifs_sb_source->local_nls,
-                                               cifs_sb_source->mnt_cifs_flags &
-                                               CIFS_MOUNT_MAP_SPECIAL_CHR);
-                       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 == -EEXIST && tcon->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) {
+                       rc = -ENOMEM;
+                       goto cifs_rename_exit;
+               }
+
+               info_buf_target = info_buf_source + 1;
+               rc = CIFSSMBUnixQPathInfo(xid, tcon, fromName,
+                                       info_buf_source,
+                                       cifs_sb_source->local_nls,
+                                       cifs_sb_source->mnt_cifs_flags &
+                                       CIFS_MOUNT_MAP_SPECIAL_CHR);
+               if (rc != 0)
+                       goto unlink_target;
 
-                       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
+               rc = CIFSSMBUnixQPathInfo(xid, tcon,
+                                       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))
+                       /* 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);
+                       /*
+                        * 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
-                * Windows servers) or files are not hardlinked. Delete the
-                * target manually before renaming to follow POSIX rather than
-                * Windows semantics
+                * 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.
                 */
-               cifs_unlink(target_inode, target_direntry);
-               rc = cifs_do_rename(xid, source_direntry, fromName,
-                                   target_direntry, toName);
+               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: