NFSv4: Resend the READ/WRITE RPC call if a stateid change causes an error
authorTrond Myklebust <Trond.Myklebust@netapp.com>
Sun, 17 Mar 2013 00:54:34 +0000 (20:54 -0400)
committerTrond Myklebust <Trond.Myklebust@netapp.com>
Mon, 25 Mar 2013 16:04:10 +0000 (12:04 -0400)
Adds logic to ensure that if the server returns a BAD_STATEID,
or other state related error, then we check if the stateid has
already changed. If it has, then rather than start state recovery,
we should just resend the failed RPC call with the new stateid.

Allow nfs4_select_rw_stateid to notify that the stateid is unstable by
having it return -EWOULDBLOCK if an RPC is underway that might change the
stateid.

Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
fs/nfs/nfs4_fs.h
fs/nfs/nfs4proc.c
fs/nfs/nfs4state.c

index 8309e98c44f90b8992eb6f2a284afbaf3f2d879d..627a74f0e248ae908a6a5f3b755161586df88c1c 100644 (file)
@@ -234,7 +234,7 @@ extern struct rpc_clnt *nfs4_proc_lookup_mountpoint(struct inode *, struct qstr
 extern int nfs4_proc_secinfo(struct inode *, const struct qstr *, struct nfs4_secinfo_flavors *);
 extern int nfs4_release_lockowner(struct nfs4_lock_state *);
 extern const struct xattr_handler *nfs4_xattr_handlers[];
-extern void nfs4_set_rw_stateid(nfs4_stateid *stateid,
+extern int nfs4_set_rw_stateid(nfs4_stateid *stateid,
                const struct nfs_open_context *ctx,
                const struct nfs_lock_context *l_ctx,
                fmode_t fmode);
@@ -358,7 +358,7 @@ extern void nfs41_handle_server_scope(struct nfs_client *,
                                      struct nfs41_server_scope **);
 extern void nfs4_put_lock_state(struct nfs4_lock_state *lsp);
 extern int nfs4_set_lock_state(struct nfs4_state *state, struct file_lock *fl);
-extern void nfs4_select_rw_stateid(nfs4_stateid *, struct nfs4_state *,
+extern int nfs4_select_rw_stateid(nfs4_stateid *, struct nfs4_state *,
                fmode_t, const struct nfs_lockowner *);
 
 extern struct nfs_seqid *nfs_alloc_seqid(struct nfs_seqid_counter *counter, gfp_t gfp_mask);
index 26176ce3d96a986549c17a99be14b8fb2c5f08be..6ad06121d88cffa9da207efb31d4e118607fd7a1 100644 (file)
@@ -3454,7 +3454,7 @@ static int nfs4_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle,
        return err;
 }
 
-void nfs4_set_rw_stateid(nfs4_stateid *stateid,
+int nfs4_set_rw_stateid(nfs4_stateid *stateid,
                const struct nfs_open_context *ctx,
                const struct nfs_lock_context *l_ctx,
                fmode_t fmode)
@@ -3463,10 +3463,37 @@ void nfs4_set_rw_stateid(nfs4_stateid *stateid,
 
        if (l_ctx != NULL)
                lockowner = &l_ctx->lockowner;
-       nfs4_select_rw_stateid(stateid, ctx->state, fmode, lockowner);
+       return nfs4_select_rw_stateid(stateid, ctx->state, fmode, lockowner);
 }
 EXPORT_SYMBOL_GPL(nfs4_set_rw_stateid);
 
+static bool nfs4_stateid_is_current(nfs4_stateid *stateid,
+               const struct nfs_open_context *ctx,
+               const struct nfs_lock_context *l_ctx,
+               fmode_t fmode)
+{
+       nfs4_stateid current_stateid;
+
+       if (nfs4_set_rw_stateid(&current_stateid, ctx, l_ctx, fmode))
+               return false;
+       return nfs4_stateid_match(stateid, &current_stateid);
+}
+
+static bool nfs4_error_stateid_expired(int err)
+{
+       switch (err) {
+       case -NFS4ERR_DELEG_REVOKED:
+       case -NFS4ERR_ADMIN_REVOKED:
+       case -NFS4ERR_BAD_STATEID:
+       case -NFS4ERR_STALE_STATEID:
+       case -NFS4ERR_OLD_STATEID:
+       case -NFS4ERR_OPENMODE:
+       case -NFS4ERR_EXPIRED:
+               return true;
+       }
+       return false;
+}
+
 void __nfs4_read_done_cb(struct nfs_read_data *data)
 {
        nfs_invalidate_atime(data->header->inode);
@@ -3487,6 +3514,20 @@ static int nfs4_read_done_cb(struct rpc_task *task, struct nfs_read_data *data)
        return 0;
 }
 
+static bool nfs4_read_stateid_changed(struct rpc_task *task,
+               struct nfs_readargs *args)
+{
+
+       if (!nfs4_error_stateid_expired(task->tk_status) ||
+               nfs4_stateid_is_current(&args->stateid,
+                               args->context,
+                               args->lock_context,
+                               FMODE_READ))
+               return false;
+       rpc_restart_call_prepare(task);
+       return true;
+}
+
 static int nfs4_read_done(struct rpc_task *task, struct nfs_read_data *data)
 {
 
@@ -3494,7 +3535,8 @@ static int nfs4_read_done(struct rpc_task *task, struct nfs_read_data *data)
 
        if (!nfs4_sequence_done(task, &data->res.seq_res))
                return -EAGAIN;
-
+       if (nfs4_read_stateid_changed(task, &data->args))
+               return -EAGAIN;
        return data->read_done_cb ? data->read_done_cb(task, data) :
                                    nfs4_read_done_cb(task, data);
 }
@@ -3533,10 +3575,26 @@ static int nfs4_write_done_cb(struct rpc_task *task, struct nfs_write_data *data
        return 0;
 }
 
+static bool nfs4_write_stateid_changed(struct rpc_task *task,
+               struct nfs_writeargs *args)
+{
+
+       if (!nfs4_error_stateid_expired(task->tk_status) ||
+               nfs4_stateid_is_current(&args->stateid,
+                               args->context,
+                               args->lock_context,
+                               FMODE_WRITE))
+               return false;
+       rpc_restart_call_prepare(task);
+       return true;
+}
+
 static int nfs4_write_done(struct rpc_task *task, struct nfs_write_data *data)
 {
        if (!nfs4_sequence_done(task, &data->res.seq_res))
                return -EAGAIN;
+       if (nfs4_write_stateid_changed(task, &data->args))
+               return -EAGAIN;
        return data->write_done_cb ? data->write_done_cb(task, data) :
                nfs4_write_done_cb(task, data);
 }
index 8db102c7add61803cc9795f4db60af1b504f4645..4e95bd72f48047f63636a44b61c1a6bee1f618db 100644 (file)
@@ -989,13 +989,14 @@ int nfs4_set_lock_state(struct nfs4_state *state, struct file_lock *fl)
        return 0;
 }
 
-static bool nfs4_copy_lock_stateid(nfs4_stateid *dst, struct nfs4_state *state,
+static int nfs4_copy_lock_stateid(nfs4_stateid *dst,
+               struct nfs4_state *state,
                const struct nfs_lockowner *lockowner)
 {
        struct nfs4_lock_state *lsp;
        fl_owner_t fl_owner;
        pid_t fl_pid;
-       bool ret = false;
+       int ret = -ENOENT;
 
 
        if (lockowner == NULL)
@@ -1010,7 +1011,10 @@ static bool nfs4_copy_lock_stateid(nfs4_stateid *dst, struct nfs4_state *state,
        lsp = __nfs4_find_lock_state(state, fl_owner, fl_pid, NFS4_ANY_LOCK_TYPE);
        if (lsp != NULL && test_bit(NFS_LOCK_INITIALIZED, &lsp->ls_flags) != 0) {
                nfs4_stateid_copy(dst, &lsp->ls_stateid);
-               ret = true;
+               ret = 0;
+               smp_rmb();
+               if (!list_empty(&lsp->ls_seqid.list))
+                       ret = -EWOULDBLOCK;
        }
        spin_unlock(&state->state_lock);
        nfs4_put_lock_state(lsp);
@@ -1018,28 +1022,38 @@ out:
        return ret;
 }
 
-static void nfs4_copy_open_stateid(nfs4_stateid *dst, struct nfs4_state *state)
+static int nfs4_copy_open_stateid(nfs4_stateid *dst, struct nfs4_state *state)
 {
+       int ret;
        int seq;
 
        do {
                seq = read_seqbegin(&state->seqlock);
                nfs4_stateid_copy(dst, &state->stateid);
+               ret = 0;
+               smp_rmb();
+               if (!list_empty(&state->owner->so_seqid.list))
+                       ret = -EWOULDBLOCK;
        } while (read_seqretry(&state->seqlock, seq));
+       return ret;
 }
 
 /*
  * Byte-range lock aware utility to initialize the stateid of read/write
  * requests.
  */
-void nfs4_select_rw_stateid(nfs4_stateid *dst, struct nfs4_state *state,
+int nfs4_select_rw_stateid(nfs4_stateid *dst, struct nfs4_state *state,
                fmode_t fmode, const struct nfs_lockowner *lockowner)
 {
+       int ret = 0;
        if (nfs4_copy_delegation_stateid(dst, state->inode, fmode))
-               return;
-       if (nfs4_copy_lock_stateid(dst, state, lockowner))
-               return;
-       nfs4_copy_open_stateid(dst, state);
+               goto out;
+       ret = nfs4_copy_lock_stateid(dst, state, lockowner);
+       if (ret != -ENOENT)
+               goto out;
+       ret = nfs4_copy_open_stateid(dst, state);
+out:
+       return ret;
 }
 
 struct nfs_seqid *nfs_alloc_seqid(struct nfs_seqid_counter *counter, gfp_t gfp_mask)