#include <linux/namei.h>
 #include <linux/mount.h>
 #include <linux/sched.h>
-#include <linux/vmalloc.h>
 #include <linux/kmemleak.h>
 
 #include "delegation.h"
 /* Perform conversion from xdr to cache array */
 static
 int nfs_readdir_page_filler(nfs_readdir_descriptor_t *desc, struct nfs_entry *entry,
-                               void *xdr_page, struct page *page, unsigned int buflen)
+                               struct page **xdr_pages, struct page *page, unsigned int buflen)
 {
        struct xdr_stream stream;
-       struct xdr_buf buf;
-       __be32 *ptr = xdr_page;
+       struct xdr_buf buf = {
+               .pages = xdr_pages,
+               .page_len = buflen,
+               .buflen = buflen,
+               .len = buflen,
+       };
+       struct page *scratch;
        struct nfs_cache_array *array;
        unsigned int count = 0;
        int status;
 
-       buf.head->iov_base = xdr_page;
-       buf.head->iov_len = buflen;
-       buf.tail->iov_len = 0;
-       buf.page_base = 0;
-       buf.page_len = 0;
-       buf.buflen = buf.head->iov_len;
-       buf.len = buf.head->iov_len;
-
-       xdr_init_decode(&stream, &buf, ptr);
+       scratch = alloc_page(GFP_KERNEL);
+       if (scratch == NULL)
+               return -ENOMEM;
 
+       xdr_init_decode(&stream, &buf, NULL);
+       xdr_set_scratch_buffer(&stream, page_address(scratch), PAGE_SIZE);
 
        do {
                status = xdr_decode(desc, entry, &stream);
                } else
                        status = PTR_ERR(array);
        }
+
+       put_page(scratch);
        return status;
 }
 
 void nfs_readdir_free_large_page(void *ptr, struct page **pages,
                unsigned int npages)
 {
-       vm_unmap_ram(ptr, npages);
        nfs_readdir_free_pagearray(pages, npages);
 }
 
  * to nfs_readdir_free_large_page
  */
 static
-void *nfs_readdir_large_page(struct page **pages, unsigned int npages)
+int nfs_readdir_large_page(struct page **pages, unsigned int npages)
 {
-       void *ptr;
        unsigned int i;
 
        for (i = 0; i < npages; i++) {
                        goto out_freepages;
                pages[i] = page;
        }
+       return 0;
 
-       ptr = vm_map_ram(pages, npages, 0, PAGE_KERNEL);
-       if (!IS_ERR_OR_NULL(ptr))
-               return ptr;
 out_freepages:
        nfs_readdir_free_pagearray(pages, i);
-       return NULL;
+       return -ENOMEM;
 }
 
 static
        memset(array, 0, sizeof(struct nfs_cache_array));
        array->eof_index = -1;
 
-       pages_ptr = nfs_readdir_large_page(pages, array_size);
-       if (!pages_ptr)
+       status = nfs_readdir_large_page(pages, array_size);
+       if (status < 0)
                goto out_release_array;
        do {
                unsigned int pglen;
                if (status < 0)
                        break;
                pglen = status;
-               status = nfs_readdir_page_filler(desc, &entry, pages_ptr, page, pglen);
+               status = nfs_readdir_page_filler(desc, &entry, pages, page, pglen);
                if (status < 0) {
                        if (status == -ENOSPC)
                                status = 0;
 
 }
 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
  */
 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
  *
  */
 __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);
 
  */
 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);