home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.bin / SourceCode / Classes / SubprocessPlus / Subprocess.m < prev   
Encoding:
Text File  |  1992-04-14  |  7.3 KB  |  366 lines

  1. /*----------------------------------------------------------------------------
  2.     Subprocess.m
  3.     
  4.     From Subprocess example by Charles L. Oei
  5.                                     pty support by Joe Freeman
  6.                                     with encouragement from Kristofer Younger
  7.                                     Subprocess Example, Release 2.0
  8.                                     NeXT Computer, Inc.
  9.     
  10.     You may freely copy, distribute and reuse the code in this example.
  11.     NeXT disclaims any warranty of any kind, expressed or implied, as to
  12.     its fitness for any particular use.
  13.         
  14.     SYNOPSIS
  15.         Handles a UNIX process that runs asynchronously.
  16.  
  17.     REVISIONS
  18.     $Log$
  19. ----------------------------------------------------------------------------*/
  20. # import <sys/wait.h>
  21. # import <sys/resource.h>
  22. # import <appkit/nextstd.h>
  23. # import <appkit/Application.h>
  24. # import <appkit/Panel.h>
  25. # import <nextutils.h>
  26. # import "Subprocess.h"
  27.  
  28. extern int        wait4(int, union wait *, int, struct rusage *);
  29. static void        fdHandler(int theFd, id self);
  30. static void        stderrFdHandler(int theFd, id self);
  31.  
  32. # define PIPE_ERROR            "Error starting UNIX pipes to subprocess."
  33. # define VFORK_ERROR            "Error starting UNIX vfork of subprocess."
  34.  
  35. @implementation Subprocess
  36.  
  37. /*---------------------------< INIT/FREE METHODS >---------------------------*/
  38. /*
  39.  * Cover for the init:withDelegate: with a nil delegate
  40.  */
  41. - init:(const char *)subprocessString
  42. {
  43.     return([self init:subprocessString
  44.                     withDelegate:nil]);
  45. }
  46.  
  47. - init:(const char *)subprocessString withDelegate:theDelegate
  48. {    int        pipeTo[2];
  49.     int        pipeFrom[2];
  50.     int        pipeStderr[2];    /* for stderr to different fd */
  51.     int        numFds,
  52.                 fd;
  53.     int        processGroup;
  54.     
  55.     [super init];
  56.     
  57.     outputBufferLen = 0;
  58.     stderrBufferLen = 0;
  59.     [self setDelegate:theDelegate];
  60.     
  61.     if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0)
  62.     {    [delegate perform:@selector(subprocess:error:)
  63.                          with:self
  64.                          with:(void *)PIPE_ERROR];
  65.         return(self);
  66.     }
  67.     
  68.     switch (childPid = vfork())
  69.     {    case -1:                        /* error */
  70.             [delegate perform:@selector(subprocess:error:)
  71.                              with:self
  72.                              with:(void *)VFORK_ERROR];
  73.             return(self);
  74.     
  75.         case 0:                        /* child */
  76.             dup2(pipeTo[0], 0);
  77.             dup2(pipeFrom[1], 1);            /* get stdout from process */
  78.             dup2(pipeStderr[1], 2);            /* get stderr here */
  79.             
  80.             numFds = getdtablesize();
  81.             for (fd=3; fd<numFds; fd++)
  82.                 close(fd);
  83.             
  84.             processGroup = getpid();
  85.             ioctl(0, TIOCSPGRP, (char *)&processGroup);
  86.             setpgrp (0, processGroup);
  87.             
  88.             /*
  89.              * we exec a /bin/sh so that cmds are easier to specify for the user
  90.              */
  91.             execl("/bin/sh", "sh", "-c", subprocessString, 0);
  92.             perror("vfork (child)");        /* should never gets here tho */
  93.             exit(1);
  94.         
  95.         default:                    /* parent */
  96.             running = YES;
  97.             
  98.             close(pipeTo[0]);
  99.             close(pipeFrom[1]);
  100.             close(pipeStderr[1]);
  101.  
  102.             fpToChild = fdopen(pipeTo[1], "w");
  103.             fromChild = pipeFrom[0];
  104.             fpFromChild = fdopen(pipeFrom[0],"r");
  105.                 
  106.             stderrFromChild = pipeStderr[0];
  107.     
  108.             /*
  109.              * Set buffering method, also make it use its own buffers
  110.              */
  111.             setbuf(fpToChild,NULL);                    /* no buffering */
  112.             setbuf(fpFromChild,NULL);
  113.             DPSAddFD(fromChild,(DPSFDProc)fdHandler,(id)self,
  114.                         NX_MODALRESPTHRESHOLD+1);
  115.  
  116.             DPSAddFD(stderrFromChild,(DPSFDProc)stderrFdHandler,(id)self,
  117.                         NX_MODALRESPTHRESHOLD+1);
  118.             
  119.             return(self);
  120.     }
  121. }
  122.  
  123. /*-----------------------------< OTHER METHODS >-----------------------------*/
  124. /*
  125.  * cleanup after a child process exits
  126.  */
  127. - childDidExit
  128. {    union wait        w;
  129.     int                status = 0;
  130.     int                thePid;
  131.     
  132.     thePid = wait4(childPid,&w,WUNTRACED,NULL);
  133. # ifdef DEBUG
  134.     fprintf(stderr,"%s: wait4() on process id #%d returned %d, with "
  135.                 "w_status = %d, w_retcode = %d, w_stopval = %d, "
  136.                 "w_stopsig = %d, w_termsig = %d\n",
  137.                 [self name],childPid,thePid,w.w_status,w.w_retcode,
  138.                 w.w_stopval,w.w_stopsig,w.w_termsig);
  139. # endif
  140.     if (thePid > 0)
  141.     {    
  142.         if (WIFEXITED(w))
  143.             status = (w.w_status >> 8);
  144.         else
  145.         {    if (WIFSTOPPED(w))
  146.                 status = SUBPROCESS_STOPPED;
  147.             else
  148.             {    if (WIFSIGNALED(w))
  149.                     status = SUBPROCESS_SIGNALED;
  150.             }
  151.         }
  152.         DPSRemoveFD(fromChild);
  153.         DPSRemoveFD(stderrFromChild);
  154.         fclose(fpFromChild);
  155.         close(fromChild);
  156.         close(stderrFromChild);
  157.         fclose(fpToChild);
  158.         running = NO;
  159.         [delegate perform:@selector(subprocess:done:)
  160.                             with:self
  161.                             with:(void *)status];
  162.     }
  163.     return(self);
  164. }
  165.  
  166. /*
  167.  * DPS handler for output from subprocess
  168.  */
  169. - fdHandler:(int)theFd
  170. {    char        *s,
  171.                 *linep;
  172.     int        bufferCount;
  173.         
  174.     bufferCount = read(theFd,outputBuffer+outputBufferLen,
  175.                                 BUFSIZ-outputBufferLen);
  176.     if (bufferCount <= 0)
  177.     {    [self childDidExit];
  178.         return(self);
  179.     }
  180.     outputBuffer[bufferCount + outputBufferLen] = '\0';
  181.  
  182.     /*
  183.      * Send lines in the buffer to the delegate
  184.      */
  185.     s = linep = outputBuffer;
  186.     while (s != NULL)
  187.     {    if ((s = index(linep,'\n')) != NULL)
  188.         {    *s = EOSTR;
  189.             [delegate perform:@selector(subprocess:output:)
  190.                              with:self
  191.                              with:(void *)linep];
  192.             linep = s+1;
  193.         }
  194.     }
  195.     
  196.     /*
  197.      * Copy the last part of the line back into the input buffer for next
  198.      * time (incomplete line)
  199.      */
  200.     outputBufferLen = strlen(linep);
  201.     strncpy(outputBuffer,linep,outputBufferLen);
  202.     
  203.     return(self);
  204. }
  205.  
  206. /*
  207.  * DPS handler for output from subprocess
  208.  */
  209. static void fdHandler(int theFd, id self)
  210. {
  211.     [self fdHandler:theFd];
  212. }
  213.  
  214. - stderrFdHandler:(int)theFd
  215. {    char        *s,
  216.                 *linep;
  217.     int        bufferCount;
  218.                 
  219.     bufferCount = read(theFd,stderrBuffer+stderrBufferLen,
  220.                                 BUFSIZ-stderrBufferLen);
  221.     if (bufferCount <= 0)
  222.         return(self);
  223.  
  224.     stderrBuffer[bufferCount + stderrBufferLen] = '\0';
  225.  
  226.     /*
  227.      * Send lines in the buffer to the delegate
  228.      */
  229.     s = linep = stderrBuffer;
  230.     while (s != NULL)
  231.     {    if ((s = index(linep,'\n')) != NULL)
  232.         {    *s = EOSTR;
  233.             [delegate perform:@selector(subprocess:stderrOutput:)
  234.                              with:self
  235.                              with:(void *)linep];
  236.             linep = s+1;
  237.         }
  238.     }
  239.     
  240.     /*
  241.      * Copy the last part of the line back into the input buffer for next
  242.      * time (incomplete line)
  243.      */
  244.     stderrBufferLen = strlen(linep);
  245.     strncpy(stderrBuffer,linep,stderrBufferLen);
  246.     
  247.     return(self);
  248. }
  249.  
  250. /*
  251.  * And standard error from subprocess
  252.  */
  253. static void stderrFdHandler(int theFd, id self)
  254. {
  255.     [self stderrFdHandler:theFd];
  256. }
  257.  
  258. - send:(const char *)string withNewline:(BOOL)wantNewline
  259. {
  260.     fputs(string,fpToChild);
  261.     if (wantNewline)
  262.         fputc('\n',fpToChild);
  263.     return(self);
  264. }
  265.  
  266. - send:(const char *)string
  267. {
  268.     [self send:string withNewline:YES];
  269.     return(self);
  270. }
  271.  
  272. /*
  273.  * Returns the process id of the process (and therefore the process group
  274.  * of the job)
  275.  */
  276. - (int)pid
  277. {
  278.     return(childPid);
  279. }
  280.  
  281. - (BOOL)isPaused
  282. {
  283.     return(paused);
  284. }
  285.  
  286. - resume:sender
  287. {
  288.     if (paused)
  289.     {    killpg(childPid,SIGCONT);            /* resume the process group */
  290.         paused = NO;
  291.     }
  292.     return(self);
  293. }
  294.  
  295. - pause:sender
  296. {
  297.     if (!paused)
  298.     {    killpg(childPid,SIGSTOP);            /* pause the process group */
  299.         paused = YES;
  300.     }
  301.     return(self);
  302. }
  303.  
  304. - (BOOL)isRunning
  305. {
  306.     return(running);
  307. }
  308.  
  309. - terminate:sender
  310. {
  311.     if (running)
  312.         killpg(childPid,SIGKILL);
  313.     return(self);
  314. }
  315.  
  316. /*
  317.  * effectively sends an EOF to the child process stdin
  318.  */
  319. - terminateInput
  320. {
  321.     fclose(fpToChild);
  322.     return(self);
  323. }
  324.  
  325. - setDelegate:anObject
  326. {
  327.     delegate = anObject;
  328.     return(self);
  329. }
  330.  
  331. - delegate
  332. {
  333.     return(delegate);
  334. }
  335.  
  336. @end
  337.  
  338. @implementation Object(SubprocessDelegate)
  339.  
  340. - subprocess:sender done:(int)exitStatus
  341. {
  342.     return(self);
  343. }
  344.  
  345. - subprocess:sender output:(char *)buffer
  346. {
  347.     return(self);
  348. }
  349.  
  350. - subprocess:sender stderrOutput:(char *)buffer
  351. {
  352.     return(self);
  353. }
  354.  
  355. - subprocess:sender error:(const char *)errorString
  356. {
  357.     if (NXApp)
  358.         NXRunAlertPanel(0, errorString, 0, 0, 0);
  359.     else
  360.         perror(errorString);
  361.     return(self);
  362. }
  363.  
  364. @end
  365.  
  366.