From 90b12bd71bb9fc79a4640b9112c13ef529d0196a Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Fri, 5 Dec 2014 22:26:11 +0000 Subject: Initial commit --- .gitignore | 5 + AUTHORS | 17 + COPYING | 13 + INSTALL | 138 +++ Makefile | 121 +++ README | 23 + README.macosx | 4 + README.solaris | 12 + configure | 385 +++++++ doc/fifodir.html | 123 +++ doc/index.html | 291 ++++++ doc/libftrig.html | 184 ++++ doc/libftrigr.html | 283 +++++ doc/libftrigw.html | 115 +++ doc/libs6lock/index.html | 256 +++++ doc/libs6lock/s6lockd-helper.html | 52 + doc/libs6lock/s6lockd.html | 73 ++ doc/notifywhenup.html | 75 ++ doc/s6-cleanfifodir.html | 42 + doc/s6-envdir.html | 66 ++ doc/s6-envuidgid.html | 64 ++ doc/s6-fghack.html | 50 + doc/s6-ftrig-listen.html | 73 ++ doc/s6-ftrig-listen1.html | 88 ++ doc/s6-ftrig-notify.html | 44 + doc/s6-ftrig-wait.html | 51 + doc/s6-ftrigrd.html | 73 ++ doc/s6-log.html | 508 +++++++++ doc/s6-mkfifodir.html | 46 + doc/s6-notifywhenup.html | 80 ++ doc/s6-setlock.html | 65 ++ doc/s6-setsid.html | 47 + doc/s6-setuidgid.html | 49 + doc/s6-softlimit.html | 54 + doc/s6-supervise.html | 125 +++ doc/s6-svc.html | 104 ++ doc/s6-svok.html | 38 + doc/s6-svscan-1.html | 374 +++++++ doc/s6-svscan-not-1.html | 140 +++ doc/s6-svscan.html | 176 ++++ doc/s6-svscanctl.html | 108 ++ doc/s6-svstat.html | 45 + doc/s6-svwait.html | 73 ++ doc/s6-tai64n.html | 50 + doc/s6-tai64nlocal.html | 73 ++ doc/scandir.html | 145 +++ doc/servicedir.html | 187 ++++ doc/systemd.html | 120 +++ doc/ucspilogd.html | 94 ++ doc/upgrade.html | 32 + doc/why.html | 203 ++++ package/deps-build | 2 + package/deps.mak | 101 ++ package/info | 4 + package/modes | 28 + package/targets.mak | 36 + patch-for-solaris | 17 + src/daemontools-extras/deps-exe/s6-envdir | 1 + src/daemontools-extras/deps-exe/s6-envuidgid | 1 + src/daemontools-extras/deps-exe/s6-fghack | 1 + src/daemontools-extras/deps-exe/s6-log | 2 + src/daemontools-extras/deps-exe/s6-notifywhenup | 3 + src/daemontools-extras/deps-exe/s6-setlock | 2 + src/daemontools-extras/deps-exe/s6-setsid | 1 + src/daemontools-extras/deps-exe/s6-setuidgid | 1 + src/daemontools-extras/deps-exe/s6-softlimit | 1 + src/daemontools-extras/deps-exe/s6-tai64n | 2 + src/daemontools-extras/deps-exe/s6-tai64nlocal | 1 + src/daemontools-extras/deps-exe/ucspilogd | 1 + src/daemontools-extras/s6-envdir.c | 40 + src/daemontools-extras/s6-envuidgid.c | 44 + src/daemontools-extras/s6-fghack.c | 68 ++ src/daemontools-extras/s6-log.c | 1265 +++++++++++++++++++++++ src/daemontools-extras/s6-notifywhenup.c | 86 ++ src/daemontools-extras/s6-setlock.c | 87 ++ src/daemontools-extras/s6-setsid.c | 35 + src/daemontools-extras/s6-setuidgid.c | 30 + src/daemontools-extras/s6-softlimit.c | 117 +++ src/daemontools-extras/s6-tai64n.c | 35 + src/daemontools-extras/s6-tai64nlocal.c | 47 + src/daemontools-extras/ucspilogd.c | 117 +++ src/include/s6/ftrigr.h | 94 ++ src/include/s6/ftrigw.h | 11 + src/include/s6/s6-supervise.h | 40 + src/include/s6/s6.h | 11 + src/include/s6/s6lock.h | 75 ++ src/libs6/deps-exe/s6-ftrigrd | 5 + src/libs6/deps-exe/s6lockd | 3 + src/libs6/deps-exe/s6lockd-helper | 1 + src/libs6/deps-lib/s6 | 33 + src/libs6/ftrig1.h | 23 + src/libs6/ftrig1_free.c | 25 + src/libs6/ftrig1_make.c | 65 ++ src/libs6/ftrigr1_zero.c | 5 + src/libs6/ftrigr_check.c | 40 + src/libs6/ftrigr_end.c | 14 + src/libs6/ftrigr_start.c | 10 + src/libs6/ftrigr_startf.c | 12 + src/libs6/ftrigr_subscribe.c | 43 + src/libs6/ftrigr_unsubscribe.c | 36 + src/libs6/ftrigr_update.c | 43 + src/libs6/ftrigr_wait_and.c | 28 + src/libs6/ftrigr_wait_or.c | 31 + src/libs6/ftrigr_zero.c | 5 + src/libs6/ftrigw_clean.c | 39 + src/libs6/ftrigw_fifodir_make.c | 26 + src/libs6/ftrigw_notify.c | 8 + src/libs6/ftrigw_notifyb.c | 67 ++ src/libs6/s6-ftrigrd.c | 266 +++++ src/libs6/s6_supervise_lock.c | 10 + src/libs6/s6_supervise_lock_mode.c | 54 + src/libs6/s6_svc_main.c | 40 + src/libs6/s6_svc_write.c | 22 + src/libs6/s6_svstatus_pack.c | 13 + src/libs6/s6_svstatus_read.c | 17 + src/libs6/s6_svstatus_unpack.c | 29 + src/libs6/s6_svstatus_write.c | 16 + src/libs6/s6lock_acquire.c | 38 + src/libs6/s6lock_check.c | 25 + src/libs6/s6lock_end.c | 14 + src/libs6/s6lock_release.c | 28 + src/libs6/s6lock_start.c | 12 + src/libs6/s6lock_startf.c | 14 + src/libs6/s6lock_update.c | 31 + src/libs6/s6lock_wait_and.c | 27 + src/libs6/s6lock_wait_or.c | 30 + src/libs6/s6lock_zero.c | 5 + src/libs6/s6lockd-helper.c | 27 + src/libs6/s6lockd.c | 316 ++++++ src/pipe-tools/deps-exe/s6-cleanfifodir | 2 + src/pipe-tools/deps-exe/s6-ftrig-listen | 4 + src/pipe-tools/deps-exe/s6-ftrig-listen1 | 3 + src/pipe-tools/deps-exe/s6-ftrig-notify | 2 + src/pipe-tools/deps-exe/s6-ftrig-wait | 3 + src/pipe-tools/deps-exe/s6-mkfifodir | 2 + src/pipe-tools/s6-cleanfifodir.c | 15 + src/pipe-tools/s6-ftrig-listen.c | 122 +++ src/pipe-tools/s6-ftrig-listen1.c | 101 ++ src/pipe-tools/s6-ftrig-notify.c | 20 + src/pipe-tools/s6-ftrig-wait.c | 48 + src/pipe-tools/s6-mkfifodir.c | 39 + src/supervision/deps-exe/s6-supervise | 3 + src/supervision/deps-exe/s6-svc | 2 + src/supervision/deps-exe/s6-svok | 1 + src/supervision/deps-exe/s6-svscan | 3 + src/supervision/deps-exe/s6-svscanctl | 2 + src/supervision/deps-exe/s6-svstat | 3 + src/supervision/deps-exe/s6-svwait | 3 + src/supervision/s6-supervise.c | 508 +++++++++ src/supervision/s6-svc.c | 12 + src/supervision/s6-svok.c | 32 + src/supervision/s6-svscan.c | 498 +++++++++ src/supervision/s6-svscanctl.c | 12 + src/supervision/s6-svstat.c | 70 ++ src/supervision/s6-svwait.c | 104 ++ tools/gen-deps.sh | 79 ++ tools/install.sh | 64 ++ 157 files changed, 11417 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 INSTALL create mode 100644 Makefile create mode 100644 README create mode 100644 README.macosx create mode 100644 README.solaris create mode 100755 configure create mode 100644 doc/fifodir.html create mode 100644 doc/index.html create mode 100644 doc/libftrig.html create mode 100644 doc/libftrigr.html create mode 100644 doc/libftrigw.html create mode 100644 doc/libs6lock/index.html create mode 100644 doc/libs6lock/s6lockd-helper.html create mode 100644 doc/libs6lock/s6lockd.html create mode 100644 doc/notifywhenup.html create mode 100644 doc/s6-cleanfifodir.html create mode 100644 doc/s6-envdir.html create mode 100644 doc/s6-envuidgid.html create mode 100644 doc/s6-fghack.html create mode 100644 doc/s6-ftrig-listen.html create mode 100644 doc/s6-ftrig-listen1.html create mode 100644 doc/s6-ftrig-notify.html create mode 100644 doc/s6-ftrig-wait.html create mode 100644 doc/s6-ftrigrd.html create mode 100644 doc/s6-log.html create mode 100644 doc/s6-mkfifodir.html create mode 100644 doc/s6-notifywhenup.html create mode 100644 doc/s6-setlock.html create mode 100644 doc/s6-setsid.html create mode 100644 doc/s6-setuidgid.html create mode 100644 doc/s6-softlimit.html create mode 100644 doc/s6-supervise.html create mode 100644 doc/s6-svc.html create mode 100644 doc/s6-svok.html create mode 100644 doc/s6-svscan-1.html create mode 100644 doc/s6-svscan-not-1.html create mode 100644 doc/s6-svscan.html create mode 100644 doc/s6-svscanctl.html create mode 100644 doc/s6-svstat.html create mode 100644 doc/s6-svwait.html create mode 100644 doc/s6-tai64n.html create mode 100644 doc/s6-tai64nlocal.html create mode 100644 doc/scandir.html create mode 100644 doc/servicedir.html create mode 100644 doc/systemd.html create mode 100644 doc/ucspilogd.html create mode 100644 doc/upgrade.html create mode 100644 doc/why.html create mode 100644 package/deps-build create mode 100644 package/deps.mak create mode 100644 package/info create mode 100644 package/modes create mode 100644 package/targets.mak create mode 100755 patch-for-solaris create mode 100644 src/daemontools-extras/deps-exe/s6-envdir create mode 100644 src/daemontools-extras/deps-exe/s6-envuidgid create mode 100644 src/daemontools-extras/deps-exe/s6-fghack create mode 100644 src/daemontools-extras/deps-exe/s6-log create mode 100644 src/daemontools-extras/deps-exe/s6-notifywhenup create mode 100644 src/daemontools-extras/deps-exe/s6-setlock create mode 100644 src/daemontools-extras/deps-exe/s6-setsid create mode 100644 src/daemontools-extras/deps-exe/s6-setuidgid create mode 100644 src/daemontools-extras/deps-exe/s6-softlimit create mode 100644 src/daemontools-extras/deps-exe/s6-tai64n create mode 100644 src/daemontools-extras/deps-exe/s6-tai64nlocal create mode 100644 src/daemontools-extras/deps-exe/ucspilogd create mode 100644 src/daemontools-extras/s6-envdir.c create mode 100644 src/daemontools-extras/s6-envuidgid.c create mode 100644 src/daemontools-extras/s6-fghack.c create mode 100644 src/daemontools-extras/s6-log.c create mode 100644 src/daemontools-extras/s6-notifywhenup.c create mode 100644 src/daemontools-extras/s6-setlock.c create mode 100644 src/daemontools-extras/s6-setsid.c create mode 100644 src/daemontools-extras/s6-setuidgid.c create mode 100644 src/daemontools-extras/s6-softlimit.c create mode 100644 src/daemontools-extras/s6-tai64n.c create mode 100644 src/daemontools-extras/s6-tai64nlocal.c create mode 100644 src/daemontools-extras/ucspilogd.c create mode 100644 src/include/s6/ftrigr.h create mode 100644 src/include/s6/ftrigw.h create mode 100644 src/include/s6/s6-supervise.h create mode 100644 src/include/s6/s6.h create mode 100644 src/include/s6/s6lock.h create mode 100755 src/libs6/deps-exe/s6-ftrigrd create mode 100755 src/libs6/deps-exe/s6lockd create mode 100755 src/libs6/deps-exe/s6lockd-helper create mode 100755 src/libs6/deps-lib/s6 create mode 100644 src/libs6/ftrig1.h create mode 100644 src/libs6/ftrig1_free.c create mode 100644 src/libs6/ftrig1_make.c create mode 100644 src/libs6/ftrigr1_zero.c create mode 100644 src/libs6/ftrigr_check.c create mode 100644 src/libs6/ftrigr_end.c create mode 100644 src/libs6/ftrigr_start.c create mode 100644 src/libs6/ftrigr_startf.c create mode 100644 src/libs6/ftrigr_subscribe.c create mode 100644 src/libs6/ftrigr_unsubscribe.c create mode 100644 src/libs6/ftrigr_update.c create mode 100644 src/libs6/ftrigr_wait_and.c create mode 100644 src/libs6/ftrigr_wait_or.c create mode 100644 src/libs6/ftrigr_zero.c create mode 100644 src/libs6/ftrigw_clean.c create mode 100644 src/libs6/ftrigw_fifodir_make.c create mode 100644 src/libs6/ftrigw_notify.c create mode 100644 src/libs6/ftrigw_notifyb.c create mode 100644 src/libs6/s6-ftrigrd.c create mode 100644 src/libs6/s6_supervise_lock.c create mode 100644 src/libs6/s6_supervise_lock_mode.c create mode 100644 src/libs6/s6_svc_main.c create mode 100644 src/libs6/s6_svc_write.c create mode 100644 src/libs6/s6_svstatus_pack.c create mode 100644 src/libs6/s6_svstatus_read.c create mode 100644 src/libs6/s6_svstatus_unpack.c create mode 100644 src/libs6/s6_svstatus_write.c create mode 100644 src/libs6/s6lock_acquire.c create mode 100644 src/libs6/s6lock_check.c create mode 100644 src/libs6/s6lock_end.c create mode 100644 src/libs6/s6lock_release.c create mode 100644 src/libs6/s6lock_start.c create mode 100644 src/libs6/s6lock_startf.c create mode 100644 src/libs6/s6lock_update.c create mode 100644 src/libs6/s6lock_wait_and.c create mode 100644 src/libs6/s6lock_wait_or.c create mode 100644 src/libs6/s6lock_zero.c create mode 100644 src/libs6/s6lockd-helper.c create mode 100644 src/libs6/s6lockd.c create mode 100644 src/pipe-tools/deps-exe/s6-cleanfifodir create mode 100644 src/pipe-tools/deps-exe/s6-ftrig-listen create mode 100644 src/pipe-tools/deps-exe/s6-ftrig-listen1 create mode 100644 src/pipe-tools/deps-exe/s6-ftrig-notify create mode 100644 src/pipe-tools/deps-exe/s6-ftrig-wait create mode 100644 src/pipe-tools/deps-exe/s6-mkfifodir create mode 100644 src/pipe-tools/s6-cleanfifodir.c create mode 100644 src/pipe-tools/s6-ftrig-listen.c create mode 100644 src/pipe-tools/s6-ftrig-listen1.c create mode 100644 src/pipe-tools/s6-ftrig-notify.c create mode 100644 src/pipe-tools/s6-ftrig-wait.c create mode 100644 src/pipe-tools/s6-mkfifodir.c create mode 100644 src/supervision/deps-exe/s6-supervise create mode 100644 src/supervision/deps-exe/s6-svc create mode 100644 src/supervision/deps-exe/s6-svok create mode 100644 src/supervision/deps-exe/s6-svscan create mode 100644 src/supervision/deps-exe/s6-svscanctl create mode 100644 src/supervision/deps-exe/s6-svstat create mode 100644 src/supervision/deps-exe/s6-svwait create mode 100644 src/supervision/s6-supervise.c create mode 100644 src/supervision/s6-svc.c create mode 100644 src/supervision/s6-svok.c create mode 100644 src/supervision/s6-svscan.c create mode 100644 src/supervision/s6-svscanctl.c create mode 100644 src/supervision/s6-svstat.c create mode 100644 src/supervision/s6-svwait.c create mode 100755 tools/gen-deps.sh create mode 100755 tools/install.sh 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.* diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..999f0c2 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,17 @@ +Main author: + Laurent Bercot + +Contributors: + Paul Jarc + Gerrit Pape + Clemens Fischer + Stefan Karrman + Jean Marot + +Thanks to: + Dan J. Bernstein + Wayne Marshall + David Madore + Nicolas George + Frans Haarman + Vallo Kallaste diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..63309ba --- /dev/null +++ b/COPYING @@ -0,0 +1,13 @@ +Copyright (c) 2011-2014 Laurent Bercot + +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. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..05a0128 --- /dev/null +++ b/INSTALL @@ -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/$( + + Please use the 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 </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 <&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 @@ + + + + + s6: fifodirs + + + + + + +

+s6
+Software
+skarnet.org +

+ +

Fifodirs

+ +

+ A fifodir is a rendez-vous point between the notifier +of certain events and its listeners. It is implemented via a +directory in the filesystem. No data is stored; it is appropriate to +create fifodirs in a RAM filesystem. +

+ +

Manipulating fifodirs

+ +

C API

+ +
    +
  • You can create fifodirs via the +ftrigw_fifodir_create() function in +libftrig.
  • +
  • You can send an event to a fifodir via the +ftrigw_notify() function in +libftrig.
  • +
  • You can clean up a fifodir via the +ftrigw_clean() function in +libftrig.
  • +
  • You can destroy fifodirs via the +rm_rf() function in +libstddjb.
  • +
+ +

Unix API

+ +
    +
  • You can create fifodirs with the +s6-mkfifodir command.
  • +
  • You can send an event to a fifodir with the +s6-ftrig-notify command.
  • +
  • You can clean up a fifodir with the +s6-cleanfifodir command.
  • +
  • You can destroy fifodirs with the rm -rf command.
  • +
+ +

Internals and Unix permissions

+ +
    +
  • Notifiers and listeners agree on a fifodir.
  • +
  • The fifodir directory is created by the notifier. It must be writable +by listeners.
  • +
  • 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.
  • +
  • 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.
  • +
  • To unsubscribe, a listener unlinks his named pipe from the directory.
  • +
+ +

+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). +

+ +

+ A publically accessible fifodir must have rights 1733: +

+ +
    +
  • Anyone can create a fifo in that fifodir
  • +
  • Only the notifier can see all the subscribers' fifos
  • +
  • A listener can only delete its own fifo
  • +
  • A notifier can delete any fifo for cleaning purposes
  • +
+ +

+ A restricted fifodir must have the gid g 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. +

+ +
    +
  • Only members of g can create a fifo in that fifodir
  • +
  • Only the notifier can see all the subscribers' fifos
  • +
  • Fifos are always created with gid g
  • +
  • A listener can only delete its own fifo
  • +
  • A notifier can delete any fifo for cleaning purposes
  • +
+ +

+ A named pipe in a fifodir must always belong to its listener and have +rights 0622: +

+ +
    +
  • Only this listener can read on the fifo
  • +
  • Anyone who has reading rights on the fifodir (i.e. only the notifier) +can write to the fifo
  • +
+ +

+ The libftrig interface takes care of all +the subtleties. +

+ + + 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 @@ + + + + + s6 - skarnet's small supervision suite + + + + + + +

+Software
+skarnet.org +

+ +

s6

+ +

What is it ?

+ +

+ s6 is a small suite of programs for UNIX, designed to allow process supervision +(a.k.a service supervision), +in the line of daemontools +and runit. +

+ +

+ 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 related resources section +below for pointers to more resources, and earlier approaches to process +supervision that might help you understand the basics. +

+ +
+ + + +
+ +

Installation

+ +

Requirements

+ +
    +
  • A POSIX-compliant system with a standard C development environment
  • +
  • GNU make, version 3.81 or later
  • +
  • skalibs version +2.0.0.0 or later
  • +
  • execline version +2.0.0.0 or later
  • +
+ +

Licensing

+ +

+ s6 is free software. It is available under the +ISC license. +

+ +

Download

+ +
    +
  • The current released version of s6 is 2.0.0.0.
  • +
  • Alternatively, you can checkout a copy of the s6 git repository: +
     git clone git://git.skarnet.org/s6 
  • +
+ +

Compilation

+ +
    +
  • See the enclosed INSTALL file for installation details.
  • +
+ +

Upgrade notes

+ +
    +
  • This page lists the differences to be aware of between +the previous versions of execline and the current one.
  • +
+ +
+ +

Reference

+ +

Commands

+ +

+ 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. +

+ +

Supervision system

+ +

+ s6-svscan and s6-supervise +are the long-lived processes maintaining the supervision tree. Other programs are +a user interface to control those processes and monitor service states. +

+ + + +

Other daemontools-like utilities

+ +

+ These programs are a rewrite of the corresponding utilities from +daemontools, with +a few extras. The +s6-log 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 multilog +counterpart. +

+ + + +

Fifodir management, notification and subscription

+ +

+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 +inter-process notification and +synchronization. +

+ + +

+

+ +

+

+ + +

Internal commands

+ + + + +

Libraries

+ + + +

Definitions

+ + + +
+ + +

Related resources

+
+ +

s6 discussion

+ + + +

Similar work

+ +
    +
  • daemontools, the pioneering +process supervision software suite.
  • +
  • daemontools-encore, +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.)
  • +
  • runit, a slightly different +approach to process supervision, with the same goals.
  • +
  • perp, yet another slightly different +approach to process supervision, also with the same goals.
  • +
+ +

Other init systems

+ +
    +
  • Felix von Leitner's minit is an +init system for Linux, with process supervision capabilities.
  • +
  • sysvinit is the +traditional init system for Linux.
  • +
  • Upstart 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.
  • +
  • Because Upstart wasn't bloated or unreliable enough, someone came up with +systemd, yet +another Linux init system, so contrary to all principles of good engineering +it's just scary.
  • +
  • The various BSD flavors have their own style of +init.
  • +
  • MacOS X has its own init spaghetti monster called +launchd.
  • +
+ +

+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. +There is a good reason for this. +

+ + +

Credits

+ + s6 is one of the (late...) results of a long design discussion that +happened in 2002-2003 on the +supervision list +and the log list. +The main contributors to the thread were + + +

Miscellaneous

+ +

Why "s6" ?

+ +

+skarnet.org's small and secure +supervision software suite. +

+ +

+ 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. +

+ + + 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 @@ + + + + + s6: libftrig + + + + + + +

+s6
+Software
+skarnet.org +

+ +

libftrig

+ +

+libftrig is a portable Unix C programming interface allowing a +process (the subscriber or listener) to be instantly +notified when another process (the notifier or writer) +signals an event. +

+ + +

What is notification ?

+
+ +

Notification vs. polling

+ +

+ Process A is notified 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 polling, where A has to +periodically (every T milliseconds) check whether E happened and has no +other way to be informed of it. +

+ +

+ Polling is generally considered bad practice - and is inferior to +notification in practically every case - for three reasons: +

+ +
    +
  • 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 polling +period). Polling processes have reaction delays due to the polling +periods.
  • +
  • Resource consumption. Even if no 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.
  • +
  • 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.
  • +
+ +

+ 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. +

+ +

+ 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 +because +it's easy. +

+ +

Notifications and Unix

+ +

+ Unix provides several frameworks so that a process B (or the kernel) can +notify process A. +

+ +
    +
  • Signals. The simplest Unix notification mechanism. Sending events amounts +to a kill() +call, and receiving events amounts to installing a signal handler (preferrably +using a self-pipe +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: +
      +
    • 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.
    • +
    • 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.
    • +
  • +
  • BSD-style IPCs, i.e. file descriptors to perform select()/poll() system +calls on, in an asynchronous event loop. 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 very specific stuff +to reach its limits. If process A is reading on +fd f, it is notified everytime another process makes f readable - +for instance by writing a byte to the other end if f 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. +
      +
    • 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.
    • +
    • 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.
    • +
  • +
  • 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.
  • +
+ +

What we want

+ +

+ We need a general framework to: +

+ +
    +
  • Allow an event-generating process to broadcast notifications to every process +that asked for one, without having to know their PIDs
  • +
  • Allow a process to subscribe to a "notification channel" and be instantly, +asynchronously notified when an event occurs on this channel.
  • +
+ +

+ This requires a many-to-many approach that Unix does not provide natively, and +that is what libftrig does. +

+ + +

That's what a bus is for. D-Bus already does all this.

+
+ +

+ 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 service, to implement a bus. And s6 is a +supervision suite, 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... without +relying on a particular service to be up. libftrig provides a notification +mechanism that does not need a bus service to be up, that's its +main advantage over a bus. +

+ +

+ 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 skabus +project in the making, which aims to be simpler, smaller and more +maintainable than D-Bus. +

+ +

How to use libftrig

+ +

+ libftrig is really a part of libs6: all the functions +are implemented in the libs6.a archive, or the libs6.so +dynamic shared object. However, the interfaces are different for notifiers +and listeners: +

+ + + + + 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 @@ + + + + + s6: the ftrigr library interface + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The ftrigr library interface

+ +

+ The ftrigr 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. +

+ +

Compiling

+ +
    +
  • Make sure the s6 headers, as well as the skalibs headers, +are visible in your header search path.
  • +
  • Use #include <s6/ftrigr.h>
  • +
+ +

Linking

+ +
    +
  • Make sure the s6 libraries, as well as the skalibs libraries, +are visible in your library search path.
  • +
  • Link against -ls6 and -lskarnet.
  • +
+ +

Programming

+ +

+ Check the s6/ftrigr.h header for the +exact function prototypes. +

+ +

+ 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 waitpid()s. The best +practice is to use a +self-pipe +to handle SIGCHLD (as well as other signals the application needs to trap), +and to always use wait_nohang() to reap children, +simply ignoring pids you don't know. +

+ +

+ If your (badly programmed) application has trouble handling unknown +children, consider using a ftrigrd service. +

+ +

A programming example

+ +

+ The src/pipe-tools/s6-ftrig-listen1.c and +src/supervision/s6-svwait.c files in the s6 package, +for instance, illustrate how to use the ftrigr library. +

+ + +

Synchronous functions with a specified maximum execution time

+ +
    +
  • Synchronous functions take a tain_t const * +(deadline) parameter and a tain_t * (stamp) +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 tain_ primitives declared in +skalibs/tai.h.
  • +
  • ("Accurate enough" means that no blocking system call must have +been made since the last time stamp was updated (by +tain_now(&stamp)). It's a good policy to always update +stamp right after a (potentially) blocking system call like +select()returns. And unless the application is extremely CPU-intensive +(think calculus for physicists or astronomers) updating stamp more +frequently is unnecessary.)
  • +
  • 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.
  • +
  • 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.
  • +
  • If any waiting occurred, the stamp 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 deadline and stamp +parameters: then the whole sequence is bound in execution time.
  • +
  • This is a general safety mechanism implemented in +libunixonacid: +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.
  • +
+ + +

Starting and ending a session

+ +
+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) ;
+
+ +

+ftrigr_start starts a session with a ftrigrd service listening on +path.
+ftrigr_startf starts a session with a ftrigrd process as a child +(which is the simplest usage).
+a is a ftrigr_t structure that must be declared in the stack and +initialized to FTRIGR_ZERO. +stamp must be an accurate enough timestamp.
+If the session initialization fails, the function returns 0 and errno is set; +else the function returns 1. +

+

+If the absolute time deadline 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 stamp and deadline should be +enough: if the function takes more than that to return, then there is a +problem with the underlying processes. +

+ +

+ You can have more than one session open in parallel, by declaring +several distinct ftrigr_t structures and calling +ftrigr_startf (or ftrigr_start) more than once. +However, this is useless, since one single session can handle +virtually as many concurrent fifodirs as your application needs. +

+ +
+ftrigr_end(&a) ;
+
+ +

+ftrigr_end frees all the resources used by the session. The +a structure is then reusable for another session. +

+ +

Subscribing to a fifodir

+ +
+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) ;
+
+ +

+ftrigr_subscribe instructs the +s6-ftrigrd daemon, related to the open +session represented by the a structure, to subscribe to the +path fifodir, and to notify the application when it receives +a series of events that matches the re regexp. +options can be 0 or FTRIGR_REPEAT. If it is 0, the daemon will +automatically unsubscribe from path once re has been +matched by a series of events. If it is FTRIGR_REPEAT, it will remain +subscribed until told otherwise. +

+ +

+ ftrigr_subscribe() returns 0 and sets errno in case of failure, or +a nonzero 16-bit number identifying the subscription in case of success. +

+ +

+ftrigr_subscribe should return near-instantly, but if +deadline is reached, it will return 0 ETIMEDOUT. If +ftrigr_subscribe returns successfully, then the +s6-ftrigrd daemon is guaranteed to be listening on path, +and events can be sent without the risk of a race condition. +

+ +

Synchronously waiting for events

+ +
+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) ;
+
+ +

+ ftrigr_wait_and() waits for all the n fifodirs +whose ids are listed in list to receive an event. It returns -1 +in case of error or timeout, or a non-negative integer in case of success.
+ ftrigr_wait_or() waits for one of the n fifodirs +whose ids are listed in list to receive an event. It returns -1 +in case of error or timeout; if it succeeds, the return value is the +position in list, starting at 0, of the identifier that received +an event; and trigger is set to the character that triggered that +event, i.e. the last character of a sequence that matched the regular +expression re used in the subscription. +

+ +

Asynchronously waiting for events

+ +

+ (from now on, the functions are listed with their prototypes instead +of usage examples.) +

+ +
+int ftrigr_fd (struct ftrigr const *a)
+
+ +

+ Returns a file descriptor to select on for reading. Do not +read() it though. +

+ +
+int ftrigr_update (ftrigr_ref a)
+
+ +

+ Call this function whenever the fd checks readability: it will +update a's internal structures with information from the +s6-ftrigrd daemon. It returns -1 if an error +occurs; in case of success, it returns the number of identifiers for +which something happened. +

+ +

+ When ftrigr_update returns, +genalloc_s(uint16, &a->list) points to an array of +genalloc_len(uint16, &a->list) 16-bit unsigned +integers. Those integers are ids waiting to be passed to +ftrigr_check. +

+ +
+int ftrigr_check (ftrigr_ref a, uint16 id, char *what)
+
+ +

+ Checks whether an event happened to id. Use after a +call to ftrigr_update(). +

+ +
    +
  • If an error occurred, returns -1 and sets errno. The error +number may have been transmitted from +s6-ftrigrd.
  • +
  • If no notification happened yet, returns 0.
  • +
  • If something happened, writes the character that triggered the +latest notification into what and returns the number of +times that an event happened to this identifier since the last +call to ftrigr_check().
  • +
+ + + 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 @@ + + + + + s6: the ftrigw library interface + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The ftrigw library interface

+ +

+ The ftrigw library provides an API for notifiers, i.e. +programs that want to regularly announce what they're doing. +

+ +

+ 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. +

+ +

Compiling

+ +
    +
  • Make sure the s6 headers, as well as the skalibs headers, +are visible in your header search path.
  • +
  • Use #include <s6/ftrigw.h>
  • +
+ +

Linking

+ +
    +
  • Make sure the s6 libraries, as well as the skalibs libraries, +are visible in your library search path.
  • +
  • Link against -ls6 and -lskarnet.
  • +
+ +

Programming

+ +

+ Check the s6/ftrigw.h header for the +exact function prototypes. +

+ +

Creating a fifodir

+ +
+char const *path = "/var/lib/myservice/fifodir" ;
+int gid = -1 ;
+int forceperms = 0 ;
+int r = ftrigw_fifodir_make(path, gid, forceperms) ;
+
+ +

+ftrigw_fifodir_make creates a fifodir at the path location. +It returns 0, and sets errno, if an error occurs. +It returns 1 if it succeeds.
+If a fifodir, owned by the user, already exists at path, and +forceperms is zero, then ftrigw_fifodir_make immediately +returns a success. If forceperms is nonzero, then +it tries to adjust path's permissions before returning. +

+ +

+If gid is negative, then path is created "public". +Any listener will be able to subscribe to path. +If gid is nonnegative, then path is created "private". +Only processes belonging to group gid will be able to +subscribe to path. +

+ +

Sending an event

+ +
+char event = 'a' ;
+r = ftrigw_notify(path, event) ;
+
+ +

+ftrigw_notify sends event to all the processes that are +currently subscribed to path. +It returns -1 if an error occurs, or the number of successfully notified +processes. +

+ +

Cleaning up

+ +

+When stray KILL signals hit s6-ftrigrd 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. +

+ +
+r = ftrigw_clean(path) ;
+
+ +

+ftrigw_clean cleans path. It returns 0, and sets errno, +if it encounters an error. It returns 1 if it succeeds. +

+ + + 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 @@ + + + + + s6: the s6lock library interface + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6lock library interface

+ +

General information

+ +

+ libs6lock 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. +

+ +

Compiling

+ +
    +
  • Make sure the s6 headers, as well as the skalibs headers, +are visible in your header search path.
  • +
  • Use #include <s6/s6lock.h>
  • +
+ +

Linking

+ +
    +
  • Make sure the s6 libraries, as well as the skalibs libraries, +are visible in your library search path.
  • +
  • Link against -ls6, -lskarnet, +`cat $sysdeps/socket.lib`, and +`cat $sysdeps/tainnow.lib`, +if $sysdeps is your skalibs installation's sysdeps directory.
  • +
+ +

Programming

+ +
    +
  • Check the s6/s6lock.h 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.
  • +
  • Given the nature of the s6lock library, it makes sense to use a +s6lockd service concurrently +accessed by several applications using such locks to gate shared +resources.
  • +
  • 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 +self-pipe +if your application is built around an event loop, are good programming +practices.
  • +
+ +

Starting and ending a session

+ +
+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) ;
+
+ +

+s6lock_start_g starts a session by connecting to a s6lockd service +listening on path. The working directory is set by the administrator +of the service.
+s6lock_startf_g starts a session with a s6lockd process as a child, +using lockdir as its working directory. +
+a 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. +

+

+If the absolute time deadline 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 deadline should be +enough: if the function takes more than that to return, then there is a +problem with the underlying processes. +

+ +

+ You can have more than one session open in parallel, by declaring +several distinct s6lock_t structures and calling +s6lock_startf_g (or s6lock_start_g) 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. +

+ +
+s6lock_end(&a) ;
+
+ +

+s6lock_end frees all the resources used by the session. The +a structure is then reusable for another session. +

+ +

Acquiring and releasing locks

+ +
+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) ;
+
+ +

+s6lock_acquire_sh_g instructs the +s6lockd daemon, related to the open +session represented by the a handle, to try and acquire a +shared lock on the +file file located under that daemon's working directory +(typically /var/lock). file will be interpreted as +relative to the daemon's working directory even if it starts with a +slash; however, slashes in the middle of file are likely to +result in an error. +

+ +

+limit and deadline are two absolute dates. +deadline is a deadline for the execution of the +function: if by deadline the function has not returned, +then it instantly returns 0 and sets errno to ETIMEDOUT. The +function is normally near-instantaneous, so deadline can +be very close in the future and serves only as a protection against +malicious servers. limit is the acquisition deadline: if +by limit the daemon still has not been able to acquire a lock +on file, then it will report a timeout to the client. +

+ +

+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 *id; this number serves as an identifier +for this lock. +

+ +

+s6lock_acquire_ex_g works just like s6lock_acquire_sh_g, +except that the daemon tries to acquire an exclusive lock. +

+ +

+s6lock_release_g releases the lock identified by id. +It normally returns 1. It can return 0 with errno set to a suitable +value if it fails. id is not valid after the corresponding +lock has been released. The function normally returns instantly, with +deadline as a safeguard. +

+ +

Asynchronously waiting for locks

+ +

+ (from now on, the functions are listed with their prototypes instead +of usage examples.) +

+ +
+int s6lock_fd (s6lock_t const *a)
+
+ +

+ Returns a file descriptor to select on for reading. Do not +read() it though. +

+ +
+int s6lock_update (s6lock_t *a)
+
+ +

+ Call this function whenever the fd checks readability: it will +update a's internal structures with information from the +s6lockd daemon. It returns -1 if an error +occurs; in case of success, it returns the number of identifiers for +which something happened. +

+ +

+ When s6lock_update returns, +genalloc_s(uint16, &a->list) points to an array of +genalloc_len(uint16, &a->list) 16-bit unsigned +integers. Those integers are ids waiting to be passed to +s6lock_check. +

+ +
+int s6lock_check (s6lock_t *a, uint16 id, char *what)
+
+ +

+ Checks whether the lock identified by id has +been acquired. Use after a call to s6lock_update(). +

+ +
    +
  • If an error occurred, returns -1 and sets errno. The error +number may have been transmitted from +s6lockd.
  • +
  • If the lock has not been acquired yet, returns 0.
  • +
  • If the lock has been acquired, returns 1.
  • +
+ +

Synchronously waiting for locks

+ +

+ int s6lock_wait_or_g (s6lock_t *a, uint16 const *idlist, unsigned int n, struct taia const *deadline)
+Synchronously waits for one of the locks represented by the array pointed to +by idlist of length n to be acquired. Returns -1 if it fails, +or a nonnegative number on success, which is the index in idlist of the +acquired lock's id. If no result has been obtained by deadline, the +function returns -1 ETIMEDOUT. +

+ +

+ int s6lock_wait_and_g (s6lock_t *a, uint16 const *idlist, unsigned int n, struct taia const *deadline)
+Synchronously waits for all of the locks represented by the array pointed to +by idlist of length n to be acquired. Returns -1 if it fails and +0 if it succeeds. If no result has been obtained by deadline, the +function returns -1 ETIMEDOUT. +

+ + + 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 @@ + + + + + s6: the s6lockd-helper internal program + + + + + + +libs6lock
+s6
+Software
+skarnet.org

+ +

The s6lockd-helper program

+ +

+s6lockd-helper 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. +

+ +

Interface

+ +

+ s6lockd-helper is not meant to be invoked directly by the user: +it will be spawned by the +s6lockd program. +

+ +

Notes

+ +
    +
  • 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.
  • +
  • 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: +
      +
    • Processes are not a scarce resource. Today's schedulers work in O(1), +i.e. a sleeping process takes no scheduling time at all.
    • +
    • s6lockd-helper is extremely tiny. Every instance should use up at +most one or two pages of non-sharable memory.
    • +
  • +
+ + + 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 @@ + + + + + s6: the s6lockd internal program + + + + + + +libs6lock
+s6
+Software
+skarnet.org

+ +

The s6lockd program

+ +

+s6lockd is the s6lock daemon. It is a program that manages +a set of lock files in a given directory, and associated timeouts. +

+ +

Interface

+ +

+ 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 +s6lock client library. +

+ +

+ There are 2 ways to use s6lockd: +

+ +
    +
  1. Use the s6lock_startf() library call. +A s6lockd child will then be spawned from your +calling process, and automatically reaped when you call +s6lock_end(). 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.
  2. +
  3. Use the s6lock_start() library call, together with a +s6lockd service. +For once, this is the recommended setup: s6lockd creates empty +lock files, and having all s6lockd instances run under the same user +simplifies permissions management considerably.
  4. + +
+ +

+When run as a service, s6lockd has no "standalone" mode: it is +designed to work with a Unix +domain superserver, like +s6-ipcserver. +s6lockd follows the UCSPI +interface, it can be directly executed from the superserver. +

+ +

Notes

+ +
    +
  • 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 s6lockd-helper child per +requested lock.
  • +
+ + + 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 @@ + + + + + s6: service startup notifications + + + + + + +

+s6
+Software
+skarnet.org +

+ +

Service startup notifications

+ +

+ It is easy for a process supervision suite to know when a service that was up +is now down: 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, s6-supervise sends a 'd' event +to its ./event fifodir, so every subscriber +knows that the service is down. All is well. +

+ +

+ It is much trickier for a process supervision suite to know when a service +that was down is now up. 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 when the service is really ready. +s6-supervise sends a 'u' event to its +./event fifodir 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. +

+ +

+ 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. +

+ +

+ s6 provides two ways for daemons to perform startup notification. +

+ +
    +
  1. Daemons can use the ftrigw_notify() function, provided in +the ftrigw library. This is extremely +simple and efficient, but requires specific s6 support in the daemon.
  2. +
  3. 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 s6-notifywhenup, +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.
  4. +
+ +

+ 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. +

+ + + 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 @@ + + + + + s6: the s6-cleanfifodir program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-cleanfifodir program

+ +

+s6-cleanfifodir cleans up a fifodir. +

+ +

Interface

+ +
+     s6-cleanfifodir fifodir
+
+ +

+s6-cleanfifodir cleans up fifodir, that must belong to the current user. +That means it removes all stale FIFOs in fifodir. +

+ +

+ In normal use, it is not necessary to call s6-cleanfifodir. However, stale +FIFOs can be left by s6-ftrigrd processes that +were violently killed, so it's good practice to regularly clean up fifodirs. +

+ + + 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 @@ + + + + + s6: the s6-envdir program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-envdir program

+ +

+s6-envdir changes its environment, then executes into another program. +

+ +

Interface

+ +
+     s6-envdir [ -I | -i ] [ -n ] [ -f ] [ -c nullis ] dir prog...
+
+ +
    +
  • s6-envdir reads files in dir. For every file f in dir, +that does not begin with a dot and does not contain the '=' character:
  • +
  • If f is empty, remove a variable named f from the environment, if any.
  • +
  • Else add a variable named f to the environment (or replace f if it +already exists) with the first line of the contents of file f 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.
  • +
+ +

Options

+ +
    +
  • -i : strict. If dir does not exist, exit 111 with an +error message. This is the default.
  • +
  • -I : loose. If dir does not exit, exec into +prog without modifying the environment first.
  • +
  • -f : verbatim mode. All the file is given as the value of the +environment variable, including newlines (except the last one if the -n +option is not given). Null characters are still translated.
  • +
  • -n : do not chomp. If the -f option is given and the +file ends with a newline, keep that last newline in the value. If the -f +option is not given, keep the trailing blanks at the end of the first line (but +not the ending newline).
  • +
  • -c nullis : replace null characters with the +first character of nullis instead of a newline.
  • +
+ +

Notes

+ +

+ s6-envdir without options behaves exactly like +envdir. +

+ + + 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 @@ + + + + + s6: the s6-envuidgid program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-envuidgid program

+ +

+s6-envuidgid sets the UID, GID and GIDLIST environment variables, +then executes into another program. +

+ +

Interface

+ +
+     s6-envuidgid account prog...
+
+ +
    +
  • s6-envuidgid looks account up by name in the account database.
  • +
  • If account is unknown, it exits 1.
  • +
  • It sets the UID environment variable to account's uid, and the GID +environment variable to account's gid.
  • +
  • It also sets the GIDLIST environment variable to a comma-separated list of +supplementary group ids account is a member of according to the +group database. (If account doesn't belong to any other group than its +primary group, GIDLIST is still set, but empty.)
  • +
  • Then it executes into prog....
  • +
+ +

Notes

+ +

+ s6-envuidgid behaves like +envuidgid, except that: +

+ +
    +
  • it also handles supplementary groups
  • +
  • It exits 1 if account does not exist.
  • +
+ +

+ 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 +s6-tcpserver4 +make use of this. +

+ + + 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 @@ + + + + + s6: the s6-fghack program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-fghack program

+ +

+s6-fghack is an anti-backgrounding tool. +

+ +

Interface

+ +
+     s6-fghack prog...
+
+ +
    +
  • s6-fghack opens a lot of file descriptors (all writing to a single pipe).
  • +
  • Then it forks and executes prog....
  • +
  • If something gets written on one of those descriptors, it's a bug in prog. +s6-fghack then complains and exits 102.
  • +
  • Unless prog... goes out of its way to close descriptors it does know about, +s6-fghack is able to detect when prog... exits. It exits with the same exit +code (or 111 if prog... has crashed).
  • +
+ +

Notes

+ +

+ 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. +

+ + + 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 @@ + + + + + s6: the s6-ftrig-listen program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-ftrig-listen program

+ +

+s6-ftrig-listen subscribes to several fifodirs, then +spawns a program, then waits for pattern of events to occur on the fifodirs. +

+ +

Interface

+ +

+ In an execlineb +script: +

+ +
+     s6-ftrig-listen [ -a | -o ] [ -t timeout ] { fifodir1 regexp1 fifodir2 regexp2 ... } prog...
+
+ +
    +
  • s6-ftrig-listen subscribes to fifodir1 with the regexp regexp1, +to fifodir2 with the regexp regexp2, and so on.
  • +
  • It then forks and exec prog... with all its arguments
  • +
  • It waits for the series of events received on fifodir-i +to match regexp-i, The regexp-i must be +extended +regular expressions.
  • +
  • When the series of read events matches the regexps, +s6-ftrig-listen exits 0.
  • +
+ +

Options

+ +
    +
  • -t timeout : if the events on the fifodirs have not +matched the regexps after timeout milliseconds, print an error message on +stderr and exit 1. By default, s6-ftrig-listen1 waits indefinitely for a matching series +of events.
  • +
  • -a : and (conjunction). s6-ftrig-listen will only exit when all +the fifodir-i have been notified with events matching the corresponding regexp-i. +This is the default.
  • +
  • -o : one (disjunction). s6-ftrig-listen will exit as soon as one +of the fifodir-i has been notified with events matching its regexp-i.
  • +
+ +

Notes

+ +

+ 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 execlineb -Pc 's6-ftrig-listen ...' to get the +benefits of the execlineb brace syntax. +

+ + + 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 @@ + + + + + s6: the s6-ftrig-listen1 program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-ftrig-listen1 program

+ +

+s6-ftrig-listen1 subscribes to a fifodir, then +spawns a program, then waits for a pattern of events to occur on the fifodir. +

+ +

+s6-ftrig-listen1 acts just as s6-ftrig-wait, +except it can make sure that the process sending the notifications is actually +started after there is a listener for those events. +

+ +

Interface

+ +
+     s6-ftrig-listen1 [ -t timeout ] fifodir regexp prog...
+
+ +
    +
  • s6-ftrig-listen1 subscribes to fifodir
  • +
  • It then forks and exec prog... with all its arguments
  • +
  • It waits for the series of events received on fifodir +to match regexp. regexp must be an +extended +regular expression.
  • +
  • When the series of read events matches regexp, +s6-ftrig-listen1 exits 0.
  • +
+ +

Options

+ +
    +
  • -t timeout : if the events on fifodir have not +matched regexp after timeout milliseconds, print an error message on +stderr and exit 1. By default, s6-ftrig-listen1 waits indefinitely for a matching series +of events.
  • +
+ +

Usage example

+ +

+ The following sequence of shell commands has a race condition: +

+ +

In terminal 1:

+
+s6-mkfifodir /tmp/toto
+s6-ftrig-wait /tmp/toto "message"
+
+ +

Then in terminal 2

+
+s6-ftrig-notify /tmp/toto message
+
+ +

+ Depending on the operating system's scheduler, there is the possibility that +the s6-ftrig-notify process starts sending "message" before the +s6-ftrig-wait process has actually subscribed to /tmp/toto, 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: +

+ +
+s6-mkfifodir /tmp/toto
+s6-ftrig-listen1 /tmp/toto "message" s6-ftrig-notify /tmp/toto message
+
+ + + 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 @@ + + + + + s6: the s6-ftrig-notify program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-ftrig-notify program

+ +

+s6-ftrig-notify sends a series of events to a fifodir. +

+ +

Interface

+ +
+     s6-ftrig-notify fifodir message
+
+ +

+s6-ftrig-notify notifies all the current listeners in fifodir +with all the characters in message, one by one. +

+ +

Notes

+ +

+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: +ftrigw_notify(). +

+ + + 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 @@ + + + + + s6: the s6-ftrig-wait program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-ftrig-wait program

+ +

+s6-ftrig-listen1 subscribes to a fifodir and +waits for a pattern of events to occur on this fifodir. +

+ +

Interface

+ +
+     s6-ftrig-wait [ -t timeout ] fifodir regexp
+
+ +
    +
  • s6-ftrig-wait subscribes to fifodir
  • +
  • It waits for the series of events received on fifodir +to match regexp. regexp must be an +extended +regular expression.
  • +
  • When the series of read events matches regexp, +s6-ftrig-wait exits 0.
  • +
+ +

Options

+ +
    +
  • -t timeout : if the events on fifodir have not +matched regexp after timeout milliseconds, print an error message on +stderr and exit 1. By default, s6-ftrig-wait waits indefinitely for a matching series +of events.
  • +
+ + + 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 @@ + + + + + s6: the s6-ftrigrd program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-ftrigrd program

+ +

+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 ftrigr library, and notifies it when desired +events happen. +

+ +

Interface

+ +

+ s6-ftrigrd is not meant to be called directly. +

+ +
    +
  • If the client program uses ftrigr_startf(), 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.
  • +
  • If the client program uses ftrigr_start(), then it tries to connect +to a Unix domain socket. A ftrigrd service should be listening to that +socket, i.e. a Unix domain superserver such as +s6-ipcserver +spawning a s6-ftrigrd program on every connection. Then a s6-ftrigrd instance is created +for the client.
  • +
  • When the client uses ftrigr_end(), or closes s6-ftrigrd's stdin in +any way, s6-ftrigrd exits 0.
  • +
+ +

+ s6-ftrigrd handles the grunt work of creating fifos in a +fifodir 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. +

+ +

Notes

+ +

+ The connection management between the client and s6-ftrigrd is entirely done +by the skaclient +library. +

+ +

+ 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 +ftrigr_update() as soon as ftrigr_fd() becomes readable. +

+ + + 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 @@ + + + + + s6: the s6-log program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-log program

+ +

+s6-log is a reliable logging program with automated log rotation, similar to +daemontools' multilog, +with full POSIX regular expression support. +

+ +

Interface

+ +
+     s6-log [ -q | -v ] [ -b ] [ -p ] [ -t ] [ -e ] logging script
+
+ +

+s6-log reads and compiles logging script 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 never any log loss. It exits cleanly when stdin closes or when +it receives SIGTERM. +

+ +

Options

+ +
    +
  • -b : 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 +multilog's behaviour. +By default, s6-log keeps reading from stdin even if its buffers still +contain data. -b 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.
  • +
  • -p : protect. Do not exit on receipt of a SIGTERM; only +exit when reading EOF on stdin.
  • +
  • -t : timestamp. Prepends every log line that is written to +a logging directory with a +TAI64N +timestamp.
  • +
  • -e : timestamp alerts. Prepends every "alert" line with a +TAI64N timestamp.
  • +
  • -q | -v : 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: +
      +
    • 0: only write alerts and fatal errors
    • +
    • 1: write alerts, warnings and fatal errors
    • +
  • +
+ +

Logdirs

+ +

+A logdir (logging directory) is a place where logs are +stored. s6-log can be scripted to write into one or more logdirs. +

+ +

+ A logdir may contain the following files: +

+ +
    +
  • lock: this file is locked by s6-log at start, +to make sure only one instance is running at the same time.
  • +
  • current: the file where selected log lines are appended. +If current 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.
  • +
  • state: last processor's output, see below.
  • +
  • previous: a rotation is happening in that logdir.
  • +
  • processed, newstate: a rotation is happening +in that logdir and the processor script is running.
  • +
  • timestamped files: those files are @timestamp.s or +@timestamp.u and are old log files that have been processed +and rotated (if they're ending in .s) or that were the current +file when s6-log got interrupted (if they're ending in .u), in which +case they have not been processed.
  • +
+ +

Rotation

+ +

+ In a logdir, selected lines are appended to the current file. +When current becomes too big, a rotation happens. +The current file will be possibly processed, then +it will become an archived log file named +@timestamp.s, where timestamp, a +TAI64N +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 current +file. +

+ +

+ 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. +

+ +

Processors

+ +

+ A processor script can be set for every logdir. When a rotation +occurs, current (which has then been renamed previous) is +fed to processor's stdin, and processor's stdout is saved +and archived. processor can also read the state file +on its fd 4; what it writes to its fd 5 will be saved as the next +state file, for the next rotation. +

+ +

+ Processors should not background themselves: s6-log considers the +processing done when its processor 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 +cooldown time. +

+ +

+ Processors make s6-log Turing-complete by allowing you to use any +external program to handle log files that are going to be archived. +

+ +

Logging script syntax

+ +

+ When starting up, s6-log reads its arguments one by one; this +argument sequence, or directive sequence, forms a +logging script which tells s6-log what to log, where, and how. +

+ +

+ Every directive can be a selection directive, a control +directive or an action 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. +

+ +

Selection directives

+ +

+ 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. +

+ +
    +
  • +regexp: select yet-unselected lines that match regexp, which must be a +POSIX +Extended Regular Expression.
  • +
  • -regexp: deselect yet-selected lines that match regexp, which must be a +POSIX Extended Regular Expression.
  • +
  • f: select exactly lines that have not yet been acted +upon (i.e. that were always deselected when the script encountered an action +directive).
  • +
+ +

Control directives

+ +

+ These directives tune s6-log's behaviour for the next actions. +

+ +
    +
  • nnumber: next logdirs will contain up to +number archived log files. If there are more, the oldest archived +log files will be suppressed, only the latest number will be kept. +By default, number is 10.
  • +
  • sfilesize: next rotations will occur when +current log files approach filesize bytes. By default, +filesize is 99999; it cannot be set lower than 4096 or +higher than 16777215.
  • +
  • Stotalsize: next logdirs will contain up +to totalsize 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 totalsize of zero means no such limit; +use n0 instead if you don't want any archived log files. By +default, totalsize is 0 (unlimited).
  • +
  • ltolerance: next rotations will be triggered +when the size of current goes higher than filesize minus +tolerance. tolerance cannot be more than half of +filesize. By default, tolerance is 2000.
  • +
  • rcooldown: if an error occurs during operations +on the next logdirs, retry every cooldown milliseconds. By default, +cooldown is 2000; it's strongly discouraged to set it to a value +under 50.
  • +
  • Ealertsize: only the first alertsize +bytes of the selected lines will be used in the next alerts. An +alertsize of 0 means no limit. By default, alertsize is +200.
  • +
  • ^statussize: only the first statussize +bytes of the selected lines will be used in the next status file updates. +If a line is shorter than statussize bytes, the status file will be +padded with newlines so it is always statussize bytes long. 0 means +an unpadded, unlimited status file. By default, statussize is 1001.
  • +
  • !processor: registers +execlineb -Pc processor as a processor for the next logdirs; +execlineb must be found in s6-log's PATH. +If processor is empty, no processor will be set for the next logdirs. +By default, no processor is set.
  • +
+ +

Action directives

+ +

+ These directives determine what s6-log actually does with the +selected lines. +

+ +
    +
  • e: alert. s6-log will print "s6-log: alert: ", +possibly prepended with a timestamp, followed by the first +alertsize bytes of the line.
  • +
  • =statusfile: status. s6-log will atomically +update the statusfile file with the first statussize +bytes of the line, and pad it with newlines. s6-log must have the right +to write to statusfile and to statusfile's directory.
  • +
  • dir (must start with '/' or '.'): logdir. +s6-log will log the line into the logdir dir. s6-log must have +the right to write to dir.
  • +
+ + +

Signals

+ +
    +
  • SIGTERM: If s6-log has been run with the -p 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.
  • +
  • SIGALRM: triggers a rotation on every logdir s6-log is monitoring, +as if the current file in those logdirs had reached the size +limit.
  • +
+ +

Examples

+ +
+     s6-log -bt n20 s1000000 /var/log/services/stuff
+
+ +

+ Logs all of stdin, prepending every line with a timestamp, into the +/var/log/services/stuff 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. +

+ +
+     s6-log -t n30 E500 - +fatal: e - +^STAT =/var/log/foobard/status f s10000000 S15000000 !"gzip -nq9" /var/log/foobard
+
+ +
    +
  • Sends alerts to stderr with the 500 first bytes of lines containing "fatal:".
  • +
  • Maintains the /var/log/foobard/status file at 1001 bytes, +updating it when it finds a log line starting with "STAT".
  • +
  • Logs all other lines to logdir /var/log/foobard. When current +reaches at least 9998 kB (i.e. 10 MB filesise minus 2kB tolerance), pipe it +through gzip -nq9 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.
  • +
+ + +

Why use execlineb to interpret the "processor" string ?

+ +

+ Because it is exactly what +execlineb +is for. +

+ +
    +
  • Directly executing processor is not flexible enough. We want +to be able to run a complete command line, with an executable name and its +arguments.
  • +
  • We could interpret the processor string via /bin/sh. +This is what multilog +does. However, /bin/sh, despite being the traditional Unix interpreter, +is overpowered for this. We don't need a complete shell script interpreter: +most processor commands will be very simple, with only two or three +words, and we only need a way to turn a string into an argv, i.e. a +command line.
  • +
  • execlineb +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 /bin/sh does. It happens to be the perfect +tool for the job.
  • +
  • Yes, I also did this on purpose so people have a reason to use the +execline language. Do not +look at me like that: it really is the perfect tool for the job.
  • +
+ +

Why have another logging mechanism ?

+ +

+ Because the syslog mechanism and all its implementations (save one) suck. +I'm not being judgmental; I'm just stating the obvious. +

+ +

The syslog design is flawed from the start

+ +

+When +asked why he started rsyslog, 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. +

+ +

+ The problem with syslogd does not come from such or such implementation. +The problem comes from syslog's design in the first place. +

+ +
    +
  • syslog makes you send all 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: +
      +
    • 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.
    • +
    • 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 /var/log/messages, +/var/log/auth.log, /var/log/daemon.log or +/var/log/syslog with very vague semantics. All of syslogd's +line processing power remains unused, because making real use of it would +be too complex.
    • +
    +
  • syslogd logs to files. This is wrong, because files grow +and disks fill up. Sure, there are utilities such as logrotate +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: logging and log +rotation management must be done by the same tool. +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.
  • +
  • syslogd is a complex process that runs as root. We all know what +complex processes running as root mean: bugs turning into security holes.
  • +
  • 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 ?
  • +
+ +

+ 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 +multilog, which +s6-log improves upon. +

+ +

A not-so-modest proposal: the logging chain

+ +

+ Unix distributions already do this to some extent, but it's at best +unclear where the logs go for any given program. +

+ +
    +
  • Every program, without exception, should send its logs (be it +error messages, warning messages, or anything) to its standard +error descriptor, i.e. fd 2. This is why it's open for.
  • +
  • When process 1 starts, the logging chain is rooted to the +machine console: 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. +
  • Process 1 should spawn and supervise a catch-all logging +mechanism 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.
  • +
  • Process 1's own error messages can go to the machine console, +or dirty tricks can be used so they +go to the catch-all logging mechanism.
  • +
  • Services that are spawned by process 1 should come with their +own logger service; the supervision mechanism offered by +s6-svscan makes it easy. Error messages +from the loggers themselves naturally go to the catch-all +mechanism.
  • +
  • User login mechanisms such as getty, xdm or +sshd 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.
  • +
  • A syslogd service may 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 ucspilogd program, +combined with the +s6-networking +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.
  • +
+ +

+ So, given a program, where are its logs sent ? +

+ +
    +
  • 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.
  • +
  • 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.
  • +
  • If the program is descended from a logged service, its logs are +naturally sent to the service's logger.
  • +
  • Else the logs are sent to the catch-all logger.
  • +
  • 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.
  • +
+ +

What does s6-log have to do with all this ?

+ +

+ In a logging chain 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: +

+ +
    +
  • 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.
  • +
  • s6-log consumes very little memory per instance (unless it +accumulates unflushed log lines, which you can avoid with the +-b option). So, launching a lot of s6-log processes does +not waste resources.
  • +
  • s6-log is vastly configurable via logging scripts; every instance +is as powerful as a traditional syslogd.
  • +
  • s6-log can log to a RAM filesystem and thus is suitable as a +catch-all logger. Clever tricks like Upstart's logd or daemontools' +readproctitle +are just that: tricks. s6-log gives a unified interface to all of +your system's loggers.
  • +
+ + +

You're wrong about being as powerful as +syslogd: s6-log does not do remote logging.

+ +

+ You mean you want to send, live, every log line +over the network via UDP ? You can't be serious. +

+ +

+ 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 current files to, write +to a small RAM filesystem. +

+ +

+ If you have to log stuff live via the network, you +do not need any local logging software. You don't even need syslogd. +Just filter your stderr via some grep that selects lines for +you, then sends them to a network socket. A trivial shell script, or +execline +script, can do that for you. +

+ +

+ Do not insist on using syslogd. It does nothing magical, and nothing +that can't be done in a simpler way using simpler tools. +

+ + + 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 @@ + + + + + s6: the s6-mkfifodir program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-mkfifodir program

+ +

+s6-mkfifodir creates a fifodir. +

+ +

Interface

+ +
+     s6-mkfifodir [ -f ] [ -g gid ] fifodir
+
+ +

+s6-mkfifodir creates fifodir, belonging to the current user. +

+ +

Options

+ +
    +
  • -f : force permissions. If fifodir already exists, +change its permissions according to the -g options. By default, if +fifodir exists, s6-mkfifodir does nothing.
  • +
  • -g gid : make fifodir only listenable +by members of group gid. If this option is not given, the fifodir is +made publically listenable.
  • +
+ + + 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 @@ + + + + + s6: the s6-notifywhenup program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-notifywhenup program

+ +

+s6-notifywhenup launches a daemon while listening to a file descriptor, +and sends a 'U' event to a fifodir when it +receives something on that file descriptor. +

+ +

+This page explains why this program is +needed. +

+ +

Interface

+ +
+     s6-notifywhenup [ -d fd ] [ -e fifodir ] [ -f ] [ -t timeout ] prog...
+
+ +
    +
  • s6-notifywhenup forks and executes prog... as the +parent, with a pipe from prog...'s stdout to the child.
  • +
  • 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 ./event fifodir.
  • +
  • The child exits 0.
  • +
+ +

Options

+ +
    +
  • -d fd : listen to +prog's output on descriptor fd. The default is 1.
  • +
  • -e fifodir : send a 'U' event to fifodir +fifodir. Default is ./event.
  • +
  • -f : simple fork. Normally, s6-notifywhenup doubleforks, +in order to accommodate for a +prog 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 prog reaps even children it doesn't know it has.
  • +
  • -t timeout : if no EOF has been received +after timeout milliseconds, exit without sending the event. +Default is 0, meaning infinite.
  • +
+ +

Notes

+ +
    +
  • s6-notifywhenup executes prog... as the parent in order +for prog to keep the same pid, which is vital for supervised +processes.
  • +
  • s6-notifywhenup can be used, for instance, with +s6-tcpserver +and its -1 option, so that reliable startup notification is +achieved. s6-notifywhenup -f s6-tcpserver -1 args will +send a 'U' event to ./event when s6-tcpserver is actually +listening to its network socket.
  • +
  • The s6-svwait program can be used +to wait for 'U' events.
  • +
+ + + 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 @@ + + + + + s6: the s6-setlock program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-setlock program

+ +

+s6-setlock takes a lock on a file, then executes into another program. +

+ +

Interface

+ +
+     s6-setlock [ -n | -N | -t timeout ] [ -r | -w ] file prog...
+
+ +
    +
  • s6-setlock creates file if it does not exist and opens it for writing.
  • +
  • It locks file. If it cannot take the lock for any reason, it exits 1.
  • +
  • It executes into prog....
  • +
+ +

Options

+ +
    +
  • -n : nonblocking lock. If s6-setlock cannot acquire the lock, it will +exit 1 immediately.
  • +
  • -N : blocking lock. s6-setlock will wait until it can acquire the lock. +This is the default.
  • +
  • -t timeout : timed lock. If s6-setlock cannot acquire +the lock after timeout milliseconds, it will exit 1.
  • +
  • -r : shared lock. Other shared locks on the same file will not prevent +the lock from being acquired (but an exclusive lock will).
  • +
  • -w : exclusive lock. This is the default.
  • +
+ +

Notes

+ +
    +
  • s6-setlock leaks an open file descriptor into the prog +execution. This is intended: the fd holds the lock, which is released +when prog exits. prog must not touch fds it does not +know about.
  • +
  • If the timed lock option is chosen, s6-setlock does not acquire the lock +itself. Instead, it spawns a s6lockd-helper +process that acquires the lock while s6-setlock controls the timeout; the +s6lockd-helper process then holds the lock and lives as long as +prog.
  • +
+ + + 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 @@ + + + + + s6: the s6-setsid program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-setsid program

+ +

+s6-setsid runs a program as session leader. +

+ +

Interface

+ +
+     s6-setsid [ -I | -i ] prog...
+
+ +
    +
  • s6-setsid creates a new session if it is not a session leader, and becomes +session leader of this new session.
  • +
  • It then executes into prog....
  • +
+ +

Options

+ +
    +
  • -i : strict. If s6-setsid is already a session leader, it will +exit 111 with an error message.
  • +
  • -I : loose. If s6-setsid is already a session leader, it will +print a warning message, but exec into prog nonetheless. This is the +default.
  • +
+ + + 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 @@ + + + + + s6: the s6-setuidgid program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-setuidgid program

+ +

+s6-setuidgid executes a program as another user. +

+ +

Interface

+ +
+     s6-setuidgid account prog...
+
+ +
    +
  • If account contains a colon, it is interpreted as uid:gid, +else it is interpreted as a username and looked up by name in the account +database.
  • +
  • If account is unknown, s6-setuidgid exits 1.
  • +
  • s6-setuidgid sets its (real and effective) uid and gid to those of account. +It also sets the list of supplementary groups to the correct one for account +according to the group database.
  • +
  • Then it executes into prog....
  • +
+ +

Notes

+ +

+ s6-setuidgid can only be run as root. Its main use is to drop root privileges before +starting a daemon. +

+ + + 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 @@ + + + + + s6: the s6-softlimit program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-softlimit program

+ +

+s6-softlimit changes its process limits, then executes into another program. +

+ +

Interface

+ +
+     s6-softlimit [ -a allmem ] [ -c core ] [ -d data ] [ -f fsize ] [ -l lock ] [ -m mem ] [ -o ofiles ] [ -p proc ] [ -r res ] [ -s stack ] [ -t cpusecs ] prog...
+
+ +
    +
  • s6-softlimit parses its options and sets process (soft) resource limits accordingly.
  • +
  • A value of '=' for any option means "set that limit to the hard limit".
  • +
  • Depending on your operating system, an option may do nothing.
  • +
  • When s6-softlimit has modified all the limits successfully, it executes into prog....
  • +
+ +

Options

+ +
    +
  • -a allmem : limit the total available memory to allmem bytes.
  • +
  • -c core : limit the core file size to core bytes.
  • +
  • -d data : limit the available heap memory to data bytes.
  • +
  • -f fsize : limit the file size to fsize bytes.
  • +
  • -l lock : limit the available locked memory to lock bytes.
  • +
  • -m mem : limit all types of memory to mem bytes.
  • +
  • -o ofiles : limit the number of open fds to ofiles.
  • +
  • -p proc : limit the number of processes to proc (per user).
  • +
  • -r allmem : limit the available physical memory to res bytes.
  • +
  • -s stack : limit the available stack memory to stack bytes.
  • +
  • -t cpusecs : limit the available CPU time to cpusecs seconds.
  • +
+ + + 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 @@ + + + + + s6: the s6-supervise program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-supervise program

+ +

+s6-supervise monitors a long-lived process (or service), 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 supervision tree, the supervised process +being a leaf. +

+ +

Interface

+ +
+     s6-supervise servicedir
+
+ +
    +
  • s6-supervise switches to the servicedir +service directory.
  • +
  • It exits 100 if another s6-supervise process is already monitoring this service.
  • +
  • If the ./event fifodir 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.
  • +
  • It sends a 's' event to ./event.
  • +
  • If the default service state is up, s6-supervise spawns ./run.
  • +
  • s6-supervise sends a 'u' event to ./event whenever it +successfully spawns ./run.
  • +
  • When ./run dies, s6-supervise sends a 'd' event to ./event.
  • +
  • When ./run dies, s6-supervise spawns ./finish if it exists.
  • +
  • ./finish must exit in less than 5 seconds. If it takes more than that, +s6-supervise kills it.
  • +
  • When ./finish dies, s6-supervise restarts ./run unless it has been +told not to.
  • +
  • There is a minimum 1-second delay between two ./run spawns, to avoid busylooping +if ./run exits too quickly.
  • +
  • When killed or asked to exit, it waits for the service to go down one last time, then +sends a 'x' event to ./event before exiting 0.
  • +
+ +

Signals

+ +

+ s6-supervise reacts to the following signals: +

+ +
    +
  • SIGTERM: bring down the service and exit, as if a +s6-svc -xd command had been received
  • +
  • SIGHUP: exit as soon as the service stops, as if a +s6-svc -x command had been received
  • +
  • SIGQUIT: currently like SIGTERM, but this might change in the future
  • +
+ +

Usage notes

+ +
    +
  • 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.
  • +
  • Even in boot scripts, s6-supervise should normally not be run directly. It's +better to have a collection of service directories in a +single scan directory, and just run +s6-svscan on that scan directory. s6-svscan will spawn +the necessary s6-supervise processes, and will also take care of logged services.
  • +
  • You can use s6-svc to send commands to the s6-supervise +process; mostly to change the service state and send signals to the monitored +process.
  • +
  • You can use s6-svok to check whether s6-supervise +is successfully running.
  • +
  • You can use s6-svstat to check the status of a +service.
  • +
  • s6-supervise maintains internal information inside the ./supervise +subdirectory of servicedir. servicedir itself can be read-only, +but both servicedir/supervise and servicedir/event +need to be read-write.
  • +
  • The ./finish script is not guaranteed to have stdin and +stdout pointing to the same locations as the ./run script. More +precisely: the stdin and stdout will be preserved for ./finish +until s6-supervise is asked to exit, but the last ./finish +execution will have its stdin and stdout redirected to /dev/null. +(This is to avoid maintaining open descriptors when a service is down, which +would prevent its logger from exiting cleanly.)
  • +
+ +

Implementation notes

+ +
    +
  • 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 s6-svscan, it will not go out +of its way to stay alive; if it encounters an unsolvable situation, it will just +die.
  • +
  • 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).
  • +
  • s6-supervise does not use malloc(). That means it will never leak +memory. 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.
  • +
  • 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.
  • +
+ + + 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 @@ + + + + + s6: the s6-svc program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-svc program

+ +

+s6-svc sends commands to a running s6-supervise +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. +

+ +

Interface

+ +
+     s6-svc [ -abqhkti12fFpcoduxO ] servicedir
+
+ +

+s6-svc sends the given series of commands to the +s6-supervise process monitoring the +servicedir directory, then exits 0. It exits 111 if it cannot send +a command, or 100 if no s6-supervise process is running on servicedir. +

+ +

Options

+ +
    +
  • -a : send a SIGALRM to the supervised process
  • +
  • -b : send a SIGABRT to the supervised process
  • +
  • -q : send a SIGQUIT to the supervised process
  • +
  • -h : send a SIGHUP to the supervised process
  • +
  • -k : send a SIGKILL to the supervised process
  • +
  • -t : send a SIGTERM to the supervised process
  • +
  • -i : send a SIGINT to the supervised process
  • +
  • -1 : send a SIGUSR1 to the supervised process
  • +
  • -2 : send a SIGUSR2 to the supervised process
  • +
  • -p : send a SIGSTOP to the supervised process
  • +
  • -c : send a SIGCONT to the supervised process
  • +
  • -o : once. Equivalent to "-uO".
  • +
  • -d : down. If the supervised process is up, send it +a SIGTERM and a SIGCONT. Do not restart it.
  • +
  • -u : up. If the supervised process is down, start it. +Automatically restart it when it dies.
  • +
  • -x : 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.
  • +
  • -O : 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.
  • +
  • -f-F : unused for now.
  • +
+ +

Usage examples

+ +
 s6-svc -h /service/httpd 
+

+ Send a SIGHUP to the process represented by the /service/httpd +service directory. Traditionally, this makes web servers reload their +configuration file. +

+ +
 s6-svc -t /service/sshd 
+

+ Kill (and automatically restart, if the wanted state of the service is up) +the process represented by the /service/sshd service directory - +typically the sshd server. +

+ +
 s6-svc -d /service/ftpd 
+

+ Take down the ftpd server. +

+ +
 s6-svc -a /service/httpd/log 
+

+ Send a SIGALRM to the logger process for the httpd server. If this logger +process is s6-log, this triggers a log rotation. +

+ +

Internals

+ +

+s6-svc writes control commands into the servicedir/supervise/control +FIFO. A s6-supervise process running on servicedir will be listening to this FIFO, +and will read and interpret those commands. +

+ + + 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 @@ + + + + + s6: the s6-svok program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-svok program

+ +

+s6-svok checks whether a service directory is +currently supervised. +

+ +

Interface

+ +
+     s6-svok servicedir
+
+ +
    +
  • s6-svok checks whether a s6-supervise +process is currently monitoring fifodir.
  • +
  • It exits 0 if there is one, or 1 if there is none.
  • +
+ + + 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 @@ + + + + + s6: How to run s6-svscan as process 1 + + + + + + +

+s6
+Software
+skarnet.org +

+ +

How to run s6-svscan as process 1

+ +

+ It is possible to run s6-svscan as process 1, i.e. the init +process. However, that does not mean you can directly boot +on s6-svscan; that little program cannot do everything +your stock init does. Replacing the init process requires a +bit of understanding of what is going on. +

+ + +

The three stages of init

+
+ +

+ The life of a Unix machine has three stages: +

+ +
    +
  1. The early initialization phase. It starts when the +kernel launches the first userland process, traditionally called init. +During this phase, init is the only lasting process; its duty is to +prepare the machine for the start of other 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.
  2. +
  3. The cruising phase. This is the "normal", stable state of an +up and running Unix machine. Early work is done, and init launches and +maintains services, 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.
  4. +
  5. The shutdown 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 kill -9 -1, +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 kill -9 -1 on and until the final poweroff or reboot +command, is performed by process 1.
  6. +
+ +

+ As you can see, process 1's duties are radically different 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. +

+ +

+ Still, all common init systems insist that the same init +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! +

+ +

+Even the runit +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 +not run as process 1. (This requires very careful handling of the +kill -9 -1 part of stage 3, though.) +

+ +

+ One init to rule them all? +It ain't necessarily so! +

+ + +

The role of s6-svscan

+
+ +

+ init does not have the right to die, but fortunately, it has the right +to execve()! +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 +/etc/runit/[123] scripts, but exec'ing the scripts as process 1 +instead of forking them. +

+ +

+It becomes clear now that +s6-svscan is perfectly suited to +exactly fulfill process 1's role during stage 2. +

+ +
    +
  • It does not die
  • +
  • The reaper takes care of every zombie on the system
  • +
  • The scanner maintains services alive
  • +
  • It can be sent commands via the s6-svscanctl +interface
  • +
  • It execs into a given script when told to
  • +
+ +

+ 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. +

+ +

+ Here are some tips though. +

+ + +

How to design a stage 1 init

+
+ +

What stage 1 init must do

+ +
    +
  • Prepare an initial scan directory, say in +/service, 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.
  • +
  • Either perform all the one-time initialization, as stage 1 +runit does;
  • +
  • or fork a process that will perform most of the one-time initialization +once s6-svscan is in charge.
  • +
  • Be extremely simple and not fail, because recovery is almost impossible +here.
  • +
+ +

+ Unlike the /etc/runit/1 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 extremely +small, 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 init=/bin/sh anyway. +

+ +

+ 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 /dev/console 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. +

+ +

+ 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. +

+ +

Is it possible to write stage 1 init in a scripting language?

+ +

+ 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 /etc/runit/1 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. +

+ +

+ Of course, most people will use the shell as scripting +language; however, I advocate the use of +execline +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 tricky fifo handling that execline can do +and the shell cannot. +

+ + +

How to design a stage 3 init

+
+ +

+ If you're using s6-svscan as stage 2 init on /service, then +stage 3 init is naturally the /service/.s6-svscan/finish program. +Of course, /service/.s6-svscan/finish 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). +

+ +

What stage 3 init must do

+ +
    +
  • Destroy the supervision tree and stop all services
  • +
  • Kill all processes save itself, first gently, then harshly
  • +
  • Unmount all the filesystems
  • +
  • Halt or reboot the machine, depending on what root asked for
  • +
+ +

+ This is also very simple; even simpler than stage 1. + The only tricky part is the kill -9 -1 phase: you must make sure +that process 1 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, +/etc/runit/3. +

+ +

Is it possible to write stage 3 init in a scripting language?

+ +

+ You'd have to be a masochist, or have extremely specific needs, not to +do so. +

+ + +

How to log the supervision tree's messages

+
+ +

+ 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 +/dev/console. 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 +logging chain 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 /dev/console as stdin, but this is easy +enough to fix: s6-svscan will be started with stdin redirected from +/dev/null.) +

+ +

+ The catch-all logger is a service, and we want every +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. +

+ +

+ 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: +

+ +
    +
  • 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 init=/bin/sh anyway.
  • +
  • Normal Unix semantics do not allow a writer to open a +FIFO before there is a reader: if there is no reader when the FIFO is +opened for writing, the open() system call blocks +until a reader appears. This is obviously not what we want: we want +to be able to actually start 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.
  • +
+ +

+ 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. +

+ +

+ Instead, you are encouraged to use the +execline language - +or, at least, +the redirfd +command, which is part of the execline distribution. The +redirfd +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: redirfd -w 1 /service/s6-svscan-log/fifo blocks +if there's no process reading on /service/s6-svscan-log/fifo, but +redirfd -wnb 1 /service/s6-svscan-log/fifo does not. +

+ +

+ 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) once again +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. +

+ +

+ It really is simpler than it sounds. :-) +

+ +

A working example

+ +

+ 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. +

+ +

+ The examples/ROOT 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 README 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. +

+ + + 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 @@ + + + + + s6: How to run s6-svscan under another init process + + + + + + +

+s6
+Software
+skarnet.org +

+ +

How to run s6-svscan under another init process

+ +

+ You can have a reliable supervision tree even if s6-svscan is not your process 1. +The supervision tree just has to be rooted 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. +

+ +

+ 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. +

+ + +

Logging the supervision tree's output

+
+ +

+ 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: +

+ +
    +
  • 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.
  • +
  • You can use a trick similar to the process +1 output logging trick 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 +examples/s6-svscanboot file in the s6 distribution gives an example of +such a script. Make sure that your initial scan +directory contains a service directory for your +initial logging service, that must read on the logging FIFO.
  • +
+ +

+ In the following examples, we'll assume that /command/s6-svscanboot +is the name of the script you are using to start s6-svscan. Adjust this accordingly. +

+ + +

System V init

+
+ +

+ Put an appropriate line in your /etc/inittab file, then reload this +config file with telinit q. +

+ +

Example

+ +
 SV:123456:respawn:/command/s6-svscanboot 
+ + + +

Upstart

+
+ +

+ Put an appropriate configuration file in the /etc/init folder, +for instance /etc/init/s6-svscan.conf, then start the service +with start s6-svscan. +

+ +

Example

+ +
# s6-svscan
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+oom never
+respawn
+exec /command/s6-svscanboot
+
+ + +

systemd

+ +

+ systemd has +its +own way 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. +

+ +

+ +

+ + +

BSD init

+
+ +

+ Put an appropriate line in your /etc/ttys file, then reload this +file with kill -s HUP 1. +

+ +

Example

+ +
 sv /command/s6-svscanboot "" on 
+ + +

MacOS X launchd

+
+ +

+ Like systemd, launchd comes with its own +way +of supervising services; if you are a launchd user, you probably do +not need s6. +

+ + + + 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 @@ + + + + + s6: the s6-svscan program + + + + + + +

+s6
+Software
+www.skarnet.org +

+ +

The s6-svscan program

+ +

+s6-svscan starts and monitors a collection of s6-supervise +processes, each of these processes monitoring a single service. It is designed to be either +the root or a branch of a supervision tree. +

+ +

Interface

+ +
+     s6-svscan [ -c max ] [ -t rescan ] [ scandir ]
+
+ +
    +
  • If given a scandir argument, s6-svscan switches to it. Else it uses +its current directory as scan directory.
  • +
  • It exits 100 if another s6-svscan process is already monitoring this +scan directory.
  • +
  • If the ./.s6-svscan control directory does not exist, +s6-svscan creates it. However, it is recommended to already have a .s6-svscan +subdirectory in your scan directory, because s6-svscan may try and execute into the +.s6-svscan/crash or .s6-svscan/finish files at some point - so those +files should exist and be executable.
  • +
  • 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 +.s6-svscan/crash program. (But if that execution fails, s6-svscan exits +111.)
  • +
  • s6-svscan performs an initial scan of its scan directory.
  • +
  • s6-svscan then occasionally runs scans or reaps, +see below.
  • +
  • s6-svscan runs until it is told to stop via +s6-svscanctl, or a signal. +Then it executes into the .s6-svscan/finish program. The program is +given an argument that depends on the s6-svscanctl options that were used.
  • +
  • If that execution fails, s6-svscan falls back on a .s6-svscan/crash +execution.
  • +
+ +

Options

+ +
    +
  • -c max : maintain services for up to max +service directories. Default is 500. Lower limit is 2. There is no upper limit, but: +
      +
    • The higher max is, the more stack memory s6-svscan will use, +approximately 50 bytes per service.
    • +
    • s6-svscan uses 2 file descriptors per logged service.
    • +
    + 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.
  • +
  • -t rescan : perform a scan every rescan +milliseconds. If rescan is 0, automatic scans are never performed after +the first one and s6-svscan will only detect new services when told to via a +s6-svscanctl -a command. The default rescan +value is 5000, for compatibility with daemontools' +svscan, which performs a +scan (and a reap) every 5 seconds. It is strongly discouraged to set +rescan to a positive value under 500.
  • +
+ +

Signals

+ +

+ s6-svscan reacts to the following signals: +

+ +
    +
  • SIGCHLD : triggers the reaper.
  • +
  • SIGALRM : triggers the scanner.
  • +
  • SIGTERM : acts as if a s6-svscanctl -t command had been received.
  • +
  • SIGHUP : acts as if a s6-svscanctl -h command had been received.
  • +
  • SIGQUIT : acts as if a s6-svscanctl -q command had been received.
  • +
  • SIGABRT : acts as if a s6-svscanctl -b command had been received.
  • +
  • SIGINT : acts as if a s6-svscanctl -i command had been received.
  • +
+ +

The reaper

+ +

+ Upon receipt of a SIGCHLD, or a s6-svscanctl -z +command, s6-svscan runs a reaper routine. +

+ +

+The reaper acknowledges (via some +wait() +function), without blocking, every terminated child of s6-svscan, even ones it does not +know it has. This is especially important when s6-svscan is +run as process 1. +

+ +

+ If the dead child is a s6-supervise process watched +by s6-svscan, and the last scan flagged that process as active, then it is restarted +one second later. +

+ +

The scanner

+ +

+ Every rescan milliseconds, or upon receipt of a SIGALRM or a +s6-svscanctl -a command, s6-svscan runs a +scanner routine. +

+ +

+ The scanner scans the current directory for subdirectories (or symbolic links +to directories), which must be service directories. +It skips names starting with dots. It will not create services for more than +max subdirectories. +

+ +

+ For every new subdirectory dir it finds, the scanner spawns a +s6-supervise process on it. If +dir/log exists, it spawns a s6-supervise process on +both dir and dir/log, and maintains a +never-closing pipe from the service's stdout to the logger's stdin. +This is starting the service, with or without a corresponding +logger. +Every service the scanner finds is flagged as "active". +

+ +

+ 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 s6-svscanctl -n), but inactive +s6-supervise processes will not be restarted if they die. +

+ +

Implementation notes

+ +
    +
  • s6-svscan is designed to run until the machine is shut down. It is +also designed as a suitable candidate for +process 1. 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 .s6-svscan/crash +instead of dying; when it is told to exit, it executes into +.s6-svscan/finish. Administrators should make sure to design +appropriate crash and finish routines.
  • +
  • s6-svscan is a fully asynchronous state machine. It will read and +process commands at any time, even when the computer is in trouble.
  • +
  • s6-svscan does not use malloc(). That means it will never leak +memory. 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.
  • +
  • When run with the -t0 option, s6-svscan never polls, +it only wakes up on notifications, just like s6-supervise. The s6 supervision +tree can be used in energy-critical environments.
  • +
+ + + 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 @@ + + + + + s6: the s6-svscanctl program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-svscanctl program

+ +

+s6-svscanctl sends commands to a running s6-svscan +process. +

+ +

Interface

+ +
+     s6-svscanctl [ -phratszbnNiq0678 ] svscandir
+
+ +

+s6-svscanctl sends the given series of commands to the +s6-svscan process monitoring the +svscandir directory, then exits 0. It exits 111 if it cannot send +a command, or 100 if no s6-svscan process is running on svscandir. +

+ +

Options

+ +
    +
  • -p : poweroff mode. s6-svscan will exec into + ./.s6-svscan/finish poweroff when it is told to terminate.
  • +
  • -h : Hangup. s6-svscan will send a SIGHUP to all the +maintained s6-supervise processes, then run its finish procedure.
  • +
  • -r : reboot mode. s6-svscan will exec into + ./.s6-svscan/finish reboot when it is told to terminate. This +is s6-svscan's default mode.
  • +
  • -a : Alarm. s6-svscan will immediately perform a scan +of svscandir to check for services.
  • +
  • -t : 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.
  • +
  • -s : halt mode. s6-svscan will exec into + ./.s6-svscan/finish halt when it is told to terminate.
  • +
  • -z : destroy zombies. Immediately triggers s6-svscan's +reaper mechanism.
  • +
  • -b : abort. s6-svscan will exec into its finishing +procedure. It will not kill any of the maintained s6-supervise processes.
  • +
  • -n : nuke. s6-svscan will kill all the +s6-supervise processes it has launched but that did not match a service +directory last time svscandir was scanned, i.e. it prunes the +supervision tree so that it matches exactly what was in svscandir +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.
  • +
  • -N : Really nuke. Does the same thing as -n, +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.
  • +
  • -i : Interrupt. Equivalent to -rt : s6-svscan +will terminate in reboot mode.
  • +
  • -q : Quit. s6-svscan will send all its s6-supervise processes +a SIGTERM, then exec into its finish procedure.
  • +
  • -0 : Halt. Equivalent to -st : s6-svscan will +terminate in halt mode.
  • +
  • -6 : Reboot. Equivalent to -i.
  • +
  • -7 : Poweroff. Equivalent to -pt: s6-svscan will +terminate in poweroff mode.
  • +
  • -8 : Other. s6-svscan will terminate in "other" mode.
  • +
+ +

Usage examples

+ +
 s6-svscanctl -an /service 
+

+ Updates the process supervision tree +to exactly match the services listed in /service. +

+ +
 s6-svscanctl -6 /service 
+

+ Orders the s6-svscan process monitoring /service to exit in +reboot mode: all the supervision tree at /service will be terminated, +and s6-svscan will execute into the /service/.s6-svscan/finish +script with the reboot argument. +

+ +

Internals

+ +

+s6-svscanctl writes control commands into the svscandir/.s6-svscan/control +FIFO. A s6-svscan process running on svscandir will be listening to this FIFO, +and will read and interpret those commands. +

+ + + 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 @@ + + + + + s6: the s6-svstat program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-svstat program

+ +

+s6-svstat prints a short, human-readable summary of the state of a supervised +service. +

+ +

Interface

+ +
+     s6-svstat servicedir
+
+ +

+ s6-svstat gives the following information about the process being monitored +at the servicedir service directory, then +exits 0: +

+ +
    +
  • whether it is up or down
  • +
  • its pid, if it is up
  • +
  • what its default state is, if it is different from its current state
  • +
  • the number of seconds since it last changed states
  • +
+ + + 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 @@ + + + + + s6: the s6-svwait program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-svwait program

+ +

+s6-svwait blocks until a collection of supervised services goes up, or down. +

+ +

+s6-svwait only waits for notifications; it never polls. +

+ +

Interface

+ +
+     s6-svwait [ -U | -u | -d ] [ -a | -o ] [ -t timeout ] servicedir...
+
+ +

+s6-svwait monitors one or more service +directories given as its arguments, waiting for a state (up or down) to +happen. It exits 0 when the wanted condition becomes true. +

+ +

Options

+ +
    +
  • -u : 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 this page +for details.
  • +
  • -U : 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 this page.
  • +
  • -d : down. s6-svwait will wait until the services are down.
  • +
  • -o : or. s6-svwait will wait until one of the +given services comes up or down.
  • +
  • -a : and. s6-svwait will wait until all of the +given services come up or down. This is the default.
  • +
  • -t timeout : if the requested events have not +happened after timeout milliseconds, s6-svwait will print a message +to stderr and exit 1. By default, timeout is 0, which means no time +limit.
  • +
+ + +

Internals

+ +

+s6-svwait spawns a s6-ftrigrd child to +listen to notifications sent by s6-supervise. +It also checks supervise/status files to get the current service +states, so it is immune to race conditions. +

+ + + 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 @@ + + + + + s6: the s6-tai64n program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-tai64n program

+ +

+s6-tai64n acts as a filter, reading from stdin and writing to stdout. +It prepends lines with a +TAI64N +timestamp and a space. +

+ +

Interface

+ +
+     s6-tai64n
+
+ +
    +
  • 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.
  • +
+ +

Notes

+ +

+ s6-tai64n does neither "line buffering" nor "block buffering". It does +optimal buffering, 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. +

+ + + 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 @@ + + + + + s6: the s6-tai64nlocal program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The s6-tai64nlocal program

+ +

+s6-tai64nlocal acts as a filter, reading from stdin and writing to stdout. +For every line that begins with a +TAI64N +timestamp, it replaces this timestamp with a human-readable local date and +time. +

+ +

Interface

+ +
+     s6-tai64nlocal
+
+ +
    +
  • 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.
  • +
+ +

Notes

+ +
    +
  • The typical use case of s6-tai64nlocal is to read files that have +been filtered through s6-tai64n, or log files +that have been produced by s6-log with the -t +option. For instance, to read the latest httpd logs with human-readable +timestamps, s6-tai64nlocal < /var/log/httpd/current | less is a +possible command.
  • +
  • s6-tai64nlocal does neither "line buffering" nor "block buffering". It does +optimal buffering, i.e. it flushes its output buffer every time +it risks blocking on input.
  • +
+ +

Troubleshooting

+ +

+ If s6-tai64nlocal does not appear to give the correct local time: +

+ +
    +
  • Check the compilation options that were used for the +skalibs libraries +your s6-tai64nlocal program was linked against. In particular, check whether the +--enable-tai-clock or --enable-right-tz configure options +have been given.
  • +
  • Compare these flags and their meanings with your current timezone. In particular, +check /etc/localtime, /etc/timezone, /etc/TZ, and the TZ +environment variable.
  • +
  • Check that you have a correct and recent version of /etc/leapsecs.dat.
  • +
+ + + 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 @@ + + + + + s6: scan directories + + + + + + +

+s6
+Software
+skarnet.org +

+ +

Scan directories

+ +

+ A scan directory is a directory containing a list of +service directories, or symbolic links +pointing to service directories. +

+ +

+ A scan directory represents a list of services that are supposed to +be supervised. Running s6-svscan on this +scan directory launches a supervision tree: every service +listed in the scan directory will be supervised. +

+ +

+ There is normally only one scan directory per system, although nothing +prevents a system administrator from having more. +daemontools traditionally +uses /service, and runit +traditionally uses /etc/service. s6 does not care where your +scan directory is, but I would advise /service for compatibility +with daemontools. Depending on your installation, /service could +be a symbolic link and point to a place either in a RAM filesystem or in +/var. +

+ + +

Where and how to build a scan directory

+
+ +

+ Opinions and practices differ. +

+ +

+ It is generally accepted that the place where you store all your +service directories (your "service repository") should not 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 subset +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 service repository, which is just a storage place +for all the services you might want to manage someday, and your scan +directory, which is a directory representing all the services that +you are currently managing. +

+ +

Service repository

+ +

+ Where to store your service repository is purely a matter of personal +preference. You just have to be aware that +s6-supervise needs writable supervise and event +subdirectories in a service directory it monitors. +

+ +

Scan directory

+ +

+ Where and how to build your scan directory depends heavily on your boot +system - and on your personal preference too. +

+ +

+ Standard daemontools and +runit 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 working copies of the service directories. This works, +as long as: +

+ +
    +
  • It is possible to create writable supervise and event +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.
  • +
  • The scan program (s6-svscan, +svscan, +runsvdir...) is +started late enough for all the necessary filesystems to be mounted.
  • +
+ +

+ My own recommendation would be to have working copies of the service +directories entirely separate 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 s6-svscan -t0: copying a +directory is not atomic, but making a symlink is, so there is no risk +of your scanner finding an incomplete directory.) +

+ +

+ An example: +

+ +
    +
  • Have your service repository in /img/services, i.e. have +service directories in /img/services/ftpd, /img/services/httpd, +/img/services/sshd, etc.
  • +
  • When booting, make /tmp a RAM filesystem, and create the +directories /tmp/services and /tmp/service.
  • +
  • Have s6-svscan run on /tmp/service, as early as possible in your +boot sequence. This is possible whether you want to run s6-svscan +as process 1 or not.
  • +
  • During the boot sequence, populate /tmp/services with copies of the +service directories you need: for instance, +
      +
    • cp -a /img/services/sshd /tmp/services/sshd
    • +
    • cp -a /img/services/ftpd /tmp/services/ftpd
    • +
    • etc.
    • +
  • +
  • When you are ready to start a service, make a symlink in the +/tmp/service scan directory pointing to the working copy of +the service directory you need in /tmp/services, then notify s6-svscan. +For instance, to start ftpd and httpd together: +
     ln -s ../services/ftpd /tmp/service
    + ln -s ../services/httpd /tmp/service
    + s6-svscanctl -a /tmp/service
  • +
+ + + 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 @@ + + + + + s6: service directories + + + + + + +

+s6
+Software
+skarnet.org +

+ +

Service directories

+ +

+ A service directory is a directory containing all the information +related to a service, i.e. a long-running process maintained and +supervised by s6-supervise. +

+ +

+ (Strictly speaking, a service is not always equivalent to a +long-running process. Things like Ethernet interfaces fit the definition +of services one may want to supervise; however, s6 does not +provide service supervision; it provides process supervision, +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 service and service directory +for historical and compatibility reasons.) +

+ +

Contents

+ + A service directory foo may contain the following elements: + +
    +
  • An executable file named run. 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 run script. +This file is the most important one in your service directory: it +contains the commands that will setup and run your foo service. +It is forked and executed by s6-supervise +every time the service must be started, i.e. normally when +s6-supervise starts, and whenever +the service goes down when it is supposed to be up. A run script +should normally: +
      +
    • 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 fdmove +-c 2 1 in execline, +and exec 2>&1 in shell. +By default, in a normal supervision tree situation, a run script's stdin will +be /dev/null, and its stdout and stderr will both be a pipe to a +catch-all logging program.
    • +
    • adjust the environment for your foo daemon. Normally the run script +inherits its environment from s6-supervise, +which normally inherits its environment from s6-svscan, +which normally inherits a minimal environment from the boot scripts. +Service-specific environment variables should be set in the run script.
    • +
    • adjust other parameters for the foo daemon, such as its +uid and gid. Normally the supervision tree, i.e. +s6-svscan and the various +s6-supervise 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 +s6-setuidgid utility in foo/run +to lose privileges before executing into foo's long-lived +process; or the s6-envuidgid utility if +your long-lived process needs root privileges at start time but can drop +them afterwards.
    • +
    • execute into the long-lived process that is to be supervised by +s6-supervise, i.e. the real foo +daemon. That process must not "background itself": being run by a supervision +tree already makes it a "background" task.
    • +
    +
  • An optional executable file named finish. Like run, +it can be any executable file. This finish script, if present, +is executed everytime the run 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 foo service is supposed to be up, +foo/run is restarted +after foo/finish 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.)
  • +
  • A directory named supervise. It is automatically created by +s6-supervise if it does not exist. This is where +s6-supervise stores its information. The directory +must be writable.
  • +
  • An optional, empty, regular file named down. 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 s6-svc -u command. If no +down file exists, the default state of the service is up.
  • +
  • An optional, empty, regular file named nosetsid. 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 nosetsid file +exists, the service has its own process group and is started as a session leader.
  • +
  • A fifodir named event. It is automatically +created by s6-supervise if it does not exist. +foo/event +is the rendez-vous point for listeners, where s6-supervise +will send notifications when the service goes up or down.
  • +
  • An optional service directory named log. If it exists and foo +is in a scandir, and s6-svscan +runs on that scandir, then two services are monitored: foo and +foo/log. A pipe is open and maintained between foo and +foo/log, i.e. everything that foo/run +writes to its stdout will appear on foo/log/run's stdin. The foo +service is said to be logged; the foo/log service is called +foo's logger. A logger service cannot be logged: if +foo/log/log exists, nothing special happens.
  • +
+ + +

Where to store my service directories ?

+
+ +

+ 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. +

+ +

+ However, two subdirectories - namely supervise and event - +of every service directory need to be writable. So it has to be a bit more +complex. Here are a few possibilities. +

+ +
    +
  • The laziest option: you're not using s6-svscan +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 scan +directory on some read-write filesystem such as /var; and you +tell your init system to launch (and, if possible, maintain) s6-svscan on +the scan directory after that filesystem is mounted.
  • +
  • The almost-as-lazy option: just have the service directories on the +root filesystem. Then your service directory collection is for instance in +/etc/services and you have a /service +scan directory 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.
  • +
  • Some people like to have +their service directories in a read-only filesystem, with supervise +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.
  • +
  • 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 copy it all +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: +
      +
    • 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.
    • +
    • 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.
    • +
    • No dangling symlinks or potential problems with unmounted +filesystems: this setup is robust. A simple /bin/cp -a or +tar -x is all it takes to get a working service infrastructure.
    • +
    • 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 +supervise and event subdirectories. No stale files can +make your system unstable.
    • +
  • +
+ + + 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 @@ + + + + + s6: a word about systemd + + + + + + +

+s6
+Software
+skarnet.org +

+ +

A word about systemd

+ +

+ systemd +is becoming de facto 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. + +

+ 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. +

+ +

+ + The single, overarching problem with systemd is that it attempts, in every +possible way, to do more instead of less. + +

+ +

The political issue

+ +

+ systemd attempts to cover more ground instead of less. +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: +

+ +
    +
  • The Unix philosophy, which is to do one job and do it well;
  • +
  • The bazaar +approach that has made the free software ecosystem what it is today;
  • +
  • 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!
  • +
+ +

+ 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. +

+ +

+ Which says something. +

+ +

The technical issue

+ +

+ Software that does more instead of less 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 nothing 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 ? +

+ +

+ 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 applications, +and system software should always partake in that goal. System software +should stay the heck out of the way, 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. +

+ +

Links

+ + + + + 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 @@ + + + + + s6: the ucspilogd program + + + + + + +

+s6
+Software
+skarnet.org +

+ +

The ucspilogd program

+ +

+ ucspilogd acts as a filter, converting syslog facility +numbers and alert levels into names. +

+ +

Interface

+ +
+     ucspilogd [ var ... ]
+
+ +
    +
  • ucspilogd reads a stream of syslog-like messages on stdin. +Those messages can be newline-terminated or null-terminated.
  • +
  • For every line it reads: if it has been given var +arguments, it writes the value of every var environment +variable, followed by a colon and a space.
  • +
  • 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.
  • +
  • It then writes the processed line to stdout.
  • +
+ +

Common use

+ +

+ You can emulate the whole syslogd behaviour by combining the following +components: +

+ +
    +
  • A Unix stream super-server such as +s6-ipcserver +listening to the Unix domain socket /dev/log, to connect to +the kernel log-reading interface.
  • +
  • ucspilogd running under that super-server, to read the +logs and perform adequate transformations.
  • +
  • A logger such as +s6-log +to store the logs into the filesystem.
  • +
  • A supervision mechanism such as s6, +to ensure ease of use and reliability of the whole chain.
  • +
+ +

+ The resulting suite of programs is still smaller, and way more reliable, +than a standard syslogd. +

+ +

+ In the examples/ROOT/img/services-local/syslogd-linux subdirectory of the s6 package, you will +find a suitable ucspilogd service directory. +The run scripts are written in the +execline +language. +

+ +

Using ucspilogd as a klogd replacement

+ +

+ Certain Unix kernels offer a nice interface to the kernel logs. +For instance, the Linux kernel provides the /proc/kmsg 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 ucspilogd to process data from those interfaces. +

+ +

+ The examples/ROOT/img/services-local/klogd-linux subdirectory of the s6 package +is a service directory providing such a klogd service +for Linux, using the /proc/kmsg interface. +

+ + + 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 @@ + + + + + How to upgrade s6 + + + + + + +

+s6
+Software
+skarnet.org +

+ +

How to upgrade s6

+ +

to 2.0.0.0

+ +
    +
  • The build system has completely changed. It is now a standard +./configure && make && sudo make install +build system. See the enclosed INSTALL file for details.
  • +
  • slashpackage is not activated by default.
  • +
  • shared libraries are not used by default.
  • +
  • skalibs dependency bumped to 2.0.0.0
  • +
+ + + 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 @@ + + + + + s6: why another supervision suite + + + + + + +

+s6
+Software
+skarnet.org +

+ +

Why another supervision suite ?

+ +

+ Supervision suites are becoming quite common. Today, we already have: +

+ +
    +
  • Good (?) old System V init, which can be made to supervise services if you perform /etc/inittab voodoo. +BSD init can also be used the same way with the /etc/ttys file, but for some reason, nobody among BSD +developers is using /etc/ttys to this purpose, so I won't consider BSD init here.
  • +
  • daemontools, the pioneer
  • +
  • daemontools-encore, Bruce Guenter's upgrade to daemontools
  • +
  • runit, Gerrit Pape's suite, well-integrated with Debian
  • +
  • perp, Wayne Marshall's take on supervision
  • +
  • Integrated init systems providing a lot of features, process supervision being one of them. +For instance, Upstart, MacOS X's +launchd, +and Fedora's systemd.
  • +
+ +

+ Why is s6 needed ? What does it do differently ? Here are the criteria I used. +

+ + +

Supervision suites should not wake up unless notified.

+ +
    +
  • System V init fails the test: it wakes up every 5 seconds, for the reason that +/dev/initctl might have changed. +m(
  • +
  • daemontools fails the test: it wakes up every 5 seconds to check for new services.
  • +
  • daemontools-encore does the same.
  • +
  • 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.
  • +
  • perp works.
  • +
  • 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.
  • +
  • s6 works. By default, s6-svscan wakes up every 5 seconds, to emulate +svscan behaviour; but it +can be told not to do so. (s6-svscan -t0)
  • +
+ + +

Supervision suites should provide a program that can run as process 1.

+ +
    +
  • System V init is process 1, so no problem here.
  • +
  • Integrated init systems, by definition, provide a process 1.
  • +
  • daemontools was not designed to take over init, although +it can be made to work with +enough hacking skills. Same thing with daemontools-encore.
  • +
  • runit provides an init functionality, but the mechanism is +separate from the supervision itself; the runit process, not the +runsvdir process, runs as process 1. This lengthens the supervision +chain.
  • +
  • perp was not designed to run as process 1. It probably could be made to work too +without too much trouble.
  • +
  • s6-svscan was designed from the start to be run as process 1, although it +does not have to.
  • +
+ + +

Supervision suites should be bug-free, lightweight and easy to understand.

+ +
    +
  • daemontools, daemontools-encore, runit and perp all qualify. All of this is excellent quality +code, unsurprisingly.
  • +
  • System V init is understandable, and reasonably lightweight; but it is still +too big for what it does - poorly. The /etc/inittab 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.
  • +
  • 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 +same package as the init and process supervision system, they add +incredible complexity where it does not belong. +
      +
    • Upstart uses ptrace to watch its children fork(), and links +process 1 against libdbus. This is insane. +Process 1 should be absolutely stable, 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.
    • +
    • 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 +using +XML. That means there is an XML parser in process 1. +What +could possibly go wrong ?
    • +
    • systemd is much, much worse than the other ones, and a real danger +for the future of GNU/Linux. I have a special page +dedicated to it.
    • +
    + 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 +those are two different functions, and a good init system +needs, and should, 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 on top of a process supervision +suite, in a different package, and it has nothing to do with process 1.
  • +
  • s6, which has been designed with embedded environments in mind, tries +harder than anyone to pass this. It tries so hard that s6-svscan +and s6-supervise, the two long-running programs that make the +supervision chain, do not even allocate heap memory, and their main +program source files are less than 500 lines long.
  • +
+ + +

Supervision suites should provide a basis for high-level service management.

+ +
    +
  • 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 ./check script +has to be manually written for every service.
  • +
  • daemontools-encore qualifies: the notify script 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.
  • +
  • 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.
  • +
  • s6 comes with libftrig, 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.
  • +
+ + +

Artistic considerations

+ +
    +
  • s6-svscan and s6-supervise are entirely asynchronous. +Even during trouble (full process table, for instance), they'll remain reactive +and instantly respond to commands they may receive. s6-supervise 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.
  • +
  • daemontools' svscan +maintains an open pipe between a daemon and its logger, so even if the daemon, +the logger, and both +supervise processes +die, the pipe is still the same so no logs are lost, ever, unless +svscan itself dies.
  • +
  • runit has only one supervisor, runsv, +for both a daemon and its logger. The pipe is maintained by runsv. +If the runsv process dies, the pipe disappears and logs are lost. +So, runit does not offer as strong a guarantee as daemontools.
  • +
  • perp has only one process, perpd, +acting both as a "daemon and logger supervisor" (like runsv) and as a +"service directory scanner" (like runsvdir). 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.
  • +
  • Besides, the runsv model, which has to handle both a daemon +and its logger, is more complex than the supervise model (which +only has to handle a daemon). Consequently, the runsvdir model is +simpler than the svscan model, but there is only one svscan +instance when there are several runsvs and supervises. +The perpd model is obviously the most complex; while very understandable, +perpd is unarguably harder to maintain than the other two.
  • +
  • So, to achieve maximum simplicity and code reuse, and minimal memory +footprint, s6's design is close to daemontools' one. +And when s6-svscan is run as process 1, +pipes between daemons and loggers are never lost.
  • +
+ + +

Conclusion

+ +

+ All in all, I believe that s6 offers the best overall implementation of a +supervision suite as it should be designed. At worst, it's just another +take on daemontools with a reliable +base library and a few nifty features. +

+ + + 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 +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include /* for rename() */ +#include /* for qsort() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = "" ; + 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 +#include +#include +#include +#include +#include +#include +#include + + + /* 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 + +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + + + /* 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 + +#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 +#include +#include +#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 +#include +#include +#include /* for rename() */ +#include +#include +#include +#include +#include +#include +#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 + +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 +#include +#include +#include + +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 +#include +#include +#include + +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 +#include +#include + +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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include + +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 + +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 +#include +#include +#include +#include +#include "ftrig1.h" +#include + +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 +#include +#include +#include +#include + +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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include "ftrig1.h" +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ftrig1.h" +#include + +#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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +#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 +#include +#include +#include + +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 +#include +#include + +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 +#include +#include + +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 +#include +#include + +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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +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 + +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 +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 -- cgit v1.3.1