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 <pias...@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 --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",
-- 
1.7.10.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to