diff options
Diffstat (limited to 'fs/nfs')
-rw-r--r-- | fs/nfs/dir.c | 223 | ||||
-rw-r--r-- | fs/nfs/file.c | 26 | ||||
-rw-r--r-- | fs/nfs/inode.c | 15 | ||||
-rw-r--r-- | fs/nfs/mount_clnt.c | 1 | ||||
-rw-r--r-- | fs/nfs/write.c | 61 |
5 files changed, 183 insertions, 143 deletions
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 66b33161f..7cc5ae160 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -29,11 +29,6 @@ #include <asm/segment.h> /* for fs functions */ -#define NFS_MAX_AGE 10*HZ /* max age for dentry validation */ - -/* needed by smbfs as well ... move to dcache? */ -extern void nfs_renew_times(struct dentry *); - #define NFS_PARANOIA 1 /* #define NFS_DEBUG_VERBOSE 1 */ @@ -79,6 +74,7 @@ static struct file_operations nfs_dir_operations = { NULL, /* ioctl - default */ NULL, /* mmap */ nfs_dir_open, /* open - revalidate */ + NULL, /* flush */ NULL, /* no special release code */ NULL /* fsync */ }; @@ -375,6 +371,7 @@ nfs_free_dircache(void) nfs_invalidate_dircache_sb(NULL); } +#define NFS_REVALIDATE_INTERVAL (5*HZ) /* * This is called every time the dcache has a lookup hit, * and we should check whether we can really trust that @@ -383,47 +380,80 @@ nfs_free_dircache(void) * NOTE! The hit can be a negative hit too, don't assume * we have an inode! * - * The decision to drop the dentry should probably be - * smarter than this. Right now we believe in directories - * for 10 seconds, and in normal files for five.. + * If the dentry is older than the revalidation interval, + * we do a new lookup and verify that the dentry is still + * correct. */ static int nfs_lookup_revalidate(struct dentry * dentry) { + struct dentry * parent = dentry->d_parent; + struct inode * inode = dentry->d_inode; unsigned long time = jiffies - dentry->d_time; - unsigned long max = 5*HZ; + int error; + struct nfs_fh fhandle; + struct nfs_fattr fattr; - if (dentry->d_inode) { - if (is_bad_inode(dentry->d_inode)) { + if (inode && is_bad_inode(inode)) { #ifdef NFS_PARANOIA printk("nfs_lookup_validate: %s/%s has dud inode\n", -dentry->d_parent->d_name.name, dentry->d_name.name); +parent->d_name.name, dentry->d_name.name); #endif - goto bad; - } - if (S_ISDIR(dentry->d_inode->i_mode)) - max = NFS_MAX_AGE; + goto out_bad; + } + + if (time < NFS_REVALIDATE_INTERVAL) + goto out_valid; + /* + * Don't bother looking up a negative dentry ... + */ + if (!inode) + goto out_bad; + + if (IS_ROOT(dentry)) + goto out_valid; + /* + * Do a new lookup and check the dentry attributes. + */ + error = nfs_proc_lookup(NFS_DSERVER(parent), NFS_FH(parent), + dentry->d_name.name, &fhandle, &fattr); + if (error) { +printk("nfs_lookup_revalidate: error=%d\n", error); + goto out_bad; } - return (time < max) || IS_ROOT(dentry); -bad: + /* Inode number matches? */ + if (fattr.fileid != inode->i_ino) { +printk("nfs_lookup_revalidate: %s/%s inode mismatch, old=%ld, new=%u\n", +parent->d_name.name, dentry->d_name.name, inode->i_ino, fattr.fileid); + goto out_bad; + } + /* Filehandle matches? */ + if (memcmp(dentry->d_fsdata, &fhandle, sizeof(struct nfs_fh))) { +printk("nfs_lookup_revalidate: %s/%s fh changed\n", +parent->d_name.name, dentry->d_name.name); + goto out_bad; + } + +out_valid: + return 1; +out_bad: return 0; } /* * This is called from dput() when d_count is going to 0. - * We use it to clean up silly-renamed files, and to check - * for dentries that have already expired. + * We use it to clean up silly-renamed files. */ static void nfs_dentry_delete(struct dentry *dentry) { + dfprintk(VFS, "NFS: dentry_delete(%s/%s, %x)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + dentry->d_flags); + if (dentry->d_flags & DCACHE_NFSFS_RENAMED) { int error; dentry->d_flags &= ~DCACHE_NFSFS_RENAMED; -#ifdef NFS_DEBUG_VERBOSE -printk("nfs_dentry_delete: unlinking %s/%s\n", -dentry->d_parent->d_name.name, dentry->d_name.name); -#endif /* Unhash it first */ d_drop(dentry); error = nfs_safe_remove(dentry); @@ -432,14 +462,6 @@ dentry->d_parent->d_name.name, dentry->d_name.name); dentry->d_parent->d_name.name, dentry->d_name.name, error); } - /* - * Check whether to expire the dentry ... - */ - else { - unsigned long age = jiffies - dentry->d_time; - if (age > NFS_MAX_AGE) - d_drop(dentry); - } #ifdef NFS_PARANOIA /* @@ -464,6 +486,10 @@ inode->i_ino, inode->i_count, inode->i_nlink); */ static void nfs_dentry_iput(struct dentry *dentry, struct inode *inode) { + dfprintk(VFS, "NFS: dentry_iput(%s/%s, cnt=%d, ino=%ld)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + dentry->d_count, inode->i_ino); + if (NFS_WRITEBACK(inode)) { #ifdef NFS_PARANOIA printk("nfs_dentry_iput: pending writes for %s/%s, i_count=%d\n", @@ -522,17 +548,12 @@ static void show_dentry(struct list_head * dlist) #endif /* - * Whenever a lookup succeeds, we know the parent directories - * are all valid, so we want to update the dentry timestamps. + * Whenever an NFS operation succeeds, we know that the dentry + * is valid, so we update the revalidation timestamp. */ -void nfs_renew_times(struct dentry * dentry) +static inline void nfs_renew_times(struct dentry * dentry) { - for (;;) { - dentry->d_time = jiffies; - if (dentry == dentry->d_parent) - break; - dentry = dentry->d_parent; - } + dentry->d_time = jiffies; } static int nfs_lookup(struct inode *dir, struct dentry * dentry) @@ -545,11 +566,6 @@ static int nfs_lookup(struct inode *dir, struct dentry * dentry) dfprintk(VFS, "NFS: lookup(%s/%s)\n", dentry->d_parent->d_name.name, dentry->d_name.name); - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_lookup: inode is NULL or not a directory\n"); - return -ENOENT; - } - error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) goto out; @@ -634,12 +650,7 @@ static int nfs_create(struct inode *dir, struct dentry *dentry, int mode) struct nfs_fh fhandle; dfprintk(VFS, "NFS: create(%x/%ld, %s\n", - dir->i_dev, dir->i_ino, dentry->d_name.name); - - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_create: inode is NULL or not a directory\n"); - return -ENOENT; - } + dir->i_dev, dir->i_ino, dentry->d_name.name); error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) @@ -674,12 +685,7 @@ static int nfs_mknod(struct inode *dir, struct dentry *dentry, int mode, int rde struct nfs_fh fhandle; dfprintk(VFS, "NFS: mknod(%x/%ld, %s\n", - dir->i_dev, dir->i_ino, dentry->d_name.name); - - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_mknod: inode is NULL or not a directory\n"); - return -ENOENT; - } + dir->i_dev, dir->i_ino, dentry->d_name.name); if (dentry->d_name.len > NFS_MAXNAMLEN) return -ENAMETOOLONG; @@ -711,12 +717,7 @@ static int nfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) struct nfs_fh fhandle; dfprintk(VFS, "NFS: mkdir(%x/%ld, %s\n", - dir->i_dev, dir->i_ino, dentry->d_name.name); - - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_mkdir: inode is NULL or not a directory\n"); - return -ENOENT; - } + dir->i_dev, dir->i_ino, dentry->d_name.name); error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) @@ -753,12 +754,7 @@ static int nfs_rmdir(struct inode *dir, struct dentry *dentry) int error, rehash = 0; dfprintk(VFS, "NFS: rmdir(%x/%ld, %s\n", - dir->i_dev, dir->i_ino, dentry->d_name.name); - - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_rmdir: inode is NULL or not a directory\n"); - return -ENOENT; - } + dir->i_dev, dir->i_ino, dentry->d_name.name); error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) @@ -867,6 +863,10 @@ static int nfs_sillyrename(struct inode *dir, struct dentry *dentry) struct dentry *sdentry; int error = -EIO; + dfprintk(VFS, "NFS: silly-rename(%s/%s, ct=%d)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + dentry->d_count); + /* * Note that a silly-renamed file can be deleted once it's * no longer in use -- it's just an ordinary file now. @@ -939,6 +939,10 @@ static int nfs_safe_remove(struct dentry *dentry) struct inode *inode = dentry->d_inode; int error, rehash = 0; + dfprintk(VFS, "NFS: safe_remove(%s/%s, %ld)\n", + dentry->d_parent->d_name.name, dentry->d_name.name, + inode->i_ino); + /* N.B. not needed now that d_delete is done in advance? */ error = -EBUSY; if (inode) { @@ -1009,12 +1013,7 @@ static int nfs_unlink(struct inode *dir, struct dentry *dentry) int error; dfprintk(VFS, "NFS: unlink(%x/%ld, %s)\n", - dir->i_dev, dir->i_ino, dentry->d_name.name); - - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_unlink: inode is NULL or not a directory\n"); - return -ENOENT; - } + dir->i_dev, dir->i_ino, dentry->d_name.name); error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) @@ -1038,12 +1037,7 @@ nfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) int error; dfprintk(VFS, "NFS: symlink(%x/%ld, %s, %s)\n", - dir->i_dev, dir->i_ino, dentry->d_name.name, symname); - - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_symlink: inode is NULL or not a directory\n"); - return -ENOENT; - } + dir->i_dev, dir->i_ino, dentry->d_name.name, symname); error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) @@ -1095,11 +1089,6 @@ nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) old_dentry->d_parent->d_name.name, old_dentry->d_name.name, dentry->d_parent->d_name.name, dentry->d_name.name); - if (!dir || !S_ISDIR(dir->i_mode)) { - printk("nfs_link: dir is NULL or not a directory\n"); - return -ENOENT; - } - error = -ENAMETOOLONG; if (dentry->d_name.len > NFS_MAXNAMLEN) goto out; @@ -1153,22 +1142,13 @@ static int nfs_rename(struct inode *old_dir, struct dentry *old_dentry, { struct inode *old_inode = old_dentry->d_inode; struct inode *new_inode = new_dentry->d_inode; + struct dentry *dentry = NULL; int error, rehash = 0, update = 1; -#ifdef NFS_DEBUG_VERBOSE -printk("nfs_rename: old %s/%s, count=%d, new %s/%s, count=%d\n", -old_dentry->d_parent->d_name.name,old_dentry->d_name.name,old_dentry->d_count, -new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count); -#endif - if (!old_dir || !S_ISDIR(old_dir->i_mode)) { - printk("nfs_rename: old inode is NULL or not a directory\n"); - return -ENOENT; - } - - if (!new_dir || !S_ISDIR(new_dir->i_mode)) { - printk("nfs_rename: new inode is NULL or not a directory\n"); - return -ENOENT; - } + dfprintk(VFS, "NFS: rename(%s/%s -> %s/%s, ct=%d)\n", + old_dentry->d_parent->d_name.name, old_dentry->d_name.name, + new_dentry->d_parent->d_name.name, new_dentry->d_name.name, + new_dentry->d_count); error = -ENAMETOOLONG; if (old_dentry->d_name.len > NFS_MAXNAMLEN || @@ -1178,16 +1158,43 @@ new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count); /* * First check whether the target is busy ... we can't * safely do _any_ rename if the target is in use. + * + * For files, make a copy of the dentry and then do a + * silly-rename. If the silly-rename succeeds, the + * copied dentry is hashed and becomes the new target. + * + * For directories, prune any unused children. */ - if (new_dentry->d_count > 1 && !list_empty(&new_dentry->d_subdirs)) - shrink_dcache_parent(new_dentry); error = -EBUSY; - if (new_dentry->d_count > 1) { + if (new_dentry->d_count > 1 && new_inode) { + if (S_ISREG(new_inode->i_mode)) { + int err; + /* copy the target dentry's name */ + dentry = d_alloc(new_dentry->d_parent, + &new_dentry->d_name); + if (!dentry) + goto out; + + /* silly-rename the existing target ... */ + err = nfs_sillyrename(new_dir, new_dentry); + if (!err) { + new_dentry = dentry; + new_inode = NULL; + /* hash the replacement target */ + d_add(new_dentry, NULL); + } + } else if (!list_empty(&new_dentry->d_subdirs)) { + shrink_dcache_parent(new_dentry); + } + + /* dentry still busy? */ + if (new_dentry->d_count > 1) { #ifdef NFS_PARANOIA printk("nfs_rename: target %s/%s busy, d_count=%d\n", new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count); #endif - goto out; + goto out; + } } /* @@ -1231,7 +1238,7 @@ old_dentry->d_parent->d_name.name,old_dentry->d_name.name,old_dentry->d_count); #endif goto out; } - if (new_dentry->d_count > 1) { + if (new_dentry->d_count > 1 && new_inode) { #ifdef NFS_PARANOIA printk("nfs_rename: new dentry %s/%s busy, d_count=%d\n", new_dentry->d_parent->d_name.name,new_dentry->d_name.name,new_dentry->d_count); @@ -1274,7 +1281,11 @@ new_inode->i_count, new_inode->i_nlink); if (update) d_move(old_dentry, new_dentry); } + out: + /* new dentry created? */ + if (dentry) + dput(dentry); return error; } diff --git a/fs/nfs/file.c b/fs/nfs/file.c index 9ec883aff..973ad52ec 100644 --- a/fs/nfs/file.c +++ b/fs/nfs/file.c @@ -35,6 +35,7 @@ static int nfs_file_mmap(struct file *, struct vm_area_struct *); static ssize_t nfs_file_read(struct file *, char *, size_t, loff_t *); static ssize_t nfs_file_write(struct file *, const char *, size_t, loff_t *); +static int nfs_file_flush(struct file *); static int nfs_file_close(struct inode *, struct file *); static int nfs_fsync(struct file *, struct dentry *dentry); @@ -47,6 +48,7 @@ static struct file_operations nfs_file_operations = { NULL, /* ioctl - default */ nfs_file_mmap, /* mmap */ NULL, /* no special open is needed */ + nfs_file_flush, /* flush */ nfs_file_close, /* release */ nfs_fsync, /* fsync */ NULL, /* fasync */ @@ -84,13 +86,7 @@ struct inode_operations nfs_file_inode_operations = { #endif /* - * Flush all dirty pages, and check for write errors. - * - * Note that since the file close operation is called only by the - * _last_ process to close the file, we need to flush _all_ dirty - * pages. This also means that there is little sense in checking - * for errors for this specific process -- we should probably just - * clear all errors. + * Sync the file.. */ static int nfs_file_close(struct inode *inode, struct file *file) @@ -106,6 +102,22 @@ nfs_file_close(struct inode *inode, struct file *file) return status; } +/* + * Flush all dirty pages, and check for write errors. + * + * We should probably do this better - this does get called at every + * close, so we should probably just flush the changes that "this" + * file has done and report on only those. + * + * Right now we use the "flush everything" approach. Overkill, but + * works. + */ +static int +nfs_file_flush(struct file *file) +{ + return nfs_file_close(file->f_dentry->d_inode, file); +} + static ssize_t nfs_file_read(struct file * file, char * buf, size_t count, loff_t *ppos) { diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index e89abdbce..597821270 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -652,6 +652,7 @@ _nfs_revalidate_inode(struct nfs_server *server, struct dentry *dentry) int status = 0; struct nfs_fattr fattr; + /* Don't bother revalidating if we've done it recently */ if (jiffies - NFS_READTIME(inode) < NFS_ATTRTIMEO(inode)) goto out; @@ -746,6 +747,20 @@ nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr) goto out_changed; /* + * If we have pending write-back entries, we don't want + * to look at the size the server sends us too closely.. + * In particular, ignore the server if it tells us that + * the file is smaller or older than we locally think it + * is.. + */ + if (NFS_WRITEBACK(inode)) { + if (inode->i_size > fattr->size) + fattr->size = inode->i_size; + if (inode->i_mtime > fattr->mtime.seconds) + fattr->mtime.seconds = inode->i_mtime; + } + + /* * If the size or mtime changed from outside, we want * to invalidate the local caches immediately. */ diff --git a/fs/nfs/mount_clnt.c b/fs/nfs/mount_clnt.c index 81da8f996..3ecb9bfa6 100644 --- a/fs/nfs/mount_clnt.c +++ b/fs/nfs/mount_clnt.c @@ -78,6 +78,7 @@ mnt_create(char *hostname, struct sockaddr_in *srvaddr) clnt->cl_softrtry = 1; clnt->cl_chatty = 1; clnt->cl_oneshot = 1; + clnt->cl_intr = 1; } return clnt; } diff --git a/fs/nfs/write.c b/fs/nfs/write.c index c317f330b..f8658f37f 100644 --- a/fs/nfs/write.c +++ b/fs/nfs/write.c @@ -56,21 +56,12 @@ #include <linux/nfs_fs.h> #include <asm/uaccess.h> -/* - * NOTE! We must NOT default to soft-mounting: that breaks too many - * programs that depend on POSIX behaviour of uninterruptible reads - * and writes. - * - * Until we have a per-mount soft/hard mount policy that we can honour - * we must default to hard mounting! - */ -#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); +static void nfs_cancel_request(struct nfs_wreq *req); /* * Cache parameters @@ -258,7 +249,7 @@ nfs_find_dentry_request(struct inode *inode, struct dentry *dentry) req = head = NFS_WRITEBACK(inode); while (req != NULL) { - if (req->wb_dentry == dentry) { + if (req->wb_dentry == dentry && !WB_CANCELLED(req)) { found = 1; break; } @@ -442,11 +433,15 @@ schedule_write_request(struct nfs_wreq *req, int sync) sync = 1; if (sync) { + sigset_t oldmask; + struct rpc_clnt *clnt = NFS_CLIENT(inode); dprintk("NFS: %4d schedule_write_request (sync)\n", task->tk_pid); /* Page is already locked */ req->wb_flags |= NFS_WRITE_LOCKED; + rpc_clnt_sigmask(clnt, &oldmask); rpc_execute(task); + rpc_clnt_sigunmask(clnt, &oldmask); } else { dprintk("NFS: %4d schedule_write_request (async)\n", task->tk_pid); @@ -467,17 +462,20 @@ wait_on_write_request(struct nfs_wreq *req) { struct wait_queue wait = { current, NULL }; struct page *page = req->wb_page; - int retval; + int retval; + sigset_t oldmask; + struct rpc_clnt *clnt = NFS_CLIENT(req->wb_inode); + rpc_clnt_sigmask(clnt, &oldmask); add_wait_queue(&page->wait, &wait); atomic_inc(&page->count); for (;;) { - current->state = IS_SOFT ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE; + current->state = TASK_INTERRUPTIBLE; retval = 0; if (!PageLocked(page)) break; retval = -ERESTARTSYS; - if (IS_SOFT && signalled()) + if (signalled()) break; schedule(); } @@ -485,6 +483,7 @@ wait_on_write_request(struct nfs_wreq *req) current->state = TASK_RUNNING; /* N.B. page may have been unused, so we must use free_page() */ free_page(page_address(page)); + rpc_clnt_sigunmask(clnt, &oldmask); return retval; } @@ -534,6 +533,10 @@ nfs_updatepage(struct file *file, 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 */ + /* + * SECURITY - copy_from_user must zero the + * rest of the data after a fault! + */ copy_from_user(page_addr + offset, buffer, count); goto updated; } @@ -583,8 +586,11 @@ done: transfer_page_lock(req); /* rpc_execute(&req->wb_task); */ if (sync) { - /* N.B. if signalled, result not ready? */ - wait_on_write_request(req); + /* if signalled, ensure request is cancelled */ + if ((count = wait_on_write_request(req)) != 0) { + nfs_cancel_request(req); + status = count; + } if ((count = nfs_write_error(inode)) < 0) status = count; } @@ -716,29 +722,21 @@ int nfs_flush_dirty_pages(struct inode *inode, pid_t pid, off_t offset, off_t len) { struct nfs_wreq *last = NULL; - int result = 0, cancel = 0; + int result = 0; dprintk("NFS: flush_dirty_pages(%x/%ld for pid %d %ld/%ld)\n", inode->i_dev, inode->i_ino, current->pid, offset, len); - if (IS_SOFT && signalled()) { - nfs_cancel_dirty(inode, pid); - cancel = 1; - } - for (;;) { - if (IS_SOFT && signalled()) { - if (!cancel) - nfs_cancel_dirty(inode, pid); - result = -ERESTARTSYS; - break; - } - /* Flush all pending writes for the pid and file region */ last = nfs_flush_pages(inode, pid, offset, len, 0); if (last == NULL) break; - wait_on_write_request(last); + result = wait_on_write_request(last); + if (result) { + nfs_cancel_dirty(inode,pid); + break; + } } return result; @@ -889,6 +887,9 @@ nfs_wback_result(struct rpc_task *task) /* Update attributes as result of writeback. * Beware: when UDP replies arrive out of order, we * may end up overwriting a previous, bigger file size. + * + * When the file size shrinks we cancel all pending + * writebacks. */ if (fattr->mtime.seconds >= inode->i_mtime) { if (fattr->size < inode->i_size) |