Welcome to little lamb

Code » anopa » commit caac2e2

first commit

author Olivier Brunel
2015-02-11 14:53:07 UTC
committer Olivier Brunel
2015-04-04 12:47:28 UTC

first commit

I really need to start this (much) sooner...

.gitignore +16 -0
Makefile +131 -0
configure +404 -0
doc/aa-enable.pod +157 -0
doc/anopa.pod +335 -0
package/deps-build +3 -0
package/info +4 -0
package/modes +2 -0
package/targets.mak +21 -0
src/anopa/aa-enable.c +271 -0
src/anopa/aa-start.c +312 -0
src/anopa/deps-exe/aa-enable +4 -0
src/anopa/deps-exe/aa-start +6 -0
src/anopa/start-stop.c +768 -0
src/anopa/start-stop.h +91 -0
src/anopa/util.c +12 -0
src/anopa/util.h +7 -0
src/include/anopa/anopa.h +12 -0
src/include/anopa/common.h +10 -0
src/include/anopa/enable_service.h +30 -0
src/include/anopa/err.h +29 -0
src/include/anopa/ga_int_list.h +21 -0
src/include/anopa/init_repo.h +14 -0
src/include/anopa/output.h +34 -0
src/include/anopa/progress.h +18 -0
src/include/anopa/scan_dir.h +11 -0
src/include/anopa/service.h +87 -0
src/include/anopa/service_status.h +52 -0
src/include/anopa/stats.h +13 -0
src/libanopa/deps-lib/anopa +18 -0
src/libanopa/die_usage.c +18 -0
src/libanopa/die_version.c +22 -0
src/libanopa/enable_service.c +489 -0
src/libanopa/errmsg.c +23 -0
src/libanopa/exec_longrun.c +156 -0
src/libanopa/exec_oneshot.c +298 -0
src/libanopa/ga_int_list.c +47 -0
src/libanopa/init_repo.c +57 -0
src/libanopa/output.c +99 -0
src/libanopa/progress.c +188 -0
src/libanopa/sa_sources.c +5 -0
src/libanopa/scan_dir.c +69 -0
src/libanopa/service.c +517 -0
src/libanopa/service_internal.h +30 -0
src/libanopa/service_start.c +161 -0
src/libanopa/service_status.c +106 -0
src/libanopa/service_stop.c +37 -0
src/libanopa/services.c +13 -0
src/libanopa/stats.c +48 -0
tools/gen-deps.sh +83 -0
tools/install.sh +64 -0

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2a68ecb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*.swp
+*.bak
+*.o
+*.a
+*.lo
+*.so
+*.orig
+*.rej
+.dirstamp
+/package/deps.mak
+/config.mak
+/src/include/anopa/config.h
+/doc/*.1
+/libanopa.a
+/aa-enable
+/aa-start
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..7fc3726
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,131 @@
+#
+# This Makefile requires GNU make.
+#
+# Do not make changes here.
+# Use the included .mak files.
+#
+
+it: all
+
+CC = $(error Please use ./configure first)
+
+STATIC_LIBS :=
+SHARED_LIBS :=
+INTERNAL_LIBS :=
+EXTRA_TARGETS :=
+DOC_TARGETS :=
+
+-include config.mak
+include package/targets.mak
+include package/deps.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
+POD2MAN := pod2man
+
+ALL_BINS := $(LIBEXEC_TARGETS) $(BIN_TARGETS) $(SBIN_TARGETS)
+ALL_LIBS := $(SHARED_LIBS) $(STATIC_LIBS) $(INTERNAL_LIBS)
+ALL_INCLUDES := $(wildcard src/include/$(package)/*.h)
+
+all: $(ALL_LIBS) $(ALL_BINS) $(ALL_INCLUDES) $(DOC_TARGETS)
+
+clean:
+	@exec rm -f $(ALL_LIBS) $(ALL_BINS) $(wildcard src/*/*.o src/*/*.lo) $(EXTRA_TARGETS)
+
+distclean: clean
+	@exec rm -f config.mak src/include/${package}/config.h $(DOC_TARGETS)
+
+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 ..$(subst $(sproot),,$(exthome))/command/$(<F) $@
+
+$(DESTDIR)$(sproot)/library.so/lib%.so: $(DESTDIR)$(dynlibdir)/lib%.so
+	exec $(INSTALL) -D -l ..$(subst $(sproot),,$(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
+	exec $(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) $^ $(EXTRA_LIBS) $(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) $^
+
+%.1: %.pod
+	exec $(POD2MAN) --center="anopa" --section=1 --release=$(version) $< > $@
+
+.PHONY: it all clean distclean tgz strip install install-dynlib install-bin install-sbin install-lib install-include
+
+.DELETE_ON_ERROR:
diff --git a/configure b/configure
new file mode 100755
index 0000000..d8d62e8
--- /dev/null
+++ b/configure
@@ -0,0 +1,404 @@
+#!/bin/sh
+
+. package/info
+
+usage () {
+cat <<EOF
+Usage: $0 [OPTION]... [TARGET]
+
+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/$package]
+  --includedir=DIR              C header files [PREFIX/include]
+
+ If no --prefix option is given, by default libdir (but not dynlibdir) will be
+ /usr/lib/$package, and includedir will be /usr/include.
+
+Dependencies:
+  --with-sysdeps=DIR            use sysdeps in DIR [PREFIX/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
+
+ If no --prefix option is given, by default sysdeps will be fetched from
+ /usr/lib/skalibs/sysdeps.
+
+Optional features:
+  --enable-shared               build shared libraries [disabled]
+  --disable-static              do not build static libraries [enabled]
+  --disable-allstatic           do not prefer linking against static libraries [enabled]
+  --enable-static-libc          make entirely static binaries [disabled]
+  --enable-slashpackage[=ROOT]  assume /package installation at ROOT [disabled]
+  --enable-cross=CROSS          prefix toolchain executable names with CROSS [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
+
+CC_AUTO="$CC"
+CFLAGS_AUTO="$CFLAGS"
+CPPFLAGS_AUTO="-D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -O2 $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/lib/$package'
+includedir='$prefix/include'
+sysdeps='$prefix/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 ; evenmorestatic=false ;;
+    --enable-static-libc|--enable-static-libc=yes) allstatic=true ; 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
+
+# Add /usr in the default default case
+if test -z "$prefix" ; then
+  if test "$libdir" = '$prefix/lib/$package' ; then
+    libdir=/usr/lib/$package
+  fi
+  if test "$includedir" = '$prefix/include' ; then
+    includedir=/usr/include
+  fi
+  if test "$sysdeps" = '$prefix/lib/skalibs/sysdeps' ; then
+    sysdeps=/usr/lib/skalibs/sysdeps
+  fi
+fi
+
+# Expand installation directories
+stripdir prefix
+for i in exec_prefix dynlibdir libexecdir bindir sbindir libdir includedir 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
+
+if $evenmorestatic ; then
+  LDFLAGS_NOSHARED=-static
+fi
+
+if $shared ; then
+  tryldflag LDFLAGS_AUTO -Wl,--hash-style=both
+fi
+
+if test -z "$vpaths" ; then
+  while read dep ; do
+    base=$(basename $dep) ;
+    vpaths="$vpaths /usr/lib/$base"
+    addlibspath="$addlibspath -L/usr/lib/$base"
+  done < package/deps-build  
+fi
+
+CPPFLAGS_AUTO="$CPPFLAGS_AUTO $addincpath"
+LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibspath"
+$allstatic || LDFLAGS_AUTO="$LDFLAGS_AUTO $addlibdpath"
+
+echo "creating config.mak..."
+cmdline=$(quote "$0")
+for i ; do cmdline="$cmdline $(quote "$i")" ; done
+exec 3>&1 1>config.mak
+cat << EOF
+# This file was generated by:
+# $cmdline
+# Any changes made here will be lost if configure is re-run.
+
+target := $target
+package := $package
+prefix := $prefix
+exec_prefix := $exec_prefix
+dynlibdir := $dynlibdir
+libexecdir := $libexecdir
+bindir := $bindir
+sbindir := $sbindir
+libdir := $libdir
+includedir := $includedir
+sysdeps := $sysdeps
+slashpackage := $slashpackage
+sproot := $sproot
+version := $version
+home := $home
+exthome := $exthome
+RT_LIB := ${rt_lib}
+SOCKET_LIB := ${socket_lib}
+SYSCLOCK_LIB := ${sysclock_lib}
+TAINNOW_LIB := ${tainnow_lib}
+UTIL_LIB := ${util_lib}
+
+CC := $CC_AUTO
+CFLAGS := $CFLAGS_AUTO
+CPPFLAGS := $CPPFLAGS_AUTO
+LDFLAGS := $LDFLAGS_AUTO
+LDFLAGS_NOSHARED := $LDFLAGS_NOSHARED
+CROSS_COMPILE := $cross
+
+vpath lib%a$vpaths
+EOF
+if $allstatic ; then
+  echo ".LIBPATTERNS := lib%.a"
+  echo "DO_ALLSTATIC := 1"
+  vpathd=
+fi
+  echo "vpath lib%.so$vpathd"
+if $static ; then
+  echo "DO_STATIC := 1"
+else
+  echo "DO_STATIC :="
+fi
+if $shared ; then
+  echo "DO_SHARED := 1"
+else
+  echo "DO_SHARED :="
+fi
+
+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/aa-enable.pod b/doc/aa-enable.pod
new file mode 100644
index 0000000..74f1c7f
--- /dev/null
+++ b/doc/aa-enable.pod
@@ -0,0 +1,157 @@
+=head1 NAME
+
+aa-enable - Enable services, i.e. copy servicedirs to repodir
+
+=head1 SYNOPSIS
+
+B<aa-enable> [B<-D>] [B<-r> I<repodir>] [B<-S> I<sourcedir>]
+[B<-s> I<sourcedir>] [B<-l> I<listdir>] [B<-n>] [B<-w>] [I<service...>]
+
+=head1 OPTIONS
+
+=over
+
+=item B<-D, --double-output>
+
+Enable double-output mode. Instead of using stdout for regular output, and
+stderr for warnings and errors, everything is sent both to stdout and stderr.
+This is intended to redirect stderr to a log file, so full output can be both
+shown on console and logged.
+
+=item B<-r, --repodir> I<dir>
+
+Use I<dir> as repository directory. This is where all servicedirs will be
+created/copied to.
+
+=item B<-S, --reset-source> I<dir>
+
+Reset the list of source directories to I<dir>. You can add more with
+B<--source>. This is useful to unset the defaults.
+
+=item B<-s, --source> I<dir>
+
+Add I<dir> as new source. Can be specified multiple times; Source directories
+will be processed in the order they were added.
+
+=item B<-l, --listdir> I<dir>
+
+Use I<dir> to list services to enable. Only one can be set, if specified more
+than once the last one will be used.
+
+=item B<-n, --no-needs>
+
+Don't auto-enable any services listed under directory I<needs> of a service
+being (auto-)enabled.
+
+=item B<-w, --no-wants>
+
+Don't auto-enable any services listed under directory I<wants> of a service
+being (auto-)enabled.
+
+=item B<-h, --help>
+
+Show help screen and exit.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+With B<anopa>, you usually create your service repository early on boot (during
+stage 1) into tmpfs (I</run/services>). This ensures that no previous
+state/files from an earlier boot will interfere with the current one, leaving
+your main filesystem untouched (it could even be read-only).
+
+By default the repository directory used is I</run/services> and source
+directories are, in order, I</etc/anopa/services> and I</usr/lib/services>
+
+Also note that by default B<aa-enable>(1) will auto-enable any service whose
+name was found in a directory I<need> or I<wants> of a created servicedir. This
+can be turned off via B<--no-needs> and/or B<--no-wants> if needed.
+
+=head2 Enabling a service / Creating a servicedir
+
+Instead of simply creating the repository ahead of time, and copying it all on
+boot, the copying process is done through B<aa-enable>(1) which works as such:
+
+- The given I<listdir> is read. It contains either empty regular files, or
+directories, whose name is the name of a service to enable.
+
+- Source directories are tried, in order, to find the corresponding servicedir,
+which is then copied over to the runtime repository (I<repodir>). During this
+copy, B<aa-enable>(1) notes whether or not a file I<run> exists, i.e. whether
+this is a long-run or one-shot service.
+
+- If the service name came from a directory in I<listdir>, its content is then
+merged/copied over into the servicedir. This allows to specify service-specific
+configuration, or could possibly be used to overwrite an actual script.
+
+- If the service is a long-run, an empty regular file I<down> is created (unless
+it already existed) to ensure the service won't be auto-started by B<s6-svscan>,
+and a symlink is added to the servicedir into the I<.scandir> sub-directory of
+I<repodir>.
+
+=head2 Special case: Instances
+
+Service names cannot start nor end with a @ character, but can contain one.
+servicedirs in source directories shall not contain a @ character, unless it is
+the last character in their name.
+
+The idea is to use the notion of "instances." Imagine a service that starts a
+getty on a tty. Instead of needing to write as many servicedirs as you want
+gettys on ttys, you simply write one, named I<getty@>
+
+Then, you can enable I<getty@tty1> and I<getty@tty2>. For the service name
+I<getty@tty1>, we say the service used in I<getty> for instance I<tty1>. The way
+it works in B<aa-enable>(1) is that it will look for a servicedir I<getty@> in
+source directories, but will copy it as I<getty@tty1> into the repository.
+
+Then, in your run/finish/start/stop script(s), you can e.g. use B<aa_service>(1)
+to get the service & instance and use them as needed.
+
+Additionally, whenever copying content of the directories I<needs>, I<wants>,
+I<after> and I<before>, B<aa-enable>(1) will automatically rename any file whose
+name ends with a @ to append the current instance name.
+
+Therefore, if servicedir I<getty@> contained a file named I<set-numlock@> into
+on of those, it would be copied as e.g. I<set-numock@tty1> into the repository.
+
+=head2 Special case: service logger
+
+Long-run services are likely to be logged, and to do so you need to provide a
+servicedir I<log> inside the servicedir of the service.
+
+To simplify things, if a regular file or directory I<log> is found in a
+servicedir, B<aa-enable>(1) will copy the servicedir I<log>, taken for source
+directories as usual, into the servicedir of the service. And in case of a
+directory, its content will then be merge/copied over.
+
+This allows you to use the service/servicedir I<log> as logger for a service by
+simply putting an empty regular file I<log> into the original servicedir (in a
+source directory); Or use a directory to simply specify/add configuration files.
+
+Note that you can also still provide files from the configuration directory in
+listdir, since it is copied last (and that this always includes providing a new
+I<run> file).
+
+=head1 BUGS
+
+They're probably crawling somewhere in there... if you happen to catch one,
+(or more) report it and I'll do my best to squash it.
+
+=head1 REPOSITORY
+
+You can find the latest source code of B<anopa> as well as report bugs and/or
+suggest features on its GitHub repository, available at
+L<https://github.com/jjk-jacky/anopa>
+
+=head1 AUTHORS
+
+=over
+
+=item Olivier Brunel <jjk@jjacky.com>
+
+=back
diff --git a/doc/anopa.pod b/doc/anopa.pod
new file mode 100644
index 0000000..9efe4bf
--- /dev/null
+++ b/doc/anopa.pod
@@ -0,0 +1,335 @@
+=head1 NAME
+
+anopa - init system/service manager build around s6 supervision suite
+
+=head1 DESCRIPTION
+
+anopa is an collection of tools and scripts aimed to provide an init system and
+service manager for Linux systems, based around the s6 supervision suite[B<1>].
+
+It provides some execline[B<2>] scripts that can be used as init for different
+stage of the boot process, leaving stage 2 to be handled by B<s6-svscan>, as
+well as tools that can be used to create a runtime repository of servicedirs,
+start/stop them and other related functions.
+
+=head1 WHAT DOES INIT (PID 1) DO ?
+
+Paraphrasing B<s6>'s author, Laurent Bercot, there are really three stages for
+the init process of a Unix system :
+
+=over
+
+=item Stage 1: Booting up phase
+
+=item Stage 2: Supervision phase
+
+=item Stage 3: Shutting down phase
+
+=back
+
+Stage 1 starts when the kernel launches the first userland process,
+traditionally called B<init>. The goal is to prepare the system: mounting
+filesystems, setting system clock, configuring the network, and other similar
+tasks are to be performed, to be ready to start long-running processes, that
+will be expected to stay running for as long as the system is up.
+
+Stage 2 starts with the launch of all long-running processes (getty-s, ssh
+server, etc) and continues - as you're using the system - with supervising said
+processes, making sure they stay up, and restarting them should they not. init's
+job here also includes reaping any orphaned zombies.
+
+Stage 3 starts when a shutdown (or reboot) is initiated. init then needs to
+"undo" what was done prior: stop all long-running processes, clean up, unmount
+filesystems, etc
+
+B<s6-svscan> is perfectly suited to be in charge of stage 2, supervising
+long-running processes. B<anopa> provides tools to take care of the other
+stages.
+
+Because the init process launched by the kernel, also known as PID 1, cannot die
+and has to be the one process to exist from start to finish, lots of init system
+include all functionnalities into a single binary. Instead, since PID 1 is
+allowed to execute I<into> another binary, this is what B<anopa> makes uses of.
+
+=head1 NOTION OF SERVICES
+
+B<s6> works with services, defined under what's called a service directory, or
+I<servicedir>. Since it is build around it, B<anopa> uses the same principle;
+However, a service from B<anopa>'s point-of-view can either be one-shot, i.e. a
+process to start on boot (during stage 1) and that's it (possibly with a
+counter-part one to be run on shutdown, during stage 3), or a long-run, i.e. a
+process to be started during phase 2, and to stay up until stage 3 is initiated.
+
+Therefore, you can have two kinds of services, and servicedir: one-shot, and
+long-run. A servicedir is simply a directory, in which the definition of the
+service is held.
+
+Long-run services requires a file I<run> to be present, which will be the
+long-running process launched & supervised (or, more likely, is a small script
+executing into said process). Therefore, the rule is that if a servicedir
+contains a file I<run> it is of a long-run service, else of a one-shot one.
+
+=head2 LONG-RUN SERVICES
+
+Long-run services will be launched & supervised by B<s6-svscan>, therefore you
+should refer to its official documentation[B<3>] when it comes to that, but such
+servicedirs contain:
+
+=over
+
+=item An executable file named I<run>
+
+It can be any executable file (binary or script), but usually will be a small
+script containing the command setting up the services, before executing into it.
+
+=item An optional executable file named I<finish>
+
+Like I<run>, it can be any executable file. If present, it is executed everytime
+the I<run> script dies. Generally, its main purpose is to clean up non-volatile
+data such as the filesystem after the supervised process has been killed.
+
+Note that it must do its work and exit in less than 5 seconds, else it'll be
+killed.
+
+=item An optional, empty, regular file named I<nosetsid>
+
+If such a file exists, B<s6-supervise> will not make the service a process group
+and session leader; the service will be run in the same process group as
+B<s6-supervise>. If no I<nosetsid> file exists, the service has its own process
+group and is started as a session leader.
+
+=item An optional service directory named I<log>
+
+If it exists then two services will be monitored: the actual service (I<./run>)
+as well as its logger (I<./log/run>).
+A pipe is open and maintained between the two, i.e. everything that I<./run>
+writes to its stdout will appear on I<./log/run>'s stdin. The service is said to
+be logged; the I<log> service is called the service's logger.
+
+=back
+
+For completeness, the following "internals" are also supported.
+
+=over
+
+=item A directory named I<supervise>
+
+It is automatically created by B<s6-supervise> if it does not exist. This is
+where B<s6-supervise> stores its information. The directory must be writable.
+
+=item A fifodir named I<event>
+
+It is automatically created by B<s6-supervise> if it does not exist. This is
+used to send notifications when the service goes up/down.
+
+=item An optional, empty, regular file named I<down>
+
+If such a file exists, the default state of the service is considered down, not
+up, and it isn't automatically started by B<s6-supervise>.
+
+=back
+
+=head2 ONE-SHOT SERVICES
+
+One-shot services are simply binaries that will be executed by B<aa-start>(1) or
+B<aa-stop>(1). Such servicedirs contain:
+
+=over
+
+=item An optional executable file named I<start>
+
+It can be any executable file (binary or script), that will be executed by
+B<aa-start> when starting the service. It if exits with 0 the service will be
+considered started, else failed.
+If no such file exists, the service will be considered started instantly.
+
+=item An optional executable file named I<stop>
+
+It can be any executable file (binary or script), that will be executed by
+B<aa-stop> when stopping the service. It if exits with 0 the service will be
+considered stopped, else stopped-failed.
+If no such file exists, the service will be considered stopped instantly.
+
+=item An optional, empty, regular file names I<essential>
+
+If present and the service fails to be started, when it exits B<aa-start> will
+return 1. It will only return 0 if no essential services failed to be started.
+This can be used by I<init>, e.g. to launch an emergency shell if B<aa-start>
+end succesfully (return non-zero).
+
+You would usually use such a file for the service mounting the root filesystem
+in initramfs, or launching getty.
+
+=back
+
+=head2 SERVICE DEPENDENCIES
+
+Common to long-run and one-shot services, therefore supported in both types of
+servicedirs, are the notion of dependencies and order supported by B<anopa>.
+
+The idea is that on boot, all long-run servicedirs will actually contain an
+empty file I<down> so that they aren't automatically started by B<s6-svscan>.
+Instead, B<aa-start>(1) will be used to start everything (during stage 2), both
+one-shot and long-run services; It will also be used by B<aa-stop>(1) to order
+stopping.
+
+Four additional directories can be found inside servicedirs, used by
+B<aa-start>(1) and B<aa-stop>(1) to determine when to start/stop what.
+
+=over
+
+=item An optional directory named I<needs>
+
+This directory can contain empty regular files, whose name is the name of a
+service to be marked as dependency of the current service. Such services will be
+auto-started by B<aa-start>(1) and the current service will automatically be
+marked to be started after it.
+
+Additionally, should the depedency service fail to start, the current service
+will not be started, but placed into a failed state (for dependency error).
+
+B<aa-stop>(1) will process those as if they were in directory I<after>.
+
+=item An optional directory named I<wants>
+
+This directory can contain empty regular files, whose name is the name of a
+service to be auto-started by B<aa-start>(1). This is not a dependency, and
+there's no ordering associated either.
+
+This is ignored by B<aa-stop>(1).
+
+=item An optional directory named I<after>
+
+This directory can contain empty regular files. The current service will only be
+started after those services have been started by B<aa-start>(1); And the other
+way around for B<aa-stop>(1), i.e. those services will be stopped after the
+current service has been.
+
+It is important to note that this is only an ordering directive, i.e. if the
+service isn't actually being started, then it is simply ignored. And if it is,
+but fails to do so, the current service will still be started regardless.
+
+=item An optional directory named I<before>
+
+This directory can contain empty regular files. If those services are being
+started, then they'll only be started after the current service by
+B<aa-start>(1); And the other way around for B<aa-stop>(1), i.e. the current
+service will be stopped after they have been.
+
+It is important to note that this is only an ordering directive, i.e. if the
+service isn't being started then it is simply ignored. And if it is, but the
+current service fails to start, it will still be started regardless.
+
+=back
+
+It should also be noted that a long-run service will be considered started once
+the event 'u' is received; Should the service actually fail right after will
+have no consequences on the rest of the B<aa-start>(1) process.
+
+=head1 SERVICE REPOSITORY (AND SCANDIR)
+
+B<s6-svscan> will supervise all services found in its scandir. Obviously,
+because it has no notion of one-shot services, only servicedir for long-run
+services should be found there.
+
+The way this works in B<anopa> is to use B<aa-enable>(1) to create the service
+repository, as well as the scandir. This is done by using source directories,
+and optionally merging in configuration directories.
+The idea is that you will not create your service repository manually, but
+instead using B<aa-enable>(1) from a pre-established definition.
+
+The service repository will contain all (enabled) servicedirs, both one-shots
+and long-runs. For long-run servicedirs, symlinks will be put into directory
+I<.scandir> which will be used by B<s6-svscan> as its scandir.
+
+A typical organization would work like the following :
+
+=over
+
+=item I</usr/lib/services>
+
+Source directory containing available servicedirs. This is where servicedirs
+from packages would be installed. Used by B<aa-enable>(1).
+
+=item I</etc/anopa/services>
+
+Source directory containing available servicedirs. This is where the
+administrator can define its own servicedirs, either because they're not
+provided by packages, or to be used instead. Used by B<aa-enable>(1).
+
+=item I</etc/anopa/enabled>
+
+List directory containing either empty regular files, whose name is the name of
+a service to enable on boot, or directories, whose name is the name of a service
+to enable on boot.
+
+In the later case, the content of the folder will also be merged/copied over
+into the servicedir.
+
+=item I</etc/anopa/start/default>
+
+List directory containing empty regular files, whose name is the name of a
+service to start on boot.
+
+=item I</run/services>
+
+Runtime repository, the service repository for the current system, containing a
+directory I<.scandir>, with symlinks for all long-run services, used by
+B<s6-svscan> as its scandir.
+
+=back
+
+On boot, B<aa-enable>(1) is used to create the runtime repository. To do so, it
+reads the content of the listdir, and for each service named there copies the
+corresponding servicedir from a source directory into the runtime repository. If
+listdir contained a directory, its content is then merged/copied over into the
+newlu created servicedir, allowing easy custom service-specific configuration.
+
+Source directories are looked up in order, thus allowing the administrator to
+provide not only its own servicedirs, but its own version of a given servicedir.
+
+Refer to B<aa-enable>(1) for more on how it works and how the copy operation
+takes place.
+
+
+
+
+
+
+=head1 LINKS
+
+=over
+
+=item [B<1>] s6 supervision suite
+
+L<http://skarnet.org/software/s6/>
+
+=item [B<2>] execline
+
+L<http://skarnet.org/software/execline/>
+
+=item [B<3>] s6 service directories
+
+L<http://skarnet.org/software/s6/servicedir.html>
+
+=back
+
+=head1 BUGS
+
+They're probably crawling somewhere in there... if you happen to catch one,
+(or more) report it and I'll do my best to squash it.
+
+=head1 REPOSITORY
+
+You can find the latest source code of B<anopa> as well as report bugs and/or
+suggest features on its GitHub repository, available at
+L<https://github.com/jjk-jacky/anopa>; or visit its official website at
+L<http://jjacky.com/anopa>
+
+=head1 AUTHORS
+
+=over
+
+=item Olivier Brunel <jjk@jjacky.com>
+
+=back
diff --git a/package/deps-build b/package/deps-build
new file mode 100644
index 0000000..e55e3f7
--- /dev/null
+++ b/package/deps-build
@@ -0,0 +1,3 @@
+/package/admin/s6
+/package/prog/skalibs
+/package/admin/execline
diff --git a/package/info b/package/info
new file mode 100644
index 0000000..5f3a600
--- /dev/null
+++ b/package/info
@@ -0,0 +1,4 @@
+package=anopa
+version=0.1.0
+category=admin
+package_macro_name=ANOPA
diff --git a/package/modes b/package/modes
new file mode 100644
index 0000000..7bb2c14
--- /dev/null
+++ b/package/modes
@@ -0,0 +1,2 @@
+aa-start                0755
+aa-enable               0755
diff --git a/package/targets.mak b/package/targets.mak
new file mode 100644
index 0000000..f829a40
--- /dev/null
+++ b/package/targets.mak
@@ -0,0 +1,21 @@
+BIN_TARGETS := \
+aa-start \
+aa-enable
+
+DOC_TARGETS := \
+doc/anopa.1 \
+doc/aa-enable.1
+
+ifdef DO_ALLSTATIC
+LIBANOPA := libanopa.a
+else
+LIBANOPA := libanopa.so
+endif
+
+ifdef DO_SHARED
+SHARED_LIBS := libanopa.so
+endif
+
+ifdef DO_STATIC
+STATIC_LIBS := libanopa.a
+endif
diff --git a/src/anopa/aa-enable.c b/src/anopa/aa-enable.c
new file mode 100644
index 0000000..b0d4447
--- /dev/null
+++ b/src/anopa/aa-enable.c
@@ -0,0 +1,271 @@
+
+#define _BSD_SOURCE
+#define _GNU_SOURCE
+
+#include "anopa/config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/direntry.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/error.h>
+#include <skalibs/uint.h>
+#include <skalibs/skamisc.h>
+#include <anopa/common.h>
+#include <anopa/output.h>
+#include <anopa/init_repo.h>
+#include <anopa/scan_dir.h>
+#include <anopa/enable_service.h>
+#include <anopa/ga_int_list.h>
+#include <anopa/stats.h>
+#include <anopa/err.h>
+#include "util.h"
+
+
+#define SOURCE_ETC      "/etc/anopa/services"
+#define SOURCE_USR      "/usr/lib/services"
+
+static aa_enable_flags flags = AA_FLAG_AUTO_ENABLE_NEEDS | AA_FLAG_AUTO_ENABLE_WANTS;
+static stralloc sa_pl = STRALLOC_ZERO;
+static const char *cur_name = NULL;
+static stralloc names = STRALLOC_ZERO;
+static int nb_enabled = 0;
+static genalloc ga_failed = GENALLOC_ZERO;
+static genalloc ga_next = GENALLOC_ZERO;
+
+static void
+warn_cb (const char *name, int err)
+{
+    aa_put_warn (cur_name, name, 0);
+    aa_bs_noflush (AA_ERR, ": ");
+    aa_bs_noflush (AA_ERR, error_str (err));
+    aa_end_warn ();
+}
+
+static void
+ae_cb (const char *name, aa_enable_flags type)
+{
+    int i;
+
+    for (i = 0; i < names.len; i += strlen (names.s + i) + 1)
+        if (str_equal (name, names.s + i))
+            return;
+
+    genalloc_append (int, &ga_next, &names.len);
+    stralloc_catb (&names, name, strlen (name) + 1);
+}
+
+static int
+enable_service (const char *name, int from_next)
+{
+    int offset;
+    int r;
+    int i;
+
+    if (*name == '/')
+        cur_name = name + byte_rchr (name, strlen (name), '/') + 1;
+    else
+        cur_name = name;
+
+    if (!from_next)
+    {
+        /* skip what's already planned to be done next (added via auto-enable) */
+        for (i = 0; i < genalloc_len (int, &ga_next); ++i)
+            if (str_equal (cur_name, names.s + list_get (&ga_next, i)))
+                return 0;
+
+        offset = names.len;
+        stralloc_catb (&names, cur_name, strlen (cur_name) + 1);
+    }
+    else
+        offset = from_next - 1;
+
+    r = aa_enable_service (name, warn_cb, flags, ae_cb);
+    if (r < 0)
+    {
+        int e = errno;
+
+        aa_put_err (cur_name, errmsg[-r], r != -ERR_IO);
+        if (r == -ERR_IO)
+        {
+            aa_bs_noflush (AA_ERR, ": ");
+            aa_bs_noflush (AA_ERR, error_str (e));
+            aa_end_err ();
+        }
+
+        genalloc_append (int, &ga_failed, &offset);
+        cur_name = NULL;
+        return -1;
+    }
+
+    ++nb_enabled;
+    cur_name = NULL;
+    return 0;
+}
+
+static int
+it_list (direntry *d, void *data)
+{
+    if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN)
+        enable_service (d->d_name, 0);
+    else
+    {
+        int l;
+
+        l = sa_pl.len;
+        sa_pl.s[l - 1] = '/';
+        stralloc_catb (&sa_pl, d->d_name, str_len (d->d_name) + 1);
+        enable_service (sa_pl.s, 0);
+        sa_pl.len = l;
+        sa_pl.s[l - 1] = '\0';
+    }
+    return 0;
+}
+
+static void
+dieusage (void)
+{
+    aa_die_usage ("[OPTION...] [service...]",
+            " -D, --double-output           Enable double-output mode\n"
+            " -r, --repodir DIR             Use DIR as repository directory\n"
+            " -S, --reset-source DIR        Reset list of source directories to DIR\n"
+            " -s, --source DIR              Add DIR as source directories\n"
+            " -l, --listdir DIR             Use DIR to list services to enable\n"
+            " -n, --no-needs                Don't auto-enable services from 'needs'\n"
+            " -w, --no-wants                Don't auto-enable services from 'wants'\n"
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "aa-enable";
+    const char *path_repo = "/run/services";
+    const char *path_list = NULL;
+    int mode_both = 0;
+    int i;
+    int r;
+
+    if (!stralloc_catb (&aa_sa_sources, SOURCE_ETC, sizeof (SOURCE_ETC)))
+        strerr_diefu1sys (1, "stralloc_catb");
+    if (!stralloc_catb (&aa_sa_sources, SOURCE_USR, sizeof (SOURCE_USR)))
+        strerr_diefu1sys (1, "stralloc_catb");
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "version",            no_argument,        NULL,   'V' },
+            { "double-output",      no_argument,        NULL,   'D' },
+            { "repodir",            required_argument,  NULL,   'r' },
+            { "listdir",            required_argument,  NULL,   'l' },
+            { "source",             required_argument,  NULL,   's' },
+            { "reset-source",       required_argument,  NULL,   'S' },
+            { "no-needs",           no_argument,        NULL,   'n' },
+            { "no-wants",           no_argument,        NULL,   'w' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "hVDr:l:s:S:nw", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'D':
+                mode_both = 1;
+                break;
+
+            case 'r':
+                unslash (optarg);
+                path_repo = optarg;
+                break;
+
+            case 'l':
+                unslash (optarg);
+                path_list = optarg;
+                break;
+
+            case 'S':
+                aa_sa_sources.len = 0;
+                /* fall through */
+
+            case 's':
+                unslash (optarg);
+                if (!stralloc_catb (&aa_sa_sources, optarg, strlen (optarg) + 1))
+                    strerr_diefu1sys (1, "stralloc_catb");
+                break;
+
+            case 'n':
+                flags &= ~AA_FLAG_AUTO_ENABLE_NEEDS;
+                break;
+
+            case 'w':
+                flags &= ~AA_FLAG_AUTO_ENABLE_WANTS;
+                break;
+
+            case 'V':
+                aa_die_version ();
+
+            default:
+                dieusage ();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    aa_init_output (mode_both);
+
+    if (!path_list && argc < 1)
+        dieusage ();
+
+    r = aa_init_repo (path_repo, AA_REPO_CREATE);
+    if (r < 0)
+    {
+        if (r == -ERR_IO_REPODIR)
+            strerr_diefu2sys (1, "create repository ", path_repo);
+        else if (r == -ERR_IO_SCANDIR)
+            strerr_diefu3sys (1, "create scandir ", path_repo, "/" AA_SCANDIR_DIRNAME);
+        else
+            strerr_diefu2sys (1, "init repository ", path_repo);
+    }
+
+    if (path_list)
+    {
+        stralloc_catb (&sa_pl, path_list, strlen (path_list) + 1);
+        r = aa_scan_dir (&sa_pl, 0, it_list, NULL);
+        if (r < 0)
+            strerr_diefu2sys (-r, "read list directory ", path_list);
+    }
+
+    for (i = 0; i < argc; ++i)
+        enable_service (argv[i], 0);
+
+    while (genalloc_len (int, &ga_next) > 0)
+    {
+        int offset;
+
+        i = genalloc_len (int, &ga_next) - 1;
+        offset = list_get (&ga_next, i);
+        genalloc_setlen (int, &ga_next, i);
+        enable_service (names.s + offset, 1 + offset);
+    }
+
+    aa_put_title (1, PROG, "Completed", 1);
+    aa_show_stat_nb (nb_enabled, "Enabled", ANSI_HIGHLIGHT_GREEN_ON);
+    aa_show_stat_names (names.s, &ga_failed, "Failed", ANSI_HIGHLIGHT_RED_ON);
+
+    genalloc_free (int, &ga_failed);
+    genalloc_free (int, &ga_next);
+    stralloc_free (&sa_pl);
+    stralloc_free (&names);
+    return 0;
+}
diff --git a/src/anopa/aa-start.c b/src/anopa/aa-start.c
new file mode 100644
index 0000000..4542f90
--- /dev/null
+++ b/src/anopa/aa-start.c
@@ -0,0 +1,312 @@
+
+#define _BSD_SOURCE
+
+#include <locale.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <langinfo.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/direntry.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/error.h>
+#include <skalibs/tai.h>
+#include <skalibs/iopause.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/uint16.h>
+#include <skalibs/uint.h>
+#include <anopa/common.h>
+#include <anopa/err.h>
+#include <anopa/init_repo.h>
+#include <anopa/output.h>
+#include <anopa/scan_dir.h>
+#include <anopa/ga_int_list.h>
+#include <anopa/service_status.h>
+#include <anopa/service.h>
+#include <anopa/progress.h>
+#include <anopa/stats.h>
+#include "start-stop.h"
+#include "util.h"
+
+
+#define ESSENTIAL_FILENAME      "essential"
+
+static genalloc ga_depend = GENALLOC_ZERO;
+static genalloc ga_skipped = GENALLOC_ZERO;
+static genalloc ga_unknown = GENALLOC_ZERO;
+static genalloc ga_io = GENALLOC_ZERO;
+static int no_wants = 0;
+static int rc = 0;
+
+void
+check_essential (int si)
+{
+    if (rc == 0)
+    {
+        struct stat st;
+        const char *name = aa_service_name (aa_service (si));
+        int l_name = strlen (name);
+        char buf[l_name + 1 + sizeof (ESSENTIAL_FILENAME)];
+
+        byte_copy (buf, l_name, name);
+        byte_copy (buf + l_name, 1 + sizeof (ESSENTIAL_FILENAME), "/" ESSENTIAL_FILENAME);
+
+        if (stat (buf, &st) < 0)
+        {
+            if (errno != ENOENT)
+            {
+                int e = errno;
+                put_warn (name, "Failed to stat " ESSENTIAL_FILENAME ": ", 0);
+                add_warn (error_str (e));
+                end_warn ();
+            }
+        }
+        else
+            rc = 1;
+    }
+}
+
+static void
+load_fail_cb (int si, aa_lf lf, const char *name, int err)
+{
+    if (lf == AA_LOADFAIL_WANTS)
+    {
+        put_warn (aa_service_name (aa_service (si)), "Skipping wanted service ", 0);
+        add_warn (name);
+        add_warn (": ");
+        add_warn (errmsg[err]);
+        end_warn ();
+        add_name_to_ga (name, &ga_skipped);
+    }
+}
+
+static int
+add_service (const char *name)
+{
+    int si = -1;
+    int type;
+    int r;
+
+    type = aa_get_service (name, &si, 1);
+    if (type < 0)
+        r = type;
+    else
+        r = aa_mark_service (si, type == AA_SERVICE_FROM_MAIN, no_wants, load_fail_cb);
+    if (r < 0)
+    {
+        if (r == -ERR_UNKNOWN)
+        {
+            put_err_service (name, ERR_UNKNOWN, 1);
+            add_name_to_ga (name, &ga_unknown);
+        }
+        else if (r == -ERR_IO)
+        {
+            /* ERR_IO from aa_get_service() means we don't have a si (it is
+             * actually set to the return value); but from aa_mark_service()
+             * (e.g. to read "needs") then we do */
+            if (si < 0)
+            {
+                put_err_service (name, ERR_IO, 1);
+                add_name_to_ga (name, &ga_io);
+            }
+            else
+            {
+                int e = errno;
+
+                put_err_service (name, ERR_IO, 0);
+                add_err (": ");
+                add_err (error_str (e));
+                end_err ();
+
+                genalloc_append (int, &ga_failed, &si);
+                check_essential (si);
+            }
+        }
+        else if (r == -ERR_ALREADY_UP)
+        {
+            put_title (1, name, errmsg[-r], 1);
+            ++nb_already;
+            r = 0;
+        }
+        else
+        {
+            aa_service *s = aa_service (si);
+            const char *msg = aa_service_status_get_msg (&s->st);
+            int has_msg;
+
+            has_msg = s->st.event == AA_EVT_ERROR && s->st.code == -r && !!msg;
+            put_err_service (name, -r, !has_msg);
+            if (has_msg)
+            {
+                add_err (": ");
+                add_err (msg);
+                end_err ();
+            }
+
+            if (r == -ERR_DEPEND)
+                genalloc_append (int, &ga_depend, &si);
+            else
+                genalloc_append (int, &ga_failed, &si);
+            check_essential (si);
+        }
+    }
+    else
+        r = 0;
+
+    return r;
+}
+
+static int
+it_start (direntry *d, void *data)
+{
+    tain_now_g ();
+    add_service (d->d_name);
+    return 0;
+}
+
+static void
+scan_cb (int si, int sni)
+{
+    put_err_service (aa_service_name (aa_service (si)), ERR_DEPEND, 0);
+    add_err (": ");
+    add_err (aa_service_name (aa_service (sni)));
+    end_err ();
+    genalloc_append (int, &ga_depend, &si);
+    check_essential (si);
+}
+
+static void
+dieusage (void)
+{
+    aa_die_usage ("[OPTION...] [service...]",
+            " -D, --double-output           Enable double-output mode\n"
+            " -r, --repodir DIR             Use DIR as repository directory\n"
+            " -l, --listdir DIR             Use DIR to list services to start\n"
+            " -w, --no-wants                Don't auto-start services from 'wants'\n"
+            " -h, --help                    Show this help screen and exit\n"
+            " -V, --version                 Show version information and exit\n"
+            );
+}
+
+static void
+close_fd (int fd)
+{
+    close_fd_for (fd, -1);
+}
+
+int
+main (int argc, char * const argv[])
+{
+    PROG = "aa-start";
+    const char *path_repo = "/run/services";
+    const char *path_list = NULL;
+    int mode_both = 0;
+    int i;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "help",               no_argument,        NULL,   'h' },
+            { "version",            no_argument,        NULL,   'V' },
+            { "double-output",      no_argument,        NULL,   'D' },
+            { "repodir",            required_argument,  NULL,   'r' },
+            { "listdir",            required_argument,  NULL,   'l' },
+            { "no-wants",           no_argument,        NULL,   'w' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "hVDr:l:w", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'D':
+                mode_both = 1;
+                break;
+
+            case 'r':
+                unslash (optarg);
+                path_repo = optarg;
+                break;
+
+            case 'l':
+                unslash (optarg);
+                path_list = optarg;
+                break;
+
+            case 'w':
+                no_wants = 1;
+                break;
+
+            case 'V':
+                aa_die_version ();
+
+            default:
+                dieusage ();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    aa_init_output (mode_both);
+    cols = get_cols (1);
+    is_utf8 = is_locale_utf8 ();
+
+    if (!path_list && argc < 1)
+        dieusage ();
+
+    if (aa_init_repo (path_repo, AA_REPO_WRITE) < 0)
+        strerr_diefu2sys (ERR_IO, "init repository ", path_repo);
+
+    if (path_list)
+    {
+        stralloc sa = STRALLOC_ZERO;
+        int r;
+
+        stralloc_catb (&sa, path_list, strlen (path_list) + 1);
+        r = aa_scan_dir (&sa, 1, it_start, NULL);
+        stralloc_free (&sa);
+        if (r < 0)
+            strerr_diefu2sys (-r, "read list directory ", path_list);
+    }
+
+    tain_now_g ();
+
+    for (i = 0; i < argc; ++i)
+        add_service (argv[i]);
+
+    mainloop (AA_MODE_START, scan_cb);
+
+    aa_bs_noflush (AA_OUT, "\n");
+    put_title (1, PROG, "Completed.", 1);
+    aa_show_stat_nb (nb_already, "Already up", ANSI_HIGHLIGHT_GREEN_ON);
+    aa_show_stat_nb (nb_done, "Started", ANSI_HIGHLIGHT_GREEN_ON);
+    show_stat_service_names (&ga_failed, "Failed", ANSI_HIGHLIGHT_RED_ON);
+    show_stat_service_names (&ga_depend, "Dependency failed", ANSI_HIGHLIGHT_RED_ON);
+    aa_show_stat_names (aa_names.s, &ga_io, "I/O error", ANSI_HIGHLIGHT_RED_ON);
+    aa_show_stat_names (aa_names.s, &ga_unknown, "Unknown", ANSI_HIGHLIGHT_RED_ON);
+    aa_show_stat_names (aa_names.s, &ga_skipped, "Skipped", ANSI_HIGHLIGHT_YELLOW_ON);
+
+    genalloc_free (int, &ga_failed);
+    genalloc_free (int, &ga_depend);
+    genalloc_free (int, &ga_unknown);
+    genalloc_free (int, &ga_io);
+    genalloc_free (int, &ga_skipped);
+    genalloc_free (pid_t, &ga_pid);
+    genalloc_free (int, &aa_tmp_list);
+    genalloc_free (int, &aa_main_list);
+    stralloc_free (&aa_names);
+    genalloc_deepfree (struct progress, &ga_progress, free_progress);
+    aa_free_services (close_fd);
+    genalloc_free (iopause_fd, &ga_iop);
+    return rc;
+}
diff --git a/src/anopa/deps-exe/aa-enable b/src/anopa/deps-exe/aa-enable
new file mode 100644
index 0000000..315c9b7
--- /dev/null
+++ b/src/anopa/deps-exe/aa-enable
@@ -0,0 +1,4 @@
+util.o
+${LIBANOPA}
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/anopa/deps-exe/aa-start b/src/anopa/deps-exe/aa-start
new file mode 100644
index 0000000..3a71bd4
--- /dev/null
+++ b/src/anopa/deps-exe/aa-start
@@ -0,0 +1,6 @@
+util.o
+start-stop.o
+${LIBANOPA}
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/anopa/start-stop.c b/src/anopa/start-stop.c
new file mode 100644
index 0000000..6190b36
--- /dev/null
+++ b/src/anopa/start-stop.c
@@ -0,0 +1,768 @@
+
+#include <locale.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <langinfo.h>
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/tai.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/iopause.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/sig.h>
+#include <skalibs/uint.h>
+#include <skalibs/uint16.h>
+#include <skalibs/strerr2.h>
+#include <anopa/service.h>
+#include <anopa/ga_int_list.h>
+#include <anopa/output.h>
+#include <anopa/err.h>
+#include "start-stop.h"
+
+genalloc ga_iop = GENALLOC_ZERO;
+genalloc ga_progress = GENALLOC_ZERO;
+genalloc ga_pid = GENALLOC_ZERO;
+tain_t iol_deadline;
+unsigned int draw = 0;
+int nb_already = 0;
+int nb_done = 0;
+int nb_wait_longrun = 0;
+genalloc ga_failed = GENALLOC_ZERO;
+int cols = 80;
+int is_utf8 = 0;
+int ioloop = 1;
+
+/* aa-start.c */
+void check_essential (int si);
+
+void
+free_progress (struct progress *pg)
+{
+    aa_progress_free (&pg->aa_pg);
+}
+
+void
+clear_draw ()
+{
+    if (draw & DRAW_CUR_WAITING)
+    {
+        aa_is_flush (AA_OUT, ANSI_CLEAR_BEFORE ANSI_START_LINE);
+        draw &= ~DRAW_NEED_WAITING;
+    }
+
+    if (draw & DRAW_CUR_PROGRESS)
+    {
+        int i;
+
+        for (i = 0; i < genalloc_len (struct progress, &ga_progress); ++i)
+        {
+            struct progress *pg = &genalloc_s (struct progress, &ga_progress)[i];
+
+            if (pg->is_drawn)
+            {
+                aa_is_flush (AA_OUT, ANSI_PREV_LINE ANSI_CLEAR_AFTER);
+                pg->is_drawn = 0;
+            }
+        }
+    }
+
+    draw &= ~DRAW_HAS_CUR;
+}
+
+void
+draw_progress_for (int si)
+{
+    struct progress *pg;
+
+    pg = &genalloc_s (struct progress, &ga_progress)[aa_service (si)->pi];
+    aa_progress_draw (&pg->aa_pg, aa_service_name (aa_service (si)), cols, is_utf8);
+    pg->is_drawn = 1;
+    draw |= DRAW_CUR_PROGRESS;
+}
+
+void
+draw_waiting (int already_drawn)
+{
+    static int n = 0;
+    const char busy[] = "-\\|/";
+    int l_b = (sizeof (busy) / sizeof (*busy)) - 1;
+    char buf[UINT_FMT];
+    int nb;
+
+    if (already_drawn)
+        aa_is_noflush (AA_OUT, ANSI_CLEAR_BEFORE ANSI_START_LINE);
+
+    nb = nb_wait_longrun + genalloc_len (pid_t, &ga_pid);
+    aa_is_noflush (AA_OUT, "Waiting on ");
+    buf[uint_fmt (buf, nb)] = '\0';
+    aa_is_noflush (AA_OUT, buf);
+    aa_is_noflush (AA_OUT, " service");
+    aa_is_noflush (AA_OUT, ("s... " + ((nb > 1) ? 0 : 1)));
+
+    aa_ib_flush (AA_OUT, busy + n, 1);
+    if (++n >= l_b)
+        n = 0;
+
+    draw |= DRAW_CUR_WAITING;
+}
+
+void
+refresh_draw ()
+{
+    unsigned int old_draw = draw;
+
+    if ((!(draw & DRAW_NEED_WAITING) && (draw & DRAW_CUR_WAITING))
+            || (draw & DRAW_CUR_PROGRESS))
+    {
+        clear_draw ();
+        if (old_draw & DRAW_NEED_WAITING)
+            draw |= DRAW_NEED_WAITING;
+    }
+
+    if (draw & DRAW_NEED_PROGRESS)
+    {
+        int i;
+
+        for (i = 0; i < genalloc_len (struct progress, &ga_progress); ++i)
+        {
+            struct progress *pg = &genalloc_s (struct progress, &ga_progress)[i];
+
+            if (pg->si >= 0)
+                draw_progress_for (pg->si);
+        }
+
+        if (!(draw & DRAW_CUR_PROGRESS))
+            draw &= ~DRAW_NEED_PROGRESS;
+    }
+
+    if (draw & DRAW_NEED_WAITING)
+        draw_waiting ((old_draw & DRAW_CUR_WAITING) && !(draw & DRAW_CUR_PROGRESS));
+
+    if (draw & DRAW_CUR_WAITING)
+    {
+        tain_t t;
+
+        tain_from_millisecs (&t, 108);
+        tain_add_g (&iol_deadline, &t);
+    }
+    else
+        iol_deadline_addsec (TIMEOUT_SECS);
+}
+
+void
+add_name_to_ga (const char *name, genalloc *ga)
+{
+    int offset = aa_add_name (name);
+    genalloc_append (int, ga, &offset);
+}
+
+void
+iol_deadline_addsec (int n)
+{
+    tain_addsec_g (&iol_deadline, n);
+}
+
+void
+remove_fd_from_iop (int fd)
+{
+    int i;
+
+    for (i = 0; i < genalloc_len (iopause_fd, &ga_iop); ++i)
+        if (genalloc_s (iopause_fd, &ga_iop)[i].fd == fd)
+        {
+            ga_remove (iopause_fd, &ga_iop, i);
+            break;
+        }
+}
+
+void
+close_fd_for (int fd, int si)
+{
+    if (si < 0)
+    {
+        int i;
+
+        for (i = 0; i < genalloc_len (int, &aa_tmp_list); ++i)
+            if (aa_service (list_get (&aa_tmp_list, i))->fd_in == fd
+                    || aa_service (list_get (&aa_tmp_list, i))->fd_out == fd
+                    || aa_service (list_get (&aa_tmp_list, i))->fd_progress == fd)
+            {
+                si = list_get (&aa_tmp_list, i);
+                break;
+            }
+    }
+
+    fd_close (fd);
+    remove_fd_from_iop (fd);
+    if (si >= 0)
+    {
+        if (aa_service (si)->fd_in == fd)
+            aa_service (si)->fd_in = -1;
+        else if (aa_service (si)->fd_out == fd)
+            aa_service (si)->fd_out = -1;
+        else if (aa_service (si)->fd_progress == fd)
+        {
+            if (aa_service (si)->pi >= 0)
+            {
+                struct progress *pg;
+
+                pg = &genalloc_s (struct progress, &ga_progress)[aa_service (si)->pi];
+                if (pg->is_drawn)
+                    clear_draw ();
+                pg->si = -1;
+                pg->aa_pg.sa.len = 0;
+            }
+            aa_service (si)->fd_progress = -1;
+        }
+    }
+}
+
+int
+handle_fd_out (int si)
+{
+    aa_service *s = aa_service (si);
+
+    for (;;)
+    {
+        char buf[256];
+        int r;
+
+        r = fd_read (s->fd_out, buf, 256);
+        if (r < 0)
+            return (errno == EAGAIN) ? 0 : r;
+        else if (r == 0)
+        {
+            close_fd_for (s->fd_out, si);
+            return 0;
+        }
+
+        if (!stralloc_catb (&s->sa_out, buf, r))
+            return -1;
+
+        for (;;)
+        {
+            int len;
+
+            len = byte_chr (s->sa_out.s, s->sa_out.len, '\n');
+            if (len >= s->sa_out.len)
+                break;
+
+            ++len;
+            clear_draw ();
+            aa_bs_noflush (AA_OUT, aa_service_name (s));
+            aa_bs_noflush (AA_OUT, ": ");
+            aa_bb_flush (AA_OUT, s->sa_out.s, len);
+
+            memmove (s->sa_out.s, s->sa_out.s + len, s->sa_out.len - len);
+            s->sa_out.len -= len;
+        }
+
+        if (r < 256)
+            return 0;
+    }
+}
+
+int
+handle_fd_progress (int si)
+{
+    aa_service *s = aa_service (si);
+    struct progress *pg;
+    char buf[256];
+    int i;
+    int r;
+
+    if (s->pi < 0)
+    {
+        for (i = 0; i < genalloc_len (struct progress, &ga_progress); ++i)
+        {
+            pg = &genalloc_s (struct progress, &ga_progress)[i];
+            if (pg->si < 0)
+            {
+                pg->si = si;
+                s->pi = i;
+                break;
+            }
+        }
+
+        if (s->pi < 0)
+        {
+            struct progress _pg = {
+                .si = si,
+                .is_drawn = 0,
+                .aa_pg.sa = STRALLOC_ZERO
+            };
+            genalloc_append (struct progress, &ga_progress, &_pg);
+            s->pi = genalloc_len (struct progress, &ga_progress) - 1;
+        }
+    }
+    pg = &genalloc_s (struct progress, &ga_progress)[s->pi];
+
+    r = fd_read (s->fd_progress, buf + 1, 255);
+    if (r < 0)
+        return r;
+    else if (r == 0)
+    {
+        close_fd_for (s->fd_progress, si);
+        return 0;
+    }
+    /* if sa is empty (i.e. we just created pg) we need to keep it consistent
+     * with our expectations: it starts with the msg before the
+     * buffered/unprocessed data. So we'll add a NUL (i.e. no msg) */
+    if (pg->aa_pg.sa.len == 0)
+    {
+        *buf = '\0';
+        if (!stralloc_catb (&pg->aa_pg.sa, buf, r + 1))
+            return -1;
+    }
+    else
+        if (!stralloc_catb (&pg->aa_pg.sa, buf + 1, r))
+            return -1;
+
+    if (aa_progress_update (&pg->aa_pg) == 0)
+    {
+        draw |= DRAW_NEED_PROGRESS;
+        /* clear in order to "reset" any WAITING */
+        clear_draw ();
+    }
+
+    return 0;
+}
+
+int
+handle_fd (int fd)
+{
+    int si;
+    int i;
+
+    for (i = 0; i < genalloc_len (int, &aa_tmp_list); ++i)
+    {
+        si = list_get (&aa_tmp_list, i);
+        if (aa_service (si)->fd_out == fd)
+            return handle_fd_out (si);
+        else if (aa_service (si)->fd_progress == fd)
+            return handle_fd_progress (si);
+    }
+
+    errno = ENOENT;
+    return -1;
+}
+
+static int
+handle_oneshot (int is_start)
+{
+    int si;
+    int r;
+    int wstat;
+
+    r = wait_pids_nohang ((pid_t const *) ga_pid.s, genalloc_len (pid_t, &ga_pid), &wstat);
+    if (r < 0)
+    {
+        strerr_warnwu1sys ("wait_pids_nohang");
+        return r;
+    }
+    else if (r == 0)
+        return r;
+
+    /* get the si; same index in tmp_list except we start at 0 */
+    si = list_get (&aa_tmp_list, r - 1);
+
+    remove_from_list (&aa_tmp_list, si);
+    ga_remove (pid_t, &ga_pid, r - 1);
+    if (aa_service (si)->fd_in > 0)
+        close_fd_for (aa_service (si)->fd_in, si);
+    if (aa_service (si)->fd_out > 0)
+        close_fd_for (aa_service (si)->fd_out, si);
+    if (aa_service (si)->fd_progress > 0)
+        close_fd_for (aa_service (si)->fd_progress, si);
+
+    if (WIFEXITED (wstat) && WEXITSTATUS (wstat) == 0)
+    {
+        aa_service_status *svst = &aa_service (si)->st;
+
+        svst->event = (is_start) ? AA_EVT_STARTED : AA_EVT_STOPPED;
+        tain_copynow (&svst->stamp);
+        if (aa_service_status_write (svst, aa_service_name (aa_service (si))) < 0)
+            strerr_warnwu2sys ("write service status file for ", aa_service_name (aa_service (si)));
+
+        put_title (1, aa_service_name (aa_service (si)),
+                (is_start) ? "Started" : "Stopped", 1);
+        ++nb_done;
+    }
+    else
+    {
+        aa_service_status *svst = &aa_service (si)->st;
+        char buf[20];
+
+        svst->event = (is_start) ? AA_EVT_START_FAILED: AA_EVT_STOP_FAILED;
+        svst->code = wstat;
+        tain_copynow (&svst->stamp);
+        aa_service_status_set_msg (svst, "");
+        if (aa_service_status_write (svst, aa_service_name (aa_service (si))) < 0)
+            strerr_warnwu2sys ("write service status file for ", aa_service_name (aa_service (si)));
+
+        if (WIFEXITED (wstat))
+        {
+            byte_copy (buf, 9, "exitcode ");
+            buf[9 + uint_fmt (buf + 9, WEXITSTATUS (wstat))] = '\0';
+        }
+        else
+        {
+            const char *name;
+
+            name = sig_name (WTERMSIG (wstat));
+            byte_copy (buf, 10, "signal SIG");
+            byte_copy (buf + 10, strlen (name) + 1, name);
+        }
+
+        put_err_service (aa_service_name (aa_service (si)), ERR_FAILED, 0);
+        add_err (": ");
+        add_err (buf);
+        end_err ();
+        genalloc_append (int, &ga_failed, &si);
+        if (is_start)
+            check_essential (si);
+    }
+
+    remove_from_list (&aa_main_list, si);
+    return 1;
+}
+
+int
+handle_longrun (aa_mode mode, uint16 id, char event)
+{
+    int si;
+    int l = genalloc_len (int, &aa_main_list);
+    int i;
+
+    for (i = 0; i < l; ++i)
+        if (aa_service (list_get (&aa_main_list, i))->ft_id == id)
+            break;
+
+    if (i >= l)
+    {
+        char buf[UINT16_FMT];
+        buf[uint16_fmt (buf, id)] = '\0';
+        strerr_warnwu2x ("find longrun service for id#", buf);
+        return -1;
+    }
+
+    si = list_get (&aa_main_list, i);
+    aa_service (si)->ft_id = 0;
+
+    put_title (1, aa_service_name (aa_service (si)),
+            (mode == AA_MODE_START) ? "Started" : "Stopped", 1);
+    ++nb_done;
+    --nb_wait_longrun;
+
+    remove_from_list (&aa_main_list, si);
+    return 1;
+}
+
+int
+is_locale_utf8 (void)
+{
+    const char *set;
+
+    setlocale (LC_CTYPE, "");
+    set = nl_langinfo (CODESET);
+    if (set && strcmp (set, "UTF-8") == 0)
+        return 1;
+    else
+        return 0;
+}
+
+int
+get_cols (int fd)
+{
+    struct winsize win;
+
+    if (isatty (fd) && ioctl (fd, TIOCGWINSZ, &win) == 0)
+        return win.ws_col;
+    else
+        return 80;
+}
+
+int
+handle_signals (aa_mode mode)
+{
+    int r = 0;
+
+    for (;;)
+    {
+        char c;
+
+        c = selfpipe_read ();
+        switch (c)
+        {
+            case -1:
+                strerr_diefu1sys (ERR_IO, "selfpipe_read");
+
+            case 0:
+                return r;
+
+            case SIGCHLD:
+                {
+                    int rr = handle_oneshot (mode == AA_MODE_START);
+                    if (rr > 0)
+                        r += rr;
+                    break;
+                }
+
+            case SIGTERM:
+            case SIGQUIT:
+                ioloop = 0;
+                break;
+
+            case SIGWINCH:
+                cols = get_cols (1);
+                break;
+
+            default:
+                strerr_dief1x (ERR_IO, "internal error: invalid selfpipe_read value");
+        }
+    }
+}
+
+void
+prepare_cb (int cur, int next, int is_needs, int first)
+{
+    int l = genalloc_len (int, &aa_tmp_list);
+    int i;
+
+    if (is_needs)
+    {
+        put_err (aa_service_name (aa_service (cur)), "remove ", 0);
+        add_err (aa_service_name (aa_service (cur)));
+        add_err (" needs/after ");
+        add_err (aa_service_name (aa_service (next)));
+        add_err (" to break dependency loop: ");
+        for (i = first; i < l; ++i)
+        {
+            add_err (aa_service_name (aa_service (list_get (&aa_tmp_list, i))));
+            if (i < l - 1)
+                add_err (" -> ");
+        }
+        end_err ();
+    }
+    else
+    {
+        put_warn (aa_service_name (aa_service (cur)), "remove ", 0);
+        add_warn (aa_service_name (aa_service (cur)));
+        add_warn (" after ");
+        add_warn (aa_service_name (aa_service (next)));
+        add_warn (" to break loop: ");
+        for (i = first; i < l; ++i)
+        {
+            add_warn (aa_service_name (aa_service (list_get (&aa_tmp_list, i))));
+            if (i < l - 1)
+                add_warn (" -> ");
+        }
+        end_warn ();
+    }
+}
+
+void
+exec_cb (int si, aa_evt evt, pid_t pid)
+{
+    aa_service *s = aa_service (si);
+
+    switch ((int) evt)
+    {
+        /* ugly hack thing; see aa_exec_service() */
+        case 0:
+            clear_draw ();
+            aa_bs_noflush (AA_OUT,
+                    (pid == AA_MODE_START) ? "Starting " : "Stopping ");
+            aa_bs_noflush (AA_OUT, aa_service_name (aa_service (si)));
+            aa_bs_flush (AA_OUT, "...\n");
+            break;
+
+        case AA_EVT_STARTING:
+        case AA_EVT_STOPPING:
+            if (s->st.type == AA_TYPE_ONESHOT)
+            {
+                iopause_fd iop;
+
+                iop.fd = s->fd_out;
+                iop.events = IOPAUSE_READ;
+                genalloc_append (iopause_fd, &ga_iop, &iop);
+                iop.fd = s->fd_progress;
+                genalloc_append (iopause_fd, &ga_iop, &iop);
+
+                add_to_list (&aa_tmp_list, si, 0);
+                genalloc_append (pid_t, &ga_pid, &pid);
+            }
+            else
+                ++nb_wait_longrun;
+            break;
+
+        case AA_EVT_STARTED:
+        case AA_EVT_STOPPED:
+            put_title (1, aa_service_name (s),
+                    (evt == AA_EVT_STARTED) ? "Started" : "Stopped", 1);
+            ++nb_done;
+            break;
+
+        case AA_EVT_STARTING_FAILED:
+        case AA_EVT_STOPPING_FAILED:
+            {
+                const char *msg;
+
+                msg = aa_service_status_get_msg (&s->st);
+                put_err_service (aa_service_name (s), s->st.code, !msg);
+                if (msg)
+                {
+                    add_err (": ");
+                    add_err (msg);
+                    end_err ();
+                }
+                genalloc_append (int, &ga_failed, &si);
+                check_essential (si);
+                break;
+            }
+
+        case -ERR_ALREADY_UP: /* could happen w/ longrun */
+            put_title (1, aa_service_name (s), errmsg[ERR_ALREADY_UP], 1);
+            ++nb_already;
+            break;
+
+        case -ERR_NOT_UP:
+            put_title (1, aa_service_name (s), errmsg[ERR_NOT_UP], 1);
+            ++nb_already;
+            break;
+    }
+}
+
+void
+mainloop (aa_mode mode, aa_scan_cb scan_cb)
+{
+    sigset_t set;
+    iopause_fd iop;
+    int i;
+
+    if (!genalloc_ready_tuned (iopause_fd, &ga_iop, 2, 0, 0, 1))
+        strerr_diefu1sys (ERR_IO, "allocate iopause_fd");
+
+    iop.fd = selfpipe_init ();
+    if (iop.fd == -1)
+        strerr_diefu1sys (ERR_IO, "init selfpipe");
+    iop.events = IOPAUSE_READ;
+    genalloc_append (iopause_fd, &ga_iop, &iop);
+
+    iop.fd = aa_prepare_mainlist (prepare_cb, exec_cb);
+    if (iop.fd < 0)
+        strerr_diefu1sys (ERR_IO, "prepare mainlist");
+    else if (iop.fd == 0)
+        iop.fd = -1;
+    genalloc_append (iopause_fd, &ga_iop, &iop);
+
+    sigemptyset (&set);
+    sigaddset (&set, SIGCHLD);
+    sigaddset (&set, SIGTERM);
+    sigaddset (&set, SIGQUIT);
+    sigaddset (&set, SIGWINCH);
+    if (selfpipe_trapset (&set) < 0)
+        strerr_diefu1sys (ERR_IO, "trap signals");
+
+    /* start what we can */
+    for (i = 0; i < genalloc_len (int, &aa_main_list); ++i)
+        if (genalloc_len (int, &aa_service (list_get (&aa_main_list, i))->after) == 0)
+            if (aa_exec_service (list_get (&aa_main_list, i), mode) < 0)
+            {
+                aa_scan_mainlist (scan_cb, mode);
+                break;
+            }
+
+    while (ioloop && (genalloc_len (int, &aa_main_list) > 0))
+    {
+        int nb_iop;
+        int r;
+
+        refresh_draw ();
+
+        nb_iop = genalloc_len (iopause_fd, &ga_iop);
+        r = iopause_g (genalloc_s (iopause_fd, &ga_iop), nb_iop, &iol_deadline);
+        if (r < 0)
+            strerr_diefu1sys (ERR_IO, "iopause");
+        else if (r == 0)
+            draw |= DRAW_NEED_WAITING;
+        else
+        {
+            iopause_fd *iofd;
+            int scan = 0;
+
+            for (--nb_iop; nb_iop > 1; --nb_iop)
+            {
+                iofd = &genalloc_s (iopause_fd, &ga_iop)[nb_iop];
+                if (iofd->revents & IOPAUSE_READ)
+                {
+                    r = handle_fd (iofd->fd);
+                    if (r < 0)
+                        strerr_warnwu1sys ("handle fd");
+                }
+                else if (iofd->revents & IOPAUSE_EXCEPT)
+                    close_fd_for (iofd->fd, -1);
+            }
+
+            iofd = &genalloc_s (iopause_fd, &ga_iop)[0];
+            if (iofd->revents & IOPAUSE_READ)
+                scan += handle_signals (mode);
+            else if (iofd->revents & IOPAUSE_EXCEPT)
+                strerr_diefu1sys (ERR_IO, "iopause: selfpipe error");
+
+            iofd = &genalloc_s (iopause_fd, &ga_iop)[1];
+            if (iofd->fd <= 0)
+                goto scan;
+            if (iofd->revents & IOPAUSE_READ)
+            {
+                for (;;)
+                {
+                    uint16 id;
+                    char event;
+
+                    r = aa_get_longrun_info (&id, &event);
+                    if (r < 0)
+                    {
+                        strerr_warnwu1sys ("get longrun information");
+                        break;
+                    }
+                    else if (r == 0)
+                        break;
+
+                    r = handle_longrun (mode, id, event);
+                    if (r > 0)
+                        scan += r;
+                }
+            }
+            else if (iofd->revents & IOPAUSE_EXCEPT)
+                strerr_diefu1sys (ERR_IO, "iopause: longrun pipe error");
+
+scan:
+            if (scan > 0)
+                aa_scan_mainlist (scan_cb, mode);
+        }
+    }
+}
+
+void
+show_stat_service_names (genalloc *ga, const char *title, const char *ansi_color)
+{
+    int i;
+
+    if (genalloc_len (int, ga) <= 0)
+        return;
+
+    put_title (0, title, "", 0);
+    for (i = 0; i < genalloc_len (int, ga); ++i)
+    {
+        if (i > 0)
+            aa_bs_noflush (AA_OUT, "; ");
+        aa_is_noflush (AA_OUT, ansi_color);
+        aa_bs_noflush (AA_OUT, aa_service_name (aa_service (list_get (ga, i))));
+        aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+    }
+    end_title ();
+}
diff --git a/src/anopa/start-stop.h b/src/anopa/start-stop.h
new file mode 100644
index 0000000..1c8ec61
--- /dev/null
+++ b/src/anopa/start-stop.h
@@ -0,0 +1,91 @@
+
+#ifndef AA_START_STOP_H
+#define AA_START_STOP_H
+
+#include <signal.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/tai.h>
+#include <anopa/service.h>
+#include <anopa/progress.h>
+#include <anopa/output.h>
+
+#define TIMEOUT_SECS                2
+
+#define ANSI_PREV_LINE              "\x1B[F"
+#define ANSI_CLEAR_AFTER            "\x1B[K"
+#define ANSI_CLEAR_BEFORE           "\x1B[1K"
+#define ANSI_START_LINE             "\x1B[1G"
+
+extern genalloc ga_iop;
+extern genalloc ga_progress;
+extern genalloc ga_pid;
+extern tain_t iol_deadline;
+extern unsigned int draw;
+extern int nb_already;
+extern int nb_done;
+extern int nb_wait_longrun;
+extern genalloc ga_failed;
+extern int cols;
+extern int is_utf8;
+extern int ioloop;
+
+enum
+{
+    DRAW_CUR_WAITING    = (1 << 0),
+    DRAW_CUR_PROGRESS   = (1 << 1),
+    DRAW_HAS_CUR        = (1 << 2) - 1,
+
+    DRAW_NEED_WAITING   = (1 << 2),
+    DRAW_NEED_PROGRESS  = (1 << 3),
+    DRAW_HAS_NEED       = (1 << 4) - DRAW_HAS_CUR - 1
+};
+
+struct progress
+{
+    aa_progress aa_pg;
+    int si;
+    int is_drawn;
+};
+
+void free_progress (struct progress *pg);
+void refresh_draw ();
+void draw_waiting (int already_drawn);
+void draw_progress_for (int si);
+void clear_draw ();
+void add_name_to_ga (const char *name, genalloc *ga);
+void iol_deadline_addsec (int n);
+void remove_fd_from_iop (int fd);
+void close_fd_for (int fd, int si);
+int handle_fd_out (int si);
+int handle_fd_progress (int si);
+int handle_fd (int fd);
+int handle_longrun (aa_mode mode, uint16 id, char event);
+int is_locale_utf8 (void);
+int get_cols (int fd);
+int handle_signals (aa_mode mode);
+void prepare_cb (int cur, int next, int is_needs, int first);
+void exec_cb (int si, aa_evt evt, pid_t pid);
+void mainloop (aa_mode mode, aa_scan_cb scan_cb);
+void show_stat_service_names (genalloc *ga, const char *title, const char *ansi_color);
+
+#define end_err()                       aa_end_err ()
+#define put_err(name,msg,end)           do { \
+    clear_draw (); \
+    aa_put_err (name, msg, end); \
+} while (0)
+#define add_err(s)                      aa_bs_noflush (AA_ERR, s)
+#define put_err_service(name,err,end)   put_err (name, errmsg[err], end)
+#define end_warn()                      aa_end_warn ()
+#define put_warn(name,msg,end)          do { \
+    clear_draw (); \
+    aa_put_warn (name, msg, end); \
+} while (0)
+#define add_warn(s)                     aa_bs_noflush (AA_ERR, s)
+#define end_title()                     aa_end_title ()
+#define put_title(main,name,title,end)  do { \
+    clear_draw (); \
+    aa_put_title (main, name, title, end); \
+} while (0)
+#define add_title(s)                    aa_bs_noflush (AA_OUT, s)
+
+#endif /* AA_START_STOP_H */
diff --git a/src/anopa/util.c b/src/anopa/util.c
new file mode 100644
index 0000000..9754c5f
--- /dev/null
+++ b/src/anopa/util.c
@@ -0,0 +1,12 @@
+
+#include <string.h>
+
+void
+unslash (char *s)
+{
+    int l = strlen (s) - 1;
+    if (l <= 0)
+        return;
+    if (s[l] == '/')
+        s[l] = '\0';
+}
diff --git a/src/anopa/util.h b/src/anopa/util.h
new file mode 100644
index 0000000..46b4431
--- /dev/null
+++ b/src/anopa/util.h
@@ -0,0 +1,7 @@
+
+#ifndef AA_UTIL_H
+#define AA_UTIL_H
+
+void unslash (char *s);
+
+#endif /* AA_UTIL_H */
diff --git a/src/include/anopa/anopa.h b/src/include/anopa/anopa.h
new file mode 100644
index 0000000..b31de05
--- /dev/null
+++ b/src/include/anopa/anopa.h
@@ -0,0 +1,12 @@
+
+#ifndef AA_ANOPA_H
+#define AA_ANOPA_H
+
+#include <anopa/enable_service.h>
+#include <anopa/err.h>
+#include <anopa/ga_int_list.h>
+#include <anopa/output.h>
+#include <anopa/scan_dir.h>
+#include <anopa/stats.h>
+
+#endif /* AA_ANOPA_H */
diff --git a/src/include/anopa/common.h b/src/include/anopa/common.h
new file mode 100644
index 0000000..234d6e3
--- /dev/null
+++ b/src/include/anopa/common.h
@@ -0,0 +1,10 @@
+
+#ifndef AA_COMMON_H
+#define AA_COMMON_H
+
+#define AA_SCANDIR_DIRNAME      ".scandir"
+
+void aa_die_usage (const char *usage, const char *details);
+void aa_die_version (void);
+
+#endif /* AA_COMMON_H */
diff --git a/src/include/anopa/enable_service.h b/src/include/anopa/enable_service.h
new file mode 100644
index 0000000..f1fbbeb
--- /dev/null
+++ b/src/include/anopa/enable_service.h
@@ -0,0 +1,30 @@
+
+#ifndef AA_ENABLE_SERVICE_H
+#define AA_ENABLE_SERVICE_H
+
+#include <skalibs/stralloc.h>
+#include <anopa/common.h>
+
+typedef enum
+{
+    AA_FLAG_AUTO_ENABLE_NEEDS   = (1 << 0),
+    AA_FLAG_AUTO_ENABLE_WANTS   = (1 << 1),
+    /* private */
+    _AA_FLAG_IS_SERVICEDIR      = (1 << 2),
+    _AA_FLAG_IS_NEEDS           = (1 << 3),
+    _AA_FLAG_IS_WANTS           = (1 << 4),
+    _AA_FLAG_IS_BEF_AFT         = (1 << 5),
+    _AA_FLAG_IS_1OF4            = _AA_FLAG_IS_NEEDS | _AA_FLAG_IS_WANTS | _AA_FLAG_IS_BEF_AFT
+} aa_enable_flags;
+
+extern stralloc aa_sa_sources;
+
+typedef void (*aa_warn_fn) (const char *name, int err);
+typedef void (*aa_auto_enable_cb) (const char *name, aa_enable_flags type);
+
+extern int aa_enable_service (const char        *name,
+                              aa_warn_fn         warn_fn,
+                              aa_enable_flags    flags,
+                              aa_auto_enable_cb  ae_cb);
+
+#endif /* AA_ENABLE_SERVICE_H */
diff --git a/src/include/anopa/err.h b/src/include/anopa/err.h
new file mode 100644
index 0000000..5c43685
--- /dev/null
+++ b/src/include/anopa/err.h
@@ -0,0 +1,29 @@
+
+#ifndef AA_ERR_H
+#define AA_ERR_H
+
+enum
+{
+    ERR_INVALID_NAME = 1,
+    ERR_UNKNOWN,
+    ERR_DEPEND,
+    ERR_IO,
+    ERR_WRITE_STATUS,
+    ERR_CHDIR,
+    ERR_EXEC,
+    ERR_PIPES,
+    ERR_S6,
+    ERR_FAILED,
+    ERR_TIMEDOUT,
+    ERR_IO_REPODIR,
+    ERR_IO_SCANDIR,
+    ERR_FAILED_ENABLE,
+    /* not actual service error, see aa_ensure_service_loaded() */
+    ERR_ALREADY_UP,
+    ERR_NOT_UP,
+    _NB_ERR
+};
+
+extern const char const *errmsg[_NB_ERR];
+
+#endif /* AA_ERR_H */
diff --git a/src/include/anopa/ga_int_list.h b/src/include/anopa/ga_int_list.h
new file mode 100644
index 0000000..ab89bcc
--- /dev/null
+++ b/src/include/anopa/ga_int_list.h
@@ -0,0 +1,21 @@
+
+#ifndef AA_GA_INT_LIST_H
+#define AA_GA_INT_LIST_H
+
+#include <skalibs/genalloc.h>
+
+#define ga_remove(type, ga, i)     do {         \
+    int len = (ga)->len / sizeof (type);        \
+    int c = len - (i) - 1;                      \
+    if (c > 0)                                  \
+        memmove (genalloc_s (type, (ga)) + (i), genalloc_s (type, (ga)) + (i) + 1, c * sizeof (type)); \
+    genalloc_setlen (type, (ga), len - 1);    \
+} while (0)
+
+#define list_get(ga, i)         (genalloc_s (int, ga)[i])
+
+extern int add_to_list      (genalloc *list, int si, int check_for_dupes);
+extern int remove_from_list (genalloc *list, int si);
+extern int is_in_list       (genalloc *list, int si);
+
+#endif /* AA_GA_INT_LIST_H */
diff --git a/src/include/anopa/init_repo.h b/src/include/anopa/init_repo.h
new file mode 100644
index 0000000..4ea23ed
--- /dev/null
+++ b/src/include/anopa/init_repo.h
@@ -0,0 +1,14 @@
+
+#ifndef AA_INIT_REPO_H
+#define AA_INIT_REPO_H
+
+typedef enum
+{
+    AA_REPO_READ = 0,
+    AA_REPO_WRITE,
+    AA_REPO_CREATE
+} aa_repo_init;
+
+int aa_init_repo (const char *path_repo, aa_repo_init ri);
+
+#endif /* AA_INIT_REPO_H */
diff --git a/src/include/anopa/output.h b/src/include/anopa/output.h
new file mode 100644
index 0000000..feeb6cf
--- /dev/null
+++ b/src/include/anopa/output.h
@@ -0,0 +1,34 @@
+
+#ifndef AA_OUTPUT_H
+#define AA_OUTPUT_H
+
+#include <string.h> /* strlen() */
+
+#define ANSI_HIGHLIGHT_ON           "\x1B[1;39m"
+#define ANSI_HIGHLIGHT_RED_ON       "\x1B[1;31m"
+#define ANSI_HIGHLIGHT_GREEN_ON     "\x1B[1;32m"
+#define ANSI_HIGHLIGHT_YELLOW_ON    "\x1B[1;33m"
+#define ANSI_HIGHLIGHT_BLUE_ON      "\x1B[1;36m"
+#define ANSI_HIGHLIGHT_OFF          "\x1B[0m"
+
+#define AA_OUT      0
+#define AA_ERR      1
+
+extern void aa_init_output (int mode_both);
+extern void aa_bb_noflush (int where, const char *s, int len);
+extern void aa_bb_flush (int where, const char *s, int len);
+#define aa_bs_noflush(w,s)  aa_bb_noflush ((w), (s), strlen (s))
+#define aa_bs_flush(w,s)    aa_bb_flush ((w), (s), strlen (s))
+extern void aa_ib_noflush (int where, const char *s, int len);
+extern void aa_ib_flush (int where, const char *s, int len);
+#define aa_is_noflush(w,s)  aa_ib_noflush ((w), (s), strlen (s))
+#define aa_is_flush(w,s)    aa_ib_flush ((w), (s), strlen (s))
+extern void aa_bs_end (int where);
+#define aa_end_err()        aa_bs_end (AA_ERR)
+extern void aa_put_err (const char *name, const char *msg, int end);
+#define aa_end_warn()       aa_bs_end (AA_ERR)
+extern void aa_put_warn (const char *name, const char *msg, int end);
+#define aa_end_title()      aa_bs_end (AA_OUT)
+extern void aa_put_title (int main, const char *name, const char *title, int end);
+
+#endif /* AA_OUTPUT_H */
diff --git a/src/include/anopa/progress.h b/src/include/anopa/progress.h
new file mode 100644
index 0000000..cdcafa8
--- /dev/null
+++ b/src/include/anopa/progress.h
@@ -0,0 +1,18 @@
+
+#ifndef AA_PROGRESS_H
+#define AA_PROGRESS_H
+
+#include <skalibs/stralloc.h>
+
+typedef struct
+{
+    int step;
+    double pctg;
+    stralloc sa;
+} aa_progress;
+
+extern void aa_progress_free    (aa_progress *p);
+extern int  aa_progress_update  (aa_progress *pg);
+extern void aa_progress_draw    (aa_progress *pg, const char *title, int cols, int is_utf8);
+
+#endif /* AA_PROGRESS_H */
diff --git a/src/include/anopa/scan_dir.h b/src/include/anopa/scan_dir.h
new file mode 100644
index 0000000..75ad070
--- /dev/null
+++ b/src/include/anopa/scan_dir.h
@@ -0,0 +1,11 @@
+
+#ifndef AA_SCAN_DIR_H
+#define AA_SCAN_DIR_H
+
+#include <skalibs/direntry.h>
+
+typedef int (*aa_sd_it_fn) (direntry *d, void *data);
+
+int aa_scan_dir (stralloc *sa, int files_only, aa_sd_it_fn iterator, void *data);
+
+#endif /* AA_SCAN_DIR_H */
diff --git a/src/include/anopa/service.h b/src/include/anopa/service.h
new file mode 100644
index 0000000..1f20b15
--- /dev/null
+++ b/src/include/anopa/service.h
@@ -0,0 +1,87 @@
+
+#ifndef AA_SERVICE_H
+#define AA_SERVICE_H
+
+#include <signal.h> /* pid_t */
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/uint16.h>
+#include <s6/ftrigr.h>
+#include <anopa/service_status.h>
+
+#define AA_START_FILENAME           "start"
+#define AA_STOP_FILENAME            "stop"
+
+extern genalloc aa_services;
+extern stralloc aa_names;
+extern genalloc aa_main_list;
+extern genalloc aa_tmp_list;
+extern genalloc aa_pid_list;
+
+#define aa_service(i)               (&((aa_service *) aa_services.s)[i])
+#define aa_service_name(service)    (aa_names.s + (service)->offset_name)
+
+typedef enum
+{
+    AA_SERVICE_FROM_MAIN = 0,
+    AA_SERVICE_FROM_TMP,
+} aa_sf;
+
+typedef enum
+{
+    AA_MODE_START = 0,
+    AA_MODE_STOP
+} aa_mode;
+
+typedef enum
+{
+    AA_LOADFAIL_NEEDS = 0,
+    AA_LOADFAIL_WANTS,
+} aa_lf;
+
+typedef enum
+{
+    AA_LOAD_NOT,
+    AA_LOAD_ING,
+    AA_LOAD_DONE,
+    AA_LOAD_DONE_CHECKED,
+    AA_LOAD_FAIL
+} aa_ls;
+
+typedef struct
+{
+    int offset_name;
+    int nb_mark;
+    genalloc needs;
+    genalloc wants;
+    genalloc after;
+    aa_ls ls;
+    aa_service_status st;
+    /* longrun */
+    uint16 ft_id;
+    /* oneshot */
+    int fd_in;
+    int fd_out;
+    stralloc sa_out;
+    int fd_progress;
+    int pi;
+} aa_service;
+
+typedef void (*aa_close_fd_fn) (int fd);
+typedef void (*aa_load_fail_cb) (int si, aa_lf lf, const char *name, int err);
+typedef void (*aa_prepare_cb) (int si, int si_next, int is_needs, int first);
+typedef void (*aa_scan_cb) (int si, int sni);
+typedef void (*aa_exec_cb) (int si, aa_evt evt, pid_t pid);
+
+extern void aa_free_services (aa_close_fd_fn close_fd_fn);
+extern int  aa_add_name (const char *name);
+extern int  aa_get_service (const char *name, int *si, int new_in_main);
+extern void aa_unmark_service (int si);
+extern int  aa_mark_service (int si, int in_main, int no_wants, aa_load_fail_cb lf_cb);
+extern int  aa_ensure_service_loaded (int si, aa_mode mode, int no_wants, aa_load_fail_cb lf_cb);
+extern int  aa_prepare_mainlist (aa_prepare_cb prepare_cb, aa_exec_cb exec_cb);
+extern void aa_scan_mainlist (aa_scan_cb scan_cb, aa_mode mode);
+extern int  aa_exec_service (int si, aa_mode mode);
+extern int  aa_get_longrun_info (uint16 *id, char *event);
+
+#endif /* AA_SERVICE_H */
diff --git a/src/include/anopa/service_status.h b/src/include/anopa/service_status.h
new file mode 100644
index 0000000..ec5d02d
--- /dev/null
+++ b/src/include/anopa/service_status.h
@@ -0,0 +1,52 @@
+
+#ifndef AA_SERVICE_STATUS_H
+#define AA_SERVICE_STATUS_H
+
+#include <skalibs/stralloc.h>
+#include <skalibs/tai.h>
+
+typedef enum
+{
+    AA_EVT_NONE = 0,
+    AA_EVT_ERROR,
+    AA_EVT_STARTING,
+    AA_EVT_STARTING_FAILED,
+    AA_EVT_START_FAILED,
+    AA_EVT_STARTED,
+    AA_EVT_STOPPING,
+    AA_EVT_STOPPING_FAILED,
+    AA_EVT_STOP_FAILED,
+    AA_EVT_STOPPED,
+    _AA_NB_EVT
+} aa_evt;
+
+enum
+{
+    AA_TYPE_UNKNOWN = 0,
+    AA_TYPE_ONESHOT,
+    AA_TYPE_LONGRUN
+};
+
+typedef struct
+{
+    tain_t stamp;
+    aa_evt event;
+    int code;
+    stralloc sa;
+    /* not saved to status file */
+    unsigned int type;
+} aa_service_status;
+
+#define AA_SVST_FIXED_SIZE          20
+#define AA_SVST_MAX_MSG_SIZE        255
+#define AA_SVST_FILENAME            "status.anopa"
+
+extern void aa_service_status_free      (aa_service_status *svst);
+extern int  aa_service_status_read      (aa_service_status *svst, const char *dir);
+extern int  aa_service_status_write     (aa_service_status *svst, const char *dir);
+extern int  aa_service_status_set_msg   (aa_service_status *svst, const char *msg);
+extern int  aa_service_status_set_err   (aa_service_status *svst, int err, const char *msg);
+#define aa_service_status_get_msg(svst) \
+    (((svst)->sa.len > AA_SVST_FIXED_SIZE) ? (svst)->sa.s + AA_SVST_FIXED_SIZE : NULL)
+
+#endif /* AA_SERVICE_STATUS_H */
diff --git a/src/include/anopa/stats.h b/src/include/anopa/stats.h
new file mode 100644
index 0000000..867f1d8
--- /dev/null
+++ b/src/include/anopa/stats.h
@@ -0,0 +1,13 @@
+
+#ifndef AA_STATS_H
+#define AA_STATS_H
+
+#include <skalibs/genalloc.h>
+
+void aa_show_stat_nb (int nb, const char *title, const char *ansi_color);
+void aa_show_stat_names (const char  *names,
+                         genalloc    *ga_offets,
+                         const char  *title,
+                         const char  *ansi_color);
+
+#endif /* AA_STATS_H */
diff --git a/src/libanopa/deps-lib/anopa b/src/libanopa/deps-lib/anopa
new file mode 100644
index 0000000..aa59e80
--- /dev/null
+++ b/src/libanopa/deps-lib/anopa
@@ -0,0 +1,18 @@
+die_usage.o
+die_version.o
+enable_service.o
+errmsg.o
+exec_longrun.o
+exec_oneshot.o
+ga_int_list.o
+init_repo.o
+output.o
+progress.o
+sa_sources.o
+service.o
+service_start.o
+service_stop.o
+services.o
+service_status.o
+scan_dir.o
+stats.o
diff --git a/src/libanopa/die_usage.c b/src/libanopa/die_usage.c
new file mode 100644
index 0000000..039ff95
--- /dev/null
+++ b/src/libanopa/die_usage.c
@@ -0,0 +1,18 @@
+
+#include <unistd.h>
+#include <anopa/output.h>
+
+extern char const *PROG;
+
+void
+aa_die_usage (const char *usage, const char *details)
+{
+    aa_init_output (0);
+    aa_bs_noflush (AA_OUT, "Usage: ");
+    aa_bs_noflush (AA_OUT, PROG);
+    aa_bs_noflush (AA_OUT, " ");
+    aa_bs_noflush (AA_OUT, usage);
+    aa_bs_noflush (AA_OUT, "\n\n");
+    aa_bs_flush (AA_OUT, details);
+    _exit (0);
+}
diff --git a/src/libanopa/die_version.c b/src/libanopa/die_version.c
new file mode 100644
index 0000000..2b7ba01
--- /dev/null
+++ b/src/libanopa/die_version.c
@@ -0,0 +1,22 @@
+
+#include "anopa/config.h"
+
+#include <unistd.h>
+#include <anopa/output.h>
+
+extern char const *PROG;
+
+void
+aa_die_version (void)
+{
+    aa_init_output (0);
+    aa_bs_noflush (AA_OUT, PROG);
+    aa_bs_noflush (AA_OUT, " v" ANOPA_VERSION "\n");
+    aa_bs_flush (AA_OUT,
+            "Copyright (C) 2015 Olivier Brunel - http://jjacky.com/anopa\n"
+            "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n"
+            "This is free software: you are free to change and redistribute it.\n"
+            "There is NO WARRANTY, to the extent permitted by law.\n"
+            );
+    _exit (0);
+}
diff --git a/src/libanopa/enable_service.c b/src/libanopa/enable_service.c
new file mode 100644
index 0000000..edfa28a
--- /dev/null
+++ b/src/libanopa/enable_service.c
@@ -0,0 +1,489 @@
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h> /* rename() */
+#include <skalibs/bytestr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/direntry.h>
+#include <skalibs/skamisc.h>
+#include <anopa/enable_service.h>
+#include <anopa/err.h>
+
+static int
+copy_from_source (const char        *name,
+                  int                len,
+                  aa_warn_fn         warn_fn,
+                  aa_enable_flags    flags,
+                  aa_auto_enable_cb  ae_cb);
+static int
+copy_dir (const char        *src,
+          const char        *dst,
+          mode_t             mode,
+          int                depth,
+          aa_warn_fn         warn_fn,
+          aa_enable_flags    flags,
+          aa_auto_enable_cb  ae_cb,
+          const char        *instance);
+
+
+static int
+is_valid_service_name (const char *name, int len)
+{
+    if (len <= 0)
+        return 0;
+    if (name[0] == '@' || name[len - 1] == '@')
+        return 0;
+    if (byte_chr (name, len, '/') < len)
+        return 0;
+    return 1;
+}
+
+static int
+copy_file (const char *src, const char *dst, mode_t mode)
+{
+    int fd_src;
+    int fd_dst;
+
+    fd_src = open_readb (src);
+    if (fd_src < 0)
+        return -1;
+
+    fd_dst = open3 (dst, O_WRONLY | O_CREAT | O_TRUNC, mode);
+    if (fd_dst < 0)
+    {
+        int e = errno;
+        fd_close (fd_src);
+        errno = e;
+        return -1;
+    }
+
+    if (fd_cat (fd_src, fd_dst) < 0)
+    {
+        int e = errno;
+        fd_close (fd_src);
+        fd_close (fd_dst);
+        errno = e;
+        return -1;
+    }
+
+    fd_close (fd_src);
+    fd_close (fd_dst);
+    return 0;
+}
+
+static int
+copy_log (const char *name, const char *cfg, mode_t mode, aa_warn_fn warn_fn)
+{
+    int fd;
+    int r;
+    int e;
+
+    /* get current dir (repo) so we can come back */
+    fd = open_read (".");
+    if (fd < 0)
+        return fd;
+
+    /* and temporarily go into the servicedir */
+    r = chdir (name);
+    if (r < 0)
+    {
+        e = errno;
+        fd_close (fd);
+        errno = e;
+        return r;
+    }
+
+    /* this is a logger, so there's no autoenable of any kind; hence we can use
+     * 0 for flags (don't process it as a servicedir either, since it doesn't
+     * apply) and not bother with a callback */
+    r = copy_from_source ("log", 3, warn_fn, 0, NULL);
+
+    if (r >= 0 && cfg)
+        r = copy_dir (cfg, "log", mode, 0, warn_fn, 0, NULL, NULL);
+
+    e = errno;
+    fd_chdir (fd);
+    fd_close (fd);
+    errno = e;
+
+    return r;
+}
+
+static int
+copy_dir (const char        *src,
+          const char        *dst,
+          mode_t             mode,
+          int                depth,
+          aa_warn_fn         warn_fn,
+          aa_enable_flags    flags,
+          aa_auto_enable_cb  ae_cb,
+          const char        *instance)
+{
+    unsigned int l_satmp = satmp.len;
+    unsigned int l_max = strlen (AA_SCANDIR_DIRNAME);
+    DIR *dir;
+    struct stat st;
+    struct {
+        unsigned int run   : 1;
+        unsigned int down  : 1;
+        unsigned int began : 1;
+    } has = { .run = 0, .down = 0, .began = 0 };
+
+    dir = opendir (src);
+    if (!dir)
+        return -ERR_IO;
+    errno = 0;
+    for (;;)
+    {
+        direntry *d;
+        unsigned int len;
+
+        d = readdir (dir);
+        if (!d)
+            break;
+        if (d->d_name[0] == '.'
+                && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+            continue;
+        len = strlen (d->d_name);
+        if (len > l_max)
+            l_max = len;
+        if (!stralloc_catb (&satmp, d->d_name, len + 1))
+            break;
+        if (depth == 0 && (flags & _AA_FLAG_IS_SERVICEDIR))
+        {
+            if (!has.run && str_equal (d->d_name, "run"))
+                has.run = 1;
+            if (!has.down && str_equal (d->d_name, "down"))
+                has.down = 1;
+        }
+    }
+    if (errno)
+    {
+        int e = errno;
+        dir_close (dir);
+        errno = e;
+        goto err;
+    }
+    dir_close (dir);
+
+    if (mkdir (dst, S_IRWXU) < 0)
+    {
+        if (errno != EEXIST || stat (dst, &st) < 0)
+            goto err;
+        else if (!S_ISDIR (st.st_mode))
+        {
+            errno = ENOTDIR;
+            goto err;
+        }
+        else if (flags & _AA_FLAG_IS_SERVICEDIR)
+        {
+            errno = EEXIST;
+            goto err;
+        }
+    }
+
+    if (flags & _AA_FLAG_IS_SERVICEDIR)
+    {
+        has.began = 1;
+        flags &= ~_AA_FLAG_IS_SERVICEDIR;
+    }
+
+    {
+        unsigned int l_inst = (instance) ? strlen (instance) : 0;
+        unsigned int l_src = strlen (src);
+        unsigned int l_dst = strlen (dst);
+        unsigned int i = l_satmp;
+        char buf_src[l_src + 1 + l_max + 1];
+        char buf_dst[l_dst + 1 + l_max + l_inst + 1];
+
+        byte_copy (buf_src, l_src, src);
+        buf_src[l_src] = '/';
+        byte_copy (buf_dst, l_dst, dst);
+        buf_dst[l_dst] = '/';
+
+        while (i < satmp.len)
+        {
+            unsigned int len;
+            int r;
+
+            len = strlen (satmp.s + i);
+            byte_copy (buf_src + l_src + 1, len + 1, satmp.s + i);
+            byte_copy (buf_dst + l_dst + 1, len + 1, satmp.s + i);
+
+            if (stat (buf_src, &st) < 0)
+            {
+                warn_fn (buf_src, errno);
+                goto err;
+            }
+
+            if (S_ISREG (st.st_mode))
+            {
+                if (has.began && depth == 0 && str_equal (satmp.s + i, "log"))
+                {
+                    r = copy_log (dst, NULL, 0, warn_fn);
+                    st.st_mode = 0755;
+                }
+                else
+                {
+                    /* for any file in one of the 4 special places that ends
+                     * with a '@' we append our instance name */
+                    if (depth == 1 && instance && (flags & _AA_FLAG_IS_1OF4)
+                            && satmp.s[i + len - 1] == '@')
+                        byte_copy (buf_dst + l_dst + 1 + len, l_inst + 1, instance);
+                    r = copy_file (buf_src, buf_dst, st.st_mode);
+                    if (depth == 1 && r == 0 && ae_cb)
+                    {
+                        if ((flags & (AA_FLAG_AUTO_ENABLE_NEEDS | _AA_FLAG_IS_NEEDS))
+                                == (AA_FLAG_AUTO_ENABLE_NEEDS | _AA_FLAG_IS_NEEDS))
+                            ae_cb (buf_dst + l_dst + 1, AA_FLAG_AUTO_ENABLE_NEEDS);
+                        else if ((flags & (AA_FLAG_AUTO_ENABLE_WANTS | _AA_FLAG_IS_WANTS))
+                                == (AA_FLAG_AUTO_ENABLE_WANTS | _AA_FLAG_IS_WANTS))
+                            ae_cb (buf_dst + l_dst + 1, AA_FLAG_AUTO_ENABLE_WANTS);
+                    }
+                }
+            }
+            else if (S_ISDIR (st.st_mode))
+            {
+                if (has.began && depth == 0 && str_equal (satmp.s + i, "log"))
+                    r = copy_log (dst, buf_src, st.st_mode, warn_fn);
+                else
+                {
+                    /* use depth because this is also enabled for the config part */
+                    if (depth == 0)
+                    {
+                        /* flag to enable auto-rename of files above */
+                        if (str_equal (satmp.s + i, "needs"))
+                            flags |= _AA_FLAG_IS_NEEDS;
+                        else if (str_equal (satmp.s + i, "wants"))
+                            flags |= _AA_FLAG_IS_WANTS;
+                        else if (str_equal (satmp.s + i, "before")
+                                || str_equal (satmp.s + i, "after"))
+                            flags |= _AA_FLAG_IS_BEF_AFT;
+                    }
+                    r = copy_dir (buf_src, buf_dst, st.st_mode, depth + 1,
+                            warn_fn, flags, ae_cb, instance);
+                    if (depth == 0)
+                        flags &= ~_AA_FLAG_IS_1OF4;
+                }
+            }
+            else if (S_ISFIFO (st.st_mode))
+                r = mkfifo (buf_dst, st.st_mode);
+            else if (S_ISLNK (st.st_mode))
+            {
+                unsigned int l_tmp = satmp.len;
+
+                if ((sareadlink (&satmp, buf_src) < 0) || !stralloc_0 (&satmp))
+                    r = -1;
+                else
+                    r = symlink (satmp.s + l_tmp, buf_dst);
+
+                satmp.len = l_tmp;
+            }
+            else if (S_ISCHR (st.st_mode) || S_ISBLK (st.st_mode) || S_ISSOCK (st.st_mode))
+                r = mknod (buf_dst, st.st_mode, st.st_rdev);
+            else
+            {
+                errno = EOPNOTSUPP;
+                r = -1;
+            }
+
+            if (r >= 0)
+                r = lchown (buf_dst, st.st_uid, st.st_gid);
+            if (r >= 0 && !S_ISLNK (st.st_mode) && !S_ISDIR (st.st_mode))
+                r = chmod (buf_dst, st.st_mode);
+
+            if (r < 0)
+            {
+                warn_fn (buf_src, errno);
+                goto err;
+            }
+
+            i += len + 1;
+        }
+
+        if (has.run)
+        {
+            if (!has.down)
+            {
+                char buf[l_dst + 1 + strlen ("down") + 1];
+                int fd;
+
+                byte_copy (buf, l_dst, dst);
+                buf[l_dst] = '/';
+                byte_copy (buf + l_dst + 1, 5, "down");
+
+                fd = open_create (buf);
+                if (fd < 0)
+                {
+                    warn_fn (buf, errno);
+                    goto err;
+                }
+                else
+                    fd_close (fd);
+            }
+
+            {
+                char buf_lnk[3 + l_dst + 1];
+                char buf_dst[sizeof (AA_SCANDIR_DIRNAME) + l_dst + 1];
+
+                byte_copy (buf_lnk, 3, "../");
+                byte_copy (buf_lnk + 3, l_dst + 1, dst);
+
+                byte_copy (buf_dst, sizeof (AA_SCANDIR_DIRNAME), AA_SCANDIR_DIRNAME "/");
+                byte_copy (buf_dst + sizeof (AA_SCANDIR_DIRNAME), l_dst + 1, dst);
+
+                if (symlink (buf_lnk, buf_dst) < 0)
+                {
+                    warn_fn (buf_dst, errno);
+                    goto err;
+                }
+            }
+        }
+    }
+
+    if (chmod (dst, mode) < 0)
+    {
+        if (has.began)
+            warn_fn (dst, errno);
+        goto err;
+    }
+
+    satmp.len = l_satmp;
+    return 0;
+
+err:
+    satmp.len = l_satmp;
+    if (!has.began)
+        return -ERR_IO;
+    else
+    {
+        unsigned int l_dst = strlen (dst);
+        char buf[1 + l_dst + 1];
+
+        *buf = '@';
+        byte_copy (buf + 1, l_dst + 1, dst);
+
+        /* rename dst servicedir by prefixing with a '@' so that aa-start would
+         * fail to find/start the service, and make it easilly noticable on the
+         * file system, since it's in an undetermined/invalid state */
+        if (rename (dst, buf) < 0)
+            warn_fn (dst, errno);
+
+        return -ERR_FAILED_ENABLE;
+    }
+}
+
+static int
+copy_from_source (const char        *name,
+                  int                len,
+                  aa_warn_fn         warn_fn,
+                  aa_enable_flags    flags,
+                  aa_auto_enable_cb  ae_cb)
+{
+    int i;
+
+    if (aa_sa_sources.len == 0)
+        return -ERR_UNKNOWN;
+
+    i = 0;
+    for (;;)
+    {
+        int l_sce = strlen (aa_sa_sources.s + i);
+        char buf[l_sce + 1 + len + 1];
+        struct stat st;
+
+        byte_copy (buf, l_sce, aa_sa_sources.s + i);
+        buf[l_sce] = '/';
+        byte_copy (buf + l_sce + 1, len, name);
+        buf[l_sce + 1 + len] = '\0';
+
+        if (stat (buf, &st) < 0)
+        {
+            if (errno != ENOENT)
+                warn_fn (buf, errno);
+        }
+        else if (!S_ISDIR (st.st_mode))
+            warn_fn (buf, ENOTDIR);
+        else
+        {
+            int r;
+
+            r = copy_dir (buf, name, st.st_mode, 0, warn_fn, flags, ae_cb,
+                    (name[len - 1] == '@') ? name + len : NULL);
+            if (r < 0)
+                return r;
+            break;
+        }
+
+        i += l_sce + 1;
+        if (i > aa_sa_sources.len)
+            return -ERR_UNKNOWN;
+    }
+
+    return 0;
+}
+
+int
+aa_enable_service (const char       *_name,
+                   aa_warn_fn        warn_fn,
+                   aa_enable_flags   flags,
+                   aa_auto_enable_cb ae_cb)
+{
+    const char *name = _name;
+    const char *instance = NULL;
+    mode_t _mode = 0; /* silence warning */
+    int l_name = strlen (name);
+    int len;
+    int r;
+
+    /* if name is a /path/to/file we get the actual/service name */
+    if (*name == '/')
+    {
+        int r;
+
+        if (l_name == 1)
+            return -ERR_INVALID_NAME;
+        r = byte_rchr (name, l_name, '/') + 1;
+        name += r;
+        l_name -= r;
+    }
+
+    if (!is_valid_service_name (name, l_name))
+        return -ERR_INVALID_NAME;
+
+    if (*_name == '/')
+    {
+        struct stat st;
+
+        if (stat (_name, &st) < 0)
+            return ERR_IO;
+        else if (S_ISREG (st.st_mode))
+            /* file; so nothing special to do, we can "drop" the path */
+            _name = name;
+        else if (!S_ISDIR (st.st_mode))
+            return (errno = EINVAL, -ERR_IO);
+        else
+            _mode = st.st_mode;
+    }
+
+    /* len is l_name unless there's a '@', then we want up to (inc.) the '@' */
+    len = byte_chr (name, l_name, '@');
+    if (len < l_name)
+    {
+        ++len;
+        instance = name + len;
+    }
+
+    r = copy_from_source (name, len, warn_fn, flags | _AA_FLAG_IS_SERVICEDIR, ae_cb);
+    if (r < 0)
+        return r;
+
+    if (name != _name)
+        return copy_dir (_name, name, _mode, 0, warn_fn, flags, ae_cb, instance);
+    else
+        return 0;
+}
diff --git a/src/libanopa/errmsg.c b/src/libanopa/errmsg.c
new file mode 100644
index 0000000..9a2e4fd
--- /dev/null
+++ b/src/libanopa/errmsg.c
@@ -0,0 +1,23 @@
+
+#include <anopa/err.h>
+
+const char const *errmsg[_NB_ERR] = {
+    "",
+    "Invalid name",
+    "Unknown service",
+    "Failed dependency",
+    "I/O error",
+    "Uable to write service status file",
+    "Unable to get into service directory",
+    "Unable to exec",
+    "Unable to setup pipes",
+    "Failed to communicate with s6",
+    "Failed",
+    "Timed out",
+    "Failed to create repository directory",
+    "Failed to create scandir directory",
+    "Failed to enable/create servicedir",
+
+    "Already up",
+    "Not up"
+};
diff --git a/src/libanopa/exec_longrun.c b/src/libanopa/exec_longrun.c
new file mode 100644
index 0000000..e88f0f9
--- /dev/null
+++ b/src/libanopa/exec_longrun.c
@@ -0,0 +1,156 @@
+
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/tai.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr2.h>
+#include <s6/s6-supervise.h>
+#include <s6/ftrigr.h>
+#include <anopa/service.h>
+#include <anopa/err.h>
+#include "service_internal.h"
+
+int
+_exec_longrun (int si, aa_mode mode)
+{
+    aa_service *s = aa_service (si);
+    s6_svstatus_t st6 = S6_SVSTATUS_ZERO;
+    int l_sn = strlen (aa_service_name (s));
+    char fifodir[l_sn + 1 + sizeof (S6_SUPERVISE_EVENTDIR)];
+    tain_t deadline;
+    int is_start = (mode == AA_MODE_START) ? 1 : 0;
+    char *event = (is_start) ? "u" : "d";
+
+    byte_copy (fifodir, l_sn, aa_service_name (s));
+    fifodir[l_sn] = '/';
+    byte_copy (fifodir + l_sn + 1, sizeof (S6_SUPERVISE_EVENTDIR), S6_SUPERVISE_EVENTDIR);
+
+    tain_addsec_g (&deadline, 1);
+    s->ft_id = ftrigr_subscribe_g (&_aa_ft, fifodir, event, 0, &deadline);
+    if (s->ft_id == 0)
+    {
+        /* this could happen e.g. if the servicedir isn't in scandir, if
+         * something failed during aa-enable for example */
+
+        s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
+        s->st.code = ERR_S6;
+        tain_copynow (&s->st.stamp);
+        aa_service_status_set_msg (&s->st, "Failed to subscribe to eventdir");
+        if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+            strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+        if (_exec_cb)
+            _exec_cb (si, s->st.event, 0);
+        return -1;
+    }
+
+    if (s6_svstatus_read (aa_service_name (s), &st6)
+            && ((is_start && !!st6.pid) || (!is_start && !st6.pid)))
+    {
+        tain_now_g ();
+
+        /* already there; unsubcribe */
+        ftrigr_unsubscribe_g (&_aa_ft, s->ft_id, &deadline);
+        s->ft_id = 0;
+
+        /* make sure our status is correct, and timestamped before s6 */
+        s->st.event = (is_start) ? AA_EVT_STARTING : AA_EVT_STOPPING;
+        tain_addsec (&s->st.stamp, &st6.stamp, -1);
+        aa_service_status_set_msg (&s->st, "");
+        if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+            strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+        if (_exec_cb)
+            _exec_cb (si, (is_start) ? -ERR_ALREADY_UP: -ERR_NOT_UP, 0);
+
+        /* this was not a failure, but we return -1 to trigger a
+         * aa_scan_mainlist() anyways, since the service changed state */
+        return -1;
+    }
+    tain_now_g ();
+
+    s->st.event = (is_start) ? AA_EVT_STARTING : AA_EVT_STOPPING;
+    tain_copynow (&s->st.stamp);
+    aa_service_status_set_msg (&s->st, "");
+    if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+    {
+        s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
+        s->st.code = ERR_WRITE_STATUS;
+        aa_service_status_set_msg (&s->st, error_str (errno));
+
+        if (_exec_cb)
+            _exec_cb (si, s->st.event, 0);
+        return -1;
+    }
+
+    {
+        char dir[l_sn + 1 + sizeof (S6_SUPERVISE_CTLDIR) + 8];
+        int r;
+
+        byte_copy (dir, l_sn, aa_service_name (s));
+        byte_copy (dir + l_sn, 9 + sizeof (S6_SUPERVISE_CTLDIR), "/" S6_SUPERVISE_CTLDIR "/control");
+
+        r = s6_svc_write (dir, event, 1);
+        if (r <= 0)
+        {
+            tain_addsec_g (&deadline, 1);
+            ftrigr_unsubscribe_g (&_aa_ft, s->ft_id, &deadline);
+            s->ft_id = 0;
+
+            s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
+            s->st.code = ERR_S6;
+            tain_copynow (&s->st.stamp);
+            aa_service_status_set_msg (&s->st, (r < 0)
+                    ? "Failed to send command"
+                    : "Supervisor not listenning");
+            if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+                strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+            if (_exec_cb)
+                _exec_cb (si, s->st.event, 0);
+            return -1;
+        }
+    }
+
+    if (is_start)
+    {
+        char buf[l_sn + 6];
+
+        byte_copy (buf, l_sn, aa_service_name (s));
+        byte_copy (buf + l_sn, 6, "/down");
+
+        unlink (buf);
+    }
+
+    if (_exec_cb)
+        _exec_cb (si, s->st.event, 0);
+    return 0;
+}
+
+int
+aa_get_longrun_info (uint16 *id, char *event)
+{
+    static int i = -1;
+    int r;
+
+    if (i == -1)
+    {
+        r = ftrigr_update (&_aa_ft);
+        if (r <= 0)
+            return r;
+        i = 0;
+    }
+
+    for ( ; i < genalloc_len (uint16, &_aa_ft.list); )
+    {
+        *id = genalloc_s (uint16, &_aa_ft.list)[i];
+        r = ftrigr_check (&_aa_ft, *id, event);
+        ++i;
+        if (r != 0)
+            return r;
+    }
+
+    i = -1;
+    return 0;
+}
diff --git a/src/libanopa/exec_oneshot.c b/src/libanopa/exec_oneshot.c
new file mode 100644
index 0000000..06ce16f
--- /dev/null
+++ b/src/libanopa/exec_oneshot.c
@@ -0,0 +1,298 @@
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/selfpipe.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/tai.h>
+#include <skalibs/uint32.h>
+#include <skalibs/error.h>
+#include <skalibs/strerr2.h>
+#include <anopa/service.h>
+#include <anopa/err.h>
+#include "service_internal.h"
+
+int
+_exec_oneshot (int si, aa_mode mode)
+{
+    aa_service *s = aa_service (si);
+    int is_start = (mode == AA_MODE_START) ? 1 : 0;
+    char * const filename = (is_start) ? AA_START_FILENAME : AA_STOP_FILENAME;
+    int l_fn = sizeof ((is_start) ? AA_START_FILENAME : AA_STOP_FILENAME);
+    int l_sn = strlen (aa_service_name (s));
+    char buf[l_sn + 1 +l_fn];
+    struct stat st;
+    const char *_err;
+    int _errno;
+    int p_int[2];
+    int p_in[2];
+    int p_out[2];
+    int p_prg[2];
+    pid_t pid;
+    char c;
+
+    byte_copy (buf, l_sn, aa_service_name (s));
+    byte_copy (buf + l_sn, 1, "/");
+    byte_copy (buf + l_sn + 1, l_fn, filename);
+
+    if (stat (buf, &st) < 0)
+    {
+        tain_now_g ();
+
+        if (errno != ENOENT)
+        {
+            _errno = errno;
+            _err = "stat ";
+            goto err;
+        }
+
+        /* nothing to do, just set it done */
+        s->st.event = (is_start) ? AA_EVT_STARTED : AA_EVT_STOPPED;
+        tain_copynow (&s->st.stamp);
+        if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+            strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+        if (_exec_cb)
+            _exec_cb (si, s->st.event, 0);
+
+        /* this was not a failure, but we return -1 to trigger a
+         * aa_scan_mainlist() anyways, since the service changed state */
+        return -1;
+    }
+
+    s->st.event = (is_start) ? AA_EVT_STARTING : AA_EVT_STOPPING;
+    tain_copynow (&s->st.stamp);
+    aa_service_status_set_msg (&s->st, "");
+    if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+    {
+        s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
+        s->st.code = ERR_WRITE_STATUS;
+        aa_service_status_set_msg (&s->st, error_str (errno));
+
+        if (_exec_cb)
+            _exec_cb (si, s->st.event, 0);
+        return -1;
+    }
+
+    if (pipecoe (p_int) < 0)
+    {
+        _errno = errno;
+        _err = "set up pipes";
+        goto err;
+    }
+    if (pipenb (p_in) < 0)
+    {
+        _errno = errno;
+        _err = "set up pipes";
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+
+        goto err;
+    }
+    if (pipenb (p_out) < 0)
+    {
+        _errno = errno;
+        _err = "set up pipes";
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        fd_close (p_in[0]);
+        fd_close (p_in[1]);
+
+        goto err;
+    }
+    if (pipenb (p_prg) < 0)
+    {
+        _errno = errno;
+        _err = "set up pipes";
+
+        fd_close (p_int[0]);
+        fd_close (p_int[1]);
+        fd_close (p_in[0]);
+        fd_close (p_in[1]);
+        fd_close (p_out[0]);
+        fd_close (p_out[1]);
+
+        goto err;
+    }
+
+    pid = fork ();
+    if (pid < 0)
+    {
+        _errno = errno;
+        _err = "fork";
+
+        fd_close (p_int[1]);
+        fd_close (p_int[0]);
+        fd_close (p_in[0]);
+        fd_close (p_in[1]);
+        fd_close (p_out[1]);
+        fd_close (p_out[0]);
+        fd_close (p_prg[1]);
+        fd_close (p_prg[0]);
+
+        goto err;
+    }
+    else if (pid == 0)
+    {
+        char * const argv[] = { filename, NULL };
+        PROG = aa_service_name (s);
+        char buf_e[UINT32_FMT];
+        uint32 e;
+
+        selfpipe_finish ();
+        fd_close (p_int[0]);
+        fd_close (p_in[1]);
+        fd_close (p_out[0]);
+        fd_close (p_prg[0]);
+        fd_close (0);
+        fd_close (1);
+        fd_close (2);
+        if (fd_move (0, p_in[0]) < 0 || fd_move (1, p_out[1]) < 0
+                || fd_copy (2, 1) < 0 || fd_move (3, p_prg[1]) < 0)
+        {
+            e = (uint32) errno;
+            fd_write (p_int[1], "p", 1);
+            uint32_pack (buf_e, e);
+            fd_write (p_int[1], buf_e, UINT32_FMT);
+            strerr_diefu1sys (ERR_IO, "set up pipes");
+        }
+
+        if (chdir (PROG) < 0)
+        {
+            e = (uint32) errno;
+            fd_write (p_int[1], "c", 1);
+            uint32_pack (buf_e, e);
+            fd_write (p_int[1], buf_e, UINT32_FMT);
+            strerr_diefu1sys (ERR_IO, "get into service directory");
+        }
+
+        buf[l_sn - 1] = '.';
+        execv (buf + l_sn - 1, argv);
+        /* if it fails... */
+        e = (uint32) errno;
+        fd_write (p_int[1], "e", 1);
+        uint32_pack (buf_e, e);
+        fd_write (p_int[1], buf_e, UINT32_FMT);
+        strerr_dieexec (ERR_IO, filename);
+    }
+
+    fd_close (p_int[1]);
+    fd_close (p_in[0]);
+    fd_close (p_out[1]);
+    fd_close (p_prg[1]);
+    switch (fd_read (p_int[0], &c, 1))
+    {
+        case 0:     /* it worked */
+            {
+                fd_close (p_int[0]);
+                s->fd_in = p_in[1];
+                s->fd_out = p_out[0];
+                s->fd_progress = p_prg[0];
+
+                tain_now_g ();
+
+                if (_exec_cb)
+                    _exec_cb (si, s->st.event, pid);
+                return 0;
+            }
+
+        case 1:     /* child failed to exec */
+            {
+                char msg[l_fn + 260];
+                char buf[UINT32_FMT];
+                uint32 e = 0;
+                int p = 0;
+                int l;
+
+                tain_now_g ();
+
+                if (fd_read (p_int[0], buf, UINT32_FMT) == UINT32_FMT)
+                    uint32_unpack (buf, &e);
+                fd_close (p_int[0]);
+                fd_close (p_in[1]);
+                fd_close (p_out[0]);
+                fd_close (p_prg[0]);
+
+                if (c == 'e')
+                {
+                    s->st.code = ERR_EXEC;
+                    byte_copy (msg, 1, " ");
+                    byte_copy (msg + 1, l_fn, filename);
+                    p += 1 + l_fn;
+                }
+                else if (c == 'p')
+                    s->st.code = ERR_PIPES;
+                else /* 'c' */
+                    s->st.code = ERR_CHDIR;
+
+                if (e > 0)
+                {
+                    if (c == 'e')
+                    {
+                        l = 2;
+                        byte_copy (msg + p, l, ": ");
+                        p += l;
+                    }
+                    l = strlen (error_str (e));
+                    if (p + l >= 260)
+                        l = 260 - p - 1;
+                    byte_copy (msg + p, l, error_str (e));
+                    p += l;
+                }
+                byte_copy (msg + p, 1, "");
+
+                s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
+                tain_copynow (&s->st.stamp);
+                aa_service_status_set_msg (&s->st, msg);
+                if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+                    strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+                if (_exec_cb)
+                    _exec_cb (si, s->st.event, 0);
+                return -1;
+            }
+
+        case -1:    /* internal failure */
+            _errno = errno;
+            _err = "read pipe";
+
+            fd_close (p_int[0]);
+            fd_close (p_in[1]);
+            fd_close (p_out[0]);
+            fd_close (p_prg[0]);
+    }
+
+err:
+    tain_now_g ();
+    s->st.event = (is_start) ? AA_EVT_STARTING_FAILED : AA_EVT_STOPPING_FAILED;
+    s->st.code = ERR_IO;
+    tain_copynow (&s->st.stamp);
+    {
+        int l_ft  = strlen ("Failed to ");
+        int l_err = strlen (_err);
+        int l_buf = strlen (buf);
+        const char *errstr = error_str (_errno);
+        int l_es = strlen (errstr);
+        char msg[l_ft + l_err + l_buf + 2 + l_es + 1];
+
+        byte_copy (msg, l_ft, "Failed to ");
+        byte_copy (msg + l_ft, l_err, _err);
+        if (*_err == 's') /* stat */
+            byte_copy (msg + l_ft + l_err, l_buf, buf);
+        else
+            l_buf = 0;
+        byte_copy (msg + l_ft + l_err + l_buf, 2, ": ");
+        byte_copy (msg + l_ft + l_err + l_buf + 2, l_es + 1, errstr);
+        aa_service_status_set_msg (&s->st, msg);
+    }
+    if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+        strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+    if (_exec_cb)
+        _exec_cb (si, s->st.event, 0);
+    return -1;
+}
diff --git a/src/libanopa/ga_int_list.c b/src/libanopa/ga_int_list.c
new file mode 100644
index 0000000..9b43dcb
--- /dev/null
+++ b/src/libanopa/ga_int_list.c
@@ -0,0 +1,47 @@
+
+#include <anopa/ga_int_list.h>
+
+int
+add_to_list (genalloc *list, int si, int check_for_dupes)
+{
+    if (check_for_dupes)
+    {
+        int len = genalloc_len (int, list);
+        int i;
+
+        for (i = 0; i < len; ++i)
+            if (list_get (list, i) == si)
+                return 0;
+    }
+
+    genalloc_append (int, list, &si);
+    return 1;
+}
+
+int
+remove_from_list (genalloc *list, int si)
+{
+    int len = genalloc_len (int, list);
+    int i;
+
+    for (i = 0; i < len; ++i)
+        if (list_get (list, i) == si)
+        {
+            ga_remove (int, list, i);
+            return 1;
+        }
+
+    return 0;
+}
+
+int
+is_in_list (genalloc *list, int si)
+{
+    int len = genalloc_len (int, list);
+    int i;
+
+    for (i = 0; i < len; ++i)
+        if (list_get (list, i) == si)
+            return 1;
+    return 0;
+}
diff --git a/src/libanopa/init_repo.c b/src/libanopa/init_repo.c
new file mode 100644
index 0000000..228a952
--- /dev/null
+++ b/src/libanopa/init_repo.c
@@ -0,0 +1,57 @@
+
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <anopa/err.h>
+#include <anopa/common.h>
+#include <anopa/init_repo.h>
+
+int
+aa_init_repo (const char *path_repo, aa_repo_init ri)
+{
+    int amode;
+
+    umask (0);
+
+    if (ri == AA_REPO_CREATE && mkdir (path_repo,
+                S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0)
+    {
+        struct stat st;
+
+        if (errno != EEXIST)
+            return -ERR_IO_REPODIR;
+        if (stat (path_repo, &st) < 0)
+            return -ERR_IO_REPODIR;
+        if (!S_ISDIR (st.st_mode))
+        {
+            errno = ENOTDIR;
+            return -ERR_IO_REPODIR;
+        }
+    }
+    if (chdir (path_repo) < 0)
+        return -ERR_IO;
+
+    if (ri == AA_REPO_CREATE && mkdir (AA_SCANDIR_DIRNAME, S_IRWXU) < 0)
+    {
+        struct stat st;
+
+        if (errno != EEXIST)
+            return -ERR_IO_SCANDIR;
+        if (stat (AA_SCANDIR_DIRNAME, &st) < 0)
+            return -ERR_IO_SCANDIR;
+        if (!S_ISDIR (st.st_mode))
+        {
+            errno = ENOTDIR;
+            return -ERR_IO_SCANDIR;
+        }
+    }
+
+    amode = R_OK;
+    if (ri != AA_REPO_READ)
+        amode |= W_OK;
+
+    if (access (".", amode) < 0)
+        return -ERR_IO;
+
+    return 0;
+}
diff --git a/src/libanopa/output.c b/src/libanopa/output.c
new file mode 100644
index 0000000..c963495
--- /dev/null
+++ b/src/libanopa/output.c
@@ -0,0 +1,99 @@
+
+#include <unistd.h> /* isatty() */
+#include <skalibs/bytestr.h>
+#include <skalibs/buffer.h>
+#include <anopa/output.h>
+
+static int istty[2] = { 0, 0 };
+static int do_both = 0;
+
+#define putb_noflush(w,s,l) buffer_putnoflush ((w) ? buffer_2 : buffer_1small, s, l)
+#define putb_flush(w,s,l)   buffer_putflush ((w) ? buffer_2 : buffer_1small, s, l)
+
+void
+aa_init_output (int mode_both)
+{
+    istty[0] = isatty (1);
+    istty[1] = isatty (2);
+    do_both = mode_both;
+}
+
+void
+aa_bb_noflush (int where, const char *s, int len)
+{
+    putb_noflush (where, s, len);
+    if (do_both)
+        putb_noflush (!where, s, len);
+}
+
+void
+aa_bb_flush (int where, const char *s, int len)
+{
+    putb_flush (where, s, len);
+    if (do_both)
+        putb_flush (!where, s, len);
+}
+
+void
+aa_ib_noflush (int where, const char *s, int len)
+{
+    if (istty[where])
+        putb_noflush (where, s, len);
+    if (do_both && istty[!where])
+        putb_noflush (!where, s, len);
+}
+
+void
+aa_ib_flush (int where, const char *s, int len)
+{
+    if (istty[where])
+        putb_flush (where, s, len);
+    if (do_both && istty[!where])
+        putb_flush (!where, s, len);
+}
+
+void
+aa_bs_end (int where)
+{
+    aa_is_noflush (where, ANSI_HIGHLIGHT_OFF);
+    aa_bs_flush (where, "\n");
+}
+
+void
+aa_put_err (const char *name, const char *msg, int end)
+{
+    aa_is_noflush (AA_ERR, ANSI_HIGHLIGHT_RED_ON);
+    aa_bs_noflush (AA_ERR, "==> ERROR: ");
+    aa_is_noflush (AA_ERR, ANSI_HIGHLIGHT_ON);
+    aa_bs_noflush (AA_ERR, name);
+    aa_bs_noflush (AA_ERR, ": ");
+    aa_bs_noflush (AA_ERR, msg);
+    if (end)
+        aa_end_err ();
+}
+
+void
+aa_put_warn (const char *name, const char *msg, int end)
+{
+    aa_is_noflush (AA_ERR, ANSI_HIGHLIGHT_YELLOW_ON);
+    aa_bs_noflush (AA_ERR, "==> WARNING: ");
+    aa_is_noflush (AA_ERR, ANSI_HIGHLIGHT_ON);
+    aa_bs_noflush (AA_ERR, name);
+    aa_bs_noflush (AA_ERR, ": ");
+    aa_bs_noflush (AA_ERR, msg);
+    if (end)
+        aa_end_warn ();
+}
+
+void
+aa_put_title (int main, const char *name, const char *title, int end)
+{
+    aa_is_noflush (AA_OUT, (main) ? ANSI_HIGHLIGHT_GREEN_ON : ANSI_HIGHLIGHT_BLUE_ON);
+    aa_bs_noflush (AA_OUT, (main) ? "==> " : "  -> ");
+    aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+    aa_bs_noflush (AA_OUT, name);
+    aa_bs_noflush (AA_OUT, ": ");
+    aa_bs_noflush (AA_OUT, title);
+    if (end)
+        aa_end_title ();
+}
diff --git a/src/libanopa/progress.c b/src/libanopa/progress.c
new file mode 100644
index 0000000..129e5bc
--- /dev/null
+++ b/src/libanopa/progress.c
@@ -0,0 +1,188 @@
+
+#include <skalibs/bytestr.h>
+#include <skalibs/uint.h>
+#include <anopa/progress.h>
+#include <anopa/output.h>
+
+#if 0
+static const char *utf8_edge[] = { "\u2595", " ", "\u258f" };
+static const char *utf8_bars[] = { " ", "\u258f", "\u258e", "\u258d", "\u258c", "\u258b", "\u258a", "\u2589", "\u2588" };
+#define utf8_per_c  sizeof (utf8_bars) / sizeof (*utf8_bars)
+#else
+static const char *utf8_edge[] = { "\u2590", " ", "\u258c" };
+static const char *utf8_bars[] = { " ", "\u258c", "\u2588" };
+#define utf8_per_c  sizeof (utf8_bars) / sizeof (*utf8_bars)
+#endif
+
+static const char *ascii_edge[] = { "[", " ", "]" };
+static const char *ascii_bars[] = { " ", "-", "=", "#" };
+#define ascii_per_c  sizeof (ascii_bars) / sizeof (*ascii_bars)
+
+void
+aa_progress_free (aa_progress *p)
+{
+    stralloc_free (&p->sa);
+}
+
+int
+aa_progress_update (aa_progress *pg)
+{
+    char *s;
+    int skip;
+    int len;
+    int rr;
+    int cur;
+    int max;
+    int r;
+
+    /* sanity: we require at least a NUL byte (for empty msg) */
+    if (pg->sa.len == 0)
+        return -1;
+
+    /* moving past msg */
+    skip = byte_chr (pg->sa.s, pg->sa.len, '\0') + 1;
+    s = pg->sa.s + skip;
+    len = pg->sa.len - skip;
+    if (len <= 0)
+        return -1;
+
+    /* now look for last full line to process */
+    r = byte_rchr (s, len, '\n');
+    if (r >= len)
+        return -1;
+    s[r] = '\0';
+
+    rr = byte_rchr (s, r, '\n');
+    if (rr < r)
+    {
+        s += rr + 1;
+        len = r - rr - 1;
+    }
+    else
+        len = r;
+
+    /* step */
+    for (rr = 0; *s != ' ' && len > 0; ++s, --len)
+    {
+        if (*s < '0' || *s > '9')
+            goto err;
+        rr *= 10;
+        rr += *s - '0';
+    }
+    if (len <= 1)
+        goto err;
+    ++s; --len;
+
+    for (cur = 0; *s != ' ' && len > 0; ++s, --len)
+    {
+        if (*s < '0' || *s > '9')
+            goto err;
+        cur *= 10;
+        cur += *s - '0';
+    }
+    if (len <= 1)
+        goto err;
+    ++s; --len;
+
+    for (max = 0; *s != ' ' && len > 0; ++s, --len)
+    {
+        if (*s < '0' || *s > '9')
+            goto err;
+        max *= 10;
+        max += *s - '0';
+    }
+    if (*s == ' ')
+    {
+        ++s; --len;
+    }
+    else if (len > 0)
+        goto err;
+
+    pg->step = rr;
+    pg->pctg = (double) cur / (double) max;
+
+    ++len; /* include NUL */
+    memmove (pg->sa.s, s, len);
+    pg->sa.len = len;
+    return 0;
+
+err:
+    s = pg->sa.s + skip + r + 1;
+    len = pg->sa.len - skip - r - 1;
+    memmove (pg->sa.s + skip, s, len);
+    pg->sa.len = skip + len;
+    return -2;
+}
+
+void
+aa_progress_draw (aa_progress *pg, const char *title, int cols, int is_utf8)
+{
+    const char **edge;
+    const char **bars;
+    int per_c;
+    char buf[UINT_FMT];
+    unsigned int p1;
+    unsigned int p2;
+    int w;
+    double d;
+    int n;
+    int i;
+
+    p1 = 100 * pg->pctg;
+    p2 = 10000 * pg->pctg - (100 * p1);
+    if (p2 == 100)
+    {
+        ++p1;
+        p2 = 0;
+    }
+
+    if (is_utf8)
+    {
+        edge = utf8_edge;
+        bars = utf8_bars;
+        per_c = utf8_per_c;
+    }
+    else
+    {
+        edge = ascii_edge;
+        bars = ascii_bars;
+        per_c = ascii_per_c;
+    }
+
+    /* 7: for "100.0% "   10: margin on the right */
+    w = cols - strlen (title) - 1 - 7 - 10;
+    if (pg->sa.s[0] != '\0')
+        w -= byte_chr (pg->sa.s, pg->sa.len, '\0');
+    if (w < 10)
+        w = 0;
+    d = pg->pctg * w * per_c;
+    n = d / per_c;
+
+    aa_is_noflush (AA_OUT, title);
+    aa_is_noflush (AA_OUT, ":");
+    if (w)
+    {
+        aa_is_noflush (AA_OUT, edge[0]);
+        for (i = 0; i < n; ++i)
+            aa_is_noflush (AA_OUT, bars[per_c - 1]);
+        if (n < w)
+            aa_is_noflush (AA_OUT, bars[(int) d % per_c]);
+        for (i = n + 1; i < w; ++i)
+            aa_is_noflush (AA_OUT, edge[1]);
+        aa_is_noflush (AA_OUT, edge[2]);
+    }
+    aa_is_noflush (AA_OUT, " ");
+
+    buf[uint_fmt (buf, p1)] = '\0';
+    aa_is_noflush (AA_OUT, buf);
+    aa_is_noflush (AA_OUT, ".");
+    if (uint_fmt (buf, p2) == 1)
+        buf[1] = '0';
+    buf[2] = '\0';
+    aa_is_noflush (AA_OUT, buf);
+    aa_is_noflush (AA_OUT, "% ");
+
+    if (pg->sa.s[0] != '\0')
+        aa_is_noflush (AA_OUT, pg->sa.s);
+    aa_is_flush (AA_OUT, "\n");
+}
diff --git a/src/libanopa/sa_sources.c b/src/libanopa/sa_sources.c
new file mode 100644
index 0000000..da1161c
--- /dev/null
+++ b/src/libanopa/sa_sources.c
@@ -0,0 +1,5 @@
+
+#include <skalibs/stralloc.h>
+#include <anopa/enable_service.h>
+
+stralloc aa_sa_sources = STRALLOC_ZERO;
diff --git a/src/libanopa/scan_dir.c b/src/libanopa/scan_dir.c
new file mode 100644
index 0000000..34d107e
--- /dev/null
+++ b/src/libanopa/scan_dir.c
@@ -0,0 +1,69 @@
+
+#define _BSD_SOURCE
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <skalibs/direntry.h>
+#include <skalibs/stralloc.h>
+#include <anopa/err.h>
+#include <anopa/scan_dir.h>
+
+
+/* breaking the rule here: we get a stralloc* but we don't own it, it's just so
+ * we can use it if needed to stat() */
+int
+aa_scan_dir (stralloc *sa, int files_only, aa_sd_it_fn iterator, void *data)
+{
+    DIR *dir;
+    int e = 0;
+    int r = 0;
+
+    dir = opendir (sa->s);
+    if (!dir)
+        return -ERR_IO;
+
+    for (;;)
+    {
+        direntry *d;
+
+        errno = 0;
+        d = readdir (dir);
+        if (!d)
+        {
+            e = errno;
+            break;
+        }
+        if (d->d_name[0] == '.'
+                && (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+            continue;
+        if (d->d_type == DT_UNKNOWN)
+        {
+            struct stat st;
+            int l;
+            int rr;
+
+            l = sa->len;
+            sa->s[l - 1] = '/';
+            stralloc_catb (sa, d->d_name, str_len (d->d_name) + 1);
+            rr = stat (sa->s, &st);
+            sa->len = l;
+            sa->s[l - 1] = '\0';
+            if (rr != 0 || (!S_ISREG (st.st_mode) && (files_only || !S_ISDIR (st.st_mode))))
+                continue;
+        }
+        else if (d->d_type != DT_REG && (files_only || d->d_type != DT_DIR))
+            continue;
+
+        r = iterator (d, data);
+        if (r < 0)
+            break;
+    }
+    dir_close (dir);
+
+    if (e > 0)
+    {
+        r = -ERR_IO;
+        errno = e;
+    }
+    return r;
+}
diff --git a/src/libanopa/service.c b/src/libanopa/service.c
new file mode 100644
index 0000000..beaaa44
--- /dev/null
+++ b/src/libanopa/service.c
@@ -0,0 +1,517 @@
+
+#include <sys/stat.h>
+#include <errno.h>
+#include <skalibs/djbunix.h> /* fd_close() */
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/direntry.h>
+#include <skalibs/tai.h>
+#include <skalibs/strerr2.h>
+#include <s6/s6-supervise.h>
+#include <s6/ftrigr.h>
+#include <anopa/service.h>
+#include <anopa/ga_int_list.h>
+#include <anopa/scan_dir.h>
+#include <anopa/err.h>
+#include "service_internal.h"
+
+static aa_close_fd_fn close_fd;
+
+static void
+free_service (aa_service *s)
+{
+    genalloc_free (int, &s->needs);
+    genalloc_free (int, &s->wants);
+    genalloc_free (int, &s->after);
+    aa_service_status_free (&s->st);
+    if (s->fd_out > 0)
+        close_fd (s->fd_out);
+    if (s->fd_progress > 0)
+        close_fd (s->fd_progress);
+    stralloc_free (&s->sa_out);
+}
+
+void
+aa_free_services (aa_close_fd_fn _close_fd)
+{
+    if (_close_fd)
+        close_fd = _close_fd;
+    else
+        close_fd = (aa_close_fd_fn) fd_close;
+    genalloc_deepfree (aa_service, &aa_services, free_service);
+}
+
+int
+aa_add_name (const char *name)
+{
+    int offset = aa_names.len;
+    if (!stralloc_catb (&aa_names, name, strlen (name) + 1))
+        return -1;
+    return offset;
+}
+
+static int
+get_new_service (const char *name)
+{
+    aa_service s = {
+        .nb_mark = 0,
+        .needs = GENALLOC_ZERO,
+        .wants = GENALLOC_ZERO,
+        .after = GENALLOC_ZERO,
+        .ls = AA_LOAD_NOT,
+        .st.event = AA_EVT_NONE,
+        .st.sa = STRALLOC_ZERO,
+        .st.type = AA_TYPE_UNKNOWN,
+        .ft_id = 0,
+        .sa_out = STRALLOC_ZERO,
+        .pi = -1
+    };
+    struct stat st;
+
+    if (stat (name, &st) < 0)
+    {
+        if (errno == ENOENT)
+            return -ERR_UNKNOWN;
+        else
+            return -ERR_IO;
+    }
+    else if (!S_ISDIR (st.st_mode))
+        return (errno = ENOTDIR, -ERR_IO);
+
+    s.offset_name = aa_add_name (name);
+    genalloc_append (aa_service, &aa_services, &s);
+    return genalloc_len (aa_service, &aa_services) - 1;
+}
+
+static int
+get_from_list (genalloc *list, const char *name)
+{
+    int l = genalloc_len (int, list);
+    int i;
+
+    for (i = 0; i < l; ++i)
+        if (!str_diff (name, aa_service_name (aa_service (list_get (list, i)))))
+            return list_get (list, i);
+
+    return -1;
+}
+
+int
+aa_get_service (const char *name, int *si, int new_in_main)
+{
+    *si = get_from_list (&aa_main_list, name);
+    if (*si >= 0)
+        return AA_SERVICE_FROM_MAIN;
+
+    *si = get_from_list (&aa_tmp_list, name);
+    if (*si >= 0)
+        return AA_SERVICE_FROM_TMP;
+
+    *si = get_new_service (name);
+    if (*si < 0)
+        return *si;
+
+    if (new_in_main)
+    {
+        add_to_list (&aa_main_list, *si, 0);
+        return AA_SERVICE_FROM_MAIN;
+    }
+    else
+    {
+        add_to_list (&aa_tmp_list, *si, 0);
+        return AA_SERVICE_FROM_TMP;
+    }
+}
+
+int
+aa_ensure_service_loaded (int si, aa_mode mode, int no_wants, aa_load_fail_cb lf_cb)
+{
+    stralloc sa = STRALLOC_ZERO;
+    struct it_data it_data = { .si = si, .no_wants = no_wants, .lf_cb = lf_cb };
+    int r;
+
+    if (aa_service (si)->ls == AA_LOAD_DONE || aa_service (si)->ls == AA_LOAD_ING)
+        return 0;
+    else if (aa_service (si)->ls == AA_LOAD_FAIL)
+        return -aa_service (si)->st.code;
+
+    {
+        aa_service_status *svst = &aa_service (si)->st;
+        struct stat st;
+        int l_sn = strlen (aa_service_name (aa_service (si)));
+        char buf[l_sn + 5];
+
+        byte_copy (buf, l_sn, aa_service_name (aa_service (si)));
+        byte_copy (buf + l_sn, 5, "/run");
+
+        if (stat (buf, &st) < 0)
+        {
+            if (errno != ENOENT)
+                return -ERR_IO;
+            else
+                svst->type = AA_TYPE_ONESHOT;
+        }
+        else
+            svst->type = AA_TYPE_LONGRUN;
+    }
+
+    {
+        aa_service_status *svst = &aa_service (si)->st;
+        int chk_st;
+        int is_up;
+
+        chk_st = aa_service_status_read (svst, aa_service_name (aa_service (si))) == 0;
+        is_up = 0;
+
+        if (svst->type == AA_TYPE_LONGRUN)
+        {
+            s6_svstatus_t st6 = S6_SVSTATUS_ZERO;
+
+            if (s6_svstatus_read (aa_service_name (aa_service (si)), &st6))
+            {
+                chk_st = 0;
+                is_up = !!st6.pid;
+            }
+            tain_now_g ();
+        }
+
+        if (chk_st)
+            is_up = (svst->event == AA_EVT_STARTED || svst->event == AA_EVT_STARTING
+                    || svst->event == AA_EVT_STOPPING_FAILED
+                    || svst->event == AA_EVT_STOP_FAILED);
+
+        if (mode == AA_MODE_START && is_up)
+        {
+            /* if already good, we "fail" because there's no need to load the
+             * service, it's already good. This error will be silently ignored
+             * */
+            aa_service (si)->ls = AA_LOAD_FAIL;
+            /* this isn't actually true, but we won't save it to file */
+            svst->code = ERR_ALREADY_UP;
+            return -ERR_ALREADY_UP;
+        }
+        else if (mode == AA_MODE_STOP && !is_up)
+        {
+            /* if not up, we "fail" because we can't stop it */
+            aa_service (si)->ls = AA_LOAD_FAIL;
+            /* this isn't actually true, but we won't save it to file */
+            svst->code = ERR_NOT_UP;
+            return -ERR_NOT_UP;
+        }
+    }
+
+    aa_service (si)->ls = AA_LOAD_ING;
+
+    stralloc_cats (&sa, aa_service_name (aa_service (si)));
+
+    stralloc_catb (&sa, "/needs", strlen ("/needs") + 1);
+    r = aa_scan_dir (&sa, 1,
+            (mode == AA_MODE_START) ? _it_start_needs : _it_stop_after,
+            &it_data);
+    /* we can get ERR_IO either from aa_scan_dir() itself, or from the iterator
+     * function. But since we haven't checked that the directory (needs) does
+     * exist, ERR_IO w/ ENOENT simply means it doesn't, and isn't an error.
+     * This works because there's no ENOENT from aa_get_service(), since that
+     * won't be an ERR_IO but an ERR_UNKNOWN */
+    if (r < 0 && (r != -ERR_IO || errno != ENOENT))
+        goto err;
+
+    sa.len -= strlen ("needs") + 1;
+    if (mode == AA_MODE_START && !no_wants)
+    {
+        stralloc_catb (&sa, "wants", strlen ("wants") + 1);
+        r = aa_scan_dir (&sa, 1, _it_start_wants, &it_data);
+        if (r < 0 && (r != -ERR_IO || errno != ENOENT))
+            goto err;
+
+        sa.len -= strlen ("wants") + 1;
+    }
+    stralloc_catb (&sa, "after", strlen ("after") + 1);
+    r = aa_scan_dir (&sa, 1,
+            (mode == AA_MODE_START) ? _it_start_after : _it_stop_after,
+            &it_data);
+    if (r < 0 && (r != -ERR_IO || errno != ENOENT))
+        goto err;
+
+    sa.len -= strlen ("after") + 1;
+    stralloc_catb (&sa, "before", strlen ("before") + 1);
+    r = aa_scan_dir (&sa, 1,
+            (mode == AA_MODE_START) ? _it_start_before : _it_stop_before,
+            &it_data);
+    if (r < 0 && (r != -ERR_IO || errno != ENOENT))
+        goto err;
+
+    stralloc_free (&sa);
+    aa_service (si)->ls = AA_LOAD_DONE;
+    tain_now_g ();
+    return 0;
+
+err:
+    aa_service (si)->ls = AA_LOAD_FAIL;
+    stralloc_free (&sa);
+    tain_now_g ();
+    return r;
+}
+
+static int
+check_afters (int si, int *sli, int *has_longrun)
+{
+    aa_service *s = aa_service (si);
+    int org = genalloc_len (int, &aa_tmp_list);
+    int i;
+
+    if (s->ls == AA_LOAD_DONE_CHECKED)
+        return 0;
+
+    if (!add_to_list (&aa_tmp_list, si, 1))
+    {
+        *sli = si;
+        return -1;
+    }
+
+    for (i = 0; i < genalloc_len (int, &s->after); )
+    {
+        int sai;
+
+        sai = list_get (&s->after, i);
+        if ((aa_service (sai)->ls != AA_LOAD_DONE
+                    && aa_service (sai)->ls != AA_LOAD_DONE_CHECKED)
+                || !is_in_list (&aa_main_list, sai))
+        {
+            remove_from_list (&s->after, sai);
+            continue;
+        }
+
+        if (check_afters (sai, sli, has_longrun) < 0)
+            return -1;
+        ++i;
+    }
+
+    if (s->st.type == AA_TYPE_LONGRUN && !*has_longrun)
+        *has_longrun = 1;
+
+    genalloc_setlen (int, &aa_tmp_list, org);
+    s->ls = AA_LOAD_DONE_CHECKED;
+
+    return 0;
+}
+
+int
+aa_prepare_mainlist (aa_prepare_cb prepare_cb, aa_exec_cb exec_cb)
+{
+    int has_longrun;
+    int i;
+
+    _exec_cb = exec_cb;
+    aa_tmp_list.len = 0;
+
+    /* scan main_list to remove unneeded afters and check for loops */
+    for (i = 0; i < genalloc_len (int, &aa_main_list); )
+    {
+        int si;
+        int sli;
+
+        si = list_get (&aa_main_list, i);
+
+        /* check the after-s of the service, recursively. It will remove any
+         * after that's not loaded or in the main list, i.e. that won't be
+         * started.
+         * It also constructs a list going down, to find any loop (e.g. a after
+         * b after a), placing it in aa_tmp_list. Should be noted that the list
+         * might be "a,b,c,d" with sli set to c if the loop is actually c->d->c
+         * but was found from b which was itself after a; hence we need to find
+         * the "real" start of the loop.
+         */
+        if (check_afters (si, &sli, &has_longrun) < 0)
+        {
+            int l;
+            int j;
+            int found = 0;
+
+            add_to_list (&aa_tmp_list, sli, 0);
+            l = genalloc_len (int, &aa_tmp_list);
+            for (j = 0; j < l - 1; ++j)
+            {
+                int cur;
+                int next;
+
+                cur = list_get (&aa_tmp_list, j);
+                if (!found && cur == sli)
+                    found = j + 1;
+                if (!found)
+                    continue;
+
+                next = list_get (&aa_tmp_list, j + 1);
+                /* remove the first after link that's not a need as well */
+                if (!is_in_list (&aa_service (cur)->needs, next))
+                {
+                    remove_from_list (&aa_service (cur)->after, next);
+                    if (prepare_cb)
+                        prepare_cb (cur, next, 0, found - 1);
+                    break;
+                }
+            }
+
+            /* this is actually a loop of needs */
+            if (j >= l - 1)
+            {
+                int cur;
+                int next;
+
+                /* we'll remove the last one (both needs & after) on the loop,
+                 * so the further one away from the explicitly asked to start
+                 * service, so it might break it less... though that really
+                 * doesn't mean much, plus it might also have been explicitly
+                 * asked as well. Either way, major config error, fix it user! */
+                cur = list_get (&aa_tmp_list, l - 2);
+                next = list_get (&aa_tmp_list, l - 1);
+
+                remove_from_list (&aa_service (cur)->needs, next);
+                remove_from_list (&aa_service (cur)->after, next);
+                if (prepare_cb)
+                    prepare_cb (cur, next, 1, found - 1);
+            }
+        }
+        else
+            ++i;
+
+       aa_tmp_list.len = 0;
+    }
+
+    if (has_longrun)
+    {
+        tain_t deadline;
+
+        tain_addsec_g (&deadline, 1);
+        if (!ftrigr_startf_g (&_aa_ft, &deadline))
+            return -1;
+        else
+            return ftrigr_fd (&_aa_ft);
+    }
+
+    return 0;
+}
+
+static int
+service_is_ok (aa_service *s)
+{
+    aa_service_status *svst = &s->st;
+    s6_svstatus_t st6 = S6_SVSTATUS_ZERO;
+    int r;
+
+    if (svst->type == AA_TYPE_ONESHOT)
+        return (svst->event == AA_EVT_STARTED) ? 1 : 0;
+
+    /* TYPE_LONGRUN -- we make assumptions here:
+     * - we have a local status, since we started the service
+     * - if there's no s6 status, that's a fail (probably fail to even exec run)
+     * - we compare stamp, if s6 is more recent, it's good (since we got the
+     *   event we were waiting for); else it's a fail (must be our
+     *   EVT_STARTING_FAILED, might be an ERR_TIMEDOUT if we're still waiting
+     *   for the 'U' event (ready)). Actually we'll allow for our event to be
+     *   EVT_STARTING because there's a possible race condition there.
+     */
+    if (s6_svstatus_read (aa_service_name (s), &st6)
+            && (tain_less (&svst->stamp, &st6.stamp) || svst->event == AA_EVT_STARTING))
+        r = 1;
+    else
+        r = 0;
+
+    tain_now_g ();
+    return r;
+}
+
+void
+aa_scan_mainlist (aa_scan_cb scan_cb, aa_mode mode)
+{
+    int i;
+
+    for (i = 0; i < genalloc_len (int, &aa_main_list); )
+    {
+        aa_service *s;
+        int si;
+        int j;
+
+        si = list_get (&aa_main_list, i);
+        s = aa_service (si);
+
+        for (j = 0; j < genalloc_len (int, &s->needs); )
+        {
+            int sni;
+
+            sni = list_get (&s->needs, j);
+            if (is_in_list (&aa_main_list, sni))
+            {
+                ++j;
+                continue;
+            }
+
+            if (service_is_ok (aa_service (sni)))
+            {
+                remove_from_list (&s->needs, sni);
+                remove_from_list (&s->after, sni);
+                continue;
+            }
+
+            aa_service_status_set_err (&s->st, ERR_DEPEND, aa_service_name (aa_service (sni)));
+            if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+                strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+            remove_from_list (&aa_main_list, si);
+
+            if (scan_cb)
+                scan_cb (si, sni);
+
+            si = -1;
+            break;
+        }
+        if (si < 0)
+        {
+            i = 0;
+            continue;
+        }
+
+        for (j = 0; j < genalloc_len (int, &s->after); )
+        {
+            int sai;
+
+            sai = list_get (&s->after, j);
+            if (is_in_list (&aa_main_list, sai))
+                ++j;
+            else
+                remove_from_list (&s->after, sai);
+        }
+
+        if (genalloc_len (int, &s->after) == 0
+                && ((mode == AA_MODE_START && s->st.event != AA_EVT_STARTING)
+                    || (mode == AA_MODE_STOP && s->st.event != AA_EVT_STOPPING))
+                && aa_exec_service (si, mode) < 0)
+            /* failed to exec service, was removed from main_list, so we need to
+             * rescan from top */
+            i = -1;
+
+        ++i;
+    }
+}
+
+int
+aa_exec_service (int si, aa_mode mode)
+{
+    int r;
+
+    if (_exec_cb)
+        /* ugly hack to announce "Starting/Stopping foobar..."; needed because
+         * we use common code for aa-start & aa-stop, so... yeah */
+        _exec_cb (si, 0, (pid_t) mode);
+
+    if (aa_service (si)->st.type == AA_TYPE_ONESHOT)
+        r = _exec_oneshot (si, mode);
+    else
+        r = _exec_longrun (si, mode);
+
+    if (r < 0)
+        remove_from_list (&aa_main_list, si);
+
+    return r;
+}
diff --git a/src/libanopa/service_internal.h b/src/libanopa/service_internal.h
new file mode 100644
index 0000000..3d800fb
--- /dev/null
+++ b/src/libanopa/service_internal.h
@@ -0,0 +1,30 @@
+
+#ifndef AA_SERVICE_INTERNAL_H
+#define AA_SERVICE_INTERNAL_H
+
+#include <skalibs/direntry.h>
+#include <anopa/service.h>
+
+extern ftrigr_t _aa_ft;
+extern aa_exec_cb _exec_cb;
+
+struct it_data
+{
+    int si;
+    int no_wants;
+    aa_load_fail_cb lf_cb;
+};
+
+extern int _it_start_needs  (direntry *d, void *data);
+extern int _it_start_wants  (direntry *d, void *data);
+extern int _it_start_after  (direntry *d, void *data);
+extern int _it_start_before (direntry *d, void *data);
+
+extern int _it_stop_needs   (direntry *d, void *data);
+extern int _it_stop_after   (direntry *d, void *data);
+extern int _it_stop_before  (direntry *d, void *data);
+
+extern int _exec_oneshot (int si, aa_mode mode);
+extern int _exec_longrun (int si, aa_mode mode);
+
+#endif /* AA_SERVICE_INTERNAL_H */
diff --git a/src/libanopa/service_start.c b/src/libanopa/service_start.c
new file mode 100644
index 0000000..6e93f40
--- /dev/null
+++ b/src/libanopa/service_start.c
@@ -0,0 +1,161 @@
+
+#include <skalibs/direntry.h>
+#include <skalibs/strerr2.h>
+#include <anopa/service.h>
+#include <anopa/ga_int_list.h>
+#include <anopa/err.h>
+#include <anopa/service_status.h>
+#include "service_internal.h"
+
+void
+aa_unmark_service (int si)
+{
+    aa_service *s = aa_service (si);
+    int i;
+
+    if (--s->nb_mark > 0)
+        return;
+
+    for (i = 0; i < genalloc_len (int, &s->needs); ++i)
+        aa_unmark_service (list_get (&s->needs, i));
+    for (i = 0; i < genalloc_len (int, &s->wants); ++i)
+        aa_unmark_service (list_get (&s->wants, i));
+
+    add_to_list (&aa_tmp_list, si, 0);
+    remove_from_list (&aa_main_list, si);
+}
+
+int
+aa_mark_service (int si, int in_main, int no_wants, aa_load_fail_cb lf_cb)
+{
+    int r;
+
+    r = aa_ensure_service_loaded (si, AA_MODE_START, no_wants, lf_cb);
+    if (r < 0)
+    {
+        if (in_main)
+        {
+            add_to_list (&aa_tmp_list, si, 0);
+            remove_from_list (&aa_main_list, si);
+        }
+        return r;
+    }
+
+    if (!in_main)
+    {
+        add_to_list (&aa_main_list, si, 0);
+        remove_from_list (&aa_tmp_list, si);
+    }
+
+    aa_service (si)->nb_mark++;
+    return 0;
+}
+
+int
+_it_start_needs (direntry *d, void *data)
+{
+    struct it_data *it_data = data;
+    int type;
+    int sni;
+    int r;
+
+    tain_now_g ();
+    type = aa_get_service (d->d_name, &sni, 1);
+    if (type < 0)
+        r = type;
+    else
+        r = aa_mark_service (sni, type == AA_SERVICE_FROM_MAIN,
+                it_data->no_wants, it_data->lf_cb);
+    if (r == -ERR_ALREADY_UP)
+        return 0;
+    else if (r < 0)
+    {
+        aa_service *s = aa_service (it_data->si);
+        const char *name = aa_service_name (s);
+        int l_n = strlen (name);
+        int l_em = strlen (errmsg[-r]);
+        char buf[l_n + 2 + l_em + 1];
+        int l = genalloc_len (int, &s->needs);
+        int i;
+
+        for (i = 0; i < l; ++i)
+            aa_unmark_service (list_get (&s->needs, i));
+
+        byte_copy (buf, l_n, name);
+        byte_copy (buf + l_n, 2, ": ");
+        byte_copy (buf + l_n + 2, l_em + 1, errmsg[-r]);
+
+        aa_service_status_set_err (&s->st, ERR_DEPEND, buf);
+        if (aa_service_status_write (&s->st, aa_service_name (s)) < 0)
+            strerr_warnwu2sys ("write service status file for ", aa_service_name (s));
+
+        if (it_data->lf_cb)
+            it_data->lf_cb (it_data->si, AA_LOADFAIL_NEEDS, d->d_name, -r);
+
+        return -ERR_DEPEND;
+    }
+
+    add_to_list (&aa_service (it_data->si)->needs, sni, 0);
+    add_to_list (&aa_service (it_data->si)->after, sni, 1);
+    return 0;
+}
+
+int
+_it_start_wants (direntry *d, void *data)
+{
+    struct it_data *it_data = data;
+    int type;
+    int swi;
+    int r;
+
+    tain_now_g ();
+    type = aa_get_service (d->d_name, &swi, 1);
+    if (type < 0)
+        r = type;
+    else
+        r = aa_mark_service (swi, type == AA_SERVICE_FROM_MAIN,
+                it_data->no_wants, it_data->lf_cb);
+    if (r == -ERR_ALREADY_UP)
+        return 0;
+    if (r < 0)
+    {
+        if (it_data->lf_cb)
+            it_data->lf_cb (it_data->si, AA_LOADFAIL_WANTS, d->d_name, -r);
+        return r;
+    }
+
+    add_to_list (&aa_service (it_data->si)->wants, swi, 0);
+    return 0;
+}
+
+int
+_it_start_after (direntry *d, void *data)
+{
+    struct it_data *it_data = data;
+    int sai;
+    int r;
+
+    tain_now_g ();
+    r = aa_get_service (d->d_name, &sai, 0);
+    if (r < 0)
+        return 0;
+
+    add_to_list (&aa_service (it_data->si)->after, sai, 1);
+    return 0;
+}
+
+int
+_it_start_before (direntry *d, void *data)
+{
+    struct it_data *it_data = data;
+    int sbi;
+    int r;
+
+    tain_now_g ();
+    r = aa_get_service (d->d_name, &sbi, 0);
+    if (r < 0)
+        return 0;
+
+    add_to_list (&aa_service (sbi)->after, it_data->si, 1);
+    return 0;
+}
diff --git a/src/libanopa/service_status.c b/src/libanopa/service_status.c
new file mode 100644
index 0000000..6d4f53f
--- /dev/null
+++ b/src/libanopa/service_status.c
@@ -0,0 +1,106 @@
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <skalibs/allreadwrite.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/uint32.h>
+#include <skalibs/tai.h>
+#include <anopa/service_status.h>
+
+
+void
+aa_service_status_free (aa_service_status *svst)
+{
+    stralloc_free (&svst->sa);
+}
+
+int
+aa_service_status_read (aa_service_status *svst, const char *dir)
+{
+    unsigned int len = strlen (dir);
+    char file[len + 1 + sizeof (AA_SVST_FILENAME)];
+    uint32 u;
+
+    byte_copy (file, len, dir);
+    byte_copy (file + len, 1 + sizeof (AA_SVST_FILENAME), "/" AA_SVST_FILENAME);
+
+    if (!openreadfileclose (file, &svst->sa, AA_SVST_FIXED_SIZE + AA_SVST_MAX_MSG_SIZE + 1)
+            || svst->sa.len < AA_SVST_FIXED_SIZE)
+    {
+        tain_now_g ();
+        return -1;
+    }
+    tain_now_g ();
+
+    svst->sa.s[svst->sa.len] = '\0';
+    if (svst->sa.len < AA_SVST_FIXED_SIZE + AA_SVST_MAX_MSG_SIZE + 1)
+        svst->sa.len++;
+
+    tain_unpack (svst->sa.s, &svst->stamp);
+    uint32_unpack (svst->sa.s + 12, &u);
+    svst->event = (unsigned int) u;
+    uint32_unpack (svst->sa.s + 16, &u);
+    svst->code = (int) u;
+
+    return 0;
+}
+
+int
+aa_service_status_write (aa_service_status *svst, const char *dir)
+{
+    unsigned int len = strlen (dir);
+    char file[len + 1 + sizeof (AA_SVST_FILENAME)];
+    mode_t mask;
+    int r;
+
+    if (!stralloc_ready_tuned (&svst->sa, AA_SVST_FIXED_SIZE, 0, 0, 1))
+        return -1;
+
+    tain_pack (svst->sa.s, &svst->stamp);
+    uint32_pack (svst->sa.s + 12, (uint32) svst->event);
+    uint32_pack (svst->sa.s + 16, (uint32) svst->code);
+    if (svst->sa.len < AA_SVST_FIXED_SIZE)
+        svst->sa.len = AA_SVST_FIXED_SIZE;
+
+    byte_copy (file, len, dir);
+    byte_copy (file + len, 1 + sizeof (AA_SVST_FILENAME), "/" AA_SVST_FILENAME);
+
+    mask = umask (0033);
+    if (!openwritenclose_suffix (file, svst->sa.s,
+                svst->sa.len + ((svst->sa.len > AA_SVST_FIXED_SIZE) ? -1 : 0), ".new"))
+        r = -1;
+    else
+        r = 0;
+    umask (mask);
+
+    tain_now_g ();
+    return r;
+}
+
+int
+aa_service_status_set_msg (aa_service_status *svst, const char *msg)
+{
+    int len;
+
+    len = strlen (msg);
+    if (len > AA_SVST_MAX_MSG_SIZE)
+        len = AA_SVST_MAX_MSG_SIZE;
+
+    if (!stralloc_ready_tuned (&svst->sa, AA_SVST_FIXED_SIZE + len + 1, 0, 0, 1))
+        return -1;
+
+    svst->sa.len = AA_SVST_FIXED_SIZE;
+    stralloc_catb (&svst->sa, msg, len);
+    stralloc_0 (&svst->sa);
+    return 0;
+}
+
+int
+aa_service_status_set_err (aa_service_status *svst, int err, const char *msg)
+{
+    svst->event = AA_EVT_ERROR;
+    svst->code = err;
+    tain_copynow (&svst->stamp);
+    return aa_service_status_set_msg (svst, (msg) ? msg : "");
+}
diff --git a/src/libanopa/service_stop.c b/src/libanopa/service_stop.c
new file mode 100644
index 0000000..9f1fdfb
--- /dev/null
+++ b/src/libanopa/service_stop.c
@@ -0,0 +1,37 @@
+
+#include <skalibs/direntry.h>
+#include <anopa/service.h>
+#include <anopa/ga_int_list.h>
+#include "service_internal.h"
+
+int
+_it_stop_after (direntry *d, void *data)
+{
+    struct it_data *it_data = data;
+    int sai;
+    int r;
+
+    tain_now_g ();
+    r = aa_get_service (d->d_name, &sai, 0);
+    if (r < 0)
+        return 0;
+
+    add_to_list (&aa_service (sai)->after, it_data->si, 1);
+    return 0;
+}
+
+int
+_it_stop_before (direntry *d, void *data)
+{
+    struct it_data *it_data = data;
+    int sbi;
+    int r;
+
+    tain_now_g ();
+    r = aa_get_service (d->d_name, &sbi, 0);
+    if (r < 0)
+        return 0;
+
+    add_to_list (&aa_service (it_data->si)->after, sbi, 1);
+    return 0;
+}
diff --git a/src/libanopa/services.c b/src/libanopa/services.c
new file mode 100644
index 0000000..27ffee4
--- /dev/null
+++ b/src/libanopa/services.c
@@ -0,0 +1,13 @@
+
+#include <skalibs/stralloc.h>
+#include <skalibs/genalloc.h>
+#include <s6/ftrigr.h>
+#include <anopa/service.h>
+
+genalloc aa_services    = GENALLOC_ZERO;
+stralloc aa_names       = STRALLOC_ZERO;
+genalloc aa_main_list   = GENALLOC_ZERO;
+genalloc aa_tmp_list    = GENALLOC_ZERO;
+
+ftrigr_t _aa_ft         = FTRIGR_ZERO;
+aa_exec_cb _exec_cb     = NULL;
diff --git a/src/libanopa/stats.c b/src/libanopa/stats.c
new file mode 100644
index 0000000..756cd6d
--- /dev/null
+++ b/src/libanopa/stats.c
@@ -0,0 +1,48 @@
+
+#include <skalibs/uint.h>
+#include <skalibs/buffer.h>
+#include <skalibs/genalloc.h>
+#include <anopa/output.h>
+#include <anopa/ga_int_list.h>
+
+void
+aa_show_stat_nb (int nb, const char *title, const char *ansi_color)
+{
+    char buf[UINT_FMT];
+
+    if (nb <= 0)
+        return;
+
+    aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_BLUE_ON);
+    aa_bs_noflush (AA_OUT, "  -> ");
+    aa_is_noflush (AA_OUT, ansi_color);
+    aa_bs_noflush (AA_OUT, title);
+    aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+    aa_bs_noflush (AA_OUT, ": ");
+    buf[uint_fmt (buf, nb)] = '\0';
+    aa_bs_noflush (AA_OUT, buf);
+    aa_end_title ();
+}
+
+void
+aa_show_stat_names (const char  *names,
+                    genalloc    *ga_offets,
+                    const char  *title,
+                    const char  *ansi_color)
+{
+    int i;
+
+    if (genalloc_len (int, ga_offets) <= 0)
+        return;
+
+    aa_put_title (0, title, "", 0);
+    for (i = 0; i < genalloc_len (int, ga_offets); ++i)
+    {
+        if (i > 0)
+            aa_bs_noflush (AA_OUT, "; ");
+        aa_is_noflush (AA_OUT, ansi_color);
+        aa_bs_noflush (AA_OUT, names + list_get (ga_offets, i));
+        aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+    }
+    aa_end_title ();
+}
diff --git a/tools/gen-deps.sh b/tools/gen-deps.sh
new file mode 100755
index 0000000..2f7c57d
--- /dev/null
+++ b/tools/gen-deps.sh
@@ -0,0 +1,83 @@
+#!/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"
+    echo "lib${file}.so: $(echo "$deps" | sed 's/\.o/.lo/g')"
+  done
+
+  for file in $(ls -1 src/$dir/deps-exe) ; do
+    deps=
+    libs=
+    while read dep ; do
+      if echo $dep | grep -q -- \\.o$ ; then
+        dep="src/$dir/$dep"
+      fi
+      if echo $dep | grep -q '^\${.*_LIB}' ; then
+        libs="$libs $dep"
+      else
+        deps="$deps $dep"
+      fi
+    done < src/$dir/deps-exe/$file
+    echo "$file: private EXTRA_LIBS :=$libs"
+    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