summaryrefslogtreecommitdiff
path: root/cscmd
diff options
context:
space:
mode:
authorkx <kx@radix.pro>2023-03-24 02:53:04 +0300
committerkx <kx@radix.pro>2023-03-24 02:53:04 +0300
commit12c7b1c5658602269da2f5b75835ec0f5fab8890 (patch)
tree93f6f6b85830af69743d5ebda902d4305bf23f4f /cscmd
parent4e72ffe940d9aff7c019d37a6459e765902c1fae (diff)
downloadcscm-trunk.tar.xz
Version 0.1.4HEADcscm-0.1.4trunk
Diffstat (limited to 'cscmd')
-rw-r--r--cscmd/Makefile.am63
-rw-r--r--cscmd/README.in5
-rw-r--r--cscmd/bconf.c477
-rw-r--r--cscmd/bconf.h25
-rw-r--r--cscmd/cscmd.8.in66
-rw-r--r--cscmd/daemon.c39
-rw-r--r--cscmd/daemon.h7
-rw-r--r--cscmd/error.c92
-rw-r--r--cscmd/error.h25
-rw-r--r--cscmd/lex.c815
-rw-r--r--cscmd/lex.h26
-rw-r--r--cscmd/logrotate.in8
-rw-r--r--cscmd/main.c737
-rw-r--r--cscmd/main.h21
-rw-r--r--cscmd/msglog.c70
-rw-r--r--cscmd/msglog.h98
-rw-r--r--cscmd/parse.y107
-rw-r--r--cscmd/rc.cscmd.in96
-rw-r--r--cscmd/symtab.c471
-rw-r--r--cscmd/symtab.h67
-rw-r--r--cscmd/utf8ing.c121
-rw-r--r--cscmd/utf8ing.h22
-rw-r--r--cscmd/xalloc.c36
-rw-r--r--cscmd/xalloc.h19
24 files changed, 3513 insertions, 0 deletions
diff --git a/cscmd/Makefile.am b/cscmd/Makefile.am
new file mode 100644
index 0000000..6fdd496
--- /dev/null
+++ b/cscmd/Makefile.am
@@ -0,0 +1,63 @@
+
+AM_CPPFLAGS = -I@top_srcdir@ -DYYERROR_VERBOSE=1
+
+sbin_PROGRAMS = cscmd
+
+cscmd_SOURCES = bconf.c daemon.c error.c lex.c main.c msglog.c symtab.c utf8ing.c xalloc.c
+
+noinst_HEADERS = bconf.h daemon.h error.h lex.h main.h msglog.h symtab.h utf8ing.h xalloc.h
+
+control_DATA = rc.cgitd rc.csvnd
+logrotate_DATA = cgit csvn
+
+csvndhome_DATA = README.csvn
+cgitdhome_DATA = README.cgit
+
+man8_MANS = cscmd.8
+notrans_nodist_man8_MANS = cscmd.8
+
+nodist_cscmd_SOURCES = parse.c parse.h rc.csvnd csvn rc.cgitd cgit
+BUILT_SOURCES = parse.c parse.h rc.csvnd csvn rc.cgitd cgit
+
+parse.c: parse.y
+ @BISON@ -lvy --defines=parse.h -o $@ $^
+
+README.csvn: README.in
+ cat $^ | sed "s,\@CSCM_PROGRAM\@,${CSVN_PROGRAM},g" | \
+ sed "s,\@CSCM_PROGRAM_NAME\@,${CSVN_PROGRAM_NAME},g" > $@
+
+README.cgit: README.in
+ cat $^ | sed "s,\@CSCM_PROGRAM\@,${CGIT_PROGRAM},g" | \
+ sed "s,\@CSCM_PROGRAM_NAME\@,${CGIT_PROGRAM_NAME},g" > $@
+
+rc.csvnd: rc.cscmd.in
+ cat $^ | sed "s,\@sbindir\@,${sbindir},g" | \
+ sed "s,\@CSCM_NAME\@,${CSVN_NAME},g" | \
+ sed "s,\@CSCM_CONFIG\@,${CSVN_CONFIG},g" | \
+ sed "s,\@CSCM_HOME_PATH\@,${CSCM_HOME_PATH},g" | \
+ sed "s,\@CSCM_PID_DIR\@,${CSCM_PID_DIR},g" | \
+ sed "s,\@CSCM_LOG_DIR\@,${CSCM_LOG_DIR},g" | \
+ sed "s,\@CSCM_PROGRAM\@,${CSVN_PROGRAM},g" | \
+ sed "s,\@CSCM_PROGRAM_NAME\@,${CSVN_PROGRAM_NAME},g" | \
+ sed "s,\@PROGRAM_DAEMON\@,${PROGRAM_DAEMON},g" > $@
+
+rc.cgitd: rc.cscmd.in
+ cat $^ | sed "s,\@sbindir\@,${sbindir},g" | \
+ sed "s,\@CSCM_NAME\@,${CGIT_NAME},g" | \
+ sed "s,\@CSCM_CONFIG\@,${CGIT_CONFIG},g" | \
+ sed "s,\@CSCM_HOME_PATH\@,${CSCM_HOME_PATH},g" | \
+ sed "s,\@CSCM_PID_DIR\@,${CSCM_PID_DIR},g" | \
+ sed "s,\@CSCM_LOG_DIR\@,${CSCM_LOG_DIR},g" | \
+ sed "s,\@CSCM_PROGRAM\@,${CGIT_PROGRAM},g" | \
+ sed "s,\@CSCM_PROGRAM_NAME\@,${CGIT_PROGRAM_NAME},g" | \
+ sed "s,\@PROGRAM_DAEMON\@,${PROGRAM_DAEMON},g" > $@
+
+csvn: logrotate.in
+ cat $^ | sed "s,\@CSCM_LOG_DIR\@,${CSCM_LOG_DIR},g" | \
+ sed "s,\@CSCM_PROGRAM\@,${CSVN_PROGRAM},g" > $@
+
+cgit: logrotate.in
+ cat $^ | sed "s,\@CSCM_LOG_DIR\@,${CSCM_LOG_DIR},g" | \
+ sed "s,\@CSCM_PROGRAM\@,${CGIT_PROGRAM},g" > $@
+
+CLEANFILES = parse.c parse.h parse.output README.csvn rc.csvnd csvn README.cgit rc.cgitd cgit cscmd.8
diff --git a/cscmd/README.in b/cscmd/README.in
new file mode 100644
index 0000000..185a415
--- /dev/null
+++ b/cscmd/README.in
@@ -0,0 +1,5 @@
+
+@CSCM_PROGRAM_NAME@ Daemon HOME directory:
+==========================
+
+@CSCM_PROGRAM@.bcf - is a binary config file created by @CSCM_PROGRAM_NAME@ Daemon
diff --git a/cscmd/bconf.c b/cscmd/bconf.c
new file mode 100644
index 0000000..8576805
--- /dev/null
+++ b/cscmd/bconf.c
@@ -0,0 +1,477 @@
+
+#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 <sys/file.h>
+#include <sys/mman.h>
+#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 <endian.h>
+
+#include <error.h>
+#include <msglog.h>
+#include <xalloc.h>
+#include <symtab.h>
+#include <parse.h>
+#include <bconf.h>
+
+#include <defs.h>
+
+extern const char *SHM_BCF;
+
+FILE *bcf = NULL;
+char *bcf_fname = NULL;
+
+static void *bcf_shm_address = NULL;
+static int bcf_shm_fd = -1;
+
+static int snum, rnum, dnum, global_dnum, global_rnum, indent;
+
+static unsigned char *ftab, *stab = NULL;
+static Bcf32_Off stabsz = 0;
+
+static Bcf32_Off shoff; /* section header table’s file offset in bytes */
+static Bcf32_Off rhoff; /* repository header table’s file offset in bytes */
+static Bcf32_Off dtoff; /* data entries table’s file offset in bytes */
+static Bcf32_Off stoff; /* string table’s file offset in bytes */
+
+static Bcf32_fhdr *fhdr;
+static Bcf32_shdr *shdr;
+static Bcf32_rhdr *rhdr;
+static Bcf32_dntr *dntr;
+
+
+void bcf_shm_free( void )
+{
+ if( bcf_shm_address )
+ {
+ struct stat st;
+
+ if( !fstat( bcf_shm_fd, (struct stat *)&st ) )
+ {
+ (void)munmap( bcf_shm_address, (size_t)st.st_size );
+ bcf_shm_address = NULL;
+ bcf_shm_fd = shm_unlink( SHM_BCF );
+ }
+ }
+ bcf_shm_address = NULL;
+ bcf_shm_fd = -1;
+}
+
+/************************************************
+ Функции создания BCF файла по таблице symlist:
+ */
+
+static Bcf32_Off extend_strtab( const char *val )
+{
+ Bcf32_Off off = 1;
+ Bcf32_Off len = 0;
+ unsigned char *dest = NULL;
+
+ if( !stab )
+ {
+ /*************************************
+ The first string in strtab is equal
+ to "" for empty strings.
+ */
+ stabsz = (Bcf32_Off)(strlen( val ) + 2);
+ stab = (unsigned char *)xmalloc( (size_t)stabsz );
+ (void)strncpy( (char *)&stab[1], val, stabsz - 1 );
+ return off;
+ }
+ else
+ {
+ off = stabsz;
+ len = (Bcf32_Off)(strlen( val ) + 1);
+ stabsz += len;
+
+ stab = (unsigned char *)xrealloc( (void *)stab, (size_t)stabsz );
+ dest = &stab[off];
+ (void)strncpy( (char *)dest, val, len );
+ return off;
+ }
+}
+
+static void count_symbols( int *snum, int *rnum, int *dnum, int *gdts, int *grps, SYMBOL *list )
+{
+ SYMBOL *head = list;
+
+ if( !head ) return;
+
+ while( head )
+ {
+ /************************************
+ count symbols( head ):
+ */
+ switch( head->type )
+ {
+ case STRING:
+ if( indent == 0 ) *gdts += 1;
+ *dnum += 1;
+ break;
+ case PATH:
+ if( indent == 0 ) *gdts += 1;
+ *dnum += 1;
+ break;
+ case NUMERICAL:
+ if( indent == 0 ) *gdts += 1;
+ *dnum += 1;
+ break;
+
+ case SECTION:
+ *snum += 1;
+ break;
+ case REPO:
+ if( indent == 0 ) *grps += 1;
+ *rnum += 1;
+ break;
+
+ default:
+ break;
+ }
+
+ if( head->list ) { indent += 1; count_symbols( snum, rnum, dnum, gdts, grps, head->list ); }
+ /*
+ End of count symbols( head ).
+ ************************************/
+
+ head = head->next;
+ }
+}
+
+static void write_global_data( SYMBOL *list )
+{
+ SYMBOL *head = list;
+
+ if( !head ) return;
+
+ while( head )
+ {
+ /************************************
+ global symbols( head ):
+ */
+ switch( head->type )
+ {
+ case STRING:
+ dntr->d_name = extend_strtab( (const char *)head->name );
+ dntr->d_type = DT_STRING;
+ dntr->_v.d_valptr = extend_strtab( (const char *)head->u.string );
+ dtoff += (Bcf32_Off)sizeof( Bcf32_dntr );
+ ++dntr;
+ break;
+ case PATH:
+ dntr->d_name = extend_strtab( (const char *)head->name );
+ dntr->d_type = DT_PATH;
+ dntr->_v.d_valptr = extend_strtab( (const char *)head->u.path );
+ dtoff += (Bcf32_Off)sizeof( Bcf32_dntr );
+ ++dntr;
+ break;
+ case NUMERICAL:
+ dntr->d_name = extend_strtab( (const char *)head->name );
+ dntr->d_type = DT_NUMERICAL;
+ dntr->_v.d_value = head->u.value;
+ dtoff += (Bcf32_Off)sizeof( Bcf32_dntr );
+ ++dntr;
+ break;
+
+ default:
+ break;
+ }
+ /*
+ End of global symbols( head ).
+ ************************************/
+
+ head = head->next;
+ }
+}
+
+static Bcf32_Half write_repo_data( SYMBOL *list )
+{
+ Bcf32_Half cntr = 0;
+ SYMBOL *head = list;
+
+ if( !head ) return cntr;
+
+ while( head )
+ {
+ /************************************
+ symbols( head ):
+ */
+ switch( head->type )
+ {
+ case STRING:
+ dntr->d_name = extend_strtab( (const char *)head->name );
+ dntr->d_type = DT_STRING;
+ dntr->_v.d_valptr = extend_strtab( (const char *)head->u.string );
+ dtoff += (Bcf32_Off)sizeof( Bcf32_dntr );
+ ++dntr;
+ ++cntr;
+ break;
+ case PATH:
+ dntr->d_name = extend_strtab( (const char *)head->name );
+ dntr->d_type = DT_PATH;
+ dntr->_v.d_valptr = extend_strtab( (const char *)head->u.path );
+ dtoff += (Bcf32_Off)sizeof( Bcf32_dntr );
+ ++dntr;
+ ++cntr;
+ break;
+ case NUMERICAL:
+ dntr->d_name = extend_strtab( (const char *)head->name );
+ dntr->d_type = DT_NUMERICAL;
+ dntr->_v.d_value = head->u.value;
+ dtoff += (Bcf32_Off)sizeof( Bcf32_dntr );
+ ++dntr;
+ ++cntr;
+ break;
+
+ default:
+ break;
+ }
+ /*
+ End of symbols( head ).
+ ************************************/
+
+ head = head->next;
+ }
+
+ return cntr;
+}
+
+static void write_global_repos( SYMBOL *list )
+{
+ SYMBOL *head = list;
+
+ if( !head ) return;
+
+ while( head )
+ {
+ /************************************
+ global symbols( head ):
+ */
+ if( head->type == REPO )
+ {
+ rhdr->r_rhdr = extend_strtab( (const char *)head->u.path );
+ rhdr->r_rdata = dtoff;
+ rhdr->r_dnum = write_repo_data( head->list );
+
+ rhoff += (Bcf32_Off)sizeof( Bcf32_rhdr );
+ ++rhdr;
+ }
+ /*
+ End of global symbols( head ).
+ ************************************/
+
+ head = head->next;
+ }
+}
+
+
+static Bcf32_Half write_repos( SYMBOL *list )
+{
+ Bcf32_Half cntr = 0;
+ SYMBOL *head = list;
+
+ if( !head ) return cntr;
+
+ while( head )
+ {
+ /************************************
+ symbols( head ):
+ */
+ if( head->type == REPO )
+ {
+ rhdr->r_rhdr = extend_strtab( (const char *)head->u.path );
+ rhdr->r_rdata = dtoff;
+ rhdr->r_dnum = write_repo_data( head->list );
+
+ rhoff += (Bcf32_Off)sizeof( Bcf32_rhdr );
+ ++rhdr;
+ ++cntr;
+ }
+ /*
+ End of symbols( head ).
+ ************************************/
+
+ head = head->next;
+ }
+
+ return cntr;
+}
+
+static void write_sections( SYMBOL *list )
+{
+ SYMBOL *head = list;
+
+ if( !head ) return;
+
+ while( head )
+ {
+ /************************************
+ global symbols( head ):
+ */
+ if( head->type == SECTION )
+ {
+ (void)strncpy( (char *)shdr->s_name, SMAG_REPOS, (size_t)SI_NIDENT );
+ shdr->s_type = ST_REPOS;
+ shdr->s_shdr = extend_strtab( (const char *)head->u.string );
+ shdr->s_sdata = rhoff;
+ shdr->s_dnum = write_repos( head->list );;
+
+ shoff += (Bcf32_Off)sizeof( Bcf32_shdr );
+ ++shdr;
+ }
+ /*
+ End of global symbols( head ).
+ ************************************/
+
+ head = head->next;
+ }
+}
+
+
+int write_binary_config( void )
+{
+ int ret = 0;
+
+ ftab = NULL;
+ stab = NULL;
+
+ snum = 0, rnum = 0, dnum = 0, global_dnum = 0, global_rnum = 0, indent = 0, stabsz = 0;
+ fhdr = NULL, shdr = NULL, rhdr = NULL, dntr = NULL;
+ shoff = 0, rhoff = 0, dtoff = 0, stoff = 0;
+
+ count_symbols( &snum, &rnum, &dnum, &global_dnum, &global_rnum, symlist );
+
+ if( global_dnum ) snum += 1; /* add .global section for global variables */
+ if( global_rnum ) snum += 1; /* add noname .repos section for global repositories */
+
+ shoff = (Bcf32_Off)sizeof( Bcf32_fhdr );
+ rhoff = (Bcf32_Off)(shoff + snum * sizeof( Bcf32_shdr ));
+ dtoff = (Bcf32_Off)(rhoff + rnum * sizeof( Bcf32_rhdr ));
+ stoff = (Bcf32_Off)(dtoff + dnum * sizeof( Bcf32_dntr ));
+
+ ftab = (unsigned char *)xmalloc( (size_t)stoff );
+
+ /******************
+ Fill File Header
+ */
+ fhdr = (Bcf32_fhdr *)ftab;
+
+ (void)strncpy( (char *)fhdr->b_ident, BCFMAG, (size_t)SZBCFMAG );
+ fhdr->b_ident[BI_CLASS] = BCF_CLASS_32;
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ fhdr->b_ident[BI_DATA] = BCF_DATA_LSB;
+#else
+ fhdr->b_ident[BI_DATA] = BCF_DATA_MSB;
+#endif
+ fhdr->b_ident[BI_VERSION] = BV_CURRENT;
+ fhdr->b_ident[BI_PAD] = BCF_PAD;
+
+ fhdr->b_hsize = (Bcf32_Half)sizeof( Bcf32_fhdr );
+ fhdr->b_shoff = (Bcf32_Off)shoff;
+ fhdr->b_shentsize = (Bcf32_Half)sizeof( Bcf32_shdr );
+ fhdr->b_shnum = (Bcf32_Half)snum;
+ fhdr->b_rhoff = (Bcf32_Off)rhoff;
+ fhdr->b_rhentsize = (Bcf32_Half)sizeof( Bcf32_rhdr );
+ fhdr->b_rhnum = (Bcf32_Half)rnum;
+ fhdr->b_dtoff = (Bcf32_Off)dtoff;
+ fhdr->b_dtentsize = (Bcf32_Half)sizeof( Bcf32_dntr );
+ fhdr->b_dtnum = (Bcf32_Half)dnum;
+ fhdr->b_stoff = (Bcf32_Off)stoff;
+
+ shdr = (Bcf32_shdr *)&ftab[shoff];
+ rhdr = (Bcf32_rhdr *)&ftab[rhoff];
+ dntr = (Bcf32_dntr *)&ftab[dtoff];
+
+ if( global_dnum )
+ {
+ (void)strncpy( (char *)shdr->s_name, SMAG_GLOBAL, (size_t)SI_NIDENT );
+ shdr->s_type = ST_GLOBAL;
+ shdr->s_shdr = 0; /* Global section is always a first noname .global section */
+ shdr->s_sdata = fhdr->b_dtoff;
+ shdr->s_dnum = (Bcf32_Half)global_dnum;
+
+ write_global_data( symlist );
+
+ shoff += (Bcf32_Off)sizeof( Bcf32_shdr );
+ ++shdr;
+ }
+
+ if( global_rnum )
+ {
+ (void)strncpy( (char *)shdr->s_name, SMAG_REPOS, (size_t)SI_NIDENT );
+ shdr->s_type = ST_REPOS;
+ shdr->s_shdr = 0; /* Global repos plased in the second noname .repos section */
+ shdr->s_sdata = fhdr->b_rhoff;
+ shdr->s_dnum = (Bcf32_Half)global_rnum;
+
+ write_global_repos( symlist );
+
+ shoff += (Bcf32_Off)sizeof( Bcf32_shdr );
+ ++shdr;
+ }
+
+ write_sections( symlist );
+
+ /**********************
+ Whole BCF file size:
+ */
+ fhdr->b_fsize = (Bcf32_Word)( stoff + stabsz );
+
+ bcf_shm_free();
+
+ bcf_shm_fd = shm_open( SHM_BCF, O_CREAT | O_TRUNC | O_RDWR, 0644 );
+ if( bcf_shm_fd != -1 )
+ {
+ (void)ftruncate( bcf_shm_fd, (size_t)fhdr->b_fsize );
+ bcf_shm_address = mmap( NULL, (size_t)fhdr->b_fsize, PROT_WRITE, MAP_SHARED, bcf_shm_fd, 0 );
+ if( bcf_shm_address != MAP_FAILED )
+ {
+ memcpy( bcf_shm_address, (const void *)ftab, (size_t)stoff );
+ memcpy( bcf_shm_address + (size_t)stoff, (const void *)stab, (size_t)stabsz );
+ }
+ }
+
+ if( bcf_fname )
+ {
+ bcf = fopen( (const char *)bcf_fname, "w" );
+ if( !bcf ) { FATAL_ERROR( "Cannot open BCF file: %s", bcf_fname ); }
+ }
+
+ (void)fwrite( (void *)ftab, (size_t)stoff, 1, bcf );
+ (void)fwrite( (void *)stab, (size_t)stabsz, 1, bcf );
+
+ if( bcf_fname )
+ {
+ if( bcf ) { fclose( bcf ); bcf = NULL; } /* Do not free bcf_fname[] */
+ }
+
+ if( ftab ) { free( ftab ); ftab = NULL; }
+ if( stab ) { free( stab ); stab = NULL; }
+
+ shoff = 0, rhoff = 0, dtoff = 0, stoff = 0;
+ fhdr = NULL, shdr = NULL, rhdr = NULL, dntr = NULL;
+ snum = 0, rnum = 0, dnum = 0, global_dnum = 0, global_rnum = 0, indent = 0, stabsz = 0;
+
+ ret = 1; /* success */
+
+ return ret;
+}
diff --git a/cscmd/bconf.h b/cscmd/bconf.h
new file mode 100644
index 0000000..cd8b49d
--- /dev/null
+++ b/cscmd/bconf.h
@@ -0,0 +1,25 @@
+
+#ifndef __BCONF_H
+#define __BCONF_H
+
+#include <cscm/bcf.h>
+
+
+extern FILE *bcf;
+extern char *bcf_fname;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+extern int write_binary_config( void );
+extern void bcf_shm_free( void );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BCONF_H */
diff --git a/cscmd/cscmd.8.in b/cscmd/cscmd.8.in
new file mode 100644
index 0000000..f9fb862
--- /dev/null
+++ b/cscmd/cscmd.8.in
@@ -0,0 +1,66 @@
+.\"
+.TH "CSCMD" 8 "2022-02-19" "cScm Configuration Daemon" "cscmd"
+
+.SH "NAME"
+\fBcscmd\fR \- cScm Configurations Daemon
+
+.SH "SYNOPSIS"
+.PP
+\fB\fBcscmd\fR [\fBOPTIONS\fR]\fR \fB\-\-scm\fR=[\fIsvn\fR|\fIgit\fR]
+
+.SH "SUMMARY"
+\fBcscmd\fR should be run at boot time by \fI@sysconfdir@/rc.d/rc.csvnd\fR or \fI@sysconfdir@/rc.d/rc.cgitd\fR.
+This daemon read the config file \fI@sysconfdir@/csvn-ui.rc\fR or \fI@sysconfdir@/cgit-ui.rc\fR (depends on \fB\-\-scm\fR option)
+and convert it to binary (see \fI@includedir@/cscm/bcf.h\fR) form for cSvn or cGit CGI scripts.
+
+
+.SH "OPTIONS"
+
+.TP
+\fB-h\fR,\fB--help\fR
+Display help information.
+
+.TP
+\fB-v\fR,\fB--version\fR
+Display the version of \fBcscm\fR daemon.
+
+.TP
+\fB-d\fR,\fB--daemonize\fR
+Run in background as a daemon.
+
+.TP
+\fB-i\fR,\fB--inotify\fR
+Notify about configuration changes. If this option is set then \fBcscmd\fR daemon selects changes made
+in \fI@sysconfdir@/csvn-ui.rc\fR or \fI@sysconfdir@/cgit-ui.rc\fR config file and reread configuration when changes is done.
+Without this option rereading configuration file can be done by sending \fB-HUP\fR to the \fBcscmd\fR daemon.
+
+.TP
+\fB-b\fR,\fB--bcf\fR=\fB<BCF_FILE>\fR
+Binary config file (depends on \fB\-\-scm\fR option). Default: \fI@CSCM_HOME_PATH@/csvn/csvn.bcf\fR.
+
+.TP
+\fB-c\fR,\fB--config\fR=\fB<CONFIG_FILE>\fR
+Config file (depends on \fB\-\-scm\fR option). Default: \fI@sysconfdir@/csvn-ui.rc\fR.
+
+.TP
+\fB-l\fR,\fB--log\fR=\fB<LOG_FILE>\fR
+Log file (depends on \fB\-\-scm\fR option). Default: \fI@CSCM_LOG_DIR@/csvnd.log\fR.
+
+.TP
+\fB-p\fR,\fB--pid\fR=\fB<PID_FILE>\fR
+Log file (depends on \fB\-\-scm\fR option). Default: \fI@CSCM_PID_DIR@/csvnd.pid\fR.
+
+.TP
+\fB-s\fR,\fB--scm\fR=\fB[svn|git]\fR
+SCM engine name: \fIsvn\fR or \fIgit\fR. Default: \fIsvn\fR.
+
+.TP
+\fB-t\fR,\fB--test\fR
+Test the config file and exit.
+
+
+.SH "SEE ALSO"
+.BR csvn-ui.rc(5),
+.BR cgit-ui.rc(5)
+
+
diff --git a/cscmd/daemon.c b/cscmd/daemon.c
new file mode 100644
index 0000000..80ad4ed
--- /dev/null
+++ b/cscmd/daemon.c
@@ -0,0 +1,39 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fcntl.h>
+#include <paths.h>
+#include <unistd.h>
+
+#include <daemon.h>
+
+int daemon( int nochdir, int noclose )
+{
+ int fd;
+
+ switch( fork() )
+ {
+ case -1:
+ return( -1 );
+ case 0:
+ break;
+ default:
+ _exit( 0 ); /* direct use kernel exit */
+ }
+
+ if( setsid() == -1 ) return( -1 );
+ if( !nochdir ) chdir( "/" );
+ if( noclose ) return( 0 );
+
+ fd = open( _PATH_DEVNULL, O_RDWR, 0 );
+ if( fd != -1 )
+ {
+ dup2( fd, STDIN_FILENO );
+ dup2( fd, STDOUT_FILENO );
+ dup2( fd, STDERR_FILENO );
+ if( fd > 2 ) close( fd );
+ }
+ return( 0 );
+}
diff --git a/cscmd/daemon.h b/cscmd/daemon.h
new file mode 100644
index 0000000..83dd044
--- /dev/null
+++ b/cscmd/daemon.h
@@ -0,0 +1,7 @@
+
+#ifndef __DAEMON_H__
+#define __DAEMON_H__
+
+extern int daemon( int, int );
+
+#endif /* __DAEMON_H__ */
diff --git a/cscmd/error.c b/cscmd/error.c
new file mode 100644
index 0000000..ec469c9
--- /dev/null
+++ b/cscmd/error.c
@@ -0,0 +1,92 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <locale.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <defs.h>
+
+#include <error.h>
+#include <msglog.h>
+#include <utf8ing.h>
+#include <lex.h>
+
+
+extern char *config_fname;
+
+int errors = 0;
+int warnings = 0;
+
+
+void error( char *fmt, ... )
+{
+ va_list arg_ptr;
+ char buf[MAX_ERROR_MSG_SIZE];
+ char msg[MAX_ERROR_MSG_SIZE];
+ char *format = "%s:%d:%d: %s";
+
+ va_start( arg_ptr, fmt );
+
+ vsnprintf( msg, MAX_ERROR_MSG_SIZE, (const void *)fmt, arg_ptr );
+
+ va_end( arg_ptr ); /* Reset variable arguments. */
+
+ snprintf( buf, MAX_ERROR_MSG_SIZE, format, config_fname, lineno, colno, msg );
+
+ ERROR( "%s", buf );
+
+ ++errors;
+}
+
+void warning( char *fmt, ... )
+{
+ va_list arg_ptr;
+ char buf[MAX_ERROR_MSG_SIZE];
+ char msg[MAX_ERROR_MSG_SIZE];
+ char *format = "%s:%d:%d: %s";
+
+ va_start( arg_ptr, fmt );
+
+ vsnprintf( msg, MAX_ERROR_MSG_SIZE, (const void *)fmt, arg_ptr );
+
+ va_end( arg_ptr ); /* Reset variable arguments. */
+
+ snprintf( buf, MAX_ERROR_MSG_SIZE, format, config_fname, lineno, colno, msg );
+
+ WARNING( "%s", buf );
+
+ ++warnings;
+}
+
+void no_space( void )
+{
+ char buf[MAX_ERROR_MSG_SIZE];
+ char *format = "%s: Cannot allocate memory";
+
+ snprintf( buf, MAX_ERROR_MSG_SIZE, format, config_fname );
+
+ FATAL_ERROR( "%s", buf );
+
+ ++errors;
+}
+
+void unterminated_comment( void )
+{
+ char buf[MAX_ERROR_MSG_SIZE];
+ char *format = "%s:%d:%d: Unterminated comment";
+
+ snprintf( buf, MAX_ERROR_MSG_SIZE, format, config_fname, lineno, colno );
+
+ ERROR( "%s", buf );
+
+ ++errors;
+}
diff --git a/cscmd/error.h b/cscmd/error.h
new file mode 100644
index 0000000..866ec2a
--- /dev/null
+++ b/cscmd/error.h
@@ -0,0 +1,25 @@
+
+#ifndef __ERROR_H
+#define __ERROR_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MAX_ERROR_MSG_SIZE PATH_MAX
+
+extern int errors;
+extern int warnings;
+
+extern void error( char *fmt, ... );
+extern void warning( char *fmt, ... );
+extern void no_space( void );
+extern void unterminated_comment( void );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ERROR_H */
diff --git a/cscmd/lex.c b/cscmd/lex.c
new file mode 100644
index 0000000..318e074
--- /dev/null
+++ b/cscmd/lex.c
@@ -0,0 +1,815 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <locale.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#define PCRE2_CODE_UNIT_WIDTH 32
+#include <pcre2.h>
+
+#include <defs.h>
+
+#include <main.h>
+#include <error.h>
+#include <msglog.h>
+#include <xalloc.h>
+#include <utf8ing.h>
+#include <symtab.h>
+#include <parse.h>
+
+#include <lex.h>
+
+
+
+int lineno = 0;
+int colno = 0;
+
+static int maxtoken;
+static wchar_t *token_buffer;
+
+static int max8token;
+static utf8_t *token_utf8_buffer;
+
+int indent_level = 0; /* Number of '{' minus number of '}'. */
+
+static int end_of_file = 0;
+static int nextchar = -1;
+
+static char *locale;
+
+#define GETC(c) ({ wint_t ret; ++colno; ret = fgetwc( config ); ret; })
+#define UNGETC(c) ({ wint_t ret; --colno; ret = ungetwc( c, config ); ret; })
+
+
+static wchar_t *extend_token_buffer( wchar_t *p )
+{
+ int offset = p - token_buffer;
+ maxtoken = maxtoken * 2 + 10;
+ token_buffer = (wchar_t *)xrealloc( token_buffer, (maxtoken + 2)*sizeof(wchar_t) );
+
+ return( token_buffer + offset );
+}
+
+static utf8_t *extend_token_utf8_buffer( utf8_t *p )
+{
+ int offset = p - token_utf8_buffer;
+ max8token = max8token * 2 + 10;
+ token_utf8_buffer = (utf8_t *)xrealloc( token_utf8_buffer, (max8token + 2)*6 );
+
+ return( token_utf8_buffer + offset );
+}
+
+
+void yyerror( char const *s )
+{
+ error( "%s", s );
+}
+
+
+void init_lex( void )
+{
+ locale = setlocale( LC_ALL, "en_US.utf8" );
+
+ lineno = 0;
+ colno = 0;
+
+ nextchar = -1;
+ maxtoken = 40;
+ max8token = 40;
+
+ indent_level = 0;
+ end_of_file = 0;
+
+ token_buffer = (wchar_t *)xmalloc( maxtoken * sizeof(wchar_t) + 2 );
+ token_utf8_buffer = (utf8_t *)xmalloc( max8token * 6 + 2 );
+}
+
+void fini_lex( void )
+{
+ locale = setlocale( LC_ALL, locale );
+
+ if( token_buffer ) { free( token_buffer ); token_buffer = NULL; }
+ if( token_utf8_buffer ) { free( token_utf8_buffer ); token_utf8_buffer = NULL; }
+
+ indent_level = 0;
+ end_of_file = 0;
+
+ max8token = 0;
+ maxtoken = 0;
+ nextchar = -1;
+
+ lineno = 0;
+ colno = 0;
+}
+
+static wint_t check_newline( void )
+{
+ wint_t c;
+
+ ++lineno;
+ colno = 0; /* считает GETC()/UNGETC(); здесь надо только обнулить */
+
+ /*****************************************
+ Read first nonwhite char on the line.
+ *****************************************/
+ c = GETC();
+ while( c == ' ' || c == '\t' ) c = GETC();
+
+ if( c == '#' ) goto skipline;
+ else return( c );
+
+ /* skip the rest of this line */
+skipline:
+
+ while( c != '\n' && c != WEOF )
+ c = GETC();
+
+ return( c );
+}
+
+static wint_t skip_comment( int c )
+{
+ if( c == '*' )
+ {
+do1:
+ do
+ {
+ c = GETC();
+ if( c == '\n' ) { ++lineno; colno = 0; }
+
+ } while( c != '*' && c != WEOF );
+
+ if( c == WEOF )
+ {
+ unterminated_comment();
+ return( WEOF );
+ }
+
+ c = GETC();
+
+ if( c == '/' )
+ {
+ c = GETC();
+ if( c == '\n' ) c = check_newline();
+ return( c );
+ }
+ else
+ {
+ UNGETC( c );
+ goto do1;
+ }
+ }
+ else if( c == '/' || c == '#' )
+ {
+ do
+ {
+ c = GETC();
+
+ } while( c != '\n' && c != WEOF );
+
+ if( c == WEOF )
+ {
+ unterminated_comment();
+ return( WEOF );
+ }
+ else c = check_newline();
+
+ return( c );
+ }
+
+ return( c );
+
+} /* End skip_commemnt() */
+
+static wint_t skip_white_space( wint_t c )
+{
+ for( ;; )
+ {
+ switch( c )
+ {
+ case '\n':
+ c = check_newline();
+ break;
+
+ case '#':
+ c = skip_comment( c );
+ return( skip_white_space( c ) );
+ break;
+
+ case '/':
+ c = GETC();
+ if( c == '/' || c == '*' )
+ {
+ c = skip_comment( c );
+ return( skip_white_space( c ) );
+ }
+ else
+ {
+ UNGETC( c );
+ return( '/' );
+ }
+ break;
+
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\v':
+ case '\b':
+ case '\r':
+ c = GETC();
+ break;
+ case '\\':
+ c = GETC();
+ if( c == '\n' ) { ++lineno; colno = 0; }
+ else
+ {
+ warning( "%s", "Stray '\\' in program" );
+ }
+ c = GETC();
+ break;
+ default:
+ return( c );
+
+ } /* End switch( c ) */
+
+ } /* End for( ;; ) */
+
+} /* End skip_white_space() */
+
+static wint_t readescape( int *ignore_ptr )
+/*
+ read escape sequence, returning a char, or store 1 in *ignore_ptr
+ if it is backslash-newline
+ */
+{
+ wint_t c = GETC();
+ wint_t code;
+ unsigned count;
+ unsigned firstdig = 0;
+ int nonull;
+
+ switch( c )
+ {
+ case 'x':
+ code = 0;
+ count = 0;
+ nonull = 0;
+ while( 1 )
+ {
+ c = GETC();
+ if( !(c >= 'a' && c <= 'f') &&
+ !(c >= 'A' && c <= 'F') &&
+ !(c >= '0' && c <= '9') )
+ {
+ UNGETC( c );
+ break;
+ }
+ code *= 16;
+ if( c >= 'a' && c <= 'f' ) code += c - 'a' + 10;
+ if( c >= 'A' && c <= 'F' ) code += c - 'A' + 10;
+ if( c >= '0' && c <= '9' ) code += c - '0';
+ if( code != 0 || count != 0 )
+ {
+ if( count == 0 ) firstdig = code;
+ count++;
+ }
+ nonull = 1;
+
+ } /* End while( 1 ) */
+
+ if( !nonull )
+ {
+ error( "%s", "\\x used with no following hex digits" );
+ }
+ else if( count == 0 )
+ /* Digits are all 0's. Ok. */
+ ;
+ else if( (count - 1) * 4 >= 32 || /* 32 == bits per INT */
+ (count > 1 && ((1 << (32 - (count-1) * 4)) <= firstdig )))
+ {
+ warning( "%s", "Hex escape out of range" );
+ }
+ return( code );
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7':
+ code = 0;
+ count = 0;
+ while( (c <= '7') && (c >= '0') && (count++ < 6) )
+ {
+ code = (code * 8) + (c - '0');
+ c = GETC();
+ }
+ UNGETC( c );
+ return( code );
+
+ case '\\': case '\'': case '"':
+ return( c );
+
+ case '\n':
+ lineno++; colno = 0;
+ *ignore_ptr = 1;
+ return( 0 );
+
+ case 'n':
+ return( '\n' );
+
+ case 't':
+ return( '\t' );
+
+ case 'r':
+ return( '\r' );
+
+ case 'f':
+ return( '\f' );
+
+ case 'b':
+ return( '\b' );
+
+ case 'a':
+ return( '\a' );
+
+ case 'v':
+ return( '\v' );
+ }
+
+ return( c );
+
+} /* End of readescape() */
+
+
+int html_symbol_name( wchar_t *str )
+{
+ int rc = 0, error = 0;
+ PCRE2_SIZE offset = 0;
+ wchar_t pattern[] = L"^(&[#A-Za-z0-9]*;)";
+
+ pcre2_match_data *match;
+
+ pcre2_code *regexp = pcre2_compile( (PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED, 0, &error, &offset, NULL );
+ if( regexp == NULL )
+ {
+ return 0; /* PCRE compilation failed */
+ }
+
+ match = pcre2_match_data_create_from_pattern( regexp, NULL );
+
+ rc = pcre2_match( regexp, (PCRE2_SPTR)str, (int)wcslen(str), 0, 0, match, NULL );
+ if( rc < 0 )
+ {
+ /* not match */
+ pcre2_match_data_free( match );
+ pcre2_code_free( regexp );
+ return 0;
+ }
+ else
+ {
+ /* match */
+ pcre2_match_data_free( match );
+ pcre2_code_free( regexp );
+ return 1;
+ }
+}
+
+
+int yylex( void )
+{
+ wint_t c;
+ wchar_t *p;
+ int value;
+
+ if( nextchar >= 0 )
+ c = nextchar, nextchar = -1;
+ else
+ c = GETC();
+
+ while( 1 )
+ {
+ switch( c )
+ {
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\v':
+ case '\b':
+ c = skip_white_space( c );
+ break;
+
+ case '\r':
+ case '\n':
+ case '/':
+ case '#':
+ case '\\':
+ c = skip_white_space( c );
+
+ default:
+ goto found_nonwhite;
+
+ } /* End switch( c ) */
+found_nonwhite:
+
+ token_buffer[0] = c;
+ token_buffer[1] = 0;
+
+ switch( c )
+ {
+ case WEOF:
+ end_of_file = 1;
+ token_buffer[0] = 0;
+ value = 0;
+ goto done;
+ break;
+
+ case '$': /* dollar in identifier */
+ if( 1 ) goto letter;
+ return '$';
+
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F': case 'G': case 'H': case 'I': case 'J':
+ case 'K': case 'L': case 'M': case 'N': case 'O':
+ case 'P': case 'Q': case 'R': case 'S': case 'T':
+ case 'U': case 'V': case 'W': case 'X': case 'Y':
+ case 'Z':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f': case 'g': case 'h': case 'i': case 'j':
+ case 'k': case 'l': case 'm': case 'n': case 'o':
+ case 'p': case 'q': case 'r': case 's': case 't':
+ case 'u': case 'v': case 'w': case 'x': case 'y':
+ case 'z':
+ case '_':
+
+ /* RUSSIAN */
+ case L'А': case L'Б': case L'В': case L'Г': case L'Д':
+ case L'Е': case L'Ё': case L'Ж': case L'З': case L'И':
+ case L'Й': case L'К': case L'Л': case L'М': case L'Н':
+ case L'О': case L'П': case L'Р': case L'С': case L'Т':
+ case L'У': case L'Ф': case L'Х': case L'Ц': case L'Ч':
+ case L'Ш': case L'Щ': case L'Ъ': case L'Ы': case L'Ь':
+ case L'Э': case L'Ю': case L'Я':
+
+ case L'а': case L'б': case L'в': case L'г': case L'д':
+ case L'е': case L'ё': case L'ж': case L'з': case L'и':
+ case L'й': case L'к': case L'л': case L'м': case L'н':
+ case L'о': case L'п': case L'р': case L'с': case L'т':
+ case L'у': case L'ф': case L'х': case L'ц': case L'ч':
+ case L'ш': case L'щ': case L'ъ': case L'ы': case L'ь':
+ case L'э': case L'ю': case L'я':
+
+letter:
+ p = token_buffer;
+ while( iswalnum( c ) || c == '_' || c == '$' || c == '@' || c == '-' || c == '.' || c == ':' )
+ {
+ if( p >= token_buffer + maxtoken )
+ {
+ p = extend_token_buffer( p );
+ extend_token_utf8_buffer( token_utf8_buffer );
+ }
+
+ *p++ = c;
+ c = GETC();
+ }
+ *p = 0;
+ nextchar = c;
+ value = VARIABLE;
+
+ (void)copy_ucs4_to_utf8( (utf8_t *)token_utf8_buffer, (const ucs4_t *)token_buffer );
+
+ /*********************
+ install into symtab
+ *********************/
+ {
+ if( !strcmp( "section", (const char *)token_utf8_buffer ) )
+ {
+ value = SECTION;
+ yylval.sym = install( NULL, SECTION, NULL );
+ }
+ else if( !strcmp( "repo", (const char *)token_utf8_buffer ) )
+ {
+ value = REPO;
+ yylval.sym = install( NULL, REPO, NULL );
+ }
+ else
+ {
+ SYMBOL *sp = NULL;
+
+ if( (sp = lookup( (const char *)token_utf8_buffer )) == (SYMBOL *)0 )
+ sp = install( (const char *)token_utf8_buffer, VARIABLE, 0 );
+
+ /******************************************************************
+ Если переменная уже в таблице, то мы предполагаем, что она имеет
+ тип равный одному из допустимых: NUMERICAL, STRING, или PATH.
+ ******************************************************************/
+ if( sp->type != VARIABLE )
+ {
+ switch( sp->type )
+ {
+ case NUMERICAL:
+ case STRING:
+ case PATH:
+ value = sp->type;
+ break;
+ default:
+ /* error */
+ break;
+ }
+ }
+ yylval.sym = sp;
+ }
+ }
+
+ token_buffer[0] = 0;
+ token_utf8_buffer[0] = 0;
+ goto done;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ {
+ int constant = 0;
+/* integer: */
+ p = token_buffer;
+ while( iswdigit( c ) )
+ {
+ if( p >= token_buffer + maxtoken )
+ {
+ p = extend_token_buffer( p );
+ extend_token_utf8_buffer( token_utf8_buffer );
+ }
+
+ *p++ = c;
+ c = GETC();
+ }
+ *p = 0;
+ nextchar = c;
+ value = NUMERICAL;
+
+ (void)copy_ucs4_to_utf8( (utf8_t *)token_utf8_buffer, (const ucs4_t *)token_buffer );
+
+ /*********************
+ install into symtab
+ *********************/
+ {
+ (void)swscanf( (const wchar_t *)token_buffer, L"%d", &constant );
+ yylval.sym = install( NULL, NUMERICAL, constant );
+ }
+
+ token_buffer[0] = 0;
+ token_utf8_buffer[0] = 0;
+ goto done;
+ break;
+ }
+
+ case '\'':
+/* path_constant: */
+ {
+ int num_chars = 0;
+ unsigned int width = 8; /* to allow non asscii in path set width = 16 */
+
+ while( 1 )
+ {
+tryagain:
+ c = GETC();
+
+ if( c == '\'' || c == WEOF ) break;
+ if( c == '\\' )
+ {
+ int ignore = 0;
+ c = readescape( &ignore );
+ if( ignore ) goto tryagain;
+ if( (unsigned)c >= (1 << width) )
+ {
+ warning( "%s", "Escape sequence out of range" );
+ }
+ }
+ else if( c == '\n' ) { lineno++; colno = 0; }
+
+ num_chars++;
+ if( num_chars > maxtoken - 4 )
+ {
+ extend_token_buffer( token_buffer );
+ extend_token_utf8_buffer( token_utf8_buffer );
+ }
+
+ token_buffer[num_chars] = c;
+
+ } /* End while( 1 ) */
+
+ token_buffer[num_chars + 1] = '\'';
+ token_buffer[num_chars + 2] = 0;
+
+ if( c != '\'' )
+ {
+ error( "%s", "Malformated path constant" );
+ }
+ else if( num_chars == 0 )
+ {
+ error( "%s", "Empty path constant" );
+ }
+
+ /* build path: */
+ {
+ wchar_t *s, *string = NULL;
+ wchar_t *p = &token_buffer[0];
+
+ while( *p )
+ {
+ if( *p == '\n' || *p == '\t' ) *p = ' ';
+ ++p;
+ }
+
+ string = (wchar_t *)malloc( maxtoken * 4 + 10 );
+
+ p = &token_buffer[1];
+ s = &string[0];
+
+ while( *p == ' ' ) ++p;
+
+ while( *p )
+ {
+ if( *p != ' ' )
+ *s++ = *p++;
+ else
+ ++p;
+ }
+ --s; *s = 0;
+ while( *(s-1) == ' ' ) --s;
+ *s = 0;
+
+ (void)copy_ucs4_to_utf8( (utf8_t *)token_utf8_buffer, (const ucs4_t *)string );
+
+ free( string );
+ }
+
+ /*********************
+ install into symtab
+ *********************/
+ {
+ yylval.sym = install( NULL, PATH, (char *)token_utf8_buffer );
+ }
+
+ token_buffer[0] = 0;
+ token_utf8_buffer[0] = 0;
+ value = PATH;
+ goto done;
+ }
+
+ case '"':
+/* string_constant: */
+ {
+ c = GETC();
+ p = token_buffer + 1;
+
+ while( c != '"' && c >= 0 )
+ {
+ if( c == '\\' )
+ {
+ int ignore = 0;
+ c = readescape( &ignore );
+ if( ignore ) goto skipnewline;
+ }
+ else if( c == '\n' ) lineno++;
+
+ if( p == token_buffer + maxtoken )
+ {
+ p = extend_token_buffer( p );
+ extend_token_utf8_buffer( token_utf8_buffer );
+ }
+ *p++ = c;
+
+skipnewline:
+ c = GETC();
+
+ } /* End while( " ) */
+
+ *p = 0;
+
+ if( c < 0 )
+ {
+ error( "%s", "Unterminated string constant" );
+ }
+
+
+ *p++ = '"';
+ *p = 0;
+
+ /* build string: */
+ {
+ wchar_t *s, *string = NULL;
+ wchar_t *p = &token_buffer[0];
+
+ while( *p )
+ {
+ if( *p == '\n' || *p == '\t' ) *p = ' ';
+ ++p;
+ }
+
+ string = (wchar_t *)malloc( maxtoken * 4 + 10 );
+
+ p = &token_buffer[1];
+ s = &string[0];
+
+ while( *p == ' ' ) ++p;
+
+ while( *p )
+ {
+ if( *p != ' ' )
+ {
+ switch( *p )
+ {
+ case '&':
+ /************************************************
+ Skip HTML symbol names such as &nbsp,... etc.:
+ */
+ if( ! html_symbol_name( p ) )
+ {
+ *s++ = '&'; *s++ = 'a'; *s++ = 'm'; *s++ = 'p'; *s++ = ';'; ++p;
+ }
+ else
+ {
+ *s++ = *p++;
+ }
+ break;
+
+ case '<':
+ *s++ = '&'; *s++ = 'l'; *s++ = 't'; *s++ = ';'; ++p;
+ break;
+
+ case '>':
+ *s++ = '&'; *s++ = 'g'; *s++ = 't'; *s++ = ';'; ++p;
+ break;
+
+ default:
+ *s++ = *p++;
+ break;
+ }
+ }
+ else
+ {
+ /* skip multiple spaces */
+ if( *(p+1) != ' ' )
+ *s++ = *p++;
+ else
+ ++p;
+ }
+ }
+ --s; *s = 0;
+ while( *(s-1) == ' ' ) --s;
+ *s = 0;
+
+ (void)copy_ucs4_to_utf8( (utf8_t *)token_utf8_buffer, (const ucs4_t *)string );
+
+ free( string );
+ }
+
+ /*********************
+ install into symtab
+ *********************/
+ {
+ yylval.sym = install( NULL, STRING, (char *)token_utf8_buffer );
+ }
+
+ token_buffer[0] = 0;
+ token_utf8_buffer[0] = 0;
+ value = STRING;
+ goto done;
+ }
+
+ case 0:
+ value = 1;
+ goto done;
+ break;
+
+ case '{':
+ indent_level++;
+ value = c;
+ goto done;
+ break;
+
+ case '}':
+ indent_level--;
+ value = c;
+ goto done;
+ break;
+
+ default:
+ value = c;
+ goto done;
+ break;
+
+ } /* End switch( c ) */
+
+ } /* End while( 1 ) */
+
+done:
+
+ return( value );
+}
diff --git a/cscmd/lex.h b/cscmd/lex.h
new file mode 100644
index 0000000..5c686e2
--- /dev/null
+++ b/cscmd/lex.h
@@ -0,0 +1,26 @@
+
+#ifndef __LEX_H
+#define __LEX_H
+
+
+extern int lineno;
+extern int colno;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+extern void init_lex( void );
+extern void fini_lex( void );
+
+extern int yylex( void );
+extern void yyerror( char const *s );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LEX_H */
diff --git a/cscmd/logrotate.in b/cscmd/logrotate.in
new file mode 100644
index 0000000..4d36ac5
--- /dev/null
+++ b/cscmd/logrotate.in
@@ -0,0 +1,8 @@
+
+@CSCM_LOG_DIR@/@CSCM_PROGRAM@d.log {
+ rotate 7
+ size=5M
+ compress
+ notifempty
+ missingok
+}
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;
+}
diff --git a/cscmd/main.h b/cscmd/main.h
new file mode 100644
index 0000000..75404a4
--- /dev/null
+++ b/cscmd/main.h
@@ -0,0 +1,21 @@
+
+#ifndef __MAIN_H
+#define __MAIN_H
+
+
+extern FILE *config;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MAIN_H */
diff --git a/cscmd/msglog.c b/cscmd/msglog.c
new file mode 100644
index 0000000..2c4a821
--- /dev/null
+++ b/cscmd/msglog.c
@@ -0,0 +1,70 @@
+
+/**********************************************************************
+
+ Copyright 2019 Andrey V.Kosteltsev
+
+ Licensed under the Radix.pro License, Version 1.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://radix.pro/licenses/LICENSE-1.0-en_US.txt
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied.
+
+ **********************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <msglog.h>
+
+FILE *errlog;
+
+void (*fatal_error_hook)( void );
+
+void logmsg( FILE *logfile, enum _msg_type type, char *format, ... )
+{
+ va_list argp;
+
+ if( ! format ) return;
+
+ {
+ time_t t = time( NULL );
+ struct tm tm = *localtime(&t);
+
+ fprintf( logfile, "[%04d-%02d-%02d %02d:%02d:%02d]: ",
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec );
+ }
+
+ switch( type )
+ {
+ case MSG_FATAL: fprintf( logfile, "%s: FATAL: ", program ); break;
+ case MSG_ERROR: fprintf( logfile, "%s: ERROR: ", program ); break;
+ case MSG_WARNING: fprintf( logfile, "%s: WARNING: ", program ); break;
+ case MSG_NOTICE: fprintf( logfile, "%s: NOTE: ", program ); break;
+ case MSG_INFO: fprintf( logfile, "%s: INFO: ", program ); break;
+ case MSG_DEBUG: fprintf( logfile, "%s: DEBUG: ", program ); break;
+ case MSG_LOG:
+ fprintf( logfile, "%s: ", program );
+ break;
+ default:
+ fprintf( logfile, "%s: ", program );
+ break;
+ }
+ va_start( argp, format );
+ vfprintf( errlog, format, argp );
+ fprintf( errlog, "\n" );
+ (void)fflush( errlog );
+}
diff --git a/cscmd/msglog.h b/cscmd/msglog.h
new file mode 100644
index 0000000..fc256f0
--- /dev/null
+++ b/cscmd/msglog.h
@@ -0,0 +1,98 @@
+
+/**********************************************************************
+
+ Copyright 2019 Andrey V.Kosteltsev
+
+ Licensed under the Radix.pro License, Version 1.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://radix.pro/licenses/LICENSE-1.0-en_US.txt
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied.
+
+ **********************************************************************/
+
+#ifndef _MSG_LOG_H_
+#define _MSG_LOG_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern FILE *errlog;
+
+extern void (*fatal_error_hook)( void );
+
+extern char *program;
+extern int exit_status;
+
+enum _msg_type
+{
+ MSG_FATAL = 0,
+ MSG_ERROR,
+ MSG_WARNING,
+ MSG_NOTICE,
+ MSG_INFO,
+ MSG_DEBUG,
+
+ MSG_LOG
+};
+
+#define FATAL_ERROR( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_FATAL, __VA_ARGS__ ); \
+ if( fatal_error_hook) fatal_error_hook(); \
+ exit( EXIT_FAILURE ); \
+ } while (0)
+
+#define ERROR( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_ERROR, __VA_ARGS__ ); \
+ ++exit_status; \
+ } while (0)
+
+#define WARNING( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_WARNING, __VA_ARGS__ ); \
+ } while (0)
+
+#define NOTICE( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_NOTICE, __VA_ARGS__ ); \
+ } while (0)
+
+#define INFO( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_INFO, __VA_ARGS__ ); \
+ } while (0)
+
+#define DEBUG( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_DEBUG, __VA_ARGS__ ); \
+ } while (0)
+
+#define LOG( ... ) \
+ do \
+ { \
+ logmsg( errlog, MSG_LOG, __VA_ARGS__ ); \
+ } while (0)
+
+
+extern void logmsg( FILE *logfile, enum _msg_type type, char *format, ... );
+
+
+#ifdef __cplusplus
+} /* ... extern "C" */
+#endif
+
+#endif /* _MSG_LOG_H_ */
diff --git a/cscmd/parse.y b/cscmd/parse.y
new file mode 100644
index 0000000..cdb2d5b
--- /dev/null
+++ b/cscmd/parse.y
@@ -0,0 +1,107 @@
+
+%{
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <locale.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <defs.h>
+
+#include <main.h>
+#include <error.h>
+#include <msglog.h>
+#include <xalloc.h>
+#include <utf8ing.h>
+#include <symtab.h>
+#include <parse.h>
+#include <lex.h>
+
+
+%}
+
+
+%union
+{
+ SYMBOL *sym;
+}
+
+%token <sym> VARIABLE 501 SECTION 502 REPO 503
+%token <sym> NUMERICAL 510 STRING 511 PATH 512
+%right '='
+%left UNARYMINUS
+/************************************************************
+ Following tokens declared only for verbose error messaging
+ to prevent "$undefined" values of unexpected symbols:
+ */
+%token '!' '"' '#' '$' '%' '&' '\'' '(' ')' '*' '/' '+' '-'
+%token '.' ',' ':' '<' '>' '?' '@' '[' '\\' ']' '^' '`'
+
+%start list
+
+%%
+list: /* nothing */
+ | list ';'
+ | list repo
+ | list section
+ | list assign ';'
+ | list error ';' { return 1; }
+ ;
+
+assign: VARIABLE '=' NUMERICAL { (void)assign_value( $1, $3 ); }
+ | VARIABLE '=' '+' NUMERICAL { (void)assign_value( $1, $4 ); }
+ | VARIABLE '=' '-' NUMERICAL %prec UNARYMINUS { $4->u.value = -$4->u.value; (void)assign_value( $1, $4 ); }
+ | VARIABLE '=' STRING { (void)assign_value( $1, $3 ); }
+ | VARIABLE '=' PATH { (void)assign_value( $1, $3 ); }
+ | NUMERICAL '=' NUMERICAL { (void)assign_value( $1, $3 ); }
+ | STRING '=' STRING { (void)assign_value( $1, $3 ); }
+ | PATH '=' PATH { (void)assign_value( $1, $3 ); }
+ ;
+
+alist: /* nothing */
+ | alist ';'
+ | alist assign ';'
+ ;
+
+repo: REPO PATH '{'
+ {
+ if( lookup_repo( $2->u.path ) )
+ {
+ error( "Repository '%s' is already defined", $2->u.path );
+ return 1;
+ }
+ (void)assign_value( $1, $2 ); push_symlist( (SYMBOL **)&($1->list) );
+ }
+ alist
+ '}' { pop_symlist(); }
+ ;
+
+rlist: /* nothing */
+ | rlist repo
+ ;
+
+section:
+ SECTION STRING '{'
+ {
+ if( lookup_section( $2->u.string ) )
+ {
+ error( "Section '%s' is already defined", $2->u.string );
+ return 1;
+ }
+ (void)assign_value( $1, $2 ); push_symlist( (SYMBOL **)&($1->list) );
+ }
+ rlist
+ '}' { pop_symlist(); }
+ ;
+
+%%
+
diff --git a/cscmd/rc.cscmd.in b/cscmd/rc.cscmd.in
new file mode 100644
index 0000000..e3297e1
--- /dev/null
+++ b/cscmd/rc.cscmd.in
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# /etc/rc.d/rc.@CSCM_PROGRAM@d - @CSCM_PROGRAM_NAME@ daemon control script.
+#
+
+BIN=@sbindir@/@PROGRAM_DAEMON@
+CONF=@CSCM_CONFIG@
+BCF=@CSCM_HOME_PATH@/@CSCM_PROGRAM@/@CSCM_PROGRAM@.bcf
+PID=@CSCM_PID_DIR@/@CSCM_PROGRAM@d.pid
+LOG=@CSCM_LOG_DIR@/@CSCM_PROGRAM@d.log
+
+INOTIFY=--inotify
+
+cscmd_start() {
+ # Sanity checks.
+ if [ ! -r $CONF ]; then
+ echo "$CONF does not appear to exist. Abort."
+ exit 1
+ fi
+
+ if [ -s $PID ]; then
+ echo "@CSCM_PROGRAM_NAME@ daemon appears to already be running?"
+ exit 1
+ fi
+
+ echo "Starting @CSCM_PROGRAM_NAME@ server daemon..."
+ if [ -x $BIN ]; then
+ $BIN --daemonize $INOTIFY --scm=@CSCM_NAME@ --pid=$PID --log=$LOG --bcf=$BCF --config=$CONF
+ fi
+}
+
+cscmd_test_conf() {
+ echo "Checking configuration for correct syntax and then"
+ echo "trying to open files referenced in configuration..."
+ echo ""
+ if [ -s $PID ] ; then
+ echo "@PROGRAM_DAEMON@: $CONF: Config file is correct."
+ else
+ $BIN --test --scm=@CSCM_NAME@ --pid=$PID --log=$LOG --bcf=$BCF --config=$CONF
+ fi
+}
+
+cscmd_status() {
+ if [ -s $PID ] ; then
+ echo "@CSCM_PROGRAM_NAME@ daemon is running as PID: $(cat $PID)"
+ else
+ echo "@CSCM_PROGRAM_NAME@ daemon is stopped."
+ fi
+}
+
+cscmd_stop() {
+ echo "Shutdown @CSCM_PROGRAM_NAME@ daemon gracefully..."
+ if [ -s $PID ] ; then
+ kill -TERM $(cat $PID)
+ else
+ echo "@CSCM_PROGRAM_NAME@ daemon appears to already be stopped."
+ fi
+}
+
+cscmd_reload() {
+ echo "Reloading @CSCM_PROGRAM_NAME@ daemon configuration..."
+ if [ -s $PID ] ; then
+ kill -HUP $(cat $PID)
+ else
+ echo "@CSCM_PROGRAM_NAME@ daemon is not running."
+ fi
+}
+
+cscmd_restart() {
+ cscmd_stop
+ sleep 3
+ cscmd_start
+}
+
+case "$1" in
+ check)
+ cscmd_test_conf
+ ;;
+ reload)
+ cscmd_reload
+ ;;
+ restart)
+ cscmd_restart
+ ;;
+ start)
+ cscmd_start
+ ;;
+ stop)
+ cscmd_stop
+ ;;
+ status)
+ cscmd_status
+ ;;
+ *)
+ echo "usage: `basename $0` {check|reload|restart|start|stop|status}"
+esac
diff --git a/cscmd/symtab.c b/cscmd/symtab.c
new file mode 100644
index 0000000..62899c1
--- /dev/null
+++ b/cscmd/symtab.c
@@ -0,0 +1,471 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+
+#include <defs.h>
+
+#include <main.h>
+#include <error.h>
+#include <msglog.h>
+#include <xalloc.h>
+#include <utf8ing.h>
+#include <lex.h>
+
+#include <symtab.h>
+#include <parse.h>
+
+
+SYMBOL *symlist = NULL;
+
+static SYMTAB *symtab = NULL;
+
+static int constants_counter = 0;
+static int sections_counter = 0;
+static int repos_counter = 0;
+
+
+static SYMBOL *free_const( SYMBOL *sp )
+{
+ SYMBOL *next = NULL;
+
+ if( !sp ) return next;
+
+ next = sp->next;
+
+ free( sp->name );
+
+ switch( sp->type )
+ {
+ case STRING:
+ if( sp->u.string ) free( sp->u.string );
+ break;
+ case PATH:
+ if( sp->u.string ) free( sp->u.path );
+ break;
+
+ case NUMERICAL:
+ default:
+ break;
+ }
+
+ free( sp );
+
+ return next;
+}
+
+static void free_symlist( SYMBOL *sp );
+
+static SYMBOL *free_symbol( SYMBOL *sp )
+{
+ SYMBOL *next = NULL;
+
+ if( !sp ) return next;
+
+ if( sp->list ) (void)free_symlist( sp->list );
+
+ next = sp->next;
+
+ free( sp->name );
+
+ switch( sp->type )
+ {
+ case SECTION:
+ case STRING:
+ if( sp->u.string ) free( sp->u.string );
+ break;
+ case REPO:
+ case PATH:
+ if( sp->u.string ) free( sp->u.path );
+ break;
+
+ case VARIABLE:
+ case NUMERICAL:
+ default:
+ break;
+ }
+
+ free( sp );
+
+ return next;
+}
+
+static void free_symlist( SYMBOL *sp )
+{
+ SYMBOL *next = NULL;
+
+ if( !sp ) return;
+
+ next = free_symbol( sp );
+ while( next )
+ {
+ next = free_symbol( next );
+ }
+}
+
+/******************************************
+ Initialize the stak of symlist pointers:
+ */
+void init_symtab( void )
+{
+ SYMTAB *sa = (SYMTAB *)xmalloc( sizeof( SYMTAB ) );
+
+ symtab = NULL;
+ symlist = NULL;
+
+ constants_counter = 0;
+ sections_counter = 0;
+ repos_counter = 0;
+
+ sa->symlist = (SYMBOL **)&symlist;
+ sa->next = symtab;
+ symtab = sa;
+}
+
+
+/*******************************************
+ Push the address of symlist to the stack:
+ */
+void push_symlist( SYMBOL **head )
+{
+ if( head )
+ {
+ SYMTAB *sa = (SYMTAB *)xmalloc( sizeof( SYMTAB ) );
+
+ sa->symlist = head;
+ sa->next = symtab;
+ symtab = sa;
+ }
+}
+
+/********************************************
+ Pop the address of symlist from the stack:
+ */
+void pop_symlist( void )
+{
+ if( symtab && symtab->next )
+ {
+ SYMTAB *sa = symtab;
+ symtab = symtab->next;
+ free( sa );
+ }
+}
+
+/************************************
+ Free the stak of symlist pointers:
+ */
+void fini_symtab( void )
+{
+ if( !symtab ) return;
+
+ while( symtab )
+ {
+ SYMTAB *sa = symtab;
+ symtab = symtab->next;
+ free( sa );
+ }
+
+ constants_counter = 0;
+ sections_counter = 0;
+ repos_counter = 0;
+
+ symtab = NULL;
+ free_symlist( symlist ); /* free main symlist */
+ symlist = NULL;
+}
+
+
+/******************************
+ Reverse symlist recursively:
+ */
+void reverse_symlist( SYMBOL **head )
+{
+ SYMBOL *prev = NULL, *curr = *head, *next;
+
+ while( curr )
+ {
+ if( curr->list ) reverse_symlist( (SYMBOL **)&(curr->list) );
+
+ next = curr->next;
+ curr->next = prev;
+ prev = curr;
+ curr = next;
+ }
+
+ *head = prev;
+}
+
+/******************************************************
+ Remove temporary constants from symlist recursively:
+ */
+void remove_consts( SYMBOL **head )
+{
+ SYMBOL *tmp = NULL;
+
+ while( *head )
+ {
+ tmp = *head;
+ if( !strncmp( tmp->name, "__const.", 8 ) )
+ {
+ *head = tmp->next;
+ (void)free_const( tmp );
+ }
+ else
+ {
+ head = &tmp->next;
+ if( tmp->list ) remove_consts( (SYMBOL **)&(tmp->list) );
+ }
+ }
+}
+
+
+SYMBOL *assign_value( SYMBOL *dest, SYMBOL *src )
+{
+ SYMBOL *ret = NULL;
+
+ if( !dest || !src ) return ret;
+
+ if( dest->type == VARIABLE ) /* always not initialized */
+ {
+ dest->type = src->type;
+ dest->u.value = 0;
+
+ switch( src->type )
+ {
+ case NUMERICAL:
+ dest->u.value = src->u.value;
+ break;
+ case STRING:
+ dest->u.string = strdup( (const char *)src->u.string );
+ break;
+ case PATH:
+ dest->u.path = strdup( (const char *)src->u.path );
+ break;
+ default:
+ /* error */
+ break;
+ }
+ }
+ else if( dest->type == STRING || dest->type == SECTION )
+ {
+ switch( src->type )
+ {
+ case STRING:
+ if( src->u.string )
+ {
+ if( dest->u.string ) free( dest->u.string );
+ dest->u.string = strdup( (const char *)src->u.string );
+ }
+ else
+ {
+ if( dest->u.string ) free( dest->u.string );
+ dest->u.string = NULL;
+ }
+ break;
+ default:
+ /* error */
+ break;
+ }
+ }
+ else if( dest->type == PATH || dest->type == REPO )
+ {
+ switch( src->type )
+ {
+ case PATH:
+ if( src->u.path )
+ {
+ if( dest->u.path ) free( dest->u.path );
+ dest->u.path = strdup( (const char *)src->u.path );
+ }
+ else
+ {
+ if( dest->u.path ) free( dest->u.path );
+ dest->u.path = NULL;
+ }
+ break;
+ default:
+ /* error */
+ break;
+ }
+ }
+ else if( dest->type == src->type )
+ {
+ switch( src->type )
+ {
+ case NUMERICAL:
+ dest->u.value = src->u.value;
+ break;
+ case STRING:
+ if( dest->u.string ) free( dest->u.string );
+ dest->u.string = strdup( (const char *)src->u.string );
+ break;
+ case PATH:
+ if( dest->u.path ) free( dest->u.path );
+ dest->u.path = strdup( (const char *)src->u.path );
+ break;
+ default:
+ /* error */
+ break;
+ }
+ }
+ else
+ {
+ /* error */
+ }
+
+ return dest;
+}
+
+
+SYMBOL *install( const char *s, int type, ... )
+{
+ SYMBOL *sp = NULL;
+ char name[80] = "__undef";
+
+ if( !symtab ) return sp;
+
+ va_list argp;
+
+ if( ! type ) return( sp );
+
+ sp = (SYMBOL *)xmalloc( sizeof( SYMBOL ) );
+
+ switch( type )
+ {
+ case NUMERICAL:
+ case STRING:
+ case PATH:
+ sprintf( (char *)&name[0], "__const.%d", constants_counter++ );
+ sp->name = strdup( (const char *)&name[0] );
+ break;
+ case REPO:
+ sprintf( (char *)&name[0], "__repo.%d", repos_counter++ );
+ sp->name = strdup( (const char *)&name[0] );
+ break;
+ case SECTION:
+ sprintf( (char *)&name[0], "__section.%d", sections_counter++ );
+ sp->name = strdup( (const char *)&name[0] );
+ break;
+ default:
+ if( !s )
+ sp->name = strdup( (const char *)&name[0] );
+ else
+ sp->name = strdup( s );
+ break;
+ }
+ sp->type = type;
+
+ va_start( argp, type );
+
+ switch( type )
+ {
+ case SECTION:
+ case STRING:
+ {
+ char *string = (char *)va_arg( argp, char * );
+ if( string ) sp->u.string = strdup( (const char *)string );
+ break;
+ }
+ case REPO:
+ case PATH:
+ {
+ char *path = (char *)va_arg( argp, char * );
+ if( path ) sp->u.path = strdup( (const char *)path );
+ break;
+ }
+
+ case VARIABLE:
+ case NUMERICAL:
+ default:
+ sp->u.value = (int)va_arg( argp, int );
+ break;
+ }
+
+ sp->next = *(symtab->symlist); /* alloc in begin of list */
+ *(symtab->symlist) = sp;
+
+ return( sp );
+}
+
+/***********************************
+ Find variable in current symlist:
+ */
+SYMBOL *lookup( const char *s )
+{
+ SYMBOL *sp;
+
+ for( sp = *(symtab->symlist); sp != (SYMBOL *)0; sp = sp->next )
+ {
+ if( strcmp( sp->name, s ) == 0 ) return( sp );
+ }
+
+ return( 0 ); /* запись не найдена */
+}
+
+/*********************************
+ Find section in global symlist:
+ */
+SYMBOL *lookup_section( const char *s )
+{
+ SYMBOL *sp;
+
+ for( sp = symlist; sp != (SYMBOL *)0; sp = sp->next )
+ {
+ if( sp->type == SECTION && sp->u.string && strcmp( sp->u.string, s ) == 0 ) return( sp );
+ }
+
+ return( 0 ); /* запись не найдена */
+}
+
+/********************************
+ Find repo globally in symlist:
+ */
+#if 0
+SYMBOL *lookup_repo_global( const char *s )
+{
+ SYMBOL *sp;
+
+ for( sp = symlist; sp != (SYMBOL *)0; sp = sp->next )
+ {
+ /***************************
+ lookup in global section:
+ */
+ if( sp->type == REPO && sp->u.path && strcmp( sp->u.path, s ) == 0 ) return( sp );
+
+ /*************************
+ lookup in each section:
+ */
+ if( sp->type == SECTION && sp->list )
+ {
+ SYMBOL *rp;
+ for( rp = sp->list; rp != (SYMBOL *)0; rp = rp->next )
+ {
+ if( rp->type == REPO && rp->u.path && strcmp( rp->u.path, s ) == 0 ) return( rp );
+ }
+ }
+ }
+
+ return( 0 ); /* запись не найдена */
+}
+#endif
+
+/*******************************
+ Find repo in current symlist:
+ */
+SYMBOL *lookup_repo( const char *s )
+{
+ SYMBOL *sp;
+
+ for( sp = *(symtab->symlist); sp != (SYMBOL *)0; sp = sp->next )
+ {
+ if( sp->type == REPO && sp->u.path && strcmp( sp->u.path, s ) == 0 ) return( sp );
+ }
+
+ return( 0 ); /* запись не найдена */
+}
diff --git a/cscmd/symtab.h b/cscmd/symtab.h
new file mode 100644
index 0000000..5019569
--- /dev/null
+++ b/cscmd/symtab.h
@@ -0,0 +1,67 @@
+
+#ifndef __SYMTAB_H
+#define __SYMTAB_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************
+ SYMBOL is a node of symlist:
+ */
+typedef struct symbol SYMBOL;
+struct symbol
+{
+ char *name; /* Variable name */
+ int type; /* VARIABLE, SECTION, REPO, NUMERICAL, STRING, PATH */
+ union
+ {
+ int value; /* for NUMERICAL */
+ char *string; /* for STRING */
+ char *path; /* for PATH */
+ } u;
+
+ struct symbol *list; /* The list of variables. Used for SECTION and REPO */
+
+ struct symbol *next; /* Next Symbol */
+};
+
+/**********************************************
+ SYMTAB is an entry of the stack of symlists:
+ */
+typedef struct symtab SYMTAB;
+struct symtab
+{
+ SYMBOL **symlist;
+ struct symtab *next; /* Next Entry */
+};
+
+
+extern SYMBOL *symlist;
+
+extern void init_symtab( void );
+extern void push_symlist( SYMBOL **head );
+extern void pop_symlist( void );
+extern void fini_symtab( void );
+
+extern void reverse_symlist( SYMBOL **head );
+extern void remove_consts( SYMBOL **head );
+
+//debug
+extern void print_symlist( int indent, SYMBOL *head );
+
+extern SYMBOL *install( const char *s, int type, ... );
+extern SYMBOL *lookup( const char *s );
+extern SYMBOL *lookup_section( const char *s );
+extern SYMBOL *lookup_repo( const char *s );
+
+extern SYMBOL *assign_value( SYMBOL *dest, SYMBOL *src );
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SYMTAB_H */
diff --git a/cscmd/utf8ing.c b/cscmd/utf8ing.c
new file mode 100644
index 0000000..1d67c79
--- /dev/null
+++ b/cscmd/utf8ing.c
@@ -0,0 +1,121 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <locale.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <defs.h>
+#include <utf8ing.h>
+
+
+static const ucs4_t replacement_char = 0xfffd;
+static const ucs4_t maximum_ucs4 = 0x7fffffff;
+
+static const int half_shift = 10;
+static const ucs4_t half_base = 0x0010000;
+
+static const ucs4_t surrogate_high_start = 0xd800;
+static const ucs4_t surrogate_high_end = 0xdbff;
+static const ucs4_t surrogate_low_start = 0xdc00;
+static const ucs4_t surrogate_low_end = 0xdfff;
+
+static utf8_t
+first_byte_mark[7] = { 0x00, 0x00, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc };
+
+
+/***************************************************************
+ static copy_ucs4_to_utf8()
+
+ Переводит строку символов UCS4( src ) в UTF8( dest ).
+
+ Возвращаемое значение:
+ Количество байт, реально записанное в DEST.
+
+ NOTE:
+ Выход за пределы памяти, выделенной под указатель DEST
+ не контролируются.
+ Подразумевается, что строка SRC имеет null-терминатор.
+ ***************************************************************/
+int copy_ucs4_to_utf8( utf8_t *dest, const ucs4_t *src )
+{
+ utf8_t target[7];
+ utf8_t *ptr;
+ int count = 0;
+
+ while( *src )
+ {
+ ucs4_t c;
+ int bytes_to_write = 0;
+ const ucs4_t byte_mask = 0xbf;
+ const ucs4_t byte_mark = 0x80;
+
+ c = *src++;
+
+ if( c >= surrogate_high_start &&
+ c <= surrogate_high_end && *src )
+ {
+ ucs4_t c2 = *src;
+
+ if( c2 >= surrogate_low_start &&
+ c2 <= surrogate_low_end )
+ {
+ c = ((c - surrogate_high_start) << half_shift) +
+ (c2 - surrogate_low_start) + half_base;
+ ++src;
+ }
+ }
+
+ if( c < 0x80 ) bytes_to_write = 1;
+ else if( c < 0x800 ) bytes_to_write = 2;
+ else if( c < 0x10000 ) bytes_to_write = 3;
+ else if( c < 0x200000 ) bytes_to_write = 4;
+ else if( c < 0x4000000 ) bytes_to_write = 5;
+ else if( c <= maximum_ucs4 ) bytes_to_write = 6;
+ else
+ {
+ bytes_to_write = 2; c = replacement_char;
+ }
+
+ ptr = &target[0] + bytes_to_write;
+
+ switch( bytes_to_write )
+ {
+ case 6:
+ *--ptr = (c | byte_mark) & byte_mask; c >>= 6;
+ case 5:
+ *--ptr = (c | byte_mark) & byte_mask; c >>= 6;
+ case 4:
+ *--ptr = (c | byte_mark) & byte_mask; c >>= 6;
+ case 3:
+ *--ptr = (c | byte_mark) & byte_mask; c >>= 6;
+ case 2:
+ *--ptr = (c | byte_mark) & byte_mask; c >>= 6;
+ case 1:
+ *--ptr = c | first_byte_mark[bytes_to_write];
+ }
+
+ ptr = &target[0];
+
+ while( bytes_to_write > 0 )
+ {
+ *dest++ = *ptr++; /* write byte */
+ --bytes_to_write;
+ ++count;
+ }
+
+ } /* End while( *src ) */
+
+ *dest = (utf8_t)0; /* null terminator */
+
+ return( count );
+
+} /* End of static copy_ucs4_to_utf8() */
diff --git a/cscmd/utf8ing.h b/cscmd/utf8ing.h
new file mode 100644
index 0000000..d96cda8
--- /dev/null
+++ b/cscmd/utf8ing.h
@@ -0,0 +1,22 @@
+
+#ifndef __UTF8_H
+#define __UTF8_H
+
+
+typedef unsigned int ucs4_t;
+typedef unsigned char utf8_t;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+extern int copy_ucs4_to_utf8( utf8_t *, const ucs4_t * );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __UTF8_H */
diff --git a/cscmd/xalloc.c b/cscmd/xalloc.c
new file mode 100644
index 0000000..80f2581
--- /dev/null
+++ b/cscmd/xalloc.c
@@ -0,0 +1,36 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <defs.h>
+
+#include <error.h>
+#include <msglog.h>
+
+
+void *xmalloc( size_t n )
+{
+ void *p = NULL;
+
+ p = malloc( n );
+ if( !p ) no_space();
+ bzero( p, n );
+
+ return( p );
+}
+
+void *xrealloc( void *b, size_t n )
+{
+ void *p = NULL;
+
+ p = realloc( b , n );
+ if( !p ) no_space();
+
+ return( p );
+}
diff --git a/cscmd/xalloc.h b/cscmd/xalloc.h
new file mode 100644
index 0000000..3c9691b
--- /dev/null
+++ b/cscmd/xalloc.h
@@ -0,0 +1,19 @@
+
+#ifndef __XALLOC_H
+#define __XALLOC_H
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+extern void *xmalloc ( size_t );
+extern void *xrealloc ( void *, size_t );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __XALLOC_H */