2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* linux/fs/nfs/read.c
|
|
|
|
*
|
|
|
|
* Block I/O for NFS
|
|
|
|
*
|
|
|
|
* Partial copy of Linus' read cache modifications to fs/nfs/file.c
|
|
|
|
* modified for async RPC by okir@monad.swb.de
|
|
|
|
*
|
|
|
|
* We do an ugly hack here in order to return proper error codes to the
|
|
|
|
* user program when a read request failed: since generic_file_read
|
|
|
|
* only checks the return value of inode->i_op->readpage() which is always 0
|
|
|
|
* for async RPC, we set the error bit of the page to 1 when an error occurs,
|
|
|
|
* and make nfs_readpage transmit requests synchronously when encountering this.
|
|
|
|
* This is only a small problem, though, since we now retry all operations
|
|
|
|
* within the RPC code when root squashing is suspected.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/time.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/fcntl.h>
|
|
|
|
#include <linux/stat.h>
|
|
|
|
#include <linux/mm.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include <linux/sunrpc/clnt.h>
|
|
|
|
#include <linux/nfs_fs.h>
|
|
|
|
#include <linux/nfs_page.h>
|
|
|
|
#include <linux/smp_lock.h>
|
|
|
|
|
|
|
|
#include <asm/system.h>
|
|
|
|
|
2006-03-21 02:44:14 +08:00
|
|
|
#include "iostat.h"
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#define NFSDBG_FACILITY NFSDBG_PAGECACHE
|
|
|
|
|
|
|
|
static int nfs_pagein_one(struct list_head *, struct inode *);
|
2006-03-21 02:44:27 +08:00
|
|
|
static const struct rpc_call_ops nfs_read_partial_ops;
|
|
|
|
static const struct rpc_call_ops nfs_read_full_ops;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static kmem_cache_t *nfs_rdata_cachep;
|
2006-03-21 02:44:37 +08:00
|
|
|
static mempool_t *nfs_rdata_mempool;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
#define MIN_POOL_READ (32)
|
|
|
|
|
2006-09-09 00:48:54 +08:00
|
|
|
struct nfs_read_data *nfs_readdata_alloc(size_t len)
|
2006-03-21 02:44:37 +08:00
|
|
|
{
|
2006-09-09 00:48:54 +08:00
|
|
|
unsigned int pagecount = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
|
2006-12-07 12:33:14 +08:00
|
|
|
struct nfs_read_data *p = mempool_alloc(nfs_rdata_mempool, GFP_NOFS);
|
2006-03-21 02:44:37 +08:00
|
|
|
|
|
|
|
if (p) {
|
|
|
|
memset(p, 0, sizeof(*p));
|
|
|
|
INIT_LIST_HEAD(&p->pages);
|
2006-09-09 00:48:54 +08:00
|
|
|
p->npages = pagecount;
|
2006-05-25 13:40:53 +08:00
|
|
|
if (pagecount <= ARRAY_SIZE(p->page_array))
|
|
|
|
p->pagevec = p->page_array;
|
2006-03-21 02:44:37 +08:00
|
|
|
else {
|
2006-05-25 13:40:53 +08:00
|
|
|
p->pagevec = kcalloc(pagecount, sizeof(struct page *), GFP_NOFS);
|
|
|
|
if (!p->pagevec) {
|
2006-03-21 02:44:37 +08:00
|
|
|
mempool_free(p, nfs_rdata_mempool);
|
|
|
|
p = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
2006-08-04 03:07:47 +08:00
|
|
|
static void nfs_readdata_free(struct nfs_read_data *p)
|
2006-03-21 02:44:37 +08:00
|
|
|
{
|
|
|
|
if (p && (p->pagevec != &p->page_array[0]))
|
|
|
|
kfree(p->pagevec);
|
|
|
|
mempool_free(p, nfs_rdata_mempool);
|
|
|
|
}
|
|
|
|
|
2006-01-03 16:55:04 +08:00
|
|
|
void nfs_readdata_release(void *data)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
nfs_readdata_free(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static
|
|
|
|
unsigned int nfs_page_length(struct inode *inode, struct page *page)
|
|
|
|
{
|
|
|
|
loff_t i_size = i_size_read(inode);
|
|
|
|
unsigned long idx;
|
|
|
|
|
|
|
|
if (i_size <= 0)
|
|
|
|
return 0;
|
|
|
|
idx = (i_size - 1) >> PAGE_CACHE_SHIFT;
|
|
|
|
if (page->index > idx)
|
|
|
|
return 0;
|
|
|
|
if (page->index != idx)
|
|
|
|
return PAGE_CACHE_SIZE;
|
|
|
|
return 1 + ((i_size - 1) & (PAGE_CACHE_SIZE - 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
static
|
|
|
|
int nfs_return_empty_page(struct page *page)
|
|
|
|
{
|
|
|
|
memclear_highpage_flush(page, 0, PAGE_CACHE_SIZE);
|
|
|
|
SetPageUptodate(page);
|
|
|
|
unlock_page(page);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-05-25 13:40:44 +08:00
|
|
|
static void nfs_readpage_truncate_uninitialised_page(struct nfs_read_data *data)
|
|
|
|
{
|
|
|
|
unsigned int remainder = data->args.count - data->res.count;
|
|
|
|
unsigned int base = data->args.pgbase + data->res.count;
|
|
|
|
unsigned int pglen;
|
|
|
|
struct page **pages;
|
|
|
|
|
|
|
|
if (data->res.eof == 0 || remainder == 0)
|
|
|
|
return;
|
|
|
|
/*
|
|
|
|
* Note: "remainder" can never be negative, since we check for
|
|
|
|
* this in the XDR code.
|
|
|
|
*/
|
|
|
|
pages = &data->args.pages[base >> PAGE_CACHE_SHIFT];
|
|
|
|
base &= ~PAGE_CACHE_MASK;
|
|
|
|
pglen = PAGE_CACHE_SIZE - base;
|
2006-08-23 01:44:32 +08:00
|
|
|
for (;;) {
|
|
|
|
if (remainder <= pglen) {
|
|
|
|
memclear_highpage_flush(*pages, base, remainder);
|
|
|
|
break;
|
|
|
|
}
|
2006-05-25 13:40:44 +08:00
|
|
|
memclear_highpage_flush(*pages, base, pglen);
|
2006-08-23 01:44:32 +08:00
|
|
|
pages++;
|
|
|
|
remainder -= pglen;
|
|
|
|
pglen = PAGE_CACHE_SIZE;
|
|
|
|
base = 0;
|
|
|
|
}
|
2006-05-25 13:40:44 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Read a page synchronously.
|
|
|
|
*/
|
|
|
|
static int nfs_readpage_sync(struct nfs_open_context *ctx, struct inode *inode,
|
|
|
|
struct page *page)
|
|
|
|
{
|
|
|
|
unsigned int rsize = NFS_SERVER(inode)->rsize;
|
|
|
|
unsigned int count = PAGE_CACHE_SIZE;
|
|
|
|
int result;
|
|
|
|
struct nfs_read_data *rdata;
|
|
|
|
|
2006-09-09 00:48:54 +08:00
|
|
|
rdata = nfs_readdata_alloc(count);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!rdata)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
memset(rdata, 0, sizeof(*rdata));
|
|
|
|
rdata->flags = (IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0);
|
|
|
|
rdata->cred = ctx->cred;
|
|
|
|
rdata->inode = inode;
|
|
|
|
INIT_LIST_HEAD(&rdata->pages);
|
|
|
|
rdata->args.fh = NFS_FH(inode);
|
|
|
|
rdata->args.context = ctx;
|
|
|
|
rdata->args.pages = &page;
|
|
|
|
rdata->args.pgbase = 0UL;
|
|
|
|
rdata->args.count = rsize;
|
|
|
|
rdata->res.fattr = &rdata->fattr;
|
|
|
|
|
|
|
|
dprintk("NFS: nfs_readpage_sync(%p)\n", page);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This works now because the socket layer never tries to DMA
|
|
|
|
* into this buffer directly.
|
|
|
|
*/
|
|
|
|
do {
|
|
|
|
if (count < rsize)
|
|
|
|
rdata->args.count = count;
|
|
|
|
rdata->res.count = rdata->args.count;
|
|
|
|
rdata->args.offset = page_offset(page) + rdata->args.pgbase;
|
|
|
|
|
|
|
|
dprintk("NFS: nfs_proc_read(%s, (%s/%Ld), %Lu, %u)\n",
|
NFS: Share NFS superblocks per-protocol per-server per-FSID
The attached patch makes NFS share superblocks between mounts from the same
server and FSID over the same protocol.
It does this by creating each superblock with a false root and returning the
real root dentry in the vfsmount presented by get_sb(). The root dentry set
starts off as an anonymous dentry if we don't already have the dentry for its
inode, otherwise it simply returns the dentry we already have.
We may thus end up with several trees of dentries in the superblock, and if at
some later point one of anonymous tree roots is discovered by normal filesystem
activity to be located in another tree within the superblock, the anonymous
root is named and materialises attached to the second tree at the appropriate
point.
Why do it this way? Why not pass an extra argument to the mount() syscall to
indicate the subpath and then pathwalk from the server root to the desired
directory? You can't guarantee this will work for two reasons:
(1) The root and intervening nodes may not be accessible to the client.
With NFS2 and NFS3, for instance, mountd is called on the server to get
the filehandle for the tip of a path. mountd won't give us handles for
anything we don't have permission to access, and so we can't set up NFS
inodes for such nodes, and so can't easily set up dentries (we'd have to
have ghost inodes or something).
With this patch we don't actually create dentries until we get handles
from the server that we can use to set up their inodes, and we don't
actually bind them into the tree until we know for sure where they go.
(2) Inaccessible symbolic links.
If we're asked to mount two exports from the server, eg:
mount warthog:/warthog/aaa/xxx /mmm
mount warthog:/warthog/bbb/yyy /nnn
We may not be able to access anything nearer the root than xxx and yyy,
but we may find out later that /mmm/www/yyy, say, is actually the same
directory as the one mounted on /nnn. What we might then find out, for
example, is that /warthog/bbb was actually a symbolic link to
/warthog/aaa/xxx/www, but we can't actually determine that by talking to
the server until /warthog is made available by NFS.
This would lead to having constructed an errneous dentry tree which we
can't easily fix. We can end up with a dentry marked as a directory when
it should actually be a symlink, or we could end up with an apparently
hardlinked directory.
With this patch we need not make assumptions about the type of a dentry
for which we can't retrieve information, nor need we assume we know its
place in the grand scheme of things until we actually see that place.
This patch reduces the possibility of aliasing in the inode and page caches for
inodes that may be accessed by more than one NFS export. It also reduces the
number of superblocks required for NFS where there are many NFS exports being
used from a server (home directory server + autofs for example).
This in turn makes it simpler to do local caching of network filesystems, as it
can then be guaranteed that there won't be links from multiple inodes in
separate superblocks to the same cache file.
Obviously, cache aliasing between different levels of NFS protocol could still
be a problem, but at least that gives us another key to use when indexing the
cache.
This patch makes the following changes:
(1) The server record construction/destruction has been abstracted out into
its own set of functions to make things easier to get right. These have
been moved into fs/nfs/client.c.
All the code in fs/nfs/client.c has to do with the management of
connections to servers, and doesn't touch superblocks in any way; the
remaining code in fs/nfs/super.c has to do with VFS superblock management.
(2) The sequence of events undertaken by NFS mount is now reordered:
(a) A volume representation (struct nfs_server) is allocated.
(b) A server representation (struct nfs_client) is acquired. This may be
allocated or shared, and is keyed on server address, port and NFS
version.
(c) If allocated, the client representation is initialised. The state
member variable of nfs_client is used to prevent a race during
initialisation from two mounts.
(d) For NFS4 a simple pathwalk is performed, walking from FH to FH to find
the root filehandle for the mount (fs/nfs/getroot.c). For NFS2/3 we
are given the root FH in advance.
(e) The volume FSID is probed for on the root FH.
(f) The volume representation is initialised from the FSINFO record
retrieved on the root FH.
(g) sget() is called to acquire a superblock. This may be allocated or
shared, keyed on client pointer and FSID.
(h) If allocated, the superblock is initialised.
(i) If the superblock is shared, then the new nfs_server record is
discarded.
(j) The root dentry for this mount is looked up from the root FH.
(k) The root dentry for this mount is assigned to the vfsmount.
(3) nfs_readdir_lookup() creates dentries for each of the entries readdir()
returns; this function now attaches disconnected trees from alternate
roots that happen to be discovered attached to a directory being read (in
the same way nfs_lookup() is made to do for lookup ops).
The new d_materialise_unique() function is now used to do this, thus
permitting the whole thing to be done under one set of locks, and thus
avoiding any race between mount and lookup operations on the same
directory.
(4) The client management code uses a new debug facility: NFSDBG_CLIENT which
is set by echoing 1024 to /proc/net/sunrpc/nfs_debug.
(5) Clone mounts are now called xdev mounts.
(6) Use the dentry passed to the statfs() op as the handle for retrieving fs
statistics rather than the root dentry of the superblock (which is now a
dummy).
Signed-Off-By: David Howells <dhowells@redhat.com>
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
2006-08-23 08:06:13 +08:00
|
|
|
NFS_SERVER(inode)->nfs_client->cl_hostname,
|
2005-04-17 06:20:36 +08:00
|
|
|
inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(inode),
|
|
|
|
(unsigned long long)rdata->args.pgbase,
|
|
|
|
rdata->args.count);
|
|
|
|
|
|
|
|
lock_kernel();
|
|
|
|
result = NFS_PROTO(inode)->read(rdata);
|
|
|
|
unlock_kernel();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Even if we had a partial success we can't mark the page
|
|
|
|
* cache valid.
|
|
|
|
*/
|
|
|
|
if (result < 0) {
|
|
|
|
if (result == -EISDIR)
|
|
|
|
result = -EINVAL;
|
|
|
|
goto io_error;
|
|
|
|
}
|
|
|
|
count -= result;
|
|
|
|
rdata->args.pgbase += result;
|
2006-03-21 02:44:14 +08:00
|
|
|
nfs_add_stats(inode, NFSIOS_SERVERREADBYTES, result);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Note: result == 0 should only happen if we're caching
|
|
|
|
* a write that extends the file and punches a hole.
|
|
|
|
*/
|
|
|
|
if (rdata->res.eof != 0 || result == 0)
|
|
|
|
break;
|
|
|
|
} while (count);
|
2005-08-19 02:24:12 +08:00
|
|
|
spin_lock(&inode->i_lock);
|
2005-08-19 02:24:09 +08:00
|
|
|
NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ATIME;
|
2005-08-19 02:24:12 +08:00
|
|
|
spin_unlock(&inode->i_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-09-16 04:03:45 +08:00
|
|
|
if (rdata->res.eof || rdata->res.count == rdata->args.count) {
|
2006-05-25 13:40:44 +08:00
|
|
|
SetPageUptodate(page);
|
2006-09-16 04:03:45 +08:00
|
|
|
if (rdata->res.eof && count != 0)
|
|
|
|
memclear_highpage_flush(page, rdata->args.pgbase, count);
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
result = 0;
|
|
|
|
|
|
|
|
io_error:
|
|
|
|
unlock_page(page);
|
|
|
|
nfs_readdata_free(rdata);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int nfs_readpage_async(struct nfs_open_context *ctx, struct inode *inode,
|
|
|
|
struct page *page)
|
|
|
|
{
|
|
|
|
LIST_HEAD(one_request);
|
|
|
|
struct nfs_page *new;
|
|
|
|
unsigned int len;
|
|
|
|
|
|
|
|
len = nfs_page_length(inode, page);
|
|
|
|
if (len == 0)
|
|
|
|
return nfs_return_empty_page(page);
|
|
|
|
new = nfs_create_request(ctx, inode, page, 0, len);
|
|
|
|
if (IS_ERR(new)) {
|
|
|
|
unlock_page(page);
|
|
|
|
return PTR_ERR(new);
|
|
|
|
}
|
|
|
|
if (len < PAGE_CACHE_SIZE)
|
|
|
|
memclear_highpage_flush(page, len, PAGE_CACHE_SIZE - len);
|
|
|
|
|
|
|
|
nfs_list_add_request(new, &one_request);
|
|
|
|
nfs_pagein_one(&one_request, inode);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfs_readpage_release(struct nfs_page *req)
|
|
|
|
{
|
|
|
|
unlock_page(req->wb_page);
|
|
|
|
|
|
|
|
dprintk("NFS: read done (%s/%Ld %d@%Ld)\n",
|
|
|
|
req->wb_context->dentry->d_inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(req->wb_context->dentry->d_inode),
|
|
|
|
req->wb_bytes,
|
|
|
|
(long long)req_offset(req));
|
2005-09-23 12:44:28 +08:00
|
|
|
nfs_clear_request(req);
|
|
|
|
nfs_release_request(req);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the NFS read request struct
|
|
|
|
*/
|
|
|
|
static void nfs_read_rpcsetup(struct nfs_page *req, struct nfs_read_data *data,
|
2006-03-21 02:44:27 +08:00
|
|
|
const struct rpc_call_ops *call_ops,
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned int count, unsigned int offset)
|
|
|
|
{
|
|
|
|
struct inode *inode;
|
2006-03-21 02:44:27 +08:00
|
|
|
int flags;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
data->req = req;
|
|
|
|
data->inode = inode = req->wb_context->dentry->d_inode;
|
|
|
|
data->cred = req->wb_context->cred;
|
|
|
|
|
|
|
|
data->args.fh = NFS_FH(inode);
|
|
|
|
data->args.offset = req_offset(req) + offset;
|
|
|
|
data->args.pgbase = req->wb_pgbase + offset;
|
|
|
|
data->args.pages = data->pagevec;
|
|
|
|
data->args.count = count;
|
|
|
|
data->args.context = req->wb_context;
|
|
|
|
|
|
|
|
data->res.fattr = &data->fattr;
|
|
|
|
data->res.count = count;
|
|
|
|
data->res.eof = 0;
|
2005-10-28 10:12:38 +08:00
|
|
|
nfs_fattr_init(&data->fattr);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 02:44:27 +08:00
|
|
|
/* Set up the initial task struct. */
|
|
|
|
flags = RPC_TASK_ASYNC | (IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0);
|
|
|
|
rpc_init_task(&data->task, NFS_CLIENT(inode), flags, call_ops, data);
|
2005-04-17 06:20:36 +08:00
|
|
|
NFS_PROTO(inode)->read_setup(data);
|
|
|
|
|
|
|
|
data->task.tk_cookie = (unsigned long)inode;
|
|
|
|
|
|
|
|
dprintk("NFS: %4d initiated read call (req %s/%Ld, %u bytes @ offset %Lu)\n",
|
|
|
|
data->task.tk_pid,
|
|
|
|
inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(inode),
|
|
|
|
count,
|
|
|
|
(unsigned long long)data->args.offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
nfs_async_read_error(struct list_head *head)
|
|
|
|
{
|
|
|
|
struct nfs_page *req;
|
|
|
|
|
|
|
|
while (!list_empty(head)) {
|
|
|
|
req = nfs_list_entry(head->next);
|
|
|
|
nfs_list_remove_request(req);
|
|
|
|
SetPageError(req->wb_page);
|
|
|
|
nfs_readpage_release(req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Start an async read operation
|
|
|
|
*/
|
|
|
|
static void nfs_execute_read(struct nfs_read_data *data)
|
|
|
|
{
|
|
|
|
struct rpc_clnt *clnt = NFS_CLIENT(data->inode);
|
|
|
|
sigset_t oldset;
|
|
|
|
|
|
|
|
rpc_clnt_sigmask(clnt, &oldset);
|
|
|
|
lock_kernel();
|
|
|
|
rpc_execute(&data->task);
|
|
|
|
unlock_kernel();
|
|
|
|
rpc_clnt_sigunmask(clnt, &oldset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate multiple requests to fill a single page.
|
|
|
|
*
|
|
|
|
* We optimize to reduce the number of read operations on the wire. If we
|
|
|
|
* detect that we're reading a page, or an area of a page, that is past the
|
|
|
|
* end of file, we do not generate NFS read operations but just clear the
|
|
|
|
* parts of the page that would have come back zero from the server anyway.
|
|
|
|
*
|
|
|
|
* We rely on the cached value of i_size to make this determination; another
|
|
|
|
* client can fill pages on the server past our cached end-of-file, but we
|
|
|
|
* won't see the new data until our attribute cache is updated. This is more
|
|
|
|
* or less conventional NFS client behavior.
|
|
|
|
*/
|
|
|
|
static int nfs_pagein_multi(struct list_head *head, struct inode *inode)
|
|
|
|
{
|
|
|
|
struct nfs_page *req = nfs_list_entry(head->next);
|
|
|
|
struct page *page = req->wb_page;
|
|
|
|
struct nfs_read_data *data;
|
2006-09-09 00:48:54 +08:00
|
|
|
size_t rsize = NFS_SERVER(inode)->rsize, nbytes;
|
|
|
|
unsigned int offset;
|
2005-04-17 06:20:36 +08:00
|
|
|
int requests = 0;
|
|
|
|
LIST_HEAD(list);
|
|
|
|
|
|
|
|
nfs_list_remove_request(req);
|
|
|
|
|
|
|
|
nbytes = req->wb_bytes;
|
2006-09-09 00:48:54 +08:00
|
|
|
do {
|
|
|
|
size_t len = min(nbytes,rsize);
|
|
|
|
|
|
|
|
data = nfs_readdata_alloc(len);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!data)
|
|
|
|
goto out_bad;
|
|
|
|
INIT_LIST_HEAD(&data->pages);
|
|
|
|
list_add(&data->pages, &list);
|
|
|
|
requests++;
|
2006-09-09 00:48:54 +08:00
|
|
|
nbytes -= len;
|
|
|
|
} while(nbytes != 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
atomic_set(&req->wb_complete, requests);
|
|
|
|
|
|
|
|
ClearPageError(page);
|
|
|
|
offset = 0;
|
|
|
|
nbytes = req->wb_bytes;
|
|
|
|
do {
|
|
|
|
data = list_entry(list.next, struct nfs_read_data, pages);
|
|
|
|
list_del_init(&data->pages);
|
|
|
|
|
|
|
|
data->pagevec[0] = page;
|
|
|
|
|
|
|
|
if (nbytes > rsize) {
|
2006-03-21 02:44:27 +08:00
|
|
|
nfs_read_rpcsetup(req, data, &nfs_read_partial_ops,
|
|
|
|
rsize, offset);
|
2005-04-17 06:20:36 +08:00
|
|
|
offset += rsize;
|
|
|
|
nbytes -= rsize;
|
|
|
|
} else {
|
2006-03-21 02:44:27 +08:00
|
|
|
nfs_read_rpcsetup(req, data, &nfs_read_partial_ops,
|
|
|
|
nbytes, offset);
|
2005-04-17 06:20:36 +08:00
|
|
|
nbytes = 0;
|
|
|
|
}
|
|
|
|
nfs_execute_read(data);
|
|
|
|
} while (nbytes != 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_bad:
|
|
|
|
while (!list_empty(&list)) {
|
|
|
|
data = list_entry(list.next, struct nfs_read_data, pages);
|
|
|
|
list_del(&data->pages);
|
|
|
|
nfs_readdata_free(data);
|
|
|
|
}
|
|
|
|
SetPageError(page);
|
|
|
|
nfs_readpage_release(req);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int nfs_pagein_one(struct list_head *head, struct inode *inode)
|
|
|
|
{
|
|
|
|
struct nfs_page *req;
|
|
|
|
struct page **pages;
|
|
|
|
struct nfs_read_data *data;
|
|
|
|
unsigned int count;
|
|
|
|
|
|
|
|
if (NFS_SERVER(inode)->rsize < PAGE_CACHE_SIZE)
|
|
|
|
return nfs_pagein_multi(head, inode);
|
|
|
|
|
2006-09-09 00:48:54 +08:00
|
|
|
data = nfs_readdata_alloc(NFS_SERVER(inode)->rsize);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!data)
|
|
|
|
goto out_bad;
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&data->pages);
|
|
|
|
pages = data->pagevec;
|
|
|
|
count = 0;
|
|
|
|
while (!list_empty(head)) {
|
|
|
|
req = nfs_list_entry(head->next);
|
|
|
|
nfs_list_remove_request(req);
|
|
|
|
nfs_list_add_request(req, &data->pages);
|
|
|
|
ClearPageError(req->wb_page);
|
|
|
|
*pages++ = req->wb_page;
|
|
|
|
count += req->wb_bytes;
|
|
|
|
}
|
|
|
|
req = nfs_list_entry(data->pages.next);
|
|
|
|
|
2006-03-21 02:44:27 +08:00
|
|
|
nfs_read_rpcsetup(req, data, &nfs_read_full_ops, count, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
nfs_execute_read(data);
|
|
|
|
return 0;
|
|
|
|
out_bad:
|
|
|
|
nfs_async_read_error(head);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
nfs_pagein_list(struct list_head *head, int rpages)
|
|
|
|
{
|
|
|
|
LIST_HEAD(one_request);
|
|
|
|
struct nfs_page *req;
|
|
|
|
int error = 0;
|
|
|
|
unsigned int pages = 0;
|
|
|
|
|
|
|
|
while (!list_empty(head)) {
|
|
|
|
pages += nfs_coalesce_requests(head, &one_request, rpages);
|
|
|
|
req = nfs_list_entry(one_request.next);
|
|
|
|
error = nfs_pagein_one(&one_request, req->wb_context->dentry->d_inode);
|
|
|
|
if (error < 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (error >= 0)
|
|
|
|
return pages;
|
|
|
|
|
|
|
|
nfs_async_read_error(head);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a read reply that fills part of a page.
|
|
|
|
*/
|
2006-03-21 02:44:27 +08:00
|
|
|
static void nfs_readpage_result_partial(struct rpc_task *task, void *calldata)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-03-21 02:44:27 +08:00
|
|
|
struct nfs_read_data *data = calldata;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct nfs_page *req = data->req;
|
|
|
|
struct page *page = req->wb_page;
|
|
|
|
|
2006-05-25 13:40:44 +08:00
|
|
|
if (likely(task->tk_status >= 0))
|
|
|
|
nfs_readpage_truncate_uninitialised_page(data);
|
|
|
|
else
|
|
|
|
SetPageError(page);
|
2006-03-21 02:44:27 +08:00
|
|
|
if (nfs_readpage_result(task, data) != 0)
|
|
|
|
return;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (atomic_dec_and_test(&req->wb_complete)) {
|
|
|
|
if (!PageError(page))
|
|
|
|
SetPageUptodate(page);
|
|
|
|
nfs_readpage_release(req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-21 02:44:27 +08:00
|
|
|
static const struct rpc_call_ops nfs_read_partial_ops = {
|
|
|
|
.rpc_call_done = nfs_readpage_result_partial,
|
|
|
|
.rpc_release = nfs_readdata_release,
|
|
|
|
};
|
|
|
|
|
2006-05-25 13:40:44 +08:00
|
|
|
static void nfs_readpage_set_pages_uptodate(struct nfs_read_data *data)
|
|
|
|
{
|
|
|
|
unsigned int count = data->res.count;
|
|
|
|
unsigned int base = data->args.pgbase;
|
|
|
|
struct page **pages;
|
|
|
|
|
2006-08-23 01:44:32 +08:00
|
|
|
if (data->res.eof)
|
|
|
|
count = data->args.count;
|
2006-05-25 13:40:44 +08:00
|
|
|
if (unlikely(count == 0))
|
|
|
|
return;
|
|
|
|
pages = &data->args.pages[base >> PAGE_CACHE_SHIFT];
|
|
|
|
base &= ~PAGE_CACHE_MASK;
|
|
|
|
count += base;
|
|
|
|
for (;count >= PAGE_CACHE_SIZE; count -= PAGE_CACHE_SIZE, pages++)
|
|
|
|
SetPageUptodate(*pages);
|
2006-08-23 01:44:32 +08:00
|
|
|
if (count != 0)
|
2006-05-25 13:40:44 +08:00
|
|
|
SetPageUptodate(*pages);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void nfs_readpage_set_pages_error(struct nfs_read_data *data)
|
|
|
|
{
|
|
|
|
unsigned int count = data->args.count;
|
|
|
|
unsigned int base = data->args.pgbase;
|
|
|
|
struct page **pages;
|
|
|
|
|
|
|
|
pages = &data->args.pages[base >> PAGE_CACHE_SHIFT];
|
|
|
|
base &= ~PAGE_CACHE_MASK;
|
|
|
|
count += base;
|
|
|
|
for (;count >= PAGE_CACHE_SIZE; count -= PAGE_CACHE_SIZE, pages++)
|
|
|
|
SetPageError(*pages);
|
2006-08-23 01:44:32 +08:00
|
|
|
if (count != 0)
|
|
|
|
SetPageError(*pages);
|
2006-05-25 13:40:44 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* This is the callback from RPC telling us whether a reply was
|
|
|
|
* received or some error occurred (timeout or socket shutdown).
|
|
|
|
*/
|
2006-03-21 02:44:27 +08:00
|
|
|
static void nfs_readpage_result_full(struct rpc_task *task, void *calldata)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-03-21 02:44:27 +08:00
|
|
|
struct nfs_read_data *data = calldata;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-05-25 13:40:44 +08:00
|
|
|
/*
|
|
|
|
* Note: nfs_readpage_result may change the values of
|
|
|
|
* data->args. In the multi-page case, we therefore need
|
|
|
|
* to ensure that we call the next nfs_readpage_set_page_uptodate()
|
|
|
|
* first in the multi-page case.
|
|
|
|
*/
|
|
|
|
if (likely(task->tk_status >= 0)) {
|
|
|
|
nfs_readpage_truncate_uninitialised_page(data);
|
|
|
|
nfs_readpage_set_pages_uptodate(data);
|
|
|
|
} else
|
|
|
|
nfs_readpage_set_pages_error(data);
|
2006-03-21 02:44:27 +08:00
|
|
|
if (nfs_readpage_result(task, data) != 0)
|
|
|
|
return;
|
2005-04-17 06:20:36 +08:00
|
|
|
while (!list_empty(&data->pages)) {
|
|
|
|
struct nfs_page *req = nfs_list_entry(data->pages.next);
|
|
|
|
|
2006-05-25 13:40:44 +08:00
|
|
|
nfs_list_remove_request(req);
|
2005-04-17 06:20:36 +08:00
|
|
|
nfs_readpage_release(req);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-03-21 02:44:27 +08:00
|
|
|
static const struct rpc_call_ops nfs_read_full_ops = {
|
|
|
|
.rpc_call_done = nfs_readpage_result_full,
|
|
|
|
.rpc_release = nfs_readdata_release,
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* This is the callback from RPC telling us whether a reply was
|
|
|
|
* received or some error occurred (timeout or socket shutdown).
|
|
|
|
*/
|
2006-03-21 02:44:27 +08:00
|
|
|
int nfs_readpage_result(struct rpc_task *task, struct nfs_read_data *data)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct nfs_readargs *argp = &data->args;
|
|
|
|
struct nfs_readres *resp = &data->res;
|
2006-03-21 02:44:27 +08:00
|
|
|
int status;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
dprintk("NFS: %4d nfs_readpage_result, (status %d)\n",
|
2006-03-21 02:44:27 +08:00
|
|
|
task->tk_pid, task->tk_status);
|
|
|
|
|
|
|
|
status = NFS_PROTO(data->inode)->read_done(task, data);
|
|
|
|
if (status != 0)
|
|
|
|
return status;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-03-21 02:44:14 +08:00
|
|
|
nfs_add_stats(data->inode, NFSIOS_SERVERREADBYTES, resp->count);
|
|
|
|
|
2006-09-15 02:03:14 +08:00
|
|
|
if (task->tk_status < 0) {
|
|
|
|
if (task->tk_status == -ESTALE) {
|
|
|
|
set_bit(NFS_INO_STALE, &NFS_FLAGS(data->inode));
|
|
|
|
nfs_mark_for_revalidate(data->inode);
|
|
|
|
}
|
|
|
|
} else if (resp->count < argp->count && !resp->eof) {
|
|
|
|
/* This is a short read! */
|
2006-03-21 02:44:14 +08:00
|
|
|
nfs_inc_stats(data->inode, NFSIOS_SHORTREAD);
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Has the server at least made some progress? */
|
|
|
|
if (resp->count != 0) {
|
|
|
|
/* Yes, so retry the read at the end of the data */
|
|
|
|
argp->offset += resp->count;
|
|
|
|
argp->pgbase += resp->count;
|
|
|
|
argp->count -= resp->count;
|
|
|
|
rpc_restart_call(task);
|
2006-03-21 02:44:27 +08:00
|
|
|
return -EAGAIN;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
task->tk_status = -EIO;
|
|
|
|
}
|
2005-08-19 02:24:12 +08:00
|
|
|
spin_lock(&data->inode->i_lock);
|
2005-08-19 02:24:09 +08:00
|
|
|
NFS_I(data->inode)->cache_validity |= NFS_INO_INVALID_ATIME;
|
2005-08-19 02:24:12 +08:00
|
|
|
spin_unlock(&data->inode->i_lock);
|
2006-03-21 02:44:27 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read a page over NFS.
|
|
|
|
* We read the page synchronously in the following case:
|
|
|
|
* - The error flag is set for this page. This happens only when a
|
|
|
|
* previous async read operation failed.
|
|
|
|
*/
|
|
|
|
int nfs_readpage(struct file *file, struct page *page)
|
|
|
|
{
|
|
|
|
struct nfs_open_context *ctx;
|
|
|
|
struct inode *inode = page->mapping->host;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
dprintk("NFS: nfs_readpage (%p %ld@%lu)\n",
|
|
|
|
page, PAGE_CACHE_SIZE, page->index);
|
2006-03-21 02:44:14 +08:00
|
|
|
nfs_inc_stats(inode, NFSIOS_VFSREADPAGE);
|
|
|
|
nfs_add_stats(inode, NFSIOS_READPAGES, 1);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Try to flush any pending writes to the file..
|
|
|
|
*
|
|
|
|
* NOTE! Because we own the page lock, there cannot
|
|
|
|
* be any new pending writes generated at this point
|
|
|
|
* for this page (other pages can be written to).
|
|
|
|
*/
|
|
|
|
error = nfs_wb_page(inode, page);
|
|
|
|
if (error)
|
|
|
|
goto out_error;
|
|
|
|
|
2006-09-15 02:03:14 +08:00
|
|
|
error = -ESTALE;
|
|
|
|
if (NFS_STALE(inode))
|
|
|
|
goto out_error;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (file == NULL) {
|
2005-11-05 04:33:38 +08:00
|
|
|
ctx = nfs_find_open_context(inode, NULL, FMODE_READ);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (ctx == NULL)
|
|
|
|
return -EBADF;
|
|
|
|
} else
|
|
|
|
ctx = get_nfs_open_context((struct nfs_open_context *)
|
|
|
|
file->private_data);
|
|
|
|
if (!IS_SYNC(inode)) {
|
|
|
|
error = nfs_readpage_async(ctx, inode, page);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
error = nfs_readpage_sync(ctx, inode, page);
|
|
|
|
if (error < 0 && IS_SWAPFILE(inode))
|
|
|
|
printk("Aiee.. nfs swap-in of page failed!\n");
|
|
|
|
out:
|
|
|
|
put_nfs_open_context(ctx);
|
|
|
|
return error;
|
|
|
|
|
|
|
|
out_error:
|
|
|
|
unlock_page(page);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct nfs_readdesc {
|
|
|
|
struct list_head *head;
|
|
|
|
struct nfs_open_context *ctx;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int
|
|
|
|
readpage_async_filler(void *data, struct page *page)
|
|
|
|
{
|
|
|
|
struct nfs_readdesc *desc = (struct nfs_readdesc *)data;
|
|
|
|
struct inode *inode = page->mapping->host;
|
|
|
|
struct nfs_page *new;
|
|
|
|
unsigned int len;
|
|
|
|
|
|
|
|
nfs_wb_page(inode, page);
|
|
|
|
len = nfs_page_length(inode, page);
|
|
|
|
if (len == 0)
|
|
|
|
return nfs_return_empty_page(page);
|
|
|
|
new = nfs_create_request(desc->ctx, inode, page, 0, len);
|
|
|
|
if (IS_ERR(new)) {
|
|
|
|
SetPageError(page);
|
|
|
|
unlock_page(page);
|
|
|
|
return PTR_ERR(new);
|
|
|
|
}
|
|
|
|
if (len < PAGE_CACHE_SIZE)
|
|
|
|
memclear_highpage_flush(page, len, PAGE_CACHE_SIZE - len);
|
|
|
|
nfs_list_add_request(new, desc->head);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int nfs_readpages(struct file *filp, struct address_space *mapping,
|
|
|
|
struct list_head *pages, unsigned nr_pages)
|
|
|
|
{
|
|
|
|
LIST_HEAD(head);
|
|
|
|
struct nfs_readdesc desc = {
|
|
|
|
.head = &head,
|
|
|
|
};
|
|
|
|
struct inode *inode = mapping->host;
|
|
|
|
struct nfs_server *server = NFS_SERVER(inode);
|
2006-09-15 02:03:14 +08:00
|
|
|
int ret = -ESTALE;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
dprintk("NFS: nfs_readpages (%s/%Ld %d)\n",
|
|
|
|
inode->i_sb->s_id,
|
|
|
|
(long long)NFS_FILEID(inode),
|
|
|
|
nr_pages);
|
2006-03-21 02:44:14 +08:00
|
|
|
nfs_inc_stats(inode, NFSIOS_VFSREADPAGES);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-09-15 02:03:14 +08:00
|
|
|
if (NFS_STALE(inode))
|
|
|
|
goto out;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (filp == NULL) {
|
2005-11-05 04:33:38 +08:00
|
|
|
desc.ctx = nfs_find_open_context(inode, NULL, FMODE_READ);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (desc.ctx == NULL)
|
|
|
|
return -EBADF;
|
|
|
|
} else
|
|
|
|
desc.ctx = get_nfs_open_context((struct nfs_open_context *)
|
|
|
|
filp->private_data);
|
|
|
|
ret = read_cache_pages(mapping, pages, readpage_async_filler, &desc);
|
|
|
|
if (!list_empty(&head)) {
|
|
|
|
int err = nfs_pagein_list(&head, server->rpages);
|
|
|
|
if (!ret)
|
2006-03-21 02:44:14 +08:00
|
|
|
nfs_add_stats(inode, NFSIOS_READPAGES, err);
|
2005-04-17 06:20:36 +08:00
|
|
|
ret = err;
|
|
|
|
}
|
|
|
|
put_nfs_open_context(desc.ctx);
|
2006-09-15 02:03:14 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
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 21:34:33 +08:00
|
|
|
int __init nfs_init_readpagecache(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
nfs_rdata_cachep = kmem_cache_create("nfs_read_data",
|
|
|
|
sizeof(struct nfs_read_data),
|
|
|
|
0, SLAB_HWCACHE_ALIGN,
|
|
|
|
NULL, NULL);
|
|
|
|
if (nfs_rdata_cachep == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2006-03-26 17:37:50 +08:00
|
|
|
nfs_rdata_mempool = mempool_create_slab_pool(MIN_POOL_READ,
|
|
|
|
nfs_rdata_cachep);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (nfs_rdata_mempool == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-06-28 03:59:15 +08:00
|
|
|
void nfs_destroy_readpagecache(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
mempool_destroy(nfs_rdata_mempool);
|
2006-09-27 16:49:40 +08:00
|
|
|
kmem_cache_destroy(nfs_rdata_cachep);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|