|
@@ -63,6 +63,7 @@
|
|
#include <linux/smp_lock.h>
|
|
#include <linux/smp_lock.h>
|
|
|
|
|
|
#include "delegation.h"
|
|
#include "delegation.h"
|
|
|
|
+#include "iostat.h"
|
|
|
|
|
|
#define NFSDBG_FACILITY NFSDBG_PAGECACHE
|
|
#define NFSDBG_FACILITY NFSDBG_PAGECACHE
|
|
|
|
|
|
@@ -76,23 +77,52 @@ static struct nfs_page * nfs_update_request(struct nfs_open_context*,
|
|
struct inode *,
|
|
struct inode *,
|
|
struct page *,
|
|
struct page *,
|
|
unsigned int, unsigned int);
|
|
unsigned int, unsigned int);
|
|
-static void nfs_writeback_done_partial(struct nfs_write_data *, int);
|
|
|
|
-static void nfs_writeback_done_full(struct nfs_write_data *, int);
|
|
|
|
static int nfs_wait_on_write_congestion(struct address_space *, int);
|
|
static int nfs_wait_on_write_congestion(struct address_space *, int);
|
|
static int nfs_wait_on_requests(struct inode *, unsigned long, unsigned int);
|
|
static int nfs_wait_on_requests(struct inode *, unsigned long, unsigned int);
|
|
static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
|
|
static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
|
|
unsigned int npages, int how);
|
|
unsigned int npages, int how);
|
|
|
|
+static const struct rpc_call_ops nfs_write_partial_ops;
|
|
|
|
+static const struct rpc_call_ops nfs_write_full_ops;
|
|
|
|
+static const struct rpc_call_ops nfs_commit_ops;
|
|
|
|
|
|
static kmem_cache_t *nfs_wdata_cachep;
|
|
static kmem_cache_t *nfs_wdata_cachep;
|
|
-mempool_t *nfs_wdata_mempool;
|
|
|
|
|
|
+static mempool_t *nfs_wdata_mempool;
|
|
static mempool_t *nfs_commit_mempool;
|
|
static mempool_t *nfs_commit_mempool;
|
|
|
|
|
|
static DECLARE_WAIT_QUEUE_HEAD(nfs_write_congestion);
|
|
static DECLARE_WAIT_QUEUE_HEAD(nfs_write_congestion);
|
|
|
|
|
|
-static inline struct nfs_write_data *nfs_commit_alloc(unsigned int pagecount)
|
|
|
|
|
|
+struct nfs_write_data *nfs_commit_alloc(unsigned int pagecount)
|
|
{
|
|
{
|
|
struct nfs_write_data *p = mempool_alloc(nfs_commit_mempool, SLAB_NOFS);
|
|
struct nfs_write_data *p = mempool_alloc(nfs_commit_mempool, SLAB_NOFS);
|
|
|
|
|
|
|
|
+ if (p) {
|
|
|
|
+ memset(p, 0, sizeof(*p));
|
|
|
|
+ INIT_LIST_HEAD(&p->pages);
|
|
|
|
+ if (pagecount < NFS_PAGEVEC_SIZE)
|
|
|
|
+ p->pagevec = &p->page_array[0];
|
|
|
|
+ else {
|
|
|
|
+ size_t size = ++pagecount * sizeof(struct page *);
|
|
|
|
+ p->pagevec = kzalloc(size, GFP_NOFS);
|
|
|
|
+ if (!p->pagevec) {
|
|
|
|
+ mempool_free(p, nfs_commit_mempool);
|
|
|
|
+ p = NULL;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return p;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+void nfs_commit_free(struct nfs_write_data *p)
|
|
|
|
+{
|
|
|
|
+ if (p && (p->pagevec != &p->page_array[0]))
|
|
|
|
+ kfree(p->pagevec);
|
|
|
|
+ mempool_free(p, nfs_commit_mempool);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+struct nfs_write_data *nfs_writedata_alloc(unsigned int pagecount)
|
|
|
|
+{
|
|
|
|
+ struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, SLAB_NOFS);
|
|
|
|
+
|
|
if (p) {
|
|
if (p) {
|
|
memset(p, 0, sizeof(*p));
|
|
memset(p, 0, sizeof(*p));
|
|
INIT_LIST_HEAD(&p->pages);
|
|
INIT_LIST_HEAD(&p->pages);
|
|
@@ -104,7 +134,7 @@ static inline struct nfs_write_data *nfs_commit_alloc(unsigned int pagecount)
|
|
if (p->pagevec) {
|
|
if (p->pagevec) {
|
|
memset(p->pagevec, 0, size);
|
|
memset(p->pagevec, 0, size);
|
|
} else {
|
|
} else {
|
|
- mempool_free(p, nfs_commit_mempool);
|
|
|
|
|
|
+ mempool_free(p, nfs_wdata_mempool);
|
|
p = NULL;
|
|
p = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -112,11 +142,11 @@ static inline struct nfs_write_data *nfs_commit_alloc(unsigned int pagecount)
|
|
return p;
|
|
return p;
|
|
}
|
|
}
|
|
|
|
|
|
-static inline void nfs_commit_free(struct nfs_write_data *p)
|
|
|
|
|
|
+void nfs_writedata_free(struct nfs_write_data *p)
|
|
{
|
|
{
|
|
if (p && (p->pagevec != &p->page_array[0]))
|
|
if (p && (p->pagevec != &p->page_array[0]))
|
|
kfree(p->pagevec);
|
|
kfree(p->pagevec);
|
|
- mempool_free(p, nfs_commit_mempool);
|
|
|
|
|
|
+ mempool_free(p, nfs_wdata_mempool);
|
|
}
|
|
}
|
|
|
|
|
|
void nfs_writedata_release(void *wdata)
|
|
void nfs_writedata_release(void *wdata)
|
|
@@ -136,6 +166,7 @@ static void nfs_grow_file(struct page *page, unsigned int offset, unsigned int c
|
|
end = ((loff_t)page->index << PAGE_CACHE_SHIFT) + ((loff_t)offset+count);
|
|
end = ((loff_t)page->index << PAGE_CACHE_SHIFT) + ((loff_t)offset+count);
|
|
if (i_size >= end)
|
|
if (i_size >= end)
|
|
return;
|
|
return;
|
|
|
|
+ nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
|
|
i_size_write(inode, end);
|
|
i_size_write(inode, end);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -225,6 +256,7 @@ static int nfs_writepage_sync(struct nfs_open_context *ctx, struct inode *inode,
|
|
wdata->args.pgbase += result;
|
|
wdata->args.pgbase += result;
|
|
written += result;
|
|
written += result;
|
|
count -= result;
|
|
count -= result;
|
|
|
|
+ nfs_add_stats(inode, NFSIOS_SERVERWRITTENBYTES, result);
|
|
} while (count);
|
|
} while (count);
|
|
/* Update file length */
|
|
/* Update file length */
|
|
nfs_grow_file(page, offset, written);
|
|
nfs_grow_file(page, offset, written);
|
|
@@ -281,6 +313,9 @@ int nfs_writepage(struct page *page, struct writeback_control *wbc)
|
|
int priority = wb_priority(wbc);
|
|
int priority = wb_priority(wbc);
|
|
int err;
|
|
int err;
|
|
|
|
|
|
|
|
+ nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGE);
|
|
|
|
+ nfs_add_stats(inode, NFSIOS_WRITEPAGES, 1);
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Note: We need to ensure that we have a reference to the inode
|
|
* Note: We need to ensure that we have a reference to the inode
|
|
* if we are to do asynchronous writes. If not, waiting
|
|
* if we are to do asynchronous writes. If not, waiting
|
|
@@ -345,6 +380,8 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
|
|
struct inode *inode = mapping->host;
|
|
struct inode *inode = mapping->host;
|
|
int err;
|
|
int err;
|
|
|
|
|
|
|
|
+ nfs_inc_stats(inode, NFSIOS_VFSWRITEPAGES);
|
|
|
|
+
|
|
err = generic_writepages(mapping, wbc);
|
|
err = generic_writepages(mapping, wbc);
|
|
if (err)
|
|
if (err)
|
|
return err;
|
|
return err;
|
|
@@ -356,6 +393,7 @@ int nfs_writepages(struct address_space *mapping, struct writeback_control *wbc)
|
|
err = nfs_flush_inode(inode, 0, 0, wb_priority(wbc));
|
|
err = nfs_flush_inode(inode, 0, 0, wb_priority(wbc));
|
|
if (err < 0)
|
|
if (err < 0)
|
|
goto out;
|
|
goto out;
|
|
|
|
+ nfs_add_stats(inode, NFSIOS_WRITEPAGES, err);
|
|
wbc->nr_to_write -= err;
|
|
wbc->nr_to_write -= err;
|
|
if (!wbc->nonblocking && wbc->sync_mode == WB_SYNC_ALL) {
|
|
if (!wbc->nonblocking && wbc->sync_mode == WB_SYNC_ALL) {
|
|
err = nfs_wait_on_requests(inode, 0, 0);
|
|
err = nfs_wait_on_requests(inode, 0, 0);
|
|
@@ -391,6 +429,7 @@ static int nfs_inode_add_request(struct inode *inode, struct nfs_page *req)
|
|
if (nfs_have_delegation(inode, FMODE_WRITE))
|
|
if (nfs_have_delegation(inode, FMODE_WRITE))
|
|
nfsi->change_attr++;
|
|
nfsi->change_attr++;
|
|
}
|
|
}
|
|
|
|
+ SetPagePrivate(req->wb_page);
|
|
nfsi->npages++;
|
|
nfsi->npages++;
|
|
atomic_inc(&req->wb_count);
|
|
atomic_inc(&req->wb_count);
|
|
return 0;
|
|
return 0;
|
|
@@ -407,6 +446,7 @@ static void nfs_inode_remove_request(struct nfs_page *req)
|
|
BUG_ON (!NFS_WBACK_BUSY(req));
|
|
BUG_ON (!NFS_WBACK_BUSY(req));
|
|
|
|
|
|
spin_lock(&nfsi->req_lock);
|
|
spin_lock(&nfsi->req_lock);
|
|
|
|
+ ClearPagePrivate(req->wb_page);
|
|
radix_tree_delete(&nfsi->nfs_page_tree, req->wb_index);
|
|
radix_tree_delete(&nfsi->nfs_page_tree, req->wb_index);
|
|
nfsi->npages--;
|
|
nfsi->npages--;
|
|
if (!nfsi->npages) {
|
|
if (!nfsi->npages) {
|
|
@@ -499,8 +539,7 @@ nfs_mark_request_commit(struct nfs_page *req)
|
|
*
|
|
*
|
|
* Interruptible by signals only if mounted with intr flag.
|
|
* Interruptible by signals only if mounted with intr flag.
|
|
*/
|
|
*/
|
|
-static int
|
|
|
|
-nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int npages)
|
|
|
|
|
|
+static int nfs_wait_on_requests_locked(struct inode *inode, unsigned long idx_start, unsigned int npages)
|
|
{
|
|
{
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
struct nfs_page *req;
|
|
struct nfs_page *req;
|
|
@@ -513,7 +552,6 @@ nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int
|
|
else
|
|
else
|
|
idx_end = idx_start + npages - 1;
|
|
idx_end = idx_start + npages - 1;
|
|
|
|
|
|
- spin_lock(&nfsi->req_lock);
|
|
|
|
next = idx_start;
|
|
next = idx_start;
|
|
while (radix_tree_gang_lookup_tag(&nfsi->nfs_page_tree, (void **)&req, next, 1, NFS_PAGE_TAG_WRITEBACK)) {
|
|
while (radix_tree_gang_lookup_tag(&nfsi->nfs_page_tree, (void **)&req, next, 1, NFS_PAGE_TAG_WRITEBACK)) {
|
|
if (req->wb_index > idx_end)
|
|
if (req->wb_index > idx_end)
|
|
@@ -526,15 +564,25 @@ nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int
|
|
spin_unlock(&nfsi->req_lock);
|
|
spin_unlock(&nfsi->req_lock);
|
|
error = nfs_wait_on_request(req);
|
|
error = nfs_wait_on_request(req);
|
|
nfs_release_request(req);
|
|
nfs_release_request(req);
|
|
|
|
+ spin_lock(&nfsi->req_lock);
|
|
if (error < 0)
|
|
if (error < 0)
|
|
return error;
|
|
return error;
|
|
- spin_lock(&nfsi->req_lock);
|
|
|
|
res++;
|
|
res++;
|
|
}
|
|
}
|
|
- spin_unlock(&nfsi->req_lock);
|
|
|
|
return res;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int nfs_wait_on_requests(struct inode *inode, unsigned long idx_start, unsigned int npages)
|
|
|
|
+{
|
|
|
|
+ struct nfs_inode *nfsi = NFS_I(inode);
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ spin_lock(&nfsi->req_lock);
|
|
|
|
+ ret = nfs_wait_on_requests_locked(inode, idx_start, npages);
|
|
|
|
+ spin_unlock(&nfsi->req_lock);
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* nfs_scan_dirty - Scan an inode for dirty requests
|
|
* nfs_scan_dirty - Scan an inode for dirty requests
|
|
* @inode: NFS inode to scan
|
|
* @inode: NFS inode to scan
|
|
@@ -586,6 +634,11 @@ nfs_scan_commit(struct inode *inode, struct list_head *dst, unsigned long idx_st
|
|
}
|
|
}
|
|
return res;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
+#else
|
|
|
|
+static inline int nfs_scan_commit(struct inode *inode, struct list_head *dst, unsigned long idx_start, unsigned int npages)
|
|
|
|
+{
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
static int nfs_wait_on_write_congestion(struct address_space *mapping, int intr)
|
|
static int nfs_wait_on_write_congestion(struct address_space *mapping, int intr)
|
|
@@ -598,6 +651,9 @@ static int nfs_wait_on_write_congestion(struct address_space *mapping, int intr)
|
|
|
|
|
|
if (!bdi_write_congested(bdi))
|
|
if (!bdi_write_congested(bdi))
|
|
return 0;
|
|
return 0;
|
|
|
|
+
|
|
|
|
+ nfs_inc_stats(mapping->host, NFSIOS_CONGESTIONWAIT);
|
|
|
|
+
|
|
if (intr) {
|
|
if (intr) {
|
|
struct rpc_clnt *clnt = NFS_CLIENT(mapping->host);
|
|
struct rpc_clnt *clnt = NFS_CLIENT(mapping->host);
|
|
sigset_t oldset;
|
|
sigset_t oldset;
|
|
@@ -653,8 +709,11 @@ static struct nfs_page * nfs_update_request(struct nfs_open_context* ctx,
|
|
spin_unlock(&nfsi->req_lock);
|
|
spin_unlock(&nfsi->req_lock);
|
|
error = nfs_wait_on_request(req);
|
|
error = nfs_wait_on_request(req);
|
|
nfs_release_request(req);
|
|
nfs_release_request(req);
|
|
- if (error < 0)
|
|
|
|
|
|
+ if (error < 0) {
|
|
|
|
+ if (new)
|
|
|
|
+ nfs_release_request(new);
|
|
return ERR_PTR(error);
|
|
return ERR_PTR(error);
|
|
|
|
+ }
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
spin_unlock(&nfsi->req_lock);
|
|
spin_unlock(&nfsi->req_lock);
|
|
@@ -748,6 +807,8 @@ int nfs_updatepage(struct file *file, struct page *page,
|
|
struct nfs_page *req;
|
|
struct nfs_page *req;
|
|
int status = 0;
|
|
int status = 0;
|
|
|
|
|
|
|
|
+ nfs_inc_stats(inode, NFSIOS_VFSUPDATEPAGE);
|
|
|
|
+
|
|
dprintk("NFS: nfs_updatepage(%s/%s %d@%Ld)\n",
|
|
dprintk("NFS: nfs_updatepage(%s/%s %d@%Ld)\n",
|
|
file->f_dentry->d_parent->d_name.name,
|
|
file->f_dentry->d_parent->d_name.name,
|
|
file->f_dentry->d_name.name, count,
|
|
file->f_dentry->d_name.name, count,
|
|
@@ -857,10 +918,12 @@ static inline int flush_task_priority(int how)
|
|
*/
|
|
*/
|
|
static void nfs_write_rpcsetup(struct nfs_page *req,
|
|
static void nfs_write_rpcsetup(struct nfs_page *req,
|
|
struct nfs_write_data *data,
|
|
struct nfs_write_data *data,
|
|
|
|
+ const struct rpc_call_ops *call_ops,
|
|
unsigned int count, unsigned int offset,
|
|
unsigned int count, unsigned int offset,
|
|
int how)
|
|
int how)
|
|
{
|
|
{
|
|
struct inode *inode;
|
|
struct inode *inode;
|
|
|
|
+ int flags;
|
|
|
|
|
|
/* Set up the RPC argument and reply structs
|
|
/* Set up the RPC argument and reply structs
|
|
* NB: take care not to mess about with data->commit et al. */
|
|
* NB: take care not to mess about with data->commit et al. */
|
|
@@ -881,6 +944,9 @@ static void nfs_write_rpcsetup(struct nfs_page *req,
|
|
data->res.verf = &data->verf;
|
|
data->res.verf = &data->verf;
|
|
nfs_fattr_init(&data->fattr);
|
|
nfs_fattr_init(&data->fattr);
|
|
|
|
|
|
|
|
+ /* Set up the initial task struct. */
|
|
|
|
+ flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
|
|
|
|
+ rpc_init_task(&data->task, NFS_CLIENT(inode), flags, call_ops, data);
|
|
NFS_PROTO(inode)->write_setup(data, how);
|
|
NFS_PROTO(inode)->write_setup(data, how);
|
|
|
|
|
|
data->task.tk_priority = flush_task_priority(how);
|
|
data->task.tk_priority = flush_task_priority(how);
|
|
@@ -910,7 +976,7 @@ static void nfs_execute_write(struct nfs_write_data *data)
|
|
* Generate multiple small requests to write out a single
|
|
* Generate multiple small requests to write out a single
|
|
* contiguous dirty area on one page.
|
|
* contiguous dirty area on one page.
|
|
*/
|
|
*/
|
|
-static int nfs_flush_multi(struct list_head *head, struct inode *inode, int how)
|
|
|
|
|
|
+static int nfs_flush_multi(struct inode *inode, struct list_head *head, int how)
|
|
{
|
|
{
|
|
struct nfs_page *req = nfs_list_entry(head->next);
|
|
struct nfs_page *req = nfs_list_entry(head->next);
|
|
struct page *page = req->wb_page;
|
|
struct page *page = req->wb_page;
|
|
@@ -944,14 +1010,15 @@ static int nfs_flush_multi(struct list_head *head, struct inode *inode, int how)
|
|
list_del_init(&data->pages);
|
|
list_del_init(&data->pages);
|
|
|
|
|
|
data->pagevec[0] = page;
|
|
data->pagevec[0] = page;
|
|
- data->complete = nfs_writeback_done_partial;
|
|
|
|
|
|
|
|
if (nbytes > wsize) {
|
|
if (nbytes > wsize) {
|
|
- nfs_write_rpcsetup(req, data, wsize, offset, how);
|
|
|
|
|
|
+ nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
|
|
|
|
+ wsize, offset, how);
|
|
offset += wsize;
|
|
offset += wsize;
|
|
nbytes -= wsize;
|
|
nbytes -= wsize;
|
|
} else {
|
|
} else {
|
|
- nfs_write_rpcsetup(req, data, nbytes, offset, how);
|
|
|
|
|
|
+ nfs_write_rpcsetup(req, data, &nfs_write_partial_ops,
|
|
|
|
+ nbytes, offset, how);
|
|
nbytes = 0;
|
|
nbytes = 0;
|
|
}
|
|
}
|
|
nfs_execute_write(data);
|
|
nfs_execute_write(data);
|
|
@@ -978,16 +1045,13 @@ out_bad:
|
|
* This is the case if nfs_updatepage detects a conflicting request
|
|
* This is the case if nfs_updatepage detects a conflicting request
|
|
* that has been written but not committed.
|
|
* that has been written but not committed.
|
|
*/
|
|
*/
|
|
-static int nfs_flush_one(struct list_head *head, struct inode *inode, int how)
|
|
|
|
|
|
+static int nfs_flush_one(struct inode *inode, struct list_head *head, int how)
|
|
{
|
|
{
|
|
struct nfs_page *req;
|
|
struct nfs_page *req;
|
|
struct page **pages;
|
|
struct page **pages;
|
|
struct nfs_write_data *data;
|
|
struct nfs_write_data *data;
|
|
unsigned int count;
|
|
unsigned int count;
|
|
|
|
|
|
- if (NFS_SERVER(inode)->wsize < PAGE_CACHE_SIZE)
|
|
|
|
- return nfs_flush_multi(head, inode, how);
|
|
|
|
-
|
|
|
|
data = nfs_writedata_alloc(NFS_SERVER(inode)->wpages);
|
|
data = nfs_writedata_alloc(NFS_SERVER(inode)->wpages);
|
|
if (!data)
|
|
if (!data)
|
|
goto out_bad;
|
|
goto out_bad;
|
|
@@ -1005,9 +1069,8 @@ static int nfs_flush_one(struct list_head *head, struct inode *inode, int how)
|
|
}
|
|
}
|
|
req = nfs_list_entry(data->pages.next);
|
|
req = nfs_list_entry(data->pages.next);
|
|
|
|
|
|
- data->complete = nfs_writeback_done_full;
|
|
|
|
/* Set up the argument struct */
|
|
/* Set up the argument struct */
|
|
- nfs_write_rpcsetup(req, data, count, 0, how);
|
|
|
|
|
|
+ nfs_write_rpcsetup(req, data, &nfs_write_full_ops, count, 0, how);
|
|
|
|
|
|
nfs_execute_write(data);
|
|
nfs_execute_write(data);
|
|
return 0;
|
|
return 0;
|
|
@@ -1021,24 +1084,32 @@ static int nfs_flush_one(struct list_head *head, struct inode *inode, int how)
|
|
return -ENOMEM;
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
|
|
-static int
|
|
|
|
-nfs_flush_list(struct list_head *head, int wpages, int how)
|
|
|
|
|
|
+static int nfs_flush_list(struct inode *inode, struct list_head *head, int npages, int how)
|
|
{
|
|
{
|
|
LIST_HEAD(one_request);
|
|
LIST_HEAD(one_request);
|
|
- struct nfs_page *req;
|
|
|
|
- int error = 0;
|
|
|
|
- unsigned int pages = 0;
|
|
|
|
|
|
+ int (*flush_one)(struct inode *, struct list_head *, int);
|
|
|
|
+ struct nfs_page *req;
|
|
|
|
+ int wpages = NFS_SERVER(inode)->wpages;
|
|
|
|
+ int wsize = NFS_SERVER(inode)->wsize;
|
|
|
|
+ int error;
|
|
|
|
|
|
- while (!list_empty(head)) {
|
|
|
|
- pages += nfs_coalesce_requests(head, &one_request, wpages);
|
|
|
|
|
|
+ flush_one = nfs_flush_one;
|
|
|
|
+ if (wsize < PAGE_CACHE_SIZE)
|
|
|
|
+ flush_one = nfs_flush_multi;
|
|
|
|
+ /* For single writes, FLUSH_STABLE is more efficient */
|
|
|
|
+ if (npages <= wpages && npages == NFS_I(inode)->npages
|
|
|
|
+ && nfs_list_entry(head->next)->wb_bytes <= wsize)
|
|
|
|
+ how |= FLUSH_STABLE;
|
|
|
|
+
|
|
|
|
+ do {
|
|
|
|
+ nfs_coalesce_requests(head, &one_request, wpages);
|
|
req = nfs_list_entry(one_request.next);
|
|
req = nfs_list_entry(one_request.next);
|
|
- error = nfs_flush_one(&one_request, req->wb_context->dentry->d_inode, how);
|
|
|
|
|
|
+ error = flush_one(inode, &one_request, how);
|
|
if (error < 0)
|
|
if (error < 0)
|
|
- break;
|
|
|
|
- }
|
|
|
|
- if (error >= 0)
|
|
|
|
- return pages;
|
|
|
|
-
|
|
|
|
|
|
+ goto out_err;
|
|
|
|
+ } while (!list_empty(head));
|
|
|
|
+ return 0;
|
|
|
|
+out_err:
|
|
while (!list_empty(head)) {
|
|
while (!list_empty(head)) {
|
|
req = nfs_list_entry(head->next);
|
|
req = nfs_list_entry(head->next);
|
|
nfs_list_remove_request(req);
|
|
nfs_list_remove_request(req);
|
|
@@ -1051,8 +1122,9 @@ nfs_flush_list(struct list_head *head, int wpages, int how)
|
|
/*
|
|
/*
|
|
* Handle a write reply that flushed part of a page.
|
|
* Handle a write reply that flushed part of a page.
|
|
*/
|
|
*/
|
|
-static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
|
|
|
|
|
|
+static void nfs_writeback_done_partial(struct rpc_task *task, void *calldata)
|
|
{
|
|
{
|
|
|
|
+ struct nfs_write_data *data = calldata;
|
|
struct nfs_page *req = data->req;
|
|
struct nfs_page *req = data->req;
|
|
struct page *page = req->wb_page;
|
|
struct page *page = req->wb_page;
|
|
|
|
|
|
@@ -1062,11 +1134,14 @@ static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
|
|
req->wb_bytes,
|
|
req->wb_bytes,
|
|
(long long)req_offset(req));
|
|
(long long)req_offset(req));
|
|
|
|
|
|
- if (status < 0) {
|
|
|
|
|
|
+ if (nfs_writeback_done(task, data) != 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ if (task->tk_status < 0) {
|
|
ClearPageUptodate(page);
|
|
ClearPageUptodate(page);
|
|
SetPageError(page);
|
|
SetPageError(page);
|
|
- req->wb_context->error = status;
|
|
|
|
- dprintk(", error = %d\n", status);
|
|
|
|
|
|
+ req->wb_context->error = task->tk_status;
|
|
|
|
+ dprintk(", error = %d\n", task->tk_status);
|
|
} else {
|
|
} else {
|
|
#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
|
|
#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
|
|
if (data->verf.committed < NFS_FILE_SYNC) {
|
|
if (data->verf.committed < NFS_FILE_SYNC) {
|
|
@@ -1087,6 +1162,11 @@ static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
|
|
nfs_writepage_release(req);
|
|
nfs_writepage_release(req);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static const struct rpc_call_ops nfs_write_partial_ops = {
|
|
|
|
+ .rpc_call_done = nfs_writeback_done_partial,
|
|
|
|
+ .rpc_release = nfs_writedata_release,
|
|
|
|
+};
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Handle a write reply that flushes a whole page.
|
|
* Handle a write reply that flushes a whole page.
|
|
*
|
|
*
|
|
@@ -1094,11 +1174,15 @@ static void nfs_writeback_done_partial(struct nfs_write_data *data, int status)
|
|
* writebacks since the page->count is kept > 1 for as long
|
|
* writebacks since the page->count is kept > 1 for as long
|
|
* as the page has a write request pending.
|
|
* as the page has a write request pending.
|
|
*/
|
|
*/
|
|
-static void nfs_writeback_done_full(struct nfs_write_data *data, int status)
|
|
|
|
|
|
+static void nfs_writeback_done_full(struct rpc_task *task, void *calldata)
|
|
{
|
|
{
|
|
|
|
+ struct nfs_write_data *data = calldata;
|
|
struct nfs_page *req;
|
|
struct nfs_page *req;
|
|
struct page *page;
|
|
struct page *page;
|
|
|
|
|
|
|
|
+ if (nfs_writeback_done(task, data) != 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
/* Update attributes as result of writeback. */
|
|
/* Update attributes as result of writeback. */
|
|
while (!list_empty(&data->pages)) {
|
|
while (!list_empty(&data->pages)) {
|
|
req = nfs_list_entry(data->pages.next);
|
|
req = nfs_list_entry(data->pages.next);
|
|
@@ -1111,13 +1195,13 @@ static void nfs_writeback_done_full(struct nfs_write_data *data, int status)
|
|
req->wb_bytes,
|
|
req->wb_bytes,
|
|
(long long)req_offset(req));
|
|
(long long)req_offset(req));
|
|
|
|
|
|
- if (status < 0) {
|
|
|
|
|
|
+ if (task->tk_status < 0) {
|
|
ClearPageUptodate(page);
|
|
ClearPageUptodate(page);
|
|
SetPageError(page);
|
|
SetPageError(page);
|
|
- req->wb_context->error = status;
|
|
|
|
|
|
+ req->wb_context->error = task->tk_status;
|
|
end_page_writeback(page);
|
|
end_page_writeback(page);
|
|
nfs_inode_remove_request(req);
|
|
nfs_inode_remove_request(req);
|
|
- dprintk(", error = %d\n", status);
|
|
|
|
|
|
+ dprintk(", error = %d\n", task->tk_status);
|
|
goto next;
|
|
goto next;
|
|
}
|
|
}
|
|
end_page_writeback(page);
|
|
end_page_writeback(page);
|
|
@@ -1139,18 +1223,30 @@ static void nfs_writeback_done_full(struct nfs_write_data *data, int status)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static const struct rpc_call_ops nfs_write_full_ops = {
|
|
|
|
+ .rpc_call_done = nfs_writeback_done_full,
|
|
|
|
+ .rpc_release = nfs_writedata_release,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* This function is called when the WRITE call is complete.
|
|
* This function is called when the WRITE call is complete.
|
|
*/
|
|
*/
|
|
-void nfs_writeback_done(struct rpc_task *task, void *calldata)
|
|
|
|
|
|
+int nfs_writeback_done(struct rpc_task *task, struct nfs_write_data *data)
|
|
{
|
|
{
|
|
- struct nfs_write_data *data = calldata;
|
|
|
|
struct nfs_writeargs *argp = &data->args;
|
|
struct nfs_writeargs *argp = &data->args;
|
|
struct nfs_writeres *resp = &data->res;
|
|
struct nfs_writeres *resp = &data->res;
|
|
|
|
+ int status;
|
|
|
|
|
|
dprintk("NFS: %4d nfs_writeback_done (status %d)\n",
|
|
dprintk("NFS: %4d nfs_writeback_done (status %d)\n",
|
|
task->tk_pid, task->tk_status);
|
|
task->tk_pid, task->tk_status);
|
|
|
|
|
|
|
|
+ /* Call the NFS version-specific code */
|
|
|
|
+ status = NFS_PROTO(data->inode)->write_done(task, data);
|
|
|
|
+ if (status != 0)
|
|
|
|
+ return status;
|
|
|
|
+ nfs_add_stats(data->inode, NFSIOS_SERVERWRITTENBYTES, resp->count);
|
|
|
|
+
|
|
#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
|
|
#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
|
|
if (resp->verf->committed < argp->stable && task->tk_status >= 0) {
|
|
if (resp->verf->committed < argp->stable && task->tk_status >= 0) {
|
|
/* We tried a write call, but the server did not
|
|
/* We tried a write call, but the server did not
|
|
@@ -1176,6 +1272,8 @@ void nfs_writeback_done(struct rpc_task *task, void *calldata)
|
|
if (task->tk_status >= 0 && resp->count < argp->count) {
|
|
if (task->tk_status >= 0 && resp->count < argp->count) {
|
|
static unsigned long complain;
|
|
static unsigned long complain;
|
|
|
|
|
|
|
|
+ nfs_inc_stats(data->inode, NFSIOS_SHORTWRITE);
|
|
|
|
+
|
|
/* Has the server at least made some progress? */
|
|
/* Has the server at least made some progress? */
|
|
if (resp->count != 0) {
|
|
if (resp->count != 0) {
|
|
/* Was this an NFSv2 write or an NFSv3 stable write? */
|
|
/* Was this an NFSv2 write or an NFSv3 stable write? */
|
|
@@ -1191,7 +1289,7 @@ void nfs_writeback_done(struct rpc_task *task, void *calldata)
|
|
argp->stable = NFS_FILE_SYNC;
|
|
argp->stable = NFS_FILE_SYNC;
|
|
}
|
|
}
|
|
rpc_restart_call(task);
|
|
rpc_restart_call(task);
|
|
- return;
|
|
|
|
|
|
+ return -EAGAIN;
|
|
}
|
|
}
|
|
if (time_before(complain, jiffies)) {
|
|
if (time_before(complain, jiffies)) {
|
|
printk(KERN_WARNING
|
|
printk(KERN_WARNING
|
|
@@ -1202,11 +1300,7 @@ void nfs_writeback_done(struct rpc_task *task, void *calldata)
|
|
/* Can't do anything about it except throw an error. */
|
|
/* Can't do anything about it except throw an error. */
|
|
task->tk_status = -EIO;
|
|
task->tk_status = -EIO;
|
|
}
|
|
}
|
|
-
|
|
|
|
- /*
|
|
|
|
- * Process the nfs_page list
|
|
|
|
- */
|
|
|
|
- data->complete(data, task->tk_status);
|
|
|
|
|
|
+ return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1220,10 +1314,12 @@ void nfs_commit_release(void *wdata)
|
|
* Set up the argument/result storage required for the RPC call.
|
|
* Set up the argument/result storage required for the RPC call.
|
|
*/
|
|
*/
|
|
static void nfs_commit_rpcsetup(struct list_head *head,
|
|
static void nfs_commit_rpcsetup(struct list_head *head,
|
|
- struct nfs_write_data *data, int how)
|
|
|
|
|
|
+ struct nfs_write_data *data,
|
|
|
|
+ int how)
|
|
{
|
|
{
|
|
struct nfs_page *first;
|
|
struct nfs_page *first;
|
|
struct inode *inode;
|
|
struct inode *inode;
|
|
|
|
+ int flags;
|
|
|
|
|
|
/* Set up the RPC argument and reply structs
|
|
/* Set up the RPC argument and reply structs
|
|
* NB: take care not to mess about with data->commit et al. */
|
|
* NB: take care not to mess about with data->commit et al. */
|
|
@@ -1243,7 +1339,10 @@ static void nfs_commit_rpcsetup(struct list_head *head,
|
|
data->res.fattr = &data->fattr;
|
|
data->res.fattr = &data->fattr;
|
|
data->res.verf = &data->verf;
|
|
data->res.verf = &data->verf;
|
|
nfs_fattr_init(&data->fattr);
|
|
nfs_fattr_init(&data->fattr);
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ /* Set up the initial task struct. */
|
|
|
|
+ flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
|
|
|
|
+ rpc_init_task(&data->task, NFS_CLIENT(inode), flags, &nfs_commit_ops, data);
|
|
NFS_PROTO(inode)->commit_setup(data, how);
|
|
NFS_PROTO(inode)->commit_setup(data, how);
|
|
|
|
|
|
data->task.tk_priority = flush_task_priority(how);
|
|
data->task.tk_priority = flush_task_priority(how);
|
|
@@ -1284,7 +1383,7 @@ nfs_commit_list(struct inode *inode, struct list_head *head, int how)
|
|
/*
|
|
/*
|
|
* COMMIT call returned
|
|
* COMMIT call returned
|
|
*/
|
|
*/
|
|
-void nfs_commit_done(struct rpc_task *task, void *calldata)
|
|
|
|
|
|
+static void nfs_commit_done(struct rpc_task *task, void *calldata)
|
|
{
|
|
{
|
|
struct nfs_write_data *data = calldata;
|
|
struct nfs_write_data *data = calldata;
|
|
struct nfs_page *req;
|
|
struct nfs_page *req;
|
|
@@ -1293,6 +1392,10 @@ void nfs_commit_done(struct rpc_task *task, void *calldata)
|
|
dprintk("NFS: %4d nfs_commit_done (status %d)\n",
|
|
dprintk("NFS: %4d nfs_commit_done (status %d)\n",
|
|
task->tk_pid, task->tk_status);
|
|
task->tk_pid, task->tk_status);
|
|
|
|
|
|
|
|
+ /* Call the NFS version-specific code */
|
|
|
|
+ if (NFS_PROTO(data->inode)->commit_done(task, data) != 0)
|
|
|
|
+ return;
|
|
|
|
+
|
|
while (!list_empty(&data->pages)) {
|
|
while (!list_empty(&data->pages)) {
|
|
req = nfs_list_entry(data->pages.next);
|
|
req = nfs_list_entry(data->pages.next);
|
|
nfs_list_remove_request(req);
|
|
nfs_list_remove_request(req);
|
|
@@ -1326,6 +1429,16 @@ void nfs_commit_done(struct rpc_task *task, void *calldata)
|
|
}
|
|
}
|
|
sub_page_state(nr_unstable,res);
|
|
sub_page_state(nr_unstable,res);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+static const struct rpc_call_ops nfs_commit_ops = {
|
|
|
|
+ .rpc_call_done = nfs_commit_done,
|
|
|
|
+ .rpc_release = nfs_commit_release,
|
|
|
|
+};
|
|
|
|
+#else
|
|
|
|
+static inline int nfs_commit_list(struct inode *inode, struct list_head *head, int how)
|
|
|
|
+{
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
|
|
static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
|
|
@@ -1333,24 +1446,16 @@ static int nfs_flush_inode(struct inode *inode, unsigned long idx_start,
|
|
{
|
|
{
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
LIST_HEAD(head);
|
|
LIST_HEAD(head);
|
|
- int res,
|
|
|
|
- error = 0;
|
|
|
|
|
|
+ int res;
|
|
|
|
|
|
spin_lock(&nfsi->req_lock);
|
|
spin_lock(&nfsi->req_lock);
|
|
res = nfs_scan_dirty(inode, &head, idx_start, npages);
|
|
res = nfs_scan_dirty(inode, &head, idx_start, npages);
|
|
spin_unlock(&nfsi->req_lock);
|
|
spin_unlock(&nfsi->req_lock);
|
|
if (res) {
|
|
if (res) {
|
|
- struct nfs_server *server = NFS_SERVER(inode);
|
|
|
|
-
|
|
|
|
- /* For single writes, FLUSH_STABLE is more efficient */
|
|
|
|
- if (res == nfsi->npages && nfsi->npages <= server->wpages) {
|
|
|
|
- if (res > 1 || nfs_list_entry(head.next)->wb_bytes <= server->wsize)
|
|
|
|
- how |= FLUSH_STABLE;
|
|
|
|
- }
|
|
|
|
- error = nfs_flush_list(&head, server->wpages, how);
|
|
|
|
|
|
+ int error = nfs_flush_list(inode, &head, res, how);
|
|
|
|
+ if (error < 0)
|
|
|
|
+ return error;
|
|
}
|
|
}
|
|
- if (error < 0)
|
|
|
|
- return error;
|
|
|
|
return res;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1359,14 +1464,13 @@ int nfs_commit_inode(struct inode *inode, int how)
|
|
{
|
|
{
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
struct nfs_inode *nfsi = NFS_I(inode);
|
|
LIST_HEAD(head);
|
|
LIST_HEAD(head);
|
|
- int res,
|
|
|
|
- error = 0;
|
|
|
|
|
|
+ int res;
|
|
|
|
|
|
spin_lock(&nfsi->req_lock);
|
|
spin_lock(&nfsi->req_lock);
|
|
res = nfs_scan_commit(inode, &head, 0, 0);
|
|
res = nfs_scan_commit(inode, &head, 0, 0);
|
|
spin_unlock(&nfsi->req_lock);
|
|
spin_unlock(&nfsi->req_lock);
|
|
if (res) {
|
|
if (res) {
|
|
- error = nfs_commit_list(inode, &head, how);
|
|
|
|
|
|
+ int error = nfs_commit_list(inode, &head, how);
|
|
if (error < 0)
|
|
if (error < 0)
|
|
return error;
|
|
return error;
|
|
}
|
|
}
|
|
@@ -1374,28 +1478,38 @@ int nfs_commit_inode(struct inode *inode, int how)
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
|
|
-int nfs_sync_inode(struct inode *inode, unsigned long idx_start,
|
|
|
|
- unsigned int npages, int how)
|
|
|
|
|
|
+int nfs_sync_inode_wait(struct inode *inode, unsigned long idx_start,
|
|
|
|
+ unsigned int npages, int how)
|
|
{
|
|
{
|
|
|
|
+ struct nfs_inode *nfsi = NFS_I(inode);
|
|
|
|
+ LIST_HEAD(head);
|
|
int nocommit = how & FLUSH_NOCOMMIT;
|
|
int nocommit = how & FLUSH_NOCOMMIT;
|
|
- int wait = how & FLUSH_WAIT;
|
|
|
|
- int error;
|
|
|
|
-
|
|
|
|
- how &= ~(FLUSH_WAIT|FLUSH_NOCOMMIT);
|
|
|
|
|
|
+ int pages, ret;
|
|
|
|
|
|
|
|
+ how &= ~FLUSH_NOCOMMIT;
|
|
|
|
+ spin_lock(&nfsi->req_lock);
|
|
do {
|
|
do {
|
|
- if (wait) {
|
|
|
|
- error = nfs_wait_on_requests(inode, idx_start, npages);
|
|
|
|
- if (error != 0)
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- error = nfs_flush_inode(inode, idx_start, npages, how);
|
|
|
|
- if (error != 0)
|
|
|
|
|
|
+ ret = nfs_wait_on_requests_locked(inode, idx_start, npages);
|
|
|
|
+ if (ret != 0)
|
|
continue;
|
|
continue;
|
|
- if (!nocommit)
|
|
|
|
- error = nfs_commit_inode(inode, how);
|
|
|
|
- } while (error > 0);
|
|
|
|
- return error;
|
|
|
|
|
|
+ pages = nfs_scan_dirty(inode, &head, idx_start, npages);
|
|
|
|
+ if (pages != 0) {
|
|
|
|
+ spin_unlock(&nfsi->req_lock);
|
|
|
|
+ ret = nfs_flush_list(inode, &head, pages, how);
|
|
|
|
+ spin_lock(&nfsi->req_lock);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ if (nocommit)
|
|
|
|
+ break;
|
|
|
|
+ pages = nfs_scan_commit(inode, &head, 0, 0);
|
|
|
|
+ if (pages == 0)
|
|
|
|
+ break;
|
|
|
|
+ spin_unlock(&nfsi->req_lock);
|
|
|
|
+ ret = nfs_commit_list(inode, &head, how);
|
|
|
|
+ spin_lock(&nfsi->req_lock);
|
|
|
|
+ } while (ret >= 0);
|
|
|
|
+ spin_unlock(&nfsi->req_lock);
|
|
|
|
+ return ret;
|
|
}
|
|
}
|
|
|
|
|
|
int nfs_init_writepagecache(void)
|
|
int nfs_init_writepagecache(void)
|