/* $Id: main.c 5078 2007-12-09 16:56:36Z morris $ */

/*
 *  Copyright (c) 2004-2007 Axel Andersson
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>
#include <openssl/ssl.h>
#include <wired/wired.h>

#include "clients.h"
#include "main.h"
#include "servers.h"
#include "settings.h"
#include "tracker.h"
#include "version.h"

static void						wt_cleanup(void);
static void						wt_usage(void);
static void						wt_version(void);

static void						wt_write_pid(void);
static void						wt_delete_pid(void);
static void						wt_delete_status(void);

static void						wt_signals_init(void);
static void						wt_block_signals(void);
static int						wt_wait_signals(void);
static void						wt_signal_thread(wi_runtime_instance_t *);
static void						wt_signal_crash(int);


static wi_boolean_t				wt_daemonize = true;

wi_boolean_t					wt_running = true;

wi_address_family_t				wt_address_family = WI_ADDRESS_NULL;

wi_lock_t						*wt_status_lock;
wi_date_t						*wt_start_date;
wi_uinteger_t					wt_current_servers, wt_total_clients;
wi_uinteger_t					wt_current_users;
wi_uinteger_t					wt_current_files;
wi_file_offset_t				wt_current_size;


int main(int argc, const char **argv) {
	wi_array_t			*arguments;
	wi_pool_t			*pool;
	wi_string_t			*string;
	const char			**xargv;
	int					ch, facility;
	wi_boolean_t		no_chroot, test_config, daemonize;

	/* init libwired */
	wi_initialize();
	wi_load(argc, argv);
	
	pool					= wi_pool_init(wi_pool_alloc());
	wi_log_startup			= true;
	wi_log_syslog			= true;
	wi_log_syslog_facility	= LOG_DAEMON;

	/* init core systems */
	wt_version_init();
	wt_status_lock			= wi_lock_init(wi_lock_alloc());
	wt_start_date			= wi_date_init(wi_date_alloc());
	
	/* set defaults */
	wi_root_path			= wi_string_init_with_cstring(wi_string_alloc(), WT_ROOT);
	wi_settings_config_path	= wi_string_init_with_cstring(wi_string_alloc(), WT_CONFIG_PATH);
	no_chroot				= false;
	test_config				= false;
	daemonize				= true;

	/* init reexec argument list */
	arguments				= wi_array_init(wi_array_alloc());

	/* parse command line switches */
	while((ch = getopt(argc, (char * const *) argv, "46Dd:f:hi:L:ls:tuVvX")) != -1) {
		switch(ch) {
			case '4':
				wt_address_family = WI_ADDRESS_IPV4;
				break;

			case '6':
				wt_address_family = WI_ADDRESS_IPV6;
				break;

			case 'D':
				wt_daemonize = false;
				wi_log_stderr = true;
				break;

			case 'd':
				wi_release(wi_root_path);
				wi_root_path = wi_string_init_with_cstring(wi_string_alloc(), optarg);
				break;

			case 'f':
				wi_release(wi_settings_config_path);
				wi_settings_config_path = wi_string_init_with_cstring(wi_string_alloc(), optarg);
				break;

			case 'i':
				wi_log_limit = wi_string_uint32(wi_string_with_cstring(optarg));
				break;

			case 'L':
				wi_log_syslog = false;
				wi_log_file = true;

				wi_release(wi_log_path);
				wi_log_path = wi_string_init_with_cstring(wi_string_alloc(), optarg);
				break;

			case 'l':
				wi_log_level++;
				break;

			case 's':
				string = wi_string_with_cstring(optarg);
				facility = wi_log_syslog_facility_with_name(string);
				
				if(facility < 0) {
					wi_log_err(WI_STR("Could not find syslog facility \"%@\": %m"),
						string);
				}
				
				wi_log_syslog_facility = facility;
				break;

			case 't':
				test_config = true;
				break;

			case 'u':
				no_chroot = true;
				break;

			case 'V':
			case 'v':
				wt_version();
				break;
				
			case 'X':
				daemonize = false;
				break;
				
			case '?':
			case 'h':
			default:
				wt_usage();
				break;
		}
		
		wi_array_add_data(arguments, wi_string_with_format(WI_STR("-%c"), ch));
		
		if(optarg)
			wi_array_add_data(arguments, wi_string_with_cstring(optarg));
	}
	
	/* detach */
	if(daemonize) {
		wi_array_add_data(arguments, WI_STR("-X"));
		wi_array_insert_data_at_index(arguments, wi_string_with_cstring(argv[0]), 0);
		
		switch(fork()) {
			case -1:
				wi_log_err(WI_STR("Could not fork: %m"));
				break;
				
			case 0:
				xargv = wi_array_argv(arguments);
				
				if(execv(argv[0], (char * const *) xargv) < 0)
					wi_log_err(WI_STR("Could not execute %s: %m"), argv[0]);
				break;
				
				default:
				_exit(0);
				break;
		}
	}
	
	wi_release(arguments);
	
	/* open log */
	wi_log_open();

	/* init subsystems */
	wt_ssl_init();
	wt_clients_init();
	wt_servers_init();

	/* read the config file */
	wt_settings_chroot = !no_chroot;
	wt_settings_init();

	if(!wt_settings_read_config())
		exit(1);

	/* change root directory */
	if(!no_chroot) {
		if(!wi_change_root())
			wi_log_err(WI_STR("Could not change root to %@: %m"), wi_root_path);
	}

	/* apply settings */
	wt_settings_apply_settings();

	if(test_config) {
		printf("Config OK\n");

		exit(0);
	}

	/* dump command line */
	wi_log_info(WI_STR("Started as %@ %@"),
		wi_process_path(wi_process()),
		wi_array_components_joined_by_string(wi_process_arguments(wi_process()), WI_STR(" ")));

	/* init tracker */
	wi_log_info(WI_STR("Starting Wired Tracker version %@"), wt_version_string);
	wt_tracker_init();

	/* switch user/group */
	wi_switch_user(wt_settings.user, wt_settings.group);
		
	/* create tracker threads after privilege drop */
	wt_signals_init();
	wt_block_signals();
	wt_servers_schedule();
	wt_tracker_create_threads();
	wt_write_pid();
	wt_write_status(true);
	wi_log_startup = false;
	
	/* clean up pool after startup */
	wi_pool_drain(pool);
	
	/* enter the signal handling thread in the main thread */
	wt_signal_thread(NULL);

	/* dropped out */
	wt_cleanup();
	wi_log_close();
	wi_release(pool);

	return 0;
}



static void wt_cleanup(void) {
	wt_delete_pid();
	wt_delete_status();
}



static void wt_usage(void) {
	fprintf(stderr,
"Usage: trackerd [-Dllhtuv] [-d path] [-f file] [-i lines] [-L file] [-s facility]\n\
\n\
Options:\n\
    -4             listen on IPv4 addresses only\n\
    -6             listen on IPv6 addresses only\n\
    -D             do not daemonize\n\
    -d path        set the server root path\n\
    -f file        set the config file to load\n\
    -h             display this message\n\
    -i lines       set limit on number of lines for -L\n\
    -L             set alternate file for log output\n\
    -l             increase log level (can be used twice)\n\
    -s facility    set the syslog(3) facility\n\
    -t             run syntax test on config\n\
    -u             do not chroot(2) to root path\n\
    -v             display version information\n\
\n\
By Axel Andersson <%s>\n", WT_BUGREPORT);

	exit(2);
}



static void wt_version(void) {
	fprintf(stderr, "Wired Tracker %s, protocol %s, %s\n",
		wi_string_cstring(wt_version_string),
		wi_string_cstring(wt_protocol_version_string),
		SSLeay_version(SSLEAY_VERSION));

	exit(2);
}



#pragma mark -

static void wt_write_pid(void) {
	wi_string_t		*string;

	if(wt_settings.pid) {
		string = wi_string_with_format(WI_STR("%d\n"), getpid());

		if(!wi_string_write_to_file(string, wt_settings.pid))
			wi_log_warn(WI_STR("Could not write to %@: %m"), wt_settings.pid);
	}
}



static void wt_delete_pid(void) {
	if(wt_settings.pid) {
		if(!wi_file_delete(wt_settings.pid))
			wi_log_warn(WI_STR("Could not delete %@: %m"), wt_settings.pid);
	}
}



void wt_write_status(wi_boolean_t force) {
	static wi_time_interval_t	update;
	wi_string_t					*string;
	wi_time_interval_t			interval;

	interval = wi_time_interval();

	if(!force && interval - update < 1.0)
		return;

	update = interval;

	wi_process_set_name(wi_process(), wi_string_with_format(WI_STR("%u %@"),
		wt_current_servers,
		wt_current_servers == 1
			? WI_STR("server")
			: WI_STR("servers")));

	if(wt_settings.status) {
		string = wi_string_with_format(WI_STR("%.0f %u %u %u %u %llu\n"),
									   wi_date_time_interval(wt_start_date),
									   wt_current_servers,
									   wt_total_clients,
									   wt_current_users,
									   wt_current_files,
									   wt_current_size);
		
		if(!wi_string_write_to_file(string, wt_settings.status))
			wi_log_warn(WI_STR("Could not write to %@: %m"), wt_settings.status);
	}
}



static void wt_delete_status(void) {
	if(wt_settings.status) {
		if(!wi_file_delete(wt_settings.status))
			wi_log_warn(WI_STR("Could not delete %@: %m"), wt_settings.status);
	}
}



#pragma mark -

static void wt_signals_init(void) {
	signal(SIGPIPE, SIG_IGN);
	signal(SIGILL, wt_signal_crash);
	signal(SIGABRT, wt_signal_crash);
	signal(SIGFPE, wt_signal_crash);
	signal(SIGBUS, wt_signal_crash);
	signal(SIGSEGV, wt_signal_crash);
}



static void wt_block_signals(void) {
	wi_thread_block_signals(SIGHUP, SIGINT, SIGTERM, SIGQUIT, 0);
}



static int wt_wait_signals(void) {
	return wi_thread_wait_for_signals(SIGHUP, SIGINT, SIGTERM, SIGQUIT, 0);
}



static void wt_signal_thread(wi_runtime_instance_t *arg) {
	wi_pool_t		*pool;
	unsigned int	i = 0;
	int				signal;
	
	pool = wi_pool_init(wi_pool_alloc());

	while(wt_running) {
		signal = wt_wait_signals();
		
		switch(signal) {
			case SIGHUP:
				wi_log_info(WI_STR("Signal HUP received, reloading configuration"));
				wt_settings_read_config();
				wt_settings_apply_settings();
				break;

			case SIGINT:
				wi_log_info(WI_STR("Signal INT received, quitting"));
				wt_running = false;
				break;

			case SIGQUIT:
				wi_log_info(WI_STR("Signal QUIT received, quitting"));
				wt_running = false;
				break;

			case SIGTERM:
				wi_log_info(WI_STR("Signal TERM received, quitting"));
				wt_running = false;
				break;
		}
		
		if(++i % 10 == 0)
			wi_pool_drain(pool);
	}
	
	wi_release(pool);
}



static void wt_signal_crash(int sigraised) {
	wt_cleanup();
	
	sleep(360);

	if(signal(sigraised, SIG_DFL) != SIG_ERR)
		raise(sigraised);
}


syntax highlighted by Code2HTML, v. 0.9.1