Welcome to little lamb

Code » anopa » commit 05b0ea5

Add aa-status

author Olivier Brunel
2015-04-01 18:03:06 UTC
committer Olivier Brunel
2015-04-04 12:47:36 UTC
parent 5ffedc8a8256bf3af87b012a30b816fc2ac313b9

Add aa-status

doc/aa-status.pod +115 -0
package/modes +1 -0
package/targets.mak +2 -0
src/anopa/aa-status.c +657 -0
src/anopa/deps-exe/aa-status +5 -0
src/include/anopa/service.h +1 -0
src/include/anopa/service_status.h +2 -0
src/libanopa/deps-lib/anopa +1 -0
src/libanopa/eventmsg.c +15 -0
src/libanopa/service.c +31 -23

diff --git a/doc/aa-status.pod b/doc/aa-status.pod
new file mode 100644
index 0000000..010a78a
--- /dev/null
+++ b/doc/aa-status.pod
@@ -0,0 +1,115 @@
+=head1 NAME
+
+aa-status - Show status of services
+
+=head1 SYNOPSIS
+
+B<aa-status> [B<-D>] [B<-r> I<repodir>] [B<-a>] [B<-l>] [I<service...>]
+
+=head1 OPTIONS
+
+=over
+
+=item B<-a, --all>
+
+Show status of all (enabled) services. This will list all folders (servicedirs)
+in the repository and show their status.
+
+=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<-h, --help>
+
+Show help screen and exit.
+
+=item B<-l, --list>
+
+Show statuses as a list, with one service per line, elipsizing service name
+and/or status if needed.
+
+=item B<-r, --repodir> I<dir>
+
+Use I<dir> as repository directory. This is where servicedirs will be looked
+for.
+
+=item B<-V, --version>
+
+Show version information and exit.
+
+=back
+
+=head1 DESCRIPTION
+
+B<aa-status>(1) will show the status of one or more services. It does so by
+reading the I<status.anopa> file created by either B<aa-start>(1) or
+B<aa-stop>(1) as well as the I<status> file from B<s6> for long-run services,
+using whichever one has more recent information.
+
+For long-run services it will also check for the I<ready> file to determine
+whether the service is "Up" or "Ready" (using the timestamp from the I<ready>
+file in the later case).
+
+=head1 POSSIBLE STATUS
+
+The different possible statuses are :
+
+=over
+
+=item B<Unknown status>
+
+E.g. if no status file exist.
+
+=item B<Error>
+
+An error occured while processing the service, e.g. a dependency failed.
+An additionnal message will be present.
+
+=item B<Starting failed> / B<Stopping failed>
+
+(I<one-shot services only.>)
+
+The script I<start>/I<stop> could not be started; E.g. a permission error
+prevented its execution. An additionnal message might be present.
+
+=item B<Start failed> / B<Stop failed>
+
+(I<one-shot services only.>)
+
+The script I<start>/I<stop> did not return zero on exit. It will say either
+which value was returned, or which signal killed it.
+
+=item B<Starting> / B<Stopping>
+
+The script I<start>/I<stop> is currently running; or the command has been sent
+to the B<s6-supervise> of the service.
+
+=item B<Started> / B<Stopped>
+
+(I<one-shot services only.>)
+
+This script I<start>/I<stop> succesfully ran (i.e. and returned 0).
+
+=item B<Up>
+
+(I<long-run services only.>)
+
+The script I<run> is currently running; Its PID will be specified.
+
+=item B<Ready>
+
+(I<long-run services only.>)
+
+The script I<run> is currently running; Its PID will be specified.
+
+=item B<Down>
+
+(I<long-run services only.>)
+
+The script I<run> isn't running anymore. Its exitcode or which signal killed it
+will be specified.
+
+=back
diff --git a/package/modes b/package/modes
index e9045c4..dfa1ed8 100644
--- a/package/modes
+++ b/package/modes
@@ -16,6 +16,7 @@ aa-stage2               0755
 aa-stage3               0755
 aa-stage4               0755
 aa-start                0755
+aa-status               0755
 aa-stop                 0755
 aa-sync                 0755
 aa-terminate            0755
diff --git a/package/targets.mak b/package/targets.mak
index 263627e..7c34bce 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -11,6 +11,7 @@ aa-reboot \
 aa-service \
 aa-setready \
 aa-start \
+aa-status \
 aa-stop \
 aa-sync \
 aa-terminate \
@@ -47,6 +48,7 @@ aa-stage2.1 \
 aa-stage3.1 \
 aa-stage4.1 \
 aa-start.1 \
+aa-status.1 \
 aa-stop.1 \
 aa-sync.1 \
 aa-terminate.1 \
diff --git a/src/anopa/aa-status.c b/src/anopa/aa-status.c
new file mode 100644
index 0000000..75b1c79
--- /dev/null
+++ b/src/anopa/aa-status.c
@@ -0,0 +1,657 @@
+
+#define _BSD_SOURCE
+
+#include "anopa/config.h"
+
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <errno.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <skalibs/bytestr.h>
+#include <skalibs/djbunix.h>
+#include <skalibs/genalloc.h>
+#include <skalibs/stralloc.h>
+#include <skalibs/strerr2.h>
+#include <skalibs/uint.h>
+#include <skalibs/djbtime.h>
+#include <skalibs/sig.h>
+#include <skalibs/error.h>
+#include <s6/s6-supervise.h>
+#include <anopa/common.h>
+#include <anopa/output.h>
+#include <anopa/init_repo.h>
+#include <anopa/scan_dir.h>
+#include <anopa/service.h>
+#include <anopa/service_status.h>
+#include <anopa/err.h>
+#include "util.h"
+
+struct config
+{
+    int mode_list;
+    int cols;
+    int max_name;
+};
+
+struct serv
+{
+    int si;
+    int is_s6;
+    s6_svstatus_t st6;
+    tain_t stamp;
+};
+
+static genalloc ga_serv = GENALLOC_ZERO;
+
+static int put_s_max (const char *s, int max, int pad);
+
+static void
+put_wstat (int wstat, int max, int pad)
+{
+    char buf[20];
+
+    if (WIFEXITED (wstat))
+    {
+        byte_copy (buf, 9, "exitcode ");
+        buf[9 + uint_fmt (buf + 9, WEXITSTATUS (wstat))] = '\0';
+    }
+    else
+    {
+        const char *signame;
+
+        signame = sig_name (WTERMSIG (wstat));
+        byte_copy (buf, 10, "signal SIG");
+        byte_copy (buf + 10, strlen (signame) + 1, signame);
+    }
+    put_s_max (buf, max, pad);
+}
+
+static void
+put_time (tain_t *st_stamp, int strict)
+{
+    char buf[LOCALTMN_FMT];
+    localtmn_t local;
+    tain_t stamp;
+    int n;
+
+    localtmn_from_tain (&local, st_stamp, 1);
+
+    if (strict)
+    {
+        localtmn_fmt (buf, &local);
+        buf[19] = ' ';
+        buf[20] = '\0';
+        aa_bs_noflush (AA_OUT, buf);
+        return;
+    }
+
+    aa_bb_noflush (AA_OUT, buf, localtmn_fmt (buf, &local));
+    aa_bs_noflush (AA_OUT, " (");
+    tain_sub (&stamp, &STAMP, st_stamp);
+    if (stamp.sec.x > 86400)
+    {
+        n = stamp.sec.x / 86400;
+        stamp.sec.x -= 86400 * n;
+
+        buf[uint_fmt (buf, n)] = '\0';
+        aa_bs_noflush (AA_OUT, buf);
+        aa_bs_noflush (AA_OUT, "d ");
+    }
+    if (stamp.sec.x > 3600)
+    {
+        n = stamp.sec.x / 3600;
+        stamp.sec.x -= 3600 * n;
+
+        buf[uint_fmt (buf, n)] = '\0';
+        aa_bs_noflush (AA_OUT, buf);
+        aa_bs_noflush (AA_OUT, "h ");
+    }
+    if (stamp.sec.x > 60)
+    {
+        n = stamp.sec.x / 60;
+        stamp.sec.x -= 60 * n;
+
+        buf[uint_fmt (buf, n)] = '\0';
+        aa_bs_noflush (AA_OUT, buf);
+        aa_bs_noflush (AA_OUT, "m ");
+    }
+    buf[uint_fmt (buf, stamp.sec.x)] = '\0';
+    aa_bs_noflush (AA_OUT, buf);
+    aa_bs_noflush (AA_OUT, "s ago)\n");
+}
+
+static struct col
+{
+    const char *title;
+    int len;
+} cols[4] = {
+    { .title = "Service",   .len = 8 },
+    { .title = "Type",      .len = 8 },
+    { .title = "Since",     .len = 20 },
+    { .title = "Status",    .len = 15 }
+};
+
+static inline void
+pad_with (int left)
+{
+    for ( ; left > 0; left -= 10)
+        aa_bb_noflush (AA_OUT, "          ", (left >= 10) ? 10 : left);
+}
+
+static inline void
+pad_col (int i, int done)
+{
+    if (cols[i].len && cols[i].len > done)
+        pad_with (cols[i].len - done);
+}
+
+static int
+put_list_header (struct config *cfg)
+{
+    int best;
+    int i;
+
+    if (cfg->max_name + 1 > cols[0].len)
+        best = cfg->max_name + 1;
+    else
+        best = cols[0].len;
+
+    if (cfg->cols < 0)
+    {
+        cols[0].len = best;
+        cols[3].len = 0;
+    }
+    else
+    {
+        int len;
+
+        /* try with the best width */
+        len = best + cols[1].len + cols[2].len + cols[3].len;
+        if (cfg->cols >= len)
+            cols[0].len = best;
+        else
+        {
+            int added;
+            int n;
+
+            added = best - cols[0].len;
+            n = len - cfg->cols;
+            /* can we remove some from col0 to get col3 at its min size? */
+            if (added >= n)
+            {
+                len -= n;
+                cols[0].len = best - n;
+            }
+            else
+            {
+                /* try all min sizes */
+                len = cols[0].len + cols[1].len + cols[2].len + cols[3].len;
+                if (cfg->cols < len)
+                {
+                    /* try without col1 */
+                    len -= cols[1].len;
+                    if (len < len)
+                    {
+                        strerr_warn1x ("Terminal too small, disabling list mode");
+                        cfg->mode_list = 0;
+                        return 0;
+                    }
+                    cols[1].len = 0;
+                }
+            }
+        }
+
+        cols[3].len += cfg->cols - len;
+    }
+
+    aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+    for (i = 0; i < 4; ++i)
+    {
+        if (i < 3 && cols[i].len == 0)
+            continue;
+
+        aa_bs_noflush (AA_OUT, cols[i].title);
+        if (i < 3)
+            pad_col (i, strlen (cols[i].title));
+    }
+    aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+    aa_bs_flush (AA_OUT, "\n");
+
+    return 1;
+}
+
+static int
+put_s_max (const char *s, int max, int pad)
+{
+    int l = strlen (s);
+
+    if (max <= 4)
+        return 0;
+    else if (l >= max)
+    {
+        aa_bb_noflush (AA_OUT, s, max - 4);
+        aa_bs_noflush (AA_OUT, "... ");
+    }
+    else
+    {
+        aa_bs_noflush (AA_OUT, s);
+        if (!pad)
+            return l;
+        pad_col (0, l);
+    }
+    return max;
+}
+
+#define put_s(s)                        \
+    if (cfg->mode_list)                 \
+        max -= put_s_max (s, max, 0);   \
+    else                                \
+        aa_bs_noflush (AA_OUT, s);
+
+static void
+status_service (struct serv *serv, struct config *cfg)
+{
+    static int first = 1;
+    aa_service *s = aa_service (serv->si);
+    const char *msg;
+
+    if (cfg->mode_list)
+    {
+        if (first && !put_list_header (cfg))
+            aa_bs_noflush (AA_OUT, "\n");
+    }
+    else if (!first)
+        aa_bs_noflush (AA_OUT, "\n");
+
+    if (cfg->mode_list)
+    {
+        put_s_max (aa_service_name (s), cols[0].len, 1);
+
+        if (cols[1].len)
+        {
+            if (s->st.type == AA_TYPE_ONESHOT)
+                aa_bs_noflush (AA_OUT, "oneshot");
+            else
+                aa_bs_noflush (AA_OUT, "longrun");
+            pad_col (1, 7);
+        }
+
+        if (!serv->is_s6 && s->st.event == AA_EVT_NONE)
+        {
+            aa_bs_noflush (AA_OUT, "-");
+            pad_col (2, 1);
+        }
+        else
+            put_time (&serv->stamp, 1);
+    }
+    else
+    {
+        aa_bs_noflush (AA_OUT, "Service: ");
+        aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+        aa_bs_noflush (AA_OUT, aa_service_name (s));
+        aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+        if (s->st.type == AA_TYPE_ONESHOT)
+            aa_bs_noflush (AA_OUT, " (one-shot)");
+        else
+            aa_bs_noflush (AA_OUT, " (long-run)");
+        aa_bs_noflush (AA_OUT, "\n");
+
+        aa_bs_noflush (AA_OUT, "Since:   ");
+        if (!serv->is_s6 && s->st.event == AA_EVT_NONE)
+            aa_bs_noflush (AA_OUT, "-\n");
+        else
+            put_time (&serv->stamp, 0);
+
+        aa_bs_noflush (AA_OUT, "Status:  ");
+    }
+
+    if (serv->is_s6)
+    {
+        int max = cols[3].len;
+
+        if (serv->st6.pid && !serv->st6.flagfinishing)
+        {
+            char buf[UINT_FMT];
+
+            aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_GREEN_ON);
+            put_s ((serv->is_s6 == 2) ? "Ready" : "Up");
+            aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+            put_s (" (PID ");
+            buf[uint_fmt (buf, serv->st6.pid)] = '\0';
+            put_s (buf);
+            put_s (")");
+        }
+        else
+        {
+            aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_ON);
+            put_s ("Down");
+            aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+            put_s (" (");
+            put_wstat (serv->st6.wstat, max, 0);
+            put_s (")");
+        }
+    }
+    else
+    {
+        int max = cols[3].len;
+
+        switch (s->st.event)
+        {
+            case AA_EVT_NONE:
+                put_s (eventmsg[s->st.event]);
+                break;
+
+            case AA_EVT_ERROR:
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_RED_ON);
+                put_s (eventmsg[s->st.event]);
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+                put_s (": ");
+                put_s (errmsg[s->st.code]);
+                break;
+
+            case AA_EVT_STARTING:
+            case AA_EVT_STOPPING:
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_BLUE_ON);
+                put_s (eventmsg[s->st.event]);
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+                break;
+
+            case AA_EVT_STARTING_FAILED:
+            case AA_EVT_STOPPING_FAILED:
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_RED_ON);
+                put_s (eventmsg[s->st.event]);
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+                if (cfg->mode_list && max <= 6)
+                {
+                    if (max > 1)
+                        aa_bb_noflush (AA_OUT, "...", (max > 4) ? 3 : max - 1);
+                    aa_bs_noflush (AA_OUT, " ");
+                }
+                else
+                {
+                    put_s (": ");
+                    put_s (errmsg[s->st.code]);
+                    msg = aa_service_status_get_msg (&s->st);
+                    if (msg)
+                    {
+                        put_s (": ");
+                        put_s (msg);
+                    }
+                }
+                break;
+
+            case AA_EVT_START_FAILED:
+            case AA_EVT_STOP_FAILED:
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_RED_ON);
+                put_s (eventmsg[s->st.event]);
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+                if (cfg->mode_list && max <= 6)
+                {
+                    if (max > 1)
+                        aa_bb_noflush (AA_OUT, "...", (max > 4) ? 3 : max - 1);
+                    aa_bs_noflush (AA_OUT, " ");
+                }
+                else
+                {
+                    put_s (": ");
+                    put_wstat (s->st.code, max, 0);
+                }
+                break;
+
+            case AA_EVT_STARTED:
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_GREEN_ON);
+                put_s (eventmsg[s->st.event]);
+                aa_is_noflush (AA_OUT, ANSI_HIGHLIGHT_OFF);
+                break;
+
+            case AA_EVT_STOPPED:
+                put_s (eventmsg[s->st.event]);
+                break;
+
+            case _AA_NB_EVT: /* silence warning */
+                break;
+        }
+    }
+    aa_bs_flush (AA_OUT, "\n");
+
+    if (first)
+        first = 0;
+}
+
+static void
+load_service (const char *name, struct config *cfg)
+{
+    aa_service *s;
+    struct serv serv = { 0, };
+    int r;
+
+    r = aa_get_service (name, &serv.si, 1);
+    if (r < 0)
+    {
+        aa_put_err (name, errmsg[-r], 1);
+        return;
+    }
+
+    r = aa_preload_service (serv.si);
+    if (r < 0)
+    {
+        aa_put_err (name, errmsg[-r], 1);
+        return;
+    }
+
+    s = aa_service (serv.si);
+    if (aa_service_status_read (&s->st, aa_service_name (s)) < 0 && errno != ENOENT)
+    {
+        int e = errno;
+
+        aa_put_err (name, "Failed to read service status file: ", 0);
+        aa_bs_noflush (AA_ERR, error_str (e));
+        aa_end_err ();
+        return;
+    }
+
+    if (s->st.type == AA_TYPE_LONGRUN)
+    {
+        if (!s6_svstatus_read (name, &serv.st6))
+        {
+            if (errno != ENOENT)
+            {
+                int e = errno;
+
+                aa_put_err (name, "Unable to read s6 status: ", 0);
+                aa_bs_noflush (AA_ERR, error_str (e));
+                aa_end_err ();
+                return;
+            }
+        }
+        else if (tain_less (&s->st.stamp, &serv.st6.stamp))
+        {
+            serv.is_s6 = 1;
+            serv.stamp = serv.st6.stamp;
+        }
+    }
+    if (!serv.is_s6)
+        serv.stamp = s->st.stamp;
+    else if (serv.st6.pid && !serv.st6.flagfinishing)
+    {
+        int l = strlen (name);
+        char buf[l + 1 + sizeof (S6_SUPERVISE_READY_FILENAME)];
+        struct stat st;
+
+        byte_copy (buf, l, name);
+        buf[l] = '/';
+        byte_copy (buf + l + 1, sizeof (S6_SUPERVISE_READY_FILENAME), S6_SUPERVISE_READY_FILENAME);
+
+        if (stat (buf, &st) < 0)
+        {
+            if (errno != ENOENT)
+            {
+                int e = errno;
+
+                aa_put_err (name, "Unable to read s6 ready file: ", 0);
+                aa_bs_noflush (AA_ERR, error_str (e));
+                aa_end_err ();
+                return;
+            }
+        }
+        else
+        {
+            char pack[TAIN_PACK];
+
+            if (openreadnclose (buf, pack, TAIN_PACK) < TAIN_PACK)
+            {
+                if (errno != ENOENT)
+                {
+                    int e = errno;
+
+                    aa_put_err (name, "Unable to read s6 ready file: ", 0);
+                    aa_bs_noflush (AA_ERR, error_str (e));
+                    aa_end_err ();
+                    return;
+                }
+            }
+            else
+            {
+                tain_unpack (pack, &serv.stamp);
+                serv.is_s6 = 2;
+            }
+        }
+    }
+
+    if (cfg->mode_list)
+    {
+        int l = strlen (name);
+
+        if (l > cfg->max_name)
+            cfg->max_name = l;
+    }
+
+    genalloc_append (struct serv, &ga_serv, &serv);
+}
+
+static int
+it_all (direntry *d, void *data)
+{
+    if (*d->d_name == '.' || d->d_type != DT_DIR)
+        return 0;
+    load_service (d->d_name, data);
+    return 0;
+}
+
+static void
+dieusage (int rc)
+{
+    aa_die_usage (rc, "[OPTION...] [service...]",
+            " -D, --double-output           Enable double-output mode\n"
+            " -r, --repodir DIR             Use DIR as repository directory\n"
+            " -a, --all                     Show status of all services\n"
+            " -l, --list                    Show statuses as one-liners list\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-status";
+    const char *path_repo = "/run/services";
+    int mode_both = 0;
+    struct config cfg = { 0, };
+    int all = 0;
+    int i;
+    int r;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "all",                no_argument,        NULL,   'a' },
+            { "double-output",      no_argument,        NULL,   'D' },
+            { "help",               no_argument,        NULL,   'h' },
+            { "list",               no_argument,        NULL,   'l' },
+            { "repodir",            required_argument,  NULL,   'r' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "aDhlr:V", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'a':
+                all = 1;
+                break;
+
+            case 'D':
+                mode_both = 1;
+                break;
+
+            case 'h':
+                dieusage (0);
+
+            case 'l':
+                cfg.mode_list = 1;
+                break;
+
+            case 'r':
+                unslash (optarg);
+                path_repo = optarg;
+                break;
+
+            case 'V':
+                aa_die_version ();
+
+            default:
+                dieusage (1);
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    aa_init_output (mode_both);
+
+    if (!all && argc < 1)
+        dieusage (1);
+
+    r = aa_init_repo (path_repo, AA_REPO_READ);
+    if (r < 0)
+        strerr_diefu2sys (2, "init repository ", path_repo);
+
+    if (cfg.mode_list)
+    {
+        struct winsize win;
+
+        if (isatty (1))
+        {
+            if (ioctl (1, TIOCGWINSZ, &win) == 0)
+                cfg.cols = win.ws_col;
+            else
+                cfg.cols = 80;
+        }
+        else
+            cfg.cols = 999999;
+    }
+
+    if (all)
+    {
+        stralloc sa = STRALLOC_ZERO;
+
+        stralloc_catb (&sa, ".", 2);
+        r = aa_scan_dir (&sa, 0, it_all, &cfg);
+        if (r < 0)
+            strerr_diefu2sys (-r, "scan repo directory ", path_repo);
+    }
+    else
+        for (i = 0; i < argc; ++i)
+            load_service (argv[i], &cfg);
+
+    for (i = 0; i < genalloc_len (struct serv, &ga_serv); ++i)
+        status_service (&genalloc_s (struct serv, &ga_serv)[i], &cfg);
+
+    return 0;
+}
diff --git a/src/anopa/deps-exe/aa-status b/src/anopa/deps-exe/aa-status
new file mode 100644
index 0000000..4e49dfb
--- /dev/null
+++ b/src/anopa/deps-exe/aa-status
@@ -0,0 +1,5 @@
+util.o
+${LIBANOPA}
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/include/anopa/service.h b/src/include/anopa/service.h
index f7313f6..ad046a8 100644
--- a/src/include/anopa/service.h
+++ b/src/include/anopa/service.h
@@ -86,6 +86,7 @@ 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_preload_service (int si);
 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);
diff --git a/src/include/anopa/service_status.h b/src/include/anopa/service_status.h
index ec5d02d..3934861 100644
--- a/src/include/anopa/service_status.h
+++ b/src/include/anopa/service_status.h
@@ -20,6 +20,8 @@ typedef enum
     _AA_NB_EVT
 } aa_evt;
 
+extern const char const *eventmsg[_AA_NB_EVT];
+
 enum
 {
     AA_TYPE_UNKNOWN = 0,
diff --git a/src/libanopa/deps-lib/anopa b/src/libanopa/deps-lib/anopa
index db9c47f..7ca4d1c 100644
--- a/src/libanopa/deps-lib/anopa
+++ b/src/libanopa/deps-lib/anopa
@@ -3,6 +3,7 @@ die_usage.o
 die_version.o
 enable_service.o
 errmsg.o
+eventmsg.o
 exec_longrun.o
 exec_oneshot.o
 ga_int_list.o
diff --git a/src/libanopa/eventmsg.c b/src/libanopa/eventmsg.c
new file mode 100644
index 0000000..e0a8950
--- /dev/null
+++ b/src/libanopa/eventmsg.c
@@ -0,0 +1,15 @@
+
+#include <anopa/service_status.h>
+
+const char const *eventmsg[_AA_NB_EVT] = {
+    "Unknown status",
+    "Error",
+    "Starting",
+    "Starting failed",
+    "Start failed",
+    "Started",
+    "Stopping",
+    "Stopping failed",
+    "Stop failed",
+    "Stopped"
+};
diff --git a/src/libanopa/service.c b/src/libanopa/service.c
index 6e532c4..f49d019 100644
--- a/src/libanopa/service.c
+++ b/src/libanopa/service.c
@@ -125,6 +125,34 @@ aa_get_service (const char *name, int *si, int new_in_main)
     }
 }
 
+int
+aa_preload_service (int si)
+{
+    aa_service_status *svst = &aa_service (si)->st;
+    int l_sn = strlen (aa_service_name (aa_service (si)));
+    char buf[l_sn + 1 + sizeof (AA_GETS_READY_FILENAME)];
+
+    byte_copy (buf, l_sn, aa_service_name (aa_service (si)));
+    byte_copy (buf + l_sn, 5, "/run");
+
+    if (access (buf, F_OK) < 0)
+    {
+        if (errno != ENOENT)
+            return -ERR_IO;
+        else
+            svst->type = AA_TYPE_ONESHOT;
+    }
+    else
+    {
+        svst->type = AA_TYPE_LONGRUN;
+
+        byte_copy (buf + l_sn, 1 + sizeof (AA_GETS_READY_FILENAME), "/" AA_GETS_READY_FILENAME);
+        aa_service (si)->gets_ready = (access (buf, F_OK) == 0) ? 1 : 0;
+    }
+
+    return 0;
+}
+
 int
 aa_ensure_service_loaded (int si, aa_mode mode, int no_wants, aa_load_fail_cb lf_cb)
 {
@@ -137,29 +165,9 @@ aa_ensure_service_loaded (int si, aa_mode mode, int no_wants, aa_load_fail_cb lf
     else if (aa_service (si)->ls == AA_LOAD_FAIL)
         return -aa_service (si)->st.code;
 
-    {
-        aa_service_status *svst = &aa_service (si)->st;
-        int l_sn = strlen (aa_service_name (aa_service (si)));
-        char buf[l_sn + 1 + sizeof (AA_GETS_READY_FILENAME)];
-
-        byte_copy (buf, l_sn, aa_service_name (aa_service (si)));
-        byte_copy (buf + l_sn, 5, "/run");
-
-        if (access (buf, F_OK) < 0)
-        {
-            if (errno != ENOENT)
-                return -ERR_IO;
-            else
-                svst->type = AA_TYPE_ONESHOT;
-        }
-        else
-        {
-            svst->type = AA_TYPE_LONGRUN;
-
-            byte_copy (buf + l_sn, 1 + sizeof (AA_GETS_READY_FILENAME), "/" AA_GETS_READY_FILENAME);
-            aa_service (si)->gets_ready = (access (buf, F_OK) == 0) ? 1 : 0;
-        }
-    }
+    r = aa_preload_service (si);
+    if (r < 0)
+        return r;
 
     {
         aa_service_status *svst = &aa_service (si)->st;