aboutsummaryrefslogtreecommitdiffstats
path: root/module/zcommon
diff options
context:
space:
mode:
authorChunwei Chen <[email protected]>2015-07-30 22:24:36 +0800
committerBrian Behlendorf <[email protected]>2015-08-24 10:17:06 -0700
commit5475aada9474464f973788c1b2fc6216486fb303 (patch)
treef4b02aff6e841b47cad4894b40c7a1092ad0be3b /module/zcommon
parent17888ae30d6111f1fe25087a256724ee9b1a0a84 (diff)
Linux 4.1 compat: loop device on ZFS
Starting from Linux 4.1 allows iov_iter with bio_vec to be passed into iter_read/iter_write. Notably, the loop device will pass bio_vec to backend filesystem. However, current ZFS code assumes iovec without any check, so it will always crash when using loop device. With the restructured uio_t, we can safely pass bio_vec in uio_t with UIO_BVEC set. The uio* functions are modified to handle bio_vec case separately. The const uio_iov causes some warning in xuio related stuff, so explicit convert them to non const. Signed-off-by: Chunwei Chen <[email protected]> Signed-off-by: Richard Yao <[email protected]> Signed-off-by: Brian Behlendorf <[email protected]> Closes #3511 Closes #3640
Diffstat (limited to 'module/zcommon')
-rw-r--r--module/zcommon/zfs_uio.c205
1 files changed, 108 insertions, 97 deletions
diff --git a/module/zcommon/zfs_uio.c b/module/zcommon/zfs_uio.c
index 90376f2ac..a5634fca0 100644
--- a/module/zcommon/zfs_uio.c
+++ b/module/zcommon/zfs_uio.c
@@ -35,6 +35,9 @@
* software developed by the University of California, Berkeley, and its
* contributors.
*/
+/*
+ * Copyright (c) 2015 by Chunwei Chen. All rights reserved.
+ */
/*
* The uio support from OpenSolaris has been added as a short term
@@ -46,6 +49,7 @@
#include <sys/types.h>
#include <sys/uio_impl.h>
+#include <linux/kmap_compat.h>
/*
* Move "n" bytes at byte address "p"; "rw" indicates the direction
@@ -53,20 +57,17 @@
* update to reflect the data which was moved. Returns 0 on success or
* a non-zero errno on failure.
*/
-int
-uiomove(void *p, size_t n, enum uio_rw rw, struct uio *uio)
+static int
+uiomove_iov(void *p, size_t n, enum uio_rw rw, struct uio *uio)
{
- struct iovec *iov;
+ const struct iovec *iov = uio->uio_iov;
+ size_t skip = uio->uio_skip;
ulong_t cnt;
+ ASSERT3U(skip, <, iov->iov_len);
+
while (n && uio->uio_resid) {
- iov = uio->uio_iov;
- cnt = MIN(iov->iov_len, n);
- if (cnt == 0l) {
- uio->uio_iov++;
- uio->uio_iovcnt--;
- continue;
- }
+ cnt = MIN(iov->iov_len - skip, n);
switch (uio->uio_segflg) {
case UIO_USERSPACE:
case UIO_USERISPACE:
@@ -75,22 +76,29 @@ uiomove(void *p, size_t n, enum uio_rw rw, struct uio *uio)
* iov->iov_base = user data pointer
*/
if (rw == UIO_READ) {
- if (copy_to_user(iov->iov_base, p, cnt))
+ if (copy_to_user(iov->iov_base+skip, p, cnt))
return (EFAULT);
} else {
- if (copy_from_user(p, iov->iov_base, cnt))
+ if (copy_from_user(p, iov->iov_base+skip, cnt))
return (EFAULT);
}
break;
case UIO_SYSSPACE:
if (rw == UIO_READ)
- bcopy(p, iov->iov_base, cnt);
+ bcopy(p, iov->iov_base + skip, cnt);
else
- bcopy(iov->iov_base, p, cnt);
+ bcopy(iov->iov_base + skip, p, cnt);
break;
+ default:
+ ASSERT(0);
+ }
+ skip += cnt;
+ if (skip == iov->iov_len) {
+ skip = 0;
+ uio->uio_iov = (++iov);
+ uio->uio_iovcnt--;
}
- iov->iov_base += cnt;
- iov->iov_len -= cnt;
+ uio->uio_skip = skip;
uio->uio_resid -= cnt;
uio->uio_loffset += cnt;
p = (caddr_t)p + cnt;
@@ -98,6 +106,50 @@ uiomove(void *p, size_t n, enum uio_rw rw, struct uio *uio)
}
return (0);
}
+
+static int
+uiomove_bvec(void *p, size_t n, enum uio_rw rw, struct uio *uio)
+{
+ const struct bio_vec *bv = uio->uio_bvec;
+ size_t skip = uio->uio_skip;
+ ulong_t cnt;
+
+ ASSERT3U(skip, <, bv->bv_len);
+
+ while (n && uio->uio_resid) {
+ void *paddr;
+ cnt = MIN(bv->bv_len - skip, n);
+
+ paddr = zfs_kmap_atomic(bv->bv_page, KM_USER1);
+ if (rw == UIO_READ)
+ bcopy(p, paddr + bv->bv_offset + skip, cnt);
+ else
+ bcopy(paddr + bv->bv_offset + skip, p, cnt);
+ zfs_kunmap_atomic(paddr, KM_USER1);
+
+ skip += cnt;
+ if (skip == bv->bv_len) {
+ skip = 0;
+ uio->uio_bvec = (++bv);
+ uio->uio_iovcnt--;
+ }
+ uio->uio_skip = skip;
+ uio->uio_resid -= cnt;
+ uio->uio_loffset += cnt;
+ p = (caddr_t)p + cnt;
+ n -= cnt;
+ }
+ return (0);
+}
+
+int
+uiomove(void *p, size_t n, enum uio_rw rw, struct uio *uio)
+{
+ if (uio->uio_segflg != UIO_BVEC)
+ return (uiomove_iov(p, n, rw, uio));
+ else
+ return (uiomove_bvec(p, n, rw, uio));
+}
EXPORT_SYMBOL(uiomove);
#define fuword8(uptr, vptr) get_user((*vptr), (uptr))
@@ -111,39 +163,39 @@ EXPORT_SYMBOL(uiomove);
void
uio_prefaultpages(ssize_t n, struct uio *uio)
{
- struct iovec *iov;
+ const struct iovec *iov;
ulong_t cnt, incr;
caddr_t p;
uint8_t tmp;
int iovcnt;
+ size_t skip = uio->uio_skip;
+
+ /* no need to fault in kernel pages */
+ switch (uio->uio_segflg) {
+ case UIO_SYSSPACE:
+ case UIO_BVEC:
+ return;
+ case UIO_USERSPACE:
+ case UIO_USERISPACE:
+ break;
+ default:
+ ASSERT(0);
+ }
iov = uio->uio_iov;
iovcnt = uio->uio_iovcnt;
+ ASSERT3U(skip, <, iov->iov_len);
while ((n > 0) && (iovcnt > 0)) {
- cnt = MIN(iov->iov_len, n);
- if (cnt == 0) {
- /* empty iov entry */
- iov++;
- iovcnt--;
- continue;
- }
+ cnt = MIN(iov->iov_len - skip, n);
n -= cnt;
/*
* touch each page in this segment.
*/
- p = iov->iov_base;
+ p = iov->iov_base + skip;
while (cnt) {
- switch (uio->uio_segflg) {
- case UIO_USERSPACE:
- case UIO_USERISPACE:
- if (fuword8((uint8_t *) p, &tmp))
- return;
- break;
- case UIO_SYSSPACE:
- bcopy(p, &tmp, 1);
- break;
- }
+ if (fuword8((uint8_t *) p, &tmp))
+ return;
incr = MIN(cnt, PAGESIZE);
p += incr;
cnt -= incr;
@@ -152,18 +204,11 @@ uio_prefaultpages(ssize_t n, struct uio *uio)
* touch the last byte in case it straddles a page.
*/
p--;
- switch (uio->uio_segflg) {
- case UIO_USERSPACE:
- case UIO_USERISPACE:
- if (fuword8((uint8_t *) p, &tmp))
- return;
- break;
- case UIO_SYSSPACE:
- bcopy(p, &tmp, 1);
- break;
- }
+ if (fuword8((uint8_t *) p, &tmp))
+ return;
iov++;
iovcnt--;
+ skip = 0;
}
}
EXPORT_SYMBOL(uio_prefaultpages);
@@ -175,49 +220,13 @@ EXPORT_SYMBOL(uio_prefaultpages);
int
uiocopy(void *p, size_t n, enum uio_rw rw, struct uio *uio, size_t *cbytes)
{
- struct iovec *iov;
- ulong_t cnt;
- int iovcnt;
-
- iovcnt = uio->uio_iovcnt;
- *cbytes = 0;
-
- for (iov = uio->uio_iov; n && iovcnt; iov++, iovcnt--) {
- cnt = MIN(iov->iov_len, n);
- if (cnt == 0)
- continue;
-
- switch (uio->uio_segflg) {
-
- case UIO_USERSPACE:
- case UIO_USERISPACE:
- /*
- * p = kernel data pointer
- * iov->iov_base = user data pointer
- */
- if (rw == UIO_READ) {
- /* UIO_READ = copy data from kernel to user */
- if (copy_to_user(iov->iov_base, p, cnt))
- return (EFAULT);
- } else {
- /* UIO_WRITE = copy data from user to kernel */
- if (copy_from_user(p, iov->iov_base, cnt))
- return (EFAULT);
- }
- break;
+ struct uio uio_copy;
+ int ret;
- case UIO_SYSSPACE:
- if (rw == UIO_READ)
- bcopy(p, iov->iov_base, cnt);
- else
- bcopy(iov->iov_base, p, cnt);
- break;
- }
- p = (caddr_t)p + cnt;
- n -= cnt;
- *cbytes += cnt;
- }
- return (0);
+ bcopy(uio, &uio_copy, sizeof (struct uio));
+ ret = uiomove(p, n, rw, &uio_copy);
+ *cbytes = uio->uio_resid - uio_copy.uio_resid;
+ return (ret);
}
EXPORT_SYMBOL(uiocopy);
@@ -229,21 +238,23 @@ uioskip(uio_t *uiop, size_t n)
{
if (n > uiop->uio_resid)
return;
- while (n != 0) {
- iovec_t *iovp = uiop->uio_iov;
- size_t niovb = MIN(iovp->iov_len, n);
- if (niovb == 0) {
+ uiop->uio_skip += n;
+ if (uiop->uio_segflg != UIO_BVEC) {
+ while (uiop->uio_skip >= uiop->uio_iov->iov_len) {
+ uiop->uio_skip -= uiop->uio_iov->iov_len;
uiop->uio_iov++;
uiop->uio_iovcnt--;
- continue;
}
- iovp->iov_base += niovb;
- uiop->uio_loffset += niovb;
- iovp->iov_len -= niovb;
- uiop->uio_resid -= niovb;
- n -= niovb;
+ } else {
+ while (uiop->uio_skip >= uiop->uio_bvec->bv_len) {
+ uiop->uio_skip -= uiop->uio_bvec->bv_len;
+ uiop->uio_bvec++;
+ uiop->uio_iovcnt--;
+ }
}
+ uiop->uio_loffset += n;
+ uiop->uio_resid -= n;
}
EXPORT_SYMBOL(uioskip);
#endif /* _KERNEL */