1851 lines · 47.0 KB · C
RawDownload
/* * 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/migrate.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 <linux/export.h> #include <asm/uaccess.h> #include "delegation.h"#include "internal.h"#include "iostat.h"#include "nfs4_fs.h"#include "fscache.h"#include "pnfs.h" #define NFSDBG_FACILITY		NFSDBG_PAGECACHE #define MIN_POOL_WRITE		(32)#define MIN_POOL_COMMIT		(4) /* * Local function declarations */static void nfs_redirty_request(struct nfs_page *req);static const struct rpc_call_ops nfs_write_common_ops;static const struct rpc_call_ops nfs_commit_ops;static const struct nfs_pgio_completion_ops nfs_async_write_completion_ops;static const struct nfs_commit_completion_ops nfs_commit_completion_ops; static struct kmem_cache *nfs_wdata_cachep;static mempool_t *nfs_wdata_mempool;static struct kmem_cache *nfs_cdata_cachep;static mempool_t *nfs_commit_mempool; struct nfs_commit_data *nfs_commitdata_alloc(void){	struct nfs_commit_data *p = mempool_alloc(nfs_commit_mempool, GFP_NOFS); 	if (p) {		memset(p, 0, sizeof(*p));		INIT_LIST_HEAD(&p->pages);	}	return p;}EXPORT_SYMBOL_GPL(nfs_commitdata_alloc); void nfs_commit_free(struct nfs_commit_data *p){	mempool_free(p, nfs_commit_mempool);}EXPORT_SYMBOL_GPL(nfs_commit_free); struct nfs_write_header *nfs_writehdr_alloc(void){	struct nfs_write_header *p = mempool_alloc(nfs_wdata_mempool, GFP_NOFS); 	if (p) {		struct nfs_pgio_header *hdr = &p->header; 		memset(p, 0, sizeof(*p));		INIT_LIST_HEAD(&hdr->pages);		INIT_LIST_HEAD(&hdr->rpc_list);		spin_lock_init(&hdr->lock);		atomic_set(&hdr->refcnt, 0);	}	return p;} static struct nfs_write_data *nfs_writedata_alloc(struct nfs_pgio_header *hdr,						  unsigned int pagecount){	struct nfs_write_data *data, *prealloc; 	prealloc = &container_of(hdr, struct nfs_write_header, header)->rpc_data;	if (prealloc->header == NULL)		data = prealloc;	else		data = kzalloc(sizeof(*data), GFP_KERNEL);	if (!data)		goto out; 	if (nfs_pgarray_set(&data->pages, pagecount)) {		data->header = hdr;		atomic_inc(&hdr->refcnt);	} else {		if (data != prealloc)			kfree(data);		data = NULL;	}out:	return data;} void nfs_writehdr_free(struct nfs_pgio_header *hdr){	struct nfs_write_header *whdr = container_of(hdr, struct nfs_write_header, header);	mempool_free(whdr, nfs_wdata_mempool);} void nfs_writedata_release(struct nfs_write_data *wdata){	struct nfs_pgio_header *hdr = wdata->header;	struct nfs_write_header *write_header = container_of(hdr, struct nfs_write_header, header); 	put_nfs_open_context(wdata->args.context);	if (wdata->pages.pagevec != wdata->pages.page_array)		kfree(wdata->pages.pagevec);	if (wdata != &write_header->rpc_data)		kfree(wdata);	else		wdata->header = NULL;	if (atomic_dec_and_test(&hdr->refcnt))		hdr->completion_ops->completion(hdr);} static void nfs_context_set_write_error(struct nfs_open_context *ctx, int error){	ctx->error = error;	smp_wmb();	set_bit(NFS_CONTEXT_ERROR_WRITE, &ctx->flags);} 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)			kref_get(&req->wb_kref);	}	return req;} static struct nfs_page *nfs_page_find_request(struct page *page){	struct inode *inode = page->mapping->host;	struct nfs_page *req = NULL; 	spin_lock(&inode->i_lock);	req = nfs_page_find_request_locked(page);	spin_unlock(&inode->i_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;	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)		goto out;	end = ((loff_t)page->index << PAGE_CACHE_SHIFT) + ((loff_t)offset+count);	if (i_size >= end)		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 */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;	SetPageUptodate(page);} static int wb_priority(struct writeback_control *wbc){	if (wbc->for_reclaim)		return FLUSH_HIGHPRI | FLUSH_STABLE;	if (wbc->for_kupdate || wbc->for_background)		return FLUSH_LOWPRI | FLUSH_COND_STABLE;	return FLUSH_COND_STABLE;} /* * 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 int nfs_set_page_writeback(struct page *page){	int ret = test_set_page_writeback(page); 	if (!ret) {		struct inode *inode = page->mapping->host;		struct nfs_server *nfss = NFS_SERVER(inode); 		if (atomic_long_inc_return(&nfss->writeback) >				NFS_CONGESTION_ON_THRESH) {			set_bdi_congested(&nfss->backing_dev_info,						BLK_RW_ASYNC);		}	}	return ret;} 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_long_dec_return(&nfss->writeback) < NFS_CONGESTION_OFF_THRESH)		clear_bdi_congested(&nfss->backing_dev_info, BLK_RW_ASYNC);} 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 (;;) {		req = nfs_page_find_request_locked(page);		if (req == NULL)			break;		if (nfs_lock_request(req))			break;		/* Note: If we hold the page lock, as is the case in nfs_writepage,		 *	 then the call to nfs_lock_request() will always		 *	 succeed provided that someone hasn't already marked the		 *	 request as dirty (in which case we don't care).		 */		spin_unlock(&inode->i_lock);		if (!nonblock)			ret = nfs_wait_on_request(req);		else			ret = -EAGAIN;		nfs_release_request(req);		if (ret != 0)			return ERR_PTR(ret);		spin_lock(&inode->i_lock);	}	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);		ret = pgio->pg_error;	}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);	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;} /* * Write an mmapped page to the server. */static int nfs_writepage_locked(struct page *page, struct writeback_control *wbc){	struct nfs_pageio_descriptor pgio;	int err; 	nfs_pageio_init_write(&pgio, page->mapping->host, wb_priority(wbc),			      &nfs_async_write_completion_ops);	err = nfs_do_writepage(page, wbc, &pgio);	nfs_pageio_complete(&pgio);	if (err < 0)		return err;	if (pgio.pg_error < 0)		return pgio.pg_error;	return 0;} int nfs_writepage(struct page *page, struct writeback_control *wbc){	int ret; 	ret = nfs_writepage_locked(page, wbc);	unlock_page(page);	return ret;} static int nfs_writepages_callback(struct page *page, struct writeback_control *wbc, void *data){	int ret; 	ret = nfs_do_writepage(page, wbc, data);	unlock_page(page);	return ret;} 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),			      &nfs_async_write_completion_ops);	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)		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){	struct nfs_inode *nfsi = NFS_I(inode); 	/* Lock the request! */	nfs_lock_request(req); 	spin_lock(&inode->i_lock);	if (!nfsi->npages && nfs_have_delegation(inode, FMODE_WRITE))		inode->i_version++;	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);	spin_unlock(&inode->i_lock);} /* * Remove a write request from an inode */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); 	BUG_ON (!NFS_WBACK_BUSY(req)); 	spin_lock(&inode->i_lock);	set_page_private(req->wb_page, 0);	ClearPagePrivate(req->wb_page);	clear_bit(PG_MAPPED, &req->wb_flags);	nfsi->npages--;	spin_unlock(&inode->i_lock);	nfs_release_request(req);} static voidnfs_mark_request_dirty(struct nfs_page *req){	__set_page_dirty_nobuffers(req->wb_page);} #if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)/** * nfs_request_add_commit_list - add request to a commit list * @req: pointer to a struct nfs_page * @dst: commit list head * @cinfo: holds list lock and accounting info * * This sets the PG_CLEAN bit, updates the cinfo count of * number of outstanding requests requiring a commit as well as * the MM page stats. * * The caller must _not_ hold the cinfo->lock, but must be * holding the nfs_page lock. */voidnfs_request_add_commit_list(struct nfs_page *req, struct list_head *dst,			    struct nfs_commit_info *cinfo){	set_bit(PG_CLEAN, &(req)->wb_flags);	spin_lock(cinfo->lock);	nfs_list_add_request(req, dst);	cinfo->mds->ncommit++;	spin_unlock(cinfo->lock);	if (!cinfo->dreq) {		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(req->wb_context->dentry->d_inode,				   I_DIRTY_DATASYNC);	}}EXPORT_SYMBOL_GPL(nfs_request_add_commit_list); /** * nfs_request_remove_commit_list - Remove request from a commit list * @req: pointer to a nfs_page * @cinfo: holds list lock and accounting info * * This clears the PG_CLEAN bit, and updates the cinfo's count of * number of outstanding requests requiring a commit * It does not update the MM page stats. * * The caller _must_ hold the cinfo->lock and the nfs_page lock. */voidnfs_request_remove_commit_list(struct nfs_page *req,			       struct nfs_commit_info *cinfo){	if (!test_and_clear_bit(PG_CLEAN, &(req)->wb_flags))		return;	nfs_list_remove_request(req);	cinfo->mds->ncommit--;}EXPORT_SYMBOL_GPL(nfs_request_remove_commit_list);

Lines 1–500 of 1851.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t