cScm Configuration Daemon

cScm – is a tool to convert SCM configuration files into binary format and store its in shared memory for reading by cSvn-ui and cGit-ui CGI scripts

2 Commits   0 Branches   1 Tag
author: kx <kx@radix.pro> 2023-03-24 02:53:04 +0300 committer: kx <kx@radix.pro> 2023-03-24 02:53:04 +0300 commit: 12c7b1c5658602269da2f5b75835ec0f5fab8890 parent: 4e72ffe940d9aff7c019d37a6459e765902c1fae
Commit Summary:
Version 0.1.4
Diffstat:
24 files changed, 2810 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 */