From 12c7b1c5658602269da2f5b75835ec0f5fab8890 Mon Sep 17 00:00:00 2001 From: kx Date: Fri, 24 Mar 2023 02:53:04 +0300 Subject: Version 0.1.4 --- .gitignore | 35 ++ .svnignore | 35 ++ Makefile.am | 17 + README | 2 + README.md | 214 +++++++ acsite.m4 | 55 ++ auto-clean | 40 ++ bootstrap | 99 ++++ configure.ac | 262 +++++++++ cscm/bcf.h | 123 ++++ cscmd/Makefile.am | 63 +++ cscmd/README.in | 5 + cscmd/bconf.c | 477 ++++++++++++++++ cscmd/bconf.h | 25 + cscmd/cscmd.8.in | 66 +++ cscmd/daemon.c | 39 ++ cscmd/daemon.h | 7 + cscmd/error.c | 92 +++ cscmd/error.h | 25 + cscmd/lex.c | 815 +++++++++++++++++++++++++++ cscmd/lex.h | 26 + cscmd/logrotate.in | 8 + cscmd/main.c | 737 ++++++++++++++++++++++++ cscmd/main.h | 21 + cscmd/msglog.c | 70 +++ cscmd/msglog.h | 98 ++++ cscmd/parse.y | 107 ++++ cscmd/rc.cscmd.in | 96 ++++ cscmd/symtab.c | 471 ++++++++++++++++ cscmd/symtab.h | 67 +++ cscmd/utf8ing.c | 121 ++++ cscmd/utf8ing.h | 22 + cscmd/xalloc.c | 36 ++ cscmd/xalloc.h | 19 + defs.h | 24 + doc/build-packages/archlinux/PKGBUILD | 49 ++ doc/build-packages/archlinux/README | 32 ++ doc/build-packages/rpms/README | 26 + doc/build-packages/rpms/cgitd.service | 13 + doc/build-packages/rpms/cscm.spec | 57 ++ doc/build-packages/rpms/csvnd.service | 13 + doc/build-packages/slackware/README | 6 + doc/build-packages/slackware/cscm.SlackBuild | 92 +++ doc/build-packages/slackware/cscm.info | 10 + doc/build-packages/slackware/doinst.sh | 15 + doc/build-packages/slackware/slack-desc | 19 + doc/cscmd.8.md | 112 ++++ 47 files changed, 4863 insertions(+) create mode 100644 .gitignore create mode 100644 .svnignore create mode 100644 Makefile.am create mode 100644 README create mode 100644 README.md create mode 100644 acsite.m4 create mode 100755 auto-clean create mode 100755 bootstrap create mode 100644 configure.ac create mode 100644 cscm/bcf.h create mode 100644 cscmd/Makefile.am create mode 100644 cscmd/README.in create mode 100644 cscmd/bconf.c create mode 100644 cscmd/bconf.h create mode 100644 cscmd/cscmd.8.in create mode 100644 cscmd/daemon.c create mode 100644 cscmd/daemon.h create mode 100644 cscmd/error.c create mode 100644 cscmd/error.h create mode 100644 cscmd/lex.c create mode 100644 cscmd/lex.h create mode 100644 cscmd/logrotate.in create mode 100644 cscmd/main.c create mode 100644 cscmd/main.h create mode 100644 cscmd/msglog.c create mode 100644 cscmd/msglog.h create mode 100644 cscmd/parse.y create mode 100644 cscmd/rc.cscmd.in create mode 100644 cscmd/symtab.c create mode 100644 cscmd/symtab.h create mode 100644 cscmd/utf8ing.c create mode 100644 cscmd/utf8ing.h create mode 100644 cscmd/xalloc.c create mode 100644 cscmd/xalloc.h create mode 100644 defs.h create mode 100644 doc/build-packages/archlinux/PKGBUILD create mode 100644 doc/build-packages/archlinux/README create mode 100644 doc/build-packages/rpms/README create mode 100644 doc/build-packages/rpms/cgitd.service create mode 100644 doc/build-packages/rpms/cscm.spec create mode 100644 doc/build-packages/rpms/csvnd.service create mode 100644 doc/build-packages/slackware/README create mode 100644 doc/build-packages/slackware/cscm.SlackBuild create mode 100644 doc/build-packages/slackware/cscm.info create mode 100644 doc/build-packages/slackware/doinst.sh create mode 100644 doc/build-packages/slackware/slack-desc create mode 100644 doc/cscmd.8.md 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 – 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),  +> [**csvn-ui.rc(5)**](https://csvn.radix.pro/csvn-ui/trunk/doc/csvn-ui.rc.5.md),  +> [**cgit-ui.rc(5)**](https://csvn.radix.pro/cgit-ui/trunk/doc/cgit-ui.rc.5.md). + + +## Copyright and License + +© Andrey V. Kosteltsev, 2019 – 2022.
+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() +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 +#endif + +#include +#include +#include +#include +#include +#include +#include /* chmod(2) */ +#include +#include +#include +#include +#include /* strdup(3) */ +#include /* basename(3) */ +#include /* tolower(3) */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +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 + + +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\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\fR +Config file (depends on \fB\-\-scm\fR option). Default: \fI@sysconfdir@/csvn-ui.rc\fR. + +.TP +\fB-l\fR,\fB--log\fR=\fB\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\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 +#endif + +#include +#include +#include + +#include + +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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + + +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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PCRE2_CODE_UNIT_WIDTH 32 +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + + +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  ,... 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 +#endif + +#include +#include +#include +#include +#include +#include +#include /* chmod(2) */ +#include +#include +#include /* strdup(3) */ +#include /* basename(3) */ +#include /* tolower(3) */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +#include +#if !defined SIGCHLD && defined SIGCLD +# define SIGCHLD SIGCLD +#endif + +#define _GNU_SOURCE +#include + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************* + 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= Binary config file. Default: %s.\n", BCF_FILE ); + fprintf( stdout, " -c,--config= Config file. Default: %s.\n", CONFIG_FILE ); + fprintf( stdout, " -l,--log= Log file. Default: %s.\n", LOG_FILE ); + fprintf( stdout, " -p,--pid= PID file. Default: %s.\n", PID_FILE ); + fprintf( stdout, " -s,--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 +#endif + +#include +#include +#include +#include +#include +#include + +#include + +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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +%} + + +%union +{ + SYMBOL *sym; +} + +%token VARIABLE 501 SECTION 502 REPO 503 +%token 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 +#endif + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + + +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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +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 +#endif + +#include +#include +#include +#include + +#include + +#include +#include + + +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 +# +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** – a daemon for monitoring changes in SCM configuration files. + + +## Table of Contents + +* [Options](#options) +* [Grammar](#grammar) +* [See Also](#see-also) + +
+ +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= + +Binary config file (depends on **--scm=[svn|git]** option). Default: */var/lib/csvn/csvn.bcf*. + + +### -c, --config= + +Config file (depends on **--scm=[svn|git]** option). Default: */etc/csvn-ui.rc*. + + +### -l, --log= + +Log file (depends on **--scm=[svn|git]** option). Default: */var/log/csvnd.log*. + + +### -p, --pid= + +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) + -- cgit v1.2.3