X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=fs%2Fnfs%2Fcallback.c;h=293fa0528a6e10001a66ee5e94c33fcfa95b7c65;hb=96ec2e0a719fd61791dd2b0dd01325c5d20e1233;hp=d53f8c6a9ecb5483fe0467404d92e895acba5a75;hpb=f7b422b17ee5ee4920e8ae24a6ad04bf3481ce72;p=safe%2Fjmp%2Flinux-2.6 diff --git a/fs/nfs/callback.c b/fs/nfs/callback.c index d53f8c6..293fa05 100644 --- a/fs/nfs/callback.c +++ b/fs/nfs/callback.c @@ -6,7 +6,6 @@ * NFSv4 callback handling */ -#include #include #include #include @@ -15,168 +14,382 @@ #include #include #include +#include +#include +#include +#if defined(CONFIG_NFS_V4_1) +#include +#endif #include #include "nfs4_fs.h" #include "callback.h" +#include "internal.h" #define NFSDBG_FACILITY NFSDBG_CALLBACK struct nfs_callback_data { unsigned int users; struct svc_serv *serv; - pid_t pid; - struct completion started; - struct completion stopped; + struct svc_rqst *rqst; + 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; unsigned int nfs_callback_set_tcpport; unsigned short nfs_callback_tcpport; +unsigned short nfs_callback_tcpport6; +#define NFS_CALLBACK_MAXPORTNR (65535U) -/* - * This is the callback kernel thread. - */ -static void nfs_callback_svc(struct svc_rqst *rqstp) +static int param_set_portnr(const char *val, struct kernel_param *kp) { - struct svc_serv *serv = rqstp->rq_server; - int err; + unsigned long num; + int ret; - __module_get(THIS_MODULE); - lock_kernel(); + if (!val) + return -EINVAL; + ret = strict_strtoul(val, 0, &num); + if (ret == -EINVAL || num > NFS_CALLBACK_MAXPORTNR) + return -EINVAL; + *((unsigned int *)kp->arg) = num; + return 0; +} + +static int param_get_portnr(char *buffer, struct kernel_param *kp) +{ + return param_get_uint(buffer, kp); +} +#define param_check_portnr(name, p) __param_check(name, p, unsigned int); - nfs_callback_info.pid = current->pid; - daemonize("nfsv4-svc"); - /* Process request with signals blocked, but allow SIGKILL. */ - allow_signal(SIGKILL); +module_param_named(callback_tcpport, nfs_callback_set_tcpport, portnr, 0644); - complete(&nfs_callback_info.started); +/* + * This is the NFSv4 callback kernel thread. + */ +static int +nfs4_callback_svc(void *vrqstp) +{ + int err, preverr = 0; + struct svc_rqst *rqstp = vrqstp; - for(;;) { - if (signalled()) { - if (nfs_callback_info.users == 0) - break; - flush_signals(current); - } + 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()) { /* * Listen for a request on the socket */ - err = svc_recv(serv, rqstp, MAX_SCHEDULE_TIMEOUT); - if (err == -EAGAIN || err == -EINTR) + err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT); + if (err == -EAGAIN || err == -EINTR) { + preverr = err; continue; + } if (err < 0) { - printk(KERN_WARNING - "%s: terminating on error %d\n", - __FUNCTION__, -err); - break; + if (err != preverr) { + printk(KERN_WARNING "%s: unexpected error " + "from svc_recv (%d)\n", __func__, err); + preverr = err; + } + schedule_timeout_uninterruptible(HZ); + continue; } - dprintk("%s: request from %u.%u.%u.%u\n", __FUNCTION__, - NIPQUAD(rqstp->rq_addr.sin_addr.s_addr)); - svc_process(serv, rqstp); + preverr = err; + svc_process(rqstp); } + unlock_kernel(); + return 0; +} + +/* + * Prepare to bring up the NFSv4 callback service + */ +struct svc_rqst * +nfs4_callback_up(struct svc_serv *serv) +{ + int ret; + + ret = svc_create_xprt(serv, "tcp", PF_INET, + nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); + if (ret <= 0) + goto out_err; + nfs_callback_tcpport = ret; + dprintk("NFS: Callback listener port = %u (af %u)\n", + nfs_callback_tcpport, PF_INET); + +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + ret = svc_create_xprt(serv, "tcp", PF_INET6, + nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS); + if (ret > 0) { + nfs_callback_tcpport6 = ret; + dprintk("NFS: Callback listener port = %u (af %u)\n", + nfs_callback_tcpport6, PF_INET6); + } else if (ret == -EAFNOSUPPORT) + ret = 0; + else + goto out_err; +#endif /* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */ + + return svc_prepare_thread(serv, &serv->sv_pools[0]); + +out_err: + if (ret == 0) + ret = -ENOMEM; + 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); - svc_exit_thread(rqstp); - nfs_callback_info.pid = 0; - complete(&nfs_callback_info.stopped); + 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(); - module_put_and_exit(0); + return 0; } /* - * Bring up the server process if it is not already up. + * Bring up the NFSv4.1 callback service */ -int nfs_callback_up(void) +struct svc_rqst * +nfs41_callback_up(struct svc_serv *serv, struct rpc_xprt *xprt) { - struct svc_serv *serv; - struct svc_sock *svsk; + 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); + 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, + 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. + */ +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; - lock_kernel(); mutex_lock(&nfs_callback_mutex); - if (nfs_callback_info.users++ || nfs_callback_info.pid != 0) + if (cb_info->users++ || cb_info->task != NULL) { + nfs_callback_bc_serv(minorversion, xprt, cb_info); goto out; - init_completion(&nfs_callback_info.started); - init_completion(&nfs_callback_info.stopped); - serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE); - ret = -ENOMEM; - if (!serv) + } + serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL); + if (!serv) { + ret = -ENOMEM; + goto out_err; + } + + 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; + } + + if (IS_ERR(rqstp)) { + ret = PTR_ERR(rqstp); goto out_err; - /* FIXME: We don't want to register this socket with the portmapper */ - ret = svc_makesock(serv, IPPROTO_TCP, nfs_callback_set_tcpport); - if (ret < 0) - goto out_destroy; - if (!list_empty(&serv->sv_permsocks)) { - svsk = list_entry(serv->sv_permsocks.next, - struct svc_sock, sk_list); - nfs_callback_tcpport = ntohs(inet_sk(svsk->sk_sk)->sport); - dprintk ("Callback port = 0x%x\n", nfs_callback_tcpport); - } else - BUG(); - ret = svc_create_thread(nfs_callback_svc, serv); - if (ret < 0) - goto out_destroy; - nfs_callback_info.serv = serv; - wait_for_completion(&nfs_callback_info.started); + } + + svc_sock_update_bufs(serv); + + sprintf(svc_name, "nfsv4.%u-svc", minorversion); + 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: + /* + * svc_create creates the svc_serv with sv_nrthreads == 1, and then + * svc_prepare_thread increments that. So we need to call svc_destroy + * on both success and failure so that the refcount is 1 when the + * thread exits. + */ + if (serv) + svc_destroy(serv); mutex_unlock(&nfs_callback_mutex); - unlock_kernel(); return ret; -out_destroy: - svc_destroy(serv); out_err: - nfs_callback_info.users--; + dprintk("NFS: Couldn't create callback socket or server thread; " + "err = %d\n", ret); + cb_info->users--; goto out; } /* - * Kill the server process if it is not already up. + * Kill the callback thread if it's no longer being used. */ -int nfs_callback_down(void) +void nfs_callback_down(int minorversion) { - int ret = 0; + struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; - lock_kernel(); mutex_lock(&nfs_callback_mutex); - nfs_callback_info.users--; - do { - if (nfs_callback_info.users != 0 || nfs_callback_info.pid == 0) - break; - if (kill_proc(nfs_callback_info.pid, SIGKILL, 1) < 0) - break; - } while (wait_for_completion_timeout(&nfs_callback_info.stopped, 5*HZ) == 0); + 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); - unlock_kernel(); - return ret; +} + +static int check_gss_callback_principal(struct nfs_client *clp, + struct svc_rqst *rqstp) +{ + struct rpc_clnt *r = clp->cl_rpcclient; + char *p = svc_gss_principal(rqstp); + + /* + * It might just be a normal user principal, in which case + * userspace won't bother to tell us the name at all. + */ + if (p == NULL) + return SVC_DENIED; + + /* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */ + + if (memcmp(p, "nfs@", 4) != 0) + return SVC_DENIED; + p += 4; + if (strcmp(p, r->cl_server) != 0) + return SVC_DENIED; + return SVC_OK; } static int nfs_callback_authenticate(struct svc_rqst *rqstp) { - struct in_addr *addr = &rqstp->rq_addr.sin_addr; - struct nfs4_client *clp; + struct nfs_client *clp; + RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]); + int ret = SVC_OK; /* Don't talk to strangers */ - clp = nfs4_find_client(addr); + clp = nfs_find_client(svc_addr(rqstp), 4); if (clp == NULL) return SVC_DROP; - dprintk("%s: %u.%u.%u.%u NFSv4 callback!\n", __FUNCTION__, NIPQUAD(addr)); - nfs4_put_client(clp); + + dprintk("%s: %s NFSv4 callback!\n", __func__, + svc_print_addr(rqstp, buf, sizeof(buf))); + switch (rqstp->rq_authop->flavour) { case RPC_AUTH_NULL: if (rqstp->rq_proc != CB_NULL) - return SVC_DENIED; + ret = SVC_DENIED; break; case RPC_AUTH_UNIX: break; case RPC_AUTH_GSS: - /* FIXME: RPCSEC_GSS handling? */ + ret = check_gss_callback_principal(clp, rqstp); + break; default: - return SVC_DENIED; + ret = SVC_DENIED; } - return SVC_OK; + nfs_put_client(clp); + return ret; } /*