diff options
author | Jeff Layton <jlayton@redhat.com> | 2011-05-19 16:22:56 -0400 |
---|---|---|
committer | Steve French <sfrench@us.ibm.com> | 2011-05-25 16:38:33 -0400 |
commit | c28c89fc43e3f81436efc4748837534d4d46f90c (patch) | |
tree | d7eec9d8aabf41d38dcecab0de6f91b6a8a037c6 | |
parent | f7910cbd9fa319ee4501074f1f3b5ce23c4b1518 (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>
-rw-r--r-- | fs/cifs/cifsproto.h | 18 | ||||
-rw-r--r-- | fs/cifs/cifssmb.c | 236 |
2 files changed, 254 insertions, 0 deletions
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h index fdc0dc2c083..8aea8850ffe 100644 --- a/fs/cifs/cifsproto.h +++ b/fs/cifs/cifsproto.h | |||
@@ -442,4 +442,22 @@ extern int mdfour(unsigned char *, unsigned char *, int); | |||
442 | extern int E_md4hash(const unsigned char *passwd, unsigned char *p16); | 442 | extern int E_md4hash(const unsigned char *passwd, unsigned char *p16); |
443 | extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, | 443 | extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, |
444 | unsigned char *p24); | 444 | unsigned char *p24); |
445 | |||
446 | /* asynchronous write support */ | ||
447 | struct 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 | |||
459 | int cifs_async_writev(struct cifs_writedata *wdata); | ||
460 | struct cifs_writedata *cifs_writedata_alloc(unsigned int nr_pages); | ||
461 | void 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 e0d24135b3c..136df013b0a 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 | ||
1608 | void | ||
1609 | cifs_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 | */ | ||
1624 | static void | ||
1625 | cifs_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 | |||
1649 | static void | ||
1650 | cifs_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 | |||
1678 | struct cifs_writedata * | ||
1679 | cifs_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 | */ | ||
1703 | static void | ||
1704 | cifs_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 */ | ||
1750 | int | ||
1751 | cifs_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 | |||
1837 | async_writev_out: | ||
1838 | cifs_small_buf_release(smb); | ||
1839 | kfree(iov); | ||
1840 | return rc; | ||
1841 | } | ||
1842 | |||
1607 | int | 1843 | int |
1608 | CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon, | 1844 | CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon, |
1609 | const int netfid, const unsigned int count, | 1845 | const int netfid, const unsigned int count, |