summaryrefslogtreecommitdiff
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
parent4e72ffe940d9aff7c019d37a6459e765902c1fae (diff)
downloadcscm-12c7b1c5658602269da2f5b75835ec0f5fab8890.tar.xz
Version 0.1.4HEADcscm-0.1.4trunk
-rw-r--r--.gitignore35
-rw-r--r--.svnignore35
-rw-r--r--Makefile.am17
-rw-r--r--README2
-rw-r--r--README.md214
-rw-r--r--acsite.m455
-rwxr-xr-xauto-clean40
-rwxr-xr-xbootstrap99
-rw-r--r--configure.ac262
-rw-r--r--cscm/bcf.h123
-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
-rw-r--r--defs.h24
-rw-r--r--doc/build-packages/archlinux/PKGBUILD49
-rw-r--r--doc/build-packages/archlinux/README32
-rw-r--r--doc/build-packages/rpms/README26
-rw-r--r--doc/build-packages/rpms/cgitd.service13
-rw-r--r--doc/build-packages/rpms/cscm.spec57
-rw-r--r--doc/build-packages/rpms/csvnd.service13
-rw-r--r--doc/build-packages/slackware/README6
-rw-r--r--doc/build-packages/slackware/cscm.SlackBuild92
-rw-r--r--doc/build-packages/slackware/cscm.info10
-rw-r--r--doc/build-packages/slackware/doinst.sh15
-rw-r--r--doc/build-packages/slackware/slack-desc19
-rw-r--r--doc/cscmd.8.md112
47 files changed, 4863 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b345e85
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+
+autom4te.cache/
+
+Makefile
+Makefile.in
+config.h
+config.h.in
+config.log
+config.status
+compile
+config.guess
+config.sub
+configure
+install-sh
+missing
+stamp-h1
+aclocal.m4
+depcomp
+ylwrap
+
+cscmd/.deps/
+cscmd/Makefile
+cscmd/Makefile.in
+cscmd/cscmd.8
+cscmd/parse.c
+cscmd/parse.h
+cscmd/parse.output
+cscmd/csvn
+cscmd/cgit
+cscmd/rc.csvnd
+cscmd/rc.cgitd
+cscmd/README.csvn
+cscmd/README.cgit
+
+*~
diff --git a/.svnignore b/.svnignore
new file mode 100644
index 0000000..b345e85
--- /dev/null
+++ b/.svnignore
@@ -0,0 +1,35 @@
+
+autom4te.cache/
+
+Makefile
+Makefile.in
+config.h
+config.h.in
+config.log
+config.status
+compile
+config.guess
+config.sub
+configure
+install-sh
+missing
+stamp-h1
+aclocal.m4
+depcomp
+ylwrap
+
+cscmd/.deps/
+cscmd/Makefile
+cscmd/Makefile.in
+cscmd/cscmd.8
+cscmd/parse.c
+cscmd/parse.h
+cscmd/parse.output
+cscmd/csvn
+cscmd/cgit
+cscmd/rc.csvnd
+cscmd/rc.cgitd
+cscmd/README.csvn
+cscmd/README.cgit
+
+*~
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..cfab06b
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,17 @@
+
+ACLOCAL_AMFLAGS = -I m4
+
+#
+# In build order:
+# ==============
+#
+SUBDIRS = cscmd
+
+EXTRA_DIST = \
+ doc LICENSE README.md acsite.m4 auto-clean bootstrap \
+ cscmd/parse.y cscmd/README.in cscmd/rc.cscmd.in cscmd/logrotate.in \
+ .svnignore .gitignore
+
+nobase_include_HEADERS = cscm/bcf.h
+
+noinst_HEADERS = defs.h
diff --git a/README b/README
new file mode 100644
index 0000000..fa88f1c
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+
+see README.md instead
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..baa706f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,214 @@
+
+# cScm Configuration Daemon
+
+**cScm** Configuration Daemon &#8211; 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.
+
+
+## Reqired packages
+
+**cScm** daemon depends on [libpcre2-32](https://www.pcre.org/) library. ArchLinux distribution has the **pcre2** binary package.
+On RPM based systems you can find somethink like **pcre2-devel** RPM.
+
+
+## Installation
+
+The **cScm** package provides a regular Linux daemon with control scripts.
+
+
+### Download Sources
+
+To obtain sources we have to checkout its from SVN repository:
+
+```Bash
+svn checkout svn://radix.pro/cscm/tags/cscm-0.1.4 cscm-0.1.4
+```
+and run the bootstrap script:
+
+```Bash
+cd csvn-0.1.4
+./bootstrap
+```
+Also **cScm** source packages are available for download on the
+[Radix.pro FTP-server](https://ftp.radix.pro/pub/cscm/).
+
+
+#### Bootstrap Script
+
+The *bootstrap* script especialy created for *Autotools* install automation. To install
+*Autotools* into sourse directory on build machine (i.e. when **build** == **host**) the *bootstrap*
+script can be run without arguments.
+
+```Bash
+./bootstrap
+```
+
+I this case *Autotools* will be installed from current root file system.
+
+For the cross environment the `--target-dest-dir` option allows to install some stuff from
+development root file system:
+
+```Bash
+TARGET_DEST_DIR=/home/developer/prog/trunk-672/dist/.s9xx-glibc/enybox-x2 \
+ ./bootstrap --target-dest-dir=${TARGET_DEST_DIR}
+```
+
+For example, in this case the *aclocal.m4* script will be collected from the
+`${TARGET_DEST_DIR}/usr/share/aclocal` directory.
+
+
+### Configuring Sources
+
+```Bash
+./configure --prefix=/usr \
+ --sysconfdir=/etc \
+ --with-controldir=/etc/rc.d \
+ --with-logrotatedir=/etc/logrotate.d \
+ --with-homepath=/var/lib \
+ --with-logdir=/var/log \
+ --with-piddir=/var/run
+```
+
+
+#### Install on the Build Machine
+
+```Bash
+make
+make install
+```
+
+
+#### Cross Compilation Example
+
+```Bash
+#!/bin/sh
+
+TARGET_DEST_DIR=/home/developer/prog/trunk-672/dist/.s9xx-glibc/enybox-x2
+TOOLCHAIN_PATH=/opt/toolchains/aarch64-S9XX-linux-glibc/1.1.4/bin
+TARGET=aarch64-s9xx-linux-gnu
+
+./bootstrap --target-dest-dir=${TARGET_DEST_DIR}
+
+PKG_CONFIG=/usr/bin/pkg-config \
+PKG_CONFIG_PATH=${TARGET_DEST_DIR}/usr/lib${LIBDIRSUFFIX}/pkgconfig:${TARGET_DEST_DIR}/usr/share/pkgconfig \
+PKG_CONFIG_LIBDIR=${TARGET_DEST_DIR}/usr/lib${LIBDIRSUFFIX}/pkgconfig:${TARGET_DEST_DIR}/usr/share/pkgconfig \
+STRIP="${TOOLCHAIN_PATH}/${TARGET}-strip" \
+CC="${TOOLCHAIN_PATH}/${TARGET}-gcc --sysroot=${TARGET_DEST_DIR}" \
+./configure --prefix=/usr
+ --build=x86_64-pc-linux-gnu \
+ --host=${TARGET} \
+ --sysconfdir=/etc \
+ --with-controldir=/etc/rc.d \
+ --with-logrotatedir=/etc/logrotate.d \
+ --with-homepath=/var/lib \
+ --with-logdir=/var/log \
+ --with-piddir=/var/run
+
+make
+make install DESTDIR=${TARGET_DEST_DIR}
+```
+
+Also we can make use of additional variables such as `CFLAGS`, `LDFLAGS`:
+
+```Bash
+LDFLAGS="-L${TARGET_DEST_DIR}/lib -L${TARGET_DEST_DIR}/usr/lib"
+TARGET_INCPATH="-L${TARGET_DEST_DIR}/usr/include"
+CFLAGS="${TARGET_INCPATH}"
+CPPFLAGS="${TARGET_INCPATH}"
+```
+
+### Post Install
+
+To run **cScm** daemon for some SCM engine we have to make control scripts executable:
+
+```Bash
+chmod a+x /etc/rc.d/rc.csvnd
+chmod a+x /etc/rc.d/rc.cgitd
+```
+
+If you want to run the **cscmd** daemons on boot time then on systems with BSD-like initialization such as **Slackware** we have
+to add following lines to the */etc/rc.d/rc.M* and */etc/rc.d/rc.6* scripts correspondengly:
+
+**/etc/rc.d/rc.M:**
+
+```Bash
+# Start cSvn SCM daemon:
+if [ -x /etc/rc.d/rc.csvnd ]; then
+ /etc/rc.d/rc.csvnd start
+fi
+
+# Start cGit SCM daemon:
+if [ -x /etc/rc.d/rc.cgitd ]; then
+ /etc/rc.d/rc.cgitd start
+fi
+```
+
+**/etc/rc.d/rc.6:**
+
+```Bash
+# Stop cSvn SCM daemon:
+if [ -x /etc/rc.d/rc.csvnd ]; then
+ /etc/rc.d/rc.csvnd stop
+fi
+
+# Stop cGit SCM daemon:
+if [ -x /etc/rc.d/rc.cgitd ]; then
+ /etc/rc.d/rc.cgitd stop
+fi
+```
+
+For systems which uses systemd initialization you have to setup your own systemd unit like following:
+
+**/etc/systemd/system/csvnd.service:**
+
+```ini
+[Unit]
+Description=The cSvn daemon
+After=network.target
+
+[Service]
+PIDFile=/var/run/csvnd.pid
+ExecStart=/usr/sbin/cscmd --daemonize --inotify --scm=svn --pid=/var/run/csvnd.pid --log=/var/log/csvnd.log --config=/etc/csvn-ui.rc
+ExecReload=/bin/kill -s HUP $MAINPID
+ExecStop=/bin/kill -s TERM $MAINPID
+
+[Install]
+WantedBy=multi-user.target
+```
+
+**/etc/systemd/system/cgitd.service:**
+
+```ini
+[Unit]
+Description=The cGit daemon
+After=network.target
+
+[Service]
+PIDFile=/var/run/cgitd.pid
+ExecStart=/usr/sbin/cscmd --daemonize --inotify --scm=git --pid=/var/run/cgitd.pid --log=/var/log/cgitd.log --config=/etc/cgit-ui.rc
+ExecReload=/bin/kill -s HUP $MAINPID
+ExecStop=/bin/kill -s TERM $MAINPID
+
+[Install]
+WantedBy=multi-user.target
+```
+
+
+## Binary Packages
+
+The instructions to build binary packages for popular Linux distributions are present in the
+[**Documentation**](https://csvn.radix.pro/cscm/trunk/doc/) directory where you can find **RPM** **spec** file and also
+**ArchLinux** Package Build script.
+
+
+## See Also
+
+> [**cscmd(8)**](https://csvn.radix.pro/cscm/trunk/doc/cscmd.8.md),&nbsp;
+> [**csvn-ui.rc(5)**](https://csvn.radix.pro/csvn-ui/trunk/doc/csvn-ui.rc.5.md),&nbsp;
+> [**cgit-ui.rc(5)**](https://csvn.radix.pro/cgit-ui/trunk/doc/cgit-ui.rc.5.md).
+
+
+## Copyright and License
+
+&#169; Andrey V. Kosteltsev, 2019 &#8211; 2022.<br/>
+Code and documentation released under [the **Radix.pro** License](https://csvn.radix.pro/cscm/trunk/LICENSE).
diff --git a/acsite.m4 b/acsite.m4
new file mode 100644
index 0000000..b8faf58
--- /dev/null
+++ b/acsite.m4
@@ -0,0 +1,55 @@
+
+dnl ============================================================
+dnl Display Configuration Headers
+dnl
+dnl configure.in:
+dnl AC_MSG_CFG_PART(<text>)
+dnl ============================================================
+
+define(AC_MSG_CFG_PART,[dnl
+ AC_MSG_RESULT()
+ AC_MSG_RESULT(${TB}$1:${TN})
+])dnl
+
+AC_DEFUN(AC_CSCM_HEADLINE,[dnl
+ # Use a `Quadrigaph'. @<:@ gives you [ and @:>@ gives you ] :
+ TB=`echo -n -e '\033@<:@1m'`
+ TN=`echo -n -e '\033@<:@0m'`
+ echo ""
+ echo "Configuring ${TB}$1${TN} ($2), Version ${TB}${PACKAGE_VERSION}${TN}"
+ echo "$3"
+])dnl
+
+
+dnl ============================================================
+dnl Test for build_host `ln -s' .
+dnl ============================
+dnl
+dnl Usage:
+dnl -----
+dnl AC_PATH_PROG_LN_S
+dnl AC_SUBST(LN)
+dnl AC_SUBST(LN_S)
+dnl
+dnl ============================================================
+AC_DEFUN(AC_PATH_PROG_LN_S,
+[AC_PATH_PROG(LN, ln, no, /usr/local/bin:/usr/bin:/bin:$PATH)
+AC_MSG_CHECKING(whether ln -s works on build host)
+AC_CACHE_VAL(ac_cv_path_prog_LN_S,
+[rm -f conftestdata
+if $LN -s X conftestdata 2>/dev/null
+then
+ rm -f conftestdata
+ ac_cv_path_prog_LN_S="$LN -s"
+else
+ ac_cv_path_prog_LN_S="$LN"
+fi])dnl
+LN_S="$ac_cv_path_prog_LN_S"
+if test "$ac_cv_path_prog_LN_S" = "$LN -s"; then
+ AC_MSG_RESULT(yes)
+else
+ AC_MSG_RESULT(no)
+fi
+AC_SUBST(LN)dnl
+AC_SUBST(LN_S)dnl
+])
diff --git a/auto-clean b/auto-clean
new file mode 100755
index 0000000..b100d64
--- /dev/null
+++ b/auto-clean
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+CWD=`pwd`
+
+program=`basename $0`
+
+usage() {
+ cat << EOF
+
+Usage: $program [options]
+
+Options:
+ -h,--help Display this message.
+
+EOF
+}
+
+if [ -f "${CWD}/Makefile" ] ; then
+ make distclean
+fi
+
+svnignore='.svnignore'
+
+while read ln; do
+ line=`echo "${ln}" | sed 's,^[ \t],,' | sed 's,[ \t]$,,'`
+ if [ "x$line" != "x" -a "${line:0:1}" != "#" ] ; then
+ if `echo "${line}" | grep -q '\*~$'` ; then
+ find "`dirname "${line}"`" -type f -iname '*~' -print0 | while IFS= read -r -d '' file ; do
+ rm -f "$file"
+ done
+ elif `echo "${line}" | grep -q '\*'` ; then
+ find "`dirname "${line}"`" -type f -iname "`basename "${line}"`" -print0 | while IFS= read -r -d '' file ; do
+ rm -f "$file"
+ done
+ else
+ if [ -d "${line}" ] ; then rm -rf "${line}" ; fi
+ if [ -f "${line}" ] ; then rm -f "${line}" ; fi
+ fi
+ fi
+done < ${CWD}/${svnignore}
diff --git a/bootstrap b/bootstrap
new file mode 100755
index 0000000..f2592c6
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,99 @@
+#!/bin/sh
+
+CWD=`pwd`
+
+program=`basename $0`
+
+usage() {
+ cat << EOF
+
+Usage: $program [options]
+
+Options:
+ -h,--help Display this message.
+ -d,--target-dest-dir=DIR The target ROOTFS directory
+ [default: DIR=/].
+
+EOF
+}
+
+TARGET_DEST_DIR=/
+ACDIR=usr/share/aclocal
+INCDIR=usr/include
+SYSTEM_ACDIR=
+SYSTEM_INCDIR=
+
+while [ 0 ] ; do
+ if [ "$1" = "-h" -o "$1" = "--help" ] ; then
+ usage
+ exit 0
+ elif [ "$1" = "-d" -o "$1" = "--target-dest-dir" ] ; then
+ if [ "$2" = "" ] ; then
+ echo -e "\n${program}: ERROR: --target-dest-dir is not specified.\n"
+ usage
+ exit 1
+ fi
+ TARGET_DEST_DIR="$2"
+ shift 2
+ elif [[ $1 == --target-dest-dir=* ]] ; then
+ TARGET_DEST_DIR="`echo $1 | cut -f2 -d'='`"
+ shift 1
+ else
+ if [ "$1" != "" ] ; then
+ echo -e "\n${program}: ERROR: Unknown argument: $1.\n"
+ usage
+ exit 1
+ fi
+ break
+ fi
+done
+
+if [ ! -d "${TARGET_DEST_DIR}" ] ; then
+ echo -e "\n${program}: ERROR: --target-dest-dir is not a directory.\n"
+ usage
+ exit 1
+fi
+
+#
+# Absolute path:
+#
+if [ "${TARGET_DEST_DIR:0:1}" != "/" ] ; then
+ TARGET_DEST_DIR=${CWD}/${TARGET_DEST_DIR}
+fi
+
+#
+# Remove last '/' char:
+#
+if [ "${TARGET_DEST_DIR: -1}" = "/" ] ; then
+ len=${#TARGET_DEST_DIR}
+ let "len = len - 1"
+ tmp="${TARGET_DEST_DIR:0:$len}"
+ TARGET_DEST_DIR=${tmp}
+fi
+
+SYSTEM_ACDIR="${TARGET_DEST_DIR}/${ACDIR}"
+SYSTEM_INCDIR="${TARGET_DEST_DIR}/${INCDIR}"
+
+
+aclocal --install -I m4 --force --system-acdir=${SYSTEM_ACDIR}
+autoheader --include=${SYSTEM_INCDIR}
+automake --foreign --add-missing --copy --force-missing
+autoconf --force
+
+#
+# Add 'cCgit Features' header to split help output:
+#
+cat >cscm-features <<_CGIT
+_ACEOF
+
+ cat <<\\_ACEOF
+
+cScm Features:
+_CGIT
+
+sed -i '/^[ ]*\-\-with\-controldir=DIR/ {
+r cscm-features
+N
+}' configure
+
+rm -f cscm-features
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..cc7eaba
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,262 @@
+
+dnl ============================================================
+dnl Process this file with autoconf to produce
+dnl a configure script.
+dnl ============================================================
+
+AC_PREREQ(2.71)dnl dnl Minimum Autoconf version required.
+
+
+AC_INIT([cscm], [0.1.4],
+ [support@radix.pro], [cscm], [https://radix.pro])
+
+dnl ============================================================
+dnl m4's diversions:
+dnl ---------------
+dnl
+dnl see: /use/share/autoconf/autoconf/general.m4
+dnl ============================================================
+m4_divert_push([M4SH-INIT])
+CSVN_NAME=svn
+CSVN_CONFIG_FILE=csvn-ui.rc
+CSVN_PROGRAM=csvn
+CSVN_PROGRAM_NAME=cSvn
+CGIT_NAME=git
+CGIT_CONFIG_FILE=cgit-ui.rc
+CGIT_PROGRAM=cgit
+CGIT_PROGRAM_NAME=cGit
+PACKAGE_DAEMON=cscmd
+PACKAGE_LICENSE=Radix-1.0
+CSCM_CONTROL_DIR=/etc/rc.d
+CSCM_LOGROTATE_DIR=/etc/logrotate.d
+CSCM_HOME_PATH=/var/lib
+CSCM_LOG_DIR=/var/log
+CSCM_PID_DIR=/var/run
+m4_divert_pop([M4SH-INIT])
+
+AC_CSCM_HEADLINE([cscm], [cScm], [Copyright (c) 2019-2022 Andrey V.Kosteltsev])
+
+
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl $$ $$
+dnl $$ PART: Init Automake environment $$
+dnl $$ $$
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+AC_MSG_CFG_PART(Init Automake environment)
+
+AC_CANONICAL_TARGET
+
+AM_INIT_AUTOMAKE([subdir-objects foreign no-dist-gzip dist-xz])
+
+AC_CONFIG_HEADERS([config.h])
+
+AC_PREFIX_DEFAULT(/usr/local)
+
+
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl $$ $$
+dnl $$ PART: Test for Build Tools $$
+dnl $$ $$
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+AC_MSG_CFG_PART(Test for build tools)
+AC_CHECK_TOOL([GCC], [gcc], [:])
+AC_PATH_PROG([BISON], [bison], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+test "$BISON" = "no" && AC_MSG_ERROR([Unable to find required program 'bison'])
+
+
+AC_MSG_CFG_PART(Test for Header files)
+dnl
+dnl Check for system header files.
+dnl =============================
+dnl /* GetText частично проверил. Но мы делаем для себя. */
+AC_CHECK_HEADERS( locale.h )
+AC_CHECK_HEADERS( sys/types.h sys/stat.h stdlib.h stddef.h )
+AC_CHECK_HEADERS( memory.h string.h strings.h )
+AC_CHECK_HEADERS( inntypes.h stdint.h unistd.h )
+AC_CHECK_HEADERS( fcntl.h errno.h )
+AC_CHECK_HEADERS( sys/file.h )
+AC_CHECK_HEADERS( sys/uio.h )
+AC_CHECK_HEADERS( stdarg.h )
+AC_CHECK_HEADERS( sys/time.h )
+AC_CHECK_HEADERS( limits.h )
+AC_CHECK_HEADERS( endian.h )
+AC_CHECK_HEADERS( pcre2.h )
+AC_CHECK_HEADERS( wchar.h )
+AC_CHECK_HEADERS( wctype.h )
+
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl $$ $$
+dnl $$ PART: Test for Libraries $$
+dnl $$ $$
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+AC_MSG_CFG_PART(Test for libraries)
+AC_CHECK_LIB([rt],[aio_suspend],[],[AC_MSG_ERROR([Unable to find required librt])])
+AC_CHECK_LIB([pcre2-32],[pcre2_compile_32],[],[AC_MSG_ERROR([Unable to find required libpcre2-32])])
+AC_CHECK_LIB([m],[round],[],[AC_MSG_ERROR([Unable to find required libm])])
+
+
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl $$ $$
+dnl $$ PART: cScm Features $$
+dnl $$ $$
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+AC_ARG_WITH([controldir],
+ [AS_HELP_STRING([--with-controldir=DIR], [cScm Daemon CONTROL directory @<:@default=/etc/rc.d@:>@],[26],[84])],
+ [controldir=$withval],
+ [controldir="/etc/rc.d"])
+AC_SUBST([controldir], [$controldir])
+AC_SUBST([CSCM_CONTROL_DIR], [$controldir])
+AC_DEFINE_UNQUOTED([CSCM_CONTROL_DIR], ["$CSCM_CONTROL_DIR"], [Define the cScm Daemon CONTROL directory])
+
+AC_ARG_WITH([logrotatedir],
+ [AS_HELP_STRING([--with-logrotatedir=DIR], [cScm Daemon LOGROTATE directory @<:@default=/etc/logrotate.d@:>@],[26],[87])],
+ [logrotatedir=$withval],
+ [logrotatedir="/etc/logrotate.d"])
+AC_SUBST([logrotatedir], [$logrotatedir])
+AC_SUBST([CSCM_LOGROTATE_DIR], [$logrotatedir])
+AC_DEFINE_UNQUOTED([CSCM_LOGROTATE_DIR], ["$CSCM_LOGROTATE_DIR"], [Define the cScm Daemon LOGROTATE directory])
+
+AC_ARG_WITH([homepath],
+ [AS_HELP_STRING([--with-homepath=DIR], [cSvn Daemon path to HOME directories @<:@default=/var/lib@:>@],[26],[84])],
+ [homepath=$withval],
+ [homepath="/var/lib"])
+AC_SUBST([homepath], [$homepath])
+AC_SUBST([CSCM_HOME_PATH], [$homepath])
+AC_DEFINE_UNQUOTED([CSCM_HOME_PATH], ["$CSCM_HOME_PATH"], [Define the cScm Daemon path to HOME directories])
+AC_SUBST([csvndhomedir], [$homepath/$CSVN_PROGRAM])
+AC_SUBST([CSVN_HOME_DIR], [$csvndhomedir])
+AC_DEFINE_UNQUOTED([CSVN_HOME_DIR], ["$CSVN_HOME_DIR"], [Define the cSvn Daemon HOME directory])
+AC_SUBST([cgitdhomedir], [$homepath/$CGIT_PROGRAM])
+AC_SUBST([CGIT_HOME_DIR], [$cgitdhomedir])
+AC_DEFINE_UNQUOTED([CGIT_HOME_DIR], ["$CGIT_HOME_DIR"], [Define the cGit Daemon HOME directory])
+
+AC_ARG_WITH([logdir],
+ [AS_HELP_STRING([--with-logdir=DIR], [cSvn Daemon LOG directory @<:@default=/var/log@:>@],[26],[84])],
+ [logdir=$withval],
+ [logdir="/var/log"])
+AC_SUBST([logdir], [$logdir])
+AC_SUBST([CSCM_LOG_DIR], [$logdir])
+AC_DEFINE_UNQUOTED([CSCM_LOG_DIR], ["$CSCM_LOG_DIR"], [Define the cScm Daemon LOG directory])
+
+AC_ARG_WITH([piddir],
+ [AS_HELP_STRING([--with-piddir=DIR], [cScm Daemon PID directory @<:@default=/var/run@:>@],[26],[84])],
+ [piddir=$withval],
+ [piddir="/var/run"])
+AC_SUBST([piddir], [$piddir])
+AC_SUBST([CSCM_PID_DIR], [$piddir])
+AC_DEFINE_UNQUOTED([CSCM_PID_DIR], ["$CSCM_PID_DIR"], [Define the cScm Daemon PID directory])
+
+AC_SUBST([CSVN_NAME], [$CSVN_NAME])
+AC_SUBST([CSVN_CONFIG], [$sysconfdir/$CSVN_CONFIG_FILE])
+AC_DEFINE_UNQUOTED([CSVN_CONFIG], ["$CSVN_CONFIG"], [Define the cSvn Default Configuration File])
+AC_SUBST([CSVN_PROGRAM], [$CSVN_PROGRAM])
+AC_DEFINE_UNQUOTED([CSVN_PROGRAM], ["$CSVN_PROGRAM"], [Define the cSvn Default Program Name])
+AC_SUBST([CSVN_PROGRAM_NAME], [$CSVN_PROGRAM_NAME])
+AC_SUBST([CGIT_NAME], [$CGIT_NAME])
+AC_SUBST([CGIT_CONFIG], [$sysconfdir/$CGIT_CONFIG_FILE])
+AC_DEFINE_UNQUOTED([CGIT_CONFIG], ["$CGIT_CONFIG"], [Define the cGit Default Configuration File])
+AC_SUBST([CGIT_PROGRAM], [$CGIT_PROGRAM])
+AC_DEFINE_UNQUOTED([CGIT_PROGRAM], ["$CGIT_PROGRAM"], [Define the cGit Default Program Name])
+AC_SUBST([CGIT_PROGRAM_NAME], [$CGIT_PROGRAM_NAME])
+
+
+AC_SUBST(PROGRAM_NAME,[${PACKAGE_NAME}])
+AC_SUBST(PROGRAM_DAEMON,[${PACKAGE_DAEMON}])
+AC_SUBST(PROGRAM_VERSION,[${PACKAGE_VERSION}])
+AC_SUBST(PROGRAM_LICENSE,[${PACKAGE_LICENSE}])
+
+AC_DEFINE_UNQUOTED([PROGRAM_NAME], ["$PROGRAM_NAME"], [Define the program name])
+AC_DEFINE_UNQUOTED([PROGRAM_DAEMON], ["$PROGRAM_DAEMON"], [Define the daemon name])
+AC_DEFINE_UNQUOTED([PROGRAM_VERSION], ["$PROGRAM_VERSION"], [Define the version of all programs in this package])
+AC_DEFINE_UNQUOTED([PROGRAM_LICENSE], ["$PROGRAM_LICENSE"], [Define the License of all programs in this package])
+
+
+dnl ============================================================
+dnl Environment Variables:
+dnl ---------------------
+dnl For 'Some influential environment variables:' help section
+dnl ============================================================
+AC_ARG_VAR([STRIP], [strip command])
+
+
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl $$ $$
+dnl $$ PART: Test for Auxiliary (my be version sensitive) $$
+dnl $$ programs $$
+dnl $$ $$
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+AC_MSG_CFG_PART(Test for aux programs)
+AC_PATH_PROG_LN_S
+AC_PATH_PROG([CAT], [cat], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([CHMOD], [chmod], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([CP], [cp], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([FIND], [find], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([GZIP], [gzip], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([MV], [mv], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([RM], [rm], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([SED], [sed], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([TAR], [tar], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+AC_PATH_PROG([XZ], [xz], [no], [/usr/local/bin:/usr/bin:/bin:$PATH])
+
+
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl $$ $$
+dnl $$ PART: OUTPUT Substitution $$
+dnl $$ $$
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+dnl ============================================================
+AC_MSG_CFG_PART(OUTPUT)
+
+AC_CONFIG_FILES([
+cscmd/Makefile
+cscmd/cscmd.8
+Makefile
+])
+AC_OUTPUT
diff --git a/cscm/bcf.h b/cscm/bcf.h
new file mode 100644
index 0000000..2ff52a9
--- /dev/null
+++ b/cscm/bcf.h
@@ -0,0 +1,123 @@
+
+#ifndef __BCF_H
+#define __BCF_H
+
+/******************************************
+ Binary config file format (32-bit only):
+ */
+
+#define BCF_MAG0 0x3f /* b_ident[BI_MAG0] */
+#define BCF_MAG1 'B' /* b_ident[BI_MAG1] */
+#define BCF_MAG2 'C' /* b_ident[BI_MAG2] */
+#define BCF_MAG3 'F' /* b_ident[BI_MAG3] */
+
+#define BCFMAG "\77BCF"
+#define SZBCFMAG 4
+
+/* b_ident[BI_CLASS]: */
+#define BCF_CLASS_NONE 0 /* invalid class */
+#define BCF_CLASS_32 1 /* 32-bit objects */
+#define BCF_CLASS_64 2 /* 64-bit objects (reserved) */
+
+/* b_ident[BI_DATA]: */
+#define BCF_DATA_NONE 0 /* invalid data encoding */
+#define BCF_DATA_LSB 1 /* Least Significant Byte */
+#define BCF_DATA_MSB 2 /* Most Significant Byte */
+
+#define BV_NONE 0 /* invalid version */
+#define BV_CURRENT 1 /* current file version b_ident[BI_VERSION] */
+
+#define BCF_PAD 8 /* start of padding bytes in b_ident b_ident[BI_PAD] */
+
+#define BI_MAG0 0 /* file identification */
+#define BI_MAG1 1 /* file identification */
+#define BI_MAG2 2 /* file identification */
+#define BI_MAG3 3 /* file identification */
+#define BI_CLASS 4 /* file class */
+#define BI_DATA 5 /* data encoding */
+#define BI_VERSION 6 /* file version */
+#define BI_PAD 7 /* start of padding bytes */
+
+#define BI_NIDENT (16) /* size of b_ident[] */
+
+#define SI_NIDENT (8) /* size of b_ident[] */
+
+/* Type for a 16-bit quantity: */
+typedef uint16_t Bcf32_Half;
+
+/* Type for a 32-bit quantity: */
+typedef uint32_t Bcf32_Word;
+typedef int32_t Bcf32_Sword;
+
+/* Type for a 64-bit quantity: */
+typedef uint64_t Bcf32_Xword;
+typedef int64_t Bcf32_Sxword;
+
+/* Types of address: */
+typedef uint32_t Bcf32_Addr;
+
+/* Types of file offsets: */
+typedef uint32_t Bcf32_Off;
+
+typedef struct {
+ unsigned char b_ident[BI_NIDENT];
+ Bcf32_Half b_hsize; /* BCF header's size in bytes */
+ Bcf32_Word b_fsize; /* Whole BCF file size in bytes */
+
+ Bcf32_Off b_shoff; /* section header table’s file offset in bytes */
+ Bcf32_Half b_shentsize; /* section header's size in bytes */
+ Bcf32_Half b_shnum; /* number of entries in section headers table */
+
+ Bcf32_Off b_rhoff; /* repository header table’s file offset in bytes */
+ Bcf32_Half b_rhentsize; /* repository header's size in bytes */
+ Bcf32_Half b_rhnum; /* number of entries in repository headers table */
+
+ Bcf32_Off b_dtoff; /* data entries table’s file offset in bytes */
+ Bcf32_Half b_dtentsize; /* data entry's size in bytes */
+ Bcf32_Half b_dtnum; /* number of entries in data entries table */
+
+ Bcf32_Off b_stoff; /* string table’s file offset in bytes */
+} __attribute__ ((packed)) Bcf32_fhdr;
+
+/* Type of setcions: */
+#define ST_GLOBAL 1 /* this section contains variable entries only */
+#define ST_REPOS 2 /* this section contains repository entries only */
+
+#define SMAG_GLOBAL ".global"
+#define SMAG_REPOS ".repos"
+
+typedef struct {
+ unsigned char s_name[SI_NIDENT];
+ Bcf32_Half s_type; /* .global or .repos */
+ Bcf32_Off s_shdr; /* section header's offset in the string table */
+ Bcf32_Off s_sdata; /* section data/repos's file offset */
+ Bcf32_Half s_dnum; /* number of data/repos entries in section */
+} __attribute__ ((packed)) Bcf32_shdr;
+
+typedef struct {
+ Bcf32_Off r_rhdr; /* repo header's offset in the string table */
+ Bcf32_Off r_rdata; /* repo data's file offset */
+ Bcf32_Half r_dnum; /* number of data entries in repo */
+} __attribute__ ((packed)) Bcf32_rhdr;
+
+#define DT_NUMERICAL 0x01
+#define DT_PATH 0x02
+#define DT_STRING 0x04
+
+typedef struct {
+ Bcf32_Off d_name; /* variable name's offset in the string table */
+ union
+ {
+ Bcf32_Word d_value; /* numerical variable's value */
+ Bcf32_Off d_valptr; /* variable value's offset in string table */
+ } _v;
+ Bcf32_Half d_type; /* 0x01 - numerical, 0x02 - path, 0x04 - string */
+} __attribute__ ((packed)) Bcf32_dntr;
+
+/************************
+ Shared memory (POSIX API):
+ */
+#define CSVN_SHM_BCF "/csvn.bcf"
+#define CGIT_SHM_BCF "/cgit.bcf"
+
+#endif /* __BCF_H */
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 */
diff --git a/defs.h b/defs.h
new file mode 100644
index 0000000..8a69be9
--- /dev/null
+++ b/defs.h
@@ -0,0 +1,24 @@
+
+#ifndef __DEFS_H
+#define __DEFS_H
+
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DEFS_H */
diff --git a/doc/build-packages/archlinux/PKGBUILD b/doc/build-packages/archlinux/PKGBUILD
new file mode 100644
index 0000000..15218b2
--- /dev/null
+++ b/doc/build-packages/archlinux/PKGBUILD
@@ -0,0 +1,49 @@
+#
+# Maintainer: Andrey V.Kosteltsev <kx@radix.pro>
+#
+pkgname=cscm
+pkgver=0.1.4
+pkgrel=2
+pkgdesc='cScm Configuration Daemon for cSvn-ui and cGit-ui packages'
+arch=('x86_64')
+url='https://csvn.radix.pro'
+license=('custom')
+depends=('pcre2')
+source=("https://ftp.radix.pro/pub/cscm/${pkgname}-${pkgver}.tar.xz")
+md5sums=('..Check MD5 sum before Building Package..')
+
+build() {
+ cd ${pkgname}-${pkgver}
+
+ CFLAGS="-O2 -fPIC -Wno-unused-result" \
+ ./configure \
+ --prefix=/usr \
+ --sysconfdir=/etc \
+ --libdir=/usr/lib \
+ --sbindir=/usr/bin \
+ --with-controldir=/etc/rc.d \
+ --with-logrotatedir=/etc/logrotate.d \
+ --with-homepath=/var/lib \
+ --with-logdir=/var/log \
+ --with-piddir=/var/run
+ make
+}
+
+package() {
+ cd ${pkgname}-${pkgver}
+
+ make install DESTDIR="${pkgdir}"
+
+ # Install systemd unit:
+ install -d ${pkgdir}/usr/lib/systemd/system
+ install -m 644 doc/build-packages/rpms/csvnd.service ${pkgdir}/usr/lib/systemd/system
+ install -m 644 doc/build-packages/rpms/cgitd.service ${pkgdir}/usr/lib/systemd/system
+ rm -rf ${pkgdir}/etc/rc.d
+
+ # Gsip man pages:
+ gzip -9 ${pkgdir}/usr/share/man/man8/cscmd.8
+
+ # Install documentation:
+ install -d ${pkgdir}/usr/share/doc/${pkgname}-${pkgver}
+ cp -a LICENSE README README.md doc/ ${pkgdir}/usr/share/doc/${pkgname}-${pkgver}
+}
diff --git a/doc/build-packages/archlinux/README b/doc/build-packages/archlinux/README
new file mode 100644
index 0000000..0ffa946
--- /dev/null
+++ b/doc/build-packages/archlinux/README
@@ -0,0 +1,32 @@
+
+Sytem requires:
+ fakeroot, logrotate
+
+Build time requires:
+ make, binutils, gcc, bison, autoconf, automake, pkgconfig,
+ pcre2
+
+Runtime requires:
+ pcre2
+
+As non-privileged user:
+
+ $ mkdir build
+ $ cp PKGBUILD build/
+ $ cd build/
+ $ makepkg
+
+As root:
+
+ # pacman -U ./cscm-0.1.4-2-x86_64.pkg.tar.zst
+
+After install binary package we have to create /etc/csvn-ui.rc [or|and] /etc/cgit-ui.rc
+configuration files and then enable and run csvnd.service [or|and] cgitd.service:
+
+ $ systemctl enable csvnd.service
+ $ systemctl start csvnd.service
+
+ $ systemctl enable cgitd.service
+ $ systemctl start cgitd.service
+
+Then we can to enable csvn-ui [or|and] cgit-ui web-servers.
diff --git a/doc/build-packages/rpms/README b/doc/build-packages/rpms/README
new file mode 100644
index 0000000..e8c6c5d
--- /dev/null
+++ b/doc/build-packages/rpms/README
@@ -0,0 +1,26 @@
+
+To build RPM packages we have to copy source package downloaded
+from https://ftp.radix.pro/pub/cscm directory to /usr/src/packages/SOURSES/
+directory. And also copy the cscm.spec file into /usr/src/packages/SPECS/
+directory. Then edit the package version in the cscm.spec file according
+to source package version.
+
+Dependencies: pcre2-devel.
+
+RPMs can be built by following command:
+
+ $ rpmbuild --define "_topdir /usr/src/packages" -ba /usr/src/packages/SPECS/cscm.spec
+
+Resulting RPMs will be saved in /usr/src/packages/SRPMS/ and
+/usr/src/packages/RPMS/`uname -m`/ directories.
+
+After install binary RPM we have to create /etc/csvn-ui.rc [or|and] /etc/cgit-ui.rc
+configuration files and then enable and run csvnd.service [or|and] cgitd.service:
+
+ $ systemctl enable csvnd.service
+ $ systemctl start csvnd.service
+
+ $ systemctl enable cgitd.service
+ $ systemctl start cgitd.service
+
+Then we can to enable csvn-ui [or|and] cgit-ui web-servers.
diff --git a/doc/build-packages/rpms/cgitd.service b/doc/build-packages/rpms/cgitd.service
new file mode 100644
index 0000000..fa87546
--- /dev/null
+++ b/doc/build-packages/rpms/cgitd.service
@@ -0,0 +1,13 @@
+
+[Unit]
+Description=The cGit daemon
+After=network.target
+
+[Service]
+PIDFile=/var/run/cgitd.pid
+ExecStart=/usr/sbin/cscmd --daemonize --inotify --scm=git --pid=/var/run/cgitd.pid --log=/var/log/cgitd.log --config=/etc/cgit-ui.rc
+ExecReload=/bin/kill -s HUP $MAINPID
+ExecStop=/bin/kill -s TERM $MAINPID
+
+[Install]
+WantedBy=multi-user.target
diff --git a/doc/build-packages/rpms/cscm.spec b/doc/build-packages/rpms/cscm.spec
new file mode 100644
index 0000000..3ce0248
--- /dev/null
+++ b/doc/build-packages/rpms/cscm.spec
@@ -0,0 +1,57 @@
+
+Name: cscm
+Version: 0.1.4
+Release: 2
+Summary: cScm Configuration Daemon
+License: RADIX-1.0
+Group: System/base
+Source: https://ftp.radix.pro/pub/cscm/cscm-0.1.4.tar.xz
+Url: https://csvn.radix/pro/cscm/
+
+
+%define _sysconfdir /etc
+%define _libdir /usr/lib64
+%define _homepath /var/lib
+%define _logdir /var/log
+%define _piddir /var/run
+%define _systemddir /usr/lib/systemd/system
+
+
+%description
+cScm - is a Configuration Daemon for cSvn-ui and cGit-ui packages
+
+%prep
+%setup -q
+
+%build
+./configure \
+ --prefix=/usr \
+ --sysconfdir=%{_sysconfdir} \
+ --libdir=%{_libdir} \
+ --with-controldir=%{_sysconfdir}/rc.d \
+ --with-logrotatedir=%{_sysconfdir}/logrotate.d \
+ --with-homepath=%{_homepath} \
+ --with-logdir=%{_logdir} \
+ --with-piddir=%{_piddir}
+
+%install
+make install DESTDIR=%{buildroot}
+install -d %{buildroot}%{_systemddir}
+install -m 644 doc/build-packages/rpms/csvnd.service %{buildroot}%{_systemddir}
+install -m 644 doc/build-packages/rpms/cgitd.service %{buildroot}%{_systemddir}
+rm -rf %{buildroot}%{_sysconfdir}/rc.d
+
+%clean
+%{?buildroot:%__rm -rf "%{buildroot}"}
+
+%files
+%defattr(-,root,root)
+%{_sysconfdir}/*
+%attr(755,root,root) %{_sbindir}/cscmd
+%{_homepath}/*
+%{_includedir}/*
+%{_datadir}/*
+%doc LICENSE README README.md doc/
+%{_systemddir}/csvnd.service
+%{_systemddir}/cgitd.service
+
diff --git a/doc/build-packages/rpms/csvnd.service b/doc/build-packages/rpms/csvnd.service
new file mode 100644
index 0000000..f51919c
--- /dev/null
+++ b/doc/build-packages/rpms/csvnd.service
@@ -0,0 +1,13 @@
+
+[Unit]
+Description=The cSvn daemon
+After=network.target
+
+[Service]
+PIDFile=/var/run/csvnd.pid
+ExecStart=/usr/sbin/cscmd --daemonize --inotify --scm=svn --pid=/var/run/csvnd.pid --log=/var/log/csvnd.log --config=/etc/csvn-ui.rc
+ExecReload=/bin/kill -s HUP $MAINPID
+ExecStop=/bin/kill -s TERM $MAINPID
+
+[Install]
+WantedBy=multi-user.target
diff --git a/doc/build-packages/slackware/README b/doc/build-packages/slackware/README
new file mode 100644
index 0000000..b9a2999
--- /dev/null
+++ b/doc/build-packages/slackware/README
@@ -0,0 +1,6 @@
+
+cscm (a SCM Configuration Daemon)
+
+cScm is a SCM Configuration daemon for cSvn-ui and cGit-ui packages.
+
+Homepage: https://csvn.radix.pro
diff --git a/doc/build-packages/slackware/cscm.SlackBuild b/doc/build-packages/slackware/cscm.SlackBuild
new file mode 100644
index 0000000..110edc0
--- /dev/null
+++ b/doc/build-packages/slackware/cscm.SlackBuild
@@ -0,0 +1,92 @@
+#!/bin/bash
+
+cd $(dirname $0) ; CWD=$(pwd)
+
+PKGNAM=cscm
+VERSION=${VERSION:-$(echo cscm-*.tar.?z* | rev | cut -f 3- -d . | cut -f 1 -d - | rev)}
+BUILD=${BUILD:-2}
+
+# Automatically determine the architecture we're building on:
+if [ -z "$ARCH" ]; then
+ case "$( uname -m )" in
+ i?86) export ARCH=i586 ;;
+ arm*) export ARCH=arm ;;
+ # Unless $ARCH is already set, use uname -m for all other archs:
+ *) export ARCH=$( uname -m ) ;;
+ esac
+fi
+
+if [ "$ARCH" = "i586" ]; then
+ SLKCFLAGS="-O2 -march=i586 -mtune=i686"
+ LIBDIRSUFFIX=""
+elif [ "$ARCH" = "s390" ]; then
+ SLKCFLAGS="-O2"
+ LIBDIRSUFFIX=""
+elif [ "$ARCH" = "x86_64" ]; then
+ SLKCFLAGS="-O2 -fPIC"
+ LIBDIRSUFFIX="64"
+else
+ SLKCFLAGS="-O2"
+ LIBDIRSUFFIX=""
+fi
+
+TMP=${TMP:-/tmp}
+PKG=$TMP/package-${PKGNAM}
+
+rm -rf $PKG
+mkdir -p $TMP $PKG
+
+cd $TMP
+rm -rf ${PKGNAM}-${VERSION}
+tar xvf $CWD/${PKGNAM}-${VERSION}.tar.?z* || exit 1
+cd ${PKGNAM}-$VERSION || exit 1
+
+chown -R root:root .
+find . \
+ \( -perm 777 -o -perm 775 -o -perm 711 -o -perm 555 -o -perm 511 \) \
+ -exec chmod 755 {} \+ -o \
+ \( -perm 666 -o -perm 664 -o -perm 600 -o -perm 444 -o -perm 440 -o -perm 400 \) \
+ -exec chmod 644 {} \+
+
+CFLAGS="$SLKCFLAGS" \
+./configure \
+ --prefix=/usr \
+ --sysconfdir=/etc \
+ --libdir=/usr/lib${LIBDIRSUFFIX} \
+ --with-controldir=/etc/rc.d \
+ --with-logrotatedir=/etc/logrotate.d \
+ --with-homepath=/var/lib \
+ --with-logdir=/var/log \
+ --with-piddir=/var/run \
+ --build=$ARCH-slackware-linux || exit 1
+
+
+make || exit 1
+make install DESTDIR=$PKG || exit 1
+
+mv $PKG/etc/rc.d/rc.csvnd $PKG/etc/rc.d/rc.csvnd.new
+mv $PKG/etc/rc.d/rc.cgitd $PKG/etc/rc.d/rc.cgitd.new
+
+find $PKG | xargs file | grep -e "executable" -e "shared object" | grep ELF \
+ | cut -f 1 -d : | xargs strip --strip-unneeded 2> /dev/null
+
+mv $PKG/usr/share/man $PKG/usr/
+gzip -9 $PKG/usr/man/man?/*
+rmdir $PKG/usr/share/man
+
+find $PKG/usr/man -type f -exec gzip -9 {} \;
+
+mkdir -p $PKG/usr/doc/${PKGNAM}-${VERSION}
+cp -a \
+ LICENSE README README.md doc \
+ $PKG/usr/doc/${PKGNAM}-${VERSION}
+
+cp -a $CWD/${PKGNAM}.info $PKG/usr/doc/${PKGNAM}-${VERSION}
+cp -a $CWD/${PKGNAM}.SlackBuild $PKG/usr/doc/${PKGNAM}-${VERSION}
+
+mkdir -p $PKG/install
+cat $CWD/doinst.sh > $PKG/install/doinst.sh
+cat $CWD/slack-desc > $PKG/install/slack-desc
+
+cd $PKG
+/sbin/makepkg -l y -c n $TMP/${PKGNAM}-${VERSION}-${ARCH}-${BUILD}.txz
diff --git a/doc/build-packages/slackware/cscm.info b/doc/build-packages/slackware/cscm.info
new file mode 100644
index 0000000..9436a98
--- /dev/null
+++ b/doc/build-packages/slackware/cscm.info
@@ -0,0 +1,10 @@
+PRGNAM="cscm"
+VERSION="0.1.4"
+HOMEPAGE="http://csvn.radix.pro"
+DOWNLOAD="https://ftp.radix.pro/pub/cscm/cscm-0.1.4.tar.xz"
+MD5SUM="..Check MD5 sum before Building Package.."
+DOWNLOAD_x86_64=""
+MD5SUM_x86_64=""
+REQUIRES="libpcre2-32"
+MAINTAINER="Andrey V.Kosteltsev"
+EMAIL="kx@radix.pro"
diff --git a/doc/build-packages/slackware/doinst.sh b/doc/build-packages/slackware/doinst.sh
new file mode 100644
index 0000000..7ee79cf
--- /dev/null
+++ b/doc/build-packages/slackware/doinst.sh
@@ -0,0 +1,15 @@
+
+config() {
+ NEW="$1"
+ OLD="`dirname $NEW`/`basename $NEW .new`"
+ # If there's no config file by that name, mv it over:
+ if [ ! -r $OLD ]; then
+ mv $NEW $OLD
+ elif [ "`cat $OLD | md5sum`" = "`cat $NEW | md5sum`" ]; then # toss the redundant copy
+ rm $NEW
+ fi
+ # Otherwise, we leave the .new copy for the admin to consider...
+}
+
+config etc/rc.d/rc.csvnd.new
+config etc/rc.d/rc.cgitd.new
diff --git a/doc/build-packages/slackware/slack-desc b/doc/build-packages/slackware/slack-desc
new file mode 100644
index 0000000..2d7c2b9
--- /dev/null
+++ b/doc/build-packages/slackware/slack-desc
@@ -0,0 +1,19 @@
+# HOW TO EDIT THIS FILE:
+# The "handy ruler" below makes it easier to edit a package description. Line
+# up the first '|' above the ':' following the base package name, and the '|' on
+# the right side marks the last column you can put a character in. You must make
+# exactly 11 lines for the formatting to be correct. It's also customary to
+# leave one space after the ':'.
+
+ |-----handy-ruler------------------------------------------------------|
+cscm: cscm (a SCM Configuration Daemon)
+cscm:
+cscm: cSvn is a SCM Configuration daemon for cSvn-ui and cGit-ui packages.
+cscm:
+cscm:
+cscm:
+cscm: Homepage: https://csvn.radix.pro
+cscm:
+cscm:
+cscm:
+cscm:
diff --git a/doc/cscmd.8.md b/doc/cscmd.8.md
new file mode 100644
index 0000000..b0c9423
--- /dev/null
+++ b/doc/cscmd.8.md
@@ -0,0 +1,112 @@
+
+# [cScm Daemon](https://csvn.radix.pro/cscm/trunk/doc/cscmd.8.md)
+
+**cscmd** &#8211; a daemon for monitoring changes in SCM configuration files.
+
+
+## Table of Contents
+
+* [Options](#options)
+* [Grammar](#grammar)
+* [See Also](#see-also)
+
+<br/>
+
+In order to not load the **cSvn-ui** and **cGit-ui** CGI scripts with unnecessary functionality and speed up
+its work, information about the list of repositories is transmitted to it in binary form through
+shared memory (see: */dev/shm/csvn.bcf*, */dev/shm/cgit.bcf*).
+
+Binary Config File (**BCF**) format is similar to the simplified implementation of the **COFF**
+and is described in the header file [*/usr/include/cscm/bcf.h*](https://csvn.radix.pro/cscm/trunk/cscm/bcf.h).
+
+On startup, the daemon parses the */etc/csvn-ui.rc* or */etc/csvn-ui.rc* configuration files
+(depends on **--scm=[svn|git]** option) and stores its binary forms in shared memory. In addition, the binary
+form is saved in the daemon's home directory, by default it is the file */var/lib/csvn/csvn.bcf* or
+*/var/lib/cgit/cgit.bcf*.
+
+**cscmd(8)** daemon rereads SCM configuration files upon arrival of the **-HUP** signal and, in addition,
+can be configured to monitor changes in the configuration files using the `--inotify` option. If the `--inotify`
+option is specified, then after editing the file */ets/csvn-ui.rc* or */ets/cgit-ui.rc* and saving it to disk,
+the **cscmd** daemon will reread file */etc/csvn-ui.rc* or */etc/cgit-ui.rc* as if it received the **-HUP** signal.
+
+The work of the **cscmd(8)** daemon's can be monitored by the log, which it leaves in the file
+*/var/log/csvnd.log* or the file */var/log/cgitd.log* (depends on **--scm=[svn|git]** option). There you can also
+observe messages about errors that are detected in */ets/csvn-ui.rc* or */ets/cgit-ui.rc* files, if any.
+
+To start the **cScm** daemon during system boot, can be used the start/stop script */etc/rc.d/rc.csvnd* or
+*/etc/rc.d/rc.cgitd*.
+
+
+## Options
+
+
+### -h, --help
+
+Display help information.
+
+
+### -v, --version
+
+Display the version of **cScm** daemon.
+
+
+### -d, --daemonize
+
+Run in background as a daemon.
+
+
+### -i, --inotify
+
+Notify about configuration changes. If this option is set then **cscmd(8)** daemon selects changes
+made in */etc/csvn-ui.rc* or */etc/cgit-ui.rc* configuration file and reread configuration when changes
+is done. Without this option rereading configuration file can be done by sending **-HUP** to the **cscmd(8)**
+process.
+
+
+### -b, --bcf=<BCF_FILE>
+
+Binary config file (depends on **--scm=[svn|git]** option). Default: */var/lib/csvn/csvn.bcf*.
+
+
+### -c, --config=<CONFIG_FILE>
+
+Config file (depends on **--scm=[svn|git]** option). Default: */etc/csvn-ui.rc*.
+
+
+### -l, --log=<LOG_FILE>
+
+Log file (depends on **--scm=[svn|git]** option). Default: */var/log/csvnd.log*.
+
+
+### -p, --pid=<PID_FILE>
+
+Log file (depends on **--scm=[svn|git]** option). Default: */var/run/csvnd.pid*.
+
+
+### -s, --scm=[svn|git]
+
+SCM engine name: **svn** or **git**. Default: **svn**.
+
+
+### -t, --test
+Test the config file and exit.
+
+
+## Grammar
+
+The grammar of the language describing the **cScm** congfiguration is simple
+([**parse.y**](https://csvn.radix.pro/cscm/trunk/cscmd/parse.y)). File */etc/csvn-ui.rc*
+allows you to set variable values and create repository lists as named structures.
+
+Any variables can be defined in the */etc/csvn-ui.rc* file, but only variables with
+reserved names will be used by the **cSvn** or **cGit** CGI script.
+
+In addition, if a variable already defined at the global level is specified in the
+description of a repository, then within this repository this variable will have a local
+value. In other words, within the repository, global variables can be overridden.
+
+
+## See Also
+
+> [**README**](https://csvn.radix.pro/cscm/trunk/README.md)
+