VServer 1.9.2 (patch-2.6.8.1-vs1.9.2.diff)
[linux-2.6.git] / fs / cifs / inode.c
index e48ed8c..a2965cf 100644 (file)
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
 
+extern int is_size_safe_to_change(struct cifsInodeInfo *);
+
 int
 cifs_get_inode_info_unix(struct inode **pinode,
                         const unsigned char *search_path,
-                        struct super_block *sb)
+                        struct super_block *sb,int xid)
 {
-       int xid;
        int rc = 0;
        FILE_UNIX_BASIC_INFO findData;
        struct cifsTconInfo *pTcon;
@@ -43,11 +44,6 @@ cifs_get_inode_info_unix(struct inode **pinode,
        struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
        char *tmp_path;
 
-/* BB add caching check so we do not go to server to overwrite inode info to cached file
-       where the local file sizes are correct and the server info is stale  BB */
-
-       xid = GetXid();
-
        pTcon = cifs_sb->tcon;
        cFYI(1, (" Getting info on %s ", search_path));
        /* we could have done a find first instead but this returns more info */
@@ -63,7 +59,6 @@ cifs_get_inode_info_unix(struct inode **pinode,
                                    strnlen(search_path, MAX_PATHCONF) + 1,
                                    GFP_KERNEL);
                        if (tmp_path == NULL) {
-                               FreeXid(xid);
                                return -ENOMEM;
                        }
         /* have to skip first of the double backslash of UNC name */
@@ -76,7 +71,6 @@ cifs_get_inode_info_unix(struct inode **pinode,
 
                        /* BB fix up inode etc. */
                } else if (rc) {
-                       FreeXid(xid);
                        return rc;
                }
 
@@ -86,9 +80,12 @@ cifs_get_inode_info_unix(struct inode **pinode,
                /* get new inode */
                if (*pinode == NULL) {
                        *pinode = new_inode(sb);
+                       if(*pinode == NULL) 
+                               return -ENOMEM;
+                       insert_inode_hash(*pinode);
                }
+                       
                inode = *pinode;
-
                cifsInfo = CIFS_I(inode);
 
                cFYI(1, (" Old time %ld ", cifsInfo->time));
@@ -113,8 +110,12 @@ cifs_get_inode_info_unix(struct inode **pinode,
                        inode->i_mode |= S_IFDIR;
                } else if (findData.Type == UNIX_CHARDEV) {
                        inode->i_mode |= S_IFCHR;
+                       inode->i_rdev = MKDEV(le64_to_cpu(findData.DevMajor),
+                               le64_to_cpu(findData.DevMinor) & MINORMASK);
                } else if (findData.Type == UNIX_BLOCKDEV) {
                        inode->i_mode |= S_IFBLK;
+                       inode->i_rdev = MKDEV(le64_to_cpu(findData.DevMajor),
+                               le64_to_cpu(findData.DevMinor) & MINORMASK);
                } else if (findData.Type == UNIX_FIFO) {
                        inode->i_mode |= S_IFIFO;
                } else if (findData.Type == UNIX_SOCKET) {
@@ -125,13 +126,29 @@ cifs_get_inode_info_unix(struct inode **pinode,
                inode->i_nlink = le64_to_cpu(findData.Nlinks);
                findData.NumOfBytes = le64_to_cpu(findData.NumOfBytes);
                findData.EndOfFile = le64_to_cpu(findData.EndOfFile);
-               i_size_write(inode,findData.EndOfFile);
+
+               if(is_size_safe_to_change(cifsInfo)) {
+               /* can not safely change the file size here if the 
+                  client is writing to it due to potential races */
+
+                       i_size_write(inode,findData.EndOfFile);
 /* blksize needs to be multiple of two. So safer to default to blksize
        and blkbits set in superblock so 2**blkbits and blksize will match */
 /*             inode->i_blksize =
                    (pTcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE) & 0xFFFFFE00;*/
-               inode->i_blocks = 
-                       (inode->i_blksize - 1 + findData.NumOfBytes) >> inode->i_blkbits;
+
+               /* This seems incredibly stupid but it turns out that
+               i_blocks is not related to (i_size / i_blksize), instead a
+               size of 512 is required to be used for calculating num blocks */
+                
+
+/*             inode->i_blocks = 
+                       (inode->i_blksize - 1 + findData.NumOfBytes) >> inode->i_blkbits;*/
+
+               /* 512 bytes (2**9) is the fake blocksize that must be used */
+               /* for this calculation */
+                       inode->i_blocks = (512 - 1 + findData.NumOfBytes) >> 9;
+               }
 
                if (findData.NumOfBytes < findData.EndOfFile)
                        cFYI(1, ("Server inconsistency Error: it says allocation size less than end of file "));
@@ -157,15 +174,13 @@ cifs_get_inode_info_unix(struct inode **pinode,
                                           inode->i_rdev);
                }
        }
-       FreeXid(xid);
        return rc;
 }
 
 int
 cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path, 
-               FILE_ALL_INFO * pfindData, struct super_block *sb)
+               FILE_ALL_INFO * pfindData, struct super_block *sb, int xid)
 {
-       int xid;
        int rc = 0;
        struct cifsTconInfo *pTcon;
        struct inode *inode;
@@ -173,15 +188,12 @@ cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path,
        char *tmp_path;
        char *buf = NULL;
 
-       xid = GetXid();
-
        pTcon = cifs_sb->tcon;
        cFYI(1,("Getting info on %s ", search_path));
 
        if((pfindData == NULL) && (*pinode != NULL)) {
                if(CIFS_I(*pinode)->clientCanCacheRead) {
                        cFYI(1,("No need to revalidate inode sizes on cached file "));
-                       FreeXid(xid);
                        return rc;
                }
        }
@@ -206,7 +218,6 @@ cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path,
                        if (tmp_path == NULL) {
                                if(buf)
                                        kfree(buf);
-                               FreeXid(xid);
                                return -ENOMEM;
                        }
 
@@ -220,7 +231,6 @@ cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path,
                } else if (rc) {
                        if(buf)
                                kfree(buf);
-                       FreeXid(xid);
                        return rc;
                }
        } else {
@@ -229,8 +239,10 @@ cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path,
                /* get new inode */
                if (*pinode == NULL) {
                        *pinode = new_inode(sb);
+                       if(*pinode == NULL)
+                               return -ENOMEM;
+                       insert_inode_hash(*pinode);
                }
-
                inode = *pinode;
                cifsInfo = CIFS_I(inode);
                pfindData->Attributes = le32_to_cpu(pfindData->Attributes);
@@ -273,10 +285,18 @@ cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path,
                                inode->i_mode &= ~(S_IWUGO);
    /* BB add code here - validate if device or weird share or device type? */
                }
-               i_size_write(inode,le64_to_cpu(pfindData->EndOfFile));
+               if(is_size_safe_to_change(cifsInfo)) {
+               /* can not safely change the file size here if the 
+               client is writing to it due to potential races */
+
+                       i_size_write(inode,le64_to_cpu(pfindData->EndOfFile));
+
+               /* 512 bytes (2**9) is the fake blocksize that must be used */
+               /* for this calculation */
+                       inode->i_blocks = (512 - 1 + pfindData->AllocationSize)
+                                >> 9;
+               }
                pfindData->AllocationSize = le64_to_cpu(pfindData->AllocationSize);
-               inode->i_blocks =
-                       (inode->i_blksize - 1 + pfindData->AllocationSize) >> inode->i_blkbits;
 
                inode->i_nlink = le32_to_cpu(pfindData->NumberOfLinks);
 
@@ -309,22 +329,23 @@ cifs_get_inode_info(struct inode **pinode, const unsigned char *search_path,
        }
        if(buf)
            kfree(buf);
-       FreeXid(xid);
        return rc;
 }
 
 void
 cifs_read_inode(struct inode *inode)
 {                              /* gets root inode */
-
+       int xid;
        struct cifs_sb_info *cifs_sb;
 
        cifs_sb = CIFS_SB(inode->i_sb);
-
+       xid = GetXid();
        if (cifs_sb->tcon->ses->capabilities & CAP_UNIX)
-               cifs_get_inode_info_unix(&inode, "", inode->i_sb);
+               cifs_get_inode_info_unix(&inode, "", inode->i_sb,xid);
        else
-               cifs_get_inode_info(&inode, "", NULL, inode->i_sb);
+               cifs_get_inode_info(&inode, "", NULL, inode->i_sb,xid);
+       /* can not call macro FreeXid here since in a void func */
+       _FreeXid(xid);
 }
 
 int
@@ -345,8 +366,15 @@ cifs_unlink(struct inode *inode, struct dentry *direntry)
        cifs_sb = CIFS_SB(inode->i_sb);
        pTcon = cifs_sb->tcon;
 
+/* Unlink can be called from rename so we can not grab
+       the sem here since we deadlock otherwise */
+/*     down(&direntry->d_sb->s_vfs_rename_sem);*/
        full_path = build_path_from_dentry(direntry);
-
+/*     up(&direntry->d_sb->s_vfs_rename_sem);*/
+       if(full_path == NULL) {
+               FreeXid(xid);
+               return -ENOMEM;
+       }
        rc = CIFSSMBDelFile(xid, pTcon, full_path, cifs_sb->local_nls);
 
        if (!rc) {
@@ -427,7 +455,13 @@ cifs_mkdir(struct inode *inode, struct dentry *direntry, int mode)
        cifs_sb = CIFS_SB(inode->i_sb);
        pTcon = cifs_sb->tcon;
 
+       down(&inode->i_sb->s_vfs_rename_sem);
        full_path = build_path_from_dentry(direntry);
+       up(&inode->i_sb->s_vfs_rename_sem);
+       if(full_path == NULL) {
+               FreeXid(xid);
+               return -ENOMEM;
+       }
        /* BB add setting the equivalent of mode via CreateX w/ACLs */
        rc = CIFSSMBMkDir(xid, pTcon, full_path, cifs_sb->local_nls);
        if (rc) {
@@ -437,21 +471,29 @@ cifs_mkdir(struct inode *inode, struct dentry *direntry, int mode)
                inode->i_nlink++;
                if (pTcon->ses->capabilities & CAP_UNIX)
                        rc = cifs_get_inode_info_unix(&newinode, full_path,
-                                                     inode->i_sb);
+                                                     inode->i_sb,xid);
                else
                        rc = cifs_get_inode_info(&newinode, full_path,NULL,
-                                                inode->i_sb);
+                                                inode->i_sb,xid);
 
                direntry->d_op = &cifs_dentry_ops;
                d_instantiate(direntry, newinode);
                if(direntry->d_inode)
                        direntry->d_inode->i_nlink = 2;
-               if (cifs_sb->tcon->ses->capabilities & CAP_UNIX)                
-                       CIFSSMBUnixSetPerms(xid, pTcon, full_path, mode,
-                               (__u64)-1,  
-                               (__u64)-1,
-                               0 /* dev_t */,
-                               cifs_sb->local_nls);
+               if (cifs_sb->tcon->ses->capabilities & CAP_UNIX)
+                       if(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_SET_UID) {
+                               CIFSSMBUnixSetPerms(xid, pTcon, full_path, mode,
+                                               (__u64)current->euid,  
+                                               (__u64)current->egid,
+                                               0 /* dev_t */,
+                                               cifs_sb->local_nls);
+                       } else {
+                               CIFSSMBUnixSetPerms(xid, pTcon, full_path, mode,
+                                               (__u64)-1,  
+                                               (__u64)-1,
+                                               0 /* dev_t */,
+                                               cifs_sb->local_nls);
+                       }
                else { /* BB to be implemented via Windows secrty descriptors*/
                /* eg CIFSSMBWinSetPerms(xid,pTcon,full_path,mode,-1,-1,local_nls);*/
                }
@@ -480,7 +522,13 @@ cifs_rmdir(struct inode *inode, struct dentry *direntry)
        cifs_sb = CIFS_SB(inode->i_sb);
        pTcon = cifs_sb->tcon;
 
+       down(&inode->i_sb->s_vfs_rename_sem);
        full_path = build_path_from_dentry(direntry);
+       up(&inode->i_sb->s_vfs_rename_sem);
+       if(full_path == NULL) {
+               FreeXid(xid);
+               return -ENOMEM;
+       }
 
        rc = CIFSSMBRmDir(xid, pTcon, full_path, cifs_sb->local_nls);
 
@@ -525,15 +573,50 @@ cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
                      different share. Might eventually add support for this */
        }
 
+       /* 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);
        toName = build_path_from_dentry(target_direntry);
+       if((fromName == NULL) || (toName == NULL)) {
+               rc = -ENOMEM;
+               goto cifs_rename_exit;
+       }
 
        rc = CIFSSMBRename(xid, pTcon, fromName, toName,
                           cifs_sb_source->local_nls);
        if(rc == -EEXIST) {
-               cifs_unlink(target_inode, target_direntry);
-               rc = CIFSSMBRename(xid, pTcon, fromName, toName,
-                                  cifs_sb_source->local_nls);
+               /* 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;
+
+               info_buf_source = 
+                       kmalloc(2 * sizeof(FILE_UNIX_BASIC_INFO),GFP_KERNEL);
+               if(info_buf_source != NULL) {
+                       info_buf_target = info_buf_source+1;
+                       rc = CIFSSMBUnixQPathInfo(xid, pTcon, fromName, 
+                               info_buf_source, cifs_sb_source->local_nls);
+                       if(rc == 0) {
+                               rc = CIFSSMBUnixQPathInfo(xid,pTcon,toName,
+                                               info_buf_target,
+                                               cifs_sb_target->local_nls);
+                       }
+                       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);
+                       }
+                       kfree(info_buf_source);
+               } /* if we can not get memory just leave rc as EEXIST */
        }
 
        if((rc == -EIO)||(rc == -EEXIST)) {
@@ -549,6 +632,8 @@ cifs_rename(struct inode *source_inode, struct dentry *source_direntry,
                        CIFSSMBClose(xid, pTcon, netfid);
                }
        }
+
+cifs_rename_exit:
        if (fromName)
                kfree(fromName);
        if (toName)
@@ -586,7 +671,13 @@ cifs_revalidate(struct dentry *direntry)
 
        cifs_sb = CIFS_SB(direntry->d_sb);
 
+       /* can not safely grab the rename sem here if
+       rename calls revalidate since that would deadlock */
        full_path = build_path_from_dentry(direntry);
+       if(full_path == NULL) {
+               FreeXid(xid);
+               return -ENOMEM;
+       }
        cFYI(1,
             ("Revalidate: %s inode 0x%p count %d dentry: 0x%p d_time %ld jiffies %ld",
              full_path, direntry->d_inode,
@@ -613,7 +704,7 @@ cifs_revalidate(struct dentry *direntry)
 
        if (cifs_sb->tcon->ses->capabilities & CAP_UNIX) {
                rc = cifs_get_inode_info_unix(&direntry->d_inode, full_path,
-                                        direntry->d_sb);
+                                        direntry->d_sb,xid);
                if(rc) {
                        cFYI(1,("error on getting revalidate info %d",rc));
 /*                     if(rc != -ENOENT)
@@ -621,7 +712,7 @@ cifs_revalidate(struct dentry *direntry)
                }
        } else {
                rc = cifs_get_inode_info(&direntry->d_inode, full_path, NULL,
-                                   direntry->d_sb);
+                                   direntry->d_sb,xid);
                if(rc) {
                        cFYI(1,("error on getting revalidate info %d",rc));
 /*                     if(rc != -ENOENT)
@@ -647,9 +738,12 @@ cifs_revalidate(struct dentry *direntry)
                }
        }
 
-
+       /* can not grab this sem since kernel filesys locking
+               documentation indicates i_sem may be taken by the kernel 
+               on lookup and rename which could deadlock if we grab
+               the i_sem here as well */
+/*     down(&direntry->d_inode->i_sem);*/
        /* need to write out dirty pages here  */
-       down(&direntry->d_inode->i_sem);
        if(direntry->d_inode->i_mapping) {
                /* do we need to lock inode until after invalidate completes below? */
                filemap_fdatawrite(direntry->d_inode->i_mapping);
@@ -663,9 +757,7 @@ cifs_revalidate(struct dentry *direntry)
                        invalidate_remote_inode(direntry->d_inode);
                }
        }
-
-
-       up(&direntry->d_inode->i_sem);
+/*     up(&direntry->d_inode->i_sem);*/
        
        if (full_path)
                kfree(full_path);
@@ -729,7 +821,13 @@ cifs_setattr(struct dentry *direntry, struct iattr *attrs)
        cifs_sb = CIFS_SB(direntry->d_inode->i_sb);
        pTcon = cifs_sb->tcon;
 
+       down(&direntry->d_sb->s_vfs_rename_sem);
        full_path = build_path_from_dentry(direntry);
+       up(&direntry->d_sb->s_vfs_rename_sem);
+       if(full_path == NULL) {
+               FreeXid(xid);
+               return -ENOMEM;
+       }
        cifsInode = CIFS_I(direntry->d_inode);
 
        /* BB check if we need to refresh inode from server now ? BB */
@@ -862,7 +960,8 @@ cifs_setattr(struct dentry *direntry, struct iattr *attrs)
        }
 
        /* do not  need local check to inode_check_ok since the server does that */
-       inode_setattr(direntry->d_inode, attrs);
+       if (!rc)
+               rc = inode_setattr(direntry->d_inode, attrs);
        if (full_path)
                kfree(full_path);
        FreeXid(xid);