nfs41: add session setup to the state manager
[pandora-kernel.git] / fs / nfs / nfs4proc.c
index eafc99a..7fc0c9c 100644 (file)
@@ -54,6 +54,7 @@
 #include "delegation.h"
 #include "internal.h"
 #include "iostat.h"
+#include "callback.h"
 
 #define NFSDBG_FACILITY                NFSDBG_PROC
 
@@ -3220,10 +3221,8 @@ static int nfs4_proc_set_acl(struct inode *inode, const void *buf, size_t buflen
 }
 
 static int
-nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server, struct nfs4_state *state)
+_nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server, struct nfs_client *clp, struct nfs4_state *state)
 {
-       struct nfs_client *clp = server->nfs_client;
-
        if (!clp || task->tk_status >= 0)
                return 0;
        switch(task->tk_status) {
@@ -3243,7 +3242,8 @@ nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server,
                        task->tk_status = 0;
                        return -EAGAIN;
                case -NFS4ERR_DELAY:
-                       nfs_inc_server_stats(server, NFSIOS_DELAY);
+                       if (server)
+                               nfs_inc_server_stats(server, NFSIOS_DELAY);
                case -NFS4ERR_GRACE:
                        rpc_delay(task, NFS4_POLL_RETRY_MAX);
                        task->tk_status = 0;
@@ -3256,6 +3256,12 @@ nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server,
        return 0;
 }
 
+static int
+nfs4_async_handle_error(struct rpc_task *task, const struct nfs_server *server, struct nfs4_state *state)
+{
+       return _nfs4_async_handle_error(task, server, server->nfs_client, state);
+}
+
 int nfs4_proc_setclientid(struct nfs_client *clp, u32 program, unsigned short port, struct rpc_cred *cred)
 {
        nfs4_verifier sc_verifier;
@@ -4283,6 +4289,81 @@ int nfs4_proc_get_lease_time(struct nfs_client *clp, struct nfs_fsinfo *fsinfo)
        return status;
 }
 
+/* Reset a slot table */
+static int nfs4_reset_slot_table(struct nfs4_session *session)
+{
+       struct nfs4_slot_table *tbl = &session->fc_slot_table;
+       int i, max_slots = session->fc_attrs.max_reqs;
+       int old_max_slots = session->fc_slot_table.max_slots;
+       int ret = 0;
+
+       dprintk("--> %s: max_reqs=%u, tbl %p\n", __func__,
+               session->fc_attrs.max_reqs, tbl);
+
+       /* Until we have dynamic slot table adjustment, insist
+        * upon the same slot table size */
+       if (max_slots != old_max_slots) {
+               dprintk("%s reset slot table does't match old\n",
+                       __func__);
+               ret = -EINVAL; /*XXX NFS4ERR_REQ_TOO_BIG ? */
+               goto out;
+       }
+       spin_lock(&tbl->slot_tbl_lock);
+       for (i = 0; i < max_slots; ++i)
+               tbl->slots[i].seq_nr = 1;
+       tbl->highest_used_slotid = -1;
+       spin_unlock(&tbl->slot_tbl_lock);
+       dprintk("%s: tbl=%p slots=%p max_slots=%d\n", __func__,
+               tbl, tbl->slots, tbl->max_slots);
+out:
+       dprintk("<-- %s: return %d\n", __func__, ret);
+       return ret;
+}
+
+/*
+ * Initialize slot table
+ */
+static int nfs4_init_slot_table(struct nfs4_session *session)
+{
+       struct nfs4_slot_table *tbl = &session->fc_slot_table;
+       int i, max_slots = session->fc_attrs.max_reqs;
+       struct nfs4_slot *slot;
+       int ret = -ENOMEM;
+
+       BUG_ON(max_slots > NFS4_MAX_SLOT_TABLE);
+
+       dprintk("--> %s: max_reqs=%u\n", __func__,
+               session->fc_attrs.max_reqs);
+
+       slot = kcalloc(max_slots, sizeof(struct nfs4_slot), GFP_KERNEL);
+       if (!slot)
+               goto out;
+       for (i = 0; i < max_slots; ++i)
+               slot[i].seq_nr = 1;
+       ret = 0;
+
+       spin_lock(&tbl->slot_tbl_lock);
+       if (tbl->slots != NULL) {
+               spin_unlock(&tbl->slot_tbl_lock);
+               dprintk("%s: slot table already initialized. tbl=%p slots=%p\n",
+                       __func__, tbl, tbl->slots);
+               WARN_ON(1);
+               goto out_free;
+       }
+       tbl->max_slots = max_slots;
+       tbl->slots = slot;
+       tbl->highest_used_slotid = -1;  /* no slot is currently used */
+       spin_unlock(&tbl->slot_tbl_lock);
+       dprintk("%s: tbl=%p slots=%p max_slots=%d\n", __func__,
+               tbl, tbl->slots, tbl->max_slots);
+out:
+       dprintk("<-- %s: return %d\n", __func__, ret);
+       return ret;
+out_free:
+       kfree(slot);
+       goto out;
+}
+
 /* Destroy the slot table */
 static void nfs4_destroy_slot_table(struct nfs4_session *session)
 {
@@ -4301,6 +4382,16 @@ struct nfs4_session *nfs4_alloc_session(struct nfs_client *clp)
        session = kzalloc(sizeof(struct nfs4_session), GFP_KERNEL);
        if (!session)
                return NULL;
+
+       set_bit(NFS4CLNT_SESSION_SETUP, &clp->cl_state);
+       /*
+        * The create session reply races with the server back
+        * channel probe. Mark the client NFS_CS_SESSION_INITING
+        * so that the client back channel can find the
+        * nfs_client struct
+        */
+       clp->cl_cons_state = NFS_CS_SESSION_INITING;
+
        tbl = &session->fc_slot_table;
        spin_lock_init(&tbl->slot_tbl_lock);
        rpc_init_wait_queue(&tbl->slot_tbl_waitq, "Slot table");
@@ -4314,6 +4405,308 @@ void nfs4_destroy_session(struct nfs4_session *session)
        kfree(session);
 }
 
+/*
+ * Initialize the values to be used by the client in CREATE_SESSION
+ * If nfs4_init_session set the fore channel request and response sizes,
+ * use them.
+ *
+ * Set the back channel max_resp_sz_cached to zero to force the client to
+ * always set csa_cachethis to FALSE because the current implementation
+ * of the back channel DRC only supports caching the CB_SEQUENCE operation.
+ */
+static void nfs4_init_channel_attrs(struct nfs41_create_session_args *args)
+{
+       struct nfs4_session *session = args->client->cl_session;
+       unsigned int mxrqst_sz = session->fc_attrs.max_rqst_sz,
+                    mxresp_sz = session->fc_attrs.max_resp_sz;
+
+       if (mxrqst_sz == 0)
+               mxrqst_sz = NFS_MAX_FILE_IO_SIZE;
+       if (mxresp_sz == 0)
+               mxresp_sz = NFS_MAX_FILE_IO_SIZE;
+       /* Fore channel attributes */
+       args->fc_attrs.headerpadsz = 0;
+       args->fc_attrs.max_rqst_sz = mxrqst_sz;
+       args->fc_attrs.max_resp_sz = mxresp_sz;
+       args->fc_attrs.max_resp_sz_cached = mxresp_sz;
+       args->fc_attrs.max_ops = NFS4_MAX_OPS;
+       args->fc_attrs.max_reqs = session->clp->cl_rpcclient->cl_xprt->max_reqs;
+
+       dprintk("%s: Fore Channel : max_rqst_sz=%u max_resp_sz=%u "
+               "max_resp_sz_cached=%u max_ops=%u max_reqs=%u\n",
+               __func__,
+               args->fc_attrs.max_rqst_sz, args->fc_attrs.max_resp_sz,
+               args->fc_attrs.max_resp_sz_cached, args->fc_attrs.max_ops,
+               args->fc_attrs.max_reqs);
+
+       /* Back channel attributes */
+       args->bc_attrs.headerpadsz = 0;
+       args->bc_attrs.max_rqst_sz = PAGE_SIZE;
+       args->bc_attrs.max_resp_sz = PAGE_SIZE;
+       args->bc_attrs.max_resp_sz_cached = 0;
+       args->bc_attrs.max_ops = NFS4_MAX_BACK_CHANNEL_OPS;
+       args->bc_attrs.max_reqs = 1;
+
+       dprintk("%s: Back Channel : max_rqst_sz=%u max_resp_sz=%u "
+               "max_resp_sz_cached=%u max_ops=%u max_reqs=%u\n",
+               __func__,
+               args->bc_attrs.max_rqst_sz, args->bc_attrs.max_resp_sz,
+               args->bc_attrs.max_resp_sz_cached, args->bc_attrs.max_ops,
+               args->bc_attrs.max_reqs);
+}
+
+static int _verify_channel_attr(char *chan, char *attr_name, u32 sent, u32 rcvd)
+{
+       if (rcvd <= sent)
+               return 0;
+       printk(KERN_WARNING "%s: Session INVALID: %s channel %s increased. "
+               "sent=%u rcvd=%u\n", __func__, chan, attr_name, sent, rcvd);
+       return -EINVAL;
+}
+
+#define _verify_fore_channel_attr(_name_) \
+       _verify_channel_attr("fore", #_name_, \
+                            args->fc_attrs._name_, \
+                            session->fc_attrs._name_)
+
+#define _verify_back_channel_attr(_name_) \
+       _verify_channel_attr("back", #_name_, \
+                            args->bc_attrs._name_, \
+                            session->bc_attrs._name_)
+
+/*
+ * The server is not allowed to increase the fore channel header pad size,
+ * maximum response size, or maximum number of operations.
+ *
+ * The back channel attributes are only negotiatied down: We send what the
+ * (back channel) server insists upon.
+ */
+static int nfs4_verify_channel_attrs(struct nfs41_create_session_args *args,
+                                    struct nfs4_session *session)
+{
+       int ret = 0;
+
+       ret |= _verify_fore_channel_attr(headerpadsz);
+       ret |= _verify_fore_channel_attr(max_resp_sz);
+       ret |= _verify_fore_channel_attr(max_ops);
+
+       ret |= _verify_back_channel_attr(headerpadsz);
+       ret |= _verify_back_channel_attr(max_rqst_sz);
+       ret |= _verify_back_channel_attr(max_resp_sz);
+       ret |= _verify_back_channel_attr(max_resp_sz_cached);
+       ret |= _verify_back_channel_attr(max_ops);
+       ret |= _verify_back_channel_attr(max_reqs);
+
+       return ret;
+}
+
+static int _nfs4_proc_create_session(struct nfs_client *clp)
+{
+       struct nfs4_session *session = clp->cl_session;
+       struct nfs41_create_session_args args = {
+               .client = clp,
+               .cb_program = NFS4_CALLBACK,
+       };
+       struct nfs41_create_session_res res = {
+               .client = clp,
+       };
+       struct rpc_message msg = {
+               .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_CREATE_SESSION],
+               .rpc_argp = &args,
+               .rpc_resp = &res,
+       };
+       int status;
+
+       nfs4_init_channel_attrs(&args);
+       args.flags = (SESSION4_PERSIST);
+
+       status = rpc_call_sync(session->clp->cl_rpcclient, &msg, 0);
+
+       if (!status)
+               /* Verify the session's negotiated channel_attrs values */
+               status = nfs4_verify_channel_attrs(&args, session);
+       if (!status) {
+               /* Increment the clientid slot sequence id */
+               clp->cl_seqid++;
+       }
+
+       return status;
+}
+
+/*
+ * Issues a CREATE_SESSION operation to the server.
+ * It is the responsibility of the caller to verify the session is
+ * expired before calling this routine.
+ */
+int nfs4_proc_create_session(struct nfs_client *clp, int reset)
+{
+       int status;
+       unsigned *ptr;
+       struct nfs_fsinfo fsinfo;
+       struct nfs4_session *session = clp->cl_session;
+
+       dprintk("--> %s clp=%p session=%p\n", __func__, clp, session);
+
+       status = _nfs4_proc_create_session(clp);
+       if (status)
+               goto out;
+
+       /* Init or reset the fore channel */
+       if (reset)
+               status = nfs4_reset_slot_table(session);
+       else
+               status = nfs4_init_slot_table(session);
+       dprintk("fore channel slot table initialization returned %d\n", status);
+       if (status)
+               goto out;
+
+       ptr = (unsigned *)&session->sess_id.data[0];
+       dprintk("%s client>seqid %d sessionid %u:%u:%u:%u\n", __func__,
+               clp->cl_seqid, ptr[0], ptr[1], ptr[2], ptr[3]);
+
+       if (reset)
+               /* Lease time is aleady set */
+               goto out;
+
+       /* Get the lease time */
+       status = nfs4_proc_get_lease_time(clp, &fsinfo);
+       if (status == 0) {
+               /* Update lease time and schedule renewal */
+               spin_lock(&clp->cl_lock);
+               clp->cl_lease_time = fsinfo.lease_time * HZ;
+               clp->cl_last_renewal = jiffies;
+               clear_bit(NFS4CLNT_LEASE_EXPIRED, &clp->cl_state);
+               spin_unlock(&clp->cl_lock);
+
+               nfs4_schedule_state_renewal(clp);
+       }
+out:
+       dprintk("<-- %s\n", __func__);
+       return status;
+}
+
+/*
+ * Issue the over-the-wire RPC DESTROY_SESSION.
+ * The caller must serialize access to this routine.
+ */
+int nfs4_proc_destroy_session(struct nfs4_session *session)
+{
+       int status = 0;
+       struct rpc_message msg;
+
+       dprintk("--> nfs4_proc_destroy_session\n");
+
+       /* session is still being setup */
+       if (session->clp->cl_cons_state != NFS_CS_READY)
+               return status;
+
+       msg.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_DESTROY_SESSION];
+       msg.rpc_argp = session;
+       msg.rpc_resp = NULL;
+       msg.rpc_cred = NULL;
+       status = rpc_call_sync(session->clp->cl_rpcclient, &msg, 0);
+
+       if (status)
+               printk(KERN_WARNING
+                       "Got error %d from the server on DESTROY_SESSION. "
+                       "Session has been destroyed regardless...\n", status);
+
+       dprintk("<-- nfs4_proc_destroy_session\n");
+       return status;
+}
+
+/*
+ * Renew the cl_session lease.
+ */
+static int nfs4_proc_sequence(struct nfs_client *clp, struct rpc_cred *cred)
+{
+       struct nfs4_sequence_args args;
+       struct nfs4_sequence_res res;
+
+       struct rpc_message msg = {
+               .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SEQUENCE],
+               .rpc_argp = &args,
+               .rpc_resp = &res,
+               .rpc_cred = cred,
+       };
+
+       args.sa_cache_this = 0;
+
+       return nfs4_call_sync_sequence(clp, clp->cl_rpcclient, &msg, &args,
+                                      &res, 0);
+}
+
+void nfs41_sequence_call_done(struct rpc_task *task, void *data)
+{
+       struct nfs_client *clp = (struct nfs_client *)data;
+
+       nfs41_sequence_done(clp, task->tk_msg.rpc_resp, task->tk_status);
+
+       if (task->tk_status < 0) {
+               dprintk("%s ERROR %d\n", __func__, task->tk_status);
+
+               if (_nfs4_async_handle_error(task, NULL, clp, NULL)
+                                                               == -EAGAIN) {
+                       rpc_restart_call(task);
+                       return;
+               }
+       }
+       nfs41_sequence_free_slot(clp, task->tk_msg.rpc_resp);
+       dprintk("%s rpc_cred %p\n", __func__, task->tk_msg.rpc_cred);
+
+       put_rpccred(task->tk_msg.rpc_cred);
+       kfree(task->tk_msg.rpc_argp);
+       kfree(task->tk_msg.rpc_resp);
+
+       dprintk("<-- %s\n", __func__);
+}
+
+static void nfs41_sequence_prepare(struct rpc_task *task, void *data)
+{
+       struct nfs_client *clp;
+       struct nfs4_sequence_args *args;
+       struct nfs4_sequence_res *res;
+
+       clp = (struct nfs_client *)data;
+       args = task->tk_msg.rpc_argp;
+       res = task->tk_msg.rpc_resp;
+
+       if (nfs4_setup_sequence(clp, args, res, 0, task))
+               return;
+       rpc_call_start(task);
+}
+
+static const struct rpc_call_ops nfs41_sequence_ops = {
+       .rpc_call_done = nfs41_sequence_call_done,
+       .rpc_call_prepare = nfs41_sequence_prepare,
+};
+
+static int nfs41_proc_async_sequence(struct nfs_client *clp,
+                                    struct rpc_cred *cred)
+{
+       struct nfs4_sequence_args *args;
+       struct nfs4_sequence_res *res;
+       struct rpc_message msg = {
+               .rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_SEQUENCE],
+               .rpc_cred = cred,
+       };
+
+       args = kzalloc(sizeof(*args), GFP_KERNEL);
+       if (!args)
+               return -ENOMEM;
+       res = kzalloc(sizeof(*res), GFP_KERNEL);
+       if (!res) {
+               kfree(args);
+               return -ENOMEM;
+       }
+       res->sr_slotid = NFS4_MAX_SLOT_TABLE;
+       msg.rpc_argp = args;
+       msg.rpc_resp = res;
+
+       return rpc_call_async(clp->cl_rpcclient, &msg, RPC_TASK_SOFT,
+                             &nfs41_sequence_ops, (void *)clp);
+}
+
 #endif /* CONFIG_NFS_V4_1 */
 
 struct nfs4_state_recovery_ops nfs4_reboot_recovery_ops = {