/********************************************************************* 이 름 : superdaemon - 등록된 프로그램을 실행 및 관리 버 전 : 1.0.2 - get_execinfo() 함수 추가 - read_task_list() 함수에서 task_t 정보를 모두 구성하도록 수정 1.0.1 - 자시 프로세스에서 프로그램을 식행하기 전에 부모 프로세스의 타스크 정보에 생성한 프로세스의 ID를 지정하는 시간을 벌어 주기 위해 sleep()함수를 호출 1.0.0 *********************************************************************/ #include #include #include #include #include #include #include #include "main.h" #include "projectinc.h" #define PATH_SIZE 255 #define TASK_ARRAY_SIZE 20 task_t ary_task[TASK_ARRAY_SIZE]; // 타스크 배열 int cnt_task; // 실행한 타스크의 개수 //-------------------------------------------------------------------- // desc 차일드 프로세스가 종료되면 발생하는 이벤트 핸들러 // params sig // ref 종료된 차일드 프로세스에 해당하는 ary_task 의 아이템 값을 // 수정한다. // // ary_task 아이템 값을 수정하면, main() 에서 check_task_list() // 를 호출함으로써 다시 실행하게 된다. //-------------------------------------------------------------------- void on_check_child( int sig) { pid_t pid; int result; int ndx; // 종료된 차일드 프로세스에 해당하는 ary_task 아이템을 찾는다. // task_t 아이템의 pid 를 0 으로 설정하면 check_task_lisk() 에서 // count_down 값을 감소하고, 0 이되면 다시 실행하게 된다. while( 0 < ( pid = waitpid( -1, &result, WNOHANG))) { dp( "killed child pid=%d", pid); for ( ndx= 0; ndx < cnt_task; ndx++) { dp( "checked child pid=%d %s", ary_task[ndx].pid, ary_task[ndx].full_filename); if ( pid == ary_task[ndx].pid) { ary_task[ndx].count_down = ary_task[ndx].interval; // 다시 실행하기 까지의 대기 시간(초) ary_task[ndx].pid = 0; // check_task_list() 의 대상이 되도록 0 으로 초기화 } } } } //-------------------------------------------------------------------- // desc _str 에서 첫번째 공백 문자 전까지 // 또는 공백 문자가 없을 때에는 문자열 끝까지를 _rst // 에 복사한다. // params _str 원본 문자열을 가지고 있는 변수 // _rst 문자열을 복사 받을 변수 // ret 복사 문자열 다음의 문자 위치의 포인터 //-------------------------------------------------------------------- char *str_spc( char *_str, char *_rst) { char *pos; *_rst = '\0'; // NULL 을 대입 pos = index(_str, ' '); if ( !pos) // 공백 구분자가 없다면 다음 루프로 { strcpy(_rst, _str); } else { strncpy(_rst, _str, pos-_str); _rst[pos-_str] = '\0'; while ( ' ' == *pos) // 앞에 있는 공백 삭제 pos++; } return( pos); } //-------------------------------------------------------------------- // desc _task 의 내용에 따라 새 프로세스를 만들어서 실행한다. // params _task : 실행 파일에 대한 정보 // ref 1. // _task 의 task 로부터 실행파일과 인수를 구한다. // 실행이나 인수에서 문제가 있다면 에러를 출력하고, // _task 의 error 에 에러코드를 넣어 다음 호출에도 실행이 // 되지 않도록 한다. // 2. // 인수는 20개까지 처리한다. // // 3. // 인수의 개수는 프로그램에 따라 다르므로, // *[] 를 사용하는 execv() 를 사용한다. //-------------------------------------------------------------------- void exec_task( task_t *_task) { pid_t pid; if ( ERR_NONE != _task->error) { return; } pid = fork(); if ( 0 == pid) // 자식 프로세서 라면 { dp( "1 sec wait and exec %s", _task->full_filename); sleep( 1); // 프모 프로세스에서 _task->pid = pid 가 실행이 되도록 sleep execv(_task->full_filename, _task->params); // 프로그램을 실행한다. } else if ( 0 < pid) // fork 실행에 이상이 없었다면 { dp( "********** task= %s New Pid= %d", _task->full_filename, pid); _task->pid = pid; } else // 만일 fork() 실행에 실패했다면 { dp( "Reset Counter %d", _task->interval); _task->count_down = _task->interval; } } //-------------------------------------------------------------------- // desc ary_task 목록을 확인해서 종료된 차일드가 있는지를 확인한다. // 종료된 차일드가 있다면 실행여부(에러 상태)를 확인하고, // 재 실행 시킨다. //-------------------------------------------------------------------- void check_task_list( void) { int ndx; // ary_task 목록에서 pid 가 0 인 항목은 // 종료된 차일드 이다. // for ( ndx = 0; ndx < cnt_task; ndx++) { if ( ( !ary_task[ndx].pid) // 타스크가 실행 전이고 && ( ERR_NONE == ary_task[ndx].error)) // 에러가 없다면 { if ( 0 < ary_task[ndx].count_down) // 아직 카우트 다운 중이라면 { dp( "count down :%d", ary_task[ndx].count_down); ary_task[ndx].count_down--; } else // 카운트 다운을 완료 { exec_task( &ary_task[ndx]); } } } return; } //-------------------------------------------------------------------- // 파일의 문자열에서 task_t 의 정보중 프로그램 이름과 인수를 구한다. // // _data : 파일에서 읽어 들인 내용중 디렉토리 위치부터의 문자열 // _task : 실행 파일에 대한 정보 // // typedef struct // { // char *full_filename; <- 디렉토리 포함 전체 파일 이름을 구한다. // char *params[20]; <- 프로그램 실행을 위한 인수 배열을 구한다. // : // int error; <- 내용을 구성 중에 에러가 있다면 에러 정보 // : // } task_t; // ref 1. // _task 의 task 로부터 실행파일과 인수를 구한다. // 실행이나 인수에서 문제가 있다면 에러를 출력하고, // _task 의 error 에 에러코드를 넣어 다음 호출에도 실행이 // 되지 않도록 한다. // 2. // 인수는 20개까지 처리한다. // // 3. // 인수의 개수는 프로그램에 따라 다르므로, // *[] 를 사용하는 execv() 를 사용한다. //-------------------------------------------------------------------- void get_execinfo( char *_data, task_t *_task) { char buf[PACKET_SIZE]; char exe_path[PATH_SIZE+5]; // 에러 대비 +5 char exe_name[PATH_SIZE+5]; // 에러 대비 +5 int nparam; int sz_str; pid_t pid; // 경로 명을 구한다. // 경로 명의 끝에 '/' 이 없다면 추가한다. _data = str_spc(_data, buf); if ( PATH_SIZE < strlen( buf)) // 경로명의 길이가 너무 길다. { printf( "%s : path is too long!!\n", buf); _task->error = ERR_PATH_TOO_LONG; return; } strcpy( exe_path, buf); // exe_path <- 경로명 // 경로 명의 끝의 '/' 를 확인한다. sz_str = strlen( exe_path); if ( '/' != exe_path[sz_str-1]) // 끝에 / 를 반드시 포함 { exe_path[sz_str ] = '/'; exe_path[sz_str+1] = '\0'; } // 실행 파일의 전체 이름을 구한다. _data = str_spc(_data, buf); if ( PATH_SIZE < strlen( exe_path)+strlen( buf)) // 경로와 실행파일의 이름 길이 합이 너무 길다. { printf( "%s/%s : full name is too long!!\n", exe_path, buf); _task->error = ERR_PATH_TOO_LONG; return; } strcpy( exe_name, buf); strcat( exe_path, exe_name); // exe_path <- 경로/실행파일이름 _task->full_filename = malloc( strlen( exe_path)+1); strcpy( _task->full_filename, exe_path); // 실행 파일의 존재 유무를 확인한다. if ( 0 != access(_task->full_filename, F_OK)) { printf( "%s is not exists!!\n",_task->full_filename); _task->error = ERR_PATH_TOO_LONG; return; } // 실행에 필요한 인수를 배열로 구성한다. // 첫번째 인수는 실행파일 이름으로 한다. // 마지막 인수는 NULL 이 되도록 한다. _task->params[0] = malloc( strlen( exe_name)+1); strcpy(_task->params[0], exe_name); // [0] 인수는 실행 파일의 이름이므로, // [1] 부터 프로그램의 인수를 대입한다. // params는 *[20]로 선언되어 있으므로 // 인수 개수가 19 개를 넘지 못하게 한다. nparam = 1; while (_data) { _data = str_spc(_data, buf); // 다음 인수값을 구한다. _task->params[nparam] = malloc( strlen( buf)+1); // param[nparam] <- 인수값 strcpy(_task->params[nparam], buf); nparam++; if ( 19 == nparam) // 인수가 너무 많다면 { printf( "%s has too many params!!\n", exe_name); _task->error = ERR_TOO_MANY_PARAMS; return; } } _task->params[nparam] = ( char *)0; // 마지막 아이템은 NULL _task->error = ERR_NONE; // 에러 없음 } //-------------------------------------------------------------------- // ini 파일에서 실행할 파일의 정보를 읽어 들이고, // ary_task 목록을 작성한다. //-------------------------------------------------------------------- void read_task_list( void) { FILE *fp; char *tag; char *pos; char buf[PACKET_SIZE]; char item[255]; cnt_task = 0; // 실행할 작업 개수 초기화 fp = fopen( "./superdaemon.ini", "r"); // 환경파일 open; if ( NULL == fp) { // 읽어 들일 타스크가 없음 return; } else { while( NULL != fgets( buf, PACKET_SIZE, fp)) { tag = buf; remove_white_char( tag); // 문장에 라인피드와 같은 화이트 문자를 없앤다. // 프로그램 종료 시 다음 시작까지의 시간 간격을 구한다. while ( ' ' == *tag) // 앞에 있는 공백 삭제 { tag++; } if ( ( ' ' > *tag) || // 문장의 끝이거나 ( !strncmp( tag, "//", 2)) ) // 주석문이라면 다음 행으로 { continue; } // 행의 첫 번째 데이터는 대기 시간 정보이다. // 첫번째 문자열에서 시간 정보를 구한다. tag = str_spc( tag, item); ary_task[cnt_task].interval = atoi( item); // 프로그램의 실행 정보를 구한다. // 실행 정보에는 경로와 프로그램 명이 있어야 하므로 // 최소 공백 문자가 하나 있어야 한다. // 인수에 공백 문자가 없다면 취소한다. if ( ( ' ' > *tag) || // 문장의 끝이거나 ( !strncmp( tag, "//", 2)) ) // 주석문이라면 다음 행으로 { continue; } pos = index( tag, ' '); // 공백 문자가 없다면 취소한다. if (!pos) { continue; } get_execinfo( tag, &ary_task[cnt_task]); // task 의 초기값을 초기화 한다. ary_task[cnt_task].count_down = 0; ary_task[cnt_task].pid = 0; cnt_task++; if ( TASK_ARRAY_SIZE == cnt_task) { printf( "***** Task list is over %d. *****\n", TASK_ARRAY_SIZE); break; } } } fclose( fp); } //-------------------------------------------------------------------- // 차일드 프로세서의 죽음을 확인하는 시그널 등록 //-------------------------------------------------------------------- void reg_child_signal( void) { struct sigaction sig_child; // 차일드의 죽음을 알기 위한 시그털 sig_child.sa_handler = on_check_child; sigemptyset( &sig_child.sa_mask); sig_child.sa_flags = 0; sigaction( SIGCHLD, &sig_child, 0); } //-------------------------------------------------------------------- // main procedure //-------------------------------------------------------------------- int main( int argc, char **argv) { #ifdef NDEBUG pid_t pid_daemon; // release 모드일 때만 endif 사이의 내용이 실행된다. pid_daemon = fork(); if ( 0 > pid_daemon) // fork() 실행에 실패했다면 { printf( "ERR: fork() failed\n"); // 실행 에러를 알린다. exit( 1); } else if( 0 != pid_daemon) // 부모 프로세스 라면 { exit( 0); // 종료한다. } #endif reg_child_signal(); // child 시그널 등록 read_task_list(); // 실행할 정보 목록을 작성한다. while( 1 ) { check_task_list(); // 작업 상태를 확인한다. sleep( 1); // 1 초를 대기 } }