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:
parent
b17eed9466
commit
c55693ce1c
|
@ -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
|
||||||
|
*/
|
|
@ -1,8 +1,8 @@
|
||||||
/* daemonitor/src/daemonitor/999_main.c
|
/* daemonitor/src/daemonitor/999_main.c
|
||||||
*
|
*
|
||||||
* (c)2007, Laurence Withers, <l@lwithers.me.uk>.
|
* Copyright: ©2007–2012, Laurence Withers.
|
||||||
* Released under the GNU GPLv3. See file COPYING or
|
* Author: Laurence Withers <l@lwithers.me.uk>
|
||||||
* http://www.gnu.org/copyleft/gpl.html for details.
|
* License: GPLv3
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +37,10 @@ void usage(void)
|
||||||
" " DEFAULT_RESTART_INTERVAL_S "). May be 0 to restart immediately.\n"
|
" " DEFAULT_RESTART_INTERVAL_S "). May be 0 to restart immediately.\n"
|
||||||
" -E, --environment <path> Name of file containing environment variables for\n"
|
" -E, --environment <path> Name of file containing environment variables for\n"
|
||||||
" child process, one NAME=VALUE entry per line.\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);
|
"", stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +57,8 @@ struct option options[] = {
|
||||||
{ "pidfile", required_argument, 0, 'p' },
|
{ "pidfile", required_argument, 0, 'p' },
|
||||||
{ "restart-interval", required_argument, 0, 'R' },
|
{ "restart-interval", required_argument, 0, 'R' },
|
||||||
{ "environment", required_argument, 0, 'E' },
|
{ "environment", required_argument, 0, 'E' },
|
||||||
|
{ "check-command", required_argument, 0, 'C' },
|
||||||
|
{ "check-file", required_argument, 0, 'F' },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,7 +67,7 @@ struct option options[] = {
|
||||||
/* optstr
|
/* optstr
|
||||||
* The list of short commandline options for getopt(3). Sorted alphabetically, lowercase first.
|
* 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 'p': pidfile_argument = optarg; break;
|
||||||
case 'R': restart_argument = optarg; break;
|
case 'R': restart_argument = optarg; break;
|
||||||
case 'E': set_environment_file(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:
|
opts_done:
|
||||||
|
@ -150,6 +158,8 @@ int main(int argc, char* argv[])
|
||||||
LOG(LOG_NOTICE, "Sleeping for %d seconds.", restart_interval);
|
LOG(LOG_NOTICE, "Sleeping for %d seconds.", restart_interval);
|
||||||
safe_sleep_fixed(restart_interval, 0);
|
safe_sleep_fixed(restart_interval, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(run_checks()) return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -158,6 +168,5 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
|
|
||||||
/* options for text editors
|
/* options for text editors
|
||||||
kate: replace-trailing-space-save true; space-indent true; tab-width 4;
|
|
||||||
vim: expandtab:ts=4:sw=4
|
vim: expandtab:ts=4:sw=4
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue