From: Pavel Shilovsky <piastryyy@xxxxxxxxx> Signed-off-by: Pavel Shilovsky <piastryyy@xxxxxxxxx> --- 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html