/*
 * Copyright 1993, 1994 by Ulrich Khn. All rights reserved.
 *
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY, NOT
 * EVEN THE IMPLIED WARRANTIES OF MERCHANTIBILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE. USE AT YOUR OWN
 * RISK.
 */

/*
   File : nfsd.c
          main driver for the nfs server daemon

   Autor: Ulrich Khn
*/

#include <stdio.h>
#include <osbind.h>
#include <mintbind.h>
#include <signal.h>
#include <string.h>
#include <memory.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "thread.h"
#include "../xfs/nfs.h"
#include "rpc.h"
#include "svc.h"
#include "nfssvc.h"
#include "auth.h"
#include "version.h"
#include "fh.h"

#ifdef SYSLOG
#include <arpa/inet.h>
#include "syslog.h"
#endif


#define NFS_PORT  2049


char msg[] = "\r\nNFSD " VERSION ", (C) Ulrich Khn\r\n"
"\033pUSE IT AT OWN RISK!\033q\r\n\r\n";

#define NFSD_NAME  "nfsd"


/* If the daemon was started by inetd, we see that stdin is a socket, and
 * then we want to remove ourselves after being idle for some time from
 * memory. This is this timeout value in seconds.
 */
#define DYNAMIC_TIMEOUT  240

char whatmsg[] = "@(#)nfsd " VERSION ", (C) Ulrich Khn  " __DATE__;


extern int __mint;
int daemon_fd;
int dynamic_rpc = 0;
int debug = 0;


void
handle_sigterm(int sig)
{
#ifdef SYSLOG
	syslog(LOG_NOTICE, "exiting on signal %d", sig);
	closelog();
#endif
	exit(0);
}


void
init_signals()
{
	signal(SIGHUP, handle_sighup);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGTERM, handle_sigterm);
}


long ticks = 0;

void
do_timeout(long val)
{
	fh_update(NULL);
	if (dynamic_rpc)
	{
		ticks += 10;
		if (ticks > 240)
			exit (0);
	}
}

int
do_daemon(long dummy)
{
	long res, maxmsgsiz = 10240;
	SVCXPRT *xprt;
	struct sockaddr_in sin;
	long addrsize = sizeof(struct sockaddr_in);

	init_signals();
	daemon_fd = 0;

#ifdef SYSLOG
	/* maybe we should use here LOG_DAEMON, but this is for system
	 * security reasons
	 */
	openlog("nfsd", LOG_PID | LOG_NDELAY, LOG_AUTH);
#endif

	/* First check if stdin is a socket. That is the case if we were
	 * started by the inetd due to an RPC request. Then we must not
	 * open a new socket, as it could not be bound to the correct port
	 * anyway. Instead, enable dynamic timeout checking for shutting
	 * down after a certain idle time.
	 */
	res = getsockname(0, (struct sockaddr*)&sin, &addrsize);
	if (res == 0)
	{
		/* Here we know that stdin is a socket, so set up all the stuff
		 * accordingly.
		 */
		long ssize = sizeof(long);
		long sock_type = 0;

		if (sin.sin_family != AF_INET)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "started on non-INET socket");
#endif
			exit(1);  /* only INET stuff supported */
		}
		dynamic_rpc = 1;

		/* for now, only SOCK_DGRAM supported */
		res = getsockopt(0, SOL_SOCKET, SO_TYPE, &sock_type, &ssize);
		if ((res < 0) || (sock_type != SOCK_DGRAM))
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "only UDP service supported");
#endif
			exit(1);
		}

		daemon_fd = 0;
	}
	else
	{
		/* Open a new socket and bind it to the desired port. */
		dynamic_rpc = 0;
		res = socket(PF_INET, SOCK_DGRAM, 0);
		if (res < 0)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "could not open socket");
#else
			Cconws("nfsd: could not open socket\r\n");
#endif
			return res;
		}
		daemon_fd = res;
	}

	res = setsockopt(daemon_fd, SOL_SOCKET, SO_RCVBUF,
	                                  &maxmsgsiz, sizeof(long));
	if (res < 0)
	{
#ifdef SYSLOG
		syslog(LOG_ERR, "could not set socket options");
#else
		if (!dynamic_rpc)
			Cconws("nfsd: could not set socket options\r\n");
#endif
		return res;
	}
	res = setsockopt(daemon_fd, SOL_SOCKET, SO_SNDBUF,
	                                  &maxmsgsiz, sizeof(long));
	if (res < 0)
	{
#ifdef SYSLOG
		syslog(LOG_ERR, "could not set socket options");
#else
		if (!dynamic_rpc)
			Cconws("nfsd: could not set socket options\r\n");
#endif
		return res;
	}

	if (!dynamic_rpc)
	{
		sin.sin_family = AF_INET;
		sin.sin_addr.s_addr = htonl(INADDR_ANY);
		sin.sin_port = htons(NFS_PORT);
		res = bind(daemon_fd, (struct sockaddr*)&sin, sizeof(sin));
		if (res < 0)
		{
#ifdef SYSLOG
			syslog(LOG_ERR, "could not bind socket");
#else
			Cconws("nfsd: could not bind socket\r\n");
#endif
			return 1;
		}
	}
	xprt = svc_create(daemon_fd, maxmsgsiz, maxmsgsiz);
	if (!xprt)
	{
#ifdef SYSLOG
		syslog(LOG_ERR, "could not create service");
#else
		Cconws("nfsd: could not create service\r\n");
#endif
		return -1;
	}

	if (dynamic_rpc)
		res = svc_register(xprt, NFS_PROGRAM, NFS_VERSION, nfsprog_2, 0);
	else
		res = svc_register(xprt, NFS_PROGRAM,
		                     NFS_VERSION, nfsprog_2, IPPROTO_UDP);
	if (res < 0)
	{
#ifdef SYSLOG
		syslog(LOG_ERR, "could not register service");
#else
		if (!dynamic_rpc)
			Cconws("nfsd: could not register service\r\n");
#endif
		return -1;
	}
	svc_timeout(xprt, 10000, do_timeout);

	xprt_register(xprt);
	fh_init();

	svc_run();
	return 0;
}



int
main(int argc, char *argv[])
{
	long r;
	long addr_size = sizeof(struct sockaddr_in);
	struct sockaddr_in sin;

	/* switch to MiNT's root drive */
	Dsetdrv('u'-'a');
	Dsetpath("\\");

	/* Test if stdin is a socket */
	if (getsockname(0, (struct sockaddr*)&sin, &addr_size) < 0)
		dynamic_rpc = 0;
	else
		dynamic_rpc = 1;

	/* set up command line options */
	if ((argc == 2) && !strncmp(argv[1], "-d", 2))
	{
		if (argv[1][2])
			debug = argv[1][2] - '0';
		else
			debug = 1;

		Cconws("Debugging enabled at level");
		Cconout(debug+'0');
		Cconws("\r\n");
	}
	else
		debug = 0;

	/* read the export file */
	r = auth_init();
	if (r < 0)
	{
		if (!dynamic_rpc)
		{
			if (argv[0])
				Cconws(argv[0]);
			else
				Cconws("NFSD");
			Cconws(": error while reading exports file.\r\n");
		}
		return 1;
	}

	/* now fork off the daemon process into the background */
	if (!dynamic_rpc)
	{
		if (thread(NFSD_NAME, do_daemon, 0) >= 0)
			Cconws(msg);
		else
		{
			if (argv[0])
				Cconws(argv[0]);
			else
				Cconws("NFSD");
			Cconws(": cannot fork.\r\n");
			return 1;
		}
	}
	else
		do_daemon(0);
	return 0;
}
