From: Pavel Shilovsky <[email protected]>

Signed-off-by: Pavel Shilovsky <[email protected]>
---
 fs/cifs/cifsglob.h      |   17 +++++++
 fs/cifs/cifsproto.h     |   17 ++-----
 fs/cifs/cifssmb.c       |    5 +-
 fs/cifs/file.c          |   14 ++++-
 fs/cifs/smb2file.c      |   10 +++-
 fs/cifs/smb2pdu.c       |  122 +++++++++++++++++++++++++++++++++++++++++++++++
 fs/cifs/smb2proto.h     |   12 +++++
 fs/cifs/smb2transport.c |   67 +++++++++++++++++++++++++-
 8 files changed, 242 insertions(+), 22 deletions(-)

diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 465e87c..54125cc 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -600,6 +600,23 @@ typedef int (reopen_callback_t)(struct cifsFileInfo 
*cifs_file, int xid,
                                 struct cifs_tcon *tcon, const char *full_path,
                                 __u32 *oplock);
 
+struct cifs_writedata;
+typedef int (awritev_callback_t)(struct cifs_writedata *);
+
+/* asynchronous write support */
+struct cifs_writedata {
+       struct kref                     refcount;
+       enum writeback_sync_modes       sync_mode;
+       struct work_struct              work;
+       awritev_callback_t              *requeue_writev;
+       struct cifsFileInfo             *cfile;
+       __u64                           offset;
+       unsigned int                    bytes;
+       int                             result;
+       unsigned int                    nr_pages;
+       struct page                     *pages[1];
+};
+
 struct cifs_io_parms {
        __u16 netfid;
        __u64 persist_fid;
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index eee59f7..6cf4313 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -207,10 +207,14 @@ extern int cifs_readpages(struct file *file, struct 
address_space *mapping,
                          struct list_head *page_list, unsigned num_pages);
 extern int cifs_writepages(struct address_space *mapping,
                           struct writeback_control *wbc);
+extern int cifs_writepages_generic(struct address_space *mapping,
+                                  struct writeback_control *wbc,
+                                  awritev_callback_t *async_writev);
 extern int cifs_writepage(struct page *page, struct writeback_control *wbc);
 void cifs_proc_init(void);
 void cifs_proc_clean(void);
 
+extern void inc_rfc1001_len(void *pSMB, int count);
 extern int cifs_negotiate_protocol(unsigned int xid,
                                  struct cifs_ses *ses);
 extern int cifs_setup_session(unsigned int xid, struct cifs_ses *ses,
@@ -509,19 +513,6 @@ struct cifs_readdata *cifs_readdata_alloc(unsigned int 
nr_pages);
 void cifs_readdata_free(struct cifs_readdata *rdata);
 int cifs_async_readv(struct cifs_readdata *rdata);
 
-/* asynchronous write support */
-struct cifs_writedata {
-       struct kref                     refcount;
-       enum writeback_sync_modes       sync_mode;
-       struct work_struct              work;
-       struct cifsFileInfo             *cfile;
-       __u64                           offset;
-       unsigned int                    bytes;
-       int                             result;
-       unsigned int                    nr_pages;
-       struct page                     *pages[1];
-};
-
 int cifs_async_writev(struct cifs_writedata *wdata);
 struct cifs_writedata *cifs_writedata_alloc(unsigned int nr_pages);
 void cifs_writedata_release(struct kref *refcount);
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index bc13c8b..0c73678 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -365,7 +365,7 @@ vt2_err:
        return -EINVAL;
 }
 
-static inline void inc_rfc1001_len(void *pSMB, int count)
+inline void inc_rfc1001_len(void *pSMB, int count)
 {
        struct smb_hdr *hdr = (struct smb_hdr *)pSMB;
 
@@ -2001,7 +2001,7 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
        }
 
        do {
-               rc = cifs_async_writev(wdata);
+               rc = wdata->requeue_writev(wdata);
        } while (rc == -EAGAIN);
 
        for (i = 0; i < wdata->nr_pages; i++) {
@@ -2108,6 +2108,7 @@ cifs_writev_callback(struct mid_q_entry *mid)
                break;
        }
 
+       wdata->requeue_writev = cifs_async_writev;
        queue_work(system_nrt_wq, &wdata->work);
        DeleteMidQEntry(mid);
        atomic_dec(&tcon->ses->server->inFlight);
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index 4e9616d..91a3edd 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
@@ -1722,8 +1722,10 @@ static int cifs_partialpagewrite(struct page *page, 
unsigned from, unsigned to)
        return rc;
 }
 
-int cifs_writepages(struct address_space *mapping,
-                          struct writeback_control *wbc)
+int
+cifs_writepages_generic(struct address_space *mapping,
+                       struct writeback_control *wbc,
+                       awritev_callback_t *async_writev)
 {
        struct cifs_sb_info *cifs_sb = CIFS_SB(mapping->host->i_sb);
        bool done = false, scanned = false, range_whole = false;
@@ -1876,7 +1878,7 @@ retry:
                                rc = -EBADF;
                                break;
                        }
-                       rc = cifs_async_writev(wdata);
+                       rc = async_writev(wdata);
                } while (wbc->sync_mode == WB_SYNC_ALL && rc == -EAGAIN);
 
                for (i = 0; i < nr_pages; ++i)
@@ -1921,6 +1923,12 @@ retry:
        return rc;
 }
 
+int
+cifs_writepages(struct address_space *mapping, struct writeback_control *wbc)
+{
+       return cifs_writepages_generic(mapping, wbc, cifs_async_writev);
+}
+
 static int
 cifs_writepage_locked(struct page *page, struct writeback_control *wbc)
 {
diff --git a/fs/cifs/smb2file.c b/fs/cifs/smb2file.c
index 40f05e4..33c0928 100644
--- a/fs/cifs/smb2file.c
+++ b/fs/cifs/smb2file.c
@@ -152,7 +152,7 @@ const struct address_space_operations smb2_addr_ops = {
        .readpage = cifs_readpage,
        /*.readpages = cifs_readpages,*/
        .writepage = cifs_writepage,
-       /*.writepages = cifs_writepages,*/
+       .writepages = smb2_writepages,
        .write_begin = cifs_write_begin,
        .write_end = cifs_write_end,
        .set_page_dirty = __set_page_dirty_nobuffers,
@@ -169,7 +169,7 @@ const struct address_space_operations smb2_addr_ops = {
 const struct address_space_operations smb2_addr_ops_smallbuf = {
        .readpage = cifs_readpage,
        .writepage = cifs_writepage,
-       /*.writepages = cifs_writepages,*/
+       .writepages = smb2_writepages,
        .write_begin = cifs_write_begin,
        .write_end = cifs_write_end,
        .set_page_dirty = __set_page_dirty_nobuffers,
@@ -459,3 +459,9 @@ smb2_write_cb(int xid, struct cifsFileInfo *cfile, struct 
cifs_io_parms *parms,
        return SMB2_write(xid, parms, written, iov, nr_segs,
                          remaining_bytes, timeout);
 }
+
+int
+smb2_writepages(struct address_space *mapping, struct writeback_control *wbc)
+{
+       return cifs_writepages_generic(mapping, wbc, smb2_async_writev);
+}
diff --git a/fs/cifs/smb2pdu.c b/fs/cifs/smb2pdu.c
index 8d13b34..cc4ed73 100644
--- a/fs/cifs/smb2pdu.c
+++ b/fs/cifs/smb2pdu.c
@@ -30,6 +30,7 @@
 #include <linux/kernel.h>
 #include <linux/vfs.h>
 #include <linux/uaccess.h>
+#include <linux/pagemap.h>
 #include <linux/xattr.h>
 #include "smb2pdu.h"
 #include "cifsglob.h"
@@ -2432,6 +2433,127 @@ int SMB2_write(const int xid, struct cifs_io_parms 
*io_parms,
        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 write_rsp *smb2 = (struct write_rsp *)mid->resp_buf;
+       unsigned int credits_received = 1;
+
+       switch (mid->mid_state) {
+       case MID_RESPONSE_RECEIVED:
+               credits_received = le16_to_cpu(smb2->hdr.CreditRequest);
+               wdata->result = smb2_check_receive(mid, tcon->ses->server,
+                                                  get_rfc1002_length(smb2), 0);
+               if (wdata->result != 0)
+                       break;
+
+               written = le32_to_cpu(smb2->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;
+       }
+
+       wdata->requeue_writev = smb2_async_writev;
+       queue_work(system_nrt_wq, &wdata->work);
+       smb2_mid_entry_free(mid);
+       atomic_add(credits_received, &tcon->ses->server->credits);
+       wake_up(&tcon->ses->server->request_q);
+}
+
+/* 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 write_req *smb2 = NULL;
+       struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink);
+       struct inode *inode = wdata->cfile->dentry->d_inode;
+       struct kvec *iov = NULL;
+
+       rc = small_smb2_init(SMB2_WRITE, tcon, (void **) &smb2);
+       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;
+       }
+
+       smb2->hdr.ProcessId = cpu_to_le32(wdata->cfile->pid);
+
+       smb2->PersistentFileId = wdata->cfile->persist_fid;
+       smb2->VolatileFileId = wdata->cfile->volatile_fid;
+       smb2->WriteChannelInfoOffset = 0;
+       smb2->WriteChannelInfoLength = 0;
+       smb2->Channel = 0;
+       smb2->Offset = cpu_to_le64(wdata->offset);
+       smb2->DataOffset = cpu_to_le16(offsetof(struct write_req, Buffer) - 4);
+       smb2->RemainingBytes = 0;
+
+       /* 4 for RFC1001 length + 1 for BCC */
+       iov[0].iov_len = be32_to_cpu(smb2->hdr.smb2_buf_length) + 4 - 1;
+       iov[0].iov_base = smb2;
+
+       /* marshal up the pages into iov array */
+       wdata->bytes = 0;
+       for (i = 0; i < wdata->nr_pages; i++) {
+               iov[i + 1].iov_len = min(inode->i_size -
+                                     page_offset(wdata->pages[i]),
+                                       (loff_t)PAGE_CACHE_SIZE);
+               iov[i + 1].iov_base = kmap(wdata->pages[i]);
+               wdata->bytes += iov[i + 1].iov_len;
+       }
+
+       cFYI(1, "async write at %llu %u bytes", wdata->offset, wdata->bytes);
+
+       smb2->Length = cpu_to_le32(wdata->bytes);
+
+       inc_rfc1001_len(&smb2->hdr, wdata->bytes - 1);
+
+       kref_get(&wdata->refcount);
+       rc = smb2_call_async(tcon->ses->server, iov, wdata->nr_pages + 1,
+                            NULL, smb2_writev_callback, wdata, false);
+
+       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(smb2);
+       kfree(iov);
+       return rc;
+}
+
 int SMB2_lock(const int xid, struct cifs_tcon *tcon,
        const u64 persistent_fid, const u64 volatile_fid,
        u64 length, u64 offset, u32 lockFlags, int wait)
diff --git a/fs/cifs/smb2proto.h b/fs/cifs/smb2proto.h
index 7798626..d017843 100644
--- a/fs/cifs/smb2proto.h
+++ b/fs/cifs/smb2proto.h
@@ -22,6 +22,7 @@
 #define _SMB2PROTO_H
 #include <linux/nls.h>
 #include <linux/key-type.h>
+#include "cifsproto.h"
 
 struct statfs;
 
@@ -66,12 +67,20 @@ extern int map_smb2_to_linux_error(struct smb2_hdr *smb2, 
int logErr);
 extern __le16 *cifs_convert_path_to_ucs(const char *from,
                                        struct nls_table *local_nls);
 
+extern int smb2_check_receive(struct mid_q_entry *mid,
+                             struct TCP_Server_Info *server,
+                             unsigned int receive_len, bool log_error);
+extern void smb2_mid_entry_free(struct mid_q_entry *mid_entry);
 extern int smb2_send(struct TCP_Server_Info *, struct smb2_hdr *,
                     unsigned int /* length */);
 extern int smb2_sendrcv2(const unsigned int /* xid */ , struct cifs_ses *,
                        struct kvec *, int /* nvec to send */,
                        int * /* type of buf returned */,
                        int * /* smb2 network status code */, const int flags);
+extern int smb2_call_async(struct TCP_Server_Info *server, struct kvec *iov,
+                          unsigned int nvec, mid_receive_t *receive,
+                          mid_callback_t *callback, void *cbdata,
+                          bool ignore_pend);
 extern int smb2_send_complex(const unsigned int, struct cifs_ses *,
                             struct kvec *, int /* nvec to send */,
                             int * /* type of buf returned */,
@@ -133,6 +142,8 @@ extern ssize_t smb2_user_readv(struct kiocb *iocb, const 
struct iovec *iov,
                               unsigned long nr_segs, loff_t pos);
 extern ssize_t smb2_user_writev(struct kiocb *iocb, const struct iovec *iov,
                                unsigned long nr_segs, loff_t pos);
+extern int smb2_writepages(struct address_space *mapping,
+                          struct writeback_control *wbc);
 
 extern int smb2_mkdir(struct inode *inode, struct dentry *direntry, int mode);
 extern int smb2_rmdir(struct inode *inode, struct dentry *direntry);
@@ -200,6 +211,7 @@ extern int SMB2_read(const int xid, struct cifs_io_parms 
*io_parms,
 extern int SMB2_write(const int xid, struct cifs_io_parms *io_parms,
                      unsigned int *nbytes, struct kvec *iov, int n_vec,
                      const unsigned int remaining_bytes, int wtimeout);
+extern int smb2_async_writev(struct cifs_writedata *wdata);
 extern int SMB2_write_complex(const int xid, struct cifs_tcon *tcon,
        const u64 persistent_fid, const u64 volatile_fid,
        const unsigned int count, const __u64 lseek,
diff --git a/fs/cifs/smb2transport.c b/fs/cifs/smb2transport.c
index 68cbba9..adb701f 100644
--- a/fs/cifs/smb2transport.c
+++ b/fs/cifs/smb2transport.c
@@ -168,7 +168,7 @@ static int get_smb2_mid(struct cifs_ses *ses, struct 
smb2_hdr *in_buf,
        return 0;
 }
 
-static void
+void
 smb2_mid_entry_free(struct mid_q_entry *mid_entry)
 {
 #ifdef CONFIG_CIFS_STATS2
@@ -273,7 +273,7 @@ free_smb2_mid(struct mid_q_entry *mid)
        smb2_mid_entry_free(mid);
 }
 
-static int
+int
 smb2_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server,
                   unsigned int receive_len, bool log_error)
 {
@@ -444,4 +444,67 @@ out:
        wake_up(&ses->server->request_q);
        return rc;
 }
+
+/*
+ * Send a SMB2 request and set the callback function in the mid to handle
+ * the result. Caller is responsible for dealing with timeouts.
+ */
+int
+smb2_call_async(struct TCP_Server_Info *server, struct kvec *iov,
+               unsigned int nvec, mid_receive_t *receive,
+               mid_callback_t *callback, void *cbdata, bool ignore_pend)
+{
+       int rc;
+       struct mid_q_entry *mid;
+       struct smb2_hdr *hdr = (struct smb2_hdr *)iov[0].iov_base;
+
+       rc = wait_for_free_request(server, ignore_pend ? CIFS_ASYNC_OP : 0);
+       if (rc)
+               return rc;
+
+       mutex_lock(&server->srv_mutex);
+
+       smb2_seq_num_into_buf(server, iov);
+
+       mid = smb2_mid_entry_alloc(hdr, server);
+       if (mid == NULL) {
+               mutex_unlock(&server->srv_mutex);
+               atomic_inc(&server->credits);
+               wake_up(&server->request_q);
+               return -ENOMEM;
+       }
+
+       /* put it on the pending_mid_q */
+       spin_lock(&GlobalMid_Lock);
+       list_add_tail(&mid->qhead, &server->pending_mid_q);
+       spin_unlock(&GlobalMid_Lock);
+
+/*     rc = cifs_sign_smb2(iov, nvec, server, &mid->sequence_number);
+       if (rc) {
+               mutex_unlock(&server->srv_mutex);
+               goto out_err;
+       }
+*/
+
+       mid->receive = receive;
+       mid->callback = callback;
+       mid->callback_data = cbdata;
+       mid->mid_state = MID_REQUEST_SUBMITTED;
+
+       cifs_in_send_inc(server);
+       rc = smb_sendv(server, iov, nvec);
+       cifs_in_send_dec(server);
+       cifs_save_when_sent(mid);
+       mutex_unlock(&server->srv_mutex);
+
+       if (rc)
+               goto out_err;
+
+       return rc;
+out_err:
+       free_smb2_mid(mid);
+       atomic_inc(&server->credits);
+       wake_up(&server->request_q);
+       return rc;
+}
 /* BB add missing functions here */
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-cifs" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to