diff mbox

[v7,7/7] NFSv4: Add O_DENY* open flags support

Message ID 1389953232-9428-8-git-send-email-piastry@etersoft.ru (mailing list archive)
State New, archived
Headers show

Commit Message

Pavel Shilovsky Jan. 17, 2014, 10:07 a.m. UTC
Pass O_DENY* flags to NFSv4 open request. Make it return
-ESHAREDENIED on share conflicts with other opens and disable
O_DENYDELETE support since NFSv4 doesn't support it.

Add extra file descriptor counters for every fmode|denymode
value into nfs4_state. Make the client not repeat the previous
open requests to the server during delegation recall because
of possible conflicts with deny modes.

Signed-off-by: Pavel Shilovsky <piastry@etersoft.ru>
---
 fs/nfs/dir.c       |   39 ++++++++++++++-
 fs/nfs/inode.c     |    3 +-
 fs/nfs/internal.h  |    3 +-
 fs/nfs/nfs4_fs.h   |   48 ++++++++++++++++--
 fs/nfs/nfs4file.c  |    8 ++-
 fs/nfs/nfs4proc.c  |  138 +++++++++++++++++++++++++++++++++++++++++++---------
 fs/nfs/nfs4state.c |   33 +++++++++++--
 fs/nfs/nfs4super.c |    9 ++--
 fs/nfs/nfs4xdr.c   |   14 +++++-
 fs/nfs/super.c     |    7 ++-
 10 files changed, 261 insertions(+), 41 deletions(-)
diff mbox

Patch

diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index fe0c7bb..627f9ea 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1362,7 +1362,8 @@  static fmode_t flags_to_mode(int flags)
 
 static struct nfs_open_context *create_nfs_open_context(struct dentry *dentry, int open_flags)
 {
-	return alloc_nfs_open_context(dentry, flags_to_mode(open_flags), 0);
+	return alloc_nfs_open_context(dentry, flags_to_mode(open_flags),
+				      open_flags & (O_DENYREAD|O_DENYWRITE));
 }
 
 static int do_open(struct inode *inode, struct file *filp)
@@ -1411,6 +1412,10 @@  int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
 	if (err)
 		return err;
 
+	/* No support for O_DENYDELETE */
+	if (open_flags & O_DENYDELETE)
+		return -EINVAL;
+
 	/* NFS only supports OPEN on regular files */
 	if ((open_flags & O_DIRECTORY)) {
 		if (!d_unhashed(dentry)) {
@@ -2256,7 +2261,37 @@  static int nfs_open_permission_mask(int openflags)
 
 int nfs_may_open(struct inode *inode, struct rpc_cred *cred, int openflags)
 {
-	return nfs_do_access(inode, cred, nfs_open_permission_mask(openflags));
+	int ret;
+	fmode_t mode = OPEN_FMODE(openflags);
+	struct nfs_inode *nfsi = NFS_I(inode);
+	struct nfs4_state *state;
+
+	ret = nfs_do_access(inode, cred, nfs_open_permission_mask(openflags));
+	if (ret)
+		goto out;
+
+	if (!IS_SHARELOCK(inode))
+		goto out;
+
+	spin_lock(&inode->i_lock);
+	/* Check for share reservation conflicts */
+	list_for_each_entry(state, &nfsi->open_states, inode_states) {
+		if ((state->state & FMODE_READ) && (openflags & O_DENYREAD))
+			goto out_set_err;
+		if ((state->state & FMODE_WRITE) && (openflags & O_DENYWRITE))
+			goto out_set_err;
+		if ((mode & FMODE_READ) && (state->deny_state & O_DENYREAD))
+			goto out_set_err;
+		if ((mode & FMODE_WRITE) && (state->deny_state & O_DENYWRITE))
+			goto out_set_err;
+	}
+	spin_unlock(&inode->i_lock);
+out:
+	return ret;
+out_set_err:
+	spin_unlock(&inode->i_lock);
+	ret = -ESHAREDENIED;
+	goto out;
 }
 EXPORT_SYMBOL_GPL(nfs_may_open);
 
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 82f8593..a228dda 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -846,7 +846,8 @@  int nfs_open(struct inode *inode, struct file *filp)
 {
 	struct nfs_open_context *ctx;
 
-	ctx = alloc_nfs_open_context(filp->f_path.dentry, filp->f_mode, 0);
+	ctx = alloc_nfs_open_context(filp->f_path.dentry, filp->f_mode,
+				     filp->f_flags & (O_DENYREAD|O_DENYWRITE));
 	if (IS_ERR(ctx))
 		return PTR_ERR(ctx);
 	nfs_file_set_open_context(filp, ctx);
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index 8b5cc04..98f95fd 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -7,7 +7,8 @@ 
 #include <linux/security.h>
 #include <linux/crc32.h>
 
-#define NFS_MS_MASK (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS)
+#define NFS_MS_MASK (MS_RDONLY | MS_NOSUID | MS_NODEV | MS_NOEXEC | \
+		     MS_SYNCHRONOUS | MS_SHARELOCK)
 
 struct nfs_string;
 
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index c455acb..ca86f66 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -164,6 +164,10 @@  enum {
 	NFS_STATE_RECLAIM_NOGRACE,	/* OPEN stateid needs to recover state */
 	NFS_STATE_POSIX_LOCKS,		/* Posix locks are supported */
 	NFS_STATE_RECOVERY_FAILED,	/* OPEN stateid state recovery failed */
+	NFS_O_DENYNONE_STATE,		/* OPEN stateid has deny none state */
+	NFS_O_DENYREAD_STATE,		/* OPEN stateid has deny read state */
+	NFS_O_DENYWRITE_STATE,		/* OPEN stateid has deny write state */
+	NFS_O_DENYRDWR_STATE,		/* OPEN stateid has deny rw state */
 };
 
 struct nfs4_state {
@@ -181,10 +185,19 @@  struct nfs4_state {
 	nfs4_stateid stateid;		/* Current stateid: may be delegation */
 	nfs4_stateid open_stateid;	/* OPEN stateid */
 
-	/* The following 3 fields are protected by owner->so_lock */
+	/* The following 12 fields are protected by owner->so_lock */
 	unsigned int n_rdonly;		/* Number of read-only references */
 	unsigned int n_wronly;		/* Number of write-only references */
 	unsigned int n_rdwr;		/* Number of read/write references */
+	unsigned int n_ro_dr;		/* Number of read/denyread refs */
+	unsigned int n_wo_dr;		/* Number of write/denyread refs */
+	unsigned int n_rw_dr;		/* Number of rw/denyread references */
+	unsigned int n_ro_dw;		/* Number of read/denywrite refs */
+	unsigned int n_wo_dw;		/* Number of write/denywrite refs */
+	unsigned int n_rw_dw;		/* Number of rw/denywrite references */
+	unsigned int n_ro_drw;		/* Number of read/denyrw references */
+	unsigned int n_wo_drw;		/* Number of write/denyrw references */
+	unsigned int n_rw_drw;		/* Number of rw/denyrw references */
 
 	fmode_t state;			/* State on the server (R,W, or RW) */
 	unsigned int deny_state;	/* Deny state on the server */
@@ -512,11 +525,38 @@  get_state_n(struct nfs4_state *state, fmode_t mode, unsigned int deny_mode)
 {
 	switch (mode & (FMODE_READ|FMODE_WRITE)) {
 	case FMODE_READ:
-		return &state->n_rdonly;
+		switch (deny_mode & (O_DENYREAD|O_DENYWRITE)) {
+		case 0:
+			return &state->n_rdonly;
+		case O_DENYREAD:
+			return &state->n_ro_dr;
+		case O_DENYWRITE:
+			return &state->n_ro_dw;
+		case O_DENYREAD|O_DENYWRITE:
+			return &state->n_ro_drw;
+		}
 	case FMODE_WRITE:
-		return &state->n_wronly;
+		switch (deny_mode & (O_DENYREAD|O_DENYWRITE)) {
+		case 0:
+			return &state->n_wronly;
+		case O_DENYREAD:
+			return &state->n_wo_dr;
+		case O_DENYWRITE:
+			return &state->n_wo_dw;
+		case O_DENYREAD|O_DENYWRITE:
+			return &state->n_wo_drw;
+		}
 	case FMODE_READ|FMODE_WRITE:
-		return &state->n_rdwr;
+		switch (deny_mode & (O_DENYREAD|O_DENYWRITE)) {
+		case 0:
+			return &state->n_rdwr;
+		case O_DENYREAD:
+			return &state->n_rw_dr;
+		case O_DENYWRITE:
+			return &state->n_rw_dw;
+		case O_DENYREAD|O_DENYWRITE:
+			return &state->n_rw_drw;
+		}
 	}
 	return NULL;
 }
diff --git a/fs/nfs/nfs4file.c b/fs/nfs/nfs4file.c
index 5f444f0..bfea7fa 100644
--- a/fs/nfs/nfs4file.c
+++ b/fs/nfs/nfs4file.c
@@ -33,6 +33,10 @@  nfs4_file_open(struct inode *inode, struct file *filp)
 
 	dprintk("NFS: open file(%pd2)\n", dentry);
 
+	/* No support for O_DENYDELETE */
+	if (openflags & O_DENYDELETE)
+		return -EINVAL;
+
 	if ((openflags & O_ACCMODE) == 3)
 		openflags--;
 
@@ -42,7 +46,8 @@  nfs4_file_open(struct inode *inode, struct file *filp)
 	parent = dget_parent(dentry);
 	dir = parent->d_inode;
 
-	ctx = alloc_nfs_open_context(filp->f_path.dentry, filp->f_mode, 0);
+	ctx = alloc_nfs_open_context(filp->f_path.dentry, filp->f_mode,
+				     filp->f_flags & (O_DENYREAD|O_DENYWRITE));
 	err = PTR_ERR(ctx);
 	if (IS_ERR(ctx))
 		goto out;
@@ -63,6 +68,7 @@  nfs4_file_open(struct inode *inode, struct file *filp)
 		case -EDQUOT:
 		case -ENOSPC:
 		case -EROFS:
+		case -ESHAREDENIED:
 			goto out_put_ctx;
 		default:
 			goto out_drop;
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 1b6f1fe..8fafe59 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -155,7 +155,7 @@  static int nfs4_map_errors(int err)
 	case -NFS4ERR_BADNAME:
 		return -EINVAL;
 	case -NFS4ERR_SHARE_DENIED:
-		return -EACCES;
+		return -ESHAREDENIED;
 	case -NFS4ERR_MINOR_VERS_MISMATCH:
 		return -EPROTONOSUPPORT;
 	case -NFS4ERR_ACCESS:
@@ -996,6 +996,8 @@  static struct nfs4_opendata *nfs4_opendata_alloc(struct dentry *dentry,
 	p->owner = sp;
 	atomic_inc(&sp->so_count);
 	p->o_arg.open_flags = flags;
+	if (!IS_SHARELOCK(dir))
+		p->o_arg.open_flags &= ~(O_DENYREAD | O_DENYWRITE);
 	p->o_arg.fmode = fmode & (FMODE_READ|FMODE_WRITE);
 	/* don't put an ACCESS op in OPEN compound if O_EXCL, because ACCESS
 	 * will return permission denied for all bits until close */
@@ -1100,6 +1102,20 @@  fmode_to_state_bit(fmode_t mode)
 	}
 }
 
+static inline unsigned int
+denymode_to_state_bit(unsigned int deny_mode)
+{
+	switch (deny_mode & (O_DENYREAD|O_DENYWRITE)) {
+	case O_DENYREAD:
+		return NFS_O_DENYREAD_STATE;
+	case O_DENYWRITE:
+		return NFS_O_DENYWRITE_STATE;
+	case O_DENYREAD|O_DENYWRITE:
+		return NFS_O_DENYRDWR_STATE;
+	}
+	return NFS_O_DENYNONE_STATE;
+}
+
 static int can_open_cached(struct nfs4_state *state, fmode_t mode, int open_mode)
 {
 	int ret = 0;
@@ -1108,6 +1124,15 @@  static int can_open_cached(struct nfs4_state *state, fmode_t mode, int open_mode
 	if (open_mode & (O_EXCL|O_TRUNC))
 		goto out;
 
+	if ((state->state & FMODE_READ) && (open_mode & O_DENYREAD))
+		goto out;
+	if ((state->state & FMODE_WRITE) && (open_mode & O_DENYWRITE))
+		goto out;
+	if ((mode & FMODE_READ) && (state->deny_state & O_DENYREAD))
+		goto out;
+	if ((mode & FMODE_WRITE) && (state->deny_state & O_DENYWRITE))
+		goto out;
+
 	state_n = get_state_n(state, mode, open_mode);
 	if (state_n == NULL)
 		goto out;
@@ -1116,17 +1141,22 @@  static int can_open_cached(struct nfs4_state *state, fmode_t mode, int open_mode
 		goto out;
 
 	ret |= test_bit(fmode_to_state_bit(mode), &state->flags) != 0 &&
-		*state_n != 0;
+		test_bit(denymode_to_state_bit(open_mode), &state->flags) != 0
+		&& *state_n != 0;
 out:
 	return ret;
 }
 
-static int can_open_delegated(struct nfs_delegation *delegation, fmode_t fmode)
+static int
+can_open_delegated(struct nfs_delegation *delegation, fmode_t fmode,
+		   unsigned int deny_mode)
 {
 	if (delegation == NULL)
 		return 0;
 	if ((delegation->type & fmode) != fmode)
 		return 0;
+	if ((deny_mode & O_DENYREAD) && (delegation->type == FMODE_READ))
+		return 0;
 	if (test_bit(NFS_DELEGATION_NEED_RECLAIM, &delegation->flags))
 		return 0;
 	if (test_bit(NFS_DELEGATION_RETURNING, &delegation->flags))
@@ -1154,6 +1184,7 @@  nfs_set_open_stateid_locked(struct nfs4_state *state, nfs4_stateid *stateid,
 	set_bit(NFS_OPEN_STATE, &state->flags);
 	if ((fmode & (FMODE_READ|FMODE_WRITE)) != 0)
 		set_bit(fmode_to_state_bit(fmode), &state->flags);
+	set_bit(denymode_to_state_bit(deny_mode), &state->flags);
 }
 
 static void
@@ -1255,7 +1286,7 @@  static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
 	struct nfs_delegation *delegation;
 	int open_mode = opendata->o_arg.open_flags;
 	fmode_t fmode = opendata->o_arg.fmode;
-	unsigned int deny_mode = 0;
+	unsigned int deny_mode = open_mode & (O_DENYREAD|O_DENYWRITE);
 	nfs4_stateid stateid;
 	int ret = -EAGAIN;
 
@@ -1271,7 +1302,7 @@  static struct nfs4_state *nfs4_try_open_cached(struct nfs4_opendata *opendata)
 		}
 		rcu_read_lock();
 		delegation = rcu_dereference(nfsi->delegation);
-		if (!can_open_delegated(delegation, fmode)) {
+		if (!can_open_delegated(delegation, fmode, deny_mode)) {
 			rcu_read_unlock();
 			break;
 		}
@@ -1353,7 +1384,8 @@  _nfs4_opendata_reclaim_to_nfs4_state(struct nfs4_opendata *data)
 		nfs4_opendata_check_deleg(data, state);
 update:
 	update_open_stateid(state, &data->o_res.stateid, NULL,
-			    data->o_arg.fmode, 0);
+			    data->o_arg.fmode,
+			    data->o_arg.open_flags & (O_DENYREAD|O_DENYWRITE));
 	atomic_inc(&state->count);
 
 	return state;
@@ -1388,7 +1420,8 @@  _nfs4_opendata_to_nfs4_state(struct nfs4_opendata *data)
 	if (data->o_res.delegation_type != 0)
 		nfs4_opendata_check_deleg(data, state);
 	update_open_stateid(state, &data->o_res.stateid, NULL,
-			    data->o_arg.fmode, 0);
+			    data->o_arg.fmode,
+			    data->o_arg.open_flags & (O_DENYREAD|O_DENYWRITE));
 	iput(inode);
 out:
 	nfs_release_seqid(data->o_arg.seqid);
@@ -1461,14 +1494,20 @@  nfs4_open_recover_helper(struct nfs4_opendata *opendata, fmode_t fmode,
 	return 0;
 }
 
+/*
+ * Recover an open state on the server. @reset indicates if we need to
+ * flush all opens or just those that were cached localy.
+ */
 static int
-nfs4_open_recover(struct nfs4_opendata *opendata, struct nfs4_state *state)
+nfs4_open_recover(struct nfs4_opendata *opendata, struct nfs4_state *state,
+		  bool reset)
 {
 	struct nfs4_state *newstate = NULL;
 	int ret;
 	unsigned int fm, dm;
 	fmode_t fmodes[] = {FMODE_READ, FMODE_WRITE, FMODE_READ|FMODE_WRITE};
-	unsigned int dmodes[] = {0};
+	unsigned int dmodes[] = {0, O_DENYREAD, O_DENYWRITE,
+				 O_DENYREAD|O_DENYWRITE};
 
 	/* memory barrier prior to reading state->n_* */
 	clear_bit(NFS_DELEGATED_STATE, &state->flags);
@@ -1478,14 +1517,21 @@  nfs4_open_recover(struct nfs4_opendata *opendata, struct nfs4_state *state)
 	for (fm = 0; fm < 3; fm++) {
 		unsigned int fmode_bit = fmode_to_state_bit(fmodes[fm]);
 
-		for (dm = 0; dm < 1; dm++) {
+		for (dm = 0; dm < 4; dm++) {
 			unsigned int *state_n;
+			unsigned int deny_bit;
 
 			state_n = get_state_n(state, fmodes[fm], dmodes[dm]);
 			if (state_n == NULL || *state_n == 0)
 				continue;
 
+			deny_bit = denymode_to_state_bit(dmodes[dm]);
+			if (!reset && test_bit(fmode_bit, &state->flags) &&
+			    test_bit(deny_bit, &state->flags))
+				continue;
+
 			clear_bit(fmode_bit, &state->flags);
+			clear_bit(deny_bit, &state->flags);
 
 			ret = nfs4_open_recover_helper(opendata, fmodes[fm],
 						       dmodes[dm], &newstate);
@@ -1530,7 +1576,7 @@  static int _nfs4_do_open_reclaim(struct nfs_open_context *ctx, struct nfs4_state
 		delegation_type = delegation->type;
 	rcu_read_unlock();
 	opendata->o_arg.u.delegation_type = delegation_type;
-	status = nfs4_open_recover(opendata, state);
+	status = nfs4_open_recover(opendata, state, true);
 	nfs4_opendata_put(opendata);
 	return status;
 }
@@ -1628,7 +1674,7 @@  int nfs4_open_delegation_recall(struct nfs_open_context *ctx, struct nfs4_state
 	if (IS_ERR(opendata))
 		return PTR_ERR(opendata);
 	nfs4_stateid_copy(&opendata->o_arg.u.delegation, stateid);
-	err = nfs4_open_recover(opendata, state);
+	err = nfs4_open_recover(opendata, state, false);
 	nfs4_opendata_put(opendata);
 	return nfs4_handle_delegation_recall_error(server, state, stateid, err);
 }
@@ -1669,7 +1715,8 @@  static void nfs4_open_confirm_release(void *calldata)
 		goto out_free;
 	state = nfs4_opendata_to_nfs4_state(data);
 	if (!IS_ERR(state))
-		nfs4_close_state(state, data->o_arg.fmode, 0);
+		nfs4_close_state(state, data->o_arg.fmode,
+			data->o_arg.open_flags & (O_DENYREAD|O_DENYWRITE));
 out_free:
 	nfs4_opendata_put(data);
 }
@@ -1742,7 +1789,8 @@  static void nfs4_open_prepare(struct rpc_task *task, void *calldata)
 		delegation = rcu_dereference(NFS_I(data->state->inode)->delegation);
 		if (data->o_arg.claim != NFS4_OPEN_CLAIM_DELEGATE_CUR &&
 		    data->o_arg.claim != NFS4_OPEN_CLAIM_DELEG_CUR_FH &&
-		    can_open_delegated(delegation, data->o_arg.fmode))
+		    can_open_delegated(delegation, data->o_arg.fmode,
+			data->o_arg.open_flags & (O_DENYREAD|O_DENYWRITE)))
 			goto unlock_no_action;
 		rcu_read_unlock();
 	}
@@ -1829,7 +1877,8 @@  static void nfs4_open_release(void *calldata)
 		goto out_free;
 	state = nfs4_opendata_to_nfs4_state(data);
 	if (!IS_ERR(state))
-		nfs4_close_state(state, data->o_arg.fmode, 0);
+		nfs4_close_state(state, data->o_arg.fmode,
+			data->o_arg.open_flags & (O_DENYREAD|O_DENYWRITE));
 out_free:
 	nfs4_opendata_put(data);
 }
@@ -1941,7 +1990,7 @@  static int nfs4_opendata_access(struct rpc_cred *cred,
 		return 0;
 
 	/* even though OPEN succeeded, access is denied. Close the file */
-	nfs4_close_state(state, fmode, 0);
+	nfs4_close_state(state, fmode, openflags & (O_DENYREAD|O_DENYWRITE));
 	return -EACCES;
 }
 
@@ -2006,7 +2055,7 @@  static int _nfs4_open_expired(struct nfs_open_context *ctx, struct nfs4_state *s
 			NFS4_OPEN_CLAIM_FH);
 	if (IS_ERR(opendata))
 		return PTR_ERR(opendata);
-	ret = nfs4_open_recover(opendata, state);
+	ret = nfs4_open_recover(opendata, state, true);
 	if (ret == -ESTALE)
 		d_drop(ctx->dentry);
 	nfs4_opendata_put(opendata);
@@ -2125,6 +2174,10 @@  static int nfs41_check_open_stateid(struct nfs4_state *state)
 		clear_bit(NFS_O_WRONLY_STATE, &state->flags);
 		clear_bit(NFS_O_RDWR_STATE, &state->flags);
 		clear_bit(NFS_OPEN_STATE, &state->flags);
+		clear_bit(NFS_O_DENYNONE_STATE, &state->flags);
+		clear_bit(NFS_O_DENYREAD_STATE, &state->flags);
+		clear_bit(NFS_O_DENYWRITE_STATE, &state->flags);
+		clear_bit(NFS_O_DENYRDWR_STATE, &state->flags);
 	}
 	return status;
 }
@@ -2498,18 +2551,40 @@  nfs4_close_clear_stateid_flags(struct nfs4_state *state, fmode_t fmode,
 			       unsigned int deny_mode)
 {
 	spin_lock(&state->owner->so_lock);
-	clear_bit(NFS_O_RDWR_STATE, &state->flags);
 	switch (fmode & (FMODE_READ|FMODE_WRITE)) {
+	case FMODE_WRITE|FMODE_READ:
+		break;
 	case FMODE_WRITE:
+		clear_bit(NFS_O_RDWR_STATE, &state->flags);
 		clear_bit(NFS_O_RDONLY_STATE, &state->flags);
 		break;
 	case FMODE_READ:
+		clear_bit(NFS_O_RDWR_STATE, &state->flags);
 		clear_bit(NFS_O_WRONLY_STATE, &state->flags);
 		break;
 	case 0:
+		clear_bit(NFS_O_RDWR_STATE, &state->flags);
 		clear_bit(NFS_O_RDONLY_STATE, &state->flags);
 		clear_bit(NFS_O_WRONLY_STATE, &state->flags);
 		clear_bit(NFS_OPEN_STATE, &state->flags);
+		clear_bit(NFS_O_DENYNONE_STATE, &state->flags);
+	}
+
+	switch (deny_mode & (O_DENYREAD|O_DENYWRITE)) {
+	case O_DENYREAD|O_DENYWRITE:
+		break;
+	case O_DENYWRITE:
+		clear_bit(NFS_O_DENYRDWR_STATE, &state->flags);
+		clear_bit(NFS_O_DENYREAD_STATE, &state->flags);
+		break;
+	case O_DENYREAD:
+		clear_bit(NFS_O_DENYRDWR_STATE, &state->flags);
+		clear_bit(NFS_O_DENYWRITE_STATE, &state->flags);
+		break;
+	case 0:
+		clear_bit(NFS_O_DENYRDWR_STATE, &state->flags);
+		clear_bit(NFS_O_DENYREAD_STATE, &state->flags);
+		clear_bit(NFS_O_DENYWRITE_STATE, &state->flags);
 	}
 	spin_unlock(&state->owner->so_lock);
 }
@@ -2570,21 +2645,40 @@  static void nfs4_close_prepare(struct rpc_task *task, void *data)
 
 	task->tk_msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_OPEN_DOWNGRADE];
 	calldata->arg.fmode = FMODE_READ|FMODE_WRITE;
-	calldata->arg.deny_mode = 0;
+	calldata->arg.deny_mode = O_DENYREAD|O_DENYWRITE;
 	spin_lock(&state->owner->so_lock);
 	/* Calculate the change in open mode */
-	if (state->n_rdwr == 0) {
-		if (state->n_rdonly == 0) {
+	if (state->n_rdwr + state->n_rw_dr +
+	    state->n_rw_dw + state->n_rw_drw == 0) {
+		if (state->n_rdonly + state->n_ro_dr + state->n_ro_dw +
+							state->n_ro_drw == 0) {
 			call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
 			call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
 			calldata->arg.fmode &= ~FMODE_READ;
 		}
-		if (state->n_wronly == 0) {
+		if (state->n_wronly + state->n_wo_dr + state->n_wo_dw +
+							state->n_wo_drw == 0) {
 			call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
 			call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
 			calldata->arg.fmode &= ~FMODE_WRITE;
 		}
 	}
+	if (state->n_ro_drw + state->n_wo_drw + state->n_rw_drw == 0) {
+		if (state->n_ro_dr + state->n_wo_dr + state->n_rw_dr == 0) {
+			call_close |= test_bit(NFS_O_DENYREAD_STATE,
+					       &state->flags);
+			call_close |= test_bit(NFS_O_DENYRDWR_STATE,
+					       &state->flags);
+			calldata->arg.deny_mode &= ~O_DENYREAD;
+		}
+		if (state->n_ro_dw + state->n_wo_dw + state->n_rw_dw == 0) {
+			call_close |= test_bit(NFS_O_DENYWRITE_STATE,
+					       &state->flags);
+			call_close |= test_bit(NFS_O_DENYRDWR_STATE,
+					       &state->flags);
+			calldata->arg.deny_mode &= ~O_DENYWRITE;
+		}
+	}
 	if (!nfs4_valid_open_stateid(state))
 		call_close = 0;
 	spin_unlock(&state->owner->so_lock);
diff --git a/fs/nfs/nfs4state.c b/fs/nfs/nfs4state.c
index 168f868..bdd7808 100644
--- a/fs/nfs/nfs4state.c
+++ b/fs/nfs/nfs4state.c
@@ -736,19 +736,23 @@  __nfs4_close(struct nfs4_state *state, fmode_t fmode, unsigned int deny_mode,
 	struct nfs4_state_owner *owner = state->owner;
 	int call_close = 0;
 	fmode_t newstate;
+	unsigned int newdeny_mode;
 
 	atomic_inc(&owner->so_count);
 	/* Protect against nfs4_find_state() */
 	spin_lock(&owner->so_lock);
 	dec_state_n(state, fmode, deny_mode);
 	newstate = FMODE_READ|FMODE_WRITE;
-	if (state->n_rdwr == 0) {
-		if (state->n_rdonly == 0) {
+	if (state->n_rdwr + state->n_rw_dr +
+	    state->n_rw_dw + state->n_rw_drw == 0) {
+		if (state->n_rdonly + state->n_ro_dr + state->n_ro_dw +
+							state->n_ro_drw == 0) {
 			newstate &= ~FMODE_READ;
 			call_close |= test_bit(NFS_O_RDONLY_STATE, &state->flags);
 			call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
 		}
-		if (state->n_wronly == 0) {
+		if (state->n_wronly + state->n_wo_dr + state->n_wo_dw +
+							state->n_wo_drw == 0) {
 			newstate &= ~FMODE_WRITE;
 			call_close |= test_bit(NFS_O_WRONLY_STATE, &state->flags);
 			call_close |= test_bit(NFS_O_RDWR_STATE, &state->flags);
@@ -756,7 +760,24 @@  __nfs4_close(struct nfs4_state *state, fmode_t fmode, unsigned int deny_mode,
 		if (newstate == 0)
 			clear_bit(NFS_DELEGATED_STATE, &state->flags);
 	}
-	nfs4_state_set_mode_locked(state, newstate, 0);
+	newdeny_mode = O_DENYREAD|O_DENYWRITE;
+	if (state->n_ro_drw + state->n_wo_drw + state->n_rw_drw == 0) {
+		if (state->n_ro_dr + state->n_wo_dr + state->n_rw_dr == 0) {
+			newdeny_mode &= ~O_DENYREAD;
+			call_close |= test_bit(NFS_O_DENYREAD_STATE,
+					       &state->flags);
+			call_close |= test_bit(NFS_O_DENYRDWR_STATE,
+					       &state->flags);
+		}
+		if (state->n_ro_dw + state->n_wo_dw + state->n_rw_dw == 0) {
+			newdeny_mode &= ~O_DENYWRITE;
+			call_close |= test_bit(NFS_O_DENYWRITE_STATE,
+					       &state->flags);
+			call_close |= test_bit(NFS_O_DENYRDWR_STATE,
+					       &state->flags);
+		}
+	}
+	nfs4_state_set_mode_locked(state, newstate, newdeny_mode);
 	spin_unlock(&owner->so_lock);
 
 	if (!call_close) {
@@ -1541,6 +1562,10 @@  static void nfs4_clear_open_state(struct nfs4_state *state)
 	clear_bit(NFS_O_RDONLY_STATE, &state->flags);
 	clear_bit(NFS_O_WRONLY_STATE, &state->flags);
 	clear_bit(NFS_O_RDWR_STATE, &state->flags);
+	clear_bit(NFS_O_DENYNONE_STATE, &state->flags);
+	clear_bit(NFS_O_DENYREAD_STATE, &state->flags);
+	clear_bit(NFS_O_DENYWRITE_STATE, &state->flags);
+	clear_bit(NFS_O_DENYRDWR_STATE, &state->flags);
 	spin_lock(&state->state_lock);
 	list_for_each_entry(lock, &state->lock_states, ls_locks) {
 		lock->ls_seqid.flags = 0;
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index 65ab0a0..1f91571 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -29,7 +29,8 @@  static struct file_system_type nfs4_remote_fs_type = {
 	.name		= "nfs4",
 	.mount		= nfs4_remote_mount,
 	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE | FS_BINARY_MOUNTDATA |
+			  FS_DOES_SHARELOCK,
 };
 
 static struct file_system_type nfs4_remote_referral_fs_type = {
@@ -37,7 +38,8 @@  static struct file_system_type nfs4_remote_referral_fs_type = {
 	.name		= "nfs4",
 	.mount		= nfs4_remote_referral_mount,
 	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE | FS_BINARY_MOUNTDATA |
+			  FS_DOES_SHARELOCK,
 };
 
 struct file_system_type nfs4_referral_fs_type = {
@@ -45,7 +47,8 @@  struct file_system_type nfs4_referral_fs_type = {
 	.name		= "nfs4",
 	.mount		= nfs4_referral_mount,
 	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE | FS_BINARY_MOUNTDATA |
+			  FS_DOES_SHARELOCK,
 };
 
 static const struct super_operations nfs4_sops = {
diff --git a/fs/nfs/nfs4xdr.c b/fs/nfs/nfs4xdr.c
index ed507f4..870d297 100644
--- a/fs/nfs/nfs4xdr.c
+++ b/fs/nfs/nfs4xdr.c
@@ -1377,7 +1377,19 @@  static void encode_share_access(struct xdr_stream *xdr, fmode_t fmode,
 	default:
 		*p++ = cpu_to_be32(0);
 	}
-	*p = cpu_to_be32(0);		/* for linux, share_deny = 0 always */
+	switch (open_flags & (O_DENYREAD|O_DENYWRITE)) {
+	case O_DENYREAD:
+		*p = cpu_to_be32(NFS4_SHARE_DENY_READ);
+		break;
+	case O_DENYWRITE:
+		*p = cpu_to_be32(NFS4_SHARE_DENY_WRITE);
+		break;
+	case O_DENYREAD|O_DENYWRITE:
+		*p = cpu_to_be32(NFS4_SHARE_DENY_BOTH);
+		break;
+	default:
+		*p = cpu_to_be32(0);
+	}
 }
 
 static inline void encode_openhdr(struct xdr_stream *xdr, const struct nfs_openargs *arg)
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index 910ed90..73bc4d8 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -333,7 +333,8 @@  struct file_system_type nfs4_fs_type = {
 	.name		= "nfs4",
 	.mount		= nfs_fs_mount,
 	.kill_sb	= nfs_kill_super,
-	.fs_flags	= FS_RENAME_DOES_D_MOVE|FS_BINARY_MOUNTDATA,
+	.fs_flags	= FS_RENAME_DOES_D_MOVE | FS_BINARY_MOUNTDATA |
+			  FS_DOES_SHARELOCK,
 };
 MODULE_ALIAS_FS("nfs4");
 MODULE_ALIAS("nfs4");
@@ -2699,6 +2700,8 @@  nfs_xdev_mount(struct file_system_type *fs_type, int flags,
 	struct nfs_server *server;
 	struct dentry *mntroot = ERR_PTR(-ENOMEM);
 	struct nfs_subversion *nfs_mod = NFS_SB(data->sb)->nfs_client->cl_nfs_mod;
+	/* save sharelock option */
+	int sharelock = data->sb->s_flags & MS_SHARELOCK;
 
 	dprintk("--> nfs_xdev_mount()\n");
 
@@ -2710,7 +2713,7 @@  nfs_xdev_mount(struct file_system_type *fs_type, int flags,
 	if (IS_ERR(server))
 		mntroot = ERR_CAST(server);
 	else
-		mntroot = nfs_fs_mount_common(server, flags,
+		mntroot = nfs_fs_mount_common(server, flags | sharelock,
 				dev_name, &mount_info, nfs_mod);
 
 	dprintk("<-- nfs_xdev_mount() = %ld\n",