#include "WorkerRecvSystem.h"

#include "Logger.h"
#include "ProcessRename.h"
#include "ShmManager.h"
#include "ProcessConfig.h"
#include "SocketCsagentd.h"
#include "ShmClientInfo.h"
#include "ReportLog.h"

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


// 공유메모리에 Data Set, Get 을 담당할 객체에 대한 포인터.
// Main.cpp 에 전역변수로 정의 되어 있다.
extern CShmManager * g_pShmManager;

// FHS 의 vmasd 가 Broadcasting 하는 각 FHS 의 상태 정보를 수신받기 위한 UDP Socket....
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockVmasdRecv;

// FHS csagentd 가 전송하는 시스템 상태 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockSystemRecv;

// FHS csagentd 가 전송하는 서비스 상태 체크 정보 수신을 위한 TCP Socket
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockCheckRecv;

// fimngd 등과 같은 외부 모듈과 통신을 통해 정보를 전달하기 위한 TCP Socket 생성.
// Main.cpp 에 전역변수로 정의 되어 있다.
extern int g_sockInfoSend;


// 각 Client 를 처리할 Thread 에 전달할 구조체 
struct stRecvSystemThreadArg {

	// client socket descriptor
	int clientSocketDesc;

	// Client 의 IP 주소 정보를 저장한 변수
	// - 공유메모리 client_ip 항목 크기와 동일한 크기로 설정.
	char szClientAddress[IP_ADDR_LENGTH];

	// client 의 공유메모리 index 정보.
	int iClientShmIndex;

};


// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExitWorkerRecvSystem()
{
	// open 된 socket 관련 Close 처리
	if( g_sockSystemRecv != -1 )
		close( g_sockSystemRecv );


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

	return;
}


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

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

	exit( EXIT_SUCCESS );
}

// Worker::RecvSystem Process 의 signal 처리기...
void SetWorkerRecvSystemSignalHandler()
{
	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 = SignalWorkerRecvSystemTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

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

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



// Worker::RecvSystem 에서 각 Client 에 대한 처리를 수행하기 위한 Thread.
void * WorkerRecvSystemClientThread( void * arg )
{
	// 전달받은 변수를 내부 변수에 저장 처리.

	// client socket 관련 처리 변수 생성	
	CSocketCsagentd clientSocket( ( ( struct stRecvSystemThreadArg * ) arg )->clientSocketDesc, CProcessConfig::GetInstance()->GetRcId() );

	// client 주소 처리 관련 
	char szClientAddress[IP_ADDR_LENGTH];
	memcpy( szClientAddress, ( ( struct stRecvSystemThreadArg * ) arg )->szClientAddress, IP_ADDR_LENGTH );

	// client shared memory index
	int iClientShmIndex = (( struct stRecvSystemThreadArg * ) arg )->iClientShmIndex;
	CShmClientInfo clientShm( g_pShmManager->GetClientPtr( iClientShmIndex ) );
	
	// malloc 로 생성된 변수 해제 처리
	free( arg );
	
	// 본 thead 는 client 연결 상태에 따라 종료되므로.. process 에서 분리 처리
	pthread_detach( pthread_self() );
		
	_LOG( LINF, "[RecvSystem:%d] client thread start. [%s] shm_index [%d]", getpid(), szClientAddress, iClientShmIndex );


	// socket 수신 대기
	int nRead;

	std::string strHostname;
	struct SystemInfo data;

	while( 1 )
	{
		// 2018-02-22 CHG huibong (#31703)
		// - FHS 가 Hang 상황으로 system 상태 정보 전송 불가한 상황인 경우....
		// - GarbageCleanThread 가 있어서.. 정리 처리를 하지만.... 운영자가 이런 flow 를 잘 모르고 오해하는 경우 있음.
		// - 따라서 지정된 시간 동안 Data 를 수신하지 못하는 경우... 기존 재시도 대신 연결을 종료 처리한다.
		// - 대신 timeout 시간은 기존 13 sec -> 15 sec 로 조정 한다.

		// Data 수신
		nRead = clientSocket.GetSystemInfo( strHostname, &data, 15 );
		if( nRead == 0 )
		{
			// timeout 발생시
			_LOG( LINF, "[RecvSystem:%d] client session timeout. session closed. [%s]", getpid(), szClientAddress );
			
			break;
		}
		else if( nRead < 0 )
		{
			// Socket 오류 발생시
			// - 관련 로그는 해당 모듈에서 다 찍었으므로.. 여기서는 루프 종료 처리.
			break;
		}
		else
		{
			// 정상 Data 수신시 			
			// RC ID 검사는 socket 수신 처리시 수행했음.

			// shared memory 유효성 검사			
			// - 유효하지 않은 경우.. 종료 처리한다.
			if( clientShm.IsValid( szClientAddress ) == false )
			{
				LOG( LERR, "client[%s] recv system: shared memory[%d] not valid. session closed."
					 , szClientAddress, iClientShmIndex );
				break;
			}

			// shared memory 에 정보 저장 처리.
			if( clientShm.UpdateSystemInfo( strHostname, &data ) == false )
			{
				// shared memory 정보가 유효하지 않은 경우...
				// - 종료 처리.
				LOG( LERR, "client[%s] recv [%s] sys: shm update fail.  session close.", szClientAddress, strHostname.c_str() );
				break;
			}
			else
			{
				_LOG( LDBG, "client[%s] recv [%s] sys: shm update. load5[%.2lf] mem[%d] swap[%d] storage[%d] busy[%.1lf] tcp80[%u of %u]"
					  , szClientAddress, strHostname.c_str()
					  , data.load_avg[1], data.memory_usage, data.swap_usage, data.storage_max_usage, data.disk_max_busy
					  , data.tcp_80_establish, data.tcp_80 );
			}			
		}
	}
	
	// client 와 연결 종료 처리.
	_LOG( LINF, "[RecvSystem:%d] client connection closed. [%s]", getpid(), szClientAddress );
	clientSocket.Close();
		
	// Thread 종료 관련 처리.
	return NULL;
}


// Worker::RecvSystem 프로세스의 Main 역활을 수행하기 위한 함수.
int WorkerRecvSystemMain()
{
	// 전역 변수로 설정된 socket 중 해당 worker 에서 사용하지 않는 socket 은 close 처리한다.
	if( g_sockVmasdRecv != -1 )
	{
		close( g_sockVmasdRecv );
		g_sockVmasdRecv = -1;
	}	
	if( g_sockCheckRecv != -1 )
	{
		close( g_sockCheckRecv );
		g_sockCheckRecv = -1;
	}
	if( g_sockInfoSend != -1 )
	{
		close( g_sockInfoSend );
		g_sockInfoSend = -1;
	}
	

	// Process Rename..
	set_ps_display( PROG_NAME": RecvSystem", false );
	
	// Set Signal Handler
	SetWorkerRecvSystemSignalHandler();


	// 사용할 객체 초기화 처리
	int clientSock;
	struct sockaddr_in clientSockAddr;
	socklen_t clientSockLen = sizeof( clientSockAddr );

	pthread_t clientThreadId;
	struct stRecvSystemThreadArg * pThreadArg = NULL;

	while( 1 )
	{
		clientSock = accept( g_sockSystemRecv, ( struct sockaddr * ) &clientSockAddr, &clientSockLen );

		if( clientSock == -1 )
		{
			// 오류 발생시.... 해당 내역 로깅 처리.
			int errorNum = errno;
			LOG( LERR, "[RecvSystem:%d] client accept failed [%d][%s]", getpid(), errorNum, strerror( errorNum ) );
		}
		else
		{
			// 정상적인 Client 인 경우...
			// Thread 로 전달할 변수를 malloc 로 생성 처리
			pThreadArg = ( struct stRecvSystemThreadArg * ) malloc( sizeof( struct stRecvSystemThreadArg ) );
			if( pThreadArg == NULL )
			{
				int errorNum = errno;
				close( clientSock );    // client 접속 종료 처리.

				char tempBuffer[IP_ADDR_LENGTH];
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
				{
					LOG( LERR, "[RecvSystem:%d] client[%s] thread malloc failed [%d][%s]", getpid(), tempBuffer, errorNum, strerror( errorNum ) );
				}
				else
				{
					LOG( LERR, "[RecvSystem:%d] client thread malloc failed [%d][%s] and inet_ntop error[%d][%s]"
						 ,getpid(), errorNum, strerror( errorNum ), errno, strerror( errno ) );
				}

				continue;
			}
			else
			{
				// 인자로 전달할 변수정보 설정.
				pThreadArg->clientSocketDesc = clientSock;
				
				char tempBuffer[IP_ADDR_LENGTH];
				memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
				if( inet_ntop( AF_INET, &( clientSockAddr.sin_addr ), tempBuffer, sizeof( tempBuffer ) ) != NULL )
				{
					memcpy( pThreadArg->szClientAddress, tempBuffer, sizeof( tempBuffer ) );
				}
				else
				{
					// inet_ntop 오류 발생시
					// client ip 를 공유메모리 할당 관련 key 값으로 사용하므로.....
					// ip 가 불분명할 경우.. 잘못된 공유메모리 할당으로 인해 문제 발생 가능.
					// 따라서 관련 상세 내역 로깅 후.. 세션 종료 처리한다.
					//snprintf( pThreadArg->szClientAddress, IP_ADDR_LENGTH-1, "0.0.0.0" );

					int errorNum = errno;
					LOG( LERR, "[RecvSystem:%d] client ip not valid. connection closed. inet_ntop error[%d][%s]", getpid(), errorNum, strerror( errorNum ) );

					close( clientSock );
					free( pThreadArg );

					continue;
				}

				// 공유메모리상에 해당 client slot 이 존재하는지 확인한다.
				// - 만약 존재하지 않을 경우....
				// - service check 관련 정보가 아직 수신되지 않은 상태이므로..... connection 을 close 처리하고.. 다음에 다시 접속하도록 한다.
				pThreadArg->iClientShmIndex = g_pShmManager->FindClientIndex( pThreadArg->szClientAddress );
				if( pThreadArg->iClientShmIndex == -1 )
				{
					LOG( LERR, "[RecvSystem:%d] client[%s] shared memory not allocated. connection close.", getpid(), pThreadArg->szClientAddress );
					
					close( clientSock );
					free( pThreadArg );
					
					continue;
				}
				
				// Client 처리를 담당할 Thread 생성 처리
				if( pthread_create( &clientThreadId, NULL, WorkerRecvSystemClientThread, (void *)pThreadArg ) != 0 )
				{
					// 생성 실패시...
					// - 로깅 
					// - client socket close 
					// - malloc 로 생성된 전달 변수 free 처리.

					int errorNum = errno;
					LOG( LERR, "[RecvSystem:%d] client[%s] thread create failed [%d][%s]"
						 , getpid(), pThreadArg->szClientAddress, errorNum, strerror( errorNum ) );

					// 할당된 공유메모리 해제
					// - WorkerRecvCheck 에서 할당했음로.. 그 쪽에서 해제 권한 가짐.

					close( clientSock );
					free( pThreadArg );
				}
				else
				{
					_LOG( LDBG, "[RecvSystem:%d] client[%s] accept & client thread create ok.", getpid(), pThreadArg->szClientAddress );
				}
			}
		}

	} // while(1)
	

	// 종료시 정리 작업 수행
	ReadyToExitWorkerRecvSystem();
	return EXIT_SUCCESS;
}
