From 9148dac885c0325636c2d33715ba248371706d0d Mon Sep 17 00:00:00 2001 From: Michael Krelin Date: Fri, 09 Jul 2004 16:48:52 +0000 Subject: dudki: initial import into svn repository --- diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..a9fb0c7 --- a/dev/null +++ b/AUTHORS @@ -0,0 +1,3 @@ +Klever dissected: + Michael 'hacker' Krelin + Leonid Ivanov diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..873f196 --- a/dev/null +++ b/COPYING @@ -0,0 +1,19 @@ +Copyright (c) 2004 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- a/dev/null +++ b/ChangeLog diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..9f0d419 --- a/dev/null +++ b/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = src man diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..efd0c31 --- a/dev/null +++ b/NEWS @@ -0,0 +1,2 @@ +0.0 + - Initial release. diff --git a/README b/README new file mode 100644 index 0000000..e69de29 --- a/dev/null +++ b/README diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..d4afeb4 --- a/dev/null +++ b/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh +aclocal \ +&& autoheader \ +&& automake -a \ +&& autoconf \ +&& ./configure "$@" diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..8a2a10b --- a/dev/null +++ b/configure.ac @@ -0,0 +1,38 @@ +AC_INIT([dudki], [0.0], [dudki-bugs@klever.net]) +AC_CONFIG_SRCDIR([src/dudki.cc]) +AC_CONFIG_HEADER([config.h]) +AM_INIT_AUTOMAKE([dist-bzip2]) + +AC_PROG_CXX +AC_PROG_CC + +AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([syslog.h unistd.h getopt.h]) + +AC_HEADER_STDBOOL +AC_C_CONST +AC_TYPE_UID_T +AC_TYPE_PID_T + +AC_FUNC_FORK +AC_HEADER_STDC +AC_TYPE_SIGNAL +AC_CHECK_FUNCS([dup2 strtol]) +AC_CHECK_FUNC([getopt_long],[ + AC_DEFINE([HAVE_GETOPT_LONG],[1],[Define to make use of getopt_long]) + AC_SUBST(HAVE_GETOPT_LONG,1) +],[ + AC_SUBST(HAVE_GETOPT_LONG,0) +]) + +PKG_CHECK_MODULES(DOTCONF,[dotconf],,[ + AC_MSG_ERROR([no dotconf library found]) +]) + +AC_CONFIG_FILES([ + Makefile + src/Makefile + man/Makefile + man/dudki.8 man/dudki.conf.5 +]) +AC_OUTPUT diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 0000000..087e3b6 --- a/dev/null +++ b/man/.gitignore @@ -0,0 +1,4 @@ +Makefile +Makefile.in +dudki.8 +dudki.conf.5 diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..a55b255 --- a/dev/null +++ b/man/Makefile.am @@ -0,0 +1 @@ +man_MANS=dudki.8 dudki.conf.5 diff --git a/man/dudki.8.in b/man/dudki.8.in new file mode 100644 index 0000000..3011034 --- a/dev/null +++ b/man/dudki.8.in @@ -0,0 +1,123 @@ +.TH dudki 8 "June 9th, 2004" "dudki(8)" "Klever Group (http://www.klever.net/)" +.hla en +.ds longopt @HAVE_GETOPT_LONG@ + +.SH NAME + +dudki \- a process monitoring daemon + +.SH SYNOPSYS + +\fBdudki\fR [\fB-h\fR] +.if \*[longopt] [\fB--help\fR] [\fB--usage\fR] +[\fB-V\fR] +.if \*[longopt] [\fB--version\fR] +[\fB-L\fR] +.if \*[longopt] [\fB--license\fR] +[\fB-f\fR \fIconfigfile\fR] +.if \*[longopt] [\fB--config=\fR\fIconfigfile\fR] +[\fB-k\fR] +.if \*[longopt] [\fB--kill\fR] +[\fB-r\fR] +.if \*[longopt] [\fB--reload\fR] +[\fB-c\fR] +.if \*[longopt] [\fB--check\fR] +[\fB-e\fR] +.if \*[longopt] [\fB--ensure\fR] +[\fB-t\fR] +.if \*[longopt] [\fB--test\fR] + +.SH DESCRIPTION + +dudki daemon is designed to run in the background and periodically +check if certain processes specified in the configuration file are +running. If a process is detected as dead dudki tries to restart it +using the command line specified in the configuration file and notifies +the specified contact (currently only via email). + +.SH OPTIONS + +.TP +.ie \*[longopt] \fB-f\fR \fIconfigfile\fR, \fB--config=\fR\fIconfigfile\fR +.el \fB-f\fR \fIconfigfile\fR +Specify the configuration file to use (default is +\fI@sysconfdir@/dudki.conf\fR). +.TP +.ie \*[longopt] \fB-k\fR, \fB--kill\fR +.el \fB-k\fR +Stop the running instance by sending the \fBSIGTERM\fR signal. +.TP +.ie \*[longopt] \fB-r\fR, \fB--reload\fR +.el \fB-r\fR +Reload the running instance by sending the \fBSIGHUP\fR signal. +.TP +.ie \*[longopt] \fB-c\fR, \fB--check\fR +.el \fB-c\fR +Check if dudki is running. Exit with non-zero status if not. +.TP +.ie \*[longopt] \fB-e\fR, \fB--ensure\fR +.el \fB-e\fR +Ensure that dudki is running. Load, if not. Useful for running as a +cron job once in a while. If the daemon is running runs quietly +providing no output. +.TP +.ie \*[longopt] \fB-t\fR, \fB--test\fR +.el \fB-t\fR +Check the syntax of configuration file and exit. +.TP +.ie \*[longopt] \fB-h\fR, \fB--help\fR, \fB--usage\fR +.el \fB-h\fR +Display short usage message and exit. +.TP +.ie \*[longopt] \fB-V\fR, \fB--version\fR +.el \fB-V\fR +Report version and exit. +.TP +.ie \*[longopt] \fB-L\fR, \fB--license\fR +.el \fB-L\fR +Show licensing terms. + +.SH EXIT STATUS + +Zero in case of success, non-zero otherwise. + +.SH FILES +.TP +@sysconfdir@/dudki.conf +Default configuration file. +.TP +/var/run/dudki.pid +Default pid-file for the dudki process. + +.SH AUTHOR + +Written by Michael 'hacker' Krelin + +.SH COPYRIGHT + +Copyright (c) 2004 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to + +.SH SEE ALSO +\fBdudki.conf\fR(5) diff --git a/man/dudki.conf.5.in b/man/dudki.conf.5.in new file mode 100644 index 0000000..23f636d --- a/dev/null +++ b/man/dudki.conf.5.in @@ -0,0 +1,154 @@ +.TH dudki.conf 5 "July 9th, 2004" "dudki.conf(5)" "Klever Group (http://www.klever.net/)" +.hla en + +.SH NAME + +dudki.conf \- The configuration file for the dudki process +monitoring daemon + +.SH SYNOPSIS + +The dudki.conf file is a runtime configuration file for the dudki +process monitoring daemon. It contains the information about the process +being monitored and the instruction on how to cope with the processes. + +.SH FILE FORMAT + +The file consist of the global configuration and per-process +configuration sections. The global configuration controls general +dudki behaviour and sets defaults for per-process configuration +directives. + +.SH GLOBAL CONFIGURATION + +.TP +\fBCheckInterval\fR \fIseconds\fR +Specifies interval in seconds at wich dudki performs checks. +.TP +\fBDaemonize\fR \fIon/off\fR +Specifies whether dudki daemon should fork itself into background. +Normally, you don't want it to run in foreground. Unless you want to run +it from \fIinit(8)\fR process (I've nevetr tried it) via +\fIinittab(5)\fR. +.TP +\fBPidFile\fR \fIfilename\fR +Specifies where to store dudki's process id (default is +/var/run/dudki.pid) +.TP +\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR +Sets global defaults for process section's MailtoHeader directive. See +below. +.TP +\fBNotify\fR \fIschema\fR:\fItarget\fR +Sets global default for per process notification. See below. + +.TP +\fB\fR +Starts per process configuration section. The process will be referenced +using the short descriptive name specified (for example in email +notifications). + +.SH PER-PROCESS CONFIGURATION + +Per-process configuration sections specifies the processes to monitor. +And parameters pertaining to the process. + +.TP +\fBPidFile\fR \fIfilename\fR +Specifies the file where to fetch process id of the process being +monitored from. The absence of file, as well as the absence of process +specified by the pid stored in the file signifies the process death and +triggers restart. +.TP +\fBRestartCommand\fR \fIcommand\fR +Specifies the command to run in order to restart the process. +.TP +\fBUser\fR \fIuser\fR +Specifies the unix user to change to before executing the command +specified by \fBRestartCommand\fR. +.TP +\fBGroup\fR \fIgroup\fR +Specifies the unix group to change to before executing the command +specified by \fBRestartCommand\fR. +.TP +\fBChroot\fR \fIpath\fR +Specifies the directory to set filesystem root to before executing the +command specified by by \fBRestartCommand\fR. +.TP +\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR +Specifies extra headers to add to mailto: notifications sent by the +dudki daemon. Headers specified in per-process section override the +ones specified globally.A +.TP +\fBNotify\fR \fIschema\fR:\fItarget\fR +Specifies the contact to be notified whenever something notable happens +to the process. The only schema currently supported is 'mailto:'. +.TP +\fB\fR +Signifies the end of process section. + +.SH EXAMPLE + +.br +CheckInterval 60 +.br +PidFile /var/run/dudki.pid +.br +Daemonize on +.br +MailtoHeader From "Dudki " +.br +MailtoHeader Reply-To devnull@klever.net +.br +Notify mailto:hacker@klever.net +.br + +.br + +.br + PidFile /var/run/httpd.pid +.br + RestartCommand "exec /usr/sbin/apachectl start" +.br + +.br + +.br + PidFile /var/run/named.pid +.br + RestartCommand "exec /usr/sbin/named" +.br + + +.SH AUTHOR + +Written by Michael 'hacker' Krelin + +.SH COPYRIGHT + +Copyright (c) 2004 Klever Group (http://www.klever.net/) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +.SH BUGS + +You tell me. Send reports to + +.SH SEE ALSO +\fBdudki\fR(8), \fBinit\fR(8), \fBinittab\fR(5) diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..e26d996 --- a/dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +.deps +Makefile +Makefile.in +COPYING.cc +dudki diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..3810272 --- a/dev/null +++ b/src/Makefile.am @@ -0,0 +1,18 @@ +sbin_PROGRAMS = dudki + +INCLUDES = ${DOTCONF_CFLAGS} +LIBS += ${DOTCONF_LIBS} +AM_CPPFLAGS = \ + -DDEFAULT_CONF_FILE=\"${sysconfdir}/${PACKAGE}.conf\" \ + -DDEFAULT_PID_FILE=\"/var/run/${PACKAGE}.pid\" + +dudki_SOURCES = dudki.cc \ + process.cc process.h \ + configuration.cc configuration.h \ + util.cc util.h \ + COPYING.cc + +COPYING.cc: ${top_srcdir}/COPYING + echo "const char * COPYING =" >$@ || (rm $@;exit 1) + sed 's/"/\\"/g' $< | sed 's/^/\"/' | sed 's/$$/\\n\"/' >>$@ || (rm $@;exit 1) + echo ";" >>$@ || (rm $@;exit 1) diff --git a/src/configuration.cc b/src/configuration.cc new file mode 100644 index 0000000..eb010c1 --- a/dev/null +++ b/src/configuration.cc @@ -0,0 +1,149 @@ +#include +using namespace std; +#include +#include "configuration.h" + +#ifndef DEFAULT_PID_FILE +# define DEFAULT_PID_FILE "/var/run/dudki.pid" +#endif + +configuration::configuration() + : check_interval(60), pidfile(DEFAULT_PID_FILE), + daemonize(true) { + } + +enum dc_ctx { + DCC_ROOT = 1, + DCC_PROCESS = 2 +}; +struct dc_context { + dc_ctx ctx; + configuration* cf; + process* ps; + + dc_context() + : ctx(DCC_ROOT), cf(NULL), ps(NULL) { } +}; + +static DOTCONF_CB(dco_check_interval) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->check_interval = cmd->data.value; + return NULL; +} +static DOTCONF_CB(dco_daemonize) { dc_context *dcc = (dc_context*)ctx; + dcc->cf->daemonize = cmd->data.value; + return NULL; +} + +static DOTCONF_CB(dco_pid_file) { dc_context *dcc = (dc_context*)ctx; + switch(dcc->ctx) { + case DCC_ROOT: + dcc->cf->pidfile = cmd->data.str; + break; + case DCC_PROCESS: + dcc->ps->pidfile = cmd->data.str; + break; + default: + return "Unexpected PidFile"; + } + return NULL; +} +static DOTCONF_CB(dco_mailto_header) { dc_context *dcc = (dc_context*)ctx; + if(cmd->arg_count!=2) + return "Invalid number of arguments"; + string h = cmd->data.list[0]; + string v = cmd->data.list[1]; + switch(dcc->ctx) { + case DCC_ROOT: + dcc->cf->mailto_headers[h] = v; + break; + case DCC_PROCESS: + dcc->ps->mailto_headers[h] = v; + break; + default: + return "Unexpected MailtoHeader"; + } + return NULL; +} +static DOTCONF_CB(dco_notify) { dc_context *dcc = (dc_context*)ctx; + switch(dcc->ctx) { + case DCC_ROOT: + dcc->cf->notify = cmd->data.str; + break; + case DCC_PROCESS: + dcc->ps->notify = cmd->data.str; + break; + default: + return "Unexpected Notify"; + } + return NULL; +} + +static DOTCONF_CB(dco_process) { dc_context *dcc = (dc_context*)ctx; + string id = cmd->data.str; + if(id[id.length()-1]=='>') + id.erase(id.length()-1); + dcc->ps = &(dcc->cf->processes[id]); + dcc->ctx = DCC_PROCESS; + return NULL; +} +static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx; + dcc->ps = NULL; + dcc->ctx = DCC_ROOT; + return NULL; +} + +static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx; + dcc->ps->restart_cmd = cmd->data.str; + return NULL; +} +static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx; + dcc->ps->user = cmd->data.str; + return NULL; +} +static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx; + dcc->ps->group = cmd->data.str; + return NULL; +} +static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx; + dcc->ps->chroot = cmd->data.str; + return NULL; +} + +static const configoption_t dc_options[] = { + { "CheckInterval", ARG_INT, dco_check_interval, NULL, DCC_ROOT }, + { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT }, + { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT|DCC_PROCESS }, + { "MailtoHeader", ARG_STR, dco_mailto_header, NULL, DCC_ROOT|DCC_PROCESS }, + { "Notify", ARG_STR, dco_notify, NULL, DCC_ROOT|DCC_PROCESS }, + { "", ARG_NONE, dco__process, NULL, DCC_PROCESS }, + LAST_OPTION +}; + +static const char *dc_context_checker(command_t *cmd,unsigned long mask) { + dc_context *dcc = (dc_context*)cmd->context; + if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) ) + return NULL; + return "misplaced option"; +} +static FUNC_ERRORHANDLER(dc_error_handler) { + throw runtime_error(string("error parsing config file: ")+msg); +} + +void configuration::parse(const string& cfile) { + struct dc_context dcc; + dcc.cf = this; + dcc.ctx = DCC_ROOT; + configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE); + if(!cf) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_create()"); + cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler; + cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker; + if(!dotconf_command_loop(cf)) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_command_loop()"); + dotconf_cleanup(cf); +} diff --git a/src/configuration.h b/src/configuration.h new file mode 100644 index 0000000..314af92 --- a/dev/null +++ b/src/configuration.h @@ -0,0 +1,23 @@ +#ifndef __CONFIGURATION_H +#define __CONFIGURATION_H + +#include +using namespace std; +#include "process.h" + +class configuration { + public: + processes_t processes; + + int check_interval; + string pidfile; + bool daemonize; + headers_t mailto_headers; + string notify; + + configuration(); + + void parse(const string& cfile); +}; + +#endif /* __CONFIGURATION_H */ diff --git a/src/dudki.cc b/src/dudki.cc new file mode 100644 index 0000000..3c50e56 --- a/dev/null +++ b/src/dudki.cc @@ -0,0 +1,244 @@ +#include +#include +#include +#include +#include +#include +using namespace std; +#include "configuration.h" +#include "util.h" + +#include "config.h" +#ifdef HAVE_GETOPT_H +# include +#endif + +#ifndef DEFAULT_CONF_FILE +# define DEFAULT_CONF_FILE "/etc/dudki.conf" +#endif + +#define PHEADER PACKAGE " Version " VERSION +#define PCOPY "Copyright (c) 2004 Klever Group" + +bool finishing = false; +static char **_argv = NULL; + +static void lethal_signal_handler(int signum) { + syslog(LOG_NOTICE,"Lethal signal received. Terminating."); + finishing = true; +} +static void sighup_handler(int signum) { + syslog(LOG_NOTICE,"SUGHUP received, reloading."); + execvp(_argv[0],_argv); +} + +void check_herd(configuration& config) { + for(processes_t::iterator i=config.processes.begin();i!=config.processes.end();++i) + i->second.check(i->first,config); +} + +void signal_self(const configuration& config,int signum) { + ifstream pids(config.pidfile.c_str(),ios::in); + if(!pids) + throw runtime_error("Can't detect running instance"); + pid_t pid = 0; + pids >> pid; + if(!pid) + throw runtime_error("Can't detect running instance"); + if(pid==getpid()) + throw 0; + if(kill(pid,signum)) + throw runtime_error("Failed to signal running instance"); +} + +int main(int argc,char **argv) { + try { + _argv = new char*[argc+1]; + if(!_argv) + throw runtime_error("memory allocation problem at the very start"); + memmove(_argv,argv,sizeof(*_argv)*(argc+1)); + string config_file = DEFAULT_CONF_FILE; + enum { + op_default, + op_work, + op_hup, + op_term, + op_check, + op_ensure, + op_test + } op = op_default; + while(true) { +#define SHORTOPTSTRING "f:hVLrkcet" +#ifdef HAVE_GETOPT_LONG + static struct option opts[] = { + { "help", no_argument, 0, 'h' }, + { "usage", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'V' }, + { "license", no_argument, 0, 'L' }, + { "config", required_argument, 0, 'f' }, + { "kill", no_argument, 0, 'k' }, + { "reload", no_argument, 0, 'r' }, + { "check", no_argument, 0, 'c' }, + { "ensure", no_argument, 0, 'e' }, + { "test", no_argument, 0, 't' }, + { NULL, 0, 0, 0 } + }; + int c = getopt_long(argc,argv,SHORTOPTSTRING,opts,NULL); +#else /* !HAVE_GETOPT_LONG */ + int c = getopt(argc,argv,SHORTOPTSTRING); +#endif /* /HAVE_GETOPT_LONG */ + if(c==-1) + break; + switch(c) { + case 'h': + cerr << PHEADER << endl + << PCOPY << endl << endl << +#ifdef HAVE_GETOPT_LONG + " -h, --help\n" + " --usage display this text\n" + " -V, --version display version number\n" + " -L, --license show license\n" + " -f filename, --config=filename\n" + " specify the configuration file to use\n" + "\n" + " -k, --kill stop running instance\n" + " -r, --reload reload running instance (send SIGHUP)\n" + " -c, --check check if dudki is running\n" + " -e, --ensure ensure that dudki is running\n" + " -t, --test test configuration file and exit" +#else /* !HAVE_GETOPT_LONG */ + " -h display this text\n" + " -V display version number\n" + " -L show license\n" + " -f filename specify the configuration file to use\n" + "\n" + " -k stop running instance\n" + " -r reload running instance (send SIGHUP)\n" + " -c check if dudki is running\n" + " -e ensure that dudki is running\n" + " -t test configuration file and exit" +#endif /* /HAVE_GETOPT_LONG */ + << endl; + exit(0); + break; + case 'V': + cerr << VERSION << endl; + exit(0); + break; + case 'L': + extern const char *COPYING; + cerr << COPYING << endl; + exit(0); + break; + case 'f': + config_file = optarg; + break; + case 'k': + if(op!=op_default) { + cerr << "Can't obey two or more orders at once" << endl; + exit(1); + } + op = op_term; + break; + case 'r': + if(op!=op_default) { + cerr << "Can't obey two or more orders at once" << endl; + exit(1); + } + op = op_hup; + break; + case 'c': + if(op!=op_default) { + cerr << "Can't obey two or more orders at once" << endl; + exit(1); + } + op = op_check; + break; + case 'e': + if(op!=op_default) { + cerr << "Can't obey two or more orders at once" << endl; + exit(1); + } + op = op_ensure; + break; + case 't': + if(op!=op_default) { + cerr << "Can't obey two or more orders at once" << endl; + exit(1); + } + op = op_test; + break; + default: + cerr << "Huh??" << endl; + exit(1); + break; + } + } + const char *sid = *argv; + const char *t; + while(t = index(sid,'/')) { + sid = t; sid++; + } + openlog(sid,LOG_CONS|LOG_PERROR|LOG_PID,LOG_DAEMON); + configuration config; + config.parse(config_file); + switch(op) { + case op_test: + cerr << "Configuration OK" << endl; + break; + case op_hup: + signal_self(config,SIGHUP); + break; + case op_term: + signal_self(config,SIGTERM); + break; + case op_check: + try{ + signal_self(config,0); + exit(0); + }catch(exception& e) { + exit(1); + } + case op_ensure: + try { + signal_self(config,0); + break; + }catch(exception& e) { + syslog(LOG_NOTICE,"The dudki process is down, taking its place"); + config.daemonize = true; + }catch(int zero) { + // we throw zero in case we're ensuring that this very process is running. + // we don't have to daemonize if we're daemonic. + config.daemonize = false; + } + case op_default: + case op_work: + { + if(config.daemonize) { + pid_t pf = fork(); + if(pf<0) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()"); + if(pf) { + _exit(0); + } + } + pid_file pidfile; + pidfile.set(config.pidfile); + signal(SIGINT,lethal_signal_handler); + signal(SIGABRT,lethal_signal_handler); + signal(SIGTERM,lethal_signal_handler); + signal(SIGHUP,sighup_handler); + while(!finishing) { + check_herd(config); + sleep(config.check_interval); + } + } + break; + default: + throw runtime_error(string(__PRETTY_FUNCTION__)+": internal error"); + } + }catch(exception& e) { + cerr << "Oops: " << e.what() << endl; + return 1; + } +} diff --git a/src/process.cc b/src/process.cc new file mode 100644 index 0000000..fda35e8 --- a/dev/null +++ b/src/process.cc @@ -0,0 +1,184 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#include "process.h" +#include "configuration.h" + +void process::check(const string& id,configuration& config) { + bool running = false; + ifstream pids(pidfile.c_str(),ios::in); + if(pids) { + pid_t pid = 0; + pids >> pid; + pids.close(); + if(pid) { + if(!kill(pid,0)) { + running = true; + } + } + } + if(running){ + patience = 0; + }else{ + if(patience>60) { // TODO: configurable + patience = 0; + }else{ + if(patience<10) { // TODO: configurable + syslog(LOG_NOTICE,"The process '%s' is down, trying to launch.",id.c_str()); + do_notify(id,"Starting up", + "The named process seems to be down. Dudki will try\n" + "to revive it by running the specified command.\n", + config); + try { + launch(id,config); + }catch(exception& e) { + syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what()); + } + }else if(patience==10){ // TODO: configurable like the above + syslog(LOG_NOTICE,"Giving up on process '%s' for a while",id.c_str()); + do_notify(id,"Giving up", + "After a number of attempts to relaunch the named process\n" + "It still seems to be down. Dudki is giving up attempts\n" + "to revive the process for a while.\n", + config); + } + patience++; + } + } +} + +void process::launch(const string& id,configuration& config) { + uid_t uid = 0; + if(!user.empty()) { + struct passwd *ptmp = getpwnam(user.c_str()); + if(ptmp) { + uid = ptmp->pw_uid; + }else{ + errno=0; + uid = strtol(user.c_str(),NULL,0); + if(errno) + throw runtime_error("Failed to resolve User value to uid"); + } + } + gid_t gid = 0; + if(!group.empty()) { + struct group *gtmp = getgrnam(group.c_str()); + if(gtmp) { + gid = gtmp->gr_gid; + }else{ + errno = 0; + gid = strtol(group.c_str(),NULL,0); + if(errno) + throw runtime_error("Failed to reslove Group value to gid"); + } + } + pid_t p = fork(); + if(p<0) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()"); + if(!p) { + // child + try { + setsid(); + if(!chroot.empty()) { + if(::chroot(chroot.c_str())) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()"); + } + if(!group.empty()) { + // TODO: initgroups()? + if((getgid()!=gid) && setgid(gid)) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()"); + } + if(!user.empty()) { + if((getuid()!=uid) && setuid(uid)) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()"); + } + char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL }; + close(0); close(1); close(2); + execv("/bin/sh",argv); + }catch(exception& e) { + syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what()); + } + _exit(-1); + } + // parent + int rv; + if(waitpid(p,&rv,0)<0) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()"); +} + +void process::do_notify(const string& id,const string& event,const string& description,configuration& config) { + string the_notify; + if(!notify.empty()) + the_notify=notify; + else if(!config.notify.empty()) + the_notify=config.notify; + else + return; + try { + string::size_type colon = the_notify.find(':'); + if(colon==string::npos) + throw runtime_error("invalid notify action specification"); + string nschema = the_notify.substr(0,colon); + string ntarget = the_notify.substr(colon+1); + if(nschema=="mailto") { + notify_mailto(ntarget,id,event,description,config); + }else + throw runtime_error("unrecognized notification schema"); + }catch(exception& e) { + syslog(LOG_ERR,"Notification error: %s",e.what()); + } +} + +void process::notify_mailto(const string& email,const string& id,const string& event,const string& description,configuration& config) { + int files[2]; + if(pipe(files)) + throw runtime_error("Failed to pipe()"); + pid_t pid = vfork(); + if(pid==-1) { + close(files[0]); + close(files[1]); + throw runtime_error("Failed to vfork()"); + } + if(!pid) { + // child + if(dup2(files[0],0)!=0) + _exit(-1); + close(1); + close(files[0]); + close(files[1]); + execl("/usr/sbin/sendmail","usr/sbin/sendmail","-i",email.c_str(),NULL); + _exit(-1); + } + // parent + close(files[0]); + FILE *mta = fdopen(files[1],"w"); + for(headers_t::const_iterator i=mailto_headers.begin();i!=mailto_headers.end();++i) { + fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str()); + } + for(headers_t::const_iterator i=config.mailto_headers.begin();i!=config.mailto_headers.end();++i) { + if(mailto_headers.find(i->first)!=mailto_headers.end()) + continue; + fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str()); + } + fprintf(mta, + "Subject: [%s] %s\n\n" + "%s\n" + "---\n" + "This message was sent automatically by the 'dudki' daemon\n", + id.c_str(), event.c_str(), + description.c_str() ); + fclose(mta); + int status; + waitpid(pid,&status,0); + // TODO: check the return code +} diff --git a/src/process.h b/src/process.h new file mode 100644 index 0000000..b6d7091 --- a/dev/null +++ b/src/process.h @@ -0,0 +1,36 @@ +#ifndef __PROCESS_H +#define __PROCESS_H + +#include +#include +using namespace std; + +class configuration; + +typedef map headers_t; + +class process { + public: + string pidfile; + string restart_cmd; + string notify; + string user; + string group; + string chroot; + headers_t mailto_headers; + + int patience; + + process() + : patience(0) { } + + void check(const string& id,configuration& config); + void launch(const string& id,configuration& config); + void do_notify(const string& id,const string& event,const string& description,configuration& config); + void notify_mailto(const string& email,const string& id,const string& event, + const string& description,configuration& config); +}; + +typedef map processes_t; + +#endif /* __PROCESS_H */ diff --git a/src/util.cc b/src/util.cc new file mode 100644 index 0000000..afb2641 --- a/dev/null +++ b/src/util.cc @@ -0,0 +1,21 @@ +#include +#include +#include +using namespace std; +#include "util.h" + +void pid_file::set(const string& f,bool u) { + ofstream of(f.c_str(),ios::trunc); + if(!of) + throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to open file for writing pid"); + of << getpid() << endl; + of.close(); + file_name = f; + unlink_pid = u; +} +void pid_file::unlink() { + if(!unlink_pid) + return; + ::unlink(file_name.c_str()); +} + diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..314d8e2 --- a/dev/null +++ b/src/util.h @@ -0,0 +1,20 @@ +#ifndef __UTIL_H +#define __UTIL_H + +#include +using namespace std; + +class pid_file { + public: + string file_name; + bool unlink_pid; + + pid_file() + : unlink_pid(false) { } + ~pid_file() { unlink(); } + + void set(const string& f,bool u=true); + void unlink(); +}; + +#endif /* __UTIL_H */ -- cgit v0.9.0.2