Skip to content

Commit 2825a7f

Browse files
author
J. Bruce Fields
committed
nfsd4: allow encoding across page boundaries
After this we can handle for example getattr of very large ACLs. Read, readdir, readlink are still special cases with their own limits. Also we can't handle a new operation starting close to the end of a page. Signed-off-by: J. Bruce Fields <bfields@redhat.com>
1 parent a8095f7 commit 2825a7f

File tree

6 files changed

+126
-19
lines changed

6 files changed

+126
-19
lines changed

fs/nfsd/nfs4proc.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1264,6 +1264,10 @@ static void svcxdr_init_encode(struct svc_rqst *rqstp,
12641264
xdr->end = head->iov_base + PAGE_SIZE - 2 * RPC_MAX_AUTH_SIZE;
12651265
/* Tail and page_len should be zero at this point: */
12661266
buf->len = buf->head[0].iov_len;
1267+
xdr->scratch.iov_len = 0;
1268+
xdr->page_ptr = buf->pages;
1269+
buf->buflen = PAGE_SIZE * (1 + rqstp->rq_page_end - buf->pages)
1270+
- 2 * RPC_MAX_AUTH_SIZE;
12671271
}
12681272

12691273
/*

fs/nfsd/nfs4xdr.c

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,6 +1624,7 @@ static int nfsd4_max_reply(u32 opnum)
16241624
* the head and tail in another page:
16251625
*/
16261626
return 2 * PAGE_SIZE;
1627+
case OP_GETATTR:
16271628
case OP_READ:
16281629
return INT_MAX;
16291630
default:
@@ -2560,21 +2561,31 @@ nfsd4_encode_fattr(struct xdr_stream *xdr, struct svc_fh *fhp,
25602561
goto out;
25612562
}
25622563

2564+
static void svcxdr_init_encode_from_buffer(struct xdr_stream *xdr,
2565+
struct xdr_buf *buf, __be32 *p, int bytes)
2566+
{
2567+
xdr->scratch.iov_len = 0;
2568+
memset(buf, 0, sizeof(struct xdr_buf));
2569+
buf->head[0].iov_base = p;
2570+
buf->head[0].iov_len = 0;
2571+
buf->len = 0;
2572+
xdr->buf = buf;
2573+
xdr->iov = buf->head;
2574+
xdr->p = p;
2575+
xdr->end = (void *)p + bytes;
2576+
buf->buflen = bytes;
2577+
}
2578+
25632579
__be32 nfsd4_encode_fattr_to_buf(__be32 **p, int words,
25642580
struct svc_fh *fhp, struct svc_export *exp,
25652581
struct dentry *dentry, u32 *bmval,
25662582
struct svc_rqst *rqstp, int ignore_crossmnt)
25672583
{
2568-
struct xdr_buf dummy = {
2569-
.head[0] = {
2570-
.iov_base = *p,
2571-
},
2572-
.buflen = words << 2,
2573-
};
2584+
struct xdr_buf dummy;
25742585
struct xdr_stream xdr;
25752586
__be32 ret;
25762587

2577-
xdr_init_encode(&xdr, &dummy, NULL);
2588+
svcxdr_init_encode_from_buffer(&xdr, &dummy, *p, words << 2);
25782589
ret = nfsd4_encode_fattr(&xdr, fhp, exp, dentry, bmval, rqstp,
25792590
ignore_crossmnt);
25802591
*p = xdr.p;
@@ -3064,8 +3075,6 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,
30643075

30653076
if (nfserr)
30663077
return nfserr;
3067-
if (resp->xdr.buf->page_len)
3068-
return nfserr_resource;
30693078

30703079
p = xdr_reserve_space(xdr, 8); /* eof flag and byte count */
30713080
if (!p)
@@ -3075,6 +3084,9 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,
30753084
if (xdr->end - xdr->p < 1)
30763085
return nfserr_resource;
30773086

3087+
if (resp->xdr.buf->page_len)
3088+
return nfserr_resource;
3089+
30783090
maxcount = svc_max_payload(resp->rqstp);
30793091
if (maxcount > read->rd_length)
30803092
maxcount = read->rd_length;
@@ -3119,6 +3131,8 @@ nfsd4_encode_read(struct nfsd4_compoundres *resp, __be32 nfserr,
31193131
- (char *)resp->xdr.buf->head[0].iov_base);
31203132
resp->xdr.buf->page_len = maxcount;
31213133
xdr->buf->len += maxcount;
3134+
xdr->page_ptr += v;
3135+
xdr->buf->buflen = maxcount + PAGE_SIZE - 2 * RPC_MAX_AUTH_SIZE;
31223136
xdr->iov = xdr->buf->tail;
31233137

31243138
/* Use rest of head for padding and remaining ops: */
@@ -3145,6 +3159,11 @@ nfsd4_encode_readlink(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd
31453159

31463160
if (nfserr)
31473161
return nfserr;
3162+
3163+
p = xdr_reserve_space(xdr, 4);
3164+
if (!p)
3165+
return nfserr_resource;
3166+
31483167
if (resp->xdr.buf->page_len)
31493168
return nfserr_resource;
31503169
if (!*resp->rqstp->rq_next_page)
@@ -3154,10 +3173,6 @@ nfsd4_encode_readlink(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd
31543173

31553174
maxcount = PAGE_SIZE;
31563175

3157-
p = xdr_reserve_space(xdr, 4);
3158-
if (!p)
3159-
return nfserr_resource;
3160-
31613176
if (xdr->end - xdr->p < 1)
31623177
return nfserr_resource;
31633178

@@ -3180,6 +3195,8 @@ nfsd4_encode_readlink(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd
31803195
- (char *)resp->xdr.buf->head[0].iov_base;
31813196
resp->xdr.buf->page_len = maxcount;
31823197
xdr->buf->len += maxcount;
3198+
xdr->page_ptr += 1;
3199+
xdr->buf->buflen -= PAGE_SIZE;
31833200
xdr->iov = xdr->buf->tail;
31843201

31853202
/* Use rest of head for padding and remaining ops: */
@@ -3206,15 +3223,16 @@ nfsd4_encode_readdir(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd4
32063223

32073224
if (nfserr)
32083225
return nfserr;
3209-
if (resp->xdr.buf->page_len)
3210-
return nfserr_resource;
3211-
if (!*resp->rqstp->rq_next_page)
3212-
return nfserr_resource;
32133226

32143227
p = xdr_reserve_space(xdr, NFS4_VERIFIER_SIZE);
32153228
if (!p)
32163229
return nfserr_resource;
32173230

3231+
if (resp->xdr.buf->page_len)
3232+
return nfserr_resource;
3233+
if (!*resp->rqstp->rq_next_page)
3234+
return nfserr_resource;
3235+
32183236
/* XXX: Following NFSv3, we ignore the READDIR verifier for now. */
32193237
WRITE32(0);
32203238
WRITE32(0);
@@ -3266,6 +3284,10 @@ nfsd4_encode_readdir(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd4
32663284

32673285
xdr->iov = xdr->buf->tail;
32683286

3287+
xdr->page_ptr++;
3288+
xdr->buf->buflen -= PAGE_SIZE;
3289+
xdr->iov = xdr->buf->tail;
3290+
32693291
/* Use rest of head for padding and remaining ops: */
32703292
resp->xdr.buf->tail[0].iov_base = tailbase;
32713293
resp->xdr.buf->tail[0].iov_len = 0;
@@ -3800,6 +3822,8 @@ nfsd4_encode_operation(struct nfsd4_compoundres *resp, struct nfsd4_op *op)
38003822
!nfsd4_enc_ops[op->opnum]);
38013823
encoder = nfsd4_enc_ops[op->opnum];
38023824
op->status = encoder(resp, op->status, &op->u);
3825+
xdr_commit_encode(xdr);
3826+
38033827
/* nfsd4_check_resp_size guarantees enough room for error status */
38043828
if (!op->status) {
38053829
int space_needed = 0;
@@ -3919,6 +3943,8 @@ nfs4svc_encode_compoundres(struct svc_rqst *rqstp, __be32 *p, struct nfsd4_compo
39193943
WARN_ON_ONCE(buf->len != buf->head[0].iov_len + buf->page_len +
39203944
buf->tail[0].iov_len);
39213945

3946+
rqstp->rq_next_page = resp->xdr.page_ptr + 1;
3947+
39223948
p = resp->tagp;
39233949
*p++ = htonl(resp->taglen);
39243950
memcpy(p, resp->tag, resp->taglen);

include/linux/sunrpc/svc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ struct svc_rqst {
244244
struct page * rq_pages[RPCSVC_MAXPAGES];
245245
struct page * *rq_respages; /* points into rq_pages */
246246
struct page * *rq_next_page; /* next reply page to use */
247+
struct page * *rq_page_end; /* one past the last page */
247248

248249
struct kvec rq_vec[RPCSVC_MAXPAGES]; /* generally useful.. */
249250

include/linux/sunrpc/xdr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ typedef int (*kxdrdproc_t)(void *rqstp, struct xdr_stream *xdr, void *obj);
215215

216216
extern void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p);
217217
extern __be32 *xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes);
218+
extern void xdr_commit_encode(struct xdr_stream *xdr);
218219
extern void xdr_truncate_encode(struct xdr_stream *xdr, size_t len);
219220
extern void xdr_write_pages(struct xdr_stream *xdr, struct page **pages,
220221
unsigned int base, unsigned int len);

net/sunrpc/svc_xprt.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ static int svc_alloc_arg(struct svc_rqst *rqstp)
597597
}
598598
rqstp->rq_pages[i] = p;
599599
}
600+
rqstp->rq_page_end = &rqstp->rq_pages[i];
600601
rqstp->rq_pages[i++] = NULL; /* this might be seen in nfs_read_actor */
601602

602603
/* Make arg->head point to first page and arg->pages point to rest */

net/sunrpc/xdr.c

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
462462
struct kvec *iov = buf->head;
463463
int scratch_len = buf->buflen - buf->page_len - buf->tail[0].iov_len;
464464

465+
xdr_set_scratch_buffer(xdr, NULL, 0);
465466
BUG_ON(scratch_len < 0);
466467
xdr->buf = buf;
467468
xdr->iov = iov;
@@ -481,6 +482,74 @@ void xdr_init_encode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)
481482
}
482483
EXPORT_SYMBOL_GPL(xdr_init_encode);
483484

485+
/**
486+
* xdr_commit_encode - Ensure all data is written to buffer
487+
* @xdr: pointer to xdr_stream
488+
*
489+
* We handle encoding across page boundaries by giving the caller a
490+
* temporary location to write to, then later copying the data into
491+
* place; xdr_commit_encode does that copying.
492+
*
493+
* Normally the caller doesn't need to call this directly, as the
494+
* following xdr_reserve_space will do it. But an explicit call may be
495+
* required at the end of encoding, or any other time when the xdr_buf
496+
* data might be read.
497+
*/
498+
void xdr_commit_encode(struct xdr_stream *xdr)
499+
{
500+
int shift = xdr->scratch.iov_len;
501+
void *page;
502+
503+
if (shift == 0)
504+
return;
505+
page = page_address(*xdr->page_ptr);
506+
memcpy(xdr->scratch.iov_base, page, shift);
507+
memmove(page, page + shift, (void *)xdr->p - page);
508+
xdr->scratch.iov_len = 0;
509+
}
510+
EXPORT_SYMBOL_GPL(xdr_commit_encode);
511+
512+
__be32 *xdr_get_next_encode_buffer(struct xdr_stream *xdr, size_t nbytes)
513+
{
514+
static __be32 *p;
515+
int space_left;
516+
int frag1bytes, frag2bytes;
517+
518+
if (nbytes > PAGE_SIZE)
519+
return NULL; /* Bigger buffers require special handling */
520+
if (xdr->buf->len + nbytes > xdr->buf->buflen)
521+
return NULL; /* Sorry, we're totally out of space */
522+
frag1bytes = (xdr->end - xdr->p) << 2;
523+
frag2bytes = nbytes - frag1bytes;
524+
if (xdr->iov)
525+
xdr->iov->iov_len += frag1bytes;
526+
else {
527+
xdr->buf->page_len += frag1bytes;
528+
xdr->page_ptr++;
529+
}
530+
xdr->iov = NULL;
531+
/*
532+
* If the last encode didn't end exactly on a page boundary, the
533+
* next one will straddle boundaries. Encode into the next
534+
* page, then copy it back later in xdr_commit_encode. We use
535+
* the "scratch" iov to track any temporarily unused fragment of
536+
* space at the end of the previous buffer:
537+
*/
538+
xdr->scratch.iov_base = xdr->p;
539+
xdr->scratch.iov_len = frag1bytes;
540+
p = page_address(*xdr->page_ptr);
541+
/*
542+
* Note this is where the next encode will start after we've
543+
* shifted this one back:
544+
*/
545+
xdr->p = (void *)p + frag2bytes;
546+
space_left = xdr->buf->buflen - xdr->buf->len;
547+
xdr->end = (void *)p + min_t(int, space_left, PAGE_SIZE);
548+
xdr->buf->page_len += frag2bytes;
549+
xdr->buf->len += nbytes;
550+
return p;
551+
}
552+
484553
/**
485554
* xdr_reserve_space - Reserve buffer space for sending
486555
* @xdr: pointer to xdr_stream
@@ -495,14 +564,18 @@ __be32 * xdr_reserve_space(struct xdr_stream *xdr, size_t nbytes)
495564
__be32 *p = xdr->p;
496565
__be32 *q;
497566

567+
xdr_commit_encode(xdr);
498568
/* align nbytes on the next 32-bit boundary */
499569
nbytes += 3;
500570
nbytes &= ~3;
501571
q = p + (nbytes >> 2);
502572
if (unlikely(q > xdr->end || q < p))
503-
return NULL;
573+
return xdr_get_next_encode_buffer(xdr, nbytes);
504574
xdr->p = q;
505-
xdr->iov->iov_len += nbytes;
575+
if (xdr->iov)
576+
xdr->iov->iov_len += nbytes;
577+
else
578+
xdr->buf->page_len += nbytes;
506579
xdr->buf->len += nbytes;
507580
return p;
508581
}
@@ -539,6 +612,7 @@ void xdr_truncate_encode(struct xdr_stream *xdr, size_t len)
539612
WARN_ON_ONCE(1);
540613
return;
541614
}
615+
xdr_commit_encode(xdr);
542616

543617
fraglen = min_t(int, buf->len - len, tail->iov_len);
544618
tail->iov_len -= fraglen;

0 commit comments

Comments
 (0)