mirror of
https://github.com/torvalds/linux.git
synced 2025-11-03 10:10:33 +02:00
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>
This commit is contained in:
parent
3fd9557aec
commit
11b4d66ea3
4 changed files with 32 additions and 44 deletions
|
|
@ -290,6 +290,7 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp)
|
||||||
RETURN_STATUS(nfserr_nametoolong);
|
RETURN_STATUS(nfserr_nametoolong);
|
||||||
|
|
||||||
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
|
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
|
||||||
|
page_address(rqstp->rq_arg.pages[0]),
|
||||||
argp->tlen);
|
argp->tlen);
|
||||||
if (IS_ERR(argp->tname))
|
if (IS_ERR(argp->tname))
|
||||||
RETURN_STATUS(nfserrno(PTR_ERR(argp->tname)));
|
RETURN_STATUS(nfserrno(PTR_ERR(argp->tname)));
|
||||||
|
|
@ -303,6 +304,7 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp)
|
||||||
fh_init(&resp->fh, NFS3_FHSIZE);
|
fh_init(&resp->fh, NFS3_FHSIZE);
|
||||||
nfserr = nfsd_symlink(rqstp, &resp->dirfh, argp->fname, argp->flen,
|
nfserr = nfsd_symlink(rqstp, &resp->dirfh, argp->fname, argp->flen,
|
||||||
argp->tname, &resp->fh);
|
argp->tname, &resp->fh);
|
||||||
|
kfree(argp->tname);
|
||||||
RETURN_STATUS(nfserr);
|
RETURN_STATUS(nfserr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -454,6 +454,7 @@ nfsd_proc_symlink(struct svc_rqst *rqstp)
|
||||||
return nfserr_nametoolong;
|
return nfserr_nametoolong;
|
||||||
|
|
||||||
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
|
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
|
||||||
|
page_address(rqstp->rq_arg.pages[0]),
|
||||||
argp->tlen);
|
argp->tlen);
|
||||||
if (IS_ERR(argp->tname))
|
if (IS_ERR(argp->tname))
|
||||||
return nfserrno(PTR_ERR(argp->tname));
|
return nfserrno(PTR_ERR(argp->tname));
|
||||||
|
|
@ -466,6 +467,7 @@ nfsd_proc_symlink(struct svc_rqst *rqstp)
|
||||||
nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen,
|
nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen,
|
||||||
argp->tname, &newfh);
|
argp->tname, &newfh);
|
||||||
|
|
||||||
|
kfree(argp->tname);
|
||||||
fh_put(&argp->ffh);
|
fh_put(&argp->ffh);
|
||||||
fh_put(&newfh);
|
fh_put(&newfh);
|
||||||
return nfserr;
|
return nfserr;
|
||||||
|
|
|
||||||
|
|
@ -499,7 +499,8 @@ unsigned int svc_fill_write_vector(struct svc_rqst *rqstp,
|
||||||
struct page **pages,
|
struct page **pages,
|
||||||
struct kvec *first, size_t total);
|
struct kvec *first, size_t total);
|
||||||
char *svc_fill_symlink_pathname(struct svc_rqst *rqstp,
|
char *svc_fill_symlink_pathname(struct svc_rqst *rqstp,
|
||||||
struct kvec *first, size_t total);
|
struct kvec *first, void *p,
|
||||||
|
size_t total);
|
||||||
|
|
||||||
#define RPC_MAX_ADDRBUFLEN (63U)
|
#define RPC_MAX_ADDRBUFLEN (63U)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1577,65 +1577,48 @@ EXPORT_SYMBOL_GPL(svc_fill_write_vector);
|
||||||
* svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call
|
* svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call
|
||||||
* @rqstp: svc_rqst to operate on
|
* @rqstp: svc_rqst to operate on
|
||||||
* @first: buffer containing first section of pathname
|
* @first: buffer containing first section of pathname
|
||||||
|
* @p: buffer containing remaining section of pathname
|
||||||
* @total: total length of the pathname argument
|
* @total: total length of the pathname argument
|
||||||
*
|
*
|
||||||
* Returns pointer to a NUL-terminated string, or an ERR_PTR. The buffer is
|
* The VFS symlink API demands a NUL-terminated pathname in mapped memory.
|
||||||
* released automatically when @rqstp is recycled.
|
* Returns pointer to a NUL-terminated string, or an ERR_PTR. Caller must free
|
||||||
|
* the returned string.
|
||||||
*/
|
*/
|
||||||
char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first,
|
char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first,
|
||||||
size_t total)
|
void *p, size_t total)
|
||||||
{
|
{
|
||||||
struct xdr_buf *arg = &rqstp->rq_arg;
|
|
||||||
struct page **pages;
|
|
||||||
char *result;
|
|
||||||
|
|
||||||
/* VFS API demands a NUL-terminated pathname. This function
|
|
||||||
* uses a page from @rqstp as the pathname buffer, to enable
|
|
||||||
* direct placement. Thus the total buffer size is PAGE_SIZE.
|
|
||||||
* Space in this buffer for NUL-termination requires that we
|
|
||||||
* cap the size of the returned symlink pathname just a
|
|
||||||
* little early.
|
|
||||||
*/
|
|
||||||
if (total > PAGE_SIZE - 1)
|
|
||||||
return ERR_PTR(-ENAMETOOLONG);
|
|
||||||
|
|
||||||
/* Some types of transport can present the pathname entirely
|
|
||||||
* in rq_arg.pages. If not, then copy the pathname into one
|
|
||||||
* page.
|
|
||||||
*/
|
|
||||||
pages = arg->pages;
|
|
||||||
WARN_ON_ONCE(arg->page_base != 0);
|
|
||||||
if (first->iov_base == 0) {
|
|
||||||
result = page_address(*pages);
|
|
||||||
result[total] = '\0';
|
|
||||||
} else {
|
|
||||||
size_t len, remaining;
|
size_t len, remaining;
|
||||||
char *dst;
|
char *result, *dst;
|
||||||
|
|
||||||
|
result = kmalloc(total + 1, GFP_KERNEL);
|
||||||
|
if (!result)
|
||||||
|
return ERR_PTR(-ESERVERFAULT);
|
||||||
|
|
||||||
result = page_address(*(rqstp->rq_next_page++));
|
|
||||||
dst = result;
|
dst = result;
|
||||||
remaining = total;
|
remaining = total;
|
||||||
|
|
||||||
len = min_t(size_t, total, first->iov_len);
|
len = min_t(size_t, total, first->iov_len);
|
||||||
|
if (len) {
|
||||||
memcpy(dst, first->iov_base, len);
|
memcpy(dst, first->iov_base, len);
|
||||||
dst += len;
|
dst += len;
|
||||||
remaining -= len;
|
remaining -= len;
|
||||||
|
}
|
||||||
|
|
||||||
/* No more than one page left */
|
|
||||||
if (remaining) {
|
if (remaining) {
|
||||||
len = min_t(size_t, remaining, PAGE_SIZE);
|
len = min_t(size_t, remaining, PAGE_SIZE);
|
||||||
memcpy(dst, page_address(*pages), len);
|
memcpy(dst, p, len);
|
||||||
dst += len;
|
dst += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
*dst = '\0';
|
*dst = '\0';
|
||||||
}
|
|
||||||
|
|
||||||
/* Sanity check: we don't allow the pathname argument to
|
/* Sanity check: Linux doesn't allow the pathname argument to
|
||||||
* contain a NUL byte.
|
* contain a NUL byte.
|
||||||
*/
|
*/
|
||||||
if (strlen(result) != total)
|
if (strlen(result) != total) {
|
||||||
|
kfree(result);
|
||||||
return ERR_PTR(-EINVAL);
|
return ERR_PTR(-EINVAL);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname);
|
EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue