home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1995 August / NEBULA.bin / SourceCode / MiscKit1.2.6 / Temp / MiscSubprocess.m < prev    next >
Encoding:
Text File  |  1994-05-19  |  11.8 KB  |  505 lines

  1. //
  2. //    MiscSubprocess.m -- a Obj-C wrapper around Unix child processes
  3. //        Originally written by Drew Davidson.
  4. //        Copyright (c) 1994 by Drew Davidson.
  5. //        Modified by Don Yacktman for inclusion into the MiscKit.
  6. //        Fixed up by Carl Lindberg and Don Yacktman.
  7. //                Version 1.2.  All rights reserved.
  8. //        This notice may not be removed from this source code.
  9. //
  10. //    This object is included in the MiscKit by permission from the author
  11. //    and its use is governed by the MiscKit license, found in the file
  12. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  13. //    for a list of all applicable permissions and restrictions.
  14. //    
  15.  
  16. /*----------------------------------------------------------------------------
  17.     $Source$
  18.  
  19.     SYNOPSIS
  20.         Handles a subprocess that is fork()'ed and handles delegate
  21.         notification of events that occur such as output and errors.
  22.  
  23.     From Subprocess example by Charles L. Oei
  24.             pty support (removed) by Joe Freeman
  25.             with encouragement from Kristofer Younger
  26.             Subprocess Example, Release 2.0
  27.             NeXT Computer, Inc.
  28.                                     
  29.     Modified to support signals SIGSTOP and SIGCONT, and to wait for error
  30.     code from terminated process, and to add delegate methods for the output by
  31.     Drew Davidson
  32.     
  33.     Modified for MiscKit inclusion by Don Yacktman
  34.     Debugged by Carl Lindberg and Don Yacktman
  35.     
  36.     REVISIONS
  37.     $Log$
  38. ----------------------------------------------------------------------------*/
  39. #import <libc.h>
  40. #import <misckit/MiscString.h>
  41. #import <misckit/MiscStringArray.h>
  42. #import "MiscSubprocess.h"
  43.  
  44. extern int    wait4(int, union wait *, int, struct rusage *);
  45. static void    stdoutFdHandler(int theFd, id self);
  46. static void    stderrFdHandler(int theFd, id self);
  47.  
  48. #define PIPE_ERROR    "Error starting UNIX pipes to subprocess."
  49. #define VFORK_ERROR    "Error starting UNIX vfork of subprocess."
  50. #define EXEC_ERROR    "Error starting UNIX exec of subprocess."
  51. #define    PTY_TEMPLATE "/dev/pty??"
  52. #define    PTY_LENGTH 11
  53.  
  54. @interface MiscSubprocess(private)
  55.  
  56. - _childDidExit;
  57. - _stdoutBuffer;
  58. - _stderrBuffer;
  59. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString;
  60. - _setRunning:(BOOL)yn;
  61.  
  62. @end
  63.  
  64. static void
  65. getptys (int *master, int *slave)
  66.     // attempt to setup the ptys
  67. {
  68.     char device[PTY_LENGTH];
  69.     char *block, *num;
  70.     char *blockLoc; // specifies the location of block for the device string
  71.     char *numLoc; // specifies the pty name with the digit ptyxD
  72.     char *msLoc; // specifies the master (ptyxx) or slave (ttyxx)
  73.     
  74.     struct sgttyb setp =
  75.     {B9600, B9600, (char)0x7f, (char)0x15, (CRMOD|ANYP)};
  76.     struct tchars setc =
  77.     {CINTR, CQUIT, CSTART, CSTOP, CEOF, CBRK};
  78.     struct ltchars sltc =
  79.     {CSUSP, CDSUSP, CRPRNT, CFLUSH, CWERASE, CLNEXT};
  80.     int    lset =
  81.     (LCRTBS|LCRTERA|LCRTKIL|LCTLECH|LPENDIN|LDECCTQ);
  82.     int    setd = NTTYDISC;
  83.     
  84.     strcpy(device, PTY_TEMPLATE); // string constants are not writable
  85.     blockLoc = &device[ strlen("/dev/pty") ];
  86.     numLoc = &device[ strlen("/dev/pty?") ];
  87.     msLoc = &device[ strlen("/dev/") ];
  88.     for (block = "pqrs"; *block; block++)
  89.     {
  90.     *blockLoc = *block;
  91.     for (num = "0123456789abcdef"; *num; num++)
  92.     {
  93.         *numLoc = *num;
  94.         *master = open(device, O_RDWR);
  95.         if (*master >= 0)
  96.         {
  97.         *msLoc = 't';
  98.         *slave = open(device, O_RDWR);
  99.         if (*slave >= 0)
  100.         {
  101.             (void) ioctl(*slave, TIOCSETP, (char *)&setp);
  102.             (void) ioctl(*slave, TIOCSETC, (char *)&setc);
  103.             (void) ioctl(*slave, TIOCSETD, (char *)&setd);
  104.             (void) ioctl(*slave, TIOCSLTC, (char *)&sltc);
  105.             (void) ioctl(*slave, TIOCLSET, (char *)&lset);
  106.             return;
  107.         }
  108.         }
  109.     } /* hunting through a bank of ptys */
  110.     } /* hunting through blocks of ptys in all the right places */
  111.     *master = -1;
  112.     *slave = -1;
  113. }
  114.  
  115.  
  116. @implementation MiscSubprocess
  117.  
  118. /*----------------------------< PRIVATE METHODS >----------------------------*/
  119. /*
  120.  * cleanup after a child process exits
  121.  */
  122. - _childDidExit
  123. {
  124.     union wait w; int status = 0;
  125.     MiscSubprocessEndCode code = Misc_UnknownEndCode;
  126.  
  127.     if (wait4(childPid,&w,WUNTRACED,NULL) > 0) {
  128.         IMP done;
  129.         if (WIFEXITED(w)) {
  130.             code = Misc_Exited;
  131.             status = w.w_retcode;
  132.         } else {
  133.             if (WIFSTOPPED(w)) {
  134.                 code = Misc_Stopped;
  135.                 status = w.w_stopsig;
  136.             } else {
  137.                 if (WIFSIGNALED(w)) {
  138.                     code = Misc_Signaled;
  139.                     status = w.w_termsig;
  140.                 }
  141.             }
  142.         }
  143.         DPSRemoveFD(stdoutFromChild);
  144.         DPSRemoveFD(stderrFromChild);
  145.         fclose(fpFromChild);
  146.         close(stdoutFromChild);
  147.         close(stderrFromChild);
  148.         fclose(fpToChild);
  149.         running = NO;
  150.         done = [delegate methodFor:@selector(subprocess:done::)];
  151.         done(delegate,@selector(subprocess:done::),self,status,code);
  152.     }
  153.     return self;
  154. }
  155.  
  156. - _stdoutBuffer
  157. {
  158.     return stdoutBuffer;
  159. }
  160.  
  161. - _stderrBuffer
  162. {
  163.     return stderrBuffer;
  164. }
  165.  
  166. /*
  167.  * DPS handler for output from subprocess
  168.  */
  169.  
  170. - _fdHandler:(int)theFd method:(SEL)aSelector buffer:aString
  171. { // re-written by Carl Lindberg to make it safer.
  172.     int  bufferCount, currleft = BUFSIZ / 2;
  173.     do {
  174.         int  currentLength = [aString length];
  175.         char buf[(currleft * 2) + 1];
  176.  
  177.         currleft *=2;
  178.         bufferCount = read(theFd, buf, currleft);
  179.         if (bufferCount <= 0) {
  180.             if (currentLength > 0)
  181.                 [self flushBuffer:aString];
  182.             [self _childDidExit];
  183.             return self;
  184.         }
  185.         buf[bufferCount] = 0;
  186.         [aString cat:buf];
  187.     } while (bufferCount == currleft);
  188.     [self flushBuffer:aString];
  189.     return self;
  190. }
  191.  
  192. /*
  193.  * DPS handler for output from subprocess
  194.  */
  195. static void stdoutFdHandler(int theFd,id self)
  196. {
  197.     [self _fdHandler:theFd method:@selector(subprocess:output:)
  198.             buffer:[self _stdoutBuffer]];
  199. }
  200.  
  201. static void stderrFdHandler(int theFd,id self)
  202. {
  203.     [self _fdHandler:theFd method:@selector(subprocess:stderrOutput:)
  204.             buffer:[self _stderrBuffer]];
  205. }
  206.  
  207. - _setRunning:(BOOL)yn
  208. {
  209.     running = yn;
  210.     return self;
  211. }
  212.  
  213. /*---------------------------< INIT/FREE METHODS >---------------------------*/
  214. - init
  215. {
  216.     return [self init:NULL];
  217. }
  218.  
  219. - init:(const char *)aString
  220. {
  221.     return [self init:aString withDelegate:nil];
  222. }
  223.  
  224. - init:(const char *)aString withDelegate:theDelegate
  225. {
  226.     return [self init:aString withDelegate:theDelegate 
  227.              keepEnvironment:YES withPtys:NO];
  228. }
  229.  
  230. - init:(const char *)aString withDelegate:theDelegate
  231.         keepEnvironment:(BOOL)flag
  232. {
  233.     return [self init:aString withDelegate:theDelegate 
  234.                  keepEnvironment:YES withPtys:NO];
  235. }
  236.  
  237. - init:(const char *)aString withDelegate:theDelegate
  238.         keepEnvironment:(BOOL)flag withPtys:(BOOL)ptyFlag
  239. {
  240.     [super init];
  241.  
  242.     stdoutBuffer = [[MiscString allocFromZone:[self zone]]
  243.         initCapacity:BUFSIZ];
  244.     stderrBuffer = [[MiscString allocFromZone:[self zone]] 
  245.         initCapacity:BUFSIZ];
  246.     environment = [[MiscStringArray allocFromZone:[self zone]] init];
  247.     if (flag) {
  248.         int i;
  249.         for (i=0; environ[i]; i++)
  250.             [environment addString:environ[i]];
  251.     }
  252.     [self setDelegate:theDelegate];
  253.     if (aString) [self execute:aString withPtys:ptyFlag];
  254.     return self;
  255. }
  256.  
  257. - free
  258. {
  259.     [self terminate:self];
  260.     [stdoutBuffer free];
  261.     [stderrBuffer free];
  262.     [environment free];
  263.     return [super free];
  264. }
  265.  
  266. /*-----------------------------< OTHER METHODS >-----------------------------*/
  267. - execute:(const char *)aString
  268. { return [self execute:aString withPtys:NO];}
  269.  
  270. - execute:(const char *)aString withPtys:(BOOL)ptyFlag
  271. {
  272.     int pipeTo[2], pipeFrom[2], pipeStderr[2]; // for stderr to different fd
  273.     int tty, numFds, fd, processGroup;
  274.     char hail[BUFSIZ];
  275.  
  276.     if ([self isRunning]) return nil;
  277.     if (ptyFlag) {
  278.         tty = open("/dev/tty", O_RDWR);
  279.         getptys(&masterPty, &slavePty);
  280.         if (masterPty <= 0 || slavePty <= 0 || pipe(pipeStderr) < 0) {
  281.             [delegate perform:@selector(subprocess:error:) with:self 
  282.                     with:(void *)"Error grabbing ptys for subprocess."];
  283.             return self;
  284.         }
  285.         // remove the controlling tty if launched from a shell,
  286.         // but not Workspace;
  287.         // so that we have job control over the parent application in shell
  288.         // and so that subprocesses can be restarted in Workspace
  289.         if  ((tty < 0) && ((tty = open("/dev/tty", 2)) >= 0)) {
  290.             printf("shouldn't be here\n");
  291.             ioctl(tty, TIOCNOTTY, 0);
  292.             close(tty);
  293.         }
  294.     } else {
  295.         if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0) {
  296.             [delegate perform:@selector(subprocess:error:)
  297.                     with:self with:(void *)PIPE_ERROR];
  298.             return nil;
  299.         }
  300.     }
  301.     switch (childPid = vfork()) {
  302.         case -1:                            /* error */
  303.             [delegate perform:@selector(subprocess:error:)
  304.                     with:self with:(void *)VFORK_ERROR];
  305.             return self;
  306.             break;
  307.         case 0:                                /* child */
  308.             if (ptyFlag) {
  309.                 dup2(slavePty, 0);
  310.                 dup2(slavePty, 1);
  311.                 dup2(pipeStderr[1], 2);  //stderr
  312.             } else {
  313.                 dup2(pipeTo[0], 0);
  314.                 dup2(pipeFrom[1], 1);            /* get stdout from process */
  315.                 dup2(pipeStderr[1], 2);            /* get stderr here */
  316.             }
  317.             numFds = getdtablesize();
  318.             for (fd = 3; fd < numFds; fd++) close(fd);
  319.             processGroup = getpid();
  320.             ioctl(0, TIOCSPGRP, (char *)&processGroup);
  321.             setpgrp(0, processGroup);
  322.             fgets(hail, BUFSIZ, stdin);        // wait for parent
  323.             [self execChild:aString];
  324.             [delegate perform:@selector(subprocess:error:)
  325.                     with:self with:(void *)EXEC_ERROR];
  326.             [self error:"vfork (child) returned!"];
  327.             break;
  328.         default:                            /* parent */
  329.             if (ptyFlag) {
  330.                 close(slavePty);
  331.                 close(pipeStderr[1]);
  332.                 fpToChild = fdopen(masterPty,"w");
  333.                 stdoutFromChild = masterPty;
  334.                 stderrFromChild = pipeStderr[0];
  335.             } else {
  336.                 close(pipeTo[0]);
  337.                 close(pipeFrom[1]);
  338.                 close(pipeStderr[1]);
  339.                 fpToChild = fdopen(pipeTo[1], "w");
  340.                 stdoutFromChild = pipeFrom[0];
  341.                 fpFromChild = fdopen(pipeFrom[0],"r");
  342.                 stderrFromChild = pipeStderr[0];
  343.             }
  344.             // Set buffering method, also make it use its own buffers
  345.             setbuf(fpToChild, NULL);                    /* no buffering */
  346.             if (!ptyFlag) setbuf(fpFromChild, NULL);
  347.             DPSAddFD(stdoutFromChild, (DPSFDProc)stdoutFdHandler,
  348.                     self, NX_MODALRESPTHRESHOLD + 1);
  349.             DPSAddFD(stderrFromChild, (DPSFDProc)stderrFdHandler,
  350.                     self, NX_MODALRESPTHRESHOLD + 1);
  351.             running = YES;
  352.             fputs("Run away!  Run away!\n", fpToChild); // tell child to go
  353.             break;
  354.     }
  355.     return self;
  356. }
  357.  
  358. - execChild:(const char *)aString
  359. {
  360.     /*
  361.      * we exec a /bin/sh so that cmds are easier to specify for the user
  362.      */
  363.     execle("/bin/sh", "sh", "-c", aString, 0, [environment stringArray]);
  364.     return self;
  365. }
  366.  
  367. - setDelegate:anObject
  368. {
  369.     delegate = anObject;
  370.     return self;
  371. }
  372.  
  373. - delegate
  374. {
  375.     return delegate;
  376. }
  377.  
  378. - environment
  379. {
  380.     return environment;
  381. }
  382.  
  383. - (SEL)outputMethodForBuffer:aBuffer
  384. {
  385.     SEL aSelector;
  386.     if (aBuffer == [self _stdoutBuffer])
  387.         aSelector = @selector(subprocess:output:);
  388.     else {
  389.         if (aBuffer == [self _stderrBuffer])
  390.             aSelector = @selector(subprocess:stderrOutput:);
  391.         else aSelector = NULL;
  392.     }
  393.     return aSelector;
  394. }
  395.  
  396. - flushBuffer:aString as:aBuffer
  397. {
  398.     if ([aString strlen] > 0) {
  399.         SEL aSelector = [self outputMethodForBuffer:aBuffer];
  400.         if (aSelector)
  401.             [delegate perform:aSelector
  402.                     with:self with:(id)[aString stringValue]];
  403.         [aString setStringValue:""];
  404.     }
  405.     return nil;
  406. }
  407.  
  408. - flushBuffer:aString
  409. {
  410.     return [self flushBuffer:aString as:aString];
  411. }
  412.  
  413. - send:(const char *)string withNewline:(BOOL)wantNewline
  414. {
  415.     fputs(string,fpToChild);
  416.     if (wantNewline)
  417.         fputc('\n',fpToChild);
  418.     return self;
  419. }
  420.  
  421. - send:(const char *)string
  422. {
  423.     [self send:string withNewline:YES];
  424.     return self;
  425. }
  426.  
  427. /*
  428.  * Returns the process id of the process (and therefore the process group
  429.  * of the job)
  430.  */
  431. - (int)pid
  432. {
  433.     return childPid;
  434. }
  435.  
  436. - pause:sender
  437. {
  438.     if (!paused) {
  439.         killpg(childPid,SIGSTOP);            /* pause the process group */
  440.         paused = YES;
  441.     }
  442.     return self;
  443. }
  444.  
  445. - resume:sender
  446. {
  447.     if (paused) {
  448.         killpg(childPid,SIGCONT);            /* resume the process group */
  449.         paused = NO;
  450.     }
  451.     return self;
  452. }
  453.  
  454. - (BOOL)isPaused
  455. {
  456.     return paused;
  457. }
  458.  
  459. - (BOOL)isRunning
  460. {
  461.     return running;
  462. }
  463.  
  464. - terminate:sender
  465. {
  466.     if (running)
  467.         killpg(childPid,SIGKILL);
  468.     return self;
  469. }
  470.  
  471. /*
  472.  * effectively sends an EOF to the child process stdin
  473.  */
  474. - terminateInput
  475. {
  476.     fclose(fpToChild);
  477.     return self;
  478. }
  479.  
  480. @end
  481.  
  482. @implementation Object(MiscSubprocessDelegate)
  483.  
  484. - subprocess:sender done:(int)status :(MiscSubprocessEndCode)code
  485. {
  486.     return self;
  487. }
  488.  
  489. - subprocess:sender output:(const char *)buffer
  490. {
  491.     return self;
  492. }
  493.  
  494. - subprocess:sender stderrOutput:(const char *)buffer
  495. {
  496.     return self;
  497. }
  498.  
  499. - subprocess:sender error:(const char *)errorString
  500. {
  501.     perror(errorString);
  502.     return self;
  503. }
  504.  
  505. @end