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 
21 void WvSubProc::init()
22 {
23  pid = -1;
24  memlimit = -1;
25  running = false;
26  estatus = 0;
27 }
28 
29 
30 WvSubProc::~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 
38 int 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 
79 void 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 
88 void 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 
100 void 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 
111 void 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 
121 int 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 
132 int WvSubProc::startv(const char cmd[], const char * const *argv)
133 {
134  preparev(cmd, argv);
135  return start_again();
136 }
137 
138 
139 int 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 
163 int 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 
232 pid_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 
255 void 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 
280 void 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 
289 void 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 
315 void 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 }
WvString::edit
char * edit()
make the string editable, and return a non-const (char*)
Definition: wvstring.h:397
WvStringList::popstr
WvString popstr()
get the first string in the list, or an empty string if the list is empty.
Definition: wvstringlist.cc:55
WvStringList::splitstrict
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 ...
Definition: wvstringlist.cc:25
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvStringList::join
WvString join(const char *joinchars=" ") const
concatenates all elements of the list seperating on joinchars
Definition: wvstringlist.cc:14
deletev
#define deletev
Remplacement for delete[].
Definition: delete.h:129
wvfork.h
wvfork_start
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
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27