[CIFS] undo changes in cifs_rename_pending_delete if it errors out
[safe/jmp/linux-2.6] / fs / cifs / inode.c
index 27e97d4..232ab16 100644 (file)
@@ -506,6 +506,7 @@ int cifs_get_inode_info(struct inode **pinode,
        inode = *pinode;
        cifsInfo = CIFS_I(inode);
        cifsInfo->cifsAttrs = attr;
+       cifsInfo->delete_pending = pfindData->DeletePending ? true : false;
        cFYI(1, ("Old time %ld", cifsInfo->time));
        cifsInfo->time = jiffies;
        cFYI(1, ("New time %ld", cifsInfo->time));
@@ -729,7 +730,10 @@ cifs_set_file_info(struct inode *inode, struct iattr *attrs, int xid,
                                     &info_buf, cifs_sb->local_nls,
                                     cifs_sb->mnt_cifs_flags &
                                        CIFS_MOUNT_MAP_SPECIAL_CHR);
-               if (rc != -EOPNOTSUPP && rc != -EINVAL)
+               if (rc == 0) {
+                       cifsInode->cifsAttrs = dosattr;
+                       goto out;
+               } else if (rc != -EOPNOTSUPP && rc != -EINVAL)
                        goto out;
        }
 
@@ -752,6 +756,9 @@ cifs_set_file_info(struct inode *inode, struct iattr *attrs, int xid,
 
 set_via_filehandle:
        rc = CIFSSMBSetFileInfo(xid, pTcon, &info_buf, netfid, netpid);
+       if (!rc)
+               cifsInode->cifsAttrs = dosattr;
+
        if (open_file == NULL)
                CIFSSMBClose(xid, pTcon, netfid);
        else
@@ -766,62 +773,106 @@ out:
  * anything else.
  */
 static int
-cifs_rename_pending_delete(char *full_path, struct inode *inode, int xid)
+cifs_rename_pending_delete(char *full_path, struct dentry *dentry, int xid)
 {
        int oplock = 0;
        int rc;
        __u16 netfid;
+       struct inode *inode = dentry->d_inode;
        struct cifsInodeInfo *cifsInode = CIFS_I(inode);
        struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb);
        struct cifsTconInfo *tcon = cifs_sb->tcon;
-       __u32 dosattr;
-       FILE_BASIC_INFO *info_buf;
+       __u32 dosattr, origattr;
+       FILE_BASIC_INFO *info_buf = NULL;
 
        rc = CIFSSMBOpen(xid, tcon, full_path, FILE_OPEN,
-                        DELETE|FILE_WRITE_ATTRIBUTES,
-                        CREATE_NOT_DIR|CREATE_DELETE_ON_CLOSE,
+                        DELETE|FILE_WRITE_ATTRIBUTES, CREATE_NOT_DIR,
                         &netfid, &oplock, NULL, cifs_sb->local_nls,
                         cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
        if (rc != 0)
                goto out;
 
-       /* set ATTR_HIDDEN and clear ATTR_READONLY */
-       cifsInode = CIFS_I(inode);
-       dosattr = cifsInode->cifsAttrs & ~ATTR_READONLY;
+       origattr = cifsInode->cifsAttrs;
+       if (origattr == 0)
+               origattr |= ATTR_NORMAL;
+
+       dosattr = origattr & ~ATTR_READONLY;
        if (dosattr == 0)
                dosattr |= ATTR_NORMAL;
        dosattr |= ATTR_HIDDEN;
 
-       info_buf = kzalloc(sizeof(*info_buf), GFP_KERNEL);
-       if (info_buf == NULL) {
-               rc = -ENOMEM;
-               goto out_close;
+       /* set ATTR_HIDDEN and clear ATTR_READONLY, but only if needed */
+       if (dosattr != origattr) {
+               info_buf = kzalloc(sizeof(*info_buf), GFP_KERNEL);
+               if (info_buf == NULL) {
+                       rc = -ENOMEM;
+                       goto out_close;
+               }
+               info_buf->Attributes = cpu_to_le32(dosattr);
+               rc = CIFSSMBSetFileInfo(xid, tcon, info_buf, netfid,
+                                       current->tgid);
+               /* although we would like to mark the file hidden
+                  if that fails we will still try to rename it */
+               if (rc != 0) {
+                       cifsInode->cifsAttrs = dosattr;
+               else
+                       dosattr = origattr; /* since not able to change them */
        }
-       info_buf->Attributes = cpu_to_le32(dosattr);
-       rc = CIFSSMBSetFileInfo(xid, tcon, info_buf, netfid, current->tgid);
-       kfree(info_buf);
-       if (rc != 0)
-               goto out_close;
 
-       /* silly-rename the file */
-       CIFSSMBRenameOpenFile(xid, tcon, netfid, NULL, cifs_sb->local_nls,
+       /* rename the file */
+       rc = CIFSSMBRenameOpenFile(xid, tcon, netfid, NULL, cifs_sb->local_nls,
                                   cifs_sb->mnt_cifs_flags &
                                            CIFS_MOUNT_MAP_SPECIAL_CHR);
+       if (rc != 0) {
+               rc = -ETXTBSY;
+               goto undo_setattr;
+       }
 
-       /* set DELETE_ON_CLOSE */
-       rc = CIFSSMBSetFileDisposition(xid, tcon, true, netfid, current->tgid);
-
-       /*
-        * some samba versions return -ENOENT when we try to set the file
-        * disposition here. Likely a samba bug, but work around it for now
-        */
-       if (rc == -ENOENT)
-               rc = 0;
+       /* try to set DELETE_ON_CLOSE */
+       if (!cifsInode->delete_pending) {
+               rc = CIFSSMBSetFileDisposition(xid, tcon, true, netfid,
+                                              current->tgid);
+               /*
+                * some samba versions return -ENOENT when we try to set the
+                * file disposition here. Likely a samba bug, but work around
+                * it for now. This means that some cifsXXX files may hang
+                * around after they shouldn't.
+                *
+                * BB: remove this hack after more servers have the fix
+                */
+               if (rc == -ENOENT)
+                       rc = 0;
+               else if (rc != 0) {
+                       rc = -ETXTBSY;
+                       goto undo_rename;
+               }
+               cifsInode->delete_pending = true;
+       }
 
 out_close:
        CIFSSMBClose(xid, tcon, netfid);
 out:
+       kfree(info_buf);
        return rc;
+
+       /*
+        * reset everything back to the original state. Don't bother
+        * dealing with errors here since we can't do anything about
+        * them anyway.
+        */
+undo_rename:
+       CIFSSMBRenameOpenFile(xid, tcon, netfid, dentry->d_name.name,
+                               cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
+                                           CIFS_MOUNT_MAP_SPECIAL_CHR);
+undo_setattr:
+       if (dosattr != origattr) {
+               info_buf->Attributes = cpu_to_le32(origattr);
+               if (!CIFSSMBSetFileInfo(xid, tcon, info_buf, netfid,
+                                       current->tgid))
+                       cifsInode->cifsAttrs = origattr;
+       }
+
+       goto out_close;
 }
 
 int cifs_unlink(struct inode *dir, struct dentry *dentry)
@@ -830,12 +881,12 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
        int xid;
        char *full_path = NULL;
        struct inode *inode = dentry->d_inode;
-       struct cifsInodeInfo *cifsInode;
+       struct cifsInodeInfo *cifsInode = CIFS_I(inode);
        struct super_block *sb = dir->i_sb;
        struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
        struct cifsTconInfo *tcon = cifs_sb->tcon;
-       struct iattr *attrs;
-       __u32 dosattr;
+       struct iattr *attrs = NULL;
+       __u32 dosattr = 0, origattr = 0;
 
        cFYI(1, ("cifs_unlink, dir=0x%p, dentry=0x%p", dir, dentry));
 
@@ -860,8 +911,10 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
                        goto psx_del_no_retry;
        }
 
+retry_std_delete:
        rc = CIFSSMBDelFile(xid, tcon, full_path, cifs_sb->local_nls,
                        cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
+
 psx_del_no_retry:
        if (!rc) {
                if (inode)
@@ -869,11 +922,10 @@ psx_del_no_retry:
        } else if (rc == -ENOENT) {
                d_drop(dentry);
        } else if (rc == -ETXTBSY) {
-               rc = cifs_rename_pending_delete(full_path, inode, xid);
+               rc = cifs_rename_pending_delete(full_path, dentry, xid);
                if (rc == 0)
                        drop_nlink(inode);
-       } else if (rc == -EACCES) {
-               /* try only if r/o attribute set in local lookup data? */
+       } else if (rc == -EACCES && dosattr == 0) {
                attrs = kzalloc(sizeof(*attrs), GFP_KERNEL);
                if (attrs == NULL) {
                        rc = -ENOMEM;
@@ -881,28 +933,25 @@ psx_del_no_retry:
                }
 
                /* try to reset dos attributes */
-               cifsInode = CIFS_I(inode);
-               dosattr = cifsInode->cifsAttrs & ~ATTR_READONLY;
+               origattr = cifsInode->cifsAttrs;
+               if (origattr == 0)
+                       origattr |= ATTR_NORMAL;
+               dosattr = origattr & ~ATTR_READONLY;
                if (dosattr == 0)
                        dosattr |= ATTR_NORMAL;
                dosattr |= ATTR_HIDDEN;
 
                rc = cifs_set_file_info(inode, attrs, xid, full_path, dosattr);
-               kfree(attrs);
                if (rc != 0)
                        goto out_reval;
-               rc = CIFSSMBDelFile(xid, tcon, full_path, cifs_sb->local_nls,
-                                   cifs_sb->mnt_cifs_flags &
-                                       CIFS_MOUNT_MAP_SPECIAL_CHR);
-               if (rc == 0) {
-                       if (inode)
-                               drop_nlink(inode);
-               } else if (rc == -ETXTBSY) {
-                       rc = cifs_rename_pending_delete(full_path, inode, xid);
-                       if (rc == 0)
-                               drop_nlink(inode);
-               }
+
+               goto retry_std_delete;
        }
+
+       /* undo the setattr if we errored out and it's needed */
+       if (rc != 0 && dosattr != 0)
+               cifs_set_file_info(inode, attrs, xid, full_path, origattr);
+
 out_reval:
        if (inode) {
                cifsInode = CIFS_I(inode);
@@ -912,9 +961,10 @@ out_reval:
        }
        dir->i_ctime = dir->i_mtime = current_fs_time(sb);
        cifsInode = CIFS_I(dir);
-       cifsInode->time = 0;    /* force revalidate of dir as well */
+       CIFS_I(dir)->time = 0;  /* force revalidate of dir as well */
 
        kfree(full_path);
+       kfree(attrs);
        FreeXid(xid);
        return rc;
 }
@@ -959,7 +1009,7 @@ static void posix_fill_in_inode(struct inode *tmp_inode,
 
 int cifs_mkdir(struct inode *inode, struct dentry *direntry, int mode)
 {
-       int rc = 0;
+       int rc = 0, tmprc;
        int xid;
        struct cifs_sb_info *cifs_sb;
        struct cifsTconInfo *pTcon;
@@ -1021,6 +1071,7 @@ int cifs_mkdir(struct inode *inode, struct dentry *direntry, int mode)
                                kfree(pInfo);
                                goto mkdir_get_info;
                        }
+
                        /* Is an i_ino of zero legal? */
                        /* Are there sanity checks we can use to ensure that
                           the server is really filling in that field? */
@@ -1109,12 +1160,20 @@ mkdir_get_info:
                        if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_CIFS_ACL) &&
                            (mode & S_IWUGO) == 0) {
                                FILE_BASIC_INFO pInfo;
+                               struct cifsInodeInfo *cifsInode;
+                               u32 dosattrs;
+
                                memset(&pInfo, 0, sizeof(pInfo));
-                               pInfo.Attributes = cpu_to_le32(ATTR_READONLY);
-                               CIFSSMBSetPathInfo(xid, pTcon, full_path,
-                                               &pInfo, cifs_sb->local_nls,
+                               cifsInode = CIFS_I(newinode);
+                               dosattrs = cifsInode->cifsAttrs|ATTR_READONLY;
+                               pInfo.Attributes = cpu_to_le32(dosattrs);
+                               tmprc = CIFSSMBSetPathInfo(xid, pTcon,
+                                               full_path, &pInfo,
+                                               cifs_sb->local_nls,
                                                cifs_sb->mnt_cifs_flags &
                                                CIFS_MOUNT_MAP_SPECIAL_CHR);
+                               if (tmprc == 0)
+                                       cifsInode->cifsAttrs = dosattrs;
                        }
                        if (direntry->d_inode) {
                                if (cifs_sb->mnt_cifs_flags &