summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore 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 @@
1Klever dissected:
2 Michael 'hacker' Krelin <hacker@klever.net>
3 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 @@
1Copyright (c) 2004 Klever Group (http://www.klever.net/)
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of
4this software and associated documentation files (the "Software"), to deal in
5the Software without restriction, including without limitation the rights to
6use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7of the Software, and to permit persons to whom the Software is furnished to do
8so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all
11copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19SOFTWARE.
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 @@
10.0
2 - 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 @@
1#!/bin/sh
2aclocal \
3&& autoheader \
4&& automake -a \
5&& autoconf \
6&& ./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 @@
1AC_INIT([dudki], [0.0], [dudki-bugs@klever.net])
2AC_CONFIG_SRCDIR([src/dudki.cc])
3AC_CONFIG_HEADER([config.h])
4AM_INIT_AUTOMAKE([dist-bzip2])
5
6AC_PROG_CXX
7AC_PROG_CC
8
9AC_HEADER_SYS_WAIT
10AC_CHECK_HEADERS([syslog.h unistd.h getopt.h])
11
12AC_HEADER_STDBOOL
13AC_C_CONST
14AC_TYPE_UID_T
15AC_TYPE_PID_T
16
17AC_FUNC_FORK
18AC_HEADER_STDC
19AC_TYPE_SIGNAL
20AC_CHECK_FUNCS([dup2 strtol])
21AC_CHECK_FUNC([getopt_long],[
22 AC_DEFINE([HAVE_GETOPT_LONG],[1],[Define to make use of getopt_long])
23 AC_SUBST(HAVE_GETOPT_LONG,1)
24],[
25 AC_SUBST(HAVE_GETOPT_LONG,0)
26])
27
28PKG_CHECK_MODULES(DOTCONF,[dotconf],,[
29 AC_MSG_ERROR([no dotconf library found])
30])
31
32AC_CONFIG_FILES([
33 Makefile
34 src/Makefile
35 man/Makefile
36 man/dudki.8 man/dudki.conf.5
37])
38AC_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 @@
1Makefile
2Makefile.in
3dudki.8
4dudki.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 @@
1.TH dudki 8 "June 9th, 2004" "dudki(8)" "Klever Group (http://www.klever.net/)"
2.hla en
3.ds longopt @HAVE_GETOPT_LONG@
4
5.SH NAME
6
7dudki \- a process monitoring daemon
8
9.SH SYNOPSYS
10
11\fBdudki\fR [\fB-h\fR]
12.if \*[longopt] [\fB--help\fR] [\fB--usage\fR]
13[\fB-V\fR]
14.if \*[longopt] [\fB--version\fR]
15[\fB-L\fR]
16.if \*[longopt] [\fB--license\fR]
17[\fB-f\fR \fIconfigfile\fR]
18.if \*[longopt] [\fB--config=\fR\fIconfigfile\fR]
19[\fB-k\fR]
20.if \*[longopt] [\fB--kill\fR]
21[\fB-r\fR]
22.if \*[longopt] [\fB--reload\fR]
23[\fB-c\fR]
24.if \*[longopt] [\fB--check\fR]
25[\fB-e\fR]
26.if \*[longopt] [\fB--ensure\fR]
27[\fB-t\fR]
28.if \*[longopt] [\fB--test\fR]
29
30.SH DESCRIPTION
31
32dudki daemon is designed to run in the background and periodically
33check if certain processes specified in the configuration file are
34running. If a process is detected as dead dudki tries to restart it
35using the command line specified in the configuration file and notifies
36the specified contact (currently only via email).
37
38.SH OPTIONS
39
40.TP
41.ie \*[longopt] \fB-f\fR \fIconfigfile\fR, \fB--config=\fR\fIconfigfile\fR
42.el \fB-f\fR \fIconfigfile\fR
43Specify the configuration file to use (default is
44\fI@sysconfdir@/dudki.conf\fR).
45.TP
46.ie \*[longopt] \fB-k\fR, \fB--kill\fR
47.el \fB-k\fR
48Stop the running instance by sending the \fBSIGTERM\fR signal.
49.TP
50.ie \*[longopt] \fB-r\fR, \fB--reload\fR
51.el \fB-r\fR
52Reload the running instance by sending the \fBSIGHUP\fR signal.
53.TP
54.ie \*[longopt] \fB-c\fR, \fB--check\fR
55.el \fB-c\fR
56Check if dudki is running. Exit with non-zero status if not.
57.TP
58.ie \*[longopt] \fB-e\fR, \fB--ensure\fR
59.el \fB-e\fR
60Ensure that dudki is running. Load, if not. Useful for running as a
61cron job once in a while. If the daemon is running runs quietly
62providing no output.
63.TP
64.ie \*[longopt] \fB-t\fR, \fB--test\fR
65.el \fB-t\fR
66Check the syntax of configuration file and exit.
67.TP
68.ie \*[longopt] \fB-h\fR, \fB--help\fR, \fB--usage\fR
69.el \fB-h\fR
70Display short usage message and exit.
71.TP
72.ie \*[longopt] \fB-V\fR, \fB--version\fR
73.el \fB-V\fR
74Report version and exit.
75.TP
76.ie \*[longopt] \fB-L\fR, \fB--license\fR
77.el \fB-L\fR
78Show licensing terms.
79
80.SH EXIT STATUS
81
82Zero in case of success, non-zero otherwise.
83
84.SH FILES
85.TP
86@sysconfdir@/dudki.conf
87Default configuration file.
88.TP
89/var/run/dudki.pid
90Default pid-file for the dudki process.
91
92.SH AUTHOR
93
94Written by Michael 'hacker' Krelin <hacker@klever.net>
95
96.SH COPYRIGHT
97
98Copyright (c) 2004 Klever Group (http://www.klever.net/)
99
100Permission is hereby granted, free of charge, to any person obtaining a copy of
101this software and associated documentation files (the "Software"), to deal in
102the Software without restriction, including without limitation the rights to
103use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
104of the Software, and to permit persons to whom the Software is furnished to do
105so, subject to the following conditions:
106
107The above copyright notice and this permission notice shall be included in all
108copies or substantial portions of the Software.
109
110THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
111IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
112FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
113AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
114LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
115OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
116SOFTWARE.
117
118.SH BUGS
119
120You tell me. Send reports to <dudki-bugs@klever.net>
121
122.SH SEE ALSO
123\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 @@
1.TH dudki.conf 5 "July 9th, 2004" "dudki.conf(5)" "Klever Group (http://www.klever.net/)"
2.hla en
3
4.SH NAME
5
6dudki.conf \- The configuration file for the dudki process
7monitoring daemon
8
9.SH SYNOPSIS
10
11The dudki.conf file is a runtime configuration file for the dudki
12process monitoring daemon. It contains the information about the process
13being monitored and the instruction on how to cope with the processes.
14
15.SH FILE FORMAT
16
17The file consist of the global configuration and per-process
18configuration sections. The global configuration controls general
19dudki behaviour and sets defaults for per-process configuration
20directives.
21
22.SH GLOBAL CONFIGURATION
23
24.TP
25\fBCheckInterval\fR \fIseconds\fR
26Specifies interval in seconds at wich dudki performs checks.
27.TP
28\fBDaemonize\fR \fIon/off\fR
29Specifies whether dudki daemon should fork itself into background.
30Normally, you don't want it to run in foreground. Unless you want to run
31it from \fIinit(8)\fR process (I've nevetr tried it) via
32\fIinittab(5)\fR.
33.TP
34\fBPidFile\fR \fIfilename\fR
35Specifies where to store dudki's process id (default is
36/var/run/dudki.pid)
37.TP
38\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR
39Sets global defaults for process section's MailtoHeader directive. See
40below.
41.TP
42\fBNotify\fR \fIschema\fR:\fItarget\fR
43Sets global default for per process notification. See below.
44
45.TP
46\fB<Process\fR \fIidentifier\fR\fB>\fR
47Starts per process configuration section. The process will be referenced
48using the short descriptive name specified (for example in email
49notifications).
50
51.SH PER-PROCESS CONFIGURATION
52
53Per-process configuration sections specifies the processes to monitor.
54And parameters pertaining to the process.
55
56.TP
57\fBPidFile\fR \fIfilename\fR
58Specifies the file where to fetch process id of the process being
59monitored from. The absence of file, as well as the absence of process
60specified by the pid stored in the file signifies the process death and
61triggers restart.
62.TP
63\fBRestartCommand\fR \fIcommand\fR
64Specifies the command to run in order to restart the process.
65.TP
66\fBUser\fR \fIuser\fR
67Specifies the unix user to change to before executing the command
68specified by \fBRestartCommand\fR.
69.TP
70\fBGroup\fR \fIgroup\fR
71Specifies the unix group to change to before executing the command
72specified by \fBRestartCommand\fR.
73.TP
74\fBChroot\fR \fIpath\fR
75Specifies the directory to set filesystem root to before executing the
76command specified by by \fBRestartCommand\fR.
77.TP
78\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR
79Specifies extra headers to add to mailto: notifications sent by the
80dudki daemon. Headers specified in per-process section override the
81ones specified globally.A
82.TP
83\fBNotify\fR \fIschema\fR:\fItarget\fR
84Specifies the contact to be notified whenever something notable happens
85to the process. The only schema currently supported is 'mailto:'.
86.TP
87\fB</Process>\fR
88Signifies the end of process section.
89
90.SH EXAMPLE
91
92.br
93CheckInterval 60
94.br
95PidFile /var/run/dudki.pid
96.br
97Daemonize on
98.br
99MailtoHeader From "Dudki <root@klever.net>"
100.br
101MailtoHeader Reply-To devnull@klever.net
102.br
103Notify mailto:hacker@klever.net
104.br
105
106.br
107<Process apache>
108.br
109 PidFile /var/run/httpd.pid
110.br
111 RestartCommand "exec /usr/sbin/apachectl start"
112.br
113</Process>
114.br
115<Process named>
116.br
117 PidFile /var/run/named.pid
118.br
119 RestartCommand "exec /usr/sbin/named"
120.br
121</Process>
122
123.SH AUTHOR
124
125Written by Michael 'hacker' Krelin <hacker@klever.net>
126
127.SH COPYRIGHT
128
129Copyright (c) 2004 Klever Group (http://www.klever.net/)
130
131Permission is hereby granted, free of charge, to any person obtaining a copy of
132this software and associated documentation files (the "Software"), to deal in
133the Software without restriction, including without limitation the rights to
134use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
135of the Software, and to permit persons to whom the Software is furnished to do
136so, subject to the following conditions:
137
138The above copyright notice and this permission notice shall be included in all
139copies or substantial portions of the Software.
140
141THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
142IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
143FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
144AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
145LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
146OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
147SOFTWARE.
148
149.SH BUGS
150
151You tell me. Send reports to <dudki-bugs@klever.net>
152
153.SH SEE ALSO
154\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 @@
1.deps
2Makefile
3Makefile.in
4COPYING.cc
5dudki
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 @@
1sbin_PROGRAMS = dudki
2
3INCLUDES = ${DOTCONF_CFLAGS}
4LIBS += ${DOTCONF_LIBS}
5AM_CPPFLAGS = \
6 -DDEFAULT_CONF_FILE=\"${sysconfdir}/${PACKAGE}.conf\" \
7 -DDEFAULT_PID_FILE=\"/var/run/${PACKAGE}.pid\"
8
9dudki_SOURCES = dudki.cc \
10 process.cc process.h \
11 configuration.cc configuration.h \
12 util.cc util.h \
13 COPYING.cc
14
15COPYING.cc: ${top_srcdir}/COPYING
16 echo "const char * COPYING =" >$@ || (rm $@;exit 1)
17 sed 's/"/\\"/g' $< | sed 's/^/\"/' | sed 's/$$/\\n\"/' >>$@ || (rm $@;exit 1)
18 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 @@
1#include <stdexcept>
2using namespace std;
3#include <dotconf.h>
4#include "configuration.h"
5
6#ifndef DEFAULT_PID_FILE
7# define DEFAULT_PID_FILE "/var/run/dudki.pid"
8#endif
9
10configuration::configuration()
11 : check_interval(60), pidfile(DEFAULT_PID_FILE),
12 daemonize(true) {
13 }
14
15enum dc_ctx {
16 DCC_ROOT = 1,
17 DCC_PROCESS = 2
18};
19struct dc_context {
20 dc_ctx ctx;
21 configuration* cf;
22 process* ps;
23
24 dc_context()
25 : ctx(DCC_ROOT), cf(NULL), ps(NULL) { }
26};
27
28static DOTCONF_CB(dco_check_interval) { dc_context *dcc = (dc_context*)ctx;
29 dcc->cf->check_interval = cmd->data.value;
30 return NULL;
31}
32static DOTCONF_CB(dco_daemonize) { dc_context *dcc = (dc_context*)ctx;
33 dcc->cf->daemonize = cmd->data.value;
34 return NULL;
35}
36
37static DOTCONF_CB(dco_pid_file) { dc_context *dcc = (dc_context*)ctx;
38 switch(dcc->ctx) {
39 case DCC_ROOT:
40 dcc->cf->pidfile = cmd->data.str;
41 break;
42 case DCC_PROCESS:
43 dcc->ps->pidfile = cmd->data.str;
44 break;
45 default:
46 return "Unexpected PidFile";
47 }
48 return NULL;
49}
50static DOTCONF_CB(dco_mailto_header) { dc_context *dcc = (dc_context*)ctx;
51 if(cmd->arg_count!=2)
52 return "Invalid number of arguments";
53 string h = cmd->data.list[0];
54 string v = cmd->data.list[1];
55 switch(dcc->ctx) {
56 case DCC_ROOT:
57 dcc->cf->mailto_headers[h] = v;
58 break;
59 case DCC_PROCESS:
60 dcc->ps->mailto_headers[h] = v;
61 break;
62 default:
63 return "Unexpected MailtoHeader";
64 }
65 return NULL;
66}
67static DOTCONF_CB(dco_notify) { dc_context *dcc = (dc_context*)ctx;
68 switch(dcc->ctx) {
69 case DCC_ROOT:
70 dcc->cf->notify = cmd->data.str;
71 break;
72 case DCC_PROCESS:
73 dcc->ps->notify = cmd->data.str;
74 break;
75 default:
76 return "Unexpected Notify";
77 }
78 return NULL;
79}
80
81static DOTCONF_CB(dco_process) { dc_context *dcc = (dc_context*)ctx;
82 string id = cmd->data.str;
83 if(id[id.length()-1]=='>')
84 id.erase(id.length()-1);
85 dcc->ps = &(dcc->cf->processes[id]);
86 dcc->ctx = DCC_PROCESS;
87 return NULL;
88}
89static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx;
90 dcc->ps = NULL;
91 dcc->ctx = DCC_ROOT;
92 return NULL;
93}
94
95static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx;
96 dcc->ps->restart_cmd = cmd->data.str;
97 return NULL;
98}
99static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx;
100 dcc->ps->user = cmd->data.str;
101 return NULL;
102}
103static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx;
104 dcc->ps->group = cmd->data.str;
105 return NULL;
106}
107static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx;
108 dcc->ps->chroot = cmd->data.str;
109 return NULL;
110}
111
112static const configoption_t dc_options[] = {
113 { "CheckInterval", ARG_INT, dco_check_interval, NULL, DCC_ROOT },
114 { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT },
115 { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT|DCC_PROCESS },
116 { "MailtoHeader", ARG_STR, dco_mailto_header, NULL, DCC_ROOT|DCC_PROCESS },
117 { "Notify", ARG_STR, dco_notify, NULL, DCC_ROOT|DCC_PROCESS },
118 { "<Process", ARG_STR, dco_process, NULL, DCC_ROOT },
119 { "RestartCommand", ARG_STR, dco_restart_command, NULL, DCC_PROCESS },
120 { "User", ARG_STR, dco_user, NULL, DCC_PROCESS },
121 { "Group", ARG_STR, dco_group, NULL, DCC_PROCESS },
122 { "Chroot", ARG_STR, dco_chroot, NULL, DCC_PROCESS },
123 { "</Process>", ARG_NONE, dco__process, NULL, DCC_PROCESS },
124 LAST_OPTION
125};
126
127static const char *dc_context_checker(command_t *cmd,unsigned long mask) {
128 dc_context *dcc = (dc_context*)cmd->context;
129 if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) )
130 return NULL;
131 return "misplaced option";
132}
133static FUNC_ERRORHANDLER(dc_error_handler) {
134 throw runtime_error(string("error parsing config file: ")+msg);
135}
136
137void configuration::parse(const string& cfile) {
138 struct dc_context dcc;
139 dcc.cf = this;
140 dcc.ctx = DCC_ROOT;
141 configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE);
142 if(!cf)
143 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_create()");
144 cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler;
145 cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker;
146 if(!dotconf_command_loop(cf))
147 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_command_loop()");
148 dotconf_cleanup(cf);
149}
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 @@
1#ifndef __CONFIGURATION_H
2#define __CONFIGURATION_H
3
4#include <string>
5using namespace std;
6#include "process.h"
7
8class configuration {
9 public:
10 processes_t processes;
11
12 int check_interval;
13 string pidfile;
14 bool daemonize;
15 headers_t mailto_headers;
16 string notify;
17
18 configuration();
19
20 void parse(const string& cfile);
21};
22
23#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 @@
1#include <unistd.h>
2#include <signal.h>
3#include <syslog.h>
4#include <iostream>
5#include <fstream>
6#include <stdexcept>
7using namespace std;
8#include "configuration.h"
9#include "util.h"
10
11#include "config.h"
12#ifdef HAVE_GETOPT_H
13# include <getopt.h>
14#endif
15
16#ifndef DEFAULT_CONF_FILE
17# define DEFAULT_CONF_FILE "/etc/dudki.conf"
18#endif
19
20#define PHEADER PACKAGE " Version " VERSION
21#define PCOPY "Copyright (c) 2004 Klever Group"
22
23bool finishing = false;
24static char **_argv = NULL;
25
26static void lethal_signal_handler(int signum) {
27 syslog(LOG_NOTICE,"Lethal signal received. Terminating.");
28 finishing = true;
29}
30static void sighup_handler(int signum) {
31 syslog(LOG_NOTICE,"SUGHUP received, reloading.");
32 execvp(_argv[0],_argv);
33}
34
35void check_herd(configuration& config) {
36 for(processes_t::iterator i=config.processes.begin();i!=config.processes.end();++i)
37 i->second.check(i->first,config);
38}
39
40void signal_self(const configuration& config,int signum) {
41 ifstream pids(config.pidfile.c_str(),ios::in);
42 if(!pids)
43 throw runtime_error("Can't detect running instance");
44 pid_t pid = 0;
45 pids >> pid;
46 if(!pid)
47 throw runtime_error("Can't detect running instance");
48 if(pid==getpid())
49 throw 0;
50 if(kill(pid,signum))
51 throw runtime_error("Failed to signal running instance");
52}
53
54int main(int argc,char **argv) {
55 try {
56 _argv = new char*[argc+1];
57 if(!_argv)
58 throw runtime_error("memory allocation problem at the very start");
59 memmove(_argv,argv,sizeof(*_argv)*(argc+1));
60 string config_file = DEFAULT_CONF_FILE;
61 enum {
62 op_default,
63 op_work,
64 op_hup,
65 op_term,
66 op_check,
67 op_ensure,
68 op_test
69 } op = op_default;
70 while(true) {
71 #defineSHORTOPTSTRING "f:hVLrkcet"
72#ifdef HAVE_GETOPT_LONG
73 static struct option opts[] = {
74 { "help", no_argument, 0, 'h' },
75 { "usage", no_argument, 0, 'h' },
76 { "version", no_argument, 0, 'V' },
77 { "license", no_argument, 0, 'L' },
78 { "config", required_argument, 0, 'f' },
79 { "kill", no_argument, 0, 'k' },
80 { "reload", no_argument, 0, 'r' },
81 { "check", no_argument, 0, 'c' },
82 { "ensure", no_argument, 0, 'e' },
83 { "test", no_argument, 0, 't' },
84 { NULL, 0, 0, 0 }
85 };
86 int c = getopt_long(argc,argv,SHORTOPTSTRING,opts,NULL);
87#else /* !HAVE_GETOPT_LONG */
88 int c = getopt(argc,argv,SHORTOPTSTRING);
89#endif /* /HAVE_GETOPT_LONG */
90 if(c==-1)
91 break;
92 switch(c) {
93 case 'h':
94 cerr << PHEADER << endl
95 << PCOPY << endl << endl <<
96#ifdef HAVE_GETOPT_LONG
97 " -h, --help\n"
98 " --usage display this text\n"
99 " -V, --version display version number\n"
100 " -L, --license show license\n"
101 " -f filename, --config=filename\n"
102 " specify the configuration file to use\n"
103 "\n"
104 " -k, --kill stop running instance\n"
105 " -r, --reload reload running instance (send SIGHUP)\n"
106 " -c, --check check if dudki is running\n"
107 " -e, --ensure ensure that dudki is running\n"
108 " -t, --test test configuration file and exit"
109#else /* !HAVE_GETOPT_LONG */
110 " -h display this text\n"
111 " -V display version number\n"
112 " -L show license\n"
113 " -f filename specify the configuration file to use\n"
114 "\n"
115 " -k stop running instance\n"
116 " -r reload running instance (send SIGHUP)\n"
117 " -c check if dudki is running\n"
118 " -e ensure that dudki is running\n"
119 " -t test configuration file and exit"
120#endif /* /HAVE_GETOPT_LONG */
121 << endl;
122 exit(0);
123 break;
124 case 'V':
125 cerr << VERSION << endl;
126 exit(0);
127 break;
128 case 'L':
129 extern const char *COPYING;
130 cerr << COPYING << endl;
131 exit(0);
132 break;
133 case 'f':
134 config_file = optarg;
135 break;
136 case 'k':
137 if(op!=op_default) {
138 cerr << "Can't obey two or more orders at once" << endl;
139 exit(1);
140 }
141 op = op_term;
142 break;
143 case 'r':
144 if(op!=op_default) {
145 cerr << "Can't obey two or more orders at once" << endl;
146 exit(1);
147 }
148 op = op_hup;
149 break;
150 case 'c':
151 if(op!=op_default) {
152 cerr << "Can't obey two or more orders at once" << endl;
153 exit(1);
154 }
155 op = op_check;
156 break;
157 case 'e':
158 if(op!=op_default) {
159 cerr << "Can't obey two or more orders at once" << endl;
160 exit(1);
161 }
162 op = op_ensure;
163 break;
164 case 't':
165 if(op!=op_default) {
166 cerr << "Can't obey two or more orders at once" << endl;
167 exit(1);
168 }
169 op = op_test;
170 break;
171 default:
172 cerr << "Huh??" << endl;
173 exit(1);
174 break;
175 }
176 }
177 const char *sid = *argv;
178 const char *t;
179 while(t = index(sid,'/')) {
180 sid = t; sid++;
181 }
182 openlog(sid,LOG_CONS|LOG_PERROR|LOG_PID,LOG_DAEMON);
183 configuration config;
184 config.parse(config_file);
185 switch(op) {
186 case op_test:
187 cerr << "Configuration OK" << endl;
188 break;
189 case op_hup:
190 signal_self(config,SIGHUP);
191 break;
192 case op_term:
193 signal_self(config,SIGTERM);
194 break;
195 case op_check:
196 try{
197 signal_self(config,0);
198 exit(0);
199 }catch(exception& e) {
200 exit(1);
201 }
202 case op_ensure:
203 try {
204 signal_self(config,0);
205 break;
206 }catch(exception& e) {
207 syslog(LOG_NOTICE,"The dudki process is down, taking its place");
208 config.daemonize = true;
209 }catch(int zero) {
210 // we throw zero in case we're ensuring that this very process is running.
211 // we don't have to daemonize if we're daemonic.
212 config.daemonize = false;
213 }
214 case op_default:
215 case op_work:
216 {
217 if(config.daemonize) {
218 pid_t pf = fork();
219 if(pf<0)
220 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
221 if(pf) {
222 _exit(0);
223 }
224 }
225 pid_file pidfile;
226 pidfile.set(config.pidfile);
227 signal(SIGINT,lethal_signal_handler);
228 signal(SIGABRT,lethal_signal_handler);
229 signal(SIGTERM,lethal_signal_handler);
230 signal(SIGHUP,sighup_handler);
231 while(!finishing) {
232 check_herd(config);
233 sleep(config.check_interval);
234 }
235 }
236 break;
237 default:
238 throw runtime_error(string(__PRETTY_FUNCTION__)+": internal error");
239 }
240 }catch(exception& e) {
241 cerr << "Oops: " << e.what() << endl;
242 return 1;
243 }
244}
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 @@
1#include <stdio.h>
2#include <sys/types.h>
3#include <unistd.h>
4#include <signal.h>
5#include <pwd.h>
6#include <grp.h>
7#include <sys/wait.h>
8#include <syslog.h>
9#include <errno.h>
10#include <iostream>
11#include <fstream>
12#include <stdexcept>
13using namespace std;
14#include "process.h"
15#include "configuration.h"
16
17void process::check(const string& id,configuration& config) {
18 bool running = false;
19 ifstream pids(pidfile.c_str(),ios::in);
20 if(pids) {
21 pid_t pid = 0;
22 pids >> pid;
23 pids.close();
24 if(pid) {
25 if(!kill(pid,0)) {
26 running = true;
27 }
28 }
29 }
30 if(running){
31 patience = 0;
32 }else{
33 if(patience>60) { // TODO: configurable
34 patience = 0;
35 }else{
36 if(patience<10) { // TODO: configurable
37 syslog(LOG_NOTICE,"The process '%s' is down, trying to launch.",id.c_str());
38 do_notify(id,"Starting up",
39 "The named process seems to be down. Dudki will try\n"
40 "to revive it by running the specified command.\n",
41 config);
42 try {
43 launch(id,config);
44 }catch(exception& e) {
45 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
46 }
47 }else if(patience==10){ // TODO: configurable like the above
48 syslog(LOG_NOTICE,"Giving up on process '%s' for a while",id.c_str());
49 do_notify(id,"Giving up",
50 "After a number of attempts to relaunch the named process\n"
51 "It still seems to be down. Dudki is giving up attempts\n"
52 "to revive the process for a while.\n",
53 config);
54 }
55 patience++;
56 }
57 }
58}
59
60void process::launch(const string& id,configuration& config) {
61 uid_t uid = 0;
62 if(!user.empty()) {
63 struct passwd *ptmp = getpwnam(user.c_str());
64 if(ptmp) {
65 uid = ptmp->pw_uid;
66 }else{
67 errno=0;
68 uid = strtol(user.c_str(),NULL,0);
69 if(errno)
70 throw runtime_error("Failed to resolve User value to uid");
71 }
72 }
73 gid_t gid = 0;
74 if(!group.empty()) {
75 struct group *gtmp = getgrnam(group.c_str());
76 if(gtmp) {
77 gid = gtmp->gr_gid;
78 }else{
79 errno = 0;
80 gid = strtol(group.c_str(),NULL,0);
81 if(errno)
82 throw runtime_error("Failed to reslove Group value to gid");
83 }
84 }
85 pid_t p = fork();
86 if(p<0)
87 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
88 if(!p) {
89 // child
90 try {
91 setsid();
92 if(!chroot.empty()) {
93 if(::chroot(chroot.c_str()))
94 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()");
95 }
96 if(!group.empty()) {
97 // TODO: initgroups()?
98 if((getgid()!=gid) && setgid(gid))
99 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()");
100 }
101 if(!user.empty()) {
102 if((getuid()!=uid) && setuid(uid))
103 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()");
104 }
105 char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL };
106 close(0); close(1); close(2);
107 execv("/bin/sh",argv);
108 }catch(exception& e) {
109 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
110 }
111 _exit(-1);
112 }
113 // parent
114 int rv;
115 if(waitpid(p,&rv,0)<0)
116 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()");
117}
118
119void process::do_notify(const string& id,const string& event,const string& description,configuration& config) {
120 string the_notify;
121 if(!notify.empty())
122 the_notify=notify;
123 else if(!config.notify.empty())
124 the_notify=config.notify;
125 else
126 return;
127 try {
128 string::size_type colon = the_notify.find(':');
129 if(colon==string::npos)
130 throw runtime_error("invalid notify action specification");
131 string nschema = the_notify.substr(0,colon);
132 string ntarget = the_notify.substr(colon+1);
133 if(nschema=="mailto") {
134 notify_mailto(ntarget,id,event,description,config);
135 }else
136 throw runtime_error("unrecognized notification schema");
137 }catch(exception& e) {
138 syslog(LOG_ERR,"Notification error: %s",e.what());
139 }
140}
141
142void process::notify_mailto(const string& email,const string& id,const string& event,const string& description,configuration& config) {
143 int files[2];
144 if(pipe(files))
145 throw runtime_error("Failed to pipe()");
146 pid_t pid = vfork();
147 if(pid==-1) {
148 close(files[0]);
149 close(files[1]);
150 throw runtime_error("Failed to vfork()");
151 }
152 if(!pid) {
153 // child
154 if(dup2(files[0],0)!=0)
155 _exit(-1);
156 close(1);
157 close(files[0]);
158 close(files[1]);
159 execl("/usr/sbin/sendmail","usr/sbin/sendmail","-i",email.c_str(),NULL);
160 _exit(-1);
161 }
162 // parent
163 close(files[0]);
164 FILE *mta = fdopen(files[1],"w");
165 for(headers_t::const_iterator i=mailto_headers.begin();i!=mailto_headers.end();++i) {
166 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
167 }
168 for(headers_t::const_iterator i=config.mailto_headers.begin();i!=config.mailto_headers.end();++i) {
169 if(mailto_headers.find(i->first)!=mailto_headers.end())
170 continue;
171 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
172 }
173 fprintf(mta,
174 "Subject: [%s] %s\n\n"
175 "%s\n"
176 "---\n"
177 "This message was sent automatically by the 'dudki' daemon\n",
178 id.c_str(), event.c_str(),
179 description.c_str() );
180 fclose(mta);
181 int status;
182 waitpid(pid,&status,0);
183 // TODO: check the return code
184}
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 @@
1#ifndef __PROCESS_H
2#define __PROCESS_H
3
4#include <string>
5#include <map>
6using namespace std;
7
8class configuration;
9
10typedef map<string,string> headers_t;
11
12class process {
13 public:
14 string pidfile;
15 string restart_cmd;
16 string notify;
17 string user;
18 string group;
19 string chroot;
20 headers_t mailto_headers;
21
22 int patience;
23
24 process()
25 : patience(0) { }
26
27 void check(const string& id,configuration& config);
28 void launch(const string& id,configuration& config);
29 void do_notify(const string& id,const string& event,const string& description,configuration& config);
30 void notify_mailto(const string& email,const string& id,const string& event,
31 const string& description,configuration& config);
32};
33
34typedef map<string,process> processes_t;
35
36#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 @@
1#include <unistd.h>
2#include <fstream>
3#include <stdexcept>
4using namespace std;
5#include "util.h"
6
7void pid_file::set(const string& f,bool u) {
8 ofstream of(f.c_str(),ios::trunc);
9 if(!of)
10 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to open file for writing pid");
11 of << getpid() << endl;
12 of.close();
13 file_name = f;
14 unlink_pid = u;
15}
16void pid_file::unlink() {
17 if(!unlink_pid)
18 return;
19 ::unlink(file_name.c_str());
20}
21
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 @@
1#ifndef __UTIL_H
2#define __UTIL_H
3
4#include <string>
5using namespace std;
6
7class pid_file {
8 public:
9 string file_name;
10 bool unlink_pid;
11
12 pid_file()
13 : unlink_pid(false) { }
14 ~pid_file() { unlink(); }
15
16 void set(const string& f,bool u=true);
17 void unlink();
18};
19
20#endif /* __UTIL_H */