From: Trond Myklebust <trond.myklebust@fys.uio.no>

NFSv2/v3/v4: New attribute revalidation code that no longer relies on ctime
for correctness in avoiding races.


---

 fs/inode.c             |    2 
 fs/nfs/dir.c           |  172 +++++++++++++++-----
 fs/nfs/direct.c        |    3 
 fs/nfs/file.c          |    7 
 fs/nfs/inode.c         |  413 +++++++++++++++++++++++++++----------------------
 fs/nfs/nfs3proc.c      |   28 ---
 fs/nfs/nfs4proc.c      |   36 ----
 fs/nfs/proc.c          |   20 --
 fs/nfs/read.c          |    2 
 fs/nfs/unlink.c        |    3 
 fs/nfs/write.c         |   16 +
 include/linux/fs.h     |    2 
 include/linux/nfs_fs.h |   97 ++++++++---
 13 files changed, 477 insertions(+), 324 deletions(-)

diff -puN fs/inode.c~nfs-31-attr fs/inode.c
--- 25/fs/inode.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/inode.c	2004-01-19 13:18:33.000000000 -0800
@@ -1181,6 +1181,8 @@ void inode_update_time(struct inode *ino
 	struct timespec now;
 	int sync_it = 0;
 
+	if (IS_NOCMTIME(inode))
+		return;
 	if (IS_RDONLY(inode))
 		return;
 
diff -puN fs/nfs/dir.c~nfs-31-attr fs/nfs/dir.c
--- 25/fs/nfs/dir.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/dir.c	2004-01-19 13:18:33.000000000 -0800
@@ -139,11 +139,13 @@ int nfs_readdir_filler(nfs_readdir_descr
 	struct file	*file = desc->file;
 	struct inode	*inode = file->f_dentry->d_inode;
 	struct rpc_cred	*cred = nfs_file_cred(file);
+	unsigned long	timestamp;
 	int		error;
 
 	dfprintk(VFS, "NFS: nfs_readdir_filler() reading cookie %Lu into page %lu.\n", (long long)desc->entry->cookie, page->index);
 
  again:
+	timestamp = jiffies;
 	error = NFS_PROTO(inode)->readdir(file->f_dentry, cred, desc->entry->cookie, page,
 					  NFS_SERVER(inode)->dtsize, desc->plus);
 	if (error < 0) {
@@ -157,18 +159,21 @@ int nfs_readdir_filler(nfs_readdir_descr
 		goto error;
 	}
 	SetPageUptodate(page);
+	NFS_FLAGS(inode) |= NFS_INO_INVALID_ATIME;
 	/* Ensure consistent page alignment of the data.
 	 * Note: assumes we have exclusive access to this mapping either
 	 *	 throught inode->i_sem or some other mechanism.
 	 */
-	if (page->index == 0)
+	if (page->index == 0) {
 		invalidate_inode_pages(inode->i_mapping);
+		NFS_I(inode)->readdir_timestamp = timestamp;
+	}
 	unlock_page(page);
 	return 0;
  error:
 	SetPageError(page);
 	unlock_page(page);
-	invalidate_inode_pages(inode->i_mapping);
+	nfs_zap_caches(inode);
 	desc->error = error;
 	return -EIO;
 }
@@ -381,6 +386,7 @@ int uncached_readdir(nfs_readdir_descrip
 						page,
 						NFS_SERVER(inode)->dtsize,
 						desc->plus);
+	NFS_FLAGS(inode) |= NFS_INO_INVALID_ATIME;
 	desc->page = page;
 	desc->ptr = kmap(page);		/* matching kunmap in nfs_do_filldir */
 	if (desc->error >= 0) {
@@ -459,7 +465,15 @@ static int nfs_readdir(struct file *filp
 			}
 			res = 0;
 			break;
-		} else if (res < 0)
+		}
+		if (res == -ETOOSMALL && desc->plus) {
+			NFS_FLAGS(inode) &= ~NFS_INO_ADVISE_RDPLUS;
+			nfs_zap_caches(inode);
+			desc->plus = 0;
+			desc->entry->eof = 0;
+			continue;
+		}
+		if (res < 0)
 			break;
 
 		res = nfs_do_filldir(desc, dirent, filldir);
@@ -481,14 +495,19 @@ static int nfs_readdir(struct file *filp
  * In the case it has, we assume that the dentries are untrustworthy
  * and may need to be looked up again.
  */
-static inline
-int nfs_check_verifier(struct inode *dir, struct dentry *dentry)
+static inline int nfs_check_verifier(struct inode *dir, struct dentry *dentry)
 {
 	if (IS_ROOT(dentry))
 		return 1;
-	if (nfs_revalidate_inode(NFS_SERVER(dir), dir))
+	if ((NFS_FLAGS(dir) & NFS_INO_INVALID_ATTR) != 0
+			|| nfs_attribute_timeout(dir))
 		return 0;
-	return time_after(dentry->d_time, NFS_MTIME_UPDATE(dir));
+	return nfs_verify_change_attribute(dir, (unsigned long)dentry->d_fsdata);
+}
+
+static inline void nfs_set_verifier(struct dentry * dentry, unsigned long verf)
+{
+	dentry->d_fsdata = (void *)verf;
 }
 
 /*
@@ -528,9 +547,7 @@ int nfs_neg_need_reval(struct inode *dir
 	/* Don't revalidate a negative dentry if we're creating a new file */
 	if ((ndflags & LOOKUP_CREATE) && !(ndflags & LOOKUP_CONTINUE))
 		return 0;
-	if (!nfs_check_verifier(dir, dentry))
-		return 1;
-	return time_after(jiffies, dentry->d_time + NFS_ATTRTIMEO(dir));
+	return !nfs_check_verifier(dir, dentry);
 }
 
 /*
@@ -552,6 +569,7 @@ static int nfs_lookup_revalidate(struct 
 	int error;
 	struct nfs_fh fhandle;
 	struct nfs_fattr fattr;
+	unsigned long verifier;
 	int isopen = 0;
 
 	parent = dget_parent(dentry);
@@ -574,6 +592,9 @@ static int nfs_lookup_revalidate(struct 
 		goto out_bad;
 	}
 
+	/* Revalidate parent directory attribute cache */
+	nfs_revalidate_inode(NFS_SERVER(dir), dir);
+
 	/* Force a full look up iff the parent directory has changed */
 	if (nfs_check_verifier(dir, dentry)) {
 		if (nfs_lookup_verify_inode(inode, isopen))
@@ -581,6 +602,12 @@ static int nfs_lookup_revalidate(struct 
 		goto out_valid;
 	}
 
+	/*
+	 * Note: we're not holding inode->i_sem and so may be racing with
+	 * operations that change the directory. We therefore save the
+	 * change attribute *before* we do the RPC call.
+	 */
+	verifier = nfs_save_change_attribute(dir);
 	error = nfs_cached_lookup(dir, dentry, &fhandle, &fattr);
 	if (!error) {
 		if (memcmp(NFS_FH(inode), &fhandle, sizeof(struct nfs_fh))!= 0)
@@ -603,6 +630,7 @@ static int nfs_lookup_revalidate(struct 
 
  out_valid_renew:
 	nfs_renew_times(dentry);
+	nfs_set_verifier(dentry, verifier);
  out_valid:
 	unlock_kernel();
 	dput(parent);
@@ -693,6 +721,8 @@ static struct dentry *nfs_lookup(struct 
 	dentry->d_op = NFS_PROTO(dir)->dentry_ops;
 
 	lock_kernel();
+	/* Revalidate parent directory attribute cache */
+	nfs_revalidate_inode(NFS_SERVER(dir), dir);
 
 	/* If we're doing an exclusive create, optimize away the lookup */
 	if (nfs_is_exclusive_create(dir, nd))
@@ -715,6 +745,7 @@ no_entry:
 	error = 0;
 	d_add(dentry, inode);
 	nfs_renew_times(dentry);
+	nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
 out_unlock:
 	unlock_kernel();
 out:
@@ -768,7 +799,15 @@ static struct dentry *nfs_atomic_lookup(
 
 	/* Open the file on the server */
 	lock_kernel();
-	inode = nfs4_atomic_open(dir, dentry, nd);
+	/* Revalidate parent directory attribute cache */
+	nfs_revalidate_inode(NFS_SERVER(dir), dir);
+
+	if (nd->intent.open.flags & O_CREAT) {
+		nfs_begin_data_update(dir);
+		inode = nfs4_atomic_open(dir, dentry, nd);
+		nfs_end_data_update(dir);
+	} else
+		inode = nfs4_atomic_open(dir, dentry, nd);
 	unlock_kernel();
 	if (IS_ERR(inode)) {
 		error = PTR_ERR(inode);
@@ -790,6 +829,7 @@ static struct dentry *nfs_atomic_lookup(
 no_entry:
 	d_add(dentry, inode);
 	nfs_renew_times(dentry);
+	nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
 out:
 	BUG_ON(error > 0);
 	return ERR_PTR(error);
@@ -801,13 +841,16 @@ static int nfs_open_revalidate(struct de
 {
 	struct dentry *parent = NULL;
 	struct inode *inode = dentry->d_inode;
+	struct inode *dir;
+	unsigned long verifier;
 	int openflags, ret = 0;
 
 	/* NFS only supports OPEN for regular files */
 	if (inode && !S_ISREG(inode->i_mode))
 		goto no_open;
 	parent = dget_parent(dentry);
-	if (!is_atomic_open(parent->d_inode, nd))
+	dir = parent->d_inode;
+	if (!is_atomic_open(dir, nd))
 		goto no_open;
 	openflags = nd->intent.open.flags;
 	if (openflags & O_CREAT) {
@@ -821,8 +864,16 @@ static int nfs_open_revalidate(struct de
 	/* We can't create new files, or truncate existing ones here */
 	openflags &= ~(O_CREAT|O_TRUNC);
 
+	/*
+	 * Note: we're not holding inode->i_sem and so may be racing with
+	 * operations that change the directory. We therefore save the
+	 * change attribute *before* we do the RPC call.
+	 */
 	lock_kernel();
-	ret = nfs4_open_revalidate(parent->d_inode, dentry, openflags);
+	verifier = nfs_save_change_attribute(dir);
+	ret = nfs4_open_revalidate(dir, dentry, openflags);
+	if (!ret)
+		nfs_set_verifier(dentry, verifier);
 	unlock_kernel();
 out:
 	dput(parent);
@@ -869,15 +920,20 @@ int nfs_cached_lookup(struct inode *dir,
 	struct nfs_server *server;
 	struct nfs_entry entry;
 	struct page *page;
-	unsigned long timestamp = NFS_MTIME_UPDATE(dir);
+	unsigned long timestamp;
 	int res;
 
 	if (!NFS_USE_READDIRPLUS(dir))
 		return -ENOENT;
 	server = NFS_SERVER(dir);
-	if (server->flags & NFS_MOUNT_NOAC)
+	/* Don't use readdirplus unless the cache is stable */
+	if ((server->flags & NFS_MOUNT_NOAC) != 0
+			|| nfs_caches_unstable(dir)
+			|| nfs_attribute_timeout(dir))
+		return -ENOENT;
+	if ((NFS_FLAGS(dir) & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA)) != 0)
 		return -ENOENT;
-	nfs_revalidate_inode(server, dir);
+	timestamp = NFS_I(dir)->readdir_timestamp;
 
 	entry.fh = fh;
 	entry.fattr = fattr;
@@ -931,6 +987,7 @@ static int nfs_instantiate(struct dentry
 	if (inode) {
 		d_instantiate(dentry, inode);
 		nfs_renew_times(dentry);
+		nfs_set_verifier(dentry, nfs_save_change_attribute(dentry->d_parent->d_inode));
 		error = 0;
 	}
 	return error;
@@ -969,11 +1026,13 @@ static int nfs_create(struct inode *dir,
 	 * does not pass the create flags.
 	 */
 	lock_kernel();
-	nfs_zap_caches(dir);
+	nfs_begin_data_update(dir);
 	inode = NFS_PROTO(dir)->create(dir, &dentry->d_name, &attr, open_flags);
+	nfs_end_data_update(dir);
 	if (!IS_ERR(inode)) {
 		d_instantiate(dentry, inode);
 		nfs_renew_times(dentry);
+		nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
 		error = 0;
 	} else {
 		error = PTR_ERR(inode);
@@ -1004,9 +1063,10 @@ nfs_mknod(struct inode *dir, struct dent
 	attr.ia_valid = ATTR_MODE;
 
 	lock_kernel();
-	nfs_zap_caches(dir);
+	nfs_begin_data_update(dir);
 	error = NFS_PROTO(dir)->mknod(dir, &dentry->d_name, &attr, rdev,
 					&fhandle, &fattr);
+	nfs_end_data_update(dir);
 	if (!error)
 		error = nfs_instantiate(dentry, &fhandle, &fattr);
 	else
@@ -1041,9 +1101,10 @@ static int nfs_mkdir(struct inode *dir, 
 	 */
 	d_drop(dentry);
 #endif
-	nfs_zap_caches(dir);
+	nfs_begin_data_update(dir);
 	error = NFS_PROTO(dir)->mkdir(dir, &dentry->d_name, &attr, &fhandle,
 					&fattr);
+	nfs_end_data_update(dir);
 	if (!error)
 		error = nfs_instantiate(dentry, &fhandle, &fattr);
 	else
@@ -1060,10 +1121,12 @@ static int nfs_rmdir(struct inode *dir, 
 		dir->i_ino, dentry->d_name.name);
 
 	lock_kernel();
-	nfs_zap_caches(dir);
+	nfs_begin_data_update(dir);
 	error = NFS_PROTO(dir)->rmdir(dir, &dentry->d_name);
-	if (!error)
+	/* Ensure the VFS deletes this inode */
+	if (error == 0 && dentry->d_inode != NULL)
 		dentry->d_inode->i_nlink = 0;
+	nfs_end_data_update(dir);
 	unlock_kernel();
 
 	return error;
@@ -1119,12 +1182,21 @@ dentry->d_parent->d_name.name, dentry->d
 			goto out;
 	} while(sdentry->d_inode != NULL); /* need negative lookup */
 
-	nfs_zap_caches(dir);
 	qsilly.name = silly;
 	qsilly.len  = strlen(silly);
-	error = NFS_PROTO(dir)->rename(dir, &dentry->d_name, dir, &qsilly);
+	nfs_begin_data_update(dir);
+	if (dentry->d_inode) {
+		nfs_begin_data_update(dentry->d_inode);
+		error = NFS_PROTO(dir)->rename(dir, &dentry->d_name,
+				dir, &qsilly);
+		nfs_end_data_update(dentry->d_inode);
+	} else
+		error = NFS_PROTO(dir)->rename(dir, &dentry->d_name,
+				dir, &qsilly);
+	nfs_end_data_update(dir);
 	if (!error) {
 		nfs_renew_times(dentry);
+		nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
 		d_move(dentry, sdentry);
 		error = nfs_async_unlink(dentry);
  		/* If we return 0 we don't unlink */
@@ -1156,14 +1228,17 @@ static int nfs_safe_remove(struct dentry
 		goto out;
 	}
 
-	nfs_zap_caches(dir);
-	if (inode)
-		NFS_CACHEINV(inode);
-	error = NFS_PROTO(dir)->remove(dir, &dentry->d_name);
-	if (error < 0)
-		goto out;
-	if (inode)
-		inode->i_nlink--;
+	nfs_begin_data_update(dir);
+	if (inode != NULL) {
+		nfs_begin_data_update(inode);
+		error = NFS_PROTO(dir)->remove(dir, &dentry->d_name);
+		/* The VFS may want to delete this inode */
+		if (error == 0)
+			inode->i_nlink--;
+		nfs_end_data_update(inode);
+	} else
+		error = NFS_PROTO(dir)->remove(dir, &dentry->d_name);
+	nfs_end_data_update(dir);
 out:
 	return error;
 }
@@ -1198,9 +1273,10 @@ static int nfs_unlink(struct inode *dir,
 	spin_unlock(&dentry->d_lock);
 	spin_unlock(&dcache_lock);
 	error = nfs_safe_remove(dentry);
-	if (!error)
+	if (!error) {
 		nfs_renew_times(dentry);
-	else if (need_rehash)
+		nfs_set_verifier(dentry, nfs_save_change_attribute(dir));
+	} else if (need_rehash)
 		d_rehash(dentry);
 	unlock_kernel();
 	return error;
@@ -1247,9 +1323,10 @@ dentry->d_parent->d_name.name, dentry->d
 	qsymname.len  = strlen(symname);
 
 	lock_kernel();
-	nfs_zap_caches(dir);
+	nfs_begin_data_update(dir);
 	error = NFS_PROTO(dir)->symlink(dir, &dentry->d_name, &qsymname,
 					  &attr, &sym_fh, &sym_attr);
+	nfs_end_data_update(dir);
 	if (!error) {
 		error = nfs_instantiate(dentry, &sym_fh, &sym_attr);
 	} else {
@@ -1281,9 +1358,12 @@ nfs_link(struct dentry *old_dentry, stru
 	 */
 	lock_kernel();
 	d_drop(dentry);
-	nfs_zap_caches(dir);
-	NFS_CACHEINV(inode);
+
+	nfs_begin_data_update(dir);
+	nfs_begin_data_update(inode);
 	error = NFS_PROTO(dir)->link(inode, dir, &dentry->d_name);
+	nfs_end_data_update(inode);
+	nfs_end_data_update(dir);
 	unlock_kernel();
 	return error;
 }
@@ -1388,16 +1468,23 @@ go_ahead:
 	if (new_inode)
 		d_delete(new_dentry);
 
-	nfs_zap_caches(new_dir);
-	nfs_zap_caches(old_dir);
+	nfs_begin_data_update(old_dir);
+	nfs_begin_data_update(new_dir);
+	nfs_begin_data_update(old_inode);
 	error = NFS_PROTO(old_dir)->rename(old_dir, &old_dentry->d_name,
 					   new_dir, &new_dentry->d_name);
+	nfs_end_data_update(old_inode);
+	nfs_end_data_update(new_dir);
+	nfs_end_data_update(old_dir);
 out:
 	if (rehash)
 		d_rehash(rehash);
-	if (!error && !S_ISDIR(old_inode->i_mode))
-		d_move(old_dentry, new_dentry);
-	nfs_renew_times(new_dentry);
+	if (!error) {
+		if (!S_ISDIR(old_inode->i_mode))
+			d_move(old_dentry, new_dentry);
+		nfs_renew_times(new_dentry);
+		nfs_set_verifier(new_dentry, nfs_save_change_attribute(new_dir));
+	}
 
 	/* new dentry created? */
 	if (dentry)
@@ -1451,7 +1538,8 @@ nfs_permission(struct inode *inode, int 
 
 	cred = rpcauth_lookupcred(NFS_CLIENT(inode)->cl_auth, 0);
 	if (cache->cred == cred
-	    && time_before(jiffies, cache->jiffies + NFS_ATTRTIMEO(inode))) {
+	    && time_before(jiffies, cache->jiffies + NFS_ATTRTIMEO(inode))
+	    && !(NFS_FLAGS(inode) & NFS_INO_INVALID_ATTR)) {
 		if (!(res = cache->err)) {
 			/* Is the mask a subset of an accepted mask? */
 			if ((cache->mask & mask) == mask)
diff -puN fs/nfs/direct.c~nfs-31-attr fs/nfs/direct.c
--- 25/fs/nfs/direct.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/direct.c	2004-01-19 13:18:33.000000000 -0800
@@ -269,6 +269,7 @@ nfs_direct_write_seg(struct inode *inode
 	if (IS_SYNC(inode) || NFS_PROTO(inode)->version == 2 || count <= wsize)
 		wdata.args.stable = NFS_FILE_SYNC;
 
+	nfs_begin_data_update(inode);
 retry:
 	need_commit = 0;
 	tot_bytes = 0;
@@ -334,6 +335,8 @@ retry:
 						VERF_SIZE) != 0)
 			goto sync_retry;
 	}
+	nfs_end_data_update(inode);
+	NFS_FLAGS(inode) |= NFS_INO_INVALID_DATA;
 
 	return tot_bytes;
 
diff -puN fs/nfs/file.c~nfs-31-attr fs/nfs/file.c
--- 25/fs/nfs/file.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/file.c	2004-01-19 13:18:33.000000000 -0800
@@ -104,11 +104,16 @@ nfs_file_flush(struct file *file)
 
 	dfprintk(VFS, "nfs: flush(%s/%ld)\n", inode->i_sb->s_id, inode->i_ino);
 
+	if ((file->f_mode & FMODE_WRITE) == 0)
+		return 0;
 	lock_kernel();
-	status = nfs_wb_file(inode, file);
+	/* Ensure that data+attribute caches are up to date after close() */
+	status = nfs_wb_all(inode);
 	if (!status) {
 		status = file->f_error;
 		file->f_error = 0;
+		if (!status)
+			__nfs_revalidate_inode(NFS_SERVER(inode), inode);
 	}
 	unlock_kernel();
 	return status;
diff -puN fs/nfs/inode.c~nfs-31-attr fs/nfs/inode.c
--- 25/fs/nfs/inode.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/inode.c	2004-01-19 13:18:33.000000000 -0800
@@ -53,8 +53,8 @@
  */
 #define NFS_MAX_READAHEAD	RPC_MAXREQS
 
-void nfs_zap_caches(struct inode *);
 static void nfs_invalidate_inode(struct inode *);
+static int nfs_update_inode(struct inode *, struct nfs_fattr *, unsigned long);
 
 static struct inode *nfs_alloc_inode(struct super_block *sb);
 static void nfs_destroy_inode(struct inode *);
@@ -151,6 +151,7 @@ nfs_clear_inode(struct inode *inode)
 	cred = nfsi->cache_access.cred;
 	if (cred)
 		put_rpccred(cred);
+	BUG_ON(atomic_read(&nfsi->data_updates) != 0);
 }
 
 void
@@ -627,13 +628,17 @@ static int nfs_show_options(struct seq_f
 void
 nfs_zap_caches(struct inode *inode)
 {
+	struct nfs_inode *nfsi = NFS_I(inode);
+	int mode = inode->i_mode;
+
 	NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
 	NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
 
-	invalidate_remote_inode(inode);
-
 	memset(NFS_COOKIEVERF(inode), 0, sizeof(NFS_COOKIEVERF(inode)));
-	NFS_CACHEINV(inode);
+	if (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))
+		nfsi->flags |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+	else
+		nfsi->flags |= NFS_INO_INVALID_ATTR;
 }
 
 /*
@@ -673,9 +678,6 @@ nfs_find_actor(struct inode *inode, void
 		return 0;
 	if (is_bad_inode(inode))
 		return 0;
-	/* Force an attribute cache update if inode->i_count == 0 */
-	if (!atomic_read(&inode->i_count))
-		NFS_CACHEINV(inode);
 	return 1;
 }
 
@@ -729,7 +731,7 @@ nfs_fhget(struct super_block *sb, struct
 		inode->i_ino = hash;
 
 		/* We can't support update_atime(), since the server will reset it */
-		inode->i_flags |= S_NOATIME;
+		inode->i_flags |= S_NOATIME|S_NOCMTIME;
 		inode->i_mode = fattr->mode;
 		/* Why so? Because we want revalidate for devices/FIFOs, and
 		 * that's precisely what we have in nfs_file_inode_operations.
@@ -754,10 +756,6 @@ nfs_fhget(struct super_block *sb, struct
 		inode->i_atime = fattr->atime;
 		inode->i_mtime = fattr->mtime;
 		inode->i_ctime = fattr->ctime;
-		nfsi->read_cache_ctime = fattr->ctime;
-		nfsi->read_cache_mtime = fattr->mtime;
-		nfsi->cache_mtime_jiffies = fattr->timestamp;
-		nfsi->read_cache_isize = fattr->size;
 		if (fattr->valid & NFS_ATTR_FATTR_V4)
 			nfsi->change_attr = fattr->change_attr;
 		inode->i_size = nfs_size_to_loff_t(fattr->size);
@@ -804,70 +802,50 @@ nfs_setattr(struct dentry *dentry, struc
 	struct nfs_fattr fattr;
 	int error;
 
+	if (attr->ia_valid & ATTR_SIZE) {
+		if (!S_ISREG(inode->i_mode) || attr->ia_size == i_size_read(inode))
+			attr->ia_valid &= ~ATTR_SIZE;
+	}
+
 	/* Optimization: if the end result is no change, don't RPC */
 	attr->ia_valid &= NFS_VALID_ATTRS;
 	if (attr->ia_valid == 0)
 		return 0;
 
 	lock_kernel();
-
-	/*
-	 * Make sure the inode is up-to-date.
-	 */
-	error = nfs_revalidate_inode(NFS_SERVER(inode),inode);
-	if (error) {
-#ifdef NFS_PARANOIA
-printk("nfs_setattr: revalidate failed, error=%d\n", error);
-#endif
-		goto out;
+	nfs_begin_data_update(inode);
+	/* Write all dirty data if we're changing file permissions or size */
+	if ((attr->ia_valid & (ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_SIZE)) != 0) {
+		if (filemap_fdatawrite(inode->i_mapping) == 0)
+			filemap_fdatawait(inode->i_mapping);
+		nfs_wb_all(inode);
 	}
-
-	if (!S_ISREG(inode->i_mode)) {
-		attr->ia_valid &= ~ATTR_SIZE;
-		if (attr->ia_valid == 0)
-			goto out;
-	} else {
-		filemap_fdatawrite(inode->i_mapping);
-		error = nfs_wb_all(inode);
-		filemap_fdatawait(inode->i_mapping);
-		if (error)
-			goto out;
-		/* Optimize away unnecessary truncates */
-		if ((attr->ia_valid & ATTR_SIZE) && i_size_read(inode) == attr->ia_size)
-			attr->ia_valid &= ~ATTR_SIZE;
-	}
-	if (!attr->ia_valid)
-		goto out;
-
 	error = NFS_PROTO(inode)->setattr(dentry, &fattr, attr);
-	if (error)
-		goto out;
-	/*
-	 * If we changed the size or mtime, update the inode
-	 * now to avoid invalidating the page cache.
-	 */
-	if (attr->ia_valid & ATTR_SIZE) {
-		if (attr->ia_size != fattr.size)
-			printk("nfs_setattr: attr=%Ld, fattr=%Ld??\n",
-			       (long long) attr->ia_size, (long long)fattr.size);
-		vmtruncate(inode, attr->ia_size);
+	if (error == 0) {
+		nfs_refresh_inode(inode, &fattr);
+		if ((attr->ia_valid & ATTR_MODE) != 0) {
+			int mode;
+			mode = inode->i_mode & ~S_IALLUGO;
+			mode |= attr->ia_mode & S_IALLUGO;
+			inode->i_mode = mode;
+		}
+		if ((attr->ia_valid & ATTR_UID) != 0)
+			inode->i_uid = attr->ia_uid;
+		if ((attr->ia_valid & ATTR_GID) != 0)
+			inode->i_gid = attr->ia_gid;
+		if ((attr->ia_valid & ATTR_SIZE) != 0) {
+			i_size_write(inode, attr->ia_size);
+			vmtruncate(inode, attr->ia_size);
+		}
 	}
-
-	/*
-	 * If we changed the size or mtime, update the inode
-	 * now to avoid invalidating the page cache.
-	 */
-	if (!(fattr.valid & NFS_ATTR_WCC)) {
-		struct nfs_inode *nfsi = NFS_I(inode);
-		fattr.pre_size = nfsi->read_cache_isize;
-		fattr.pre_mtime = nfsi->read_cache_mtime;
-		fattr.pre_ctime = nfsi->read_cache_ctime;
-		fattr.valid |= NFS_ATTR_WCC;
-	}
-	/* Force an attribute cache update */
-	NFS_CACHEINV(inode);
-	error = nfs_refresh_inode(inode, &fattr);
-out:
+	if ((attr->ia_valid & (ATTR_MODE|ATTR_UID|ATTR_GID)) != 0) {
+		struct rpc_cred **cred = &NFS_I(inode)->cache_access.cred;
+		if (*cred) {
+			put_rpccred(*cred);
+			*cred = NULL;
+		}
+	}
+	nfs_end_data_update(inode);
 	unlock_kernel();
 	return error;
 }
@@ -895,7 +873,19 @@ nfs_wait_on_inode(struct inode *inode, i
 int nfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
 {
 	struct inode *inode = dentry->d_inode;
-	int err = nfs_revalidate_inode(NFS_SERVER(inode), inode);
+	struct nfs_inode *nfsi = NFS_I(inode);
+	int need_atime = nfsi->flags & NFS_INO_INVALID_ATIME;
+	int err;
+
+	if (__IS_FLG(inode, MS_NOATIME))
+		need_atime = 0;
+	else if (__IS_FLG(inode, MS_NODIRATIME) && S_ISDIR(inode->i_mode))
+		need_atime = 0;
+	/* We may force a getattr if the user cares about atime */
+	if (need_atime)
+		err = __nfs_revalidate_inode(NFS_SERVER(inode), inode);
+	else
+		err = nfs_revalidate_inode(NFS_SERVER(inode), inode);
 	if (!err)
 		generic_fillattr(inode, stat);
 	return err;
@@ -930,8 +920,10 @@ int nfs_open(struct inode *inode, struct
 	auth = NFS_CLIENT(inode)->cl_auth;
 	cred = rpcauth_lookupcred(auth, 0);
 	filp->private_data = cred;
-	if (filp->f_mode & FMODE_WRITE)
+	if ((filp->f_mode & FMODE_WRITE) != 0) {
 		nfs_set_mmcred(inode, cred);
+		nfs_begin_data_update(inode);
+	}
 	return 0;
 }
 
@@ -940,6 +932,8 @@ int nfs_release(struct inode *inode, str
 	struct rpc_cred *cred;
 
 	lock_kernel();
+	if ((filp->f_mode & FMODE_WRITE) != 0)
+		nfs_end_data_update(inode);
 	cred = nfs_file_cred(filp);
 	if (cred)
 		put_rpccred(cred);
@@ -956,6 +950,9 @@ __nfs_revalidate_inode(struct nfs_server
 {
 	int		 status = -ESTALE;
 	struct nfs_fattr fattr;
+	struct nfs_inode *nfsi = NFS_I(inode);
+	unsigned long verifier;
+	unsigned int flags;
 
 	dfprintk(PAGECACHE, "NFS: revalidating (%s/%Ld)\n",
 		inode->i_sb->s_id, (long long)NFS_FILEID(inode));
@@ -965,23 +962,16 @@ __nfs_revalidate_inode(struct nfs_server
  		goto out_nowait;
 	if (NFS_STALE(inode) && inode != inode->i_sb->s_root->d_inode)
  		goto out_nowait;
-	if (NFS_FAKE_ROOT(inode)) {
-		dfprintk(VFS, "NFS: not revalidating fake root\n");
-		status = 0;
-		goto out_nowait;
-	}
 
 	while (NFS_REVALIDATING(inode)) {
 		status = nfs_wait_on_inode(inode, NFS_INO_REVALIDATING);
 		if (status < 0)
 			goto out_nowait;
-		if (time_before(jiffies,NFS_READTIME(inode)+NFS_ATTRTIMEO(inode))) {
-			status = NFS_STALE(inode) ? -ESTALE : 0;
-			goto out_nowait;
-		}
 	}
 	NFS_FLAGS(inode) |= NFS_INO_REVALIDATING;
 
+	/* Protect against RPC races by saving the change attribute */
+	verifier = nfs_save_change_attribute(inode);
 	status = NFS_PROTO(inode)->getattr(inode, &fattr);
 	if (status) {
 		dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Ld) getattr failed, error=%d\n",
@@ -995,13 +985,34 @@ __nfs_revalidate_inode(struct nfs_server
 		goto out;
 	}
 
-	status = nfs_refresh_inode(inode, &fattr);
+	status = nfs_update_inode(inode, &fattr, verifier);
 	if (status) {
 		dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Ld) refresh failed, error=%d\n",
 			 inode->i_sb->s_id,
 			 (long long)NFS_FILEID(inode), status);
 		goto out;
 	}
+	flags = nfsi->flags;
+	/*
+	 * We may need to keep the attributes marked as invalid if
+	 * we raced with nfs_end_attr_update().
+	 */
+	if (verifier == nfsi->cache_change_attribute)
+		nfsi->flags &= ~(NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME);
+	/* Do the page cache invalidation */
+	if (flags & NFS_INO_INVALID_DATA) {
+		if (S_ISREG(inode->i_mode)) {
+			if (filemap_fdatawrite(inode->i_mapping) == 0)
+				filemap_fdatawait(inode->i_mapping);
+			nfs_wb_all(inode);
+		}
+		nfsi->flags &= ~NFS_INO_INVALID_DATA;
+		invalidate_inode_pages2(inode->i_mapping);
+		memset(NFS_COOKIEVERF(inode), 0, sizeof(NFS_COOKIEVERF(inode)));
+		dfprintk(PAGECACHE, "NFS: (%s/%Ld) data cache invalidated\n",
+				inode->i_sb->s_id,
+				(long long)NFS_FILEID(inode));
+	}
 	dfprintk(PAGECACHE, "NFS: (%s/%Ld) revalidation complete\n",
 		inode->i_sb->s_id,
 		(long long)NFS_FILEID(inode));
@@ -1009,41 +1020,104 @@ __nfs_revalidate_inode(struct nfs_server
 	NFS_FLAGS(inode) &= ~NFS_INO_STALE;
 out:
 	NFS_FLAGS(inode) &= ~NFS_INO_REVALIDATING;
-	wake_up(&NFS_I(inode)->nfs_i_wait);
+	wake_up(&nfsi->nfs_i_wait);
  out_nowait:
 	unlock_kernel();
 	return status;
 }
 
-/*
- * nfs_fattr_obsolete - Test if attribute data is newer than cached data
- * @inode: inode
- * @fattr: attributes to test
+/**
+ * nfs_begin_data_update
+ * @inode - pointer to inode
+ * Declare that a set of operations will update file data on the server
+ */
+void nfs_begin_data_update(struct inode *inode)
+{
+	atomic_inc(&NFS_I(inode)->data_updates);
+}
+
+/**
+ * nfs_end_data_update
+ * @inode - pointer to inode
+ * Declare end of the operations that will update file data
+ */
+void nfs_end_data_update(struct inode *inode)
+{
+	struct nfs_inode *nfsi = NFS_I(inode);
+
+	if (atomic_dec_and_test(&nfsi->data_updates)) {
+		nfsi->cache_change_attribute ++;
+		/* Mark the attribute cache for revalidation */
+		nfsi->flags |= NFS_INO_INVALID_ATTR;
+		/* Directories and symlinks: invalidate page cache too */
+		if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
+			nfsi->flags |= NFS_INO_INVALID_DATA;
+	}
+}
+
+/**
+ * nfs_refresh_inode - verify consistency of the inode attribute cache
+ * @inode - pointer to inode
+ * @fattr - updated attributes
  *
- * Avoid stuffing the attribute cache with obsolete information.
- * We always accept updates if the attribute cache timed out, or if
- * fattr->ctime is newer than our cached value.
- * If fattr->ctime matches the cached value, we still accept the update
- * if it increases the file size.
+ * Verifies the attribute cache. If we have just changed the attributes,
+ * so that fattr carries weak cache consistency data, then it may
+ * also update the ctime/mtime/change_attribute.
  */
-static inline
-int nfs_fattr_obsolete(struct inode *inode, struct nfs_fattr *fattr)
+int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
 {
 	struct nfs_inode *nfsi = NFS_I(inode);
-	long cdif;
+	loff_t cur_size, new_isize;
+	int data_unstable;
+
+	/* Are we in the process of updating data on the server? */
+	data_unstable = nfs_caches_unstable(inode);
+
+	if (fattr->valid & NFS_ATTR_FATTR_V4) {
+		if ((fattr->valid & NFS_ATTR_PRE_CHANGE) != 0
+				&& nfsi->change_attr == fattr->pre_change_attr)
+			nfsi->change_attr = fattr->change_attr;
+		if (!data_unstable && nfsi->change_attr != fattr->change_attr)
+			nfsi->flags |= NFS_INO_INVALID_ATTR;
+	}
+
+	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
+		return 0;
 
-	if (time_after(jiffies, nfsi->read_cache_jiffies + nfsi->attrtimeo))
-		goto out_valid;
-	cdif = fattr->ctime.tv_sec - nfsi->read_cache_ctime.tv_sec;
-	if (cdif == 0)
-		cdif = fattr->ctime.tv_nsec - nfsi->read_cache_ctime.tv_nsec;
-	if (cdif > 0)
-		goto out_valid;
-	/* Ugh... */
-	if (cdif == 0 && fattr->size > nfsi->read_cache_isize)
-		goto out_valid;
-	return -1;
- out_valid:
+	/* Has the inode gone and changed behind our back? */
+	if (nfsi->fileid != fattr->fileid
+			|| (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
+		return -EIO;
+
+	cur_size = i_size_read(inode);
+ 	new_isize = nfs_size_to_loff_t(fattr->size);
+
+	/* If we have atomic WCC data, we may update some attributes */
+	if ((fattr->valid & NFS_ATTR_WCC) != 0) {
+		if (timespec_equal(&inode->i_ctime, &fattr->pre_ctime))
+			memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
+		if (timespec_equal(&inode->i_mtime, &fattr->pre_mtime))
+			memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
+	}
+
+	/* Verify a few of the more important attributes */
+	if (!data_unstable) {
+		if (!timespec_equal(&inode->i_mtime, &fattr->mtime)
+				|| cur_size != new_isize)
+			nfsi->flags |= NFS_INO_INVALID_ATTR;
+	} else if (S_ISREG(inode->i_mode) && new_isize > cur_size)
+			nfsi->flags |= NFS_INO_INVALID_ATTR;
+
+	/* Have any file permissions changed? */
+	if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO)
+			|| inode->i_uid != fattr->uid
+			|| inode->i_gid != fattr->gid)
+		nfsi->flags |= NFS_INO_INVALID_ATTR;
+
+	if (!timespec_equal(&inode->i_atime, &fattr->atime))
+		nfsi->flags |= NFS_INO_INVALID_ATIME;
+
+	nfsi->read_cache_jiffies = fattr->timestamp;
 	return 0;
 }
 
@@ -1059,20 +1133,22 @@ int nfs_fattr_obsolete(struct inode *ino
  *
  * A very similar scenario holds for the dir cache.
  */
-int
-__nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
+static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr, unsigned long verifier)
 {
 	struct nfs_inode *nfsi = NFS_I(inode);
 	__u64		new_size;
 	loff_t		new_isize;
-	int		invalid = 0;
-	int		mtime_update = 0;
+	unsigned int	invalid = 0;
 	loff_t		cur_isize;
+	int data_unstable;
 
-	dfprintk(VFS, "NFS: refresh_inode(%s/%ld ct=%d info=0x%x)\n",
-			inode->i_sb->s_id, inode->i_ino,
+	dfprintk(VFS, "NFS: %s(%s/%ld ct=%d info=0x%x)\n",
+			__FUNCTION__, inode->i_sb->s_id, inode->i_ino,
 			atomic_read(&inode->i_count), fattr->valid);
 
+	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
+		return 0;
+
 	/* First successful call after mount, fill real data. */
 	if (NFS_FAKE_ROOT(inode)) {
 		dfprintk(VFS, "NFS: updating fake root\n");
@@ -1081,43 +1157,49 @@ __nfs_refresh_inode(struct inode *inode,
 	}
 
 	if (nfsi->fileid != fattr->fileid) {
-		printk(KERN_ERR "nfs_refresh_inode: inode number mismatch\n"
+		printk(KERN_ERR "%s: inode number mismatch\n"
 		       "expected (%s/0x%Lx), got (%s/0x%Lx)\n",
+		       __FUNCTION__,
 		       inode->i_sb->s_id, (long long)nfsi->fileid,
 		       inode->i_sb->s_id, (long long)fattr->fileid);
 		goto out_err;
 	}
 
-	/* Throw out obsolete READDIRPLUS attributes */
-	if (time_before(fattr->timestamp, NFS_READTIME(inode)))
-		return 0;
 	/*
 	 * Make sure the inode's type hasn't changed.
 	 */
 	if ((inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
 		goto out_changed;
 
-	new_size = fattr->size;
- 	new_isize = nfs_size_to_loff_t(fattr->size);
-
-	/* Avoid races */
-	if (nfs_fattr_obsolete(inode, fattr))
-		goto out_nochange;
-
 	/*
 	 * Update the read time so we don't revalidate too often.
 	 */
 	nfsi->read_cache_jiffies = fattr->timestamp;
 
-	/*
-	 * Note: NFS_CACHE_ISIZE(inode) reflects the state of the cache.
-	 *       NOT inode->i_size!!!
-	 */
-	if (nfsi->read_cache_isize != new_size) {
+	/* Are we racing with known updates of the metadata on the server? */
+	data_unstable = ! nfs_verify_change_attribute(inode, verifier);
+
+	/* Check if the file size agrees */
+	new_size = fattr->size;
+ 	new_isize = nfs_size_to_loff_t(fattr->size);
+	cur_isize = i_size_read(inode);
+	if (cur_isize != new_size) {
 #ifdef NFS_DEBUG_VERBOSE
 		printk(KERN_DEBUG "NFS: isize change on %s/%ld\n", inode->i_sb->s_id, inode->i_ino);
 #endif
-		invalid = 1;
+		/*
+		 * If we have pending writebacks, things can get
+		 * messy.
+		 */
+		if (S_ISREG(inode->i_mode) && data_unstable) {
+			if (new_isize > cur_isize) {
+				i_size_write(inode, new_isize);
+				invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+			}
+		} else {
+			i_size_write(inode, new_isize);
+			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+		}
 	}
 
 	/*
@@ -1125,12 +1207,13 @@ __nfs_refresh_inode(struct inode *inode,
 	 *       can change this value in VFS without requiring a
 	 *	 cache revalidation.
 	 */
-	if (!timespec_equal(&nfsi->read_cache_mtime, &fattr->mtime)) {
+	if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) {
+		memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
 #ifdef NFS_DEBUG_VERBOSE
 		printk(KERN_DEBUG "NFS: mtime change on %s/%ld\n", inode->i_sb->s_id, inode->i_ino);
 #endif
-		invalid = 1;
-		mtime_update = 1;
+		if (!data_unstable)
+			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
 	}
 
 	if ((fattr->valid & NFS_ATTR_FATTR_V4)
@@ -1139,47 +1222,15 @@ __nfs_refresh_inode(struct inode *inode,
 		printk(KERN_DEBUG "NFS: change_attr change on %s/%ld\n",
 		       inode->i_sb->s_id, inode->i_ino);
 #endif
-		invalid = 1;
-	}
-
-	/* 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_PRE_CHANGE)
-	    && nfsi->change_attr == fattr->pre_change_attr) {
-		invalid = 0;
-	}
-	else if ((fattr->valid & NFS_ATTR_WCC)
-	    && nfsi->read_cache_isize == fattr->pre_size
-	    && timespec_equal(&nfsi->read_cache_mtime, &fattr->pre_mtime)) {
-		invalid = 0;
-	}
-
-	/*
-	 * If we have pending writebacks, things can get
-	 * messy.
-	 */
-	cur_isize = i_size_read(inode);
-	if (nfs_have_writebacks(inode) && new_isize < cur_isize)
-		new_isize = cur_isize;
-
-	nfsi->read_cache_ctime = fattr->ctime;
-	inode->i_ctime = fattr->ctime;
-	inode->i_atime = fattr->atime;
-
-	if (mtime_update) {
-		if (invalid)
-			nfsi->cache_mtime_jiffies = fattr->timestamp;
-		nfsi->read_cache_mtime = fattr->mtime;
-		inode->i_mtime = fattr->mtime;
+		nfsi->change_attr = fattr->change_attr;
+		if (!data_unstable)
+			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
 	}
 
-	nfsi->read_cache_isize = new_size;
-	i_size_write(inode, new_isize);
+	memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
+	memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime));
 
-	if (inode->i_mode != fattr->mode ||
+	if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO) ||
 	    inode->i_uid != fattr->uid ||
 	    inode->i_gid != fattr->gid) {
 		struct rpc_cred **cred = &NFS_I(inode)->cache_access.cred;
@@ -1187,11 +1238,9 @@ __nfs_refresh_inode(struct inode *inode,
 			put_rpccred(*cred);
 			*cred = NULL;
 		}
+		invalid |= NFS_INO_INVALID_ATTR;
 	}
 
-	if (fattr->valid & NFS_ATTR_FATTR_V4)
-		nfsi->change_attr = fattr->change_attr;
-
 	inode->i_mode = fattr->mode;
 	inode->i_nlink = fattr->nlink;
 	inode->i_uid = fattr->uid;
@@ -1207,31 +1256,30 @@ __nfs_refresh_inode(struct inode *inode,
  		inode->i_blocks = fattr->du.nfs2.blocks;
  		inode->i_blksize = fattr->du.nfs2.blocksize;
  	}
- 
-	/* Update attrtimeo value */
-	if (invalid) {
+
+	/* Update attrtimeo value if we're out of the unstable period */
+	if (invalid & NFS_INO_INVALID_ATTR) {
 		nfsi->attrtimeo = NFS_MINATTRTIMEO(inode);
 		nfsi->attrtimeo_timestamp = jiffies;
-		invalidate_remote_inode(inode);
-		memset(NFS_COOKIEVERF(inode), 0, sizeof(NFS_COOKIEVERF(inode)));
 	} else if (time_after(jiffies, nfsi->attrtimeo_timestamp+nfsi->attrtimeo)) {
 		if ((nfsi->attrtimeo <<= 1) > NFS_MAXATTRTIMEO(inode))
 			nfsi->attrtimeo = NFS_MAXATTRTIMEO(inode);
 		nfsi->attrtimeo_timestamp = jiffies;
 	}
+	/* Don't invalidate the data if we were to blame */
+	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)
+				|| S_ISLNK(inode->i_mode)))
+		invalid &= ~NFS_INO_INVALID_DATA;
+	nfsi->flags |= invalid;
 
 	return 0;
- out_nochange:
-	if (!timespec_equal(&fattr->atime, &inode->i_atime))
-		inode->i_atime = fattr->atime;
-	return 0;
  out_changed:
 	/*
 	 * Big trouble! The inode has become a different object.
 	 */
 #ifdef NFS_PARANOIA
-	printk(KERN_DEBUG "nfs_refresh_inode: inode %ld mode changed, %07o to %07o\n",
-	       inode->i_ino, inode->i_mode, fattr->mode);
+	printk(KERN_DEBUG "%s: inode %ld mode changed, %07o to %07o\n",
+			__FUNCTION__, inode->i_ino, inode->i_mode, fattr->mode);
 #endif
 	/*
 	 * No need to worry about unhashing the dentry, as the
@@ -1718,6 +1766,7 @@ static void init_once(void * foo, kmem_c
 		INIT_LIST_HEAD(&nfsi->dirty);
 		INIT_LIST_HEAD(&nfsi->commit);
 		INIT_RADIX_TREE(&nfsi->nfs_page_tree, GFP_ATOMIC);
+		atomic_set(&nfsi->data_updates, 0);
 		nfsi->ndirty = 0;
 		nfsi->ncommit = 0;
 		nfsi->npages = 0;
diff -puN fs/nfs/nfs3proc.c~nfs-31-attr fs/nfs/nfs3proc.c
--- 25/fs/nfs/nfs3proc.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/nfs3proc.c	2004-01-19 13:18:33.000000000 -0800
@@ -68,20 +68,6 @@ nfs3_async_handle_jukebox(struct rpc_tas
 	return 1;
 }
 
-static void
-nfs3_write_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
-{
-	if (fattr->valid & NFS_ATTR_FATTR) {
-		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;
-		}
-		nfs_refresh_inode(inode, fattr);
-	}
-}
-
 static struct rpc_cred *
 nfs_cred(struct inode *inode, struct file *filp)
 {
@@ -280,7 +266,7 @@ nfs3_proc_write(struct nfs_write_data *w
 	msg.rpc_cred = nfs_cred(inode, filp);
 	status = rpc_call_sync(NFS_CLIENT(inode), &msg, rpcflags);
 	if (status >= 0)
-		nfs3_write_refresh_inode(inode, fattr);
+		nfs_refresh_inode(inode, fattr);
 	dprintk("NFS reply write: %d\n", status);
 	return status < 0? status : wdata->res.count;
 }
@@ -303,7 +289,7 @@ nfs3_proc_commit(struct nfs_write_data *
 	msg.rpc_cred = nfs_cred(inode, filp);
 	status = rpc_call_sync(NFS_CLIENT(inode), &msg, 0);
 	if (status >= 0)
-		nfs3_write_refresh_inode(inode, fattr);
+		nfs_refresh_inode(inode, fattr);
 	dprintk("NFS reply commit: %d\n", status);
 	return status;
 }
@@ -777,12 +763,13 @@ nfs3_proc_read_setup(struct nfs_read_dat
 static void
 nfs3_write_done(struct rpc_task *task)
 {
-	struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata;
+	struct nfs_write_data *data;
 
 	if (nfs3_async_handle_jukebox(task))
 		return;
+	data = (struct nfs_write_data *)task->tk_calldata;
 	if (task->tk_status >= 0)
-		nfs3_write_refresh_inode(data->inode, data->res.fattr);
+		nfs_refresh_inode(data->inode, data->res.fattr);
 	nfs_writeback_done(task);
 }
 
@@ -835,12 +822,13 @@ nfs3_proc_write_setup(struct nfs_write_d
 static void
 nfs3_commit_done(struct rpc_task *task)
 {
-	struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata;
+	struct nfs_write_data *data;
 
 	if (nfs3_async_handle_jukebox(task))
 		return;
+	data = (struct nfs_write_data *)task->tk_calldata;
 	if (task->tk_status >= 0)
-		nfs3_write_refresh_inode(data->inode, data->res.fattr);
+		nfs_refresh_inode(data->inode, data->res.fattr);
 	nfs_commit_done(task);
 }
 
diff -puN fs/nfs/nfs4proc.c~nfs-31-attr fs/nfs/nfs4proc.c
--- 25/fs/nfs/nfs4proc.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/nfs4proc.c	2004-01-19 13:18:33.000000000 -0800
@@ -1088,12 +1088,8 @@ nfs4_proc_read(struct nfs_read_data *rda
 
 	fattr->valid = 0;
 	status = rpc_call_sync(server->client, &msg, flags);
-	if (!status) {
+	if (!status)
 		renew_lease(server, timestamp);
-		/* Check cache consistency */
-		if (fattr->change_attr != NFS_CHANGE_ATTR(inode))
-			nfs_zap_caches(inode);
-	}
 	dprintk("NFS reply read: %d\n", status);
 	return status;
 }
@@ -1130,7 +1126,6 @@ nfs4_proc_write(struct nfs_write_data *w
 
 	fattr->valid = 0;
 	status = rpc_call_sync(server->client, &msg, rpcflags);
-	NFS_CACHEINV(inode);
 	dprintk("NFS reply write: %d\n", status);
 	return status;
 }
@@ -1517,7 +1512,6 @@ nfs4_read_done(struct rpc_task *task)
 {
 	struct nfs_read_data *data = (struct nfs_read_data *) task->tk_calldata;
 	struct inode *inode = data->inode;
-	struct nfs_fattr *fattr = data->res.fattr;
 
 	if (nfs4_async_handle_error(task, NFS_SERVER(inode)) == -EAGAIN) {
 		task->tk_action = nfs4_restart_read;
@@ -1525,11 +1519,6 @@ nfs4_read_done(struct rpc_task *task)
 	}
 	if (task->tk_status > 0)
 		renew_lease(NFS_SERVER(inode), data->timestamp);
-	/* Check cache consistency */
-	if (fattr->change_attr != NFS_CHANGE_ATTR(inode))
-		nfs_zap_caches(inode);
-	if (fattr->bitmap[1] & FATTR4_WORD1_TIME_ACCESS)
-		inode->i_atime = fattr->atime;
 	/* Call back common NFS readpage processing */
 	nfs_readpage_result(task);
 }
@@ -1577,21 +1566,6 @@ nfs4_proc_read_setup(struct nfs_read_dat
 }
 
 static void
-nfs4_write_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
-{
-	/* Check cache consistency */
-	if (fattr->pre_change_attr != NFS_CHANGE_ATTR(inode))
-		nfs_zap_caches(inode);
-	NFS_CHANGE_ATTR(inode) = fattr->change_attr;
-	if (fattr->bitmap[1] & FATTR4_WORD1_SPACE_USED)
-		inode->i_blocks = (fattr->du.nfs3.used + 511) >> 9;
-	if (fattr->bitmap[1] & FATTR4_WORD1_TIME_METADATA)
-		inode->i_ctime = fattr->ctime;
-	if (fattr->bitmap[1] & FATTR4_WORD1_TIME_MODIFY)
-		inode->i_mtime = fattr->mtime;
-}
-
-static void
 nfs4_restart_write(struct rpc_task *task)
 {
 	struct nfs_write_data *data = (struct nfs_write_data *)task->tk_calldata;
@@ -1617,7 +1591,6 @@ nfs4_write_done(struct rpc_task *task)
 	}
 	if (task->tk_status >= 0)
 		renew_lease(NFS_SERVER(inode), data->timestamp);
-	nfs4_write_refresh_inode(inode, data->res.fattr);
 	/* Call back common NFS writeback processing */
 	nfs_writeback_done(task);
 }
@@ -1684,7 +1657,6 @@ nfs4_commit_done(struct rpc_task *task)
 		task->tk_action = nfs4_restart_write;
 		return;
 	}
-	nfs4_write_refresh_inode(inode, data->res.fattr);
 	/* Call back common NFS writeback processing */
 	nfs_commit_done(task);
 }
@@ -1807,6 +1779,7 @@ nfs4_proc_file_open(struct inode *inode,
 	if (filp->f_mode & FMODE_WRITE) {
 		lock_kernel();
 		nfs_set_mmcred(inode, state->owner->so_cred);
+		nfs_begin_data_update(inode);
 		unlock_kernel();
 	}
 	filp->private_data = state;
@@ -1823,6 +1796,11 @@ nfs4_proc_file_release(struct inode *ino
 
 	if (state)
 		nfs4_close_state(state, filp->f_mode);
+	if (filp->f_mode & FMODE_WRITE) {
+		lock_kernel();
+		nfs_end_data_update(inode);
+		unlock_kernel();
+	}
 	return 0;
 }
 
diff -puN fs/nfs/proc.c~nfs-31-attr fs/nfs/proc.c
--- 25/fs/nfs/proc.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/proc.c	2004-01-19 13:18:33.000000000 -0800
@@ -49,18 +49,6 @@
 
 extern struct rpc_procinfo nfs_procedures[];
 
-static void
-nfs_write_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
-{
-	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;
-	}
-	nfs_refresh_inode(inode, fattr);
-}
-
 static struct rpc_cred *
 nfs_cred(struct inode *inode, struct file *filp)
 {
@@ -205,7 +193,7 @@ nfs_proc_write(struct nfs_write_data *wd
 	msg.rpc_cred = nfs_cred(inode, filp);
 	status = rpc_call_sync(NFS_CLIENT(inode), &msg, flags);
 	if (status >= 0) {
-		nfs_write_refresh_inode(inode, fattr);
+		nfs_refresh_inode(inode, fattr);
 		wdata->res.count = wdata->args.count;
 		wdata->verf.committed = NFS_FILE_SYNC;
 	}
@@ -331,10 +319,8 @@ nfs_proc_unlink_done(struct dentry *dir,
 {
 	struct rpc_message *msg = &task->tk_msg;
 	
-	if (msg->rpc_argp) {
-		NFS_CACHEINV(dir->d_inode);
+	if (msg->rpc_argp)
 		kfree(msg->rpc_argp);
-	}
 	return 0;
 }
 
@@ -584,7 +570,7 @@ nfs_write_done(struct rpc_task *task)
 	struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata;
 
 	if (task->tk_status >= 0)
-		nfs_write_refresh_inode(data->inode, data->res.fattr);
+		nfs_refresh_inode(data->inode, data->res.fattr);
 	nfs_writeback_done(task);
 }
 
diff -puN fs/nfs/read.c~nfs-31-attr fs/nfs/read.c
--- 25/fs/nfs/read.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/read.c	2004-01-19 13:18:33.000000000 -0800
@@ -124,6 +124,7 @@ nfs_readpage_sync(struct file *file, str
 		if (result < rdata.args.count)	/* NFSv2ism */
 			break;
 	} while (count);
+	NFS_FLAGS(inode) |= NFS_INO_INVALID_ATIME;
 
 	if (count)
 		memclear_highpage_flush(page, rdata.args.pgbase, count);
@@ -266,6 +267,7 @@ nfs_readpage_result(struct rpc_task *tas
 	dprintk("NFS: %4d nfs_readpage_result, (status %d)\n",
 		task->tk_pid, task->tk_status);
 
+	NFS_FLAGS(data->inode) |= NFS_INO_INVALID_ATIME;
 	while (!list_empty(&data->pages)) {
 		struct nfs_page *req = nfs_list_entry(data->pages.next);
 		struct page *page = req->wb_page;
diff -puN fs/nfs/unlink.c~nfs-31-attr fs/nfs/unlink.c
--- 25/fs/nfs/unlink.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/unlink.c	2004-01-19 13:18:33.000000000 -0800
@@ -104,6 +104,7 @@ nfs_async_unlink_init(struct rpc_task *t
 	status = NFS_PROTO(dir->d_inode)->unlink_setup(&msg, dir, &data->name);
 	if (status < 0)
 		goto out_err;
+	nfs_begin_data_update(dir->d_inode);
 	rpc_call_setup(task, &msg, 0);
 	return;
  out_err:
@@ -126,7 +127,7 @@ nfs_async_unlink_done(struct rpc_task *t
 	if (!dir)
 		return;
 	dir_i = dir->d_inode;
-	nfs_zap_caches(dir_i);
+	nfs_end_data_update(dir_i);
 	if (NFS_PROTO(dir_i)->unlink_done(dir, task))
 		return;
 	put_rpccred(data->cred);
diff -puN fs/nfs/write.c~nfs-31-attr fs/nfs/write.c
--- 25/fs/nfs/write.c~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/fs/nfs/write.c	2004-01-19 13:18:33.000000000 -0800
@@ -157,6 +157,7 @@ nfs_writepage_sync(struct file *file, st
 		(long long)NFS_FILEID(inode),
 		count, (long long)(page_offset(page) + offset));
 
+	nfs_begin_data_update(inode);
 	do {
 		if (count < wsize && !swapfile)
 			wdata.args.count = count;
@@ -185,6 +186,7 @@ nfs_writepage_sync(struct file *file, st
 		if (wdata.args.offset > i_size_read(inode))
 			i_size_write(inode, wdata.args.offset);
 	} while (count);
+	nfs_end_data_update(inode);
 
 	if (PageError(page))
 		ClearPageError(page);
@@ -204,6 +206,7 @@ nfs_writepage_async(struct file *file, s
 	loff_t		end;
 	int		status;
 
+	nfs_begin_data_update(inode);
 	req = nfs_update_request(file, inode, page, offset, count);
 	status = (IS_ERR(req)) ? PTR_ERR(req) : 0;
 	if (status < 0)
@@ -213,6 +216,7 @@ nfs_writepage_async(struct file *file, s
 	end = ((loff_t)page->index<<PAGE_CACHE_SHIFT) + (loff_t)(offset + count);
 	if (i_size_read(inode) < end)
 		i_size_write(inode, end);
+	nfs_end_data_update(inode);
 
  out:
 	return status;
@@ -312,8 +316,10 @@ nfs_inode_add_request(struct inode *inod
 	BUG_ON(error == -EEXIST);
 	if (error)
 		return error;
-	if (!nfsi->npages)
+	if (!nfsi->npages) {
 		igrab(inode);
+		nfs_begin_data_update(inode);
+	}
 	nfsi->npages++;
 	req->wb_count++;
 	return 0;
@@ -336,6 +342,7 @@ nfs_inode_remove_request(struct nfs_page
 	nfsi->npages--;
 	if (!nfsi->npages) {
 		spin_unlock(&nfs_wreq_lock);
+		nfs_end_data_update(inode);
 		iput(inode);
 	} else
 		spin_unlock(&nfs_wreq_lock);
@@ -696,6 +703,7 @@ nfs_updatepage(struct file *file, struct
 		return status;
 	}
 
+	nfs_begin_data_update(inode);
 	/*
 	 * Try to find an NFS request corresponding to this page
 	 * and update it.
@@ -729,6 +737,7 @@ nfs_updatepage(struct file *file, struct
 	} else
 		nfs_unlock_request(req);
 done:
+	nfs_end_data_update(inode);
         dprintk("NFS:      nfs_updatepage returns %d (isize %Ld)\n",
 			status, (long long)i_size_read(inode));
 	if (status < 0)
@@ -891,10 +900,7 @@ nfs_writeback_done(struct rpc_task *task
 #endif
 
 	/*
-	 * Update attributes as result of writeback.
-	 * FIXME: There is an inherent race with invalidate_inode_pages and
-	 *	  writebacks since the page->count is kept > 1 for as long
-	 *	  as the page has a write request pending.
+	 * Process the nfs_page list
 	 */
 	while (!list_empty(&data->pages)) {
 		req = nfs_list_entry(data->pages.next);
diff -puN include/linux/fs.h~nfs-31-attr include/linux/fs.h
--- 25/include/linux/fs.h~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/include/linux/fs.h	2004-01-19 13:18:33.000000000 -0800
@@ -137,6 +137,7 @@ extern int leases_enable, dir_notify_ena
 #define S_DEAD		32	/* removed, but still open directory */
 #define S_NOQUOTA	64	/* Inode is not counted to quota */
 #define S_DIRSYNC	128	/* Directory modifications are synchronous */
+#define S_NOCMTIME	256	/* Do not update file c/mtime */
 
 /*
  * Note that nosuid etc flags are inode-specific: setting some file-system
@@ -170,6 +171,7 @@ extern int leases_enable, dir_notify_ena
 #define IS_ONE_SECOND(inode)	__IS_FLG(inode, MS_ONE_SECOND)
 
 #define IS_DEADDIR(inode)	((inode)->i_flags & S_DEAD)
+#define IS_NOCMTIME(inode)	((inode)->i_flags & S_NOCMTIME)
 
 /* the read-only stuff doesn't really belong here, but any other place is
    probably as bad and I don't want to create yet another include file. */
diff -puN include/linux/nfs_fs.h~nfs-31-attr include/linux/nfs_fs.h
--- 25/include/linux/nfs_fs.h~nfs-31-attr	2004-01-19 13:18:33.000000000 -0800
+++ 25-akpm/include/linux/nfs_fs.h	2004-01-19 13:18:33.000000000 -0800
@@ -99,7 +99,7 @@ struct nfs_inode {
 	/*
 	 * Various flags
 	 */
-	unsigned short		flags;
+	unsigned int		flags;
 
 	/*
 	 * read_cache_jiffies is when we started read-caching this inode,
@@ -118,19 +118,22 @@ struct nfs_inode {
 	 *
 	 *	mtime != read_cache_mtime
 	 */
+	unsigned long		readdir_timestamp;
 	unsigned long		read_cache_jiffies;
-	struct timespec		read_cache_ctime;
-	struct timespec		read_cache_mtime;
-	__u64			read_cache_isize;
 	unsigned long		attrtimeo;
 	unsigned long		attrtimeo_timestamp;
 	__u64			change_attr;		/* v4 only */
 
+	/* "Generation counter" for the attribute cache. This is
+	 * bumped whenever we update the metadata on the
+	 * server.
+	 */
+	unsigned long		cache_change_attribute;
 	/*
-	 * Timestamp that dates the change made to read_cache_mtime.
-	 * This is of use for dentry revalidation
+	 * Counter indicating the number of outstanding requests that
+	 * will cause a file data update.
 	 */
-	unsigned long		cache_mtime_jiffies;
+	atomic_t		data_updates;
 
 	struct nfs_access_cache	cache_access;
 
@@ -170,7 +173,9 @@ struct nfs_inode {
 #define NFS_INO_STALE		0x0001		/* possible stale inode */
 #define NFS_INO_ADVISE_RDPLUS   0x0002          /* advise readdirplus */
 #define NFS_INO_REVALIDATING	0x0004		/* revalidating attrs */
-#define NFS_INO_FLUSH		0x0008		/* inode is due for flushing */
+#define NFS_INO_INVALID_ATTR	0x0008		/* cached attrs are invalid */
+#define NFS_INO_INVALID_DATA	0x0010		/* cached data is invalid */
+#define NFS_INO_INVALID_ATIME	0x0020		/* cached atime is invalid */
 #define NFS_INO_FAKE_ROOT	0x0080		/* root inode placeholder */
 
 static inline struct nfs_inode *NFS_I(struct inode *inode)
@@ -186,15 +191,7 @@ static inline struct nfs_inode *NFS_I(st
 #define NFS_ADDR(inode)			(RPC_PEERADDR(NFS_CLIENT(inode)))
 #define NFS_COOKIEVERF(inode)		(NFS_I(inode)->cookieverf)
 #define NFS_READTIME(inode)		(NFS_I(inode)->read_cache_jiffies)
-#define NFS_MTIME_UPDATE(inode)		(NFS_I(inode)->cache_mtime_jiffies)
-#define NFS_CACHE_CTIME(inode)		(NFS_I(inode)->read_cache_ctime)
-#define NFS_CACHE_MTIME(inode)		(NFS_I(inode)->read_cache_mtime)
-#define NFS_CACHE_ISIZE(inode)		(NFS_I(inode)->read_cache_isize)
 #define NFS_CHANGE_ATTR(inode)		(NFS_I(inode)->change_attr)
-#define NFS_CACHEINV(inode) \
-do { \
-	NFS_READTIME(inode) = jiffies - NFS_MAXATTRTIMEO(inode) - 1; \
-} while (0)
 #define NFS_ATTRTIMEO(inode)		(NFS_I(inode)->attrtimeo)
 #define NFS_MINATTRTIMEO(inode) \
 	(S_ISDIR(inode->i_mode)? NFS_SERVER(inode)->acdirmin \
@@ -211,6 +208,17 @@ do { \
 
 #define NFS_FILEID(inode)		(NFS_I(inode)->fileid)
 
+static inline int nfs_caches_unstable(struct inode *inode)
+{
+	return atomic_read(&NFS_I(inode)->data_updates) != 0;
+}
+
+static inline void NFS_CACHEINV(struct inode *inode)
+{
+	if (!nfs_caches_unstable(inode))
+		NFS_FLAGS(inode) |= NFS_INO_INVALID_ATTR;
+}
+
 static inline int nfs_server_capable(struct inode *inode, int cap)
 {
 	return NFS_SERVER(inode)->caps & cap;
@@ -227,13 +235,37 @@ loff_t page_offset(struct page *page)
 	return ((loff_t)page->index) << PAGE_CACHE_SHIFT;
 }
 
+/**
+ * nfs_save_change_attribute - Returns the inode attribute change cookie
+ * @inode - pointer to inode
+ * The "change attribute" is updated every time we finish an operation
+ * that will result in a metadata change on the server.
+ */
+static inline long nfs_save_change_attribute(struct inode *inode)
+{
+	return NFS_I(inode)->cache_change_attribute;
+}
+
+/**
+ * nfs_verify_change_attribute - Detects NFS inode cache updates
+ * @inode - pointer to inode
+ * @chattr - previously saved change attribute
+ * Return "false" if metadata has been updated (or is in the process of
+ * being updated) since the change attribute was saved.
+ */
+static inline int nfs_verify_change_attribute(struct inode *inode, unsigned long chattr)
+{
+	return !nfs_caches_unstable(inode)
+		&& chattr == NFS_I(inode)->cache_change_attribute;
+}
+
 /*
  * linux/fs/nfs/inode.c
  */
 extern void nfs_zap_caches(struct inode *);
 extern struct inode *nfs_fhget(struct super_block *, struct nfs_fh *,
 				struct nfs_fattr *);
-extern int __nfs_refresh_inode(struct inode *, struct nfs_fattr *);
+extern int nfs_refresh_inode(struct inode *, struct nfs_fattr *);
 extern int nfs_getattr(struct vfsmount *, struct dentry *, struct kstat *);
 extern int nfs_permission(struct inode *, int, struct nameidata *);
 extern void nfs_set_mmcred(struct inode *, struct rpc_cred *);
@@ -241,6 +273,10 @@ extern int nfs_open(struct inode *, stru
 extern int nfs_release(struct inode *, struct file *);
 extern int __nfs_revalidate_inode(struct nfs_server *, struct inode *);
 extern int nfs_setattr(struct dentry *, struct iattr *);
+extern void nfs_begin_attr_update(struct inode *);
+extern void nfs_end_attr_update(struct inode *);
+extern void nfs_begin_data_update(struct inode *);
+extern void nfs_end_data_update(struct inode *);
 
 /*
  * linux/fs/nfs/file.c
@@ -383,20 +419,27 @@ extern int  nfsroot_mount(struct sockadd
 /*
  * inline functions
  */
-static inline int
-nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
+
+static inline int nfs_attribute_timeout(struct inode *inode)
 {
-	if (time_before(jiffies, NFS_READTIME(inode)+NFS_ATTRTIMEO(inode)))
-		return NFS_STALE(inode) ? -ESTALE : 0;
-	return __nfs_revalidate_inode(server, inode);
+	struct nfs_inode *nfsi = NFS_I(inode);
+
+	return time_after(jiffies, nfsi->read_cache_jiffies+nfsi->attrtimeo);
 }
 
-static inline int
-nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
+/**
+ * nfs_revalidate_inode - Revalidate the inode attributes
+ * @server - pointer to nfs_server struct
+ * @inode - pointer to inode struct
+ *
+ * Updates inode attribute information by retrieving the data from the server.
+ */
+static inline int nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
 {
-	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
-		return 0;
-	return __nfs_refresh_inode(inode,fattr);
+	if (!(NFS_FLAGS(inode) & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA))
+			&& !nfs_attribute_timeout(inode))
+		return NFS_STALE(inode) ? -ESTALE : 0;
+	return __nfs_revalidate_inode(server, inode);
 }
 
 static inline loff_t

_