diff options
Diffstat (limited to 'fs/nfs/write.c')
-rw-r--r-- | fs/nfs/write.c | 316 |
1 files changed, 151 insertions, 165 deletions
diff --git a/fs/nfs/write.c b/fs/nfs/write.c index 53c227e58..71bdcf645 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -46,12 +46,12 @@ * Copyright (C) 1996, 1997, Olaf Kirch <okir@monad.swb.de> */ -#define NFS_NEED_XDR_TYPES #include <linux/config.h> #include <linux/types.h> #include <linux/malloc.h> #include <linux/swap.h> #include <linux/pagemap.h> + #include <linux/sunrpc/clnt.h> #include <linux/nfs_fs.h> #include <asm/uaccess.h> @@ -66,51 +66,13 @@ */ #define IS_SOFT 0 +#define NFS_PARANOIA 1 #define NFSDBG_FACILITY NFSDBG_PAGECACHE static void nfs_wback_lock(struct rpc_task *task); static void nfs_wback_result(struct rpc_task *task); /* - * This struct describes a file region to be written. - * It's kind of a pity we have to keep all these lists ourselves, rather - * than sticking an extra pointer into struct page. - */ -struct nfs_wreq { - struct rpc_listitem wb_list; /* linked list of req's */ - struct rpc_task wb_task; /* RPC task */ - struct inode * wb_inode; /* inode referenced */ - struct page * wb_page; /* page to be written */ - unsigned int wb_offset; /* offset within page */ - unsigned int wb_bytes; /* dirty range */ - pid_t wb_pid; /* owner process */ - unsigned short wb_flags; /* status flags */ - - struct nfs_writeargs * wb_args; /* NFS RPC stuff */ - struct nfs_fattr * wb_fattr; /* file attributes */ -}; -#define wb_status wb_task.tk_status - -#define WB_NEXT(req) ((struct nfs_wreq *) ((req)->wb_list.next)) - -/* - * Various flags for wb_flags - */ -#define NFS_WRITE_WANTLOCK 0x0001 /* needs to lock page */ -#define NFS_WRITE_LOCKED 0x0002 /* holds lock on page */ -#define NFS_WRITE_CANCELLED 0x0004 /* has been cancelled */ -#define NFS_WRITE_UNCOMMITTED 0x0008 /* written but uncommitted (NFSv3) */ -#define NFS_WRITE_INVALIDATE 0x0010 /* invalidate after write */ -#define NFS_WRITE_INPROGRESS 0x0020 /* RPC call in progress */ - -#define WB_INPROGRESS(req) ((req)->wb_flags & NFS_WRITE_INPROGRESS) -#define WB_WANTLOCK(req) ((req)->wb_flags & NFS_WRITE_WANTLOCK) -#define WB_HAVELOCK(req) ((req)->wb_flags & NFS_WRITE_LOCKED) -#define WB_CANCELLED(req) ((req)->wb_flags & NFS_WRITE_CANCELLED) -#define WB_UNCOMMITTED(req) ((req)->wb_flags & NFS_WRITE_UNCOMMITTED) -#define WB_INVALIDATE(req) ((req)->wb_flags & NFS_WRITE_INVALIDATE) - -/* * Cache parameters */ #define NFS_WRITEBACK_DELAY (10 * HZ) @@ -143,8 +105,13 @@ nfs_unlock_page(struct page *page) /* async swap-out support */ if (test_and_clear_bit(PG_decr_after, &page->flags)) atomic_dec(&page->count); - if (test_and_clear_bit(PG_swap_unlock_after, &page->flags)) - swap_after_unlock_page(page->pg_swap_entry); + if (test_and_clear_bit(PG_swap_unlock_after, &page->flags)) { + /* + * We're doing a swap, so check that this page is + * swap-cached and do the necessary cleanup. + */ + swap_after_unlock_page(page->offset); + } #endif } @@ -160,7 +127,7 @@ transfer_page_lock(struct nfs_wreq *req) req->wb_flags |= NFS_WRITE_LOCKED; rpc_wake_up_task(&req->wb_task); - dprintk("nfs: wake up task %d (flags %x)\n", + dprintk("NFS: wake up task %d (flags %x)\n", req->wb_task.tk_pid, req->wb_flags); } @@ -169,17 +136,17 @@ transfer_page_lock(struct nfs_wreq *req) * Offset is the data offset within the page. */ static int -nfs_writepage_sync(struct inode *inode, struct page *page, - unsigned long offset, unsigned int count) +nfs_writepage_sync(struct dentry *dentry, struct inode *inode, + struct page *page, unsigned long offset, unsigned int count) { - struct nfs_fattr fattr; unsigned int wsize = NFS_SERVER(inode)->wsize; int result, refresh = 0, written = 0; u8 *buffer; + struct nfs_fattr fattr; - dprintk("NFS: nfs_writepage_sync(%x/%ld %d@%ld)\n", - inode->i_dev, inode->i_ino, - count, page->offset + offset); + dprintk("NFS: nfs_writepage_sync(%s/%s %d@%ld)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + count, page->offset + offset); buffer = (u8 *) page_address(page) + offset; offset += page->offset; @@ -188,7 +155,7 @@ nfs_writepage_sync(struct inode *inode, struct page *page, if (count < wsize && !IS_SWAPFILE(inode)) wsize = count; - result = nfs_proc_write(NFS_SERVER(inode), NFS_FH(inode), + result = nfs_proc_write(NFS_DSERVER(dentry), NFS_FH(dentry), IS_SWAPFILE(inode), offset, wsize, buffer, &fattr); @@ -214,8 +181,8 @@ nfs_writepage_sync(struct inode *inode, struct page *page, } while (count); io_error: - /* N.B. do we want to refresh if there was an error?? (fattr valid?) */ - if (refresh) { + /* Note: we don't refresh if the call failed (fattr invalid) */ + if (refresh && result >= 0) { /* See comments in nfs_wback_result */ /* N.B. I don't think this is right -- sync writes in order */ if (fattr.size < inode->i_size) @@ -281,6 +248,27 @@ find_write_request(struct inode *inode, struct page *page) } /* + * Find any requests for the specified dentry. + */ +int +nfs_find_dentry_request(struct inode *inode, struct dentry *dentry) +{ + struct nfs_wreq *head, *req; + int found = 0; + + req = head = NFS_WRITEBACK(inode); + while (req != NULL) { + if (req->wb_dentry == dentry) { + found = 1; + break; + } + if ((req = WB_NEXT(req)) == head) + break; + } + return found; +} + +/* * Find a failed write request by pid */ static struct nfs_wreq * @@ -380,16 +368,16 @@ update_write_request(struct nfs_wreq *req, unsigned int first, * Create and initialize a writeback request */ static inline struct nfs_wreq * -create_write_request(struct inode *inode, struct page *page, - unsigned int offset, unsigned int bytes) +create_write_request(struct dentry *dentry, struct inode *inode, + struct page *page, unsigned int offset, unsigned int bytes) { - struct nfs_wreq *wreq; struct rpc_clnt *clnt = NFS_CLIENT(inode); + struct nfs_wreq *wreq; struct rpc_task *task; - dprintk("NFS: create_write_request(%x/%ld, %ld+%d)\n", - inode->i_dev, inode->i_ino, - page->offset + offset, bytes); + dprintk("NFS: create_write_request(%s/%s, %ld+%d)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + page->offset + offset, bytes); /* FIXME: Enforce hard limit on number of concurrent writes? */ @@ -399,7 +387,7 @@ create_write_request(struct inode *inode, struct page *page, memset(wreq, 0, sizeof(*wreq)); task = &wreq->wb_task; - rpc_init_task(task, clnt, nfs_wback_result, 0); + rpc_init_task(task, clnt, nfs_wback_result, RPC_TASK_NFSWRITE); task->tk_calldata = wreq; task->tk_action = nfs_wback_lock; @@ -408,6 +396,7 @@ create_write_request(struct inode *inode, struct page *page, goto out_req; /* Put the task on inode's writeback request list. */ + wreq->wb_dentry = dentry; wreq->wb_inode = inode; wreq->wb_pid = current->pid; wreq->wb_page = page; @@ -434,7 +423,7 @@ out_fail: * Schedule a writeback RPC call. * If the server is congested, don't add to our backlog of queued * requests but call it synchronously. - * The function returns true if the page has been unlocked as the + * The function returns false if the page has been unlocked as the * consequence of a synchronous write call. * * FIXME: Here we could walk the inode's lock list to see whether the @@ -504,9 +493,10 @@ wait_on_write_request(struct nfs_wreq *req) * (for now), and we currently do this synchronously only. */ int -nfs_writepage(struct inode *inode, struct page *page) +nfs_writepage(struct file * file, struct page *page) { - return nfs_writepage_sync(inode, page, 0, PAGE_SIZE); + struct dentry *dentry = file->f_dentry; + return nfs_writepage_sync(dentry, dentry->d_inode, page, 0, PAGE_SIZE); } /* @@ -516,27 +506,20 @@ nfs_writepage(struct inode *inode, struct page *page) * things with a page scheduled for an RPC call (e.g. invalidate it). */ int -nfs_updatepage(struct inode *inode, struct page *page, const char *buffer, +nfs_updatepage(struct file *file, struct page *page, const char *buffer, unsigned long offset, unsigned int count, int sync) { + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + u8 *page_addr = (u8 *) page_address(page); struct nfs_wreq *req; int status = 0, page_locked = 1; - u8 *page_addr; - dprintk("NFS: nfs_updatepage(%x/%ld %d@%ld, sync=%d)\n", - inode->i_dev, inode->i_ino, - count, page->offset+offset, sync); + dprintk("NFS: nfs_updatepage(%s/%s %d@%ld, sync=%d)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + count, page->offset+offset, sync); set_bit(PG_locked, &page->flags); - page_addr = (u8 *) page_address(page); - - /* If wsize is smaller than page size, update and write - * page synchronously. - */ - if (NFS_SERVER(inode)->wsize < PAGE_SIZE) { - copy_from_user(page_addr + offset, buffer, count); - return nfs_writepage_sync(inode, page, offset, count); - } /* * Try to find a corresponding request on the writeback queue. @@ -550,6 +533,7 @@ nfs_updatepage(struct inode *inode, struct page *page, const char *buffer, */ if ((req = find_write_request(inode, page)) != NULL) { if (update_write_request(req, offset, count)) { + /* N.B. check for a fault here and cancel the req */ copy_from_user(page_addr + offset, buffer, count); goto updated; } @@ -558,16 +542,23 @@ nfs_updatepage(struct inode *inode, struct page *page, const char *buffer, return 0; } + /* Copy data to page buffer. */ + status = -EFAULT; + if (copy_from_user(page_addr + offset, buffer, count)) + goto done; + + /* If wsize is smaller than page size, update and write + * page synchronously. + */ + if (NFS_SERVER(inode)->wsize < PAGE_SIZE) + return nfs_writepage_sync(dentry, inode, page, offset, count); + /* Create the write request. */ status = -ENOBUFS; - req = create_write_request(inode, page, offset, count); + req = create_write_request(dentry, inode, page, offset, count); if (!req) goto done; - /* Copy data to page buffer. */ - /* N.B. should check for fault here ... */ - copy_from_user(page_addr + offset, buffer, count); - /* Schedule request */ page_locked = schedule_write_request(req, sync); @@ -597,8 +588,14 @@ done: if ((count = nfs_write_error(inode)) < 0) status = count; } - } else + } else { + if (status < 0) { +printk("NFS: %s/%s write failed, clearing bit\n", +dentry->d_parent->d_name.name, dentry->d_name.name); + clear_bit(PG_uptodate, &page->flags); + } nfs_unlock_page(page); + } } dprintk("NFS: nfs_updatepage returns %d (isize %ld)\n", @@ -609,14 +606,18 @@ done: /* * Flush out a dirty page. */ -static inline void +static void nfs_flush_request(struct nfs_wreq *req) { struct page *page = req->wb_page; - dprintk("NFS: nfs_flush_request(%x/%ld, @%ld)\n", - page->inode->i_dev, page->inode->i_ino, - page->offset); +#ifdef NFS_DEBUG_VERBOSE +if (req->wb_inode != page->inode) +printk("NFS: inode %ld no longer has page %p\n", req->wb_inode->i_ino, page); +#endif + dprintk("NFS: nfs_flush_request(%s/%s, @%ld)\n", + req->wb_dentry->d_parent->d_name.name, + req->wb_dentry->d_name.name, page->offset); req->wb_flags |= NFS_WRITE_WANTLOCK; if (!test_and_set_bit(PG_locked, &page->flags)) { @@ -642,30 +643,24 @@ nfs_flush_pages(struct inode *inode, pid_t pid, off_t offset, off_t len, req = head = NFS_WRITEBACK(inode); while (req != NULL) { - dprintk("NFS: %4d nfs_flush inspect %x/%ld @%ld fl %x\n", - req->wb_task.tk_pid, - req->wb_inode->i_dev, req->wb_inode->i_ino, - req->wb_page->offset, req->wb_flags); - if (!WB_INPROGRESS(req)) { - rqoffset = req->wb_page->offset + req->wb_offset; - rqend = rqoffset + req->wb_bytes; - - if (rqoffset < end && offset < rqend - && (pid == 0 || req->wb_pid == pid)) { - if (!WB_HAVELOCK(req)) { -#ifdef NFS_PARANOIA + dprintk("NFS: %4d nfs_flush inspect %s/%s @%ld fl %x\n", + req->wb_task.tk_pid, + req->wb_dentry->d_parent->d_name.name, + req->wb_dentry->d_name.name, + req->wb_page->offset, req->wb_flags); + + rqoffset = req->wb_page->offset + req->wb_offset; + rqend = rqoffset + req->wb_bytes; + if (rqoffset < end && offset < rqend && + (pid == 0 || req->wb_pid == pid)) { + if (!WB_INPROGRESS(req) && !WB_HAVELOCK(req)) { +#ifdef NFS_DEBUG_VERBOSE printk("nfs_flush: flushing inode=%ld, %d @ %lu\n", req->wb_inode->i_ino, req->wb_bytes, rqoffset); #endif - nfs_flush_request(req); - } - last = req; + nfs_flush_request(req); } - } else { -#ifdef NFS_PARANOIA -printk("nfs_flush_pages: in progress inode=%ld, %d @ %lu\n", -req->wb_inode->i_ino, req->wb_bytes, rqoffset); -#endif + last = req; } if (invalidate) req->wb_flags |= NFS_WRITE_INVALIDATE; @@ -677,11 +672,23 @@ req->wb_inode->i_ino, req->wb_bytes, rqoffset); } /* + * Cancel a write request. We always mark it cancelled, + * but if it's already in progress there's no point in + * calling rpc_exit, and we don't want to overwrite the + * tk_status field. + */ +static void +nfs_cancel_request(struct nfs_wreq *req) +{ + req->wb_flags |= NFS_WRITE_CANCELLED; + if (!WB_INPROGRESS(req)) { + rpc_exit(&req->wb_task, 0); + rpc_wake_up_task(&req->wb_task); + } +} + +/* * Cancel all writeback requests, both pending and in progress. - * - * N.B. This doesn't seem to wake up the tasks -- are we sure - * they will eventually complete? Also, this could overwrite a - * failed status code from an already-completed task. */ static void nfs_cancel_dirty(struct inode *inode, pid_t pid) @@ -690,11 +697,8 @@ nfs_cancel_dirty(struct inode *inode, pid_t pid) req = head = NFS_WRITEBACK(inode); while (req != NULL) { - /* N.B. check for task already finished? */ - if (pid == 0 || req->wb_pid == pid) { - req->wb_flags |= NFS_WRITE_CANCELLED; - rpc_exit(&req->wb_task, 0); - } + if (pid == 0 || req->wb_pid == pid) + nfs_cancel_request(req); if ((req = WB_NEXT(req)) == head) break; } @@ -715,8 +719,7 @@ nfs_flush_dirty_pages(struct inode *inode, pid_t pid, off_t offset, off_t len) int result = 0, cancel = 0; dprintk("NFS: flush_dirty_pages(%x/%ld for pid %d %ld/%ld)\n", - inode->i_dev, inode->i_ino, current->pid, - offset, len); + inode->i_dev, inode->i_ino, current->pid, offset, len); if (IS_SOFT && signalled()) { nfs_cancel_dirty(inode, pid); @@ -769,16 +772,15 @@ nfs_truncate_dirty_pages(struct inode *inode, unsigned long offset) struct nfs_wreq *req, *head; unsigned long rqoffset; - dprintk("NFS: truncate_dirty_pages(%x/%ld, %ld)\n", - inode->i_dev, inode->i_ino, offset); + dprintk("NFS: truncate_dirty_pages(%d/%ld, %ld)\n", + inode->i_dev, inode->i_ino, offset); req = head = NFS_WRITEBACK(inode); while (req != NULL) { rqoffset = req->wb_page->offset + req->wb_offset; if (rqoffset >= offset) { - req->wb_flags |= NFS_WRITE_CANCELLED; - rpc_exit(&req->wb_task, 0); + nfs_cancel_request(req); } else if (rqoffset + req->wb_bytes >= offset) { req->wb_bytes = offset - rqoffset; } @@ -823,47 +825,37 @@ nfs_wback_lock(struct rpc_task *task) { struct nfs_wreq *req = (struct nfs_wreq *) task->tk_calldata; struct page *page = req->wb_page; - struct inode *inode = req->wb_inode; + struct dentry *dentry = req->wb_dentry; - dprintk("NFS: %4d nfs_wback_lock (status %d flags %x)\n", - task->tk_pid, task->tk_status, req->wb_flags); + dprintk("NFS: %4d nfs_wback_lock (%s/%s, status=%d flags=%x)\n", + task->tk_pid, dentry->d_parent->d_name.name, + dentry->d_name.name, task->tk_status, req->wb_flags); if (!WB_HAVELOCK(req)) req->wb_flags |= NFS_WRITE_WANTLOCK; - if (WB_WANTLOCK(req) && test_and_set_bit(PG_locked, &page->flags)) { - dprintk("NFS: page already locked in writeback_lock!\n"); - task->tk_timeout = 2 * HZ; - rpc_sleep_on(&write_queue, task, NULL, NULL); - return; - } - task->tk_status = 0; + if (WB_WANTLOCK(req) && test_and_set_bit(PG_locked, &page->flags)) + goto out_locked; req->wb_flags &= ~NFS_WRITE_WANTLOCK; req->wb_flags |= NFS_WRITE_LOCKED; - - if (req->wb_args == 0) { - size_t size = sizeof(struct nfs_writeargs) - + sizeof(struct nfs_fattr); - void *ptr; - - if (!(ptr = kmalloc(size, GFP_KERNEL))) { - task->tk_timeout = HZ; - rpc_sleep_on(&write_queue, task, NULL, NULL); - return; - } - req->wb_args = (struct nfs_writeargs *) ptr; - req->wb_fattr = (struct nfs_fattr *) (req->wb_args + 1); - } + task->tk_status = 0; /* Setup the task struct for a writeback call */ - req->wb_args->fh = NFS_FH(inode); - req->wb_args->offset = page->offset + req->wb_offset; - req->wb_args->count = req->wb_bytes; - req->wb_args->buffer = (void *) (page_address(page) + req->wb_offset); + req->wb_args.fh = NFS_FH(dentry); + req->wb_args.offset = page->offset + req->wb_offset; + req->wb_args.count = req->wb_bytes; + req->wb_args.buffer = (void *) (page_address(page) + req->wb_offset); - rpc_call_setup(task, NFSPROC_WRITE, req->wb_args, req->wb_fattr, 0); + rpc_call_setup(task, NFSPROC_WRITE, &req->wb_args, &req->wb_fattr, 0); req->wb_flags |= NFS_WRITE_INPROGRESS; + return; + +out_locked: + printk("NFS: page already locked in writeback_lock!\n"); + task->tk_timeout = 2 * HZ; + rpc_sleep_on(&write_queue, task, NULL, NULL); + return; } /* @@ -873,17 +865,16 @@ static void nfs_wback_result(struct rpc_task *task) { struct nfs_wreq *req = (struct nfs_wreq *) task->tk_calldata; - struct inode *inode; - struct page *page; - int status; - - dprintk("NFS: %4d nfs_wback_result (status %d)\n", - task->tk_pid, task->tk_status); + struct inode *inode = req->wb_inode; + struct page *page = req->wb_page; + int status = task->tk_status; - inode = req->wb_inode; - page = req->wb_page; - status = task->tk_status; + dprintk("NFS: %4d nfs_wback_result (%s/%s, status=%d, flags=%x)\n", + task->tk_pid, req->wb_dentry->d_parent->d_name.name, + req->wb_dentry->d_name.name, status, req->wb_flags); + /* Set the WRITE_COMPLETE flag, but leave WRITE_INPROGRESS set */ + req->wb_flags |= NFS_WRITE_COMPLETE; if (status < 0) { /* * An error occurred. Report the error back to the @@ -894,7 +885,7 @@ nfs_wback_result(struct rpc_task *task) status = 0; clear_bit(PG_uptodate, &page->flags); } else if (!WB_CANCELLED(req)) { - struct nfs_fattr *fattr = req->wb_fattr; + struct nfs_fattr *fattr = &req->wb_fattr; /* Update attributes as result of writeback. * Beware: when UDP replies arrive out of order, we * may end up overwriting a previous, bigger file size. @@ -930,11 +921,6 @@ nfs_wback_result(struct rpc_task *task) if (WB_HAVELOCK(req)) nfs_unlock_page(page); - if (req->wb_args) { - kfree(req->wb_args); - req->wb_args = 0; - } - /* * Now it's safe to remove the request from the inode's * writeback list and wake up any tasks sleeping on it. |