CIFS: Add SMB2 support for cifs_iovec_write
[linux-3.10.git] / fs / cifs / smb2pdu.c
index e186718..cb6acc7 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;
+}