/*
 * isnsd - the iSNS Daemon
 *
 * Copyright (C) 2007 Olaf Kirch <olaf.kirch@oracle.com>
 */

#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>

#ifdef MTRACE
# include <mcheck.h>
#endif

#include <libisns/isns.h>
#include "security.h"
#include <libisns/util.h>
#include <libisns/paths.h>
#include "internal.h"

enum {
	MODE_NORMAL,
	MODE_DUMP_DB,
	MODE_INIT,
};

static const char *	opt_configfile = ISNS_DEFAULT_ISNSD_CONFIG;
static int		opt_af = AF_INET6; // should be AF_INET6 as described in isnsd's manual

static int		opt_mode = MODE_NORMAL;
static int		opt_foreground = 0;

static char *		slp_url;

static int		init_server(void);
static void		run_server(isns_server_t *, isns_db_t *);
static void		usage_and_exit(int, const char *);
static void		cleanup(int);

static struct option	options[] = {
      {	"config",		required_argument,	NULL,	'c'		},
      {	"debug",		required_argument,	NULL,	'd'		},
      { "foreground",		no_argument,		NULL,	'f'		},
      { "init",			no_argument,		NULL,	MODE_INIT	},
      { "dump-db",		no_argument,		NULL,	MODE_DUMP_DB	},
      { "help",			no_argument,		NULL,	'h'		},
      { "version",		no_argument,		NULL,	'V'		},
      { NULL }
};

int
main(int argc, char **argv)
{
	isns_server_t	*server;
	isns_source_t	*source;
	isns_db_t	*db;
	int		c;

#ifdef MTRACE
	mtrace();
#endif

	while ((c = getopt_long(argc, argv, "46c:d:fh", options, NULL)) != -1) {
		switch (c) {
		case '4':
			opt_af = AF_INET;
			break;

		case '6':
			opt_af = AF_INET6;
			break;

		case 'c':
			opt_configfile = optarg;
			break;

		case 'd':
			isns_enable_debugging(optarg);
			break;

		case 'f':
			opt_foreground = 1;
			break;

		case MODE_DUMP_DB:
		case MODE_INIT:
			opt_mode = c;
			break;

		case 'h':
			usage_and_exit(0, NULL);
			__builtin_unreachable();

		case 'V':
			printf("Open-iSNS version %s\n"
			       "Copyright (C) 2007, Olaf Kirch <olaf.kirch@oracle.com>\n",
			       OPENISNS_VERSION_STRING);
			return 0;

		default:
			usage_and_exit(1, "Unknown option");
		}
	}

	if (optind != argc)
		usage_and_exit(1, NULL);

	isns_read_config(opt_configfile);

	if (!isns_config.ic_source_name) {
		/*
		 * Try to read the source name from open-iscsi configuration,
		 * using the default iniatiator name
		 */
		isns_read_initiatorname(ISCSI_DEFAULT_INITIATORNAME);
	}

	isns_init_names();

	if (!isns_config.ic_source_name)
		usage_and_exit(1, "Please specify an iSNS source name");

	source = isns_source_create_iscsi(isns_config.ic_source_name);

	if (opt_mode == MODE_INIT)
		return !init_server();

	if (opt_mode == MODE_NORMAL)
		isns_write_pidfile(isns_config.ic_pidfile);

	db = isns_db_open(isns_config.ic_database);
	if (db == NULL)
		isns_fatal("Unable to open database\n");

	if (opt_mode == MODE_DUMP_DB) {
		isns_db_print(db, isns_print_stdout);
		exit(0);
	}

	if (!opt_foreground) {
		if (daemon(0, 0) < 0)
			isns_fatal("Unable to background server process\n");
		isns_log_background();
		isns_update_pidfile(isns_config.ic_pidfile);
	}

	signal(SIGTERM, cleanup);
	signal(SIGINT, cleanup);

	server = isns_create_server(source, db, &isns_default_service_ops);

	run_server(server, db);
	return 0;
}

void
usage_and_exit(int exval, const char *msg)
{
	if (msg)
		fprintf(stderr, "Error: %s\n", msg);
	fprintf(stderr,
	"Usage: isnsd [options]\n\n"
	"  --config        Specify alternative config fille\n"
	"  --foreground    Do not put daemon in the background\n"
	"  --debug         Enable debugging (list of debug flags)\n"
	"  --init          Initialize the server (key generation etc)\n"
	"  --dump-db       Display the database contents and exit\n"
	"  --help          Print this message\n"
	);
	exit(exval);
}

void
cleanup(int sig)
{
	isns_remove_pidfile(isns_config.ic_pidfile);
	exit(sig == SIGTERM ? 0 : 1);
}

static void
slp_cleanup(void)
{
	char	*url = slp_url;

	slp_url = NULL;
	if (url) {
		isns_slp_unregister(url);
		isns_free(url);
	}
}

/*
 * Initialize server
 */
int
init_server(void)
{
	if (!isns_security_init())
		return 0;

	/* Anything else? */

	return 1;
}

/*
 * Server main loop
 */
void
run_server(isns_server_t *server, isns_db_t *db)
{
	isns_socket_t	*sock;
	isns_security_t	*ctx = NULL;
	isns_message_t	*msg, *resp;
	int status;

	if (isns_config.ic_security) {
		const char	*ksname;
		isns_keystore_t	*ks;

		ctx = isns_default_security_context(1);
		if (!(ksname = isns_config.ic_client_keystore))
			isns_fatal("config problem: no key store specified\n");
		if (!strcasecmp(ksname, "db:"))
			ks = isns_create_db_keystore(db);
		else
			ks = isns_create_keystore(ksname);
		if (ks == NULL)
			isns_fatal("Unable to create keystore %s\n", ksname);
		isns_security_set_keystore(ctx, ks);
	}

	status = isns_dd_load_all(db);
	if (status != ISNS_SUCCESS)
		isns_fatal("Problem loading Discovery Domains from database\n");


	/* First socket is the control socket */
	sock = isns_create_systemd_socket(0);
	if (sock) {
		/* Second socket is the iSNS port */
		sock = isns_create_systemd_socket(1);
		isns_debug_socket("Using systemd fds\n");
		goto set_security;
	}

	if (isns_config.ic_control_socket) {
		sock = isns_create_server_socket(isns_config.ic_control_socket,
				NULL, AF_UNSPEC, SOCK_STREAM);
		if (sock == NULL)
			isns_fatal("Unable to create control socket\n");
		/*
		 * isns_socket_set_security_ctx(sock, ctx);
		 */
	}

	sock = isns_create_server_socket(isns_config.ic_bind_address,
			"isns", opt_af, SOCK_STREAM);
	if (sock == NULL)
		isns_fatal("Unable to create server socket\n");
set_security:
	isns_socket_set_security_ctx(sock, ctx);

	if (isns_config.ic_slp_register) {
		slp_url = isns_slp_build_url(0);
		isns_slp_register(slp_url);

		atexit(slp_cleanup);
	}

	isns_esi_init(server);
	isns_scn_init(server);

	while (1) {
		struct timeval timeout = { 0, 0 };
		time_t	now, then, next_timeout = time(NULL) + 3600;

		/* Expire entities that haven't seen any activity
		 * for a while. */
		if (isns_config.ic_registration_period) {
			then = isns_db_expire(db);
			if (then && then < next_timeout)
				next_timeout = then;
		}

		/* Run any timers (eg for ESI) */
		then = isns_run_timers();
		if (then && then < next_timeout)
			next_timeout = then;

		/* There may be pending SCNs, push them out now */
		then = isns_scn_transmit_all();
		if (then && then < next_timeout)
			next_timeout = then;

		/* Purge any objects that have been marked for removal
		 * from the DB (deleting them, or moving them to limbo
		 * state). */
		isns_db_purge(db);

		/* Determine how long we can sleep before working
		 * the ESI queues and DB expiry again. */
		now = time(NULL);
		if (next_timeout <= now)
			continue;
		timeout.tv_sec = next_timeout - now;

		if ((msg = isns_recv_message(&timeout)) == NULL)
			continue;

		if ((resp = isns_process_message(server, msg)) != NULL) {
			isns_socket_t *sock = isns_message_socket(msg);

			isns_socket_send(sock, resp);
			isns_message_release(resp);
		}

		isns_message_release(msg);
	}
}
