tclPipe.c
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:31k
源码类别:

通讯编程

开发平台:

Visual C++

  1. /* 
  2.  * tclPipe.c --
  3.  *
  4.  * This file contains the generic portion of the command channel
  5.  * driver as well as various utility routines used in managing
  6.  * subprocesses.
  7.  *
  8.  * Copyright (c) 1997 by Sun Microsystems, Inc.
  9.  *
  10.  * See the file "license.terms" for information on usage and redistribution
  11.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  12.  *
  13.  * RCS: @(#) $Id: tclPipe.c,v 1.7.2.5 2006/03/16 00:35:58 andreas_kupries Exp $
  14.  */
  15. #include "tclInt.h"
  16. #include "tclPort.h"
  17. /*
  18.  * A linked list of the following structures is used to keep track
  19.  * of child processes that have been detached but haven't exited
  20.  * yet, so we can make sure that they're properly "reaped" (officially
  21.  * waited for) and don't lie around as zombies cluttering the
  22.  * system.
  23.  */
  24. typedef struct Detached {
  25.     Tcl_Pid pid; /* Id of process that's been detached
  26.  * but isn't known to have exited. */
  27.     struct Detached *nextPtr; /* Next in list of all detached
  28.  * processes. */
  29. } Detached;
  30. static Detached *detList = NULL; /* List of all detached proceses. */
  31. TCL_DECLARE_MUTEX(pipeMutex) /* Guard access to detList. */
  32. /*
  33.  * Declarations for local procedures defined in this file:
  34.  */
  35. static TclFile FileForRedirect _ANSI_ARGS_((Tcl_Interp *interp,
  36.             CONST char *spec, int atOk, CONST char *arg, 
  37.     CONST char *nextArg, int flags, int *skipPtr,
  38.     int *closePtr, int *releasePtr));
  39. /*
  40.  *----------------------------------------------------------------------
  41.  *
  42.  * FileForRedirect --
  43.  *
  44.  * This procedure does much of the work of parsing redirection
  45.  * operators.  It handles "@" if specified and allowed, and a file
  46.  * name, and opens the file if necessary.
  47.  *
  48.  * Results:
  49.  * The return value is the descriptor number for the file.  If an
  50.  * error occurs then NULL is returned and an error message is left
  51.  * in the interp's result.  Several arguments are side-effected; see
  52.  * the argument list below for details.
  53.  *
  54.  * Side effects:
  55.  * None.
  56.  *
  57.  *----------------------------------------------------------------------
  58.  */
  59. static TclFile
  60. FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,
  61. releasePtr)
  62.     Tcl_Interp *interp; /* Intepreter to use for error reporting. */
  63.     CONST char *spec; /* Points to character just after
  64.  * redirection character. */
  65.     int atOK; /* Non-zero means that '@' notation can be 
  66.  * used to specify a channel, zero means that
  67.  * it isn't. */
  68.     CONST char *arg; /* Pointer to entire argument containing 
  69.  * spec:  used for error reporting. */
  70.     CONST char *nextArg; /* Next argument in argc/argv array, if needed 
  71.  * for file name or channel name.  May be 
  72.  * NULL. */
  73.     int flags; /* Flags to use for opening file or to 
  74.  * specify mode for channel. */
  75.     int *skipPtr; /* Filled with 1 if redirection target was
  76.  * in spec, 2 if it was in nextArg. */
  77.     int *closePtr; /* Filled with one if the caller should 
  78.  * close the file when done with it, zero
  79.  * otherwise. */
  80.     int *releasePtr;
  81. {
  82.     int writing = (flags & O_WRONLY);
  83.     Tcl_Channel chan;
  84.     TclFile file;
  85.     *skipPtr = 1;
  86.     if ((atOK != 0)  && (*spec == '@')) {
  87. spec++;
  88. if (*spec == '') {
  89.     spec = nextArg;
  90.     if (spec == NULL) {
  91. goto badLastArg;
  92.     }
  93.     *skipPtr = 2;
  94. }
  95.         chan = Tcl_GetChannel(interp, spec, NULL);
  96.         if (chan == (Tcl_Channel) NULL) {
  97.             return NULL;
  98.         }
  99. file = TclpMakeFile(chan, writing ? TCL_WRITABLE : TCL_READABLE);
  100.         if (file == NULL) {
  101.     Tcl_AppendResult(interp, "channel "", Tcl_GetChannelName(chan),
  102.     "" wasn't opened for ",
  103.     ((writing) ? "writing" : "reading"), (char *) NULL);
  104.             return NULL;
  105.         }
  106. *releasePtr = 1;
  107. if (writing) {
  108.     /*
  109.      * Be sure to flush output to the file, so that anything
  110.      * written by the child appears after stuff we've already
  111.      * written.
  112.      */
  113.             Tcl_Flush(chan);
  114. }
  115.     } else {
  116. CONST char *name;
  117. Tcl_DString nameString;
  118. if (*spec == '') {
  119.     spec = nextArg;
  120.     if (spec == NULL) {
  121. goto badLastArg;
  122.     }
  123.     *skipPtr = 2;
  124. }
  125. name = Tcl_TranslateFileName(interp, spec, &nameString);
  126. if (name == NULL) {
  127.     return NULL;
  128. }
  129. file = TclpOpenFile(name, flags);
  130. Tcl_DStringFree(&nameString);
  131. if (file == NULL) {
  132.     Tcl_AppendResult(interp, "couldn't ",
  133.     ((writing) ? "write" : "read"), " file "", spec, "": ",
  134.     Tcl_PosixError(interp), (char *) NULL);
  135.     return NULL;
  136. }
  137.         *closePtr = 1;
  138.     }
  139.     return file;
  140.     badLastArg:
  141.     Tcl_AppendResult(interp, "can't specify "", arg,
  142.     "" as last word in command", (char *) NULL);
  143.     return NULL;
  144. }
  145. /*
  146.  *----------------------------------------------------------------------
  147.  *
  148.  * Tcl_DetachPids --
  149.  *
  150.  * This procedure is called to indicate that one or more child
  151.  * processes have been placed in background and will never be
  152.  * waited for;  they should eventually be reaped by
  153.  * Tcl_ReapDetachedProcs.
  154.  *
  155.  * Results:
  156.  * None.
  157.  *
  158.  * Side effects:
  159.  * None.
  160.  *
  161.  *----------------------------------------------------------------------
  162.  */
  163. void
  164. Tcl_DetachPids(numPids, pidPtr)
  165.     int numPids; /* Number of pids to detach:  gives size
  166.  * of array pointed to by pidPtr. */
  167.     Tcl_Pid *pidPtr; /* Array of pids to detach. */
  168. {
  169.     register Detached *detPtr;
  170.     int i;
  171.     Tcl_MutexLock(&pipeMutex);
  172.     for (i = 0; i < numPids; i++) {
  173. detPtr = (Detached *) ckalloc(sizeof(Detached));
  174. detPtr->pid = pidPtr[i];
  175. detPtr->nextPtr = detList;
  176. detList = detPtr;
  177.     }
  178.     Tcl_MutexUnlock(&pipeMutex);
  179. }
  180. /*
  181.  *----------------------------------------------------------------------
  182.  *
  183.  * Tcl_ReapDetachedProcs --
  184.  *
  185.  * This procedure checks to see if any detached processes have
  186.  * exited and, if so, it "reaps" them by officially waiting on
  187.  * them.  It should be called "occasionally" to make sure that
  188.  * all detached processes are eventually reaped.
  189.  *
  190.  * Results:
  191.  * None.
  192.  *
  193.  * Side effects:
  194.  * Processes are waited on, so that they can be reaped by the
  195.  * system.
  196.  *
  197.  *----------------------------------------------------------------------
  198.  */
  199. void
  200. Tcl_ReapDetachedProcs()
  201. {
  202.     register Detached *detPtr;
  203.     Detached *nextPtr, *prevPtr;
  204.     int status;
  205.     Tcl_Pid pid;
  206.     Tcl_MutexLock(&pipeMutex);
  207.     for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) {
  208. pid = Tcl_WaitPid(detPtr->pid, &status, WNOHANG);
  209. if ((pid == 0) || ((pid == (Tcl_Pid) -1) && (errno != ECHILD))) {
  210.     prevPtr = detPtr;
  211.     detPtr = detPtr->nextPtr;
  212.     continue;
  213. }
  214. nextPtr = detPtr->nextPtr;
  215. if (prevPtr == NULL) {
  216.     detList = detPtr->nextPtr;
  217. } else {
  218.     prevPtr->nextPtr = detPtr->nextPtr;
  219. }
  220. ckfree((char *) detPtr);
  221. detPtr = nextPtr;
  222.     }
  223.     Tcl_MutexUnlock(&pipeMutex);
  224. }
  225. /*
  226.  *----------------------------------------------------------------------
  227.  *
  228.  * TclCleanupChildren --
  229.  *
  230.  * This is a utility procedure used to wait for child processes
  231.  * to exit, record information about abnormal exits, and then
  232.  * collect any stderr output generated by them.
  233.  *
  234.  * Results:
  235.  * The return value is a standard Tcl result.  If anything at
  236.  * weird happened with the child processes, TCL_ERROR is returned
  237.  * and a message is left in the interp's result.
  238.  *
  239.  * Side effects:
  240.  * If the last character of the interp's result is a newline, then it
  241.  * is removed unless keepNewline is non-zero.  File errorId gets
  242.  * closed, and pidPtr is freed back to the storage allocator.
  243.  *
  244.  *----------------------------------------------------------------------
  245.  */
  246. int
  247. TclCleanupChildren(interp, numPids, pidPtr, errorChan)
  248.     Tcl_Interp *interp; /* Used for error messages. */
  249.     int numPids; /* Number of entries in pidPtr array. */
  250.     Tcl_Pid *pidPtr; /* Array of process ids of children. */
  251.     Tcl_Channel errorChan; /* Channel for file containing stderr output
  252.  * from pipeline.  NULL means there isn't any
  253.  * stderr output. */
  254. {
  255.     int result = TCL_OK;
  256.     int i, abnormalExit, anyErrorInfo;
  257.     Tcl_Pid pid;
  258.     WAIT_STATUS_TYPE waitStatus;
  259.     CONST char *msg;
  260.     unsigned long resolvedPid;
  261.     abnormalExit = 0;
  262.     for (i = 0; i < numPids; i++) {
  263. /*
  264.  * We need to get the resolved pid before we wait on it as
  265.  * the windows implimentation of Tcl_WaitPid deletes the
  266.  * information such that any following calls to TclpGetPid
  267.  * fail.
  268.  */
  269. resolvedPid = TclpGetPid(pidPtr[i]);
  270.         pid = Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0);
  271. if (pid == (Tcl_Pid) -1) {
  272.     result = TCL_ERROR;
  273.             if (interp != (Tcl_Interp *) NULL) {
  274.                 msg = Tcl_PosixError(interp);
  275.                 if (errno == ECHILD) {
  276.     /*
  277.                      * This changeup in message suggested by Mark Diekhans
  278.                      * to remind people that ECHILD errors can occur on
  279.                      * some systems if SIGCHLD isn't in its default state.
  280.                      */
  281.                     msg =
  282.                         "child process lost (is SIGCHLD ignored or trapped?)";
  283.                 }
  284.                 Tcl_AppendResult(interp, "error waiting for process to exit: ",
  285.                         msg, (char *) NULL);
  286.             }
  287.     continue;
  288. }
  289. /*
  290.  * Create error messages for unusual process exits.  An
  291.  * extra newline gets appended to each error message, but
  292.  * it gets removed below (in the same fashion that an
  293.  * extra newline in the command's output is removed).
  294.  */
  295. if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
  296.     char msg1[TCL_INTEGER_SPACE], msg2[TCL_INTEGER_SPACE];
  297.     result = TCL_ERROR;
  298.     TclFormatInt(msg1, (long) resolvedPid);
  299.     if (WIFEXITED(waitStatus)) {
  300.                 if (interp != (Tcl_Interp *) NULL) {
  301.     TclFormatInt(msg2, WEXITSTATUS(waitStatus));
  302.                     Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2,
  303.                             (char *) NULL);
  304.                 }
  305. abnormalExit = 1;
  306.     } else if (WIFSIGNALED(waitStatus)) {
  307.                 if (interp != (Tcl_Interp *) NULL) {
  308.                     CONST char *p;
  309.                     
  310.                     p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
  311.                     Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
  312.                             Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
  313.                             (char *) NULL);
  314.                     Tcl_AppendResult(interp, "child killed: ", p, "n",
  315.                             (char *) NULL);
  316.                 }
  317.     } else if (WIFSTOPPED(waitStatus)) {
  318.                 if (interp != (Tcl_Interp *) NULL) {
  319.                     CONST char *p;
  320.                     p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
  321.                     Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
  322.                             Tcl_SignalId((int) (WSTOPSIG(waitStatus))),
  323.                             p, (char *) NULL);
  324.                     Tcl_AppendResult(interp, "child suspended: ", p, "n",
  325.                             (char *) NULL);
  326.                 }
  327.     } else {
  328.                 if (interp != (Tcl_Interp *) NULL) {
  329.                     Tcl_AppendResult(interp,
  330.                             "child wait status didn't make sensen",
  331.                             (char *) NULL);
  332.                 }
  333.     }
  334. }
  335.     }
  336.     /*
  337.      * Read the standard error file.  If there's anything there,
  338.      * then return an error and add the file's contents to the result
  339.      * string.
  340.      */
  341.     anyErrorInfo = 0;
  342.     if (errorChan != NULL) {
  343. /*
  344.  * Make sure we start at the beginning of the file.
  345.  */
  346.         if (interp != NULL) {
  347.     int count;
  348.     Tcl_Obj *objPtr;
  349.     
  350.     Tcl_Seek(errorChan, (Tcl_WideInt)0, SEEK_SET);
  351.     objPtr = Tcl_NewObj();
  352.     count = Tcl_ReadChars(errorChan, objPtr, -1, 0);
  353.     if (count < 0) {
  354. result = TCL_ERROR;
  355. Tcl_DecrRefCount(objPtr);
  356. Tcl_ResetResult(interp);
  357. Tcl_AppendResult(interp, "error reading stderr output file: ",
  358. Tcl_PosixError(interp), NULL);
  359.     } else if (count > 0) {
  360. anyErrorInfo = 1;
  361. Tcl_SetObjResult(interp, objPtr);
  362. result = TCL_ERROR;
  363.     } else {
  364. Tcl_DecrRefCount(objPtr);
  365.     }
  366. }
  367. Tcl_Close(NULL, errorChan);
  368.     }
  369.     /*
  370.      * If a child exited abnormally but didn't output any error information
  371.      * at all, generate an error message here.
  372.      */
  373.     if ((abnormalExit != 0) && (anyErrorInfo == 0) && (interp != NULL)) {
  374. Tcl_AppendResult(interp, "child process exited abnormally",
  375. (char *) NULL);
  376.     }
  377.     return result;
  378. }
  379. /*
  380.  *----------------------------------------------------------------------
  381.  *
  382.  * TclCreatePipeline --
  383.  *
  384.  * Given an argc/argv array, instantiate a pipeline of processes
  385.  * as described by the argv.
  386.  *
  387.  * This procedure is unofficially exported for use by BLT.
  388.  *
  389.  * Results:
  390.  * The return value is a count of the number of new processes
  391.  * created, or -1 if an error occurred while creating the pipeline.
  392.  * *pidArrayPtr is filled in with the address of a dynamically
  393.  * allocated array giving the ids of all of the processes.  It
  394.  * is up to the caller to free this array when it isn't needed
  395.  * anymore.  If inPipePtr is non-NULL, *inPipePtr is filled in
  396.  * with the file id for the input pipe for the pipeline (if any):
  397.  * the caller must eventually close this file.  If outPipePtr
  398.  * isn't NULL, then *outPipePtr is filled in with the file id
  399.  * for the output pipe from the pipeline:  the caller must close
  400.  * this file.  If errFilePtr isn't NULL, then *errFilePtr is filled
  401.  * with a file id that may be used to read error output after the
  402.  * pipeline completes.
  403.  *
  404.  * Side effects:
  405.  * Processes and pipes are created.
  406.  *
  407.  *----------------------------------------------------------------------
  408.  */
  409. int
  410. TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
  411. outPipePtr, errFilePtr)
  412.     Tcl_Interp *interp; /* Interpreter to use for error reporting. */
  413.     int argc; /* Number of entries in argv. */
  414.     CONST char **argv; /* Array of strings describing commands in
  415.  * pipeline plus I/O redirection with <,
  416.  * <<,  >, etc.  Argv[argc] must be NULL. */
  417.     Tcl_Pid **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with
  418.  * address of array of pids for processes
  419.  * in pipeline (first pid is first process
  420.  * in pipeline). */
  421.     TclFile *inPipePtr; /* If non-NULL, input to the pipeline comes
  422.  * from a pipe (unless overridden by
  423.  * redirection in the command).  The file
  424.  * id with which to write to this pipe is
  425.  * stored at *inPipePtr.  NULL means command
  426.  * specified its own input source. */
  427.     TclFile *outPipePtr; /* If non-NULL, output to the pipeline goes
  428.  * to a pipe, unless overriden by redirection
  429.  * in the command.  The file id with which to
  430.  * read frome this pipe is stored at
  431.  * *outPipePtr.  NULL means command specified
  432.  * its own output sink. */
  433.     TclFile *errFilePtr; /* If non-NULL, all stderr output from the
  434.  * pipeline will go to a temporary file
  435.  * created here, and a descriptor to read
  436.  * the file will be left at *errFilePtr.
  437.  * The file will be removed already, so
  438.  * closing this descriptor will be the end
  439.  * of the file.  If this is NULL, then
  440.  * all stderr output goes to our stderr.
  441.  * If the pipeline specifies redirection
  442.  * then the file will still be created
  443.  * but it will never get any data. */
  444. {
  445.     Tcl_Pid *pidPtr = NULL; /* Points to malloc-ed array holding all
  446.  * the pids of child processes. */
  447.     int numPids; /* Actual number of processes that exist
  448.  * at *pidPtr right now. */
  449.     int cmdCount; /* Count of number of distinct commands
  450.  * found in argc/argv. */
  451.     CONST char *inputLiteral = NULL; /* If non-null, then this points to a
  452.  * string containing input data (specified
  453.  * via <<) to be piped to the first process
  454.  * in the pipeline. */
  455.     TclFile inputFile = NULL; /* If != NULL, gives file to use as input for
  456.  * first process in pipeline (specified via <
  457.  * or <@). */
  458.     int inputClose = 0; /* If non-zero, then inputFile should be 
  459.       * closed when cleaning up. */
  460.     int inputRelease = 0;
  461.     TclFile outputFile = NULL; /* Writable file for output from last command
  462.  * in pipeline (could be file or pipe).  NULL
  463.  * means use stdout. */
  464.     int outputClose = 0; /* If non-zero, then outputFile should be 
  465.       * closed when cleaning up. */
  466.     int outputRelease = 0;
  467.     TclFile errorFile = NULL; /* Writable file for error output from all
  468.  * commands in pipeline.  NULL means use
  469.  * stderr. */
  470.     int errorClose = 0; /* If non-zero, then errorFile should be 
  471.       * closed when cleaning up. */
  472.     int errorRelease = 0;
  473.     CONST char *p;
  474.     CONST char *nextArg;
  475.     int skip, lastBar, lastArg, i, j, atOK, flags, needCmd, errorToOutput = 0;
  476.     Tcl_DString execBuffer;
  477.     TclFile pipeIn;
  478.     TclFile curInFile, curOutFile, curErrFile;
  479.     Tcl_Channel channel;
  480.     if (inPipePtr != NULL) {
  481. *inPipePtr = NULL;
  482.     }
  483.     if (outPipePtr != NULL) {
  484. *outPipePtr = NULL;
  485.     }
  486.     if (errFilePtr != NULL) {
  487. *errFilePtr = NULL;
  488.     }
  489.     Tcl_DStringInit(&execBuffer);
  490.     
  491.     pipeIn = NULL;
  492.     curInFile = NULL;
  493.     curOutFile = NULL;
  494.     numPids = 0;
  495.     /*
  496.      * First, scan through all the arguments to figure out the structure
  497.      * of the pipeline.  Process all of the input and output redirection
  498.      * arguments and remove them from the argument list in the pipeline.
  499.      * Count the number of distinct processes (it's the number of "|"
  500.      * arguments plus one) but don't remove the "|" arguments because 
  501.      * they'll be used in the second pass to seperate the individual 
  502.      * child processes.  Cannot start the child processes in this pass 
  503.      * because the redirection symbols may appear anywhere in the 
  504.      * command line -- e.g., the '<' that specifies the input to the 
  505.      * entire pipe may appear at the very end of the argument list.
  506.      */
  507.     lastBar = -1;
  508.     cmdCount = 1;
  509.     needCmd = 1;
  510.     for (i = 0; i < argc; i++) {
  511. errorToOutput = 0;
  512. skip = 0;
  513. p = argv[i];
  514. switch (*p++) {
  515. case '|':
  516.     if (*p == '&') {
  517. p++;
  518.     }
  519.     if (*p == '') {
  520. if ((i == (lastBar + 1)) || (i == (argc - 1))) {
  521.     Tcl_SetResult(interp,
  522.     "illegal use of | or |& in command",
  523.     TCL_STATIC);
  524.     goto error;
  525. }
  526.     }
  527.     lastBar = i;
  528.     cmdCount++;
  529.     needCmd = 1;
  530.     break;
  531. case '<':
  532.     if (inputClose != 0) {
  533. inputClose = 0;
  534. TclpCloseFile(inputFile);
  535.     }
  536.     if (inputRelease != 0) {
  537. inputRelease = 0;
  538. TclpReleaseFile(inputFile);
  539.     }
  540.     if (*p == '<') {
  541. inputFile = NULL;
  542. inputLiteral = p + 1;
  543. skip = 1;
  544. if (*inputLiteral == '') {
  545.     inputLiteral = ((i + 1) == argc) ? NULL : argv[i + 1];
  546.     if (inputLiteral == NULL) {
  547. Tcl_AppendResult(interp, "can't specify "", argv[i],
  548. "" as last word in command", (char *) NULL);
  549. goto error;
  550.     }
  551.     skip = 2;
  552. }
  553.     } else {
  554. nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
  555. inputLiteral = NULL;
  556. inputFile = FileForRedirect(interp, p, 1, argv[i], 
  557. nextArg, O_RDONLY, &skip, &inputClose, &inputRelease);
  558. if (inputFile == NULL) {
  559.     goto error;
  560. }
  561.     }
  562.     break;
  563. case '>':
  564.     atOK = 1;
  565.     flags = O_WRONLY | O_CREAT | O_TRUNC;
  566.     if (*p == '>') {
  567. p++;
  568. atOK = 0;
  569. /*
  570.  * Note that the O_APPEND flag only has an effect on POSIX
  571.  * platforms. On Windows, we just have to carry on regardless.
  572.  */
  573. flags = O_WRONLY | O_CREAT | O_APPEND;
  574.     }
  575.     if (*p == '&') {
  576. if (errorClose != 0) {
  577.     errorClose = 0;
  578.     TclpCloseFile(errorFile);
  579. }
  580. errorToOutput = 1;
  581. p++;
  582.     }
  583.     /*
  584.      * Close the old output file, but only if the error file is
  585.      * not also using it.
  586.      */
  587.     if (outputClose != 0) {
  588. outputClose = 0;
  589. if (errorFile == outputFile) {
  590.     errorClose = 1;
  591. } else {
  592.     TclpCloseFile(outputFile);
  593. }
  594.     }
  595.     if (outputRelease != 0) {
  596. outputRelease = 0;
  597. if (errorFile == outputFile) {
  598.     errorRelease = 1;
  599. } else {
  600.     TclpReleaseFile(outputFile);
  601. }
  602.     }
  603.     nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
  604.     outputFile = FileForRedirect(interp, p, atOK, argv[i], 
  605.     nextArg, flags, &skip, &outputClose, &outputRelease);
  606.     if (outputFile == NULL) {
  607. goto error;
  608.     }
  609.     if (errorToOutput) {
  610. if (errorClose != 0) {
  611.     errorClose = 0;
  612.     TclpCloseFile(errorFile);
  613. }
  614. if (errorRelease != 0) {
  615.     errorRelease = 0;
  616.     TclpReleaseFile(errorFile);
  617. }
  618. errorFile = outputFile;
  619.     }
  620.     break;
  621. case '2':
  622.     if (*p != '>') {
  623. break;
  624.     }
  625.     p++;
  626.     atOK = 1;
  627.     flags = O_WRONLY | O_CREAT | O_TRUNC;
  628.     if (*p == '>') {
  629. p++;
  630. atOK = 0;
  631. flags = O_WRONLY | O_CREAT;
  632.     }
  633.     if (errorClose != 0) {
  634. errorClose = 0;
  635. TclpCloseFile(errorFile);
  636.     }
  637.     if (errorRelease != 0) {
  638. errorRelease = 0;
  639. TclpReleaseFile(errorFile);
  640.     }
  641.     if (atOK && p[0] == '@' && p[1] == '1' && p[2] == '') {
  642. /*
  643.  * Special case handling of 2>@1 to redirect stderr to the
  644.  * exec/open output pipe as well.  This is meant for the end
  645.  * of the command string, otherwise use |& between commands.
  646.  */
  647. if (i != argc - 1) {
  648.     Tcl_AppendResult(interp, "must specify "", argv[i],
  649.     "" as last word in command", (char *) NULL);
  650.     goto error;
  651. }
  652. errorFile = outputFile;
  653. errorToOutput = 2;
  654. skip = 1;
  655.     } else {
  656. nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
  657. errorFile = FileForRedirect(interp, p, atOK, argv[i], 
  658. nextArg, flags, &skip, &errorClose, &errorRelease);
  659. if (errorFile == NULL) {
  660.     goto error;
  661. }
  662.     }
  663.     break;
  664. default:
  665.   /* Got a command word, not a redirection */
  666.   needCmd = 0;
  667.   break;
  668. }
  669. if (skip != 0) {
  670.     for (j = i + skip; j < argc; j++) {
  671. argv[j - skip] = argv[j];
  672.     }
  673.     argc -= skip;
  674.     i -= 1;
  675. }
  676.     }
  677.     if (needCmd) {
  678.         /* We had a bar followed only by redirections. */
  679.         Tcl_SetResult(interp,
  680.       "illegal use of | or |& in command",
  681.       TCL_STATIC);
  682. goto error;
  683.     }
  684.     if (inputFile == NULL) {
  685. if (inputLiteral != NULL) {
  686.     /*
  687.      * The input for the first process is immediate data coming from
  688.      * Tcl.  Create a temporary file for it and put the data into the
  689.      * file.
  690.      */
  691.     inputFile = TclpCreateTempFile(inputLiteral);
  692.     if (inputFile == NULL) {
  693. Tcl_AppendResult(interp,
  694. "couldn't create input file for command: ",
  695. Tcl_PosixError(interp), (char *) NULL);
  696. goto error;
  697.     }
  698.     inputClose = 1;
  699. } else if (inPipePtr != NULL) {
  700.     /*
  701.      * The input for the first process in the pipeline is to
  702.      * come from a pipe that can be written from by the caller.
  703.      */
  704.     if (TclpCreatePipe(&inputFile, inPipePtr) == 0) {
  705. Tcl_AppendResult(interp, 
  706. "couldn't create input pipe for command: ",
  707. Tcl_PosixError(interp), (char *) NULL);
  708. goto error;
  709.     }
  710.     inputClose = 1;
  711. } else {
  712.     /*
  713.      * The input for the first process comes from stdin.
  714.      */
  715.     channel = Tcl_GetStdChannel(TCL_STDIN);
  716.     if (channel != NULL) {
  717. inputFile = TclpMakeFile(channel, TCL_READABLE);
  718. if (inputFile != NULL) {
  719.     inputRelease = 1;
  720. }
  721.     }
  722. }
  723.     }
  724.     if (outputFile == NULL) {
  725. if (outPipePtr != NULL) {
  726.     /*
  727.      * Output from the last process in the pipeline is to go to a
  728.      * pipe that can be read by the caller.
  729.      */
  730.     if (TclpCreatePipe(outPipePtr, &outputFile) == 0) {
  731. Tcl_AppendResult(interp, 
  732. "couldn't create output pipe for command: ",
  733. Tcl_PosixError(interp), (char *) NULL);
  734. goto error;
  735.     }
  736.     outputClose = 1;
  737. } else {
  738.     /*
  739.      * The output for the last process goes to stdout.
  740.      */
  741.     channel = Tcl_GetStdChannel(TCL_STDOUT);
  742.     if (channel) {
  743. outputFile = TclpMakeFile(channel, TCL_WRITABLE);
  744. if (outputFile != NULL) {
  745.     outputRelease = 1;
  746. }
  747.     }
  748. }
  749.     }
  750.     if (errorFile == NULL) {
  751. if (errorToOutput == 2) {
  752.     /*
  753.      * Handle 2>@1 special case at end of cmd line
  754.      */
  755.     errorFile = outputFile;
  756. } else if (errFilePtr != NULL) {
  757.     /*
  758.      * Set up the standard error output sink for the pipeline, if
  759.      * requested.  Use a temporary file which is opened, then deleted.
  760.      * Could potentially just use pipe, but if it filled up it could
  761.      * cause the pipeline to deadlock:  we'd be waiting for processes
  762.      * to complete before reading stderr, and processes couldn't 
  763.      * complete because stderr was backed up.
  764.      */
  765.     errorFile = TclpCreateTempFile(NULL);
  766.     if (errorFile == NULL) {
  767. Tcl_AppendResult(interp,
  768. "couldn't create error file for command: ",
  769. Tcl_PosixError(interp), (char *) NULL);
  770. goto error;
  771.     }
  772.     *errFilePtr = errorFile;
  773. } else {
  774.     /*
  775.      * Errors from the pipeline go to stderr.
  776.      */
  777.     channel = Tcl_GetStdChannel(TCL_STDERR);
  778.     if (channel) {
  779. errorFile = TclpMakeFile(channel, TCL_WRITABLE);
  780. if (errorFile != NULL) {
  781.     errorRelease = 1;
  782. }
  783.     }
  784. }
  785.     }
  786.     /*
  787.      * Scan through the argc array, creating a process for each
  788.      * group of arguments between the "|" characters.
  789.      */
  790.     Tcl_ReapDetachedProcs();
  791.     pidPtr = (Tcl_Pid *) ckalloc((unsigned) (cmdCount * sizeof(Tcl_Pid)));
  792.     curInFile = inputFile;
  793.     for (i = 0; i < argc; i = lastArg + 1) { 
  794. int result, joinThisError;
  795. Tcl_Pid pid;
  796. CONST char *oldName;
  797. /*
  798.  * Convert the program name into native form. 
  799.  */
  800. if (Tcl_TranslateFileName(interp, argv[i], &execBuffer) == NULL) {
  801.     goto error;
  802. }
  803. /*
  804.  * Find the end of the current segment of the pipeline.
  805.  */
  806. joinThisError = 0;
  807. for (lastArg = i; lastArg < argc; lastArg++) {
  808.     if (argv[lastArg][0] == '|') { 
  809. if (argv[lastArg][1] == '') { 
  810.     break;
  811. }
  812. if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '')) {
  813.     joinThisError = 1;
  814.     break;
  815. }
  816.     }
  817. }
  818. /*
  819.  * If this is the last segment, use the specified outputFile.
  820.  * Otherwise create an intermediate pipe.  pipeIn will become the
  821.  * curInFile for the next segment of the pipe.
  822.  */
  823. if (lastArg == argc) {
  824.     curOutFile = outputFile;
  825. } else {
  826.     argv[lastArg] = NULL;
  827.     if (TclpCreatePipe(&pipeIn, &curOutFile) == 0) {
  828. Tcl_AppendResult(interp, "couldn't create pipe: ",
  829. Tcl_PosixError(interp), (char *) NULL);
  830. goto error;
  831.     }
  832. }
  833. if (joinThisError != 0) {
  834.     curErrFile = curOutFile;
  835. } else {
  836.     curErrFile = errorFile;
  837. }
  838. /*
  839.  * Restore argv[i], since a caller wouldn't expect the contents of
  840.  * argv to be modified.
  841.  */
  842.  
  843. oldName = argv[i];
  844. argv[i] = Tcl_DStringValue(&execBuffer);
  845. result = TclpCreateProcess(interp, lastArg - i, argv + i,
  846. curInFile, curOutFile, curErrFile, &pid);
  847. argv[i] = oldName;
  848. if (result != TCL_OK) {
  849.     goto error;
  850. }
  851. Tcl_DStringFree(&execBuffer);
  852. pidPtr[numPids] = pid;
  853. numPids++;
  854. /*
  855.  * Close off our copies of file descriptors that were set up for
  856.  * this child, then set up the input for the next child.
  857.  */
  858. if ((curInFile != NULL) && (curInFile != inputFile)) {
  859.     TclpCloseFile(curInFile);
  860. }
  861. curInFile = pipeIn;
  862. pipeIn = NULL;
  863. if ((curOutFile != NULL) && (curOutFile != outputFile)) {
  864.     TclpCloseFile(curOutFile);
  865. }
  866. curOutFile = NULL;
  867.     }
  868.     *pidArrayPtr = pidPtr;
  869.     /*
  870.      * All done.  Cleanup open files lying around and then return.
  871.      */
  872. cleanup:
  873.     Tcl_DStringFree(&execBuffer);
  874.     if (inputClose) {
  875. TclpCloseFile(inputFile);
  876.     } else if (inputRelease) {
  877. TclpReleaseFile(inputFile);
  878.     }
  879.     if (outputClose) {
  880. TclpCloseFile(outputFile);
  881.     } else if (outputRelease) {
  882. TclpReleaseFile(outputFile);
  883.     }
  884.     if (errorClose) {
  885. TclpCloseFile(errorFile);
  886.     } else if (errorRelease) {
  887. TclpReleaseFile(errorFile);
  888.     }
  889.     return numPids;
  890.     /*
  891.      * An error occurred.  There could have been extra files open, such
  892.      * as pipes between children.  Clean them all up.  Detach any child
  893.      * processes that have been created.
  894.      */
  895. error:
  896.     if (pipeIn != NULL) {
  897. TclpCloseFile(pipeIn);
  898.     }
  899.     if ((curOutFile != NULL) && (curOutFile != outputFile)) {
  900. TclpCloseFile(curOutFile);
  901.     }
  902.     if ((curInFile != NULL) && (curInFile != inputFile)) {
  903. TclpCloseFile(curInFile);
  904.     }
  905.     if ((inPipePtr != NULL) && (*inPipePtr != NULL)) {
  906. TclpCloseFile(*inPipePtr);
  907. *inPipePtr = NULL;
  908.     }
  909.     if ((outPipePtr != NULL) && (*outPipePtr != NULL)) {
  910. TclpCloseFile(*outPipePtr);
  911. *outPipePtr = NULL;
  912.     }
  913.     if ((errFilePtr != NULL) && (*errFilePtr != NULL)) {
  914. TclpCloseFile(*errFilePtr);
  915. *errFilePtr = NULL;
  916.     }
  917.     if (pidPtr != NULL) {
  918. for (i = 0; i < numPids; i++) {
  919.     if (pidPtr[i] != (Tcl_Pid) -1) {
  920. Tcl_DetachPids(1, &pidPtr[i]);
  921.     }
  922. }
  923. ckfree((char *) pidPtr);
  924.     }
  925.     numPids = -1;
  926.     goto cleanup;
  927. }
  928. /*
  929.  *----------------------------------------------------------------------
  930.  *
  931.  * Tcl_OpenCommandChannel --
  932.  *
  933.  * Opens an I/O channel to one or more subprocesses specified
  934.  * by argc and argv.  The flags argument determines the
  935.  * disposition of the stdio handles.  If the TCL_STDIN flag is
  936.  * set then the standard input for the first subprocess will
  937.  * be tied to the channel:  writing to the channel will provide
  938.  * input to the subprocess.  If TCL_STDIN is not set, then
  939.  * standard input for the first subprocess will be the same as
  940.  * this application's standard input.  If TCL_STDOUT is set then
  941.  * standard output from the last subprocess can be read from the
  942.  * channel;  otherwise it goes to this application's standard
  943.  * output.  If TCL_STDERR is set, standard error output for all
  944.  * subprocesses is returned to the channel and results in an error
  945.  * when the channel is closed;  otherwise it goes to this
  946.  * application's standard error.  If TCL_ENFORCE_MODE is not set,
  947.  * then argc and argv can redirect the stdio handles to override
  948.  * TCL_STDIN, TCL_STDOUT, and TCL_STDERR;  if it is set, then it 
  949.  * is an error for argc and argv to override stdio channels for
  950.  * which TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set.
  951.  *
  952.  * Results:
  953.  * A new command channel, or NULL on failure with an error
  954.  * message left in interp.
  955.  *
  956.  * Side effects:
  957.  * Creates processes, opens pipes.
  958.  *
  959.  *----------------------------------------------------------------------
  960.  */
  961. Tcl_Channel
  962. Tcl_OpenCommandChannel(interp, argc, argv, flags)
  963.     Tcl_Interp *interp; /* Interpreter for error reporting. Can
  964.                                  * NOT be NULL. */
  965.     int argc; /* How many arguments. */
  966.     CONST char **argv; /* Array of arguments for command pipe. */
  967.     int flags; /* Or'ed combination of TCL_STDIN, TCL_STDOUT,
  968.  * TCL_STDERR, and TCL_ENFORCE_MODE. */
  969. {
  970.     TclFile *inPipePtr, *outPipePtr, *errFilePtr;
  971.     TclFile inPipe, outPipe, errFile;
  972.     int numPids;
  973.     Tcl_Pid *pidPtr;
  974.     Tcl_Channel channel;
  975.     inPipe = outPipe = errFile = NULL;
  976.     inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL;
  977.     outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL;
  978.     errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL;
  979.     
  980.     numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr,
  981.             outPipePtr, errFilePtr);
  982.     if (numPids < 0) {
  983. goto error;
  984.     }
  985.     /*
  986.      * Verify that the pipes that were created satisfy the
  987.      * readable/writable constraints. 
  988.      */
  989.     if (flags & TCL_ENFORCE_MODE) {
  990. if ((flags & TCL_STDOUT) && (outPipe == NULL)) {
  991.     Tcl_AppendResult(interp, "can't read output from command:",
  992.     " standard output was redirected", (char *) NULL);
  993.     goto error;
  994. }
  995. if ((flags & TCL_STDIN) && (inPipe == NULL)) {
  996.     Tcl_AppendResult(interp, "can't write input to command:",
  997.     " standard input was redirected", (char *) NULL);
  998.     goto error;
  999. }
  1000.     }
  1001.     
  1002.     channel = TclpCreateCommandChannel(outPipe, inPipe, errFile,
  1003.     numPids, pidPtr);
  1004.     if (channel == (Tcl_Channel) NULL) {
  1005.         Tcl_AppendResult(interp, "pipe for command could not be created",
  1006.                 (char *) NULL);
  1007. goto error;
  1008.     }
  1009.     return channel;
  1010. error:
  1011.     if (numPids > 0) {
  1012. Tcl_DetachPids(numPids, pidPtr);
  1013. ckfree((char *) pidPtr);
  1014.     }
  1015.     if (inPipe != NULL) {
  1016. TclpCloseFile(inPipe);
  1017.     }
  1018.     if (outPipe != NULL) {
  1019. TclpCloseFile(outPipe);
  1020.     }
  1021.     if (errFile != NULL) {
  1022. TclpCloseFile(errFile);
  1023.     }
  1024.     return NULL;
  1025. }