Merge commit 'v2.6.34-rc6'
[safe/jmp/linux-2.6] / fs / nfsd / nfs4callback.c
index 7e32bd3..1d5051d 100644 (file)
@@ -32,6 +32,7 @@
  */
 
 #include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc_xprt.h>
 #include <linux/slab.h>
 #include "nfsd.h"
 #include "state.h"
@@ -79,11 +80,6 @@ enum nfs_cb_opnum4 {
                                        cb_sequence_dec_sz +            \
                                        op_dec_sz)
 
-struct nfs4_rpc_args {
-       void                            *args_op;
-       struct nfsd4_cb_sequence        args_seq;
-};
-
 /*
 * Generic encode routines from fs/nfs/nfs4xdr.c
 */
@@ -456,15 +452,14 @@ static struct rpc_program cb_program = {
 
 static int max_cb_time(void)
 {
-       return max(NFSD_LEASE_TIME/10, (time_t)1) * HZ;
+       return max(nfsd4_lease/10, (time_t)1) * HZ;
 }
 
 /* Reference counting, callback cleanup, etc., all look racy as heck.
- * And why is cb_set an atomic? */
+ * And why is cl_cb_set an atomic? */
 
-int setup_callback_client(struct nfs4_client *clp)
+int setup_callback_client(struct nfs4_client *clp, struct nfs4_cb_conn *cb)
 {
-       struct nfs4_cb_conn *cb = &clp->cl_cb_conn;
        struct rpc_timeout      timeparms = {
                .to_initval     = max_cb_time(),
                .to_retries     = 0,
@@ -486,7 +481,7 @@ int setup_callback_client(struct nfs4_client *clp)
        if (!clp->cl_principal && (clp->cl_flavor >= RPC_AUTH_GSS_KRB5))
                return -EINVAL;
        if (cb->cb_minorversion) {
-               args.bc_xprt = clp->cl_cb_xprt;
+               args.bc_xprt = cb->cb_xprt;
                args.protocol = XPRT_TRANSPORT_BC_TCP;
        }
        /* Create RPC client */
@@ -496,7 +491,7 @@ int setup_callback_client(struct nfs4_client *clp)
                        PTR_ERR(client));
                return PTR_ERR(client);
        }
-       cb->cb_client = client;
+       nfsd4_set_callback_client(clp, client);
        return 0;
 
 }
@@ -514,8 +509,7 @@ static void nfsd4_cb_probe_done(struct rpc_task *task, void *calldata)
        if (task->tk_status)
                warn_no_callback_path(clp, task->tk_status);
        else
-               atomic_set(&clp->cl_cb_conn.cb_set, 1);
-       put_nfs4_client(clp);
+               atomic_set(&clp->cl_cb_set, 1);
 }
 
 static const struct rpc_call_ops nfsd4_cb_probe_ops = {
@@ -537,7 +531,6 @@ int set_callback_cred(void)
 
 void do_probe_callback(struct nfs4_client *clp)
 {
-       struct nfs4_cb_conn *cb = &clp->cl_cb_conn;
        struct rpc_message msg = {
                .rpc_proc       = &nfs4_cb_procedures[NFSPROC4_CLNT_CB_NULL],
                .rpc_argp       = clp,
@@ -545,34 +538,27 @@ void do_probe_callback(struct nfs4_client *clp)
        };
        int status;
 
-       status = rpc_call_async(cb->cb_client, &msg,
+       status = rpc_call_async(clp->cl_cb_client, &msg,
                                RPC_TASK_SOFT | RPC_TASK_SOFTCONN,
                                &nfsd4_cb_probe_ops, (void *)clp);
-       if (status) {
+       if (status)
                warn_no_callback_path(clp, status);
-               put_nfs4_client(clp);
-       }
 }
 
 /*
  * Set up the callback client and put a NFSPROC4_CB_NULL on the wire...
  */
-void
-nfsd4_probe_callback(struct nfs4_client *clp)
+void nfsd4_probe_callback(struct nfs4_client *clp, struct nfs4_cb_conn *cb)
 {
        int status;
 
-       BUG_ON(atomic_read(&clp->cl_cb_conn.cb_set));
+       BUG_ON(atomic_read(&clp->cl_cb_set));
 
-       status = setup_callback_client(clp);
+       status = setup_callback_client(clp, cb);
        if (status) {
                warn_no_callback_path(clp, status);
                return;
        }
-
-       /* the task holds a reference to the nfs4_client struct */
-       atomic_inc(&clp->cl_count);
-
        do_probe_callback(clp);
 }
 
@@ -658,18 +644,32 @@ static void nfsd4_cb_done(struct rpc_task *task, void *calldata)
        }
 }
 
+
 static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata)
 {
        struct nfs4_delegation *dp = calldata;
        struct nfs4_client *clp = dp->dl_client;
+       struct rpc_clnt *current_rpc_client = clp->cl_cb_client;
 
        nfsd4_cb_done(task, calldata);
 
+       if (current_rpc_client == NULL) {
+               /* We're shutting down; give up. */
+               /* XXX: err, or is it ok just to fall through
+                * and rpc_restart_call? */
+               return;
+       }
+
        switch (task->tk_status) {
        case -EIO:
                /* Network partition? */
-               atomic_set(&clp->cl_cb_conn.cb_set, 0);
+               atomic_set(&clp->cl_cb_set, 0);
                warn_no_callback_path(clp, task->tk_status);
+               if (current_rpc_client != task->tk_client) {
+                       /* queue a callback on the new connection: */
+                       nfsd4_cb_recall(dp);
+                       return;
+               }
        case -EBADHANDLE:
        case -NFS4ERR_BAD_STATEID:
                /* Race: client probably got cb_recall
@@ -677,7 +677,7 @@ static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata)
                break;
        default:
                /* success, or error we can't handle */
-               goto done;
+               return;
        }
        if (dp->dl_retries--) {
                rpc_delay(task, 2*HZ);
@@ -685,20 +685,16 @@ static void nfsd4_cb_recall_done(struct rpc_task *task, void *calldata)
                rpc_restart_call(task);
                return;
        } else {
-               atomic_set(&clp->cl_cb_conn.cb_set, 0);
+               atomic_set(&clp->cl_cb_set, 0);
                warn_no_callback_path(clp, task->tk_status);
        }
-done:
-       kfree(task->tk_msg.rpc_argp);
 }
 
 static void nfsd4_cb_recall_release(void *calldata)
 {
        struct nfs4_delegation *dp = calldata;
-       struct nfs4_client *clp = dp->dl_client;
 
        nfs4_put_delegation(dp);
-       put_nfs4_client(clp);
 }
 
 static const struct rpc_call_ops nfsd4_cb_recall_ops = {
@@ -707,33 +703,74 @@ static const struct rpc_call_ops nfsd4_cb_recall_ops = {
        .rpc_release = nfsd4_cb_recall_release,
 };
 
+static struct workqueue_struct *callback_wq;
+
+int nfsd4_create_callback_queue(void)
+{
+       callback_wq = create_singlethread_workqueue("nfsd4_callbacks");
+       if (!callback_wq)
+               return -ENOMEM;
+       return 0;
+}
+
+void nfsd4_destroy_callback_queue(void)
+{
+       destroy_workqueue(callback_wq);
+}
+
+void nfsd4_set_callback_client(struct nfs4_client *clp, struct rpc_clnt *new)
+{
+       struct rpc_clnt *old = clp->cl_cb_client;
+
+       clp->cl_cb_client = new;
+       /*
+        * After this, any work that saw the old value of cl_cb_client will
+        * be gone:
+        */
+       flush_workqueue(callback_wq);
+       /* So we can safely shut it down: */
+       if (old)
+               rpc_shutdown_client(old);
+}
+
 /*
  * called with dp->dl_count inc'ed.
  */
-void
-nfsd4_cb_recall(struct nfs4_delegation *dp)
+static void _nfsd4_cb_recall(struct nfs4_delegation *dp)
 {
        struct nfs4_client *clp = dp->dl_client;
-       struct rpc_clnt *clnt = clp->cl_cb_conn.cb_client;
-       struct nfs4_rpc_args *args;
+       struct rpc_clnt *clnt = clp->cl_cb_client;
+       struct nfs4_rpc_args *args = &dp->dl_recall.cb_args;
        struct rpc_message msg = {
                .rpc_proc = &nfs4_cb_procedures[NFSPROC4_CLNT_CB_RECALL],
                .rpc_cred = callback_cred
        };
-       int status = -ENOMEM;
+       int status;
+
+       if (clnt == NULL)
+               return; /* Client is shutting down; give up. */
 
-       args = kzalloc(sizeof(*args), GFP_KERNEL);
-       if (!args)
-               goto out;
        args->args_op = dp;
        msg.rpc_argp = args;
        dp->dl_retries = 1;
        status = rpc_call_async(clnt, &msg, RPC_TASK_SOFT,
                                &nfsd4_cb_recall_ops, dp);
-out:
-       if (status) {
-               kfree(args);
-               put_nfs4_client(clp);
+       if (status)
                nfs4_put_delegation(dp);
-       }
+}
+
+void nfsd4_do_callback_rpc(struct work_struct *w)
+{
+       /* XXX: for now, just send off delegation recall. */
+       /* In future, generalize to handle any sort of callback. */
+       struct nfsd4_callback *c = container_of(w, struct nfsd4_callback, cb_work);
+       struct nfs4_delegation *dp = container_of(c, struct nfs4_delegation, dl_recall);
+
+       _nfsd4_cb_recall(dp);
+}
+
+
+void nfsd4_cb_recall(struct nfs4_delegation *dp)
+{
+       queue_work(callback_wq, &dp->dl_recall.cb_work);
 }