Linux 5.10 compat: restore custom uio_prefaultpages()

As part of commit 1c2358c1 the custom uio_prefaultpages() code
was removed in favor of using the generic kernel provided
iov_iter_fault_in_readable() interface.  Unfortunately, it
turns out that up until the Linux 4.7 kernel the function would
only ever fault in the first iovec of the iov_iter.  The result
being uiomove_iov() may hang waiting for the page.

This commit effectively restores the custom uio_prefaultpages()
pages code for Linux 4.9 and earlier kernels which contain the
troublesome version of iov_iter_fault_in_readable().

Signed-off-by: Brian Behlendorf <behlendorf1@llnl.gov>
Closes #11463 
Closes #11484
This commit is contained in:
Brian Behlendorf 2021-01-21 10:43:39 -08:00 committed by GitHub
parent d0cd9a5cc6
commit 83b91ae1a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -209,25 +209,58 @@ zfs_uiomove(void *p, size_t n, zfs_uio_rw_t rw, zfs_uio_t *uio)
}
EXPORT_SYMBOL(zfs_uiomove);
/*
* Fault in the pages of the first n bytes specified by the uio structure.
* 1 byte in each page is touched and the uio struct is unmodified. Any
* error will terminate the process as this is only a best attempt to get
* the pages resident.
*/
int
zfs_uio_prefaultpages(ssize_t n, zfs_uio_t *uio)
{
struct iov_iter iter, *iterp = NULL;
#if defined(HAVE_IOV_ITER_FAULT_IN_READABLE)
if (uio->uio_segflg == UIO_USERSPACE) {
iterp = &iter;
iov_iter_init_compat(iterp, READ, uio->uio_iov,
uio->uio_iovcnt, uio->uio_resid);
if (uio->uio_segflg == UIO_SYSSPACE || uio->uio_segflg == UIO_BVEC) {
/* There's never a need to fault in kernel pages */
return (0);
#if defined(HAVE_VFS_IOV_ITER)
} else if (uio->uio_segflg == UIO_ITER) {
iterp = uio->uio_iter;
/*
* At least a Linux 4.9 kernel, iov_iter_fault_in_readable()
* can be relied on to fault in user pages when referenced.
*/
if (iov_iter_fault_in_readable(uio->uio_iter, n))
return (EFAULT);
#endif
} else {
/* Fault in all user pages */
ASSERT3S(uio->uio_segflg, ==, UIO_USERSPACE);
const struct iovec *iov = uio->uio_iov;
int iovcnt = uio->uio_iovcnt;
size_t skip = uio->uio_skip;
uint8_t tmp;
caddr_t p;
for (; n > 0 && iovcnt > 0; iov++, iovcnt--, skip = 0) {
ulong_t cnt = MIN(iov->iov_len - skip, n);
/* empty iov */
if (cnt == 0)
continue;
n -= cnt;
/* touch each page in this segment. */
p = iov->iov_base + skip;
while (cnt) {
if (get_user(tmp, (uint8_t *)p))
return (EFAULT);
ulong_t incr = MIN(cnt, PAGESIZE);
p += incr;
cnt -= incr;
}
/* touch the last byte in case it straddles a page. */
p--;
if (get_user(tmp, (uint8_t *)p))
return (EFAULT);
}
}
if (iterp && iov_iter_fault_in_readable(iterp, n))
return (EFAULT);
#endif
return (0);
}
EXPORT_SYMBOL(zfs_uio_prefaultpages);