Merge branch 'topic/core-cleanup' into for-linus
[safe/jmp/linux-2.6] / fs / nfs / inode.c
index 7c8ca17..50a56ed 100644 (file)
@@ -5,7 +5,7 @@
  *
  *  nfs inode and superblock handling functions
  *
- *  Modularised by Alan Cox <Alan.Cox@linux.org>, while hacking some
+ *  Modularised by Alan Cox <alan@lxorguk.ukuu.org.uk>, while hacking some
  *  experimental NFS changes. Modularisation taken straight from SYS5 fs.
  *
  *  Change to nfs_read_super() to permit NFS mounts to multi-homed hosts.
 #include <linux/nfs_mount.h>
 #include <linux/nfs4_mount.h>
 #include <linux/lockd/bind.h>
-#include <linux/smp_lock.h>
 #include <linux/seq_file.h>
 #include <linux/mount.h>
 #include <linux/nfs_idmap.h>
 #include <linux/vfs.h>
 #include <linux/inet.h>
 #include <linux/nfs_xdr.h>
+#include <linux/slab.h>
 
 #include <asm/system.h>
 #include <asm/uaccess.h>
 #include "delegation.h"
 #include "iostat.h"
 #include "internal.h"
+#include "fscache.h"
+#include "dns_resolve.h"
 
 #define NFSDBG_FACILITY                NFSDBG_VFS
 
+#define NFS_64_BIT_INODE_NUMBERS_ENABLED       1
+
+/* Default is to see 64-bit inode numbers */
+static int enable_ino64 = NFS_64_BIT_INODE_NUMBERS_ENABLED;
+
 static void nfs_invalidate_inode(struct inode *);
 static int nfs_update_inode(struct inode *, struct nfs_fattr *);
 
-static void nfs_zap_acl_cache(struct inode *);
-
 static struct kmem_cache * nfs_inode_cachep;
 
 static inline unsigned long
@@ -62,20 +67,35 @@ nfs_fattr_to_ino_t(struct nfs_fattr *fattr)
        return nfs_fileid_to_ino_t(fattr->fileid);
 }
 
-int nfs_write_inode(struct inode *inode, int sync)
+/**
+ * nfs_wait_bit_killable - helper for functions that are sleeping on bit locks
+ * @word: long word containing the bit lock
+ */
+int nfs_wait_bit_killable(void *word)
 {
-       int ret;
+       if (fatal_signal_pending(current))
+               return -ERESTARTSYS;
+       schedule();
+       return 0;
+}
 
-       if (sync) {
-               ret = filemap_fdatawait(inode->i_mapping);
-               if (ret == 0)
-                       ret = nfs_commit_inode(inode, FLUSH_SYNC);
-       } else
-               ret = nfs_commit_inode(inode, 0);
-       if (ret >= 0)
-               return 0;
-       __mark_inode_dirty(inode, I_DIRTY_DATASYNC);
-       return ret;
+/**
+ * nfs_compat_user_ino64 - returns the user-visible inode number
+ * @fileid: 64-bit fileid
+ *
+ * This function returns a 32-bit inode number if the boot parameter
+ * nfs.enable_ino64 is zero.
+ */
+u64 nfs_compat_user_ino64(u64 fileid)
+{
+       int ino;
+
+       if (enable_ino64)
+               return fileid;
+       ino = fileid;
+       if (sizeof(ino) < sizeof(fileid))
+               ino ^= fileid >> (sizeof(fileid)-sizeof(ino)) * 8;
+       return ino;
 }
 
 void nfs_clear_inode(struct inode *inode)
@@ -85,9 +105,9 @@ void nfs_clear_inode(struct inode *inode)
         */
        BUG_ON(nfs_have_writebacks(inode));
        BUG_ON(!list_empty(&NFS_I(inode)->open_files));
-       BUG_ON(atomic_read(&NFS_I(inode)->data_updates) != 0);
        nfs_zap_acl_cache(inode);
        nfs_access_zap_cache(inode);
+       nfs_fscache_release_inode_cookie(inode);
 }
 
 /**
@@ -95,16 +115,12 @@ void nfs_clear_inode(struct inode *inode)
  */
 int nfs_sync_mapping(struct address_space *mapping)
 {
-       int ret;
+       int ret = 0;
 
-       if (mapping->nrpages == 0)
-               return 0;
-       unmap_mapping_range(mapping, 0, 0, 0);
-       ret = filemap_write_and_wait(mapping);
-       if (ret != 0)
-               goto out;
-       ret = nfs_wb_all(mapping->host);
-out:
+       if (mapping->nrpages != 0) {
+               unmap_mapping_range(mapping, 0, 0, 0);
+               ret = nfs_wb_all(mapping->host);
+       }
        return ret;
 }
 
@@ -118,8 +134,8 @@ static void nfs_zap_caches_locked(struct inode *inode)
 
        nfs_inc_stats(inode, NFSIOS_ATTRINVALIDATE);
 
-       NFS_ATTRTIMEO(inode) = NFS_MINATTRTIMEO(inode);
-       NFS_ATTRTIMEO_UPDATE(inode) = jiffies;
+       nfsi->attrtimeo = NFS_MINATTRTIMEO(inode);
+       nfsi->attrtimeo_timestamp = jiffies;
 
        memset(NFS_COOKIEVERF(inode), 0, sizeof(NFS_COOKIEVERF(inode)));
        if (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))
@@ -144,7 +160,7 @@ void nfs_zap_mapping(struct inode *inode, struct address_space *mapping)
        }
 }
 
-static void nfs_zap_acl_cache(struct inode *inode)
+void nfs_zap_acl_cache(struct inode *inode)
 {
        void (*clear_acl_cache)(struct inode *);
 
@@ -156,13 +172,20 @@ static void nfs_zap_acl_cache(struct inode *inode)
        spin_unlock(&inode->i_lock);
 }
 
+void nfs_invalidate_atime(struct inode *inode)
+{
+       spin_lock(&inode->i_lock);
+       NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ATIME;
+       spin_unlock(&inode->i_lock);
+}
+
 /*
  * Invalidate, but do not unhash, the inode.
  * NB: must be called with inode->i_lock held!
  */
 static void nfs_invalidate_inode(struct inode *inode)
 {
-       set_bit(NFS_INO_STALE, &NFS_FLAGS(inode));
+       set_bit(NFS_INO_STALE, &NFS_I(inode)->flags);
        nfs_zap_caches_locked(inode);
 }
 
@@ -199,7 +222,7 @@ nfs_init_locked(struct inode *inode, void *opaque)
        struct nfs_find_desc    *desc = (struct nfs_find_desc *)opaque;
        struct nfs_fattr        *fattr = desc->fattr;
 
-       NFS_FILEID(inode) = fattr->fileid;
+       set_nfs_fileid(inode, fattr->fileid);
        nfs_copy_fh(NFS_FH(inode), desc->fh);
        return 0;
 }
@@ -221,13 +244,10 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
        struct inode *inode = ERR_PTR(-ENOENT);
        unsigned long hash;
 
-       if ((fattr->valid & NFS_ATTR_FATTR) == 0)
+       if ((fattr->valid & NFS_ATTR_FATTR_FILEID) == 0)
                goto out_no_inode;
-
-       if (!fattr->nlink) {
-               printk("NFS: Buggy server - nlink == 0!\n");
+       if ((fattr->valid & NFS_ATTR_FATTR_TYPE) == 0)
                goto out_no_inode;
-       }
 
        hash = nfs_fattr_to_ino_t(fattr);
 
@@ -248,6 +268,11 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
                /* We can't support update_atime(), since the server will reset it */
                inode->i_flags |= S_NOATIME|S_NOCMTIME;
                inode->i_mode = fattr->mode;
+               if ((fattr->valid & NFS_ATTR_FATTR_MODE) == 0
+                               && nfs_server_capable(inode, NFS_CAP_MODE))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL;
                /* Why so? Because we want revalidate for devices/FIFOs, and
                 * that's precisely what we have in nfs_file_inode_operations.
                 */
@@ -261,45 +286,91 @@ nfs_fhget(struct super_block *sb, struct nfs_fh *fh, struct nfs_fattr *fattr)
                        inode->i_fop = &nfs_dir_operations;
                        if (nfs_server_capable(inode, NFS_CAP_READDIRPLUS)
                            && fattr->size <= NFS_LIMIT_READDIRPLUS)
-                               set_bit(NFS_INO_ADVISE_RDPLUS, &NFS_FLAGS(inode));
+                               set_bit(NFS_INO_ADVISE_RDPLUS, &NFS_I(inode)->flags);
                        /* Deal with crossing mountpoints */
-                       if (!nfs_fsid_equal(&NFS_SB(sb)->fsid, &fattr->fsid)) {
+                       if ((fattr->valid & NFS_ATTR_FATTR_FSID)
+                                       && !nfs_fsid_equal(&NFS_SB(sb)->fsid, &fattr->fsid)) {
                                if (fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL)
                                        inode->i_op = &nfs_referral_inode_operations;
                                else
                                        inode->i_op = &nfs_mountpoint_inode_operations;
                                inode->i_fop = NULL;
+                               set_bit(NFS_INO_MOUNTPOINT, &nfsi->flags);
                        }
                } else if (S_ISLNK(inode->i_mode))
                        inode->i_op = &nfs_symlink_inode_operations;
                else
                        init_special_inode(inode, inode->i_mode, fattr->rdev);
 
+               memset(&inode->i_atime, 0, sizeof(inode->i_atime));
+               memset(&inode->i_mtime, 0, sizeof(inode->i_mtime));
+               memset(&inode->i_ctime, 0, sizeof(inode->i_ctime));
+               nfsi->change_attr = 0;
+               inode->i_size = 0;
+               inode->i_nlink = 0;
+               inode->i_uid = -2;
+               inode->i_gid = -2;
+               inode->i_blocks = 0;
+               memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));
+
                nfsi->read_cache_jiffies = fattr->time_start;
-               nfsi->last_updated = now;
-               nfsi->cache_change_attribute = now;
-               inode->i_atime = fattr->atime;
-               inode->i_mtime = fattr->mtime;
-               inode->i_ctime = fattr->ctime;
-               if (fattr->valid & NFS_ATTR_FATTR_V4)
+               nfsi->attr_gencount = fattr->gencount;
+               if (fattr->valid & NFS_ATTR_FATTR_ATIME)
+                       inode->i_atime = fattr->atime;
+               else if (nfs_server_capable(inode, NFS_CAP_ATIME))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
+               if (fattr->valid & NFS_ATTR_FATTR_MTIME)
+                       inode->i_mtime = fattr->mtime;
+               else if (nfs_server_capable(inode, NFS_CAP_MTIME))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_DATA;
+               if (fattr->valid & NFS_ATTR_FATTR_CTIME)
+                       inode->i_ctime = fattr->ctime;
+               else if (nfs_server_capable(inode, NFS_CAP_CTIME))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL;
+               if (fattr->valid & NFS_ATTR_FATTR_CHANGE)
                        nfsi->change_attr = fattr->change_attr;
-               inode->i_size = nfs_size_to_loff_t(fattr->size);
-               inode->i_nlink = fattr->nlink;
-               inode->i_uid = fattr->uid;
-               inode->i_gid = fattr->gid;
-               if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) {
+               else if (nfs_server_capable(inode, NFS_CAP_CHANGE_ATTR))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_DATA;
+               if (fattr->valid & NFS_ATTR_FATTR_SIZE)
+                       inode->i_size = nfs_size_to_loff_t(fattr->size);
+               else
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_DATA
+                               | NFS_INO_REVAL_PAGECACHE;
+               if (fattr->valid & NFS_ATTR_FATTR_NLINK)
+                       inode->i_nlink = fattr->nlink;
+               else if (nfs_server_capable(inode, NFS_CAP_NLINK))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
+               if (fattr->valid & NFS_ATTR_FATTR_OWNER)
+                       inode->i_uid = fattr->uid;
+               else if (nfs_server_capable(inode, NFS_CAP_OWNER))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL;
+               if (fattr->valid & NFS_ATTR_FATTR_GROUP)
+                       inode->i_gid = fattr->gid;
+               else if (nfs_server_capable(inode, NFS_CAP_OWNER_GROUP))
+                       nfsi->cache_validity |= NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL;
+               if (fattr->valid & NFS_ATTR_FATTR_BLOCKS_USED)
+                       inode->i_blocks = fattr->du.nfs2.blocks;
+               if (fattr->valid & NFS_ATTR_FATTR_SPACE_USED) {
                        /*
                         * report the blocks in 512byte units
                         */
                        inode->i_blocks = nfs_calc_block_size(fattr->du.nfs3.used);
-               } else {
-                       inode->i_blocks = fattr->du.nfs2.blocks;
                }
                nfsi->attrtimeo = NFS_MINATTRTIMEO(inode);
                nfsi->attrtimeo_timestamp = now;
-               memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));
                nfsi->access_cache = RB_ROOT;
 
+               nfs_fscache_init_inode_cookie(inode);
+
                unlock_new_inode(inode);
        } else
                nfs_refresh_inode(inode, fattr);
@@ -316,7 +387,7 @@ out_no_inode:
        goto out;
 }
 
-#define NFS_VALID_ATTRS (ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_SIZE|ATTR_ATIME|ATTR_ATIME_SET|ATTR_MTIME|ATTR_MTIME_SET)
+#define NFS_VALID_ATTRS (ATTR_MODE|ATTR_UID|ATTR_GID|ATTR_SIZE|ATTR_ATIME|ATTR_ATIME_SET|ATTR_MTIME|ATTR_MTIME_SET|ATTR_FILE)
 
 int
 nfs_setattr(struct dentry *dentry, struct iattr *attr)
@@ -327,6 +398,10 @@ nfs_setattr(struct dentry *dentry, struct iattr *attr)
 
        nfs_inc_stats(inode, NFSIOS_VFSSETATTR);
 
+       /* skip mode change if it's just for clearing setuid/setgid */
+       if (attr->ia_valid & (ATTR_KILL_SUID | ATTR_KILL_SGID))
+               attr->ia_valid &= ~ATTR_MODE;
+
        if (attr->ia_valid & ATTR_SIZE) {
                if (!S_ISREG(inode->i_mode) || attr->ia_size == i_size_read(inode))
                        attr->ia_valid &= ~ATTR_SIZE;
@@ -334,11 +409,9 @@ nfs_setattr(struct dentry *dentry, struct iattr *attr)
 
        /* Optimization: if the end result is no change, don't RPC */
        attr->ia_valid &= NFS_VALID_ATTRS;
-       if (attr->ia_valid == 0)
+       if ((attr->ia_valid & ~ATTR_FILE) == 0)
                return 0;
 
-       lock_kernel();
-       nfs_begin_data_update(inode);
        /* Write all dirty data */
        if (S_ISREG(inode->i_mode)) {
                filemap_write_and_wait(inode->i_mapping);
@@ -352,12 +425,38 @@ nfs_setattr(struct dentry *dentry, struct iattr *attr)
        error = NFS_PROTO(inode)->setattr(dentry, &fattr, attr);
        if (error == 0)
                nfs_refresh_inode(inode, &fattr);
-       nfs_end_data_update(inode);
-       unlock_kernel();
        return error;
 }
 
 /**
+ * nfs_vmtruncate - unmap mappings "freed" by truncate() syscall
+ * @inode: inode of the file used
+ * @offset: file offset to start truncating
+ *
+ * This is a copy of the common vmtruncate, but with the locking
+ * corrected to take into account the fact that NFS requires
+ * inode->i_size to be updated under the inode->i_lock.
+ */
+static int nfs_vmtruncate(struct inode * inode, loff_t offset)
+{
+       loff_t oldsize;
+       int err;
+
+       err = inode_newsize_ok(inode, offset);
+       if (err)
+               goto out;
+
+       spin_lock(&inode->i_lock);
+       oldsize = inode->i_size;
+       i_size_write(inode, offset);
+       spin_unlock(&inode->i_lock);
+
+       truncate_pagecache(inode, oldsize, offset);
+out:
+       return err;
+}
+
+/**
  * nfs_setattr_update_inode - Update inode metadata after a setattr call.
  * @inode: pointer to struct inode
  * @attr: pointer to struct iattr
@@ -368,6 +467,7 @@ nfs_setattr(struct dentry *dentry, struct iattr *attr)
 void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr)
 {
        if ((attr->ia_valid & (ATTR_MODE|ATTR_UID|ATTR_GID)) != 0) {
+               spin_lock(&inode->i_lock);
                if ((attr->ia_valid & ATTR_MODE) != 0) {
                        int mode = attr->ia_mode & S_IALLUGO;
                        mode |= inode->i_mode & ~S_IALLUGO;
@@ -377,61 +477,27 @@ void nfs_setattr_update_inode(struct inode *inode, struct iattr *attr)
                        inode->i_uid = attr->ia_uid;
                if ((attr->ia_valid & ATTR_GID) != 0)
                        inode->i_gid = attr->ia_gid;
-               spin_lock(&inode->i_lock);
                NFS_I(inode)->cache_validity |= NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
                spin_unlock(&inode->i_lock);
        }
        if ((attr->ia_valid & ATTR_SIZE) != 0) {
                nfs_inc_stats(inode, NFSIOS_SETATTRTRUNC);
-               inode->i_size = attr->ia_size;
-               vmtruncate(inode, attr->ia_size);
+               nfs_vmtruncate(inode, attr->ia_size);
        }
 }
 
-static int nfs_wait_schedule(void *word)
-{
-       if (signal_pending(current))
-               return -ERESTARTSYS;
-       schedule();
-       return 0;
-}
-
-/*
- * Wait for the inode to get unlocked.
- */
-static int nfs_wait_on_inode(struct inode *inode)
-{
-       struct rpc_clnt *clnt = NFS_CLIENT(inode);
-       struct nfs_inode *nfsi = NFS_I(inode);
-       sigset_t oldmask;
-       int error;
-
-       rpc_clnt_sigmask(clnt, &oldmask);
-       error = wait_on_bit_lock(&nfsi->flags, NFS_INO_REVALIDATING,
-                                       nfs_wait_schedule, TASK_INTERRUPTIBLE);
-       rpc_clnt_sigunmask(clnt, &oldmask);
-
-       return error;
-}
-
-static void nfs_wake_up_inode(struct inode *inode)
-{
-       struct nfs_inode *nfsi = NFS_I(inode);
-
-       clear_bit(NFS_INO_REVALIDATING, &nfsi->flags);
-       smp_mb__after_clear_bit();
-       wake_up_bit(&nfsi->flags, NFS_INO_REVALIDATING);
-}
-
 int nfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
 {
        struct inode *inode = dentry->d_inode;
        int need_atime = NFS_I(inode)->cache_validity & NFS_INO_INVALID_ATIME;
        int err;
 
-       /* Flush out writes to the server in order to update c/mtime */
-       if (S_ISREG(inode->i_mode))
-               nfs_wb_nocommit(inode);
+       /* Flush out writes to the server in order to update c/mtime.  */
+       if (S_ISREG(inode->i_mode)) {
+               err = filemap_write_and_wait(inode->i_mapping);
+               if (err)
+                       goto out;
+       }
 
        /*
         * We may force a getattr if the user cares about atime.
@@ -452,22 +518,50 @@ int nfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
                err = nfs_revalidate_inode(NFS_SERVER(inode), inode);
        if (!err) {
                generic_fillattr(inode, stat);
-               stat->ino = NFS_FILEID(inode);
+               stat->ino = nfs_compat_user_ino64(NFS_FILEID(inode));
        }
+out:
        return err;
 }
 
-static struct nfs_open_context *alloc_nfs_open_context(struct vfsmount *mnt, struct dentry *dentry, struct rpc_cred *cred)
+/**
+ * nfs_close_context - Common close_context() routine NFSv2/v3
+ * @ctx: pointer to context
+ * @is_sync: is this a synchronous close
+ *
+ * always ensure that the attributes are up to date if we're mounted
+ * with close-to-open semantics
+ */
+void nfs_close_context(struct nfs_open_context *ctx, int is_sync)
+{
+       struct inode *inode;
+       struct nfs_server *server;
+
+       if (!(ctx->mode & FMODE_WRITE))
+               return;
+       if (!is_sync)
+               return;
+       inode = ctx->path.dentry->d_inode;
+       if (!list_empty(&NFS_I(inode)->open_files))
+               return;
+       server = NFS_SERVER(inode);
+       if (server->flags & NFS_MOUNT_NOCTO)
+               return;
+       nfs_revalidate_inode(server, inode);
+}
+
+static struct nfs_open_context *alloc_nfs_open_context(struct path *path, struct rpc_cred *cred)
 {
        struct nfs_open_context *ctx;
 
        ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
        if (ctx != NULL) {
-               ctx->path.dentry = dget(dentry);
-               ctx->path.mnt = mntget(mnt);
+               ctx->path = *path;
+               path_get(&ctx->path);
                ctx->cred = get_rpccred(cred);
                ctx->state = NULL;
                ctx->lockowner = current->files;
+               ctx->flags = 0;
                ctx->error = 0;
                ctx->dir_cookie = 0;
                atomic_set(&ctx->count, 1);
@@ -482,7 +576,7 @@ struct nfs_open_context *get_nfs_open_context(struct nfs_open_context *ctx)
        return ctx;
 }
 
-void put_nfs_open_context(struct nfs_open_context *ctx)
+static void __put_nfs_open_context(struct nfs_open_context *ctx, int is_sync)
 {
        struct inode *inode = ctx->path.dentry->d_inode;
 
@@ -490,15 +584,18 @@ void put_nfs_open_context(struct nfs_open_context *ctx)
                return;
        list_del(&ctx->list);
        spin_unlock(&inode->i_lock);
-       if (ctx->state != NULL)
-               nfs4_close_state(&ctx->path, ctx->state, ctx->mode);
+       NFS_PROTO(inode)->close_context(ctx, is_sync);
        if (ctx->cred != NULL)
                put_rpccred(ctx->cred);
-       dput(ctx->path.dentry);
-       mntput(ctx->path.mnt);
+       path_put(&ctx->path);
        kfree(ctx);
 }
 
+void put_nfs_open_context(struct nfs_open_context *ctx)
+{
+       __put_nfs_open_context(ctx, 0);
+}
+
 /*
  * Ensure that mmap has a recent RPC credential for use when writing out
  * shared pages
@@ -517,7 +614,7 @@ static void nfs_file_set_open_context(struct file *filp, struct nfs_open_context
 /*
  * Given an inode, search for an open context with the desired characteristics
  */
-struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_cred *cred, int mode)
+struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_cred *cred, fmode_t mode)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        struct nfs_open_context *pos, *ctx = NULL;
@@ -526,10 +623,10 @@ struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_c
        list_for_each_entry(pos, &nfsi->open_files, list) {
                if (cred != NULL && pos->cred != cred)
                        continue;
-               if ((pos->mode & mode) == mode) {
-                       ctx = get_nfs_open_context(pos);
-                       break;
-               }
+               if ((pos->mode & (FMODE_READ|FMODE_WRITE)) != mode)
+                       continue;
+               ctx = get_nfs_open_context(pos);
+               break;
        }
        spin_unlock(&inode->i_lock);
        return ctx;
@@ -538,14 +635,14 @@ struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_c
 static void nfs_file_clear_open_context(struct file *filp)
 {
        struct inode *inode = filp->f_path.dentry->d_inode;
-       struct nfs_open_context *ctx = (struct nfs_open_context *)filp->private_data;
+       struct nfs_open_context *ctx = nfs_file_open_context(filp);
 
        if (ctx) {
                filp->private_data = NULL;
                spin_lock(&inode->i_lock);
                list_move_tail(&ctx->list, &NFS_I(inode)->open_files);
                spin_unlock(&inode->i_lock);
-               put_nfs_open_context(ctx);
+               __put_nfs_open_context(ctx, filp->f_flags & O_DIRECT ? 0 : 1);
        }
 }
 
@@ -557,16 +654,17 @@ int nfs_open(struct inode *inode, struct file *filp)
        struct nfs_open_context *ctx;
        struct rpc_cred *cred;
 
-       cred = rpcauth_lookupcred(NFS_CLIENT(inode)->cl_auth, 0);
+       cred = rpc_lookup_cred();
        if (IS_ERR(cred))
                return PTR_ERR(cred);
-       ctx = alloc_nfs_open_context(filp->f_path.mnt, filp->f_path.dentry, cred);
+       ctx = alloc_nfs_open_context(&filp->f_path, cred);
        put_rpccred(cred);
        if (ctx == NULL)
                return -ENOMEM;
        ctx->mode = filp->f_mode;
        nfs_file_set_open_context(filp, ctx);
        put_nfs_open_context(ctx);
+       nfs_fscache_set_inode_cookie(inode, filp);
        return 0;
 }
 
@@ -590,27 +688,12 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
        dfprintk(PAGECACHE, "NFS: revalidating (%s/%Ld)\n",
                inode->i_sb->s_id, (long long)NFS_FILEID(inode));
 
-       nfs_inc_stats(inode, NFSIOS_INODEREVALIDATE);
-       lock_kernel();
        if (is_bad_inode(inode))
-               goto out_nowait;
+               goto out;
        if (NFS_STALE(inode))
-               goto out_nowait;
-
-       status = nfs_wait_on_inode(inode);
-       if (status < 0)
                goto out;
-       if (NFS_STALE(inode)) {
-               status = -ESTALE;
-               /* Do we trust the cached ESTALE? */
-               if (NFS_ATTRTIMEO(inode) != 0) {
-                       if (nfsi->cache_validity & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME)) {
-                               /* no */
-                       } else
-                               goto out;
-               }
-       }
 
+       nfs_inc_stats(inode, NFSIOS_INODEREVALIDATE);
        status = NFS_PROTO(inode)->getattr(server, NFS_FH(inode), &fattr);
        if (status != 0) {
                dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Ld) getattr failed, error=%d\n",
@@ -619,21 +702,18 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
                if (status == -ESTALE) {
                        nfs_zap_caches(inode);
                        if (!S_ISDIR(inode->i_mode))
-                               set_bit(NFS_INO_STALE, &NFS_FLAGS(inode));
+                               set_bit(NFS_INO_STALE, &NFS_I(inode)->flags);
                }
                goto out;
        }
 
-       spin_lock(&inode->i_lock);
-       status = nfs_update_inode(inode, &fattr);
+       status = nfs_refresh_inode(inode, &fattr);
        if (status) {
-               spin_unlock(&inode->i_lock);
                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;
        }
-       spin_unlock(&inode->i_lock);
 
        if (nfsi->cache_validity & NFS_INO_INVALID_ACL)
                nfs_zap_acl_cache(inode);
@@ -643,10 +723,6 @@ __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
                (long long)NFS_FILEID(inode));
 
  out:
-       nfs_wake_up_inode(inode);
-
- out_nowait:
-       unlock_kernel();
        return status;
 }
 
@@ -654,9 +730,9 @@ int nfs_attribute_timeout(struct inode *inode)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
 
-       if (nfs_have_delegation(inode, FMODE_READ))
+       if (nfs_have_delegated_attributes(inode))
                return 0;
-       return !time_in_range(jiffies, nfsi->read_cache_jiffies, nfsi->read_cache_jiffies + nfsi->attrtimeo);
+       return !time_in_range_open(jiffies, nfsi->read_cache_jiffies, nfsi->read_cache_jiffies + nfsi->attrtimeo);
 }
 
 /**
@@ -674,7 +750,7 @@ int nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
        return __nfs_revalidate_inode(server, inode);
 }
 
-static int nfs_invalidate_mapping_nolock(struct inode *inode, struct address_space *mapping)
+static int nfs_invalidate_mapping(struct inode *inode, struct address_space *mapping)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        
@@ -685,61 +761,20 @@ static int nfs_invalidate_mapping_nolock(struct inode *inode, struct address_spa
        }
        spin_lock(&inode->i_lock);
        nfsi->cache_validity &= ~NFS_INO_INVALID_DATA;
-       if (S_ISDIR(inode->i_mode)) {
+       if (S_ISDIR(inode->i_mode))
                memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));
-               /* This ensures we revalidate child dentries */
-               nfsi->cache_change_attribute = jiffies;
-       }
        spin_unlock(&inode->i_lock);
        nfs_inc_stats(inode, NFSIOS_DATAINVALIDATE);
+       nfs_fscache_reset_inode_cookie(inode);
        dfprintk(PAGECACHE, "NFS: (%s/%Ld) data cache invalidated\n",
                        inode->i_sb->s_id, (long long)NFS_FILEID(inode));
        return 0;
 }
 
-static int nfs_invalidate_mapping(struct inode *inode, struct address_space *mapping)
-{
-       int ret = 0;
-
-       mutex_lock(&inode->i_mutex);
-       if (NFS_I(inode)->cache_validity & NFS_INO_INVALID_DATA) {
-               ret = nfs_sync_mapping(mapping);
-               if (ret == 0)
-                       ret = nfs_invalidate_mapping_nolock(inode, mapping);
-       }
-       mutex_unlock(&inode->i_mutex);
-       return ret;
-}
-
-/**
- * nfs_revalidate_mapping_nolock - Revalidate the pagecache
- * @inode - pointer to host inode
- * @mapping - pointer to mapping
- */
-int nfs_revalidate_mapping_nolock(struct inode *inode, struct address_space *mapping)
-{
-       struct nfs_inode *nfsi = NFS_I(inode);
-       int ret = 0;
-
-       if ((nfsi->cache_validity & NFS_INO_REVAL_PAGECACHE)
-                       || nfs_attribute_timeout(inode) || NFS_STALE(inode)) {
-               ret = __nfs_revalidate_inode(NFS_SERVER(inode), inode);
-               if (ret < 0)
-                       goto out;
-       }
-       if (nfsi->cache_validity & NFS_INO_INVALID_DATA)
-               ret = nfs_invalidate_mapping_nolock(inode, mapping);
-out:
-       return ret;
-}
-
 /**
  * nfs_revalidate_mapping - Revalidate the pagecache
  * @inode - pointer to host inode
  * @mapping - pointer to mapping
- *
- * This version of the function will take the inode->i_mutex and attempt to
- * flush out all dirty data if it needs to invalidate the page cache.
  */
 int nfs_revalidate_mapping(struct inode *inode, struct address_space *mapping)
 {
@@ -758,57 +793,35 @@ out:
        return ret;
 }
 
-/**
- * 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
- * This will mark the inode as immediately needing revalidation
- * of its attribute cache.
- */
-void nfs_end_data_update(struct inode *inode)
-{
-       struct nfs_inode *nfsi = NFS_I(inode);
-
-       /* Directories: invalidate page cache */
-       if (S_ISDIR(inode->i_mode)) {
-               spin_lock(&inode->i_lock);
-               nfsi->cache_validity |= NFS_INO_INVALID_DATA;
-               spin_unlock(&inode->i_lock);
-       }
-       nfsi->cache_change_attribute = jiffies;
-       atomic_dec(&nfsi->data_updates);
-}
-
 static void nfs_wcc_update_inode(struct inode *inode, struct nfs_fattr *fattr)
 {
        struct nfs_inode *nfsi = NFS_I(inode);
-       unsigned long now = jiffies;
 
+       if ((fattr->valid & NFS_ATTR_FATTR_PRECHANGE)
+                       && (fattr->valid & NFS_ATTR_FATTR_CHANGE)
+                       && nfsi->change_attr == fattr->pre_change_attr) {
+               nfsi->change_attr = fattr->change_attr;
+               if (S_ISDIR(inode->i_mode))
+                       nfsi->cache_validity |= NFS_INO_INVALID_DATA;
+       }
        /* 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)) {
+       if ((fattr->valid & NFS_ATTR_FATTR_PRECTIME)
+                       && (fattr->valid & NFS_ATTR_FATTR_CTIME)
+                       && timespec_equal(&inode->i_ctime, &fattr->pre_ctime))
                        memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
-                       nfsi->cache_change_attribute = now;
-               }
-               if (timespec_equal(&inode->i_mtime, &fattr->pre_mtime)) {
+
+       if ((fattr->valid & NFS_ATTR_FATTR_PREMTIME)
+                       && (fattr->valid & NFS_ATTR_FATTR_MTIME)
+                       && timespec_equal(&inode->i_mtime, &fattr->pre_mtime)) {
                        memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
-                       nfsi->cache_change_attribute = now;
-               }
-               if (inode->i_size == fattr->pre_size && nfsi->npages == 0) {
-                       inode->i_size = fattr->size;
-                       nfsi->cache_change_attribute = now;
-               }
+                       if (S_ISDIR(inode->i_mode))
+                               nfsi->cache_validity |= NFS_INO_INVALID_DATA;
        }
+       if ((fattr->valid & NFS_ATTR_FATTR_PRESIZE)
+                       && (fattr->valid & NFS_ATTR_FATTR_SIZE)
+                       && i_size_read(inode) == nfs_size_to_loff_t(fattr->pre_size)
+                       && nfsi->npages == 0)
+                       i_size_write(inode, nfs_size_to_loff_t(fattr->size));
 }
 
 /**
@@ -824,51 +837,121 @@ static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fat
 {
        struct nfs_inode *nfsi = NFS_I(inode);
        loff_t cur_size, new_isize;
-       int data_unstable;
+       unsigned long invalid = 0;
 
 
        /* Has the inode gone and changed behind our back? */
-       if (nfsi->fileid != fattr->fileid
-                       || (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT)) {
+       if ((fattr->valid & NFS_ATTR_FATTR_FILEID) && nfsi->fileid != fattr->fileid)
+               return -EIO;
+       if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
                return -EIO;
-       }
-
-       /* Are we in the process of updating data on the server? */
-       data_unstable = nfs_caches_unstable(inode);
-
-       /* Do atomic weak cache consistency updates */
-       nfs_wcc_update_inode(inode, fattr);
 
-       if ((fattr->valid & NFS_ATTR_FATTR_V4) != 0 &&
+       if ((fattr->valid & NFS_ATTR_FATTR_CHANGE) != 0 &&
                        nfsi->change_attr != fattr->change_attr)
-               nfsi->cache_validity |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
+               invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
 
        /* Verify a few of the more important attributes */
-       if (!timespec_equal(&inode->i_mtime, &fattr->mtime))
-               nfsi->cache_validity |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
-
-       cur_size = i_size_read(inode);
-       new_isize = nfs_size_to_loff_t(fattr->size);
-       if (cur_size != new_isize && nfsi->npages == 0)
-               nfsi->cache_validity |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
+       if ((fattr->valid & NFS_ATTR_FATTR_MTIME) && !timespec_equal(&inode->i_mtime, &fattr->mtime))
+               invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
+
+       if (fattr->valid & NFS_ATTR_FATTR_SIZE) {
+               cur_size = i_size_read(inode);
+               new_isize = nfs_size_to_loff_t(fattr->size);
+               if (cur_size != new_isize && nfsi->npages == 0)
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
+       }
 
        /* 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->cache_validity |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL;
+       if ((fattr->valid & NFS_ATTR_FATTR_MODE) && (inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO))
+               invalid |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL;
+       if ((fattr->valid & NFS_ATTR_FATTR_OWNER) && inode->i_uid != fattr->uid)
+               invalid |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL;
+       if ((fattr->valid & NFS_ATTR_FATTR_GROUP) && inode->i_gid != fattr->gid)
+               invalid |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL;
 
        /* Has the link count changed? */
-       if (inode->i_nlink != fattr->nlink)
-               nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
+       if ((fattr->valid & NFS_ATTR_FATTR_NLINK) && inode->i_nlink != fattr->nlink)
+               invalid |= NFS_INO_INVALID_ATTR;
 
-       if (!timespec_equal(&inode->i_atime, &fattr->atime))
-               nfsi->cache_validity |= NFS_INO_INVALID_ATIME;
+       if ((fattr->valid & NFS_ATTR_FATTR_ATIME) && !timespec_equal(&inode->i_atime, &fattr->atime))
+               invalid |= NFS_INO_INVALID_ATIME;
+
+       if (invalid != 0)
+               nfsi->cache_validity |= invalid;
 
        nfsi->read_cache_jiffies = fattr->time_start;
        return 0;
 }
 
+static int nfs_ctime_need_update(const struct inode *inode, const struct nfs_fattr *fattr)
+{
+       if (!(fattr->valid & NFS_ATTR_FATTR_CTIME))
+               return 0;
+       return timespec_compare(&fattr->ctime, &inode->i_ctime) > 0;
+}
+
+static int nfs_size_need_update(const struct inode *inode, const struct nfs_fattr *fattr)
+{
+       if (!(fattr->valid & NFS_ATTR_FATTR_SIZE))
+               return 0;
+       return nfs_size_to_loff_t(fattr->size) > i_size_read(inode);
+}
+
+static atomic_long_t nfs_attr_generation_counter;
+
+static unsigned long nfs_read_attr_generation_counter(void)
+{
+       return atomic_long_read(&nfs_attr_generation_counter);
+}
+
+unsigned long nfs_inc_attr_generation_counter(void)
+{
+       return atomic_long_inc_return(&nfs_attr_generation_counter);
+}
+
+void nfs_fattr_init(struct nfs_fattr *fattr)
+{
+       fattr->valid = 0;
+       fattr->time_start = jiffies;
+       fattr->gencount = nfs_inc_attr_generation_counter();
+}
+
+/**
+ * nfs_inode_attrs_need_update - check if the inode attributes need updating
+ * @inode - pointer to inode
+ * @fattr - attributes
+ *
+ * Attempt to divine whether or not an RPC call reply carrying stale
+ * attributes got scheduled after another call carrying updated ones.
+ *
+ * To do so, the function first assumes that a more recent ctime means
+ * that the attributes in fattr are newer, however it also attempt to
+ * catch the case where ctime either didn't change, or went backwards
+ * (if someone reset the clock on the server) by looking at whether
+ * or not this RPC call was started after the inode was last updated.
+ * Note also the check for wraparound of 'attr_gencount'
+ *
+ * The function returns 'true' if it thinks the attributes in 'fattr' are
+ * more recent than the ones cached in the inode.
+ *
+ */
+static int nfs_inode_attrs_need_update(const struct inode *inode, const struct nfs_fattr *fattr)
+{
+       const struct nfs_inode *nfsi = NFS_I(inode);
+
+       return ((long)fattr->gencount - (long)nfsi->attr_gencount) > 0 ||
+               nfs_ctime_need_update(inode, fattr) ||
+               nfs_size_need_update(inode, fattr) ||
+               ((long)nfsi->attr_gencount - (long)nfs_read_attr_generation_counter() > 0);
+}
+
+static int nfs_refresh_inode_locked(struct inode *inode, struct nfs_fattr *fattr)
+{
+       if (nfs_inode_attrs_need_update(inode, fattr))
+               return nfs_update_inode(inode, fattr);
+       return nfs_check_inode_attributes(inode, fattr);
+}
+
 /**
  * nfs_refresh_inode - try to update the inode attribute cache
  * @inode - pointer to inode
@@ -881,21 +964,29 @@ static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fat
  */
 int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
 {
-       struct nfs_inode *nfsi = NFS_I(inode);
        int status;
 
        if ((fattr->valid & NFS_ATTR_FATTR) == 0)
                return 0;
        spin_lock(&inode->i_lock);
-       if (time_after(fattr->time_start, nfsi->last_updated))
-               status = nfs_update_inode(inode, fattr);
-       else
-               status = nfs_check_inode_attributes(inode, fattr);
-
+       status = nfs_refresh_inode_locked(inode, fattr);
        spin_unlock(&inode->i_lock);
+
        return status;
 }
 
+static int nfs_post_op_update_inode_locked(struct inode *inode, struct nfs_fattr *fattr)
+{
+       struct nfs_inode *nfsi = NFS_I(inode);
+
+       nfsi->cache_validity |= NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
+       if (S_ISDIR(inode->i_mode))
+               nfsi->cache_validity |= NFS_INO_INVALID_DATA;
+       if ((fattr->valid & NFS_ATTR_FATTR) == 0)
+               return 0;
+       return nfs_refresh_inode_locked(inode, fattr);
+}
+
 /**
  * nfs_post_op_update_inode - try to update the inode attribute cache
  * @inode - pointer to inode
@@ -912,16 +1003,61 @@ int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
  */
 int nfs_post_op_update_inode(struct inode *inode, struct nfs_fattr *fattr)
 {
-       struct nfs_inode *nfsi = NFS_I(inode);
-       int status = 0;
+       int status;
 
        spin_lock(&inode->i_lock);
-       if (unlikely((fattr->valid & NFS_ATTR_FATTR) == 0)) {
-               nfsi->cache_validity |= NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE;
-               goto out;
+       status = nfs_post_op_update_inode_locked(inode, fattr);
+       spin_unlock(&inode->i_lock);
+       return status;
+}
+
+/**
+ * nfs_post_op_update_inode_force_wcc - try to update the inode attribute cache
+ * @inode - pointer to inode
+ * @fattr - updated attributes
+ *
+ * After an operation that has changed the inode metadata, mark the
+ * attribute cache as being invalid, then try to update it. Fake up
+ * weak cache consistency data, if none exist.
+ *
+ * This function is mainly designed to be used by the ->write_done() functions.
+ */
+int nfs_post_op_update_inode_force_wcc(struct inode *inode, struct nfs_fattr *fattr)
+{
+       int status;
+
+       spin_lock(&inode->i_lock);
+       /* Don't do a WCC update if these attributes are already stale */
+       if ((fattr->valid & NFS_ATTR_FATTR) == 0 ||
+                       !nfs_inode_attrs_need_update(inode, fattr)) {
+               fattr->valid &= ~(NFS_ATTR_FATTR_PRECHANGE
+                               | NFS_ATTR_FATTR_PRESIZE
+                               | NFS_ATTR_FATTR_PREMTIME
+                               | NFS_ATTR_FATTR_PRECTIME);
+               goto out_noforce;
        }
-       status = nfs_update_inode(inode, fattr);
-out:
+       if ((fattr->valid & NFS_ATTR_FATTR_CHANGE) != 0 &&
+                       (fattr->valid & NFS_ATTR_FATTR_PRECHANGE) == 0) {
+               fattr->pre_change_attr = NFS_I(inode)->change_attr;
+               fattr->valid |= NFS_ATTR_FATTR_PRECHANGE;
+       }
+       if ((fattr->valid & NFS_ATTR_FATTR_CTIME) != 0 &&
+                       (fattr->valid & NFS_ATTR_FATTR_PRECTIME) == 0) {
+               memcpy(&fattr->pre_ctime, &inode->i_ctime, sizeof(fattr->pre_ctime));
+               fattr->valid |= NFS_ATTR_FATTR_PRECTIME;
+       }
+       if ((fattr->valid & NFS_ATTR_FATTR_MTIME) != 0 &&
+                       (fattr->valid & NFS_ATTR_FATTR_PREMTIME) == 0) {
+               memcpy(&fattr->pre_mtime, &inode->i_mtime, sizeof(fattr->pre_mtime));
+               fattr->valid |= NFS_ATTR_FATTR_PREMTIME;
+       }
+       if ((fattr->valid & NFS_ATTR_FATTR_SIZE) != 0 &&
+                       (fattr->valid & NFS_ATTR_FATTR_PRESIZE) == 0) {
+               fattr->pre_size = i_size_read(inode);
+               fattr->valid |= NFS_ATTR_FATTR_PRESIZE;
+       }
+out_noforce:
+       status = nfs_post_op_update_inode_locked(inode, fattr);
        spin_unlock(&inode->i_lock);
        return status;
 }
@@ -943,133 +1079,194 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
        struct nfs_server *server;
        struct nfs_inode *nfsi = NFS_I(inode);
        loff_t cur_isize, new_isize;
-       unsigned int    invalid = 0;
+       unsigned long invalid = 0;
        unsigned long now = jiffies;
-       int data_stable;
+       unsigned long save_cache_validity;
 
        dfprintk(VFS, "NFS: %s(%s/%ld ct=%d info=0x%x)\n",
-                       __FUNCTION__, inode->i_sb->s_id, inode->i_ino,
+                       __func__, inode->i_sb->s_id, inode->i_ino,
                        atomic_read(&inode->i_count), fattr->valid);
 
-       if (nfsi->fileid != fattr->fileid)
+       if ((fattr->valid & NFS_ATTR_FATTR_FILEID) && nfsi->fileid != fattr->fileid)
                goto out_fileid;
 
        /*
         * Make sure the inode's type hasn't changed.
         */
-       if ((inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
+       if ((fattr->valid & NFS_ATTR_FATTR_TYPE) && (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
                goto out_changed;
 
        server = NFS_SERVER(inode);
        /* Update the fsid? */
-       if (S_ISDIR(inode->i_mode)
-                       && !nfs_fsid_equal(&server->fsid, &fattr->fsid))
+       if (S_ISDIR(inode->i_mode) && (fattr->valid & NFS_ATTR_FATTR_FSID) &&
+                       !nfs_fsid_equal(&server->fsid, &fattr->fsid) &&
+                       !test_bit(NFS_INO_MOUNTPOINT, &nfsi->flags))
                server->fsid = fattr->fsid;
 
        /*
         * Update the read time so we don't revalidate too often.
         */
        nfsi->read_cache_jiffies = fattr->time_start;
-       nfsi->last_updated = now;
-
-       /* Fix a wraparound issue with nfsi->cache_change_attribute */
-       if (time_before(now, nfsi->cache_change_attribute))
-               nfsi->cache_change_attribute = now - 600*HZ;
 
-       /* Are we racing with known updates of the metadata on the server? */
-       data_stable = nfs_verify_change_attribute(inode, fattr->time_start);
-       if (data_stable)
-               nfsi->cache_validity &= ~(NFS_INO_INVALID_ATTR|NFS_INO_REVAL_PAGECACHE|NFS_INO_INVALID_ATIME);
+       save_cache_validity = nfsi->cache_validity;
+       nfsi->cache_validity &= ~(NFS_INO_INVALID_ATTR
+                       | NFS_INO_INVALID_ATIME
+                       | NFS_INO_REVAL_FORCED
+                       | NFS_INO_REVAL_PAGECACHE);
 
        /* Do atomic weak cache consistency updates */
        nfs_wcc_update_inode(inode, fattr);
 
-       /* Check if our cached file size is stale */
-       new_isize = nfs_size_to_loff_t(fattr->size);
-       cur_isize = i_size_read(inode);
-       if (new_isize != cur_isize) {
-               /* Do we perhaps have any outstanding writes? */
-               if (nfsi->npages == 0) {
-                       /* No, but did we race with nfs_end_data_update()? */
-                       if (data_stable) {
-                               inode->i_size = new_isize;
+       /* More cache consistency checks */
+       if (fattr->valid & NFS_ATTR_FATTR_CHANGE) {
+               if (nfsi->change_attr != fattr->change_attr) {
+                       dprintk("NFS: change_attr change on server for file %s/%ld\n",
+                                       inode->i_sb->s_id, inode->i_ino);
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
+                       if (S_ISDIR(inode->i_mode))
+                               nfs_force_lookup_revalidate(inode);
+                       nfsi->change_attr = fattr->change_attr;
+               }
+       } else if (server->caps & NFS_CAP_CHANGE_ATTR)
+               invalid |= save_cache_validity;
+
+       if (fattr->valid & NFS_ATTR_FATTR_MTIME) {
+               /* NFSv2/v3: Check if the mtime agrees */
+               if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) {
+                       dprintk("NFS: mtime change on server for file %s/%ld\n",
+                                       inode->i_sb->s_id, inode->i_ino);
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+                       if (S_ISDIR(inode->i_mode))
+                               nfs_force_lookup_revalidate(inode);
+                       memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
+               }
+       } else if (server->caps & NFS_CAP_MTIME)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_DATA
+                               | NFS_INO_REVAL_PAGECACHE
+                               | NFS_INO_REVAL_FORCED);
+
+       if (fattr->valid & NFS_ATTR_FATTR_CTIME) {
+               /* If ctime has changed we should definitely clear access+acl caches */
+               if (!timespec_equal(&inode->i_ctime, &fattr->ctime)) {
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
+                       /* and probably clear data for a directory too as utimes can cause
+                        * havoc with our cache.
+                        */
+                       if (S_ISDIR(inode->i_mode)) {
                                invalid |= NFS_INO_INVALID_DATA;
+                               nfs_force_lookup_revalidate(inode);
                        }
-                       invalid |= NFS_INO_INVALID_ATTR;
-               } else if (new_isize > cur_isize) {
-                       inode->i_size = new_isize;
-                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+                       memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
                }
-               nfsi->cache_change_attribute = now;
-               dprintk("NFS: isize change on server for file %s/%ld\n",
-                               inode->i_sb->s_id, inode->i_ino);
-       }
-
-       /* Check if the mtime agrees */
-       if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) {
-               memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
-               dprintk("NFS: mtime change on server for file %s/%ld\n",
-                               inode->i_sb->s_id, inode->i_ino);
-               invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
-               nfsi->cache_change_attribute = now;
-       }
-
-       /* If ctime has changed we should definitely clear access+acl caches */
-       if (!timespec_equal(&inode->i_ctime, &fattr->ctime)) {
-               invalid |= NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
-               memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
-               nfsi->cache_change_attribute = now;
-       }
-       memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime));
+       } else if (server->caps & NFS_CAP_CTIME)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL
+                               | NFS_INO_REVAL_FORCED);
 
-       if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO) ||
-           inode->i_uid != fattr->uid ||
-           inode->i_gid != fattr->gid)
-               invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
-
-       inode->i_mode = fattr->mode;
-       inode->i_nlink = fattr->nlink;
-       inode->i_uid = fattr->uid;
-       inode->i_gid = fattr->gid;
+       /* Check if our cached file size is stale */
+       if (fattr->valid & NFS_ATTR_FATTR_SIZE) {
+               new_isize = nfs_size_to_loff_t(fattr->size);
+               cur_isize = i_size_read(inode);
+               if (new_isize != cur_isize) {
+                       /* Do we perhaps have any outstanding writes, or has
+                        * the file grown beyond our last write? */
+                       if (nfsi->npages == 0 || new_isize > cur_isize) {
+                               i_size_write(inode, new_isize);
+                               invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
+                       }
+                       dprintk("NFS: isize change on server for file %s/%ld\n",
+                                       inode->i_sb->s_id, inode->i_ino);
+               }
+       } else
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_REVAL_PAGECACHE
+                               | NFS_INO_REVAL_FORCED);
+
+
+       if (fattr->valid & NFS_ATTR_FATTR_ATIME)
+               memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime));
+       else if (server->caps & NFS_CAP_ATIME)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATIME
+                               | NFS_INO_REVAL_FORCED);
+
+       if (fattr->valid & NFS_ATTR_FATTR_MODE) {
+               if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO)) {
+                       umode_t newmode = inode->i_mode & S_IFMT;
+                       newmode |= fattr->mode & S_IALLUGO;
+                       inode->i_mode = newmode;
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
+               }
+       } else if (server->caps & NFS_CAP_MODE)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL
+                               | NFS_INO_REVAL_FORCED);
+
+       if (fattr->valid & NFS_ATTR_FATTR_OWNER) {
+               if (inode->i_uid != fattr->uid) {
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
+                       inode->i_uid = fattr->uid;
+               }
+       } else if (server->caps & NFS_CAP_OWNER)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL
+                               | NFS_INO_REVAL_FORCED);
+
+       if (fattr->valid & NFS_ATTR_FATTR_GROUP) {
+               if (inode->i_gid != fattr->gid) {
+                       invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
+                       inode->i_gid = fattr->gid;
+               }
+       } else if (server->caps & NFS_CAP_OWNER_GROUP)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_INVALID_ACCESS
+                               | NFS_INO_INVALID_ACL
+                               | NFS_INO_REVAL_FORCED);
+
+       if (fattr->valid & NFS_ATTR_FATTR_NLINK) {
+               if (inode->i_nlink != fattr->nlink) {
+                       invalid |= NFS_INO_INVALID_ATTR;
+                       if (S_ISDIR(inode->i_mode))
+                               invalid |= NFS_INO_INVALID_DATA;
+                       inode->i_nlink = fattr->nlink;
+               }
+       } else if (server->caps & NFS_CAP_NLINK)
+               invalid |= save_cache_validity & (NFS_INO_INVALID_ATTR
+                               | NFS_INO_REVAL_FORCED);
 
-       if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) {
+       if (fattr->valid & NFS_ATTR_FATTR_SPACE_USED) {
                /*
                 * report the blocks in 512byte units
                 */
                inode->i_blocks = nfs_calc_block_size(fattr->du.nfs3.used);
-       } else {
-               inode->i_blocks = fattr->du.nfs2.blocks;
        }
-
-       if ((fattr->valid & NFS_ATTR_FATTR_V4) != 0 &&
-                       nfsi->change_attr != fattr->change_attr) {
-               dprintk("NFS: change_attr change on server for file %s/%ld\n",
-                               inode->i_sb->s_id, inode->i_ino);
-               nfsi->change_attr = fattr->change_attr;
-               invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
-               nfsi->cache_change_attribute = now;
-       }
+       if (fattr->valid & NFS_ATTR_FATTR_BLOCKS_USED)
+               inode->i_blocks = fattr->du.nfs2.blocks;
 
        /* Update attrtimeo value if we're out of the unstable period */
        if (invalid & NFS_INO_INVALID_ATTR) {
                nfs_inc_stats(inode, NFSIOS_ATTRINVALIDATE);
                nfsi->attrtimeo = NFS_MINATTRTIMEO(inode);
                nfsi->attrtimeo_timestamp = now;
-       } else if (!time_in_range(now, nfsi->attrtimeo_timestamp, nfsi->attrtimeo_timestamp + nfsi->attrtimeo)) {
-               if ((nfsi->attrtimeo <<= 1) > NFS_MAXATTRTIMEO(inode))
-                       nfsi->attrtimeo = NFS_MAXATTRTIMEO(inode);
-               nfsi->attrtimeo_timestamp = now;
+               nfsi->attr_gencount = nfs_inc_attr_generation_counter();
+       } else {
+               if (!time_in_range_open(now, nfsi->attrtimeo_timestamp, nfsi->attrtimeo_timestamp + nfsi->attrtimeo)) {
+                       if ((nfsi->attrtimeo <<= 1) > NFS_MAXATTRTIMEO(inode))
+                               nfsi->attrtimeo = NFS_MAXATTRTIMEO(inode);
+                       nfsi->attrtimeo_timestamp = now;
+               }
        }
+       invalid &= ~NFS_INO_INVALID_ATTR;
        /* 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;
-       if (data_stable)
-               invalid &= ~(NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME|NFS_INO_REVAL_PAGECACHE);
        if (!nfs_have_delegation(inode, FMODE_READ) ||
-                       (nfsi->cache_validity & NFS_INO_REVAL_FORCED))
+                       (save_cache_validity & NFS_INO_REVAL_FORCED))
                nfsi->cache_validity |= invalid;
-       nfsi->cache_validity &= ~NFS_INO_REVAL_FORCED;
 
        return 0;
  out_changed:
@@ -1077,7 +1274,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
         * Big trouble! The inode has become a different object.
         */
        printk(KERN_DEBUG "%s: inode %ld mode changed, %07o to %07o\n",
-                       __FUNCTION__, inode->i_ino, inode->i_mode, fattr->mode);
+                       __func__, inode->i_ino, inode->i_mode, fattr->mode);
  out_err:
        /*
         * No need to worry about unhashing the dentry, as the
@@ -1106,7 +1303,7 @@ static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
 void nfs4_clear_inode(struct inode *inode)
 {
        /* If we are holding a delegation, return it! */
-       nfs_inode_return_delegation(inode);
+       nfs_inode_return_delegation_noreclaim(inode);
        /* First call standard NFS clear_inode() code */
        nfs_clear_inode(inode);
 }
@@ -1145,7 +1342,7 @@ static inline void nfs4_init_once(struct nfs_inode *nfsi)
 #endif
 }
 
-static void init_once(void * foo, struct kmem_cache * cachep, unsigned long flags)
+static void init_once(void *foo)
 {
        struct nfs_inode *nfsi = (struct nfs_inode *) foo;
 
@@ -1154,9 +1351,11 @@ static void init_once(void * foo, struct kmem_cache * cachep, unsigned long flag
        INIT_LIST_HEAD(&nfsi->access_cache_entry_lru);
        INIT_LIST_HEAD(&nfsi->access_cache_inode_lru);
        INIT_RADIX_TREE(&nfsi->nfs_page_tree, GFP_ATOMIC);
-       atomic_set(&nfsi->data_updates, 0);
-       nfsi->ncommit = 0;
        nfsi->npages = 0;
+       nfsi->ncommit = 0;
+       atomic_set(&nfsi->silly_count, 1);
+       INIT_HLIST_HEAD(&nfsi->silly_list);
+       init_waitqueue_head(&nfsi->waitqueue);
        nfs4_init_once(nfsi);
 }
 
@@ -1178,6 +1377,36 @@ static void nfs_destroy_inodecache(void)
        kmem_cache_destroy(nfs_inode_cachep);
 }
 
+struct workqueue_struct *nfsiod_workqueue;
+
+/*
+ * start up the nfsiod workqueue
+ */
+static int nfsiod_start(void)
+{
+       struct workqueue_struct *wq;
+       dprintk("RPC:       creating workqueue nfsiod\n");
+       wq = create_singlethread_workqueue("nfsiod");
+       if (wq == NULL)
+               return -ENOMEM;
+       nfsiod_workqueue = wq;
+       return 0;
+}
+
+/*
+ * Destroy the nfsiod workqueue
+ */
+static void nfsiod_stop(void)
+{
+       struct workqueue_struct *wq;
+
+       wq = nfsiod_workqueue;
+       if (wq == NULL)
+               return;
+       nfsiod_workqueue = NULL;
+       destroy_workqueue(wq);
+}
+
 /*
  * Initialize NFS
  */
@@ -1185,6 +1414,18 @@ static int __init init_nfs_fs(void)
 {
        int err;
 
+       err = nfs_dns_resolver_init();
+       if (err < 0)
+               goto out8;
+
+       err = nfs_fscache_register();
+       if (err < 0)
+               goto out7;
+
+       err = nfsiod_start();
+       if (err)
+               goto out6;
+
        err = nfs_fs_proc_init();
        if (err)
                goto out5;
@@ -1231,6 +1472,12 @@ out3:
 out4:
        nfs_fs_proc_exit();
 out5:
+       nfsiod_stop();
+out6:
+       nfs_fscache_unregister();
+out7:
+       nfs_dns_resolver_destroy();
+out8:
        return err;
 }
 
@@ -1241,16 +1488,20 @@ static void __exit exit_nfs_fs(void)
        nfs_destroy_readpagecache();
        nfs_destroy_inodecache();
        nfs_destroy_nfspagecache();
+       nfs_fscache_unregister();
+       nfs_dns_resolver_destroy();
 #ifdef CONFIG_PROC_FS
        rpc_proc_unregister("nfs");
 #endif
        unregister_nfs_fs();
        nfs_fs_proc_exit();
+       nfsiod_stop();
 }
 
 /* Not quite true; I just maintain it */
 MODULE_AUTHOR("Olaf Kirch <okir@monad.swb.de>");
 MODULE_LICENSE("GPL");
+module_param(enable_ino64, bool, 0644);
 
 module_init(init_nfs_fs)
 module_exit(exit_nfs_fs)