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.
This commit is contained in:
Laurence Withers 2012-10-02 19:03:51 +00:00
parent b17eed9466
commit c55693ce1c
2 changed files with 201 additions and 6 deletions

186
src/daemonitor/800_checks.c Normal file
View File

@ -0,0 +1,186 @@
/* daemonitor/src/daemonitor/800_checks.c
*
* Copyright: ©2012, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* 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
*/

View File

@ -1,9 +1,9 @@
/* daemonitor/src/daemonitor/999_main.c
*
* (c)2007, Laurence Withers, <l@lwithers.me.uk>.
* Released under the GNU GPLv3. See file COPYING or
* http://www.gnu.org/copyleft/gpl.html for details.
*/
* Copyright: ©20072012, Laurence Withers.
* Author: Laurence Withers <l@lwithers.me.uk>
* License: GPLv3
*/
@ -37,6 +37,10 @@ void usage(void)
" " DEFAULT_RESTART_INTERVAL_S "). May be 0 to restart immediately.\n"
" -E, --environment <path> Name of file containing environment variables for\n"
" child process, one NAME=VALUE entry per line.\n"
" -C, --check-command <cmd> Runs <cmd> via /bin/sh before restarting a service\n"
" that has exited. Stops if <cmd> fails.\n"
" -F, --check-file <path> Checks that <path> 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
*/