#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;
}