#include "WorkerMonitoringPortControl.h"

#include "ProcessConfig.h"
#include "Logger.h"
#include "FhsShm.h"

#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>


// Service status Check 주기 (sec)
// - Service status 에 따른 Listen Port 제어 관련 Loop 주기
#define SERVICE_STATUS_CHECK_INTERVAL	2

// accept() 처리 관련 timeout (sec)
#define ACCEPT_TIMEOUT_SEC		2


// listen queue 에서 accept 대기 Count 수
//  - 기본 적으로 Monitoring 용 Listen Port 이므로... 갯수를 적게 유지 처리한다.
#define DEFAULT_LISTEN_WAIT_COUNT	5	



// 공유메모리에 Data Set, Get 을 담당할 객체에 대한 포인터.
// Main 프로세스에서 생성 처리하며... 전역변수로 설정되어 있다.
extern CFhsShm * g_pFhsShm;


// Listener Socket
// 시그널 등의 전역 처리 문제로 전역 변수로 사용
int g_listenSocket = -1;


// Listen 처리를 위한 socket 생성
// @param listenSocket [out] 생성된 Socket 정보를 저장하기 위한 참조변수
// @return 성공시 true, 오류발생시 false 반환.
bool MakeListenSocketV6( int & listenSocket )
{
	// socket 생성
	listenSocket = socket( AF_INET6, SOCK_STREAM, 0 );
	if( listenSocket == -1 )
	{
		int errorNum = errno;
		LOG( LERR, "[MonPortControl] listen socket create failed. [%d][%s]", errorNum, strerror( errorNum ) );
		return false;
	}

	struct sockaddr_in6 listenSockAddr;
	socklen_t listenSockLen = 0;
	memset( &listenSockAddr, 0x00, sizeof( listenSockAddr ) );

	listenSockAddr.sin6_family = AF_INET6;
	listenSockAddr.sin6_flowinfo = 0;
	listenSockAddr.sin6_port = htons( CProcessConfig::GetInstance()->GetListenPortForStatusMonitoring() );
	listenSockAddr.sin6_addr = in6addr_any;

	listenSockLen = sizeof( listenSockAddr );
	
	int result = 0;
	int opt = 0;

	// socket address reuse option set
	opt = 1;
	result = setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof( opt ) );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[MonPortControl] listen socket option[SO_REUSEADDR] set failed. [%d][%s]", errorNum, strerror( errorNum ) );
		
		close( listenSocket );
		listenSocket = -1;
		return false;
	}

	// keep alive option set
	opt = 1;
	result = setsockopt( listenSocket, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[MonPortControl] listen socket option[SO_KEEPALIVE] set failed. [%d][%s]", errorNum, strerror( errorNum ) );
	
		close( listenSocket );
		listenSocket = -1;
		return false;
	}

	// IPv6 에서 IPv4 지원을 위한 설정
	opt = 0;
	result = setsockopt( listenSocket, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof( opt ) );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[MonPortControl] listen socket option[IPV6_V6ONLY] set failed. [%d][%s]", errorNum, strerror( errorNum ) );
	
		close( listenSocket );
		listenSocket = -1;
		return false;
	}

	// socket bind
	result = bind( listenSocket, ( struct sockaddr * )&listenSockAddr, listenSockLen );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[MonPortControl] listen socket bind failed. [%d][%s]", errorNum, strerror( errorNum ) );
	
		close( listenSocket );
		listenSocket = -1;
		return false;
	}

	// socket listen
	result = listen( listenSocket, DEFAULT_LISTEN_WAIT_COUNT );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "[MonPortControl] listen socket listen failed. [%d][%s]", errorNum, strerror( errorNum ) );
	
		close( listenSocket );
		listenSocket = -1;
		return false;
	}
		
	_LOG( LINF, "[MonPortControl] tcp port[%u] listen start."
		  , CProcessConfig::GetInstance()->GetListenPortForStatusMonitoring() );

	return true;
}


// Listen 처리를 위한 socket close 처리
// @param listenSocket [in] listen socket 정보
void CloseListenSocket( int & listenSocket )
{
	if( listenSocket != -1 )
	{
		close( listenSocket );
		listenSocket = -1;
	}

	return;
}


// Worker::MonPortControl 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerMonPortControlMain()
{
	setproctitle( "MonPortControl [initialize]" );

	// Set Signal Handler
	SetWorkerMonPortControlSignalHandler();
	

	// Main Process 에서 생성한 공유메모리 접근 객체 유효성 확인
	// NULL 인 경우... 초기화 실패로 처리한다.
	if( g_pFhsShm == NULL )
	{
		// 객체 생성 실패시
		LOG( LERR, "[MonPortControl:%d] Shared memory object not valid. process exit", getpid() );

		ReadyToExitWorkerMonPortControl();
		return EXIT_FAILURE;
	}


	// sleep 처리 관련 설정 처리
	struct timespec sleepLoop;
	sleepLoop.tv_sec = SERVICE_STATUS_CHECK_INTERVAL;
	sleepLoop.tv_nsec = 0;

	// 서비스 상태 Check 관련 변수
	int iServiceStat;
	time_t timeServiceCheck = 0;
	struct timespec currentClock;


	// select 관련 변수
	struct timeval selectTimeOut;
	int iSelectResult;
	fd_set selectFds;
	FD_ZERO( &selectFds );

	// socket 처리 관련 변수
	int errorNum;
	char tempBuffer[INET6_ADDRSTRLEN];
	int clientSocket;
	struct sockaddr_in6 clientSockAddr;
	socklen_t clientSockLen = sizeof( clientSockAddr );



	// 전체 프로세스의 생성이 완료될때까지 대기 후 작업 시작
	setproctitle( "MonPortControl [TCP %u]", CProcessConfig::GetInstance()->GetListenPortForStatusMonitoring() );
	sleep( 3 );

	// Listen 처리 관련 FreeBSD 10.X , 6.X 차이점 
	// - FB 10.X 의 경우.. listen 상태에서 accept() 를 호출하지 않아도.. neocast 에서 정상 상태로 인식
	// - FB 6.X 의 경우.. neocast 에서 listen 만 하면 비정상 상태로 인식하고... accept 까지 해야 정상 상태로 인식됨.
	// - 이러한 차이로 인해 accept() 를 하지 않는 방법으로 Port 제어를 하는 것으로 적합하지 않음.
	// - 이에 따라 서비스 상태에 따른 Listen Socket 생성, Close 처리 방법을 사용한다.

	// 주기적으로 Service Status 정보를 Check 한다.
	// - 서비스 상태 Check 프로세스에서 blocking 상황 발생시
	// - RCTS 전송 프로세스에서 Timeout 처리를 수행하여 서비스 상태를 Disable 로 처리한다.
	// - 하지만 RCTS 전송 프로세스에서도 blocking 상황 발생하면.. 서비스 상태 변경이 안되서..
	// - 모니터링 Port 관련 제어가 불가하므로... 보조적으로 RCTS 전송 프로세스에 적용한 Timeout Check 기능을 넣는다.
	while( 1 )
	{
		iServiceStat = g_pFhsShm->GetServiceStat();

		if( iServiceStat == SERVICE_STAT_DISABLE || iServiceStat == SERVICE_STAT_OUT )
		{
			// 서비스 상태가 out, disable 상태인 경우....
			// - monitoring port close 처리한다.
			if( g_listenSocket != -1 )
			{
				CloseListenSocket( g_listenSocket );

				_LOG( LINF, "[MonPortControl] service stat [%s]. TCP monitoring port[%u] closed."
					  , g_pFhsShm->GetServiceStatString( iServiceStat )
					  , CProcessConfig::GetInstance()->GetListenPortForStatusMonitoring() );
			}
				
			// 지정된 시간동안 sleep 한 후 다시 check 수행
			nanosleep( &sleepLoop, NULL );
			continue;
		}
		else
		{
			// 서비스 상태가 정상인 상황인 경우...
			// - 서비스 상태 check 가 정상적으로 동작 중인지 체크하여
			// - 서비스 상태 check 가 blocking 상황이면.. 자체적으로 모니터링 Port 를 차단한다.
			timeServiceCheck = g_pFhsShm->GetTimeServiceCheck();
			if( timeServiceCheck == 0 )
			{
				// 아직 초기화 중인 상태
				// - Port 관련 처리를 수행하지 않고.. 지정된 시간 sleep 후 다시 check 수행한다.
				nanosleep( &sleepLoop, NULL );
				continue;
			}
			else
			{
				// 현재 시간을 추출
				memset( &currentClock, 0x00, sizeof( struct timespec ) );
				clock_gettime( CLOCK_MONOTONIC, &currentClock );

				if( timeServiceCheck < currentClock.tv_sec - TIMEOUT_SERVICE_CHECK )
				{
					// timeout 발생시 서비스 Disable 상태로 변경 처리
					g_pFhsShm->SetServiceStat( SERVICE_STAT_DISABLE );

					_LOG( LERR, "[MonPortControl] service check timeout[%d] last[%ld], current[%ld]. service stat set: %d (%s)"
						  , TIMEOUT_SERVICE_CHECK, timeServiceCheck, currentClock.tv_sec
						  , g_pFhsShm->GetServiceStat(), g_pFhsShm->GetServiceStatString() );

					// 서비스 상태가 out, disable 상태이므로... monitoring port close 처리한다.
					if( g_listenSocket != -1 )
					{
						CloseListenSocket( g_listenSocket );

						_LOG( LINF, "[MonPortControl] service stat timeout changed [%s]. TCP monitoring port[%u] closed."
							  , g_pFhsShm->GetServiceStatString( iServiceStat )
							  , CProcessConfig::GetInstance()->GetListenPortForStatusMonitoring() );
					}
					
					// 지정된 시간동안 sleep 한 후 다시 check 수행
					nanosleep( &sleepLoop, NULL );
					continue;
				}
			}
		}


		// 서비스 상태가 정상 또는 Read Only 상태인 경우

		// Listen Socket 이 이미 존재하는지 체크하여...
		// - Listen Socket 가 생성되지 않은 경우 생성 처리.
		if( g_listenSocket == -1 )
		{
			// Listen Socket 생성 처리
			if( MakeListenSocketV6( g_listenSocket ) == false )
			{
				// Listen socket 실패시
				// - 지정된 시간 대기 후 다시 실행하도록 처리
				// 지정된 시간동안 sleep 한 후 다시 check 수행
				nanosleep( &sleepLoop, NULL );
				continue;
			}
		}

		// client accept 처리를 수행한다.
		// - accept 는 blocking 함수 이므로.. 
		// - client 가 없는 상황에서 blocking 되면... 서비스 상태를 바로 반영할 수 없는 문제가 발생
		// - 따라서... select() 함수를 사용하여 timeout 처리를 수행한다.
		selectTimeOut.tv_sec = ACCEPT_TIMEOUT_SEC;
		selectTimeOut.tv_usec = 0;
		FD_SET( g_listenSocket, &selectFds );

		iSelectResult = select( g_listenSocket + 1, &selectFds, ( fd_set * ) NULL, ( fd_set * ) NULL, &selectTimeOut );

		if( iSelectResult == 0 )	
		{
			// Timeout 인 경우
			continue;
		}
		else if( iSelectResult > 0 )
		{
			// accept 처리 수행
			clientSocket = accept( g_listenSocket, ( struct sockaddr * ) &clientSockAddr, &clientSockLen );
			if( clientSocket == -1 )
			{
				// 오류 발생시
				errorNum = errno;
				LOG( LERR, "[MonPortControl] client accept faild. [%d][%s]", errorNum, strerror( errorNum ) );
			}
			else
			{
				// client IP 주소 추출
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET6, &( clientSockAddr.sin6_addr ), tempBuffer, sizeof( tempBuffer ) ) == NULL )
				{
					errorNum = errno;
					LOG( LERR, "[MonPortControl] client inet_ntop error. [%d][%s]", errorNum, strerror( errorNum ) );

					sprintf( tempBuffer, "unknown" );
				}

				// 관련 내역 로깅 후 monitoring 처리 socket 이므로 client 종료 처리.
				_LOG( LINF, "[MonPortControl] client accepted. ip: %s", tempBuffer );
				close( clientSocket );
			}
		}
		else // select 함수 오류
		{
			errorNum = errno;
			LOG( LERR, "[MonPortControl] listen socket select func faild. [%d][%s]", errorNum, strerror( errorNum ) );

			// select 함수 오류시 listen socket 문제인 경우이므로... listen socket close 처리한다.
			CloseListenSocket( g_listenSocket );
		}
		
	}
	
	// 종료시 정리 작업 수행
	ReadyToExitWorkerMonPortControl();
	return EXIT_SUCCESS;
}

// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerMonPortControl()
{
	// listen socket close
	CloseListenSocket( g_listenSocket );

	// Logger 객체 종료 처리.
	CLogger::Exit();

	return;
}

// Worker MonPortControl 프로세스에 대한 종료 처리 수신시.
static void SignalWorkerMonPortControlTerminate( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		_LOG( LINF, "[MonPortControl:%d] Process exit by SIGTERM signal. Good Bye..", getpid() );
	}
	else
	{
		_LOG( LWAR, "[MonPortControl:%d] Process exit by abnormal signal[%d], Good Bye..", getpid(), nSignalNumber );
	}

	ReadyToExitWorkerMonPortControl();

	// 종료전 잠시 대기
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );

	exit( EXIT_SUCCESS );
}

// Worker::MonPortControl Process 의 signal 처리기...
void SetWorkerMonPortControlSignalHandler()
{
	sigset_t set;
	struct sigaction act;

	memset( &act, 0x00, sizeof( act ) );
	sigfillset( &set );
	sigprocmask( SIG_SETMASK, &set, NULL );
	sigfillset( &act.sa_mask );

	// 무시 처리 signal
	act.sa_handler = SIG_IGN;
	sigaction( SIGPIPE, &act, NULL );	/* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
	sigaction( SIGHUP, &act, NULL );	/* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
	sigaction( SIGINT, &act, NULL );	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	sigaction( SIGQUIT, &act, NULL );	/* 키보드에 의한  Abort 신호 처리 => ? */

	// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
	act.sa_handler = SIG_IGN;
	sigaction( SIGCHLD, &act, NULL );

	/* 각종 에러나 사용자의 종료 신호 처리 */
	act.sa_handler = SignalWorkerMonPortControlTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

	// 나머지 신호는 default 처리.

	sigemptyset( &set );                  /* 신호 처리기 처리 설정 위한 블록 해제 */
	sigprocmask( SIG_SETMASK, &set, NULL );
}
