aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorJeff Layton <jlayton@redhat.com>2011-05-19 16:22:56 -0400
committerSteve French <sfrench@us.ibm.com>2011-05-25 16:38:33 -0400
commitc28c89fc43e3f81436efc4748837534d4d46f90c (patch)
treed7eec9d8aabf41d38dcecab0de6f91b6a8a037c6 /fs
parentf7910cbd9fa319ee4501074f1f3b5ce23c4b1518 (diff)
cifs: add cifs_async_writev
Add the ability for CIFS to do an asynchronous write. The kernel will set the frame up as it would for a "normal" SMBWrite2 request, and use cifs_call_async to send it. The mid callback will then be configured to handle the result. Reviewed-by: Pavel Shilovsky <piastry@etersoft.ru> Signed-off-by: Jeff Layton <jlayton@redhat.com> Signed-off-by: Steve French <sfrench@us.ibm.com>
Diffstat (limited to 'fs')
-rw-r--r--fs/cifs/cifsproto.h18
-rw-r--r--fs/cifs/cifssmb.c236
2 files changed, 254 insertions, 0 deletions
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index fdc0dc2c083c..8aea8850ffee 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -442,4 +442,22 @@ extern int mdfour(unsigned char *, unsigned char *, int);
442extern int E_md4hash(const unsigned char *passwd, unsigned char *p16); 442extern int E_md4hash(const unsigned char *passwd, unsigned char *p16);
443extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, 443extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8,
444 unsigned char *p24); 444 unsigned char *p24);
445
446/* asynchronous write support */
447struct cifs_writedata {
448 struct kref refcount;
449 enum writeback_sync_modes sync_mode;
450 struct work_struct work;
451 struct cifsFileInfo *cfile;
452 __u64 offset;
453 unsigned int bytes;
454 int result;
455 unsigned int nr_pages;
456 struct page *pages[1];
457};
458
459int cifs_async_writev(struct cifs_writedata *wdata);
460struct cifs_writedata *cifs_writedata_alloc(unsigned int nr_pages);
461void cifs_writedata_release(struct kref *refcount);
462
445#endif /* _CIFSPROTO_H */ 463#endif /* _CIFSPROTO_H */
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index e0d24135b3c6..136df013b0aa 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -32,6 +32,7 @@
32#include <linux/vfs.h> 32#include <linux/vfs.h>
33#include <linux/slab.h> 33#include <linux/slab.h>
34#include <linux/posix_acl_xattr.h> 34#include <linux/posix_acl_xattr.h>
35#include <linux/pagemap.h>
35#include <asm/uaccess.h> 36#include <asm/uaccess.h>
36#include "cifspdu.h" 37#include "cifspdu.h"
37#include "cifsglob.h" 38#include "cifsglob.h"
@@ -1604,6 +1605,241 @@ CIFSSMBWrite(const int xid, struct cifsTconInfo *tcon,
1604 return rc; 1605 return rc;
1605} 1606}
1606 1607
1608void
1609cifs_writedata_release(struct kref *refcount)
1610{
1611 struct cifs_writedata *wdata = container_of(refcount,
1612 struct cifs_writedata, refcount);
1613
1614 if (wdata->cfile)
1615 cifsFileInfo_put(wdata->cfile);
1616
1617 kfree(wdata);
1618}
1619
1620/*
1621 * Write failed with a retryable error. Resend the write request. It's also
1622 * possible that the page was redirtied so re-clean the page.
1623 */
1624static void
1625cifs_writev_requeue(struct cifs_writedata *wdata)
1626{
1627 int i, rc;
1628 struct inode *inode = wdata->cfile->dentry->d_inode;
1629
1630 for (i = 0; i < wdata->nr_pages; i++) {
1631 lock_page(wdata->pages[i]);
1632 clear_page_dirty_for_io(wdata->pages[i]);
1633 }
1634
1635 do {
1636 rc = cifs_async_writev(wdata);
1637 } while (rc == -EAGAIN);
1638
1639 for (i = 0; i < wdata->nr_pages; i++) {
1640 if (rc != 0)
1641 SetPageError(wdata->pages[i]);
1642 unlock_page(wdata->pages[i]);
1643 }
1644
1645 mapping_set_error(inode->i_mapping, rc);
1646 kref_put(&wdata->refcount, cifs_writedata_release);
1647}
1648
1649static void
1650cifs_writev_complete(struct work_struct *work)
1651{
1652 struct cifs_writedata *wdata = container_of(work,
1653 struct cifs_writedata, work);
1654 struct inode *inode = wdata->cfile->dentry->d_inode;
1655 int i = 0;
1656
1657 if (wdata->result == 0) {
1658 cifs_update_eof(CIFS_I(inode), wdata->offset, wdata->bytes);
1659 cifs_stats_bytes_written(tlink_tcon(wdata->cfile->tlink),
1660 wdata->bytes);
1661 } else if (wdata->sync_mode == WB_SYNC_ALL && wdata->result == -EAGAIN)
1662 return cifs_writev_requeue(wdata);
1663
1664 for (i = 0; i < wdata->nr_pages; i++) {
1665 struct page *page = wdata->pages[i];
1666 if (wdata->result == -EAGAIN)
1667 __set_page_dirty_nobuffers(page);
1668 else if (wdata->result < 0)
1669 SetPageError(page);
1670 end_page_writeback(page);
1671 page_cache_release(page);
1672 }
1673 if (wdata->result != -EAGAIN)
1674 mapping_set_error(inode->i_mapping, wdata->result);
1675 kref_put(&wdata->refcount, cifs_writedata_release);
1676}
1677
1678struct cifs_writedata *
1679cifs_writedata_alloc(unsigned int nr_pages)
1680{
1681 struct cifs_writedata *wdata;
1682
1683 /* this would overflow */
1684 if (nr_pages == 0) {
1685 cERROR(1, "%s: called with nr_pages == 0!", __func__);
1686 return NULL;
1687 }
1688
1689 /* writedata + number of page pointers */
1690 wdata = kzalloc(sizeof(*wdata) +
1691 sizeof(struct page *) * (nr_pages - 1), GFP_NOFS);
1692 if (wdata != NULL) {
1693 INIT_WORK(&wdata->work, cifs_writev_complete);
1694 kref_init(&wdata->refcount);
1695 }
1696 return wdata;
1697}
1698
1699/*
1700 * Check the midState and signature on received buffer (if any), and queue the
1701 * workqueue completion task.
1702 */
1703static void
1704cifs_writev_callback(struct mid_q_entry *mid)
1705{
1706 struct cifs_writedata *wdata = mid->callback_data;
1707 struct cifsTconInfo *tcon = tlink_tcon(wdata->cfile->tlink);
1708 unsigned int written;
1709 WRITE_RSP *smb = (WRITE_RSP *)mid->resp_buf;
1710
1711 switch (mid->midState) {
1712 case MID_RESPONSE_RECEIVED:
1713 wdata->result = cifs_check_receive(mid, tcon->ses->server, 0);
1714 if (wdata->result != 0)
1715 break;
1716
1717 written = le16_to_cpu(smb->CountHigh);
1718 written <<= 16;
1719 written += le16_to_cpu(smb->Count);
1720 /*
1721 * Mask off high 16 bits when bytes written as returned
1722 * by the server is greater than bytes requested by the
1723 * client. OS/2 servers are known to set incorrect
1724 * CountHigh values.
1725 */
1726 if (written > wdata->bytes)
1727 written &= 0xFFFF;
1728
1729 if (written < wdata->bytes)
1730 wdata->result = -ENOSPC;
1731 else
1732 wdata->bytes = written;
1733 break;
1734 case MID_REQUEST_SUBMITTED:
1735 case MID_RETRY_NEEDED:
1736 wdata->result = -EAGAIN;
1737 break;
1738 default:
1739 wdata->result = -EIO;
1740 break;
1741 }
1742
1743 queue_work(system_nrt_wq, &wdata->work);
1744 DeleteMidQEntry(mid);
1745 atomic_dec(&tcon->ses->server->inFlight);
1746 wake_up(&tcon->ses->server->request_q);
1747}
1748
1749/* cifs_async_writev - send an async write, and set up mid to handle result */
1750int
1751cifs_async_writev(struct cifs_writedata *wdata)
1752{
1753 int i, rc = -EACCES;
1754 WRITE_REQ *smb = NULL;
1755 int wct;
1756 struct cifsTconInfo *tcon = tlink_tcon(wdata->cfile->tlink);
1757 struct inode *inode = wdata->cfile->dentry->d_inode;
1758 struct kvec *iov = NULL;
1759
1760 if (tcon->ses->capabilities & CAP_LARGE_FILES) {
1761 wct = 14;
1762 } else {
1763 wct = 12;
1764 if (wdata->offset >> 32 > 0) {
1765 /* can not handle big offset for old srv */
1766 return -EIO;
1767 }
1768 }
1769
1770 rc = small_smb_init(SMB_COM_WRITE_ANDX, wct, tcon, (void **)&smb);
1771 if (rc)
1772 goto async_writev_out;
1773
1774 /* 1 iov per page + 1 for header */
1775 iov = kzalloc((wdata->nr_pages + 1) * sizeof(*iov), GFP_NOFS);
1776 if (iov == NULL) {
1777 rc = -ENOMEM;
1778 goto async_writev_out;
1779 }
1780
1781 smb->AndXCommand = 0xFF; /* none */
1782 smb->Fid = wdata->cfile->netfid;
1783 smb->OffsetLow = cpu_to_le32(wdata->offset & 0xFFFFFFFF);
1784 if (wct == 14)
1785 smb->OffsetHigh = cpu_to_le32(wdata->offset >> 32);
1786 smb->Reserved = 0xFFFFFFFF;
1787 smb->WriteMode = 0;
1788 smb->Remaining = 0;
1789
1790 smb->DataOffset =
1791 cpu_to_le16(offsetof(struct smb_com_write_req, Data) - 4);
1792
1793 /* 4 for RFC1001 length + 1 for BCC */
1794 iov[0].iov_len = be32_to_cpu(smb->hdr.smb_buf_length) + 4 + 1;
1795 iov[0].iov_base = smb;
1796
1797 /* marshal up the pages into iov array */
1798 wdata->bytes = 0;
1799 for (i = 0; i < wdata->nr_pages; i++) {
1800 iov[i + 1].iov_len = min(inode->i_size -
1801 page_offset(wdata->pages[i]),
1802 (loff_t)PAGE_CACHE_SIZE);
1803 iov[i + 1].iov_base = kmap(wdata->pages[i]);
1804 wdata->bytes += iov[i + 1].iov_len;
1805 }
1806
1807 cFYI(1, "async write at %llu %u bytes", wdata->offset, wdata->bytes);
1808
1809 smb->DataLengthLow = cpu_to_le16(wdata->bytes & 0xFFFF);
1810 smb->DataLengthHigh = cpu_to_le16(wdata->bytes >> 16);
1811
1812 if (wct == 14) {
1813 inc_rfc1001_len(&smb->hdr, wdata->bytes + 1);
1814 put_bcc(wdata->bytes + 1, &smb->hdr);
1815 } else {
1816 /* wct == 12 */
1817 struct smb_com_writex_req *smbw =
1818 (struct smb_com_writex_req *)smb;
1819 inc_rfc1001_len(&smbw->hdr, wdata->bytes + 5);
1820 put_bcc(wdata->bytes + 5, &smbw->hdr);
1821 iov[0].iov_len += 4; /* pad bigger by four bytes */
1822 }
1823
1824 kref_get(&wdata->refcount);
1825 rc = cifs_call_async(tcon->ses->server, iov, wdata->nr_pages + 1,
1826 cifs_writev_callback, wdata, false);
1827
1828 if (rc == 0)
1829 cifs_stats_inc(&tcon->num_writes);
1830 else
1831 kref_put(&wdata->refcount, cifs_writedata_release);
1832
1833 /* send is done, unmap pages */
1834 for (i = 0; i < wdata->nr_pages; i++)
1835 kunmap(wdata->pages[i]);
1836
1837async_writev_out:
1838 cifs_small_buf_release(smb);
1839 kfree(iov);
1840 return rc;
1841}
1842
1607int 1843int
1608CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon, 1844CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon,
1609 const int netfid, const unsigned int count, 1845 const int netfid, const unsigned int count,