cifs: fix locking and list handling code in cifs_open and its helper
[sfrench/cifs-2.6.git] / fs / cifs / file.c
index c34b7f8a217b383b1f8505822f00052276779833..429337eb7afec9f0933c72927eedcc7412207c88 100644 (file)
@@ -30,6 +30,7 @@
 #include <linux/writeback.h>
 #include <linux/task_io_accounting_ops.h>
 #include <linux/delay.h>
+#include <linux/mount.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;
-       mutex_init(&private_data->fh_mutex);
-       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)
@@ -125,9 +103,11 @@ 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, int oplock, u16 netfid)
+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);
@@ -221,17 +201,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 write_begin */
-       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 */
@@ -281,7 +250,8 @@ 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 *tcon;
        struct cifsFileInfo *pCifsFile;
@@ -326,7 +296,7 @@ int cifs_open(struct inode *inode, struct file *file)
                        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, inode->i_sb,
+               rc = cifs_posix_open(full_path, &inode, file->f_path.mnt,
                                     cifs_sb->mnt_file_mode /* ignored */,
                                     oflags, &oplock, &netfid, xid);
                if (rc == 0) {
@@ -416,24 +386,17 @@ 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, &tcon->openFileList);
 
-       pCifsInode = CIFS_I(file->f_path.dentry->d_inode);
-       if (pCifsInode) {
-               rc = cifs_open_inode_helper(inode, file, pCifsInode,
-                                           pCifsFile, tcon,
-                                           &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
@@ -476,7 +439,8 @@ 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 *tcon;
        struct cifsFileInfo *pCifsFile;
@@ -545,7 +509,7 @@ reopen_error_exit:
                        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, inode->i_sb,
+               rc = cifs_posix_open(full_path, NULL, file->f_path.mnt,
                                     cifs_sb->mnt_file_mode /* ignored */,
                                     oflags, &oplock, &netfid, xid);
                if (rc == 0) {
@@ -643,7 +607,7 @@ int cifs_close(struct inode *inode, struct file *file)
                        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
@@ -657,8 +621,6 @@ int cifs_close(struct inode *inode, struct file *file)
                                        msleep(timeout);
                                        timeout *= 4;
                                }
-                               if (atomic_read(&pSMBFile->wrtPending))
-                                       cERROR(1, ("close with pending write"));
                                if (!pTcon->need_reconnect &&
                                    !pSMBFile->invalidHandle)
                                        rc = CIFSSMBClose(xid, pTcon,
@@ -681,24 +643,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;
@@ -1236,7 +1181,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
@@ -1276,7 +1221,7 @@ refind_writable:
                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 */
@@ -1293,7 +1238,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;
                                }
                        }
@@ -1309,7 +1254,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;
@@ -1373,7 +1318,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))
@@ -1562,7 +1507,7 @@ retry:
                                                   bytes_to_write, offset,
                                                   &bytes_written, iov, n_iov,
                                                   long_op);
-                               atomic_dec(&open_file->wrtPending);
+                               cifsFileInfo_put(open_file);
                                cifs_update_eof(cifsi, offset, bytes_written);
 
                                if (rc || bytes_written < bytes_to_write) {
@@ -2329,6 +2274,73 @@ out:
        return rc;
 }
 
+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, FMODE_READ);
+               else if (cinode->clientCanCacheRead == 0)
+                       break_lease(inode, FMODE_WRITE);
+#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,