summaryrefslogtreecommitdiffabout
path: root/src/process.cc
Unidiff
Diffstat (limited to 'src/process.cc') (more/less context) (ignore whitespace changes)
-rw-r--r--src/process.cc118
1 files changed, 107 insertions, 11 deletions
diff --git a/src/process.cc b/src/process.cc
index 1ffac9f..8a5b5d2 100644
--- a/src/process.cc
+++ b/src/process.cc
@@ -1,189 +1,285 @@
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;
68 }else{ 84 }else{
69 errno = 0; 85 errno = 0;
70 gid = strtol(group.c_str(),NULL,0); 86 gid = strtol(group.c_str(),NULL,0);
71 if(errno) 87 if(errno)
72 throw runtime_error("Failed to reslove Group value to gid"); 88 throw runtime_error("Failed to reslove Group value to gid");
73 } 89 }
74 } 90 }
75 pid_t p = fork(); 91 pid_t p = fork();
76 if(p<0) 92 if(p<0)
77 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()"); 93 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to fork()");
78 if(!p) { 94 if(!p) {
79 // child 95 // child
80 try { 96 try {
81 setsid(); 97 setsid();
82 if(user.empty()) { 98 if(user.empty()) {
83 if((getgid()!=gid) && setgid(gid)) 99 if((getgid()!=gid) && setgid(gid))
84 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()"); 100 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setgid()");
85 }else{ 101 }else{
86 if(initgroups(user.c_str(),gid)) 102 if(initgroups(user.c_str(),gid))
87 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to initgroups()"); 103 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to initgroups()");
88 } 104 }
89 if(!chroot.empty()) { 105 if(!chroot.empty()) {
90 if(::chroot(chroot.c_str())) 106 if(::chroot(chroot.c_str()))
91 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()"); 107 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to chroot()");
92 } 108 }
93 if(!user.empty()) { 109 if(!user.empty()) {
94 if((getuid()!=uid) && setuid(uid)) 110 if((getuid()!=uid) && setuid(uid))
95 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()"); 111 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to setuid()");
96 } 112 }
97 char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL }; 113 char *argv[] = { "/bin/sh", "-c", (char*)restart_cmd.c_str(), NULL };
98 close(0); close(1); close(2); 114 close(0); close(1); close(2);
99 execv("/bin/sh",argv); 115 execv("/bin/sh",argv);
100 }catch(exception& e) { 116 }catch(exception& e) {
101 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());
102 } 118 }
103 _exit(-1); 119 _exit(-1);
104 } 120 }
105 // parent 121 // parent
106 int rv; 122 int rv;
107 if(waitpid(p,&rv,0)<0) 123 if(waitpid(p,&rv,0)<0)
108 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()"); 124 throw runtime_error(string(__PRETTY_FUNCTION__)+": failed to waitpid()");
109} 125}
110 126
111void 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) {
112 string the_notify; 128 string the_notify;
113 if(!notify.empty()) 129 if(!notify.empty())
114 the_notify=notify; 130 the_notify=notify;
115 else if(!config.notify.empty()) 131 else if(!config.notify.empty())
116 the_notify=config.notify; 132 the_notify=config.notify;
117 else 133 else
118 return; 134 return;
119 try { 135 try {
120 string::size_type colon = the_notify.find(':'); 136 string::size_type colon = the_notify.find(':');
121 if(colon==string::npos) 137 if(colon==string::npos)
122 throw runtime_error("invalid notify action specification"); 138 throw runtime_error("invalid notify action specification");
123 string nschema = the_notify.substr(0,colon); 139 string nschema = the_notify.substr(0,colon);
124 string ntarget = the_notify.substr(colon+1); 140 string ntarget = the_notify.substr(colon+1);
125 if(nschema=="mailto") { 141 if(nschema=="mailto") {
126 notify_mailto(ntarget,id,event,description,config); 142 notify_mailto(ntarget,id,event,description,config);
127 }else 143 }else
128 throw runtime_error("unrecognized notification schema"); 144 throw runtime_error("unrecognized notification schema");
129 }catch(exception& e) { 145 }catch(exception& e) {
130 syslog(LOG_ERR,"Notification error: %s",e.what()); 146 syslog(LOG_ERR,"Notification error: %s",e.what());
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 {
179 ifstream pids(pidfile.c_str(),ios::in); 195 if(!pidfile.empty()) {
180 if(!pids) 196 ifstream pids(pidfile.c_str(),ios::in);
181 throw runtime_error("no pidfile found"); 197 if(!pids)
182 pid_t pid = 0; 198 throw runtime_error("no pidfile found");
183 pids >> pid; 199 pid_t pid = 0;
184 pids.close(); 200 pids >> pid;
185 if(!pid) 201 pids.close();
186 throw runtime_error("no pid in pidfile"); 202 if(!pid)
187 if(kill(pid,signum)) 203 throw runtime_error("no pid in pidfile");
188 throw runtime_error("failed to signal process"); 204 if(kill(pid,signum))
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}