#include "Main.h" #include "ProcessRename.h" #include "ProcessConfig.h" #include "Logger.h" #include "WorkerAccess.h" #include #include #include #include #include #include #include #include #include // Worker 프로세스 중 content access time 정보 추출을 담당할 Worker PID 정보. // 시그널 처리에 의해 재생성 처리 단순화를 위해 전역 변수로 처리 pid_t g_pidWorkerAccess = -1; int main( int argc, char * argv[] ) { // 설정 파일을 저장할 변수. std::string strConfigFileName = DEFAULT_CONFIG_FILE; // linux 에서 프로세스를 Titile 변경을 지원을 위해서 argv 메모리에 저장 argv = save_ps_display_args( argc, argv ); // 전달 받은 옵션 여부 확인 및 처리 if( argc >= 2 ) { int opt; while( ( opt = getopt( argc, argv, "hvc:" ) ) != -1 ) // 옵션 끝까지 파싱처리 { switch( opt ) { case 'h': case '?': // 정의되지 않은 문자가 나타날 경우 getopt 에 자동반환. 도움말 표시 처리. PrintUsage(); return EXIT_SUCCESS; case 'v': PrintVersion(); return EXIT_SUCCESS; case 'c': strConfigFileName = optarg; fprintf( stderr, PROG_NAME " start with user define conf[%s]\n", strConfigFileName.c_str() ); break; default: // 본 조건절은 getopt 특성상 동작하지 않지만...업무 Flow 이해(?)를 위해 유지한다. fprintf( stderr, "Unkonwn options[%c] used. program terminated.\n", opt ); return EXIT_FAILURE; } } } // 프로세스 중복 실행 여부 검사 if( IsCurrentProcessRun() == true ) { fprintf( stderr, "[warning] Process [" PROG_NAME "] is already running....\n" ); return EXIT_FAILURE; } std::string szErrorMessage; // conf 로딩 및 검사 if( CProcessConfig::Init( PROG_NAME, strConfigFileName, szErrorMessage ) == false ) { fprintf( stderr, "[ERR] %s\n\n", szErrorMessage.c_str() ); return EXIT_FAILURE; } // 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; } // 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 _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, "Access DB : %s %u %s %s", CProcessConfig::GetInstance()->GetRcdbIp() , CProcessConfig::GetInstance()->GetRcdbPort() , CProcessConfig::GetInstance()->GetRcdbName() , CProcessConfig::GetInstance()->GetRcdbAcct() ); _LOG( LINF, "FHS ftsd tcp port : %u", CProcessConfig::GetInstance()->GetFhsTransferDaemonPort() ); _LOG( LINF, "Max queue size : %u", CProcessConfig::GetInstance()->GetMaxQueueSize() ); _LOG( LINF, "Max thread count : %u", CProcessConfig::GetInstance()->GetMaxThreadCount() ); _LOG( LINF, "Access time extract service : %u", CProcessConfig::GetInstance()->GetAccessTimeExtractService() ); _LOG( LINF, "***********************************************************" ); // Log Level 재설정. -> conf 설정대로 변경 처리. #ifdef _DEBUG_ CLogger::GetInstance()->SetLogLevel( LDBG ); #else CLogger::GetInstance()->SetLogLevel( CProcessConfig::GetInstance()->GetLogLevel() ); #endif // Worker Process 생성 // content access time 추출 처리용 worker 프로세스 생성 if( MakeWorkerAccess() == false ) { LOG( LERR, "[Main %d] content access time extract worker process create failed. => Process exit.", getpid() ); ReadyToExit(); return EXIT_FAILURE; } // Worker 생성되면.. worker 초기화 작업을 위해 약 2 sec 정도 대기 sleep( 2 ); // 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; } // 사용방법 표시 void PrintUsage() { fprintf( stderr, "\n" ); fprintf( stderr, "Usage: " PROG_NAME " [-h] [-v] [-c {file}] \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, "\n" ); fprintf( stderr, PROG_NAME " is Solbox Cloud Storage module.\n" ); fprintf( stderr, " - content access time extract module.\n" ); fprintf( stderr, " -- content access time extract and save to DB.\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; } } /// @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 ); } // 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수. void ReadyToExit() { // Logger 객체 종료 처리. CLogger::Exit(); return; } // Worker 프로세스 종료 신호 수신시 static void SignalWorkerDead( int nSignalNumber ) { pid_t deadPid; int nDeadStatus; while( ( deadPid = waitpid( -1, &nDeadStatus, WNOHANG ) ) > 0 ) { // Worker 프로세스가 signal 에 의해 종료되었는지 검사. // - 종료 사유 로깅 및 // - Worker 의 초기화 실패로 인한 종료시... Main 프로세스가 종료되도록 처리. if( WIFSIGNALED( nDeadStatus ) ) { // signal 에 의해 종료된 경우... // - 관련 내역 로깅 if( deadPid == g_pidWorkerAccess ) { _LOG( LWAR, "[Main %d] content access time extract worker process[%d] killed by signal[%d]" , getpid(), deadPid, WTERMSIG( nDeadStatus ) ); } else { _LOG( LWAR, "[Main %d] Unknown child process[%d] killed by signal[%d]" , getpid(), deadPid, WTERMSIG( nDeadStatus ) ); } } else { // signal 에 종료된 상황이 아닌 경우... // - 각 Worker 의 초기화 실패 발생시... return 구문에 의해 종료됨. // - 각 Worker 가 signal 을 받아서.. 정상적으로 return 처리하여 종료된 경우. // - 기타 등등 // 관련 내역 로깅. if( deadPid == g_pidWorkerAccess ) { _LOG( LWAR, "[Main %d] content access time extract worker process[%d] exited.", getpid(), deadPid ); } else { _LOG( LWAR, "[Main %d] Unknown child process[%d] exited.", getpid(), deadPid ); } // 만약 Worker 프로세스들이 초기화 실패로 인해 EXIT_FAILURE 반환되는 상황이라면... // 자식 프로세스를 재생성하지 않고.. 프로세스를 종료 처리한다. if( WIFEXITED( nDeadStatus ) ) { if( WEXITSTATUS( nDeadStatus ) == EXIT_FAILURE ) { if( deadPid == g_pidWorkerAccess ) { _LOG( LWAR, "[Main %d] content access time extract worker process[%d] initialize failed.", getpid(), deadPid ); } else { _LOG( LWAR, "[Main %d] Unknown child process[%d] exit with fail.", getpid(), deadPid ); } _LOG( LERR, "[Main %d] Program abnormally exited because Worker initialize faild.", getpid() ); // Main 종료 시그널 발생 raise( SIGTERM ); continue; } } } // 위에서 로깅 및 초기화 실패로 인한 Main 종료 처리 작업을 수행 // 그 외의 경우에는 다음과 같이 한다. // - 심각한 문제가 발생한 경우 일 경우에는... 전체 프로세스를 종료 처리한다. // - 그 외에는 Worker 를 재생성 처리한다. if( deadPid == g_pidWorkerAccess ) { if( MakeWorkerAccess() == false ) { _LOG( LERR, "[Main %d] content access time extract worker process recreate failed.=> Main process exit.", getpid() ); raise( SIGTERM ); } else { _LOG( LWAR, "[Main %d] content access time extract worker process recreate success.", getpid() ); } } } // 오류 발생시 해당 내역 로깅 if( deadPid < 0 ) { int errorNum = errno; LOG( LERR, "[Main %d] SIG_CHLD receive but waitpid return error[%d][%s]" , getpid(), errorNum, strerror( errorNum ) ); } return; } // Main 프로세스에 대한 종료 처리 수신시 static void SignalMainTerminate( int nSignalNumber ) { // 자식 프로세스를 종료 처리한다. if( g_pidWorkerAccess != -1 ) { kill( g_pidWorkerAccess, SIGTERM ); } // Signal Number 에 따른 로깅처리. if( nSignalNumber == SIGTERM ) { _LOG( LINF, "[Main %d] Process exit by SIGTERM signal. Good Bye..", getpid() ); } else { _LOG( LWAR, "[Main %d] Process exit by abnormal signal[%d]. Good Bye..", getpid(), nSignalNumber ); } // 잠시 대기 sleep( 1 ); // 기타 모듈들의 종료 처리 수행 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 종료에 대한 처리기 설정. act.sa_handler = SignalWorkerDead; sigaction( SIGCHLD, &act, NULL ); // 사용자의 종료 또는 에러 관련 신호 처리. act.sa_handler = SignalMainTerminate; sigaction( SIGTERM, &act, NULL ); /* kill -TERM 에 의한 프로세스 종료시 */ // 나머지 신호는 default 처리. sigemptyset( &set ); /* 신호 처리기 처리 설정 위한 블록 해제 */ sigprocmask( SIG_SETMASK, &set, NULL ); } // content access time extract worker 프로세스 생성. bool MakeWorkerAccess() { // fork() 결과를 Worker 프로세스의 PID 정보 저장 목적 전역 변수에 저장. g_pidWorkerAccess = fork(); if( g_pidWorkerAccess == 0 ) { // 생성된 Worker 프로세스인 경우... int iResult; // content access time extract worker 프로세스의 Main 함수 호출 iResult = WorkerAccessMain(); // content access time extract worker 프로세스의 Main 함수 종료시 종료 처리 _LOG( LINF, "[Worker %d] content access time extract worker process exit. return[%d]", getpid(), iResult ); ReadyToExit(); exit( iResult ); } else if( g_pidWorkerAccess < 0 ) { // fork 실패시 int errorNum = errno; LOG( LERR, "[Main %d] content access time extract worker process create failed. [%d][%s]" , getpid(), errorNum, strerror( errorNum ) ); return false; } else { // 현재의 Main 프로세스 _LOG( LINF, "[Main %d] content access time extract worker process created.. PID[%d]", getpid(), g_pidWorkerAccess ); // 잠시 대기 struct timespec sleep; sleep.tv_sec = 0; sleep.tv_nsec = 200000000; // 0.2 sec nanosleep( &sleep, NULL ); } return true; }