Merge commit 'tip/tracing/core' into oprofile/core
[safe/jmp/linux-2.6] / fs / cifs / file.c
index cbefe1f..9b11a8f 100644 (file)
@@ -30,6 +30,8 @@
 #include <linux/writeback.h>
 #include <linux/task_io_accounting_ops.h>
 #include <linux/delay.h>
+#include <linux/mount.h>
+#include <linux/slab.h>
 #include <asm/div64.h>
 #include "cifsfs.h"
 #include "cifspdu.h"
 #include "cifs_debug.h"
 #include "cifs_fs_sb.h"
 
-static inline struct cifsFileInfo *cifs_init_private(
-       struct cifsFileInfo *private_data, struct inode *inode,
-       struct file *file, __u16 netfid)
-{
-       memset(private_data, 0, sizeof(struct cifsFileInfo));
-       private_data->netfid = netfid;
-       private_data->pid = current->tgid;
-       init_MUTEX(&private_data->fh_sem);
-       mutex_init(&private_data->lock_mutex);
-       INIT_LIST_HEAD(&private_data->llist);
-       private_data->pfile = file; /* needed for writepage */
-       private_data->pInode = inode;
-       private_data->invalidHandle = false;
-       private_data->closePend = false;
-       /* we have to track num writers to the inode, since writepages
-       does not tell us which handle the write is for so there can
-       be a close (overlapping with write) of the filehandle that
-       cifs_writepages chose to use */
-       atomic_set(&private_data->wrtPending, 0);
-
-       return private_data;
-}
-
 static inline int cifs_convert_flags(unsigned int flags)
 {
        if ((flags & O_ACCMODE) == O_RDONLY)
@@ -78,8 +57,38 @@ static inline int cifs_convert_flags(unsigned int flags)
        return (READ_CONTROL | FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES |
                FILE_WRITE_EA | FILE_APPEND_DATA | FILE_WRITE_DATA |
                FILE_READ_DATA);
+}
 
+static inline fmode_t cifs_posix_convert_flags(unsigned int flags)
+{
+       fmode_t posix_flags = 0;
 
+       if ((flags & O_ACCMODE) == O_RDONLY)
+               posix_flags = FMODE_READ;
+       else if ((flags & O_ACCMODE) == O_WRONLY)
+               posix_flags = FMODE_WRITE;
+       else if ((flags & O_ACCMODE) == O_RDWR) {
+               /* GENERIC_ALL is too much permission to request
+                  can cause unnecessary access denied on create */
+               /* return GENERIC_ALL; */
+               posix_flags = FMODE_READ | FMODE_WRITE;
+       }
+       /* can not map O_CREAT or O_EXCL or O_TRUNC flags when
+          reopening a file.  They had their effect on the original open */
+       if (flags & O_APPEND)
+               posix_flags |= (fmode_t)O_APPEND;
+       if (flags & O_DSYNC)
+               posix_flags |= (fmode_t)O_DSYNC;
+       if (flags & __O_SYNC)
+               posix_flags |= (fmode_t)__O_SYNC;
+       if (flags & O_DIRECTORY)
+               posix_flags |= (fmode_t)O_DIRECTORY;
+       if (flags & O_NOFOLLOW)
+               posix_flags |= (fmode_t)O_NOFOLLOW;
+       if (flags & O_DIRECT)
+               posix_flags |= (fmode_t)O_DIRECT;
+
+       return posix_flags;
 }
 
 static inline int cifs_get_disposition(unsigned int flags)
@@ -97,6 +106,96 @@ static inline int cifs_get_disposition(unsigned int flags)
 }
 
 /* all arguments to this function must be checked for validity in caller */
+static inline int
+cifs_posix_open_inode_helper(struct inode *inode, struct file *file,
+                            struct cifsInodeInfo *pCifsInode,
+                            struct cifsFileInfo *pCifsFile, __u32 oplock,
+                            u16 netfid)
+{
+
+       write_lock(&GlobalSMBSeslock);
+
+       pCifsInode = CIFS_I(file->f_path.dentry->d_inode);
+       if (pCifsInode == NULL) {
+               write_unlock(&GlobalSMBSeslock);
+               return -EINVAL;
+       }
+
+       if (pCifsInode->clientCanCacheRead) {
+               /* we have the inode open somewhere else
+                  no need to discard cache data */
+               goto psx_client_can_cache;
+       }
+
+       /* BB FIXME need to fix this check to move it earlier into posix_open
+          BB  fIX following section BB FIXME */
+
+       /* if not oplocked, invalidate inode pages if mtime or file
+          size changed */
+/*     temp = cifs_NTtimeToUnix(le64_to_cpu(buf->LastWriteTime));
+       if (timespec_equal(&file->f_path.dentry->d_inode->i_mtime, &temp) &&
+                          (file->f_path.dentry->d_inode->i_size ==
+                           (loff_t)le64_to_cpu(buf->EndOfFile))) {
+               cFYI(1, ("inode unchanged on server"));
+       } else {
+               if (file->f_path.dentry->d_inode->i_mapping) {
+                       rc = filemap_write_and_wait(file->f_path.dentry->d_inode->i_mapping);
+                       if (rc != 0)
+                               CIFS_I(file->f_path.dentry->d_inode)->write_behind_rc = rc;
+               }
+               cFYI(1, ("invalidating remote inode since open detected it "
+                        "changed"));
+               invalidate_remote_inode(file->f_path.dentry->d_inode);
+       } */
+
+psx_client_can_cache:
+       if ((oplock & 0xF) == OPLOCK_EXCLUSIVE) {
+               pCifsInode->clientCanCacheAll = true;
+               pCifsInode->clientCanCacheRead = true;
+               cFYI(1, ("Exclusive Oplock granted on inode %p",
+                        file->f_path.dentry->d_inode));
+       } else if ((oplock & 0xF) == OPLOCK_READ)
+               pCifsInode->clientCanCacheRead = true;
+
+       /* will have to change the unlock if we reenable the
+          filemap_fdatawrite (which does not seem necessary */
+       write_unlock(&GlobalSMBSeslock);
+       return 0;
+}
+
+static struct cifsFileInfo *
+cifs_fill_filedata(struct file *file)
+{
+       struct list_head *tmp;
+       struct cifsFileInfo *pCifsFile = NULL;
+       struct cifsInodeInfo *pCifsInode = NULL;
+
+       /* search inode for this file and fill in file->private_data */
+       pCifsInode = CIFS_I(file->f_path.dentry->d_inode);
+       read_lock(&GlobalSMBSeslock);
+       list_for_each(tmp, &pCifsInode->openFileList) {
+               pCifsFile = list_entry(tmp, struct cifsFileInfo, flist);
+               if ((pCifsFile->pfile == NULL) &&
+                   (pCifsFile->pid == current->tgid)) {
+                       /* mode set in cifs_create */
+
+                       /* needed for writepage */
+                       pCifsFile->pfile = file;
+                       file->private_data = pCifsFile;
+                       break;
+               }
+       }
+       read_unlock(&GlobalSMBSeslock);
+
+       if (file->private_data != NULL) {
+               return pCifsFile;
+       } else if ((file->f_flags & O_CREAT) && (file->f_flags & O_EXCL))
+                       cERROR(1, ("could not find file instance for "
+                                  "new file %p", file));
+       return NULL;
+}
+
+/* all arguments to this function must be checked for validity in caller */
 static inline int cifs_open_inode_helper(struct inode *inode, struct file *file,
        struct cifsInodeInfo *pCifsInode, struct cifsFileInfo *pCifsFile,
        struct cifsTconInfo *pTcon, int *oplock, FILE_ALL_INFO *buf,
@@ -105,17 +204,6 @@ static inline int cifs_open_inode_helper(struct inode *inode, struct file *file,
        struct timespec temp;
        int rc;
 
-       /* want handles we can use to read with first
-          in the list so we do not have to walk the
-          list to search for one in prepare_write */
-       if ((file->f_flags & O_ACCMODE) == O_WRONLY) {
-               list_add_tail(&pCifsFile->flist,
-                             &pCifsInode->openFileList);
-       } else {
-               list_add(&pCifsFile->flist,
-                        &pCifsInode->openFileList);
-       }
-       write_unlock(&GlobalSMBSeslock);
        if (pCifsInode->clientCanCacheRead) {
                /* we have the inode open somewhere else
                   no need to discard cache data */
@@ -125,15 +213,15 @@ static inline int cifs_open_inode_helper(struct inode *inode, struct file *file,
        /* BB need same check in cifs_create too? */
        /* if not oplocked, invalidate inode pages if mtime or file
           size changed */
-       temp = cifs_NTtimeToUnix(le64_to_cpu(buf->LastWriteTime));
+       temp = cifs_NTtimeToUnix(buf->LastWriteTime);
        if (timespec_equal(&file->f_path.dentry->d_inode->i_mtime, &temp) &&
                           (file->f_path.dentry->d_inode->i_size ==
                            (loff_t)le64_to_cpu(buf->EndOfFile))) {
                cFYI(1, ("inode unchanged on server"));
        } else {
                if (file->f_path.dentry->d_inode->i_mapping) {
-               /* BB no need to lock inode until after invalidate
-                  since namei code should already have it locked? */
+                       /* BB no need to lock inode until after invalidate
+                       since namei code should already have it locked? */
                        rc = filemap_write_and_wait(file->f_path.dentry->d_inode->i_mapping);
                        if (rc != 0)
                                CIFS_I(file->f_path.dentry->d_inode)->write_behind_rc = rc;
@@ -165,12 +253,12 @@ client_can_cache:
 int cifs_open(struct inode *inode, struct file *file)
 {
        int rc = -EACCES;
-       int xid, oplock;
+       int xid;
+       __u32 oplock;
        struct cifs_sb_info *cifs_sb;
-       struct cifsTconInfo *pTcon;
+       struct cifsTconInfo *tcon;
        struct cifsFileInfo *pCifsFile;
        struct cifsInodeInfo *pCifsInode;
-       struct list_head *tmp;
        char *full_path = NULL;
        int desiredAccess;
        int disposition;
@@ -180,46 +268,65 @@ int cifs_open(struct inode *inode, struct file *file)
        xid = GetXid();
 
        cifs_sb = CIFS_SB(inode->i_sb);
-       pTcon = cifs_sb->tcon;
+       tcon = cifs_sb->tcon;
 
-       if (file->f_flags & O_CREAT) {
-               /* search inode for this file and fill in file->private_data */
-               pCifsInode = CIFS_I(file->f_path.dentry->d_inode);
-               read_lock(&GlobalSMBSeslock);
-               list_for_each(tmp, &pCifsInode->openFileList) {
-                       pCifsFile = list_entry(tmp, struct cifsFileInfo,
-                                              flist);
-                       if ((pCifsFile->pfile == NULL) &&
-                           (pCifsFile->pid == current->tgid)) {
-                               /* mode set in cifs_create */
-
-                               /* needed for writepage */
-                               pCifsFile->pfile = file;
-
-                               file->private_data = pCifsFile;
-                               break;
-                       }
-               }
-               read_unlock(&GlobalSMBSeslock);
-               if (file->private_data != NULL) {
-                       rc = 0;
-                       FreeXid(xid);
-                       return rc;
-               } else {
-                       if (file->f_flags & O_EXCL)
-                               cERROR(1, ("could not find file instance for "
-                                          "new file %p", file));
-               }
+       pCifsInode = CIFS_I(file->f_path.dentry->d_inode);
+       pCifsFile = cifs_fill_filedata(file);
+       if (pCifsFile) {
+               rc = 0;
+               FreeXid(xid);
+               return rc;
        }
 
        full_path = build_path_from_dentry(file->f_path.dentry);
        if (full_path == NULL) {
+               rc = -ENOMEM;
                FreeXid(xid);
-               return -ENOMEM;
+               return rc;
        }
 
        cFYI(1, ("inode = 0x%p file flags are 0x%x for %s",
                 inode, file->f_flags, full_path));
+
+       if (oplockEnabled)
+               oplock = REQ_OPLOCK;
+       else
+               oplock = 0;
+
+       if (!tcon->broken_posix_open && tcon->unix_ext &&
+           (tcon->ses->capabilities & CAP_UNIX) &&
+           (CIFS_UNIX_POSIX_PATH_OPS_CAP &
+                       le64_to_cpu(tcon->fsUnixInfo.Capability))) {
+               int oflags = (int) cifs_posix_convert_flags(file->f_flags);
+               /* can not refresh inode info since size could be stale */
+               rc = cifs_posix_open(full_path, &inode, file->f_path.mnt,
+                                    cifs_sb->mnt_file_mode /* ignored */,
+                                    oflags, &oplock, &netfid, xid);
+               if (rc == 0) {
+                       cFYI(1, ("posix open succeeded"));
+                       /* no need for special case handling of setting mode
+                          on read only files needed here */
+
+                       pCifsFile = cifs_fill_filedata(file);
+                       cifs_posix_open_inode_helper(inode, file, pCifsInode,
+                                                    pCifsFile, oplock, netfid);
+                       goto out;
+               } else if ((rc == -EINVAL) || (rc == -EOPNOTSUPP)) {
+                       if (tcon->ses->serverNOS)
+                               cERROR(1, ("server %s of type %s returned"
+                                          " unexpected error on SMB posix open"
+                                          ", disabling posix open support."
+                                          " Check if server update available.",
+                                          tcon->ses->serverName,
+                                          tcon->ses->serverNOS));
+                       tcon->broken_posix_open = true;
+               } else if ((rc != -EIO) && (rc != -EREMOTE) &&
+                        (rc != -EOPNOTSUPP)) /* path not found or net err */
+                       goto out;
+               /* else fallthrough to retry open the old way on network i/o
+                  or DFS errors */
+       }
+
        desiredAccess = cifs_convert_flags(file->f_flags);
 
 /*********************************************************************
@@ -248,11 +355,6 @@ int cifs_open(struct inode *inode, struct file *file)
 
        disposition = cifs_get_disposition(file->f_flags);
 
-       if (oplockEnabled)
-               oplock = REQ_OPLOCK;
-       else
-               oplock = 0;
-
        /* BB pass O_SYNC flag through on file attributes .. BB */
 
        /* Also refresh inode by passing in file_info buf returned by SMBOpen
@@ -269,7 +371,7 @@ int cifs_open(struct inode *inode, struct file *file)
        }
 
        if (cifs_sb->tcon->ses->capabilities & CAP_NT_SMBS)
-               rc = CIFSSMBOpen(xid, pTcon, full_path, disposition,
+               rc = CIFSSMBOpen(xid, tcon, full_path, disposition,
                         desiredAccess, CREATE_NOT_DIR, &netfid, &oplock, buf,
                         cifs_sb->local_nls, cifs_sb->mnt_cifs_flags
                                 & CIFS_MOUNT_MAP_SPECIAL_CHR);
@@ -278,7 +380,7 @@ int cifs_open(struct inode *inode, struct file *file)
 
        if (rc == -EIO) {
                /* Old server, try legacy style OpenX */
-               rc = SMBLegacyOpen(xid, pTcon, full_path, disposition,
+               rc = SMBLegacyOpen(xid, tcon, full_path, disposition,
                        desiredAccess, CREATE_NOT_DIR, &netfid, &oplock, buf,
                        cifs_sb->local_nls, cifs_sb->mnt_cifs_flags
                                & CIFS_MOUNT_MAP_SPECIAL_CHR);
@@ -287,29 +389,22 @@ int cifs_open(struct inode *inode, struct file *file)
                cFYI(1, ("cifs_open returned 0x%x", rc));
                goto out;
        }
-       file->private_data =
-               kmalloc(sizeof(struct cifsFileInfo), GFP_KERNEL);
+
+       pCifsFile = cifs_new_fileinfo(inode, netfid, file, file->f_path.mnt,
+                                       file->f_flags);
+       file->private_data = pCifsFile;
        if (file->private_data == NULL) {
                rc = -ENOMEM;
                goto out;
        }
-       pCifsFile = cifs_init_private(file->private_data, inode, file, netfid);
-       write_lock(&GlobalSMBSeslock);
-       list_add(&pCifsFile->tlist, &pTcon->openFileList);
 
-       pCifsInode = CIFS_I(file->f_path.dentry->d_inode);
-       if (pCifsInode) {
-               rc = cifs_open_inode_helper(inode, file, pCifsInode,
-                                           pCifsFile, pTcon,
-                                           &oplock, buf, full_path, xid);
-       } else {
-               write_unlock(&GlobalSMBSeslock);
-       }
+       rc = cifs_open_inode_helper(inode, file, pCifsInode, pCifsFile, tcon,
+                                   &oplock, buf, full_path, xid);
 
        if (oplock & CIFS_CREATE_ACTION) {
                /* time to set mode which we can not set earlier due to
                   problems creating new read-only files */
-               if (pTcon->unix_ext) {
+               if (tcon->unix_ext) {
                        struct cifs_unix_set_info_args args = {
                                .mode   = inode->i_mode,
                                .uid    = NO_CHANGE_64,
@@ -319,9 +414,9 @@ int cifs_open(struct inode *inode, struct file *file)
                                .mtime  = NO_CHANGE_64,
                                .device = 0,
                        };
-                       CIFSSMBUnixSetInfo(xid, pTcon, full_path, &args,
-                                           cifs_sb->local_nls,
-                                           cifs_sb->mnt_cifs_flags &
+                       CIFSSMBUnixSetPathInfo(xid, tcon, full_path, &args,
+                                              cifs_sb->local_nls,
+                                              cifs_sb->mnt_cifs_flags &
                                                CIFS_MOUNT_MAP_SPECIAL_CHR);
                }
        }
@@ -347,9 +442,10 @@ static int cifs_relock_file(struct cifsFileInfo *cifsFile)
 static int cifs_reopen_file(struct file *file, bool can_flush)
 {
        int rc = -EACCES;
-       int xid, oplock;
+       int xid;
+       __u32 oplock;
        struct cifs_sb_info *cifs_sb;
-       struct cifsTconInfo *pTcon;
+       struct cifsTconInfo *tcon;
        struct cifsFileInfo *pCifsFile;
        struct cifsInodeInfo *pCifsInode;
        struct inode *inode;
@@ -364,11 +460,12 @@ static int cifs_reopen_file(struct file *file, bool can_flush)
                return -EBADF;
 
        xid = GetXid();
-       down(&pCifsFile->fh_sem);
+       mutex_lock(&pCifsFile->fh_mutex);
        if (!pCifsFile->invalidHandle) {
-               up(&pCifsFile->fh_sem);
+               mutex_unlock(&pCifsFile->fh_mutex);
+               rc = 0;
                FreeXid(xid);
-               return 0;
+               return rc;
        }
 
        if (file->f_path.dentry == NULL) {
@@ -387,7 +484,7 @@ static int cifs_reopen_file(struct file *file, bool can_flush)
        }
 
        cifs_sb = CIFS_SB(inode->i_sb);
-       pTcon = cifs_sb->tcon;
+       tcon = cifs_sb->tcon;
 
 /* can not grab rename sem here because various ops, including
    those that already have the rename sem can end up causing writepage
@@ -397,38 +494,56 @@ static int cifs_reopen_file(struct file *file, bool can_flush)
        if (full_path == NULL) {
                rc = -ENOMEM;
 reopen_error_exit:
-               up(&pCifsFile->fh_sem);
+               mutex_unlock(&pCifsFile->fh_mutex);
                FreeXid(xid);
                return rc;
        }
 
        cFYI(1, ("inode = 0x%p file flags 0x%x for %s",
                 inode, file->f_flags, full_path));
-       desiredAccess = cifs_convert_flags(file->f_flags);
 
        if (oplockEnabled)
                oplock = REQ_OPLOCK;
        else
                oplock = 0;
 
+       if (tcon->unix_ext && (tcon->ses->capabilities & CAP_UNIX) &&
+           (CIFS_UNIX_POSIX_PATH_OPS_CAP &
+                       le64_to_cpu(tcon->fsUnixInfo.Capability))) {
+               int oflags = (int) cifs_posix_convert_flags(file->f_flags);
+               /* can not refresh inode info since size could be stale */
+               rc = cifs_posix_open(full_path, NULL, file->f_path.mnt,
+                                    cifs_sb->mnt_file_mode /* ignored */,
+                                    oflags, &oplock, &netfid, xid);
+               if (rc == 0) {
+                       cFYI(1, ("posix reopen succeeded"));
+                       goto reopen_success;
+               }
+               /* fallthrough to retry open the old way on errors, especially
+                  in the reconnect path it is important to retry hard */
+       }
+
+       desiredAccess = cifs_convert_flags(file->f_flags);
+
        /* Can not refresh inode by passing in file_info buf to be returned
           by SMBOpen and then calling get_inode_info with returned buf
           since file might have write behind data that needs to be flushed
           and server version of file size can be stale. If we knew for sure
           that inode was not dirty locally we could do this */
 
-       rc = CIFSSMBOpen(xid, pTcon, full_path, disposition, desiredAccess,
+       rc = CIFSSMBOpen(xid, tcon, full_path, disposition, desiredAccess,
                         CREATE_NOT_DIR, &netfid, &oplock, NULL,
                         cifs_sb->local_nls, cifs_sb->mnt_cifs_flags &
                                CIFS_MOUNT_MAP_SPECIAL_CHR);
        if (rc) {
-               up(&pCifsFile->fh_sem);
+               mutex_unlock(&pCifsFile->fh_mutex);
                cFYI(1, ("cifs_open returned 0x%x", rc));
                cFYI(1, ("oplock: %d", oplock));
        } else {
+reopen_success:
                pCifsFile->netfid = netfid;
                pCifsFile->invalidHandle = false;
-               up(&pCifsFile->fh_sem);
+               mutex_unlock(&pCifsFile->fh_mutex);
                pCifsInode = CIFS_I(inode);
                if (pCifsInode) {
                        if (can_flush) {
@@ -439,7 +554,7 @@ reopen_error_exit:
                           go to server to get inode info */
                                pCifsInode->clientCanCacheAll = false;
                                pCifsInode->clientCanCacheRead = false;
-                               if (pTcon->unix_ext)
+                               if (tcon->unix_ext)
                                        rc = cifs_get_inode_info_unix(&inode,
                                                full_path, inode->i_sb, xid);
                                else
@@ -467,7 +582,6 @@ reopen_error_exit:
                        cifs_relock_file(pCifsFile);
                }
        }
-
        kfree(full_path);
        FreeXid(xid);
        return rc;
@@ -488,14 +602,15 @@ int cifs_close(struct inode *inode, struct file *file)
        pTcon = cifs_sb->tcon;
        if (pSMBFile) {
                struct cifsLockInfo *li, *tmp;
-
+               write_lock(&GlobalSMBSeslock);
                pSMBFile->closePend = true;
                if (pTcon) {
                        /* no sense reconnecting to close a file that is
                           already closed */
-                       if (pTcon->tidStatus != CifsNeedReconnect) {
+                       if (!pTcon->need_reconnect) {
+                               write_unlock(&GlobalSMBSeslock);
                                timeout = 2;
-                               while ((atomic_read(&pSMBFile->wrtPending) != 0)
+                               while ((atomic_read(&pSMBFile->count) != 1)
                                        && (timeout <= 2048)) {
                                        /* Give write a better chance to get to
                                        server ahead of the close.  We do not
@@ -509,13 +624,14 @@ int cifs_close(struct inode *inode, struct file *file)
                                        msleep(timeout);
                                        timeout *= 4;
                                }
-                               if (atomic_read(&pSMBFile->wrtPending))
-                                       cERROR(1,
-                                               ("close with pending writes"));
-                               rc = CIFSSMBClose(xid, pTcon,
+                               if (!pTcon->need_reconnect &&
+                                   !pSMBFile->invalidHandle)
+                                       rc = CIFSSMBClose(xid, pTcon,
                                                  pSMBFile->netfid);
-                       }
-               }
+                       } else
+                               write_unlock(&GlobalSMBSeslock);
+               } else
+                       write_unlock(&GlobalSMBSeslock);
 
                /* Delete any outstanding lock records.
                   We'll lose them when the file is closed anyway. */
@@ -530,24 +646,7 @@ int cifs_close(struct inode *inode, struct file *file)
                list_del(&pSMBFile->flist);
                list_del(&pSMBFile->tlist);
                write_unlock(&GlobalSMBSeslock);
-               timeout = 10;
-               /* We waited above to give the SMBWrite a chance to issue
-                  on the wire (so we do not get SMBWrite returning EBADF
-                  if writepages is racing with close.  Note that writepages
-                  does not specify a file handle, so it is possible for a file
-                  to be opened twice, and the application close the "wrong"
-                  file handle - in these cases we delay long enough to allow
-                  the SMBWrite to get on the wire before the SMB Close.
-                  We allow total wait here over 45 seconds, more than
-                  oplock break time, and more than enough to allow any write
-                  to complete on the server, or to time out on the client */
-               while ((atomic_read(&pSMBFile->wrtPending) != 0)
-                               && (timeout <= 50000)) {
-                       cERROR(1, ("writes pending, delay free of handle"));
-                       msleep(timeout);
-                       timeout *= 8;
-               }
-               kfree(file->private_data);
+               cifsFileInfo_put(file->private_data);
                file->private_data = NULL;
        } else
                rc = -EBADF;
@@ -587,15 +686,18 @@ int cifs_closedir(struct inode *inode, struct file *file)
                pTcon = cifs_sb->tcon;
 
                cFYI(1, ("Freeing private data in close dir"));
+               write_lock(&GlobalSMBSeslock);
                if (!pCFileStruct->srch_inf.endOfSearch &&
                    !pCFileStruct->invalidHandle) {
                        pCFileStruct->invalidHandle = true;
+                       write_unlock(&GlobalSMBSeslock);
                        rc = CIFSFindClose(xid, pTcon, pCFileStruct->netfid);
                        cFYI(1, ("Closing uncompleted readdir with rc %d",
                                 rc));
                        /* not much we can do if it fails anyway, ignore rc */
                        rc = 0;
-               }
+               } else
+                       write_unlock(&GlobalSMBSeslock);
                ptmp = pCFileStruct->srch_inf.ntwrk_buf_start;
                if (ptmp) {
                        cFYI(1, ("closedir free smb buf in srch struct"));
@@ -637,10 +739,10 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
        __u64 length;
        bool wait_flag = false;
        struct cifs_sb_info *cifs_sb;
-       struct cifsTconInfo *pTcon;
+       struct cifsTconInfo *tcon;
        __u16 netfid;
        __u8 lockType = LOCKING_ANDX_LARGE_FILES;
-       bool posix_locking;
+       bool posix_locking = 0;
 
        length = 1 + pfLock->fl_end - pfLock->fl_start;
        rc = -EACCES;
@@ -691,17 +793,19 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
                cFYI(1, ("Unknown type of lock"));
 
        cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
-       pTcon = cifs_sb->tcon;
+       tcon = cifs_sb->tcon;
 
        if (file->private_data == NULL) {
+               rc = -EBADF;
                FreeXid(xid);
-               return -EBADF;
+               return rc;
        }
        netfid = ((struct cifsFileInfo *)file->private_data)->netfid;
 
-       posix_locking = (cifs_sb->tcon->ses->capabilities & CAP_UNIX) &&
-                       (CIFS_UNIX_FCNTL_CAP & le64_to_cpu(cifs_sb->tcon->fsUnixInfo.Capability));
-
+       if ((tcon->ses->capabilities & CAP_UNIX) &&
+           (CIFS_UNIX_FCNTL_CAP & le64_to_cpu(tcon->fsUnixInfo.Capability)) &&
+           ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NOPOSIXBRL) == 0))
+               posix_locking = 1;
        /* BB add code here to normalize offset and length to
        account for negative length which we can not accept over the
        wire */
@@ -712,7 +816,7 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
                                posix_lock_type = CIFS_RDLCK;
                        else
                                posix_lock_type = CIFS_WRLCK;
-                       rc = CIFSSMBPosixLock(xid, pTcon, netfid, 1 /* get */,
+                       rc = CIFSSMBPosixLock(xid, tcon, netfid, 1 /* get */,
                                        length, pfLock,
                                        posix_lock_type, wait_flag);
                        FreeXid(xid);
@@ -720,10 +824,10 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
                }
 
                /* BB we could chain these into one lock request BB */
-               rc = CIFSSMBLock(xid, pTcon, netfid, length, pfLock->fl_start,
+               rc = CIFSSMBLock(xid, tcon, netfid, length, pfLock->fl_start,
                                 0, 1, lockType, 0 /* wait flag */ );
                if (rc == 0) {
-                       rc = CIFSSMBLock(xid, pTcon, netfid, length,
+                       rc = CIFSSMBLock(xid, tcon, netfid, length,
                                         pfLock->fl_start, 1 /* numUnlock */ ,
                                         0 /* numLock */ , lockType,
                                         0 /* wait flag */ );
@@ -735,8 +839,32 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
 
                } else {
                        /* if rc == ERR_SHARING_VIOLATION ? */
-                       rc = 0; /* do not change lock type to unlock
-                                  since range in use */
+                       rc = 0;
+
+                       if (lockType & LOCKING_ANDX_SHARED_LOCK) {
+                               pfLock->fl_type = F_WRLCK;
+                       } else {
+                               rc = CIFSSMBLock(xid, tcon, netfid, length,
+                                       pfLock->fl_start, 0, 1,
+                                       lockType | LOCKING_ANDX_SHARED_LOCK,
+                                       0 /* wait flag */);
+                               if (rc == 0) {
+                                       rc = CIFSSMBLock(xid, tcon, netfid,
+                                               length, pfLock->fl_start, 1, 0,
+                                               lockType |
+                                               LOCKING_ANDX_SHARED_LOCK,
+                                               0 /* wait flag */);
+                                       pfLock->fl_type = F_RDLCK;
+                                       if (rc != 0)
+                                               cERROR(1, ("Error unlocking "
+                                               "previously locked range %d "
+                                               "during test of lock", rc));
+                                       rc = 0;
+                               } else {
+                                       pfLock->fl_type = F_WRLCK;
+                                       rc = 0;
+                               }
+                       }
                }
 
                FreeXid(xid);
@@ -760,7 +888,7 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
                if (numUnlock == 1)
                        posix_lock_type = CIFS_UNLCK;
 
-               rc = CIFSSMBPosixLock(xid, pTcon, netfid, 0 /* set */,
+               rc = CIFSSMBPosixLock(xid, tcon, netfid, 0 /* set */,
                                      length, pfLock,
                                      posix_lock_type, wait_flag);
        } else {
@@ -768,7 +896,7 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
                        (struct cifsFileInfo *)file->private_data;
 
                if (numLock) {
-                       rc = CIFSSMBLock(xid, pTcon, netfid, length,
+                       rc = CIFSSMBLock(xid, tcon, netfid, length,
                                        pfLock->fl_start,
                                        0, numLock, lockType, wait_flag);
 
@@ -789,7 +917,7 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
                                if (pfLock->fl_start <= li->offset &&
                                                (pfLock->fl_start + length) >=
                                                (li->offset + li->length)) {
-                                       stored_rc = CIFSSMBLock(xid, pTcon,
+                                       stored_rc = CIFSSMBLock(xid, tcon,
                                                        netfid,
                                                        li->length, li->offset,
                                                        1, 0, li->type, false);
@@ -810,6 +938,40 @@ int cifs_lock(struct file *file, int cmd, struct file_lock *pfLock)
        return rc;
 }
 
+/*
+ * Set the timeout on write requests past EOF. For some servers (Windows)
+ * these calls can be very long.
+ *
+ * If we're writing >10M past the EOF we give a 180s timeout. Anything less
+ * than that gets a 45s timeout. Writes not past EOF get 15s timeouts.
+ * The 10M cutoff is totally arbitrary. A better scheme for this would be
+ * welcome if someone wants to suggest one.
+ *
+ * We may be able to do a better job with this if there were some way to
+ * declare that a file should be sparse.
+ */
+static int
+cifs_write_timeout(struct cifsInodeInfo *cifsi, loff_t offset)
+{
+       if (offset <= cifsi->server_eof)
+               return CIFS_STD_OP;
+       else if (offset > (cifsi->server_eof + (10 * 1024 * 1024)))
+               return CIFS_VLONG_OP;
+       else
+               return CIFS_LONG_OP;
+}
+
+/* update the file size (if needed) after a write */
+static void
+cifs_update_eof(struct cifsInodeInfo *cifsi, loff_t offset,
+                     unsigned int bytes_written)
+{
+       loff_t end_of_write = offset + bytes_written;
+
+       if (end_of_write > cifsi->server_eof)
+               cifsi->server_eof = end_of_write;
+}
+
 ssize_t cifs_user_write(struct file *file, const char __user *write_data,
        size_t write_size, loff_t *poffset)
 {
@@ -820,6 +982,7 @@ ssize_t cifs_user_write(struct file *file, const char __user *write_data,
        struct cifsTconInfo *pTcon;
        int xid, long_op;
        struct cifsFileInfo *open_file;
+       struct cifsInodeInfo *cifsi = CIFS_I(file->f_path.dentry->d_inode);
 
        cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
 
@@ -839,11 +1002,7 @@ ssize_t cifs_user_write(struct file *file, const char __user *write_data,
 
        xid = GetXid();
 
-       if (*poffset > file->f_path.dentry->d_inode->i_size)
-               long_op = CIFS_VLONG_OP; /* writes past EOF take long time */
-       else
-               long_op = CIFS_LONG_OP;
-
+       long_op = cifs_write_timeout(cifsi, *poffset);
        for (total_written = 0; write_size > total_written;
             total_written += bytes_written) {
                rc = -EAGAIN;
@@ -887,8 +1046,10 @@ ssize_t cifs_user_write(struct file *file, const char __user *write_data,
                                FreeXid(xid);
                                return rc;
                        }
-               } else
+               } else {
+                       cifs_update_eof(cifsi, *poffset, bytes_written);
                        *poffset += bytes_written;
+               }
                long_op = CIFS_STD_OP; /* subsequent writes fast -
                                    15 seconds is plenty */
        }
@@ -915,7 +1076,7 @@ ssize_t cifs_user_write(struct file *file, const char __user *write_data,
 }
 
 static ssize_t cifs_write(struct file *file, const char *write_data,
-       size_t write_size, loff_t *poffset)
+                         size_t write_size, loff_t *poffset)
 {
        int rc = 0;
        unsigned int bytes_written = 0;
@@ -924,6 +1085,7 @@ static ssize_t cifs_write(struct file *file, const char *write_data,
        struct cifsTconInfo *pTcon;
        int xid, long_op;
        struct cifsFileInfo *open_file;
+       struct cifsInodeInfo *cifsi = CIFS_I(file->f_path.dentry->d_inode);
 
        cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
 
@@ -938,11 +1100,7 @@ static ssize_t cifs_write(struct file *file, const char *write_data,
 
        xid = GetXid();
 
-       if (*poffset > file->f_path.dentry->d_inode->i_size)
-               long_op = CIFS_VLONG_OP; /* writes past EOF can be slow */
-       else
-               long_op = CIFS_LONG_OP;
-
+       long_op = cifs_write_timeout(cifsi, *poffset);
        for (total_written = 0; write_size > total_written;
             total_written += bytes_written) {
                rc = -EAGAIN;
@@ -1005,8 +1163,10 @@ static ssize_t cifs_write(struct file *file, const char *write_data,
                                FreeXid(xid);
                                return rc;
                        }
-               } else
+               } else {
+                       cifs_update_eof(cifsi, *poffset, bytes_written);
                        *poffset += bytes_written;
+               }
                long_op = CIFS_STD_OP; /* subsequent writes fast -
                                    15 seconds is plenty */
        }
@@ -1048,7 +1208,7 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode)
                        if (!open_file->invalidHandle) {
                                /* found a good file */
                                /* lock it so it will not be closed on us */
-                               atomic_inc(&open_file->wrtPending);
+                               cifsFileInfo_get(open_file);
                                read_unlock(&GlobalSMBSeslock);
                                return open_file;
                        } /* else might as well continue, and look for
@@ -1065,6 +1225,7 @@ struct cifsFileInfo *find_readable_file(struct cifsInodeInfo *cifs_inode)
 struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode)
 {
        struct cifsFileInfo *open_file;
+       bool any_available = false;
        int rc;
 
        /* Having a null inode here (because mapping->host was set to zero by
@@ -1080,12 +1241,14 @@ struct cifsFileInfo *find_writable_file(struct cifsInodeInfo *cifs_inode)
        read_lock(&GlobalSMBSeslock);
 refind_writable:
        list_for_each_entry(open_file, &cifs_inode->openFileList, flist) {
-               if (open_file->closePend)
+               if (open_file->closePend ||
+                   (!any_available && open_file->pid != current->tgid))
                        continue;
+
                if (open_file->pfile &&
                    ((open_file->pfile->f_flags & O_RDWR) ||
                     (open_file->pfile->f_flags & O_WRONLY))) {
-                       atomic_inc(&open_file->wrtPending);
+                       cifsFileInfo_get(open_file);
 
                        if (!open_file->invalidHandle) {
                                /* found a good writable file */
@@ -1102,7 +1265,7 @@ refind_writable:
                                else { /* start over in case this was deleted */
                                       /* since the list could be modified */
                                        read_lock(&GlobalSMBSeslock);
-                                       atomic_dec(&open_file->wrtPending);
+                                       cifsFileInfo_put(open_file);
                                        goto refind_writable;
                                }
                        }
@@ -1118,7 +1281,7 @@ refind_writable:
                        read_lock(&GlobalSMBSeslock);
                        /* can not use this handle, no write
                           pending on this one after all */
-                       atomic_dec(&open_file->wrtPending);
+                       cifsFileInfo_put(open_file);
 
                        if (open_file->closePend) /* list could have changed */
                                goto refind_writable;
@@ -1131,6 +1294,11 @@ refind_writable:
                           of the loop here. */
                }
        }
+       /* couldn't find useable FH with same pid, try any available */
+       if (!any_available) {
+               any_available = true;
+               goto refind_writable;
+       }
        read_unlock(&GlobalSMBSeslock);
        return NULL;
 }
@@ -1177,7 +1345,7 @@ static int cifs_partialpagewrite(struct page *page, unsigned from, unsigned to)
        if (open_file) {
                bytes_written = cifs_write(open_file->pfile, write_data,
                                           to-from, &offset);
-               atomic_dec(&open_file->wrtPending);
+               cifsFileInfo_put(open_file);
                /* Does mm or vfs already set times? */
                inode->i_atime = inode->i_mtime = current_fs_time(inode->i_sb);
                if ((bytes_written > 0) && (offset))
@@ -1211,11 +1379,12 @@ static int cifs_writepages(struct address_space *mapping,
        int nr_pages;
        __u64 offset = 0;
        struct cifsFileInfo *open_file;
+       struct cifsInodeInfo *cifsi = CIFS_I(mapping->host);
        struct page *page;
        struct pagevec pvec;
        int rc = 0;
        int scanned = 0;
-       int xid;
+       int xid, long_op;
 
        cifs_sb = CIFS_SB(mapping->host->i_sb);
 
@@ -1359,12 +1528,15 @@ retry:
                                cERROR(1, ("No writable handles for inode"));
                                rc = -EBADF;
                        } else {
+                               long_op = cifs_write_timeout(cifsi, offset);
                                rc = CIFSSMBWrite2(xid, cifs_sb->tcon,
                                                   open_file->netfid,
                                                   bytes_to_write, offset,
                                                   &bytes_written, iov, n_iov,
-                                                  CIFS_LONG_OP);
-                               atomic_dec(&open_file->wrtPending);
+                                                  long_op);
+                               cifsFileInfo_put(open_file);
+                               cifs_update_eof(cifsi, offset, bytes_written);
+
                                if (rc || bytes_written < bytes_to_write) {
                                        cERROR(1, ("Write2 ret %d, wrote %d",
                                                  rc, bytes_written));
@@ -1396,7 +1568,10 @@ retry:
                        if ((wbc->nr_to_write -= n_iov) <= 0)
                                done = 1;
                        index = next;
-               }
+               } else
+                       /* Need to re-find the pages we skipped */
+                       index = pvec.pages[0]->index + 1;
+
                pagevec_release(&pvec);
        }
        if (!scanned && !done) {
@@ -1447,49 +1622,56 @@ static int cifs_writepage(struct page *page, struct writeback_control *wbc)
        return rc;
 }
 
-static int cifs_commit_write(struct file *file, struct page *page,
-       unsigned offset, unsigned to)
+static int cifs_write_end(struct file *file, struct address_space *mapping,
+                       loff_t pos, unsigned len, unsigned copied,
+                       struct page *page, void *fsdata)
 {
-       int xid;
-       int rc = 0;
-       struct inode *inode = page->mapping->host;
-       loff_t position = ((loff_t)page->index << PAGE_CACHE_SHIFT) + to;
-       char *page_data;
+       int rc;
+       struct inode *inode = mapping->host;
 
-       xid = GetXid();
-       cFYI(1, ("commit write for page %p up to position %lld for %d",
-                page, position, to));
-       spin_lock(&inode->i_lock);
-       if (position > inode->i_size)
-               i_size_write(inode, position);
+       cFYI(1, ("write_end for page %p from pos %lld with %d bytes",
+                page, pos, copied));
+
+       if (PageChecked(page)) {
+               if (copied == len)
+                       SetPageUptodate(page);
+               ClearPageChecked(page);
+       } else if (!PageUptodate(page) && copied == PAGE_CACHE_SIZE)
+               SetPageUptodate(page);
 
-       spin_unlock(&inode->i_lock);
        if (!PageUptodate(page)) {
-               position =  ((loff_t)page->index << PAGE_CACHE_SHIFT) + offset;
-               /* can not rely on (or let) writepage write this data */
-               if (to < offset) {
-                       cFYI(1, ("Illegal offsets, can not copy from %d to %d",
-                               offset, to));
-                       FreeXid(xid);
-                       return rc;
-               }
+               char *page_data;
+               unsigned offset = pos & (PAGE_CACHE_SIZE - 1);
+               int xid;
+
+               xid = GetXid();
                /* this is probably better than directly calling
                   partialpage_write since in this function the file handle is
                   known which we might as well leverage */
                /* BB check if anything else missing out of ppw
                   such as updating last write time */
                page_data = kmap(page);
-               rc = cifs_write(file, page_data + offset, to-offset,
-                               &position);
-               if (rc > 0)
-                       rc = 0;
-               /* else if (rc < 0) should we set writebehind rc? */
+               rc = cifs_write(file, page_data + offset, copied, &pos);
+               /* if (rc < 0) should we set writebehind rc? */
                kunmap(page);
+
+               FreeXid(xid);
        } else {
+               rc = copied;
+               pos += copied;
                set_page_dirty(page);
        }
 
-       FreeXid(xid);
+       if (rc > 0) {
+               spin_lock(&inode->i_lock);
+               if (pos > inode->i_size)
+                       i_size_write(inode, pos);
+               spin_unlock(&inode->i_lock);
+       }
+
+       unlock_page(page);
+       page_cache_release(page);
+
        return rc;
 }
 
@@ -1497,6 +1679,9 @@ int cifs_fsync(struct file *file, struct dentry *dentry, int datasync)
 {
        int xid;
        int rc = 0;
+       struct cifsTconInfo *tcon;
+       struct cifsFileInfo *smbfile =
+               (struct cifsFileInfo *)file->private_data;
        struct inode *inode = file->f_path.dentry->d_inode;
 
        xid = GetXid();
@@ -1508,7 +1693,12 @@ int cifs_fsync(struct file *file, struct dentry *dentry, int datasync)
        if (rc == 0) {
                rc = CIFS_I(inode)->write_behind_rc;
                CIFS_I(inode)->write_behind_rc = 0;
+               tcon = CIFS_SB(inode->i_sb)->tcon;
+               if (!rc && tcon && smbfile &&
+                  !(CIFS_SB(inode->i_sb)->mnt_cifs_flags & CIFS_MOUNT_NOSSYNC))
+                       rc = CIFSSMBFlush(xid, tcon, smbfile->netfid);
        }
+
        FreeXid(xid);
        return rc;
 }
@@ -1591,8 +1781,9 @@ ssize_t cifs_user_read(struct file *file, char __user *read_data,
        pTcon = cifs_sb->tcon;
 
        if (file->private_data == NULL) {
+               rc = -EBADF;
                FreeXid(xid);
-               return -EBADF;
+               return rc;
        }
        open_file = (struct cifsFileInfo *)file->private_data;
 
@@ -1671,8 +1862,9 @@ static ssize_t cifs_read(struct file *file, char *read_data, size_t read_size,
        pTcon = cifs_sb->tcon;
 
        if (file->private_data == NULL) {
+               rc = -EBADF;
                FreeXid(xid);
-               return -EBADF;
+               return rc;
        }
        open_file = (struct cifsFileInfo *)file->private_data;
 
@@ -1723,11 +1915,10 @@ static ssize_t cifs_read(struct file *file, char *read_data, size_t read_size,
 
 int cifs_file_mmap(struct file *file, struct vm_area_struct *vma)
 {
-       struct dentry *dentry = file->f_path.dentry;
        int rc, xid;
 
        xid = GetXid();
-       rc = cifs_revalidate(dentry);
+       rc = cifs_revalidate_file(file);
        if (rc) {
                cFYI(1, ("Validation prior to mmap failed, error=%d", rc));
                FreeXid(xid);
@@ -1780,7 +1971,7 @@ static void cifs_copy_cache_pages(struct address_space *mapping,
                SetPageUptodate(page);
                unlock_page(page);
                if (!pagevec_add(plru_pvec, page))
-                       __pagevec_lru_add(plru_pvec);
+                       __pagevec_lru_add_file(plru_pvec);
                data += PAGE_CACHE_SIZE;
        }
        return;
@@ -1805,15 +1996,16 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
 
        xid = GetXid();
        if (file->private_data == NULL) {
+               rc = -EBADF;
                FreeXid(xid);
-               return -EBADF;
+               return rc;
        }
        open_file = (struct cifsFileInfo *)file->private_data;
        cifs_sb = CIFS_SB(file->f_path.dentry->d_sb);
        pTcon = cifs_sb->tcon;
 
        pagevec_init(&lru_pvec, 0);
-               cFYI(DBG2, ("rpages: num pages %d", num_pages));
+       cFYI(DBG2, ("rpages: num pages %d", num_pages));
        for (i = 0; i < num_pages; ) {
                unsigned contig_pages;
                struct page *tmp_page;
@@ -1914,7 +2106,7 @@ static int cifs_readpages(struct file *file, struct address_space *mapping,
                bytes_read = 0;
        }
 
-       pagevec_lru_add(&lru_pvec);
+       pagevec_lru_add_file(&lru_pvec);
 
 /* need to free smb_read_data buf before exit */
        if (smb_read_data) {
@@ -1971,8 +2163,9 @@ static int cifs_readpage(struct file *file, struct page *page)
        xid = GetXid();
 
        if (file->private_data == NULL) {
+               rc = -EBADF;
                FreeXid(xid);
-               return -EBADF;
+               return rc;
        }
 
        cFYI(1, ("readpage %p at offset %d 0x%x\n",
@@ -2035,59 +2228,152 @@ bool is_size_safe_to_change(struct cifsInodeInfo *cifsInode, __u64 end_of_file)
                return true;
 }
 
-static int cifs_prepare_write(struct file *file, struct page *page,
-       unsigned from, unsigned to)
+static int cifs_write_begin(struct file *file, struct address_space *mapping,
+                       loff_t pos, unsigned len, unsigned flags,
+                       struct page **pagep, void **fsdata)
 {
-       int rc = 0;
+       pgoff_t index = pos >> PAGE_CACHE_SHIFT;
+       loff_t offset = pos & (PAGE_CACHE_SIZE - 1);
+       loff_t page_start = pos & PAGE_MASK;
        loff_t i_size;
-       loff_t offset;
+       struct page *page;
+       int rc = 0;
 
-       cFYI(1, ("prepare write for page %p from %d to %d", page, from, to));
-       if (PageUptodate(page))
-               return 0;
+       cFYI(1, ("write_begin from %lld len %d", (long long)pos, len));
 
-       /* If we are writing a full page it will be up to date,
-          no need to read from the server */
-       if ((to == PAGE_CACHE_SIZE) && (from == 0)) {
-               SetPageUptodate(page);
-               return 0;
+       page = grab_cache_page_write_begin(mapping, index, flags);
+       if (!page) {
+               rc = -ENOMEM;
+               goto out;
        }
 
-       offset = (loff_t)page->index << PAGE_CACHE_SHIFT;
-       i_size = i_size_read(page->mapping->host);
+       if (PageUptodate(page))
+               goto out;
+
+       /*
+        * If we write a full page it will be up to date, no need to read from
+        * the server. If the write is short, we'll end up doing a sync write
+        * instead.
+        */
+       if (len == PAGE_CACHE_SIZE)
+               goto out;
+
+       /*
+        * optimize away the read when we have an oplock, and we're not
+        * expecting to use any of the data we'd be reading in. That
+        * is, when the page lies beyond the EOF, or straddles the EOF
+        * and the write will cover all of the existing data.
+        */
+       if (CIFS_I(mapping->host)->clientCanCacheRead) {
+               i_size = i_size_read(mapping->host);
+               if (page_start >= i_size ||
+                   (offset == 0 && (pos + len) >= i_size)) {
+                       zero_user_segments(page, 0, offset,
+                                          offset + len,
+                                          PAGE_CACHE_SIZE);
+                       /*
+                        * PageChecked means that the parts of the page
+                        * to which we're not writing are considered up
+                        * to date. Once the data is copied to the
+                        * page, it can be set uptodate.
+                        */
+                       SetPageChecked(page);
+                       goto out;
+               }
+       }
 
-       if ((offset >= i_size) ||
-           ((from == 0) && (offset + to) >= i_size)) {
+       if ((file->f_flags & O_ACCMODE) != O_WRONLY) {
                /*
-                * We don't need to read data beyond the end of the file.
-                * zero it, and set the page uptodate
+                * might as well read a page, it is fast enough. If we get
+                * an error, we don't need to return it. cifs_write_end will
+                * do a sync write instead since PG_uptodate isn't set.
                 */
-               simple_prepare_write(file, page, from, to);
-               SetPageUptodate(page);
-       } else if ((file->f_flags & O_ACCMODE) != O_WRONLY) {
-               /* might as well read a page, it is fast enough */
-               rc = cifs_readpage_worker(file, page, &offset);
+               cifs_readpage_worker(file, page, &page_start);
        } else {
                /* we could try using another file handle if there is one -
                   but how would we lock it to prevent close of that handle
                   racing with this read? In any case
-                  this will be written out by commit_write so is fine */
+                  this will be written out by write_end so is fine */
        }
+out:
+       *pagep = page;
+       return rc;
+}
 
-       /* we do not need to pass errors back
-          e.g. if we do not have read access to the file
-          because cifs_commit_write will do the right thing.  -- shaggy */
+static void
+cifs_oplock_break(struct slow_work *work)
+{
+       struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+                                                 oplock_break);
+       struct inode *inode = cfile->pInode;
+       struct cifsInodeInfo *cinode = CIFS_I(inode);
+       struct cifs_sb_info *cifs_sb = CIFS_SB(cfile->mnt->mnt_sb);
+       int rc, waitrc = 0;
+
+       if (inode && S_ISREG(inode->i_mode)) {
+#ifdef CONFIG_CIFS_EXPERIMENTAL
+               if (cinode->clientCanCacheAll == 0)
+                       break_lease(inode, O_RDONLY);
+               else if (cinode->clientCanCacheRead == 0)
+                       break_lease(inode, O_WRONLY);
+#endif
+               rc = filemap_fdatawrite(inode->i_mapping);
+               if (cinode->clientCanCacheRead == 0) {
+                       waitrc = filemap_fdatawait(inode->i_mapping);
+                       invalidate_remote_inode(inode);
+               }
+               if (!rc)
+                       rc = waitrc;
+               if (rc)
+                       cinode->write_behind_rc = rc;
+               cFYI(1, ("Oplock flush inode %p rc %d", inode, rc));
+       }
 
+       /*
+        * releasing stale oplock after recent reconnect of smb session using
+        * a now incorrect file handle is not a data integrity issue but do
+        * not bother sending an oplock release if session to server still is
+        * disconnected since oplock already released by the server
+        */
+       if (!cfile->closePend && !cfile->oplock_break_cancelled) {
+               rc = CIFSSMBLock(0, cifs_sb->tcon, cfile->netfid, 0, 0, 0, 0,
+                                LOCKING_ANDX_OPLOCK_RELEASE, false);
+               cFYI(1, ("Oplock release rc = %d", rc));
+       }
+}
+
+static int
+cifs_oplock_break_get(struct slow_work *work)
+{
+       struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+                                                 oplock_break);
+       mntget(cfile->mnt);
+       cifsFileInfo_get(cfile);
        return 0;
 }
 
+static void
+cifs_oplock_break_put(struct slow_work *work)
+{
+       struct cifsFileInfo *cfile = container_of(work, struct cifsFileInfo,
+                                                 oplock_break);
+       mntput(cfile->mnt);
+       cifsFileInfo_put(cfile);
+}
+
+const struct slow_work_ops cifs_oplock_break_ops = {
+       .get_ref        = cifs_oplock_break_get,
+       .put_ref        = cifs_oplock_break_put,
+       .execute        = cifs_oplock_break,
+};
+
 const struct address_space_operations cifs_addr_ops = {
        .readpage = cifs_readpage,
        .readpages = cifs_readpages,
        .writepage = cifs_writepage,
        .writepages = cifs_writepages,
-       .prepare_write = cifs_prepare_write,
-       .commit_write = cifs_commit_write,
+       .write_begin = cifs_write_begin,
+       .write_end = cifs_write_end,
        .set_page_dirty = __set_page_dirty_nobuffers,
        /* .sync_page = cifs_sync_page, */
        /* .direct_IO = */
@@ -2102,8 +2388,8 @@ const struct address_space_operations cifs_addr_ops_smallbuf = {
        .readpage = cifs_readpage,
        .writepage = cifs_writepage,
        .writepages = cifs_writepages,
-       .prepare_write = cifs_prepare_write,
-       .commit_write = cifs_commit_write,
+       .write_begin = cifs_write_begin,
+       .write_end = cifs_write_end,
        .set_page_dirty = __set_page_dirty_nobuffers,
        /* .sync_page = cifs_sync_page, */
        /* .direct_IO = */