/*
 * nfsd
 *
 * This is the user level part of nfsd. This is very primitive, because
 * all the work is now done in the kernel module.
 *
 * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <netdb.h>
#include <libgen.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sched.h>

#include "conffile.h"
#include "nfslib.h"
#include "nfssvc.h"
#include "xlog.h"
#include "xcommon.h"

#ifndef NFSD_NPROC
#define NFSD_NPROC 8
#endif

static void	usage(const char *);

static struct option longopts[] =
{
	{ "host", 1, 0, 'H' },
	{ "scope", 1, 0, 'S'},
	{ "help", 0, 0, 'h' },
	{ "no-nfs-version", 1, 0, 'N' },
	{ "nfs-version", 1, 0, 'V' },
	{ "tcp", 0, 0, 't' },
	{ "no-tcp", 0, 0, 'T' },
	{ "udp", 0, 0, 'u' },
	{ "no-udp", 0, 0, 'U' },
	{ "port", 1, 0, 'P' },
	{ "port", 1, 0, 'p' },
	{ "debug", 0, 0, 'd' },
	{ "syslog", 0, 0, 's' },
	{ "rdma", 2, 0, 'R' },
	{ "grace-time", 1, 0, 'G'},
	{ "lease-time", 1, 0, 'L'},
	{ NULL, 0, 0, 0 }
};

inline static void 
read_nfsd_conf(void)
{
	conf_init_file(NFS_CONFFILE); 
	xlog_set_debug("nfsd");
}

int
main(int argc, char **argv)
{
	int	count = NFSD_NPROC, c, i, error = 0, portnum, fd, found_one;
	char *p, *progname, *port, *rdma_port = NULL;
	char **haddr = NULL;
	char *scope = NULL;
	int hcounter = 0;
	struct conf_list *hosts;
	int	socket_up = 0;
	unsigned int minorvers = NFSCTL_MINDEFAULT;
	unsigned int minorversset = NFSCTL_MINDEFAULT;
	unsigned int minormask = 0;
	unsigned int versbits = NFSCTL_VERDEFAULT;
	unsigned int protobits = NFSCTL_PROTODEFAULT;
	int grace = -1;
	int lease = -1;
	int force4dot0 = 0;

	progname = basename(argv[0]);
	haddr = xmalloc(sizeof(char *));
	haddr[0] = NULL;

	xlog_syslog(0);
	xlog_stderr(1);

	/* Read in config setting */
	read_nfsd_conf();

	nfssvc_get_minormask(&minormask);

	count = conf_get_num("nfsd", "threads", count);
	grace = conf_get_num("nfsd", "grace-time", grace);
	lease = conf_get_num("nfsd", "lease-time", lease);
	port = conf_get_str("nfsd", "port");
	if (!port)
		port = "nfs";
	if (conf_get_bool("nfsd", "rdma", false)) {
		rdma_port = conf_get_str("nfsd", "rdma-port");
		if (!rdma_port)
			rdma_port = "nfsrdma";
	}
	/* backward compatibility - nfs.conf used to set rdma port directly */
	if (!rdma_port)
		rdma_port = conf_get_str("nfsd", "rdma");
	if (conf_get_bool("nfsd", "udp", NFSCTL_UDPISSET(protobits)))
		NFSCTL_UDPSET(protobits);
	else
		NFSCTL_UDPUNSET(protobits);
	if (conf_get_bool("nfsd", "tcp", NFSCTL_TCPISSET(protobits)))
		NFSCTL_TCPSET(protobits);
	else
		NFSCTL_TCPUNSET(protobits);
	for (i = 2; i <= 4; i++) {
		char tag[20];
		sprintf(tag, "vers%d", i);
		if (conf_get_bool("nfsd", tag, NFSCTL_VERISSET(versbits, i))) {
			NFSCTL_VERSET(versbits, i);
			if (i == 4)
				minorvers = minorversset = minormask;
		} else {
			NFSCTL_VERUNSET(versbits, i);
			if (i == 4) {
				minorvers = 0;
				minorversset = minormask;
			}
		}
	}

	/* We assume the kernel will default all minor versions to 'on',
	 * and allow the config file to disable some.
	 */
	for (i = NFS4_MINMINOR; i <= NFS4_MAXMINOR; i++) {
		char tag[20];
		sprintf(tag, "vers4.%d", i);
		/* The default for minor version support is to let the
		 * kernel decide.  We could ask the kernel what that choice
		 * will be, but that is needlessly complex.
		 * Instead, perform a config-file lookup using each of the
		 * two possible default.  If the result is different from the
		 * default, then impose that value, else don't make a change
		 * (i.e. don't set the bit in minorversset).
		 */
		if (!conf_get_bool("nfsd", tag, 1)) {
			NFSCTL_MINORSET(minorversset, i);
			NFSCTL_MINORUNSET(minorvers, i);
			if (i == 0)
				force4dot0 = 1;
		}
		if (conf_get_bool("nfsd", tag, 0)) {
			NFSCTL_MINORSET(minorversset, i);
			NFSCTL_MINORSET(minorvers, i);
			if (i == 0)
				force4dot0 = 1;
		}
	}

	hosts = conf_get_list("nfsd", "host");
	if (hosts && hosts->cnt) {
		struct conf_list_node *n;
		haddr = realloc(haddr, sizeof(char*) * hosts->cnt);
		TAILQ_FOREACH(n, &(hosts->fields), link) {
			haddr[hcounter] = n->field;
			hcounter++;
		}
	}
	scope = conf_get_str("nfsd", "scope");

	while ((c = getopt_long(argc, argv, "dH:S:hN:V:p:P:stTuUrG:L:", longopts, NULL)) != EOF) {
		switch(c) {
		case 'd':
			xlog_config(D_ALL, 1);
			break;
		case 'H':
			if (hosts) {
				hosts = NULL;
				hcounter = 0;
			}
			if (hcounter) {
				haddr = realloc(haddr, sizeof(char*) * hcounter+1);
				if(!haddr) {
					fprintf(stderr, "%s: unable to allocate "
							"memory.\n", progname);
					exit(1);
				}
			}
			haddr[hcounter] = optarg;
			hcounter++;
			break;
		case 'S':
			scope = optarg;
			break;
		case 'P':	/* XXX for nfs-server compatibility */
		case 'p':
			/* only the last -p option has any effect */
			port = optarg;
			break;
		case 'r':
			rdma_port = "nfsrdma";
			break;
		case 'R': /* --rdma */
			if (optarg)
				rdma_port = optarg;
			else
				rdma_port = "nfsrdma";
			break;

		case 'N':
			switch((c = strtol(optarg, &p, 0))) {
			case 4:
				if (*p == '.') {
					int i = atoi(p+1);
					if (i < 0 || i > NFS4_MAXMINOR) {
						fprintf(stderr, "%s: unsupported minor version\n", optarg);
						exit(1);
					}
					NFSCTL_MINORSET(minorversset, i);
					NFSCTL_MINORUNSET(minorvers, i);
					if (i == 0)
						force4dot0 = 1;
					if (minorvers != 0)
						break;
				} else {
					minorvers = 0;
					minorversset = minormask;
				}
				/* FALLTHRU */
			case 3:
				NFSCTL_VERUNSET(versbits, c);
				break;
			default:
				fprintf(stderr, "%s: Unsupported version\n", optarg);
				exit(1);
			}
			break;
		case 'V':
			switch((c = strtol(optarg, &p, 0))) {
			case 4:
				if (*p == '.') {
					int i = atoi(p+1);
					if (i < 0 || i > NFS4_MAXMINOR) {
						fprintf(stderr, "%s: unsupported minor version\n", optarg);
						exit(1);
					}
					NFSCTL_MINORSET(minorversset, i);
					NFSCTL_MINORSET(minorvers, i);
					if (i == 0)
						force4dot0 = 1;
				} else
					minorvers = minorversset = minormask;
				/* FALLTHRU */
			case 3:
				NFSCTL_VERSET(versbits, c);
				break;
			default:
				fprintf(stderr, "%s: Unsupported version\n", optarg);
				exit(1);
			}
			break;
		case 's':
			xlog_syslog(1);
			xlog_stderr(0);
			break;
		case 't':
			NFSCTL_TCPSET(protobits);
			break;
		case 'T':
			NFSCTL_TCPUNSET(protobits);
			break;
		case 'u':
			NFSCTL_UDPSET(protobits);
			break;
		case 'U':
			NFSCTL_UDPUNSET(protobits);
			break;
		case 'G':
			grace = strtol(optarg, &p, 0);
			if (*p || grace <= 0) {
				fprintf(stderr, "%s: Unrecognized grace time.\n", optarg);
				exit(1);
			}
			break;
		case 'L':
			lease = strtol(optarg, &p, 0);
			if (*p || lease <= 0) {
				fprintf(stderr, "%s: Unrecognized lease time.\n", optarg);
				exit(1);
			}
			break;
		default:
			fprintf(stderr, "Invalid argument: '%c'\n", c);
			/* FALLTHRU */
		case 'h':
			usage(progname);
		}
	}

	if (optind < argc) {
		if ((count = atoi(argv[optind])) < 0) {
			/* insane # of servers */
			fprintf(stderr,
				"%s: invalid server count (%d), using 1\n",
				argv[0], count);
			count = 1;
		} else if (count == 0) {
			/*
			 * don't bother setting anything else if the threads
			 * are coming down anyway.
			 */
			socket_up = 1;
			goto set_threads;
		}
	}

	xlog_open(progname);

	portnum = strtol(port, &p, 0);
	if (!*p && (portnum <= 0 || portnum > 65535)) {
		/* getaddrinfo will catch other errors, but not
		 * out-of-range numbers.
		 */
		xlog(L_ERROR, "invalid port number: %s", port);
		exit(1);
	}

	/* make sure that at least one version is enabled */
	found_one = 0;
	for (c = NFSD_MINVERS; c <= NFSD_MAXVERS; c++) {
		if (NFSCTL_VERISSET(versbits, c))
			found_one = 1;
	}
	if (!found_one) {
		xlog(L_ERROR, "no version specified");
		exit(1);
	}

	if (NFSCTL_VERISSET(versbits, 4) &&
	    !NFSCTL_TCPISSET(protobits)) {
		xlog(L_ERROR, "version 4 requires the TCP protocol");
		exit(1);
	}

	if (chdir(NFS_STATEDIR)) {
		xlog(L_ERROR, "chdir(%s) failed: %m", NFS_STATEDIR);
		exit(1);
	}

	/* make sure nfsdfs is mounted if it's available */
	nfssvc_mount_nfsdfs(progname);

	/* can only change number of threads if nfsd is already up */
	if (nfssvc_inuse()) {
		socket_up = 1;
		goto set_threads;
	}

	/*
	 * Must set versions before the fd's so that the right versions get
	 * registered with rpcbind. Note that on older kernels w/o the right
	 * interfaces, these are a no-op.
	 * Timeouts must also be set before ports are created else we get
	 * EBUSY.
	 */
	nfssvc_setvers(versbits, minorvers, minorversset, force4dot0);
	if (grace > 0)
		nfssvc_set_time("grace", grace);
	if (lease  > 0)
		nfssvc_set_time("lease", lease);

	if (scope) {
		if (unshare(CLONE_NEWUTS) < 0 ||
		    sethostname(scope, strlen(scope)) < 0) {
			xlog(L_ERROR, "Unable to set server scope: %m");
			error = -1;
			goto out;
		}
	}
	i = 0;
	do {
		error = nfssvc_set_sockets(protobits, haddr[i], port);
		if (!error)
			socket_up = 1;
	} while (++i < hcounter);

	if (rdma_port) {
		error = nfssvc_set_rdmaport(rdma_port);
		if (!error)
			socket_up = 1;
	}
set_threads:
	/* don't start any threads if unable to hand off any sockets */
	if (!socket_up) {
		xlog(L_ERROR, "unable to set any sockets for nfsd");
		goto out;
	}
	error = 0;

	/*
	 * KLUDGE ALERT:
	 * Some kernels let nfsd kernel threads inherit open files
	 * from the program that spawns them (i.e. us).  So close
	 * everything before spawning kernel threads.  --Chip
	 */
	fd = open("/dev/null", O_RDWR);
	if (fd == -1)
		xlog(L_ERROR, "Unable to open /dev/null: %m");
	else {
		/* switch xlog output to syslog since stderr is being closed */
		xlog_syslog(1);
		xlog_stderr(0);
		(void) dup2(fd, 0);
		(void) dup2(fd, 1);
		(void) dup2(fd, 2);
	}
	closeall(3);

	if ((error = nfssvc_threads(count)) < 0)
		xlog(L_ERROR, "error starting threads: errno %d (%m)", errno);
out:
	free(haddr);
	return (error != 0);
}

static void
usage(const char *prog)
{
	fprintf(stderr, "Usage:\n"
		"%s [-d|--debug] [-H hostname] [-p|-P|--port port]\n"
		"   [-N|--no-nfs-version version] [-V|--nfs-version version]\n"
		"   [-s|--syslog] [-t|--tcp] [-T|--no-tcp] [-u|--udp] [-U|--no-udp]\n"
		"   [-r|--rdma=] [-G|--grace-time secs] [-L|--leasetime secs] nrservs\n",
		prog);
	exit(2);
}
