488 lines
14 KiB
C++
488 lines
14 KiB
C++
#include "Main.h"
|
|
|
|
#include "ProcessRename.h"
|
|
#include "ServiceConfig.h"
|
|
#include "Logger.h"
|
|
#include "Worker.h"
|
|
#include "Util.h"
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
|
|
// Accept 대기 Count 수
|
|
#define DEFAULT_ACCEPT_WAIT_COUNT 50
|
|
|
|
// 전역 객체 정보
|
|
// 명령 수신, Data 송수신을 담당할 TCP Listen Socket
|
|
int g_listenSocket = -1;
|
|
|
|
|
|
// 사용방법 표시
|
|
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 that service - service content sync and check .\n\n");
|
|
|
|
return;
|
|
}
|
|
|
|
// 버전 정보 표시
|
|
void PrintVersion()
|
|
{
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, PROG_NAME " version: " PROG_VERSION "\n\n");
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
// conf 로딩 및 검사
|
|
std::string szErrorMessage;
|
|
if( CServiceConfig::Init( PROG_NAME, strConfigFileName, szErrorMessage ) == false)
|
|
{
|
|
fprintf(stderr, "[ERR] %s\n\n", szErrorMessage.c_str());
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Log 객체 생성 및 초기화
|
|
if (CLogger::Init(PROG_NAME, CServiceConfig::GetInstance()->GetLogPath(), LINF) == false)
|
|
{
|
|
fprintf(stderr, "[ERR] Log module initialize failed.[%s]\n\n", CServiceConfig::GetInstance()->GetLogPath());
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Daemonize...
|
|
// - nochdir: true(작업 디렉토리 변경 안함)
|
|
// - noclose: false (표준 입출력, 에러를 /dev/null 로 리디렉트 처리)
|
|
if( daemon(1, 0) == -1)
|
|
{
|
|
fprintf(stderr, "[ERR] Process daemonize failed.[%s]\n\n", strerror(errno) );
|
|
|
|
ReadyToExit();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Signal 처리기 설정
|
|
SetSignalMain();
|
|
|
|
// Process 기동 관련 정보 로깅
|
|
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", CServiceConfig::GetInstance()->GetLogPath(), PROG_NAME);
|
|
_LOG( LINF, "Log level : %d", CServiceConfig::GetInstance()->GetLogLevel());
|
|
_LOG( LINF, "RC ID : %s", CServiceConfig::GetInstance()->GetRcId());
|
|
_LOG( LINF, "RCDB : %s %u %s %s", CServiceConfig::GetInstance()->GetRcdbIp()
|
|
, CServiceConfig::GetInstance()->GetRcdbPort()
|
|
, CServiceConfig::GetInstance()->GetRcdbName()
|
|
, CServiceConfig::GetInstance()->GetRcdbAcct());
|
|
_LOG( LINF, "Skip keyword" );
|
|
vecTemp = CServiceConfig::GetInstance()->GetSkipWord();
|
|
for( it = vecTemp.begin(); it != vecTemp.end(); it++ )
|
|
{
|
|
_LOG( LINF, " : %s", it->c_str() );
|
|
}
|
|
_LOG( LINF, "Operation tcp port : %u", CServiceConfig::GetInstance()->GetOperationPort() );
|
|
_LOG( LINF, "DB pool count : %u", CServiceConfig::GetInstance()->GetDbPoolCount() );
|
|
_LOG( LINF, "Work pool count : %u", CServiceConfig::GetInstance()->GetWorkPoolCount() );
|
|
_LOG( LINF, "Safety factor : %u (sec)", CServiceConfig::GetInstance()->GetSafetyFactor() );
|
|
_LOG( LINF, "Once Size : %u (day)", CServiceConfig::GetInstance()->GetOnceSize() );
|
|
_LOG( LINF, "Check" );
|
|
_LOG( LINF, " small : %u (sec)", CServiceConfig::GetInstance()->GetChkSmall() );
|
|
_LOG( LINF, " middle : %u (sec)", CServiceConfig::GetInstance()->GetChkMiddle() );
|
|
_LOG( LINF, " once : %u (hour)", CServiceConfig::GetInstance()->GetChkOnce() );
|
|
_LOG( LINF, "Diff command : %s", CServiceConfig::GetInstance()->GetDiffCommand() );
|
|
_LOG( LINF, "***********************************************************");
|
|
|
|
// Log Level 재설정. -> conf 설정대로 변경 처리.
|
|
#ifdef _DEBUG_
|
|
CLogger::GetInstance()->SetLogLevel(LDBG);
|
|
#else
|
|
CLogger::GetInstance()->SetLogLevel(CServiceConfig::GetInstance()->GetLogLevel());
|
|
#endif
|
|
|
|
// Command 및 Data 송수신 처리 관련 Listen Socket 생성
|
|
if( MakeListenSocket( g_listenSocket ) == false )
|
|
{
|
|
LOG( LERR, "Listen socket create failed. => process exit." );
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Worker Process 생성
|
|
if( MakeProcessWorker( g_listenSocket ) == false )
|
|
{
|
|
LOG( LERR, "Worker process make failed. => process exit.");
|
|
|
|
ReadyToExit();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
// Process Rename
|
|
set_ps_display(PROG_NAME": Main [monitor worker process]", false);
|
|
|
|
// Main Process : 그냥 대기
|
|
while( 1 )
|
|
{
|
|
// Main Process 는 할 일이 없다.
|
|
// => Worker 프로세스 종료시 SIGCHLD 신호로 인해 신호처리기에서 Worker Process 재성성 수행함.
|
|
// => 따라서 그냥 시그널 대기
|
|
pause();
|
|
}
|
|
|
|
// 다음의 코드는 Daemon 으로 동작하기 때문에 수행되지 않는다.
|
|
ReadyToExit();
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
|
|
// Command, Data 수신을 위한 TCP Listen socket 생성
|
|
bool MakeListenSocket( int & listenSocket )
|
|
{
|
|
listenSocket = socket( AF_INET, SOCK_STREAM, 0 );
|
|
if( listenSocket == -1 )
|
|
{
|
|
int errorNum = errno;
|
|
LOG( LERR, "Listen socket create failed.[%d][%s]", errorNum, strerror( errorNum ) );
|
|
return false;
|
|
}
|
|
|
|
struct sockaddr_in listenSockAddr;
|
|
socklen_t listenSockLen = 0;
|
|
memset( &listenSockAddr, 0x00, sizeof( listenSockAddr ) );
|
|
|
|
listenSockAddr.sin_family = AF_INET;
|
|
listenSockAddr.sin_port = htons( CServiceConfig::GetInstance()->GetOperationPort() );
|
|
listenSockAddr.sin_addr.s_addr = htonl( INADDR_ANY );
|
|
|
|
listenSockLen = sizeof( listenSockAddr );
|
|
|
|
int result = 0;
|
|
|
|
// Socket Port Reuse Option Set
|
|
int opt = 1;
|
|
result = setsockopt( listenSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof( opt ) );
|
|
if( result != 0 )
|
|
{
|
|
int errorNum = errno;
|
|
LOG( LERR, "Listen socket option[SO_REUSEADDR] set failed. [%d][%s]", errorNum, strerror( errorNum ) );
|
|
return false;
|
|
}
|
|
|
|
// Keep Alive Option set
|
|
opt = 1;
|
|
result = setsockopt( listenSocket, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof( opt ) );
|
|
if( result != 0 )
|
|
{
|
|
int errorNum = errno;
|
|
LOG( LERR, "Listen socket option[SO_KEEPALIVE] set failed. [%d][%s]", errorNum, strerror( errorNum ) );
|
|
return false;
|
|
}
|
|
|
|
// Socket Bind
|
|
result = bind( listenSocket, ( struct sockaddr * )&listenSockAddr, listenSockLen );
|
|
if( result != 0 )
|
|
{
|
|
int errorNum = errno;
|
|
LOG( LERR, "Listen socket bind failed.[%d][%s]", errorNum, strerror( errorNum ) );
|
|
return false;
|
|
}
|
|
|
|
// Socket Listen
|
|
result = listen( listenSocket, DEFAULT_ACCEPT_WAIT_COUNT );
|
|
if( result != 0 )
|
|
{
|
|
int errorNum = errno;
|
|
LOG( LERR, "Listen socket listen failed.[%d][%s]", errorNum, strerror( errorNum ) );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Worker Process 생성(fork) 처리 함수
|
|
// @return 생성 성공시 true, 실패시에는 false 를 반환.
|
|
bool MakeProcessWorker( int listenSocket )
|
|
{
|
|
// Worker 프로세스 fork
|
|
pid_t 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 함수 호출 및 종료처리.
|
|
int iResult;
|
|
iResult = WorkerMain( listenSocket );
|
|
|
|
ReadyToExit();
|
|
|
|
// WorkerMain() 함수 return 값으로 종료처리되도록 한다.
|
|
exit( iResult );
|
|
}
|
|
else
|
|
{
|
|
// Parent Process => Logging
|
|
_LOG(LINF, "Worker[%d] create success." , processId);
|
|
|
|
// 잠시 대기
|
|
solusleep(100000);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 현재 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 )
|
|
{
|
|
// 오류 발생시 true 반환하여 프로세스 실행 방지처리
|
|
fprintf(stderr, "[ERR] Process duplication check failed.[popen error][%s]\n", strerror(errno));
|
|
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() )
|
|
{
|
|
fprintf(stderr, "[info] Process duplication found. pid[%d]\n", atoi(tempPid.c_str()));
|
|
bRun = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pclose( fd );
|
|
return bRun;
|
|
}
|
|
}
|
|
|
|
|
|
// 프로세스 종료 전 처리할 종료 관련 각 작업을 일괄로 처리하기 위한 함수.
|
|
void ReadyToExit()
|
|
{
|
|
if( g_listenSocket != -1 )
|
|
{
|
|
close( g_listenSocket );
|
|
g_listenSocket = -1;
|
|
}
|
|
|
|
// Logger 객체 종료 처리.
|
|
CLogger::Exit();
|
|
|
|
return;
|
|
}
|
|
|
|
// Main Process 가 종료 Signal 을 전달받은 경우 이를 처리하기 위한 함수.
|
|
// @param nSignalNumber [in] 발생한 시그널 Number
|
|
// @return void
|
|
static void SignalMainTerminate(int nSignalNumber)
|
|
{
|
|
// Signal Number 에 따른 로깅처리.
|
|
if( nSignalNumber == SIGTERM )
|
|
{
|
|
_LOG( LINF, "Main[%d] process exit by user signal[SIGTERM]. Good Bye..", getpid() );
|
|
}
|
|
else
|
|
{
|
|
_LOG( LWAR, "Main[%d] process exit by abnormal signal[%d]. Good Bye..", getpid(), nSignalNumber );
|
|
}
|
|
|
|
// KILL - Child
|
|
kill(0, SIGTERM);
|
|
|
|
ReadyToExit();
|
|
|
|
solusleep(500000);
|
|
exit( EXIT_SUCCESS );
|
|
}
|
|
|
|
// Main Process 에서 자식 프로세스인 Worker Process 가 종료된 경우 시그널 처리를 위한 함수
|
|
// @param nSignalNumber [in] 발생한 시그널 Number
|
|
// @return void
|
|
static void SignalWorkerDead(int nSignalNumber)
|
|
{
|
|
pid_t deadPid;
|
|
int nDeadStatus;
|
|
|
|
while( (deadPid = waitpid(-1, &nDeadStatus, WNOHANG)) > 0 )
|
|
{
|
|
// Worker 프로세스가 Signal 에 의해 종료되었는가?
|
|
if( WIFSIGNALED( nDeadStatus ) )
|
|
{
|
|
_LOG( LWAR, "Worker[%d] killed by signal[%d].", deadPid, WTERMSIG(nDeadStatus));
|
|
}
|
|
else
|
|
{
|
|
_LOG( LWAR, "Worker[%d] killed. Not signal.", deadPid);
|
|
|
|
// Worker 프로세스가 EXIT_FAILURE 반환 ( 초기화 실패시 )
|
|
// 해당 내역을 화면 및 로그 상에 출력하고
|
|
// Worker 프로세스를 재생성 처리하지 않는다.
|
|
if( WIFEXITED( nDeadStatus ) )
|
|
{
|
|
if( WEXITSTATUS( nDeadStatus ) == EXIT_FAILURE )
|
|
{
|
|
_LOG( LERR, "Worker[%d] initilaize failed.", deadPid);
|
|
_LOG( LERR, "Main[%d] abnormally exit by worker initialize failed. Good Bye..", getpid());
|
|
|
|
ReadyToExit();
|
|
|
|
solusleep(500000);
|
|
exit( EXIT_SUCCESS );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Worker Process 재생성 처리.
|
|
if( MakeProcessWorker( g_listenSocket ) == false )
|
|
{
|
|
// Worker Process 재성성 실패시
|
|
// 로깅 처리 후 Main 프로세스 종료 시그널 발생
|
|
_LOG( LERR, "Worker process recreate failed. => Main process exit.");
|
|
raise( SIGTERM );
|
|
}
|
|
else
|
|
{
|
|
_LOG( LWAR, "Worker process recreate success.");
|
|
}
|
|
}
|
|
|
|
// 오류 발생시 해당 내역 로깅
|
|
if( deadPid < 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); /* desciptor 오류 발생시 Process가 죽는 것은 방지하기 위하여 설정 */
|
|
sigaction( SIGHUP, &act, NULL); /* process를 기동시킨 관리자의 로그아웃시, 또는 config reload 등의 처리 signal*/
|
|
sigaction( SIGINT, &act, NULL); /* ^C 키를 누른 경우 받는 신호 => demon 으로 기동되기 땜시 이 신호 못받음 */
|
|
sigaction( SIGQUIT, &act, NULL); /* 키보드에 의한 Abort 신호 처리 => ? */
|
|
|
|
// Child Process 인 Worker 프로세스 종료에 대한 처리기 설정.
|
|
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);
|
|
}
|