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

#include "TOTOChecksum.h"   // NEW 2016-05-11 토토디스크 고객사 checksum 추출 지원 목적.

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>


#define DEFAULT_ALIVE_CHECK_TIME	5	// sec

// 1. config 정보를 저장하기 위한 전역 객체 => Listener.cpp 에 정의
extern struct st_config g_envConfig;

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


/// @brief Worker Process 의 main 함수
/// @param listenSocket [in] Listen Socket Descriptor
int WorkerMain( const int listenSocket )
{
	// Set Signal Handler
	SetSignalWorker();

#if defined(__FreeBSD__)
	setproctitle( "Worker [initialize process]");
#endif
	
	// Object initialize
	
	// 1. Local NIC Cache Class 생성 및 초기화.
	CLocalIPCache localIpCache( g_envConfig.szInternalNicName);
	localIpCache.Start();

	// 2. Target 내부 IP Cache Class 생성 및 초기화
	CIPCache ipCache;
	ipCache.SetTimeout( g_envConfig.nFhsInternalIpCacheTimeout );

	// 3. Process Status 생성 및 초기화
	CProcessStatus processStatus;

	// 4. Replication 처리시 내부 저장위치를 알려줄 Class 초기화.
	CProperStorage properStorage( g_envConfig.szStorageRoot, g_envConfig.nProperStorageRefresPeriod );
	if( properStorage.Start() == false )
	{
		_LOG( LWAR, "Worker[%d]: Proper Storage thread create failed. => Process Exit", getpid() );
		exit( EXIT_FAILURE );	// EXIT_FAILURE 를 반환하여 재생성 처리 방지...
	}
	
	// 접속 요청을 변수 생성 및 초기화.
	int clientSocket;
	struct sockaddr_in clientSockAddr;
	socklen_t clientSockLen = sizeof(clientSockAddr);

	// Thread 관련 변수.
	pthread_t workerThreadId;
	struct WorkerThreadArg * pThreadArg = NULL;

	// 접속 요청 대기 및 처리 루프
#if defined(__FreeBSD__)
	setproctitle( "Worker [wait for connection]");
#endif

	while(1)	
	{		
		clientSocket = accept( listenSocket, (struct sockaddr *) &clientSockAddr, &clientSockLen );
		if( clientSocket == -1 )
		{
			// 오류발생시 해당 내역 로깅처리.
			int errorNum = errno;
			LOG( LERR, "Worker process: client accept failed. [%d][%s]", errorNum, strerror(errorNum) );
		}
		else
		{
			// 정상적인 client 인 경우 
			// 1. 변수 전달을 위한 객체 생성
			pThreadArg = ( struct WorkerThreadArg *)malloc( sizeof(struct WorkerThreadArg));
			if( pThreadArg == NULL)
			{
				int errorNum = errno;
				close( clientSocket );

				// Error Logging
				// CHG 2012-02-01 huibong
				// inet_ntoa() 함수가 Thread-Safe 하지 않으므로 inet_ntop() 함수로 변경처리
				//LOG( LERR, "Worker Thread argument malloc failed.[%s]->[%d][%s]",inet_ntoa(clientSockAddr.sin_addr), errorNum, strerror( errorNum ));

				char tempBuffer[INET_ADDRSTRLEN];
				memset(tempBuffer, 0x00, sizeof(tempBuffer));

				if( inet_ntop( AF_INET, &(clientSockAddr.sin_addr), tempBuffer, sizeof(tempBuffer)) != NULL )
				{
					LOG( LERR, "[%s] Worker Thread argument malloc failed [%d][%s]", tempBuffer, errorNum, strerror( errorNum ));
				}
				else
				{
					LOG( LERR, "Worker Thread argument malloc failed [%d][%s] and inet_ntop error[%d][%s]", errorNum, strerror( errorNum ), errno, strerror(errno) );
				}
				
				continue;

			}
			else
			{
				// 인자로 전달할 변수정보 세팅.
				pThreadArg->clientSocketDesc	= clientSocket;
				pThreadArg->pLocalIpCache		= &localIpCache;
				pThreadArg->pIpCache			= &ipCache;
				pThreadArg->pProcessStatus		= &processStatus;
				pThreadArg->pProperStorage		= &properStorage;
			}

			// 2. 각 client 요청을 처리할 Thread 생성
			if( pthread_create( &workerThreadId, NULL, WorkerThreadProc, (void *)pThreadArg) != 0 )
			{
				int errorNum = errno;
				close( clientSocket );
				free( pThreadArg );

				// Error Logging
				// CHG 2012-02-01 huibong
				// inet_ntoa() 함수가 Thread-Safe 하지 않으므로 inet_ntop() 함수로 변경처리
				//LOG( LERR, "Worker Thread create failed.[%s]->[%d][%s]",inet_ntoa(clientSockAddr.sin_addr), errorNum, strerror( errorNum ));

				char tempBuffer[INET_ADDRSTRLEN];
				memset(tempBuffer, 0x00, sizeof(tempBuffer));

				if( inet_ntop( AF_INET, &(clientSockAddr.sin_addr), tempBuffer, sizeof(tempBuffer)) != NULL )
				{
					LOG( LERR, "[%s] Worker Thread create failed [%d][%s]",tempBuffer, errorNum, strerror( errorNum ));
				}
				else
				{
					LOG( LERR, "Worker Thread create failed [%d][%s] and inet_ntop error[%d][%s]", errorNum, strerror( errorNum ), errno, strerror(errno) );
				}

			}

		}
	} // while( 1 )


	// 아래의 코드는 실행되지 않는 코드임.
	// Process 종료 처리.
	close( listenSocket );
	LOG( LWAR, "Worker Process[%d] exit.. 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 );
	return EXIT_SUCCESS;	// fork 를 수행한 함수에서 exit 함수 호출을 통한 종료처리 (Listener.cpp)
}


void * WorkerThreadProc( void * arg)
{
	// 전달받은 인자값을 내부 변수에 저장처리.
	int clientSocket = ((struct WorkerThreadArg *) arg)->clientSocketDesc;

	// 아래의 정보는 WorkerMain() 에서 생성한 전역관련 정보임.
	// 전역정보의 생성 및 해제처리는 WorkerMain() 함수에서 처리된다.
	CLocalIPCache *		pLocalIpCache	= ((struct WorkerThreadArg *) arg)->pLocalIpCache;
	CIPCache *			pIpCache		= ((struct WorkerThreadArg *) arg)->pIpCache;
	CProcessStatus *	pProcessStatus	= ((struct WorkerThreadArg *) arg)->pProcessStatus;
	CProperStorage *	pProperStorage	= ((struct WorkerThreadArg *) arg)->pProperStorage;

	free( arg );						// 전달 받은 변수를 저장한 동적 생성 버퍼를 해제한다.
	pthread_detach(pthread_self());		// 메인 함수에서  이 스레드가 종료되기를 기다리지 않게 분리

	LOG( LDBG, "Worker[%d] client[%d] start", getpid(), clientSocket );
	
	// client 로 부터 전달받은 요청 패킷을 처리할 socket 관련 class 생성
	CSocketControl socketControl( clientSocket );

	struct timeval timeOver;
	int result;
	int receive_result;
	fd_set selectFds;
	FD_ZERO( &selectFds );

	// 파일의 송신을 담당하기 위한 FileSender Thread Class 생성
	CFileSender fileSender( pIpCache, pProcessStatus, &socketControl);

	// Packet Header 정보를 이용한 client 요청 분석
	bool bRequest;
	bool bFromControl;
	char command;

	// Socket 요청 대기 - Server 이므로 client 에서 접속 종료전까지 접속 유지 처리.
	while(1)
	{
		timeOver.tv_sec  = DEFAULT_ALIVE_CHECK_TIME;
		timeOver.tv_usec = 0;
		FD_SET( clientSocket, &selectFds );

		 result = select( clientSocket+1, &selectFds, (fd_set *)NULL, (fd_set *)NULL, &timeOver );

		 if( result > 0 )
		 {
			 if( FD_ISSET( clientSocket, &selectFds) )
			 {
				 // Socket 으로 신호가 들어온 경우 ( Data 수신 또는 접속 종료시)
				 receive_result = socketControl.GetHeaderInfo( bRequest, bFromControl, command );
				 
				 if( receive_result == -1 )
				 {
					 // Socket이 종료된 경우

					 // File Sender Thread 가 동작 중이지 않은 경우 그냥 종료 처리.
					 if( fileSender.IsIdle() == true )
					 {
						 LOG( LDBG, "Worker[%d] client[%d] connection closed => Thread exit", getpid(), clientSocket );
						 break; // 종료 처리.
					 }
					 else
					 {
						 // Control 과의 접속이 종료된 상황에서 File Sender Thread 가 동작중인 경우.
						 // fileSender Thread 를 정지시키고 최대 10초간 대기 후 종료처리를 수행한다.
						 fileSender.Stop();

						 for( int i=0; i < 10; i++)
						 {
							 if( fileSender.IsIdle() == true )
								 break;
							 else
								 sleep(1);
						 }

						 LOG( LDBG, "Worker[%d] client[%d] connection closed in file sending. Thread exit", getpid(), clientSocket );
						 break; // 종료 처리.
					 }
				 }
				 else if( receive_result == 0 )
				 {
					 // Read Timeout 발생시 => Server Daemon 이므로 client 접속종료시까지 대기
					 continue;
				 }


				 // 정상적으로 데이터를 수신한 경우.
				 // 요청 분석.
				 if( bFromControl == false && bRequest == true )
				 {
					 // Transfer Daemon 간 요청인 경우
					 switch( command )
					 {
						 case TRANSFER_LOCAL_IP:				// 1. 내부 NIC IP 요청인 경우
							 LOG( LDBG, "Worker[%d] client[%d] receive TRANSFER_LOCAL_IP", getpid(), clientSocket );
							 ProcessLocalIpRequest( &socketControl, pLocalIpCache);
							 break;

						 case TRANSFER_FILE_REPLICATION:		// 2. 파일 Replication 수신 요청인 경우
							 LOG( LDBG, "Worker[%d] client[%d] receive TRANSFER_FILE_REPLICATION", getpid(), clientSocket );
							 pProcessStatus->AddReceiveCount();
							 ProcessFileReceive( &socketControl, pProperStorage, command );
							 pProcessStatus->SubtractReceiveCount();
							 break;

						 case TRANSFER_FILE_CACHE:				// 3. 파일 Cache 수신 요청인 경우
							 LOG( LDBG, "Worker[%d] client[%d] receive TRANSFER_FILE_CACHE", getpid(), clientSocket );
							 pProcessStatus->AddReceiveCount();
							 ProcessFileReceive( &socketControl, pProperStorage, command );
							 pProcessStatus->SubtractReceiveCount();
							 break;

						 case TRANSFER_FILE_HASH:				// 4. File Hash 검사 요청시.
							 LOG( LDBG, "Worker[%d] client[%d] receive TRANSFER_FILE_HASH", getpid(), clientSocket );
							 pProcessStatus->AddReceiveCount();
							 ProcessFileHashCheck( &socketControl );
							 pProcessStatus->SubtractReceiveCount();
							 break;

						 case COMMON_ALIVE_CHECK:				// 5. Alive Check 요청인 경우 무시처리.
							 LOG( LDBG, "Worker[%d] client[%d] receive COMMON_ALIVE_CHECK", getpid(), clientSocket );
							 break;

						 default:
							 LOG( LERR, "Worker[%d] client[%d] receive [Unknown request from transfer][%02x]", getpid(), clientSocket, command );
							 socketControl.PrintHeaderToLog();
							 break;
					 }
				 }		
				 else if( bFromControl == true && bRequest == true )
				 {
					 // Control Daemon 으로부터 요청인 경우
					 switch( command )
					 {

						 case CONTROL_FILE_REPLICATION:		// 1. 파일 Replication 처리 요청
							 LOG( LDBG, "Worker[%d] client[%d] receive CONTROL_FILE_REPLICATION", getpid(), clientSocket );
							 ProcessFileSend( fileSender, pProcessStatus, &socketControl, command );
							 break;

						 case CONTROL_FILE_CACHE:			// 2. 파일 Cache 처리 요청							 
							 LOG( LDBG, "Worker[%d] client[%d] receive CONTROL_FILE_CACHE", getpid(), clientSocket );
							 ProcessFileSend( fileSender, pProcessStatus, &socketControl, command );
							 break;

						 case CONTROL_FILE_CHECK:			// 3. Content 에 대한 존재 여부 및 Size, Hash 정보 추출 요청 
							 LOG( LDBG, "Worker[%d] client[%d] receive CONTROL_FILE_CHECK", getpid(), clientSocket );
							 ProcessFileCheck( &socketControl, command );
							 break;

						 case CONTROL_FILE_UNLINK:			// 4. Content 에 대한 unlink 처리 요청
							 LOG( LDBG, "Worker[%d] client[%d] receive CONTROL_FILE_UNLINK", getpid(), clientSocket );
							 ProcessFileUnlink( &socketControl, command );
							 break;

						 case COMMON_ALIVE_CHECK:			// 5. Alive Check 요청인 경우 무시처리.
							 LOG( LDBG, "Worker[%d] client[%d] receive COMMON_ALIVE_CHECK", getpid(), clientSocket );
							 break;
						
						  // NEW 2016-05-04 huibong 토토디스크 지원용 기능 추가 (#27460)
						 case CONTROL_FILE_CHECK_TOTO:		// 6. 토토디스크 고객사 지원을 위한 MD5 Hash 추출 기능 (2016-05-03 추가)
							 LOG( LDBG, "Worker[%d] client[%d] receive CONTROL_FILE_CHECK_TOTO", getpid(), clientSocket );
							 ProcessFileCheckTOTO( &socketControl, command );
							 break;
						
						 // NEW 2021-04-01 huibong content stat(size, atime, mtime, ctime) 정보 추출 기능 추가 (#33516)
						 case CONTROL_FILE_STAT:
							 LOG( LDBG, "Worker[%d] client[%d] receive CONTROL_FILE_STAT", getpid(), clientSocket );
							 ProcessFileStat( &socketControl, command );
							 break;
							 
						 default:
							 LOG( LERR, "Worker[%d] client[%d] receive [Unknown request from control][%02x]", getpid(), clientSocket, command );
							 socketControl.PrintHeaderToLog();
							 break;
					 }
				 }
				 else
				 {
					 LOG( LERR, "Worker[%d] client[%d] receive error [Not defined packet header]", getpid(), clientSocket );
					 socketControl.PrintHeaderToLog();
				 }
			 }
		 }
		 else if( result == 0 ) // Timeout
		 {
			 // Alive Check Packet send.
			 if( socketControl.SendAliveCheck() == false )
			 {
				 _LOG( LINF, "Worker[%d] client[%d] alive check failed", getpid(), clientSocket );
				 break;
			 }
		 }
		 else // select function 오류.
		 {
			 int errorNum = errno;
			 LOG( LERR, "Worker[%d] client[%d] select function error [%d][%s]" , getpid(), clientSocket, errorNum, strerror(errorNum));
			 break;
		 }
	}

	// 종료 처리.
	socketControl.Close();
	
	// Logging
	LOG( LDBG, "Worker[%d] client[%d] thread end", getpid(), clientSocket );
	
	// Thread 종료 처리.
	
	// CHG 2013-03-19 huibong
	// detach 된 Thread 에 대해 pthread_exit() 호출시 
	// 기준 할당 자원에 대한 해지 처리가 되지 않아 메모리 누수 상황이 발생.
	// 따라서 이를 해결하기 detach 된 thread 에 대해 pthread_exit() 호출 코드 제거.
	//pthread_exit(NULL);
	return NULL;
}


/// @brief Control 로 부터 Transfer 간 파일 전송(Replication, Cache, Move) 요청이 들어온 경우 이를 처리하기 위한 함수.
bool ProcessFileSend( CFileSender& fileSender, CProcessStatus * pProcessStatus, CSocketControl * pSocketControl, char command )
{
	// 1. Control 로 부터 Packet Data 부분을 전달받는다.
	std::string szSourceFileName;
	std::string szTargetTranId;		// RC 간 동기화시 Target Tran ID 정보를 저장하기 위한 변수
	unsigned long long nSourceFileSize;
	int nFileHashCheckLevel;
	bool bUseInternalIp;
	std::vector< std::string > vecTargetFhs;

	std::string szErrorMessage;		// 오류 메시지 정보를 임시로 저장하기 위한 변수.
	

	if( pSocketControl->GetFileRequestInfo( szSourceFileName, nSourceFileSize, szTargetTranId
				, nFileHashCheckLevel, bUseInternalIp, vecTargetFhs ) == false )
	{
		// Data 정보 수신 실패시 Control 로 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( "File Replication/Cache request info get failed.", command );
		LOG( LERR, "Worker[%d] File Replication/Cache request info get failed.", getpid() );
		return false;
	}

	// 2. 현재 Process 에서 MAX File Send Thread 값에 제한이 걸리지 않는지 검사.
	if( pProcessStatus->GetSendCount() >= g_envConfig.nMaxFileSendThread )
	{
		// 오류 메시지를 Socket 으로 전달하고 종료처리.
		pSocketControl->SendErrorMessageToControl( "Max file send thread count over", command );
		LOG( LERR, "Worker[%d] Max file send thread count over.", getpid() );
		return false;
	}

	// 3. 파일 전송을 담당하는 FileSender 가 현재 사용중인지 검사.
	if( fileSender.IsIdle() == false )
	{
		// 오류 메시지를 Socket 으로 전달하고 종료한다.
		pSocketControl->SendErrorMessageToControl( "Already file send job stated.", command );
		LOG( LERR, "Worker[%d] Already file send job stated.", getpid());
		return false;
	}
	
	// 4. 요청 정보를 fileSender 에게 설정한다.
	if( fileSender.Init( szSourceFileName, nSourceFileSize, szTargetTranId, vecTargetFhs, nFileHashCheckLevel, bUseInternalIp, command, szErrorMessage ) == false )
	{
		// 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d] Request or Source file info not valid.[%s]", getpid(), szErrorMessage.c_str());
		return false;
	}

	// 5. fileSender 에게 작업 시작을 알리고 반환처리한다.
	// 나머지 작업은 fileSender 에서 내부적으로 처리해 버린다.
	fileSender.Start();

	return true;
}


/// @brief Local NIC IP 요청에 대한 처리를 수행한다.
/// @param pSocketControl [in] 요청 Client socket 에 대한 CSocketControl object pointer
/// @param pLocalIpCache [in] Local NIC IP 정보 처리를 위한 CLocalIPCache object pointer
/// @return Local NIC 처리결과 전송 성공시 true, 실패시 false 반환.
bool ProcessLocalIpRequest( CSocketControl * pSocketControl, CLocalIPCache * pLocalIpCache )
{
	string szLocalIp;
	bool bResult;
	
	// Local NIC IP 정보 확인.
	bResult = pLocalIpCache->GetLocalIP( szLocalIp );
	if( bResult == false )
	{
		LOG( LWAR, "Worker[%d]: Local NIC IP get failed.", getpid() );
	}

	// 해당 NIC 정보 전송
	if( pSocketControl->SendLocalIpInfo( bResult, szLocalIp ) == true )
		return true;
	else
		return false;
}

/// @brief Replication 대상 파일의 수신 요청에 대한 처리를 수행한다.
/// @param pSocketControl [in] 요청 Client socket 에 대한 CSocketControl object pointer
/// @param pProperStorage [in] CProperStorage Object Pointer.
/// @param commandCode    [in] Replication 또는 Cache 요청인지 여부값 (TRANSFER_FILE_REPLICATION, TRANSFER_FILE_CACHE)
/// @return 파일 수신 및 처리 성공시 true, 오류 발생시 false 반환.
bool ProcessFileReceive( CSocketControl * pSocketControl, CProperStorage * pProperStorage, char commandCode )
{
	string szProperStorage;
	unsigned long long nMaxAvailableSize;
	
	// 2014-03-04 CHG huibong
	// CProperStorage 객체 수정에 따른 처리 코드 변경함.
	//  - CProperStorage::GetProperPath() 함수는 적합한 저장 경로를 추출하지 못한 경우... 빈값을 반환한다.
	//  - 하지만 본 함수에서는 socket 을 통해 오류 내역을 전달할 수 없으므로... socket 처리 함수에서 이를 처리토록 한다.

	// 1. 전달받은 파일에 대해 저장할 위치를 검색한다.
	//  - 본 함수의 인자로 전달받은 CProperStorage 객체 포인터는 NULL 이 될 수 없다.	
	pProperStorage->GetProperPath( szProperStorage, nMaxAvailableSize );

	// 2. 만약 Cache 처리요청인 경우
	// 위에서 구한 경로 하위 cache 폴더로 저장되도록 저장경로를 수정처리한다.
	// 즉 /stg/node1/cache/261/abdeesss 로 처리되도록 한다.
	if( commandCode == TRANSFER_FILE_CACHE && szProperStorage.empty() == false )
	{
		szProperStorage += "/cache";
	}

		
	// 3. SocketControl 에서 해당 내역을 처리하도록 전달처리.
	if( pSocketControl->ReceiveFile( szProperStorage, nMaxAvailableSize, commandCode ) == false )
		return false;

	return true;
}

bool ProcessFileHashCheck( CSocketControl * pSocketControl )
{
	// ScocketControl 에게 파일 정보 및 Hash Level, Hash 값 정보를 전달받아
	// Hash 를 검사하도록 처리한다.	
	if( pSocketControl->CheckFileHash( ) == false )
	{
		LOG( LERR, "Worker[%d]: Reciever hash check fail ", getpid());
		return false;
	}

	return true;
}

/// @brief Control 로 부터 Content 에 대한 Check ( Size, Hash ) 요청을 받은 경우 이를 처리하기 위한 함수
/// @param
/// @retrun
bool ProcessFileCheck( CSocketControl * pSocketControl, char command )
{
	// 1. Control 로 부터 Packet Data 부분을 전달받는다.
	std::string szFileName;
	bool bHashCheck;
	unsigned long long nHashCheckSize; // MByte 단위값.

	std::string szErrorMessage;		// 오류 메시지 정보를 임시로 저장하기 위한 변수.
	
	if( pSocketControl->GetRequestCheckFromControl( szFileName, bHashCheck, nHashCheckSize ) == false )
	{
		// Data 정보 수신 실패시 Control 로 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( "File Check request info get failed.", command );
		LOG( LERR, "Worker[%d] File Check request info get failed.", getpid() );
		return false;
	}

	// 2. 결과값을 저장하기 위한 변수 초기화
	unsigned long long nFileSize = 0;
	std::string szHashValue;

	// 3. 전달받은 파일이 존재하는지 검사.
	if( access(szFileName.c_str(), R_OK) == -1 )
	{
		szErrorMessage = "Check file access fail.[" + szFileName + "]";

		// 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());
		return false;
	}

	// 4. 파일 크기값 추출 
	FILE * pFile = fopen( szFileName.c_str(), "r+" );
	if( pFile == NULL )
	{
		szErrorMessage = "Check file open fail.[" + szFileName + "]";
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());

		return false;
	}
	else
	{
		fseeko( pFile, 0, SEEK_END );
		off_t totalSize = ftello( pFile );
		rewind( pFile );
		fclose( pFile );	// 열려진 descriptor close

		nFileSize = (unsigned long long)totalSize;  // 파일 Size 추출 완료
	}

	// 5. Hash 값 추출인 경우
	if( bHashCheck == true )
	{
		// Hash 처리를 위한 객체를 생성한 후 Hash 검사를 수행
		CFileHash fileHash;

		if( nHashCheckSize == 0 )  // 전체 Size 에 대한 Hash 추출인 경우...
		{
			if( fileHash.GetFileHash( szFileName, HASH_LEVEL_FULL, szHashValue) == false )
			{
				szErrorMessage = "Hash(full) create fail [" + fileHash.GetLastErrorMessage() + "]";
				pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
				LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());

				return false;
			}
		}
		else
		{
			// Content 앞부분에 대한 일부 Size 에 Hash 추출인 경우... 추출 Size 설정
			fileHash.SetPartitialSize( nHashCheckSize );
			
			if( fileHash.GetFileHash( szFileName, HASH_LEVEL_FRONT_PARTITIAL, szHashValue) == false )
			{
				szErrorMessage = "Hash(front) create fail [" + fileHash.GetLastErrorMessage() + "]";
				pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
				LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());

				return false;
			}
		}
	}

	// 6. 추출된 정보를 전달한다.
	if( pSocketControl->SendCheckResultToControl( command, nFileSize, szHashValue ) == false )
	{
		// 실패 발생시 로깅 처리.
		LOG( LERR, "Worker[%d] File[%s] Check result[%llu] send fail.", getpid(), szFileName.c_str(), nFileSize );
		return false;
	}

	// 7. 처리 내역 로깅
	_LOG( LINF, "File[%s] check complete. size[%llu] hash[%s]", szFileName.c_str(), nFileSize, szHashValue.c_str()); 

	return true;
}


// NEW 2016-05-04 huibong 토토디스크 지원용 기능 추가 (#27460, #27533)
/// @brief 고객사 토토디스크 지원을 위한 기능
///        Control 로 부터 전달받은 Content 에 대해 Size 정보 및 MD5 Hash, Checksum 정보 등을 추출하여 전달한다.
/// @param
/// @retrun
bool ProcessFileCheckTOTO( CSocketControl * pSocketControl, char command )
{
	// 1. Control 로 부터 Packet Data 부분을 전달받는다.
	std::string szFileName;
	std::string szErrorMessage;	       // 오류 메시지 정보를 임시로 저장하기 위한 변수.
	
	if( pSocketControl->GetRequestCheckTOTOFromControl( szFileName ) == false )
	{
		// Data 정보 수신 실패시 Control 로 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( "File Check request info get failed.", command );
		LOG( LERR, "Worker[%d]: [TOTO] File Check request info get failed.", getpid() );
		return false;
	}

	// 2. 결과값을 저장하기 위한 변수 초기화
	unsigned long long nFileSize = 0;
	std::string szResultTotoMd5;
	std::string szResultChecksum;
	std::string szResultDnaCheckKey;
	

	// 3. 전달받은 파일이 존재하는지 검사.
	if( access( szFileName.c_str(), R_OK ) == -1 )
	{
		szErrorMessage = "Check file access fail.[" + szFileName + "]";

		// 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: [TOTO] %s", getpid(), szErrorMessage.c_str() );
		return false;
	}

	// 4. 파일 크기값 추출 
	FILE * pFile = fopen( szFileName.c_str(), "r+" );
	if( pFile == NULL )
	{
		szErrorMessage = "Check file open fail.[" + szFileName + "]";
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: [TOTO] %s", getpid(), szErrorMessage.c_str() );

		return false;
	}
	else
	{
		fseeko( pFile, 0, SEEK_END );
		off_t totalSize = ftello( pFile );
		rewind( pFile );
		fclose( pFile );	// 열려진 descriptor close

		nFileSize = (unsigned long long)totalSize;  // 파일 Size 추출 완료
	}

	
	// 5. 토토측 MD5 및 DNA Check key 추출 함수 호출
	CFileHash fileHash;
	if( fileHash.GetHashTOTO( szFileName, nFileSize, szResultTotoMd5, szResultDnaCheckKey ) == false )
	{
		szErrorMessage = "MD5 extract fail [" + fileHash.GetLastErrorMessage() + "]";
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: [TOTO] %s", getpid(), szErrorMessage.c_str() );

		return false;
	}

	// 6. 토토측 Checksum 추출 함수 호출.
	CTotoChecksum totoChecksum;
	if( totoChecksum.GetChecksum( szFileName, nFileSize, szResultChecksum ) == false )
	{
		szErrorMessage = "Checksum extract fail [" + totoChecksum.GetErrorMessage() + "]";
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: [TOTO] %s", getpid(), szErrorMessage.c_str() );

		return false;
	}
	
	// 7. 추출된 정보를 전달한다.
	if( pSocketControl->SendTOTOCheckResultToControl( command, nFileSize, szResultTotoMd5, szResultChecksum, szResultDnaCheckKey ) == false )
	{
		// 실패 발생시 로깅 처리.
		LOG( LERR, "Worker[%d] [TOTO] File[%s] Check size[%llu] & result send fail.", getpid(), szFileName.c_str(), nFileSize);
		return false;
	}
	
	// 8. 처리 내역 로깅
	_LOG( LINF, "[TOTO] File[%s] check complete. size[%llu] totomd5[%s] checksum[%s] dnakey[%s]"
		, szFileName.c_str(), nFileSize, szResultTotoMd5.c_str(), szResultChecksum.c_str(), szResultDnaCheckKey.c_str() );

	return true;
}



/// @brief Control 로 부터 Content 에 대한 Unlink 요청을 받은 경우 이를 처리하기 위한 함수
/// @param
/// @retrun
bool ProcessFileUnlink( CSocketControl * pSocketControl, char command )
{
	// 1. Control 로 부터 Packet Data 부분을 전달받는다.
	std::string szFileName;
	unsigned long long nFileSize;
	unsigned int nUnlinkMode = 0;

	std::string szErrorMessage;		// 오류 메시지 정보를 임시로 저장하기 위한 변수.

	if( pSocketControl->GetRequestUnlinkFromControl( szFileName, nFileSize, nUnlinkMode ) == false )
	{
		// Data 정보 수신 실패시 Control 로 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( "File Unlink request info get failed.", command );
		LOG( LERR, "Worker[%d] File Unlink request info get failed.", getpid() );
		return false;
	}

	// 3. 전달받은 파일이 존재하는지 검사.
	//   - 일반 모드 : 해당 파일이 존재하지 않을 경우 오류로 처리...
	//   - 강제 모드 : 해당 파일이 존재하지 않을 경우.. 이미 삭제된 것으로 판단하여 정상으로 처리.
	if( access(szFileName.c_str(), R_OK) == -1 )
	{
		if( nUnlinkMode == UNLINK_MODE_FORCE )
		{
			// 강제 삭제 모드인 경우 해당 파일이 존재하지 않으므로.. 정상으로 처리..
			// 로깅은 필히 남길 것.
			_LOG( LWAR, "File[%s][[%llu] not found. but unlink result success by unlink force mode", szFileName.c_str(), nFileSize);
			
			// 처리 결과 전달
			if( pSocketControl->SendUnlinkResultToControl( command ) == false )
			{
				// 실패 발생시 로깅 처리.
				LOG( LERR, "Worker[%d] file[%s] unlink result send fail.", getpid(), szFileName.c_str() );
				return false;
			}

			return true;
		}
		else
		{
			// 강제 삭제 모드가 아닌 경우.. 해당 파일이 존재하지 않으므로 실패로 처리.
			szErrorMessage = "unlink file not found.[" + szFileName + "]";

			// 오류 메시지를 전송한다.
			pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
			LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());
			return false;
		}
	}

	// 4. 파일 크기값 추출 
	FILE * pFile = fopen( szFileName.c_str(), "r+" );
	if( pFile == NULL )
	{
		szErrorMessage = "Unlink file open fail.[" + szFileName + "]";
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());

		return false;
	}
	else
	{
		fseeko( pFile, 0, SEEK_END );
		off_t totalSize = ftello( pFile );
		rewind( pFile );
		fclose( pFile );	// 열려진 descriptor close

		if( (unsigned long long)totalSize != nFileSize )
		{
			// std::string 에 숫자를 append 처리시 오류가 자주 발생하여 C Style 로 변환하여 처리함.
			char tempBuffer[1024];
			snprintf( tempBuffer, sizeof(tempBuffer)-1
					, "unlink file[%s] size not match.[request:%llu, local:%jd]"
					, szFileName.c_str(), nFileSize, totalSize );

			if( nUnlinkMode == UNLINK_MODE_FORCE )
			{
				// 강제 삭제 모드인 경우.. 관련 내역을 로깅 처리만 수행
				_LOG( LWAR, "%s. but unlink force mode", tempBuffer );
			}
			else
			{
				// 일반 모드인 경우 Size 가 서로 다르므로.. 실패로 처리
				szErrorMessage = tempBuffer;
				pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
				LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());

				return false;
			}
		}		
	}

	// 5. unlink 처리
	if( unlink( szFileName.c_str() ) != 0 )
	{
		int errorNum = errno;

		char tempBuffer[1024];
		snprintf( tempBuffer, sizeof(tempBuffer)-1
				, "Unlink [%s][%llu][%d] system error[%d][%s]"
				, szFileName.c_str(), nFileSize, nUnlinkMode, errorNum, strerror(errorNum) );

		szErrorMessage = tempBuffer;
		pSocketControl->SendErrorMessageToControl( szErrorMessage, command );
		LOG( LERR, "Worker[%d]: %s", getpid(), szErrorMessage.c_str());

		return false;
	}

	// 6. 처리 결과를 전달한다.
	if( pSocketControl->SendUnlinkResultToControl( command ) == false )
	{
		// 실패 발생시 로깅 처리.
		LOG( LERR, "Worker[%d] File[%s] unlink result send fail.", getpid(), szFileName.c_str() );
		return false;
	}

	// 7. 로깅
	if( nUnlinkMode == UNLINK_MODE_FORCE )
	{
		_LOG( LINF, "File[%s][%llu] unlink complete with force mode.", szFileName.c_str(), nFileSize );
	}
	else
	{
		_LOG( LINF, "File[%s][%llu] unlink complete.", szFileName.c_str(), nFileSize );
	}

	return true;
}

// NEW 2021-04-01 huibong content stat(size, atime, mtime, ctime) 정보 추출 기능 추가 (#33516)
bool ProcessFileStat( CSocketControl * pSocketControl, char command )
{
	// 1. Control 로 부터 Packet Data 부분을 전달받는다.
	std::string strFileName;
	
	// 오류 메시지 정보를 임시로 저장하기 위한 변수.
	std::string strErrorMessage;

	if( pSocketControl->GetRequestStatFromControl( strFileName ) == false )
	{
		// Data 정보 수신 실패시 Control 로 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( "file stat request receive failed.", command );
		LOG( LERR, "Worker[%d] file stat request receive failed.", getpid() );
		return false;
	}

	// 2. 전달받은 파일이 존재하는지 검사.
	if( access( strFileName.c_str(), R_OK ) == -1 )
	{
		int errorNum = errno;
		//strErrorMessage = "file access error [" + strFileName + "][" + strerror( errorNum ) + "]";
		strErrorMessage =  strerror( errorNum );

		// 오류 메시지를 전송한다.
		pSocketControl->SendErrorMessageToControl( strErrorMessage, command );
		LOG( LERR, "Worker[%d]: stat access error [%s][%s]", getpid(), strFileName.c_str(), strErrorMessage.c_str() );
		return false;
	}


	// 3. 요청 파일에 대한 stat 정보를 추출한다.

	struct stat stFileStat;
	if( stat( strFileName.c_str(), &stFileStat ) != 0 )
	{
		int errorNum = errno;
		//strErrorMessage = "stat function error [" + strFileName + "][" + strerror( errorNum ) + "]";
		strErrorMessage = strerror( errorNum );

		pSocketControl->SendErrorMessageToControl( strErrorMessage, command );
		LOG( LERR, "Worker[%d]: stat function error [%s][%s]", getpid(), strFileName.c_str(), strErrorMessage.c_str());

		return false;
	}

	// 4. 추출된 정보를 전달한다.

	if( pSocketControl->SendStatResultToControl( command
		, stFileStat.st_size, stFileStat.st_atime, stFileStat.st_mtime, stFileStat.st_ctime ) == false )
	{
		// 실패 발생시 로깅 처리.
		LOG( LERR, "Worker[%d] file[%s] stat result send fail. [ size:%ld access:%ld modify:%ld change:%ld ]"
			, getpid(), strFileName.c_str()
			, stFileStat.st_size, stFileStat.st_atime, stFileStat.st_mtime, stFileStat.st_ctime );

		return false;
	}

	// 7. 처리 내역 로깅
	_LOG( LINF, "file[%s] stat complete.[ size:%ld access:%ld modify:%ld change:%ld ]"
		, strFileName.c_str(), stFileStat.st_size, stFileStat.st_atime, stFileStat.st_mtime, stFileStat.st_ctime );

	return true;
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

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

	LOG( LWAR, "Worker 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 Worker 프로세스 signal 처리 설정을 위한 함수	
/// @return void	
void SetSignalWorker( void )
{
	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 신호 처리 => ? */
	
	// Worker 프로세스는 자식 프로세스가 존재하지 않으므로 그냥 무시처리함.
	//act.sa_handler = SigChldWorker;
	sigaction( SIGCHLD, &act, NULL);    
	
	/* 각종 에러나 사용자의 종료 신호 처리 */
	act.sa_handler = SigTermWorker;
	sigaction( SIGTERM, &act, NULL);	/* kill -TERM 에 의한 프로세스 종료시 */

	/* Core 관련 signal 처리 : 오류 처리 및 Debug(core dump) 목적 */
	
	// CHG 2012-05-21 huibong
	// Worker 프로세스의 문제 발생시 문제점 해결을 위한 core 파일 분석을 위해.. signal handler 를 기본으로  변경 처리함.	
	act.sa_handler = SIG_DFL;
	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);
}

