/*
 * linux/fs/nfsd/nfssvc.c
 *
 * Central processing for nfsd.
 *
 * Authors:	Olaf Kirch (okir@monad.swb.de)
 *
 * Copyright (C) 1995-1999 Olaf Kirch <okir@monad.swb.de>
 *
 * Apr 24, 2001: Added semaphores around nfsd_cache calls
 * 		 to protect it since it can yeild the processor
 * 		 in its kmalloc call.
 * 		 Craig I. Hagan <hagan@cih.com>
 */

#define __NO_VERSION__
#include <linux/config.h>
#include <linux/module.h>

#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/nfs.h>
#include <linux/in.h>
#include <linux/uio.h>
#include <linux/version.h>
#include <linux/unistd.h>
#include <linux/malloc.h>
#include <linux/smp.h>
#include <linux/smp_lock.h>

#include <linux/sunrpc/types.h>
#include <linux/sunrpc/stats.h>
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svcsock.h>
#include <linux/nfsd/nfsd.h>
#include <linux/nfsd/stats.h>
#include <linux/nfsd/cache.h>
#include <linux/nfsd/xdr.h>
#include <linux/lockd/bind.h>

#define NFSDDBG_FACILITY	NFSDDBG_SVC
#define NFSD_BUFSIZE		(1024 + NFSSVC_MAXBLKSIZE)

#define ALLOWED_SIGS	(sigmask(SIGKILL))
#define SHUTDOWN_SIGS	(sigmask(SIGKILL) | sigmask(SIGINT) | sigmask(SIGQUIT))

extern struct svc_program	nfsd_program;
static void			nfsd(struct svc_rqst *rqstp);
struct timeval			nfssvc_boot = { 0, 0 };
static atomic_t			nfsd_active = ATOMIC_INIT(0);

static struct semaphore nfsd_cache_sem = MUTEX;

/*
 * Maximum number of nfsd processes
 */
#define	NFSD_MAXSERVS		128

int
nfsd_svc(unsigned short port, int nrservs)
{
	struct svc_serv *	serv;
	int			error;

	error = -EINVAL;
	if (atomic_read(&nfsd_active))
		goto out;	/* already running */
	if (nrservs <= 0)
		goto out;
	if (nrservs > NFSD_MAXSERVS)
		nrservs = NFSD_MAXSERVS;

	dprintk("nfsd: creating service (%d)\n", nrservs);
	nfsd_nservers = nrservs;
	nfssvc_boot = xtime;

	error = -ENOMEM;
	serv = svc_create(&nfsd_program, NFSD_BUFSIZE, NFSSVC_XDRSIZE);
	if (serv == NULL)
		goto out;

	error = svc_makesock(serv, IPPROTO_UDP, port);
	if (error < 0)
		goto failure;

#ifdef CONFIG_NFSD_TCP
	error = svc_makesock(serv, IPPROTO_TCP, port);
	if (error < 0)
		goto failure;
#endif
	down(&nfsd_cache_sem);
	nfsd_racache_init();	/* Readahead param cache */
	up(&nfsd_cache_sem);

	while (nrservs--) {
		error = svc_create_thread(nfsd, serv);
		if (error < 0)
			break;
	}

failure:
	svc_destroy(serv);		/* Release server */
out:
	return error;
}

/*
 * This is the NFS server kernel thread
 */
static void
nfsd(struct svc_rqst *rqstp)
{
	struct svc_serv	*serv = rqstp->rq_server;
	int		err;

	/* Lock module */
	MOD_INC_USE_COUNT;

	/* Set up kernel thread */
	lock_kernel();
	exit_mm(current);
	sprintf(current->comm, "nfsd");
	current->session = 1;
	current->pgrp = 1;
	current->fs->umask = 0;

	/* Count active threads */
	atomic_inc(&nfsd_active);

	/* Start lockd */
	lockd_up();

	/* Let svc_process check client's authentication. */
	rqstp->rq_auth = 1;

	/*
	 * The main request loop
	 */
	for (;;) {
		/* Block all but the shutdown signals */
		spin_lock_irq(&current->sigmask_lock);
		siginitsetinv(&current->blocked, SHUTDOWN_SIGS);
		recalc_sigpending(current);
		spin_unlock_irq(&current->sigmask_lock);

		/*
		 * Find a socket with data available and call its
		 * recvfrom routine.
		 */
		while ((err = svc_recv(serv, rqstp,
				       MAX_SCHEDULE_TIMEOUT)) == -EAGAIN)
		    ;

                if (err < 0)
                        break;

		/* Lock the export hash tables for reading. */
		exp_readlock();

		/* Validate the client's address. This will also defeat
		 * port probes on port 2049 by unauthorized clients.
		 */
		rqstp->rq_client = exp_getclient(&rqstp->rq_addr);
		/* Process request with signals blocked.  */
		spin_lock_irq(&current->sigmask_lock);
		siginitsetinv(&current->blocked, ALLOWED_SIGS);
		recalc_sigpending(current);
		spin_unlock_irq(&current->sigmask_lock);


		svc_process(serv, rqstp);

		/* Unlock export hash tables */
		exp_unlock();
	}

	if (err != -EINTR) {
		printk(KERN_WARNING "nfsd: terminating on error %d\n", -err);
	} else {
		unsigned int	signo;

		for (signo = 1; signo <= _NSIG; signo++)
			if (sigismember(&current->signal, signo) &&
			    !sigismember(&current->blocked, signo))
				break;
		printk(KERN_WARNING "nfsd: terminating on signal %d\n", signo);
	}

	/* Count active threads */
	if (atomic_dec_and_test(&nfsd_active)) {
		down(&nfsd_cache_sem);
		nfsd_export_shutdown();		/* revoke all exports */
	        nfsd_racache_shutdown();	/* release read-ahead cache */
		up(&nfsd_cache_sem);
	}

	/* Release lockd */
	lockd_down();

	/* Destroy the thread's resources */
	svc_exit_thread(rqstp);

	/* Release module */
	MOD_DEC_USE_COUNT;
}

static int
nfsd_dispatch(struct svc_rqst *rqstp, u32 *statp)
{
	struct svc_procedure	*proc;
	kxdrproc_t		xdr;
	u32			nfserr;
	int			res_lookup;

	dprintk("nfsd_dispatch: vers %d proc %d\n",
				rqstp->rq_vers, rqstp->rq_proc);
	proc = rqstp->rq_procinfo;

	/* Check whether we have this call in the cache. */

	down(&nfsd_cache_sem);
	res_lookup = nfsd_cache_lookup(rqstp, proc->pc_cachetype);
	up(&nfsd_cache_sem);

	switch (res_lookup) {
	case RC_INTR:
	case RC_DROPIT:
		return 0;
	case RC_REPLY:
		return 1;
	case RC_DOIT:
		/* do it */
	}

	/* Decode arguments */
	xdr = proc->pc_decode;
	if (xdr && !xdr(rqstp, rqstp->rq_argbuf.buf, rqstp->rq_argp)) {
		dprintk("nfsd: failed to decode arguments!\n");
		down(&nfsd_cache_sem);
		nfsd_cache_update(rqstp, RC_NOCACHE, NULL);
		up(&nfsd_cache_sem);
		*statp = rpc_garbage_args;
		return 1;
	}

	/* Now call the procedure handler, and encode NFS status. */
	nfserr = proc->pc_func(rqstp, rqstp->rq_argp, rqstp->rq_resp);
	if (rqstp->rq_proc != 0)
		svc_putlong(&rqstp->rq_resbuf, nfserr);

	/* Encode result.
	 * For NFSv2, additional info is never returned in case of an error.
	 */
#ifdef CONFIG_NFSD_V3
	if (!(nfserr && rqstp->rq_vers == 2)) {
		xdr = proc->pc_encode;
		if (xdr && !xdr(rqstp, rqstp->rq_resbuf.buf, rqstp->rq_resp)) {
			/* Failed to encode result. Release cache entry */
			dprintk("nfsd: failed to encode result!\n");
			down(&nfsd_cache_sem);
			nfsd_cache_update(rqstp, RC_NOCACHE, NULL);
			up(&nfsd_cache_sem);
			*statp = rpc_system_err;
			return 1;
		}
	}
#else
	xdr = proc->pc_encode;
	if (!nfserr && xdr
	 && !xdr(rqstp, rqstp->rq_resbuf.buf, rqstp->rq_resp)) {
		/* Failed to encode result. Release cache entry */
		dprintk("nfsd: failed to encode result!\n");
		down(&nfsd_cache_sem);
		nfsd_cache_update(rqstp, RC_NOCACHE, NULL);
		up(&nfsd_cache_sem);
		*statp = rpc_system_err;
		return 1;
	}
#endif /* CONFIG_NFSD_V3 */

	/* Store reply in cache. */
	down(&nfsd_cache_sem);
	nfsd_cache_update(rqstp, proc->pc_cachetype, statp + 1);
	up(&nfsd_cache_sem);
	return 1;
}

static struct svc_version	nfsd_version2 = {
	2, 18, nfsd_procedures2, nfsd_dispatch
};
#ifdef CONFIG_NFSD_V3
static struct svc_version	nfsd_version3 = {
	3, 22, nfsd_procedures3, nfsd_dispatch
};
#endif
static struct svc_version *	nfsd_version[] = {
	NULL,
	NULL,
	&nfsd_version2,
#ifdef CONFIG_NFSD_V3
	&nfsd_version3,
#endif
};

#define NFSD_NRVERS		(sizeof(nfsd_version)/sizeof(nfsd_version[0]))
struct svc_program		nfsd_program = {
	NFS_PROGRAM,		/* program number */
	2, NFSD_NRVERS-1,	/* version range */
	NFSD_NRVERS,		/* nr of entries in nfsd_version */
	nfsd_version,		/* version table */
	"nfsd",			/* program name */
	&nfsd_svcstats,		/* version table */
};
