]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/nfs/write.c
Merge tag 'nfs-for-3.16-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
[karo-tx-linux.git] / fs / nfs / write.c
index d877f15fb31a1c50660eea7b8897136fa97597ff..3ee5af4e738efb0f014d5b9dbd9963a179de5dc5 100644 (file)
@@ -154,18 +154,78 @@ static void nfs_set_pageerror(struct page *page)
        nfs_zap_mapping(page_file_mapping(page)->host, page_file_mapping(page));
 }
 
+/*
+ * nfs_page_group_search_locked
+ * @head - head request of page group
+ * @page_offset - offset into page
+ *
+ * Search page group with head @head to find a request that contains the
+ * page offset @page_offset.
+ *
+ * Returns a pointer to the first matching nfs request, or NULL if no
+ * match is found.
+ *
+ * Must be called with the page group lock held
+ */
+static struct nfs_page *
+nfs_page_group_search_locked(struct nfs_page *head, unsigned int page_offset)
+{
+       struct nfs_page *req;
+
+       WARN_ON_ONCE(head != head->wb_head);
+       WARN_ON_ONCE(!test_bit(PG_HEADLOCK, &head->wb_head->wb_flags));
+
+       req = head;
+       do {
+               if (page_offset >= req->wb_pgbase &&
+                   page_offset < (req->wb_pgbase + req->wb_bytes))
+                       return req;
+
+               req = req->wb_this_page;
+       } while (req != head);
+
+       return NULL;
+}
+
+/*
+ * nfs_page_group_covers_page
+ * @head - head request of page group
+ *
+ * Return true if the page group with head @head covers the whole page,
+ * returns false otherwise
+ */
+static bool nfs_page_group_covers_page(struct nfs_page *req)
+{
+       struct nfs_page *tmp;
+       unsigned int pos = 0;
+       unsigned int len = nfs_page_length(req->wb_page);
+
+       nfs_page_group_lock(req);
+
+       do {
+               tmp = nfs_page_group_search_locked(req->wb_head, pos);
+               if (tmp) {
+                       /* no way this should happen */
+                       WARN_ON_ONCE(tmp->wb_pgbase != pos);
+                       pos += tmp->wb_bytes - (pos - tmp->wb_pgbase);
+               }
+       } while (tmp && pos < len);
+
+       nfs_page_group_unlock(req);
+       WARN_ON_ONCE(pos > len);
+       return pos == len;
+}
+
 /* We can set the PG_uptodate flag if we see that a write request
  * covers the full page.
  */
-static void nfs_mark_uptodate(struct page *page, unsigned int base, unsigned int count)
+static void nfs_mark_uptodate(struct nfs_page *req)
 {
-       if (PageUptodate(page))
-               return;
-       if (base != 0)
+       if (PageUptodate(req->wb_page))
                return;
-       if (count != nfs_page_length(page))
+       if (!nfs_page_group_covers_page(req))
                return;
-       SetPageUptodate(page);
+       SetPageUptodate(req->wb_page);
 }
 
 static int wb_priority(struct writeback_control *wbc)
@@ -201,12 +261,15 @@ static void nfs_set_page_writeback(struct page *page)
        }
 }
 
-static void nfs_end_page_writeback(struct page *page)
+static void nfs_end_page_writeback(struct nfs_page *req)
 {
-       struct inode *inode = page_file_mapping(page)->host;
+       struct inode *inode = page_file_mapping(req->wb_page)->host;
        struct nfs_server *nfss = NFS_SERVER(inode);
 
-       end_page_writeback(page);
+       if (!nfs_page_group_sync_on_bit(req, PG_WB_END))
+               return;
+
+       end_page_writeback(req->wb_page);
        if (atomic_long_dec_return(&nfss->writeback) < NFS_CONGESTION_OFF_THRESH)
                clear_bdi_congested(&nfss->backing_dev_info, BLK_RW_ASYNC);
 }
@@ -347,7 +410,7 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
        nfs_pageio_complete(&pgio);
 
        clear_bit_unlock(NFS_INO_FLUSHING, bitlock);
-       smp_mb__after_clear_bit();
+       smp_mb__after_atomic();
        wake_up_bit(bitlock, NFS_INO_FLUSHING);
 
        if (err < 0)
@@ -367,6 +430,8 @@ static void nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
 
+       WARN_ON_ONCE(req->wb_this_page != req);
+
        /* Lock the request! */
        nfs_lock_request(req);
 
@@ -383,6 +448,7 @@ static void nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
                set_page_private(req->wb_page, (unsigned long)req);
        }
        nfsi->npages++;
+       set_bit(PG_INODE_REF, &req->wb_flags);
        kref_get(&req->wb_kref);
        spin_unlock(&inode->i_lock);
 }
@@ -394,15 +460,20 @@ static void nfs_inode_remove_request(struct nfs_page *req)
 {
        struct inode *inode = req->wb_context->dentry->d_inode;
        struct nfs_inode *nfsi = NFS_I(inode);
+       struct nfs_page *head;
 
-       spin_lock(&inode->i_lock);
-       if (likely(!PageSwapCache(req->wb_page))) {
-               set_page_private(req->wb_page, 0);
-               ClearPagePrivate(req->wb_page);
-               clear_bit(PG_MAPPED, &req->wb_flags);
+       if (nfs_page_group_sync_on_bit(req, PG_REMOVE)) {
+               head = req->wb_head;
+
+               spin_lock(&inode->i_lock);
+               if (likely(!PageSwapCache(head->wb_page))) {
+                       set_page_private(head->wb_page, 0);
+                       ClearPagePrivate(head->wb_page);
+                       clear_bit(PG_MAPPED, &head->wb_flags);
+               }
+               nfsi->npages--;
+               spin_unlock(&inode->i_lock);
        }
-       nfsi->npages--;
-       spin_unlock(&inode->i_lock);
        nfs_release_request(req);
 }
 
@@ -567,6 +638,7 @@ static void nfs_write_completion(struct nfs_pgio_header *hdr)
 {
        struct nfs_commit_info cinfo;
        unsigned long bytes = 0;
+       bool do_destroy;
 
        if (test_bit(NFS_IOHDR_REDO, &hdr->flags))
                goto out;
@@ -595,7 +667,8 @@ remove_req:
                nfs_inode_remove_request(req);
 next:
                nfs_unlock_request(req);
-               nfs_end_page_writeback(req->wb_page);
+               nfs_end_page_writeback(req);
+               do_destroy = !test_bit(NFS_IOHDR_NEED_COMMIT, &hdr->flags);
                nfs_release_request(req);
        }
 out:
@@ -603,7 +676,7 @@ out:
 }
 
 #if  IS_ENABLED(CONFIG_NFS_V3) || IS_ENABLED(CONFIG_NFS_V4)
-static unsigned long
+unsigned long
 nfs_reqs_to_commit(struct nfs_commit_info *cinfo)
 {
        return cinfo->mds->ncommit;
@@ -660,7 +733,7 @@ nfs_scan_commit(struct inode *inode, struct list_head *dst,
 }
 
 #else
-static unsigned long nfs_reqs_to_commit(struct nfs_commit_info *cinfo)
+unsigned long nfs_reqs_to_commit(struct nfs_commit_info *cinfo)
 {
        return 0;
 }
@@ -700,6 +773,10 @@ static struct nfs_page *nfs_try_to_update_request(struct inode *inode,
                if (req == NULL)
                        goto out_unlock;
 
+               /* should be handled by nfs_flush_incompatible */
+               WARN_ON_ONCE(req->wb_head != req);
+               WARN_ON_ONCE(req->wb_this_page != req);
+
                rqend = req->wb_offset + req->wb_bytes;
                /*
                 * Tell the caller to flush out the request if
@@ -761,7 +838,7 @@ static struct nfs_page * nfs_setup_write_request(struct nfs_open_context* ctx,
        req = nfs_try_to_update_request(inode, page, offset, bytes);
        if (req != NULL)
                goto out;
-       req = nfs_create_request(ctx, inode, page, offset, bytes);
+       req = nfs_create_request(ctx, page, NULL, offset, bytes);
        if (IS_ERR(req))
                goto out;
        nfs_inode_add_request(inode, req);
@@ -779,7 +856,7 @@ static int nfs_writepage_setup(struct nfs_open_context *ctx, struct page *page,
                return PTR_ERR(req);
        /* Update file length */
        nfs_grow_file(page, offset, count);
-       nfs_mark_uptodate(page, req->wb_pgbase, req->wb_bytes);
+       nfs_mark_uptodate(req);
        nfs_mark_request_dirty(req);
        nfs_unlock_and_release_request(req);
        return 0;
@@ -805,6 +882,8 @@ int nfs_flush_incompatible(struct file *file, struct page *page)
                        return 0;
                l_ctx = req->wb_lock_context;
                do_flush = req->wb_page != page || req->wb_context != ctx;
+               /* for now, flush if more than 1 request in page_group */
+               do_flush |= req->wb_this_page != req;
                if (l_ctx && ctx->dentry->d_inode->i_flock != NULL) {
                        do_flush |= l_ctx->lockowner.l_owner != current->files
                                || l_ctx->lockowner.l_pid != current->tgid;
@@ -932,126 +1011,17 @@ static int flush_task_priority(int how)
        return RPC_PRIORITY_NORMAL;
 }
 
-int nfs_initiate_write(struct rpc_clnt *clnt,
-                      struct nfs_pgio_data *data,
-                      const struct rpc_call_ops *call_ops,
-                      int how, int flags)
+static void nfs_initiate_write(struct nfs_pgio_data *data, struct rpc_message *msg,
+                              struct rpc_task_setup *task_setup_data, int how)
 {
        struct inode *inode = data->header->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 = data->header->cred,
-       };
-       struct rpc_task_setup task_setup_data = {
-               .rpc_client = clnt,
-               .task = &data->task,
-               .rpc_message = &msg,
-               .callback_ops = call_ops,
-               .callback_data = data,
-               .workqueue = nfsiod_workqueue,
-               .flags = RPC_TASK_ASYNC | flags,
-               .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/%llu, %u bytes @ offset %llu)\n",
-               data->task.tk_pid,
-               inode->i_sb->s_id,
-               (unsigned long long)NFS_FILEID(inode),
-               data->args.count,
-               (unsigned long long)data->args.offset);
+       task_setup_data->priority = priority;
+       NFS_PROTO(inode)->write_setup(data, msg);
 
        nfs4_state_protect_write(NFS_SERVER(inode)->nfs_client,
-                                &task_setup_data.rpc_client, &msg, data);
-
-       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 void nfs_write_rpcsetup(struct nfs_pgio_data *data,
-               unsigned int count, unsigned int offset,
-               int how, struct nfs_commit_info *cinfo)
-{
-       struct nfs_page *req = data->header->req;
-
-       /* Set up the RPC argument and reply structs
-        * NB: take care not to mess about with data->commit et al. */
-
-       data->args.fh     = NFS_FH(data->header->inode);
-       data->args.offset = req_offset(req) + offset;
-       /* pnfs_set_layoutcommit needs this */
-       data->mds_offset = data->args.offset;
-       data->args.pgbase = req->wb_pgbase + offset;
-       data->args.pages  = data->pages.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;
-       switch (how & (FLUSH_STABLE | FLUSH_COND_STABLE)) {
-       case 0:
-               break;
-       case FLUSH_COND_STABLE:
-               if (nfs_reqs_to_commit(cinfo))
-                       break;
-       default:
-               data->args.stable = NFS_FILE_SYNC;
-       }
-
-       data->res.fattr   = &data->fattr;
-       data->res.count   = count;
-       data->res.verf    = &data->verf;
-       nfs_fattr_init(&data->fattr);
-}
-
-static int nfs_do_write(struct nfs_pgio_data *data,
-               const struct rpc_call_ops *call_ops,
-               int how)
-{
-       struct inode *inode = data->header->inode;
-
-       return nfs_initiate_write(NFS_CLIENT(inode), data, call_ops, how, 0);
-}
-
-static int nfs_do_multiple_writes(struct list_head *head,
-               const struct rpc_call_ops *call_ops,
-               int how)
-{
-       struct nfs_pgio_data *data;
-       int ret = 0;
-
-       while (!list_empty(head)) {
-               int ret2;
-
-               data = list_first_entry(head, struct nfs_pgio_data, list);
-               list_del_init(&data->list);
-               
-               ret2 = nfs_do_write(data, call_ops, how);
-                if (ret == 0)
-                        ret = ret2;
-       }
-       return ret;
+                                &task_setup_data->rpc_client, msg, data);
 }
 
 /* If a nfs_flush_* function fails, it should remove reqs from @head and
@@ -1062,7 +1032,7 @@ static void nfs_redirty_request(struct nfs_page *req)
 {
        nfs_mark_request_dirty(req);
        nfs_unlock_request(req);
-       nfs_end_page_writeback(req->wb_page);
+       nfs_end_page_writeback(req);
        nfs_release_request(req);
 }
 
@@ -1082,153 +1052,12 @@ static const struct nfs_pgio_completion_ops nfs_async_write_completion_ops = {
        .completion = nfs_write_completion,
 };
 
-static void nfs_flush_error(struct nfs_pageio_descriptor *desc,
-               struct nfs_pgio_header *hdr)
-{
-       set_bit(NFS_IOHDR_REDO, &hdr->flags);
-       while (!list_empty(&hdr->rpc_list)) {
-               struct nfs_pgio_data *data = list_first_entry(&hdr->rpc_list,
-                               struct nfs_pgio_data, list);
-               list_del(&data->list);
-               nfs_pgio_data_release(data);
-       }
-       desc->pg_completion_ops->error_cleanup(&desc->pg_list);
-}
-
-/*
- * Generate multiple small requests to write out a single
- * contiguous dirty area on one page.
- */
-static int nfs_flush_multi(struct nfs_pageio_descriptor *desc,
-                          struct nfs_pgio_header *hdr)
-{
-       struct nfs_page *req = hdr->req;
-       struct page *page = req->wb_page;
-       struct nfs_pgio_data *data;
-       size_t wsize = desc->pg_bsize, nbytes;
-       unsigned int offset;
-       int requests = 0;
-       struct nfs_commit_info cinfo;
-
-       nfs_init_cinfo(&cinfo, desc->pg_inode, desc->pg_dreq);
-
-       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
-           (desc->pg_moreio || nfs_reqs_to_commit(&cinfo) ||
-            desc->pg_count > wsize))
-               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
-
-
-       offset = 0;
-       nbytes = desc->pg_count;
-       do {
-               size_t len = min(nbytes, wsize);
-
-               data = nfs_pgio_data_alloc(hdr, 1);
-               if (!data) {
-                       nfs_flush_error(desc, hdr);
-                       return -ENOMEM;
-               }
-               data->pages.pagevec[0] = page;
-               nfs_write_rpcsetup(data, len, offset, desc->pg_ioflags, &cinfo);
-               list_add(&data->list, &hdr->rpc_list);
-               requests++;
-               nbytes -= len;
-               offset += len;
-       } while (nbytes != 0);
-       nfs_list_remove_request(req);
-       nfs_list_add_request(req, &hdr->pages);
-       desc->pg_rpc_callops = &nfs_pgio_common_ops;
-       return 0;
-}
-
-/*
- * Create an RPC task for the given write request and kick it.
- * The page must have been locked by the caller.
- *
- * It may happen that the page we're passed is not marked dirty.
- * This is the case if nfs_updatepage detects a conflicting request
- * that has been written but not committed.
- */
-static int nfs_flush_one(struct nfs_pageio_descriptor *desc,
-                        struct nfs_pgio_header *hdr)
-{
-       struct nfs_page         *req;
-       struct page             **pages;
-       struct nfs_pgio_data    *data;
-       struct list_head *head = &desc->pg_list;
-       struct nfs_commit_info cinfo;
-
-       data = nfs_pgio_data_alloc(hdr, nfs_page_array_len(desc->pg_base,
-                                                          desc->pg_count));
-       if (!data) {
-               nfs_flush_error(desc, hdr);
-               return -ENOMEM;
-       }
-
-       nfs_init_cinfo(&cinfo, desc->pg_inode, desc->pg_dreq);
-       pages = data->pages.pagevec;
-       while (!list_empty(head)) {
-               req = nfs_list_entry(head->next);
-               nfs_list_remove_request(req);
-               nfs_list_add_request(req, &hdr->pages);
-               *pages++ = req->wb_page;
-       }
-
-       if ((desc->pg_ioflags & FLUSH_COND_STABLE) &&
-           (desc->pg_moreio || nfs_reqs_to_commit(&cinfo)))
-               desc->pg_ioflags &= ~FLUSH_COND_STABLE;
-
-       /* Set up the argument struct */
-       nfs_write_rpcsetup(data, desc->pg_count, 0, desc->pg_ioflags, &cinfo);
-       list_add(&data->list, &hdr->rpc_list);
-       desc->pg_rpc_callops = &nfs_pgio_common_ops;
-       return 0;
-}
-
-int nfs_generic_flush(struct nfs_pageio_descriptor *desc,
-                     struct nfs_pgio_header *hdr)
-{
-       if (desc->pg_bsize < PAGE_CACHE_SIZE)
-               return nfs_flush_multi(desc, hdr);
-       return nfs_flush_one(desc, hdr);
-}
-EXPORT_SYMBOL_GPL(nfs_generic_flush);
-
-static int nfs_generic_pg_writepages(struct nfs_pageio_descriptor *desc)
-{
-       struct nfs_rw_header *whdr;
-       struct nfs_pgio_header *hdr;
-       int ret;
-
-       whdr = nfs_rw_header_alloc(desc->pg_rw_ops);
-       if (!whdr) {
-               desc->pg_completion_ops->error_cleanup(&desc->pg_list);
-               return -ENOMEM;
-       }
-       hdr = &whdr->header;
-       nfs_pgheader_init(desc, hdr, nfs_rw_header_free);
-       atomic_inc(&hdr->refcnt);
-       ret = nfs_generic_flush(desc, hdr);
-       if (ret == 0)
-               ret = nfs_do_multiple_writes(&hdr->rpc_list,
-                                            desc->pg_rpc_callops,
-                                            desc->pg_ioflags);
-       if (atomic_dec_and_test(&hdr->refcnt))
-               hdr->completion_ops->completion(hdr);
-       return ret;
-}
-
-static const struct nfs_pageio_ops nfs_pageio_write_ops = {
-       .pg_test = nfs_generic_pg_test,
-       .pg_doio = nfs_generic_pg_writepages,
-};
-
 void nfs_pageio_init_write(struct nfs_pageio_descriptor *pgio,
                               struct inode *inode, int ioflags, bool force_mds,
                               const struct nfs_pgio_completion_ops *compl_ops)
 {
        struct nfs_server *server = NFS_SERVER(inode);
-       const struct nfs_pageio_ops *pg_ops = &nfs_pageio_write_ops;
+       const struct nfs_pageio_ops *pg_ops = &nfs_pgio_rw_ops;
 
 #ifdef CONFIG_NFS_V4_1
        if (server->pnfs_curr_ld && !force_mds)
@@ -1241,7 +1070,7 @@ EXPORT_SYMBOL_GPL(nfs_pageio_init_write);
 
 void nfs_pageio_reset_write_mds(struct nfs_pageio_descriptor *pgio)
 {
-       pgio->pg_ops = &nfs_pageio_write_ops;
+       pgio->pg_ops = &nfs_pgio_rw_ops;
        pgio->pg_bsize = NFS_SERVER(pgio->pg_inode)->wsize;
 }
 EXPORT_SYMBOL_GPL(nfs_pageio_reset_write_mds);
@@ -1408,7 +1237,7 @@ static int nfs_commit_set_lock(struct nfs_inode *nfsi, int may_wait)
 static void nfs_commit_clear_lock(struct nfs_inode *nfsi)
 {
        clear_bit(NFS_INO_COMMIT, &nfsi->flags);
-       smp_mb__after_clear_bit();
+       smp_mb__after_atomic();
        wake_up_bit(&nfsi->flags, NFS_INO_COMMIT);
 }
 
@@ -1893,4 +1722,5 @@ static const struct nfs_rw_ops nfs_rw_write_ops = {
        .rw_release             = nfs_writeback_release_common,
        .rw_done                = nfs_writeback_done,
        .rw_result              = nfs_writeback_result,
+       .rw_initiate            = nfs_initiate_write,
 };