WvStreams
wvsubproc.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * A class for reliably starting/stopping subprocesses.  See
00006  * wvsubproc.h.
00007  */
00008 #include "wvsubproc.h"
00009 #include "wvtimeutils.h"
00010 #include <stdio.h>
00011 #include <unistd.h>
00012 #include <sys/types.h>
00013 #include <sys/wait.h>
00014 #include <sys/time.h>
00015 #include <stdarg.h>
00016 #include <errno.h>
00017 #include <assert.h>
00018 
00019 #include "wvfork.h"
00020 
00021 void WvSubProc::init()
00022 {
00023     pid = -1;
00024     memlimit = -1;
00025     running = false;
00026     estatus = 0;
00027 }
00028 
00029 
00030 WvSubProc::~WvSubProc()
00031 {
00032     // we need to kill the process here, or else we could leave
00033     // zombies lying around...
00034     stop(100);
00035 }
00036 
00037 
00038 int WvSubProc::_startv(const char cmd[], const char * const *argv)
00039 {
00040     int waitfd = -1;
00041     
00042     pid = fork(&waitfd);
00043     //fprintf(stderr, "pid for '%s' is %d\n", cmd, pid);
00044     
00045     if (!pid) // child process
00046     {
00047         // unblock the parent.
00048         close(waitfd);
00049         
00050 #ifdef RLIMIT_AS
00051         // Set memory limit, if applicable
00052         if (memlimit > 0)
00053         {
00054             struct rlimit rlim;
00055             memset(&rlim, 0, sizeof(rlim));
00056             rlim.rlim_cur = memlimit * 1024 * 1024;
00057             rlim.rlim_max = memlimit * 1024 * 1024;
00058             setrlimit(RLIMIT_AS, &rlim);
00059         }
00060 #endif
00061 
00062         // run the subprocess.
00063         execvp(cmd, (char * const *)argv);
00064         
00065         // if we get this far, just make sure we exit, not return.
00066         // The code 242 should be somewhat recognizable by the calling
00067         // process so we know something is up.
00068         _exit(242);
00069     }
00070     else if (pid > 0) // parent process
00071         running = true;
00072     else if (pid < 0)
00073         return pid;
00074     
00075     return 0; // ok
00076 }
00077 
00078 
00079 void WvSubProc::prepare(const char cmd[], ...)
00080 {
00081     va_list ap;
00082     va_start(ap, cmd);
00083     preparev(cmd, ap);
00084     va_end(ap);
00085 }
00086 
00087 
00088 void WvSubProc::preparev(const char cmd[], va_list ap)
00089 {
00090     const char *argptr;
00091     
00092     // remember the command so start_again() will work
00093     last_cmd = cmd;
00094     last_args.zap();
00095     while ((argptr = va_arg(ap, const char *)) != NULL)
00096         last_args.append(new WvString(argptr), true);
00097 }
00098 
00099 
00100 void WvSubProc::preparev(const char cmd[], const char * const *argv)
00101 {
00102     const char * const *argptr;
00103     
00104     // remember the command so start_again() will work
00105     last_cmd = cmd;
00106     last_args.zap();
00107     for (argptr = argv; argptr && *argptr; argptr++)
00108         last_args.append(new WvString(*argptr), true);
00109 }
00110 
00111 void WvSubProc::preparev(const char cmd[], WvStringList &args)
00112 {
00113     last_cmd = cmd;
00114     last_args.zap();
00115 
00116     WvStringList::Iter i(args);
00117     for (i.rewind(); i.next(); )
00118         last_args.append(new WvString(*i), true);
00119 }
00120 
00121 int WvSubProc::start(const char cmd[], ...)
00122 {
00123     va_list ap;
00124     va_start(ap, cmd);
00125     preparev(cmd, ap);
00126     va_end(ap);
00127     
00128     return start_again();
00129 }
00130 
00131 
00132 int WvSubProc::startv(const char cmd[], const char * const *argv)
00133 {
00134     preparev(cmd, argv);
00135     return start_again();
00136 }
00137 
00138 
00139 int WvSubProc::start_again()
00140 {
00141     int retval;
00142     const char **argptr;
00143     
00144     assert(!!last_cmd);
00145     
00146     // create a new argv array from our stored values
00147     const char **argv = new const char*[last_args.count() + 1];
00148     WvStringList::Iter i(last_args);
00149     for (argptr = argv, i.rewind(); i.next(); argptr++)
00150         *argptr = *i;
00151     *argptr = NULL;
00152     
00153     // run the program
00154     retval = _startv(last_cmd, argv);
00155     
00156     // clean up
00157     deletev argv;
00158     
00159     return retval;
00160 }
00161 
00162 
00163 int WvSubProc::fork(int *waitfd)
00164 {
00165     static WvString ldpreload, ldlibrary;
00166     
00167     running = false;
00168     estatus = 0;
00169 
00170     pid = wvfork_start(waitfd);
00171 
00172     if (!pid)
00173     {
00174         // child process
00175          
00176         // set the process group of this process, so "negative" kill
00177         // will kill everything in the whole session, not just the
00178         // main process.
00179         setpgid(0,0);
00180 
00181         // set up any extra environment variables
00182         WvStringList::Iter i(env);
00183         for (i.rewind(); i.next(); )
00184         {
00185             WvStringList words;
00186             words.splitstrict(*i, "=");
00187             WvString name = words.popstr();
00188             WvString value = words.join("=");
00189             if (name == "LD_LIBRARY_PATH" && getenv("LD_LIBRARY_PATH"))
00190             {
00191                 if (!!value)
00192                 {
00193                     // don't override - merge!
00194                     ldlibrary = WvString("%s=%s:%s", name,
00195                             value, getenv("LD_LIBRARY_PATH"));
00196                     putenv(ldlibrary.edit());
00197                 }
00198             }
00199             else if (name == "LD_PRELOAD" && getenv("LD_PRELOAD"))
00200             {
00201                 if (!!value)
00202                 {
00203                     // don't override - merge!
00204                     ldpreload = WvString("%s=%s:%s", name,
00205                             value, getenv("LD_PRELOAD"));
00206                     putenv(ldpreload.edit());
00207                 }
00208             }
00209             else if (!value)
00210             {
00211                 // no equals or setting to empty string?
00212                 // then we must want to unset it!
00213                 // This is evil, but this is the most simple
00214                 unsetenv(name);
00215             } 
00216             else 
00217                 putenv(i->edit());
00218         }
00219     }
00220     else if (pid > 0)
00221     {
00222         // parent process
00223         running = true;
00224     }
00225     else if (pid < 0)
00226         return -errno;
00227     
00228     return pid;
00229 }
00230 
00231 
00232 pid_t WvSubProc::pidfile_pid()
00233 {
00234     if (!!pidfile)
00235     {
00236         // unfortunately, we don't have WvFile in basic wvutils...
00237         char buf[1024];
00238         pid_t p = -1;
00239         FILE *file = fopen(pidfile, "r");
00240         
00241         memset(buf, 0, sizeof(buf));
00242         if (file && fread(buf, 1, sizeof(buf), file) > 0)
00243             p = atoi(buf);
00244         if (file)
00245             fclose(file);
00246         if (p <= 0)
00247             p = -1;
00248         return p;
00249     }
00250     
00251     return -1;
00252 }
00253 
00254 
00255 void WvSubProc::kill(int sig)
00256 {
00257     assert(!running || pid > 0 || !old_pids.isempty());
00258     
00259     if (pid > 0)
00260     {
00261         // if the process group has disappeared, kill the main process
00262         // instead.
00263         assert(pid != 1);  // make sure we don't kill -1
00264         if (::kill(-pid, sig) < 0 && errno == ESRCH)
00265             kill_primary(sig);
00266     }
00267     
00268     // kill leftover subprocesses too.
00269     pid_tList::Iter i(old_pids);
00270     for (i.rewind(); i.next(); )
00271     {
00272         pid_t subpid = *i;
00273         assert(subpid != 1 && subpid != -1); // make sure we don't kill -1
00274         if (::kill(-subpid, sig) < 0 && errno == ESRCH)
00275             ::kill(subpid, sig);
00276     }
00277 }
00278 
00279 
00280 void WvSubProc::kill_primary(int sig)
00281 {
00282     assert(!running || pid > 0 || !old_pids.isempty());
00283     
00284     if (running && pid > 0)
00285         ::kill(pid, sig);
00286 }
00287 
00288 
00289 void WvSubProc::stop(time_t msec_delay, bool kill_children)
00290 {
00291     wait(0);
00292     
00293     if (running)
00294     {
00295         if (kill_children)
00296             kill(SIGTERM);
00297         else
00298             kill_primary(SIGTERM);
00299 
00300         wait(msec_delay, kill_children);
00301     }
00302     
00303     if (running)
00304     {
00305         if (kill_children)
00306             kill(SIGKILL);
00307         else
00308             kill_primary(SIGKILL);
00309 
00310         wait(-1, kill_children);
00311     }
00312 }
00313 
00314 
00315 void WvSubProc::wait(time_t msec_delay, bool wait_children)
00316 {
00317     bool xrunning;
00318     int status;
00319     pid_t dead_pid;
00320     struct timeval tv1, tv2;
00321     struct timezone tz;
00322     
00323     assert(!running || pid > 0 || !old_pids.isempty());
00324 
00325     // running might be false if the parent process is dead and you called
00326     // wait(x, false) before.  However, if we're now doing wait(x, true),
00327     // we want to keep going until the children are dead too.
00328     xrunning = (running || (wait_children && !old_pids.isempty()));
00329     
00330     if (!xrunning) return;
00331     
00332     gettimeofday(&tv1, &tz);
00333     tv2 = tv1;
00334     
00335     do
00336     {
00337         if (pid > 0)
00338         {
00339             // waiting on a process group is unfortunately useless
00340             // since you can only get notifications for your direct
00341             // descendants.  We have to "kill" with a zero signal instead
00342             // to try to detect whether they've died or not.
00343             dead_pid = waitpid(pid, &status, (msec_delay >= 0) ? WNOHANG : 0);
00344         
00345             //fprintf(stderr, "%ld: dead_pid=%d; pid=%d\n",
00346             //  msecdiff(tv2, tv1), dead_pid, pid);
00347             
00348             if (dead_pid == pid 
00349                 || (dead_pid < 0 && (errno == ECHILD || errno == ESRCH)))
00350             {
00351                 // the main process is dead - save its status.
00352                 estatus = status;
00353                 old_pids.append(new pid_t(pid), true);
00354                 
00355                 pid_t p2 = pidfile_pid();
00356                 if (pid != p2)
00357                     pid = p2;
00358                 else
00359                     pid = -1;
00360             }
00361             else if (dead_pid < 0)
00362                 perror("WvSubProc::waitpid");
00363         }
00364         
00365         // no need to do this next part if the primary subproc isn't dead yet
00366         if (pid < 0)
00367         {
00368             pid_tList::Iter i(old_pids);
00369             for (i.rewind(); i.next(); )
00370             {
00371                 pid_t subpid = *i;
00372                 
00373                 // if the subproc is our direct descendant, we'll be able
00374                 // to kill it forever if it's a zombie.  Sigh.  waitpid()
00375                 // on it just in case.
00376                 waitpid(subpid, NULL, WNOHANG);
00377                 
00378                 if (::kill(-subpid, 0) && errno == ESRCH)
00379                     i.xunlink();
00380             }
00381             
00382             // if the primary is dead _and_ we either don't care about
00383             // children or all our children are dead, then the subproc
00384             // isn't actually running.
00385             if (!wait_children || old_pids.isempty())
00386                 xrunning = false;
00387         }
00388 
00389         // wait a while, so we're not spinning _too_ fast in a loop
00390         if (xrunning && msec_delay != 0)
00391             usleep(50*1000);
00392         
00393         gettimeofday(&tv2, &tz);
00394         
00395     } while (xrunning && msec_delay
00396              && (msec_delay < 0 || msecdiff(tv2, tv1) < msec_delay));
00397 
00398     if (!xrunning)
00399         running = false;
00400 }