summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2014-12-05 22:26:11 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2014-12-05 22:26:11 +0000
commit90b12bd71bb9fc79a4640b9112c13ef529d0196a (patch)
tree523b3f4ee2969e7a729bab2ba749c4b924ae62af
downloads6-90b12bd71bb9fc79a4640b9112c13ef529d0196a.tar.gz
s6-90b12bd71bb9fc79a4640b9112c13ef529d0196a.tar.xz
s6-90b12bd71bb9fc79a4640b9112c13ef529d0196a.zip
Initial commit
-rw-r--r--.gitignore5
-rw-r--r--AUTHORS17
-rw-r--r--COPYING13
-rw-r--r--INSTALL138
-rw-r--r--Makefile121
-rw-r--r--README23
-rw-r--r--README.macosx4
-rw-r--r--README.solaris12
-rwxr-xr-xconfigure385
-rw-r--r--doc/fifodir.html123
-rw-r--r--doc/index.html291
-rw-r--r--doc/libftrig.html184
-rw-r--r--doc/libftrigr.html283
-rw-r--r--doc/libftrigw.html115
-rw-r--r--doc/libs6lock/index.html256
-rw-r--r--doc/libs6lock/s6lockd-helper.html52
-rw-r--r--doc/libs6lock/s6lockd.html73
-rw-r--r--doc/notifywhenup.html75
-rw-r--r--doc/s6-cleanfifodir.html42
-rw-r--r--doc/s6-envdir.html66
-rw-r--r--doc/s6-envuidgid.html64
-rw-r--r--doc/s6-fghack.html50
-rw-r--r--doc/s6-ftrig-listen.html73
-rw-r--r--doc/s6-ftrig-listen1.html88
-rw-r--r--doc/s6-ftrig-notify.html44
-rw-r--r--doc/s6-ftrig-wait.html51
-rw-r--r--doc/s6-ftrigrd.html73
-rw-r--r--doc/s6-log.html508
-rw-r--r--doc/s6-mkfifodir.html46
-rw-r--r--doc/s6-notifywhenup.html80
-rw-r--r--doc/s6-setlock.html65
-rw-r--r--doc/s6-setsid.html47
-rw-r--r--doc/s6-setuidgid.html49
-rw-r--r--doc/s6-softlimit.html54
-rw-r--r--doc/s6-supervise.html125
-rw-r--r--doc/s6-svc.html104
-rw-r--r--doc/s6-svok.html38
-rw-r--r--doc/s6-svscan-1.html374
-rw-r--r--doc/s6-svscan-not-1.html140
-rw-r--r--doc/s6-svscan.html176
-rw-r--r--doc/s6-svscanctl.html108
-rw-r--r--doc/s6-svstat.html45
-rw-r--r--doc/s6-svwait.html73
-rw-r--r--doc/s6-tai64n.html50
-rw-r--r--doc/s6-tai64nlocal.html73
-rw-r--r--doc/scandir.html145
-rw-r--r--doc/servicedir.html187
-rw-r--r--doc/systemd.html120
-rw-r--r--doc/ucspilogd.html94
-rw-r--r--doc/upgrade.html32
-rw-r--r--doc/why.html203
-rw-r--r--package/deps-build2
-rw-r--r--package/deps.mak101
-rw-r--r--package/info4
-rw-r--r--package/modes28
-rw-r--r--package/targets.mak36
-rwxr-xr-xpatch-for-solaris17
-rw-r--r--src/daemontools-extras/deps-exe/s6-envdir1
-rw-r--r--src/daemontools-extras/deps-exe/s6-envuidgid1
-rw-r--r--src/daemontools-extras/deps-exe/s6-fghack1
-rw-r--r--src/daemontools-extras/deps-exe/s6-log2
-rw-r--r--src/daemontools-extras/deps-exe/s6-notifywhenup3
-rw-r--r--src/daemontools-extras/deps-exe/s6-setlock2
-rw-r--r--src/daemontools-extras/deps-exe/s6-setsid1
-rw-r--r--src/daemontools-extras/deps-exe/s6-setuidgid1
-rw-r--r--src/daemontools-extras/deps-exe/s6-softlimit1
-rw-r--r--src/daemontools-extras/deps-exe/s6-tai64n2
-rw-r--r--src/daemontools-extras/deps-exe/s6-tai64nlocal1
-rw-r--r--src/daemontools-extras/deps-exe/ucspilogd1
-rw-r--r--src/daemontools-extras/s6-envdir.c40
-rw-r--r--src/daemontools-extras/s6-envuidgid.c44
-rw-r--r--src/daemontools-extras/s6-fghack.c68
-rw-r--r--src/daemontools-extras/s6-log.c1265
-rw-r--r--src/daemontools-extras/s6-notifywhenup.c86
-rw-r--r--src/daemontools-extras/s6-setlock.c87
-rw-r--r--src/daemontools-extras/s6-setsid.c35
-rw-r--r--src/daemontools-extras/s6-setuidgid.c30
-rw-r--r--src/daemontools-extras/s6-softlimit.c117
-rw-r--r--src/daemontools-extras/s6-tai64n.c35
-rw-r--r--src/daemontools-extras/s6-tai64nlocal.c47
-rw-r--r--src/daemontools-extras/ucspilogd.c117
-rw-r--r--src/include/s6/ftrigr.h94
-rw-r--r--src/include/s6/ftrigw.h11
-rw-r--r--src/include/s6/s6-supervise.h40
-rw-r--r--src/include/s6/s6.h11
-rw-r--r--src/include/s6/s6lock.h75
-rwxr-xr-xsrc/libs6/deps-exe/s6-ftrigrd5
-rwxr-xr-xsrc/libs6/deps-exe/s6lockd3
-rwxr-xr-xsrc/libs6/deps-exe/s6lockd-helper1
-rwxr-xr-xsrc/libs6/deps-lib/s633
-rw-r--r--src/libs6/ftrig1.h23
-rw-r--r--src/libs6/ftrig1_free.c25
-rw-r--r--src/libs6/ftrig1_make.c65
-rw-r--r--src/libs6/ftrigr1_zero.c5
-rw-r--r--src/libs6/ftrigr_check.c40
-rw-r--r--src/libs6/ftrigr_end.c14
-rw-r--r--src/libs6/ftrigr_start.c10
-rw-r--r--src/libs6/ftrigr_startf.c12
-rw-r--r--src/libs6/ftrigr_subscribe.c43
-rw-r--r--src/libs6/ftrigr_unsubscribe.c36
-rw-r--r--src/libs6/ftrigr_update.c43
-rw-r--r--src/libs6/ftrigr_wait_and.c28
-rw-r--r--src/libs6/ftrigr_wait_or.c31
-rw-r--r--src/libs6/ftrigr_zero.c5
-rw-r--r--src/libs6/ftrigw_clean.c39
-rw-r--r--src/libs6/ftrigw_fifodir_make.c26
-rw-r--r--src/libs6/ftrigw_notify.c8
-rw-r--r--src/libs6/ftrigw_notifyb.c67
-rw-r--r--src/libs6/s6-ftrigrd.c266
-rw-r--r--src/libs6/s6_supervise_lock.c10
-rw-r--r--src/libs6/s6_supervise_lock_mode.c54
-rw-r--r--src/libs6/s6_svc_main.c40
-rw-r--r--src/libs6/s6_svc_write.c22
-rw-r--r--src/libs6/s6_svstatus_pack.c13
-rw-r--r--src/libs6/s6_svstatus_read.c17
-rw-r--r--src/libs6/s6_svstatus_unpack.c29
-rw-r--r--src/libs6/s6_svstatus_write.c16
-rw-r--r--src/libs6/s6lock_acquire.c38
-rw-r--r--src/libs6/s6lock_check.c25
-rw-r--r--src/libs6/s6lock_end.c14
-rw-r--r--src/libs6/s6lock_release.c28
-rw-r--r--src/libs6/s6lock_start.c12
-rw-r--r--src/libs6/s6lock_startf.c14
-rw-r--r--src/libs6/s6lock_update.c31
-rw-r--r--src/libs6/s6lock_wait_and.c27
-rw-r--r--src/libs6/s6lock_wait_or.c30
-rw-r--r--src/libs6/s6lock_zero.c5
-rw-r--r--src/libs6/s6lockd-helper.c27
-rw-r--r--src/libs6/s6lockd.c316
-rw-r--r--src/pipe-tools/deps-exe/s6-cleanfifodir2
-rw-r--r--src/pipe-tools/deps-exe/s6-ftrig-listen4
-rw-r--r--src/pipe-tools/deps-exe/s6-ftrig-listen13
-rw-r--r--src/pipe-tools/deps-exe/s6-ftrig-notify2
-rw-r--r--src/pipe-tools/deps-exe/s6-ftrig-wait3
-rw-r--r--src/pipe-tools/deps-exe/s6-mkfifodir2
-rw-r--r--src/pipe-tools/s6-cleanfifodir.c15
-rw-r--r--src/pipe-tools/s6-ftrig-listen.c122
-rw-r--r--src/pipe-tools/s6-ftrig-listen1.c101
-rw-r--r--src/pipe-tools/s6-ftrig-notify.c20
-rw-r--r--src/pipe-tools/s6-ftrig-wait.c48
-rw-r--r--src/pipe-tools/s6-mkfifodir.c39
-rw-r--r--src/supervision/deps-exe/s6-supervise3
-rw-r--r--src/supervision/deps-exe/s6-svc2
-rw-r--r--src/supervision/deps-exe/s6-svok1
-rw-r--r--src/supervision/deps-exe/s6-svscan3
-rw-r--r--src/supervision/deps-exe/s6-svscanctl2
-rw-r--r--src/supervision/deps-exe/s6-svstat3
-rw-r--r--src/supervision/deps-exe/s6-svwait3
-rw-r--r--src/supervision/s6-supervise.c508
-rw-r--r--src/supervision/s6-svc.c12
-rw-r--r--src/supervision/s6-svok.c32
-rw-r--r--src/supervision/s6-svscan.c498
-rw-r--r--src/supervision/s6-svscanctl.c12
-rw-r--r--src/supervision/s6-svstat.c70
-rw-r--r--src/supervision/s6-svwait.c104
-rwxr-xr-xtools/gen-deps.sh79
-rwxr-xr-xtools/install.sh64
157 files changed, 11417 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5c6415e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.o
+*.a
+*.lo
+*.so
+*.so.*
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 <ska-skaware@skarnet.org>
+
+Contributors:
+  Paul Jarc <prj@dogmap.org>
+  Gerrit Pape <pape@smarden.org>
+  Clemens Fischer <ino-waiting@gmx.net>
+  Stefan Karrman <sk@mathematik.uni-ulm.de>
+  Jean Marot <marot@quatramaran.ens.fr>
+
+Thanks to:
+  Dan J. Bernstein <djb@cr.yp.to>
+  Wayne Marshall <wcm@b0llix.net>
+  David Madore <david@madore.org>
+  Nicolas George <cigaes@salle-s.org>
+  Frans Haarman <franshaarman@gmail.com>
+  Vallo Kallaste <kalts@estpak.ee>
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 <ska-skaware@skarnet.org>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
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/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so: $(DESTDIR)$(dynlibdir)/lib%.so
+	exec $(INSTALL) -D -l ..$(exthome)/library.so/$(<F) $@
+
+.PHONY: update global-links
+
+endif
+
+$(DESTDIR)$(dynlibdir)/lib%.so: lib%.so
+	$(INSTALL) -D -m 755 $< $@.$(version) && \
+	$(INSTALL) -l $<.$(version) $@.$(version_m) && \
+	$(INSTALL) -l $<.$(version_m) $@.$(version_M) && \
+	$(INSTALL) -l $<.$(version_M) $@.$(version_l) && \
+	exec $(INSTALL) -l $<.$(version_l) $@
+
+$(DESTDIR)$(libexecdir)/% $(DESTDIR)$(bindir)/% $(DESTDIR)$(sbindir)/%: % package/modes
+	exec $(INSTALL) -D -m 600 $< $@
+	grep -- ^$(@F) < package/modes | { read name mode owner && \
+	if [ x$$owner != x ] ; then chown -- $$owner $@ ; fi && \
+	chmod $$mode $@ ; }
+
+$(DESTDIR)$(libdir)/lib%.a: lib%.a
+	$(INSTALL) -D -m 644 $< $@
+
+$(DESTDIR)$(includedir)/$(package)/%.h: src/include/$(package)/%.h
+	exec $(INSTALL) -D -m 644 $< $@
+
+%.o: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) -c -o $@ $<
+
+%.lo: %.c
+	exec $(REALCC) $(CPPFLAGS_ALL) $(CFLAGS_ALL) $(CFLAGS_SHARED) -c -o $@ $<
+
+$(ALL_BINS):
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(LDFLAGS_ALL) $(LDFLAGS_NOSHARED) $^ $(LDLIBS_ALL)
+
+lib%.a:
+	exec $(AR) rc $@ $^
+	exec $(RANLIB) $@
+
+lib%.so:
+	exec $(REALCC) -o $@ $(CFLAGS_ALL) $(CFLAGS_SHARED) $(LDFLAGS_ALL) $(LDFLAGS_SHARED) -Wl,-soname,$@.$(version_l) $^
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-sbin install-lib install-include
+
+.DELETE_ON_ERROR:
diff --git a/README b/README
new file mode 100644
index 0000000..c0476fa
--- /dev/null
+++ b/README
@@ -0,0 +1,23 @@
+s6 - a process supervision suite
+--------------------------------
+
+ s6 is a suite of programs designed to allow process
+supervision and management. It can also be used as the
+foundation for a complete init system.
+
+ See http://skarnet.org/software/s6/ for details.
+
+
+* Installation
+  ------------
+
+ See the INSTALL file.
+
+
+* Contact information
+  -------------------
+
+ Laurent Bercot <ska-skaware at skarnet.org>
+
+ Please use the <skaware at list.skarnet.org> mailing-list for
+questions about s6.
diff --git a/README.macosx b/README.macosx
new file mode 100644
index 0000000..d71a096
--- /dev/null
+++ b/README.macosx
@@ -0,0 +1,4 @@
+
+ This package will compile and run on Darwin (MacOS X), but the building of
+shared libraries is not supported.
+ Make sure you use the --disable-shared option to configure.
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..91a5b26
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,12 @@
+
+ This package assumes the existence of a POSIX shell in /bin/sh.
+ On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide
+a POSIX shell in /usr/xpg4/bin/sh.
+
+ To compile this package on Solaris, you will need to run
+
+     ./patch-for-solaris
+
+ before you run ./configure. This script will change the #! invocation
+of the configure script and various tools so that a POSIX shell is used
+for the compilation process.
diff --git a/configure b/configure
new file mode 100755
index 0000000..60751c8
--- /dev/null
+++ b/configure
@@ -0,0 +1,385 @@
+#!/bin/sh
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [VAR=VALUE]... [TARGET]
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=DIR                  user executables [EPREFIX/bin]
+  --sbindir=DIR                 admin executables [EPREFIX/sbin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib]
+  --includedir=DIR              include files for the C compiler [PREFIX/include]
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [/usr/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-cross=PREFIX         prefix toolchain executable names with PREFIX [none]
+
+EOF
+exit 0
+}
+
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://www.etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO=$1
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $LDFLAGS_AUTO -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+. package/info
+
+CC_AUTO="$CC"
+CFLAGS_AUTO="$CFLAGS"
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=600 $CPPFLAGS"
+LDFLAGS_AUTO="$LDFLAGS"
+LDFLAGS_NOSHARED=
+prefix=
+exec_prefix='$prefix'
+dynlibdir='$prefix/lib'
+libexecdir='$exec_prefix/libexec'
+bindir='$exec_prefix/bin'
+sbindir='$exec_prefix/sbin'
+libdir='$prefix/usr/lib/'$package
+includedir='$prefix/usr/include'
+sysdeps='$prefix/usr/lib/skalibs/sysdeps'
+manualsysdeps=false
+shared=false
+static=true
+slashpackage=false
+sproot=
+home=
+exthome=
+allstatic=true
+evenmorestatic=false
+addincpath=''
+addlibspath=''
+addlibdpath=''
+vpaths=''
+vpathd=''
+cross="$CROSS_COMPILE"
+
+for arg ; do
+  case "$arg" in
+    --help) usage ;;
+    --prefix=*) prefix=${arg#*=} ;;
+    --exec-prefix=*) exec_prefix=${arg#*=} ;;
+    --dynlibdir=*) dynlibdir=${arg#*=} ;;
+    --libexecdir=*) libexecdir=${arg#*=} ;;
+    --bindir=*) bindir=${arg#*=} ;;
+    --sbindir=*) sbindir=${arg#*=} ;;
+    --libdir=*) libdir=${arg#*=} ;;
+    --includedir=*) includedir=${arg#*=} ;;
+    --with-sysdeps=*) sysdeps=${arg#*=} manualsysdeps=true ;;
+    --with-include=*) var=${arg#*=} ; stripdir var ; addincpath="$addincpath -I$var" ;;
+    --with-lib=*) var=${arg#*=} ; stripdir var ; addlibspath="$addlibspath -L$var" ; vpaths="$vpaths $var" ;;
+    --with-dynlib=*) var=${arg#*=} ; stripdir var ; addlibdpath="$addlibdpath -L$var" ; vpathd="$vpathd $var" ;;
+    --enable-shared|--enable-shared=yes) shared=true ;;
+    --disable-shared|--enable-shared=no) shared=false ;;
+    --enable-static|--enable-static=yes) static=true ;;
+    --disable-static|--enable-static=no) static=false ;;
+    --enable-allstatic|--enable-allstatic=yes) allstatic=true ;;
+    --disable-allstatic|--enable-allstatic=no) allstatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) evenmorestatic=true ;;
+    --disable-static-libc|--enable-static-libc=no) evenmorestatic=false ;;
+    --enable-slashpackage=*) sproot=${arg#*=} ; slashpackage=true ; ;;
+    --enable-slashpackage) sproot= ; slashpackage=true ;;
+    --disable-slashpackage) sproot= ; slashpackage=false ;;
+    --enable-cross=*) cross=${arg#*=} ;;
+    --enable-cross) cross= ;;
+    --disable-cross) cross= ;;
+    --enable-*|--disable-*|--with-*|--without-*|--*dir=*|--build=*) ;;
+    --host=*|--target=*) target=${arg#*=} ;;
+    -* ) echo "$0: unknown option $arg" ;;
+    *=*) ;;
+    *) target=$arg ;;
+  esac
+done
+
+for i in prefix exec_prefix dynlibdir libexecdir bindir sbindir libdir includedir linkdynlibdir linkbindir linksbindir sysdeps sproot skalibs ; do
+  eval tmp=\${$i}
+  eval $i=$tmp
+  stripdir $i
+done
+
+# Get usable temp filenames
+i=0
+set -C
+while : ; do
+  i=$(($i+1))
+  tmpc="./tmp-configure-$$-$PPID-$i.c"
+  tmpe="./tmp-configure-$$-$PPID-$i.tmp"
+  2>|/dev/null > "$tmpc" && break
+  2>|/dev/null > "$tmpe" && break
+  test "$i" -gt 50 && fail "$0: cannot create temporary files"
+done
+set +C
+trap 'rm -f "$tmpc" "$tmpe"' EXIT ABRT INT QUIT TERM HUP
+
+# Set slashpackage values
+if $slashpackage ; then
+  home=${sproot}/package/${category}/${package}-${version}
+  exthome=${sproot}/package/${category}/${package}
+  if $manualsysdeps ; then
+    :
+  else
+    sysdeps=${sproot}/package/prog/skalibs/sysdeps
+  fi
+  binprefix=${home}/command
+  extbinprefix=${exthome}/command
+  dynlibdir=${home}/library.so
+  libexecdir=$binprefix
+  bindir=$binprefix
+  sbindir=$binprefix
+  libdir=${home}/library
+  includedir=${home}/include
+  while read dep ; do
+    addincpath="$addincpath -I${sproot}${dep}/include"
+    vpaths="$vpaths ${sproot}${dep}/library"
+    addlibspath="$addlibspath -L${sproot}${dep}/library"
+    if $allstatic ; then : ; else
+      vpathd="$vpathd ${sproot}${dep}/library.so"
+      addlibdpath="$addlibdpath -L${sproot}${dep}/library.so"
+    fi
+  done < package/deps-build
+fi
+
+# Find a C compiler to use
+echo "checking for C compiler..."
+trycc ${cross}gcc
+trycc ${cross}c99
+trycc ${cross}cc
+test -n "$CC_AUTO" || { echo "$0: cannot find a C compiler" ; exit 1 ; }
+echo "  ... $CC_AUTO"
+echo "checking whether C compiler works... "
+echo "typedef int x;" > "$tmpc"
+if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO -c -o /dev/null "$tmpc" 2>"$tmpe" ; then
+  echo "  ... yes"
+else
+  echo "  ... no. Compiler output follows:"
+  cat < "$tmpe"
+  exit 1
+fi
+
+echo "checking target system type..."
+test -n "$target" || target=$($CC_AUTO -dumpmachine 2>/dev/null) || target=unknown
+echo "  ... $target"
+if test ! -d $sysdeps || test ! -f $sysdeps/target ; then
+  echo "$0: error: $sysdeps is not a valid sysdeps directory"
+  exit 1
+fi
+if [ "x$target" != "x$(cat $sysdeps/target)" ] ; then
+  echo "$0: error: target $target does not match the contents of $sysdeps/target"
+  exit 1
+fi
+
+rt_lib=$(cat $sysdeps/rt.lib)
+socket_lib=$(cat $sysdeps/socket.lib)
+sysclock_lib=$(cat $sysdeps/sysclock.lib)
+tainnow_lib=$(cat $sysdeps/tainnow.lib)
+util_lib=$(cat $sysdeps/util.lib)
+
+tryflag CFLAGS_AUTO -std=c99
+tryflag CFLAGS_AUTO -fomit-frame-pointer
+tryflag CFLAGS_AUTO -fno-exceptions
+tryflag CFLAGS_AUTO -fno-unwind-tables
+tryflag CFLAGS_AUTO -fno-asynchronous-unwind-tables
+tryflag CFLAGS_AUTO -Wa,--noexecstack
+tryflag CFLAGS_AUTO -fno-stack-protector
+tryflag CPPFLAGS_AUTO -Werror=implicit-function-declaration
+tryflag CPPFLAGS_AUTO -Werror=implicit-int
+tryflag CPPFLAGS_AUTO -Werror=pointer-sign
+tryflag CPPFLAGS_AUTO -Werror=pointer-arith
+tryflag CPPFLAGS_AUTO -Wno-parentheses
+tryflag CPPFLAGS_AUTO -Wno-uninitialized
+tryflag CPPFLAGS_AUTO -Wno-missing-braces
+tryflag CPPFLAGS_AUTO -Wno-unused-value
+tryflag CPPFLAGS_AUTO -Wno-unused-but-set-variable
+tryflag CPPFLAGS_AUTO -Wno-unknown-pragmas
+tryflag CPPFLAGS_AUTO -Wno-pointer-to-int-cast
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS_AUTO -Wl,--hash-style=both
+fi
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+CPPFLAGS_AUTO="$CPPFLAGS_AUTO $addincpath"
+LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibspath"
+$allstatic || LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibdpath"
+
+echo "creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+sbindir := $sbindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sproot := $sproot
+version := $version
+home := $home
+exthome := $exthome
+RT_LIB := ${rt_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TAINNOW_LIB := ${tainnow_lib}
+UTIL_LIB := ${util_lib}
+
+CC := $CC_AUTO
+CFLAGS := $CFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS_AUTO
+LDFLAGS := $LDFLAGS_AUTO
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%a$vpaths
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  vpathd=
+fi
+  echo "vpath lib%.so$vpathd"
+echo
+$static || echo "STATIC_LIBS :="
+$shared || echo "SHARED_LIBS :="
+exec 1>&3 3>&-
+echo "  ... done."
+
+echo "creating src/include/${package}/config.h..."
+mkdir -p -m 0755 src/include/${package}
+exec 3>&1 1> src/include/${package}/config.h
+cat <<EOF
+/* ISC license. */
+
+/* Generated by: $cmdline */
+
+#ifndef ${package_macro_name}_CONFIG_H
+#define ${package_macro_name}_CONFIG_H
+
+#define ${package_macro_name}_VERSION "$version"
+EOF
+if $slashpackage ; then
+  echo "#define ${package_macro_name}_BINPREFIX \"$binprefix\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"$extbinprefix\""
+  echo "#define ${package_macro_name}_LIBEXECPREFIX \"$binprefix\""
+else
+  echo "#define ${package_macro_name}_BINPREFIX \"\""
+  echo "#define ${package_macro_name}_EXTBINPREFIX \"\""
+  echo "#define ${package_macro_name}_LIBEXECPREFIX \"$libexecdir\""
+fi
+echo
+echo "#endif"
+exec 1>&3 3>&-
+echo "  ... done."
diff --git a/doc/fifodir.html b/doc/fifodir.html
new file mode 100644
index 0000000..99805ed
--- /dev/null
+++ b/doc/fifodir.html
@@ -0,0 +1,123 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: fifodirs</title>
+    <meta name="Description" content="s6: fifodirs" />
+    <meta name="Keywords" content="s6 instant notification polling fifodir named pipe filesystem" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Fifodirs </h1>
+
+<p>
+ A <em>fifodir</em> is a rendez-vous point between the <em>notifier</em>
+of certain events and its <em>listeners</em>. It is implemented via a
+directory in the filesystem. No data is stored; it is appropriate to
+create fifodirs in a RAM filesystem.
+</p>
+
+<h2> Manipulating fifodirs </h2>
+
+<h3> C API </h3>
+
+<ul>
+ <li> You can create fifodirs via the
+<tt>ftrigw_fifodir_create()</tt> function in
+<a href="libftrigw.html">libftrig</a>. </li>
+ <li> You can send an event to a fifodir via the
+<tt>ftrigw_notify()</tt> function in 
+<a href="libftrigw.html">libftrig</a>. </li>
+ <li> You can clean up a fifodir via the
+<tt>ftrigw_clean()</tt> function in
+<a href="libftrigw.html">libftrig</a>. </li>
+ <li> You can destroy fifodirs via the
+<tt>rm_rf()</tt> function in
+<a href="http://skarnet.org/software/skalibs/doc/libstddjb/djbunix.html">libstddjb</a>. </li>
+</ul>
+
+<h3> Unix API </h3>
+
+<ul>
+ <li> You can create fifodirs with the
+<a href="s6-mkfifodir.html">s6-mkfifodir</a> command. </li>
+ <li> You can send an event to a fifodir with the
+<a href="s6-ftrig-notify.html">s6-ftrig-notify</a> command. </li>
+ <li> You can clean up a fifodir with the
+<a href="s6-cleanfifodir.html">s6-cleanfifodir</a> command. </li>    
+ <li> You can destroy fifodirs with the <tt>rm -rf</tt> command. </li>
+</ul>  
+    
+<h2> Internals and Unix permissions </h2>
+
+<ul>
+ <li> Notifiers and listeners agree on a fifodir. </li>
+ <li> The fifodir directory is created by the notifier. It must be writable
+by listeners. </li>
+ <li> To subscribe, a listener atomically creates a named pipe (FIFO) in this
+directory and listens to the reading end. This named pipe must be writable
+by the notifier. </li>
+ <li> To send an event to listeners, the notifier writes the event byte to
+all the named pipes in the directory. Credit for this idea goes to Stefan
+Karrmann. </li>
+ <li> To unsubscribe, a listener unlinks his named pipe from the directory. </li>
+</ul>  
+
+<p>
+Fifodirs are created by, so they always originally have the same uid and gid as,
+their notifier. A notifier must be able to make his fifodir either publically
+accessible (anyone can subscribe) or restricted (only a given group can
+subscribe).
+</p> 
+
+<p>
+ A publically accessible fifodir must have rights 1733: 
+</p>
+
+<ul>
+ <li> Anyone can create a fifo in that fifodir </li>
+ <li> Only the notifier can see all the subscribers' fifos </li>
+ <li> A listener can only delete its own fifo </li>
+ <li> A notifier can delete any fifo for cleaning purposes </li> 
+</ul>
+
+<p>
+ A restricted fifodir must have the gid <em>g</em> of the group of allowed
+listeners and have rights 3730. Unless the notifier is root, it
+must be in the group of allowed listeners to be able to create
+such a fifodir.
+</p>
+
+<ul>
+ <li> Only members of <em>g</em> can create a fifo in that fifodir </li>
+ <li> Only the notifier can see all the subscribers' fifos </li>
+ <li> Fifos are always created with gid <em>g</em> </li>
+ <li> A listener can only delete its own fifo </li>
+ <li> A notifier can delete any fifo for cleaning purposes </li>
+</ul>
+
+<p>
+ A named pipe in a fifodir must always belong to its listener and have
+rights 0622:
+</p>
+
+<ul>
+ <li> Only this listener can read on the fifo </li>
+ <li> Anyone who has reading rights on the fifodir (i.e. only the notifier)
+can write to the fifo </li>
+</ul>
+
+<p>
+ The <a href="libftrig.html">libftrig<a/> interface takes care of all
+the subtleties.
+</p>
+
+</body>
+</html>
diff --git a/doc/index.html b/doc/index.html
new file mode 100644
index 0000000..048af4f
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,291 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6 - skarnet's small supervision suite</title>
+    <meta name="Description" content="s6 - skarnet's small supervision suite" />
+    <meta name="Keywords" content="s6 unix administration root pipe laurent bercot ska skarnet supervision djb" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> s6 </h1>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ s6 is a small suite of programs for UNIX, designed to allow process supervision
+(a.k.a service supervision),
+in the line of <a href="http://cr.yp.to/daemontools.html">daemontools</a>
+and <a href="http://smarden.org/runit/">runit</a>.
+</p>
+
+<p>
+ The s6 documentation tries to be complete and self-contained; however,
+if you have never heard of process supervision before, you might be
+confused at first. See the <a href="#related">related resources</a> section
+below for pointers to more resources, and earlier approaches to process
+supervision that might help you understand the basics.
+</p>
+
+<hr />
+
+<ul>
+<li> <a href="why.html">Why another supervision suite?</a> Isn't <a href="http://smarden.org/runit/">runit</a> good enough?</li>
+<li> What is instant notification? What does the <a href="libftrig.html">libftrig</a> do exactly?</li>
+<li> How to run a s6-svscan-based supervision tree <a href="s6-svscan-not-1.html">without replacing init</a> </li>
+<li> How to <a href="s6-svscan-1.html">replace init</a> </li>
+</ul>
+
+<hr />
+
+<h2> Installation </h2>
+
+<h3> Requirements </h3>
+
+<ul>
+ <li> A POSIX-compliant system with a standard C development environment </li>
+ <li> GNU make, version 3.81 or later </li>
+ <li> <a href="http://skarnet.org/software/skalibs/">skalibs</a> version
+2.0.0.0 or later </li>
+ <li> <a href="http://skarnet.org/software/execline/">execline</a> version
+2.0.0.0 or later </li>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ s6 is free software. It is available under the
+<a href="http://opensource.org/licenses/ISC">ISC license</a>.
+</p>
+
+<h3> Download </h3>
+
+<ul>
+ <li> The current released version of s6 is <a href="s6-2.0.0.0.tar.gz">2.0.0.0</a>. </li>
+ <li> Alternatively, you can checkout a copy of the s6 git repository:
+<pre> git clone git://git.skarnet.org/s6 </pre> </li>
+</ul>
+
+<h3> Compilation </h3>
+
+<ul>
+ <li> See the enclosed INSTALL file for installation details. </li>
+</ul>
+
+<h3> Upgrade notes </h3>
+
+<ul>
+ <li> <a href="upgrade.html">This page</a> lists the differences to be aware of between
+the previous versions of execline and the current one. </li>
+</ul>
+
+<hr />
+
+<h2> Reference </h2>
+
+<h3> Commands </h3>
+
+<p>
+ All these commands exit 111 if they encounter a temporary error or
+hardware error, and
+100 if they encounter a permanent error - such as a misuse. Short-lived
+commands exit 0 on success.
+</p>
+
+<h4> Supervision system </h4>
+
+<p>
+ <a href="s6-svscan.html">s6-svscan</a> and <a href="s6-supervise.html">s6-supervise</a>
+are the long-lived processes maintaining the supervision tree. Other programs are
+a user interface to control those processes and monitor service states.
+</p>
+
+<ul>
+<li><a href="s6-svscan.html">The <tt>s6-svscan</tt> program</a></li>
+<li><a href="s6-svscanctl.html">The <tt>s6-svscanctl</tt> program</a></li>
+<li><a href="s6-supervise.html">The <tt>s6-supervise</tt> program</a></li>
+<li><a href="s6-svc.html">The <tt>s6-svc</tt> program</a></li>
+<li><a href="s6-svok.html">The <tt>s6-svok</tt> program</a></li>
+<li><a href="s6-svstat.html">The <tt>s6-svstat</tt> program</a></li>
+<li><a href="s6-svwait.html">The <tt>s6-svwait</tt> program</a></li>
+</ul>
+
+<h4> Other daemontools-like utilities </h4>
+
+<p>
+ These programs are a rewrite of the corresponding utilities from
+<a href="http://cr.yp.to/daemontools.html">daemontools</a>, with
+a few extras. The
+<a href="s6-log.html">s6-log</a> program is important in itself: it's
+a powerful, scriptable, general-purpose filtering and logging tool
+that can be used to entirely replace syslogd. It has many more
+features than its <a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>
+counterpart.
+</p>
+
+<ul>
+<li><a href="s6-envdir.html">The <tt>s6-envdir</tt> program</a></li>
+<li><a href="s6-envuidgid.html">The <tt>s6-envuidgid</tt> program</a></li>
+<li><a href="s6-fghack.html">The <tt>s6-fghack</tt> program</a></li>
+<li><a href="s6-log.html">The <tt>s6-log</tt> program</a></li>
+<li><a href="s6-setlock.html">The <tt>s6-setlock</tt> program</a></li>
+<li><a href="s6-setsid.html">The <tt>s6-setsid</tt> program</a></li>
+<li><a href="s6-setuidgid.html">The <tt>s6-setuidgid</tt> program</a></li>
+<li><a href="s6-softlimit.html">The <tt>s6-softlimit</tt> program</a></li>
+<li><a href="s6-tai64n.html">The <tt>s6-tai64n</tt> program</a></li>
+<li><a href="s6-tai64nlocal.html">The <tt>s6-tai64nlocal</tt> program</a></li>
+<li><a href="ucspilogd.html">The <tt>ucspilogd</tt> program</a></li>
+<li><a href="s6-notifywhenup.html">The <tt>s6-notifywhenup</tt> program</a> (NEW in 2.0.0.0)</li>
+</ul>
+
+<h4> Fifodir management, notification and subscription </h4>
+
+<p>
+These programs are a clean rewrite of the obsolete "pipe-tools" package; they
+are now based on a properly designed notification library.
+They provide a command-line interface to
+<a href="libftrig.html#notification">inter-process notification and
+synchronization</a>.
+</p>
+
+<ul>
+<li><a href="s6-mkfifodir.html">The <tt>s6-mkfifodir</tt> program</a></li>
+<li><a href="s6-cleanfifodir.html">The <tt>s6-cleanfifodir</tt> program</a></li>
+</ul>
+<p>
+</p>
+<ul>
+<li><a href="s6-ftrig-notify.html">The <tt>s6-ftrig-notify</tt> program</a></li>
+</ul>
+<p>
+</p>
+<ul>
+<li><a href="s6-ftrig-wait.html">The <tt>s6-ftrig-wait</tt> program</a></li>
+<li><a href="s6-ftrig-listen1.html">The <tt>s6-ftrig-listen1</tt> program</a></li>
+<li><a href="s6-ftrig-listen.html">The <tt>s6-ftrig-listen</tt> program</a></li>
+</ul>
+
+<h4> Internal commands </h4>
+
+<ul>
+<li><a href="s6-ftrigrd.html">The <tt>s6-ftrigrd</tt> internal program</a></li>
+<li><a href="libs6lock/s6-lockd.html">The <tt>s6lockd</tt> internal program</a></li>
+<li><a href="libs6lock/s6lockd-helper.html">The <tt>s6lockd-helper</tt> internal program</a></li>
+</ul>
+
+
+<h3> Libraries </h3>
+
+<ul>
+<li><a href="libftrigw.html">The <tt>ftrigw</tt> library interface</a></li>
+<li><a href="libftrigr.html">The <tt>ftrigr</tt> library interface</a></li>
+<li><a href="libs6lock/">The <tt>s6lock</tt> library interface</a></li>
+</ul>
+
+<h3> Definitions </h3>
+
+<ul>
+<li> What is a <a href="fifodir.html">fifodir</a></li>
+<li> What is a <a href="servicedir.html">service directory</a></li>
+<li> What is a <a href="scandir.html">scan directory</a></li>
+<li> Why are the <a href="libftrig.html">libftrigw and libftrigr</a> needed </li>
+</ul>
+
+<hr />
+
+<a name="related">
+<h2> Related resources </h2>
+</a>
+
+<h3> s6 discussion </h3>
+
+<ul>
+ <li> <tt>s6</tt> is discussed on the
+<a href="http://www.skarnet.org/lists.html#supervision">supervision</a> mailing-list. </li>
+ <li> <tt>s6</tt> has a
+<a href="http://freecode.com/projects/s6">freecode page</a>.
+ </li>
+</ul>
+
+<h3> Similar work </h3>
+
+<ul>
+ <li> <a href="http://cr.yp.to/daemontools.html">daemontools</a>, the pioneering
+process supervision software suite. </li>
+ <li> <a href="http://untroubled.org/daemontools-encore/">daemontools-encore</a>,
+a derived work from daemontools with enhancements. (Note that although s6 follows
+the same naming scheme, the same general design, and many of the same architecture
+choices as daemontools, it is still original work, sharing no code at all with
+daemontools.) </li>
+ <li> <a href="http://smarden.org/runit/">runit</a>, a slightly different
+approach to process supervision, with the same goals. </li>
+ <li> <a href="http://b0llix.net/perp/">perp</a>, yet another slightly different
+approach to process supervision, also with the same goals. </li>
+</ul>
+
+<h3> Other init systems </h3>
+
+<ul>
+ <li> Felix von Leitner's <a href="http://www.fefe.de/minit/">minit</a> is an
+init system for Linux, with process supervision capabilities. </li>
+ <li> <a href="http://freshmeat.net/projects/sysvinit/">sysvinit</a> is the
+traditional init system for Linux. </li>
+ <li> <a href="http://upstart.ubuntu.com/">Upstart</a> is a well-known init system
+for Linux, with complete service management, that comes with the Ubuntu
+distribution. It includes a coffee machine and the kitchen sink.</li>
+ <li> Because Upstart wasn't bloated or unreliable enough, someone came up with
+<a href="http://freedesktop.org/wiki/Software/systemd/">systemd</a>, yet
+another Linux init system, so contrary to all principles of good engineering
+it's just scary. </li>
+ <li> The various BSD flavors have their own style of
+<a href="http://www.freebsd.org/doc/handbook/boot-init.html">init</a>. </li>
+ <li> MacOS X has its own init spaghetti monster called
+<a href="http://en.wikipedia.org/wiki/Launchd">launchd</a>. </li>
+</ul>
+
+<p>
+All-in-one init systems generally feel complex and convoluted, and when most
+people find out about the process supervision approach to init systems, they
+usually find it much simpler.
+<a href="s6-svscan-1.html#stages">There is a good reason for this.</a>
+</p>
+
+
+<h2> Credits </h2>
+
+ s6 is one of the (late...) results of a long design discussion that
+happened in 2002-2003 on the
+<a href="http://www.skarnet.org/lists.html#supervision">supervision list</a>
+and the <a href="http://cr.yp.to/lists.html#log">log list</a>.
+The main contributors to the thread were
+<ul>
+ <li> <a href="http://dogmap.org/">Paul Jarc</a> </li>
+ <li> <a href="http://smarden.org/pape/">Gerrit Pape</a> </li>
+ <li> <a href="http://ino-waiting.gmxhome.de/">Clemens Fischer</a> </li>
+ <li> <a href="http://www.mathematik.uni-ulm.de/m5/sk/">Stefan Karrman</a> </li>
+ <li> and me. </li>
+</ul>
+
+<h2> Miscellaneous </h2>
+
+<h3> Why "s6"&nbsp;? </h3>
+
+<p>
+<strong>s</strong>karnet.org's <strong>s</strong>mall and <strong>s</strong>ecure
+<strong>s</strong>upervision <strong>s</strong>oftware <strong>s</strong>uite.
+</p>
+
+<p>
+ Also, s6 is a nice command name prefix to have: it identifies the origin of the
+software, and it's short. Expect more use of s6- in future skarnet.org software
+releases. And please avoid using that prefix for your own projects.
+</p>
+
+</body>
+</html>
diff --git a/doc/libftrig.html b/doc/libftrig.html
new file mode 100644
index 0000000..da4c25b
--- /dev/null
+++ b/doc/libftrig.html
@@ -0,0 +1,184 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: libftrig</title>
+    <meta name="Description" content="s6 libftrig" />
+    <meta name="Keywords" content="s6 libftrig" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> libftrig </h1>
+
+<p>
+<t>libftrig</t> is a portable Unix C programming interface allowing a
+process (the <em>subscriber</em> or <em>listener</em>) to be instantly
+notified when another process (the <em>notifier</em> or <em>writer</em>)
+signals an event.
+</p>
+
+<a name="notification">
+<h2> What is notification&nbsp;? </h2>
+</a>
+
+<h3> Notification vs. polling </h3>
+
+<p>
+ Process A is <em>notified</em> of an event E when it gets a instant
+notice that event E has happened; the notice may disrupt A's execution
+flow. Notification is the opposite of <em>polling</em>, where A has to
+periodically (every T milliseconds) check whether E happened and has no
+other way to be informed of it.
+</p>
+
+<p>
+ Polling is generally considered bad practice - and is inferior to
+notification in practically every case - for three reasons:
+</p>
+
+<ul>
+ <li> Reaction time. When event E happens, process A does not know it
+instantly. It will only learn of E, and be able to react to it, when
+it explicitly checks for E; and if E happened right after A performed
+the check, this can take as long as T milliseconds (the <em>polling
+period</em>). Polling processes have reaction delays due to the polling
+periods. </li>
+ <li> Resource consumption. Even if <em>no</em> event ever happens, process A
+will still wake up needlessly every T milliseconds. This might not seem like
+a problem, but it is a serious one in energy-critical environments. Polling
+processes use more CPU time than is necessary and are not energy-friendly. </li>
+ <li> Conflict between the two above reasons. The longer the polling period,
+the more energy-friendly the process, but the longer the reaction time. The
+shorter the polling period, the shorter the reaction time, but the more
+resource-consuming the process. A delicate balance has to be found, and
+acceptable behaviour is different in every case, so there's no general rule
+of optimization. </li>
+</ul>
+
+<p>
+ Notification, on the other hand, is generally optimal: reaction time is
+zero, and resource consumption is minimal - a process can sleep as soon as
+it's not handling an event, and only wake up when needed.
+</p>
+
+<p>
+ Of course, the problem of notification is that it's often more difficult
+to implement. Notification frameworks are generally more complex, involving
+lots of asynchronism; polling is widely used
+<a href="http://lib.store.yahoo.net/lib/demotivators/mediocritydemotivationalposter.jpg">because
+it's easy.</a>
+</p>
+
+<h3> Notifications and Unix </h3>
+
+<p>
+ Unix provides several frameworks so that a process B (or the kernel) can
+notify process A.
+</p>
+
+<ul>
+ <li> Signals. The simplest Unix notification mechanism. Sending events amounts
+to a <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/kill.html">kill()</a>
+call, and receiving events amounts to installing a signal handler (preferrably
+using a <a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a>
+if mixing signals with an event loop). Unfortunately, Unix signals, even the more
+recent and powerful real-time Posix signals, have important limitations when it's
+about generic notification:
+ <ul>
+  <li> non-root processes can only send signals to a very restricted and
+implementation-dependent set of processes (roughly, processes with the same UID). This is a problem when
+designing secure programs that make use of the Unix privilege separation. </li>
+  <li> you need to know the PID of a process to send it signals. This is generally
+impractical; process management systems that do not use supervisor processes have
+to do exactly that, and they resort to unreliable, ugly hacks (.pid files) to track
+down process PIDs. </li>
+ </ul> </li>
+ <li> BSD-style IPCs, i.e. file descriptors to perform select()/poll() system
+calls on, in an <em>asynchronous event loop</em>. This mechanism is very widely used,
+and rightly so, because it's extremely generic and works in every ordinary situation;
+you have to be doing <a href="http://www.kegel.com/c10k.html">very specific stuff</a>
+to reach its limits. If process A is reading on
+fd <em>f</em>, it is notified everytime another process makes <em>f</em> readable -
+for instance by writing a byte to the other end if <em>f</em> is the reading end
+of a pipe. And indeed, this is how libftrig works internally; but libftrig is needed
+because direct use of BSD-style IPCs also has limitations.
+ <ul>
+  <li> Anonymous pipes are the simplest and most common BSD-style IPC. If there is a
+pipe from process B to process A, then B can notify A by writing to the pipe. The
+limitation is that A and B must have a common ancestor that created the pipe; two
+unrelated processes cannot communicate this way. </li>
+ <li> Sockets are a good many-to-one notification system: once a server is up, it
+can be notified by any client, and notify all its clients. The limitation of sockets
+is that the server must be up before the client, which prevents us from using them
+in a general notification scheme. </li>
+ </ul> </li>
+ <li> System V IPCs, i.e. message queues and semaphores. The interfaces to those IPCs
+are quite specific and can't mix with select/poll loops, that's why nobody in their
+right mind uses them. </li>
+</ul>
+
+<h3> What we want </h3>
+
+<p>
+ We need a general framework to:
+</p>
+
+<ul>
+ <li> Allow an event-generating process to broadcast notifications to every process
+that asked for one, without having to know their PIDs </li>
+ <li> Allow a process to subscribe to a "notification channel" and be instantly,
+asynchronously notified when an event occurs on this channel. </li>
+</ul>
+
+<p>
+ This requires a many-to-many approach that Unix does not provide natively, and
+that is what libftrig does.
+</p>
+
+<a name="bus">
+<h2> That's what a bus is for. D-Bus already does all this. </h2>
+</a>
+
+<p>
+ Yes, a bus is a good many-to-many notification mechanism indeed. However,
+a Unix bus can only be implemented via a daemon - you need a long-running
+process, i.e. a <em>service</em>, to implement a bus. And s6 is a
+<em>supervision suite</em>, i.e. a set of programs designed to manage
+services; we would like to be able to use notifications in the supervision
+suite, to be able to wait for a service to be up or down... <em>without</em>
+relying on a particular service to be up. libftrig provides a notification
+mechanism that <em>does not need</em> a bus service to be up, that's its
+main advantage over a bus.
+</p>
+
+<p>
+ If you are not concerned with supervision and can depend on a bus service,
+though, then yes, by all means, use a bus for your notification needs.
+There is a <a href="http://skarnet.org/software/skabus/">skabus</a>
+project in the making, which aims to be simpler, smaller and more
+maintainable than D-Bus.
+</p>
+
+<h2> How to use libftrig </h2>
+
+<p>
+ <tt>libftrig</tt> is really a part of <tt>libs6</tt>: all the functions
+are implemented in the <tt>libs6.a</tt> archive, or the <tt>libs6.so</tt>
+dynamic shared object. However, the interfaces are different for notifiers
+and listeners:
+</p>
+
+<ul>
+<li> Notifiers use the <a href="libftrigw.html">libftrigw</a> interface. </li>
+<li> Listeners use the <a href="libftrigr.html">libftrigr</a> interface. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libftrigr.html b/doc/libftrigr.html
new file mode 100644
index 0000000..27ffd61
--- /dev/null
+++ b/doc/libftrigr.html
@@ -0,0 +1,283 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the ftrigr library interface</title>
+    <meta name="Description" content="s6: the ftrigr library interface" />
+    <meta name="Keywords" content="s6 ftrig notification subscriber listener libftrigr ftrigr library interface" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ftrigr</tt> library interface </h1>
+
+<p>
+ The <tt>ftrigr</tt> library provides an API for listeners, i.e.
+programs that want to subscribe to fifodirs and be instantly
+notified when the proper sequence of events happens.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure the s6 headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;s6/ftrigr.h&gt;</tt> </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the s6 libraries, as well as the skalibs libraries,
+are visible in your library search path. </li>
+ <li> Link against <tt>-ls6</tt> and <tt>-lskarnet</tt>. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<p>
+ Check the <tt>s6/ftrigr.h</tt> header for the
+exact function prototypes.
+</p>
+
+<p>
+ Make sure your application is not disturbed by children it doesn't
+know it has. This means paying some attention to the SIGCHLD handler,
+if any, and to the way you perform <tt>waitpid()</tt>s. The best
+practice is to use a
+<a href="http://www.skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a>
+to handle SIGCHLD (as well as other signals the application needs to trap),
+and to <em>always</em> use <tt>wait_nohang()</tt> to reap children,
+simply ignoring pids you don't know.
+</p>
+
+<p>
+ If your (badly programmed) application has trouble handling unknown
+children, consider using a ftrigrd service.
+</p>
+
+<h3> A programming example </h3>
+
+<p>
+ The <tt>src/pipe-tools/s6-ftrig-listen1.c</tt> and
+<tt>src/supervision/s6-svwait.c</tt> files in the s6 package,
+for instance, illustrate how to use the ftrigr library.
+</p>
+
+
+<h3> Synchronous functions with a specified maximum execution time </h3>
+
+<ul>
+ <li> Synchronous functions take a <tt>tain_t const *</tt>
+(<em>deadline</em>) parameter and a <tt>tain_t *</tt> (<em>stamp</em>)
+parameter. Those are pointers to taia structures containing absolute times;
+the former represents a deadline (in most cases, this time will be in the
+future) and the latter must be an accurate enough timestamp. These
+structures can be filled using the <tt>tain_</tt> primitives declared in
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html">skalibs/tai.h</a>. </li>
+ <li> ("Accurate enough" means that <strong>no blocking system call must have
+been made</strong> since the last time <em>stamp</em> was updated (by
+<tt>tain_now(&amp;stamp)</tt>). It's a good policy to always update
+<em>stamp</em> right after a (potentially) blocking system call like
+<tt>select()</tt>returns. And unless the application is extremely CPU-intensive
+(think calculus for physicists or astronomers) updating <em>stamp</em> more
+frequently is unnecessary.) </li>
+ <li> If such a synchronous function still hasn't returned when the deadline
+occurs, then it will immediately return a failure code and set errno to ETIMEDOUT.
+It is possible to pass null pointers to the function instead of pointers to
+taia structures, in which case the function will never timeout. </li>
+ <li> If a timeout occurs, the library does not guarantee proper interprocess
+communication later on; the application should either die, or at least close
+the communication channel and open a new one. </li>
+ <li> If any waiting occurred, the <em>stamp</em> structure is automatically
+updated by the called function, so it always represents an accurate enough estimation
+of the current time. This allows the programmer to call several such functions
+in a sequence without modifying the <em>deadline</em> and <em>stamp</em>
+parameters: then the whole sequence is bound in execution time. </li>
+ <li> This is a general safety mechanism implemented in
+<a href="http://skarnet.org/software/skalibs/libunixonacid/">libunixonacid</a>:
+in interprocess communication, purely synchronous primitives are dangerous
+because they make the calling process rely on proper behaviour of the called
+process. Giving synchronous primitives the ability to timeout allows developers
+to write reliable programs even when interacting with software they have no
+control on. </li>
+</ul>
+
+
+<h3> Starting and ending a session </h3>
+
+<pre>
+ftrigr_t a = FTRIGR_ZERO ;
+struct taia deadline, stamp ;
+
+taia_now(&amp;stamp) ;
+taia_addsec(&amp;deadline, &amp;stamp, 2)
+
+// char const *path = FTRIGR_IPCPATH ;
+// ftrigr_start(&amp;a, path, &amp;deadline, &amp;stamp) ;
+ftrigr_startf(&amp;a, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+<tt>ftrigr_start</tt> starts a session with a ftrigrd service listening on
+<em>path</em>. <br />
+<tt>ftrigr_startf</tt> starts a session with a ftrigrd process as a child
+(which is the simplest usage). <br />
+<tt>a</tt> is a ftrigr_t structure that must be declared in the stack and
+initialized to FTRIGR_ZERO.
+<tt>stamp</tt> must be an accurate enough timestamp. <br />
+If the session initialization fails, the function returns 0 and errno is set;
+else the function returns 1.
+</p>
+<p>
+If the absolute time <tt>deadline</tt> is reached and the function
+has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT.
+
+Only local interprocess communications are involved; unless your system is
+heavily overloaded, the function should return near-instantly. One or two
+seconds of delay between <tt>stamp</tt> and <tt>deadline</tt> should be
+enough: if the function takes more than that to return, then there is a
+problem with the underlying processes.
+</p>
+
+<p>
+ You can have more than one session open in parallel, by declaring
+several distinct <tt>ftrigr_t</tt> structures and calling
+<tt>ftrigr_startf</tt> (or <tt>ftrigr_start</tt>) more than once.
+However, this is useless, since one single session can handle
+virtually as many concurrent fifodirs as your application needs.
+</p>
+
+<pre>
+ftrigr_end(&amp;a) ;
+</pre>
+
+<p>
+<tt>ftrigr_end</tt> frees all the resources used by the session. The
+<tt>a</tt> structure is then reusable for another session.
+</p>
+
+<h3> Subscribing to a fifodir </h3>
+
+<pre>
+char const *path = "/var/lib/myservice/fifodir" ;
+char const *re = "a.*b|c*d" ;
+uint32 options = 0 ;
+
+uint16 id = ftrigr_subscribe (&amp;a, path, re, options, &amp;deadline, &amp;stamp) ;
+</pre>
+
+<p>
+<tt>ftrigr_subscribe</tt> instructs the
+<a href="s6-ftrigrd.html">s6-ftrigrd daemon</a>, related to the open
+session represented by the <tt>a</tt> structure, to subscribe to the
+<tt>path</tt> fifodir, and to notify the application when it receives
+a series of events that matches the <tt>re</tt> regexp.
+<tt>options</tt> can be 0 or FTRIGR_REPEAT. If it is 0, the daemon will
+automatically unsubscribe from <tt>path</tt> once <tt>re</tt> has been
+matched by a series of events. If it is FTRIGR_REPEAT, it will remain
+subscribed until told otherwise.
+</p>
+
+<p>
+ <tt>ftrigr_subscribe()</tt> returns 0 and sets errno in case of failure, or
+a nonzero 16-bit number identifying the subscription in case of success.
+</p>
+
+<p>
+<tt>ftrigr_subscribe</tt> should return near-instantly, but if
+<em>deadline</em> is reached, it will return 0 ETIMEDOUT. If
+<tt>ftrigr_subscribe</tt> returns successfully, then the
+s6-ftrigrd daemon is guaranteed to be listening on <tt>path</tt>,
+and events can be sent without the risk of a race condition.
+</p>
+
+<h3> Synchronously waiting for events </h3>
+
+<pre>
+uint16 list[1] ;
+unsigned int n = 1 ;
+char trigger ;
+list[0] = id ;
+
+// r = ftrigr_wait_and(&amp;a, list, n, &amp;deadline) ;
+r = ftrigr_wait_or(&amp;a, list, n, &amp;deadline, &amp;trigger) ;
+</pre>
+
+<p>
+ <tt>ftrigr_wait_and()</tt> waits for <em>all</em> the <tt>n</tt> fifodirs
+whose ids are listed in <tt>list</tt> to receive an event. It returns -1
+in case of error or timeout, or a non-negative integer in case of success. <br />
+ <tt>ftrigr_wait_or()</tt> waits for <em>one</em> of the <tt>n</tt> fifodirs
+whose ids are listed in <tt>list</tt> to receive an event. It returns -1
+in case of error or timeout; if it succeeds, the return value is the
+position in <tt>list</tt>, starting at 0, of the identifier that received
+an event; and <tt>trigger</tt> is set to the character that triggered that
+event, i.e. the last character of a sequence that matched the regular
+expression <tt>re</tt> used in the subscription.
+</p>
+
+<h3> Asynchronously waiting for events </h3>
+
+<p>
+<em> (from now on, the functions are listed with their prototypes instead
+of usage examples.) </em>
+</p>
+
+<pre>
+int ftrigr_fd (struct ftrigr const *a)
+</pre>
+
+<p>
+ Returns a file descriptor to select on for reading. Do not
+<tt>read()</tt> it though.
+</p>
+
+<pre>
+int ftrigr_update (ftrigr_ref a)
+</pre>
+
+<p>
+ Call this function whenever the fd checks readability: it will
+update <em>a</em>'s internal structures with information from the
+<a href="s6-ftrigrd.html">s6-ftrigrd</a> daemon. It returns -1 if an error
+occurs; in case of success, it returns the number of identifiers for
+which something happened.
+</p>
+
+<p>
+ When <tt>ftrigr_update</tt> returns,
+<tt>genalloc_s(uint16, &amp;a-&gt;list)</tt> points to an array of
+<tt>genalloc_len(uint16, &amp;a-&gt;list)</tt> 16-bit unsigned
+integers. Those integers are ids waiting to be passed to
+<tt>ftrigr_check</tt>.
+</p>
+
+<pre>
+int ftrigr_check (ftrigr_ref a, uint16 id, char *what)
+</pre>
+
+<p>
+ Checks whether an event happened to <em>id</em>. Use after a
+call to <tt>ftrigr_update()</tt>.
+</p>
+
+<ul>
+ <li> If an error occurred, returns -1 and sets errno. The error
+number may have been transmitted from
+<a href="s6-ftrigrd.html">s6-ftrigrd</a>. </li>
+ <li> If no notification happened yet, returns 0. </li>
+ <li> If something happened, writes the character that triggered the
+latest notification into <em>what</em> and returns the number of
+times that an event happened to this identifier since the last
+call to <tt>ftrigr_check()</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libftrigw.html b/doc/libftrigw.html
new file mode 100644
index 0000000..d804976
--- /dev/null
+++ b/doc/libftrigw.html
@@ -0,0 +1,115 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the ftrigw library interface</title>
+    <meta name="Description" content="s6: the ftrigw library interface" />
+    <meta name="Keywords" content="s6 ftrig notification notifier writer libftrigw ftrigw library interface" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ftrigw</tt> library interface </h1>
+
+<p>
+ The <tt>ftrigw</tt> library provides an API for notifiers, i.e.
+programs that want to regularly announce what they're doing.
+</p>
+
+<p>
+ Notifiers should create a fifodir in a hardcoded place in the
+filesystem, and document its location. Listeners will then be
+able to subscribe to that fifodir, and receive the events.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure the s6 headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;s6/ftrigw.h&gt;</tt> </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the s6 libraries, as well as the skalibs libraries,
+are visible in your library search path. </li>
+ <li> Link against <tt>-ls6</tt> and <tt>-lskarnet</tt>. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<p>
+ Check the <tt>s6/ftrigw.h</tt> header for the
+exact function prototypes.
+</p>
+
+<h3> Creating a fifodir </h3>
+
+<pre>
+char const *path = "/var/lib/myservice/fifodir" ;
+int gid = -1 ;
+int forceperms = 0 ;
+int r = ftrigw_fifodir_make(path, gid, forceperms) ;
+</pre>
+
+<p>
+<tt>ftrigw_fifodir_make</tt> creates a fifodir at the <tt>path</tt> location.
+It returns 0, and sets errno, if an error occurs.
+It returns 1 if it succeeds. <br />
+If a fifodir, owned by the user, already exists at <tt>path</tt>, and
+<tt>forceperms</tt> is zero, then <tt>ftrigw_fifodir_make</tt> immediately
+returns a success. If <tt>forceperms</tt> is nonzero, then
+it tries to adjust <tt>path</tt>'s permissions before returning.
+</p>
+
+<p>
+If <tt>gid</tt> is negative, then <tt>path</tt> is created "public".
+Any listener will be able to subscribe to <tt>path</tt>.
+If <tt>gid</tt> is nonnegative, then <tt>path</tt> is created "private".
+Only processes belonging to group <tt>gid</tt> will be able to
+subscribe to <tt>path</tt>.
+</p>
+
+<h3> Sending an event </h3>
+
+<pre>
+char event = 'a' ;
+r = ftrigw_notify(path, event) ;
+</pre>
+
+<p>
+<tt>ftrigw_notify</tt> sends <tt>event</tt> to all the processes that are
+currently subscribed to <tt>path</tt>.
+It returns -1 if an error occurs, or the number of successfully notified
+processes.
+</p>
+
+<h3> Cleaning up </h3>
+
+<p>
+When stray KILL signals hit <a href="s6-ftrigrd.html">s6-ftrigrd</a> processes,
+1. it's a sign of incorrect system administration, 2. they can leave
+unused named pipes in the fifodir. It's the fifodir's owner's job, i.e.
+the notifier's job, to periodically do some housecleaning and take out
+the trash.
+</p>
+
+<pre>
+r = ftrigw_clean(path) ;
+</pre>
+
+<p>
+<tt>ftrigw_clean</tt> cleans <tt>path</tt>. It returns 0, and sets errno,
+if it encounters an error. It returns 1 if it succeeds.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6lock/index.html b/doc/libs6lock/index.html
new file mode 100644
index 0000000..3696b41
--- /dev/null
+++ b/doc/libs6lock/index.html
@@ -0,0 +1,256 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6lock library interface</title>
+    <meta name="Description" content="s6: the s6lock library interface" />
+    <meta name="Keywords" content="s6 timed lock s6lock libs6lock library interface" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6lock</tt> library interface </h1>
+
+<h2> General information </h2>
+
+<p>
+ <tt>libs6lock</tt> is a C interface to timed locks. Unix natively provides
+locks, but the locking primitives are synchronous, so either they are
+unbounded in execution time or they require polling. libs6lock provides
+poll-free locks that can timeout during attempted acquisition.
+</p>
+
+<h2> Compiling </h2>
+
+<ul>
+ <li> Make sure the s6 headers, as well as the skalibs headers,
+are visible in your header search path. </li>
+ <li> Use <tt>#include &lt;s6/s6lock.h&gt;</tt> </li>
+</ul>
+
+<h2> Linking </h2>
+
+<ul>
+ <li> Make sure the s6 libraries, as well as the skalibs libraries,
+are visible in your library search path. </li>
+ <li> Link against <tt>-ls6</tt>, <tt>-lskarnet</tt>,
+<tt>`cat $sysdeps/socket.lib`</tt>, and
+<tt>`cat $sysdeps/tainnow.lib`</tt>,
+if <tt>$sysdeps</tt> is your skalibs installation's sysdeps directory. </li>
+</ul>
+
+<h2> Programming </h2>
+
+<ul>
+ <li> Check the <tt>s6/s6lock.h</tt> header
+for the prototypes. The functions documented here are
+often simplified macros, for instance relying on the STAMP global variable
+to hold the current time. Fully reentrant functions with more control
+options are usually available. </li>
+ <li> Given the nature of the s6lock library, it makes sense to use a
+<a href="http://skarnet.org/software/s6-networking/localservice.html">s6lockd service</a> concurrently
+accessed by several applications using such locks to gate shared
+resources. </li>
+ <li> If you're not using a s6lockd service,
+make sure your application is not disturbed by children it doesn't
+know it has. Using nonblocking waits, ignoring pids you don't know, and
+using a
+<a href="http://skarnet.org/software/skalibs/libstddjb/selfpipe.html">self-pipe</a>
+if your application is built around an event loop, are good programming
+practices. </li>
+</ul>
+
+<h3> Starting and ending a session </h3>
+
+<pre>
+s6lock_t a = S6LOCK_ZERO ;
+struct taia deadline ;
+
+taia_now_g() ;
+taia_addsec_g(&amp;deadline, 2)
+
+char const *path = S6LOCK_IPCPATH ;
+s6lock_start_g(&amp;a, path, &amp;deadline) ;
+// char const *lockdir = "/tmp/lock" ;
+// s6lock_startf_g(&amp;a, lockdir, &amp;deadline) ;
+</pre>
+
+<p>
+<tt>s6lock_start_g</tt> starts a session by connecting to a s6lockd service
+listening on <em>path</em>. The working directory is set by the administrator
+of the service. <br />
+<tt>s6lock_startf_g</tt> starts a session with a s6lockd process as a child,
+using <em>lockdir</em> as its working directory.
+<br />
+<tt>a</tt> is a s6lock_t structure that must be declared in the stack and
+initialized to S6LOCK_ZERO.
+If the session initialization fails, the function returns 0 and errno is set;
+else the function returns 1.
+</p>
+<p>
+If the absolute time <tt>deadline</tt> is reached and the function
+has not returned yet, it immediately returns 0 with errno set to ETIMEDOUT.
+
+Only local interprocess communications are involved; unless your system is
+heavily overloaded, the function should return near-instantly. One or two
+seconds of delay between the current time and <tt>deadline</tt> should be
+enough: if the function takes more than that to return, then there is a
+problem with the underlying processes.
+</p>
+
+<p>
+ You can have more than one session open in parallel, by declaring
+several distinct <tt>s6lock_t</tt> structures and calling
+<tt>s6lock_startf_g</tt> (or <tt>s6lock_start_g</tt>) more than once.
+However, one single session can handle
+virtually as many concurrent locks as your application needs, so
+opening several sessions is only useful if you need to acquire locks
+in various distinct lock directories.
+</p>
+
+<pre>
+s6lock_end(&amp;a) ;
+</pre>
+
+<p>
+<tt>s6lock_end</tt> frees all the resources used by the session. The
+<tt>a</tt> structure is then reusable for another session.
+</p>
+
+<h3> Acquiring and releasing locks </h3>
+
+<pre>
+uint16 id ;
+char const *file = "lockfile" ;
+struct taia limit ;
+struct taia deadline ;
+
+int r = s6lock_acquire_sh_g (&amp;a, &amp;id, file, &amp;limit, &amp;deadline) ;
+/* int r = s6lock_acquire_ex_g (&amp;a, &amp;id, file, &amp;limit, &amp;deadline) ; */
+r = s6lock_release_g(&amp;a, id, &amp;deadline) ;
+</pre>
+
+<p>
+<tt>s6lock_acquire_sh_g</tt> instructs the
+<a href="s6lockd.html">s6lockd daemon</a>, related to the open
+session represented by the <tt>a</tt> handle, to try and acquire a
+shared lock on the
+<em>file</em> file located under that daemon's working directory
+(typically <tt>/var/lock</tt>). <em>file</em> will be interpreted as
+relative to the daemon's working directory even if it starts with a
+slash; however, slashes in the middle of <em>file</em> are likely to
+result in an error.
+</p>
+
+<p>
+<em>limit</em> and <em>deadline</em> are two absolute dates.
+<em>deadline</em> is a deadline for the execution of the
+function: if by <em>deadline</em> the function has not returned,
+then it instantly returns 0 and sets errno to ETIMEDOUT. The
+function is normally near-instantaneous, so <em>deadline</em> can
+be very close in the future and serves only as a protection against
+malicious servers. <em>limit</em> is the acquisition deadline: if
+by <em>limit</em> the daemon still has not been able to acquire a lock
+on <em>file</em>, then it will report a timeout to the client.
+</p>
+
+<p>
+The function returns 1 in case of success, or 0 if an error occurs,
+with errno set to a suitable value. If it succeeds, then a 16-bit
+number is stored into *<em>id</em>; this number serves as an identifier
+for this lock.
+</p>
+
+<p>
+<tt>s6lock_acquire_ex_g</tt> works just like <tt>s6lock_acquire_sh_g</tt>,
+except that the daemon tries to acquire an exclusive lock.
+</p>
+
+<p>
+<tt>s6lock_release_g</tt> releases the lock identified by <em>id</em>.
+It normally returns 1. It can return 0 with errno set to a suitable
+value if it fails. <em>id</em> is not valid after the corresponding
+lock has been released. The function normally returns instantly, with
+<em>deadline</em> as a safeguard.
+</p>
+
+<h3> Asynchronously waiting for locks </h3>
+
+<p>
+<em> (from now on, the functions are listed with their prototypes instead
+of usage examples.) </em>
+</p>
+
+<pre>
+int s6lock_fd (s6lock_t const *a)
+</pre>
+
+<p>
+ Returns a file descriptor to select on for reading. Do not
+<tt>read()</tt> it though.
+</p>
+
+<pre>
+int s6lock_update (s6lock_t *a)
+</pre>
+
+<p>
+ Call this function whenever the fd checks readability: it will
+update <em>a</em>'s internal structures with information from the
+<a href="s6lockd.html">s6lockd</a> daemon. It returns -1 if an error
+occurs; in case of success, it returns the number of identifiers for
+which something happened.
+</p>
+
+<p>
+ When <tt>s6lock_update</tt> returns,
+<tt>genalloc_s(uint16, &amp;a-&gt;list)</tt> points to an array of
+<tt>genalloc_len(uint16, &amp;a-&gt;list)</tt> 16-bit unsigned
+integers. Those integers are ids waiting to be passed to
+<tt>s6lock_check</tt>.
+</p>
+
+<pre>
+int s6lock_check (s6lock_t *a, uint16 id, char *what)
+</pre>
+
+<p>
+ Checks whether the lock identified by <em>id</em> has
+been acquired. Use after a call to <tt>s6lock_update()</tt>.
+</p>
+
+<ul>
+ <li> If an error occurred, returns -1 and sets errno. The error
+number may have been transmitted from
+<a href="s6lockd.html">s6lockd</a>. </li>
+ <li> If the lock has not been acquired yet, returns 0. </li>
+ <li> If the lock has been acquired, returns 1. </li>
+</ul>
+
+<h3> Synchronously waiting for locks </h3>
+
+<p>
+<code> int s6lock_wait_or_g (s6lock_t *a, uint16 const *idlist, unsigned int n, struct taia const *deadline) </code> <br />
+Synchronously waits for <em>one</em> of the locks represented by the array pointed to
+by <em>idlist</em> of length <em>n</em> to be acquired. Returns -1 if it fails,
+or a nonnegative number on success, which is the index in <em>idlist</em> of the
+acquired lock's id. If no result has been obtained by <em>deadline</em>, the
+function returns -1 ETIMEDOUT.
+</p>
+
+<p>
+<code> int s6lock_wait_and_g (s6lock_t *a, uint16 const *idlist, unsigned int n, struct taia const *deadline) </code> <br />
+Synchronously waits for <em>all</em> of the locks represented by the array pointed to
+by <em>idlist</em> of length <em>n</em> to be acquired. Returns -1 if it fails and
+0 if it succeeds. If no result has been obtained by <em>deadline</em>, the
+function returns -1 ETIMEDOUT.
+</p>
+
+</body>
+</html>
diff --git a/doc/libs6lock/s6lockd-helper.html b/doc/libs6lock/s6lockd-helper.html
new file mode 100644
index 0000000..839dce4
--- /dev/null
+++ b/doc/libs6lock/s6lockd-helper.html
@@ -0,0 +1,52 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6lockd-helper internal program</title>
+    <meta name="Description" content="s6: the s6lockd-helper internal program" />
+    <meta name="Keywords" content="s6 s6lockd-helper lockd asynchronous timed lock daemon helper" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<a href="index.html">libs6lock</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a><p />
+
+<h1> The <tt>s6lockd-helper</tt> program </h1>
+
+<p>
+<tt>s6lockd-helper</tt> is a helper program for the s6lock daemon.
+It just acquires a lock and holds it until it is killed or told to
+exit by its parent daemon.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+  s6lockd-helper is not meant to be invoked directly by the user:
+it will be spawned by the
+<a href="s6lockd.html">s6lockd</a> program.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6lockd-helper blocks on lock acquisition until it succeeds. It then
+notifies its parent. It exits when its parent tells him to (i.e. when the
+client asks for lock release). During the lock acquisition phase, it can
+be killed if its parent detects a timeout. </li>
+ <li> One s6lockd-helper process per lock is the only way (apart from
+threads) to implement timed lock acquisition. This can lead to a lot of
+s6lockd-helper processes, but this is not a problem:
+  <ul>
+   <li> Processes are not a scarce resource. Today's schedulers work in O(1),
+i.e. a sleeping process takes no scheduling time at all. </li>
+   <li> s6lockd-helper is extremely tiny. Every instance should use up at
+most one or two pages of non-sharable memory. </li>
+  </ul> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/libs6lock/s6lockd.html b/doc/libs6lock/s6lockd.html
new file mode 100644
index 0000000..726d2f8
--- /dev/null
+++ b/doc/libs6lock/s6lockd.html
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6lockd internal program</title>
+    <meta name="Description" content="s6: the s6lockd internal program" />
+    <meta name="Keywords" content="s6 s6lockd lockd asynchronous timed lock daemon" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<a href="index.html">libs6lock</a><br />
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a><p />
+
+<h1> The <tt>s6lockd</tt> program </h1>
+
+<p>
+<tt>s6lockd</tt> is the s6lock daemon. It is a program that manages
+a set of lock files in a given directory, and associated timeouts.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+  s6lockd does not fork, does not background itself automatically,
+and does not use syslog. It is not meant to be run directly by the
+user: it will be spawned by the
+<a href="index.html">s6lock client library</a>.
+</p>
+
+<p>
+ There are 2 ways to use s6lockd:
+</p>
+
+<ol>
+ <li> Use the <tt>s6lock_startf()</tt> library call.
+A <tt>s6lockd</tt> child will then be spawned from your
+calling process, and automatically reaped when you call
+<tt>s6lock_end()</tt>. It requires care with applications that
+trap SIGCHLD. It also requires care with lock file permissions:
+a s6lockd instance might not be able
+to open a lock file created by a former instance run by another
+client with different permissions. </li>
+ <li> Use the <tt>s6lock_start()</tt> library call, together with a
+<a href="http://skarnet.org/software/s6-networking/localservice.html">s6lockd service</a>.
+For once, <em>this is the recommended setup</em>: s6lockd creates empty
+lock files, and having all s6lockd instances run under the same user
+simplifies permissions management considerably. </li>
+ </li>
+</ol>
+
+<p>
+When run as a service, s6lockd has no "standalone" mode: it is
+designed to work with a Unix
+domain superserver, like
+<a href="http://skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a>.
+s6lockd follows the <a href="http://cr.yp.to/proto/ucspi.txt">UCSPI</a>
+interface, it can be directly executed from the superserver.
+</p>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Unix does not natively provide a way to stop blocking on a lock
+acquisition after a timeout. To emulate such behaviour, s6lockd actually
+spawns a <a href="s6lockd-helper.html">s6lockd-helper</a> child per
+requested lock. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/notifywhenup.html b/doc/notifywhenup.html
new file mode 100644
index 0000000..40b0593
--- /dev/null
+++ b/doc/notifywhenup.html
@@ -0,0 +1,75 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: service startup notifications</title>
+    <meta name="Description" content="s6: service startup notifications" />
+    <meta name="Keywords" content="s6 ftrig notification notifier writer libftrigw ftrigw startup U up svwait s6-svwait" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Service startup notifications </h1>
+
+<p>
+ It is easy for a process supervision suite to know when a service that was <em>up</em>
+is now <em>down</em>: the long-lived process implementing the service is dead. The
+supervisor, running as the daemon's parent, is instantly notified via a SIGCHLD.
+When it happens, <a href="s6-supervise.html">s6-supervise</a> sends a 'd' event
+to its <tt>./event</tt> <a href="fifodir.html">fifodir</a>, so every subscriber
+knows that the service is down. All is well.
+</p>
+
+<p>
+ It is much trickier for a process supervision suite to know when a service
+that was <em>down</em> is now <em>up</em>. The supervisor forks and execs the
+daemon, and knows when the exec has succeeded; but after that point, it's all
+up to the daemon itself. Some daemons do a lot of initialization work before
+they're actually ready to serve, and it is impossible for the supervisor to
+know exactly <em>when</em> the service is really ready.
+<a href="s6-supervise.html">s6-supervise</a> sends a 'u' event to its
+<tt>./event</tt> <a href="fifodir.html">fifodir</a> when it successfully
+spawns the daemon, but any subscriber
+reacting to 'u' is subject to a race condition - the service provided by the
+daemon may not be ready yet.
+</p>
+
+<p>
+ Reliable startup notifications need support from the daemons themselves.
+Daemons should notify the outside world when the service they are providing
+is reliably up - because only they know when it is the case.
+</p>
+
+<p>
+ s6 provides two ways for daemons to perform startup notification.
+</p>
+
+<ol>
+ <li> Daemons can use the <tt>ftrigw_notify()</tt> function, provided in
+<a href="libftrigw.html">the ftrigw library</a>. This is extremely
+simple and efficient, but requires specific s6 support in the daemon. </li>
+ <li> Daemons can write something to a file descriptor of their choice,
+then close that file descriptor, when they're ready to serve. This is
+a generic mechanism that some daemons already implement, and does not
+require anything specific in the daemon's code. The administrator can
+then run the daemon under <a href="s6-notifywhenup.html">s6-notifywhenup</a>,
+which will properly catch the daemon's message and notify all the subscribers
+with a 'U' event, meaning that the service is now up with no possible race
+condition. </li>
+</ol>
+
+<p>
+ The second method should really be implemented in every long-running
+program providing a service. When it is not the case, it's impossible
+to provide race-free startup notifications, and subscribers should be
+content with the unreliable 'u' events provided by s6-supervise.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-cleanfifodir.html b/doc/s6-cleanfifodir.html
new file mode 100644
index 0000000..5724666
--- /dev/null
+++ b/doc/s6-cleanfifodir.html
@@ -0,0 +1,42 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-cleanfifodir program</title>
+    <meta name="Description" content="s6: the s6-cleanfifodir program" />
+    <meta name="Keywords" content="s6 command s6-cleanfifodir fifodir notification cleaning" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-cleanfifodir program </h1>
+
+<p>
+s6-cleanfifodir cleans up a <a href="fifodir.html">fifodir</a>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-cleanfifodir <em>fifodir</em>
+</pre>
+
+<p>
+s6-cleanfifodir cleans up <em>fifodir</em>, that must belong to the current user.
+That means it removes all stale FIFOs in <em>fifodir</em>.
+</p>
+
+<p>
+ In normal use, it is not necessary to call s6-cleanfifodir. However, stale
+FIFOs can be left by <a href="s6-ftrigrd.html">s6-ftrigrd</a> processes that
+were violently killed, so it's good practice to regularly clean up fifodirs.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-envdir.html b/doc/s6-envdir.html
new file mode 100644
index 0000000..5ac92e2
--- /dev/null
+++ b/doc/s6-envdir.html
@@ -0,0 +1,66 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-envdir program</title>
+    <meta name="Description" content="s6: the s6-envdir program" />
+    <meta name="Keywords" content="s6 command s6-envdir dir environment modification" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-envdir program </h1>
+
+<p>
+s6-envdir changes its environment, then executes into another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-envdir [ -I | -i ] [ -n ] [ -f ] [ -c <em>nullis</em> ] <em>dir</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-envdir reads files in <em>dir</em>. For every file <em>f</em> in <em>dir</em>,
+that does not begin with a dot and does not contain the '=' character: </li>
+ <li> If <em>f</em> is empty, remove a variable named <em>f</em> from the environment, if any. </li>
+ <li> Else add a variable named <em>f</em> to the environment (or replace <em>f</em> if it
+already exists) with the first line of the contents of file <em>f</em> as value.
+Spaces and tabs at the end of this line are removed;
+null characters in this line are changed to newlines in the environment variable.</li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-i</tt>&nbsp;: strict. If <em>dir</em> does not exist, exit 111 with an
+error message. This is the default. </li>
+ <li> <tt>-I</tt>&nbsp;: loose. If <em>dir</em> does not exit, exec into
+<em>prog</em> without modifying the environment first. </li>
+ <li> <tt>-f</tt>&nbsp;: verbatim mode. All the file is given as the value of the
+environment variable, including newlines (except the last one if the <tt>-n</tt>
+option is not given). Null characters are still translated. </li>
+ <li> <tt>-n</tt>&nbsp;: do not chomp. If the <tt>-f</tt> option is given and the
+file ends with a newline, keep that last newline in the value. If the <tt>-f</tt>
+option is not given, keep the trailing blanks at the end of the first line (but
+not the ending newline). </li>
+ <li> <tt>-c</tt>&nbsp;<em>nullis</em>&nbsp;: replace null characters with the
+first character of <em>nullis</em> instead of a newline. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-envdir without options behaves exactly like
+<a href="http://cr.yp.to/daemontools/envdir.html">envdir</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-envuidgid.html b/doc/s6-envuidgid.html
new file mode 100644
index 0000000..680e841
--- /dev/null
+++ b/doc/s6-envuidgid.html
@@ -0,0 +1,64 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-envuidgid program</title>
+    <meta name="Description" content="s6: the s6-envuidgid program" />
+    <meta name="Keywords" content="s6 command s6-envuidgid uid gid environment modification" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-envuidgid program </h1>
+
+<p>
+s6-envuidgid sets the UID, GID and GIDLIST environment variables,
+then executes into another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-envuidgid <em>account</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-envuidgid looks <em>account</em> up by name in the account database. </li>
+ <li> If <em>account</em> is unknown, it exits 1. </li>
+ <li> It sets the UID environment variable to <em>account</em>'s uid, and the GID
+environment variable to <em>account</em>'s gid. </li>
+ <li> It also sets the GIDLIST environment variable to a comma-separated list of
+supplementary group ids <em>account</em> is a member of according to the
+group database. (If <em>account</em> doesn't belong to any other group than its
+primary group, GIDLIST is still set, but empty.) </li>
+ <li> Then it executes into <em>prog...</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-envuidgid behaves like
+<a href="http://cr.yp.to/daemontools/envuidgid.html">envuidgid</a>, except that:
+</p>
+
+<ul>
+ <li> it also handles supplementary groups </li>
+ <li> It exits 1 if <em>account</em> does not exist. </li>
+</ul>
+
+<p>
+ s6-envuidgid is useful when running a program that must start as root but can
+drop its privileges later. Such a program can read its new uid/gid/groups info
+from the UID, GID and GIDLIST environment variables. Superservers such as
+<a href="http://skarnet.org/software/s6-networking/s6-tcpserver4.html">s6-tcpserver4</a>
+make use of this.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-fghack.html b/doc/s6-fghack.html
new file mode 100644
index 0000000..ba3f351
--- /dev/null
+++ b/doc/s6-fghack.html
@@ -0,0 +1,50 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-fghack program</title>
+    <meta name="Description" content="s6: the s6-fghack program" />
+    <meta name="Keywords" content="s6 command s6-fghack foreground program background hack anti-backgrounding tool" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-fghack program </h1>
+
+<p>
+s6-fghack is an anti-backgrounding tool.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-fghack <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-fghack opens a lot of file descriptors (all writing to a single pipe). </li>
+ <li> Then it forks and executes <em>prog...</em>. </li>
+ <li> If something gets written on one of those descriptors, it's a bug in <em>prog</em>.
+s6-fghack then complains and exits 102. </li>
+ <li> Unless <em>prog...</em> goes out of its way to close descriptors it does know about,
+s6-fghack is able to detect when <em>prog...</em> exits. It exits with the same exit
+code (or 111 if <em>prog...</em> has crashed). </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-fghack is what it says: a hack. Ideally, you should never have to use it.
+It is only useful when you want to supervise a daemon that does not provide a
+"stay in the foreground" option; and even then, the right thing is to report
+this as a bug to the daemon author and have it fixed.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-ftrig-listen.html b/doc/s6-ftrig-listen.html
new file mode 100644
index 0000000..e1d2518
--- /dev/null
+++ b/doc/s6-ftrig-listen.html
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-ftrig-listen program</title>
+    <meta name="Description" content="s6: the s6-ftrig-listen program" />
+    <meta name="Keywords" content="s6 command s6-ftrig-listen fifodir notification event listener subscriber receive" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-ftrig-listen program </h1>
+
+<p>
+s6-ftrig-listen subscribes to several <a href="fifodir.html">fifodirs</a>, then
+spawns a program, then waits for pattern of events to occur on the fifodirs.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ In an <a href="http://skarnet.org/software/execline/execlineb.html">execlineb</a>
+script:
+</p>
+
+<pre>
+     s6-ftrig-listen [ -a | -o ] [ -t <em>timeout</em> ] { <em>fifodir1</em> <em>regexp1</em> <em>fifodir2</em> <em>regexp2</em> ... } <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-ftrig-listen subscribes to <em>fifodir1</em> with the regexp <em>regexp1</em>,
+to <em>fifodir2</em> with the regexp <em>regexp2</em>, and so on. </li>
+ <li> It then forks and exec <em>prog...</em> with all its arguments </li>
+ <li> It waits for the series of events received on <em>fifodir-i</em>
+to match <em>regexp-i</em>,  The <em>regexp-i</em> must be
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">extended
+regular expressions</a>. </li>
+ <li> When the series of read events matches the <em>regexp</em>s,
+s6-ftrig-listen exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-t <em>timeout</em></tt>&nbsp;: if the events on the <em>fifodir</em>s have not
+matched the <em>regexp</em>s after <em>timeout</em> milliseconds, print an error message on
+stderr and exit 1. By default, s6-ftrig-listen1 waits indefinitely for a matching series
+of events. </li>
+ <li> <tt>-a</tt>&nbsp;: and (conjunction). s6-ftrig-listen will only exit when <em>all</em>
+the <em>fifodir-i</em> have been notified with events matching the corresponding <em>regexp-i</em>.
+This is the default. </li>
+ <li> <tt>-o</tt>&nbsp;: one (disjunction). s6-ftrig-listen will exit as soon as <em>one</em>
+of the <em>fifodir-i</em> has been notified with events matching its <em>regexp-i</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-ftrig-listen can be used outside of an execlineb script by using the
+internal argv syntax, but this syntax is an implementation detail and is
+not documented as stable. In a shell
+script, use <tt>execlineb -Pc 's6-ftrig-listen ...'</tt> to get the
+benefits of the execlineb brace syntax.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-ftrig-listen1.html b/doc/s6-ftrig-listen1.html
new file mode 100644
index 0000000..88e2c6a
--- /dev/null
+++ b/doc/s6-ftrig-listen1.html
@@ -0,0 +1,88 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-ftrig-listen1 program</title>
+    <meta name="Description" content="s6: the s6-ftrig-listen1 program" />
+    <meta name="Keywords" content="s6 command s6-ftrig-listen1 fifodir notification event listener subscriber receive" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-ftrig-listen1 program </h1>
+
+<p>
+s6-ftrig-listen1 subscribes to a <a href="fifodir.html">fifodir</a>, then
+spawns a program, then waits for a pattern of events to occur on the fifodir.
+</p>
+
+<p>
+s6-ftrig-listen1 acts just as <a href="s6-ftrig-wait.html">s6-ftrig-wait</a>,
+except it can make sure that the process sending the notifications is actually
+started <em>after</em> there is a listener for those events.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-ftrig-listen1 [ -t <em>timeout</em> ] <em>fifodir</em> <em>regexp</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-ftrig-listen1 subscribes to <em>fifodir</em> </li>
+ <li> It then forks and exec <em>prog...</em> with all its arguments </li>
+ <li> It waits for the series of events received on <em>fifodir</em>
+to match <em>regexp</em>. <em>regexp</em> must be an
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">extended
+regular expression</a>. </li>
+ <li> When the series of read events matches <em>regexp</em>,
+s6-ftrig-listen1 exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-t <em>timeout</em></tt>&nbsp;: if the events on <em>fifodir</em> have not
+matched <em>regexp</em> after <em>timeout</em> milliseconds, print an error message on
+stderr and exit 1. By default, s6-ftrig-listen1 waits indefinitely for a matching series
+of events. </li>
+</ul>
+
+<h2> Usage example </h2>
+
+<p>
+ The following sequence of shell commands has a race condition:
+</p>
+
+<p> <em>In terminal 1:</em> </p>
+<pre>
+s6-mkfifodir /tmp/toto
+s6-ftrig-wait /tmp/toto "message"
+</pre>
+
+<p> <em>Then in terminal 2</em> </p>
+<pre>
+s6-ftrig-notify /tmp/toto message
+</pre>
+
+<p>
+ Depending on the operating system's scheduler, there is the possibility that
+the s6-ftrig-notify process starts sending "message" <em>before</em> the
+s6-ftrig-wait process has actually subscribed to <tt>/tmp/toto</tt>, in which
+case the notification will be missed. The following sequence of shell commands
+accomplishes the same goal in a reliable way, without the race condition:
+</p>
+
+<pre>
+s6-mkfifodir /tmp/toto
+s6-ftrig-listen1 /tmp/toto "message" s6-ftrig-notify /tmp/toto message
+</pre>
+
+</body>
+</html>
diff --git a/doc/s6-ftrig-notify.html b/doc/s6-ftrig-notify.html
new file mode 100644
index 0000000..fa1e8b5
--- /dev/null
+++ b/doc/s6-ftrig-notify.html
@@ -0,0 +1,44 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-ftrig-notify program</title>
+    <meta name="Description" content="s6: the s6-ftrig-notify program" />
+    <meta name="Keywords" content="s6 command s6-ftrig-notify fifodir notification event notifier send" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-ftrig-notify program </h1>
+
+<p>
+s6-ftrig-notify sends a series of events to a <a href="fifodir.html">fifodir</a>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-ftrig-notify <em>fifodir</em> <em>message</em>
+</pre>
+
+<p>
+s6-ftrig-notify notifies all the current listeners in <em>fifodir</em>
+with all the characters in <em>message</em>, one by one.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+s6-ftrig-notify cannot be used to send the null character (event 0x00).
+If you need to send the null character, use the C API:
+<a href="libftrigw.html">ftrigw_notify()</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-ftrig-wait.html b/doc/s6-ftrig-wait.html
new file mode 100644
index 0000000..a833782
--- /dev/null
+++ b/doc/s6-ftrig-wait.html
@@ -0,0 +1,51 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-ftrig-wait program</title>
+    <meta name="Description" content="s6: the s6-ftrig-wait program" />
+    <meta name="Keywords" content="s6 command s6-ftrig-wait fifodir notification event listener subscriber receive" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-ftrig-wait program </h1>
+
+<p>
+s6-ftrig-listen1 subscribes to a <a href="fifodir.html">fifodir</a> and
+waits for a pattern of events to occur on this fifodir.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-ftrig-wait [ -t <em>timeout</em> ] <em>fifodir</em> <em>regexp</em>
+</pre>
+
+<ul>
+ <li> s6-ftrig-wait subscribes to <em>fifodir</em> </li>
+ <li> It waits for the series of events received on <em>fifodir</em>
+to match <em>regexp</em>. <em>regexp</em> must be an
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">extended
+regular expression</a>. </li>
+ <li> When the series of read events matches <em>regexp</em>,
+s6-ftrig-wait exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-t <em>timeout</em></tt>&nbsp;: if the events on <em>fifodir</em> have not
+matched <em>regexp</em> after <em>timeout</em> milliseconds, print an error message on
+stderr and exit 1. By default, s6-ftrig-wait waits indefinitely for a matching series
+of events. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-ftrigrd.html b/doc/s6-ftrigrd.html
new file mode 100644
index 0000000..dbd611e
--- /dev/null
+++ b/doc/s6-ftrigrd.html
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-ftrigrd program</title>
+    <meta name="Description" content="s6: the s6-ftrigrd program" />
+    <meta name="Keywords" content="s6 command s6-ftrigrd program internal libexec fifodir regexp subscribe notification listener" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-ftrigrd program </h1>
+
+<p>
+s6-ftrigrd is a helper program that manages a set of subscriptions to fifodirs as well
+as regular expressions on events. It takes orders from its client program that controls
+it via the <a href="libftrigr.html">ftrigr library</a>, and notifies it when desired
+events happen.
+</p>
+
+<h2> Interface </h2>
+
+<p>
+ s6-ftrigrd is not meant to be called directly.
+</p>
+
+<ul>
+ <li> If the client program uses <tt>ftrigr_startf()</tt>, it spawns an instance of
+s6-ftrigrd as a child. s6-ftrigrd's stdin is a pipe reading from the client; its
+stdout is a pipe writing to the client; its stderr is the same as the client's; and
+there's an additional pipe from s6-ftrigrd to the client, used for asynchronous
+notifications. </li>
+ <li> If the client program uses <tt>ftrigr_start()</tt>, then it tries to connect
+to a Unix domain socket. A <em>ftrigrd service</em> should be listening to that
+socket, i.e. a Unix domain superserver such as
+<a href="http://www.skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a>
+spawning a s6-ftrigrd program on every connection. Then a s6-ftrigrd instance is created
+for the client. </li>
+ <li> When the client uses <tt>ftrigr_end()</tt>, or closes s6-ftrigrd's stdin in
+any way, s6-ftrigrd exits 0. </li>
+</ul>
+
+<p>
+ s6-ftrigrd handles the grunt work of creating fifos in a
+<a href="fifodir.html">fifodir</a> for a subscriber. It also wakes up on every
+event, and compares the chain of events it received on a given fifodir with the
+client-provided regexp. If the chain of events matches the regexp, it notifies
+the client.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+ The connection management between the client and s6-ftrigrd is entirely done
+by the <a href="http://www.skarnet.org/software/skalibs/libunixonacid/skaclient.html">skaclient</a>
+library.
+</p>
+
+<p>
+ s6-ftrigrd is entirely asynchronous. It stores unread notifications into heap
+memory; it can grow in size if there are a lot of events and the client fails
+to read them. To avoid uncontrolled growth, make sure your client calls
+<tt>ftrigr_update()</tt> as soon as <tt>ftrigr_fd()</tt> becomes readable.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-log.html b/doc/s6-log.html
new file mode 100644
index 0000000..0a525d6
--- /dev/null
+++ b/doc/s6-log.html
@@ -0,0 +1,508 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-log program</title>
+    <meta name="Description" content="s6: the s6-log program" />
+    <meta name="Keywords" content="s6 command s6-log log logger logging multilog" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-log program </h1>
+
+<p>
+s6-log is a reliable logging program with automated log rotation, similar to
+daemontools' <a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>,
+with full POSIX regular expression support.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-log [ -q | -v ] [ -b ] [ -p ] [ -t ] [ -e ] <em>logging script</em>
+</pre>
+
+<p>
+s6-log reads and compiles <em>logging script</em> to an internal
+form. Then it reads its standard input, line by line, and performs actions
+on it, following the script it is given. It does its best to ensure there
+is <em>never any log loss</em>. It exits cleanly when stdin closes or when
+it receives SIGTERM.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-b</tt>&nbsp;: blocking mode. With this option, s6-log stops
+reading its standard input while it has unflushed buffers. This ensures that
+every log line has been fully processed before reading the next one; this is also
+<a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>'s behaviour.
+By default, s6-log keeps reading from stdin even if its buffers still
+contain data. <tt>-b</tt> is safer, but may slow down your service; the default
+is faster, but may lead to unbound memory use if you have a lot of output to
+write to a slow file system. </li>
+ <li> <tt>-p</tt>&nbsp;: protect. Do not exit on receipt of a SIGTERM; only
+exit when reading EOF on stdin. </li>
+ <li> <tt>-t</tt>&nbsp;: timestamp. Prepends every log line that is written to
+a logging directory with a
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html">TAI64N</a>
+timestamp.  </li>
+ <li> <tt>-e</tt>&nbsp;: timestamp alerts. Prepends every "alert" line with a
+TAI64N timestamp. </li>
+ <li> <tt>-q | -v</tt>&nbsp;: quiet | verbose. Decreases | increases s6-log's
+verbosity, i.e. which messages are sent to stderr. The default verbosity is 1.
+Currently supported verbosity levels:
+  <ul>
+   <li> 0: only write alerts and fatal errors </li>
+   <li> 1: write alerts, warnings and fatal errors </li>
+  </ul> </li>
+</ul>
+
+<h2> Logdirs </h2>
+
+<p>
+A <em>logdir</em> (<em>logging directory</em>) is a place where logs are
+stored. s6-log can be scripted to write into one or more logdirs.
+</p>
+
+<p>
+ A logdir may contain the following files:
+</p>
+
+<ul>
+ <li> <tt>lock</tt>: this file is locked by s6-log at start,
+to make sure only one instance is running at the same time. </li>
+ <li> <tt>current</tt>: the file where selected log lines are appended.
+If <tt>current</tt> has the executable-by-user flag, it means that no
+s6-log process is currently writing to it and the previous
+s6-log process managed to cleanly finalize it. If it does not,
+either a s6-log process is writing to it or the previous one
+has been interrupted without finalizing it. </li>
+ <li> <tt>state</tt>: last processor's output, see below. </li>
+ <li> <tt>previous</tt>: a rotation is happening in that logdir. </li>
+ <li> <tt>processed</tt>, <tt>newstate</tt>: a rotation is happening
+in that logdir and the processor script is running. </li>
+ <li> timestamped files: those files are @<em>timestamp</em>.s or
+@<em>timestamp</em>.u and are old log files that have been processed
+and rotated (if they're ending in .s) or that were the <tt>current</tt>
+file when s6-log got interrupted (if they're ending in .u), in which
+case they have not been processed. </li>
+</ul>
+
+<h3> Rotation </h3>
+
+<p>
+ In a logdir, selected lines are appended to the <tt>current</tt> file.
+When <tt>current</tt> becomes too big, a <em>rotation</em> happens.
+The <tt>current</tt> file will be possibly <em>processed</em>, then
+it will become an <em>archived</em> log file named
+@<em>timestamp</em>.s, where <em>timestamp</em>, a
+<a href="http://www.skarnet.org/software/skalibs/libstddjb/tai.html">TAI64N</a>
+timestamp, is the absolute time of the rotation. If there are too
+many archived log files in the logdir, the older ones are then
+suppressed. Logging then resumes, to a brand new <tt>current</tt>
+file.
+</p>
+
+<p>
+ You can use this mechanism to ensure that your logs never fill up
+the available disk space, for instance: something that neither
+syslogd, nor syslog-ng, nor rsyslog offers.
+</p>
+
+<h3> Processors </h3>
+
+<p>
+ A <em>processor</em> script can be set for every logdir. When a rotation
+occurs, <tt>current</tt> (which has then been renamed <tt>previous</tt>) is
+fed to <em>processor</em>'s stdin, and <em>processor</em>'s stdout is saved
+and archived. <em>processor</em> can also read the <tt>state</tt> file
+on its fd 4; what it writes to its fd 5 will be saved as the next
+<tt>state</tt> file, for the next rotation.
+</p>
+
+<p>
+ Processors should not background themselves: s6-log considers the
+processing done when its <em>processor</em> direct child dies.
+Processors should exit 0 on success and nonzero on failure; if a
+processor fails, s6-log will try it again after some
+<em>cooldown</em> time.
+</p>
+
+<p>
+ Processors make s6-log Turing-complete by allowing you to use any
+external program to handle log files that are going to be archived.
+</p>
+
+<h2> Logging script syntax </h2>
+
+<p>
+ When starting up, s6-log reads its arguments one by one; this
+argument sequence, or <em>directive sequence</em>, forms a
+<em>logging script</em> which tells s6-log what to log, where, and how.
+</p>
+
+<p>
+ Every directive can be a <em>selection</em> directive, a <em>control</em>
+directive or an <em>action</em> directive. A valid logging script always
+contains at least one action directive; every action directive can be
+preceded by zero or more selection or control directives. s6-log will exit 100
+if the script is invalid. If it can process the script but the last directive
+is not an action directive, s6-log will emit a warning.
+</p>
+
+<h3> Selection directives </h3>
+
+<p>
+ These directives tell s6-log whether to select or deselect lines it reads
+from stdin; actions will only happen on selected lines. By default, every
+line is selected.
+</p>
+
+<ul>
+ <li> <strong>+<em>regexp</em></strong>: select yet-unselected lines that match <em>regexp</em>, which must be a
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">POSIX
+Extended Regular Expression</a>. </li>
+ <li> <strong>-<em>regexp</em></strong>: deselect yet-selected lines that match <em>regexp</em>, which must be a
+POSIX Extended Regular Expression. </li>
+ <li> <strong>f</strong>: select exactly lines that have not yet been acted
+upon (i.e. that were always deselected when the script encountered an action
+directive). </li>
+</ul>
+
+<h3> Control directives </h3>
+
+<p>
+ These directives tune s6-log's behaviour for the next actions.
+</p>
+
+<ul>
+ <li> <strong>n<em>number</em></strong>: next logdirs will contain up to
+<em>number</em> archived log files. If there are more, the oldest archived
+log files will be suppressed, only the latest <em>number</em> will be kept.
+By default, <em>number</em> is 10. </li>
+ <li> <strong>s<em>filesize</em></strong>: next rotations will occur when
+<tt>current</tt> log files approach <em>filesize</em> bytes. By default,
+<em>filesize</em> is 99999; it cannot be set lower than 4096 or
+higher than 16777215. </li>
+ <li> <strong>S<em>totalsize</em></strong>: next logdirs will contain up
+to <em>totalsize</em> bytes of archived (and maybe processed) log files. If
+archived log files take more space than that, the older ones are suppressed
+until the total size fits. A <em>totalsize</em> of zero means no such limit;
+use <strong>n0</strong> instead if you don't want any archived log files. By
+default, <em>totalsize</em> is 0 (unlimited). </li>
+ <li> <strong>l<em>tolerance</em></strong>: next rotations will be triggered
+when the size of <tt>current</tt> goes higher than <em>filesize</em> minus
+<em>tolerance</em>. <em>tolerance</em> cannot be more than half of
+<em>filesize</em>. By default, <em>tolerance</em> is 2000. </li>
+ <li> <strong>r<em>cooldown</em></strong>: if an error occurs during operations
+on the next logdirs, retry every <em>cooldown</em> milliseconds. By default,
+<em>cooldown</em> is 2000; it's strongly discouraged to set it to a value
+under 50. </li>
+ <li> <strong>E<em>alertsize</em></strong>: only the first <em>alertsize</em>
+bytes of the selected lines will be used in the next alerts. An
+<em>alertsize</em> of 0 means no limit. By default, <em>alertsize</em> is
+200. </li>
+ <li> <strong>^<em>statussize</em></strong>: only the first <em>statussize</em>
+bytes of the selected lines will be used in the next status file updates.
+If a line is shorter than <em>statussize</em> bytes, the status file will be
+padded with newlines so it is always <em>statussize</em> bytes long. 0 means
+an unpadded, unlimited status file. By default, <em>statussize</em> is 1001. </li>
+ <li> <strong>!<em>processor</em></strong>: registers
+<tt>execlineb -Pc <em>processor</em></tt> as a processor for the next logdirs;
+<tt>execlineb</tt> must be found in s6-log's PATH.
+If <em>processor</em> is empty, no processor will be set for the next logdirs.
+By default, no processor is set. </li>
+</ul>
+
+<h3> Action directives </h3>
+
+<p>
+ These directives determine what s6-log actually <em>does</em> with the
+selected lines.
+</p>
+
+<ul>
+ <li> <strong>e</strong>: alert. s6-log will print "s6-log: alert: ",
+possibly prepended with a timestamp, followed by the first
+<em>alertsize</em> bytes of the line. </li>
+ <li> <strong>=<em>statusfile</em></strong>: status. s6-log will atomically
+update the <em>statusfile</em> file with the first <em>statussize</em>
+bytes of the line, and pad it with newlines. s6-log must have the right
+to write to <em>statusfile</em> and to <em>statusfile</em>'s directory. </li>
+ <li> <strong><em>dir</em></strong> (must start with '/' or '.'): logdir.
+s6-log will log the line into the logdir <em>dir</em>. s6-log must have
+the right to write to <em>dir</em>.</li>
+</ul>
+
+
+<h2> Signals </h2>
+
+<ul>
+ <li> SIGTERM: If s6-log has been run with the <tt>-p</tt> option, does nothing.
+Without this option, SIGTERM instructs s6-log to stop reading stdin after the
+next newline and exit after logging the last line. </li>
+ <li> SIGALRM: triggers a rotation on every logdir s6-log is monitoring,
+as if the <tt>current</tt> file in those logdirs had reached the size
+limit. </li>
+</ul>
+
+<h2> Examples </h2>
+
+<pre>
+     s6-log -bt n20 s1000000 /var/log/services/stuff
+</pre>
+
+<p>
+ Logs all of stdin, prepending every line with a timestamp, into the
+<tt>/var/log/services/stuff</tt> logdir, with a maximum archive of
+20 log files of 1 MB each; makes sure every line has been written
+before reading the next one.
+</p>
+
+<pre>
+     s6-log -t n30 E500 - +fatal: e - +^STAT =/var/log/foobard/status f s10000000 S15000000 !"gzip -nq9" /var/log/foobard
+</pre>
+
+<ul>
+ <li> Sends alerts to stderr with the 500 first bytes of lines containing "fatal:". </li>
+ <li> Maintains the <tt>/var/log/foobard/status</tt> file at 1001 bytes,
+updating it when it finds a log line starting with "STAT". </li>
+ <li> Logs all other lines to logdir <tt>/var/log/foobard</tt>. When <tt>current</tt>
+reaches at least 9998 kB (i.e. 10 MB filesise minus 2kB tolerance), pipe it
+through <tt>gzip -nq9</tt> and save the result into a timestamped archive file, with
+a maximum of 30 such files or a total of 15 MB of compressed archive files. </li>
+</ul>
+
+
+<h2> Why use execlineb to interpret the "processor" string ? </h2>
+
+<p>
+ Because it is <em>exactly</em> what
+<a href="http://skarnet.org/software/execline/execlineb.html">execlineb</a>
+is for.
+</p>
+
+<ul>
+ <li> Directly executing <em>processor</em> is not flexible enough. We want
+to be able to run a complete command line, with an executable name and its
+arguments. </li>
+ <li> We could interpret the <em>processor</em> string via <tt>/bin/sh</tt>.
+This is what <a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>
+does. However, <tt>/bin/sh</tt>, despite being the traditional Unix interpreter,
+is overpowered for this. We don't need a complete shell script interpreter:
+most <em>processor</em> commands will be very simple, with only two or three
+words, and we only need a way to turn a string into an <em>argv</em>, i.e. a
+command line. </li>
+ <li> <a href="http://www.skarnet.org/software/execline/execlineb.html">execlineb</a>
+was designed just for this: to turn simple strings into command lines.
+It is a very fast and lightweight script launcher, that does not do any heavy
+startup initialization like <tt>/bin/sh</tt> does. It happens to be the perfect
+tool for the job. </li>
+ <li> Yes, I also did this on purpose so people have a reason to use the
+<a href="http://www.skarnet.org/software/execline/">execline</a> language. Do not
+look at me like that: it <em>really</em> is the perfect tool for the job. </li>
+</ul>
+
+<h2> Why have another logging mechanism ? </h2>
+
+<p>
+ Because the syslog mechanism and all its implementations (save one) suck.
+I'm not being judgmental; I'm just stating the obvious.
+</p>
+
+<a name="diesyslogdiedie"><h3> The syslog design is flawed from the start </h3></a>
+
+<p>
+<a href="http://blog.gerhards.net/2007/08/why-does-world-need-another-syslogd.html">When
+asked why he started rsyslog</a>, Rainer Gerhards came up with a lot of
+hand-waving and not a single word about technical points. There is a
+reason for that: rsyslog is forked from sysklogd! So, no matter how
+many bells and whistles are added to it, it still suffers from the same
+basic flaws.
+</p>
+
+<p>
+ The problem with syslogd does not come from such or such implementation.
+The problem comes from syslog's <em>design</em> in the first place.
+</p>
+
+<ul>
+ <li> syslog makes you send <em>all</em> your logs to the same place.
+The logs from a zillion processes are read by a single syslogd server.
+The server checks log lines against system-wide regular expressions
+to decide where to write them. This raises the following issues:
+ <ul>
+  <li> Unless the client explicitly mentions its name in every log
+line, there is no way for log readers to know what process generated a
+given line. Some syslogd implementations can log the pid of the client;
+big deal. </li>
+  <li> Log lines from every client have to run through the same regular
+expression matching. This requires huge regular expression sets, and an
+obvious performance impact, to do anything meaningful. And as a matter
+of fact, standard syslogd configurations don't do anything meaningful:
+they separate the logs into a few streams such as <tt>/var/log/messages</tt>,
+<tt>/var/log/auth.log</tt>, <tt>/var/log/daemon.log</tt> or
+<tt>/var/log/syslog</tt> with very vague semantics. All of syslogd's
+line processing power remains unused, because making real use of it would
+be too complex. </li>
+ </ul>
+ <li> syslogd logs to <em>files</em>. This is wrong, because files grow
+and disks fill up. Sure, there are utilities such as <tt>logrotate</tt>
+to perform cleaning up, but as long as logging and log rotation are
+kept separate, there is a race condition: a log file can grow and fill
+up a disk before a rotation occurs. I am all for separating tasks that
+can be separated, but there is no choice here: <em>logging and log
+rotation management must be done <strong>by the same tool</strong></em>.
+Only the Busybox implementation of syslogd does this, and that is a
+feature added by the Busybox developers who are aware of the problem
+but want to maintain compatibility with the historical syslogd.
+No other syslogd implementation I know of manages its log files: that's a
+flaw that no amount of external tools is going to fix. </li>
+ <li> syslogd is a complex process that runs as root. We all know what
+complex processes running as root mean: bugs turning into security holes. </li>
+ <li> syslog requires a syslogd service, and fails otherwise. A syslogd
+service may not be present, it may fail... or it may want to log stuff.
+Who's going to take care of syslogd's error messages ? </li>
+</ul>
+
+<p>
+ syslog is slow, it's unsafe, and it's incomplete. The only reason people
+use it is because it's historical, it exists, and there hasn't been any
+serious alternative yet, except maybe
+<a href="http://cr.yp.to/daemontools/multilog.html">multilog</a>, which
+s6-log improves upon.
+</p>
+
+<a name="loggingchain"><h3> A not-so-modest proposal: the logging chain </h3></a>
+
+<p>
+ Unix distributions already do this to some extent, but it's at best
+unclear where the logs go for any given program.
+</p>
+
+<ul>
+ <li> Every program, without exception, should send its logs (be it
+error messages, warning messages, or anything) to its <em>standard
+error descriptor</em>, i.e. fd 2. <em>This is why it's open for.</em> </li>
+ <li> When process 1 starts, the logging chain is rooted to the
+<em>machine console</em>: anything process 1 sends to its stderr
+appears, without modification, on the machine console, which should
+at any time remain the last resort place where logs are sent. </em>
+ <li> Process 1 should spawn and supervise a <em>catch-all logging
+mechanism</em> that handles logs from every service that does not
+take care of its own logging. Error messages from this logging
+mechanism naturally go to the machine console. </li>
+ <li> Process 1's own error messages can go to the machine console,
+or <a href="s6-svscan-1.html#log">dirty tricks can be used</a> so they
+go to the catch-all logging mechanism. </li>
+ <li> Services that are spawned by process 1 should come with their
+own logger service; the supervision mechanism offered by
+<a href="s6-svscan.html">s6-svscan</a> makes it easy. Error messages
+from the loggers themselves naturally go to the catch-all
+mechanism. </li>
+ <li> User login mechanisms such as <tt>getty</tt>, <tt>xdm</tt> or
+<tt>sshd</tt> are services: they should be started with their own
+loggers. Of course, when a user gets a terminal and a shell, the
+shell's stderr should be redirected to the terminal: interactive
+programs break the automatic logging chain and delegate responsibility
+to the user. </li>
+ <li> A syslogd service <em>may</em> exist, to catch logs sent via
+syslog() by legacy programs. But it is a normal service, and logs
+caught by this syslogd service are not part of the logging chain.
+ It is probably overkill to provide the syslogd service with its own
+logger; error messages from syslogd can default to the catch-all logger.
+ The s6 package, including the <a href="ucspilogd.html">ucspilogd</a> program,
+combined with the
+<a href="http://skarnet.org/software/s6-networking/">s6-networking</a>
+package, provides enough tools to easily implement
+a complete syslogd system, for a small fraction of the resource needs and
+the complexity of native syslogd implementations. </li>
+</ul>
+
+<p>
+ So, given a program, where are its logs sent&nbsp;?
+</p>
+
+<ul>
+ <li> Logs sent via syslog() will be handled by the syslogd service as
+usual. Smart administrators will make sure that those ones are as few as
+possible. The rest of this analysis is about logs sent to stderr. </li>
+ <li> If the program is descended from a user's interactive program,
+its logs are sent to the user's terminal or the user's choice redirection
+target. </li>
+ <li> If the program is descended from a logged service, its logs are
+naturally sent to the service's logger. </li>
+ <li> Else the logs are sent to the catch-all logger. </li>
+ <li> Only the catch-all logger's error messages, the kernel's fatal
+error messages, and maybe process 1's error messages, are sent to the
+system console. </li>
+</ul>
+
+<a name="#howtouse"><h3> What does s6-log have to do with all this ? </h3></a>
+
+<p>
+ In a <em>logging chain</em> situation, every service must have
+its own logger. To avoid syslogd's design mistakes, one logger process
+per service must be run. s6-log fits that role. Using s6-log as
+your one-stop logger offers the following benefits:
+</p>
+
+<ul>
+ <li> Every instance of s6-log can run as a different user, so it's
+easy to give different access rights to different logs. It is also
+more secure not to have any logger process running as root. </li>
+ <li> s6-log consumes very little memory per instance (unless it
+accumulates unflushed log lines, which you can avoid with the
+<tt>-b</tt> option). So, launching a lot of s6-log processes does
+not waste resources. </li>
+ <li> s6-log is vastly configurable via logging scripts; every instance
+is as powerful as a traditional syslogd. </li>
+ <li> s6-log can log to a RAM filesystem and thus is suitable as a
+catch-all logger. Clever tricks like Upstart's <em>logd</em> or daemontools'
+<a href="http://cr.yp.to/daemontools/readproctitle.html">readproctitle</a>
+are just that: tricks. s6-log gives a unified interface to all of
+your system's loggers. </li>
+</ul>
+
+
+<a name="#network"><h3> You're wrong about being as powerful as
+syslogd: s6-log does not do remote logging. </h3></a>
+
+<p>
+ You mean you want to send, <em>live</em>, every <em>log line</em>
+over the network via <em>UDP</em>&nbsp;? You can't be serious.
+</p>
+
+<p>
+ Do yourself a favor and use s6-log to write log lines to a logdir,
+with a processor script that sends files-being-archived to the
+network, possibly after compressing them. More reliability, less
+log lines lost, less network traffic, better engineering. If you
+have no disk to even write the <tt>current</tt> files to, write
+to a small RAM filesystem.
+</p>
+
+<p>
+ If you <em>have to</em> log stuff <em>live</em> via the network, you
+do not need any local logging software. You don't even need syslogd.
+Just filter your stderr via some <tt>grep</tt> that selects lines for
+you, then sends them to a network socket. A trivial shell script, or
+<a href="http://skarnet.org/software/execline/">execline</a>
+script, can do that for you.
+</p>
+
+<p>
+ Do not insist on using syslogd. It does nothing magical, and nothing
+that can't be done in a simpler way using simpler tools.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-mkfifodir.html b/doc/s6-mkfifodir.html
new file mode 100644
index 0000000..818a514
--- /dev/null
+++ b/doc/s6-mkfifodir.html
@@ -0,0 +1,46 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-mkfifodir program</title>
+    <meta name="Description" content="s6: the s6-mkfifodir program" />
+    <meta name="Keywords" content="s6 command s6-mkfifodir fifodir notification creation" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-mkfifodir program </h1>
+
+<p>
+s6-mkfifodir creates a <a href="fifodir.html">fifodir</a>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-mkfifodir [ -f ] [ -g <em>gid</em> ] <em>fifodir</em>
+</pre>
+
+<p>
+s6-mkfifodir creates <em>fifodir</em>, belonging to the current user.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-f</tt>&nbsp;: force permissions. If <em>fifodir</em> already exists,
+change its permissions according to the <tt>-g</tt> options. By default, if
+<em>fifodir</em> exists, s6-mkfifodir does nothing. </li>
+ <li> <tt>-g <em>gid</em></tt>&nbsp;: make <em>fifodir</em> only listenable
+by members of group <em>gid</em>. If this option is not given, the fifodir is
+made publically listenable. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-notifywhenup.html b/doc/s6-notifywhenup.html
new file mode 100644
index 0000000..ad7ef8e
--- /dev/null
+++ b/doc/s6-notifywhenup.html
@@ -0,0 +1,80 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-notifywhenup program</title>
+    <meta name="Description" content="s6: the s6-notifywhenup program" />
+    <meta name="Keywords" content="s6 command s6-notifywhenup fifodir notification event notifier send service daemon ready" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-notifywhenup program </h1>
+
+<p>
+s6-notifywhenup launches a daemon while listening to a file descriptor,
+and sends a 'U' event to a <a href="fifodir.html">fifodir</a> when it
+receives something on that file descriptor.
+</p>
+
+<p>
+<a href="notifywhenup.html">This page</a> explains why this program is
+needed.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-notifywhenup [ -d <em>fd</em> ] [ -e <em>fifodir</em> ] [ -f ] [ -t <em>timeout</em> ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-notifywhenup forks and executes <em>prog...</em> as the
+parent, with a pipe from <em>prog...</em>'s stdout to the child. </li>
+ <li> The child waits for EOF on the pipe. When it gets it, if there
+have been other characters written before the EOF, it sends a 'U'
+event to the <tt>./event</tt> fifodir. </li>
+ <li> The child exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-d&nbsp;<em>fd</em></tt>&nbsp;: listen to
+<em>prog</em>'s output on descriptor <em>fd</em>. The default is 1. </li>
+ <li> <tt>-e&nbsp;<em>fifodir</em></tt>&nbsp;: send a 'U' event to fifodir
+<em>fifodir</em>. Default is <tt>./event</tt>. </li>
+ <li> <tt>-f</tt>&nbsp;: simple fork. Normally, s6-notifywhenup doubleforks,
+in order to accommodate for a
+<em>prog</em> that does not expect to have a child and avoid a
+pending zombie. This option avoids the doublefork, but it should only be
+set if <em>prog</em> reaps even children it doesn't know it has. </li>
+ <li> <tt>-t&nbsp;<em>timeout</em></tt>&nbsp;: if no EOF has been received
+after <em>timeout</em> milliseconds, exit without sending the event.
+Default is 0, meaning infinite. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-notifywhenup executes <em>prog...</em> as the parent in order
+for <em>prog</em> to keep the same pid, which is vital for supervised
+processes. </li>
+ <li> s6-notifywhenup can be used, for instance, with
+<a href="http://skarnet.org/software/s6-networking/s6-tcpserver.html">s6-tcpserver</a>
+and its <tt>-1</tt> option, so that reliable startup notification is
+achieved. <tt>s6-notifywhenup -f s6-tcpserver -1 <em>args</em></tt> will
+send a 'U' event to <tt>./event</tt> when s6-tcpserver is actually
+listening to its network socket. </li>
+ <li> The <a href="s6-svwait.html">s6-svwait</a> program can be used
+to wait for 'U' events. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-setlock.html b/doc/s6-setlock.html
new file mode 100644
index 0000000..bab3e23
--- /dev/null
+++ b/doc/s6-setlock.html
@@ -0,0 +1,65 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-setlock program</title>
+    <meta name="Description" content="s6: the s6-setlock program" />
+    <meta name="Keywords" content="s6 command s6-setlock lock program" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-setlock program </h1>
+
+<p>
+s6-setlock takes a lock on a file, then executes into another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-setlock [ -n | -N | -t <em>timeout</em> ] [ -r | -w ] <em>file</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-setlock creates <em>file</em> if it does not exist and opens it for writing. </li>
+ <li> It locks <em>file</em>. If it cannot take the lock for any reason, it exits 1. </li>
+ <li> It executes into <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: nonblocking lock. If s6-setlock cannot acquire the lock, it will
+exit 1 immediately. </li>
+ <li> <tt>-N</tt>&nbsp;: blocking lock. s6-setlock will wait until it can acquire the lock.
+This is the default. </li>
+ <li> <tt>-t&nbsp;<em>timeout</em>&nbsp;: timed lock. If s6-setlock cannot acquire
+the lock after <em>timeout</em> milliseconds, it will exit 1. </li>
+ <li> <tt>-r</tt>&nbsp;: shared lock. Other shared locks on the same file will not prevent
+the lock from being acquired (but an exclusive lock will). </li>
+ <li> <tt>-w</tt>&nbsp;: exclusive lock. This is the default. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-setlock leaks an open file descriptor into the <em>prog</em>
+execution. This is intended: the fd holds the lock, which is released
+when <em>prog</em> exits. <em>prog</em> must not touch fds it does not
+know about. </li>
+ <li> If the timed lock option is chosen, s6-setlock does not acquire the lock
+itself. Instead, it spawns a <a href="libs6lock/s6lockd-helper.html">s6lockd-helper</a>
+process that acquires the lock while s6-setlock controls the timeout; the
+s6lockd-helper process then holds the lock and lives as long as
+<em>prog</em>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-setsid.html b/doc/s6-setsid.html
new file mode 100644
index 0000000..2fe991b
--- /dev/null
+++ b/doc/s6-setsid.html
@@ -0,0 +1,47 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-setsid program</title>
+    <meta name="Description" content="s6: the s6-setsid program" />
+    <meta name="Keywords" content="s6 command s6-setsid session leader setting" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-setsid program </h1>
+
+<p>
+s6-setsid runs a program as session leader.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-setsid [ -I | -i ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-setsid creates a new session if it is not a session leader, and becomes
+session leader of this new session. </li>
+ <li> It then executes into <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-i</tt>&nbsp;: strict. If s6-setsid is already a session leader, it will
+exit 111 with an error message. </li>
+ <li> <tt>-I</tt>&nbsp;: loose. If s6-setsid is already a session leader, it will
+print a warning message, but exec into <em>prog</em> nonetheless. This is the
+default. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-setuidgid.html b/doc/s6-setuidgid.html
new file mode 100644
index 0000000..f12ae04
--- /dev/null
+++ b/doc/s6-setuidgid.html
@@ -0,0 +1,49 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-setuidgid program</title>
+    <meta name="Description" content="s6: the s6-setuidgid program" />
+    <meta name="Keywords" content="s6 command s6-setuidgid uid gid groups privilege dropping loss user change su" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-setuidgid program </h1>
+
+<p>
+s6-setuidgid executes a program as another user.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-setuidgid <em>account</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> If <em>account</em> contains a colon, it is interpreted as <em>uid:gid</em>,
+else it is interpreted as a username and looked up by name in the account
+database. </li>
+ <li> If <em>account</em> is unknown, s6-setuidgid exits 1. </li>
+ <li> s6-setuidgid sets its (real and effective) uid and gid to those of <em>account</em>.
+It also sets the list of supplementary groups to the correct one for <em>account</em>
+according to the group database. </li>
+<li> Then it executes into <em>prog...</em>. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-setuidgid can only be run as root. Its main use is to drop root privileges before
+starting a daemon.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-softlimit.html b/doc/s6-softlimit.html
new file mode 100644
index 0000000..4b112f8
--- /dev/null
+++ b/doc/s6-softlimit.html
@@ -0,0 +1,54 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-softlimit program</title>
+    <meta name="Description" content="s6: the s6-softlimit program" />
+    <meta name="Keywords" content="s6 command s6-softlimit process limits" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-softlimit program </h1>
+
+<p>
+s6-softlimit changes its process limits, then executes into another program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-softlimit [ -a <em>allmem</em> ] [ -c <em>core</em> ] [ -d <em>data</em> ] [ -f <em>fsize</em> ] [ -l <em>lock</em> ] [ -m <em>mem</em> ] [ -o <em>ofiles</em> ] [ -p <em>proc</em> ] [ -r <em>res</em> ] [ -s <em>stack</em> ] [ -t <em>cpusecs</em> ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-softlimit parses its options and sets process (soft) resource limits accordingly. </li>
+ <li> A value of '=' for any option means "set that limit to the hard limit". </li>
+ <li> Depending on your operating system, an option may do nothing. </li>
+ <li> When s6-softlimit has modified all the limits successfully, it executes into <em>prog...</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-a&nbsp;<em>allmem</em></tt>&nbsp;: limit the total available memory to <em>allmem</em> bytes. </li>
+ <li> <tt>-c&nbsp;<em>core</em></tt>&nbsp;: limit the core file size to <em>core</em> bytes. </li>
+ <li> <tt>-d&nbsp;<em>data</em></tt>&nbsp;: limit the available heap memory to <em>data</em> bytes. </li>
+ <li> <tt>-f&nbsp;<em>fsize</em></tt>&nbsp;: limit the file size to <em>fsize</em> bytes. </li>
+ <li> <tt>-l&nbsp;<em>lock</em></tt>&nbsp;: limit the available locked memory to <em>lock</em> bytes. </li>
+ <li> <tt>-m&nbsp;<em>mem</em></tt>&nbsp;: limit all types of memory to <em>mem</em> bytes. </li>
+ <li> <tt>-o&nbsp;<em>ofiles</em></tt>&nbsp;: limit the number of open fds to <em>ofiles</em>. </li>
+ <li> <tt>-p&nbsp;<em>proc</em></tt>&nbsp;: limit the number of processes to <em>proc</em> (per user). </li>
+ <li> <tt>-r&nbsp;<em>allmem</em></tt>&nbsp;: limit the available physical memory to <em>res</em> bytes. </li>
+ <li> <tt>-s&nbsp;<em>stack</em></tt>&nbsp;: limit the available stack memory to <em>stack</em> bytes. </li>
+ <li> <tt>-t&nbsp;<em>cpusecs</em></tt>&nbsp;: limit the available CPU time to <em>cpusecs</em> seconds. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-supervise.html b/doc/s6-supervise.html
new file mode 100644
index 0000000..1c1551e
--- /dev/null
+++ b/doc/s6-supervise.html
@@ -0,0 +1,125 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-supervise program</title>
+    <meta name="Description" content="s6: the s6-supervise program" />
+    <meta name="Keywords" content="s6 command s6-supervise servicedir supervision supervise" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-supervise program </h1>
+
+<p>
+s6-supervise monitors a long-lived process (or <em>service</em>), making sure it
+stays alive, sending notifications to registered processes when it dies, and
+providing an interface to control its state. s6-supervise is designed to be the
+last non-leaf branch of a <em>supervision tree</em>, the supervised process
+being a leaf.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-supervise <em>servicedir</em>
+</pre>
+
+<ul>
+ <li> s6-supervise switches to the <em>servicedir</em>
+<a href="servicedir.html">service directory</a>. </li>
+ <li> It exits 100 if another s6-supervise process is already monitoring this service. </li>
+ <li> If the <tt>./event</tt> <a href="fifodir.html">fifodir</a> does not exist,
+s6-supervise creates it and allows public subscriptions to it.
+If it already exists, it uses it as is, without modifying the subscription rights. </li>
+ <li> It <a href="libftrigw.html">sends</a> a <tt>'s'</tt> event to <tt>./event</tt>. </li>
+ <li> If the default service state is up, s6-supervise spawns <tt>./run</tt>. </li>
+ <li> s6-supervise sends a <tt>'u'</tt> event to <tt>./event</tt> whenever it
+successfully spawns <tt>./run</tt>. </li>
+ <li> When <tt>./run</tt> dies, s6-supervise sends a <tt>'d'</tt> event to <tt>./event</tt>. </li>
+ <li> When <tt>./run</tt> dies, s6-supervise spawns <tt>./finish</tt> if it exists. </li>
+ <li> <tt>./finish</tt> must exit in less than 5 seconds. If it takes more than that,
+s6-supervise kills it. </li>
+ <li> When <tt>./finish</tt> dies, s6-supervise restarts <tt>./run</tt> unless it has been
+told not to. </li>
+ <li> There is a minimum 1-second delay between two <tt>./run</tt> spawns, to avoid busylooping
+if <tt>./run</tt> exits too quickly. </li>
+ <li> When killed or asked to exit, it waits for the service to go down one last time, then
+sends a <tt>'x'</tt> event to <tt>./event</tt> before exiting 0. </li>
+</ul>
+
+<h2> Signals </h2>
+
+<p>
+ s6-supervise reacts to the following signals:
+</p>
+
+<ul>
+ <li> SIGTERM: bring down the service and exit, as if a
+<a href="s6-svc.html">s6-svc -xd</a> command had been received </li>
+ <li> SIGHUP: exit as soon as the service stops, as if a
+<a href="s6-svc.html">s6-svc -x</a> command had been received </li>
+ <li> SIGQUIT: currently like SIGTERM, but this might change in the future </li>
+</ul>
+
+<h2> Usage notes </h2>
+
+<ul>
+ <li> s6-supervise is a long-lived process. It normally runs forever, from the system's
+boot scripts, until shutdown time; it should not be killed or told to exit. If you have
+no use for a service, just turn it off; the s6-supervise process does not hurt. </li>
+ <li> Even in boot scripts, s6-supervise should normally not be run directly. It's
+better to have a collection of <a href="servicedir.html">service directories</a> in a
+single <a href="scandir.html">scan directory</a>, and just run
+<a href="s6-svscan.html">s6-svscan</a> on that scan directory. s6-svscan will spawn
+the necessary s6-supervise processes, and will also take care of logged services. </li>
+ <li> You can use <a href="s6-svc.html">s6-svc</a> to send commands to the s6-supervise
+process; mostly to change the service state and send signals to the monitored
+process. </li>
+ <li> You can use <a href="s6-svok.html">s6-svok</a> to check whether s6-supervise
+is successfully running. </li>
+ <li> You can use <a href="s6-svstat.html">s6-svstat</a> to check the status of a
+service. </li>
+ <li> s6-supervise maintains internal information inside the <tt>./supervise</tt>
+subdirectory of <em>servicedir</em>. <em>servicedir</em> itself can be read-only,
+but both <em>servicedir</em><tt>/supervise</tt> and <em>servicedir</em><tt>/event</tt>
+need to be read-write. </li>
+ <li> The <tt>./finish</tt> script is not guaranteed to have stdin and
+stdout pointing to the same locations as the <tt>./run</tt> script. More
+precisely: the stdin and stdout will be preserved for <tt>./finish</tt>
+until s6-supervise is asked to exit, but the last <tt>./finish</tt>
+execution will have its stdin and stdout redirected to <tt>/dev/null</tt>.
+(This is to avoid maintaining open descriptors when a service is down, which
+would prevent its logger from exiting cleanly.) </li>
+</ul>
+
+<h2> Implementation notes </h2>
+
+<ul>
+ <li> s6-supervise tries its best to stay alive and running despite possible
+system call failures. It will write to its standard error everytime it encounters a
+problem. However, unlike <a href="s6-svscan.html">s6-svscan</a>, it will not go out
+of its way to stay alive; if it encounters an unsolvable situation, it will just
+die. </li>
+ <li> Unlike other "supervise" implementations, s6-supervise is a fully asynchronous
+state machine. That means that it can read and process commands at any time, even
+when the machine is in trouble (full process table, for instance). </li>
+ <li> s6-supervise <em>does not use malloc()</em>. That means it will <em>never leak
+memory</em>. <small>However, s6-supervise uses opendir(), and most opendir()
+implementations internally use heap memory - so unfortunately, it's impossible to
+guarantee that s6-supervise does not use heap memory at all.</small> </li>
+ <li> s6-supervise has been carefully designed so every instance maintains as little
+data as possible, so it uses a very small
+amount of non-sharable memory. It is not a problem to have several
+dozens of s6-supervise processes, even on constrained systems: resource consumption
+will be negligible. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-svc.html b/doc/s6-svc.html
new file mode 100644
index 0000000..72f0776
--- /dev/null
+++ b/doc/s6-svc.html
@@ -0,0 +1,104 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-svc program</title>
+    <meta name="Description" content="s6: the s6-svc program" />
+    <meta name="Keywords" content="s6 command s6-svc supervise command service" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-svc program </h1>
+
+<p>
+s6-svc sends commands to a running <a href="s6-supervise.html">s6-supervise</a>
+process. In other words, it's used to control a supervised process; among
+other benefits, it allows an administrator to send signals to daemons without
+knowing their PIDs, and without using horrible hacks such as .pid files.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-svc [ -abqhkti12fFpcoduxO ] <em>servicedir</em>
+</pre>
+
+<p>
+s6-svc sends the given series of commands to the
+<a href="s6-supervise.html">s6-supervise</a> process monitoring the
+<em>servicedir</em> directory, then exits 0. It exits 111 if it cannot send
+a command, or 100 if no s6-supervise process is running on <em>servicedir</em>.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-a</tt>&nbsp;: send a SIGALRM to the supervised process </li>
+ <li> <tt>-b</tt>&nbsp;: send a SIGABRT to the supervised process </li>
+ <li> <tt>-q</tt>&nbsp;: send a SIGQUIT to the supervised process </li>
+ <li> <tt>-h</tt>&nbsp;: send a SIGHUP to the supervised process </li>
+ <li> <tt>-k</tt>&nbsp;: send a SIGKILL to the supervised process </li>
+ <li> <tt>-t</tt>&nbsp;: send a SIGTERM to the supervised process </li>
+ <li> <tt>-i</tt>&nbsp;: send a SIGINT to the supervised process </li>
+ <li> <tt>-1</tt>&nbsp;: send a SIGUSR1 to the supervised process </li>
+ <li> <tt>-2</tt>&nbsp;: send a SIGUSR2 to the supervised process </li>
+ <li> <tt>-p</tt>&nbsp;: send a SIGSTOP to the supervised process </li>
+ <li> <tt>-c</tt>&nbsp;: send a SIGCONT to the supervised process </li>
+ <li> <tt>-o</tt>&nbsp;: once. Equivalent to "-uO". </li>
+ <li> <tt>-d</tt>&nbsp;: down. If the supervised process is up, send it
+a SIGTERM and a SIGCONT. Do not restart it. </li>
+ <li> <tt>-u</tt>&nbsp;: up. If the supervised process is down, start it.
+Automatically restart it when it dies. </li>
+ <li> <tt>-x</tt>&nbsp;: exit. When the service is asked to be down and
+the supervised process dies, supervise will exit too. This command should
+normally never be used on a working system. </li>
+ <li> <tt>-O</tt>&nbsp;: Once at most. Do not restart the supervised process
+when it dies. If it is down when the command is received, do not even start
+it. </li>
+ <li> <tt>-f</tt>,&nbsp;<tt>-F</tt>&nbsp;: unused for now. </li>
+</ul>
+
+<h2> Usage examples </h2>
+
+<pre> s6-svc -h /service/httpd </pre>
+<p>
+ Send a SIGHUP to the process represented by the <tt>/service/httpd</tt>
+service directory. Traditionally, this makes web servers reload their
+configuration file.
+</p>
+
+<pre> s6-svc -t /service/sshd </pre>
+<p>
+ Kill (and automatically restart, if the wanted state of the service is up)
+the process represented by the <tt>/service/sshd</tt> service directory -
+typically the sshd server.
+</p>
+
+<pre> s6-svc -d /service/ftpd </pre>
+<p>
+ Take down the ftpd server.
+</p>
+
+<pre> s6-svc -a /service/httpd/log </pre>
+<p>
+ Send a SIGALRM to the logger process for the httpd server. If this logger
+process is <a href="s6-log.html">s6-log</a>, this triggers a log rotation.
+</p>
+
+<h2> Internals </h2>
+
+<p>
+s6-svc writes control commands into the <tt><em>servicedir</em>/supervise/control</tt>
+FIFO. A s6-supervise process running on <em>servicedir</em> will be listening to this FIFO,
+and will read and interpret those commands.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-svok.html b/doc/s6-svok.html
new file mode 100644
index 0000000..a43d253
--- /dev/null
+++ b/doc/s6-svok.html
@@ -0,0 +1,38 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-svok program</title>
+    <meta name="Description" content="s6: the s6-svok program" />
+    <meta name="Keywords" content="s6 command s6-svok servicedir checking supervision s6-supervise" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-svok program </h1>
+
+<p>
+s6-svok checks whether a <a href="servicedir.html">service directory</a> is
+currently supervised.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-svok <em>servicedir</em>
+</pre>
+
+<ul>
+<li> s6-svok checks whether a <a href="s6-supervise.html">s6-supervise</a>
+process is currently monitoring <em>fifodir</em>. </li>
+<li> It exits 0 if there is one, or 1 if there is none. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-svscan-1.html b/doc/s6-svscan-1.html
new file mode 100644
index 0000000..76bc31c
--- /dev/null
+++ b/doc/s6-svscan-1.html
@@ -0,0 +1,374 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: How to run s6-svscan as process 1</title>
+    <meta name="Description" content="s6: s6-svscan as init" />
+    <meta name="Keywords" content="s6 supervision svscan s6-svscan init process boot 1" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> How to run s6-svscan as process 1 </h1>
+
+<p>
+ It is possible to run s6-svscan as process 1, i.e. the <tt>init</tt>
+process. However, that does not mean you can directly <em>boot</em>
+on s6-svscan; that little program cannot do everything
+your stock init does. Replacing the <tt>init</tt> process requires a
+bit of understanding of what is going on.
+</p>
+
+<a name="stages">
+<h2> The three stages of init </h2>
+</a>
+
+<p>
+ The life of a Unix machine has three stages:
+</p>
+
+<ol>
+ <li> The <em>early initialization</em> phase. It starts when the
+kernel launches the first userland process, traditionally called <tt>init</tt>.
+During this phase, init is the only lasting process; its duty is to
+prepare the machine for the start of <em>other</em> long-lived processes,
+i.e. services. Work such as mounting filesystems, setting the system clock,
+etc. can be done at this point. This phase ends when process 1 launches
+its first services. </li>
+ <li> The <em>cruising</em> phase. This is the "normal", stable state of an
+up and running Unix machine. Early work is done, and init launches and
+maintains <em>services</em>, i.e. long-lived processes such as gettys,
+the ssh server, and so on. During this phase, init's duties are to reap
+orphaned zombies and to supervise services - also allowing the administrator
+to add or remove services. This phase ends when the administrator
+requires a shutdown. </li>
+ <li> The <em>shutdown</em> phase. Everything is cleaned up, services are
+stopped, filesystems are unmounted, the machine is getting ready to be
+halted. During this phase, everything but the shutdown procedure gets
+killed - the only surefire way to kill everything is <tt>kill -9 -1</tt>,
+and only process 1 can survive it and keep working: it's only logical
+that the shutdown procedure, or at least the shutdown procedure from
+the <tt>kill -9 -1</tt> on and until the final poweroff or reboot
+command, is performed by process 1. </li>
+</ol>
+
+<p>
+ As you can see, process 1's duties are <em>radically different</em> from
+one stage to the next, and init has the most work when the machine
+is booting or shutting down, which means a normally negligible fraction
+of the time it is up. The only common thing is that at no point is process
+1 allowed to exit.
+</p>
+
+<p>
+ Still, all common init systems insist that the same <tt>init</tt>
+executable must handle these three stages. From System V init to launchd,
+via busybox init, you name it - one init program from bootup to shutdown.
+No wonder those programs, even basic ones, seem complex to write and
+complex to understand!
+</p>
+
+<p>
+Even the <a href="http://smarden.org/runit/runit.8.html">runit</a>
+program, designed with supervision in mind, remains as process 1 all the
+time; at least runit makes things simple by clearly separating the three
+stages and delegating every stage's work to a different script that is
+<em>not</em> run as process 1. (This requires very careful handling of the
+<tt>kill -9 -1</tt> part of stage 3, though.)
+</p>
+
+<p>
+ One init to rule them all?
+<a href="http://en.wikipedia.org/wiki/Porgy_and_Bess">It ain't necessarily so!</a>
+</p>
+
+<a name="stage2">
+<h2> The role of s6-svscan </h2>
+</a>
+
+<p>
+ init does not have the right to die, but fortunately, <em>it has the right
+to <a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/execve.html">execve()</a>!</em>
+During stage 2, why use precious RAM, or at best, swap space, to store data
+that are only relevant to stages 1 or 3? It only makes sense to have an
+init process that handles stage 1, then executes into an init process that
+handles stage 2, and when told to shutdown, this "stage 2" init executes into
+a "stage 3" init which just performs shutdown. Just as runit does with the
+<tt>/etc/runit/[123]</tt> scripts, but exec'ing the scripts as process 1
+instead of forking them.
+</p>
+
+<p>
+It becomes clear now that
+<a href="s6-svscan.html">s6-svscan</a> is perfectly suited to
+exactly fulfill process 1's role <strong>during stage 2</strong>.
+</p>
+
+<ul>
+ <li> It does not die </li>
+ <li> The reaper takes care of every zombie on the system </li>
+ <li> The scanner maintains services alive </li>
+ <li> It can be sent commands via the <a href="s6-svscanctl.html">s6-svscanctl</a>
+interface </li>
+ <li> It execs into a given script when told to </li>
+</ul>
+
+<p>
+ However, an init process for stage 1 and another one for stage 3 are still
+needed. Fortunately, those processes are very easy to design! The only
+difficulty here is that they're heavily system-dependent, so it's not possible
+to provide a stage 1 init and a stage 3 init that will work everywhere.
+s6 was designed to be as portable as possible, and it should run on virtually
+every Unix platform; but outside of stage 2 is where portability stops, and
+the s6 package can't help you there.
+</p>
+
+<p>
+ Here are some tips though.
+</p>
+
+<a name="stage1">
+<h2> How to design a stage 1 init </h2>
+</a>
+
+<h3> What stage 1 init must do </h3>
+
+<ul>
+ <li> Prepare an initial <a href="scandir.html">scan directory</a>, say in
+<tt>/service</tt>, with a few vital services, such as s6-svscan's own logger,
+and an early getty (in case debugging is needed). That implies mounting a
+read-write filesystem, creating it in RAM if needed, if the root filesystem
+is read-only. </li>
+ <li> Either perform all the one-time initialization, as stage 1
+<a href="http://smarden.org/runit/">runit</a> does; </li>
+ <li> or fork a process that will perform most of the one-time initialization
+once s6-svscan is in charge. </li>
+ <li> Be extremely simple and not fail, because recovery is almost impossible
+here. </li>
+</ul>
+
+<p>
+ Unlike the <tt>/etc/runit/1</tt> script, an init-stage1 script running as
+process 1 has nothing to back it up, and if it fails and dies, the machine
+crashes. Does that mean the runit approach is better? It's certainly safer,
+but not necessarily better, because init-stage1 can be made <em>extremely
+small</em>, to the point it is practically failproof, and if it fails, it
+means something is so wrong that you
+would have had to reboot the machine with <tt>init=/bin/sh</tt> anyway.
+</p>
+
+<p>
+ To make init-stage1 as small as possible, only this realization is needed:
+you do not need to perform all of the one-time initialization tasks before
+launching s6-svscan. Actually, once init-stage1 has made it possible for
+s6-svscan to run, it can fork a background "init-stage2" process and exec
+into s6-svscan immediately! The "init-stage2" process can then pursue the
+one-time initialization, with a big advantage over the "init-stage1"
+process: s6-svscan is running, as well as a few vital services, and if
+something bad happens, there's a getty for the administrator to log on.
+No need to play fancy tricks with <tt>/dev/console</tt> anymore! Yes,
+the theoretical separation in 3 stages is a bit more supple in practice:
+the "stage 2" process 1 can be already running when a part of the
+"stage 1" one-time tasks are still being run.
+</p>
+
+<p>
+ Of course, that means that the scan directory is still incomplete when
+s6-svscan first starts, because most services can't yet be run, for
+lack of mounted filesystems, network etc. The "init-stage2" one-time
+initialization script must populate the scan directory when it has made
+it possible for all wanted services to run, and trigger the scanner.
+Once all the one-time tasks are done, the scan directory is fully
+populated and the scanner has been triggered, the machine is fully
+operational and in stage 2, and the "init-stage2" script can die.
+</p>
+
+<h3> Is it possible to write stage 1 init in a scripting language? </h3>
+
+<p>
+ It is very possible, and I even recommend it. If you are using
+s6-svscan as stage 2 init, stage 1 init should be simple enough
+that it can be written in any scripting language you want, just
+as <tt>/etc/runit/1</tt> is if you're using runit. And since it
+should be so small, the performance impact will be negligible,
+while maintainability is enhanced. Definitely make your stage 1
+init a script.
+</p>
+
+<p>
+ Of course, most people will use the <em>shell</em> as scripting
+language; however, I advocate the use of
+<a href="http://www.skarnet.org/software/execline/">execline</a>
+for this, and not only for the obvious reasons. Piping s6-svscan's
+stderr to a logging service before said service is even up requires
+some <a href="#log">tricky fifo handling</a> that execline can do
+and the shell cannot.
+</p>
+
+<a name="stage3">
+<h2> How to design a stage 3 init </h2>
+</a>
+
+<p>
+ If you're using s6-svscan as stage 2 init on <tt>/service</tt>, then
+stage 3 init is naturally the <tt>/service/.s6-svscan/finish</tt> program.
+Of course, <tt>/service/.s6-svscan/finish</tt> can be a symbolic link
+to anything else; just make sure it points to something in the root
+filesystem (unless your program is an execline script, in which case
+it is not even necessary).
+</p>
+
+<h3> What stage 3 init must do </h3>
+
+<ul>
+ <li> Destroy the supervision tree and stop all services </li>
+ <li> Kill all processes <em>save itself</em>, first gently, then harshly </li>
+ <li> Unmount all the filesystems </li>
+ <li> Halt or reboot the machine, depending on what root asked for </li>
+</ul>
+
+<p>
+ This is also very simple; even simpler than stage 1.
+ The only tricky part is the <tt>kill -9 -1</tt> phase: you must make sure
+that <em>process 1</em> regains control and keeps running after it, because
+it will be the only process left alive. But since we're running stage 3
+init directly, it's almost automatic! this is an advantage of running
+the shutdown procedure as process 1, as opposed to, for instance,
+<tt>/etc/runit/3</tt>.
+</p>
+
+<h3> Is it possible to write stage 3 init in a scripting language? </h3>
+
+<p>
+ You'd have to be a masochist, or have extremely specific needs, not to
+do so.
+</p>
+
+<a name="log">
+<h2> How to log the supervision tree's messages </h2>
+</a>
+
+<p>
+ When the Unix kernel launches your (stage 1) init process, it does it
+with descriptors 0, 1 and 2 open and reading from or writing to
+<tt>/dev/console</tt>. This is okay for the early boot: you actually
+want early error messages to be displayed to the system console. But
+this is not okay for stage 2: the system console should only be used
+to display extremely serious error messages such as kernel errors, or
+errors from the logging system itself; everything else should be
+handled by the logging system, following the
+<a href="s6-log.html#loggingchain">logging chain</a> mechanism. The
+supervision tree's messages should go to the catch-all logger instead
+of the system console. (And the console should never be read, so no
+program should run with <tt>/dev/console</tt> as stdin, but this is easy
+enough to fix: s6-svscan will be started with stdin redirected from
+<tt>/dev/null</tt>.)
+</p>
+
+<p>
+ The catch-all logger is a service, and we want <em>every</em>
+service to run under the supervision tree. Chicken and egg problem:
+before starting s6-svscan, we must redirect s6-svscan's output to
+the input of a program that will only be started once s6-svscan is
+running and can start services.
+</p>
+
+<p>
+ There are several solutions to this problem, but the simplest one is
+to use a FIFO, a.k.a. named pipe. s6-svscan's stdout and stderr can
+be redirected to a named pipe before s6-svscan is run, and the
+catch-all logger service can be made to read from this named pipe.
+Only two minor problems remain:
+</p>
+
+<ul>
+ <li> If s6-svscan or s6-supervise writes to the FIFO before there is
+a reader, i.e. before the catch-all logging service is started, the
+write will fail (and a SIGPIPE will be emitted). This is not a real issue
+for an s6 installation because s6-svscan and s6-supervise ignore SIGPIPE,
+and they only write
+to their stderr if an error occurs; and if an error occurs before they are
+able to start the catch-all logger, this means that the system is seriously
+damaged (as if an error occurs during stage 1) and the only solution is
+to reboot with <tt>init=/bin/sh</tt> anyway. </li>
+ <li> Normal Unix semantics <em>do not allow</em> a writer to open a
+FIFO before there is a reader: if there is no reader when the FIFO is
+opened for writing, the <tt>open()</tt> system call <em>blocks</em>
+until a reader appears. This is obviously not what we want: we want
+to be able to <em>actually start</em> s6-svscan with its stdout and
+stderr pointing to the logging FIFO, even without a reader process,
+and we want it to run normally so it can start the logging service
+that will provide such a reader process. </li>
+</ul>
+
+<p>
+ This second point cannot be solved in a shell script, and that is why
+you are discouraged to write your stage 1 init script in the shell
+language: you cannot properly set up a FIFO output for s6-svscan without
+resorting to horrible and unreliable hacks involving a temporary background
+FIFO reader process.
+</p>
+
+<p>
+ Instead, you are encouraged to use the
+<a href="http://skarnet.org/software/execline/">execline</a> language -
+or, at least,
+the <a href="http://skarnet.org/software/execline/redirfd.html">redirfd</a>
+command, which is part of the execline distribution. The
+<a href="http://www.skarnet.org/software/execline/redirfd.html">redirfd</a>
+command does just the right amount of trickery with FIFOs for you to be
+able to properly redirect process 1's stdout and stderr to the logging FIFO
+without blocking: <tt>redirfd -w 1 /service/s6-svscan-log/fifo</tt> blocks
+if there's no process reading on <tt>/service/s6-svscan-log/fifo</tt>, but
+<tt>redirfd -wnb 1 /service/s6-svscan-log/fifo</tt> <em>does not</em>.
+</p>
+
+<p>
+ This trick with FIFOs can even be used to avoid potential race conditions
+in the one-time initialization script that runs in stage 2. If forked from
+init-stage1 right before executing s6-svscan, depending on the scheduler
+mood, this script may actually run a long way before s6-svscan is actually
+executed and running the initial services - and may do dangerous things,
+such as writing messages to the logging FIFO before there's a reader, and
+eating a SIGPIPE and dying without completing the initialization. To avoid
+that and be sure that s6-svscan really runs and initial services are really
+started before the stage 2 init script is allowed to continue, it is possible
+to redirect the child script's output (stdout and/or stderr) <em>once again</em>
+to the logging FIFO, but in the normal way without redirfd trickery,  before
+it execs into the init-stage2 script. So, the child process blocks on the
+FIFO until a reader appears, while process 1 - which does not block - execs
+into s6-svscan and starts the logging service, which then opens the logging
+FIFO for reading and unblocks the child process, which then runs the
+initialization tasks with the guarantee that s6-svscan is running.
+</p>
+
+<p>
+ It really is simpler than it sounds. :-)
+</p>
+
+<h2> A working example </h2>
+
+<p>
+ This whole page may sound very theoretical, dry, wordy, and hard to
+grasp without a live example to try things on; unfortunately, s6 cannot provide
+live examples without becoming system-specific. However, it provides a whole
+set of script skeletons for you to edit and make your own working init.
+</p>
+
+<p>
+ The <tt>examples/ROOT</tt> subdirectory in the s6 distribution contains
+the relevant parts of a small root filesystem that works under Linux and follows
+all that has been explained here. In every directory, a <tt>README</tt> file
+has been added, to sum up what this directory does. You can copy those files
+and modify them to suit your needs; if you have the proper software installed,
+and the right configuration, some of them might even work verbatim.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-svscan-not-1.html b/doc/s6-svscan-not-1.html
new file mode 100644
index 0000000..613ba3d
--- /dev/null
+++ b/doc/s6-svscan-not-1.html
@@ -0,0 +1,140 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: How to run s6-svscan under another init process</title>
+    <meta name="Description" content="s6: s6-svscan as not-init" />
+    <meta name="Keywords" content="s6 supervision svscan s6-svscan init process boot" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> How to run s6-svscan under another init process </h1>
+
+<p>
+ You can have a reliable supervision tree even if s6-svscan is not your process 1.
+The supervision tree just has to be <em>rooted</em> in process 1: that means that
+your process 1 will have to supervise your s6-svscan process somehow. That way,
+if s6-svscan dies, it will be restarted, and your set of services will always
+be maintained.
+</p>
+
+<p>
+ Be aware, though, that pipes between services and loggers are maintained
+by the s6-svscan process; if this process dies, the pipes will be closed and
+some logs may be lost.
+</p>
+
+<a name="log">
+<h2> Logging the supervision tree's output </h2>
+</a>
+
+<p>
+ s6-svscan and the various s6-supervise processes might produce error or
+warning messages; those messages are written to s6-svscan's stderr (which
+is inherited by the s6-supervise processes). To log these messages:
+</p>
+
+<ul>
+ <li> You can use your init system's logging tools, and make your init
+system launch s6-svscan as is; its stderr should already be taken care
+of by the logging tools. </li>
+ <li> You can use a trick similar to the <a href="s6-svscan-1.html#log">process
+1 output logging trick</a> so the supervision tree's messages are logged via
+a service that's maintained by the supervision tree itself. Then your init
+system should not launch s6-svscan directly, but a wrapper script that performs
+the proper redirections. The
+<tt>examples/s6-svscanboot</tt> file in the s6 distribution gives an example of
+such a script. Make sure that your initial <a href="scandir.html">scan
+directory</a> contains a <a href="servicedir.html">service directory</a> for your
+initial logging service, that must read on the logging FIFO. </li>
+</ul>
+
+<p>
+ In the following examples, we'll assume that <tt>/command/s6-svscanboot</tt>
+is the name of the script you are using to start s6-svscan. Adjust this accordingly.
+</p>
+
+<a name="sysv">
+<h2> System V init </h2>
+</a>
+
+<p>
+ Put an appropriate line in your <tt>/etc/inittab</tt> file, then reload this
+config file with <tt>telinit q</tt>.
+</p>
+
+<h3> Example </h3>
+
+<pre> SV:123456:respawn:/command/s6-svscanboot </pre>
+
+
+<a name="upstart">
+<h2> Upstart </h2>
+</a>
+
+<p>
+ Put an appropriate configuration file in the <tt>/etc/init</tt> folder,
+for instance <tt>/etc/init/s6-svscan.conf</tt>, then start the service
+with <tt>start s6-svscan</tt>.
+</p>
+
+<h3>Example </h3>
+
+<pre># s6-svscan
+start on runlevel [2345]
+stop on runlevel [!2345]
+
+oom never
+respawn
+exec /command/s6-svscanboot
+</pre>
+
+<a name="systemd">
+<h2> systemd </h2>
+
+<p>
+ systemd has
+<a href="http://www.freedesktop.org/software/systemd/man/daemon.html">its
+own way</a> of supervising services. If you are a systemd user, chances
+are you do not need s6. If you are interested in using s6, I encourage
+you to also stop using systemd.
+</p>
+
+<p>
+
+</p>
+
+<a name="bsd">
+<h2> BSD init </h2>
+</a>
+
+<p>
+ Put an appropriate line in your <tt>/etc/ttys</tt> file, then reload this
+file with <tt>kill -s HUP 1</tt>.
+</p>
+
+<h3> Example </h3>
+
+<pre> sv /command/s6-svscanboot "" on </pre>
+
+<a name="launchd">
+<h2> MacOS X launchd </h2>
+</a>
+
+<p>
+ Like systemd, launchd comes with its own
+<a href="https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/CreatingLaunchdJobs.html">way
+of supervising services</a>; if you are a launchd user, you probably do
+not need s6.
+</p>
+
+
+</body>
+</html>
diff --git a/doc/s6-svscan.html b/doc/s6-svscan.html
new file mode 100644
index 0000000..4a7e800
--- /dev/null
+++ b/doc/s6-svscan.html
@@ -0,0 +1,176 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-svscan program</title>
+    <meta name="Description" content="s6: the s6-svscan program" />
+    <meta name="Keywords" content="s6 command s6-svscan scandir supervision supervise svscan monitoring collection" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">www.skarnet.org</a>
+</p>
+
+<h1> The s6-svscan program </h1>
+
+<p>
+s6-svscan starts and monitors a collection of <a href="s6-supervise.html">s6-supervise</a>
+processes, each of these processes monitoring a single service. It is designed to be either
+the root or a branch of a <em>supervision tree</em>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-svscan [ -c max ] [ -t <em>rescan</em> ] [ <em>scandir</em> ]
+</pre>
+
+<ul>
+ <li> If given a <em>scandir</em> argument, s6-svscan switches to it. Else it uses
+its current directory as <a href="scandir.html">scan directory</a>. </li>
+ <li> It exits 100 if another s6-svscan process is already monitoring this
+<a href="scandir.html">scan directory</a>. </li>
+ <li> If the <tt>./.s6-svscan</tt> control directory does not exist,
+s6-svscan creates it. However, it is recommended to already have a <tt>.s6-svscan</tt>
+subdirectory in your scan directory, because s6-svscan may try and execute into the
+<tt>.s6-svscan/crash</tt> or <tt>.s6-svscan/finish</tt> files at some point - so those
+files should exist and be executable. </li>
+ <li> From this point on, s6-svscan never dies. It tries its best to keep
+control of what's happening. In case of a major system call failure, which means
+that the kernel or hardware is broken in some fashion, it executes into the
+<tt>.s6-svscan/crash</tt> program. (But if that execution fails, s6-svscan exits
+111.) </li>
+ <li> s6-svscan performs an initial <em>scan</em> of its scan directory. </li>
+ <li> s6-svscan then occasionally runs <em>scans</em> or <em>reaps</em>,
+see below. </li>
+ <li> s6-svscan runs until it is told to stop via <a href="s6-svscanctl.html">
+s6-svscanctl</a>, or a signal.
+Then it executes into the <tt>.s6-svscan/finish</tt> program. The program is
+given an argument that depends on the s6-svscanctl options that were used. </li>
+ <li> If that execution fails, s6-svscan falls back on a <tt>.s6-svscan/crash</tt>
+execution. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-c&nbsp;<em>max</em></tt>&nbsp;: maintain services for up to <em>max</em>
+service directories. Default is 500. Lower limit is 2. There is no upper limit, but:
+ <ul>
+  <li> The higher <em>max</em> is, the more stack memory s6-svscan will use,
+approximately 50 bytes per service. </li>
+  <li> s6-svscan uses 2 file descriptors per logged service. </li>
+ </ul>
+ It is the admin's responsibility to make sure that s6-svscan has enough available
+descriptors to function properly and does not exceed its stack limit. The default
+of 500 is safe and provides enough room for every reasonable system. </li>
+ <li> <tt>-t&nbsp;<em>rescan</em></tt>&nbsp;: perform a scan every <em>rescan</em>
+milliseconds. If <em>rescan</em> is 0, automatic scans are never performed after
+the first one and s6-svscan will only detect new services when told to via a
+<a href="s6-svscanctl.html">s6-svscanctl -a</a> command. The default <em>rescan</em>
+value is 5000, for compatibility with daemontools'
+<a href="http://cr.yp.to/daemontools/svscan.html">svscan</a>, which performs a
+scan (and a reap) every 5 seconds. It is <em>strongly</em> discouraged to set
+<em>rescan</em> to a positive value under 500. </li>
+</ul>
+
+<h2> Signals </h2>
+
+<p>
+ s6-svscan reacts to the following signals:
+</p>
+
+<ul>
+ <li> SIGCHLD&nbsp;: triggers the reaper. </li>
+ <li> SIGALRM&nbsp;: triggers the scanner. </li>
+ <li> SIGTERM&nbsp;: acts as if a <tt>s6-svscanctl -t</tt> command had been received. </li>
+ <li> SIGHUP&nbsp;: acts as if a <tt>s6-svscanctl -h</tt> command had been received. </li>
+ <li> SIGQUIT&nbsp;: acts as if a <tt>s6-svscanctl -q</tt> command had been received. </li>
+ <li> SIGABRT&nbsp;: acts as if a <tt>s6-svscanctl -b</tt> command had been received. </li>
+ <li> SIGINT&nbsp;: acts as if a <tt>s6-svscanctl -i</tt> command had been received. </li>
+</ul>
+
+<h2> The reaper </h2>
+
+<p>
+ Upon receipt of a SIGCHLD, or a <a href="s6-svscanctl.html">s6-svscanctl -z</a>
+command, s6-svscan runs a <em>reaper</em> routine.
+</p>
+
+<p>
+The reaper acknowledges (via some
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html">wait()</a>
+function), without blocking, every terminated child of s6-svscan, even ones it does not
+know it has. This is especially important when <a href="s6-svscan-1.html">s6-svscan is
+run as process 1</a>.
+</p>
+
+<p>
+ If the dead child is a <a href="s6-supervise.html">s6-supervise</a> process watched
+by s6-svscan, and the last scan flagged that process as active, then it is restarted
+one second later.
+</p>
+
+<h2> The scanner </h2>
+
+<p>
+ Every <em>rescan</em> milliseconds, or upon receipt of a SIGALRM or a
+<a href="s6-svscanctl.html">s6-svscanctl -a</a> command, s6-svscan runs a
+<em>scanner</em> routine.
+</p>
+
+<p>
+ The scanner scans the current directory for subdirectories (or symbolic links
+to directories), which must be <a href="servicedir.html">service directories</a>.
+It skips names starting with dots. It will not create services for more than
+<em>max</em> subdirectories.
+</p>
+
+<p>
+ For every new subdirectory <em>dir</em> it finds, the scanner spawns a
+<a href="s6-supervise.html">s6-supervise</a> process on it. If
+<em>dir</em><tt>/log</tt> exists, it spawns a s6-supervise process on
+both <em>dir</em> and <em>dir</em><tt>/log</tt>, and maintains a
+never-closing pipe from the service's stdout to the logger's stdin.
+This is <em>starting the service</em>, with or without a corresponding
+logger.
+Every service the scanner finds is flagged as "active".
+</p>
+
+<p>
+ The scanner remembers the services it found. If a service has been
+started in an earlier scan, but the current scan can't find the corresponding
+directory, the service is then flagged as inactive. No command is sent
+to stop inactive s6-supervise processes (unless the administrator
+uses <a href="s6-svscanctl.html">s6-svscanctl -n</a>), but inactive
+s6-supervise processes will not be restarted if they die.
+</p>
+
+<h2> Implementation notes </h2>
+
+<ul>
+ <li> s6-svscan is designed to run until the machine is shut down. It is
+also designed as a suitable candidate for
+<a href="s6-svscan-1.html">process 1</a>. So, it goes out of its way to
+stay alive, even in dire situations. When it encounters a fatal situation,
+something it really cannot handle, it executes into <tt>.s6-svscan/crash</tt>
+instead of dying; when it is told to exit, it executes into
+<tt>.s6-svscan/finish</tt>. Administrators should make sure to design
+appropriate <tt>crash</tt> and <tt>finish</tt> routines. </li>
+ <li> s6-svscan is a fully asynchronous state machine. It will read and
+process commands at any time, even when the computer is in trouble. </li>
+ <li> s6-svscan <em>does not use malloc()</em>. That means it will <em>never leak
+memory</em>. <small>However, s6-svscan uses opendir(), and most opendir()
+implementations internally use heap memory - so unfortunately, it's impossible
+to guarantee that s6-svscan does not use heap memory at all.</small> </li>
+ <li> When run with the <tt>-t0</tt> option, s6-svscan <em>never polls</em>,
+it only wakes up on notifications, just like s6-supervise. The s6 supervision
+tree can be used in energy-critical environments. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-svscanctl.html b/doc/s6-svscanctl.html
new file mode 100644
index 0000000..cc7ae99
--- /dev/null
+++ b/doc/s6-svscanctl.html
@@ -0,0 +1,108 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-svscanctl program</title>
+    <meta name="Description" content="s6: the s6-svscanctl program" />
+    <meta name="Keywords" content="s6 command s6-svscanctl svscan command service" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-svscanctl program </h1>
+
+<p>
+s6-svscanctl sends commands to a running <a href="s6-svscan.html">s6-svscan</a>
+process.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-svscanctl [ -phratszbnNiq0678 ] <em>svscandir</em>
+</pre>
+
+<p>
+s6-svscanctl sends the given series of commands to the
+<a href="s6-svscan.html">s6-svscan</a> process monitoring the
+<em>svscandir</em> directory, then exits 0. It exits 111 if it cannot send
+a command, or 100 if no s6-svscan process is running on <em>svscandir</em>.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-p</tt>&nbsp;: poweroff mode. s6-svscan will exec into
+ <tt>./.s6-svscan/finish poweroff</tt> when it is told to terminate. </li>
+ <li> <tt>-h</tt>&nbsp;: Hangup. s6-svscan will send a SIGHUP to all the
+maintained s6-supervise processes, then run its finish procedure. </li>
+ <li> <tt>-r</tt>&nbsp;: reboot mode. s6-svscan will exec into
+ <tt>./.s6-svscan/finish reboot</tt> when it is told to terminate. This
+is s6-svscan's default mode.</li>
+ <li> <tt>-a</tt>&nbsp;: Alarm. s6-svscan will immediately perform a scan
+of <em>svscandir</em> to check for services. </li>
+ <li> <tt>-t</tt>&nbsp;: Terminate. s6-svscan will send a
+SIGTERM to all the s6-supervise processes supervising a service and a
+SIGHUP to all the s6-supervise processes supervising a logger, then run its
+finish procedure. </li>
+ <li> <tt>-s</tt>&nbsp;: halt mode. s6-svscan will exec into
+ <tt>./.s6-svscan/finish halt</tt> when it is told to terminate. </li>
+ <li> <tt>-z</tt>&nbsp;: destroy zombies. Immediately triggers s6-svscan's
+reaper mechanism. </li>
+ <li> <tt>-b</tt>&nbsp;: abort. s6-svscan will exec into its finishing
+procedure. It will not kill any of the maintained s6-supervise processes. </li>
+ <li> <tt>-n</tt>&nbsp;: nuke. s6-svscan will kill all the
+s6-supervise processes it has launched but that did not match a service
+directory last time <em>svscandir</em> was scanned, i.e. it prunes the
+supervision tree so that it matches exactly what was in <em>svscandir</em>
+at the time of the last scan. A SIGTERM is sent to the s6-supervise processes
+supervising services and a SIGHUP is sent to the s6-supervise processes
+supervising loggers. </li>
+ <li> <tt>-N</tt>&nbsp;: Really nuke. Does the same thing as <tt>-n</tt>,
+except that SIGTERM is sent to all the relevant s6-supervise processes, even
+if they are supervising loggers. That means that the logger processes will
+be killed with a SIGTERM instead of being allowed to exit at their own pace. </li>
+ <li> <tt>-i</tt>&nbsp;: Interrupt. Equivalent to <tt>-rt</tt>&nbsp;: s6-svscan
+will terminate in reboot mode. </li>
+ <li> <tt>-q</tt>&nbsp;: Quit. s6-svscan will send all its s6-supervise processes
+a SIGTERM, then exec into its finish procedure. </li>
+ <li> <tt>-0</tt>&nbsp;: Halt. Equivalent to <tt>-st</tt>&nbsp;: s6-svscan will
+terminate in halt mode. </li>
+ <li> <tt>-6</tt>&nbsp;: Reboot. Equivalent to <tt>-i</tt>. </li>
+ <li> <tt>-7</tt>&nbsp;: Poweroff. Equivalent to <tt>-pt</tt>: s6-svscan will
+terminate in poweroff mode. </li>
+ <li> <tt>-8</tt>&nbsp;: Other. s6-svscan will terminate in "other" mode. </li>
+</ul>
+
+<h2> Usage examples </h2>
+
+<pre> s6-svscanctl -an /service </pre>
+<p>
+ Updates the process supervision tree
+to exactly match the services listed in <tt>/service</tt>.
+</p>
+
+<pre> s6-svscanctl -6 /service </pre>
+<p>
+ Orders the s6-svscan process monitoring <tt>/service</tt> to exit in
+reboot mode: all the supervision tree at <tt>/service</tt> will be terminated,
+and s6-svscan will execute into the <tt>/service/.s6-svscan/finish</tt>
+script with the <tt>reboot</tt> argument.
+</p>
+
+<h2> Internals </h2>
+
+<p>
+s6-svscanctl writes control commands into the <tt><em>svscandir</em>/.s6-svscan/control</tt>
+FIFO. A s6-svscan process running on <em>svscandir</em> will be listening to this FIFO,
+and will read and interpret those commands.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-svstat.html b/doc/s6-svstat.html
new file mode 100644
index 0000000..75ee7e2
--- /dev/null
+++ b/doc/s6-svstat.html
@@ -0,0 +1,45 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-svstat program</title>
+    <meta name="Description" content="s6: the s6-svstat program" />
+    <meta name="Keywords" content="s6 command s6-svstat servicedir checking supervision s6-supervise" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-svstat program </h1>
+
+<p>
+s6-svstat prints a short, human-readable summary of the state of a supervised
+service.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-svstat <em>servicedir</em>
+</pre>
+
+<p>
+ s6-svstat gives the following information about the process being monitored
+at the <em>servicedir</em> <a href="servicedir.html">service directory</a>, then
+exits 0:
+</p>
+
+<ul>
+ <li> whether it is up or down </li>
+ <li> its pid, if it is up </li>
+ <li> what its default state is, if it is different from its current state </li>
+ <li> the number of seconds since it last changed states </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-svwait.html b/doc/s6-svwait.html
new file mode 100644
index 0000000..6e15704
--- /dev/null
+++ b/doc/s6-svwait.html
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-svwait program</title>
+    <meta name="Description" content="s6: the s6-svwait program" />
+    <meta name="Keywords" content="s6 command s6-svwait notification service waiting" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-svwait program </h1>
+
+<p>
+s6-svwait blocks until a collection of supervised services goes up, or down.
+</p>
+
+<p>
+s6-svwait only waits for notifications; it never polls.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-svwait [ -U | -u | -d ] [ -a | -o ] [ -t <em>timeout</em> ] <em>servicedir...</em>
+</pre>
+
+<p>
+s6-svwait monitors one or more <a href="servicedir.html">service
+directories</a> given as its arguments, waiting for a state (up or down) to
+happen. It exits 0 when the wanted condition becomes true.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-u</tt>&nbsp;: up. s6-svwait will wait until the services are up, as
+reported by s6-supervise.
+This is the default; it is not reliable, but it does not depend on specific
+support in the service programs. See <a href="notifywhenup.html">this page</a>
+for details. </li>
+ <li> <tt>-U</tt>&nbsp;: really up. s6-svwait will wait until the services are
+up, as reported by the services themselves. This requires specific support in the
+service programs: see the explanation on <a href="notifywhenup.html">this page</a>. </li>
+ <li> <tt>-d</tt>&nbsp;: down. s6-svwait will wait until the services are down. </li>
+ <li> <tt>-o</tt>&nbsp;: or. s6-svwait will wait until <em>one</em> of the
+given services comes up or down. </li>
+ <li> <tt>-a</tt>&nbsp;: and. s6-svwait will wait until <em>all</em> of the
+given services come up or down. This is the default. </li>
+ <li> <tt>-t <em>timeout</em></tt>&nbsp;: if the requested events have not
+happened after <em>timeout</em> milliseconds, s6-svwait will print a message
+to stderr and exit 1. By default, <em>timeout</em> is 0, which means no time
+limit. </li>
+</ul>
+
+
+<h2> Internals </h2>
+
+<p>
+s6-svwait spawns a <a href="s6-ftrigrd.html">s6-ftrigrd</a> child to
+listen to notifications sent by <a href="s6-supervise.html">s6-supervise</a>.
+It also checks <tt>supervise/status</tt> files to get the current service
+states, so it is immune to race conditions.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-tai64n.html b/doc/s6-tai64n.html
new file mode 100644
index 0000000..8b7f5e0
--- /dev/null
+++ b/doc/s6-tai64n.html
@@ -0,0 +1,50 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-tai64n program</title>
+    <meta name="Description" content="s6: the s6-tai64n program" />
+    <meta name="Keywords" content="s6 command s6-tai64n filter timestamp TAI64 TAI64N" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-tai64n program </h1>
+
+<p>
+s6-tai64n acts as a filter, reading from stdin and writing to stdout.
+It prepends lines with a
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html#timestamp">TAI64N
+timestamp</a> and a space.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-tai64n
+</pre>
+
+<ul>
+ <li> s6-tai64n exits 0 when it sees the end of stdin. If there's an
+unfinished line, s6-tai64n processes it, adds a newline character to it,
+and writes it before exiting. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ s6-tai64n does neither "line buffering" nor "block buffering". It does
+<em>optimal buffering</em>, i.e. it flushes its output buffer every time
+it risks blocking on input. Every filter should behave this way, whether
+its output is a tty or not: it's simpler and more efficient in every
+case.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-tai64nlocal.html b/doc/s6-tai64nlocal.html
new file mode 100644
index 0000000..f6d2280
--- /dev/null
+++ b/doc/s6-tai64nlocal.html
@@ -0,0 +1,73 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the s6-tai64nlocal program</title>
+    <meta name="Description" content="s6: the s6-tai64nlocal program" />
+    <meta name="Keywords" content="s6 command s6-tai64nlocal filter timestamp TAI64 TAI64N human-readable date time" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The s6-tai64nlocal program </h1>
+
+<p>
+s6-tai64nlocal acts as a filter, reading from stdin and writing to stdout.
+For every line that begins with a
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html#timestamp">TAI64N
+timestamp</a>, it replaces this timestamp with a human-readable local date and
+time.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-tai64nlocal
+</pre>
+
+<ul>
+ <li> s6-tai64nlocal exits 0 when it sees the end of stdin. If there's an
+unfinished line, s6-tai64n processes it
+and writes it before exiting. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> The typical use case of s6-tai64nlocal is to read files that have
+been filtered through <a href="s6-tai64n.html">s6-tai64n</a>, or log files
+that have been produced by <a href="s6-log.html">s6-log</a> with the <tt>-t</tt>
+option. For instance, to read the latest httpd logs with human-readable
+timestamps, <tt>s6-tai64nlocal &lt; /var/log/httpd/current | less</tt> is a
+possible command. </li>
+ <li> s6-tai64nlocal does neither "line buffering" nor "block buffering". It does
+<em>optimal buffering</em>, i.e. it flushes its output buffer every time
+it risks blocking on input. </li>
+</ul>
+
+<h2> Troubleshooting </h2>
+
+<p>
+ If s6-tai64nlocal does not appear to give the correct local time:
+</p>
+
+<ul>
+ <li> Check the compilation options that were used for the
+<a href="http://skarnet.org/software/skalibs/">skalibs</a> libraries
+your s6-tai64nlocal program was linked against. In particular, check whether the
+<tt>--enable-tai-clock</tt> or <tt>--enable-right-tz</tt> configure options
+have been given. </li>
+ <li> Compare these flags and their meanings with your current timezone. In particular,
+check <tt>/etc/localtime</tt>, <tt>/etc/timezone</tt>, <tt>/etc/TZ</tt>, and the TZ
+environment variable. </li>
+ <li> Check that you have a correct and recent version of <tt>/etc/leapsecs.dat</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/scandir.html b/doc/scandir.html
new file mode 100644
index 0000000..366bd08
--- /dev/null
+++ b/doc/scandir.html
@@ -0,0 +1,145 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: scan directories</title>
+    <meta name="Description" content="s6: scan directory" />
+    <meta name="Keywords" content="s6 scandir supervision svscan s6-svscan scan directory servicedir" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Scan directories </h1>
+
+<p>
+ A <em>scan directory</em> is a directory containing a list of
+<a href="servicedir.html">service directories</a>, or symbolic links
+pointing to service directories.
+</p>
+
+<p>
+ A scan directory represents a list of services that are supposed to
+be supervised. Running <a href="s6-svscan.html">s6-svscan</a> on this
+scan directory launches a <em>supervision tree</em>: every service
+listed in the scan directory will be supervised.
+</p>
+
+<p>
+ There is normally only one scan directory per system, although nothing
+prevents a system administrator from having more.
+<a href="http://cr.yp.to/daemontools.html">daemontools</a> traditionally
+uses <tt>/service</tt>, and <a href="http://smarden.org/runit/">runit</a>
+traditionally uses <tt>/etc/service</tt>. s6 does not care where your
+scan directory is, but I would advise <tt>/service</tt> for compatibility
+with daemontools. Depending on your installation, <tt>/service</tt> could
+be a symbolic link and point to a place either in a RAM filesystem or in
+<tt>/var</tt>.
+</p>
+
+<a name="where">
+<h2> Where and how to build a scan directory </h2>
+</a>
+
+<p>
+ Opinions and practices differ.
+</p>
+
+<p>
+ It is generally accepted that the place where you store all your
+service directories (your "service repository") should <em>not</em> be
+used as a scan directory - for a simple reason: you might want to have
+service directories for more services than what you want to start at
+any given time. In other words, your scan directory will be a <em>subset</em>
+of your service repository, so you cannot just run s6-svscan on every
+service you have a service directory for. So, the first thing is to
+separate your <em>service repository</em>, which is just a storage place
+for all the services you might want to manage someday, and your <em>scan
+directory</em>, which is a directory representing all the services that
+you are <em>currently</em> managing.
+</p>
+
+<h3> Service repository </h3>
+
+<p>
+ Where to store your service repository is purely a matter of personal
+preference. You just have to be aware that <a href="s6-supervise.html">
+s6-supervise</a> needs writable <tt>supervise</tt> and <tt>event</tt>
+subdirectories in a service directory it monitors.
+</p>
+
+<h3> Scan directory </h3>
+
+<p>
+ Where and how to build your scan directory depends heavily on your boot
+system - and on your personal preference too.
+</p>
+
+<p>
+ Standard <a href="http://cr.yp.to/daemontools.html">daemontools</a> and
+<a href="http://smarden.org/runit/">runit</a> installations like to have
+a fixed scan directory containing symlinks to service directories located
+in the service repository. In other words, the service repository contains
+the real <em>working copies</em> of the service directories. This works,
+as long as:
+</p>
+
+<ul>
+ <li> It is possible to create writable <tt>supervise</tt> and <tt>event</tt>
+subdirectories in every managed service directory. This can be achieved for
+instance via symlinks, or by having the service repository stored on a writable
+filesystem. </li>
+ <li> The scan program (<a href="s6-svscan.html">s6-svscan</a>,
+<a href="http://cr.yp.to/daemontools/svscan.html">svscan</a>,
+<a href="http://smarden.org/runit/runsvdir.8.html">runsvdir</a>...) is
+started late enough for all the necessary filesystems to be mounted. </li>
+</ul>
+
+<p>
+ My own recommendation would be to have working copies of the service
+directories <em>entirely separate</em> from the service repository. The
+service repository can be safely stored on the root filesystem, and the
+needed directories copied to a RAM filesystem at boot time. The scan
+directory can be either the place where the working copies are written,
+or another directory containing symlinks to those working copies. (The
+latter is useful if you are not using <tt>s6-svscan -t0</tt>: copying a
+directory is not atomic, but making a symlink is, so there is no risk
+of your scanner finding an incomplete directory.)
+</p>
+
+<p>
+ An example:
+</p>
+
+<ul>
+ <li> Have your service repository in <tt>/img/services</tt>, i.e. have
+service directories in <tt>/img/services/ftpd</tt>, <tt>/img/services/httpd</tt>,
+<tt>/img/services/sshd</tt>, etc. </li>
+ <li> When booting, make <tt>/tmp</tt> a RAM filesystem, and create the
+directories <tt>/tmp/services</tt> and <tt>/tmp/service</tt>. </li>
+ <li> Have s6-svscan run on <tt>/tmp/service</tt>, as early as possible in your
+boot sequence. This is possible whether you want to run s6-svscan
+<a href="s6-svscan-1.html">as process 1</a> or <a href="s6-svscan-not-1.html">not</a>. </li>
+ <li> During the boot sequence, populate <tt>/tmp/services</tt> with copies of the
+service directories you need: for instance,
+ <ul>
+  <li> <tt>cp -a /img/services/sshd /tmp/services/sshd</tt> </li>
+  <li> <tt>cp -a /img/services/ftpd /tmp/services/ftpd</tt> </li>
+  <li> etc. </li>
+ </ul> </li>
+ <li> When you are ready to start a service, make a symlink in the
+<tt>/tmp/service</tt> <em>scan directory</em> pointing to the working copy of
+the service directory you need in <tt>/tmp/services</tt>, then notify s6-svscan.
+For instance, to start ftpd and httpd together:
+<pre> ln -s ../services/ftpd /tmp/service
+ ln -s ../services/httpd /tmp/service
+ s6-svscanctl -a /tmp/service</pre> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/servicedir.html b/doc/servicedir.html
new file mode 100644
index 0000000..b5c4d23
--- /dev/null
+++ b/doc/servicedir.html
@@ -0,0 +1,187 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: service directories</title>
+    <meta name="Description" content="s6: service directory" />
+    <meta name="Keywords" content="s6 supervision supervise service directory run finish servicedir" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Service directories </h1>
+
+<p>
+ A <em>service directory</em> is a directory containing all the information
+related to a <em>service</em>, i.e. a long-running process maintained and
+supervised by <a href="s6-supervise.html">s6-supervise</a>.
+</p>
+
+<p>
+ (Strictly speaking, a <em>service</em> is not always equivalent to a
+long-running process. Things like Ethernet interfaces fit the definition
+of <em>services</em> one may want to supervise; however, s6 does not
+provide <em>service supervision</em>; it provides <em>process supervision</em>,
+and it is impractical to use the s6 architecture as is to supervise
+services that are not equivalent to one long-running process. However,
+we still use the terms <em>service</em> and <em>service directory</em>
+for historical and compatibility reasons.)
+</p>
+
+<h2> Contents </h2>
+
+ A service directory <em>foo</em> may contain the following elements:
+
+<ul>
+ <li> An executable file named <tt>run</tt>. It can be any executable
+file (such as a binary file or a link to any other executable file),
+but most of the time it will be a script, called <em>run script</em>.
+This file is the most important one in your service directory: it
+contains the commands that will setup and run your <em>foo</em> service.
+It is forked and executed by <a href="s6-supervise.html">s6-supervise</a>
+every time the service must be started, i.e. normally when
+<a href="s6-supervise.html">s6-supervise</a> starts, and whenever
+the service goes down when it is supposed to be up. A run script
+should normally:
+ <ul>
+  <li> adjust redirections for stdin, stdout and stderr. For instance,
+if your service is logged, the run script should make sure that its
+stderr goes into the log pipe (which is on stdout by default), which
+is achieved by <tt><a href="http://skarnet.org/software/execline/fdmove.html">fdmove</a>
+-c 2 1</tt> in <a href="http://skarnet.org/software/execline/">execline</a>,
+and <tt>exec 2>&1</tt> in <a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html">shell</a>.
+By default, in a normal supervision tree situation, a run script's stdin will
+be <tt>/dev/null</tt>, and its stdout and stderr will both be a pipe to a
+catch-all logging program. </li>
+<li> adjust the environment for your <em>foo</em> daemon. Normally the run script
+inherits its environment from <a href="s6-supervise.html">s6-supervise</a>,
+which normally inherits its environment from <a href="s6-svscan.html">s6-svscan</a>,
+which normally inherits a minimal environment from the boot scripts.
+Service-specific environment variables should be set in the run script. </li>
+ <li> adjust other parameters for the <em>foo</em> daemon, such as its
+uid and gid. Normally the supervision tree, i.e.
+<a href="s6-svscan.html">s6-svscan</a> and the various
+<a href="s6-supervise.html">s6-supervise</a> processes, is run as root, so
+run scripts are also run as root; however, for security purposes, services
+should not run as root if they don't need to. You can use the
+<a href="s6-setuidgid.html">s6-setuidgid</a> utility in <em>foo</em><tt>/run</tt>
+to lose privileges before executing into <em>foo</em>'s long-lived
+process; or the <a href="s6-envuidgid.html">s6-envuidgid</a> utility if
+your long-lived process needs root privileges at start time but can drop
+them afterwards. </li>
+ <li> execute into the long-lived process that is to be supervised by
+<a href="s6-supervise.html">s6-supervise</a>, i.e. the real <em>foo</em>
+daemon. That process must not "background itself": being run by a supervision
+tree already makes it a "background" task. </li>
+ </ul>
+ <li> An optional executable file named <tt>finish</tt>. Like <tt>run</tt>,
+it can be any executable file. This <em>finish script</em>, if present,
+is executed everytime the <tt>run</tt> script dies. Generally, its main
+purpose is to clean up non-volatile data such as the filesystem after the supervised
+process has been killed. If the <em>foo</em> service is supposed to be up,
+<em>foo</em><tt>/run</tt> is restarted
+after <em>foo</em><tt>/finish</tt> dies. A finish script must do its work and exit in less than                              
+3 seconds; if it takes more than that, it is killed. (The point is that the run
+script, not the finish script, should be running; the finish script should really
+be short-lived.) </li>
+ <li> A directory named <tt>supervise</tt>. It is automatically created by
+<a href="s6-supervise.html">s6-supervise</a> if it does not exist. This is where
+<a href="s6-supervise.html">s6-supervise</a> stores its information. The directory
+must be writable. </li>
+ <li> An optional, empty, regular file named <tt>down</tt>. If such a file exists,
+the default state of the service is considered down, not up: s6-supervise will not
+automatically start it until it receives a <tt>s6-svc -u</tt> command. If no
+<tt>down</tt> file exists, the default state of the service is up. </li>
+ <li> An optional, empty, regular file named <tt>nosetsid</tt>. If such a file exists,
+s6-supervise will not make the service a process group and session leader; the service
+will be run in the same process group as s6-supervise. If no <tt>nosetsid</tt> file
+exists, the service has its own process group and is started as a session leader. </li>
+ <li> A <a href="fifodir.html">fifodir</a> named <tt>event</tt>. It is automatically
+created by <a href="s6-supervise.html">s6-supervise</a> if it does not exist.
+<em>foo</em><tt>/event</tt>
+is the rendez-vous point for listeners, where <a href="s6-supervise.html">s6-supervise</a>
+will send notifications when the service goes up or down. </li>
+ <li> An optional service directory named <tt>log</tt>. If it exists and <em>foo</em>
+is in a <a href="scandir.html">scandir</a>, and <a href="s6-svscan.html">s6-svscan</a>
+runs on that scandir, then <em>two</em> services are monitored: <em>foo</em> and
+<em>foo</em><tt>/log</tt>. A pipe is open and maintained between <em>foo</em> and
+<em>foo</em><tt>/log</tt>, i.e. everything that <em>foo</em><tt>/run</tt>
+writes to its stdout will appear on <em>foo</em><tt>/log/run</tt>'s stdin. The <em>foo</em>
+service is said to be <em>logged</em>; the <em>foo</em><tt>/log</tt> service is called
+<em>foo</em>'s <em>logger</em>. A logger service cannot be logged: if
+<em>foo</em><tt>/log/log</tt> exists, nothing special happens. </li>
+</ul>
+
+<a name="where">
+ <h2> Where to store my service directories&nbsp;? </h2>
+</a>
+
+<p>
+ Service directories describe the way services are launched. Once they are
+designed, they have little reason to change on a given machine. They can
+theoretically reside on a read-only filesystem - for instance, the root
+filesystem, to avoid problems with mounting failures.
+</p>
+
+<p>
+ However, two subdirectories - namely <tt>supervise</tt> and <tt>event</tt> -
+of every service directory need to be writable. So it has to be a bit more
+complex. Here are a few possibilities.
+</p>
+
+<ul>
+ <li> The laziest option: you're not using <a href="s6-svscan.html">s6-svscan</a>
+as process 1, you're only using it to start a collection of services, and
+your booting process is already handled by another init system. Then you can
+just store your service directories and your <a href="scandir.html">scan
+directory</a> on some read-write filesystem such as <tt>/var</tt>; and you
+tell your init system to launch (and, if possible, maintain) s6-svscan on
+the scan directory after that filesystem is mounted. </li>
+ <li> The almost-as-lazy option: just have the service directories on the
+root filesystem. Then your service directory collection is for instance in
+<tt>/etc/services</tt> and you have a <tt>/service</tt>
+<a href="scandir.html">scan directory</a> containing symlinks to that
+collection. This is the easy setup, not requiring an external init system
+to mount your filesystems - however, it requires your root filesystem to be
+read-write, which is unacceptable if you are concerned with reliability - if
+you are, for instance, designing an embedded platform. </li>
+ <li> <a href="http://code.dogmap.org/">Some people</a> like to have
+their service directories in a read-only filesystem, with <tt>supervise</tt>
+symlinks pointing to various places in writable filesystems. This setup looks
+a bit complex to me: it requires careful handling of the writable
+filesystems, with not much room for error if the directory structure does not
+match the symlinks (which are then dangling). But it works. </li>
+ <li> Service directories are usually small; most daemons store their
+information elsewhere. Even a complete set of service directories often
+amounts to less than a megabyte of data - sometimes much less. Knowing this,
+it makes sense to have an image of your service directories in the
+(possibly read-only) root filesystem, and <em>copy it all</em>
+to a scan directory located on a RAM filesystem that is mounted at boot time.
+This is the setup I recommend. It has several advantages:
+ <ul>
+  <li> Your service directories reside on the root filesystem and are not
+modified during the lifetime of the system. If your root filesystem is
+read-only and you have a working set of service directories, you have the
+guarantee that a reboot will set your system in a working state. </li>
+ <li> Every boot system requires an early writeable filesystem, and many
+create it in RAM. You can take advantage of this to copy your service
+directories early and run s6-svscan early. </li>
+ <li> No dangling symlinks or potential problems with unmounted
+filesystems: this setup is robust. A simple <tt>/bin/cp -a</tt> or
+<tt>tar -x</tt> is all it takes to get a working service infrastructure. </li>
+ <li> You can make temporary modifications to your service directories
+without affecting the main ones, safely stored on the disk. Conversely,
+every boot ensures clean service directories - including freshly created
+<tt>supervise</tt> and <tt>event</tt> subdirectories. No stale files can
+make your system unstable. </li>
+ </ul> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/systemd.html b/doc/systemd.html
new file mode 100644
index 0000000..49ba2aa
--- /dev/null
+++ b/doc/systemd.html
@@ -0,0 +1,120 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: a word about systemd</title>
+    <meta name="Description" content="s6: a word about systemd" />
+    <meta name="Keywords" content="s6 supervision init systemd" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> A word about systemd </h1>
+
+<p>
+ <a href="http://www.freedesktop.org/wiki/Software/systemd/">systemd</a>
+is becoming <i>de facto</i> a standard init system for Linux. But even
+this choice of words is treacherous, because systemd is much more than
+an init system. It's basically an integrated redesign of all the low-level
+userspace of a Linux system, with great plans to change how software is
+run and organized.
+
+<p>
+ Which is not a bad thing per se: Unix software can definitely benefit
+from improvements in this area, and the s6 suite, among other software,
+comes from the same assessment and ultimately has the same goal. But
+systemd suffers from a single conception flaw that sets it apart from
+the other initiatives, and that has both political and technical
+repercussions.
+</p>
+
+<p>
+<strong>
+ The single, overarching problem with systemd is that it attempts, in every
+possible way, to do <em>more</em> instead of <em>less</em>.
+</strong>
+</p>
+
+<h2> The political issue </h2>
+
+<p>
+ systemd attempts to cover <em>more</em> ground instead of <em>less</em>.
+In other words, rather than simply being an init system, it tries to be
+a complete overhaul of the way a Linux system is run, and tries to force
+other software to hook with it in order to be supported. This goes very
+much against: 
+</p>
+
+<ul>
+ <li> The Unix philosophy, which is to do one job and do it well; </li>
+ <li> The <a href="http://www.catb.org/esr/writings/cathedral-bazaar/">bazaar</a>
+approach that has made the free software ecosystem what it is today; </li>
+ <li> Cross-platform compatibility. BSD is not dead, Solaris is not dead,
+but systemd ignores Unix. It even ignores Linux to some extent: the systemd
+authors had the guts to ask for specific kernel interfaces! </li>
+</ul>
+
+<p>
+ The reason why systemd has become so prevalent is not that it has been
+accepted by the community. It's that it has manpower. It is backed up by
+open source software companies that can provide much more manpower than
+developers like myself working on free software on their own time. The
+distribution model of systemd, made of lobbying and bullying, is much more
+akin to the distribution model of Microsoft Windows than the one of GNU/Linux.
+</p>
+
+<p>
+ Which says something.
+</p>
+
+<h2> The technical issue </h2>
+
+<p>
+ Software that does <em>more</em> instead of <em>less</em> is, simply put,
+badly designed software. Trying to come up with an all-encompassing solution
+is always a sign of developer hubris and inexperience, and never a sign of
+good engineering. Ever. Remember sendmail, BIND, INN, and, definitely a better
+analogy, the early days of Microsoft Windows&nbsp;? Yes, systemd is in
+exactly the same league. It's as if we had learned <em>nothing</em> from the
+mistakes of the past 20 years. Technically as well as politically, systemd
+is actually very close to Windows; is that the future we want for Linux
+machines&nbsp?
+</p>
+
+<p>
+ Doing more instead of less is bad, and it's especially true in the case of
+system software, i.e. low-level software that
+aims to make the machine work and that application software depends upon.
+The goal of an operating system is to make it possible to run <em>applications</em>,
+and system software should always partake in that goal. <strong>System software
+should stay the heck out of the way</strong>, and systemd is big, loud and
+obnoxious. Embedded devices are common, and will become even more common in
+the future; that is a market that systemd will have trouble breaking into, because
+it's a lot more complex than embedded devices need. And that, too, says something:
+if a software suite is too complex for an embedded device, maybe it's just too
+complex, period.
+</p>
+
+<h2> Links </h2>
+
+<ul>
+ <li> <a href="http://freedesktop.org/wiki/Software/systemd/">systemd's home page</a> </li>
+ <li> <a href="http://uselessd.darknedgy.net/ProSystemdAntiSystemd/">An analysis of
+the vacuity of most Internet arguments about systemd</a>, by the author of
+<a href="http://uselessd.darknedgy.net/">uselessd</a>. </li>
+ <li> <a href="http://boycottsystemd.org">boycottsystemd.org</a>, summarizing
+political arguments against systemd </li>
+ <li> <a href="http://ewontfix.com/14/">Technical arguments against systemd</a>,
+by Rich Felker, main author of <a href="http://musl-libc.org/">musl</a> </li>
+ <li> <a href="http://judecnelson.blogspot.fr/2014/09/systemd-biggest-fallacies.html">A
+list of fallacies about systemd, with debunk</a> </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/ucspilogd.html b/doc/ucspilogd.html
new file mode 100644
index 0000000..1b06335
--- /dev/null
+++ b/doc/ucspilogd.html
@@ -0,0 +1,94 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: the ucspilogd program</title>
+    <meta name="Description" content="s6: the ucspilogd program" />
+    <meta name="Keywords" content="s6 command ucspilogd log logging UCSPI" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="../">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>ucspilogd</tt> program </h1>
+
+<p>
+ <tt>ucspilogd</tt> acts as a filter, converting syslog facility
+numbers and alert levels into names.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     ucspilogd [ <em>var</em> ... ]
+</pre>
+
+<ul>
+ <li> ucspilogd reads a stream of syslog-like messages on stdin.
+Those messages can be newline-terminated or null-terminated. </li>
+ <li> For every line it reads: if it has been given <em>var</em>
+arguments, it writes the value of every <em>var</em> environment
+variable, followed by a colon and a space. </li>
+ <li> If the line begins with a syslog facility number and/or
+alert level in the syslog format, it converts them into a
+human-readable name in the syslogd fashion. </li>
+ <li> It then writes the processed line to stdout. </li>
+</ul>
+
+<h2> Common use </h2>
+
+<p>
+ You can emulate the whole <em>syslogd</em> behaviour by combining the following
+components:
+</p>
+
+<ul>
+ <li> A Unix stream super-server such as
+<a href="http://skarnet.org/software/s6-networking/s6-ipcserver.html">s6-ipcserver</a>
+listening to the Unix domain socket <tt>/dev/log</tt>, to connect to
+the kernel log-reading interface. </li>
+ <li> <tt>ucspilogd</tt> running under that super-server, to read the
+logs and perform adequate transformations. </li>
+ <li> A logger such as
+<a href="s6-log.html">s6-log</a>
+to store the logs into the filesystem. </li>
+ <li> A supervision mechanism such as s6,
+to ensure ease of use and reliability of the whole chain. </li>
+</ul>
+
+<p>
+ The resulting suite of programs is still smaller, and way more reliable,
+than a standard <em>syslogd</em>.
+</p>
+
+<p>
+ In the <tt>examples/ROOT/img/services-local/syslogd-linux</tt> subdirectory of the s6 package, you will
+find a suitable ucspilogd <a href="servicedir.html">service directory</a>.
+The run scripts are written in the
+<a href="http://skarnet.org/software/execline/">execline</a>
+language.
+</p>
+
+<h2> Using <tt>ucspilogd</tt> as a <em>klogd</em> replacement </h2>
+
+<p>
+ Certain Unix kernels offer a nice interface to the kernel logs.
+For instance, the Linux kernel provides the <tt>/proc/kmsg</tt> fake
+file, that can be opened and read like a normal file, excepts that
+it gives the kernel logs when they are available and blocks otherwise.
+You can use <tt>ucspilogd</tt> to process data from those interfaces.
+</p>
+
+<p>
+ The <tt>examples/ROOT/img/services-local/klogd-linux</tt> subdirectory of the s6 package
+is a <a href="servicedir.html">service directory</a> providing such a <em>klogd</em> service
+for Linux, using the <tt>/proc/kmsg</tt> interface.
+</p>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..1a35178
--- /dev/null
+++ b/doc/upgrade.html
@@ -0,0 +1,32 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>How to upgrade s6</title>
+    <meta name="Description" content="How to upgrade s6" />
+    <meta name="Keywords" content="s6 installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> How to upgrade s6 </h1>
+
+<h2> to 2.0.0.0 </h2>
+
+<ul>
+ <li> The build system has completely changed. It is now a standard
+<tt>./configure &amp;&amp; make &amp;&amp; sudo make install</tt>
+build system. See the enclosed INSTALL file for details. </li>
+ <li> slashpackage is not activated by default. </li>
+ <li> shared libraries are not used by default. </li>
+ <li> skalibs dependency bumped to 2.0.0.0 </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/why.html b/doc/why.html
new file mode 100644
index 0000000..1901259
--- /dev/null
+++ b/doc/why.html
@@ -0,0 +1,203 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6: why another supervision suite</title>
+    <meta name="Description" content="s6: why another supervision suite" />
+    <meta name="Keywords" content="s6 supervision daemontools runit perp service svscan supervise" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> Why another supervision suite ? </h1>
+
+<p>
+ Supervision suites are becoming quite common. Today, we already have:
+</p>
+
+<ul>
+ <li> Good (?) old System V init, which can be made to supervise services if you perform <tt>/etc/inittab</tt> voodoo.
+BSD init can also be used the same way with the <tt>/etc/ttys</tt> file, but for some reason, nobody among BSD
+developers is using <tt>/etc/ttys</tt> to this purpose, so I won't consider BSD init here. </li>
+ <li> <a href="http://cr.yp.to/daemontools.html">daemontools</a>, the pioneer </li>
+ <li> <a href="http://untroubled.org/daemontools-encore/">daemontools-encore</a>, Bruce Guenter's upgrade to daemontools </li>
+ <li> <a href="http://smarden.org/runit/">runit</a>, Gerrit Pape's suite, well-integrated with Debian </li>
+ <li> <a href="http://b0llix.net/perp/">perp</a>, Wayne Marshall's take on supervision </li>
+ <li> Integrated init systems providing a lot of features, process supervision being one of them.
+For instance, <a href="http://upstart.ubuntu.com/">Upstart</a>, MacOS X's 
+<a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man8/launchd.8.html">launchd</a>,
+and Fedora's <a href="http://freedesktop.org/wiki/Software/systemd">systemd</a>. </li>
+</ul>
+
+<p>
+ Why is s6 needed ? What does it do differently ? Here are the criteria I used.
+</p>
+
+
+<h2> Supervision suites should not wake up unless notified. </h2>
+
+<ul>
+ <li> System V init fails the test: it wakes up every 5 seconds, for the reason that
+<tt>/dev/initctl</tt> might have changed.
+<a href="http://demotivate.me/mediafiles/full/4162010103911AM_picard-no-facepalm.jpg"><tt>m(</tt></a> </li>
+ <li> daemontools fails the test: it wakes up every 5 seconds to check for new services. </li>
+ <li> daemontools-encore does the same. </li>
+ <li> the current version of runit fails the test: it wakes up every 14 seconds. But this is a workaround for a bug in some Linux kernels;
+there is no design flaw in runit that prevents it from passing the test. </li>
+ <li> perp works. </li>
+ <li> Upstart works. I have no idea what other integrated init systems do: it's much too difficult to strace them
+to see exactly where they're spending their time, and when it is possible, the trace output is so big that it's
+hard to extract any valuable information from it. </li>
+ <li> s6 works. By default, s6-svscan wakes up every 5 seconds, to emulate
+<a href="http://cr.yp.to/daemontools/svscan.html">svscan</a> behaviour; but it
+can be told not to do so. (<tt>s6-svscan -t0</tt>) </li>
+</ul>
+
+
+<h2> Supervision suites should provide a program that can run as process 1. </h2>
+
+<ul>
+ <li> System V init <em>is</em> process 1, so no problem here. </li>
+ <li> Integrated init systems, by definition, provide a process 1. </li>
+ <li> daemontools was not designed to take over init, although
+<a href="http://code.dogmap.org./svscan-1/">it can be made to work</a> with
+enough hacking skills. Same thing with daemontools-encore. </li>
+ <li> runit provides an <em>init</em> functionality, but the mechanism is
+separate from the supervision itself; the <tt>runit</tt> process, not the
+<tt>runsvdir</tt> process, runs as process 1. This lengthens the supervision
+chain. </li>
+ <li> perp was not designed to run as process 1. It probably could be made to work too
+without too much trouble. </li>
+ <li> s6-svscan was designed from the start to be run as process 1, although it
+does not have to. </li>
+</ul>
+
+
+<h2> Supervision suites should be bug-free, lightweight and easy to understand. </h2>
+
+<ul>
+ <li> daemontools, daemontools-encore, runit and perp all qualify. All of this is excellent quality
+code, <a href="http://skarnet.org/software/skalibs/djblegacy.html">unsurprisingly</a>. </li>
+ <li> System V init is understandable, and reasonably lightweight; but it is still
+too big for what it does - poorly. The <tt>/etc/inittab</tt> file needs to be parsed;
+that parser has to be in process 1. There is support in process 1 for the whole
+"runlevel" concept, which is a primitive form of service management. The same
+executable handles all 3 stages of the machine's lifetime and does not separate
+them properly. All in all, System V init does its job, but is showing its age
+and nowadays we know much better designs. </li>
+ <li> This is where integrated init systems fail, hard. By wanting to organize
+the way a the machine is operated - so, machine state management - in the
+<em>same package</em> as the init and process supervision system, they add
+incredible complexity where it does not belong.
+ <ul>
+  <li> Upstart uses <tt>ptrace</tt> to watch its children fork(), and links
+process 1 against libdbus. This is insane.
+Process 1 should be <em>absolutely stable</em>, it should be guaranteed
+to never crash, so the whole of its source code should be under control. At
+Upstart's level of complexity, those goals are outright impossible to achieve,
+so this approach is flawed by design. </li>
+ <li> launchd suffers from the same kind of problem. Regardless of how
+things are actually implemented inside (which I have no idea about), services
+running under launchd must be configured
+<a href="https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html">using
+XML</a>. That means there is an XML parser in process 1.
+<a href="https://software.intel.com/sites/default/files/race.jpg">What
+could possibly go wrong&nbsp;?</a> </li>
+ <li> systemd is much, much worse than the other ones, and a real danger
+for the future of GNU/Linux. I have a <a href="systemd.html">special page</a>
+dedicated to it. </li>
+ </ul>
+ What those systems fail to recognize is that process supervision, rooted in
+process 1, is a good thing, and machine management is also a good thing, but
+<strong>those are two different functions</strong>, and a good init system
+needs, and <strong>should</strong>, only provide process supervision, in
+order to keep such a crucial piece of code as easy to maintain as possible.
+Machine management can be added <em>on top of</em> a process supervision
+suite, in a different package, and it has nothing to do with process 1. </li>
+ <li> s6, which has been designed with embedded environments in mind, tries
+harder than anyone to pass this. It tries so hard that <tt>s6-svscan</tt>
+and <tt>s6-supervise</tt>, the two long-running programs that make the
+supervision chain, <em>do not even allocate heap memory</em>, and their main
+program source files are less than 500 lines long. </li>
+</ul>
+
+
+<h2> Supervision suites should provide a basis for high-level service management. </h2>
+
+<ul>
+ <li> Neither System V init, daemontools, runit or perp
+provides any hooks to wait for a service to go up or down. runit provides a
+waiting mechanism, but it's based on polling, and the <tt>./check</tt> script
+has to be manually written for every service. </li>
+ <li> daemontools-encore qualifies: the <em>notify script</em> can be used for
+inter-service communication. But it's just a hook: all the real notification
+work has to be done by the notify script itself, no notification framework is
+provided. </li>
+ <li> Integrated init systems provide high-level service management
+themselves. Again, this is not good design: service management has nothing
+to do with init or process supervision, and should be implemented on top
+of it, not as a part of it. </li>
+ <li> s6 comes with <a href="libftrig.html">libftrig</a>, an event notification
+library, and command-line tools based on this library, thus providing a simple
+API for future service management tools to build upon. </li>
+</ul>
+
+
+<h2> Artistic considerations </h2>
+
+<ul>
+ <li> <tt>s6-svscan</tt> and <tt>s6-supervise</tt> are <em>entirely asynchronous</em>.
+Even during trouble (full process table, for instance), they'll remain reactive
+and instantly respond to commands they may receive. <tt>s6-supervise</tt> has
+even been implemented as a full deterministic finite automaton, to ensure it
+always does the right thing under any circumstance. Other supervision suites
+do not achieve that for now. </li>
+ <li> daemontools' <a href="http://cr.yp.to/daemontools/svscan.html">svscan</a>
+maintains an open pipe between a daemon and its logger, so even if the daemon,
+the logger, <em>and</em> both
+<a href="http://cr.yp.to/daemontools/supervise.html">supervise</a> processes
+die, the pipe is still the same so <em>no logs are lost, ever</em>, unless
+svscan itself dies. </li>
+ <li> runit has only one supervisor, <a href="http://smarden.org/runit/runsv.8.html">runsv</a>,
+for both a daemon and its logger. The pipe is maintained by <tt>runsv</tt>.
+If the <tt>runsv</tt> process dies, the pipe disappears and logs are lost.
+So, runit does not offer as strong a guarantee as daemontools. </li>
+ <li> perp has only one process, <a href="http://b0llix.net/perp/site.cgi?page=perpd.8">perpd</a>,
+acting both as a "daemon and logger supervisor" (like <tt>runsv</tt>) and as a
+"service directory scanner" (like <tt>runsvdir</tt>). It maintains the pipes
+between the daemons and their respective loggers. If perpd dies, everything
+is lost. Since perpd cannot be run as process 1, this is a possible SPOF for
+a perp installation; however, perpd is well-written and has virtually no risk of
+dying, especially compared to process 1 behemoths provided by integrated
+init systems. </li>
+ <li> Besides, the <tt>runsv</tt> model, which has to handle both a daemon
+and its logger, is more complex than the <tt>supervise</tt> model (which
+only has to handle a daemon). Consequently, the <tt>runsvdir</tt> model is
+simpler than the <tt>svscan</tt> model, but there is only one <tt>svscan</tt>
+instance when there are several <tt>runsv</tt>s and <tt>supervise</tt>s.
+The <tt>perpd</tt> model is obviously the most complex; while very understandable,
+<tt>perpd</tt> is unarguably harder to maintain than the other two. </li>
+ <li> So, to achieve maximum simplicity and code reuse, and minimal memory
+footprint, s6's design is close to daemontools' one.
+And when <a href="s6-svscan-1.html">s6-svscan is run as process 1</a>,
+pipes between daemons and loggers are never lost. </li>
+</ul>
+
+
+<h2> Conclusion </h2>
+
+<p>
+ All in all, I believe that s6 offers the best overall implementation of a
+supervision suite <em>as it should be designed</em>. At worst, it's just another
+take on daemontools with a <a href="http://skarnet.org/software/skalibs/">reliable
+base library</a> and a few nifty features.
+</p>
+
+</body>
+</html>
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..a966904
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1,2 @@
+/package/prog/skalibs
+/package/admin/execline
diff --git a/package/deps.mak b/package/deps.mak
new file mode 100644
index 0000000..97fd866
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,101 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/include/s6/ftrigr.h: src/include/s6/config.h
+src/include/s6/s6.h: src/include/s6/ftrigr.h src/include/s6/ftrigw.h src/include/s6/s6-supervise.h src/include/s6/s6lock.h
+src/include/s6/s6lock.h: src/include/s6/config.h
+src/daemontools-extras/s6-envdir.o src/daemontools-extras/s6-envdir.lo: src/daemontools-extras/s6-envdir.c
+src/daemontools-extras/s6-envuidgid.o src/daemontools-extras/s6-envuidgid.lo: src/daemontools-extras/s6-envuidgid.c
+src/daemontools-extras/s6-fghack.o src/daemontools-extras/s6-fghack.lo: src/daemontools-extras/s6-fghack.c
+src/daemontools-extras/s6-log.o src/daemontools-extras/s6-log.lo: src/daemontools-extras/s6-log.c
+src/daemontools-extras/s6-notifywhenup.o src/daemontools-extras/s6-notifywhenup.lo: src/daemontools-extras/s6-notifywhenup.c src/include/s6/ftrigw.h
+src/daemontools-extras/s6-setlock.o src/daemontools-extras/s6-setlock.lo: src/daemontools-extras/s6-setlock.c src/include/s6/config.h
+src/daemontools-extras/s6-setsid.o src/daemontools-extras/s6-setsid.lo: src/daemontools-extras/s6-setsid.c
+src/daemontools-extras/s6-setuidgid.o src/daemontools-extras/s6-setuidgid.lo: src/daemontools-extras/s6-setuidgid.c
+src/daemontools-extras/s6-softlimit.o src/daemontools-extras/s6-softlimit.lo: src/daemontools-extras/s6-softlimit.c
+src/daemontools-extras/s6-tai64n.o src/daemontools-extras/s6-tai64n.lo: src/daemontools-extras/s6-tai64n.c
+src/daemontools-extras/s6-tai64nlocal.o src/daemontools-extras/s6-tai64nlocal.lo: src/daemontools-extras/s6-tai64nlocal.c
+src/daemontools-extras/ucspilogd.o src/daemontools-extras/ucspilogd.lo: src/daemontools-extras/ucspilogd.c
+src/libs6/ftrig1_free.o src/libs6/ftrig1_free.lo: src/libs6/ftrig1_free.c src/libs6/ftrig1.h
+src/libs6/ftrig1_make.o src/libs6/ftrig1_make.lo: src/libs6/ftrig1_make.c src/libs6/ftrig1.h
+src/libs6/ftrigr1_zero.o src/libs6/ftrigr1_zero.lo: src/libs6/ftrigr1_zero.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_check.o src/libs6/ftrigr_check.lo: src/libs6/ftrigr_check.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_end.o src/libs6/ftrigr_end.lo: src/libs6/ftrigr_end.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_start.o src/libs6/ftrigr_start.lo: src/libs6/ftrigr_start.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_startf.o src/libs6/ftrigr_startf.lo: src/libs6/ftrigr_startf.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_subscribe.o src/libs6/ftrigr_subscribe.lo: src/libs6/ftrigr_subscribe.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_unsubscribe.o src/libs6/ftrigr_unsubscribe.lo: src/libs6/ftrigr_unsubscribe.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_update.o src/libs6/ftrigr_update.lo: src/libs6/ftrigr_update.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_wait_and.o src/libs6/ftrigr_wait_and.lo: src/libs6/ftrigr_wait_and.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_wait_or.o src/libs6/ftrigr_wait_or.lo: src/libs6/ftrigr_wait_or.c src/include/s6/ftrigr.h
+src/libs6/ftrigr_zero.o src/libs6/ftrigr_zero.lo: src/libs6/ftrigr_zero.c src/include/s6/ftrigr.h
+src/libs6/ftrigw_clean.o src/libs6/ftrigw_clean.lo: src/libs6/ftrigw_clean.c src/libs6/ftrig1.h src/include/s6/ftrigw.h
+src/libs6/ftrigw_fifodir_make.o src/libs6/ftrigw_fifodir_make.lo: src/libs6/ftrigw_fifodir_make.c src/include/s6/ftrigw.h
+src/libs6/ftrigw_notify.o src/libs6/ftrigw_notify.lo: src/libs6/ftrigw_notify.c src/include/s6/ftrigw.h
+src/libs6/ftrigw_notifyb.o src/libs6/ftrigw_notifyb.lo: src/libs6/ftrigw_notifyb.c src/libs6/ftrig1.h src/include/s6/ftrigw.h
+src/libs6/s6-ftrigrd.o src/libs6/s6-ftrigrd.lo: src/libs6/s6-ftrigrd.c src/libs6/ftrig1.h src/include/s6/ftrigr.h
+src/libs6/s6_supervise_lock.o src/libs6/s6_supervise_lock.lo: src/libs6/s6_supervise_lock.c src/include/s6/s6-supervise.h
+src/libs6/s6_supervise_lock_mode.o src/libs6/s6_supervise_lock_mode.lo: src/libs6/s6_supervise_lock_mode.c src/include/s6/s6-supervise.h
+src/libs6/s6_svc_main.o src/libs6/s6_svc_main.lo: src/libs6/s6_svc_main.c src/include/s6/s6-supervise.h
+src/libs6/s6_svc_write.o src/libs6/s6_svc_write.lo: src/libs6/s6_svc_write.c src/include/s6/s6-supervise.h
+src/libs6/s6_svstatus_pack.o src/libs6/s6_svstatus_pack.lo: src/libs6/s6_svstatus_pack.c src/include/s6/s6-supervise.h
+src/libs6/s6_svstatus_read.o src/libs6/s6_svstatus_read.lo: src/libs6/s6_svstatus_read.c src/include/s6/s6-supervise.h
+src/libs6/s6_svstatus_unpack.o src/libs6/s6_svstatus_unpack.lo: src/libs6/s6_svstatus_unpack.c src/include/s6/s6-supervise.h
+src/libs6/s6_svstatus_write.o src/libs6/s6_svstatus_write.lo: src/libs6/s6_svstatus_write.c src/include/s6/s6-supervise.h
+src/libs6/s6lock_acquire.o src/libs6/s6lock_acquire.lo: src/libs6/s6lock_acquire.c src/include/s6/s6lock.h
+src/libs6/s6lock_check.o src/libs6/s6lock_check.lo: src/libs6/s6lock_check.c src/include/s6/s6lock.h
+src/libs6/s6lock_end.o src/libs6/s6lock_end.lo: src/libs6/s6lock_end.c src/include/s6/s6lock.h
+src/libs6/s6lock_release.o src/libs6/s6lock_release.lo: src/libs6/s6lock_release.c src/include/s6/s6lock.h
+src/libs6/s6lock_start.o src/libs6/s6lock_start.lo: src/libs6/s6lock_start.c src/include/s6/s6lock.h
+src/libs6/s6lock_startf.o src/libs6/s6lock_startf.lo: src/libs6/s6lock_startf.c src/include/s6/s6lock.h
+src/libs6/s6lock_update.o src/libs6/s6lock_update.lo: src/libs6/s6lock_update.c src/include/s6/s6lock.h
+src/libs6/s6lock_wait_and.o src/libs6/s6lock_wait_and.lo: src/libs6/s6lock_wait_and.c src/include/s6/s6lock.h
+src/libs6/s6lock_wait_or.o src/libs6/s6lock_wait_or.lo: src/libs6/s6lock_wait_or.c src/include/s6/s6lock.h
+src/libs6/s6lock_zero.o src/libs6/s6lock_zero.lo: src/libs6/s6lock_zero.c src/include/s6/s6lock.h
+src/libs6/s6lockd-helper.o src/libs6/s6lockd-helper.lo: src/libs6/s6lockd-helper.c
+src/libs6/s6lockd.o src/libs6/s6lockd.lo: src/libs6/s6lockd.c src/include/s6/s6lock.h
+src/pipe-tools/s6-cleanfifodir.o src/pipe-tools/s6-cleanfifodir.lo: src/pipe-tools/s6-cleanfifodir.c src/include/s6/ftrigw.h
+src/pipe-tools/s6-ftrig-listen.o src/pipe-tools/s6-ftrig-listen.lo: src/pipe-tools/s6-ftrig-listen.c src/include/s6/ftrigr.h
+src/pipe-tools/s6-ftrig-listen1.o src/pipe-tools/s6-ftrig-listen1.lo: src/pipe-tools/s6-ftrig-listen1.c src/include/s6/ftrigr.h
+src/pipe-tools/s6-ftrig-notify.o src/pipe-tools/s6-ftrig-notify.lo: src/pipe-tools/s6-ftrig-notify.c src/include/s6/ftrigw.h
+src/pipe-tools/s6-ftrig-wait.o src/pipe-tools/s6-ftrig-wait.lo: src/pipe-tools/s6-ftrig-wait.c src/include/s6/ftrigr.h
+src/pipe-tools/s6-mkfifodir.o src/pipe-tools/s6-mkfifodir.lo: src/pipe-tools/s6-mkfifodir.c src/include/s6/ftrigw.h
+src/supervision/s6-supervise.o src/supervision/s6-supervise.lo: src/supervision/s6-supervise.c src/include/s6/ftrigw.h src/include/s6/s6-supervise.h
+src/supervision/s6-svc.o src/supervision/s6-svc.lo: src/supervision/s6-svc.c src/include/s6/s6-supervise.h
+src/supervision/s6-svok.o src/supervision/s6-svok.lo: src/supervision/s6-svok.c src/include/s6/s6-supervise.h
+src/supervision/s6-svscan.o src/supervision/s6-svscan.lo: src/supervision/s6-svscan.c src/include/s6/config.h src/include/s6/s6-supervise.h
+src/supervision/s6-svscanctl.o src/supervision/s6-svscanctl.lo: src/supervision/s6-svscanctl.c src/include/s6/s6-supervise.h
+src/supervision/s6-svstat.o src/supervision/s6-svstat.lo: src/supervision/s6-svstat.c src/include/s6/s6-supervise.h
+src/supervision/s6-svwait.o src/supervision/s6-svwait.lo: src/supervision/s6-svwait.c src/include/s6/ftrigr.h src/include/s6/s6-supervise.h
+
+s6-envdir: src/daemontools-extras/s6-envdir.o -lskarnet
+s6-envuidgid: src/daemontools-extras/s6-envuidgid.o -lskarnet
+s6-fghack: src/daemontools-extras/s6-fghack.o -lskarnet
+s6-log: src/daemontools-extras/s6-log.o -lskarnet ${TAINNOW_LIB}
+s6-notifywhenup: src/daemontools-extras/s6-notifywhenup.o -ls6 -lskarnet ${TAINNOW_LIB}
+s6-setlock: src/daemontools-extras/s6-setlock.o -lskarnet ${TAINNOW_LIB}
+s6-setsid: src/daemontools-extras/s6-setsid.o -lskarnet
+s6-setuidgid: src/daemontools-extras/s6-setuidgid.o -lskarnet
+s6-softlimit: src/daemontools-extras/s6-softlimit.o -lskarnet
+s6-tai64n: src/daemontools-extras/s6-tai64n.o -lskarnet ${SYSCLOCK_LIB}
+s6-tai64nlocal: src/daemontools-extras/s6-tai64nlocal.o -lskarnet
+ucspilogd: src/daemontools-extras/ucspilogd.o -lskarnet
+libs6.a:  src/libs6/ftrigr1_zero.o src/libs6/ftrigr_check.o src/libs6/ftrigr_end.o src/libs6/ftrigr_start.o src/libs6/ftrigr_startf.o src/libs6/ftrigr_subscribe.o src/libs6/ftrigr_unsubscribe.o src/libs6/ftrigr_update.o src/libs6/ftrigr_wait_and.o src/libs6/ftrigr_wait_or.o src/libs6/ftrigr_zero.o src/libs6/ftrigw_clean.o src/libs6/ftrigw_fifodir_make.o src/libs6/ftrigw_notify.o src/libs6/ftrigw_notifyb.o src/libs6/s6_supervise_lock.o src/libs6/s6_supervise_lock_mode.o src/libs6/s6_svc_main.o src/libs6/s6_svc_write.o src/libs6/s6_svstatus_pack.o src/libs6/s6_svstatus_read.o src/libs6/s6_svstatus_unpack.o src/libs6/s6_svstatus_write.o src/libs6/s6lock_acquire.o src/libs6/s6lock_check.o src/libs6/s6lock_end.o src/libs6/s6lock_release.o src/libs6/s6lock_start.o src/libs6/s6lock_startf.o src/libs6/s6lock_update.o src/libs6/s6lock_wait_and.o src/libs6/s6lock_wait_or.o src/libs6/s6lock_zero.o
+libs6.so:  src/libs6/ftrigr1_zero.lo src/libs6/ftrigr_check.lo src/libs6/ftrigr_end.lo src/libs6/ftrigr_start.lo src/libs6/ftrigr_startf.lo src/libs6/ftrigr_subscribe.lo src/libs6/ftrigr_unsubscribe.lo src/libs6/ftrigr_update.lo src/libs6/ftrigr_wait_and.lo src/libs6/ftrigr_wait_or.lo src/libs6/ftrigr_zero.lo src/libs6/ftrigw_clean.lo src/libs6/ftrigw_fifodir_make.lo src/libs6/ftrigw_notify.lo src/libs6/ftrigw_notifyb.lo src/libs6/s6_supervise_lock.lo src/libs6/s6_supervise_lock_mode.lo src/libs6/s6_svc_main.lo src/libs6/s6_svc_write.lo src/libs6/s6_svstatus_pack.lo src/libs6/s6_svstatus_read.lo src/libs6/s6_svstatus_unpack.lo src/libs6/s6_svstatus_write.lo src/libs6/s6lock_acquire.lo src/libs6/s6lock_check.lo src/libs6/s6lock_end.lo src/libs6/s6lock_release.lo src/libs6/s6lock_start.lo src/libs6/s6lock_startf.lo src/libs6/s6lock_update.lo src/libs6/s6lock_wait_and.lo src/libs6/s6lock_wait_or.lo src/libs6/s6lock_zero.lo
+s6-ftrigrd: src/libs6/s6-ftrigrd.o src/libs6/ftrig1_free.o src/libs6/ftrig1_make.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB}
+s6lockd: src/libs6/s6lockd.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB}
+s6lockd-helper: src/libs6/s6lockd-helper.o -lskarnet
+s6-cleanfifodir: src/pipe-tools/s6-cleanfifodir.o -ls6 -lskarnet
+s6-ftrig-listen: src/pipe-tools/s6-ftrig-listen.o -ls6 -lexecline -lskarnet ${TAINNOW_LIB}
+s6-ftrig-listen1: src/pipe-tools/s6-ftrig-listen1.o -ls6 -lskarnet ${TAINNOW_LIB}
+s6-ftrig-notify: src/pipe-tools/s6-ftrig-notify.o -ls6 -lskarnet
+s6-ftrig-wait: src/pipe-tools/s6-ftrig-wait.o -ls6 -lskarnet ${TAINNOW_LIB}
+s6-mkfifodir: src/pipe-tools/s6-mkfifodir.o -ls6 -lskarnet
+s6-supervise: src/supervision/s6-supervise.o -ls6 -lskarnet ${TAINNOW_LIB}
+s6-svc: src/supervision/s6-svc.o -ls6 -lskarnet
+s6-svok: src/supervision/s6-svok.o -lskarnet
+s6-svscan: src/supervision/s6-svscan.o -ls6 -lskarnet ${TAINNOW_LIB}
+s6-svscanctl: src/supervision/s6-svscanctl.o -ls6 -lskarnet
+s6-svstat: src/supervision/s6-svstat.o -ls6 -lskarnet ${SYSCLOCK_LIB}
+s6-svwait: src/supervision/s6-svwait.o -ls6 -lskarnet ${TAINNOW_LIB}
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..0686726
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=s6
+version=2.0.0.0
+category=admin
+package_macro_name=S6
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..d3fb2e0
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,28 @@
+s6-ftrigrd		0755
+s6-ftrig-listen1	0755
+s6-ftrig-listen		0755
+s6-ftrig-notify		0755
+s6-ftrig-wait		0755
+s6lockd			0755
+s6lockd-helper		0755
+s6-cleanfifodir		0755
+s6-mkfifodir		0755
+s6-notifywhenup		0755
+s6-svscan		0755
+s6-supervise		0755
+s6-svc			0755
+s6-svscanctl		0755
+s6-svok			0755
+s6-svstat		0755
+s6-svwait		0755
+s6-envdir		0755
+s6-envuidgid		0755
+s6-fghack		0755
+s6-log			0755
+s6-setlock		0755
+s6-setsid		0755
+s6-setuidgid		0700
+s6-softlimit		0755
+s6-tai64n		0755
+s6-tai64nlocal		0755
+ucspilogd		0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..9387792
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,36 @@
+BIN_TARGETS = \
+s6-ftrigrd \
+s6-ftrig-listen1 \
+s6-ftrig-listen \
+s6-ftrig-notify \
+s6-ftrig-wait \
+s6lockd \
+s6-cleanfifodir \
+s6-mkfifodir \
+s6-notifywhenup \
+s6-svscan \
+s6-supervise \
+s6-svc \
+s6-svscanctl \
+s6-svok \
+s6-svstat \
+s6-svwait \
+s6-envdir \
+s6-envuidgid \
+s6-fghack \
+s6-log \
+s6-setlock \
+s6-setsid \
+s6-setuidgid \
+s6-softlimit \
+s6-tai64n \
+s6-tai64nlocal
+
+LIBEXEC_TARGETS = \
+s6lockd-helper
+
+SHARED_LIBS = \
+libs6.so
+
+STATIC_LIBS = \
+libs6.a
diff --git a/patch-for-solaris b/patch-for-solaris
new file mode 100755
index 0000000..02f2e3c
--- /dev/null
+++ b/patch-for-solaris
@@ -0,0 +1,17 @@
+#!/usr/xpg4/bin/sh
+
+patchit () {
+  echo '#!/usr/xpg4/bin/sh' > $1.tmp
+  tail -n +2 $1 >> $1.tmp
+  mv -f $1.tmp $1
+  chmod 755 $1
+}
+
+patchit ./configure
+patchit ./tools/install.sh
+patchit ./tools/gen-deps.sh
+
+echo 'SHELL := /usr/xpg4/bin/sh' > Makefile.tmp
+echo >> Makefile.tmp
+cat Makefile >> Makefile.tmp
+mv -f Makefile.tmp Makefile
diff --git a/src/daemontools-extras/deps-exe/s6-envdir b/src/daemontools-extras/deps-exe/s6-envdir
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-envdir
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/s6-envuidgid b/src/daemontools-extras/deps-exe/s6-envuidgid
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-envuidgid
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/s6-fghack b/src/daemontools-extras/deps-exe/s6-fghack
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-fghack
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/s6-log b/src/daemontools-extras/deps-exe/s6-log
new file mode 100644
index 0000000..1840bc1
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-log
@@ -0,0 +1,2 @@
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/daemontools-extras/deps-exe/s6-notifywhenup b/src/daemontools-extras/deps-exe/s6-notifywhenup
new file mode 100644
index 0000000..58a34e0
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-notifywhenup
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/daemontools-extras/deps-exe/s6-setlock b/src/daemontools-extras/deps-exe/s6-setlock
new file mode 100644
index 0000000..1840bc1
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-setlock
@@ -0,0 +1,2 @@
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/daemontools-extras/deps-exe/s6-setsid b/src/daemontools-extras/deps-exe/s6-setsid
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-setsid
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/s6-setuidgid b/src/daemontools-extras/deps-exe/s6-setuidgid
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-setuidgid
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/s6-softlimit b/src/daemontools-extras/deps-exe/s6-softlimit
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-softlimit
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/s6-tai64n b/src/daemontools-extras/deps-exe/s6-tai64n
new file mode 100644
index 0000000..a11a5f4
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-tai64n
@@ -0,0 +1,2 @@
+-lskarnet
+${SYSCLOCK_LIB}
diff --git a/src/daemontools-extras/deps-exe/s6-tai64nlocal b/src/daemontools-extras/deps-exe/s6-tai64nlocal
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/s6-tai64nlocal
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/deps-exe/ucspilogd b/src/daemontools-extras/deps-exe/ucspilogd
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/daemontools-extras/deps-exe/ucspilogd
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/daemontools-extras/s6-envdir.c b/src/daemontools-extras/s6-envdir.c
new file mode 100644
index 0000000..394253e
--- /dev/null
+++ b/src/daemontools-extras/s6-envdir.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-envdir [ -I | -i ] [ -n ] [ -f ] [ -c nullchar ] dir prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  stralloc modifs = STRALLOC_ZERO ;
+  subgetopt_t l = SUBGETOPT_ZERO ;
+  int insist = 1 ;
+  unsigned int options = 0 ;
+  char nullis = '\n' ;
+  PROG = "s6-envdir" ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "Iinfc:", &l) ;
+    if (opt == -1) break ;
+    switch (opt)
+    {
+      case 'I' : insist = 0 ; break ;
+      case 'i' : insist = 1 ; break ;
+      case 'n' : options |= SKALIBS_ENVDIR_NOCHOMP ; break ;
+      case 'f' : options |= SKALIBS_ENVDIR_VERBATIM ; break ;
+      case 'c' : nullis = *l.arg ; break ;
+      default : strerr_dieusage(100, USAGE) ;
+    }
+  }
+  argc -= l.ind ; argv += l.ind ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if ((envdir_internal(*argv++, &modifs, options, nullis) < 0) && (insist || (errno != ENOENT)))
+    strerr_diefu1sys(111, "envdir") ;
+  pathexec_r(argv, envp, env_len(envp), modifs.s, modifs.len) ;
+  strerr_dieexec(111, argv[0]) ;
+}
diff --git a/src/daemontools-extras/s6-envuidgid.c b/src/daemontools-extras/s6-envuidgid.c
new file mode 100644
index 0000000..64521b0
--- /dev/null
+++ b/src/daemontools-extras/s6-envuidgid.c
@@ -0,0 +1,44 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <limits.h>
+#include <skalibs/gidstuff.h>
+#include <skalibs/env.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-envuidgid username prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  PROG = "s6-envuidgid" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  {
+    char fmt[UINT64_FMT] ;
+    struct passwd *pw = getpwnam(argv[1]) ;
+    if (!pw) strerr_dief2x(1, "unknown user: ", argv[1]) ;
+    fmt[gid_fmt(fmt, pw->pw_gid)] = 0 ;
+    if (!pathexec_env("GID", fmt))
+      strerr_diefu1sys(111, "update environment") ;
+    fmt[uint64_fmt(fmt, pw->pw_uid)] = 0 ;
+    if (!pathexec_env("UID", fmt))
+      strerr_diefu1sys(111, "update environment") ;
+  }
+  
+  {
+    gid_t tab[NGROUPS_MAX] ;
+    int n = prot_readgroups(argv[1], tab, NGROUPS_MAX) ;
+    if (n < 0)
+      strerr_diefu2sys(111, "get supplementary groups for ", argv[1]) ;
+    {
+      char fmt[GID_FMT * n] ;
+      fmt[gid_fmtlist(fmt, tab, n)] = 0 ;
+      if (!pathexec_env("GIDLIST", fmt))
+        strerr_diefu1sys(111, "update environment") ;
+    }
+  }
+  pathexec_fromenv(argv+2, envp, env_len(envp)) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/daemontools-extras/s6-fghack.c b/src/daemontools-extras/s6-fghack.c
new file mode 100644
index 0000000..1415706
--- /dev/null
+++ b/src/daemontools-extras/s6-fghack.c
@@ -0,0 +1,68 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-fghack prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int p[2] ;
+  int pcoe[2] ;
+  pid_t pid ;
+  char dummy ;
+  PROG = "s6-fghack" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if (pipe(p) < 0) strerr_diefu1sys(111, "create hackpipe") ;
+  if (pipe(pcoe) < 0) strerr_diefu1sys(111, "create coepipe") ;
+
+  switch (pid = fork())
+  {
+    case -1 : strerr_diefu1sys(111, "fork") ;
+    case 0 :
+    {
+      int i = 0 ;
+      fd_close(p[0]) ;
+      fd_close(pcoe[0]) ;
+      if (coe(pcoe[1]) < 0) _exit(111) ;
+      for (; i < 30 ; i++) dup(p[1]) ; /* hack. gcc's warning is justified. */
+      pathexec_run(argv[1], argv+1, envp) ;
+      i = errno ;
+      if (fd_write(pcoe[1], "", 1) < 1) _exit(111) ;
+      _exit(i) ;
+    }
+  }
+
+  fd_close(p[1]) ;
+  fd_close(pcoe[1]) ;
+
+  switch (fd_read(pcoe[0], &dummy, 1))
+  {
+    case -1 : strerr_diefu1sys(111, "read on coepipe") ;
+    case 1 :
+    {
+      int wstat ;
+      if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ;
+      errno = WEXITSTATUS(wstat) ;
+      strerr_dieexec(111, argv[1]) ;
+    }
+  }
+
+  fd_close(pcoe[0]) ;
+
+  p[1] = fd_read(p[0], &dummy, 1) ;
+  if (p[1] < 0) strerr_diefu1sys(111, "read on hackpipe") ;
+  if (p[1]) strerr_dief2x(102, argv[1], " wrote on hackpipe") ;
+
+  {
+    int wstat ;
+    if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ;
+    if (WIFSIGNALED(wstat)) strerr_dief2x(111, argv[2], " crashed") ;
+    return WEXITSTATUS(wstat) ;
+  }
+}
diff --git a/src/daemontools-extras/s6-log.c b/src/daemontools-extras/s6-log.c
new file mode 100644
index 0000000..e788016
--- /dev/null
+++ b/src/daemontools-extras/s6-log.c
@@ -0,0 +1,1265 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>  /* for rename() */
+#include <stdlib.h>  /* for qsort() */
+#include <regex.h>
+#include <skalibs/uint32.h>
+#include <skalibs/uint64.h>
+#include <skalibs/uint.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/buffer.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/bufalloc.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/tai.h>
+#include <skalibs/error.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/direntry.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/environ.h>
+
+#define USAGE "s6-log [ -q | -v ] [ -b ] [ -p ] [ -t ] [ -e ] logging_script"
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+static int flagstampalert = 0 ;
+static int flagstamp = 0 ;
+static int flagprotect = 0 ;
+static int flagexiting = 0 ;
+static unsigned int verbosity = 1 ;
+
+static stralloc indata = STRALLOC_ZERO ;
+
+
+/* Begin datatypes. Get ready for some lulz. */
+
+typedef int qcmpfunc_t (void const *, void const *) ;
+typedef qcmpfunc_t *qcmpfunc_t_ref ;
+
+typedef enum rotstate_e rotstate_t, *rotstate_t_ref ;
+enum rotstate_e
+{
+  ROTSTATE_WRITABLE,
+  ROTSTATE_START,
+  ROTSTATE_RENAME,
+  ROTSTATE_NEWCURRENT,
+  ROTSTATE_CHMODPREVIOUS,
+  ROTSTATE_FINISHPREVIOUS,
+  ROTSTATE_RUNPROCESSOR,
+  ROTSTATE_WAITPROCESSOR,
+  ROTSTATE_SYNCPROCESSED,
+  ROTSTATE_SYNCNEWSTATE,
+  ROTSTATE_UNLINKPREVIOUS,
+  ROTSTATE_RENAMESTATE,
+  ROTSTATE_FINISHPROCESSED,
+  ROTSTATE_ENDFCHMOD,
+  ROTSTATE_END
+} ;
+
+typedef enum seltype_e seltype_t, *seltype_t_ref ;
+enum seltype_e
+{
+  SELTYPE_DEFAULT,
+  SELTYPE_PLUS,
+  SELTYPE_MINUS,
+  SELTYPE_PHAIL
+} ;
+
+typedef struct sel_s sel_t, *sel_t_ref ;
+struct sel_s
+{
+  seltype_t type ;
+  regex_t re ;
+} ;
+
+#define SEL_ZERO { .type = SELTYPE_PHAIL }
+
+static void sel_free (sel_t_ref s)
+{
+  if (s->type != SELTYPE_DEFAULT) regfree(&s->re) ;
+  s->type = SELTYPE_PHAIL ;
+}
+
+typedef enum acttype_e acttype_t, *acttype_t_ref ;
+enum acttype_e
+{
+  ACTTYPE_FD2,
+  ACTTYPE_STATUS,
+  ACTTYPE_DIR,
+  ACTTYPE_PHAIL
+} ;
+
+typedef struct as_fd2_s as_fd2_t, *as_fd2_t_ref ;
+struct as_fd2_s
+{
+  unsigned int size ;
+} ;
+
+typedef struct as_status_s as_status_t, *as_status_t_ref ;
+struct as_status_s
+{
+  stralloc content ;
+  char const *file ;
+} ;
+
+static void as_status_free (as_status_t_ref ap)
+{
+  stralloc_free(&ap->content) ;
+  ap->file = 0 ;
+}
+
+typedef struct as_dir_s as_dir_t, *as_dir_t_ref ;
+struct as_dir_s
+{
+  unsigned int lindex ;
+} ;
+
+typedef union actstuff_u actstuff_t, *actstuff_t_ref ;
+union actstuff_u
+{
+  as_fd2_t fd2 ;
+  as_status_t status ;
+  as_dir_t dir ;
+} ;
+
+typedef struct act_s act_t, *act_t_ref ;
+struct act_s
+{
+  acttype_t type ;
+  actstuff_t data ;
+} ;
+
+static void act_free (act_t_ref ap)
+{
+  switch (ap->type)
+  {
+    case ACTTYPE_FD2 :
+      break ;
+    case ACTTYPE_STATUS :
+      as_status_free(&ap->data.status) ;
+      break ;
+    case ACTTYPE_DIR :
+      break ;
+    default : break ;
+  }
+  ap->type = ACTTYPE_PHAIL ;
+}
+
+typedef struct scriptelem_s scriptelem_t, *scriptelem_t_ref ;
+struct scriptelem_s
+{
+  genalloc selections ; /* array of sel_t */
+  genalloc actions ; /* array of act_t */
+} ;
+
+#define SCRIPTELEM_ZERO { .selections = GENALLOC_ZERO, .actions = GENALLOC_ZERO }
+
+static void scriptelem_free (scriptelem_t_ref se)
+{
+  scriptelem_t zero = SCRIPTELEM_ZERO ;
+  genalloc_deepfree(sel_t, &se->selections, &sel_free) ;
+  genalloc_deepfree(act_t, &se->actions, &act_free) ;
+  *se = zero ;
+}
+
+typedef void inputprocfunc_t (scriptelem_t const *, unsigned int) ;
+typedef inputprocfunc_t *inputprocfunc_t_ref ;
+
+typedef struct logdir_s logdir_t, *logdir_t_ref ;
+struct logdir_s
+{
+  bufalloc out ;
+  tain_t retrytto ;
+  tain_t deadline ;
+  uint64 maxdirsize ;
+  uint32 b ;
+  uint32 n ;
+  uint32 s ;
+  uint32 tolerance ;
+  unsigned int pid ;
+  char const *dir ;
+  char const *processor ;
+  int fd ;
+  int fdlock ;
+  rotstate_t rstate ;
+} ;
+
+#define LOGDIR_ZERO { \
+  .out = BUFALLOC_ZERO, \
+  .retrytto = TAIN_ZERO, \
+  .deadline = TAIN_ZERO, \
+  .maxdirsize = 0, \
+  .b = 0, \
+  .n = 0, \
+  .s = 0, \
+  .tolerance = 0, \
+  .pid = 0, \
+  .dir = 0, \
+  .processor = 0, \
+  .fd = -1, \
+  .fdlock = -1, \
+  .rstate = ROTSTATE_WRITABLE \
+}
+
+ /* If freeing a logdir before exiting is ever needed:
+static void logdir_free (logdir_t_ref ldp)
+{
+  bufalloc_free(&ldp->out) ;
+  fd_close(ldp->fd) ; ldp->fd = -1 ;
+  fd_close(ldp->fdlock) ; ldp->fdlock = -1 ;
+}
+ */
+
+/* End datatypes. All of this was just to optimize the script interpretation. :-) */
+
+static genalloc logdirs = GENALLOC_ZERO ; /* array of logdir_t */
+
+typedef struct filesize_s filesize_t, *filesize_t_ref ;
+struct filesize_s
+{
+  uint64 size ;
+  char name[28] ;
+} ;
+
+static int filesize_cmp (filesize_t const *a, filesize_t const *b)
+{
+  return byte_diff(a->name+1, 26, b->name+1) ;
+}
+
+static int name_is_relevant (char const *name)
+{
+  if (name[0] != '@') return 0 ;
+  if (str_len(name) != 27) return 0 ;
+  {
+    char tmp[12] ;
+    if (!ucharn_scan(name+1, tmp, 12)) return 0 ;
+  }
+  if (name[25] != '.') return 0 ;
+  if ((name[26] != 's') && (name[26] != 'u')) return 0 ;
+  return 1 ;
+}
+
+static inline int logdir_trim (logdir_t_ref ldp)
+{
+  unsigned int n = 0 ;
+  DIR *dir = opendir(ldp->dir) ;
+  if (!dir) return -1 ;
+  for (;;)
+  {
+    direntry *d ;
+    errno = 0 ;
+    d = readdir(dir) ;
+    if (!d) break ;
+    if (name_is_relevant(d->d_name)) n++ ;
+  }
+  if (errno)
+  {
+    register int e = errno ;
+    dir_close(dir) ;
+    errno = e ;
+    return -1 ;
+  }
+  rewinddir(dir) ;
+  {
+    filesize_t blurgh[n] ;
+    uint64 totalsize = 0 ;
+    unsigned int dirlen = str_len(ldp->dir) ;
+    unsigned int i = 0 ;
+    char fullname[dirlen + 29] ;
+    byte_copy(fullname, dirlen, ldp->dir) ;
+    fullname[dirlen] = '/' ;
+    for (;;)
+    {
+      struct stat st ;
+      direntry *d ;
+      errno = 0 ;
+      d = readdir(dir) ;
+      if (!d) break ;
+      if (!name_is_relevant(d->d_name)) continue ;
+      if (i >= n) { errno = EBUSY ; break ; }
+      byte_copy(fullname + dirlen + 1, 28, d->d_name) ;
+      if (stat(fullname, &st) < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("stat ", fullname) ;
+        continue ;
+      }
+      byte_copy(blurgh[i].name, 28, d->d_name) ;
+      blurgh[i].size = st.st_size ;
+      totalsize += st.st_size ;
+      i++ ;
+    }
+    if (errno)
+    {
+      register int e = errno ;
+      dir_close(dir) ;
+      errno = e ;
+      return -1 ;
+    }
+    dir_close(dir) ;
+    if ((i <= ldp->n) && (!ldp->maxdirsize || (totalsize <= ldp->maxdirsize)))
+      return 0 ;
+    qsort(blurgh, i, sizeof(filesize_t), (qcmpfunc_t_ref)&filesize_cmp) ;
+    n = 0 ;
+    while ((i > ldp->n + n) || (ldp->maxdirsize && (totalsize > ldp->maxdirsize)))
+    {
+      byte_copy(fullname + dirlen + 1, 28, blurgh[n].name) ;
+      if (unlink(fullname) < 0)
+      {
+        if (errno == ENOENT) totalsize -= blurgh[n].size ;
+        if (verbosity) strerr_warnwu2sys("unlink ", fullname) ;
+      }
+      else totalsize -= blurgh[n].size ;
+      n++ ;
+    }
+  }
+  return n ;
+}
+
+static int finish (logdir_t *ldp, char const *name, char suffix)
+{
+  struct stat st ;
+  unsigned int dirlen = str_len(ldp->dir) ;
+  unsigned int namelen = str_len(name) ;
+  char x[dirlen + namelen + 2] ;
+  byte_copy(x, dirlen, ldp->dir) ;
+  x[dirlen] = '/' ;
+  byte_copy(x + dirlen + 1, namelen + 1, name) ;
+  if (stat(x, &st) < 0) return (errno == ENOENT) ;
+  if (st.st_nlink == 1)
+  {
+    char y[dirlen + 29] ;
+    byte_copy(y, dirlen, ldp->dir) ;
+    y[dirlen] = '/' ;
+    timestamp_g(y + dirlen + 1) ;
+    y[dirlen + 26] = '.' ;
+    y[dirlen + 27] = suffix ;
+    y[dirlen + 28] = 0 ;
+    if (link(x, y) < 0) return 0 ;
+  }
+  if (unlink(x) < 0) return 0 ;
+  return logdir_trim(ldp) ;
+}
+
+static inline void exec_processor (logdir_t_ref ldp)
+{
+  char const *cargv[4] = { "execlineb", "-Pc", ldp->processor, 0 } ;
+  unsigned int dirlen = str_len(ldp->dir) ;
+  int fd ;
+  char x[dirlen + 10] ;
+  PROG = "s6-log (processor child)" ;
+  byte_copy(x, dirlen, ldp->dir) ;
+  byte_copy(x + dirlen, 10, "/previous") ;
+  fd = open_readb(x) ;
+  if (fd < 0) strerr_diefu2sys(111, "open_readb ", x) ;
+  if (fd_move(0, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ;
+  byte_copy(x + dirlen + 1, 10, "processed") ;
+  fd = open_trunc(x) ;
+  if (fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ;
+  if (fd_move(1, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ;
+  byte_copy(x + dirlen + 1, 6, "state") ;
+  fd = open_readb(x) ;
+  if (fd < 0) strerr_diefu2sys(111, "open_readb ", x) ;
+  if (fd_move(4, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ;
+  byte_copy(x + dirlen + 1, 9, "newstate") ;
+  fd = open_trunc(x) ;
+  if (fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ;
+  if (fd_move(5, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ;
+  selfpipe_finish() ;
+  sig_restore(SIGPIPE) ;
+  pathexec_run(cargv[0], cargv, (char const *const *)environ) ;
+  strerr_dieexec(111, cargv[0]) ;
+}
+
+static int rotator (logdir_t_ref ldp)
+{
+  unsigned int dirlen = str_len(ldp->dir) ;
+  switch (ldp->rstate)
+  {
+    case ROTSTATE_START :
+    {
+      if (fd_sync(ldp->fd) < 0)
+      {
+        if (verbosity) strerr_warnwu3sys("fd_sync ", ldp->dir, "/current") ;
+        goto fail ;
+      }
+      tain_now_g() ;
+      ldp->rstate = ROTSTATE_RENAME ;
+    }
+    case ROTSTATE_RENAME :
+    {
+      char current[dirlen + 9] ;
+      char previous[dirlen + 10] ;
+      byte_copy(current, dirlen, ldp->dir) ;
+      byte_copy(current + dirlen, 9, "/current") ;
+      byte_copy(previous, dirlen, ldp->dir) ;
+      byte_copy(previous + dirlen, 10, "/previous") ;
+      if (rename(current, previous) < 0)
+      {
+        if (verbosity) strerr_warnwu4sys("rename ", current, " to ", previous) ;
+        goto fail ;
+      }
+      ldp->rstate = ROTSTATE_NEWCURRENT ;
+    }
+    case ROTSTATE_NEWCURRENT :
+    {
+      int fd ;
+      char x[dirlen + 9] ;
+      byte_copy(x, dirlen, ldp->dir) ;
+      byte_copy(x + dirlen, 9, "/current") ;
+      fd = open_append(x) ;
+      if (fd < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("open_append ", x) ;
+        goto fail ;
+      }
+      if (coe(fd) < 0)
+      {
+        register int e = errno ;
+        fd_close(fd) ;
+        errno = e ;
+        if (verbosity) strerr_warnwu2sys("coe ", x) ;
+        goto fail ;
+      }
+      if (fd_chmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
+      {
+        register int e = errno ;
+        fd_close(fd) ;
+        errno = e ;
+        if (verbosity) strerr_warnwu3sys("fchmod ", x, " to 0644") ;
+        goto fail ;
+      }
+      fd_close(ldp->fd) ;
+      ldp->fd = fd ;
+      ldp->b = 0 ;
+      ldp->rstate = ROTSTATE_CHMODPREVIOUS ;
+    }
+    case ROTSTATE_CHMODPREVIOUS :
+    {
+      char x[dirlen + 10] ;
+      byte_copy(x, dirlen, ldp->dir) ;
+      byte_copy(x + dirlen, 10, "/previous") ;
+      if (chmod(x, S_IRWXU | S_IRGRP | S_IROTH) < 0)
+      {
+        if (verbosity) strerr_warnwu3sys("chmod ", x, " to 0744") ;
+        goto fail ;
+      }
+      if (ldp->processor) goto runprocessor ;
+      ldp->rstate = ROTSTATE_FINISHPREVIOUS ;
+    }
+    case ROTSTATE_FINISHPREVIOUS :
+    {
+      if (finish(ldp, "previous", 's') < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("finish previous .s to logdir ", ldp->dir) ;
+        goto fail ;
+      }
+      tain_copynow(&ldp->deadline) ;
+      ldp->rstate = ROTSTATE_WRITABLE ;
+      break ;
+    }
+   runprocessor :
+      ldp->rstate = ROTSTATE_RUNPROCESSOR ;
+    case ROTSTATE_RUNPROCESSOR :
+    {
+      int pid = fork() ;
+      if (pid < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("fork processor for logdir ", ldp->dir) ;
+        goto fail ;
+      }
+      else if (!pid) exec_processor(ldp) ;
+      ldp->pid = (unsigned int)pid ;
+      tain_add_g(&ldp->deadline, &tain_infinite_relative) ;
+      ldp->rstate = ROTSTATE_WAITPROCESSOR ;
+    }
+    case ROTSTATE_WAITPROCESSOR :
+    {
+      return (errno = EAGAIN, 0) ;
+    }
+    case ROTSTATE_SYNCPROCESSED :
+    {
+      int fd ;
+      char x[dirlen + 11] ;
+      byte_copy(x, dirlen, ldp->dir) ;
+      byte_copy(x + dirlen, 11, "/processed") ;
+      fd = open_append(x) ;
+      if (fd < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("open_append ", x) ;
+        goto fail ;
+      }
+      if (fd_sync(fd) < 0)
+      {
+        register int e = errno ;
+        fd_close(fd) ;
+        errno = e ;
+        if (verbosity) strerr_warnwu2sys("fd_sync ", x) ;
+        goto fail ;
+      }
+      tain_now_g() ;
+      if (fd_chmod(fd, S_IRWXU | S_IRGRP | S_IROTH) < 0)
+      {
+        register int e = errno ;
+        fd_close(fd) ;
+        errno = e ;
+        if (verbosity) strerr_warnwu3sys("fd_chmod ", x, " to 0744") ;
+        goto fail ;
+      }
+      fd_close(fd) ;
+      ldp->rstate = ROTSTATE_SYNCNEWSTATE ;
+    }
+    case ROTSTATE_SYNCNEWSTATE :
+    {
+      int fd ;
+      char x[dirlen + 10] ;
+      byte_copy(x, dirlen, ldp->dir) ;
+      byte_copy(x + dirlen, 10, "/newstate") ;
+      fd = open_append(x) ;
+      if (ldp->fd < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("open_append ", x) ;
+        goto fail ;
+      }
+      if (fd_sync(fd) < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("fd_sync ", x) ;
+        goto fail ;
+      }
+      tain_now_g() ;
+      fd_close(fd) ;
+      ldp->rstate = ROTSTATE_UNLINKPREVIOUS ;
+    }
+    case ROTSTATE_UNLINKPREVIOUS :
+    {
+      char x[dirlen + 10] ;
+      byte_copy(x, dirlen, ldp->dir) ;
+      byte_copy(x + dirlen, 10, "/previous") ;
+      if ((unlink(x) < 0) && (errno != ENOENT))
+      {
+        if (verbosity) strerr_warnwu2sys("open_append ", x) ;
+        goto fail ;
+      }
+      ldp->rstate = ROTSTATE_RENAMESTATE ;
+    }
+    case ROTSTATE_RENAMESTATE :
+    {
+      char newstate[dirlen + 10] ;
+      char state[dirlen + 7] ;
+      byte_copy(newstate, dirlen, ldp->dir) ;
+      byte_copy(state, dirlen, ldp->dir) ;
+      byte_copy(newstate + dirlen, 10, "/newstate") ;
+      byte_copy(state + dirlen, 7, "/state") ;
+      if (rename(newstate, state) < 0)
+      {
+        if (verbosity) strerr_warnwu4sys("rename ", newstate, " to ", state) ;
+        goto fail ;
+      }
+      ldp->rstate = ROTSTATE_FINISHPROCESSED ;
+    }
+    case ROTSTATE_FINISHPROCESSED :
+    {
+      if (finish(ldp, "processed", 's') < 0)
+      {
+        if (verbosity) strerr_warnwu2sys("finish processed .s to logdir ", ldp->dir) ;
+        goto fail ;
+      }
+      tain_copynow(&ldp->deadline) ;
+      ldp->rstate = ROTSTATE_WRITABLE ;
+      break ;
+    }
+    default : strerr_dief1x(101, "inconsistent state in rotator()") ;
+  }
+  return 1 ;
+ fail:
+   tain_add_g(&ldp->deadline, &ldp->retrytto) ;
+   return 0 ;
+}
+
+static int logdir_write (int i, char const *s, unsigned int len)
+{
+  logdir_t_ref ldp = genalloc_s(logdir_t, &logdirs) + (unsigned int)i ;
+  int r ;
+  unsigned int n = len ;
+  {
+    unsigned int m = byte_rchr(s, n, '\n') ;
+    if (m < n) n = m+1 ;
+  }
+  r = fd_write(ldp->fd, s, n) ;
+  if (r < 0)
+  {
+    if (!error_isagain(errno))
+    {
+      tain_add_g(&ldp->deadline, &ldp->retrytto) ;
+      if (verbosity) strerr_warnwu3sys("write to ", ldp->dir, "/current") ;
+    }
+    return r ;
+  }
+  ldp->b += r ;
+  if ((ldp->b + ldp->tolerance >= ldp->s) && (s[r-1] == '\n'))
+  {
+    ldp->rstate = ROTSTATE_START ;
+    rotator(ldp) ;
+  }
+  return r ;
+}
+
+static inline void rotate_or_flush (logdir_t *ldp)
+{
+  if ((ldp->rstate != ROTSTATE_WRITABLE) && !rotator(ldp)) return ;
+  if (ldp->b >= ldp->s)
+  {
+    ldp->rstate = ROTSTATE_START ;
+    if (!rotator(ldp)) return ;
+  }
+  bufalloc_flush(&ldp->out) ;
+}
+
+static inline void logdir_init (logdir_t *ap, uint32 s, uint32 n, uint32 tolerance, uint64 maxdirsize, tain_t const *retrytto, char const *processor, char const *name, unsigned int index)
+{
+  struct stat st ;
+  unsigned int dirlen = str_len(name) ;
+  int r ;
+  char x[dirlen + 11] ;
+  ap->s = s ;
+  ap->n = n ;
+  ap->pid = 0 ;
+  ap->tolerance = tolerance ;
+  ap->maxdirsize = maxdirsize ;
+  ap->retrytto = *retrytto ;
+  ap->processor = processor ;
+  ap->dir = name ;
+  ap->fd = -1 ;
+  ap->rstate = ROTSTATE_WRITABLE ;
+  r = mkdir(ap->dir, S_IRWXU | S_ISGID) ;
+  if ((r < 0) && (errno != EEXIST)) strerr_diefu2sys(111, "mkdir ", name) ;
+  byte_copy(x, dirlen, name) ;
+  byte_copy(x + dirlen, 6, "/lock") ;
+  ap->fdlock = open_append(x) ;
+  if ((ap->fdlock) < 0) strerr_diefu2sys(111, "open_append ", x) ;
+  if (lock_exnb(ap->fdlock) < 0) strerr_diefu2sys(111, "lock_exnb ", x) ;
+  if (coe(ap->fdlock) < 0) strerr_diefu2sys(111, "coe ", x) ;
+  byte_copy(x + dirlen + 1, 8, "current") ;
+  if (stat(x, &st) < 0)
+  {
+    if (errno != ENOENT) strerr_diefu2sys(111, "stat ", x) ;
+  }
+  else if (st.st_mode & S_IXUSR) goto opencurrent ;
+  byte_copy(x + dirlen + 1, 6, "state") ;
+  unlink(x) ;
+  byte_copy(x + dirlen + 1, 9, "newstate") ;
+  unlink(x) ;
+  {
+    int flagprocessed = 0 ;
+    byte_copy(x + dirlen + 1, 10, "processed") ;
+    if (stat(x, &st) < 0)
+    {
+      if (errno != ENOENT) strerr_diefu2sys(111, "stat ", x) ;
+    }
+    else if (st.st_mode & S_IXUSR) flagprocessed = 1 ;
+    if (flagprocessed)
+    {
+      byte_copy(x + dirlen + 1, 9, "previous") ;
+      unlink(x) ;
+      if (finish(ap, "processed", 's') < 0)
+        strerr_diefu2sys(111, "finish processed .s for logdir ", ap->dir) ;
+    }
+    else
+    {
+      unlink(x) ;
+      if (finish(ap, "previous", 'u') < 0)
+        strerr_diefu2sys(111, "finish previous .u for logdir ", ap->dir) ;
+    }
+  }
+  if (finish(ap, "current", 'u') < 0)
+    strerr_diefu2sys(111, "finish current .u for logdir ", ap->dir) ;
+  byte_copy(x + dirlen + 1, 6, "state") ;
+  ap->fd = open_trunc(x) ;
+  if (ap->fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ;
+  fd_close(ap->fd) ;
+  st.st_size = 0 ;
+  byte_copy(x + dirlen + 1, 8, "current") ;
+ opencurrent:
+  ap->fd = open_append(x) ;
+  if (ap->fd < 0) strerr_diefu2sys(111, "open_append ", x) ;
+  if (fd_chmod(ap->fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1)
+    strerr_diefu2sys(111, "fd_chmod ", x) ;
+  if (coe(ap->fd) < 0) strerr_diefu2sys(111, "coe ", x) ;
+  ap->b = st.st_size ;
+  tain_copynow(&ap->deadline) ;
+  bufalloc_init(&ap->out, &logdir_write, (int)index) ;
+}
+
+
+ /* Script */
+ 
+static int script_update (genalloc *sc, genalloc *sa, genalloc *aa)
+{
+  scriptelem_t foo ;
+  genalloc_shrink(sel_t, sa) ;
+  genalloc_shrink(act_t, aa) ;
+  foo.selections = *sa ;
+  foo.actions = *aa ;
+  if (!genalloc_append(scriptelem_t, sc, &foo)) return 0 ;
+  *sa = genalloc_zero ;
+  *aa = genalloc_zero ;
+  return 1 ;
+}
+
+static inline int script_init (genalloc *sc, char const *const *argv)
+{
+  tain_t cur_retrytto ;
+  unsigned int cur_fd2_size = 200 ;
+  unsigned int cur_status_size = 1001 ;
+  uint32 cur_s = 99999 ;
+  uint32 cur_n = 10 ;
+  uint32 cur_tolerance = 2000 ;
+  uint64 cur_maxdirsize = 0 ;
+  genalloc cur_selections = GENALLOC_ZERO ; /* sel_t */
+  genalloc cur_actions = GENALLOC_ZERO ; /* act_t */
+  char const *cur_processor = 0 ;
+  int flagacted = 0 ;
+  tain_uint(&cur_retrytto, 2) ;
+  
+  for (; *argv ; argv++)
+  {
+    switch (**argv)
+    {
+      case 'f' :
+      {
+        sel_t selitem ;
+        if (flagacted)
+        {
+          if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ;
+          flagacted = 0 ;
+        }
+        selitem.type = SELTYPE_DEFAULT ;
+        if (!genalloc_append(sel_t, &cur_selections, &selitem)) return 0 ;
+        break ;
+      }
+      case '+' :
+      case '-' :
+      {
+        sel_t selitem ;
+        int r ;
+        if (flagacted)
+        {
+          if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ;
+          flagacted = 0 ;
+        }
+        selitem.type = (**argv == '+') ? SELTYPE_PLUS : SELTYPE_MINUS ;
+        r = regcomp(&selitem.re, *argv+1, REG_EXTENDED | REG_NOSUB | REG_NEWLINE) ;
+        if (r == REG_ESPACE) return (errno = ENOMEM, 0) ;
+        if (r) goto fail ;
+        if (!genalloc_append(sel_t, &cur_selections, &selitem)) return 0 ;
+        break ;
+      }
+      case 'n' :
+      {
+        if (!uint320_scan(*argv + 1, &cur_n)) goto fail ;
+        break ;
+      }
+      case 's' :
+      {
+        if (!uint320_scan(*argv + 1, &cur_s)) goto fail ;
+        if (cur_s < 4096) cur_s = 4096 ;
+        if (cur_s > 16777215) cur_s = 16777215 ;
+        break ;
+      }
+      case 'S' :
+      {
+        if (!uint640_scan(*argv + 1, &cur_maxdirsize)) goto fail ;
+        break ;
+      }
+      case 'l' :
+      {
+        if (!uint320_scan(*argv + 1, &cur_tolerance)) goto fail ;
+        if (cur_tolerance > (cur_s >> 1))
+          strerr_dief3x(100, "directive ", *argv, " conflicts with previous s directive") ;
+        break ;
+      }
+      case 'r' :
+      {
+        uint32 t ;
+        if (!uint320_scan(*argv + 1, &t)) goto fail ;
+        if (!tain_from_millisecs(&cur_retrytto, (int)t)) return (errno = EINVAL, 0) ;
+        break ;
+      }
+      case 'E' :
+      {
+        if (!uint0_scan(*argv + 1, &cur_fd2_size)) goto fail ;
+        break ;
+      }
+      case '^' :
+      {
+        if (!uint0_scan(*argv + 1, &cur_status_size)) goto fail ;
+        break ;
+      }
+      case '!' :
+      {
+        cur_processor = (*argv)[1] ? *argv + 1 : 0 ;
+        break ;
+      }
+      case 'e' :
+      {
+        act_t a ;
+        flagacted = 1 ;
+        a.type = ACTTYPE_FD2 ;
+        a.data.fd2.size = cur_fd2_size ;
+        if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ;
+        break ;
+      }
+      case '=' :
+      {
+        act_t a ;
+        flagacted = 1 ;
+        a.type = ACTTYPE_STATUS ;
+        a.data.status.file = *argv + 1 ;
+        a.data.status.content = stralloc_zero ;
+        if (cur_status_size && !stralloc_ready_tuned(&a.data.status.content, cur_status_size, 0, 0, 1)) return 0 ;
+        a.data.status.content.len = cur_status_size ;
+        if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ;
+        break ;
+      }
+      case '.' : 
+      case '/' :
+      {
+        act_t a ;
+        logdir_t ld = LOGDIR_ZERO ;
+        flagacted = 1 ;
+        a.type = ACTTYPE_DIR ;
+        a.data.dir.lindex = genalloc_len(logdir_t, &logdirs) ;
+        if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ;
+        logdir_init(&ld, cur_s, cur_n, cur_tolerance, cur_maxdirsize, &cur_retrytto, cur_processor, *argv, genalloc_len(logdir_t, &logdirs)) ;
+        if (!genalloc_append(logdir_t, &logdirs, &ld)) return 0 ;
+        break ;
+      }
+      default : goto fail ;
+    }
+  }
+  if (flagacted)
+  {
+    if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ;
+  }
+  else
+  {
+    genalloc_deepfree(sel_t, &cur_selections, &sel_free) ;
+    if (verbosity) strerr_warnw1x("ignoring extraneous non-action directives") ;
+  }
+  genalloc_shrink(logdir_t, &logdirs) ;
+  genalloc_shrink(scriptelem_t, sc) ;
+  if (!genalloc_len(scriptelem_t, sc))
+    strerr_dief1x(100, "no action directive specified") ;
+  return 1 ;
+ fail:
+  strerr_dief2x(100, "unrecognized directive: ", *argv) ;
+}
+
+static inline void doit_fd2 (as_fd2_t const *ap, char const *s, unsigned int len)
+{
+  if (flagstampalert)
+  {
+    char fmt[TIMESTAMP+1] ;
+    tain_now_g() ;
+    timestamp_g(fmt) ;
+    fmt[TIMESTAMP] = ' ' ;
+    buffer_put(buffer_2, fmt, TIMESTAMP+1) ;
+  }
+  buffer_puts(buffer_2, PROG) ;
+  buffer_puts(buffer_2, ": alert: ") ;
+  if (ap->size && len > ap->size) len = ap->size ;
+  buffer_put(buffer_2, s, len) ;
+  if (len == ap->size) buffer_puts(buffer_2, "...") ;
+  buffer_putflush(buffer_2, "\n", 1) ;
+}
+
+static inline void doit_status (as_status_t const *ap, char const *s, unsigned int len)
+{
+  if (ap->content.len)
+  {
+    register unsigned int i ;
+    if (len > ap->content.len) len = ap->content.len ;
+    byte_copy(ap->content.s, len, s) ;
+    for (i = len ; i < ap->content.len ; i++) ap->content.s[i] = '\n' ;
+    if (!openwritenclose_suffix_sync(ap->file, ap->content.s, ap->content.len, ".new"))
+      strerr_warnwu2sys("openwritenclose ", ap->file) ;
+  }
+  else if (!openwritenclose_suffix_sync(ap->file, s, len, ".new"))
+    strerr_warnwu2sys("openwritenclose ", ap->file) ;
+}
+
+static inline void doit_dir (as_dir_t const *ap, char const *s, unsigned int len)
+{
+  logdir_t_ref ldp = genalloc_s(logdir_t, &logdirs) + ap->lindex ;
+  if (!bufalloc_put(&ldp->out, s, len) || !bufalloc_put(&ldp->out, "\n", 1))
+    strerr_diefu1sys(111, "bufalloc_put") ;
+}
+
+
+ /* The script interpreter. */
+
+static inline void doit (scriptelem_t const *se, unsigned int n, char const *s, unsigned int len)
+{
+  int flagselected = 1 ;
+  int flagacted = 0 ;
+  unsigned int i = 0 ;
+  for (; i < n ; i++)
+  {
+    unsigned int sellen = genalloc_len(sel_t, &se[i].selections) ;
+    sel_t *sels = genalloc_s(sel_t, &se[i].selections) ;
+    unsigned int j = 0 ;
+    for (; j < sellen ; j++)
+    {
+      switch (sels[j].type)
+      {
+        case SELTYPE_DEFAULT :
+          flagselected = !flagacted ;
+          break ;
+        case SELTYPE_PLUS :
+	  if (!flagselected && !regexec(&sels[j].re, flagstamp ? s+TIMESTAMP+1 : s, 0, 0, 0)) flagselected = 1 ;
+          break ;
+        case SELTYPE_MINUS :
+	  if (flagselected && !regexec(&sels[j].re, flagstamp ? s+TIMESTAMP+1 : s, 0, 0, 0)) flagselected = 0 ;
+          break ;
+        default :
+          strerr_dief2x(101, "internal consistency error in ", "selection type") ;
+      }
+    }
+    if (flagselected)
+    {
+      unsigned int actlen = genalloc_len(act_t, &se[i].actions) ;
+      act_t *acts = genalloc_s(act_t, &se[i].actions) ;
+      flagacted = 1 ;
+      for (j = 0 ; j < actlen ; j++)
+      {
+        switch (acts[j].type)
+        {
+          case ACTTYPE_FD2 :
+            doit_fd2(&acts[j].data.fd2, s, len) ;
+            break ;
+          case ACTTYPE_STATUS :
+            doit_status(&acts[j].data.status, s, len) ;
+            break ;
+          case ACTTYPE_DIR :
+            doit_dir(&acts[j].data.dir, s, len) ;
+            break ;
+          default :
+            strerr_dief2x(101, "internal consistency error in ", "action type") ;
+        }
+      }
+    }
+  }
+  if (flagstamp) tain_now_g() ;
+}
+
+static inline void processor_died (logdir_t_ref ldp, int wstat)
+{
+  ldp->pid = 0 ;
+  if (WIFSIGNALED(wstat))
+  {
+    if (verbosity) strerr_warnw2x("processor crashed in ", ldp->dir) ;
+    tain_add_g(&ldp->deadline, &ldp->retrytto) ;
+    ldp->rstate = ROTSTATE_RUNPROCESSOR ;
+  }
+  else if (WEXITSTATUS(wstat))
+  {
+    if (verbosity) strerr_warnw2x("processor failed in ", ldp->dir) ;
+    tain_add_g(&ldp->deadline, &ldp->retrytto) ;
+    ldp->rstate = ROTSTATE_RUNPROCESSOR ;
+  }
+  else
+  {
+    ldp->rstate = ROTSTATE_SYNCPROCESSED ;
+    rotator(ldp) ;
+  }
+}
+
+static void prepare_to_exit (void)
+{
+  fd_close(0) ;
+  flagexiting = 1 ;
+}
+
+static void stampanddoit (scriptelem_t const *se, unsigned int n)
+{
+  if (flagstamp) indata.s[timestamp_g(indata.s)] = ' ' ;
+  indata.s[indata.len] = 0 ;
+  doit(se, n, indata.s, indata.len-1) ;
+  indata.len = flagstamp ? TIMESTAMP+1 : 0 ;
+}
+
+static void normal_stdin (scriptelem_t const *se, unsigned int selen)
+{
+  int r = sanitize_read(buffer_fill(buffer_0)) ;
+  if (r < 0)
+  {
+    if ((errno != EPIPE) && verbosity) strerr_warnwu1sys("read from stdin") ;
+    prepare_to_exit() ;
+  }
+  else if (r)
+    while (skagetln_nofill(buffer_0, &indata, '\n') > 0)
+      stampanddoit(se, selen) ;
+}
+
+static void last_stdin (scriptelem_t const *se, unsigned int selen)
+{
+  int cont = 1 ;
+  while (cont)
+  {
+    char c ;
+    switch (sanitize_read(fd_read(0, &c, 1)))
+    {
+      case 0 :
+        cont = 0 ;
+        break ;
+      case -1 :
+        if ((errno != EPIPE) && verbosity) strerr_warnwu1sys("read from stdin") ;
+        if (indata.len <= (flagstamp ? TIMESTAMP+1 : 0))
+        {
+          prepare_to_exit() ;
+          cont = 0 ;
+          break ;
+        }
+        c = '\n' ;
+      case 1 :
+        if (!stralloc_catb(&indata, &c, 1)) dienomem() ;
+        if (c == '\n')
+        {
+          stampanddoit(se, selen) ;
+          prepare_to_exit() ;
+          cont = 0 ;
+        }
+        break ;
+    }
+  }
+}
+
+static inputprocfunc_t_ref handle_stdin = &normal_stdin ;
+
+static inline void handle_signals (void)
+{
+  for (;;)
+  {
+    switch (selfpipe_read())
+    {
+      case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+      case 0 : return ;
+      case SIGALRM :
+      {
+        unsigned int llen = genalloc_len(logdir_t, &logdirs) ;
+        logdir_t *ls = genalloc_s(logdir_t, &logdirs) ;
+        register unsigned int i = 0 ;
+        for (i = 0 ; i < llen ; i++)
+          if ((ls[i].rstate == ROTSTATE_WRITABLE) && ls[i].b)
+          {
+            ls[i].rstate = ROTSTATE_START ;
+            rotator(ls + i) ;
+          }
+        break ;
+      }
+      case SIGTERM :
+      {
+        if (flagprotect) break ;
+        handle_stdin = &last_stdin ;
+        if (indata.len <= (flagstamp ? TIMESTAMP+1 : 0)) prepare_to_exit() ;
+        break ;
+      }
+      case SIGCHLD :
+      {
+        unsigned int llen = genalloc_len(logdir_t, &logdirs) ;
+        logdir_t *ls = genalloc_s(logdir_t, &logdirs) ;
+        for (;;)
+        {
+          int wstat ;
+          register unsigned int i = 0 ;
+          register int r = wait_nohang(&wstat) ;
+          if (r <= 0) break ;
+          for (; i < llen ; i++) if ((unsigned int)r == ls[i].pid) break ;
+          if (i < llen) processor_died(ls + i, wstat) ;
+        }
+        break ;
+      }
+      default : strerr_dief1x(101, "internal consistency error with signal handling") ;
+    }
+  }
+}
+
+static inline int logdir_finalize (logdir_t_ref ldp)
+{
+  switch (ldp->rstate)
+  {
+    case ROTSTATE_WRITABLE :
+    {
+      if (fd_sync(ldp->fd) < 0)
+      {
+        if (verbosity) strerr_warnwu3sys("fd_sync ", ldp->dir, "/current") ;
+        goto fail ;
+      }
+      tain_now_g() ;
+      ldp->rstate = ROTSTATE_ENDFCHMOD ;
+    }
+    case ROTSTATE_ENDFCHMOD :
+    {
+      if (fd_chmod(ldp->fd, S_IRWXU | S_IRGRP | S_IROTH) < 0)
+      {
+        if (verbosity) strerr_warnwu3sys("fd_chmod ", ldp->dir, "/current to 0744") ;
+        goto fail ;
+      }
+      ldp->rstate = ROTSTATE_END ;
+      break ;
+    }
+    default : strerr_dief1x(101, "inconsistent state in logdir_finalize()") ;
+  }
+  return 1 ;
+ fail:
+  tain_add_g(&ldp->deadline, &ldp->retrytto) ;
+  return 0 ;
+}
+
+static inline void finalize (void)
+{
+  unsigned int llen = genalloc_len(logdir_t, &logdirs) ;
+  logdir_t *ls = genalloc_s(logdir_t, &logdirs) ;
+  unsigned int n = llen ;
+  for (;;)
+  {
+    unsigned int i = 0 ;
+    tain_t deadline ;
+    tain_addsec_g(&deadline, 2) ;
+    for (; i < llen ; i++)
+      if (ls[i].rstate != ROTSTATE_END)
+      {
+        if (logdir_finalize(ls + i)) n-- ;
+        else if (tain_less(&ls[i].deadline, &deadline))
+          deadline = ls[i].deadline ;
+      }
+    if (!n) break ;
+    {
+      iopause_fd x ;
+      iopause_g(&x, 0, &deadline) ;
+    }
+  }
+}
+
+int main (int argc, char const *const *argv)
+{
+  genalloc logscript = GENALLOC_ZERO ; /* array of scriptelem_t */
+  int flagblock = 0 ;
+  PROG = "s6-log" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "qvbpte", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'q' : if (verbosity) verbosity-- ; break ;
+        case 'v' : verbosity++ ; break ;
+        case 'b' : flagblock = 1 ; break ;
+        case 'p' : flagprotect = 1 ; break ;
+        case 't' : flagstamp = 1 ; break ;
+        case 'e' : flagstampalert = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc < 1) strerr_dieusage(100, USAGE) ;
+
+  fd_close(1) ;
+  {
+    int r = tain_now_g() ;
+    if (flagstamp)
+    {
+      char fmt[TIMESTAMP+1] ;
+      if (!stralloc_catb(&indata, fmt, TIMESTAMP+1)) dienomem() ;
+      if (!r) strerr_warnwu1sys("read current time - timestamps may be wrong for a while") ;
+    }
+  }
+  if (!script_init(&logscript, argv)) strerr_diefu1sys(111, "initialize logging script") ;
+  if (ndelay_on(0) < 0) strerr_diefu1sys(111, "ndelay_on(0)") ;
+
+  {
+    unsigned int llen = genalloc_len(logdir_t, &logdirs) ;
+    logdir_t *ls = genalloc_s(logdir_t, &logdirs) ;
+    iopause_fd x[2 + llen] ;
+    unsigned int active[llen] ;
+    x[0].fd = 0 ;
+    x[1].fd = selfpipe_init() ;
+    if (x[1].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ;
+    if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore(SIGPIPE)") ;
+    {
+      sigset_t set ;
+      sigemptyset(&set) ;
+      sigaddset(&set, SIGTERM) ; sigaddset(&set, SIGALRM) ; sigaddset(&set, SIGCHLD) ;
+      if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "selfpipe_trapset") ;
+    }
+    x[1].events = IOPAUSE_READ ;
+
+    for (;;)
+    {
+      tain_t deadline ;
+      int r ;
+      unsigned int j = 0 ;
+      unsigned int i = 0 ;
+      int allflushed = 1 ;
+      tain_add_g(&deadline, &tain_infinite_relative) ;
+      for (; i < llen ; i++)
+      {
+        if (bufalloc_len(&ls[i].out) || (ls[i].rstate != ROTSTATE_WRITABLE))
+        {
+          allflushed = 0 ;
+          if (!tain_future(&ls[i].deadline))
+          {
+            x[2+j].fd = ls[i].fd ;
+            x[2+j].events = IOPAUSE_WRITE ;
+            active[j++] = i ;
+          }
+          else if (tain_less(&ls[i].deadline, &deadline))
+              deadline = ls[i].deadline ;
+        }
+      }
+      if (flagexiting && allflushed) break ;
+      x[0].events = (allflushed || !flagblock) ? IOPAUSE_READ : 0 ;
+      r = iopause_g(x + flagexiting, 2 - flagexiting + j, &deadline) ;
+      if (r < 0) strerr_diefu1sys(111, "iopause") ;
+      else if (r)
+      {
+        if (x[1].revents & IOPAUSE_READ) handle_signals() ;
+        else if (x[1].revents & IOPAUSE_EXCEPT) strerr_dief1sys(111, "trouble with selfpipe") ;
+        for (i = 0 ; i < j ; i++)
+          if (x[2+i].revents & IOPAUSE_WRITE)
+            rotate_or_flush(ls + active[i]) ;
+        if (!flagexiting)
+        {
+          if (x[0].revents & IOPAUSE_READ)
+            (*handle_stdin)(genalloc_s(scriptelem_t, &logscript), genalloc_len(scriptelem_t, &logscript)) ;
+          else if (x[0].revents & IOPAUSE_EXCEPT)
+          {
+            prepare_to_exit() ;
+            if (indata.len > (flagstamp ? TIMESTAMP+1 : 0))
+            {
+              if (!stralloc_0(&indata)) dienomem() ;
+              stampanddoit(genalloc_s(scriptelem_t, &logscript), genalloc_len(scriptelem_t, &logscript)) ;
+            }
+          }
+        }
+      }
+    }
+  }
+  genalloc_deepfree(scriptelem_t, &logscript, &scriptelem_free) ;
+  finalize() ;
+  return 0 ;
+}
diff --git a/src/daemontools-extras/s6-notifywhenup.c b/src/daemontools-extras/s6-notifywhenup.c
new file mode 100644
index 0000000..a4be329
--- /dev/null
+++ b/src/daemontools-extras/s6-notifywhenup.c
@@ -0,0 +1,86 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/uint.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <s6/ftrigw.h>
+
+#define USAGE "s6-notifywhenup [ -d fd ] [ -e fifodir ] [ -f ] [ -t timeout ] prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static int run_child (int fd, char const *fifodir, unsigned int timeout)
+{
+  char dummy[4096] ;
+  iopause_fd x = { .fd = fd, .events = IOPAUSE_READ } ;
+  tain_t deadline ;
+  int haswritten = 0 ;
+  register int r = 0 ;
+  if (!tain_now_g()) strerr_diefu1sys(111, "tain_now") ;
+  tain_from_millisecs(&deadline, timeout) ;
+  tain_add_g(&deadline, &deadline) ;
+  while (!r)
+  {
+    register int r = iopause_g(&x, 1, &deadline) ;
+    if (r < 0) strerr_diefu1sys(111, "iopause") ;
+    if (!r) return 99 ;
+    while (r > 0)
+    {
+      r = sanitize_read(fd_read(fd, dummy, 4096)) ;
+      if (r > 0) haswritten = 1 ;
+    }
+  }
+  if (errno != EPIPE) strerr_diefu1sys(111, "read from parent") ;
+  if (haswritten) ftrigw_notify(fifodir, 'U') ;
+  return 0 ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int fd = 1 ;
+  char const *fifodir = "event" ;
+  int df = 1 ;
+  unsigned int timeout = 0 ;
+  PROG = "s6-notifywhenup" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "d:e:ft:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'd' : if (!uint0_scan(l.arg, &fd)) dieusage() ; break ;
+        case 'e' : fifodir = l.arg ; break ;
+        case 'f' : df = 0 ; break ;
+        case 't' : if (!uint0_scan(l.arg, &timeout)) dieusage() ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) dieusage() ;
+
+  {
+    int p[2] ;
+    pid_t pid ;
+    if (pipe(p) < 0) strerr_diefu1sys(111, "pipe") ;
+    pid = df ? doublefork() : fork() ;
+    if (pid < 0) strerr_diefu1sys(111, df ? "doublefork" : "fork") ;
+    else if (pid)
+    {
+      PROG = "s6-notifywhenup (child)" ;
+      fd_close(p[1]) ;
+      return run_child(p[0], fifodir, timeout) ;
+    }
+    fd_close(p[0]) ;
+    if (fd_move((int)fd, p[1]) < 0) strerr_diefu1sys(111, "fd_move") ;
+  }
+  pathexec_run(argv[0], argv, envp) ;
+  strerr_dieexec(111, argv[1]) ;
+}
diff --git a/src/daemontools-extras/s6-setlock.c b/src/daemontools-extras/s6-setlock.c
new file mode 100644
index 0000000..2fb6f12
--- /dev/null
+++ b/src/daemontools-extras/s6-setlock.c
@@ -0,0 +1,87 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/uint.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <s6/config.h>
+
+#define USAGE "s6-setlock [ -r | -w ] [ -n | -N | -t timeout ] lockfile prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+typedef int lockfunc_t (int) ;
+typedef lockfunc_t *lockfunc_t_ref ;
+
+static lockfunc_t_ref f[2][2] = { { &lock_sh, &lock_shnb }, { &lock_ex, &lock_exnb } } ;
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int nb = 0, ex = 1 ;
+  unsigned int timeout = 0 ;
+  PROG = "s6-setlock" ;
+  for (;;)
+  {
+    register int opt = subgetopt(argc, argv, "nNrwt:") ;
+    if (opt == -1) break ;
+    switch (opt)
+    {
+      case 'n' : nb = 1 ; break ;
+      case 'N' : nb = 0 ; break ;
+      case 'r' : ex = 0 ; break ;
+      case 'w' : ex = 1 ; break ;
+      case 't' : if (!uint0_scan(subgetopt_here.arg, &timeout)) dieusage() ;
+                 nb = 2 ; break ;
+      default : dieusage() ;
+    }
+  }
+  argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  if (argc < 2) dieusage() ;
+
+  if (nb < 2)
+  {
+    int fd = open_create(argv[0]) ;
+    if (fd == -1) strerr_diefu2sys(111, "open_create ", argv[0]) ;
+    if ((*f[ex][nb])(fd) == -1) strerr_diefu2sys(1, "lock ", argv[0]) ;
+  }
+  else
+  {
+    char const *cargv[3] = { "s6lockd-helper", argv[0], 0 } ;
+    char const *cenvp[2] = { ex ? "S6LOCK_EX=1" : 0, 0 } ;
+    iopause_fd x = { .events = IOPAUSE_READ } ;
+    tain_t deadline ;
+    int p[2] ;
+    unsigned int pid ;
+    char c ;
+    if (!tain_now_g()) strerr_diefu1sys(111, "tain_now") ;
+    tain_from_millisecs(&deadline, timeout) ;
+    tain_add_g(&deadline, &deadline) ;
+    pid = child_spawn(S6_BINPREFIX "s6lockd-helper", cargv, cenvp, p, 2) ;
+    if (!pid) strerr_diefu2sys(111, "spawn ", S6_BINPREFIX "s6lockd-helper") ;
+    x.fd = p[0] ;
+    for (;;)
+    {
+      register int r = iopause_g(&x, 1, &deadline) ;
+      if (r < 0) strerr_diefu1sys(111, "iopause") ;
+      if (!r)
+      {
+        kill(pid, SIGTERM) ;
+        errno = ETIMEDOUT ;
+        strerr_diefu1sys(1, "acquire lock") ;
+      }
+      r = sanitize_read(fd_read(p[0], &c, 1)) ;
+      if (r < 0) strerr_diefu1sys(111, "read ack from helper") ;
+      if (r) break ;
+    }
+    if (c != '!') strerr_dief1x(111, "helper sent garbage ack") ;
+    fd_close(p[0]) ;
+    if (uncoe(p[1]) < 0) strerr_diefu1sys(111, "uncoe fd to helper") ;
+  }
+  pathexec_run(argv[1], argv+1, envp) ;
+  strerr_dieexec(111, argv[1]) ;
+}
diff --git a/src/daemontools-extras/s6-setsid.c b/src/daemontools-extras/s6-setsid.c
new file mode 100644
index 0000000..efd3832
--- /dev/null
+++ b/src/daemontools-extras/s6-setsid.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-setsid [ -i | -I ] prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int insist = 0 ;
+  PROG = "s6-setsid" ;
+  for (;;)
+  {
+    register int opt = subgetopt(argc, argv, "iI") ;
+    if (opt == -1) break ;
+    switch (opt)
+    {
+      case 'i' : insist = 1 ; break ;
+      case 'I' : insist = 0 ; break ;
+      default : strerr_dieusage(100, USAGE) ;
+    }
+  }
+  argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  if (!argc) strerr_dieusage(100, USAGE) ;
+
+  if (setsid() < 0)
+  {
+    if (insist) strerr_diefu1sys(111, "setsid") ;
+    else strerr_warnwu1sys("setsid") ;
+  }
+  pathexec_run(argv[0], argv, envp) ;
+  strerr_dieexec(111, argv[0]) ;
+}
diff --git a/src/daemontools-extras/s6-setuidgid.c b/src/daemontools-extras/s6-setuidgid.c
new file mode 100644
index 0000000..d2e7361
--- /dev/null
+++ b/src/daemontools-extras/s6-setuidgid.c
@@ -0,0 +1,30 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-setuidgid username prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int pos ;
+  PROG = "s6-setuidgid" ;
+  if (argc < 3) dieusage() ;
+  pos = str_chr(argv[1], ':') ;
+  if (argv[1][pos])
+  {
+    unsigned int uid = 0, gid = 0, len = uint_scan(argv[1], &uid) ;
+    if (len != pos) dieusage() ;
+    if (argv[1][pos+1] && !uint0_scan(argv[1]+pos+1, &gid)) dieusage() ;
+    if (gid && setgid(gid)) strerr_diefu1sys(111, "setgid") ;
+    if (uid && setuid(uid)) strerr_diefu1sys(111, "setuid") ;
+  }
+  else if (!prot_setuidgid(argv[1]))
+    strerr_diefu2sys(111, "change identity to ", argv[1]) ;
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/daemontools-extras/s6-softlimit.c b/src/daemontools-extras/s6-softlimit.c
new file mode 100644
index 0000000..61e0e4d
--- /dev/null
+++ b/src/daemontools-extras/s6-softlimit.c
@@ -0,0 +1,117 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint64.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-softlimit [ -a allbytes ] [ -c corebytes ] [ -d databytes ] [ -f filebytes ] [ -l lockbytes ] [ -m membytes ] [ -o openfiles ] [ -p processes ] [ -r residentbytes ] [ -s stackbytes ] [ -t cpusecs ] prog..."
+
+static void doit (int res, char const *arg)
+{
+  struct rlimit r ;
+  if (getrlimit(res, &r) < 0) strerr_diefu1sys(111, "getrlimit") ;
+  if ((arg[0] == '=') && !arg[1]) r.rlim_cur = r.rlim_max ;
+  else
+  {
+    uint64 n ;
+    if (!uint640_scan(arg, &n)) strerr_dieusage(100, USAGE) ;
+    if (n > (uint64)r.rlim_max) n = (uint64)r.rlim_max ;
+    r.rlim_cur = (rlim_t)n ;
+  }
+  if (setrlimit(res, &r) == -1) strerr_diefu1sys(111, "setrlimit") ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  PROG = "s6-softlimit" ;
+  for (;;)
+  {
+    register int opt = sgetopt(argc, argv, "a:c:d:f:l:m:o:p:r:s:t:") ;
+    if (opt == -1) break ;
+    switch (opt)
+    {
+      case 'a' :
+#ifdef RLIMIT_AS
+        doit(RLIMIT_AS, subgetopt_here.arg) ;
+#endif
+#ifdef RLIMIT_VMEM
+        doit(RLIMIT_VMEM, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'c' :
+#ifdef RLIMIT_CORE
+        doit(RLIMIT_CORE, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'd' :
+#ifdef RLIMIT_DATA
+        doit(RLIMIT_DATA, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'f' :
+#ifdef RLIMIT_FSIZE
+        doit(RLIMIT_FSIZE, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'l' :
+#ifdef RLIMIT_MEMLOCK
+        doit(RLIMIT_MEMLOCK, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'm' :
+#ifdef RLIMIT_DATA
+        doit(RLIMIT_DATA, subgetopt_here.arg) ;
+#endif
+#ifdef RLIMIT_STACK
+        doit(RLIMIT_STACK, subgetopt_here.arg) ;
+#endif
+#ifdef RLIMIT_MEMLOCK
+        doit(RLIMIT_MEMLOCK, subgetopt_here.arg) ;
+#endif
+#ifdef RLIMIT_VMEM
+        doit(RLIMIT_VMEM, subgetopt_here.arg) ;
+#endif
+#ifdef RLIMIT_AS
+        doit(RLIMIT_AS, subgetopt_here.arg) ;
+#endif
+	break ;
+      case 'o' :
+#ifdef RLIMIT_NOFILE
+        doit(RLIMIT_NOFILE, subgetopt_here.arg) ;
+#endif
+#ifdef RLIMIT_OFILE
+        doit(RLIMIT_OFILE, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'p' :
+#ifdef RLIMIT_NPROC
+        doit(RLIMIT_NPROC, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 'r' :
+#ifdef RLIMIT_RSS
+        doit(RLIMIT_RSS, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 's' :
+#ifdef RLIMIT_STACK
+        doit(RLIMIT_STACK, subgetopt_here.arg) ;
+#endif
+        break ;
+      case 't' :
+#ifdef RLIMIT_CPU
+        doit(RLIMIT_CPU, subgetopt_here.arg) ;
+#endif
+        break ;
+    }
+  }
+  argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  pathexec_run(argv[0], argv, envp) ;
+  strerr_dieexec(111, argv[0]) ;
+}
diff --git a/src/daemontools-extras/s6-tai64n.c b/src/daemontools-extras/s6-tai64n.c
new file mode 100644
index 0000000..085c053
--- /dev/null
+++ b/src/daemontools-extras/s6-tai64n.c
@@ -0,0 +1,35 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+int main (void)
+{
+  char stamp[TIMESTAMP+1] ;
+  PROG = "s6-tai64n" ;
+  stamp[TIMESTAMP] = ' ' ;
+  for (;;)
+  {
+    register int r = skagetln(buffer_0f1, &satmp, '\n') ;
+    if (r < 0)
+      if (errno != EPIPE)
+        strerr_diefu1sys(111, "read from stdin") ;
+      else
+      {
+        r = 1 ;
+        if (!stralloc_catb(&satmp, "\n", 1))
+          strerr_diefu1sys(111, "add newline") ;
+      }
+    else if (!r) break ;
+    timestamp(stamp) ;
+    if ((buffer_put(buffer_1, stamp, TIMESTAMP+1) < 0)
+     || (buffer_put(buffer_1, satmp.s, satmp.len) < 0))
+      strerr_diefu1sys(111, "write to stdout") ;
+    satmp.len = 0 ;
+  }
+  return 0 ;
+}
diff --git a/src/daemontools-extras/s6-tai64nlocal.c b/src/daemontools-extras/s6-tai64nlocal.c
new file mode 100644
index 0000000..d7be880
--- /dev/null
+++ b/src/daemontools-extras/s6-tai64nlocal.c
@@ -0,0 +1,47 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbtime.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+int main (void)
+{
+  PROG = "s6-tai64nlocal" ;
+  for (;;)
+  {
+    unsigned int p = 0 ;
+    int r = skagetln(buffer_0f1, &satmp, '\n') ;
+    if (r == -1)
+      if (errno != EPIPE)
+        strerr_diefu1sys(111, "read from stdin") ;
+      else r = 1 ;
+    else if (!r) break ;
+    if (satmp.len > TIMESTAMP)
+    {
+      tain_t a ;
+      p = timestamp_scan(satmp.s, &a) ;
+      if (p)
+      {
+        char fmt[LOCALTMN_FMT+1] ;
+        localtmn_t local ;
+        unsigned int len ;
+        localtmn_from_tain(&local, &a, 1) ;
+        len = localtmn_fmt(fmt, &local) ;
+        fmt[len++] = ' ' ;
+        if (buffer_put(buffer_1, fmt, len) < 0)
+          strerr_diefu1sys(111, "write to stdout") ;
+      }
+    }
+    if (buffer_put(buffer_1, satmp.s + p, satmp.len - p) < 0)
+      strerr_diefu1sys(111, "write to stdout") ;
+    satmp.len = 0 ;
+  }
+  return 0 ;
+}
diff --git a/src/daemontools-extras/ucspilogd.c b/src/daemontools-extras/ucspilogd.c
new file mode 100644
index 0000000..ddb6362
--- /dev/null
+++ b/src/daemontools-extras/ucspilogd.c
@@ -0,0 +1,117 @@
+/* ISC license. */
+
+#ifndef SYSLOG_NAMES
+#define SYSLOG_NAMES
+#endif
+
+#include <stdlib.h>
+#include <syslog.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "ucspilogd [ -D default ] [ var... ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static inline void die (void)
+{
+  strerr_diefu1sys(111, "write to stdout") ;
+}
+
+static unsigned int syslog_names (char const *line)
+{
+  unsigned int fpr, i ;
+  int fp ;
+  CODE *p = facilitynames ;
+
+  if (line[0] != '<') return 0 ;
+  i = uint_scan(line+1, &fpr) ;
+  if (!i || (line[i+1] != '>')) return 0 ;
+  i += 2 ;
+  
+  fp = LOG_FAC(fpr) << 3 ;
+  for (; p->c_name ; p++) if (p->c_val == fp) break ;
+  if (p->c_name)
+  {
+    if ((buffer_puts(buffer_1, p->c_name) < 0)
+     || (buffer_put(buffer_1, ".", 1) < 1)) die() ;
+  }
+  else
+  {
+    if (buffer_put(buffer_1, "unknown.", 8) < 8) die() ;
+    i = 0 ;
+  }
+
+  fp = LOG_PRI(fpr) ;
+  for (p = prioritynames ; p->c_name ; p++) if (p->c_val == fp) break ;
+  if (p->c_name)
+  {
+    if ((buffer_puts(buffer_1, p->c_name) < 0)
+     || (buffer_put(buffer_1, ": ", 2) < 2)) die() ;
+  }
+  else
+  {
+    if (buffer_put(buffer_1, "unknown: ", 9) < 9) die() ;
+    i = 0 ;
+  }
+  return i ;
+}
+
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  char const *d = "<undefined>" ;
+  PROG = "ucspilogd" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "D:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'D' : d = l.arg ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  {
+    char const *envs[argc] ;
+    unsigned int i = 0 ;
+    for (; i < (unsigned int)argc ; i++)
+    {
+      envs[i] = env_get2(envp, argv[i]) ;
+      if (!envs[i]) envs[i] = d ;
+    }
+    for (;;)
+    {
+      unsigned int pos = 0 ;
+      satmp.len = 0 ;
+      {
+        register int r = skagetlnsep(buffer_0f1, &satmp, "\n", 2) ;
+        if (r < 0) strerr_diefu1sys(111, "read from stdin") ;
+        if (!r) break ;
+      }
+      if (!satmp.len) continue ;
+      satmp.s[satmp.len-1] = '\n' ;
+      if ((satmp.s[0] == '@') && (satmp.len > 26) && (byte_chr(satmp.s, 26, ' ') == 25))
+      {
+        if (buffer_put(buffer_1, satmp.s, 26) < 26) die() ;
+        pos += 26 ;
+      }
+      for (i = 0 ; i < (unsigned int)argc ; i++)
+        if ((buffer_puts(buffer_1, envs[i]) < 0)
+         || (buffer_put(buffer_1, ": ", 2) < 2)) die() ;
+      pos += syslog_names(satmp.s + pos) ;
+      if (buffer_put(buffer_1, satmp.s + pos, satmp.len - pos) < (int)(satmp.len - pos)) die() ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/include/s6/ftrigr.h b/src/include/s6/ftrigr.h
new file mode 100644
index 0000000..e7c8b0c
--- /dev/null
+++ b/src/include/s6/ftrigr.h
@@ -0,0 +1,94 @@
+/* ISC license. */
+
+#ifndef FTRIGR_H
+#define FTRIGR_H
+
+#include <skalibs/config.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/tai.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/config.h>
+
+
+ /* Constants */
+
+#define FTRIGR_IPCPATH SKALIBS_SPROOT "/service/ftrigrd/s"
+
+#define FTRIGRD_PROG S6_BINPREFIX "/s6-ftrigrd"
+#define FTRIGR_BANNER1 "ftrigr v1.0 (b)\n"
+#define FTRIGR_BANNER1_LEN (sizeof FTRIGR_BANNER1 - 1)
+#define FTRIGR_BANNER2 "ftrigr v1.0 (a)\n"
+#define FTRIGR_BANNER2_LEN (sizeof FTRIGR_BANNER2 - 1)
+
+
+ /* Internals of the ftrigr_t */
+
+typedef enum fr1state_e fr1state_t, *fr1state_t_ref ;
+enum fr1state_e
+{
+  FR1STATE_WAITACK,
+  FR1STATE_WAITACKDATA,
+  FR1STATE_LISTENING,
+  FR1STATE_ERROR
+} ;
+      
+typedef struct ftrigr1_s ftrigr1_t, *ftrigr1_t_ref ;
+struct ftrigr1_s
+{
+  uint32 options ;
+  unsigned int count ;
+  fr1state_t state ;
+  char what ;
+} ;
+#define FTRIGR1_ZERO { 0, 0, FR1STATE_ERROR, 0 }
+extern ftrigr1_t const ftrigr1_zero ;
+
+
+ /* The ftrigr_t itself */
+
+typedef struct ftrigr_s ftrigr, ftrigr_t, *ftrigr_ref, *ftrigr_t_ref ;
+struct ftrigr_s
+{
+  skaclient_t connection ;
+  genalloc list ; /* array of uint16 */
+  gensetdyn data ; /* set of ftrigr1_t */
+  skaclient_buffer_t buffers ;
+} ;
+#define FTRIGR_ZERO { .connection = SKACLIENT_ZERO, .list = GENALLOC_ZERO, .data = GENSETDYN_INIT(ftrigr1_t, 2, 0, 1) }
+extern ftrigr_t const ftrigr_zero ;
+
+
+ /* Starting and ending a session */
+
+extern int ftrigr_start (ftrigr_t *, char const *, tain_t const *, tain_t *) ;
+#define ftrigr_start_g(a, path, deadline) ftrigr_start(a, path, (deadline), &STAMP)
+extern int ftrigr_startf (ftrigr_t *, tain_t const *, tain_t *) ;
+#define ftrigr_startf_g(a, deadline) ftrigr_startf(a, (deadline), &STAMP)
+extern void ftrigr_end (ftrigr_t *) ;
+                    
+
+ /* Instant primitives for async programming */
+
+#define ftrigr_fd(a) skaclient_fd(&(a)->connection)
+extern int ftrigr_update (ftrigr_t *) ;
+extern int ftrigr_check (ftrigr_t *, uint16, char *) ;
+
+
+ /* Synchronous functions with timeouts */
+
+#define FTRIGR_REPEAT 0x0001
+
+extern uint16 ftrigr_subscribe (ftrigr_t *, char const *, char const *, uint32, tain_t const *, tain_t *) ;
+#define ftrigr_subscribe_g(a, path, re, options, deadline) ftrigr_subscribe(a, path, re, options, (deadline), &STAMP)
+extern int ftrigr_unsubscribe (ftrigr_t *, uint16, tain_t const *, tain_t *) ;
+#define ftrigr_unsubscribe_g(a, id, deadline) ftrigr_unsubscribe(a, id, (deadline), &STAMP)
+
+extern int ftrigr_wait_and (ftrigr_t *, uint16 const *, unsigned int, tain_t const *, tain_t *) ;
+#define ftrigr_wait_and_g(a, list, len, deadline) ftrigr_wait_and(a, list, len, (deadline), &STAMP)
+extern int ftrigr_wait_or  (ftrigr_t *, uint16 const *, unsigned int, tain_t const *, tain_t *, char *) ;
+#define ftrigr_wait_or_g(a, list, len, deadline, what) ftrigr_wait_or(a, list, len, deadline, &STAMP, what)
+
+#endif
diff --git a/src/include/s6/ftrigw.h b/src/include/s6/ftrigw.h
new file mode 100644
index 0000000..ccaf078
--- /dev/null
+++ b/src/include/s6/ftrigw.h
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#ifndef FTRIGW_H
+#define FTRIGW_H
+
+extern int ftrigw_fifodir_make (char const *, int, int) ;
+extern int ftrigw_notify (char const *, char) ;
+extern int ftrigw_notifyb (char const *, char const *, unsigned int) ;
+extern int ftrigw_clean (char const *) ;
+
+#endif
diff --git a/src/include/s6/s6-supervise.h b/src/include/s6/s6-supervise.h
new file mode 100644
index 0000000..2a39393
--- /dev/null
+++ b/src/include/s6/s6-supervise.h
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#ifndef S6_SUPERVISE_H
+#define S6_SUPERVISE_H
+
+#include <skalibs/tai.h>
+
+#define S6_SUPERVISE_CTLDIR "supervise"
+#define S6_SUPERVISE_EVENTDIR "event"
+#define S6_SVSCAN_CTLDIR ".s6-svscan"
+#define S6_SVSTATUS_FILENAME S6_SUPERVISE_CTLDIR "/status"
+#define S6_SVSTATUS_SIZE 18
+
+extern int s6_svc_write (char const *, char const *, unsigned int) ;
+extern int s6_svc_main (int, char const *const *, char const *, char const *, char const *) ;
+
+typedef struct s6_svstatus_s s6_svstatus_t, *s6_svstatus_t_ref ;
+struct s6_svstatus_s
+{
+  tain_t stamp ;
+  unsigned int pid ;
+  unsigned int flagwant : 1 ;
+  unsigned int flagwantup : 1 ;
+  unsigned int flagpaused : 1 ;
+  unsigned int flagfinishing : 1 ;
+} ;
+
+#define S6_SVSTATUS_ZERO { .stamp = TAIN_ZERO, .pid = 0, .flagwant = 0, .flagwantup = 0, .flagpaused = 0, .flagfinishing = 0 }
+
+
+extern void s6_svstatus_pack (char *, s6_svstatus_t const *) ;
+extern void s6_svstatus_unpack (char const *, s6_svstatus_t_ref) ;
+extern int s6_svstatus_read (char const *, s6_svstatus_t_ref) ;
+extern int s6_svstatus_write (char const *, s6_svstatus_t const *) ;
+
+/* These functions leak a fd, that's intended */
+extern int s6_supervise_lock (char const *) ;
+extern int s6_supervise_lock_mode (char const *, unsigned int, unsigned int) ;
+
+#endif
diff --git a/src/include/s6/s6.h b/src/include/s6/s6.h
new file mode 100644
index 0000000..84c552d
--- /dev/null
+++ b/src/include/s6/s6.h
@@ -0,0 +1,11 @@
+/* ISC license. */
+
+#ifndef S6_H
+#define S6_H
+
+#include <s6/s6-supervise.h>
+#include <s6/ftrigr.h>
+#include <s6/ftrigw.h>
+#include <s6/s6lock.h>
+
+#endif
diff --git a/src/include/s6/s6lock.h b/src/include/s6/s6lock.h
new file mode 100644
index 0000000..a44d80a
--- /dev/null
+++ b/src/include/s6/s6lock.h
@@ -0,0 +1,75 @@
+/* ISC license. */
+
+#ifndef S6LOCK_H
+#define S6LOCK_H
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/config.h>
+
+
+ /* Constants */
+
+#define S6LOCKD_PROG S6_BINPREFIX "/s6lockd"
+#define S6LOCKD_HELPER_PROG S6_BINPREFIX "/s6lockd-helper"
+
+#define S6LOCK_BANNER1 "s6lock v1.0 (b)\n"
+#define S6LOCK_BANNER1_LEN (sizeof S6LOCK_BANNER1 - 1)
+#define S6LOCK_BANNER2 "s6lock v1.0 (a)\n"
+#define S6LOCK_BANNER2_LEN (sizeof S6LOCK_BANNER2 - 1)
+
+
+ /* The client handle */
+
+typedef struct s6lock_s s6lock_t, *s6lock_t_ref ;
+struct s6lock_s
+{
+  skaclient_t connection ;
+  genalloc list ; /* array of uint16 */
+  gensetdyn data ; /* set of char */
+  skaclient_buffer_t buffers ;
+} ;
+#define S6LOCK_ZERO { .connection = SKACLIENT_ZERO, .list = GENALLOC_ZERO, .data = GENSETDYN_INIT(int, 2, 0, 1) }
+extern s6lock_t const s6lock_zero ;
+
+
+ /* Starting and ending a session */
+
+extern int s6lock_start (s6lock_t *, char const *, tain_t const *, tain_t *) ;
+#define s6lock_start_g(a, ipcpath, deadline) s6lock_start(a, ipcpath, (deadline), &STAMP)
+extern int s6lock_startf (s6lock_t *, char const *, tain_t const *, tain_t *) ;
+#define s6lock_startf_g(a, lockdir, deadline) s6lock_startf(a, lockdir, (deadline), &STAMP)
+extern void s6lock_end (s6lock_t *) ;
+                    
+
+ /* Asynchronous primitives */
+
+#define s6lock_fd(a) skaclient_fd(&(a)->connection)
+extern int s6lock_update (s6lock_t *) ;
+extern int s6lock_check (s6lock_t *, uint16) ;
+
+
+ /* Synchronous functions */
+
+#define S6LOCK_OPTIONS_SH 0x0000U
+#define S6LOCK_OPTIONS_EX 0x0001U
+
+extern int s6lock_acquire (s6lock_t *, uint16 *, char const *, uint32, tain_t const *, tain_t const *, tain_t *) ;
+#define s6lock_acquire_g(a, id, path, options, limit, deadline) s6lock_acquire(a, id, path, options, limit, (deadline), &STAMP)
+#define s6lock_acquire_sh(a, id, path, limit, deadline, stamp) s6lock_aquire(a, id, path, S6LOCK_OPTIONS_SH, limit, deadline, stamp)
+#define s6lock_acquire_ex(a, id, path, limit, deadline, stamp) s6lock_aquire(a, id, path, S6LOCK_OPTIONS_EX, limit, deadline, stamp)
+#define s6lock_acquire_sh_g(a, id, path, limit, deadline) s6lock_acquire_sh(a, id, path, limit, (deadline), &STAMP)
+#define s6lock_acquire_ex_g(a, id, path, limit, deadline) s6lock_acquire_ex(a, id, path, limit, (deadline), &STAMP)
+extern int s6lock_release (s6lock_t *, uint16, tain_t const *, tain_t *) ;
+#define s6lock_release_g(a, id, deadline) s6lock_release(a, id, (deadline), &STAMP)
+
+extern int s6lock_wait_and (s6lock_t *, uint16 const *, unsigned int, tain_t const *, tain_t *) ;
+#define s6lock_wait_and_g(a, list, len, deadline) s6lock_wait_and(a, list, len, (deadline), &STAMP)
+extern int s6lock_wait_or  (s6lock_t *, uint16 const *, unsigned int, tain_t const *, tain_t *) ;
+#define s6lock_wait_or_g(a, list, len, deadline) s6lock_wait_or(a, list, len, (deadline), &STAMP)
+
+#endif
diff --git a/src/libs6/deps-exe/s6-ftrigrd b/src/libs6/deps-exe/s6-ftrigrd
new file mode 100755
index 0000000..4b86d93
--- /dev/null
+++ b/src/libs6/deps-exe/s6-ftrigrd
@@ -0,0 +1,5 @@
+ftrig1_free.o
+ftrig1_make.o
+-lskarnet
+${SOCKET_LIB}
+${TAINNOW_LIB}
diff --git a/src/libs6/deps-exe/s6lockd b/src/libs6/deps-exe/s6lockd
new file mode 100755
index 0000000..e027835
--- /dev/null
+++ b/src/libs6/deps-exe/s6lockd
@@ -0,0 +1,3 @@
+-lskarnet
+${SOCKET_LIB}
+${TAINNOW_LIB}
diff --git a/src/libs6/deps-exe/s6lockd-helper b/src/libs6/deps-exe/s6lockd-helper
new file mode 100755
index 0000000..e7187fe
--- /dev/null
+++ b/src/libs6/deps-exe/s6lockd-helper
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/libs6/deps-lib/s6 b/src/libs6/deps-lib/s6
new file mode 100755
index 0000000..502694e
--- /dev/null
+++ b/src/libs6/deps-lib/s6
@@ -0,0 +1,33 @@
+ftrigr1_zero.o
+ftrigr_check.o
+ftrigr_end.o
+ftrigr_start.o
+ftrigr_startf.o
+ftrigr_subscribe.o
+ftrigr_unsubscribe.o
+ftrigr_update.o
+ftrigr_wait_and.o
+ftrigr_wait_or.o
+ftrigr_zero.o
+ftrigw_clean.o
+ftrigw_fifodir_make.o
+ftrigw_notify.o
+ftrigw_notifyb.o
+s6_supervise_lock.o
+s6_supervise_lock_mode.o
+s6_svc_main.o
+s6_svc_write.o
+s6_svstatus_pack.o
+s6_svstatus_read.o
+s6_svstatus_unpack.o
+s6_svstatus_write.o
+s6lock_acquire.o
+s6lock_check.o
+s6lock_end.o
+s6lock_release.o
+s6lock_start.o
+s6lock_startf.o
+s6lock_update.o
+s6lock_wait_and.o
+s6lock_wait_or.o
+s6lock_zero.o
diff --git a/src/libs6/ftrig1.h b/src/libs6/ftrig1.h
new file mode 100644
index 0000000..229de66
--- /dev/null
+++ b/src/libs6/ftrig1.h
@@ -0,0 +1,23 @@
+/* ISC license. */
+
+#ifndef FTRIG1_H
+#define FTRIG1_H
+
+#include <skalibs/stralloc.h>
+
+#define FTRIG1_PREFIX "ftrig1"
+#define FTRIG1_PREFIXLEN (sizeof FTRIG1_PREFIX - 1)
+
+typedef struct ftrig1_s ftrig1_t, *ftrig1_t_ref ;
+struct ftrig1_s
+{
+  int fd ;
+  int fdw ;
+  stralloc name ;
+} ;
+#define FTRIG1_ZERO { .fd = -1, .fdw = -1, .name = STRALLOC_ZERO }
+
+extern int ftrig1_make (ftrig1_t *, char const *) ;
+extern void ftrig1_free (ftrig1_t *) ;
+
+#endif
diff --git a/src/libs6/ftrig1_free.c b/src/libs6/ftrig1_free.c
new file mode 100644
index 0000000..091dc87
--- /dev/null
+++ b/src/libs6/ftrig1_free.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include "ftrig1.h"
+
+void ftrig1_free (ftrig1_t *p)
+{
+  if (p->name.s)
+  {
+    unlink(p->name.s) ;
+    stralloc_free(&p->name) ;
+  }
+  if (p->fd >= 0)
+  {
+    fd_close(p->fd) ;
+    p->fd = -1 ;
+  }
+  if (p->fdw >= 0)
+  {
+    fd_close(p->fdw) ;
+    p->fdw = -1 ;
+  }
+}
diff --git a/src/libs6/ftrig1_make.c b/src/libs6/ftrig1_make.c
new file mode 100644
index 0000000..7aedd08
--- /dev/null
+++ b/src/libs6/ftrig1_make.c
@@ -0,0 +1,65 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>      /* for rename() */
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/tai.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/random.h>
+#include "ftrig1.h"
+
+int ftrig1_make (ftrig1_t *f, char const *path)
+{
+  ftrig1_t ff = FTRIG1_ZERO ;
+  unsigned int pathlen = str_len(path) ;
+  int e = 0 ;
+  char tmp[pathlen + 46 + FTRIG1_PREFIXLEN] ;
+  
+  byte_copy(tmp, pathlen, path) ;
+  tmp[pathlen] = '/' ; tmp[pathlen+1] = '.' ;
+  byte_copy(tmp + pathlen + 2, FTRIG1_PREFIXLEN, FTRIG1_PREFIX) ;
+  tmp[pathlen + 2 + FTRIG1_PREFIXLEN] = ':' ;
+  if (!timestamp(tmp + pathlen + 3 + FTRIG1_PREFIXLEN)) return 0 ;
+  tmp[pathlen + 28 + FTRIG1_PREFIXLEN] = ':' ;
+  if (random_name(tmp + pathlen + 29 + FTRIG1_PREFIXLEN, 16) < 16) return 0 ;
+  tmp[pathlen + 45 + FTRIG1_PREFIXLEN] = 0 ;
+  
+  {
+    mode_t m = umask(0) ;
+    if (mkfifo(tmp, S_IRUSR|S_IWUSR|S_IWGRP|S_IWOTH) == -1)
+    {
+      umask(m) ;
+      return 0 ;
+    }
+    umask(m) ;
+  }
+
+  if (!stralloc_catb(&ff.name, tmp, pathlen+1)) { e = errno ; goto err0 ; }
+  if (!stralloc_catb(&ff.name, tmp + pathlen + 2, FTRIG1_PREFIXLEN + 44))
+  {
+    e = errno ; goto err1 ;
+  }
+  ff.fd = open_read(tmp) ;
+  if (ff.fd == -1) { e = errno ; goto err1 ; }
+  ff.fdw = open_write(tmp) ;
+  if (ff.fdw == -1) { e = errno ; goto err2 ; }
+  if (rename(tmp, ff.name.s) == -1) goto err3 ;
+  *f = ff ;
+  return 1 ;
+
+ err3:
+  e = errno ;
+  fd_close(ff.fdw) ;
+ err2:
+  fd_close(ff.fd) ;
+ err1:
+  stralloc_free(&ff.name) ;
+ err0:
+  unlink(tmp) ;
+  errno = e ;
+  return 0 ;
+}
diff --git a/src/libs6/ftrigr1_zero.c b/src/libs6/ftrigr1_zero.c
new file mode 100644
index 0000000..967b4e0
--- /dev/null
+++ b/src/libs6/ftrigr1_zero.c
@@ -0,0 +1,5 @@
+/* ISC license. */
+
+#include <s6/ftrigr.h>
+
+ftrigr1_t const ftrigr1_zero = FTRIGR1_ZERO ;
diff --git a/src/libs6/ftrigr_check.c b/src/libs6/ftrigr_check.c
new file mode 100644
index 0000000..147deca
--- /dev/null
+++ b/src/libs6/ftrigr_check.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/gensetdyn.h>
+#include <s6/ftrigr.h>
+
+int ftrigr_check (ftrigr_t *a, uint16 id, char *c)
+{
+  ftrigr1_t *p ;
+  if (!id--) return (errno = EINVAL, -1) ;
+  p = GENSETDYN_P(ftrigr1_t, &a->data, id) ;
+  if (!p) return (errno = EINVAL, -1) ;
+  switch (p->state)
+  {
+    case FR1STATE_WAITACKDATA :
+    {
+      *c = p->what ;
+      *p = ftrigr1_zero ;
+      gensetdyn_delete(&a->data, id) ;
+      return 1 ;
+    }
+    case FR1STATE_LISTENING :
+    {
+      register unsigned int r = p->count ;
+      if (r) *c = p->what ;
+      p->count = 0 ;
+      return (int)r ;
+    }
+    case FR1STATE_WAITACK :
+    {
+      errno = p->what ;
+      *p = ftrigr1_zero ;
+      gensetdyn_delete(&a->data, id) ;
+      return -1 ;
+    }
+    default: return (errno = EINVAL, -1) ;
+  }
+  return 0 ;
+}
diff --git a/src/libs6/ftrigr_end.c b/src/libs6/ftrigr_end.c
new file mode 100644
index 0000000..f35f06c
--- /dev/null
+++ b/src/libs6/ftrigr_end.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/ftrigr.h>
+
+void ftrigr_end (ftrigr_ref a)
+{
+  gensetdyn_free(&a->data) ;
+  genalloc_free(uint16, &a->list) ;
+  skaclient_end(&a->connection) ;
+  *a = ftrigr_zero ;
+}
diff --git a/src/libs6/ftrigr_start.c b/src/libs6/ftrigr_start.c
new file mode 100644
index 0000000..baf9ce5
--- /dev/null
+++ b/src/libs6/ftrigr_start.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <skalibs/tai.h>
+#include <skalibs/skaclient.h>
+#include <s6/ftrigr.h>
+
+int ftrigr_start (ftrigr_t *a, char const *path, tain_t const *deadline, tain_t *stamp)
+{
+  return skaclient_start_b(&a->connection, &a->buffers, path, FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, deadline, stamp) ;
+}
diff --git a/src/libs6/ftrigr_startf.c b/src/libs6/ftrigr_startf.c
new file mode 100644
index 0000000..28c81aa
--- /dev/null
+++ b/src/libs6/ftrigr_startf.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/tai.h>
+#include <skalibs/skaclient.h>
+#include <s6/ftrigr.h>
+
+int ftrigr_startf (ftrigr_ref a, tain_t const *deadline, tain_t *stamp)
+{
+  char const *cargv[2] = { FTRIGRD_PROG, 0 } ;
+  char const *cenvp[1] = { 0 } ;
+  return skaclient_startf_b(&a->connection, &a->buffers, cargv[0], cargv, cenvp, SKACLIENT_OPTION_WAITPID, FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, deadline, stamp) ;
+}
diff --git a/src/libs6/ftrigr_subscribe.c b/src/libs6/ftrigr_subscribe.c
new file mode 100644
index 0000000..d645931
--- /dev/null
+++ b/src/libs6/ftrigr_subscribe.c
@@ -0,0 +1,43 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/siovec.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/ftrigr.h>
+
+uint16 ftrigr_subscribe (ftrigr_t *a, char const *path, char const *re, uint32 options, tain_t const *deadline, tain_t *stamp)
+{
+  unsigned int pathlen = str_len(path) ;
+  unsigned int relen = str_len(re) ;
+  unsigned int i ;
+  char err ;
+  char tmp[15] = "--L" ;
+  siovec_t v[3] = { { .s = tmp, .len = 15 }, { .s = (char *)path, .len = pathlen + 1 }, { .s = (char *)re, .len = relen + 1 } } ;
+  if (!gensetdyn_new(&a->data, &i)) return 0 ;
+  uint16_pack_big(tmp, (uint16)i) ;
+  uint32_pack_big(tmp+3, options) ;
+  uint32_pack_big(tmp+7, (uint32)pathlen) ;
+  uint32_pack_big(tmp+11, (uint32)relen) ;
+  if (!skaclient_sendv(&a->connection, v, 3, &skaclient_default_cb, &err, deadline, stamp))
+  {
+    gensetdyn_delete(&a->data, i) ;
+    return 0 ;
+  }
+  if (err)
+  {
+    gensetdyn_delete(&a->data, i) ;
+    return (errno = err, 0) ;
+  }
+  {
+    register ftrigr1_t *p = GENSETDYN_P(ftrigr1_t, &a->data, i) ;
+    p->options = options ;
+    p->state = FR1STATE_LISTENING ;
+    p->count = 0 ;
+    p->what = 0 ;
+  }
+  return (uint16)(i+1) ;
+}
diff --git a/src/libs6/ftrigr_unsubscribe.c b/src/libs6/ftrigr_unsubscribe.c
new file mode 100644
index 0000000..4833571
--- /dev/null
+++ b/src/libs6/ftrigr_unsubscribe.c
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/ftrigr.h>
+
+int ftrigr_unsubscribe (ftrigr_ref a, uint16 i, tain_t const *deadline, tain_t *stamp)
+{
+  ftrigr1_t *p ;
+  if (!i--) return (errno = EINVAL, 0) ;
+  p = GENSETDYN_P(ftrigr1_t, &a->data, i) ;
+  if (!p) return (errno = EINVAL, 0) ;
+  switch (p->state)
+  {
+    case FR1STATE_WAITACK :
+    case FR1STATE_WAITACKDATA :
+    {
+      char dummy ;
+      ftrigr_check(a, i+1, &dummy) ;
+      return 1 ;
+    }
+    default : break ;
+  }
+  {
+    char err ;
+    char pack[3] = "--U" ;
+    uint16_pack_big(pack, i) ;
+    if (!skaclient_send(&a->connection, pack, 3, &skaclient_default_cb, &err, deadline, stamp)) return 0 ;
+    if (err) return (errno = err, 0) ;
+  }
+  *p = ftrigr1_zero ;
+  return gensetdyn_delete(&a->data, i) ;
+}
diff --git a/src/libs6/ftrigr_update.c b/src/libs6/ftrigr_update.c
new file mode 100644
index 0000000..ad69714
--- /dev/null
+++ b/src/libs6/ftrigr_update.c
@@ -0,0 +1,43 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/error.h>
+#include <skalibs/uint16.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/unixmessage.h>
+#include <skalibs/skaclient.h>
+#include <s6/ftrigr.h>
+
+static int msghandler (unixmessage_t const *m, void *context)
+{
+  ftrigr_t *a = (ftrigr_t *)context ;
+  ftrigr1_t *p ;
+  uint16 id ;
+  if (m->len != 4 || m->nfds) return (errno = EPROTO, 0) ;
+  uint16_unpack_big(m->s, &id) ;
+  p = GENSETDYN_P(ftrigr1_t, &a->data, id) ;
+  if (!p) return 1 ;
+  if (p->state != FR1STATE_LISTENING) return (errno = EINVAL, 0) ;
+  if (!genalloc_readyplus(uint16, &a->list, 1)) return 0 ;
+  switch (m->s[2])
+  {
+    case 'd' :
+      p->state = FR1STATE_WAITACK ;
+      break ;
+    case '!' :
+      if (p->options & FTRIGR_REPEAT) p->count++ ;
+      else p->state = FR1STATE_WAITACKDATA ;
+      break ;
+    default : return (errno = EPROTO, 0) ;
+  }
+  p->what = m->s[3] ;
+  id++ ; genalloc_append(uint16, &a->list, &id) ;
+  return 1 ;
+}
+
+int ftrigr_update (ftrigr_t *a)
+{
+  genalloc_setlen(uint16, &a->list, 0) ;
+  return skaclient_update(&a->connection, &msghandler, a) ;
+}
diff --git a/src/libs6/ftrigr_wait_and.c b/src/libs6/ftrigr_wait_and.c
new file mode 100644
index 0000000..f854a8d
--- /dev/null
+++ b/src/libs6/ftrigr_wait_and.c
@@ -0,0 +1,28 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <s6/ftrigr.h>
+
+int ftrigr_wait_and (ftrigr_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp)
+{
+  iopause_fd x = { -1, IOPAUSE_READ, 0 } ;
+  x.fd = ftrigr_fd(a) ;
+  for (; n ; n--, idlist++)
+  {
+    for (;;)
+    {
+      char dummy ;
+      register int r = ftrigr_check(a, *idlist, &dummy) ;
+      if (r < 0) return r ;
+      else if (r) break ;
+      r = iopause_stamp(&x, 1, deadline, stamp) ;
+      if (r < 0) return r ;
+      else if (!r) return (errno = ETIMEDOUT, -1) ;
+      else if (ftrigr_update(a) < 0) return -1 ;
+    }
+  }
+  return 1 ;
+}
diff --git a/src/libs6/ftrigr_wait_or.c b/src/libs6/ftrigr_wait_or.c
new file mode 100644
index 0000000..8a01d85
--- /dev/null
+++ b/src/libs6/ftrigr_wait_or.c
@@ -0,0 +1,31 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/error.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <s6/ftrigr.h>
+
+int ftrigr_wait_or (ftrigr_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp, char *c)
+{
+  iopause_fd x = { -1, IOPAUSE_READ | IOPAUSE_EXCEPT, 0 } ;
+  x.fd = ftrigr_fd(a) ;
+  if (x.fd < 0) return -1 ;
+  for (;;)
+  {
+    register unsigned int i = 0 ;
+    register int r ;
+    for (; i < n ; i++)
+    {
+      r = ftrigr_check(a, idlist[i], c) ;
+      if (r < 0) return r ;
+      else if (r) return i ;
+    }
+    r = iopause_stamp(&x, 1, deadline, stamp) ;
+    if (r < 0) return 0 ;
+    else if (!r) return (errno = ETIMEDOUT, -1) ;
+    else if (ftrigr_update(a) < 0) return -1 ;
+  }
+  return (errno = EPROTO, -1) ; /* can't happen */
+}
diff --git a/src/libs6/ftrigr_zero.c b/src/libs6/ftrigr_zero.c
new file mode 100644
index 0000000..b09ddb6
--- /dev/null
+++ b/src/libs6/ftrigr_zero.c
@@ -0,0 +1,5 @@
+/* ISC license. */
+
+#include <s6/ftrigr.h>
+
+ftrigr_t const ftrigr_zero = FTRIGR_ZERO ;
diff --git a/src/libs6/ftrigw_clean.c b/src/libs6/ftrigw_clean.c
new file mode 100644
index 0000000..1198828
--- /dev/null
+++ b/src/libs6/ftrigw_clean.c
@@ -0,0 +1,39 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/direntry.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/djbunix.h>
+#include "ftrig1.h"
+#include <s6/ftrigw.h>
+
+int ftrigw_clean (char const *path)
+{
+  unsigned int pathlen = str_len(path) ;
+  int e = 0 ;
+  DIR *dir = opendir(path) ;
+  if (!dir) return 0 ;
+  {
+    char tmp[pathlen + FTRIG1_PREFIXLEN + 45] ;
+    byte_copy(tmp, pathlen, path) ;
+    tmp[pathlen] = '/' ; tmp[pathlen + FTRIG1_PREFIXLEN + 44] = 0 ;
+    for (;;)
+    {
+      direntry *d ;
+      int fd ;
+      errno = 0 ;
+      d = readdir(dir) ;
+      if (!d) break ;
+      if (str_diffn(d->d_name, FTRIG1_PREFIX, FTRIG1_PREFIXLEN)) continue ;
+      if (str_len(d->d_name) != FTRIG1_PREFIXLEN + 43) continue ;
+      byte_copy(tmp + pathlen + 1, FTRIG1_PREFIXLEN + 43, d->d_name) ;
+      fd = open_write(tmp) ;
+      if (fd >= 0) fd_close(fd) ;
+      else if ((errno == ENXIO) && (unlink(tmp) < 0)) e = errno ;
+    }
+  }
+  if (errno) e = errno ;
+  dir_close(dir) ;
+  return e ? (errno = e, 0) : 1 ;
+}
diff --git a/src/libs6/ftrigw_fifodir_make.c b/src/libs6/ftrigw_fifodir_make.c
new file mode 100644
index 0000000..1a69a8e
--- /dev/null
+++ b/src/libs6/ftrigw_fifodir_make.c
@@ -0,0 +1,26 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <s6/ftrigw.h>
+
+int ftrigw_fifodir_make (char const *path, int gid, int force)
+{
+  mode_t m = umask(0) ;
+  if (mkdir(path, 0700) == -1)
+  {
+    struct stat st ;
+    umask(m) ;
+    if (errno != EEXIST) return 0 ;
+    if (stat(path, &st) == -1) return 0 ;
+    if (st.st_uid != getuid()) return (errno = EACCES, 0) ;
+    if (!S_ISDIR(st.st_mode)) return (errno = ENOTDIR, 0) ;
+    if (!force) return 1 ;
+  }
+  else umask(m) ;
+  if ((gid >= 0) && (chown(path, -1, gid) == -1)) return 0 ;
+  if (chmod(path, (gid >= 0) ? 03730 : 01733) == -1) return 0 ;
+  return 1 ;
+}
diff --git a/src/libs6/ftrigw_notify.c b/src/libs6/ftrigw_notify.c
new file mode 100644
index 0000000..230dad6
--- /dev/null
+++ b/src/libs6/ftrigw_notify.c
@@ -0,0 +1,8 @@
+/* ISC license. */
+
+#include <s6/ftrigw.h>
+
+int ftrigw_notify (char const *path, char c)
+{
+  return ftrigw_notifyb(path, &c, 1) ;
+}
diff --git a/src/libs6/ftrigw_notifyb.c b/src/libs6/ftrigw_notifyb.c
new file mode 100644
index 0000000..345a3cc
--- /dev/null
+++ b/src/libs6/ftrigw_notifyb.c
@@ -0,0 +1,67 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <skalibs/direntry.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sig.h>
+#include <skalibs/djbunix.h>
+#include "ftrig1.h"
+#include <s6/ftrigw.h>
+
+int ftrigw_notifyb (char const *path, char const *s, unsigned int len)
+{
+  unsigned int i = 0 ;
+  struct skasigaction old ;
+  DIR *dir = opendir(path) ;
+  if (!dir) return -1 ;
+  if (skasigaction(SIGPIPE, &SKASIG_IGN, &old) < 0) return -1 ;
+  {
+    unsigned int pathlen = str_len(path) ;
+    char tmp[pathlen + FTRIG1_PREFIXLEN + 45] ;
+    byte_copy(tmp, pathlen, path) ;
+    tmp[pathlen] = '/' ; tmp[pathlen + FTRIG1_PREFIXLEN + 44] = 0 ;
+    for (;;)
+    {
+      direntry *d ;
+      int fd ;
+      errno = 0 ;
+      d = readdir(dir) ;
+      if (!d) break ;
+      if (str_diffn(d->d_name, FTRIG1_PREFIX, FTRIG1_PREFIXLEN)) continue ;
+      if (str_len(d->d_name) != FTRIG1_PREFIXLEN + 43) continue ;
+      byte_copy(tmp + pathlen + 1, FTRIG1_PREFIXLEN + 43, d->d_name) ;
+      fd = open_write(tmp) ;
+      if (fd == -1)
+      {
+        if (errno == ENXIO) unlink(tmp) ;
+      }
+      else
+      {
+        register int r = fd_write(fd, s, len) ;
+        if ((r < 0) || (unsigned int)r < len)
+        {
+          if (errno == EPIPE) unlink(tmp) ;
+           /* what to do if EGAIN ? full fifo -> fix the reader !
+              There's a race condition in extreme cases though ;
+              but it's still better to be nonblocking - the writer
+              shouldn't get in trouble because of a bad reader. */
+          fd_close(fd) ;
+        }
+        else
+        {
+          fd_close(fd) ;
+          i++ ;
+        }
+      }
+    }
+  }
+  {
+    int e = errno ;
+    skasigaction(SIGPIPE, &old, 0) ;
+    dir_close(dir) ;
+    return e ? (errno = e, -1) : (int)i ;
+  }
+}
diff --git a/src/libs6/s6-ftrigrd.c b/src/libs6/s6-ftrigrd.c
new file mode 100644
index 0000000..b766f36
--- /dev/null
+++ b/src/libs6/s6-ftrigrd.c
@@ -0,0 +1,266 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <signal.h>
+#include <regex.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/bufalloc.h>
+#include <skalibs/sig.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/unixmessage.h>
+#include <skalibs/skaclient.h>
+#include "ftrig1.h"
+#include <s6/ftrigr.h>
+
+#define FTRIGRD_MAXREADS 32
+#define FTRIGRD_BUFSIZE 16
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+typedef struct ftrigio_s ftrigio_t, *ftrigio_t_ref ;
+struct ftrigio_s
+{
+  unsigned int xindex ;
+  ftrig1_t trig ;
+  buffer b ;
+  char buf[FTRIGRD_BUFSIZE] ;
+  regex_t re ;
+  stralloc sa ;
+  uint32 options ;
+  uint16 id ; /* given by client */
+} ;
+#define FTRIGIO_ZERO { .xindex = 0, .trig = FTRIG1_ZERO, .b = BUFFER_INIT(0, -1, 0, 0), .buf = "", .sa = STRALLOC_ZERO, .options = 0, .id = 0 }
+static ftrigio_t const fzero = FTRIGIO_ZERO ;
+
+static genalloc a = GENALLOC_ZERO ; /* array of ftrigio_t */
+
+static void ftrigio_deepfree (ftrigio_t_ref p)
+{
+  ftrig1_free(&p->trig) ;
+  stralloc_free(&p->sa) ;
+  regfree(&p->re) ;
+  *p = fzero ;
+}
+
+static void cleanup (void)
+{
+  register unsigned int i = genalloc_len(ftrigio_t, &a) ;
+  for (; i ; i--) ftrigio_deepfree(genalloc_s(ftrigio_t, &a) + i - 1) ;
+  genalloc_setlen(ftrigio_t, &a, 0) ;
+}
+ 
+static void trig (uint16 id, char what, char info)
+{
+  char pack[4] ;
+  unixmessage_t m = { .s = pack, .len = 4, .fds = 0, .nfds = 0 } ;
+  uint16_pack_big(pack, id) ;
+  pack[2] = what ; pack[3] = info ;
+  if (!unixmessage_put(unixmessage_sender_x, &m))
+  {
+    cleanup() ;
+    strerr_diefu1sys(111, "build answer") ;
+  }
+}
+
+static void answer (char c)
+{
+  unixmessage_t m = { &c, 1, 0, 0 } ;
+  if (!unixmessage_put(unixmessage_sender_1, &m))
+  {
+    cleanup() ;
+    strerr_diefu1sys(111, "unixmessage_put") ;
+  }
+}
+
+static void remove (unsigned int i)
+{
+  register unsigned int n = genalloc_len(ftrigio_t, &a) - 1 ;
+  ftrigio_deepfree(genalloc_s(ftrigio_t, &a) + i) ;
+  genalloc_s(ftrigio_t, &a)[i] = genalloc_s(ftrigio_t, &a)[n] ;
+  genalloc_setlen(ftrigio_t, &a, n) ;
+}
+
+static inline int ftrigio_read (ftrigio_t *p)
+{
+  unsigned int n = FTRIGRD_MAXREADS ;
+  while (n--)
+  {
+    regmatch_t pmatch ;
+    unsigned int blen ;
+    register int r = sanitize_read(buffer_fill(&p->b)) ;
+    if (!r) break ;
+    if (r < 0) return (trig(p->id, 'd', errno), 0) ;
+    blen = buffer_len(&p->b) ;
+    if (!stralloc_readyplus(&p->sa, blen+1)) dienomem() ;
+    buffer_getnofill(&p->b, p->sa.s + p->sa.len, blen) ;
+    p->sa.len += blen ;
+    p->sa.s[p->sa.len] = 0 ;
+    while (!regexec(&p->re, p->sa.s, 1, &pmatch, REG_NOTBOL | REG_NOTEOL))
+    {
+      trig(p->id, '!', p->sa.s[pmatch.rm_eo - 1]) ;
+      if (!(p->options & FTRIGR_REPEAT)) return 0 ;
+      byte_copy(p->sa.s, p->sa.len + 1 - pmatch.rm_eo, p->sa.s + pmatch.rm_eo) ;
+      p->sa.len -= pmatch.rm_eo ;
+    }
+  }
+  return 1 ;
+}
+
+static int parse_protocol (unixmessage_t const *m, void *context)
+{
+  uint16 id ;
+  if (m->len < 3 || m->nfds)
+  {
+    cleanup() ;
+    strerr_dief1x(100, "invalid client request") ;
+  }
+  uint16_unpack_big(m->s, &id) ;
+  switch (m->s[2])
+  {
+    case 'U' : /* unsubscribe */
+    {
+      register unsigned int i = genalloc_len(ftrigio_t, &a) ;
+      for (; i ; i--) if (genalloc_s(ftrigio_t, &a)[i-1].id == id) break ;
+      if (i) remove(i-1) ;
+      answer(0) ;
+      break ;
+    }
+    case 'L' : /* subscribe to path and match re */
+    {
+      ftrigio_t f = FTRIGIO_ZERO ;
+      uint32 pathlen, relen ;
+      int r ;
+      if (m->len < 18)
+      {
+        answer(EPROTO) ;
+        break ;
+      }
+      uint32_unpack_big(m->s + 3, &f.options) ;
+      uint32_unpack_big(m->s + 7, &pathlen) ;
+      uint32_unpack_big(m->s + 11, &relen) ;
+      if (((pathlen + relen + 17) != m->len) || m->s[15 + pathlen] || m->s[m->len - 1])
+      {
+        answer(EPROTO) ;
+        break ;
+      }
+      f.id = id ;
+      r = regcomp(&f.re, m->s + 16 + pathlen, REG_EXTENDED) ;
+      if (r)
+      {
+        answer(r == REG_ESPACE ? ENOMEM : EINVAL) ;
+        break ;
+      }
+      if (!ftrig1_make(&f.trig, m->s + 15))
+      {
+        regfree(&f.re) ;
+        answer(errno) ;
+        break ;
+      }
+      if (!genalloc_append(ftrigio_t, &a, &f))
+      {
+        ftrigio_deepfree(&f) ;
+        answer(errno) ;
+        break ;
+      }
+      answer(0) ;
+      break ;
+    }
+    default :
+    {
+      cleanup() ;
+      strerr_dief1x(100, "invalid client request") ;
+    }
+  }
+  (void)context ;
+  return 1 ;
+}
+
+int main (void)
+{
+  PROG = "s6-ftrigrd" ;
+
+  if (ndelay_on(0) < 0) strerr_diefu2sys(111, "ndelay_on ", "0") ;
+  if (ndelay_on(1) < 0) strerr_diefu2sys(111, "ndelay_on ", "1") ;
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+
+  {
+    tain_t deadline ;
+    tain_now_g() ;
+    tain_addsec_g(&deadline, 2) ;
+    if (!skaclient_server_01x_init_g(FTRIGR_BANNER1, FTRIGR_BANNER1_LEN, FTRIGR_BANNER2, FTRIGR_BANNER2_LEN, &deadline))
+      strerr_diefu1sys(111, "sync with client") ;
+  }
+
+  for (;;)
+  {
+    register unsigned int n = genalloc_len(ftrigio_t, &a) ;
+    iopause_fd x[3 + n] ;
+    unsigned int i = 0 ;
+
+    x[0].fd = 0 ; x[0].events = IOPAUSE_EXCEPT | IOPAUSE_READ ;
+    x[1].fd = 1 ; x[1].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_1) ? 0 : IOPAUSE_WRITE) ;
+    x[2].fd = unixmessage_sender_fd(unixmessage_sender_x) ;
+    x[2].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_x) ? 0 : IOPAUSE_WRITE) ;
+    for (; i < n ; i++)
+    {
+      register ftrigio_t_ref p = genalloc_s(ftrigio_t, &a) + i ;
+      p->xindex = 3 + i ;
+      x[3+i].fd = p->trig.fd ;
+      x[3+i].events = IOPAUSE_READ ;
+    }
+
+    if (iopause(x, 3 + n, 0, 0) < 0)
+    {
+      cleanup() ;
+      strerr_diefu1sys(111, "iopause") ;
+    }
+
+   /* client closed */
+    if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) break ;
+
+   /* client is reading */
+    if (x[1].revents & IOPAUSE_WRITE)
+      if ((unixmessage_sender_flush(unixmessage_sender_1) < 0) && !error_isagain(errno))
+      {
+        cleanup() ;
+        strerr_diefu1sys(111, "flush stdout") ;
+      }
+    if (x[2].revents & IOPAUSE_WRITE)
+      if ((!unixmessage_sender_flush(unixmessage_sender_x) < 0) && !error_isagain(errno))
+      {
+        cleanup() ;
+        strerr_diefu1sys(111, "flush asyncout") ;
+      }
+
+   /* scan listening ftrigs */
+    for (i = 0 ; i < genalloc_len(ftrigio_t, &a) ; i++)
+    {
+      register ftrigio_t_ref p = genalloc_s(ftrigio_t, &a) + i ;
+      if (x[p->xindex].revents & IOPAUSE_READ)
+        if (!ftrigio_read(p)) remove(i--) ;
+    }
+
+   /* client is writing */
+    if (!unixmessage_receiver_isempty(unixmessage_receiver_0) || x[0].revents & IOPAUSE_READ)
+    {
+      if (unixmessage_handle(unixmessage_receiver_0, &parse_protocol, 0) < 0)
+      {
+        if (errno == EPIPE) break ; /* normal exit */
+        cleanup() ;
+        strerr_diefu1sys(111, "handle messages from client") ;
+      }
+    }
+  }
+  cleanup() ;
+  return 0 ;
+}
diff --git a/src/libs6/s6_supervise_lock.c b/src/libs6/s6_supervise_lock.c
new file mode 100644
index 0000000..5c9ca30
--- /dev/null
+++ b/src/libs6/s6_supervise_lock.c
@@ -0,0 +1,10 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <s6/s6-supervise.h>
+
+int s6_supervise_lock (char const *subdir)
+{
+  return s6_supervise_lock_mode(subdir, S_IRWXU, S_IRUSR | S_IWUSR) ;
+}
diff --git a/src/libs6/s6_supervise_lock_mode.c b/src/libs6/s6_supervise_lock_mode.c
new file mode 100644
index 0000000..ff3e11e
--- /dev/null
+++ b/src/libs6/s6_supervise_lock_mode.c
@@ -0,0 +1,54 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <s6/s6-supervise.h>
+
+int s6_supervise_lock_mode (char const *subdir, unsigned int subdirmode, unsigned int controlmode)
+{
+  unsigned int subdirlen = str_len(subdir) ;
+  int fdctl, fdctlw, fdlock ;
+  char control[subdirlen + 9] ;
+  char lock[subdirlen + 6] ;
+  byte_copy(control, subdirlen, subdir) ;
+  byte_copy(control + subdirlen, 9, "/control") ;
+  byte_copy(lock, subdirlen, subdir) ;
+  byte_copy(lock + subdirlen, 6, "/lock") ;
+  if ((mkdir(subdir, (mode_t)subdirmode) == -1) && (errno != EEXIST))
+    strerr_diefu2sys(111, "mkdir ", subdir) ;
+  if (mkfifo(control, controlmode) < 0)
+  {
+    struct stat st ;
+    if (errno != EEXIST)
+      strerr_diefu2sys(111, "mkfifo ", control) ;
+    if (stat(control, &st) < 0)
+      strerr_diefu2sys(111, "stat ", control) ;
+    if (!S_ISFIFO(st.st_mode))
+      strerr_diefu2x(100, control, " is not a FIFO") ;
+  }
+  fdlock = open_create(lock) ;
+  if (fdlock < 0)
+    strerr_diefu2sys(111, "open_create ", lock) ;
+  if (lock_ex(fdlock) < 0)
+    strerr_diefu2sys(111, "lock ", lock) ;
+  fdctlw = open_write(control) ;
+  if (fdctlw >= 0) strerr_dief1x(100, "directory already locked") ;
+  if (errno != ENXIO)
+    strerr_diefu2sys(111, "open_write ", control) ;
+  fdctl = open_read(control) ;
+  if (fdctl < 0)
+    strerr_diefu2sys(111, "open_read ", control) ;
+  fdctlw = open_write(control) ;
+  if (fdctlw < 0)
+    strerr_diefu2sys(111, "open_write ", control) ;
+  fd_close(fdlock) ;
+  if ((coe(fdctlw) < 0) || (coe(fdctl) < 0))
+    strerr_diefu2sys(111, "coe ", control) ;
+
+  return fdctl ;
+  /* fdctlw is leaking. That's okay, it's coe. */
+}
diff --git a/src/libs6/s6_svc_main.c b/src/libs6/s6_svc_main.c
new file mode 100644
index 0000000..5d14904
--- /dev/null
+++ b/src/libs6/s6_svc_main.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/skamisc.h>
+#include <s6/s6-supervise.h>
+
+#define DATASIZE 256
+
+int s6_svc_main (int argc, char const *const *argv, char const *optstring, char const *usage, char const *controldir)
+{
+  char data[DATASIZE] ;
+  unsigned int datalen = 0 ;
+  register int r ;
+  for (;;)
+  {
+    register int opt = subgetopt(argc, argv, optstring) ;
+    if (opt == -1) break ;
+    if (opt == '?') strerr_dieusage(100, usage) ;
+    if (datalen >= DATASIZE) strerr_dief1x(100, "too many commands") ;
+    data[datalen++] = opt ;
+  }
+  argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  if (!argc) strerr_dieusage(100, usage) ;
+
+  {
+    unsigned int arglen = str_len(*argv) ;
+    unsigned int cdirlen = str_len(controldir) ;
+    char tmp[arglen + cdirlen + 10] ;
+    byte_copy(tmp, arglen, *argv) ;
+    tmp[arglen] = '/' ;
+    byte_copy(tmp + arglen + 1, cdirlen, controldir) ;
+    byte_copy(tmp + arglen + 1 + cdirlen, 9, "/control") ;
+    r = s6_svc_write(tmp, data, datalen) ;
+  }
+  if (r < 0) strerr_diefu2sys(111, "control ", *argv) ;
+  else if (!r) strerr_diefu3x(100, "control ", *argv, ": supervisor not listening") ;
+  return 0 ;
+}
diff --git a/src/libs6/s6_svc_write.c b/src/libs6/s6_svc_write.c
new file mode 100644
index 0000000..ea9eee5
--- /dev/null
+++ b/src/libs6/s6_svc_write.c
@@ -0,0 +1,22 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+#include <s6/s6-supervise.h>
+
+int s6_svc_write (char const *fifo, char const *data, unsigned int datalen)
+{
+  int fd = open_write(fifo) ;
+  if (fd < 0) return (errno == ENXIO) ? 0 : -1 ;
+  else if (ndelay_off(fd) == -1) return -1 ;
+  else if (fd_write(fd, data, datalen) == -1)
+  {
+    register int e = errno ;
+    fd_close(fd) ;
+    errno = e ;
+    return -1 ;
+  }
+  fd_close(fd) ;
+  return 1 ;
+}
diff --git a/src/libs6/s6_svstatus_pack.c b/src/libs6/s6_svstatus_pack.c
new file mode 100644
index 0000000..2d5baf6
--- /dev/null
+++ b/src/libs6/s6_svstatus_pack.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <skalibs/uint32.h>
+#include <skalibs/tai.h>
+#include <s6/s6-supervise.h>
+
+void s6_svstatus_pack (char *pack, s6_svstatus_t const *sv)
+{
+  tain_pack(pack, &sv->stamp) ;
+  uint32_pack(pack + 12, (uint32)sv->pid) ;
+  pack[16] = sv->flagpaused | (sv->flagfinishing << 1) ;
+  pack[17] = sv->flagwant ? sv->flagwantup ? 'u' : 'd' : 0 ;
+}
diff --git a/src/libs6/s6_svstatus_read.c b/src/libs6/s6_svstatus_read.c
new file mode 100644
index 0000000..32ec660
--- /dev/null
+++ b/src/libs6/s6_svstatus_read.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/djbunix.h>
+#include <s6/s6-supervise.h>
+
+int s6_svstatus_read (char const *dir, s6_svstatus_t_ref status)
+{
+  unsigned int n = str_len(dir) ;
+  char pack[S6_SVSTATUS_SIZE] ;
+  char tmp[n + 2 + sizeof(S6_SVSTATUS_FILENAME)] ;
+  byte_copy(tmp, n, dir) ;
+  byte_copy(tmp + n, 2 + sizeof(S6_SVSTATUS_FILENAME), "/" S6_SVSTATUS_FILENAME) ;
+  if (openreadnclose(tmp, pack, S6_SVSTATUS_SIZE) < S6_SVSTATUS_SIZE) return 0 ;
+  s6_svstatus_unpack(pack, status) ;
+  return 1 ;
+}
diff --git a/src/libs6/s6_svstatus_unpack.c b/src/libs6/s6_svstatus_unpack.c
new file mode 100644
index 0000000..cce6989
--- /dev/null
+++ b/src/libs6/s6_svstatus_unpack.c
@@ -0,0 +1,29 @@
+/* ISC license. */
+
+#include <skalibs/uint32.h>
+#include <skalibs/tai.h>
+#include <s6/s6-supervise.h>
+
+void s6_svstatus_unpack (char const *pack, s6_svstatus_t_ref sv)
+{
+  uint32 pid ;
+  tain_unpack(pack, &sv->stamp) ;
+  uint32_unpack(pack + 12, &pid) ;
+  sv->pid = (int)pid ;
+  sv->flagpaused = pack[16] & 1 ;
+  sv->flagfinishing = (pack[16] >> 1) & 1 ;
+  switch (pack[17])
+  {
+    case 'u' :
+      sv->flagwant = 1 ;
+      sv->flagwantup = 1 ;
+      break ;
+    case 'd' :
+      sv->flagwant = 1 ;
+      sv->flagwantup = 0 ;
+      break ;
+    default :
+      sv->flagwant = 0 ;
+      sv->flagwantup = 0 ;
+  }
+}
diff --git a/src/libs6/s6_svstatus_write.c b/src/libs6/s6_svstatus_write.c
new file mode 100644
index 0000000..2cc8a7b
--- /dev/null
+++ b/src/libs6/s6_svstatus_write.c
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/djbunix.h>
+#include <s6/s6-supervise.h>
+
+int s6_svstatus_write (char const *dir, s6_svstatus_t const *status)
+{
+  unsigned int n = str_len(dir) ;
+  char pack[S6_SVSTATUS_SIZE] ;
+  char tmp[n + 2 + sizeof(S6_SVSTATUS_FILENAME)] ;
+  byte_copy(tmp, n, dir) ;
+  byte_copy(tmp + n, 2 + sizeof(S6_SVSTATUS_FILENAME), "/" S6_SVSTATUS_FILENAME) ;
+  s6_svstatus_pack(pack, status) ;
+  return openwritenclose_suffix(tmp, pack, S6_SVSTATUS_SIZE, ".new") ;
+}
diff --git a/src/libs6/s6lock_acquire.c b/src/libs6/s6lock_acquire.c
new file mode 100644
index 0000000..b0fef54
--- /dev/null
+++ b/src/libs6/s6lock_acquire.c
@@ -0,0 +1,38 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/siovec.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+int s6lock_acquire (s6lock_t *a, uint16 *u, char const *path, uint32 options, tain_t const *limit, tain_t const *deadline, tain_t *stamp)
+{
+  unsigned int pathlen = str_len(path) ;
+  char err ;
+  char tmp[23] = "--<" ;
+  siovec_t v[2] = { { .s = tmp, .len = 23 }, { .s = (char *)path, .len = pathlen + 1 } } ;
+  unsigned int i ;
+  if (!gensetdyn_new(&a->data, &i)) return 0 ;
+  uint16_pack_big(tmp, (uint16)i) ;
+  uint32_pack_big(tmp+3, options) ;
+  tain_pack(tmp+7, limit) ;
+  uint32_pack_big(tmp+19, (uint32)pathlen) ;
+  if (!skaclient_sendv(&a->connection, v, 2, &skaclient_default_cb, &err, deadline, stamp))
+  {
+    gensetdyn_delete(&a->data, i) ;
+    return 0 ;
+  }
+  if (err)
+  {
+    gensetdyn_delete(&a->data, i) ;
+    return (errno = err, 0) ;
+  }
+  *GENSETDYN_P(char, &a->data, i) = EAGAIN ;
+  *u = i ;
+  return 1 ;
+}
diff --git a/src/libs6/s6lock_check.c b/src/libs6/s6lock_check.c
new file mode 100644
index 0000000..e794128
--- /dev/null
+++ b/src/libs6/s6lock_check.c
@@ -0,0 +1,25 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/error.h>
+#include <skalibs/uint16.h>
+#include <skalibs/gensetdyn.h>
+#include <s6/s6lock.h>
+
+int s6lock_check (s6lock_t *a, uint16 id)
+{
+  char *p = GENSETDYN_P(char, &a->data, id) ;
+  switch (*p)
+  {
+    case EBUSY : return 1 ;
+    case EINVAL : return (errno = EINVAL, -1) ;
+    default :
+    {
+      if (error_isagain(*p)) return 0 ;
+      errno = *p ;
+      *p = EINVAL ;
+      gensetdyn_delete(&a->data, id) ;
+      return -1 ;
+    }
+  }
+}
diff --git a/src/libs6/s6lock_end.c b/src/libs6/s6lock_end.c
new file mode 100644
index 0000000..c460efd
--- /dev/null
+++ b/src/libs6/s6lock_end.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+void s6lock_end (s6lock_t *a)
+{
+  gensetdyn_free(&a->data) ;
+  genalloc_free(uint16, &a->list) ;
+  skaclient_end(&a->connection) ;
+  *a = s6lock_zero ;
+}
diff --git a/src/libs6/s6lock_release.c b/src/libs6/s6lock_release.c
new file mode 100644
index 0000000..95e863f
--- /dev/null
+++ b/src/libs6/s6lock_release.c
@@ -0,0 +1,28 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/error.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+int s6lock_release (s6lock_t *a, uint16 i, tain_t const *deadline, tain_t *stamp)
+{
+  char *p = GENSETDYN_P(char, &a->data, i) ;
+  if ((*p != EBUSY) && !error_isagain(*p))
+  {
+    s6lock_check(a, i) ;
+    return 1 ;
+  }
+  {
+    char err ;
+    char pack[3] = "-->" ;
+    uint16_pack_big(pack, i) ;
+    if (!skaclient_send(&a->connection, pack, 3, &skaclient_default_cb, &err, deadline, stamp)) return 0 ;
+    if (err) return (errno = err, 0) ;
+  }
+  *p = EINVAL ;
+  return gensetdyn_delete(&a->data, i) ;
+}
diff --git a/src/libs6/s6lock_start.c b/src/libs6/s6lock_start.c
new file mode 100644
index 0000000..e7993d9
--- /dev/null
+++ b/src/libs6/s6lock_start.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/environ.h>
+#include <skalibs/tai.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+int s6lock_start (s6lock_t *a, char const *path, tain_t const *deadline, tain_t *stamp)
+{
+  return skaclient_start_b(&a->connection, &a->buffers, path, S6LOCK_BANNER1, S6LOCK_BANNER1_LEN, S6LOCK_BANNER2, S6LOCK_BANNER2_LEN, deadline, stamp) ;
+}
diff --git a/src/libs6/s6lock_startf.c b/src/libs6/s6lock_startf.c
new file mode 100644
index 0000000..c34a595
--- /dev/null
+++ b/src/libs6/s6lock_startf.c
@@ -0,0 +1,14 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/environ.h>
+#include <skalibs/tai.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+int s6lock_startf (s6lock_t *a, char const *lockdir, tain_t const *deadline, tain_t *stamp)
+{
+  char const *cargv[3] = { S6LOCKD_PROG, lockdir, 0 } ;
+  if (!lockdir) return (errno = EINVAL, 0) ;
+  return skaclient_startf_b(&a->connection, &a->buffers, cargv[0], cargv, (char const *const *)environ, SKACLIENT_OPTION_WAITPID, S6LOCK_BANNER1, S6LOCK_BANNER1_LEN, S6LOCK_BANNER2, S6LOCK_BANNER2_LEN, deadline, stamp) ;
+}
diff --git a/src/libs6/s6lock_update.c b/src/libs6/s6lock_update.c
new file mode 100644
index 0000000..6e6a2a0
--- /dev/null
+++ b/src/libs6/s6lock_update.c
@@ -0,0 +1,31 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/error.h>
+#include <skalibs/uint16.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/gensetdyn.h>
+#include <skalibs/unixmessage.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+static int msghandler (unixmessage_t const *m, void *context)
+{
+  s6lock_t *a = (s6lock_t *)context ;
+  char *p ;
+  uint16 id ;
+  if (m->len != 3 || m->nfds) return (errno = EPROTO, 0) ;
+  uint16_unpack_big(m->s, &id) ;
+  p = GENSETDYN_P(char, &a->data, id) ;
+  if (*p == EBUSY) *p = m->s[2] ;
+  else if (error_isagain(*p)) *p = m->s[2] ? m->s[2] : EBUSY ;
+  else return (errno = EPROTO, 0) ;
+  if (!genalloc_append(uint16, &a->list, &id)) return 0 ;
+  return 1 ;
+}
+
+int s6lock_update (s6lock_t *a)
+{
+  genalloc_setlen(uint16, &a->list, 0) ;
+  return skaclient_update(&a->connection, &msghandler, a) ;
+}
diff --git a/src/libs6/s6lock_wait_and.c b/src/libs6/s6lock_wait_and.c
new file mode 100644
index 0000000..460cc07
--- /dev/null
+++ b/src/libs6/s6lock_wait_and.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <s6/s6lock.h>
+
+int s6lock_wait_and (s6lock_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp)
+{
+  iopause_fd x = { -1, IOPAUSE_READ, 0 } ;
+  x.fd = s6lock_fd(a) ;
+  for (; n ; n--, idlist++)
+  {
+    for (;;)
+    {
+      register int r = s6lock_check(a, *idlist) ;
+      if (r < 0) return r ;
+      else if (r) break ;
+      r = iopause_stamp(&x, 1, deadline, stamp) ;
+      if (r < 0) return r ;
+      else if (!r) return (errno = ETIMEDOUT, -1) ;
+      else if (s6lock_update(a) < 0) return -1 ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/libs6/s6lock_wait_or.c b/src/libs6/s6lock_wait_or.c
new file mode 100644
index 0000000..0219574
--- /dev/null
+++ b/src/libs6/s6lock_wait_or.c
@@ -0,0 +1,30 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint16.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <s6/s6lock.h>
+
+int s6lock_wait_or (s6lock_t *a, uint16 const *idlist, unsigned int n, tain_t const *deadline, tain_t *stamp)
+{
+  iopause_fd x = { -1, IOPAUSE_READ | IOPAUSE_EXCEPT, 0 } ;
+  x.fd = s6lock_fd(a) ;
+  if (x.fd < 0) return -1 ;
+  for (;;)
+  {
+    register unsigned int i = 0 ;
+    register int r ;
+    for (; i < n ; i++)
+    {
+      r = s6lock_check(a, idlist[i]) ;
+      if (r < 0) return r ;
+      else if (r) return i ;
+    }
+    r = iopause_stamp(&x, 1, deadline, stamp) ;
+    if (r < 0) return 0 ;
+    else if (!r) return (errno = ETIMEDOUT, -1) ;
+    else if (s6lock_update(a) < 0) return -1 ;
+  }
+  return (errno = EPROTO, -1) ; /* can't happen */
+}
diff --git a/src/libs6/s6lock_zero.c b/src/libs6/s6lock_zero.c
new file mode 100644
index 0000000..a4e0138
--- /dev/null
+++ b/src/libs6/s6lock_zero.c
@@ -0,0 +1,5 @@
+/* ISC license. */
+
+#include <s6/s6lock.h>
+
+s6lock_t const s6lock_zero = S6LOCK_ZERO ;
diff --git a/src/libs6/s6lockd-helper.c b/src/libs6/s6lockd-helper.c
new file mode 100644
index 0000000..8979c67
--- /dev/null
+++ b/src/libs6/s6lockd-helper.c
@@ -0,0 +1,27 @@
+/* ISC license. */
+
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6lockd-helper lockfile"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int fd ;
+  char const *x = env_get2(envp, "S6LOCK_EX") ;
+  char c ;
+  PROG = "s6lockd-helper" ;
+  if (argc < 2) dieusage() ;
+  fd = open_create(argv[1]) ;
+  if (fd < 0) strerr_diefu2sys(111, "open ", argv[1]) ;
+  if (((x && *x) ? lock_ex(fd) : lock_sh(fd)) < 0)
+    strerr_diefu2sys(111, "lock ", argv[1]) ;
+  if (fd_write(1, "!", 1) <= 0)
+    strerr_diefu1sys(111, "write to stdout") ;
+  if (fd_read(0, &c, 1) < 0)
+    strerr_diefu1sys(111, "read from stdin") ;
+  return 0 ;
+}
diff --git a/src/libs6/s6lockd.c b/src/libs6/s6lockd.c
new file mode 100644
index 0000000..ef088f9
--- /dev/null
+++ b/src/libs6/s6lockd.c
@@ -0,0 +1,316 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint32.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/unixmessage.h>
+#include <skalibs/skaclient.h>
+#include <s6/s6lock.h>
+
+#define USAGE "s6lockd lockdir"
+#define X() strerr_dief1x(101, "internal inconsistency, please submit a bug-report.")
+
+typedef struct s6lockio_s s6lockio_t, *s6lockio_t_ref ;
+struct s6lockio_s
+{
+  unsigned int xindex ;
+  unsigned int pid ;
+  tain_t limit ;
+  int p[2] ;
+  uint16 id ; /* given by client */
+} ;
+#define S6LOCKIO_ZERO { 0, 0, TAIN_ZERO, { -1, -1 }, 0 }
+static s6lockio_t const szero = S6LOCKIO_ZERO ;
+
+static genalloc a = GENALLOC_ZERO ; /* array of s6lockio_t */
+
+static void s6lockio_free (s6lockio_t_ref p)
+{
+  register int e = errno ;
+  fd_close(p->p[1]) ;
+  fd_close(p->p[0]) ;
+  kill(p->pid, SIGTERM) ;
+  *p = szero ;
+  errno = e ;
+}
+
+static void cleanup (void)
+{
+  register unsigned int i = genalloc_len(s6lockio_t, &a) ;
+  for (; i ; i--) s6lockio_free(genalloc_s(s6lockio_t, &a) + i - 1) ;
+  genalloc_setlen(s6lockio_t, &a, 0) ;
+}
+ 
+static void trig (uint16 id, char e)
+{
+  char pack[3] ;
+  unixmessage_t m = { .s = pack, .len = 3, .fds = 0, .nfds = 0 } ;
+  uint16_pack_big(pack, id) ;
+  pack[2] = e ;
+  if (!unixmessage_put(unixmessage_sender_x, &m))
+  {
+    cleanup() ;
+    strerr_diefu1sys(111, "build answer") ;
+  }
+}
+
+static void answer (char c)
+{
+  unixmessage_t m = { .s = &c, .len = 1, .fds = 0, .nfds = 0 } ;
+  if (!unixmessage_put(unixmessage_sender_1, &m))
+  {
+    cleanup() ;
+    strerr_diefu1sys(111, "unixmessage_put") ;
+  }
+}
+
+static void remove (unsigned int i)
+{
+  register unsigned int n = genalloc_len(s6lockio_t, &a) - 1 ;
+  s6lockio_free(genalloc_s(s6lockio_t, &a) + i) ;
+  genalloc_s(s6lockio_t, &a)[i] = genalloc_s(s6lockio_t, &a)[n] ;
+  genalloc_setlen(s6lockio_t, &a, n) ;
+}
+
+static void handle_signals (void)
+{
+  for (;;)
+  {
+    switch (selfpipe_read())
+    {
+      case -1 : cleanup() ; strerr_diefu1sys(111, "selfpipe_read") ;
+      case 0 : return ;
+      case SIGTERM :
+      case SIGQUIT :
+      case SIGHUP :
+      case SIGABRT :
+      case SIGINT : cleanup() ; _exit(0) ;
+      case SIGCHLD : wait_reap() ; break ;
+      default : cleanup() ; X() ;
+    }
+  }
+}
+
+static int parse_protocol (unixmessage_t const *m, void *context)
+{
+  uint16 id ;
+  if (m->len < 3 || m->nfds)
+  {
+    cleanup() ;
+    strerr_dief1x(100, "invalid client request") ;
+  }
+  uint16_unpack_big(m->s, &id) ;
+  switch (m->s[2])
+  {
+    case '>' : /* release */
+    {
+      register unsigned int i = genalloc_len(s6lockio_t, &a) ;
+      for (; i ; i--) if (genalloc_s(s6lockio_t, &a)[i-1].id == id) break ;
+      if (i)
+      {
+        remove(i-1) ;
+        answer(0) ;
+      }
+      else answer(ENOENT) ;
+      break ;
+    }
+    case '<' : /* lock path */
+    {
+      s6lockio_t f = S6LOCKIO_ZERO ;
+      char const *cargv[3] = { S6LOCKD_HELPER_PROG, 0, 0 } ;
+      char const *cenvp[2] = { 0, 0 } ;
+      uint32 options, pathlen ;
+      if (m->len < 23)
+      {
+        answer(EPROTO) ;
+        break ;
+      }
+      uint32_unpack_big(m->s + 3, &options) ;
+      tain_unpack(m->s + 7, &f.limit) ;
+      uint32_unpack_big(m->s + 19, &pathlen) ;
+      if (pathlen + 23 != m->len || m->s[m->len - 1])
+      {
+        answer(EPROTO) ;
+        break ;
+      }
+      f.id = id ;
+      m->s[21] = '.' ;
+      m->s[22] = '/' ;
+      cargv[1] = (char const *)m->s + 21 ;
+      if (options & S6LOCK_OPTIONS_EX) cenvp[0] = "S6LOCK_EX=1" ;
+      f.pid = child_spawn(cargv[0], cargv, cenvp, f.p, 2) ;
+      if (!f.pid)
+      {
+        answer(errno) ;
+        break ;
+      }
+      if (!genalloc_append(s6lockio_t, &a, &f))
+      {
+        s6lockio_free(&f) ;
+        answer(errno) ;
+        break ;
+      }
+      answer(0) ;
+      break ;
+    }
+    default :
+    {
+      cleanup() ;
+      strerr_dief1x(100, "invalid client request") ;
+    }
+  }
+  (void)context ;
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  tain_t deadline ;
+  int sfd ;
+  PROG = "s6lockd" ;
+  
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if (chdir(argv[1]) < 0) strerr_diefu2sys(111, "chdir to ", argv[1]) ;
+  if (ndelay_on(0) < 0) strerr_diefu2sys(111, "ndelay_on ", "0") ;
+  if (ndelay_on(1) < 0) strerr_diefu2sys(111, "ndelay_on ", "1") ;
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+
+  sfd = selfpipe_init() ;
+  if (sfd < 0) strerr_diefu1sys(111, "selfpipe_init") ;
+  {
+    sigset_t set ;
+    sigemptyset(&set) ;
+    sigaddset(&set, SIGCHLD) ;
+    sigaddset(&set, SIGTERM) ;
+    sigaddset(&set, SIGQUIT) ;
+    sigaddset(&set, SIGHUP) ;
+    sigaddset(&set, SIGABRT) ;
+    sigaddset(&set, SIGINT) ;
+    if (selfpipe_trapset(&set) < 0)
+      strerr_diefu1sys(111, "trap signals") ;
+  }
+  
+  tain_now_g() ;
+  tain_addsec_g(&deadline, 2) ;
+
+  if (!skaclient_server_01x_init_g(S6LOCK_BANNER1, S6LOCK_BANNER1_LEN, S6LOCK_BANNER2, S6LOCK_BANNER2_LEN, &deadline))
+    strerr_diefu1sys(111, "sync with client") ;
+
+  for (;;)
+  {
+    register unsigned int n = genalloc_len(s6lockio_t, &a) ;
+    iopause_fd x[4 + n] ;
+    unsigned int i = 0 ;
+    int r ;
+
+    tain_add_g(&deadline, &tain_infinite_relative) ;
+    x[0].fd = 0 ; x[0].events = IOPAUSE_EXCEPT | IOPAUSE_READ ;
+    x[1].fd = 1 ; x[1].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_1) ? 0 : IOPAUSE_WRITE ) ;
+    x[2].fd = unixmessage_sender_fd(unixmessage_sender_x) ;
+    x[2].events = IOPAUSE_EXCEPT | (unixmessage_sender_isempty(unixmessage_sender_x) ? 0 : IOPAUSE_WRITE) ;
+    x[3].fd = sfd ; x[3].events = IOPAUSE_READ ;
+    for (; i < n ; i++)
+    {
+      register s6lockio_t_ref p = genalloc_s(s6lockio_t, &a) + i ;
+      x[4+i].fd = p->p[0] ;
+      x[4+i].events = IOPAUSE_READ ;
+      if (p->limit.sec.x && tain_less(&p->limit, &deadline)) deadline = p->limit ;
+      p->xindex = 4+i ;
+    }
+
+    r = iopause_g(x, 4 + n, &deadline) ;
+    if (r < 0)
+    {
+      cleanup() ;
+      strerr_diefu1sys(111, "iopause") ;
+    }
+
+   /* timeout => seek and destroy */
+    if (!r)
+    {
+      for (i = 0 ; i < n ; i++)
+      {
+        register s6lockio_t_ref p = genalloc_s(s6lockio_t, &a) + i ;
+        if (p->limit.sec.x && !tain_future(&p->limit)) break ;
+      }
+      if (i < n)
+      {
+        trig(genalloc_s(s6lockio_t, &a)[i].id, ETIMEDOUT) ;
+        remove(i) ;
+      }
+      continue ;
+    }
+
+   /* client closed */
+    if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT) break ;
+
+   /* client is reading */
+    if (x[1].revents & IOPAUSE_WRITE)
+      if ((unixmessage_sender_flush(unixmessage_sender_1) < 0) && !error_isagain(errno))
+      {
+        cleanup() ;
+        strerr_diefu1sys(111, "flush stdout") ;
+      }
+    if (x[2].revents & IOPAUSE_WRITE)
+      if ((unixmessage_sender_flush(unixmessage_sender_x) < 0) && !error_isagain(errno))
+      {
+        cleanup() ;
+        strerr_diefu1sys(111, "flush asyncout") ;
+      }
+
+   /* scan children for successes */
+    for (i = 0 ; i < genalloc_len(s6lockio_t, &a) ; i++)
+    {
+      register s6lockio_t_ref p = genalloc_s(s6lockio_t, &a) + i ;
+      if (p->p[0] < 0) continue ;
+      if (x[p->xindex].revents & IOPAUSE_READ)
+      {
+        char c ;
+        register int r = sanitize_read(fd_read(p->p[0], &c, 1)) ;
+        if (!r) continue ;
+        if (r < 0)
+        {
+          trig(p->id, errno) ;
+          remove(i--) ;
+        }
+        else if (c != '!')
+        {
+          trig(p->id, EPROTO) ;
+          remove(i--) ;
+        }
+        else
+        {
+          trig(p->id, 0) ;
+          p->limit = tain_zero ;
+        }
+      }
+    }
+
+   /* signals arrived */
+    if (x[3].revents & (IOPAUSE_READ | IOPAUSE_EXCEPT)) handle_signals() ;
+
+   /* client is writing */
+    if (!unixmessage_receiver_isempty(unixmessage_receiver_0) || x[0].revents & IOPAUSE_READ)
+    {
+      if (unixmessage_handle(unixmessage_receiver_0, &parse_protocol, 0) < 0)
+      {
+        if (errno == EPIPE) break ; /* normal exit */
+        cleanup() ;
+        strerr_diefu1sys(111, "handle messages from client") ;
+      }
+    }
+  }
+  cleanup() ;
+  return 0 ;
+}
diff --git a/src/pipe-tools/deps-exe/s6-cleanfifodir b/src/pipe-tools/deps-exe/s6-cleanfifodir
new file mode 100644
index 0000000..83cec1e
--- /dev/null
+++ b/src/pipe-tools/deps-exe/s6-cleanfifodir
@@ -0,0 +1,2 @@
+-ls6
+-lskarnet
diff --git a/src/pipe-tools/deps-exe/s6-ftrig-listen b/src/pipe-tools/deps-exe/s6-ftrig-listen
new file mode 100644
index 0000000..38a1f7a
--- /dev/null
+++ b/src/pipe-tools/deps-exe/s6-ftrig-listen
@@ -0,0 +1,4 @@
+-ls6
+-lexecline
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/pipe-tools/deps-exe/s6-ftrig-listen1 b/src/pipe-tools/deps-exe/s6-ftrig-listen1
new file mode 100644
index 0000000..58a34e0
--- /dev/null
+++ b/src/pipe-tools/deps-exe/s6-ftrig-listen1
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/pipe-tools/deps-exe/s6-ftrig-notify b/src/pipe-tools/deps-exe/s6-ftrig-notify
new file mode 100644
index 0000000..83cec1e
--- /dev/null
+++ b/src/pipe-tools/deps-exe/s6-ftrig-notify
@@ -0,0 +1,2 @@
+-ls6
+-lskarnet
diff --git a/src/pipe-tools/deps-exe/s6-ftrig-wait b/src/pipe-tools/deps-exe/s6-ftrig-wait
new file mode 100644
index 0000000..58a34e0
--- /dev/null
+++ b/src/pipe-tools/deps-exe/s6-ftrig-wait
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/pipe-tools/deps-exe/s6-mkfifodir b/src/pipe-tools/deps-exe/s6-mkfifodir
new file mode 100644
index 0000000..83cec1e
--- /dev/null
+++ b/src/pipe-tools/deps-exe/s6-mkfifodir
@@ -0,0 +1,2 @@
+-ls6
+-lskarnet
diff --git a/src/pipe-tools/s6-cleanfifodir.c b/src/pipe-tools/s6-cleanfifodir.c
new file mode 100644
index 0000000..4af38e1
--- /dev/null
+++ b/src/pipe-tools/s6-cleanfifodir.c
@@ -0,0 +1,15 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <s6/ftrigw.h>
+
+#define USAGE "s6-cleanfifodir fifodir"
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-cleanfifodir" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if (!ftrigw_clean(argv[1]))
+    strerr_diefu2sys(111, "clean up fifodir at ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/pipe-tools/s6-ftrig-listen.c b/src/pipe-tools/s6-ftrig-listen.c
new file mode 100644
index 0000000..2f6e82b
--- /dev/null
+++ b/src/pipe-tools/s6-ftrig-listen.c
@@ -0,0 +1,122 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <execline/execline.h>
+#include <s6/ftrigr.h>
+
+#define USAGE "s6-ftrig-listen [ -a | -o ] [ -t timeout ] ~fifodir1 ~regexp1 ... ; prog..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static void handle_signals (void)
+{
+  for (;;) switch (selfpipe_read())
+  {
+    case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+    case 0 : return ;
+    case SIGCHLD : wait_reap() ; break ;
+    default : strerr_dief1x(101, "unexpected data in selfpipe") ;
+  }
+}
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ;
+  tain_t deadline, tto ;
+  ftrigr_t a = FTRIGR_ZERO ;
+  int argc1 ;
+  unsigned int i = 0 ;
+  char or = 0 ;
+  PROG = "s6-ftrig-listen" ;
+  {
+    unsigned int t = 0 ;
+    for (;;)
+    {
+      register int opt = subgetopt(argc, argv, "aot:") ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'a' : or = 0 ; break ;
+        case 'o' : or = 1 ; break ;
+        case 't' : if (uint0_scan(subgetopt_here.arg, &t)) break ;
+        default : dieusage() ;
+      }
+    }
+    if (t) tain_from_millisecs(&tto, t) ; else tto = tain_infinite_relative ;
+    argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  }
+  if (argc < 2) dieusage() ;
+  argc1 = el_semicolon(argv) ;
+  if (!argc1 || (argc1 & 1) || (argc == argc1 + 1)) dieusage() ;
+  if (argc1 >= argc) strerr_dief1x(100, "unterminated fifodir+regex block") ;
+  tain_now_g() ;
+  tain_add_g(&deadline, &tto) ;
+  x[0].fd = selfpipe_init() ;
+  if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ;
+  if (selfpipe_trap(SIGCHLD) < 0) strerr_diefu1sys(111, "selfpipe_trap") ;
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore") ;
+
+  if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ;
+  x[1].fd = ftrigr_fd(&a) ;
+
+  {
+    int pid = 0 ;
+    unsigned int idlen = argc1 >> 1 ;
+    uint16 ids[idlen] ;
+    for (; i < idlen ; i++)
+    {
+      ids[i] = ftrigr_subscribe_g(&a, argv[i<<1], argv[(i<<1)+1], 0, &deadline) ;
+      if (!ids[i]) strerr_diefu4sys(111, "subscribe to ", argv[i<<1], " with regexp ", argv[(i<<1)+1]) ;
+    }
+
+    pid = fork() ;
+    switch (pid)
+    {
+      case -1 : strerr_diefu1sys(111, "fork") ;
+      case 0  :
+      {
+        PROG = "s6-ftrig-listen (child)" ;
+        pathexec_run(argv[argc1 + 1], argv + argc1 + 1, envp) ;
+        strerr_dieexec(111, argv[argc1 + 1]) ;
+      }
+    }
+
+    for (;;)
+    {
+      register int r ;
+      i = 0 ;
+      while (i < idlen)
+      {
+        char dummy ;
+        r = ftrigr_check(&a, ids[i], &dummy) ;
+        if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ;
+        else if (!r) i++ ;
+        else if (or) idlen = 0 ;
+        else ids[i] = ids[--idlen] ;
+      }
+      if (!idlen) break ;
+      r = iopause_g(x, 2, &deadline) ;
+      if (r < 0) strerr_diefu1sys(111, "iopause") ;
+      else if (!r)
+      {
+        errno = ETIMEDOUT ;
+        strerr_diefu1sys(1, "get expected event") ;
+      }
+      if (x[0].revents & IOPAUSE_READ) handle_signals() ;
+      if (x[1].revents & IOPAUSE_READ)
+      {
+        if (ftrigr_update(&a) < 0) strerr_diefu1sys(111, "ftrigr_update") ;
+      }
+    }
+  }
+  return 0 ;
+}
diff --git a/src/pipe-tools/s6-ftrig-listen1.c b/src/pipe-tools/s6-ftrig-listen1.c
new file mode 100644
index 0000000..1354a64
--- /dev/null
+++ b/src/pipe-tools/s6-ftrig-listen1.c
@@ -0,0 +1,101 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <s6/ftrigr.h>
+
+#define USAGE "s6-ftrig-listen1 [ -t timeout ] fifodir regexp prog..."
+
+static void handle_signals (void)
+{
+  for (;;) switch (selfpipe_read())
+  {
+    case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+    case 0 : return ;
+    case SIGCHLD : wait_reap() ; break ;
+    default : strerr_dief1x(101, "unexpected data in selfpipe") ;
+  }
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ;
+  tain_t deadline, tto ;
+  ftrigr_t a = FTRIGR_ZERO ;
+  int pid ;
+  uint16 id ;
+  PROG = "s6-ftrig-listen1" ;
+  {
+    unsigned int t = 0 ;
+    for (;;)
+    {
+      register int opt = subgetopt(argc, argv, "t:") ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 't' : if (uint0_scan(subgetopt_here.arg, &t)) break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    if (t) tain_from_millisecs(&tto, t) ;
+    else tto = tain_infinite_relative ;
+    argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  }
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+
+  tain_now_g() ;
+  tain_add_g(&deadline, &tto) ;
+
+  if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ;
+  id = ftrigr_subscribe_g(&a, argv[0], argv[1], 0, &deadline) ;
+  if (!id) strerr_diefu4sys(111, "subscribe to ", argv[0], " with regexp ", argv[1]) ;
+
+  x[0].fd = selfpipe_init() ;
+  if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ;
+  if (selfpipe_trap(SIGCHLD) < 0) strerr_diefu1sys(111, "selfpipe_trap") ;
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore") ;
+  x[1].fd = ftrigr_fd(&a) ;
+
+  pid = fork() ;
+  switch (pid)
+  {
+    case -1 : strerr_diefu1sys(111, "fork") ;
+    case 0  :
+    {
+      PROG = "s6-ftrig-listen1 (child)" ;
+      pathexec_run(argv[2], argv+2, envp) ;
+      strerr_dieexec(111, argv[2]) ;
+    }
+  }
+
+  for (;;)
+  {
+    char dummy ;
+    register int r = ftrigr_check(&a, id, &dummy) ;
+    if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ;
+    if (r) break ;
+    r = iopause_g(x, 2, &deadline) ;
+    if (r < 0) strerr_diefu1sys(111, "iopause") ;
+    else if (!r)
+    {
+      errno = ETIMEDOUT ;
+      strerr_diefu1sys(1, "get expected event") ;
+    }
+    if (x[0].revents & IOPAUSE_READ) handle_signals() ;
+    if (x[1].revents & IOPAUSE_READ)
+    {
+      if (ftrigr_update(&a) < 0) strerr_diefu1sys(111, "ftrigr_update") ;
+    }
+  }
+
+  return 0 ;
+}
diff --git a/src/pipe-tools/s6-ftrig-notify.c b/src/pipe-tools/s6-ftrig-notify.c
new file mode 100644
index 0000000..1216a6a
--- /dev/null
+++ b/src/pipe-tools/s6-ftrig-notify.c
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <s6/ftrigw.h>
+
+#define USAGE "s6-ftrig-notify fifodir message"
+
+int main (int argc, char const *const *argv)
+{
+  char const *p ;
+  PROG = "s6-ftrig-notify" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  p = argv[2] ;
+  for (; *p ; p++)
+  {
+    if (ftrigw_notify(argv[1], *p) == -1)
+      strerr_diefu2sys(111, "notify ", argv[1]) ;
+  }
+  return 0 ;
+}
diff --git a/src/pipe-tools/s6-ftrig-wait.c b/src/pipe-tools/s6-ftrig-wait.c
new file mode 100644
index 0000000..772ce86
--- /dev/null
+++ b/src/pipe-tools/s6-ftrig-wait.c
@@ -0,0 +1,48 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <s6/ftrigr.h>
+
+#define USAGE "s6-ftrig-wait [ -t timeout ] fifodir regexp"
+
+int main (int argc, char const *const *argv)
+{
+  tain_t deadline, tto ;
+  ftrigr_t a = FTRIGR_ZERO ;
+  uint16 id ;
+  char pack[2] = " \n" ;
+  PROG = "s6-ftrig-wait" ;
+  {
+    unsigned int t = 0 ;
+    for (;;)
+    {
+      register int opt = subgetopt(argc, argv, "t:") ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 't' : if (uint0_scan(subgetopt_here.arg, &t)) break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    if (t) tain_from_millisecs(&tto, t) ;
+    else tto = tain_infinite_relative ;
+    argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ;
+  }
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+
+  tain_now_g() ;
+  tain_add_g(&deadline, &tto) ;
+
+  if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ;
+  id = ftrigr_subscribe_g(&a, argv[0], argv[1], 0, &deadline) ;
+  if (!id) strerr_diefu4sys(111, "subscribe to ", argv[0], " with regexp ", argv[1]) ;
+  if (ftrigr_wait_or_g(&a, &id, 1, &deadline, &pack[0]) == -1)
+    strerr_diefu2sys((errno == ETIMEDOUT) ? 1 : 111, "match regexp on ", argv[1]) ;
+  if (allwrite(1, pack, 2) < 2) strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/pipe-tools/s6-mkfifodir.c b/src/pipe-tools/s6-mkfifodir.c
new file mode 100644
index 0000000..4f5151a
--- /dev/null
+++ b/src/pipe-tools/s6-mkfifodir.c
@@ -0,0 +1,39 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <s6/ftrigw.h>
+
+#define USAGE "s6-mkfifodir [ -f ] [ -g gid ] fifodir"
+
+int main (int argc, char const *const *argv)
+{
+  subgetopt_t l = SUBGETOPT_ZERO ;
+  int gid = -1 ;
+  int force = 0 ;
+  PROG = "s6-mkfifodir" ;
+  for (;;)
+  {
+    register int opt = subgetopt_r(argc, argv, "fg:", &l) ;
+    if (opt == -1) break ;
+    switch (opt)
+    {
+      case 'f' : force = 1 ; break ;
+      case 'g' :
+      {
+        unsigned int g ;
+        if (!uint0_scan(l.arg, &g)) strerr_dieusage(100, USAGE) ;
+        gid = (int)g ;
+        break ;
+      }
+      default : strerr_dieusage(100, USAGE) ;
+    }
+  }
+  argc -= l.ind ; argv += l.ind ;
+  if (argc < 1) strerr_dieusage(100, USAGE) ;
+
+  if (!ftrigw_fifodir_make(*argv, gid, force))
+    strerr_diefu2sys(111, "create fifodir at ", *argv) ;
+  return 0 ;
+}
diff --git a/src/supervision/deps-exe/s6-supervise b/src/supervision/deps-exe/s6-supervise
new file mode 100644
index 0000000..58a34e0
--- /dev/null
+++ b/src/supervision/deps-exe/s6-supervise
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/supervision/deps-exe/s6-svc b/src/supervision/deps-exe/s6-svc
new file mode 100644
index 0000000..83cec1e
--- /dev/null
+++ b/src/supervision/deps-exe/s6-svc
@@ -0,0 +1,2 @@
+-ls6
+-lskarnet
diff --git a/src/supervision/deps-exe/s6-svok b/src/supervision/deps-exe/s6-svok
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/supervision/deps-exe/s6-svok
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/supervision/deps-exe/s6-svscan b/src/supervision/deps-exe/s6-svscan
new file mode 100644
index 0000000..58a34e0
--- /dev/null
+++ b/src/supervision/deps-exe/s6-svscan
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/supervision/deps-exe/s6-svscanctl b/src/supervision/deps-exe/s6-svscanctl
new file mode 100644
index 0000000..83cec1e
--- /dev/null
+++ b/src/supervision/deps-exe/s6-svscanctl
@@ -0,0 +1,2 @@
+-ls6
+-lskarnet
diff --git a/src/supervision/deps-exe/s6-svstat b/src/supervision/deps-exe/s6-svstat
new file mode 100644
index 0000000..7065b26
--- /dev/null
+++ b/src/supervision/deps-exe/s6-svstat
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${SYSCLOCK_LIB}
diff --git a/src/supervision/deps-exe/s6-svwait b/src/supervision/deps-exe/s6-svwait
new file mode 100644
index 0000000..58a34e0
--- /dev/null
+++ b/src/supervision/deps-exe/s6-svwait
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/supervision/s6-supervise.c b/src/supervision/s6-supervise.c
new file mode 100644
index 0000000..f9a9872
--- /dev/null
+++ b/src/supervision/s6-supervise.c
@@ -0,0 +1,508 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/environ.h>
+#include <skalibs/skamisc.h>
+#include <s6/ftrigw.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-supervise dir"
+
+typedef enum trans_e trans_t, *trans_t_ref ;
+enum trans_e
+{
+  V_TIMEOUT, V_CHLD, V_TERM, V_HUP, V_QUIT,
+  V_a, V_b, V_q, V_h, V_k, V_t, V_i, V_1, V_2, V_f, V_F, V_p, V_c,
+  V_o, V_d, V_u, V_x, V_O
+} ;
+
+typedef enum state_e state_t, *state_t_ref ;
+enum state_e
+{
+  DOWN,
+  UP,
+  FINISH,
+  LASTUP,
+  LASTFINISH
+} ;
+
+typedef void action_t (void) ;
+typedef action_t *action_t_ref ;
+
+static tain_t deadline ;
+static s6_svstatus_t status = { .stamp = TAIN_ZERO, .pid = 0, .flagwant = 1, .flagwantup = 1, .flagpaused = 0, .flagfinishing = 0 } ;
+static state_t state = DOWN ;
+static int flagsetsid = 1 ;
+static int cont = 1 ;
+
+static inline void settimeout (int secs)
+{
+  tain_addsec_g(&deadline, secs) ;
+}
+
+static inline void settimeout_infinite (void)
+{
+  tain_add_g(&deadline, &tain_infinite_relative) ;
+}
+
+static inline void announce (void)
+{
+  if (!s6_svstatus_write(".", &status))
+    strerr_warnwu1sys("write status file") ;
+}
+
+
+/* The action array. */
+
+static void nop (void)
+{
+}
+
+static void bail (void)
+{
+  cont = 0 ;
+}
+
+static void killa (void)
+{
+  kill(status.pid, SIGALRM) ;
+}
+
+static void killb (void)
+{
+  kill(status.pid, SIGABRT) ;
+}
+
+static void killh (void)
+{
+  kill(status.pid, SIGHUP) ;
+}
+
+static void killq (void)
+{
+  kill(status.pid, SIGQUIT) ;
+}
+
+static void killk (void)
+{
+  kill(status.pid, SIGKILL) ;
+}
+
+static void killt (void)
+{
+  kill(status.pid, SIGTERM) ;
+}
+
+static void killi (void)
+{
+  kill(status.pid, SIGINT) ;
+}
+
+static void kill1 (void)
+{
+  kill(status.pid, SIGUSR1) ;
+}
+
+static void kill2 (void)
+{
+  kill(status.pid, SIGUSR2) ;
+}
+
+static void killp (void)
+{
+  kill(status.pid, SIGSTOP) ;
+  status.flagpaused = 1 ;
+  announce() ;
+}
+
+static void killc (void)
+{
+  kill(status.pid, SIGCONT) ;
+  status.flagpaused = 0 ;
+  announce() ;
+}
+
+static void trystart (void)
+{
+  int p[2] ;
+  pid_t pid ;
+  if (pipecoe(p) < 0)
+  {
+    settimeout(60) ;
+    strerr_warnwu1sys("pipecoe (waiting 60 seconds)") ;
+    return ;
+  }
+  pid = fork() ;
+  if (pid < 0)
+  {
+    settimeout(60) ;
+    strerr_warnwu1sys("fork (waiting 60 seconds)") ;
+    fd_close(p[1]) ; fd_close(p[0]) ;
+    return ;
+  }
+  else if (!pid)
+  {
+    char const *cargv[2] = { "run", 0 } ;
+    PROG = "s6-supervise (child)" ;
+    selfpipe_finish() ;
+    fd_close(p[0]) ;
+    if (flagsetsid) setsid() ;
+    execve("./run", (char *const *)cargv, (char *const *)environ) ;
+    fd_write(p[1], "", 1) ;
+    strerr_dieexec(111, "run") ;
+  }
+  fd_close(p[1]) ;
+  {
+    char c ;
+    switch (fd_read(p[0], &c, 1))
+    {
+      case -1 :
+        fd_close(p[0]) ;
+        settimeout(60) ;
+        strerr_warnwu1sys("read pipe (waiting 60 seconds)") ;
+        kill(pid, SIGKILL) ;
+        return ;
+      case 1 :
+      {
+        fd_close(p[0]) ;
+        settimeout(10) ;
+        strerr_warnwu1x("spawn ./run - waiting 10 seconds") ;
+        return ;
+      }
+    }
+  }
+  fd_close(p[0]) ;
+  settimeout_infinite() ;
+  state = UP ;
+  status.pid = pid ;
+  tain_copynow(&status.stamp) ;
+  announce() ;
+  ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'u') ;
+}
+
+static void downtimeout (void)
+{
+  if (status.flagwant && status.flagwantup) trystart() ;
+  else settimeout_infinite() ;
+}
+
+static void down_O (void)
+{
+  status.flagwant = 0 ;
+  announce() ;
+}
+
+static void down_o (void)
+{
+  down_O() ;
+  trystart() ;
+}
+
+static void down_u (void)
+{
+  status.flagwant = 1 ;
+  status.flagwantup = 1 ;
+  announce() ;
+  trystart() ;
+}
+
+static void down_d (void)
+{
+  status.flagwant = 1 ;
+  status.flagwantup = 0 ;
+  announce() ;
+}
+
+static void tryfinish (int wstat, int islast)
+{
+  register pid_t pid = fork() ;
+  if (pid < 0)
+  {
+    strerr_warnwu2sys("fork for ", "./finish") ;
+    if (islast) bail() ;
+    state = DOWN ;
+    status.pid = 0 ;
+    settimeout(1) ;
+    return ;
+  }
+  else if (!pid)
+  {
+    char fmt0[UINT_FMT] ;
+    char fmt1[UINT_FMT] ;
+    char *cargv[4] = { "finish", fmt0, fmt1, 0 } ;
+    selfpipe_finish() ;
+    fmt0[uint_fmt(fmt0, WIFSIGNALED(wstat) ? 255 : WEXITSTATUS(wstat))] = 0 ;
+    fmt1[uint_fmt(fmt1, WIFSIGNALED(wstat))] = 0 ;
+    if (flagsetsid) setsid() ;
+    execve("./finish", cargv, (char *const *)environ) ;
+    _exit(111) ;
+  }
+  status.pid = pid ;
+  status.flagfinishing = 1 ;
+  state = islast ? LASTFINISH : FINISH ;
+  settimeout(5) ;
+}
+
+static void uptimeout (void)
+{
+  settimeout_infinite() ;
+  strerr_warnw1x("can't happen: timeout while the service is up!") ;
+}
+
+static void up_z (void)
+{
+  int wstat = status.pid ;
+  status.pid = 0 ;
+  tain_copynow(&status.stamp) ;
+  announce() ;
+  ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'd') ;
+  tryfinish(wstat, 0) ;
+}
+
+static void up_o (void)
+{
+  status.flagwant = 0 ;
+  announce() ;
+}
+
+static void up_d (void)
+{
+  status.flagwant = 1 ;
+  status.flagwantup = 0 ;
+  killt() ;
+  killc() ;
+}
+
+static void up_u (void)
+{
+  status.flagwant = 1 ;
+  status.flagwantup = 1 ;
+  announce() ;
+}
+
+static void closethem (void)
+{
+  fd_close(0) ;
+  fd_close(1) ;
+  open_read("/dev/null") ;
+  open_write("/dev/null") ;
+}
+
+static void up_x (void)
+{
+  state = LASTUP ;
+  closethem() ;
+}
+
+static void up_term (void)
+{
+  up_x() ;
+  up_d() ;
+}
+
+static void finishtimeout (void)
+{
+  strerr_warnw1x("finish script takes too long - killing it") ;
+  killc() ; killk() ;
+  settimeout(3) ;
+}
+
+static void finish_z (void)
+{
+  status.pid = 0 ;
+  status.flagfinishing = 0 ;
+  state = DOWN ;
+  announce() ;
+  settimeout(1) ;
+}
+
+static void finish_u (void)
+{
+  status.flagwant = 1 ;
+  status.flagwantup = 1 ;
+  announce() ;
+}
+
+static void finish_x (void)
+{
+  state = LASTFINISH ;
+  closethem() ;
+}
+
+static void lastup_z (void)
+{
+  int wstat = status.pid ;
+  status.pid = 0 ;
+  tain_copynow(&status.stamp) ;
+  announce() ;
+  ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'd') ;
+  tryfinish(wstat, 1) ;
+}
+
+static action_t_ref const actions[5][23] =
+{
+  { &downtimeout, &nop, &bail, &bail, &bail,
+    &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop,
+    &down_o, &down_d, &down_u, &bail, &down_O },
+  { &uptimeout, &up_z, &up_term, &up_x, &up_term,
+    &killa, &killb, &killq, &killh, &killk, &killt, &killi, &kill1, &kill2, &nop, &nop, &killp, &killc,
+    &up_o, &up_d, &up_u, &up_x, &up_o },
+  { &finishtimeout, &finish_z, &finish_x, &finish_x, &finish_x,
+    &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop,
+    &up_o, &down_d, &finish_u, &finish_x, &up_o },
+  { &uptimeout, &lastup_z, &up_d, &nop, &up_d,
+    &killa, &killb, &killq, &killh, &killk, &killt, &killi, &kill1, &kill2, &nop, &nop, &killp, &killc,
+    &up_o, &up_d, &nop, &nop, &up_o },
+  { &finishtimeout, &bail, &nop, &nop, &nop,
+    &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop, &nop,
+    &nop, &nop, &nop, &nop, &nop }
+} ;
+
+
+/* The main loop.
+   It just loops around the iopause(), calling snippets of code in "actions" when needed. */
+
+
+static void handle_signals (void)
+{
+  for (;;)
+  {
+    char c = selfpipe_read() ;
+    switch (c)
+    {
+      case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+      case 0 : return ;
+      case SIGCHLD :
+        if (!status.pid) wait_reap() ;
+        else
+        {
+          int wstat ;
+          int r = wait_pid_nohang(status.pid, &wstat) ;
+          if (r < 0)
+            if (errno != ECHILD) strerr_diefu1sys(111, "wait_pid_nohang") ;
+            else break ;
+          else if (!r) break ;
+          status.pid = wstat ;
+          (*actions[state][V_CHLD])() ;
+        }
+        break ;
+      case SIGTERM :
+        (*actions[state][V_TERM])() ;
+        break ;
+      case SIGHUP :
+        (*actions[state][V_HUP])() ;
+        break ;
+      case SIGQUIT :
+        (*actions[state][V_QUIT])() ;
+        break ;
+      default :
+        strerr_dief1x(101, "internal error: inconsistent signal state. Please submit a bug-report.") ;
+    }
+  }
+}
+
+static void handle_control (int fd)
+{
+  for (;;)
+  {
+    char c ;
+    register int r = sanitize_read(fd_read(fd, &c, 1)) ;
+    if (r < 0) strerr_diefu1sys(111, "read " S6_SUPERVISE_CTLDIR "/control") ;
+    else if (!r) break ;
+    else
+    {
+      register unsigned int pos = byte_chr("abqhkti12fFpcoduxO", 18, c) ;
+      if (pos < 18) (*actions[state][V_a + pos])() ;
+    }
+  }
+}
+
+int main (int argc, char const *const *argv)
+{
+  iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ;
+  PROG = "s6-supervise" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if (chdir(argv[1]) < 0) strerr_diefu2sys(111, "chdir to ", argv[1]) ;
+  {
+    register unsigned int proglen = str_len(PROG) ;
+    register unsigned int namelen = str_len(argv[1]) ;
+    char progname[proglen + namelen + 2] ;
+    byte_copy(progname, proglen, PROG) ;
+    progname[proglen] = ' ' ;
+    byte_copy(progname + proglen + 1, namelen + 1, argv[1]) ;
+    PROG = progname ;
+    if (!fd_sanitize()) strerr_diefu1sys(111, "sanitize stdin and stdout") ;
+    x[1].fd = s6_supervise_lock(S6_SUPERVISE_CTLDIR) ;
+    if (!ftrigw_fifodir_make(S6_SUPERVISE_EVENTDIR, -1, 0))
+      strerr_diefu2sys(111, "mkfifodir ", S6_SUPERVISE_EVENTDIR) ;
+    x[0].fd = selfpipe_init() ;
+    if (x[0].fd == -1) strerr_diefu1sys(111, "init selfpipe") ;
+    if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+    {
+      sigset_t set ;
+      sigemptyset(&set) ;
+      sigaddset(&set, SIGTERM) ;
+      sigaddset(&set, SIGHUP) ;
+      sigaddset(&set, SIGQUIT) ;
+      sigaddset(&set, SIGCHLD) ;
+      if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ;
+    }
+    
+    if (!ftrigw_clean(S6_SUPERVISE_EVENTDIR))
+      strerr_warnwu2sys("ftrigw_clean ", S6_SUPERVISE_EVENTDIR) ;
+
+    {
+      struct stat st ;
+      if (stat("down", &st) == -1)
+      {
+        if (errno != ENOENT)
+          strerr_diefu1sys(111, "stat down") ;
+      }
+      else status.flagwantup = 0 ;
+      if (stat("nosetsid", &st) == -1)
+      {
+        if (errno != ENOENT)
+          strerr_diefu1sys(111, "stat nosetsid") ;
+      }
+      else flagsetsid = 0 ;
+    }
+
+    tain_now_g() ;
+    settimeout(0) ;
+    tain_copynow(&status.stamp) ;
+    announce() ;
+    ftrigw_notify(S6_SUPERVISE_EVENTDIR, 's') ;
+
+    while (cont)
+    {
+      register int r = iopause_g(x, 2, &deadline) ;
+      if (r < 0) strerr_diefu1sys(111, "iopause") ;
+      else if (!r) (*actions[state][V_TIMEOUT])() ;
+      else
+      {
+        if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT)
+          strerr_diefu1x(111, "iopause: trouble with pipes") ;
+        if (x[0].revents & IOPAUSE_READ) handle_signals() ;
+        else if (x[1].revents & IOPAUSE_READ) handle_control(x[1].fd) ;
+      }
+    }
+
+    ftrigw_notify(S6_SUPERVISE_EVENTDIR, 'x') ;
+  }
+  return 0 ;
+}
diff --git a/src/supervision/s6-svc.c b/src/supervision/s6-svc.c
new file mode 100644
index 0000000..699eefd
--- /dev/null
+++ b/src/supervision/s6-svc.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-svc [ -abqhkti12fFpcoduxO ] servicedir"
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-svc" ;
+  return s6_svc_main(argc, argv, "abqhkti12fFpcoduxO", USAGE, "supervise") ;
+}
diff --git a/src/supervision/s6-svok.c b/src/supervision/s6-svok.c
new file mode 100644
index 0000000..4a615e9
--- /dev/null
+++ b/src/supervision/s6-svok.c
@@ -0,0 +1,32 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-svok servicedir"
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-svok" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  argv++ ; argc-- ;
+  {
+    int fd ;
+    unsigned int dirlen = str_len(*argv) ;
+    char fn[dirlen + 9 + sizeof(S6_SUPERVISE_CTLDIR)] ;
+    byte_copy(fn, dirlen, *argv) ;
+    fn[dirlen] = '/' ;
+    byte_copy(fn + dirlen + 1, sizeof(S6_SUPERVISE_CTLDIR) - 1, S6_SUPERVISE_CTLDIR) ;
+    byte_copy(fn + dirlen + sizeof(S6_SUPERVISE_CTLDIR), 9, "/control") ;
+    fd = open_write(fn) ;
+    if (fd < 0)
+    {
+      if ((errno == ENXIO) || (errno == ENOENT)) return 1 ;
+      else strerr_diefu2sys(111, "open_write ", fn) ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/supervision/s6-svscan.c b/src/supervision/s6-svscan.c
new file mode 100644
index 0000000..8b0f82e
--- /dev/null
+++ b/src/supervision/s6-svscan.c
@@ -0,0 +1,498 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/direntry.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/environ.h>
+#include <s6/config.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-svscan [ -c maxservices ] [ -t timeout ] [ dir ]"
+
+#define FINISH_PROG S6_SVSCAN_CTLDIR "/finish"
+#define CRASH_PROG S6_SVSCAN_CTLDIR "/crash"
+
+#define DIR_RETRY_TIMEOUT 3
+#define CHECK_RETRY_TIMEOUT 4
+
+struct svinfo
+{
+  dev_t dev ;
+  ino_t ino ;
+  tain_t restartafter[2] ;
+  int pid[2] ;
+  int p[2] ;
+  unsigned int flagactive : 1 ;
+  unsigned int flaglog : 1 ;
+} ;
+#define SVINFO_ZERO { -1, -1, { TAIN_ZERO, TAIN_ZERO }, { 0, 0 }, { -1, -1 }, 0, 0, 0 } ;
+
+static struct svinfo *services ;
+static unsigned int max = 500 ;
+static unsigned int n = 0 ;
+static tain_t deadline, defaulttimeout ;
+static char const *finish_arg = "reboot" ;
+static int wantreap = 1 ;
+static int wantscan = 1 ;
+static unsigned int wantkill = 0 ;
+static int cont = 1 ;
+
+static void panicnosp (char const *) gccattr_noreturn ;
+static void panicnosp (char const *errmsg)
+{
+  char const *eargv[2] = { CRASH_PROG, 0 } ;
+  strerr_warnwu1sys(errmsg) ;
+  strerr_warnw2x("executing into ", eargv[0]) ;
+  execve(eargv[0], (char *const *)eargv, (char *const *)environ) ;
+ /* and if that execve fails, screw it and just die */
+  strerr_dieexec(111, eargv[0]) ;
+}
+
+static void panic (char const *) gccattr_noreturn ;
+static void panic (char const *errmsg)
+{
+  int e = errno ;
+  selfpipe_finish() ;
+  errno = e ;
+  panicnosp(errmsg) ;
+}
+
+static void killthem (void)
+{
+  register unsigned int i = 0 ;
+  if (!wantkill) return ;
+  for (; i < n ; i++)
+  {
+    if (!(wantkill & 1) && services[i].flagactive) continue ;
+    if (services[i].pid[0])
+      kill(services[i].pid[0], (wantkill & 2) ? SIGTERM : SIGHUP) ;
+    if (services[i].flaglog && services[i].pid[1])
+      kill(services[i].pid[1], (wantkill & 4) ? SIGTERM : SIGHUP) ;
+  }
+  wantkill = 0 ;
+}
+
+static void term (void)
+{
+  cont = 0 ;
+  wantkill = 3 ;
+}
+
+static void hup (void)
+{
+  cont = 0 ;
+  wantkill = 1 ;
+}
+
+static void quit (void)
+{
+  cont = 0 ;
+  wantkill = 7 ;
+}
+
+static void intr (void)
+{
+  finish_arg = "reboot" ;
+  term() ;
+}
+
+static void handle_signals (void)
+{
+  for (;;)
+  {
+    switch (selfpipe_read())
+    {
+      case -1 : panic("selfpipe_read") ;
+      case 0 : return ;
+      case SIGCHLD : wantreap = 1 ; break ;
+      case SIGALRM : wantscan = 1 ; break ;
+      case SIGTERM : term() ; break ;
+      case SIGHUP : hup() ; break ;
+      case SIGQUIT : quit() ; break ;
+      case SIGABRT : cont = 0 ; break ;
+      case SIGINT : intr() ; break ;
+    }
+  }
+}
+
+static void handle_control (int fd)
+{
+  for (;;)
+  {
+    char c ;
+    int r = sanitize_read(fd_read(fd, &c, 1)) ;
+    if (r == -1) panic("read control pipe") ;
+    else if (!r) break ;
+    else switch (c)
+    {
+      case 'p' : finish_arg = "poweroff" ; break ;
+      case 'h' : hup() ; return ;
+      case 'r' : finish_arg = "reboot" ; break ;
+      case 'a' : wantscan = 1 ; break ;
+      case 't' : term() ; return ;
+      case 's' : finish_arg = "halt" ; break ;
+      case 'z' : wantreap = 1 ; break ;
+      case 'b' : cont = 0 ; return ;
+      case 'n' : wantkill = 2 ; break ;
+      case 'N' : wantkill = 6 ; break ;
+      case '6' :
+      case 'i' : intr() ; return ;
+      case 'q' : quit() ; return ;
+      case '0' : finish_arg = "halt" ; term() ; return ;
+      case '7' : finish_arg = "poweroff" ; term() ; return ;
+      case '8' : finish_arg = "other" ; term() ; return ;
+      default :
+      {
+        char s[2] = { c, 0 } ;
+        strerr_warnw2x("received unknown control command: ", s) ;
+      }
+    }
+  }
+}
+
+
+/* First essential function: the reaper.
+   s6-svscan must wait() for all children,
+   including ones it doesn't know it has.
+   Dead active services are flagged to be restarted in 1 second. */
+
+static void reap (void)
+{
+  tain_t nextscan ;
+  if (!wantreap) return ;
+  wantreap = 0 ;
+  tain_addsec_g(&nextscan, 1) ;
+  for (;;)
+  {
+    int wstat ;
+    int r = wait_nohang(&wstat) ;
+    if (r < 0)
+      if (errno != ECHILD) panic("wait_nohang") ;
+      else break ;
+    else if (!r) break ;
+    else
+    {
+      register unsigned int i = 0 ;
+      for (; i < n ; i++)
+      {
+        if (services[i].pid[0] == r)
+        {
+          services[i].pid[0] = 0 ;
+          services[i].restartafter[0] = nextscan ;
+          break ;
+        }
+        else if (services[i].pid[1] == r)
+        {
+          services[i].pid[1] = 0 ;
+          services[i].restartafter[1] = nextscan ;
+          break ;
+        }
+      }
+      if (i == n) continue ;
+      if (services[i].flagactive)
+      {
+        if (tain_less(&nextscan, &deadline)) deadline = nextscan ;
+      }
+      else
+      {
+        if (services[i].flaglog)
+        {
+ /*
+    BLACK MAGIC:
+     - we need to close the pipe early:
+       * as soon as the writer exits so the logger can exit on EOF
+       * or as soon as the logger exits so the writer can crash on EPIPE
+     - but if the same service gets reactivated before the second
+       supervise process exits, ouch: we've lost the pipe
+     - so we can't reuse the same service even if it gets reactivated
+     - so we're marking a dying service with a closed pipe
+     - if the scanner sees a service with p[0] = -1 it won't flag
+       it as active (and won't restart the dead supervise)
+     - but if the service gets reactivated we want it to restart
+       as soon as the 2nd supervise process dies
+     - so the scanner marks such a process with p[0] = -2
+     - and the reaper triggers a scan when it finds a -2.
+ */
+          if (services[i].p[0] >= 0)
+          {
+            fd_close(services[i].p[1]) ; services[i].p[1] = -1 ;
+            fd_close(services[i].p[0]) ; services[i].p[0] = -1 ;
+          }
+          else if (services[i].p[0] == -2) wantscan = 1 ;
+        }
+        if (!services[i].pid[0] && !services[i].pid[1])
+          services[i] = services[--n] ;
+      }
+    }
+  }
+}
+
+
+/* Second essential function: the scanner.
+   It monitors the service directories and spawns a supervisor
+   if needed. */
+
+static void trystart (unsigned int i, char const *name, int islog)
+{
+  int pid = fork() ;
+  switch (pid)
+  {
+    case -1 :
+      tain_addsec_g(&services[i].restartafter[islog], CHECK_RETRY_TIMEOUT) ;
+      strerr_warnwu2sys("fork for ", name) ;
+      return ;
+    case 0 :
+    {
+      char const *cargv[3] = { "s6-supervise", name, 0 } ;
+      PROG = "s6-svscan (child)" ;
+      selfpipe_finish() ;
+      if (services[i].flaglog)
+        if (fd_move(!islog, services[i].p[!islog]) == -1)
+          strerr_diefu2sys(111, "set fds for ", name) ;
+      pathexec_run(S6_BINPREFIX "s6-supervise", cargv, (char const **)environ) ;
+      strerr_dieexec(111, S6_BINPREFIX "s6-supervise") ;
+    }
+  }
+  services[i].pid[islog] = pid ;
+}
+
+static void retrydirlater (void)
+{
+  tain_t a ;
+  tain_addsec_g(&a, DIR_RETRY_TIMEOUT) ;
+  if (tain_less(&a, &deadline)) deadline = a ;
+}
+
+static void check (char const *name)
+{
+  struct stat st ;
+  unsigned int namelen ;
+  unsigned int i = 0 ;
+  if (name[0] == '.') return ;
+  if (stat(name, &st) == -1)
+  {
+    strerr_warnwu2sys("stat ", name) ;
+    retrydirlater() ;
+    return ;
+  }
+  if (!S_ISDIR(st.st_mode)) return ;
+  namelen = str_len(name) ;
+  for (; i < n ; i++) if ((services[i].ino == st.st_ino) && (services[i].dev == st.st_dev)) break ;
+  if (i < n)
+  {
+    if (services[i].flaglog && (services[i].p[0] < 0))
+    {
+     /* See BLACK MAGIC above. */
+      services[i].p[0] = -2 ;
+      return ;
+    }
+  }
+  else
+  {
+    if (n >= max)
+    {
+      strerr_warnwu3x("start supervisor for ", name, ": too many services") ;
+      return ;
+    }
+    else
+    {
+      struct stat su ;
+      char tmp[namelen + 5] ;
+      byte_copy(tmp, namelen, name) ;
+      byte_copy(tmp + namelen, 5, "/log") ;
+      if (stat(tmp, &su) < 0)
+        if (errno == ENOENT) services[i].flaglog = 0 ;
+        else
+        {
+          strerr_warnwu2sys("stat ", tmp) ;
+          retrydirlater() ;
+          return ;
+        }
+      else if (!S_ISDIR(su.st_mode))
+        services[i].flaglog = 0 ;
+      else
+      {
+        if (pipecoe(services[i].p) < 0)
+        {
+          strerr_warnwu1sys("pipecoe") ;
+          retrydirlater() ;
+          return ;
+        }
+        services[i].flaglog = 1 ;
+      }
+      services[i].ino = st.st_ino ;
+      services[i].dev = st.st_dev ;
+      tain_copynow(&services[i].restartafter[0]) ;
+      tain_copynow(&services[i].restartafter[1]) ;
+      services[i].pid[0] = 0 ;
+      services[i].pid[1] = 0 ;
+      n++ ;
+    }
+  }
+  
+  services[i].flagactive = 1 ;
+
+  if (services[i].flaglog && !services[i].pid[1])
+  {
+    if (!tain_future(&services[i].restartafter[1]))
+    {
+      char tmp[namelen + 5] ;
+      byte_copy(tmp, namelen, name) ;
+      byte_copy(tmp + namelen, 5, "/log") ;
+      trystart(i, tmp, 1) ;
+    }
+    else if (tain_less(&services[i].restartafter[1], &deadline))
+      deadline = services[i].restartafter[1] ;
+  }
+
+  if (!services[i].pid[0])
+  {
+    if (!tain_future(&services[i].restartafter[0]))
+      trystart(i, name, 0) ;
+    else if (tain_less(&services[i].restartafter[0], &deadline))
+      deadline = services[i].restartafter[0] ;
+  }
+}
+
+static void scan (void)
+{
+  DIR *dir ;
+  if (!wantscan) return ;
+  wantscan = 0 ;
+  dir = opendir(".") ;
+  if (!dir)
+  {
+    strerr_warnwu1sys("opendir .") ;
+    retrydirlater() ;
+    return ;
+  }
+  {
+    register unsigned int i = 0 ;
+    for (; i < n ; i++) services[i].flagactive = 0 ;
+  }
+  for (;;)
+  {
+    direntry *d ;
+    errno = 0 ;
+    d = readdir(dir) ;
+    if (!d) break ;
+    check(d->d_name) ;
+  }
+  if (errno)
+  {
+    strerr_warnwu1sys("readdir .") ;
+    retrydirlater() ;
+  }
+  dir_close(dir) ;
+}
+
+
+int main (int argc, char const *const *argv)
+{
+  iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ;
+  PROG = "s6-svscan" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    unsigned int t = 5000 ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "t:c:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 't' : if (uint0_scan(l.arg, &t)) break ;
+        case 'c' : if (uint0_scan(l.arg, &max)) break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+    if (t) tain_from_millisecs(&defaulttimeout, t) ;
+    else defaulttimeout = tain_infinite_relative ;
+    if (max < 2) max = 2 ;
+  }
+
+  /* Init phase.
+     If something fails here, we can die, because it means that
+     something is seriously wrong with the system, and we can't
+     run correctly anyway.
+  */
+
+  if (argc && (chdir(argv[0]) < 0)) strerr_diefu1sys(111, "chdir") ;
+  x[1].fd = s6_supervise_lock(S6_SVSCAN_CTLDIR) ;
+  x[0].fd = selfpipe_init() ;
+  if (x[0].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ;
+
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+  {
+    sigset_t set ;
+    sigemptyset(&set) ;
+    sigaddset(&set, SIGCHLD) ;
+    sigaddset(&set, SIGALRM) ;
+    sigaddset(&set, SIGTERM) ;
+    sigaddset(&set, SIGHUP) ;
+    sigaddset(&set, SIGQUIT) ;
+    sigaddset(&set, SIGABRT) ;
+    sigaddset(&set, SIGINT) ;
+    if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ;
+  }
+
+
+  {
+    struct svinfo blob[max] ; /* careful with that stack, Eugene */
+    services = blob ;
+    tain_now_g() ;
+
+
+    /* Loop phase.
+       From now on, we must not die.
+       Temporize on recoverable errors, and panic on serious ones. */
+
+    while (cont)
+    {
+      int r ;
+      tain_add_g(&deadline, &defaulttimeout) ;
+      reap() ;
+      scan() ;
+      killthem() ;
+      r = iopause_g(x, 2, &deadline) ;
+      if (r < 0) panic("iopause") ;
+      else if (!r) wantscan = 1 ;
+      else
+      {
+        if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT)
+        {
+          errno = EIO ;
+          panic("check internal pipes") ;
+        }
+        if (x[0].revents & IOPAUSE_READ) handle_signals() ;
+        if (x[1].revents & IOPAUSE_READ) handle_control(x[1].fd) ;
+      }
+    }
+
+
+    /* Finish phase. */
+
+    selfpipe_finish() ;
+    killthem() ;
+    reap() ;
+  }
+  {
+    char const *eargv[3] = { FINISH_PROG, finish_arg, 0 } ;
+    execve(eargv[0], (char **)eargv, (char *const *)environ) ;
+  }
+  panicnosp("exec finish script " FINISH_PROG) ;
+}
diff --git a/src/supervision/s6-svscanctl.c b/src/supervision/s6-svscanctl.c
new file mode 100644
index 0000000..48e6420
--- /dev/null
+++ b/src/supervision/s6-svscanctl.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-svscanctl [ -phratszbnNiq0678 ] svscandir"
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-svscanctl" ;
+  return s6_svc_main(argc, argv, "phratszbnNiq0678", USAGE, ".s6-svscan") ;
+}
diff --git a/src/supervision/s6-svstat.c b/src/supervision/s6-svstat.c
new file mode 100644
index 0000000..c986b8d
--- /dev/null
+++ b/src/supervision/s6-svstat.c
@@ -0,0 +1,70 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <skalibs/uint64.h>
+#include <skalibs/uint.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-svstat servicedir"
+
+int main (int argc, char const *const *argv)
+{
+  s6_svstatus_t status ;
+  char fmt[UINT_FMT] ;
+  int isup, normallyup ;
+  PROG = "s6-svstat" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  argv++ ; argc-- ;
+  if (!s6_svstatus_read(*argv, &status))
+    strerr_diefu2sys(111, "read status for ", *argv) ;
+
+  {
+    struct stat st ;
+    unsigned int dirlen = str_len(*argv) ;
+    char fn[dirlen + 6] ;
+    byte_copy(fn, dirlen, *argv) ;
+    byte_copy(fn + dirlen, 6, "/down") ;
+    if (stat(fn, &st) == -1)
+      if (errno != ENOENT) strerr_diefu2sys(111, "stat ", fn) ;
+      else normallyup = 1 ;
+    else normallyup = 0 ;
+  }
+
+  tain_now_g() ;
+  if (tain_future(&status.stamp)) tain_copynow(&status.stamp) ;
+  tain_sub(&status.stamp, &STAMP, &status.stamp) ;
+
+  isup = status.pid && !status.flagfinishing ;
+  if (isup)
+  {
+    buffer_putnoflush(buffer_1small,"up (pid ", 8) ;
+    buffer_putnoflush(buffer_1small, fmt, uint_fmt(fmt, status.pid)) ;
+    buffer_putnoflush(buffer_1small, ") ", 2) ;
+  }
+  else buffer_putnoflush(buffer_1small, "down ", 5) ;
+
+  buffer_putnoflush(buffer_1small, fmt, uint64_fmt(fmt, status.stamp.sec.x)) ;
+  buffer_putnoflush(buffer_1small," seconds", 8) ;
+
+  if (isup && !normallyup)
+    buffer_putnoflush(buffer_1small, ", normally down", 15) ;
+  if (!isup && normallyup)
+    buffer_putnoflush(buffer_1small, ", normally up", 13) ;
+  if (isup && status.flagpaused)
+    buffer_putnoflush(buffer_1small, ", paused", 8) ;
+  if (!isup && (status.flagwant == 'u'))
+    buffer_putnoflush(buffer_1small, ", want up", 10) ;
+  if (isup && (status.flagwant == 'd'))
+    buffer_putnoflush(buffer_1small, ", want down", 12) ;
+
+  if (buffer_putflush(buffer_1small, "\n", 1) < 0)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/supervision/s6-svwait.c b/src/supervision/s6-svwait.c
new file mode 100644
index 0000000..0d7c96c
--- /dev/null
+++ b/src/supervision/s6-svwait.c
@@ -0,0 +1,104 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint.h>
+#include <skalibs/bitarray.h>
+#include <skalibs/tai.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/iopause.h>
+#include <s6/ftrigr.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-svwait [ -U | -u | -d ] [ -A | -a | -o ] [ -t timeout ] servicedir..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static inline int check (unsigned char const *ba, unsigned int n, int wantup, int or)
+{
+  return (bitarray_first(ba, n, or == wantup) < n) == or ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  tain_t deadline, tto ;
+  ftrigr_t a = FTRIGR_ZERO ;
+  uint32 options = FTRIGR_REPEAT ;
+  int or = 0 ;
+  int wantup = 1 ;
+  char re[4] = "u|d" ;
+  PROG = "s6-svwait" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    unsigned int t = 0 ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "uUdAaot:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'U' : wantup = 1 ; re[0] = 'U' ; break ;
+        case 'u' : wantup = 1 ; re[0] = 'u' ; break ;
+        case 'd' : wantup = 0 ; break ;
+        case 'A' : or = 0 ; options |= FTRIGR_REPEAT ; break ;
+        case 'a' : or = 0 ; options &= ~FTRIGR_REPEAT ; break ;
+        case 'o' : or = 1 ; options &= ~FTRIGR_REPEAT ; break ;
+        case 't' : if (!uint0_scan(l.arg, &t)) dieusage() ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+    if (t) tain_from_millisecs(&tto, t) ; else tto = tain_infinite_relative ;
+  }
+  if (!argc) dieusage() ;
+
+  tain_now_g() ;
+  tain_add_g(&deadline, &tto) ;
+
+  if (!ftrigr_startf_g(&a, &deadline)) strerr_diefu1sys(111, "ftrigr_startf") ;
+
+  {
+    iopause_fd x = { -1, IOPAUSE_READ, 0 } ;
+    unsigned int i = 0 ;
+    uint16 list[argc] ;
+    unsigned char states[bitarray_div8(argc)] ;
+    x.fd = ftrigr_fd(&a) ;
+    for (; i < (unsigned int)argc ; i++)
+    {
+      unsigned int len = str_len(argv[i]) ;
+      char s[len + 1 + sizeof(S6_SUPERVISE_EVENTDIR)] ;
+      byte_copy(s, len, argv[i]) ;
+      s[len] = '/' ;
+      byte_copy(s + len + 1, sizeof(S6_SUPERVISE_EVENTDIR), S6_SUPERVISE_EVENTDIR) ;
+      list[i] = ftrigr_subscribe_g(&a, s, re, options, &deadline) ;
+      if (!list[i]) strerr_diefu2sys(111, "ftrigr_subscribe to ", argv[i]) ;
+    }
+
+    for (i = 0 ; i < (unsigned int)argc ; i++)
+    {
+      s6_svstatus_t st = S6_SVSTATUS_ZERO ;
+      if (!s6_svstatus_read(argv[i], &st)) strerr_diefu1sys(111, "s6_svstatus_read") ;
+      bitarray_poke(states, i, !!st.pid) ;
+    }
+
+    for (;;)
+    {
+      if (check(states, argc, wantup, or)) break ;
+      {
+        register int r = iopause_g(&x, 1, &deadline) ;
+        if (r < 0) strerr_diefu1sys(111, "iopause") ;
+        else if (!r) strerr_dief1x(1, "timed out") ;
+      }
+
+      if (ftrigr_update(&a) < 0) strerr_diefu1sys(111, "ftrigr_update") ;
+      for (i = 0 ; i < (unsigned int)argc ; i++)
+      {
+        char what ;
+        register int r = ftrigr_check(&a, list[i], &what) ;
+        if (r < 0) strerr_diefu1sys(111, "ftrigr_check") ;
+        if (r) bitarray_poke(states, i, what == re[0]) ;
+      } 
+    }
+  }
+  return 0 ;
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..af31259
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,79 @@
+#!/bin/sh -e
+
+. package/info
+
+echo '#'
+echo '# This file has been generated by tools/gen-deps.sh'
+echo '#'
+echo
+
+for dir in src/include/${package} src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.h$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      if test -n "$deps" ; then
+        echo "${dir}/${file}:${deps}"
+      fi
+    }
+  done
+done
+
+for dir in src/* ; do
+  for file in $(ls -1 $dir | grep -- \\.c$) ; do
+    {
+      grep -F -- "#include <${package}/" < ${dir}/$file | cut -d'<' -f2 | cut -d'>' -f1 ;
+      grep -- '#include ".*\.h"' < ${dir}/$file | cut -d'"' -f2
+    } | sort -u | {
+      deps=" ${dir}/$file"
+      while read dep ; do
+        if echo $dep | grep -q "^${package}/" ; then
+          deps="$deps src/include/$dep"
+        elif test -f "${dir}/$dep" ; then
+          deps="$deps ${dir}/$dep"
+        else
+          deps="$deps src/include-local/$dep"
+        fi
+      done
+      o=$(echo $file | sed s/\\.c$/.o/)
+      lo=$(echo $file | sed s/\\.c$/.lo/)
+      echo "${dir}/${o} ${dir}/${lo}:${deps}"
+    }
+  done
+done
+echo
+
+for dir in $(ls -1 src | grep -v ^include) ; do
+  for file in $(ls -1 src/$dir/deps-lib) ; do
+    deps=
+    while read dep ; do
+      deps="$deps src/$dir/$dep"
+    done < src/$dir/deps-lib/$file
+    echo "lib$file.a: $deps"
+    if test -x "src/$dir/deps-lib/$file" ; then
+      echo "lib${file}.so: $(echo "$deps" | sed 's/\.o/.lo/g')"
+    fi
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      deps="$deps $dep"
+    done < src/$dir/deps-exe/$file
+    echo "$file: src/$dir/$file.o$deps"
+  done
+done
diff --git a/tools/install.sh b/tools/install.sh
new file mode 100755
index 0000000..89f9428
--- /dev/null
+++ b/tools/install.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+usage() {
+  echo "usage: $0 [-D] [-l] [-m mode] src dst" 1>&2
+  exit 1
+}
+
+mkdirp=false
+symlink=false
+mode=0755
+
+while getopts Dlm: name ; do
+  case "$name" in
+    D) mkdirp=true ;;
+    l) symlink=true ;;
+    m) mode=$OPTARG ;;
+    ?) usage ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+test "$#" -eq 2 || usage
+src=$1
+dst=$2
+tmp="$dst.tmp.$$"
+
+case "$dst" in
+  */) echo "$0: $dst ends in /" 1>&2 ; exit 1 ;;
+esac
+
+set -C
+set -e
+
+if $mkdirp ; then
+  umask 022
+  case "$2" in
+    */*) mkdir -p "${dst%/*}" ;;
+  esac
+fi
+
+trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
+
+umask 077
+
+if $symlink ; then
+  ln -s "$src" "$tmp"
+else
+  cat < "$1" > "$tmp"
+  chmod "$mode" "$tmp"
+fi
+
+mv -f "$tmp" "$dst"
+if test -d "$dst" ; then
+  rm -f "$dst/$(basename $tmp)"
+  if $symlink ; then
+    mkdir "$tmp"
+    ln -s "$src" "$tmp/$(basename $dst)"
+    mv -f "$tmp/$(basename $dst)" "${dst%/*}"
+    rmdir "$tmp"
+  else
+    echo "$0: $dst is a directory" 1>&2
+    exit 1
+  fi
+fi