summaryrefslogtreecommitdiffabout
path: root/src/process.cc
Unidiff
Diffstat (limited to 'src/process.cc') (more/less context) (ignore whitespace changes)
-rw-r--r--src/process.cc32
1 files changed, 21 insertions, 11 deletions
diff --git a/src/process.cc b/src/process.cc
index 8a5b5d2..96c874f 100644
--- a/src/process.cc
+++ b/src/process.cc
@@ -1,285 +1,295 @@
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 <dirent.h>
8#include <sys/wait.h> 8#include <sys/wait.h>
9#include <syslog.h> 9#include <syslog.h>
10#include <errno.h> 10#include <errno.h>
11#include <iostream> 11#include <iostream>
12#include <fstream> 12#include <fstream>
13#include <sstream> 13#include <sstream>
14#include <stdexcept> 14#include <stdexcept>
15#include <string> 15#include <string>
16#include <vector> 16#include <vector>
17using namespace std; 17using namespace std;
18#include "process.h" 18#include "process.h"
19#include "configuration.h" 19#include "configuration.h"
20 20
21static multimap<string,pid_t> procpids; 21static multimap<string,pid_t> procpids;
22 22
23void process::check() const { 23void process::check() const {
24 if(!pidfile.empty()) { 24 if(!pidfile.empty()) {
25 signal(0); 25 signal(0);
26 }else if(!process_name.empty()) { 26 }else if(!process_name.empty()) {
27 if(procpids.empty()) 27 if(procpids.empty())
28 gather_proc_info(); 28 gather_proc_info();
29 if(procpids.find(process_name)==procpids.end()) 29 if(procpids.find(process_name)==procpids.end())
30 throw runtime_error("no such process"); 30 throw runtime_error("no such process");
31 } // XXX: or else? 31 } // XXX: or else?
32} 32}
33void process::check(const string& id,configuration& config) { 33void process::check(const string& id,configuration& config) {
34 try { 34 try {
35 check(); 35 check();
36 patience = 0; 36 patience = 0;
37 }catch(exception& e) { 37 }catch(exception& e) {
38 if(patience>60) { // TODO: configurable 38 if(patience>60) { // TODO: configurable
39 patience = 0; 39 patience = 0;
40 }else{ 40 }else{
41 if(patience<10) { // TODO: configurable 41 if(patience<10) { // TODO: configurable
42 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());
43 do_notify(id,"Starting up", 43 do_notify(id,"Starting up",
44 "The named process seems to be down. Dudki will try\n" 44 "The named process seems to be down. Dudki will try\n"
45 "to revive it by running the specified command.\n", 45 "to revive it by running the specified command.\n",
46 config); 46 config);
47 try { 47 try {
48 launch(id,config); 48 launch(id,config);
49 }catch(exception& e) { 49 }catch(exception& e) {
50 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());
51 } 51 }
52 }else if(patience==10){ // TODO: configurable like the above 52 }else if(patience==10){ // TODO: configurable like the above
53 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());
54 do_notify(id,"Giving up", 54 do_notify(id,"Giving up",
55 "After a number of attempts to relaunch the named process\n" 55 "After a number of attempts to relaunch the named process\n"
56 "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"
57 "to revive the process for a while.\n", 57 "to revive the process for a while.\n",
58 config); 58 config);
59 } 59 }
60 patience++; 60 patience++;
61 } 61 }
62 } 62 }
63} 63}
64 64
65void process::launch(const string& id,configuration& config) { 65void process::launch(const string& id,configuration& config) {
66 uid_t uid = (uid_t)-1; 66 uid_t uid = (uid_t)-1;
67 gid_t gid = (gid_t)-1; 67 gid_t gid = (gid_t)-1;
68 if(!user.empty()) { 68 if(!user.empty()) {
69 struct passwd *ptmp = getpwnam(user.c_str()); 69 struct passwd *ptmp = getpwnam(user.c_str());
70 if(ptmp) { 70 if(ptmp) {
71 uid = ptmp->pw_uid; 71 uid = ptmp->pw_uid;
72 gid = ptmp->pw_gid; 72 gid = ptmp->pw_gid;
73 }else{ 73 }else{
74 errno=0; 74 errno=0;
75 uid = strtol(user.c_str(),NULL,0); 75 uid = strtol(user.c_str(),NULL,0);
76 if(errno) 76 if(errno)
77 throw runtime_error("Failed to resolve User value to uid"); 77 throw runtime_error("Failed to resolve User value to uid");
78 } 78 }
79 } 79 }
80 if(!group.empty()) { 80 if(!group.empty()) {
81 struct group *gtmp = getgrnam(group.c_str()); 81 struct group *gtmp = getgrnam(group.c_str());
82 if(gtmp) { 82 if(gtmp) {
83 gid = gtmp->gr_gid; 83 gid = gtmp->gr_gid;
84 }else{ 84 }else{
85 errno = 0; 85 errno = 0;
86 gid = strtol(group.c_str(),NULL,0); 86 gid = strtol(group.c_str(),NULL,0);
87 if(errno) 87 if(errno)
88 throw runtime_error("Failed to reslove Group value to gid"); 88 throw runtime_error("Failed to reslove Group value to gid");
89 } 89 }
90 } 90 }
91 pid_t p = fork(); 91 pid_t p = fork();
92 if(p<0) 92 if(p<0)
93 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()"); 93 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
94 if(!p) { 94 if(!p) {
95 // child 95 // child
96 try { 96 try {
97 setsid(); 97 setsid();
98 if(user.empty()) { 98 if(user.empty()) {
99 if((getgid()!=gid) && setgid(gid)) 99 if((getgid()!=gid) && setgid(gid))
100 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()"); 100 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()");
101 }else{ 101 }else{
102 if(initgroups(user.c_str(),gid)) 102 if(initgroups(user.c_str(),gid))
103 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to initgroups()"); 103 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to initgroups()");
104 } 104 }
105 if(!chroot.empty()) { 105 if(!chroot.empty()) {
106 if(::chroot(chroot.c_str())) 106 if(::chroot(chroot.c_str()))
107 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()"); 107 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()");
108 } 108 }
109 if(!user.empty()) { 109 if(!user.empty()) {
110 if((getuid()!=uid) && setuid(uid)) 110 if((getuid()!=uid) && setuid(uid))
111 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()"); 111 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()");
112 } 112 }
113 char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL }; 113 char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL };
114 close(0); close(1); close(2); 114 close(0); close(1); close(2);
115 execv("/bin/sh",argv); 115 execv("/bin/sh",argv);
116 }catch(exception& e) { 116 }catch(exception& e) {
117 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what()); 117 syslog(LOG_ERR,"Error trying to launch process '%s': %s",id.c_str(),e.what());
118 } 118 }
119 _exit(-1); 119 _exit(-1);
120 } 120 }
121 // parent 121 // parent
122 int rv; 122 int rv;
123 if(waitpid(p,&rv,0)<0) 123 if(waitpid(p,&rv,0)<0)
124 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()"); 124 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()");
125} 125}
126 126
127void process::do_notify(const string& id,const string& event,const string& description,configuration& config) { 127void process::do_notify(const string& id,const string& event,const string& description,configuration& config) {
128 string the_notify; 128 string the_notify;
129 if(!notify.empty()) 129 if(!notify.empty())
130 the_notify=notify; 130 the_notify=notify;
131 else if(!config.notify.empty()) 131 else if(!config.notify.empty())
132 the_notify=config.notify; 132 the_notify=config.notify;
133 else 133 else
134 return; 134 return;
135 try { 135 try {
136 string::size_type colon = the_notify.find(':'); 136 string::size_type colon = the_notify.find(':');
137 if(colon==string::npos) 137 if(colon==string::npos)
138 throw runtime_error("invalid notify action specification"); 138 throw runtime_error("invalid notify action specification");
139 string nschema = the_notify.substr(0,colon); 139 string nschema = the_notify.substr(0,colon);
140 string ntarget = the_notify.substr(colon+1); 140 string ntarget = the_notify.substr(colon+1);
141 if(nschema=="mailto") { 141 if(nschema=="mailto") {
142 notify_mailto(ntarget,id,event,description,config); 142 notify_mailto(ntarget,id,event,description,config);
143 }else 143 }else
144 throw runtime_error("unrecognized notification schema"); 144 throw runtime_error("unrecognized notification schema");
145 }catch(exception& e) { 145 }catch(exception& e) {
146 syslog(LOG_ERR,"Notification error: %s",e.what()); 146 syslog(LOG_ERR,"Notification error: %s",e.what());
147 } 147 }
148} 148}
149 149
150void 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) {
151 int files[2]; 151 int files[2];
152 if(pipe(files)) 152 if(pipe(files))
153 throw runtime_error("Failed to pipe()"); 153 throw runtime_error("Failed to pipe()");
154 pid_t pid = vfork(); 154 pid_t pid = vfork();
155 if(pid==-1) { 155 if(pid==-1) {
156 close(files[0]); 156 close(files[0]);
157 close(files[1]); 157 close(files[1]);
158 throw runtime_error("Failed to vfork()"); 158 throw runtime_error("Failed to vfork()");
159 } 159 }
160 if(!pid) { 160 if(!pid) {
161 // child 161 // child
162 if(dup2(files[0],0)!=0) 162 if(dup2(files[0],0)!=0)
163 _exit(-1); 163 _exit(-1);
164 close(1); 164 close(1);
165 close(files[0]); 165 close(files[0]);
166 close(files[1]); 166 close(files[1]);
167 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);
168 _exit(-1); 168 _exit(-1);
169 } 169 }
170 // parent 170 // parent
171 close(files[0]); 171 close(files[0]);
172 FILE *mta = fdopen(files[1],"w"); 172 FILE *mta = fdopen(files[1],"w");
173 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) {
174 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());
175 } 175 }
176 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) {
177 if(mailto_headers.find(i->first)!=mailto_headers.end()) 177 if(mailto_headers.find(i->first)!=mailto_headers.end())
178 continue; 178 continue;
179 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());
180 } 180 }
181 fprintf(mta, 181 fprintf(mta,
182 "Subject: [%s] %s\n\n" 182 "Subject: [%s] %s\n\n"
183 "%s\n" 183 "%s\n"
184 "---\n" 184 "---\n"
185 "This message was sent automatically by the 'dudki' daemon\n", 185 "This message was sent automatically by the 'dudki' daemon\n",
186 id.c_str(), event.c_str(), 186 id.c_str(), event.c_str(),
187 description.c_str() ); 187 description.c_str() );
188 fclose(mta); 188 fclose(mta);
189 int status; 189 int status;
190 waitpid(pid,&status,0); 190 waitpid(pid,&status,0);
191 // TODO: check the return code 191 // TODO: check the return code
192} 192}
193 193
194void process::signal(int signum) const { 194void process::signal(int signum) const {
195 if(!pidfile.empty()) { 195 if(!pidfile.empty()) {
196 ifstream pids(pidfile.c_str(),ios::in); 196 ifstream pids(pidfile.c_str(),ios::in);
197 if(!pids) 197 if(!pids)
198 throw runtime_error("no pidfile found"); 198 throw runtime_error("no pidfile found");
199 pid_t pid = 0; 199 pid_t pid = 0;
200 pids >> pid; 200 pids >> pid;
201 pids.close(); 201 pids.close();
202 if(!pid) 202 if(!pid)
203 throw runtime_error("no pid in pidfile"); 203 throw runtime_error("no pid in pidfile");
204 if(kill(pid,signum)) 204 if(kill(pid,signum))
205 throw runtime_error("failed to signal process"); 205 throw runtime_error("failed to signal process");
206 }else if(!process_name.empty()) { 206 }else if(!process_name.empty()) {
207 if(procpids.empty()) 207 if(procpids.empty())
208 gather_proc_info(); 208 gather_proc_info();
209 pair<multimap<string,pid_t>::const_iterator,multimap<string,pid_t>::const_iterator> range = procpids.equal_range(process_name); 209 pair<multimap<string,pid_t>::const_iterator,multimap<string,pid_t>::const_iterator> range = procpids.equal_range(process_name);
210 int count = 0; 210 int count = 0;
211 for(multimap<string,pid_t>::const_iterator i=range.first;i!=range.second;++i) { 211 for(multimap<string,pid_t>::const_iterator i=range.first;i!=range.second;++i) {
212 pid_t pid = i->second; 212 pid_t pid = i->second;
213 if(kill(i->second,signum)) 213 if(kill(i->second,signum))
214 throw runtime_error("failed to signal process"); 214 throw runtime_error("failed to signal process");
215 count++; 215 count++;
216 } 216 }
217 if(!count) 217 if(!count)
218 throw runtime_error("no running instance detected"); 218 throw runtime_error("no running instance detected");
219 }else 219 }else
220 throw runtime_error("nothing is known about the process"); 220 throw runtime_error("nothing is known about the process");
221} 221}
222 222
223void process::prepare_herd() { 223void process::prepare_herd() {
224 procpids.clear(); 224 procpids.clear();
225} 225}
226void process::unprepare_herd() { 226void process::unprepare_herd() {
227 procpids.clear(); 227 procpids.clear();
228} 228}
229void process::gather_proc_info() { 229void process::gather_proc_info() {
230 vector<pid_t> allpids; 230 vector<pid_t> allpids;
231 DIR *pd = opendir("/proc"); 231 DIR *pd = opendir("/proc");
232 if(!pd) 232 if(!pd)
233 throw runtime_error("failed to open /proc"); 233 throw runtime_error("failed to open /proc");
234 struct dirent *pde; 234 struct dirent *pde;
235 pid_t selfpid = getpid(); 235 pid_t selfpid = getpid();
236 while(pde=readdir(pd)) { 236 while(pde=readdir(pd)) {
237 errno=0; 237 errno=0;
238 pid_t pid = atoi(pde->d_name); 238 pid_t pid = atoi(pde->d_name);
239 if((!pid) || pid==selfpid) 239 if((!pid) || pid==selfpid)
240 continue; 240 continue;
241 allpids.push_back(pid); 241 allpids.push_back(pid);
242 } 242 }
243 closedir(pd); 243 closedir(pd);
244 char s[256]; 244 char s[256];
245 procpids.clear(); 245 procpids.clear();
246 for(vector<pid_t>::const_iterator i=allpids.begin();i!=allpids.end();++i) { 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); 247 int r = snprintf(s,sizeof(s),"/proc/%d/stat",*i);
248 if(r>=sizeof(s) || r<1) 248 if(r>=sizeof(s) || r<1)
249 continue; 249 continue;
250 string cmd; 250 string cmd;
251 ifstream ss(s,ios::in); 251 ifstream ss(s,ios::in);
252 if(!ss) 252 if(ss) {
253 continue; 253 getline(ss,cmd);
254 getline(ss,cmd); 254 string::size_type op = cmd.find('(');
255 string::size_type op = cmd.find('('); 255 if(op==string::npos)
256 if(op==string::npos) 256 continue;
257 continue; 257 cmd.erase(0,op+1);
258 cmd.erase(0,op+1); 258 string::size_type cp = cmd.find(')');
259 string::size_type cp = cmd.find(')'); 259 if(cp==string::npos)
260 if(cp==string::npos) 260 continue;
261 continue; 261 cmd.erase(cp);
262 cmd.erase(cp); 262 }else{
263 r = snprintf(s,sizeof(s),"/proc/%d/status",*i);
264 if(r>=sizeof(s) || r<1)
265 continue;
266 ifstream ss(s,ios::in);
267 if(!ss)
268 continue;
269 ss >> cmd;
270 if(cmd.empty())
271 continue;
272 }
263 r = snprintf(s,sizeof(s),"/proc/%d/cmdline",*i); 273 r = snprintf(s,sizeof(s),"/proc/%d/cmdline",*i);
264 if(r>=sizeof(s) || r<1) 274 if(r>=sizeof(s) || r<1)
265 continue; 275 continue;
266 ifstream cs(s,ios::binary); 276 ifstream cs(s,ios::binary);
267 if(!cs) 277 if(!cs)
268 continue; 278 continue;
269 string command; 279 string command;
270 while(cs) { 280 while(cs) {
271 string cl; 281 string cl;
272 getline(cs,cl,(char)0); 282 getline(cs,cl,(char)0);
273 string::size_type lsl = cl.rfind('/'); 283 string::size_type lsl = cl.rfind('/');
274 if(lsl!=string::npos) 284 if(lsl!=string::npos)
275 cl.erase(0,lsl+1); 285 cl.erase(0,lsl+1);
276 if(cl.substr(0,cmd.length())==cmd) { 286 if(cl.substr(0,cmd.length())==cmd) {
277 command = cl; 287 command = cl;
278 break; 288 break;
279 } 289 }
280 } 290 }
281 procpids.insert(pair<string,pid_t>(cmd,*i)); 291 procpids.insert(pair<string,pid_t>(cmd,*i));
282 if((!command.empty()) && cmd!=command) 292 if((!command.empty()) && cmd!=command)
283 procpids.insert(pair<string,pid_t>(command,*i)); 293 procpids.insert(pair<string,pid_t>(command,*i));
284 } 294 }
285} 295}