/*
|
/*
|
* linux/net/sunrpc/xdr.c
|
* linux/net/sunrpc/xdr.c
|
*
|
*
|
* Generic XDR support.
|
* Generic XDR support.
|
*
|
*
|
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
|
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
|
*/
|
*/
|
|
|
#include <linux/types.h>
|
#include <linux/types.h>
|
#include <linux/socket.h>
|
#include <linux/socket.h>
|
#include <linux/string.h>
|
#include <linux/string.h>
|
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
#include <linux/pagemap.h>
|
#include <linux/pagemap.h>
|
#include <linux/errno.h>
|
#include <linux/errno.h>
|
#include <linux/in.h>
|
#include <linux/in.h>
|
#include <linux/sunrpc/xdr.h>
|
#include <linux/sunrpc/xdr.h>
|
#include <linux/sunrpc/msg_prot.h>
|
#include <linux/sunrpc/msg_prot.h>
|
|
|
/*
|
/*
|
* XDR functions for basic NFS types
|
* XDR functions for basic NFS types
|
*/
|
*/
|
u32 *
|
u32 *
|
xdr_encode_netobj(u32 *p, const struct xdr_netobj *obj)
|
xdr_encode_netobj(u32 *p, const struct xdr_netobj *obj)
|
{
|
{
|
unsigned int quadlen = XDR_QUADLEN(obj->len);
|
unsigned int quadlen = XDR_QUADLEN(obj->len);
|
|
|
p[quadlen] = 0; /* zero trailing bytes */
|
p[quadlen] = 0; /* zero trailing bytes */
|
*p++ = htonl(obj->len);
|
*p++ = htonl(obj->len);
|
memcpy(p, obj->data, obj->len);
|
memcpy(p, obj->data, obj->len);
|
return p + XDR_QUADLEN(obj->len);
|
return p + XDR_QUADLEN(obj->len);
|
}
|
}
|
|
|
u32 *
|
u32 *
|
xdr_decode_netobj_fixed(u32 *p, void *obj, unsigned int len)
|
xdr_decode_netobj_fixed(u32 *p, void *obj, unsigned int len)
|
{
|
{
|
if (ntohl(*p++) != len)
|
if (ntohl(*p++) != len)
|
return NULL;
|
return NULL;
|
memcpy(obj, p, len);
|
memcpy(obj, p, len);
|
return p + XDR_QUADLEN(len);
|
return p + XDR_QUADLEN(len);
|
}
|
}
|
|
|
u32 *
|
u32 *
|
xdr_decode_netobj(u32 *p, struct xdr_netobj *obj)
|
xdr_decode_netobj(u32 *p, struct xdr_netobj *obj)
|
{
|
{
|
unsigned int len;
|
unsigned int len;
|
|
|
if ((len = ntohl(*p++)) > XDR_MAX_NETOBJ)
|
if ((len = ntohl(*p++)) > XDR_MAX_NETOBJ)
|
return NULL;
|
return NULL;
|
obj->len = len;
|
obj->len = len;
|
obj->data = (u8 *) p;
|
obj->data = (u8 *) p;
|
return p + XDR_QUADLEN(len);
|
return p + XDR_QUADLEN(len);
|
}
|
}
|
|
|
u32 *
|
u32 *
|
xdr_encode_array(u32 *p, const char *array, unsigned int len)
|
xdr_encode_array(u32 *p, const char *array, unsigned int len)
|
{
|
{
|
int quadlen = XDR_QUADLEN(len);
|
int quadlen = XDR_QUADLEN(len);
|
|
|
p[quadlen] = 0;
|
p[quadlen] = 0;
|
*p++ = htonl(len);
|
*p++ = htonl(len);
|
memcpy(p, array, len);
|
memcpy(p, array, len);
|
return p + quadlen;
|
return p + quadlen;
|
}
|
}
|
|
|
u32 *
|
u32 *
|
xdr_encode_string(u32 *p, const char *string)
|
xdr_encode_string(u32 *p, const char *string)
|
{
|
{
|
return xdr_encode_array(p, string, strlen(string));
|
return xdr_encode_array(p, string, strlen(string));
|
}
|
}
|
|
|
u32 *
|
u32 *
|
xdr_decode_string(u32 *p, char **sp, int *lenp, int maxlen)
|
xdr_decode_string(u32 *p, char **sp, int *lenp, int maxlen)
|
{
|
{
|
unsigned int len;
|
unsigned int len;
|
char *string;
|
char *string;
|
|
|
if ((len = ntohl(*p++)) > maxlen)
|
if ((len = ntohl(*p++)) > maxlen)
|
return NULL;
|
return NULL;
|
if (lenp)
|
if (lenp)
|
*lenp = len;
|
*lenp = len;
|
if ((len % 4) != 0) {
|
if ((len % 4) != 0) {
|
string = (char *) p;
|
string = (char *) p;
|
} else {
|
} else {
|
string = (char *) (p - 1);
|
string = (char *) (p - 1);
|
memmove(string, p, len);
|
memmove(string, p, len);
|
}
|
}
|
string[len] = '\0';
|
string[len] = '\0';
|
*sp = string;
|
*sp = string;
|
return p + XDR_QUADLEN(len);
|
return p + XDR_QUADLEN(len);
|
}
|
}
|
|
|
u32 *
|
u32 *
|
xdr_decode_string_inplace(u32 *p, char **sp, int *lenp, int maxlen)
|
xdr_decode_string_inplace(u32 *p, char **sp, int *lenp, int maxlen)
|
{
|
{
|
unsigned int len;
|
unsigned int len;
|
|
|
if ((len = ntohl(*p++)) > maxlen)
|
if ((len = ntohl(*p++)) > maxlen)
|
return NULL;
|
return NULL;
|
*lenp = len;
|
*lenp = len;
|
*sp = (char *) p;
|
*sp = (char *) p;
|
return p + XDR_QUADLEN(len);
|
return p + XDR_QUADLEN(len);
|
}
|
}
|
|
|
|
|
void
|
void
|
xdr_encode_pages(struct xdr_buf *xdr, struct page **pages, unsigned int base,
|
xdr_encode_pages(struct xdr_buf *xdr, struct page **pages, unsigned int base,
|
unsigned int len)
|
unsigned int len)
|
{
|
{
|
xdr->pages = pages;
|
xdr->pages = pages;
|
xdr->page_base = base;
|
xdr->page_base = base;
|
xdr->page_len = len;
|
xdr->page_len = len;
|
|
|
if (len & 3) {
|
if (len & 3) {
|
struct iovec *iov = xdr->tail;
|
struct iovec *iov = xdr->tail;
|
unsigned int pad = 4 - (len & 3);
|
unsigned int pad = 4 - (len & 3);
|
|
|
iov->iov_base = (void *) "\0\0\0";
|
iov->iov_base = (void *) "\0\0\0";
|
iov->iov_len = pad;
|
iov->iov_len = pad;
|
len += pad;
|
len += pad;
|
}
|
}
|
xdr->len += len;
|
xdr->len += len;
|
}
|
}
|
|
|
void
|
void
|
xdr_inline_pages(struct xdr_buf *xdr, unsigned int offset,
|
xdr_inline_pages(struct xdr_buf *xdr, unsigned int offset,
|
struct page **pages, unsigned int base, unsigned int len)
|
struct page **pages, unsigned int base, unsigned int len)
|
{
|
{
|
struct iovec *head = xdr->head;
|
struct iovec *head = xdr->head;
|
struct iovec *tail = xdr->tail;
|
struct iovec *tail = xdr->tail;
|
char *buf = (char *)head->iov_base;
|
char *buf = (char *)head->iov_base;
|
unsigned int buflen = head->iov_len;
|
unsigned int buflen = head->iov_len;
|
|
|
head->iov_len = offset;
|
head->iov_len = offset;
|
|
|
xdr->pages = pages;
|
xdr->pages = pages;
|
xdr->page_base = base;
|
xdr->page_base = base;
|
xdr->page_len = len;
|
xdr->page_len = len;
|
|
|
tail->iov_base = buf + offset;
|
tail->iov_base = buf + offset;
|
tail->iov_len = buflen - offset;
|
tail->iov_len = buflen - offset;
|
|
|
xdr->len += len;
|
xdr->len += len;
|
}
|
}
|
|
|
/*
|
/*
|
* Realign the iovec if the server missed out some reply elements
|
* Realign the iovec if the server missed out some reply elements
|
* (such as post-op attributes,...)
|
* (such as post-op attributes,...)
|
* Note: This is a simple implementation that assumes that
|
* Note: This is a simple implementation that assumes that
|
* len <= iov->iov_len !!!
|
* len <= iov->iov_len !!!
|
* The RPC header (assumed to be the 1st element in the iov array)
|
* The RPC header (assumed to be the 1st element in the iov array)
|
* is not shifted.
|
* is not shifted.
|
*/
|
*/
|
void xdr_shift_iovec(struct iovec *iov, int nr, size_t len)
|
void xdr_shift_iovec(struct iovec *iov, int nr, size_t len)
|
{
|
{
|
struct iovec *pvec;
|
struct iovec *pvec;
|
|
|
for (pvec = iov + nr - 1; nr > 1; nr--, pvec--) {
|
for (pvec = iov + nr - 1; nr > 1; nr--, pvec--) {
|
struct iovec *svec = pvec - 1;
|
struct iovec *svec = pvec - 1;
|
|
|
if (len > pvec->iov_len) {
|
if (len > pvec->iov_len) {
|
printk(KERN_DEBUG "RPC: Urk! Large shift of short iovec.\n");
|
printk(KERN_DEBUG "RPC: Urk! Large shift of short iovec.\n");
|
return;
|
return;
|
}
|
}
|
memmove((char *)pvec->iov_base + len, pvec->iov_base,
|
memmove((char *)pvec->iov_base + len, pvec->iov_base,
|
pvec->iov_len - len);
|
pvec->iov_len - len);
|
|
|
if (len > svec->iov_len) {
|
if (len > svec->iov_len) {
|
printk(KERN_DEBUG "RPC: Urk! Large shift of short iovec.\n");
|
printk(KERN_DEBUG "RPC: Urk! Large shift of short iovec.\n");
|
return;
|
return;
|
}
|
}
|
memcpy(pvec->iov_base,
|
memcpy(pvec->iov_base,
|
(char *)svec->iov_base + svec->iov_len - len, len);
|
(char *)svec->iov_base + svec->iov_len - len, len);
|
}
|
}
|
}
|
}
|
|
|
/*
|
/*
|
* Map a struct xdr_buf into an iovec array.
|
* Map a struct xdr_buf into an iovec array.
|
*/
|
*/
|
int xdr_kmap(struct iovec *iov_base, struct xdr_buf *xdr, unsigned int base)
|
int xdr_kmap(struct iovec *iov_base, struct xdr_buf *xdr, unsigned int base)
|
{
|
{
|
struct iovec *iov = iov_base;
|
struct iovec *iov = iov_base;
|
struct page **ppage = xdr->pages;
|
struct page **ppage = xdr->pages;
|
struct page **first_kmap = NULL;
|
struct page **first_kmap = NULL;
|
unsigned int len, pglen = xdr->page_len;
|
unsigned int len, pglen = xdr->page_len;
|
|
|
len = xdr->head[0].iov_len;
|
len = xdr->head[0].iov_len;
|
if (base < len) {
|
if (base < len) {
|
iov->iov_len = len - base;
|
iov->iov_len = len - base;
|
iov->iov_base = (char *)xdr->head[0].iov_base + base;
|
iov->iov_base = (char *)xdr->head[0].iov_base + base;
|
iov++;
|
iov++;
|
base = 0;
|
base = 0;
|
} else
|
} else
|
base -= len;
|
base -= len;
|
|
|
if (pglen == 0)
|
if (pglen == 0)
|
goto map_tail;
|
goto map_tail;
|
if (base >= pglen) {
|
if (base >= pglen) {
|
base -= pglen;
|
base -= pglen;
|
goto map_tail;
|
goto map_tail;
|
}
|
}
|
if (base || xdr->page_base) {
|
if (base || xdr->page_base) {
|
pglen -= base;
|
pglen -= base;
|
base += xdr->page_base;
|
base += xdr->page_base;
|
ppage += base >> PAGE_CACHE_SHIFT;
|
ppage += base >> PAGE_CACHE_SHIFT;
|
base &= ~PAGE_CACHE_MASK;
|
base &= ~PAGE_CACHE_MASK;
|
}
|
}
|
do {
|
do {
|
len = PAGE_CACHE_SIZE;
|
len = PAGE_CACHE_SIZE;
|
if (!first_kmap) {
|
if (!first_kmap) {
|
first_kmap = ppage;
|
first_kmap = ppage;
|
iov->iov_base = kmap(*ppage);
|
iov->iov_base = kmap(*ppage);
|
} else {
|
} else {
|
iov->iov_base = kmap_nonblock(*ppage);
|
iov->iov_base = kmap_nonblock(*ppage);
|
if (!iov->iov_base)
|
if (!iov->iov_base)
|
goto out_err;
|
goto out_err;
|
}
|
}
|
if (base) {
|
if (base) {
|
iov->iov_base += base;
|
iov->iov_base += base;
|
len -= base;
|
len -= base;
|
base = 0;
|
base = 0;
|
}
|
}
|
if (pglen < len)
|
if (pglen < len)
|
len = pglen;
|
len = pglen;
|
iov->iov_len = len;
|
iov->iov_len = len;
|
iov++;
|
iov++;
|
ppage++;
|
ppage++;
|
} while ((pglen -= len) != 0);
|
} while ((pglen -= len) != 0);
|
map_tail:
|
map_tail:
|
if (xdr->tail[0].iov_len) {
|
if (xdr->tail[0].iov_len) {
|
iov->iov_len = xdr->tail[0].iov_len - base;
|
iov->iov_len = xdr->tail[0].iov_len - base;
|
iov->iov_base = (char *)xdr->tail[0].iov_base + base;
|
iov->iov_base = (char *)xdr->tail[0].iov_base + base;
|
iov++;
|
iov++;
|
}
|
}
|
return (iov - iov_base);
|
return (iov - iov_base);
|
out_err:
|
out_err:
|
for (; first_kmap != ppage; first_kmap++)
|
for (; first_kmap != ppage; first_kmap++)
|
kunmap(*first_kmap);
|
kunmap(*first_kmap);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
void xdr_kunmap(struct xdr_buf *xdr, unsigned int base, int niov)
|
void xdr_kunmap(struct xdr_buf *xdr, unsigned int base, int niov)
|
{
|
{
|
struct page **ppage = xdr->pages;
|
struct page **ppage = xdr->pages;
|
unsigned int pglen = xdr->page_len;
|
unsigned int pglen = xdr->page_len;
|
|
|
if (!pglen)
|
if (!pglen)
|
return;
|
return;
|
if (base >= xdr->head[0].iov_len)
|
if (base >= xdr->head[0].iov_len)
|
base -= xdr->head[0].iov_len;
|
base -= xdr->head[0].iov_len;
|
else {
|
else {
|
niov--;
|
niov--;
|
base = 0;
|
base = 0;
|
}
|
}
|
|
|
if (base >= pglen)
|
if (base >= pglen)
|
return;
|
return;
|
if (base || xdr->page_base) {
|
if (base || xdr->page_base) {
|
pglen -= base;
|
pglen -= base;
|
base += xdr->page_base;
|
base += xdr->page_base;
|
ppage += base >> PAGE_CACHE_SHIFT;
|
ppage += base >> PAGE_CACHE_SHIFT;
|
/* Note: The offset means that the length of the first
|
/* Note: The offset means that the length of the first
|
* page is really (PAGE_CACHE_SIZE - (base & ~PAGE_CACHE_MASK)).
|
* page is really (PAGE_CACHE_SIZE - (base & ~PAGE_CACHE_MASK)).
|
* In order to avoid an extra test inside the loop,
|
* In order to avoid an extra test inside the loop,
|
* we bump pglen here, and just subtract PAGE_CACHE_SIZE... */
|
* we bump pglen here, and just subtract PAGE_CACHE_SIZE... */
|
pglen += base & ~PAGE_CACHE_MASK;
|
pglen += base & ~PAGE_CACHE_MASK;
|
}
|
}
|
/*
|
/*
|
* In case we could only do a partial xdr_kmap, all remaining iovecs
|
* In case we could only do a partial xdr_kmap, all remaining iovecs
|
* refer to pages. Otherwise we detect the end through pglen.
|
* refer to pages. Otherwise we detect the end through pglen.
|
*/
|
*/
|
for (; niov; niov--) {
|
for (; niov; niov--) {
|
flush_dcache_page(*ppage);
|
flush_dcache_page(*ppage);
|
kunmap(*ppage);
|
kunmap(*ppage);
|
if (pglen <= PAGE_CACHE_SIZE)
|
if (pglen <= PAGE_CACHE_SIZE)
|
break;
|
break;
|
pglen -= PAGE_CACHE_SIZE;
|
pglen -= PAGE_CACHE_SIZE;
|
ppage++;
|
ppage++;
|
}
|
}
|
}
|
}
|
|
|
void
|
void
|
xdr_partial_copy_from_skb(struct xdr_buf *xdr, unsigned int base,
|
xdr_partial_copy_from_skb(struct xdr_buf *xdr, unsigned int base,
|
skb_reader_t *desc,
|
skb_reader_t *desc,
|
skb_read_actor_t copy_actor)
|
skb_read_actor_t copy_actor)
|
{
|
{
|
struct page **ppage = xdr->pages;
|
struct page **ppage = xdr->pages;
|
unsigned int len, pglen = xdr->page_len;
|
unsigned int len, pglen = xdr->page_len;
|
int ret;
|
int ret;
|
|
|
len = xdr->head[0].iov_len;
|
len = xdr->head[0].iov_len;
|
if (base < len) {
|
if (base < len) {
|
len -= base;
|
len -= base;
|
ret = copy_actor(desc, (char *)xdr->head[0].iov_base + base, len);
|
ret = copy_actor(desc, (char *)xdr->head[0].iov_base + base, len);
|
if (ret != len || !desc->count)
|
if (ret != len || !desc->count)
|
return;
|
return;
|
base = 0;
|
base = 0;
|
} else
|
} else
|
base -= len;
|
base -= len;
|
|
|
if (pglen == 0)
|
if (pglen == 0)
|
goto copy_tail;
|
goto copy_tail;
|
if (base >= pglen) {
|
if (base >= pglen) {
|
base -= pglen;
|
base -= pglen;
|
goto copy_tail;
|
goto copy_tail;
|
}
|
}
|
if (base || xdr->page_base) {
|
if (base || xdr->page_base) {
|
pglen -= base;
|
pglen -= base;
|
base += xdr->page_base;
|
base += xdr->page_base;
|
ppage += base >> PAGE_CACHE_SHIFT;
|
ppage += base >> PAGE_CACHE_SHIFT;
|
base &= ~PAGE_CACHE_MASK;
|
base &= ~PAGE_CACHE_MASK;
|
}
|
}
|
do {
|
do {
|
char *kaddr;
|
char *kaddr;
|
|
|
len = PAGE_CACHE_SIZE;
|
len = PAGE_CACHE_SIZE;
|
kaddr = kmap_atomic(*ppage, KM_SKB_SUNRPC_DATA);
|
kaddr = kmap_atomic(*ppage, KM_SKB_SUNRPC_DATA);
|
if (base) {
|
if (base) {
|
len -= base;
|
len -= base;
|
if (pglen < len)
|
if (pglen < len)
|
len = pglen;
|
len = pglen;
|
ret = copy_actor(desc, kaddr + base, len);
|
ret = copy_actor(desc, kaddr + base, len);
|
base = 0;
|
base = 0;
|
} else {
|
} else {
|
if (pglen < len)
|
if (pglen < len)
|
len = pglen;
|
len = pglen;
|
ret = copy_actor(desc, kaddr, len);
|
ret = copy_actor(desc, kaddr, len);
|
}
|
}
|
kunmap_atomic(kaddr, KM_SKB_SUNRPC_DATA);
|
kunmap_atomic(kaddr, KM_SKB_SUNRPC_DATA);
|
if (ret != len || !desc->count)
|
if (ret != len || !desc->count)
|
return;
|
return;
|
ppage++;
|
ppage++;
|
} while ((pglen -= len) != 0);
|
} while ((pglen -= len) != 0);
|
copy_tail:
|
copy_tail:
|
len = xdr->tail[0].iov_len;
|
len = xdr->tail[0].iov_len;
|
if (len)
|
if (len)
|
copy_actor(desc, (char *)xdr->tail[0].iov_base + base, len);
|
copy_actor(desc, (char *)xdr->tail[0].iov_base + base, len);
|
}
|
}
|
|
|
/*
|
/*
|
* Helper routines for doing 'memmove' like operations on a struct xdr_buf
|
* Helper routines for doing 'memmove' like operations on a struct xdr_buf
|
*
|
*
|
* _shift_data_right_pages
|
* _shift_data_right_pages
|
* @pages: vector of pages containing both the source and dest memory area.
|
* @pages: vector of pages containing both the source and dest memory area.
|
* @pgto_base: page vector address of destination
|
* @pgto_base: page vector address of destination
|
* @pgfrom_base: page vector address of source
|
* @pgfrom_base: page vector address of source
|
* @len: number of bytes to copy
|
* @len: number of bytes to copy
|
*
|
*
|
* Note: the addresses pgto_base and pgfrom_base are both calculated in
|
* Note: the addresses pgto_base and pgfrom_base are both calculated in
|
* the same way:
|
* the same way:
|
* if a memory area starts at byte 'base' in page 'pages[i]',
|
* if a memory area starts at byte 'base' in page 'pages[i]',
|
* then its address is given as (i << PAGE_CACHE_SHIFT) + base
|
* then its address is given as (i << PAGE_CACHE_SHIFT) + base
|
* Also note: pgfrom_base must be < pgto_base, but the memory areas
|
* Also note: pgfrom_base must be < pgto_base, but the memory areas
|
* they point to may overlap.
|
* they point to may overlap.
|
*/
|
*/
|
static void
|
static void
|
_shift_data_right_pages(struct page **pages, size_t pgto_base,
|
_shift_data_right_pages(struct page **pages, size_t pgto_base,
|
size_t pgfrom_base, size_t len)
|
size_t pgfrom_base, size_t len)
|
{
|
{
|
struct page **pgfrom, **pgto;
|
struct page **pgfrom, **pgto;
|
char *vfrom, *vto;
|
char *vfrom, *vto;
|
size_t copy;
|
size_t copy;
|
|
|
BUG_ON(pgto_base <= pgfrom_base);
|
BUG_ON(pgto_base <= pgfrom_base);
|
|
|
pgto_base += len;
|
pgto_base += len;
|
pgfrom_base += len;
|
pgfrom_base += len;
|
|
|
pgto = pages + (pgto_base >> PAGE_CACHE_SHIFT);
|
pgto = pages + (pgto_base >> PAGE_CACHE_SHIFT);
|
pgfrom = pages + (pgfrom_base >> PAGE_CACHE_SHIFT);
|
pgfrom = pages + (pgfrom_base >> PAGE_CACHE_SHIFT);
|
|
|
pgto_base &= ~PAGE_CACHE_MASK;
|
pgto_base &= ~PAGE_CACHE_MASK;
|
pgfrom_base &= ~PAGE_CACHE_MASK;
|
pgfrom_base &= ~PAGE_CACHE_MASK;
|
|
|
do {
|
do {
|
/* Are any pointers crossing a page boundary? */
|
/* Are any pointers crossing a page boundary? */
|
if (pgto_base == 0) {
|
if (pgto_base == 0) {
|
pgto_base = PAGE_CACHE_SIZE;
|
pgto_base = PAGE_CACHE_SIZE;
|
pgto--;
|
pgto--;
|
}
|
}
|
if (pgfrom_base == 0) {
|
if (pgfrom_base == 0) {
|
pgfrom_base = PAGE_CACHE_SIZE;
|
pgfrom_base = PAGE_CACHE_SIZE;
|
pgfrom--;
|
pgfrom--;
|
}
|
}
|
|
|
copy = len;
|
copy = len;
|
if (copy > pgto_base)
|
if (copy > pgto_base)
|
copy = pgto_base;
|
copy = pgto_base;
|
if (copy > pgfrom_base)
|
if (copy > pgfrom_base)
|
copy = pgfrom_base;
|
copy = pgfrom_base;
|
pgto_base -= copy;
|
pgto_base -= copy;
|
pgfrom_base -= copy;
|
pgfrom_base -= copy;
|
|
|
vto = kmap_atomic(*pgto, KM_USER0);
|
vto = kmap_atomic(*pgto, KM_USER0);
|
vfrom = kmap_atomic(*pgfrom, KM_USER1);
|
vfrom = kmap_atomic(*pgfrom, KM_USER1);
|
memmove(vto + pgto_base, vfrom + pgfrom_base, copy);
|
memmove(vto + pgto_base, vfrom + pgfrom_base, copy);
|
kunmap_atomic(vfrom, KM_USER1);
|
kunmap_atomic(vfrom, KM_USER1);
|
kunmap_atomic(vto, KM_USER0);
|
kunmap_atomic(vto, KM_USER0);
|
|
|
} while ((len -= copy) != 0);
|
} while ((len -= copy) != 0);
|
}
|
}
|
|
|
/*
|
/*
|
* _copy_to_pages
|
* _copy_to_pages
|
* @pages: array of pages
|
* @pages: array of pages
|
* @pgbase: page vector address of destination
|
* @pgbase: page vector address of destination
|
* @p: pointer to source data
|
* @p: pointer to source data
|
* @len: length
|
* @len: length
|
*
|
*
|
* Copies data from an arbitrary memory location into an array of pages
|
* Copies data from an arbitrary memory location into an array of pages
|
* The copy is assumed to be non-overlapping.
|
* The copy is assumed to be non-overlapping.
|
*/
|
*/
|
static void
|
static void
|
_copy_to_pages(struct page **pages, size_t pgbase, const char *p, size_t len)
|
_copy_to_pages(struct page **pages, size_t pgbase, const char *p, size_t len)
|
{
|
{
|
struct page **pgto;
|
struct page **pgto;
|
char *vto;
|
char *vto;
|
size_t copy;
|
size_t copy;
|
|
|
pgto = pages + (pgbase >> PAGE_CACHE_SHIFT);
|
pgto = pages + (pgbase >> PAGE_CACHE_SHIFT);
|
pgbase &= ~PAGE_CACHE_MASK;
|
pgbase &= ~PAGE_CACHE_MASK;
|
|
|
do {
|
do {
|
copy = PAGE_CACHE_SIZE - pgbase;
|
copy = PAGE_CACHE_SIZE - pgbase;
|
if (copy > len)
|
if (copy > len)
|
copy = len;
|
copy = len;
|
|
|
vto = kmap_atomic(*pgto, KM_USER0);
|
vto = kmap_atomic(*pgto, KM_USER0);
|
memcpy(vto + pgbase, p, copy);
|
memcpy(vto + pgbase, p, copy);
|
kunmap_atomic(vto, KM_USER0);
|
kunmap_atomic(vto, KM_USER0);
|
|
|
pgbase += copy;
|
pgbase += copy;
|
if (pgbase == PAGE_CACHE_SIZE) {
|
if (pgbase == PAGE_CACHE_SIZE) {
|
pgbase = 0;
|
pgbase = 0;
|
pgto++;
|
pgto++;
|
}
|
}
|
p += copy;
|
p += copy;
|
|
|
} while ((len -= copy) != 0);
|
} while ((len -= copy) != 0);
|
}
|
}
|
|
|
/*
|
/*
|
* _copy_from_pages
|
* _copy_from_pages
|
* @p: pointer to destination
|
* @p: pointer to destination
|
* @pages: array of pages
|
* @pages: array of pages
|
* @pgbase: offset of source data
|
* @pgbase: offset of source data
|
* @len: length
|
* @len: length
|
*
|
*
|
* Copies data into an arbitrary memory location from an array of pages
|
* Copies data into an arbitrary memory location from an array of pages
|
* The copy is assumed to be non-overlapping.
|
* The copy is assumed to be non-overlapping.
|
*/
|
*/
|
static void
|
static void
|
_copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
|
_copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
|
{
|
{
|
struct page **pgfrom;
|
struct page **pgfrom;
|
char *vfrom;
|
char *vfrom;
|
size_t copy;
|
size_t copy;
|
|
|
pgfrom = pages + (pgbase >> PAGE_CACHE_SHIFT);
|
pgfrom = pages + (pgbase >> PAGE_CACHE_SHIFT);
|
pgbase &= ~PAGE_CACHE_MASK;
|
pgbase &= ~PAGE_CACHE_MASK;
|
|
|
do {
|
do {
|
copy = PAGE_CACHE_SIZE - pgbase;
|
copy = PAGE_CACHE_SIZE - pgbase;
|
if (copy > len)
|
if (copy > len)
|
copy = len;
|
copy = len;
|
|
|
vfrom = kmap_atomic(*pgfrom, KM_USER0);
|
vfrom = kmap_atomic(*pgfrom, KM_USER0);
|
memcpy(p, vfrom + pgbase, copy);
|
memcpy(p, vfrom + pgbase, copy);
|
kunmap_atomic(vfrom, KM_USER0);
|
kunmap_atomic(vfrom, KM_USER0);
|
|
|
pgbase += copy;
|
pgbase += copy;
|
if (pgbase == PAGE_CACHE_SIZE) {
|
if (pgbase == PAGE_CACHE_SIZE) {
|
pgbase = 0;
|
pgbase = 0;
|
pgfrom++;
|
pgfrom++;
|
}
|
}
|
p += copy;
|
p += copy;
|
|
|
} while ((len -= copy) != 0);
|
} while ((len -= copy) != 0);
|
}
|
}
|
|
|
/*
|
/*
|
* xdr_shrink_bufhead
|
* xdr_shrink_bufhead
|
* @buf: xdr_buf
|
* @buf: xdr_buf
|
* @len: bytes to remove from buf->head[0]
|
* @len: bytes to remove from buf->head[0]
|
*
|
*
|
* Shrinks XDR buffer's header iovec buf->head[0] by
|
* Shrinks XDR buffer's header iovec buf->head[0] by
|
* 'len' bytes. The extra data is not lost, but is instead
|
* 'len' bytes. The extra data is not lost, but is instead
|
* moved into the inlined pages and/or the tail.
|
* moved into the inlined pages and/or the tail.
|
*/
|
*/
|
void
|
void
|
xdr_shrink_bufhead(struct xdr_buf *buf, size_t len)
|
xdr_shrink_bufhead(struct xdr_buf *buf, size_t len)
|
{
|
{
|
struct iovec *head, *tail;
|
struct iovec *head, *tail;
|
size_t copy, offs;
|
size_t copy, offs;
|
unsigned int pglen = buf->page_len;
|
unsigned int pglen = buf->page_len;
|
|
|
tail = buf->tail;
|
tail = buf->tail;
|
head = buf->head;
|
head = buf->head;
|
BUG_ON (len > head->iov_len);
|
BUG_ON (len > head->iov_len);
|
|
|
/* Shift the tail first */
|
/* Shift the tail first */
|
if (tail->iov_len != 0) {
|
if (tail->iov_len != 0) {
|
if (tail->iov_len > len) {
|
if (tail->iov_len > len) {
|
copy = tail->iov_len - len;
|
copy = tail->iov_len - len;
|
memmove((char *)tail->iov_base + len,
|
memmove((char *)tail->iov_base + len,
|
tail->iov_base, copy);
|
tail->iov_base, copy);
|
}
|
}
|
/* Copy from the inlined pages into the tail */
|
/* Copy from the inlined pages into the tail */
|
copy = len;
|
copy = len;
|
if (copy > pglen)
|
if (copy > pglen)
|
copy = pglen;
|
copy = pglen;
|
offs = len - copy;
|
offs = len - copy;
|
if (offs >= tail->iov_len)
|
if (offs >= tail->iov_len)
|
copy = 0;
|
copy = 0;
|
else if (copy > tail->iov_len - offs)
|
else if (copy > tail->iov_len - offs)
|
copy = tail->iov_len - offs;
|
copy = tail->iov_len - offs;
|
if (copy != 0)
|
if (copy != 0)
|
_copy_from_pages((char *)tail->iov_base + offs,
|
_copy_from_pages((char *)tail->iov_base + offs,
|
buf->pages,
|
buf->pages,
|
buf->page_base + pglen + offs - len,
|
buf->page_base + pglen + offs - len,
|
copy);
|
copy);
|
/* Do we also need to copy data from the head into the tail ? */
|
/* Do we also need to copy data from the head into the tail ? */
|
if (len > pglen) {
|
if (len > pglen) {
|
offs = copy = len - pglen;
|
offs = copy = len - pglen;
|
if (copy > tail->iov_len)
|
if (copy > tail->iov_len)
|
copy = tail->iov_len;
|
copy = tail->iov_len;
|
memcpy(tail->iov_base,
|
memcpy(tail->iov_base,
|
(char *)head->iov_base +
|
(char *)head->iov_base +
|
head->iov_len - offs,
|
head->iov_len - offs,
|
copy);
|
copy);
|
}
|
}
|
}
|
}
|
/* Now handle pages */
|
/* Now handle pages */
|
if (pglen != 0) {
|
if (pglen != 0) {
|
if (pglen > len)
|
if (pglen > len)
|
_shift_data_right_pages(buf->pages,
|
_shift_data_right_pages(buf->pages,
|
buf->page_base + len,
|
buf->page_base + len,
|
buf->page_base,
|
buf->page_base,
|
pglen - len);
|
pglen - len);
|
copy = len;
|
copy = len;
|
if (len > pglen)
|
if (len > pglen)
|
copy = pglen;
|
copy = pglen;
|
_copy_to_pages(buf->pages, buf->page_base,
|
_copy_to_pages(buf->pages, buf->page_base,
|
(char *)head->iov_base + head->iov_len - len,
|
(char *)head->iov_base + head->iov_len - len,
|
copy);
|
copy);
|
}
|
}
|
head->iov_len -= len;
|
head->iov_len -= len;
|
buf->len -= len;
|
buf->len -= len;
|
}
|
}
|
|
|
void
|
void
|
xdr_shift_buf(struct xdr_buf *buf, size_t len)
|
xdr_shift_buf(struct xdr_buf *buf, size_t len)
|
{
|
{
|
xdr_shrink_bufhead(buf, len);
|
xdr_shrink_bufhead(buf, len);
|
}
|
}
|
|
|