#include "Main.h"
#include "ProcessConfig.h"
#include "Logger.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <time.h>
#include <sys/stat.h>

#include <sqlite3.h>

#include <iostream>

#include "MetaCopy.h"
#include "IntegrityCheck.h"
#include "GarbageClean.h"


// FHS 의 업로드 방지 상태를 판단하기 위한 값.
#define FILE_SERVICE_OUT     "/user/service/fhs.out"
#define FILE_SERVICE_RONLY   "/user/service/fhs.ronly"
#define FILE_VMASD_DISABLE   "/user/service/vmasd.disable"


// sqlite memory db 사용 관련 전역 객체..
// - 시그널 처리 관련 종료시 삭제 처리 목적으로 전역변수 선언.
sqlite3 * g_pLocalDb = NULL;


int main( int argc, char * argv[] )
{
	// 기본 변수.
	std::string strConfigFileName = DEFAULT_CONFIG_FILE;   // conf 관련 처리를 위한 변수.
	std::string szErrorMessage;                            // 에러 메시지 관련 처리를 위한 변수.
	
	
	// 1. 전달 받은 옵션 여부 확인 및 처리
	// - 무조건 옵션으로 동작 방식을 결정하도록 한다. ( 무옵션은 오류로 처리)

	// 인자로 전달받은 정보를 저장하기 위한 변수.
	bool bRunIntegrityCheck		= false;
	bool bRunGarbageClean		= false;
	bool bGarbageCleanSlow		= false;
	bool bGarbageForceDelete	= false;
	bool bBatchRun				= false;

	std::string strWorkPath;
	strWorkPath.clear();

	if( argc >= 2 )
	{
		int opt;
		while( ( opt = getopt( argc, argv, "hvbficsp:" ) ) != -1 )	// 옵션 끝까지 파싱처리
		{
			switch( opt )
			{
				case 'h':
				case '?':	// 정의되지 않은 문자가 나타날 경우 getopt 에 자동반환. 도움말 표시 처리. 
					PrintUsage();
					return EXIT_SUCCESS;

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

				case 's':
					bGarbageCleanSlow = true;
					break;
				
				case 'b':
					bBatchRun = true;
					break;
				
				case 'f':
					bGarbageForceDelete = true;
					break;

				case 'i':
					bRunIntegrityCheck = true;					
					break;

				case 'c':
					bRunGarbageClean = true;
					break;

				case 'p':
					strWorkPath = optarg;					
					break;

				default:	// 본 조건절은 getopt 특성상 동작하지 않지만...업무 Flow 이해(?)를 위해 유지한다.
					fprintf( stderr, "Unkonwn options[%c] used. program terminated.\n", opt );
					return EXIT_FAILURE;
			}
		}
	}
	else
	{
		// 옵션값이 설정되지 않은 경우....
		fprintf( stderr, PROG_NAME ": not enough option.\n" );
		PrintUsage();
		return EXIT_FAILURE;
	}
	
	// 2. 프로세스 중복 실행 여부 검사 
	// - 검사 대상 경로 처리 관련 복잡성 문제로 인해 중복 실행을 허용하지 않는다.
	if( IsCurrentProcessRun() == true )
	{
		fprintf( stderr, "[warning] Process [" PROG_NAME "] is already running....\n" );
		return EXIT_FAILURE;
	}
	
	// 3. conf 로딩 및 검사 
	// - fhs.conf 가 존재해야 하며....
	// - 전달받은 옵션에 대한 정합성 검증을 위해... conf 를 미리 로딩 처리한다.
	if( CProcessConfig::Init( PROG_NAME, strConfigFileName, szErrorMessage ) == false )
	{
		fprintf( stderr, PROG_NAME ": conf error [%s]\n\n", szErrorMessage.c_str() );
		return EXIT_FAILURE;
	}
		
	// 4. 전달받은 옵션에 대한 검증 및 conf 저장 처리.
	// - 1) integrity chek, garbage clean 작업 중 최소 1개 작업 선택 필요 (아무 작업도 선택하지 않은 경우.. 오류 처리 )
	if( bRunIntegrityCheck == false && bRunGarbageClean == false )
	{
		fprintf( stderr, PROG_NAME ": not enough option. select -i or -c \n" );
		PrintUsage();
		return EXIT_FAILURE;
	}
	else
	{
		// conf 객체에 저장 처리한다.
		CProcessConfig::GetInstance()->SetIntegrityCheck( bRunIntegrityCheck );
		CProcessConfig::GetInstance()->SetGarbageClean( bRunGarbageClean );
		CProcessConfig::GetInstance()->SetGarbageCleanSlow( bGarbageCleanSlow );
		CProcessConfig::GetInstance()->SetGarbageForceDelete( bGarbageForceDelete );
	}
	
	// - 2) 작업 대상 경로를 전달받은 경우.. 해당 경로를 저장 처리한다.
	//      저장 처리 함수에서 유효성 검증을 수행하며.. 오류 발생시 해당 내역을 출력 처리한다.
	if( strWorkPath.empty() == false )
	{
		// conf 객체에 저장 처리한다.
		if( CProcessConfig::GetInstance()->SetJobRoot( strWorkPath, szErrorMessage ) == false )
		{
			// 유효성 검증에서 오류가 발생한 경우...
			fprintf( stderr, PROG_NAME ": %s \n", szErrorMessage.c_str());
			PrintUsage();
			return EXIT_FAILURE;
		}	
	}

	// 5. 장비의 hostname 정보 추출.
	char tempBuffer[256];
	if( gethostname( tempBuffer, 255 ) == 0 )
	{
		// 정보 추출 성공시
		if( CProcessConfig::GetInstance()->SetHostname( tempBuffer ) == false )
		{
			fprintf( stderr, PROG_NAME ": hostname not valid. [%s]\n", tempBuffer );
			return EXIT_FAILURE;
		}
	}
	else
	{
		int errorNum = errno;
		fprintf( stderr, PROG_NAME ": hostname not valid. [%d][%s]\n", errorNum, strerror( errorNum ) );
		return EXIT_FAILURE;
	}
	
	// 6. 업로드 방지 상태인지 확인...
	// - 업로드 방지 상태가 아닌 경우 아래와 같은 문제점 발생.
	// - 정합성 검증
	//   -- 업로드 중인 파일에 대해 size mismatch 상황 발생 가능.
	// - garbage clean
	//   -- meta copy 후 local meta 정보로 check 를 수행하므로..신규 업로드 파일에 대해서 copy 된 meta 에 존재하지 않아... 삭제 처리될 가능성 존재.
	//   -- repliation 생성시.. 장비가 복제 완료 후 DB 에 insert 처리하므로...  garbage 로 판단될 가능성 존재.
	// - 따라서 업로드 방지 상태에서 작업 수행 필요.
	if( IsUploadDisable() == false )
	{
		// 해당 함수에서 관련 내역 출력 처리 했으므로... 그냥 종료 처리만 수행.
		return EXIT_FAILURE;
	}
	
	
	// 7. 최종 사용자 확인 처리.
	// - batch 작업을 위한 -b 옵션을 사용한 경우에는 사용자 확인 없이 그냥 진행 처리.
	if( bBatchRun == false )
	{
		fprintf( stderr, "\nRun with the following options? \n" );
		fprintf( stderr, "  hostname                 : %s \n", CProcessConfig::GetInstance()->GetHostname() );
		fprintf( stderr, "  content integrity check  : %s \n", ( CProcessConfig::GetInstance()->IsRunIntegrityCheck() == true ? "yes" : "no" ) );
		
		fprintf( stderr, "  garbage content clean    : %s \n", ( CProcessConfig::GetInstance()->IsRunGarbageClean() == true ? "yes" : "no" ) );
		if( CProcessConfig::GetInstance()->IsRunGarbageClean() == true )
		{			
			fprintf( stderr, "    - unlink speed         : %s \n", ( CProcessConfig::GetInstance()->IsSlowGarbageClean() == true ? "slow" : "normal" ) );
			fprintf( stderr, "    - unlink mode          : %s \n", ( CProcessConfig::GetInstance()->IsForceGarbageDelete() == true ? "force" : "normal" ) );
		}
		fprintf( stderr, "  target directory path    : %s %s\n"
				 , CProcessConfig::GetInstance()->GetJobRoot()
				 , ( CProcessConfig::GetInstance()->IsUserDefineJobRoot() == true ? "(user)" : "(default)" ) );
		
		// garbage clean 작업을 진행할 경우.. disk busy check 하라는 경고 문고 추가.
		if( CProcessConfig::GetInstance()->IsRunGarbageClean() == true )
		{
			fprintf( stderr, "\nWarning : garbage clean job is caused disk busy. Check disk busy before run.\n" );
		}
		
		fprintf( stderr, "\nIs ok? [y/N] : " );

		char chUserYN;
		chUserYN = getchar();

		if( chUserYN != 'y' )
		{
			// 중지 시키는 경우.
			fprintf( stderr, "\n" PROG_NAME " exit. good bye.\n\n" );
			return EXIT_SUCCESS;
		}
	}
		
	// Log 객체 생성 및 초기화
	if( CLogger::Init( PROG_NAME, CProcessConfig::GetInstance()->GetLogPath(), LINF ) == false )
	{
		fprintf( stderr, "[ERR] Log module initialize failed.[%s]\n\n", CProcessConfig::GetInstance()->GetLogPath() );
		return EXIT_FAILURE;
	}
	else
	{
		fprintf( stderr, "\n" PROG_NAME " running with daemon. use log file for processing check [%s/%s]\n\n", CProcessConfig::GetInstance()->GetLogPath(), PROG_NAME );
	}
	
	// Daemonize...
	if( daemon( 1, 0 ) == -1 ) // nochdir: true(작업 디렉토리 변경 안함), noclose: false (표준 입출력, 에러를 /dev/null 로 리디렉트 처리)
	{
		fprintf( stderr, "[ERR] Process daemonize failed.[%s]\n\n", strerror( errno ) );
		return EXIT_FAILURE;
	}
	
	
	// signal 처리 설정
	SetSignalMain();
	

	// Process 기동 관련 정보 기록 -> Log
	std::vector<std::string> vecTemp;
	std::vector< std::string >::const_iterator it;
	_LOG( LINF, "***********************************************************" );
	_LOG( LINF, " %s Start. Version: %s", PROG_NAME, PROG_VERSION );
	_LOG( LINF, "***********************************************************" );
	_LOG( LINF, "Config                      : %s", strConfigFileName.c_str() );
	_LOG( LINF, "Log                         : %s/%s", CProcessConfig::GetInstance()->GetLogPath(), PROG_NAME );
	_LOG( LINF, "Log level                   : %d", CProcessConfig::GetInstance()->GetLogLevel() );	
	_LOG( LINF, "RCDB                        : %s %u %s %s", CProcessConfig::GetInstance()->GetRcdbIp()
														, CProcessConfig::GetInstance()->GetRcdbPort()
														, CProcessConfig::GetInstance()->GetRcdbName() 
														, CProcessConfig::GetInstance()->GetRcdbAcct() );

	_LOG( LINF, "Command option");
	_LOG( LINF, "    job root path           : %s %s", CProcessConfig::GetInstance()->GetJobRoot()
												     , ( CProcessConfig::GetInstance()->IsUserDefineJobRoot() == true ? "(user)" : "(default)" ) );
	_LOG( LINF, "    integrity check         : %s", ( CProcessConfig::GetInstance()->IsRunIntegrityCheck() == true ? "yes" : "no" ) );
	_LOG( LINF, "    garbage clean           : %s", ( CProcessConfig::GetInstance()->IsRunGarbageClean() == true ? "yes" : "no" ) );
	if( CProcessConfig::GetInstance()->IsRunGarbageClean() == true )
	{
		_LOG( LINF, "        garbage clean speed : %s", ( CProcessConfig::GetInstance()->IsSlowGarbageClean() == true ? "slow" : "normal" ) );
		_LOG( LINF, "        garbage clean mode  : %s", ( CProcessConfig::GetInstance()->IsForceGarbageDelete() == true ? "force" : "normal" ) );
	}
	_LOG( LINF, "    last user confirm       : %s", ( bBatchRun == false ? "yes" : "batch mode" ) );
	_LOG( LINF, "Local" );
	_LOG( LINF, "    hostname                : %s", CProcessConfig::GetInstance()->GetHostname());

	_LOG( LINF, "***********************************************************" );
	

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


	// 0. sqlite memory db 객체 생성.
	int nResult = sqlite3_open( ":memory:", &g_pLocalDb );
	if( nResult != SQLITE_OK )
	{
		LOG( LERR, "sqlite3 memory db open failed. [%d][%s]", nResult, sqlite3_errmsg( g_pLocalDb ) );
		
		_LOG( LERR, "Process exit by sqlite local db create error. Job not completed.. " );
		_LOG( LINF, "***********************************************************" );

		ReadyToExit();
		return EXIT_FAILURE;
	}
	else
	{
		// sqlite memory DB 생성이 정상 수행 된 경우.
		_LOG( LINF, "sqlite memory db open success.");
	}
	

	// 1. RCDB 로 부터 Meta 정보 조회
	// - 해당 객체는 작업 완료 후.. 계속 메모리에 유지할 필요가 없으므로.. 포인터 객체로 처리
	setproctitle( "meta copy" );

	CMetaCopy * pMeta = new CMetaCopy( g_pLocalDb );
	unsigned long long ullMetaTotalCount = 0;

	if( pMeta == NULL )
	{
		_LOG( LERR, "Process exit by meta copy create error. Job not completed.. " );
		_LOG( LINF, "***********************************************************" );
		ReadyToExit();
		return EXIT_FAILURE;
	}

	if( pMeta->Init() == false )
	{
		// 관련 오류 발생 로깅은 해당 모듈에서 했으므로... 생략
		_LOG( LERR, "Process exit by meta copy init error. Job not completed.. ");
		_LOG( LINF, "***********************************************************" );

		delete pMeta;
		pMeta = NULL;

		ReadyToExit();
		return EXIT_FAILURE;
	}
		
	if( pMeta->Start() == false )
	{
		// 관련 오류 발생 로깅은 해당 모듈에서 했으므로... 생략
		_LOG( LERR, "Process exit by meta copy start error. Job not completed.. ");
		_LOG( LINF, "***********************************************************" );

		delete pMeta;
		pMeta = NULL;

		ReadyToExit();
		return EXIT_FAILURE;
	}


	// 조회된 Meta 의 최종 count 정보를 가져와 저장 처리한다.
	ullMetaTotalCount = pMeta->GetTotalCount();
	

	// Meta copy 작업이 완료된 경우.. 해당 모듈은 더 이상 필요 없으므로.. 삭제 처리.
	delete pMeta;
	pMeta = NULL;
	
	
	
	// 2. Content integrity check 수행
	if( CProcessConfig::GetInstance()->IsRunIntegrityCheck() == true )
	{
		setproctitle( "integrity check" );

		// 앞의 Meta Copy 작업에서... 조회된 Content 가 없는 경우... 
		// - 그냥 처리 완료된 것으로 한다.
		if( ullMetaTotalCount == 0 )
		{
			_LOG( LINF, "[%s] Available content not exist. Integrity check complete.", CProcessConfig::GetInstance()->GetJobRoot() );
		}
		else
		{
			// 정합성 검증 처리 모듈 생성.
			CIntegrityCheck * pIntegrityCheck = new CIntegrityCheck( g_pLocalDb );
			if( pIntegrityCheck == NULL )
			{
				_LOG( LERR, "Process exit by integrity check create error. Job not completed.. " );
				_LOG( LINF, "***********************************************************" );

				ReadyToExit();
				return EXIT_FAILURE;
			}

			// 정합성 검증 처리 모듈 초기화
			if( pIntegrityCheck->Init() == false )
			{
				// 관련 오류 발생 로깅은 해당 모듈에서 했으므로... 생략
				_LOG( LERR, "Process exit by integrity check init error. Job not completed.. " );
				_LOG( LINF, "***********************************************************" );

				delete pIntegrityCheck;
				pIntegrityCheck = NULL;

				ReadyToExit();
				return EXIT_FAILURE;
			}

			// 정합성 검증 처리 시작.
			if( pIntegrityCheck->Start() == false )
			{
				// 관련 오류 발생 로깅은 해당 모듈에서 했으므로... 생략
				_LOG( LERR, "Process exit by integrity check start error. Job not completed.. " );
				_LOG( LINF, "***********************************************************" );

				delete pIntegrityCheck;
				pIntegrityCheck = NULL;

				ReadyToExit();
				return EXIT_FAILURE;
			}

			// Integrity Check 작업이 완료된 경우.. 해당 모듈은 더 이상 필요 없으므로.. 삭제 처리.
			delete pIntegrityCheck;
			pIntegrityCheck = NULL;
		}
	}
	

	// 3. Garbage Content Clean 수행
	if( CProcessConfig::GetInstance()->IsRunGarbageClean() == true )
	{
		setproctitle( "garbage clean" );
		
		// 앞의 Meta Copy 작업에서... 조회된 Content 가 없는 경우... 
		// - DB 작업 등으로 인해 깡통 DB 인 상태에서 조회가 수행된 경우.....(예전 파일바다 장애)
		//   => FHS 에 존재하는 모든 content 가 삭제될 수 있어 위험함.
		// - 실제 RCDB 상에 content 가 없는 경우....
		//   => RCDB 에 최종 확인 작업을 수행할 경우.. RCDB 부하만 높아짐.
		//   => 특정 기간 이상 된 Content만 삭제 처리시.... 남아 있게 되는 content 존재.
		// 따라서 이럴 경우... 운영자가 직접 확인 후 폴더를 통째로 삭제 처리하는 것이 깔끔하다.
		
		if( ullMetaTotalCount == 0 )
		{
			_LOG( LWAR, "[%s][%s] Available content not exist. Garbage clean not run. Check RCDB and Clean manual."
				  , CProcessConfig::GetInstance()->GetHostname(), CProcessConfig::GetInstance()->GetJobRoot() );
		}
		else
		{			
			// Garbage content clean 처리 모듈 생성.
			CGarbageClean * pGarbageClean = new CGarbageClean( g_pLocalDb );
			if( pGarbageClean == NULL )
			{
				_LOG( LERR, "Process exit by garbage clean create error. Job not completed.. " );
				_LOG( LINF, "***********************************************************" );

				ReadyToExit();
				return EXIT_FAILURE;
			}

			// garbage clean 처리 모듈 초기화
			if( pGarbageClean->Init() == false )
			{
				// 관련 오류 발생 로깅은 해당 모듈에서 했으므로... 생략
				_LOG( LERR, "Process exit by garbage clean init error. Job not completed.. " );
				_LOG( LINF, "***********************************************************" );

				delete pGarbageClean;
				pGarbageClean = NULL;

				ReadyToExit();
				return EXIT_FAILURE;
			}

			// garbage clean 처리 시작.
			// - unlink slow 모드 인지 여부를 인자로 전달.
			// - unlink force 옵션을 인자로 전달.
			bGarbageCleanSlow = CProcessConfig::GetInstance()->IsSlowGarbageClean();
			bGarbageForceDelete = CProcessConfig::GetInstance()->IsForceGarbageDelete();

			if( pGarbageClean->Start( bGarbageCleanSlow, bGarbageForceDelete ) == false )
			{
				// 관련 오류 발생 로깅은 해당 모듈에서 했으므로... 생략
				_LOG( LERR, "Process exit by garbage clean start error. Job not completed.. " );
				_LOG( LINF, "***********************************************************" );

				delete pGarbageClean;
				pGarbageClean = NULL;

				ReadyToExit();
				return EXIT_FAILURE;
			}

			// garbage clean 작업이 완료된 경우.. 해당 모듈은 더 이상 필요 없으므로.. 삭제 처리.
			delete pGarbageClean;
			pGarbageClean = NULL;			
		}

	}
	

	// 작업 완료 후 종료시.. 로깅 처리.
	_LOG( LINF, "Job completed. Good bye...");
	_LOG( LINF, "***********************************************************" );
	
	ReadyToExit();
	return EXIT_SUCCESS;
}


// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
void ReadyToExit()
{
	// sqlite memory db 를 삭제한다.
	if( g_pLocalDb != NULL )
	{
		sqlite3_close( g_pLocalDb );
		g_pLocalDb = NULL;
	}
	
	// Logger 객체 종료 처리.
	CLogger::Exit();

	return;
}


// 사용방법 표시
void PrintUsage()
{
	fprintf( stderr, "\n" );
	fprintf( stderr, "Usage: " PROG_NAME " [OPTION] \n" );
	fprintf( stderr, "Options:                                              \n" );

	fprintf( stderr, "  -i                       Execute content integrity check \n" );
	fprintf( stderr, "  -c                       Execute garbage content clean \n" );
	fprintf( stderr, "  -p {path}                Target path set for integrity check or garbage clean (ex: /stg/node0 )\n" );
	fprintf( stderr, "                           fhs.conf [FILE_STORAGE_ROOT] value is default.\n" );
	fprintf( stderr, "  -s                       Very slow unlink when execute garbage clean. but need many time.\n" );
	fprintf( stderr, "  -f                       Force garbage unlink mode. never access time check before garbage content unlink.\n");
	fprintf( stderr, "  -b                       Batch run mode. never print confirm prompt before job execute.\n" );
	fprintf( stderr, "  -h                       Display help information   \n" );
	fprintf( stderr, "  -v                       Display version            \n" );
	
	fprintf( stderr, "\n" );
	fprintf( stderr, PROG_NAME " is Solbox Cloud Storage module.\n" );
	fprintf( stderr, "    - FHS saved content integrity check based RCDB.\n" );
	fprintf( stderr, "    - Garbage content find & unlink for storage free capacity.\n\n" );

	return;
}


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

	return;
}


/// @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 )
	{
		std::cerr << "[error] Process duplication check failed.[popen error][" << strerror( errno ) << "]" << std::endl;

		// 오류 발생시 true 반환하여 프로세스 실행 방지처리
		return true;
	}
	else
	{
		memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
		while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
		{
			std::string tempPid( tempBuffer );
			Trim( tempPid );

			if( atoi( tempPid.c_str() ) != getpid() )
			{
				std::cout << "[info] Process duplication found. pid[" << atoi( tempPid.c_str() ) << "]" << std::endl;

				bRun = true;
				break;
			}
		}

		pclose( fd );
		return bRun;
	}
}


// 본 FHS 장비가 업로드 방지 상태인지 여부를 반환.
// 업로드 방지 상태인 경우 true, 업로드 가능 상태인 경우.. false 를 반환한다.
bool IsUploadDisable()
{
	// 업로드 방지 상태 여부 확인.
	// 1. vmasd 가 동작 중인 경우... /user/service/vmasd.disable 설정되었는지 확인.
	// 2. vmasd 미동작, csagentd 가 동작 중인 경우... 아래 사항 체크.
	//    - /user/service/fhs.ronly
	//    - /user/service/fhs.out 
	// 3. vmasd, csagentd 모두 미동작인 경우...
	//    - 정상 서비스 상태가 아니므로.... 업로드 방지 상태로 처리.
		
	char tempBuffer[512];
	FILE * fd = NULL;
	bool bRun = false;

	struct stat stFileStat;

	// vmasd 가 동작 중인지 확인.
	snprintf( tempBuffer, sizeof( tempBuffer ), "pgrep -x vmasd | sort");
	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		int errorNum = errno;
		fprintf( stderr, PROG_NAME ": vmasd upload disable check error. popen error. [%d][%s]\n", errorNum, strerror( errorNum ) );

		return false;
	}
	else
	{
		memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
		while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
		{
			std::string tempPid( tempBuffer );
			Trim( tempPid );

			if( tempPid.empty() == false )
			{
				bRun = true;
				break;
			}
		}

		pclose( fd );
	}

	// vmasd 가 동작 중인 경우...
	if( bRun == true )
	{
		// /user/service/vmasd.disable 설정 체크.
		memset( &stFileStat, 0x00, sizeof( struct stat ) );
		if( lstat( FILE_VMASD_DISABLE, &stFileStat ) == 0 )
		{
			// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
			if( S_ISREG( stFileStat.st_mode ) != 0 )
			{
				// 해당 파일이 존재하는 경우...				
				fprintf( stderr, PROG_NAME ": upload disable check OK. [%s]\n", FILE_VMASD_DISABLE );
				return true;				
			}
		}

		// vmasd 가 동작 중인데... 위의 return 경우 외에는 전부 오류로 처리.
		fprintf( stderr, PROG_NAME ": vmasd run. but upload disable[%s] not set. check and retry. \n", FILE_VMASD_DISABLE);
		return false;
	}

	// csagentd 가 동작 중인지 확인.
	snprintf( tempBuffer, sizeof( tempBuffer ), "pgrep -x csagentd | sort" );
	fd = popen( tempBuffer, "r" );
	if( fd == NULL )
	{
		int errorNum = errno;
		fprintf( stderr, PROG_NAME ": csagentd upload disable check error. popen error. [%d][%s]\n", errorNum, strerror( errorNum ) );

		return false;
	}
	else
	{
		memset( tempBuffer, 0x00, sizeof( tempBuffer ) );
		while( fgets( tempBuffer, sizeof( tempBuffer ) - 1, fd ) != NULL )
		{
			std::string tempPid( tempBuffer );
			Trim( tempPid );

			if( tempPid.empty() == false )
			{
				bRun = true;
				break;
			}
		}

		pclose( fd );
	}

	// csagentd 가 동작 중인 경우...
	if( bRun == true )
	{
		// /user/service/fhs.ronly 설정 체크.
		memset( &stFileStat, 0x00, sizeof( struct stat ) );
		if( lstat( FILE_SERVICE_RONLY, &stFileStat ) == 0 )
		{
			// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
			if( S_ISREG( stFileStat.st_mode ) != 0 )
			{
				// 해당 파일이 존재하는 경우...				
				fprintf( stderr, PROG_NAME ": upload disable check OK. [%s]\n", FILE_SERVICE_RONLY );
				return true;
			}
		}

		// /user/service/fhs.out 설정 체크.
		memset( &stFileStat, 0x00, sizeof( struct stat ) );
		if( lstat( FILE_SERVICE_OUT, &stFileStat ) == 0 )
		{
			// 해당 파일이 정규 파일인 경우.. ( 폴더, 링크 등은 제외 처리 )
			if( S_ISREG( stFileStat.st_mode ) != 0 )
			{
				// 해당 파일이 존재하는 경우...				
				fprintf( stderr, PROG_NAME ": upload disable check OK. [%s]\n", FILE_SERVICE_OUT );
				return true;
			}
		}
			

		// csagentd 가 동작 중인데... 위의 return 경우 외에는 전부 오류로 처리.
		fprintf( stderr, PROG_NAME ": csagentd run. but upload disable[%s] not set. check and retry. \n", FILE_SERVICE_RONLY );
		return false;
	}

	// vmasd, csagentd 모두 미기동 상태인 경우....
	// - 서비스 상태가 아니므로.. 정상 처리.
	fprintf( stderr, PROG_NAME ": FHS is not service status. upload disable check OK.\n");
	return true;
}


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

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

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


// Main 프로세스에 대한 종료 처리 수신시
// - 요청한 모든 작업이 완료된 후 종료 되어야 정상 종료이며...
// - 만약 작업 도중 signal 에 의한 종료 요청이 발생하는 경우.. 비정상 종료로 처리해야 한다.
static void SignalMainTerminate( int nSignalNumber )
{
	// Signal Number 에 따른 로깅처리.
	if( nSignalNumber == SIGTERM )
	{
		_LOG( LERR, "[Main:%d] Process exit by SIGTERM signal. Job not completed..", getpid() );
		_LOG( LINF, "***********************************************************" );
	}
	else
	{
		_LOG( LERR, "[Main:%d] Process exit by abnormal signal[%d]. Job not completed..", getpid(), nSignalNumber );
		_LOG( LINF, "***********************************************************" );
	}
	
	// 기타 모듈들의 종료 처리 수행
	ReadyToExit();
	
	// 종료전 잠시 대기
	struct timespec sleep;
	sleep.tv_sec = 0;
	sleep.tv_nsec = 500000000;  // 0.5 sec
	nanosleep( &sleep, NULL );

	exit( EXIT_SUCCESS );
}


// Main Process 의 signal 처리기...
// fork 된 worker 프로세스 역시 기본적으로 본 signal 처리 action 을 상속받는다.
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 );	/* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
	sigaction( SIGHUP, &act, NULL );	/* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
	sigaction( SIGINT, &act, NULL );	/* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
	sigaction( SIGQUIT, &act, NULL );	/* 키보드에 의한  Abort 신호 처리 => ? */
	
	// Worker child process 종료에 대한 처리기 설정.
	// - 자식 프로세스가 존재하지 않으므로.. 그냥 무시 처리.
	// - 만약 system() 함수 등을 이용하여 외부 모듈 수행시.. system() 함수의 정상적 반환값 처리를 위해서는 SIG_DFL 로 설정해야 한다.
	act.sa_handler = SIG_DFL;
	sigaction( SIGCHLD, &act, NULL );
	
	// 사용자의 종료 또는 에러 관련 신호 처리.
	act.sa_handler = SignalMainTerminate;
	sigaction( SIGTERM, &act, NULL );	/* kill -TERM 에 의한 프로세스 종료시 */

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