#include "Listener.h"

#include "GetConfig.h"
#include "Logger.h"
#include "Worker.h"

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>

#include <iostream>
#include <fstream>


using namespace std;


// Accept 대기 Count 수
// CHG 2010-03-18 huibong
// Accept 접속 대기 수가 몰리는 현상이 발생하여 5->30 으로 변경함.
#define DEFAULT_ACCEPT_WAIT_COUNT	30	

// 전역 객체 정보
// 1. config 정보를 저장하기 위한 전역 객체
struct st_config g_envConfig;

// 2. Listener Socket
// => 시그널 처리시 문제로 인해 전역함수로 생성. initialize listener.cpp
int g_listenSocket = -1;


int main( int argc, char * argv[] )
{
	// Version 정보 표시
	if( argc == 2 && strcmp( argv[1], "-v") == 0)
	{
		cerr << "[info] " << PROG_NAME << " (File TranSfer Daemon) Version : " << PROG_VERSION << endl;
		return EXIT_SUCCESS;
	}

	// 프로그램 중복 실행 체크
	if( IsCurrentProcessRun() == true )
	{
		cerr << "[warning] Process[" << PROG_NAME << "] is already running...." << endl;
		return EXIT_SUCCESS;
	}

	// Config 정보 설정 및 전역 정보 설정
	// extern struct st_config g_envConfig => define_ftsd.h 파일 참조
	string szErrorMessage;
	if( GetConfig( DEFAULT_CONFIG_FILE, g_envConfig, szErrorMessage ) == false )
	{
		cerr << "[error] Process[" << PROG_NAME << "]: " << szErrorMessage  << endl;
		return EXIT_FAILURE;
	}
	
	// Log 처리 객체 생성
	if( CLogger::Init( PROG_NAME, g_envConfig.szLogDir.c_str(), LINF ) == false )
	{
		cerr << "[error] Process[" << PROG_NAME << "]: Log init failed." << endl;
		return EXIT_FAILURE;
	}


	// Process 기동 관련 정보 로깅 
	_LOG( LINF, "***********************************************************" );
	_LOG( LINF, " %s (File TranSfer Daemon) Start. Version: %s", PROG_NAME, PROG_VERSION );
	_LOG( LINF, "***********************************************************" );
	_LOG( LINF, "Config file     : %s", DEFAULT_CONFIG_FILE );
	_LOG( LINF, "Listen Port     : %d", SVC_LISTEN_PORT );
	_LOG( LINF, "Log Path        : %s/%s", g_envConfig.szLogDir.c_str(), PROG_NAME );
	_LOG( LINF, "Storage root    : %s", g_envConfig.szStorageRoot.c_str() );
	_LOG( LINF, "Internal NIC    : %s", g_envConfig.szInternalNicName.c_str() );
	_LOG( LINF, "Worker Process  : %d", g_envConfig.nWorkerProcessCount );
	_LOG( LINF, "Max Send Thread : %d", g_envConfig.nMaxFileSendThread );
	_LOG( LINF, "FHS Cache Time  : %d (sec)", g_envConfig.nFhsInternalIpCacheTimeout );
	_LOG( LINF, "Stroage Refresh : %d (sec)", g_envConfig.nProperStorageRefresPeriod );
	_LOG( LINF, "MAX Bandwidth   : %d (%%)", g_envConfig.nMaxBandwidthUse );
	_LOG( LINF, "Logging Level   : %d", g_envConfig.nLogLevel );
	_LOG( LINF, "***********************************************************" );


	// Log Level 재설정
#ifdef _DEBUG_
	if( CLogger::GetInstance() != NULL )
	{
		CLogger::GetInstance()->SetLogLevel( LDBG );
	}
#else
	if( CLogger::GetInstance() != NULL )
	{
		CLogger::GetInstance()->SetLogLevel( g_envConfig.nLogLevel);
	}
#endif


	// 프로그램 Daemonize
	if( MakeDaemon() != 0 )
	{
		LOG( LERR, "Process[%s] Daemonize failed.", PROG_NAME);
		cerr << "[error] Process[" << PROG_NAME << "] Daemonize failed." << endl;
		return EXIT_FAILURE;
	}
	
	// Listener 프로세스의 Signal 처리기 설정
	SetSignalListener();

	// Listen Socket 생성
	if(	MakeListenSocket( g_listenSocket ) == false )
	{
		LOG( LERR, "Listen Socket Create failed. => Process Exit." );
		return EXIT_FAILURE;
	}
	
	// Worker Process 생성
	if(	MakeProcessWorker( g_listenSocket ) == false )
	{
		close( g_listenSocket );
		LOG( LERR, "Worker Process make failed. => Listener Process exit. Goob Bye..");
		return EXIT_FAILURE;
	}
	
	// Listener Process : 그냥 대기
#if defined(__FreeBSD__)
	setproctitle( "Listener [monitor worker process]" );
#endif
	while( 1 )
	{
		// Listener Process 는 할 일이 없다. 
		// => Worker 프로세스 종료시 SIGCHLD 신호로 인해 신호처리기에서 Worker Process 재성성 수행함.
		// => 따라서 그냥 시그널 대기
		pause();
		//LOG( LNOT, "Listener process receive signal. -> suspend" );
	}

	// 다음의 코드는 Daemon 으로 동작하기 때문에 수행되지 않는다.
	close( g_listenSocket );
	CLogger::Exit();

	return EXIT_SUCCESS;
}


/// @brief Worker Process 생성(fork) 처리 함수
/// @param listenSocket [in] Listen Socket
/// @return 생성 성공시 true, 실패시에는 false 를 반환.
bool MakeProcessWorker( int listenSocket )
{
	pid_t processId;

	// Conf 에 정의된 Worker Process Count 만큼 Worker 프로세스 생성 처리.
	for( int index = 0; index < g_envConfig.nWorkerProcessCount; index++ )
	{
		// Worker 프로세스 fork
		processId = fork();
		if( processId < 0 ) // Fork fail
		{
			int errorNum = errno;
			LOG( LERR, "Worker Process[%d th] create failed. [%d][%s]", index, errorNum, strerror(errorNum) );
			return false;
		}
		else if( processId == 0 ) // Child Process => Worker Process
		{
			// Worker Process Main 함수 호출 및 종료처리.
			WorkerMain( listenSocket );
			exit( EXIT_SUCCESS );
		}
		else
		{
			// Parent Process => Logging
			_LOG( LINF, "Worker Process[%d th] create success. PID[%d]" , index, processId);
			
			// 잠시 대기
			// CHG 2015-09-14 huibong
			// - #24376 usleep -> nanosleep 으로 변경 처리.
			struct timespec sleep;
			sleep.tv_sec = 0;
			sleep.tv_nsec = 200000000;  // 0.2 sec
			nanosleep( &sleep, NULL );

		}
	}

	return true;
}

/// @brief Worker Process 재생성(fork) 처리 함수
/// @param listenSocket [in] Listen Socket
/// @return 생성 성공시 true, 실패시에는 false 를 반환.
bool RemakeProcessWorker( int listenSocket )
{
	pid_t processId;

	// Worker 프로세스 fork
	processId = fork();
	if( processId < 0 ) // Fork fail
	{
		int errorNum = errno;
		LOG( LERR, "Worker Process recreate failed. [%d][%s]", errorNum, strerror(errorNum) );
		return false;
	}
	else if( processId == 0 ) // Child Process => Worker Process
	{
		// Worker Process Main 함수 호출 및 종료처리.
		WorkerMain( listenSocket );
		exit( EXIT_SUCCESS );
	}
	else
	{
		// Parent Process => Logging
		LOG( LWAR, "Worker Process recreate success. PID[%d]" , processId);
		
		// 잠시 대기
		// CHG 2015-09-14 huibong
		// - #24376 usleep -> nanosleep 으로 변경 처리.
		struct timespec sleep;
		sleep.tv_sec = 0;
		sleep.tv_nsec = 200000000;  // 0.2 sec
		nanosleep( &sleep, NULL );

	}
	
	return true;
}


/// @brief 현재 Process가 기동중인지 여부를 판단하기 위한 함수( 프로세스 중복 실행 체크)
/// @return 이미 해당 프로세스가 기동 중인 경우 true 반환, 그렇지 않으면 false 반환.
bool IsCurrentProcessRun()
{
	char tempBuffer[512];
	FILE * fd = NULL;
	bool bRun = false;

	snprintf( tempBuffer, sizeof(tempBuffer), "pgrep -x %s | sort", PROG_NAME );

	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		cerr << "[error] Process duplication check failed.[popen error][" << strerror(errno) << "]" << endl;
		
		// 오류 발생시 true 반환하여 프로세스 실행 방지처리
		return true;
	}
	else
	{
		memset( tempBuffer, 0x00, sizeof(tempBuffer) );
		while( fgets( tempBuffer, sizeof(tempBuffer)-1, fd) != NULL )
		{
			string tempPid( tempBuffer );
			Trim(tempPid);

			if( atoi( tempPid.c_str() ) != getpid() )
			{
				cout << "[info] Process duplication found. pid[" << atoi( tempPid.c_str() ) << "]" << endl;
				
				bRun = true;
				break;
			}
		}
		
		pclose( fd );
		return bRun;
	}
}

/// @brief std 상에 trim 함수가 없어서 직접 구현 아니면 boost/algorithm/string.hpp 상의 boost::trim 함수 사용
/// @return void
void Trim( string & str )
{
	if( str.length() == 0 )
		return ;

	// 문자열 뒤의 공백, TAB, CR 등의 문자 제거처리.
	string::size_type pos = str.find_last_not_of(" \a\b\f\n\r\t\v");
	if( pos != string::npos )
		str.erase( pos + 1 );
	
	// 문자열 앞의 공백, TAB, CR 등의 문자 제거처리.
	pos = str.find_first_not_of(" \a\b\f\n\r\t\v");
	if( pos != string::npos )
		str.erase( 0, pos );
}

/// @brief Daemon Process 생성처리
/// @return 0 인 경우 성공, -1 인 경우 오류발생
int MakeDaemon()
{
	pid_t processId;

	processId = fork();
	if( processId == -1 )
	{
		int errorNum = errno;
		cout << "[error] " << PROG_NAME << ": daemon fork fail.[" << strerror(errorNum) << "]" << endl;
		LOG( LERR, "Daemon fork failed. [%d][%s]", errorNum, strerror(errorNum)); 

		return -1;
	}

	if( processId != 0 )
	{
		// Parent Process => exit
		exit( EXIT_SUCCESS );
	}
	else
	{
		// NEW 2010-11-30 huibong
		// Daemon 생성시 파일 권한처리 관련 umask 코드 추가.
		umask(0);
		setsid();
	}

	// NEW 2010-11-30 huibong
	// Daemon 생성시 stdin, stdout, stderr 처리 Miss로 인해  
	// 원격 ssh 를 이용한 기동 처리시 CTRL+C 를 눌러야 기동처리되는 버그 수정
	//fclose(stdin);
	//fclose(stdout);
	//fclose(stderr);
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

	return 0;
}


/// @brief Listener Process 종료 Signal 을 전달받은 경우 이를 처리하기 위한 함수.
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SigTermListener( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		LOG( LWAR, "Listener Process[%d] exit job start by user signal [SIGTERM]", getpid() );
	}
	else
	{
		LOG( LWAR, "Listener Process[%d] exit job start by abnormal signal [%d]", getpid(), nSignalNumber );
	}

	// Process 종료관련 작업 추가
	close( g_listenSocket );
	
	LOG( LWAR, "Listener Process[%d] exit job end. Good Bye..", getpid());
	

	CLogger::Exit();


	// CHG 2015-09-14 huibong
	// - #24376 usleep -> nanosleep 으로 변경 처리.
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );
	

	exit( EXIT_SUCCESS );
}

/// @brief Listener Process 에서 Core 생성 관련 Signal 을 받은 경우 이를 처리하기 위한 함수.
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SigCoreListener( int nSignalNumber )
{
	LOG( LERR, "Listener Process[%d] abnoraml exit. Check Core file. Receive signal [%d]", getpid(), nSignalNumber );
	LOG( LERR, "Core file path : [%s/%s.core]", g_envConfig.szLogDir.c_str(), PROG_NAME );

	// Process 종료관련 작업 추가
	close( g_listenSocket );
	CLogger::Exit();

	// Log Directory 상에 Core 파일 생성처리.
	chdir( g_envConfig.szLogDir.c_str() );

	signal( nSignalNumber, SIG_DFL );

	// Core dump 생성을 위한 신호 발생처리.=> ?
	raise( nSignalNumber );
}

/// @brief Listener Process 에서 자식 프로세스인 Worker Process 가 종료된 경우 시그널 처리를 위한 함수
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SigChldListener( int nSignalNumber )
{
	pid_t killPid;
	int nKillStatus;
	
	while( ( killPid = waitpid( -1, &nKillStatus, WNOHANG ) ) > 0 )
	{
		// 자식 프로세스가 Signal 에 의해 종료되었는지 검사.
		if( WIFSIGNALED( nKillStatus ) )
		{
			LOG( LWAR, "Worker process[%d] killed by signal[%d]", killPid, WTERMSIG( nKillStatus ) );
		}
		else
		{
			LOG( LWAR, "Worker process[%d] killed. Not signal", killPid );

			// CHG 2014-02-28 huibong
			// Worker 프로세스가 EXIT_FAILURE 반환 ( 초기화 실패시 )
			// 해당 내역을 화면 및 로그 상에 출력하고 
			// 자식 프로세스를 재생성 처리하지 않는다.

			if( WIFEXITED( nKillStatus ) )
			{
				if( WEXITSTATUS( nKillStatus ) == EXIT_FAILURE )
				{
					_LOG( LERR, "Worker Process[%d] initilaize failed.", killPid );
					_LOG( LERR, "Program abnormally exited because Worker initialize faild..");

					// Main 종료 시그널 발생
					raise( SIGTERM );
					continue;

				}
			}			

		}

		// Worker Process 재생성 처리.
		if( RemakeProcessWorker( g_listenSocket ) == false )
		{
			// Worker Process 재성성 실패시 => 그냥 로깅
			LOG( LERR, "Worker process recreate failed.");			
		}
		else
		{
			LOG( LWAR, "Worker process recreate success by SIGCHLD");
		}
	}

	// 오류 발생시 해당 내역 로깅
	if( killPid < 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Listener process error: SIG_CHLD receive but waitpid return error[%d][%s]", errorNum, strerror(errorNum));
	}

	return;
}

/// @brief Listener프로세스 signal 처리 설정을 위한 함수	
/// @return void	
void SetSignalListener()
{
	sigset_t set;
	struct sigaction act;

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

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

	// Child Process 인 Worker 프로세스 종료에 대한 처리기 설정.
	act.sa_handler = SigChldListener;
	sigaction( SIGCHLD, &act, NULL);

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

	/* Core 관련 signal 처리 : 오류 처리 및 Debug(core dump) 목적 */
	act.sa_handler = SigCoreListener;
	sigaction( SIGILL , &act, NULL);	/* Illegal instruction */
	sigaction( SIGFPE , &act, NULL);	/* Erroneout arithmetic operation */
	sigaction( SIGBUS , &act, NULL);	/* Access to undefined portion of a memory object */
	sigaction( SIGSEGV, &act, NULL);	/* Invalid memory reference */
	sigaction( SIGSYS , &act, NULL);	/* Bad System Call */
	sigaction( SIGXCPU, &act, NULL);	/* CPU-time limit exceeded */
	sigaction( SIGXFSZ, &act, NULL);	/* File-size limit exceeded */

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


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

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

	listenSockAddr.sin_family = AF_INET;
	listenSockAddr.sin_port = htons( SVC_LISTEN_PORT );		// SVC_LISTEN_PORT : defined Makefile
	listenSockAddr.sin_addr.s_addr = htonl( INADDR_ANY );

	listenSockLen = sizeof(listenSockAddr);

	int result = 0;

	// Socket Port Reuse Option Set
	int opt = 1;
	result = setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt) );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Listen socket option[SO_REUSEADDR] set failed. [%d][%s]", errorNum, strerror(errorNum) );
		cerr << "[error] Process[" << PROG_NAME << "] Listen socket SO_REUSEADDR set failed.[" << strerror(errorNum) << "]" << endl;
		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, "Listen socket option[SO_KEEPALIVE] set failed. [%d][%s]", errorNum, strerror(errorNum) );
		cerr << "[error] Process[" << PROG_NAME << "] Listen socket SO_KEEPALIVE set failed.[" << strerror(errorNum) << "]" << endl;
		return false;
	}

	// Non Block Mode Set
	/*
	int flags;
	if(flags = fcntl(listenSocket, F_GETFL, 0) >= 0)
	{
		flags |= O_NONBLOCK;
		if(fcntl(listenSocket, F_SETFL, flags) < 0)
		{
			int errorNum = errno;
			ERROR_LOG( "Listen socket non-block set failed.[%d][%s]", errorNum, strerror(errorNum) );
			cerr << "[error] Process[" << PROG_NAME << "] Listen socket non-block set failed.[" << strerror(errorNum) << "]" << endl;
			return false;
		}
	}
	*/

	// Socket Bind
	result = bind( listenSocket, (struct sockaddr *)&listenSockAddr, listenSockLen);
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Listen socket bind failed.[%d][%s]", errorNum, strerror(errorNum) );
		cerr << "[error] Process[" << PROG_NAME << "] Listen socket bind failed.[" << strerror(errorNum) << "]" << endl;
		return false;
	}

	// Socket Listen
	result = listen( listenSocket, DEFAULT_ACCEPT_WAIT_COUNT );
	if( result != 0 )
	{
		int errorNum = errno;
		LOG( LERR, "Listen socket bind failed.[%d][%s]", errorNum, strerror(errorNum) );
		cerr << "[error] Process[" << PROG_NAME << "] Listen socket bind failed.[" << strerror(errorNum) << "]" << endl;
		return false;
	}

	return true;
}

