fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / fs / nfs / nfs4state.c
index 9c3405a..5fffbdf 100644 (file)
  * subsequent patch.
  */
 
-#include <linux/config.h>
 #include <linux/slab.h>
+#include <linux/smp_lock.h>
 #include <linux/nfs_fs.h>
 #include <linux/nfs_idmap.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
 #include <linux/workqueue.h>
 #include <linux/bitops.h>
 
-#define OPENOWNER_POOL_SIZE    8
-
-static spinlock_t              state_spinlock = SPIN_LOCK_UNLOCKED;
+#include "nfs4_fs.h"
+#include "callback.h"
+#include "delegation.h"
+#include "internal.h"
 
-nfs4_stateid zero_stateid;
+#define OPENOWNER_POOL_SIZE    8
 
-#if 0
-nfs4_stateid one_stateid =
-       { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
-#endif
+const nfs4_stateid zero_stateid;
 
 static LIST_HEAD(nfs4_clientid_list);
 
-static void nfs4_recover_state(void *);
-extern void nfs4_renew_state(void *);
-
-void
-init_nfsv4_state(struct nfs_server *server)
-{
-       server->nfs4_state = NULL;
-       INIT_LIST_HEAD(&server->nfs4_siblings);
-}
-
-void
-destroy_nfsv4_state(struct nfs_server *server)
+static int nfs4_init_client(struct nfs_client *clp, struct rpc_cred *cred)
 {
-       if (server->mnt_path) {
-               kfree(server->mnt_path);
-               server->mnt_path = NULL;
-       }
-       if (server->nfs4_state) {
-               nfs4_put_client(server->nfs4_state);
-               server->nfs4_state = NULL;
-       }
-}
-
-/*
- * nfs4_get_client(): returns an empty client structure
- * nfs4_put_client(): drops reference to client structure
- *
- * Since these are allocated/deallocated very rarely, we don't
- * bother putting them in a slab cache...
- */
-static struct nfs4_client *
-nfs4_alloc_client(struct in_addr *addr)
-{
-       struct nfs4_client *clp;
-
-       if ((clp = kmalloc(sizeof(*clp), GFP_KERNEL))) {
-               memset(clp, 0, sizeof(*clp));
-               memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr));
-               init_rwsem(&clp->cl_sem);
-               INIT_LIST_HEAD(&clp->cl_state_owners);
-               INIT_LIST_HEAD(&clp->cl_unused);
-               spin_lock_init(&clp->cl_lock);
-               atomic_set(&clp->cl_count, 1);
-               INIT_WORK(&clp->cl_recoverd, nfs4_recover_state, clp);
-               INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp);
-               INIT_LIST_HEAD(&clp->cl_superblocks);
-               init_waitqueue_head(&clp->cl_waitq);
-               rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS4 client");
-               clp->cl_state = 1 << NFS4CLNT_NEW;
-       }
-       return clp;
-}
-
-static void
-nfs4_free_client(struct nfs4_client *clp)
-{
-       struct nfs4_state_owner *sp;
-
-       while (!list_empty(&clp->cl_unused)) {
-               sp = list_entry(clp->cl_unused.next,
-                               struct nfs4_state_owner,
-                               so_list);
-               list_del(&sp->so_list);
-               kfree(sp);
-       }
-       BUG_ON(!list_empty(&clp->cl_state_owners));
-       if (clp->cl_cred)
-               put_rpccred(clp->cl_cred);
-       nfs_idmap_delete(clp);
-       if (clp->cl_rpcclient)
-               rpc_shutdown_client(clp->cl_rpcclient);
-       kfree(clp);
-}
-
-struct nfs4_client *
-nfs4_get_client(struct in_addr *addr)
-{
-       struct nfs4_client *new, *clp = NULL;
-
-       new = nfs4_alloc_client(addr);
-       spin_lock(&state_spinlock);
-       list_for_each_entry(clp, &nfs4_clientid_list, cl_servers) {
-               if (memcmp(&clp->cl_addr, addr, sizeof(clp->cl_addr)) == 0)
-                       goto found;
-       }
-       if (new)
-               list_add(&new->cl_servers, &nfs4_clientid_list);
-       spin_unlock(&state_spinlock);
-       return new;
-found:
-       atomic_inc(&clp->cl_count);
-       spin_unlock(&state_spinlock);
-       if (new)
-               nfs4_free_client(new);
-       return clp;
-}
-
-void
-nfs4_put_client(struct nfs4_client *clp)
-{
-       if (!atomic_dec_and_lock(&clp->cl_count, &state_spinlock))
-               return;
-       list_del(&clp->cl_servers);
-       spin_unlock(&state_spinlock);
-       BUG_ON(!list_empty(&clp->cl_superblocks));
-       wake_up_all(&clp->cl_waitq);
-       rpc_wake_up(&clp->cl_rpcwaitq);
-       nfs4_kill_renewd(clp);
-       nfs4_free_client(clp);
+       int status = nfs4_proc_setclientid(clp, NFS4_CALLBACK,
+                       nfs_callback_tcpport, cred);
+       if (status == 0)
+               status = nfs4_proc_setclientid_confirm(clp, cred);
+       if (status == 0)
+               nfs4_schedule_state_renewal(clp);
+       return status;
 }
 
 u32
-nfs4_alloc_lockowner_id(struct nfs4_client *clp)
+nfs4_alloc_lockowner_id(struct nfs_client *clp)
 {
        return clp->cl_lockowner_id ++;
 }
 
 static struct nfs4_state_owner *
-nfs4_client_grab_unused(struct nfs4_client *clp, struct rpc_cred *cred)
+nfs4_client_grab_unused(struct nfs_client *clp, struct rpc_cred *cred)
 {
        struct nfs4_state_owner *sp = NULL;
 
@@ -185,14 +85,39 @@ nfs4_client_grab_unused(struct nfs4_client *clp, struct rpc_cred *cred)
                atomic_inc(&sp->so_count);
                sp->so_cred = cred;
                list_move(&sp->so_list, &clp->cl_state_owners);
-               sp->so_generation = clp->cl_generation;
                clp->cl_nunused--;
        }
        return sp;
 }
 
+struct rpc_cred *nfs4_get_renew_cred(struct nfs_client *clp)
+{
+       struct nfs4_state_owner *sp;
+       struct rpc_cred *cred = NULL;
+
+       list_for_each_entry(sp, &clp->cl_state_owners, so_list) {
+               if (list_empty(&sp->so_states))
+                       continue;
+               cred = get_rpccred(sp->so_cred);
+               break;
+       }
+       return cred;
+}
+
+struct rpc_cred *nfs4_get_setclientid_cred(struct nfs_client *clp)
+{
+       struct nfs4_state_owner *sp;
+
+       if (!list_empty(&clp->cl_state_owners)) {
+               sp = list_entry(clp->cl_state_owners.next,
+                               struct nfs4_state_owner, so_list);
+               return get_rpccred(sp->so_cred);
+       }
+       return NULL;
+}
+
 static struct nfs4_state_owner *
-nfs4_find_state_owner(struct nfs4_client *clp, struct rpc_cred *cred)
+nfs4_find_state_owner(struct nfs_client *clp, struct rpc_cred *cred)
 {
        struct nfs4_state_owner *sp, *res = NULL;
 
@@ -218,29 +143,36 @@ nfs4_alloc_state_owner(void)
 {
        struct nfs4_state_owner *sp;
 
-       sp = kmalloc(sizeof(*sp),GFP_KERNEL);
+       sp = kzalloc(sizeof(*sp),GFP_KERNEL);
        if (!sp)
                return NULL;
-       init_MUTEX(&sp->so_sema);
-       sp->so_seqid = 0;                 /* arbitrary */
+       spin_lock_init(&sp->so_lock);
        INIT_LIST_HEAD(&sp->so_states);
+       INIT_LIST_HEAD(&sp->so_delegations);
+       rpc_init_wait_queue(&sp->so_sequence.wait, "Seqid_waitqueue");
+       sp->so_seqid.sequence = &sp->so_sequence;
+       spin_lock_init(&sp->so_sequence.lock);
+       INIT_LIST_HEAD(&sp->so_sequence.list);
        atomic_set(&sp->so_count, 1);
        return sp;
 }
 
-static void
-nfs4_unhash_state_owner(struct nfs4_state_owner *sp)
+void
+nfs4_drop_state_owner(struct nfs4_state_owner *sp)
 {
-       struct nfs4_client *clp = sp->so_client;
+       struct nfs_client *clp = sp->so_client;
        spin_lock(&clp->cl_lock);
        list_del_init(&sp->so_list);
        spin_unlock(&clp->cl_lock);
 }
 
-struct nfs4_state_owner *
-nfs4_get_state_owner(struct nfs_server *server, struct rpc_cred *cred)
+/*
+ * Note: must be called with clp->cl_sem held in order to prevent races
+ *       with reboot recovery!
+ */
+struct nfs4_state_owner *nfs4_get_state_owner(struct nfs_server *server, struct rpc_cred *cred)
 {
-       struct nfs4_client *clp = server->nfs4_state;
+       struct nfs_client *clp = server->nfs_client;
        struct nfs4_state_owner *sp, *new;
 
        get_rpccred(cred);
@@ -254,25 +186,24 @@ nfs4_get_state_owner(struct nfs_server *server, struct rpc_cred *cred)
                new->so_client = clp;
                new->so_id = nfs4_alloc_lockowner_id(clp);
                new->so_cred = cred;
-               new->so_generation = clp->cl_generation;
                sp = new;
                new = NULL;
        }
        spin_unlock(&clp->cl_lock);
-       if (new)
-               kfree(new);
-       if (sp) {
-               if (!test_bit(NFS4CLNT_OK, &clp->cl_state))
-                       nfs4_wait_clnt_recover(server->client, clp);
-       } else
-               put_rpccred(cred);
-       return sp;
+       kfree(new);
+       if (sp != NULL)
+               return sp;
+       put_rpccred(cred);
+       return NULL;
 }
 
-void
-nfs4_put_state_owner(struct nfs4_state_owner *sp)
+/*
+ * Must be called with clp->cl_sem held in order to avoid races
+ * with state recovery...
+ */
+void nfs4_put_state_owner(struct nfs4_state_owner *sp)
 {
-       struct nfs4_client *clp = sp->so_client;
+       struct nfs_client *clp = sp->so_client;
        struct rpc_cred *cred = sp->so_cred;
 
        if (!atomic_dec_and_lock(&sp->so_count, &clp->cl_lock))
@@ -299,47 +230,30 @@ nfs4_alloc_open_state(void)
 {
        struct nfs4_state *state;
 
-       state = kmalloc(sizeof(*state), GFP_KERNEL);
+       state = kzalloc(sizeof(*state), GFP_KERNEL);
        if (!state)
                return NULL;
-       state->state = 0;
-       state->nreaders = 0;
-       state->nwriters = 0;
-       state->flags = 0;
-       memset(state->stateid.data, 0, sizeof(state->stateid.data));
        atomic_set(&state->count, 1);
        INIT_LIST_HEAD(&state->lock_states);
-       init_MUTEX(&state->lock_sema);
-       rwlock_init(&state->state_lock);
+       spin_lock_init(&state->state_lock);
        return state;
 }
 
-static struct nfs4_state *
-__nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode)
+void
+nfs4_state_set_mode_locked(struct nfs4_state *state, mode_t mode)
 {
-       struct nfs_inode *nfsi = NFS_I(inode);
-       struct nfs4_state *state;
-
-       mode &= (FMODE_READ|FMODE_WRITE);
-       list_for_each_entry(state, &nfsi->open_states, inode_states) {
-               if (state->owner->so_cred != cred)
-                       continue;
-               if ((mode & FMODE_READ) != 0 && state->nreaders == 0)
-                       continue;
-               if ((mode & FMODE_WRITE) != 0 && state->nwriters == 0)
-                       continue;
-               if ((state->state & mode) != mode)
-                       continue;
-               /* Add the state to the head of the inode's list */
-               list_move(&state->inode_states, &nfsi->open_states);
-               atomic_inc(&state->count);
-               if (mode & FMODE_READ)
-                       state->nreaders++;
+       if (state->state == mode)
+               return;
+       /* NB! List reordering - see the reclaim code for why.  */
+       if ((mode & FMODE_WRITE) != (state->state & FMODE_WRITE)) {
                if (mode & FMODE_WRITE)
-                       state->nwriters++;
-               return state;
+                       list_move(&state->open_states, &state->owner->so_states);
+               else
+                       list_move_tail(&state->open_states, &state->owner->so_states);
        }
-       return NULL;
+       if (mode == 0)
+               list_del_init(&state->inode_states);
+       state->state = mode;
 }
 
 static struct nfs4_state *
@@ -350,11 +264,9 @@ __nfs4_find_state_byowner(struct inode *inode, struct nfs4_state_owner *owner)
 
        list_for_each_entry(state, &nfsi->open_states, inode_states) {
                /* Is this in the process of being freed? */
-               if (state->nreaders == 0 && state->nwriters == 0)
+               if (state->state == 0)
                        continue;
                if (state->owner == owner) {
-                       /* Add the state to the head of the inode's list */
-                       list_move(&state->inode_states, &nfsi->open_states);
                        atomic_inc(&state->count);
                        return state;
                }
@@ -362,17 +274,6 @@ __nfs4_find_state_byowner(struct inode *inode, struct nfs4_state_owner *owner)
        return NULL;
 }
 
-struct nfs4_state *
-nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode)
-{
-       struct nfs4_state *state;
-
-       spin_lock(&inode->i_lock);
-       state = __nfs4_find_state(inode, cred, mode);
-       spin_unlock(&inode->i_lock);
-       return state;
-}
-
 static void
 nfs4_free_open_state(struct nfs4_state *state)
 {
@@ -391,19 +292,23 @@ nfs4_get_open_state(struct inode *inode, struct nfs4_state_owner *owner)
        if (state)
                goto out;
        new = nfs4_alloc_open_state();
+       spin_lock(&owner->so_lock);
        spin_lock(&inode->i_lock);
        state = __nfs4_find_state_byowner(inode, owner);
        if (state == NULL && new != NULL) {
                state = new;
-               /* Caller *must* be holding owner->so_sem */
-               list_add(&state->open_states, &owner->so_states);
                state->owner = owner;
                atomic_inc(&owner->so_count);
                list_add(&state->inode_states, &nfsi->open_states);
-               state->inode = inode;
+               state->inode = igrab(inode);
                spin_unlock(&inode->i_lock);
+               /* Note: The reclaim code dictates that we add stateless
+                * and read-only stateids to the end of the list */
+               list_add_tail(&state->open_states, &owner->so_states);
+               spin_unlock(&owner->so_lock);
        } else {
                spin_unlock(&inode->i_lock);
+               spin_unlock(&owner->so_lock);
                if (new)
                        nfs4_free_open_state(new);
        }
@@ -411,84 +316,69 @@ out:
        return state;
 }
 
-static void
-__nfs4_put_open_state(struct nfs4_state *state)
+/*
+ * Beware! Caller must be holding exactly one
+ * reference to clp->cl_sem!
+ */
+void nfs4_put_open_state(struct nfs4_state *state)
 {
        struct inode *inode = state->inode;
        struct nfs4_state_owner *owner = state->owner;
-       int status = 0;
 
-       if (!atomic_dec_and_lock(&state->count, &inode->i_lock)) {
-               up(&owner->so_sema);
+       if (!atomic_dec_and_lock(&state->count, &owner->so_lock))
                return;
-       }
+       spin_lock(&inode->i_lock);
        if (!list_empty(&state->inode_states))
                list_del(&state->inode_states);
-       spin_unlock(&inode->i_lock);
        list_del(&state->open_states);
-       if (state->state != 0) {
-               do {
-                       status = nfs4_do_close(inode, state);
-                       if (!status)
-                               break;
-                       up(&owner->so_sema);
-                       status = nfs4_handle_error(NFS_SERVER(inode), status);
-                       down(&owner->so_sema);
-               } while (!status);
-       }
-       up(&owner->so_sema);
+       spin_unlock(&inode->i_lock);
+       spin_unlock(&owner->so_lock);
+       iput(inode);
        nfs4_free_open_state(state);
        nfs4_put_state_owner(owner);
 }
 
-void
-nfs4_put_open_state(struct nfs4_state *state)
-{
-       down(&state->owner->so_sema);
-       __nfs4_put_open_state(state);
-}
-
-void
-nfs4_close_state(struct nfs4_state *state, mode_t mode)
+/*
+ * Close the current file.
+ */
+void nfs4_close_state(struct nfs4_state *state, mode_t mode)
 {
        struct inode *inode = state->inode;
        struct nfs4_state_owner *owner = state->owner;
-       int newstate;
-       int status = 0;
+       int oldstate, newstate = 0;
 
-       down(&owner->so_sema);
+       atomic_inc(&owner->so_count);
        /* Protect against nfs4_find_state() */
+       spin_lock(&owner->so_lock);
        spin_lock(&inode->i_lock);
-       if (mode & FMODE_READ)
-               state->nreaders--;
-       if (mode & FMODE_WRITE)
-               state->nwriters--;
-       if (state->nwriters == 0 && state->nreaders == 0)
-               list_del_init(&state->inode_states);
-       spin_unlock(&inode->i_lock);
-       do {
-               newstate = 0;
-               if (state->state == 0)
-                       break;
-               if (state->nreaders)
-                       newstate |= FMODE_READ;
-               if (state->nwriters)
-                       newstate |= FMODE_WRITE;
-               if (state->state == newstate)
+       switch (mode & (FMODE_READ | FMODE_WRITE)) {
+               case FMODE_READ:
+                       state->n_rdonly--;
                        break;
-               if (newstate != 0)
-                       status = nfs4_do_downgrade(inode, state, newstate);
-               else
-                       status = nfs4_do_close(inode, state);
-               if (!status) {
-                       state->state = newstate;
+               case FMODE_WRITE:
+                       state->n_wronly--;
                        break;
-               }
-               up(&owner->so_sema);
-               status = nfs4_handle_error(NFS_SERVER(inode), status);
-               down(&owner->so_sema);
-       } while (!status);
-       __nfs4_put_open_state(state);
+               case FMODE_READ|FMODE_WRITE:
+                       state->n_rdwr--;
+       }
+       oldstate = newstate = state->state;
+       if (state->n_rdwr == 0) {
+               if (state->n_rdonly == 0)
+                       newstate &= ~FMODE_READ;
+               if (state->n_wronly == 0)
+                       newstate &= ~FMODE_WRITE;
+       }
+       if (test_bit(NFS_DELEGATED_STATE, &state->flags)) {
+               nfs4_state_set_mode_locked(state, newstate);
+               oldstate = newstate;
+       }
+       spin_unlock(&inode->i_lock);
+       spin_unlock(&owner->so_lock);
+
+       if (oldstate != newstate && nfs4_do_close(inode, state) == 0)
+               return;
+       nfs4_put_open_state(state);
+       nfs4_put_state_owner(owner);
 }
 
 /*
@@ -508,230 +398,276 @@ __nfs4_find_lock_state(struct nfs4_state *state, fl_owner_t fl_owner)
        return NULL;
 }
 
-struct nfs4_lock_state *
-nfs4_find_lock_state(struct nfs4_state *state, fl_owner_t fl_owner)
-{
-       struct nfs4_lock_state *lsp;
-       read_lock(&state->state_lock);
-       lsp = __nfs4_find_lock_state(state, fl_owner);
-       read_unlock(&state->state_lock);
-       return lsp;
-}
-
 /*
  * Return a compatible lock_state. If no initialized lock_state structure
  * exists, return an uninitialized one.
  *
- * The caller must be holding state->lock_sema
  */
-struct nfs4_lock_state *
-nfs4_alloc_lock_state(struct nfs4_state *state, fl_owner_t fl_owner)
+static struct nfs4_lock_state *nfs4_alloc_lock_state(struct nfs4_state *state, fl_owner_t fl_owner)
 {
        struct nfs4_lock_state *lsp;
-       struct nfs4_client *clp = state->owner->so_client;
+       struct nfs_client *clp = state->owner->so_client;
 
-       lsp = kmalloc(sizeof(*lsp), GFP_KERNEL);
+       lsp = kzalloc(sizeof(*lsp), GFP_KERNEL);
        if (lsp == NULL)
                return NULL;
-       lsp->ls_seqid = 0;      /* arbitrary */
-       lsp->ls_id = -1; 
-       memset(lsp->ls_stateid.data, 0, sizeof(lsp->ls_stateid.data));
+       lsp->ls_seqid.sequence = &state->owner->so_sequence;
        atomic_set(&lsp->ls_count, 1);
        lsp->ls_owner = fl_owner;
-       lsp->ls_parent = state;
-       INIT_LIST_HEAD(&lsp->ls_locks);
        spin_lock(&clp->cl_lock);
        lsp->ls_id = nfs4_alloc_lockowner_id(clp);
        spin_unlock(&clp->cl_lock);
+       INIT_LIST_HEAD(&lsp->ls_locks);
        return lsp;
 }
 
 /*
- * Byte-range lock aware utility to initialize the stateid of read/write
- * requests.
+ * Return a compatible lock_state. If no initialized lock_state structure
+ * exists, return an uninitialized one.
+ *
+ * The caller must be holding clp->cl_sem
  */
-void
-nfs4_copy_stateid(nfs4_stateid *dst, struct nfs4_state *state, fl_owner_t fl_owner)
-{
-       if (test_bit(LK_STATE_IN_USE, &state->flags)) {
-               struct nfs4_lock_state *lsp;
-
-               lsp = nfs4_find_lock_state(state, fl_owner);
-               if (lsp) {
-                       memcpy(dst, &lsp->ls_stateid, sizeof(*dst));
-                       nfs4_put_lock_state(lsp);
-                       return;
+static struct nfs4_lock_state *nfs4_get_lock_state(struct nfs4_state *state, fl_owner_t owner)
+{
+       struct nfs4_lock_state *lsp, *new = NULL;
+       
+       for(;;) {
+               spin_lock(&state->state_lock);
+               lsp = __nfs4_find_lock_state(state, owner);
+               if (lsp != NULL)
+                       break;
+               if (new != NULL) {
+                       new->ls_state = state;
+                       list_add(&new->ls_locks, &state->lock_states);
+                       set_bit(LK_STATE_IN_USE, &state->flags);
+                       lsp = new;
+                       new = NULL;
+                       break;
                }
+               spin_unlock(&state->state_lock);
+               new = nfs4_alloc_lock_state(state, owner);
+               if (new == NULL)
+                       return NULL;
        }
-       memcpy(dst, &state->stateid, sizeof(*dst));
+       spin_unlock(&state->state_lock);
+       kfree(new);
+       return lsp;
 }
 
 /*
-* Called with state->lock_sema held.
-*/
-void
-nfs4_increment_lock_seqid(int status, struct nfs4_lock_state *lsp)
+ * Release reference to lock_state, and free it if we see that
+ * it is no longer in use
+ */
+void nfs4_put_lock_state(struct nfs4_lock_state *lsp)
 {
-       if (status == NFS_OK || seqid_mutating_err(-status))
-               lsp->ls_seqid++;
+       struct nfs4_state *state;
+
+       if (lsp == NULL)
+               return;
+       state = lsp->ls_state;
+       if (!atomic_dec_and_lock(&lsp->ls_count, &state->state_lock))
+               return;
+       list_del(&lsp->ls_locks);
+       if (list_empty(&state->lock_states))
+               clear_bit(LK_STATE_IN_USE, &state->flags);
+       spin_unlock(&state->state_lock);
+       kfree(lsp);
 }
 
-/* 
-* Check to see if the request lock (type FL_UNLK) effects the fl lock.
-*
-* fl and request must have the same posix owner
-*
-* return: 
-* 0 -> fl not effected by request
-* 1 -> fl consumed by request
-*/
+static void nfs4_fl_copy_lock(struct file_lock *dst, struct file_lock *src)
+{
+       struct nfs4_lock_state *lsp = src->fl_u.nfs4_fl.owner;
 
-static int
-nfs4_check_unlock(struct file_lock *fl, struct file_lock *request)
+       dst->fl_u.nfs4_fl.owner = lsp;
+       atomic_inc(&lsp->ls_count);
+}
+
+static void nfs4_fl_release_lock(struct file_lock *fl)
 {
-       if (fl->fl_start >= request->fl_start && fl->fl_end <= request->fl_end)
-               return 1;
+       nfs4_put_lock_state(fl->fl_u.nfs4_fl.owner);
+}
+
+static struct file_lock_operations nfs4_fl_lock_ops = {
+       .fl_copy_lock = nfs4_fl_copy_lock,
+       .fl_release_private = nfs4_fl_release_lock,
+};
+
+int nfs4_set_lock_state(struct nfs4_state *state, struct file_lock *fl)
+{
+       struct nfs4_lock_state *lsp;
+
+       if (fl->fl_ops != NULL)
+               return 0;
+       lsp = nfs4_get_lock_state(state, fl->fl_owner);
+       if (lsp == NULL)
+               return -ENOMEM;
+       fl->fl_u.nfs4_fl.owner = lsp;
+       fl->fl_ops = &nfs4_fl_lock_ops;
        return 0;
 }
 
 /*
- * Post an initialized lock_state on the state->lock_states list.
+ * Byte-range lock aware utility to initialize the stateid of read/write
+ * requests.
  */
-void
-nfs4_notify_setlk(struct inode *inode, struct file_lock *request, struct nfs4_lock_state *lsp)
+void nfs4_copy_stateid(nfs4_stateid *dst, struct nfs4_state *state, fl_owner_t fl_owner)
 {
-       struct nfs4_state *state = lsp->ls_parent;
+       struct nfs4_lock_state *lsp;
 
-       if (!list_empty(&lsp->ls_locks))
+       memcpy(dst, &state->stateid, sizeof(*dst));
+       if (test_bit(LK_STATE_IN_USE, &state->flags) == 0)
                return;
-       write_lock(&state->state_lock);
-       list_add(&lsp->ls_locks, &state->lock_states);
-       set_bit(LK_STATE_IN_USE, &state->flags);
-       write_unlock(&state->state_lock);
+
+       spin_lock(&state->state_lock);
+       lsp = __nfs4_find_lock_state(state, fl_owner);
+       if (lsp != NULL && (lsp->ls_flags & NFS_LOCK_INITIALIZED) != 0)
+               memcpy(dst, &lsp->ls_stateid, sizeof(*dst));
+       spin_unlock(&state->state_lock);
+       nfs4_put_lock_state(lsp);
 }
 
-/* 
- * to decide to 'reap' lock state:
- * 1) search i_flock for file_locks with fl.lock_state = to ls.
- * 2) determine if unlock will consume found lock. 
- *     if so, reap
- *
- *     else, don't reap.
- *
- */
-void
-nfs4_notify_unlck(struct inode *inode, struct file_lock *request, struct nfs4_lock_state *lsp)
+struct nfs_seqid *nfs_alloc_seqid(struct nfs_seqid_counter *counter)
 {
-       struct nfs4_state *state = lsp->ls_parent;
-       struct file_lock *fl;
+       struct rpc_sequence *sequence = counter->sequence;
+       struct nfs_seqid *new;
 
-       for (fl = inode->i_flock; fl != NULL; fl = fl->fl_next) {
-               if (!(fl->fl_flags & FL_POSIX))
-                       continue;
-               if (fl->fl_owner != lsp->ls_owner)
-                       continue;
-               /* Exit if we find at least one lock which is not consumed */
-               if (nfs4_check_unlock(fl,request) == 0)
-                       return;
+       new = kmalloc(sizeof(*new), GFP_KERNEL);
+       if (new != NULL) {
+               new->sequence = counter;
+               spin_lock(&sequence->lock);
+               list_add_tail(&new->list, &sequence->list);
+               spin_unlock(&sequence->lock);
        }
+       return new;
+}
 
-       write_lock(&state->state_lock);
-       list_del_init(&lsp->ls_locks);
-       if (list_empty(&state->lock_states))
-               clear_bit(LK_STATE_IN_USE, &state->flags);
-       write_unlock(&state->state_lock);
+void nfs_free_seqid(struct nfs_seqid *seqid)
+{
+       struct rpc_sequence *sequence = seqid->sequence->sequence;
+
+       spin_lock(&sequence->lock);
+       list_del(&seqid->list);
+       spin_unlock(&sequence->lock);
+       rpc_wake_up(&sequence->wait);
+       kfree(seqid);
 }
 
 /*
- * Release reference to lock_state, and free it if we see that
- * it is no longer in use
+ * Increment the seqid if the OPEN/OPEN_DOWNGRADE/CLOSE succeeded, or
+ * failed with a seqid incrementing error -
+ * see comments nfs_fs.h:seqid_mutating_error()
  */
-void
-nfs4_put_lock_state(struct nfs4_lock_state *lsp)
+static inline void nfs_increment_seqid(int status, struct nfs_seqid *seqid)
 {
-       if (!atomic_dec_and_test(&lsp->ls_count))
-               return;
-       if (!list_empty(&lsp->ls_locks))
-               return;
-       kfree(lsp);
+       switch (status) {
+               case 0:
+                       break;
+               case -NFS4ERR_BAD_SEQID:
+               case -NFS4ERR_STALE_CLIENTID:
+               case -NFS4ERR_STALE_STATEID:
+               case -NFS4ERR_BAD_STATEID:
+               case -NFS4ERR_BADXDR:
+               case -NFS4ERR_RESOURCE:
+               case -NFS4ERR_NOFILEHANDLE:
+                       /* Non-seqid mutating errors */
+                       return;
+       };
+       /*
+        * Note: no locking needed as we are guaranteed to be first
+        * on the sequence list
+        */
+       seqid->sequence->counter++;
 }
 
-/*
-* Called with sp->so_sema held.
-*
-* Increment the seqid if the OPEN/OPEN_DOWNGRADE/CLOSE succeeded, or
-* failed with a seqid incrementing error -
-* see comments nfs_fs.h:seqid_mutating_error()
-*/
-void
-nfs4_increment_seqid(int status, struct nfs4_state_owner *sp)
+void nfs_increment_open_seqid(int status, struct nfs_seqid *seqid)
 {
-       if (status == NFS_OK || seqid_mutating_err(-status))
-               sp->so_seqid++;
-       /* If the server returns BAD_SEQID, unhash state_owner here */
-       if (status == -NFS4ERR_BAD_SEQID)
-               nfs4_unhash_state_owner(sp);
+       if (status == -NFS4ERR_BAD_SEQID) {
+               struct nfs4_state_owner *sp = container_of(seqid->sequence,
+                               struct nfs4_state_owner, so_seqid);
+               nfs4_drop_state_owner(sp);
+       }
+       return nfs_increment_seqid(status, seqid);
 }
 
-static int reclaimer(void *);
-struct reclaimer_args {
-       struct nfs4_client *clp;
-       struct completion complete;
-};
-
 /*
- * State recovery routine
+ * Increment the seqid if the LOCK/LOCKU succeeded, or
+ * failed with a seqid incrementing error -
+ * see comments nfs_fs.h:seqid_mutating_error()
  */
-void
-nfs4_recover_state(void *data)
+void nfs_increment_lock_seqid(int status, struct nfs_seqid *seqid)
 {
-       struct nfs4_client *clp = (struct nfs4_client *)data;
-       struct reclaimer_args args = {
-               .clp = clp,
-       };
-       might_sleep();
+       return nfs_increment_seqid(status, seqid);
+}
 
-       init_completion(&args.complete);
+int nfs_wait_on_sequence(struct nfs_seqid *seqid, struct rpc_task *task)
+{
+       struct rpc_sequence *sequence = seqid->sequence->sequence;
+       int status = 0;
 
-       down_read(&clp->cl_sem);
-       if (test_and_set_bit(NFS4CLNT_SETUP_STATE, &clp->cl_state))
-               goto out_failed;
-       if (kernel_thread(reclaimer, &args, CLONE_KERNEL) < 0)
-               goto out_failed_clear;
-       wait_for_completion(&args.complete);
-       return;
-out_failed_clear:
+       if (sequence->list.next == &seqid->list)
+               goto out;
+       spin_lock(&sequence->lock);
+       if (sequence->list.next != &seqid->list) {
+               rpc_sleep_on(&sequence->wait, task, NULL, NULL);
+               status = -EAGAIN;
+       }
+       spin_unlock(&sequence->lock);
+out:
+       return status;
+}
+
+static int reclaimer(void *);
+
+static inline void nfs4_clear_recover_bit(struct nfs_client *clp)
+{
        smp_mb__before_clear_bit();
-       clear_bit(NFS4CLNT_SETUP_STATE, &clp->cl_state);
+       clear_bit(NFS4CLNT_STATE_RECOVER, &clp->cl_state);
        smp_mb__after_clear_bit();
-       wake_up_all(&clp->cl_waitq);
+       wake_up_bit(&clp->cl_state, NFS4CLNT_STATE_RECOVER);
        rpc_wake_up(&clp->cl_rpcwaitq);
-out_failed:
-       up_read(&clp->cl_sem);
+}
+
+/*
+ * State recovery routine
+ */
+static void nfs4_recover_state(struct nfs_client *clp)
+{
+       struct task_struct *task;
+
+       __module_get(THIS_MODULE);
+       atomic_inc(&clp->cl_count);
+       task = kthread_run(reclaimer, clp, "%u.%u.%u.%u-reclaim",
+                       NIPQUAD(clp->cl_addr.sin_addr));
+       if (!IS_ERR(task))
+               return;
+       nfs4_clear_recover_bit(clp);
+       nfs_put_client(clp);
+       module_put(THIS_MODULE);
 }
 
 /*
  * Schedule a state recovery attempt
  */
-void
-nfs4_schedule_state_recovery(struct nfs4_client *clp)
+void nfs4_schedule_state_recovery(struct nfs_client *clp)
 {
        if (!clp)
                return;
-       smp_mb__before_clear_bit();
-       clear_bit(NFS4CLNT_OK, &clp->cl_state);
-       smp_mb__after_clear_bit();
-       schedule_work(&clp->cl_recoverd);
+       if (test_and_set_bit(NFS4CLNT_STATE_RECOVER, &clp->cl_state) == 0)
+               nfs4_recover_state(clp);
 }
 
-static int
-nfs4_reclaim_open_state(struct nfs4_state_owner *sp)
+static int nfs4_reclaim_locks(struct nfs4_state_recovery_ops *ops, struct nfs4_state *state)
 {
-       struct nfs4_state *state;
+       struct inode *inode = state->inode;
+       struct file_lock *fl;
        int status = 0;
 
-       list_for_each_entry(state, &sp->so_states, open_states) {
-               status = nfs4_open_reclaim(sp, state);
+       for (fl = inode->i_flock; fl != 0; fl = fl->fl_next) {
+               if (!(fl->fl_flags & (FL_POSIX|FL_FLOCK)))
+                       continue;
+               if (((struct nfs_open_context *)fl->fl_file->private_data)->state != state)
+                       continue;
+               status = ops->recover_lock(state, fl);
                if (status >= 0)
                        continue;
                switch (status) {
@@ -741,6 +677,53 @@ nfs4_reclaim_open_state(struct nfs4_state_owner *sp)
                        case -NFS4ERR_EXPIRED:
                        case -NFS4ERR_NO_GRACE:
                        case -NFS4ERR_RECLAIM_BAD:
+                       case -NFS4ERR_RECLAIM_CONFLICT:
+                               /* kill_proc(fl->fl_pid, SIGLOST, 1); */
+                               break;
+                       case -NFS4ERR_STALE_CLIENTID:
+                               goto out_err;
+               }
+       }
+       return 0;
+out_err:
+       return status;
+}
+
+static int nfs4_reclaim_open_state(struct nfs4_state_recovery_ops *ops, struct nfs4_state_owner *sp)
+{
+       struct nfs4_state *state;
+       struct nfs4_lock_state *lock;
+       int status = 0;
+
+       /* Note: we rely on the sp->so_states list being ordered 
+        * so that we always reclaim open(O_RDWR) and/or open(O_WRITE)
+        * states first.
+        * This is needed to ensure that the server won't give us any
+        * read delegations that we have to return if, say, we are
+        * recovering after a network partition or a reboot from a
+        * server that doesn't support a grace period.
+        */
+       list_for_each_entry(state, &sp->so_states, open_states) {
+               if (state->state == 0)
+                       continue;
+               status = ops->recover_open(sp, state);
+               if (status >= 0) {
+                       status = nfs4_reclaim_locks(ops, state);
+                       if (status < 0)
+                               goto out_err;
+                       list_for_each_entry(lock, &state->lock_states, ls_locks) {
+                               if (!(lock->ls_flags & NFS_LOCK_INITIALIZED))
+                                       printk("%s: Lock reclaim failed!\n",
+                                                       __FUNCTION__);
+                       }
+                       continue;
+               }
+               switch (status) {
+                       default:
+                               printk(KERN_ERR "%s: unhandled error %d. Zeroing state\n",
+                                               __FUNCTION__, status);
+                       case -ENOENT:
+                       case -NFS4ERR_RECLAIM_BAD:
                        case -NFS4ERR_RECLAIM_CONFLICT:
                                /*
                                 * Open state on this file cannot be recovered
@@ -751,6 +734,8 @@ nfs4_reclaim_open_state(struct nfs4_state_owner *sp)
                                /* Mark the file as being 'closed' */
                                state->state = 0;
                                break;
+                       case -NFS4ERR_EXPIRED:
+                       case -NFS4ERR_NO_GRACE:
                        case -NFS4ERR_STALE_CLIENTID:
                                goto out_err;
                }
@@ -760,75 +745,104 @@ out_err:
        return status;
 }
 
-static int
-reclaimer(void *ptr)
+static void nfs4_state_mark_reclaim(struct nfs_client *clp)
 {
-       struct reclaimer_args *args = (struct reclaimer_args *)ptr;
-       struct nfs4_client *clp = args->clp;
        struct nfs4_state_owner *sp;
-       int generation;
-       int status;
+       struct nfs4_state *state;
+       struct nfs4_lock_state *lock;
 
-       daemonize("%u.%u.%u.%u-reclaim", NIPQUAD(clp->cl_addr));
-       allow_signal(SIGKILL);
+       /* Reset all sequence ids to zero */
+       list_for_each_entry(sp, &clp->cl_state_owners, so_list) {
+               sp->so_seqid.counter = 0;
+               sp->so_seqid.flags = 0;
+               spin_lock(&sp->so_lock);
+               list_for_each_entry(state, &sp->so_states, open_states) {
+                       list_for_each_entry(lock, &state->lock_states, ls_locks) {
+                               lock->ls_seqid.counter = 0;
+                               lock->ls_seqid.flags = 0;
+                               lock->ls_flags &= ~NFS_LOCK_INITIALIZED;
+                       }
+               }
+               spin_unlock(&sp->so_lock);
+       }
+}
+
+static int reclaimer(void *ptr)
+{
+       struct nfs_client *clp = ptr;
+       struct nfs4_state_owner *sp;
+       struct nfs4_state_recovery_ops *ops;
+       struct rpc_cred *cred;
+       int status = 0;
 
-       complete(&args->complete);
+       allow_signal(SIGKILL);
 
+       /* Ensure exclusive access to NFSv4 state */
+       lock_kernel();
+       down_write(&clp->cl_sem);
        /* Are there any NFS mounts out there? */
        if (list_empty(&clp->cl_superblocks))
                goto out;
-       if (!test_bit(NFS4CLNT_NEW, &clp->cl_state)) {
-               status = nfs4_proc_renew(clp);
-               if (status == 0) {
-                       set_bit(NFS4CLNT_OK, &clp->cl_state);
-                       goto out;
+restart_loop:
+       ops = &nfs4_network_partition_recovery_ops;
+       /* Are there any open files on this volume? */
+       cred = nfs4_get_renew_cred(clp);
+       if (cred != NULL) {
+               /* Yes there are: try to renew the old lease */
+               status = nfs4_proc_renew(clp, cred);
+               switch (status) {
+                       case 0:
+                       case -NFS4ERR_CB_PATH_DOWN:
+                               put_rpccred(cred);
+                               goto out;
+                       case -NFS4ERR_STALE_CLIENTID:
+                       case -NFS4ERR_LEASE_MOVED:
+                               ops = &nfs4_reboot_recovery_ops;
                }
+       } else {
+               /* "reboot" to ensure we clear all state on the server */
+               clp->cl_boot_time = CURRENT_TIME;
+               cred = nfs4_get_setclientid_cred(clp);
+       }
+       /* We're going to have to re-establish a clientid */
+       nfs4_state_mark_reclaim(clp);
+       status = -ENOENT;
+       if (cred != NULL) {
+               status = nfs4_init_client(clp, cred);
+               put_rpccred(cred);
        }
-       status = nfs4_proc_setclientid(clp, 0, 0);
-       if (status)
-               goto out_error;
-       status = nfs4_proc_setclientid_confirm(clp);
        if (status)
                goto out_error;
-       generation = ++(clp->cl_generation);
-       clear_bit(NFS4CLNT_NEW, &clp->cl_state);
-       set_bit(NFS4CLNT_OK, &clp->cl_state);
-       up_read(&clp->cl_sem);
-       nfs4_schedule_state_renewal(clp);
-restart_loop:
-       spin_lock(&clp->cl_lock);
+       /* Mark all delegations for reclaim */
+       nfs_delegation_mark_reclaim(clp);
+       /* Note: list is protected by exclusive lock on cl->cl_sem */
        list_for_each_entry(sp, &clp->cl_state_owners, so_list) {
-               if (sp->so_generation - generation >= 0)
-                       continue;
-               atomic_inc(&sp->so_count);
-               spin_unlock(&clp->cl_lock);
-               down(&sp->so_sema);
-               if (sp->so_generation - generation < 0) {
-                       smp_rmb();
-                       sp->so_generation = clp->cl_generation;
-                       status = nfs4_reclaim_open_state(sp);
-               }
-               up(&sp->so_sema);
-               nfs4_put_state_owner(sp);
+               status = nfs4_reclaim_open_state(ops, sp);
                if (status < 0) {
+                       if (status == -NFS4ERR_NO_GRACE) {
+                               ops = &nfs4_network_partition_recovery_ops;
+                               status = nfs4_reclaim_open_state(ops, sp);
+                       }
                        if (status == -NFS4ERR_STALE_CLIENTID)
-                               nfs4_schedule_state_recovery(clp);
-                       goto out;
+                               goto restart_loop;
+                       if (status == -NFS4ERR_EXPIRED)
+                               goto restart_loop;
                }
-               goto restart_loop;
        }
-       spin_unlock(&clp->cl_lock);
+       nfs_delegation_reap_unclaimed(clp);
 out:
-       smp_mb__before_clear_bit();
-       clear_bit(NFS4CLNT_SETUP_STATE, &clp->cl_state);
-       smp_mb__after_clear_bit();
-       wake_up_all(&clp->cl_waitq);
-       rpc_wake_up(&clp->cl_rpcwaitq);
+       up_write(&clp->cl_sem);
+       unlock_kernel();
+       if (status == -NFS4ERR_CB_PATH_DOWN)
+               nfs_handle_cb_pathdown(clp);
+       nfs4_clear_recover_bit(clp);
+       nfs_put_client(clp);
+       module_put_and_exit(0);
        return 0;
 out_error:
-       printk(KERN_WARNING "Error: state recovery failed on NFSv4 server %u.%u.%u.%u\n",
-                               NIPQUAD(clp->cl_addr.s_addr));
-       up_read(&clp->cl_sem);
+       printk(KERN_WARNING "Error: state recovery failed on NFSv4 server %u.%u.%u.%u with error %d\n",
+                               NIPQUAD(clp->cl_addr.sin_addr), -status);
+       set_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
        goto out;
 }