NFS: Clean up and fix page zeroing when we have short reads
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Thu, 25 May 2006 05:40:44 +0000 (01:40 -0400)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Fri, 9 Jun 2006 13:34:03 +0000 (09:34 -0400)
The code that is supposed to zero the uninitialised partial pages when the
server returns a short read is currently broken: it looks at the nfs_page
wb_pgbase and wb_bytes fields instead of the equivalent nfs_read_data
values when deciding where to start truncating the page.

Also ensure that we are more careful about setting PG_uptodate
before retrying a short read: the retry will change the nfs_read_data
args.pgbase and args.count.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/read.c

index 624ca7146b6b590d6ad6e25233250c4829fdac9a..4b5f58da565083a229cabb0021492671de1cb663 100644 (file)
@@ -104,6 +104,28 @@ int nfs_return_empty_page(struct page *page)
        return 0;
 }
 
+static void nfs_readpage_truncate_uninitialised_page(struct nfs_read_data *data)
+{
+       unsigned int remainder = data->args.count - data->res.count;
+       unsigned int base = data->args.pgbase + data->res.count;
+       unsigned int pglen;
+       struct page **pages;
+
+       if (data->res.eof == 0 || remainder == 0)
+               return;
+       /*
+        * Note: "remainder" can never be negative, since we check for
+        *      this in the XDR code.
+        */
+       pages = &data->args.pages[base >> PAGE_CACHE_SHIFT];
+       base &= ~PAGE_CACHE_MASK;
+       pglen = PAGE_CACHE_SIZE - base;
+       if (pglen < remainder)
+               memclear_highpage_flush(*pages, base, pglen);
+       else
+               memclear_highpage_flush(*pages, base, remainder);
+}
+
 /*
  * Read a page synchronously.
  */
@@ -177,11 +199,9 @@ static int nfs_readpage_sync(struct nfs_open_context *ctx, struct inode *inode,
        NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ATIME;
        spin_unlock(&inode->i_lock);
 
-       if (count)
-               memclear_highpage_flush(page, rdata->args.pgbase, count);
-       SetPageUptodate(page);
-       if (PageError(page))
-               ClearPageError(page);
+       nfs_readpage_truncate_uninitialised_page(rdata);
+       if (rdata->res.eof || rdata->res.count == rdata->args.count)
+               SetPageUptodate(page);
        result = 0;
 
 io_error:
@@ -436,20 +456,12 @@ static void nfs_readpage_result_partial(struct rpc_task *task, void *calldata)
        struct nfs_page *req = data->req;
        struct page *page = req->wb_page;
  
+       if (likely(task->tk_status >= 0))
+               nfs_readpage_truncate_uninitialised_page(data);
+       else
+               SetPageError(page);
        if (nfs_readpage_result(task, data) != 0)
                return;
-       if (task->tk_status >= 0) {
-               unsigned int request = data->args.count;
-               unsigned int result = data->res.count;
-
-               if (result < request) {
-                       memclear_highpage_flush(page,
-                                               data->args.pgbase + result,
-                                               request - result);
-               }
-       } else
-               SetPageError(page);
-
        if (atomic_dec_and_test(&req->wb_complete)) {
                if (!PageError(page))
                        SetPageUptodate(page);
@@ -462,6 +474,40 @@ static const struct rpc_call_ops nfs_read_partial_ops = {
        .rpc_release = nfs_readdata_release,
 };
 
+static void nfs_readpage_set_pages_uptodate(struct nfs_read_data *data)
+{
+       unsigned int count = data->res.count;
+       unsigned int base = data->args.pgbase;
+       struct page **pages;
+
+       if (unlikely(count == 0))
+               return;
+       pages = &data->args.pages[base >> PAGE_CACHE_SHIFT];
+       base &= ~PAGE_CACHE_MASK;
+       count += base;
+       for (;count >= PAGE_CACHE_SIZE; count -= PAGE_CACHE_SIZE, pages++)
+               SetPageUptodate(*pages);
+       /*
+        * Was this an eof or a short read? If the latter, don't mark the page
+        * as uptodate yet.
+        */
+       if (count > 0 && (data->res.eof || data->args.count == data->res.count))
+               SetPageUptodate(*pages);
+}
+
+static void nfs_readpage_set_pages_error(struct nfs_read_data *data)
+{
+       unsigned int count = data->args.count;
+       unsigned int base = data->args.pgbase;
+       struct page **pages;
+
+       pages = &data->args.pages[base >> PAGE_CACHE_SHIFT];
+       base &= ~PAGE_CACHE_MASK;
+       count += base;
+       for (;count >= PAGE_CACHE_SIZE; count -= PAGE_CACHE_SIZE, pages++)
+               SetPageError(*pages);
+}
+
 /*
  * This is the callback from RPC telling us whether a reply was
  * received or some error occurred (timeout or socket shutdown).
@@ -469,27 +515,24 @@ static const struct rpc_call_ops nfs_read_partial_ops = {
 static void nfs_readpage_result_full(struct rpc_task *task, void *calldata)
 {
        struct nfs_read_data *data = calldata;
-       unsigned int count = data->res.count;
 
+       /*
+        * Note: nfs_readpage_result may change the values of
+        * data->args. In the multi-page case, we therefore need
+        * to ensure that we call the next nfs_readpage_set_page_uptodate()
+        * first in the multi-page case.
+        */
+       if (likely(task->tk_status >= 0)) {
+               nfs_readpage_truncate_uninitialised_page(data);
+               nfs_readpage_set_pages_uptodate(data);
+       } else
+               nfs_readpage_set_pages_error(data);
        if (nfs_readpage_result(task, data) != 0)
                return;
        while (!list_empty(&data->pages)) {
                struct nfs_page *req = nfs_list_entry(data->pages.next);
-               struct page *page = req->wb_page;
-               nfs_list_remove_request(req);
 
-               if (task->tk_status >= 0) {
-                       if (count < PAGE_CACHE_SIZE) {
-                               if (count < req->wb_bytes)
-                                       memclear_highpage_flush(page,
-                                                       req->wb_pgbase + count,
-                                                       req->wb_bytes - count);
-                               count = 0;
-                       } else
-                               count -= PAGE_CACHE_SIZE;
-                       SetPageUptodate(page);
-               } else
-                       SetPageError(page);
+               nfs_list_remove_request(req);
                nfs_readpage_release(req);
        }
 }