NFSv4: Fix problem with OPEN_DOWNGRADE

 RFC 3530 states that for OPEN_DOWNGRADE "The share_access and share_deny
 bits specified must be exactly equal to the union of the share_access and
 share_deny bits specified for some subset of the OPENs in effect for
 current openowner on the current file.

 Setattr is currently violating the NFSv4 rules for OPEN_DOWNGRADE in that
 it may cause a downgrade from OPEN4_SHARE_ACCESS_BOTH to
 OPEN4_SHARE_ACCESS_WRITE despite the fact that there exists no open file
 with O_WRONLY access mode.

 Fix the problem by replacing nfs4_find_state() with a modified version of
 nfs_find_open_context().

 Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index fc0f12b..24d2fbf 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -1009,13 +1009,18 @@
 	spin_unlock(&inode->i_lock);
 }
 
-struct nfs_open_context *nfs_find_open_context(struct inode *inode, int mode)
+/*
+ * 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_inode *nfsi = NFS_I(inode);
 	struct nfs_open_context *pos, *ctx = NULL;
 
 	spin_lock(&inode->i_lock);
 	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;
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index 5396902..b7f262d 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -247,7 +247,6 @@
 extern struct nfs4_state * nfs4_get_open_state(struct inode *, struct nfs4_state_owner *);
 extern void nfs4_put_open_state(struct nfs4_state *);
 extern void nfs4_close_state(struct nfs4_state *, mode_t);
-extern struct nfs4_state *nfs4_find_state(struct inode *, struct rpc_cred *, mode_t mode);
 extern void nfs4_state_set_mode_locked(struct nfs4_state *, mode_t);
 extern void nfs4_schedule_state_recovery(struct nfs4_client *);
 extern void nfs4_put_lock_state(struct nfs4_lock_state *lsp);
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 02fddd0..9e492c2 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -214,7 +214,7 @@
 	struct inode *inode = state->inode;
 
 	open_flags &= (FMODE_READ|FMODE_WRITE);
-	/* Protect against nfs4_find_state() */
+	/* Protect against nfs4_find_state_byowner() */
 	spin_lock(&state->owner->so_lock);
 	spin_lock(&inode->i_lock);
 	memcpy(&state->stateid, stateid, sizeof(state->stateid));
@@ -1274,7 +1274,8 @@
 {
 	struct rpc_cred *cred;
 	struct inode *inode = dentry->d_inode;
-	struct nfs4_state *state;
+	struct nfs_open_context *ctx;
+	struct nfs4_state *state = NULL;
 	int status;
 
 	nfs_fattr_init(fattr);
@@ -1282,22 +1283,18 @@
 	cred = rpcauth_lookupcred(NFS_SERVER(inode)->client->cl_auth, 0);
 	if (IS_ERR(cred))
 		return PTR_ERR(cred);
-	/* Search for an existing WRITE delegation first */
-	state = nfs4_open_delegated(inode, FMODE_WRITE, cred);
-	if (!IS_ERR(state)) {
-		/* NB: nfs4_open_delegated() bumps the inode->i_count */
-		iput(inode);
-	} else {
-		/* Search for an existing open(O_WRITE) stateid */
-		state = nfs4_find_state(inode, cred, FMODE_WRITE);
-	}
+
+	/* Search for an existing open(O_WRITE) file */
+	ctx = nfs_find_open_context(inode, cred, FMODE_WRITE);
+	if (ctx != NULL)
+		state = ctx->state;
 
 	status = nfs4_do_setattr(NFS_SERVER(inode), fattr,
 			NFS_FH(inode), sattr, state);
 	if (status == 0)
 		nfs_setattr_update_inode(inode, sattr);
-	if (state != NULL)
-		nfs4_close_state(state, FMODE_WRITE);
+	if (ctx != NULL)
+		put_nfs_open_context(ctx);
 	put_rpccred(cred);
 	return status;
 }
diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c
index 959374d..81d964b 100644
--- a/fs/nfs/nfs4state.c
+++ b/fs/nfs/nfs4state.c
@@ -384,28 +384,6 @@
 }
 
 static struct nfs4_state *
-__nfs4_find_state(struct inode *inode, struct rpc_cred *cred, 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 ((state->state & mode) != mode)
-			continue;
-		atomic_inc(&state->count);
-		if (mode & FMODE_READ)
-			state->nreaders++;
-		if (mode & FMODE_WRITE)
-			state->nwriters++;
-		return state;
-	}
-	return NULL;
-}
-
-static struct nfs4_state *
 __nfs4_find_state_byowner(struct inode *inode, struct nfs4_state_owner *owner)
 {
 	struct nfs_inode *nfsi = NFS_I(inode);
@@ -423,17 +401,6 @@
 	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)
 {
diff --git a/fs/nfs/read.c b/fs/nfs/read.c
index 43b03b1..5f20eaf 100644
--- a/fs/nfs/read.c
+++ b/fs/nfs/read.c
@@ -507,7 +507,7 @@
 		goto out_error;
 
 	if (file == NULL) {
-		ctx = nfs_find_open_context(inode, FMODE_READ);
+		ctx = nfs_find_open_context(inode, NULL, FMODE_READ);
 		if (ctx == NULL)
 			return -EBADF;
 	} else
@@ -576,7 +576,7 @@
 			nr_pages);
 
 	if (filp == NULL) {
-		desc.ctx = nfs_find_open_context(inode, FMODE_READ);
+		desc.ctx = nfs_find_open_context(inode, NULL, FMODE_READ);
 		if (desc.ctx == NULL)
 			return -EBADF;
 	} else
diff --git a/fs/nfs/write.c b/fs/nfs/write.c
index 819a65f..1bdbd4f 100644
--- a/fs/nfs/write.c
+++ b/fs/nfs/write.c
@@ -294,7 +294,7 @@
 	if (page->index >= end_index+1 || !offset)
 		goto out;
 do_it:
-	ctx = nfs_find_open_context(inode, FMODE_WRITE);
+	ctx = nfs_find_open_context(inode, NULL, FMODE_WRITE);
 	if (ctx == NULL) {
 		err = -EBADF;
 		goto out;
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index 325fe7a..12787a9 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -316,7 +316,7 @@
 extern struct nfs_open_context *get_nfs_open_context(struct nfs_open_context *ctx);
 extern void put_nfs_open_context(struct nfs_open_context *ctx);
 extern void nfs_file_set_open_context(struct file *filp, struct nfs_open_context *ctx);
-extern struct nfs_open_context *nfs_find_open_context(struct inode *inode, int mode);
+extern struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_cred *cred, int mode);
 extern void nfs_file_clear_open_context(struct file *filp);
 
 /* linux/net/ipv4/ipconfig.c: trims ip addr off front of name, too. */