summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--AUTHORS6
-rw-r--r--COPYING13
-rw-r--r--INSTALL130
-rw-r--r--Makefile121
-rw-r--r--README30
-rw-r--r--README.macosx4
-rw-r--r--README.solaris12
-rwxr-xr-xconfigure385
-rw-r--r--doc/index.html126
-rw-r--r--doc/s6-chroot.html40
-rw-r--r--doc/s6-devd.html80
-rw-r--r--doc/s6-freeramdisk.html32
-rw-r--r--doc/s6-halt.html36
-rw-r--r--doc/s6-hiercopy.html66
-rw-r--r--doc/s6-hostname.html39
-rw-r--r--doc/s6-logwatch.html72
-rw-r--r--doc/s6-mount.html52
-rw-r--r--doc/s6-pivotchroot.html41
-rw-r--r--doc/s6-poweroff.html39
-rw-r--r--doc/s6-ps.html128
-rw-r--r--doc/s6-reboot.html38
-rw-r--r--doc/s6-swapoff.html36
-rw-r--r--doc/s6-swapon.html36
-rw-r--r--doc/s6-umount.html38
-rw-r--r--doc/upgrade.html32
-rw-r--r--package/deps-build1
-rw-r--r--package/deps.mak42
-rw-r--r--package/info4
-rw-r--r--package/modes15
-rw-r--r--package/targets.mak22
-rwxr-xr-xpatch-for-solaris17
-rw-r--r--src/minutils/deps-exe/s6-chroot1
-rw-r--r--src/minutils/deps-exe/s6-devd1
-rw-r--r--src/minutils/deps-exe/s6-freeramdisk1
-rw-r--r--src/minutils/deps-exe/s6-halt1
-rw-r--r--src/minutils/deps-exe/s6-hiercopy1
-rw-r--r--src/minutils/deps-exe/s6-hostname1
-rw-r--r--src/minutils/deps-exe/s6-logwatch1
-rw-r--r--src/minutils/deps-exe/s6-mount1
-rw-r--r--src/minutils/deps-exe/s6-pivotchroot1
-rw-r--r--src/minutils/deps-exe/s6-poweroff1
-rw-r--r--src/minutils/deps-exe/s6-ps8
-rw-r--r--src/minutils/deps-exe/s6-reboot1
-rw-r--r--src/minutils/deps-exe/s6-swapoff1
-rw-r--r--src/minutils/deps-exe/s6-swapon1
-rw-r--r--src/minutils/deps-exe/s6-umount1
-rw-r--r--src/minutils/mount-constants.h81
-rw-r--r--src/minutils/s6-chroot.c21
-rw-r--r--src/minutils/s6-devd.c288
-rw-r--r--src/minutils/s6-freeramdisk.c21
-rw-r--r--src/minutils/s6-halt.c13
-rw-r--r--src/minutils/s6-hiercopy.c156
-rw-r--r--src/minutils/s6-hostname.c37
-rw-r--r--src/minutils/s6-logwatch.c156
-rw-r--r--src/minutils/s6-mount.c126
-rw-r--r--src/minutils/s6-pivotchroot.c24
-rw-r--r--src/minutils/s6-poweroff.c13
-rw-r--r--src/minutils/s6-ps.c385
-rw-r--r--src/minutils/s6-ps.h153
-rw-r--r--src/minutils/s6-reboot.c13
-rw-r--r--src/minutils/s6-swapoff.c53
-rw-r--r--src/minutils/s6-swapon.c37
-rw-r--r--src/minutils/s6-umount.c65
-rw-r--r--src/minutils/s6ps_grcache.c66
-rw-r--r--src/minutils/s6ps_otree.c98
-rw-r--r--src/minutils/s6ps_pfield.c570
-rw-r--r--src/minutils/s6ps_pwcache.c66
-rw-r--r--src/minutils/s6ps_statparse.c155
-rw-r--r--src/minutils/s6ps_ttycache.c153
-rw-r--r--src/minutils/s6ps_wchan.c96
-rwxr-xr-xtools/gen-deps.sh79
-rwxr-xr-xtools/install.sh64
73 files changed, 4749 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..4efb17d
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,6 @@
+Main author:
+  Laurent Bercot <ska-skaware@skarnet.org>
+
+Thanks to:
+  Dan J. Bernstein <djb@cr.yp.to>
+  Jorge Almeida <jalmeida@math.ist.utl.pt>
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..d78becc
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,130 @@
+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 is Linux-specific. It will run on a Linux kernel,
+version 2.6.32 or later.
+
+
+* Standard usage
+  --------------
+
+  ./configure && make && sudo make install
+
+ will work for most users.
+ It will install the binaries in /bin.
+
+ You can strip the binaries and libraries of their extra symbols via
+"make strip" before the "make install" phase. It will shave a few bytes
+off them.
+
+
+* Customization
+  -------------
+
+ You can customize paths via flags given to configure.
+ See ./configure --help for a list of all available configure options.
+
+
+* Environment variables
+  ---------------------
+
+ Controlling a build process via environment variables is a big and
+dangerous hammer. You should try and pass flags to configure instead;
+nevertheless, the standard environment variables are recognized.
+
+ The value of the CROSS_COMPILE environment variable will prefix the
+building tools' names. The --enable-cross option is preferred, see
+"Cross-compilation" below.
+ 
+ If the CC environment variable is set, its value will override compiler
+detection by configure.
+
+ The values of CFLAGS, CPPFLAGS and LDFLAGS will be appended to flags
+auto-detected by configure. To entirely override the flags set by
+configure, use make -e.
+
+ The value of LDLIBS will be appended by make to command lines that link
+an executable, even without the -e option.
+
+ The Makefile supports the DESTDIR convention for staging.
+
+
+* Shared libraries
+  ----------------
+
+ Software from skarnet.org is small enough that shared libraries are
+generally not worth using. Static linking is simpler and incurs less
+runtime overhead and less points of failure: so by default, shared
+libraries are not built and binaries are linked against the static
+versions of the skarnet.org libraries. Nevertheless, you can:
+  * build shared libraries: --enable-shared
+  * link binaries against shared libraries: --disable-allstatic
+
+
+* Static binaries
+  ---------------
+
+ By default, binaries are linked against static versions of all the
+libraries they depend on, except for the libc. You can enforce
+linking against the static libc with --enable-static-libc.
+
+ 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..4ba497f
--- /dev/null
+++ b/README
@@ -0,0 +1,30 @@
+s6-linux-utils - tiny Linux-specific utilities
+-----------------------------------------------
+
+s6-linux-utils is a set of tiny Linux-specific utilities,
+often performing well-known tasks such as mount and swapon
+ but optimized for simplicity and small size. They were
+designed for embedded systems and other constrained
+environments, but they work on any Linux system.
+
+ Of particular interest in this package are the following
+programs:
+ - s6-devd, a minimal udevd implementation
+ - s6-ps, an independent ps implementation
+
+ See http://skarnet.org/software/s6-linux-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-linux-utils.
diff --git a/README.macosx b/README.macosx
new file mode 100644
index 0000000..d71a096
--- /dev/null
+++ b/README.macosx
@@ -0,0 +1,4 @@
+
+ This package will compile and run on Darwin (MacOS X), but the building of
+shared libraries is not supported.
+ Make sure you use the --disable-shared option to configure.
diff --git a/README.solaris b/README.solaris
new file mode 100644
index 0000000..91a5b26
--- /dev/null
+++ b/README.solaris
@@ -0,0 +1,12 @@
+
+ This package assumes the existence of a POSIX shell in /bin/sh.
+ On Solaris, /bin/sh is not POSIX. Most versions of Solaris provide
+a POSIX shell in /usr/xpg4/bin/sh.
+
+ To compile this package on Solaris, you will need to run
+
+     ./patch-for-solaris
+
+ before you run ./configure. This script will change the #! invocation
+of the configure script and various tools so that a POSIX shell is used
+for the compilation process.
diff --git a/configure b/configure
new file mode 100755
index 0000000..deabe48
--- /dev/null
+++ b/configure
@@ -0,0 +1,385 @@
+#!/bin/sh
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [VAR=VALUE]... [TARGET]
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+System types:
+  --target=TARGET               configure to run on target TARGET [detected]
+  --host=TARGET                 same as --target
+
+Installation directories:
+  --prefix=PREFIX               main installation prefix [/]
+  --exec-prefix=EPREFIX         installation prefix for executable files [PREFIX]
+
+Fine tuning of the installation directories:
+  --dynlibdir=DIR               shared library files [PREFIX/lib]
+  --bindir=DIR                  user executables [EPREFIX/bin]
+  --sbindir=DIR                 admin executables [EPREFIX/sbin]
+  --libexecdir=DIR              package-scoped executables [EPREFIX/libexec]
+  --libdir=DIR                  static library files [PREFIX/lib]
+  --includedir=DIR              include files for the C compiler [PREFIX/include]
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [/usr/lib/skalibs/sysdeps]
+  --with-include=DIR            add DIR to the list of searched directories for headers
+  --with-lib=DIR                add DIR to the list of searched directories for static libraries
+  --with-dynlib=DIR             add DIR to the list of searched directories for shared libraries
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-cross=PREFIX         prefix toolchain executable names with PREFIX [none]
+
+EOF
+exit 0
+}
+
+
+# Helper functions
+
+# If your system does not have printf, you can comment this, but it is
+# generally not a good idea to use echo.
+# See http://www.etalabs.net/sh_tricks.html
+echo () {
+  IFS=" "
+  printf %s\\n "$*"
+}
+
+quote () {
+  tr '\n' ' ' <<EOF | grep '^[-[:alnum:]_=,./:]* $' >/dev/null 2>&1 && { echo "$1" ; return 0 ; }
+$1
+EOF
+  echo "$1" | sed -e "s/'/'\\\\''/g" -e "1s/^/'/" -e "\$s/\$/'/" -e "s#^'\([-[:alnum:]_,./:]*\)=\(.*\)\$#\1='\2#" -e "s|\*/|* /|g"
+}
+
+fail () {
+  echo "$*"
+  exit 1
+}
+
+fnmatch () {
+  eval "case \"\$2\" in $1) return 0 ;; *) return 1 ;; esac"
+}
+
+cmdexists () {
+  type "$1" >/dev/null 2>&1
+}
+
+trycc () {
+  test -z "$CC_AUTO" && cmdexists "$1" && CC_AUTO=$1
+}
+
+stripdir () {
+  while eval "fnmatch '*/' \"\${$1}\"" ; do
+    eval "$1=\${$1%/}"
+  done
+}
+
+tryflag () {
+  echo "checking whether compiler accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CPPFLAGS_AUTO $CFLAGS_AUTO "$2" -c -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+tryldflag () {
+  echo "checking whether linker accepts $2 ..."
+  echo "typedef int x;" > "$tmpc"
+  if $CC_AUTO $CFLAGS_AUTO $LDFLAGS_AUTO -nostdlib "$2" -o /dev/null "$tmpc" >/dev/null 2>&1 ; then
+    echo "  ... yes"
+    eval "$1=\"\${$1} \$2\""
+    eval "$1=\${$1# }"
+    return 0
+  else
+    echo "  ... no"
+    return 1
+  fi
+}
+
+
+# Actual script
+
+. package/info
+
+CC_AUTO="$CC"
+CFLAGS_AUTO="$CFLAGS"
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 $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..1d289cc
--- /dev/null
+++ b/doc/index.html
@@ -0,0 +1,126 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils - skarnet's tiny Linux-specific utilities</title>
+    <meta name="Description" content="s6-linux-utils - skarnet's tiny Linux-specific utilities" />
+    <meta name="Keywords" content="s6 unix administration root laurent bercot ska skarnet linux utilities tiny linux-specific" />
+    <!-- <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-linux-utils </h1>
+
+<h2> What is it&nbsp;? </h2>
+
+<p>
+ s6-linux-utils is a set of minimalistic Linux-specific system utilities.
+</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-linux-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-linux-utils is <a href="s6-linux-utils-2.0.0.0.tar.gz">2.0.0.0</a>. </li>
+ <li> Alternatively, you can checkout a copy of the s6-linux-utils git repository:
+<pre> git clone git://git.skarnet.org/s6-linux-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-linux-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><a href="s6-chroot.html">The <tt>s6-chroot</tt> program</a></li>
+<li><a href="s6-devd.html">The <tt>s6-devd</tt> program</a></li>
+<li><a href="s6-freeramdisk.html">The <tt>s6-freeramdisk</tt> program</a></li>
+<li><a href="s6-halt.html">The <tt>s6-halt</tt> program</a></li>
+<li><a href="s6-hiercopy.html">The <tt>s6-hiercopy</tt> program</a></li>
+<li><a href="s6-hostname.html">The <tt>s6-hostname</tt> program</a></li>
+<li><a href="s6-logwatch.html">The <tt>s6-logwatch</tt> program</a></li>
+<li><a href="s6-mount.html">The <tt>s6-mount</tt> program</a></li>
+<li><a href="s6-pivotchroot.html">The <tt>s6-pivotchroot</tt> program</a></li>
+<li><a href="s6-poweroff.html">The <tt>s6-poweroff</tt> program</a></li>
+<li><a href="s6-ps.html">The <tt>s6-ps</tt> program</a></li>
+<li><a href="s6-reboot.html">The <tt>s6-reboot</tt> program</a></li>
+<li><a href="s6-swapoff.html">The <tt>s6-swapoff</tt> program</a></li>
+<li><a href="s6-swapon.html">The <tt>s6-swapon</tt> program</a></li>
+<li><a href="s6-umount.html">The <tt>s6-umount</tt> program</a></li>
+</ul>
+
+<h2> Related resources </h2>
+
+<ul>
+ <li> <tt>s6-linux-utils</tt> is discussed on the
+<a href="http://skarnet.org/lists.html#skaware">skaware</a> mailing-list. </li>
+</ul>
+
+<h2> Similar work </h2>
+
+<p>
+ There are several good projects aiming to provide a minimal userspace
+environment for Linux, suitable for embedded systems. Among them, for
+instance:
+</p>
+
+<ul>
+ <li> <a href="http://busybox.net/">BusyBox</a> </li>
+ <li> <a href="http://landley.net/code/toybox/">toybox</a> </li>
+</ul>
+
+<p>
+ Most of the time, these projects aim to implement standard commands in a
+lightweight way, and
+they do it well enough. So, although some standard reimplentation already
+exists in s6-linux-utils and its sibling package
+<a href="http://skarnet.org/software/s6-portable-utils/">s6-portable-utils</a>,
+it is an explicit non-goal of those packages to duplicate the work of those
+projects, and no more rewriting of standard commands will occur.
+</p>
+
+</body>
+</html>
diff --git a/doc/s6-chroot.html b/doc/s6-chroot.html
new file mode 100644
index 0000000..a15d0fa
--- /dev/null
+++ b/doc/s6-chroot.html
@@ -0,0 +1,40 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-chroot program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-chroot program" />
+    <meta name="Keywords" content="s6-linux-utils linux utilities chroot" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-chroot</tt> program </h1>
+
+<p>
+<tt>s6-chroot</tt> executes a program in a chroot jail.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-chroot <em>newroot</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>s6-chroot</tt> sets the root filesystem for the current
+process to <em>newroot</em>, which must be an existing directory. </li>
+ <li> <tt>s6-chroot</tt> changes directory to this new root, and
+executes <em>prog</em>
+with its arguments. <em>prog</em> is searched under the new root. </li>
+ <li> s6-chroot's parent process is unaffected by the root change. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-devd.html b/doc/s6-devd.html
new file mode 100644
index 0000000..5ae6160
--- /dev/null
+++ b/doc/s6-devd.html
@@ -0,0 +1,80 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-devd program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-devd program" />
+    <meta name="Keywords" content="s6 linux administration root utilities devd mdev udev" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-devd</tt> program </h1>
+
+<p>
+<tt>s6-devd</tt> listens to the netlink interface for udev events, and
+launches a helper program for every event, similarly to what the hotplug
+interface does.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-devd [ -q | -v ] [ -b kbufsz ] [ -t l:t:k ] <em>prog...</em>
+</pre>
+
+<ul>
+ <li> s6-devd binds to the netlink interface and listens for
+hotplug events, as the <em>udev</em> program does. </li>
+ <li> For every event it receives, it spawns <em>prog...</em> with
+the event variables added to the environment, just as if <em>prog...</em>
+had been registered in <tt>/proc/sys/kernel/hotplug</tt>. </li>
+ <li> However, unlike the kernel, s6-devd spawns the <em>prog...</em> helpers
+sequentially: it waits for an instance to finish before spawning another one. </li>
+ <li> s6-devd is a long-lived program; it exits 0 when it receives a
+SIGTERM. If a helper program is alive at that time, s6-devd waits for it to die
+before exiting. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-q</tt>&nbsp;: be more quiet. </li>
+ <li> <tt>-v</tt>&nbsp;: be more verbose. </li>
+ <li> <tt>-b</tt> <em>kbufsz</em>&nbsp;: try and reserve a kernel buffer of
+<em>kbufsz</em> bytes for the netlink queue. Too large a buffer wastes kernel memory;
+too small a buffer risks losing events. The default is 65536. </li>
+ <li> <tt>-t</tt> <em>l:t:k</em>&nbsp;: If <em>l</em>, <em>t</em> or <em>k</em> is
+specified, they specify timeouts; by default, they are infinite.
+If <em>prog...</em> is still alive after <em>l</em> milliseconds, s6-devd sends
+it a SIGTERM. Then, if <em>prog...</em> is still alive after <em>t</em> more
+milliseconds, s6-devd sends it a SIGKILL. Then, if <em>prog...</em> is still
+alive after <em>k</em> more milliseconds, s6-devd yells and exits 99. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-devd is a daemon; it should be run under a proper supervision system such
+as <a href="http://skarnet.org/software/s6/">s6</a>. (That is why it does not
+fork and logs to stderr.) </li>
+ <li> The <em>prog...</em> helper, on the other hand, should be very short-lived,
+even if you are not using the <tt>-t</tt> option to s6-devd. Since helpers are
+spawned sequentially, slow helpers can make events queue up and fill the netlink
+kernel buffer. </li>
+ <li> If you are using <a href="http://busybox.net/">busybox</a> and want a
+minimal udev-style dynamic <tt>/dev</tt>
+handling, <tt>/sbin/mdev</tt> is a suitable <em>prog...</em> helper. </li>
+ <li> The point of s6-devd is that it runs the helpers sequentially, so it solves
+the race condition that appears when helpers are run via the hotplug interface.
+When s6-devd is used, <tt>/proc/sys/kernel/hotplug</tt> should be empty. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-freeramdisk.html b/doc/s6-freeramdisk.html
new file mode 100644
index 0000000..859571a
--- /dev/null
+++ b/doc/s6-freeramdisk.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-linux-utils: the s6-freeramdisk program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-freeramdisk program" />
+    <meta name="Keywords" content="s6 linux administration root utilities freeramdisk" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-freeramdisk</tt> program </h1>
+
+<p>
+<tt>freeramdisk</tt> frees the memory occupied by a RAM disk. Call it
+when your RAM disk is not in use anymore.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-freeramdisk <em>ramdisk-device</em>
+</pre>
+
+</body>
+</html>
diff --git a/doc/s6-halt.html b/doc/s6-halt.html
new file mode 100644
index 0000000..2c11c5a
--- /dev/null
+++ b/doc/s6-halt.html
@@ -0,0 +1,36 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-halt program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-halt program" />
+    <meta name="Keywords" content="s6 linux administration root utilities halt" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-halt</tt> program </h1>
+
+<p>
+<tt>s6-halt</tt> syncs the filesystems and halts the machine
+immediately, without switching the power off.
+<br /> This is different from the sysvinit <tt>halt</tt>
+command, which is an alias for <tt>shutdown -h</tt>. The
+s6-linux-utils <tt>s6-halt</tt> command is more or less equivalent to
+sysvinit's <tt>halt -f</tt>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-halt
+</pre>
+
+</body>
+</html>
diff --git a/doc/s6-hiercopy.html b/doc/s6-hiercopy.html
new file mode 100644
index 0000000..e8dd2ed
--- /dev/null
+++ b/doc/s6-hiercopy.html
@@ -0,0 +1,66 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-hiercopy program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-hiercopy program" />
+    <meta name="Keywords" content="s6 linux administration root utilities hiercopy cp -a" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-hiercopy</tt> program </h1>
+
+<p>
+<tt>s6-hiercopy</tt> copies a directory structure recursively.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-hiercopy <em>source</em> <em>destination</em>
+</pre>
+
+<ul>
+ <li> <tt>s6-hiercopy</tt> recursively copies <em>source</em> to
+<em>destination</em>, which is created if it doesn't exist.
+The permissions are preserved. The owner and group are preserved
+if the user is the superuser. </li>
+ <li> It exits 0 on success and 111 on temporary failure. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<p>
+ Copying files and browsing through directories is one of Unix's
+weakest points, and <tt>s6-hiercopy</tt> is not meant to work around
+the problem; it's only a quick hack that I needed to boot my embedded
+platform. I originally planned to write the ultimate <tt>cp</tt> utility,
+portable and reliable and featureful and everything - while needing
+approximately a hundred times less resources than GNU <tt>cp</tt> does,
+of course. But I eventually dropped the idea: it's just impossible to
+design, much less write, such a utility.
+</p>
+
+<ul>
+ <li> You can't make it portable because there's no universal standard.
+There is no portable way of creating device special files, for instance.
+So <tt>s6-hiercopy</tt> appears here instead of in
+<a href="http://skarnet.org/software/s6-portable-utils/">s6-portable-utils</a>:
+the platform where I needed that kind of tool is Linux. </li>
+ <li> You can't make it reliable because Unix's set of filesystem
+management primitives is just too weak. It lacks a lot of atomic
+operations, and filesystem transactions. As a result, <tt>s6-hiercopy</tt>
+is a walking race condition and should <strong>absolutely not</strong>
+be considered instant when used in a multitasking environment.
+But then, <tt>cp -a</tt> shouldn't either. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-hostname.html b/doc/s6-hostname.html
new file mode 100644
index 0000000..8ff0081
--- /dev/null
+++ b/doc/s6-hostname.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-linux-utils: the s6-hostname program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-hostname program" />
+    <meta name="Keywords" content="s6-linux-utils linux utilities hostname" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-hostname</tt> program </h1>
+
+<p>
+<tt>s6-hostname</tt> gets or sets the machine hostname.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-hostname [ <em>name</em> ]
+</pre>
+
+<ul>
+ <li> When called without an argument, <tt>s6-hostname</tt> prints the
+machine hostname to stdout. </li>
+ <li> When called with an argument, <tt>s6-hostname</tt> sets the machine
+hostname to <em>name</em>. </li>
+ <li> Then it exits 0. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-logwatch.html b/doc/s6-logwatch.html
new file mode 100644
index 0000000..5075d83
--- /dev/null
+++ b/doc/s6-logwatch.html
@@ -0,0 +1,72 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-logwatch program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-logwatch program" />
+    <meta name="Keywords" content="s6-linux-utils linux utilities log s6-log logwatch" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-logwatch</tt> program </h1>
+
+<p>
+<tt>s6-logwatch</tt> watches the <tt>current</tt> file of a logdir, printing it
+in real time.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-logwatch [ -m <em>buflen</em> ] <em>logdir</em>
+</pre>
+
+<ul>
+ <li> s6-logwatch prints <em>logdir</em><tt>/current</tt> and watches
+the file. </li>
+ <li> <em>logdir</em> must be managed by a
+<a href="http://skarnet.org/software/s6/s6-log.html">s6-log</a> instance. </li>
+ <li> When new logs are appended to the <tt>current</tt> file, s6-logwatch prints
+them in real-time to stdout. </li>
+ <li> When a rotation happens, s6-logwatch notices, and keeps watching the
+new <tt>current</tt> file. </li>
+ <li> s6-logwatch runs forever until killed. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-m</tt>&nbsp;<em>buflen</em>&nbsp;: accumulate at most <em>buflen</em>
+bytes into the stdout buffer before flushing it. By default, <em>buflen</em> is
+4000. </li>
+</ul>
+
+<h2> Bugs </h2>
+
+<ul>
+ <li> s6-logwatch is not entirely reliable because there is an unavoidable
+race condition when a rotation occurs; it's a hack for humans to keep reading
+logs across rotations, not a tool to be used in safe programming. When the
+race condition is triggered, s6-logwatch will be unable to understand what
+state <em>logdir</em> is in and will exit 101 with an error message. </li>
+ <li> Specific support in the logger program would be needed to avoid this
+race condition; it would significantly bloat the logger program, so it has
+not been deemed useful. </li>
+</ul>
+
+<h2> Notes </h2>
+
+<ul>
+ <li> s6-logwatch is Linux-specific because it uses the
+<a href="http://inotify.aiken.cz/">inotify</a> interface. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-mount.html b/doc/s6-mount.html
new file mode 100644
index 0000000..b12c505
--- /dev/null
+++ b/doc/s6-mount.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-linux-utils: the s6-mount program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-mount program" />
+    <meta name="Keywords" content="s6 linux administration root utilities mount" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-mount</tt> program </h1>
+
+<p>
+<tt>s6-mount</tt> mounts filesystems.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-mount -a
+     s6-mount [ -r | -w ] [ -t fstype ] [ -o option[,option...] ] <em>device</em> <em>mntpoint</em>
+</pre>
+
+<ul>
+ <li> <tt>s6-mount -a</tt> mounts all partitions according to <tt>/etc/fstab</tt>. </li>
+ <li> If the <tt>-a</tt> option is not given,
+<tt>s6-mount</tt> mounts <em>device</em> on <em>mntpoint</em>. </li>
+ <li> <tt>s6-mount</tt> does not touch <tt>/etc/mtab</tt>. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-a</tt>&nbsp;: read <tt>/etc/fstab</tt> instead of other arguments </li>
+ <li> <tt>-t</tt> <em>fstype</em>&nbsp;: filesystem is of type <em>fstype</em>. Default: ext2. </li>
+ <li> <tt>-r</tt>&nbsp;: mount read-only </li>
+ <li> <tt>-w</tt>&nbsp;: mount read-write (default) </li>
+ <li> <tt>-o</tt> <em>option</em>&nbsp;: mount with option <em>option</em>.
+Currently recognized options: defaults, ro, rw, remount, sync, async,
+nodev, dev, noexec, exec, nosuid, suid, noatime, atime, nodiratime, diratime,
+bind, nobind, move, nomove. Unrecognized options are given directly to the kernel. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-pivotchroot.html b/doc/s6-pivotchroot.html
new file mode 100644
index 0000000..42ba383
--- /dev/null
+++ b/doc/s6-pivotchroot.html
@@ -0,0 +1,41 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-pivotchroot program</title>
+    <meta name="Description" content="s6-linux-utils: the pivotchroot program" />
+    <meta name="Keywords" content="s6 linux administration root utilities pivot_root chroot pivotchroot" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-pivotchroot</tt> program </h1>
+
+<p>
+<tt>s6-pivotchroot</tt> performs a <em>pivot_root</em> system call,
+then changes root to the new root filesystem and executes a program.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-pivotchroot <em>newroot</em> <em>putold</em> <em>prog...</em>
+</pre>
+
+<ul>
+ <li> <tt>s6-pivotchroot</tt> sets the root filesystem to <em>newroot</em>,
+which must be a mounted filesystem entry point. The old root filesystem
+will be available under <em>putold</em> (relative to the old root). </li>
+ <li> <tt>s6-pivotchroot</tt> changes directory to the new root, performs
+a <em>chroot</em> system call to the new root, and executes <em>prog</em>
+with its arguments. <em>prog</em> is searched under the new root. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-poweroff.html b/doc/s6-poweroff.html
new file mode 100644
index 0000000..3685fff
--- /dev/null
+++ b/doc/s6-poweroff.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-linux-utils: the s6-poweroff program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-poweroff program" />
+    <meta name="Keywords" content="s6 linux administration root poweroff system halt power off" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-poweroff</tt> program </h1>
+
+<p>
+<tt>s6-poweroff</tt> syncs the filesystems and halts the machine
+immediately, switching the power off if possible.
+</p>
+
+<p>
+ This is different from the sysvinit <tt>poweroff</tt>
+command, which performs <tt>shutdown</tt>. The
+s6-linux-utils <tt>s6-poweroff</tt> command is more or less equivalent to
+sysvinit's <tt>poweroff -f</tt>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-poweroff
+</pre>
+
+</body>
+</html>
diff --git a/doc/s6-ps.html b/doc/s6-ps.html
new file mode 100644
index 0000000..43ed0d2
--- /dev/null
+++ b/doc/s6-ps.html
@@ -0,0 +1,128 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-ps program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-ps program" />
+    <meta name="Keywords" content="s6 linux administration root utilities ps proc procps process list" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-ps</tt> program </h1>
+
+<p>
+ <tt>s6-ps</tt> shows a list of processes on the system.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-ps [ -H ] [ -w spacing ] [ -W chanfile ] [ -l | -o <em>field</em>,<em>field</em>,... ]
+</pre>
+
+<ul>
+ <li> s6-ps assumes the <em>proc</em> filesystem is mounted on <tt>/proc</tt>.
+It scans <tt>/proc</tt> for a list of all processes on the system. </li>
+ <li> It prints a header line, then information about the processes it has found,
+one process per line. Then it exits 0. </li>
+ <li> If s6-ps is not allowed to read a field, it will print <tt>*</tt> (a star) instead
+of the field's content. <tt>-</tt> (a dash) means the field is unapplicable here,
+for instance the "tty" field for a process that has no controlling terminal. </li>
+</ul>
+
+<h2> Options </h2>
+
+<ul>
+ <li> <tt>-H</tt>&nbsp;: tree. s6-ps will display he process list as a tree,
+as <tt>ps f</tt> does. It will print kernel processes first, then user processes.
+By default, the processes are simply ordered by increasing pid number, without
+care for the process hierarchy. </li>
+ <li> <tt>-w&nbsp;<em>spacing</em></tt>&nbsp;: leave at least <em>spacing</em>
+space characters between two adjacent fields. Minimum is 1, default is 2,
+maximum is 256. </li>
+ <li> <tt>-W&nbsp;<em>wchanfile</em></tt>&nbsp;: force <em>wchanfile</em> as the
+file listing kernel addresses for WCHAN fields. By default, s6-ps tries to use
+<tt>/proc/kallsyms</tt>, <tt>/boot/System.map-`uname -r`</tt>, and
+<tt>/boot/System.map</tt>, in that order. </li>
+ <li> <tt>-l</tt>&nbsp;: long. Equivalent to
+<tt>-o user,pid,cpcpu,pmem,vsize,rss,tty,s,start,cttime,args</tt>. </li>
+ <li> <tt>-o&nbsp;<em>fieldlist</em></tt>&nbsp;: list of fields to print.
+<em>fieldlist</em> must be a comma-separated list of fields, without spaces.
+Fields cannot be duplicated. They will be printed in the given order.
+The valid field names are listed below. The default field list is
+<tt>user,pid,tty,s,start,args</tt>. </li>
+</ul>
+
+<h2> Fields </h2>
+
+<p>
+ The <tt>-o</tt> option makes it possible to customize s6-ps's output. Here are the
+recognized <em>field</em> keywords and the corresponding field they display.
+</p>
+
+<ul>
+ <li> <tt>pid</tt>&nbsp: the process id number. </li>
+ <li> <tt>comm</tt>&nbsp: the command name as known by the kernel. </li>
+ <li> <tt>s</tt>&nbsp: the one-character state of the process, then <tt>s</tt>
+if the process is a session leader or <tt>+</tt> if it is a foreground
+process group leader, then <tt>N</tt> if
+the process is niced or <tt>&lt;</tt> if it is anti-niced. Unlike ps, s6-ps
+cannot tell whether a process has locked memory pages or not. </li>
+ <li> <tt>ppid</tt>&nbsp: the parent process' pid. </li>
+ <li> <tt>pgrp</tt>&nbsp: the process group number. </li>
+ <li> <tt>sess</tt>&nbsp: the session leader's pid. </li>
+ <li> <tt>tty</tt>&nbsp: the name of the process's controlling terminal.</li>
+ <li> <tt>tpgid</tt>&nbsp: the pid of the foreground process group. </li>
+ <li> <tt>utime</tt>&nbsp: the time the process spent in user mode. </li>
+ <li> <tt>stime</tt>&nbsp: the time the process spent in kernel mode. </li>
+ <li> <tt>cutime</tt>&nbsp: the time spent in user mode by the process and
+all its dead children. </li>
+ <li> <tt>cstime</tt>&nbsp: the time spent in kernel mode by the process and
+all its dead children. </li>
+ <li> <tt>prio</tt>&nbsp: the process' priority as computed by the kernel. </li>
+ <li> <tt>nice</tt>&nbsp: the process' nice value. </li>
+ <li> <tt>thcount</tt>&nbsp: the number of threads in the process. </li>
+ <li> <tt>start</tt>&nbsp: the start time of the process. </li>
+ <li> <tt>vsize</tt>&nbsp: the virtual memory size of the process. </li>
+ <li> <tt>rss</tt>&nbsp: the resident set size of the process. </li>
+ <li> <tt>rsslimit</tt>&nbsp: the maximum rss allowed for the process. </li>
+ <li> <tt>psr</tt>&nbsp: the number of the CPU  the process is running on. </li>
+ <li> <tt>rtprio</tt>&nbsp: the real-time priority of the process. </li>
+ <li> <tt>policy</tt>&nbsp: the real-time policy of the process - a symbolic name. </li>
+ <li> <tt>user</tt>&nbsp: the user the process is running as. </li>
+ <li> <tt>group</tt>&nbsp: the group the process is running as. </li>
+ <li> <tt>pmem</tt>&nbsp: the percentage of the available virtual memory the process
+is using. Be aware that it is a very rough estimate: determining exactly how much memory
+a process is using is a complex task. </li>
+ <li> <tt>wchan</tt>&nbsp: the name or address of the kernel function where the
+proces is sleeping. It is actually very rare that a kernel is configured to properly
+export that value, because it incurs a small performance cost. </li>
+ <li> <tt>args</tt>&nbsp: the command line of the process. </li>
+ <li> <tt>env</tt>&nbsp: the process environment. s6-ps can normally only print
+the environment for processes running as the same user, or if it is run as root.
+Be aware that the environment is usually quite large, and will create very long
+lines, likely to mess up the display on a standard terminal. </li>
+</li>
+ <li> <tt>pcpu</tt>&nbsp: the percentage of CPU used by the process (total cpu time
+divided by total running time). It is a mean value and does not reflect current CPU
+usage. </li>
+ <li> <tt>ttime</tt>&nbsp: total CPU time used by the process (user + kernel mode). </li>
+ <li> <tt>cttime</tt>&nbsp: total CPU time used by the process and all its dead
+children (user + kernel mode). </li>
+ <li> <tt>tstart</tt>&nbsp: the start time of the process as a
+<a href="http://cr.yp.to/libtai/tai64.html">TAI64N</a> value. </li>
+ <li> <tt>cpcpu</tt>&nbsp: the percentage of CPU used by the process and its dead
+children (total cpu time
+divided by total running time). It is a mean value and does not reflect current CPU
+usage. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-reboot.html b/doc/s6-reboot.html
new file mode 100644
index 0000000..9d2c594
--- /dev/null
+++ b/doc/s6-reboot.html
@@ -0,0 +1,38 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-reboot program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-reboot program" />
+    <meta name="Keywords" content="s6 linux administration root utilities reboot power shutdown" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-reboot</tt> program </h1>
+
+<p>
+<tt>s6-reboot</tt> syncs the filesystems and reboots the machine
+immediately.
+</p>
+
+<p> This is different from the sysvinit <tt>reboot</tt>
+command, which is an alias for <tt>shutdown -r</tt>. The
+s6-linux-utils <tt>s6-reboot</tt> command is more or less equivalent to
+sysvinit's <tt>reboot -f</tt>.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-reboot
+</pre>
+
+</body>
+</html>
diff --git a/doc/s6-swapoff.html b/doc/s6-swapoff.html
new file mode 100644
index 0000000..00307da
--- /dev/null
+++ b/doc/s6-swapoff.html
@@ -0,0 +1,36 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-swapoff program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-swapoff program" />
+    <meta name="Keywords" content="s6 linux administration root utilities swap off partition" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-swapoff</tt> program </h1>
+
+<tt>s6-swapoff</tt> deactivates swap partitions.
+
+<h2> Interface </h2>
+
+<pre>
+     s6-swapoff [ -a | <em>device</em> ]
+</pre>
+
+<ul>
+ <li> <tt>s6-swapoff</tt> deactivates the swap on <em>device</em>. </li>
+ <li> If the <tt>-a</tt> option is given instead of <em>device</em>,
+<tt>s6-swapoff</tt> looks for swap partitions in
+<tt>/proc/swaps</tt> and deactivates them all. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-swapon.html b/doc/s6-swapon.html
new file mode 100644
index 0000000..3298478
--- /dev/null
+++ b/doc/s6-swapon.html
@@ -0,0 +1,36 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-swapon program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-swapon program" />
+    <meta name="Keywords" content="s6 linux administration root utilities swap on partition" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-swapon</tt> program </h1>
+
+<tt>s6-swapon</tt> activates swap partitions.
+
+<h2> Interface </h2>
+
+<pre>
+     s6-swapon [ -a | <em>device</em> ]
+</pre>
+
+<ul>
+ <li> <tt>s6-swapon</tt> activates the swap on <em>device</em>. </li>
+ <li> If the <tt>-a</tt> option is given instead of <em>device</em>,
+<tt>s6-swapon</tt> looks for partitions marked <em>swap</em> in
+<tt>/etc/fstab</tt> and activates them all. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/s6-umount.html b/doc/s6-umount.html
new file mode 100644
index 0000000..3efe82c
--- /dev/null
+++ b/doc/s6-umount.html
@@ -0,0 +1,38 @@
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <meta http-equiv="Content-Language" content="en" />
+    <title>s6-linux-utils: the s6-umount program</title>
+    <meta name="Description" content="s6-linux-utils: the s6-umount program" />
+    <meta name="Keywords" content="s6 linux administration root linux utilities umount unmount filesystem" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-umount</tt> program </h1>
+
+<p>
+ <tt>s6-umount</tt> unmounts filesystems.
+</p>
+
+<h2> Interface </h2>
+
+<pre>
+     s6-umount [ -a | <em>mntpoint</em> ]
+</pre>
+
+<ul>
+ <li> <tt>s6-umount -a</tt> unmounts all partitions according to <tt>/proc/mounts</tt>. </li>
+ <li> If the <tt>-a</tt> option is not given,
+<tt>s6-umount</tt> unmounts the filesystem mounted on <em>mntpoint</em>. </li>
+ <li> <tt>s6-umount</tt> does not touch <tt>/etc/mtab</tt>. </li>
+</ul>
+
+</body>
+</html>
diff --git a/doc/upgrade.html b/doc/upgrade.html
new file mode 100644
index 0000000..5ab660e
--- /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-linux-utils: how to upgrade</title>
+    <meta name="Description" content="s6-linux-utils: how to upgrade" />
+    <meta name="Keywords" content="s6-linux-utils installation upgrade" />
+    <!-- <link rel="stylesheet" type="text/css" href="http://skarnet.org/default.css" /> -->
+  </head>
+<body>
+
+<p>
+<a href="index.html">s6-linux-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-linux-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..7946756
--- /dev/null
+++ b/package/deps.mak
@@ -0,0 +1,42 @@
+#
+# This file has been generated by tools/gen-deps.sh
+#
+
+src/minutils/s6-chroot.o src/minutils/s6-chroot.lo: src/minutils/s6-chroot.c
+src/minutils/s6-devd.o src/minutils/s6-devd.lo: src/minutils/s6-devd.c
+src/minutils/s6-freeramdisk.o src/minutils/s6-freeramdisk.lo: src/minutils/s6-freeramdisk.c
+src/minutils/s6-halt.o src/minutils/s6-halt.lo: src/minutils/s6-halt.c
+src/minutils/s6-hiercopy.o src/minutils/s6-hiercopy.lo: src/minutils/s6-hiercopy.c
+src/minutils/s6-hostname.o src/minutils/s6-hostname.lo: src/minutils/s6-hostname.c
+src/minutils/s6-logwatch.o src/minutils/s6-logwatch.lo: src/minutils/s6-logwatch.c
+src/minutils/s6-mount.o src/minutils/s6-mount.lo: src/minutils/s6-mount.c src/minutils/mount-constants.h
+src/minutils/s6-pivotchroot.o src/minutils/s6-pivotchroot.lo: src/minutils/s6-pivotchroot.c
+src/minutils/s6-poweroff.o src/minutils/s6-poweroff.lo: src/minutils/s6-poweroff.c
+src/minutils/s6-ps.o src/minutils/s6-ps.lo: src/minutils/s6-ps.c src/minutils/s6-ps.h
+src/minutils/s6-reboot.o src/minutils/s6-reboot.lo: src/minutils/s6-reboot.c
+src/minutils/s6-swapoff.o src/minutils/s6-swapoff.lo: src/minutils/s6-swapoff.c
+src/minutils/s6-swapon.o src/minutils/s6-swapon.lo: src/minutils/s6-swapon.c
+src/minutils/s6-umount.o src/minutils/s6-umount.lo: src/minutils/s6-umount.c
+src/minutils/s6ps_grcache.o src/minutils/s6ps_grcache.lo: src/minutils/s6ps_grcache.c
+src/minutils/s6ps_otree.o src/minutils/s6ps_otree.lo: src/minutils/s6ps_otree.c src/minutils/s6-ps.h
+src/minutils/s6ps_pfield.o src/minutils/s6ps_pfield.lo: src/minutils/s6ps_pfield.c src/minutils/s6-ps.h
+src/minutils/s6ps_pwcache.o src/minutils/s6ps_pwcache.lo: src/minutils/s6ps_pwcache.c src/minutils/s6-ps.h
+src/minutils/s6ps_statparse.o src/minutils/s6ps_statparse.lo: src/minutils/s6ps_statparse.c src/minutils/s6-ps.h
+src/minutils/s6ps_ttycache.o src/minutils/s6ps_ttycache.lo: src/minutils/s6ps_ttycache.c src/minutils/s6-ps.h
+src/minutils/s6ps_wchan.o src/minutils/s6ps_wchan.lo: src/minutils/s6ps_wchan.c src/minutils/s6-ps.h
+
+s6-chroot: src/minutils/s6-chroot.o -lskarnet
+s6-devd: src/minutils/s6-devd.o -lskarnet
+s6-freeramdisk: src/minutils/s6-freeramdisk.o -lskarnet
+s6-halt: src/minutils/s6-halt.o -lskarnet
+s6-hiercopy: src/minutils/s6-hiercopy.o -lskarnet
+s6-hostname: src/minutils/s6-hostname.o -lskarnet
+s6-logwatch: src/minutils/s6-logwatch.o -lskarnet
+s6-mount: src/minutils/s6-mount.o -lskarnet
+s6-pivotchroot: src/minutils/s6-pivotchroot.o -lskarnet
+s6-poweroff: src/minutils/s6-poweroff.o -lskarnet
+s6-ps: src/minutils/s6-ps.o src/minutils/s6ps_statparse.o src/minutils/s6ps_otree.o src/minutils/s6ps_pfield.o src/minutils/s6ps_pwcache.o src/minutils/s6ps_grcache.o src/minutils/s6ps_ttycache.o src/minutils/s6ps_wchan.o -lskarnet
+s6-reboot: src/minutils/s6-reboot.o -lskarnet
+s6-swapoff: src/minutils/s6-swapoff.o -lskarnet
+s6-swapon: src/minutils/s6-swapon.o -lskarnet
+s6-umount: src/minutils/s6-umount.o -lskarnet
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..2fcdf48
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=s6-linux-utils
+version=2.0.0.0
+category=admin
+package_macro_name=S6_LINUX_UTILS
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..fddaa83
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,15 @@
+s6-chroot	0700
+s6-devd		0700
+s6-freeramdisk	0700
+s6-halt		0700
+s6-hiercopy	0755
+s6-hostname	0755
+s6-logwatch	0755
+s6-mount	0700
+s6-pivotchroot	0700
+s6-poweroff	0700
+s6-ps		0755
+s6-reboot	0700
+s6-swapoff	0700
+s6-swapon	0700
+s6-umount	0700
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..49d5077
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,22 @@
+BIN_TARGETS = \
+s6-chroot \
+s6-devd \
+s6-freeramdisk \
+s6-halt \
+s6-hiercopy \
+s6-hostname \
+s6-logwatch \
+s6-mount \
+s6-pivotchroot \
+s6-poweroff \
+s6-ps \
+s6-reboot \
+s6-swapoff \
+s6-swapon \
+s6-umount \
+
+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/minutils/deps-exe/s6-chroot b/src/minutils/deps-exe/s6-chroot
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-chroot
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-devd b/src/minutils/deps-exe/s6-devd
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-devd
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-freeramdisk b/src/minutils/deps-exe/s6-freeramdisk
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-freeramdisk
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-halt b/src/minutils/deps-exe/s6-halt
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-halt
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-hiercopy b/src/minutils/deps-exe/s6-hiercopy
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-hiercopy
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-hostname b/src/minutils/deps-exe/s6-hostname
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-hostname
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-logwatch b/src/minutils/deps-exe/s6-logwatch
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-logwatch
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-mount b/src/minutils/deps-exe/s6-mount
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-mount
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-pivotchroot b/src/minutils/deps-exe/s6-pivotchroot
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-pivotchroot
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-poweroff b/src/minutils/deps-exe/s6-poweroff
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-poweroff
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-ps b/src/minutils/deps-exe/s6-ps
new file mode 100644
index 0000000..f03a58b
--- /dev/null
+++ b/src/minutils/deps-exe/s6-ps
@@ -0,0 +1,8 @@
+s6ps_statparse.o
+s6ps_otree.o
+s6ps_pfield.o
+s6ps_pwcache.o
+s6ps_grcache.o
+s6ps_ttycache.o
+s6ps_wchan.o
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-reboot b/src/minutils/deps-exe/s6-reboot
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-reboot
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-swapoff b/src/minutils/deps-exe/s6-swapoff
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-swapoff
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-swapon b/src/minutils/deps-exe/s6-swapon
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-swapon
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/deps-exe/s6-umount b/src/minutils/deps-exe/s6-umount
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/minutils/deps-exe/s6-umount
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/minutils/mount-constants.h b/src/minutils/mount-constants.h
new file mode 100644
index 0000000..1e6f3c0
--- /dev/null
+++ b/src/minutils/mount-constants.h
@@ -0,0 +1,81 @@
+/* ISC license. */
+
+#ifndef MOUNT_CONSTANTS_H
+#define MOUNT_CONSTANTS_H
+
+/* taken from util-linux-ng */
+
+#ifndef MS_RDONLY
+#define MS_RDONLY	 1	/* Mount read-only */
+#endif
+#ifndef MS_NOSUID
+#define MS_NOSUID	 2	/* Ignore suid and sgid bits */
+#endif
+#ifndef MS_NODEV
+#define MS_NODEV	 4	/* Disallow access to device special files */
+#endif
+#ifndef MS_NOEXEC
+#define MS_NOEXEC	 8	/* Disallow program execution */
+#endif
+#ifndef MS_SYNCHRONOUS
+#define MS_SYNCHRONOUS	16	/* Writes are synced at once */
+#endif
+#ifndef MS_REMOUNT
+#define MS_REMOUNT	32	/* Alter flags of a mounted FS */
+#endif
+#ifndef MS_MANDLOCK
+#define MS_MANDLOCK	64	/* Allow mandatory locks on an FS */
+#endif
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC	128	/* Directory modifications are synchronous */
+#endif
+#ifndef MS_NOATIME
+#define MS_NOATIME	0x400	/* 1024: Do not update access times. */
+#endif
+#ifndef MS_NODIRATIME
+#define MS_NODIRATIME   0x800	/* 2048: Don't update directory access times */
+#endif
+#ifndef MS_BIND
+#define	MS_BIND		0x1000	/* 4096: Mount existing tree also elsewhere */
+#endif
+#ifndef MS_MOVE
+#define MS_MOVE		0x2000	/* 8192: Atomically move tree */
+#endif
+#ifndef MS_REC
+#define MS_REC		0x4000	/* 16384: Recursive loopback */
+#endif
+#ifndef MS_VERBOSE
+#define MS_VERBOSE	0x8000	/* 32768 */
+#endif
+#ifndef MS_RELATIME
+#define MS_RELATIME	0x200000 /* 200000: Update access times relative to mtime/ctime */
+#endif
+#ifndef MS_UNBINDABLE
+#define MS_UNBINDABLE	(1<<17)	/* 131072 unbindable */
+#endif
+#ifndef MS_PRIVATE
+#define MS_PRIVATE	(1<<18)	/* 262144 Private */
+#endif
+#ifndef MS_SLAVE
+#define MS_SLAVE	(1<<19)	/* 524288 Slave */
+#endif
+#ifndef MS_SHARED
+#define MS_SHARED	(1<<20)	/* 1048576 Shared */
+#endif
+#ifndef MS_I_VERSION
+#define MS_I_VERSION	(1<<23)	/* update inode I_version field */
+#endif
+#ifndef MS_STRICTATIME
+#define MS_STRICTATIME	(1<<24) /* strict atime semantics */
+#endif
+/*
+ * Magic mount flag number. Had to be or-ed to the flag values.
+ */
+#ifndef MS_MGC_VAL
+#define MS_MGC_VAL 0xC0ED0000	/* magic flag number to indicate "new" flags */
+#endif
+#ifndef MS_MGC_MSK
+#define MS_MGC_MSK 0xffff0000	/* magic flag number mask */
+#endif
+
+#endif
diff --git a/src/minutils/s6-chroot.c b/src/minutils/s6-chroot.c
new file mode 100644
index 0000000..654ee8f
--- /dev/null
+++ b/src/minutils/s6-chroot.c
@@ -0,0 +1,21 @@
+/* ISC license. */
+
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include <unistd.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-chroot dir prog..."
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  PROG = "s6-chroot" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  if (chdir(argv[1]) == -1) strerr_diefu2sys(111, "chdir to ", argv[1]) ;
+  if (chroot(".") == -1) strerr_diefu2sys(111, "chroot in ", argv[1]) ;
+  pathexec_run(argv[2], argv+2, envp) ;
+  strerr_dieexec(111, argv[2]) ;
+}
diff --git a/src/minutils/s6-devd.c b/src/minutils/s6-devd.c
new file mode 100644
index 0000000..2e5c9ce
--- /dev/null
+++ b/src/minutils/s6-devd.c
@@ -0,0 +1,288 @@
+/* ISC license. */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <spawn.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/sig.h>
+#include <skalibs/selfpipe.h>
+
+#define USAGE "s6-devd [ -q | -v ] [ -b kbufsz ] [ -t maxlife:maxterm:maxkill ] helperprogram..."
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static unsigned int cont = 1, state = 0, verbosity = 1 ;
+static pid_t pid ;
+static tain_t lifetto = TAIN_INFINITE_RELATIVE,
+              termtto = TAIN_INFINITE_RELATIVE,
+              killtto = TAIN_INFINITE_RELATIVE,
+              deadline ;
+
+static inline int fd_recvmsg (int fd, struct msghdr *hdr)
+{
+  int r ;
+  do r = recvmsg(fd, hdr, 0) ;
+  while ((r == -1) && (errno == EINTR)) ;
+  return r ;
+}
+
+static inline int netlink_init (unsigned int kbufsz)
+{
+  struct sockaddr_nl nl = { .nl_family = AF_NETLINK, .nl_pad = 0, .nl_groups = 1, .nl_pid = 0 } ;
+  int fd = socket_internal(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT, DJBUNIX_FLAG_NB|DJBUNIX_FLAG_COE) ;
+  if (fd < 0) return -1 ;
+  if (bind(fd, (struct sockaddr *)&nl, sizeof(struct sockaddr_nl)) < 0) goto err ;
+  if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &kbufsz, sizeof(unsigned int)) < 0) goto err ;
+  return fd ;
+ err:
+  {
+    register int e = errno ;
+    fd_close(fd) ;
+    errno = e ;
+  }
+  return -1 ;
+}
+
+static inline void on_death (void)
+{
+  pid = 0 ;
+  state = 0 ;
+  tain_add_g(&deadline, &tain_infinite_relative) ;
+  if (cont == 2) cont = 0 ;
+}
+
+static inline void on_event (char const *const *argv, char const *const *envp, char const *s, unsigned int len)
+{
+  unsigned int envlen = env_len(envp) ;
+  unsigned int n = envlen + 1 + byte_count(s, len, '\0') ;
+  int e ;
+  char const *v[n] ;
+  if (!env_merge(v, n, envp, envlen, s, len))
+    strerr_diefu1sys(111, "env_merge") ;
+  e = posix_spawnp(&pid, argv[0], 0, 0, (char *const *)argv, (char * const *)v) ;
+  if (e) { errno = e ; strerr_diefu2sys(111, "spawn ", argv[0]) ; }
+  state = 1 ;
+  tain_add_g(&deadline, &lifetto) ;
+}
+
+static inline void handle_timeout (void)
+{
+  switch (state)
+  {
+    case 0 :
+      tain_add_g(&deadline, &tain_infinite_relative) ;
+      break ;
+    case 1 :
+      kill(pid, SIGTERM) ;
+      tain_add_g(&deadline, &termtto) ;
+      state++ ;
+      break ;
+    case 2 :
+      kill(pid, SIGKILL) ;
+      tain_add_g(&deadline, &killtto) ;
+      state++ ;
+      break ;
+    case 3 :
+      strerr_dief1x(99, "child resisted SIGKILL - check your kernel logs.") ;
+    default :
+      strerr_dief1x(101, "internal error: inconsistent state. Please submit a bug-report.") ;
+  }
+}
+
+static inline void handle_signals (void)
+{
+  for (;;)
+  {
+    char c = selfpipe_read() ;
+    switch (c)
+    {
+      case -1 : strerr_diefu1sys(111, "selfpipe_read") ;
+      case 0 : return ;
+      case SIGTERM :
+        cont = pid ? 2 : 0 ;
+        break ;
+      case SIGCHLD :
+        if (!pid) wait_reap() ;
+        else
+        {
+          int wstat ;
+          int r = wait_pid_nohang(pid, &wstat) ;
+          if (r < 0)
+            if (errno != ECHILD) strerr_diefu1sys(111, "wait_pid_nohang") ;
+            else break ;
+          else if (!r) break ;
+          on_death() ;
+        }
+        break ;
+      default :
+        strerr_dief1x(101, "internal error: inconsistent signal state. Please submit a bug-report.") ;
+    }
+  }
+}
+
+static inline void handle_netlink (int fd, char const *const *argv, char const *const *envp)
+{
+  char buf[4096] ;
+  int r ;
+  {
+    struct sockaddr_nl nl;
+    struct iovec iov = { .iov_base = &buf, .iov_len = sizeof(buf) } ;
+    char ctlmsg[CMSG_SPACE(sizeof(struct ucred))] ;
+    struct msghdr msg = {
+                          .msg_name = &nl,
+                          .msg_namelen = sizeof(struct sockaddr_nl),
+                          .msg_iov = &iov,
+                          .msg_iovlen = 1,
+                          .msg_control = ctlmsg,
+                          .msg_controllen = sizeof(ctlmsg),
+                          .msg_flags = 0
+                        } ;
+    r = sanitize_read(fd_recvmsg(fd, &msg)) ;
+    if (r < 0)
+    {
+      if (errno == EPIPE)
+      {
+        if (verbosity >= 2) strerr_warnw1x("received EOF on netlink") ;
+        cont = 0 ;
+        return ;
+      }
+      else strerr_diefu1sys(111, "receive netlink message") ;
+    }
+    if (!r) return ;
+    if (r < 32 || r > 4096)
+    {
+      if (verbosity >= 2)
+        strerr_warnw2x("received and ignored netlink message ", "with invalid length") ;
+      return ;
+    }
+    if (nl.nl_pid)
+    {
+      if (verbosity >= 3)
+      {
+        char fmt[UINT_FMT] ;
+        fmt[uint_fmt(fmt, nl.nl_pid)] = 0 ;
+        strerr_warnw3x("received and ignored netlink message ", "from userspace process ", fmt) ;
+      }
+      return ;
+    }
+  }
+  {
+    unsigned int start = str_len(buf) + 1 ;
+    if (start < 5 || start > (unsigned int)r)
+    {
+      if (verbosity >= 2)
+        strerr_warnw3x("received and ignored netlink message ", "with invalid header", " length") ;
+      return ;
+    }
+    if (str_strn(buf, start, "@/", 2) >= start)
+    {
+      if (verbosity >= 2)
+        strerr_warnw2x("received and ignored netlink message ", "with invalid header") ;
+      return ;
+    }
+    on_event(argv, envp, buf + start, r - start) ;
+  }
+}
+
+static inline int make_ttos (char const *s)
+{
+  unsigned int tlife = 0, tterm = 0, tkill = 0, pos = 0 ;
+  pos += uint_scan(s + pos, &tlife) ;
+  if (s[pos] && s[pos++] != ':') return 0 ;
+  if (!tlife) return 1 ;
+  tain_from_millisecs(&lifetto, tlife) ;
+  pos += uint_scan(s + pos, &tterm) ;
+  if (s[pos] && s[pos++] != ':') return 0 ;
+  if (!tterm) return 1 ;
+  tain_from_millisecs(&termtto, tterm) ;
+  tain_add(&termtto, &termtto, &lifetto) ;
+  pos += uint_scan(s + pos, &tkill) ;
+  if (s[pos]) return 0 ;
+  if (!tkill) return 1 ;
+  tain_from_millisecs(&killtto, tkill) ;
+  tain_add(&killtto, &killtto, &termtto) ;
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  iopause_fd x[2] = { { -1, IOPAUSE_READ, 0 }, { -1, IOPAUSE_READ, 0 } } ;
+  PROG = "s6-devd" ;
+  {
+    unsigned int kbufsz = 65536 ;
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "qvb:t:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'q' : if (verbosity) verbosity-- ; break ;
+        case 'v' : verbosity++ ; break ;
+        case 'b' : if (!uint0_scan(l.arg, &kbufsz)) dieusage() ; break ;
+        case 't' : if (!make_ttos(l.arg)) dieusage() ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+    if (!argc) strerr_dieusage(100, USAGE) ;
+    {
+      int fd = open_readb("/dev/null") ;
+      if (fd < 0) strerr_diefu2sys(111, "open /dev/null for ", "reading") ;
+      if (fd_move(0, fd) < 0) strerr_diefu2sys(111, "redirect std", "in") ;
+      fd = open_write("/dev/null") ;
+      if (fd < 0) strerr_diefu2sys(111, "open /dev/null for ", "writing") ;
+      if (ndelay_off(fd) < 0) strerr_diefu1sys(111, "ndelay_off /dev/null") ;
+      if (fd_move(1, fd) < 0) strerr_diefu2sys(111, "redirect std", "out") ;
+    }
+    x[1].fd = netlink_init(kbufsz) ;
+    if (x[1].fd < 0) strerr_diefu1sys(111, "init netlink") ;
+  }
+
+  x[0].fd = selfpipe_init() ;
+  if (x[0].fd == -1) strerr_diefu1sys(111, "init selfpipe") ;
+  if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "ignore SIGPIPE") ;
+  {
+    sigset_t set ;
+    sigemptyset(&set) ;
+    sigaddset(&set, SIGTERM) ;
+    sigaddset(&set, SIGCHLD) ;
+    if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "trap signals") ;
+  }
+
+  tain_now_g() ;
+  tain_add_g(&deadline, &tain_infinite_relative) ;
+  if (verbosity >= 2) strerr_warni1x("starting") ;
+
+  while (cont)
+  {
+    register int r = iopause_g(x, 1 + !pid, &deadline) ;
+    if (r < 0) strerr_diefu1sys(111, "iopause") ;
+    else if (!r) handle_timeout() ;
+    else
+    {
+      if ((x[0].revents | x[1].revents) & IOPAUSE_EXCEPT)
+        strerr_diefu1x(111, "iopause: trouble with pipes") ;
+      if (x[0].revents & IOPAUSE_READ) handle_signals() ;
+      else if (!pid && (x[1].revents & IOPAUSE_READ))
+        handle_netlink(x[1].fd, argv, envp) ;
+    }
+  }
+  if (verbosity >= 2) strerr_warni1x("exiting") ;
+  return 0 ;
+}
diff --git a/src/minutils/s6-freeramdisk.c b/src/minutils/s6-freeramdisk.c
new file mode 100644
index 0000000..3b24656
--- /dev/null
+++ b/src/minutils/s6-freeramdisk.c
@@ -0,0 +1,21 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mount.h>
+#include <sys/ioctl.h>
+#include <skalibs/strerr2.h>
+
+#define USAGE "s6-freeramdisk ramdisk_device"
+
+int main (int argc, char const *const *argv)
+{
+  int fd ;
+  PROG = "s6-freeramdisk" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  fd = open(argv[1], O_RDWR) ;
+  if (fd < 0) strerr_diefu3sys(111, "open ", argv[1], " in read-write mode") ;
+  if (ioctl(fd, BLKFLSBUF) < 0) strerr_diefu2sys(111, "ioctl ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/minutils/s6-halt.c b/src/minutils/s6-halt.c
new file mode 100644
index 0000000..9613017
--- /dev/null
+++ b/src/minutils/s6-halt.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <sys/reboot.h>
+#include <skalibs/strerr2.h>
+
+int main ()
+{
+  PROG = "s6-halt" ;
+  sync() ;
+  reboot(RB_HALT_SYSTEM) ;
+  strerr_diefu1sys(111, "reboot()") ;
+}
diff --git a/src/minutils/s6-hiercopy.c b/src/minutils/s6-hiercopy.c
new file mode 100644
index 0000000..f8b7666
--- /dev/null
+++ b/src/minutils/s6-hiercopy.c
@@ -0,0 +1,156 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/direntry.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-hiercopy src dst"
+
+static void hiercopy (char const *, char const *) ;
+
+static int filecopy (char const *src, char const *dst, mode_t mode)
+{
+  int d ;
+  int s = open_readb(src) ;
+  if (s < 0) return 0 ;
+  d = open3(dst, O_WRONLY | O_CREAT | O_TRUNC, mode) ;
+  if (d < 0)
+  {
+    fd_close(s) ;
+    return 0 ;
+  }
+  if (fd_cat(s, d) < 0) goto err ;
+  fd_close(s) ;
+  fd_close(d) ;
+  return 1 ;
+
+err:
+  {
+    register int e = errno ;
+    fd_close(s) ;
+    fd_close(d) ;
+    errno = e ;
+  }
+  return 0 ;
+}
+
+static int dircopy (char const *src, char const *dst, mode_t mode)
+{
+  unsigned int tmpbase = satmp.len ;
+  unsigned int maxlen = 0 ;
+  {
+    DIR *dir = opendir(src) ;
+    if (!dir) return 0 ;
+    for (;;)
+    {
+      direntry *d ;
+      register unsigned int n ;
+      errno = 0 ;
+      d = readdir(dir) ;
+      if (!d) break ;
+      if (d->d_name[0] == '.')
+        if (((d->d_name[1] == '.') && !d->d_name[2]) || !d->d_name[1])
+          continue ;
+      n = str_len(d->d_name) ;
+      if (n > maxlen) maxlen = n ;
+      if (!stralloc_catb(&satmp, d->d_name, n+1)) break ;
+    }
+    if (errno)
+    {
+      int e = errno ;
+      dir_close(dir) ;
+      errno = e ;
+      goto err ;
+    }
+    dir_close(dir) ;
+  }
+
+  if (mkdir(dst, S_IRWXU) == -1)
+  {
+    struct stat st ;
+    if (errno != EEXIST) goto err ;
+    if (stat(dst, &st) < 0) goto err ;
+    if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR ; goto err ; }
+  }
+
+  {
+    unsigned int srclen = str_len(src) ;
+    unsigned int dstlen = str_len(dst) ;
+    unsigned int i = tmpbase ;
+    char srcbuf[srclen + maxlen + 2] ;
+    char dstbuf[dstlen + maxlen + 2] ;
+    byte_copy(srcbuf, srclen, src) ;
+    byte_copy(dstbuf, dstlen, dst) ;
+    srcbuf[srclen] = '/' ;
+    dstbuf[dstlen] = '/' ;
+    while (i < satmp.len)
+    {
+      register unsigned int n = str_len(satmp.s + i) + 1 ;
+      byte_copy(srcbuf + srclen + 1, n, satmp.s + i) ;
+      byte_copy(dstbuf + dstlen + 1, n, satmp.s + i) ;
+      i += n ;
+      hiercopy(srcbuf, dstbuf) ;
+    }
+  }
+  if (chmod(dst, mode) == -1) goto err ;
+  satmp.len = tmpbase ;
+  return 1 ;
+err:
+  satmp.len = tmpbase ;
+  return 0 ;
+}
+
+static void hiercopy (char const *src, char const *dst)
+{
+  struct stat st ;
+  if (lstat(src, &st) == -1) strerr_diefu2sys(111, "stat ", src) ;
+  if (S_ISREG(st.st_mode))
+  {
+    if (!filecopy(src, dst, st.st_mode))
+      strerr_diefu4sys(111, "copy regular file ", src, " to ", dst) ;
+  }
+  else if (S_ISDIR(st.st_mode))
+  {
+    if (!dircopy(src, dst, st.st_mode))
+      strerr_diefu4sys(111, "recursively copy directory ", src, " to ", dst) ;
+  }
+  else if (S_ISFIFO(st.st_mode))
+  {
+    if (mkfifo(dst, st.st_mode) < 0)
+      strerr_diefu2sys(111, "mkfifo ", dst) ;
+  }
+  else if (S_ISLNK(st.st_mode))
+  {
+    unsigned int tmpbase = satmp.len ;
+    if ((sareadlink(&satmp, src) < 0) || !stralloc_0(&satmp))
+      strerr_diefu2sys(111, "readlink ", src) ;
+    if (symlink(satmp.s + tmpbase, dst) < 0)
+      strerr_diefu4sys(111, "symlink ", satmp.s + tmpbase, " to ", dst) ;
+    satmp.len = tmpbase ;
+  }
+  else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode) || S_ISSOCK(st.st_mode))
+  {
+    if (mknod(dst, st.st_mode, st.st_rdev) < 0)
+      strerr_diefu2sys(111, "mknod ", dst) ;
+  }
+  else strerr_dief2x(111, "unrecognized file type for ", src) ;
+  lchown(dst, st.st_uid, st.st_gid) ;
+  if (!S_ISLNK(st.st_mode)) chmod(dst, st.st_mode) ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-hiercopy" ;
+  if (argc < 3) strerr_dieusage(100, USAGE) ;
+  umask(0) ;
+  hiercopy(argv[1], argv[2]) ;
+  return 0 ;
+}
diff --git a/src/minutils/s6-hostname.c b/src/minutils/s6-hostname.c
new file mode 100644
index 0000000..deb525b
--- /dev/null
+++ b/src/minutils/s6-hostname.c
@@ -0,0 +1,37 @@
+/* ISC license. */
+
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include <unistd.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-hostname [ hostname ]"
+
+static int getit (void)
+{
+  stralloc sa = STRALLOC_ZERO ;
+  if (sagethostname(&sa) < 0) strerr_diefu1sys(111, "get hostname") ;
+  sa.s[sa.len++] = '\n' ;
+  if (allwrite(1, sa.s, sa.len) < sa.len)
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
+
+static int setit (char const *h)
+{
+  if (sethostname(h, str_len(h)) < 0)
+    strerr_diefu1sys(111, "set hostname") ;
+  return 0 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-hostname" ;
+  return (argc < 2) ? getit() : setit(argv[1]) ;
+}
diff --git a/src/minutils/s6-logwatch.c b/src/minutils/s6-logwatch.c
new file mode 100644
index 0000000..a2c493d
--- /dev/null
+++ b/src/minutils/s6-logwatch.c
@@ -0,0 +1,156 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/ioctl.h>
+#include <sys/inotify.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/error.h>
+#include <skalibs/buffer.h>
+#include <skalibs/bufalloc.h>
+#include <skalibs/sig.h>
+#include <skalibs/siovec.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/iopause.h>
+#include <skalibs/ulong.h>
+
+#define USAGE "s6-logwatch [ -m maxbuffer ] logdir"
+#define dieusage() strerr_dieusage(100, USAGE)
+
+#define N 4096
+#define IESIZE 100
+
+typedef enum bstate_e bstate_t, *bstate_t_ref ;
+enum bstate_e
+{
+  B_TAILING = 0,
+  B_WAITING = 1
+} ;
+
+static void X (void)
+{
+  strerr_diefu1x(101, "follow file state changes (race condition triggered). Sorry.") ;
+}
+
+static unsigned long nbcat (int fdcurrent)
+{
+  char buf[N+1] ;
+  buffer b = BUFFER_INIT(&buffer_read, fdcurrent, buf, N+1) ;
+  siovec_t v[2] ;
+  unsigned long bytes = 0 ;
+  for (;;)
+  {
+    int r = sanitize_read(buffer_fill(&b)) ;
+    if (!r) break ;
+    if (r < 0)
+    {
+      if (errno == EPIPE) break ;
+      else strerr_diefu1sys(111, "buffer_fill") ;
+    }
+    buffer_rpeek(&b, v) ;
+    if (!bufalloc_putv(bufalloc_1, v, 2))
+      strerr_diefu1sys(111, "bufalloc_putv") ;
+    buffer_rseek(&b, r) ;
+    bytes += r ;
+  }
+  return bytes ;
+}
+
+
+int main (int argc, char const *const *argv)
+{
+  char const *dir = "." ;
+  unsigned long maxlen = 4000 ;
+  PROG = "s6-logwatch" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "m:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'm' : if (!ulong0_scan(l.arg, &maxlen)) dieusage() ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (argc) dir = *argv ;
+  if (chdir(dir) < 0) strerr_diefu2sys(111, "chdir to ", dir) ;
+  {
+    iopause_fd x[1] = { { -1, IOPAUSE_READ, 0 } } ;
+    int fdcurrent = -1 ;
+    unsigned long pos = 0 ;
+    int w ;
+    bstate_t state = B_TAILING ;
+    x[0].fd = inotify_init() ;
+    if (x[0].fd < 0) strerr_diefu1sys(111, "inotify_init") ;
+    if (ndelay_on(x[0].fd) < 0) strerr_diefu1sys(111, "ndelay_on inotify fd") ;
+    w = inotify_add_watch(x[0].fd, ".", IN_CREATE | IN_MODIFY | IN_CLOSE_WRITE) ;
+    if (w < 0) strerr_diefu1sys(111, "inotify_add_watch") ;
+    if (sig_ignore(SIGPIPE) == -1) strerr_diefu1sys(111, "sig_ignore(SIGPIPE)") ;
+    fdcurrent = open_readb("current") ;
+    if (fdcurrent < 0)
+      if (errno != ENOENT) strerr_diefu1sys(111, "open_readb current") ;
+      else state = B_WAITING ;
+    else pos = nbcat(fdcurrent) ;
+
+    for (;;)
+    {
+      int r ;
+      if (!bufalloc_flush(bufalloc_1)) strerr_diefu1sys(111, "write to stdout") ;
+      r = iopause(x, 1, 0, 0) ;
+      if (r < 0) strerr_diefu1sys(111, "iopause") ;
+      if (x[0].revents & IOPAUSE_READ)
+      {
+        char iebuf[IESIZE] ;
+        while (bufalloc_len(bufalloc_1) < maxlen)
+        {
+          unsigned int i = 0 ;
+          r = sanitize_read(fd_read(x[0].fd, iebuf, IESIZE)) ;
+          if (r < 0) strerr_diefu1sys(111, "read from inotify fd") ;
+          if (!r) break ;
+          while (i < (unsigned int)r)
+          {
+            struct inotify_event *ie = (struct inotify_event *)(iebuf + i) ;
+            if ((ie->wd != w) || !ie->len || str_diff(ie->name, "current")) goto cont ;
+            if (ie->mask & IN_MODIFY)
+            {
+              if (state) X() ;
+              pos += nbcat(fdcurrent) ;
+            }
+            else if (ie->mask & IN_CLOSE_WRITE)
+            {
+              if (state) X() ;
+              fd_close(fdcurrent) ;
+              fdcurrent = -1 ;
+              pos = 0 ;
+              state = B_WAITING ;
+            }
+            else if (ie->mask & IN_CREATE)
+            {
+              if (!state) X() ;
+              fdcurrent = open_readb("current") ;
+              if (fdcurrent < 0)
+              {
+                if (errno != ENOENT) strerr_diefu1sys(111, "open_readb current") ;
+                else goto cont ;
+              }
+              pos = nbcat(fdcurrent) ;
+              state = B_TAILING ;
+            }
+           cont:
+            i += sizeof(struct inotify_event) + ie->len ;
+          }
+        }
+      }
+    }
+  }
+  return 0 ;
+}
diff --git a/src/minutils/s6-mount.c b/src/minutils/s6-mount.c
new file mode 100644
index 0000000..6970c05
--- /dev/null
+++ b/src/minutils/s6-mount.c
@@ -0,0 +1,126 @@
+/* ISC license. */
+
+#include <sys/mount.h>
+#include <mntent.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 "mount-constants.h"
+
+#define USAGE "s6-mount -a [ -z fstab ] | s6-mount [ -t type ] [ -o option[,option...] ]... device mountpoint"
+#define BUFSIZE 4096
+
+#define SWITCH(opt) do
+#define HCTIWS(opt) while(0) ;
+#define CASE(s) if (!str_diffn(opt, (s), str_len(s)))
+
+static void scanopt (stralloc *data, unsigned long *flags, char const *opt)
+{
+  for (;;)
+  {
+    register unsigned int n = str_chr(opt, ',') ;
+
+    SWITCH(opt)
+    {
+      CASE("defaults") { *flags = MS_MGC_VAL ; break ; }
+      CASE("ro") { *flags |= MS_RDONLY ; break ; }
+      CASE("rw") { *flags &= ~MS_RDONLY ; break ; }
+      CASE("remount") { *flags |= MS_REMOUNT ; break ; }
+      CASE("sync") { *flags |= MS_SYNCHRONOUS ; break ; }
+      CASE("async") { *flags &= ~MS_SYNCHRONOUS ; break ; }
+      CASE("nodev") { *flags |= MS_NODEV ; break ; }
+      CASE("dev") { *flags &= ~MS_NODEV ; break ; }
+      CASE("noexec") { *flags |= MS_NOEXEC ; break ; }
+      CASE("exec") { *flags &= ~MS_NOEXEC ; break ; }
+      CASE("nosuid") { *flags |= MS_NOSUID ; break ; }
+      CASE("suid") { *flags &= ~MS_NOSUID ; break ; }
+      CASE("noatime") { *flags |= MS_NOATIME ; break ; }
+      CASE("atime") { *flags &= ~MS_NOATIME ; break ; }
+      CASE("nodiratime") { *flags |= MS_NODIRATIME ; break ; }
+      CASE("diratime") { *flags &= ~MS_NODIRATIME ; break ; }
+      CASE("bind") { *flags |= MS_BIND ; break ; }
+      CASE("nobind") { *flags &= ~MS_BIND ; break ; }
+      CASE("move") { *flags |= MS_MOVE ; break ; }
+      CASE("nomove") { *flags &= ~MS_MOVE ; break ; }
+
+        if ((data->s && data->len && !stralloc_catb(data, ",", 1)) || !stralloc_catb(data, opt, n))
+          strerr_diefu1sys(111, "build data string") ;
+    }
+    HCTIWS(opt)
+
+    opt += n ;
+    if (!*opt) break ;
+    if (*opt != ',') strerr_dief1x(100, "unrecognized option") ;
+    opt++ ;
+  }
+}
+
+static int mountall (char const *fstab)
+{
+  struct mntent *d ;
+  int e = 0 ;
+  FILE *yuck = setmntent(fstab, "r") ;
+  if (!yuck) strerr_diefu2sys(111, "open ", fstab) ;
+  while ((d = getmntent(yuck)))
+  {
+    unsigned long flags = MS_MGC_VAL ;
+    stralloc data = STRALLOC_ZERO ;
+    scanopt(&data, &flags, d->mnt_opts) ;
+    if (!stralloc_0(&data))
+      strerr_diefu1sys(111, "build data string") ;
+#ifdef DEBUG
+    strerr_warni4x("mounting ", d->mnt_fsname, " on ", d->mnt_dir) ;
+#endif
+    if (mount(d->mnt_fsname, d->mnt_dir, d->mnt_type, flags, data.s) == -1)
+    {
+      e++ ;
+      strerr_warnwu4sys("mount ", d->mnt_fsname, " on ", d->mnt_dir) ;
+    }
+    stralloc_free(&data) ;
+  }
+  endmntent(yuck) ;
+  return e ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  stralloc data = STRALLOC_ZERO ;
+  unsigned long flags = MS_MGC_VAL ;
+  char const *fstype = "none" ;
+  char const *fstab = "/etc/fstab" ;
+  PROG = "s6-mount" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "z:arwt:o:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'z' : fstab = l.arg ; break ;
+        case 'a' : return mountall(fstab) ;
+        case 't' : fstype = l.arg ; break ;
+        case 'w' : scanopt(&data, &flags, "rw") ; break ;
+        case 'r' : scanopt(&data, &flags, "ro") ; break ;
+        case 'o' : scanopt(&data, &flags, l.arg) ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc)
+  {
+    int fd = open_readb("/proc/mounts") ;
+    if (fd < 0) strerr_diefu2sys(111, "read ", "/proc/mounts") ;
+    if (fd_cat(fd, 1) < 0) strerr_diefu2sys(111, "fd_cat ", "/proc/mounts") ;
+    fd_close(fd) ;
+  }
+  else if (argc == 1) strerr_dieusage(100, USAGE) ;
+  else if (!stralloc_0(&data)) strerr_diefu1sys(111, "build data string") ;  
+  else if (mount(argv[0], argv[1], fstype, flags, data.s) == -1)
+    strerr_diefu4sys(111, "mount ", argv[0], " on ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/minutils/s6-pivotchroot.c b/src/minutils/s6-pivotchroot.c
new file mode 100644
index 0000000..ee4db73
--- /dev/null
+++ b/src/minutils/s6-pivotchroot.c
@@ -0,0 +1,24 @@
+/* ISC license. */
+
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include <unistd.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#define USAGE "s6-pivotchroot old-place-for-new-root new-place-for-old-root prog..."
+
+extern int pivot_root (char const *, char const *) ;
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  PROG = "s6-pivotchroot" ;
+  if (argc < 4) strerr_dieusage(100, USAGE) ;
+  if (chdir(argv[1]) < 0) strerr_diefu2sys(111, "chdir to ", argv[1]) ;
+  if (pivot_root(".", argv[2]) < 0) strerr_diefu1sys(111, "pivot_root") ;
+  if (chroot(".") < 0) strerr_diefu1sys(111, "chroot") ;
+  pathexec_run(argv[3], argv+3, envp) ;
+  strerr_dieexec(111, argv[3]) ;
+}
diff --git a/src/minutils/s6-poweroff.c b/src/minutils/s6-poweroff.c
new file mode 100644
index 0000000..b3576b3
--- /dev/null
+++ b/src/minutils/s6-poweroff.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <sys/reboot.h>
+#include <skalibs/strerr2.h>
+
+int main ()
+{
+  PROG = "s6-poweroff" ;
+  sync() ;
+  reboot(RB_POWER_OFF) ;
+  strerr_diefu1sys(111, "reboot()") ;
+}
diff --git a/src/minutils/s6-ps.c b/src/minutils/s6-ps.c
new file mode 100644
index 0000000..5a22068
--- /dev/null
+++ b/src/minutils/s6-ps.c
@@ -0,0 +1,385 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <dirent.h>
+
+#include <skalibs/uint.h>
+#include <skalibs/uint64.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/diuint.h>
+#include <skalibs/tai.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/direntry.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/unix-transactional.h>
+#include <skalibs/avltreen.h>
+#include "s6-ps.h"
+
+#define USAGE "s6-ps [ -H ] [ -w spacing ] [ -W wchanfile ] [ -l | -o field,field... ]"
+
+#define RIGHTFORMATTED ( \
+  (1 << PFIELD_PID) | \
+  (1 << PFIELD_PPID) | \
+  (1 << PFIELD_PGRP) | \
+  (1 << PFIELD_SESSION) | \
+  (1 << PFIELD_TPGID) | \
+  (1 << PFIELD_PRIO) | \
+  (1 << PFIELD_NICE) | \
+  (1 << PFIELD_THREADS) | \
+  (1 << PFIELD_VSIZE) | \
+  (1 << PFIELD_RSS) | \
+  (1 << PFIELD_RSSLIM) | \
+  (1 << PFIELD_CPUNO) | \
+  (1 << PFIELD_RTPRIO) | \
+  (1 << PFIELD_PMEM) | \
+  (1 << PFIELD_PCPU) | \
+  ((uint64)1 << PFIELD_CPCPU))
+
+void *left_dtok (unsigned int d, void *x)
+{
+  return (void *)&genalloc_s(diuint, (genalloc *)x)[d].left ;
+}
+
+int uint_cmp (void const *a, void const *b, void *x)
+{
+  register unsigned int aa = *(unsigned int *)a ;
+  register unsigned int bb = *(unsigned int *)b ;
+  (void)x ;
+  return (aa < bb) ? -1 : (aa > bb) ;
+}
+
+static void *pid_dtok (unsigned int d, void *x)
+{
+  return &((pscan_t *)x)[d].pid ;
+}
+
+static int fillo_notree (unsigned int i, unsigned int h, void *x)
+{
+  static unsigned int j = 0 ;
+  unsigned int *list = x ;
+  list[j++] = i ;
+  (void)h ;
+  return 1 ;
+}
+
+static inline unsigned int fieldscan (char const *s, pfield_t *list, uint64 *fbf)
+{
+  uint64 bits = 0 ;
+  unsigned int n = 0 ;
+  int cont = 1 ;
+  for (; cont ; n++)
+  {
+    unsigned int len = str_chr(s, ',') ;
+    register pfield_t i = 0 ;
+    if (!len) strerr_dief3x(100, "invalid", " (empty)", " field for -o option") ;
+    if (!s[len]) cont = 0 ;
+    {
+      char tmp[len+1] ;
+      byte_copy(tmp, len, s) ;
+      tmp[len] = 0 ;
+      for (; i < PFIELD_PHAIL ; i++) if (!str_diff(tmp, s6ps_opttable[i])) break ;
+      if (i >= PFIELD_PHAIL)
+        strerr_dief4x(100, "invalid", " field for -o option", ": ", tmp) ;
+      if (bits & (1 << i))
+        strerr_dief4x(100, "duplicate", " field for -o option", ": ", tmp) ;
+    }
+    s += len + 1 ;
+    list[n] = i ;
+    bits |= (1 << i) ;
+  }
+  *fbf = bits ;
+  return n ;
+}
+
+static int slurpit (unsigned int dirfd, stralloc *data, char const *buf, char const *what, unsigned int *len)
+{
+  unsigned int start = data->len ;
+  int fd = open_readat(dirfd, what) ;
+  if (fd < 0) return 0 ;
+  if (!slurp(data, fd)) strerr_diefu4sys(111, "slurp ", buf, "/", what) ;
+  fd_close(fd) ;
+  *len = data->len - start ;
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  genalloc pscans = GENALLOC_ZERO ; /* array of pscan_t */
+  pfield_t fieldlist[PFIELD_PHAIL] = { PFIELD_USER, PFIELD_PID, PFIELD_TTY, PFIELD_STATE, PFIELD_START, PFIELD_ARGS } ;
+  uint64 fbf = (1 << PFIELD_USER) | (1 << PFIELD_PID) | (1 << PFIELD_TTY) | (1 << PFIELD_STATE) | (1 << PFIELD_START) | (1 << PFIELD_ARGS) ;
+  unsigned int mypos = 0 ;
+  unsigned int nfields = 6 ;
+  pscan_t *p ;
+  unsigned int n ;
+  unsigned int spacing = 2 ;
+  int flagtree = 0 ;
+  char const *wchanfile = 0 ;
+  int needstat ;
+  PROG = "s6-ps" ;
+
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      register int opt = subgetopt_r(argc, argv, "Hlw:W:o:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'H' : flagtree = 1 ; break ;
+        case 'l' :
+        {
+          nfields = 11 ;
+          fbf = (1 << PFIELD_USER) | (1 << PFIELD_PID) | ((uint64)1 << PFIELD_CPCPU) | (1 << PFIELD_PMEM) | (1 << PFIELD_VSIZE) | (1 << PFIELD_RSS) | (1 << PFIELD_TTY) | (1 << PFIELD_STATE) | (1 << PFIELD_START) | (1 << PFIELD_CTTIME) | (1 << PFIELD_ARGS) ;
+          fieldlist[0] = PFIELD_USER ;
+          fieldlist[1] = PFIELD_PID ;
+          fieldlist[2] = PFIELD_CPCPU ;
+          fieldlist[3] = PFIELD_PMEM ;
+          fieldlist[4] = PFIELD_VSIZE ;
+          fieldlist[5] = PFIELD_RSS ;
+          fieldlist[6] = PFIELD_TTY ;
+          fieldlist[7] = PFIELD_STATE ;
+          fieldlist[8] = PFIELD_START ;
+          fieldlist[9] = PFIELD_CTTIME ;
+          fieldlist[10] = PFIELD_ARGS ;
+          break ;
+        }
+        case 'w' :
+        {
+          if (!uint0_scan(l.arg, &spacing)) strerr_dieusage(100, USAGE) ;
+          break ;
+        }
+        case 'W' : wchanfile = l.arg ; break ;
+        case 'o' :
+        {
+          nfields = fieldscan(l.arg, fieldlist, &fbf) ;
+          break ;
+        }
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (!spacing) spacing = 1 ;
+  if (spacing > 256) spacing = 256 ;
+
+  needstat = flagtree || !!(fbf & (
+   (1 << PFIELD_PID) |
+   (1 << PFIELD_COMM) |
+   (1 << PFIELD_STATE) |
+   (1 << PFIELD_PPID) |
+   (1 << PFIELD_PGRP) |
+   (1 << PFIELD_SESSION) |
+   (1 << PFIELD_TTY) |
+   (1 << PFIELD_TPGID) |
+   (1 << PFIELD_UTIME) |
+   (1 << PFIELD_STIME) |
+   (1 << PFIELD_CUTIME) |
+   (1 << PFIELD_CSTIME) |
+   (1 << PFIELD_PRIO) |
+   (1 << PFIELD_NICE) |
+   (1 << PFIELD_THREADS) |
+   (1 << PFIELD_START) |
+   (1 << PFIELD_VSIZE) |
+   (1 << PFIELD_RSS) |
+   (1 << PFIELD_RSSLIM) |
+   (1 << PFIELD_CPUNO) |
+   (1 << PFIELD_RTPRIO) |
+   (1 << PFIELD_RTPOLICY) |
+   (1 << PFIELD_PMEM) |
+   (1 << PFIELD_WCHAN) |
+   (1 << PFIELD_PCPU) |
+   (1 << PFIELD_TTIME) |
+   (1 << PFIELD_CTTIME) |
+   ((uint64)1 << PFIELD_TSTART) |
+   ((uint64)1 << PFIELD_CPCPU))) ;
+
+
+ /* Scan /proc */
+
+  {
+    int needstatdir = !!(fbf & ((1 << PFIELD_USER) | (1 << PFIELD_GROUP))) ;
+    unsigned int mypid = getpid() ;
+    DIR *dir = opendir("/proc") ;
+    direntry *d ;
+    char buf[25] = "/proc/" ;
+
+    if (!dir) strerr_diefu1sys(111, "open /proc") ;
+    for (;;)
+    {
+      pscan_t pscan = PSCAN_ZERO ;
+      int dirfd ;
+      errno = 0 ;
+      d = readdir(dir) ;
+      if (!d) break ;
+      if (!uint0_scan(d->d_name, &pscan.pid)) continue ;
+      strcpy(buf+6, d->d_name) ;
+      dirfd = open_read(buf) ;
+      if (dirfd < 0) continue ;
+
+      if (needstatdir)
+      {
+        struct stat st ;
+        if (fstat(dirfd, &st) < 0) goto errindir ;
+        pscan.uid = st.st_uid ;
+        pscan.gid = st.st_gid ;
+      }
+      if (needstat)
+      {
+        if (!slurpit(dirfd, &pscan.data, buf, "stat", &pscan.statlen))
+          goto errindir ;
+        if (!slurpit(dirfd, &pscan.data, buf, "comm", &pscan.commlen))
+          goto errindir ;
+        if (pscan.commlen) { pscan.commlen-- ; pscan.data.len-- ; }
+      }
+      if (fbf & (1 << PFIELD_ARGS))
+      {
+        if (!slurpit(dirfd, &pscan.data, buf, "cmdline", &pscan.cmdlen)) goto errindir ;
+        while (!pscan.data.s[pscan.data.len-1])
+        {
+          pscan.cmdlen-- ;
+          pscan.data.len-- ;
+        }
+      }
+      if (fbf & (1 << PFIELD_ENV)) slurpit(dirfd, &pscan.data, buf, "environ", &pscan.envlen) ;
+      fd_close(dirfd) ;
+      if (!genalloc_append(pscan_t, &pscans, &pscan))
+        strerr_diefu1sys(111, "genalloc_append") ;
+      if (pscan.pid == mypid) mypos = genalloc_len(pscan_t, &pscans) ;
+      continue ;
+     errindir:
+      fd_close(dirfd) ;
+      stralloc_free(&pscan.data) ;
+    }
+    if (errno) strerr_diefu1sys(111, "readdir /proc") ;
+    dir_close(dir) ;
+  }
+
+ /* Add a process 0 as a root and sentinel */
+  {
+    pscan_t pscan = { .pid = 0, .ppid = 0 } ;
+    if (!genalloc_append(pscan_t, &pscans, &pscan)) strerr_diefu1sys(111, "genalloc_append") ;
+  }
+
+  p = genalloc_s(pscan_t, &pscans) ;
+  n = genalloc_len(pscan_t, &pscans) - 1 ;
+
+  {
+    unsigned int orderedlist[n+1] ; /* 1st element will be 0, ignored */
+    register unsigned int i = 0 ;
+
+    /* Order the processes for display */
+
+    {
+      AVLTREEB_TYPE(n+1) pidtree ;
+      avltreeb_init(&pidtree, n+1, &pid_dtok, &uint_cmp, p) ;
+      for (i = 0 ; i < n ; i++)
+      {
+        if (needstat && !s6ps_statparse(p+i))
+          strerr_diefu1sys(111, "parse process stats") ;
+        if (!avltreeb_insert(&pidtree, i))
+          strerr_diefu1sys(111, "avltreeb_insert") ;
+      }
+      if (!avltreeb_insert(&pidtree, n))
+        strerr_diefu1sys(111, "avltreeb_insert") ;
+
+      if (flagtree) s6ps_otree(p, n+1, &pidtree.info, orderedlist) ;
+      else avltreeb_iter(&pidtree, &fillo_notree, orderedlist) ;
+    }
+
+
+   /* Format, compute length, output */
+
+    if (fbf & ((1 << PFIELD_START) | ((uint64)1 << PFIELD_TSTART) | (1 << PFIELD_PCPU) | ((uint64)1 << PFIELD_CPCPU)))
+    {
+      tain_now_g() ;
+      s6ps_compute_boottime(p, mypos) ;
+    }
+    if (fbf & (1 << PFIELD_USER) && !s6ps_pwcache_init())
+      strerr_diefu1sys(111, "init user name cache") ;
+    if (fbf & (1 << PFIELD_GROUP) && !s6ps_grcache_init())
+      strerr_diefu1sys(111, "init group name cache") ;
+    if (fbf & (1 << PFIELD_TTY) && !s6ps_ttycache_init())
+      strerr_diefu1sys(111, "init tty name cache") ;
+    if (fbf & (1 << PFIELD_WCHAN) && !s6ps_wchan_init(wchanfile))
+    {
+      if (wchanfile) strerr_warnwu2sys("init wchan file ", wchanfile) ;
+      else strerr_warnwu1sys("init wchan") ;
+    }
+
+    {
+      unsigned int fmtpos[n][nfields] ;
+      unsigned int fmtlen[n][nfields] ;
+      unsigned int maxlen[nfields] ;
+      unsigned int maxspaces = 0 ;
+      for (i = 0 ; i < nfields ; i++) maxlen[i] = str_len(s6ps_fieldheaders[fieldlist[i]]) ;
+      for (i = 0 ; i < n ; i++)
+      {
+        register unsigned int j = 0 ;
+        for (; j < nfields ; j++)
+        {
+          if (!(*s6ps_pfield_fmt[fieldlist[j]])(p+i, &fmtpos[i][j], &fmtlen[i][j]))
+            strerr_diefu1sys(111, "format fields") ;
+          if (fmtlen[i][j] > maxlen[j]) maxlen[j] = fmtlen[i][j] ;
+        }
+      }
+      for (i = 0 ; i < nfields ; i++)
+        if (maxlen[i] > maxspaces) maxspaces = maxlen[i] ;
+      maxspaces += spacing ;
+      if (fbf & (1 << PFIELD_USER)) s6ps_pwcache_finish() ;
+      if (fbf & (1 << PFIELD_GROUP)) s6ps_grcache_finish() ;
+      if (fbf & (1 << PFIELD_TTY)) s6ps_ttycache_finish() ;
+      if (fbf & (1 << PFIELD_WCHAN)) s6ps_wchan_finish() ;
+      stralloc_free(&satmp) ;
+      {
+        char spaces[maxspaces] ;
+        for (i = 0 ; i < maxspaces ; i++) spaces[i] = ' ' ;
+        for (i = 0 ; i < nfields ; i++)
+        {
+          register unsigned int rightformatted = !!(((uint64)1 << fieldlist[i]) & RIGHTFORMATTED) ;
+          register unsigned int len = str_len(s6ps_fieldheaders[fieldlist[i]]) ;
+          if (rightformatted && (buffer_put(buffer_1, spaces, maxlen[i] - len) < (int)(maxlen[i] - len)))
+            goto nowrite ;
+          if (buffer_put(buffer_1, s6ps_fieldheaders[fieldlist[i]], len) < (int)len)
+            goto nowrite ;
+          if ((i < nfields-1) && (buffer_put(buffer_1, spaces, !rightformatted * (maxlen[i] - len) + spacing) < (int)(!rightformatted * (maxlen[i] - len) + spacing)))
+            goto nowrite ;
+        }
+        if (buffer_put(buffer_1, "\n", 1) < 1) goto nowrite ;
+        for (i = 0 ; i < n ; i++)
+        {
+          register unsigned int oi = orderedlist[i+1] ;
+          register unsigned int j = 0 ;
+          for (; j < nfields ; j++)
+          {
+            register unsigned int rightformatted = !!(((uint64)1 << fieldlist[j]) & RIGHTFORMATTED) ;
+            if (rightformatted && (buffer_put(buffer_1, spaces, maxlen[j] - fmtlen[oi][j]) < (int)(maxlen[j] - fmtlen[oi][j])))
+              goto nowrite ;
+            if (buffer_put(buffer_1, p[oi].data.s + fmtpos[oi][j], fmtlen[oi][j]) < (int)fmtlen[oi][j])
+              goto nowrite ;
+            if ((j < nfields-1) && (buffer_put(buffer_1, spaces, !rightformatted * (maxlen[j] - fmtlen[oi][j]) + spacing) < (int)(!rightformatted * (maxlen[j] - fmtlen[oi][j]) + spacing)))
+              goto nowrite ;
+          }
+          if (buffer_put(buffer_1, "\n", 1) < 1) goto nowrite ;
+        }
+      }
+    }
+  }
+  buffer_flush(buffer_1) ;
+  return 0 ;
+
+ nowrite:
+  strerr_diefu1sys(111, "write to stdout") ;
+}
diff --git a/src/minutils/s6-ps.h b/src/minutils/s6-ps.h
new file mode 100644
index 0000000..3e7d84a
--- /dev/null
+++ b/src/minutils/s6-ps.h
@@ -0,0 +1,153 @@
+/* ISC license. */
+
+#ifndef _S6PS_H_
+#define _S6PS_H_
+
+#include <sys/types.h>
+#include <skalibs/uint32.h>
+#include <skalibs/uint64.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/tai.h>
+#include <skalibs/avltreen.h>
+
+ /* pfield: the output fields */
+
+typedef enum pfield_e pfield_t, *pfield_t_ref ;
+enum pfield_e
+{
+  PFIELD_PID,
+  PFIELD_COMM,
+  PFIELD_STATE,
+  PFIELD_PPID,
+  PFIELD_PGRP,
+  PFIELD_SESSION,
+  PFIELD_TTY,
+  PFIELD_TPGID, 
+  PFIELD_UTIME,
+  PFIELD_STIME,
+  PFIELD_CUTIME,
+  PFIELD_CSTIME,
+  PFIELD_PRIO,
+  PFIELD_NICE,
+  PFIELD_THREADS,
+  PFIELD_START,
+  PFIELD_VSIZE,
+  PFIELD_RSS,
+  PFIELD_RSSLIM,
+  PFIELD_CPUNO,
+  PFIELD_RTPRIO,
+  PFIELD_RTPOLICY,
+  PFIELD_USER,
+  PFIELD_GROUP,
+  PFIELD_PMEM,
+  PFIELD_WCHAN,
+  PFIELD_ARGS,
+  PFIELD_ENV,
+  PFIELD_PCPU,
+  PFIELD_TTIME,
+  PFIELD_CTTIME,
+  PFIELD_TSTART,
+  PFIELD_CPCPU,
+  PFIELD_PHAIL
+} ;
+
+extern char const *const *s6ps_opttable ;
+extern char const *const *s6ps_fieldheaders ;
+
+ /* pscan: the main structure */
+
+typedef struct pscan_s pscan_t, *pscan_t_ref ;
+struct pscan_s
+{
+  stralloc data ;
+  unsigned int pid ;
+  signed int height ;
+  unsigned int statlen ;
+  unsigned int commlen ;
+  unsigned int cmdlen ;
+  unsigned int envlen ;
+  uid_t uid ;
+  gid_t gid ;
+  uint32 ppid ;
+  unsigned int state ;
+  uint32 pgrp ;
+  uint32 session ;
+  uint32 ttynr ;
+  int tpgid ;
+  uint64 utime ;
+  uint64 stime ;
+  uint64 cutime ;
+  uint64 cstime ;
+  int prio ;
+  int nice ;
+  uint32 threads ;
+  uint64 start ;
+  uint64 vsize ;
+  uint64 rss ;
+  uint64 rsslim ;
+  uint64 wchan ;
+  uint32 cpuno ;
+  uint32 rtprio ;
+  uint32 policy ;
+} ;
+
+#define PSCAN_ZERO \
+{ \
+  .data = STRALLOC_ZERO, \
+  .pid = 0, \
+  .height = 0, \
+  .statlen = 0, \
+  .commlen = 0, \
+  .cmdlen = 0, \
+  .envlen = 0, \
+  .uid = 0, \
+  .gid = 0, \
+  .ppid = 0, \
+  .state = 0, \
+  .pgrp = 0, \
+  .session = 0, \
+  .ttynr = 0, \
+  .tpgid = -1, \
+  .utime = 0, \
+  .stime = 0, \
+  .cutime = 0, \
+  .cstime = 0, \
+  .prio = 0, \
+  .nice = 0, \
+  .threads = 0, \
+  .start = 0, \
+  .vsize = 0, \
+  .rss = 0, \
+  .rsslim = 0, \
+  .wchan = 0, \
+  .cpuno = 0, \
+  .rtprio = 0, \
+  .policy = 0 \
+}
+
+extern int s6ps_statparse (pscan_t *) ;
+extern void s6ps_otree (pscan_t *, unsigned int, avltreen *, unsigned int *) ;
+
+extern int s6ps_compute_boottime (pscan_t *, unsigned int) ;
+
+typedef int pfieldfmt_func_t (pscan_t *, unsigned int *, unsigned int *) ;
+typedef pfieldfmt_func_t *pfieldfmt_func_t_ref ;
+
+extern pfieldfmt_func_t_ref *s6ps_pfield_fmt ;
+
+extern void *left_dtok (unsigned int, void *) ;
+extern int uint_cmp (void const *, void const *, void *) ;
+extern int s6ps_pwcache_init (void) ;
+extern void s6ps_pwcache_finish (void) ;
+extern int s6ps_pwcache_lookup (stralloc *, unsigned int) ;
+extern int s6ps_grcache_init (void) ;
+extern void s6ps_grcache_finish (void) ;
+extern int s6ps_grcache_lookup (stralloc *, unsigned int) ;
+extern int s6ps_ttycache_init (void) ;
+extern void s6ps_ttycache_finish (void) ;
+extern int s6ps_ttycache_lookup (stralloc *, uint32) ;
+extern int s6ps_wchan_init (char const *) ;
+extern void s6ps_wchan_finish (void) ;
+extern int s6ps_wchan_lookup (stralloc *, uint64) ;
+
+#endif
diff --git a/src/minutils/s6-reboot.c b/src/minutils/s6-reboot.c
new file mode 100644
index 0000000..f006b35
--- /dev/null
+++ b/src/minutils/s6-reboot.c
@@ -0,0 +1,13 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <sys/reboot.h>
+#include <skalibs/strerr2.h>
+
+int main ()
+{
+  PROG = "s6-reboot" ;
+  sync() ;
+  reboot(RB_AUTOBOOT) ;
+  strerr_diefu1sys(111, "reboot()") ;
+}
diff --git a/src/minutils/s6-swapoff.c b/src/minutils/s6-swapoff.c
new file mode 100644
index 0000000..1067040
--- /dev/null
+++ b/src/minutils/s6-swapoff.c
@@ -0,0 +1,53 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+
+extern int swapoff (char const *) ;
+
+#define USAGE "s6-swapoff device <or> s6-swapoff -a"
+
+#define BUFSIZE 4096
+
+static int swapoffall ( )
+{
+  char buf[BUFSIZE+1] ;
+  buffer b ;
+  stralloc sa = STRALLOC_ZERO ;
+  int e = 0 ;
+  int r ;
+  int fd = open_readb("/proc/swaps") ;
+  if (fd < 0) strerr_diefu1sys(111, "open_readb /proc/swaps") ;
+  buffer_init(&b, &buffer_read, fd, buf, BUFSIZE+1) ;
+  if (skagetln(&b, &sa, '\n') < 0) strerr_diefu1sys(111, "skagetln") ;
+  for (;;)
+  {
+    unsigned int n ;
+    sa.len = 0 ;
+    r = skagetln(&b, &sa, '\n') ;
+    if (r < 0) strerr_diefu1sys(111, "skagetln") ;
+    if (!r) break ;
+    n = byte_chr(sa.s, sa.len, ' ') ;
+    if (n >= sa.len) strerr_dief1x(111, "invalid line in /proc/swaps") ;
+    sa.s[n] = 0 ;
+    if (swapoff(sa.s) < 0) { e++ ; strerr_warnwu2sys("swapoff ", sa.s) ; }
+  }
+  fd_close(fd) ;
+  stralloc_free(&sa) ;
+  return e ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-swapoff" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if ((argv[1][0] == '-') && (argv[1][1] == 'a') && !argv[1][2])
+    return swapoffall() ;
+  if (swapoff(argv[1]) == -1) strerr_diefu2sys(111, "swapoff ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/minutils/s6-swapon.c b/src/minutils/s6-swapon.c
new file mode 100644
index 0000000..6edd743
--- /dev/null
+++ b/src/minutils/s6-swapon.c
@@ -0,0 +1,37 @@
+/* ISC license. */
+
+#include <stdio.h>
+#include <mntent.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+
+extern int swapon (const char *, unsigned int) ;
+
+#define USAGE "s6-swapon device <or> s6-swapon -a"
+
+static int swaponall ()
+{
+  struct mntent *d ;
+  int e = 0 ;
+  FILE *yuck = setmntent("/etc/fstab", "r") ;
+  if (!yuck) strerr_diefu1sys(111, "setmntent /etc/fstab") ;
+  while ((d = getmntent(yuck)))
+    if (!str_diff(d->mnt_type, "swap") && (swapon(d->mnt_fsname, 0) == -1))
+    {
+      e++ ;
+      strerr_warnwu2sys("swapon ", d->mnt_fsname) ;
+    }
+  endmntent(yuck) ;
+  return e ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-swapon" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if ((argv[1][0] == '-') && (argv[1][1] == 'a') && !argv[1][2])
+    return swaponall() ;
+  if (swapon(argv[1], 0) == -1)
+    strerr_diefu2sys(111, "swapon ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/minutils/s6-umount.c b/src/minutils/s6-umount.c
new file mode 100644
index 0000000..966b455
--- /dev/null
+++ b/src/minutils/s6-umount.c
@@ -0,0 +1,65 @@
+/* ISC license. */
+
+#include <sys/mount.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+
+#define USAGE "s6-umount mountpoint <or> s6-umount -a"
+
+#define BUFSIZE 4096
+#define MAXLINES 512
+
+static int umountall ( )
+{
+  stralloc mountpoints[MAXLINES] ;
+  char buf[BUFSIZE+1] ;
+  buffer b ;
+  stralloc sa = STRALLOC_ZERO ;
+  unsigned int line = 0 ;
+  int e = 0 ;
+  int r ;
+  int fd = open_readb("/proc/mounts") ;
+  if (fd < 0) strerr_diefu1sys(111, "open /proc/mounts") ;
+  byte_zero(mountpoints, sizeof(mountpoints)) ;
+  buffer_init(&b, &buffer_read, fd, buf, BUFSIZE+1) ;
+  for (;;)
+  {
+    unsigned int n, p ;
+    if (line >= MAXLINES) strerr_dief1x(111, "/proc/mounts too big") ;
+    sa.len = 0 ;
+    r = skagetln(&b, &sa, '\n') ;
+    if (r <= 0) break ;
+    p = byte_chr(sa.s, sa.len, ' ') ;
+    if (p >= sa.len) strerr_dief1x(111, "bad /proc/mounts format") ;
+    p++ ;
+    n = byte_chr(sa.s + p, sa.len - p, ' ') ;
+    if (n == sa.len - p) strerr_dief1x(111, "bad /proc/mounts format") ;
+    if (!stralloc_catb(&mountpoints[line], sa.s + p, n) || !stralloc_0(&mountpoints[line]))
+      strerr_diefu1sys(111, "store mount point") ;
+    line++ ;
+  }
+  fd_close(fd) ;
+  stralloc_free(&sa) ;
+  if (r < 0) strerr_diefu1sys(111, "read /proc/mounts") ;
+  while (line--)
+    if (umount(mountpoints[line].s) == -1)
+    {
+      e++ ;
+      strerr_warnwu2sys("umount ", mountpoints[line].s) ;
+    }
+  return e ;
+}
+
+int main (int argc, char const *const *argv)
+{
+  PROG = "s6-umount" ;
+  if (argc < 2) strerr_dieusage(100, USAGE) ;
+  if ((argv[1][0] == '-') && (argv[1][1] == 'a') && !argv[1][2])
+    return umountall() ;
+  if (umount(argv[1]) == -1) strerr_diefu2sys(111, "umount ", argv[1]) ;
+  return 0 ;
+}
diff --git a/src/minutils/s6ps_grcache.c b/src/minutils/s6ps_grcache.c
new file mode 100644
index 0000000..1fe9380
--- /dev/null
+++ b/src/minutils/s6ps_grcache.c
@@ -0,0 +1,66 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <grp.h>
+#include <errno.h>
+#include <skalibs/uint.h>
+#include <skalibs/diuint.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+#include "s6-ps.h"
+
+static avltree grcache_tree = AVLTREE_ZERO ;
+static genalloc grcache_index = GENALLOC_ZERO ;
+
+int s6ps_grcache_init (void)
+{
+  avltree_init(&grcache_tree, 5, 3, 8, &left_dtok, &uint_cmp, &grcache_index) ;
+  return 1 ;
+}
+
+void s6ps_grcache_finish (void)
+{
+  avltree_free(&grcache_tree) ;
+  genalloc_free(diuint, &grcache_index) ;
+}
+
+int s6ps_grcache_lookup (stralloc *sa, unsigned int gid)
+{
+  int wasnull = !satmp.s ;
+  diuint d = { .left = gid, .right = satmp.len } ;
+  unsigned int i ;
+  if (!avltree_search(&grcache_tree, &d.left, &i))
+  {
+    struct group *gr ;
+    unsigned int n = genalloc_len(diuint, &grcache_index) ;
+    errno = 0 ;
+    gr = getgrgid(gid) ;
+    if (!gr)
+    {
+      if (errno) return 0 ;
+      if (!stralloc_readyplus(&satmp, UINT_FMT + 2)) return 0 ;
+      stralloc_catb(&satmp, "(", 1) ;
+      satmp.len += uint_fmt(satmp.s + satmp.len, gid) ;
+      stralloc_catb(&satmp, ")", 2) ;
+    }
+    else if (!stralloc_cats(&satmp, gr->gr_name) || !stralloc_0(&satmp)) return 0 ;
+    if (!genalloc_append(diuint, &grcache_index, &d)) goto err ;
+    if (!avltree_insert(&grcache_tree, n))
+    {
+      genalloc_setlen(diuint, &grcache_index, n) ;
+      goto err ;
+    }
+    i = n ;
+  }
+  return stralloc_cats(sa, satmp.s + genalloc_s(diuint, &grcache_index)[i].right) ;
+ err:
+  {
+    register int e = errno ;
+    if (wasnull) stralloc_free(&satmp) ;
+    else satmp.len = d.right ;
+    errno = e ;
+  }
+  return 0 ;
+}
diff --git a/src/minutils/s6ps_otree.c b/src/minutils/s6ps_otree.c
new file mode 100644
index 0000000..5e96409
--- /dev/null
+++ b/src/minutils/s6ps_otree.c
@@ -0,0 +1,98 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/avltreen.h>
+#include "s6-ps.h"
+
+typedef struct ptreeiter_s ptreeiter_t, *ptreeiter_t_ref ;
+struct ptreeiter_s
+{
+  unsigned int *childlist ;
+  unsigned int const *childindex ;
+  unsigned int const *ppindex ;
+  unsigned int *cpos ;
+} ;
+
+typedef struct pstuff_s pstuff_t, *pstuff_t_ref ;
+struct pstuff_s
+{
+  unsigned int *orderedlist ;
+  pscan_t *p ;
+  unsigned int const *childlist ;
+  unsigned int const *childindex ;
+  unsigned int const *nchild ;
+} ;
+
+static int fillchildlist (unsigned int i, unsigned int h, void *x)
+{
+  register ptreeiter_t *pt = x ;
+  register unsigned int j = pt->ppindex[i] ;
+  pt->childlist[pt->childindex[j] + pt->cpos[j]++] = i ;
+  (void)h ;
+  return 1 ;
+}
+
+static void fillo_tree_rec (pstuff_t *blah, unsigned int root, signed int h)
+{
+  static unsigned int j = 0 ;
+  register unsigned int i = !blah->p[root].pid ;
+  if (blah->p[root].pid == 1) h = -1 ;
+  blah->p[root].height = (h > 0) ? h : 0 ;
+  blah->orderedlist[j++] = root ;
+  for (; i < blah->nchild[root] ; i++)
+    fillo_tree_rec(blah, blah->childlist[blah->childindex[root] + i], h+1) ;
+}
+
+ /*
+    Fills up orderedlist with the right indices to print a process tree.
+    O(n log n) time, O(n) space, all in the stack.
+ */
+
+void s6ps_otree (pscan_t *p, unsigned int n, avltreen *pidtree, unsigned int *orderedlist)
+{
+  unsigned int childlist[n] ;
+  unsigned int childindex[n] ;
+  unsigned int nchild[n] ;
+  register unsigned int i = 0 ;
+  for (; i < n ; i++) nchild[i] = 0 ;
+
+ /* Compute the ppid tree */
+  for (i = 0 ; i < n ; i++)
+  {
+    unsigned int k ;
+    if (!avltreen_search(pidtree, &p[i].ppid, &k)) k = n-1 ;
+    orderedlist[i] = k ; /* using orderedlist as ppindex */
+    nchild[k]++ ;
+  }
+  {
+    unsigned int j = 0 ;
+    for (i = 0 ; i < n ; i++)
+    {
+      childindex[i] = j ;
+      j += nchild[i] ;
+    }
+  }
+
+ /* Fill the childlist by increasing pids so it is sorted */
+  {
+    unsigned int cpos[n] ;
+    ptreeiter_t blah = { .childlist = childlist, .childindex = childindex, .ppindex = orderedlist, .cpos = cpos } ;
+    for (i = 0 ; i < n ; i++) cpos[i] = 0 ;
+    avltreen_iter(pidtree, &fillchildlist, &blah) ;
+  }
+
+ /* If we have init, make it the last in the orphan list */
+  if (p[childlist[childindex[n-1]+1]].pid == 1)
+  {
+    unsigned int pos1 = childlist[childindex[n-1] + 1] ;
+    for (i = 2 ; i < nchild[n-1] ; i++)
+      childlist[childindex[n-1]+i-1] = childlist[childindex[n-1]+i] ;
+    childlist[childindex[n-1]+nchild[n-1]-1] = pos1 ;
+  } 
+
+ /* Finally, fill orderedlist by walking the childindex tree. */
+  {
+    pstuff_t blah = { .orderedlist = orderedlist, .p = p, .childlist = childlist, .childindex = childindex, .nchild = nchild } ;
+    fillo_tree_rec(&blah, n-1, -1) ;
+  }
+}
diff --git a/src/minutils/s6ps_pfield.c b/src/minutils/s6ps_pfield.c
new file mode 100644
index 0000000..3a960a4
--- /dev/null
+++ b/src/minutils/s6ps_pfield.c
@@ -0,0 +1,570 @@
+/* ISC license. */
+
+#include <unistd.h>
+#include <time.h>
+#include <sys/sysinfo.h>
+#include <skalibs/uint32.h>
+#include <skalibs/uint64.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/ulong.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/tai.h>
+#include <skalibs/djbtime.h>
+#include <skalibs/stralloc.h>
+#include "s6-ps.h"
+
+static char const *const fieldheaders[PFIELD_PHAIL] =
+{
+  "PID",
+  "COMM",
+  "STAT",
+  "PPID",
+  "PGRP",
+  "SESSION",
+  "TTY",
+  "TPGID",
+  "UTIME",
+  "STIME",
+  "CUTIME",
+  "CSTIME",
+  "PRIO",
+  "NICE",
+  "THREADS",
+  "START",
+  "VSZ",
+  "RSS",
+  "RSSLIM",
+  "CPU",
+  "RTPRIO",
+  "RTPOLICY",
+  "USER",
+  "GROUP",
+  "%MEM",
+  "WCHAN",
+  "COMMAND",
+  "ENVIRONMENT",
+  "%CPU",
+  "TTIME",
+  "CTTIME",
+  "TSTART",
+  "C%CPU"
+} ;
+
+char const *const *s6ps_fieldheaders = fieldheaders ;
+
+static char const *const opttable[PFIELD_PHAIL] =
+{
+  "pid",
+  "comm",
+  "s",
+  "ppid",
+  "pgrp",
+  "sess",
+  "tty",
+  "tpgid",
+  "utime",
+  "stime",
+  "cutime",
+  "cstime",
+  "prio",
+  "nice",
+  "thcount",
+  "start",
+  "vsize",
+  "rss",
+  "rsslimit",
+  "psr",
+  "rtprio",
+  "policy",
+  "user",
+  "group",
+  "pmem",
+  "wchan",
+  "args",
+  "env",
+  "pcpu",
+  "ttime",
+  "cttime",
+  "tstart",
+  "cpcpu"
+} ;
+
+char const *const *s6ps_opttable = opttable ;
+
+static tain_t boottime = TAIN_EPOCH ;
+
+static int fmt_32 (pscan_t *p, unsigned int *pos, unsigned int *len, uint32 u)
+{
+  if (!stralloc_readyplus(&p->data, UINT32_FMT)) return 0 ;
+  *pos = p->data.len ;
+  *len = uint32_fmt(p->data.s + *pos, u) ;
+  p->data.len += *len ;
+  return 1 ;
+}
+
+static int fmt_64 (pscan_t *p, unsigned int *pos, unsigned int *len, uint64 u)
+{                                                          
+  if (!stralloc_readyplus(&p->data, UINT64_FMT)) return 0 ;
+  *pos = p->data.len ;
+  *len = uint64_fmt(p->data.s + *pos, u) ;
+  p->data.len += *len ;
+  return 1 ;
+}                                                          
+
+static int fmt_i (pscan_t *p, unsigned int *pos, unsigned int *len, int d)
+{
+  if (!stralloc_readyplus(&p->data, UINT32_FMT+1)) return 0 ;
+  *pos = p->data.len ;
+  *len = int_fmt(p->data.s + *pos, d) ;
+  p->data.len += *len ;
+  return 1 ;
+}
+
+static int fmt_pid (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->pid) ;
+}
+
+static int fmt_comm (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  *pos = p->statlen ;
+  *len = p->commlen ;
+  return 1 ;
+}
+
+static int fmt_s (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  if (!stralloc_readyplus(&p->data, 4)) return 0 ;
+  *pos = p->data.len ;
+  p->data.s[p->data.len++] = p->data.s[p->state] ;
+  if (p->pid == p->session) p->data.s[p->data.len++] = 's' ;
+  if (p->threads > 1) p->data.s[p->data.len++] = 'l' ;
+  if ((p->tpgid > 0) && ((unsigned int)p->tpgid == p->pgrp))
+    p->data.s[p->data.len++] = '+' ;
+  if (p->nice) p->data.s[p->data.len++] = (p->nice < 0) ? '<' : 'N' ;
+  
+  *len = p->data.len - *pos ;
+  return 1 ;
+}
+
+static int fmt_ppid (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->ppid) ;
+}
+
+static int fmt_pgrp (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->pgrp) ;
+}
+
+static int fmt_session (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->session) ;
+}
+
+static int fmt_ttynr(pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  if (p->ttynr)
+  {
+    unsigned int tmppos = p->data.len ;
+    if (!s6ps_ttycache_lookup(&p->data, p->ttynr)) return 0 ;
+    *pos = tmppos ;
+    *len = p->data.len - tmppos ;
+  }
+  else
+  {
+    if (!stralloc_catb(&p->data, "-", 1)) return 0 ;
+    *pos = p->data.len - 1 ;
+    *len = 1 ;
+  }
+  return 1 ;
+}
+
+static int fmt_tpgid (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_i(p, pos, len, p->tpgid) ;
+}
+
+static unsigned int gethz (void)
+{
+  static unsigned int hz = 0 ;
+  if (!hz)
+  {            
+    long jiffies = sysconf(_SC_CLK_TCK) ;
+    if (jiffies < 1)
+    {             
+      char fmt[ULONG_FMT + 1] ;
+      fmt[long_fmt(fmt, jiffies)] = 0 ;
+      strerr_warnw3x("invalid _SC_CLK_TCK value (", fmt, "), using 100") ;
+      hz = 100 ;
+    }         
+    else hz = (unsigned int)jiffies ;
+  }
+  return hz ;
+}                
+
+int s6ps_compute_boottime (pscan_t *p, unsigned int mypos)
+{
+  if (!mypos--)
+  {
+    strerr_warnwu1x("compute boot time - using epoch") ;
+    return 0 ;
+  }
+  else
+  {
+    unsigned int hz = gethz() ;
+    tain_t offset = { .sec = { .x = p[mypos].start / hz }, .nano = (p[mypos].start % hz) * (1000000000 / hz) } ;
+    tain_sub(&boottime, &STAMP, &offset) ;
+    return 1 ;
+  }
+}
+
+static int fmt_jiffies (pscan_t *p, unsigned int *pos, unsigned int *len, uint64 j)
+{
+  unsigned int hz = gethz() ;
+  uint32 hrs, mins, secs, hfrac ;
+  if (!stralloc_readyplus(&p->data, UINT64_FMT + 13)) return 0 ;
+  hfrac = (j % hz) * 100 / hz ;
+  *pos = p->data.len ;
+  j /= hz ;
+  secs = j % 60 ; j /= 60 ;
+  mins = j % 60 ; j /= 60 ;
+  hrs = j % 24 ; j /= 24 ;
+  if (j)
+  {
+    p->data.len += uint64_fmt(p->data.s + p->data.len, j) ;
+    p->data.s[p->data.len++] = 'd' ;
+  }
+  if (j || hrs)
+  {
+    uint320_fmt(p->data.s + p->data.len, hrs, 2) ;
+    p->data.len += 2 ;
+    p->data.s[p->data.len++] = 'h' ;
+  }
+  if (j || hrs || mins)
+  {
+    uint320_fmt(p->data.s + p->data.len, mins, 2) ;
+    p->data.len += 2 ;
+    p->data.s[p->data.len++] = 'm' ;
+  }
+  uint320_fmt(p->data.s + p->data.len, secs, 2) ;
+  p->data.len += 2 ;
+  if (!j && !hrs && !mins)
+  {
+    p->data.s[p->data.len++] = '.' ;
+    uint320_fmt(p->data.s + p->data.len, hfrac, 2) ;
+    p->data.len += 2 ;
+  }
+  p->data.s[p->data.len++] = 's' ;
+  *len = p->data.len - *pos ;
+  return 1 ;
+}
+
+static int fmt_utime (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_jiffies(p, pos, len, p->utime) ;
+}
+
+static int fmt_stime (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_jiffies(p, pos, len, p->stime) ;
+}
+
+static int fmt_cutime (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_jiffies(p, pos, len, p->utime + p->cutime) ;
+}
+
+static int fmt_cstime (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_jiffies(p, pos, len, p->stime + p->cstime) ;
+}
+
+static int fmt_prio (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_i(p, pos, len, p->prio) ;
+}
+
+static int fmt_nice (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_i(p, pos, len, p->nice) ;
+}
+
+static int fmt_threads (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->threads) ;
+}
+
+static int fmt_timedate (pscan_t *p, unsigned int *pos, unsigned int *len, struct tm const *tm)
+{
+  static struct tm nowtm = { .tm_year = 0 } ;
+  unsigned int tmplen ;
+  char *tmpstrf = "%F" ;
+  if (!nowtm.tm_year && !localtm_from_tai(&nowtm, tain_secp(&STAMP), 1)) return 0 ;
+  if (!stralloc_readyplus(&p->data, 20)) return 0 ;
+  if (tm->tm_year == nowtm.tm_year && tm->tm_yday == nowtm.tm_yday)
+    tmpstrf = "%T" ;
+  else if (tm->tm_year == nowtm.tm_year || (tm->tm_year+1 == nowtm.tm_year && (nowtm.tm_mon + 12 - tm->tm_mon) % 12 < 9))
+    tmpstrf = "%b%d %R" ;
+  tmplen = strftime(p->data.s + p->data.len, 20, tmpstrf, tm) ;
+  if (!tmplen) return 0 ;
+  *len = tmplen ;
+  *pos = p->data.len ;
+  p->data.len += tmplen ;
+  return 1 ;
+}
+
+static int fmt_start (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  struct tm starttm ;
+  unsigned int hz = gethz() ;
+  tain_t blah = { .sec = { .x = p->start / hz }, .nano = (p->start % hz) * (1000000000 / hz) } ;
+  tain_add(&blah, &boottime, &blah) ;
+  if (!localtm_from_tai(&starttm, tain_secp(&blah), 1)) return 0 ;
+  return fmt_timedate(p, pos, len, &starttm) ;
+}
+
+static unsigned int getpgsz (void)
+{
+  static unsigned int pgsz = 0 ;
+  if (!pgsz)
+  {
+    long sz = sysconf(_SC_PAGESIZE) ;
+    if (sz < 1)
+    {
+      char fmt[ULONG_FMT + 1] ;
+      fmt[long_fmt(fmt, sz)] = 0 ;
+      strerr_warnw3x("invalid _SC_PAGESIZE value (", fmt, "), using 4096") ;
+      pgsz = 4096 ;
+    }
+    else pgsz = sz ;
+  }
+  return pgsz ;
+}
+
+static int fmt_vsize (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_64(p, pos, len, p->vsize / 1024) ;
+}
+
+static int fmt_rss (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_64(p, pos, len, p->rss * (getpgsz() / 1024)) ;
+}
+
+static int fmt_rsslim (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_64(p, pos, len, p->rsslim / 1024) ;
+}
+
+static int fmt_cpuno (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->cpuno) ;
+}
+
+static int fmt_rtprio (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_32(p, pos, len, p->rtprio) ;
+}
+
+static int fmt_policy (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  static char const *const policies[8] = { "NORMAL", "FIFO", "RR", "BATCH", "ISO", "IDLE", "UNKNOWN", "UNKNOWN" } ;
+  unsigned int tmppos = p->data.len ;
+  if (!stralloc_cats(&p->data, policies[p->policy & 7])) return 0 ;
+  *pos = tmppos ;
+  *len = p->data.len - tmppos ;
+  return 1 ;
+}
+
+static int fmt_user (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  unsigned int tmppos = p->data.len ;
+  if (!s6ps_pwcache_lookup(&p->data, p->uid)) return 0 ;
+  *pos = tmppos ;
+  *len = p->data.len - tmppos ;
+  return 1 ;
+}
+
+static int fmt_group (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  unsigned int tmppos = p->data.len ;
+  if (!s6ps_grcache_lookup(&p->data, p->gid)) return 0 ;
+  *pos = tmppos ;
+  *len = p->data.len - tmppos ;
+  return 1 ;
+}
+
+static struct sysinfo si = { .totalram = 0, .loads = { 0, 0, 0 } } ;
+
+static uint64 gettotalmem (void)
+{
+  uint64 totalmem = 0 ;
+  if (!si.totalram && (sysinfo(&si) < 0)) return 0 ;
+  totalmem = si.totalram ;
+  totalmem *= si.mem_unit ;
+  return totalmem ;
+}
+
+static int percent (stralloc *sa, unsigned int n, unsigned int *pos, unsigned int *len)
+{
+  if (!stralloc_readyplus(sa, UINT64_FMT+1)) return 0 ;
+  *pos = sa->len ;
+  sa->len += uint64_fmt(sa->s + sa->len, n / 100) ;
+  sa->s[sa->len++] = '.' ;
+  uint320_fmt(sa->s + sa->len, (uint32)(n % 100), 2) ;
+  sa->len += 2 ;
+  *len = sa->len - *pos ;
+  return 1 ;
+}
+
+static int fmt_pmem (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  uint64 l = gettotalmem() ;
+  return l ? percent(&p->data, p->rss * getpgsz() * 10000 / l, pos, len) : 0 ;
+}
+
+static int fmt_wchan (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  unsigned int tmppos = p->data.len ;
+  if (!s6ps_wchan_lookup(&p->data, p->wchan)) return 0 ;
+  *len = p->data.len - tmppos ;
+  *pos = tmppos ;
+  return 1 ;
+}
+
+static int fmt_args (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  if (!stralloc_readyplus(&p->data, (p->height << 2) + (p->cmdlen ? p->cmdlen : p->commlen + (p->data.s[p->state] == 'Z' ? 11 : 3))))
+    return 0 ;
+  *pos = p->data.len ;
+  if (p->height)
+  {
+    register unsigned int i = 0 ;
+    for (; i < 4 * (unsigned int)p->height - 3 ; i++)
+      p->data.s[p->data.len + i] = ' ' ;
+    byte_copy(p->data.s + p->data.len + 4 * p->height - 3, 3, "\\_ ") ;
+    p->data.len += p->height << 2 ;
+  }
+  if (p->cmdlen)
+  {
+    register char const *r = p->data.s + p->statlen + p->commlen ;
+    register char *w = p->data.s + p->data.len ;
+    register unsigned int i = p->cmdlen ;
+    while (i--)
+    {
+      register char c = *r++ ;
+      *w++ = c ? c : ' ' ;
+    }
+    p->data.len += p->cmdlen ;
+  }
+  else if (p->data.s[p->state] == 'Z')
+  {
+    stralloc_catb(&p->data, p->data.s + uint32_fmt(0, p->pid) + 2, p->commlen) ;
+    stralloc_catb(&p->data, " <defunct>", 10) ;
+  }
+  else
+    stralloc_catb(&p->data, p->data.s + uint32_fmt(0, p->pid) + 1, p->commlen+2) ;
+  *len = p->data.len - *pos ;
+  return 1 ;
+}
+
+static int fmt_env (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  register unsigned int i = 0 ;
+  if (!p->envlen)
+  {
+    if (!stralloc_catb(&p->data, "*", 1)) return 0 ;
+    *pos = p->data.len - 1 ;
+    *len = 1 ;
+    return 1 ;
+  }
+  *pos = p->statlen + p->commlen + p->cmdlen ;
+  *len = p->envlen ;
+  for (; i < *len ; i++)
+    if (!p->data.s[*pos + i]) p->data.s[*pos + i] = ' ' ;
+  return 1 ;
+}
+
+static uint64 gettotalj (uint64 j)
+{
+  tain_t totaltime ;
+  register unsigned int hz = gethz() ;
+  tain_sub(&totaltime, &STAMP, &boottime) ;
+  j = totaltime.sec.x * hz + totaltime.nano / (1000000000 / hz) - j ;
+  if (!j) j = 1 ;
+  return j ;
+}
+
+static int fmt_pcpu (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return percent(&p->data, 10000 * (p->utime + p->stime) / gettotalj(p->start), pos, len) ;
+}
+
+
+static int fmt_ttime (pscan_t *p, unsigned int *pos, unsigned int *len) 
+{
+  return fmt_jiffies(p, pos, len, p->utime + p->stime) ;
+}
+
+static int fmt_cttime (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return fmt_jiffies(p, pos, len, p->utime + p->stime + p->cutime + p->cstime) ;
+}
+
+static int fmt_tstart (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  unsigned int hz = gethz() ;
+  tain_t blah = { .sec = { .x = p->start / hz }, .nano = (p->start % hz) * (1000000000 / hz) } ;
+  if (!stralloc_readyplus(&p->data, TIMESTAMP)) return 0 ;
+  tain_add(&blah, &boottime, &blah) ;
+  *pos = p->data.len ;
+  *len = timestamp_fmt(p->data.s + p->data.len, &blah) ;
+  p->data.len += *len ;
+  return 1 ;
+}
+
+static int fmt_cpcpu (pscan_t *p, unsigned int *pos, unsigned int *len)
+{
+  return percent(&p->data, 10000 * (p->utime + p->stime + p->cutime + p->cstime) / gettotalj(p->start), pos, len) ;
+}
+
+static pfieldfmt_func_t_ref pfieldfmt_table[PFIELD_PHAIL] =
+{
+  &fmt_pid,
+  &fmt_comm,
+  &fmt_s,
+  &fmt_ppid,
+  &fmt_pgrp,
+  &fmt_session,
+  &fmt_ttynr,
+  &fmt_tpgid,
+  &fmt_utime,
+  &fmt_stime,
+  &fmt_cutime,
+  &fmt_cstime,
+  &fmt_prio,
+  &fmt_nice,
+  &fmt_threads,
+  &fmt_start,
+  &fmt_vsize,
+  &fmt_rss,
+  &fmt_rsslim,
+  &fmt_cpuno,
+  &fmt_rtprio,
+  &fmt_policy,
+  &fmt_user,
+  &fmt_group,
+  &fmt_pmem,
+  &fmt_wchan,
+  &fmt_args,
+  &fmt_env,
+  &fmt_pcpu,
+  &fmt_ttime,
+  &fmt_cttime,
+  &fmt_tstart,
+  &fmt_cpcpu
+} ;
+
+pfieldfmt_func_t_ref *s6ps_pfield_fmt = pfieldfmt_table ;
diff --git a/src/minutils/s6ps_pwcache.c b/src/minutils/s6ps_pwcache.c
new file mode 100644
index 0000000..4c78460
--- /dev/null
+++ b/src/minutils/s6ps_pwcache.c
@@ -0,0 +1,66 @@
+/* ISC license. */
+
+#include <sys/types.h>
+#include <pwd.h>
+#include <errno.h>
+#include <skalibs/uint.h>
+#include <skalibs/diuint.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+#include "s6-ps.h"
+
+static avltree pwcache_tree = AVLTREE_ZERO ;
+static genalloc pwcache_index = GENALLOC_ZERO ;
+
+int s6ps_pwcache_init (void)
+{
+  avltree_init(&pwcache_tree, 5, 3, 8, &left_dtok, &uint_cmp, &pwcache_index) ;
+  return 1 ;
+}
+
+void s6ps_pwcache_finish (void)
+{
+  avltree_free(&pwcache_tree) ;
+  genalloc_free(diuint, &pwcache_index) ;
+}
+
+int s6ps_pwcache_lookup (stralloc *sa, unsigned int uid)
+{
+  int wasnull = !satmp.s ;
+  diuint d = { .left = uid, .right = satmp.len } ;
+  unsigned int i ;
+  if (!avltree_search(&pwcache_tree, &d.left, &i))
+  {
+    struct passwd *pw ;
+    unsigned int n = genalloc_len(diuint, &pwcache_index) ;
+    errno = 0 ;
+    pw = getpwuid(uid) ;
+    if (!pw)
+    {
+      if (errno) return 0 ;
+      if (!stralloc_readyplus(&satmp, UINT_FMT + 2)) return 0 ;
+      stralloc_catb(&satmp, "(", 1) ;
+      satmp.len += uint_fmt(satmp.s + satmp.len, uid) ;
+      stralloc_catb(&satmp, ")", 2) ;
+    }
+    else if (!stralloc_cats(&satmp, pw->pw_name) || !stralloc_0(&satmp)) return 0 ;
+    if (!genalloc_append(diuint, &pwcache_index, &d)) goto err ;
+    if (!avltree_insert(&pwcache_tree, n))
+    {
+      genalloc_setlen(diuint, &pwcache_index, n) ;
+      goto err ;
+    }
+    i = n ;
+  }
+  return stralloc_cats(sa, satmp.s + genalloc_s(diuint, &pwcache_index)[i].right) ;
+ err:
+  {
+    register int e = errno ;
+    if (wasnull) stralloc_free(&satmp) ;
+    else satmp.len = d.right ;
+    errno = e ;
+  }
+  return 0 ;
+}
diff --git a/src/minutils/s6ps_statparse.c b/src/minutils/s6ps_statparse.c
new file mode 100644
index 0000000..b5976a1
--- /dev/null
+++ b/src/minutils/s6ps_statparse.c
@@ -0,0 +1,155 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <skalibs/uint32.h>
+#include <skalibs/uint64.h>
+#include <skalibs/fmtscan.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/tai.h>
+#include "s6-ps.h"
+
+ /*
+    going to great lengths to avoid scanf(), but all this code
+    is still smaller than scanf (no floating point parsing etc.)
+ */
+
+#define STATVARS 41
+
+typedef unsigned int scanfunc_t (char const *, void *) ;
+typedef scanfunc_t *scanfunc_t_ref ;
+
+static unsigned int f32 (char const *s, void *u32)
+{
+  uint32 *u = u32 ;
+  return uint32_scan(s, u) ;
+}
+
+static unsigned int f64 (char const *s, void *u64)
+{
+  uint64 *u = u64 ;
+  return uint64_scan(s, u) ;
+}
+
+static unsigned int fint (char const *s, void *i)
+{
+  int *d = i ;
+  return int_scan(s, d) ;
+}
+
+static scanfunc_t_ref scanfuncs[STATVARS] =
+{
+  &f32, /* ppid */
+  &f32, /* pgrp */
+  &f32, /* session */
+  &f32, /* tty_nr */
+  &fint, /* tpgid */
+  &f32, /* flags */
+  &f32, /* minflt */
+  &f32, /* cminflt */
+  &f32, /* majflt */
+  &f32, /* cmajflt */
+  &f64, /* utime */
+  &f64, /* stime */
+  &f64, /* cutime */
+  &f64, /* cstime */
+  &fint, /* priority */
+  &fint, /* nice */
+  &f32, /* num_threads */
+  &f32, /* itrealvalue */
+  &f64, /* starttime */
+  &f64, /* vsize */
+  &f64, /* rss */
+  &f64, /* rsslim */
+  &f64, /* startcode */
+  &f64, /* endcode */
+  &f64, /* startstack */
+  &f64, /* kstkesp */
+  &f64, /* kstkeip */
+  &f32, /* signal */
+  &f32, /* blocked */
+  &f32, /* sigignore */
+  &f32, /* sigcatch */
+  &f64, /* wchan */
+  &f32, /* nswap */
+  &f32, /* cnswap */
+  &f32, /* exit_signal */
+  &f32, /* processor */
+  &f32, /* rt_priority */
+  &f32, /* policy */
+  &f64, /* delayacct_blkio_ticks */
+  &f32, /* guest_time */
+  &f32 /* cguest_time */
+} ;
+
+int s6ps_statparse (pscan_t *p)
+{
+  uint64 dummy64 ;
+  uint32 dummy32 ;
+  unsigned int pos = 0 ;
+  void *scanresults[STATVARS] =
+  {
+    &p->ppid,
+    &p->pgrp,
+    &p->session,
+    &p->ttynr,
+    &p->tpgid,
+    &dummy32,
+    &dummy32,
+    &dummy32,
+    &dummy32,
+    &dummy32,
+    &p->utime,
+    &p->stime,
+    &p->cutime,
+    &p->cstime,
+    &p->prio,
+    &p->nice,
+    &p->threads,
+    &dummy32,
+    &p->start,
+    &p->vsize,
+    &p->rss,
+    &p->rsslim,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy64,
+    &dummy32,
+    &dummy32,
+    &dummy32,
+    &dummy32,
+    &p->wchan,
+    &dummy32,
+    &dummy32,
+    &dummy32,
+    &p->cpuno,
+    &p->rtprio,
+    &p->policy,
+    &dummy64,
+    &dummy32,
+    &dummy32
+  } ;
+  register unsigned int i = 0 ;
+
+  if (!p->statlen) return 0 ;
+  pos = uint32_scan(p->data.s, &dummy32) ;
+  if (!pos) return 0 ;
+  if (dummy32 != p->pid) return 0 ;
+  if (pos + 5 + p->commlen > p->statlen) return 0 ;
+  if (p->data.s[pos++] != ' ') return 0 ;
+  if (p->data.s[pos++] != '(') return 0 ;
+  pos += p->commlen ;
+  if (p->data.s[pos++] != ')') return 0 ;
+  if (p->data.s[pos++] != ' ') return 0 ;
+  p->state = pos++ ;
+  for (; i < STATVARS ; i++)
+  {
+    unsigned int w ;
+    if (pos + 1 > p->statlen) return 0 ;
+    if (p->data.s[pos++] != ' ') return 0 ;
+    w = (*scanfuncs[i])(p->data.s + pos, scanresults[i]) ;
+    if (!w) return 0 ; pos += w ;
+  }
+  return 1 ;
+}
diff --git a/src/minutils/s6ps_ttycache.c b/src/minutils/s6ps_ttycache.c
new file mode 100644
index 0000000..c50c3ea
--- /dev/null
+++ b/src/minutils/s6ps_ttycache.c
@@ -0,0 +1,153 @@
+/* ISC license. */
+
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <skalibs/uint32.h>
+#include <skalibs/diuint32.h>
+#include <skalibs/buffer.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+#include "s6-ps.h"
+
+static avltree ttycache_tree = AVLTREE_ZERO ;
+static genalloc ttycache_index = GENALLOC_ZERO ;
+
+static void *left32_dtok (unsigned int d, void *x)
+{
+  return (void *)&genalloc_s(diuint32, (genalloc *)x)[d].left ;
+}
+
+static int uint32_cmp (void const *a, void const *b, void *x)
+{
+  register uint32 aa = *(uint32 *)a ;
+  register uint32 bb = *(uint32 *)b ;
+  (void)x ;
+  return (aa < bb) ? -1 : (aa > bb) ;
+}
+
+int s6ps_ttycache_init (void)
+{
+  avltree_init(&ttycache_tree, 5, 3, 8, &left32_dtok, &uint32_cmp, &ttycache_index) ;
+  return 1 ;
+}
+
+void s6ps_ttycache_finish (void)
+{
+  avltree_free(&ttycache_tree) ;
+  genalloc_free(diuint, &ttycache_index) ;
+}
+
+static int check (char const *s, uint32 ttynr)
+{
+  struct stat st ;
+  if (stat(s, &st) < 0) return 0 ;
+  return S_ISCHR(st.st_mode) && (st.st_rdev == ttynr) ;
+}
+
+
+ /* No blind scanning of all /dev or /sys/devices, kthx */
+
+static int ttyguess (stralloc *sa, uint32 ttynr)
+{
+  unsigned int maj = major(ttynr), min = minor(ttynr) ;
+
+ /* Try /dev/tty? and /dev/pts/? */
+  if (maj == 4 && min < 64)
+  {
+    char tmp[11] = "/dev/tty" ;
+    tmp[uint_fmt(tmp+8, min)] = 0 ;
+    if (check(tmp, ttynr)) return stralloc_cats(sa, tmp+5) && stralloc_0(sa) ;
+  }
+  else if (maj >= 136 && maj < 144)
+  {
+    char tmp[9 + UINT_FMT] = "/dev/pts/" ;
+    register unsigned int n = ((maj - 136) << 20) | min ;
+    tmp[9 + uint_fmt(tmp+9, n)] = 0 ;
+    if (check(tmp, ttynr)) return stralloc_cats(sa, tmp+5) && stralloc_0(sa) ;
+  }
+
+ /* Use /sys/dev/char/maj:min if it exists */
+  {
+    int fd ;
+    char path[23 + 2 * UINT_FMT] = "/sys/dev/char/" ;
+    register unsigned int pos = 14 ;
+    pos += uint_fmt(path + pos, maj) ;
+    path[pos++] = ':' ;
+    pos += uint_fmt(path + pos, min) ;
+    byte_copy(path + pos, 8, "/uevent") ;
+    fd = open_read(path) ;
+    if (fd >= 0)
+    {
+      char buf[4097] ;
+      buffer b = BUFFER_INIT(&buffer_read, fd, buf, 4097) ;
+      unsigned int start = satmp.len ;
+      register int r ;
+      for (;;)
+      {
+        satmp.len = start ;
+        r = skagetln(&b, &satmp, '\n') ;
+        if (r <= 0) break ;
+        if ((satmp.len - start) > 8 && !byte_diff(satmp.s + start, 8, "DEVNAME=")) break ;
+      }
+      fd_close(fd) ;
+      if (r > 0)
+      {
+        satmp.s[satmp.len - 1] = 0 ;
+        satmp.len = start ;
+        byte_copy(satmp.s + start + 3, 5, "/dev/") ;
+        if (check(satmp.s + start + 3, ttynr))
+          return stralloc_cats(sa, satmp.s + start + 8) && stralloc_0(sa) ;
+      }
+    }
+  }
+
+ /* Fallback: print explicit maj:min */
+  {
+    char tmp[3 + 2 * UINT_FMT] = "(" ;
+    register unsigned int pos = 1 ;
+    pos += uint_fmt(tmp + pos, maj) ;
+    tmp[pos++] = ':' ;
+    pos += uint_fmt(tmp + pos, min) ;
+    tmp[pos++] = ')' ;
+    tmp[pos++] = 0 ;
+    return stralloc_catb(sa, tmp, pos) ;
+  }
+}
+
+int s6ps_ttycache_lookup (stralloc *sa, uint32 ttynr)
+{
+  int wasnull = !satmp.s ;
+  diuint32 d = { .left = ttynr, .right = satmp.len } ;
+  unsigned int i ;
+  if (!avltree_search(&ttycache_tree, &d.left, &i))
+  {
+    unsigned int n = genalloc_len(diuint32, &ttycache_index) ;
+    if (!ttyguess(&satmp, ttynr)) return 0 ;
+    if (!genalloc_append(diuint32, &ttycache_index, &d)) goto err ;
+    if (!avltree_insert(&ttycache_tree, n))
+    {
+      genalloc_setlen(diuint32, &ttycache_index, n) ;
+      goto err ;
+    }
+    i = n ;
+  }
+  return stralloc_cats(sa, satmp.s + genalloc_s(diuint32, &ttycache_index)[i].right) ;
+ err:
+  {
+    register int e = errno ;
+    if (wasnull) stralloc_free(&satmp) ;
+    else satmp.len = d.right ;
+    errno = e ;
+  }
+  return 0 ;
+}
diff --git a/src/minutils/s6ps_wchan.c b/src/minutils/s6ps_wchan.c
new file mode 100644
index 0000000..e702ba9
--- /dev/null
+++ b/src/minutils/s6ps_wchan.c
@@ -0,0 +1,96 @@
+/* ISC license. */
+
+#include <sys/utsname.h>
+#include <skalibs/uint64.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/djbunix.h>
+#include "s6-ps.h"
+
+static stralloc sysmap = STRALLOC_ZERO ;
+static genalloc ind = GENALLOC_ZERO ;
+
+int s6ps_wchan_init (char const *file)
+{
+  if (file)
+  {
+    if (!openslurpclose(&sysmap, file)) return 0 ;
+  }
+  else
+  {
+    char *files[3] = { "/proc/kallsyms", 0, "/boot/System.map" } ;
+    struct utsname uts ;
+    unsigned int n ;
+    if (uname(&uts) < 0) return 0 ;
+    n = str_len(uts.release) ;
+    {
+      char buf[18 + n] ;
+      register unsigned int i = 0 ;
+      byte_copy(buf, 16, "/boot/System.map") ;
+      buf[16] = '-' ;
+      byte_copy(buf + 17, n + 1, uts.release) ;
+      files[1] = buf ;
+      for (; i < 3 ; i++)
+        if (openslurpclose(&sysmap, files[i])) break ;
+      if (i >= 3) return 0 ;
+    }
+  }
+  {
+    unsigned int i = 0 ;
+    if (!genalloc_append(unsigned int, &ind, &i)) goto err2 ;
+    for (i = 1 ; i <= sysmap.len ; i++)
+      if (sysmap.s[i-1] == '\n')
+        if (!genalloc_append(unsigned int, &ind, &i)) goto err ;
+  }
+  return 1 ;
+ err:
+  genalloc_free(unsigned int, &ind) ;
+ err2:
+  stralloc_free(&sysmap) ;
+  return 0 ;
+}
+
+void s6ps_wchan_finish (void)
+{
+  genalloc_free(unsigned int, &ind) ;
+  stralloc_free(&sysmap) ;
+}
+
+static inline unsigned int lookup (uint64 addr, unsigned int *i)
+{
+  unsigned int low = 0, mid, high = genalloc_len(unsigned int, &ind), len ;
+  for (;;)
+  {
+    uint64 cur ;
+    mid = (low + high) >> 1 ;
+    len = uint64_xscan(sysmap.s + genalloc_s(unsigned int, &ind)[mid], &cur) ;
+    if (!len) return 0 ;
+    if (cur == addr) break ;
+    if (mid == low) return 0 ;
+    if (addr < cur) high = mid ; else low = mid ;
+  }
+  *i = mid ;
+  return len ;
+}
+
+int s6ps_wchan_lookup (stralloc *sa, uint64 addr)
+{
+  if (addr == (sizeof(void *) == 8 ? 0xffffffffffffffffULL : 0xffffffffUL))
+    return stralloc_catb(sa, "*", 1) ;
+  if (!addr) return stralloc_catb(sa, "-", 1) ;
+  if (sysmap.len)
+  {
+    unsigned int i ;
+    unsigned int len = lookup(addr, &i) ;
+    register unsigned int pos ;
+    if (!len) return stralloc_catb(sa, "?", 1) ;
+    pos = genalloc_s(unsigned int, &ind)[i] + len + 3 ;
+    return stralloc_catb(sa, sysmap.s + pos, genalloc_s(unsigned int, &ind)[i+1] - 1 - pos) ;
+  }
+  if (!stralloc_readyplus(sa, UINT64_FMT + 3)) return 0 ;
+  stralloc_catb(sa, "(0x", 3) ;
+  sa->len += uint64_fmt(sa->s + sa->len, addr) ;
+  stralloc_catb(sa, ")", 1) ;
+  return 1 ;
+}
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