patch-2.2.18 linux/fs/nfs/inode.c

Next file: linux/fs/nfs/mount_clnt.c
Previous file: linux/fs/nfs/flushd.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/fs/nfs/inode.c linux/fs/nfs/inode.c
@@ -24,11 +24,19 @@
 #include <linux/errno.h>
 #include <linux/locks.h>
 #include <linux/unistd.h>
+#include <linux/major.h>
 #include <linux/sunrpc/clnt.h>
 #include <linux/sunrpc/stats.h>
+#include <linux/nfs.h>
+#include <linux/nfs2.h>
+#include <linux/nfs3.h>
 #include <linux/nfs_fs.h>
+#include <linux/nfs_mount.h>
+#include <linux/nfs_flushd.h>
 #include <linux/lockd/bind.h>
 
+#include <asm/spinlock.h>
+
 #include <asm/system.h>
 #include <asm/uaccess.h>
 
@@ -37,16 +45,16 @@
 #define NFS_PARANOIA 1
 
 static struct inode * __nfs_fhget(struct super_block *, struct nfs_fattr *);
-static void nfs_zap_caches(struct inode *);
-static void nfs_invalidate_inode(struct inode *);
 
 static void nfs_read_inode(struct inode *);
 static void nfs_put_inode(struct inode *);
 static void nfs_delete_inode(struct inode *);
 static int  nfs_notify_change(struct dentry *, struct iattr *);
 static void nfs_put_super(struct super_block *);
-static void nfs_umount_begin(struct super_block *);
 static int  nfs_statfs(struct super_block *, struct statfs *, int);
+static void nfs_umount_begin(struct super_block *);
+static struct nfs_file *nfs_file_alloc(void);
+static void nfs_file_free(struct nfs_file *p);
 
 static struct super_operations nfs_sops = { 
 	nfs_read_inode,		/* read inode */
@@ -57,12 +65,44 @@
 	nfs_put_super,		/* put superblock */
 	NULL,			/* write superblock */
 	nfs_statfs,		/* stat filesystem */
-	NULL,			/* no remount */
-	NULL,			/* no clear inode */
+	NULL,			/* remount */
+	NULL,			/* clear inode */
 	nfs_umount_begin	/* umount attempt begin */
 };
 
-struct rpc_stat			nfs_rpcstat = { &nfs_program };
+
+/*
+ * RPC crutft for NFS
+ */
+static struct rpc_stat		nfs_rpcstat = { &nfs_program };
+static struct rpc_version *	nfs_version[] = {
+	NULL,
+	NULL,
+	&nfs_version2,
+#ifdef CONFIG_NFS_V3
+	&nfs_version3,
+#endif
+};
+
+struct rpc_program		nfs_program = {
+	"nfs",
+	NFS_PROGRAM,
+	sizeof(nfs_version) / sizeof(nfs_version[0]),
+	nfs_version,
+	&nfs_rpcstat,
+};
+
+static inline unsigned long
+nfs_fattr_to_ino_t(struct nfs_fattr *fattr)
+{
+	return nfs_fileid_to_ino_t(fattr->fileid);
+}
+
+/*
+ * We don't keep the file handle in the inode anymore to avoid bloating
+ * struct inode and use a pointer to external memory instead.
+ */
+#define NFS_SB_FHSIZE(sb)	((sb)->u.nfs_sb.s_fhsize)
 
 /*
  * The "read_inode" function doesn't actually do anything:
@@ -78,8 +118,19 @@
 	inode->i_mode = 0;
 	inode->i_rdev = 0;
 	inode->i_op = NULL;
+	NFS_FILEID(inode) = 0;
+	NFS_FSID(inode) = 0;
+	INIT_LIST_HEAD(&inode->u.nfs_i.read);
+	INIT_LIST_HEAD(&inode->u.nfs_i.dirty);
+	INIT_LIST_HEAD(&inode->u.nfs_i.commit);
+	INIT_LIST_HEAD(&inode->u.nfs_i.writeback);
+	inode->u.nfs_i.nread = 0;
+	inode->u.nfs_i.ndirty = 0;
+	inode->u.nfs_i.ncommit = 0;
+	inode->u.nfs_i.npages = 0;
 	NFS_CACHEINV(inode);
 	NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
+	NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
 }
 
 static void
@@ -96,32 +147,15 @@
 static void
 nfs_delete_inode(struct inode * inode)
 {
-	int failed;
-
 	dprintk("NFS: delete_inode(%x/%ld)\n", inode->i_dev, inode->i_ino);
+#ifdef NFS_DEBUG_VERBOSE
 	/*
 	 * Flush out any pending write requests ...
 	 */
-	if (NFS_WRITEBACK(inode) != NULL) {
-		unsigned long timeout = jiffies + 5*HZ;
-#ifdef NFS_DEBUG_VERBOSE
-printk("nfs_delete_inode: inode %ld has pending RPC requests\n", inode->i_ino);
-#endif
-		nfs_inval(inode);
-		while (NFS_WRITEBACK(inode) != NULL &&
-		       time_before(jiffies, timeout)) {
-			current->state = TASK_INTERRUPTIBLE;
-			schedule_timeout(HZ/10);
-		}
-		current->state = TASK_RUNNING;
-		if (NFS_WRITEBACK(inode) != NULL)
-			printk("NFS: Arghhh, stuck RPC requests!\n");
+	if (nfs_have_writebacks(inode) || nfs_have_read(inode)) {
+		printk(KERN_ERR "nfs_delete_inode: inode %ld has pending RPC requests\n", inode->i_ino);
 	}
-
-	failed = nfs_check_failed_request(inode);
-	if (failed)
-		printk("NFS: inode %ld had %d failed requests\n",
-			inode->i_ino, failed);
+#endif
 	clear_inode(inode);
 }
 
@@ -131,18 +165,22 @@
 	struct nfs_server *server = &sb->u.nfs_sb.s_server;
 	struct rpc_clnt	*rpc;
 
+	/*
+	 * First get rid of the request flushing daemon.
+	 * Relies on rpc_shutdown_client() waiting on all
+	 * client tasks to finish.
+	 */
+	nfs_reqlist_exit(server);
+
 	if ((rpc = server->client) != NULL)
 		rpc_shutdown_client(rpc);
 
-#if 0
+	nfs_reqlist_free(server);
+
 	if (!(server->flags & NFS_MOUNT_NONLM))
 		lockd_down();	/* release rpc.lockd */
-#endif
+
 	rpciod_down();		/* release rpciod */
-	/*
-	 * Invalidate the dircache for this superblock.
-	 */
-	nfs_invalidate_dircache_sb(sb);
 
 	kfree(server->hostname);
 
@@ -160,17 +198,10 @@
 		rpc_killall_tasks(rpc);
 }
 
-/*
- * Compute and set NFS server blocksize
- */
-static unsigned int
-nfs_block_size(unsigned int bsize, unsigned char *nrbitsp)
-{
-	if (bsize < 1024)
-		bsize = NFS_DEF_FILE_IO_BUFFER_SIZE;
-	else if (bsize >= NFS_MAX_FILE_IO_BUFFER_SIZE)
-		bsize = NFS_MAX_FILE_IO_BUFFER_SIZE;
 
+static inline unsigned long
+nfs_block_bits(unsigned long bsize, unsigned char *nrbitsp)
+{
 	/* make sure blocksize is a power of two */
 	if ((bsize & (bsize - 1)) || nrbitsp) {
 		unsigned int	nrbits;
@@ -180,14 +211,56 @@
 		bsize = 1 << nrbits;
 		if (nrbitsp)
 			*nrbitsp = nrbits;
-		if (bsize < NFS_DEF_FILE_IO_BUFFER_SIZE)
-			bsize = NFS_DEF_FILE_IO_BUFFER_SIZE;
 	}
 
 	return bsize;
 }
 
 /*
+ * Calculate the number of 512byte blocks used.
+ */
+static inline unsigned long
+nfs_calc_block_size(u64 tsize)
+{
+	off_t used = nfs_size_to_off_t(tsize);
+	return (used + 511) >> 9;
+}
+
+/*
+ * Compute and set NFS server blocksize
+ */
+static inline unsigned long
+nfs_block_size(unsigned long bsize, unsigned char *nrbitsp)
+{
+	if (bsize < 1024)
+		bsize = NFS_DEF_FILE_IO_BUFFER_SIZE;
+	else if (bsize >= NFS_MAX_FILE_IO_BUFFER_SIZE)
+		bsize = NFS_MAX_FILE_IO_BUFFER_SIZE;
+
+	return nfs_block_bits(bsize, nrbitsp);
+}
+
+/*
+ * Obtain the root inode of the file system.
+ */
+static struct inode *
+nfs_get_root(struct super_block *sb, struct nfs_fh *rootfh)
+{
+	struct nfs_server	*server = &sb->u.nfs_sb.s_server;
+	struct nfs_fattr	fattr;
+	struct inode		*inode;
+	int			error;
+
+	if ((error = server->rpc_ops->getroot(server, rootfh, &fattr)) < 0) {
+		printk(KERN_NOTICE "nfs_get_root: getattr error = %d\n", -error);
+		return NULL;
+	}
+
+	inode = __nfs_fhget(sb, &fattr);
+	return inode;
+}
+
+/*
  * The way this works is that the mount process passes a structure
  * in the data argument which contains the server's IP address
  * and the root file handle obtained from the server's mount
@@ -198,38 +271,47 @@
 {
 	struct nfs_mount_data	*data = (struct nfs_mount_data *) raw_data;
 	struct nfs_server	*server;
-	struct rpc_xprt		*xprt;
-	struct rpc_clnt		*clnt;
-	struct nfs_fh		*root_fh;
-	struct inode		*root_inode;
+	struct rpc_xprt		*xprt = 0;
+	struct rpc_clnt		*clnt = 0;
+	struct nfs_fh		*root_fh = NULL,
+				*root = &data->root,
+				fh;
+	struct inode		*root_inode = NULL;
 	unsigned int		authflavor;
-	int			tcp;
 	struct sockaddr_in	srvaddr;
 	struct rpc_timeout	timeparms;
-	struct nfs_fattr	fattr;
+	struct nfs_fsinfo       fsinfo;
+	int                     tcp, version, maxlen;
 
 	MOD_INC_USE_COUNT;
-	if (!data)
-		goto out_miss_args;
+	memset(&sb->u.nfs_sb, 0, sizeof(sb->u.nfs_sb));
+	if (!data) {
+		printk(KERN_NOTICE "nfs_read_super: missing data argument\n");
+		goto failure;
+	}
 
-	/* No NFS V3. */
-	if (data->flags & NFS_MOUNT_VER3)
-		goto out_fail;
-
-	/* Don't complain if "mount" is newer. */
-	if (data->version < NFS_MOUNT_VERSION) {
-		printk("nfs warning: mount version %s than kernel\n",
+	memset(&fh, 0, sizeof(fh));
+	if (data->version != NFS_MOUNT_VERSION) {
+		printk(KERN_WARNING "nfs warning: mount version %s than kernel\n",
 			data->version < NFS_MOUNT_VERSION ? "older" : "newer");
 		if (data->version < 2)
 			data->namlen = 0;
 		if (data->version < 3)
 			data->bsize  = 0;
+		if (data->version < 4) {
+			data->flags &= ~NFS_MOUNT_VER3;
+			root = &fh;
+			root->size = NFS2_FHSIZE;
+			memcpy(root->data, data->old_root.data, NFS2_FHSIZE);
+		}
 	}
 
 	/* We now require that the mount process passes the remote address */
 	memcpy(&srvaddr, &data->addr, sizeof(srvaddr));
-	if (srvaddr.sin_addr.s_addr == INADDR_ANY)
-		goto out_no_remote;
+	if (srvaddr.sin_addr.s_addr == INADDR_ANY) {
+		printk(KERN_WARNING "NFS: mount program didn't pass remote address!\n");
+		goto failure;
+	}
 
 	lock_super(sb);
 
@@ -237,12 +319,16 @@
 
 	sb->s_magic      = NFS_SUPER_MAGIC;
 	sb->s_op         = &nfs_sops;
-	sb->s_blocksize  = nfs_block_size(data->bsize, &sb->s_blocksize_bits);
-	sb->u.nfs_sb.s_root = data->root;
+
+	sb->s_blocksize_bits = 0;
+	sb->s_blocksize = nfs_block_bits(data->bsize, &sb->s_blocksize_bits);
+
 	server           = &sb->u.nfs_sb.s_server;
+	memset(server, 0, sizeof(*server));
+
 	server->rsize    = nfs_block_size(data->rsize, NULL);
 	server->wsize    = nfs_block_size(data->wsize, NULL);
-	server->flags    = data->flags;
+	server->flags    = data->flags & NFS_MOUNT_FLAGMASK;
 
 	if (data->flags & NFS_MOUNT_NOAC) {
 		data->acregmin = data->acregmax = 0;
@@ -253,11 +339,34 @@
 	server->acdirmin = data->acdirmin*HZ;
 	server->acdirmax = data->acdirmax*HZ;
 
+	server->namelen  = data->namlen;
 	server->hostname = kmalloc(strlen(data->hostname) + 1, GFP_KERNEL);
 	if (!server->hostname)
-		goto out_unlock;
+		goto failure_unlock;
 	strcpy(server->hostname, data->hostname);
 
+ nfsv3_try_again:
+	/* Check NFS protocol revision and initialize RPC op vector
+	 * and file handle pool. */
+	if (data->flags & NFS_MOUNT_VER3) {
+#ifdef CONFIG_NFS_V3
+		server->rpc_ops = &nfs_v3_clientops;
+		NFS_SB_FHSIZE(sb) = sizeof(unsigned short) + NFS3_FHSIZE;
+		version = 3;
+		if (data->version < 4) {
+			printk(KERN_NOTICE "NFS: NFSv3 not supported by mount program.\n");
+			goto failure_unlock;
+		}
+#else
+		printk(KERN_NOTICE "NFS: NFSv3 not supported.\n");
+		goto failure_unlock;
+#endif
+	} else {
+		server->rpc_ops = &nfs_v2_clientops;
+		NFS_SB_FHSIZE(sb) = sizeof(unsigned short) + NFS2_FHSIZE;
+		version = 2;
+	}
+
 	/* Which protocol do we use? */
 	tcp   = (data->flags & NFS_MOUNT_TCP);
 
@@ -267,11 +376,18 @@
 	timeparms.to_maxval  = tcp? RPC_MAX_TCP_TIMEOUT : RPC_MAX_UDP_TIMEOUT;
 	timeparms.to_exponential = 1;
 
+	if (!timeparms.to_initval)
+		timeparms.to_initval = (tcp ? 600 : 11) * HZ / 10;
+	if (!timeparms.to_retries)
+		timeparms.to_retries = 5;
+
 	/* Now create transport and client */
 	xprt = xprt_create_proto(tcp? IPPROTO_TCP : IPPROTO_UDP,
 						&srvaddr, &timeparms);
-	if (xprt == NULL)
-		goto out_no_xprt;
+	if (xprt == NULL) {
+		printk(KERN_NOTICE "NFS: cannot create RPC transport. \n");
+		goto failure_unlock;
+	}
 
 	/* Choose authentication flavor */
 	authflavor = RPC_AUTH_UNIX;
@@ -281,92 +397,145 @@
 		authflavor = RPC_AUTH_KRB;
 
 	clnt = rpc_create_client(xprt, server->hostname, &nfs_program,
-						NFS_VERSION, authflavor);
-	if (clnt == NULL)
-		goto out_no_client;
+						version, authflavor);
+	if (clnt == NULL) {
+		printk(KERN_NOTICE "NFS: cannot create RPC client \n");
+		goto failure_unlock;
+	}
 
 	clnt->cl_intr     = (data->flags & NFS_MOUNT_INTR)? 1 : 0;
 	clnt->cl_softrtry = (data->flags & NFS_MOUNT_SOFT)? 1 : 0;
+	clnt->cl_droppriv = (data->flags & NFS_MOUNT_BROKEN_SUID) ? 1 : 0;
 	clnt->cl_chatty   = 1;
 	server->client    = clnt;
 
 	/* Fire up rpciod if not yet running */
-	if (rpciod_up() != 0)
-		goto out_no_iod;
+	if (rpciod_up() != 0) {
+		printk(KERN_NOTICE "NFS: cannot start rpciod!\n");
+		goto failure_unlock;
+	}
 
 	/*
 	 * Keep the super block locked while we try to get 
 	 * the root fh attributes.
 	 */
-	root_fh = kmalloc(sizeof(struct nfs_fh), GFP_KERNEL);
+	root_fh = nfs_fh_alloc();
 	if (!root_fh)
 		goto out_no_fh;
-	*root_fh = data->root;
-
-	if (nfs_proc_getattr(server, root_fh, &fattr) != 0)
-		goto out_no_fattr;
+	memcpy((u8*)root_fh, (u8*)root, sizeof(*root_fh));
 
-	root_inode = __nfs_fhget(sb, &fattr);
+	/* Did getting the root inode fail? */
+	if ((root->size > NFS_SB_FHSIZE(sb)
+	     || ! (root_inode = nfs_get_root(sb, root)))
+	    && (data->flags & NFS_MOUNT_VER3)) {
+		data->flags &= ~NFS_MOUNT_VER3;
+		nfs_fh_free(root_fh);
+		rpciod_down();
+		rpc_shutdown_client(server->client);
+		goto nfsv3_try_again;
+	}
 	if (!root_inode)
-		goto out_no_root;
-	sb->s_root = d_alloc_root(root_inode, NULL);
-	if (!sb->s_root)
-		goto out_no_root;
+		goto failure_put_root;
+
+	if (! (sb->s_root = d_alloc_root(root_inode, NULL)))
+		goto failure_put_root;
+
 	sb->s_root->d_op = &nfs_dentry_operations;
 	sb->s_root->d_fsdata = root_fh;
+	sb->u.nfs_sb.s_root = root_fh;
+
+	/* Get some general file system info */
+	if (server->rpc_ops->statfs(server, root, &fsinfo) >= 0) {
+		if (server->namelen == 0)
+			server->namelen = fsinfo.namelen;
+	} else {
+		printk(KERN_NOTICE "NFS: cannot retrieve file system info.\n");
+		goto failure_put_root;
+	}
+
+	/* Fire up the writeback cache */
+	if (nfs_reqlist_alloc(server) < 0) {
+		printk(KERN_NOTICE "NFS: cannot initialize writeback cache.\n");
+                goto failure_kill_reqlist;
+	}
+
+	if (data->rsize == 0 && tcp)
+		server->rsize = nfs_block_size(fsinfo.rtpref, NULL);
+	if (data->wsize == 0 && tcp)
+		server->wsize = nfs_block_size(fsinfo.wtpref, NULL);
+
+	/* NFSv3: we don't have bsize, but rather rtmult and wtmult... */
+	if (!fsinfo.bsize)
+		fsinfo.bsize = (fsinfo.rtmult>fsinfo.wtmult) ? fsinfo.rtmult : fsinfo.wtmult;
+	/* Also make sure we don't go below rsize/wsize since
+	 * RPC calls are expensive */
+	if (fsinfo.bsize < server->rsize)
+		fsinfo.bsize = server->rsize;
+	if (fsinfo.bsize < server->wsize)
+		fsinfo.bsize = server->wsize;
+
+	if (data->bsize == 0)
+		sb->s_blocksize = nfs_block_bits(fsinfo.bsize, &sb->s_blocksize_bits);
+	if (server->rsize > fsinfo.rtmax)
+		server->rsize = fsinfo.rtmax;
+	server->rpages = (server->rsize + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+	if (server->rpages > NFS_READ_MAXIOV) {
+		server->rpages = NFS_READ_MAXIOV;
+		server->rsize = server->rpages << PAGE_CACHE_SHIFT;
+	}
+
+	if (server->wsize > fsinfo.wtmax)
+		server->wsize = fsinfo.wtmax;
+	server->wpages = (server->wsize + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT;
+	if (server->wpages > NFS_WRITE_MAXIOV) {
+		server->wpages = NFS_WRITE_MAXIOV;
+		server->wsize = server->wpages << PAGE_CACHE_SHIFT;
+	}
+
+	server->dtsize = nfs_block_size(fsinfo.dtpref, NULL);
+	if (server->dtsize > PAGE_CACHE_SIZE)
+		server->dtsize = PAGE_CACHE_SIZE;
+	if (server->dtsize > server->rsize)
+		server->dtsize = server->rsize;
+
+	maxlen = (version == 2) ? NFS2_MAXNAMLEN : NFS3_MAXNAMLEN;
+
+	if (server->namelen == 0 || server->namelen > maxlen)
+		server->namelen = maxlen;
 
 	/* We're airborne */
 	unlock_super(sb);
 
-#if 0
 	/* Check whether to start the lockd process */
 	if (!(server->flags & NFS_MOUNT_NONLM))
 		lockd_up();
-#endif
+
 	return sb;
 
 	/* Yargs. It didn't work out. */
-out_no_root:
-	printk("nfs_read_super: get root inode failed\n");
-	iput(root_inode);
-	goto out_free_fh;
-
-out_no_fattr:
-	printk("nfs_read_super: get root fattr failed\n");
-out_free_fh:
-	kfree(root_fh);
-out_no_fh:
+ failure_kill_reqlist:
+	nfs_reqlist_exit(server);
+ failure_put_root:
+	if (root_inode)
+		iput(root_inode);
+	if (root_fh)
+		nfs_fh_free(root_fh);
+ out_no_fh:
 	rpciod_down();
-	goto out_shutdown;
-
-out_no_iod:
-	printk(KERN_WARNING "NFS: couldn't start rpciod!\n");
-out_shutdown:
-	rpc_shutdown_client(server->client);
-	goto out_free_host;
-
-out_no_client:
-	printk(KERN_WARNING "NFS: cannot create RPC client.\n");
-	xprt_destroy(xprt);
-	goto out_free_host;
 
-out_no_xprt:
-	printk(KERN_WARNING "NFS: cannot create RPC transport.\n");
-
-out_free_host:
-	kfree(server->hostname);
-out_unlock:
+ failure_unlock:
+	/* Yargs. It didn't work out. */
+	if (clnt)
+		rpc_shutdown_client(server->client);
+	else if (xprt)
+		xprt_destroy(xprt);
 	unlock_super(sb);
-	goto out_fail;
-
-out_no_remote:
-	printk("NFS: mount program didn't pass remote address!\n");
-	goto out_fail;
-
-out_miss_args:
-	printk("nfs_read_super: missing data argument\n");
+	nfs_reqlist_free(server);
+	if (server->hostname)
+		kfree(server->hostname);
+	printk(KERN_NOTICE "NFS: cannot create RPC transport.\n");
 
-out_fail:
+failure:
 	sb->s_dev = 0;
 	MOD_DEC_USE_COUNT;
 	return NULL;
@@ -375,27 +544,51 @@
 static int
 nfs_statfs(struct super_block *sb, struct statfs *buf, int bufsiz)
 {
-	int error;
-	struct nfs_fsinfo res;
-	struct statfs tmp;
+	struct nfs_sb_info	*si = &sb->u.nfs_sb;
+	struct nfs_server	*server = &si->s_server;
+	unsigned char		blockbits;
+	unsigned long		blockres;
+	int			error;
+	struct nfs_fsinfo	res;
+	struct statfs		tmp;
 
-	error = nfs_proc_statfs(&sb->u.nfs_sb.s_server, &sb->u.nfs_sb.s_root,
-		&res);
+	error = server->rpc_ops->statfs(server, NFS_FH(sb->s_root), &res);
 	if (error) {
-		printk("nfs_statfs: statfs error = %d\n", -error);
-		res.bsize = res.blocks = res.bfree = res.bavail = 0;
+		printk(KERN_NOTICE "nfs_statfs: statfs error = %d\n", -error);
+		memset(&res, 0, sizeof(res));
 	}
 	tmp.f_type = NFS_SUPER_MAGIC;
-	tmp.f_bsize = res.bsize;
-	tmp.f_blocks = res.blocks;
-	tmp.f_bfree = res.bfree;
-	tmp.f_bavail = res.bavail;
-	tmp.f_files = 0;
-	tmp.f_ffree = 0;
-	tmp.f_namelen = NAME_MAX;
+	if (res.bsize == 0)
+		res.bsize = sb->s_blocksize;
+	if (res.namelen == 0)
+		res.namelen = server->namelen;
+	tmp.f_bsize   = nfs_block_bits(res.bsize, &blockbits);
+	blockres = (1 << blockbits) - 1;
+	tmp.f_blocks  = (res.tbytes + blockres) >> blockbits;
+	tmp.f_bfree   = (res.fbytes + blockres) >> blockbits;
+	tmp.f_bavail  = (res.abytes + blockres) >> blockbits;
+	tmp.f_files   = res.tfiles;
+	tmp.f_ffree   = res.ffiles;
+	tmp.f_namelen = res.namelen;
 	return copy_to_user(buf, &tmp, bufsiz) ? -EFAULT : 0;
 }
 
+#if 0
+int nfs_remountfs(struct super_block *sb, int *flags, char *data)
+{
+	struct nfs_server *server = &sb->u.nfs_sb.s_server;
+
+	if (*flags & ~(NFS_MOUNT_NONLM|MS_RDONLY))
+		return -EINVAL;
+
+	if (*flags & ~NFS_MOUNT_NONLM)
+		return 0;
+
+	if ((*flags & NFS_MOUNT_NONLM) == (server->flags & NFS_MOUNT_NONLM))
+		return 0;
+}
+#endif
+
 /*
  * Free all unused dentries in an inode's alias list.
  *
@@ -437,23 +630,19 @@
 }
 
 /*
- * Invalidate the local caches
+ * Zap the caches.
  */
-static void
-nfs_zap_caches(struct inode *inode)
+void nfs_zap_caches(struct inode *inode)
 {
 	NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
-	NFS_CACHEINV(inode);
+	NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
+
+	invalidate_inode_pages(inode);
 
-	if (S_ISDIR(inode->i_mode))
-		nfs_invalidate_dircache(inode);
-	else
-		invalidate_inode_pages(inode);
+	memset(NFS_COOKIEVERF(inode), 0, sizeof(NFS_COOKIEVERF(inode)));
+	NFS_CACHEINV(inode);
 }
 
-/*
- * Invalidate, but do not unhash, the inode
- */
 static void
 nfs_invalidate_inode(struct inode *inode)
 {
@@ -461,7 +650,6 @@
 
 	make_bad_inode(inode);
 	inode->i_mode = save_mode;
-	nfs_inval(inode);
 	nfs_zap_caches(inode);
 }
 
@@ -476,6 +664,8 @@
 	 * do this once. (We don't allow inodes to change types.)
 	 */
 	if (inode->i_mode == 0) {
+		NFS_FILEID(inode) = fattr->fileid;
+		NFS_FSID(inode) = fattr->fsid;
 		inode->i_mode = fattr->mode;
 		if (S_ISREG(inode->i_mode))
 			inode->i_op = &nfs_file_inode_operations;
@@ -496,26 +686,62 @@
 		/*
 		 * Preset the size and mtime, as there's no need
 		 * to invalidate the caches.
-		 */ 
-		inode->i_size  = fattr->size;
-		inode->i_mtime = fattr->mtime.seconds;
-		NFS_OLDMTIME(inode) = fattr->mtime.seconds;
+		 */
+		inode->i_size  = nfs_size_to_off_t(fattr->size);
+		inode->i_mtime = nfs_time_to_secs(fattr->mtime);
+		inode->i_atime = nfs_time_to_secs(fattr->atime);
+		inode->i_ctime = nfs_time_to_secs(fattr->ctime);
+		NFS_CACHE_CTIME(inode) = fattr->ctime;
+		NFS_CACHE_MTIME(inode) = fattr->mtime;
+		NFS_CACHE_ATIME(inode) = fattr->atime;
+		NFS_CACHE_ISIZE(inode) = fattr->size;
+		NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
+		NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
 	}
 	nfs_refresh_inode(inode, fattr);
 }
 
+static struct inode *
+nfs_make_new_inode(struct super_block *sb, struct nfs_fattr *fattr)
+{
+	struct inode *inode = get_empty_inode();
+
+	if (!inode)
+		return NULL;	
+	inode->i_sb = sb;
+	inode->i_dev = sb->s_dev;
+	inode->i_flags = 0;
+	inode->i_ino = nfs_fattr_to_ino_t(fattr);
+	nfs_read_inode(inode);
+	nfs_fill_inode(inode, fattr);
+	return inode;
+}
+
 /*
- * The following may seem pretty minimal, but the stateless nature
- * of NFS means that we can't do too much more. Previous attempts to use
- * fattr->nlink to determine how well the cached state matches the
- * server suffer from races with stale dentries. You also risk killing
- * off processes by just doing 'mv file newdir' on the server.
- *
- * FIXME: Of course, if 2 exported files have the same fileid (but
- * different fsid which makes it legal) you're still buggered...
- *                                      Trond, August 1999.
+ * In NFSv3 we can have 64bit inode numbers. In order to support
+ * this, and re-exported directories (also seen in NFSv2)
+ * we are forced to allow 2 different inodes to have the same
+ * i_ino.
  */
 static int
+nfs_find_actor(struct inode *inode, unsigned long ino, void *opaque)
+{
+	struct nfs_fattr *fattr = (struct nfs_fattr *)opaque;
+	if (NFS_FSID(inode) != fattr->fsid)
+		return 0;
+	if (NFS_FILEID(inode) != fattr->fileid)
+		return 0;
+	if (inode->i_mode &&
+	    (fattr->mode & S_IFMT) != (inode->i_mode & S_IFMT))
+		return 0;
+	if (is_bad_inode(inode))
+		return 0;
+	if (NFS_FLAGS(inode) & NFS_INO_STALE)
+		return 0;
+	return 1;
+}
+
+static int
 nfs_inode_is_stale(struct inode *inode, struct nfs_fattr *fattr)
 {
 	int unhashed;
@@ -529,16 +755,16 @@
 		is_stale = 1;
 
 	/*
-	 * Free up unused cached dentries to see if it's wise to unhash
-	 * the inode (which we can do if all the dentries have been unhashed).
+	 * If the inode seems stale, free up cached dentries.
 	 */
 	unhashed = nfs_free_dentries(inode);
 
-	/* Assume we're holding 1 lock on the inode from 'iget'
+	/* Assume we're holding an i_count
 	 *
 	 * NB: sockets sometimes have volatile file handles
 	 *     don't invalidate their inodes even if all dentries are
-	 *     unhashed. */
+	 *     unhashed.
+	 */
 	if (unhashed && inode->i_count == unhashed + 1
 	    && !S_ISSOCK(inode->i_mode) && !S_ISFIFO(inode->i_mode))
 		is_stale = 1;
@@ -561,12 +787,12 @@
 {
 	struct super_block *sb = dentry->d_sb;
 
-	dprintk("NFS: nfs_fhget(%s/%s fileid=%d)\n",
+	dprintk("NFS: nfs_fhget(%s/%s fileid=%Lu)\n",
 		dentry->d_parent->d_name.name, dentry->d_name.name,
-		fattr->fileid);
+		(long long) fattr->fileid);
 
 	/* Install the file handle in the dentry */
-	*((struct nfs_fh *) dentry->d_fsdata) = *fhandle;
+	memcpy(NFS_FH(dentry), (u8*)fhandle, sizeof(*fhandle));
 
 #ifdef CONFIG_NFS_SNAPSHOT
 	/*
@@ -576,15 +802,9 @@
 	if ((dentry->d_parent->d_inode->u.nfs_i.flags & NFS_IS_SNAPSHOT) ||
 	    (dentry->d_name.len == 9 &&
 	     memcmp(dentry->d_name.name, ".snapshot", 9) == 0)) {
-		struct inode *inode = get_empty_inode();
+		struct inode *inode = nfs_make_new_inode(sb, fattr);
 		if (!inode)
-			goto out;	
-		inode->i_sb = sb;
-		inode->i_dev = sb->s_dev;
-		inode->i_flags = 0;
-		inode->i_ino = fattr->fileid;
-		nfs_read_inode(inode);
-		nfs_fill_inode(inode, fattr);
+			goto out;
 		inode->u.nfs_i.flags |= NFS_IS_SNAPSHOT;
 		dprintk("NFS: nfs_fhget(snapshot ino=%ld)\n", inode->i_ino);
 	out:
@@ -605,27 +825,39 @@
 static struct inode *
 __nfs_fhget(struct super_block *sb, struct nfs_fattr *fattr)
 {
-	struct inode *inode = NULL;
+	struct inode	*inode = NULL;
+	unsigned long	ino;
 
-	if (!fattr)
+	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
 		goto out_no_inode;
 
-	while (!inode) {
-		inode = iget(sb, fattr->fileid);
-		if (!inode)
-			goto out_no_inode;
-		/* N.B. This should be impossible ... */
-		if (inode->i_ino != fattr->fileid)
-			goto out_bad_id;
+	ino = nfs_fattr_to_ino_t(fattr);
+
+	while((inode = iget4(sb, ino, nfs_find_actor, fattr)) != NULL) {
 
+		/*
+		 * Check for busy inodes, and attempt to get rid of any
+		 * unused local references. If successful, we release the
+		 * inode and try again.
+		 *
+		 * Note that the busy test uses the values in the fattr,
+		 * as the inode may have become a different object.
+		 * (We can probably handle modes changes here, too.)
+		 */
 		if (!nfs_inode_is_stale(inode,fattr))
 			break;
 
-		remove_inode_hash(inode);
-		nfs_invalidate_inode(inode);
+		dprintk("__nfs_fhget: inode %ld still busy, i_count=%d\n",
+		       inode->i_ino, inode->i_count);
+		/* Mark the inode as being stale */
+		NFS_FLAGS(inode) |= NFS_INO_STALE;
+		nfs_zap_caches(inode);
 		iput(inode);
-		inode = NULL;
 	}
+
+	if (!inode)
+		goto out_no_inode;
+
 	nfs_fill_inode(inode, fattr);
 	dprintk("NFS: __nfs_fhget(%x/%ld ct=%d)\n",
 		inode->i_dev, inode->i_ino, inode->i_count);
@@ -634,10 +866,7 @@
 	return inode;
 
 out_no_inode:
-	printk("__nfs_fhget: iget failed\n");
-	goto out;
-out_bad_id:
-	printk("__nfs_fhget: unexpected inode from iget\n");
+	printk(KERN_NOTICE "__nfs_fhget: iget failed\n");
 	goto out;
 }
 
@@ -645,9 +874,8 @@
 nfs_notify_change(struct dentry *dentry, struct iattr *attr)
 {
 	struct inode *inode = dentry->d_inode;
-	int error;
-	struct nfs_sattr sattr;
 	struct nfs_fattr fattr;
+	int              error;
 
 	/*
 	 * Make sure the inode is up-to-date.
@@ -655,87 +883,123 @@
 	error = nfs_revalidate(dentry);
 	if (error) {
 #ifdef NFS_PARANOIA
-printk("nfs_notify_change: revalidate failed, error=%d\n", error);
+		printk(KERN_DEBUG "nfs_notify_change: revalidate failed, error=%d\n", error);
 #endif
 		goto out;
 	}
 
-	sattr.mode = (u32) -1;
-	if (attr->ia_valid & ATTR_MODE) 
-		sattr.mode = attr->ia_mode;
-
-	sattr.uid = (u32) -1;
-	if (attr->ia_valid & ATTR_UID)
-		sattr.uid = attr->ia_uid;
-
-	sattr.gid = (u32) -1;
-	if (attr->ia_valid & ATTR_GID)
-		sattr.gid = attr->ia_gid;
-
-	sattr.size = (u32) -1;
-	if ((attr->ia_valid & ATTR_SIZE) && S_ISREG(inode->i_mode))
-		sattr.size = attr->ia_size;
-
-	sattr.mtime.seconds = sattr.mtime.useconds = (u32) -1;
-	if (attr->ia_valid & ATTR_MTIME) {
-		sattr.mtime.seconds = attr->ia_mtime;
-		sattr.mtime.useconds = 0;
-	}
-
-	sattr.atime.seconds = sattr.atime.useconds = (u32) -1;
-	if (attr->ia_valid & ATTR_ATIME) {
-		sattr.atime.seconds = attr->ia_atime;
-		sattr.atime.useconds = 0;
-	}
+	if (!S_ISREG(inode->i_mode))
+		attr->ia_valid &= ~ATTR_SIZE;
 
 	error = nfs_wb_all(inode);
-	if (error)
+	if (error < 0)
 		goto out;
 
-	error = nfs_proc_setattr(NFS_DSERVER(dentry), NFS_FH(dentry),
-				&sattr, &fattr);
-	if (error)
+	/* Now perform the setattr call */
+	error = NFS_CALL(setattr, inode, (dentry, &fattr, attr));
+	if (error || !(fattr.valid & NFS_ATTR_FATTR)) {
+		nfs_zap_caches(inode);
 		goto out;
+	}
 	/*
 	 * If we changed the size or mtime, update the inode
 	 * now to avoid invalidating the page cache.
 	 */
-	if (sattr.size != (u32) -1) {
-		if (sattr.size != fattr.size)
-			printk("nfs_notify_change: sattr=%d, fattr=%d??\n",
-				sattr.size, fattr.size);
-		inode->i_size  = sattr.size;
-		inode->i_mtime = fattr.mtime.seconds;
+	if (!(fattr.valid & NFS_ATTR_WCC)) {
+		fattr.pre_size = NFS_CACHE_ISIZE(inode);
+		fattr.pre_mtime = NFS_CACHE_MTIME(inode);
+		fattr.pre_ctime = NFS_CACHE_CTIME(inode);
+		fattr.valid |= NFS_ATTR_WCC;
 	}
-	if (sattr.mtime.seconds != (u32) -1)
-		inode->i_mtime = fattr.mtime.seconds;
 	error = nfs_refresh_inode(inode, &fattr);
 out:
 	return error;
 }
 
+int
+nfs_update_atime(struct dentry *dentry)
+{
+	struct iattr attr;
+	struct inode *inode = dentry->d_inode;
+
+	nfs_revalidate(dentry);
+	if (!inode || time_before(inode->i_atime,nfs_time_to_secs(NFS_CACHE_ATIME(inode))))
+		return 0;
+
+	attr.ia_valid = ATTR_ATIME|ATTR_ATIME_SET;
+	attr.ia_atime = inode->i_atime;
+	return nfs_notify_change(dentry, &attr);
+}
+
 /*
- * Externally visible revalidation function
+ * Wait for the inode to get unlocked.
+ * (Used for NFS_INO_LOCKED and NFS_INO_REVALIDATING).
  */
 int
-nfs_revalidate(struct dentry *dentry)
+nfs_wait_on_inode(struct inode *inode, int flag)
 {
-	return nfs_revalidate_inode(NFS_DSERVER(dentry), dentry);
+	struct rpc_clnt		*clnt = NFS_CLIENT(inode);
+	int error;
+	if (!(NFS_FLAGS(inode) & flag))
+		return 0;
+	inode->i_count++;
+	error = nfs_wait_event(clnt, inode->i_wait, !(NFS_FLAGS(inode) & flag));
+	iput(inode);
+	return error;
 }
 
 /*
- * These are probably going to contain hooks for
- * allocating and releasing RPC credentials for
- * the file. I'll have to think about Tronds patch
- * a bit more..
+ * Externally visible revalidation function
  */
+int
+nfs_revalidate(struct dentry *dentry)
+{
+	return nfs_revalidate_inode(dentry);
+}
+
+static __inline__ struct nfs_file *nfs_file_alloc(void)
+{
+	struct nfs_file	*p;
+	p = kmalloc(sizeof(*p), GFP_KERNEL);
+	if (p) {
+		memset(p, 0, sizeof(*p));
+		p->magic = NFS_FILE_MAGIC;
+	}
+	return p;
+}
+
+static __inline__ void nfs_file_free(struct nfs_file *p)
+{
+	if (p->magic == NFS_FILE_MAGIC) {
+		p->magic = 0;
+		kfree(p);
+	} else
+		printk(KERN_ERR "NFS: extra file info corrupted!\n");
+}
+
 int nfs_open(struct inode *inode, struct file *filp)
 {
+	struct rpc_auth	*auth = NFS_CLIENT(inode)->cl_auth;
+	struct nfs_file	*data;
+
+	data = nfs_file_alloc();
+	if (!data)
+		return -ENOMEM;
+	data->cred = rpcauth_lookupcred(auth, 0);
+	filp->private_data = data;
 	return 0;
 }
 
 int nfs_release(struct inode *inode, struct file *filp)
 {
+	struct nfs_file	*data = NFS_FILE(filp);
+	struct rpc_auth	*auth = NFS_CLIENT(inode)->cl_auth;
+	struct rpc_cred	*cred;
+
+	cred = nfs_file_cred(filp);
+	if (cred)
+		rpcauth_releasecred(auth, cred);
+	nfs_file_free(data);
 	return 0;
 }
 
@@ -744,25 +1008,44 @@
  * the cached attributes have to be refreshed.
  */
 int
-_nfs_revalidate_inode(struct nfs_server *server, struct dentry *dentry)
+__nfs_revalidate_inode(struct dentry *dentry)
 {
 	struct inode	*inode = dentry->d_inode;
-	int		 status = 0;
 	struct nfs_fattr fattr;
+	int		 status = 0;
 
 	dfprintk(PAGECACHE, "NFS: revalidating %s/%s, ino=%ld\n",
 		dentry->d_parent->d_name.name, dentry->d_name.name,
 		inode->i_ino);
-	status = nfs_proc_getattr(server, NFS_FH(dentry), &fattr);
+
+	if (!inode || is_bad_inode(inode))
+		return -ESTALE;
+
+	while (NFS_REVALIDATING(inode)) {
+		status = nfs_wait_on_inode(inode, NFS_INO_REVALIDATING);
+		if (status < 0)
+			return status;
+		if (time_before(jiffies,NFS_READTIME(inode)+NFS_ATTRTIMEO(inode)))
+			return 0;
+	}
+	NFS_FLAGS(inode) |= NFS_INO_REVALIDATING;
+
+	status = NFS_CALL(getattr, inode, (dentry, &fattr));
 	if (status) {
 		int error;
 		u32 *fh;
+		struct dentry *dir = dentry->d_parent;
 		struct nfs_fh fhandle;
+		struct nfs_fattr dir_attr;
+
 		dfprintk(PAGECACHE, "nfs_revalidate_inode: %s/%s getattr failed, ino=%ld, error=%d\n",
-			dentry->d_parent->d_name.name,
-			dentry->d_name.name, inode->i_ino, status);
+		       dentry->d_parent->d_name.name, dentry->d_name.name,
+		       inode->i_ino, status);
+		nfs_zap_caches(inode);
+
 		if (status != -ESTALE)
 			goto out;
+
 		/*
 		 * A "stale filehandle" error ... show the current fh
 		 * and find out what the filehandle should be.
@@ -770,8 +1053,9 @@
 		fh = (u32 *) NFS_FH(dentry);
 		dfprintk(PAGECACHE, "NFS: bad fh %08x%08x%08x%08x%08x%08x%08x%08x\n",
 			fh[0],fh[1],fh[2],fh[3],fh[4],fh[5],fh[6],fh[7]);
-		error = nfs_proc_lookup(server, NFS_FH(dentry->d_parent), 
-					dentry->d_name.name, &fhandle, &fattr);
+		error = NFS_CALL(lookup, dir->d_inode, (dir, &dir_attr, 
+					&dentry->d_name, &fhandle, &fattr));
+		nfs_refresh_inode(dir->d_inode, &dir_attr);
 		if (error) {
 			dfprintk(PAGECACHE, "NFS: lookup failed, error=%d\n", error);
 			goto out;
@@ -787,13 +1071,16 @@
 	status = nfs_refresh_inode(inode, &fattr);
 	if (status) {
 		dfprintk(PAGECACHE, "nfs_revalidate_inode: %s/%s refresh failed, ino=%ld, error=%d\n",
-			dentry->d_parent->d_name.name,
-			dentry->d_name.name, inode->i_ino, status);
+			 dentry->d_parent->d_name.name, dentry->d_name.name,
+			 inode->i_ino, status);
 		goto out;
 	}
+
 	dfprintk(PAGECACHE, "NFS: %s/%s revalidation complete\n",
 		dentry->d_parent->d_name.name, dentry->d_name.name);
 out:
+	NFS_FLAGS(inode) &= ~NFS_INO_REVALIDATING;
+	wake_up(&inode->i_wait);
 	return status;
 }
 
@@ -802,29 +1089,54 @@
  * an operation.  Here we update the inode to reflect the state
  * of the server's inode.
  *
- * This is a bit tricky because we have to make sure all dirty pages
- * have been sent off to the server before calling invalidate_inode_pages.
- * To make sure no other process adds more write requests while we try
- * our best to flush them, we make them sleep during the attribute refresh.
+ * If we have reason to believe that any data we cached has become
+ * invalid, we schedule it to be flushed on the next occasion
+ * (i.e. when nfs_revalidate_inode is called).
  *
- * A very similar scenario holds for the dir cache.
+ * The reason we don't do it here is because nfs_refresh_inode can
+ * be called outside of the process context, e.g. from nfs_readpage_result,
+ * which is invoked by rpciod.
  */
 int
 nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
 {
-	int invalid = 0;
-	int error = -EIO;
-
-	dfprintk(VFS, "NFS: refresh_inode(%x/%ld ct=%d)\n",
-		 inode->i_dev, inode->i_ino, inode->i_count);
+	off_t		new_size, new_isize;
+	__u64		new_mtime;
+	int		invalid = 0;
+	int		error = -EIO;
 
 	if (!inode || !fattr) {
-		printk("nfs_refresh_inode: inode or fattr is NULL\n");
+		printk(KERN_ERR "nfs_refresh_inode: inode or fattr is NULL\n");
+		goto out;
+	}
+	if (inode->i_mode == 0) {
+		printk(KERN_ERR "nfs_refresh_inode: empty inode\n");
 		goto out;
 	}
-	if (inode->i_ino != fattr->fileid) {
-		printk("nfs_refresh_inode: mismatch, ino=%ld, fattr=%d\n",
-			inode->i_ino, fattr->fileid);
+
+	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
+		goto out;
+
+	if (is_bad_inode(inode))
+		goto out;
+
+	dfprintk(VFS, "NFS: refresh_inode(%x/%ld ct=%d info=0x%x)\n",
+			inode->i_dev, inode->i_ino, inode->i_count,
+			fattr->valid);
+
+
+	if (NFS_FSID(inode) != fattr->fsid ||
+	    NFS_FILEID(inode) != fattr->fileid) {
+		printk(KERN_ERR "nfs_refresh_inode: inode number mismatch\n"
+		       "expected (0x%lx%08lx/0x%lx%08lx), got (0x%lx%08lx/0x%lx%08lx)\n",
+		       (unsigned long) (NFS_FSID(inode)>>32),
+		       (unsigned long) (NFS_FSID(inode) & 0xFFFFFFFFUL),
+		       (unsigned long) (NFS_FILEID(inode)>>32),
+		       (unsigned long) (NFS_FILEID(inode) & 0xFFFFFFFFUL),
+		       (unsigned long) (fattr->fsid >> 32),
+		       (unsigned long) (fattr->fsid & 0xFFFFFFFFUL),
+		       (unsigned long) (fattr->fileid >> 32),
+		       (unsigned long) (fattr->fileid & 0xFFFFFFFFUL));
 		goto out;
 	}
 
@@ -834,54 +1146,101 @@
 	if ((inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
 		goto out_changed;
 
-	inode->i_mode = fattr->mode;
-	inode->i_nlink = fattr->nlink;
-	inode->i_uid = fattr->uid;
-	inode->i_gid = fattr->gid;
+ 	new_mtime = fattr->mtime;
+	new_size = fattr->size;
+ 	new_isize = nfs_size_to_off_t(fattr->size);
 
-	inode->i_blocks = fattr->blocks;
-	inode->i_atime = fattr->atime.seconds;
-	inode->i_ctime = fattr->ctime.seconds;
+	error = 0;
 
 	/*
 	 * Update the read time so we don't revalidate too often.
 	 */
 	NFS_READTIME(inode) = jiffies;
-	error = 0;
 
 	/*
-	 * If we have pending write-back entries, we don't want
-	 * to look at the size or the mtime the server sends us
-	 * too closely, as we're in the middle of modifying them.
+	 * Note: NFS_CACHE_ISIZE(inode) reflects the state of the cache.
+	 *       NOT inode->i_size!!!
 	 */
-	if (NFS_WRITEBACK(inode))
-		goto out;
-
-	if (inode->i_size != fattr->size) {
+	if (NFS_CACHE_ISIZE(inode) != new_size) {
 #ifdef NFS_DEBUG_VERBOSE
-printk("NFS: size change on %x/%ld\n", inode->i_dev, inode->i_ino);
+		printk(KERN_DEBUG "NFS: isize change on %x/%ld\n", inode->i_dev, inode->i_ino);
 #endif
-		inode->i_size = fattr->size;
 		invalid = 1;
 	}
 
-	if (inode->i_mtime != fattr->mtime.seconds) {
+	/*
+	 * Note: we don't check inode->i_mtime since pipes etc.
+	 *       can change this value in VFS without requiring a
+	 *	 cache revalidation.
+	 */
+	if (NFS_CACHE_MTIME(inode) != new_mtime) {
 #ifdef NFS_DEBUG_VERBOSE
-printk("NFS: mtime change on %x/%ld\n", inode->i_dev, inode->i_ino);
+		printk(KERN_DEBUG "NFS: mtime change on %x/%ld\n", inode->i_dev, inode->i_ino);
 #endif
-		inode->i_mtime = fattr->mtime.seconds;
 		invalid = 1;
 	}
 
-	if (invalid)
-		goto out_invalid;
+	/* Check Weak Cache Consistency data.
+	 * If size and mtime match the pre-operation values, we can
+	 * assume that any attribute changes were caused by our NFS
+         * operation, so there's no need to invalidate the caches.
+         */
+        if ((fattr->valid & NFS_ATTR_WCC)
+	    && NFS_CACHE_ISIZE(inode) == fattr->pre_size
+	    && NFS_CACHE_MTIME(inode) == fattr->pre_mtime) {
+		invalid = 0;
+	}
+
+	/*
+	 * If we have pending writebacks, things can get
+	 * messy.
+	 */
+	if (nfs_have_writebacks(inode) && new_isize < inode->i_size)
+		new_isize = inode->i_size;
+
+	NFS_CACHE_CTIME(inode) = fattr->ctime;
+	inode->i_ctime = nfs_time_to_secs(fattr->ctime);
+	/* If we've been messing around with atime, don't
+	 * update it. Save the server value in NFS_CACHE_ATIME.
+	 */
+	NFS_CACHE_ATIME(inode) = fattr->atime;
+	if (time_before(inode->i_atime, nfs_time_to_secs(fattr->atime)))
+		inode->i_atime = nfs_time_to_secs(fattr->atime);
+
+	NFS_CACHE_MTIME(inode) = new_mtime;
+	inode->i_mtime = nfs_time_to_secs(new_mtime);
+
+	NFS_CACHE_ISIZE(inode) = new_size;
+	inode->i_size = new_isize;
 
+	inode->i_mode = fattr->mode;
+	inode->i_nlink = fattr->nlink;
+	inode->i_uid = fattr->uid;
+	inode->i_gid = fattr->gid;
+
+	if (fattr->valid & NFS_ATTR_FATTR_V3) {
+		/*
+		 * report the blocks in 512byte units
+		 */
+		inode->i_blocks = nfs_calc_block_size(fattr->du.nfs3.used);
+		inode->i_blksize = inode->i_sb->s_blocksize;
+ 	} else {
+ 		inode->i_blocks = fattr->du.nfs2.blocks;
+ 		inode->i_blksize = fattr->du.nfs2.blocksize;
+ 	}
+ 	inode->i_rdev = 0;
+ 	if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode))
+ 		inode->i_rdev = to_kdev_t(fattr->rdev);
+ 
 	/* Update attrtimeo value */
-	if (fattr->mtime.seconds == NFS_OLDMTIME(inode)) {
+	if (!invalid && time_after(jiffies, NFS_ATTRTIMEO_UPDATE(inode)+NFS_ATTRTIMEO(inode))) {
 		if ((NFS_ATTRTIMEO(inode) <<= 1) > NFS_MAXATTRTIMEO(inode))
 			NFS_ATTRTIMEO(inode) = NFS_MAXATTRTIMEO(inode);
+		NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
 	}
-	NFS_OLDMTIME(inode) = fattr->mtime.seconds;
+
+	if (invalid)
+		nfs_zap_caches(inode);
 
 out:
 	return error;
@@ -891,22 +1250,16 @@
 	 * Big trouble! The inode has become a different object.
 	 */
 #ifdef NFS_PARANOIA
-printk("nfs_refresh_inode: inode %ld mode changed, %07o to %07o\n",
-inode->i_ino, inode->i_mode, fattr->mode);
+	printk(KERN_DEBUG "nfs_refresh_inode: inode %ld mode changed, %07o to %07o\n",
+	       inode->i_ino, inode->i_mode, fattr->mode);
 #endif
 	/*
 	 * No need to worry about unhashing the dentry, as the
 	 * lookup validation will know that the inode is bad.
+	 * (But we fall through to invalidate the caches.)
 	 */
 	nfs_invalidate_inode(inode);
 	goto out;
-
-out_invalid:
-#ifdef NFS_DEBUG_VERBOSE
-printk("nfs_refresh_inode: invalidating %ld pages\n", inode->i_nrpages);
-#endif
-	nfs_zap_caches(inode);
-	goto out;
 }
 
 /*
@@ -926,8 +1279,6 @@
 init_nfs_fs(void)
 {
 #ifdef CONFIG_PROC_FS
-	rpc_register_sysctl();
-	rpc_proc_init();
 	rpc_proc_register(&nfs_rpcstat);
 #endif
         return register_filesystem(&nfs_fs_type);
@@ -955,6 +1306,5 @@
 	rpc_proc_unregister("nfs");
 #endif
 	unregister_filesystem(&nfs_fs_type);
-	nfs_free_dircache();
 }
 #endif

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)