]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - fs/nfs/write.c
NFSv4.1: layoutcommit
[linux-2.6.git] / fs / nfs / write.c
index 4cb88df12f8314d7ca684a8b4d61bf902bb79a5f..a03c11f9081edf6b5e38ad6fbdeebd715a8bebc4 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/file.h>
 #include <linux/writeback.h>
 #include <linux/swap.h>
+#include <linux/migrate.h>
 
 #include <linux/sunrpc/clnt.h>
 #include <linux/nfs_fs.h>
@@ -25,6 +26,9 @@
 #include "delegation.h"
 #include "internal.h"
 #include "iostat.h"
+#include "nfs4_fs.h"
+#include "fscache.h"
+#include "pnfs.h"
 
 #define NFSDBG_FACILITY                NFSDBG_PAGECACHE
 
@@ -34,9 +38,6 @@
 /*
  * Local function declarations
  */
-static struct nfs_page * nfs_update_request(struct nfs_open_context*,
-                                           struct page *,
-                                           unsigned int, unsigned int);
 static void nfs_pageio_init_write(struct nfs_pageio_descriptor *desc,
                                  struct inode *inode, int ioflags);
 static void nfs_redirty_request(struct nfs_page *req);
@@ -48,7 +49,7 @@ static struct kmem_cache *nfs_wdata_cachep;
 static mempool_t *nfs_wdata_mempool;
 static mempool_t *nfs_commit_mempool;
 
-struct nfs_write_data *nfs_commit_alloc(void)
+struct nfs_write_data *nfs_commitdata_alloc(void)
 {
        struct nfs_write_data *p = mempool_alloc(nfs_commit_mempool, GFP_NOFS);
 
@@ -58,6 +59,7 @@ struct nfs_write_data *nfs_commit_alloc(void)
        }
        return p;
 }
+EXPORT_SYMBOL_GPL(nfs_commitdata_alloc);
 
 void nfs_commit_free(struct nfs_write_data *p)
 {
@@ -65,6 +67,7 @@ void nfs_commit_free(struct nfs_write_data *p)
                kfree(p->pagevec);
        mempool_free(p, nfs_commit_mempool);
 }
+EXPORT_SYMBOL_GPL(nfs_commit_free);
 
 struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
 {
@@ -87,17 +90,16 @@ struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
        return p;
 }
 
-static void nfs_writedata_free(struct nfs_write_data *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 *data)
+static void nfs_writedata_release(struct nfs_write_data *wdata)
 {
-       struct nfs_write_data *wdata = data;
-
+       put_lseg(wdata->lseg);
        put_nfs_open_context(wdata->args.context);
        nfs_writedata_free(wdata);
 }
@@ -136,16 +138,21 @@ static struct nfs_page *nfs_page_find_request(struct page *page)
 static void nfs_grow_file(struct page *page, unsigned int offset, unsigned int count)
 {
        struct inode *inode = page->mapping->host;
-       loff_t end, i_size = i_size_read(inode);
-       pgoff_t end_index = (i_size - 1) >> PAGE_CACHE_SHIFT;
+       loff_t end, i_size;
+       pgoff_t end_index;
 
+       spin_lock(&inode->i_lock);
+       i_size = i_size_read(inode);
+       end_index = (i_size - 1) >> PAGE_CACHE_SHIFT;
        if (i_size > 0 && page->index < end_index)
-               return;
+               goto out;
        end = ((loff_t)page->index << PAGE_CACHE_SHIFT) + ((loff_t)offset+count);
        if (i_size >= end)
-               return;
-       nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
+               goto out;
        i_size_write(inode, end);
+       nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
+out:
+       spin_unlock(&inode->i_lock);
 }
 
 /* A writeback failed: mark the page as bad, and invalidate the page cache */
@@ -169,36 +176,13 @@ static void nfs_mark_uptodate(struct page *page, unsigned int base, unsigned int
        SetPageUptodate(page);
 }
 
-static int nfs_writepage_setup(struct nfs_open_context *ctx, struct page *page,
-               unsigned int offset, unsigned int count)
-{
-       struct nfs_page *req;
-       int ret;
-
-       for (;;) {
-               req = nfs_update_request(ctx, page, offset, count);
-               if (!IS_ERR(req))
-                       break;
-               ret = PTR_ERR(req);
-               if (ret != -EBUSY)
-                       return ret;
-               ret = nfs_wb_page(page->mapping->host, page);
-               if (ret != 0)
-                       return ret;
-       }
-       /* Update file length */
-       nfs_grow_file(page, offset, count);
-       nfs_clear_page_tag_locked(req);
-       return 0;
-}
-
 static int wb_priority(struct writeback_control *wbc)
 {
        if (wbc->for_reclaim)
                return FLUSH_HIGHPRI | FLUSH_STABLE;
-       if (wbc->for_kupdate)
-               return FLUSH_LOWPRI;
-       return 0;
+       if (wbc->for_kupdate || wbc->for_background)
+               return FLUSH_LOWPRI | FLUSH_COND_STABLE;
+       return FLUSH_COND_STABLE;
 }
 
 /*
@@ -219,9 +203,12 @@ static int nfs_set_page_writeback(struct page *page)
                struct inode *inode = page->mapping->host;
                struct nfs_server *nfss = NFS_SERVER(inode);
 
+               page_cache_get(page);
                if (atomic_long_inc_return(&nfss->writeback) >
-                               NFS_CONGESTION_ON_THRESH)
-                       set_bdi_congested(&nfss->backing_dev_info, WRITE);
+                               NFS_CONGESTION_ON_THRESH) {
+                       set_bdi_congested(&nfss->backing_dev_info,
+                                               BLK_RW_ASYNC);
+               }
        }
        return ret;
 }
@@ -232,28 +219,22 @@ static void nfs_end_page_writeback(struct page *page)
        struct nfs_server *nfss = NFS_SERVER(inode);
 
        end_page_writeback(page);
+       page_cache_release(page);
        if (atomic_long_dec_return(&nfss->writeback) < NFS_CONGESTION_OFF_THRESH)
-               clear_bdi_congested(&nfss->backing_dev_info, WRITE);
+               clear_bdi_congested(&nfss->backing_dev_info, BLK_RW_ASYNC);
 }
 
-/*
- * Find an associated nfs write request, and prepare to flush it out
- * May return an error if the user signalled nfs_wait_on_request().
- */
-static int nfs_page_async_flush(struct nfs_pageio_descriptor *pgio,
-                               struct page *page)
+static struct nfs_page *nfs_find_and_lock_request(struct page *page, bool nonblock)
 {
        struct inode *inode = page->mapping->host;
        struct nfs_page *req;
        int ret;
 
        spin_lock(&inode->i_lock);
-       for(;;) {
+       for (;;) {
                req = nfs_page_find_request_locked(page);
-               if (req == NULL) {
-                       spin_unlock(&inode->i_lock);
-                       return 0;
-               }
+               if (req == NULL)
+                       break;
                if (nfs_set_page_tag_locked(req))
                        break;
                /* Note: If we hold the page lock, as is the case in nfs_writepage,
@@ -262,42 +243,63 @@ static int nfs_page_async_flush(struct nfs_pageio_descriptor *pgio,
                 *       request as dirty (in which case we don't care).
                 */
                spin_unlock(&inode->i_lock);
-               ret = nfs_wait_on_request(req);
+               if (!nonblock)
+                       ret = nfs_wait_on_request(req);
+               else
+                       ret = -EAGAIN;
                nfs_release_request(req);
                if (ret != 0)
-                       return ret;
+                       return ERR_PTR(ret);
                spin_lock(&inode->i_lock);
        }
-       if (test_bit(PG_NEED_COMMIT, &req->wb_flags)) {
-               /* This request is marked for commit */
-               spin_unlock(&inode->i_lock);
-               nfs_clear_page_tag_locked(req);
-               nfs_pageio_complete(pgio);
-               return 0;
-       }
-       if (nfs_set_page_writeback(page) != 0) {
-               spin_unlock(&inode->i_lock);
-               BUG();
-       }
        spin_unlock(&inode->i_lock);
+       return req;
+}
+
+/*
+ * Find an associated nfs write request, and prepare to flush it out
+ * May return an error if the user signalled nfs_wait_on_request().
+ */
+static int nfs_page_async_flush(struct nfs_pageio_descriptor *pgio,
+                               struct page *page, bool nonblock)
+{
+       struct nfs_page *req;
+       int ret = 0;
+
+       req = nfs_find_and_lock_request(page, nonblock);
+       if (!req)
+               goto out;
+       ret = PTR_ERR(req);
+       if (IS_ERR(req))
+               goto out;
+
+       ret = nfs_set_page_writeback(page);
+       BUG_ON(ret != 0);
+       BUG_ON(test_bit(PG_CLEAN, &req->wb_flags));
+
        if (!nfs_pageio_add_request(pgio, req)) {
                nfs_redirty_request(req);
-               nfs_end_page_writeback(page);
-               nfs_clear_page_tag_locked(req);
-               return pgio->pg_error;
+               ret = pgio->pg_error;
        }
-       return 0;
+out:
+       return ret;
 }
 
 static int nfs_do_writepage(struct page *page, struct writeback_control *wbc, struct nfs_pageio_descriptor *pgio)
 {
        struct inode *inode = page->mapping->host;
+       int ret;
 
        nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGE);
        nfs_add_stats(inode, NFSIOS_WRITEPAGES, 1);
 
        nfs_pageio_cond_complete(pgio, page->index);
-       return nfs_page_async_flush(pgio, page);
+       ret = nfs_page_async_flush(pgio, page, wbc->sync_mode == WB_SYNC_NONE);
+       if (ret == -EAGAIN) {
+               redirty_page_for_writepage(wbc, page);
+               ret = 0;
+       }
+       return ret;
 }
 
 /*
@@ -339,29 +341,52 @@ static int nfs_writepages_callback(struct page *page, struct writeback_control *
 int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
 {
        struct inode *inode = mapping->host;
+       unsigned long *bitlock = &NFS_I(inode)->flags;
        struct nfs_pageio_descriptor pgio;
        int err;
 
+       /* Stop dirtying of new pages while we sync */
+       err = wait_on_bit_lock(bitlock, NFS_INO_FLUSHING,
+                       nfs_wait_bit_killable, TASK_KILLABLE);
+       if (err)
+               goto out_err;
+
        nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES);
 
        nfs_pageio_init_write(&pgio, inode, wb_priority(wbc));
        err = write_cache_pages(mapping, wbc, nfs_writepages_callback, &pgio);
        nfs_pageio_complete(&pgio);
+
+       clear_bit_unlock(NFS_INO_FLUSHING, bitlock);
+       smp_mb__after_clear_bit();
+       wake_up_bit(bitlock, NFS_INO_FLUSHING);
+
        if (err < 0)
-               return err;
-       if (pgio.pg_error < 0)
-               return pgio.pg_error;
+               goto out_err;
+       err = pgio.pg_error;
+       if (err < 0)
+               goto out_err;
        return 0;
+out_err:
+       return err;
 }
 
 /*
  * Insert a write request into an inode
  */
-static void nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
+static int nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        int error;
 
+       error = radix_tree_preload(GFP_NOFS);
+       if (error != 0)
+               goto out;
+
+       /* Lock the request! */
+       nfs_lock_request_dontget(req);
+
+       spin_lock(&inode->i_lock);
        error = radix_tree_insert(&nfsi->nfs_page_tree, req->wb_index, req);
        BUG_ON(error);
        if (!nfsi->npages) {
@@ -369,12 +394,17 @@ static void nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
                if (nfs_have_delegation(inode, FMODE_WRITE))
                        nfsi->change_attr++;
        }
+       set_bit(PG_MAPPED, &req->wb_flags);
        SetPagePrivate(req->wb_page);
        set_page_private(req->wb_page, (unsigned long)req);
        nfsi->npages++;
        kref_get(&req->wb_kref);
        radix_tree_tag_set(&nfsi->nfs_page_tree, req->wb_index,
                                NFS_PAGE_TAG_LOCKED);
+       spin_unlock(&inode->i_lock);
+       radix_tree_preload_end();
+out:
+       return error;
 }
 
 /*
@@ -390,6 +420,7 @@ static void nfs_inode_remove_request(struct nfs_page *req)
        spin_lock(&inode->i_lock);
        set_page_private(req->wb_page, 0);
        ClearPagePrivate(req->wb_page);
+       clear_bit(PG_MAPPED, &req->wb_flags);
        radix_tree_delete(&nfsi->nfs_page_tree, req->wb_index);
        nfsi->npages--;
        if (!nfsi->npages) {
@@ -397,27 +428,14 @@ static void nfs_inode_remove_request(struct nfs_page *req)
                iput(inode);
        } else
                spin_unlock(&inode->i_lock);
-       nfs_clear_request(req);
        nfs_release_request(req);
 }
 
 static void
-nfs_redirty_request(struct nfs_page *req)
+nfs_mark_request_dirty(struct nfs_page *req)
 {
        __set_page_dirty_nobuffers(req->wb_page);
-}
-
-/*
- * Check if a request is dirty
- */
-static inline int
-nfs_dirty_request(struct nfs_page *req)
-{
-       struct page *page = req->wb_page;
-
-       if (page == NULL || test_bit(PG_NEED_COMMIT, &req->wb_flags))
-               return 0;
-       return !PageWriteback(req->wb_page);
+       __mark_inode_dirty(req->wb_page->mapping->host, I_DIRTY_DATASYNC);
 }
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
@@ -425,116 +443,93 @@ nfs_dirty_request(struct nfs_page *req)
  * Add a request to the inode's commit list.
  */
 static void
-nfs_mark_request_commit(struct nfs_page *req)
+nfs_mark_request_commit(struct nfs_page *req, struct pnfs_layout_segment *lseg)
 {
        struct inode *inode = req->wb_context->path.dentry->d_inode;
        struct nfs_inode *nfsi = NFS_I(inode);
 
        spin_lock(&inode->i_lock);
-       nfsi->ncommit++;
-       set_bit(PG_NEED_COMMIT, &(req)->wb_flags);
+       set_bit(PG_CLEAN, &(req)->wb_flags);
        radix_tree_tag_set(&nfsi->nfs_page_tree,
                        req->wb_index,
                        NFS_PAGE_TAG_COMMIT);
+       nfsi->ncommit++;
        spin_unlock(&inode->i_lock);
+       pnfs_mark_request_commit(req, lseg);
        inc_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
        inc_bdi_stat(req->wb_page->mapping->backing_dev_info, BDI_RECLAIMABLE);
        __mark_inode_dirty(inode, I_DIRTY_DATASYNC);
 }
 
+static int
+nfs_clear_request_commit(struct nfs_page *req)
+{
+       struct page *page = req->wb_page;
+
+       if (test_and_clear_bit(PG_CLEAN, &(req)->wb_flags)) {
+               dec_zone_page_state(page, NR_UNSTABLE_NFS);
+               dec_bdi_stat(page->mapping->backing_dev_info, BDI_RECLAIMABLE);
+               return 1;
+       }
+       return 0;
+}
+
 static inline
 int nfs_write_need_commit(struct nfs_write_data *data)
 {
-       return data->verf.committed != NFS_FILE_SYNC;
+       if (data->verf.committed == NFS_DATA_SYNC)
+               return data->lseg == NULL;
+       else
+               return data->verf.committed != NFS_FILE_SYNC;
 }
 
 static inline
-int nfs_reschedule_unstable_write(struct nfs_page *req)
+int nfs_reschedule_unstable_write(struct nfs_page *req,
+                                 struct nfs_write_data *data)
 {
-       if (test_bit(PG_NEED_COMMIT, &req->wb_flags)) {
-               nfs_mark_request_commit(req);
+       if (test_and_clear_bit(PG_NEED_COMMIT, &req->wb_flags)) {
+               nfs_mark_request_commit(req, data->lseg);
                return 1;
        }
        if (test_and_clear_bit(PG_NEED_RESCHED, &req->wb_flags)) {
-               nfs_redirty_request(req);
+               nfs_mark_request_dirty(req);
                return 1;
        }
        return 0;
 }
 #else
 static inline void
-nfs_mark_request_commit(struct nfs_page *req)
+nfs_mark_request_commit(struct nfs_page *req, struct pnfs_layout_segment *lseg)
 {
 }
 
-static inline
-int nfs_write_need_commit(struct nfs_write_data *data)
+static inline int
+nfs_clear_request_commit(struct nfs_page *req)
 {
        return 0;
 }
 
 static inline
-int nfs_reschedule_unstable_write(struct nfs_page *req)
+int nfs_write_need_commit(struct nfs_write_data *data)
 {
        return 0;
 }
-#endif
 
-/*
- * Wait for a request to complete.
- *
- * Interruptible by fatal signals only.
- */
-static int nfs_wait_on_requests_locked(struct inode *inode, pgoff_t idx_start, unsigned int npages)
+static inline
+int nfs_reschedule_unstable_write(struct nfs_page *req,
+                                 struct nfs_write_data *data)
 {
-       struct nfs_inode *nfsi = NFS_I(inode);
-       struct nfs_page *req;
-       pgoff_t idx_end, next;
-       unsigned int            res = 0;
-       int                     error;
-
-       if (npages == 0)
-               idx_end = ~0;
-       else
-               idx_end = idx_start + npages - 1;
-
-       next = idx_start;
-       while (radix_tree_gang_lookup_tag(&nfsi->nfs_page_tree, (void **)&req, next, 1, NFS_PAGE_TAG_LOCKED)) {
-               if (req->wb_index > idx_end)
-                       break;
-
-               next = req->wb_index + 1;
-               BUG_ON(!NFS_WBACK_BUSY(req));
-
-               kref_get(&req->wb_kref);
-               spin_unlock(&inode->i_lock);
-               error = nfs_wait_on_request(req);
-               nfs_release_request(req);
-               spin_lock(&inode->i_lock);
-               if (error < 0)
-                       return error;
-               res++;
-       }
-       return res;
+       return 0;
 }
+#endif
 
-static void nfs_cancel_commit_list(struct list_head *head)
+#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
+static int
+nfs_need_commit(struct nfs_inode *nfsi)
 {
-       struct nfs_page *req;
-
-       while(!list_empty(head)) {
-               req = nfs_list_entry(head->next);
-               dec_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
-               dec_bdi_stat(req->wb_page->mapping->backing_dev_info,
-                               BDI_RECLAIMABLE);
-               nfs_list_remove_request(req);
-               clear_bit(PG_NEED_COMMIT, &(req)->wb_flags);
-               nfs_inode_remove_request(req);
-               nfs_unlock_request(req);
-       }
+       return radix_tree_tagged(&nfsi->nfs_page_tree, NFS_PAGE_TAG_COMMIT);
 }
 
-#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
 /*
  * nfs_scan_commit - Scan an inode for commit requests
  * @inode: NFS inode to scan
@@ -549,16 +544,24 @@ static int
 nfs_scan_commit(struct inode *inode, struct list_head *dst, pgoff_t idx_start, unsigned int npages)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
-       int res = 0;
+       int ret;
 
-       if (nfsi->ncommit != 0) {
-               res = nfs_scan_list(nfsi, dst, idx_start, npages,
-                               NFS_PAGE_TAG_COMMIT);
-               nfsi->ncommit -= res;
-       }
-       return res;
+       if (!nfs_need_commit(nfsi))
+               return 0;
+
+       ret = nfs_scan_list(nfsi, dst, idx_start, npages, NFS_PAGE_TAG_COMMIT);
+       if (ret > 0)
+               nfsi->ncommit -= ret;
+       if (nfs_need_commit(NFS_I(inode)))
+               __mark_inode_dirty(inode, I_DIRTY_DATASYNC);
+       return ret;
 }
 #else
+static inline int nfs_need_commit(struct nfs_inode *nfsi)
+{
+       return 0;
+}
+
 static inline int nfs_scan_commit(struct inode *inode, struct list_head *dst, pgoff_t idx_start, unsigned int npages)
 {
        return 0;
@@ -566,110 +569,129 @@ static inline int nfs_scan_commit(struct inode *inode, struct list_head *dst, pg
 #endif
 
 /*
- * Try to update any existing write request, or create one if there is none.
- * In order to match, the request's credentials must match those of
- * the calling process.
+ * Search for an existing write request, and attempt to update
+ * it to reflect a new dirty region on a given page.
  *
- * Note: Should always be called with the Page Lock held!
+ * If the attempt fails, then the existing request is flushed out
+ * to disk.
  */
-static struct nfs_page * nfs_update_request(struct nfs_open_context* ctx,
-               struct page *page, unsigned int offset, unsigned int bytes)
+static struct nfs_page *nfs_try_to_update_request(struct inode *inode,
+               struct page *page,
+               unsigned int offset,
+               unsigned int bytes)
 {
-       struct address_space *mapping = page->mapping;
-       struct inode *inode = mapping->host;
-       struct nfs_page         *req, *new = NULL;
-       pgoff_t         rqend, end;
+       struct nfs_page *req;
+       unsigned int rqend;
+       unsigned int end;
+       int error;
+
+       if (!PagePrivate(page))
+               return NULL;
 
        end = offset + bytes;
+       spin_lock(&inode->i_lock);
 
        for (;;) {
-               /* Loop over all inode entries and see if we find
-                * A request for the page we wish to update
+               req = nfs_page_find_request_locked(page);
+               if (req == NULL)
+                       goto out_unlock;
+
+               rqend = req->wb_offset + req->wb_bytes;
+               /*
+                * Tell the caller to flush out the request if
+                * the offsets are non-contiguous.
+                * Note: nfs_flush_incompatible() will already
+                * have flushed out requests having wrong owners.
                 */
-               if (new) {
-                       if (radix_tree_preload(GFP_NOFS)) {
-                               nfs_release_request(new);
-                               return ERR_PTR(-ENOMEM);
-                       }
-               }
+               if (offset > rqend
+                   || end < req->wb_offset)
+                       goto out_flushme;
 
-               spin_lock(&inode->i_lock);
-               req = nfs_page_find_request_locked(page);
-               if (req) {
-                       if (!nfs_set_page_tag_locked(req)) {
-                               int error;
-
-                               spin_unlock(&inode->i_lock);
-                               error = nfs_wait_on_request(req);
-                               nfs_release_request(req);
-                               if (error < 0) {
-                                       if (new) {
-                                               radix_tree_preload_end();
-                                               nfs_release_request(new);
-                                       }
-                                       return ERR_PTR(error);
-                               }
-                               continue;
-                       }
-                       spin_unlock(&inode->i_lock);
-                       if (new) {
-                               radix_tree_preload_end();
-                               nfs_release_request(new);
-                       }
+               if (nfs_set_page_tag_locked(req))
                        break;
-               }
 
-               if (new) {
-                       nfs_lock_request_dontget(new);
-                       nfs_inode_add_request(inode, new);
-                       spin_unlock(&inode->i_lock);
-                       radix_tree_preload_end();
-                       req = new;
-                       goto zero_page;
-               }
+               /* The request is locked, so wait and then retry */
                spin_unlock(&inode->i_lock);
-
-               new = nfs_create_request(ctx, inode, page, offset, bytes);
-               if (IS_ERR(new))
-                       return new;
+               error = nfs_wait_on_request(req);
+               nfs_release_request(req);
+               if (error != 0)
+                       goto out_err;
+               spin_lock(&inode->i_lock);
        }
 
-       /* We have a request for our page.
-        * If the creds don't match, or the
-        * page addresses don't match,
-        * tell the caller to wait on the conflicting
-        * request.
-        */
-       rqend = req->wb_offset + req->wb_bytes;
-       if (req->wb_context != ctx
-           || req->wb_page != page
-           || !nfs_dirty_request(req)
-           || offset > rqend || end < req->wb_offset) {
-               nfs_clear_page_tag_locked(req);
-               return ERR_PTR(-EBUSY);
+       if (nfs_clear_request_commit(req) &&
+           radix_tree_tag_clear(&NFS_I(inode)->nfs_page_tree,
+                                req->wb_index, NFS_PAGE_TAG_COMMIT) != NULL) {
+               NFS_I(inode)->ncommit--;
+               pnfs_clear_request_commit(req);
        }
 
        /* Okay, the request matches. Update the region */
        if (offset < req->wb_offset) {
                req->wb_offset = offset;
                req->wb_pgbase = offset;
-               req->wb_bytes = max(end, rqend) - req->wb_offset;
-               goto zero_page;
        }
-
        if (end > rqend)
                req->wb_bytes = end - req->wb_offset;
-
+       else
+               req->wb_bytes = rqend - req->wb_offset;
+out_unlock:
+       spin_unlock(&inode->i_lock);
        return req;
-zero_page:
-       /* If this page might potentially be marked as up to date,
-        * then we need to zero any uninitalised data. */
-       if (req->wb_pgbase == 0 && req->wb_bytes != PAGE_CACHE_SIZE
-                       && !PageUptodate(req->wb_page))
-               zero_user_segment(req->wb_page, req->wb_bytes, PAGE_CACHE_SIZE);
+out_flushme:
+       spin_unlock(&inode->i_lock);
+       nfs_release_request(req);
+       error = nfs_wb_page(inode, page);
+out_err:
+       return ERR_PTR(error);
+}
+
+/*
+ * Try to update an existing write request, or create one if there is none.
+ *
+ * Note: Should always be called with the Page Lock held to prevent races
+ * if we have to add a new request. Also assumes that the caller has
+ * already called nfs_flush_incompatible() if necessary.
+ */
+static struct nfs_page * nfs_setup_write_request(struct nfs_open_context* ctx,
+               struct page *page, unsigned int offset, unsigned int bytes)
+{
+       struct inode *inode = page->mapping->host;
+       struct nfs_page *req;
+       int error;
+
+       req = nfs_try_to_update_request(inode, page, offset, bytes);
+       if (req != NULL)
+               goto out;
+       req = nfs_create_request(ctx, inode, page, offset, bytes);
+       if (IS_ERR(req))
+               goto out;
+       error = nfs_inode_add_request(inode, req);
+       if (error != 0) {
+               nfs_release_request(req);
+               req = ERR_PTR(error);
+       }
+out:
        return req;
 }
 
+static int nfs_writepage_setup(struct nfs_open_context *ctx, struct page *page,
+               unsigned int offset, unsigned int count)
+{
+       struct nfs_page *req;
+
+       req = nfs_setup_write_request(ctx, page, offset, count);
+       if (IS_ERR(req))
+               return PTR_ERR(req);
+       nfs_mark_request_dirty(req);
+       /* Update file length */
+       nfs_grow_file(page, offset, count);
+       nfs_mark_uptodate(page, req->wb_pgbase, req->wb_bytes);
+       nfs_mark_request_dirty(req);
+       nfs_clear_page_tag_locked(req);
+       return 0;
+}
+
 int nfs_flush_incompatible(struct file *file, struct page *page)
 {
        struct nfs_open_context *ctx = nfs_file_open_context(file);
@@ -687,8 +709,9 @@ int nfs_flush_incompatible(struct file *file, struct page *page)
                req = nfs_page_find_request(page);
                if (req == NULL)
                        return 0;
-               do_flush = req->wb_page != page || req->wb_context != ctx
-                       || !nfs_dirty_request(req);
+               do_flush = req->wb_page != page || req->wb_context != ctx ||
+                       req->wb_lock_context->lockowner != current->files ||
+                       req->wb_lock_context->pid != current->tgid;
                nfs_release_request(req);
                if (!do_flush)
                        return 0;
@@ -723,10 +746,10 @@ int nfs_updatepage(struct file *file, struct page *page,
 
        nfs_inc_stats(inode, NFSIOS_VFSUPDATEPAGE);
 
-       dprintk("NFS:      nfs_updatepage(%s/%s %d@%Ld)\n",
+       dprintk("NFS:       nfs_updatepage(%s/%s %d@%lld)\n",
                file->f_path.dentry->d_parent->d_name.name,
                file->f_path.dentry->d_name.name, count,
-               (long long)(page_offset(page) +offset));
+               (long long)(page_offset(page) + offset));
 
        /* If we're not using byte range locks, and we know the page
         * is up to date, it may be more efficient to extend the write
@@ -735,35 +758,29 @@ int nfs_updatepage(struct file *file, struct page *page,
         */
        if (nfs_write_pageuptodate(page, inode) &&
                        inode->i_flock == NULL &&
-                       !(file->f_flags & O_SYNC)) {
+                       !(file->f_flags & O_DSYNC)) {
                count = max(count + offset, nfs_page_length(page));
                offset = 0;
        }
 
        status = nfs_writepage_setup(ctx, page, offset, count);
-       __set_page_dirty_nobuffers(page);
-
-        dprintk("NFS:      nfs_updatepage returns %d (isize %Ld)\n",
-                       status, (long long)i_size_read(inode));
        if (status < 0)
                nfs_set_pageerror(page);
+
+       dprintk("NFS:       nfs_updatepage returns %d (isize %lld)\n",
+                       status, (long long)i_size_read(inode));
        return status;
 }
 
-static void nfs_writepage_release(struct nfs_page *req)
+static void nfs_writepage_release(struct nfs_page *req,
+                                 struct nfs_write_data *data)
 {
+       struct page *page = req->wb_page;
 
-       if (PageError(req->wb_page)) {
-               nfs_end_page_writeback(req->wb_page);
+       if (PageError(req->wb_page) || !nfs_reschedule_unstable_write(req, data))
                nfs_inode_remove_request(req);
-       } else if (!nfs_reschedule_unstable_write(req)) {
-               /* Set the PG_uptodate flag */
-               nfs_mark_uptodate(req->wb_page, req->wb_pgbase, req->wb_bytes);
-               nfs_end_page_writeback(req->wb_page);
-               nfs_inode_remove_request(req);
-       } else
-               nfs_end_page_writeback(req->wb_page);
        nfs_clear_page_tag_locked(req);
+       nfs_end_page_writeback(page);
 }
 
 static int flush_task_priority(int how)
@@ -777,41 +794,77 @@ static int flush_task_priority(int how)
        return RPC_PRIORITY_NORMAL;
 }
 
-/*
- * Set up the argument/result storage required for the RPC call.
- */
-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)
+int nfs_initiate_write(struct nfs_write_data *data,
+                      struct rpc_clnt *clnt,
+                      const struct rpc_call_ops *call_ops,
+                      int how)
 {
-       struct inode *inode = req->wb_context->path.dentry->d_inode;
-       int flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
+       struct inode *inode = data->inode;
        int priority = flush_task_priority(how);
        struct rpc_task *task;
        struct rpc_message msg = {
                .rpc_argp = &data->args,
                .rpc_resp = &data->res,
-               .rpc_cred = req->wb_context->cred,
+               .rpc_cred = data->cred,
        };
        struct rpc_task_setup task_setup_data = {
-               .rpc_client = NFS_CLIENT(inode),
+               .rpc_client = clnt,
                .task = &data->task,
                .rpc_message = &msg,
                .callback_ops = call_ops,
                .callback_data = data,
                .workqueue = nfsiod_workqueue,
-               .flags = flags,
+               .flags = RPC_TASK_ASYNC,
                .priority = priority,
        };
+       int ret = 0;
+
+       /* Set up the initial task struct.  */
+       NFS_PROTO(inode)->write_setup(data, &msg);
+
+       dprintk("NFS: %5u initiated write call "
+               "(req %s/%lld, %u bytes @ offset %llu)\n",
+               data->task.tk_pid,
+               inode->i_sb->s_id,
+               (long long)NFS_FILEID(inode),
+               data->args.count,
+               (unsigned long long)data->args.offset);
+
+       task = rpc_run_task(&task_setup_data);
+       if (IS_ERR(task)) {
+               ret = PTR_ERR(task);
+               goto out;
+       }
+       if (how & FLUSH_SYNC) {
+               ret = rpc_wait_for_completion_task(task);
+               if (ret == 0)
+                       ret = task->tk_status;
+       }
+       rpc_put_task(task);
+out:
+       return ret;
+}
+EXPORT_SYMBOL_GPL(nfs_initiate_write);
+
+/*
+ * Set up the argument/result storage required for the RPC call.
+ */
+static int 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,
+               struct pnfs_layout_segment *lseg,
+               int how)
+{
+       struct inode *inode = req->wb_context->path.dentry->d_inode;
 
        /* Set up the RPC argument and reply structs
         * NB: take care not to mess about with data->commit et al. */
 
        data->req = req;
        data->inode = inode = req->wb_context->path.dentry->d_inode;
-       data->cred = msg.rpc_cred;
+       data->cred = req->wb_context->cred;
+       data->lseg = get_lseg(lseg);
 
        data->args.fh     = NFS_FH(inode);
        data->args.offset = req_offset(req) + offset;
@@ -819,10 +872,11 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
        data->args.pages  = data->pagevec;
        data->args.count  = count;
        data->args.context = get_nfs_open_context(req->wb_context);
+       data->args.lock_context = req->wb_lock_context;
        data->args.stable  = NFS_UNSTABLE;
-       if (how & FLUSH_STABLE) {
+       if (how & (FLUSH_STABLE | FLUSH_COND_STABLE)) {
                data->args.stable = NFS_DATA_SYNC;
-               if (!NFS_I(inode)->ncommit)
+               if (!nfs_need_commit(NFS_I(inode)))
                        data->args.stable = NFS_FILE_SYNC;
        }
 
@@ -831,39 +885,51 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
        data->res.verf    = &data->verf;
        nfs_fattr_init(&data->fattr);
 
-       /* Set up the initial task struct.  */
-       NFS_PROTO(inode)->write_setup(data, &msg);
+       if (data->lseg &&
+           (pnfs_try_to_write_data(data, call_ops, how) == PNFS_ATTEMPTED))
+               return 0;
 
-       dprintk("NFS: %5u initiated write call "
-               "(req %s/%Ld, %u bytes @ offset %Lu)\n",
-               data->task.tk_pid,
-               inode->i_sb->s_id,
-               (long long)NFS_FILEID(inode),
-               count,
-               (unsigned long long)data->args.offset);
+       return nfs_initiate_write(data, NFS_CLIENT(inode), call_ops, how);
+}
 
-       task = rpc_run_task(&task_setup_data);
-       if (!IS_ERR(task))
-               rpc_put_task(task);
+/* If a nfs_flush_* function fails, it should remove reqs from @head and
+ * call this on each, which will prepare them to be retried on next
+ * writeback using standard nfs.
+ */
+static void nfs_redirty_request(struct nfs_page *req)
+{
+       struct page *page = req->wb_page;
+
+       nfs_mark_request_dirty(req);
+       nfs_clear_page_tag_locked(req);
+       nfs_end_page_writeback(page);
 }
 
 /*
  * Generate multiple small requests to write out a single
  * contiguous dirty area on one page.
  */
-static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned int npages, size_t count, int how)
+static int nfs_flush_multi(struct nfs_pageio_descriptor *desc)
 {
-       struct nfs_page *req = nfs_list_entry(head->next);
+       struct nfs_page *req = nfs_list_entry(desc->pg_list.next);
        struct page *page = req->wb_page;
        struct nfs_write_data *data;
-       size_t wsize = NFS_SERVER(inode)->wsize, nbytes;
+       size_t wsize = NFS_SERVER(desc->pg_inode)->wsize, nbytes;
        unsigned int offset;
        int requests = 0;
+       int ret = 0;
+       struct pnfs_layout_segment *lseg;
        LIST_HEAD(list);
 
        nfs_list_remove_request(req);
 
-       nbytes = count;
+       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
+           (desc->pg_moreio || NFS_I(desc->pg_inode)->ncommit ||
+            desc->pg_count > wsize))
+               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
+
+
+       nbytes = desc->pg_count;
        do {
                size_t len = min(nbytes, wsize);
 
@@ -876,10 +942,14 @@ static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned
        } while (nbytes != 0);
        atomic_set(&req->wb_complete, requests);
 
+       BUG_ON(desc->pg_lseg);
+       lseg = pnfs_update_layout(desc->pg_inode, req->wb_context, IOMODE_RW);
        ClearPageError(page);
        offset = 0;
-       nbytes = count;
+       nbytes = desc->pg_count;
        do {
+               int ret2;
+
                data = list_entry(list.next, struct nfs_write_data, pages);
                list_del_init(&data->pages);
 
@@ -887,23 +957,25 @@ static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned
 
                if (nbytes < wsize)
                        wsize = nbytes;
-               nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
-                                  wsize, offset, how);
+               ret2 = nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
+                                         wsize, offset, lseg, desc->pg_ioflags);
+               if (ret == 0)
+                       ret = ret2;
                offset += wsize;
                nbytes -= wsize;
        } while (nbytes != 0);
 
-       return 0;
+       put_lseg(lseg);
+       desc->pg_lseg = NULL;
+       return ret;
 
 out_bad:
        while (!list_empty(&list)) {
                data = list_entry(list.next, struct nfs_write_data, pages);
                list_del(&data->pages);
-               nfs_writedata_release(data);
+               nfs_writedata_free(data);
        }
        nfs_redirty_request(req);
-       nfs_end_page_writeback(req->wb_page);
-       nfs_clear_page_tag_locked(req);
        return -ENOMEM;
 }
 
@@ -915,16 +987,26 @@ 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 inode *inode, struct list_head *head, unsigned int npages, size_t count, int how)
+static int nfs_flush_one(struct nfs_pageio_descriptor *desc)
 {
        struct nfs_page         *req;
        struct page             **pages;
        struct nfs_write_data   *data;
+       struct list_head *head = &desc->pg_list;
+       struct pnfs_layout_segment *lseg = desc->pg_lseg;
+       int ret;
 
-       data = nfs_writedata_alloc(npages);
-       if (!data)
-               goto out_bad;
-
+       data = nfs_writedata_alloc(nfs_page_array_len(desc->pg_base,
+                                                     desc->pg_count));
+       if (!data) {
+               while (!list_empty(head)) {
+                       req = nfs_list_entry(head->next);
+                       nfs_list_remove_request(req);
+                       nfs_redirty_request(req);
+               }
+               ret = -ENOMEM;
+               goto out;
+       }
        pages = data->pagevec;
        while (!list_empty(head)) {
                req = nfs_list_entry(head->next);
@@ -934,20 +1016,19 @@ static int nfs_flush_one(struct inode *inode, struct list_head *head, unsigned i
                *pages++ = req->wb_page;
        }
        req = nfs_list_entry(data->pages.next);
+       if ((!lseg) && list_is_singular(&data->pages))
+               lseg = pnfs_update_layout(desc->pg_inode, req->wb_context, IOMODE_RW);
 
-       /* Set up the argument struct */
-       nfs_write_rpcsetup(req, data, &nfs_write_full_ops, count, 0, how);
+       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
+           (desc->pg_moreio || NFS_I(desc->pg_inode)->ncommit))
+               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
 
-       return 0;
- out_bad:
-       while (!list_empty(head)) {
-               req = nfs_list_entry(head->next);
-               nfs_list_remove_request(req);
-               nfs_redirty_request(req);
-               nfs_end_page_writeback(req->wb_page);
-               nfs_clear_page_tag_locked(req);
-       }
-       return -ENOMEM;
+       /* Set up the argument struct */
+       ret = nfs_write_rpcsetup(req, data, &nfs_write_full_ops, desc->pg_count, 0, lseg, desc->pg_ioflags);
+out:
+       put_lseg(lseg); /* Cleans any gotten in ->pg_test */
+       desc->pg_lseg = NULL;
+       return ret;
 }
 
 static void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio,
@@ -955,6 +1036,8 @@ static void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio,
 {
        size_t wsize = NFS_SERVER(inode)->wsize;
 
+       pnfs_pageio_init_write(pgio, inode);
+
        if (wsize < PAGE_CACHE_SIZE)
                nfs_pageio_init(pgio, inode, nfs_flush_multi, wsize, ioflags);
        else
@@ -967,22 +1050,28 @@ static void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio,
 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;
 
-       dprintk("NFS: write (%s/%Ld %d@%Ld)",
-               req->wb_context->path.dentry->d_inode->i_sb->s_id,
-               (long long)NFS_FILEID(req->wb_context->path.dentry->d_inode),
-               req->wb_bytes,
-               (long long)req_offset(req));
+       dprintk("NFS: %5u write(%s/%lld %d@%lld)",
+               task->tk_pid,
+               data->req->wb_context->path.dentry->d_inode->i_sb->s_id,
+               (long long)
+                 NFS_FILEID(data->req->wb_context->path.dentry->d_inode),
+               data->req->wb_bytes, (long long)req_offset(data->req));
 
-       if (nfs_writeback_done(task, data) != 0)
-               return;
+       nfs_writeback_done(task, data);
+}
 
-       if (task->tk_status < 0) {
+static void nfs_writeback_release_partial(void *calldata)
+{
+       struct nfs_write_data   *data = calldata;
+       struct nfs_page         *req = data->req;
+       struct page             *page = req->wb_page;
+       int status = data->task.tk_status;
+
+       if (status < 0) {
                nfs_set_pageerror(page);
-               nfs_context_set_write_error(req->wb_context, task->tk_status);
-               dprintk(", error = %d\n", task->tk_status);
+               nfs_context_set_write_error(req->wb_context, status);
+               dprintk(", error = %d\n", status);
                goto out;
        }
 
@@ -1006,12 +1095,29 @@ static void nfs_writeback_done_partial(struct rpc_task *task, void *calldata)
 
 out:
        if (atomic_dec_and_test(&req->wb_complete))
-               nfs_writepage_release(req);
+               nfs_writepage_release(req, data);
+       nfs_writedata_release(calldata);
+}
+
+#if defined(CONFIG_NFS_V4_1)
+void nfs_write_prepare(struct rpc_task *task, void *calldata)
+{
+       struct nfs_write_data *data = calldata;
+
+       if (nfs4_setup_sequence(NFS_SERVER(data->inode),
+                               &data->args.seq_args,
+                               &data->res.seq_res, 1, task))
+               return;
+       rpc_call_start(task);
 }
+#endif /* CONFIG_NFS_V4_1 */
 
 static const struct rpc_call_ops nfs_write_partial_ops = {
+#if defined(CONFIG_NFS_V4_1)
+       .rpc_call_prepare = nfs_write_prepare,
+#endif /* CONFIG_NFS_V4_1 */
        .rpc_call_done = nfs_writeback_done_partial,
-       .rpc_release = nfs_writedata_release,
+       .rpc_release = nfs_writeback_release_partial,
 };
 
 /*
@@ -1024,62 +1130,69 @@ static const struct rpc_call_ops nfs_write_partial_ops = {
 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;
+       nfs_writeback_done(task, data);
+}
+
+static void nfs_writeback_release_full(void *calldata)
+{
+       struct nfs_write_data   *data = calldata;
+       int status = data->task.tk_status;
 
        /* Update attributes as result of writeback. */
        while (!list_empty(&data->pages)) {
-               req = nfs_list_entry(data->pages.next);
+               struct nfs_page *req = nfs_list_entry(data->pages.next);
+               struct page *page = req->wb_page;
+
                nfs_list_remove_request(req);
-               page = req->wb_page;
 
-               dprintk("NFS: write (%s/%Ld %d@%Ld)",
+               dprintk("NFS: %5u write (%s/%lld %d@%lld)",
+                       data->task.tk_pid,
                        req->wb_context->path.dentry->d_inode->i_sb->s_id,
                        (long long)NFS_FILEID(req->wb_context->path.dentry->d_inode),
                        req->wb_bytes,
                        (long long)req_offset(req));
 
-               if (task->tk_status < 0) {
+               if (status < 0) {
                        nfs_set_pageerror(page);
-                       nfs_context_set_write_error(req->wb_context, task->tk_status);
-                       dprintk(", error = %d\n", task->tk_status);
+                       nfs_context_set_write_error(req->wb_context, status);
+                       dprintk(", error = %d\n", status);
                        goto remove_request;
                }
 
                if (nfs_write_need_commit(data)) {
                        memcpy(&req->wb_verf, &data->verf, sizeof(req->wb_verf));
-                       nfs_mark_request_commit(req);
-                       nfs_end_page_writeback(page);
+                       nfs_mark_request_commit(req, data->lseg);
                        dprintk(" marked for commit\n");
                        goto next;
                }
-               /* Set the PG_uptodate flag? */
-               nfs_mark_uptodate(page, req->wb_pgbase, req->wb_bytes);
                dprintk(" OK\n");
 remove_request:
-               nfs_end_page_writeback(page);
                nfs_inode_remove_request(req);
        next:
                nfs_clear_page_tag_locked(req);
+               nfs_end_page_writeback(page);
        }
+       nfs_writedata_release(calldata);
 }
 
 static const struct rpc_call_ops nfs_write_full_ops = {
+#if defined(CONFIG_NFS_V4_1)
+       .rpc_call_prepare = nfs_write_prepare,
+#endif /* CONFIG_NFS_V4_1 */
        .rpc_call_done = nfs_writeback_done_full,
-       .rpc_release = nfs_writedata_release,
+       .rpc_release = nfs_writeback_release_full,
 };
 
 
 /*
  * This function is called when the WRITE call is complete.
  */
-int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
+void nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
 {
        struct nfs_writeargs    *argp = &data->args;
        struct nfs_writeres     *resp = &data->res;
+       struct nfs_server       *server = NFS_SERVER(data->inode);
        int status;
 
        dprintk("NFS: %5u nfs_writeback_done (status %d)\n",
@@ -1094,7 +1207,7 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
         */
        status = NFS_PROTO(data->inode)->write_done(task, data);
        if (status != 0)
-               return status;
+               return;
        nfs_add_stats(data->inode, NFSIOS_SERVERWRITTENBYTES, resp->count);
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
@@ -1109,10 +1222,11 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
                 */
                static unsigned long    complain;
 
+               /* Note this will print the MDS for a DS write */
                if (time_before(complain, jiffies)) {
-                       dprintk("NFS: faulty NFS server %s:"
+                       dprintk("NFS:       faulty NFS server %s:"
                                " (committed = %d) != (stable = %d)\n",
-                               NFS_SERVER(data->inode)->nfs_client->cl_hostname,
+                               server->nfs_client->cl_hostname,
                                resp->verf->committed, argp->stable);
                        complain = jiffies + 300 * HZ;
                }
@@ -1129,6 +1243,7 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
                        /* Was this an NFSv2 write or an NFSv3 stable write? */
                        if (resp->verf->committed != NFS_UNSTABLE) {
                                /* Resend from where the server left off */
+                               data->mds_offset += resp->count;
                                argp->offset += resp->count;
                                argp->pgbase += resp->count;
                                argp->count -= resp->count;
@@ -1138,8 +1253,8 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
                                 */
                                argp->stable = NFS_FILE_SYNC;
                        }
-                       rpc_restart_call(task);
-                       return -EAGAIN;
+                       nfs_restart_rpc(task, server->nfs_client);
+                       return;
                }
                if (time_before(complain, jiffies)) {
                        printk(KERN_WARNING
@@ -1150,46 +1265,89 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
                /* Can't do anything about it except throw an error. */
                task->tk_status = -EIO;
        }
-       return 0;
+       return;
 }
 
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
-void nfs_commit_release(void *data)
+static int nfs_commit_set_lock(struct nfs_inode *nfsi, int may_wait)
+{
+       int ret;
+
+       if (!test_and_set_bit(NFS_INO_COMMIT, &nfsi->flags))
+               return 1;
+       if (!may_wait)
+               return 0;
+       ret = out_of_line_wait_on_bit_lock(&nfsi->flags,
+                               NFS_INO_COMMIT,
+                               nfs_wait_bit_killable,
+                               TASK_KILLABLE);
+       return (ret < 0) ? ret : 1;
+}
+
+void nfs_commit_clear_lock(struct nfs_inode *nfsi)
+{
+       clear_bit(NFS_INO_COMMIT, &nfsi->flags);
+       smp_mb__after_clear_bit();
+       wake_up_bit(&nfsi->flags, NFS_INO_COMMIT);
+}
+EXPORT_SYMBOL_GPL(nfs_commit_clear_lock);
+
+void nfs_commitdata_release(void *data)
 {
        struct nfs_write_data *wdata = data;
 
+       put_lseg(wdata->lseg);
        put_nfs_open_context(wdata->args.context);
        nfs_commit_free(wdata);
 }
+EXPORT_SYMBOL_GPL(nfs_commitdata_release);
 
-/*
- * 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)
+int nfs_initiate_commit(struct nfs_write_data *data, struct rpc_clnt *clnt,
+                       const struct rpc_call_ops *call_ops,
+                       int how)
 {
-       struct nfs_page *first = nfs_list_entry(head->next);
-       struct inode *inode = first->wb_context->path.dentry->d_inode;
-       int flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
-       int priority = flush_task_priority(how);
        struct rpc_task *task;
+       int priority = flush_task_priority(how);
        struct rpc_message msg = {
                .rpc_argp = &data->args,
                .rpc_resp = &data->res,
-               .rpc_cred = first->wb_context->cred,
+               .rpc_cred = data->cred,
        };
        struct rpc_task_setup task_setup_data = {
                .task = &data->task,
-               .rpc_client = NFS_CLIENT(inode),
+               .rpc_client = clnt,
                .rpc_message = &msg,
-               .callback_ops = &nfs_commit_ops,
+               .callback_ops = call_ops,
                .callback_data = data,
                .workqueue = nfsiod_workqueue,
-               .flags = flags,
+               .flags = RPC_TASK_ASYNC,
                .priority = priority,
        };
+       /* Set up the initial task struct.  */
+       NFS_PROTO(data->inode)->commit_setup(data, &msg);
+
+       dprintk("NFS: %5u initiated commit call\n", data->task.tk_pid);
+
+       task = rpc_run_task(&task_setup_data);
+       if (IS_ERR(task))
+               return PTR_ERR(task);
+       if (how & FLUSH_SYNC)
+               rpc_wait_for_completion_task(task);
+       rpc_put_task(task);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(nfs_initiate_commit);
+
+/*
+ * Set up the argument/result storage required for the RPC call.
+ */
+void nfs_init_commit(struct nfs_write_data *data,
+                           struct list_head *head,
+                           struct pnfs_layout_segment *lseg)
+{
+       struct nfs_page *first = nfs_list_entry(head->next);
+       struct inode *inode = first->wb_context->path.dentry->d_inode;
 
        /* Set up the RPC argument and reply structs
         * NB: take care not to mess about with data->commit et al. */
@@ -1197,7 +1355,9 @@ static void nfs_commit_rpcsetup(struct list_head *head,
        list_splice_init(head, &data->pages);
 
        data->inode       = inode;
-       data->cred        = msg.rpc_cred;
+       data->cred        = first->wb_context->cred;
+       data->lseg        = lseg; /* reference transferred */
+       data->mds_ops     = &nfs_commit_ops;
 
        data->args.fh     = NFS_FH(data->inode);
        /* Note: we always request a commit of the entire inode */
@@ -1208,16 +1368,25 @@ static void nfs_commit_rpcsetup(struct list_head *head,
        data->res.fattr   = &data->fattr;
        data->res.verf    = &data->verf;
        nfs_fattr_init(&data->fattr);
+}
+EXPORT_SYMBOL_GPL(nfs_init_commit);
 
-       /* Set up the initial task struct.  */
-       NFS_PROTO(inode)->commit_setup(data, &msg);
-
-       dprintk("NFS: %5u initiated commit call\n", data->task.tk_pid);
+void nfs_retry_commit(struct list_head *page_list,
+                     struct pnfs_layout_segment *lseg)
+{
+       struct nfs_page *req;
 
-       task = rpc_run_task(&task_setup_data);
-       if (!IS_ERR(task))
-               rpc_put_task(task);
+       while (!list_empty(page_list)) {
+               req = nfs_list_entry(page_list->next);
+               nfs_list_remove_request(req);
+               nfs_mark_request_commit(req, lseg);
+               dec_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
+               dec_bdi_stat(req->wb_page->mapping->backing_dev_info,
+                            BDI_RECLAIMABLE);
+               nfs_clear_page_tag_locked(req);
+       }
 }
+EXPORT_SYMBOL_GPL(nfs_retry_commit);
 
 /*
  * Commit dirty pages
@@ -1226,27 +1395,18 @@ static int
 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_commitdata_alloc();
 
        if (!data)
                goto out_bad;
 
        /* Set up the argument struct */
-       nfs_commit_rpcsetup(head, data, how);
-
-       return 0;
+       nfs_init_commit(data, head, NULL);
+       return nfs_initiate_commit(data, NFS_CLIENT(inode), data->mds_ops, how);
  out_bad:
-       while (!list_empty(head)) {
-               req = nfs_list_entry(head->next);
-               nfs_list_remove_request(req);
-               nfs_mark_request_commit(req);
-               dec_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
-               dec_bdi_stat(req->wb_page->mapping->backing_dev_info,
-                               BDI_RECLAIMABLE);
-               nfs_clear_page_tag_locked(req);
-       }
+       nfs_retry_commit(head, NULL);
+       nfs_commit_clear_lock(NFS_I(inode));
        return -ENOMEM;
 }
 
@@ -1256,7 +1416,6 @@ nfs_commit_list(struct inode *inode, struct list_head *head, int how)
 static void nfs_commit_done(struct rpc_task *task, void *calldata)
 {
        struct nfs_write_data   *data = calldata;
-       struct nfs_page         *req;
 
         dprintk("NFS: %5u nfs_commit_done (status %d)\n",
                                 task->tk_pid, task->tk_status);
@@ -1264,24 +1423,27 @@ static void nfs_commit_done(struct rpc_task *task, void *calldata)
        /* Call the NFS version-specific code */
        if (NFS_PROTO(data->inode)->commit_done(task, data) != 0)
                return;
+}
+
+void nfs_commit_release_pages(struct nfs_write_data *data)
+{
+       struct nfs_page *req;
+       int status = data->task.tk_status;
 
        while (!list_empty(&data->pages)) {
                req = nfs_list_entry(data->pages.next);
                nfs_list_remove_request(req);
-               clear_bit(PG_NEED_COMMIT, &(req)->wb_flags);
-               dec_zone_page_state(req->wb_page, NR_UNSTABLE_NFS);
-               dec_bdi_stat(req->wb_page->mapping->backing_dev_info,
-                               BDI_RECLAIMABLE);
+               nfs_clear_request_commit(req);
 
-               dprintk("NFS: commit (%s/%Ld %d@%Ld)",
+               dprintk("NFS:       commit (%s/%lld %d@%lld)",
                        req->wb_context->path.dentry->d_inode->i_sb->s_id,
                        (long long)NFS_FILEID(req->wb_context->path.dentry->d_inode),
                        req->wb_bytes,
                        (long long)req_offset(req));
-               if (task->tk_status < 0) {
-                       nfs_context_set_write_error(req->wb_context, task->tk_status);
+               if (status < 0) {
+                       nfs_context_set_write_error(req->wb_context, status);
                        nfs_inode_remove_request(req);
-                       dprintk(", error = %d\n", task->tk_status);
+                       dprintk(", error = %d\n", status);
                        goto next;
                }
 
@@ -1289,22 +1451,32 @@ static void nfs_commit_done(struct rpc_task *task, void *calldata)
                 * returned by the server against all stored verfs. */
                if (!memcmp(req->wb_verf.verifier, data->verf.verifier, sizeof(data->verf.verifier))) {
                        /* We have a match */
-                       /* Set the PG_uptodate flag */
-                       nfs_mark_uptodate(req->wb_page, req->wb_pgbase,
-                                       req->wb_bytes);
                        nfs_inode_remove_request(req);
                        dprintk(" OK\n");
                        goto next;
                }
                /* We have a mismatch. Write the page again */
                dprintk(" mismatch\n");
-               nfs_redirty_request(req);
+               nfs_mark_request_dirty(req);
        next:
                nfs_clear_page_tag_locked(req);
        }
 }
+EXPORT_SYMBOL_GPL(nfs_commit_release_pages);
+
+static void nfs_commit_release(void *calldata)
+{
+       struct nfs_write_data *data = calldata;
+
+       nfs_commit_release_pages(data);
+       nfs_commit_clear_lock(NFS_I(data->inode));
+       nfs_commitdata_release(calldata);
+}
 
 static const struct rpc_call_ops nfs_commit_ops = {
+#if defined(CONFIG_NFS_V4_1)
+       .rpc_call_prepare = nfs_write_prepare,
+#endif /* CONFIG_NFS_V4_1 */
        .rpc_call_done = nfs_commit_done,
        .rpc_release = nfs_commit_release,
 };
@@ -1312,109 +1484,98 @@ static const struct rpc_call_ops nfs_commit_ops = {
 int nfs_commit_inode(struct inode *inode, int how)
 {
        LIST_HEAD(head);
+       int may_wait = how & FLUSH_SYNC;
        int res;
 
+       res = nfs_commit_set_lock(NFS_I(inode), may_wait);
+       if (res <= 0)
+               goto out_mark_dirty;
        spin_lock(&inode->i_lock);
        res = nfs_scan_commit(inode, &head, 0, 0);
        spin_unlock(&inode->i_lock);
        if (res) {
-               int error = nfs_commit_list(inode, &head, how);
+               int error;
+
+               error = pnfs_commit_list(inode, &head, how);
+               if (error == PNFS_NOT_ATTEMPTED)
+                       error = nfs_commit_list(inode, &head, how);
                if (error < 0)
                        return error;
-       }
+               if (!may_wait)
+                       goto out_mark_dirty;
+               error = wait_on_bit(&NFS_I(inode)->flags,
+                               NFS_INO_COMMIT,
+                               nfs_wait_bit_killable,
+                               TASK_KILLABLE);
+               if (error < 0)
+                       return error;
+       } else
+               nfs_commit_clear_lock(NFS_I(inode));
+       return res;
+       /* Note: If we exit without ensuring that the commit is complete,
+        * we must mark the inode as dirty. Otherwise, future calls to
+        * sync_inode() with the WB_SYNC_ALL flag set will fail to ensure
+        * that the data is on the disk.
+        */
+out_mark_dirty:
+       __mark_inode_dirty(inode, I_DIRTY_DATASYNC);
        return res;
 }
-#else
-static inline int nfs_commit_list(struct inode *inode, struct list_head *head, int how)
-{
-       return 0;
-}
-#endif
 
-long nfs_sync_mapping_wait(struct address_space *mapping, struct writeback_control *wbc, int how)
+static int nfs_commit_unstable_pages(struct inode *inode, struct writeback_control *wbc)
 {
-       struct inode *inode = mapping->host;
-       pgoff_t idx_start, idx_end;
-       unsigned int npages = 0;
-       LIST_HEAD(head);
-       int nocommit = how & FLUSH_NOCOMMIT;
-       long pages, ret;
-
-       /* FIXME */
-       if (wbc->range_cyclic)
-               idx_start = 0;
-       else {
-               idx_start = wbc->range_start >> PAGE_CACHE_SHIFT;
-               idx_end = wbc->range_end >> PAGE_CACHE_SHIFT;
-               if (idx_end > idx_start) {
-                       pgoff_t l_npages = 1 + idx_end - idx_start;
-                       npages = l_npages;
-                       if (sizeof(npages) != sizeof(l_npages) &&
-                                       (pgoff_t)npages != l_npages)
-                               npages = 0;
-               }
+       struct nfs_inode *nfsi = NFS_I(inode);
+       int flags = FLUSH_SYNC;
+       int ret = 0;
+
+       if (wbc->sync_mode == WB_SYNC_NONE) {
+               /* Don't commit yet if this is a non-blocking flush and there
+                * are a lot of outstanding writes for this mapping.
+                */
+               if (nfsi->ncommit <= (nfsi->npages >> 1))
+                       goto out_mark_dirty;
+
+               /* don't wait for the COMMIT response */
+               flags = 0;
        }
-       how &= ~FLUSH_NOCOMMIT;
-       spin_lock(&inode->i_lock);
-       do {
-               ret = nfs_wait_on_requests_locked(inode, idx_start, npages);
-               if (ret != 0)
-                       continue;
-               if (nocommit)
-                       break;
-               pages = nfs_scan_commit(inode, &head, idx_start, npages);
-               if (pages == 0)
-                       break;
-               if (how & FLUSH_INVALIDATE) {
-                       spin_unlock(&inode->i_lock);
-                       nfs_cancel_commit_list(&head);
-                       ret = pages;
-                       spin_lock(&inode->i_lock);
-                       continue;
-               }
-               pages += nfs_scan_commit(inode, &head, 0, 0);
-               spin_unlock(&inode->i_lock);
-               ret = nfs_commit_list(inode, &head, how);
-               spin_lock(&inode->i_lock);
 
-       } while (ret >= 0);
-       spin_unlock(&inode->i_lock);
+       ret = nfs_commit_inode(inode, flags);
+       if (ret >= 0) {
+               if (wbc->sync_mode == WB_SYNC_NONE) {
+                       if (ret < wbc->nr_to_write)
+                               wbc->nr_to_write -= ret;
+                       else
+                               wbc->nr_to_write = 0;
+               }
+               return 0;
+       }
+out_mark_dirty:
+       __mark_inode_dirty(inode, I_DIRTY_DATASYNC);
        return ret;
 }
-
-static int __nfs_write_mapping(struct address_space *mapping, struct writeback_control *wbc, int how)
+#else
+static int nfs_commit_unstable_pages(struct inode *inode, struct writeback_control *wbc)
 {
-       int ret;
-
-       ret = nfs_writepages(mapping, wbc);
-       if (ret < 0)
-               goto out;
-       ret = nfs_sync_mapping_wait(mapping, wbc, how);
-       if (ret < 0)
-               goto out;
        return 0;
-out:
-       __mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
-       return ret;
 }
+#endif
 
-/* Two pass sync: first using WB_SYNC_NONE, then WB_SYNC_ALL */
-static int nfs_write_mapping(struct address_space *mapping, int how)
+int nfs_write_inode(struct inode *inode, struct writeback_control *wbc)
 {
-       struct writeback_control wbc = {
-               .bdi = mapping->backing_dev_info,
-               .sync_mode = WB_SYNC_NONE,
-               .nr_to_write = LONG_MAX,
-               .for_writepages = 1,
-               .range_cyclic = 1,
-       };
        int ret;
 
-       ret = __nfs_write_mapping(mapping, &wbc, how);
-       if (ret < 0)
-               return ret;
-       wbc.sync_mode = WB_SYNC_ALL;
-       return __nfs_write_mapping(mapping, &wbc, how);
+       ret = nfs_commit_unstable_pages(inode, wbc);
+       if (ret >= 0 && test_bit(NFS_INO_LAYOUTCOMMIT, &NFS_I(inode)->flags)) {
+               int status, sync = wbc->sync_mode;
+
+               if (wbc->nonblocking || wbc->for_background)
+                               sync = 0;
+
+               status = pnfs_layoutcommit_inode(inode, sync);
+               if (status < 0)
+                       return status;
+       }
+       return ret;
 }
 
 /*
@@ -1422,37 +1583,27 @@ static int nfs_write_mapping(struct address_space *mapping, int how)
  */
 int nfs_wb_all(struct inode *inode)
 {
-       return nfs_write_mapping(inode->i_mapping, 0);
-}
+       struct writeback_control wbc = {
+               .sync_mode = WB_SYNC_ALL,
+               .nr_to_write = LONG_MAX,
+               .range_start = 0,
+               .range_end = LLONG_MAX,
+       };
 
-int nfs_wb_nocommit(struct inode *inode)
-{
-       return nfs_write_mapping(inode->i_mapping, FLUSH_NOCOMMIT);
+       return sync_inode(inode, &wbc);
 }
 
 int nfs_wb_page_cancel(struct inode *inode, struct page *page)
 {
        struct nfs_page *req;
-       loff_t range_start = page_offset(page);
-       loff_t range_end = range_start + (loff_t)(PAGE_CACHE_SIZE - 1);
-       struct writeback_control wbc = {
-               .bdi = page->mapping->backing_dev_info,
-               .sync_mode = WB_SYNC_ALL,
-               .nr_to_write = LONG_MAX,
-               .range_start = range_start,
-               .range_end = range_end,
-       };
        int ret = 0;
 
        BUG_ON(!PageLocked(page));
        for (;;) {
+               wait_on_page_writeback(page);
                req = nfs_page_find_request(page);
                if (req == NULL)
-                       goto out;
-               if (test_bit(PG_NEED_COMMIT, &req->wb_flags)) {
-                       nfs_release_request(req);
                        break;
-               }
                if (nfs_lock_request_dontget(req)) {
                        nfs_inode_remove_request(req);
                        /*
@@ -1464,53 +1615,81 @@ int nfs_wb_page_cancel(struct inode *inode, struct page *page)
                        break;
                }
                ret = nfs_wait_on_request(req);
+               nfs_release_request(req);
                if (ret < 0)
-                       goto out;
+                       break;
        }
-       if (!PagePrivate(page))
-               return 0;
-       ret = nfs_sync_mapping_wait(page->mapping, &wbc, FLUSH_INVALIDATE);
-out:
        return ret;
 }
 
-static int nfs_wb_page_priority(struct inode *inode, struct page *page,
-                               int how)
+/*
+ * Write back all requests on one page - we do this before reading it.
+ */
+int nfs_wb_page(struct inode *inode, struct page *page)
 {
        loff_t range_start = page_offset(page);
        loff_t range_end = range_start + (loff_t)(PAGE_CACHE_SIZE - 1);
        struct writeback_control wbc = {
-               .bdi = page->mapping->backing_dev_info,
                .sync_mode = WB_SYNC_ALL,
-               .nr_to_write = LONG_MAX,
+               .nr_to_write = 0,
                .range_start = range_start,
                .range_end = range_end,
        };
        int ret;
 
-       BUG_ON(!PageLocked(page));
-       if (clear_page_dirty_for_io(page)) {
-               ret = nfs_writepage_locked(page, &wbc);
+       for (;;) {
+               wait_on_page_writeback(page);
+               if (clear_page_dirty_for_io(page)) {
+                       ret = nfs_writepage_locked(page, &wbc);
+                       if (ret < 0)
+                               goto out_error;
+                       continue;
+               }
+               if (!PagePrivate(page))
+                       break;
+               ret = nfs_commit_inode(inode, FLUSH_SYNC);
                if (ret < 0)
-                       goto out;
+                       goto out_error;
        }
-       if (!PagePrivate(page))
-               return 0;
-       ret = nfs_sync_mapping_wait(page->mapping, &wbc, how);
-       if (ret >= 0)
-               return 0;
-out:
-       __mark_inode_dirty(inode, I_DIRTY_PAGES);
+       return 0;
+out_error:
        return ret;
 }
 
-/*
- * Write back all requests on one page - we do this before reading it.
- */
-int nfs_wb_page(struct inode *inode, struct page* page)
+#ifdef CONFIG_MIGRATION
+int nfs_migrate_page(struct address_space *mapping, struct page *newpage,
+               struct page *page)
 {
-       return nfs_wb_page_priority(inode, page, FLUSH_STABLE);
+       struct nfs_page *req;
+       int ret;
+
+       nfs_fscache_release_page(page, GFP_KERNEL);
+
+       req = nfs_find_and_lock_request(page, false);
+       ret = PTR_ERR(req);
+       if (IS_ERR(req))
+               goto out;
+
+       ret = migrate_page(mapping, newpage, page);
+       if (!req)
+               goto out;
+       if (ret)
+               goto out_unlock;
+       page_cache_get(newpage);
+       spin_lock(&mapping->host->i_lock);
+       req->wb_page = newpage;
+       SetPagePrivate(newpage);
+       set_page_private(newpage, (unsigned long)req);
+       ClearPagePrivate(page);
+       set_page_private(page, 0);
+       spin_unlock(&mapping->host->i_lock);
+       page_cache_release(page);
+out_unlock:
+       nfs_clear_page_tag_locked(req);
+out:
+       return ret;
 }
+#endif
 
 int __init nfs_init_writepagecache(void)
 {