/*
* linux/fs/nfs/write.c
*
* Write file data over NFS.
*
* Copyright (C) 1996, 1997, Olaf Kirch <okir@monad.swb.de>
*/
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/file.h>
#include <linux/writeback.h>
#include <linux/swap.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_mount.h>
#include <linux/nfs_page.h>
#include <linux/backing-dev.h>
#include <asm/uaccess.h>
#include <linux/smp_lock.h>
#include "delegation.h"
#include "internal.h"
#include "iostat.h"
#define NFSDBG_FACILITY NFSDBG_PAGECACHE
#define MIN_POOL_WRITE (32)
#define MIN_POOL_COMMIT (4)
/*
* Local function declarations
*/
static struct nfs_page * nfs_update_request(struct nfs_open_context*,
struct page *,
unsigned int, unsigned int);
static void nfs_mark_request_dirty(struct nfs_page *req);
static long nfs_flush_mapping(struct address_space *mapping, struct writeback_control *wbc, 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 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 *p = mempool_alloc(nfs_commit_mempool, GFP_NOFS);
if (p) {
memset(p, 0, sizeof(*p));
INIT_LIST_HEAD(&p->pages);
}
return p;
}
void nfs_commit_rcu_free(struct rcu_head *head)
{
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(size_t len)
{
unsigned int pagecount = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
struct nfs_write_data *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS);
if (p) {
memset(p, 0, sizeof(*p));
INIT_LIST_HEAD(&p->pages);
p->npages = pagecount;
if (pagecount <= ARRAY_SIZE(p->page_array))
p->pagevec = p->page_array;
else {
p->pagevec = kcalloc(pagecount, sizeof(struct page *), GFP_NOFS);
if (!p->pagevec) {
mempool_free(p, nfs_wdata_mempool);
p = NULL;
}
}
}
return p;
}
static void nfs_writedata_rcu_free(struct rcu_head *head)
{
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)
{
call_rcu_bh(&wdata->task.u.tk_rcu, nfs_writedata_rcu_free);
}
void nfs_writedata_release(void *wdata)
{
nfs_writedata_free(wdata);
}
static struct nfs_page *nfs_page_find_request_locked(struct page *page)
{
struct nfs_page *req = NULL;
if (PagePrivate(page)) {
req = (struct nfs_page *)page_private(page);
if (req != NULL)
atomic_inc(&req->wb_count);
}
return req;
}
static struct nfs_page *nfs_page_find_request(struct page *page)
{
struct nfs_page *req = NULL;
spinlock_t *req_lock = &NFS_I(page->mapping->host)->req_lock;
spin_lock(req_lock);
req = nfs_page_find_request_locked(page);
spin_unlock(req_lock);
return req;
}
/* Adjust the file length if we're writing beyond the end */
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);
unsigned long end_index = (i_size - 1) >> PAGE_CACHE_SHIFT;
if (i_size > 0 && page->index < end_index)
return;
end = ((loff_t)page->index << PAGE_CACHE_SHIFT) + ((loff_t)offset+count);
if (i_size >= end)
return;
nfs_inc_stats(inode, NFSIOS_EXTENDWRITE);
i_size_write(inode, end);
}
/* A writeback failed: mark the page as bad, and invalidate the page cache */
static void nfs_set_pageerror(struct page *page)
{
SetPageError(page);
nfs_zap_mapping(page->mapping->host, page->mapping);
}
/* 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)
{
if (PageUptodate(page))
return;
if (base != 0)
return;
if (count != nfs_page_length(page))
return;
if (count != PAGE_CACHE_SIZE)
memclear_highpage_flush(page, count, PAGE_CACHE_SIZE - count);
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);
/* Set the PG_uptodate flag? */
nfs_mark_uptodate(page, offset, count);
nfs_unlock_request(req);
return 0;
}
static int wb_priority(struct writeback_control *wbc)
{
if (wbc->for_reclaim)
return FLUSH_HIGHPRI;
if (wbc->for_kupdate)
return FLUSH_LOWPRI;
return 0;
}
/*
* NFS congestion control
*/
int nfs_congestion_kb;
#define NFS_CONGESTION_ON_THRESH (nfs_congestion_kb >> (PAGE_SHIFT-10))
#define NFS_CONGESTION_OFF_THRESH \
(NFS_CONGESTION_ON_THRESH - (NFS_CONGESTION_ON_THRESH >> 2))
static void nfs_set_page_writeback(struct page *page)
{
if (!test_set_page_writeback(page)) {
struct inode *inode = page->mapping->host;
struct nfs_server *nfss = NFS_SERVER(inode);
if (atomic_inc_return(&nfss->writeback) >
NFS_CONGESTION_ON_THRESH)
set_bdi_congested(&nfss->backing_dev_info, WRITE);
}
}
static void nfs_end_page_writeback(struct page *page)
{
struct inode *inode = page->mapping->host;
struct nfs_server *nfss = NFS_SERVER(inode);
end_page_writeback(page);
if (atomic_dec_return(&nfss->writeback) < NFS_CONGESTION_OFF_THRESH) {
clear_bdi_congested(&nfss->backing_dev_info, WRITE);
congestion_end(WRITE);
}
}
/*
* Find an associated nfs write request, and prepare to flush it out
* Returns 1 if there was no write request, or if the request was
* already tagged by nfs_set_page_dirty.Returns 0 if the request
* was not tagged.
* May also return an error if
|