Merge branch 'x86-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git...
[pandora-kernel.git] / fs / nfs / write.c
index bed6341..f333848 100644 (file)
@@ -48,7 +48,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);
 
@@ -59,19 +59,13 @@ struct nfs_write_data *nfs_commit_alloc(void)
        return p;
 }
 
-static void nfs_commit_rcu_free(struct rcu_head *head)
+void nfs_commit_free(struct nfs_write_data *p)
 {
-       struct nfs_write_data *p = container_of(head, struct nfs_write_data, task.u.tk_rcu);
        if (p && (p->pagevec != &p->page_array[0]))
                kfree(p->pagevec);
        mempool_free(p, nfs_commit_mempool);
 }
 
-void nfs_commit_free(struct nfs_write_data *wdata)
-{
-       call_rcu_bh(&wdata->task.u.tk_rcu, nfs_commit_rcu_free);
-}
-
 struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
 {
        struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS);
@@ -93,21 +87,18 @@ struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
        return p;
 }
 
-static void nfs_writedata_rcu_free(struct rcu_head *head)
+static void nfs_writedata_free(struct nfs_write_data *p)
 {
-       struct nfs_write_data *p = container_of(head, struct nfs_write_data, task.u.tk_rcu);
        if (p && (p->pagevec != &p->page_array[0]))
                kfree(p->pagevec);
        mempool_free(p, nfs_wdata_mempool);
 }
 
-static void nfs_writedata_free(struct nfs_write_data *wdata)
+void nfs_writedata_release(void *data)
 {
-       call_rcu_bh(&wdata->task.u.tk_rcu, nfs_writedata_rcu_free);
-}
+       struct nfs_write_data *wdata = data;
 
-void nfs_writedata_release(void *wdata)
-{
+       put_nfs_open_context(wdata->args.context);
        nfs_writedata_free(wdata);
 }
 
@@ -291,8 +282,6 @@ static int nfs_page_async_flush(struct nfs_pageio_descriptor *pgio,
        spin_unlock(&inode->i_lock);
        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;
        }
        return 0;
@@ -366,15 +355,13 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
 /*
  * Insert a write request into an inode
  */
-static int nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
+static void nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        int error;
 
        error = radix_tree_insert(&nfsi->nfs_page_tree, req->wb_index, req);
-       BUG_ON(error == -EEXIST);
-       if (error)
-               return error;
+       BUG_ON(error);
        if (!nfsi->npages) {
                igrab(inode);
                if (nfs_have_delegation(inode, FMODE_WRITE))
@@ -384,8 +371,8 @@ static int nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
        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);
-       return 0;
+       radix_tree_tag_set(&nfsi->nfs_page_tree, req->wb_index,
+                               NFS_PAGE_TAG_LOCKED);
 }
 
 /*
@@ -413,7 +400,7 @@ static void nfs_inode_remove_request(struct nfs_page *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);
 }
@@ -428,7 +415,7 @@ nfs_dirty_request(struct nfs_page *req)
 
        if (page == NULL || test_bit(PG_NEED_COMMIT, &req->wb_flags))
                return 0;
-       return !PageWriteback(req->wb_page);
+       return !PageWriteback(page);
 }
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
@@ -467,7 +454,7 @@ int nfs_reschedule_unstable_write(struct nfs_page *req)
                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;
@@ -597,6 +584,13 @@ static struct nfs_page * nfs_update_request(struct nfs_open_context* ctx,
                /* Loop over all inode entries and see if we find
                 * A request for the page we wish to update
                 */
+               if (new) {
+                       if (radix_tree_preload(GFP_NOFS)) {
+                               nfs_release_request(new);
+                               return ERR_PTR(-ENOMEM);
+                       }
+               }
+
                spin_lock(&inode->i_lock);
                req = nfs_page_find_request_locked(page);
                if (req) {
@@ -607,28 +601,27 @@ static struct nfs_page * nfs_update_request(struct nfs_open_context* ctx,
                                error = nfs_wait_on_request(req);
                                nfs_release_request(req);
                                if (error < 0) {
-                                       if (new)
+                                       if (new) {
+                                               radix_tree_preload_end();
                                                nfs_release_request(new);
+                                       }
                                        return ERR_PTR(error);
                                }
                                continue;
                        }
                        spin_unlock(&inode->i_lock);
-                       if (new)
+                       if (new) {
+                               radix_tree_preload_end();
                                nfs_release_request(new);
+                       }
                        break;
                }
 
                if (new) {
-                       int error;
                        nfs_lock_request_dontget(new);
-                       error = nfs_inode_add_request(inode, new);
-                       if (error) {
-                               spin_unlock(&inode->i_lock);
-                               nfs_unlock_request(new);
-                               return ERR_PTR(error);
-                       }
+                       nfs_inode_add_request(inode, new);
                        spin_unlock(&inode->i_lock);
+                       radix_tree_preload_end();
                        req = new;
                        goto zero_page;
                }
@@ -746,12 +739,13 @@ int nfs_updatepage(struct file *file, struct page *page,
        }
 
        status = nfs_writepage_setup(ctx, page, offset, count);
-       __set_page_dirty_nobuffers(page);
+       if (status < 0)
+               nfs_set_pageerror(page);
+       else
+               __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);
        return status;
 }
 
@@ -785,7 +779,7 @@ static int flush_task_priority(int how)
 /*
  * Set up the argument/result storage required for the RPC call.
  */
-static void nfs_write_rpcsetup(struct nfs_page *req,
+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,
@@ -806,6 +800,7 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
                .rpc_message = &msg,
                .callback_ops = call_ops,
                .callback_data = data,
+               .workqueue = nfsiod_workqueue,
                .flags = flags,
                .priority = priority,
        };
@@ -822,7 +817,7 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
        data->args.pgbase = req->wb_pgbase + offset;
        data->args.pages  = data->pagevec;
        data->args.count  = count;
-       data->args.context = req->wb_context;
+       data->args.context = get_nfs_open_context(req->wb_context);
        data->args.stable  = NFS_UNSTABLE;
        if (how & FLUSH_STABLE) {
                data->args.stable = NFS_DATA_SYNC;
@@ -847,8 +842,21 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
                (unsigned long long)data->args.offset);
 
        task = rpc_run_task(&task_setup_data);
-       if (!IS_ERR(task))
-               rpc_put_task(task);
+       if (IS_ERR(task))
+               return PTR_ERR(task);
+       rpc_put_task(task);
+       return 0;
+}
+
+/* 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)
+{
+       nfs_mark_request_dirty(req);
+       nfs_end_page_writeback(req->wb_page);
+       nfs_clear_page_tag_locked(req);
 }
 
 /*
@@ -863,6 +871,7 @@ static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned
        size_t wsize = NFS_SERVER(inode)->wsize, nbytes;
        unsigned int offset;
        int requests = 0;
+       int ret = 0;
        LIST_HEAD(list);
 
        nfs_list_remove_request(req);
@@ -884,6 +893,8 @@ static int nfs_flush_multi(struct inode *inode, struct list_head *head, unsigned
        offset = 0;
        nbytes = count;
        do {
+               int ret2;
+
                data = list_entry(list.next, struct nfs_write_data, pages);
                list_del_init(&data->pages);
 
@@ -891,13 +902,15 @@ 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,
+               ret2 = nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
                                   wsize, offset, how);
+               if (ret == 0)
+                       ret = ret2;
                offset += wsize;
                nbytes -= wsize;
        } while (nbytes != 0);
 
-       return 0;
+       return ret;
 
 out_bad:
        while (!list_empty(&list)) {
@@ -906,8 +919,6 @@ out_bad:
                nfs_writedata_release(data);
        }
        nfs_redirty_request(req);
-       nfs_end_page_writeback(req->wb_page);
-       nfs_clear_page_tag_locked(req);
        return -ENOMEM;
 }
 
@@ -940,16 +951,12 @@ static int nfs_flush_one(struct inode *inode, struct list_head *head, unsigned i
        req = nfs_list_entry(data->pages.next);
 
        /* Set up the argument struct */
-       nfs_write_rpcsetup(req, data, &nfs_write_full_ops, count, 0, how);
-
-       return 0;
+       return nfs_write_rpcsetup(req, data, &nfs_write_full_ops, count, 0, how);
  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;
 }
@@ -972,7 +979,6 @@ 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,
@@ -980,13 +986,20 @@ static void nfs_writeback_done_partial(struct rpc_task *task, void *calldata)
                req->wb_bytes,
                (long long)req_offset(req));
 
-       if (nfs_writeback_done(task, data) != 0)
-               return;
+       nfs_writeback_done(task, data);
+}
+
+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 (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 out;
        }
 
@@ -1011,11 +1024,12 @@ 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_writedata_release(calldata);
 }
 
 static const struct rpc_call_ops nfs_write_partial_ops = {
        .rpc_call_done = nfs_writeback_done_partial,
-       .rpc_release = nfs_writedata_release,
+       .rpc_release = nfs_writeback_release_partial,
 };
 
 /*
@@ -1028,17 +1042,21 @@ 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)",
                        req->wb_context->path.dentry->d_inode->i_sb->s_id,
@@ -1046,10 +1064,10 @@ static void nfs_writeback_done_full(struct rpc_task *task, void *calldata)
                        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;
                }
 
@@ -1069,11 +1087,12 @@ remove_request:
        next:
                nfs_clear_page_tag_locked(req);
        }
+       nfs_writedata_release(calldata);
 }
 
 static const struct rpc_call_ops nfs_write_full_ops = {
        .rpc_call_done = nfs_writeback_done_full,
-       .rpc_release = nfs_writedata_release,
+       .rpc_release = nfs_writeback_release_full,
 };
 
 
@@ -1159,15 +1178,18 @@ int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
 
 
 #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
-void nfs_commit_release(void *wdata)
+void nfs_commitdata_release(void *data)
 {
+       struct nfs_write_data *wdata = data;
+
+       put_nfs_open_context(wdata->args.context);
        nfs_commit_free(wdata);
 }
 
 /*
  * Set up the argument/result storage required for the RPC call.
  */
-static void nfs_commit_rpcsetup(struct list_head *head,
+static int nfs_commit_rpcsetup(struct list_head *head,
                struct nfs_write_data *data,
                int how)
 {
@@ -1187,6 +1209,7 @@ static void nfs_commit_rpcsetup(struct list_head *head,
                .rpc_message = &msg,
                .callback_ops = &nfs_commit_ops,
                .callback_data = data,
+               .workqueue = nfsiod_workqueue,
                .flags = flags,
                .priority = priority,
        };
@@ -1203,6 +1226,7 @@ static void nfs_commit_rpcsetup(struct list_head *head,
        /* Note: we always request a commit of the entire inode */
        data->args.offset = 0;
        data->args.count  = 0;
+       data->args.context = get_nfs_open_context(first->wb_context);
        data->res.count   = 0;
        data->res.fattr   = &data->fattr;
        data->res.verf    = &data->verf;
@@ -1214,8 +1238,10 @@ static void nfs_commit_rpcsetup(struct list_head *head,
        dprintk("NFS: %5u initiated commit call\n", data->task.tk_pid);
 
        task = rpc_run_task(&task_setup_data);
-       if (!IS_ERR(task))
-               rpc_put_task(task);
+       if (IS_ERR(task))
+               return PTR_ERR(task);
+       rpc_put_task(task);
+       return 0;
 }
 
 /*
@@ -1227,15 +1253,13 @@ 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;
+       return nfs_commit_rpcsetup(head, data, how);
  out_bad:
        while (!list_empty(head)) {
                req = nfs_list_entry(head->next);
@@ -1255,7 +1279,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);
@@ -1263,6 +1286,13 @@ 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;
+}
+
+static void nfs_commit_release(void *calldata)
+{
+       struct nfs_write_data   *data = calldata;
+       struct nfs_page         *req;
+       int status = data->task.tk_status;
 
        while (!list_empty(&data->pages)) {
                req = nfs_list_entry(data->pages.next);
@@ -1277,10 +1307,10 @@ static void nfs_commit_done(struct rpc_task *task, void *calldata)
                        (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;
                }
 
@@ -1297,10 +1327,11 @@ static void nfs_commit_done(struct rpc_task *task, void *calldata)
                }
                /* 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);
        }
+       nfs_commitdata_release(calldata);
 }
 
 static const struct rpc_call_ops nfs_commit_ops = {
@@ -1487,18 +1518,19 @@ static int nfs_wb_page_priority(struct inode *inode, struct page *page,
        };
        int ret;
 
-       BUG_ON(!PageLocked(page));
-       if (clear_page_dirty_for_io(page)) {
-               ret = nfs_writepage_locked(page, &wbc);
+       do {
+               if (clear_page_dirty_for_io(page)) {
+                       ret = nfs_writepage_locked(page, &wbc);
+                       if (ret < 0)
+                               goto out_error;
+               } else if (!PagePrivate(page))
+                       break;
+               ret = nfs_sync_mapping_wait(page->mapping, &wbc, how);
                if (ret < 0)
-                       goto out;
-       }
-       if (!PagePrivate(page))
-               return 0;
-       ret = nfs_sync_mapping_wait(page->mapping, &wbc, how);
-       if (ret >= 0)
-               return 0;
-out:
+                       goto out_error;
+       } while (PagePrivate(page));
+       return 0;
+out_error:
        __mark_inode_dirty(inode, I_DIRTY_PAGES);
        return ret;
 }