Welcome to little lamb

Code » anopa » commit ca9656c

Add aa-stop

author Olivier Brunel
2015-02-13 20:54:32 UTC
committer Olivier Brunel
2015-04-04 12:47:33 UTC
parent d2f682f82d42b8d69ea071e07af4cc133a85bd58

Add aa-stop

.gitignore +1 -0
doc/aa-stop.pod +103 -0
package/modes +1 -0
package/targets.mak +2 -0
src/anopa/aa-stop.c +276 -0
src/anopa/deps-exe/aa-stop +6 -0
src/include/anopa/service.h +2 -1
src/libanopa/exec_longrun.c +14 -1
src/libanopa/service.c +3 -2

diff --git a/.gitignore b/.gitignore
index ae76f3b..fd1fd1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@
 /aa-echo
 /aa-enable
 /aa-start
+/aa-stop
diff --git a/doc/aa-stop.pod b/doc/aa-stop.pod
new file mode 100644
index 0000000..df07eaa
--- /dev/null
+++ b/doc/aa-stop.pod
@@ -0,0 +1,103 @@
+=head1 NAME
+
+aa-stop - Stop services
+
+=head1 SYNOPSIS
+
+B<aa-stop> [B<-D>] [B<-r> I<repodir>] [B<-a>] [B<-k> I<service>]
+[I<service...>]
+
+=head1 OPTIONS
+
+=over
+
+=item B<-a, --all>
+
+Stops all running/started services. This option is intended to be used during
+stage 3; When used, you shouldn't specify any service on the command line.
+See below for its implications.
+
+=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<-k, --skip> I<service>
+
+If I<service> was asked to be stopped, silently ignore it. This is intended for
+use alongside B<--all> to keep the catch-all logger service running as long as
+possible (It will be stopped when sending SIGTERM to all running processes.).
+
+=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-stop>(1) allows to stop one or more services. Unlike B<aa-start>(1) it
+doesn't take into account any dependency relation, and the only services that
+will be stopped are those asked to be stopped.
+
+It does however accounts for I<after> and I<before> to stop services in reverse
+order they were started in, treating I<needs> exactly as it does I<after>.
+
+Refer to B<anopa>(1) for descriptions of servicedirs and service dependencies.
+
+B<aa-stop>(1) works in a very similar manner as B<aa-start>(1), with the
+following differences :
+
+=head1 STOPPING A LONG-RUN SERVICE
+
+B<aa-stop>(1) will check if the service is running, and if not simply announce
+it as not up.
+
+When B<--all> is used, if the service has a logger then command 'x' will be sent
+to said logger's B<s6-supervise> first, so that when the logger exits it isn't
+restarted and B<s6-supervise> exits as well instead.
+
+Similarly, instead of 'd' the commands 'dx' are sent to the service's
+B<s6-supervise>, so that after bringing the service down it exits as well. This
+is obviously all intended to bring the supervised tree all down, as is expected
+when using B<--all> (usually from stage 3).
+
+Note that if a service was not running, no 'x' command is sent so the
+B<s6-supervise> process of the service - and of its logger, if any - are kept
+running. This isn't a problem, since they'll simply exit when sending SIGTERM to
+all process further down in stage 3.
+
+=head1 STOPPING A ONE-SHOT SERVICE
+
+Obviously, the script used is I<stop> and not I<start>. Other than that, the
+process is much the same, so you can refer to B<aa-start>(1) for more.
+
+=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 AUTHOR
+
+=over
+
+=item Olivier Brunel <jjk@jjacky.com>
+
+=back
diff --git a/package/modes b/package/modes
index 99ef6e0..31e810b 100644
--- a/package/modes
+++ b/package/modes
@@ -1,3 +1,4 @@
 aa-start                0755
+aa-stop                 0755
 aa-enable               0755
 aa-echo                 0755
diff --git a/package/targets.mak b/package/targets.mak
index 68f62ef..f5a9f1e 100644
--- a/package/targets.mak
+++ b/package/targets.mak
@@ -1,11 +1,13 @@
 BIN_TARGETS := \
 aa-start \
+aa-stop \
 aa-enable \
 aa-echo
 
 DOC_TARGETS := \
 doc/anopa.1 \
 doc/aa-start.1 \
+doc/aa-stop.1 \
 doc/aa-enable.1 \
 doc/aa-echo.1
 
diff --git a/src/anopa/aa-stop.c b/src/anopa/aa-stop.c
new file mode 100644
index 0000000..b452544
--- /dev/null
+++ b/src/anopa/aa-stop.c
@@ -0,0 +1,276 @@
+
+#define _BSD_SOURCE
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.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/djbunix.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"
+
+
+static genalloc ga_unknown = GENALLOC_ZERO;
+static genalloc ga_io = GENALLOC_ZERO;
+static int rc = 0;
+static const char *skip = NULL;
+static int all = 0;
+
+void
+check_essential (int si)
+{
+    /* required by start-stop.c; only used by aa-start.c */
+}
+
+static int
+add_service (const char *name)
+{
+    int si = -1;
+    int type;
+    int r;
+
+    if (skip && str_equal (name, skip))
+        return 0;
+
+    type = aa_get_service (name, &si, 1);
+    if (type < 0)
+        r = type;
+    else
+        r = aa_ensure_service_loaded (si, AA_MODE_STOP, 0, NULL);
+    if (r < 0)
+    {
+        if (type == AA_SERVICE_FROM_MAIN)
+        {
+            add_to_list (&aa_tmp_list, si, 1);
+            remove_from_list (&aa_main_list, si);
+        }
+
+        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);
+            }
+        }
+        else if (r == -ERR_NOT_UP)
+        {
+            if (!all)
+            {
+                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 ();
+            }
+
+            genalloc_append (int, &ga_failed, &si);
+        }
+    }
+    else
+    {
+        if (type == AA_SERVICE_FROM_TMP)
+        {
+            add_to_list (&aa_main_list, si, 1);
+            remove_from_list (&aa_tmp_list, si);
+        }
+
+        r = 0;
+    }
+
+    return r;
+}
+
+static int
+it_stop (direntry *d, void *data)
+{
+    if (*d->d_name == '.' || (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN))
+        return 0;
+    else if (d->d_type == DT_UNKNOWN)
+    {
+        struct stat st;
+
+        if (stat (d->d_name, &st) < 0)
+            return 0;
+        if (!S_ISDIR (st.st_mode))
+            return 0;
+    }
+
+    tain_now_g ();
+    add_service (d->d_name);
+
+    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"
+            " -k, --skip SERVICE            Skip (do not stop) SERVICE\n"
+            " -a, --all                     Stop all running services\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-stop";
+    const char *path_repo = "/run/services";
+    int mode_both = 0;
+    int i;
+
+    for (;;)
+    {
+        struct option longopts[] = {
+            { "all",                no_argument,        NULL,   'a' },
+            { "double-output",      no_argument,        NULL,   'D' },
+            { "help",               no_argument,        NULL,   'h' },
+            { "skip",               required_argument,  NULL,   'k' },
+            { "repodir",            required_argument,  NULL,   'r' },
+            { "version",            no_argument,        NULL,   'V' },
+            { NULL, 0, 0, 0 }
+        };
+        int c;
+
+        c = getopt_long (argc, argv, "aDhk:r:V", longopts, NULL);
+        if (c == -1)
+            break;
+        switch (c)
+        {
+            case 'D':
+                mode_both = 1;
+                break;
+
+            case 'a':
+                all = 1;
+                break;
+
+            case 'k':
+                skip = optarg;
+                break;
+
+            case 'r':
+                unslash (optarg);
+                path_repo = optarg;
+                break;
+
+            case 'V':
+                aa_die_version ();
+
+            case 'h':
+            default:
+                dieusage ();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+
+    aa_init_output (mode_both);
+    cols = get_cols (1);
+    is_utf8 = is_locale_utf8 ();
+
+    if ((all && argc > 0) || (!all && argc < 1))
+        dieusage ();
+
+    if (aa_init_repo (path_repo, AA_REPO_WRITE) < 0)
+        strerr_diefu2sys (ERR_IO, "init repository ", path_repo);
+
+    if (all)
+    {
+        stralloc sa = STRALLOC_ZERO;
+        int r;
+
+        stralloc_catb (&sa, ".", 2);
+        r = aa_scan_dir (&sa, 0, it_stop, NULL);
+        stralloc_free (&sa);
+        if (r < 0)
+            strerr_diefu1sys (-r, "read repository directory");
+    }
+    else
+        for (i = 0; i < argc; ++i)
+            add_service (argv[i]);
+
+    tain_now_g ();
+
+    mainloop ((all) ? AA_MODE_STOP_ALL : AA_MODE_STOP, NULL);
+
+    aa_bs_noflush (AA_OUT, "\n");
+    put_title (1, PROG, "Completed.", 1);
+    aa_show_stat_nb (nb_already, "Not up", ANSI_HIGHLIGHT_GREEN_ON);
+    aa_show_stat_nb (nb_done, "Stopped", ANSI_HIGHLIGHT_GREEN_ON);
+    show_stat_service_names (&ga_failed, "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);
+
+    genalloc_free (int, &ga_failed);
+    genalloc_free (int, &ga_unknown);
+    genalloc_free (int, &ga_io);
+    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-stop b/src/anopa/deps-exe/aa-stop
new file mode 100644
index 0000000..3a71bd4
--- /dev/null
+++ b/src/anopa/deps-exe/aa-stop
@@ -0,0 +1,6 @@
+util.o
+start-stop.o
+${LIBANOPA}
+-ls6
+-lskarnet
+${TAINNOW_LIB}
diff --git a/src/include/anopa/service.h b/src/include/anopa/service.h
index 1f20b15..0e73b0d 100644
--- a/src/include/anopa/service.h
+++ b/src/include/anopa/service.h
@@ -30,7 +30,8 @@ typedef enum
 typedef enum
 {
     AA_MODE_START = 0,
-    AA_MODE_STOP
+    AA_MODE_STOP,
+    AA_MODE_STOP_ALL
 } aa_mode;
 
 typedef enum
diff --git a/src/libanopa/exec_longrun.c b/src/libanopa/exec_longrun.c
index e88f0f9..0ef860b 100644
--- a/src/libanopa/exec_longrun.c
+++ b/src/libanopa/exec_longrun.c
@@ -84,6 +84,18 @@ _exec_longrun (int si, aa_mode mode)
         return -1;
     }
 
+    if (mode == AA_MODE_STOP_ALL)
+    {
+        char dir[l_sn + 5 + sizeof (S6_SUPERVISE_CTLDIR) + 8];
+
+        byte_copy (dir, l_sn, aa_service_name (s));
+        byte_copy (dir + l_sn, 13 + sizeof (S6_SUPERVISE_CTLDIR), "/log/" S6_SUPERVISE_CTLDIR "/control");
+
+        /* ignore any error, starting with the fact that there might not be a
+         * logger for this service */
+        s6_svc_write (dir, "x", 1);
+    }
+
     {
         char dir[l_sn + 1 + sizeof (S6_SUPERVISE_CTLDIR) + 8];
         int r;
@@ -91,7 +103,8 @@ _exec_longrun (int si, aa_mode mode)
         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);
+        r = s6_svc_write (dir, (mode == AA_MODE_STOP_ALL) ? "dx" : event,
+                (mode == AA_MODE_STOP_ALL) ? 2 : 1);
         if (r <= 0)
         {
             tain_addsec_g (&deadline, 1);
diff --git a/src/libanopa/service.c b/src/libanopa/service.c
index beaaa44..fc26753 100644
--- a/src/libanopa/service.c
+++ b/src/libanopa/service.c
@@ -191,7 +191,7 @@ aa_ensure_service_loaded (int si, aa_mode mode, int no_wants, aa_load_fail_cb lf
             svst->code = ERR_ALREADY_UP;
             return -ERR_ALREADY_UP;
         }
-        else if (mode == AA_MODE_STOP && !is_up)
+        else if ((mode == AA_MODE_STOP || mode == AA_MODE_STOP_ALL) && !is_up)
         {
             /* if not up, we "fail" because we can't stop it */
             aa_service (si)->ls = AA_LOAD_FAIL;
@@ -485,7 +485,8 @@ aa_scan_mainlist (aa_scan_cb scan_cb, aa_mode mode)
 
         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))
+                    || ((mode == AA_MODE_STOP || mode == AA_MODE_STOP_ALL)
+                        && 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 */