Commit 11b4d66e authored by Chuck Lever's avatar Chuck Lever Committed by J. Bruce Fields

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: default avatarChuck Lever <chuck.lever@oracle.com>
Signed-off-by: default avatarJ. Bruce Fields <bfields@redhat.com>
parent 3fd9557a
...@@ -290,6 +290,7 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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, ...@@ -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); ...@@ -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; size_t len, remaining;
struct page **pages; char *result, *dst;
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 result = kmalloc(total + 1, GFP_KERNEL);
* in rq_arg.pages. If not, then copy the pathname into one if (!result)
* page. return ERR_PTR(-ESERVERFAULT);
*/
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;
char *dst;
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, p, len);
memcpy(dst, page_address(*pages), len); dst += len;
dst += len;
}
*dst = '\0';
} }
/* Sanity check: we don't allow the pathname argument to *dst = '\0';
/* 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);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment