#include "unp.h"
#include "unpipc.h"

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>


#define PGCONN_STRING   "host=%s port=%s dbname=%s user=%s password=%s"
#define	DEBUG_MODE	1
#define RELEASE_MODE 0

//const char a_ServType[NumOfServerType][MaxSizeOfServerType] = {"WWW", "RTS", "RC", "CCDB", "RCTS", "GTS", "RCDB", "GMS", "TIMED", "STAT"};
const char a_ServType[NumOfServerType][MaxSizeOfServerType] = {"RC", "STAT", "RTS", "RCTS", "RCDB", "WWW", "CCDB", "GTS", "GMS", "TIMED", "CACHE"};
static int SetSigHandle(int Option);



#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
       /* union semun is defined by including <sys/sem.h> */
#else
       /* according to X/OPEN we have to define it ourselves */
       union semun {
               int val;                    /* SETVAL을 위한값 */
               struct semid_ds *buf;       /* IPC_STAT, IPC_SET을 위한 버퍼 */
               unsigned short int *array;  /* GETALL, SETALL을 위한 배열 */
               struct seminfo *__buf;      /* IPC_INFO을 위한 버퍼 */
       };
#endif


int main(int argc, char **argv)
{
	void    sig_int(int);
	void    sig_int_report(int);
	void	sig_chld(int);
	void    vrcd_thread_make(int);
	void    report_thread_make();

	pid_t check_process();
    pid_t send_update_process();
    pid_t vrcd_process();

	/* declare Server List Structure */
	//servinfo_t *g_servinfo;
    servinfo_t *start_servinfo;
	g_shm_t *g_shm_ptr;
	union semun sem_union;

	MYSQL	mysql;
	MYSQL_RES *res;
	MYSQL_ROW row;

	PGconn *conn;
    PGresult *result;

	void *shared_memory = (void *)0;
	int		fd;
	int		mysql_port, fields;
	int		shmid;
	long	numRows;
	pid_t	pids[3];

	char	mysql_db[8];
	char	mysql_user[16];
	char	mysql_pass[16];

	char	pg_addr[30];
	char	pg_port[10];
	char	pg_name[30];
	char	pg_acct[30];
	char	pg_pass[30];
    char	pgsql_query[1024];

	/* damonlizing... */
	//makedaemon(DEBUG_MODE);
	makedaemon(RELEASE_MODE);
	
	//setproctitle("Parents(guard): initialize");

	/* initialize mysqlDB */
	mysql_init(&mysql);

	/* Load conf file */
	SetIniName(CFGFILE);

	GetProfile("PGSQL", "DATABASE_ADDR", pg_addr, "");
	GetProfile("PGSQL", "DATABASE_PORT", pg_port, "");
	GetProfile("PGSQL", "DATABASE_NAME", pg_name, "");
	GetProfile("PGSQL", "DATABASE_ACCT", pg_acct, "");
	GetProfile("PGSQL", "DATABASE_PW", pg_pass, "");

	GetProfile("MYSQL", "DATABASE_NAME", mysql_db, "CCDB");
	GetProfile("MYSQL", "DATABASE_ACCT", mysql_user, "root");
	GetProfile("MYSQL", "DATABASE_PW", mysql_pass, "alfkzmf!?{}");
	mysql_port = GetProfileInt("MYSQL", "DATABASE_PORT", 3306);

	WriteSHLog_safe("[PG]DATABASE_ADDR -->%s\n", pg_addr);
	WriteSHLog_safe("[PG]DATABASE_PORT -->%s\n", pg_port);
	WriteSHLog_safe("[PG]DATABASE_NAME -->%s\n", pg_name);
	WriteSHLog_safe("[PG]DATABASE_ACCT -->%s\n", pg_acct);
	WriteSHLog_safe("[PG]DATABASE_PW -->%s\n\n", pg_pass);
	WriteSHLog_safe("[MY]DATABASE_NAME -->%s\n", mysql_db);
	WriteSHLog_safe("[MY]DATABASE_ACCT -->%s\n", mysql_user);
	WriteSHLog_safe("[MY]DATABASE_PW -->%s\n", mysql_pass);
	WriteSHLog_safe("[MY}DATABASE_PORT -->%d\n", mysql_port);
	/* connect mysqlDB */
	if (!mysql_real_connect(&mysql, NULL, mysql_user, mysql_pass, mysql_db, mysql_port, (char *)NULL, 0))
	{
		fprintf(stderr, "%s\n", mysql_error(&mysql));
		WriteSHLog_safe("%s\n", mysql_error(&mysql));
		exit(EXIT_FAILURE);
	}
	WriteSHLog_safe("Query to get arranged Server List :: %s\n", MYQUERY);

	/* start transaction */
	mysql_autocommit(&mysql, 0);
	if (mysql_query(&mysql, MYQUERY))
	{
		fprintf(stderr, "[error]%s\n", mysql_error(&mysql));
		WriteSHLog_safe("[error]%s\n", mysql_error(&mysql));
		mysql_rollback(&mysql);
	}
	else
	{
		fprintf(stderr, "[MySQL]Server List Loading Success!!\n");
		WriteSHLog_safe("%s\n", "[MySQL]Server List Loading Success!!");
		mysql_commit(&mysql);
	}
	res = mysql_store_result(&mysql);
    fields = mysql_num_fields(res);
	numRows = mysql_num_rows(res);
#ifdef __SETLOG__
	WriteSHLog_safe("numRows --> %ld, Sizeof structure servinfo_t : %d\n", numRows, sizeof(servinfo_t));
#endif

	/* connect postgreSQL */
	snprintf(pgsql_query, 1024, PGCONN_STRING, pg_addr, pg_port, pg_name, pg_acct, pg_pass);
	conn = PQconnectdb(pgsql_query);
	//conn = PQconnectdb("host=211.38.137.34 port=5432 dbname=opendisk user=opendisk password=os1234!");

	if (PQstatus(conn) == CONNECTION_BAD)
	{
		fprintf(stderr, "pgSQL Connection failed, %s\n", PQerrorMessage(conn));
		WriteSHLog_safe("pgSQL Connection failed, %s\n", PQerrorMessage(conn));
		if (conn != NULL)
			PQfinish(conn);
		exit(EXIT_FAILURE);
	}
	else
	{
		fprintf(stderr, "pgSQL Connected OK!\n");
		WriteSHLog("pgSQL Connected OK!\n");
	}
	result = PQexec(conn, "select now() ");
	if (!result)
	{
		fprintf(stderr, "PQexec command failed, no error code\n");
		WriteSHLog("PQexec command failed, no error code\n");
	}
	else
	{
		switch (PQresultStatus(result))
		{
			case PGRES_COMMAND_OK:
				WriteSHLog_safe("Command executed OK, %s rows affected\n", PQcmdTuples(result));
				break;
			case PGRES_TUPLES_OK:
				WriteSHLog_safe("Query may have returned data. %d rows found\n", PQntuples(result));
				break;
			default:
				WriteSHLog_safe("Command failed with code %s, error message %s\n", PQresStatus(PQresultStatus(result)), PQresultErrorMessage(result));
				break;
		}
		PQclear(result);
	}

	//close postgreSQL
	PQfinish(conn);

	/**************************************************************/
	/******************** create system V shm *********************/
	/**************************************************************/
    shmid = shmget((key_t)6677, sizeof(g_shm_t), 0666|IPC_CREAT);
    if (shmid == -1)
    {
        WriteSHLog("system V shmget failed\n");
        exit(EXIT_FAILURE);
    }
    shared_memory = shmat(shmid, (void *)0, 0);
    if (shared_memory == (void *)-1)
    {
        WriteSHLog("system V shmat failed\n");
        exit(EXIT_FAILURE);
    }
    g_shm_ptr = (g_shm_t *)shared_memory;
	g_shm_ptr->is_dirty = 0;
	g_shm_ptr->serv_num = numRows + 1;

	if (shmdt(shared_memory) == -1)
    {
        WriteSHLog("system V shmdt failed\n");
        exit(EXIT_FAILURE);
    }
	WriteSHLog("create system V shm ok!!\n");
	
	/***************************************************************/
	/********************* create shared memory ********************/
	/***************************************************************/
	if (numRows == 0)
		numRows = 1;
	
	shm_unlink(SHM_NAME);
	
	fd = Shm_open(SHM_NAME, O_RDWR | O_CREAT | O_EXCL | O_TRUNC, FILE_MODE);
	
	Ftruncate(fd, sizeof(servinfo_t)*(numRows+1));
	g_servinfo = Mmap(NULL, sizeof(servinfo_t)*(numRows+1), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	memset(g_servinfo, 0, sizeof(servinfo_t)*(numRows*1));
	
	//Close(fd);
	/***************************************************************/
	int cnt = 1;
	start_servinfo = g_servinfo;
	while((row = mysql_fetch_row(res)))
	{
		addServerToList(g_servinfo, row);
		//g_servinfo->next = Mmap(NULL, (sizeof(servinfo_t)), PROT_READ | PROT_WRITE, MAP_SHARED, fd, (sizeof(servinfo_t))*cnt);
		//memset(g_servinfo->next, 0, sizeof(servinfo_t));
		g_servinfo++;
		cnt++;
	}

	strncpy(g_servinfo->m_pszServName, END_OF_SHM, 11);
	g_servinfo = start_servinfo;
	Close(fd);
	
	WriteSHLog("create posix shared memory ok!!\n");
	/****************************************************************/

	/********************** create system V Semaphore  ********************/

	//sem_id = Semget(Ftok(SEM_NAME, 's'), 1, IPC_CREAT | SVSEM_MODE); // FreeBSD
	sem_id = Semget(SEM_NAME, 1, IPC_CREAT | SVSEM_MODE);              // Linux

	sem_union.val = 1;
	if ( -1 == Semctl(sem_id, 0, SETVAL, sem_union))
	{
		WriteSHLog("system V initialization fail!!\n");
		return -1;
	}
	waitop.sem_num = 0;
	waitop.sem_op = -1;
	waitop.sem_flg = SEM_UNDO;
	postop.sem_num = 0;
	postop.sem_op = 1;
	postop.sem_flg = SEM_UNDO;
	/******************************************************************/

	WriteSHLog("create system V semaphore ok!!\n");
	/* close mysqlDB */
	mysql_free_result(res);
	mysql_close(&mysql);

	munmap((void *)g_servinfo, get_sizeof_shm());

	// server status check process
	pids[0] = check_process();
	// send/update sms process
    pids[1] = send_update_process();
	// vrcd value gathering process
    pids[2] = vrcd_process();

	//setproctitle("parents(status): Monitoring Three Major Process..");
	for ( ; ; )
		pause();
	return 0;
}

pid_t check_process()
{
	pid_t pid;
    void check_child_process();

	if ((pid = fork()) > 0)
		return (pid);

	check_child_process();
}

pid_t send_update_process()
{
	pid_t pid;
	void send_update_child_process();

	if ((pid = fork()) > 0)
		return (pid);

	send_update_child_process();
}

pid_t vrcd_process()
{
	pid_t pid;
	void vrcd_child_process();

	if ((pid = fork()) > 0)
		return (pid);

	vrcd_child_process();
}

void check_child_process()
{
	void thread_func(int);
	pid_t cpid;
	int cstatus = 0;

	while (1)
	{
		//setproctitle("guard(check) : initializing.......");
		if ((cpid = fork()) < 0)
		{
			fprintf(stderr, "Cannot Fork(child)\n");
			exit(0);
		}
		else if (cpid == 0)
		{
			fprintf(stderr, "start check child process\n");
			SetSigHandle(0);
			//check_semid = Semget(Ftok(SEM_NAME, 's'), 0, 0);
			check_semid = Semget(SEM_NAME, 0, 0);
			
			//setproctitle("child(check): running...");
	        chk_rtime_update_serv_status();
			sleep(1);
		}
		//setproctitle("guard(check) : waiting... until done");
		wait(&cstatus);
	}
}

void send_update_child_process()
{
	pid_t cpid;
    int cstatus = 0;

	while (1)
	{
		if ((cpid = fork()) < 0)
		{
			fprintf(stderr, "Cannot Fork(child)\n");
			exit(0);
		}
		else if (cpid == 0)
		{
			fprintf(stderr, "start send, update child process\n");
			SetSigHandle(0);

			//report_s_lock = PTHREAD_MUTEX_INITIALIZER;
            //report_u_lock = PTHREAD_MUTEX_INITIALIZER;
			
			pthread_mutex_init( &report_s_lock, NULL);
            pthread_mutex_init( &report_u_lock, NULL);

			//report_semid = Semget(Ftok(SEM_NAME, 's'), 0, 0);
			report_semid = Semget(SEM_NAME, 0, 0);
			
			/* To do REPORT */
            report_threads = 2;
            report_tptr = Calloc(report_threads, sizeof(Thread));

            /* make thread(servList update thread(0), servList send thread(1)) */
            report_thread_make(2);
			//setproctitle("child(send/update): running...");
			
			for (;;)
				pause();
		}
		//setproctitle("guard(send/update) : waiting... until done");
		wait(&cstatus);
	}
}

void vrcd_child_process()
{
	void thread_func(int);
	pid_t cpid;
	int cstatus = 0;

	while (1)
	{
		if ((cpid = fork()) < 0)
		{
			fprintf(stderr, "Cannot Fork(child)\n");
			exit(0);
		}
		else if (cpid == 0)
		{
			int i;
			char    HostName[512];
			char    sh_port[16];
			fprintf(stderr, "start vrcd child process\n");
			SetSigHandle(0);
			
			//vrcd_lock = PTHREAD_MUTEX_INITIALIZER;
			//vrcd_lock2 = PTHREAD_MUTEX_INITIALIZER;
			
			pthread_mutex_init( &vrcd_lock, NULL);
			pthread_mutex_init( &vrcd_lock2, NULL);
			
			//vrcd_semid = Semget(Ftok(SEM_NAME, 's'), 0, 0);
			vrcd_semid = Semget(SEM_NAME, 0, 0);
           
			/* To do VRCD */
            GetProfile("PARAM", "HOSTNAME", HostName, "localhost");
            GetProfile("PARAM", "VRCD_PORT", sh_port, "22200");
            vrcd_threads = GetProfileInt("PARAM", "VRCD_THREAD_NUM", 10);
            vrcdfd = Tcp_listen(HostName, sh_port, &vrcd_addrlen);
            vrcd_tptr = Calloc(vrcd_threads, sizeof(Thread));

			running_vrcd_thread_count = 0;
            for (i = 0; i < vrcd_threads; i++)
	            vrcd_thread_make(i);                    // only main thread returns

			//setproctitle("child(vrcd): running...");
			for (;;)
				pause();
		}
		//setproctitle("guard(vrcd) : waiting... until done");
		wait(&cstatus);
	}
}

void addServerToList(servinfo_t *serv, MYSQL_ROW row)
{
	int j;
	strncpy(serv->m_pszServName, (char *)row[0], MAX_SRV_NAME);
	strncpy(serv->m_szIpString, (char *)row[1], MAX_IP_STRING);
	serv->m_sPort = atoi(row[2]);

	/* Server Type String to Integer */
	for (j=0; j < NumOfServerType; j++)
	{
		if(!strcmp((char *)row[3], a_ServType[j]))
			serv->m_ServType = j;
	}
	serv->m_StatChgTime = time(NULL);
	if (!strncmp((char *)row[4], "N", 2))
	    serv->m_Status = NO_MONITOR;
	else
		serv->m_Status = SERV_ALIVE;
	serv->m_DeadCount = 0;
	strncpy(serv->m_monitorYN, (char *)row[4], 2);
	serv->next = NULL;
}

int write_mylock(int g_pidfd)
{
    struct flock lock;

    lock.l_type = F_WRLCK;
    lock.l_start = 0;
    lock.l_whence = SEEK_SET;
    lock.l_len = 0;

    return fcntl(g_pidfd, F_SETLK, &lock);
}

int create_pid_new_sh_statusd()
{
    // new version for check that Daemon is exist already
    static int g_pidfd;
    char buf[512];

    if ((g_pidfd = open(PID_FILE_PATH, O_RDWR | O_CREAT, "w+")) < 0)
    {
        fprintf(stderr, "unable to open %s\n",PID_FILE_PATH);
        exit(1);
    }

    if ( write_mylock(g_pidfd) < 0 ) {
        if (errno == EACCES || errno == EAGAIN) {
            return -2;
        } else {
            fprintf(stderr, " unable to lock %s\n", PID_FILE_PATH);
            exit(1);
        }
    }
    snprintf(buf, 512, "%d", getpid());

    if (write(g_pidfd, buf, strlen(buf)) == -1)
    {
        fprintf(stderr, "unable to write PID\n");
        exit(1);
    }

    return 1;
}

void makedaemon(int mode) 
{ 
#ifdef SIGTTOU
	signal(SIGTTOU, SIG_IGN);
#endif
#ifdef SIGTTIN
	signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
	signal(SIGTSTP, SIG_IGN);
#endif
#ifdef SIGINT
	signal(SIGINT, SIG_IGN);
#endif
#ifdef SIGSTP
	signal(SIGSTP, SIG_IGN);
#endif

	pid_t pid; 
	if ((pid = fork()) < 0) 
		exit(0); 

	else if (pid != 0) 
		exit(0); 

	// check if Daemon is exist already
    if (create_pid_new_sh_statusd() < 0)
    {
        fprintf(stderr, "new_sh_statusd Daemon(%d) already exist!!\n", getpid());
        exit(0);
    }

	if (mode == 0) 
	{ 
		close(0); 
		close(1); 
		close(2);
	} 
	setsid(); 
}

#define DISPLAY_SIGNAL_ACTION_ERROR(rv) WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return rv
static void HandleCoreDump(int signum)
{
	int kpid;
	signal(signum, SIG_DFL);
	WriteSHLog_safe("killed process(%d)\n", getpid());
	kpid = getpid();
	kill(kpid, SIGKILL);
	usleep(100);
	kill(kpid, SIGKILL);
	
	sleep(1);
}

static void HandleChildHang(int signum)
{
	int status = 1;
	fprintf(stderr, "Signum: %d", signum);
	while (waitpid(-1, &status, WNOHANG) > 0)
	{
		fprintf(stderr, "decrease one child process!\n");
		WriteSHLog_safe("decrease one child process!\n");
	}
}

static int SetSigHandle(int Option)
{
	struct sigaction sa;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;

#if defined(SA_ONESHOT)
	sa.sa_flags = SA_ONESHOT;
#elif defined(SA_RESETHAND)
	sa.sa_flags = SA_RESETHAND;
#endif
	if(Option == 1)
	{
		sa.sa_handler = HandleChildHang;
		if(sigaction(SIGCHLD, &sa, NULL) < 0)
		{
			WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -1;
			//DISPLAY_SIGNAL_ACTION_ERROR(-1);
		}
	}
	else
	{
		sa.sa_handler = HandleCoreDump;
	}

	if(sigaction(SIGSEGV, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -2;
		//DISPLAY_SIGNAL_ACTION_ERROR(-2);
	}
#ifdef SIGBUS
	if(sigaction(SIGBUS, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -3;
		//DISPLAY_SIGNAL_ACTION_ERROR(-3);
	}
#endif
#ifdef SIGABORT
	if(sigaction(SIGABORT, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -4;
		//DISPLAY_SIGNAL_ACTION_ERROR(-4);
	}
#endif
#ifdef SIGABRT
	if(sigaction(SIGABRT, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -5;
		//DISPLAY_SIGNAL_ACTION_ERROR(-5);
	}
#endif
#ifdef SIGILL
	if(sigaction(SIGILL, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -6;
		//DISPLAY_SIGNAL_ACTION_ERROR(-6);
	}
#endif
	if(sigaction(SIGHUP, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -7;
		//DISPLAY_SIGNAL_ACTION_ERROR(-7);
	}
	if(sigaction(SIGUSR1, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -8;
		//DISPLAY_SIGNAL_ACTION_ERROR(-8);
	}

#ifdef SIGINT
	if(sigaction(SIGINT, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -9;
		//DISPLAY_SIGNAL_ACTION_ERROR(-9);
	}
#endif
#ifdef SIGXCPU
	sa.sa_handler = SIG_DFL;
	if(sigaction(SIGXCPU, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -10;
		//DISPLAY_SIGNAL_ACTION_ERROR(-10);
	}
#endif
#ifdef SIGXPSZ
	sa.sa_handler = SIG_DFL;
	if(sigaction(SIGSEGV, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -11;
		//DISPLAY_SIGNAL_ACTION_ERROR(-11);
	}
#endif
#ifdef SIGPIPE
	sa.sa_handler = SIG_IGN;
	if(sigaction(SIGPIPE, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -12;
		//DISPLAY_SIGNAL_ACTION_ERROR(-12);
	}
#endif
#ifdef SIGSYS
	sa.sa_handler = SIG_IGN;
	if(sigaction(SIGPIPE, &sa, NULL) < 0)
	{
		WriteSHLog_safe("!sigaction : %s\n", strerror(errno)); return -13;
		//DISPLAY_SIGNAL_ACTION_ERROR(-13);
	}
#endif
	return 1;
}

void sig_chld(int signo)
{
	pid_t pid;
	int	  stat;

	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		fprintf(stderr, "child %d terminated\n", pid);
	return;
}

void sig_int(int signo)
{
	int i;

	for (i = 0; i < vrcd_threads; i++)
		fprintf(stderr, "vrcd thread %d, %ld connections\n", i, vrcd_tptr[i].thread_count);
//	exit(0);
}

void sig_int_report(int signo)
{
	int     i;

	for (i = 0; i < report_threads; i++)
		fprintf(stderr, "report thread %d, %ld connections\n", i, report_tptr[i].thread_count);
//	exit(0);
}
