From 96342de68d0d6de71a062d984dafd2a0905ed9fe Mon Sep 17 00:00:00 2001 From: Lukas Nykryn Date: Mon, 13 Aug 2012 13:58:01 +0200 Subject: [PATCH] service: add options RestartPreventExitStatus and SuccessExitStatus In some cases, like wrong configuration, restarting after error does not help, so administrator can specify statuses by RestartPreventExitStatus which will not cause restart of a service. Sometimes you have non-standart exit status, so this can be specified by SuccessfulExitStatus. --- man/systemd.service.xml | 14 ++++++ src/core/load-fragment-gperf.gperf.m4 | 2 + src/core/mount.c | 2 +- src/core/service.c | 20 +++++++- src/core/service.h | 3 ++ src/core/socket.c | 2 +- src/core/swap.c | 2 +- src/remount-fs/remount-fs.c | 2 +- src/shared/conf-parser.c | 70 +++++++++++++++++++++++++++ src/shared/conf-parser.h | 1 + src/shared/exit-status.c | 16 ++++-- src/shared/exit-status.h | 11 +++-- src/shared/hashmap.c | 15 ++++++ src/shared/hashmap.h | 1 + src/shared/set.c | 4 ++ src/shared/set.h | 1 + src/shared/util.c | 2 +- src/systemctl/systemctl.c | 2 +- 18 files changed, 154 insertions(+), 16 deletions(-) diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 1946d85f48..c4bd65e349 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -579,6 +579,20 @@ hit a timeout. + + RestartPreventExitStatus= + Specify exit status list, which + will prevent service from restart. Codes are + separated by whitespace (e.g. "1 6 SIGKILL"). + + + + SuccessExitStatus= + Specify exit status list, which + will be considered as successful exit. Codes are + separated by whitespace (e.g. "1 6 SIGKILL"). + + PermissionsStartOnly= Takes a boolean diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index e738213e27..84eea1c465 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -158,6 +158,8 @@ Service.PermissionsStartOnly, config_parse_bool, 0, Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit) Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid) +Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_ignore_status) +Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status) m4_ifdef(`HAVE_SYSV_COMPAT', `Service.SysVStartPriority, config_parse_sysv_priority, 0, offsetof(Service, sysv_start_priority)', `Service.SysVStartPriority, config_parse_warn_compat, 0, 0') diff --git a/src/core/mount.c b/src/core/mount.c index 83e51a7aba..fc981c74f4 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -1225,7 +1225,7 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { m->control_pid = 0; - if (is_clean_exit(code, status)) + if (is_clean_exit(code, status, NULL)) f = MOUNT_SUCCESS; else if (code == CLD_EXITED) f = MOUNT_FAILURE_EXIT_CODE; diff --git a/src/core/service.c b/src/core/service.c index e74da54eac..f540752b61 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -294,6 +294,16 @@ static void service_done(Unit *u) { s->control_command = NULL; s->main_command = NULL; + set_free(s->restart_ignore_status.code); + s->restart_ignore_status.code = NULL; + set_free(s->restart_ignore_status.signal); + s->restart_ignore_status.signal = NULL; + + set_free(s->success_status.code); + s->success_status.code = NULL; + set_free(s->success_status.signal); + s->success_status.signal = NULL; + /* This will leak a process, but at least no memory or any of * our resources */ service_unwatch_main_pid(s); @@ -1902,7 +1912,12 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) (s->restart == SERVICE_RESTART_ON_SUCCESS && s->result == SERVICE_SUCCESS) || (s->restart == SERVICE_RESTART_ON_FAILURE && s->result != SERVICE_SUCCESS) || (s->restart == SERVICE_RESTART_ON_ABORT && (s->result == SERVICE_FAILURE_SIGNAL || - s->result == SERVICE_FAILURE_CORE_DUMP)))) { + s->result == SERVICE_FAILURE_CORE_DUMP))) && + (s->result != SERVICE_FAILURE_EXIT_CODE || + !set_contains(s->restart_ignore_status.code, INT_TO_PTR(s->main_exec_status.status))) && + (s->result != SERVICE_FAILURE_SIGNAL || + !set_contains(s->restart_ignore_status.signal, INT_TO_PTR(s->main_exec_status.status))) + ) { r = unit_watch_timer(UNIT(s), s->restart_usec, &s->timer_watch); if (r < 0) @@ -2874,7 +2889,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { assert(s); assert(pid >= 0); - if (UNIT(s)->fragment_path ? is_clean_exit(code, status) : is_clean_exit_lsb(code, status)) + if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) : + is_clean_exit_lsb(code, status, &s->success_status)) f = SERVICE_SUCCESS; else if (code == CLD_EXITED) f = SERVICE_FAILURE_EXIT_CODE; diff --git a/src/core/service.h b/src/core/service.h index c78de79a09..2a4dc30d0b 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -28,6 +28,7 @@ typedef struct Service Service; #include "ratelimit.h" #include "service.h" #include "kill.h" +#include "exit-status.h" typedef enum ServiceState { SERVICE_DEAD, @@ -115,6 +116,8 @@ struct Service { ServiceType type; ServiceRestart restart; + ExitStatusSet restart_ignore_status; + ExitStatusSet success_status; /* If set we'll read the main daemon PID from this file */ char *pid_file; diff --git a/src/core/socket.c b/src/core/socket.c index 837b166e3b..cbbfb0cd3a 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -1884,7 +1884,7 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_pid = 0; - if (is_clean_exit(code, status)) + if (is_clean_exit(code, status, NULL)) f = SOCKET_SUCCESS; else if (code == CLD_EXITED) f = SOCKET_FAILURE_EXIT_CODE; diff --git a/src/core/swap.c b/src/core/swap.c index 458e00efe5..cd4d9ab3d7 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -917,7 +917,7 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_pid = 0; - if (is_clean_exit(code, status)) + if (is_clean_exit(code, status, NULL)) f = SWAP_SUCCESS; else if (code == CLD_EXITED) f = SWAP_FAILURE_EXIT_CODE; diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c index 636c46f0f3..b49d095cbb 100644 --- a/src/remount-fs/remount-fs.c +++ b/src/remount-fs/remount-fs.c @@ -145,7 +145,7 @@ int main(int argc, char *argv[]) { s = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)); if (s) { - if (!is_clean_exit(si.si_code, si.si_status)) { + if (!is_clean_exit(si.si_code, si.si_status, NULL)) { if (si.si_code == CLD_EXITED) log_error("/bin/mount for %s exited with exit status %i.", s, si.si_status); else diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index 1eccec5989..595bb51a27 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -32,6 +32,8 @@ #include "log.h" #include "utf8.h" #include "path-util.h" +#include "set.h" +#include "exit-status.h" int config_item_table_lookup( void *table, @@ -933,3 +935,71 @@ int config_parse_level( *o = (*o & LOG_FACMASK) | x; return 0; } + +int config_parse_set_status( + const char *filename, + unsigned line, + const char *section, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + char *w; + size_t l; + char *state; + int r; + ExitStatusSet *status_set = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD(w, l, rvalue, state) { + int val; + char *temp = strndup(w, l); + if (!temp) + return log_oom(); + + r = safe_atoi(temp, &val); + if (r < 0) { + val = signal_from_string_try_harder(temp); + free(temp); + if (val > 0) { + if (!status_set->signal) { + status_set->signal = set_new(trivial_hash_func, trivial_compare_func); + if (!status_set->signal) + return log_oom(); + } + r = set_put(status_set->signal, INT_TO_PTR(val)); + if (r < 0) { + log_error("[%s:%u] Unable to store: %s", filename, line, w); + return r; + } + } else { + log_error("[%s:%u] Failed to parse value: %s", filename, line, w); + return r; + } + } else { + free(temp); + if(val < 0 || val > 255) + log_warning("[%s:%u] Value %d is outside range 0-255, ignoring", filename, line, val); + else { + if (!status_set->code) { + status_set->code = set_new(trivial_hash_func, trivial_compare_func); + if (!status_set->code) + return log_oom(); + } + r = set_put(status_set->code, INT_TO_PTR(val)); + if (r < 0) { + log_error("[%s:%u] Unable to store: %s", filename, line, w); + return r; + } + } + } + + } + return 0; +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index 4f94b3b907..56ffc2f8a8 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -105,6 +105,7 @@ int config_parse_nsec(const char *filename, unsigned line, const char *section, int config_parse_mode(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_facility(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_level(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_set_status(const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); #define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ int function( \ diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c index 0dc82b2e13..45131f2b2a 100644 --- a/src/shared/exit-status.c +++ b/src/shared/exit-status.c @@ -23,6 +23,8 @@ #include #include "exit-status.h" +#include "set.h" +#include "macro.h" const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { @@ -158,10 +160,12 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { } -bool is_clean_exit(int code, int status) { +bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { if (code == CLD_EXITED) - return status == 0; + return status == 0 || + (success_status && + set_contains(success_status->code, INT_TO_PTR(status))); /* If a daemon does not implement handlers for some of the * signals that's not considered an unclean shutdown */ @@ -170,14 +174,16 @@ bool is_clean_exit(int code, int status) { status == SIGHUP || status == SIGINT || status == SIGTERM || - status == SIGPIPE; + status == SIGPIPE || + (success_status && + set_contains(success_status->signal, INT_TO_PTR(status))); return false; } -bool is_clean_exit_lsb(int code, int status) { +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { - if (is_clean_exit(code, status)) + if (is_clean_exit(code, status, success_status)) return true; return diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h index 3f42c15e3b..d3b548fc96 100644 --- a/src/shared/exit-status.h +++ b/src/shared/exit-status.h @@ -22,7 +22,7 @@ ***/ #include - +#include "set.h" typedef enum ExitStatus { /* EXIT_SUCCESS defined by libc */ /* EXIT_FAILURE defined by libc */ @@ -77,7 +77,12 @@ typedef enum ExitStatusLevel { EXIT_STATUS_FULL = EXIT_STATUS_LSB } ExitStatusLevel; +typedef struct ExitStatusSet { + Set *code; + Set *signal; +} ExitStatusSet; + const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level); -bool is_clean_exit(int code, int status); -bool is_clean_exit_lsb(int code, int status); +bool is_clean_exit(int code, int status, ExitStatusSet *success_status); +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); diff --git a/src/shared/hashmap.c b/src/shared/hashmap.c index e2395d4d42..be37a3659d 100644 --- a/src/shared/hashmap.c +++ b/src/shared/hashmap.c @@ -378,6 +378,21 @@ void* hashmap_get(Hashmap *h, const void *key) { return e->value; } +bool hashmap_contains(Hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + if (!h) + return false; + + hash = h->hash_func(key) % NBUCKETS; + + if (!(e = hash_scan(h, hash, key))) + return false; + + return true; +} + void* hashmap_remove(Hashmap *h, const void *key) { struct hashmap_entry *e; unsigned hash; diff --git a/src/shared/hashmap.h b/src/shared/hashmap.h index ee810f5ae1..504f0b7637 100644 --- a/src/shared/hashmap.h +++ b/src/shared/hashmap.h @@ -53,6 +53,7 @@ int hashmap_ensure_allocated(Hashmap **h, hash_func_t hash_func, compare_func_t int hashmap_put(Hashmap *h, const void *key, void *value); int hashmap_replace(Hashmap *h, const void *key, void *value); void* hashmap_get(Hashmap *h, const void *key); +bool hashmap_contains(Hashmap *h, const void *key); void* hashmap_remove(Hashmap *h, const void *key); void* hashmap_remove_value(Hashmap *h, const void *key, void *value); int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); diff --git a/src/shared/set.c b/src/shared/set.c index f5c7c37cab..4d56c4f505 100644 --- a/src/shared/set.c +++ b/src/shared/set.c @@ -57,6 +57,10 @@ void *set_get(Set *s, void *value) { return hashmap_get(MAKE_HASHMAP(s), value); } +bool set_contains(Set *s, void *value) { + return hashmap_contains(MAKE_HASHMAP(s), value); +} + void *set_remove(Set *s, void *value) { return hashmap_remove(MAKE_HASHMAP(s), value); } diff --git a/src/shared/set.h b/src/shared/set.h index c7b6231eed..a6c1d76b0e 100644 --- a/src/shared/set.h +++ b/src/shared/set.h @@ -40,6 +40,7 @@ int set_ensure_allocated(Set **s, hash_func_t hash_func, compare_func_t compare_ int set_put(Set *s, void *value); int set_replace(Set *s, void *value); void *set_get(Set *s, void *value); +bool set_contains(Set *s, void *value); void *set_remove(Set *s, void *value); int set_remove_and_put(Set *s, void *old_value, void *new_value); diff --git a/src/shared/util.c b/src/shared/util.c index e615195af5..cbf44ebdfd 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -4357,7 +4357,7 @@ void execute_directory(const char *directory, DIR *d, char *argv[]) { } if ((path = hashmap_remove(pids, UINT_TO_PTR(si.si_pid)))) { - if (!is_clean_exit(si.si_code, si.si_status)) { + if (!is_clean_exit(si.si_code, si.si_status, NULL)) { if (si.si_code == CLD_EXITED) log_error("%s exited with exit status %i.", path, si.si_status); else diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 13e0f91096..2481849232 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -2148,7 +2148,7 @@ static void print_status_info(UnitStatusInfo *i) { printf("\t Process: %u %s=%s ", p->pid, p->name, strna(t)); free(t); - good = is_clean_exit_lsb(p->code, p->status); + good = is_clean_exit_lsb(p->code, p->status, NULL); if (!good) { on = ansi_highlight_red(true); off = ansi_highlight_red(false);