2005-04-16 22:20:36 +00:00
|
|
|
/*
|
|
|
|
* linux/fs/nfs/direct.c
|
|
|
|
*
|
|
|
|
* Copyright (C) 2003 by Chuck Lever <cel@netapp.com>
|
|
|
|
*
|
|
|
|
* High-performance uncached I/O for the Linux NFS client
|
|
|
|
*
|
|
|
|
* There are important applications whose performance or correctness
|
|
|
|
* depends on uncached access to file data. Database clusters
|
2006-03-20 18:44:34 +00:00
|
|
|
* (multiple copies of the same instance running on separate hosts)
|
2005-04-16 22:20:36 +00:00
|
|
|
* implement their own cache coherency protocol that subsumes file
|
2006-03-20 18:44:34 +00:00
|
|
|
* system cache protocols. Applications that process datasets
|
|
|
|
* considerably larger than the client's memory do not always benefit
|
|
|
|
* from a local cache. A streaming video server, for instance, has no
|
2005-04-16 22:20:36 +00:00
|
|
|
* need to cache the contents of a file.
|
|
|
|
*
|
|
|
|
* When an application requests uncached I/O, all read and write requests
|
|
|
|
* are made directly to the server; data stored or fetched via these
|
|
|
|
* requests is not cached in the Linux page cache. The client does not
|
|
|
|
* correct unaligned requests from applications. All requested bytes are
|
|
|
|
* held on permanent storage before a direct write system call returns to
|
|
|
|
* an application.
|
|
|
|
*
|
|
|
|
* Solaris implements an uncached I/O facility called directio() that
|
|
|
|
* is used for backups and sequential I/O to very large files. Solaris
|
|
|
|
* also supports uncaching whole NFS partitions with "-o forcedirectio,"
|
|
|
|
* an undocumented mount option.
|
|
|
|
*
|
|
|
|
* Designed by Jeff Kimmel, Chuck Lever, and Trond Myklebust, with
|
|
|
|
* help from Andrew Morton.
|
|
|
|
*
|
|
|
|
* 18 Dec 2001 Initial implementation for 2.4 --cel
|
|
|
|
* 08 Jul 2002 Version for 2.4.19, with bug fixes --trondmy
|
|
|
|
* 08 Jun 2003 Port to 2.5 APIs --cel
|
|
|
|
* 31 Mar 2004 Handle direct I/O without VFS support --cel
|
|
|
|
* 15 Sep 2004 Parallel async reads --cel
|
2006-03-20 18:44:34 +00:00
|
|
|
* 04 May 2005 support O_DIRECT with aio --cel
|
2005-04-16 22:20:36 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/config.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/smp_lock.h>
|
|
|
|
#include <linux/file.h>
|
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include <linux/kref.h>
|
|
|
|
|
|
|
|
#include <linux/nfs_fs.h>
|
|
|
|
#include <linux/nfs_page.h>
|
|
|
|
#include <linux/sunrpc/clnt.h>
|
|
|
|
|
|
|
|
#include <asm/system.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
#include <asm/atomic.h>
|
|
|
|
|
2006-03-20 18:44:14 +00:00
|
|
|
#include "iostat.h"
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
#define NFSDBG_FACILITY NFSDBG_VFS
|
|
|
|
|
|
|
|
static kmem_cache_t *nfs_direct_cachep;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This represents a set of asynchronous requests that we're waiting on
|
|
|
|
*/
|
|
|
|
struct nfs_direct_req {
|
|
|
|
struct kref kref; /* release manager */
|
2006-03-20 18:44:34 +00:00
|
|
|
|
|
|
|
/* I/O parameters */
|
2006-03-20 18:44:36 +00:00
|
|
|
struct list_head list, /* nfs_read/write_data structs */
|
|
|
|
rewrite_list; /* saved nfs_write_data structs */
|
2006-03-20 18:44:36 +00:00
|
|
|
struct nfs_open_context *ctx; /* file open context info */
|
2006-03-20 18:44:30 +00:00
|
|
|
struct kiocb * iocb; /* controlling i/o request */
|
2006-03-20 18:44:34 +00:00
|
|
|
struct inode * inode; /* target file of i/o */
|
2006-03-20 18:44:34 +00:00
|
|
|
|
|
|
|
/* completion state */
|
2006-06-20 16:55:19 +00:00
|
|
|
atomic_t io_count; /* i/os we're waiting for */
|
2006-03-20 18:44:34 +00:00
|
|
|
spinlock_t lock; /* protect completion state */
|
|
|
|
ssize_t count, /* bytes actually processed */
|
2005-04-16 22:20:36 +00:00
|
|
|
error; /* any reported error */
|
2006-03-20 18:44:43 +00:00
|
|
|
struct completion completion; /* wait for i/o completion */
|
2006-03-20 18:44:36 +00:00
|
|
|
|
|
|
|
/* commit state */
|
|
|
|
struct nfs_write_data * commit_data; /* special write_data for commits */
|
|
|
|
int flags;
|
|
|
|
#define NFS_ODIRECT_DO_COMMIT (1) /* an unstable reply was received */
|
|
|
|
#define NFS_ODIRECT_RESCHED_WRITES (2) /* write verification failed */
|
|
|
|
struct nfs_writeverf verf; /* unstable write verifier */
|
2005-04-16 22:20:36 +00:00
|
|
|
};
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
static void nfs_direct_write_complete(struct nfs_direct_req *dreq, struct inode *inode);
|
2006-06-20 16:55:45 +00:00
|
|
|
static const struct rpc_call_ops nfs_write_direct_ops;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:55:19 +00:00
|
|
|
static inline void get_dreq(struct nfs_direct_req *dreq)
|
|
|
|
{
|
|
|
|
atomic_inc(&dreq->io_count);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int put_dreq(struct nfs_direct_req *dreq)
|
|
|
|
{
|
|
|
|
return atomic_dec_and_test(&dreq->io_count);
|
|
|
|
}
|
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
/*
|
|
|
|
* "size" is never larger than rsize or wsize.
|
|
|
|
*/
|
|
|
|
static inline int nfs_direct_count_pages(unsigned long user_addr, size_t size)
|
|
|
|
{
|
|
|
|
int page_count;
|
|
|
|
|
|
|
|
page_count = (user_addr + size + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
|
|
|
page_count -= user_addr >> PAGE_SHIFT;
|
|
|
|
BUG_ON(page_count < 0);
|
|
|
|
|
|
|
|
return page_count;
|
|
|
|
}
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
/**
|
2006-03-20 18:44:28 +00:00
|
|
|
* nfs_direct_IO - NFS address space operation for direct I/O
|
|
|
|
* @rw: direction (read or write)
|
|
|
|
* @iocb: target I/O control block
|
|
|
|
* @iov: array of vectors that define I/O buffer
|
|
|
|
* @pos: offset in file to begin the operation
|
|
|
|
* @nr_segs: size of iovec array
|
|
|
|
*
|
|
|
|
* The presence of this routine in the address space ops vector means
|
|
|
|
* the NFS client supports direct I/O. However, we shunt off direct
|
|
|
|
* read and write requests before the VFS gets them, so this method
|
|
|
|
* should never be called.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2006-03-20 18:44:28 +00:00
|
|
|
ssize_t nfs_direct_IO(int rw, struct kiocb *iocb, const struct iovec *iov, loff_t pos, unsigned long nr_segs)
|
|
|
|
{
|
|
|
|
dprintk("NFS: nfs_direct_IO (%s) off/no(%Ld/%lu) EINVAL\n",
|
2006-04-18 17:21:42 +00:00
|
|
|
iocb->ki_filp->f_dentry->d_name.name,
|
|
|
|
(long long) pos, nr_segs);
|
2006-03-20 18:44:28 +00:00
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2006-06-20 16:56:31 +00:00
|
|
|
static void nfs_direct_dirty_pages(struct page **pages, int npages)
|
2006-03-20 18:44:43 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < npages; i++) {
|
|
|
|
struct page *page = pages[i];
|
2006-06-20 16:56:31 +00:00
|
|
|
if (!PageCompound(page))
|
2006-03-20 18:44:43 +00:00
|
|
|
set_page_dirty_lock(page);
|
|
|
|
}
|
2006-06-20 16:56:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void nfs_direct_release_pages(struct page **pages, int npages)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < npages; i++)
|
|
|
|
page_cache_release(pages[i]);
|
2006-03-20 18:44:43 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:31 +00:00
|
|
|
static inline struct nfs_direct_req *nfs_direct_req_alloc(void)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:31 +00:00
|
|
|
struct nfs_direct_req *dreq;
|
|
|
|
|
|
|
|
dreq = kmem_cache_alloc(nfs_direct_cachep, SLAB_KERNEL);
|
|
|
|
if (!dreq)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
kref_init(&dreq->kref);
|
2006-03-20 18:44:43 +00:00
|
|
|
init_completion(&dreq->completion);
|
2006-03-20 18:44:31 +00:00
|
|
|
INIT_LIST_HEAD(&dreq->list);
|
2006-03-20 18:44:36 +00:00
|
|
|
INIT_LIST_HEAD(&dreq->rewrite_list);
|
2006-03-20 18:44:31 +00:00
|
|
|
dreq->iocb = NULL;
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->ctx = NULL;
|
2006-03-20 18:44:34 +00:00
|
|
|
spin_lock_init(&dreq->lock);
|
2006-06-20 16:55:19 +00:00
|
|
|
atomic_set(&dreq->io_count, 0);
|
2006-03-20 18:44:34 +00:00
|
|
|
dreq->count = 0;
|
|
|
|
dreq->error = 0;
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->flags = 0;
|
2006-03-20 18:44:31 +00:00
|
|
|
|
|
|
|
return dreq;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void nfs_direct_req_release(struct kref *kref)
|
|
|
|
{
|
|
|
|
struct nfs_direct_req *dreq = container_of(kref, struct nfs_direct_req, kref);
|
2006-03-20 18:44:36 +00:00
|
|
|
|
|
|
|
if (dreq->ctx != NULL)
|
|
|
|
put_nfs_open_context(dreq->ctx);
|
2005-04-16 22:20:36 +00:00
|
|
|
kmem_cache_free(nfs_direct_cachep, dreq);
|
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:31 +00:00
|
|
|
/*
|
|
|
|
* Collects and returns the final error value/byte-count.
|
|
|
|
*/
|
|
|
|
static ssize_t nfs_direct_wait(struct nfs_direct_req *dreq)
|
|
|
|
{
|
2006-03-20 18:44:34 +00:00
|
|
|
ssize_t result = -EIOCBQUEUED;
|
2006-03-20 18:44:31 +00:00
|
|
|
|
|
|
|
/* Async requests don't wait here */
|
|
|
|
if (dreq->iocb)
|
|
|
|
goto out;
|
|
|
|
|
2006-03-20 18:44:43 +00:00
|
|
|
result = wait_for_completion_interruptible(&dreq->completion);
|
2006-03-20 18:44:31 +00:00
|
|
|
|
|
|
|
if (!result)
|
2006-03-20 18:44:34 +00:00
|
|
|
result = dreq->error;
|
2006-03-20 18:44:31 +00:00
|
|
|
if (!result)
|
2006-03-20 18:44:34 +00:00
|
|
|
result = dreq->count;
|
2006-03-20 18:44:31 +00:00
|
|
|
|
|
|
|
out:
|
|
|
|
kref_put(&dreq->kref, nfs_direct_req_release);
|
|
|
|
return (ssize_t) result;
|
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:31 +00:00
|
|
|
/*
|
2006-06-20 16:56:49 +00:00
|
|
|
* Synchronous I/O uses a stack-allocated iocb. Thus we can't trust
|
|
|
|
* the iocb is still valid here if this is a synchronous request.
|
2006-03-20 18:44:31 +00:00
|
|
|
*/
|
|
|
|
static void nfs_direct_complete(struct nfs_direct_req *dreq)
|
|
|
|
{
|
|
|
|
if (dreq->iocb) {
|
2006-03-20 18:44:34 +00:00
|
|
|
long res = (long) dreq->error;
|
2006-03-20 18:44:31 +00:00
|
|
|
if (!res)
|
2006-03-20 18:44:34 +00:00
|
|
|
res = (long) dreq->count;
|
2006-03-20 18:44:31 +00:00
|
|
|
aio_complete(dreq->iocb, res, 0);
|
2006-03-20 18:44:43 +00:00
|
|
|
}
|
|
|
|
complete_all(&dreq->completion);
|
2006-03-20 18:44:31 +00:00
|
|
|
|
|
|
|
kref_put(&dreq->kref, nfs_direct_req_release);
|
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:28 +00:00
|
|
|
/*
|
2005-04-16 22:20:36 +00:00
|
|
|
* Note we also set the number of requests we have in the dreq when we are
|
|
|
|
* done. This prevents races with I/O completion so we will always wait
|
|
|
|
* until all requests have been dispatched and completed.
|
|
|
|
*/
|
2006-03-20 18:44:29 +00:00
|
|
|
static struct nfs_direct_req *nfs_direct_read_alloc(size_t nbytes, size_t rsize)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
struct list_head *list;
|
|
|
|
struct nfs_direct_req *dreq;
|
2005-11-30 23:09:02 +00:00
|
|
|
unsigned int rpages = (rsize + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:31 +00:00
|
|
|
dreq = nfs_direct_req_alloc();
|
2005-04-16 22:20:36 +00:00
|
|
|
if (!dreq)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
list = &dreq->list;
|
|
|
|
for(;;) {
|
2005-11-30 23:09:02 +00:00
|
|
|
struct nfs_read_data *data = nfs_readdata_alloc(rpages);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
if (unlikely(!data)) {
|
|
|
|
while (!list_empty(list)) {
|
|
|
|
data = list_entry(list->next,
|
|
|
|
struct nfs_read_data, pages);
|
|
|
|
list_del(&data->pages);
|
|
|
|
nfs_readdata_free(data);
|
|
|
|
}
|
|
|
|
kref_put(&dreq->kref, nfs_direct_req_release);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&data->pages);
|
|
|
|
list_add(&data->pages, list);
|
|
|
|
|
|
|
|
data->req = (struct nfs_page *) dreq;
|
2006-06-20 16:55:19 +00:00
|
|
|
get_dreq(dreq);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (nbytes <= rsize)
|
|
|
|
break;
|
|
|
|
nbytes -= rsize;
|
|
|
|
}
|
|
|
|
kref_get(&dreq->kref);
|
|
|
|
return dreq;
|
|
|
|
}
|
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
/*
|
|
|
|
* We must hold a reference to all the pages in this direct read request
|
|
|
|
* until the RPCs complete. This could be long *after* we are woken up in
|
|
|
|
* nfs_direct_wait (for instance, if someone hits ^C on a slow server).
|
|
|
|
*/
|
2006-03-20 18:44:27 +00:00
|
|
|
static void nfs_direct_read_result(struct rpc_task *task, void *calldata)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:27 +00:00
|
|
|
struct nfs_read_data *data = calldata;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct nfs_direct_req *dreq = (struct nfs_direct_req *) data->req;
|
|
|
|
|
2006-03-20 18:44:27 +00:00
|
|
|
if (nfs_readpage_result(task, data) != 0)
|
|
|
|
return;
|
2006-03-20 18:44:34 +00:00
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
nfs_direct_dirty_pages(data->pagevec, data->npages);
|
|
|
|
nfs_direct_release_pages(data->pagevec, data->npages);
|
|
|
|
|
2006-03-20 18:44:34 +00:00
|
|
|
spin_lock(&dreq->lock);
|
|
|
|
|
2006-03-20 18:44:27 +00:00
|
|
|
if (likely(task->tk_status >= 0))
|
2006-03-20 18:44:34 +00:00
|
|
|
dreq->count += data->res.count;
|
2005-04-16 22:20:36 +00:00
|
|
|
else
|
2006-03-20 18:44:34 +00:00
|
|
|
dreq->error = task->tk_status;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:34 +00:00
|
|
|
spin_unlock(&dreq->lock);
|
2006-06-20 16:55:19 +00:00
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
if (put_dreq(dreq))
|
2006-06-20 16:55:19 +00:00
|
|
|
nfs_direct_complete(dreq);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:27 +00:00
|
|
|
static const struct rpc_call_ops nfs_read_direct_ops = {
|
|
|
|
.rpc_call_done = nfs_direct_read_result,
|
|
|
|
.rpc_release = nfs_readdata_release,
|
|
|
|
};
|
|
|
|
|
2006-03-20 18:44:28 +00:00
|
|
|
/*
|
2005-04-16 22:20:36 +00:00
|
|
|
* For each nfs_read_data struct that was allocated on the list, dispatch
|
2006-06-20 16:56:49 +00:00
|
|
|
* an NFS READ operation. If get_user_pages() fails, we stop sending reads.
|
|
|
|
* Read length accounting is handled by nfs_direct_read_result().
|
|
|
|
* Otherwise, if no requests have been sent, just return an error.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2006-06-20 16:56:49 +00:00
|
|
|
static ssize_t nfs_direct_read_schedule(struct nfs_direct_req *dreq, unsigned long user_addr, size_t count, loff_t pos)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:36 +00:00
|
|
|
struct nfs_open_context *ctx = dreq->ctx;
|
|
|
|
struct inode *inode = ctx->dentry->d_inode;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct list_head *list = &dreq->list;
|
2006-03-20 18:44:29 +00:00
|
|
|
size_t rsize = NFS_SERVER(inode)->rsize;
|
2006-06-20 16:56:49 +00:00
|
|
|
unsigned int pgbase;
|
|
|
|
int result;
|
|
|
|
ssize_t started = 0;
|
|
|
|
struct nfs_read_data *data;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:56:16 +00:00
|
|
|
pgbase = user_addr & ~PAGE_MASK;
|
2005-04-16 22:20:36 +00:00
|
|
|
do {
|
2006-03-20 18:44:29 +00:00
|
|
|
size_t bytes;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
bytes = rsize;
|
|
|
|
if (count < rsize)
|
|
|
|
bytes = count;
|
|
|
|
|
2006-03-20 18:44:37 +00:00
|
|
|
BUG_ON(list_empty(list));
|
2005-04-16 22:20:36 +00:00
|
|
|
data = list_entry(list->next, struct nfs_read_data, pages);
|
|
|
|
list_del_init(&data->pages);
|
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
data->npages = nfs_direct_count_pages(user_addr, bytes);
|
|
|
|
down_read(¤t->mm->mmap_sem);
|
|
|
|
result = get_user_pages(current, current->mm, user_addr,
|
|
|
|
data->npages, 1, 0, data->pagevec, NULL);
|
|
|
|
up_read(¤t->mm->mmap_sem);
|
|
|
|
if (unlikely(result < data->npages))
|
|
|
|
goto out_err;
|
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
data->inode = inode;
|
|
|
|
data->cred = ctx->cred;
|
|
|
|
data->args.fh = NFS_FH(inode);
|
|
|
|
data->args.context = ctx;
|
2006-03-20 18:44:34 +00:00
|
|
|
data->args.offset = pos;
|
2005-04-16 22:20:36 +00:00
|
|
|
data->args.pgbase = pgbase;
|
2006-06-20 16:56:49 +00:00
|
|
|
data->args.pages = data->pagevec;
|
2005-04-16 22:20:36 +00:00
|
|
|
data->args.count = bytes;
|
|
|
|
data->res.fattr = &data->fattr;
|
|
|
|
data->res.eof = 0;
|
|
|
|
data->res.count = bytes;
|
|
|
|
|
2006-03-20 18:44:27 +00:00
|
|
|
rpc_init_task(&data->task, NFS_CLIENT(inode), RPC_TASK_ASYNC,
|
|
|
|
&nfs_read_direct_ops, data);
|
2005-04-16 22:20:36 +00:00
|
|
|
NFS_PROTO(inode)->read_setup(data);
|
|
|
|
|
|
|
|
data->task.tk_cookie = (unsigned long) inode;
|
|
|
|
|
|
|
|
lock_kernel();
|
|
|
|
rpc_execute(&data->task);
|
|
|
|
unlock_kernel();
|
|
|
|
|
2006-03-20 18:44:42 +00:00
|
|
|
dfprintk(VFS, "NFS: %5u initiated direct read call (req %s/%Ld, %zu bytes @ offset %Lu)\n",
|
2005-04-16 22:20:36 +00:00
|
|
|
data->task.tk_pid,
|
|
|
|
inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(inode),
|
|
|
|
bytes,
|
|
|
|
(unsigned long long)data->args.offset);
|
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
started += bytes;
|
|
|
|
user_addr += bytes;
|
2006-03-20 18:44:34 +00:00
|
|
|
pos += bytes;
|
2005-04-16 22:20:36 +00:00
|
|
|
pgbase += bytes;
|
|
|
|
pgbase &= ~PAGE_MASK;
|
|
|
|
|
|
|
|
count -= bytes;
|
|
|
|
} while (count != 0);
|
2006-03-20 18:44:37 +00:00
|
|
|
BUG_ON(!list_empty(list));
|
2006-06-20 16:56:49 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_err:
|
|
|
|
if (result > 0)
|
|
|
|
nfs_direct_release_pages(data->pagevec, result);
|
|
|
|
|
|
|
|
list_add(&data->pages, list);
|
|
|
|
while (!list_empty(list)) {
|
|
|
|
data = list_entry(list->next, struct nfs_read_data, pages);
|
|
|
|
list_del(&data->pages);
|
|
|
|
nfs_readdata_free(data);
|
|
|
|
if (put_dreq(dreq))
|
|
|
|
nfs_direct_complete(dreq);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (started)
|
|
|
|
return 0;
|
|
|
|
return result < 0 ? (ssize_t) result : -EFAULT;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
static ssize_t nfs_direct_read(struct kiocb *iocb, unsigned long user_addr, size_t count, loff_t pos)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
ssize_t result;
|
|
|
|
sigset_t oldset;
|
2006-03-20 18:44:30 +00:00
|
|
|
struct inode *inode = iocb->ki_filp->f_mapping->host;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct rpc_clnt *clnt = NFS_CLIENT(inode);
|
|
|
|
struct nfs_direct_req *dreq;
|
|
|
|
|
|
|
|
dreq = nfs_direct_read_alloc(count, NFS_SERVER(inode)->rsize);
|
|
|
|
if (!dreq)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2006-03-20 18:44:14 +00:00
|
|
|
dreq->inode = inode;
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->ctx = get_nfs_open_context((struct nfs_open_context *)iocb->ki_filp->private_data);
|
2006-03-20 18:44:30 +00:00
|
|
|
if (!is_sync_kiocb(iocb))
|
|
|
|
dreq->iocb = iocb;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:14 +00:00
|
|
|
nfs_add_stats(inode, NFSIOS_DIRECTREADBYTES, count);
|
2005-04-16 22:20:36 +00:00
|
|
|
rpc_clnt_sigmask(clnt, &oldset);
|
2006-06-20 16:56:49 +00:00
|
|
|
result = nfs_direct_read_schedule(dreq, user_addr, count, pos);
|
|
|
|
if (!result)
|
|
|
|
result = nfs_direct_wait(dreq);
|
2005-04-16 22:20:36 +00:00
|
|
|
rpc_clnt_sigunmask(clnt, &oldset);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
static void nfs_direct_free_writedata(struct nfs_direct_req *dreq)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:36 +00:00
|
|
|
list_splice_init(&dreq->rewrite_list, &dreq->list);
|
|
|
|
while (!list_empty(&dreq->list)) {
|
|
|
|
struct nfs_write_data *data = list_entry(dreq->list.next, struct nfs_write_data, pages);
|
|
|
|
list_del(&data->pages);
|
2006-06-20 16:56:49 +00:00
|
|
|
nfs_direct_release_pages(data->pagevec, data->npages);
|
2006-03-20 18:44:36 +00:00
|
|
|
nfs_writedata_release(data);
|
|
|
|
}
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
#if defined(CONFIG_NFS_V3) || defined(CONFIG_NFS_V4)
|
|
|
|
static void nfs_direct_write_reschedule(struct nfs_direct_req *dreq)
|
|
|
|
{
|
2006-06-20 16:55:45 +00:00
|
|
|
struct inode *inode = dreq->inode;
|
|
|
|
struct list_head *p;
|
|
|
|
struct nfs_write_data *data;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->count = 0;
|
2006-06-20 16:55:45 +00:00
|
|
|
get_dreq(dreq);
|
|
|
|
|
|
|
|
list_for_each(p, &dreq->rewrite_list) {
|
|
|
|
data = list_entry(p, struct nfs_write_data, pages);
|
|
|
|
|
|
|
|
get_dreq(dreq);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reset data->res.
|
|
|
|
*/
|
|
|
|
nfs_fattr_init(&data->fattr);
|
|
|
|
data->res.count = data->args.count;
|
|
|
|
memset(&data->verf, 0, sizeof(data->verf));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reuse data->task; data->args should not have changed
|
|
|
|
* since the original request was sent.
|
|
|
|
*/
|
|
|
|
rpc_init_task(&data->task, NFS_CLIENT(inode), RPC_TASK_ASYNC,
|
|
|
|
&nfs_write_direct_ops, data);
|
|
|
|
NFS_PROTO(inode)->write_setup(data, FLUSH_STABLE);
|
|
|
|
|
|
|
|
data->task.tk_priority = RPC_PRIORITY_NORMAL;
|
|
|
|
data->task.tk_cookie = (unsigned long) inode;
|
2006-03-20 18:44:36 +00:00
|
|
|
|
2006-06-20 16:55:45 +00:00
|
|
|
/*
|
|
|
|
* We're called via an RPC callback, so BKL is already held.
|
|
|
|
*/
|
|
|
|
rpc_execute(&data->task);
|
|
|
|
|
|
|
|
dprintk("NFS: %5u rescheduled direct write call (req %s/%Ld, %u bytes @ offset %Lu)\n",
|
|
|
|
data->task.tk_pid,
|
|
|
|
inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(inode),
|
|
|
|
data->args.count,
|
|
|
|
(unsigned long long)data->args.offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (put_dreq(dreq))
|
|
|
|
nfs_direct_write_complete(dreq, inode);
|
2006-03-20 18:44:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void nfs_direct_commit_result(struct rpc_task *task, void *calldata)
|
|
|
|
{
|
|
|
|
struct nfs_write_data *data = calldata;
|
|
|
|
struct nfs_direct_req *dreq = (struct nfs_direct_req *) data->req;
|
|
|
|
|
|
|
|
/* Call the NFS version-specific code */
|
|
|
|
if (NFS_PROTO(data->inode)->commit_done(task, data) != 0)
|
|
|
|
return;
|
|
|
|
if (unlikely(task->tk_status < 0)) {
|
|
|
|
dreq->error = task->tk_status;
|
|
|
|
dreq->flags = NFS_ODIRECT_RESCHED_WRITES;
|
|
|
|
}
|
|
|
|
if (memcmp(&dreq->verf, &data->verf, sizeof(data->verf))) {
|
|
|
|
dprintk("NFS: %5u commit verify failed\n", task->tk_pid);
|
|
|
|
dreq->flags = NFS_ODIRECT_RESCHED_WRITES;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
dprintk("NFS: %5u commit returned %d\n", task->tk_pid, task->tk_status);
|
|
|
|
nfs_direct_write_complete(dreq, data->inode);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
static const struct rpc_call_ops nfs_commit_direct_ops = {
|
|
|
|
.rpc_call_done = nfs_direct_commit_result,
|
|
|
|
.rpc_release = nfs_commit_release,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void nfs_direct_commit_schedule(struct nfs_direct_req *dreq)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:36 +00:00
|
|
|
struct nfs_write_data *data = dreq->commit_data;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
data->inode = dreq->inode;
|
2006-03-20 18:44:36 +00:00
|
|
|
data->cred = dreq->ctx->cred;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
data->args.fh = NFS_FH(data->inode);
|
2006-06-20 16:56:16 +00:00
|
|
|
data->args.offset = 0;
|
|
|
|
data->args.count = 0;
|
2006-03-20 18:44:36 +00:00
|
|
|
data->res.count = 0;
|
|
|
|
data->res.fattr = &data->fattr;
|
|
|
|
data->res.verf = &data->verf;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
rpc_init_task(&data->task, NFS_CLIENT(dreq->inode), RPC_TASK_ASYNC,
|
|
|
|
&nfs_commit_direct_ops, data);
|
|
|
|
NFS_PROTO(data->inode)->commit_setup(data, 0);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
data->task.tk_priority = RPC_PRIORITY_NORMAL;
|
|
|
|
data->task.tk_cookie = (unsigned long)data->inode;
|
|
|
|
/* Note: task.tk_ops->rpc_release will free dreq->commit_data */
|
|
|
|
dreq->commit_data = NULL;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-04-18 17:21:42 +00:00
|
|
|
dprintk("NFS: %5u initiated commit call\n", data->task.tk_pid);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
lock_kernel();
|
|
|
|
rpc_execute(&data->task);
|
|
|
|
unlock_kernel();
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
static void nfs_direct_write_complete(struct nfs_direct_req *dreq, struct inode *inode)
|
|
|
|
{
|
|
|
|
int flags = dreq->flags;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->flags = 0;
|
|
|
|
switch (flags) {
|
|
|
|
case NFS_ODIRECT_DO_COMMIT:
|
|
|
|
nfs_direct_commit_schedule(dreq);
|
2005-04-16 22:20:36 +00:00
|
|
|
break;
|
2006-03-20 18:44:36 +00:00
|
|
|
case NFS_ODIRECT_RESCHED_WRITES:
|
|
|
|
nfs_direct_write_reschedule(dreq);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
nfs_end_data_update(inode);
|
|
|
|
if (dreq->commit_data != NULL)
|
|
|
|
nfs_commit_free(dreq->commit_data);
|
|
|
|
nfs_direct_free_writedata(dreq);
|
|
|
|
nfs_direct_complete(dreq);
|
|
|
|
}
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
static void nfs_alloc_commit_data(struct nfs_direct_req *dreq)
|
|
|
|
{
|
|
|
|
dreq->commit_data = nfs_commit_alloc(0);
|
|
|
|
if (dreq->commit_data != NULL)
|
|
|
|
dreq->commit_data->req = (struct nfs_page *) dreq;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void nfs_alloc_commit_data(struct nfs_direct_req *dreq)
|
|
|
|
{
|
|
|
|
dreq->commit_data = NULL;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
static void nfs_direct_write_complete(struct nfs_direct_req *dreq, struct inode *inode)
|
|
|
|
{
|
|
|
|
nfs_end_data_update(inode);
|
|
|
|
nfs_direct_free_writedata(dreq);
|
|
|
|
nfs_direct_complete(dreq);
|
|
|
|
}
|
|
|
|
#endif
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
static struct nfs_direct_req *nfs_direct_write_alloc(size_t nbytes, size_t wsize)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:32 +00:00
|
|
|
struct list_head *list;
|
|
|
|
struct nfs_direct_req *dreq;
|
|
|
|
unsigned int wpages = (wsize + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
dreq = nfs_direct_req_alloc();
|
|
|
|
if (!dreq)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
list = &dreq->list;
|
|
|
|
for(;;) {
|
|
|
|
struct nfs_write_data *data = nfs_writedata_alloc(wpages);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
if (unlikely(!data)) {
|
|
|
|
while (!list_empty(list)) {
|
|
|
|
data = list_entry(list->next,
|
|
|
|
struct nfs_write_data, pages);
|
|
|
|
list_del(&data->pages);
|
|
|
|
nfs_writedata_free(data);
|
|
|
|
}
|
|
|
|
kref_put(&dreq->kref, nfs_direct_req_release);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&data->pages);
|
|
|
|
list_add(&data->pages, list);
|
|
|
|
|
|
|
|
data->req = (struct nfs_page *) dreq;
|
2006-06-20 16:55:19 +00:00
|
|
|
get_dreq(dreq);
|
2006-03-20 18:44:32 +00:00
|
|
|
if (nbytes <= wsize)
|
|
|
|
break;
|
|
|
|
nbytes -= wsize;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
nfs_alloc_commit_data(dreq);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
kref_get(&dreq->kref);
|
|
|
|
return dreq;
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
static void nfs_direct_write_result(struct rpc_task *task, void *calldata)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:32 +00:00
|
|
|
struct nfs_write_data *data = calldata;
|
|
|
|
struct nfs_direct_req *dreq = (struct nfs_direct_req *) data->req;
|
|
|
|
int status = task->tk_status;
|
|
|
|
|
|
|
|
if (nfs_writeback_done(task, data) != 0)
|
|
|
|
return;
|
|
|
|
|
2006-03-20 18:44:34 +00:00
|
|
|
spin_lock(&dreq->lock);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
if (likely(status >= 0))
|
2006-03-20 18:44:34 +00:00
|
|
|
dreq->count += data->res.count;
|
2006-03-20 18:44:32 +00:00
|
|
|
else
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->error = task->tk_status;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
if (data->res.verf->committed != NFS_FILE_SYNC) {
|
|
|
|
switch (dreq->flags) {
|
|
|
|
case 0:
|
|
|
|
memcpy(&dreq->verf, &data->verf, sizeof(dreq->verf));
|
|
|
|
dreq->flags = NFS_ODIRECT_DO_COMMIT;
|
2005-04-16 22:20:36 +00:00
|
|
|
break;
|
2006-03-20 18:44:36 +00:00
|
|
|
case NFS_ODIRECT_DO_COMMIT:
|
|
|
|
if (memcmp(&dreq->verf, &data->verf, sizeof(dreq->verf))) {
|
|
|
|
dprintk("NFS: %5u write verify failed\n", task->tk_pid);
|
|
|
|
dreq->flags = NFS_ODIRECT_RESCHED_WRITES;
|
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
}
|
2006-03-20 18:44:36 +00:00
|
|
|
|
|
|
|
spin_unlock(&dreq->lock);
|
2005-04-16 22:20:36 +00:00
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
/*
|
|
|
|
* NB: Return the value of the first error return code. Subsequent
|
|
|
|
* errors after the first one are ignored.
|
2005-04-16 22:20:36 +00:00
|
|
|
*/
|
2006-03-20 18:44:36 +00:00
|
|
|
static void nfs_direct_write_release(void *calldata)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2006-03-20 18:44:36 +00:00
|
|
|
struct nfs_write_data *data = calldata;
|
|
|
|
struct nfs_direct_req *dreq = (struct nfs_direct_req *) data->req;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:55:19 +00:00
|
|
|
if (put_dreq(dreq))
|
|
|
|
nfs_direct_write_complete(dreq, data->inode);
|
2006-03-20 18:44:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct rpc_call_ops nfs_write_direct_ops = {
|
|
|
|
.rpc_call_done = nfs_direct_write_result,
|
2006-03-20 18:44:36 +00:00
|
|
|
.rpc_release = nfs_direct_write_release,
|
2006-03-20 18:44:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For each nfs_write_data struct that was allocated on the list, dispatch
|
2006-06-20 16:56:49 +00:00
|
|
|
* an NFS WRITE operation. If get_user_pages() fails, we stop sending writes.
|
|
|
|
* Write length accounting is handled by nfs_direct_write_result().
|
|
|
|
* Otherwise, if no requests have been sent, just return an error.
|
2006-03-20 18:44:32 +00:00
|
|
|
*/
|
2006-06-20 16:56:49 +00:00
|
|
|
static ssize_t nfs_direct_write_schedule(struct nfs_direct_req *dreq, unsigned long user_addr, size_t count, loff_t pos, int sync)
|
2006-03-20 18:44:32 +00:00
|
|
|
{
|
2006-03-20 18:44:36 +00:00
|
|
|
struct nfs_open_context *ctx = dreq->ctx;
|
|
|
|
struct inode *inode = ctx->dentry->d_inode;
|
2006-03-20 18:44:32 +00:00
|
|
|
struct list_head *list = &dreq->list;
|
|
|
|
size_t wsize = NFS_SERVER(inode)->wsize;
|
2006-06-20 16:56:49 +00:00
|
|
|
unsigned int pgbase;
|
|
|
|
int result;
|
|
|
|
ssize_t started = 0;
|
|
|
|
struct nfs_write_data *data;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:56:16 +00:00
|
|
|
pgbase = user_addr & ~PAGE_MASK;
|
2005-04-16 22:20:36 +00:00
|
|
|
do {
|
2006-03-20 18:44:32 +00:00
|
|
|
size_t bytes;
|
|
|
|
|
|
|
|
bytes = wsize;
|
|
|
|
if (count < wsize)
|
|
|
|
bytes = count;
|
|
|
|
|
2006-03-20 18:44:37 +00:00
|
|
|
BUG_ON(list_empty(list));
|
2006-03-20 18:44:32 +00:00
|
|
|
data = list_entry(list->next, struct nfs_write_data, pages);
|
2006-06-20 16:56:49 +00:00
|
|
|
|
|
|
|
data->npages = nfs_direct_count_pages(user_addr, bytes);
|
|
|
|
down_read(¤t->mm->mmap_sem);
|
|
|
|
result = get_user_pages(current, current->mm, user_addr,
|
|
|
|
data->npages, 0, 0, data->pagevec, NULL);
|
|
|
|
up_read(¤t->mm->mmap_sem);
|
|
|
|
if (unlikely(result < data->npages))
|
|
|
|
goto out_err;
|
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
list_move_tail(&data->pages, &dreq->rewrite_list);
|
2006-03-20 18:44:32 +00:00
|
|
|
|
|
|
|
data->inode = inode;
|
|
|
|
data->cred = ctx->cred;
|
|
|
|
data->args.fh = NFS_FH(inode);
|
|
|
|
data->args.context = ctx;
|
2006-03-20 18:44:34 +00:00
|
|
|
data->args.offset = pos;
|
2006-03-20 18:44:32 +00:00
|
|
|
data->args.pgbase = pgbase;
|
2006-06-20 16:56:49 +00:00
|
|
|
data->args.pages = data->pagevec;
|
2006-03-20 18:44:32 +00:00
|
|
|
data->args.count = bytes;
|
|
|
|
data->res.fattr = &data->fattr;
|
|
|
|
data->res.count = bytes;
|
2006-03-20 18:44:32 +00:00
|
|
|
data->res.verf = &data->verf;
|
2006-03-20 18:44:32 +00:00
|
|
|
|
|
|
|
rpc_init_task(&data->task, NFS_CLIENT(inode), RPC_TASK_ASYNC,
|
|
|
|
&nfs_write_direct_ops, data);
|
2006-03-20 18:44:36 +00:00
|
|
|
NFS_PROTO(inode)->write_setup(data, sync);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
data->task.tk_priority = RPC_PRIORITY_NORMAL;
|
|
|
|
data->task.tk_cookie = (unsigned long) inode;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
lock_kernel();
|
2006-03-20 18:44:32 +00:00
|
|
|
rpc_execute(&data->task);
|
2005-04-16 22:20:36 +00:00
|
|
|
unlock_kernel();
|
|
|
|
|
2006-03-20 18:44:42 +00:00
|
|
|
dfprintk(VFS, "NFS: %5u initiated direct write call (req %s/%Ld, %zu bytes @ offset %Lu)\n",
|
2006-03-20 18:44:32 +00:00
|
|
|
data->task.tk_pid,
|
|
|
|
inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(inode),
|
|
|
|
bytes,
|
|
|
|
(unsigned long long)data->args.offset);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
started += bytes;
|
|
|
|
user_addr += bytes;
|
2006-03-20 18:44:34 +00:00
|
|
|
pos += bytes;
|
2006-03-20 18:44:32 +00:00
|
|
|
pgbase += bytes;
|
|
|
|
pgbase &= ~PAGE_MASK;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
count -= bytes;
|
|
|
|
} while (count != 0);
|
2006-03-20 18:44:37 +00:00
|
|
|
BUG_ON(!list_empty(list));
|
2006-06-20 16:56:49 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_err:
|
|
|
|
if (result > 0)
|
|
|
|
nfs_direct_release_pages(data->pagevec, result);
|
|
|
|
|
|
|
|
list_add(&data->pages, list);
|
|
|
|
while (!list_empty(list)) {
|
|
|
|
data = list_entry(list->next, struct nfs_write_data, pages);
|
|
|
|
list_del(&data->pages);
|
|
|
|
nfs_writedata_free(data);
|
|
|
|
if (put_dreq(dreq))
|
|
|
|
nfs_direct_write_complete(dreq, inode);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (started)
|
|
|
|
return 0;
|
|
|
|
return result < 0 ? (ssize_t) result : -EFAULT;
|
2006-03-20 18:44:32 +00:00
|
|
|
}
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
static ssize_t nfs_direct_write(struct kiocb *iocb, unsigned long user_addr, size_t count, loff_t pos)
|
2006-03-20 18:44:32 +00:00
|
|
|
{
|
|
|
|
ssize_t result;
|
|
|
|
sigset_t oldset;
|
2006-03-20 18:44:33 +00:00
|
|
|
struct inode *inode = iocb->ki_filp->f_mapping->host;
|
2006-03-20 18:44:32 +00:00
|
|
|
struct rpc_clnt *clnt = NFS_CLIENT(inode);
|
|
|
|
struct nfs_direct_req *dreq;
|
2006-03-20 18:44:36 +00:00
|
|
|
size_t wsize = NFS_SERVER(inode)->wsize;
|
|
|
|
int sync = 0;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq = nfs_direct_write_alloc(count, wsize);
|
2006-03-20 18:44:32 +00:00
|
|
|
if (!dreq)
|
|
|
|
return -ENOMEM;
|
2006-03-20 18:44:36 +00:00
|
|
|
if (dreq->commit_data == NULL || count < wsize)
|
|
|
|
sync = FLUSH_STABLE;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:33 +00:00
|
|
|
dreq->inode = inode;
|
2006-03-20 18:44:36 +00:00
|
|
|
dreq->ctx = get_nfs_open_context((struct nfs_open_context *)iocb->ki_filp->private_data);
|
2006-03-20 18:44:33 +00:00
|
|
|
if (!is_sync_kiocb(iocb))
|
|
|
|
dreq->iocb = iocb;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
nfs_add_stats(inode, NFSIOS_DIRECTWRITTENBYTES, count);
|
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
nfs_begin_data_update(inode);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-03-20 18:44:32 +00:00
|
|
|
rpc_clnt_sigmask(clnt, &oldset);
|
2006-06-20 16:56:49 +00:00
|
|
|
result = nfs_direct_write_schedule(dreq, user_addr, count, pos, sync);
|
|
|
|
if (!result)
|
|
|
|
result = nfs_direct_wait(dreq);
|
2006-03-20 18:44:32 +00:00
|
|
|
rpc_clnt_sigunmask(clnt, &oldset);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nfs_file_direct_read - file direct read operation for NFS files
|
|
|
|
* @iocb: target I/O control block
|
|
|
|
* @buf: user's buffer into which to read data
|
2006-03-20 18:44:34 +00:00
|
|
|
* @count: number of bytes to read
|
|
|
|
* @pos: byte offset in file where reading starts
|
2005-04-16 22:20:36 +00:00
|
|
|
*
|
|
|
|
* We use this function for direct reads instead of calling
|
|
|
|
* generic_file_aio_read() in order to avoid gfar's check to see if
|
|
|
|
* the request starts before the end of the file. For that check
|
|
|
|
* to work, we must generate a GETATTR before each direct read, and
|
|
|
|
* even then there is a window between the GETATTR and the subsequent
|
2006-03-20 18:44:34 +00:00
|
|
|
* READ where the file size could change. Our preference is simply
|
2005-04-16 22:20:36 +00:00
|
|
|
* to do all reads the application wants, and the server will take
|
|
|
|
* care of managing the end of file boundary.
|
2006-03-20 18:44:34 +00:00
|
|
|
*
|
2005-04-16 22:20:36 +00:00
|
|
|
* This function also eliminates unnecessarily updating the file's
|
|
|
|
* atime locally, as the NFS server sets the file's atime, and this
|
|
|
|
* client must read the updated atime from the server back into its
|
|
|
|
* cache.
|
|
|
|
*/
|
2006-03-20 18:44:28 +00:00
|
|
|
ssize_t nfs_file_direct_read(struct kiocb *iocb, char __user *buf, size_t count, loff_t pos)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
ssize_t retval = -EINVAL;
|
|
|
|
struct file *file = iocb->ki_filp;
|
|
|
|
struct address_space *mapping = file->f_mapping;
|
|
|
|
|
2005-11-30 23:08:17 +00:00
|
|
|
dprintk("nfs: direct read(%s/%s, %lu@%Ld)\n",
|
2005-11-01 21:53:32 +00:00
|
|
|
file->f_dentry->d_parent->d_name.name,
|
|
|
|
file->f_dentry->d_name.name,
|
2005-11-30 23:08:17 +00:00
|
|
|
(unsigned long) count, (long long) pos);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
if (count < 0)
|
|
|
|
goto out;
|
|
|
|
retval = -EFAULT;
|
2006-03-20 18:44:29 +00:00
|
|
|
if (!access_ok(VERIFY_WRITE, buf, count))
|
2005-04-16 22:20:36 +00:00
|
|
|
goto out;
|
|
|
|
retval = 0;
|
|
|
|
if (!count)
|
|
|
|
goto out;
|
|
|
|
|
2005-12-13 21:13:54 +00:00
|
|
|
retval = nfs_sync_mapping(mapping);
|
|
|
|
if (retval)
|
|
|
|
goto out;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
retval = nfs_direct_read(iocb, (unsigned long) buf, count, pos);
|
2005-04-16 22:20:36 +00:00
|
|
|
if (retval > 0)
|
2006-03-20 18:44:29 +00:00
|
|
|
iocb->ki_pos = pos + retval;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
out:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* nfs_file_direct_write - file direct write operation for NFS files
|
|
|
|
* @iocb: target I/O control block
|
|
|
|
* @buf: user's buffer from which to write data
|
2006-03-20 18:44:34 +00:00
|
|
|
* @count: number of bytes to write
|
|
|
|
* @pos: byte offset in file where writing starts
|
2005-04-16 22:20:36 +00:00
|
|
|
*
|
|
|
|
* We use this function for direct writes instead of calling
|
|
|
|
* generic_file_aio_write() in order to avoid taking the inode
|
|
|
|
* semaphore and updating the i_size. The NFS server will set
|
|
|
|
* the new i_size and this client must read the updated size
|
|
|
|
* back into its cache. We let the server do generic write
|
|
|
|
* parameter checking and report problems.
|
|
|
|
*
|
|
|
|
* We also avoid an unnecessary invocation of generic_osync_inode(),
|
|
|
|
* as it is fairly meaningless to sync the metadata of an NFS file.
|
|
|
|
*
|
|
|
|
* We eliminate local atime updates, see direct read above.
|
|
|
|
*
|
|
|
|
* We avoid unnecessary page cache invalidations for normal cached
|
|
|
|
* readers of this file.
|
|
|
|
*
|
|
|
|
* Note that O_APPEND is not supported for NFS direct writes, as there
|
|
|
|
* is no atomic O_APPEND write facility in the NFS protocol.
|
|
|
|
*/
|
2006-03-20 18:44:28 +00:00
|
|
|
ssize_t nfs_file_direct_write(struct kiocb *iocb, const char __user *buf, size_t count, loff_t pos)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
2005-11-30 23:08:17 +00:00
|
|
|
ssize_t retval;
|
2005-04-16 22:20:36 +00:00
|
|
|
struct file *file = iocb->ki_filp;
|
|
|
|
struct address_space *mapping = file->f_mapping;
|
|
|
|
|
2005-11-30 23:08:17 +00:00
|
|
|
dfprintk(VFS, "nfs: direct write(%s/%s, %lu@%Ld)\n",
|
2005-11-01 21:53:32 +00:00
|
|
|
file->f_dentry->d_parent->d_name.name,
|
2005-11-30 23:08:17 +00:00
|
|
|
file->f_dentry->d_name.name,
|
|
|
|
(unsigned long) count, (long long) pos);
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2005-11-30 23:08:17 +00:00
|
|
|
retval = generic_write_checks(file, &pos, &count, 0);
|
|
|
|
if (retval)
|
2005-04-16 22:20:36 +00:00
|
|
|
goto out;
|
2005-11-30 23:08:17 +00:00
|
|
|
|
|
|
|
retval = -EINVAL;
|
|
|
|
if ((ssize_t) count < 0)
|
2005-04-16 22:20:36 +00:00
|
|
|
goto out;
|
|
|
|
retval = 0;
|
|
|
|
if (!count)
|
|
|
|
goto out;
|
2005-11-30 23:08:17 +00:00
|
|
|
|
|
|
|
retval = -EFAULT;
|
2006-03-20 18:44:32 +00:00
|
|
|
if (!access_ok(VERIFY_READ, buf, count))
|
2005-11-30 23:08:17 +00:00
|
|
|
goto out;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2005-12-13 21:13:54 +00:00
|
|
|
retval = nfs_sync_mapping(mapping);
|
|
|
|
if (retval)
|
|
|
|
goto out;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
2006-06-20 16:56:49 +00:00
|
|
|
retval = nfs_direct_write(iocb, (unsigned long) buf, count, pos);
|
2006-03-20 18:44:33 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX: nfs_end_data_update() already ensures this file's
|
|
|
|
* cached data is subsequently invalidated. Do we really
|
|
|
|
* need to call invalidate_inode_pages2() again here?
|
|
|
|
*
|
|
|
|
* For aio writes, this invalidation will almost certainly
|
|
|
|
* occur before the writes complete. Kind of racey.
|
|
|
|
*/
|
2005-04-16 22:20:36 +00:00
|
|
|
if (mapping->nrpages)
|
|
|
|
invalidate_inode_pages2(mapping);
|
2006-03-20 18:44:33 +00:00
|
|
|
|
2005-04-16 22:20:36 +00:00
|
|
|
if (retval > 0)
|
2005-11-30 23:08:17 +00:00
|
|
|
iocb->ki_pos = pos + retval;
|
2005-04-16 22:20:36 +00:00
|
|
|
|
|
|
|
out:
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:34 +00:00
|
|
|
/**
|
|
|
|
* nfs_init_directcache - create a slab cache for nfs_direct_req structures
|
|
|
|
*
|
|
|
|
*/
|
NFS: Split fs/nfs/inode.c
As fs/nfs/inode.c is rather large, heterogenous and unwieldy, the attached
patch splits it up into a number of files:
(*) fs/nfs/inode.c
Strictly inode specific functions.
(*) fs/nfs/super.c
Superblock management functions for NFS and NFS4, normal access, clones
and referrals. The NFS4 superblock functions _could_ move out into a
separate conditionally compiled file, but it's probably not worth it as
there're so many common bits.
(*) fs/nfs/namespace.c
Some namespace-specific functions have been moved here.
(*) fs/nfs/nfs4namespace.c
NFS4-specific namespace functions (this could be merged into the previous
file). This file is conditionally compiled.
(*) fs/nfs/internal.h
Inter-file declarations, plus a few simple utility functions moved from
fs/nfs/inode.c.
Additionally, all the in-.c-file externs have been moved here, and those
files they were moved from now includes this file.
For the most part, the functions have not been changed, only some multiplexor
functions have changed significantly.
I've also:
(*) Added some extra banner comments above some functions.
(*) Rearranged the function order within the files to be more logical and
better grouped (IMO), though someone may prefer a different order.
(*) Reduced the number of #ifdefs in .c files.
(*) Added missing __init and __exit directives.
Signed-Off-By: David Howells <dhowells@redhat.com>
2006-06-09 13:34:33 +00:00
|
|
|
int __init nfs_init_directcache(void)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
nfs_direct_cachep = kmem_cache_create("nfs_direct_cache",
|
|
|
|
sizeof(struct nfs_direct_req),
|
2006-03-24 11:16:06 +00:00
|
|
|
0, (SLAB_RECLAIM_ACCOUNT|
|
|
|
|
SLAB_MEM_SPREAD),
|
2005-04-16 22:20:36 +00:00
|
|
|
NULL, NULL);
|
|
|
|
if (nfs_direct_cachep == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-03-20 18:44:34 +00:00
|
|
|
/**
|
NFS: Split fs/nfs/inode.c
As fs/nfs/inode.c is rather large, heterogenous and unwieldy, the attached
patch splits it up into a number of files:
(*) fs/nfs/inode.c
Strictly inode specific functions.
(*) fs/nfs/super.c
Superblock management functions for NFS and NFS4, normal access, clones
and referrals. The NFS4 superblock functions _could_ move out into a
separate conditionally compiled file, but it's probably not worth it as
there're so many common bits.
(*) fs/nfs/namespace.c
Some namespace-specific functions have been moved here.
(*) fs/nfs/nfs4namespace.c
NFS4-specific namespace functions (this could be merged into the previous
file). This file is conditionally compiled.
(*) fs/nfs/internal.h
Inter-file declarations, plus a few simple utility functions moved from
fs/nfs/inode.c.
Additionally, all the in-.c-file externs have been moved here, and those
files they were moved from now includes this file.
For the most part, the functions have not been changed, only some multiplexor
functions have changed significantly.
I've also:
(*) Added some extra banner comments above some functions.
(*) Rearranged the function order within the files to be more logical and
better grouped (IMO), though someone may prefer a different order.
(*) Reduced the number of #ifdefs in .c files.
(*) Added missing __init and __exit directives.
Signed-Off-By: David Howells <dhowells@redhat.com>
2006-06-09 13:34:33 +00:00
|
|
|
* nfs_destroy_directcache - destroy the slab cache for nfs_direct_req structures
|
2006-03-20 18:44:34 +00:00
|
|
|
*
|
|
|
|
*/
|
NFS: Split fs/nfs/inode.c
As fs/nfs/inode.c is rather large, heterogenous and unwieldy, the attached
patch splits it up into a number of files:
(*) fs/nfs/inode.c
Strictly inode specific functions.
(*) fs/nfs/super.c
Superblock management functions for NFS and NFS4, normal access, clones
and referrals. The NFS4 superblock functions _could_ move out into a
separate conditionally compiled file, but it's probably not worth it as
there're so many common bits.
(*) fs/nfs/namespace.c
Some namespace-specific functions have been moved here.
(*) fs/nfs/nfs4namespace.c
NFS4-specific namespace functions (this could be merged into the previous
file). This file is conditionally compiled.
(*) fs/nfs/internal.h
Inter-file declarations, plus a few simple utility functions moved from
fs/nfs/inode.c.
Additionally, all the in-.c-file externs have been moved here, and those
files they were moved from now includes this file.
For the most part, the functions have not been changed, only some multiplexor
functions have changed significantly.
I've also:
(*) Added some extra banner comments above some functions.
(*) Rearranged the function order within the files to be more logical and
better grouped (IMO), though someone may prefer a different order.
(*) Reduced the number of #ifdefs in .c files.
(*) Added missing __init and __exit directives.
Signed-Off-By: David Howells <dhowells@redhat.com>
2006-06-09 13:34:33 +00:00
|
|
|
void __exit nfs_destroy_directcache(void)
|
2005-04-16 22:20:36 +00:00
|
|
|
{
|
|
|
|
if (kmem_cache_destroy(nfs_direct_cachep))
|
|
|
|
printk(KERN_INFO "nfs_direct_cache: not all structures were freed\n");
|
|
|
|
}
|