author | Olivier Brunel
<jjk@jjacky.com> 2015-02-11 14:53:07 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2015-04-04 12:47:28 UTC |
.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