Skip to content

Commit 38a7031

Browse files
chuckleverJ. Bruce Fields
authored andcommitted
NFSD: Clean up legacy NFS SYMLINK argument XDR decoders
Move common code in NFSD's legacy SYMLINK decoders into a helper. The immediate benefits include: - one fewer data copies on transports that support DDP - consistent error checking across all versions - reduction of code duplication - support for both legal forms of SYMLINK requests on RDMA transports for all versions of NFS (in particular, NFSv2, for completeness) In the long term, this helper is an appropriate spot to perform a per-transport call-out to fill the pathname argument using, say, RDMA Reads. Filling the pathname in the proc function also means that eventually the incoming filehandle can be interpreted so that filesystem- specific memory can be allocated as a sink for the pathname argument, rather than using anonymous pages. Signed-off-by: Chuck Lever <[email protected]> Signed-off-by: J. Bruce Fields <[email protected]>
1 parent 8154ef2 commit 38a7031

File tree

9 files changed

+132
-65
lines changed

9 files changed

+132
-65
lines changed

fs/nfsd/nfs3proc.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,16 @@ nfsd3_proc_symlink(struct svc_rqst *rqstp)
283283
struct nfsd3_diropres *resp = rqstp->rq_resp;
284284
__be32 nfserr;
285285

286+
if (argp->tlen == 0)
287+
RETURN_STATUS(nfserr_inval);
288+
if (argp->tlen > NFS3_MAXPATHLEN)
289+
RETURN_STATUS(nfserr_nametoolong);
290+
291+
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
292+
argp->tlen);
293+
if (IS_ERR(argp->tname))
294+
RETURN_STATUS(nfserrno(PTR_ERR(argp->tname)));
295+
286296
dprintk("nfsd: SYMLINK(3) %s %.*s -> %.*s\n",
287297
SVCFH_fmt(&argp->ffh),
288298
argp->flen, argp->fname,

fs/nfsd/nfs3xdr.c

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -481,51 +481,24 @@ int
481481
nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p)
482482
{
483483
struct nfsd3_symlinkargs *args = rqstp->rq_argp;
484-
unsigned int len, avail;
485-
char *old, *new;
486-
struct kvec *vec;
484+
char *base = (char *)p;
485+
size_t dlen;
487486

488487
if (!(p = decode_fh(p, &args->ffh)) ||
489-
!(p = decode_filename(p, &args->fname, &args->flen))
490-
)
488+
!(p = decode_filename(p, &args->fname, &args->flen)))
491489
return 0;
492490
p = decode_sattr3(p, &args->attrs);
493491

494-
/* now decode the pathname, which might be larger than the first page.
495-
* As we have to check for nul's anyway, we copy it into a new page
496-
* This page appears in the rq_res.pages list, but as pages_len is always
497-
* 0, it won't get in the way
498-
*/
499-
len = ntohl(*p++);
500-
if (len == 0 || len > NFS3_MAXPATHLEN || len >= PAGE_SIZE)
501-
return 0;
502-
args->tname = new = page_address(*(rqstp->rq_next_page++));
503-
args->tlen = len;
504-
/* first copy and check from the first page */
505-
old = (char*)p;
506-
vec = &rqstp->rq_arg.head[0];
507-
if ((void *)old > vec->iov_base + vec->iov_len)
508-
return 0;
509-
avail = vec->iov_len - (old - (char*)vec->iov_base);
510-
while (len && avail && *old) {
511-
*new++ = *old++;
512-
len--;
513-
avail--;
514-
}
515-
/* now copy next page if there is one */
516-
if (len && !avail && rqstp->rq_arg.page_len) {
517-
avail = min_t(unsigned int, rqstp->rq_arg.page_len, PAGE_SIZE);
518-
old = page_address(rqstp->rq_arg.pages[0]);
519-
}
520-
while (len && avail && *old) {
521-
*new++ = *old++;
522-
len--;
523-
avail--;
524-
}
525-
*new = '\0';
526-
if (len)
527-
return 0;
492+
args->tlen = ntohl(*p++);
528493

494+
args->first.iov_base = p;
495+
args->first.iov_len = rqstp->rq_arg.head[0].iov_len;
496+
args->first.iov_len -= (char *)p - base;
497+
498+
dlen = args->first.iov_len + rqstp->rq_arg.page_len +
499+
rqstp->rq_arg.tail[0].iov_len;
500+
if (dlen < XDR_QUADLEN(args->tlen) << 2)
501+
return 0;
529502
return 1;
530503
}
531504

fs/nfsd/nfsproc.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,17 +449,19 @@ nfsd_proc_symlink(struct svc_rqst *rqstp)
449449
struct svc_fh newfh;
450450
__be32 nfserr;
451451

452+
if (argp->tlen > NFS_MAXPATHLEN)
453+
return nfserr_nametoolong;
454+
455+
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
456+
argp->tlen);
457+
if (IS_ERR(argp->tname))
458+
return nfserrno(PTR_ERR(argp->tname));
459+
452460
dprintk("nfsd: SYMLINK %s %.*s -> %.*s\n",
453461
SVCFH_fmt(&argp->ffh), argp->flen, argp->fname,
454462
argp->tlen, argp->tname);
455463

456464
fh_init(&newfh, NFS_FHSIZE);
457-
/*
458-
* Crazy hack: the request fits in a page, and already-decoded
459-
* attributes follow argp->tname, so it's safe to just write a
460-
* null to ensure it's null-terminated:
461-
*/
462-
argp->tname[argp->tlen] = '\0';
463465
nfserr = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen,
464466
argp->tname, &newfh);
465467

fs/nfsd/nfsxdr.c

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,22 +70,6 @@ decode_filename(__be32 *p, char **namp, unsigned int *lenp)
7070
return p;
7171
}
7272

73-
static __be32 *
74-
decode_pathname(__be32 *p, char **namp, unsigned int *lenp)
75-
{
76-
char *name;
77-
unsigned int i;
78-
79-
if ((p = xdr_decode_string_inplace(p, namp, lenp, NFS_MAXPATHLEN)) != NULL) {
80-
for (i = 0, name = *namp; i < *lenp; i++, name++) {
81-
if (*name == '\0')
82-
return NULL;
83-
}
84-
}
85-
86-
return p;
87-
}
88-
8973
static __be32 *
9074
decode_sattr(__be32 *p, struct iattr *iap)
9175
{
@@ -384,14 +368,39 @@ int
384368
nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, __be32 *p)
385369
{
386370
struct nfsd_symlinkargs *args = rqstp->rq_argp;
371+
char *base = (char *)p;
372+
size_t xdrlen;
387373

388374
if ( !(p = decode_fh(p, &args->ffh))
389-
|| !(p = decode_filename(p, &args->fname, &args->flen))
390-
|| !(p = decode_pathname(p, &args->tname, &args->tlen)))
375+
|| !(p = decode_filename(p, &args->fname, &args->flen)))
391376
return 0;
392-
p = decode_sattr(p, &args->attrs);
393377

394-
return xdr_argsize_check(rqstp, p);
378+
args->tlen = ntohl(*p++);
379+
if (args->tlen == 0)
380+
return 0;
381+
382+
args->first.iov_base = p;
383+
args->first.iov_len = rqstp->rq_arg.head[0].iov_len;
384+
args->first.iov_len -= (char *)p - base;
385+
386+
/* This request is never larger than a page. Therefore,
387+
* transport will deliver either:
388+
* 1. pathname in the pagelist -> sattr is in the tail.
389+
* 2. everything in the head buffer -> sattr is in the head.
390+
*/
391+
if (rqstp->rq_arg.page_len) {
392+
if (args->tlen != rqstp->rq_arg.page_len)
393+
return 0;
394+
p = rqstp->rq_arg.tail[0].iov_base;
395+
} else {
396+
xdrlen = XDR_QUADLEN(args->tlen);
397+
if (xdrlen > args->first.iov_len - (8 * sizeof(__be32)))
398+
return 0;
399+
p += xdrlen;
400+
}
401+
decode_sattr(p, &args->attrs);
402+
403+
return 1;
395404
}
396405

397406
int

fs/nfsd/xdr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ struct nfsd_symlinkargs {
7272
char * tname;
7373
unsigned int tlen;
7474
struct iattr attrs;
75+
struct kvec first;
7576
};
7677

7778
struct nfsd_readdirargs {

fs/nfsd/xdr3.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ struct nfsd3_symlinkargs {
9090
char * tname;
9191
unsigned int tlen;
9292
struct iattr attrs;
93+
struct kvec first;
9394
};
9495

9596
struct nfsd3_readdirargs {

fs/nfsd/xdr4.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ struct nfsd4_create {
110110
struct {
111111
u32 datalen;
112112
char *data;
113+
struct kvec first;
113114
} link; /* NF4LNK */
114115
struct {
115116
u32 specdata1;
@@ -124,6 +125,7 @@ struct nfsd4_create {
124125
};
125126
#define cr_datalen u.link.datalen
126127
#define cr_data u.link.data
128+
#define cr_first u.link.first
127129
#define cr_specdata1 u.dev.specdata1
128130
#define cr_specdata2 u.dev.specdata2
129131

include/linux/sunrpc/svc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,8 @@ struct svc_pool * svc_pool_for_cpu(struct svc_serv *serv, int cpu);
497497
char * svc_print_addr(struct svc_rqst *, char *, size_t);
498498
unsigned int svc_fill_write_vector(struct svc_rqst *rqstp,
499499
struct kvec *first, size_t total);
500+
char *svc_fill_symlink_pathname(struct svc_rqst *rqstp,
501+
struct kvec *first, size_t total);
500502

501503
#define RPC_MAX_ADDRBUFLEN (63U)
502504

net/sunrpc/svc.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,3 +1575,70 @@ unsigned int svc_fill_write_vector(struct svc_rqst *rqstp, struct kvec *first,
15751575
return i;
15761576
}
15771577
EXPORT_SYMBOL_GPL(svc_fill_write_vector);
1578+
1579+
/**
1580+
* svc_fill_symlink_pathname - Construct pathname argument for VFS symlink call
1581+
* @rqstp: svc_rqst to operate on
1582+
* @first: buffer containing first section of pathname
1583+
* @total: total length of the pathname argument
1584+
*
1585+
* Returns pointer to a NUL-terminated string, or an ERR_PTR. The buffer is
1586+
* released automatically when @rqstp is recycled.
1587+
*/
1588+
char *svc_fill_symlink_pathname(struct svc_rqst *rqstp, struct kvec *first,
1589+
size_t total)
1590+
{
1591+
struct xdr_buf *arg = &rqstp->rq_arg;
1592+
struct page **pages;
1593+
char *result;
1594+
1595+
/* VFS API demands a NUL-terminated pathname. This function
1596+
* uses a page from @rqstp as the pathname buffer, to enable
1597+
* direct placement. Thus the total buffer size is PAGE_SIZE.
1598+
* Space in this buffer for NUL-termination requires that we
1599+
* cap the size of the returned symlink pathname just a
1600+
* little early.
1601+
*/
1602+
if (total > PAGE_SIZE - 1)
1603+
return ERR_PTR(-ENAMETOOLONG);
1604+
1605+
/* Some types of transport can present the pathname entirely
1606+
* in rq_arg.pages. If not, then copy the pathname into one
1607+
* page.
1608+
*/
1609+
pages = arg->pages;
1610+
WARN_ON_ONCE(arg->page_base != 0);
1611+
if (first->iov_base == 0) {
1612+
result = page_address(*pages);
1613+
result[total] = '\0';
1614+
} else {
1615+
size_t len, remaining;
1616+
char *dst;
1617+
1618+
result = page_address(*(rqstp->rq_next_page++));
1619+
dst = result;
1620+
remaining = total;
1621+
1622+
len = min_t(size_t, total, first->iov_len);
1623+
memcpy(dst, first->iov_base, len);
1624+
dst += len;
1625+
remaining -= len;
1626+
1627+
/* No more than one page left */
1628+
if (remaining) {
1629+
len = min_t(size_t, remaining, PAGE_SIZE);
1630+
memcpy(dst, page_address(*pages), len);
1631+
dst += len;
1632+
}
1633+
1634+
*dst = '\0';
1635+
}
1636+
1637+
/* Sanity check: we don't allow the pathname argument to
1638+
* contain a NUL byte.
1639+
*/
1640+
if (strlen(result) != total)
1641+
return ERR_PTR(-EINVAL);
1642+
return result;
1643+
}
1644+
EXPORT_SYMBOL_GPL(svc_fill_symlink_pathname);

0 commit comments

Comments
 (0)