From c55693ce1c33dad3ce1909f0cc7ec2c5287bafa9 Mon Sep 17 00:00:00 2001 From: Laurence Withers Date: Tue, 2 Oct 2012 19:03:51 +0000 Subject: [PATCH] Add respawn checking feature Add a feature which allows arbitrary checks to be performed whenever the child process is about to be respawned. Currently implemented checks allow running of a command (via system(3)) or testing the existence of a file. This was developed to be used to stop respawns of a daemon that was started in response to a udev hotplug event, but it is generally applicable to many other scenarios. --- src/daemonitor/800_checks.c | 186 ++++++++++++++++++++++++++++++++++++ src/daemonitor/999_main.c | 21 ++-- 2 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 src/daemonitor/800_checks.c diff --git a/src/daemonitor/800_checks.c b/src/daemonitor/800_checks.c new file mode 100644 index 0000000..1f7f11b --- /dev/null +++ b/src/daemonitor/800_checks.c @@ -0,0 +1,186 @@ +/* daemonitor/src/daemonitor/800_checks.c + * + * Copyright: ©2012, Laurence Withers. + * Author: Laurence Withers + * License: GPLv3 + */ + + + +/* RESPAWN CHECKS ************************************************************** + * + * This file implements functionality for various checks that may be carried + * out whenever the child process needs to be respawned. An arbitrary number of + * checks may be registered; if any single check fails, the entire test counts + * as failing. The result of the checks is found by calling run_checks(). + * + * A failed check will log details at LOG_NOTICE (or above). + */ + + + +/* PUBLIC API *****************************************************************/ + + + +/* check_command_register() + * cmd: A command (passed to system(3)) to run. If it exits with code 0, the + * check passes. + */ +void check_command_register(const char* cmd); + + + +/* check_file_register() + * path: A file to test. If it exists, the check passes. + */ +void check_file_register(const char* cmd); + + + +/* run_checks() + * Returns 0 if all checks succeeded (including when none are registered) and + * non-0 if any check failed. + */ +int run_checks(void); + + + +/* IMPLEMENTATION *************************************************************/ + + + +enum check_type { + check_type_command, + check_type_file, +}; + + + +/* struct check + * check_head + * A linked list of the checks that are to be run. + */ +struct check { + enum check_type type; + + /* can be the argument for system(3) or the path to check for a file */ + const char* arg; + + /* linked list structure */ + struct check* next; +}; + +struct check* check_head = 0; + + + +/* check_generic_register() + * Instantiates a ‘struct check’ and adds it to the list at ‘check_head’. + */ +void +check_generic_register(enum check_type type, const char* arg) +{ + struct check* c; + + c = malloc(sizeof(*c)); + c->type = type; + c->arg = arg; + c->next = check_head; + check_head = c; +} + + + +void +check_command_register(const char* cmd) +{ + check_generic_register(check_type_command, cmd); +} + + + +int +check_command_run(const struct check* iter) +{ + int ret; + + ret = system(iter->arg); + if(ret == -1) { + LOG(LOG_ERR, "Unable to run check command \"%s\": %m.", iter->arg); + return 0; /* don't exit just because system(3) failed */ + } + + if(WIFSIGNALED(ret)) { + LOG(LOG_WARNING, "Check command \"%s\" killed by signal (%s). Exiting.", + iter->arg, strsignal(WTERMSIG(ret))); + return -1; + } + + if(!WIFEXITED(ret)) { + LOG(LOG_WARNING, "Unrecognised exit code 0x%X from check command " + "\"%s\".", ret, iter->arg); + return 0; + } + + if(WEXITSTATUS(ret)) { + LOG(LOG_NOTICE, "Check command \"%s\" signalled failure (exit code " + "%d). Exiting.", iter->arg, WEXITSTATUS(ret)); + return -1; + } + + return 0; +} + + + +void +check_file_register(const char* path) +{ + check_generic_register(check_type_file, path); +} + + + +int +check_file_run(const struct check* iter) +{ + int ret; + + ret = access(iter->arg, F_OK); + if(ret) { + LOG(LOG_NOTICE, "Check file \"%s\" does not exist. Exiting.", + iter->arg); + return -1; + } + + return 0; +} + + + +int +run_checks(void) +{ + const struct check* iter; + + for(iter = check_head; iter; iter = iter->next) { + switch(iter->type) { + case check_type_command: + if(check_command_run(iter)) return -1; + break; + + case check_type_file: + if(check_file_run(iter)) return -1; + break; + } + } + + return 0; +} + + + +/* options for text editors +vim: expandtab:ts=4:sw=4 +*/ diff --git a/src/daemonitor/999_main.c b/src/daemonitor/999_main.c index 7474a0e..b4819b2 100644 --- a/src/daemonitor/999_main.c +++ b/src/daemonitor/999_main.c @@ -1,9 +1,9 @@ /* daemonitor/src/daemonitor/999_main.c * - * (c)2007, Laurence Withers, . - * Released under the GNU GPLv3. See file COPYING or - * http://www.gnu.org/copyleft/gpl.html for details. -*/ + * Copyright: ©2007–2012, Laurence Withers. + * Author: Laurence Withers + * License: GPLv3 + */ @@ -37,6 +37,10 @@ void usage(void) " " DEFAULT_RESTART_INTERVAL_S "). May be 0 to restart immediately.\n" " -E, --environment Name of file containing environment variables for\n" " child process, one NAME=VALUE entry per line.\n" + " -C, --check-command Runs via /bin/sh before restarting a service\n" + " that has exited. Stops if fails.\n" + " -F, --check-file Checks that exists before restarting a service\n" + " that has exited. Stops if it does not exist.\n" "", stderr); } @@ -53,6 +57,8 @@ struct option options[] = { { "pidfile", required_argument, 0, 'p' }, { "restart-interval", required_argument, 0, 'R' }, { "environment", required_argument, 0, 'E' }, + { "check-command", required_argument, 0, 'C' }, + { "check-file", required_argument, 0, 'F' }, { 0, 0, 0, 0 } }; @@ -61,7 +67,7 @@ struct option options[] = { /* optstr * The list of short commandline options for getopt(3). Sorted alphabetically, lowercase first. */ -const char* optstr = "dhl:p:E:R:V"; +const char* optstr = "dhl:p:C:E:F:R:V"; @@ -90,6 +96,8 @@ int main(int argc, char* argv[]) case 'p': pidfile_argument = optarg; break; case 'R': restart_argument = optarg; break; case 'E': set_environment_file(optarg); break; + case 'C': check_command_register(optarg); break; + case 'F': check_file_register(optarg); break; } } opts_done: @@ -150,6 +158,8 @@ int main(int argc, char* argv[]) LOG(LOG_NOTICE, "Sleeping for %d seconds.", restart_interval); safe_sleep_fixed(restart_interval, 0); } + + if(run_checks()) return 0; } return 0; @@ -158,6 +168,5 @@ int main(int argc, char* argv[]) /* options for text editors -kate: replace-trailing-space-save true; space-indent true; tab-width 4; vim: expandtab:ts=4:sw=4 */