diff options
Diffstat (limited to 'fs/nfs/write.c')
-rw-r--r-- | fs/nfs/write.c | 823 |
1 files changed, 823 insertions, 0 deletions
diff --git a/fs/nfs/write.c b/fs/nfs/write.c new file mode 100644 index 000000000..f48df4571 --- /dev/null +++ b/fs/nfs/write.c @@ -0,0 +1,823 @@ +/* + * linux/fs/nfs/write.c + * + * Writing file data over NFS. + * + * We do it like this: When a (user) process wishes to write data to an + * NFS file, a write request is allocated that contains the RPC task data + * plus some info on the page to be written, and added to the inode's + * write chain. If the process writes past the end of the page, an async + * RPC call to write the page is scheduled immediately; otherwise, the call + * is delayed for a few seconds. + * + * Just like readahead, no async I/O is performed if wsize < PAGE_SIZE. + * + * Write requests are kept on the inode's writeback list. Each entry in + * that list references the page (portion) to be written. When the + * cache timeout has expired, the RPC task is woken up, and tries to + * lock the page. As soon as it manages to do so, the request is moved + * from the writeback list to the writelock list. + * + * Note: we must make sure never to confuse the inode passed in the + * write_page request with the one in page->inode. As far as I understant + * it, these are different when doing a swap-out. + * + * To understand everything that goes one here and in the nfs read code, + * one should be aware that a page is locked in exactly one of the following + * cases: + * + * - A write request is in progress. + * - A user process is in generic_file_write/nfs_update_page + * - A user process is in generic_file_read + * + * Also note that because of the way pages are invalidated in + * nfs_revalidate_inode, the following assertions hold: + * + * - If a page is dirty, there will be no read requests (a page will + * not be re-read unless invalidated by nfs_revalidate_inode). + * - If the page is not uptodate, there will be no pending write + * requests, and no process will be in nfs_update_page. + * + * FIXME: Interaction with the vmscan routines is not optimal yet. + * Either vmscan must be made nfs-savvy, or we need a different page + * reclaim concept that supports something like FS-independent + * buffer_heads with a b_ops-> field. + * + * 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> + +#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) +#define NFS_WRITEBACK_MAX 64 + +/* + * Limit number of delayed writes + */ +static int nr_write_requests = 0; +static struct rpc_wait_queue write_queue = RPC_INIT_WAITQ("write_chain"); +struct nfs_wreq * nfs_failed_requests = NULL; + +/* Hack for future NFS swap support */ +#ifndef IS_SWAPFILE +# define IS_SWAPFILE(inode) (0) +#endif + +/* + * Unlock a page after writing it + */ +static inline void +nfs_unlock_page(struct page *page) +{ + dprintk("NFS: unlock %ld\n", page->offset); + clear_bit(PG_locked, &page->flags); + wake_up(&page->wait); + +#ifdef CONFIG_NFS_SWAP + /* async swap-out support */ + if (clear_bit(PG_decr_after, &page->flags)) + atomic_dec(&page->count); + if (clear_bit(PG_swap_unlock_after, &page->flags)) + swap_after_unlock_page(page->swap_unlock_entry); +#endif +} + +/* + * Transfer a page lock to a write request waiting for it. + */ +static inline void +transfer_page_lock(struct nfs_wreq *req) +{ + dprintk("NFS: transfer_page_lock\n"); + + req->wb_flags &= ~NFS_WRITE_WANTLOCK; + req->wb_flags |= NFS_WRITE_LOCKED; + rpc_wake_up_task(&req->wb_task); + + dprintk("nfs: wake up task %d (flags %x)\n", + req->wb_task.tk_pid, req->wb_flags); +} + +/* + * Write a page synchronously. + * 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) +{ + struct nfs_fattr fattr; + unsigned int wsize = NFS_SERVER(inode)->wsize; + int result, refresh = 0, written = 0; + u8 *buffer; + + dprintk("NFS: nfs_writepage_sync(%x/%ld %d@%ld)\n", + inode->i_dev, inode->i_ino, + count, page->offset + offset); + + buffer = (u8 *) page_address(page) + offset; + offset += page->offset; + + do { + if (count < wsize && !IS_SWAPFILE(inode)) + wsize = count; + + result = nfs_proc_write(NFS_SERVER(inode), NFS_FH(inode), + offset, wsize, IS_SWAPFILE(inode), + buffer, &fattr); + + if (result < 0) { + /* Must mark the page invalid after I/O error */ + clear_bit(PG_uptodate, &page->flags); + goto io_error; + } + refresh = 1; + buffer += wsize; + offset += wsize; + written += wsize; + count -= wsize; + } while (count); + +io_error: + if (refresh) { + /* See comments in nfs_wback_result */ + if (fattr.size < inode->i_size) + fattr.size = inode->i_size; + /* Solaris 2.5 server seems to send garbled + * fattrs occasionally */ + if (inode->i_ino == fattr.fileid) + nfs_refresh_inode(inode, &fattr); + } + + nfs_unlock_page(page); + return written? written : result; +} + +/* + * Append a writeback request to a list + */ +static inline void +append_write_request(struct nfs_wreq **q, struct nfs_wreq *wreq) +{ + dprintk("NFS: append_write_request(%p, %p)\n", q, wreq); + rpc_append_list(q, wreq); +} + +/* + * Remove a writeback request from a list + */ +static inline void +remove_write_request(struct nfs_wreq **q, struct nfs_wreq *wreq) +{ + dprintk("NFS: remove_write_request(%p, %p)\n", q, wreq); + rpc_remove_list(q, wreq); +} + +/* + * Find a write request for a given page + */ +static inline struct nfs_wreq * +find_write_request(struct inode *inode, struct page *page) +{ + struct nfs_wreq *head, *req; + + dprintk("NFS: find_write_request(%x/%ld, %p)\n", + inode->i_dev, inode->i_ino, page); + if (!(req = head = NFS_WRITEBACK(inode))) + return NULL; + do { + if (req->wb_page == page) + return req; + } while ((req = WB_NEXT(req)) != head); + return NULL; +} + +/* + * Find a failed write request by pid + */ +static inline struct nfs_wreq * +find_failed_request(struct inode *inode, pid_t pid) +{ + struct nfs_wreq *head, *req; + + if (!(req = head = nfs_failed_requests)) + return NULL; + do { + if (req->wb_inode == inode && req->wb_pid == pid) + return req; + } while ((req = WB_NEXT(req)) != head); + return NULL; +} + +/* + * Try to merge adjacent write requests. This works only for requests + * issued by the same user. + */ +static inline int +update_write_request(struct nfs_wreq *req, unsigned first, unsigned bytes) +{ + unsigned rqfirst = req->wb_offset, + rqlast = rqfirst + req->wb_bytes, + last = first + bytes; + + dprintk("nfs: trying to update write request %p\n", req); + + /* Check the credentials associated with this write request. + * If the buffer is owned by the same user, we can happily + * add our data without risking server permission problems. + * Note that I'm not messing around with RPC root override creds + * here, because they're used by swap requests only which + * always write out full pages. */ + if (!rpcauth_matchcred(&req->wb_task, req->wb_task.tk_cred)) { + dprintk("NFS: update failed (cred mismatch)\n"); + return 0; + } + + if (first < rqfirst) + rqfirst = first; + if (rqlast < last) + rqlast = last; + req->wb_offset = rqfirst; + req->wb_bytes = rqlast - rqfirst; + + return 1; +} + +/* + * Create and initialize a writeback request + */ +static inline struct nfs_wreq * +create_write_request(struct inode *inode, struct page *page, + unsigned offset, unsigned bytes) +{ + struct nfs_wreq *wreq; + struct rpc_clnt *clnt = NFS_CLIENT(inode); + struct rpc_task *task; + + dprintk("NFS: create_write_request(%x/%ld, %ld+%d)\n", + inode->i_dev, inode->i_ino, + page->offset + offset, bytes); + + /* FIXME: Enforce hard limit on number of concurrent writes? */ + + wreq = (struct nfs_wreq *) kmalloc(sizeof(*wreq), GFP_USER); + if (!wreq) + return NULL; + memset(wreq, 0, sizeof(*wreq)); + + task = &wreq->wb_task; + rpc_init_task(task, clnt, nfs_wback_result, 0); + task->tk_calldata = wreq; + task->tk_action = nfs_wback_lock; + + rpcauth_lookupcred(task); /* Obtain user creds */ + if (task->tk_status < 0) { + rpc_release_task(task); + kfree(wreq); + return NULL; + } + + /* Put the task on inode's writeback request list. */ + wreq->wb_inode = inode; + wreq->wb_pid = current->pid; + wreq->wb_page = page; + wreq->wb_offset = offset; + wreq->wb_bytes = bytes; + inode->i_count++; + atomic_inc(&page->count); + + append_write_request(&NFS_WRITEBACK(inode), wreq); + + if (nr_write_requests++ > NFS_WRITEBACK_MAX*3/4) + rpc_wake_up_next(&write_queue); + + return wreq; +} + +/* + * 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 + * consequence of a synchronous write call. + * + * FIXME: Here we could walk the inode's lock list to see whether the + * page we're currently writing to has been write-locked by the caller. + * If it is, we could schedule an async write request with a long + * delay in order to avoid writing back the page until the lock is + * released. + */ +static inline int +schedule_write_request(struct nfs_wreq *req, int sync) +{ + struct rpc_task *task = &req->wb_task; + struct inode *inode = req->wb_inode; + + if (NFS_CONGESTED(inode) || nr_write_requests >= NFS_WRITEBACK_MAX) + sync = 1; + + if (sync) { + dprintk("NFS: %4d schedule_write_request (sync)\n", + task->tk_pid); + /* Page is already locked */ + req->wb_flags |= NFS_WRITE_LOCKED; + rpc_execute(task); + } else { + dprintk("NFS: %4d schedule_write_request (async)\n", + task->tk_pid); + task->tk_flags |= RPC_TASK_ASYNC; + task->tk_timeout = NFS_WRITEBACK_DELAY; + rpc_sleep_on(&write_queue, task, NULL, NULL); + } + + return sync == 0; +} + +/* + * Wait for request to complete + * This is almost a copy of __wait_on_page + */ +static inline int +wait_on_write_request(struct nfs_wreq *req) +{ + struct wait_queue wait = { current, NULL }; + struct page *page = req->wb_page; + + add_wait_queue(&page->wait, &wait); + atomic_inc(&page->count); +repeat: + current->state = TASK_INTERRUPTIBLE; + if (PageLocked(page)) { + schedule(); + goto repeat; + } + remove_wait_queue(&page->wait, &wait); + current->state = TASK_RUNNING; + atomic_dec(&page->count); + return signalled()? -ERESTARTSYS : 0; +} + +/* + * Write a page to the server. This will be used for NFS swapping only + * (for now), and we currently do this synchronously only. + */ +int +nfs_writepage(struct inode *inode, struct page *page) +{ + return nfs_writepage_sync(inode, page, 0, PAGE_SIZE); +} + +/* + * Update and possibly write a cached page of an NFS file. + * The page is already locked when we get here. + * + * XXX: Keep an eye on generic_file_read to make sure it doesn't do bad + * 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, + unsigned long offset, unsigned int count, int sync) +{ + 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); + + 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. + * If there is one, we can be sure that this request is not + * yet being processed, because we hold a lock on the page. + * + * If the request was created by us, update it. Otherwise, + * transfer the page lock and flush out the dirty page now. + * After returning, generic_file_write will wait on the + * page and retry the update. + */ + if ((req = find_write_request(inode, page)) != NULL) { + if (update_write_request(req, offset, count)) { + copy_from_user(page_addr + offset, buffer, count); + goto updated; + } + dprintk("NFS: wake up conflicting write request.\n"); + transfer_page_lock(req); + return 0; + } + + /* Create the write request. */ + if (!(req = create_write_request(inode, page, offset, count))) { + status = -ENOBUFS; + goto done; + } + + /* Copy data to page buffer. */ + copy_from_user(page_addr + offset, buffer, count); + + /* Schedule request */ + page_locked = schedule_write_request(req, sync); + +updated: + /* + * If we wrote up to the end of the chunk, transmit request now. + * We should be a bit more intelligent about detecting whether a + * process accesses the file sequentially or not. + */ + if (page_locked && (offset + count >= PAGE_SIZE || sync)) + req->wb_flags |= NFS_WRITE_WANTLOCK; + + /* If the page was written synchronously, return any error that + * may have happened; otherwise return the write count. */ + if (page_locked || (status = nfs_write_error(inode)) >= 0) + status = count; + +done: + /* Unlock page and wake up anyone sleeping on it */ + if (page_locked) { + if (req && WB_WANTLOCK(req)) { + transfer_page_lock(req); + /* rpc_execute(&req->wb_task); */ + if (sync) { + wait_on_write_request(req); + if ((count = nfs_write_error(inode)) < 0) + status = count; + } + } else + nfs_unlock_page(page); + } + + dprintk("NFS: nfs_updatepage returns %d (isize %ld)\n", + status, inode->i_size); + return status; +} + +/* + * Flush out a dirty page. + */ +static inline 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); + + req->wb_flags |= NFS_WRITE_WANTLOCK; + if (!set_bit(PG_locked, &page->flags)) { + transfer_page_lock(req); + } else { + printk(KERN_WARNING "NFS oops in %s: can't lock page!\n", + __FUNCTION__); + rpc_wake_up_task(&req->wb_task); + } +} + +/* + * Flush writeback requests. See nfs_flush_dirty_pages for details. + */ +static struct nfs_wreq * +nfs_flush_pages(struct inode *inode, pid_t pid, off_t offset, off_t len, + int invalidate) +{ + struct nfs_wreq *head, *req, *last = NULL; + off_t rqoffset, rqend, end; + + end = len? offset + len : 0x7fffffffUL; + + 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)) + nfs_flush_request(req); + last = req; + } + } + if (invalidate) + req->wb_flags |= NFS_WRITE_INVALIDATE; + if ((req = WB_NEXT(req)) == head) + break; + } + + return last; +} + +/* + * Cancel all writeback requests, both pending and in process. + */ +static void +nfs_cancel_dirty(struct inode *inode, pid_t pid) +{ + struct nfs_wreq *head, *req; + + req = head = NFS_WRITEBACK(inode); + while (req != NULL) { + if (req->wb_pid == pid) { + req->wb_flags |= NFS_WRITE_CANCELLED; + rpc_exit(&req->wb_task, 0); + } + if ((req = WB_NEXT(req)) == head) + break; + } +} + +/* + * Flush out all dirty pages belonging to a certain user process and + * maybe wait for the RPC calls to complete. + * + * Another purpose of this function is sync()ing a file range before a + * write lock is released. This is what offset and length are for, even if + * this isn't used by the nlm module yet. + */ +int +nfs_flush_dirty_pages(struct inode *inode, off_t offset, off_t len) +{ + struct nfs_wreq *last = NULL; + + dprintk("NFS: flush_dirty_pages(%x/%ld for pid %d %ld/%ld)\n", + inode->i_dev, inode->i_ino, current->pid, + offset, len); + + if (signalled()) + nfs_cancel_dirty(inode, current->pid); + + while (!signalled()) { + /* Flush all pending writes for this pid and file region */ + last = nfs_flush_pages(inode, current->pid, offset, len, 0); + if (last == NULL) + break; + wait_on_write_request(last); + } + + return signalled()? -ERESTARTSYS : 0; +} + +/* + * Flush out any pending write requests and flag that they be discarded + * after the write is complete. + * + * This function is called from nfs_revalidate_inode just before it calls + * invalidate_inode_pages. After nfs_flush_pages returns, we can be sure + * that all dirty pages are locked, so that invalidate_inode_pages does + * not throw away any dirty pages. + */ +void +nfs_invalidate_pages(struct inode *inode) +{ + dprintk("NFS: nfs_invalidate_pages(%x/%ld)\n", + inode->i_dev, inode->i_ino); + + nfs_flush_pages(inode, 0, 0, 0, 1); +} + +/* + * Cancel any pending write requests after a given offset + * (called from nfs_notify_change). + */ +int +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); + + 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); + } else if (rqoffset + req->wb_bytes >= offset) { + req->wb_bytes = offset - rqoffset; + } + if ((req = WB_NEXT(req)) == head) + break; + } + + return 0; +} + +/* + * Check if a previous write operation returned an error + */ +int +nfs_check_error(struct inode *inode) +{ + struct nfs_wreq *req; + int status = 0; + + dprintk("nfs: checking for write error inode %04x/%ld\n", + inode->i_dev, inode->i_ino); + + if (!(req = find_failed_request(inode, current->pid))) + return 0; + + dprintk("nfs: write error %d inode %04x/%ld\n", + req->wb_task.tk_status, inode->i_dev, inode->i_ino); + + status = req->wb_task.tk_status; + remove_write_request(&nfs_failed_requests, req); + iput(req->wb_inode); + kfree(req); + return status; +} + +/* + * The following procedures make up the writeback finite state machinery: + * + * 1. Try to lock the page if not yet locked by us, + * set up the RPC call info, and pass to the call FSM. + */ +static void +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; + + dprintk("NFS: %4d nfs_wback_lock (status %d flags %x)\n", + task->tk_pid, task->tk_status, req->wb_flags); + + if (!WB_HAVELOCK(req)) + req->wb_flags |= NFS_WRITE_WANTLOCK; + + if (WB_WANTLOCK(req) && 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; + 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); + } + + /* 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); + + rpc_call_setup(task, NFSPROC_WRITE, req->wb_args, req->wb_fattr, 0); + + req->wb_flags |= NFS_WRITE_INPROGRESS; +} + +/* + * 2. Collect the result + */ +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); + + inode = req->wb_inode; + page = req->wb_page; + status = task->tk_status; + + /* Remove request from writeback list and wake up tasks + * sleeping on it. */ + remove_write_request(&NFS_WRITEBACK(inode), req); + + if (status < 0) { + /* + * An error occurred. Report the error back to the + * application by adding the failed request to the + * inode's error list. + */ + if (find_failed_request(inode, req->wb_pid)) { + status = 0; + } else { + dprintk("NFS: %4d saving write failure code\n", + task->tk_pid); + append_write_request(&nfs_failed_requests, req); + inode->i_count++; + } + clear_bit(PG_uptodate, &page->flags); + } else if (!WB_CANCELLED(req)) { + /* Update attributes as result of writeback. + * Beware: when UDP replies arrive out of order, we + * may end up overwriting a previous, bigger file size. + */ + if (req->wb_fattr->size < inode->i_size) + req->wb_fattr->size = inode->i_size; + /* possible Solaris 2.5 server bug workaround */ + if (inode->i_ino == req->wb_fattr->fileid) + nfs_refresh_inode(inode, req->wb_fattr); + } + + rpc_release_task(task); + + if (WB_INVALIDATE(req)) + clear_bit(PG_uptodate, &page->flags); + if (WB_HAVELOCK(req)) + nfs_unlock_page(page); + + if (req->wb_args) { + kfree(req->wb_args); + req->wb_args = 0; + } + if (status >= 0) + kfree(req); + + free_page(page_address(page)); + iput(inode); + nr_write_requests--; +} |