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