Merge git://git.kernel.org/pub/scm/linux/kernel/git/bunk/trivial
[safe/jmp/linux-2.6] / fs / nfs / write.c
index 6f7a4af..bca5734 100644 (file)
@@ -46,7 +46,6 @@
  * Copyright (C) 1996, 1997, Olaf Kirch <okir@monad.swb.de>
  */
 
-#include <linux/config.h>
 #include <linux/types.h>
 #include <linux/slab.h>
 #include <linux/mm.h>
@@ -63,6 +62,7 @@
 #include <linux/smp_lock.h>
 
 #include "delegation.h"
+#include "iostat.h"
 
 #define NFSDBG_FACILITY                NFSDBG_PAGECACHE
 
@@ -76,37 +76,76 @@ static struct nfs_page * nfs_update_request(struct nfs_open_context*,
                                            struct inode *,
                                            struct page *,
                                            unsigned int, unsigned int);
-static void nfs_writeback_done_partial(struct nfs_write_data *, int);
-static void nfs_writeback_done_full(struct nfs_write_data *, int);
 static int nfs_wait_on_write_congestion(struct address_space *, int);
 static int nfs_wait_on_requests(struct inode *, unsigned long, unsigned int);
 static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
                           unsigned int npages, int how);
+static const struct rpc_call_ops nfs_write_partial_ops;
+static const struct rpc_call_ops nfs_write_full_ops;
+static const struct rpc_call_ops nfs_commit_ops;
 
 static kmem_cache_t *nfs_wdata_cachep;
-mempool_t *nfs_wdata_mempool;
+static mempool_t *nfs_wdata_mempool;
 static mempool_t *nfs_commit_mempool;
 
 static DECLARE_WAIT_QUEUE_HEAD(nfs_write_congestion);
 
-static inline struct nfs_write_data *nfs_commit_alloc(void)
+struct nfs_write_data *nfs_commit_alloc(unsigned int pagecount)
 {
        struct nfs_write_data *p = mempool_alloc(nfs_commit_mempool, SLAB_NOFS);
+
        if (p) {
                memset(p, 0, sizeof(*p));
                INIT_LIST_HEAD(&p->pages);
+               if (pagecount <= ARRAY_SIZE(p->page_array))
+                       p->pagevec = p->page_array;
+               else {
+                       p->pagevec = kcalloc(pagecount, sizeof(struct page *), GFP_NOFS);
+                       if (!p->pagevec) {
+                               mempool_free(p, nfs_commit_mempool);
+                               p = NULL;
+                       }
+               }
        }
        return p;
 }
 
-static inline void nfs_commit_free(struct nfs_write_data *p)
+void nfs_commit_free(struct nfs_write_data *p)
 {
+       if (p && (p->pagevec != &p->page_array[0]))
+               kfree(p->pagevec);
        mempool_free(p, nfs_commit_mempool);
 }
 
-static void nfs_writedata_release(struct rpc_task *task)
+struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
+{
+       struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, SLAB_NOFS);
+
+       if (p) {
+               memset(p, 0, sizeof(*p));
+               INIT_LIST_HEAD(&p->pages);
+               if (pagecount <= ARRAY_SIZE(p->page_array))
+                       p->pagevec = p->page_array;
+               else {
+                       p->pagevec = kcalloc(pagecount, sizeof(struct page *), GFP_NOFS);
+                       if (!p->pagevec) {
+                               mempool_free(p, nfs_wdata_mempool);
+                               p = NULL;
+                       }
+               }
+       }
+       return p;
+}
+
+void nfs_writedata_free(struct nfs_write_data *p)
+{
+       if (p && (p->pagevec != &p->page_array[0]))
+               kfree(p->pagevec);
+       mempool_free(p, nfs_wdata_mempool);
+}
+
+void nfs_writedata_release(void *wdata)
 {
-       struct nfs_write_data   *wdata = (struct nfs_write_data *)task->tk_calldata;
        nfs_writedata_free(wdata);
 }
 
@@ -122,6 +161,7 @@ static void nfs_grow_file(struct page *page, unsigned int offset, unsigned int c
        end = ((loff_t)page->index << PAGE_CACHE_SHIFT) + ((loff_t)offset+count);
        if (i_size >= end)
                return;
+       nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
        i_size_write(inode, end);
 }
 
@@ -168,7 +208,7 @@ static int nfs_writepage_sync(struct nfs_open_context *ctx, struct inode *inode,
        int             result, written = 0;
        struct nfs_write_data *wdata;
 
-       wdata = nfs_writedata_alloc();
+       wdata = nfs_writedata_alloc(1);
        if (!wdata)
                return -ENOMEM;
 
@@ -189,6 +229,7 @@ static int nfs_writepage_sync(struct nfs_open_context *ctx, struct inode *inode,
                (long long)NFS_FILEID(inode),
                count, (long long)(page_offset(page) + offset));
 
+       set_page_writeback(page);
        nfs_begin_data_update(inode);
        do {
                if (count < wsize)
@@ -210,6 +251,7 @@ static int nfs_writepage_sync(struct nfs_open_context *ctx, struct inode *inode,
                wdata->args.pgbase += result;
                written += result;
                count -= result;
+               nfs_add_stats(inode, NFSIOS_SERVERWRITTENBYTES, result);
        } while (count);
        /* Update file length */
        nfs_grow_file(page, offset, written);
@@ -220,7 +262,8 @@ static int nfs_writepage_sync(struct nfs_open_context *ctx, struct inode *inode,
                ClearPageError(page);
 
 io_error:
-       nfs_end_data_update_defer(inode);
+       nfs_end_data_update(inode);
+       end_page_writeback(page);
        nfs_writedata_free(wdata);
        return written ? written : result;
 }
@@ -230,19 +273,16 @@ static int nfs_writepage_async(struct nfs_open_context *ctx,
                unsigned int offset, unsigned int count)
 {
        struct nfs_page *req;
-       int             status;
 
        req = nfs_update_request(ctx, inode, page, offset, count);
-       status = (IS_ERR(req)) ? PTR_ERR(req) : 0;
-       if (status < 0)
-               goto out;
+       if (IS_ERR(req))
+               return PTR_ERR(req);
        /* Update file length */
        nfs_grow_file(page, offset, count);
        /* Set the PG_uptodate flag? */
        nfs_mark_uptodate(page, offset, count);
        nfs_unlock_request(req);
- out:
-       return status;
+       return 0;
 }
 
 static int wb_priority(struct writeback_control *wbc)
@@ -268,6 +308,9 @@ int nfs_writepage(struct page *page, struct writeback_control *wbc)
        int priority = wb_priority(wbc);
        int err;
 
+       nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGE);
+       nfs_add_stats(inode, NFSIOS_WRITEPAGES, 1);
+
        /*
         * Note: We need to ensure that we have a reference to the inode
         *       if we are to do asynchronous writes. If not, waiting
@@ -294,7 +337,7 @@ int nfs_writepage(struct page *page, struct writeback_control *wbc)
        if (page->index >= end_index+1 || !offset)
                goto out;
 do_it:
-       ctx = nfs_find_open_context(inode, FMODE_WRITE);
+       ctx = nfs_find_open_context(inode, NULL, FMODE_WRITE);
        if (ctx == NULL) {
                err = -EBADF;
                goto out;
@@ -302,11 +345,8 @@ do_it:
        lock_kernel();
        if (!IS_SYNC(inode) && inode_referenced) {
                err = nfs_writepage_async(ctx, inode, page, 0, offset);
-               if (err >= 0) {
-                       err = 0;
-                       if (wbc->for_reclaim)
-                               nfs_flush_inode(inode, 0, 0, FLUSH_STABLE);
-               }
+               if (!wbc->for_writepages)
+                       nfs_flush_inode(inode, 0, 0, wb_priority(wbc));
        } else {
                err = nfs_writepage_sync(ctx, inode, page, 0,
                                                offset, priority);
@@ -335,6 +375,8 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
        struct inode *inode = mapping->host;
        int err;
 
+       nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES);
+
        err = generic_writepages(mapping, wbc);
        if (err)
                return err;
@@ -346,13 +388,14 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
        err = nfs_flush_inode(inode, 0, 0, wb_priority(wbc));
        if (err < 0)
                goto out;
+       nfs_add_stats(inode, NFSIOS_WRITEPAGES, err);
        wbc->nr_to_write -= err;
        if (!wbc->nonblocking && wbc->sync_mode == WB_SYNC_ALL) {
                err = nfs_wait_on_requests(inode, 0, 0);
                if (err < 0)
                        goto out;
        }
-       err = nfs_commit_inode(inode, 0, 0, wb_priority(wbc));
+       err = nfs_commit_inode(inode, wb_priority(wbc));
        if (err > 0) {
                wbc->nr_to_write -= err;
                err = 0;
@@ -381,6 +424,7 @@ static int nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
                if (nfs_have_delegation(inode, FMODE_WRITE))
                        nfsi->change_attr++;
        }
+       SetPagePrivate(req->wb_page);
        nfsi->npages++;
        atomic_inc(&req->wb_count);
        return 0;
@@ -397,11 +441,12 @@ static void nfs_inode_remove_request(struct nfs_page *req)
        BUG_ON (!NFS_WBACK_BUSY(req));
 
        spin_lock(&nfsi->req_lock);
+       ClearPagePrivate(req->wb_page);
        radix_tree_delete(&nfsi->nfs_page_tree, req->wb_index);
        nfsi->npages--;
        if (!nfsi->npages) {
                spin_unlock(&nfsi->req_lock);
-               nfs_end_data_update_defer(inode);
+               nfs_end_data_update(inode);
                iput(inode);
        } else
                spin_unlock(&nfsi->req_lock);
@@ -446,10 +491,12 @@ nfs_mark_request_dirty(struct nfs_page *req)
        struct nfs_inode *nfsi = NFS_I(inode);
 
        spin_lock(&nfsi->req_lock);
+       radix_tree_tag_set(&nfsi->nfs_page_tree,
+                       req->wb_index, NFS_PAGE_TAG_DIRTY);
        nfs_list_add_request(req, &nfsi->dirty);
        nfsi->ndirty++;
        spin_unlock(&nfsi->req_lock);
-       inc_page_state(nr_dirty);
+       inc_zone_page_state(req->wb_page, NR_FILE_DIRTY);
        mark_inode_dirty(inode);
 }
 
@@ -477,7 +524,7 @@ nfs_mark_request_commit(struct nfs_page *req)
        nfs_list_add_request(req, &nfsi->commit);
        nfsi->ncommit++;
        spin_unlock(&nfsi->req_lock);
-       inc_page_state(nr_unstable);
+       inc_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
        mark_inode_dirty(inode);
 }
 #endif
@@ -487,8 +534,7 @@ nfs_mark_request_commit(struct nfs_page *req)
  *
  * Interruptible by signals only if mounted with intr flag.
  */
-static int
-nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int npages)
+static int nfs_wait_on_requests_locked(struct inode *inode, unsigned long idx_start, unsigned int npages)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_page *req;
@@ -501,29 +547,48 @@ nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int
        else
                idx_end = idx_start + npages - 1;
 
-       spin_lock(&nfsi->req_lock);
        next = idx_start;
-       while (radix_tree_gang_lookup(&nfsi->nfs_page_tree, (void **)&req, next, 1)) {
+       while (radix_tree_gang_lookup_tag(&nfsi->nfs_page_tree, (void **)&req, next, 1, NFS_PAGE_TAG_WRITEBACK)) {
                if (req->wb_index > idx_end)
                        break;
 
                next = req->wb_index + 1;
-               if (!NFS_WBACK_BUSY(req))
-                       continue;
+               BUG_ON(!NFS_WBACK_BUSY(req));
 
                atomic_inc(&req->wb_count);
                spin_unlock(&nfsi->req_lock);
                error = nfs_wait_on_request(req);
                nfs_release_request(req);
+               spin_lock(&nfsi->req_lock);
                if (error < 0)
                        return error;
-               spin_lock(&nfsi->req_lock);
                res++;
        }
-       spin_unlock(&nfsi->req_lock);
        return res;
 }
 
+static int nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int npages)
+{
+       struct nfs_inode *nfsi = NFS_I(inode);
+       int ret;
+
+       spin_lock(&nfsi->req_lock);
+       ret = nfs_wait_on_requests_locked(inode, idx_start, npages);
+       spin_unlock(&nfsi->req_lock);
+       return ret;
+}
+
+static void nfs_cancel_requests(struct list_head *head)
+{
+       struct nfs_page *req;
+       while(!list_empty(head)) {
+               req = nfs_list_entry(head->next);
+               nfs_list_remove_request(req);
+               nfs_inode_remove_request(req);
+               nfs_clear_page_writeback(req);
+       }
+}
+
 /*
  * nfs_scan_dirty - Scan an inode for dirty requests
  * @inode: NFS inode to scan
@@ -538,12 +603,14 @@ static int
 nfs_scan_dirty(struct inode *inode, struct list_head *dst, unsigned long idx_start, unsigned int npages)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
-       int     res;
-       res = nfs_scan_list(&nfsi->dirty, dst, idx_start, npages);
-       nfsi->ndirty -= res;
-       sub_page_state(nr_dirty,res);
-       if ((nfsi->ndirty == 0) != list_empty(&nfsi->dirty))
-               printk(KERN_ERR "NFS: desynchronized value of nfs_i.ndirty.\n");
+       int res = 0;
+
+       if (nfsi->ndirty != 0) {
+               res = nfs_scan_lock_dirty(nfsi, dst, idx_start, npages);
+               nfsi->ndirty -= res;
+               if ((nfsi->ndirty == 0) != list_empty(&nfsi->dirty))
+                       printk(KERN_ERR "NFS: desynchronized value of nfs_i.ndirty.\n");
+       }
        return res;
 }
 
@@ -562,13 +629,21 @@ static int
 nfs_scan_commit(struct inode *inode, struct list_head *dst, unsigned long idx_start, unsigned int npages)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
-       int     res;
-       res = nfs_scan_list(&nfsi->commit, dst, idx_start, npages);
-       nfsi->ncommit -= res;
-       if ((nfsi->ncommit == 0) != list_empty(&nfsi->commit))
-               printk(KERN_ERR "NFS: desynchronized value of nfs_i.ncommit.\n");
+       int res = 0;
+
+       if (nfsi->ncommit != 0) {
+               res = nfs_scan_list(nfsi, &nfsi->commit, dst, idx_start, npages);
+               nfsi->ncommit -= res;
+               if ((nfsi->ncommit == 0) != list_empty(&nfsi->commit))
+                       printk(KERN_ERR "NFS: desynchronized value of nfs_i.ncommit.\n");
+       }
        return res;
 }
+#else
+static inline int nfs_scan_commit(struct inode *inode, struct list_head *dst, unsigned long idx_start, unsigned int npages)
+{
+       return 0;
+}
 #endif
 
 static int nfs_wait_on_write_congestion(struct address_space *mapping, int intr)
@@ -581,6 +656,9 @@ static int nfs_wait_on_write_congestion(struct address_space *mapping, int intr)
 
        if (!bdi_write_congested(bdi))
                return 0;
+
+       nfs_inc_stats(mapping->host, NFSIOS_CONGESTIONWAIT);
+
        if (intr) {
                struct rpc_clnt *clnt = NFS_CLIENT(mapping->host);
                sigset_t oldset;
@@ -636,8 +714,11 @@ static struct nfs_page * nfs_update_request(struct nfs_open_context* ctx,
                                spin_unlock(&nfsi->req_lock);
                                error = nfs_wait_on_request(req);
                                nfs_release_request(req);
-                               if (error < 0)
+                               if (error < 0) {
+                                       if (new)
+                                               nfs_release_request(new);
                                        return ERR_PTR(error);
+                               }
                                continue;
                        }
                        spin_unlock(&nfsi->req_lock);
@@ -727,14 +808,16 @@ int nfs_updatepage(struct file *file, struct page *page,
                unsigned int offset, unsigned int count)
 {
        struct nfs_open_context *ctx = (struct nfs_open_context *)file->private_data;
-       struct dentry   *dentry = file->f_dentry;
        struct inode    *inode = page->mapping->host;
        struct nfs_page *req;
        int             status = 0;
 
+       nfs_inc_stats(inode, NFSIOS_VFSUPDATEPAGE);
+
        dprintk("NFS:      nfs_updatepage(%s/%s %d@%Ld)\n",
-               dentry->d_parent->d_name.name, dentry->d_name.name,
-               count, (long long)(page_offset(page) +offset));
+               file->f_dentry->d_parent->d_name.name,
+               file->f_dentry->d_name.name, count,
+               (long long)(page_offset(page) +offset));
 
        if (IS_SYNC(inode)) {
                status = nfs_writepage_sync(ctx, inode, page, offset, count, 0);
@@ -750,7 +833,7 @@ int nfs_updatepage(struct file *file, struct page *page,
         * is entirely in cache, it may be more efficient to avoid
         * fragmenting write requests.
         */
-       if (PageUptodate(page) && inode->i_flock == NULL) {
+       if (PageUptodate(page) && inode->i_flock == NULL && !(file->f_mode & O_SYNC)) {
                loff_t end_offs = i_size_read(inode) - 1;
                unsigned long end_index = end_offs >> PAGE_CACHE_SHIFT;
 
@@ -821,7 +904,7 @@ out:
 #else
        nfs_inode_remove_request(req);
 #endif
-       nfs_unlock_request(req);
+       nfs_clear_page_writeback(req);
 }
 
 static inline int flush_task_priority(int how)
@@ -840,11 +923,12 @@ static inline int flush_task_priority(int how)
  */
 static void nfs_write_rpcsetup(struct nfs_page *req,
                struct nfs_write_data *data,
+               const struct rpc_call_ops *call_ops,
                unsigned int count, unsigned int offset,
                int how)
 {
-       struct rpc_task         *task = &data->task;
        struct inode            *inode;
+       int flags;
 
        /* Set up the RPC argument and reply structs
         * NB: take care not to mess about with data->commit et al. */
@@ -863,17 +947,18 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
        data->res.fattr   = &data->fattr;
        data->res.count   = count;
        data->res.verf    = &data->verf;
+       nfs_fattr_init(&data->fattr);
 
+       /* Set up the initial task struct.  */
+       flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
+       rpc_init_task(&data->task, NFS_CLIENT(inode), flags, call_ops, data);
        NFS_PROTO(inode)->write_setup(data, how);
 
        data->task.tk_priority = flush_task_priority(how);
        data->task.tk_cookie = (unsigned long)inode;
-       data->task.tk_calldata = data;
-       /* Release requests */
-       data->task.tk_release = nfs_writedata_release;
 
        dprintk("NFS: %4d initiated write call (req %s/%Ld, %u bytes @ offset %Lu)\n",
-               task->tk_pid,
+               data->task.tk_pid,
                inode->i_sb->s_id,
                (long long)NFS_FILEID(inode),
                count,
@@ -896,7 +981,7 @@ static void nfs_execute_write(struct nfs_write_data *data)
  * Generate multiple small requests to write out a single
  * contiguous dirty area on one page.
  */
-static int nfs_flush_multi(struct list_head *head, struct inode *inode, int how)
+static int nfs_flush_multi(struct inode *inode, struct list_head *head, int how)
 {
        struct nfs_page *req = nfs_list_entry(head->next);
        struct page *page = req->wb_page;
@@ -910,7 +995,7 @@ static int nfs_flush_multi(struct list_head *head, struct inode *inode, int how)
 
        nbytes = req->wb_bytes;
        for (;;) {
-               data = nfs_writedata_alloc();
+               data = nfs_writedata_alloc(1);
                if (!data)
                        goto out_bad;
                list_add(&data->pages, &list);
@@ -922,7 +1007,7 @@ static int nfs_flush_multi(struct list_head *head, struct inode *inode, int how)
        atomic_set(&req->wb_complete, requests);
 
        ClearPageError(page);
-       SetPageWriteback(page);
+       set_page_writeback(page);
        offset = 0;
        nbytes = req->wb_bytes;
        do {
@@ -930,14 +1015,15 @@ static int nfs_flush_multi(struct list_head *head, struct inode *inode, int how)
                list_del_init(&data->pages);
 
                data->pagevec[0] = page;
-               data->complete = nfs_writeback_done_partial;
 
                if (nbytes > wsize) {
-                       nfs_write_rpcsetup(req, data, wsize, offset, how);
+                       nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
+                                       wsize, offset, how);
                        offset += wsize;
                        nbytes -= wsize;
                } else {
-                       nfs_write_rpcsetup(req, data, nbytes, offset, how);
+                       nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
+                                       nbytes, offset, how);
                        nbytes = 0;
                }
                nfs_execute_write(data);
@@ -952,7 +1038,7 @@ out_bad:
                nfs_writedata_free(data);
        }
        nfs_mark_request_dirty(req);
-       nfs_unlock_request(req);
+       nfs_clear_page_writeback(req);
        return -ENOMEM;
 }
 
@@ -964,17 +1050,14 @@ out_bad:
  * This is the case if nfs_updatepage detects a conflicting request
  * that has been written but not committed.
  */
-static int nfs_flush_one(struct list_head *head, struct inode *inode, int how)
+static int nfs_flush_one(struct inode *inode, struct list_head *head, int how)
 {
        struct nfs_page         *req;
        struct page             **pages;
        struct nfs_write_data   *data;
        unsigned int            count;
 
-       if (NFS_SERVER(inode)->wsize < PAGE_CACHE_SIZE)
-               return nfs_flush_multi(head, inode, how);
-
-       data = nfs_writedata_alloc();
+       data = nfs_writedata_alloc(NFS_SERVER(inode)->wpages);
        if (!data)
                goto out_bad;
 
@@ -985,15 +1068,14 @@ static int nfs_flush_one(struct list_head *head, struct inode *inode, int how)
                nfs_list_remove_request(req);
                nfs_list_add_request(req, &data->pages);
                ClearPageError(req->wb_page);
-               SetPageWriteback(req->wb_page);
+               set_page_writeback(req->wb_page);
                *pages++ = req->wb_page;
                count += req->wb_bytes;
        }
        req = nfs_list_entry(data->pages.next);
 
-       data->complete = nfs_writeback_done_full;
        /* Set up the argument struct */
-       nfs_write_rpcsetup(req, data, count, 0, how);
+       nfs_write_rpcsetup(req, data, &nfs_write_full_ops, count, 0, how);
 
        nfs_execute_write(data);
        return 0;
@@ -1002,34 +1084,42 @@ static int nfs_flush_one(struct list_head *head, struct inode *inode, int how)
                struct nfs_page *req = nfs_list_entry(head->next);
                nfs_list_remove_request(req);
                nfs_mark_request_dirty(req);
-               nfs_unlock_request(req);
+               nfs_clear_page_writeback(req);
        }
        return -ENOMEM;
 }
 
-static int
-nfs_flush_list(struct list_head *head, int wpages, int how)
+static int nfs_flush_list(struct inode *inode, struct list_head *head, int npages, int how)
 {
        LIST_HEAD(one_request);
-       struct nfs_page         *req;
-       int                     error = 0;
-       unsigned int            pages = 0;
+       int (*flush_one)(struct inode *, struct list_head *, int);
+       struct nfs_page *req;
+       int wpages = NFS_SERVER(inode)->wpages;
+       int wsize = NFS_SERVER(inode)->wsize;
+       int error;
 
-       while (!list_empty(head)) {
-               pages += nfs_coalesce_requests(head, &one_request, wpages);
+       flush_one = nfs_flush_one;
+       if (wsize < PAGE_CACHE_SIZE)
+               flush_one = nfs_flush_multi;
+       /* For single writes, FLUSH_STABLE is more efficient */
+       if (npages <= wpages && npages == NFS_I(inode)->npages
+                       && nfs_list_entry(head->next)->wb_bytes <= wsize)
+               how |= FLUSH_STABLE;
+
+       do {
+               nfs_coalesce_requests(head, &one_request, wpages);
                req = nfs_list_entry(one_request.next);
-               error = nfs_flush_one(&one_request, req->wb_context->dentry->d_inode, how);
+               error = flush_one(inode, &one_request, how);
                if (error < 0)
-                       break;
-       }
-       if (error >= 0)
-               return pages;
-
+                       goto out_err;
+       } while (!list_empty(head));
+       return 0;
+out_err:
        while (!list_empty(head)) {
                req = nfs_list_entry(head->next);
                nfs_list_remove_request(req);
                nfs_mark_request_dirty(req);
-               nfs_unlock_request(req);
+               nfs_clear_page_writeback(req);
        }
        return error;
 }
@@ -1037,8 +1127,9 @@ nfs_flush_list(struct list_head *head, int wpages, int how)
 /*
  * Handle a write reply that flushed part of a page.
  */
-static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
+static void nfs_writeback_done_partial(struct rpc_task *task, void *calldata)
 {
+       struct nfs_write_data   *data = calldata;
        struct nfs_page         *req = data->req;
        struct page             *page = req->wb_page;
 
@@ -1048,11 +1139,14 @@ static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
                req->wb_bytes,
                (long long)req_offset(req));
 
-       if (status < 0) {
+       if (nfs_writeback_done(task, data) != 0)
+               return;
+
+       if (task->tk_status < 0) {
                ClearPageUptodate(page);
                SetPageError(page);
-               req->wb_context->error = status;
-               dprintk(", error = %d\n", status);
+               req->wb_context->error = task->tk_status;
+               dprintk(", error = %d\n", task->tk_status);
        } else {
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
                if (data->verf.committed < NFS_FILE_SYNC) {
@@ -1073,6 +1167,11 @@ static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
                nfs_writepage_release(req);
 }
 
+static const struct rpc_call_ops nfs_write_partial_ops = {
+       .rpc_call_done = nfs_writeback_done_partial,
+       .rpc_release = nfs_writedata_release,
+};
+
 /*
  * Handle a write reply that flushes a whole page.
  *
@@ -1080,11 +1179,15 @@ static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
  *       writebacks since the page->count is kept > 1 for as long
  *       as the page has a write request pending.
  */
-static void nfs_writeback_done_full(struct nfs_write_data *data, int status)
+static void nfs_writeback_done_full(struct rpc_task *task, void *calldata)
 {
+       struct nfs_write_data   *data = calldata;
        struct nfs_page         *req;
        struct page             *page;
 
+       if (nfs_writeback_done(task, data) != 0)
+               return;
+
        /* Update attributes as result of writeback. */
        while (!list_empty(&data->pages)) {
                req = nfs_list_entry(data->pages.next);
@@ -1097,13 +1200,13 @@ static void nfs_writeback_done_full(struct nfs_write_data *data, int status)
                        req->wb_bytes,
                        (long long)req_offset(req));
 
-               if (status < 0) {
+               if (task->tk_status < 0) {
                        ClearPageUptodate(page);
                        SetPageError(page);
-                       req->wb_context->error = status;
+                       req->wb_context->error = task->tk_status;
                        end_page_writeback(page);
                        nfs_inode_remove_request(req);
-                       dprintk(", error = %d\n", status);
+                       dprintk(", error = %d\n", task->tk_status);
                        goto next;
                }
                end_page_writeback(page);
@@ -1121,22 +1224,34 @@ static void nfs_writeback_done_full(struct nfs_write_data *data, int status)
                nfs_inode_remove_request(req);
 #endif
        next:
-               nfs_unlock_request(req);
+               nfs_clear_page_writeback(req);
        }
 }
 
+static const struct rpc_call_ops nfs_write_full_ops = {
+       .rpc_call_done = nfs_writeback_done_full,
+       .rpc_release = nfs_writedata_release,
+};
+
+
 /*
  * This function is called when the WRITE call is complete.
  */
-void nfs_writeback_done(struct rpc_task *task)
+int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
 {
-       struct nfs_write_data   *data = (struct nfs_write_data *) task->tk_calldata;
        struct nfs_writeargs    *argp = &data->args;
        struct nfs_writeres     *resp = &data->res;
+       int status;
 
        dprintk("NFS: %4d nfs_writeback_done (status %d)\n",
                task->tk_pid, task->tk_status);
 
+       /* Call the NFS version-specific code */
+       status = NFS_PROTO(data->inode)->write_done(task, data);
+       if (status != 0)
+               return status;
+       nfs_add_stats(data->inode, NFSIOS_SERVERWRITTENBYTES, resp->count);
+
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
        if (resp->verf->committed < argp->stable && task->tk_status >= 0) {
                /* We tried a write call, but the server did not
@@ -1162,6 +1277,8 @@ void nfs_writeback_done(struct rpc_task *task)
        if (task->tk_status >= 0 && resp->count < argp->count) {
                static unsigned long    complain;
 
+               nfs_inc_stats(data->inode, NFSIOS_SHORTWRITE);
+
                /* Has the server at least made some progress? */
                if (resp->count != 0) {
                        /* Was this an NFSv2 write or an NFSv3 stable write? */
@@ -1177,7 +1294,7 @@ void nfs_writeback_done(struct rpc_task *task)
                                argp->stable = NFS_FILE_SYNC;
                        }
                        rpc_restart_call(task);
-                       return;
+                       return -EAGAIN;
                }
                if (time_before(complain, jiffies)) {
                        printk(KERN_WARNING
@@ -1188,18 +1305,13 @@ void nfs_writeback_done(struct rpc_task *task)
                /* Can't do anything about it except throw an error. */
                task->tk_status = -EIO;
        }
-
-       /*
-        * Process the nfs_page list
-        */
-       data->complete(data, task->tk_status);
+       return 0;
 }
 
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
-static void nfs_commit_release(struct rpc_task *task)
+void nfs_commit_release(void *wdata)
 {
-       struct nfs_write_data   *wdata = (struct nfs_write_data *)task->tk_calldata;
        nfs_commit_free(wdata);
 }
 
@@ -1207,63 +1319,53 @@ static void nfs_commit_release(struct rpc_task *task)
  * Set up the argument/result storage required for the RPC call.
  */
 static void nfs_commit_rpcsetup(struct list_head *head,
-               struct nfs_write_data *data, int how)
+               struct nfs_write_data *data,
+               int how)
 {
-       struct rpc_task         *task = &data->task;
-       struct nfs_page         *first, *last;
+       struct nfs_page         *first;
        struct inode            *inode;
-       loff_t                  start, end, len;
+       int flags;
 
        /* Set up the RPC argument and reply structs
         * NB: take care not to mess about with data->commit et al. */
 
        list_splice_init(head, &data->pages);
        first = nfs_list_entry(data->pages.next);
-       last = nfs_list_entry(data->pages.prev);
        inode = first->wb_context->dentry->d_inode;
 
-       /*
-        * Determine the offset range of requests in the COMMIT call.
-        * We rely on the fact that data->pages is an ordered list...
-        */
-       start = req_offset(first);
-       end = req_offset(last) + last->wb_bytes;
-       len = end - start;
-       /* If 'len' is not a 32-bit quantity, pass '0' in the COMMIT call */
-       if (end >= i_size_read(inode) || len < 0 || len > (~((u32)0) >> 1))
-               len = 0;
-
        data->inode       = inode;
        data->cred        = first->wb_context->cred;
 
        data->args.fh     = NFS_FH(data->inode);
-       data->args.offset = start;
-       data->args.count  = len;
-       data->res.count   = len;
+       /* Note: we always request a commit of the entire inode */
+       data->args.offset = 0;
+       data->args.count  = 0;
+       data->res.count   = 0;
        data->res.fattr   = &data->fattr;
        data->res.verf    = &data->verf;
-       
+       nfs_fattr_init(&data->fattr);
+
+       /* Set up the initial task struct.  */
+       flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
+       rpc_init_task(&data->task, NFS_CLIENT(inode), flags, &nfs_commit_ops, data);
        NFS_PROTO(inode)->commit_setup(data, how);
 
        data->task.tk_priority = flush_task_priority(how);
        data->task.tk_cookie = (unsigned long)inode;
-       data->task.tk_calldata = data;
-       /* Release requests */
-       data->task.tk_release = nfs_commit_release;
        
-       dprintk("NFS: %4d initiated commit call\n", task->tk_pid);
+       dprintk("NFS: %4d initiated commit call\n", data->task.tk_pid);
 }
 
 /*
  * Commit dirty pages
  */
 static int
-nfs_commit_list(struct list_head *head, int how)
+nfs_commit_list(struct inode *inode, struct list_head *head, int how)
 {
        struct nfs_write_data   *data;
        struct nfs_page         *req;
 
-       data = nfs_commit_alloc();
+       data = nfs_commit_alloc(NFS_SERVER(inode)->wpages);
 
        if (!data)
                goto out_bad;
@@ -1278,7 +1380,7 @@ nfs_commit_list(struct list_head *head, int how)
                req = nfs_list_entry(head->next);
                nfs_list_remove_request(req);
                nfs_mark_request_commit(req);
-               nfs_unlock_request(req);
+               nfs_clear_page_writeback(req);
        }
        return -ENOMEM;
 }
@@ -1286,19 +1388,22 @@ nfs_commit_list(struct list_head *head, int how)
 /*
  * COMMIT call returned
  */
-void
-nfs_commit_done(struct rpc_task *task)
+static void nfs_commit_done(struct rpc_task *task, void *calldata)
 {
-       struct nfs_write_data   *data = (struct nfs_write_data *)task->tk_calldata;
+       struct nfs_write_data   *data = calldata;
        struct nfs_page         *req;
-       int res = 0;
 
         dprintk("NFS: %4d nfs_commit_done (status %d)\n",
                                 task->tk_pid, task->tk_status);
 
+       /* Call the NFS version-specific code */
+       if (NFS_PROTO(data->inode)->commit_done(task, data) != 0)
+               return;
+
        while (!list_empty(&data->pages)) {
                req = nfs_list_entry(data->pages.next);
                nfs_list_remove_request(req);
+               dec_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
 
                dprintk("NFS: commit (%s/%Ld %d@%Ld)",
                        req->wb_context->dentry->d_inode->i_sb->s_id,
@@ -1324,10 +1429,18 @@ nfs_commit_done(struct rpc_task *task)
                dprintk(" mismatch\n");
                nfs_mark_request_dirty(req);
        next:
-               nfs_unlock_request(req);
-               res++;
+               nfs_clear_page_writeback(req);
        }
-       sub_page_state(nr_unstable,res);
+}
+
+static const struct rpc_call_ops nfs_commit_ops = {
+       .rpc_call_done = nfs_commit_done,
+       .rpc_release = nfs_commit_release,
+};
+#else
+static inline int nfs_commit_list(struct inode *inode, struct list_head *head, int how)
+{
+       return 0;
 }
 #endif
 
@@ -1336,66 +1449,83 @@ static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        LIST_HEAD(head);
-       int                     res,
-                               error = 0;
+       int res;
 
        spin_lock(&nfsi->req_lock);
        res = nfs_scan_dirty(inode, &head, idx_start, npages);
        spin_unlock(&nfsi->req_lock);
-       if (res)
-               error = nfs_flush_list(&head, NFS_SERVER(inode)->wpages, how);
-       if (error < 0)
-               return error;
+       if (res) {
+               int error = nfs_flush_list(inode, &head, res, how);
+               if (error < 0)
+                       return error;
+       }
        return res;
 }
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
-int nfs_commit_inode(struct inode *inode, unsigned long idx_start,
-                   unsigned int npages, int how)
+int nfs_commit_inode(struct inode *inode, int how)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        LIST_HEAD(head);
-       int                     res,
-                               error = 0;
+       int res;
 
        spin_lock(&nfsi->req_lock);
-       res = nfs_scan_commit(inode, &head, idx_start, npages);
+       res = nfs_scan_commit(inode, &head, 0, 0);
+       spin_unlock(&nfsi->req_lock);
        if (res) {
-               res += nfs_scan_commit(inode, &head, 0, 0);
-               spin_unlock(&nfsi->req_lock);
-               error = nfs_commit_list(&head, how);
-       } else
-               spin_unlock(&nfsi->req_lock);
-       if (error < 0)
-               return error;
+               int error = nfs_commit_list(inode, &head, how);
+               if (error < 0)
+                       return error;
+       }
        return res;
 }
 #endif
 
-int nfs_sync_inode(struct inode *inode, unsigned long idx_start,
-                 unsigned int npages, int how)
+int nfs_sync_inode_wait(struct inode *inode, unsigned long idx_start,
+               unsigned int npages, int how)
 {
-       int     error,
-               wait;
-
-       wait = how & FLUSH_WAIT;
-       how &= ~FLUSH_WAIT;
+       struct nfs_inode *nfsi = NFS_I(inode);
+       LIST_HEAD(head);
+       int nocommit = how & FLUSH_NOCOMMIT;
+       int pages, ret;
 
+       how &= ~FLUSH_NOCOMMIT;
+       spin_lock(&nfsi->req_lock);
        do {
-               error = 0;
-               if (wait)
-                       error = nfs_wait_on_requests(inode, idx_start, npages);
-               if (error == 0)
-                       error = nfs_flush_inode(inode, idx_start, npages, how);
-#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
-               if (error == 0)
-                       error = nfs_commit_inode(inode, idx_start, npages, how);
-#endif
-       } while (error > 0);
-       return error;
+               ret = nfs_wait_on_requests_locked(inode, idx_start, npages);
+               if (ret != 0)
+                       continue;
+               pages = nfs_scan_dirty(inode, &head, idx_start, npages);
+               if (pages != 0) {
+                       spin_unlock(&nfsi->req_lock);
+                       if (how & FLUSH_INVALIDATE)
+                               nfs_cancel_requests(&head);
+                       else
+                               ret = nfs_flush_list(inode, &head, pages, how);
+                       spin_lock(&nfsi->req_lock);
+                       continue;
+               }
+               if (nocommit)
+                       break;
+               pages = nfs_scan_commit(inode, &head, idx_start, npages);
+               if (pages == 0)
+                       break;
+               if (how & FLUSH_INVALIDATE) {
+                       spin_unlock(&nfsi->req_lock);
+                       nfs_cancel_requests(&head);
+                       spin_lock(&nfsi->req_lock);
+                       continue;
+               }
+               pages += nfs_scan_commit(inode, &head, 0, 0);
+               spin_unlock(&nfsi->req_lock);
+               ret = nfs_commit_list(inode, &head, how);
+               spin_lock(&nfsi->req_lock);
+       } while (ret >= 0);
+       spin_unlock(&nfsi->req_lock);
+       return ret;
 }
 
-int nfs_init_writepagecache(void)
+int __init nfs_init_writepagecache(void)
 {
        nfs_wdata_cachep = kmem_cache_create("nfs_write_data",
                                             sizeof(struct nfs_write_data),
@@ -1404,17 +1534,13 @@ int nfs_init_writepagecache(void)
        if (nfs_wdata_cachep == NULL)
                return -ENOMEM;
 
-       nfs_wdata_mempool = mempool_create(MIN_POOL_WRITE,
-                                          mempool_alloc_slab,
-                                          mempool_free_slab,
-                                          nfs_wdata_cachep);
+       nfs_wdata_mempool = mempool_create_slab_pool(MIN_POOL_WRITE,
+                                                    nfs_wdata_cachep);
        if (nfs_wdata_mempool == NULL)
                return -ENOMEM;
 
-       nfs_commit_mempool = mempool_create(MIN_POOL_COMMIT,
-                                          mempool_alloc_slab,
-                                          mempool_free_slab,
-                                          nfs_wdata_cachep);
+       nfs_commit_mempool = mempool_create_slab_pool(MIN_POOL_COMMIT,
+                                                     nfs_wdata_cachep);
        if (nfs_commit_mempool == NULL)
                return -ENOMEM;