author | Olivier Brunel
<jjk@jjacky.com> 2015-02-13 20:54:32 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2015-04-04 12:47:33 UTC |
parent | d2f682f82d42b8d69ea071e07af4cc133a85bd58 |
.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 */