author | Michael Krelin <hacker@klever.net> | 2004-07-09 16:48:52 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2004-07-09 16:48:52 (UTC) |
commit | 9148dac885c0325636c2d33715ba248371706d0d (patch) (unidiff) | |
tree | fd95d60f6fd051c3ad46a966882e2be48440452f /src | |
download | dudki-9148dac885c0325636c2d33715ba248371706d0d.zip dudki-9148dac885c0325636c2d33715ba248371706d0d.tar.gz dudki-9148dac885c0325636c2d33715ba248371706d0d.tar.bz2 |
dudki: initial import into svn repository
-rw-r--r-- | src/.gitignore | 5 | ||||
-rw-r--r-- | src/Makefile.am | 18 | ||||
-rw-r--r-- | src/configuration.cc | 149 | ||||
-rw-r--r-- | src/configuration.h | 23 | ||||
-rw-r--r-- | src/dudki.cc | 244 | ||||
-rw-r--r-- | src/process.cc | 184 | ||||
-rw-r--r-- | src/process.h | 36 | ||||
-rw-r--r-- | src/util.cc | 21 | ||||
-rw-r--r-- | src/util.h | 20 |
9 files changed, 700 insertions, 0 deletions
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 | ||
2 | Makefile | ||
3 | Makefile.in | ||
4 | COPYING.cc | ||
5 | 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 @@ | |||
1 | sbin_PROGRAMS = dudki | ||
2 | |||
3 | INCLUDES = ${DOTCONF_CFLAGS} | ||
4 | LIBS += ${DOTCONF_LIBS} | ||
5 | AM_CPPFLAGS = \ | ||
6 | -DDEFAULT_CONF_FILE=\"${sysconfdir}/${PACKAGE}.conf\" \ | ||
7 | -DDEFAULT_PID_FILE=\"/var/run/${PACKAGE}.pid\" | ||
8 | |||
9 | dudki_SOURCES = dudki.cc \ | ||
10 | process.cc process.h \ | ||
11 | configuration.cc configuration.h \ | ||
12 | util.cc util.h \ | ||
13 | COPYING.cc | ||
14 | |||
15 | COPYING.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> | ||
2 | using 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 | |||
10 | configuration::configuration() | ||
11 | : check_interval(60), pidfile(DEFAULT_PID_FILE), | ||
12 | daemonize(true) { | ||
13 | } | ||
14 | |||
15 | enum dc_ctx { | ||
16 | DCC_ROOT = 1, | ||
17 | DCC_PROCESS = 2 | ||
18 | }; | ||
19 | struct 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 | |||
28 | static DOTCONF_CB(dco_check_interval) { dc_context *dcc = (dc_context*)ctx; | ||
29 | dcc->cf->check_interval = cmd->data.value; | ||
30 | return NULL; | ||
31 | } | ||
32 | static DOTCONF_CB(dco_daemonize) { dc_context *dcc = (dc_context*)ctx; | ||
33 | dcc->cf->daemonize = cmd->data.value; | ||
34 | return NULL; | ||
35 | } | ||
36 | |||
37 | static 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 | } | ||
50 | static 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 | } | ||
67 | static 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 | |||
81 | static 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 | } | ||
89 | static DOTCONF_CB(dco__process) { dc_context *dcc = (dc_context*)ctx; | ||
90 | dcc->ps = NULL; | ||
91 | dcc->ctx = DCC_ROOT; | ||
92 | return NULL; | ||
93 | } | ||
94 | |||
95 | static DOTCONF_CB(dco_restart_command) { dc_context *dcc = (dc_context*)ctx; | ||
96 | dcc->ps->restart_cmd = cmd->data.str; | ||
97 | return NULL; | ||
98 | } | ||
99 | static DOTCONF_CB(dco_user) { dc_context *dcc = (dc_context*)ctx; | ||
100 | dcc->ps->user = cmd->data.str; | ||
101 | return NULL; | ||
102 | } | ||
103 | static DOTCONF_CB(dco_group) { dc_context *dcc = (dc_context*)ctx; | ||
104 | dcc->ps->group = cmd->data.str; | ||
105 | return NULL; | ||
106 | } | ||
107 | static DOTCONF_CB(dco_chroot) { dc_context *dcc = (dc_context*)ctx; | ||
108 | dcc->ps->chroot = cmd->data.str; | ||
109 | return NULL; | ||
110 | } | ||
111 | |||
112 | static 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 | |||
127 | static 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 | } | ||
133 | static FUNC_ERRORHANDLER(dc_error_handler) { | ||
134 | throw runtime_error(string("error parsing config file: ")+msg); | ||
135 | } | ||
136 | |||
137 | void 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> | ||
5 | using namespace std; | ||
6 | #include "process.h" | ||
7 | |||
8 | class 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> | ||
7 | using 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 | |||
23 | bool finishing = false; | ||
24 | static char **_argv = NULL; | ||
25 | |||
26 | static void lethal_signal_handler(int signum) { | ||
27 | syslog(LOG_NOTICE,"Lethal signal received. Terminating."); | ||
28 | finishing = true; | ||
29 | } | ||
30 | static void sighup_handler(int signum) { | ||
31 | syslog(LOG_NOTICE,"SUGHUP received, reloading."); | ||
32 | execvp(_argv[0],_argv); | ||
33 | } | ||
34 | |||
35 | void 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 | |||
40 | void 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 | |||
54 | int 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> | ||
13 | using namespace std; | ||
14 | #include "process.h" | ||
15 | #include "configuration.h" | ||
16 | |||
17 | void 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 | |||
60 | void 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 | |||
119 | void 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 | |||
142 | void 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> | ||
6 | using namespace std; | ||
7 | |||
8 | class configuration; | ||
9 | |||
10 | typedef map<string,string> headers_t; | ||
11 | |||
12 | class 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 | |||
34 | typedef 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> | ||
4 | using namespace std; | ||
5 | #include "util.h" | ||
6 | |||
7 | void 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 | } | ||
16 | void 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> | ||
5 | using namespace std; | ||
6 | |||
7 | class 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 */ | ||