ext3: Don't update superblock write time when filesystem is read-only
[safe/jmp/linux-2.6] / fs / nfs / callback.c
index fe0a6b8..293fa05 100644 (file)
 #include <linux/sunrpc/svcsock.h>
 #include <linux/nfs_fs.h>
 #include <linux/mutex.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/sunrpc/svcauth_gss.h>
+#if defined(CONFIG_NFS_V4_1)
+#include <linux/sunrpc/bc_xprt.h>
+#endif
 
 #include <net/inet_sock.h>
 
 #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;
 }
 
 /*