NFSv4: Fix a race between open() and close()

 We must not remove the nfs4_state structure from the inode open lists
 before we are in sequence lock.

 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c
index 2d5a6a2..959374d 100644
--- a/fs/nfs/nfs4state.c
+++ b/fs/nfs/nfs4state.c
@@ -366,6 +366,23 @@
 	return state;
 }
 
+void
+nfs4_state_set_mode_locked(struct nfs4_state *state, mode_t mode)
+{
+	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)
+			list_move(&state->open_states, &state->owner->so_states);
+		else
+			list_move_tail(&state->open_states, &state->owner->so_states);
+	}
+	if (mode == 0)
+		list_del_init(&state->inode_states);
+	state->state = mode;
+}
+
 static struct nfs4_state *
 __nfs4_find_state(struct inode *inode, struct rpc_cred *cred, mode_t mode)
 {
@@ -376,10 +393,6 @@
 	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;
 		atomic_inc(&state->count);
@@ -400,7 +413,7 @@
 
 	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) {
 			atomic_inc(&state->count);
@@ -481,7 +494,6 @@
 	spin_unlock(&inode->i_lock);
 	spin_unlock(&owner->so_lock);
 	iput(inode);
-	BUG_ON (state->state != 0);
 	nfs4_free_open_state(state);
 	nfs4_put_state_owner(owner);
 }
@@ -493,7 +505,7 @@
 {
 	struct inode *inode = state->inode;
 	struct nfs4_state_owner *owner = state->owner;
-	int newstate;
+	int oldstate, newstate = 0;
 
 	atomic_inc(&owner->so_count);
 	/* Protect against nfs4_find_state() */
@@ -503,30 +515,20 @@
 		state->nreaders--;
 	if (mode & FMODE_WRITE)
 		state->nwriters--;
-	if (state->nwriters == 0) {
-		if (state->nreaders == 0)
-			list_del_init(&state->inode_states);
-		/* See reclaim code */
-		list_move_tail(&state->open_states, &owner->so_states);
+	oldstate = newstate = state->state;
+	if (state->nreaders == 0)
+		newstate &= ~FMODE_READ;
+	if (state->nwriters == 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);
-	newstate = 0;
-	if (state->state != 0) {
-		if (state->nreaders)
-			newstate |= FMODE_READ;
-		if (state->nwriters)
-			newstate |= FMODE_WRITE;
-		if (state->state == newstate)
-			goto out;
-		if (test_bit(NFS_DELEGATED_STATE, &state->flags)) {
-			state->state = newstate;
-			goto out;
-		}
-		if (nfs4_do_close(inode, state, newstate) == 0)
-			return;
-	}
-out:
+
+	if (oldstate != newstate && nfs4_do_close(inode, state) == 0)
+		return;
 	nfs4_put_open_state(state);
 	nfs4_put_state_owner(owner);
 }