summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--NEWS.xml3
-rw-r--r--man/dudki.conf.5.in6
-rw-r--r--src/configuration.cc5
-rw-r--r--src/dudki.cc5
-rw-r--r--src/process.cc98
-rw-r--r--src/process.h6
6 files changed, 120 insertions, 3 deletions
diff --git a/NEWS.xml b/NEWS.xml
index cd12d05..cc6085d 100644
--- a/NEWS.xml
+++ b/NEWS.xml
@@ -1,14 +1,15 @@
1<?xml version="1.0" encoding="us-ascii"?> 1<?xml version="1.0" encoding="us-ascii"?>
2<news> 2<news>
3 <version version="0.2" date="July 23rd, 2004"> 3 <version version="0.2" date="July 24th, 2004">
4 <ni>now dudki sends arbitrary signals to the processes being monitored from the command line</ni> 4 <ni>now dudki sends arbitrary signals to the processes being monitored from the command line</ni>
5 <ni>detection of running processes which do not keep pidfiles, using process name</ni>
5 </version> 6 </version>
6 <version version="0.1" date="July 21st, 2004"> 7 <version version="0.1" date="July 21st, 2004">
7 <ni><kbd>initgroups()</kbd> called before executing <kbd>RestartCommand</kbd></ni> 8 <ni><kbd>initgroups()</kbd> called before executing <kbd>RestartCommand</kbd></ni>
8 <ni>more civilized way of restarting on <kbd>SIGHUP</kbd></ni> 9 <ni>more civilized way of restarting on <kbd>SIGHUP</kbd></ni>
9 <ni>minor changes to build process</ni> 10 <ni>minor changes to build process</ni>
10 </version> 11 </version>
11 <version version="0.0" date="July 11th, 2004"> 12 <version version="0.0" date="July 11th, 2004">
12 <ni>Initial release</ni> 13 <ni>Initial release</ni>
13 </version> 14 </version>
14</news> 15</news>
diff --git a/man/dudki.conf.5.in b/man/dudki.conf.5.in
index 23f636d..7d365d3 100644
--- a/man/dudki.conf.5.in
+++ b/man/dudki.conf.5.in
@@ -1,110 +1,114 @@
1.TH dudki.conf 5 "July 9th, 2004" "dudki.conf(5)" "Klever Group (http://www.klever.net/)" 1.TH dudki.conf 5 "July 24th, 2004" "dudki.conf(5)" "Klever Group (http://www.klever.net/)"
2.hla en 2.hla en
3 3
4.SH NAME 4.SH NAME
5 5
6dudki.conf \- The configuration file for the dudki process 6dudki.conf \- The configuration file for the dudki process
7monitoring daemon 7monitoring daemon
8 8
9.SH SYNOPSIS 9.SH SYNOPSIS
10 10
11The dudki.conf file is a runtime configuration file for the dudki 11The dudki.conf file is a runtime configuration file for the dudki
12process monitoring daemon. It contains the information about the process 12process monitoring daemon. It contains the information about the process
13being monitored and the instruction on how to cope with the processes. 13being monitored and the instruction on how to cope with the processes.
14 14
15.SH FILE FORMAT 15.SH FILE FORMAT
16 16
17The file consist of the global configuration and per-process 17The file consist of the global configuration and per-process
18configuration sections. The global configuration controls general 18configuration sections. The global configuration controls general
19dudki behaviour and sets defaults for per-process configuration 19dudki behaviour and sets defaults for per-process configuration
20directives. 20directives.
21 21
22.SH GLOBAL CONFIGURATION 22.SH GLOBAL CONFIGURATION
23 23
24.TP 24.TP
25\fBCheckInterval\fR \fIseconds\fR 25\fBCheckInterval\fR \fIseconds\fR
26Specifies interval in seconds at wich dudki performs checks. 26Specifies interval in seconds at wich dudki performs checks.
27.TP 27.TP
28\fBDaemonize\fR \fIon/off\fR 28\fBDaemonize\fR \fIon/off\fR
29Specifies whether dudki daemon should fork itself into background. 29Specifies whether dudki daemon should fork itself into background.
30Normally, you don't want it to run in foreground. Unless you want to run 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 31it from \fIinit(8)\fR process (I've nevetr tried it) via
32\fIinittab(5)\fR. 32\fIinittab(5)\fR.
33.TP 33.TP
34\fBPidFile\fR \fIfilename\fR 34\fBPidFile\fR \fIfilename\fR
35Specifies where to store dudki's process id (default is 35Specifies where to store dudki's process id (default is
36/var/run/dudki.pid) 36/var/run/dudki.pid)
37.TP 37.TP
38\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR 38\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR
39Sets global defaults for process section's MailtoHeader directive. See 39Sets global defaults for process section's MailtoHeader directive. See
40below. 40below.
41.TP 41.TP
42\fBNotify\fR \fIschema\fR:\fItarget\fR 42\fBNotify\fR \fIschema\fR:\fItarget\fR
43Sets global default for per process notification. See below. 43Sets global default for per process notification. See below.
44 44
45.TP 45.TP
46\fB<Process\fR \fIidentifier\fR\fB>\fR 46\fB<Process\fR \fIidentifier\fR\fB>\fR
47Starts per process configuration section. The process will be referenced 47Starts per process configuration section. The process will be referenced
48using the short descriptive name specified (for example in email 48using the short descriptive name specified (for example in email
49notifications). 49notifications).
50 50
51.SH PER-PROCESS CONFIGURATION 51.SH PER-PROCESS CONFIGURATION
52 52
53Per-process configuration sections specifies the processes to monitor. 53Per-process configuration sections specifies the processes to monitor.
54And parameters pertaining to the process. 54And parameters pertaining to the process.
55 55
56.TP 56.TP
57\fBPidFile\fR \fIfilename\fR 57\fBPidFile\fR \fIfilename\fR
58Specifies the file where to fetch process id of the process being 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 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 60specified by the pid stored in the file signifies the process death and
61triggers restart. 61triggers restart.
62.TP 62.TP
63\fBProcessName\fR \fIprocess name\fR
64Specifies the name of the process. The alternative way to find process if it
65doesn't keep pid in the file. Similar to \fBpidof\fR(1).
66.TP
63\fBRestartCommand\fR \fIcommand\fR 67\fBRestartCommand\fR \fIcommand\fR
64Specifies the command to run in order to restart the process. 68Specifies the command to run in order to restart the process.
65.TP 69.TP
66\fBUser\fR \fIuser\fR 70\fBUser\fR \fIuser\fR
67Specifies the unix user to change to before executing the command 71Specifies the unix user to change to before executing the command
68specified by \fBRestartCommand\fR. 72specified by \fBRestartCommand\fR.
69.TP 73.TP
70\fBGroup\fR \fIgroup\fR 74\fBGroup\fR \fIgroup\fR
71Specifies the unix group to change to before executing the command 75Specifies the unix group to change to before executing the command
72specified by \fBRestartCommand\fR. 76specified by \fBRestartCommand\fR.
73.TP 77.TP
74\fBChroot\fR \fIpath\fR 78\fBChroot\fR \fIpath\fR
75Specifies the directory to set filesystem root to before executing the 79Specifies the directory to set filesystem root to before executing the
76command specified by by \fBRestartCommand\fR. 80command specified by by \fBRestartCommand\fR.
77.TP 81.TP
78\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR 82\fBMailtoHeader\fR \fIheader\fR \fIcontent\fR
79Specifies extra headers to add to mailto: notifications sent by the 83Specifies extra headers to add to mailto: notifications sent by the
80dudki daemon. Headers specified in per-process section override the 84dudki daemon. Headers specified in per-process section override the
81ones specified globally.A 85ones specified globally.A
82.TP 86.TP
83\fBNotify\fR \fIschema\fR:\fItarget\fR 87\fBNotify\fR \fIschema\fR:\fItarget\fR
84Specifies the contact to be notified whenever something notable happens 88Specifies the contact to be notified whenever something notable happens
85to the process. The only schema currently supported is 'mailto:'. 89to the process. The only schema currently supported is 'mailto:'.
86.TP 90.TP
87\fB</Process>\fR 91\fB</Process>\fR
88Signifies the end of process section. 92Signifies the end of process section.
89 93
90.SH EXAMPLE 94.SH EXAMPLE
91 95
92.br 96.br
93CheckInterval 60 97CheckInterval 60
94.br 98.br
95PidFile /var/run/dudki.pid 99PidFile /var/run/dudki.pid
96.br 100.br
97Daemonize on 101Daemonize on
98.br 102.br
99MailtoHeader From "Dudki <root@klever.net>" 103MailtoHeader From "Dudki <root@klever.net>"
100.br 104.br
101MailtoHeader Reply-To devnull@klever.net 105MailtoHeader Reply-To devnull@klever.net
102.br 106.br
103Notify mailto:hacker@klever.net 107Notify mailto:hacker@klever.net
104.br 108.br
105 109
106.br 110.br
107<Process apache> 111<Process apache>
108.br 112.br
109 PidFile /var/run/httpd.pid 113 PidFile /var/run/httpd.pid
110.br 114.br
diff --git a/src/configuration.cc b/src/configuration.cc
index eb010c1..edc8c04 100644
--- a/src/configuration.cc
+++ b/src/configuration.cc
@@ -47,103 +47,108 @@ static DOTCONF_CB(dco_pid_file) { dc_context *dcc = (dc_context*)ctx;
47 } 47 }
48 return NULL; 48 return NULL;
49} 49}
50static DOTCONF_CB(dco_mailto_header) { dc_context *dcc = (dc_context*)ctx; 50static DOTCONF_CB(dco_mailto_header) { dc_context *dcc = (dc_context*)ctx;
51 if(cmd->arg_count!=2) 51 if(cmd->arg_count!=2)
52 return "Invalid number of arguments"; 52 return "Invalid number of arguments";
53 string h = cmd->data.list[0]; 53 string h = cmd->data.list[0];
54 string v = cmd->data.list[1]; 54 string v = cmd->data.list[1];
55 switch(dcc->ctx) { 55 switch(dcc->ctx) {
56 case DCC_ROOT: 56 case DCC_ROOT:
57 dcc->cf->mailto_headers[h] = v; 57 dcc->cf->mailto_headers[h] = v;
58 break; 58 break;
59 case DCC_PROCESS: 59 case DCC_PROCESS:
60 dcc->ps->mailto_headers[h] = v; 60 dcc->ps->mailto_headers[h] = v;
61 break; 61 break;
62 default: 62 default:
63 return "Unexpected MailtoHeader"; 63 return "Unexpected MailtoHeader";
64 } 64 }
65 return NULL; 65 return NULL;
66} 66}
67static DOTCONF_CB(dco_notify) { dc_context *dcc = (dc_context*)ctx; 67static DOTCONF_CB(dco_notify) { dc_context *dcc = (dc_context*)ctx;
68 switch(dcc->ctx) { 68 switch(dcc->ctx) {
69 case DCC_ROOT: 69 case DCC_ROOT:
70 dcc->cf->notify = cmd->data.str; 70 dcc->cf->notify = cmd->data.str;
71 break; 71 break;
72 case DCC_PROCESS: 72 case DCC_PROCESS:
73 dcc->ps->notify = cmd->data.str; 73 dcc->ps->notify = cmd->data.str;
74 break; 74 break;
75 default: 75 default:
76 return "Unexpected Notify"; 76 return "Unexpected Notify";
77 } 77 }
78 return NULL; 78 return NULL;
79} 79}
80 80
81static DOTCONF_CB(dco_process) { dc_context *dcc = (dc_context*)ctx; 81static DOTCONF_CB(dco_process) { dc_context *dcc = (dc_context*)ctx;
82 string id = cmd->data.str; 82 string id = cmd->data.str;
83 if(id[id.length()-1]=='>') 83 if(id[id.length()-1]=='>')
84 id.erase(id.length()-1); 84 id.erase(id.length()-1);
85 dcc->ps = &(dcc->cf->processes[id]); 85 dcc->ps = &(dcc->cf->processes[id]);
86 dcc->ctx = DCC_PROCESS; 86 dcc->ctx = DCC_PROCESS;
87 return NULL; 87 return NULL;
88} 88}
89static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx; 89static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx;
90 dcc->ps = NULL; 90 dcc->ps = NULL;
91 dcc->ctx = DCC_ROOT; 91 dcc->ctx = DCC_ROOT;
92 return NULL; 92 return NULL;
93} 93}
94 94
95static DOTCONF_CB(dco_process_name) { dc_context *dcc = (dc_context*)ctx;
96 dcc->ps->process_name = cmd->data.str;
97 return NULL;
98}
95static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx; 99static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx;
96 dcc->ps->restart_cmd = cmd->data.str; 100 dcc->ps->restart_cmd = cmd->data.str;
97 return NULL; 101 return NULL;
98} 102}
99static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx; 103static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx;
100 dcc->ps->user = cmd->data.str; 104 dcc->ps->user = cmd->data.str;
101 return NULL; 105 return NULL;
102} 106}
103static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx; 107static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx;
104 dcc->ps->group = cmd->data.str; 108 dcc->ps->group = cmd->data.str;
105 return NULL; 109 return NULL;
106} 110}
107static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx; 111static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx;
108 dcc->ps->chroot = cmd->data.str; 112 dcc->ps->chroot = cmd->data.str;
109 return NULL; 113 return NULL;
110} 114}
111 115
112static const configoption_t dc_options[] = { 116static const configoption_t dc_options[] = {
113 { "CheckInterval", ARG_INT, dco_check_interval, NULL, DCC_ROOT }, 117 { "CheckInterval", ARG_INT, dco_check_interval, NULL, DCC_ROOT },
114 { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT }, 118 { "Daemonize", ARG_TOGGLE, dco_daemonize, NULL, DCC_ROOT },
115 { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT|DCC_PROCESS }, 119 { "PidFile", ARG_STR, dco_pid_file, NULL, DCC_ROOT|DCC_PROCESS },
116 { "MailtoHeader", ARG_STR, dco_mailto_header, NULL, DCC_ROOT|DCC_PROCESS }, 120 { "MailtoHeader", ARG_STR, dco_mailto_header, NULL, DCC_ROOT|DCC_PROCESS },
117 { "Notify", ARG_STR, dco_notify, NULL, DCC_ROOT|DCC_PROCESS }, 121 { "Notify", ARG_STR, dco_notify, NULL, DCC_ROOT|DCC_PROCESS },
118 { "<Process", ARG_STR, dco_process, NULL, DCC_ROOT }, 122 { "<Process", ARG_STR, dco_process, NULL, DCC_ROOT },
123 { "ProcessName", ARG_STR, dco_process_name, NULL, DCC_PROCESS },
119 { "RestartCommand", ARG_STR, dco_restart_command, NULL, DCC_PROCESS }, 124 { "RestartCommand", ARG_STR, dco_restart_command, NULL, DCC_PROCESS },
120 { "User", ARG_STR, dco_user, NULL, DCC_PROCESS }, 125 { "User", ARG_STR, dco_user, NULL, DCC_PROCESS },
121 { "Group", ARG_STR, dco_group, NULL, DCC_PROCESS }, 126 { "Group", ARG_STR, dco_group, NULL, DCC_PROCESS },
122 { "Chroot", ARG_STR, dco_chroot, NULL, DCC_PROCESS }, 127 { "Chroot", ARG_STR, dco_chroot, NULL, DCC_PROCESS },
123 { "</Process>", ARG_NONE, dco__process, NULL, DCC_PROCESS }, 128 { "</Process>", ARG_NONE, dco__process, NULL, DCC_PROCESS },
124 LAST_OPTION 129 LAST_OPTION
125}; 130};
126 131
127static const char *dc_context_checker(command_t *cmd,unsigned long mask) { 132static const char *dc_context_checker(command_t *cmd,unsigned long mask) {
128 dc_context *dcc = (dc_context*)cmd->context; 133 dc_context *dcc = (dc_context*)cmd->context;
129 if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) ) 134 if( (mask==CTX_ALL) || ((mask&dcc->ctx)==dcc->ctx) )
130 return NULL; 135 return NULL;
131 return "misplaced option"; 136 return "misplaced option";
132} 137}
133static FUNC_ERRORHANDLER(dc_error_handler) { 138static FUNC_ERRORHANDLER(dc_error_handler) {
134 throw runtime_error(string("error parsing config file: ")+msg); 139 throw runtime_error(string("error parsing config file: ")+msg);
135} 140}
136 141
137void configuration::parse(const string& cfile) { 142void configuration::parse(const string& cfile) {
138 struct dc_context dcc; 143 struct dc_context dcc;
139 dcc.cf = this; 144 dcc.cf = this;
140 dcc.ctx = DCC_ROOT; 145 dcc.ctx = DCC_ROOT;
141 configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE); 146 configfile_t *cf = dotconf_create((char*)cfile.c_str(),dc_options,(context_t*)&dcc,CASE_INSENSITIVE);
142 if(!cf) 147 if(!cf)
143 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_create()"); 148 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_create()");
144 cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler; 149 cf->errorhandler = (dotconf_errorhandler_t) dc_error_handler;
145 cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker; 150 cf->contextchecker = (dotconf_contextchecker_t) dc_context_checker;
146 if(!dotconf_command_loop(cf)) 151 if(!dotconf_command_loop(cf))
147 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_command_loop()"); 152 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to dotconf_command_loop()");
148 dotconf_cleanup(cf); 153 dotconf_cleanup(cf);
149} 154}
diff --git a/src/dudki.cc b/src/dudki.cc
index 9562079..c966695 100644
--- a/src/dudki.cc
+++ b/src/dudki.cc
@@ -1,87 +1,89 @@
1#include <unistd.h> 1#include <unistd.h>
2#include <signal.h> 2#include <signal.h>
3#include <syslog.h> 3#include <syslog.h>
4#include <errno.h> 4#include <errno.h>
5#include <iostream> 5#include <iostream>
6#include <fstream> 6#include <fstream>
7#include <stdexcept> 7#include <stdexcept>
8using namespace std; 8using namespace std;
9#include "configuration.h" 9#include "configuration.h"
10#include "util.h" 10#include "util.h"
11 11
12#include "config.h" 12#include "config.h"
13#ifdef HAVE_GETOPT_H 13#ifdef HAVE_GETOPT_H
14# include <getopt.h> 14# include <getopt.h>
15#endif 15#endif
16 16
17#ifndef DEFAULT_CONF_FILE 17#ifndef DEFAULT_CONF_FILE
18# define DEFAULT_CONF_FILE "/etc/dudki.conf" 18# define DEFAULT_CONF_FILE "/etc/dudki.conf"
19#endif 19#endif
20 20
21#define PHEADER PACKAGE " Version " VERSION 21#define PHEADER PACKAGE " Version " VERSION
22#define PCOPY "Copyright (c) 2004 Klever Group" 22#define PCOPY "Copyright (c) 2004 Klever Group"
23 23
24bool finishing = false; 24bool finishing = false;
25bool restarting = false; 25bool restarting = false;
26static char **_argv = NULL; 26static char **_argv = NULL;
27 27
28static void lethal_signal_handler(int signum) { 28static void lethal_signal_handler(int signum) {
29 syslog(LOG_NOTICE,"Lethal signal received. Terminating."); 29 syslog(LOG_NOTICE,"Lethal signal received. Terminating.");
30 finishing = true; 30 finishing = true;
31} 31}
32static void sighup_handler(int signum) { 32static void sighup_handler(int signum) {
33 syslog(LOG_NOTICE,"SUGHUP received, reloading."); 33 syslog(LOG_NOTICE,"SUGHUP received, reloading.");
34 restarting = finishing = true; 34 restarting = finishing = true;
35} 35}
36 36
37void check_herd(configuration& config) { 37void check_herd(configuration& config) {
38 process::prepare_herd();
38 for(processes_t::iterator i=config.processes.begin();i!=config.processes.end();++i) 39 for(processes_t::iterator i=config.processes.begin();i!=config.processes.end();++i)
39 i->second.check(i->first,config); 40 i->second.check(i->first,config);
41 process::unprepare_herd();
40} 42}
41 43
42void signal_self(const configuration& config,int signum) { 44void signal_self(const configuration& config,int signum) {
43 ifstream pids(config.pidfile.c_str(),ios::in); 45 ifstream pids(config.pidfile.c_str(),ios::in);
44 if(!pids) 46 if(!pids)
45 throw runtime_error("Can't detect running instance"); 47 throw runtime_error("Can't detect running instance");
46 pid_t pid = 0; 48 pid_t pid = 0;
47 pids >> pid; 49 pids >> pid;
48 if(!pid) 50 if(!pid)
49 throw runtime_error("Can't detect running instance"); 51 throw runtime_error("Can't detect running instance");
50 if(pid==getpid()) 52 if(pid==getpid())
51 throw 0; 53 throw 0;
52 if(kill(pid,signum)) 54 if(kill(pid,signum))
53 throw runtime_error("Failed to signal running instance"); 55 throw runtime_error("Failed to signal running instance");
54} 56}
55 57
56int main(int argc,char **argv) { 58int main(int argc,char **argv) {
57 try { 59 try {
58 _argv = new char*[argc+1]; 60 _argv = new char*[argc+1];
59 if(!_argv) 61 if(!_argv)
60 throw runtime_error("memory allocation problem at the very start"); 62 throw runtime_error("memory allocation problem at the very start");
61 memmove(_argv,argv,sizeof(*_argv)*(argc+1)); 63 memmove(_argv,argv,sizeof(*_argv)*(argc+1));
62 string config_file = DEFAULT_CONF_FILE; 64 string config_file = DEFAULT_CONF_FILE;
63 enum { 65 enum {
64 op_default, 66 op_default,
65 op_work, 67 op_work,
66 op_signal, 68 op_signal,
67 op_ensure, 69 op_ensure,
68 op_test 70 op_test
69 } op = op_default; 71 } op = op_default;
70 int op_signum = 0; 72 int op_signum = 0;
71 while(true) { 73 while(true) {
72 #defineSHORTOPTSTRING "f:hVLrkcets:" 74 #defineSHORTOPTSTRING "f:hVLrkcets:"
73#ifdef HAVE_GETOPT_LONG 75#ifdef HAVE_GETOPT_LONG
74 static struct option opts[] = { 76 static struct option opts[] = {
75 { "help", no_argument, 0, 'h' }, 77 { "help", no_argument, 0, 'h' },
76 { "usage", no_argument, 0, 'h' }, 78 { "usage", no_argument, 0, 'h' },
77 { "version", no_argument, 0, 'V' }, 79 { "version", no_argument, 0, 'V' },
78 { "license", no_argument, 0, 'L' }, 80 { "license", no_argument, 0, 'L' },
79 { "config", required_argument, 0, 'f' }, 81 { "config", required_argument, 0, 'f' },
80 { "kill", no_argument, 0, 'k' }, 82 { "kill", no_argument, 0, 'k' },
81 { "reload", no_argument, 0, 'r' }, 83 { "reload", no_argument, 0, 'r' },
82 { "signal", required_argument, 0, 's' }, 84 { "signal", required_argument, 0, 's' },
83 { "check", no_argument, 0, 'c' }, 85 { "check", no_argument, 0, 'c' },
84 { "ensure", no_argument, 0, 'e' }, 86 { "ensure", no_argument, 0, 'e' },
85 { "test", no_argument, 0, 't' }, 87 { "test", no_argument, 0, 't' },
86 { NULL, 0, 0, 0 } 88 { NULL, 0, 0, 0 }
87 }; 89 };
@@ -175,97 +177,100 @@ int main(int argc,char **argv) {
175 if(op!=op_default) { 177 if(op!=op_default) {
176 cerr << "Can't obey two or more orders at once" << endl; 178 cerr << "Can't obey two or more orders at once" << endl;
177 exit(1); 179 exit(1);
178 } 180 }
179 op = op_test; 181 op = op_test;
180 break; 182 break;
181 case 's': 183 case 's':
182 if(op!=op_default) { 184 if(op!=op_default) {
183 cerr << "Can't obey two or more orders at once" << endl; 185 cerr << "Can't obey two or more orders at once" << endl;
184 exit(1); 186 exit(1);
185 } 187 }
186 op = op_signal; 188 op = op_signal;
187 errno = 0; 189 errno = 0;
188 op_signum = strtol(optarg,NULL,0); 190 op_signum = strtol(optarg,NULL,0);
189 if(errno) { 191 if(errno) {
190 cerr << "Can't obtain the signal value" << endl; 192 cerr << "Can't obtain the signal value" << endl;
191 exit(1); 193 exit(1);
192 } 194 }
193 break; 195 break;
194 default: 196 default:
195 cerr << "Huh??" << endl; 197 cerr << "Huh??" << endl;
196 exit(1); 198 exit(1);
197 break; 199 break;
198 } 200 }
199 } 201 }
200 const char *sid = *argv; 202 const char *sid = *argv;
201 const char *t; 203 const char *t;
202 while(t = index(sid,'/')) { 204 while(t = index(sid,'/')) {
203 sid = t; sid++; 205 sid = t; sid++;
204 } 206 }
205 openlog(sid,LOG_CONS|LOG_PERROR|LOG_PID,LOG_DAEMON); 207 openlog(sid,LOG_CONS|LOG_PERROR|LOG_PID,LOG_DAEMON);
206 configuration config; 208 configuration config;
207 config.parse(config_file); 209 config.parse(config_file);
208 switch(op) { 210 switch(op) {
209 case op_test: 211 case op_test:
210 cerr << "Configuration OK" << endl; 212 cerr << "Configuration OK" << endl;
211 break; 213 break;
212 case op_signal: 214 case op_signal:
213 try { 215 try {
214 if(optind>=argc) { 216 if(optind>=argc) {
215 signal_self(config,op_signum); 217 signal_self(config,op_signum);
216 }else{ 218 }else{
217 int failures = 0; 219 int failures = 0;
218 for(int narg=optind;narg<argc;narg++) { 220 for(int narg=optind;narg<argc;narg++) {
219 try { 221 try {
220 processes_t::const_iterator i = config.processes.find(argv[narg]); 222 processes_t::const_iterator i = config.processes.find(argv[narg]);
221 if(i==config.processes.end()) 223 if(i==config.processes.end())
222 throw runtime_error("no such process configured"); 224 throw runtime_error("no such process configured");
225 if(op_signum)
223 i->second.signal(op_signum); 226 i->second.signal(op_signum);
227 else
228 i->second.check();
224 }catch(exception& e) { 229 }catch(exception& e) {
225 cerr << "dudki(" << argv[narg] << "): " << e.what() << endl; 230 cerr << "dudki(" << argv[narg] << "): " << e.what() << endl;
226 failures++; 231 failures++;
227 } 232 }
228 } 233 }
229 if(failures) 234 if(failures)
230 throw runtime_error("not all processes have been successfully signaled"); 235 throw runtime_error("not all processes have been successfully signaled");
231 } 236 }
232 if(!op_signum) 237 if(!op_signum)
233 exit(0); 238 exit(0);
234 }catch(exception& e) { 239 }catch(exception& e) {
235 if(!op_signum) 240 if(!op_signum)
236 exit(1); 241 exit(1);
237 } 242 }
238 case op_ensure: 243 case op_ensure:
239 try { 244 try {
240 signal_self(config,0); 245 signal_self(config,0);
241 break; 246 break;
242 }catch(exception& e) { 247 }catch(exception& e) {
243 syslog(LOG_NOTICE,"The dudki process is down, taking its place"); 248 syslog(LOG_NOTICE,"The dudki process is down, taking its place");
244 config.daemonize = true; 249 config.daemonize = true;
245 }catch(int zero) { 250 }catch(int zero) {
246 // we throw zero in case we're ensuring that this very process is running. 251 // we throw zero in case we're ensuring that this very process is running.
247 // we don't have to daemonize if we're daemonic. 252 // we don't have to daemonize if we're daemonic.
248 config.daemonize = false; 253 config.daemonize = false;
249 } 254 }
250 case op_default: 255 case op_default:
251 case op_work: 256 case op_work:
252 { 257 {
253 if(config.daemonize) { 258 if(config.daemonize) {
254 pid_t pf = fork(); 259 pid_t pf = fork();
255 if(pf<0) 260 if(pf<0)
256 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()"); 261 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
257 if(pf) { 262 if(pf) {
258 _exit(0); 263 _exit(0);
259 } 264 }
260 } 265 }
261 pid_file pidfile; 266 pid_file pidfile;
262 pidfile.set(config.pidfile); 267 pidfile.set(config.pidfile);
263 signal(SIGINT,lethal_signal_handler); 268 signal(SIGINT,lethal_signal_handler);
264 signal(SIGABRT,lethal_signal_handler); 269 signal(SIGABRT,lethal_signal_handler);
265 signal(SIGTERM,lethal_signal_handler); 270 signal(SIGTERM,lethal_signal_handler);
266 signal(SIGHUP,sighup_handler); 271 signal(SIGHUP,sighup_handler);
267 sigset_t sset; 272 sigset_t sset;
268 sigemptyset(&sset); 273 sigemptyset(&sset);
269 sigaddset(&sset,SIGINT); sigaddset(&sset,SIGABRT); 274 sigaddset(&sset,SIGINT); sigaddset(&sset,SIGABRT);
270 sigaddset(&sset,SIGTERM); sigaddset(&sset,SIGHUP); 275 sigaddset(&sset,SIGTERM); sigaddset(&sset,SIGHUP);
271 sigprocmask(SIG_UNBLOCK,&sset,NULL); 276 sigprocmask(SIG_UNBLOCK,&sset,NULL);
diff --git a/src/process.cc b/src/process.cc
index 1ffac9f..8a5b5d2 100644
--- a/src/process.cc
+++ b/src/process.cc
@@ -1,67 +1,83 @@
1#include <stdio.h> 1#include <stdio.h>
2#include <sys/types.h> 2#include <sys/types.h>
3#include <unistd.h> 3#include <unistd.h>
4#include <signal.h> 4#include <signal.h>
5#include <pwd.h> 5#include <pwd.h>
6#include <grp.h> 6#include <grp.h>
7#include <dirent.h>
7#include <sys/wait.h> 8#include <sys/wait.h>
8#include <syslog.h> 9#include <syslog.h>
9#include <errno.h> 10#include <errno.h>
10#include <iostream> 11#include <iostream>
11#include <fstream> 12#include <fstream>
13#include <sstream>
12#include <stdexcept> 14#include <stdexcept>
15#include <string>
16#include <vector>
13using namespace std; 17using namespace std;
14#include "process.h" 18#include "process.h"
15#include "configuration.h" 19#include "configuration.h"
16 20
21static multimap<string,pid_t> procpids;
22
23void process::check() const {
24 if(!pidfile.empty()) {
25 signal(0);
26 }else if(!process_name.empty()) {
27 if(procpids.empty())
28 gather_proc_info();
29 if(procpids.find(process_name)==procpids.end())
30 throw runtime_error("no such process");
31 } // XXX: or else?
32}
17void process::check(const string& id,configuration& config) { 33void process::check(const string& id,configuration& config) {
18 try { 34 try {
19 signal(0); 35 check();
20 patience = 0; 36 patience = 0;
21 }catch(exception& e) { 37 }catch(exception& e) {
22 if(patience>60) { // TODO: configurable 38 if(patience>60) { // TODO: configurable
23 patience = 0; 39 patience = 0;
24 }else{ 40 }else{
25 if(patience<10) { // TODO: configurable 41 if(patience<10) { // TODO: configurable
26 syslog(LOG_NOTICE,"The process '%s' is down, trying to launch.",id.c_str()); 42 syslog(LOG_NOTICE,"The process '%s' is down, trying to launch.",id.c_str());
27 do_notify(id,"Starting up", 43 do_notify(id,"Starting up",
28 "The named process seems to be down. Dudki will try\n" 44 "The named process seems to be down. Dudki will try\n"
29 "to revive it by running the specified command.\n", 45 "to revive it by running the specified command.\n",
30 config); 46 config);
31 try { 47 try {
32 launch(id,config); 48 launch(id,config);
33 }catch(exception& e) { 49 }catch(exception& e) {
34 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what()); 50 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
35 } 51 }
36 }else if(patience==10){ // TODO: configurable like the above 52 }else if(patience==10){ // TODO: configurable like the above
37 syslog(LOG_NOTICE,"Giving up on process '%s' for a while",id.c_str()); 53 syslog(LOG_NOTICE,"Giving up on process '%s' for a while",id.c_str());
38 do_notify(id,"Giving up", 54 do_notify(id,"Giving up",
39 "After a number of attempts to relaunch the named process\n" 55 "After a number of attempts to relaunch the named process\n"
40 "It still seems to be down. Dudki is giving up attempts\n" 56 "It still seems to be down. Dudki is giving up attempts\n"
41 "to revive the process for a while.\n", 57 "to revive the process for a while.\n",
42 config); 58 config);
43 } 59 }
44 patience++; 60 patience++;
45 } 61 }
46 } 62 }
47} 63}
48 64
49void process::launch(const string& id,configuration& config) { 65void process::launch(const string& id,configuration& config) {
50 uid_t uid = (uid_t)-1; 66 uid_t uid = (uid_t)-1;
51 gid_t gid = (gid_t)-1; 67 gid_t gid = (gid_t)-1;
52 if(!user.empty()) { 68 if(!user.empty()) {
53 struct passwd *ptmp = getpwnam(user.c_str()); 69 struct passwd *ptmp = getpwnam(user.c_str());
54 if(ptmp) { 70 if(ptmp) {
55 uid = ptmp->pw_uid; 71 uid = ptmp->pw_uid;
56 gid = ptmp->pw_gid; 72 gid = ptmp->pw_gid;
57 }else{ 73 }else{
58 errno=0; 74 errno=0;
59 uid = strtol(user.c_str(),NULL,0); 75 uid = strtol(user.c_str(),NULL,0);
60 if(errno) 76 if(errno)
61 throw runtime_error("Failed to resolve User value to uid"); 77 throw runtime_error("Failed to resolve User value to uid");
62 } 78 }
63 } 79 }
64 if(!group.empty()) { 80 if(!group.empty()) {
65 struct group *gtmp = getgrnam(group.c_str()); 81 struct group *gtmp = getgrnam(group.c_str());
66 if(gtmp) { 82 if(gtmp) {
67 gid = gtmp->gr_gid; 83 gid = gtmp->gr_gid;
@@ -131,59 +147,139 @@ void process::do_notify(const string& id,const string& event,const string& descr
131 } 147 }
132} 148}
133 149
134void process::notify_mailto(const string& email,const string& id,const string& event,const string& description,configuration& config) { 150void process::notify_mailto(const string& email,const string& id,const string& event,const string& description,configuration& config) {
135 int files[2]; 151 int files[2];
136 if(pipe(files)) 152 if(pipe(files))
137 throw runtime_error("Failed to pipe()"); 153 throw runtime_error("Failed to pipe()");
138 pid_t pid = vfork(); 154 pid_t pid = vfork();
139 if(pid==-1) { 155 if(pid==-1) {
140 close(files[0]); 156 close(files[0]);
141 close(files[1]); 157 close(files[1]);
142 throw runtime_error("Failed to vfork()"); 158 throw runtime_error("Failed to vfork()");
143 } 159 }
144 if(!pid) { 160 if(!pid) {
145 // child 161 // child
146 if(dup2(files[0],0)!=0) 162 if(dup2(files[0],0)!=0)
147 _exit(-1); 163 _exit(-1);
148 close(1); 164 close(1);
149 close(files[0]); 165 close(files[0]);
150 close(files[1]); 166 close(files[1]);
151 execl("/usr/sbin/sendmail","usr/sbin/sendmail","-i",email.c_str(),NULL); 167 execl("/usr/sbin/sendmail","usr/sbin/sendmail","-i",email.c_str(),NULL);
152 _exit(-1); 168 _exit(-1);
153 } 169 }
154 // parent 170 // parent
155 close(files[0]); 171 close(files[0]);
156 FILE *mta = fdopen(files[1],"w"); 172 FILE *mta = fdopen(files[1],"w");
157 for(headers_t::const_iterator i=mailto_headers.begin();i!=mailto_headers.end();++i) { 173 for(headers_t::const_iterator i=mailto_headers.begin();i!=mailto_headers.end();++i) {
158 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str()); 174 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
159 } 175 }
160 for(headers_t::const_iterator i=config.mailto_headers.begin();i!=config.mailto_headers.end();++i) { 176 for(headers_t::const_iterator i=config.mailto_headers.begin();i!=config.mailto_headers.end();++i) {
161 if(mailto_headers.find(i->first)!=mailto_headers.end()) 177 if(mailto_headers.find(i->first)!=mailto_headers.end())
162 continue; 178 continue;
163 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str()); 179 fprintf(mta,"%s: %s\n",i->first.c_str(),i->second.c_str());
164 } 180 }
165 fprintf(mta, 181 fprintf(mta,
166 "Subject: [%s] %s\n\n" 182 "Subject: [%s] %s\n\n"
167 "%s\n" 183 "%s\n"
168 "---\n" 184 "---\n"
169 "This message was sent automatically by the 'dudki' daemon\n", 185 "This message was sent automatically by the 'dudki' daemon\n",
170 id.c_str(), event.c_str(), 186 id.c_str(), event.c_str(),
171 description.c_str() ); 187 description.c_str() );
172 fclose(mta); 188 fclose(mta);
173 int status; 189 int status;
174 waitpid(pid,&status,0); 190 waitpid(pid,&status,0);
175 // TODO: check the return code 191 // TODO: check the return code
176} 192}
177 193
178void process::signal(int signum) const { 194void process::signal(int signum) const {
195 if(!pidfile.empty()) {
179 ifstream pids(pidfile.c_str(),ios::in); 196 ifstream pids(pidfile.c_str(),ios::in);
180 if(!pids) 197 if(!pids)
181 throw runtime_error("no pidfile found"); 198 throw runtime_error("no pidfile found");
182 pid_t pid = 0; 199 pid_t pid = 0;
183 pids >> pid; 200 pids >> pid;
184 pids.close(); 201 pids.close();
185 if(!pid) 202 if(!pid)
186 throw runtime_error("no pid in pidfile"); 203 throw runtime_error("no pid in pidfile");
187 if(kill(pid,signum)) 204 if(kill(pid,signum))
188 throw runtime_error("failed to signal process"); 205 throw runtime_error("failed to signal process");
206 }else if(!process_name.empty()) {
207 if(procpids.empty())
208 gather_proc_info();
209 pair<multimap<string,pid_t>::const_iterator,multimap<string,pid_t>::const_iterator> range = procpids.equal_range(process_name);
210 int count = 0;
211 for(multimap<string,pid_t>::const_iterator i=range.first;i!=range.second;++i) {
212 pid_t pid = i->second;
213 if(kill(i->second,signum))
214 throw runtime_error("failed to signal process");
215 count++;
216 }
217 if(!count)
218 throw runtime_error("no running instance detected");
219 }else
220 throw runtime_error("nothing is known about the process");
221}
222
223void process::prepare_herd() {
224 procpids.clear();
225}
226void process::unprepare_herd() {
227 procpids.clear();
228}
229void process::gather_proc_info() {
230 vector<pid_t> allpids;
231 DIR *pd = opendir("/proc");
232 if(!pd)
233 throw runtime_error("failed to open /proc");
234 struct dirent *pde;
235 pid_t selfpid = getpid();
236 while(pde=readdir(pd)) {
237 errno=0;
238 pid_t pid = atoi(pde->d_name);
239 if((!pid) || pid==selfpid)
240 continue;
241 allpids.push_back(pid);
242 }
243 closedir(pd);
244 char s[256];
245 procpids.clear();
246 for(vector<pid_t>::const_iterator i=allpids.begin();i!=allpids.end();++i) {
247 int r = snprintf(s,sizeof(s),"/proc/%d/stat",*i);
248 if(r>=sizeof(s) || r<1)
249 continue;
250 string cmd;
251 ifstream ss(s,ios::in);
252 if(!ss)
253 continue;
254 getline(ss,cmd);
255 string::size_type op = cmd.find('(');
256 if(op==string::npos)
257 continue;
258 cmd.erase(0,op+1);
259 string::size_type cp = cmd.find(')');
260 if(cp==string::npos)
261 continue;
262 cmd.erase(cp);
263 r = snprintf(s,sizeof(s),"/proc/%d/cmdline",*i);
264 if(r>=sizeof(s) || r<1)
265 continue;
266 ifstream cs(s,ios::binary);
267 if(!cs)
268 continue;
269 string command;
270 while(cs) {
271 string cl;
272 getline(cs,cl,(char)0);
273 string::size_type lsl = cl.rfind('/');
274 if(lsl!=string::npos)
275 cl.erase(0,lsl+1);
276 if(cl.substr(0,cmd.length())==cmd) {
277 command = cl;
278 break;
279 }
280 }
281 procpids.insert(pair<string,pid_t>(cmd,*i));
282 if((!command.empty()) && cmd!=command)
283 procpids.insert(pair<string,pid_t>(command,*i));
284 }
189} 285}
diff --git a/src/process.h b/src/process.h
index 27ee049..90b12d9 100644
--- a/src/process.h
+++ b/src/process.h
@@ -1,38 +1,44 @@
1#ifndef __PROCESS_H 1#ifndef __PROCESS_H
2#define __PROCESS_H 2#define __PROCESS_H
3 3
4#include <string> 4#include <string>
5#include <map> 5#include <map>
6using namespace std; 6using namespace std;
7 7
8class configuration; 8class configuration;
9 9
10typedef map<string,string> headers_t; 10typedef map<string,string> headers_t;
11 11
12class process { 12class process {
13 public: 13 public:
14 string pidfile; 14 string pidfile;
15 string process_name;
15 string restart_cmd; 16 string restart_cmd;
16 string notify; 17 string notify;
17 string user; 18 string user;
18 string group; 19 string group;
19 string chroot; 20 string chroot;
20 headers_t mailto_headers; 21 headers_t mailto_headers;
21 22
22 int patience; 23 int patience;
23 24
24 process() 25 process()
25 : patience(0) { } 26 : patience(0) { }
26 27
27 void check(const string& id,configuration& config); 28 void check(const string& id,configuration& config);
28 void launch(const string& id,configuration& config); 29 void launch(const string& id,configuration& config);
29 void do_notify(const string& id,const string& event,const string& description,configuration& config); 30 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 void notify_mailto(const string& email,const string& id,const string& event,
31 const string& description,configuration& config); 32 const string& description,configuration& config);
32 33
33 void signal(int signum) const; 34 void signal(int signum) const;
35 void check() const;
36
37 static void prepare_herd();
38 static void gather_proc_info();
39 static void unprepare_herd();
34}; 40};
35 41
36typedef map<string,process> processes_t; 42typedef map<string,process> processes_t;
37 43
38#endif /* __PROCESS_H */ 44#endif /* __PROCESS_H */