diff options
Diffstat (limited to 'fs/nfs/read.c')
-rw-r--r-- | fs/nfs/read.c | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/fs/nfs/read.c b/fs/nfs/read.c new file mode 100644 index 000000000..cf7c5ece7 --- /dev/null +++ b/fs/nfs/read.c @@ -0,0 +1,232 @@ +/* + * 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. + */ + +#define NFS_NEED_XDR_TYPES +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fcntl.h> +#include <linux/stat.h> +#include <linux/mm.h> +#include <linux/malloc.h> +#include <linux/pagemap.h> +#include <linux/sunrpc/clnt.h> +#include <linux/nfs_fs.h> + +#include <asm/segment.h> +#include <asm/system.h> + +#define NFSDBG_FACILITY NFSDBG_PAGECACHE + +struct nfs_rreq { + struct inode * ra_inode; /* inode from which to read */ + struct page * ra_page; /* page to be read */ + struct nfs_readargs ra_args; /* XDR argument struct */ + struct nfs_readres ra_res; /* ... and result struct */ + struct nfs_fattr ra_fattr; /* fattr storage */ +}; + +/* Hack for future NFS swap support */ +#ifndef IS_SWAPFILE +# define IS_SWAPFILE(inode) (0) +#endif + + +/* + * Set up the NFS read request struct + */ +static inline void +nfs_readreq_setup(struct nfs_rreq *req, struct nfs_fh *fh, + unsigned long offset, void *buffer, unsigned int rsize) +{ + req->ra_args.fh = fh; + req->ra_args.offset = offset; + req->ra_args.count = rsize; + req->ra_args.buffer = buffer; + req->ra_res.fattr = &req->ra_fattr; + req->ra_res.count = rsize; +} + + +/* + * Read a page synchronously. + */ +int +nfs_readpage_sync(struct inode *inode, struct page *page) +{ + struct nfs_rreq rqst; + unsigned long offset = page->offset; + char *buffer = (char *) page_address(page); + int rsize = NFS_SERVER(inode)->rsize; + int result, refresh = 0; + int count = PAGE_SIZE; + int flags = IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0; + + dprintk("NFS: nfs_readpage_sync(%p)\n", page); + clear_bit(PG_error, &page->flags); + + do { + if (count < rsize) + rsize = count; + + dprintk("NFS: nfs_proc_read(%s, (%x,%lx), %ld, %d, %p)\n", + NFS_SERVER(inode)->hostname, inode->i_dev, + inode->i_ino, offset, rsize, buffer); + + /* Set up arguments and perform rpc call */ + nfs_readreq_setup(&rqst, NFS_FH(inode), offset, buffer, rsize); + result = rpc_call(NFS_CLIENT(inode), NFSPROC_READ, + &rqst.ra_args, &rqst.ra_res, flags); + + /* + * 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; + } + refresh = 1; + count -= result; + offset += result; + buffer += result; + if (result < rsize) /* NFSv2ism */ + break; + } while (count); + + memset(buffer, 0, count); + set_bit(PG_uptodate, &page->flags); + result = 0; + +io_error: + if (refresh) + nfs_refresh_inode(inode, &rqst.ra_fattr); + clear_bit(PG_locked, &page->flags); + wake_up(&page->wait); + return result; +} + +/* + * This is the callback from RPC telling us whether a reply was + * received or some error occurred (timeout or socket shutdown). + */ +static void +nfs_readpage_result(struct rpc_task *task) +{ + struct nfs_rreq *req = (struct nfs_rreq *) task->tk_calldata; + struct page *page = req->ra_page; + int result = task->tk_status; + static int succ = 0, fail = 0; + + dprintk("NFS: %4d received callback for page %lx, result %d\n", + task->tk_pid, page_address(page), result); + + if (result >= 0) { + result = req->ra_res.count; + if (result < PAGE_SIZE) { + memset((char *) page_address(page) + result, 0, + PAGE_SIZE - result); + } + nfs_refresh_inode(req->ra_inode, &req->ra_fattr); + set_bit(PG_uptodate, &page->flags); + succ++; + } else { + set_bit(PG_error, &page->flags); + fail++; + dprintk("NFS: %d successful reads, %d failures\n", succ, fail); + } + iput(req->ra_inode); + clear_bit(PG_locked, &page->flags); + wake_up(&page->wait); + + free_page(page_address(page)); + + rpc_release_task(task); + kfree(req); +} + +static inline int +nfs_readpage_async(struct inode *inode, struct page *page) +{ + struct nfs_rreq *req; + int result, flags; + + dprintk("NFS: nfs_readpage_async(%p)\n", page); + flags = RPC_TASK_ASYNC | (IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0); + + if (NFS_CONGESTED(inode) + || !(req = (struct nfs_rreq *) rpc_allocate(flags, sizeof(*req)))) { + dprintk("NFS: deferring async READ request.\n"); + return -1; + } + + /* Initialize request */ + nfs_readreq_setup(req, NFS_FH(inode), page->offset, + (void *) page_address(page), PAGE_SIZE); + req->ra_inode = inode; + req->ra_page = page; + + /* Start the async call */ + dprintk("NFS: executing async READ request.\n"); + result = rpc_do_call(NFS_CLIENT(inode), NFSPROC_READ, + &req->ra_args, &req->ra_res, flags, + nfs_readpage_result, req); + + if (result >= 0) { + inode->i_count++; + atomic_inc(&page->count); + return 0; + } + + dprintk("NFS: failed to enqueue async READ request.\n"); + kfree(req); + return -1; +} + +/* + * Read a page over NFS. + * We read the page synchronously in the following cases: + * - The file is a swap file. Swap-ins are always sync operations, + * so there's no need bothering to make async reads 100% fail-safe. + * - The NFS rsize is smaller than PAGE_SIZE. We could kludge our way + * around this by creating several consecutive read requests, but + * that's hardly worth it. + * - The error flag is set for this page. This happens only when a + * previous async read operation failed. + * - The server is congested. + */ +int +nfs_readpage(struct inode *inode, struct page *page) +{ + unsigned long address; + int error = -1; + + dprintk("NFS: nfs_readpage %08lx\n", page_address(page)); + set_bit(PG_locked, &page->flags); + address = page_address(page); + atomic_inc(&page->count); + if (!IS_SWAPFILE(inode) && !PageError(page) + && NFS_SERVER(inode)->rsize >= PAGE_SIZE) + error = nfs_readpage_async(inode, page); + if (error < 0) /* couldn't enqueue */ + error = nfs_readpage_sync(inode, page); + if (error < 0 && IS_SWAPFILE(inode)) + printk("Aiee.. nfs swap-in of page failed!\n"); + free_page(address); + return error; +} |