From 7146851376861fe55c7a48ac8fc1354a5fff6cd0 Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Wed, 1 Apr 2009 09:22:56 -0400 Subject: nfs41: minorversion support for nfs4_{init,destroy}_callback move nfs4_init_callback into nfs4_init_client_minor_version and nfs4_destroy_callback into nfs4_clear_client_minor_version as these need to happen also when auto-negotiating the minorversion once the callback service for nfs41 becomes different than for nfs4.0 Signed-off-by: Benny Halevy [nfs41: Fix checkpatch warning] Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy [Type check arguments of nfs_callback_up] Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy [nfs41: Backchannel: Remove FIXME comment] Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy --- fs/nfs/callback.c | 65 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 19 deletions(-) (limited to 'fs/nfs/callback.c') diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index a886e692ddd0..4b1313eda6f5 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -59,7 +59,7 @@ module_param_call(callback_tcpport, param_set_port, param_get_int, * This is the callback kernel thread. */ static int -nfs_callback_svc(void *vrqstp) +nfs4_callback_svc(void *vrqstp) { int err, preverr = 0; struct svc_rqst *rqstp = vrqstp; @@ -97,20 +97,12 @@ nfs_callback_svc(void *vrqstp) } /* - * Bring up the callback thread if it is not already up. + * Prepare to bring up the NFSv4 callback service */ -int nfs_callback_up(void) +struct svc_rqst * +nfs4_callback_up(struct svc_serv *serv) { - struct svc_serv *serv = NULL; - int ret = 0; - - mutex_lock(&nfs_callback_mutex); - if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) - goto out; - serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); - ret = -ENOMEM; - if (!serv) - goto out_err; + int ret; ret = svc_create_xprt(serv, "tcp", PF_INET, nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); @@ -131,18 +123,53 @@ int nfs_callback_up(void) goto out_err; #endif /* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */ - nfs_callback_info.rqst = svc_prepare_thread(serv, &serv->sv_pools[0]); - if (IS_ERR(nfs_callback_info.rqst)) { - ret = PTR_ERR(nfs_callback_info.rqst); - nfs_callback_info.rqst = NULL; + return svc_prepare_thread(serv, &serv->sv_pools[0]); + +out_err: + if (ret == 0) + ret = -ENOMEM; + return ERR_PTR(ret); +} + +/* + * Bring up the callback thread if it is not already up. + */ +int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) +{ + struct svc_serv *serv = NULL; + struct svc_rqst *rqstp; + int (*callback_svc)(void *vrqstp); + char svc_name[12]; + int ret = 0; + + mutex_lock(&nfs_callback_mutex); + if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) + goto out; + serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); + if (!serv) { + ret = -ENOMEM; + goto out_err; + } + + if (!minorversion) { + rqstp = nfs4_callback_up(serv); + callback_svc = nfs4_callback_svc; + } else { + BUG(); /* for now */ + } + + if (IS_ERR(rqstp)) { + ret = PTR_ERR(rqstp); goto out_err; } svc_sock_update_bufs(serv); - nfs_callback_info.task = kthread_run(nfs_callback_svc, + sprintf(svc_name, "nfsv4.%u-svc", minorversion); + nfs_callback_info.rqst = rqstp; + nfs_callback_info.task = kthread_run(callback_svc, nfs_callback_info.rqst, - "nfsv4-svc"); + svc_name); if (IS_ERR(nfs_callback_info.task)) { ret = PTR_ERR(nfs_callback_info.task); svc_exit_thread(nfs_callback_info.rqst); -- cgit From a43cde94feded0f65fce36330614691c650ae8fe Mon Sep 17 00:00:00 2001 From: Ricardo Labiaga Date: Wed, 1 Apr 2009 09:23:08 -0400 Subject: nfs41: Implement NFSv4.1 callback service process. nfs41_callback_up() initializes the necessary queues and creates the new nfs41_callback_svc thread. This thread executes the callback service which waits for requests to arrive on the svc_serv->sv_cb_list. NFS41_BC_MIN_CALLBACKS is set to 1 because we expect callbacks to not cause substantial latency. The actual processing of the callback will be implemented as a separate patch. There is only one NFSv4.1 callback service. The first caller of nfs4_callback_up() creates the service, subsequent callers increment a reference count on the service. The service is destroyed when the last caller invokes nfs_callback_down(). The transport needs to hold a reference to the callback service in order to invoke it during callback processing. Currently this reference is only obtained when the service is first created. This is incorrect, since subsequent registrations for other transports will leave the xprt->serv pointer uninitialized, leading to an oops when a callback arrives on the "unreferenced" transport. This patch fixes the problem by ensuring that a reference to the service is saved in xprt->serv, either because the service is created by this invocation to nfs4_callback_up() or by a prior invocation. Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy [nfs41: Add a reference to svc_serv during callback service bring up] Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy [Type check arguments of nfs_callback_up] Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy [nfs41: save svc_serv in nfs_callback_info] Signed-off-by: Benny Halevy [Removal of ugly #ifdefs] [nfs41: Update to removal of ugly #ifdefs] Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy --- fs/nfs/callback.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- fs/nfs/callback.h | 7 ++++ 2 files changed, 115 insertions(+), 4 deletions(-) (limited to 'fs/nfs/callback.c') diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index 4b1313eda6f5..470928898063 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -17,6 +17,9 @@ #include #include #include +#if defined(CONFIG_NFS_V4_1) +#include +#endif #include @@ -28,6 +31,7 @@ struct nfs_callback_data { unsigned int users; + struct svc_serv *serv; struct svc_rqst *rqst; struct task_struct *task; }; @@ -131,6 +135,99 @@ out_err: return ERR_PTR(ret); } +#if defined(CONFIG_NFS_V4_1) +/* + * The callback service for NFSv4.1 callbacks + */ +static int +nfs41_callback_svc(void *vrqstp) +{ + struct svc_rqst *rqstp = vrqstp; + struct svc_serv *serv = rqstp->rq_server; + struct rpc_rqst *req; + int error; + DEFINE_WAIT(wq); + + set_freezable(); + + /* + * FIXME: do we really need to run this under the BKL? If so, please + * add a comment about what it's intended to protect. + */ + lock_kernel(); + while (!kthread_should_stop()) { + prepare_to_wait(&serv->sv_cb_waitq, &wq, TASK_INTERRUPTIBLE); + spin_lock_bh(&serv->sv_cb_lock); + if (!list_empty(&serv->sv_cb_list)) { + req = list_first_entry(&serv->sv_cb_list, + struct rpc_rqst, rq_bc_list); + list_del(&req->rq_bc_list); + spin_unlock_bh(&serv->sv_cb_lock); + dprintk("Invoking bc_svc_process()\n"); + error = bc_svc_process(serv, req, rqstp); + dprintk("bc_svc_process() returned w/ error code= %d\n", + error); + } else { + spin_unlock_bh(&serv->sv_cb_lock); + schedule(); + } + finish_wait(&serv->sv_cb_waitq, &wq); + } + unlock_kernel(); + nfs_callback_info.task = NULL; + svc_exit_thread(rqstp); + return 0; +} + +/* + * Bring up the NFSv4.1 callback service + */ +struct svc_rqst * +nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) +{ + /* + * Save the svc_serv in the transport so that it can + * be referenced when the session backchannel is initialized + */ + xprt->bc_serv = serv; + + INIT_LIST_HEAD(&serv->sv_cb_list); + spin_lock_init(&serv->sv_cb_lock); + init_waitqueue_head(&serv->sv_cb_waitq); + return svc_prepare_thread(serv, &serv->sv_pools[0]); +} + +static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, + struct svc_serv *serv, struct rpc_xprt *xprt, + struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) +{ + if (minorversion) { + *rqstpp = nfs41_callback_up(serv, xprt); + *callback_svc = nfs41_callback_svc; + } + return minorversion; +} + +static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, + struct nfs_callback_data *cb_info) +{ + if (minorversion) + xprt->bc_serv = cb_info->serv; +} +#else +static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, + struct svc_serv *serv, struct rpc_xprt *xprt, + struct svc_rqst **rqstpp, int (**callback_svc)(void *vrqstp)) +{ + return 0; +} + +static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, + struct nfs_callback_data *cb_info) +{ +} +#endif /* CONFIG_NFS_V4_1 */ + /* * Bring up the callback thread if it is not already up. */ @@ -141,21 +238,25 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) int (*callback_svc)(void *vrqstp); char svc_name[12]; int ret = 0; + int minorversion_setup; mutex_lock(&nfs_callback_mutex); - if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) + if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) { + nfs_callback_bc_serv(minorversion, xprt, &nfs_callback_info); goto out; + } serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); if (!serv) { ret = -ENOMEM; goto out_err; } - if (!minorversion) { + minorversion_setup = nfs_minorversion_callback_svc_setup(minorversion, + serv, xprt, &rqstp, &callback_svc); + if (!minorversion_setup) { + /* v4.0 callback setup */ rqstp = nfs4_callback_up(serv); callback_svc = nfs4_callback_svc; - } else { - BUG(); /* for now */ } if (IS_ERR(rqstp)) { @@ -166,6 +267,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) svc_sock_update_bufs(serv); sprintf(svc_name, "nfsv4.%u-svc", minorversion); + nfs_callback_info.serv = serv; nfs_callback_info.rqst = rqstp; nfs_callback_info.task = kthread_run(callback_svc, nfs_callback_info.rqst, @@ -173,6 +275,7 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) if (IS_ERR(nfs_callback_info.task)) { ret = PTR_ERR(nfs_callback_info.task); svc_exit_thread(nfs_callback_info.rqst); + nfs_callback_info.serv = NULL; nfs_callback_info.rqst = NULL; nfs_callback_info.task = NULL; goto out_err; @@ -205,6 +308,7 @@ void nfs_callback_down(void) if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) { kthread_stop(nfs_callback_info.task); svc_exit_thread(nfs_callback_info.rqst); + nfs_callback_info.serv = NULL; nfs_callback_info.rqst = NULL; nfs_callback_info.task = NULL; } diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h index 9b907f408b8d..29123b5604f2 100644 --- a/fs/nfs/callback.h +++ b/fs/nfs/callback.h @@ -70,6 +70,13 @@ extern void nfs_callback_down(void); #define nfs_callback_down() do {} while(0) #endif +/* + * nfs41: Callbacks are expected to not cause substantial latency, + * so we limit their concurrency to 1 by setting up the maximum number + * of slots for the backchannel. + */ +#define NFS41_BC_MIN_CALLBACKS 1 + extern unsigned int nfs_callback_set_tcpport; extern unsigned short nfs_callback_tcpport; extern unsigned short nfs_callback_tcpport6; -- cgit From 8f975242352e92898dc641ebff0d24808f39848a Mon Sep 17 00:00:00 2001 From: Benny Halevy Date: Wed, 1 Apr 2009 09:23:11 -0400 Subject: nfs41: create a svc_xprt for nfs41 callback thread and use for incoming callbacks Signed-off-by: Benny Halevy --- fs/nfs/callback.c | 17 ++++++++++++++++- net/sunrpc/svc.c | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'fs/nfs/callback.c') diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index 470928898063..37815f3216aa 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -185,16 +185,31 @@ nfs41_callback_svc(void *vrqstp) struct svc_rqst * nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) { + struct svc_xprt *bc_xprt; + struct svc_rqst *rqstp = ERR_PTR(-ENOMEM); + + dprintk("--> %s\n", __func__); + /* Create a svc_sock for the service */ + bc_xprt = svc_sock_create(serv, xprt->prot); + if (!bc_xprt) + goto out; + /* * Save the svc_serv in the transport so that it can * be referenced when the session backchannel is initialized */ + serv->bc_xprt = bc_xprt; xprt->bc_serv = serv; INIT_LIST_HEAD(&serv->sv_cb_list); spin_lock_init(&serv->sv_cb_lock); init_waitqueue_head(&serv->sv_cb_waitq); - return svc_prepare_thread(serv, &serv->sv_pools[0]); + rqstp = svc_prepare_thread(serv, &serv->sv_pools[0]); + if (IS_ERR(rqstp)) + svc_sock_destroy(bc_xprt); +out: + dprintk("--> %s return %p\n", __func__, rqstp); + return rqstp; } static inline int nfs_minorversion_callback_svc_setup(u32 minorversion, diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index b35048fabe22..6b90ce439c00 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -1258,6 +1258,7 @@ bc_svc_process(struct svc_serv *serv, struct rpc_rqst *req, int error; /* Build the svc_rqst used by the common processing routine */ + rqstp->rq_xprt = serv->bc_xprt; rqstp->rq_xid = req->rq_xid; rqstp->rq_prot = req->rq_xprt->prot; rqstp->rq_server = serv; -- cgit From e82dc22dac6525a2f365a1d53c0483252d4aa38e Mon Sep 17 00:00:00 2001 From: Andy Adamson Date: Wed, 1 Apr 2009 09:23:14 -0400 Subject: nfs41: Allow NFSv4 and NFSv4.1 callback services to coexist Tracks the nfs_callback_info for both versions, enabling the callback service for v4 and v4.1 to run concurrently and be stopped independently of each other. Signed-off-by: Ricardo Labiaga Signed-off-by: Benny Halevy --- fs/nfs/callback.c | 50 ++++++++++++++++++++++++-------------------------- fs/nfs/callback.h | 7 ++----- fs/nfs/client.c | 2 +- 3 files changed, 27 insertions(+), 32 deletions(-) (limited to 'fs/nfs/callback.c') diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index 37815f3216aa..e69b8f61189e 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -36,7 +36,7 @@ struct nfs_callback_data { struct task_struct *task; }; -static struct nfs_callback_data nfs_callback_info; +static struct nfs_callback_data nfs_callback_info[NFS4_MAX_MINOR_VERSION + 1]; static DEFINE_MUTEX(nfs_callback_mutex); static struct svc_program nfs4_callback_program; @@ -60,7 +60,7 @@ module_param_call(callback_tcpport, param_set_port, param_get_int, &nfs_callback_set_tcpport, 0644); /* - * This is the callback kernel thread. + * This is the NFSv4 callback kernel thread. */ static int nfs4_callback_svc(void *vrqstp) @@ -174,8 +174,6 @@ nfs41_callback_svc(void *vrqstp) finish_wait(&serv->sv_cb_waitq, &wq); } unlock_kernel(); - nfs_callback_info.task = NULL; - svc_exit_thread(rqstp); return 0; } @@ -251,13 +249,14 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) struct svc_serv *serv = NULL; struct svc_rqst *rqstp; int (*callback_svc)(void *vrqstp); + struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; char svc_name[12]; int ret = 0; int minorversion_setup; mutex_lock(&nfs_callback_mutex); - if (nfs_callback_info.users++ || nfs_callback_info.task != NULL) { - nfs_callback_bc_serv(minorversion, xprt, &nfs_callback_info); + if (cb_info->users++ || cb_info->task != NULL) { + nfs_callback_bc_serv(minorversion, xprt, cb_info); goto out; } serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); @@ -282,17 +281,14 @@ int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) svc_sock_update_bufs(serv); sprintf(svc_name, "nfsv4.%u-svc", minorversion); - nfs_callback_info.serv = serv; - nfs_callback_info.rqst = rqstp; - nfs_callback_info.task = kthread_run(callback_svc, - nfs_callback_info.rqst, - svc_name); - if (IS_ERR(nfs_callback_info.task)) { - ret = PTR_ERR(nfs_callback_info.task); - svc_exit_thread(nfs_callback_info.rqst); - nfs_callback_info.serv = NULL; - nfs_callback_info.rqst = NULL; - nfs_callback_info.task = NULL; + cb_info->serv = serv; + cb_info->rqst = rqstp; + cb_info->task = kthread_run(callback_svc, cb_info->rqst, svc_name); + if (IS_ERR(cb_info->task)) { + ret = PTR_ERR(cb_info->task); + svc_exit_thread(cb_info->rqst); + cb_info->rqst = NULL; + cb_info->task = NULL; goto out_err; } out: @@ -309,23 +305,25 @@ out: out_err: dprintk("NFS: Couldn't create callback socket or server thread; " "err = %d\n", ret); - nfs_callback_info.users--; + cb_info->users--; goto out; } /* * Kill the callback thread if it's no longer being used. */ -void nfs_callback_down(void) +void nfs_callback_down(int minorversion) { + struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; + mutex_lock(&nfs_callback_mutex); - nfs_callback_info.users--; - if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) { - kthread_stop(nfs_callback_info.task); - svc_exit_thread(nfs_callback_info.rqst); - nfs_callback_info.serv = NULL; - nfs_callback_info.rqst = NULL; - nfs_callback_info.task = NULL; + cb_info->users--; + if (cb_info->users == 0 && cb_info->task != NULL) { + kthread_stop(cb_info->task); + svc_exit_thread(cb_info->rqst); + cb_info->serv = NULL; + cb_info->rqst = NULL; + cb_info->task = NULL; } mutex_unlock(&nfs_callback_mutex); } diff --git a/fs/nfs/callback.h b/fs/nfs/callback.h index 29123b5604f2..88d2e1135b5a 100644 --- a/fs/nfs/callback.h +++ b/fs/nfs/callback.h @@ -64,11 +64,8 @@ extern __be32 nfs4_callback_recall(struct cb_recallargs *args, void *dummy); #ifdef CONFIG_NFS_V4 extern int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt); -extern void nfs_callback_down(void); -#else -#define nfs_callback_up() (0) -#define nfs_callback_down() do {} while(0) -#endif +extern void nfs_callback_down(int minorversion); +#endif /* CONFIG_NFS_V4 */ /* * nfs41: Callbacks are expected to not cause substantial latency, diff --git a/fs/nfs/client.c b/fs/nfs/client.c index df2b40d1883d..ac6575c4da31 100644 --- a/fs/nfs/client.c +++ b/fs/nfs/client.c @@ -181,7 +181,7 @@ static void nfs4_destroy_callback(struct nfs_client *clp) { #ifdef CONFIG_NFS_V4 if (__test_and_clear_bit(NFS_CS_CALLBACK, &clp->cl_res_state)) - nfs_callback_down(); + nfs_callback_down(clp->cl_minorversion); #endif /* CONFIG_NFS_V4 */ } -- cgit From 18fc31641925867c871bc75270ce642c039188d3 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 17 Jun 2009 18:02:10 -0700 Subject: NFS: Fix false error return from nfs_callback_up() if ipv6.ko is not available Clear "ret" if the error return from svc_create_xprt(AF_INET6) was -EAFNOSUPORT. Otherwise, callback start-up will succeed, but nfs_callback_up() will return -EAFNOSUPPORT anyway, and the first NFSv4 mount attempt after a reboot will fail. Bug introduced by commit f738f517 in 2.6.30-rc1. Signed-off-by: Chuck Lever Signed-off-by: Trond Myklebust --- fs/nfs/callback.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'fs/nfs/callback.c') diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index a886e692ddd0..6e3bd42c21cd 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -127,7 +127,9 @@ int nfs_callback_up(void) nfs_callback_tcpport6 = ret; dprintk("NFS: Callback listener port = %u (af %u)\n", nfs_callback_tcpport6, PF_INET6); - } else if (ret != -EAFNOSUPPORT) + } else if (ret == -EAFNOSUPPORT) + ret = 0; + else goto out_err; #endif /* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */ -- cgit