about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2014-09-19 02:53:32 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2014-09-19 02:53:32 +0000
commitbea0037dbdd979603fb0b5be8b43f5478c1f6fec (patch)
tree5776ae3af5a3e83d41c7087f70713952b6360988
downloads6-portable-utils-bea0037dbdd979603fb0b5be8b43f5478c1f6fec.tar.gz
s6-portable-utils-bea0037dbdd979603fb0b5be8b43f5478c1f6fec.tar.xz
s6-portable-utils-bea0037dbdd979603fb0b5be8b43f5478c1f6fec.zip
initial commit
-rw-r--r--.gitignore5
-rw-r--r--AUTHORS8
-rw-r--r--CHANGES62
-rw-r--r--COPYING13
-rw-r--r--INSTALL118
-rw-r--r--Makefile121
-rw-r--r--README25
-rw-r--r--README.solaris12
-rwxr-xr-xconfigure383
-rw-r--r--doc/index.html162
-rw-r--r--doc/s6-basename.html50
-rw-r--r--doc/s6-cat.html51
-rw-r--r--doc/s6-chmod.html45
-rw-r--r--doc/s6-chown.html52
-rw-r--r--doc/s6-clock.html54
-rw-r--r--doc/s6-cut.html44
-rw-r--r--doc/s6-dirname.html50
-rw-r--r--doc/s6-echo.html51
-rw-r--r--doc/s6-env.html45
-rw-r--r--doc/s6-expr.html50
-rw-r--r--doc/s6-false.html39
-rw-r--r--doc/s6-format-filter.html42
-rw-r--r--doc/s6-grep.html67
-rw-r--r--doc/s6-head.html55
-rw-r--r--doc/s6-linkname.html45
-rw-r--r--doc/s6-ln.html44
-rw-r--r--doc/s6-ls.html57
-rw-r--r--doc/s6-maximumtime.html53
-rw-r--r--doc/s6-mkdir.html50
-rw-r--r--doc/s6-mkfifo.html44
-rw-r--r--doc/s6-nice.html53
-rw-r--r--doc/s6-nuke.html56
-rw-r--r--doc/s6-pause.html39
-rw-r--r--doc/s6-printenv.html46
-rw-r--r--doc/s6-quote-filter.html56
-rw-r--r--doc/s6-quote.html57
-rw-r--r--doc/s6-rename.html35
-rw-r--r--doc/s6-rmrf.html35
-rw-r--r--doc/s6-sleep.html51
-rw-r--r--doc/s6-sort.html59
-rw-r--r--doc/s6-sync.html32
-rw-r--r--doc/s6-tail.html46
-rw-r--r--doc/s6-test.html53
-rw-r--r--doc/s6-touch.html45
-rw-r--r--doc/s6-true.html39
-rw-r--r--doc/s6-uniquename.html45
-rw-r--r--doc/s6-unquote-filter.html69
-rw-r--r--doc/s6-unquote.html58
-rw-r--r--doc/s6-update-symlinks.html83
-rw-r--r--doc/upgrade.html32
-rw-r--r--package/deps-build1
-rw-r--r--package/deps.mak83
-rw-r--r--package/info4
-rw-r--r--package/modes39
-rw-r--r--package/targets.mak45
-rwxr-xr-xpatch-for-solaris17
-rw-r--r--src/skaembutils/deps-exe/s6-basename1
-rw-r--r--src/skaembutils/deps-exe/s6-cat1
-rw-r--r--src/skaembutils/deps-exe/s6-chmod1
-rw-r--r--src/skaembutils/deps-exe/s6-chown1
-rw-r--r--src/skaembutils/deps-exe/s6-clock2
-rw-r--r--src/skaembutils/deps-exe/s6-cut1
-rw-r--r--src/skaembutils/deps-exe/s6-dirname1
-rw-r--r--src/skaembutils/deps-exe/s6-echo1
-rw-r--r--src/skaembutils/deps-exe/s6-env1
-rw-r--r--src/skaembutils/deps-exe/s6-expr1
-rw-r--r--src/skaembutils/deps-exe/s6-false0
-rw-r--r--src/skaembutils/deps-exe/s6-format-filter1
-rw-r--r--src/skaembutils/deps-exe/s6-grep1
-rw-r--r--src/skaembutils/deps-exe/s6-head1
-rw-r--r--src/skaembutils/deps-exe/s6-linkname1
-rw-r--r--src/skaembutils/deps-exe/s6-ln3
-rw-r--r--src/skaembutils/deps-exe/s6-ls1
-rw-r--r--src/skaembutils/deps-exe/s6-maximumtime2
-rw-r--r--src/skaembutils/deps-exe/s6-mkdir1
-rw-r--r--src/skaembutils/deps-exe/s6-mkfifo1
-rw-r--r--src/skaembutils/deps-exe/s6-nice1
-rw-r--r--src/skaembutils/deps-exe/s6-nuke1
-rw-r--r--src/skaembutils/deps-exe/s6-pause0
-rw-r--r--src/skaembutils/deps-exe/s6-printenv1
-rw-r--r--src/skaembutils/deps-exe/s6-quote1
-rw-r--r--src/skaembutils/deps-exe/s6-quote-filter1
-rw-r--r--src/skaembutils/deps-exe/s6-rename1
-rw-r--r--src/skaembutils/deps-exe/s6-rmrf1
-rw-r--r--src/skaembutils/deps-exe/s6-sleep2
-rw-r--r--src/skaembutils/deps-exe/s6-sort1
-rw-r--r--src/skaembutils/deps-exe/s6-sync0
-rw-r--r--src/skaembutils/deps-exe/s6-tail1
-rw-r--r--src/skaembutils/deps-exe/s6-test1
-rw-r--r--src/skaembutils/deps-exe/s6-touch1
-rw-r--r--src/skaembutils/deps-exe/s6-true0
-rw-r--r--src/skaembutils/deps-exe/s6-uniquename3
-rw-r--r--src/skaembutils/deps-exe/s6-unquote1
-rw-r--r--src/skaembutils/deps-exe/s6-unquote-filter1
-rw-r--r--src/skaembutils/deps-exe/s6-update-symlinks3
-rw-r--r--src/skaembutils/s6-basename.c45
-rw-r--r--src/skaembutils/s6-cat.c12
-rw-r--r--src/skaembutils/s6-chmod.c34
-rw-r--r--src/skaembutils/s6-chown.c60
-rw-r--r--src/skaembutils/s6-clock.c31
-rw-r--r--src/skaembutils/s6-cut.c212
-rw-r--r--src/skaembutils/s6-dirname.c40
-rw-r--r--src/skaembutils/s6-echo.c38
-rw-r--r--src/skaembutils/s6-env.c43
-rw-r--r--src/skaembutils/s6-expr.c211
-rw-r--r--src/skaembutils/s6-false.c6
-rw-r--r--src/skaembutils/s6-format-filter.c61
-rw-r--r--src/skaembutils/s6-grep.c136
-rw-r--r--src/skaembutils/s6-head.c159
-rw-r--r--src/skaembutils/s6-linkname.c45
-rw-r--r--src/skaembutils/s6-ln.c145
-rw-r--r--src/skaembutils/s6-ls.c56
-rw-r--r--src/skaembutils/s6-maximumtime.c99
-rw-r--r--src/skaembutils/s6-mkdir.c79
-rw-r--r--src/skaembutils/s6-mkfifo.c36
-rw-r--r--src/skaembutils/s6-nice.c46
-rw-r--r--src/skaembutils/s6-nuke.c50
-rw-r--r--src/skaembutils/s6-pause.c9
-rw-r--r--src/skaembutils/s6-printenv.c51
-rw-r--r--src/skaembutils/s6-quote-filter.c72
-rw-r--r--src/skaembutils/s6-quote.c58
-rw-r--r--src/skaembutils/s6-rename.c16
-rw-r--r--src/skaembutils/s6-rmrf.c17
-rw-r--r--src/skaembutils/s6-sleep.c46
-rw-r--r--src/skaembutils/s6-sort.c123
-rw-r--r--src/skaembutils/s6-sync.c9
-rw-r--r--src/skaembutils/s6-tail.c200
-rw-r--r--src/skaembutils/s6-test.c515
-rw-r--r--src/skaembutils/s6-touch.c20
-rw-r--r--src/skaembutils/s6-true.c6
-rw-r--r--src/skaembutils/s6-uniquename.c40
-rw-r--r--src/skaembutils/s6-unquote-filter.c198
-rw-r--r--src/skaembutils/s6-unquote.c70
-rw-r--r--src/skaembutils/s6-update-symlinks.c367
-rwxr-xr-xtools/gen-deps.sh79
-rwxr-xr-xtools/install.sh64
136 files changed, 6723 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..d2df44b
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,8 @@
+Main author:
+  Laurent Bercot <ska-skaware@skarnet.org>
+
+Thanks to:
+  Dan J. Bernstein <djb@cr.yp.to>
+  Jean Marot <jean.marot@salle-s.org>
+  Jorge Almeida <jalmeida@math.ist.utl.pt>
+  Vallo Kallaste <kalts@estpak.ee>
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..9c67f65
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,62 @@
+20110701
+	version: 0.10
+20110809
+	bug: s6-head didn't handle chars properly
+	  fix: rewrote variable management
+	version: 0.11
+20110822
+	bug: s6-pause was incorrectly linked with libstddjb
+	  impact: did not run when linked with libstddjb.so because of PROG
+	  fix: :>deps-exe/s6-pause
+	version: 0.12
+20110929
+	code: added s6-memoryhog
+	version: 0.13
+20111108
+	bug: s6-ln.c did not define _ATFILE_SOURCE when needed
+	  fix: trivial
+	version: 0.14
+20120110
+	bug: s6-tail did not work with non-positive lines
+	  fix: trivial
+	version: 0.15
+20120215
+	internal: changed all bools from unsigned char to int
+20120220
+	version: 0.16
+20120327
+	bug: potential crash (and wrong results) with s6-tail -c n when input is < n chars
+	  impact: m( How many bugs can I leave in those stupid utilities ?
+	  fix: added guard so n is never greater than the input length
+	version: 0.17
+20120929
+	code: s6-update-symlinks accepts trailing slashes in dst
+	doc: fixed s6-update-symlinks.html
+20130206
+	build: switched to non-sp supporting build
+20130212
+	version: 1.0.0
+20130518
+	bug: some binaries didn't link with flag-usert
+	  fix: add `cat taianow.lib` to their deps-obj
+	version: 1.0.1
+20130913
+	internal: removed deprecated calls
+20130926
+	version: 1.0.2
+20140327
+	bug: s6-mkfifo respected umask
+	  fix: add umask(0111)
+	version: 1.0.3
+20140409
+	bug: segfault in s6-cut when passing dash filenames
+	  fix: trivial
+20140411
+	build: changed to 4-number versioning
+	deps: moved to skalibs-1.6.0.0
+20140514
+	version: 1.0.3.1
+20140604
+	bug: wrong libexec directory in package/export
+	  fix: trivial
+	version: 1.0.3.2
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..f81ab06
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,118 @@
+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/
+
+ 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.
+
+ You can strip the binaries 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.
+
+
+* 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/)
+
+
+* 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..49d52da
--- /dev/null
+++ b/README
@@ -0,0 +1,25 @@
+s6-portable-utils - tiny general Unix utilities
+-----------------------------------------------
+
+s6-portable-utils is a set of tiny general Unix utilities,
+often performing well-known tasks such as cut and grep
+ but optimized for simplicity and small size. They were
+designed for embedded systems and other constrained
+environments, but they work everywhere.
+
+ See http://skarnet.org/software/s6-portable-utils/ 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-portable-utils.
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..552373d
--- /dev/null
+++ b/configure
@@ -0,0 +1,383 @@
+#!/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:
+  --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
+sp_root := $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/index.html b/doc/index.html
new file mode 100644
index 0000000..1775002
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,162 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils - skarnet.org's tiny general Unix utilities</title>
+    <meta name="Description" content="s6-portable-utils - skarnet.org's tiny general Unix utilities" />
+    <meta name="Keywords" content="s6 unix administration root skarnet portable utilities tiny coreutils" />
+    <!-- <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-portable-utils </h1>
+
+<ul>
+<li><a href="install.html">How to install s6-portable-utils</a></li>
+<li><a href="upgrade.html">Upgrading from previous versions of s6-portable-utils</a></li>
+</ul>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ s6-portable-utils is a set of tiny general Unix utilities, often
+performing well-known tasks such as <em>cut</em> and <em>grep</em>,
+but optimized for simplicity and small size. They were designed
+for embedded systems and other constrained environments, but they
+work everywhere.
+</p>
+
+<p>
+ Other set of small utilities are usually system-specific; for
+instance, the (otherwise excellent)
+<a href="http://busybox.net/">BusyBox</a> project only works on Linux.
+</p>
+
+<p>
+ Some of s6-portable-utils' programs are a conformant implementation
+of a POSIX utility as determined by the
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/nfindex.html">Single
+Unix Specification, version 4</a>; the relevant documentation pages mentions
+this.
+However, none of the s6-portable-utils programs supports internationalization.
+</p>
+
+<p>
+It is now an explicit non-goal of s6-portable-utils to duplicate the work of
+existing projects that aim to provide a lightweight implementation of
+standard commands. 
+ No more rewriting of standard commands will occur in s6-portable-utils;
+the package is now used to host specific utilities such as
+<a href="s6-uniquename.html"><tt>s6-uniquename</tt></a>.
+</p>
+
+<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>
+</ul>
+
+<h3> Licensing </h3>
+
+<p>
+ s6-portable-utils 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-portable-utils is
+<a href="s6-portable-utils-2.0.0.0.tar.gz">2.0.0.0</a>. </li>
+ <li> Alternatively, you can checkout a copy of the s6-portable-utils git repository:
+<pre> git clone git://git.skarnet.org/s6-portable-utils </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 s6-portable-utils 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, and
+100 if they encounter a permanent error - such as a misuse.
+</p>
+
+<ul>
+ <li> The <a href="s6-basename.html"><tt>s6-basename</tt></a> program </li>
+ <li> The <a href="s6-cat.html"><tt>s6-cat</tt></a> program </li>
+ <li> The <a href="s6-chmod.html"><tt>s6-chmod</tt></a> program </li>
+ <li> The <a href="s6-chown.html"><tt>s6-chown</tt></a> program </li>
+ <li> The <a href="s6-clock.html"><tt>s6-clock</tt></a> program </li>
+ <li> The <a href="s6-cut.html"><tt>s6-cut</tt></a> program </li>
+ <li> The <a href="s6-dirname.html"><tt>s6-dirname</tt></a> program </li>
+ <li> The <a href="s6-echo.html"><tt>s6-echo</tt></a> program </li>
+ <li> The <a href="s6-env.html"><tt>s6-env</tt></a> program </li>
+ <li> The <a href="s6-expr.html"><tt>s6-expr</tt></a> program </li>
+ <li> The <a href="s6-false.html"><tt>s6-false</tt></a> program </li>
+ <li> The <a href="s6-format-filter.html"><tt>s6-format-filter</tt></a> program </li>
+ <li> The <a href="s6-grep.html"><tt>s6-grep</tt></a> program </li>
+ <li> The <a href="s6-head.html"><tt>s6-head</tt></a> program </li>
+ <li> The <a href="s6-linkname.html"><tt>s6-linkname</tt></a> program </li>
+ <li> The <a href="s6-ln.html"><tt>s6-ln</tt></a> program </li>
+ <li> The <a href="s6-ls.html"><tt>s6-ls</tt></a> program </li>
+ <li> The <a href="s6-maximumtime.html"><tt>s6-maximumtime</tt></a> program </li>
+ <li> The <a href="s6-mkdir.html"><tt>s6-mkdir</tt></a> program </li>
+ <li> The <a href="s6-mkfifo.html"><tt>s6-mkfifo</tt></a> program </li>
+ <li> The <a href="s6-nice.html"><tt>s6-nice</tt></a> program </li>
+ <li> The <a href="s6-nuke.html"><tt>s6-nuke</tt></a> program </li>
+ <li> The <a href="s6-pause.html"><tt>s6-pause</tt></a> program </li>
+ <li> The <a href="s6-printenv.html"><tt>s6-printenv</tt></a> program </li>
+ <li> The <a href="s6-quote-filter.html"><tt>s6-quote-filter</tt></a> program </li>
+ <li> The <a href="s6-quote.html"><tt>s6-quote</tt></a> program </li>
+ <li> The <a href="s6-rename.html"><tt>s6-rename</tt></a> program </li>
+ <li> The <a href="s6-rmrf.html"><tt>s6-rmrf</tt></a> program </li>
+ <li> The <a href="s6-sleep.html"><tt>s6-sleep</tt></a> program </li>
+ <li> The <a href="s6-sort.html"><tt>s6-sort</tt></a> program </li>
+ <li> The <a href="s6-sync.html"><tt>s6-sync</tt></a> program </li>
+ <li> The <a href="s6-tail.html"><tt>s6-tail</tt></a> program </li>
+ <li> The <a href="s6-test.html"><tt>s6-test</tt></a> program </li>
+ <li> The <a href="s6-touch.html"><tt>s6-touch</tt></a> program </li>
+ <li> The <a href="s6-true.html"><tt>s6-true</tt></a> program </li>
+ <li> The <a href="s6-uniquename.html"><tt>s6-uniquename</tt></a> program </li>
+ <li> The <a href="s6-unquote-filter.html"><tt>s6-unquote-filter</tt></a> program </li>
+ <li> The <a href="s6-unquote.html"><tt>s6-unquote</tt></a> program </li>
+ <li> The <a href="s6-update-symlinks.html"><tt>s6-update-symlinks</tt></a> program </li>
+</ul>
+
+<h2> Related resources </h2>
+
+<ul>
+ <li> <tt>s6-portable-utils</tt> is discussed on the
+<a href="http://skarnet.org/lists.html#skaware">skaware</a> mailing-list. </li>
+ </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-basename.html b/doc/s6-basename.html
new file mode 100644
index 0000000..b656599
--- /dev/null
+++ b/doc/s6-basename.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-portable-utils: the s6-basename program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-basename program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-basename basename" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-basename</tt> program </h1>
+
+<p>
+ s6-basename prints the basename of a pathname.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-basename [ -n ] <em>path</em> [ <em>suffix</em> ]
+</pre>
+
+<p>
+ s6-basename acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html">basename</a> utility.
+</p>
+
+<h2> Extra options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: do not print a trailing newline after the output. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-basename <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/basename.html">basename</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-cat.html b/doc/s6-cat.html
new file mode 100644
index 0000000..71610e8
--- /dev/null
+++ b/doc/s6-cat.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-portable-utils: the s6-cat program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-cat program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-cat cat" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-cat</tt> program </h1>
+
+<p>
+ s6-cat copies stdin to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-cat
+</pre>
+
+<p>
+ s6-cat transfers data from stdin to stdout, until it receives EOF or
+gets killed.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+ On systems that support it (as of 2.0.0.0, only Linux 2.6.17 or later),
+s6-cat performs zero-copy transfer.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-cat <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cat.html">cat</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-chmod.html b/doc/s6-chmod.html
new file mode 100644
index 0000000..165790c
--- /dev/null
+++ b/doc/s6-chmod.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-portable-utils: the s6-chmod program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-chmod program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-chmod chmod" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-chmod</tt> program </h1>
+
+<p>
+ s6-chmod changes the permissions of a file.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-chmod perms file
+</pre>
+
+<p>
+ s6-chmod changes <em>file</em>'s permissions to <em>perms</em>, which
+must be an absolute octal number such as 0755 for
+<tt>rwxr-xr-x</tt> or 2700 for <tt>rwx--S---</tt>.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-chmod <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/chmod.html">chmod</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-chown.html b/doc/s6-chown.html
new file mode 100644
index 0000000..0cbbe57
--- /dev/null
+++ b/doc/s6-chown.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-portable-utils: the s6-chown program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-chown program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-chown chown" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-chown</tt> program </h1>
+
+<p>
+ s6-chown changes the owner and/or group of a file.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-chown [ -U ] [ -u uid ] [ -g gid ] file
+</pre>
+
+<ul>
+ <li> s6-chown changes <em>file</em>'s owner to (numeric) <em>uid</em> and group
+to (numeric) <em>gid</em>. </li>
+ <li> If <em>uid</em> isn't provided, the owner remains the same; if <em>gid</em>
+isn't provided, the group remains the same. </li>
+ <li> The <tt>-U</tt> option sets <em>uid</em> to the value of the UID
+environment variable and <em>gid</em> to the value of the GID environment variable.
+This makes s6-chown easily usable with
+<a href="http://www.skarnet.org/software/s6/s6-envuidgid.html">s6-envuidgid</a>:
+<tt> s6-envuidgid account s6-chown -U file </tt> changes <em>file</em>'s uid and
+gid to <em>account</em>'s. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-chown <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/chown.html">chown</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-clock.html b/doc/s6-clock.html
new file mode 100644
index 0000000..37e05ec
--- /dev/null
+++ b/doc/s6-clock.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-portable-utils: the s6-clock program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-clock program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-clock clock" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-clock</tt> program </h1>
+
+<p>
+ s6-clock gets or sets the system clock.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-clock [ <em>label</em> ]
+</pre>
+
+<p>
+ When called without an argument, s6-clock writes the current system
+time, as a
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html#timestamp">TAI64N
+timestamp</a>, to stdout.
+</p>
+
+<p>
+ When given an argument <em>label</em>, which must be a
+<a href="http://skarnet.org/software/skalibs/libstddjb/tai.html#timestamp">TAI64N
+timestamp</a>, s6-clock sets the system time to this value.
+</p>
+
+<h2> Notes </h2>
+
+<p>
+ s6-clock only prints or accepts TAI time, no matter what the system clock is set
+to (TAI-10 or UTC); it will automatically make the right conversions for your
+system clock. Make sure your
+<a href="http://skarnet.org/software/skalibs/">skalibs</a> has been built with
+the right settings.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-cut.html b/doc/s6-cut.html
new file mode 100644
index 0000000..2d93762
--- /dev/null
+++ b/doc/s6-cut.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-portable-utils: the s6-cut program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-cut program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-cut cut" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-cut</tt> program </h1>
+
+<p>
+ s6-cut prints selected portions of the lines of its input.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-cut [ -b list | -c list | -f list ] [ -n ] [ -s ] [ file ... ]
+</pre>
+
+<p>
+ s6-cut acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html">cut</a> utility.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-cut <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/cut.html">cut</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-dirname.html b/doc/s6-dirname.html
new file mode 100644
index 0000000..4fee876
--- /dev/null
+++ b/doc/s6-dirname.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-portable-utils: the s6-dirname program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-dirname program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-dirname dirname" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-dirname</tt> program </h1>
+
+<p>
+ s6-dirname prints the dirname of a path.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-dirname [ -n ] path
+</pre>
+
+<p>
+ s6-dirname acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html">dirname</a> utility.
+</p>
+
+<h2> Extra options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: do not print a trailing newline after the output. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-dirname <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html">dirname</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-echo.html b/doc/s6-echo.html
new file mode 100644
index 0000000..5cbcc88
--- /dev/null
+++ b/doc/s6-echo.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-portable-utils: the s6-echo program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-echo program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-echo echo" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-echo</tt> program </h1>
+
+<p>
+ s6-echo writes its arguments to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-echo [ -n ] [ -s sep ] <em>args...</em>
+</pre>
+
+<p>
+ s6-echo writes its arguments <em>args</em> to stdout, separated with spaces.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: do not output a trailing newline. </li>
+ <li> <tt>-s</tt>&nbsp;<em>sep</em>&nbsp;: separate arguments with the <em>sep</em>
+character instead of a space. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-echo <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html">echo</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-env.html b/doc/s6-env.html
new file mode 100644
index 0000000..fa53a45
--- /dev/null
+++ b/doc/s6-env.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-portable-utils: the s6-env program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-env program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-env env" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-env</tt> program </h1>
+
+<p>
+s6-env prints the current environment or modifies the environment
+before running a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-env [ -i ] [ <em>name</em>=<em>value</em>... ] [ <em>prog...</em> ]
+</pre>
+
+<p>
+ s6-env acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html">env</a> utility.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-env <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html">env</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-expr.html b/doc/s6-expr.html
new file mode 100644
index 0000000..b535eb6
--- /dev/null
+++ b/doc/s6-expr.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-portable-utils: the s6-expr program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-expr program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-expr expr" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-expr</tt> program </h1>
+
+<p>
+ s6-expr evaluates an expression and writes the result to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-expr <em>expression...</em>
+</pre>
+
+<p>
+ s6-expr acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html">expr</a> utility,
+except that the <strong>:</strong> operator (pattern matching) is not supported.
+</p>
+
+<p>
+ s6-expr accepts an arbitrary number of arguments.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-expr <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/expr.html">expr</a>
+program; however, if you are never using the pattern matching functionality, s6-expr
+scrupulously follows the rest of the specification.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-false.html b/doc/s6-false.html
new file mode 100644
index 0000000..4b21046
--- /dev/null
+++ b/doc/s6-false.html
@@ -0,0 +1,39 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-false program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-false program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-false" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-false</tt> program </h1>
+
+<p>
+ s6-false returns 1.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-false
+</pre>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-false <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/false.html">false</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-format-filter.html b/doc/s6-format-filter.html
new file mode 100644
index 0000000..4492786
--- /dev/null
+++ b/doc/s6-format-filter.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-portable-utils: the s6-format-filter program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-format-filter program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-format filter printf format" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-format-filter</tt> program </h1>
+
+<p>
+s6-format-filter processes lines according to its arguments, and
+prints the result.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-format-filter <em>format</em> [ <em>args...</em> ]
+</pre>
+
+<ul>
+ <li> <em>format</em> is a format string that can contain the following special sequences:
+<tt>%%</tt>, <tt>%s</tt>, and <tt>%0</tt> to <tt>%9</tt>. </li>
+ <li> For every line it reads on stdin, s6-format-filter prints <em>format</em> on
+stdout, replacing <tt>%%</tt> with <tt>%</tt>, <tt>%0</tt> with the command name
+(probably <tt>s6-format-filter</tt>), <tt>%1</tt> to <tt>%9</tt> with the first
+to the ninth argument in <em>args</em>, and <tt>%s</tt> with the input line. </li>
+ <li> s6-format-filter exits 0 when it reads EOF. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-grep.html b/doc/s6-grep.html
new file mode 100644
index 0000000..fc0cb1b
--- /dev/null
+++ b/doc/s6-grep.html
@@ -0,0 +1,67 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-grep program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-grep program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-grep grep" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-grep</tt> program </h1>
+
+<p>
+ s6-grep matches its input against a pattern.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-grep [ -E | -F ] [ -i ] [ -c ] [ -n ] [ -q ] [ -v ] <em>pattern</em>
+</pre>
+
+<ul>
+ <li> s6-grep compiles <em>pattern</em> as an
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03">Basic
+Regular Expression</a> </li>
+ <li> It reads stdin and matches every line against this regexp </li>
+ <li> If the line matches, it prints it to stdout </li>
+ <li> It exits on EOF with code 0 if one or more lines matched and 1
+otherwise. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-F</tt>&nbsp;: <em>pattern</em> is not compiled as a BRE, but is
+interpreted as a literal string. </li>
+ <li> <tt>-E</tt>&nbsp;: <em>pattern</em> is not compiled as a BRE, but as an
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04">Extended
+Regular Expression</a> (ERE). </li>
+ <li> <tt>-i</tt>&nbsp;: ignore case during the match </li>
+ <li> <tt>-c</tt>&nbsp;: do not write normal output; only write the number of
+lines that have matched <em>pattern</em> after EOF is received </li>
+ <li> <tt>-n</tt>&nbsp;: precede every output line by its number and a colon.
+The first input line has number 1. </li>
+ <li> <tt>-q</tt>&nbsp;: do not write anything to stdout </li>
+ <li> <tt>-v</tt>&nbsp;: invert the pattern matching (select lines that do not
+match <em>pattern</em>). </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-grep <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html">grep</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-head.html b/doc/s6-head.html
new file mode 100644
index 0000000..3fd3360
--- /dev/null
+++ b/doc/s6-head.html
@@ -0,0 +1,55 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-head program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-head program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-head head" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-head</tt> program </h1>
+
+<p>
+ s6-head prints the first lines of its input files.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-head [ -S ] [ -1..9 | -n <em>lines</em> | -c <em>chars</em> ] <em>file...</em>
+</pre>
+
+<p>
+ s6-head acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/head.html">head</a> utility.
+</p>
+
+<h2> Extra options </h2>
+
+<ul>
+ <li> <tt>-S</tt>&nbsp;: safe mode. s6-head stops reading its input files <em>right after</em>
+getting all the lines it needs. The rest of the stream can then be handled by other
+utilities without any data loss. </li>
+ <li> <tt>-1..9</tt>&nbsp;: equivalent to <tt>-n 1</tt> .. <tt>-n 9</tt>. </li>
+ <li> <tt>-c</tt>&nbsp;<em>chars</em>&nbsp;: cuts after <em>chars</em> characters instead
+of <em>lines</em> lines. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-head <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/head.html">head</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-linkname.html b/doc/s6-linkname.html
new file mode 100644
index 0000000..e4caf84
--- /dev/null
+++ b/doc/s6-linkname.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-portable-utils: the s6-linkname program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-linkname program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-linkname linkname readlink" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-linkname</tt> program </h1>
+
+<p>
+ s6-linkname gives the content of a symbolic link or resolves a path.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-linkname [ -f ] <em>path</em>
+</pre>
+
+<ul>
+ <li> If <em>path</em> is a symbolic link, s6-linkname prints its
+content then exits 0. </li>
+ <li> Else it prints an error message and exits 111. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-f</tt>&nbsp;: s6-linkname fully resolves <em>path</em> then
+prints the result on stdout. <em>path</em> does not have to be a symbolic
+link. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-ln.html b/doc/s6-ln.html
new file mode 100644
index 0000000..392476f
--- /dev/null
+++ b/doc/s6-ln.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-portable-utils: the s6-ln program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-ln program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-ln ln link symbolic hard" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-ln</tt> program </h1>
+
+<p>
+ s6-ln creates a link to a file.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-ln [ -s ] [ -f ] [ -L ] [ -P ] <em>old</em> <em>new</em>
+</pre>
+
+<p>
+ s6-ln acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ln.html">ln</a> utility.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-ln <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ln.html">ln</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-ls.html b/doc/s6-ls.html
new file mode 100644
index 0000000..492ea25
--- /dev/null
+++ b/doc/s6-ls.html
@@ -0,0 +1,57 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-ls program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-ls program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-ls ls directory list" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-ls</tt> program </h1>
+
+<p>
+ s6-ls lists the contents of a directory.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-ls [ -0 ] [ -a | -A ] [ -x <em>exclude</em> ] <em>dir</em>
+</pre>
+
+<p>
+ s6-ls lists the contents of <em>dir</em>, one file per line. It
+omits files starting with a dot.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-0</tt>&nbsp;: separate file names with a null character
+instead of a newline. </li>
+ <li> <tt>-a</tt>&nbsp;: do not omit files starting with a dot. Do
+not list <tt>.</tt> or <tt>..</tt> though. </li>
+ <li> <tt>-A</tt>&nbsp;: do not omit files starting with a dot, even
+<tt>.</tt> and <tt>..</tt> </li>
+ <li> <tt>-x</tt>&nbsp;<em>exclude</em>&nbsp;: if a file name is
+<em>exclude</em>, do not print it. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-ls <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ls.html">ls</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-maximumtime.html b/doc/s6-maximumtime.html
new file mode 100644
index 0000000..eea72b3
--- /dev/null
+++ b/doc/s6-maximumtime.html
@@ -0,0 +1,53 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-maximumtime program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-maximumtime program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-maximumtime maximumtime timelimit time limit" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-maximumtime</tt> program </h1>
+
+<p>
+ s6-maximumtime executes a program with a time limit.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-maximumtime [ -0 | -a | -b | -i | -k | -q | -t | -x | -1 | -2 ] <em>milli</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-maximumtime forks and execs <em>prog...</em> as a child. </li>
+ <li> If <em>milli</em> milliseconds elapse before <em>prog</em> exits,
+s6-maximumtime sends it a signal and exits 99 with a message. </li>
+ <li> Else s6-maximumtime exits with the same exit code as <em>prog</em>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-0</tt>&nbsp;: on timeout, do not send any signal, just exit </li>
+ <li> <tt>-a</tt>&nbsp;: on timeout, send a SIGALRM </li>
+ <li> <tt>-b</tt>&nbsp;: on timeout, send a SIGABRT </li>
+ <li> <tt>-i</tt>&nbsp;: on timeout, send a SIGINT </li>
+ <li> <tt>-k</tt>&nbsp;: on timeout, send a SIGKILL </li>
+ <li> <tt>-q</tt>&nbsp;: on timeout, send a SIGQUIT </li>
+ <li> <tt>-t</tt>&nbsp;: on timeout, send a SIGTERM - this is the default </li>
+ <li> <tt>-x</tt>&nbsp;: on timeout, send a SIGXCPU </li>
+ <li> <tt>-1</tt>&nbsp;: on timeout, send a SIGUSR1 </li>
+ <li> <tt>-2</tt>&nbsp;: on timeout, send a SIGUSR2 </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-mkdir.html b/doc/s6-mkdir.html
new file mode 100644
index 0000000..8dd0e02
--- /dev/null
+++ b/doc/s6-mkdir.html
@@ -0,0 +1,50 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-mkdir program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-mkdir program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-mkdir mkdir" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-mkdir</tt> program </h1>
+
+<p>
+ s6-mkdir creates directories.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-mkdir [ -p ] [ -v ] [ -m <em>mode</em> ] [ <em>dir...</em> ]
+</pre>
+
+<p>
+ s6-mkdir acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mkdir.html">mkdir</a> utility.
+</p>
+
+<h2> Extra options </h2>
+
+<ul>
+ <li> <tt>-v</tt>&nbsp;: write what it does to stderr. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-mkdir <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mkdir.html">mkdir</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-mkfifo.html b/doc/s6-mkfifo.html
new file mode 100644
index 0000000..5f38072
--- /dev/null
+++ b/doc/s6-mkfifo.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-portable-utils: the s6-mkfifo program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-mkfifo program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-mkfifo mkfifo" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-mkfifo</tt> program </h1>
+
+<p>
+ s6-mkfifo creates FIFOs, a.k.a. named pipes.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-mkfifo [ -m mode ] [ <em>fifo...</em> ]
+</pre>
+
+<p>
+ s6-mkfifo acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mkfifo.html">mkfifo</a> utility.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-mkfifo <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/mkfifo.html">mkfifo</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-nice.html b/doc/s6-nice.html
new file mode 100644
index 0000000..98149c8
--- /dev/null
+++ b/doc/s6-nice.html
@@ -0,0 +1,53 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-nice program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-nice program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-nice nice" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">www.skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-nice</tt> program </h1>
+
+<p>
+ s6-nice executes into a program with an altered nice value.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-nice [ -I | -i ] [ -n <em>value</em> ] [ <em>prog...</em> ]
+</pre>
+
+<p>
+ s6-nice acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/nice.html">nice</a> utility.
+</p>
+
+<h2> Extra options </h2>
+
+<ul>
+ <li> <tt>-I</tt>&nbsp;: loose. If the nice value cannot be set to <em>value</em>,
+print a warning message and exec into <em>prog...</em> anyway. This is the default. </li>
+ <li> <tt>-i</tt>&nbsp;: strict. If the nice value cannot be set to <em>value</em>,
+exit 111 with an error message.
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-nice <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/nice.html">nice</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-nuke.html b/doc/s6-nuke.html
new file mode 100644
index 0000000..3db674c
--- /dev/null
+++ b/doc/s6-nuke.html
@@ -0,0 +1,56 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-nuke program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-nuke program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-nuke signal kill" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-nuke</tt> program </h1>
+
+<p>
+ s6-nuke sends signals to every process it is allowed to send.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-nuke [ -h | -t | -k ]
+</pre>
+
+<ul>
+ <li> Depending on the options it is given, s6-nuke sends signals to
+all processes; depending on s6-nuke's rights, not all processes may
+receive them. </li>
+ <li> s6-nuke protects itself against the signals it sends (which
+doesn't do much good against SIGKILL). If it survives the blast,
+it exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-h</tt>&nbsp;: send a SIGHUP </li>
+ <li> <tt>-t</tt>&nbsp;: send a SIGTERM then a SIGCONT </li>
+ <li> <tt>-k</tt>&nbsp;: send a SIGKILL </li>
+</ul>
+
+<h2> Usage notes </h2>
+
+<p>
+ s6-nuke can be used during the shutdown procedure of a system, which is
+described
+<a href="http://skarnet.org/software/s6/s6-svscan-1.html#stage3">here</a>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-pause.html b/doc/s6-pause.html
new file mode 100644
index 0000000..edb129f
--- /dev/null
+++ b/doc/s6-pause.html
@@ -0,0 +1,39 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-pause program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-pause program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-pause pause" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-pause</tt> program </h1>
+
+<p>
+ s6-pause blocks until it is killed.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-pause
+</pre>
+
+<h2> Usage notes </h2>
+
+<p>
+ s6-pause is one of the smallest possible long-lived programs. It can be
+used to emulate a running service with the least possible resource
+consumption.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-printenv.html b/doc/s6-printenv.html
new file mode 100644
index 0000000..96216ab
--- /dev/null
+++ b/doc/s6-printenv.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-portable-utils: the s6-printenv program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-printenv program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-printenv environment variables" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-printenv</tt> program </h1>
+
+<p>
+ s6-printenv prints its environment variables.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-printenv [ -n ] [ -0 | -d <em>delim</em> ]
+</pre>
+
+<p>
+ s6-printenv prints its environment on stdout, like
+<a href="s6-env.html">s6-env</a>.
+</p>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-0</tt>&nbsp;: separate file names with a null character
+instead of a newline. </li>
+ <li> <tt>-d</tt>&nbsp;<em>delim</em>&nbsp;: separate file names with the
+first character of <em>delim</em> instead of a newline </li>
+ <li> <tt>-n</tt>&nbsp;: omit the last delimiter character </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-quote-filter.html b/doc/s6-quote-filter.html
new file mode 100644
index 0000000..867acbe
--- /dev/null
+++ b/doc/s6-quote-filter.html
@@ -0,0 +1,56 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-quote-filter program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-quote-filter program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-quote-filter quote" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-quote-filter</tt> program </h1>
+
+<p>
+ s6-quote-filter acts as a filter, quoting lines it reads on stdin
+and writing the quoted lines to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-quote-filter [ -u ] [ -d <em>delim</em> ]
+</pre>
+
+<ul>
+ <li> s6-quote reads lines on stdin; it quotes every line, putting it inside double quotes
+and escaping all dubious characters </li>
+ <li> It writes the quoted strings to stdout </li>
+ <li> It exits 0 on EOF </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-u</tt>&nbsp;: do not put read lines inside double quotes, only escape
+characters if needed </li>
+ <li> <tt>-d</tt>&nbsp;<em>delim</em>&nbsp;: use the first character of <em>delim</em>
+as a quote character, instead of double quotes </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Quoted strings are guaranteed to be pure printable ASCII, without control characters. </li>
+ <li> Quoted strings can be unquoted via the <a href="s6-unquote.html">s6-unquote</a> or
+<a href="s6-unquote-filter.html">s6-unquote-filter</a> programs. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-quote.html b/doc/s6-quote.html
new file mode 100644
index 0000000..a24069f
--- /dev/null
+++ b/doc/s6-quote.html
@@ -0,0 +1,57 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-quote program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-quote program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-quote quote" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-quote</tt> program </h1>
+
+<p>
+ s6-quote quotes a string and writes it to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-quote [ -n ] [ -u ] [ -d <em>delim</em> ] <em>string</em>
+</pre>
+
+<ul>
+ <li> s6-quote quotes <em>string</em>, putting it inside double quotes
+and escaping all dubious characters </li>
+ <li> It writes the quoted string to stdout and exits 0 </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: do not print a trailing newline </li>
+ <li> <tt>-u</tt>&nbsp;: do not put <em>string</em> inside double quotes, only escape
+characters if needed </li>
+ <li> <tt>-d</tt>&nbsp;<em>delim</em>&nbsp;: use the first character of <em>delim</em>
+as a quote character, instead of double quotes </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Quoted strings are guaranteed to be pure printable ASCII, without control characters. </li>
+ <li> Quoted strings can be unquoted via the <a href="s6-unquote.html">s6-unquote</a> or
+<a href="s6-unquote-filter.html">s6-unquote-filter</a> programs. </li>
+ <li> Quoted strings are suitable for interpretation by
+<a href="http://skarnet.org/software/execline/execlineb.html">execlineb</a>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-rename.html b/doc/s6-rename.html
new file mode 100644
index 0000000..1691328
--- /dev/null
+++ b/doc/s6-rename.html
@@ -0,0 +1,35 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-rename program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-rename program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-rename rename atomic" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-rename</tt> program </h1>
+
+<p>
+ s6-rename atomically renames a file.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-rename <em>old</em> <em>new</em>
+</pre>
+
+<p>
+ s6-rename atomically renames <em>old</em> to <em>new</em>.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-rmrf.html b/doc/s6-rmrf.html
new file mode 100644
index 0000000..ccbded4
--- /dev/null
+++ b/doc/s6-rmrf.html
@@ -0,0 +1,35 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-rmrf program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-rmrf program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-rmrf rm remove delete" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-rmrf</tt> program </h1>
+
+<p>
+ s6-rmrf removes a file or directory tree entirely.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-rmrf <em>subtree</em>
+</pre>
+
+<p>
+ s6-rmrf removes the <em>subtree</em> file hierarchy.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-sleep.html b/doc/s6-sleep.html
new file mode 100644
index 0000000..68137d3
--- /dev/null
+++ b/doc/s6-sleep.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-portable-utils: the s6-sleep program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-sleep program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-sleep sleep" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-sleep</tt> program </h1>
+
+<p>
+ s6-sleep sleeps for a given time.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-sleep [ -m ] <em>time</em>
+</pre>
+
+<p>
+ s6-sleep acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sleep.html">sleep</a> utility.
+It ignores non-lethal signals.
+</p>
+
+<h2> Extra options </h2>
+
+<ul>
+ <li> <tt>-m</tt>&nbsp;: Interpret <em>time</em> as milliseconds instead of seconds. </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-sleep <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sleep.html">sleep</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-sort.html b/doc/s6-sort.html
new file mode 100644
index 0000000..5e2d468
--- /dev/null
+++ b/doc/s6-sort.html
@@ -0,0 +1,59 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-sort program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-sort program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-sort sort" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-sort</tt> program </h1>
+
+<p>
+ s6-sort sorts its input.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-sort [ -b ] [ -c ] [ -f ] [ -r ] [ -u ] [ -0 ]
+</pre>
+
+<ul>
+ <li> s6-sort reads its stdin until EOF </li>
+ <li> It sorts all the lines it read alphanumerically </li>
+ <li> It prints the sorted lines to stdout and exits 0 </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-b</tt>&nbsp;: ignore leading spaces and tabs at the beginning of each
+line when sorting </li>
+ <li> <tt>-c</tt>&nbsp;: suppresses normal output, just returns 0 if the output
+would have been the same as the input and 1 otherwise </li>
+ <li> <tt>-f</tt>&nbsp;: ignore case when sorting </li>
+ <li> <tt>-r</tt>&nbsp;: reverse sort </li>
+ <li> <tt>-u</tt>&nbsp;: suppresses duplicate lines </li>
+ <li> <tt>-0</tt>&nbsp;: input and output lines are separated by null characters
+instead of newlines </li>
+</ul>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-sort <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html">sort</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-sync.html b/doc/s6-sync.html
new file mode 100644
index 0000000..daf88f3
--- /dev/null
+++ b/doc/s6-sync.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>s6-portable-utils: the s6-sync program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-sync program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-sync sync filesystem" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-sync</tt> program </h1>
+
+<p>
+ s6-sync flushes all the dirty system buffers, and blocks until they're
+clean.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-sync
+</pre>
+
+</body>
+</html>
diff --git a/doc/s6-tail.html b/doc/s6-tail.html
new file mode 100644
index 0000000..abf2b76
--- /dev/null
+++ b/doc/s6-tail.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-portable-utils: the s6-tail program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-tail program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-tail tail" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-tail</tt> program </h1>
+
+<p>
+ s6-tail prints a file or its stdin after a certain number of bytes or lines.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-tail [ -n <em>lines</em> | -c <em>chars</em> ] [ <em>file</em> ]
+</pre>
+
+<p>
+ s6-tail acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tail.html">tail</a> utility,
+except that the <strong>-f</strong> option is not supported.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-tail <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/tail.html">tail</a>
+program; however, if you are never using the <tt>-f</tt> functionality, s6-tail
+scrupulously follows the rest of the specification.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-test.html b/doc/s6-test.html
new file mode 100644
index 0000000..22b6fbd
--- /dev/null
+++ b/doc/s6-test.html
@@ -0,0 +1,53 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-test program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-test program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-test test" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-test</tt> program </h1>
+
+<p>
+ s6-test evaluates an expression and indicates the result via its
+exit status.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-test <em>expression...</em>
+</pre>
+
+<p>
+ s6-test acts as the generic
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a> utility,
+but it diverges from the specification on one point: if an argument starts with a backslash,
+this backslash is ignored (and the argument cannot be an operation). This is a simple
+disambiguation technique that has unfortunately not been chosen by the standard.
+</p>
+
+<p>
+ s6-test accepts an arbitrary number of arguments.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-test <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html">test</a>
+program; however, if your arguments never start with a backslash, it exhibits the
+exact same behaviour.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-touch.html b/doc/s6-touch.html
new file mode 100644
index 0000000..ccc40ca
--- /dev/null
+++ b/doc/s6-touch.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-portable-utils: the s6-touch program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-touch program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-touch touch" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-touch</tt> program </h1>
+
+<p>
+ s6-touch changes the modification and access times of a file, creating it if it does
+not exist.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-touch <em>file...</em>
+</pre>
+
+<p>
+ s6-touch touches every <em>file</em> in the list by opening them for appending
+then closing them.
+</p>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-touch <strong>is not</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/touch.html">touch</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-true.html b/doc/s6-true.html
new file mode 100644
index 0000000..60ec1c7
--- /dev/null
+++ b/doc/s6-true.html
@@ -0,0 +1,39 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-true program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-true program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-true" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-true</tt> program </h1>
+
+<p>
+ s6-true returns 0.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-true
+</pre>
+
+<h2> Posixness </h2>
+
+<p>
+ s6-true <strong>is</strong> suitable as a Single Unix
+<a href="http://pubs.opengroup.org/onlinepubs/9699919799/utilities/false.html">true</a>
+program.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-uniquename.html b/doc/s6-uniquename.html
new file mode 100644
index 0000000..7315d9b
--- /dev/null
+++ b/doc/s6-uniquename.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-portable-utils: the s6-uniquename program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-uniquename program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-uniquename uniquename random" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-uniquename</tt> program </h1>
+
+<p>
+ s6-uniquename creates a guaranteed unique file name and prints it to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-uniquename [ -n <em>randomlen</em> ] <em>prefix</em>
+</pre>
+
+<ul>
+ <li> s6-uniquename creates a unique filename starting with <em>prefix</em>
+involving a TAI64N timestamp, the machine's FQDN and the process' PID. </li>
+ <li> It prints it to stdout and exits 0. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;<em>randomlen</em>&nbsp;: for added security, also
+add a random readable string of <em>randomlen</em> characters to the
+created pathname. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-unquote-filter.html b/doc/s6-unquote-filter.html
new file mode 100644
index 0000000..7262722
--- /dev/null
+++ b/doc/s6-unquote-filter.html
@@ -0,0 +1,69 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-unquote-filter program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-unquote-filter program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-unquote-filter unquote filter" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-unquote-filter</tt> program </h1>
+
+<p>
+ s6-unquote acts as a filter, reading quoted strings on stdin,
+unquoting them and writing the results to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-unquote-filter [ -q | -Q | -v | -w ] [ -d <em>delim</em> ]
+</pre>
+
+<ul>
+ <li> s6-unquote-filter reads lines on stdin. It exits 0 on EOF. </li>
+ <li> It expects read lines to follow the
+syntax of <a href="s6-quote.html">s6-quote</a>'s output strings </li>
+ <li> Depending on the strictness options, it prints various warning
+or error messages to stderr if it cannot properly unquote lines. In
+the very strict mode, it exits 100 on the first unquoting error. </li>
+ <li> If it is successful at unquoting, it prints the resulting
+lines to stdout. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-d</tt>&nbsp;<em>delim</em>&nbsp;: a list of characters that
+will be considered as delimitors (to start and end the quoted string).
+By default, only the double quote is such a character. If <em>delim</em>
+is the empty string, s6-unquote-filter interprets <em>string</em> as
+non-delimited, only escaped (i.e. for instance the result of some
+<tt>s6-quote-filter -u</tt> operation). </li>
+ <li> <tt>-q</tt>&nbsp;: loose/quiet mode. s6-unquote-filter will
+silently accommodate errors. </li>
+ <li> <tt>-Q</tt>&nbsp;: normal mode. This is the default. s6-unquote-filter
+will warn on errors. </li>
+ <li> <tt>-v</tt>&nbsp;: strict/verbose mode. s6-unquote-filter will
+warn loudly on errors, with many details. </li>
+ <li> <tt>-w</tt>&nbsp;: very strict mode. s6-unquote-filter will complain
+and die on the first unquoting error it encounters. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Quoted strings can be produced via the <a href="s6-quote.html">s6-quote</a> or
+<a href="s6-quote-filter.html">s6-quote-filter</a> programs. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-unquote.html b/doc/s6-unquote.html
new file mode 100644
index 0000000..546082e
--- /dev/null
+++ b/doc/s6-unquote.html
@@ -0,0 +1,58 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-unquote program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-unquote program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-unquote quote" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</a><br />
+<a href="http://skarnet.org/software/">Software</a><br />
+<a href="http://skarnet.org/">skarnet.org</a>
+</p>
+
+<h1> The <tt>s6-unquote</tt> program </h1>
+
+<p>
+ s6-unquote unquotes a quoted string and writes it to stdout.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-unquote [ -n ] [ -d <em>delim</em> ] <em>string</em>
+</pre>
+
+<ul>
+ <li> s6-unquote unquotes <em>string</em>, which must follow the
+syntax of <a href="s6-quote.html">s6-quote</a>'s output strings </li>
+ <li> It prints various warning or error messages to stderr if it
+cannot unquote <em>string</em> properly </li>
+ <li> If successful, it prints the result to stdout and exits 0 </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-n</tt>&nbsp;: do not print a trailing newline. </li>
+ <li> <tt>-d</tt>&nbsp;<em>delim</em>&nbsp;: a list of characters that
+will be considered as delimitors (to start and end the quoted string).
+By default, only the double quote is such a character. If <em>delim</em>
+is the empty string, s6-unquote interprets <em>string</em> as
+non-delimited, only escaped (i.e. the result of some
+<tt>s6-quote -u</tt> operation). </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> Quoted strings can be produced via the <a href="s6-quote.html">s6-quote</a> or
+<a href="s6-quote-filter.html">s6-quote-filter</a> programs. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-update-symlinks.html b/doc/s6-update-symlinks.html
new file mode 100644
index 0000000..96cc15b
--- /dev/null
+++ b/doc/s6-update-symlinks.html
@@ -0,0 +1,83 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-portable-utils: the s6-update-symlinks program</title>
+    <meta name="Description" content="s6-portable-utils: the s6-update-symlinks program" />
+    <meta name="Keywords" content="s6-portable-utils command s6-update-symlinks update-symlinks symlinks" />
+    <!-- <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>s6-update-symlinks</tt> program </h1>
+
+<p>
+ <tt>s6-update-symlinks</tt> links the content of several similar directory trees
+under a single tree. Its main use is to replace search paths like $PATH
+or $MANPATH, by creating a unique access tree based on the source trees.
+Name conflicts are solved by giving precedence to the last named directory.
+Subdirectories are created exactly as needed ; what can be shared is
+shared.
+</p>
+
+<p>
+<tt>s6-update-symlinks</tt> is useful when one wants to combine
+a logical package system, like Dan Bernstein's
+<a href="http://cr.yp.to/slashpackage.html"><tt>/package</tt></a> and
+<a href="http://cr.yp.to/slashcommand.html"><tt>/command</tt></a>, with
+physical filesystem constraints, like <tt>/</tt>, <tt>/usr</tt> and
+<tt>/usr/local</tt> on separate filesystems.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-update-symlinks <em>d</em> <em>src1</em> <em>src2</em> ...
+</pre>
+
+<ul>
+ <li> <em>d</em> is the destination directory. It will be created if it doesn't
+exist. </li>
+ <li> <em>src1</em>, <em>src2</em>, ... are the directory containing the files
+to be linked.</li>
+ <li> <em>d</em> and <em>srcn</em> <strong>must</strong> be
+<strong>absolute</strong> paths, else s6-update-symlinks refuses to run. Using
+relative paths doesn't make sense here, anyway. </li>
+ <li> If <em>src2</em> is empty, then <em>d</em> becomes a link to <em>src1</em>. </li>
+ <li> If <em>src1</em> is empty or entirely overridden by <em>src2</em>, then
+<em>d</em> becomes a link to <em>src2</em>.</li>
+ <li> If <em>src1/file</em> exists but not <em>src2/file</em>, then <em>d</em>
+becomes a real directory and <em>d/file</em> a link to <em>src1/file</em>.
+Then if <em>src2/file2</em> exists, <em>d/file2</em> links to it. </li>
+ <li> And so on with other <em>src</em> directories, and subdirs. </li>
+ <li> If <tt>s6-update-symlinks</tt> manages to performs all the requested
+tasks, it exits 0. If it encounters a hard error, it exits 111. If it is
+unable to resolve a conflict between given sources, it exits 100. </li>
+</ul>
+
+<h2> Examples </h2>
+
+<ul>
+ <li>
+<tt>s6-update-symlinks /command /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin</tt>
+<br> makes all files under <tt>/bin</tt>, ..., <tt>/usr/local/sbin</tt>
+available under <tt>/command</tt>. The programs linked are the same as the ones
+that would be accessed with PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".
+ </li>
+ <li>
+<tt>s6-update-symlinks /package /initrd/package /slash/package /usr/package /usr/local/package</tt>
+<br /> builds a <tt>/package</tt> hierarchy with what it finds in the listed
+directories. This allows oddities like, for instance, having the daemontools
+sources in <tt>/usr/package/admin/daemontools/src</tt>, and the daemontools
+binaries in <tt>/initrd/package/admin/daemontools/bin</tt>, but accessing
+both through <tt>/package/admin/daemontools/</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..978f397
--- /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>s6-portable-utils: how to upgrade</title>
+    <meta name="Description" content="s6-portable-utils: how to upgrade" />
+    <meta name="Keywords" content="s6-portable-utils installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-portable-utils</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-portable-utils </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/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..05d5af4
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1 @@
+/package/prog/skalibs
diff --git a/package/deps.mak b/package/deps.mak
new file mode 100644
index 0000000..425b475
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,83 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/skaembutils/s6-basename.o src/skaembutils/s6-basename.lo: src/skaembutils/s6-basename.c
+src/skaembutils/s6-cat.o src/skaembutils/s6-cat.lo: src/skaembutils/s6-cat.c
+src/skaembutils/s6-chmod.o src/skaembutils/s6-chmod.lo: src/skaembutils/s6-chmod.c
+src/skaembutils/s6-chown.o src/skaembutils/s6-chown.lo: src/skaembutils/s6-chown.c
+src/skaembutils/s6-clock.o src/skaembutils/s6-clock.lo: src/skaembutils/s6-clock.c
+src/skaembutils/s6-cut.o src/skaembutils/s6-cut.lo: src/skaembutils/s6-cut.c
+src/skaembutils/s6-dirname.o src/skaembutils/s6-dirname.lo: src/skaembutils/s6-dirname.c
+src/skaembutils/s6-echo.o src/skaembutils/s6-echo.lo: src/skaembutils/s6-echo.c
+src/skaembutils/s6-env.o src/skaembutils/s6-env.lo: src/skaembutils/s6-env.c src/include/s6-portable-utils/config.h
+src/skaembutils/s6-expr.o src/skaembutils/s6-expr.lo: src/skaembutils/s6-expr.c
+src/skaembutils/s6-false.o src/skaembutils/s6-false.lo: src/skaembutils/s6-false.c
+src/skaembutils/s6-format-filter.o src/skaembutils/s6-format-filter.lo: src/skaembutils/s6-format-filter.c
+src/skaembutils/s6-grep.o src/skaembutils/s6-grep.lo: src/skaembutils/s6-grep.c
+src/skaembutils/s6-head.o src/skaembutils/s6-head.lo: src/skaembutils/s6-head.c
+src/skaembutils/s6-linkname.o src/skaembutils/s6-linkname.lo: src/skaembutils/s6-linkname.c
+src/skaembutils/s6-ln.o src/skaembutils/s6-ln.lo: src/skaembutils/s6-ln.c
+src/skaembutils/s6-ls.o src/skaembutils/s6-ls.lo: src/skaembutils/s6-ls.c
+src/skaembutils/s6-maximumtime.o src/skaembutils/s6-maximumtime.lo: src/skaembutils/s6-maximumtime.c
+src/skaembutils/s6-mkdir.o src/skaembutils/s6-mkdir.lo: src/skaembutils/s6-mkdir.c
+src/skaembutils/s6-mkfifo.o src/skaembutils/s6-mkfifo.lo: src/skaembutils/s6-mkfifo.c
+src/skaembutils/s6-nice.o src/skaembutils/s6-nice.lo: src/skaembutils/s6-nice.c
+src/skaembutils/s6-nuke.o src/skaembutils/s6-nuke.lo: src/skaembutils/s6-nuke.c
+src/skaembutils/s6-pause.o src/skaembutils/s6-pause.lo: src/skaembutils/s6-pause.c
+src/skaembutils/s6-printenv.o src/skaembutils/s6-printenv.lo: src/skaembutils/s6-printenv.c
+src/skaembutils/s6-quote-filter.o src/skaembutils/s6-quote-filter.lo: src/skaembutils/s6-quote-filter.c
+src/skaembutils/s6-quote.o src/skaembutils/s6-quote.lo: src/skaembutils/s6-quote.c
+src/skaembutils/s6-rename.o src/skaembutils/s6-rename.lo: src/skaembutils/s6-rename.c
+src/skaembutils/s6-rmrf.o src/skaembutils/s6-rmrf.lo: src/skaembutils/s6-rmrf.c
+src/skaembutils/s6-sleep.o src/skaembutils/s6-sleep.lo: src/skaembutils/s6-sleep.c
+src/skaembutils/s6-sort.o src/skaembutils/s6-sort.lo: src/skaembutils/s6-sort.c
+src/skaembutils/s6-sync.o src/skaembutils/s6-sync.lo: src/skaembutils/s6-sync.c
+src/skaembutils/s6-tail.o src/skaembutils/s6-tail.lo: src/skaembutils/s6-tail.c
+src/skaembutils/s6-test.o src/skaembutils/s6-test.lo: src/skaembutils/s6-test.c
+src/skaembutils/s6-touch.o src/skaembutils/s6-touch.lo: src/skaembutils/s6-touch.c
+src/skaembutils/s6-true.o src/skaembutils/s6-true.lo: src/skaembutils/s6-true.c
+src/skaembutils/s6-uniquename.o src/skaembutils/s6-uniquename.lo: src/skaembutils/s6-uniquename.c
+src/skaembutils/s6-unquote-filter.o src/skaembutils/s6-unquote-filter.lo: src/skaembutils/s6-unquote-filter.c
+src/skaembutils/s6-unquote.o src/skaembutils/s6-unquote.lo: src/skaembutils/s6-unquote.c
+src/skaembutils/s6-update-symlinks.o src/skaembutils/s6-update-symlinks.lo: src/skaembutils/s6-update-symlinks.c
+
+s6-basename: src/skaembutils/s6-basename.o -lskarnet
+s6-cat: src/skaembutils/s6-cat.o -lskarnet
+s6-chmod: src/skaembutils/s6-chmod.o -lskarnet
+s6-chown: src/skaembutils/s6-chown.o -lskarnet
+s6-clock: src/skaembutils/s6-clock.o -lskarnet ${SYSCLOCK_LIB}
+s6-cut: src/skaembutils/s6-cut.o -lskarnet
+s6-dirname: src/skaembutils/s6-dirname.o -lskarnet
+s6-echo: src/skaembutils/s6-echo.o -lskarnet
+s6-env: src/skaembutils/s6-env.o -lskarnet
+s6-expr: src/skaembutils/s6-expr.o -lskarnet
+s6-false: src/skaembutils/s6-false.o
+s6-format-filter: src/skaembutils/s6-format-filter.o -lskarnet
+s6-grep: src/skaembutils/s6-grep.o -lskarnet
+s6-head: src/skaembutils/s6-head.o -lskarnet
+s6-linkname: src/skaembutils/s6-linkname.o -lskarnet
+s6-ln: src/skaembutils/s6-ln.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB}
+s6-ls: src/skaembutils/s6-ls.o -lskarnet
+s6-maximumtime: src/skaembutils/s6-maximumtime.o -lskarnet ${TAINNOW_LIB}
+s6-mkdir: src/skaembutils/s6-mkdir.o -lskarnet
+s6-mkfifo: src/skaembutils/s6-mkfifo.o -lskarnet
+s6-nice: src/skaembutils/s6-nice.o -lskarnet
+s6-nuke: src/skaembutils/s6-nuke.o -lskarnet
+s6-pause: src/skaembutils/s6-pause.o
+s6-printenv: src/skaembutils/s6-printenv.o -lskarnet
+s6-quote: src/skaembutils/s6-quote.o -lskarnet
+s6-quote-filter: src/skaembutils/s6-quote-filter.o -lskarnet
+s6-rename: src/skaembutils/s6-rename.o -lskarnet
+s6-rmrf: src/skaembutils/s6-rmrf.o -lskarnet
+s6-sleep: src/skaembutils/s6-sleep.o -lskarnet ${TAINNOW_LIB}
+s6-sort: src/skaembutils/s6-sort.o -lskarnet
+s6-sync: src/skaembutils/s6-sync.o
+s6-tail: src/skaembutils/s6-tail.o -lskarnet
+s6-test: src/skaembutils/s6-test.o -lskarnet
+s6-touch: src/skaembutils/s6-touch.o -lskarnet
+s6-true: src/skaembutils/s6-true.o
+s6-uniquename: src/skaembutils/s6-uniquename.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB}
+s6-unquote: src/skaembutils/s6-unquote.o -lskarnet
+s6-unquote-filter: src/skaembutils/s6-unquote-filter.o -lskarnet
+s6-update-symlinks: src/skaembutils/s6-update-symlinks.o -lskarnet ${SOCKET_LIB} ${TAINNOW_LIB}
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..6d862af
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=s6-portable-utils
+version=2.0.0.0
+category=admin
+package_macro_name=S6_PORTABLE_UTILS
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..f32d538
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,39 @@
+s6-basename		0755
+s6-cat			0755
+s6-chmod		0755
+s6-chown		0755
+s6-clock		0755
+s6-cut			0755
+s6-dirname		0755
+s6-echo			0755
+s6-env			0755
+s6-expr			0755
+s6-false		0755
+s6-format-filter	0755
+s6-grep			0755
+s6-head			0755
+s6-linkname		0755
+s6-ln			0755
+s6-ls			0755
+s6-maximumtime		0755
+s6-mkdir		0755
+s6-mkfifo		0755
+s6-nice			0755
+s6-nuke			0755
+s6-pause		0755
+s6-printenv		0755
+s6-quote		0755
+s6-quote-filter		0755
+s6-rename		0755
+s6-rmrf			0755
+s6-sleep		0755
+s6-sort			0755
+s6-sync			0755
+s6-tail			0755
+s6-test			0755
+s6-touch		0755
+s6-true			0755
+s6-uniquename		0755
+s6-unquote		0755
+s6-unquote-filter	0755
+s6-update-symlinks	0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..256c88a
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,45 @@
+BIN_TARGETS = \
+s6-basename \
+s6-cat \
+s6-chmod \
+s6-chown \
+s6-clock \
+s6-cut \
+s6-dirname \
+s6-echo \
+s6-env \
+s6-expr \
+s6-false \
+s6-format-filter \
+s6-grep \
+s6-head \
+s6-linkname \
+s6-ln \
+s6-ls \
+s6-maximumtime \
+s6-mkdir \
+s6-mkfifo \
+s6-nice \
+s6-nuke \
+s6-pause \
+s6-printenv \
+s6-quote \
+s6-quote-filter \
+s6-rename \
+s6-rmrf \
+s6-sleep \
+s6-sort \
+s6-sync \
+s6-tail \
+s6-test \
+s6-touch \
+s6-true \
+s6-uniquename \
+s6-unquote \
+s6-unquote-filter \
+s6-update-symlinks
+
+SBIN_TARGETS =
+LIBEXEC_TARGETS =
+SHARED_LIBS =
+STATIC_LIBS =
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/skaembutils/deps-exe/s6-basename b/src/skaembutils/deps-exe/s6-basename
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-basename
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-cat b/src/skaembutils/deps-exe/s6-cat
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-cat
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-chmod b/src/skaembutils/deps-exe/s6-chmod
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-chmod
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-chown b/src/skaembutils/deps-exe/s6-chown
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-chown
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-clock b/src/skaembutils/deps-exe/s6-clock
new file mode 100644
index 0000000..a11a5f4
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-clock
@@ -0,0 +1,2 @@
+-lskarnet
+${SYSCLOCK_LIB}
diff --git a/src/skaembutils/deps-exe/s6-cut b/src/skaembutils/deps-exe/s6-cut
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-cut
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-dirname b/src/skaembutils/deps-exe/s6-dirname
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-dirname
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-echo b/src/skaembutils/deps-exe/s6-echo
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-echo
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-env b/src/skaembutils/deps-exe/s6-env
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-env
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-expr b/src/skaembutils/deps-exe/s6-expr
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-expr
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-false b/src/skaembutils/deps-exe/s6-false
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-false
diff --git a/src/skaembutils/deps-exe/s6-format-filter b/src/skaembutils/deps-exe/s6-format-filter
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-format-filter
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-grep b/src/skaembutils/deps-exe/s6-grep
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-grep
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-head b/src/skaembutils/deps-exe/s6-head
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-head
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-linkname b/src/skaembutils/deps-exe/s6-linkname
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-linkname
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-ln b/src/skaembutils/deps-exe/s6-ln
new file mode 100644
index 0000000..e027835
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-ln
@@ -0,0 +1,3 @@
+-lskarnet
+${SOCKET_LIB}
+${TAINNOW_LIB}
diff --git a/src/skaembutils/deps-exe/s6-ls b/src/skaembutils/deps-exe/s6-ls
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-ls
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-maximumtime b/src/skaembutils/deps-exe/s6-maximumtime
new file mode 100644
index 0000000..1840bc1
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-maximumtime
@@ -0,0 +1,2 @@
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/skaembutils/deps-exe/s6-mkdir b/src/skaembutils/deps-exe/s6-mkdir
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-mkdir
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-mkfifo b/src/skaembutils/deps-exe/s6-mkfifo
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-mkfifo
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-nice b/src/skaembutils/deps-exe/s6-nice
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-nice
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-nuke b/src/skaembutils/deps-exe/s6-nuke
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-nuke
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-pause b/src/skaembutils/deps-exe/s6-pause
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-pause
diff --git a/src/skaembutils/deps-exe/s6-printenv b/src/skaembutils/deps-exe/s6-printenv
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-printenv
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-quote b/src/skaembutils/deps-exe/s6-quote
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-quote
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-quote-filter b/src/skaembutils/deps-exe/s6-quote-filter
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-quote-filter
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-rename b/src/skaembutils/deps-exe/s6-rename
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-rename
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-rmrf b/src/skaembutils/deps-exe/s6-rmrf
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-rmrf
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-sleep b/src/skaembutils/deps-exe/s6-sleep
new file mode 100644
index 0000000..1840bc1
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-sleep
@@ -0,0 +1,2 @@
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/skaembutils/deps-exe/s6-sort b/src/skaembutils/deps-exe/s6-sort
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-sort
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-sync b/src/skaembutils/deps-exe/s6-sync
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-sync
diff --git a/src/skaembutils/deps-exe/s6-tail b/src/skaembutils/deps-exe/s6-tail
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-tail
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-test b/src/skaembutils/deps-exe/s6-test
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-test
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-touch b/src/skaembutils/deps-exe/s6-touch
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-touch
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-true b/src/skaembutils/deps-exe/s6-true
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-true
diff --git a/src/skaembutils/deps-exe/s6-uniquename b/src/skaembutils/deps-exe/s6-uniquename
new file mode 100644
index 0000000..e027835
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-uniquename
@@ -0,0 +1,3 @@
+-lskarnet
+${SOCKET_LIB}
+${TAINNOW_LIB}
diff --git a/src/skaembutils/deps-exe/s6-unquote b/src/skaembutils/deps-exe/s6-unquote
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-unquote
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-unquote-filter b/src/skaembutils/deps-exe/s6-unquote-filter
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-unquote-filter
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/skaembutils/deps-exe/s6-update-symlinks b/src/skaembutils/deps-exe/s6-update-symlinks
new file mode 100644
index 0000000..e027835
--- /dev/null
+++ b/src/skaembutils/deps-exe/s6-update-symlinks
@@ -0,0 +1,3 @@
+-lskarnet
+${SOCKET_LIB}
+${TAINNOW_LIB}
diff --git a/src/skaembutils/s6-basename.c b/src/skaembutils/s6-basename.c
new file mode 100644
index 0000000..113e14b
--- /dev/null
+++ b/src/skaembutils/s6-basename.c
@@ -0,0 +1,45 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-basename [ -n ] file [ suffix ]"
+
+int main (int argc, char const *const *argv)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  int nl = 1 ;
+  PROG = "s6-basename" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "n", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  if (!sabasename(&sa, argv[0], str_len(argv[0])))
+    strerr_diefu2sys(111, "get basename of ", argv[0]) ;
+  if (argc >= 2)
+  {
+    unsigned int n = str_len(argv[1]) ;
+    if ((n < sa.len) && !byte_diff(argv[1], n, sa.s + sa.len - n))
+      sa.len -= n ;
+  }
+  if (nl && !stralloc_catb(&sa, "\n", 1))
+    strerr_diefu2sys(111, "get basename of ", argv[0]) ;
+  if (allwrite(1, sa.s, sa.len) < sa.len)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-cat.c b/src/skaembutils/s6-cat.c
new file mode 100644
index 0000000..f0166be
--- /dev/null
+++ b/src/skaembutils/s6-cat.c
@@ -0,0 +1,12 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+int main (void)
+{
+  PROG = "s6-cat" ;
+  if (fd_cat(0, 1) < 0) strerr_diefu1sys(111, "fd_cat") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-chmod.c b/src/skaembutils/s6-chmod.c
new file mode 100644
index 0000000..9cf6f80
--- /dev/null
+++ b/src/skaembutils/s6-chmod.c
@@ -0,0 +1,34 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-chmod mode file"
+
+int main (int argc, char const *const *argv)
+{
+  mode_t mode = 0 ;
+  unsigned int m ;
+  PROG = "s6-chmod" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (!uint0_oscan(argv[1], &m)) strerr_dieusage(100, USAGE) ;
+
+  if (m & 0001) mode |= S_IXOTH ;
+  if (m & 0002) mode |= S_IWOTH ;
+  if (m & 0004) mode |= S_IROTH ;
+  if (m & 0010) mode |= S_IXGRP ;
+  if (m & 0020) mode |= S_IWGRP ;
+  if (m & 0040) mode |= S_IRGRP ;
+  if (m & 0100) mode |= S_IXUSR ;
+  if (m & 0200) mode |= S_IWUSR ;
+  if (m & 0400) mode |= S_IRUSR ;
+  if (m & 01000) mode |= S_ISVTX ;
+  if (m & 02000) mode |= S_ISGID ;
+  if (m & 04000) mode |= S_ISUID ;
+
+  if (chmod(argv[2], mode) == -1)
+    strerr_diefu2sys(111, "change mode of ", argv[2]) ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-chown.c b/src/skaembutils/s6-chown.c
new file mode 100644
index 0000000..ef6a4a0
--- /dev/null
+++ b/src/skaembutils/s6-chown.c
@@ -0,0 +1,60 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-chown [ -U ] [ -u uid ] [ -g gid ] file"
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int uid = -1, gid = -1 ;
+  PROG = "s6-chown" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "Uu:g:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'u':
+        {
+          unsigned int u ;
+          if (!uint0_scan(l.arg, &u)) strerr_dieusage(100, USAGE) ;
+          uid = u ;
+          break ;
+        }
+        case 'g':
+        {
+          unsigned int g ;
+          if (!uint0_scan(l.arg, &g)) strerr_dieusage(100, USAGE) ;
+          gid = g ;
+          break ;
+        }
+        case 'U':
+        {
+          unsigned int x ;
+          char const *s = env_get2(envp, "UID") ;
+          if (!s) strerr_dienotset(100, "UID") ;
+          if (!uint0_scan(s, &x)) strerr_dieinvalid(100, "UID") ;
+          uid = x ;
+          s = env_get2(envp, "GID") ;
+          if (!s) strerr_dienotset(100, "GID") ;
+          if (!uint0_scan(s, &x)) strerr_dieinvalid(100, "GID") ;
+          gid = x ;
+          break ;
+        }
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  if (chown(*argv, uid, gid) == -1)
+    strerr_diefu2sys(111, "chown ", argv[0]) ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-clock.c b/src/skaembutils/s6-clock.c
new file mode 100644
index 0000000..822500a
--- /dev/null
+++ b/src/skaembutils/s6-clock.c
@@ -0,0 +1,31 @@
+/* ISC license. */
+
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+
+#define USAGE "s6-clock [ tai64nlabel ]"
+
+static int getit (void)
+{
+  char fmt[TIMESTAMP+1] ;
+  timestamp(fmt) ;
+  fmt[TIMESTAMP] = '\n' ;
+  if (allwrite(1, fmt, TIMESTAMP+1) < TIMESTAMP+1)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
+
+static int setit (char const *h)
+{
+  tain_t a ;
+  if (!timestamp_scan(h, &a)) strerr_dieusage(100, USAGE) ;
+  if (!tain_setnow(&a)) strerr_diefu1sys(111, "taia_setnow") ;
+  return 0 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-clock" ;
+  return (argc < 2) ? getit() : setit(argv[1]) ;
+}
diff --git a/src/skaembutils/s6-cut.c b/src/skaembutils/s6-cut.c
new file mode 100644
index 0000000..bb729d0
--- /dev/null
+++ b/src/skaembutils/s6-cut.c
@@ -0,0 +1,212 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/diuint.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-cut [ -b list | -c list | -f list ] [ -d delim ] [ -n ] [ -s ] [ file... ]"
+
+static int diuint_cmpleft (void const *a, void const *b)
+{
+  return ((diuint const *)a)->left - ((diuint const *)b)->left ;
+}
+
+static void diuintalloc_normalize (genalloc *list)
+{
+  unsigned int i = 1, cur = 0 ;
+  unsigned int len = genalloc_len(diuint, list) ;
+  register diuint *const s = genalloc_s(diuint, list) ;
+  qsort(s, len, sizeof(diuint), &diuint_cmpleft) ;
+  for (; i < len ; i++)
+    if (!s[cur].right) break ;
+    else if (s[i].left > s[cur].right) s[++cur] = s[i] ;
+    else if (s[cur].right < s[i].right)
+      s[cur].right = s[i].right ;
+  genalloc_setlen(diuint, list, cur+1) ;
+}
+
+static void scanlist (genalloc *list, char const *s)
+{
+  register unsigned int i = 0 ;
+  genalloc_setlen(diuint, list, 0) ;
+  while (s[i])
+  {
+    char const sep[4] = ", \t" ;
+    diuint iv ;
+    if (s[i] == '-') iv.left = 1 ;
+    else
+    {
+      unsigned int j = uint_scan(s+i, &iv.left) ;
+      if (!j || !iv.left) strerr_dief2x(100, "invalid list argument: ", s) ;
+      i += j ;
+    }
+    if (s[i] != '-') iv.right = iv.left ;
+    else
+    {
+      unsigned int j = uint_scan(s + ++i, &iv.right) ;
+      if (!j) iv.right = 0 ;
+      else if (iv.right < iv.left)
+        strerr_dief2x(100, "invalid list argument: ", s) ;
+      else i += j ;
+    }
+    switch (byte_chr(sep, 4, s[i]))
+    {
+      case 0 :
+      case 1 :
+      case 2 : i++ ;
+      case 3 : break ;
+      case 4 :
+        strerr_dief2x(100, "invalid list argument: ", s) ;
+    }
+    if (!genalloc_append(diuint, list, &iv))
+      strerr_diefu1sys(111, "build interval list") ;
+  }
+}
+
+static int doit (int fd, diuint const *s, unsigned int len, unsigned int flags, char delim)
+{
+  char buf[BUFFER_INSIZE] ;
+  buffer b = BUFFER_INIT(&buffer_flush1read, fd, buf, BUFFER_INSIZE) ;
+  for (;;)
+  {
+    int r ;
+    satmp.len = 0 ;
+    r = skagetln(&b, &satmp, '\n') ;
+    if ((r == -1) && (errno != EPIPE)) return 0 ;
+    if (!r) break ;
+    if (flags & 2)
+    {
+      register unsigned int i = 0 ;
+      for (; i < len ; i++)
+      {
+        register unsigned int j = s[i].right ;
+        if (s[i].left >= satmp.len) break ;
+        if (!j || (j > satmp.len))
+        {
+          j = satmp.len ;
+          r = 0 ;
+        }
+        if (buffer_put(buffer_1, satmp.s + s[i].left - 1, j + 1 - s[i].left) == -1)
+          return 0 ;
+      }
+    }
+    else
+    {
+      register unsigned int i = 0, j = 0, count = 1 ;
+      for (; i < len ; i++)
+      {
+        for (; count < s[i].left ; count++)
+        {
+          j += byte_chr(satmp.s + j, satmp.len - j, delim) ;
+          if (j == satmp.len) break ;
+          j++ ;
+        }
+        if (j == satmp.len)
+        {
+          if (count == 1)
+          {
+            if ((flags & 1) && (buffer_put(buffer_1, satmp.s, satmp.len) < 0))
+              return 0 ;
+            r = 0 ;
+          }
+          break ;
+        }
+        for (; !s[i].right || (count <= s[i].right) ; count++)
+        {
+          register unsigned int k = byte_chr(satmp.s + j, satmp.len - j, delim) ;
+          if ((count > s[0].left) && (buffer_put(buffer_1, &delim, 1) < 0)) return 0 ;
+          if (buffer_put(buffer_1, satmp.s + j, k) < 0) return 0 ;
+          j += k ;
+          if (j == satmp.len)
+          {
+            r = 0 ;
+            break ;
+          }
+          j++ ;
+        }
+        if (j == satmp.len) break ;
+      }
+    }
+    if ((r > 0) && (buffer_put(buffer_1, "\n", 1) < 0)) return 0 ;
+  }
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  genalloc list = GENALLOC_ZERO ; /* array of diuint */
+  char delim = '\t' ;
+  unsigned int what = 0 ;
+  PROG = "s6-cut" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    int flagnodel = 1 ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nsb:c:f:d:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n': break ;  /* ignored */
+        case 's': flagnodel = 0 ; break ;
+        case 'd': delim = *l.arg ; break ;
+        case 'b':
+        case 'c':
+        {
+          if (what) strerr_dieusage(100, USAGE) ;
+          what = 2 ;
+          scanlist(&list, l.arg) ;
+          break ;
+        }
+        case 'f':
+        {
+          if (what) strerr_dieusage(100, USAGE) ;
+          what = 4 ;
+          scanlist(&list, l.arg) ;
+          break ;
+        }
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    what += flagnodel ;
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!genalloc_len(diuint, &list)) strerr_dieusage(100, USAGE) ;
+  diuintalloc_normalize(&list) ;
+
+  if (!argc)
+  {
+    if (!doit(0, genalloc_s(diuint, &list), genalloc_len(diuint, &list), what, delim))
+      strerr_diefu1sys(111, "cut stdin") ;
+  }
+  else
+  {
+    for (; *argv ; argv++)
+    {
+      if ((argv[0][0] == '-') && !argv[0][1])
+      {
+        if (!doit(0, genalloc_s(diuint, &list), genalloc_len(diuint, &list), what, delim))
+          strerr_diefu1sys(111, "process stdin") ;
+      }
+      else
+      {
+        int fd = open_readb(*argv) ;
+        if (fd == -1)
+          strerr_diefu3sys(111, "open ", *argv, " for reading") ;
+        if (!doit(fd, genalloc_s(diuint, &list), genalloc_len(diuint, &list), what, delim))
+          strerr_diefu2sys(111, "cut ", *argv) ;
+        fd_close(fd) ;
+      }
+    }
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-dirname.c b/src/skaembutils/s6-dirname.c
new file mode 100644
index 0000000..5883180
--- /dev/null
+++ b/src/skaembutils/s6-dirname.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-dirname [ -n ] file"
+
+int main (int argc, char const *const *argv)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  int nl = 1 ;
+  PROG = "s6-dirname" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "n", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  if (!sadirname(&sa, argv[0], str_len(argv[0])))
+    strerr_diefu2sys(111, "get dirname of ", argv[0]) ;
+  if (nl && !stralloc_catb(&sa, "\n", 1))
+    strerr_diefu2sys(111, "get dirname of ", argv[0]) ;
+  if (allwrite(1, sa.s, sa.len) < sa.len)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-echo.c b/src/skaembutils/s6-echo.c
new file mode 100644
index 0000000..6d30576
--- /dev/null
+++ b/src/skaembutils/s6-echo.c
@@ -0,0 +1,38 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-echo [ -n ] [ -s sep ] args..."
+
+int main (int argc, char const *const *argv)
+{
+  char sep = ' ' ;
+  char donl = 1 ;
+  PROG = "s6-echo" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "ns:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n': donl = 0 ; break ;
+        case 's': sep = *l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  for ( ; *argv ; argv++)
+    if ((buffer_puts(buffer_1small, *argv) < 0)
+     || (argv[1] && (buffer_put(buffer_1small, &sep, 1) == -1)))
+      goto err ;
+  if (donl && (buffer_put(buffer_1small, "\n", 1) == -1)) goto err ;
+  if (!buffer_flush(buffer_1small)) goto err ;
+  return 0 ;
+err:
+  strerr_diefu1sys(111, "write to stdout") ;
+}
diff --git a/src/skaembutils/s6-env.c b/src/skaembutils/s6-env.c
new file mode 100644
index 0000000..b2e1312
--- /dev/null
+++ b/src/skaembutils/s6-env.c
@@ -0,0 +1,43 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <s6-portable-utils/config.h>
+
+#define USAGE "s6-env [ -i ] [ name=value... ] prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  stralloc modifs = STRALLOC_ZERO ;
+  char const *arg_zero[2] = { S6_PORTABLE_UTILS_BINPREFIX "s6-printenv", 0 } ;
+  char const *env_zero[1] = { 0 } ;
+  PROG = "s6-env" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "i", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'i': envp = env_zero ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  for (; argc ; argc--, argv++)
+  {
+    if (!(*argv)[str_chr(*argv, '=')]) break ;
+    if (!stralloc_cats(&modifs, *argv) || !stralloc_0(&modifs))
+      strerr_diefu1sys(111, "stralloc_cats") ;
+  }
+  if (!argc) argv = arg_zero ;
+  pathexec_r(argv, envp, env_len(envp), modifs.s, modifs.len) ;
+  stralloc_free(&modifs) ;
+  strerr_dieexec((errno == ENOENT) ? 127 : 126, argv[0]) ;
+}
diff --git a/src/skaembutils/s6-expr.c b/src/skaembutils/s6-expr.c
new file mode 100644
index 0000000..4404d44
--- /dev/null
+++ b/src/skaembutils/s6-expr.c
@@ -0,0 +1,211 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-expr arithmetic expression"
+
+enum opnum
+{
+  T_DATA,
+  T_AND,
+  T_OR,
+  T_LEFTP,
+  T_RIGHTP,
+  T_EQUAL,
+  T_NEQUAL,
+  T_GREATER,
+  T_GREATERE,
+  T_LESSER,
+  T_LESSERE,
+  T_PLUS,
+  T_MINUS,
+  T_TIMES,
+  T_DIV,
+  T_MOD
+} ;
+
+struct token
+{
+  char const *string ;
+  enum opnum op ;
+  unsigned int type ;
+} ;
+
+struct node
+{
+  enum opnum op ;
+  unsigned int type ;
+  unsigned int arg1 ;
+  unsigned int arg2 ;
+  int data ;
+} ;
+
+static unsigned int lex (struct node *tree, char const *const *argv)
+{
+  static struct token const tokens[16] =
+  {
+    { "+", T_PLUS, 3 },
+    { "-", T_MINUS, 3 },
+    { "*", T_TIMES, 2 },
+    { "/", T_DIV, 2 },
+    { "%", T_MOD, 2 },
+    { "(", T_LEFTP, 7 },
+    { ")", T_RIGHTP, 8 },
+    { "=", T_EQUAL, 4 },
+    { "!=", T_NEQUAL, 4 },
+    { "<", T_LESSER, 4 },
+    { "<=", T_LESSERE, 4 },
+    { ">", T_GREATER, 4 },
+    { ">=", T_GREATERE, 4 },
+    { "|", T_OR, 6 },
+    { "&", T_AND, 5 },
+    { 0, 0, 0 }
+  } ;
+  register unsigned int pos = 0 ;
+
+  for (; argv[pos] ; pos++)
+  {
+    register unsigned int i = 0 ;
+    for (i = 0 ; tokens[i].string ; i++)
+      if (!str_diff(argv[pos], tokens[i].string))
+      {
+        tree[pos].op = tokens[i].op ;
+        tree[pos].type = tokens[i].type ;
+        break ;
+      }
+    if (!tokens[i].string)
+    {
+      tree[pos].op = T_DATA ;
+      tree[pos].type = 0 ;
+      if (!int_scan(argv[pos], &tree[pos].data))
+        strerr_dief1x(2, "invalid expression") ;
+    }
+  }
+  return pos ;
+}
+
+static void reduce (struct node *tree, register unsigned int *stack, register unsigned int *sp, unsigned int type)
+{
+  if (tree[stack[*sp-1]].type == type)
+  {
+    tree[stack[*sp-1]].arg1 = stack[*sp-2] ;
+    tree[stack[*sp-1]].arg2 = stack[*sp] ;
+    stack[*sp-2] = stack[*sp-1] ;
+    *sp -= 2 ;
+  }
+  tree[stack[*sp]].type = type + 7 ;
+}
+
+static unsigned int parse (struct node *tree, unsigned int n)
+{
+  static char const table[9][15] =
+  {
+    "xsssssssxzzzzzz",
+    "xxxxxxxx!zzzzzz",
+    "mxxxxxxxMszzzzz",
+    "mxxxxxxxMaszzzz",
+    "mxxxxxxxMacszzz",
+    "mxxxxxxxMacAszz",
+    "mxxxxxxxMacAOsz",
+    "xsssssssxzzzzzz",
+    "mxxxxxxxMacAOEs"
+  } ;
+  unsigned int stack[n] ;
+  unsigned int sp = 0, pos = 0 ;
+  char cont = 1 ;
+  stack[0] = n + 1 ;
+  tree[n].type = 8 ; /* add ) for the final reduce */
+  tree[n+1].type = 1 ; /* add EOF */
+  while (cont)
+  {
+    switch (table[tree[pos].type][tree[stack[sp]].type])
+    {
+      case 'x' :  _exit(2) ;
+      case '!' :  cont = 0 ; break ;
+      case 's' :  stack[++sp] = pos++ ; break ;
+      case 'm' :  reduce(tree, stack, &sp, 2) ; break ;
+      case 'a' :  reduce(tree, stack, &sp, 3) ; break ;
+      case 'c' :  reduce(tree, stack, &sp, 4) ; break ;
+      case 'A' :  reduce(tree, stack, &sp, 5) ; break ;
+      case 'O' :  reduce(tree, stack, &sp, 6) ; break ;
+      case 'E' :  tree[stack[sp]].type = 14 ; break ;
+      case 'M' : 
+      {
+        if (tree[stack[sp-2]].type != 7) _exit(2) ;
+        stack[sp-2] = stack[sp-1] ;
+        sp -= 2 ;
+        reduce(tree, stack, &sp, 2) ;
+        break ;
+      }
+      case 'z' : 
+      default : strerr_dief1x(101, "internal error in parse, please submit a bug-report.") ; /* can't happen */
+    }
+  }
+  if (sp != 2) strerr_dief1x(2, "invalid expression") ;
+  return stack[1] ;
+}
+
+static int run (struct node const *tree, unsigned int root)
+{
+  switch (tree[root].op)
+  {
+    case T_DATA :
+      return tree[root].data ;
+    case T_OR :
+    {
+      int r = run(tree, tree[root].arg1) ;
+      return r ? r : run(tree, tree[root].arg2) ;
+    }
+    case T_AND :
+    {
+      int r = run(tree, tree[root].arg1) ;
+      return r ? run(tree, tree[root].arg2) ? r : 0 : 0 ;
+    }
+    case T_EQUAL :
+      return run(tree, tree[root].arg1) == run(tree, tree[root].arg2) ;
+    case T_NEQUAL :
+      return run(tree, tree[root].arg1) != run(tree, tree[root].arg2) ;
+    case T_GREATER :
+      return run(tree, tree[root].arg1) > run(tree, tree[root].arg2) ;
+    case T_GREATERE :
+      return run(tree, tree[root].arg1) >= run(tree, tree[root].arg2) ;
+    case T_LESSER :
+      return run(tree, tree[root].arg1) < run(tree, tree[root].arg2) ;
+    case T_LESSERE :
+      return run(tree, tree[root].arg1) <= run(tree, tree[root].arg2) ;
+    case T_PLUS :
+      return run(tree, tree[root].arg1) + run(tree, tree[root].arg2) ;
+    case T_MINUS :
+      return run(tree, tree[root].arg1) - run(tree, tree[root].arg2) ;
+    case T_TIMES :
+      return run(tree, tree[root].arg1) * run(tree, tree[root].arg2) ;
+    case T_DIV :
+      return run(tree, tree[root].arg1) / run(tree, tree[root].arg2) ;
+    case T_MOD :
+      return run(tree, tree[root].arg1) % run(tree, tree[root].arg2) ;
+    default: strerr_dief1x(101, "internal error in run, please submit a bug-report") ;
+  }
+}
+
+int main (int argc, char const *const *argv)
+{
+  char fmt[UINT_FMT+1] ;
+  int val ;
+  unsigned int len ;
+  PROG = "s6-expr" ;
+  if (argc <= 1) return 2 ;
+  {
+    struct node tree[argc + 1] ;
+    val = run(tree, parse(tree, lex(tree, argv+1))) ;
+  }
+  len = int_fmt(fmt, val) ;
+  fmt[len++] = '\n' ;
+  if (allwrite(1, fmt, len) < len)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return !val ;
+}
diff --git a/src/skaembutils/s6-false.c b/src/skaembutils/s6-false.c
new file mode 100644
index 0000000..fb13dcd
--- /dev/null
+++ b/src/skaembutils/s6-false.c
@@ -0,0 +1,6 @@
+/* ISC license. */
+
+int main ()
+{
+  return 1 ;
+}
diff --git a/src/skaembutils/s6-format-filter.c b/src/skaembutils/s6-format-filter.c
new file mode 100644
index 0000000..78e4437
--- /dev/null
+++ b/src/skaembutils/s6-format-filter.c
@@ -0,0 +1,61 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-format-filter format [ args... ]"
+
+int main (int argc, char const *const *argv)
+{
+  stralloc src = STRALLOC_ZERO ;
+  stralloc dst = STRALLOC_ZERO ;
+  char vars[12] = "s0123456789" ;
+  char const *args[12] = { "" } ;
+  char const *format ;
+  PROG = "s6-format-filter" ;
+  argc-- ; args[1] = *argv++ ;
+  if (!argc--) strerr_dieusage(100, USAGE) ;
+  format = *argv++ ;
+  if (argc > 9) argc = 9 ;
+  vars[argc+2] = 0 ;
+  {
+    register unsigned int i = 0 ;
+    for (; i < (unsigned int)argc ; i++) args[2+i] = argv[i] ;
+  }
+  if (!string_format(&dst, vars, format, args))
+    strerr_diefu1sys(111, "compile format") ;
+
+  for (;;)
+  {
+    register int r ;
+    src.len = 0 ;
+    dst.len = 0 ;
+    r = skagetln(buffer_0f1, &src, '\n') ;
+    if (!r) break ;
+    else if (r < 0)
+    {
+      if ((errno != EPIPE) || !stralloc_0(&src))
+        strerr_diefu1sys(111, "read from stdin") ;
+    }
+    else src.s[src.len-1] = 0 ;
+    args[0] = src.s ;    
+    if (!string_format(&dst, vars, format, args))
+    {
+      int e = errno ;
+      buffer_flush(buffer_1) ;
+      errno = e ;
+      strerr_diefu1sys(111, "format") ;
+    }
+    if (r > 0)
+    {
+      if (!stralloc_catb(&dst, "\n", 1))
+        strerr_diefu1sys(111, "format") ;
+    }
+    if (buffer_put(buffer_1, dst.s, dst.len) < 0)
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-grep.c b/src/skaembutils/s6-grep.c
new file mode 100644
index 0000000..cd991e7
--- /dev/null
+++ b/src/skaembutils/s6-grep.c
@@ -0,0 +1,136 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <regex.h>
+#include <string.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-grep [ -E | -F ] [ -i ] [ -c ] [ -n ] [ -q ] [ -v ] pattern"
+
+typedef struct flags_s flags_t, *flags_t_ref ;
+struct flags_s
+{
+  unsigned int extended : 1 ;
+  unsigned int ignorecase: 1 ;
+  unsigned int fixed : 1 ;
+  unsigned int count : 1 ;
+  unsigned int num : 1 ;
+  unsigned int quiet : 1 ;
+  unsigned int not : 1 ;
+} ;
+#define FLAGS_ZERO { .extended = 0, .ignorecase = 0, .fixed = 0, .count = 0, .num = 0, .quiet = 0, .not = 0 }
+
+int main (int argc, char const *const *argv)
+{
+  unsigned int count = 0 ;
+  flags_t flags = FLAGS_ZERO ;
+  PROG = "s6-grep" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "EFicnqv", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'E': flags.extended = 1 ; break ;
+        case 'F': flags.fixed = 1 ; break ;
+        case 'i': flags.ignorecase = 1 ; break ;
+        case 'c': flags.count = 1 ; break ;
+        case 'n': flags.num = 1 ; break ;
+        case 'q': flags.quiet = 1 ; break ;
+        case 'v': flags.not = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  {
+    stralloc line = STRALLOC_ZERO ;
+    regex_t re ;
+    unsigned int num = 0 ;
+    unsigned int arglen ;
+    if (flags.fixed)
+    {
+      if (flags.ignorecase) arglen = str_len(argv[0]) ;
+    }
+    else
+    {
+      register int e = regcomp(&re, argv[0], REG_NOSUB | (flags.extended ? REG_EXTENDED : 0) | (flags.ignorecase ? REG_ICASE : 0)) ;
+      if (e)
+      {
+        char buf[256] ;
+        regerror(e, &re, buf, 256) ;
+        strerr_diefu2x(111, "compile regular expression: ", buf) ;
+      }
+    }
+
+    for (;;)
+    {
+      register int r ;
+      line.len = 0 ;
+      r = skagetln(buffer_0f1, &line, '\n') ;
+      if (!r) break ;
+      if (r < 0)
+      {
+        if ((errno != EPIPE) || !stralloc_catb(&line, "\n", 1))
+          strerr_diefu1sys(111, "read from stdin") ;
+      }
+      num++ ; line.s[line.len-1] = 0 ;
+      if (flags.fixed)
+      {
+        if (flags.ignorecase)
+          r = case_str(line.s, argv[0]) >= arglen ;
+        else
+          r = !strstr(line.s, argv[0]) ;
+      }
+      else
+      {
+        r = regexec(&re, line.s, 0, 0, 0) ;
+        if (r && r != REG_NOMATCH)
+        {
+          char buf[256] ;
+          regerror(r, &re, buf, 256) ;
+          strerr_diefu2x(111, "match regular expression: ", buf) ;
+        }
+      }
+      line.s[line.len-1] = '\n' ;
+      if (!r ^ flags.not)
+      {
+        count++ ;
+        if (!flags.quiet && !flags.count)
+        {
+          if (flags.num)
+          {
+            char fmt[UINT_FMT] ;
+            register unsigned int n = uint_fmt(fmt, num) ;
+            fmt[n++] = ':' ;
+            if (buffer_put(buffer_1, fmt, n) < (int)n)
+              strerr_diefu1sys(111, "write to stdout") ;
+          }
+          if (buffer_put(buffer_1, line.s, line.len) < (int)line.len)
+            strerr_diefu1sys(111, "write to stdout") ;
+        }
+      }
+    }
+    if (flags.quiet) return !count ;
+    stralloc_free(&line) ;
+    if (!flags.fixed) regfree(&re) ;
+  }
+  if (flags.count)
+  {
+    char fmt[UINT_FMT] ;
+    register unsigned int n = uint_fmt(fmt, count) ;
+    fmt[n++] = '\n' ;
+    if (buffer_put(buffer_1, fmt, n) < (int)n)
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+  return !count ;
+}
diff --git a/src/skaembutils/s6-head.c b/src/skaembutils/s6-head.c
new file mode 100644
index 0000000..1ef24c0
--- /dev/null
+++ b/src/skaembutils/s6-head.c
@@ -0,0 +1,159 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/buffer.h>
+#include <skalibs/siovec.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-head [ -S ] [ -1..9 | -n lines | -c chars ] [ file... ]"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+typedef int headfunc_t (int, unsigned int) ;
+typedef headfunc_t *headfunc_t_ref ;
+
+static int dolines (int fd, unsigned int lines)
+{
+  char buf[BUFFER_INSIZE] ;
+  buffer in = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE) ;
+  buffer out = BUFFER_INIT(&buffer_write, 1, buf, BUFFER_INSIZE) ;
+  siovec_t v[2] ;
+  while (lines)
+  {
+    unsigned int w = 0 ;
+    register int r = buffer_fill(&in) ;
+    if (r <= 0) return !r ;
+    out.c.n = in.c.n ; out.c.p = in.c.p ;
+    buffer_rpeek(&in, v) ;
+    for (;;)
+    {
+      unsigned int n = siovec_len(v, 2) ;
+      register unsigned int i ;
+      if (!n) break ;
+      i = siovec_bytechr(v, 2, '\n') ;
+      if (i < n)
+      {
+        w += i+1 ;
+        siovec_seek(v, 2, i+1) ;
+        if (!--lines)
+        {
+          out.c.n = (out.c.p + w) % out.c.a ;
+          break ;
+        }
+      }
+      else siovec_seek(v, 2, i) ;
+    }
+    if (!buffer_flush(&out)) return 0 ;
+    in.c.n = out.c.n ; in.c.p = out.c.p ;
+  }
+  return 1 ;
+}
+
+static int safedolines (int fd, unsigned int lines)
+{
+  char tmp[lines] ;
+  while (lines)
+  {
+    unsigned int r = allread(fd, tmp, lines) ;
+    if ((r < lines) && (errno != EPIPE)) return 0 ;
+    lines -= byte_count(tmp, r, '\n') ;
+    if (buffer_put(buffer_1, tmp, r) < (int)r) return 0 ;
+  }
+  if (!buffer_flush(buffer_1)) return 0 ;
+  return 1 ;
+}
+
+static int safedochars (int fd, unsigned int chars)
+{
+  return (fd_catn(fd, 1, chars) >= chars) ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  headfunc_t_ref f ;
+  unsigned int lines = 10 ;
+  int islines = 1, safe = 0 ;
+  PROG = "s6-head" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    int done = 0 ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "S123456789n:c:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'S' : safe = 1 ; break ;
+        case '1' :
+        case '2' :
+        case '3' :
+        case '4' :
+        case '5' :
+        case '6' :
+        case '7' :
+        case '8' :
+        case '9' :
+        {
+          if (done) dieusage() ;
+          islines = 1 ;
+          lines = opt - '0' ;
+          done = 1 ;
+          break ;
+        }
+        case 'n' :
+        {
+          if (done || !uint0_scan(l.arg, &lines))
+            strerr_dieusage(100, USAGE) ;
+          islines = 1 ;
+          done = 1 ;
+          break ;
+        }
+        case 'c' :
+        {
+          if (done || !uint0_scan(l.arg, &lines))
+            strerr_dieusage(100, USAGE) ;
+          islines = 0 ;
+          done = 1 ;
+          break ;
+        }
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc) safe = 0 ;
+  f = islines ? safe ? &safedolines : &dolines : &safedochars ;
+  if (!argc)
+  {
+    if (!(*f)(0, lines))
+      strerr_diefu1sys(111, "head stdin") ;
+  }
+  else
+  {
+    unsigned int i = 0 ;
+    for (; argv[i] ; i++)
+    {
+      int fd ;
+      if (argc >= 2)
+      {
+        if (i) buffer_putnoflush(buffer_1, "\n", 1) ;
+        buffer_putnoflush(buffer_1, "==> ", 4) ;
+        if ((buffer_puts(buffer_1, argv[i]) <= 0)
+         || (buffer_putflush(buffer_1, " <==\n", 5) < 0))
+          strerr_diefu1sys(111, "write to stdout") ;
+      }
+      if ((argv[i][0] == '-') && !argv[i][1]) fd = 0 ;
+      else fd = open_readb(argv[i]) ;
+      if (fd == -1)
+        strerr_diefu3sys(111, "open ", argv[i], " for reading") ;
+      if (!(*f)(fd, lines))
+        strerr_diefu2sys(111, "head ", argv[i]) ;
+      fd_close(fd) ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-linkname.c b/src/skaembutils/s6-linkname.c
new file mode 100644
index 0000000..a9425a1
--- /dev/null
+++ b/src/skaembutils/s6-linkname.c
@@ -0,0 +1,45 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-linkname [ -n ] [ -f ] link"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  int path = 0, nl = 1 ;
+  PROG = "s6-linkname" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nf", &l) ;
+      if (opt == -1) break ;
+      switch(opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        case 'f' : path = 1 ; break ;
+        default :  dieusage() ;
+      }
+    }
+    argv += l.ind ; argc -= l.ind ;
+  }
+  if (!argc) dieusage() ;
+
+  if ((path ? sarealpath(&sa, *argv) : sareadlink(&sa, *argv)) < 0)
+    strerr_diefu2sys(111, "resolve ", *argv) ;
+
+  if ((buffer_put(buffer_1small, sa.s, sa.len) < 0)
+   || (nl && (buffer_put(buffer_1small, "\n", 1)) < 0)
+   || (!buffer_flush(buffer_1small)))
+    strerr_diefu1sys(111, "write to stdout") ;
+
+ /* stralloc_free(&sa) ; */
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-ln.c b/src/skaembutils/s6-ln.c
new file mode 100644
index 0000000..7f6f7b6
--- /dev/null
+++ b/src/skaembutils/s6-ln.c
@@ -0,0 +1,145 @@
+/* ISC license. */
+
+#include <skalibs/sysdeps.h>
+
+#ifdef SKALIBS_HASLINKAT
+#include <skalibs/nonposix.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/random.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-ln [ -s ] [ -f ] [ -L ] [ -P ] src... dest"
+
+typedef int linkfunc_t (char const *, char const *) ;
+typedef linkfunc_t *linkfunc_t_ref ;
+
+typedef void ln_t (char const *, char const *, linkfunc_t_ref) ;
+typedef ln_t *ln_t_ref ;
+
+#ifdef SKALIBS_HASLINKAT
+
+static int linknoderef (char const *old, char const *new)
+{
+  return linkat(AT_FDCWD, old, AT_FDCWD, new, 0) ;
+}
+
+static int linkderef (char const *old, char const *new)
+{
+  return linkat(AT_FDCWD, old, AT_FDCWD, new, AT_SYMLINK_FOLLOW) ;
+}
+
+#else /* can't implement SUSv4, default to link */
+
+# define linknoderef link
+# define linkderef link
+
+#endif
+
+static void force (char const *old, char const *new, linkfunc_t_ref doit)
+{
+  if ((*doit)(old, new) == -1)
+  {
+    unsigned int base = satmp.len ;
+    if (errno != EEXIST)
+      strerr_diefu5sys(111, "make a link", " from ", new, " to ", old) ;
+    if (!stralloc_catb(&satmp, new, str_len(new))
+     || (random_sauniquename(&satmp, 8) == -1)
+     || !stralloc_0(&satmp))
+      strerr_diefu2sys(111, "make a unique name for ", old) ;
+    if ((*doit)(old, satmp.s + base) == -1)
+      strerr_diefu3sys(111, "make a link", " to ", old) ;
+    if (rename(satmp.s + base, new) == -1)
+    {
+      unlink(satmp.s + base) ;
+      strerr_diefu2sys(111, "atomically replace ", new) ;
+    }
+    satmp.len = base ;
+  }
+}
+
+static void noforce (char const *old, char const *new, linkfunc_t_ref doit)
+{
+  if ((*doit)(old, new) == -1)
+    strerr_diefu5sys(111, "make a link", " from ", new, " to ", old) ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  linkfunc_t_ref mylink = &link ; /* default to system behaviour */
+  ln_t_ref f = &noforce ;
+  PROG = "s6-ln" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "sfLP", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 's': mylink = &symlink ; break ;
+        case 'f': f = &force ; break ;
+        case 'L': if (mylink != &symlink) mylink = &linkderef ; break ;
+        case 'P': if (mylink != &symlink) mylink = &linknoderef ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if (argc > 2)
+  {
+    stralloc sa = STRALLOC_ZERO ;
+    unsigned int i = 0 ;
+    unsigned int base ;
+    if (!stralloc_cats(&sa, argv[argc-1]) || !stralloc_catb(&sa, "/", 1))
+      strerr_diefu1sys(111, "stralloc_cats") ;
+    base = sa.len ;
+    for (; i < (unsigned int)(argc-1) ; i++)
+    {
+      sa.len = base ;
+      if (!sabasename(&sa, argv[i], str_len(argv[i])))
+        strerr_diefu1sys(111, "sabasename") ;
+      if (!stralloc_0(&sa)) strerr_diefu1sys(111, "stralloc_0") ;
+      (*f)(argv[i], sa.s, mylink) ;
+    }
+    return 0 ;
+  }
+
+  {
+    struct stat st ;
+    if (stat(argv[1], &st) < 0)
+    {
+      if (errno != ENOENT) strerr_diefu2sys(111, "stat ", argv[1]) ;
+      (*f)(argv[0], argv[1], mylink) ;
+      return 0 ;
+    }
+    if (!S_ISDIR(st.st_mode))
+    {
+      (*f)(argv[0], argv[1], mylink) ;
+      return 0 ;
+    }
+  }
+
+  {
+    stralloc sa = STRALLOC_ZERO ;
+    if (!stralloc_cats(&sa, argv[1])
+      || !stralloc_catb(&sa, "/", 1)
+      || !sabasename(&sa, argv[0], str_len(argv[0]))
+      || !stralloc_0(&sa))
+        strerr_diefu1sys(111, "stralloc_catb") ;
+      (*f)(argv[0], sa.s, mylink) ;
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-ls.c b/src/skaembutils/s6-ls.c
new file mode 100644
index 0000000..25045a7
--- /dev/null
+++ b/src/skaembutils/s6-ls.c
@@ -0,0 +1,56 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/direntry.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-ls [ -0 ] [ -a | -A ] [ -x exclude ] dir"
+
+int main (int argc, char const *const *argv)
+{
+  unsigned int all = 0 ;
+  char const *exclude = 0 ;
+  char delim = '\n' ;
+  PROG = "s6-ls" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "0aAx:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case '0': delim = '\0' ; break ;
+        case 'a': all = 1 ; break ;
+        case 'A': all = 2 ; break ;
+        case 'x': exclude = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  {
+    direntry *d ;
+    DIR *dir = opendir(*argv) ;
+    if (!dir)
+      strerr_diefu2sys(111, "open directory ", *argv) ;
+    while ((d = readdir(dir)))
+    {
+      if ((d->d_name[0] == '.') && (all < 2))
+      {
+        if (!all || !d->d_name[1] || ((d->d_name[1] == '.') && !d->d_name[2])) continue ;
+      }
+      if (exclude && !str_diff(exclude, d->d_name)) continue ;
+      if ((buffer_puts(buffer_1, d->d_name) < 0)
+       || (buffer_put(buffer_1, &delim, 1) < 0))
+        strerr_diefu1sys(111, "write to stdout") ;
+    }
+    dir_close(dir) ;
+  }
+  if (!buffer_flush(buffer_1))
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-maximumtime.c b/src/skaembutils/s6-maximumtime.c
new file mode 100644
index 0000000..571914f
--- /dev/null
+++ b/src/skaembutils/s6-maximumtime.c
@@ -0,0 +1,99 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <errno.h>
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/error.h>
+#include <skalibs/sig.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-maximumtime [ -0 | -a | -b | -i | -k | -q | -t | -x | -1 | -2 ] milliseconds prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int timeout ;
+  tain_t stamp, deadline ;
+  iopause_fd x[1] = { { .fd = -1, .events = IOPAUSE_READ, .revents = 0 } } ;
+  pid_t pid = 0 ;
+  int tosend = SIGTERM ;
+  PROG = "s6-maximumtime" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "0abikqtx12", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case '0': tosend = 0 ; break ;
+        case 'a': tosend = SIGALRM ; break ;
+        case 'b': tosend = SIGABRT ; break ;
+        case 'i': tosend = SIGINT ; break ;
+        case 'k': tosend = SIGKILL ; break ;
+        case 'q': tosend = SIGQUIT ; break ;
+        case 't': tosend = SIGTERM ; break ;
+        case 'x': tosend = SIGXCPU ; break ;
+        case '1': tosend = SIGUSR1 ; break ;
+        case '2': tosend = SIGUSR2 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if ((argc < 2) || !uint0_scan(argv[0], &timeout)) strerr_dieusage(100, USAGE) ;
+  if (!timeout) timeout = 1 ;
+  if (!tain_from_millisecs(&deadline, timeout))
+    strerr_diefu1sys(111, "taia_from_millisecs") ;
+
+  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") ;
+
+  pid = child_spawn0(argv[1], argv+1, envp) ;
+  if (!pid)  strerr_diefu2sys(111, "spawn ", argv[1]) ;
+  tain_now(&stamp) ;
+  tain_add(&deadline, &deadline, &stamp) ;
+
+  for (;;)
+  {
+    int r = iopause_stamp(x, 1, &deadline, &stamp) ;
+    if (r < 0) strerr_diefu1sys(111, "iopause") ;
+    if (!r) break ;
+    if (x[0].revents & IOPAUSE_READ)
+    {
+      int cont = 1 ;
+      while (cont)
+      {
+        switch (selfpipe_read())
+        {
+          case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+          case 0 : cont = 0 ; break ;
+          case SIGCHLD :
+          {
+            int wstat ;
+            if (wait_pid_nohang(pid, &wstat) == pid)
+            {
+              if (WIFSIGNALED(wstat))
+                strerr_diew1x(111, "child process crashed") ;
+              else return WEXITSTATUS(wstat) ;
+            }
+          }
+          default : strerr_diefu1x(101, "internal error, please submit a bug-report.") ;
+        }
+      }
+    }
+  }
+  kill(pid, tosend) ;
+  errno = ETIMEDOUT ;
+  strerr_diewu1sys(99, "wait for child process") ;
+}
diff --git a/src/skaembutils/s6-mkdir.c b/src/skaembutils/s6-mkdir.c
new file mode 100644
index 0000000..92bae67
--- /dev/null
+++ b/src/skaembutils/s6-mkdir.c
@@ -0,0 +1,79 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-mkdir [ -p ] [ -v ] [ -m mode ] dir"
+
+static int doit (char const *s, unsigned int mode, int verbose, int ee)
+{
+  if (mkdir(s, mode) == -1)
+  {
+    if (ee || (errno != EEXIST))
+    {
+      strerr_warnwu2sys("mkdir ", s) ;
+      return 111 ;
+    }
+  }
+  else if (verbose)
+  {
+    buffer_puts(buffer_2, PROG) ;
+    buffer_puts(buffer_2, ": created directory ") ;
+    buffer_puts(buffer_2, s) ;
+    buffer_putflush(buffer_2, "\n", 1) ;
+  }
+  return 0 ;
+}
+
+static int doparents (char const *s, unsigned int mode, int verbose)
+{
+  unsigned int n = str_len(s), i = 0 ;
+  char tmp[n+1] ;
+  for (; i < n ; i++)
+  {
+    if ((s[i] == '/') && i)
+    {
+      register int e ;
+      tmp[i] = 0 ;
+      e = doit(tmp, mode, verbose, 0) ;
+      if (e) return e ;
+    }
+    tmp[i] = s[i] ;
+  }
+  return doit(s, mode, verbose, 0) ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  int parents = 0, verbose = 0 ;
+  unsigned int mode = 0777 ;
+  int e = 0 ;
+  PROG = "s6-mkdir" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "pvm:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'p': parents = 1 ; break ;
+        case 'v': verbose = 1 ; break ;
+        case 'm': if (uint_oscan(l.arg, &mode)) break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  for ( ; *argv ; argv++)
+    e |= parents ? doparents(*argv, mode, verbose) :
+                   doit(*argv, mode, verbose, 1) ;
+  return e ;
+}
diff --git a/src/skaembutils/s6-mkfifo.c b/src/skaembutils/s6-mkfifo.c
new file mode 100644
index 0000000..9278eb9
--- /dev/null
+++ b/src/skaembutils/s6-mkfifo.c
@@ -0,0 +1,36 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-mkfifo [ -m mode ] fifo..."
+
+int main (int argc, char const *const *argv)
+{
+  unsigned int mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH ;
+  PROG = "s6-mkfifo" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "m:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'm': if (uint0_oscan(l.arg, &mode)) break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  umask(S_IXUSR|S_IXGRP|S_IXOTH) ;
+  for (; *argv ; argv++)
+    if (mkfifo(*argv, mode) < 0)
+      strerr_diefu2sys(111, "mkfifo ", *argv) ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-nice.c b/src/skaembutils/s6-nice.c
new file mode 100644
index 0000000..ec9f477
--- /dev/null
+++ b/src/skaembutils/s6-nice.c
@@ -0,0 +1,46 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-nice [ -I | -i ] [ -n value ] prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int incr = 10 ;
+  int strict = 0 ;
+  PROG = "s6-nice" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "Iin:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'I' : strict = 0 ; break ;
+        case 'i' : strict = 1 ; break ;
+        case 'n': if (!int_scan(l.arg, &incr)) strerr_dieusage(100, USAGE) ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+
+  errno = 0 ;
+  if ((nice(incr) < 0) && errno)
+  {
+    char fmt[1+UINT_FMT] ;
+    fmt[int_fmt(fmt, incr)] = 0 ;
+    if (strict) strerr_diefu2sys(111, "nice to ", fmt) ;
+    else strerr_warnwu2sys("nice to ", fmt) ;
+  }
+  pathexec_run(argv[0], argv, envp) ;
+  strerr_dieexec((errno == ENOENT) ? 127 : 126, argv[0]) ;
+}
diff --git a/src/skaembutils/s6-nuke.c b/src/skaembutils/s6-nuke.c
new file mode 100644
index 0000000..cda1279
--- /dev/null
+++ b/src/skaembutils/s6-nuke.c
@@ -0,0 +1,50 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <signal.h>
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/sig.h>
+
+#define USAGE "s6-nuke [ -h | -t | -k ]"
+
+int main (int argc, char const *const *argv)
+{
+  int doterm = 0, dohangup = 0, dokill = 0 ;
+  PROG = "s6-nuke" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "htk", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'h': dohangup = 1 ; break ;
+        case 't': doterm = 1 ; break ;
+        case 'k': dokill = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (dohangup)
+  {
+    sig_ignore(SIGHUP) ;
+    kill(-1, SIGHUP) ;
+  }
+
+  if (doterm)
+  {
+    sig_ignore(SIGTERM) ;
+    kill(-1, SIGTERM) ;
+    kill(-1, SIGCONT) ;
+  }
+
+  if (dokill) kill(-1, SIGKILL) ;
+
+  if (errno) strerr_diefu1sys(111, "kill") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-pause.c b/src/skaembutils/s6-pause.c
new file mode 100644
index 0000000..8eafdce
--- /dev/null
+++ b/src/skaembutils/s6-pause.c
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#include <unistd.h>
+
+int main ()
+{
+  pause() ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-printenv.c b/src/skaembutils/s6-printenv.c
new file mode 100644
index 0000000..3f4571b
--- /dev/null
+++ b/src/skaembutils/s6-printenv.c
@@ -0,0 +1,51 @@
+/* ISC license. */
+
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/netstring.h>
+
+#define USAGE "s6-printenv [ -n ] [ -0 | -d delimchar ]"
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  char delim = '\n' ;
+  int zero = 0, nl = 1 ;
+  PROG = "s6-printenv" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nd:0", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        case 'd' : delim = *l.arg ; break ;
+        case '0' : zero = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (zero) delim = 0 ;
+  for (; *envp ; envp++)
+  {
+    if (delim || zero)
+    {
+      if ((buffer_puts(buffer_1, *envp) < 0)
+       || ((nl || envp[1]) && (buffer_put(buffer_1, &delim, 1) < 0)))
+        strerr_diefu1sys(111, "write to stdout") ;
+    }
+    else
+    {
+      unsigned int written = 0 ;
+      if (!netstring_put(buffer_1, *envp, str_len(*envp), &written))
+        strerr_diefu1sys(111, "write a netstring to stdout") ;
+    }
+  }
+  if (!buffer_flush(buffer_1))
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-quote-filter.c b/src/skaembutils/s6-quote-filter.c
new file mode 100644
index 0000000..d9489a1
--- /dev/null
+++ b/src/skaembutils/s6-quote-filter.c
@@ -0,0 +1,72 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-quote-filter [ -u ] [ -d delim ]"
+
+int main (int argc, char const *const *argv)
+{
+  stralloc src = STRALLOC_ZERO ;
+  stralloc dst = STRALLOC_ZERO ;
+  char const *delim = "\"" ;
+  unsigned int delimlen ;
+  unsigned int startquote = 1 ;
+  PROG = "s6-quote-filter" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "ud:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'u' : startquote = 0 ; break ;
+        case 'd': delim = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  delimlen = str_len(delim) ;
+  if (startquote)
+  {
+    if(!delimlen) strerr_dief1x(100, "no character to quote with!") ;
+    if (!stralloc_catb(&dst, delim, 1))
+      strerr_diefu1sys(111, "stralloc_catb") ;
+  }
+  for (;;)
+  {
+    int r ;
+    src.len = 0 ;
+    r = skagetln(buffer_0f1, &src, '\n') ;
+    if (!r) break ;
+    if ((r < 0) && (errno != EPIPE))
+      strerr_diefu1sys(111, "read from stdin") ;
+    dst.len = startquote ;
+    if (!string_quote_nodelim_mustquote(&dst, src.s, src.len - (r > 0), delim, delimlen))
+    {
+      int e = errno ;
+      buffer_flush(buffer_1) ;
+      errno = e ;
+      strerr_diefu1sys(111, "quote") ;
+    }
+    if (startquote)
+    {
+      if (!stralloc_catb(&dst, delim, 1))
+        strerr_diefu1sys(111, "stralloc_catb") ;
+    }
+    if (r > 0)
+    {
+      if (!stralloc_catb(&dst, "\n", 1))
+        strerr_diefu1sys(111, "stralloc_catb") ;
+    }
+    if (buffer_put(buffer_1, dst.s, dst.len) < 0)
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-quote.c b/src/skaembutils/s6-quote.c
new file mode 100644
index 0000000..b370e39
--- /dev/null
+++ b/src/skaembutils/s6-quote.c
@@ -0,0 +1,58 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-quote [ -n ] [ -u ] [ -d delim ] string"
+
+int main (int argc, char const *const *argv)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  char const *delim = "\"" ;
+  unsigned int delimlen ;
+  int nl = 1 ;
+  int startquote = 1 ;
+  PROG = "s6-quote" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nud:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        case 'u' : startquote = 0 ; break ;
+        case 'd': delim = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  delimlen = str_len(delim) ;
+  if (startquote)
+  {
+    if (!delimlen) strerr_dief1x(100, "no character to quote with!") ;
+    if (!stralloc_catb(&sa, delim, 1))
+      strerr_diefu1sys(111, "stralloc_catb") ;
+  }
+  if (!string_quote_nodelim_mustquote(&sa, *argv, str_len(*argv), delim, delimlen))
+    strerr_diefu1sys(111, "quote") ;
+  if (startquote)
+  {
+    if (!stralloc_catb(&sa, delim, 1))
+      strerr_diefu1sys(111, "stralloc_catb") ;
+  }
+  if (nl)
+  {
+    if (!stralloc_catb(&sa, "\n", 1))
+      strerr_diefu1sys(111, "stralloc_catb") ;
+  }
+  if (allwrite(1, sa.s, sa.len) < sa.len)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-rename.c b/src/skaembutils/s6-rename.c
new file mode 100644
index 0000000..55c92fa
--- /dev/null
+++ b/src/skaembutils/s6-rename.c
@@ -0,0 +1,16 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-rename old new"
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-rename" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (rename(argv[1], argv[2]) == -1)
+    strerr_diefu4sys(111, "rename ", argv[1], " to ", argv[2]) ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-rmrf.c b/src/skaembutils/s6-rmrf.c
new file mode 100644
index 0000000..4d57530
--- /dev/null
+++ b/src/skaembutils/s6-rmrf.c
@@ -0,0 +1,17 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-rmrf file ..."
+
+int main (int argc, char const *const *argv)
+{
+  char const *const *p = argv + 1 ;
+  PROG = "s6-rmrf" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  for (; *p ; p++)
+    if (rm_rf(*p) == -1)
+      strerr_diefu2sys(111, "remove ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-sleep.c b/src/skaembutils/s6-sleep.c
new file mode 100644
index 0000000..ea25514
--- /dev/null
+++ b/src/skaembutils/s6-sleep.c
@@ -0,0 +1,46 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/uint.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+
+#define USAGE "s6-sleep [ -m ] duration prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  unsigned int n ;
+  int milli = 0 ;
+  PROG = "s6-sleep" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "m", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'm': milli = 1 ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  if (!uint0_scan(argv[0], &n)) strerr_dieusage(100, USAGE) ;
+
+  {
+    tain_t deadline ;
+    if (milli) tain_from_millisecs(&deadline, n) ;
+    else tain_uint(&deadline, n) ;
+    tain_now_g() ;
+    tain_add_g(&deadline, &deadline) ;
+    deepsleepuntil_g(&deadline) ;
+  }
+
+  pathexec0_run(argv+1, envp) ;
+  strerr_dieexec(111, argv[1]) ;
+}
diff --git a/src/skaembutils/s6-sort.c b/src/skaembutils/s6-sort.c
new file mode 100644
index 0000000..5b259b8
--- /dev/null
+++ b/src/skaembutils/s6-sort.c
@@ -0,0 +1,123 @@
+/* ISC license. */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-sort [ -bcfru0 ]"
+
+typedef int strncmp_t (char const *, unsigned int, char const *) ;
+typedef strncmp_t *strncmp_t_ref ;
+typedef int qsortcmp_t (void const *, void const *) ;
+typedef qsortcmp_t *qsortcmp_t_ref ;
+
+static int flagnoblanks = 0, flagreverse = 0, flaguniq = 0 ;
+
+static int str_diffb_f (register char const *s1, register unsigned int n, register char const *s2)
+{
+  return str_diffb(s1, n, s2) ;
+}
+
+static strncmp_t_ref comp = &str_diffb_f ;
+
+static int compit (register char const *s1, register unsigned int n1, register char const *s2, register unsigned int n2)
+{
+  register int r ;
+  if (flagnoblanks)
+  {
+    while ((*s1 == ' ') || (*s1 == '\t')) (s1++, n1--) ;
+    while ((*s2 == ' ') || (*s2 == '\t')) (s2++, n2--) ;
+  }
+  r = (*comp)(s1, n1 < n2 ? n1 : n2, s2) ;
+  if (!r) r = n1 - n2 ;
+  return flagreverse ? -r : r ;
+}
+
+static int sacmp (stralloc const *a, stralloc const *b)
+{
+  return compit(a->s, a->len - 1, b->s, b->len - 1) ;
+}
+
+static int slurplines (genalloc *lines, char sep)
+{
+  unsigned int i = 0 ;
+  for (;; i++)
+  {
+    stralloc sa = STRALLOC_ZERO ;
+    int r = skagetln(buffer_0, &sa, sep) ;
+    if (!r) break ;
+    if ((r < 0) && ((errno != EPIPE) || !stralloc_catb(&sa, &sep, 1)))
+      return -1 ;
+    stralloc_shrink(&sa) ;
+    if (!genalloc_append(stralloc, lines, &sa)) return -1 ;
+  }
+  return (int)i ;
+}
+
+static void uniq (genalloc *lines)
+{
+  unsigned int len = genalloc_len(stralloc, lines) ;
+  register stralloc *s = genalloc_s(stralloc, lines) ;
+  register unsigned int i = 1 ;
+  for (; i < len ; i++)
+    if (!sacmp(s+i-1, s+i)) stralloc_free(s+i-1) ;
+}
+
+static int outputlines (stralloc const *s, unsigned int len)
+{
+  register unsigned int i = 0 ;
+  for (; i < len ; i++)
+    if (buffer_put(buffer_1, s[i].s, s[i].len) < 0) return 0 ;
+  return buffer_flush(buffer_1) ;
+}
+
+static int check (stralloc const *s, unsigned int len)
+{
+  register unsigned int i = 1 ;
+  for (; i < len ; i++)
+    if (sacmp(s+i-1, s+i) >= !flaguniq) return 0 ;
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  genalloc lines = GENALLOC_ZERO ; /* array of stralloc */
+  char sep = '\n' ;
+  int flagcheck = 0 ;
+  PROG = "s6-sort" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "bcfru0", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'b' : flagnoblanks = 1 ; break ;
+        case 'c' : flagcheck = 1 ; break ;
+        case 'f' : comp = &case_diffb ; break ;
+        case 'r' : flagreverse = 1 ; break ;
+        case 'u' : flaguniq = 1 ; break ;
+        case '0' : sep = '\0' ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (slurplines(&lines, sep) < 0) strerr_diefu1sys(111, "read from stdin") ;
+  if (flagcheck) return !check(genalloc_s(stralloc, &lines), genalloc_len(stralloc, &lines)) ;
+  qsort(genalloc_s(stralloc, &lines), genalloc_len(stralloc, &lines), sizeof(stralloc), (qsortcmp_t_ref)&sacmp) ;
+  if (flaguniq) uniq(&lines) ;
+  if (!outputlines(genalloc_s(stralloc, &lines), genalloc_len(stralloc, &lines)))
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-sync.c b/src/skaembutils/s6-sync.c
new file mode 100644
index 0000000..a8829cd
--- /dev/null
+++ b/src/skaembutils/s6-sync.c
@@ -0,0 +1,9 @@
+/* ISC license. */
+
+#include <unistd.h>
+
+int main (void)
+{
+  sync() ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-tail.c b/src/skaembutils/s6-tail.c
new file mode 100644
index 0000000..cc2d1f1
--- /dev/null
+++ b/src/skaembutils/s6-tail.c
@@ -0,0 +1,200 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/siovec.h>
+
+#define USAGE "s6-tail [ -c chars | -n lines | -1..9 ] [ file ]"
+
+typedef int tailfunc_t (int, unsigned int) ;
+typedef tailfunc_t *tailfunc_t_ref ;
+
+static int pluslines (int fd, unsigned int n)
+{
+  if (n) n-- ;
+  {
+    char buf[BUFFER_INSIZE] ;
+    buffer b = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE) ;
+    unsigned int count = 0 ;
+    while (count < n)
+    {
+      register int r = buffer_fill(&b) ;
+      if (r <= 0) return !r ;
+      while (!buffer_isempty(&b) && (count < n))
+      {
+        siovec_t v[2] ;
+        unsigned int i ;
+        buffer_rpeek(&b, v) ;
+        i = siovec_bytechr(v, 2, '\n') ;
+        if (i < buffer_len(&b))
+        {
+          count++ ; i++ ;
+        }
+        buffer_rseek(&b, i) ;
+      }
+    }
+    b.op = &buffer_write ;
+    b.fd = 1 ;
+    if (!buffer_flush(&b)) return 0 ;
+  }
+  return (fd_cat(fd, 1) >= 0) ;
+}
+
+static int pluschars (int fd, unsigned int n)
+{
+  if (n-- > 1)
+  {
+    int nil = open_write("/dev/null") ;
+    if (nil < 0) return 0 ;
+    if (!fd_catn(fd, nil, n))
+    {
+      register int e = errno ;
+      fd_close(nil) ;
+      errno = e ;
+      return 0 ;
+    }
+    fd_close(nil) ;
+  }
+  return (fd_cat(fd, 1) >= 0) ;
+}
+
+static int minuslines (int fd, unsigned int n)
+{
+  char buf[BUFFER_INSIZE] ;
+  buffer b = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE) ;
+  unsigned int head = 0, tail = 0 ;
+  stralloc tab[n+1] ;
+  for (; head <= n ; head++) tab[head] = stralloc_zero ;
+  head = 0 ;
+  for (;;)
+  {
+    register int r ;
+    r = skagetln(&b, tab + tail, '\n') ;
+    if (!r) break ;
+    if (r < 0)
+    {
+      if (errno == EPIPE) break ;
+      else goto err ;
+    }
+    tail = (tail + 1) % (n+1) ;
+    if (tail == head)
+    {
+      tab[head].len = 0 ;
+      head = (head + 1) % (n+1) ;
+    }
+  }
+  buffer_init(&b, &buffer_write, 1, buf, BUFFER_INSIZE) ;
+  for (; head != tail ; head = (head + 1) % (n+1))
+  {
+    if (buffer_put(&b, tab[head].s, tab[head].len) < tab[head].len)
+      goto err ;
+  }
+  for (head = 0 ; head <= n ; head++) stralloc_free(tab + head) ;
+  return buffer_flush(&b) ;
+ err:
+  for (head = 0 ; head <= n ; head++) stralloc_free(tab + head) ;
+  return 0 ;
+}
+
+static int minuschars (int fd, unsigned int n)
+{
+  char buf[BUFFER_INSIZE + n] ;
+  buffer b = BUFFER_INIT(&buffer_read, fd, buf, BUFFER_INSIZE + n) ;
+  for (;;)
+  {
+    register int r = buffer_fill(&b) ;
+    if (!r) break ;
+    if (r < 0) return 0 ;
+    buffer_rseek(&b, buffer_len(&b)) ;
+    buffer_unget(&b, n) ;
+  }
+  b.op = &buffer_write ;
+  b.fd = 1 ;
+  return buffer_flush(&b) ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  tailfunc_t_ref f = &minuslines ;
+  unsigned int n = 10 ;
+  int gotit = 0 ;
+  PROG = "s6-tail" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "123456789n:c:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case '1' :
+        case '2' :
+        case '3' :
+        case '4' :
+        case '5' :
+        case '6' :
+        case '7' :
+        case '8' :
+        case '9' :
+        {
+          if (gotit) strerr_dieusage(100, USAGE) ;
+          gotit = 1 ;
+          f = &minuslines ;
+          n = opt - '0' ;
+          break ;
+        }
+        case 'n':
+        {
+          if (gotit) strerr_dieusage(100, USAGE) ;
+          gotit = 1 ;
+          f = &minuslines ;
+          if (*l.arg == '-') l.arg++ ;
+          else if (*l.arg == '+')
+          {
+            f = &pluslines ;
+            l.arg++ ;
+          }
+          if (!uint0_scan(l.arg, &n)) strerr_dieusage(100, USAGE) ;
+          break ;
+        }
+        case 'c':
+        {
+          if (gotit) strerr_dieusage(100, USAGE) ;
+          gotit = 1 ;
+          f = &minuschars ;
+          if (*l.arg == '-') l.arg++ ;
+          else if (*l.arg == '+')
+          {
+            f = &pluschars ;
+            l.arg++ ;
+          }
+          if (!uint0_scan(l.arg, &n)) strerr_dieusage(100, USAGE) ;
+          break ;
+        }
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc)
+  {
+    if (!(*f)(0, n))
+      strerr_diefu1sys(111, "tail stdin") ;
+  }
+  else
+  {
+    int fd = open_readb(argv[0]) ;
+    if (fd == -1) strerr_diefu3sys(111, "open ", argv[0], " for reading") ;
+    if (!(*f)(fd, n))
+      strerr_diefu2sys(111, "tail ", argv[0]) ;
+    fd_close(fd) ;
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-test.c b/src/skaembutils/s6-test.c
new file mode 100644
index 0000000..3dda0b7
--- /dev/null
+++ b/src/skaembutils/s6-test.c
@@ -0,0 +1,515 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <skalibs/uint.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-test expression  or  [ expression ]"
+
+enum opnum
+{
+  T_NOT,
+  T_AND,
+  T_OR,
+  T_LEFTP,
+  T_RIGHTP,
+  T_BLOCK,
+  T_CHAR,
+  T_DIR,
+  T_EXIST,
+  T_REGULAR,
+  T_SGID,
+  T_SYMLINK,
+  T_STICKY,
+  T_NONZERO,
+  T_FIFO,
+  T_READABLE,
+  T_NONZEROFILE,
+  T_TERM,
+  T_SUID,
+  T_WRITABLE,
+  T_EXECUTABLE,
+  T_ZERO,
+  T_EUID,
+  T_EGID,
+  T_SOCKET,
+  T_MODIFIED,
+  T_NEWER,
+  T_OLDER,
+  T_DEVINO,
+  T_STREQUAL,
+  T_STRNEQUAL,
+  T_STRLESSER,
+  T_STRLESSERE,
+  T_STRGREATER,
+  T_STRGREATERE,
+  T_NUMEQUAL,
+  T_NUMNEQUAL,
+  T_NUMGREATER,
+  T_NUMGREATERE,
+  T_NUMLESSER,
+  T_NUMLESSERE
+} ;
+
+struct token
+{
+  char const *string ;
+  enum opnum op ;
+  unsigned int type ;
+} ;
+
+struct node
+{
+  enum opnum op ;
+  unsigned int type ;
+  unsigned int arg1 ;
+  unsigned int arg2 ;
+  char const *data ;
+} ;
+
+static unsigned int lex (struct node *tree, char const *const *argv)
+{
+  static struct token const tokens[44] =
+  {
+    { "-n", T_NONZERO, 2 },
+    { "-z", T_ZERO, 2 },
+    { "=", T_STREQUAL, 3 },
+    { "!=", T_STRNEQUAL, 3 },
+    { "-eq", T_NUMEQUAL, 3 },
+    { "-ne", T_NUMNEQUAL, 3 },
+    { "-gt", T_NUMGREATER, 3 },
+    { "-ge", T_NUMGREATERE, 3 },
+    { "-lt", T_NUMLESSER, 3 },
+    { "-le", T_NUMLESSERE, 3 },
+    { "-f", T_REGULAR, 2 },
+    { "-h", T_SYMLINK, 2 },
+    { "-L", T_SYMLINK, 2 },
+    { "-e", T_EXIST, 2 },
+    { "-k", T_STICKY, 2 },
+    { "-a", T_AND, 7 },
+    { "-o", T_OR, 8 },
+    { "!", T_NOT, 6 },
+    { "(", T_LEFTP, 4 },
+    { ")", T_RIGHTP, 5 },
+    { "-b", T_BLOCK, 2 },
+    { "-c", T_CHAR, 2 },
+    { "-d", T_DIR, 2 },
+    { "-g", T_SGID, 2 },
+    { "-p", T_FIFO, 2 },
+    { "-r", T_READABLE, 2 },
+    { "-s", T_NONZEROFILE, 2 },
+    { "-t", T_TERM, 2 },
+    { "-u", T_SUID, 2 },
+    { "-w", T_WRITABLE, 2 },
+    { "-x", T_EXECUTABLE, 2 },
+    { "-O", T_EUID, 2 },
+    { "-U", T_EUID, 2 },
+    { "-G", T_EGID, 2 },
+    { "-S", T_SOCKET, 2 },
+    { "-N", T_MODIFIED, 2 },
+    { "-nt", T_NEWER, 3 },
+    { "-ot", T_OLDER, 3 },
+    { "-ef", T_DEVINO, 3 },
+    { "<", T_STRLESSER, 3 },
+    { "<=", T_STRLESSERE, 3 },
+    { ">", T_STRGREATER, 3 },
+    { ">=", T_STRGREATERE, 3 },
+    { 0, 0, 0 }
+  } ;
+  register unsigned int pos = 0 ;
+
+  for (; argv[pos] ; pos++)
+  {
+    unsigned int i = 0 ;
+    tree[pos].data = argv[pos] ;
+    for (i = 0 ; tokens[i].string ; i++)
+      if (!str_diff(argv[pos], tokens[i].string))
+      {
+        tree[pos].op = tokens[i].op ;
+        tree[pos].type = tokens[i].type ;
+        break ;
+      }
+    if (!tokens[i].string)
+    {
+      tree[pos].op = T_NONZERO ;
+      tree[pos].type = 0 ;
+      tree[pos].arg1 = pos ;
+      if (*(argv[pos]) == '\\') tree[pos].data++ ; /* better than SUSv3 */
+    }
+  }
+  return pos ;
+}
+
+static unsigned int parse (struct node *tree, unsigned int n)
+{
+  static char const table[9][13] =
+  {
+    "xssssxsssxxxx",
+    "xxxxxaxxxxxxx",
+    "xsxxsxsssxxxx",
+    "sxxxxxxxxxxxx",
+    "xsxxsxsssxxxx",
+    "nxxxxNxxxAOEs",
+    "xsxxsxsssxxxx",
+    "nxxxxNxxxAsxx",
+    "nxxxxNxxxAOsx"
+  } ;
+
+  unsigned int stack[n+2] ;
+  unsigned int sp = 0, pos = 0 ;
+  int cont = 1 ;
+
+  stack[0] = n+1 ;
+  tree[n].type = 5 ; /* add ) for the final reduce */
+  tree[n+1].type = 1 ; /* add EOF */
+
+  while (cont)
+  {
+    switch (table[tree[pos].type][tree[stack[sp]].type])
+    {
+      case 'x' : /* error */
+      {
+        char fmt[UINT_FMT] ;
+        fmt[uint_fmt(fmt, pos)] = 0 ;
+        strerr_dief2x(100, "parse error at argument ", fmt) ;
+        break ;
+      }
+      case 'a' : /* accept */
+      {
+        cont = 0 ;
+        break ;
+      }
+      case 's' : /* shift */
+      {
+        stack[++sp] = pos++ ;
+        break ;
+      }
+      case 'n' : /* reduce -> expr without nots, from atom */
+      {
+        switch (tree[stack[sp-1]].type)
+        {
+          case 2 :
+          {
+            tree[stack[sp-1]].arg1 = stack[sp] ;
+            sp-- ;
+            break ;
+          }
+          case 3 :
+          {
+            tree[stack[sp-1]].arg1 = stack[sp-2] ;
+            tree[stack[sp-1]].arg2 = stack[sp] ;
+            stack[sp-2] = stack[sp-1] ;
+            sp -= 2 ;
+            break ;
+          }
+          /* default : assert: its a zero */
+        }
+        tree[stack[sp]].type = 9 ;
+        while (tree[stack[sp-1]].type == 6)
+        {
+          tree[stack[sp-1]].type = 9 ;
+          tree[stack[sp-1]].arg1 = stack[sp] ;
+          sp-- ;
+        }
+        break ;
+      }
+      case 'N' : /* reduce -> expr without nots, from expr */
+      {
+        if (tree[stack[sp-2]].type != 4)
+        {
+          char fmt[UINT_FMT] ;
+          fmt[uint_fmt(fmt, pos)] = 0 ;
+          strerr_dief2x(100, "parse error: bad right parenthesis at argument ", fmt) ;
+        }
+        stack[sp-2] = stack[sp-1] ;
+        sp -= 2 ;
+        tree[stack[sp]].type = 9 ;
+        while (tree[stack[sp-1]].type == 6)
+        {
+          tree[stack[sp-1]].type = 9 ;
+          tree[stack[sp-1]].arg1 = stack[sp] ;
+          sp-- ;
+        }
+        break ;
+      }
+      case 'A' : /* reduce -> exprs without ands */
+      {
+        if (tree[stack[sp-1]].type == 7)
+        {
+          tree[stack[sp-1]].arg1 = stack[sp-2] ;
+          tree[stack[sp-1]].arg2 = stack[sp] ;
+          stack[sp-2] = stack[sp-1] ;
+          sp -= 2 ;
+        }
+        tree[stack[sp]].type = 10 ;
+        break ;
+      }
+      case 'O' : /* reduce -> expr without ors */
+      {
+        if (tree[stack[sp-1]].type == 8)
+        {
+          tree[stack[sp-1]].arg1 = stack[sp-2] ;
+          tree[stack[sp-1]].arg2 = stack[sp] ;
+          stack[sp-2] = stack[sp-1] ;
+          sp -= 2 ;
+        }
+        tree[stack[sp]].type = 11 ;
+        break ;
+      }
+      case 'E' : /* reduce -> expr */
+      {
+        tree[stack[sp]].type = 12 ;
+        break ;
+      }
+      default : /* can't happen */
+        strerr_dief1x(101, "internal error, please submit a bug-report.") ;
+    }
+  }
+  if (sp != 2) strerr_dief1x(100, "parse error: too many left parentheses") ;
+  return stack[1] ;
+}
+
+static int run (struct node const *tree, unsigned int root)
+{
+  switch (tree[root].op)
+  {
+    case T_NOT :
+      return !run(tree, tree[root].arg1) ;
+    case T_AND :
+      return run(tree, tree[root].arg1) && run(tree, tree[root].arg2) ;
+    case T_OR :
+      return run(tree, tree[root].arg1) || run(tree, tree[root].arg2) ;
+    case T_EXIST :
+    {
+      struct stat st ;
+      return !stat(tree[tree[root].arg1].data, &st) ;
+    }
+    case T_BLOCK :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISBLK(st.st_mode) ;
+    }
+    case T_CHAR :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISCHR(st.st_mode) ;
+    }
+    case T_DIR :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISDIR(st.st_mode) ;
+    }
+    case T_REGULAR :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISREG(st.st_mode) ;
+    }
+    case T_FIFO :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISFIFO(st.st_mode) ;
+    }
+    case T_SOCKET :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISSOCK(st.st_mode) ;
+    }
+    case T_SYMLINK :
+    {
+      struct stat st ;
+      if (lstat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return S_ISLNK(st.st_mode) ;
+    }
+    case T_SGID :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return (st.st_mode & S_ISGID) ;
+    }
+    case T_SUID :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return (st.st_mode & S_ISUID) ;
+    }
+    case T_STICKY :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return (st.st_mode & S_ISVTX) ;
+    }
+    case T_NONZEROFILE :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return (st.st_size > 0) ;
+    }
+    case T_MODIFIED :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return (st.st_mtime > st.st_atime) ;
+    }
+    case T_EUID :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return st.st_uid == geteuid() ;
+    }
+    case T_EGID :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      return st.st_gid == getegid() ;
+    }
+    case T_READABLE :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      if (st.st_uid == geteuid()) return st.st_mode & S_IRUSR ;
+      else if (st.st_gid == getegid()) return st.st_mode & S_IRGRP ;
+      else return st.st_mode & S_IROTH ;
+    }
+    case T_WRITABLE :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      if (st.st_uid == geteuid()) return st.st_mode & S_IWUSR ;
+      else if (st.st_gid == getegid()) return st.st_mode & S_IWGRP ;
+      else return st.st_mode & S_IWOTH ;
+    }
+    case T_EXECUTABLE :
+    {
+      struct stat st ;
+      if (stat(tree[tree[root].arg1].data, &st) == -1) return 0 ;
+      if (st.st_uid == geteuid()) return st.st_mode & S_IXUSR ;
+      else if (st.st_gid == getegid()) return st.st_mode & S_IXGRP ;
+      else return st.st_mode & S_IXOTH ;
+    }
+    case T_NEWER :
+    {
+      struct stat st1, st2 ;
+      if (stat(tree[tree[root].arg1].data, &st1) == -1) return 0 ;
+      if (stat(tree[tree[root].arg2].data, &st2) == -1) return 1 ;
+      return st1.st_mtime > st2.st_mtime ;
+    }
+    case T_OLDER :
+    {
+      struct stat st1, st2 ;
+      if (stat(tree[tree[root].arg1].data, &st1) == -1) return 1 ;
+      if (stat(tree[tree[root].arg2].data, &st2) == -1) return 0 ;
+      return st1.st_mtime < st2.st_mtime ;
+    }
+    case T_DEVINO :
+    {
+      struct stat st1, st2 ;
+      if (stat(tree[tree[root].arg1].data, &st1) == -1) return 0 ;
+      if (stat(tree[tree[root].arg2].data, &st2) == -1) return 1 ;
+      return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino) ;
+    }
+    case T_TERM :
+    {
+      unsigned int fd ;
+      if (!uint0_scan(tree[tree[root].arg1].data, &fd))
+        strerr_dief2x(100, tree[root].data, " requires an integer argument") ;
+      return isatty((int)fd) ;
+    }
+    case T_NONZERO :
+      return tree[tree[root].arg1].data[0] ;
+    case T_ZERO :
+      return !tree[tree[root].arg1].data[0] ;
+    case T_STREQUAL :
+      return !str_diff(tree[tree[root].arg1].data, tree[tree[root].arg2].data) ;
+    case T_STRNEQUAL :
+      return !!str_diff(tree[tree[root].arg1].data, tree[tree[root].arg2].data) ;
+    case T_STRLESSER :
+      return str_diff(tree[tree[root].arg1].data, tree[tree[root].arg2].data) < 0 ;
+    case T_STRLESSERE :
+      return str_diff(tree[tree[root].arg1].data, tree[tree[root].arg2].data) <= 0 ;
+    case T_STRGREATER :
+      return str_diff(tree[tree[root].arg1].data, tree[tree[root].arg2].data) > 0 ;
+    case T_STRGREATERE :
+      return str_diff(tree[tree[root].arg1].data, tree[tree[root].arg2].data) >= 0 ;
+    case T_NUMEQUAL :
+    {
+      int n1, n2 ;
+      if (!int_scan(tree[tree[root].arg1].data, &n1)
+       || !int_scan(tree[tree[root].arg2].data, &n2))
+        goto errorint ;
+      return n1 == n2 ;
+    }
+    case T_NUMNEQUAL :
+    {
+      int n1, n2 ;
+      if (!int_scan(tree[tree[root].arg1].data, &n1)
+       || !int_scan(tree[tree[root].arg2].data, &n2))
+        goto errorint ;
+      return n1 != n2 ;
+    }
+    case T_NUMGREATER :
+    {
+      int n1, n2 ;
+      if (!int_scan(tree[tree[root].arg1].data, &n1)
+       || !int_scan(tree[tree[root].arg2].data, &n2))
+        goto errorint ;
+      return n1 > n2 ;
+    }
+    case T_NUMGREATERE :
+    {
+      int n1, n2 ;
+      if (!int_scan(tree[tree[root].arg1].data, &n1)
+       || !int_scan(tree[tree[root].arg2].data, &n2))
+        goto errorint ;
+      return n1 >= n2 ;
+    }
+    case T_NUMLESSER :
+    {
+      int n1, n2 ;
+      if (!int_scan(tree[tree[root].arg1].data, &n1)
+       || !int_scan(tree[tree[root].arg2].data, &n2))
+        goto errorint ;
+      return n1 < n2 ;
+    }
+    case T_NUMLESSERE :
+    {
+      int n1, n2 ;
+      if (!int_scan(tree[tree[root].arg1].data, &n1)
+       || !int_scan(tree[tree[root].arg2].data, &n2))
+        goto errorint ;
+      return n1 <= n2 ;
+    }
+    default:
+      strerr_dief1x(111, "operation not implemented") ;
+  }
+
+errorint:
+  strerr_dief2x(100, tree[root].data, " requires integer arguments") ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-test" ;
+  if (argc <= 1) return 1 ;
+  {
+    struct node tree[argc + 2] ;    
+    unsigned int n = lex(tree, argv+1) ;
+    if ((argv[0][0] == '[') && !argv[0][1])
+    {
+      if (n && (!tree[n-1].type) && (tree[n-1].data[0] == ']') && !tree[n-1].data[1])
+        n-- ;
+      else strerr_dief1x(100, "parse error: missing closing bracket") ;
+    }
+    return !run(tree, parse(tree, n)) ;
+  }
+}
diff --git a/src/skaembutils/s6-touch.c b/src/skaembutils/s6-touch.c
new file mode 100644
index 0000000..b149835
--- /dev/null
+++ b/src/skaembutils/s6-touch.c
@@ -0,0 +1,20 @@
+/* ISC license. */
+
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-touch file ..."
+
+int main (int argc, char const *const *argv)
+{
+  char const *const *p = argv + 1 ;
+  PROG = "s6-touch" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  for (; *p ; p++)
+  {
+    register int fd = open_append(*p) ;
+    if (fd < 0) strerr_diefu2sys(111, "open_append ", *p) ;
+    fd_close(fd) ;
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-true.c b/src/skaembutils/s6-true.c
new file mode 100644
index 0000000..65c3af4
--- /dev/null
+++ b/src/skaembutils/s6-true.c
@@ -0,0 +1,6 @@
+/* ISC license. */
+
+int main ()
+{
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-uniquename.c b/src/skaembutils/s6-uniquename.c
new file mode 100644
index 0000000..06418e5
--- /dev/null
+++ b/src/skaembutils/s6-uniquename.c
@@ -0,0 +1,40 @@
+/* ISC license. */
+
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/random.h>
+
+#define USAGE "s6-uniquename [ -n randomlen ] prefix"
+#define usage() strerr_dieusage(100, USAGE)
+
+int main (int argc, char const *const *argv)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  unsigned int n = 8 ;
+  PROG = "s6-uniquename" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "n:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : if (!uint0_scan(l.arg, &n)) usage() ; break ;
+        default : usage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (argc < 1) usage() ;
+  if (!stralloc_cats(&sa, argv[0])) strerr_diefu1sys(111, "stralloc_cats") ;
+  if ((n ? random_sauniquename(&sa, n) : sauniquename(&sa)) < 0)
+    strerr_diefu1sys(111, "make unique name") ;
+  if (!stralloc_catb(&sa, "\n", 1)) strerr_diefu1sys(111, "stralloc_cats") ;
+  if (allwrite(1, sa.s, sa.len) < sa.len) strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-unquote-filter.c b/src/skaembutils/s6-unquote-filter.c
new file mode 100644
index 0000000..08477ef
--- /dev/null
+++ b/src/skaembutils/s6-unquote-filter.c
@@ -0,0 +1,198 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-unquote-filter [ -q | -Q | -v | -w ] [ -d delim ]"
+
+static unsigned int strictness = 1 ;
+static char const *delim = "\"" ;
+static unsigned int delimlen = 1 ;
+
+static void fillfmt (char *fmt, char const *s, unsigned int len)
+{
+  register unsigned int n = len < 39 ? len+1 : 36 ;
+  byte_copy(fmt, n, s) ;
+  if (len >= 39)
+  {
+    byte_copy(fmt+n, 3, "...") ;
+    n += 3 ;
+  }
+  fmt[n] = 0 ;
+}
+
+static int doit (char const *s, unsigned int len)
+{
+  if (delimlen)
+  {
+    if (!len)
+    {
+      switch (strictness)
+      {
+        case 1 :
+        case 2 :
+          strerr_warnw1x("empty line") ;
+          break ;
+        case 3 :
+          buffer_flush(buffer_1) ;
+          strerr_dief1x(100, "empty line") ;
+        default : break ;
+      }
+      return 1 ;
+    }
+    if (byte_chr(delim, delimlen, *s) >= delimlen)
+    {
+      switch (strictness)
+      {
+        case 0 : return 0 ;
+        case 1 :
+        {
+          strerr_warnw1x("invalid starting quote character") ;
+          return 0 ;
+        }
+        case 2 :
+        {
+          char fmt[40] ;
+          fillfmt(fmt, s, len) ;
+          strerr_warnw3x("invalid starting quote character", " in line: ", fmt) ;
+          return 0 ;
+        }
+        case 3 :
+        {
+          buffer_flush(buffer_1) ;
+          strerr_dief1x(100, "invalid starting quote character") ;
+        }
+        default : strerr_dief1x(101, "can't happen: unknown strictness") ;
+      }
+    }
+  }
+  {
+    unsigned int r, w ;
+    char d[len] ;
+    if (!string_unquote_withdelim(d, &w, s + !!delimlen, len - !!delimlen, &r, delim, delimlen))
+    {
+      switch (strictness)
+      {
+        case 0 : return 0 ;
+        case 1 :
+        {
+          strerr_warnwu1sys("unquote") ;
+          return 0 ;
+        }
+        case 2 :
+        {
+          char fmt[40] ;
+          fillfmt(fmt, s, len) ;
+          strerr_warnwu3sys("unquote", " line: ", fmt) ;
+          return 0 ;
+        }
+        case 3 :
+        {
+          int e = errno ;
+          buffer_flush(buffer_1) ;
+          errno = e ;
+          strerr_diefu1sys(100, "unquote") ;
+        }
+        default : strerr_dief1x(101, "can't happen: unknown strictness") ;
+      }
+    }
+    if (delimlen)
+    {
+      if (r+1 == len)
+      {
+        switch (strictness)
+        {
+          case 0 : return 0 ;
+          case 1 :
+          {
+            strerr_warnwu2x("unquote", ": no ending quote character") ;
+            return 0 ;
+          }
+          case 2 :
+          {
+            char fmt[40] ;
+            fillfmt(fmt, s, len) ;
+            strerr_warnwu5x("unquote", ": no ending quote character", " in ", "line: ", fmt) ;
+            return 0 ;
+          }
+          case 3 :
+          {
+            int e = errno ;
+            buffer_flush(buffer_1) ;
+            errno = e ;
+            strerr_diefu2x(100, "unquote", ": no ending quote character") ;
+          }
+          default : strerr_dief1x(101, "can't happen: unknown strictness") ;
+        }
+      }
+      else if ((r+2 < len) && (strictness >= 2))
+      {
+        char fmtnum[UINT_FMT] ;
+        char fmtden[UINT_FMT] ;
+        char fmt[40] ;
+        fillfmt(fmt, s, len) ;
+        fmtnum[uint_fmt(fmtnum, r+1)] = 0 ;
+        fmtden[uint_fmt(fmtden, len-1)] = 0 ;
+        strerr_warnw7x("found ending quote character at position ", fmtnum, "/", fmtden, ", ignoring remainder of ", "line: ", fmt) ;
+      }
+    }
+    if (buffer_put(buffer_1, d, w) < (int)w)
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+  return 1 ;
+}
+
+
+int main (int argc, char const *const *argv)
+{
+  stralloc src = STRALLOC_ZERO ;
+  PROG = "s6-unquote-filter" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "qQvwd:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'q': strictness = 0 ; break ;
+        case 'Q': strictness = 1 ; break ;
+        case 'v': strictness = 2 ; break ;
+        case 'w': strictness = 3 ; break ;
+        case 'd': delim = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  delimlen = str_len(delim) ;
+  for (;;)
+  {
+    int r ;
+    src.len = 0 ;
+    r = skagetln(buffer_0f1, &src, '\n') ;
+    if (!r) break ;
+    if (r < 0)
+    {
+      if (errno != EPIPE) strerr_diefu1sys(111, "read from stdin") ;
+    }
+    else src.len-- ;
+    if (!doit(src.s, src.len))
+    {
+      if (buffer_put(buffer_1, src.s, src.len) < (int)src.len)
+        strerr_diefu1sys(111, "write to stdout") ;
+    }
+    if (r > 0)
+    {
+      if (buffer_put(buffer_1, "\n", 1) < 1)
+        strerr_diefu1sys(111, "write to stdout") ;
+    }
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-unquote.c b/src/skaembutils/s6-unquote.c
new file mode 100644
index 0000000..37c3dfb
--- /dev/null
+++ b/src/skaembutils/s6-unquote.c
@@ -0,0 +1,70 @@
+/* ISC license. */
+
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-unquote [ -n ] [ -d delim ] string"
+
+int main (int argc, char const *const *argv)
+{
+  char const *delim = "\"" ;
+  unsigned int len, delimlen ;
+  int nl = 1 ;
+  char const *string ;
+  PROG = "s6-unquote" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "nd:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'n' : nl = 0 ; break ;
+        case 'd': delim = l.arg ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) strerr_dieusage(100, USAGE) ;
+  string = *argv ;
+  len = str_len(string) ;
+  delimlen = str_len(delim) ;
+  if (delimlen)
+  {
+    if (!len--) strerr_dief1x(100, "the empty string isn't a quoted string") ;
+    if (byte_chr(delim, delimlen, *string++) >= delimlen)
+      strerr_dief1x(100, "invalid starting quote character") ;
+  }
+  {
+    unsigned int r = 0, w = 0 ;
+    char buf[len+1] ;
+    if (!string_unquote_withdelim(buf, &w, string, len, &r, delim, delimlen))
+    {
+      char fmt[UINT_FMT] ;
+      fmt[uint_fmt(fmt, r + !!delimlen)] = 0 ;
+      strerr_diefu2sys(100, "unquote at character ", fmt) ;
+    }
+    if (delimlen)
+    {
+      if (r == len) strerr_dief1x(100, "no ending quote character") ;
+      else if (r < len - 1)
+      {
+        char fmtnum[UINT_FMT] ;
+        char fmtden[UINT_FMT] ;
+        fmtnum[uint_fmt(fmtnum, r+1)] = 0 ;
+        fmtden[uint_fmt(fmtden, len)] = 0 ;
+        strerr_warnw5x("found ending quote character at position ", fmtnum, "/", fmtden, "; ignoring remainder") ;
+      }
+    }
+    if (nl) buf[w++] = '\n' ;
+    if (allwrite(1, buf, w) < w)
+      strerr_diefu1sys(111, "write to stdout") ;
+  }
+  return 0 ;
+}
diff --git a/src/skaembutils/s6-update-symlinks.c b/src/skaembutils/s6-update-symlinks.c
new file mode 100644
index 0000000..ba89b06
--- /dev/null
+++ b/src/skaembutils/s6-update-symlinks.c
@@ -0,0 +1,367 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/direntry.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/random.h>
+
+#define USAGE "s6-update-symlinks /destdir /srcdir [ /srcdir ... ]"
+
+#define MAGICNEW ":s6-update-symlinks-new"
+#define MAGICOLD ":s6-update-symlinks-old"
+
+#define CONFLICT -2
+#define ERROR -1
+#define MODIFIED 0
+#define OVERRIDEN 1
+
+static stralloc errdst = STRALLOC_ZERO ;
+static stralloc errsrc = STRALLOC_ZERO ;
+
+
+typedef struct stralloc3 stralloc3, *stralloc3_ref ;
+struct stralloc3
+{
+  stralloc dst ;
+  stralloc src ;
+  stralloc tmp ;
+} ;
+
+#define STRALLOC3_ZERO { STRALLOC_ZERO, STRALLOC_ZERO, STRALLOC_ZERO }
+
+
+static void cleanup (stralloc *sa, unsigned int pos)
+{
+  register int e = errno ;
+  rm_rf_in_tmp(sa, pos) ;
+  errno = e ;
+}
+
+
+static int makeuniquename (stralloc *sa, char const *path, char const *magic)
+{
+  unsigned int base = sa->len ;
+  int wasnull = !sa->s ;
+  if (!stralloc_cats(sa, path)) return 0 ;
+  if (!stralloc_cats(sa, magic)) goto err ;
+  if (random_sauniquename(sa, 8) == -1) goto err ;
+  if (!stralloc_0(sa)) goto err ;
+  return 1 ;
+
+err:
+  if (wasnull) stralloc_free(sa) ; else sa->len = base ;
+  return 0 ;
+}
+
+
+static int addlink (stralloc3 *blah, unsigned int dstpos, unsigned int srcpos)
+{
+  if (symlink(blah->src.s + srcpos, blah->dst.s + dstpos) >= 0) return MODIFIED ;
+  if (errno != EEXIST) return ERROR ;
+
+  {
+    unsigned int dstbase = blah->dst.len ;
+    unsigned int srcbase = blah->src.len ;
+    unsigned int tmpbase = blah->tmp.len ;
+    unsigned int dststop ;
+    unsigned int srcstop ;
+    signed int diffsize = 0 ;
+    int collect = 1 ;
+
+    {
+      register unsigned int n = str_len(blah->dst.s + dstpos) ;
+      if (!stralloc_readyplus(&blah->dst, n+1)) return ERROR ;
+      stralloc_catb(&blah->dst, blah->dst.s + dstpos, n) ;
+    }
+    stralloc_catb(&blah->dst, "/", 1) ;
+    dststop = blah->dst.len ;
+
+    {
+      int r ;
+      DIR *dir = opendir(blah->dst.s + dstpos) ;
+      if (!dir)
+      {
+        blah->dst.len = dstbase ;
+        if (errno != ENOTDIR) return ERROR ;
+        if ((unlink(blah->dst.s + dstpos) == -1)
+         || (symlink(blah->src.s + srcpos, blah->dst.s + dstpos) == -1))
+          return ERROR ;
+        return OVERRIDEN ; /* replaced a link to a normal file */
+      }
+      r = sareadlink(&blah->src, blah->dst.s + dstpos) ;
+      if ((r == -1) && (errno != EINVAL))
+      {
+        register int e = errno ;
+        blah->dst.len = dstbase ;
+        dir_close(dir) ;
+        errno = e ;
+        return ERROR ;
+      }
+      if (r < 0)
+      {
+        for (;;)
+        {
+          register direntry *d ;
+          errno = 0 ;
+          d = readdir(dir) ;
+          if (!d) break ;
+          if ((d->d_name[0] == '.') && (!d->d_name[1] || ((d->d_name[1] == '.') && !d->d_name[2])))
+            continue ;
+          diffsize-- ;  /* need to know the size for collect */
+        }
+        if (errno)
+        {
+          register int e = errno ;
+          blah->src.len = srcbase ;
+          blah->dst.len = dstbase ;
+          dir_close(dir) ;
+          errno = e ;
+          return ERROR ;
+        }
+      }
+      else if ((unlink(blah->dst.s + dstpos) == -1)
+            || (mkdir(blah->dst.s + dstpos, 0777) == -1)
+            || !stralloc_catb(&blah->src, "/", 1))
+      {
+        register int e = errno ;
+        blah->src.len = srcbase ;
+        blah->dst.len = dstbase ;
+        dir_close(dir) ;
+        errno = e ;
+        return ERROR ;
+      }
+      else         /* expand */
+      {
+        srcstop = blah->src.len ;
+        for (;;)
+        {
+          register direntry *d ;
+          errno = 0 ;
+          d = readdir(dir) ;
+          if (!d) break ;
+          if ((d->d_name[0] == '.') && (!d->d_name[1] || ((d->d_name[1] == '.') && !d->d_name[2])))
+            continue ;
+          diffsize-- ;
+          blah->dst.len = dststop ;
+          blah->src.len = srcstop ;
+          if (!stralloc_cats(&blah->dst, d->d_name) || !stralloc_0(&blah->dst)
+           || !stralloc_cats(&blah->src, d->d_name) || !stralloc_0(&blah->src)
+           || (symlink(blah->src.s + srcbase, blah->dst.s + dstbase) == -1))
+          {
+            register int e = errno ;
+            blah->src.len = srcbase ;
+            blah->dst.len = dstbase ;
+            dir_close(dir) ;
+            errno = e ;
+            return ERROR ;
+          }
+        }
+        if (errno)
+        {
+          register int e = errno ;
+          blah->src.len = srcbase ;
+          blah->dst.len = dstbase ;
+          dir_close(dir) ;
+          errno = e ;
+          return ERROR ;
+        }
+      }
+      dir_close(dir) ;
+    }
+
+    blah->src.len = srcbase ;
+    {
+      register unsigned int n = str_len(blah->src.s + srcpos) ;
+      if (!stralloc_readyplus(&blah->src, n+1))
+      {
+        blah->dst.len = dstbase ;
+        return ERROR ;
+      }
+      stralloc_catb(&blah->src, blah->src.s + srcpos, n) ;
+    }
+    stralloc_catb(&blah->src, "/", 1) ;
+    srcstop = blah->src.len ;
+
+
+   /* prepare tmp for recursion */
+
+    {
+      DIR *dir = opendir(blah->src.s + srcpos) ;
+      if (!dir)
+      {
+        blah->src.len = srcbase ;
+        blah->dst.len = dstbase ;
+        if (errno != ENOTDIR) return ERROR ;
+        errdst.len = errsrc.len = 0 ;
+        if (!stralloc_cats(&errdst, blah->dst.s + dstpos) || !stralloc_0(&errdst)
+         || !stralloc_cats(&errsrc, blah->src.s + srcpos) || !stralloc_0(&errsrc))
+          return ERROR ;
+        return CONFLICT ; /* dst is a dir but src is not */
+      }
+      for (;;)
+      {
+        register direntry *d ;
+        errno = 0 ;
+        d = readdir(dir) ;
+        if (!d) break ;
+        if ((d->d_name[0] == '.') && (!d->d_name[1] || ((d->d_name[1] == '.') && !d->d_name[2])))
+          continue ;
+        if (!stralloc_cats(&blah->tmp, d->d_name) || !stralloc_0(&blah->tmp))
+        {
+          register int e = errno ;
+          blah->tmp.len = tmpbase ;
+          blah->src.len = srcbase ;
+          blah->dst.len = dstbase ;
+          dir_close(dir) ;
+          errno = e ;
+          return ERROR ;
+        }
+      }
+      if (errno)
+      {
+        register int e = errno ;
+        blah->tmp.len = tmpbase ;
+        blah->src.len = srcbase ;
+        blah->dst.len = dstbase ;
+        dir_close(dir) ;
+        errno = e ;
+        return ERROR ;
+      }
+      dir_close(dir) ;
+    }
+
+
+   /* recurse */
+
+    {
+      unsigned int i = tmpbase ;
+      while (i < blah->tmp.len)
+      {
+        diffsize++ ;
+        blah->dst.len = dststop ;
+        blah->src.len = srcstop ;
+        {
+          register unsigned int n = str_len(blah->tmp.s + i) + 1 ;
+          if (!stralloc_catb(&blah->dst, blah->tmp.s + i, n)
+           || !stralloc_catb(&blah->src, blah->tmp.s + i, n))
+          {
+            blah->tmp.len = tmpbase ;
+            blah->src.len = srcbase ;
+            blah->dst.len = dstbase ;
+            return ERROR ;
+          }
+          i += n ;
+        }
+        switch (addlink(blah, dstbase, srcbase))
+        {
+          case ERROR :
+            blah->tmp.len = tmpbase ;
+            blah->src.len = srcbase ;
+            blah->dst.len = dstbase ;
+            return ERROR ;
+          case CONFLICT :
+            blah->tmp.len = tmpbase ;
+            blah->src.len = srcbase ;
+            blah->dst.len = dstbase ;
+            return CONFLICT ;
+          case MODIFIED :
+            collect = 0 ;
+        }
+      }
+    }
+    blah->tmp.len = tmpbase ;
+    blah->src.len = srcbase ;
+    blah->dst.len = dstbase ;
+
+
+   /* collect */
+
+    if (collect && !diffsize)
+    {
+      if (rm_rf_in_tmp(&blah->dst, dstpos) == -1) return ERROR ;
+      if (symlink(blah->src.s + srcpos, blah->dst.s + dstpos) == -1) return ERROR ;
+      return OVERRIDEN ;
+    }
+  }
+  return MODIFIED ;
+}
+
+int main (int argc, char *const *argv)
+{
+  stralloc3 blah = STRALLOC3_ZERO ;
+  PROG = "s6-update-symlinks" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  {
+    register char *const *p = argv + 1 ;
+    for (; *p ; p++) if (**p != '/') strerr_dieusage(100, USAGE) ;
+  }
+  {
+    register unsigned int i = str_len(argv[1]) ;
+    while (i && (argv[1][i-1] == '/')) argv[1][--i] = 0 ;
+    if (!i) strerr_diefu1x(100, "replace root directory") ;
+  }
+  if (!makeuniquename(&blah.dst, argv[1], MAGICNEW))
+    strerr_diefu2sys(111, "make random unique name based on ", argv[1]) ;
+  if ((unlink(blah.dst.s) == -1) && (errno != ENOENT))
+    strerr_diefu2sys(111, "unlink ", blah.dst.s) ;
+
+  {
+    char *const *p = argv + 2 ;
+    for (; *p ; p++)
+    {
+      register int r ;
+      blah.src.len = 0 ;
+      if (!stralloc_cats(&blah.src, *p) || !stralloc_0(&blah.src))
+        strerr_diefu1sys(111, "make stralloc") ;
+      r = addlink(&blah, 0, 0) ;
+      if (r < 0)
+      {
+        stralloc_free(&blah.tmp) ;
+        stralloc_free(&blah.src) ;
+        cleanup(&blah.dst, 0) ;
+        stralloc_free(&blah.dst) ;
+        if (r == CONFLICT)
+          strerr_dief4x(100, "destination ", errdst.s, " conflicts with source ", errsrc.s) ;
+        else
+          strerr_dief2sys(111, "error processing ", *p) ;
+      }
+    }
+  }
+  stralloc_free(&blah.tmp) ;
+
+  if (rename(blah.dst.s, argv[1]) == -1) /* be atomic if possible */
+  {
+    blah.src.len = 0 ;
+    if (!makeuniquename(&blah.src, argv[1], MAGICOLD))
+    {
+      cleanup(&blah.dst, 0) ;
+      strerr_diefu2sys(111, "make random unique name based on ", argv[1]) ;
+    }
+
+    if (rename(argv[1], blah.src.s) == -1)
+    {
+      cleanup(&blah.dst, 0) ;
+      strerr_diefu4sys(111, "rename ", argv[1], " to ", blah.src.s) ;
+    }
+   /* XXX: unavoidable race condition here: argv[1] does not exist */
+    if (rename(blah.dst.s, argv[1]) == -1)
+    {
+      rename(blah.src.s, argv[1]) ;
+      cleanup(&blah.dst, 0) ;
+      strerr_diefu4sys(111, "rename ", blah.dst.s, " to ", argv[1]) ;
+    }
+    stralloc_free(&blah.dst) ;
+    if (rm_rf_in_tmp(&blah.src, 0) == -1)
+      strerr_warnwu2sys("remove old directory ", blah.src.s) ;
+    stralloc_free(&blah.src) ;
+  }
+
+  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