diff options
author | Chuck Lever <chuck.lever@oracle.com> | 2018-07-27 11:19:10 -0400 |
---|---|---|
committer | J. Bruce Fields <bfields@redhat.com> | 2018-08-09 16:11:21 -0400 |
commit | 11b4d66ea3313d9b03a83b80458ddee64990e3c3 (patch) | |
tree | d2558f204fe319fc365c07756bfd48a055a0580d | |
parent | 3fd9557aec919e2db99365ad5a2c00d04ae8893c (diff) |
NFSD: Handle full-length symlinks
I've given up on the idea of zero-copy handling of SYMLINK on the
server side. This is because the Linux VFS symlink API requires the
symlink pathname to be in a NUL-terminated kmalloc'd buffer. The
NUL-termination is going to be problematic (watching out for
landing on a page boundary and dealing with a 4096-byte pathname).
I don't believe that SYMLINK creation is on a performance path or is
requested frequently enough that it will cause noticeable CPU cache
pollution due to data copies.
There will be two places where a transport callout will be necessary
to fill in the rqstp: one will be in the svc_fill_symlink_pathname()
helper that is used by NFSv2 and NFSv3, and the other will be in
nfsd4_decode_create().
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
-rw-r--r-- | fs/nfsd/nfs3proc.c | 2 | ||||
-rw-r--r-- | fs/nfsd/nfsproc.c | 2 | ||||
-rw-r--r-- | include/linux/sunrpc/svc.h | 3 | ||||
-rw-r--r-- | net/sunrpc/svc.c | 67 |
4 files changed, 31 insertions, 43 deletions
diff --git a/fs/nfsd/nfs3proc.c b/fs/nfsd/nfs3proc.c index 8d1c2d1a159b..9eb8086ea841 100644 --- a/fs/nfsd/nfs3proc.c +++ b/fs/nfsd/nfs3proc.c | |||
@@ -290,6 +290,7 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) | |||
290 | RETURN_STATUS(nfserr_nametoolong); | 290 | RETURN_STATUS(nfserr_nametoolong); |
291 | 291 | ||
292 | argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, | 292 | argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, |
293 | page_address(rqstp->rq_arg.pages[0]), | ||
293 | argp->tlen); | 294 | argp->tlen); |
294 | if (IS_ERR(argp->tname)) | 295 | if (IS_ERR(argp->tname)) |
295 | RETURN_STATUS(nfserrno(PTR_ERR(argp->tname))); | 296 | RETURN_STATUS(nfserrno(PTR_ERR(argp->tname))); |
@@ -303,6 +304,7 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) | |||
303 | fh_init(&resp->fh, NFS3_FHSIZE); | 304 | fh_init(&resp->fh, NFS3_FHSIZE); |
304 | nfserr = nfsd_symlink(rqstp, &resp->dirfh, argp->fname, argp->flen, | 305 | nfserr = nfsd_symlink(rqstp, &resp->dirfh, argp->fname, argp->flen, |
305 | argp->tname, &resp->fh); | 306 | argp->tname, &resp->fh); |
307 | kfree(argp->tname); | ||
306 | RETURN_STATUS(nfserr); | 308 | RETURN_STATUS(nfserr); |
307 | } | 309 | } |
308 | 310 | ||
diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index a6faee562b31..0d20fd161225 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c | |||
@@ -454,6 +454,7 @@ nfsd_proc_symlink(struct svc_rqst *rqstp) | |||
454 | return nfserr_nametoolong; | 454 | return nfserr_nametoolong; |
455 | 455 | ||
456 | argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, | 456 | argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, |
457 | page_address(rqstp->rq_arg.pages[0]), | ||
457 | argp->tlen); | 458 | argp->tlen); |
458 | if (IS_ERR(argp->tname)) | 459 | if (IS_ERR(argp->tname)) |
459 | return nfserrno(PTR_ERR(argp->tname)); | 460 | return nfserrno(PTR_ERR(argp->tname)); |
@@ -466,6 +467,7 @@ nfsd_proc_symlink(struct svc_rqst *rqstp) | |||
466 | nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen, | 467 | nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen, |
467 | argp->tname, &newfh); | 468 | argp->tname, &newfh); |
468 | 469 | ||
470 | kfree(argp->tname); | ||
469 | fh_put(&argp->ffh); | 471 | fh_put(&argp->ffh); |
470 | fh_put(&newfh); | 472 | fh_put(&newfh); |
471 | return nfserr; | 473 | return nfserr; |
diff --git a/include/linux/sunrpc/svc.h b/include/linux/sunrpc/svc.h index 43f88bd7b601..73e130a840ce 100644 --- a/include/linux/sunrpc/svc.h +++ b/include/linux/sunrpc/svc.h | |||
@@ -499,7 +499,8 @@ unsigned int svc_fill_write_vector(struct svc_rqst *rqstp, | |||
499 | struct page **pages, | 499 | struct page **pages, |
500 | struct kvec *first, size_t total); | 500 | struct kvec *first, size_t total); |
501 | char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, | 501 | char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, |
502 | struct kvec *first, size_t total); | 502 | struct kvec *first, void *p, |
503 | size_t total); | ||
503 | 504 | ||
504 | #define RPC_MAX_ADDRBUFLEN (63U) | 505 | #define RPC_MAX_ADDRBUFLEN (63U) |
505 | 506 | ||
diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 2194ed507991..d13e05f1a990 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c | |||
@@ -1577,65 +1577,48 @@ EXPORT_SYMBOL_GPL(svc_fill_write_vector); | |||
1577 | * svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call | 1577 | * svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call |
1578 | * @rqstp: svc_rqst to operate on | 1578 | * @rqstp: svc_rqst to operate on |
1579 | * @first: buffer containing first section of pathname | 1579 | * @first: buffer containing first section of pathname |
1580 | * @p: buffer containing remaining section of pathname | ||
1580 | * @total: total length of the pathname argument | 1581 | * @total: total length of the pathname argument |
1581 | * | 1582 | * |
1582 | * Returns pointer to a NUL-terminated string, or an ERR_PTR. The buffer is | 1583 | * The VFS symlink API demands a NUL-terminated pathname in mapped memory. |
1583 | * released automatically when @rqstp is recycled. | 1584 | * Returns pointer to a NUL-terminated string, or an ERR_PTR. Caller must free |
1585 | * the returned string. | ||
1584 | */ | 1586 | */ |
1585 | char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first, | 1587 | char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first, |
1586 | size_t total) | 1588 | void *p, size_t total) |
1587 | { | 1589 | { |
1588 | struct xdr_buf *arg = &rqstp->rq_arg; | 1590 | size_t len, remaining; |
1589 | struct page **pages; | 1591 | char *result, *dst; |
1590 | char *result; | ||
1591 | |||
1592 | /* VFS API demands a NUL-terminated pathname. This function | ||
1593 | * uses a page from @rqstp as the pathname buffer, to enable | ||
1594 | * direct placement. Thus the total buffer size is PAGE_SIZE. | ||
1595 | * Space in this buffer for NUL-termination requires that we | ||
1596 | * cap the size of the returned symlink pathname just a | ||
1597 | * little early. | ||
1598 | */ | ||
1599 | if (total > PAGE_SIZE - 1) | ||
1600 | return ERR_PTR(-ENAMETOOLONG); | ||
1601 | 1592 | ||
1602 | /* Some types of transport can present the pathname entirely | 1593 | result = kmalloc(total + 1, GFP_KERNEL); |
1603 | * in rq_arg.pages. If not, then copy the pathname into one | 1594 | if (!result) |
1604 | * page. | 1595 | return ERR_PTR(-ESERVERFAULT); |
1605 | */ | ||
1606 | pages = arg->pages; | ||
1607 | WARN_ON_ONCE(arg->page_base != 0); | ||
1608 | if (first->iov_base == 0) { | ||
1609 | result = page_address(*pages); | ||
1610 | result[total] = '\0'; | ||
1611 | } else { | ||
1612 | size_t len, remaining; | ||
1613 | char *dst; | ||
1614 | 1596 | ||
1615 | result = page_address(*(rqstp->rq_next_page++)); | 1597 | dst = result; |
1616 | dst = result; | 1598 | remaining = total; |
1617 | remaining = total; | ||
1618 | 1599 | ||
1619 | len = min_t(size_t, total, first->iov_len); | 1600 | len = min_t(size_t, total, first->iov_len); |
1601 | if (len) { | ||
1620 | memcpy(dst, first->iov_base, len); | 1602 | memcpy(dst, first->iov_base, len); |
1621 | dst += len; | 1603 | dst += len; |
1622 | remaining -= len; | 1604 | remaining -= len; |
1605 | } | ||
1623 | 1606 | ||
1624 | /* No more than one page left */ | 1607 | if (remaining) { |
1625 | if (remaining) { | 1608 | len = min_t(size_t, remaining, PAGE_SIZE); |
1626 | len = min_t(size_t, remaining, PAGE_SIZE); | 1609 | memcpy(dst, p, len); |
1627 | memcpy(dst, page_address(*pages), len); | 1610 | dst += len; |
1628 | dst += len; | ||
1629 | } | ||
1630 | |||
1631 | *dst = '\0'; | ||
1632 | } | 1611 | } |
1633 | 1612 | ||
1634 | /* Sanity check: we don't allow the pathname argument to | 1613 | *dst = '\0'; |
1614 | |||
1615 | /* Sanity check: Linux doesn't allow the pathname argument to | ||
1635 | * contain a NUL byte. | 1616 | * contain a NUL byte. |
1636 | */ | 1617 | */ |
1637 | if (strlen(result) != total) | 1618 | if (strlen(result) != total) { |
1619 | kfree(result); | ||
1638 | return ERR_PTR(-EINVAL); | 1620 | return ERR_PTR(-EINVAL); |
1621 | } | ||
1639 | return result; | 1622 | return result; |
1640 | } | 1623 | } |
1641 | EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname); | 1624 | EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname); |