/***************************************************************************
				main.cpp
                -----------------------------------------
    begin      : 2012/10/12
    copyright  : (C) 2005 SolutionBox Inc.
    author     : Service 1 Team
    email      : svc1@solbox.com
    version    : 1.0

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

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

#include "Logger.h"
#include "Config.h"
#include "ProcessRename.h"
#include "ProcessStatus.h"
#include "Worker.h"
#include "GcmdConfig.h"

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 );
}


// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExit()
{
	// 프로세스 상태 모듈 종료 처리.
	CProcessStatus::Exit();

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


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

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

	// CHG 2015-09-22 webting
	// - #24369 usleep -> nanosleep 으로 변경 처리.
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );
	
	exit( EXIT_SUCCESS );
}


/// @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
	{
		int iResult;


		// Worker Process Main 함수 호출 및 종료처리.
		iResult = WorkerMain();

		ReadyToExit();

		exit( iResult );
	}
	else
	{
	    CProcessStatus::GetInstance()->SetWorkerPid((int)processId);
		// Parent Process => Logging
		LOG(LNOT, "Worker Process create success. PID[%d]" , processId);
		
		// 잠시 대기
		// CHG 2015-09-22 webting
		// - #24369 usleep -> nanosleep 으로 변경 처리.
		struct timespec sleep;
		sleep.tv_sec = 0;
		sleep.tv_nsec = 100000000;  // 0.1 sec
		nanosleep( &sleep, NULL );
	}
	
	return true;
}

static void SignalWorkerDead( 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, "Main Process[%d] exit by worker. Good Bye..", getpid());

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

					ReadyToExit();					

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

					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, "Main process error: SIG_CHLD receive but waitpid return error[%d][%s]", errorNum, strerror(errorNum));
	}

	return;
}
/// @brief Main 프로세스 signal 처리 설정을 위한 함수	
/// @return void	
void SetSignalMain()
{
	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);	/* 파이프 디스크립터 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
	sigaction( SIGHUP , &act, NULL);	/* Process를 기동시킨 관리자의 로그아웃시 발생 시그널 */
	sigaction( SIGINT , &act, NULL);	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	sigaction( SIGQUIT, &act, NULL);	/* 키보드에 의한  Abort 신호 처리 => ? */
	
	// Worker child process 종료에 대한 처리기 설정.
	act.sa_handler = SignalWorkerDead;
	sigaction( SIGCHLD, &act, NULL);
	
	// 사용자의 종료 또는 에러 관련 신호 처리.
	act.sa_handler = SignalMainTerminate;
	sigaction( SIGTERM, &act, NULL);	/* kill -TERM 에 의한 프로세스 종료시 */
	
	sigemptyset(&set);					/* 신호 처리기 처리 설정 위한 블록 해제 */
	sigprocmask(SIG_SETMASK, &set, NULL);
}



// 버전 정보 표시
void PrintVersion()
{
	fprintf( stderr, "\n" );
	fprintf( stderr, PROG_NAME " version: " PROG_VERSION "\n\n" );

	return;
}

// 사용방법 표시
void PrintUsage()
{
	fprintf( stderr, "\n" );
	fprintf( stderr, "Usage: " PROG_NAME " [-h] [-v] [-c {file}] [-s yyyymmdd] \n" );
	fprintf( stderr, "Options:                                              \n" );
	fprintf( stderr, "  -h                       Display help information   \n" );
	fprintf( stderr, "  -v                       Display version            \n" );
	fprintf( stderr, "  -c {file}                Use {file} as config file  \n" );
	fprintf( stderr, "  -s yyyymmdd              Set start date (ex: -s 20160518) \n" );
	
	fprintf( stderr, "\n" );
	fprintf( stderr, PROG_NAME " is Solbox Cloud Storage module.\n" );
	fprintf( stderr, "    - deleted content and folder check. \n" );
	fprintf( stderr, "    - delete physical files, folder that exceed the retention period.\n" );
	fprintf( stderr, "\n" );
	
	return;
}


// CHG 2016-05-18 유희곤
// - help 처리 관련 기능 추가 (#27605)
// - 유효하지 않는 기능, 변수 정리.
// - 전반적으로 main 프로세스 구조 정리...
int main( int argc, char * argv[] )
{
	std::string szConfFilename = DEFAULT_CONFIG_FILE;	
	std::string szStartPeriod = "";

	// linux 에서 프로세스를 Titile 변경을 지원을 위해서 argv 메모리에 저장
	argv = save_ps_display_args( argc, argv );
	
	
	// 전달 받은 옵션 여부 확인 및 처리
	if( argc >= 2 )
	{
		int opt;
		while( (opt = getopt( argc, argv, "hvc:s:" )) != -1 )	// 옵션 끝까지 파싱처리
		{
			switch( opt )
			{
			case 'h':
			case '?':	// 정의되지 않은 문자가 나타날 경우 getopt 에 자동반환. 도움말 표시 처리. 
				PrintUsage();
				return EXIT_SUCCESS;

			case 'v':
				PrintVersion();
				return EXIT_SUCCESS;

			case 'c':
				szConfFilename = optarg;
				fprintf( stderr, PROG_NAME " use user define conf[%s]\n", szConfFilename.c_str() );				
				break;

			case 's':
				szStartPeriod = optarg;				
				break;

			default:	// 본 조건절은 getopt 특성상 동작하지 않지만...업무 Flow 이해(?)를 위해 유지한다.
				fprintf( stderr, "Unknown options[%c] used. program terminated.\n\n", opt );
				return EXIT_FAILURE;
			}
		}
	}

	// 사용자 Start 지정 옵션 검증.
	if( szStartPeriod.length() > 0 )
	{
		// 사용자가 -s 옵션을 사용하여 start 날짜를 지정한 경우...

		// 날짜 길이값이 부정확한 경우...
		if( szStartPeriod.length() != 8 )
		{
			fprintf( stderr, PROG_NAME " terminated. -s %s format not vaild. use -s yyyymmdd format. \n\n", szStartPeriod.c_str() );
			return EXIT_SUCCESS;
		}

		// 설정값이 모두 숫자이어야 함.
		for( unsigned int i = 0; i < szStartPeriod.length(); i++ )
		{
			char ch = szStartPeriod.at( i );

			if( ch < 48 || ch > 57 )
			{
				fprintf( stderr, PROG_NAME " terminated. -s %s format not vaild. use -s yyyymmdd format. \n\n", szStartPeriod.c_str() );
				return EXIT_SUCCESS;
			}
		}

		fprintf( stderr, PROG_NAME " use uer define start time [%s]\n", szStartPeriod.c_str() );
	}
	


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

    // process status Singleton 객체 생성.
    CProcessStatus::Init();    
    CProcessStatus::GetInstance()->SetStartPeriod(szStartPeriod);
    

    // conf 파일 로드 및 항목별 검증 
	// - 내부적으로 설정 관련 값을 CProcessStatus 객체에 저장토록 되어 있음.
    CGcmdConfig objConf(szConfFilename, PROG_NAME, PROG_VERSION );
    if ( objConf.LoadConfig() == false )
    {
		fprintf( stderr, "[ERR] %s\n\n", objConf.GetErrorMessage().c_str() );
		return EXIT_FAILURE;
    }
	
	
	// Log 객체 생성
	if( CLogger::Init( PROG_NAME, CProcessStatus::GetInstance()->GetAppLogRoot(), LINF ) == false )
	{
		fprintf( stderr, "[ERR] Log module initialize failed.[%s]\n\n", CProcessStatus::GetInstance()->GetAppLogRoot().c_str() );
		return EXIT_FAILURE;
	}
	
	// Daemonize...
	if( daemon( 1, 0 ) == -1 ) // nochdir: true(작업 디렉토리 변경 안함), noclose: false (표준 입출력, 에러를 /dev/null 로 리디렉트 처리)
	{
		fprintf( stderr, "[ERR] Process daemonize failed.[%s]\n\n", strerror( errno ) );

		ReadyToExit();

		return EXIT_FAILURE;
	}

	// Signal 처리기 설정
	SetSignalMain();
	    
	// 기동시 설정 정보 관련 로깅
    CProcessStatus::GetInstance()->LoggingConf();


	// Log Level 재설정. -> conf 설정대로 변경 처리.
#ifdef _DEBUG_
	CLogger::GetInstance()->SetLogLevel( LDBG );
#else
	CLogger::GetInstance()->SetLogLevel( CProcessStatus::GetInstance()->GetLogLevel() );
#endif


	// Worker Process 생성
	if(	MakeProcessWorker() == false )
	{
		LOG(LERR, "Worker process make failed. => Main process exit. Good Bye.");
		cerr << "Worker process make failed. => Main process exit. Good Bye." << endl;

		ReadyToExit();

		return EXIT_FAILURE;
	}
	

	// Process Rename..
	set_ps_display( PROG_NAME": Main", false );


	// Main Process : 그냥 대기
	while( 1 )
	{
		// Main Process 는 할 일이 없다. 
		// => Worker 프로세스 종료시 SIGCHLD 신호로 인해 신호처리기에서 Worker Process 재성성 수행함.
		// => 따라서 그냥 시그널 대기
		pause();
	}
	
    // 다음의 코드는 Daemon 으로 동작하기 때문에 수행되지 않는다.
	ReadyToExit();
	    
	return EXIT_SUCCESS;
}
