#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include /* chmod(2) */ #include #include #include /* strdup(3) */ #include /* basename(3) */ #include /* tolower(3) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined SIGCHLD && defined SIGCLD # define SIGCHLD SIGCLD #endif #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include /********************* Default File Names: */ const char *CONFIG_FILE = CSVN_CONFIG; const char *BCF_FILE = CSVN_HOME_DIR "/" CSVN_PROGRAM ".bcf"; const char *LOG_FILE = CSCM_LOG_DIR "/" CSVN_PROGRAM "d.log"; const char *PID_FILE = CSCM_PID_DIR "/" CSVN_PROGRAM "d.pid"; const char *SHM_BCF = CSVN_SHM_BCF; char *program = PROGRAM_DAEMON; int exit_status = EXIT_SUCCESS; /* errors counter */ FILE *config = NULL; char *config_fname = NULL; char *log_fname = NULL; char *pid_fname = NULL; static sigset_t blockmask; static int run_as_daemon = 0; static int test_config_file = 0; static int config_inotify = 0; static void free_resources( void ); /*********************** Inotify declarations: */ #define IN_BUFFER_SIZE (7 * (sizeof(struct inotify_event) + NAME_MAX + 1)) static int inotify_fd = 0, wd = 0; static struct inotify_event *event = NULL; static char buf[IN_BUFFER_SIZE] __attribute__ ((aligned(8))); static int check_event( struct inotify_event *e ) { if( e->mask & IN_CLOSE_WRITE ) return 1; return 0; } static void init_config_inotify( void ) { inotify_fd = inotify_init(); /* Create inotify instance */ if( inotify_fd == -1 ) { ERROR( "Cannot initialize inotify for file: %s", config_fname ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 1 ); } wd = inotify_add_watch( inotify_fd, config_fname, IN_CLOSE_WRITE ); if( wd == -1 ) { ERROR( "Cannot add inotify watch for file: %s", config_fname ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 1 ); } } static void fini_config_inotify( void ) { if( wd > 0 ) { (void)inotify_rm_watch( inotify_fd, wd ); wd = 0; } if( inotify_fd > 0 ) { (void)close( inotify_fd ); inotify_fd = 0; } } static void free_resources( void ) { fini_config_inotify(); if( log_fname ) { if( errlog ) { fclose( errlog ); errlog = NULL; } free( log_fname ); log_fname = NULL; } if( config_fname ) { if( config ) { fclose( config ); config = NULL; } free( config_fname ); config_fname = NULL; } bcf_shm_free(); if( bcf_fname ) { if( bcf ) { fclose( bcf ); bcf = NULL; } (void)unlink( (const char *)bcf_fname ); free( bcf_fname ); bcf_fname = NULL; } if( pid_fname ) { (void)unlink( (const char *)pid_fname ); free( pid_fname ); pid_fname = NULL; } } void usage() { free_resources(); fprintf( stdout, "\n" ); fprintf( stdout, "Usage: %s [options]\n", program ); fprintf( stdout, "\n" ); fprintf( stdout, "Start cScm Configuration Daemon to read config file.\n" ); fprintf( stdout, "\n" ); fprintf( stdout, "Options:\n" ); fprintf( stdout, " -h,--help Display this information.\n" ); fprintf( stdout, " -v,--version Display the version of %s utility.\n", program ); fprintf( stdout, " -d,--daemonize Run in background as a daemon.\n" ); fprintf( stdout, " -i,--inotify Notify about configuration changes.\n" ); fprintf( stdout, " -b,--bcf= Binary config file. Default: %s.\n", BCF_FILE ); fprintf( stdout, " -c,--config= Config file. Default: %s.\n", CONFIG_FILE ); fprintf( stdout, " -l,--log= Log file. Default: %s.\n", LOG_FILE ); fprintf( stdout, " -p,--pid= PID file. Default: %s.\n", PID_FILE ); fprintf( stdout, " -s,--scm= SCM 'svn' or 'git'. Default: 'svn'.\n" ); fprintf( stdout, " -t,--test Test config file and exit.\n" ); fprintf( stdout, "\n" ); exit( EXIT_FAILURE ); } void to_lowercase( char *s ) { char *p = s; while( p && *p ) { int c = *p; *p = tolower( c ); ++p; } } void to_uppercase( char *s ) { char *p = s; while( p && *p ) { int c = *p; *p = toupper( c ); ++p; } } void version() { char *upper = NULL; upper = (char *)alloca( strlen( program ) + 1 ); strcpy( (char *)upper, (const char *)program ); to_uppercase( upper ); fprintf( stdout, "%s (%s) %s\n", program, upper, PROGRAM_VERSION ); fprintf( stdout, "Copyright (C) 2022 Andrey V.Kosteltsev.\n" ); fprintf( stdout, "This is free software. There is NO warranty; not even\n" ); fprintf( stdout, "for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" ); fprintf( stdout, "\n" ); free_resources(); exit( EXIT_SUCCESS ); } void get_args( int argc, char *argv[] ) { const char* short_options = "hvdic:l:p:s:t"; const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'v' }, { "daemonize", no_argument, NULL, 'd' }, { "inotify", no_argument, NULL, 'i' }, { "bcf", required_argument, NULL, 'b' }, { "config", required_argument, NULL, 'c' }, { "log", required_argument, NULL, 'l' }, { "pid", required_argument, NULL, 'p' }, { "scm", required_argument, NULL, 's' }, { "test", no_argument, NULL, 't' }, { NULL, 0, NULL, 0 } }; int ret; int option_index = 0; while( (ret = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) { switch( ret ) { case 'h': { usage(); break; } case 'v': { version(); break; } case 'd': { run_as_daemon = 1; break; } case 'i': { config_inotify = 1; break; } case 't': { test_config_file = 1; break; } case 'b': { if( optarg != NULL ) { bcf_fname = strdup( optarg ); } else /* option is present but without value */ usage(); break; } case 'c': { if( optarg != NULL ) { config_fname = strdup( optarg ); } else /* option is present but without value */ usage(); break; } case 'l': { if( optarg != NULL ) { log_fname = strdup( optarg ); } else /* option is present but without value */ usage(); break; } case 'p': { if( optarg != NULL ) { pid_fname = strdup( optarg ); } else /* option is present but without value */ usage(); break; } case 's': { if( optarg != NULL ) { if( *optarg && !strncmp( optarg, "git", 3 ) ) { CONFIG_FILE = CGIT_CONFIG; BCF_FILE = CGIT_HOME_DIR "/" CGIT_PROGRAM ".bcf"; LOG_FILE = CSCM_LOG_DIR "/" CGIT_PROGRAM "d.log"; PID_FILE = CSCM_PID_DIR "/" CGIT_PROGRAM "d.pid"; SHM_BCF = CGIT_SHM_BCF; } } else /* option is present but without value */ usage(); break; } case '?': default: { usage(); break; } } } } static int is_directory( const char *path ) { struct stat st; return ( !stat( path, &st ) && S_ISDIR(st.st_mode) ); } static void logpid( void ) { FILE *fp; if( !pid_fname ) { /* allocate memory for PID file name */ pid_fname = (char *)malloc( strlen( PID_FILE ) + 1 ); if( !pid_fname ) { FATAL_ERROR( "Cannot allocate memory for PID file name: %s", PID_FILE ); } (void)strcpy( pid_fname, PID_FILE ); } /* Create CSCM_PID_DIR if not exists: */ { char *pid_dir = NULL, *dir = strdup( pid_fname ); pid_dir = dirname( dir ); if( !is_directory( (const char *)pid_dir ) ) { /* Create if not exists */ if( 0 != mkdir( (const char *)pid_dir, 0755 ) ) { FATAL_ERROR( "Cannot create directory for PID file: %s", PID_FILE ); } } free( dir ); } if( (fp = fopen( pid_fname, "w" )) != NULL ) { fprintf( fp, "%u\n", getpid() ); (void)fclose( fp ); } } void init_logs( void ) { /* print to stderr until the errlog file is open */ errlog = stderr; if( !log_fname ) { /* allocate memory for LOG file name */ log_fname = (char *)malloc( strlen( LOG_FILE ) + 1 ); if( !log_fname ) { FATAL_ERROR( "Cannot open log file: %s", LOG_FILE ); } (void)strcpy( log_fname, LOG_FILE ); } /* Create CSCM_LOG_DIR if not exists: */ { char *log_dir = NULL, *dir = strdup( log_fname ); log_dir = dirname( dir ); if( !is_directory( (const char *)log_dir ) ) { /* Create if not exists */ if( 0 != mkdir( (const char *)log_dir, 0755 ) ) { FATAL_ERROR( "Cannot create directory for log file: %s", LOG_FILE ); } } free( dir ); } /* open LOG file */ errlog = fopen( (const char *)log_fname, "a" ); if( !errlog ) { errlog = stderr; FATAL_ERROR( "Cannot open log file: %s", log_fname ); } } void open_config_file( void ) { if( !config_fname ) { /* allocate memory for CONFIG file name */ config_fname = (char *)malloc( strlen( CONFIG_FILE ) + 1 ); if( !config_fname ) { FATAL_ERROR( "Cannot open config file: %s", CONFIG_FILE ); if( log_fname ) { fclose( errlog ); free( log_fname ); } } (void)strcpy( config_fname, CONFIG_FILE ); } /* open CONFIG file */ config = fopen( (const char *)config_fname, "r" ); if( !config ) { FATAL_ERROR( "Cannot open config file: %s", config_fname ); if( log_fname ) { fclose( errlog ); free( log_fname ); } } if( !bcf_fname ) { /* allocate memory for BCF file name */ bcf_fname = (char *)malloc( strlen( BCF_FILE ) + 1 ); if( !bcf_fname ) { FATAL_ERROR( "Cannot allocate memory gor BCF file name: %s", BCF_FILE ); if( log_fname ) { fclose( errlog ); free( log_fname ); } } (void)strcpy( bcf_fname, BCF_FILE ); } } void close_config_file( void ) { if( config ) { fclose( config ); config = NULL; } } void parse_config_file( void ) { int ret; /*********************************** Blick signals until parser works: */ (void)sigprocmask( SIG_BLOCK, (const sigset_t *)&blockmask, (sigset_t *)NULL ); open_config_file(); init_symtab(); init_lex(); if( !(ret = yyparse()) ) { reverse_symlist( (SYMBOL **)&symlist ); remove_consts( (SYMBOL **)&symlist ); (void)write_binary_config(); } else { bcf_shm_free(); if( bcf_fname ) { if( bcf ) { fclose( bcf ); bcf = NULL; } (void)unlink( (const char *)bcf_fname ); } } fini_lex(); fini_symtab(); close_config_file(); (void)sigprocmask( SIG_UNBLOCK, (const sigset_t *)&blockmask, (sigset_t *)NULL ); if( test_config_file ) { if( ret ) { fprintf( stdout, "%s: %s: Config file is not correct. See: %s\n", program, config_fname, log_fname ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 1 ); } else { fprintf( stdout, "%s: %s: Config file is correct.\n", program, config_fname ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 0 ); } } } void fatal_error_actions( void ) { free_resources(); } void sigint( int signum ) { (void)signum; LOG( "received SIGINT: free resources at exit" ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 0 ); } void sigusr( int signum ) { if( signum == SIGUSR1 ) { LOG( "signal USR1 has been received" ); } else if( signum == SIGUSR2 ) { LOG( "signal USR2 has been received" ); } } void sighup( int signum ) { (void)signum; LOG( "received SIGHUP: parse config file: %s", config_fname ); parse_config_file(); } static void set_signal_handlers() { struct sigaction sa; sigset_t set; memset( &sa, 0, sizeof( sa ) ); sa.sa_handler = sighup; /* HUP: read config file */ sa.sa_flags = SA_RESTART; sigemptyset( &set ); sigaddset( &set, SIGHUP ); sa.sa_mask = set; sigaction( SIGHUP, &sa, NULL ); memset( &sa, 0, sizeof( sa ) ); sa.sa_handler = sigusr; /* USR1, USR2 */ sa.sa_flags = SA_RESTART; sigemptyset( &set ); sigaddset( &set, SIGUSR1 ); sigaddset( &set, SIGUSR2 ); sa.sa_mask = set; sigaction( SIGUSR1, &sa, NULL ); sigaction( SIGUSR2, &sa, NULL ); memset( &sa, 0, sizeof( sa ) ); sa.sa_handler = sigint; /* TERM, INT */ sa.sa_flags = SA_RESTART; sigemptyset( &set ); sigaddset( &set, SIGTERM ); sigaddset( &set, SIGINT ); sa.sa_mask = set; sigaction( SIGTERM, &sa, NULL ); sigaction( SIGINT, &sa, NULL ); memset( &sa, 0, sizeof( sa ) ); /* ignore SIGPIPE */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigaction( SIGPIPE, &sa, NULL ); /* на случай блокировки сигналов с помощью sigprocmask(): */ sigemptyset( &blockmask ); sigaddset( &blockmask, SIGHUP ); sigaddset( &blockmask, SIGUSR1 ); sigaddset( &blockmask, SIGUSR2 ); sigaddset( &blockmask, SIGTERM ); sigaddset( &blockmask, SIGINT ); /* System V fork+wait does not work if SIGCHLD is ignored */ signal( SIGCHLD, SIG_DFL ); } int main( int argc, char *argv[] ) { gid_t gid; set_signal_handlers(); gid = getgid(); setgroups( 1, &gid ); fatal_error_hook = fatal_error_actions; program = basename( argv[0] ); get_args( argc, argv ); if( getppid() != 1 ) { if( run_as_daemon ) daemon( 1, 0 ); } init_logs(); logpid(); LOG( "Start cScm Configuration Daemon..." ); parse_config_file(); if( config_inotify ) init_config_inotify(); for( ;; ) { int max_sd; struct timeval timeout; fd_set listen_set; /************************************* Waiting for events from inotify_fd: */ max_sd = inotify_fd + 1; FD_ZERO( &listen_set ); FD_SET( inotify_fd, &listen_set ); do { int rc; /********************************************* Initialize the timeval struct to 5 seconds: */ timeout.tv_sec = 5; timeout.tv_usec = 0; rc = select( max_sd, &listen_set, NULL, NULL, &timeout ); /***************************************** Check to see if the select call failed: */ if( rc < 0 && errno != EINTR ) { WARNING( "%s: inotify select() failed", config_fname ); break; } /************************************************ Check to see if the 5 second time out expired: */ if( rc == 0 ) { /* Here we can output some log info. */ break; } if( FD_ISSET( inotify_fd, &listen_set ) ) { ssize_t n; char *p; n = read( inotify_fd, buf, IN_BUFFER_SIZE ); if( n == 0 ) { ERROR( "%s: read() from inotify file descriptor returned '0'", config_fname ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 1 ); } if( n == -1 ) { ERROR( "%s: read() from inotify file descriptor returned '-1'", config_fname ); LOG( "Stop cScm Configuration Daemon." ); free_resources(); exit( 1 ); } for( p = buf; p < buf + n; ) { event = (struct inotify_event *)p; /************************************************************ в принципе, нам хватает одного события и, если мы получили нужное событие и перечитали config file, то здесь мы можем выйти из цикла чтения событий с помощью break */ if( check_event( event ) ) { LOG( "Config file '%s' has been changed. Read new config content...", config_fname ); parse_config_file(); break; } p += sizeof(struct inotify_event) + event->len; } } /* End if( FD_ISSET() ) */ } while( 1 ); /* Здесь мы можем выполнить действия, которые необходимы в том случае, если в течение 5-и секунд мы не получили ни одного сигнала об изменении дескриптора inotify_fd. */ { sleep( 5 ); } } /* End of waiting for( ;; ) */ LOG( "Stop cScm Configuration Daemon." ); free_resources(); return 0; }