diff options
Diffstat (limited to 'net/sunrpc/xdr.c')
| -rw-r--r-- | net/sunrpc/xdr.c | 155 | 
1 files changed, 124 insertions, 31 deletions
| diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index cd9e841e749..679cd674b81 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -552,6 +552,74 @@ void xdr_write_pages(struct xdr_stream *xdr, struct page **pages, unsigned int b  }  EXPORT_SYMBOL_GPL(xdr_write_pages); +static void xdr_set_iov(struct xdr_stream *xdr, struct kvec *iov, +		__be32 *p, unsigned int len) +{ +	if (len > iov->iov_len) +		len = iov->iov_len; +	if (p == NULL) +		p = (__be32*)iov->iov_base; +	xdr->p = p; +	xdr->end = (__be32*)(iov->iov_base + len); +	xdr->iov = iov; +	xdr->page_ptr = NULL; +} + +static int xdr_set_page_base(struct xdr_stream *xdr, +		unsigned int base, unsigned int len) +{ +	unsigned int pgnr; +	unsigned int maxlen; +	unsigned int pgoff; +	unsigned int pgend; +	void *kaddr; + +	maxlen = xdr->buf->page_len; +	if (base >= maxlen) +		return -EINVAL; +	maxlen -= base; +	if (len > maxlen) +		len = maxlen; + +	base += xdr->buf->page_base; + +	pgnr = base >> PAGE_SHIFT; +	xdr->page_ptr = &xdr->buf->pages[pgnr]; +	kaddr = page_address(*xdr->page_ptr); + +	pgoff = base & ~PAGE_MASK; +	xdr->p = (__be32*)(kaddr + pgoff); + +	pgend = pgoff + len; +	if (pgend > PAGE_SIZE) +		pgend = PAGE_SIZE; +	xdr->end = (__be32*)(kaddr + pgend); +	xdr->iov = NULL; +	return 0; +} + +static void xdr_set_next_page(struct xdr_stream *xdr) +{ +	unsigned int newbase; + +	newbase = (1 + xdr->page_ptr - xdr->buf->pages) << PAGE_SHIFT; +	newbase -= xdr->buf->page_base; + +	if (xdr_set_page_base(xdr, newbase, PAGE_SIZE) < 0) +		xdr_set_iov(xdr, xdr->buf->tail, NULL, xdr->buf->len); +} + +static bool xdr_set_next_buffer(struct xdr_stream *xdr) +{ +	if (xdr->page_ptr != NULL) +		xdr_set_next_page(xdr); +	else if (xdr->iov == xdr->buf->head) { +		if (xdr_set_page_base(xdr, 0, PAGE_SIZE) < 0) +			xdr_set_iov(xdr, xdr->buf->tail, NULL, xdr->buf->len); +	} +	return xdr->p != xdr->end; +} +  /**   * xdr_init_decode - Initialize an xdr_stream for decoding data.   * @xdr: pointer to xdr_stream struct @@ -560,41 +628,67 @@ EXPORT_SYMBOL_GPL(xdr_write_pages);   */  void xdr_init_decode(struct xdr_stream *xdr, struct xdr_buf *buf, __be32 *p)  { -	struct kvec *iov = buf->head; -	unsigned int len = iov->iov_len; - -	if (len > buf->len) -		len = buf->len;  	xdr->buf = buf; -	xdr->iov = iov; -	xdr->p = p; -	xdr->end = (__be32 *)((char *)iov->iov_base + len); +	xdr->scratch.iov_base = NULL; +	xdr->scratch.iov_len = 0; +	if (buf->head[0].iov_len != 0) +		xdr_set_iov(xdr, buf->head, p, buf->len); +	else if (buf->page_len != 0) +		xdr_set_page_base(xdr, 0, buf->len);  }  EXPORT_SYMBOL_GPL(xdr_init_decode); -/** - * xdr_inline_peek - Allow read-ahead in the XDR data stream - * @xdr: pointer to xdr_stream struct - * @nbytes: number of bytes of data to decode - * - * Check if the input buffer is long enough to enable us to decode - * 'nbytes' more bytes of data starting at the current position. - * If so return the current pointer without updating the current - * pointer position. - */ -__be32 * xdr_inline_peek(struct xdr_stream *xdr, size_t nbytes) +static __be32 * __xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)  {  	__be32 *p = xdr->p;  	__be32 *q = p + XDR_QUADLEN(nbytes);  	if (unlikely(q > xdr->end || q < p))  		return NULL; +	xdr->p = q;  	return p;  } -EXPORT_SYMBOL_GPL(xdr_inline_peek);  /** - * xdr_inline_decode - Retrieve non-page XDR data to decode + * xdr_set_scratch_buffer - Attach a scratch buffer for decoding data. + * @xdr: pointer to xdr_stream struct + * @buf: pointer to an empty buffer + * @buflen: size of 'buf' + * + * The scratch buffer is used when decoding from an array of pages. + * If an xdr_inline_decode() call spans across page boundaries, then + * we copy the data into the scratch buffer in order to allow linear + * access. + */ +void xdr_set_scratch_buffer(struct xdr_stream *xdr, void *buf, size_t buflen) +{ +	xdr->scratch.iov_base = buf; +	xdr->scratch.iov_len = buflen; +} +EXPORT_SYMBOL_GPL(xdr_set_scratch_buffer); + +static __be32 *xdr_copy_to_scratch(struct xdr_stream *xdr, size_t nbytes) +{ +	__be32 *p; +	void *cpdest = xdr->scratch.iov_base; +	size_t cplen = (char *)xdr->end - (char *)xdr->p; + +	if (nbytes > xdr->scratch.iov_len) +		return NULL; +	memcpy(cpdest, xdr->p, cplen); +	cpdest += cplen; +	nbytes -= cplen; +	if (!xdr_set_next_buffer(xdr)) +		return NULL; +	p = __xdr_inline_decode(xdr, nbytes); +	if (p == NULL) +		return NULL; +	memcpy(cpdest, p, nbytes); +	return xdr->scratch.iov_base; +} + +/** + * xdr_inline_decode - Retrieve XDR data to decode   * @xdr: pointer to xdr_stream struct   * @nbytes: number of bytes of data to decode   * @@ -605,13 +699,16 @@ EXPORT_SYMBOL_GPL(xdr_inline_peek);   */  __be32 * xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes)  { -	__be32 *p = xdr->p; -	__be32 *q = p + XDR_QUADLEN(nbytes); +	__be32 *p; -	if (unlikely(q > xdr->end || q < p)) +	if (nbytes == 0) +		return xdr->p; +	if (xdr->p == xdr->end && !xdr_set_next_buffer(xdr))  		return NULL; -	xdr->p = q; -	return p; +	p = __xdr_inline_decode(xdr, nbytes); +	if (p != NULL) +		return p; +	return xdr_copy_to_scratch(xdr, nbytes);  }  EXPORT_SYMBOL_GPL(xdr_inline_decode); @@ -671,16 +768,12 @@ EXPORT_SYMBOL_GPL(xdr_read_pages);   */  void xdr_enter_page(struct xdr_stream *xdr, unsigned int len)  { -	char * kaddr = page_address(xdr->buf->pages[0]);  	xdr_read_pages(xdr, len);  	/*  	 * Position current pointer at beginning of tail, and  	 * set remaining message length.  	 */ -	if (len > PAGE_CACHE_SIZE - xdr->buf->page_base) -		len = PAGE_CACHE_SIZE - xdr->buf->page_base; -	xdr->p = (__be32 *)(kaddr + xdr->buf->page_base); -	xdr->end = (__be32 *)((char *)xdr->p + len); +	xdr_set_page_base(xdr, 0, len);  }  EXPORT_SYMBOL_GPL(xdr_enter_page); | 
