/*
** CBLM v3
** $Id: cblm.c,v 1.30 2008/02/07 06:48:33 sella Exp $
** Copyright (c) 2004-2007 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

static const char rcsid[] = "$Id: cblm.c,v 1.30 2008/02/07 06:48:33 sella Exp $";

#include "cblm.h"
#include "db.h"
#include "prefs.h"
#include "thread.h"

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

/* Global Variables */
struct args_t args;
pthread_mutex_t probe_mutex, packet_list_mutex, db_mutex, hash_mutex;
int running = 1, restart = 0;
pid_t pid = 0;
time_t startup_time = 0;

/* External Global Variables */
extern MYSQL mysql;

//#define HASH_TEST

int main(int argc, char **argv) {
	pid_t pid = 1;
	int ret_val;
	pthread_t transmit_tid = 0, receive_tid = 0, data_tid = 0;
	pthread_attr_t attr;
	struct config_node *node = NULL;

#ifdef HASH_TEST
	return hash_test();
#endif

	if ((ret_val = get_args(argc, argv)) != EXIT_SUCCESS) {
		goto exit;
	}

	/* Reopen the log based on args. */
#ifdef HAVE_NET_SNMP
	snmp_enable_syslog();
#endif
	openlog(PROGRAM_NAME, (args.debug) ? LOG_PID | LOG_PERROR : LOG_PID, DEF_SYSLOG_FACILITY);

	if (args.config) {
		read_conf_file(args.config, &node);

		if (args.debug >= DEBUG_INTERNAL) {
			print_config_nodes(node, 0);
		}

		process_config(node);
		free_config_nodes(node);
	}

	if (args.debug >= DEBUG_INTERNAL) {
		display_args();
	}

	/* Install signal handlers. */
	if (signal(SIGINT, &sighandler) == SIG_ERR) {
		fprintf(stderr, "Failed to install signal handler for SIGINT.\n");
	}
	if (signal(SIGTERM, &sighandler) == SIG_ERR) {
		fprintf(stderr, "Failed to install signal handler for SIGTERM.\n");
	}
	if (signal(SIGHUP, &sighandler) == SIG_ERR) {
		fprintf(stderr, "Failed to install signal handler for SIGTERM.\n");
	}
//	if (signal(SIGCHLD, &sighandler) == SIG_ERR) {
//		fprintf(stderr, "Failed to install signal handler for SIGCHLD.\n");
//	}
	if (signal(SIGUSR1, &sighandler) == SIG_ERR) {
		fprintf(stderr, "Failed to install signal handler for SIGUSR1.\n");
	}
	if (signal(SIGUSR2, &sighandler) == SIG_ERR) {
		fprintf(stderr, "Failed to install signal handler for SIGUSR2.\n");
	}

	if ((ret_val = use_args()) != EXIT_SUCCESS) {
		goto exit;
	}

	pthread_mutex_init(&probe_mutex, NULL);
	pthread_mutex_init(&packet_list_mutex, NULL);
	pthread_mutex_init(&db_mutex, NULL);
	pthread_mutex_init(&hash_mutex, NULL);

	srandom((unsigned int) time(NULL));

	pthread_mutex_lock(&db_mutex);
	ret_val = db_connect();
	pthread_mutex_unlock(&db_mutex);
	if (ret_val != 0) {
		if (args.debug == 0) {
			fprintf(stderr, "Failed to connect to datebase.\n");
			fprintf(stderr, "MySQL Error(%d): %s\n", mysql_errno(&mysql), mysql_error(&mysql));
		}

		goto exit;
	}

	if (args.debug == 0 && (pid = fork()) != 0) {
		return EXIT_SUCCESS;
	} else if (pid == -1) {
		syslog(LOG_ERR, "Failed to fork.");
		ret_val = 1;

		goto exit;
	} else if (pid = 0) {
		/* Only a forked child gets to this block. */
		fclose(stdin); /* Will no longer accept input (ie: Ctrl-C) */
		fclose(stdout); /* Our output to stdout will go unheard. */
		fclose(stderr); /* Our output to stderr will go unheard. */
	}

	/* Update or display preferences. */
	if (args.write) {
		if (read_prefs(0) != 0) {
			goto exit;
		}

		version(2);

		if (write_prefs() == 0) {
			display_prefs(1);
			printf("Preferences were written to the database.\n");
		} else {
			printf("\nFailed to write preferences to the database.\n");
		}
		goto exit;
	} else if (args.print) {
		if (read_prefs(0) != 0) {
			goto exit;
		}

		version(2);
		display_prefs(1);
		goto exit;
	}

	/* Use preferences */
	if (read_prefs(1) != 0) {
		goto exit;
	} else if (args.debug >= DEBUG_INTERNAL) {
		display_prefs(0);
	}

	syslog(LOG_INFO, "%s v%s", PROGRAM_NAME, PROGRAM_VERS);

	/* Capture PID and startup time */
	pid = getpid();
	startup_time = time(NULL);

	/* Send startup trap */
	snmptrap_process_startup();

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_INFO, "Staring up threads...");
	}

	pthread_create(&receive_tid, NULL, (void* (*)(void*))receive_thread, (void*) NULL);
	pthread_create(&data_tid, NULL, (void* (*)(void*))data_thread, (void*) NULL);
	pthread_create(&transmit_tid, NULL, (void* (*)(void*))transmit_thread, (void*) NULL);

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_INFO, "Threads are created.");
	}

	pthread_join(transmit_tid, NULL);
	pthread_join(receive_tid, NULL);
	pthread_join(data_tid, NULL);

	if (args.debug >= DEBUG_INTERNAL) {
		syslog(LOG_INFO, "Threads have completed.");
	}

	/* Send shutdown trap */
	snmptrap_process_shutdown();

exit:
	pthread_mutex_lock(&db_mutex);
	db_disconnect();
	pthread_mutex_unlock(&db_mutex);

	free_probes();
	free_prefs();
	free_args();

	pthread_mutex_destroy(&db_mutex);
	pthread_mutex_destroy(&packet_list_mutex);
	pthread_mutex_destroy(&probe_mutex);

	closelog();

	return (ret_val == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}

void sighandler(int sig) {
	pid_t pid;

	//FIXME: Do not call syslog within a signal handler. Causes hangs and possibly exploitable.

	switch (sig) {
		case SIGINT:
		case SIGTERM:
			running = 0;
#if 0
			if (args.debug >= DEBUG_INTERNAL) {
				syslog(LOG_INFO, "Caught SIGTERM/SIGINT signal. Exiting...");
			}
#endif
			break;

		case SIGHUP:
			restart = 1;
#if 0
			syslog(LOG_INFO, "Rereading preferences and restarting.");
#endif
			break;

		case SIGUSR1:
			if (args.debug >= DEBUG_MAXVAL) {
				args.debug = DEBUG_MAXVAL;
			} else {
				args.debug++;
			}
#if 0
			syslog(LOG_INFO, "Debug level now set to '%d'.", args.debug);
#endif
			break;

		case SIGUSR2:
			if (args.debug <= 0) {
				args.debug = 0;
			} else {
				args.debug--;
			}
#if 0
			syslog(LOG_INFO, "Debug level now set to '%d'.", args.debug);
#endif
			break;

#if 0
		default:
			if (args.debug >= DEBUG_INTERNAL) {
				syslog(LOG_INFO, "Caught unused signal '%d'.", sig);
			}
#endif
	}
}

int hash_test() {
	struct in_addr ip;
	struct data_t data;
	int s, save = 0;

	hash_init();

	for (s = 0; s < 500; s++) {
		ip.s_addr = s;
		data.src = ip;

		hash_add(&ip, 0, &data);
		if (hash_find(&ip, 0) == (int) NULL) {
			abort();
		}

		if (s % 3 == 0) {
			ip.s_addr = save;
			data.src = ip;
			save = s;
			
			if (hash_del(&ip, 0) == 0) {
				printf("Was able to Deleted - s: %lu\n", save);
			} else {
				printf("Failed to Deleted - s: %lu\n", save);
			}
		}
	}

	hash_stats();

	printf("Starting delete\n");
	hash_free();

	return EXIT_SUCCESS;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim: noet ts=3 sw=3
*/
