about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorLaurent Bercot <ska-skaware@skarnet.org>2019-12-30 23:27:32 +0000
committerLaurent Bercot <ska-skaware@skarnet.org>2019-12-30 23:27:32 +0000
commited9dadb2778ab4ff7683622907e22ed8c561bfda (patch)
tree59d4341aa2d885d6928e1e30ab6f5f57916c09ee /src
downloads6-frontend-ed9dadb2778ab4ff7683622907e22ed8c561bfda.tar.gz
s6-frontend-ed9dadb2778ab4ff7683622907e22ed8c561bfda.tar.xz
s6-frontend-ed9dadb2778ab4ff7683622907e22ed8c561bfda.zip
Initial commit.
Diffstat (limited to 'src')
-rw-r--r--src/alias/deps-exe/s6-frontend-alias1
-rw-r--r--src/alias/deps-exe/s6-frontend-alias-chpst1
-rw-r--r--src/alias/deps-exe/s6-frontend-alias-sv3
-rw-r--r--src/alias/s6-frontend-alias-chpst.c347
-rw-r--r--src/alias/s6-frontend-alias-sv.c377
-rw-r--r--src/alias/s6-frontend-alias.c235
-rw-r--r--src/config/deps-exe/s6-frontend-config-preprocess1
-rw-r--r--src/config/s6-frontend-config-preprocess.c286
-rw-r--r--src/config/s6-frontend-config-preprocess.txt38
9 files changed, 1289 insertions, 0 deletions
diff --git a/src/alias/deps-exe/s6-frontend-alias b/src/alias/deps-exe/s6-frontend-alias
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/alias/deps-exe/s6-frontend-alias
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/alias/deps-exe/s6-frontend-alias-chpst b/src/alias/deps-exe/s6-frontend-alias-chpst
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/alias/deps-exe/s6-frontend-alias-chpst
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/alias/deps-exe/s6-frontend-alias-sv b/src/alias/deps-exe/s6-frontend-alias-sv
new file mode 100644
index 0000000..b3e8440
--- /dev/null
+++ b/src/alias/deps-exe/s6-frontend-alias-sv
@@ -0,0 +1,3 @@
+-ls6
+-lskarnet
+${SPAWN_LIB}
diff --git a/src/alias/s6-frontend-alias-chpst.c b/src/alias/s6-frontend-alias-chpst.c
new file mode 100644
index 0000000..860654e
--- /dev/null
+++ b/src/alias/s6-frontend-alias-chpst.c
@@ -0,0 +1,347 @@
+ /* ISC license. */
+
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/types.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/env.h>
+#include <skalibs/djbunix.h>
+
+#include <execline/config.h>
+
+#include <s6/config.h>
+
+#define USAGE "s6-frontend-alias-chpst [ -v ] [ -P ] [ -0 ] [ -1 ] [ -2 ] [ -u user ] [ -U user ] [ -b argv0 ] [ -e dir ] [ -n niceness ] [ -l lock | -L lock ] [ -m bytes ] [ -d bytes ] [ -o n ] [ -p n ] [ -f bytes ] [ -c bytes ] prog..."
+
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static unsigned int verbosity = 0 ;
+
+static void printit (char const *const *argv)
+{
+  buffer_puts(buffer_2, PROG) ;
+  buffer_puts(buffer_2, ": info: executing the following command line:") ;
+  for (; *argv ; argv++)
+  {
+    buffer_puts(buffer_2, " ") ;
+    buffer_puts(buffer_2, *argv) ;
+  }
+  buffer_putsflush(buffer_2, "\n") ;
+}
+
+static inline size_t parseuggnum (char const *s, uint32_t *flags, uid_t *uid, gid_t *gid, gid_t *tab)
+{
+  size_t n = 0 ;
+  size_t pos = uid_scan(s, uid) ;
+  if (!pos) dieusage() ;
+  if (!s[pos]) return 0 ;
+  if (s[pos] != ':') dieusage() ;
+  s += pos+1 ;
+  pos = gid_scan(s, gid) ;
+  if (!pos) dieusage() ;
+  *flags |= 32768 ;
+  if (!s[pos]) return 0 ;
+  if (s[pos] != ':') dieusage() ;
+  s += pos+1 ;
+  if (!gid_scanlist(tab, NGROUPS_MAX, s, &n)) dieusage() ;
+  return n ;
+}
+
+static struct passwd *do_getpwnam (char const *s)
+{
+  struct passwd *pw = getpwnam(s) ;
+  if (!pw)
+  {
+    if (errno) strerr_diefu1sys(111, "read user database") ;
+    else strerr_dief2x(100, "user not found in user database: ", s) ;
+  }
+  return pw ;
+}
+
+static struct group *do_getgrnam (char const *s)
+{
+  struct group *gr = getgrnam(s) ;
+  if (!gr)
+  {
+    if (errno) strerr_diefu1sys(111, "read group database") ;
+    else strerr_dief2x(100, "group not found in group database: ", s) ;
+  }
+  return gr ;
+}
+
+static inline size_t parseuggsym (char const *s, uint32_t *flags, uid_t *uid, gid_t *gid, gid_t *tab)
+{
+  size_t n = 0 ;
+  struct passwd *pw ;
+  struct group *gr ;
+  size_t pos = str_chr(s, ':') ;
+  *flags |= 32768 ;
+  errno = 0 ;
+  if (!s[pos]) pw = do_getpwnam(s) ;
+  else
+  {
+    char tmp[pos+1] ;
+    memcpy(tmp, s, pos) ;
+    tmp[pos] = 0 ;
+    pw = do_getpwnam(tmp) ;
+  }
+  *uid = pw->pw_uid ;
+  if (!s[pos])
+  {
+    *gid = pw->pw_gid ;
+    return 0 ;
+  }
+  s += pos+1 ;
+  pos = str_chr(s, ':') ;
+  errno = 0 ;
+  if (!s[pos]) gr = do_getgrnam(s) ;
+  else
+  {
+    char tmp[pos+1] ;
+    memcpy(tmp, s, pos) ;
+    tmp[pos] = 0 ;
+    gr = do_getgrnam(tmp) ;
+  }
+  *gid = gr->gr_gid ;
+  if (!s[pos]) return 0 ;
+  s += pos+1 ;
+  while (*s)
+  {
+    if (n >= NGROUPS_MAX)
+      strerr_dief1x(100, "too many supplementary groups listed for the -u option") ;
+    pos = str_chr(s, ':') ;
+    errno = 0 ;
+    if (!s[pos]) gr = do_getgrnam(s) ;
+    else
+    {
+      char tmp[pos+1] ;
+      memcpy(tmp, s, pos) ;
+      tmp[pos] = 0 ;
+      gr = do_getgrnam(tmp) ;
+    }
+    tab[n++] = gr->gr_gid ;
+    s += s[pos] ? pos+1 : pos ;
+  }
+  return n ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  static char const *valopt[6] = { "-m", "-d", "-o", "-p", "-f", "-c" } ;
+  genalloc envdirs = GENALLOC_ZERO ; /* char const * */
+  stralloc newroot = STRALLOC_ZERO ;
+  unsigned int newargc = 0 ;
+  char const *argv0 = 0 ;
+  char const *lockfile = 0 ;
+  char const *envug = 0 ;
+  uint32_t flags = 0 ;
+  int niceval = 0 ;
+  char valfmt[6][UINT64_FMT] ;
+  char nicefmt[INT_FMT] ;
+  char uidfmt[UID_FMT] ;
+  char gidfmt[GID_FMT] ;
+  char gidlistfmt[GID_FMT * NGROUPS_MAX] ;
+  PROG = "s6-frontend-alias-chpst" ;
+
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "vP012u:U:b:e:/:n:l:L:m:d:o:p:f:c:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'v' : verbosity++ ; break ;
+        case '0' : flags |= 1 ; newargc += 2 ; break ;
+        case '1' : flags |= 2 ; newargc += 2 ; break ;
+        case '2' : flags |= 4 ; newargc += 2 ; break ;
+        case 'P' : flags |= 8 ; newargc += 2 ; break ;
+        case 'b' : argv0 = l.arg ; newargc += 4 ; break ;
+        case 'u' :
+        {
+          size_t n ;
+          uid_t uid ;
+          gid_t gid ;
+          gid_t tab[NGROUPS_MAX] ;
+          newargc += 6 ;
+          flags |= 16384 ; flags &= ~32768 ;
+          n = l.arg[0] == ':' ?
+            parseuggnum(l.arg + 1, &flags, &uid, &gid, tab) :
+            parseuggsym(l.arg, &flags, &uid, &gid, tab) ;
+          uidfmt[uid_fmt(uidfmt, uid)] = 0 ;
+          if (flags & 32768)
+          {
+            newargc += 2 ;
+            gidfmt[gid_fmt(gidfmt, gid)] = 0 ;
+          }
+          gidlistfmt[gid_fmtlist(gidlistfmt, tab, n)] = 0 ;
+          break ;
+        }
+        case 'U' :
+          envug = l.arg ;
+          newargc += 3 ;
+          if (envug[0] == ':') { flags |= 4096 ; envug++ ; newargc++ ; } else flags &= ~4096 ;
+          if (strchr(envug, ':')) { flags |= 8192 ; newargc++ ; } else flags &= ~8192 ;
+          break ;
+        case 'e' :
+          if (!genalloc_append(char const *, &envdirs, &l.arg)) dienomem() ;
+          newargc += 3 ;
+          break ;
+        case '/' :
+          newroot.len = 0 ;
+          if (sarealpath(&newroot, l.arg) < 0 || !stralloc_0(&newroot)) dienomem() ;
+          newargc += 2 ;
+          break ;
+        case 'n' :
+          if (!int0_scan(l.arg, &niceval)) dieusage() ;
+          newargc += 3 ;
+          break ;
+        case 'l' : lockfile = l.arg ; flags &= ~16 ; newargc += 4 ; break ;
+        case 'L' : lockfile = l.arg ; flags |= 16 ; newargc += 4 ; break ;
+        case 'm' :
+        case 'd' :
+        case 'o' :
+        case 'p' :
+        case 'f' :
+        case 'c' :
+        {
+          uint64_t val ;
+          size_t pos = byte_chr("mdopfc", 6, opt) ;
+          if (!uint640_scan(l.arg, &val)) dieusage() ;
+          valfmt[pos][uint64_fmt(valfmt[pos], val)] = 0 ;
+          flags |= 32 | (1 << (6 + pos)) ;
+          newargc += 2 ;
+          break ;
+        }
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (flags & 32) newargc += 2 ;
+  newargc += argc ;
+
+  {
+    unsigned int m = 0 ;
+    char const *newargv[newargc + 1] ;
+
+    if (niceval)
+    {
+      nicefmt[int_fmt(nicefmt, niceval)] = 0 ;
+      newargv[m++] = "nice" ;
+      newargv[m++] = "-n" ;
+      newargv[m++] = nicefmt ;
+      newargv[m++] = "--" ;
+    }
+
+    if (flags & 8)
+    {
+      newargv[m++] = S6_EXTBINPREFIX "s6-setsid" ;
+      newargv[m++] = "--" ;
+    }
+
+    if (genalloc_len(char const *, &envdirs))
+    {
+      for (size_t i = 0 ; i < genalloc_len(char const *, &envdirs) ; i++)
+      {
+        newargv[m++] = S6_EXTBINPREFIX "s6-envdir" ;
+        newargv[m++] = "--" ;
+        newargv[m++] = genalloc_s(char const *, &envdirs)[i] ;
+      }
+    }
+
+    if (lockfile)
+    {
+      newargv[m++] = S6_EXTBINPREFIX "s6-setlock" ;
+      newargv[m++] = flags & 16 ? "-n" : "-N" ;
+      newargv[m++] = "--" ;
+      newargv[m++] = lockfile ;
+    }
+
+    if (flags & 16384)
+    {
+      newargv[m++] = S6_EXTBINPREFIX "s6-applyuidgid" ;
+      newargv[m++] = "-u" ;
+      newargv[m++] = uidfmt ;
+      if (flags & 32768)
+      {
+        newargv[m++] = "-g" ;
+        newargv[m++] = gidfmt ;
+      }
+      newargv[m++] = "-G" ;
+      newargv[m++] = gidlistfmt ;
+      newargv[m++] = "--" ;
+    }
+
+    if (envug)
+    {
+      newargv[m++] = S6_EXTBINPREFIX "s6-envuidgid" ;
+      if (flags & 4096) newargv[m++] = "-n" ;
+      if (flags & 8192) newargv[m++] = "-B" ;
+      newargv[m++] = "--" ;
+      newargv[m++] = envug ;
+    }
+
+    if (flags & 32)
+    {
+      newargv[m++] = S6_EXTBINPREFIX "s6-softlimit" ;
+      for (unsigned int i = 0 ; i < 6 ; i++) if (flags & (1 << (i + 6)))
+      {
+        newargv[m++] = valopt[i] ;
+        newargv[m++] = valfmt[i] ;
+      }
+      newargv[m++] = "--" ;
+    }
+
+    if (flags & 1)
+    {
+      newargv[m++] = EXECLINE_EXTBINPREFIX "fdclose" ;
+      newargv[m++] = "0" ;
+    }
+
+    if (flags & 2)
+    {
+      newargv[m++] = EXECLINE_EXTBINPREFIX "fdclose" ;
+      newargv[m++] = "1" ;
+    }
+
+    if (flags & 4)
+    {
+      newargv[m++] = EXECLINE_EXTBINPREFIX "fdclose" ;
+      newargv[m++] = "2" ;
+    }
+
+    if (argv0)
+    {
+      newargv[m++] = EXECLINE_BINPREFIX "exec" ;
+      newargv[m++] = "-a" ;
+      newargv[m++] = argv0 ;
+      newargv[m++] = "--" ;
+    }
+
+    if (newroot.s)
+    {
+      newargv[m++] = "chroot" ;
+      newargv[m++] = newroot.s ;
+      if (argv0) strerr_warnw1x("the -b option is ineffective when the -/ option is also given") ;
+    }
+
+    for (int i = 0 ; i < argc+1 ; i++) newargv[m++] = argv[i] ;
+    if (verbosity) printit(newargv) ;
+    xpathexec0_run(newargv, envp) ;
+  }
+}
diff --git a/src/alias/s6-frontend-alias-sv.c b/src/alias/s6-frontend-alias-sv.c
new file mode 100644
index 0000000..e6e0ec8
--- /dev/null
+++ b/src/alias/s6-frontend-alias-sv.c
@@ -0,0 +1,377 @@
+ /* ISC license. */
+
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <skalibs/uint32.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/djbunix.h>
+
+#include <s6/config.h>
+#include <s6/s6-supervise.h>
+
+#define USAGE "s6-frontend-alias-sv [ -v ] [ -w sec ] command services..."
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+
+typedef int execfunc_t (char const *, char const *const *) ;
+typedef execfunc_t *execfunc_t_ref ;
+
+typedef struct info_s info_t, *info_t_ref ;
+struct info_s
+{
+  char const *name ;
+  execfunc_t_ref f ;
+} ;
+
+static int dowait = 0 ;
+static uint32_t secs = 7 ;
+
+static void warnnolog (void)
+{
+  strerr_warnw1x("s6-svc only sends commands to a single service, even if it has a dedicated logger") ;
+}
+
+static void warnnokill (void)
+{
+  strerr_warnw1x("s6-supervise pilots a kill signal via the timeout-kill file in the service directory") ;
+}
+
+static int info_cmp (void const *a, void const *b)
+{
+  char const *name = a ;
+  info_t const *info = b ;
+  return strcmp(name, info->name) ;
+}
+
+static int spawnit (char const *const *argv, char const *const *envp)
+{
+  int wstat ;
+  pid_t r ;
+  pid_t pid = child_spawn0(argv[0], argv, envp) ;
+  if (!pid)
+  {
+    strerr_warnwu2sys("spawn ", argv[0]) ;
+    return 1 ;
+  }
+  r = wait_pid(pid, &wstat) ;
+  if (r != pid)
+  {
+    strerr_warnwu2sys("wait for ", argv[0]) ;
+    return 1 ;
+  }
+  return !!WIFSIGNALED(wstat) || !!WEXITSTATUS(wstat) ;
+}
+
+static int simple_svc (char const *dir, char const *options, char const *const *envp)
+{
+  char const *argv[5] = { S6_EXTBINPREFIX "s6-svc", options, "--", dir, 0 } ;
+  return spawnit(argv, envp) ;
+}
+
+static int complex_svc (char const *dir, char const *order, char waitfor, char const *const *envp)
+{
+  char warg[4] = "-w?" ;
+  char fmt[2 + UINT32_FMT] = "-T" ;
+  char const *argv[7] = { S6_EXTBINPREFIX "s6-svc", warg, fmt, order, "--", dir, 0 } ;
+  fmt[2 + uint32_fmt(fmt + 2, 1000 * secs)] = 0 ;
+  warg[2] = waitfor ;
+  return spawnit(argv, envp) ;
+}
+
+static int status (char const *dir, char const *const *envp)
+{
+  char const *argv[4] = { S6_EXTBINPREFIX "s6-svstat", "--", dir, 0 } ;
+  return spawnit(argv, envp) ;
+}
+
+static int usr1_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-1", envp) ;
+}
+
+static int usr2_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-2", envp) ;
+}
+
+static int alarm_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-a", envp) ;
+}
+
+static int cont_h (char const *dir, char const *const *envp)
+{
+  if (dowait)
+  {
+    complex_svc(dir, "-o", 'U', envp) ;
+    return status(dir, envp) ;
+  }
+  else return simple_svc(dir, "-c", envp) ;
+}
+
+static int down (char const *dir, char const *const *envp)
+{
+  if (dowait)
+  {
+    complex_svc(dir, "-d", 'D', envp) ;
+    return status(dir, envp) ;
+  }
+  else return simple_svc(dir, "-d", envp) ;
+}
+
+static int bail (char const *dir, char const *const *envp)
+{
+  warnnolog() ;
+  if (dowait)
+  {
+    complex_svc(dir, "-d", 'D', envp) ;
+    status(dir, envp) ;
+  }
+  return simple_svc(dir, "-xd", envp) ;
+}
+
+static int hup_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-h", envp) ;
+}
+
+static int int_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-i", envp) ;
+}
+
+static int kill_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-k", envp) ;
+}
+
+static int once (char const *dir, char const *const *envp)
+{
+  if (dowait)
+  {
+    complex_svc(dir, "-o", 'U', envp) ;
+    return status(dir, envp) ;
+  }
+  else return simple_svc(dir, "-o", envp) ;
+}
+
+static int pause_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-p", envp) ;
+}
+
+static int quit_h (char const *dir, char const *const *envp)
+{
+  return simple_svc(dir, "-q", envp) ;
+}
+
+static int term_h (char const *dir, char const *const *envp)
+{
+  if (dowait)
+  {
+    complex_svc(dir, "-r", 'R', envp) ;
+    return status(dir, envp) ;
+  }
+  else return simple_svc(dir, "-t", envp) ;
+}
+
+static int up (char const *dir, char const *const *envp)
+{
+  if (dowait)
+  {
+    complex_svc(dir, "-u", 'U', envp) ;
+    return status(dir, envp) ;
+  }
+  else return simple_svc(dir, "-u", envp) ;
+}
+
+static int check (char const *dir, char const *const *envp)
+{
+  s6_svstatus_t svst ;
+  char warg[3] = "-?" ;
+  char fmt[2 + UINT32_FMT] = "-t" ;
+  char const *argv[6] = { S6_EXTBINPREFIX "s6-svwait", warg, fmt, "--", dir, 0 } ;
+  fmt[2 + uint32_fmt(fmt + 2, 1000 * secs)] = 0 ;
+  if (!s6_svstatus_read(dir, &svst)) return 1 ;
+  warg[1] = svst.flagwantup ? 'U' : 'D' ;
+  spawnit(argv, envp) ;
+  return status(dir, envp) ;
+}
+
+static int lsb_reload (char const *dir, char const *const *envp)
+{
+  hup_h(dir, envp) ;
+  return status(dir, envp) ;
+}
+
+static int lsb_restart (char const *dir, char const *const *envp)
+{
+  complex_svc(dir, "-ru", 'U', envp) ;
+  return status(dir, envp) ;
+}
+
+static int lsb_start (char const *dir, char const *const *envp)
+{
+  complex_svc(dir, "-u", 'U', envp) ;
+  return status(dir, envp) ;
+}
+
+static int lsb_stop (char const *dir, char const *const *envp)
+{
+  complex_svc(dir, "-d", 'D', envp) ;
+  return status(dir, envp) ;
+}
+
+static int lsb_shutdown (char const *dir, char const *const *envp)
+{
+  warnnolog() ;
+  complex_svc(dir, "-d", 'D', envp) ;
+  status(dir, envp) ;
+  return simple_svc(dir, "-x", envp) ;
+}
+
+static int lsb_forcereload (char const *dir, char const *const *envp)
+{
+  warnnokill() ;
+  return lsb_reload(dir, envp) ;
+}
+
+static int lsb_forcerestart (char const *dir, char const *const *envp)
+{
+  warnnokill() ;
+  return lsb_restart(dir, envp) ;
+}
+
+static int lsb_forcestop (char const *dir, char const *const *envp)
+{
+  warnnokill() ;
+  return lsb_stop(dir, envp) ;
+}
+
+static int lsb_forceshutdown (char const *dir, char const *const *envp)
+{
+  warnnokill() ;
+  return lsb_shutdown(dir, envp) ;
+}
+
+static int lsb_tryrestart (char const *dir, char const *const *envp)
+{
+  s6_svstatus_t svst ;
+  if (s6_svstatus_read(dir, &svst) && svst.flagwantup && svst.pid && !svst.flagfinishing)
+    complex_svc(dir, "-r", 'U', envp) ;    
+  return status(dir, envp) ;
+}
+
+static info_t const commands[] =
+{
+  { .name = "1", .f = &usr1_h },
+  { .name = "2", .f = &usr2_h },
+  { .name = "a", .f = &alarm_h },
+  { .name = "alarm", .f = &alarm_h },
+  { .name = "c", .f = &cont_h },
+  { .name = "check", .f = &check },
+  { .name = "cont", .f = &cont_h },
+  { .name = "d", .f = &down },
+  { .name = "down", .f = &down },
+  { .name = "e", .f = &bail },
+  { .name = "exit", .f = &bail },
+  { .name = "force-reload", .f = &lsb_forcereload },
+  { .name = "force-restart", .f = &lsb_forcerestart },
+  { .name = "force-shutdown", .f = &lsb_forceshutdown },
+  { .name = "force-stop", .f = &lsb_forcestop },
+  { .name = "h", .f = &hup_h },
+  { .name = "hup", .f = &hup_h },
+  { .name = "i", .f = &int_h },
+  { .name = "interrupt", .f = &int_h },
+  { .name = "k", .f = &kill_h },
+  { .name = "kill", .f = &kill_h },
+  { .name = "o", .f = &once },
+  { .name = "once", .f = &once },
+  { .name = "p", .f = &pause_h },
+  { .name = "pause", .f = &pause_h },
+  { .name = "q", .f = &quit_h },
+  { .name = "quit", .f = &quit_h },
+  { .name = "reload", .f = &lsb_reload },
+  { .name = "restart", .f = &lsb_restart },
+  { .name = "s", .f = &status },
+  { .name = "shutdown", .f = &lsb_shutdown },
+  { .name = "start", .f = &lsb_start },
+  { .name = "status", .f = &status },
+  { .name = "stop", .f = &lsb_stop },
+  { .name = "t", .f = &term_h },
+  { .name = "term", .f = &term_h },
+  { .name = "try-restart", .f = &lsb_tryrestart },
+  { .name = "u", .f = &up },
+  { .name = "up", .f = &up }
+} ;
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  int e = 0 ;
+  info_t *p ;
+  char const *x = getenv("SVWAIT") ;
+  char const *scandir = getenv("SVDIR") ;
+  size_t scandirlen ;
+  PROG = "s6-frontend-alias-sv" ;
+  if (!scandir) scandir = "/run/service" ; /* TODO: infer from s6li config */
+  scandirlen = strlen(scandir) ;
+  if (x)
+  {
+    if (!uint320_scan(x, &secs))
+      strerr_warnw1x("invalid SVWAIT value") ;
+  }
+
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "vw:", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'v' : dowait = 1 ; break ;
+        case 'w' : if (!uint320_scan(l.arg, &secs)) dieusage() ; break ;
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+
+  if (argc < 2) dieusage() ;
+  p = bsearch(argv[0], commands, sizeof(commands) / sizeof(info_t), sizeof(info_t), &info_cmp) ;
+  if (!p) strerr_dief2x(100, "unknown command: ", argv[0]) ;
+
+  for (argv++ ; *argv ; argv++)
+  {
+    if ((argv[0][0] == '.' && (argv[0][1] == '/' || (argv[0][1] == '.' && argv[0][2] == '/'))) || argv[0][0] == '/')
+      e += (*p->f)(*argv, envp) ;
+    else
+    {
+      int what = 1 ;
+      struct stat st ;
+      size_t len = strlen(*argv) ;
+      char fn[scandirlen + len + 2] ;
+      memcpy(fn, scandir, scandirlen) ;
+      fn[scandirlen] = '/' ;
+      memcpy(fn + scandirlen + 1, *argv, len + 1) ;
+      if (stat(fn, &st) < 0)  /* XXX: TOCTOU but we don't care */
+      {
+        if (errno != ENOENT)
+        {
+          e++ ;
+          what = 0 ;
+          strerr_warnwu2sys("stat ", fn) ;
+        }
+      }
+      else if (S_ISDIR(st.st_mode)) what = 2 ;
+      if (what) e += (*p->f)(what > 1 ? fn : *argv, envp) ;
+    }
+  }
+  return e ;
+}
diff --git a/src/alias/s6-frontend-alias.c b/src/alias/s6-frontend-alias.c
new file mode 100644
index 0000000..7467632
--- /dev/null
+++ b/src/alias/s6-frontend-alias.c
@@ -0,0 +1,235 @@
+ /* ISC license. */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <skalibs/buffer.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+
+#include <s6/config.h>
+
+#include <s6-frontend/config.h>
+
+#define USAGE "s6-frontend-alias [ -v ] cmdname options..."
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb")
+#define dieusage() strerr_dieusage(100, USAGE)
+
+static unsigned int verbosity = 0 ;
+
+typedef void execfunc_t (int, char const *const *, char const *const *) ;
+typedef execfunc_t *execfunc_t_ref ;
+
+typedef struct info_s info_t, *info_t_ref ;
+struct info_s
+{
+  char const *name ;
+  char const *cmd ;
+  execfunc_t_ref f ;
+} ;
+
+static int info_cmp (void const *a, void const *b)
+{
+  char const *name = a ;
+  info_t const *info = b ;
+  return strcmp(name, info->name) ;
+}
+
+#if defined(S6_FRONTEND_WRAP_DAEMONTOOLS) || defined (S6_FRONTEND_WRAP_RUNIT)
+static void noboot (char const *name)
+{
+  strerr_dief3x(100, "s6 does not provide a ", name, " emulation. To boot on a s6 supervision tree, please consider the s6-linux-init package.") ;
+}
+#endif
+
+#ifdef S6_FRONTEND_WRAP_DAEMONTOOLS
+
+static void readproctitle (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  noboot("readproctitle") ;
+}
+
+static void svscanboot (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  noboot("svscanboot") ;
+}
+
+#endif
+
+#ifdef S6_FRONTEND_WRAP_RUNIT
+
+static void runit (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  noboot("runit") ;
+}
+
+static void runit_init (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  noboot("runit-init") ;
+}
+
+static void runsvchdir (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  strerr_dief1x(100, "s6 does not provide a runsvchdir emulation. To handle several different sets of services, please consider the s6-rc package.") ;
+}
+
+static void runsvdir (int argc, char const *const *argv, char const *const *envp)
+{
+  char const *newargv[4] = { S6_EXTBINPREFIX "s6-svscan", "-St14000", 0, 0 } ;
+  int dosetsid = 0 ;
+  subgetopt_t l = SUBGETOPT_ZERO ;
+  for (;;)
+  {
+    int opt = subgetopt_r(argc, argv, "P", &l) ;
+    if (opt == -1) break ;
+    switch (opt)
+    {
+      case 'P' : dosetsid = 1 ; break ;
+      default : dieusage() ;
+    }
+  }
+  argc -= l.ind ; argv += l.ind ;
+  if (dosetsid)
+    strerr_warnw1x("-P option ignored: s6-svscan does not run its supervisor processes (s6-supervise) in a new session. However, by default, it runs every service in a new session.") ;
+  if (argc >= 2)
+    strerr_warnw1x("s6-svscan does not support logging to a readproctitle process. To log the output of your supervision tree, please consider using the s6-linux-init package.") ;
+  newargv[2] = argv[0] ;
+  if (verbosity)
+  {
+    buffer_puts(buffer_2, PROG) ;
+    buffer_puts(buffer_2, ": info: executing command line:") ;
+    for (char const *const *p = argv ; *p ; p++)
+    {
+      buffer_puts(buffer_2, " ") ;
+      buffer_puts(buffer_2, *p) ;
+    }
+    buffer_putsflush(buffer_2, "\n") ;
+  }
+  xpathexec_run(newargv[0], newargv, envp) ;
+}
+
+static void svlogd (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  strerr_dief1x(100, "the s6-log program is similar to svlogd, but uses a different filtering syntax and does not use a config file in the logdir. Please see https://skarnet.org/software/s6/s6-log.html") ;
+}
+
+static void utmpset (int argc, char const *const *argv, char const *const *envp)
+{
+  (void)argc ;
+  (void)argv ;
+  (void)envp ;
+  strerr_dief1x(100, "s6 does not provide a utmpset emulation. To handle utmp records, please consider the s6-linux-init package, along with the utmps package if necessary.") ;
+}
+
+#endif
+
+static info_t const aliases[] =
+{
+#ifdef S6_FRONTEND_WRAP_RUNIT
+  { .name = "chpst", .cmd = S6_FRONTEND_BINPREFIX "s6-frontend-alias-chpst", .f = 0 },
+#endif
+#ifdef S6_FRONTEND_WRAP_DAEMONTOOLS
+  { .name = "envdir", .cmd = S6_EXTBINPREFIX "s6-envdir", .f = 0 },
+  { .name = "envuidgid", .cmd = S6_EXTBINPREFIX "s6-envuidgid", .f = 0 },
+  { .name = "fghack", .cmd = S6_EXTBINPREFIX "s6-fghack", .f = 0 },
+  { .name = "multilog", .cmd = S6_EXTBINPREFIX "s6-log", .f = 0 },
+  { .name = "pgrphack", .cmd = S6_EXTBINPREFIX "s6-setsid", .f = 0 },
+  { .name = "readproctitle", .cmd = 0, .f = &readproctitle },
+#endif
+#ifdef S6_FRONTEND_WRAP_RUNIT
+  { .name = "runit", .cmd = 0, .f = &runit },
+  { .name = "runit-init", .cmd = 0, .f = &runit_init },
+  { .name = "runsv", .cmd = S6_EXTBINPREFIX "s6-supervise", .f = 0 },
+  { .name = "runsvchdir", .cmd = 0, .f = &runsvchdir },
+  { .name = "runsvdir", .cmd = 0, .f = &runsvdir },
+#endif
+#ifdef S6_FRONTEND_WRAP_DAEMONTOOLS
+  { .name = "setlock", .cmd = S6_EXTBINPREFIX "s6-setlock", .f = 0 },
+  { .name = "setuidgid", .cmd = S6_EXTBINPREFIX "s6-setuidgid", .f = 0 },
+  { .name = "softlimit", .cmd = S6_EXTBINPREFIX "s6-softlimit", .f = 0 },
+  { .name = "supervise", .cmd = S6_EXTBINPREFIX "s6-supervise", .f = 0 },
+#endif
+#ifdef S6_FRONTEND_WRAP_RUNIT
+  { .name = "sv", .cmd = S6_FRONTEND_BINPREFIX "s6-frontend-alias-sv", .f = 0 },
+#endif
+#ifdef S6_FRONTEND_WRAP_DAEMONTOOLS
+  { .name = "svc", .cmd = S6_EXTBINPREFIX "s6-svc", .f = 0 },
+#endif
+#ifdef S6_FRONTEND_WRAP_RUNIT
+  { .name = "svlogd", .cmd = 0, .f = &svlogd },
+#endif
+#ifdef S6_FRONTEND_WRAP_DAEMONTOOLS
+  { .name = "svok", .cmd = S6_EXTBINPREFIX "s6-svok", .f = 0 },
+  { .name = "svscan", .cmd = S6_EXTBINPREFIX "s6-svscan", .f = 0 },
+  { .name = "svscanboot", .cmd = 0, .f = &svscanboot },
+  { .name = "svstat", .cmd = S6_EXTBINPREFIX "s6-svstat", .f = 0 },
+  { .name = "tai64n", .cmd = S6_EXTBINPREFIX "s6-tai64n", .f = 0 },
+  { .name = "tai64nlocal", .cmd = S6_EXTBINPREFIX "s6-tai64nlocal", .f = 0 },
+#endif
+#ifdef S6_FRONTEND_WRAP_RUNIT
+  { .name = "utmpset", .cmd = 0, .f = &utmpset },
+#endif
+} ;
+
+int main (int argc, char const **argv, char const *const *envp)
+{
+  char const *name = argv[0] ;
+  stralloc sa = STRALLOC_ZERO ;
+  info_t *p ;
+  PROG = "s6-frontend-alias" ;
+
+  if (!sabasename(&sa, name, strlen(name)) || !stralloc_0(&sa)) dienomem() ;
+  if (!strcmp(sa.s, PROG))
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "v", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        case 'v' : verbosity++ ; break ;
+        default : strerr_dieusage(100, USAGE) ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+    if (!argc) dieusage() ;
+    name = *argv ;
+    stralloc_free(&sa) ;
+  }
+  else name = sa.s ;
+
+  p = bsearch(name, aliases, sizeof(aliases) / sizeof(info_t), sizeof(info_t), &info_cmp) ;
+  if (!p) strerr_dief2x(100, "unknown alias: ", name) ;
+  if (p->cmd)
+  {
+    argv[0] = p->cmd ;
+    if (verbosity)
+      strerr_warni4x("the s6 version of ", name, " is ", p->cmd) ;
+    xpathexec_run(argv[0], argv, envp) ;
+  }
+  else (*p->f)(argc, argv, envp) ;
+  strerr_dief1x(101, "can't happen: incorrect alias handler. Please submit a bug-report.") ;
+}
diff --git a/src/config/deps-exe/s6-frontend-config-preprocess b/src/config/deps-exe/s6-frontend-config-preprocess
new file mode 100644
index 0000000..e7187fe
--- /dev/null
+++ b/src/config/deps-exe/s6-frontend-config-preprocess
@@ -0,0 +1 @@
+-lskarnet
diff --git a/src/config/s6-frontend-config-preprocess.c b/src/config/s6-frontend-config-preprocess.c
new file mode 100644
index 0000000..db09e24
--- /dev/null
+++ b/src/config/s6-frontend-config-preprocess.c
@@ -0,0 +1,286 @@
+/* ISC license. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include <skalibs/uint64.h>
+#include <skalibs/sgetopt.h>
+#include <skalibs/buffer.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/direntry.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/skamisc.h>
+#include <skalibs/avltree.h>
+
+#define USAGE "s6-frontend-config-preprocess file"
+#define dieusage() strerr_dieusage(100, USAGE)
+#define dienomem() strerr_diefu1sys(111, "stralloc_catb") ;
+#define MAXDEPTH 100
+
+static stralloc sa = STRALLOC_ZERO ;
+static unsigned int depth = 0 ;
+
+
+ /* Name storage */
+
+static stralloc namesa = STRALLOC_ZERO ;
+
+static void *name_dtok (uint32_t pos, void *aux)
+{
+  (void)aux ;
+  return namesa.s + pos ;
+}
+
+static int name_cmp (void const *a, void const *b, void *aux)
+{
+  (void)aux ;
+  return strcmp((char const *)a, (char const *)b) ;
+}
+
+static avltree namemap = AVLTREE_INIT(8, 3, 8, &name_dtok, &name_cmp, 0) ;
+
+
+ /* Directory sorting */
+
+static char *dname_cmp_base ;
+static int dname_cmp (void const *a, void const *b)
+{
+  return strcmp(dname_cmp_base + *(size_t *)a, dname_cmp_base + *(size_t *)b) ;
+}
+
+
+ /* Recursive inclusion functions */
+
+static int includefromhere (char const *, int) ;
+
+static inline void includecwd (int once)
+{
+  genalloc ga = GENALLOC_ZERO ; /* size_t */
+  DIR *dir ;
+  size_t sabase = sa.len ;
+  if (sagetcwd(&sa) < 0 || !stralloc_0(&sa)) dienomem() ;
+  dir = opendir(".") ;
+  if (!dir) strerr_diefu2sys(111, "opendir ", sa.s + sabase) ;
+
+  for (;;)
+  {
+    direntry *d ;
+    errno = 0 ;
+    d = readdir(dir) ;
+    if (!d) break ;
+    if (d->d_name[0] == '.') continue ;
+    if (!genalloc_catb(size_t, &ga, &sa.len, 1)) break ;
+    if (!stralloc_catb(&sa, d->d_name, strlen(d->d_name)+1)) break ;
+  }
+  dir_close(dir) ;
+  if (errno) strerr_diefu2sys(111, "readdir ", sa.s + sabase) ;
+
+  dname_cmp_base = sa.s ;
+  qsort(genalloc_s(size_t, &ga), genalloc_len(size_t, &ga), sizeof(size_t), &dname_cmp) ;
+
+  for (size_t i = 0 ; i < genalloc_len(size_t, &ga) ; i++)
+    if (!includefromhere(sa.s + genalloc_s(size_t, &ga)[i], once))
+      strerr_dief4sys(1, "in ", sa.s + sabase, ": unable to include ", sa.s + genalloc_s(size_t, &ga)[i]) ;
+
+  genalloc_free(size_t, &ga) ;
+  sa.len = sabase ;
+}
+
+static int include (char const *file, int once)
+{
+  size_t sabase = sa.len ;
+  size_t filelen = strlen(file) ;
+  if (!sadirname(&sa, file, filelen) || !stralloc_0(&sa)) dienomem() ;
+  if (chdir(sa.s + sabase) < 0) return 0 ;
+  sa.len = sabase ;
+  if (!sabasename(&sa, file, filelen)) dienomem() ;
+  {
+    char fn[sa.len + 1 - sabase] ;
+    memcpy(fn, sa.s + sabase, sa.len - sabase) ;
+    fn[sa.len - sabase] = 0 ;
+    sa.len = sabase ;
+    return includefromhere(fn, once) ;
+  }
+}
+
+static inline int idcmd (char const *s)
+{
+  static char const *commands[] =
+  {
+    "include",
+    "includeonce",
+    "includedir",
+    "includedironce",
+    0
+  } ;
+  for (char const **p = commands ; *p ; p++)
+    if (!strcmp(s, *p)) return p - commands ;
+  return -1 ;
+}
+
+static inline unsigned char cclass (char c)
+{
+  static unsigned char const classtable[34] = "0444444443144344444444444444444432" ;
+  return (unsigned char)c < 34 ? classtable[(unsigned char)c] - '0' : 4 ;
+}
+
+static int includefromhere (char const *file, int once)
+{
+  size_t sabase = sa.len ;
+  if (sarealpath(&sa, file) < 0 || !stralloc_0(&sa)) return 0 ;
+
+  if (once)
+  {
+    uint32_t dummy ;
+    if (avltree_search(&namemap, sa.s + sabase, &dummy)) goto end ;
+    {
+      size_t cur = namesa.len ;
+      size_t filelen = strlen(file) ;
+      if (cur + filelen + 2 < cur || cur + filelen + 2 > UINT32_MAX)
+        strerr_dief3x(3, "in ", sa.s + sabase, ": too much nesting") ;
+      if (!stralloc_catb(&namesa, sa.s + sabase, sa.len - sabase)) dienomem() ;
+      if (!avltree_insert(&namemap, cur)) dienomem() ;
+    }
+  }
+
+  if (depth++ > MAXDEPTH)
+    strerr_dief3x(3, "in ", sa.s + sabase, ": too much nesting") ;
+
+  {
+    static unsigned char const table[8][5] =
+    {
+      { 0x08, 0x10, 0x02, 0x10, 0x10 },
+      { 0x08, 0x10, 0x11, 0x11, 0x11 },
+      { 0x08, 0x00, 0x03, 0x04, 0x25 },
+      { 0x08, 0x00, 0x03, 0x03, 0x03 },
+      { 0x09, 0x09, 0x09, 0x04, 0x25 },
+      { 0x09, 0x09, 0x09, 0x46, 0x25 },
+      { 0x0a, 0x0a, 0x07, 0x06, 0x27 },
+      { 0x88, 0x80, 0x27, 0x27, 0x27 }
+    } ;
+    stralloc localsa = STRALLOC_ZERO ;
+    uint64_t line = 1 ;
+    size_t sacur = sa.len ;
+    int cmd = -1 ;
+    buffer b ;
+    char buf[4096] ;
+    unsigned char state = 0 ;
+    int fd = open_readb(file) ;
+    if (fd < 0) strerr_diefu2sys(111, "open ", sa.s + sabase) ;
+    buffer_init(&b, &buffer_read, fd, buf, 4096) ;
+
+    if (buffer_put(buffer_1, "! 0 ", 4) < 4
+     || buffer_put(buffer_1, sa.s + sabase, sacur - 1 - sabase) < sacur - 1 - sabase
+     || buffer_put(buffer_1, "\n", 1) < 1)
+      strerr_diefu1sys(111, "write to stdout") ;
+
+    while (state < 8)
+    {
+      uint16_t what ;
+      char c = 0 ;
+      if (buffer_get(&b, &c, 1) < 0)
+        strerr_diefu2sys(111, "read from ", sa.s + sabase) ;
+      what = table[state][cclass((unsigned char)c)] ;
+      state = what & 0x000f ;
+      if (what & 0x0010)
+        if (buffer_put(buffer_1, &c, 1) < 1)
+          strerr_diefu1sys(111, "write to stdout") ;
+      if (what & 0x0020)
+        if (!stralloc_catb(&localsa, &c, 1)) dienomem() ;
+      if (what & 0x0040)
+      {
+        if (!stralloc_0(&localsa)) dienomem() ;
+        cmd = idcmd(localsa.s) ;
+        if (cmd < 0)
+        {
+          char fmt[UINT64_FMT] ;
+          fmt[uint64_fmt(fmt, line)] = 0 ;
+          strerr_dief6x(2, "in ", sa.s + sabase, " line ", fmt, ": unrecognized directive: ", localsa.s) ;
+        }
+        localsa.len = 0 ;
+      }
+      if (what & 0x0080)
+      {
+        int fdhere = open(".", O_RDONLY | O_DIRECTORY) ;
+        if (fdhere < 0)
+          strerr_dief3sys(111, "in ", sa.s + sabase, ": unable to open base directory: ") ;
+        if (!stralloc_0(&localsa)) dienomem() ;
+        if (cmd & 2)
+        {
+          if (chdir(localsa.s) < 0)
+          {
+            char fmt[UINT64_FMT] ;
+            fmt[uint64_fmt(fmt, line)] = 0 ;
+            strerr_dief6sys(111, "in ", sa.s + sabase, " line ", fmt, ": unable to chdir to ", localsa.s) ;
+          }
+          includecwd(cmd & 1) ;
+        }
+        else if (!include(localsa.s, cmd & 1))
+        {
+          char fmt[UINT64_FMT] ;
+          fmt[uint64_fmt(fmt, line)] = 0 ;
+          strerr_dief6sys(111, "in ", sa.s + sabase, " line ", fmt, ": unable to include ", localsa.s) ;
+        }
+        if (fchdir(fdhere) < 0)
+          strerr_dief3sys(111, "in ", sa.s + sabase, ": unable to fchdir back") ;
+        fd_close(fdhere) ;
+        localsa.len = 0 ;
+        {
+          char fmt[UINT64_FMT] ;
+          size_t fmtlen = uint64_fmt(fmt, line) ;
+          fmt[fmtlen++] = ' ' ;
+          if (buffer_put(buffer_1, "! ", 2) < 2
+           || buffer_put(buffer_1, fmt, fmtlen) < fmtlen
+           || buffer_put(buffer_1, sa.s + sabase, sacur - 1 - sabase) < sacur - 1 - sabase
+           || buffer_put(buffer_1, "\n", 1) < 1)
+            strerr_diefu1sys(111, "write to stdout") ;
+        }
+      }
+      if (c == '\n' && state <= 8) line++ ;
+    }
+    if (state > 8)
+    {
+      char fmt[UINT64_FMT] ;
+      fmt[uint64_fmt(fmt, line)] = 0 ;
+      strerr_dief5x(2, "in ", sa.s + sabase, " line ", fmt, ": syntax error: invalid ! line") ;
+    }
+    fd_close(fd) ;
+    stralloc_free(&localsa) ;
+  }
+
+ depth-- ;
+ end:
+  sa.len = sabase ;
+  return 1 ;
+}
+
+int main (int argc, char const *const *argv, char const *const *envp)
+{
+  PROG = "s6-frontend-config-preprocess" ;
+  {
+    subgetopt_t l = SUBGETOPT_ZERO ;
+    for (;;)
+    {
+      int opt = subgetopt_r(argc, argv, "", &l) ;
+      if (opt == -1) break ;
+      switch (opt)
+      {
+        default : dieusage() ;
+      }
+    }
+    argc -= l.ind ; argv += l.ind ;
+  }
+  if (!argc) dieusage() ;
+
+  if (!include(argv[0], 1)) strerr_diefu2sys(1, "preprocess ", argv[0]) ;
+  if (!buffer_flush(buffer_1))
+    strerr_diefu1sys(111, "write to stdout") ;
+  return 0 ;
+}
diff --git a/src/config/s6-frontend-config-preprocess.txt b/src/config/s6-frontend-config-preprocess.txt
new file mode 100644
index 0000000..e81c86e
--- /dev/null
+++ b/src/config/s6-frontend-config-preprocess.txt
@@ -0,0 +1,38 @@
+
+ Automaton for the preprocessor:
+
+
+class	|	0	1	2	3	4
+st\ev	|	\0	\n	!	space	other
+
+START	|		print		print	print
+0	|	END	START	CMD	NORMAL	NORMAL
+
+NORMAL	|		print	print	print	print
+1	|	END	START	NORMAL	NORMAL	NORMAL
+
+CMD	|					add
+2	|	END	START	IGNORE	CMD1	CMD2
+
+IGNORE	|
+3	|	END	START	IGNORE	IGNORE	IGNORE
+
+CMD1	|					add
+4	|	X	X	X	CMD1	CMD2
+
+CMD2	|				idcmd   add
+5	|	X	X	X	ARG	CMD2
+
+ARG	|					add
+6	|	X	X	ARG1	ARG	ARG1
+
+ARG1	|	proc	proc    add     add     add
+7	|	END	START	ARG1	ARG1	ARG1
+
+states: 0-7 plus END and X -> 4 bits
+actions: 4. -> 8 bits total, fits in a char.
+
+print 0x10 copies the character to stdout
+add   0x20 adds the character to the processing string
+idcmd 0x40 ids the processing string for an !include cmd
+proc  0x80 gets the filename and procs the include