diff options
author | Laurent Bercot <ska-skaware@skarnet.org> | 2014-12-05 22:26:11 +0000 |
---|---|---|
committer | Laurent Bercot <ska-skaware@skarnet.org> | 2014-12-05 22:26:11 +0000 |
commit | 90b12bd71bb9fc79a4640b9112c13ef529d0196a (patch) | |
tree | 523b3f4ee2969e7a729bab2ba749c4b924ae62af | |
download | s6-90b12bd71bb9fc79a4640b9112c13ef529d0196a.tar.xz |
Initial commit
157 files changed, 11417 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c6415e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +*.a +*.lo +*.so +*.so.* @@ -0,0 +1,17 @@ +Main author: + Laurent Bercot <ska-skaware@skarnet.org> + +Contributors: + Paul Jarc <prj@dogmap.org> + Gerrit Pape <pape@smarden.org> + Clemens Fischer <ino-waiting@gmx.net> + Stefan Karrman <sk@mathematik.uni-ulm.de> + Jean Marot <marot@quatramaran.ens.fr> + +Thanks to: + Dan J. Bernstein <djb@cr.yp.to> + Wayne Marshall <wcm@b0llix.net> + David Madore <david@madore.org> + Nicolas George <cigaes@salle-s.org> + Frans Haarman <franshaarman@gmail.com> + Vallo Kallaste <kalts@estpak.ee> @@ -0,0 +1,13 @@ +Copyright (c) 2011-2014 Laurent Bercot <ska-skaware@skarnet.org> + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. @@ -0,0 +1,138 @@ +Build Instructions +------------------ + +* Requirements + ------------ + + - A POSIX-compliant C development environment + - GNU make version 3.81 or later + - skalibs version 2.0.0.0 or later: http://skarnet.org/software/skalibs/ + - execline version 2.0.0.0 or later: http://skarnet.org/software/execline/ + + This software will run on any operating system that implements +POSIX.1-2008, available at: + http://pubs.opengroup.org/onlinepubs/9699919799/ + + +* Standard usage + -------------- + + ./configure && make && sudo make install + + will work for most users. + It will install the binaries in /bin and the static libraries in +/usr/lib/s6. + + You can strip the binaries and libraries of their extra symbols via +"make strip" before the "make install" phase. It will shave a few bytes +off them. + + +* Customization + ------------- + + You can customize paths via flags given to configure. + See ./configure --help for a list of all available configure options. + + +* Environment variables + --------------------- + + Controlling a build process via environment variables is a big and +dangerous hammer. You should try and pass flags to configure instead; +nevertheless, the standard environment variables are recognized. + + The value of the CROSS_COMPILE environment variable will prefix the +building tools' names. The --enable-cross option is preferred, see +"Cross-compilation" below. + + If the CC environment variable is set, its value will override compiler +detection by configure. + + The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags +auto-detected by configure. To entirely override the flags set by +configure, use make -e. + + The value of LDLIBS will be appended by make to command lines that link +an executable, even without the -e option. + + The Makefile supports the DESTDIR convention for staging. + + +* Shared libraries + ---------------- + + Software from skarnet.org is small enough that shared libraries are +generally not worth using. Static linking is simpler and incurs less +runtime overhead and less points of failure: so by default, shared +libraries are not built and binaries are linked against the static +versions of the skarnet.org libraries. Nevertheless, you can: + * build shared libraries: --enable-shared + * link binaries against shared libraries: --disable-allstatic + + +* Static binaries + --------------- + + By default, binaries are linked against static versions of all the +libraries they depend on, except for the libc. You can enforce +linking against the static libc with --enable-static-libc. + + (If you are using a GNU/Linux system, be aware that the GNU libc +behaves badly with static linking and produces huge executables, +which is why it is not the default. Other libcs are better suited +to static linking, for instance musl: http://musl-libc.org/) + + s6 includes software running at a very low level, including a +program that may run as process 1: to avoid run-time dependencies, +it is recommended that you link the executables statically if you +have a suitable libc. + + +* Cross-compilation + ----------------- + + skarnet.org packages centralize all the difficulty of +cross-compilation in one place: skalibs. Once you have +cross-compiled skalibs, the rest is easy. + + Use the --enable-cross=PREFIX option to configure, or simply +--enable-cross if your default toolchain is a cross-compiling +toolchain. And make sure to use the correct version of skalibs +for your target, and the correct sysdeps directory, making use +of the --with-include, --with-lib, --with-dynlib and --with-sysdeps +options as necessary. + + +* The slashpackage convention + --------------------------- + + The slashpackage convention (http://cr.yp.to/slashpackage.html) +is a package installation scheme that provides a few guarantees +over other conventions such as the FHS, for instance fixed +absolute pathnames. skarnet.org packages support it: use the +--enable-slashpackage option to configure, or +--enable-slashpackage=DIR for a prefixed DIR/package tree. +This option will activate slashpackage support during the build +and set slashpackage-compatible installation directories. +Other options setting individual installation directories will be +ignored. + + When using slashpackage, two additional Makefile targets are +available after "make install": + - "make update" changes the default version of the software to the +freshly installed one. (This is useful when you have several installed +versions of the same software, which slashpackage supports.) + - "make -L global-links" adds links from /command and /library.so to the +default version of the binaries and shared libraries. The "-L" option to +make is necessary because targets are symbolic links, and the default make +behaviour is to check the pointed file's timestamp and not the symlink's +timestamp. + + +* Out-of-tree builds + ------------------ + + skarnet.org packages do not support out-of-tree builds. They +are small, so it does not cost much to duplicate the entire +source tree if parallel builds are needed. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7d8901c --- /dev/null +++ b/Makefile @@ -0,0 +1,121 @@ +# +# This Makefile requires GNU make. +# +# Do not make changes here. +# Use the included .mak files. +# + +it: all + +CC = $(error Please use ./configure first) + +include package/targets.mak +include package/deps.mak +-include config.mak + +version_m := $(basename $(version)) +version_M := $(basename $(version_m)) +version_l := $(basename $(version_M)) +CPPFLAGS_ALL := -iquote src/include-local -Isrc/include $(CPPFLAGS) +CFLAGS_ALL := $(CFLAGS) -pipe -Wall +CFLAGS_SHARED := -fPIC +LDFLAGS_ALL := $(LDFLAGS) +LDFLAGS_SHARED := -shared +LDLIBS_ALL := $(LDLIBS) +REALCC = $(CROSS_COMPILE)$(CC) +AR := $(CROSS_COMPILE)ar +RANLIB := $(CROSS_COMPILE)ranlib +STRIP := $(CROSS_COMPILE)strip +INSTALL := ./tools/install.sh + +ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS) $(SBIN_TARGETS) +ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) +ALL_INCLUDES := $(wildcard src/include/$(package)/*.h) + +all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES) + +clean: + @exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo) + +distclean: clean + @exec rm -f config.mak src/include/${package}/config.h + +tgz: distclean + @. package/info && \ + rm -rf /tmp/$$package-$$version && \ + cp -a . /tmp/$$package-$$version && \ + cd /tmp && \ + tar -zpcv --owner=0 --group=0 --numeric-owner --exclude=.git* -f /tmp/$$package-$$version.tar.gz $$package-$$version && \ + exec rm -rf /tmp/$$package-$$version + +strip: $(ALL_LIBS) $(ALL_BINS) +ifneq ($(strip $(ALL_LIBS)),) + exec ${STRIP} -x -R .note -R .comment -R .note.GNU-stack $(ALL_LIBS) +endif +ifneq ($(strip $(ALL_BINS)),) + exec ${STRIP} -R .note -R .comment -R .note.GNU-stack $(ALL_BINS) +endif + +install: install-dynlib install-libexec install-bin install-sbin install-lib install-include +install-dynlib: $(SHARED_LIBS:lib%.so=$(DESTDIR)$(dynlibdir)/lib%.so) +install-libexec: $(LIBEXEC_TARGETS:%=$(DESTDIR)$(libexecdir)/%) +install-bin: $(BIN_TARGETS:%=$(DESTDIR)$(bindir)/%) +install-sbin: $(SBIN_TARGETS:%=$(DESTDIR)$(sbindir)/%) +install-lib: $(STATIC_LIBS:lib%.a=$(DESTDIR)$(libdir)/lib%.a) +install-include: $(ALL_INCLUDES:src/include/$(package)/%.h=$(DESTDIR)$(includedir)/$(package)/%.h) + +ifneq ($(exthome),) + +update: + exec $(INSTALL) -l $(notdir $(home)) $(DESTDIR)$(exthome) + +global-links: $(DESTDIR)$(exthome) $(SHARED_LIBS:lib%.so=$(DESTDIR)$(sproot)/library.so/lib%.so) $(BIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%) $(SBIN_TARGETS:%=$(DESTDIR)$(sproot)/command/%) + +$(DESTDIR)$(sproot)/command/%: $(DESTDIR)$(home)/command/% + exec $(INSTALL) -D -l ..$(exthome)/command/$(<F) $@ + +$(DESTDIR)$(sproot)/library.so/lib%.so: $(DESTDIR)$(dynlibdir)/lib%.so + exec $(INSTALL) -D -l ..$(exthome)/library.so/$(<F) $@ + +.PHONY: update global-links + +endif + +$(DESTDIR)$(dynlibdir)/lib%.so: lib%.so + $(INSTALL) -D -m 755 $< $@.$(version) && \ + $(INSTALL) -l $<.$(version) $@.$(version_m) && \ + $(INSTALL) -l $<.$(version_m) $@.$(version_M) && \ + $(INSTALL) -l $<.$(version_M) $@.$(version_l) && \ + exec $(INSTALL) -l $<.$(version_l) $@ + +$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/% $(DESTDIR)$(sbindir)/%: % package/modes + exec $(INSTALL) -D -m 600 $< $@ + grep -- ^$(@F) < package/modes | { read name mode owner && \ + if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \ + chmod $$mode $@ ; } + +$(DESTDIR)$(libdir)/lib%.a: lib%.a + $(INSTALL) -D -m 644 $< $@ + +$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h + exec $(INSTALL) -D -m 644 $< $@ + +%.o: %.c + exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $< + +%.lo: %.c + exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $< + +$(ALL_BINS): + exec $(REALCC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(LDLIBS_ALL) + +lib%.a: + exec $(AR) rc $@ $^ + exec $(RANLIB) $@ + +lib%.so: + exec $(REALCC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$@.$(version_l) $^ + +.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-sbin install-lib install-include + +.DELETE_ON_ERROR: @@ -0,0 +1,23 @@ +s6 - a process supervision suite +-------------------------------- + + s6 is a suite of programs designed to allow process +supervision and management. It can also be used as the +foundation for a complete init system. + + See http://skarnet.org/software/s6/ for details. + + +* Installation + ------------ + + See the INSTALL file. + + +* Contact information + ------------------- + + Laurent Bercot <ska-skaware at skarnet.org> + + Please use the <skaware at list.skarnet.org> mailing-list for +questions about s6. diff --git a/README.macosx b/README.macosx new file mode 100644 index 0000000..d71a096 --- /dev/null +++ b/README.macosx @@ -0,0 +1,4 @@ + + This package will compile and run on Darwin (MacOS X), but the building of +shared libraries is not supported. + Make sure you use the --disable-shared option to configure. diff --git a/README.solaris b/README.solaris new file mode 100644 index 0000000..91a5b26 --- /dev/null +++ b/README.solaris @@ -0,0 +1,12 @@ + + This package assumes the existence of a POSIX shell in /bin/sh. + On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide +a POSIX shell in /usr/xpg4/bin/sh. + + To compile this package on Solaris, you will need to run + + ./patch-for-solaris + + before you run ./configure. This script will change the #! invocation +of the configure script and various tools so that a POSIX shell is used +for the compilation process. diff --git a/configure b/configure new file mode 100755 index 0000000..60751c8 --- /dev/null +++ b/configure @@ -0,0 +1,385 @@ +#!/bin/sh + +usage () { +cat <<EOF +Usage: $0 [OPTION]... [VAR=VALUE]... [TARGET] + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +System types: + --target=TARGET configure to run on target TARGET [detected] + --host=TARGET same as --target + +Installation directories: + --prefix=PREFIX main installation prefix [/] + --exec-prefix=EPREFIX installation prefix for executable files [PREFIX] + +Fine tuning of the installation directories: + --dynlibdir=DIR shared library files [PREFIX/lib] + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR admin executables [EPREFIX/sbin] + --libexecdir=DIR package-scoped executables [EPREFIX/libexec] + --libdir=DIR static library files [PREFIX/lib] + --includedir=DIR include files for the C compiler [PREFIX/include] + +Dependencies: + --with-sysdeps=DIR use sysdeps in DIR [/usr/lib/skalibs/sysdeps] + --with-include=DIR add DIR to the list of searched directories for headers + --with-lib=DIR add DIR to the list of searched directories for static libraries + --with-dynlib=DIR add DIR to the list of searched directories for shared libraries + +Optional features: + --enable-shared build shared libraries [disabled] + --disable-static do not build static libraries [enabled] + --disable-allstatic do not prefer linking against static libraries [enabled] + --enable-static-libc make entirely static binaries [disabled] + --enable-slashpackage[=ROOT] assume /package installation at ROOT [disabled] + --enable-cross=PREFIX prefix toolchain executable names with PREFIX [none] + +EOF +exit 0 +} + + +# Helper functions + +# If your system does not have printf, you can comment this, but it is +# generally not a good idea to use echo. +# See http://www.etalabs.net/sh_tricks.html +echo () { + IFS=" " + printf %s\\n "$*" +} + +quote () { + tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; } +$1 +EOF + echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g" +} + +fail () { + echo "$*" + exit 1 +} + +fnmatch () { + eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac" +} + +cmdexists () { + type "$1" >/dev/null 2>&1 +} + +trycc () { + test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO=$1 +} + +stripdir () { + while eval "fnmatch '*/' \"\${$1}\"" ; do + eval "$1=\${$1%/}" + done +} + +tryflag () { + echo "checking whether compiler accepts $2 ..." + echo "typedef int x;" > "$tmpc" + if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then + echo " ... yes" + eval "$1=\"\${$1} \$2\"" + eval "$1=\${$1# }" + return 0 + else + echo " ... no" + return 1 + fi +} + +tryldflag () { + echo "checking whether linker accepts $2 ..." + echo "typedef int x;" > "$tmpc" + if $CC_AUTO $CFLAGS_AUTO $LDFLAGS_AUTO -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then + echo " ... yes" + eval "$1=\"\${$1} \$2\"" + eval "$1=\${$1# }" + return 0 + else + echo " ... no" + return 1 + fi +} + + +# Actual script + +. package/info + +CC_AUTO="$CC" +CFLAGS_AUTO="$CFLAGS" +CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=600 $CPPFLAGS" +LDFLAGS_AUTO="$LDFLAGS" +LDFLAGS_NOSHARED= +prefix= +exec_prefix='$prefix' +dynlibdir='$prefix/lib' +libexecdir='$exec_prefix/libexec' +bindir='$exec_prefix/bin' +sbindir='$exec_prefix/sbin' +libdir='$prefix/usr/lib/'$package +includedir='$prefix/usr/include' +sysdeps='$prefix/usr/lib/skalibs/sysdeps' +manualsysdeps=false +shared=false +static=true +slashpackage=false +sproot= +home= +exthome= +allstatic=true +evenmorestatic=false +addincpath='' +addlibspath='' +addlibdpath='' +vpaths='' +vpathd='' +cross="$CROSS_COMPILE" + +for arg ; do + case "$arg" in + --help) usage ;; + --prefix=*) prefix=${arg#*=} ;; + --exec-prefix=*) exec_prefix=${arg#*=} ;; + --dynlibdir=*) dynlibdir=${arg#*=} ;; + --libexecdir=*) libexecdir=${arg#*=} ;; + --bindir=*) bindir=${arg#*=} ;; + --sbindir=*) sbindir=${arg#*=} ;; + --libdir=*) libdir=${arg#*=} ;; + --includedir=*) includedir=${arg#*=} ;; + --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;; + --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;; + --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;; + --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;; + --enable-shared|--enable-shared=yes) shared=true ;; + --disable-shared|--enable-shared=no) shared=false ;; + --enable-static|--enable-static=yes) static=true ;; + --disable-static|--enable-static=no) static=false ;; + --enable-allstatic|--enable-allstatic=yes) allstatic=true ;; + --disable-allstatic|--enable-allstatic=no) allstatic=false ;; + --enable-static-libc|--enable-static-libc=yes) evenmorestatic=true ;; + --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;; + --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;; + --enable-slashpackage) sproot= ; slashpackage=true ;; + --disable-slashpackage) sproot= ; slashpackage=false ;; + --enable-cross=*) cross=${arg#*=} ;; + --enable-cross) cross= ;; + --disable-cross) cross= ;; + --enable-*|--disable-*|--with-*|--without-*|--*dir=*|--build=*) ;; + --host=*|--target=*) target=${arg#*=} ;; + -* ) echo "$0: unknown option $arg" ;; + *=*) ;; + *) target=$arg ;; + esac +done + +for i in prefix exec_prefix dynlibdir libexecdir bindir sbindir libdir includedir linkdynlibdir linkbindir linksbindir sysdeps sproot skalibs ; do + eval tmp=\${$i} + eval $i=$tmp + stripdir $i +done + +# Get usable temp filenames +i=0 +set -C +while : ; do + i=$(($i+1)) + tmpc="./tmp-configure-$$-$PPID-$i.c" + tmpe="./tmp-configure-$$-$PPID-$i.tmp" + 2>|/dev/null > "$tmpc" && break + 2>|/dev/null > "$tmpe" && break + test "$i" -gt 50 && fail "$0: cannot create temporary files" +done +set +C +trap 'rm -f "$tmpc" "$tmpe"' EXIT ABRT INT QUIT TERM HUP + +# Set slashpackage values +if $slashpackage ; then + home=${sproot}/package/${category}/${package}-${version} + exthome=${sproot}/package/${category}/${package} + if $manualsysdeps ; then + : + else + sysdeps=${sproot}/package/prog/skalibs/sysdeps + fi + binprefix=${home}/command + extbinprefix=${exthome}/command + dynlibdir=${home}/library.so + libexecdir=$binprefix + bindir=$binprefix + sbindir=$binprefix + libdir=${home}/library + includedir=${home}/include + while read dep ; do + addincpath="$addincpath -I${sproot}${dep}/include" + vpaths="$vpaths ${sproot}${dep}/library" + addlibspath="$addlibspath -L${sproot}${dep}/library" + if $allstatic ; then : ; else + vpathd="$vpathd ${sproot}${dep}/library.so" + addlibdpath="$addlibdpath -L${sproot}${dep}/library.so" + fi + done < package/deps-build +fi + +# Find a C compiler to use +echo "checking for C compiler..." +trycc ${cross}gcc +trycc ${cross}c99 +trycc ${cross}cc +test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; } +echo " ... $CC_AUTO" +echo "checking whether C compiler works... " +echo "typedef int x;" > "$tmpc" +if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO -c -o /dev/null "$tmpc" 2>"$tmpe" ; then + echo " ... yes" +else + echo " ... no. Compiler output follows:" + cat < "$tmpe" + exit 1 +fi + +echo "checking target system type..." +test -n "$target" || target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown +echo " ... $target" +if test ! -d $sysdeps || test ! -f $sysdeps/target ; then + echo "$0: error: $sysdeps is not a valid sysdeps directory" + exit 1 +fi +if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then + echo "$0: error: target $target does not match the contents of $sysdeps/target" + exit 1 +fi + +rt_lib=$(cat $sysdeps/rt.lib) +socket_lib=$(cat $sysdeps/socket.lib) +sysclock_lib=$(cat $sysdeps/sysclock.lib) +tainnow_lib=$(cat $sysdeps/tainnow.lib) +util_lib=$(cat $sysdeps/util.lib) + +tryflag CFLAGS_AUTO -std=c99 +tryflag CFLAGS_AUTO -fomit-frame-pointer +tryflag CFLAGS_AUTO -fno-exceptions +tryflag CFLAGS_AUTO -fno-unwind-tables +tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables +tryflag CFLAGS_AUTO -Wa,--noexecstack +tryflag CFLAGS_AUTO -fno-stack-protector +tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration +tryflag CPPFLAGS_AUTO -Werror=implicit-int +tryflag CPPFLAGS_AUTO -Werror=pointer-sign +tryflag CPPFLAGS_AUTO -Werror=pointer-arith +tryflag CPPFLAGS_AUTO -Wno-parentheses +tryflag CPPFLAGS_AUTO -Wno-uninitialized +tryflag CPPFLAGS_AUTO -Wno-missing-braces +tryflag CPPFLAGS_AUTO -Wno-unused-value +tryflag CPPFLAGS_AUTO -Wno-unused-but-set-variable +tryflag CPPFLAGS_AUTO -Wno-unknown-pragmas +tryflag CPPFLAGS_AUTO -Wno-pointer-to-int-cast + +if $evenmorestatic ; then + LDFLAGS_NOSHARED=-static +fi + +if $shared ; then + tryldflag LDFLAGS_AUTO -Wl,--hash-style=both +fi + +if test -z "$vpaths" ; then + while read dep ; do + base=$(basename $dep) ; + vpaths="$vpaths /usr/lib/$base" + addlibspath="$addlibspath -L/usr/lib/$base" + done < package/deps-build +fi + +CPPFLAGS_AUTO="$CPPFLAGS_AUTO $addincpath" +LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibspath" +$allstatic || LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibdpath" + +echo "creating config.mak..." +cmdline=$(quote "$0") +for i ; do cmdline="$cmdline $(quote "$i")" ; done +exec 3>&1 1>config.mak +cat << EOF +# This file was generated by: +# $cmdline +# Any changes made here will be lost if configure is re-run. + +target := $target +package := $package +prefix := $prefix +exec_prefix := $exec_prefix +dynlibdir := $dynlibdir +libexecdir := $libexecdir +bindir := $bindir +sbindir := $sbindir +libdir := $libdir +includedir := $includedir +sysdeps := $sysdeps +slashpackage := $slashpackage +sproot := $sproot +version := $version +home := $home +exthome := $exthome +RT_LIB := ${rt_lib} +SOCKET_LIB := ${socket_lib} +SYSCLOCK_LIB := ${sysclock_lib} +TAINNOW_LIB := ${tainnow_lib} +UTIL_LIB := ${util_lib} + +CC := $CC_AUTO +CFLAGS := $CFLAGS_AUTO +CPPFLAGS := $CPPFLAGS_AUTO +LDFLAGS := $LDFLAGS_AUTO +LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED +CROSS_COMPILE := $cross + +vpath lib%a$vpaths +EOF +if $allstatic ; then + echo ".LIBPATTERNS := lib%.a" + vpathd= +fi + echo "vpath lib%.so$vpathd" +echo +$static || echo "STATIC_LIBS :=" +$shared || echo "SHARED_LIBS :=" +exec 1>&3 3>&- +echo " ... done." + +echo "creating src/include/${package}/config.h..." +mkdir -p -m 0755 src/include/${package} +exec 3>&1 1> src/include/${package}/config.h +cat <<EOF +/* ISC license. */ + +/* Generated by: $cmdline */ + +#ifndef ${package_macro_name}_CONFIG_H +#define ${package_macro_name}_CONFIG_H + +#define ${package_macro_name}_VERSION "$version" +EOF +if $slashpackage ; then + echo "#define ${package_macro_name}_BINPREFIX \"$binprefix\"" + echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix\"" + echo "#define ${package_macro_name}_LIBEXECPREFIX \"$binprefix\"" +else + echo "#define ${package_macro_name}_BINPREFIX \"\"" + echo "#define ${package_macro_name}_EXTBINPREFIX \"\"" + echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir\"" +fi +echo +echo "#endif" +exec 1>&3 3>&- +echo " ... done." diff --git a/doc/fifodir.html b/doc/fifodir.html new file mode 100644 index 0000000..99805ed --- /dev/null +++ b/doc/fifodir.html @@ -0,0 +1,123 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: fifodirs</title> + <meta name="Description" content="s6: fifodirs" /> + <meta name="Keywords" content="s6 instant notification polling fifodir named pipe filesystem" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> Fifodirs </h1> + +<p> + A <em>fifodir</em> is a rendez-vous point between the <em>notifier</em> +of certain events and its <em>listeners</em>. It is implemented via a +directory in the filesystem. No data is stored; it is appropriate to +create fifodirs in a RAM filesystem. +</p> + +<h2> Manipulating fifodirs </h2> + +<h3> C API </h3> + +<ul> + <li> You can create fifodirs via the +<tt>ftrigw_fifodir_create()</tt> function in +<a href="libftrigw.html">libftrig</a>. </li> + <li> You can send an event to a fifodir via the +<tt>ftrigw_notify()</tt> function in +<a href="libftrigw.html">libftrig</a>. </li> + <li> You can clean up a fifodir via the +<tt>ftrigw_clean()</tt> function in +<a href="libftrigw.html">libftrig</a>. </li> + <li> You can destroy fifodirs via the +<tt>rm_rf()</tt> function in +<a href="http://skarnet.org/software/skalibs/doc/libstddjb/djbunix.html">libstddjb</a>. </li> +</ul> + +<h3> Unix API </h3> + +<ul> + <li> You can create fifodirs with the +<a href="s6-mkfifodir.html">s6-mkfifodir</a> command. </li> + <li> You can send an event to a fifodir with the +<a href="s6-ftrig-notify.html">s6-ftrig-notify</a> command. </li> + <li> You can clean up a fifodir with the +<a href="s6-cleanfifodir.html">s6-cleanfifodir</a> command. </li> + <li> You can destroy fifodirs with the <tt>rm -rf</tt> command. </li> +</ul> + +<h2> Internals and Unix permissions </h2> + +<ul> + <li> Notifiers and listeners agree on a fifodir. </li> + <li> The fifodir directory is created by the notifier. It must be writable +by listeners. </li> + <li> To subscribe, a listener atomically creates a named pipe (FIFO) in this +directory and listens to the reading end. This named pipe must be writable +by the notifier. </li> + <li> To send an event to listeners, the notifier writes the event byte to +all the named pipes in the directory. Credit for this idea goes to Stefan +Karrmann. </li> + <li> To unsubscribe, a listener unlinks his named pipe from the directory. </li> +</ul> + +<p> +Fifodirs are created by, so they always originally have the same uid and gid as, +their notifier. A notifier must be able to make his fifodir either publically +accessible (anyone can subscribe) or restricted (only a given group can +subscribe). +</p> + +<p> + A publically accessible fifodir must have rights 1733: +</p> + +<ul> + <li> Anyone can create a fifo in that fifodir </li> + <li> Only the notifier can see all the subscribers' fifos </li> + <li> A listener can only delete its own fifo </li> + <li> A notifier can delete any fifo for cleaning purposes </li> +</ul> + +<p> + A restricted fifodir must have the gid <em>g</em> of the group of allowed +listeners and have rights 3730. Unless the notifier is root, it +must be in the group of allowed listeners to be able to create +such a fifodir. +</p> + +<ul> + <li> Only members of <em>g</em> can create a fifo in that fifodir </li> + <li> Only the notifier can see all the subscribers' fifos </li> + <li> Fifos are always created with gid <em>g</em> </li> + <li> A listener can only delete its own fifo </li> + <li> A notifier can delete any fifo for cleaning purposes </li> +</ul> + +<p> + A named pipe in a fifodir must always belong to its listener and have +rights 0622: +</p> + +<ul> + <li> Only this listener can read on the fifo </li> + <li> Anyone who has reading rights on the fifodir (i.e. only the notifier) +can write to the fifo </li> +</ul> + +<p> + The <a href="libftrig.html">libftrig<a/> interface takes care of all +the subtleties. +</p> + +</body> +</html> diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..048af4f --- /dev/null +++ b/doc/index.html @@ -0,0 +1,291 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6 - skarnet's small supervision suite</title> + <meta name="Description" content="s6 - skarnet's small supervision suite" /> + <meta name="Keywords" content="s6 unix administration root pipe laurent bercot ska skarnet supervision djb" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> s6 </h1> + +<h2> What is it ? </h2> + +<p> + s6 is a small suite of programs for UNIX, designed to allow process supervision +(a.k.a service supervision), +in the line of <a href="http://cr.yp.to/daemontools.html">daemontools</a> +and <a href="http://smarden.org/runit/">runit</a>. +</p> + +<p> + The s6 documentation tries to be complete and self-contained; however, +if you have never heard of process supervision before, you might be +confused at first. See the <a href="#related">related resources</a> section +below for pointers to more resources, and earlier approaches to process +supervision that might help you understand the basics. +</p> + +<hr /> + +<ul> +<li> <a href="why.html">Why another supervision suite?</a> Isn't <a href="http://smarden.org/runit/">runit</a> good enough?</li> +<li> What is instant notification? What does the <a href="libftrig.html">libftrig</a> do exactly?</li> +<li> How to run a s6-svscan-based supervision tree <a href="s6-svscan-not-1.html">without replacing init</a> </li> +<li> How to <a href="s6-svscan-1.html">replace init</a> </li> +</ul> + +<hr /> + +<h2> Installation </h2> + +<h3> Requirements </h3> + +<ul> + <li> A POSIX-compliant system with a standard C development environment </li> + <li> GNU make, version 3.81 or later </li> + <li> <a href="http://skarnet.org/software/skalibs/">skalibs</a> version +2.0.0.0 or later </li> + <li> <a href="http://skarnet.org/software/execline/">execline</a> version +2.0.0.0 or later </li> +</ul> + +<h3> Licensing </h3> + +<p> + s6 is free software. It is available under the +<a href="http://opensource.org/licenses/ISC">ISC license</a>. +</p> + +<h3> Download </h3> + +<ul> + <li> The current released version of s6 is <a href="s6-2.0.0.0.tar.gz">2.0.0.0</a>. </li> + <li> Alternatively, you can checkout a copy of the s6 git repository: +<pre> git clone git://git.skarnet.org/s6 </pre> </li> +</ul> + +<h3> Compilation </h3> + +<ul> + <li> See the enclosed INSTALL file for installation details. </li> +</ul> + +<h3> Upgrade notes </h3> + +<ul> + <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between +the previous versions of execline and the current one. </li> +</ul> + +<hr /> + +<h2> Reference </h2> + +<h3> Commands </h3> + +<p> + All these commands exit 111 if they encounter a temporary error or +hardware error, and +100 if they encounter a permanent error - such as a misuse. Short-lived +commands exit 0 on success. +</p> + +<h4> Supervision system </h4> + +<p> + <a href="s6-svscan.html">s6-svscan</a> and <a href="s6-supervise.html">s6-supervise</a> +are the long-lived processes maintaining the supervision tree. Other programs are +a user interface to control those processes and monitor service states. +</p> + +<ul> +<li><a href="s6-svscan.html">The <tt>s6-svscan</tt> program</a></li> +<li><a href="s6-svscanctl.html">The <tt>s6-svscanctl</tt> program</a></li> +<li><a href="s6-supervise.html">The <tt>s6-supervise</tt> program</a></li> +<li><a href="s6-svc.html">The <tt>s6-svc</tt> program</a></li> +<li><a href="s6-svok.html">The <tt>s6-svok</tt> program</a></li> +<li><a href="s6-svstat.html">The <tt>s6-svstat</tt> program</a></li> +<li><a href="s6-svwait.html">The <tt>s6-svwait</tt> program</a></li> +</ul> + +<h4> Other daemontools-like utilities </h4> + +<p> + These programs are a rewrite of the corresponding utilities from +<a href="http://cr.yp.to/daemontools.html">daemontools</a>, with +a few extras. The +<a href="s6-log.html">s6-log</a> program is important in itself: it's +a powerful, scriptable, general-purpose filtering and logging tool +that can be used to entirely replace syslogd. It has many more +features than its <a href="http://cr.yp.to/daemontools/multilog.html">multilog</a> +counterpart. +</p> + +<ul> +<li><a href="s6-envdir.html">The <tt>s6-envdir</tt> program</a></li> +<li><a href="s6-envuidgid.html">The <tt>s6-envuidgid</tt> program</a></li> +<li><a href="s6-fghack.html">The <tt>s6-fghack</tt> program</a></li> +<li><a href="s6-log.html">The <tt>s6-log</tt> program</a></li> +<li><a href="s6-setlock.html">The <tt>s6-setlock</tt> program</a></li> +<li><a href="s6-setsid.html">The <tt>s6-setsid</tt> program</a></li> +<li><a href="s6-setuidgid.html">The <tt>s6-setuidgid</tt> program</a></li> +<li><a href="s6-softlimit.html">The <tt>s6-softlimit</tt> program</a></li> +<li><a href="s6-tai64n.html">The <tt>s6-tai64n</tt> program</a></li> +<li><a href="s6-tai64nlocal.html">The <tt>s6-tai64nlocal</tt> program</a></li> +<li><a href="ucspilogd.html">The <tt>ucspilogd</tt> program</a></li> +<li><a href="s6-notifywhenup.html">The <tt>s6-notifywhenup</tt> program</a> (NEW in 2.0.0.0)</li> +</ul> + +<h4> Fifodir management, notification and subscription </h4> + +<p> +These programs are a clean rewrite of the obsolete "pipe-tools" package; they +are now based on a properly designed notification library. +They provide a command-line interface to +<a href="libftrig.html#notification">inter-process notification and +synchronization</a>. +</p> + +<ul> +<li><a href="s6-mkfifodir.html">The <tt>s6-mkfifodir</tt> program</a></li> +<li><a href="s6-cleanfifodir.html">The <tt>s6-cleanfifodir</tt> program</a></li> +</ul> +<p> +</p> +<ul> +<li><a href="s6-ftrig-notify.html">The <tt>s6-ftrig-notify</tt> program</a></li> +</ul> +<p> +</p> +<ul> +<li><a href="s6-ftrig-wait.html">The <tt>s6-ftrig-wait</tt> program</a></li> +<li><a href="s6-ftrig-listen1.html">The <tt>s6-ftrig-listen1</tt> program</a></li> +<li><a href="s6-ftrig-listen.html">The <tt>s6-ftrig-listen</tt> program</a></li> +</ul> + +<h4> Internal commands </h4> + +<ul> +<li><a href="s6-ftrigrd.html">The <tt>s6-ftrigrd</tt> internal program</a></li> +<li><a href="libs6lock/s6-lockd.html">The <tt>s6lockd</tt> internal program</a></li> +<li><a href="libs6lock/s6lockd-helper.html">The <tt>s6lockd-helper</tt> internal program</a></li> +</ul> + + +<h3> Libraries </h3> + +<ul> +<li><a href="libftrigw.html">The <tt>ftrigw</tt> library interface</a></li> +<li><a href="libftrigr.html">The <tt>ftrigr</tt> library interface</a></li> +<li><a href="libs6lock/">The <tt>s6lock</tt> library interface</a></li> +</ul> + +<h3> Definitions </h3> + +<ul> +<li> What is a <a href="fifodir.html">fifodir</a></li> +<li> What is a <a href="servicedir.html">service directory</a></li> +<li> What is a <a href="scandir.html">scan directory</a></li> +<li> Why are the <a href="libftrig.html">libftrigw and libftrigr</a> needed </li> +</ul> + +<hr /> + +<a name="related"> +<h2> Related resources </h2> +</a> + +<h3> s6 discussion </h3> + +<ul> + <li> <tt>s6</tt> is discussed on the +<a href="http://www.skarnet.org/lists.html#supervision">supervision</a> mailing-list. </li> + <li> <tt>s6</tt> has a +<a href="http://freecode.com/projects/s6">freecode page</a>. + </li> +</ul> + +<h3> Similar work </h3> + +<ul> + <li> <a href="http://cr.yp.to/daemontools.html">daemontools</a>, the pioneering +process supervision software suite. </li> + <li> <a href="http://untroubled.org/daemontools-encore/">daemontools-encore</a>, +a derived work from daemontools with enhancements. (Note that although s6 follows +the same naming scheme, the same general design, and many of the same architecture +choices as daemontools, it is still original work, sharing no code at all with +daemontools.) </li> + <li> <a href="http://smarden.org/runit/">runit</a>, a slightly different +approach to process supervision, with the same goals. </li> + <li> <a href="http://b0llix.net/perp/">perp</a>, yet another slightly different +approach to process supervision, also with the same goals. </li> +</ul> + +<h3> Other init systems </h3> + +<ul> + <li> Felix von Leitner's <a href="http://www.fefe.de/minit/">minit</a> is an +init system for Linux, with process supervision capabilities. </li> + <li> <a href="http://freshmeat.net/projects/sysvinit/">sysvinit</a> is the +traditional init system for Linux. </li> + <li> <a href="http://upstart.ubuntu.com/">Upstart</a> is a well-known init system +for Linux, with complete service management, that comes with the Ubuntu +distribution. It includes a coffee machine and the kitchen sink.</li> + <li> Because Upstart wasn't bloated or unreliable enough, someone came up with +<a href="http://freedesktop.org/wiki/Software/systemd/">systemd</a>, yet +another Linux init system, so contrary to all principles of good engineering +it's just scary. </li> + <li> The various BSD flavors have their own style of +<a href="http://www.freebsd.org/doc/handbook/boot-init.html">init</a>. </li> + <li> MacOS X has its own init spaghetti monster called +<a href="http://en.wikipedia.org/wiki/Launchd">launchd</a>. </li> +</ul> + +<p> +All-in-one init systems generally feel complex and convoluted, and when most +people find out about the process supervision approach to init systems, they +usually find it much simpler. +<a href="s6-svscan-1.html#stages">There is a good reason for this.</a> +</p> + + +<h2> Credits </h2> + + s6 is one of the (late...) results of a long design discussion that +happened in 2002-2003 on the +<a href="http://www.skarnet.org/lists.html#supervision">supervision list</a> +and the <a href="http://cr.yp.to/lists.html#log">log list</a>. +The main contributors to the thread were +<ul> + <li> <a href="http://dogmap.org/">Paul Jarc</a> </li> + <li> <a href="http://smarden.org/pape/">Gerrit Pape</a> </li> + <li> <a href="http://ino-waiting.gmxhome.de/">Clemens Fischer</a> </li> + <li> <a href="http://www.mathematik.uni-ulm.de/m5/sk/">Stefan Karrman</a> </li> + <li> and me. </li> +</ul> + +<h2> Miscellaneous </h2> + +<h3> Why "s6" ? </h3> + +<p> +<strong>s</strong>karnet.org's <strong>s</strong>mall and <strong>s</strong>ecure +<strong>s</strong>upervision <strong>s</strong>oftware <strong>s</strong>uite. +</p> + +<p> + Also, s6 is a nice command name prefix to have: it identifies the origin of the +software, and it's short. Expect more use of s6- in future skarnet.org software +releases. And please avoid using that prefix for your own projects. +</p> + +</body> +</html> diff --git a/doc/libftrig.html b/doc/libftrig.html new file mode 100644 index 0000000..da4c25b --- /dev/null +++ b/doc/libftrig.html @@ -0,0 +1,184 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: libftrig</title> + <meta name="Description" content="s6 libftrig" /> + <meta name="Keywords" content="s6 libftrig" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> libftrig </h1> + +<p> +<t>libftrig</t> is a portable Unix C programming interface allowing a +process (the <em>subscriber</em> or <em>listener</em>) to be instantly +notified when another process (the <em>notifier</em> or <em>writer</em>) +signals an event. +</p> + +<a name="notification"> +<h2> What is notification ? </h2> +</a> + +<h3> Notification vs. polling </h3> + +<p> + Process A is <em>notified</em> of an event E when it gets a instant +notice that event E has happened; the notice may disrupt A's execution +flow. Notification is the opposite of <em>polling</em>, where A has to +periodically (every T milliseconds) check whether E happened and has no +other way to be informed of it. +</p> + +<p> + Polling is generally considered bad practice - and is inferior to +notification in practically every case - for three reasons: +</p> + +<ul> + <li> Reaction time. When event E happens, process A does not know it +instantly. It will only learn of E, and be able to react to it, when +it explicitly checks for E; and if E happened right after A performed +the check, this can take as long as T milliseconds (the <em>polling +period</em>). Polling processes have reaction delays due to the polling +periods. </li> + <li> Resource consumption. Even if <em>no</em> event ever happens, process A +will still wake up needlessly every T milliseconds. This might not seem like +a problem, but it is a serious one in energy-critical environments. Polling +processes use more CPU time than is necessary and are not energy-friendly. </li> + <li> Conflict between the two above reasons. The longer the polling period, +the more energy-friendly the process, but the longer the reaction time. The +shorter the polling period, the shorter the reaction time, but the more +resource-consuming the process. A delicate balance has to be found, and +acceptable behaviour is different in every case, so there's no general rule +of optimization. </li> +</ul> + +<p> + Notification, on the other hand, is generally optimal: reaction time is +zero, and resource consumption is minimal - a process can sleep as soon as +it's not handling an event, and only wake up when needed. +</p> + +<p> + Of course, the problem of notification is that it's often more difficult +to implement. Notification frameworks are generally more complex, involving +lots of asynchronism; polling is widely used +<a href="http://lib.store.yahoo.net/lib/demotivators/mediocritydemotivationalposter.jpg">because +it's easy.</a> +</p> + +<h3> Notifications and Unix </h3> + +<p> + Unix provides several frameworks so that a process B (or the kernel) can +notify process A. +</p> + +<ul> + <li> Signals. The simplest Unix notification mechanism. Sending events amounts +to a <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html">kill()</a> +call, and receiving events amounts to installing a signal handler (preferrably +using a <a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a> +if mixing signals with an event loop). Unfortunately, Unix signals, even the more +recent and powerful real-time Posix signals, have important limitations when it's +about generic notification: + <ul> + <li> non-root processes can only send signals to a very restricted and +implementation-dependent set of processes (roughly, processes with the same UID). This is a problem when +designing secure programs that make use of the Unix privilege separation. </li> + <li> you need to know the PID of a process to send it signals. This is generally +impractical; process management systems that do not use supervisor processes have +to do exactly that, and they resort to unreliable, ugly hacks (.pid files) to track +down process PIDs. </li> + </ul> </li> + <li> BSD-style IPCs, i.e. file descriptors to perform select()/poll() system +calls on, in an <em>asynchronous event loop</em>. This mechanism is very widely used, +and rightly so, because it's extremely generic and works in every ordinary situation; +you have to be doing <a href="http://www.kegel.com/c10k.html">very specific stuff</a> +to reach its limits. If process A is reading on +fd <em>f</em>, it is notified everytime another process makes <em>f</em> readable - +for instance by writing a byte to the other end if <em>f</em> is the reading end +of a pipe. And indeed, this is how libftrig works internally; but libftrig is needed +because direct use of BSD-style IPCs also has limitations. + <ul> + <li> Anonymous pipes are the simplest and most common BSD-style IPC. If there is a +pipe from process B to process A, then B can notify A by writing to the pipe. The +limitation is that A and B must have a common ancestor that created the pipe; two +unrelated processes cannot communicate this way. </li> + <li> Sockets are a good many-to-one notification system: once a server is up, it +can be notified by any client, and notify all its clients. The limitation of sockets +is that the server must be up before the client, which prevents us from using them +in a general notification scheme. </li> + </ul> </li> + <li> System V IPCs, i.e. message queues and semaphores. The interfaces to those IPCs +are quite specific and can't mix with select/poll loops, that's why nobody in their +right mind uses them. </li> +</ul> + +<h3> What we want </h3> + +<p> + We need a general framework to: +</p> + +<ul> + <li> Allow an event-generating process to broadcast notifications to every process +that asked for one, without having to know their PIDs </li> + <li> Allow a process to subscribe to a "notification channel" and be instantly, +asynchronously notified when an event occurs on this channel. </li> +</ul> + +<p> + This requires a many-to-many approach that Unix does not provide natively, and +that is what libftrig does. +</p> + +<a name="bus"> +<h2> That's what a bus is for. D-Bus already does all this. </h2> +</a> + +<p> + Yes, a bus is a good many-to-many notification mechanism indeed. However, +a Unix bus can only be implemented via a daemon - you need a long-running +process, i.e. a <em>service</em>, to implement a bus. And s6 is a +<em>supervision suite</em>, i.e. a set of programs designed to manage +services; we would like to be able to use notifications in the supervision +suite, to be able to wait for a service to be up or down... <em>without</em> +relying on a particular service to be up. libftrig provides a notification +mechanism that <em>does not need</em> a bus service to be up, that's its +main advantage over a bus. +</p> + +<p> + If you are not concerned with supervision and can depend on a bus service, +though, then yes, by all means, use a bus for your notification needs. +There is a <a href="http://skarnet.org/software/skabus/">skabus</a> +project in the making, which aims to be simpler, smaller and more +maintainable than D-Bus. +</p> + +<h2> How to use libftrig </h2> + +<p> + <tt>libftrig</tt> is really a part of <tt>libs6</tt>: all the functions +are implemented in the <tt>libs6.a</tt> archive, or the <tt>libs6.so</tt> +dynamic shared object. However, the interfaces are different for notifiers +and listeners: +</p> + +<ul> +<li> Notifiers use the <a href="libftrigw.html">libftrigw</a> interface. </li> +<li> Listeners use the <a href="libftrigr.html">libftrigr</a> interface. </li> +</ul> + +</body> +</html> diff --git a/doc/libftrigr.html b/doc/libftrigr.html new file mode 100644 index 0000000..27ffd61 --- /dev/null +++ b/doc/libftrigr.html @@ -0,0 +1,283 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the ftrigr library interface</title> + <meta name="Description" content="s6: the ftrigr library interface" /> + <meta name="Keywords" content="s6 ftrig notification subscriber listener libftrigr ftrigr library interface" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The <tt>ftrigr</tt> library interface </h1> + +<p> + The <tt>ftrigr</tt> library provides an API for listeners, i.e. +programs that want to subscribe to fifodirs and be instantly +notified when the proper sequence of events happens. +</p> + +<h2> Compiling </h2> + +<ul> + <li> Make sure the s6 headers, as well as the skalibs headers, +are visible in your header search path. </li> + <li> Use <tt>#include <s6/ftrigr.h></tt> </li> +</ul> + +<h2> Linking </h2> + +<ul> + <li> Make sure the s6 libraries, as well as the skalibs libraries, +are visible in your library search path. </li> + <li> Link against <tt>-ls6</tt> and <tt>-lskarnet</tt>. </li> +</ul> + +<h2> Programming </h2> + +<p> + Check the <tt>s6/ftrigr.h</tt> header for the +exact function prototypes. +</p> + +<p> + Make sure your application is not disturbed by children it doesn't +know it has. This means paying some attention to the SIGCHLD handler, +if any, and to the way you perform <tt>waitpid()</tt>s. The best +practice is to use a +<a href="http://www.skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a> +to handle SIGCHLD (as well as other signals the application needs to trap), +and to <em>always</em> use <tt>wait_nohang()</tt> to reap children, +simply ignoring pids you don't know. +</p> + +<p> + If your (badly programmed) application has trouble handling unknown +children, consider using a ftrigrd service. +</p> + +<h3> A programming example </h3> + +<p> + The <tt>src/pipe-tools/s6-ftrig-listen1.c</tt> and +<tt>src/supervision/s6-svwait.c</tt> files in the s6 package, +for instance, illustrate how to use the ftrigr library. +</p> + + +<h3> Synchronous functions with a specified maximum execution time </h3> + +<ul> + <li> Synchronous functions take a <tt>tain_t const *</tt> +(<em>deadline</em>) parameter and a <tt>tain_t *</tt> (<em>stamp</em>) +parameter. Those are pointers to taia structures containing absolute times; +the former represents a deadline (in most cases, this time will be in the +future) and the latter must be an accurate enough timestamp. These +structures can be filled using the <tt>tain_</tt> primitives declared in +<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html">skalibs/tai.h</a>. </li> + <li> ("Accurate enough" means that <strong>no blocking system call must have +been made</strong> since the last time <em>stamp</em> was updated (by +<tt>tain_now(&stamp)</tt>). It's a good policy to always update +<em>stamp</em> right after a (potentially) blocking system call like +<tt>select()</tt>returns. And unless the application is extremely CPU-intensive +(think calculus for physicists or astronomers) updating <em>stamp</em> more +frequently is unnecessary.) </li> + <li> If such a synchronous function still hasn't returned when the deadline +occurs, then it will immediately return a failure code and set errno to ETIMEDOUT. +It is possible to pass null pointers to the function instead of pointers to +taia structures, in which case the function will never timeout. </li> + <li> If a timeout occurs, the library does not guarantee proper interprocess +communication later on; the application should either die, or at least close +the communication channel and open a new one. </li> + <li> If any waiting occurred, the <em>stamp</em> structure is automatically +updated by the called function, so it always represents an accurate enough estimation +of the current time. This allows the programmer to call several such functions +in a sequence without modifying the <em>deadline</em> and <em>stamp</em> +parameters: then the whole sequence is bound in execution time. </li> + <li> This is a general safety mechanism implemented in +<a href="http://skarnet.org/software/skalibs/libunixonacid/">libunixonacid</a>: +in interprocess communication, purely synchronous primitives are dangerous +because they make the calling process rely on proper behaviour of the called +process. Giving synchronous primitives the ability to timeout allows developers +to write reliable programs even when interacting with software they have no +control on. </li> +</ul> + + +<h3> Starting and ending a session </h3> + +<pre> +ftrigr_t a = FTRIGR_ZERO ; +struct taia deadline, stamp ; + +taia_now(&stamp) ; +taia_addsec(&deadline, &stamp, 2) + +// char const *path = FTRIGR_IPCPATH ; +// ftrigr_start(&a, path, &deadline, &stamp) ; +ftrigr_startf(&a, &deadline, &stamp) ; +</pre> + +<p> +<tt>ftrigr_start</tt> starts a session with a ftrigrd service listening on +<em>path</em>. <br /> +<tt>ftrigr_startf</tt> starts a session with a ftrigrd process as a child +(which is the simplest usage). <br /> +<tt>a</tt> is a ftrigr_t structure that must be declared in the stack and +initialized to FTRIGR_ZERO. +<tt>stamp</tt> must be an accurate enough timestamp. <br /> +If the session initialization fails, the function returns 0 and errno is set; +else the function returns 1. +</p> +<p> +If the absolute time <tt>deadline</tt> is reached and the function +has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT. + +Only local interprocess communications are involved; unless your system is +heavily overloaded, the function should return near-instantly. One or two +seconds of delay between <tt>stamp</tt> and <tt>deadline</tt> should be +enough: if the function takes more than that to return, then there is a +problem with the underlying processes. +</p> + +<p> + You can have more than one session open in parallel, by declaring +several distinct <tt>ftrigr_t</tt> structures and calling +<tt>ftrigr_startf</tt> (or <tt>ftrigr_start</tt>) more than once. +However, this is useless, since one single session can handle +virtually as many concurrent fifodirs as your application needs. +</p> + +<pre> +ftrigr_end(&a) ; +</pre> + +<p> +<tt>ftrigr_end</tt> frees all the resources used by the session. The +<tt>a</tt> structure is then reusable for another session. +</p> + +<h3> Subscribing to a fifodir </h3> + +<pre> +char const *path = "/var/lib/myservice/fifodir" ; +char const *re = "a.*b|c*d" ; +uint32 options = 0 ; + +uint16 id = ftrigr_subscribe (&a, path, re, options, &deadline, &stamp) ; +</pre> + +<p> +<tt>ftrigr_subscribe</tt> instructs the +<a href="s6-ftrigrd.html">s6-ftrigrd daemon</a>, related to the open +session represented by the <tt>a</tt> structure, to subscribe to the +<tt>path</tt> fifodir, and to notify the application when it receives +a series of events that matches the <tt>re</tt> regexp. +<tt>options</tt> can be 0 or FTRIGR_REPEAT. If it is 0, the daemon will +automatically unsubscribe from <tt>path</tt> once <tt>re</tt> has been +matched by a series of events. If it is FTRIGR_REPEAT, it will remain +subscribed until told otherwise. +</p> + +<p> + <tt>ftrigr_subscribe()</tt> returns 0 and sets errno in case of failure, or +a nonzero 16-bit number identifying the subscription in case of success. +</p> + +<p> +<tt>ftrigr_subscribe</tt> should return near-instantly, but if +<em>deadline</em> is reached, it will return 0 ETIMEDOUT. If +<tt>ftrigr_subscribe</tt> returns successfully, then the +s6-ftrigrd daemon is guaranteed to be listening on <tt>path</tt>, +and events can be sent without the risk of a race condition. +</p> + +<h3> Synchronously waiting for events </h3> + +<pre> +uint16 list[1] ; +unsigned int n = 1 ; +char trigger ; +list[0] = id ; + +// r = ftrigr_wait_and(&a, list, n, &deadline) ; +r = ftrigr_wait_or(&a, list, n, &deadline, &trigger) ; +</pre> + +<p> + <tt>ftrigr_wait_and()</tt> waits for <em>all</em> the <tt>n</tt> fifodirs +whose ids are listed in <tt>list</tt> to receive an event. It returns -1 +in case of error or timeout, or a non-negative integer in case of success. <br /> + <tt>ftrigr_wait_or()</tt> waits for <em>one</em> of the <tt>n</tt> fifodirs +whose ids are listed in <tt>list</tt> to receive an event. It returns -1 +in case of error or timeout; if it succeeds, the return value is the +position in <tt>list</tt>, starting at 0, of the identifier that received +an event; and <tt>trigger</tt> is set to the character that triggered that +event, i.e. the last character of a sequence that matched the regular +expression <tt>re</tt> used in the subscription. +</p> + +<h3> Asynchronously waiting for events </h3> + +<p> +<em> (from now on, the functions are listed with their prototypes instead +of usage examples.) </em> +</p> + +<pre> +int ftrigr_fd (struct ftrigr const *a) +</pre> + +<p> + Returns a file descriptor to select on for reading. Do not +<tt>read()</tt> it though. +</p> + +<pre> +int ftrigr_update (ftrigr_ref a) +</pre> + +<p> + Call this function whenever the fd checks readability: it will +update <em>a</em>'s internal structures with information from the +<a href="s6-ftrigrd.html">s6-ftrigrd</a> daemon. It returns -1 if an error +occurs; in case of success, it returns the number of identifiers for +which something happened. +</p> + +<p> + When <tt>ftrigr_update</tt> returns, +<tt>genalloc_s(uint16, &a->list)</tt> points to an array of +<tt>genalloc_len(uint16, &a->list)</tt> 16-bit unsigned +integers. Those integers are ids waiting to be passed to +<tt>ftrigr_check</tt>. +</p> + +<pre> +int ftrigr_check (ftrigr_ref a, uint16 id, char *what) +</pre> + +<p> + Checks whether an event happened to <em>id</em>. Use after a +call to <tt>ftrigr_update()</tt>. +</p> + +<ul> + <li> If an error occurred, returns -1 and sets errno. The error +number may have been transmitted from +<a href="s6-ftrigrd.html">s6-ftrigrd</a>. </li> + <li> If no notification happened yet, returns 0. </li> + <li> If something happened, writes the character that triggered the +latest notification into <em>what</em> and returns the number of +times that an event happened to this identifier since the last +call to <tt>ftrigr_check()</tt>. </li> +</ul> + +</body> +</html> diff --git a/doc/libftrigw.html b/doc/libftrigw.html new file mode 100644 index 0000000..d804976 --- /dev/null +++ b/doc/libftrigw.html @@ -0,0 +1,115 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the ftrigw library interface</title> + <meta name="Description" content="s6: the ftrigw library interface" /> + <meta name="Keywords" content="s6 ftrig notification notifier writer libftrigw ftrigw library interface" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The <tt>ftrigw</tt> library interface </h1> + +<p> + The <tt>ftrigw</tt> library provides an API for notifiers, i.e. +programs that want to regularly announce what they're doing. +</p> + +<p> + Notifiers should create a fifodir in a hardcoded place in the +filesystem, and document its location. Listeners will then be +able to subscribe to that fifodir, and receive the events. +</p> + +<h2> Compiling </h2> + +<ul> + <li> Make sure the s6 headers, as well as the skalibs headers, +are visible in your header search path. </li> + <li> Use <tt>#include <s6/ftrigw.h></tt> </li> +</ul> + +<h2> Linking </h2> + +<ul> + <li> Make sure the s6 libraries, as well as the skalibs libraries, +are visible in your library search path. </li> + <li> Link against <tt>-ls6</tt> and <tt>-lskarnet</tt>. </li> +</ul> + +<h2> Programming </h2> + +<p> + Check the <tt>s6/ftrigw.h</tt> header for the +exact function prototypes. +</p> + +<h3> Creating a fifodir </h3> + +<pre> +char const *path = "/var/lib/myservice/fifodir" ; +int gid = -1 ; +int forceperms = 0 ; +int r = ftrigw_fifodir_make(path, gid, forceperms) ; +</pre> + +<p> +<tt>ftrigw_fifodir_make</tt> creates a fifodir at the <tt>path</tt> location. +It returns 0, and sets errno, if an error occurs. +It returns 1 if it succeeds. <br /> +If a fifodir, owned by the user, already exists at <tt>path</tt>, and +<tt>forceperms</tt> is zero, then <tt>ftrigw_fifodir_make</tt> immediately +returns a success. If <tt>forceperms</tt> is nonzero, then +it tries to adjust <tt>path</tt>'s permissions before returning. +</p> + +<p> +If <tt>gid</tt> is negative, then <tt>path</tt> is created "public". +Any listener will be able to subscribe to <tt>path</tt>. +If <tt>gid</tt> is nonnegative, then <tt>path</tt> is created "private". +Only processes belonging to group <tt>gid</tt> will be able to +subscribe to <tt>path</tt>. +</p> + +<h3> Sending an event </h3> + +<pre> +char event = 'a' ; +r = ftrigw_notify(path, event) ; +</pre> + +<p> +<tt>ftrigw_notify</tt> sends <tt>event</tt> to all the processes that are +currently subscribed to <tt>path</tt>. +It returns -1 if an error occurs, or the number of successfully notified +processes. +</p> + +<h3> Cleaning up </h3> + +<p> +When stray KILL signals hit <a href="s6-ftrigrd.html">s6-ftrigrd</a> processes, +1. it's a sign of incorrect system administration, 2. they can leave +unused named pipes in the fifodir. It's the fifodir's owner's job, i.e. +the notifier's job, to periodically do some housecleaning and take out +the trash. +</p> + +<pre> +r = ftrigw_clean(path) ; +</pre> + +<p> +<tt>ftrigw_clean</tt> cleans <tt>path</tt>. It returns 0, and sets errno, +if it encounters an error. It returns 1 if it succeeds. +</p> + +</body> +</html> diff --git a/doc/libs6lock/index.html b/doc/libs6lock/index.html new file mode 100644 index 0000000..3696b41 --- /dev/null +++ b/doc/libs6lock/index.html @@ -0,0 +1,256 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6lock library interface</title> + <meta name="Description" content="s6: the s6lock library interface" /> + <meta name="Keywords" content="s6 timed lock s6lock libs6lock library interface" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="../">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The <tt>s6lock</tt> library interface </h1> + +<h2> General information </h2> + +<p> + <tt>libs6lock</tt> is a C interface to timed locks. Unix natively provides +locks, but the locking primitives are synchronous, so either they are +unbounded in execution time or they require polling. libs6lock provides +poll-free locks that can timeout during attempted acquisition. +</p> + +<h2> Compiling </h2> + +<ul> + <li> Make sure the s6 headers, as well as the skalibs headers, +are visible in your header search path. </li> + <li> Use <tt>#include <s6/s6lock.h></tt> </li> +</ul> + +<h2> Linking </h2> + +<ul> + <li> Make sure the s6 libraries, as well as the skalibs libraries, +are visible in your library search path. </li> + <li> Link against <tt>-ls6</tt>, <tt>-lskarnet</tt>, +<tt>`cat $sysdeps/socket.lib`</tt>, and +<tt>`cat $sysdeps/tainnow.lib`</tt>, +if <tt>$sysdeps</tt> is your skalibs installation's sysdeps directory. </li> +</ul> + +<h2> Programming </h2> + +<ul> + <li> Check the <tt>s6/s6lock.h</tt> header +for the prototypes. The functions documented here are +often simplified macros, for instance relying on the STAMP global variable +to hold the current time. Fully reentrant functions with more control +options are usually available. </li> + <li> Given the nature of the s6lock library, it makes sense to use a +<a href="http://skarnet.org/software/s6-networking/localservice.html">s6lockd service</a> concurrently +accessed by several applications using such locks to gate shared +resources. </li> + <li> If you're not using a s6lockd service, +make sure your application is not disturbed by children it doesn't +know it has. Using nonblocking waits, ignoring pids you don't know, and +using a +<a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a> +if your application is built around an event loop, are good programming +practices. </li> +</ul> + +<h3> Starting and ending a session </h3> + +<pre> +s6lock_t a = S6LOCK_ZERO ; +struct taia deadline ; + +taia_now_g() ; +taia_addsec_g(&deadline, 2) + +char const *path = S6LOCK_IPCPATH ; +s6lock_start_g(&a, path, &deadline) ; +// char const *lockdir = "/tmp/lock" ; +// s6lock_startf_g(&a, lockdir, &deadline) ; +</pre> + +<p> +<tt>s6lock_start_g</tt> starts a session by connecting to a s6lockd service +listening on <em>path</em>. The working directory is set by the administrator +of the service. <br /> +<tt>s6lock_startf_g</tt> starts a session with a s6lockd process as a child, +using <em>lockdir</em> as its working directory. +<br /> +<tt>a</tt> is a s6lock_t structure that must be declared in the stack and +initialized to S6LOCK_ZERO. +If the session initialization fails, the function returns 0 and errno is set; +else the function returns 1. +</p> +<p> +If the absolute time <tt>deadline</tt> is reached and the function +has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT. + +Only local interprocess communications are involved; unless your system is +heavily overloaded, the function should return near-instantly. One or two +seconds of delay between the current time and <tt>deadline</tt> should be +enough: if the function takes more than that to return, then there is a +problem with the underlying processes. +</p> + +<p> + You can have more than one session open in parallel, by declaring +several distinct <tt>s6lock_t</tt> structures and calling +<tt>s6lock_startf_g</tt> (or <tt>s6lock_start_g</tt>) more than once. +However, one single session can handle +virtually as many concurrent locks as your application needs, so +opening several sessions is only useful if you need to acquire locks +in various distinct lock directories. +</p> + +<pre> +s6lock_end(&a) ; +</pre> + +<p> +<tt>s6lock_end</tt> frees all the resources used by the session. The +<tt>a</tt> structure is then reusable for another session. +</p> + +<h3> Acquiring and releasing locks </h3> + +<pre> +uint16 id ; +char const *file = "lockfile" ; +struct taia limit ; +struct taia deadline ; + +int r = s6lock_acquire_sh_g (&a, &id, file, &limit, &deadline) ; +/* int r = s6lock_acquire_ex_g (&a, &id, file, &limit, &deadline) ; */ +r = s6lock_release_g(&a, id, &deadline) ; +</pre> + +<p> +<tt>s6lock_acquire_sh_g</tt> instructs the +<a href="s6lockd.html">s6lockd daemon</a>, related to the open +session represented by the <tt>a</tt> handle, to try and acquire a +shared lock on the +<em>file</em> file located under that daemon's working directory +(typically <tt>/var/lock</tt>). <em>file</em> will be interpreted as +relative to the daemon's working directory even if it starts with a +slash; however, slashes in the middle of <em>file</em> are likely to +result in an error. +</p> + +<p> +<em>limit</em> and <em>deadline</em> are two absolute dates. +<em>deadline</em> is a deadline for the execution of the +function: if by <em>deadline</em> the function has not returned, +then it instantly returns 0 and sets errno to ETIMEDOUT. The +function is normally near-instantaneous, so <em>deadline</em> can +be very close in the future and serves only as a protection against +malicious servers. <em>limit</em> is the acquisition deadline: if +by <em>limit</em> the daemon still has not been able to acquire a lock +on <em>file</em>, then it will report a timeout to the client. +</p> + +<p> +The function returns 1 in case of success, or 0 if an error occurs, +with errno set to a suitable value. If it succeeds, then a 16-bit +number is stored into *<em>id</em>; this number serves as an identifier +for this lock. +</p> + +<p> +<tt>s6lock_acquire_ex_g</tt> works just like <tt>s6lock_acquire_sh_g</tt>, +except that the daemon tries to acquire an exclusive lock. +</p> + +<p> +<tt>s6lock_release_g</tt> releases the lock identified by <em>id</em>. +It normally returns 1. It can return 0 with errno set to a suitable +value if it fails. <em>id</em> is not valid after the corresponding +lock has been released. The function normally returns instantly, with +<em>deadline</em> as a safeguard. +</p> + +<h3> Asynchronously waiting for locks </h3> + +<p> +<em> (from now on, the functions are listed with their prototypes instead +of usage examples.) </em> +</p> + +<pre> +int s6lock_fd (s6lock_t const *a) +</pre> + +<p> + Returns a file descriptor to select on for reading. Do not +<tt>read()</tt> it though. +</p> + +<pre> +int s6lock_update (s6lock_t *a) +</pre> + +<p> + Call this function whenever the fd checks readability: it will +update <em>a</em>'s internal structures with information from the +<a href="s6lockd.html">s6lockd</a> daemon. It returns -1 if an error +occurs; in case of success, it returns the number of identifiers for +which something happened. +</p> + +<p> + When <tt>s6lock_update</tt> returns, +<tt>genalloc_s(uint16, &a->list)</tt> points to an array of +<tt>genalloc_len(uint16, &a->list)</tt> 16-bit unsigned +integers. Those integers are ids waiting to be passed to +<tt>s6lock_check</tt>. +</p> + +<pre> +int s6lock_check (s6lock_t *a, uint16 id, char *what) +</pre> + +<p> + Checks whether the lock identified by <em>id</em> has +been acquired. Use after a call to <tt>s6lock_update()</tt>. +</p> + +<ul> + <li> If an error occurred, returns -1 and sets errno. The error +number may have been transmitted from +<a href="s6lockd.html">s6lockd</a>. </li> + <li> If the lock has not been acquired yet, returns 0. </li> + <li> If the lock has been acquired, returns 1. </li> +</ul> + +<h3> Synchronously waiting for locks </h3> + +<p> +<code> int s6lock_wait_or_g (s6lock_t *a, uint16 const *idlist, unsigned int n, struct taia const *deadline) </code> <br /> +Synchronously waits for <em>one</em> of the locks represented by the array pointed to +by <em>idlist</em> of length <em>n</em> to be acquired. Returns -1 if it fails, +or a nonnegative number on success, which is the index in <em>idlist</em> of the +acquired lock's id. If no result has been obtained by <em>deadline</em>, the +function returns -1 ETIMEDOUT. +</p> + +<p> +<code> int s6lock_wait_and_g (s6lock_t *a, uint16 const *idlist, unsigned int n, struct taia const *deadline) </code> <br /> +Synchronously waits for <em>all</em> of the locks represented by the array pointed to +by <em>idlist</em> of length <em>n</em> to be acquired. Returns -1 if it fails and +0 if it succeeds. If no result has been obtained by <em>deadline</em>, the +function returns -1 ETIMEDOUT. +</p> + +</body> +</html> diff --git a/doc/libs6lock/s6lockd-helper.html b/doc/libs6lock/s6lockd-helper.html new file mode 100644 index 0000000..839dce4 --- /dev/null +++ b/doc/libs6lock/s6lockd-helper.html @@ -0,0 +1,52 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6lockd-helper internal program</title> + <meta name="Description" content="s6: the s6lockd-helper internal program" /> + <meta name="Keywords" content="s6 s6lockd-helper lockd asynchronous timed lock daemon helper" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<a href="index.html">libs6lock</a><br /> +<a href="../">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a><p /> + +<h1> The <tt>s6lockd-helper</tt> program </h1> + +<p> +<tt>s6lockd-helper</tt> is a helper program for the s6lock daemon. +It just acquires a lock and holds it until it is killed or told to +exit by its parent daemon. +</p> + +<h2> Interface </h2> + +<p> + s6lockd-helper is not meant to be invoked directly by the user: +it will be spawned by the +<a href="s6lockd.html">s6lockd</a> program. +</p> + +<h2> Notes </h2> + +<ul> + <li> s6lockd-helper blocks on lock acquisition until it succeeds. It then +notifies its parent. It exits when its parent tells him to (i.e. when the +client asks for lock release). During the lock acquisition phase, it can +be killed if its parent detects a timeout. </li> + <li> One s6lockd-helper process per lock is the only way (apart from +threads) to implement timed lock acquisition. This can lead to a lot of +s6lockd-helper processes, but this is not a problem: + <ul> + <li> Processes are not a scarce resource. Today's schedulers work in O(1), +i.e. a sleeping process takes no scheduling time at all. </li> + <li> s6lockd-helper is extremely tiny. Every instance should use up at +most one or two pages of non-sharable memory. </li> + </ul> </li> +</ul> + +</body> +</html> diff --git a/doc/libs6lock/s6lockd.html b/doc/libs6lock/s6lockd.html new file mode 100644 index 0000000..726d2f8 --- /dev/null +++ b/doc/libs6lock/s6lockd.html @@ -0,0 +1,73 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6lockd internal program</title> + <meta name="Description" content="s6: the s6lockd internal program" /> + <meta name="Keywords" content="s6 s6lockd lockd asynchronous timed lock daemon" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<a href="index.html">libs6lock</a><br /> +<a href="../">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a><p /> + +<h1> The <tt>s6lockd</tt> program </h1> + +<p> +<tt>s6lockd</tt> is the s6lock daemon. It is a program that manages +a set of lock files in a given directory, and associated timeouts. +</p> + +<h2> Interface </h2> + +<p> + s6lockd does not fork, does not background itself automatically, +and does not use syslog. It is not meant to be run directly by the +user: it will be spawned by the +<a href="index.html">s6lock client library</a>. +</p> + +<p> + There are 2 ways to use s6lockd: +</p> + +<ol> + <li> Use the <tt>s6lock_startf()</tt> library call. +A <tt>s6lockd</tt> child will then be spawned from your +calling process, and automatically reaped when you call +<tt>s6lock_end()</tt>. It requires care with applications that +trap SIGCHLD. It also requires care with lock file permissions: +a s6lockd instance might not be able +to open a lock file created by a former instance run by another +client with different permissions. </li> + <li> Use the <tt>s6lock_start()</tt> library call, together with a +<a href="http://skarnet.org/software/s6-networking/localservice.html">s6lockd service</a>. +For once, <em>this is the recommended setup</em>: s6lockd creates empty +lock files, and having all s6lockd instances run under the same user +simplifies permissions management considerably. </li> + </li> +</ol> + +<p> +When run as a service, s6lockd has no "standalone" mode: it is +designed to work with a Unix +domain superserver, like +<a href="http://skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a>. +s6lockd follows the <a href="http://cr.yp.to/proto/ucspi.txt">UCSPI</a> +interface, it can be directly executed from the superserver. +</p> + +<h2> Notes </h2> + +<ul> + <li> Unix does not natively provide a way to stop blocking on a lock +acquisition after a timeout. To emulate such behaviour, s6lockd actually +spawns a <a href="s6lockd-helper.html">s6lockd-helper</a> child per +requested lock. </li> +</ul> + +</body> +</html> diff --git a/doc/notifywhenup.html b/doc/notifywhenup.html new file mode 100644 index 0000000..40b0593 --- /dev/null +++ b/doc/notifywhenup.html @@ -0,0 +1,75 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: service startup notifications</title> + <meta name="Description" content="s6: service startup notifications" /> + <meta name="Keywords" content="s6 ftrig notification notifier writer libftrigw ftrigw startup U up svwait s6-svwait" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> Service startup notifications </h1> + +<p> + It is easy for a process supervision suite to know when a service that was <em>up</em> +is now <em>down</em>: the long-lived process implementing the service is dead. The +supervisor, running as the daemon's parent, is instantly notified via a SIGCHLD. +When it happens, <a href="s6-supervise.html">s6-supervise</a> sends a 'd' event +to its <tt>./event</tt> <a href="fifodir.html">fifodir</a>, so every subscriber +knows that the service is down. All is well. +</p> + +<p> + It is much trickier for a process supervision suite to know when a service +that was <em>down</em> is now <em>up</em>. The supervisor forks and execs the +daemon, and knows when the exec has succeeded; but after that point, it's all +up to the daemon itself. Some daemons do a lot of initialization work before +they're actually ready to serve, and it is impossible for the supervisor to +know exactly <em>when</em> the service is really ready. +<a href="s6-supervise.html">s6-supervise</a> sends a 'u' event to its +<tt>./event</tt> <a href="fifodir.html">fifodir</a> when it successfully +spawns the daemon, but any subscriber +reacting to 'u' is subject to a race condition - the service provided by the +daemon may not be ready yet. +</p> + +<p> + Reliable startup notifications need support from the daemons themselves. +Daemons should notify the outside world when the service they are providing +is reliably up - because only they know when it is the case. +</p> + +<p> + s6 provides two ways for daemons to perform startup notification. +</p> + +<ol> + <li> Daemons can use the <tt>ftrigw_notify()</tt> function, provided in +<a href="libftrigw.html">the ftrigw library</a>. This is extremely +simple and efficient, but requires specific s6 support in the daemon. </li> + <li> Daemons can write something to a file descriptor of their choice, +then close that file descriptor, when they're ready to serve. This is +a generic mechanism that some daemons already implement, and does not +require anything specific in the daemon's code. The administrator can +then run the daemon under <a href="s6-notifywhenup.html">s6-notifywhenup</a>, +which will properly catch the daemon's message and notify all the subscribers +with a 'U' event, meaning that the service is now up with no possible race +condition. </li> +</ol> + +<p> + The second method should really be implemented in every long-running +program providing a service. When it is not the case, it's impossible +to provide race-free startup notifications, and subscribers should be +content with the unreliable 'u' events provided by s6-supervise. +</p> + +</body> +</html> diff --git a/doc/s6-cleanfifodir.html b/doc/s6-cleanfifodir.html new file mode 100644 index 0000000..5724666 --- /dev/null +++ b/doc/s6-cleanfifodir.html @@ -0,0 +1,42 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-cleanfifodir program</title> + <meta name="Description" content="s6: the s6-cleanfifodir program" /> + <meta name="Keywords" content="s6 command s6-cleanfifodir fifodir notification cleaning" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-cleanfifodir program </h1> + +<p> +s6-cleanfifodir cleans up a <a href="fifodir.html">fifodir</a>. +</p> + +<h2> Interface </h2> + +<pre> + s6-cleanfifodir <em>fifodir</em> +</pre> + +<p> +s6-cleanfifodir cleans up <em>fifodir</em>, that must belong to the current user. +That means it removes all stale FIFOs in <em>fifodir</em>. +</p> + +<p> + In normal use, it is not necessary to call s6-cleanfifodir. However, stale +FIFOs can be left by <a href="s6-ftrigrd.html">s6-ftrigrd</a> processes that +were violently killed, so it's good practice to regularly clean up fifodirs. +</p> + +</body> +</html> diff --git a/doc/s6-envdir.html b/doc/s6-envdir.html new file mode 100644 index 0000000..5ac92e2 --- /dev/null +++ b/doc/s6-envdir.html @@ -0,0 +1,66 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-envdir program</title> + <meta name="Description" content="s6: the s6-envdir program" /> + <meta name="Keywords" content="s6 command s6-envdir dir environment modification" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-envdir program </h1> + +<p> +s6-envdir changes its environment, then executes into another program. +</p> + +<h2> Interface </h2> + +<pre> + s6-envdir [ -I | -i ] [ -n ] [ -f ] [ -c <em>nullis</em> ] <em>dir</em> <em>prog...</em> +</pre> + +<ul> + <li> s6-envdir reads files in <em>dir</em>. For every file <em>f</em> in <em>dir</em>, +that does not begin with a dot and does not contain the '=' character: </li> + <li> If <em>f</em> is empty, remove a variable named <em>f</em> from the environment, if any. </li> + <li> Else add a variable named <em>f</em> to the environment (or replace <em>f</em> if it +already exists) with the first line of the contents of file <em>f</em> as value. +Spaces and tabs at the end of this line are removed; +null characters in this line are changed to newlines in the environment variable.</li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-i</tt> : strict. If <em>dir</em> does not exist, exit 111 with an +error message. This is the default. </li> + <li> <tt>-I</tt> : loose. If <em>dir</em> does not exit, exec into +<em>prog</em> without modifying the environment first. </li> + <li> <tt>-f</tt> : verbatim mode. All the file is given as the value of the +environment variable, including newlines (except the last one if the <tt>-n</tt> +option is not given). Null characters are still translated. </li> + <li> <tt>-n</tt> : do not chomp. If the <tt>-f</tt> option is given and the +file ends with a newline, keep that last newline in the value. If the <tt>-f</tt> +option is not given, keep the trailing blanks at the end of the first line (but +not the ending newline). </li> + <li> <tt>-c</tt> <em>nullis</em> : replace null characters with the +first character of <em>nullis</em> instead of a newline. </li> +</ul> + +<h2> Notes </h2> + +<p> + s6-envdir without options behaves exactly like +<a href="http://cr.yp.to/daemontools/envdir.html">envdir</a>. +</p> + +</body> +</html> diff --git a/doc/s6-envuidgid.html b/doc/s6-envuidgid.html new file mode 100644 index 0000000..680e841 --- /dev/null +++ b/doc/s6-envuidgid.html @@ -0,0 +1,64 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-envuidgid program</title> + <meta name="Description" content="s6: the s6-envuidgid program" /> + <meta name="Keywords" content="s6 command s6-envuidgid uid gid environment modification" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-envuidgid program </h1> + +<p> +s6-envuidgid sets the UID, GID and GIDLIST environment variables, +then executes into another program. +</p> + +<h2> Interface </h2> + +<pre> + s6-envuidgid <em>account</em> <em>prog...</em> +</pre> + +<ul> + <li> s6-envuidgid looks <em>account</em> up by name in the account database. </li> + <li> If <em>account</em> is unknown, it exits 1. </li> + <li> It sets the UID environment variable to <em>account</em>'s uid, and the GID +environment variable to <em>account</em>'s gid. </li> + <li> It also sets the GIDLIST environment variable to a comma-separated list of +supplementary group ids <em>account</em> is a member of according to the +group database. (If <em>account</em> doesn't belong to any other group than its +primary group, GIDLIST is still set, but empty.) </li> + <li> Then it executes into <em>prog...</em>. </li> +</ul> + +<h2> Notes </h2> + +<p> + s6-envuidgid behaves like +<a href="http://cr.yp.to/daemontools/envuidgid.html">envuidgid</a>, except that: +</p> + +<ul> + <li> it also handles supplementary groups </li> + <li> It exits 1 if <em>account</em> does not exist. </li> +</ul> + +<p> + s6-envuidgid is useful when running a program that must start as root but can +drop its privileges later. Such a program can read its new uid/gid/groups info +from the UID, GID and GIDLIST environment variables. Superservers such as +<a href="http://skarnet.org/software/s6-networking/s6-tcpserver4.html">s6-tcpserver4</a> +make use of this. +</p> + +</body> +</html> diff --git a/doc/s6-fghack.html b/doc/s6-fghack.html new file mode 100644 index 0000000..ba3f351 --- /dev/null +++ b/doc/s6-fghack.html @@ -0,0 +1,50 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-fghack program</title> + <meta name="Description" content="s6: the s6-fghack program" /> + <meta name="Keywords" content="s6 command s6-fghack foreground program background hack anti-backgrounding tool" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-fghack program </h1> + +<p> +s6-fghack is an anti-backgrounding tool. +</p> + +<h2> Interface </h2> + +<pre> + s6-fghack <em>prog...</em> +</pre> + +<ul> + <li> s6-fghack opens a lot of file descriptors (all writing to a single pipe). </li> + <li> Then it forks and executes <em>prog...</em>. </li> + <li> If something gets written on one of those descriptors, it's a bug in <em>prog</em>. +s6-fghack then complains and exits 102. </li> + <li> Unless <em>prog...</em> goes out of its way to close descriptors it does know about, +s6-fghack is able to detect when <em>prog...</em> exits. It exits with the same exit +code (or 111 if <em>prog...</em> has crashed). </li> +</ul> + +<h2> Notes </h2> + +<p> + s6-fghack is what it says: a hack. Ideally, you should never have to use it. +It is only useful when you want to supervise a daemon that does not provide a +"stay in the foreground" option; and even then, the right thing is to report +this as a bug to the daemon author and have it fixed. +</p> + +</body> +</html> diff --git a/doc/s6-ftrig-listen.html b/doc/s6-ftrig-listen.html new file mode 100644 index 0000000..e1d2518 --- /dev/null +++ b/doc/s6-ftrig-listen.html @@ -0,0 +1,73 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-ftrig-listen program</title> + <meta name="Description" content="s6: the s6-ftrig-listen program" /> + <meta name="Keywords" content="s6 command s6-ftrig-listen fifodir notification event listener subscriber receive" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-ftrig-listen program </h1> + +<p> +s6-ftrig-listen subscribes to several <a href="fifodir.html">fifodirs</a>, then +spawns a program, then waits for pattern of events to occur on the fifodirs. +</p> + +<h2> Interface </h2> + +<p> + In an <a href="http://skarnet.org/software/execline/execlineb.html">execlineb</a> +script: +</p> + +<pre> + s6-ftrig-listen [ -a | -o ] [ -t <em>timeout</em> ] { <em>fifodir1</em> <em>regexp1</em> <em>fifodir2</em> <em>regexp2</em> ... } <em>prog...</em> +</pre> + +<ul> + <li> s6-ftrig-listen subscribes to <em>fifodir1</em> with the regexp <em>regexp1</em>, +to <em>fifodir2</em> with the regexp <em>regexp2</em>, and so on. </li> + <li> It then forks and exec <em>prog...</em> with all its arguments </li> + <li> It waits for the series of events received on <em>fifodir-i</em> +to match <em>regexp-i</em>, The <em>regexp-i</em> must be +<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">extended +regular expressions</a>. </li> + <li> When the series of read events matches the <em>regexp</em>s, +s6-ftrig-listen exits 0. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-t <em>timeout</em></tt> : if the events on the <em>fifodir</em>s have not +matched the <em>regexp</em>s after <em>timeout</em> milliseconds, print an error message on +stderr and exit 1. By default, s6-ftrig-listen1 waits indefinitely for a matching series +of events. </li> + <li> <tt>-a</tt> : and (conjunction). s6-ftrig-listen will only exit when <em>all</em> +the <em>fifodir-i</em> have been notified with events matching the corresponding <em>regexp-i</em>. +This is the default. </li> + <li> <tt>-o</tt> : one (disjunction). s6-ftrig-listen will exit as soon as <em>one</em> +of the <em>fifodir-i</em> has been notified with events matching its <em>regexp-i</em>. </li> +</ul> + +<h2> Notes </h2> + +<p> + s6-ftrig-listen can be used outside of an execlineb script by using the +internal argv syntax, but this syntax is an implementation detail and is +not documented as stable. In a shell +script, use <tt>execlineb -Pc 's6-ftrig-listen ...'</tt> to get the +benefits of the execlineb brace syntax. +</p> + +</body> +</html> diff --git a/doc/s6-ftrig-listen1.html b/doc/s6-ftrig-listen1.html new file mode 100644 index 0000000..88e2c6a --- /dev/null +++ b/doc/s6-ftrig-listen1.html @@ -0,0 +1,88 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-ftrig-listen1 program</title> + <meta name="Description" content="s6: the s6-ftrig-listen1 program" /> + <meta name="Keywords" content="s6 command s6-ftrig-listen1 fifodir notification event listener subscriber receive" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-ftrig-listen1 program </h1> + +<p> +s6-ftrig-listen1 subscribes to a <a href="fifodir.html">fifodir</a>, then +spawns a program, then waits for a pattern of events to occur on the fifodir. +</p> + +<p> +s6-ftrig-listen1 acts just as <a href="s6-ftrig-wait.html">s6-ftrig-wait</a>, +except it can make sure that the process sending the notifications is actually +started <em>after</em> there is a listener for those events. +</p> + +<h2> Interface </h2> + +<pre> + s6-ftrig-listen1 [ -t <em>timeout</em> ] <em>fifodir</em> <em>regexp</em> <em>prog...</em> +</pre> + +<ul> + <li> s6-ftrig-listen1 subscribes to <em>fifodir</em> </li> + <li> It then forks and exec <em>prog...</em> with all its arguments </li> + <li> It waits for the series of events received on <em>fifodir</em> +to match <em>regexp</em>. <em>regexp</em> must be an +<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">extended +regular expression</a>. </li> + <li> When the series of read events matches <em>regexp</em>, +s6-ftrig-listen1 exits 0. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-t <em>timeout</em></tt> : if the events on <em>fifodir</em> have not +matched <em>regexp</em> after <em>timeout</em> milliseconds, print an error message on +stderr and exit 1. By default, s6-ftrig-listen1 waits indefinitely for a matching series +of events. </li> +</ul> + +<h2> Usage example </h2> + +<p> + The following sequence of shell commands has a race condition: +</p> + +<p> <em>In terminal 1:</em> </p> +<pre> +s6-mkfifodir /tmp/toto +s6-ftrig-wait /tmp/toto "message" +</pre> + +<p> <em>Then in terminal 2</em> </p> +<pre> +s6-ftrig-notify /tmp/toto message +</pre> + +<p> + Depending on the operating system's scheduler, there is the possibility that +the s6-ftrig-notify process starts sending "message" <em>before</em> the +s6-ftrig-wait process has actually subscribed to <tt>/tmp/toto</tt>, in which +case the notification will be missed. The following sequence of shell commands +accomplishes the same goal in a reliable way, without the race condition: +</p> + +<pre> +s6-mkfifodir /tmp/toto +s6-ftrig-listen1 /tmp/toto "message" s6-ftrig-notify /tmp/toto message +</pre> + +</body> +</html> diff --git a/doc/s6-ftrig-notify.html b/doc/s6-ftrig-notify.html new file mode 100644 index 0000000..fa1e8b5 --- /dev/null +++ b/doc/s6-ftrig-notify.html @@ -0,0 +1,44 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-ftrig-notify program</title> + <meta name="Description" content="s6: the s6-ftrig-notify program" /> + <meta name="Keywords" content="s6 command s6-ftrig-notify fifodir notification event notifier send" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-ftrig-notify program </h1> + +<p> +s6-ftrig-notify sends a series of events to a <a href="fifodir.html">fifodir</a>. +</p> + +<h2> Interface </h2> + +<pre> + s6-ftrig-notify <em>fifodir</em> <em>message</em> +</pre> + +<p> +s6-ftrig-notify notifies all the current listeners in <em>fifodir</em> +with all the characters in <em>message</em>, one by one. +</p> + +<h2> Notes </h2> + +<p> +s6-ftrig-notify cannot be used to send the null character (event 0x00). +If you need to send the null character, use the C API: +<a href="libftrigw.html">ftrigw_notify()</a>. +</p> + +</body> +</html> diff --git a/doc/s6-ftrig-wait.html b/doc/s6-ftrig-wait.html new file mode 100644 index 0000000..a833782 --- /dev/null +++ b/doc/s6-ftrig-wait.html @@ -0,0 +1,51 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-ftrig-wait program</title> + <meta name="Description" content="s6: the s6-ftrig-wait program" /> + <meta name="Keywords" content="s6 command s6-ftrig-wait fifodir notification event listener subscriber receive" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-ftrig-wait program </h1> + +<p> +s6-ftrig-listen1 subscribes to a <a href="fifodir.html">fifodir</a> and +waits for a pattern of events to occur on this fifodir. +</p> + +<h2> Interface </h2> + +<pre> + s6-ftrig-wait [ -t <em>timeout</em> ] <em>fifodir</em> <em>regexp</em> +</pre> + +<ul> + <li> s6-ftrig-wait subscribes to <em>fifodir</em> </li> + <li> It waits for the series of events received on <em>fifodir</em> +to match <em>regexp</em>. <em>regexp</em> must be an +<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">extended +regular expression</a>. </li> + <li> When the series of read events matches <em>regexp</em>, +s6-ftrig-wait exits 0. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-t <em>timeout</em></tt> : if the events on <em>fifodir</em> have not +matched <em>regexp</em> after <em>timeout</em> milliseconds, print an error message on +stderr and exit 1. By default, s6-ftrig-wait waits indefinitely for a matching series +of events. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-ftrigrd.html b/doc/s6-ftrigrd.html new file mode 100644 index 0000000..dbd611e --- /dev/null +++ b/doc/s6-ftrigrd.html @@ -0,0 +1,73 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-ftrigrd program</title> + <meta name="Description" content="s6: the s6-ftrigrd program" /> + <meta name="Keywords" content="s6 command s6-ftrigrd program internal libexec fifodir regexp subscribe notification listener" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-ftrigrd program </h1> + +<p> +s6-ftrigrd is a helper program that manages a set of subscriptions to fifodirs as well +as regular expressions on events. It takes orders from its client program that controls +it via the <a href="libftrigr.html">ftrigr library</a>, and notifies it when desired +events happen. +</p> + +<h2> Interface </h2> + +<p> + s6-ftrigrd is not meant to be called directly. +</p> + +<ul> + <li> If the client program uses <tt>ftrigr_startf()</tt>, it spawns an instance of +s6-ftrigrd as a child. s6-ftrigrd's stdin is a pipe reading from the client; its +stdout is a pipe writing to the client; its stderr is the same as the client's; and +there's an additional pipe from s6-ftrigrd to the client, used for asynchronous +notifications. </li> + <li> If the client program uses <tt>ftrigr_start()</tt>, then it tries to connect +to a Unix domain socket. A <em>ftrigrd service</em> should be listening to that +socket, i.e. a Unix domain superserver such as +<a href="http://www.skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a> +spawning a s6-ftrigrd program on every connection. Then a s6-ftrigrd instance is created +for the client. </li> + <li> When the client uses <tt>ftrigr_end()</tt>, or closes s6-ftrigrd's stdin in +any way, s6-ftrigrd exits 0. </li> +</ul> + +<p> + s6-ftrigrd handles the grunt work of creating fifos in a +<a href="fifodir.html">fifodir</a> for a subscriber. It also wakes up on every +event, and compares the chain of events it received on a given fifodir with the +client-provided regexp. If the chain of events matches the regexp, it notifies +the client. +</p> + +<h2> Notes </h2> + +<p> + The connection management between the client and s6-ftrigrd is entirely done +by the <a href="http://www.skarnet.org/software/skalibs/libunixonacid/skaclient.html">skaclient</a> +library. +</p> + +<p> + s6-ftrigrd is entirely asynchronous. It stores unread notifications into heap +memory; it can grow in size if there are a lot of events and the client fails +to read them. To avoid uncontrolled growth, make sure your client calls +<tt>ftrigr_update()</tt> as soon as <tt>ftrigr_fd()</tt> becomes readable. +</p> + +</body> +</html> diff --git a/doc/s6-log.html b/doc/s6-log.html new file mode 100644 index 0000000..0a525d6 --- /dev/null +++ b/doc/s6-log.html @@ -0,0 +1,508 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-log program</title> + <meta name="Description" content="s6: the s6-log program" /> + <meta name="Keywords" content="s6 command s6-log log logger logging multilog" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-log program </h1> + +<p> +s6-log is a reliable logging program with automated log rotation, similar to +daemontools' <a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>, +with full POSIX regular expression support. +</p> + +<h2> Interface </h2> + +<pre> + s6-log [ -q | -v ] [ -b ] [ -p ] [ -t ] [ -e ] <em>logging script</em> +</pre> + +<p> +s6-log reads and compiles <em>logging script</em> to an internal +form. Then it reads its standard input, line by line, and performs actions +on it, following the script it is given. It does its best to ensure there +is <em>never any log loss</em>. It exits cleanly when stdin closes or when +it receives SIGTERM. +</p> + +<h2> Options </h2> + +<ul> + <li> <tt>-b</tt> : blocking mode. With this option, s6-log stops +reading its standard input while it has unflushed buffers. This ensures that +every log line has been fully processed before reading the next one; this is also +<a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>'s behaviour. +By default, s6-log keeps reading from stdin even if its buffers still +contain data. <tt>-b</tt> is safer, but may slow down your service; the default +is faster, but may lead to unbound memory use if you have a lot of output to +write to a slow file system. </li> + <li> <tt>-p</tt> : protect. Do not exit on receipt of a SIGTERM; only +exit when reading EOF on stdin. </li> + <li> <tt>-t</tt> : timestamp. Prepends every log line that is written to +a logging directory with a +<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html">TAI64N</a> +timestamp. </li> + <li> <tt>-e</tt> : timestamp alerts. Prepends every "alert" line with a +TAI64N timestamp. </li> + <li> <tt>-q | -v</tt> : quiet | verbose. Decreases | increases s6-log's +verbosity, i.e. which messages are sent to stderr. The default verbosity is 1. +Currently supported verbosity levels: + <ul> + <li> 0: only write alerts and fatal errors </li> + <li> 1: write alerts, warnings and fatal errors </li> + </ul> </li> +</ul> + +<h2> Logdirs </h2> + +<p> +A <em>logdir</em> (<em>logging directory</em>) is a place where logs are +stored. s6-log can be scripted to write into one or more logdirs. +</p> + +<p> + A logdir may contain the following files: +</p> + +<ul> + <li> <tt>lock</tt>: this file is locked by s6-log at start, +to make sure only one instance is running at the same time. </li> + <li> <tt>current</tt>: the file where selected log lines are appended. +If <tt>current</tt> has the executable-by-user flag, it means that no +s6-log process is currently writing to it and the previous +s6-log process managed to cleanly finalize it. If it does not, +either a s6-log process is writing to it or the previous one +has been interrupted without finalizing it. </li> + <li> <tt>state</tt>: last processor's output, see below. </li> + <li> <tt>previous</tt>: a rotation is happening in that logdir. </li> + <li> <tt>processed</tt>, <tt>newstate</tt>: a rotation is happening +in that logdir and the processor script is running. </li> + <li> timestamped files: those files are @<em>timestamp</em>.s or +@<em>timestamp</em>.u and are old log files that have been processed +and rotated (if they're ending in .s) or that were the <tt>current</tt> +file when s6-log got interrupted (if they're ending in .u), in which +case they have not been processed. </li> +</ul> + +<h3> Rotation </h3> + +<p> + In a logdir, selected lines are appended to the <tt>current</tt> file. +When <tt>current</tt> becomes too big, a <em>rotation</em> happens. +The <tt>current</tt> file will be possibly <em>processed</em>, then +it will become an <em>archived</em> log file named +@<em>timestamp</em>.s, where <em>timestamp</em>, a +<a href="http://www.skarnet.org/software/skalibs/libstddjb/tai.html">TAI64N</a> +timestamp, is the absolute time of the rotation. If there are too +many archived log files in the logdir, the older ones are then +suppressed. Logging then resumes, to a brand new <tt>current</tt> +file. +</p> + +<p> + You can use this mechanism to ensure that your logs never fill up +the available disk space, for instance: something that neither +syslogd, nor syslog-ng, nor rsyslog offers. +</p> + +<h3> Processors </h3> + +<p> + A <em>processor</em> script can be set for every logdir. When a rotation +occurs, <tt>current</tt> (which has then been renamed <tt>previous</tt>) is +fed to <em>processor</em>'s stdin, and <em>processor</em>'s stdout is saved +and archived. <em>processor</em> can also read the <tt>state</tt> file +on its fd 4; what it writes to its fd 5 will be saved as the next +<tt>state</tt> file, for the next rotation. +</p> + +<p> + Processors should not background themselves: s6-log considers the +processing done when its <em>processor</em> direct child dies. +Processors should exit 0 on success and nonzero on failure; if a +processor fails, s6-log will try it again after some +<em>cooldown</em> time. +</p> + +<p> + Processors make s6-log Turing-complete by allowing you to use any +external program to handle log files that are going to be archived. +</p> + +<h2> Logging script syntax </h2> + +<p> + When starting up, s6-log reads its arguments one by one; this +argument sequence, or <em>directive sequence</em>, forms a +<em>logging script</em> which tells s6-log what to log, where, and how. +</p> + +<p> + Every directive can be a <em>selection</em> directive, a <em>control</em> +directive or an <em>action</em> directive. A valid logging script always +contains at least one action directive; every action directive can be +preceded by zero or more selection or control directives. s6-log will exit 100 +if the script is invalid. If it can process the script but the last directive +is not an action directive, s6-log will emit a warning. +</p> + +<h3> Selection directives </h3> + +<p> + These directives tell s6-log whether to select or deselect lines it reads +from stdin; actions will only happen on selected lines. By default, every +line is selected. +</p> + +<ul> + <li> <strong>+<em>regexp</em></strong>: select yet-unselected lines that match <em>regexp</em>, which must be a +<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">POSIX +Extended Regular Expression</a>. </li> + <li> <strong>-<em>regexp</em></strong>: deselect yet-selected lines that match <em>regexp</em>, which must be a +POSIX Extended Regular Expression. </li> + <li> <strong>f</strong>: select exactly lines that have not yet been acted +upon (i.e. that were always deselected when the script encountered an action +directive). </li> +</ul> + +<h3> Control directives </h3> + +<p> + These directives tune s6-log's behaviour for the next actions. +</p> + +<ul> + <li> <strong>n<em>number</em></strong>: next logdirs will contain up to +<em>number</em> archived log files. If there are more, the oldest archived +log files will be suppressed, only the latest <em>number</em> will be kept. +By default, <em>number</em> is 10. </li> + <li> <strong>s<em>filesize</em></strong>: next rotations will occur when +<tt>current</tt> log files approach <em>filesize</em> bytes. By default, +<em>filesize</em> is 99999; it cannot be set lower than 4096 or +higher than 16777215. </li> + <li> <strong>S<em>totalsize</em></strong>: next logdirs will contain up +to <em>totalsize</em> bytes of archived (and maybe processed) log files. If +archived log files take more space than that, the older ones are suppressed +until the total size fits. A <em>totalsize</em> of zero means no such limit; +use <strong>n0</strong> instead if you don't want any archived log files. By +default, <em>totalsize</em> is 0 (unlimited). </li> + <li> <strong>l<em>tolerance</em></strong>: next rotations will be triggered +when the size of <tt>current</tt> goes higher than <em>filesize</em> minus +<em>tolerance</em>. <em>tolerance</em> cannot be more than half of +<em>filesize</em>. By default, <em>tolerance</em> is 2000. </li> + <li> <strong>r<em>cooldown</em></strong>: if an error occurs during operations +on the next logdirs, retry every <em>cooldown</em> milliseconds. By default, +<em>cooldown</em> is 2000; it's strongly discouraged to set it to a value +under 50. </li> + <li> <strong>E<em>alertsize</em></strong>: only the first <em>alertsize</em> +bytes of the selected lines will be used in the next alerts. An +<em>alertsize</em> of 0 means no limit. By default, <em>alertsize</em> is +200. </li> + <li> <strong>^<em>statussize</em></strong>: only the first <em>statussize</em> +bytes of the selected lines will be used in the next status file updates. +If a line is shorter than <em>statussize</em> bytes, the status file will be +padded with newlines so it is always <em>statussize</em> bytes long. 0 means +an unpadded, unlimited status file. By default, <em>statussize</em> is 1001. </li> + <li> <strong>!<em>processor</em></strong>: registers +<tt>execlineb -Pc <em>processor</em></tt> as a processor for the next logdirs; +<tt>execlineb</tt> must be found in s6-log's PATH. +If <em>processor</em> is empty, no processor will be set for the next logdirs. +By default, no processor is set. </li> +</ul> + +<h3> Action directives </h3> + +<p> + These directives determine what s6-log actually <em>does</em> with the +selected lines. +</p> + +<ul> + <li> <strong>e</strong>: alert. s6-log will print "s6-log: alert: ", +possibly prepended with a timestamp, followed by the first +<em>alertsize</em> bytes of the line. </li> + <li> <strong>=<em>statusfile</em></strong>: status. s6-log will atomically +update the <em>statusfile</em> file with the first <em>statussize</em> +bytes of the line, and pad it with newlines. s6-log must have the right +to write to <em>statusfile</em> and to <em>statusfile</em>'s directory. </li> + <li> <strong><em>dir</em></strong> (must start with '/' or '.'): logdir. +s6-log will log the line into the logdir <em>dir</em>. s6-log must have +the right to write to <em>dir</em>.</li> +</ul> + + +<h2> Signals </h2> + +<ul> + <li> SIGTERM: If s6-log has been run with the <tt>-p</tt> option, does nothing. +Without this option, SIGTERM instructs s6-log to stop reading stdin after the +next newline and exit after logging the last line. </li> + <li> SIGALRM: triggers a rotation on every logdir s6-log is monitoring, +as if the <tt>current</tt> file in those logdirs had reached the size +limit. </li> +</ul> + +<h2> Examples </h2> + +<pre> + s6-log -bt n20 s1000000 /var/log/services/stuff +</pre> + +<p> + Logs all of stdin, prepending every line with a timestamp, into the +<tt>/var/log/services/stuff</tt> logdir, with a maximum archive of +20 log files of 1 MB each; makes sure every line has been written +before reading the next one. +</p> + +<pre> + s6-log -t n30 E500 - +fatal: e - +^STAT =/var/log/foobard/status f s10000000 S15000000 !"gzip -nq9" /var/log/foobard +</pre> + +<ul> + <li> Sends alerts to stderr with the 500 first bytes of lines containing "fatal:". </li> + <li> Maintains the <tt>/var/log/foobard/status</tt> file at 1001 bytes, +updating it when it finds a log line starting with "STAT". </li> + <li> Logs all other lines to logdir <tt>/var/log/foobard</tt>. When <tt>current</tt> +reaches at least 9998 kB (i.e. 10 MB filesise minus 2kB tolerance), pipe it +through <tt>gzip -nq9</tt> and save the result into a timestamped archive file, with +a maximum of 30 such files or a total of 15 MB of compressed archive files. </li> +</ul> + + +<h2> Why use execlineb to interpret the "processor" string ? </h2> + +<p> + Because it is <em>exactly</em> what +<a href="http://skarnet.org/software/execline/execlineb.html">execlineb</a> +is for. +</p> + +<ul> + <li> Directly executing <em>processor</em> is not flexible enough. We want +to be able to run a complete command line, with an executable name and its +arguments. </li> + <li> We could interpret the <em>processor</em> string via <tt>/bin/sh</tt>. +This is what <a href="http://cr.yp.to/daemontools/multilog.html">multilog</a> +does. However, <tt>/bin/sh</tt>, despite being the traditional Unix interpreter, +is overpowered for this. We don't need a complete shell script interpreter: +most <em>processor</em> commands will be very simple, with only two or three +words, and we only need a way to turn a string into an <em>argv</em>, i.e. a +command line. </li> + <li> <a href="http://www.skarnet.org/software/execline/execlineb.html">execlineb</a> +was designed just for this: to turn simple strings into command lines. +It is a very fast and lightweight script launcher, that does not do any heavy +startup initialization like <tt>/bin/sh</tt> does. It happens to be the perfect +tool for the job. </li> + <li> Yes, I also did this on purpose so people have a reason to use the +<a href="http://www.skarnet.org/software/execline/">execline</a> language. Do not +look at me like that: it <em>really</em> is the perfect tool for the job. </li> +</ul> + +<h2> Why have another logging mechanism ? </h2> + +<p> + Because the syslog mechanism and all its implementations (save one) suck. +I'm not being judgmental; I'm just stating the obvious. +</p> + +<a name="diesyslogdiedie"><h3> The syslog design is flawed from the start </h3></a> + +<p> +<a href="http://blog.gerhards.net/2007/08/why-does-world-need-another-syslogd.html">When +asked why he started rsyslog</a>, Rainer Gerhards came up with a lot of +hand-waving and not a single word about technical points. There is a +reason for that: rsyslog is forked from sysklogd! So, no matter how +many bells and whistles are added to it, it still suffers from the same +basic flaws. +</p> + +<p> + The problem with syslogd does not come from such or such implementation. +The problem comes from syslog's <em>design</em> in the first place. +</p> + +<ul> + <li> syslog makes you send <em>all</em> your logs to the same place. +The logs from a zillion processes are read by a single syslogd server. +The server checks log lines against system-wide regular expressions +to decide where to write them. This raises the following issues: + <ul> + <li> Unless the client explicitly mentions its name in every log +line, there is no way for log readers to know what process generated a +given line. Some syslogd implementations can log the pid of the client; +big deal. </li> + <li> Log lines from every client have to run through the same regular +expression matching. This requires huge regular expression sets, and an +obvious performance impact, to do anything meaningful. And as a matter +of fact, standard syslogd configurations don't do anything meaningful: +they separate the logs into a few streams such as <tt>/var/log/messages</tt>, +<tt>/var/log/auth.log</tt>, <tt>/var/log/daemon.log</tt> or +<tt>/var/log/syslog</tt> with very vague semantics. All of syslogd's +line processing power remains unused, because making real use of it would +be too complex. </li> + </ul> + <li> syslogd logs to <em>files</em>. This is wrong, because files grow +and disks fill up. Sure, there are utilities such as <tt>logrotate</tt> +to perform cleaning up, but as long as logging and log rotation are +kept separate, there is a race condition: a log file can grow and fill +up a disk before a rotation occurs. I am all for separating tasks that +can be separated, but there is no choice here: <em>logging and log +rotation management must be done <strong>by the same tool</strong></em>. +Only the Busybox implementation of syslogd does this, and that is a +feature added by the Busybox developers who are aware of the problem +but want to maintain compatibility with the historical syslogd. +No other syslogd implementation I know of manages its log files: that's a +flaw that no amount of external tools is going to fix. </li> + <li> syslogd is a complex process that runs as root. We all know what +complex processes running as root mean: bugs turning into security holes. </li> + <li> syslog requires a syslogd service, and fails otherwise. A syslogd +service may not be present, it may fail... or it may want to log stuff. +Who's going to take care of syslogd's error messages ? </li> +</ul> + +<p> + syslog is slow, it's unsafe, and it's incomplete. The only reason people +use it is because it's historical, it exists, and there hasn't been any +serious alternative yet, except maybe +<a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>, which +s6-log improves upon. +</p> + +<a name="loggingchain"><h3> A not-so-modest proposal: the logging chain </h3></a> + +<p> + Unix distributions already do this to some extent, but it's at best +unclear where the logs go for any given program. +</p> + +<ul> + <li> Every program, without exception, should send its logs (be it +error messages, warning messages, or anything) to its <em>standard +error descriptor</em>, i.e. fd 2. <em>This is why it's open for.</em> </li> + <li> When process 1 starts, the logging chain is rooted to the +<em>machine console</em>: anything process 1 sends to its stderr +appears, without modification, on the machine console, which should +at any time remain the last resort place where logs are sent. </em> + <li> Process 1 should spawn and supervise a <em>catch-all logging +mechanism</em> that handles logs from every service that does not +take care of its own logging. Error messages from this logging +mechanism naturally go to the machine console. </li> + <li> Process 1's own error messages can go to the machine console, +or <a href="s6-svscan-1.html#log">dirty tricks can be used</a> so they +go to the catch-all logging mechanism. </li> + <li> Services that are spawned by process 1 should come with their +own logger service; the supervision mechanism offered by +<a href="s6-svscan.html">s6-svscan</a> makes it easy. Error messages +from the loggers themselves naturally go to the catch-all +mechanism. </li> + <li> User login mechanisms such as <tt>getty</tt>, <tt>xdm</tt> or +<tt>sshd</tt> are services: they should be started with their own +loggers. Of course, when a user gets a terminal and a shell, the +shell's stderr should be redirected to the terminal: interactive +programs break the automatic logging chain and delegate responsibility +to the user. </li> + <li> A syslogd service <em>may</em> exist, to catch logs sent via +syslog() by legacy programs. But it is a normal service, and logs +caught by this syslogd service are not part of the logging chain. + It is probably overkill to provide the syslogd service with its own +logger; error messages from syslogd can default to the catch-all logger. + The s6 package, including the <a href="ucspilogd.html">ucspilogd</a> program, +combined with the +<a href="http://skarnet.org/software/s6-networking/">s6-networking</a> +package, provides enough tools to easily implement +a complete syslogd system, for a small fraction of the resource needs and +the complexity of native syslogd implementations. </li> +</ul> + +<p> + So, given a program, where are its logs sent ? +</p> + +<ul> + <li> Logs sent via syslog() will be handled by the syslogd service as +usual. Smart administrators will make sure that those ones are as few as +possible. The rest of this analysis is about logs sent to stderr. </li> + <li> If the program is descended from a user's interactive program, +its logs are sent to the user's terminal or the user's choice redirection +target. </li> + <li> If the program is descended from a logged service, its logs are +naturally sent to the service's logger. </li> + <li> Else the logs are sent to the catch-all logger. </li> + <li> Only the catch-all logger's error messages, the kernel's fatal +error messages, and maybe process 1's error messages, are sent to the +system console. </li> +</ul> + +<a name="#howtouse"><h3> What does s6-log have to do with all this ? </h3></a> + +<p> + In a <em>logging chain</em> situation, every service must have +its own logger. To avoid syslogd's design mistakes, one logger process +per service must be run. s6-log fits that role. Using s6-log as +your one-stop logger offers the following benefits: +</p> + +<ul> + <li> Every instance of s6-log can run as a different user, so it's +easy to give different access rights to different logs. It is also +more secure not to have any logger process running as root. </li> + <li> s6-log consumes very little memory per instance (unless it +accumulates unflushed log lines, which you can avoid with the +<tt>-b</tt> option). So, launching a lot of s6-log processes does +not waste resources. </li> + <li> s6-log is vastly configurable via logging scripts; every instance +is as powerful as a traditional syslogd. </li> + <li> s6-log can log to a RAM filesystem and thus is suitable as a +catch-all logger. Clever tricks like Upstart's <em>logd</em> or daemontools' +<a href="http://cr.yp.to/daemontools/readproctitle.html">readproctitle</a> +are just that: tricks. s6-log gives a unified interface to all of +your system's loggers. </li> +</ul> + + +<a name="#network"><h3> You're wrong about being as powerful as +syslogd: s6-log does not do remote logging. </h3></a> + +<p> + You mean you want to send, <em>live</em>, every <em>log line</em> +over the network via <em>UDP</em> ? You can't be serious. +</p> + +<p> + Do yourself a favor and use s6-log to write log lines to a logdir, +with a processor script that sends files-being-archived to the +network, possibly after compressing them. More reliability, less +log lines lost, less network traffic, better engineering. If you +have no disk to even write the <tt>current</tt> files to, write +to a small RAM filesystem. +</p> + +<p> + If you <em>have to</em> log stuff <em>live</em> via the network, you +do not need any local logging software. You don't even need syslogd. +Just filter your stderr via some <tt>grep</tt> that selects lines for +you, then sends them to a network socket. A trivial shell script, or +<a href="http://skarnet.org/software/execline/">execline</a> +script, can do that for you. +</p> + +<p> + Do not insist on using syslogd. It does nothing magical, and nothing +that can't be done in a simpler way using simpler tools. +</p> + +</body> +</html> diff --git a/doc/s6-mkfifodir.html b/doc/s6-mkfifodir.html new file mode 100644 index 0000000..818a514 --- /dev/null +++ b/doc/s6-mkfifodir.html @@ -0,0 +1,46 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-mkfifodir program</title> + <meta name="Description" content="s6: the s6-mkfifodir program" /> + <meta name="Keywords" content="s6 command s6-mkfifodir fifodir notification creation" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-mkfifodir program </h1> + +<p> +s6-mkfifodir creates a <a href="fifodir.html">fifodir</a>. +</p> + +<h2> Interface </h2> + +<pre> + s6-mkfifodir [ -f ] [ -g <em>gid</em> ] <em>fifodir</em> +</pre> + +<p> +s6-mkfifodir creates <em>fifodir</em>, belonging to the current user. +</p> + +<h2> Options </h2> + +<ul> + <li> <tt>-f</tt> : force permissions. If <em>fifodir</em> already exists, +change its permissions according to the <tt>-g</tt> options. By default, if +<em>fifodir</em> exists, s6-mkfifodir does nothing. </li> + <li> <tt>-g <em>gid</em></tt> : make <em>fifodir</em> only listenable +by members of group <em>gid</em>. If this option is not given, the fifodir is +made publically listenable. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-notifywhenup.html b/doc/s6-notifywhenup.html new file mode 100644 index 0000000..ad7ef8e --- /dev/null +++ b/doc/s6-notifywhenup.html @@ -0,0 +1,80 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-notifywhenup program</title> + <meta name="Description" content="s6: the s6-notifywhenup program" /> + <meta name="Keywords" content="s6 command s6-notifywhenup fifodir notification event notifier send service daemon ready" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-notifywhenup program </h1> + +<p> +s6-notifywhenup launches a daemon while listening to a file descriptor, +and sends a 'U' event to a <a href="fifodir.html">fifodir</a> when it +receives something on that file descriptor. +</p> + +<p> +<a href="notifywhenup.html">This page</a> explains why this program is +needed. +</p> + +<h2> Interface </h2> + +<pre> + s6-notifywhenup [ -d <em>fd</em> ] [ -e <em>fifodir</em> ] [ -f ] [ -t <em>timeout</em> ] <em>prog...</em> +</pre> + +<ul> + <li> s6-notifywhenup forks and executes <em>prog...</em> as the +parent, with a pipe from <em>prog...</em>'s stdout to the child. </li> + <li> The child waits for EOF on the pipe. When it gets it, if there +have been other characters written before the EOF, it sends a 'U' +event to the <tt>./event</tt> fifodir. </li> + <li> The child exits 0. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-d <em>fd</em></tt> : listen to +<em>prog</em>'s output on descriptor <em>fd</em>. The default is 1. </li> + <li> <tt>-e <em>fifodir</em></tt> : send a 'U' event to fifodir +<em>fifodir</em>. Default is <tt>./event</tt>. </li> + <li> <tt>-f</tt> : simple fork. Normally, s6-notifywhenup doubleforks, +in order to accommodate for a +<em>prog</em> that does not expect to have a child and avoid a +pending zombie. This option avoids the doublefork, but it should only be +set if <em>prog</em> reaps even children it doesn't know it has. </li> + <li> <tt>-t <em>timeout</em></tt> : if no EOF has been received +after <em>timeout</em> milliseconds, exit without sending the event. +Default is 0, meaning infinite. </li> +</ul> + +<h2> Notes </h2> + +<ul> + <li> s6-notifywhenup executes <em>prog...</em> as the parent in order +for <em>prog</em> to keep the same pid, which is vital for supervised +processes. </li> + <li> s6-notifywhenup can be used, for instance, with +<a href="http://skarnet.org/software/s6-networking/s6-tcpserver.html">s6-tcpserver</a> +and its <tt>-1</tt> option, so that reliable startup notification is +achieved. <tt>s6-notifywhenup -f s6-tcpserver -1 <em>args</em></tt> will +send a 'U' event to <tt>./event</tt> when s6-tcpserver is actually +listening to its network socket. </li> + <li> The <a href="s6-svwait.html">s6-svwait</a> program can be used +to wait for 'U' events. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-setlock.html b/doc/s6-setlock.html new file mode 100644 index 0000000..bab3e23 --- /dev/null +++ b/doc/s6-setlock.html @@ -0,0 +1,65 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-setlock program</title> + <meta name="Description" content="s6: the s6-setlock program" /> + <meta name="Keywords" content="s6 command s6-setlock lock program" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-setlock program </h1> + +<p> +s6-setlock takes a lock on a file, then executes into another program. +</p> + +<h2> Interface </h2> + +<pre> + s6-setlock [ -n | -N | -t <em>timeout</em> ] [ -r | -w ] <em>file</em> <em>prog...</em> +</pre> + +<ul> + <li> s6-setlock creates <em>file</em> if it does not exist and opens it for writing. </li> + <li> It locks <em>file</em>. If it cannot take the lock for any reason, it exits 1. </li> + <li> It executes into <em>prog...</em>. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-n</tt> : nonblocking lock. If s6-setlock cannot acquire the lock, it will +exit 1 immediately. </li> + <li> <tt>-N</tt> : blocking lock. s6-setlock will wait until it can acquire the lock. +This is the default. </li> + <li> <tt>-t <em>timeout</em> : timed lock. If s6-setlock cannot acquire +the lock after <em>timeout</em> milliseconds, it will exit 1. </li> + <li> <tt>-r</tt> : shared lock. Other shared locks on the same file will not prevent +the lock from being acquired (but an exclusive lock will). </li> + <li> <tt>-w</tt> : exclusive lock. This is the default. </li> +</ul> + +<h2> Notes </h2> + +<ul> + <li> s6-setlock leaks an open file descriptor into the <em>prog</em> +execution. This is intended: the fd holds the lock, which is released +when <em>prog</em> exits. <em>prog</em> must not touch fds it does not +know about. </li> + <li> If the timed lock option is chosen, s6-setlock does not acquire the lock +itself. Instead, it spawns a <a href="libs6lock/s6lockd-helper.html">s6lockd-helper</a> +process that acquires the lock while s6-setlock controls the timeout; the +s6lockd-helper process then holds the lock and lives as long as +<em>prog</em>. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-setsid.html b/doc/s6-setsid.html new file mode 100644 index 0000000..2fe991b --- /dev/null +++ b/doc/s6-setsid.html @@ -0,0 +1,47 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-setsid program</title> + <meta name="Description" content="s6: the s6-setsid program" /> + <meta name="Keywords" content="s6 command s6-setsid session leader setting" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-setsid program </h1> + +<p> +s6-setsid runs a program as session leader. +</p> + +<h2> Interface </h2> + +<pre> + s6-setsid [ -I | -i ] <em>prog...</em> +</pre> + +<ul> + <li> s6-setsid creates a new session if it is not a session leader, and becomes +session leader of this new session. </li> + <li> It then executes into <em>prog...</em>. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-i</tt> : strict. If s6-setsid is already a session leader, it will +exit 111 with an error message. </li> + <li> <tt>-I</tt> : loose. If s6-setsid is already a session leader, it will +print a warning message, but exec into <em>prog</em> nonetheless. This is the +default. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-setuidgid.html b/doc/s6-setuidgid.html new file mode 100644 index 0000000..f12ae04 --- /dev/null +++ b/doc/s6-setuidgid.html @@ -0,0 +1,49 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-setuidgid program</title> + <meta name="Description" content="s6: the s6-setuidgid program" /> + <meta name="Keywords" content="s6 command s6-setuidgid uid gid groups privilege dropping loss user change su" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-setuidgid program </h1> + +<p> +s6-setuidgid executes a program as another user. +</p> + +<h2> Interface </h2> + +<pre> + s6-setuidgid <em>account</em> <em>prog...</em> +</pre> + +<ul> + <li> If <em>account</em> contains a colon, it is interpreted as <em>uid:gid</em>, +else it is interpreted as a username and looked up by name in the account +database. </li> + <li> If <em>account</em> is unknown, s6-setuidgid exits 1. </li> + <li> s6-setuidgid sets its (real and effective) uid and gid to those of <em>account</em>. +It also sets the list of supplementary groups to the correct one for <em>account</em> +according to the group database. </li> +<li> Then it executes into <em>prog...</em>. </li> +</ul> + +<h2> Notes </h2> + +<p> + s6-setuidgid can only be run as root. Its main use is to drop root privileges before +starting a daemon. +</p> + +</body> +</html> diff --git a/doc/s6-softlimit.html b/doc/s6-softlimit.html new file mode 100644 index 0000000..4b112f8 --- /dev/null +++ b/doc/s6-softlimit.html @@ -0,0 +1,54 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-softlimit program</title> + <meta name="Description" content="s6: the s6-softlimit program" /> + <meta name="Keywords" content="s6 command s6-softlimit process limits" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-softlimit program </h1> + +<p> +s6-softlimit changes its process limits, then executes into another program. +</p> + +<h2> Interface </h2> + +<pre> + s6-softlimit [ -a <em>allmem</em> ] [ -c <em>core</em> ] [ -d <em>data</em> ] [ -f <em>fsize</em> ] [ -l <em>lock</em> ] [ -m <em>mem</em> ] [ -o <em>ofiles</em> ] [ -p <em>proc</em> ] [ -r <em>res</em> ] [ -s <em>stack</em> ] [ -t <em>cpusecs</em> ] <em>prog...</em> +</pre> + +<ul> + <li> s6-softlimit parses its options and sets process (soft) resource limits accordingly. </li> + <li> A value of '=' for any option means "set that limit to the hard limit". </li> + <li> Depending on your operating system, an option may do nothing. </li> + <li> When s6-softlimit has modified all the limits successfully, it executes into <em>prog...</em>. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-a <em>allmem</em></tt> : limit the total available memory to <em>allmem</em> bytes. </li> + <li> <tt>-c <em>core</em></tt> : limit the core file size to <em>core</em> bytes. </li> + <li> <tt>-d <em>data</em></tt> : limit the available heap memory to <em>data</em> bytes. </li> + <li> <tt>-f <em>fsize</em></tt> : limit the file size to <em>fsize</em> bytes. </li> + <li> <tt>-l <em>lock</em></tt> : limit the available locked memory to <em>lock</em> bytes. </li> + <li> <tt>-m <em>mem</em></tt> : limit all types of memory to <em>mem</em> bytes. </li> + <li> <tt>-o <em>ofiles</em></tt> : limit the number of open fds to <em>ofiles</em>. </li> + <li> <tt>-p <em>proc</em></tt> : limit the number of processes to <em>proc</em> (per user). </li> + <li> <tt>-r <em>allmem</em></tt> : limit the available physical memory to <em>res</em> bytes. </li> + <li> <tt>-s <em>stack</em></tt> : limit the available stack memory to <em>stack</em> bytes. </li> + <li> <tt>-t <em>cpusecs</em></tt> : limit the available CPU time to <em>cpusecs</em> seconds. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-supervise.html b/doc/s6-supervise.html new file mode 100644 index 0000000..1c1551e --- /dev/null +++ b/doc/s6-supervise.html @@ -0,0 +1,125 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-supervise program</title> + <meta name="Description" content="s6: the s6-supervise program" /> + <meta name="Keywords" content="s6 command s6-supervise servicedir supervision supervise" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-supervise program </h1> + +<p> +s6-supervise monitors a long-lived process (or <em>service</em>), making sure it +stays alive, sending notifications to registered processes when it dies, and +providing an interface to control its state. s6-supervise is designed to be the +last non-leaf branch of a <em>supervision tree</em>, the supervised process +being a leaf. +</p> + +<h2> Interface </h2> + +<pre> + s6-supervise <em>servicedir</em> +</pre> + +<ul> + <li> s6-supervise switches to the <em>servicedir</em> +<a href="servicedir.html">service directory</a>. </li> + <li> It exits 100 if another s6-supervise process is already monitoring this service. </li> + <li> If the <tt>./event</tt> <a href="fifodir.html">fifodir</a> does not exist, +s6-supervise creates it and allows public subscriptions to it. +If it already exists, it uses it as is, without modifying the subscription rights. </li> + <li> It <a href="libftrigw.html">sends</a> a <tt>'s'</tt> event to <tt>./event</tt>. </li> + <li> If the default service state is up, s6-supervise spawns <tt>./run</tt>. </li> + <li> s6-supervise sends a <tt>'u'</tt> event to <tt>./event</tt> whenever it +successfully spawns <tt>./run</tt>. </li> + <li> When <tt>./run</tt> dies, s6-supervise sends a <tt>'d'</tt> event to <tt>./event</tt>. </li> + <li> When <tt>./run</tt> dies, s6-supervise spawns <tt>./finish</tt> if it exists. </li> + <li> <tt>./finish</tt> must exit in less than 5 seconds. If it takes more than that, +s6-supervise kills it. </li> + <li> When <tt>./finish</tt> dies, s6-supervise restarts <tt>./run</tt> unless it has been +told not to. </li> + <li> There is a minimum 1-second delay between two <tt>./run</tt> spawns, to avoid busylooping +if <tt>./run</tt> exits too quickly. </li> + <li> When killed or asked to exit, it waits for the service to go down one last time, then +sends a <tt>'x'</tt> event to <tt>./event</tt> before exiting 0. </li> +</ul> + +<h2> Signals </h2> + +<p> + s6-supervise reacts to the following signals: +</p> + +<ul> + <li> SIGTERM: bring down the service and exit, as if a +<a href="s6-svc.html">s6-svc -xd</a> command had been received </li> + <li> SIGHUP: exit as soon as the service stops, as if a +<a href="s6-svc.html">s6-svc -x</a> command had been received </li> + <li> SIGQUIT: currently like SIGTERM, but this might change in the future </li> +</ul> + +<h2> Usage notes </h2> + +<ul> + <li> s6-supervise is a long-lived process. It normally runs forever, from the system's +boot scripts, until shutdown time; it should not be killed or told to exit. If you have +no use for a service, just turn it off; the s6-supervise process does not hurt. </li> + <li> Even in boot scripts, s6-supervise should normally not be run directly. It's +better to have a collection of <a href="servicedir.html">service directories</a> in a +single <a href="scandir.html">scan directory</a>, and just run +<a href="s6-svscan.html">s6-svscan</a> on that scan directory. s6-svscan will spawn +the necessary s6-supervise processes, and will also take care of logged services. </li> + <li> You can use <a href="s6-svc.html">s6-svc</a> to send commands to the s6-supervise +process; mostly to change the service state and send signals to the monitored +process. </li> + <li> You can use <a href="s6-svok.html">s6-svok</a> to check whether s6-supervise +is successfully running. </li> + <li> You can use <a href="s6-svstat.html">s6-svstat</a> to check the status of a +service. </li> + <li> s6-supervise maintains internal information inside the <tt>./supervise</tt> +subdirectory of <em>servicedir</em>. <em>servicedir</em> itself can be read-only, +but both <em>servicedir</em><tt>/supervise</tt> and <em>servicedir</em><tt>/event</tt> +need to be read-write. </li> + <li> The <tt>./finish</tt> script is not guaranteed to have stdin and +stdout pointing to the same locations as the <tt>./run</tt> script. More +precisely: the stdin and stdout will be preserved for <tt>./finish</tt> +until s6-supervise is asked to exit, but the last <tt>./finish</tt> +execution will have its stdin and stdout redirected to <tt>/dev/null</tt>. +(This is to avoid maintaining open descriptors when a service is down, which +would prevent its logger from exiting cleanly.) </li> +</ul> + +<h2> Implementation notes </h2> + +<ul> + <li> s6-supervise tries its best to stay alive and running despite possible +system call failures. It will write to its standard error everytime it encounters a +problem. However, unlike <a href="s6-svscan.html">s6-svscan</a>, it will not go out +of its way to stay alive; if it encounters an unsolvable situation, it will just +die. </li> + <li> Unlike other "supervise" implementations, s6-supervise is a fully asynchronous +state machine. That means that it can read and process commands at any time, even +when the machine is in trouble (full process table, for instance). </li> + <li> s6-supervise <em>does not use malloc()</em>. That means it will <em>never leak +memory</em>. <small>However, s6-supervise uses opendir(), and most opendir() +implementations internally use heap memory - so unfortunately, it's impossible to +guarantee that s6-supervise does not use heap memory at all.</small> </li> + <li> s6-supervise has been carefully designed so every instance maintains as little +data as possible, so it uses a very small +amount of non-sharable memory. It is not a problem to have several +dozens of s6-supervise processes, even on constrained systems: resource consumption +will be negligible. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-svc.html b/doc/s6-svc.html new file mode 100644 index 0000000..72f0776 --- /dev/null +++ b/doc/s6-svc.html @@ -0,0 +1,104 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-svc program</title> + <meta name="Description" content="s6: the s6-svc program" /> + <meta name="Keywords" content="s6 command s6-svc supervise command service" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-svc program </h1> + +<p> +s6-svc sends commands to a running <a href="s6-supervise.html">s6-supervise</a> +process. In other words, it's used to control a supervised process; among +other benefits, it allows an administrator to send signals to daemons without +knowing their PIDs, and without using horrible hacks such as .pid files. +</p> + +<h2> Interface </h2> + +<pre> + s6-svc [ -abqhkti12fFpcoduxO ] <em>servicedir</em> +</pre> + +<p> +s6-svc sends the given series of commands to the +<a href="s6-supervise.html">s6-supervise</a> process monitoring the +<em>servicedir</em> directory, then exits 0. It exits 111 if it cannot send +a command, or 100 if no s6-supervise process is running on <em>servicedir</em>. +</p> + +<h2> Options </h2> + +<ul> + <li> <tt>-a</tt> : send a SIGALRM to the supervised process </li> + <li> <tt>-b</tt> : send a SIGABRT to the supervised process </li> + <li> <tt>-q</tt> : send a SIGQUIT to the supervised process </li> + <li> <tt>-h</tt> : send a SIGHUP to the supervised process </li> + <li> <tt>-k</tt> : send a SIGKILL to the supervised process </li> + <li> <tt>-t</tt> : send a SIGTERM to the supervised process </li> + <li> <tt>-i</tt> : send a SIGINT to the supervised process </li> + <li> <tt>-1</tt> : send a SIGUSR1 to the supervised process </li> + <li> <tt>-2</tt> : send a SIGUSR2 to the supervised process </li> + <li> <tt>-p</tt> : send a SIGSTOP to the supervised process </li> + <li> <tt>-c</tt> : send a SIGCONT to the supervised process </li> + <li> <tt>-o</tt> : once. Equivalent to "-uO". </li> + <li> <tt>-d</tt> : down. If the supervised process is up, send it +a SIGTERM and a SIGCONT. Do not restart it. </li> + <li> <tt>-u</tt> : up. If the supervised process is down, start it. +Automatically restart it when it dies. </li> + <li> <tt>-x</tt> : exit. When the service is asked to be down and +the supervised process dies, supervise will exit too. This command should +normally never be used on a working system. </li> + <li> <tt>-O</tt> : Once at most. Do not restart the supervised process +when it dies. If it is down when the command is received, do not even start +it. </li> + <li> <tt>-f</tt>, <tt>-F</tt> : unused for now. </li> +</ul> + +<h2> Usage examples </h2> + +<pre> s6-svc -h /service/httpd </pre> +<p> + Send a SIGHUP to the process represented by the <tt>/service/httpd</tt> +service directory. Traditionally, this makes web servers reload their +configuration file. +</p> + +<pre> s6-svc -t /service/sshd </pre> +<p> + Kill (and automatically restart, if the wanted state of the service is up) +the process represented by the <tt>/service/sshd</tt> service directory - +typically the sshd server. +</p> + +<pre> s6-svc -d /service/ftpd </pre> +<p> + Take down the ftpd server. +</p> + +<pre> s6-svc -a /service/httpd/log </pre> +<p> + Send a SIGALRM to the logger process for the httpd server. If this logger +process is <a href="s6-log.html">s6-log</a>, this triggers a log rotation. +</p> + +<h2> Internals </h2> + +<p> +s6-svc writes control commands into the <tt><em>servicedir</em>/supervise/control</tt> +FIFO. A s6-supervise process running on <em>servicedir</em> will be listening to this FIFO, +and will read and interpret those commands. +</p> + +</body> +</html> diff --git a/doc/s6-svok.html b/doc/s6-svok.html new file mode 100644 index 0000000..a43d253 --- /dev/null +++ b/doc/s6-svok.html @@ -0,0 +1,38 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-svok program</title> + <meta name="Description" content="s6: the s6-svok program" /> + <meta name="Keywords" content="s6 command s6-svok servicedir checking supervision s6-supervise" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-svok program </h1> + +<p> +s6-svok checks whether a <a href="servicedir.html">service directory</a> is +currently supervised. +</p> + +<h2> Interface </h2> + +<pre> + s6-svok <em>servicedir</em> +</pre> + +<ul> +<li> s6-svok checks whether a <a href="s6-supervise.html">s6-supervise</a> +process is currently monitoring <em>fifodir</em>. </li> +<li> It exits 0 if there is one, or 1 if there is none. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-svscan-1.html b/doc/s6-svscan-1.html new file mode 100644 index 0000000..76bc31c --- /dev/null +++ b/doc/s6-svscan-1.html @@ -0,0 +1,374 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: How to run s6-svscan as process 1</title> + <meta name="Description" content="s6: s6-svscan as init" /> + <meta name="Keywords" content="s6 supervision svscan s6-svscan init process boot 1" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> How to run s6-svscan as process 1 </h1> + +<p> + It is possible to run s6-svscan as process 1, i.e. the <tt>init</tt> +process. However, that does not mean you can directly <em>boot</em> +on s6-svscan; that little program cannot do everything +your stock init does. Replacing the <tt>init</tt> process requires a +bit of understanding of what is going on. +</p> + +<a name="stages"> +<h2> The three stages of init </h2> +</a> + +<p> + The life of a Unix machine has three stages: +</p> + +<ol> + <li> The <em>early initialization</em> phase. It starts when the +kernel launches the first userland process, traditionally called <tt>init</tt>. +During this phase, init is the only lasting process; its duty is to +prepare the machine for the start of <em>other</em> long-lived processes, +i.e. services. Work such as mounting filesystems, setting the system clock, +etc. can be done at this point. This phase ends when process 1 launches +its first services. </li> + <li> The <em>cruising</em> phase. This is the "normal", stable state of an +up and running Unix machine. Early work is done, and init launches and +maintains <em>services</em>, i.e. long-lived processes such as gettys, +the ssh server, and so on. During this phase, init's duties are to reap +orphaned zombies and to supervise services - also allowing the administrator +to add or remove services. This phase ends when the administrator +requires a shutdown. </li> + <li> The <em>shutdown</em> phase. Everything is cleaned up, services are +stopped, filesystems are unmounted, the machine is getting ready to be +halted. During this phase, everything but the shutdown procedure gets +killed - the only surefire way to kill everything is <tt>kill -9 -1</tt>, +and only process 1 can survive it and keep working: it's only logical +that the shutdown procedure, or at least the shutdown procedure from +the <tt>kill -9 -1</tt> on and until the final poweroff or reboot +command, is performed by process 1. </li> +</ol> + +<p> + As you can see, process 1's duties are <em>radically different</em> from +one stage to the next, and init has the most work when the machine +is booting or shutting down, which means a normally negligible fraction +of the time it is up. The only common thing is that at no point is process +1 allowed to exit. +</p> + +<p> + Still, all common init systems insist that the same <tt>init</tt> +executable must handle these three stages. From System V init to launchd, +via busybox init, you name it - one init program from bootup to shutdown. +No wonder those programs, even basic ones, seem complex to write and +complex to understand! +</p> + +<p> +Even the <a href="http://smarden.org/runit/runit.8.html">runit</a> +program, designed with supervision in mind, remains as process 1 all the +time; at least runit makes things simple by clearly separating the three +stages and delegating every stage's work to a different script that is +<em>not</em> run as process 1. (This requires very careful handling of the +<tt>kill -9 -1</tt> part of stage 3, though.) +</p> + +<p> + One init to rule them all? +<a href="http://en.wikipedia.org/wiki/Porgy_and_Bess">It ain't necessarily so!</a> +</p> + +<a name="stage2"> +<h2> The role of s6-svscan </h2> +</a> + +<p> + init does not have the right to die, but fortunately, <em>it has the right +to <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/execve.html">execve()</a>!</em> +During stage 2, why use precious RAM, or at best, swap space, to store data +that are only relevant to stages 1 or 3? It only makes sense to have an +init process that handles stage 1, then executes into an init process that +handles stage 2, and when told to shutdown, this "stage 2" init executes into +a "stage 3" init which just performs shutdown. Just as runit does with the +<tt>/etc/runit/[123]</tt> scripts, but exec'ing the scripts as process 1 +instead of forking them. +</p> + +<p> +It becomes clear now that +<a href="s6-svscan.html">s6-svscan</a> is perfectly suited to +exactly fulfill process 1's role <strong>during stage 2</strong>. +</p> + +<ul> + <li> It does not die </li> + <li> The reaper takes care of every zombie on the system </li> + <li> The scanner maintains services alive </li> + <li> It can be sent commands via the <a href="s6-svscanctl.html">s6-svscanctl</a> +interface </li> + <li> It execs into a given script when told to </li> +</ul> + +<p> + However, an init process for stage 1 and another one for stage 3 are still +needed. Fortunately, those processes are very easy to design! The only +difficulty here is that they're heavily system-dependent, so it's not possible +to provide a stage 1 init and a stage 3 init that will work everywhere. +s6 was designed to be as portable as possible, and it should run on virtually +every Unix platform; but outside of stage 2 is where portability stops, and +the s6 package can't help you there. +</p> + +<p> + Here are some tips though. +</p> + +<a name="stage1"> +<h2> How to design a stage 1 init </h2> +</a> + +<h3> What stage 1 init must do </h3> + +<ul> + <li> Prepare an initial <a href="scandir.html">scan directory</a>, say in +<tt>/service</tt>, with a few vital services, such as s6-svscan's own logger, +and an early getty (in case debugging is needed). That implies mounting a +read-write filesystem, creating it in RAM if needed, if the root filesystem +is read-only. </li> + <li> Either perform all the one-time initialization, as stage 1 +<a href="http://smarden.org/runit/">runit</a> does; </li> + <li> or fork a process that will perform most of the one-time initialization +once s6-svscan is in charge. </li> + <li> Be extremely simple and not fail, because recovery is almost impossible +here. </li> +</ul> + +<p> + Unlike the <tt>/etc/runit/1</tt> script, an init-stage1 script running as +process 1 has nothing to back it up, and if it fails and dies, the machine +crashes. Does that mean the runit approach is better? It's certainly safer, +but not necessarily better, because init-stage1 can be made <em>extremely +small</em>, to the point it is practically failproof, and if it fails, it +means something is so wrong that you +would have had to reboot the machine with <tt>init=/bin/sh</tt> anyway. +</p> + +<p> + To make init-stage1 as small as possible, only this realization is needed: +you do not need to perform all of the one-time initialization tasks before +launching s6-svscan. Actually, once init-stage1 has made it possible for +s6-svscan to run, it can fork a background "init-stage2" process and exec +into s6-svscan immediately! The "init-stage2" process can then pursue the +one-time initialization, with a big advantage over the "init-stage1" +process: s6-svscan is running, as well as a few vital services, and if +something bad happens, there's a getty for the administrator to log on. +No need to play fancy tricks with <tt>/dev/console</tt> anymore! Yes, +the theoretical separation in 3 stages is a bit more supple in practice: +the "stage 2" process 1 can be already running when a part of the +"stage 1" one-time tasks are still being run. +</p> + +<p> + Of course, that means that the scan directory is still incomplete when +s6-svscan first starts, because most services can't yet be run, for +lack of mounted filesystems, network etc. The "init-stage2" one-time +initialization script must populate the scan directory when it has made +it possible for all wanted services to run, and trigger the scanner. +Once all the one-time tasks are done, the scan directory is fully +populated and the scanner has been triggered, the machine is fully +operational and in stage 2, and the "init-stage2" script can die. +</p> + +<h3> Is it possible to write stage 1 init in a scripting language? </h3> + +<p> + It is very possible, and I even recommend it. If you are using +s6-svscan as stage 2 init, stage 1 init should be simple enough +that it can be written in any scripting language you want, just +as <tt>/etc/runit/1</tt> is if you're using runit. And since it +should be so small, the performance impact will be negligible, +while maintainability is enhanced. Definitely make your stage 1 +init a script. +</p> + +<p> + Of course, most people will use the <em>shell</em> as scripting +language; however, I advocate the use of +<a href="http://www.skarnet.org/software/execline/">execline</a> +for this, and not only for the obvious reasons. Piping s6-svscan's +stderr to a logging service before said service is even up requires +some <a href="#log">tricky fifo handling</a> that execline can do +and the shell cannot. +</p> + +<a name="stage3"> +<h2> How to design a stage 3 init </h2> +</a> + +<p> + If you're using s6-svscan as stage 2 init on <tt>/service</tt>, then +stage 3 init is naturally the <tt>/service/.s6-svscan/finish</tt> program. +Of course, <tt>/service/.s6-svscan/finish</tt> can be a symbolic link +to anything else; just make sure it points to something in the root +filesystem (unless your program is an execline script, in which case +it is not even necessary). +</p> + +<h3> What stage 3 init must do </h3> + +<ul> + <li> Destroy the supervision tree and stop all services </li> + <li> Kill all processes <em>save itself</em>, first gently, then harshly </li> + <li> Unmount all the filesystems </li> + <li> Halt or reboot the machine, depending on what root asked for </li> +</ul> + +<p> + This is also very simple; even simpler than stage 1. + The only tricky part is the <tt>kill -9 -1</tt> phase: you must make sure +that <em>process 1</em> regains control and keeps running after it, because +it will be the only process left alive. But since we're running stage 3 +init directly, it's almost automatic! this is an advantage of running +the shutdown procedure as process 1, as opposed to, for instance, +<tt>/etc/runit/3</tt>. +</p> + +<h3> Is it possible to write stage 3 init in a scripting language? </h3> + +<p> + You'd have to be a masochist, or have extremely specific needs, not to +do so. +</p> + +<a name="log"> +<h2> How to log the supervision tree's messages </h2> +</a> + +<p> + When the Unix kernel launches your (stage 1) init process, it does it +with descriptors 0, 1 and 2 open and reading from or writing to +<tt>/dev/console</tt>. This is okay for the early boot: you actually +want early error messages to be displayed to the system console. But +this is not okay for stage 2: the system console should only be used +to display extremely serious error messages such as kernel errors, or +errors from the logging system itself; everything else should be +handled by the logging system, following the +<a href="s6-log.html#loggingchain">logging chain</a> mechanism. The +supervision tree's messages should go to the catch-all logger instead +of the system console. (And the console should never be read, so no +program should run with <tt>/dev/console</tt> as stdin, but this is easy +enough to fix: s6-svscan will be started with stdin redirected from +<tt>/dev/null</tt>.) +</p> + +<p> + The catch-all logger is a service, and we want <em>every</em> +service to run under the supervision tree. Chicken and egg problem: +before starting s6-svscan, we must redirect s6-svscan's output to +the input of a program that will only be started once s6-svscan is +running and can start services. +</p> + +<p> + There are several solutions to this problem, but the simplest one is +to use a FIFO, a.k.a. named pipe. s6-svscan's stdout and stderr can +be redirected to a named pipe before s6-svscan is run, and the +catch-all logger service can be made to read from this named pipe. +Only two minor problems remain: +</p> + +<ul> + <li> If s6-svscan or s6-supervise writes to the FIFO before there is +a reader, i.e. before the catch-all logging service is started, the +write will fail (and a SIGPIPE will be emitted). This is not a real issue +for an s6 installation because s6-svscan and s6-supervise ignore SIGPIPE, +and they only write +to their stderr if an error occurs; and if an error occurs before they are +able to start the catch-all logger, this means that the system is seriously +damaged (as if an error occurs during stage 1) and the only solution is +to reboot with <tt>init=/bin/sh</tt> anyway. </li> + <li> Normal Unix semantics <em>do not allow</em> a writer to open a +FIFO before there is a reader: if there is no reader when the FIFO is +opened for writing, the <tt>open()</tt> system call <em>blocks</em> +until a reader appears. This is obviously not what we want: we want +to be able to <em>actually start</em> s6-svscan with its stdout and +stderr pointing to the logging FIFO, even without a reader process, +and we want it to run normally so it can start the logging service +that will provide such a reader process. </li> +</ul> + +<p> + This second point cannot be solved in a shell script, and that is why +you are discouraged to write your stage 1 init script in the shell +language: you cannot properly set up a FIFO output for s6-svscan without +resorting to horrible and unreliable hacks involving a temporary background +FIFO reader process. +</p> + +<p> + Instead, you are encouraged to use the +<a href="http://skarnet.org/software/execline/">execline</a> language - +or, at least, +the <a href="http://skarnet.org/software/execline/redirfd.html">redirfd</a> +command, which is part of the execline distribution. The +<a href="http://www.skarnet.org/software/execline/redirfd.html">redirfd</a> +command does just the right amount of trickery with FIFOs for you to be +able to properly redirect process 1's stdout and stderr to the logging FIFO +without blocking: <tt>redirfd -w 1 /service/s6-svscan-log/fifo</tt> blocks +if there's no process reading on <tt>/service/s6-svscan-log/fifo</tt>, but +<tt>redirfd -wnb 1 /service/s6-svscan-log/fifo</tt> <em>does not</em>. +</p> + +<p> + This trick with FIFOs can even be used to avoid potential race conditions +in the one-time initialization script that runs in stage 2. If forked from +init-stage1 right before executing s6-svscan, depending on the scheduler +mood, this script may actually run a long way before s6-svscan is actually +executed and running the initial services - and may do dangerous things, +such as writing messages to the logging FIFO before there's a reader, and +eating a SIGPIPE and dying without completing the initialization. To avoid +that and be sure that s6-svscan really runs and initial services are really +started before the stage 2 init script is allowed to continue, it is possible +to redirect the child script's output (stdout and/or stderr) <em>once again</em> +to the logging FIFO, but in the normal way without redirfd trickery, before +it execs into the init-stage2 script. So, the child process blocks on the +FIFO until a reader appears, while process 1 - which does not block - execs +into s6-svscan and starts the logging service, which then opens the logging +FIFO for reading and unblocks the child process, which then runs the +initialization tasks with the guarantee that s6-svscan is running. +</p> + +<p> + It really is simpler than it sounds. :-) +</p> + +<h2> A working example </h2> + +<p> + This whole page may sound very theoretical, dry, wordy, and hard to +grasp without a live example to try things on; unfortunately, s6 cannot provide +live examples without becoming system-specific. However, it provides a whole +set of script skeletons for you to edit and make your own working init. +</p> + +<p> + The <tt>examples/ROOT</tt> subdirectory in the s6 distribution contains +the relevant parts of a small root filesystem that works under Linux and follows +all that has been explained here. In every directory, a <tt>README</tt> file +has been added, to sum up what this directory does. You can copy those files +and modify them to suit your needs; if you have the proper software installed, +and the right configuration, some of them might even work verbatim. +</p> + +</body> +</html> diff --git a/doc/s6-svscan-not-1.html b/doc/s6-svscan-not-1.html new file mode 100644 index 0000000..613ba3d --- /dev/null +++ b/doc/s6-svscan-not-1.html @@ -0,0 +1,140 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: How to run s6-svscan under another init process</title> + <meta name="Description" content="s6: s6-svscan as not-init" /> + <meta name="Keywords" content="s6 supervision svscan s6-svscan init process boot" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> How to run s6-svscan under another init process </h1> + +<p> + You can have a reliable supervision tree even if s6-svscan is not your process 1. +The supervision tree just has to be <em>rooted</em> in process 1: that means that +your process 1 will have to supervise your s6-svscan process somehow. That way, +if s6-svscan dies, it will be restarted, and your set of services will always +be maintained. +</p> + +<p> + Be aware, though, that pipes between services and loggers are maintained +by the s6-svscan process; if this process dies, the pipes will be closed and +some logs may be lost. +</p> + +<a name="log"> +<h2> Logging the supervision tree's output </h2> +</a> + +<p> + s6-svscan and the various s6-supervise processes might produce error or +warning messages; those messages are written to s6-svscan's stderr (which +is inherited by the s6-supervise processes). To log these messages: +</p> + +<ul> + <li> You can use your init system's logging tools, and make your init +system launch s6-svscan as is; its stderr should already be taken care +of by the logging tools. </li> + <li> You can use a trick similar to the <a href="s6-svscan-1.html#log">process +1 output logging trick</a> so the supervision tree's messages are logged via +a service that's maintained by the supervision tree itself. Then your init +system should not launch s6-svscan directly, but a wrapper script that performs +the proper redirections. The +<tt>examples/s6-svscanboot</tt> file in the s6 distribution gives an example of +such a script. Make sure that your initial <a href="scandir.html">scan +directory</a> contains a <a href="servicedir.html">service directory</a> for your +initial logging service, that must read on the logging FIFO. </li> +</ul> + +<p> + In the following examples, we'll assume that <tt>/command/s6-svscanboot</tt> +is the name of the script you are using to start s6-svscan. Adjust this accordingly. +</p> + +<a name="sysv"> +<h2> System V init </h2> +</a> + +<p> + Put an appropriate line in your <tt>/etc/inittab</tt> file, then reload this +config file with <tt>telinit q</tt>. +</p> + +<h3> Example </h3> + +<pre> SV:123456:respawn:/command/s6-svscanboot </pre> + + +<a name="upstart"> +<h2> Upstart </h2> +</a> + +<p> + Put an appropriate configuration file in the <tt>/etc/init</tt> folder, +for instance <tt>/etc/init/s6-svscan.conf</tt>, then start the service +with <tt>start s6-svscan</tt>. +</p> + +<h3>Example </h3> + +<pre># s6-svscan +start on runlevel [2345] +stop on runlevel [!2345] + +oom never +respawn +exec /command/s6-svscanboot +</pre> + +<a name="systemd"> +<h2> systemd </h2> + +<p> + systemd has +<a href="http://www.freedesktop.org/software/systemd/man/daemon.html">its +own way</a> of supervising services. If you are a systemd user, chances +are you do not need s6. If you are interested in using s6, I encourage +you to also stop using systemd. +</p> + +<p> + +</p> + +<a name="bsd"> +<h2> BSD init </h2> +</a> + +<p> + Put an appropriate line in your <tt>/etc/ttys</tt> file, then reload this +file with <tt>kill -s HUP 1</tt>. +</p> + +<h3> Example </h3> + +<pre> sv /command/s6-svscanboot "" on </pre> + +<a name="launchd"> +<h2> MacOS X launchd </h2> +</a> + +<p> + Like systemd, launchd comes with its own +<a href="https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/CreatingLaunchdJobs.html">way +of supervising services</a>; if you are a launchd user, you probably do +not need s6. +</p> + + +</body> +</html> diff --git a/doc/s6-svscan.html b/doc/s6-svscan.html new file mode 100644 index 0000000..4a7e800 --- /dev/null +++ b/doc/s6-svscan.html @@ -0,0 +1,176 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-svscan program</title> + <meta name="Description" content="s6: the s6-svscan program" /> + <meta name="Keywords" content="s6 command s6-svscan scandir supervision supervise svscan monitoring collection" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">www.skarnet.org</a> +</p> + +<h1> The s6-svscan program </h1> + +<p> +s6-svscan starts and monitors a collection of <a href="s6-supervise.html">s6-supervise</a> +processes, each of these processes monitoring a single service. It is designed to be either +the root or a branch of a <em>supervision tree</em>. +</p> + +<h2> Interface </h2> + +<pre> + s6-svscan [ -c max ] [ -t <em>rescan</em> ] [ <em>scandir</em> ] +</pre> + +<ul> + <li> If given a <em>scandir</em> argument, s6-svscan switches to it. Else it uses +its current directory as <a href="scandir.html">scan directory</a>. </li> + <li> It exits 100 if another s6-svscan process is already monitoring this +<a href="scandir.html">scan directory</a>. </li> + <li> If the <tt>./.s6-svscan</tt> control directory does not exist, +s6-svscan creates it. However, it is recommended to already have a <tt>.s6-svscan</tt> +subdirectory in your scan directory, because s6-svscan may try and execute into the +<tt>.s6-svscan/crash</tt> or <tt>.s6-svscan/finish</tt> files at some point - so those +files should exist and be executable. </li> + <li> From this point on, s6-svscan never dies. It tries its best to keep +control of what's happening. In case of a major system call failure, which means +that the kernel or hardware is broken in some fashion, it executes into the +<tt>.s6-svscan/crash</tt> program. (But if that execution fails, s6-svscan exits +111.) </li> + <li> s6-svscan performs an initial <em>scan</em> of its scan directory. </li> + <li> s6-svscan then occasionally runs <em>scans</em> or <em>reaps</em>, +see below. </li> + <li> s6-svscan runs until it is told to stop via <a href="s6-svscanctl.html"> +s6-svscanctl</a>, or a signal. +Then it executes into the <tt>.s6-svscan/finish</tt> program. The program is +given an argument that depends on the s6-svscanctl options that were used. </li> + <li> If that execution fails, s6-svscan falls back on a <tt>.s6-svscan/crash</tt> +execution. </li> +</ul> + +<h2> Options </h2> + +<ul> + <li> <tt>-c <em>max</em></tt> : maintain services for up to <em>max</em> +service directories. Default is 500. Lower limit is 2. There is no upper limit, but: + <ul> + <li> The higher <em>max</em> is, the more stack memory s6-svscan will use, +approximately 50 bytes per service. </li> + <li> s6-svscan uses 2 file descriptors per logged service. </li> + </ul> + It is the admin's responsibility to make sure that s6-svscan has enough available +descriptors to function properly and does not exceed its stack limit. The default +of 500 is safe and provides enough room for every reasonable system. </li> + <li> <tt>-t <em>rescan</em></tt> : perform a scan every <em>rescan</em> +milliseconds. If <em>rescan</em> is 0, automatic scans are never performed after +the first one and s6-svscan will only detect new services when told to via a +<a href="s6-svscanctl.html">s6-svscanctl -a</a> command. The default <em>rescan</em> +value is 5000, for compatibility with daemontools' +<a href="http://cr.yp.to/daemontools/svscan.html">svscan</a>, which performs a +scan (and a reap) every 5 seconds. It is <em>strongly</em> discouraged to set +<em>rescan</em> to a positive value under 500. </li> +</ul> + +<h2> Signals </h2> + +<p> + s6-svscan reacts to the following signals: +</p> + +<ul> + <li> SIGCHLD : triggers the reaper. </li> + <li> SIGALRM : triggers the scanner. </li> + <li> SIGTERM : acts as if a <tt>s6-svscanctl -t</tt> command had been received. </li> + <li> SIGHUP : acts as if a <tt>s6-svscanctl -h</tt> command had been received. </li> + <li> SIGQUIT : acts as if a <tt>s6-svscanctl -q</tt> command had been received. </li> + <li> SIGABRT : acts as if a <tt>s6-svscanctl -b</tt> command had been received. </li> + <li> SIGINT : acts as if a <tt>s6-svscanctl -i</tt> command had been received. </li> +</ul> + +<h2> The reaper </h2> + +<p> + Upon receipt of a SIGCHLD, or a <a href="s6-svscanctl.html">s6-svscanctl -z</a> +command, s6-svscan runs a <em>reaper</em> routine. +</p> + +<p> +The reaper acknowledges (via some +<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html">wait()</a> +function), without blocking, every terminated child of s6-svscan, even ones it does not +know it has. This is especially important when <a href="s6-svscan-1.html">s6-svscan is +run as process 1</a>. +</p> + +<p> + If the dead child is a <a href="s6-supervise.html">s6-supervise</a> process watched +by s6-svscan, and the last scan flagged that process as active, then it is restarted +one second later. +</p> + +<h2> The scanner </h2> + +<p> + Every <em>rescan</em> milliseconds, or upon receipt of a SIGALRM or a +<a href="s6-svscanctl.html">s6-svscanctl -a</a> command, s6-svscan runs a +<em>scanner</em> routine. +</p> + +<p> + The scanner scans the current directory for subdirectories (or symbolic links +to directories), which must be <a href="servicedir.html">service directories</a>. +It skips names starting with dots. It will not create services for more than +<em>max</em> subdirectories. +</p> + +<p> + For every new subdirectory <em>dir</em> it finds, the scanner spawns a +<a href="s6-supervise.html">s6-supervise</a> process on it. If +<em>dir</em><tt>/log</tt> exists, it spawns a s6-supervise process on +both <em>dir</em> and <em>dir</em><tt>/log</tt>, and maintains a +never-closing pipe from the service's stdout to the logger's stdin. +This is <em>starting the service</em>, with or without a corresponding +logger. +Every service the scanner finds is flagged as "active". +</p> + +<p> + The scanner remembers the services it found. If a service has been +started in an earlier scan, but the current scan can't find the corresponding +directory, the service is then flagged as inactive. No command is sent +to stop inactive s6-supervise processes (unless the administrator +uses <a href="s6-svscanctl.html">s6-svscanctl -n</a>), but inactive +s6-supervise processes will not be restarted if they die. +</p> + +<h2> Implementation notes </h2> + +<ul> + <li> s6-svscan is designed to run until the machine is shut down. It is +also designed as a suitable candidate for +<a href="s6-svscan-1.html">process 1</a>. So, it goes out of its way to +stay alive, even in dire situations. When it encounters a fatal situation, +something it really cannot handle, it executes into <tt>.s6-svscan/crash</tt> +instead of dying; when it is told to exit, it executes into +<tt>.s6-svscan/finish</tt>. Administrators should make sure to design +appropriate <tt>crash</tt> and <tt>finish</tt> routines. </li> + <li> s6-svscan is a fully asynchronous state machine. It will read and +process commands at any time, even when the computer is in trouble. </li> + <li> s6-svscan <em>does not use malloc()</em>. That means it will <em>never leak +memory</em>. <small>However, s6-svscan uses opendir(), and most opendir() +implementations internally use heap memory - so unfortunately, it's impossible +to guarantee that s6-svscan does not use heap memory at all.</small> </li> + <li> When run with the <tt>-t0</tt> option, s6-svscan <em>never polls</em>, +it only wakes up on notifications, just like s6-supervise. The s6 supervision +tree can be used in energy-critical environments. </li> +</ul> + +</body> +</html> diff --git a/doc/s6-svscanctl.html b/doc/s6-svscanctl.html new file mode 100644 index 0000000..cc7ae99 --- /dev/null +++ b/doc/s6-svscanctl.html @@ -0,0 +1,108 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-svscanctl program</title> + <meta name="Description" content="s6: the s6-svscanctl program" /> + <meta name="Keywords" content="s6 command s6-svscanctl svscan command service" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-svscanctl program </h1> + +<p> +s6-svscanctl sends commands to a running <a href="s6-svscan.html">s6-svscan</a> +process. +</p> + +<h2> Interface </h2> + +<pre> + s6-svscanctl [ -phratszbnNiq0678 ] <em>svscandir</em> +</pre> + +<p> +s6-svscanctl sends the given series of commands to the +<a href="s6-svscan.html">s6-svscan</a> process monitoring the +<em>svscandir</em> directory, then exits 0. It exits 111 if it cannot send +a command, or 100 if no s6-svscan process is running on <em>svscandir</em>. +</p> + +<h2> Options </h2> + +<ul> + <li> <tt>-p</tt> : poweroff mode. s6-svscan will exec into + <tt>./.s6-svscan/finish poweroff</tt> when it is told to terminate. </li> + <li> <tt>-h</tt> : Hangup. s6-svscan will send a SIGHUP to all the +maintained s6-supervise processes, then run its finish procedure. </li> + <li> <tt>-r</tt> : reboot mode. s6-svscan will exec into + <tt>./.s6-svscan/finish reboot</tt> when it is told to terminate. This +is s6-svscan's default mode.</li> + <li> <tt>-a</tt> : Alarm. s6-svscan will immediately perform a scan +of <em>svscandir</em> to check for services. </li> + <li> <tt>-t</tt> : Terminate. s6-svscan will send a +SIGTERM to all the s6-supervise processes supervising a service and a +SIGHUP to all the s6-supervise processes supervising a logger, then run its +finish procedure. </li> + <li> <tt>-s</tt> : halt mode. s6-svscan will exec into + <tt>./.s6-svscan/finish halt</tt> when it is told to terminate. </li> + <li> <tt>-z</tt> : destroy zombies. Immediately triggers s6-svscan's +reaper mechanism. </li> + <li> <tt>-b</tt> : abort. s6-svscan will exec into its finishing +procedure. It will not kill any of the maintained s6-supervise processes. </li> + <li> <tt>-n</tt> : nuke. s6-svscan will kill all the +s6-supervise processes it has launched but that did not match a service +directory last time <em>svscandir</em> was scanned, i.e. it prunes the +supervision tree so that it matches exactly what was in <em>svscandir</em> +at the time of the last scan. A SIGTERM is sent to the s6-supervise processes +supervising services and a SIGHUP is sent to the s6-supervise processes +supervising loggers. </li> + <li> <tt>-N</tt> : Really nuke. Does the same thing as <tt>-n</tt>, +except that SIGTERM is sent to all the relevant s6-supervise processes, even +if they are supervising loggers. That means that the logger processes will +be killed with a SIGTERM instead of being allowed to exit at their own pace. </li> + <li> <tt>-i</tt> : Interrupt. Equivalent to <tt>-rt</tt> : s6-svscan +will terminate in reboot mode. </li> + <li> <tt>-q</tt> : Quit. s6-svscan will send all its s6-supervise processes +a SIGTERM, then exec into its finish procedure. </li> + <li> <tt>-0</tt> : Halt. Equivalent to <tt>-st</tt> : s6-svscan will +terminate in halt mode. </li> + <li> <tt>-6</tt> : Reboot. Equivalent to <tt>-i</tt>. </li> + <li> <tt>-7</tt> : Poweroff. Equivalent to <tt>-pt</tt>: s6-svscan will +terminate in poweroff mode. </li> + <li> <tt>-8</tt> : Other. s6-svscan will terminate in "other" mode. </li> +</ul> + +<h2> Usage examples </h2> + +<pre> s6-svscanctl -an /service </pre> +<p> + Updates the process supervision tree +to exactly match the services listed in <tt>/service</tt>. +</p> + +<pre> s6-svscanctl -6 /service </pre> +<p> + Orders the s6-svscan process monitoring <tt>/service</tt> to exit in +reboot mode: all the supervision tree at <tt>/service</tt> will be terminated, +and s6-svscan will execute into the <tt>/service/.s6-svscan/finish</tt> +script with the <tt>reboot</tt> argument. +</p> + +<h2> Internals </h2> + +<p> +s6-svscanctl writes control commands into the <tt><em>svscandir</em>/.s6-svscan/control</tt> +FIFO. A s6-svscan process running on <em>svscandir</em> will be listening to this FIFO, +and will read and interpret those commands. +</p> + +</body> +</html> diff --git a/doc/s6-svstat.html b/doc/s6-svstat.html new file mode 100644 index 0000000..75ee7e2 --- /dev/null +++ b/doc/s6-svstat.html @@ -0,0 +1,45 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-svstat program</title> + <meta name="Description" content="s6: the s6-svstat program" /> + <meta name="Keywords" content="s6 command s6-svstat servicedir checking supervision s6-supervise" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-svstat program </h1> + +<p> +s6-svstat prints a short, human-readable summary of the state of a supervised +service. +</p> + +<h2> Interface </h2> + +<pre> + s6-svstat <em>servicedir</em> +</pre> + +<p> + s6-svstat gives the following information about the process being monitored +at the <em>servicedir</em> <a href="servicedir.html">service directory</a>, then +exits 0: +</p> + +<ul> + <li> whether it is up or down </li> + <li> its pid, if it is up </li> + <li> what its default state is, if it is different from its current state </li> + <li> the number of seconds since it last changed states </li> +</ul> + +</body> +</html> diff --git a/doc/s6-svwait.html b/doc/s6-svwait.html new file mode 100644 index 0000000..6e15704 --- /dev/null +++ b/doc/s6-svwait.html @@ -0,0 +1,73 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-svwait program</title> + <meta name="Description" content="s6: the s6-svwait program" /> + <meta name="Keywords" content="s6 command s6-svwait notification service waiting" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-svwait program </h1> + +<p> +s6-svwait blocks until a collection of supervised services goes up, or down. +</p> + +<p> +s6-svwait only waits for notifications; it never polls. +</p> + +<h2> Interface </h2> + +<pre> + s6-svwait [ -U | -u | -d ] [ -a | -o ] [ -t <em>timeout</em> ] <em>servicedir...</em> +</pre> + +<p> +s6-svwait monitors one or more <a href="servicedir.html">service +directories</a> given as its arguments, waiting for a state (up or down) to +happen. It exits 0 when the wanted condition becomes true. +</p> + +<h2> Options </h2> + +<ul> + <li> <tt>-u</tt> : up. s6-svwait will wait until the services are up, as +reported by s6-supervise. +This is the default; it is not reliable, but it does not depend on specific +support in the service programs. See <a href="notifywhenup.html">this page</a> +for details. </li> + <li> <tt>-U</tt> : really up. s6-svwait will wait until the services are +up, as reported by the services themselves. This requires specific support in the +service programs: see the explanation on <a href="notifywhenup.html">this page</a>. </li> + <li> <tt>-d</tt> : down. s6-svwait will wait until the services are down. </li> + <li> <tt>-o</tt> : or. s6-svwait will wait until <em>one</em> of the +given services comes up or down. </li> + <li> <tt>-a</tt> : and. s6-svwait will wait until <em>all</em> of the +given services come up or down. This is the default. </li> + <li> <tt>-t <em>timeout</em></tt> : if the requested events have not +happened after <em>timeout</em> milliseconds, s6-svwait will print a message +to stderr and exit 1. By default, <em>timeout</em> is 0, which means no time +limit. </li> +</ul> + + +<h2> Internals </h2> + +<p> +s6-svwait spawns a <a href="s6-ftrigrd.html">s6-ftrigrd</a> child to +listen to notifications sent by <a href="s6-supervise.html">s6-supervise</a>. +It also checks <tt>supervise/status</tt> files to get the current service +states, so it is immune to race conditions. +</p> + +</body> +</html> diff --git a/doc/s6-tai64n.html b/doc/s6-tai64n.html new file mode 100644 index 0000000..8b7f5e0 --- /dev/null +++ b/doc/s6-tai64n.html @@ -0,0 +1,50 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-tai64n program</title> + <meta name="Description" content="s6: the s6-tai64n program" /> + <meta name="Keywords" content="s6 command s6-tai64n filter timestamp TAI64 TAI64N" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-tai64n program </h1> + +<p> +s6-tai64n acts as a filter, reading from stdin and writing to stdout. +It prepends lines with a +<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html#timestamp">TAI64N +timestamp</a> and a space. +</p> + +<h2> Interface </h2> + +<pre> + s6-tai64n +</pre> + +<ul> + <li> s6-tai64n exits 0 when it sees the end of stdin. If there's an +unfinished line, s6-tai64n processes it, adds a newline character to it, +and writes it before exiting. </li> +</ul> + +<h2> Notes </h2> + +<p> + s6-tai64n does neither "line buffering" nor "block buffering". It does +<em>optimal buffering</em>, i.e. it flushes its output buffer every time +it risks blocking on input. Every filter should behave this way, whether +its output is a tty or not: it's simpler and more efficient in every +case. +</p> + +</body> +</html> diff --git a/doc/s6-tai64nlocal.html b/doc/s6-tai64nlocal.html new file mode 100644 index 0000000..f6d2280 --- /dev/null +++ b/doc/s6-tai64nlocal.html @@ -0,0 +1,73 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the s6-tai64nlocal program</title> + <meta name="Description" content="s6: the s6-tai64nlocal program" /> + <meta name="Keywords" content="s6 command s6-tai64nlocal filter timestamp TAI64 TAI64N human-readable date time" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The s6-tai64nlocal program </h1> + +<p> +s6-tai64nlocal acts as a filter, reading from stdin and writing to stdout. +For every line that begins with a +<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html#timestamp">TAI64N +timestamp</a>, it replaces this timestamp with a human-readable local date and +time. +</p> + +<h2> Interface </h2> + +<pre> + s6-tai64nlocal +</pre> + +<ul> + <li> s6-tai64nlocal exits 0 when it sees the end of stdin. If there's an +unfinished line, s6-tai64n processes it +and writes it before exiting. </li> +</ul> + +<h2> Notes </h2> + +<ul> + <li> The typical use case of s6-tai64nlocal is to read files that have +been filtered through <a href="s6-tai64n.html">s6-tai64n</a>, or log files +that have been produced by <a href="s6-log.html">s6-log</a> with the <tt>-t</tt> +option. For instance, to read the latest httpd logs with human-readable +timestamps, <tt>s6-tai64nlocal < /var/log/httpd/current | less</tt> is a +possible command. </li> + <li> s6-tai64nlocal does neither "line buffering" nor "block buffering". It does +<em>optimal buffering</em>, i.e. it flushes its output buffer every time +it risks blocking on input. </li> +</ul> + +<h2> Troubleshooting </h2> + +<p> + If s6-tai64nlocal does not appear to give the correct local time: +</p> + +<ul> + <li> Check the compilation options that were used for the +<a href="http://skarnet.org/software/skalibs/">skalibs</a> libraries +your s6-tai64nlocal program was linked against. In particular, check whether the +<tt>--enable-tai-clock</tt> or <tt>--enable-right-tz</tt> configure options +have been given. </li> + <li> Compare these flags and their meanings with your current timezone. In particular, +check <tt>/etc/localtime</tt>, <tt>/etc/timezone</tt>, <tt>/etc/TZ</tt>, and the TZ +environment variable. </li> + <li> Check that you have a correct and recent version of <tt>/etc/leapsecs.dat</tt>. </li> +</ul> + +</body> +</html> diff --git a/doc/scandir.html b/doc/scandir.html new file mode 100644 index 0000000..366bd08 --- /dev/null +++ b/doc/scandir.html @@ -0,0 +1,145 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: scan directories</title> + <meta name="Description" content="s6: scan directory" /> + <meta name="Keywords" content="s6 scandir supervision svscan s6-svscan scan directory servicedir" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> Scan directories </h1> + +<p> + A <em>scan directory</em> is a directory containing a list of +<a href="servicedir.html">service directories</a>, or symbolic links +pointing to service directories. +</p> + +<p> + A scan directory represents a list of services that are supposed to +be supervised. Running <a href="s6-svscan.html">s6-svscan</a> on this +scan directory launches a <em>supervision tree</em>: every service +listed in the scan directory will be supervised. +</p> + +<p> + There is normally only one scan directory per system, although nothing +prevents a system administrator from having more. +<a href="http://cr.yp.to/daemontools.html">daemontools</a> traditionally +uses <tt>/service</tt>, and <a href="http://smarden.org/runit/">runit</a> +traditionally uses <tt>/etc/service</tt>. s6 does not care where your +scan directory is, but I would advise <tt>/service</tt> for compatibility +with daemontools. Depending on your installation, <tt>/service</tt> could +be a symbolic link and point to a place either in a RAM filesystem or in +<tt>/var</tt>. +</p> + +<a name="where"> +<h2> Where and how to build a scan directory </h2> +</a> + +<p> + Opinions and practices differ. +</p> + +<p> + It is generally accepted that the place where you store all your +service directories (your "service repository") should <em>not</em> be +used as a scan directory - for a simple reason: you might want to have +service directories for more services than what you want to start at +any given time. In other words, your scan directory will be a <em>subset</em> +of your service repository, so you cannot just run s6-svscan on every +service you have a service directory for. So, the first thing is to +separate your <em>service repository</em>, which is just a storage place +for all the services you might want to manage someday, and your <em>scan +directory</em>, which is a directory representing all the services that +you are <em>currently</em> managing. +</p> + +<h3> Service repository </h3> + +<p> + Where to store your service repository is purely a matter of personal +preference. You just have to be aware that <a href="s6-supervise.html"> +s6-supervise</a> needs writable <tt>supervise</tt> and <tt>event</tt> +subdirectories in a service directory it monitors. +</p> + +<h3> Scan directory </h3> + +<p> + Where and how to build your scan directory depends heavily on your boot +system - and on your personal preference too. +</p> + +<p> + Standard <a href="http://cr.yp.to/daemontools.html">daemontools</a> and +<a href="http://smarden.org/runit/">runit</a> installations like to have +a fixed scan directory containing symlinks to service directories located +in the service repository. In other words, the service repository contains +the real <em>working copies</em> of the service directories. This works, +as long as: +</p> + +<ul> + <li> It is possible to create writable <tt>supervise</tt> and <tt>event</tt> +subdirectories in every managed service directory. This can be achieved for +instance via symlinks, or by having the service repository stored on a writable +filesystem. </li> + <li> The scan program (<a href="s6-svscan.html">s6-svscan</a>, +<a href="http://cr.yp.to/daemontools/svscan.html">svscan</a>, +<a href="http://smarden.org/runit/runsvdir.8.html">runsvdir</a>...) is +started late enough for all the necessary filesystems to be mounted. </li> +</ul> + +<p> + My own recommendation would be to have working copies of the service +directories <em>entirely separate</em> from the service repository. The +service repository can be safely stored on the root filesystem, and the +needed directories copied to a RAM filesystem at boot time. The scan +directory can be either the place where the working copies are written, +or another directory containing symlinks to those working copies. (The +latter is useful if you are not using <tt>s6-svscan -t0</tt>: copying a +directory is not atomic, but making a symlink is, so there is no risk +of your scanner finding an incomplete directory.) +</p> + +<p> + An example: +</p> + +<ul> + <li> Have your service repository in <tt>/img/services</tt>, i.e. have +service directories in <tt>/img/services/ftpd</tt>, <tt>/img/services/httpd</tt>, +<tt>/img/services/sshd</tt>, etc. </li> + <li> When booting, make <tt>/tmp</tt> a RAM filesystem, and create the +directories <tt>/tmp/services</tt> and <tt>/tmp/service</tt>. </li> + <li> Have s6-svscan run on <tt>/tmp/service</tt>, as early as possible in your +boot sequence. This is possible whether you want to run s6-svscan +<a href="s6-svscan-1.html">as process 1</a> or <a href="s6-svscan-not-1.html">not</a>. </li> + <li> During the boot sequence, populate <tt>/tmp/services</tt> with copies of the +service directories you need: for instance, + <ul> + <li> <tt>cp -a /img/services/sshd /tmp/services/sshd</tt> </li> + <li> <tt>cp -a /img/services/ftpd /tmp/services/ftpd</tt> </li> + <li> etc. </li> + </ul> </li> + <li> When you are ready to start a service, make a symlink in the +<tt>/tmp/service</tt> <em>scan directory</em> pointing to the working copy of +the service directory you need in <tt>/tmp/services</tt>, then notify s6-svscan. +For instance, to start ftpd and httpd together: +<pre> ln -s ../services/ftpd /tmp/service + ln -s ../services/httpd /tmp/service + s6-svscanctl -a /tmp/service</pre> </li> +</ul> + +</body> +</html> diff --git a/doc/servicedir.html b/doc/servicedir.html new file mode 100644 index 0000000..b5c4d23 --- /dev/null +++ b/doc/servicedir.html @@ -0,0 +1,187 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: service directories</title> + <meta name="Description" content="s6: service directory" /> + <meta name="Keywords" content="s6 supervision supervise service directory run finish servicedir" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> Service directories </h1> + +<p> + A <em>service directory</em> is a directory containing all the information +related to a <em>service</em>, i.e. a long-running process maintained and +supervised by <a href="s6-supervise.html">s6-supervise</a>. +</p> + +<p> + (Strictly speaking, a <em>service</em> is not always equivalent to a +long-running process. Things like Ethernet interfaces fit the definition +of <em>services</em> one may want to supervise; however, s6 does not +provide <em>service supervision</em>; it provides <em>process supervision</em>, +and it is impractical to use the s6 architecture as is to supervise +services that are not equivalent to one long-running process. However, +we still use the terms <em>service</em> and <em>service directory</em> +for historical and compatibility reasons.) +</p> + +<h2> Contents </h2> + + A service directory <em>foo</em> may contain the following elements: + +<ul> + <li> An executable file named <tt>run</tt>. It can be any executable +file (such as a binary file or a link to any other executable file), +but most of the time it will be a script, called <em>run script</em>. +This file is the most important one in your service directory: it +contains the commands that will setup and run your <em>foo</em> service. +It is forked and executed by <a href="s6-supervise.html">s6-supervise</a> +every time the service must be started, i.e. normally when +<a href="s6-supervise.html">s6-supervise</a> starts, and whenever +the service goes down when it is supposed to be up. A run script +should normally: + <ul> + <li> adjust redirections for stdin, stdout and stderr. For instance, +if your service is logged, the run script should make sure that its +stderr goes into the log pipe (which is on stdout by default), which +is achieved by <tt><a href="http://skarnet.org/software/execline/fdmove.html">fdmove</a> +-c 2 1</tt> in <a href="http://skarnet.org/software/execline/">execline</a>, +and <tt>exec 2>&1</tt> in <a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html">shell</a>. +By default, in a normal supervision tree situation, a run script's stdin will +be <tt>/dev/null</tt>, and its stdout and stderr will both be a pipe to a +catch-all logging program. </li> +<li> adjust the environment for your <em>foo</em> daemon. Normally the run script +inherits its environment from <a href="s6-supervise.html">s6-supervise</a>, +which normally inherits its environment from <a href="s6-svscan.html">s6-svscan</a>, +which normally inherits a minimal environment from the boot scripts. +Service-specific environment variables should be set in the run script. </li> + <li> adjust other parameters for the <em>foo</em> daemon, such as its +uid and gid. Normally the supervision tree, i.e. +<a href="s6-svscan.html">s6-svscan</a> and the various +<a href="s6-supervise.html">s6-supervise</a> processes, is run as root, so +run scripts are also run as root; however, for security purposes, services +should not run as root if they don't need to. You can use the +<a href="s6-setuidgid.html">s6-setuidgid</a> utility in <em>foo</em><tt>/run</tt> +to lose privileges before executing into <em>foo</em>'s long-lived +process; or the <a href="s6-envuidgid.html">s6-envuidgid</a> utility if +your long-lived process needs root privileges at start time but can drop +them afterwards. </li> + <li> execute into the long-lived process that is to be supervised by +<a href="s6-supervise.html">s6-supervise</a>, i.e. the real <em>foo</em> +daemon. That process must not "background itself": being run by a supervision +tree already makes it a "background" task. </li> + </ul> + <li> An optional executable file named <tt>finish</tt>. Like <tt>run</tt>, +it can be any executable file. This <em>finish script</em>, if present, +is executed everytime the <tt>run</tt> script dies. Generally, its main +purpose is to clean up non-volatile data such as the filesystem after the supervised +process has been killed. If the <em>foo</em> service is supposed to be up, +<em>foo</em><tt>/run</tt> is restarted +after <em>foo</em><tt>/finish</tt> dies. A finish script must do its work and exit in less than +3 seconds; if it takes more than that, it is killed. (The point is that the run +script, not the finish script, should be running; the finish script should really +be short-lived.) </li> + <li> A directory named <tt>supervise</tt>. It is automatically created by +<a href="s6-supervise.html">s6-supervise</a> if it does not exist. This is where +<a href="s6-supervise.html">s6-supervise</a> stores its information. The directory +must be writable. </li> + <li> An optional, empty, regular file named <tt>down</tt>. If such a file exists, +the default state of the service is considered down, not up: s6-supervise will not +automatically start it until it receives a <tt>s6-svc -u</tt> command. If no +<tt>down</tt> file exists, the default state of the service is up. </li> + <li> An optional, empty, regular file named <tt>nosetsid</tt>. If such a file exists, +s6-supervise will not make the service a process group and session leader; the service +will be run in the same process group as s6-supervise. If no <tt>nosetsid</tt> file +exists, the service has its own process group and is started as a session leader. </li> + <li> A <a href="fifodir.html">fifodir</a> named <tt>event</tt>. It is automatically +created by <a href="s6-supervise.html">s6-supervise</a> if it does not exist. +<em>foo</em><tt>/event</tt> +is the rendez-vous point for listeners, where <a href="s6-supervise.html">s6-supervise</a> +will send notifications when the service goes up or down. </li> + <li> An optional service directory named <tt>log</tt>. If it exists and <em>foo</em> +is in a <a href="scandir.html">scandir</a>, and <a href="s6-svscan.html">s6-svscan</a> +runs on that scandir, then <em>two</em> services are monitored: <em>foo</em> and +<em>foo</em><tt>/log</tt>. A pipe is open and maintained between <em>foo</em> and +<em>foo</em><tt>/log</tt>, i.e. everything that <em>foo</em><tt>/run</tt> +writes to its stdout will appear on <em>foo</em><tt>/log/run</tt>'s stdin. The <em>foo</em> +service is said to be <em>logged</em>; the <em>foo</em><tt>/log</tt> service is called +<em>foo</em>'s <em>logger</em>. A logger service cannot be logged: if +<em>foo</em><tt>/log/log</tt> exists, nothing special happens. </li> +</ul> + +<a name="where"> + <h2> Where to store my service directories ? </h2> +</a> + +<p> + Service directories describe the way services are launched. Once they are +designed, they have little reason to change on a given machine. They can +theoretically reside on a read-only filesystem - for instance, the root +filesystem, to avoid problems with mounting failures. +</p> + +<p> + However, two subdirectories - namely <tt>supervise</tt> and <tt>event</tt> - +of every service directory need to be writable. So it has to be a bit more +complex. Here are a few possibilities. +</p> + +<ul> + <li> The laziest option: you're not using <a href="s6-svscan.html">s6-svscan</a> +as process 1, you're only using it to start a collection of services, and +your booting process is already handled by another init system. Then you can +just store your service directories and your <a href="scandir.html">scan +directory</a> on some read-write filesystem such as <tt>/var</tt>; and you +tell your init system to launch (and, if possible, maintain) s6-svscan on +the scan directory after that filesystem is mounted. </li> + <li> The almost-as-lazy option: just have the service directories on the +root filesystem. Then your service directory collection is for instance in +<tt>/etc/services</tt> and you have a <tt>/service</tt> +<a href="scandir.html">scan directory</a> containing symlinks to that +collection. This is the easy setup, not requiring an external init system +to mount your filesystems - however, it requires your root filesystem to be +read-write, which is unacceptable if you are concerned with reliability - if +you are, for instance, designing an embedded platform. </li> + <li> <a href="http://code.dogmap.org/">Some people</a> like to have +their service directories in a read-only filesystem, with <tt>supervise</tt> +symlinks pointing to various places in writable filesystems. This setup looks +a bit complex to me: it requires careful handling of the writable +filesystems, with not much room for error if the directory structure does not +match the symlinks (which are then dangling). But it works. </li> + <li> Service directories are usually small; most daemons store their +information elsewhere. Even a complete set of service directories often +amounts to less than a megabyte of data - sometimes much less. Knowing this, +it makes sense to have an image of your service directories in the +(possibly read-only) root filesystem, and <em>copy it all</em> +to a scan directory located on a RAM filesystem that is mounted at boot time. +This is the setup I recommend. It has several advantages: + <ul> + <li> Your service directories reside on the root filesystem and are not +modified during the lifetime of the system. If your root filesystem is +read-only and you have a working set of service directories, you have the +guarantee that a reboot will set your system in a working state. </li> + <li> Every boot system requires an early writeable filesystem, and many +create it in RAM. You can take advantage of this to copy your service +directories early and run s6-svscan early. </li> + <li> No dangling symlinks or potential problems with unmounted +filesystems: this setup is robust. A simple <tt>/bin/cp -a</tt> or +<tt>tar -x</tt> is all it takes to get a working service infrastructure. </li> + <li> You can make temporary modifications to your service directories +without affecting the main ones, safely stored on the disk. Conversely, +every boot ensures clean service directories - including freshly created +<tt>supervise</tt> and <tt>event</tt> subdirectories. No stale files can +make your system unstable. </li> + </ul> </li> +</ul> + +</body> +</html> diff --git a/doc/systemd.html b/doc/systemd.html new file mode 100644 index 0000000..49ba2aa --- /dev/null +++ b/doc/systemd.html @@ -0,0 +1,120 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: a word about systemd</title> + <meta name="Description" content="s6: a word about systemd" /> + <meta name="Keywords" content="s6 supervision init systemd" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> A word about systemd </h1> + +<p> + <a href="http://www.freedesktop.org/wiki/Software/systemd/">systemd</a> +is becoming <i>de facto</i> a standard init system for Linux. But even +this choice of words is treacherous, because systemd is much more than +an init system. It's basically an integrated redesign of all the low-level +userspace of a Linux system, with great plans to change how software is +run and organized. + +<p> + Which is not a bad thing per se: Unix software can definitely benefit +from improvements in this area, and the s6 suite, among other software, +comes from the same assessment and ultimately has the same goal. But +systemd suffers from a single conception flaw that sets it apart from +the other initiatives, and that has both political and technical +repercussions. +</p> + +<p> +<strong> + The single, overarching problem with systemd is that it attempts, in every +possible way, to do <em>more</em> instead of <em>less</em>. +</strong> +</p> + +<h2> The political issue </h2> + +<p> + systemd attempts to cover <em>more</em> ground instead of <em>less</em>. +In other words, rather than simply being an init system, it tries to be +a complete overhaul of the way a Linux system is run, and tries to force +other software to hook with it in order to be supported. This goes very +much against: +</p> + +<ul> + <li> The Unix philosophy, which is to do one job and do it well; </li> + <li> The <a href="http://www.catb.org/esr/writings/cathedral-bazaar/">bazaar</a> +approach that has made the free software ecosystem what it is today; </li> + <li> Cross-platform compatibility. BSD is not dead, Solaris is not dead, +but systemd ignores Unix. It even ignores Linux to some extent: the systemd +authors had the guts to ask for specific kernel interfaces! </li> +</ul> + +<p> + The reason why systemd has become so prevalent is not that it has been +accepted by the community. It's that it has manpower. It is backed up by +open source software companies that can provide much more manpower than +developers like myself working on free software on their own time. The +distribution model of systemd, made of lobbying and bullying, is much more +akin to the distribution model of Microsoft Windows than the one of GNU/Linux. +</p> + +<p> + Which says something. +</p> + +<h2> The technical issue </h2> + +<p> + Software that does <em>more</em> instead of <em>less</em> is, simply put, +badly designed software. Trying to come up with an all-encompassing solution +is always a sign of developer hubris and inexperience, and never a sign of +good engineering. Ever. Remember sendmail, BIND, INN, and, definitely a better +analogy, the early days of Microsoft Windows ? Yes, systemd is in +exactly the same league. It's as if we had learned <em>nothing</em> from the +mistakes of the past 20 years. Technically as well as politically, systemd +is actually very close to Windows; is that the future we want for Linux +machines ? +</p> + +<p> + Doing more instead of less is bad, and it's especially true in the case of +system software, i.e. low-level software that +aims to make the machine work and that application software depends upon. +The goal of an operating system is to make it possible to run <em>applications</em>, +and system software should always partake in that goal. <strong>System software +should stay the heck out of the way</strong>, and systemd is big, loud and +obnoxious. Embedded devices are common, and will become even more common in +the future; that is a market that systemd will have trouble breaking into, because +it's a lot more complex than embedded devices need. And that, too, says something: +if a software suite is too complex for an embedded device, maybe it's just too +complex, period. +</p> + +<h2> Links </h2> + +<ul> + <li> <a href="http://freedesktop.org/wiki/Software/systemd/">systemd's home page</a> </li> + <li> <a href="http://uselessd.darknedgy.net/ProSystemdAntiSystemd/">An analysis of +the vacuity of most Internet arguments about systemd</a>, by the author of +<a href="http://uselessd.darknedgy.net/">uselessd</a>. </li> + <li> <a href="http://boycottsystemd.org">boycottsystemd.org</a>, summarizing +political arguments against systemd </li> + <li> <a href="http://ewontfix.com/14/">Technical arguments against systemd</a>, +by Rich Felker, main author of <a href="http://musl-libc.org/">musl</a> </li> + <li> <a href="http://judecnelson.blogspot.fr/2014/09/systemd-biggest-fallacies.html">A +list of fallacies about systemd, with debunk</a> </li> +</ul> + +</body> +</html> diff --git a/doc/ucspilogd.html b/doc/ucspilogd.html new file mode 100644 index 0000000..1b06335 --- /dev/null +++ b/doc/ucspilogd.html @@ -0,0 +1,94 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: the ucspilogd program</title> + <meta name="Description" content="s6: the ucspilogd program" /> + <meta name="Keywords" content="s6 command ucspilogd log logging UCSPI" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="../">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> The <tt>ucspilogd</tt> program </h1> + +<p> + <tt>ucspilogd</tt> acts as a filter, converting syslog facility +numbers and alert levels into names. +</p> + +<h2> Interface </h2> + +<pre> + ucspilogd [ <em>var</em> ... ] +</pre> + +<ul> + <li> ucspilogd reads a stream of syslog-like messages on stdin. +Those messages can be newline-terminated or null-terminated. </li> + <li> For every line it reads: if it has been given <em>var</em> +arguments, it writes the value of every <em>var</em> environment +variable, followed by a colon and a space. </li> + <li> If the line begins with a syslog facility number and/or +alert level in the syslog format, it converts them into a +human-readable name in the syslogd fashion. </li> + <li> It then writes the processed line to stdout. </li> +</ul> + +<h2> Common use </h2> + +<p> + You can emulate the whole <em>syslogd</em> behaviour by combining the following +components: +</p> + +<ul> + <li> A Unix stream super-server such as +<a href="http://skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a> +listening to the Unix domain socket <tt>/dev/log</tt>, to connect to +the kernel log-reading interface. </li> + <li> <tt>ucspilogd</tt> running under that super-server, to read the +logs and perform adequate transformations. </li> + <li> A logger such as +<a href="s6-log.html">s6-log</a> +to store the logs into the filesystem. </li> + <li> A supervision mechanism such as s6, +to ensure ease of use and reliability of the whole chain. </li> +</ul> + +<p> + The resulting suite of programs is still smaller, and way more reliable, +than a standard <em>syslogd</em>. +</p> + +<p> + In the <tt>examples/ROOT/img/services-local/syslogd-linux</tt> subdirectory of the s6 package, you will +find a suitable ucspilogd <a href="servicedir.html">service directory</a>. +The run scripts are written in the +<a href="http://skarnet.org/software/execline/">execline</a> +language. +</p> + +<h2> Using <tt>ucspilogd</tt> as a <em>klogd</em> replacement </h2> + +<p> + Certain Unix kernels offer a nice interface to the kernel logs. +For instance, the Linux kernel provides the <tt>/proc/kmsg</tt> fake +file, that can be opened and read like a normal file, excepts that +it gives the kernel logs when they are available and blocks otherwise. +You can use <tt>ucspilogd</tt> to process data from those interfaces. +</p> + +<p> + The <tt>examples/ROOT/img/services-local/klogd-linux</tt> subdirectory of the s6 package +is a <a href="servicedir.html">service directory</a> providing such a <em>klogd</em> service +for Linux, using the <tt>/proc/kmsg</tt> interface. +</p> + +</body> +</html> diff --git a/doc/upgrade.html b/doc/upgrade.html new file mode 100644 index 0000000..1a35178 --- /dev/null +++ b/doc/upgrade.html @@ -0,0 +1,32 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>How to upgrade s6</title> + <meta name="Description" content="How to upgrade s6" /> + <meta name="Keywords" content="s6 installation upgrade" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> How to upgrade s6 </h1> + +<h2> to 2.0.0.0 </h2> + +<ul> + <li> The build system has completely changed. It is now a standard +<tt>./configure && make && sudo make install</tt> +build system. See the enclosed INSTALL file for details. </li> + <li> slashpackage is not activated by default. </li> + <li> shared libraries are not used by default. </li> + <li> skalibs dependency bumped to 2.0.0.0 </li> +</ul> + +</body> +</html> diff --git a/doc/why.html b/doc/why.html new file mode 100644 index 0000000..1901259 --- /dev/null +++ b/doc/why.html @@ -0,0 +1,203 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <meta http-equiv="Content-Language" content="en" /> + <title>s6: why another supervision suite</title> + <meta name="Description" content="s6: why another supervision suite" /> + <meta name="Keywords" content="s6 supervision daemontools runit perp service svscan supervise" /> + <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> --> + </head> +<body> + +<p> +<a href="index.html">s6</a><br /> +<a href="http://skarnet.org/software/">Software</a><br /> +<a href="http://skarnet.org/">skarnet.org</a> +</p> + +<h1> Why another supervision suite ? </h1> + +<p> + Supervision suites are becoming quite common. Today, we already have: +</p> + +<ul> + <li> Good (?) old System V init, which can be made to supervise services if you perform <tt>/etc/inittab</tt> voodoo. +BSD init can also be used the same way with the <tt>/etc/ttys</tt> file, but for some reason, nobody among BSD +developers is using <tt>/etc/ttys</tt> to this purpose, so I won't consider BSD init here. </li> + <li> <a href="http://cr.yp.to/daemontools.html">daemontools</a>, the pioneer </li> + <li> <a href="http://untroubled.org/daemontools-encore/">daemontools-encore</a>, Bruce Guenter's upgrade to daemontools </li> + <li> <a href="http://smarden.org/runit/">runit</a>, Gerrit Pape's suite, well-integrated with Debian </li> + <li> <a href="http://b0llix.net/perp/">perp</a>, Wayne Marshall's take on supervision </li> + <li> Integrated init systems providing a lot of features, process supervision being one of them. +For instance, <a href="http://upstart.ubuntu.com/">Upstart</a>, MacOS X's +<a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man8/launchd.8.html">launchd</a>, +and Fedora's <a href="http://freedesktop.org/wiki/Software/systemd">systemd</a>. </li> +</ul> + +<p> + Why is s6 needed ? What does it do differently ? Here are the criteria I used. +</p> + + +<h2> Supervision suites should not wake up unless notified. </h2> + +<ul> + <li> System V init fails the test: it wakes up every 5 seconds, for the reason that +<tt>/dev/initctl</tt> might have changed. +<a href="http://demotivate.me/mediafiles/full/4162010103911AM_picard-no-facepalm.jpg"><tt>m(</tt></a> </li> + <li> daemontools fails the test: it wakes up every 5 seconds to check for new services. </li> + <li> daemontools-encore does the same. </li> + <li> the current version of runit fails the test: it wakes up every 14 seconds. But this is a workaround for a bug in some Linux kernels; +there is no design flaw in runit that prevents it from passing the test. </li> + <li> perp works. </li> + <li> Upstart works. I have no idea what other integrated init systems do: it's much too difficult to strace them +to see exactly where they're spending their time, and when it is possible, the trace output is so big that it's +hard to extract any valuable information from it. </li> + <li> s6 works. By default, s6-svscan wakes up every 5 seconds, to emulate +<a href="http://cr.yp.to/daemontools/svscan.html">svscan</a> behaviour; but it +can be told not to do so. (<tt>s6-svscan -t0</tt>) </li> +</ul> + + +<h2> Supervision suites should provide a program that can run as process 1. </h2> + +<ul> + <li> System V init <em>is</em> process 1, so no problem here. </li> + <li> Integrated init systems, by definition, provide a process 1. </li> + <li> daemontools was not designed to take over init, although +<a href="http://code.dogmap.org./svscan-1/">it can be made to work</a> with +enough hacking skills. Same thing with daemontools-encore. </li> + <li> runit provides an <em>init</em> functionality, but the mechanism is +separate from the supervision itself; the <tt>runit</tt> process, not the +<tt>runsvdir</tt> process, runs as process 1. This lengthens the supervision +chain. </li> + <li> perp was not designed to run as process 1. It probably could be made to work too +without too much trouble. </li> + <li> s6-svscan was designed from the start to be run as process 1, although it +does not have to. </li> +</ul> + + +<h2> Supervision suites should be bug-free, lightweight and easy to understand. </h2> + +<ul> + <li> daemontools, daemontools-encore, runit and perp all qualify. All of this is excellent quality +code, <a href="http://skarnet.org/software/skalibs/djblegacy.html">unsurprisingly</a>. </li> + <li> System V init is understandable, and reasonably lightweight; but it is still +too big for what it does - poorly. The <tt>/etc/inittab</tt> file needs to be parsed; +that parser has to be in process 1. There is support in process 1 for the whole +"runlevel" concept, which is a primitive form of service management. The same +executable handles all 3 stages of the machine's lifetime and does not separate +them properly. All in all, System V init does its job, but is showing its age +and nowadays we know much better designs. </li> + <li> This is where integrated init systems fail, hard. By wanting to organize +the way a the machine is operated - so, machine state management - in the +<em>same package</em> as the init and process supervision system, they add +incredible complexity where it does not belong. + <ul> + <li> Upstart uses <tt>ptrace</tt> to watch its children fork(), and links +process 1 against libdbus. This is insane. +Process 1 should be <em>absolutely stable</em>, it should be guaranteed +to never crash, so the whole of its source code should be under control. At +Upstart's level of complexity, those goals are outright impossible to achieve, +so this approach is flawed by design. </li> + <li> launchd suffers from the same kind of problem. Regardless of how +things are actually implemented inside (which I have no idea about), services +running under launchd must be configured +<a href="https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html">using +XML</a>. That means there is an XML parser in process 1. +<a href="https://software.intel.com/sites/default/files/race.jpg">What +could possibly go wrong ?</a> </li> + <li> systemd is much, much worse than the other ones, and a real danger +for the future of GNU/Linux. I have a <a href="systemd.html">special page</a> +dedicated to it. </li> + </ul> + What those systems fail to recognize is that process supervision, rooted in +process 1, is a good thing, and machine management is also a good thing, but +<strong>those are two different functions</strong>, and a good init system +needs, and <strong>should</strong>, only provide process supervision, in +order to keep such a crucial piece of code as easy to maintain as possible. +Machine management can be added <em>on top of</em> a process supervision +suite, in a different package, and it has nothing to do with process 1. </li> + <li> s6, which has been designed with embedded environments in mind, tries +harder than anyone to pass this. It tries so hard that <tt>s6-svscan</tt> +and <tt>s6-supervise</tt>, the two long-running programs that make the +supervision chain, <em>do not even allocate heap memory</em>, and their main +program source files are less than 500 lines long. </li> +</ul> + + +<h2> Supervision suites should provide a basis for high-level service management. </h2> + +<ul> + <li> Neither System V init, daemontools, runit or perp +provides any hooks to wait for a service to go up or down. runit provides a +waiting mechanism, but it's based on polling, and the <tt>./check</tt> script +has to be manually written for every service. </li> + <li> daemontools-encore qualifies: the <em>notify script</em> can be used for +inter-service communication. But it's just a hook: all the real notification +work has to be done by the notify script itself, no notification framework is +provided. </li> + <li> Integrated init systems provide high-level service management +themselves. Again, this is not good design: service management has nothing +to do with init or process supervision, and should be implemented on top +of it, not as a part of it. </li> + <li> s6 comes with <a href="libftrig.html">libftrig</a>, an event notification +library, and command-line tools based on this library, thus providing a simple +API for future service management tools to build upon. </li> +</ul> + + +<h2> Artistic considerations </h2> + +<ul> + <li> <tt>s6-svscan</tt> and <tt>s6-supervise</tt> are <em>entirely asynchronous</em>. +Even during trouble (full process table, for instance), they'll remain reactive +and instantly respond to commands they may receive. <tt>s6-supervise</tt> has +even been implemented as a full deterministic finite automaton, to ensure it +always does the right thing under any circumstance. Other supervision suites +do not achieve that for now. </li> + <li> daemontools' <a href="http://cr.yp.to/daemontools/svscan.html">svscan</a> +maintains an open pipe between a daemon and its logger, so even if the daemon, +the logger, <em>and</em> both +<a href="http://cr.yp.to/daemontools/supervise.html">supervise</a> processes +die, the pipe is still the same so <em>no logs are lost, ever</em>, unless +svscan itself dies. </li> + <li> runit has only one supervisor, <a href="http://smarden.org/runit/runsv.8.html">runsv</a>, +for both a daemon and its logger. The pipe is maintained by <tt>runsv</tt>. +If the <tt>runsv</tt> process dies, the pipe disappears and logs are lost. +So, runit does not offer as strong a guarantee as daemontools. </li> + <li> perp has only one process, <a href="http://b0llix.net/perp/site.cgi?page=perpd.8">perpd</a>, +acting both as a "daemon and logger supervisor" (like <tt>runsv</tt>) and as a +"service directory scanner" (like <tt>runsvdir</tt>). It maintains the pipes +between the daemons and their respective loggers. If perpd dies, everything +is lost. Since perpd cannot be run as process 1, this is a possible SPOF for +a perp installation; however, perpd is well-written and has virtually no risk of +dying, especially compared to process 1 behemoths provided by integrated +init systems. </li> + <li> Besides, the <tt>runsv</tt> model, which has to handle both a daemon +and its logger, is more complex than the <tt>supervise</tt> model (which +only has to handle a daemon). Consequently, the <tt>runsvdir</tt> model is +simpler than the <tt>svscan</tt> model, but there is only one <tt>svscan</tt> +instance when there are several <tt>runsv</tt>s and <tt>supervise</tt>s. +The <tt>perpd</tt> model is obviously the most complex; while very understandable, +<tt>perpd</tt> is unarguably harder to maintain than the other two. </li> + <li> So, to achieve maximum simplicity and code reuse, and minimal memory +footprint, s6's design is close to daemontools' one. +And when <a href="s6-svscan-1.html">s6-svscan is run as process 1</a>, +pipes between daemons and loggers are never lost. </li> +</ul> + + +<h2> Conclusion </h2> + +<p> + All in all, I believe that s6 offers the best overall implementation of a +supervision suite <em>as it should be designed</em>. At worst, it's just another +take on daemontools with a <a href="http://skarnet.org/software/skalibs/">reliable +base library</a> and a few nifty features. +</p> + +</body> +</html> diff --git a/package/deps-build b/package/deps-build new file mode 100644 index 0000000..a966904 --- /dev/null +++ b/package/deps-build @@ -0,0 +1,2 @@ +/package/prog/skalibs +/package/admin/execline diff --git a/package/deps.mak b/package/deps.mak new file mode 100644 index 0000000..97fd866 --- /dev/null +++ b/package/deps.mak @@ -0,0 +1,101 @@ +# +# This file has been generated by tools/gen-deps.sh +# + +src/include/s6/ftrigr.h: src/include/s6/config.h +src/include/s6/s6.h: src/include/s6/ftrigr.h src/include/s6/ftrigw.h src/include/s6/s6-supervise.h src/include/s6/s6lock.h +src/include/s6/s6lock.h: src/include/s6/config.h +src/daemontools-extras/s6-envdir.o src/daemontools-extras/s6-envdir.lo: src/daemontools-extras/s6-envdir.c +src/daemontools-extras/s6-envuidgid.o src/daemontools-extras/s6-envuidgid.lo: src/daemontools-extras/s6-envuidgid.c +src/daemontools-extras/s6-fghack.o src/daemontools-extras/s6-fghack.lo: src/daemontools-extras/s6-fghack.c +src/daemontools-extras/s6-log.o src/daemontools-extras/s6-log.lo: src/daemontools-extras/s6-log.c +src/daemontools-extras/s6-notifywhenup.o src/daemontools-extras/s6-notifywhenup.lo: src/daemontools-extras/s6-notifywhenup.c src/include/s6/ftrigw.h +src/daemontools-extras/s6-setlock.o src/daemontools-extras/s6-setlock.lo: src/daemontools-extras/s6-setlock.c src/include/s6/config.h +src/daemontools-extras/s6-setsid.o src/daemontools-extras/s6-setsid.lo: src/daemontools-extras/s6-setsid.c +src/daemontools-extras/s6-setuidgid.o src/daemontools-extras/s6-setuidgid.lo: src/daemontools-extras/s6-setuidgid.c +src/daemontools-extras/s6-softlimit.o src/daemontools-extras/s6-softlimit.lo: src/daemontools-extras/s6-softlimit.c +src/daemontools-extras/s6-tai64n.o src/daemontools-extras/s6-tai64n.lo: src/daemontools-extras/s6-tai64n.c +src/daemontools-extras/s6-tai64nlocal.o src/daemontools-extras/s6-tai64nlocal.lo: src/daemontools-extras/s6-tai64nlocal.c +src/daemontools-extras/ucspilogd.o src/daemontools-extras/ucspilogd.lo: src/daemontools-extras/ucspilogd.c +src/libs6/ftrig1_free.o src/libs6/ftrig1_free.lo: src/libs6/ftrig1_free.c src/libs6/ftrig1.h +src/libs6/ftrig1_make.o src/libs6/ftrig1_make.lo: src/libs6/ftrig1_make.c src/libs6/ftrig1.h +src/libs6/ftrigr1_zero.o src/libs6/ftrigr1_zero.lo: src/libs6/ftrigr1_zero.c src/include/s6/ftrigr.h +src/libs6/ftrigr_check.o src/libs6/ftrigr_check.lo: src/libs6/ftrigr_check.c src/include/s6/ftrigr.h +src/libs6/ftrigr_end.o src/libs6/ftrigr_end.lo: src/libs6/ftrigr_end.c src/include/s6/ftrigr.h +src/libs6/ftrigr_start.o src/libs6/ftrigr_start.lo: src/libs6/ftrigr_start.c src/include/s6/ftrigr.h +src/libs6/ftrigr_startf.o src/libs6/ftrigr_startf.lo: src/libs6/ftrigr_startf.c src/include/s6/ftrigr.h +src/libs6/ftrigr_subscribe.o src/libs6/ftrigr_subscribe.lo: src/libs6/ftrigr_subscribe.c src/include/s6/ftrigr.h +src/libs6/ftrigr_unsubscribe.o src/libs6/ftrigr_unsubscribe.lo: src/libs6/ftrigr_unsubscribe.c src/include/s6/ftrigr.h +src/libs6/ftrigr_update.o src/libs6/ftrigr_update.lo: src/libs6/ftrigr_update.c src/include/s6/ftrigr.h +src/libs6/ftrigr_wait_and.o src/libs6/ftrigr_wait_and.lo: src/libs6/ftrigr_wait_and.c src/include/s6/ftrigr.h +src/libs6/ftrigr_wait_or.o src/libs6/ftrigr_wait_or.lo: src/libs6/ftrigr_wait_or.c src/include/s6/ftrigr.h +src/libs6/ftrigr_zero.o src/libs6/ftrigr_zero.lo: src/libs6/ftrigr_zero.c src/include/s6/ftrigr.h +src/libs6/ftrigw_clean.o src/libs6/ftrigw_clean.lo: src/libs6/ftrigw_clean.c src/libs6/ftrig1.h src/include/s6/ftrigw.h +src/libs6/ftrigw_fifodir_make.o src/libs6/ftrigw_fifodir_make.lo: src/libs6/ftrigw_fifodir_make.c src/include/s6/ftrigw.h +src/libs6/ftrigw_notify.o src/libs6/ftrigw_notify.lo: src/libs6/ftrigw_notify.c src/include/s6/ftrigw.h +src/libs6/ftrigw_notifyb.o src/libs6/ftrigw_notifyb.lo: src/libs6/ftrigw_notifyb.c src/libs6/ftrig1.h src/include/s6/ftrigw.h +src/libs6/s6-ftrigrd.o src/libs6/s6-ftrigrd.lo: src/libs6/s6-ftrigrd.c src/libs6/ftrig1.h src/include/s6/ftrigr.h +src/libs6/s6_supervise_lock.o src/libs6/s6_supervise_lock.lo: src/libs6/s6_supervise_lock.c src/include/s6/s6-supervise.h +src/libs6/s6_supervise_lock_mode.o src/libs6/s6_supervise_lock_mode.lo: src/libs6/s6_supervise_lock_mode.c src/include/s6/s6-supervise.h +src/libs6/s6_svc_main.o src/libs6/s6_svc_main.lo: src/libs6/s6_svc_main.c src/include/s6/s6-supervise.h +src/libs6/s6_svc_write.o src/libs6/s6_svc_write.lo: src/libs6/s6_svc_write.c src/include/s6/s6-supervise.h +src/libs6/s6_svstatus_pack.o src/libs6/s6_svstatus_pack.lo: src/libs6/s6_svstatus_pack.c src/include/s6/s6-supervise.h +src/libs6/s6_svstatus_read.o src/libs6/s6_svstatus_read.lo: src/libs6/s6_svstatus_read.c src/include/s6/s6-supervise.h +src/libs6/s6_svstatus_unpack.o src/libs6/s6_svstatus_unpack.lo: src/libs6/s6_svstatus_unpack.c src/include/s6/s6-supervise.h +src/libs6/s6_svstatus_write.o src/libs6/s6_svstatus_write.lo: src/libs6/s6_svstatus_write.c src/include/s6/s6-supervise.h +src/libs6/s6lock_acquire.o src/libs6/s6lock_acquire.lo: src/libs6/s6lock_acquire.c src/include/s6/s6lock.h +src/libs6/s6lock_check.o src/libs6/s6lock_check.lo: src/libs6/s6lock_check.c src/include/s6/s6lock.h +src/libs6/s6lock_end.o src/libs6/s6lock_end.lo: src/libs6/s6lock_end.c src/include/s6/s6lock.h +src/libs6/s6lock_release.o src/libs6/s6lock_release.lo: src/libs6/s6lock_release.c src/include/s6/s6lock.h +src/libs6/s6lock_start.o src/libs6/s6lock_start.lo: src/libs6/s6lock_start.c src/include/s6/s6lock.h +src/libs6/s6lock_startf.o src/libs6/s6lock_startf.lo: src/libs6/s6lock_startf.c src/include/s6/s6lock.h +src/libs6/s6lock_update.o src/libs6/s6lock_update.lo: src/libs6/s6lock_update.c src/include/s6/s6lock.h +src/libs6/s6lock_wait_and.o src/libs6/s6lock_wait_and.lo: src/libs6/s6lock_wait_and.c src/include/s6/s6lock.h +src/libs6/s6lock_wait_or.o src/libs6/s6lock_wait_or.lo: src/libs6/s6lock_wait_or.c src/include/s6/s6lock.h +src/libs6/s6lock_zero.o src/libs6/s6lock_zero.lo: src/libs6/s6lock_zero.c src/include/s6/s6lock.h +src/libs6/s6lockd-helper.o src/libs6/s6lockd-helper.lo: src/libs6/s6lockd-helper.c +src/libs6/s6lockd.o src/libs6/s6lockd.lo: src/libs6/s6lockd.c src/include/s6/s6lock.h +src/pipe-tools/s6-cleanfifodir.o src/pipe-tools/s6-cleanfifodir.lo: src/pipe-tools/s6-cleanfifodir.c src/include/s6/ftrigw.h +src/pipe-tools/s6-ftrig-listen.o src/pipe-tools/s6-ftrig-listen.lo: src/pipe-tools/s6-ftrig-listen.c src/include/s6/ftrigr.h +src/pipe-tools/s6-ftrig-listen1.o src/pipe-tools/s6-ftrig-listen1.lo: src/pipe-tools/s6-ftrig-listen1.c src/include/s6/ftrigr.h +src/pipe-tools/s6-ftrig-notify.o src/pipe-tools/s6-ftrig-notify.lo: src/pipe-tools/s6-ftrig-notify.c src/include/s6/ftrigw.h +src/pipe-tools/s6-ftrig-wait.o src/pipe-tools/s6-ftrig-wait.lo: src/pipe-tools/s6-ftrig-wait.c src/include/s6/ftrigr.h +src/pipe-tools/s6-mkfifodir.o src/pipe-tools/s6-mkfifodir.lo: src/pipe-tools/s6-mkfifodir.c src/include/s6/ftrigw.h +src/supervision/s6-supervise.o src/supervision/s6-supervise.lo: src/supervision/s6-supervise.c src/include/s6/ftrigw.h src/include/s6/s6-supervise.h +src/supervision/s6-svc.o src/supervision/s6-svc.lo: src/supervision/s6-svc.c src/include/s6/s6-supervise.h +src/supervision/s6-svok.o src/supervision/s6-svok.lo: src/supervision/s6-svok.c src/include/s6/s6-supervise.h +src/supervision/s6-svscan.o src/supervision/s6-svscan.lo: src/supervision/s6-svscan.c src/include/s6/config.h src/include/s6/s6-supervise.h +src/supervision/s6-svscanctl.o src/supervision/s6-svscanctl.lo: src/supervision/s6-svscanctl.c src/include/s6/s6-supervise.h +src/supervision/s6-svstat.o src/supervision/s6-svstat.lo: src/supervision/s6-svstat.c src/include/s6/s6-supervise.h +src/supervision/s6-svwait.o src/supervision/s6-svwait.lo: src/supervision/s6-svwait.c src/include/s6/ftrigr.h src/include/s6/s6-supervise.h + +s6-envdir: src/daemontools-extras/s6-envdir.o -lskarnet +s6-envuidgid: src/daemontools-extras/s6-envuidgid.o -lskarnet +s6-fghack: src/daemontools-extras/s6-fghack.o -lskarnet +s6-log: src/daemontools-extras/s6-log.o -lskarnet ${TAINNOW_LIB} +s6-notifywhenup: src/daemontools-extras/s6-notifywhenup.o -ls6 -lskarnet ${TAINNOW_LIB} +s6-setlock: src/daemontools-extras/s6-setlock.o -lskarnet ${TAINNOW_LIB} +s6-setsid: src/daemontools-extras/s6-setsid.o -lskarnet +s6-setuidgid: src/daemontools-extras/s6-setuidgid.o -lskarnet +s6-softlimit: src/daemontools-extras/s6-softlimit.o -lskarnet +s6-tai64n: src/daemontools-extras/s6-tai64n.o -lskarnet ${SYSCLOCK_LIB} +s6-tai64nlocal: src/daemontools-extras/s6-tai64nlocal.o -lskarnet +ucspilogd: src/daemontools-extras/ucspilogd.o -lskarnet +libs6.a: src/libs6/ftrigr1_zero.o src/libs6/ftrigr_check.o src/libs6/ftrigr_end.o src/libs6/ftrigr_start.o src/libs6/ftrigr_startf.o src/libs6/ftrigr_subscribe.o src/libs6/ftrigr_unsubscribe.o src/libs6/ftrigr_update.o src/libs6/ftrigr_wait_and.o src/libs6/ftrigr_wait_or.o src/libs6/ftrigr_zero.o src/libs6/ftrigw_clean.o src/libs6/ftrigw_fifodir_make.o src/libs6/ftrigw_notify.o src/libs6/ftrigw_notifyb.o src/libs6/s6_supervise_lock.o src/libs6/s6_supervise_lock_mode.o src/libs6/s6_svc_main.o src/libs6/s6_svc_write.o src/libs6/s6_svstatus_pack.o src/libs6/s6_svstatus_read.o src/libs6/s6_svstatus_unpack.o src/libs6/s6_svstatus_write.o src/libs6/s6lock_acquire.o src/libs6/s6lock_check.o src/libs6/s6lock_end.o src/libs6/s6lock_release.o src/libs6/s6lock_start.o src/libs6/s6lock_startf.o src/libs6/s6lock_update.o src/libs6/s6lock_wait_and.o src/libs6/s6lock_wait_or.o src/libs6/s6lock_zero.o +libs6.so: src/libs6/ftrigr1_zero.lo src/libs6/ftrigr_check.lo src/libs6/ftrigr_end.lo src/libs6/ftrigr_start.lo src/libs6/ftrigr_startf.lo src/libs6/ftrigr_subscribe.lo src/libs6/ftrigr_unsubscribe.lo src/libs6/ftrigr_update.lo src/libs6/ftrigr_wait_and.lo src/libs6/ftrigr_wait_or.lo src/libs6/ftrigr_zero.lo src/libs6/ftrigw_clean.lo src/libs6/ftrigw_fifodir_make.lo src/libs6/ftrigw_notify.lo src/libs6/ftrigw_notifyb.lo src/libs6/s6_supervise_lock.lo src/libs6/s6_supervise_lock_mode.lo src/libs6/s6_svc_main.lo src/libs6/s6_svc_write.lo src/libs6/s6_svstatus_pack.lo src/libs6/s6_svstatus_read.lo src/libs6/s6_svstatus_unpack.lo src/libs6/s6_svstatus_write.lo src/libs6/s6lock_acquire.lo src/libs6/s6lock_check.lo src/libs6/s6lock_end.lo src/libs6/s6lock_release.lo src/libs6/s6lock_start.lo src/libs6/s6lock_startf.lo src/libs6/s6lock_update.lo src/libs6/s6lock_wait_and.lo src/libs6/s6lock_wait_or.lo src/libs6/s6lock_zero.lo +s6-ftrigrd: src/libs6/s6-ftrigrd.o src/libs6/ftrig1_free.o src/libs6/ftrig1_make.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB} +s6lockd: src/libs6/s6lockd.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB} +s6lockd-helper: src/libs6/s6lockd-helper.o -lskarnet +s6-cleanfifodir: src/pipe-tools/s6-cleanfifodir.o -ls6 -lskarnet +s6-ftrig-listen: src/pipe-tools/s6-ftrig-listen.o -ls6 -lexecline -lskarnet ${TAINNOW_LIB} +s6-ftrig-listen1: src/pipe-tools/s6-ftrig-listen1.o -ls6 -lskarnet ${TAINNOW_LIB} +s6-ftrig-notify: src/pipe-tools/s6-ftrig-notify.o -ls6 -lskarnet +s6-ftrig-wait: src/pipe-tools/s6-ftrig-wait.o -ls6 -lskarnet ${TAINNOW_LIB} +s6-mkfifodir: src/pipe-tools/s6-mkfifodir.o -ls6 -lskarnet +s6-supervise: src/supervision/s6-supervise.o -ls6 -lskarnet ${TAINNOW_LIB} +s6-svc: src/supervision/s6-svc.o -ls6 -lskarnet +s6-svok: src/supervision/s6-svok.o -lskarnet +s6-svscan: src/supervision/s6-svscan.o -ls6 -lskarnet ${TAINNOW_LIB} +s6-svscanctl: src/supervision/s6-svscanctl.o -ls6 -lskarnet +s6-svstat: src/supervision/s6-svstat.o -ls6 -lskarnet ${SYSCLOCK_LIB} +s6-svwait: src/supervision/s6-svwait.o -ls6 -lskarnet ${TAINNOW_LIB} diff --git a/package/info b/package/info new file mode 100644 index 0000000..0686726 --- /dev/null +++ b/package/info @@ -0,0 +1,4 @@ +package=s6 +version=2.0.0.0 +category=admin +package_macro_name=S6 diff --git a/package/modes b/package/modes new file mode 100644 index 0000000..d3fb2e0 --- /dev/null +++ b/package/modes @@ -0,0 +1,28 @@ +s6-ftrigrd 0755 +s6-ftrig-listen1 0755 +s6-ftrig-listen 0755 +s6-ftrig-notify 0755 +s6-ftrig-wait 0755 +s6lockd 0755 +s6lockd-helper 0755 +s6-cleanfifodir 0755 +s6-mkfifodir 0755 +s6-notifywhenup 0755 +s6-svscan 0755 +s6-supervise 0755 +s6-svc 0755 +s6-svscanctl 0755 +s6-svok 0755 +s6-svstat 0755 +s6-svwait 0755 +s6-envdir 0755 +s6-envuidgid 0755 +s6-fghack 0755 +s6-log 0755 +s6-setlock 0755 +s6-setsid 0755 +s6-setuidgid 0700 +s6-softlimit 0755 +s6-tai64n 0755 +s6-tai64nlocal 0755 +ucspilogd 0755 diff --git a/package/targets.mak b/package/targets.mak new file mode 100644 index 0000000..9387792 --- /dev/null +++ b/package/targets.mak @@ -0,0 +1,36 @@ +BIN_TARGETS = \ +s6-ftrigrd \ +s6-ftrig-listen1 \ +s6-ftrig-listen \ +s6-ftrig-notify \ +s6-ftrig-wait \ +s6lockd \ +s6-cleanfifodir \ +s6-mkfifodir \ +s6-notifywhenup \ +s6-svscan \ +s6-supervise \ +s6-svc \ +s6-svscanctl \ +s6-svok \ +s6-svstat \ +s6-svwait \ +s6-envdir \ +s6-envuidgid \ +s6-fghack \ +s6-log \ +s6-setlock \ +s6-setsid \ +s6-setuidgid \ +s6-softlimit \ +s6-tai64n \ +s6-tai64nlocal + +LIBEXEC_TARGETS = \ +s6lockd-helper + +SHARED_LIBS = \ +libs6.so + +STATIC_LIBS = \ +libs6.a diff --git a/patch-for-solaris b/patch-for-solaris new file mode 100755 index 0000000..02f2e3c --- /dev/null +++ b/patch-for-solaris @@ -0,0 +1,17 @@ +#!/usr/xpg4/bin/sh + +patchit () { + echo '#!/usr/xpg4/bin/sh' > $1.tmp + tail -n +2 $1 >> $1.tmp + mv -f $1.tmp $1 + chmod 755 $1 +} + +patchit ./configure +patchit ./tools/install.sh +patchit ./tools/gen-deps.sh + +echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp +echo >> Makefile.tmp +cat Makefile >> Makefile.tmp +mv -f Makefile.tmp Makefile diff --git a/src/daemontools-extras/deps-exe/s6-envdir b/src/daemontools-extras/deps-exe/s6-envdir new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-envdir @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-envuidgid b/src/daemontools-extras/deps-exe/s6-envuidgid new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-envuidgid @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-fghack b/src/daemontools-extras/deps-exe/s6-fghack new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-fghack @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-log b/src/daemontools-extras/deps-exe/s6-log new file mode 100644 index 0000000..1840bc1 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-log @@ -0,0 +1,2 @@ +-lskarnet +${TAINNOW_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-notifywhenup b/src/daemontools-extras/deps-exe/s6-notifywhenup new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-notifywhenup @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-setlock b/src/daemontools-extras/deps-exe/s6-setlock new file mode 100644 index 0000000..1840bc1 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-setlock @@ -0,0 +1,2 @@ +-lskarnet +${TAINNOW_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-setsid b/src/daemontools-extras/deps-exe/s6-setsid new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-setsid @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-setuidgid b/src/daemontools-extras/deps-exe/s6-setuidgid new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-setuidgid @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-softlimit b/src/daemontools-extras/deps-exe/s6-softlimit new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-softlimit @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-tai64n b/src/daemontools-extras/deps-exe/s6-tai64n new file mode 100644 index 0000000..a11a5f4 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-tai64n @@ -0,0 +1,2 @@ +-lskarnet +${SYSCLOCK_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-tai64nlocal b/src/daemontools-extras/deps-exe/s6-tai64nlocal new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-tai64nlocal @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/ucspilogd b/src/daemontools-extras/deps-exe/ucspilogd new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/ucspilogd @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/s6-envdir.c b/src/daemontools-extras/s6-envdir.c new file mode 100644 index 0000000..394253e --- /dev/null +++ b/src/daemontools-extras/s6-envdir.c @@ -0,0 +1,40 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/stralloc.h> +#include <skalibs/env.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6-envdir [ -I | -i ] [ -n ] [ -f ] [ -c nullchar ] dir prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + stralloc modifs = STRALLOC_ZERO ; + subgetopt_t l = SUBGETOPT_ZERO ; + int insist = 1 ; + unsigned int options = 0 ; + char nullis = '\n' ; + PROG = "s6-envdir" ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "Iinfc:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'I' : insist = 0 ; break ; + case 'i' : insist = 1 ; break ; + case 'n' : options |= SKALIBS_ENVDIR_NOCHOMP ; break ; + case 'f' : options |= SKALIBS_ENVDIR_VERBATIM ; break ; + case 'c' : nullis = *l.arg ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if ((envdir_internal(*argv++, &modifs, options, nullis) < 0) && (insist || (errno != ENOENT))) + strerr_diefu1sys(111, "envdir") ; + pathexec_r(argv, envp, env_len(envp), modifs.s, modifs.len) ; + strerr_dieexec(111, argv[0]) ; +} diff --git a/src/daemontools-extras/s6-envuidgid.c b/src/daemontools-extras/s6-envuidgid.c new file mode 100644 index 0000000..64521b0 --- /dev/null +++ b/src/daemontools-extras/s6-envuidgid.c @@ -0,0 +1,44 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <pwd.h> +#include <limits.h> +#include <skalibs/gidstuff.h> +#include <skalibs/env.h> +#include <skalibs/strerr2.h> +#include <skalibs/fmtscan.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6-envuidgid username prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "s6-envuidgid" ; + if (argc < 3) strerr_dieusage(100, USAGE) ; + { + char fmt[UINT64_FMT] ; + struct passwd *pw = getpwnam(argv[1]) ; + if (!pw) strerr_dief2x(1, "unknown user: ", argv[1]) ; + fmt[gid_fmt(fmt, pw->pw_gid)] = 0 ; + if (!pathexec_env("GID", fmt)) + strerr_diefu1sys(111, "update environment") ; + fmt[uint64_fmt(fmt, pw->pw_uid)] = 0 ; + if (!pathexec_env("UID", fmt)) + strerr_diefu1sys(111, "update environment") ; + } + + { + gid_t tab[NGROUPS_MAX] ; + int n = prot_readgroups(argv[1], tab, NGROUPS_MAX) ; + if (n < 0) + strerr_diefu2sys(111, "get supplementary groups for ", argv[1]) ; + { + char fmt[GID_FMT * n] ; + fmt[gid_fmtlist(fmt, tab, n)] = 0 ; + if (!pathexec_env("GIDLIST", fmt)) + strerr_diefu1sys(111, "update environment") ; + } + } + pathexec_fromenv(argv+2, envp, env_len(envp)) ; + strerr_dieexec(111, argv[2]) ; +} diff --git a/src/daemontools-extras/s6-fghack.c b/src/daemontools-extras/s6-fghack.c new file mode 100644 index 0000000..1415706 --- /dev/null +++ b/src/daemontools-extras/s6-fghack.c @@ -0,0 +1,68 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#include <sys/wait.h> +#include <skalibs/strerr2.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6-fghack prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + int p[2] ; + int pcoe[2] ; + pid_t pid ; + char dummy ; + PROG = "s6-fghack" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if (pipe(p) < 0) strerr_diefu1sys(111, "create hackpipe") ; + if (pipe(pcoe) < 0) strerr_diefu1sys(111, "create coepipe") ; + + switch (pid = fork()) + { + case -1 : strerr_diefu1sys(111, "fork") ; + case 0 : + { + int i = 0 ; + fd_close(p[0]) ; + fd_close(pcoe[0]) ; + if (coe(pcoe[1]) < 0) _exit(111) ; + for (; i < 30 ; i++) dup(p[1]) ; /* hack. gcc's warning is justified. */ + pathexec_run(argv[1], argv+1, envp) ; + i = errno ; + if (fd_write(pcoe[1], "", 1) < 1) _exit(111) ; + _exit(i) ; + } + } + + fd_close(p[1]) ; + fd_close(pcoe[1]) ; + + switch (fd_read(pcoe[0], &dummy, 1)) + { + case -1 : strerr_diefu1sys(111, "read on coepipe") ; + case 1 : + { + int wstat ; + if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ; + errno = WEXITSTATUS(wstat) ; + strerr_dieexec(111, argv[1]) ; + } + } + + fd_close(pcoe[0]) ; + + p[1] = fd_read(p[0], &dummy, 1) ; + if (p[1] < 0) strerr_diefu1sys(111, "read on hackpipe") ; + if (p[1]) strerr_dief2x(102, argv[1], " wrote on hackpipe") ; + + { + int wstat ; + if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ; + if (WIFSIGNALED(wstat)) strerr_dief2x(111, argv[2], " crashed") ; + return WEXITSTATUS(wstat) ; + } +} diff --git a/src/daemontools-extras/s6-log.c b/src/daemontools-extras/s6-log.c new file mode 100644 index 0000000..e788016 --- /dev/null +++ b/src/daemontools-extras/s6-log.c @@ -0,0 +1,1265 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#include <signal.h> +#include <unistd.h> +#include <stdio.h> /* for rename() */ +#include <stdlib.h> /* for qsort() */ +#include <regex.h> +#include <skalibs/uint32.h> +#include <skalibs/uint64.h> +#include <skalibs/uint.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/buffer.h> +#include <skalibs/bytestr.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/fmtscan.h> +#include <skalibs/bufalloc.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/tai.h> +#include <skalibs/error.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <skalibs/direntry.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <skalibs/skamisc.h> +#include <skalibs/environ.h> + +#define USAGE "s6-log [ -q | -v ] [ -b ] [ -p ] [ -t ] [ -e ] logging_script" +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") + +static int flagstampalert = 0 ; +static int flagstamp = 0 ; +static int flagprotect = 0 ; +static int flagexiting = 0 ; +static unsigned int verbosity = 1 ; + +static stralloc indata = STRALLOC_ZERO ; + + +/* Begin datatypes. Get ready for some lulz. */ + +typedef int qcmpfunc_t (void const *, void const *) ; +typedef qcmpfunc_t *qcmpfunc_t_ref ; + +typedef enum rotstate_e rotstate_t, *rotstate_t_ref ; +enum rotstate_e +{ + ROTSTATE_WRITABLE, + ROTSTATE_START, + ROTSTATE_RENAME, + ROTSTATE_NEWCURRENT, + ROTSTATE_CHMODPREVIOUS, + ROTSTATE_FINISHPREVIOUS, + ROTSTATE_RUNPROCESSOR, + ROTSTATE_WAITPROCESSOR, + ROTSTATE_SYNCPROCESSED, + ROTSTATE_SYNCNEWSTATE, + ROTSTATE_UNLINKPREVIOUS, + ROTSTATE_RENAMESTATE, + ROTSTATE_FINISHPROCESSED, + ROTSTATE_ENDFCHMOD, + ROTSTATE_END +} ; + +typedef enum seltype_e seltype_t, *seltype_t_ref ; +enum seltype_e +{ + SELTYPE_DEFAULT, + SELTYPE_PLUS, + SELTYPE_MINUS, + SELTYPE_PHAIL +} ; + +typedef struct sel_s sel_t, *sel_t_ref ; +struct sel_s +{ + seltype_t type ; + regex_t re ; +} ; + +#define SEL_ZERO { .type = SELTYPE_PHAIL } + +static void sel_free (sel_t_ref s) +{ + if (s->type != SELTYPE_DEFAULT) regfree(&s->re) ; + s->type = SELTYPE_PHAIL ; +} + +typedef enum acttype_e acttype_t, *acttype_t_ref ; +enum acttype_e +{ + ACTTYPE_FD2, + ACTTYPE_STATUS, + ACTTYPE_DIR, + ACTTYPE_PHAIL +} ; + +typedef struct as_fd2_s as_fd2_t, *as_fd2_t_ref ; +struct as_fd2_s +{ + unsigned int size ; +} ; + +typedef struct as_status_s as_status_t, *as_status_t_ref ; +struct as_status_s +{ + stralloc content ; + char const *file ; +} ; + +static void as_status_free (as_status_t_ref ap) +{ + stralloc_free(&ap->content) ; + ap->file = 0 ; +} + +typedef struct as_dir_s as_dir_t, *as_dir_t_ref ; +struct as_dir_s +{ + unsigned int lindex ; +} ; + +typedef union actstuff_u actstuff_t, *actstuff_t_ref ; +union actstuff_u +{ + as_fd2_t fd2 ; + as_status_t status ; + as_dir_t dir ; +} ; + +typedef struct act_s act_t, *act_t_ref ; +struct act_s +{ + acttype_t type ; + actstuff_t data ; +} ; + +static void act_free (act_t_ref ap) +{ + switch (ap->type) + { + case ACTTYPE_FD2 : + break ; + case ACTTYPE_STATUS : + as_status_free(&ap->data.status) ; + break ; + case ACTTYPE_DIR : + break ; + default : break ; + } + ap->type = ACTTYPE_PHAIL ; +} + +typedef struct scriptelem_s scriptelem_t, *scriptelem_t_ref ; +struct scriptelem_s +{ + genalloc selections ; /* array of sel_t */ + genalloc actions ; /* array of act_t */ +} ; + +#define SCRIPTELEM_ZERO { .selections = GENALLOC_ZERO, .actions = GENALLOC_ZERO } + +static void scriptelem_free (scriptelem_t_ref se) +{ + scriptelem_t zero = SCRIPTELEM_ZERO ; + genalloc_deepfree(sel_t, &se->selections, &sel_free) ; + genalloc_deepfree(act_t, &se->actions, &act_free) ; + *se = zero ; +} + +typedef void inputprocfunc_t (scriptelem_t const *, unsigned int) ; +typedef inputprocfunc_t *inputprocfunc_t_ref ; + +typedef struct logdir_s logdir_t, *logdir_t_ref ; +struct logdir_s +{ + bufalloc out ; + tain_t retrytto ; + tain_t deadline ; + uint64 maxdirsize ; + uint32 b ; + uint32 n ; + uint32 s ; + uint32 tolerance ; + unsigned int pid ; + char const *dir ; + char const *processor ; + int fd ; + int fdlock ; + rotstate_t rstate ; +} ; + +#define LOGDIR_ZERO { \ + .out = BUFALLOC_ZERO, \ + .retrytto = TAIN_ZERO, \ + .deadline = TAIN_ZERO, \ + .maxdirsize = 0, \ + .b = 0, \ + .n = 0, \ + .s = 0, \ + .tolerance = 0, \ + .pid = 0, \ + .dir = 0, \ + .processor = 0, \ + .fd = -1, \ + .fdlock = -1, \ + .rstate = ROTSTATE_WRITABLE \ +} + + /* If freeing a logdir before exiting is ever needed: +static void logdir_free (logdir_t_ref ldp) +{ + bufalloc_free(&ldp->out) ; + fd_close(ldp->fd) ; ldp->fd = -1 ; + fd_close(ldp->fdlock) ; ldp->fdlock = -1 ; +} + */ + +/* End datatypes. All of this was just to optimize the script interpretation. :-) */ + +static genalloc logdirs = GENALLOC_ZERO ; /* array of logdir_t */ + +typedef struct filesize_s filesize_t, *filesize_t_ref ; +struct filesize_s +{ + uint64 size ; + char name[28] ; +} ; + +static int filesize_cmp (filesize_t const *a, filesize_t const *b) +{ + return byte_diff(a->name+1, 26, b->name+1) ; +} + +static int name_is_relevant (char const *name) +{ + if (name[0] != '@') return 0 ; + if (str_len(name) != 27) return 0 ; + { + char tmp[12] ; + if (!ucharn_scan(name+1, tmp, 12)) return 0 ; + } + if (name[25] != '.') return 0 ; + if ((name[26] != 's') && (name[26] != 'u')) return 0 ; + return 1 ; +} + +static inline int logdir_trim (logdir_t_ref ldp) +{ + unsigned int n = 0 ; + DIR *dir = opendir(ldp->dir) ; + if (!dir) return -1 ; + for (;;) + { + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (name_is_relevant(d->d_name)) n++ ; + } + if (errno) + { + register int e = errno ; + dir_close(dir) ; + errno = e ; + return -1 ; + } + rewinddir(dir) ; + { + filesize_t blurgh[n] ; + uint64 totalsize = 0 ; + unsigned int dirlen = str_len(ldp->dir) ; + unsigned int i = 0 ; + char fullname[dirlen + 29] ; + byte_copy(fullname, dirlen, ldp->dir) ; + fullname[dirlen] = '/' ; + for (;;) + { + struct stat st ; + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (!name_is_relevant(d->d_name)) continue ; + if (i >= n) { errno = EBUSY ; break ; } + byte_copy(fullname + dirlen + 1, 28, d->d_name) ; + if (stat(fullname, &st) < 0) + { + if (verbosity) strerr_warnwu2sys("stat ", fullname) ; + continue ; + } + byte_copy(blurgh[i].name, 28, d->d_name) ; + blurgh[i].size = st.st_size ; + totalsize += st.st_size ; + i++ ; + } + if (errno) + { + register int e = errno ; + dir_close(dir) ; + errno = e ; + return -1 ; + } + dir_close(dir) ; + if ((i <= ldp->n) && (!ldp->maxdirsize || (totalsize <= ldp->maxdirsize))) + return 0 ; + qsort(blurgh, i, sizeof(filesize_t), (qcmpfunc_t_ref)&filesize_cmp) ; + n = 0 ; + while ((i > ldp->n + n) || (ldp->maxdirsize && (totalsize > ldp->maxdirsize))) + { + byte_copy(fullname + dirlen + 1, 28, blurgh[n].name) ; + if (unlink(fullname) < 0) + { + if (errno == ENOENT) totalsize -= blurgh[n].size ; + if (verbosity) strerr_warnwu2sys("unlink ", fullname) ; + } + else totalsize -= blurgh[n].size ; + n++ ; + } + } + return n ; +} + +static int finish (logdir_t *ldp, char const *name, char suffix) +{ + struct stat st ; + unsigned int dirlen = str_len(ldp->dir) ; + unsigned int namelen = str_len(name) ; + char x[dirlen + namelen + 2] ; + byte_copy(x, dirlen, ldp->dir) ; + x[dirlen] = '/' ; + byte_copy(x + dirlen + 1, namelen + 1, name) ; + if (stat(x, &st) < 0) return (errno == ENOENT) ; + if (st.st_nlink == 1) + { + char y[dirlen + 29] ; + byte_copy(y, dirlen, ldp->dir) ; + y[dirlen] = '/' ; + timestamp_g(y + dirlen + 1) ; + y[dirlen + 26] = '.' ; + y[dirlen + 27] = suffix ; + y[dirlen + 28] = 0 ; + if (link(x, y) < 0) return 0 ; + } + if (unlink(x) < 0) return 0 ; + return logdir_trim(ldp) ; +} + +static inline void exec_processor (logdir_t_ref ldp) +{ + char const *cargv[4] = { "execlineb", "-Pc", ldp->processor, 0 } ; + unsigned int dirlen = str_len(ldp->dir) ; + int fd ; + char x[dirlen + 10] ; + PROG = "s6-log (processor child)" ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/previous") ; + fd = open_readb(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_readb ", x) ; + if (fd_move(0, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + byte_copy(x + dirlen + 1, 10, "processed") ; + fd = open_trunc(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ; + if (fd_move(1, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + byte_copy(x + dirlen + 1, 6, "state") ; + fd = open_readb(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_readb ", x) ; + if (fd_move(4, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + byte_copy(x + dirlen + 1, 9, "newstate") ; + fd = open_trunc(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ; + if (fd_move(5, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + selfpipe_finish() ; + sig_restore(SIGPIPE) ; + pathexec_run(cargv[0], cargv, (char const *const *)environ) ; + strerr_dieexec(111, cargv[0]) ; +} + +static int rotator (logdir_t_ref ldp) +{ + unsigned int dirlen = str_len(ldp->dir) ; + switch (ldp->rstate) + { + case ROTSTATE_START : + { + if (fd_sync(ldp->fd) < 0) + { + if (verbosity) strerr_warnwu3sys("fd_sync ", ldp->dir, "/current") ; + goto fail ; + } + tain_now_g() ; + ldp->rstate = ROTSTATE_RENAME ; + } + case ROTSTATE_RENAME : + { + char current[dirlen + 9] ; + char previous[dirlen + 10] ; + byte_copy(current, dirlen, ldp->dir) ; + byte_copy(current + dirlen, 9, "/current") ; + byte_copy(previous, dirlen, ldp->dir) ; + byte_copy(previous + dirlen, 10, "/previous") ; + if (rename(current, previous) < 0) + { + if (verbosity) strerr_warnwu4sys("rename ", current, " to ", previous) ; + goto fail ; + } + ldp->rstate = ROTSTATE_NEWCURRENT ; + } + case ROTSTATE_NEWCURRENT : + { + int fd ; + char x[dirlen + 9] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 9, "/current") ; + fd = open_append(x) ; + if (fd < 0) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + if (coe(fd) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu2sys("coe ", x) ; + goto fail ; + } + if (fd_chmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu3sys("fchmod ", x, " to 0644") ; + goto fail ; + } + fd_close(ldp->fd) ; + ldp->fd = fd ; + ldp->b = 0 ; + ldp->rstate = ROTSTATE_CHMODPREVIOUS ; + } + case ROTSTATE_CHMODPREVIOUS : + { + char x[dirlen + 10] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/previous") ; + if (chmod(x, S_IRWXU | S_IRGRP | S_IROTH) < 0) + { + if (verbosity) strerr_warnwu3sys("chmod ", x, " to 0744") ; + goto fail ; + } + if (ldp->processor) goto runprocessor ; + ldp->rstate = ROTSTATE_FINISHPREVIOUS ; + } + case ROTSTATE_FINISHPREVIOUS : + { + if (finish(ldp, "previous", 's') < 0) + { + if (verbosity) strerr_warnwu2sys("finish previous .s to logdir ", ldp->dir) ; + goto fail ; + } + tain_copynow(&ldp->deadline) ; + ldp->rstate = ROTSTATE_WRITABLE ; + break ; + } + runprocessor : + ldp->rstate = ROTSTATE_RUNPROCESSOR ; + case ROTSTATE_RUNPROCESSOR : + { + int pid = fork() ; + if (pid < 0) + { + if (verbosity) strerr_warnwu2sys("fork processor for logdir ", ldp->dir) ; + goto fail ; + } + else if (!pid) exec_processor(ldp) ; + ldp->pid = (unsigned int)pid ; + tain_add_g(&ldp->deadline, &tain_infinite_relative) ; + ldp->rstate = ROTSTATE_WAITPROCESSOR ; + } + case ROTSTATE_WAITPROCESSOR : + { + return (errno = EAGAIN, 0) ; + } + case ROTSTATE_SYNCPROCESSED : + { + int fd ; + char x[dirlen + 11] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 11, "/processed") ; + fd = open_append(x) ; + if (fd < 0) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + if (fd_sync(fd) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu2sys("fd_sync ", x) ; + goto fail ; + } + tain_now_g() ; + if (fd_chmod(fd, S_IRWXU | S_IRGRP | S_IROTH) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu3sys("fd_chmod ", x, " to 0744") ; + goto fail ; + } + fd_close(fd) ; + ldp->rstate = ROTSTATE_SYNCNEWSTATE ; + } + case ROTSTATE_SYNCNEWSTATE : + { + int fd ; + char x[dirlen + 10] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/newstate") ; + fd = open_append(x) ; + if (ldp->fd < 0) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + if (fd_sync(fd) < 0) + { + if (verbosity) strerr_warnwu2sys("fd_sync ", x) ; + goto fail ; + } + tain_now_g() ; + fd_close(fd) ; + ldp->rstate = ROTSTATE_UNLINKPREVIOUS ; + } + case ROTSTATE_UNLINKPREVIOUS : + { + char x[dirlen + 10] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/previous") ; + if ((unlink(x) < 0) && (errno != ENOENT)) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + ldp->rstate = ROTSTATE_RENAMESTATE ; + } + case ROTSTATE_RENAMESTATE : + { + char newstate[dirlen + 10] ; + char state[dirlen + 7] ; + byte_copy(newstate, dirlen, ldp->dir) ; + byte_copy(state, dirlen, ldp->dir) ; + byte_copy(newstate + dirlen, 10, "/newstate") ; + byte_copy(state + dirlen, 7, "/state") ; + if (rename(newstate, state) < 0) + { + if (verbosity) strerr_warnwu4sys("rename ", newstate, " to ", state) ; + goto fail ; + } + ldp->rstate = ROTSTATE_FINISHPROCESSED ; + } + case ROTSTATE_FINISHPROCESSED : + { + if (finish(ldp, "processed", 's') < 0) + { + if (verbosity) strerr_warnwu2sys("finish processed .s to logdir ", ldp->dir) ; + goto fail ; + } + tain_copynow(&ldp->deadline) ; + ldp->rstate = ROTSTATE_WRITABLE ; + break ; + } + default : strerr_dief1x(101, "inconsistent state in rotator()") ; + } + return 1 ; + fail: + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + return 0 ; +} + +static int logdir_write (int i, char const *s, unsigned int len) +{ + logdir_t_ref ldp = genalloc_s(logdir_t, &logdirs) + (unsigned int)i ; + int r ; + unsigned int n = len ; + { + unsigned int m = byte_rchr(s, n, '\n') ; + if (m < n) n = m+1 ; + } + r = fd_write(ldp->fd, s, n) ; + if (r < 0) + { + if (!error_isagain(errno)) + { + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + if (verbosity) strerr_warnwu3sys("write to ", ldp->dir, "/current") ; + } + return r ; + } + ldp->b += r ; + if ((ldp->b + ldp->tolerance >= ldp->s) && (s[r-1] == '\n')) + { + ldp->rstate = ROTSTATE_START ; + rotator(ldp) ; + } + return r ; +} + +static inline void rotate_or_flush (logdir_t *ldp) +{ + if ((ldp->rstate != ROTSTATE_WRITABLE) && !rotator(ldp)) return ; + if (ldp->b >= ldp->s) + { + ldp->rstate = ROTSTATE_START ; + if (!rotator(ldp)) return ; + } + bufalloc_flush(&ldp->out) ; +} + +static inline void logdir_init (logdir_t *ap, uint32 s, uint32 n, uint32 tolerance, uint64 maxdirsize, tain_t const *retrytto, char const *processor, char const *name, unsigned int index) +{ + struct stat st ; + unsigned int dirlen = str_len(name) ; + int r ; + char x[dirlen + 11] ; + ap->s = s ; + ap->n = n ; + ap->pid = 0 ; + ap->tolerance = tolerance ; + ap->maxdirsize = maxdirsize ; + ap->retrytto = *retrytto ; + ap->processor = processor ; + ap->dir = name ; + ap->fd = -1 ; + ap->rstate = ROTSTATE_WRITABLE ; + r = mkdir(ap->dir, S_IRWXU | S_ISGID) ; + if ((r < 0) && (errno != EEXIST)) strerr_diefu2sys(111, "mkdir ", name) ; + byte_copy(x, dirlen, name) ; + byte_copy(x + dirlen, 6, "/lock") ; + ap->fdlock = open_append(x) ; + if ((ap->fdlock) < 0) strerr_diefu2sys(111, "open_append ", x) ; + if (lock_exnb(ap->fdlock) < 0) strerr_diefu2sys(111, "lock_exnb ", x) ; + if (coe(ap->fdlock) < 0) strerr_diefu2sys(111, "coe ", x) ; + byte_copy(x + dirlen + 1, 8, "current") ; + if (stat(x, &st) < 0) + { + if (errno != ENOENT) strerr_diefu2sys(111, "stat ", x) ; + } + else if (st.st_mode & S_IXUSR) goto opencurrent ; + byte_copy(x + dirlen + 1, 6, "state") ; + unlink(x) ; + byte_copy(x + dirlen + 1, 9, "newstate") ; + unlink(x) ; + { + int flagprocessed = 0 ; + byte_copy(x + dirlen + 1, 10, "processed") ; + if (stat(x, &st) < 0) + { + if (errno != ENOENT) strerr_diefu2sys(111, "stat ", x) ; + } + else if (st.st_mode & S_IXUSR) flagprocessed = 1 ; + if (flagprocessed) + { + byte_copy(x + dirlen + 1, 9, "previous") ; + unlink(x) ; + if (finish(ap, "processed", 's') < 0) + strerr_diefu2sys(111, "finish processed .s for logdir ", ap->dir) ; + } + else + { + unlink(x) ; + if (finish(ap, "previous", 'u') < 0) + strerr_diefu2sys(111, "finish previous .u for logdir ", ap->dir) ; + } + } + if (finish(ap, "current", 'u') < 0) + strerr_diefu2sys(111, "finish current .u for logdir ", ap->dir) ; + byte_copy(x + dirlen + 1, 6, "state") ; + ap->fd = open_trunc(x) ; + if (ap->fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ; + fd_close(ap->fd) ; + st.st_size = 0 ; + byte_copy(x + dirlen + 1, 8, "current") ; + opencurrent: + ap->fd = open_append(x) ; + if (ap->fd < 0) strerr_diefu2sys(111, "open_append ", x) ; + if (fd_chmod(ap->fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) + strerr_diefu2sys(111, "fd_chmod ", x) ; + if (coe(ap->fd) < 0) strerr_diefu2sys(111, "coe ", x) ; + ap->b = st.st_size ; + tain_copynow(&ap->deadline) ; + bufalloc_init(&ap->out, &logdir_write, (int)index) ; +} + + + /* Script */ + +static int script_update (genalloc *sc, genalloc *sa, genalloc *aa) +{ + scriptelem_t foo ; + genalloc_shrink(sel_t, sa) ; + genalloc_shrink(act_t, aa) ; + foo.selections = *sa ; + foo.actions = *aa ; + if (!genalloc_append(scriptelem_t, sc, &foo)) return 0 ; + *sa = genalloc_zero ; + *aa = genalloc_zero ; + return 1 ; +} + +static inline int script_init (genalloc *sc, char const *const *argv) +{ + tain_t cur_retrytto ; + unsigned int cur_fd2_size = 200 ; + unsigned int cur_status_size = 1001 ; + uint32 cur_s = 99999 ; + uint32 cur_n = 10 ; + uint32 cur_tolerance = 2000 ; + uint64 cur_maxdirsize = 0 ; + genalloc cur_selections = GENALLOC_ZERO ; /* sel_t */ + genalloc cur_actions = GENALLOC_ZERO ; /* act_t */ + char const *cur_processor = 0 ; + int flagacted = 0 ; + tain_uint(&cur_retrytto, 2) ; + + for (; *argv ; argv++) + { + switch (**argv) + { + case 'f' : + { + sel_t selitem ; + if (flagacted) + { + if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ; + flagacted = 0 ; + } + selitem.type = SELTYPE_DEFAULT ; + if (!genalloc_append(sel_t, &cur_selections, &selitem)) return 0 ; + break ; + } + case '+' : + case '-' : + { + sel_t selitem ; + int r ; + if (flagacted) + { + if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ; + flagacted = 0 ; + } + selitem.type = (**argv == '+') ? SELTYPE_PLUS : SELTYPE_MINUS ; + r = regcomp(&selitem.re, *argv+1, REG_EXTENDED | REG_NOSUB | REG_NEWLINE) ; + if (r == REG_ESPACE) return (errno = ENOMEM, 0) ; + if (r) goto fail ; + if (!genalloc_append(sel_t, &cur_selections, &selitem)) return 0 ; + break ; + } + case 'n' : + { + if (!uint320_scan(*argv + 1, &cur_n)) goto fail ; + break ; + } + case 's' : + { + if (!uint320_scan(*argv + 1, &cur_s)) goto fail ; + if (cur_s < 4096) cur_s = 4096 ; + if (cur_s > 16777215) cur_s = 16777215 ; + break ; + } + case 'S' : + { + if (!uint640_scan(*argv + 1, &cur_maxdirsize)) goto fail ; + break ; + } + case 'l' : + { + if (!uint320_scan(*argv + 1, &cur_tolerance)) goto fail ; + if (cur_tolerance > (cur_s >> 1)) + strerr_dief3x(100, "directive ", *argv, " conflicts with previous s directive") ; + break ; + } + case 'r' : + { + uint32 t ; + if (!uint320_scan(*argv + 1, &t)) goto fail ; + if (!tain_from_millisecs(&cur_retrytto, (int)t)) return (errno = EINVAL, 0) ; + break ; + } + case 'E' : + { + if (!uint0_scan(*argv + 1, &cur_fd2_size)) goto fail ; + break ; + } + case '^' : + { + if (!uint0_scan(*argv + 1, &cur_status_size)) goto fail ; + break ; + } + case '!' : + { + cur_processor = (*argv)[1] ? *argv + 1 : 0 ; + break ; + } + case 'e' : + { + act_t a ; + flagacted = 1 ; + a.type = ACTTYPE_FD2 ; + a.data.fd2.size = cur_fd2_size ; + if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ; + break ; + } + case '=' : + { + act_t a ; + flagacted = 1 ; + a.type = ACTTYPE_STATUS ; + a.data.status.file = *argv + 1 ; + a.data.status.content = stralloc_zero ; + if (cur_status_size && !stralloc_ready_tuned(&a.data.status.content, cur_status_size, 0, 0, 1)) return 0 ; + a.data.status.content.len = cur_status_size ; + if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ; + break ; + } + case '.' : + case '/' : + { + act_t a ; + logdir_t ld = LOGDIR_ZERO ; + flagacted = 1 ; + a.type = ACTTYPE_DIR ; + a.data.dir.lindex = genalloc_len(logdir_t, &logdirs) ; + if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ; + logdir_init(&ld, cur_s, cur_n, cur_tolerance, cur_maxdirsize, &cur_retrytto, cur_processor, *argv, genalloc_len(logdir_t, &logdirs)) ; + if (!genalloc_append(logdir_t, &logdirs, &ld)) return 0 ; + break ; + } + default : goto fail ; + } + } + if (flagacted) + { + if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ; + } + else + { + genalloc_deepfree(sel_t, &cur_selections, &sel_free) ; + if (verbosity) strerr_warnw1x("ignoring extraneous non-action directives") ; + } + genalloc_shrink(logdir_t, &logdirs) ; + genalloc_shrink(scriptelem_t, sc) ; + if (!genalloc_len(scriptelem_t, sc)) + strerr_dief1x(100, "no action directive specified") ; + return 1 ; + fail: + strerr_dief2x(100, "unrecognized directive: ", *argv) ; +} + +static inline void doit_fd2 (as_fd2_t const *ap, char const *s, unsigned int len) +{ + if (flagstampalert) + { + char fmt[TIMESTAMP+1] ; + tain_now_g() ; + timestamp_g(fmt) ; + fmt[TIMESTAMP] = ' ' ; + buffer_put(buffer_2, fmt, TIMESTAMP+1) ; + } + buffer_puts(buffer_2, PROG) ; + buffer_puts(buffer_2, ": alert: ") ; + if (ap->size && len > ap->size) len = ap->size ; + buffer_put(buffer_2, s, len) ; + if (len == ap->size) buffer_puts(buffer_2, "...") ; + buffer_putflush(buffer_2, "\n", 1) ; +} + +static inline void doit_status (as_status_t const *ap, char const *s, unsigned int len) +{ + if (ap->content.len) + { + register unsigned int i ; + if (len > ap->content.len) len = ap->content.len ; + byte_copy(ap->content.s, len, s) ; + for (i = len ; i < ap->content.len ; i++) ap->content.s[i] = '\n' ; + if (!openwritenclose_suffix_sync(ap->file, ap->content.s, ap->content.len, ".new")) + strerr_warnwu2sys("openwritenclose ", ap->file) ; + } + else if (!openwritenclose_suffix_sync(ap->file, s, len, ".new")) + strerr_warnwu2sys("openwritenclose ", ap->file) ; +} + +static inline void doit_dir (as_dir_t const *ap, char const *s, unsigned int len) +{ + logdir_t_ref ldp = genalloc_s(logdir_t, &logdirs) + ap->lindex ; + if (!bufalloc_put(&ldp->out, s, len) || !bufalloc_put(&ldp->out, "\n", 1)) + strerr_diefu1sys(111, "bufalloc_put") ; +} + + + /* The script interpreter. */ + +static inline void doit (scriptelem_t const *se, unsigned int n, char const *s, unsigned int len) +{ + int flagselected = 1 ; + int flagacted = 0 ; + unsigned int i = 0 ; + for (; i < n ; i++) + { + unsigned int sellen = genalloc_len(sel_t, &se[i].selections) ; + sel_t *sels = genalloc_s(sel_t, &se[i].selections) ; + unsigned int j = 0 ; + for (; j < sellen ; j++) + { + switch (sels[j].type) + { + case SELTYPE_DEFAULT : + flagselected = !flagacted ; + break ; + case SELTYPE_PLUS : + if (!flagselected && !regexec(&sels[j].re, flagstamp ? s+TIMESTAMP+1 : s, 0, 0, 0)) flagselected = 1 ; + break ; + case SELTYPE_MINUS : + if (flagselected && !regexec(&sels[j].re, flagstamp ? s+TIMESTAMP+1 : s, 0, 0, 0)) flagselected = 0 ; + break ; + default : + strerr_dief2x(101, "internal consistency error in ", "selection type") ; + } + } + if (flagselected) + { + unsigned int actlen = genalloc_len(act_t, &se[i].actions) ; + act_t *acts = genalloc_s(act_t, &se[i].actions) ; + flagacted = 1 ; + for (j = 0 ; j < actlen ; j++) + { + switch (acts[j].type) + { + case ACTTYPE_FD2 : + doit_fd2(&acts[j].data.fd2, s, len) ; + break ; + case ACTTYPE_STATUS : + doit_status(&acts[j].data.status, s, len) ; + break ; + case ACTTYPE_DIR : + doit_dir(&acts[j].data.dir, s, len) ; + break ; + default : + strerr_dief2x(101, "internal consistency error in ", "action type") ; + } + } + } + } + if (flagstamp) tain_now_g() ; +} + +static inline void processor_died (logdir_t_ref ldp, int wstat) +{ + ldp->pid = 0 ; + if (WIFSIGNALED(wstat)) + { + if (verbosity) strerr_warnw2x("processor crashed in ", ldp->dir) ; + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + ldp->rstate = ROTSTATE_RUNPROCESSOR ; + } + else if (WEXITSTATUS(wstat)) + { + if (verbosity) strerr_warnw2x("processor failed in ", ldp->dir) ; + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + ldp->rstate = ROTSTATE_RUNPROCESSOR ; + } + else + { + ldp->rstate = ROTSTATE_SYNCPROCESSED ; + rotator(ldp) ; + } +} + +static void prepare_to_exit (void) +{ + fd_close(0) ; + flagexiting = 1 ; +} + +static void stampanddoit (scriptelem_t const *se, unsigned int n) +{ + if (flagstamp) indata.s[timestamp_g(indata.s)] = ' ' ; + indata.s[indata.len] = 0 ; + doit(se, n, indata.s, indata.len-1) ; + indata.len = flagstamp ? TIMESTAMP+1 : 0 ; +} + +static void normal_stdin (scriptelem_t const *se, unsigned int selen) +{ + int r = sanitize_read(buffer_fill(buffer_0)) ; + if (r < 0) + { + if ((errno != EPIPE) && verbosity) strerr_warnwu1sys("read from stdin") ; + prepare_to_exit() ; + } + else if (r) + while (skagetln_nofill(buffer_0, &indata, '\n') > 0) + stampanddoit(se, selen) ; +} + +static void last_stdin (scriptelem_t const *se, unsigned int selen) +{ + int cont = 1 ; + while (cont) + { + char c ; + switch (sanitize_read(fd_read(0, &c, 1))) + { + case 0 : + cont = 0 ; + break ; + case -1 : + if ((errno != EPIPE) && verbosity) strerr_warnwu1sys("read from stdin") ; + if (indata.len <= (flagstamp ? TIMESTAMP+1 : 0)) + { + prepare_to_exit() ; + cont = 0 ; + break ; + } + c = '\n' ; + case 1 : + if (!stralloc_catb(&indata, &c, 1)) dienomem() ; + if (c == '\n') + { + stampanddoit(se, selen) ; + prepare_to_exit() ; + cont = 0 ; + } + break ; + } + } +} + +static inputprocfunc_t_ref handle_stdin = &normal_stdin ; + +static inline void handle_signals (void) +{ + for (;;) + { + switch (selfpipe_read()) + { + case -1 : strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGALRM : + { + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + register unsigned int i = 0 ; + for (i = 0 ; i < llen ; i++) + if ((ls[i].rstate == ROTSTATE_WRITABLE) && ls[i].b) + { + ls[i].rstate = ROTSTATE_START ; + rotator(ls + i) ; + } + break ; + } + case SIGTERM : + { + if (flagprotect) break ; + handle_stdin = &last_stdin ; + if (indata.len <= (flagstamp ? TIMESTAMP+1 : 0)) prepare_to_exit() ; + break ; + } + case SIGCHLD : + { + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + for (;;) + { + int wstat ; + register unsigned int i = 0 ; + register int r = wait_nohang(&wstat) ; + if (r <= 0) break ; + for (; i < llen ; i++) if ((unsigned int)r == ls[i].pid) break ; + if (i < llen) processor_died(ls + i, wstat) ; + } + break ; + } + default : strerr_dief1x(101, "internal consistency error with signal handling") ; + } + } +} + +static inline int logdir_finalize (logdir_t_ref ldp) +{ + switch (ldp->rstate) + { + case ROTSTATE_WRITABLE : + { + if (fd_sync(ldp->fd) < 0) + { + if (verbosity) strerr_warnwu3sys("fd_sync ", ldp->dir, "/current") ; + goto fail ; + } + tain_now_g() ; + ldp->rstate = ROTSTATE_ENDFCHMOD ; + } + case ROTSTATE_ENDFCHMOD : + { + if (fd_chmod(ldp->fd, S_IRWXU | S_IRGRP | S_IROTH) < 0) + { + if (verbosity) strerr_warnwu3sys("fd_chmod ", ldp->dir, "/current to 0744") ; + goto fail ; + } + ldp->rstate = ROTSTATE_END ; + break ; + } + default : strerr_dief1x(101, "inconsistent state in logdir_finalize()") ; + } + return 1 ; + fail: + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + return 0 ; +} + +static inline void finalize (void) +{ + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + unsigned int n = llen ; + for (;;) + { + unsigned int i = 0 ; + tain_t deadline ; + tain_addsec_g(&deadline, 2) ; + for (; i < llen ; i++) + if (ls[i].rstate != ROTSTATE_END) + { + if (logdir_finalize(ls + i)) n-- ; + else if (tain_less(&ls[i].deadline, &deadline)) + deadline = ls[i].deadline ; + } + if (!n) break ; + { + iopause_fd x ; + iopause_g(&x, 0, &deadline) ; + } + } +} + +int main (int argc, char const *const *argv) +{ + genalloc logscript = GENALLOC_ZERO ; /* array of scriptelem_t */ + int flagblock = 0 ; + PROG = "s6-log" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "qvbpte", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'q' : if (verbosity) verbosity-- ; break ; + case 'v' : verbosity++ ; break ; + case 'b' : flagblock = 1 ; break ; + case 'p' : flagprotect = 1 ; break ; + case 't' : flagstamp = 1 ; break ; + case 'e' : flagstampalert = 1 ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (argc < 1) strerr_dieusage(100, USAGE) ; + + fd_close(1) ; + { + int r = tain_now_g() ; + if (flagstamp) + { + char fmt[TIMESTAMP+1] ; + if (!stralloc_catb(&indata, fmt, TIMESTAMP+1)) dienomem() ; + if (!r) strerr_warnwu1sys("read current time - timestamps may be wrong for a while") ; + } + } + if (!script_init(&logscript, argv)) strerr_diefu1sys(111, "initialize logging script") ; + if (ndelay_on(0) < 0) strerr_diefu1sys(111, "ndelay_on(0)") ; + + { + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + iopause_fd x[2 + llen] ; + unsigned int active[llen] ; + x[0].fd = 0 ; + x[1].fd = selfpipe_init() ; + if (x[1].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore(SIGPIPE)") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGTERM) ; sigaddset(&set, SIGALRM) ; sigaddset(&set, SIGCHLD) ; + if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "selfpipe_trapset") ; + } + x[1].events = IOPAUSE_READ ; + + for (;;) + { + tain_t deadline ; + int r ; + unsigned int j = 0 ; + unsigned int i = 0 ; + int allflushed = 1 ; + tain_add_g(&deadline, &tain_infinite_relative) ; + for (; i < llen ; i++) + { + if (bufalloc_len(&ls[i].out) || (ls[i].rstate != ROTSTATE_WRITABLE)) + { + allflushed = 0 ; + if (!tain_future(&ls[i].deadline)) + { + x[2+j].fd = ls[i].fd ; + x[2+j].events = IOPAUSE_WRITE ; + active[j++] = i ; + } + else if (tain_less(&ls[i].deadline, &deadline)) + deadline = ls[i].deadline ; + } + } + if (flagexiting && allflushed) break ; + x[0].events = (allflushed || !flagblock) ? IOPAUSE_READ : 0 ; + r = iopause_g(x + flagexiting, 2 - flagexiting + j, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (r) + { + if (x[1].revents & IOPAUSE_READ) handle_signals() ; + else if (x[1].revents & IOPAUSE_EXCEPT) strerr_dief1sys(111, "trouble with selfpipe") ; + for (i = 0 ; i < j ; i++) + if (x[2+i].revents & IOPAUSE_WRITE) + rotate_or_flush(ls + active[i]) ; + if (!flagexiting) + { + if (x[0].revents & IOPAUSE_READ) + (*handle_stdin)(genalloc_s(scriptelem_t, &logscript), genalloc_len(scriptelem_t, &logscript)) ; + else if (x[0].revents & IOPAUSE_EXCEPT) + { + prepare_to_exit() ; + if (indata.len > (flagstamp ? TIMESTAMP+1 : 0)) + { + if (!stralloc_0(&indata)) dienomem() ; + stampanddoit(genalloc_s(scriptelem_t, &logscript), genalloc_len(scriptelem_t, &logscript)) ; + } + } + } + } + } + } + genalloc_deepfree(scriptelem_t, &logscript, &scriptelem_free) ; + finalize() ; + return 0 ; +} diff --git a/src/daemontools-extras/s6-notifywhenup.c b/src/daemontools-extras/s6-notifywhenup.c new file mode 100644 index 0000000..a4be329 --- /dev/null +++ b/src/daemontools-extras/s6-notifywhenup.c @@ -0,0 +1,86 @@ +/* ISC license. */ + +#include <unistd.h> +#include <errno.h> +#include <skalibs/uint.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <s6/ftrigw.h> + +#define USAGE "s6-notifywhenup [ -d fd ] [ -e fifodir ] [ -f ] [ -t timeout ] prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +static int run_child (int fd, char const *fifodir, unsigned int timeout) +{ + char dummy[4096] ; + iopause_fd x = { .fd = fd, .events = IOPAUSE_READ } ; + tain_t deadline ; + int haswritten = 0 ; + register int r = 0 ; + if (!tain_now_g()) strerr_diefu1sys(111, "tain_now") ; + tain_from_millisecs(&deadline, timeout) ; + tain_add_g(&deadline, &deadline) ; + while (!r) + { + register int r = iopause_g(&x, 1, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + if (!r) return 99 ; + while (r > 0) + { + r = sanitize_read(fd_read(fd, dummy, 4096)) ; + if (r > 0) haswritten = 1 ; + } + } + if (errno != EPIPE) strerr_diefu1sys(111, "read from parent") ; + if (haswritten) ftrigw_notify(fifodir, 'U') ; + return 0 ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + unsigned int fd = 1 ; + char const *fifodir = "event" ; + int df = 1 ; + unsigned int timeout = 0 ; + PROG = "s6-notifywhenup" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "d:e:ft:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'd' : if (!uint0_scan(l.arg, &fd)) dieusage() ; break ; + case 'e' : fifodir = l.arg ; break ; + case 'f' : df = 0 ; break ; + case 't' : if (!uint0_scan(l.arg, &timeout)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (!argc) dieusage() ; + + { + int p[2] ; + pid_t pid ; + if (pipe(p) < 0) strerr_diefu1sys(111, "pipe") ; + pid = df ? doublefork() : fork() ; + if (pid < 0) strerr_diefu1sys(111, df ? "doublefork" : "fork") ; + else if (pid) + { + PROG = "s6-notifywhenup (child)" ; + fd_close(p[1]) ; + return run_child(p[0], fifodir, timeout) ; + } + fd_close(p[0]) ; + if (fd_move((int)fd, p[1]) < 0) strerr_diefu1sys(111, "fd_move") ; + } + pathexec_run(argv[0], argv, envp) ; + strerr_dieexec(111, argv[1]) ; +} diff --git a/src/daemontools-extras/s6-setlock.c b/src/daemontools-extras/s6-setlock.c new file mode 100644 index 0000000..2fb6f12 --- /dev/null +++ b/src/daemontools-extras/s6-setlock.c @@ -0,0 +1,87 @@ +/* ISC license. */ + +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/uint.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <s6/config.h> + +#define USAGE "s6-setlock [ -r | -w ] [ -n | -N | -t timeout ] lockfile prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +typedef int lockfunc_t (int) ; +typedef lockfunc_t *lockfunc_t_ref ; + +static lockfunc_t_ref f[2][2] = { { &lock_sh, &lock_shnb }, { &lock_ex, &lock_exnb } } ; + +int main (int argc, char const *const *argv, char const *const *envp) +{ + unsigned int nb = 0, ex = 1 ; + unsigned int timeout = 0 ; + PROG = "s6-setlock" ; + for (;;) + { + register int opt = subgetopt(argc, argv, "nNrwt:") ; + if (opt == -1) break ; + switch (opt) + { + case 'n' : nb = 1 ; break ; + case 'N' : nb = 0 ; break ; + case 'r' : ex = 0 ; break ; + case 'w' : ex = 1 ; break ; + case 't' : if (!uint0_scan(subgetopt_here.arg, &timeout)) dieusage() ; + nb = 2 ; break ; + default : dieusage() ; + } + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (argc < 2) dieusage() ; + + if (nb < 2) + { + int fd = open_create(argv[0]) ; + if (fd == -1) strerr_diefu2sys(111, "open_create ", argv[0]) ; + if ((*f[ex][nb])(fd) == -1) strerr_diefu2sys(1, "lock ", argv[0]) ; + } + else + { + char const *cargv[3] = { "s6lockd-helper", argv[0], 0 } ; + char const *cenvp[2] = { ex ? "S6LOCK_EX=1" : 0, 0 } ; + iopause_fd x = { .events = IOPAUSE_READ } ; + tain_t deadline ; + int p[2] ; + unsigned int pid ; + char c ; + if (!tain_now_g()) strerr_diefu1sys(111, "tain_now") ; + tain_from_millisecs(&deadline, timeout) ; + tain_add_g(&deadline, &deadline) ; + pid = child_spawn(S6_BINPREFIX "s6lockd-helper", cargv, cenvp, p, 2) ; + if (!pid) strerr_diefu2sys(111, "spawn ", S6_BINPREFIX "s6lockd-helper") ; + x.fd = p[0] ; + for (;;) + { + register int r = iopause_g(&x, 1, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + if (!r) + { + kill(pid, SIGTERM) ; + errno = ETIMEDOUT ; + strerr_diefu1sys(1, "acquire lock") ; + } + r = sanitize_read(fd_read(p[0], &c, 1)) ; + if (r < 0) strerr_diefu1sys(111, "read ack from helper") ; + if (r) break ; + } + if (c != '!') strerr_dief1x(111, "helper sent garbage ack") ; + fd_close(p[0]) ; + if (uncoe(p[1]) < 0) strerr_diefu1sys(111, "uncoe fd to helper") ; + } + pathexec_run(argv[1], argv+1, envp) ; + strerr_dieexec(111, argv[1]) ; +} diff --git a/src/daemontools-extras/s6-setsid.c b/src/daemontools-extras/s6-setsid.c new file mode 100644 index 0000000..efd3832 --- /dev/null +++ b/src/daemontools-extras/s6-setsid.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include <unistd.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6-setsid [ -i | -I ] prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + int insist = 0 ; + PROG = "s6-setsid" ; + for (;;) + { + register int opt = subgetopt(argc, argv, "iI") ; + if (opt == -1) break ; + switch (opt) + { + case 'i' : insist = 1 ; break ; + case 'I' : insist = 0 ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (!argc) strerr_dieusage(100, USAGE) ; + + if (setsid() < 0) + { + if (insist) strerr_diefu1sys(111, "setsid") ; + else strerr_warnwu1sys("setsid") ; + } + pathexec_run(argv[0], argv, envp) ; + strerr_dieexec(111, argv[0]) ; +} diff --git a/src/daemontools-extras/s6-setuidgid.c b/src/daemontools-extras/s6-setuidgid.c new file mode 100644 index 0000000..d2e7361 --- /dev/null +++ b/src/daemontools-extras/s6-setuidgid.c @@ -0,0 +1,30 @@ +/* ISC license. */ + +#include <unistd.h> +#include <skalibs/bytestr.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6-setuidgid username prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +int main (int argc, char const *const *argv, char const *const *envp) +{ + unsigned int pos ; + PROG = "s6-setuidgid" ; + if (argc < 3) dieusage() ; + pos = str_chr(argv[1], ':') ; + if (argv[1][pos]) + { + unsigned int uid = 0, gid = 0, len = uint_scan(argv[1], &uid) ; + if (len != pos) dieusage() ; + if (argv[1][pos+1] && !uint0_scan(argv[1]+pos+1, &gid)) dieusage() ; + if (gid && setgid(gid)) strerr_diefu1sys(111, "setgid") ; + if (uid && setuid(uid)) strerr_diefu1sys(111, "setuid") ; + } + else if (!prot_setuidgid(argv[1])) + strerr_diefu2sys(111, "change identity to ", argv[1]) ; + pathexec_run(argv[2], argv+2, envp) ; + strerr_dieexec(111, argv[2]) ; +} diff --git a/src/daemontools-extras/s6-softlimit.c b/src/daemontools-extras/s6-softlimit.c new file mode 100644 index 0000000..61e0e4d --- /dev/null +++ b/src/daemontools-extras/s6-softlimit.c @@ -0,0 +1,117 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <time.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <skalibs/strerr2.h> +#include <skalibs/sgetopt.h> +#include <skalibs/uint64.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6-softlimit [ -a allbytes ] [ -c corebytes ] [ -d databytes ] [ -f filebytes ] [ -l lockbytes ] [ -m membytes ] [ -o openfiles ] [ -p processes ] [ -r residentbytes ] [ -s stackbytes ] [ -t cpusecs ] prog..." + +static void doit (int res, char const *arg) +{ + struct rlimit r ; + if (getrlimit(res, &r) < 0) strerr_diefu1sys(111, "getrlimit") ; + if ((arg[0] == '=') && !arg[1]) r.rlim_cur = r.rlim_max ; + else + { + uint64 n ; + if (!uint640_scan(arg, &n)) strerr_dieusage(100, USAGE) ; + if (n > (uint64)r.rlim_max) n = (uint64)r.rlim_max ; + r.rlim_cur = (rlim_t)n ; + } + if (setrlimit(res, &r) == -1) strerr_diefu1sys(111, "setrlimit") ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "s6-softlimit" ; + for (;;) + { + register int opt = sgetopt(argc, argv, "a:c:d:f:l:m:o:p:r:s:t:") ; + if (opt == -1) break ; + switch (opt) + { + case 'a' : +#ifdef RLIMIT_AS + doit(RLIMIT_AS, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_VMEM + doit(RLIMIT_VMEM, subgetopt_here.arg) ; +#endif + break ; + case 'c' : +#ifdef RLIMIT_CORE + doit(RLIMIT_CORE, subgetopt_here.arg) ; +#endif + break ; + case 'd' : +#ifdef RLIMIT_DATA + doit(RLIMIT_DATA, subgetopt_here.arg) ; +#endif + break ; + case 'f' : +#ifdef RLIMIT_FSIZE + doit(RLIMIT_FSIZE, subgetopt_here.arg) ; +#endif + break ; + case 'l' : +#ifdef RLIMIT_MEMLOCK + doit(RLIMIT_MEMLOCK, subgetopt_here.arg) ; +#endif + break ; + case 'm' : +#ifdef RLIMIT_DATA + doit(RLIMIT_DATA, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_STACK + doit(RLIMIT_STACK, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_MEMLOCK + doit(RLIMIT_MEMLOCK, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_VMEM + doit(RLIMIT_VMEM, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_AS + doit(RLIMIT_AS, subgetopt_here.arg) ; +#endif + break ; + case 'o' : +#ifdef RLIMIT_NOFILE + doit(RLIMIT_NOFILE, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_OFILE + doit(RLIMIT_OFILE, subgetopt_here.arg) ; +#endif + break ; + case 'p' : +#ifdef RLIMIT_NPROC + doit(RLIMIT_NPROC, subgetopt_here.arg) ; +#endif + break ; + case 'r' : +#ifdef RLIMIT_RSS + doit(RLIMIT_RSS, subgetopt_here.arg) ; +#endif + break ; + case 's' : +#ifdef RLIMIT_STACK + doit(RLIMIT_STACK, subgetopt_here.arg) ; +#endif + break ; + case 't' : +#ifdef RLIMIT_CPU + doit(RLIMIT_CPU, subgetopt_here.arg) ; +#endif + break ; + } + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (!argc) strerr_dieusage(100, USAGE) ; + pathexec_run(argv[0], argv, envp) ; + strerr_dieexec(111, argv[0]) ; +} diff --git a/src/daemontools-extras/s6-tai64n.c b/src/daemontools-extras/s6-tai64n.c new file mode 100644 index 0000000..085c053 --- /dev/null +++ b/src/daemontools-extras/s6-tai64n.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/stralloc.h> +#include <skalibs/skamisc.h> + +int main (void) +{ + char stamp[TIMESTAMP+1] ; + PROG = "s6-tai64n" ; + stamp[TIMESTAMP] = ' ' ; + for (;;) + { + register int r = skagetln(buffer_0f1, &satmp, '\n') ; + if (r < 0) + if (errno != EPIPE) + strerr_diefu1sys(111, "read from stdin") ; + else + { + r = 1 ; + if (!stralloc_catb(&satmp, "\n", 1)) + strerr_diefu1sys(111, "add newline") ; + } + else if (!r) break ; + timestamp(stamp) ; + if ((buffer_put(buffer_1, stamp, TIMESTAMP+1) < 0) + || (buffer_put(buffer_1, satmp.s, satmp.len) < 0)) + strerr_diefu1sys(111, "write to stdout") ; + satmp.len = 0 ; + } + return 0 ; +} diff --git a/src/daemontools-extras/s6-tai64nlocal.c b/src/daemontools-extras/s6-tai64nlocal.c new file mode 100644 index 0000000..d7be880 --- /dev/null +++ b/src/daemontools-extras/s6-tai64nlocal.c @@ -0,0 +1,47 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <errno.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/fmtscan.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/djbtime.h> +#include <skalibs/stralloc.h> +#include <skalibs/skamisc.h> + +int main (void) +{ + PROG = "s6-tai64nlocal" ; + for (;;) + { + unsigned int p = 0 ; + int r = skagetln(buffer_0f1, &satmp, '\n') ; + if (r == -1) + if (errno != EPIPE) + strerr_diefu1sys(111, "read from stdin") ; + else r = 1 ; + else if (!r) break ; + if (satmp.len > TIMESTAMP) + { + tain_t a ; + p = timestamp_scan(satmp.s, &a) ; + if (p) + { + char fmt[LOCALTMN_FMT+1] ; + localtmn_t local ; + unsigned int len ; + localtmn_from_tain(&local, &a, 1) ; + len = localtmn_fmt(fmt, &local) ; + fmt[len++] = ' ' ; + if (buffer_put(buffer_1, fmt, len) < 0) + strerr_diefu1sys(111, "write to stdout") ; + } + } + if (buffer_put(buffer_1, satmp.s + p, satmp.len - p) < 0) + strerr_diefu1sys(111, "write to stdout") ; + satmp.len = 0 ; + } + return 0 ; +} diff --git a/src/daemontools-extras/ucspilogd.c b/src/daemontools-extras/ucspilogd.c new file mode 100644 index 0000000..ddb6362 --- /dev/null +++ b/src/daemontools-extras/ucspilogd.c @@ -0,0 +1,117 @@ +/* ISC license. */ + +#ifndef SYSLOG_NAMES +#define SYSLOG_NAMES +#endif + +#include <stdlib.h> +#include <syslog.h> +#include <skalibs/sgetopt.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr2.h> +#include <skalibs/fmtscan.h> +#include <skalibs/stralloc.h> +#include <skalibs/djbunix.h> +#include <skalibs/skamisc.h> + +#define USAGE "ucspilogd [ -D default ] [ var... ]" +#define dieusage() strerr_dieusage(100, USAGE) + +static inline void die (void) +{ + strerr_diefu1sys(111, "write to stdout") ; +} + +static unsigned int syslog_names (char const *line) +{ + unsigned int fpr, i ; + int fp ; + CODE *p = facilitynames ; + + if (line[0] != '<') return 0 ; + i = uint_scan(line+1, &fpr) ; + if (!i || (line[i+1] != '>')) return 0 ; + i += 2 ; + + fp = LOG_FAC(fpr) << 3 ; + for (; p->c_name ; p++) if (p->c_val == fp) break ; + if (p->c_name) + { + if ((buffer_puts(buffer_1, p->c_name) < 0) + || (buffer_put(buffer_1, ".", 1) < 1)) die() ; + } + else + { + if (buffer_put(buffer_1, "unknown.", 8) < 8) die() ; + i = 0 ; + } + + fp = LOG_PRI(fpr) ; + for (p = prioritynames ; p->c_name ; p++) if (p->c_val == fp) break ; + if (p->c_name) + { + if ((buffer_puts(buffer_1, p->c_name) < 0) + || (buffer_put(buffer_1, ": ", 2) < 2)) die() ; + } + else + { + if (buffer_put(buffer_1, "unknown: ", 9) < 9) die() ; + i = 0 ; + } + return i ; +} + + +int main (int argc, char const *const *argv, char const *const *envp) +{ + char const *d = "<undefined>" ; + PROG = "ucspilogd" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "D:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'D' : d = l.arg ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + { + char const *envs[argc] ; + unsigned int i = 0 ; + for (; i < (unsigned int)argc ; i++) + { + envs[i] = env_get2(envp, argv[i]) ; + if (!envs[i]) envs[i] = d ; + } + for (;;) + { + unsigned int pos = 0 ; + satmp.len = 0 ; + { + register int r = skagetlnsep(buffer_0f1, &satmp, "\n", 2) ; + if (r < 0) strerr_diefu1sys(111, "read from stdin") ; + if (!r) break ; + } + if (!satmp.len) continue ; + satmp.s[satmp.len-1] = '\n' ; + if ((satmp.s[0] == '@') && (satmp.len > 26) && (byte_chr(satmp.s, 26, ' ') == 25)) + { + if (buffer_put(buffer_1, satmp.s, 26) < 26) die() ; + pos += 26 ; + } + for (i = 0 ; i < (unsigned int)argc ; i++) + if ((buffer_puts(buffer_1, envs[i]) < 0) + || (buffer_put(buffer_1, ": ", 2) < 2)) die() ; + pos += syslog_names(satmp.s + pos) ; + if (buffer_put(buffer_1, satmp.s + pos, satmp.len - pos) < (int)(satmp.len - pos)) die() ; + } + } + return 0 ; +} diff --git a/src/include/s6/ftrigr.h b/src/include/s6/ftrigr.h new file mode 100644 index 0000000..e7c8b0c --- /dev/null +++ b/src/include/s6/ftrigr.h @@ -0,0 +1,94 @@ +/* ISC license. */ + +#ifndef FTRIGR_H +#define FTRIGR_H + +#include <skalibs/config.h> +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/tai.h> +#include <skalibs/genalloc.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/config.h> + + + /* Constants */ + +#define FTRIGR_IPCPATH SKALIBS_SPROOT "/service/ftrigrd/s" + +#define FTRIGRD_PROG S6_BINPREFIX "/s6-ftrigrd" +#define FTRIGR_BANNER1 "ftrigr v1.0 (b)\n" +#define FTRIGR_BANNER1_LEN (sizeof FTRIGR_BANNER1 - 1) +#define FTRIGR_BANNER2 "ftrigr v1.0 (a)\n" +#define FTRIGR_BANNER2_LEN (sizeof FTRIGR_BANNER2 - 1) + + + /* Internals of the ftrigr_t */ + +typedef enum fr1state_e fr1state_t, *fr1state_t_ref ; +enum fr1state_e +{ + FR1STATE_WAITACK, + FR1STATE_WAITACKDATA, + FR1STATE_LISTENING, + FR1STATE_ERROR +} ; + +typedef struct ftrigr1_s ftrigr1_t, *ftrigr1_t_ref ; +struct ftrigr1_s +{ + uint32 options ; + unsigned int count ; + fr1state_t state ; + char what ; +} ; +#define FTRIGR1_ZERO { 0, 0, FR1STATE_ERROR, 0 } +extern ftrigr1_t const ftrigr1_zero ; + + + /* The ftrigr_t itself */ + +typedef struct ftrigr_s ftrigr, ftrigr_t, *ftrigr_ref, *ftrigr_t_ref ; +struct ftrigr_s +{ + skaclient_t connection ; + genalloc list ; /* array of uint16 */ + gensetdyn data ; /* set of ftrigr1_t */ + skaclient_buffer_t buffers ; +} ; +#define FTRIGR_ZERO { .connection = SKACLIENT_ZERO, .list = GENALLOC_ZERO, .data = GENSETDYN_INIT(ftrigr1_t, 2, 0, 1) } +extern ftrigr_t const ftrigr_zero ; + + + /* Starting and ending a session */ + +extern int ftrigr_start (ftrigr_t *, char const *, tain_t const *, tain_t *) ; +#define ftrigr_start_g(a, path, deadline) ftrigr_start(a, path, (deadline), &STAMP) +extern int ftrigr_startf (ftrigr_t *, tain_t const *, tain_t *) ; +#define ftrigr_startf_g(a, deadline) ftrigr_startf(a, (deadline), &STAMP) +extern void ftrigr_end (ftrigr_t *) ; + + + /* Instant primitives for async programming */ + +#define ftrigr_fd(a) skaclient_fd(&(a)->connection) +extern int ftrigr_update (ftrigr_t *) ; +extern int ftrigr_check (ftrigr_t *, uint16, char *) ; + + + /* Synchronous functions with timeouts */ + +#define FTRIGR_REPEAT 0x0001 + +extern uint16 ftrigr_subscribe (ftrigr_t *, char const *, char const *, uint32, tain_t const *, tain_t *) ; +#define ftrigr_subscribe_g(a, path, re, options, deadline) ftrigr_subscribe(a, path, re, options, (deadline), &STAMP) +extern int ftrigr_unsubscribe (ftrigr_t *, uint16, tain_t const *, tain_t *) ; +#define ftrigr_unsubscribe_g(a, id, deadline) ftrigr_unsubscribe(a, id, (deadline), &STAMP) + +extern int ftrigr_wait_and (ftrigr_t *, uint16 const *, unsigned int, tain_t const *, tain_t *) ; +#define ftrigr_wait_and_g(a, list, len, deadline) ftrigr_wait_and(a, list, len, (deadline), &STAMP) +extern int ftrigr_wait_or (ftrigr_t *, uint16 const *, unsigned int, tain_t const *, tain_t *, char *) ; +#define ftrigr_wait_or_g(a, list, len, deadline, what) ftrigr_wait_or(a, list, len, deadline, &STAMP, what) + +#endif diff --git a/src/include/s6/ftrigw.h b/src/include/s6/ftrigw.h new file mode 100644 index 0000000..ccaf078 --- /dev/null +++ b/src/include/s6/ftrigw.h @@ -0,0 +1,11 @@ +/* ISC license. */ + +#ifndef FTRIGW_H +#define FTRIGW_H + +extern int ftrigw_fifodir_make (char const *, int, int) ; +extern int ftrigw_notify (char const *, char) ; +extern int ftrigw_notifyb (char const *, char const *, unsigned int) ; +extern int ftrigw_clean (char const *) ; + +#endif diff --git a/src/include/s6/s6-supervise.h b/src/include/s6/s6-supervise.h new file mode 100644 index 0000000..2a39393 --- /dev/null +++ b/src/include/s6/s6-supervise.h @@ -0,0 +1,40 @@ +/* ISC license. */ + +#ifndef S6_SUPERVISE_H +#define S6_SUPERVISE_H + +#include <skalibs/tai.h> + +#define S6_SUPERVISE_CTLDIR "supervise" +#define S6_SUPERVISE_EVENTDIR "event" +#define S6_SVSCAN_CTLDIR ".s6-svscan" +#define S6_SVSTATUS_FILENAME S6_SUPERVISE_CTLDIR "/status" +#define S6_SVSTATUS_SIZE 18 + +extern int s6_svc_write (char const *, char const *, unsigned int) ; +extern int s6_svc_main (int, char const *const *, char const *, char const *, char const *) ; + +typedef struct s6_svstatus_s s6_svstatus_t, *s6_svstatus_t_ref ; +struct s6_svstatus_s +{ + tain_t stamp ; + unsigned int pid ; + unsigned int flagwant : 1 ; + unsigned int flagwantup : 1 ; + unsigned int flagpaused : 1 ; + unsigned int flagfinishing : 1 ; +} ; + +#define S6_SVSTATUS_ZERO { .stamp = TAIN_ZERO, .pid = 0, .flagwant = 0, .flagwantup = 0, .flagpaused = 0, .flagfinishing = 0 } + + +extern void s6_svstatus_pack (char *, s6_svstatus_t const *) ; +extern void s6_svstatus_unpack (char const *, s6_svstatus_t_ref) ; +extern int s6_svstatus_read (char const *, s6_svstatus_t_ref) ; +extern int s6_svstatus_write (char const *, s6_svstatus_t const *) ; + +/* These functions leak a fd, that's intended */ +extern int s6_supervise_lock (char const *) ; +extern int s6_supervise_lock_mode (char const *, unsigned int, unsigned int) ; + +#endif diff --git a/src/include/s6/s6.h b/src/include/s6/s6.h new file mode 100644 index 0000000..84c552d --- /dev/null +++ b/src/include/s6/s6.h @@ -0,0 +1,11 @@ +/* ISC license. */ + +#ifndef S6_H +#define S6_H + +#include <s6/s6-supervise.h> +#include <s6/ftrigr.h> +#include <s6/ftrigw.h> +#include <s6/s6lock.h> + +#endif diff --git a/src/include/s6/s6lock.h b/src/include/s6/s6lock.h new file mode 100644 index 0000000..a44d80a --- /dev/null +++ b/src/include/s6/s6lock.h @@ -0,0 +1,75 @@ +/* ISC license. */ + +#ifndef S6LOCK_H +#define S6LOCK_H + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/genalloc.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/config.h> + + + /* Constants */ + +#define S6LOCKD_PROG S6_BINPREFIX "/s6lockd" +#define S6LOCKD_HELPER_PROG S6_BINPREFIX "/s6lockd-helper" + +#define S6LOCK_BANNER1 "s6lock v1.0 (b)\n" +#define S6LOCK_BANNER1_LEN (sizeof S6LOCK_BANNER1 - 1) +#define S6LOCK_BANNER2 "s6lock v1.0 (a)\n" +#define S6LOCK_BANNER2_LEN (sizeof S6LOCK_BANNER2 - 1) + + + /* The client handle */ + +typedef struct s6lock_s s6lock_t, *s6lock_t_ref ; +struct s6lock_s +{ + skaclient_t connection ; + genalloc list ; /* array of uint16 */ + gensetdyn data ; /* set of char */ + skaclient_buffer_t buffers ; +} ; +#define S6LOCK_ZERO { .connection = SKACLIENT_ZERO, .list = GENALLOC_ZERO, .data = GENSETDYN_INIT(int, 2, 0, 1) } +extern s6lock_t const s6lock_zero ; + + + /* Starting and ending a session */ + +extern int s6lock_start (s6lock_t *, char const *, tain_t const *, tain_t *) ; +#define s6lock_start_g(a, ipcpath, deadline) s6lock_start(a, ipcpath, (deadline), &STAMP) +extern int s6lock_startf (s6lock_t *, char const *, tain_t const *, tain_t *) ; +#define s6lock_startf_g(a, lockdir, deadline) s6lock_startf(a, lockdir, (deadline), &STAMP) +extern void s6lock_end (s6lock_t *) ; + + + /* Asynchronous primitives */ + +#define s6lock_fd(a) skaclient_fd(&(a)->connection) +extern int s6lock_update (s6lock_t *) ; +extern int s6lock_check (s6lock_t *, uint16) ; + + + /* Synchronous functions */ + +#define S6LOCK_OPTIONS_SH 0x0000U +#define S6LOCK_OPTIONS_EX 0x0001U + +extern int s6lock_acquire (s6lock_t *, uint16 *, char const *, uint32, tain_t const *, tain_t const *, tain_t *) ; +#define s6lock_acquire_g(a, id, path, options, limit, deadline) s6lock_acquire(a, id, path, options, limit, (deadline), &STAMP) +#define s6lock_acquire_sh(a, id, path, limit, deadline, stamp) s6lock_aquire(a, id, path, S6LOCK_OPTIONS_SH, limit, deadline, stamp) +#define s6lock_acquire_ex(a, id, path, limit, deadline, stamp) s6lock_aquire(a, id, path, S6LOCK_OPTIONS_EX, limit, deadline, stamp) +#define s6lock_acquire_sh_g(a, id, path, limit, deadline) s6lock_acquire_sh(a, id, path, limit, (deadline), &STAMP) +#define s6lock_acquire_ex_g(a, id, path, limit, deadline) s6lock_acquire_ex(a, id, path, limit, (deadline), &STAMP) +extern int s6lock_release (s6lock_t *, uint16, tain_t const *, tain_t *) ; +#define s6lock_release_g(a, id, deadline) s6lock_release(a, id, (deadline), &STAMP) + +extern int s6lock_wait_and (s6lock_t *, uint16 const *, unsigned int, tain_t const *, tain_t *) ; +#define s6lock_wait_and_g(a, list, len, deadline) s6lock_wait_and(a, list, len, (deadline), &STAMP) +extern int s6lock_wait_or (s6lock_t *, uint16 const *, unsigned int, tain_t const *, tain_t *) ; +#define s6lock_wait_or_g(a, list, len, deadline) s6lock_wait_or(a, list, len, (deadline), &STAMP) + +#endif diff --git a/src/libs6/deps-exe/s6-ftrigrd b/src/libs6/deps-exe/s6-ftrigrd new file mode 100755 index 0000000..4b86d93 --- /dev/null +++ b/src/libs6/deps-exe/s6-ftrigrd @@ -0,0 +1,5 @@ +ftrig1_free.o +ftrig1_make.o +-lskarnet +${SOCKET_LIB} +${TAINNOW_LIB} diff --git a/src/libs6/deps-exe/s6lockd b/src/libs6/deps-exe/s6lockd new file mode 100755 index 0000000..e027835 --- /dev/null +++ b/src/libs6/deps-exe/s6lockd @@ -0,0 +1,3 @@ +-lskarnet +${SOCKET_LIB} +${TAINNOW_LIB} diff --git a/src/libs6/deps-exe/s6lockd-helper b/src/libs6/deps-exe/s6lockd-helper new file mode 100755 index 0000000..e7187fe --- /dev/null +++ b/src/libs6/deps-exe/s6lockd-helper @@ -0,0 +1 @@ +-lskarnet diff --git a/src/libs6/deps-lib/s6 b/src/libs6/deps-lib/s6 new file mode 100755 index 0000000..502694e --- /dev/null +++ b/src/libs6/deps-lib/s6 @@ -0,0 +1,33 @@ +ftrigr1_zero.o +ftrigr_check.o +ftrigr_end.o +ftrigr_start.o +ftrigr_startf.o +ftrigr_subscribe.o +ftrigr_unsubscribe.o +ftrigr_update.o +ftrigr_wait_and.o +ftrigr_wait_or.o +ftrigr_zero.o +ftrigw_clean.o +ftrigw_fifodir_make.o +ftrigw_notify.o +ftrigw_notifyb.o +s6_supervise_lock.o +s6_supervise_lock_mode.o +s6_svc_main.o +s6_svc_write.o +s6_svstatus_pack.o +s6_svstatus_read.o +s6_svstatus_unpack.o +s6_svstatus_write.o +s6lock_acquire.o +s6lock_check.o +s6lock_end.o +s6lock_release.o +s6lock_start.o +s6lock_startf.o +s6lock_update.o +s6lock_wait_and.o +s6lock_wait_or.o +s6lock_zero.o diff --git a/src/libs6/ftrig1.h b/src/libs6/ftrig1.h new file mode 100644 index 0000000..229de66 --- /dev/null +++ b/src/libs6/ftrig1.h @@ -0,0 +1,23 @@ +/* ISC license. */ + +#ifndef FTRIG1_H +#define FTRIG1_H + +#include <skalibs/stralloc.h> + +#define FTRIG1_PREFIX "ftrig1" +#define FTRIG1_PREFIXLEN (sizeof FTRIG1_PREFIX - 1) + +typedef struct ftrig1_s ftrig1_t, *ftrig1_t_ref ; +struct ftrig1_s +{ + int fd ; + int fdw ; + stralloc name ; +} ; +#define FTRIG1_ZERO { .fd = -1, .fdw = -1, .name = STRALLOC_ZERO } + +extern int ftrig1_make (ftrig1_t *, char const *) ; +extern void ftrig1_free (ftrig1_t *) ; + +#endif diff --git a/src/libs6/ftrig1_free.c b/src/libs6/ftrig1_free.c new file mode 100644 index 0000000..091dc87 --- /dev/null +++ b/src/libs6/ftrig1_free.c @@ -0,0 +1,25 @@ +/* ISC license. */ + +#include <unistd.h> +#include <skalibs/stralloc.h> +#include <skalibs/djbunix.h> +#include "ftrig1.h" + +void ftrig1_free (ftrig1_t *p) +{ + if (p->name.s) + { + unlink(p->name.s) ; + stralloc_free(&p->name) ; + } + if (p->fd >= 0) + { + fd_close(p->fd) ; + p->fd = -1 ; + } + if (p->fdw >= 0) + { + fd_close(p->fdw) ; + p->fdw = -1 ; + } +} diff --git a/src/libs6/ftrig1_make.c b/src/libs6/ftrig1_make.c new file mode 100644 index 0000000..7aedd08 --- /dev/null +++ b/src/libs6/ftrig1_make.c @@ -0,0 +1,65 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdio.h> /* for rename() */ +#include <errno.h> +#include <skalibs/bytestr.h> +#include <skalibs/tai.h> +#include <skalibs/stralloc.h> +#include <skalibs/djbunix.h> +#include <skalibs/random.h> +#include "ftrig1.h" + +int ftrig1_make (ftrig1_t *f, char const *path) +{ + ftrig1_t ff = FTRIG1_ZERO ; + unsigned int pathlen = str_len(path) ; + int e = 0 ; + char tmp[pathlen + 46 + FTRIG1_PREFIXLEN] ; + + byte_copy(tmp, pathlen, path) ; + tmp[pathlen] = '/' ; tmp[pathlen+1] = '.' ; + byte_copy(tmp + pathlen + 2, FTRIG1_PREFIXLEN, FTRIG1_PREFIX) ; + tmp[pathlen + 2 + FTRIG1_PREFIXLEN] = ':' ; + if (!timestamp(tmp + pathlen + 3 + FTRIG1_PREFIXLEN)) return 0 ; + tmp[pathlen + 28 + FTRIG1_PREFIXLEN] = ':' ; + if (random_name(tmp + pathlen + 29 + FTRIG1_PREFIXLEN, 16) < 16) return 0 ; + tmp[pathlen + 45 + FTRIG1_PREFIXLEN] = 0 ; + + { + mode_t m = umask(0) ; + if (mkfifo(tmp, S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH) == -1) + { + umask(m) ; + return 0 ; + } + umask(m) ; + } + + if (!stralloc_catb(&ff.name, tmp, pathlen+1)) { e = errno ; goto err0 ; } + if (!stralloc_catb(&ff.name, tmp + pathlen + 2, FTRIG1_PREFIXLEN + 44)) + { + e = errno ; goto err1 ; + } + ff.fd = open_read(tmp) ; + if (ff.fd == -1) { e = errno ; goto err1 ; } + ff.fdw = open_write(tmp) ; + if (ff.fdw == -1) { e = errno ; goto err2 ; } + if (rename(tmp, ff.name.s) == -1) goto err3 ; + *f = ff ; + return 1 ; + + err3: + e = errno ; + fd_close(ff.fdw) ; + err2: + fd_close(ff.fd) ; + err1: + stralloc_free(&ff.name) ; + err0: + unlink(tmp) ; + errno = e ; + return 0 ; +} diff --git a/src/libs6/ftrigr1_zero.c b/src/libs6/ftrigr1_zero.c new file mode 100644 index 0000000..967b4e0 --- /dev/null +++ b/src/libs6/ftrigr1_zero.c @@ -0,0 +1,5 @@ +/* ISC license. */ + +#include <s6/ftrigr.h> + +ftrigr1_t const ftrigr1_zero = FTRIGR1_ZERO ; diff --git a/src/libs6/ftrigr_check.c b/src/libs6/ftrigr_check.c new file mode 100644 index 0000000..147deca --- /dev/null +++ b/src/libs6/ftrigr_check.c @@ -0,0 +1,40 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/gensetdyn.h> +#include <s6/ftrigr.h> + +int ftrigr_check (ftrigr_t *a, uint16 id, char *c) +{ + ftrigr1_t *p ; + if (!id--) return (errno = EINVAL, -1) ; + p = GENSETDYN_P(ftrigr1_t, &a->data, id) ; + if (!p) return (errno = EINVAL, -1) ; + switch (p->state) + { + case FR1STATE_WAITACKDATA : + { + *c = p->what ; + *p = ftrigr1_zero ; + gensetdyn_delete(&a->data, id) ; + return 1 ; + } + case FR1STATE_LISTENING : + { + register unsigned int r = p->count ; + if (r) *c = p->what ; + p->count = 0 ; + return (int)r ; + } + case FR1STATE_WAITACK : + { + errno = p->what ; + *p = ftrigr1_zero ; + gensetdyn_delete(&a->data, id) ; + return -1 ; + } + default: return (errno = EINVAL, -1) ; + } + return 0 ; +} diff --git a/src/libs6/ftrigr_end.c b/src/libs6/ftrigr_end.c new file mode 100644 index 0000000..f35f06c --- /dev/null +++ b/src/libs6/ftrigr_end.c @@ -0,0 +1,14 @@ +/* ISC license. */ + +#include <skalibs/genalloc.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/ftrigr.h> + +void ftrigr_end (ftrigr_ref a) +{ + gensetdyn_free(&a->data) ; + genalloc_free(uint16, &a->list) ; + skaclient_end(&a->connection) ; + *a = ftrigr_zero ; +} diff --git a/src/libs6/ftrigr_start.c b/src/libs6/ftrigr_start.c new file mode 100644 index 0000000..baf9ce5 --- /dev/null +++ b/src/libs6/ftrigr_start.c @@ -0,0 +1,10 @@ +/* ISC license. */ + +#include <skalibs/tai.h> +#include <skalibs/skaclient.h> +#include <s6/ftrigr.h> + +int ftrigr_start (ftrigr_t *a, char const *path, tain_t const *deadline, tain_t *stamp) +{ + return skaclient_start_b(&a->connection, &a->buffers, path, FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, deadline, stamp) ; +} diff --git a/src/libs6/ftrigr_startf.c b/src/libs6/ftrigr_startf.c new file mode 100644 index 0000000..28c81aa --- /dev/null +++ b/src/libs6/ftrigr_startf.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include <skalibs/tai.h> +#include <skalibs/skaclient.h> +#include <s6/ftrigr.h> + +int ftrigr_startf (ftrigr_ref a, tain_t const *deadline, tain_t *stamp) +{ + char const *cargv[2] = { FTRIGRD_PROG, 0 } ; + char const *cenvp[1] = { 0 } ; + return skaclient_startf_b(&a->connection, &a->buffers, cargv[0], cargv, cenvp, SKACLIENT_OPTION_WAITPID, FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, deadline, stamp) ; +} diff --git a/src/libs6/ftrigr_subscribe.c b/src/libs6/ftrigr_subscribe.c new file mode 100644 index 0000000..d645931 --- /dev/null +++ b/src/libs6/ftrigr_subscribe.c @@ -0,0 +1,43 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/siovec.h> +#include <skalibs/tai.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/ftrigr.h> + +uint16 ftrigr_subscribe (ftrigr_t *a, char const *path, char const *re, uint32 options, tain_t const *deadline, tain_t *stamp) +{ + unsigned int pathlen = str_len(path) ; + unsigned int relen = str_len(re) ; + unsigned int i ; + char err ; + char tmp[15] = "--L" ; + siovec_t v[3] = { { .s = tmp, .len = 15 }, { .s = (char *)path, .len = pathlen + 1 }, { .s = (char *)re, .len = relen + 1 } } ; + if (!gensetdyn_new(&a->data, &i)) return 0 ; + uint16_pack_big(tmp, (uint16)i) ; + uint32_pack_big(tmp+3, options) ; + uint32_pack_big(tmp+7, (uint32)pathlen) ; + uint32_pack_big(tmp+11, (uint32)relen) ; + if (!skaclient_sendv(&a->connection, v, 3, &skaclient_default_cb, &err, deadline, stamp)) + { + gensetdyn_delete(&a->data, i) ; + return 0 ; + } + if (err) + { + gensetdyn_delete(&a->data, i) ; + return (errno = err, 0) ; + } + { + register ftrigr1_t *p = GENSETDYN_P(ftrigr1_t, &a->data, i) ; + p->options = options ; + p->state = FR1STATE_LISTENING ; + p->count = 0 ; + p->what = 0 ; + } + return (uint16)(i+1) ; +} diff --git a/src/libs6/ftrigr_unsubscribe.c b/src/libs6/ftrigr_unsubscribe.c new file mode 100644 index 0000000..4833571 --- /dev/null +++ b/src/libs6/ftrigr_unsubscribe.c @@ -0,0 +1,36 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/ftrigr.h> + +int ftrigr_unsubscribe (ftrigr_ref a, uint16 i, tain_t const *deadline, tain_t *stamp) +{ + ftrigr1_t *p ; + if (!i--) return (errno = EINVAL, 0) ; + p = GENSETDYN_P(ftrigr1_t, &a->data, i) ; + if (!p) return (errno = EINVAL, 0) ; + switch (p->state) + { + case FR1STATE_WAITACK : + case FR1STATE_WAITACKDATA : + { + char dummy ; + ftrigr_check(a, i+1, &dummy) ; + return 1 ; + } + default : break ; + } + { + char err ; + char pack[3] = "--U" ; + uint16_pack_big(pack, i) ; + if (!skaclient_send(&a->connection, pack, 3, &skaclient_default_cb, &err, deadline, stamp)) return 0 ; + if (err) return (errno = err, 0) ; + } + *p = ftrigr1_zero ; + return gensetdyn_delete(&a->data, i) ; +} diff --git a/src/libs6/ftrigr_update.c b/src/libs6/ftrigr_update.c new file mode 100644 index 0000000..ad69714 --- /dev/null +++ b/src/libs6/ftrigr_update.c @@ -0,0 +1,43 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/error.h> +#include <skalibs/uint16.h> +#include <skalibs/genalloc.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/unixmessage.h> +#include <skalibs/skaclient.h> +#include <s6/ftrigr.h> + +static int msghandler (unixmessage_t const *m, void *context) +{ + ftrigr_t *a = (ftrigr_t *)context ; + ftrigr1_t *p ; + uint16 id ; + if (m->len != 4 || m->nfds) return (errno = EPROTO, 0) ; + uint16_unpack_big(m->s, &id) ; + p = GENSETDYN_P(ftrigr1_t, &a->data, id) ; + if (!p) return 1 ; + if (p->state != FR1STATE_LISTENING) return (errno = EINVAL, 0) ; + if (!genalloc_readyplus(uint16, &a->list, 1)) return 0 ; + switch (m->s[2]) + { + case 'd' : + p->state = FR1STATE_WAITACK ; + break ; + case '!' : + if (p->options & FTRIGR_REPEAT) p->count++ ; + else p->state = FR1STATE_WAITACKDATA ; + break ; + default : return (errno = EPROTO, 0) ; + } + p->what = m->s[3] ; + id++ ; genalloc_append(uint16, &a->list, &id) ; + return 1 ; +} + +int ftrigr_update (ftrigr_t *a) +{ + genalloc_setlen(uint16, &a->list, 0) ; + return skaclient_update(&a->connection, &msghandler, a) ; +} diff --git a/src/libs6/ftrigr_wait_and.c b/src/libs6/ftrigr_wait_and.c new file mode 100644 index 0000000..f854a8d --- /dev/null +++ b/src/libs6/ftrigr_wait_and.c @@ -0,0 +1,28 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <s6/ftrigr.h> + +int ftrigr_wait_and (ftrigr_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp) +{ + iopause_fd x = { -1, IOPAUSE_READ, 0 } ; + x.fd = ftrigr_fd(a) ; + for (; n ; n--, idlist++) + { + for (;;) + { + char dummy ; + register int r = ftrigr_check(a, *idlist, &dummy) ; + if (r < 0) return r ; + else if (r) break ; + r = iopause_stamp(&x, 1, deadline, stamp) ; + if (r < 0) return r ; + else if (!r) return (errno = ETIMEDOUT, -1) ; + else if (ftrigr_update(a) < 0) return -1 ; + } + } + return 1 ; +} diff --git a/src/libs6/ftrigr_wait_or.c b/src/libs6/ftrigr_wait_or.c new file mode 100644 index 0000000..8a01d85 --- /dev/null +++ b/src/libs6/ftrigr_wait_or.c @@ -0,0 +1,31 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/error.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <s6/ftrigr.h> + +int ftrigr_wait_or (ftrigr_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp, char *c) +{ + iopause_fd x = { -1, IOPAUSE_READ | IOPAUSE_EXCEPT, 0 } ; + x.fd = ftrigr_fd(a) ; + if (x.fd < 0) return -1 ; + for (;;) + { + register unsigned int i = 0 ; + register int r ; + for (; i < n ; i++) + { + r = ftrigr_check(a, idlist[i], c) ; + if (r < 0) return r ; + else if (r) return i ; + } + r = iopause_stamp(&x, 1, deadline, stamp) ; + if (r < 0) return 0 ; + else if (!r) return (errno = ETIMEDOUT, -1) ; + else if (ftrigr_update(a) < 0) return -1 ; + } + return (errno = EPROTO, -1) ; /* can't happen */ +} diff --git a/src/libs6/ftrigr_zero.c b/src/libs6/ftrigr_zero.c new file mode 100644 index 0000000..b09ddb6 --- /dev/null +++ b/src/libs6/ftrigr_zero.c @@ -0,0 +1,5 @@ +/* ISC license. */ + +#include <s6/ftrigr.h> + +ftrigr_t const ftrigr_zero = FTRIGR_ZERO ; diff --git a/src/libs6/ftrigw_clean.c b/src/libs6/ftrigw_clean.c new file mode 100644 index 0000000..1198828 --- /dev/null +++ b/src/libs6/ftrigw_clean.c @@ -0,0 +1,39 @@ +/* ISC license. */ + +#include <unistd.h> +#include <errno.h> +#include <skalibs/direntry.h> +#include <skalibs/bytestr.h> +#include <skalibs/djbunix.h> +#include "ftrig1.h" +#include <s6/ftrigw.h> + +int ftrigw_clean (char const *path) +{ + unsigned int pathlen = str_len(path) ; + int e = 0 ; + DIR *dir = opendir(path) ; + if (!dir) return 0 ; + { + char tmp[pathlen + FTRIG1_PREFIXLEN + 45] ; + byte_copy(tmp, pathlen, path) ; + tmp[pathlen] = '/' ; tmp[pathlen + FTRIG1_PREFIXLEN + 44] = 0 ; + for (;;) + { + direntry *d ; + int fd ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (str_diffn(d->d_name, FTRIG1_PREFIX, FTRIG1_PREFIXLEN)) continue ; + if (str_len(d->d_name) != FTRIG1_PREFIXLEN + 43) continue ; + byte_copy(tmp + pathlen + 1, FTRIG1_PREFIXLEN + 43, d->d_name) ; + fd = open_write(tmp) ; + if (fd >= 0) fd_close(fd) ; + else if ((errno == ENXIO) && (unlink(tmp) < 0)) e = errno ; + } + } + if (errno) e = errno ; + dir_close(dir) ; + return e ? (errno = e, 0) : 1 ; +} diff --git a/src/libs6/ftrigw_fifodir_make.c b/src/libs6/ftrigw_fifodir_make.c new file mode 100644 index 0000000..1a69a8e --- /dev/null +++ b/src/libs6/ftrigw_fifodir_make.c @@ -0,0 +1,26 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <s6/ftrigw.h> + +int ftrigw_fifodir_make (char const *path, int gid, int force) +{ + mode_t m = umask(0) ; + if (mkdir(path, 0700) == -1) + { + struct stat st ; + umask(m) ; + if (errno != EEXIST) return 0 ; + if (stat(path, &st) == -1) return 0 ; + if (st.st_uid != getuid()) return (errno = EACCES, 0) ; + if (!S_ISDIR(st.st_mode)) return (errno = ENOTDIR, 0) ; + if (!force) return 1 ; + } + else umask(m) ; + if ((gid >= 0) && (chown(path, -1, gid) == -1)) return 0 ; + if (chmod(path, (gid >= 0) ? 03730 : 01733) == -1) return 0 ; + return 1 ; +} diff --git a/src/libs6/ftrigw_notify.c b/src/libs6/ftrigw_notify.c new file mode 100644 index 0000000..230dad6 --- /dev/null +++ b/src/libs6/ftrigw_notify.c @@ -0,0 +1,8 @@ +/* ISC license. */ + +#include <s6/ftrigw.h> + +int ftrigw_notify (char const *path, char c) +{ + return ftrigw_notifyb(path, &c, 1) ; +} diff --git a/src/libs6/ftrigw_notifyb.c b/src/libs6/ftrigw_notifyb.c new file mode 100644 index 0000000..345a3cc --- /dev/null +++ b/src/libs6/ftrigw_notifyb.c @@ -0,0 +1,67 @@ +/* ISC license. */ + +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <skalibs/direntry.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/bytestr.h> +#include <skalibs/sig.h> +#include <skalibs/djbunix.h> +#include "ftrig1.h" +#include <s6/ftrigw.h> + +int ftrigw_notifyb (char const *path, char const *s, unsigned int len) +{ + unsigned int i = 0 ; + struct skasigaction old ; + DIR *dir = opendir(path) ; + if (!dir) return -1 ; + if (skasigaction(SIGPIPE, &SKASIG_IGN, &old) < 0) return -1 ; + { + unsigned int pathlen = str_len(path) ; + char tmp[pathlen + FTRIG1_PREFIXLEN + 45] ; + byte_copy(tmp, pathlen, path) ; + tmp[pathlen] = '/' ; tmp[pathlen + FTRIG1_PREFIXLEN + 44] = 0 ; + for (;;) + { + direntry *d ; + int fd ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (str_diffn(d->d_name, FTRIG1_PREFIX, FTRIG1_PREFIXLEN)) continue ; + if (str_len(d->d_name) != FTRIG1_PREFIXLEN + 43) continue ; + byte_copy(tmp + pathlen + 1, FTRIG1_PREFIXLEN + 43, d->d_name) ; + fd = open_write(tmp) ; + if (fd == -1) + { + if (errno == ENXIO) unlink(tmp) ; + } + else + { + register int r = fd_write(fd, s, len) ; + if ((r < 0) || (unsigned int)r < len) + { + if (errno == EPIPE) unlink(tmp) ; + /* what to do if EGAIN ? full fifo -> fix the reader ! + There's a race condition in extreme cases though ; + but it's still better to be nonblocking - the writer + shouldn't get in trouble because of a bad reader. */ + fd_close(fd) ; + } + else + { + fd_close(fd) ; + i++ ; + } + } + } + } + { + int e = errno ; + skasigaction(SIGPIPE, &old, 0) ; + dir_close(dir) ; + return e ? (errno = e, -1) : (int)i ; + } +} diff --git a/src/libs6/s6-ftrigrd.c b/src/libs6/s6-ftrigrd.c new file mode 100644 index 0000000..b766f36 --- /dev/null +++ b/src/libs6/s6-ftrigrd.c @@ -0,0 +1,266 @@ +/* ISC license. */ + +#include <errno.h> +#include <signal.h> +#include <regex.h> +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/bytestr.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/error.h> +#include <skalibs/strerr2.h> +#include <skalibs/buffer.h> +#include <skalibs/stralloc.h> +#include <skalibs/genalloc.h> +#include <skalibs/bufalloc.h> +#include <skalibs/sig.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/iopause.h> +#include <skalibs/unixmessage.h> +#include <skalibs/skaclient.h> +#include "ftrig1.h" +#include <s6/ftrigr.h> + +#define FTRIGRD_MAXREADS 32 +#define FTRIGRD_BUFSIZE 16 + +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") + +typedef struct ftrigio_s ftrigio_t, *ftrigio_t_ref ; +struct ftrigio_s +{ + unsigned int xindex ; + ftrig1_t trig ; + buffer b ; + char buf[FTRIGRD_BUFSIZE] ; + regex_t re ; + stralloc sa ; + uint32 options ; + uint16 id ; /* given by client */ +} ; +#define FTRIGIO_ZERO { .xindex = 0, .trig = FTRIG1_ZERO, .b = BUFFER_INIT(0, -1, 0, 0), .buf = "", .sa = STRALLOC_ZERO, .options = 0, .id = 0 } +static ftrigio_t const fzero = FTRIGIO_ZERO ; + +static genalloc a = GENALLOC_ZERO ; /* array of ftrigio_t */ + +static void ftrigio_deepfree (ftrigio_t_ref p) +{ + ftrig1_free(&p->trig) ; + stralloc_free(&p->sa) ; + regfree(&p->re) ; + *p = fzero ; +} + +static void cleanup (void) +{ + register unsigned int i = genalloc_len(ftrigio_t, &a) ; + for (; i ; i--) ftrigio_deepfree(genalloc_s(ftrigio_t, &a) + i - 1) ; + genalloc_setlen(ftrigio_t, &a, 0) ; +} + +static void trig (uint16 id, char what, char info) +{ + char pack[4] ; + unixmessage_t m = { .s = pack, .len = 4, .fds = 0, .nfds = 0 } ; + uint16_pack_big(pack, id) ; + pack[2] = what ; pack[3] = info ; + if (!unixmessage_put(unixmessage_sender_x, &m)) + { + cleanup() ; + strerr_diefu1sys(111, "build answer") ; + } +} + +static void answer (char c) +{ + unixmessage_t m = { &c, 1, 0, 0 } ; + if (!unixmessage_put(unixmessage_sender_1, &m)) + { + cleanup() ; + strerr_diefu1sys(111, "unixmessage_put") ; + } +} + +static void remove (unsigned int i) +{ + register unsigned int n = genalloc_len(ftrigio_t, &a) - 1 ; + ftrigio_deepfree(genalloc_s(ftrigio_t, &a) + i) ; + genalloc_s(ftrigio_t, &a)[i] = genalloc_s(ftrigio_t, &a)[n] ; + genalloc_setlen(ftrigio_t, &a, n) ; +} + +static inline int ftrigio_read (ftrigio_t *p) +{ + unsigned int n = FTRIGRD_MAXREADS ; + while (n--) + { + regmatch_t pmatch ; + unsigned int blen ; + register int r = sanitize_read(buffer_fill(&p->b)) ; + if (!r) break ; + if (r < 0) return (trig(p->id, 'd', errno), 0) ; + blen = buffer_len(&p->b) ; + if (!stralloc_readyplus(&p->sa, blen+1)) dienomem() ; + buffer_getnofill(&p->b, p->sa.s + p->sa.len, blen) ; + p->sa.len += blen ; + p->sa.s[p->sa.len] = 0 ; + while (!regexec(&p->re, p->sa.s, 1, &pmatch, REG_NOTBOL | REG_NOTEOL)) + { + trig(p->id, '!', p->sa.s[pmatch.rm_eo - 1]) ; + if (!(p->options & FTRIGR_REPEAT)) return 0 ; + byte_copy(p->sa.s, p->sa.len + 1 - pmatch.rm_eo, p->sa.s + pmatch.rm_eo) ; + p->sa.len -= pmatch.rm_eo ; + } + } + return 1 ; +} + +static int parse_protocol (unixmessage_t const *m, void *context) +{ + uint16 id ; + if (m->len < 3 || m->nfds) + { + cleanup() ; + strerr_dief1x(100, "invalid client request") ; + } + uint16_unpack_big(m->s, &id) ; + switch (m->s[2]) + { + case 'U' : /* unsubscribe */ + { + register unsigned int i = genalloc_len(ftrigio_t, &a) ; + for (; i ; i--) if (genalloc_s(ftrigio_t, &a)[i-1].id == id) break ; + if (i) remove(i-1) ; + answer(0) ; + break ; + } + case 'L' : /* subscribe to path and match re */ + { + ftrigio_t f = FTRIGIO_ZERO ; + uint32 pathlen, relen ; + int r ; + if (m->len < 18) + { + answer(EPROTO) ; + break ; + } + uint32_unpack_big(m->s + 3, &f.options) ; + uint32_unpack_big(m->s + 7, &pathlen) ; + uint32_unpack_big(m->s + 11, &relen) ; + if (((pathlen + relen + 17) != m->len) || m->s[15 + pathlen] || m->s[m->len - 1]) + { + answer(EPROTO) ; + break ; + } + f.id = id ; + r = regcomp(&f.re, m->s + 16 + pathlen, REG_EXTENDED) ; + if (r) + { + answer(r == REG_ESPACE ? ENOMEM : EINVAL) ; + break ; + } + if (!ftrig1_make(&f.trig, m->s + 15)) + { + regfree(&f.re) ; + answer(errno) ; + break ; + } + if (!genalloc_append(ftrigio_t, &a, &f)) + { + ftrigio_deepfree(&f) ; + answer(errno) ; + break ; + } + answer(0) ; + break ; + } + default : + { + cleanup() ; + strerr_dief1x(100, "invalid client request") ; + } + } + (void)context ; + return 1 ; +} + +int main (void) +{ + PROG = "s6-ftrigrd" ; + + if (ndelay_on(0) < 0) strerr_diefu2sys(111, "ndelay_on ", "0") ; + if (ndelay_on(1) < 0) strerr_diefu2sys(111, "ndelay_on ", "1") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ; + + { + tain_t deadline ; + tain_now_g() ; + tain_addsec_g(&deadline, 2) ; + if (!skaclient_server_01x_init_g(FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, &deadline)) + strerr_diefu1sys(111, "sync with client") ; + } + + for (;;) + { + register unsigned int n = genalloc_len(ftrigio_t, &a) ; + iopause_fd x[3 + n] ; + unsigned int i = 0 ; + + x[0].fd = 0 ; x[0].events = IOPAUSE_EXCEPT | IOPAUSE_READ ; + x[1].fd = 1 ; x[1].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_1) ? 0 : IOPAUSE_WRITE) ; + x[2].fd = unixmessage_sender_fd(unixmessage_sender_x) ; + x[2].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_x) ? 0 : IOPAUSE_WRITE) ; + for (; i < n ; i++) + { + register ftrigio_t_ref p = genalloc_s(ftrigio_t, &a) + i ; + p->xindex = 3 + i ; + x[3+i].fd = p->trig.fd ; + x[3+i].events = IOPAUSE_READ ; + } + + if (iopause(x, 3 + n, 0, 0) < 0) + { + cleanup() ; + strerr_diefu1sys(111, "iopause") ; + } + + /* client closed */ + if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) break ; + + /* client is reading */ + if (x[1].revents & IOPAUSE_WRITE) + if ((unixmessage_sender_flush(unixmessage_sender_1) < 0) && !error_isagain(errno)) + { + cleanup() ; + strerr_diefu1sys(111, "flush stdout") ; + } + if (x[2].revents & IOPAUSE_WRITE) + if ((!unixmessage_sender_flush(unixmessage_sender_x) < 0) && !error_isagain(errno)) + { + cleanup() ; + strerr_diefu1sys(111, "flush asyncout") ; + } + + /* scan listening ftrigs */ + for (i = 0 ; i < genalloc_len(ftrigio_t, &a) ; i++) + { + register ftrigio_t_ref p = genalloc_s(ftrigio_t, &a) + i ; + if (x[p->xindex].revents & IOPAUSE_READ) + if (!ftrigio_read(p)) remove(i--) ; + } + + /* client is writing */ + if (!unixmessage_receiver_isempty(unixmessage_receiver_0) || x[0].revents & IOPAUSE_READ) + { + if (unixmessage_handle(unixmessage_receiver_0, &parse_protocol, 0) < 0) + { + if (errno == EPIPE) break ; /* normal exit */ + cleanup() ; + strerr_diefu1sys(111, "handle messages from client") ; + } + } + } + cleanup() ; + return 0 ; +} diff --git a/src/libs6/s6_supervise_lock.c b/src/libs6/s6_supervise_lock.c new file mode 100644 index 0000000..5c9ca30 --- /dev/null +++ b/src/libs6/s6_supervise_lock.c @@ -0,0 +1,10 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <s6/s6-supervise.h> + +int s6_supervise_lock (char const *subdir) +{ + return s6_supervise_lock_mode(subdir, S_IRWXU, S_IRUSR | S_IWUSR) ; +} diff --git a/src/libs6/s6_supervise_lock_mode.c b/src/libs6/s6_supervise_lock_mode.c new file mode 100644 index 0000000..ff3e11e --- /dev/null +++ b/src/libs6/s6_supervise_lock_mode.c @@ -0,0 +1,54 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <skalibs/bytestr.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> +#include <s6/s6-supervise.h> + +int s6_supervise_lock_mode (char const *subdir, unsigned int subdirmode, unsigned int controlmode) +{ + unsigned int subdirlen = str_len(subdir) ; + int fdctl, fdctlw, fdlock ; + char control[subdirlen + 9] ; + char lock[subdirlen + 6] ; + byte_copy(control, subdirlen, subdir) ; + byte_copy(control + subdirlen, 9, "/control") ; + byte_copy(lock, subdirlen, subdir) ; + byte_copy(lock + subdirlen, 6, "/lock") ; + if ((mkdir(subdir, (mode_t)subdirmode) == -1) && (errno != EEXIST)) + strerr_diefu2sys(111, "mkdir ", subdir) ; + if (mkfifo(control, controlmode) < 0) + { + struct stat st ; + if (errno != EEXIST) + strerr_diefu2sys(111, "mkfifo ", control) ; + if (stat(control, &st) < 0) + strerr_diefu2sys(111, "stat ", control) ; + if (!S_ISFIFO(st.st_mode)) + strerr_diefu2x(100, control, " is not a FIFO") ; + } + fdlock = open_create(lock) ; + if (fdlock < 0) + strerr_diefu2sys(111, "open_create ", lock) ; + if (lock_ex(fdlock) < 0) + strerr_diefu2sys(111, "lock ", lock) ; + fdctlw = open_write(control) ; + if (fdctlw >= 0) strerr_dief1x(100, "directory already locked") ; + if (errno != ENXIO) + strerr_diefu2sys(111, "open_write ", control) ; + fdctl = open_read(control) ; + if (fdctl < 0) + strerr_diefu2sys(111, "open_read ", control) ; + fdctlw = open_write(control) ; + if (fdctlw < 0) + strerr_diefu2sys(111, "open_write ", control) ; + fd_close(fdlock) ; + if ((coe(fdctlw) < 0) || (coe(fdctl) < 0)) + strerr_diefu2sys(111, "coe ", control) ; + + return fdctl ; + /* fdctlw is leaking. That's okay, it's coe. */ +} diff --git a/src/libs6/s6_svc_main.c b/src/libs6/s6_svc_main.c new file mode 100644 index 0000000..5d14904 --- /dev/null +++ b/src/libs6/s6_svc_main.c @@ -0,0 +1,40 @@ +/* ISC license. */ + +#include <skalibs/bytestr.h> +#include <skalibs/sgetopt.h> +#include <skalibs/strerr2.h> +#include <skalibs/skamisc.h> +#include <s6/s6-supervise.h> + +#define DATASIZE 256 + +int s6_svc_main (int argc, char const *const *argv, char const *optstring, char const *usage, char const *controldir) +{ + char data[DATASIZE] ; + unsigned int datalen = 0 ; + register int r ; + for (;;) + { + register int opt = subgetopt(argc, argv, optstring) ; + if (opt == -1) break ; + if (opt == '?') strerr_dieusage(100, usage) ; + if (datalen >= DATASIZE) strerr_dief1x(100, "too many commands") ; + data[datalen++] = opt ; + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (!argc) strerr_dieusage(100, usage) ; + + { + unsigned int arglen = str_len(*argv) ; + unsigned int cdirlen = str_len(controldir) ; + char tmp[arglen + cdirlen + 10] ; + byte_copy(tmp, arglen, *argv) ; + tmp[arglen] = '/' ; + byte_copy(tmp + arglen + 1, cdirlen, controldir) ; + byte_copy(tmp + arglen + 1 + cdirlen, 9, "/control") ; + r = s6_svc_write(tmp, data, datalen) ; + } + if (r < 0) strerr_diefu2sys(111, "control ", *argv) ; + else if (!r) strerr_diefu3x(100, "control ", *argv, ": supervisor not listening") ; + return 0 ; +} diff --git a/src/libs6/s6_svc_write.c b/src/libs6/s6_svc_write.c new file mode 100644 index 0000000..ea9eee5 --- /dev/null +++ b/src/libs6/s6_svc_write.c @@ -0,0 +1,22 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/djbunix.h> +#include <s6/s6-supervise.h> + +int s6_svc_write (char const *fifo, char const *data, unsigned int datalen) +{ + int fd = open_write(fifo) ; + if (fd < 0) return (errno == ENXIO) ? 0 : -1 ; + else if (ndelay_off(fd) == -1) return -1 ; + else if (fd_write(fd, data, datalen) == -1) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + return -1 ; + } + fd_close(fd) ; + return 1 ; +} diff --git a/src/libs6/s6_svstatus_pack.c b/src/libs6/s6_svstatus_pack.c new file mode 100644 index 0000000..2d5baf6 --- /dev/null +++ b/src/libs6/s6_svstatus_pack.c @@ -0,0 +1,13 @@ +/* ISC license. */ + +#include <skalibs/uint32.h> +#include <skalibs/tai.h> +#include <s6/s6-supervise.h> + +void s6_svstatus_pack (char *pack, s6_svstatus_t const *sv) +{ + tain_pack(pack, &sv->stamp) ; + uint32_pack(pack + 12, (uint32)sv->pid) ; + pack[16] = sv->flagpaused | (sv->flagfinishing << 1) ; + pack[17] = sv->flagwant ? sv->flagwantup ? 'u' : 'd' : 0 ; +} diff --git a/src/libs6/s6_svstatus_read.c b/src/libs6/s6_svstatus_read.c new file mode 100644 index 0000000..32ec660 --- /dev/null +++ b/src/libs6/s6_svstatus_read.c @@ -0,0 +1,17 @@ +/* ISC license. */ + +#include <skalibs/bytestr.h> +#include <skalibs/djbunix.h> +#include <s6/s6-supervise.h> + +int s6_svstatus_read (char const *dir, s6_svstatus_t_ref status) +{ + unsigned int n = str_len(dir) ; + char pack[S6_SVSTATUS_SIZE] ; + char tmp[n + 2 + sizeof(S6_SVSTATUS_FILENAME)] ; + byte_copy(tmp, n, dir) ; + byte_copy(tmp + n, 2 + sizeof(S6_SVSTATUS_FILENAME), "/" S6_SVSTATUS_FILENAME) ; + if (openreadnclose(tmp, pack, S6_SVSTATUS_SIZE) < S6_SVSTATUS_SIZE) return 0 ; + s6_svstatus_unpack(pack, status) ; + return 1 ; +} diff --git a/src/libs6/s6_svstatus_unpack.c b/src/libs6/s6_svstatus_unpack.c new file mode 100644 index 0000000..cce6989 --- /dev/null +++ b/src/libs6/s6_svstatus_unpack.c @@ -0,0 +1,29 @@ +/* ISC license. */ + +#include <skalibs/uint32.h> +#include <skalibs/tai.h> +#include <s6/s6-supervise.h> + +void s6_svstatus_unpack (char const *pack, s6_svstatus_t_ref sv) +{ + uint32 pid ; + tain_unpack(pack, &sv->stamp) ; + uint32_unpack(pack + 12, &pid) ; + sv->pid = (int)pid ; + sv->flagpaused = pack[16] & 1 ; + sv->flagfinishing = (pack[16] >> 1) & 1 ; + switch (pack[17]) + { + case 'u' : + sv->flagwant = 1 ; + sv->flagwantup = 1 ; + break ; + case 'd' : + sv->flagwant = 1 ; + sv->flagwantup = 0 ; + break ; + default : + sv->flagwant = 0 ; + sv->flagwantup = 0 ; + } +} diff --git a/src/libs6/s6_svstatus_write.c b/src/libs6/s6_svstatus_write.c new file mode 100644 index 0000000..2cc8a7b --- /dev/null +++ b/src/libs6/s6_svstatus_write.c @@ -0,0 +1,16 @@ +/* ISC license. */ + +#include <skalibs/bytestr.h> +#include <skalibs/djbunix.h> +#include <s6/s6-supervise.h> + +int s6_svstatus_write (char const *dir, s6_svstatus_t const *status) +{ + unsigned int n = str_len(dir) ; + char pack[S6_SVSTATUS_SIZE] ; + char tmp[n + 2 + sizeof(S6_SVSTATUS_FILENAME)] ; + byte_copy(tmp, n, dir) ; + byte_copy(tmp + n, 2 + sizeof(S6_SVSTATUS_FILENAME), "/" S6_SVSTATUS_FILENAME) ; + s6_svstatus_pack(pack, status) ; + return openwritenclose_suffix(tmp, pack, S6_SVSTATUS_SIZE, ".new") ; +} diff --git a/src/libs6/s6lock_acquire.c b/src/libs6/s6lock_acquire.c new file mode 100644 index 0000000..b0fef54 --- /dev/null +++ b/src/libs6/s6lock_acquire.c @@ -0,0 +1,38 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/bytestr.h> +#include <skalibs/siovec.h> +#include <skalibs/tai.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +int s6lock_acquire (s6lock_t *a, uint16 *u, char const *path, uint32 options, tain_t const *limit, tain_t const *deadline, tain_t *stamp) +{ + unsigned int pathlen = str_len(path) ; + char err ; + char tmp[23] = "--<" ; + siovec_t v[2] = { { .s = tmp, .len = 23 }, { .s = (char *)path, .len = pathlen + 1 } } ; + unsigned int i ; + if (!gensetdyn_new(&a->data, &i)) return 0 ; + uint16_pack_big(tmp, (uint16)i) ; + uint32_pack_big(tmp+3, options) ; + tain_pack(tmp+7, limit) ; + uint32_pack_big(tmp+19, (uint32)pathlen) ; + if (!skaclient_sendv(&a->connection, v, 2, &skaclient_default_cb, &err, deadline, stamp)) + { + gensetdyn_delete(&a->data, i) ; + return 0 ; + } + if (err) + { + gensetdyn_delete(&a->data, i) ; + return (errno = err, 0) ; + } + *GENSETDYN_P(char, &a->data, i) = EAGAIN ; + *u = i ; + return 1 ; +} diff --git a/src/libs6/s6lock_check.c b/src/libs6/s6lock_check.c new file mode 100644 index 0000000..e794128 --- /dev/null +++ b/src/libs6/s6lock_check.c @@ -0,0 +1,25 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/error.h> +#include <skalibs/uint16.h> +#include <skalibs/gensetdyn.h> +#include <s6/s6lock.h> + +int s6lock_check (s6lock_t *a, uint16 id) +{ + char *p = GENSETDYN_P(char, &a->data, id) ; + switch (*p) + { + case EBUSY : return 1 ; + case EINVAL : return (errno = EINVAL, -1) ; + default : + { + if (error_isagain(*p)) return 0 ; + errno = *p ; + *p = EINVAL ; + gensetdyn_delete(&a->data, id) ; + return -1 ; + } + } +} diff --git a/src/libs6/s6lock_end.c b/src/libs6/s6lock_end.c new file mode 100644 index 0000000..c460efd --- /dev/null +++ b/src/libs6/s6lock_end.c @@ -0,0 +1,14 @@ +/* ISC license. */ + +#include <skalibs/genalloc.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +void s6lock_end (s6lock_t *a) +{ + gensetdyn_free(&a->data) ; + genalloc_free(uint16, &a->list) ; + skaclient_end(&a->connection) ; + *a = s6lock_zero ; +} diff --git a/src/libs6/s6lock_release.c b/src/libs6/s6lock_release.c new file mode 100644 index 0000000..95e863f --- /dev/null +++ b/src/libs6/s6lock_release.c @@ -0,0 +1,28 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/error.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +int s6lock_release (s6lock_t *a, uint16 i, tain_t const *deadline, tain_t *stamp) +{ + char *p = GENSETDYN_P(char, &a->data, i) ; + if ((*p != EBUSY) && !error_isagain(*p)) + { + s6lock_check(a, i) ; + return 1 ; + } + { + char err ; + char pack[3] = "-->" ; + uint16_pack_big(pack, i) ; + if (!skaclient_send(&a->connection, pack, 3, &skaclient_default_cb, &err, deadline, stamp)) return 0 ; + if (err) return (errno = err, 0) ; + } + *p = EINVAL ; + return gensetdyn_delete(&a->data, i) ; +} diff --git a/src/libs6/s6lock_start.c b/src/libs6/s6lock_start.c new file mode 100644 index 0000000..e7993d9 --- /dev/null +++ b/src/libs6/s6lock_start.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/environ.h> +#include <skalibs/tai.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +int s6lock_start (s6lock_t *a, char const *path, tain_t const *deadline, tain_t *stamp) +{ + return skaclient_start_b(&a->connection, &a->buffers, path, S6LOCK_BANNER1, S6LOCK_BANNER1_LEN, S6LOCK_BANNER2, S6LOCK_BANNER2_LEN, deadline, stamp) ; +} diff --git a/src/libs6/s6lock_startf.c b/src/libs6/s6lock_startf.c new file mode 100644 index 0000000..c34a595 --- /dev/null +++ b/src/libs6/s6lock_startf.c @@ -0,0 +1,14 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/environ.h> +#include <skalibs/tai.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +int s6lock_startf (s6lock_t *a, char const *lockdir, tain_t const *deadline, tain_t *stamp) +{ + char const *cargv[3] = { S6LOCKD_PROG, lockdir, 0 } ; + if (!lockdir) return (errno = EINVAL, 0) ; + return skaclient_startf_b(&a->connection, &a->buffers, cargv[0], cargv, (char const *const *)environ, SKACLIENT_OPTION_WAITPID, S6LOCK_BANNER1, S6LOCK_BANNER1_LEN, S6LOCK_BANNER2, S6LOCK_BANNER2_LEN, deadline, stamp) ; +} diff --git a/src/libs6/s6lock_update.c b/src/libs6/s6lock_update.c new file mode 100644 index 0000000..6e6a2a0 --- /dev/null +++ b/src/libs6/s6lock_update.c @@ -0,0 +1,31 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/error.h> +#include <skalibs/uint16.h> +#include <skalibs/genalloc.h> +#include <skalibs/gensetdyn.h> +#include <skalibs/unixmessage.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +static int msghandler (unixmessage_t const *m, void *context) +{ + s6lock_t *a = (s6lock_t *)context ; + char *p ; + uint16 id ; + if (m->len != 3 || m->nfds) return (errno = EPROTO, 0) ; + uint16_unpack_big(m->s, &id) ; + p = GENSETDYN_P(char, &a->data, id) ; + if (*p == EBUSY) *p = m->s[2] ; + else if (error_isagain(*p)) *p = m->s[2] ? m->s[2] : EBUSY ; + else return (errno = EPROTO, 0) ; + if (!genalloc_append(uint16, &a->list, &id)) return 0 ; + return 1 ; +} + +int s6lock_update (s6lock_t *a) +{ + genalloc_setlen(uint16, &a->list, 0) ; + return skaclient_update(&a->connection, &msghandler, a) ; +} diff --git a/src/libs6/s6lock_wait_and.c b/src/libs6/s6lock_wait_and.c new file mode 100644 index 0000000..460cc07 --- /dev/null +++ b/src/libs6/s6lock_wait_and.c @@ -0,0 +1,27 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <s6/s6lock.h> + +int s6lock_wait_and (s6lock_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp) +{ + iopause_fd x = { -1, IOPAUSE_READ, 0 } ; + x.fd = s6lock_fd(a) ; + for (; n ; n--, idlist++) + { + for (;;) + { + register int r = s6lock_check(a, *idlist) ; + if (r < 0) return r ; + else if (r) break ; + r = iopause_stamp(&x, 1, deadline, stamp) ; + if (r < 0) return r ; + else if (!r) return (errno = ETIMEDOUT, -1) ; + else if (s6lock_update(a) < 0) return -1 ; + } + } + return 0 ; +} diff --git a/src/libs6/s6lock_wait_or.c b/src/libs6/s6lock_wait_or.c new file mode 100644 index 0000000..0219574 --- /dev/null +++ b/src/libs6/s6lock_wait_or.c @@ -0,0 +1,30 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/uint16.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <s6/s6lock.h> + +int s6lock_wait_or (s6lock_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp) +{ + iopause_fd x = { -1, IOPAUSE_READ | IOPAUSE_EXCEPT, 0 } ; + x.fd = s6lock_fd(a) ; + if (x.fd < 0) return -1 ; + for (;;) + { + register unsigned int i = 0 ; + register int r ; + for (; i < n ; i++) + { + r = s6lock_check(a, idlist[i]) ; + if (r < 0) return r ; + else if (r) return i ; + } + r = iopause_stamp(&x, 1, deadline, stamp) ; + if (r < 0) return 0 ; + else if (!r) return (errno = ETIMEDOUT, -1) ; + else if (s6lock_update(a) < 0) return -1 ; + } + return (errno = EPROTO, -1) ; /* can't happen */ +} diff --git a/src/libs6/s6lock_zero.c b/src/libs6/s6lock_zero.c new file mode 100644 index 0000000..a4e0138 --- /dev/null +++ b/src/libs6/s6lock_zero.c @@ -0,0 +1,5 @@ +/* ISC license. */ + +#include <s6/s6lock.h> + +s6lock_t const s6lock_zero = S6LOCK_ZERO ; diff --git a/src/libs6/s6lockd-helper.c b/src/libs6/s6lockd-helper.c new file mode 100644 index 0000000..8979c67 --- /dev/null +++ b/src/libs6/s6lockd-helper.c @@ -0,0 +1,27 @@ +/* ISC license. */ + +#include <skalibs/allreadwrite.h> +#include <skalibs/strerr2.h> +#include <skalibs/env.h> +#include <skalibs/djbunix.h> + +#define USAGE "s6lockd-helper lockfile" +#define dieusage() strerr_dieusage(100, USAGE) + +int main (int argc, char const *const *argv, char const *const *envp) +{ + int fd ; + char const *x = env_get2(envp, "S6LOCK_EX") ; + char c ; + PROG = "s6lockd-helper" ; + if (argc < 2) dieusage() ; + fd = open_create(argv[1]) ; + if (fd < 0) strerr_diefu2sys(111, "open ", argv[1]) ; + if (((x && *x) ? lock_ex(fd) : lock_sh(fd)) < 0) + strerr_diefu2sys(111, "lock ", argv[1]) ; + if (fd_write(1, "!", 1) <= 0) + strerr_diefu1sys(111, "write to stdout") ; + if (fd_read(0, &c, 1) < 0) + strerr_diefu1sys(111, "read from stdin") ; + return 0 ; +} diff --git a/src/libs6/s6lockd.c b/src/libs6/s6lockd.c new file mode 100644 index 0000000..ef088f9 --- /dev/null +++ b/src/libs6/s6lockd.c @@ -0,0 +1,316 @@ +/* ISC license. */ + +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <skalibs/uint16.h> +#include <skalibs/uint32.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/error.h> +#include <skalibs/strerr2.h> +#include <skalibs/genalloc.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/iopause.h> +#include <skalibs/unixmessage.h> +#include <skalibs/skaclient.h> +#include <s6/s6lock.h> + +#define USAGE "s6lockd lockdir" +#define X() strerr_dief1x(101, "internal inconsistency, please submit a bug-report.") + +typedef struct s6lockio_s s6lockio_t, *s6lockio_t_ref ; +struct s6lockio_s +{ + unsigned int xindex ; + unsigned int pid ; + tain_t limit ; + int p[2] ; + uint16 id ; /* given by client */ +} ; +#define S6LOCKIO_ZERO { 0, 0, TAIN_ZERO, { -1, -1 }, 0 } +static s6lockio_t const szero = S6LOCKIO_ZERO ; + +static genalloc a = GENALLOC_ZERO ; /* array of s6lockio_t */ + +static void s6lockio_free (s6lockio_t_ref p) +{ + register int e = errno ; + fd_close(p->p[1]) ; + fd_close(p->p[0]) ; + kill(p->pid, SIGTERM) ; + *p = szero ; + errno = e ; +} + +static void cleanup (void) +{ + register unsigned int i = genalloc_len(s6lockio_t, &a) ; + for (; i ; i--) s6lockio_free(genalloc_s(s6lockio_t, &a) + i - 1) ; + genalloc_setlen(s6lockio_t, &a, 0) ; +} + +static void trig (uint16 id, char e) +{ + char pack[3] ; + unixmessage_t m = { .s = pack, .len = 3, .fds = 0, .nfds = 0 } ; + uint16_pack_big(pack, id) ; + pack[2] = e ; + if (!unixmessage_put(unixmessage_sender_x, &m)) + { + cleanup() ; + strerr_diefu1sys(111, "build answer") ; + } +} + +static void answer (char c) +{ + unixmessage_t m = { .s = &c, .len = 1, .fds = 0, .nfds = 0 } ; + if (!unixmessage_put(unixmessage_sender_1, &m)) + { + cleanup() ; + strerr_diefu1sys(111, "unixmessage_put") ; + } +} + +static void remove (unsigned int i) +{ + register unsigned int n = genalloc_len(s6lockio_t, &a) - 1 ; + s6lockio_free(genalloc_s(s6lockio_t, &a) + i) ; + genalloc_s(s6lockio_t, &a)[i] = genalloc_s(s6lockio_t, &a)[n] ; + genalloc_setlen(s6lockio_t, &a, n) ; +} + +static void handle_signals (void) +{ + for (;;) + { + switch (selfpipe_read()) + { + case -1 : cleanup() ; strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGTERM : + case SIGQUIT : + case SIGHUP : + case SIGABRT : + case SIGINT : cleanup() ; _exit(0) ; + case SIGCHLD : wait_reap() ; break ; + default : cleanup() ; X() ; + } + } +} + +static int parse_protocol (unixmessage_t const *m, void *context) +{ + uint16 id ; + if (m->len < 3 || m->nfds) + { + cleanup() ; + strerr_dief1x(100, "invalid client request") ; + } + uint16_unpack_big(m->s, &id) ; + switch (m->s[2]) + { + case '>' : /* release */ + { + register unsigned int i = genalloc_len(s6lockio_t, &a) ; + for (; i ; i--) if (genalloc_s(s6lockio_t, &a)[i-1].id == id) break ; + if (i) + { + remove(i-1) ; + answer(0) ; + } + else answer(ENOENT) ; + break ; + } + case '<' : /* lock path */ + { + s6lockio_t f = S6LOCKIO_ZERO ; + char const *cargv[3] = { S6LOCKD_HELPER_PROG, 0, 0 } ; + char const *cenvp[2] = { 0, 0 } ; + uint32 options, pathlen ; + if (m->len < 23) + { + answer(EPROTO) ; + break ; + } + uint32_unpack_big(m->s + 3, &options) ; + tain_unpack(m->s + 7, &f.limit) ; + uint32_unpack_big(m->s + 19, &pathlen) ; + if (pathlen + 23 != m->len || m->s[m->len - 1]) + { + answer(EPROTO) ; + break ; + } + f.id = id ; + m->s[21] = '.' ; + m->s[22] = '/' ; + cargv[1] = (char const *)m->s + 21 ; + if (options & S6LOCK_OPTIONS_EX) cenvp[0] = "S6LOCK_EX=1" ; + f.pid = child_spawn(cargv[0], cargv, cenvp, f.p, 2) ; + if (!f.pid) + { + answer(errno) ; + break ; + } + if (!genalloc_append(s6lockio_t, &a, &f)) + { + s6lockio_free(&f) ; + answer(errno) ; + break ; + } + answer(0) ; + break ; + } + default : + { + cleanup() ; + strerr_dief1x(100, "invalid client request") ; + } + } + (void)context ; + return 1 ; +} + +int main (int argc, char const *const *argv) +{ + tain_t deadline ; + int sfd ; + PROG = "s6lockd" ; + + if (argc < 2) strerr_dieusage(100, USAGE) ; + if (chdir(argv[1]) < 0) strerr_diefu2sys(111, "chdir to ", argv[1]) ; + if (ndelay_on(0) < 0) strerr_diefu2sys(111, "ndelay_on ", "0") ; + if (ndelay_on(1) < 0) strerr_diefu2sys(111, "ndelay_on ", "1") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ; + + sfd = selfpipe_init() ; + if (sfd < 0) strerr_diefu1sys(111, "selfpipe_init") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGCHLD) ; + sigaddset(&set, SIGTERM) ; + sigaddset(&set, SIGQUIT) ; + sigaddset(&set, SIGHUP) ; + sigaddset(&set, SIGABRT) ; + sigaddset(&set, SIGINT) ; + if (selfpipe_trapset(&set) < 0) + strerr_diefu1sys(111, "trap signals") ; + } + + tain_now_g() ; + tain_addsec_g(&deadline, 2) ; + + if (!skaclient_server_01x_init_g(S6LOCK_BANNER1, S6LOCK_BANNER1_LEN, S6LOCK_BANNER2, S6LOCK_BANNER2_LEN, &deadline)) + strerr_diefu1sys(111, "sync with client") ; + + for (;;) + { + register unsigned int n = genalloc_len(s6lockio_t, &a) ; + iopause_fd x[4 + n] ; + unsigned int i = 0 ; + int r ; + + tain_add_g(&deadline, &tain_infinite_relative) ; + x[0].fd = 0 ; x[0].events = IOPAUSE_EXCEPT | IOPAUSE_READ ; + x[1].fd = 1 ; x[1].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_1) ? 0 : IOPAUSE_WRITE ) ; + x[2].fd = unixmessage_sender_fd(unixmessage_sender_x) ; + x[2].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_x) ? 0 : IOPAUSE_WRITE) ; + x[3].fd = sfd ; x[3].events = IOPAUSE_READ ; + for (; i < n ; i++) + { + register s6lockio_t_ref p = genalloc_s(s6lockio_t, &a) + i ; + x[4+i].fd = p->p[0] ; + x[4+i].events = IOPAUSE_READ ; + if (p->limit.sec.x && tain_less(&p->limit, &deadline)) deadline = p->limit ; + p->xindex = 4+i ; + } + + r = iopause_g(x, 4 + n, &deadline) ; + if (r < 0) + { + cleanup() ; + strerr_diefu1sys(111, "iopause") ; + } + + /* timeout => seek and destroy */ + if (!r) + { + for (i = 0 ; i < n ; i++) + { + register s6lockio_t_ref p = genalloc_s(s6lockio_t, &a) + i ; + if (p->limit.sec.x && !tain_future(&p->limit)) break ; + } + if (i < n) + { + trig(genalloc_s(s6lockio_t, &a)[i].id, ETIMEDOUT) ; + remove(i) ; + } + continue ; + } + + /* client closed */ + if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) break ; + + /* client is reading */ + if (x[1].revents & IOPAUSE_WRITE) + if ((unixmessage_sender_flush(unixmessage_sender_1) < 0) && !error_isagain(errno)) + { + cleanup() ; + strerr_diefu1sys(111, "flush stdout") ; + } + if (x[2].revents & IOPAUSE_WRITE) + if ((unixmessage_sender_flush(unixmessage_sender_x) < 0) && !error_isagain(errno)) + { + cleanup() ; + strerr_diefu1sys(111, "flush asyncout") ; + } + + /* scan children for successes */ + for (i = 0 ; i < genalloc_len(s6lockio_t, &a) ; i++) + { + register s6lockio_t_ref p = genalloc_s(s6lockio_t, &a) + i ; + if (p->p[0] < 0) continue ; + if (x[p->xindex].revents & IOPAUSE_READ) + { + char c ; + register int r = sanitize_read(fd_read(p->p[0], &c, 1)) ; + if (!r) continue ; + if (r < 0) + { + trig(p->id, errno) ; + remove(i--) ; + } + else if (c != '!') + { + trig(p->id, EPROTO) ; + remove(i--) ; + } + else + { + trig(p->id, 0) ; + p->limit = tain_zero ; + } + } + } + + /* signals arrived */ + if (x[3].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT)) handle_signals() ; + + /* client is writing */ + if (!unixmessage_receiver_isempty(unixmessage_receiver_0) || x[0].revents & IOPAUSE_READ) + { + if (unixmessage_handle(unixmessage_receiver_0, &parse_protocol, 0) < 0) + { + if (errno == EPIPE) break ; /* normal exit */ + cleanup() ; + strerr_diefu1sys(111, "handle messages from client") ; + } + } + } + cleanup() ; + return 0 ; +} diff --git a/src/pipe-tools/deps-exe/s6-cleanfifodir b/src/pipe-tools/deps-exe/s6-cleanfifodir new file mode 100644 index 0000000..83cec1e --- /dev/null +++ b/src/pipe-tools/deps-exe/s6-cleanfifodir @@ -0,0 +1,2 @@ +-ls6 +-lskarnet diff --git a/src/pipe-tools/deps-exe/s6-ftrig-listen b/src/pipe-tools/deps-exe/s6-ftrig-listen new file mode 100644 index 0000000..38a1f7a --- /dev/null +++ b/src/pipe-tools/deps-exe/s6-ftrig-listen @@ -0,0 +1,4 @@ +-ls6 +-lexecline +-lskarnet +${TAINNOW_LIB} diff --git a/src/pipe-tools/deps-exe/s6-ftrig-listen1 b/src/pipe-tools/deps-exe/s6-ftrig-listen1 new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/pipe-tools/deps-exe/s6-ftrig-listen1 @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/pipe-tools/deps-exe/s6-ftrig-notify b/src/pipe-tools/deps-exe/s6-ftrig-notify new file mode 100644 index 0000000..83cec1e --- /dev/null +++ b/src/pipe-tools/deps-exe/s6-ftrig-notify @@ -0,0 +1,2 @@ +-ls6 +-lskarnet diff --git a/src/pipe-tools/deps-exe/s6-ftrig-wait b/src/pipe-tools/deps-exe/s6-ftrig-wait new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/pipe-tools/deps-exe/s6-ftrig-wait @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/pipe-tools/deps-exe/s6-mkfifodir b/src/pipe-tools/deps-exe/s6-mkfifodir new file mode 100644 index 0000000..83cec1e --- /dev/null +++ b/src/pipe-tools/deps-exe/s6-mkfifodir @@ -0,0 +1,2 @@ +-ls6 +-lskarnet diff --git a/src/pipe-tools/s6-cleanfifodir.c b/src/pipe-tools/s6-cleanfifodir.c new file mode 100644 index 0000000..4af38e1 --- /dev/null +++ b/src/pipe-tools/s6-cleanfifodir.c @@ -0,0 +1,15 @@ +/* ISC license. */ + +#include <skalibs/strerr2.h> +#include <s6/ftrigw.h> + +#define USAGE "s6-cleanfifodir fifodir" + +int main (int argc, char const *const *argv) +{ + PROG = "s6-cleanfifodir" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if (!ftrigw_clean(argv[1])) + strerr_diefu2sys(111, "clean up fifodir at ", argv[1]) ; + return 0 ; +} diff --git a/src/pipe-tools/s6-ftrig-listen.c b/src/pipe-tools/s6-ftrig-listen.c new file mode 100644 index 0000000..2f6e82b --- /dev/null +++ b/src/pipe-tools/s6-ftrig-listen.c @@ -0,0 +1,122 @@ +/* ISC license. */ + +#include <errno.h> +#include <signal.h> +#include <unistd.h> +#include <skalibs/sgetopt.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <execline/execline.h> +#include <s6/ftrigr.h> + +#define USAGE "s6-ftrig-listen [ -a | -o ] [ -t timeout ] ~fifodir1 ~regexp1 ... ; prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +static void handle_signals (void) +{ + for (;;) switch (selfpipe_read()) + { + case -1 : strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGCHLD : wait_reap() ; break ; + default : strerr_dief1x(101, "unexpected data in selfpipe") ; + } +} + +int main (int argc, char const **argv, char const *const *envp) +{ + iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ; + tain_t deadline, tto ; + ftrigr_t a = FTRIGR_ZERO ; + int argc1 ; + unsigned int i = 0 ; + char or = 0 ; + PROG = "s6-ftrig-listen" ; + { + unsigned int t = 0 ; + for (;;) + { + register int opt = subgetopt(argc, argv, "aot:") ; + if (opt == -1) break ; + switch (opt) + { + case 'a' : or = 0 ; break ; + case 'o' : or = 1 ; break ; + case 't' : if (uint0_scan(subgetopt_here.arg, &t)) break ; + default : dieusage() ; + } + } + if (t) tain_from_millisecs(&tto, t) ; else tto = tain_infinite_relative ; + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + } + if (argc < 2) dieusage() ; + argc1 = el_semicolon(argv) ; + if (!argc1 || (argc1 & 1) || (argc == argc1 + 1)) dieusage() ; + if (argc1 >= argc) strerr_dief1x(100, "unterminated fifodir+regex block") ; + tain_now_g() ; + tain_add_g(&deadline, &tto) ; + x[0].fd = selfpipe_init() ; + if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ; + if (selfpipe_trap(SIGCHLD) < 0) strerr_diefu1sys(111, "selfpipe_trap") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore") ; + + if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ; + x[1].fd = ftrigr_fd(&a) ; + + { + int pid = 0 ; + unsigned int idlen = argc1 >> 1 ; + uint16 ids[idlen] ; + for (; i < idlen ; i++) + { + ids[i] = ftrigr_subscribe_g(&a, argv[i<<1], argv[(i<<1)+1], 0, &deadline) ; + if (!ids[i]) strerr_diefu4sys(111, "subscribe to ", argv[i<<1], " with regexp ", argv[(i<<1)+1]) ; + } + + pid = fork() ; + switch (pid) + { + case -1 : strerr_diefu1sys(111, "fork") ; + case 0 : + { + PROG = "s6-ftrig-listen (child)" ; + pathexec_run(argv[argc1 + 1], argv + argc1 + 1, envp) ; + strerr_dieexec(111, argv[argc1 + 1]) ; + } + } + + for (;;) + { + register int r ; + i = 0 ; + while (i < idlen) + { + char dummy ; + r = ftrigr_check(&a, ids[i], &dummy) ; + if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ; + else if (!r) i++ ; + else if (or) idlen = 0 ; + else ids[i] = ids[--idlen] ; + } + if (!idlen) break ; + r = iopause_g(x, 2, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (!r) + { + errno = ETIMEDOUT ; + strerr_diefu1sys(1, "get expected event") ; + } + if (x[0].revents & IOPAUSE_READ) handle_signals() ; + if (x[1].revents & IOPAUSE_READ) + { + if (ftrigr_update(&a) < 0) strerr_diefu1sys(111, "ftrigr_update") ; + } + } + } + return 0 ; +} diff --git a/src/pipe-tools/s6-ftrig-listen1.c b/src/pipe-tools/s6-ftrig-listen1.c new file mode 100644 index 0000000..1354a64 --- /dev/null +++ b/src/pipe-tools/s6-ftrig-listen1.c @@ -0,0 +1,101 @@ +/* ISC license. */ + +#include <errno.h> +#include <signal.h> +#include <unistd.h> +#include <skalibs/sgetopt.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <s6/ftrigr.h> + +#define USAGE "s6-ftrig-listen1 [ -t timeout ] fifodir regexp prog..." + +static void handle_signals (void) +{ + for (;;) switch (selfpipe_read()) + { + case -1 : strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGCHLD : wait_reap() ; break ; + default : strerr_dief1x(101, "unexpected data in selfpipe") ; + } +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ; + tain_t deadline, tto ; + ftrigr_t a = FTRIGR_ZERO ; + int pid ; + uint16 id ; + PROG = "s6-ftrig-listen1" ; + { + unsigned int t = 0 ; + for (;;) + { + register int opt = subgetopt(argc, argv, "t:") ; + if (opt == -1) break ; + switch (opt) + { + case 't' : if (uint0_scan(subgetopt_here.arg, &t)) break ; + default : strerr_dieusage(100, USAGE) ; + } + } + if (t) tain_from_millisecs(&tto, t) ; + else tto = tain_infinite_relative ; + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + } + if (argc < 3) strerr_dieusage(100, USAGE) ; + + tain_now_g() ; + tain_add_g(&deadline, &tto) ; + + if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ; + id = ftrigr_subscribe_g(&a, argv[0], argv[1], 0, &deadline) ; + if (!id) strerr_diefu4sys(111, "subscribe to ", argv[0], " with regexp ", argv[1]) ; + + x[0].fd = selfpipe_init() ; + if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ; + if (selfpipe_trap(SIGCHLD) < 0) strerr_diefu1sys(111, "selfpipe_trap") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore") ; + x[1].fd = ftrigr_fd(&a) ; + + pid = fork() ; + switch (pid) + { + case -1 : strerr_diefu1sys(111, "fork") ; + case 0 : + { + PROG = "s6-ftrig-listen1 (child)" ; + pathexec_run(argv[2], argv+2, envp) ; + strerr_dieexec(111, argv[2]) ; + } + } + + for (;;) + { + char dummy ; + register int r = ftrigr_check(&a, id, &dummy) ; + if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ; + if (r) break ; + r = iopause_g(x, 2, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (!r) + { + errno = ETIMEDOUT ; + strerr_diefu1sys(1, "get expected event") ; + } + if (x[0].revents & IOPAUSE_READ) handle_signals() ; + if (x[1].revents & IOPAUSE_READ) + { + if (ftrigr_update(&a) < 0) strerr_diefu1sys(111, "ftrigr_update") ; + } + } + + return 0 ; +} diff --git a/src/pipe-tools/s6-ftrig-notify.c b/src/pipe-tools/s6-ftrig-notify.c new file mode 100644 index 0000000..1216a6a --- /dev/null +++ b/src/pipe-tools/s6-ftrig-notify.c @@ -0,0 +1,20 @@ +/* ISC license. */ + +#include <skalibs/strerr2.h> +#include <s6/ftrigw.h> + +#define USAGE "s6-ftrig-notify fifodir message" + +int main (int argc, char const *const *argv) +{ + char const *p ; + PROG = "s6-ftrig-notify" ; + if (argc < 3) strerr_dieusage(100, USAGE) ; + p = argv[2] ; + for (; *p ; p++) + { + if (ftrigw_notify(argv[1], *p) == -1) + strerr_diefu2sys(111, "notify ", argv[1]) ; + } + return 0 ; +} diff --git a/src/pipe-tools/s6-ftrig-wait.c b/src/pipe-tools/s6-ftrig-wait.c new file mode 100644 index 0000000..772ce86 --- /dev/null +++ b/src/pipe-tools/s6-ftrig-wait.c @@ -0,0 +1,48 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/sgetopt.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <s6/ftrigr.h> + +#define USAGE "s6-ftrig-wait [ -t timeout ] fifodir regexp" + +int main (int argc, char const *const *argv) +{ + tain_t deadline, tto ; + ftrigr_t a = FTRIGR_ZERO ; + uint16 id ; + char pack[2] = " \n" ; + PROG = "s6-ftrig-wait" ; + { + unsigned int t = 0 ; + for (;;) + { + register int opt = subgetopt(argc, argv, "t:") ; + if (opt == -1) break ; + switch (opt) + { + case 't' : if (uint0_scan(subgetopt_here.arg, &t)) break ; + default : strerr_dieusage(100, USAGE) ; + } + } + if (t) tain_from_millisecs(&tto, t) ; + else tto = tain_infinite_relative ; + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + } + if (argc < 2) strerr_dieusage(100, USAGE) ; + + tain_now_g() ; + tain_add_g(&deadline, &tto) ; + + if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ; + id = ftrigr_subscribe_g(&a, argv[0], argv[1], 0, &deadline) ; + if (!id) strerr_diefu4sys(111, "subscribe to ", argv[0], " with regexp ", argv[1]) ; + if (ftrigr_wait_or_g(&a, &id, 1, &deadline, &pack[0]) == -1) + strerr_diefu2sys((errno == ETIMEDOUT) ? 1 : 111, "match regexp on ", argv[1]) ; + if (allwrite(1, pack, 2) < 2) strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} diff --git a/src/pipe-tools/s6-mkfifodir.c b/src/pipe-tools/s6-mkfifodir.c new file mode 100644 index 0000000..4f5151a --- /dev/null +++ b/src/pipe-tools/s6-mkfifodir.c @@ -0,0 +1,39 @@ +/* ISC license. */ + +#include <skalibs/sgetopt.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <s6/ftrigw.h> + +#define USAGE "s6-mkfifodir [ -f ] [ -g gid ] fifodir" + +int main (int argc, char const *const *argv) +{ + subgetopt_t l = SUBGETOPT_ZERO ; + int gid = -1 ; + int force = 0 ; + PROG = "s6-mkfifodir" ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "fg:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'f' : force = 1 ; break ; + case 'g' : + { + unsigned int g ; + if (!uint0_scan(l.arg, &g)) strerr_dieusage(100, USAGE) ; + gid = (int)g ; + break ; + } + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + if (argc < 1) strerr_dieusage(100, USAGE) ; + + if (!ftrigw_fifodir_make(*argv, gid, force)) + strerr_diefu2sys(111, "create fifodir at ", *argv) ; + return 0 ; +} diff --git a/src/supervision/deps-exe/s6-supervise b/src/supervision/deps-exe/s6-supervise new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/supervision/deps-exe/s6-supervise @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/supervision/deps-exe/s6-svc b/src/supervision/deps-exe/s6-svc new file mode 100644 index 0000000..83cec1e --- /dev/null +++ b/src/supervision/deps-exe/s6-svc @@ -0,0 +1,2 @@ +-ls6 +-lskarnet diff --git a/src/supervision/deps-exe/s6-svok b/src/supervision/deps-exe/s6-svok new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/supervision/deps-exe/s6-svok @@ -0,0 +1 @@ +-lskarnet diff --git a/src/supervision/deps-exe/s6-svscan b/src/supervision/deps-exe/s6-svscan new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/supervision/deps-exe/s6-svscan @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/supervision/deps-exe/s6-svscanctl b/src/supervision/deps-exe/s6-svscanctl new file mode 100644 index 0000000..83cec1e --- /dev/null +++ b/src/supervision/deps-exe/s6-svscanctl @@ -0,0 +1,2 @@ +-ls6 +-lskarnet diff --git a/src/supervision/deps-exe/s6-svstat b/src/supervision/deps-exe/s6-svstat new file mode 100644 index 0000000..7065b26 --- /dev/null +++ b/src/supervision/deps-exe/s6-svstat @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${SYSCLOCK_LIB} diff --git a/src/supervision/deps-exe/s6-svwait b/src/supervision/deps-exe/s6-svwait new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/supervision/deps-exe/s6-svwait @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/supervision/s6-supervise.c b/src/supervision/s6-supervise.c new file mode 100644 index 0000000..f9a9872 --- /dev/null +++ b/src/supervision/s6-supervise.c @@ -0,0 +1,508 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/bytestr.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <skalibs/environ.h> +#include <skalibs/skamisc.h> +#include <s6/ftrigw.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-supervise dir" + +typedef enum trans_e trans_t, *trans_t_ref ; +enum trans_e +{ + V_TIMEOUT, V_CHLD, V_TERM, V_HUP, V_QUIT, + V_a, V_b, V_q, V_h, V_k, V_t, V_i, V_1, V_2, V_f, V_F, V_p, V_c, + V_o, V_d, V_u, V_x, V_O +} ; + +typedef enum state_e state_t, *state_t_ref ; +enum state_e +{ + DOWN, + UP, + FINISH, + LASTUP, + LASTFINISH +} ; + +typedef void action_t (void) ; +typedef action_t *action_t_ref ; + +static tain_t deadline ; +static s6_svstatus_t status = { .stamp = TAIN_ZERO, .pid = 0, .flagwant = 1, .flagwantup = 1, .flagpaused = 0, .flagfinishing = 0 } ; +static state_t state = DOWN ; +static int flagsetsid = 1 ; +static int cont = 1 ; + +static inline void settimeout (int secs) +{ + tain_addsec_g(&deadline, secs) ; +} + +static inline void settimeout_infinite (void) +{ + tain_add_g(&deadline, &tain_infinite_relative) ; +} + +static inline void announce (void) +{ + if (!s6_svstatus_write(".", &status)) + strerr_warnwu1sys("write status file") ; +} + + +/* The action array. */ + +static void nop (void) +{ +} + +static void bail (void) +{ + cont = 0 ; +} + +static void killa (void) +{ + kill(status.pid, SIGALRM) ; +} + +static void killb (void) +{ + kill(status.pid, SIGABRT) ; +} + +static void killh (void) +{ + kill(status.pid, SIGHUP) ; +} + +static void killq (void) +{ + kill(status.pid, SIGQUIT) ; +} + +static void killk (void) +{ + kill(status.pid, SIGKILL) ; +} + +static void killt (void) +{ + kill(status.pid, SIGTERM) ; +} + +static void killi (void) +{ + kill(status.pid, SIGINT) ; +} + +static void kill1 (void) +{ + kill(status.pid, SIGUSR1) ; +} + +static void kill2 (void) +{ + kill(status.pid, SIGUSR2) ; +} + +static void killp (void) +{ + kill(status.pid, SIGSTOP) ; + status.flagpaused = 1 ; + announce() ; +} + +static void killc (void) +{ + kill(status.pid, SIGCONT) ; + status.flagpaused = 0 ; + announce() ; +} + +static void trystart (void) +{ + int p[2] ; + pid_t pid ; + if (pipecoe(p) < 0) + { + settimeout(60) ; + strerr_warnwu1sys("pipecoe (waiting 60 seconds)") ; + return ; + } + pid = fork() ; + if (pid < 0) + { + settimeout(60) ; + strerr_warnwu1sys("fork (waiting 60 seconds)") ; + fd_close(p[1]) ; fd_close(p[0]) ; + return ; + } + else if (!pid) + { + char const *cargv[2] = { "run", 0 } ; + PROG = "s6-supervise (child)" ; + selfpipe_finish() ; + fd_close(p[0]) ; + if (flagsetsid) setsid() ; + execve("./run", (char *const *)cargv, (char *const *)environ) ; + fd_write(p[1], "", 1) ; + strerr_dieexec(111, "run") ; + } + fd_close(p[1]) ; + { + char c ; + switch (fd_read(p[0], &c, 1)) + { + case -1 : + fd_close(p[0]) ; + settimeout(60) ; + strerr_warnwu1sys("read pipe (waiting 60 seconds)") ; + kill(pid, SIGKILL) ; + return ; + case 1 : + { + fd_close(p[0]) ; + settimeout(10) ; + strerr_warnwu1x("spawn ./run - waiting 10 seconds") ; + return ; + } + } + } + fd_close(p[0]) ; + settimeout_infinite() ; + state = UP ; + status.pid = pid ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'u') ; +} + +static void downtimeout (void) +{ + if (status.flagwant && status.flagwantup) trystart() ; + else settimeout_infinite() ; +} + +static void down_O (void) +{ + status.flagwant = 0 ; + announce() ; +} + +static void down_o (void) +{ + down_O() ; + trystart() ; +} + +static void down_u (void) +{ + status.flagwant = 1 ; + status.flagwantup = 1 ; + announce() ; + trystart() ; +} + +static void down_d (void) +{ + status.flagwant = 1 ; + status.flagwantup = 0 ; + announce() ; +} + +static void tryfinish (int wstat, int islast) +{ + register pid_t pid = fork() ; + if (pid < 0) + { + strerr_warnwu2sys("fork for ", "./finish") ; + if (islast) bail() ; + state = DOWN ; + status.pid = 0 ; + settimeout(1) ; + return ; + } + else if (!pid) + { + char fmt0[UINT_FMT] ; + char fmt1[UINT_FMT] ; + char *cargv[4] = { "finish", fmt0, fmt1, 0 } ; + selfpipe_finish() ; + fmt0[uint_fmt(fmt0, WIFSIGNALED(wstat) ? 255 : WEXITSTATUS(wstat))] = 0 ; + fmt1[uint_fmt(fmt1, WIFSIGNALED(wstat))] = 0 ; + if (flagsetsid) setsid() ; + execve("./finish", cargv, (char *const *)environ) ; + _exit(111) ; + } + status.pid = pid ; + status.flagfinishing = 1 ; + state = islast ? LASTFINISH : FINISH ; + settimeout(5) ; +} + +static void uptimeout (void) +{ + settimeout_infinite() ; + strerr_warnw1x("can't happen: timeout while the service is up!") ; +} + +static void up_z (void) +{ + int wstat = status.pid ; + status.pid = 0 ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'd') ; + tryfinish(wstat, 0) ; +} + +static void up_o (void) +{ + status.flagwant = 0 ; + announce() ; +} + +static void up_d (void) +{ + status.flagwant = 1 ; + status.flagwantup = 0 ; + killt() ; + killc() ; +} + +static void up_u (void) +{ + status.flagwant = 1 ; + status.flagwantup = 1 ; + announce() ; +} + +static void closethem (void) +{ + fd_close(0) ; + fd_close(1) ; + open_read("/dev/null") ; + open_write("/dev/null") ; +} + +static void up_x (void) +{ + state = LASTUP ; + closethem() ; +} + +static void up_term (void) +{ + up_x() ; + up_d() ; +} + +static void finishtimeout (void) +{ + strerr_warnw1x("finish script takes too long - killing it") ; + killc() ; killk() ; + settimeout(3) ; +} + +static void finish_z (void) +{ + status.pid = 0 ; + status.flagfinishing = 0 ; + state = DOWN ; + announce() ; + settimeout(1) ; +} + +static void finish_u (void) +{ + status.flagwant = 1 ; + status.flagwantup = 1 ; + announce() ; +} + +static void finish_x (void) +{ + state = LASTFINISH ; + closethem() ; +} + +static void lastup_z (void) +{ + int wstat = status.pid ; + status.pid = 0 ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'd') ; + tryfinish(wstat, 1) ; +} + +static action_t_ref const actions[5][23] = +{ + { &downtimeout, &nop, &bail, &bail, &bail, + &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, + &down_o, &down_d, &down_u, &bail, &down_O }, + { &uptimeout, &up_z, &up_term, &up_x, &up_term, + &killa, &killb, &killq, &killh, &killk, &killt, &killi, &kill1, &kill2, &nop, &nop, &killp, &killc, + &up_o, &up_d, &up_u, &up_x, &up_o }, + { &finishtimeout, &finish_z, &finish_x, &finish_x, &finish_x, + &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, + &up_o, &down_d, &finish_u, &finish_x, &up_o }, + { &uptimeout, &lastup_z, &up_d, &nop, &up_d, + &killa, &killb, &killq, &killh, &killk, &killt, &killi, &kill1, &kill2, &nop, &nop, &killp, &killc, + &up_o, &up_d, &nop, &nop, &up_o }, + { &finishtimeout, &bail, &nop, &nop, &nop, + &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, + &nop, &nop, &nop, &nop, &nop } +} ; + + +/* The main loop. + It just loops around the iopause(), calling snippets of code in "actions" when needed. */ + + +static void handle_signals (void) +{ + for (;;) + { + char c = selfpipe_read() ; + switch (c) + { + case -1 : strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGCHLD : + if (!status.pid) wait_reap() ; + else + { + int wstat ; + int r = wait_pid_nohang(status.pid, &wstat) ; + if (r < 0) + if (errno != ECHILD) strerr_diefu1sys(111, "wait_pid_nohang") ; + else break ; + else if (!r) break ; + status.pid = wstat ; + (*actions[state][V_CHLD])() ; + } + break ; + case SIGTERM : + (*actions[state][V_TERM])() ; + break ; + case SIGHUP : + (*actions[state][V_HUP])() ; + break ; + case SIGQUIT : + (*actions[state][V_QUIT])() ; + break ; + default : + strerr_dief1x(101, "internal error: inconsistent signal state. Please submit a bug-report.") ; + } + } +} + +static void handle_control (int fd) +{ + for (;;) + { + char c ; + register int r = sanitize_read(fd_read(fd, &c, 1)) ; + if (r < 0) strerr_diefu1sys(111, "read " S6_SUPERVISE_CTLDIR "/control") ; + else if (!r) break ; + else + { + register unsigned int pos = byte_chr("abqhkti12fFpcoduxO", 18, c) ; + if (pos < 18) (*actions[state][V_a + pos])() ; + } + } +} + +int main (int argc, char const *const *argv) +{ + iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ; + PROG = "s6-supervise" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if (chdir(argv[1]) < 0) strerr_diefu2sys(111, "chdir to ", argv[1]) ; + { + register unsigned int proglen = str_len(PROG) ; + register unsigned int namelen = str_len(argv[1]) ; + char progname[proglen + namelen + 2] ; + byte_copy(progname, proglen, PROG) ; + progname[proglen] = ' ' ; + byte_copy(progname + proglen + 1, namelen + 1, argv[1]) ; + PROG = progname ; + if (!fd_sanitize()) strerr_diefu1sys(111, "sanitize stdin and stdout") ; + x[1].fd = s6_supervise_lock(S6_SUPERVISE_CTLDIR) ; + if (!ftrigw_fifodir_make(S6_SUPERVISE_EVENTDIR, -1, 0)) + strerr_diefu2sys(111, "mkfifodir ", S6_SUPERVISE_EVENTDIR) ; + x[0].fd = selfpipe_init() ; + if (x[0].fd == -1) strerr_diefu1sys(111, "init selfpipe") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGTERM) ; + sigaddset(&set, SIGHUP) ; + sigaddset(&set, SIGQUIT) ; + sigaddset(&set, SIGCHLD) ; + if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ; + } + + if (!ftrigw_clean(S6_SUPERVISE_EVENTDIR)) + strerr_warnwu2sys("ftrigw_clean ", S6_SUPERVISE_EVENTDIR) ; + + { + struct stat st ; + if (stat("down", &st) == -1) + { + if (errno != ENOENT) + strerr_diefu1sys(111, "stat down") ; + } + else status.flagwantup = 0 ; + if (stat("nosetsid", &st) == -1) + { + if (errno != ENOENT) + strerr_diefu1sys(111, "stat nosetsid") ; + } + else flagsetsid = 0 ; + } + + tain_now_g() ; + settimeout(0) ; + tain_copynow(&status.stamp) ; + announce() ; + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 's') ; + + while (cont) + { + register int r = iopause_g(x, 2, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (!r) (*actions[state][V_TIMEOUT])() ; + else + { + if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) + strerr_diefu1x(111, "iopause: trouble with pipes") ; + if (x[0].revents & IOPAUSE_READ) handle_signals() ; + else if (x[1].revents & IOPAUSE_READ) handle_control(x[1].fd) ; + } + } + + ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'x') ; + } + return 0 ; +} diff --git a/src/supervision/s6-svc.c b/src/supervision/s6-svc.c new file mode 100644 index 0000000..699eefd --- /dev/null +++ b/src/supervision/s6-svc.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include <skalibs/strerr2.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-svc [ -abqhkti12fFpcoduxO ] servicedir" + +int main (int argc, char const *const *argv) +{ + PROG = "s6-svc" ; + return s6_svc_main(argc, argv, "abqhkti12fFpcoduxO", USAGE, "supervise") ; +} diff --git a/src/supervision/s6-svok.c b/src/supervision/s6-svok.c new file mode 100644 index 0000000..4a615e9 --- /dev/null +++ b/src/supervision/s6-svok.c @@ -0,0 +1,32 @@ +/* ISC license. */ + +#include <errno.h> +#include <skalibs/bytestr.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-svok servicedir" + +int main (int argc, char const *const *argv) +{ + PROG = "s6-svok" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + argv++ ; argc-- ; + { + int fd ; + unsigned int dirlen = str_len(*argv) ; + char fn[dirlen + 9 + sizeof(S6_SUPERVISE_CTLDIR)] ; + byte_copy(fn, dirlen, *argv) ; + fn[dirlen] = '/' ; + byte_copy(fn + dirlen + 1, sizeof(S6_SUPERVISE_CTLDIR) - 1, S6_SUPERVISE_CTLDIR) ; + byte_copy(fn + dirlen + sizeof(S6_SUPERVISE_CTLDIR), 9, "/control") ; + fd = open_write(fn) ; + if (fd < 0) + { + if ((errno == ENXIO) || (errno == ENOENT)) return 1 ; + else strerr_diefu2sys(111, "open_write ", fn) ; + } + } + return 0 ; +} diff --git a/src/supervision/s6-svscan.c b/src/supervision/s6-svscan.c new file mode 100644 index 0000000..8b0f82e --- /dev/null +++ b/src/supervision/s6-svscan.c @@ -0,0 +1,498 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/bytestr.h> +#include <skalibs/sgetopt.h> +#include <skalibs/uint.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/iopause.h> +#include <skalibs/djbunix.h> +#include <skalibs/direntry.h> +#include <skalibs/sig.h> +#include <skalibs/selfpipe.h> +#include <skalibs/environ.h> +#include <s6/config.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-svscan [ -c maxservices ] [ -t timeout ] [ dir ]" + +#define FINISH_PROG S6_SVSCAN_CTLDIR "/finish" +#define CRASH_PROG S6_SVSCAN_CTLDIR "/crash" + +#define DIR_RETRY_TIMEOUT 3 +#define CHECK_RETRY_TIMEOUT 4 + +struct svinfo +{ + dev_t dev ; + ino_t ino ; + tain_t restartafter[2] ; + int pid[2] ; + int p[2] ; + unsigned int flagactive : 1 ; + unsigned int flaglog : 1 ; +} ; +#define SVINFO_ZERO { -1, -1, { TAIN_ZERO, TAIN_ZERO }, { 0, 0 }, { -1, -1 }, 0, 0, 0 } ; + +static struct svinfo *services ; +static unsigned int max = 500 ; +static unsigned int n = 0 ; +static tain_t deadline, defaulttimeout ; +static char const *finish_arg = "reboot" ; +static int wantreap = 1 ; +static int wantscan = 1 ; +static unsigned int wantkill = 0 ; +static int cont = 1 ; + +static void panicnosp (char const *) gccattr_noreturn ; +static void panicnosp (char const *errmsg) +{ + char const *eargv[2] = { CRASH_PROG, 0 } ; + strerr_warnwu1sys(errmsg) ; + strerr_warnw2x("executing into ", eargv[0]) ; + execve(eargv[0], (char *const *)eargv, (char *const *)environ) ; + /* and if that execve fails, screw it and just die */ + strerr_dieexec(111, eargv[0]) ; +} + +static void panic (char const *) gccattr_noreturn ; +static void panic (char const *errmsg) +{ + int e = errno ; + selfpipe_finish() ; + errno = e ; + panicnosp(errmsg) ; +} + +static void killthem (void) +{ + register unsigned int i = 0 ; + if (!wantkill) return ; + for (; i < n ; i++) + { + if (!(wantkill & 1) && services[i].flagactive) continue ; + if (services[i].pid[0]) + kill(services[i].pid[0], (wantkill & 2) ? SIGTERM : SIGHUP) ; + if (services[i].flaglog && services[i].pid[1]) + kill(services[i].pid[1], (wantkill & 4) ? SIGTERM : SIGHUP) ; + } + wantkill = 0 ; +} + +static void term (void) +{ + cont = 0 ; + wantkill = 3 ; +} + +static void hup (void) +{ + cont = 0 ; + wantkill = 1 ; +} + +static void quit (void) +{ + cont = 0 ; + wantkill = 7 ; +} + +static void intr (void) +{ + finish_arg = "reboot" ; + term() ; +} + +static void handle_signals (void) +{ + for (;;) + { + switch (selfpipe_read()) + { + case -1 : panic("selfpipe_read") ; + case 0 : return ; + case SIGCHLD : wantreap = 1 ; break ; + case SIGALRM : wantscan = 1 ; break ; + case SIGTERM : term() ; break ; + case SIGHUP : hup() ; break ; + case SIGQUIT : quit() ; break ; + case SIGABRT : cont = 0 ; break ; + case SIGINT : intr() ; break ; + } + } +} + +static void handle_control (int fd) +{ + for (;;) + { + char c ; + int r = sanitize_read(fd_read(fd, &c, 1)) ; + if (r == -1) panic("read control pipe") ; + else if (!r) break ; + else switch (c) + { + case 'p' : finish_arg = "poweroff" ; break ; + case 'h' : hup() ; return ; + case 'r' : finish_arg = "reboot" ; break ; + case 'a' : wantscan = 1 ; break ; + case 't' : term() ; return ; + case 's' : finish_arg = "halt" ; break ; + case 'z' : wantreap = 1 ; break ; + case 'b' : cont = 0 ; return ; + case 'n' : wantkill = 2 ; break ; + case 'N' : wantkill = 6 ; break ; + case '6' : + case 'i' : intr() ; return ; + case 'q' : quit() ; return ; + case '0' : finish_arg = "halt" ; term() ; return ; + case '7' : finish_arg = "poweroff" ; term() ; return ; + case '8' : finish_arg = "other" ; term() ; return ; + default : + { + char s[2] = { c, 0 } ; + strerr_warnw2x("received unknown control command: ", s) ; + } + } + } +} + + +/* First essential function: the reaper. + s6-svscan must wait() for all children, + including ones it doesn't know it has. + Dead active services are flagged to be restarted in 1 second. */ + +static void reap (void) +{ + tain_t nextscan ; + if (!wantreap) return ; + wantreap = 0 ; + tain_addsec_g(&nextscan, 1) ; + for (;;) + { + int wstat ; + int r = wait_nohang(&wstat) ; + if (r < 0) + if (errno != ECHILD) panic("wait_nohang") ; + else break ; + else if (!r) break ; + else + { + register unsigned int i = 0 ; + for (; i < n ; i++) + { + if (services[i].pid[0] == r) + { + services[i].pid[0] = 0 ; + services[i].restartafter[0] = nextscan ; + break ; + } + else if (services[i].pid[1] == r) + { + services[i].pid[1] = 0 ; + services[i].restartafter[1] = nextscan ; + break ; + } + } + if (i == n) continue ; + if (services[i].flagactive) + { + if (tain_less(&nextscan, &deadline)) deadline = nextscan ; + } + else + { + if (services[i].flaglog) + { + /* + BLACK MAGIC: + - we need to close the pipe early: + * as soon as the writer exits so the logger can exit on EOF + * or as soon as the logger exits so the writer can crash on EPIPE + - but if the same service gets reactivated before the second + supervise process exits, ouch: we've lost the pipe + - so we can't reuse the same service even if it gets reactivated + - so we're marking a dying service with a closed pipe + - if the scanner sees a service with p[0] = -1 it won't flag + it as active (and won't restart the dead supervise) + - but if the service gets reactivated we want it to restart + as soon as the 2nd supervise process dies + - so the scanner marks such a process with p[0] = -2 + - and the reaper triggers a scan when it finds a -2. + */ + if (services[i].p[0] >= 0) + { + fd_close(services[i].p[1]) ; services[i].p[1] = -1 ; + fd_close(services[i].p[0]) ; services[i].p[0] = -1 ; + } + else if (services[i].p[0] == -2) wantscan = 1 ; + } + if (!services[i].pid[0] && !services[i].pid[1]) + services[i] = services[--n] ; + } + } + } +} + + +/* Second essential function: the scanner. + It monitors the service directories and spawns a supervisor + if needed. */ + +static void trystart (unsigned int i, char const *name, int islog) +{ + int pid = fork() ; + switch (pid) + { + case -1 : + tain_addsec_g(&services[i].restartafter[islog], CHECK_RETRY_TIMEOUT) ; + strerr_warnwu2sys("fork for ", name) ; + return ; + case 0 : + { + char const *cargv[3] = { "s6-supervise", name, 0 } ; + PROG = "s6-svscan (child)" ; + selfpipe_finish() ; + if (services[i].flaglog) + if (fd_move(!islog, services[i].p[!islog]) == -1) + strerr_diefu2sys(111, "set fds for ", name) ; + pathexec_run(S6_BINPREFIX "s6-supervise", cargv, (char const **)environ) ; + strerr_dieexec(111, S6_BINPREFIX "s6-supervise") ; + } + } + services[i].pid[islog] = pid ; +} + +static void retrydirlater (void) +{ + tain_t a ; + tain_addsec_g(&a, DIR_RETRY_TIMEOUT) ; + if (tain_less(&a, &deadline)) deadline = a ; +} + +static void check (char const *name) +{ + struct stat st ; + unsigned int namelen ; + unsigned int i = 0 ; + if (name[0] == '.') return ; + if (stat(name, &st) == -1) + { + strerr_warnwu2sys("stat ", name) ; + retrydirlater() ; + return ; + } + if (!S_ISDIR(st.st_mode)) return ; + namelen = str_len(name) ; + for (; i < n ; i++) if ((services[i].ino == st.st_ino) && (services[i].dev == st.st_dev)) break ; + if (i < n) + { + if (services[i].flaglog && (services[i].p[0] < 0)) + { + /* See BLACK MAGIC above. */ + services[i].p[0] = -2 ; + return ; + } + } + else + { + if (n >= max) + { + strerr_warnwu3x("start supervisor for ", name, ": too many services") ; + return ; + } + else + { + struct stat su ; + char tmp[namelen + 5] ; + byte_copy(tmp, namelen, name) ; + byte_copy(tmp + namelen, 5, "/log") ; + if (stat(tmp, &su) < 0) + if (errno == ENOENT) services[i].flaglog = 0 ; + else + { + strerr_warnwu2sys("stat ", tmp) ; + retrydirlater() ; + return ; + } + else if (!S_ISDIR(su.st_mode)) + services[i].flaglog = 0 ; + else + { + if (pipecoe(services[i].p) < 0) + { + strerr_warnwu1sys("pipecoe") ; + retrydirlater() ; + return ; + } + services[i].flaglog = 1 ; + } + services[i].ino = st.st_ino ; + services[i].dev = st.st_dev ; + tain_copynow(&services[i].restartafter[0]) ; + tain_copynow(&services[i].restartafter[1]) ; + services[i].pid[0] = 0 ; + services[i].pid[1] = 0 ; + n++ ; + } + } + + services[i].flagactive = 1 ; + + if (services[i].flaglog && !services[i].pid[1]) + { + if (!tain_future(&services[i].restartafter[1])) + { + char tmp[namelen + 5] ; + byte_copy(tmp, namelen, name) ; + byte_copy(tmp + namelen, 5, "/log") ; + trystart(i, tmp, 1) ; + } + else if (tain_less(&services[i].restartafter[1], &deadline)) + deadline = services[i].restartafter[1] ; + } + + if (!services[i].pid[0]) + { + if (!tain_future(&services[i].restartafter[0])) + trystart(i, name, 0) ; + else if (tain_less(&services[i].restartafter[0], &deadline)) + deadline = services[i].restartafter[0] ; + } +} + +static void scan (void) +{ + DIR *dir ; + if (!wantscan) return ; + wantscan = 0 ; + dir = opendir(".") ; + if (!dir) + { + strerr_warnwu1sys("opendir .") ; + retrydirlater() ; + return ; + } + { + register unsigned int i = 0 ; + for (; i < n ; i++) services[i].flagactive = 0 ; + } + for (;;) + { + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + check(d->d_name) ; + } + if (errno) + { + strerr_warnwu1sys("readdir .") ; + retrydirlater() ; + } + dir_close(dir) ; +} + + +int main (int argc, char const *const *argv) +{ + iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ; + PROG = "s6-svscan" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + unsigned int t = 5000 ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "t:c:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 't' : if (uint0_scan(l.arg, &t)) break ; + case 'c' : if (uint0_scan(l.arg, &max)) break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + if (t) tain_from_millisecs(&defaulttimeout, t) ; + else defaulttimeout = tain_infinite_relative ; + if (max < 2) max = 2 ; + } + + /* Init phase. + If something fails here, we can die, because it means that + something is seriously wrong with the system, and we can't + run correctly anyway. + */ + + if (argc && (chdir(argv[0]) < 0)) strerr_diefu1sys(111, "chdir") ; + x[1].fd = s6_supervise_lock(S6_SVSCAN_CTLDIR) ; + x[0].fd = selfpipe_init() ; + if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ; + + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGCHLD) ; + sigaddset(&set, SIGALRM) ; + sigaddset(&set, SIGTERM) ; + sigaddset(&set, SIGHUP) ; + sigaddset(&set, SIGQUIT) ; + sigaddset(&set, SIGABRT) ; + sigaddset(&set, SIGINT) ; + if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ; + } + + + { + struct svinfo blob[max] ; /* careful with that stack, Eugene */ + services = blob ; + tain_now_g() ; + + + /* Loop phase. + From now on, we must not die. + Temporize on recoverable errors, and panic on serious ones. */ + + while (cont) + { + int r ; + tain_add_g(&deadline, &defaulttimeout) ; + reap() ; + scan() ; + killthem() ; + r = iopause_g(x, 2, &deadline) ; + if (r < 0) panic("iopause") ; + else if (!r) wantscan = 1 ; + else + { + if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) + { + errno = EIO ; + panic("check internal pipes") ; + } + if (x[0].revents & IOPAUSE_READ) handle_signals() ; + if (x[1].revents & IOPAUSE_READ) handle_control(x[1].fd) ; + } + } + + + /* Finish phase. */ + + selfpipe_finish() ; + killthem() ; + reap() ; + } + { + char const *eargv[3] = { FINISH_PROG, finish_arg, 0 } ; + execve(eargv[0], (char **)eargv, (char *const *)environ) ; + } + panicnosp("exec finish script " FINISH_PROG) ; +} diff --git a/src/supervision/s6-svscanctl.c b/src/supervision/s6-svscanctl.c new file mode 100644 index 0000000..48e6420 --- /dev/null +++ b/src/supervision/s6-svscanctl.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include <skalibs/strerr2.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-svscanctl [ -phratszbnNiq0678 ] svscandir" + +int main (int argc, char const *const *argv) +{ + PROG = "s6-svscanctl" ; + return s6_svc_main(argc, argv, "phratszbnNiq0678", USAGE, ".s6-svscan") ; +} diff --git a/src/supervision/s6-svstat.c b/src/supervision/s6-svstat.c new file mode 100644 index 0000000..c986b8d --- /dev/null +++ b/src/supervision/s6-svstat.c @@ -0,0 +1,70 @@ +/* ISC license. */ + +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <skalibs/uint64.h> +#include <skalibs/uint.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr2.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-svstat servicedir" + +int main (int argc, char const *const *argv) +{ + s6_svstatus_t status ; + char fmt[UINT_FMT] ; + int isup, normallyup ; + PROG = "s6-svstat" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + argv++ ; argc-- ; + if (!s6_svstatus_read(*argv, &status)) + strerr_diefu2sys(111, "read status for ", *argv) ; + + { + struct stat st ; + unsigned int dirlen = str_len(*argv) ; + char fn[dirlen + 6] ; + byte_copy(fn, dirlen, *argv) ; + byte_copy(fn + dirlen, 6, "/down") ; + if (stat(fn, &st) == -1) + if (errno != ENOENT) strerr_diefu2sys(111, "stat ", fn) ; + else normallyup = 1 ; + else normallyup = 0 ; + } + + tain_now_g() ; + if (tain_future(&status.stamp)) tain_copynow(&status.stamp) ; + tain_sub(&status.stamp, &STAMP, &status.stamp) ; + + isup = status.pid && !status.flagfinishing ; + if (isup) + { + buffer_putnoflush(buffer_1small,"up (pid ", 8) ; + buffer_putnoflush(buffer_1small, fmt, uint_fmt(fmt, status.pid)) ; + buffer_putnoflush(buffer_1small, ") ", 2) ; + } + else buffer_putnoflush(buffer_1small, "down ", 5) ; + + buffer_putnoflush(buffer_1small, fmt, uint64_fmt(fmt, status.stamp.sec.x)) ; + buffer_putnoflush(buffer_1small," seconds", 8) ; + + if (isup && !normallyup) + buffer_putnoflush(buffer_1small, ", normally down", 15) ; + if (!isup && normallyup) + buffer_putnoflush(buffer_1small, ", normally up", 13) ; + if (isup && status.flagpaused) + buffer_putnoflush(buffer_1small, ", paused", 8) ; + if (!isup && (status.flagwant == 'u')) + buffer_putnoflush(buffer_1small, ", want up", 10) ; + if (isup && (status.flagwant == 'd')) + buffer_putnoflush(buffer_1small, ", want down", 12) ; + + if (buffer_putflush(buffer_1small, "\n", 1) < 0) + strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} diff --git a/src/supervision/s6-svwait.c b/src/supervision/s6-svwait.c new file mode 100644 index 0000000..0d7c96c --- /dev/null +++ b/src/supervision/s6-svwait.c @@ -0,0 +1,104 @@ +/* ISC license. */ + +#include <skalibs/sgetopt.h> +#include <skalibs/bytestr.h> +#include <skalibs/uint16.h> +#include <skalibs/uint.h> +#include <skalibs/bitarray.h> +#include <skalibs/tai.h> +#include <skalibs/strerr2.h> +#include <skalibs/iopause.h> +#include <s6/ftrigr.h> +#include <s6/s6-supervise.h> + +#define USAGE "s6-svwait [ -U | -u | -d ] [ -A | -a | -o ] [ -t timeout ] servicedir..." +#define dieusage() strerr_dieusage(100, USAGE) + +static inline int check (unsigned char const *ba, unsigned int n, int wantup, int or) +{ + return (bitarray_first(ba, n, or == wantup) < n) == or ; +} + +int main (int argc, char const *const *argv) +{ + tain_t deadline, tto ; + ftrigr_t a = FTRIGR_ZERO ; + uint32 options = FTRIGR_REPEAT ; + int or = 0 ; + int wantup = 1 ; + char re[4] = "u|d" ; + PROG = "s6-svwait" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + unsigned int t = 0 ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "uUdAaot:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'U' : wantup = 1 ; re[0] = 'U' ; break ; + case 'u' : wantup = 1 ; re[0] = 'u' ; break ; + case 'd' : wantup = 0 ; break ; + case 'A' : or = 0 ; options |= FTRIGR_REPEAT ; break ; + case 'a' : or = 0 ; options &= ~FTRIGR_REPEAT ; break ; + case 'o' : or = 1 ; options &= ~FTRIGR_REPEAT ; break ; + case 't' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + if (t) tain_from_millisecs(&tto, t) ; else tto = tain_infinite_relative ; + } + if (!argc) dieusage() ; + + tain_now_g() ; + tain_add_g(&deadline, &tto) ; + + if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ; + + { + iopause_fd x = { -1, IOPAUSE_READ, 0 } ; + unsigned int i = 0 ; + uint16 list[argc] ; + unsigned char states[bitarray_div8(argc)] ; + x.fd = ftrigr_fd(&a) ; + for (; i < (unsigned int)argc ; i++) + { + unsigned int len = str_len(argv[i]) ; + char s[len + 1 + sizeof(S6_SUPERVISE_EVENTDIR)] ; + byte_copy(s, len, argv[i]) ; + s[len] = '/' ; + byte_copy(s + len + 1, sizeof(S6_SUPERVISE_EVENTDIR), S6_SUPERVISE_EVENTDIR) ; + list[i] = ftrigr_subscribe_g(&a, s, re, options, &deadline) ; + if (!list[i]) strerr_diefu2sys(111, "ftrigr_subscribe to ", argv[i]) ; + } + + for (i = 0 ; i < (unsigned int)argc ; i++) + { + s6_svstatus_t st = S6_SVSTATUS_ZERO ; + if (!s6_svstatus_read(argv[i], &st)) strerr_diefu1sys(111, "s6_svstatus_read") ; + bitarray_poke(states, i, !!st.pid) ; + } + + for (;;) + { + if (check(states, argc, wantup, or)) break ; + { + register int r = iopause_g(&x, 1, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (!r) strerr_dief1x(1, "timed out") ; + } + + if (ftrigr_update(&a) < 0) strerr_diefu1sys(111, "ftrigr_update") ; + for (i = 0 ; i < (unsigned int)argc ; i++) + { + char what ; + register int r = ftrigr_check(&a, list[i], &what) ; + if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ; + if (r) bitarray_poke(states, i, what == re[0]) ; + } + } + } + return 0 ; +} diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh new file mode 100755 index 0000000..af31259 --- /dev/null +++ b/tools/gen-deps.sh @@ -0,0 +1,79 @@ +#!/bin/sh -e + +. package/info + +echo '#' +echo '# This file has been generated by tools/gen-deps.sh' +echo '#' +echo + +for dir in src/include/${package} src/* ; do + for file in $(ls -1 $dir | grep -- \\.h$) ; do + { + grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ; + grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2 + } | sort -u | { + deps= + while read dep ; do + if echo $dep | grep -q "^${package}/" ; then + deps="$deps src/include/$dep" + elif test -f "${dir}/$dep" ; then + deps="$deps ${dir}/$dep" + else + deps="$deps src/include-local/$dep" + fi + done + if test -n "$deps" ; then + echo "${dir}/${file}:${deps}" + fi + } + done +done + +for dir in src/* ; do + for file in $(ls -1 $dir | grep -- \\.c$) ; do + { + grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ; + grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2 + } | sort -u | { + deps=" ${dir}/$file" + while read dep ; do + if echo $dep | grep -q "^${package}/" ; then + deps="$deps src/include/$dep" + elif test -f "${dir}/$dep" ; then + deps="$deps ${dir}/$dep" + else + deps="$deps src/include-local/$dep" + fi + done + o=$(echo $file | sed s/\\.c$/.o/) + lo=$(echo $file | sed s/\\.c$/.lo/) + echo "${dir}/${o} ${dir}/${lo}:${deps}" + } + done +done +echo + +for dir in $(ls -1 src | grep -v ^include) ; do + for file in $(ls -1 src/$dir/deps-lib) ; do + deps= + while read dep ; do + deps="$deps src/$dir/$dep" + done < src/$dir/deps-lib/$file + echo "lib$file.a: $deps" + if test -x "src/$dir/deps-lib/$file" ; then + echo "lib${file}.so: $(echo "$deps" | sed 's/\.o/.lo/g')" + fi + done + + for file in $(ls -1 src/$dir/deps-exe) ; do + deps= + while read dep ; do + if echo $dep | grep -q -- \\.o$ ; then + dep="src/$dir/$dep" + fi + deps="$deps $dep" + done < src/$dir/deps-exe/$file + echo "$file: src/$dir/$file.o$deps" + done +done diff --git a/tools/install.sh b/tools/install.sh new file mode 100755 index 0000000..89f9428 --- /dev/null +++ b/tools/install.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +usage() { + echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2 + exit 1 +} + +mkdirp=false +symlink=false +mode=0755 + +while getopts Dlm: name ; do + case "$name" in + D) mkdirp=true ;; + l) symlink=true ;; + m) mode=$OPTARG ;; + ?) usage ;; + esac +done +shift $(($OPTIND - 1)) + +test "$#" -eq 2 || usage +src=$1 +dst=$2 +tmp="$dst.tmp.$$" + +case "$dst" in + */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;; +esac + +set -C +set -e + +if $mkdirp ; then + umask 022 + case "$2" in + */*) mkdir -p "${dst%/*}" ;; + esac +fi + +trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP + +umask 077 + +if $symlink ; then + ln -s "$src" "$tmp" +else + cat < "$1" > "$tmp" + chmod "$mode" "$tmp" +fi + +mv -f "$tmp" "$dst" +if test -d "$dst" ; then + rm -f "$dst/$(basename $tmp)" + if $symlink ; then + mkdir "$tmp" + ln -s "$src" "$tmp/$(basename $dst)" + mv -f "$tmp/$(basename $dst)" "${dst%/*}" + rmdir "$tmp" + else + echo "$0: $dst is a directory" 1>&2 + exit 1 + fi +fi |