/***************************************************************************
				main.cpp (cc_tsd)
                -----------------------------------------

	begin      : 2013/09/16
	copyright  : (C) 2005 Solbox Inc.
	author     : Development 1 Team
					- 2013/09/16
	email      : dev1@solbox.com
	version    : 3.2.0

	CopyRight(C) 2005 Solbox Inc. All Rights reserved.
	Redistribution and use in source and binary forms, with or with out
	modification, are not permitted in outside of Solbox Inc.
***************************************************************************/

#include <errno.h>
#include <iostream>
#include <string>
#include <signal.h>
#include <sys/wait.h>

#include "Logger.h"
#include "Config.h"
#include "Worker.h"
#include "Configs.h"
#include "InterfaceServer.h"

CInterfaceServer _iServer;
using namespace std;

/// @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 현재 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;
	}
}

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;
	}
	
	// Parent Process => exit
	if( processId != 0 )
		exit( EXIT_SUCCESS );
	else
		setsid();
	
	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);
	
	return 0;
}

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

	// Process 종료관련 작업 추가
	LOG(LWAR, "Guard Process[%d] exit job end. Good Bye..", getpid());
	
	usleep(500000);
	exit( EXIT_SUCCESS );
}

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

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

	// Log Directory 상에 Core 파일 생성처리.
	chdir( CMyConfig::GetInstance()->GetAppLogRoot() );

	signal( nSignalNumber, SIG_DFL );

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


/// @brief Worker Process 생성(fork) 처리 함수
/// @return 생성 성공시 true, 실패시에는 false 를 반환.
bool MakeProcessWorker()
{
	pid_t processId;
    
	// Worker 프로세스 fork
	processId = fork();
	if( processId < 0 ) // Fork fail
	{
		int errorNum = errno;
		LOG(LERR, "Worker Process create failed. [%d][%s]", errorNum, strerror(errorNum) );
		return false;
	}
	else if( processId == 0 ) // Child Process => Worker Process
	{
		// Worker Process Main 함수 호출 및 종료처리.
		WorkerMain();
		exit( EXIT_SUCCESS );
	}
	else
	{
		// Parent Process => Logging
		LOG(LNOT, "Worker Process create success. PID[%d]" , processId);
		// 잠시 대기
		usleep(100000);
	}
	
	return true;
}

static void SigChldGuard( 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 );

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

			if( WIFEXITED( nKillStatus ) )
			{
				if( WEXITSTATUS( nKillStatus ) == EXIT_FAILURE )
				{
					LOG(LERR, "Worker Process[%d] initilaize failed.", killPid );
					LOG(LERR, "Guard Process[%d] exit by worker. Good Bye..", getpid());

					cerr << "[error] " << PROG_NAME << ": Process exit by worker process initialize failed. check log file." << endl;

					CLogger::Exit();
					CMyConfig::Exit();

					usleep(500000);
					exit( EXIT_SUCCESS );
				}
			}			
		}

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

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

	return;
}
/// @brief Guard프로세스 signal 처리 설정을 위한 함수	
/// @return void	
void SetSignalGuard()
{
	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 = SigChldGuard;
	sigaction( SIGCHLD, &act, NULL);

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

	/* Core 관련 signal 처리 : 오류 처리 및 Debug(core dump) 목적 */
	act.sa_handler = SigCoreGuard;
	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);
}


int main( int argc, char * argv[] )
{
	std::string szConfFilename = DEFAULT_CONFIG_FILE;
	std::string szProgramName = PROG_NAME;
	std::string szProgVer = PROG_VERSION;
	std::string szStartPeriod = "";
    
	// Version 정보 표시
	if( argc == 2 && strcmp( argv[1], "-v") == 0)
	{
		cerr << "[info] " << PROG_NAME << " Version : " << PROG_VERSION << endl;
		return EXIT_SUCCESS;
	}
    
	// 프로그램 중복 실행 체크
	if( IsCurrentProcessRun() == true )
	{
		cerr << "[warning] Process[" << PROG_NAME << "] is already running...." << endl;
		return EXIT_SUCCESS;
	}
    
	//initialized Config object
	if(CMyConfig::Init( PROG_NAME, szConfFilename) == false)
	{
		cerr << "[error] Failed to initialize the config object." << endl;
		return EXIT_FAILURE;
	}

	// load config
	if( CMyConfig::GetInstance()->LoadConf() == false )
	{
		cerr << "[error] Config load error." << CMyConfig::GetInstance()->GetErrMessage() << endl;
		return EXIT_FAILURE;
	}

	if (CMyConfig::GetInstance()->CheckValue() == false)
	{
		cerr << "[error] Config load error." << CMyConfig::GetInstance()->GetErrMessage() << endl;
		return EXIT_FAILURE;
	}
    
	// initialized Log object
	if( CLogger::Init( PROG_NAME, CMyConfig::GetInstance()->GetAppLogRoot(), 
		CMyConfig::GetInstance()->GetAppLogLevel() ) == false )
	{
		cerr << "[error] Failed to initialize the log object." << endl;
		return EXIT_FAILURE;
	}	
    
    // 프로그램 Daemonize
	if( MakeDaemon() != 0 )
	{
		LOG(LERR, "Process[%s] Daemonize failed.", PROG_NAME);
		cerr << "[error] Process[" << PROG_NAME << "] Daemonize failed." << endl;
		return EXIT_FAILURE;
	}
	
	// Guard 프로세스의 Signal 처리기 설정
	SetSignalGuard();
	
	// Interface server socket bind
	if(_iServer.bind(80) == 0)
	{
		// Worker Process 생성
		if(	MakeProcessWorker() == false )
		{
			LOG(LERR, "Worker Process make failed. => Guard Process exit. Goob Bye..");
			cerr << "Worker Process make failed. => Guard Process exit. Goob Bye.." << endl;
			return EXIT_FAILURE;
		}
	}
	else
	{
		_LOG(LERR, "Interface server socket bind failed");
		return EXIT_FAILURE;
	}
	
	// Guard Process : 그냥 대기
#if defined(__FreeBSD__)
	setproctitle( "Main(Guard Procss) [monitor worker process]" );
#endif
	
	while( 1 )
	{
		// Guard Process 는 할 일이 없다. 
		// => Worker 프로세스 종료시 SIGCHLD 신호로 인해 신호처리기에서 Worker Process 재성성 수행함.
		// => 따라서 그냥 시그널 대기
		pause();
	}
	
    // 다음의 코드는 Daemon 으로 동작하기 때문에 수행되지 않는다.
    CLogger::Exit();
	CMyConfig::Exit();

	return EXIT_SUCCESS;
	

}
