#include "fhs_raidmond.h"
#include "GetConfig.h"
#include "Child.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>


/// config object initialize 
struct st_config g_envConfig;

/// daemon flag
static bool _isdaemon = true;

/// @brief Program version 출력 함수.
/// @return void
static void ShowVersion(void)
{
	cerr << "[info] " << PROG_NAME << " Version :" << PROG_VERSION << endl;
}


/// @brief Program version 사용 방법 출력 함수.
/// @return void
static void ShowUsages(void) 
{
	ShowVersion();
	cerr << "usage: "  << PROG_NAME << " [-vhcD]" << endl;
	cerr << "options are: " << endl;
	cerr << "-v : print current version" << endl;
	cerr << "-c <path> : Log Diretory" << endl;
	cerr << "-D : running console mode" << endl;
	cerr << "-h : help " << endl;
}

/// @brief Child 프로세스 생성(fork) 함수 
/// @return void	
static void MakeChild(void)
{
	pid_t processId;
	// Child 프로세스 fork
	processId = fork();
	if( processId < 0 ) // Fork fail
	{
		int errorNum = errno;
		LOG(LERR, "Child Process create failed. [%d][%s]", errorNum, strerror(errorNum) );
		return;
	}
	else if( processId == 0 ) // Parent Process => Child Process
	{
		// Child Process Main 함수 호출 및 종료처리.
		ChildMain();
		exit( EXIT_SUCCESS );
	}
	else
	{
		// Parent Process => Logging
		_LOG(LNOT, "Child Process create success. PID[%d]" , processId);
		// 잠시 대기
		usleep(100000);
	}
}

/// @brief Parent Process 에서 child Process 가 종료된 경우 시그널 처리를 위한 함수
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void sigchld_hdl( int nSignalNumber )
{
	pid_t killPid;
	int nKillStatus;
	while( ( killPid = waitpid( -1, &nKillStatus, WNOHANG ) ) > 0 )
	{
		// 자식 프로세스가 Signal 에 의해 종료되었는지 검사.
		if( WIFSIGNALED( nKillStatus ) )
		{
			LOG(LWAR,  "Child process[%d] killed by signal[%d]", killPid, WTERMSIG( nKillStatus ) );
		}
		else
		{
			LOG(LWAR,  "Child process[%d] killed. Not signal", killPid );
			// Child 프로세스가 EXIT_FAILURE 반환 ( 초기화 실패시 )
			// 해당 내역을 화면 및 로그 상에 출력하고 
			// 자식 프로세스를 재생성 처리하지 않는다.
			if( WIFEXITED( nKillStatus ) )
			{
				if( WEXITSTATUS( nKillStatus ) == EXIT_FAILURE )
				{
					LOG(LERR, "Child Process[%d] initilaize failed.", killPid );
					LOG(LERR,  "Parent Process[%d] exit by Child. Good Bye..", getpid());
					cerr << "[error] " << PROG_NAME << ": Process exit by Child process initialize failed. check log file." << endl;
					usleep(500000);
					exit( EXIT_FAILURE );
				}
			}			
		}
		// Child Process 재생성 처리.
		MakeChild();
	}
	// 오류 발생시 해당 내역 로깅
	if( killPid < 0 )
	{
		int errorNum = errno;
		LOG(LERR, "Parent process error: SIG_CHLD receive but waitpid return error[%d][%s]", errorNum, strerror(errorNum));
	}
	return;
}

/// @brief Parent Process 종료 Signal 을 전달받은 경우 이를 처리하기 위한 함수.
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SigTermParent( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		LOG(LWAR,  "Parent Process[%d] exit job start by user signal [SIGTERM]", getpid() );
	}		
	else
	{
		LOG(LWAR,  "Parent Process[%d] exit job start by abnormal signal [%d]", getpid(), nSignalNumber );
	}
	// Process 종료관련 작업 추가
	LOG(LWAR,  "Parent Process[%d] exit job end. Good Bye..", getpid());
	// KILL - Child
	kill(0, SIGTERM);
	// waitpid
	while(waitpid(-1, NULL, WNOHANG) > 0);

	usleep(500000);
	exit( EXIT_SUCCESS );
}

/// @brief Parent Process 에서 Core 생성 관련 Signal 을 받은 경우 이를 처리하기 위한 함수.
/// @param nSignalNumber [in] 발생한 시그널 Number
/// @return void
static void SigCoreParent( int nSignalNumber )
{
	LOG(LERR,  "Parent 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 종료관련 작업 추가

	// Log Directory 상에 Core 파일 생성처리.
	chdir( g_envConfig.szLogDir.c_str() );
	signal( nSignalNumber, SIG_DFL );
	// Core dump 생성을 위한 신호 발생처리.=> ?
	raise( nSignalNumber );
}

/// @brief Parent 프로세스 signal 처리 설정을 위한 함수	
/// @return void	
static void SetSignalParent()
{
	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( SIGQUIT, &act, NULL);	/* 키보드에 의한  Abort 신호 처리 => ? */
	
	if(_isdaemon)
	{
		sigaction( SIGINT , &act, NULL);	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	}
	else 
	{
		act.sa_handler = SigTermParent;
		sigaction( SIGINT, &act, NULL);	/*  ^C 키를 누른 경우 받는 신호 => kill -TERM 과 동일한 처리함 */
	}
	
	// Child Process 종료에 대한 처리기 설정.
	act.sa_handler = sigchld_hdl;
	sigaction( SIGCHLD, &act, NULL);
	/* 각종 에러나 사용자의 종료 신호 처리 */
	act.sa_handler = SigTermParent;
	sigaction( SIGTERM, &act, NULL);	/* kill -TERM 에 의한 프로세스 종료시 */
	/* Core 관련 signal 처리 : 오류 처리 및 Debug(core dump) 목적 */
	// dadamin : 팀 기준 적용
	//act.sa_handler = SigCoreParent;
	//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);
}
// daemon 생성 함수
static bool daemon_create(bool bcreate) 
{
	// set parent signal
	SetSignalParent();

	// set mask
	umask(0);

	// daemon	
	if(bcreate && daemon(false, false) != 0 ) // after this line, parent's pid will be changed.
	{
		cerr << "[error] Daemonize Failed.(errno : " << errno << ")" << endl;
		return false;
	}	
	
	return true;
}
/// @brief std 상에 trim 함수가 없어서 직접 구현 아니면 boost/algorithm/string.hpp 상의 boost::trim 함수 사용
/// @return void
static 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 현재 Process가 기동중인지 여부를 판단하기 위한 함수( 프로세스 중복 실행 체크)
/// @return 이미 해당 프로세스가 기동 중인 경우 true 반환, 그렇지 않으면 false 반환.
static 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;
	}
}
// main function
int main( int argc, char * argv[] )
{
	string cofigpath = DEFAULT_CONFIG_FILE;
	// parse command line arguments
	int	o;
	while ((o = getopt(argc, argv, "vhDc:")) >= 0) 
	{
		switch (o) 
		{
		case 'h': // help
			ShowUsages();
			return EXIT_SUCCESS;
			break;
		case 'v': // version
			ShowVersion();
			return EXIT_SUCCESS;
			break;
		case 'D': // consol mode
			_isdaemon = false;
			cerr << "Runnig Console mode." << endl;
			break;
		case 'c': // config file
			cofigpath = optarg;
			break;
		}
	}

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

	// load config 
	string szErrorMessage;
	if(!GetConfig( cofigpath, g_envConfig, szErrorMessage ) ) 
	{
		cerr << "[error] Process[" << PROG_NAME << "]: " << szErrorMessage  << endl;
		return EXIT_FAILURE;
	}

	// init log
	if( CLogger::Init( PROG_NAME, g_envConfig.szLogDir.c_str(), g_envConfig.nLogLevel ) == false )
	{
		cerr << "[error] Process[" << PROG_NAME << "]: Log init failed." << endl;
		return EXIT_FAILURE;
	}


	// Global log object set 
	
	// daemon create	
	if(!daemon_create(_isdaemon)) 
		return EXIT_FAILURE;

	// Process 기동 관련 정보 로깅 
	_LOG(LINF, "***********************************************************" );
	_LOG(LINF, " %s (FHS RAID Monitor Daemon) Start. Version: %s", PROG_NAME, PROG_VERSION );
	_LOG(LINF, "***********************************************************" );
	_LOG(LINF, "Config file         : %s", cofigpath.c_str() );
	_LOG(LINF, "Log Path            : %s/%s", g_envConfig.szLogDir.c_str(), PROG_NAME );
	_LOG(LINF, "Logging Level       : %d", g_envConfig.nLogLevel );	
	_LOG(LINF, "health check term   : %d", g_envConfig.nCheckTerm );
	_LOG(LINF, "report thread port  : %d", g_envConfig.nReportPort );
	_LOG(LINF, "event level         : %d", g_envConfig.nEventLevel );
	_LOG(LINF, "sms send host       : %s", g_envConfig.szSMSHost.c_str() );
	_LOG(LINF, "sms send port       : %d", g_envConfig.nSMSPort);
	_LOG(LINF, "sms send file       : %s", g_envConfig.szSMSSendfile.c_str() );
	_LOG(LINF, "***********************************************************" );
	
	// fork child
	MakeChild();

	// set process title
#if defined(__FreeBSD__)
	setproctitle( "Parent [monitor Child process]" );
#endif
	// wait
	while( 1 )
	{
		pause();
	}

	return EXIT_SUCCESS;
}

