/*
** CBLM v3
** $Id: cblm.c,v 1.17 2006/06/10 05:22:44 sella Exp $
** Copyright (c) 2004-2006 James M. Sella. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
** 
** Cheap Bastard Latency Monitor v3
*/

#include "cblm.h"
#include "db.h"
#include "prefs.h"
#include "util.h"
#include "packet.h"
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/resource.h>

/* Global Variables */
struct args_t args;
pthread_mutex_t probe_mutex, packet_list_mutex, db_mutex, hash_mutex;
int running = 1, restart = 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;

#ifdef HASH_TEST
	return hash_test();
#endif

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

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

	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");
	}

	use_args();

	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));

	if ((ret_val = db_connect()) != 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. */
	}

	if (read_prefs(0) != 0) {
		goto exit;
	}

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

	if (args.write) {
		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) {
		version(2);
		display_prefs(1);
		goto exit;
	}

	if (register_probe() != 0) {
		running = 0;
	}

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

		pthread_create(&data_tid, NULL, (void* (*)(void*))data_thread, (void*) NULL);
		pthread_create(&receive_tid, NULL, (void* (*)(void*))receive_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.");
		}

		if (restart) {
			/* Toggle running back on. */
			running = 1;

			/* Re-establish db connection. */
			while (running && restart) {
				restart = 0;

				if (args.debug >= DEBUG_WARNING) {
					syslog(LOG_WARNING, "Reconnecting to database...");
				}

				db_disconnect();

				if (db_connect() != 0) {
					if (args.debug >= DEBUG_ERROR) {
						syslog(LOG_ERR, "Failed to reconnect to database. Retry in %lu sec.", DB_RECON_WAIT / 1000000);
					}

					/* Wait DB_RECON_WAIT sec before trying again. */
					usleep(DB_RECON_WAIT);
					restart = 1; /* Restart db again. */
				} else {
					if (args.debug >= DEBUG_WARNING) {
						syslog(LOG_WARNING, "Reconnected to database.");
					}
					read_prefs(1);

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

	unregister_probe();
	free_probes();

exit:
	db_disconnect();

	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;

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

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

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

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

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

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
*/
