home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / tcl / dist6.3 / tclUnixUtil.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-11-21  |  27.4 KB  |  1,009 lines

  1. /* 
  2.  * tclUnixUtil.c --
  3.  *
  4.  *    This file contains a collection of utility procedures that
  5.  *    are present in the Tcl's UNIX core but not in the generic
  6.  *    core.  For example, they do file manipulation and process
  7.  *    manipulation.
  8.  *
  9.  *    The Tcl_Fork and Tcl_WaitPids procedures are based on code
  10.  *    contributed by Karl Lehenbauer, Mark Diekhans and Peter
  11.  *    da Silva.
  12.  *
  13.  * Copyright 1991 Regents of the University of California
  14.  * Permission to use, copy, modify, and distribute this
  15.  * software and its documentation for any purpose and without
  16.  * fee is hereby granted, provided that this copyright
  17.  * notice appears in all copies.  The University of California
  18.  * makes no representations about the suitability of this
  19.  * software for any purpose.  It is provided "as is" without
  20.  * express or implied warranty.
  21.  */
  22.  
  23. #ifndef lint
  24. static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclUnixUtil.c,v 1.18 91/11/21 14:53:46 ouster Exp $ SPRITE (Berkeley)";
  25. #endif /* not lint */
  26.  
  27. #include "tclInt.h"
  28. #include "tclUnix.h"
  29.  
  30. /*
  31.  * Data structures of the following type are used by Tcl_Fork and
  32.  * Tcl_WaitPids to keep track of child processes.
  33.  */
  34.  
  35. typedef struct {
  36.     int pid;            /* Process id of child. */
  37.     WAIT_STATUS_TYPE status;    /* Status returned when child exited or
  38.                  * suspended. */
  39.     int flags;            /* Various flag bits;  see below for
  40.                  * definitions. */
  41. } WaitInfo;
  42.  
  43. /*
  44.  * Flag bits in WaitInfo structures:
  45.  *
  46.  * WI_READY -            Non-zero means process has exited or
  47.  *                suspended since it was forked or last
  48.  *                returned by Tcl_WaitPids.
  49.  * WI_DETACHED -        Non-zero means no-one cares about the
  50.  *                process anymore.  Ignore it until it
  51.  *                exits, then forget about it.
  52.  */
  53.  
  54. #define WI_READY    1
  55. #define WI_DETACHED    2
  56.  
  57. static WaitInfo *waitTable = NULL;
  58. static int waitTableSize = 0;    /* Total number of entries available in
  59.                  * waitTable. */
  60. static int waitTableUsed = 0;    /* Number of entries in waitTable that
  61.                  * are actually in use right now.  Active
  62.                  * entries are always at the beginning
  63.                  * of the table. */
  64. #define WAIT_TABLE_GROW_BY 4
  65.  
  66. /*
  67.  *----------------------------------------------------------------------
  68.  *
  69.  * Tcl_EvalFile --
  70.  *
  71.  *    Read in a file and process the entire file as one gigantic
  72.  *    Tcl command.
  73.  *
  74.  * Results:
  75.  *    A standard Tcl result, which is either the result of executing
  76.  *    the file or an error indicating why the file couldn't be read.
  77.  *
  78.  * Side effects:
  79.  *    Depends on the commands in the file.
  80.  *
  81.  *----------------------------------------------------------------------
  82.  */
  83.  
  84. int
  85. Tcl_EvalFile(interp, fileName)
  86.     Tcl_Interp *interp;        /* Interpreter in which to process file. */
  87.     char *fileName;        /* Name of file to process.  Tilde-substitution
  88.                  * will be performed on this name. */
  89. {
  90.     int fileId, result;
  91.     struct stat statBuf;
  92.     char *cmdBuffer, *end, *oldScriptFile;
  93.     Interp *iPtr = (Interp *) interp;
  94.  
  95.     oldScriptFile = iPtr->scriptFile;
  96.     iPtr->scriptFile = fileName;
  97.     fileName = Tcl_TildeSubst(interp, fileName);
  98.     if (fileName == NULL) {
  99.     goto error;
  100.     }
  101.     fileId = open(fileName, O_RDONLY, 0);
  102.     if (fileId < 0) {
  103.     Tcl_AppendResult(interp, "couldn't read file \"", fileName,
  104.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  105.     goto error;
  106.     }
  107.     if (fstat(fileId, &statBuf) == -1) {
  108.     Tcl_AppendResult(interp, "couldn't stat file \"", fileName,
  109.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  110.     close(fileId);
  111.     goto error;
  112.     }
  113.     cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
  114.     if (read(fileId, cmdBuffer, (int) statBuf.st_size) != statBuf.st_size) {
  115.     Tcl_AppendResult(interp, "error in reading file \"", fileName,
  116.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  117.     close(fileId);
  118.     goto error;
  119.     }
  120.     if (close(fileId) != 0) {
  121.     Tcl_AppendResult(interp, "error closing file \"", fileName,
  122.         "\": ", Tcl_UnixError(interp), (char *) NULL);
  123.     goto error;
  124.     }
  125.     cmdBuffer[statBuf.st_size] = 0;
  126.     result = Tcl_Eval(interp, cmdBuffer, 0, &end);
  127.     if (result == TCL_RETURN) {
  128.     result = TCL_OK;
  129.     }
  130.     if (result == TCL_ERROR) {
  131.     char msg[200];
  132.  
  133.     /*
  134.      * Record information telling where the error occurred.
  135.      */
  136.  
  137.     sprintf(msg, "\n    (file \"%.150s\" line %d)", fileName,
  138.         interp->errorLine);
  139.     Tcl_AddErrorInfo(interp, msg);
  140.     }
  141.     ckfree(cmdBuffer);
  142.     iPtr->scriptFile = oldScriptFile;
  143.     return result;
  144.  
  145.     error:
  146.     iPtr->scriptFile = oldScriptFile;
  147.     return TCL_ERROR;
  148. }
  149.  
  150. /*
  151.  *----------------------------------------------------------------------
  152.  *
  153.  * Tcl_Fork --
  154.  *
  155.  *    Create a new process using the vfork system call, and keep
  156.  *    track of it for "safe" waiting with Tcl_WaitPids.
  157.  *
  158.  * Results:
  159.  *    The return value is the value returned by the vfork system
  160.  *    call (0 means child, > 0 means parent (value is child id),
  161.  *    < 0 means error).
  162.  *
  163.  * Side effects:
  164.  *    A new process is created, and an entry is added to an internal
  165.  *    table of child processes if the process is created successfully.
  166.  *
  167.  *----------------------------------------------------------------------
  168.  */
  169.  
  170. int
  171. Tcl_Fork()
  172. {
  173.     WaitInfo *waitPtr;
  174.     pid_t pid;
  175.  
  176.     /*
  177.      * Disable SIGPIPE signals:  if they were allowed, this process
  178.      * might go away unexpectedly if children misbehave.  This code
  179.      * can potentially interfere with other application code that
  180.      * expects to handle SIGPIPEs;  what's really needed is an
  181.      * arbiter for signals to allow them to be "shared".
  182.      */
  183.  
  184.     if (waitTable == NULL) {
  185.     (void) signal(SIGPIPE, SIG_IGN);
  186.     }
  187.  
  188.     /*
  189.      * Enlarge the wait table if there isn't enough space for a new
  190.      * entry.
  191.      */
  192.  
  193.     if (waitTableUsed == waitTableSize) {
  194.     int newSize;
  195.     WaitInfo *newWaitTable;
  196.  
  197.     newSize = waitTableSize + WAIT_TABLE_GROW_BY;
  198.     newWaitTable = (WaitInfo *) ckalloc((unsigned)
  199.         (newSize * sizeof(WaitInfo)));
  200.     memcpy((VOID *) newWaitTable, (VOID *) waitTable,
  201.         (waitTableSize * sizeof(WaitInfo)));
  202.     if (waitTable != NULL) {
  203.         ckfree((char *) waitTable);
  204.     }
  205.     waitTable = newWaitTable;
  206.     waitTableSize = newSize;
  207.     }
  208.  
  209.     /*
  210.      * Make a new process and enter it into the table if the fork
  211.      * is successful.
  212.      */
  213.  
  214.     waitPtr = &waitTable[waitTableUsed];
  215.     pid = fork();
  216.     if (pid > 0) {
  217.     waitPtr->pid = pid;
  218.     waitPtr->flags = 0;
  219.     waitTableUsed++;
  220.     }
  221.     return pid;
  222. }
  223.  
  224. /*
  225.  *----------------------------------------------------------------------
  226.  *
  227.  * Tcl_WaitPids --
  228.  *
  229.  *    This procedure is used to wait for one or more processes created
  230.  *    by Tcl_Fork to exit or suspend.  It records information about
  231.  *    all processes that exit or suspend, even those not waited for,
  232.  *    so that later waits for them will be able to get the status
  233.  *    information.
  234.  *
  235.  * Results:
  236.  *    -1 is returned if there is an error in the wait kernel call.
  237.  *    Otherwise the pid of an exited/suspended process from *pidPtr
  238.  *    is returned and *statusPtr is set to the status value returned
  239.  *    by the wait kernel call.
  240.  *
  241.  * Side effects:
  242.  *    Doesn't return until one of the pids at *pidPtr exits or suspends.
  243.  *
  244.  *----------------------------------------------------------------------
  245.  */
  246.  
  247. int
  248. Tcl_WaitPids(numPids, pidPtr, statusPtr)
  249.     int numPids;        /* Number of pids to wait on:  gives size
  250.                  * of array pointed to by pidPtr. */
  251.     int *pidPtr;        /* Pids to wait on:  return when one of
  252.                  * these processes exits or suspends. */
  253.     int *statusPtr;        /* Wait status is returned here. */
  254. {
  255.     int i, count, pid;
  256.     register WaitInfo *waitPtr;
  257.     int anyProcesses;
  258.     WAIT_STATUS_TYPE status;
  259.  
  260.     while (1) {
  261.     /*
  262.      * Scan the table of child processes to see if one of the
  263.      * specified children has already exited or suspended.  If so,
  264.      * remove it from the table and return its status.
  265.      */
  266.  
  267.     anyProcesses = 0;
  268.     for (waitPtr = waitTable, count = waitTableUsed;
  269.         count > 0; waitPtr++, count--) {
  270.         for (i = 0; i < numPids; i++) {
  271.         if (pidPtr[i] != waitPtr->pid) {
  272.             continue;
  273.         }
  274.         anyProcesses = 1;
  275.         if (waitPtr->flags & WI_READY) {
  276.             *statusPtr = *((int *) &waitPtr->status);
  277.             pid = waitPtr->pid;
  278.             if (WIFEXITED(waitPtr->status)
  279.                 || WIFSIGNALED(waitPtr->status)) {
  280.             *waitPtr = waitTable[waitTableUsed-1];
  281.             waitTableUsed--;
  282.             } else {
  283.             waitPtr->flags &= ~WI_READY;
  284.             }
  285.             return pid;
  286.         }
  287.         }
  288.     }
  289.  
  290.     /*
  291.      * Make sure that the caller at least specified one valid
  292.      * process to wait for.
  293.      */
  294.  
  295.     if (!anyProcesses) {
  296.         errno = ECHILD;
  297.         return -1;
  298.     }
  299.  
  300.     /*
  301.      * Wait for a process to exit or suspend, then update its
  302.      * entry in the table and go back to the beginning of the
  303.      * loop to see if it's one of the desired processes.
  304.      */
  305.  
  306.     pid = wait(&status);
  307.     if (pid < 0) {
  308.         return pid;
  309.     }
  310.     for (waitPtr = waitTable, count = waitTableUsed; ;
  311.         waitPtr++, count--) {
  312.         if (count == 0) {
  313.         break;            /* Ignore unknown processes. */
  314.         }
  315.         if (pid != waitPtr->pid) {
  316.         continue;
  317.         }
  318.  
  319.         /*
  320.          * If the process has been detached, then ignore anything
  321.          * other than an exit, and drop the entry on exit.
  322.          */
  323.  
  324.         if (waitPtr->flags & WI_DETACHED) {
  325.         if (WIFEXITED(status) || WIFSIGNALED(status)) {
  326.             *waitPtr = waitTable[waitTableUsed-1];
  327.             waitTableUsed--;
  328.         }
  329.         } else {
  330.         waitPtr->status = status;
  331.         waitPtr->flags |= WI_READY;
  332.         }
  333.         break;
  334.     }
  335.     }
  336. }
  337.  
  338. /*
  339.  *----------------------------------------------------------------------
  340.  *
  341.  * Tcl_DetachPids --
  342.  *
  343.  *    This procedure is called to indicate that one or more child
  344.  *    processes have been placed in background and are no longer
  345.  *    cared about.  They should be ignored in future calls to
  346.  *    Tcl_WaitPids.
  347.  *
  348.  * Results:
  349.  *    None.
  350.  *
  351.  * Side effects:
  352.  *    None.
  353.  *
  354.  *----------------------------------------------------------------------
  355.  */
  356.  
  357. void
  358. Tcl_DetachPids(numPids, pidPtr)
  359.     int numPids;        /* Number of pids to detach:  gives size
  360.                  * of array pointed to by pidPtr. */
  361.     int *pidPtr;        /* Array of pids to detach:  must have
  362.                  * been created by Tcl_Fork. */
  363. {
  364.     register WaitInfo *waitPtr;
  365.     int i, count, pid;
  366.  
  367.     for (i = 0; i < numPids; i++) {
  368.     pid = pidPtr[i];
  369.     for (waitPtr = waitTable, count = waitTableUsed;
  370.         count > 0; waitPtr++, count--) {
  371.         if (pid != waitPtr->pid) {
  372.         continue;
  373.         }
  374.  
  375.         /*
  376.          * If the process has already exited then destroy its
  377.          * table entry now.
  378.          */
  379.  
  380.         if ((waitPtr->flags & WI_READY) && (WIFEXITED(waitPtr->status)
  381.             || WIFSIGNALED(waitPtr->status))) {
  382.         *waitPtr = waitTable[waitTableUsed-1];
  383.         waitTableUsed--;
  384.         } else {
  385.         waitPtr->flags |= WI_DETACHED;
  386.         }
  387.         goto nextPid;
  388.     }
  389.     panic("Tcl_Detach couldn't find process");
  390.  
  391.     nextPid:
  392.     continue;
  393.     }
  394. }
  395.  
  396. /*
  397.  *----------------------------------------------------------------------
  398.  *
  399.  * Tcl_CreatePipeline --
  400.  *
  401.  *    Given an argc/argv array, instantiate a pipeline of processes
  402.  *    as described by the argv.
  403.  *
  404.  * Results:
  405.  *    The return value is a count of the number of new processes
  406.  *    created, or -1 if an error occurred while creating the pipeline.
  407.  *    *pidArrayPtr is filled in with the address of a dynamically
  408.  *    allocated array giving the ids of all of the processes.  It
  409.  *    is up to the caller to free this array when it isn't needed
  410.  *    anymore.  If inPipePtr is non-NULL, *inPipePtr is filled in
  411.  *    with the file id for the input pipe for the pipeline (if any):
  412.  *    the caller must eventually close this file.  If outPipePtr
  413.  *    isn't NULL, then *outPipePtr is filled in with the file id
  414.  *    for the output pipe from the pipeline:  the caller must close
  415.  *    this file.  If errFilePtr isn't NULL, then *errFilePtr is filled
  416.  *    with a file id that may be used to read error output after the
  417.  *    pipeline completes.
  418.  *
  419.  * Side effects:
  420.  *    Processes and pipes are created.
  421.  *
  422.  *----------------------------------------------------------------------
  423.  */
  424.  
  425. int
  426. Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
  427.     outPipePtr, errFilePtr)
  428.     Tcl_Interp *interp;        /* Interpreter to use for error reporting. */
  429.     int argc;            /* Number of entries in argv. */
  430.     char **argv;        /* Array of strings describing commands in
  431.                  * pipeline plus I/O redirection with <,
  432.                  * <<, and >.  Argv[argc] must be NULL. */
  433.     int **pidArrayPtr;        /* Word at *pidArrayPtr gets filled in with
  434.                  * address of array of pids for processes
  435.                  * in pipeline (first pid is first process
  436.                  * in pipeline). */
  437.     int *inPipePtr;        /* If non-NULL, input to the pipeline comes
  438.                  * from a pipe (unless overridden by
  439.                  * redirection in the command).  The file
  440.                  * id with which to write to this pipe is
  441.                  * stored at *inPipePtr.  -1 means command
  442.                  * specified its own input source. */
  443.     int *outPipePtr;        /* If non-NULL, output to the pipeline goes
  444.                  * to a pipe, unless overriden by redirection
  445.                  * in the command.  The file id with which to
  446.                  * read frome this pipe is stored at
  447.                  * *outPipePtr.  -1 means command specified
  448.                  * its own output sink. */
  449.     int *errFilePtr;        /* If non-NULL, all stderr output from the
  450.                  * pipeline will go to a temporary file
  451.                  * created here, and a descriptor to read
  452.                  * the file will be left at *errFilePtr.
  453.                  * The file will be removed already, so
  454.                  * closing this descriptor will be the end
  455.                  * of the file.  If this is NULL, then
  456.                  * all stderr output goes to our stderr. */
  457. {
  458.     int *pidPtr = NULL;        /* Points to malloc-ed array holding all
  459.                  * the pids of child processes. */
  460.     int numPids = 0;        /* Actual number of processes that exist
  461.                  * at *pidPtr right now. */
  462.     int cmdCount;        /* Count of number of distinct commands
  463.                  * found in argc/argv. */
  464.     char *input = NULL;        /* Describes input for pipeline, depending
  465.                  * on "inputFile".  NULL means take input
  466.                  * from stdin/pipe. */
  467.     int inputFile = 0;        /* Non-zero means input is name of input
  468.                  * file.  Zero means input holds actual
  469.                  * text to be input to command. */
  470.     char *output = NULL;    /* Holds name of output file to pipe to,
  471.                  * or NULL if output goes to stdout/pipe. */
  472.     int inputId = -1;        /* Readable file id input to current command in
  473.                  * pipeline (could be file or pipe).  -1
  474.                  * means use stdin. */
  475.     int outputId = -1;        /* Writable file id for output from current
  476.                  * command in pipeline (could be file or pipe).
  477.                  * -1 means use stdout. */
  478.     int errorId = -1;        /* Writable file id for all standard error
  479.                  * output from all commands in pipeline.  -1
  480.                  * means use stderr. */
  481.     int lastOutputId = -1;    /* Write file id for output from last command
  482.                  * in pipeline (could be file or pipe).
  483.                  * -1 means use stdout. */
  484.     int pipeIds[2];        /* File ids for pipe that's being created. */
  485.     int firstArg, lastArg;    /* Indexes of first and last arguments in
  486.                  * current command. */
  487.     int lastBar;
  488.     char *execName;
  489.     int i, j, pid;
  490.  
  491.     if (inPipePtr != NULL) {
  492.     *inPipePtr = -1;
  493.     }
  494.     if (outPipePtr != NULL) {
  495.     *outPipePtr = -1;
  496.     }
  497.     if (errFilePtr != NULL) {
  498.     *errFilePtr = -1;
  499.     }
  500.     pipeIds[0] = pipeIds[1] = -1;
  501.  
  502.     /*
  503.      * First, scan through all the arguments to figure out the structure
  504.      * of the pipeline.  Count the number of distinct processes (it's the
  505.      * number of "|" arguments).  If there are "<", "<<", or ">" arguments
  506.      * then make note of input and output redirection and remove these
  507.      * arguments and the arguments that follow them.
  508.      */
  509.  
  510.     cmdCount = 1;
  511.     lastBar = -1;
  512.     for (i = 0; i < argc; i++) {
  513.     if ((argv[i][0] == '|') && ((argv[i][1] == 0))) {
  514.         if ((i == (lastBar+1)) || (i == (argc-1))) {
  515.         interp->result = "illegal use of | in command";
  516.         return -1;
  517.         }
  518.         lastBar = i;
  519.         cmdCount++;
  520.         continue;
  521.     } else if (argv[i][0] == '<') {
  522.         if (argv[i][1] == 0) {
  523.         input = argv[i+1];
  524.         inputFile = 1;
  525.         } else if ((argv[i][1] == '<') && (argv[i][2] == 0)) {
  526.         input = argv[i+1];
  527.         inputFile = 0;
  528.         } else {
  529.         continue;
  530.         }
  531.     } else if ((argv[i][0] == '>') && (argv[i][1] == 0)) {
  532.         output = argv[i+1];
  533.     } else {
  534.         continue;
  535.     }
  536.     if (i >= (argc-1)) {
  537.         Tcl_AppendResult(interp, "can't specify \"", argv[i],
  538.             "\" as last word in command", (char *) NULL);
  539.         return -1;
  540.     }
  541.     for (j = i+2; j < argc; j++) {
  542.         argv[j-2] = argv[j];
  543.     }
  544.     argc -= 2;
  545.     i--;            /* Process new arg from same position. */
  546.     }
  547.     if (argc == 0) {
  548.     interp->result =  "didn't specify command to execute";
  549.     return -1;
  550.     }
  551.  
  552.     /*
  553.      * Set up the redirected input source for the pipeline, if
  554.      * so requested.
  555.      */
  556.  
  557.     if (input != NULL) {
  558.     if (!inputFile) {
  559.         /*
  560.          * Immediate data in command.  Create temporary file and
  561.          * put data into file.
  562.          */
  563.  
  564. #        define TMP_STDIN_NAME "/tmp/tcl.in.XXXXXX"
  565.         char inName[sizeof(TMP_STDIN_NAME) + 1];
  566.         int length;
  567.  
  568.         strcpy(inName, TMP_STDIN_NAME);
  569.         mktemp(inName);
  570.         inputId = open(inName, O_RDWR|O_CREAT|O_TRUNC, 0600);
  571.         if (inputId < 0) {
  572.         Tcl_AppendResult(interp,
  573.             "couldn't create input file for command: ",
  574.             Tcl_UnixError(interp), (char *) NULL);
  575.         goto error;
  576.         }
  577.         length = strlen(input);
  578.         if (write(inputId, input, length) != length) {
  579.         Tcl_AppendResult(interp,
  580.             "couldn't write file input for command: ",
  581.             Tcl_UnixError(interp), (char *) NULL);
  582.         goto error;
  583.         }
  584.         if ((lseek(inputId, 0L, 0) == -1) || (unlink(inName) == -1)) {
  585.         Tcl_AppendResult(interp,
  586.             "couldn't reset or remove input file for command: ",
  587.             Tcl_UnixError(interp), (char *) NULL);
  588.         goto error;
  589.         }
  590.     } else {
  591.         /*
  592.          * File redirection.  Just open the file.
  593.          */
  594.  
  595.         inputId = open(input, O_RDONLY, 0);
  596.         if (inputId < 0) {
  597.         Tcl_AppendResult(interp,
  598.             "couldn't read file \"", input, "\": ",
  599.             Tcl_UnixError(interp), (char *) NULL);
  600.         goto error;
  601.         }
  602.     }
  603.     } else if (inPipePtr != NULL) {
  604.     if (pipe(pipeIds) != 0) {
  605.         Tcl_AppendResult(interp,
  606.             "couldn't create input pipe for command: ",
  607.             Tcl_UnixError(interp), (char *) NULL);
  608.         goto error;
  609.     }
  610.     inputId = pipeIds[0];
  611.     *inPipePtr = pipeIds[1];
  612.     pipeIds[0] = pipeIds[1] = -1;
  613.     }
  614.  
  615.     /*
  616.      * Set up the redirected output sink for the pipeline from one
  617.      * of two places, if requested.
  618.      */
  619.  
  620.     if (output != NULL) {
  621.     /*
  622.      * Output is to go to a file.
  623.      */
  624.  
  625.     lastOutputId = open(output, O_WRONLY|O_CREAT|O_TRUNC, 0666);
  626.     if (lastOutputId < 0) {
  627.         Tcl_AppendResult(interp,
  628.             "couldn't write file \"", output, "\": ",
  629.             Tcl_UnixError(interp), (char *) NULL);
  630.         goto error;
  631.     }
  632.     } else if (outPipePtr != NULL) {
  633.     /*
  634.      * Output is to go to a pipe.
  635.      */
  636.  
  637.     if (pipe(pipeIds) != 0) {
  638.         Tcl_AppendResult(interp,
  639.             "couldn't create output pipe: ",
  640.             Tcl_UnixError(interp), (char *) NULL);
  641.         goto error;
  642.     }
  643.     lastOutputId = pipeIds[1];
  644.     *outPipePtr = pipeIds[0];
  645.     pipeIds[0] = pipeIds[1] = -1;
  646.     }
  647.  
  648.     /*
  649.      * Set up the standard error output sink for the pipeline, if
  650.      * requested.  Use a temporary file which is opened, then deleted.
  651.      * Could potentially just use pipe, but if it filled up it could
  652.      * cause the pipeline to deadlock:  we'd be waiting for processes
  653.      * to complete before reading stderr, and processes couldn't complete
  654.      * because stderr was backed up.
  655.      */
  656.  
  657.     if (errFilePtr != NULL) {
  658. #    define TMP_STDERR_NAME "/tmp/tcl.err.XXXXXX"
  659.     char errName[sizeof(TMP_STDERR_NAME) + 1];
  660.  
  661.     strcpy(errName, TMP_STDERR_NAME);
  662.     mktemp(errName);
  663.     errorId = open(errName, O_WRONLY|O_CREAT|O_TRUNC, 0600);
  664.     if (errorId < 0) {
  665.         errFileError:
  666.         Tcl_AppendResult(interp,
  667.             "couldn't create error file for command: ",
  668.             Tcl_UnixError(interp), (char *) NULL);
  669.         goto error;
  670.     }
  671.     *errFilePtr = open(errName, O_RDONLY, 0);
  672.     if (*errFilePtr < 0) {
  673.         goto errFileError;
  674.     }
  675.     if (unlink(errName) == -1) {
  676.         Tcl_AppendResult(interp,
  677.             "couldn't remove error file for command: ",
  678.             Tcl_UnixError(interp), (char *) NULL);
  679.         goto error;
  680.     }
  681.     }
  682.  
  683.     /*
  684.      * Scan through the argc array, forking off a process for each
  685.      * group of arguments between "|" arguments.
  686.      */
  687.  
  688.     pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int)));
  689.     for (i = 0; i < numPids; i++) {
  690.     pidPtr[i] = -1;
  691.     }
  692.     for (firstArg = 0; firstArg < argc; numPids++, firstArg = lastArg+1) {
  693.     for (lastArg = firstArg; lastArg < argc; lastArg++) {
  694.         if ((argv[lastArg][0] == '|') && (argv[lastArg][1] == 0)) {
  695.         break;
  696.         }
  697.     }
  698.     argv[lastArg] = NULL;
  699.     if (lastArg == argc) {
  700.         outputId = lastOutputId;
  701.     } else {
  702.         if (pipe(pipeIds) != 0) {
  703.         Tcl_AppendResult(interp, "couldn't create pipe: ",
  704.             Tcl_UnixError(interp), (char *) NULL);
  705.         goto error;
  706.         }
  707.         outputId = pipeIds[1];
  708.     }
  709.     execName = Tcl_TildeSubst(interp, argv[firstArg]);
  710.     pid = Tcl_Fork();
  711.     if (pid == -1) {
  712.         Tcl_AppendResult(interp, "couldn't fork child process: ",
  713.             Tcl_UnixError(interp), (char *) NULL);
  714.         goto error;
  715.     }
  716.     if (pid == 0) {
  717.         char errSpace[200];
  718.  
  719.         if (((inputId != -1) && (dup2(inputId, 0) == -1))
  720.             || ((outputId != -1) && (dup2(outputId, 1) == -1))
  721.             || ((errorId != -1) && (dup2(errorId, 2) == -1))) {
  722.         char *err;
  723.         err = "forked process couldn't set up input/output\n";
  724.         write(errorId < 0 ? 2 : errorId, err, strlen(err));
  725.         _exit(1);
  726.         }
  727.         for (i = 3; (i <= outputId) || (i <= inputId) || (i <= errorId);
  728.             i++) {
  729.         close(i);
  730.         }
  731.         execvp(execName, &argv[firstArg]);
  732.         sprintf(errSpace, "couldn't find \"%.150s\" to execute\n",
  733.             argv[firstArg]);
  734.         write(2, errSpace, strlen(errSpace));
  735.         _exit(1);
  736.     } else {
  737.         pidPtr[numPids] = pid;
  738.     }
  739.  
  740.     /*
  741.      * Close off our copies of file descriptors that were set up for
  742.      * this child, then set up the input for the next child.
  743.      */
  744.  
  745.     if (inputId != -1) {
  746.         close(inputId);
  747.     }
  748.     if (outputId != -1) {
  749.         close(outputId);
  750.     }
  751.     inputId = pipeIds[0];
  752.     pipeIds[0] = pipeIds[1] = -1;
  753.     }
  754.     *pidArrayPtr = pidPtr;
  755.  
  756.     /*
  757.      * All done.  Cleanup open files lying around and then return.
  758.      */
  759.  
  760. cleanup:
  761.     if (inputId != -1) {
  762.     close(inputId);
  763.     }
  764.     if (lastOutputId != -1) {
  765.     close(lastOutputId);
  766.     }
  767.     if (errorId != -1) {
  768.     close(errorId);
  769.     }
  770.     return numPids;
  771.  
  772.     /*
  773.      * An error occurred.  There could have been extra files open, such
  774.      * as pipes between children.  Clean them all up.  Detach any child
  775.      * processes that have been created.
  776.      */
  777.  
  778.     error:
  779.     if ((inPipePtr != NULL) && (*inPipePtr != -1)) {
  780.     close(*inPipePtr);
  781.     *inPipePtr = -1;
  782.     }
  783.     if ((outPipePtr != NULL) && (*outPipePtr != -1)) {
  784.     close(*outPipePtr);
  785.     *outPipePtr = -1;
  786.     }
  787.     if ((errFilePtr != NULL) && (*errFilePtr != -1)) {
  788.     close(*errFilePtr);
  789.     *errFilePtr = -1;
  790.     }
  791.     if (pipeIds[0] != -1) {
  792.     close(pipeIds[0]);
  793.     }
  794.     if (pipeIds[1] != -1) {
  795.     close(pipeIds[1]);
  796.     }
  797.     if (pidPtr != NULL) {
  798.     for (i = 0; i < numPids; i++) {
  799.         if (pidPtr[i] != -1) {
  800.         Tcl_DetachPids(1, &pidPtr[i]);
  801.         }
  802.     }
  803.     ckfree((char *) pidPtr);
  804.     }
  805.     numPids = -1;
  806.     goto cleanup;
  807. }
  808.  
  809. /*
  810.  *----------------------------------------------------------------------
  811.  *
  812.  * Tcl_UnixError --
  813.  *
  814.  *    This procedure is typically called after UNIX kernel calls
  815.  *    return errors.  It stores machine-readable information about
  816.  *    the error in $errorCode returns an information string for
  817.  *    the caller's use.
  818.  *
  819.  * Results:
  820.  *    The return value is a human-readable string describing the
  821.  *    error, as returned by strerror.
  822.  *
  823.  * Side effects:
  824.  *    The global variable $errorCode is reset.
  825.  *
  826.  *----------------------------------------------------------------------
  827.  */
  828.  
  829. char *
  830. Tcl_UnixError(interp)
  831.     Tcl_Interp *interp;        /* Interpreter whose $errorCode variable
  832.                  * is to be changed. */
  833. {
  834.     char *id, *msg;
  835.  
  836.     id = Tcl_ErrnoId();
  837.     msg = strerror(errno);
  838.     Tcl_SetErrorCode(interp, "UNIX", id, msg, (char *) NULL);
  839.     return msg;
  840. }
  841.  
  842. /*
  843.  *----------------------------------------------------------------------
  844.  *
  845.  * TclMakeFileTable --
  846.  *
  847.  *    Create or enlarge the file table for the interpreter, so that
  848.  *    there is room for a given index.
  849.  *
  850.  * Results:
  851.  *    None.
  852.  *
  853.  * Side effects:
  854.  *    The file table for iPtr will be created if it doesn't exist
  855.  *    (and entries will be added for stdin, stdout, and stderr).
  856.  *    If it already exists, then it will be grown if necessary.
  857.  *
  858.  *----------------------------------------------------------------------
  859.  */
  860.  
  861. void
  862. TclMakeFileTable(iPtr, index)
  863.     Interp *iPtr;        /* Interpreter whose table of files is
  864.                  * to be manipulated. */
  865.     int index;            /* Make sure table is large enough to
  866.                  * hold at least this index. */
  867. {
  868.     /*
  869.      * If the table doesn't even exist, then create it and initialize
  870.      * entries for standard files.
  871.      */
  872.  
  873.     if (iPtr->numFiles == 0) {
  874.     OpenFile *filePtr;
  875.     int i;
  876.  
  877.     if (index < 2) {
  878.         iPtr->numFiles = 3;
  879.     } else {
  880.         iPtr->numFiles = index+1;
  881.     }
  882.     iPtr->filePtrArray = (OpenFile **) ckalloc((unsigned)
  883.         ((iPtr->numFiles)*sizeof(OpenFile *)));
  884.     for (i = iPtr->numFiles-1; i >= 0; i--) {
  885.         iPtr->filePtrArray[i] = NULL;
  886.     }
  887.  
  888.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  889.     filePtr->f = stdin;
  890.     filePtr->f2 = NULL;
  891.     filePtr->readable = 1;
  892.     filePtr->writable = 0;
  893.     filePtr->numPids = 0;
  894.     filePtr->pidPtr = NULL;
  895.     filePtr->errorId = -1;
  896.     iPtr->filePtrArray[0] = filePtr;
  897.  
  898.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  899.     filePtr->f = stdout;
  900.     filePtr->f2 = NULL;
  901.     filePtr->readable = 0;
  902.     filePtr->writable = 1;
  903.     filePtr->numPids = 0;
  904.     filePtr->pidPtr = NULL;
  905.     filePtr->errorId = -1;
  906.     iPtr->filePtrArray[1] = filePtr;
  907.  
  908.     filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
  909.     filePtr->f = stderr;
  910.     filePtr->f2 = NULL;
  911.     filePtr->readable = 0;
  912.     filePtr->writable = 1;
  913.     filePtr->numPids = 0;
  914.     filePtr->pidPtr = NULL;
  915.     filePtr->errorId = -1;
  916.     iPtr->filePtrArray[2] = filePtr;
  917.     } else if (index >= iPtr->numFiles) {
  918.     int newSize;
  919.     OpenFile **newPtrArray;
  920.     int i;
  921.  
  922.     newSize = index+1;
  923.     newPtrArray = (OpenFile **) ckalloc((unsigned)
  924.         ((newSize)*sizeof(OpenFile *)));
  925.     memcpy((VOID *) newPtrArray, (VOID *) iPtr->filePtrArray,
  926.         iPtr->numFiles*sizeof(OpenFile *));
  927.     for (i = iPtr->numFiles; i < newSize; i++) {
  928.         newPtrArray[i] = NULL;
  929.     }
  930.     ckfree((char *) iPtr->filePtrArray);
  931.     iPtr->numFiles = newSize;
  932.     iPtr->filePtrArray = newPtrArray;
  933.     }
  934. }
  935.  
  936. /*
  937.  *----------------------------------------------------------------------
  938.  *
  939.  * TclGetOpenFile --
  940.  *
  941.  *    Given a string identifier for an open file, find the corresponding
  942.  *    open file structure, if there is one.
  943.  *
  944.  * Results:
  945.  *    A standard Tcl return value.  If the open file is successfully
  946.  *    located, *filePtrPtr is modified to point to its structure.
  947.  *    If TCL_ERROR is returned then interp->result contains an error
  948.  *    message.
  949.  *
  950.  * Side effects:
  951.  *    None.
  952.  *
  953.  *----------------------------------------------------------------------
  954.  */
  955.  
  956. int
  957. TclGetOpenFile(interp, string, filePtrPtr)
  958.     Tcl_Interp *interp;        /* Interpreter in which to find file. */
  959.     char *string;        /* String that identifies file. */
  960.     OpenFile **filePtrPtr;    /* Address of word in which to store pointer
  961.                  * to structure about open file. */
  962. {
  963.     int fd = 0;            /* Initial value needed only to stop compiler
  964.                  * warnings. */
  965.     Interp *iPtr = (Interp *) interp;
  966.  
  967.     if ((string[0] == 'f') && (string[1] == 'i') && (string[2] == 'l')
  968.         & (string[3] == 'e')) {
  969.     char *end;
  970.  
  971.     fd = strtoul(string+4, &end, 10);
  972.     if ((end == string+4) || (*end != 0)) {
  973.         goto badId;
  974.     }
  975.     } else if ((string[0] == 's') && (string[1] == 't')
  976.         && (string[2] == 'd')) {
  977.     if (strcmp(string+3, "in") == 0) {
  978.         fd = 0;
  979.     } else if (strcmp(string+3, "out") == 0) {
  980.         fd = 1;
  981.     } else if (strcmp(string+3, "err") == 0) {
  982.         fd = 2;
  983.     } else {
  984.         goto badId;
  985.     }
  986.     } else {
  987.     badId:
  988.     Tcl_AppendResult(interp, "bad file identifier \"", string,
  989.         "\"", (char *) NULL);
  990.     return TCL_ERROR;
  991.     }
  992.  
  993.     if (fd >= iPtr->numFiles) {
  994.     if ((iPtr->numFiles == 0) && (fd <= 2)) {
  995.         TclMakeFileTable(iPtr, fd);
  996.     } else {
  997.         notOpen:
  998.         Tcl_AppendResult(interp, "file \"", string, "\" isn't open",
  999.             (char *) NULL);
  1000.         return TCL_ERROR;
  1001.     }
  1002.     }
  1003.     if (iPtr->filePtrArray[fd] == NULL) {
  1004.     goto notOpen;
  1005.     }
  1006.     *filePtrPtr = iPtr->filePtrArray[fd];
  1007.     return TCL_OK;
  1008. }
  1009.