WvStreams
wvsubproc.cc
1/*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * A class for reliably starting/stopping subprocesses. See
6 * wvsubproc.h.
7 */
8#include "wvsubproc.h"
9#include "wvtimeutils.h"
10#include <stdio.h>
11#include <unistd.h>
12#include <sys/types.h>
13#include <sys/wait.h>
14#include <sys/time.h>
15#include <stdarg.h>
16#include <errno.h>
17#include <assert.h>
18
19#include "wvfork.h"
20
21void WvSubProc::init()
22{
23 pid = -1;
24 memlimit = -1;
25 running = false;
26 estatus = 0;
27}
28
29
30WvSubProc::~WvSubProc()
31{
32 // we need to kill the process here, or else we could leave
33 // zombies lying around...
34 stop(100);
35}
36
37
38int WvSubProc::_startv(const char cmd[], const char * const *argv)
39{
40 int waitfd = -1;
41
42 pid = fork(&waitfd);
43 //fprintf(stderr, "pid for '%s' is %d\n", cmd, pid);
44
45 if (!pid) // child process
46 {
47 // unblock the parent.
48 close(waitfd);
49
50#ifdef RLIMIT_AS
51 // Set memory limit, if applicable
52 if (memlimit > 0)
53 {
54 struct rlimit rlim;
55 memset(&rlim, 0, sizeof(rlim));
56 rlim.rlim_cur = memlimit * 1024 * 1024;
57 rlim.rlim_max = memlimit * 1024 * 1024;
58 setrlimit(RLIMIT_AS, &rlim);
59 }
60#endif
61
62 // run the subprocess.
63 execvp(cmd, (char * const *)argv);
64
65 // if we get this far, just make sure we exit, not return.
66 // The code 242 should be somewhat recognizable by the calling
67 // process so we know something is up.
68 _exit(242);
69 }
70 else if (pid > 0) // parent process
71 running = true;
72 else if (pid < 0)
73 return pid;
74
75 return 0; // ok
76}
77
78
79void WvSubProc::prepare(const char cmd[], ...)
80{
81 va_list ap;
82 va_start(ap, cmd);
83 preparev(cmd, ap);
84 va_end(ap);
85}
86
87
88void WvSubProc::preparev(const char cmd[], va_list ap)
89{
90 const char *argptr;
91
92 // remember the command so start_again() will work
93 last_cmd = cmd;
94 last_args.zap();
95 while ((argptr = va_arg(ap, const char *)) != NULL)
96 last_args.append(new WvString(argptr), true);
97}
98
99
100void WvSubProc::preparev(const char cmd[], const char * const *argv)
101{
102 const char * const *argptr;
103
104 // remember the command so start_again() will work
105 last_cmd = cmd;
106 last_args.zap();
107 for (argptr = argv; argptr && *argptr; argptr++)
108 last_args.append(new WvString(*argptr), true);
109}
110
111void WvSubProc::preparev(const char cmd[], WvStringList &args)
112{
113 last_cmd = cmd;
114 last_args.zap();
115
116 WvStringList::Iter i(args);
117 for (i.rewind(); i.next(); )
118 last_args.append(new WvString(*i), true);
119}
120
121int WvSubProc::start(const char cmd[], ...)
122{
123 va_list ap;
124 va_start(ap, cmd);
125 preparev(cmd, ap);
126 va_end(ap);
127
128 return start_again();
129}
130
131
132int WvSubProc::startv(const char cmd[], const char * const *argv)
133{
134 preparev(cmd, argv);
135 return start_again();
136}
137
138
139int WvSubProc::start_again()
140{
141 int retval;
142 const char **argptr;
143
144 assert(!!last_cmd);
145
146 // create a new argv array from our stored values
147 const char **argv = new const char*[last_args.count() + 1];
148 WvStringList::Iter i(last_args);
149 for (argptr = argv, i.rewind(); i.next(); argptr++)
150 *argptr = *i;
151 *argptr = NULL;
152
153 // run the program
154 retval = _startv(last_cmd, argv);
155
156 // clean up
157 deletev argv;
158
159 return retval;
160}
161
162
163int WvSubProc::fork(int *waitfd)
164{
165 static WvString ldpreload, ldlibrary;
166
167 running = false;
168 estatus = 0;
169
170 pid = wvfork_start(waitfd);
171
172 if (!pid)
173 {
174 // child process
175
176 // set the process group of this process, so "negative" kill
177 // will kill everything in the whole session, not just the
178 // main process.
179 setpgid(0,0);
180
181 // set up any extra environment variables
182 WvStringList::Iter i(env);
183 for (i.rewind(); i.next(); )
184 {
185 WvStringList words;
186 words.splitstrict(*i, "=");
187 WvString name = words.popstr();
188 WvString value = words.join("=");
189 if (name == "LD_LIBRARY_PATH" && getenv("LD_LIBRARY_PATH"))
190 {
191 if (!!value)
192 {
193 // don't override - merge!
194 ldlibrary = WvString("%s=%s:%s", name,
195 value, getenv("LD_LIBRARY_PATH"));
196 putenv(ldlibrary.edit());
197 }
198 }
199 else if (name == "LD_PRELOAD" && getenv("LD_PRELOAD"))
200 {
201 if (!!value)
202 {
203 // don't override - merge!
204 ldpreload = WvString("%s=%s:%s", name,
205 value, getenv("LD_PRELOAD"));
206 putenv(ldpreload.edit());
207 }
208 }
209 else if (!value)
210 {
211 // no equals or setting to empty string?
212 // then we must want to unset it!
213 // This is evil, but this is the most simple
214 unsetenv(name);
215 }
216 else
217 putenv(i->edit());
218 }
219 }
220 else if (pid > 0)
221 {
222 // parent process
223 running = true;
224 }
225 else if (pid < 0)
226 return -errno;
227
228 return pid;
229}
230
231
232pid_t WvSubProc::pidfile_pid()
233{
234 if (!!pidfile)
235 {
236 // unfortunately, we don't have WvFile in basic wvutils...
237 char buf[1024];
238 pid_t p = -1;
239 FILE *file = fopen(pidfile, "r");
240
241 memset(buf, 0, sizeof(buf));
242 if (file && fread(buf, 1, sizeof(buf), file) > 0)
243 p = atoi(buf);
244 if (file)
245 fclose(file);
246 if (p <= 0)
247 p = -1;
248 return p;
249 }
250
251 return -1;
252}
253
254
255void WvSubProc::kill(int sig)
256{
257 assert(!running || pid > 0 || !old_pids.isempty());
258
259 if (pid > 0)
260 {
261 // if the process group has disappeared, kill the main process
262 // instead.
263 assert(pid != 1); // make sure we don't kill -1
264 if (::kill(-pid, sig) < 0 && errno == ESRCH)
265 kill_primary(sig);
266 }
267
268 // kill leftover subprocesses too.
269 pid_tList::Iter i(old_pids);
270 for (i.rewind(); i.next(); )
271 {
272 pid_t subpid = *i;
273 assert(subpid != 1 && subpid != -1); // make sure we don't kill -1
274 if (::kill(-subpid, sig) < 0 && errno == ESRCH)
275 ::kill(subpid, sig);
276 }
277}
278
279
280void WvSubProc::kill_primary(int sig)
281{
282 assert(!running || pid > 0 || !old_pids.isempty());
283
284 if (running && pid > 0)
285 ::kill(pid, sig);
286}
287
288
289void WvSubProc::stop(time_t msec_delay, bool kill_children)
290{
291 wait(0);
292
293 if (running)
294 {
295 if (kill_children)
296 kill(SIGTERM);
297 else
298 kill_primary(SIGTERM);
299
300 wait(msec_delay, kill_children);
301 }
302
303 if (running)
304 {
305 if (kill_children)
306 kill(SIGKILL);
307 else
308 kill_primary(SIGKILL);
309
310 wait(-1, kill_children);
311 }
312}
313
314
315void WvSubProc::wait(time_t msec_delay, bool wait_children)
316{
317 bool xrunning;
318 int status;
319 pid_t dead_pid;
320 struct timeval tv1, tv2;
321 struct timezone tz;
322
323 assert(!running || pid > 0 || !old_pids.isempty());
324
325 // running might be false if the parent process is dead and you called
326 // wait(x, false) before. However, if we're now doing wait(x, true),
327 // we want to keep going until the children are dead too.
328 xrunning = (running || (wait_children && !old_pids.isempty()));
329
330 if (!xrunning) return;
331
332 gettimeofday(&tv1, &tz);
333 tv2 = tv1;
334
335 do
336 {
337 if (pid > 0)
338 {
339 // waiting on a process group is unfortunately useless
340 // since you can only get notifications for your direct
341 // descendants. We have to "kill" with a zero signal instead
342 // to try to detect whether they've died or not.
343 dead_pid = waitpid(pid, &status, (msec_delay >= 0) ? WNOHANG : 0);
344
345 //fprintf(stderr, "%ld: dead_pid=%d; pid=%d\n",
346 // msecdiff(tv2, tv1), dead_pid, pid);
347
348 if (dead_pid == pid
349 || (dead_pid < 0 && (errno == ECHILD || errno == ESRCH)))
350 {
351 // the main process is dead - save its status.
352 estatus = status;
353 old_pids.append(new pid_t(pid), true);
354
355 pid_t p2 = pidfile_pid();
356 if (pid != p2)
357 pid = p2;
358 else
359 pid = -1;
360 }
361 else if (dead_pid < 0)
362 perror("WvSubProc::waitpid");
363 }
364
365 // no need to do this next part if the primary subproc isn't dead yet
366 if (pid < 0)
367 {
368 pid_tList::Iter i(old_pids);
369 for (i.rewind(); i.next(); )
370 {
371 pid_t subpid = *i;
372
373 // if the subproc is our direct descendant, we'll be able
374 // to kill it forever if it's a zombie. Sigh. waitpid()
375 // on it just in case.
376 waitpid(subpid, NULL, WNOHANG);
377
378 if (::kill(-subpid, 0) && errno == ESRCH)
379 i.xunlink();
380 }
381
382 // if the primary is dead _and_ we either don't care about
383 // children or all our children are dead, then the subproc
384 // isn't actually running.
385 if (!wait_children || old_pids.isempty())
386 xrunning = false;
387 }
388
389 // wait a while, so we're not spinning _too_ fast in a loop
390 if (xrunning && msec_delay != 0)
391 usleep(50*1000);
392
393 gettimeofday(&tv2, &tz);
394
395 } while (xrunning && msec_delay
396 && (msec_delay < 0 || msecdiff(tv2, tv1) < msec_delay));
397
398 if (!xrunning)
399 running = false;
400}
This is a WvList of WvStrings, and is a really handy way to parse strings.
WvString join(const char *joinchars=" ") const
concatenates all elements of the list seperating on joinchars
void splitstrict(WvStringParm s, const char *splitchars=" \t\r\n", int limit=0)
split s and form a list creating null entries when there are multiple splitchars ie " happy birthday ...
WvString popstr()
get the first string in the list, or an empty string if the list is empty.
WvString is an implementation of a simple and efficient printable-string class.
Definition wvstring.h:330
char * edit()
make the string editable, and return a non-const (char*)
Definition wvstring.h:397
Provides support for forking processes.
pid_t wvfork_start(int *waitfd)
wvfork_start is just like fork, except that it will block the parent until the child process closes t...
Definition wvfork.cc:81