/*
 * server.c
 * main() program for the server.
 *
 * Copyright (C) Leni Mayo 2001-2002
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License, version 2, as
 *	published by the Free Software Foundation.
 *	
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <string.h>
#include <unistd.h>
#include <stdlib.h>    /* for exit  */
#include <sys/stat.h>  /* for umask */
#include <getopt.h>
#include "config.h"  /* generated by autoconf configure */
#include "common.h"
#ifndef SEREL_SERVER_NO_RDF
#include "rdfserver.h"
#endif
#include "serviceid.h"
#include "servercallbacks.h"
#include "serverconfig.h"
#include "serverhandle.h"
#include "log.h"

ServerHandle serverhandle;

static const char *argv0 = NULL;

static int turnIntoDaemon();
static void parseCommandLine(int argc, char *argv[], int *pDebugLevel, int *pIsDaemon, const char **ppszLogName, const char **ppszConfigFile, LogOpenMode *pLogMode);
static void printUsage(FILE *fp, const char *argv0);

int main (int argc, char *argv[])
{
	int ret = TRUE;
	int debuglevel = -1;
	const char *pszLogName    = NULL;
	LogOpenMode logMode = eLogOpenModeAppend;
	const char *pszConfigFile = NULL;
	int fIsDaemon  = FALSE;

	argv0 = getArgv0(argv, argvCodeAsString(eArgvCodeSereld));

	parseEnvironment(&debuglevel, &pszLogName);

	parseCommandLine(argc, argv, &debuglevel, &fIsDaemon, &pszLogName, &pszConfigFile, &logMode);

	logInitialise(argv0, fIsDaemon ? eLogContextDaemon : eLogContextTerminal,
				         debuglevel, pszLogName, logMode);

	/*
	 * Read the config file before turning into a daemon:
	 * - may write to stderr,
	 * - turning into a daemon changes the current working directory to "/"
	 *   Ref: Stevenson's "Advanced Programming in UNIX ..."  for why you should
	 *   probably avoid changing cwd to something other than "/" once a daemon.
	 */
	configInitialise(pszConfigFile);

#ifndef SEREL_SERVER_NO_RDF
	rdfServerSetDataDirectory(configAsString(eConfigKeyRdfDbDirectory));
	rdfServerSetUriSeq(configAsString(eConfigKeyServiceUriSeq));
#endif

	/*
	 * Open the pipe before turning into a daemon,
	 * otherwise we'd be in a race with clients.
	 */
	ret = ret && serverHandleInitialise(&serverhandle);

	if (fIsDaemon)
	{
		ret = ret && turnIntoDaemon();

		printLog(eLogLevelDefault, "%s ... started.\n", argv0);
	}

	if (ret)
	{
		ret = serverCallbackInitialise();

		if (!ret)
		{
			printLog(eLogLevelDefault, "%s initialisation failure ... exiting.\n", argv0);
		}
	}

	commandInitialiseTables();

	if (ret)
	{
		serverHandleEventLoop(&serverhandle);
	}

	serverHandleCleanup(&serverhandle);

	serverCallbackCleanup();

	configCleanup();

	if (fIsDaemon)
	{
		printLog(eLogLevelDefault, "%s ... stopped.\n", argv0);
	}

	logCleanup();

	return ret;
}

int turnIntoDaemon()
{
	pid_t pid;
	int ret = 0;

	if ((pid = fork()) < 0)
	{
		ret = -1;  /* fork failed */
	}
	else if (pid != 0)
	{
		exit(0);  /* parent dies happy */
	}

	/* Now in child
	*/
	if (getenv("DDD") != NULL)
	{
		/* if we're debugging, we need a few seconds to attach to the child
		*/
		sleep(10);
	}

	if (ret == 0)
	{
		ret = setsid();

		assert(ret > 0); /* setsid should never fail inside the child */
		ret = 0;
	}

	if (ret == 0)
	{
		ret = chdir("/");
	}

	umask(0);

	return ret == 0;
}

void parseCommandLine(int argc, char *argv[], int *pDebugLevel, int *pIsDaemon,
		const char **ppszLogName, const char **ppszConfigFile, LogOpenMode *pLogMode)
{
	int c;
	int option_index = 0;
	static struct option long_options[] =
	{
		{"help",         no_argument,       NULL, 'h'},
		{"version",      no_argument,       NULL, 'v'},
		{"config-file",  required_argument, NULL, 'c'},
		{"debuglevel",   required_argument, NULL, 'd'},
		{"log",          required_argument, NULL, 'l'},
		{"log-append",   no_argument,       NULL, 'a'},
		{"log-truncate", no_argument,       NULL, 't'},
		{"daemon",       no_argument,       NULL, 'b'},
		{0, 0, 0, 0}
	};

	assert(pDebugLevel && pIsDaemon && ppszLogName && ppszConfigFile);

	while ((c = getopt_long (argc, argv, "abc:d:hl:tv", long_options, &option_index)) != -1)
	{
		switch(c)
		{
		case 'a':
			*pLogMode = eLogOpenModeAppend;
			break;
		case 'b':
			*pIsDaemon = TRUE;
			break;
		case 'c':
			*ppszConfigFile = optarg;
			break;
		case 'd':
			*pDebugLevel = atoi(optarg);
			break;
		case 'h':
			printUsage(stdout, argvCodeAsString(eArgvCodeSereld));
			exit(0);
		case 'l':
			*ppszLogName = optarg;
			break;
		case 't':
			*pLogMode = eLogOpenModeTruncate;
			break;
		case 'v':
			printVersion(stdout, PACKAGE, VERSION);
			exit(0);
		default:
			printUsage(stderr, argvCodeAsString(eArgvCodeSereld));
			exit(1);
		}
	}
}

void printUsage(FILE *fp, const char *argv0)
{
	assert(fp);

	fprintf(fp, "Usage: %s [general_options] [options]\n", argv0);
	fprintf(fp, "\n");
	fprintf(fp, "  [general_options]\n");
	printCommonCmdlineOptions(fp);
	fprintf(fp, "\n");
	fprintf(fp, "  [options]\n");
	fprintf(fp, "   -b                     Run as daemon\n");
	fprintf(fp, "   -c config-file         Use config-file, instead of compile-time default\n");
}
