diff options
Diffstat (limited to 'cscmd/main.c')
-rw-r--r-- | cscmd/main.c | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/cscmd/main.c b/cscmd/main.c new file mode 100644 index 0000000..9058046 --- /dev/null +++ b/cscmd/main.c @@ -0,0 +1,737 @@ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysinfo.h> +#include <sys/types.h> +#include <stdint.h> +#include <dirent.h> +#include <sys/stat.h> /* chmod(2) */ +#include <fcntl.h> +#include <limits.h> +#include <string.h> /* strdup(3) */ +#include <libgen.h> /* basename(3) */ +#include <ctype.h> /* tolower(3) */ +#include <errno.h> +#include <time.h> +#include <sys/time.h> +#include <pwd.h> +#include <grp.h> +#include <stdarg.h> +#include <unistd.h> + +#include <sys/inotify.h> + +#include <locale.h> +#include <wchar.h> +#include <wctype.h> + +#include <sys/wait.h> + +#include <sys/resource.h> + +#include <signal.h> +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include <getopt.h> + + +#include <daemon.h> +#include <msglog.h> +#include <error.h> +#include <utf8ing.h> +#include <symtab.h> +#include <parse.h> +#include <lex.h> +#include <bconf.h> + +#include <defs.h> + + +/********************* + 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=<BCF_FILE> Binary config file. Default: %s.\n", BCF_FILE ); + fprintf( stdout, " -c,--config=<CONFIG_FILE> Config file. Default: %s.\n", CONFIG_FILE ); + fprintf( stdout, " -l,--log=<LOG_FILE> Log file. Default: %s.\n", LOG_FILE ); + fprintf( stdout, " -p,--pid=<PID_FILE> PID file. Default: %s.\n", PID_FILE ); + fprintf( stdout, " -s,--scm=<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; +} |