CIFS: Add SMB2 support for cifs_iovec_write
authorPavel Shilovsky <pshilovsky@samba.org>
Tue, 18 Sep 2012 23:20:29 +0000 (16:20 -0700)
committerSteve French <smfrench@gmail.com>
Tue, 25 Sep 2012 02:46:28 +0000 (21:46 -0500)
Signed-off-by: Pavel Shilovsky <pshilovsky@samba.org>
Signed-off-by: Steve French <smfrench@gmail.com>
Signed-off-by: Steve French <sfrench@us.ibm.com>
fs/cifs/cifsfs.c
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/cifssmb.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.c
fs/cifs/smb2pdu.h
fs/cifs/smb2proto.h

index db8a404a51dd03436653c0a52d0a6e5330bbe5bc..2829f374dbf78e044397ded8bb931f1cf6b6dfd1 100644 (file)
@@ -89,6 +89,10 @@ extern mempool_t *cifs_mid_poolp;
 
 struct workqueue_struct        *cifsiod_wq;
 
+#ifdef CONFIG_HIGHMEM
+DEFINE_MUTEX(cifs_kmap_mutex);
+#endif
+
 static int
 cifs_read_super(struct super_block *sb)
 {
index aef167470654ec793a8e47b6b843986f9aed8e11..330f6259bb6d376f43f59996623e731e5fc25be6 100644 (file)
@@ -582,6 +582,33 @@ get_next_mid(struct TCP_Server_Info *server)
 #define CIFS_KMAP_SIZE_LIMIT   (1<<24)
 #endif /* CONFIG_HIGHMEM */
 
+#ifdef CONFIG_HIGHMEM
+/*
+ * On arches that have high memory, kmap address space is limited. By
+ * serializing the kmap operations on those arches, we ensure that we don't
+ * end up with a bunch of threads in writeback with partially mapped page
+ * arrays, stuck waiting for kmap to come back. That situation prevents
+ * progress and can deadlock.
+ */
+
+extern struct mutex cifs_kmap_mutex;
+
+static inline void
+cifs_kmap_lock(void)
+{
+       mutex_lock(&cifs_kmap_mutex);
+}
+
+static inline void
+cifs_kmap_unlock(void)
+{
+       mutex_unlock(&cifs_kmap_mutex);
+}
+#else /* !CONFIG_HIGHMEM */
+#define cifs_kmap_lock() do { ; } while (0)
+#define cifs_kmap_unlock() do { ; } while (0)
+#endif /* CONFIG_HIGHMEM */
+
 /*
  * Macros to allow the TCP_Server_Info->net field and related code to drop out
  * when CONFIG_NET_NS isn't set.
@@ -891,6 +918,26 @@ struct cifs_readdata {
        struct kvec                     iov[1];
 };
 
+struct cifs_writedata;
+
+/* asynchronous write support */
+struct cifs_writedata {
+       struct kref                     refcount;
+       struct list_head                list;
+       struct completion               done;
+       enum writeback_sync_modes       sync_mode;
+       struct work_struct              work;
+       struct cifsFileInfo             *cfile;
+       __u64                           offset;
+       pid_t                           pid;
+       unsigned int                    bytes;
+       int                             result;
+       void (*marshal_iov) (struct kvec *iov,
+                            struct cifs_writedata *wdata);
+       unsigned int                    nr_pages;
+       struct page                     *pages[1];
+};
+
 /*
  * Take a reference on the file private data. Must be called with
  * cifs_file_list_lock held.
index 6656eb5dbf70495b9070e477442875aa102ca102..8b320c7b582cb5a269105f2b4ea6407ebb72c1ad 100644 (file)
@@ -468,24 +468,6 @@ void cifs_readdata_release(struct kref *refcount);
 int cifs_async_readv(struct cifs_readdata *rdata);
 int cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid);
 
-/* asynchronous write support */
-struct cifs_writedata {
-       struct kref                     refcount;
-       struct list_head                list;
-       struct completion               done;
-       enum writeback_sync_modes       sync_mode;
-       struct work_struct              work;
-       struct cifsFileInfo             *cfile;
-       __u64                           offset;
-       pid_t                           pid;
-       unsigned int                    bytes;
-       int                             result;
-       void (*marshal_iov) (struct kvec *iov,
-                            struct cifs_writedata *wdata);
-       unsigned int                    nr_pages;
-       struct page                     *pages[1];
-};
-
 int cifs_async_writev(struct cifs_writedata *wdata);
 void cifs_writev_complete(struct work_struct *work);
 struct cifs_writedata *cifs_writedata_alloc(unsigned int nr_pages,
index 2a9b2738770875597c14041154e2b7bcb964359d..f27b13ea08b88fb7c33ffae1c2040b46cb0df462 100644 (file)
@@ -86,32 +86,6 @@ static struct {
 #endif /* CONFIG_CIFS_WEAK_PW_HASH */
 #endif /* CIFS_POSIX */
 
-#ifdef CONFIG_HIGHMEM
-/*
- * On arches that have high memory, kmap address space is limited. By
- * serializing the kmap operations on those arches, we ensure that we don't
- * end up with a bunch of threads in writeback with partially mapped page
- * arrays, stuck waiting for kmap to come back. That situation prevents
- * progress and can deadlock.
- */
-static DEFINE_MUTEX(cifs_kmap_mutex);
-
-static inline void
-cifs_kmap_lock(void)
-{
-       mutex_lock(&cifs_kmap_mutex);
-}
-
-static inline void
-cifs_kmap_unlock(void)
-{
-       mutex_unlock(&cifs_kmap_mutex);
-}
-#else /* !CONFIG_HIGHMEM */
-#define cifs_kmap_lock() do { ; } while(0)
-#define cifs_kmap_unlock() do { ; } while(0)
-#endif /* CONFIG_HIGHMEM */
-
 /*
  * Mark as invalid, all open files on tree connections since they
  * were closed when session to server was lost.
index d9ca357d98093c688ee96eac3834ce8b4be6d26b..da31235023fbafe361e9f2f5eca3f99e72200dba 100644 (file)
@@ -434,6 +434,7 @@ struct smb_version_operations smb21_operations = {
        .close = smb2_close_file,
        .flush = smb2_flush_file,
        .async_readv = smb2_async_readv,
+       .async_writev = smb2_async_writev,
 };
 
 struct smb_version_values smb21_values = {
index e18671852d41e99b9c1fbae1003241eb52b04115..cb6acc7b1ca8181b0ec1f096344cc6e666329aa0 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/vfs.h>
 #include <linux/task_io_accounting_ops.h>
 #include <linux/uaccess.h>
+#include <linux/pagemap.h>
 #include <linux/xattr.h>
 #include "smb2pdu.h"
 #include "cifsglob.h"
@@ -1327,3 +1328,125 @@ smb2_async_readv(struct cifs_readdata *rdata)
        cifs_small_buf_release(buf);
        return rc;
 }
+
+/*
+ * Check the mid_state and signature on received buffer (if any), and queue the
+ * workqueue completion task.
+ */
+static void
+smb2_writev_callback(struct mid_q_entry *mid)
+{
+       struct cifs_writedata *wdata = mid->callback_data;
+       struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink);
+       unsigned int written;
+       struct smb2_write_rsp *rsp = (struct smb2_write_rsp *)mid->resp_buf;
+       unsigned int credits_received = 1;
+
+       switch (mid->mid_state) {
+       case MID_RESPONSE_RECEIVED:
+               credits_received = le16_to_cpu(rsp->hdr.CreditRequest);
+               wdata->result = smb2_check_receive(mid, tcon->ses->server, 0);
+               if (wdata->result != 0)
+                       break;
+
+               written = le32_to_cpu(rsp->DataLength);
+               /*
+                * Mask off high 16 bits when bytes written as returned
+                * by the server is greater than bytes requested by the
+                * client. OS/2 servers are known to set incorrect
+                * CountHigh values.
+                */
+               if (written > wdata->bytes)
+                       written &= 0xFFFF;
+
+               if (written < wdata->bytes)
+                       wdata->result = -ENOSPC;
+               else
+                       wdata->bytes = written;
+               break;
+       case MID_REQUEST_SUBMITTED:
+       case MID_RETRY_NEEDED:
+               wdata->result = -EAGAIN;
+               break;
+       default:
+               wdata->result = -EIO;
+               break;
+       }
+
+       if (wdata->result)
+               cifs_stats_fail_inc(tcon, SMB2_WRITE_HE);
+
+       queue_work(cifsiod_wq, &wdata->work);
+       DeleteMidQEntry(mid);
+       add_credits(tcon->ses->server, credits_received, 0);
+}
+
+/* smb2_async_writev - send an async write, and set up mid to handle result */
+int
+smb2_async_writev(struct cifs_writedata *wdata)
+{
+       int i, rc = -EACCES;
+       struct smb2_write_req *req = NULL;
+       struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink);
+       struct kvec *iov = NULL;
+
+       rc = small_smb2_init(SMB2_WRITE, tcon, (void **) &req);
+       if (rc)
+               goto async_writev_out;
+
+       /* 1 iov per page + 1 for header */
+       iov = kzalloc((wdata->nr_pages + 1) * sizeof(*iov), GFP_NOFS);
+       if (iov == NULL) {
+               rc = -ENOMEM;
+               goto async_writev_out;
+       }
+
+       req->hdr.ProcessId = cpu_to_le32(wdata->cfile->pid);
+
+       req->PersistentFileId = wdata->cfile->fid.persistent_fid;
+       req->VolatileFileId = wdata->cfile->fid.volatile_fid;
+       req->WriteChannelInfoOffset = 0;
+       req->WriteChannelInfoLength = 0;
+       req->Channel = 0;
+       req->Offset = cpu_to_le64(wdata->offset);
+       /* 4 for rfc1002 length field */
+       req->DataOffset = cpu_to_le16(
+                               offsetof(struct smb2_write_req, Buffer) - 4);
+       req->RemainingBytes = 0;
+
+       /* 4 for rfc1002 length field and 1 for Buffer */
+       iov[0].iov_len = get_rfc1002_length(req) + 4 - 1;
+       iov[0].iov_base = (char *)req;
+
+       /*
+        * This function should marshal up the page array into the kvec
+        * array, reserving [0] for the header. It should kmap the pages
+        * and set the iov_len properly for each one. It may also set
+        * wdata->bytes too.
+        */
+       cifs_kmap_lock();
+       wdata->marshal_iov(iov, wdata);
+       cifs_kmap_unlock();
+
+       cFYI(1, "async write at %llu %u bytes", wdata->offset, wdata->bytes);
+
+       req->Length = cpu_to_le32(wdata->bytes);
+
+       inc_rfc1001_len(&req->hdr, wdata->bytes - 1 /* Buffer */);
+
+       kref_get(&wdata->refcount);
+       rc = cifs_call_async(tcon->ses->server, iov, wdata->nr_pages + 1,
+                            NULL, smb2_writev_callback, wdata, 0);
+
+       if (rc)
+               kref_put(&wdata->refcount, cifs_writedata_release);
+
+       /* send is done, unmap pages */
+       for (i = 0; i < wdata->nr_pages; i++)
+               kunmap(wdata->pages[i]);
+
+async_writev_out:
+       cifs_small_buf_release(req);
+       kfree(iov);
+       return rc;
+}
index 4abb5810680943289b5e8b8d4bca575803d6516b..21ec9ed280f9568d883323c84664323a99c5eb2e 100644 (file)
@@ -496,6 +496,36 @@ struct smb2_read_rsp {
        __u8   Buffer[1];
 } __packed;
 
+/* For write request Flags field below the following flag is defined: */
+#define SMB2_WRITEFLAG_WRITE_THROUGH 0x00000001
+
+struct smb2_write_req {
+       struct smb2_hdr hdr;
+       __le16 StructureSize; /* Must be 49 */
+       __le16 DataOffset; /* offset from start of SMB2 header to write data */
+       __le32 Length;
+       __le64 Offset;
+       __u64  PersistentFileId; /* opaque endianness */
+       __u64  VolatileFileId; /* opaque endianness */
+       __le32 Channel; /* Reserved MBZ */
+       __le32 RemainingBytes;
+       __le16 WriteChannelInfoOffset; /* Reserved MBZ */
+       __le16 WriteChannelInfoLength; /* Reserved MBZ */
+       __le32 Flags;
+       __u8   Buffer[1];
+} __packed;
+
+struct smb2_write_rsp {
+       struct smb2_hdr hdr;
+       __le16 StructureSize; /* Must be 17 */
+       __u8   DataOffset;
+       __u8   Reserved;
+       __le32 DataLength;
+       __le32 DataRemaining;
+       __u32  Reserved2;
+       __u8   Buffer[1];
+} __packed;
+
 struct smb2_echo_req {
        struct smb2_hdr hdr;
        __le16 StructureSize;   /* Must be 4 */
index f442e469997402467b04198f49f889e8795dd9f8..ec983be0ba318906dbfbd3d60b5d542fc15c3dbd 100644 (file)
@@ -98,6 +98,7 @@ extern int SMB2_get_srv_num(const unsigned int xid, struct cifs_tcon *tcon,
                            u64 persistent_fid, u64 volatile_fid,
                            __le64 *uniqueid);
 extern int smb2_async_readv(struct cifs_readdata *rdata);
+extern int smb2_async_writev(struct cifs_writedata *wdata);
 extern int SMB2_echo(struct TCP_Server_Info *server);
 
 #endif                 /* _SMB2PROTO_H */