summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--AUTHORS3
-rw-r--r--COPYING19
-rw-r--r--ChangeLog0
-rw-r--r--Makefile.am1
-rw-r--r--NEWS2
-rw-r--r--README0
-rwxr-xr-xautogen.sh6
-rw-r--r--configure.ac38
-rw-r--r--man/.gitignore4
-rw-r--r--man/Makefile.am1
-rw-r--r--man/dudki.8.in123
-rw-r--r--man/dudki.conf.5.in154
-rw-r--r--src/.gitignore5
-rw-r--r--src/Makefile.am18
-rw-r--r--src/configuration.cc149
-rw-r--r--src/configuration.h23
-rw-r--r--src/dudki.cc244
-rw-r--r--src/process.cc184
-rw-r--r--src/process.h36
-rw-r--r--src/util.cc21
-rw-r--r--src/util.h20
21 files changed, 1051 insertions, 0 deletions
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 <hacker@klever.net>
+ Leonid Ivanov <kamel@klever.net>
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 <hacker@klever.net>
+
+.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 <dudki-bugs@klever.net>
+
+.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<Process\fR \fIidentifier\fR\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</Process>\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 <root@klever.net>"
+.br
+MailtoHeader Reply-To devnull@klever.net
+.br
+Notify mailto:hacker@klever.net
+.br
+
+.br
+<Process apache>
+.br
+ PidFile /var/run/httpd.pid
+.br
+ RestartCommand "exec /usr/sbin/apachectl start"
+.br
+</Process>
+.br
+<Process named>
+.br
+ PidFile /var/run/named.pid
+.br
+ RestartCommand "exec /usr/sbin/named"
+.br
+</Process>
+
+.SH AUTHOR
+
+Written by Michael 'hacker' Krelin <hacker@klever.net>
+
+.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 <dudki-bugs@klever.net>
+
+.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 <stdexcept>
+using namespace std;
+#include <dotconf.h>
+#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 },
+ { "<Process", ARG_STR, dco_process, NULL, DCC_ROOT },
+ { "RestartCommand", ARG_STR, dco_restart_command, NULL, DCC_PROCESS },
+ { "User", ARG_STR, dco_user, NULL, DCC_PROCESS },
+ { "Group", ARG_STR, dco_group, NULL, DCC_PROCESS },
+ { "Chroot", ARG_STR, dco_chroot, NULL, DCC_PROCESS },
+ { "</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 <string>
+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 <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+using namespace std;
+#include "configuration.h"
+#include "util.h"
+
+#include "config.h"
+#ifdef HAVE_GETOPT_H
+# include <getopt.h>
+#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 <stdio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/wait.h>
+#include <syslog.h>
+#include <errno.h>
+#include <iostream>
+#include <fstream>
+#include <stdexcept>
+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 <string>
+#include <map>
+using namespace std;
+
+class configuration;
+
+typedef map<string,string> 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<string,process> 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 <unistd.h>
+#include <fstream>
+#include <stdexcept>
+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 <string>
+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 */