Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Chuck Lever and committed by
J. Bruce Fields
11b4d66e 3fd9557a

+32 -44
+2
fs/nfsd/nfs3proc.c
··· 290 290 RETURN_STATUS(nfserr_nametoolong); 291 291 292 292 argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, 293 + page_address(rqstp->rq_arg.pages[0]), 293 294 argp->tlen); 294 295 if (IS_ERR(argp->tname)) 295 296 RETURN_STATUS(nfserrno(PTR_ERR(argp->tname))); ··· 304 303 fh_init(&resp->fh, NFS3_FHSIZE); 305 304 nfserr = nfsd_symlink(rqstp, &resp->dirfh, argp->fname, argp->flen, 306 305 argp->tname, &resp->fh); 306 + kfree(argp->tname); 307 307 RETURN_STATUS(nfserr); 308 308 } 309 309
+2
fs/nfsd/nfsproc.c
··· 454 454 return nfserr_nametoolong; 455 455 456 456 argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first, 457 + page_address(rqstp->rq_arg.pages[0]), 457 458 argp->tlen); 458 459 if (IS_ERR(argp->tname)) 459 460 return nfserrno(PTR_ERR(argp->tname)); ··· 467 466 nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen, 468 467 argp->tname, &newfh); 469 468 469 + kfree(argp->tname); 470 470 fh_put(&argp->ffh); 471 471 fh_put(&newfh); 472 472 return nfserr;
+2 -1
include/linux/sunrpc/svc.h
··· 499 499 struct page **pages, 500 500 struct kvec *first, size_t total); 501 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 505 #define RPC_MAX_ADDRBUFLEN (63U) 505 506
+26 -43
net/sunrpc/svc.c
··· 1577 1577 * svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call 1578 1578 * @rqstp: svc_rqst to operate on 1579 1579 * @first: buffer containing first section of pathname 1580 + * @p: buffer containing remaining section of pathname 1580 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 - * released automatically when @rqstp is recycled. 1583 + * The VFS symlink API demands a NUL-terminated pathname in mapped memory. 1584 + * Returns pointer to a NUL-terminated string, or an ERR_PTR. Caller must free 1585 + * the returned string. 1584 1586 */ 1585 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; 1589 - struct page **pages; 1590 - char *result; 1590 + size_t len, remaining; 1591 + char *result, *dst; 1591 1592 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); 1593 + result = kmalloc(total + 1, GFP_KERNEL); 1594 + if (!result) 1595 + return ERR_PTR(-ESERVERFAULT); 1601 1596 1602 - /* Some types of transport can present the pathname entirely 1603 - * in rq_arg.pages. If not, then copy the pathname into one 1604 - * page. 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; 1597 + dst = result; 1598 + remaining = total; 1614 1599 1615 - result = page_address(*(rqstp->rq_next_page++)); 1616 - dst = result; 1617 - remaining = total; 1618 - 1619 - len = min_t(size_t, total, first->iov_len); 1600 + len = min_t(size_t, total, first->iov_len); 1601 + if (len) { 1620 1602 memcpy(dst, first->iov_base, len); 1621 1603 dst += len; 1622 1604 remaining -= len; 1623 - 1624 - /* No more than one page left */ 1625 - if (remaining) { 1626 - len = min_t(size_t, remaining, PAGE_SIZE); 1627 - memcpy(dst, page_address(*pages), len); 1628 - dst += len; 1629 - } 1630 - 1631 - *dst = '\0'; 1632 1605 } 1633 1606 1634 - /* Sanity check: we don't allow the pathname argument to 1607 + if (remaining) { 1608 + len = min_t(size_t, remaining, PAGE_SIZE); 1609 + memcpy(dst, p, len); 1610 + dst += len; 1611 + } 1612 + 1613 + *dst = '\0'; 1614 + 1615 + /* Sanity check: Linux doesn't allow the pathname argument to 1635 1616 * contain a NUL byte. 1636 1617 */ 1637 - if (strlen(result) != total) 1618 + if (strlen(result) != total) { 1619 + kfree(result); 1638 1620 return ERR_PTR(-EINVAL); 1621 + } 1639 1622 return result; 1640 1623 } 1641 1624 EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname);