tclPipe.c

Go to the documentation of this file.
00001 /*
00002  * tclPipe.c --
00003  *
00004  *      This file contains the generic portion of the command channel driver
00005  *      as well as various utility routines used in managing subprocesses.
00006  *
00007  * Copyright (c) 1997 by Sun Microsystems, Inc.
00008  *
00009  * See the file "license.terms" for information on usage and redistribution of
00010  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00011  *
00012  * RCS: @(#) $Id: tclPipe.c,v 1.19 2007/04/20 06:10:58 kennykb Exp $
00013  */
00014 
00015 #include "tclInt.h"
00016 
00017 /*
00018  * A linked list of the following structures is used to keep track of child
00019  * processes that have been detached but haven't exited yet, so we can make
00020  * sure that they're properly "reaped" (officially waited for) and don't lie
00021  * around as zombies cluttering the system.
00022  */
00023 
00024 typedef struct Detached {
00025     Tcl_Pid pid;                /* Id of process that's been detached but
00026                                  * isn't known to have exited. */
00027     struct Detached *nextPtr;   /* Next in list of all detached processes. */
00028 } Detached;
00029 
00030 static Detached *detList = NULL;/* List of all detached proceses. */
00031 TCL_DECLARE_MUTEX(pipeMutex)    /* Guard access to detList. */
00032 
00033 /*
00034  * Declarations for local functions defined in this file:
00035  */
00036 
00037 static TclFile          FileForRedirect(Tcl_Interp *interp, CONST char *spec,
00038                             int atOk, CONST char *arg, CONST char *nextArg,
00039                             int flags, int *skipPtr, int *closePtr,
00040                             int *releasePtr);
00041 
00042 /*
00043  *----------------------------------------------------------------------
00044  *
00045  * FileForRedirect --
00046  *
00047  *      This function does much of the work of parsing redirection operators.
00048  *      It handles "@" if specified and allowed, and a file name, and opens
00049  *      the file if necessary.
00050  *
00051  * Results:
00052  *      The return value is the descriptor number for the file. If an error
00053  *      occurs then NULL is returned and an error message is left in the
00054  *      interp's result. Several arguments are side-effected; see the argument
00055  *      list below for details.
00056  *
00057  * Side effects:
00058  *      None.
00059  *
00060  *----------------------------------------------------------------------
00061  */
00062 
00063 static TclFile
00064 FileForRedirect(
00065     Tcl_Interp *interp,         /* Intepreter to use for error reporting. */
00066     CONST char *spec,           /* Points to character just after redirection
00067                                  * character. */
00068     int atOK,                   /* Non-zero means that '@' notation can be
00069                                  * used to specify a channel, zero means that
00070                                  * it isn't. */
00071     CONST char *arg,            /* Pointer to entire argument containing spec:
00072                                  * used for error reporting. */
00073     CONST char *nextArg,        /* Next argument in argc/argv array, if needed
00074                                  * for file name or channel name. May be
00075                                  * NULL. */
00076     int flags,                  /* Flags to use for opening file or to specify
00077                                  * mode for channel. */
00078     int *skipPtr,               /* Filled with 1 if redirection target was in
00079                                  * spec, 2 if it was in nextArg. */
00080     int *closePtr,              /* Filled with one if the caller should close
00081                                  * the file when done with it, zero
00082                                  * otherwise. */
00083     int *releasePtr)
00084 {
00085     int writing = (flags & O_WRONLY);
00086     Tcl_Channel chan;
00087     TclFile file;
00088 
00089     *skipPtr = 1;
00090     if ((atOK != 0) && (*spec == '@')) {
00091         spec++;
00092         if (*spec == '\0') {
00093             spec = nextArg;
00094             if (spec == NULL) {
00095                 goto badLastArg;
00096             }
00097             *skipPtr = 2;
00098         }
00099         chan = Tcl_GetChannel(interp, spec, NULL);
00100         if (chan == (Tcl_Channel) NULL) {
00101             return NULL;
00102         }
00103         file = TclpMakeFile(chan, writing ? TCL_WRITABLE : TCL_READABLE);
00104         if (file == NULL) {
00105             Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan),
00106                     "\" wasn't opened for ",
00107                     ((writing) ? "writing" : "reading"), NULL);
00108             return NULL;
00109         }
00110         *releasePtr = 1;
00111         if (writing) {
00112             /*
00113              * Be sure to flush output to the file, so that anything written
00114              * by the child appears after stuff we've already written.
00115              */
00116 
00117             Tcl_Flush(chan);
00118         }
00119     } else {
00120         CONST char *name;
00121         Tcl_DString nameString;
00122 
00123         if (*spec == '\0') {
00124             spec = nextArg;
00125             if (spec == NULL) {
00126                 goto badLastArg;
00127             }
00128             *skipPtr = 2;
00129         }
00130         name = Tcl_TranslateFileName(interp, spec, &nameString);
00131         if (name == NULL) {
00132             return NULL;
00133         }
00134         file = TclpOpenFile(name, flags);
00135         Tcl_DStringFree(&nameString);
00136         if (file == NULL) {
00137             Tcl_AppendResult(interp, "couldn't ",
00138                     ((writing) ? "write" : "read"), " file \"", spec, "\": ",
00139                     Tcl_PosixError(interp), NULL);
00140             return NULL;
00141         }
00142         *closePtr = 1;
00143     }
00144     return file;
00145 
00146   badLastArg:
00147     Tcl_AppendResult(interp, "can't specify \"", arg,
00148             "\" as last word in command", NULL);
00149     return NULL;
00150 }
00151 
00152 /*
00153  *----------------------------------------------------------------------
00154  *
00155  * Tcl_DetachPids --
00156  *
00157  *      This function is called to indicate that one or more child processes
00158  *      have been placed in background and will never be waited for; they
00159  *      should eventually be reaped by Tcl_ReapDetachedProcs.
00160  *
00161  * Results:
00162  *      None.
00163  *
00164  * Side effects:
00165  *      None.
00166  *
00167  *----------------------------------------------------------------------
00168  */
00169 
00170 void
00171 Tcl_DetachPids(
00172     int numPids,                /* Number of pids to detach: gives size of
00173                                  * array pointed to by pidPtr. */
00174     Tcl_Pid *pidPtr)            /* Array of pids to detach. */
00175 {
00176     register Detached *detPtr;
00177     int i;
00178 
00179     Tcl_MutexLock(&pipeMutex);
00180     for (i = 0; i < numPids; i++) {
00181         detPtr = (Detached *) ckalloc(sizeof(Detached));
00182         detPtr->pid = pidPtr[i];
00183         detPtr->nextPtr = detList;
00184         detList = detPtr;
00185     }
00186     Tcl_MutexUnlock(&pipeMutex);
00187 
00188 }
00189 
00190 /*
00191  *----------------------------------------------------------------------
00192  *
00193  * Tcl_ReapDetachedProcs --
00194  *
00195  *      This function checks to see if any detached processes have exited and,
00196  *      if so, it "reaps" them by officially waiting on them. It should be
00197  *      called "occasionally" to make sure that all detached processes are
00198  *      eventually reaped.
00199  *
00200  * Results:
00201  *      None.
00202  *
00203  * Side effects:
00204  *      Processes are waited on, so that they can be reaped by the system.
00205  *
00206  *----------------------------------------------------------------------
00207  */
00208 
00209 void
00210 Tcl_ReapDetachedProcs(void)
00211 {
00212     register Detached *detPtr;
00213     Detached *nextPtr, *prevPtr;
00214     int status;
00215     Tcl_Pid pid;
00216 
00217     Tcl_MutexLock(&pipeMutex);
00218     for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) {
00219         pid = Tcl_WaitPid(detPtr->pid, &status, WNOHANG);
00220         if ((pid == 0) || ((pid == (Tcl_Pid) -1) && (errno != ECHILD))) {
00221             prevPtr = detPtr;
00222             detPtr = detPtr->nextPtr;
00223             continue;
00224         }
00225         nextPtr = detPtr->nextPtr;
00226         if (prevPtr == NULL) {
00227             detList = detPtr->nextPtr;
00228         } else {
00229             prevPtr->nextPtr = detPtr->nextPtr;
00230         }
00231         ckfree((char *) detPtr);
00232         detPtr = nextPtr;
00233     }
00234     Tcl_MutexUnlock(&pipeMutex);
00235 }
00236 
00237 /*
00238  *----------------------------------------------------------------------
00239  *
00240  * TclCleanupChildren --
00241  *
00242  *      This is a utility function used to wait for child processes to exit,
00243  *      record information about abnormal exits, and then collect any stderr
00244  *      output generated by them.
00245  *
00246  * Results:
00247  *      The return value is a standard Tcl result. If anything at weird
00248  *      happened with the child processes, TCL_ERROR is returned and a message
00249  *      is left in the interp's result.
00250  *
00251  * Side effects:
00252  *      If the last character of the interp's result is a newline, then it is
00253  *      removed unless keepNewline is non-zero. File errorId gets closed, and
00254  *      pidPtr is freed back to the storage allocator.
00255  *
00256  *----------------------------------------------------------------------
00257  */
00258 
00259 int
00260 TclCleanupChildren(
00261     Tcl_Interp *interp,         /* Used for error messages. */
00262     int numPids,                /* Number of entries in pidPtr array. */
00263     Tcl_Pid *pidPtr,            /* Array of process ids of children. */
00264     Tcl_Channel errorChan)      /* Channel for file containing stderr output
00265                                  * from pipeline. NULL means there isn't any
00266                                  * stderr output. */
00267 {
00268     int result = TCL_OK;
00269     int i, abnormalExit, anyErrorInfo;
00270     Tcl_Pid pid;
00271     WAIT_STATUS_TYPE waitStatus;
00272     CONST char *msg;
00273     unsigned long resolvedPid;
00274 
00275     abnormalExit = 0;
00276     for (i = 0; i < numPids; i++) {
00277         /*
00278          * We need to get the resolved pid before we wait on it as the windows
00279          * implimentation of Tcl_WaitPid deletes the information such that any
00280          * following calls to TclpGetPid fail.
00281          */
00282 
00283         resolvedPid = TclpGetPid(pidPtr[i]);
00284         pid = Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0);
00285         if (pid == (Tcl_Pid) -1) {
00286             result = TCL_ERROR;
00287             if (interp != NULL) {
00288                 msg = Tcl_PosixError(interp);
00289                 if (errno == ECHILD) {
00290                     /*
00291                      * This changeup in message suggested by Mark Diekhans to
00292                      * remind people that ECHILD errors can occur on some
00293                      * systems if SIGCHLD isn't in its default state.
00294                      */
00295 
00296                     msg =
00297                         "child process lost (is SIGCHLD ignored or trapped?)";
00298                 }
00299                 Tcl_AppendResult(interp, "error waiting for process to exit: ",
00300                         msg, NULL);
00301             }
00302             continue;
00303         }
00304 
00305         /*
00306          * Create error messages for unusual process exits. An extra newline
00307          * gets appended to each error message, but it gets removed below (in
00308          * the same fashion that an extra newline in the command's output is
00309          * removed).
00310          */
00311 
00312         if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {
00313             char msg1[TCL_INTEGER_SPACE], msg2[TCL_INTEGER_SPACE];
00314 
00315             result = TCL_ERROR;
00316             sprintf(msg1, "%lu", resolvedPid);
00317             if (WIFEXITED(waitStatus)) {
00318                 if (interp != (Tcl_Interp *) NULL) {
00319                     sprintf(msg2, "%lu",
00320                             (unsigned long) WEXITSTATUS(waitStatus));
00321                     Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2, NULL);
00322                 }
00323                 abnormalExit = 1;
00324             } else if (interp != NULL) {
00325                 CONST char *p;
00326 
00327                 if (WIFSIGNALED(waitStatus)) {
00328                     p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));
00329                     Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,
00330                             Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,
00331                             NULL);
00332                     Tcl_AppendResult(interp, "child killed: ", p, "\n", NULL);
00333                 } else if (WIFSTOPPED(waitStatus)) {
00334                     p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));
00335                     Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,
00336                             Tcl_SignalId((int) (WSTOPSIG(waitStatus))), p,
00337                             NULL);
00338                     Tcl_AppendResult(interp, "child suspended: ", p, "\n",
00339                             NULL);
00340                 } else {
00341                     Tcl_AppendResult(interp,
00342                             "child wait status didn't make sense\n", NULL);
00343                 }
00344             }
00345         }
00346     }
00347 
00348     /*
00349      * Read the standard error file. If there's anything there, then return an
00350      * error and add the file's contents to the result string.
00351      */
00352 
00353     anyErrorInfo = 0;
00354     if (errorChan != NULL) {
00355         /*
00356          * Make sure we start at the beginning of the file.
00357          */
00358 
00359         if (interp != NULL) {
00360             int count;
00361             Tcl_Obj *objPtr;
00362 
00363             Tcl_Seek(errorChan, (Tcl_WideInt)0, SEEK_SET);
00364             objPtr = Tcl_NewObj();
00365             count = Tcl_ReadChars(errorChan, objPtr, -1, 0);
00366             if (count < 0) {
00367                 result = TCL_ERROR;
00368                 Tcl_DecrRefCount(objPtr);
00369                 Tcl_ResetResult(interp);
00370                 Tcl_AppendResult(interp, "error reading stderr output file: ",
00371                         Tcl_PosixError(interp), NULL);
00372             } else if (count > 0) {
00373                 anyErrorInfo = 1;
00374                 Tcl_SetObjResult(interp, objPtr);
00375                 result = TCL_ERROR;
00376             } else {
00377                 Tcl_DecrRefCount(objPtr);
00378             }
00379         }
00380         Tcl_Close(NULL, errorChan);
00381     }
00382 
00383     /*
00384      * If a child exited abnormally but didn't output any error information at
00385      * all, generate an error message here.
00386      */
00387 
00388     if ((abnormalExit != 0) && (anyErrorInfo == 0) && (interp != NULL)) {
00389         Tcl_AppendResult(interp, "child process exited abnormally", NULL);
00390     }
00391     return result;
00392 }
00393 
00394 /*
00395  *----------------------------------------------------------------------
00396  *
00397  * TclCreatePipeline --
00398  *
00399  *      Given an argc/argv array, instantiate a pipeline of processes as
00400  *      described by the argv.
00401  *
00402  *      This function is unofficially exported for use by BLT.
00403  *
00404  * Results:
00405  *      The return value is a count of the number of new processes created, or
00406  *      -1 if an error occurred while creating the pipeline. *pidArrayPtr is
00407  *      filled in with the address of a dynamically allocated array giving the
00408  *      ids of all of the processes. It is up to the caller to free this array
00409  *      when it isn't needed anymore. If inPipePtr is non-NULL, *inPipePtr is
00410  *      filled in with the file id for the input pipe for the pipeline (if
00411  *      any): the caller must eventually close this file. If outPipePtr isn't
00412  *      NULL, then *outPipePtr is filled in with the file id for the output
00413  *      pipe from the pipeline: the caller must close this file. If errFilePtr
00414  *      isn't NULL, then *errFilePtr is filled with a file id that may be used
00415  *      to read error output after the pipeline completes.
00416  *
00417  * Side effects:
00418  *      Processes and pipes are created.
00419  *
00420  *----------------------------------------------------------------------
00421  */
00422 
00423 int
00424 TclCreatePipeline(
00425     Tcl_Interp *interp,         /* Interpreter to use for error reporting. */
00426     int argc,                   /* Number of entries in argv. */
00427     CONST char **argv,          /* Array of strings describing commands in
00428                                  * pipeline plus I/O redirection with <, <<,
00429                                  * >, etc. Argv[argc] must be NULL. */
00430     Tcl_Pid **pidArrayPtr,      /* Word at *pidArrayPtr gets filled in with
00431                                  * address of array of pids for processes in
00432                                  * pipeline (first pid is first process in
00433                                  * pipeline). */
00434     TclFile *inPipePtr,         /* If non-NULL, input to the pipeline comes
00435                                  * from a pipe (unless overridden by
00436                                  * redirection in the command). The file id
00437                                  * with which to write to this pipe is stored
00438                                  * at *inPipePtr. NULL means command specified
00439                                  * its own input source. */
00440     TclFile *outPipePtr,        /* If non-NULL, output to the pipeline goes to
00441                                  * a pipe, unless overriden by redirection in
00442                                  * the command. The file id with which to read
00443                                  * frome this pipe is stored at *outPipePtr.
00444                                  * NULL means command specified its own output
00445                                  * sink. */
00446     TclFile *errFilePtr)        /* If non-NULL, all stderr output from the
00447                                  * pipeline will go to a temporary file
00448                                  * created here, and a descriptor to read the
00449                                  * file will be left at *errFilePtr. The file
00450                                  * will be removed already, so closing this
00451                                  * descriptor will be the end of the file. If
00452                                  * this is NULL, then all stderr output goes
00453                                  * to our stderr. If the pipeline specifies
00454                                  * redirection then the file will still be
00455                                  * created but it will never get any data. */
00456 {
00457     Tcl_Pid *pidPtr = NULL;     /* Points to malloc-ed array holding all the
00458                                  * pids of child processes. */
00459     int numPids;                /* Actual number of processes that exist at
00460                                  * *pidPtr right now. */
00461     int cmdCount;               /* Count of number of distinct commands found
00462                                  * in argc/argv. */
00463     CONST char *inputLiteral = NULL;
00464                                 /* If non-null, then this points to a string
00465                                  * containing input data (specified via <<) to
00466                                  * be piped to the first process in the
00467                                  * pipeline. */
00468     TclFile inputFile = NULL;   /* If != NULL, gives file to use as input for
00469                                  * first process in pipeline (specified via <
00470                                  * or <@). */
00471     int inputClose = 0;         /* If non-zero, then inputFile should be
00472                                  * closed when cleaning up. */
00473     int inputRelease = 0;
00474     TclFile outputFile = NULL;  /* Writable file for output from last command
00475                                  * in pipeline (could be file or pipe). NULL
00476                                  * means use stdout. */
00477     int outputClose = 0;        /* If non-zero, then outputFile should be
00478                                  * closed when cleaning up. */
00479     int outputRelease = 0;
00480     TclFile errorFile = NULL;   /* Writable file for error output from all
00481                                  * commands in pipeline. NULL means use
00482                                  * stderr. */
00483     int errorClose = 0;         /* If non-zero, then errorFile should be
00484                                  * closed when cleaning up. */
00485     int errorRelease = 0;
00486     CONST char *p;
00487     CONST char *nextArg;
00488     int skip, lastBar, lastArg, i, j, atOK, flags, needCmd, errorToOutput = 0;
00489     Tcl_DString execBuffer;
00490     TclFile pipeIn;
00491     TclFile curInFile, curOutFile, curErrFile;
00492     Tcl_Channel channel;
00493 
00494     if (inPipePtr != NULL) {
00495         *inPipePtr = NULL;
00496     }
00497     if (outPipePtr != NULL) {
00498         *outPipePtr = NULL;
00499     }
00500     if (errFilePtr != NULL) {
00501         *errFilePtr = NULL;
00502     }
00503 
00504     Tcl_DStringInit(&execBuffer);
00505 
00506     pipeIn = NULL;
00507     curInFile = NULL;
00508     curOutFile = NULL;
00509     numPids = 0;
00510 
00511     /*
00512      * First, scan through all the arguments to figure out the structure of
00513      * the pipeline. Process all of the input and output redirection arguments
00514      * and remove them from the argument list in the pipeline. Count the
00515      * number of distinct processes (it's the number of "|" arguments plus
00516      * one) but don't remove the "|" arguments because they'll be used in the
00517      * second pass to seperate the individual child processes. Cannot start
00518      * the child processes in this pass because the redirection symbols may
00519      * appear anywhere in the command line - e.g., the '<' that specifies the
00520      * input to the entire pipe may appear at the very end of the argument
00521      * list.
00522      */
00523 
00524     lastBar = -1;
00525     cmdCount = 1;
00526     needCmd = 1;
00527     for (i = 0; i < argc; i++) {
00528         errorToOutput = 0;
00529         skip = 0;
00530         p = argv[i];
00531         switch (*p++) {
00532         case '|':
00533             if (*p == '&') {
00534                 p++;
00535             }
00536             if (*p == '\0') {
00537                 if ((i == (lastBar + 1)) || (i == (argc - 1))) {
00538                     Tcl_SetResult(interp, "illegal use of | or |& in command",
00539                             TCL_STATIC);
00540                     goto error;
00541                 }
00542             }
00543             lastBar = i;
00544             cmdCount++;
00545             needCmd = 1;
00546             break;
00547 
00548         case '<':
00549             if (inputClose != 0) {
00550                 inputClose = 0;
00551                 TclpCloseFile(inputFile);
00552             }
00553             if (inputRelease != 0) {
00554                 inputRelease = 0;
00555                 TclpReleaseFile(inputFile);
00556             }
00557             if (*p == '<') {
00558                 inputFile = NULL;
00559                 inputLiteral = p + 1;
00560                 skip = 1;
00561                 if (*inputLiteral == '\0') {
00562                     inputLiteral = ((i + 1) == argc) ? NULL : argv[i + 1];
00563                     if (inputLiteral == NULL) {
00564                         Tcl_AppendResult(interp, "can't specify \"", argv[i],
00565                                 "\" as last word in command", NULL);
00566                         goto error;
00567                     }
00568                     skip = 2;
00569                 }
00570             } else {
00571                 nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
00572                 inputLiteral = NULL;
00573                 inputFile = FileForRedirect(interp, p, 1, argv[i], nextArg,
00574                         O_RDONLY, &skip, &inputClose, &inputRelease);
00575                 if (inputFile == NULL) {
00576                     goto error;
00577                 }
00578             }
00579             break;
00580 
00581         case '>':
00582             atOK = 1;
00583             flags = O_WRONLY | O_CREAT | O_TRUNC;
00584             if (*p == '>') {
00585                 p++;
00586                 atOK = 0;
00587 
00588                 /*
00589                  * Note that the O_APPEND flag only has an effect on POSIX
00590                  * platforms. On Windows, we just have to carry on regardless.
00591                  */
00592 
00593                 flags = O_WRONLY | O_CREAT | O_APPEND;
00594             }
00595             if (*p == '&') {
00596                 if (errorClose != 0) {
00597                     errorClose = 0;
00598                     TclpCloseFile(errorFile);
00599                 }
00600                 errorToOutput = 1;
00601                 p++;
00602             }
00603 
00604             /*
00605              * Close the old output file, but only if the error file is not
00606              * also using it.
00607              */
00608 
00609             if (outputClose != 0) {
00610                 outputClose = 0;
00611                 if (errorFile == outputFile) {
00612                     errorClose = 1;
00613                 } else {
00614                     TclpCloseFile(outputFile);
00615                 }
00616             }
00617             if (outputRelease != 0) {
00618                 outputRelease = 0;
00619                 if (errorFile == outputFile) {
00620                     errorRelease = 1;
00621                 } else {
00622                     TclpReleaseFile(outputFile);
00623                 }
00624             }
00625             nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
00626             outputFile = FileForRedirect(interp, p, atOK, argv[i], nextArg,
00627                     flags, &skip, &outputClose, &outputRelease);
00628             if (outputFile == NULL) {
00629                 goto error;
00630             }
00631             if (errorToOutput) {
00632                 if (errorClose != 0) {
00633                     errorClose = 0;
00634                     TclpCloseFile(errorFile);
00635                 }
00636                 if (errorRelease != 0) {
00637                     errorRelease = 0;
00638                     TclpReleaseFile(errorFile);
00639                 }
00640                 errorFile = outputFile;
00641             }
00642             break;
00643 
00644         case '2':
00645             if (*p != '>') {
00646                 break;
00647             }
00648             p++;
00649             atOK = 1;
00650             flags = O_WRONLY | O_CREAT | O_TRUNC;
00651             if (*p == '>') {
00652                 p++;
00653                 atOK = 0;
00654                 flags = O_WRONLY | O_CREAT;
00655             }
00656             if (errorClose != 0) {
00657                 errorClose = 0;
00658                 TclpCloseFile(errorFile);
00659             }
00660             if (errorRelease != 0) {
00661                 errorRelease = 0;
00662                 TclpReleaseFile(errorFile);
00663             }
00664             if (atOK && p[0] == '@' && p[1] == '1' && p[2] == '\0') {
00665                 /*
00666                  * Special case handling of 2>@1 to redirect stderr to the
00667                  * exec/open output pipe as well. This is meant for the end of
00668                  * the command string, otherwise use |& between commands.
00669                  */
00670 
00671                 if (i != argc-1) {
00672                     Tcl_AppendResult(interp, "must specify \"", argv[i],
00673                             "\" as last word in command", NULL);
00674                     goto error;
00675                 }
00676                 errorFile = outputFile;
00677                 errorToOutput = 2;
00678                 skip = 1;
00679             } else {
00680                 nextArg = ((i + 1) == argc) ? NULL : argv[i + 1];
00681                 errorFile = FileForRedirect(interp, p, atOK, argv[i],
00682                         nextArg, flags, &skip, &errorClose, &errorRelease);
00683                 if (errorFile == NULL) {
00684                     goto error;
00685                 }
00686             }
00687             break;
00688 
00689         default:
00690           /* Got a command word, not a redirection */
00691           needCmd = 0;
00692           break;
00693         }
00694 
00695         if (skip != 0) {
00696             for (j = i + skip; j < argc; j++) {
00697                 argv[j - skip] = argv[j];
00698             }
00699             argc -= skip;
00700             i -= 1;
00701         }
00702     }
00703 
00704     if (needCmd) {
00705         /* We had a bar followed only by redirections. */
00706 
00707         Tcl_SetResult(interp,
00708                       "illegal use of | or |& in command",
00709                       TCL_STATIC);
00710         goto error;
00711     }
00712 
00713     if (inputFile == NULL) {
00714         if (inputLiteral != NULL) {
00715             /*
00716              * The input for the first process is immediate data coming from
00717              * Tcl. Create a temporary file for it and put the data into the
00718              * file.
00719              */
00720 
00721             inputFile = TclpCreateTempFile(inputLiteral);
00722             if (inputFile == NULL) {
00723                 Tcl_AppendResult(interp,
00724                         "couldn't create input file for command: ",
00725                         Tcl_PosixError(interp), NULL);
00726                 goto error;
00727             }
00728             inputClose = 1;
00729         } else if (inPipePtr != NULL) {
00730             /*
00731              * The input for the first process in the pipeline is to come from
00732              * a pipe that can be written from by the caller.
00733              */
00734 
00735             if (TclpCreatePipe(&inputFile, inPipePtr) == 0) {
00736                 Tcl_AppendResult(interp,
00737                         "couldn't create input pipe for command: ",
00738                         Tcl_PosixError(interp), NULL);
00739                 goto error;
00740             }
00741             inputClose = 1;
00742         } else {
00743             /*
00744              * The input for the first process comes from stdin.
00745              */
00746 
00747             channel = Tcl_GetStdChannel(TCL_STDIN);
00748             if (channel != NULL) {
00749                 inputFile = TclpMakeFile(channel, TCL_READABLE);
00750                 if (inputFile != NULL) {
00751                     inputRelease = 1;
00752                 }
00753             }
00754         }
00755     }
00756 
00757     if (outputFile == NULL) {
00758         if (outPipePtr != NULL) {
00759             /*
00760              * Output from the last process in the pipeline is to go to a pipe
00761              * that can be read by the caller.
00762              */
00763 
00764             if (TclpCreatePipe(outPipePtr, &outputFile) == 0) {
00765                 Tcl_AppendResult(interp,
00766                         "couldn't create output pipe for command: ",
00767                         Tcl_PosixError(interp), NULL);
00768                 goto error;
00769             }
00770             outputClose = 1;
00771         } else {
00772             /*
00773              * The output for the last process goes to stdout.
00774              */
00775 
00776             channel = Tcl_GetStdChannel(TCL_STDOUT);
00777             if (channel) {
00778                 outputFile = TclpMakeFile(channel, TCL_WRITABLE);
00779                 if (outputFile != NULL) {
00780                     outputRelease = 1;
00781                 }
00782             }
00783         }
00784     }
00785 
00786     if (errorFile == NULL) {
00787         if (errorToOutput == 2) {
00788             /*
00789              * Handle 2>@1 special case at end of cmd line.
00790              */
00791 
00792             errorFile = outputFile;
00793         } else if (errFilePtr != NULL) {
00794             /*
00795              * Set up the standard error output sink for the pipeline, if
00796              * requested. Use a temporary file which is opened, then deleted.
00797              * Could potentially just use pipe, but if it filled up it could
00798              * cause the pipeline to deadlock: we'd be waiting for processes
00799              * to complete before reading stderr, and processes couldn't
00800              * complete because stderr was backed up.
00801              */
00802 
00803             errorFile = TclpCreateTempFile(NULL);
00804             if (errorFile == NULL) {
00805                 Tcl_AppendResult(interp,
00806                         "couldn't create error file for command: ",
00807                         Tcl_PosixError(interp), NULL);
00808                 goto error;
00809             }
00810             *errFilePtr = errorFile;
00811         } else {
00812             /*
00813              * Errors from the pipeline go to stderr.
00814              */
00815 
00816             channel = Tcl_GetStdChannel(TCL_STDERR);
00817             if (channel) {
00818                 errorFile = TclpMakeFile(channel, TCL_WRITABLE);
00819                 if (errorFile != NULL) {
00820                     errorRelease = 1;
00821                 }
00822             }
00823         }
00824     }
00825 
00826     /*
00827      * Scan through the argc array, creating a process for each group of
00828      * arguments between the "|" characters.
00829      */
00830 
00831     Tcl_ReapDetachedProcs();
00832     pidPtr = (Tcl_Pid *) ckalloc((unsigned) (cmdCount * sizeof(Tcl_Pid)));
00833 
00834     curInFile = inputFile;
00835 
00836     for (i = 0; i < argc; i = lastArg + 1) {
00837         int result, joinThisError;
00838         Tcl_Pid pid;
00839         CONST char *oldName;
00840 
00841         /*
00842          * Convert the program name into native form.
00843          */
00844 
00845         if (Tcl_TranslateFileName(interp, argv[i], &execBuffer) == NULL) {
00846             goto error;
00847         }
00848 
00849         /*
00850          * Find the end of the current segment of the pipeline.
00851          */
00852 
00853         joinThisError = 0;
00854         for (lastArg = i; lastArg < argc; lastArg++) {
00855             if (argv[lastArg][0] != '|') {
00856                 continue;
00857             }
00858             if (argv[lastArg][1] == '\0') {
00859                 break;
00860             }
00861             if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) {
00862                 joinThisError = 1;
00863                 break;
00864             }
00865         }
00866 
00867         /*
00868          * If this is the last segment, use the specified outputFile.
00869          * Otherwise create an intermediate pipe. pipeIn will become the
00870          * curInFile for the next segment of the pipe.
00871          */
00872 
00873         if (lastArg == argc) {
00874             curOutFile = outputFile;
00875         } else {
00876             argv[lastArg] = NULL;
00877             if (TclpCreatePipe(&pipeIn, &curOutFile) == 0) {
00878                 Tcl_AppendResult(interp, "couldn't create pipe: ",
00879                         Tcl_PosixError(interp), NULL);
00880                 goto error;
00881             }
00882         }
00883 
00884         if (joinThisError != 0) {
00885             curErrFile = curOutFile;
00886         } else {
00887             curErrFile = errorFile;
00888         }
00889 
00890         /*
00891          * Restore argv[i], since a caller wouldn't expect the contents of
00892          * argv to be modified.
00893          */
00894 
00895         oldName = argv[i];
00896         argv[i] = Tcl_DStringValue(&execBuffer);
00897         result = TclpCreateProcess(interp, lastArg - i, argv + i,
00898                 curInFile, curOutFile, curErrFile, &pid);
00899         argv[i] = oldName;
00900         if (result != TCL_OK) {
00901             goto error;
00902         }
00903         Tcl_DStringFree(&execBuffer);
00904 
00905         pidPtr[numPids] = pid;
00906         numPids++;
00907 
00908         /*
00909          * Close off our copies of file descriptors that were set up for this
00910          * child, then set up the input for the next child.
00911          */
00912 
00913         if ((curInFile != NULL) && (curInFile != inputFile)) {
00914             TclpCloseFile(curInFile);
00915         }
00916         curInFile = pipeIn;
00917         pipeIn = NULL;
00918 
00919         if ((curOutFile != NULL) && (curOutFile != outputFile)) {
00920             TclpCloseFile(curOutFile);
00921         }
00922         curOutFile = NULL;
00923     }
00924 
00925     *pidArrayPtr = pidPtr;
00926 
00927     /*
00928      * All done. Cleanup open files lying around and then return.
00929      */
00930 
00931   cleanup:
00932     Tcl_DStringFree(&execBuffer);
00933 
00934     if (inputClose) {
00935         TclpCloseFile(inputFile);
00936     } else if (inputRelease) {
00937         TclpReleaseFile(inputFile);
00938     }
00939     if (outputClose) {
00940         TclpCloseFile(outputFile);
00941     } else if (outputRelease) {
00942         TclpReleaseFile(outputFile);
00943     }
00944     if (errorClose) {
00945         TclpCloseFile(errorFile);
00946     } else if (errorRelease) {
00947         TclpReleaseFile(errorFile);
00948     }
00949     return numPids;
00950 
00951     /*
00952      * An error occurred. There could have been extra files open, such as
00953      * pipes between children. Clean them all up. Detach any child processes
00954      * that have been created.
00955      */
00956 
00957   error:
00958     if (pipeIn != NULL) {
00959         TclpCloseFile(pipeIn);
00960     }
00961     if ((curOutFile != NULL) && (curOutFile != outputFile)) {
00962         TclpCloseFile(curOutFile);
00963     }
00964     if ((curInFile != NULL) && (curInFile != inputFile)) {
00965         TclpCloseFile(curInFile);
00966     }
00967     if ((inPipePtr != NULL) && (*inPipePtr != NULL)) {
00968         TclpCloseFile(*inPipePtr);
00969         *inPipePtr = NULL;
00970     }
00971     if ((outPipePtr != NULL) && (*outPipePtr != NULL)) {
00972         TclpCloseFile(*outPipePtr);
00973         *outPipePtr = NULL;
00974     }
00975     if ((errFilePtr != NULL) && (*errFilePtr != NULL)) {
00976         TclpCloseFile(*errFilePtr);
00977         *errFilePtr = NULL;
00978     }
00979     if (pidPtr != NULL) {
00980         for (i = 0; i < numPids; i++) {
00981             if (pidPtr[i] != (Tcl_Pid) -1) {
00982                 Tcl_DetachPids(1, &pidPtr[i]);
00983             }
00984         }
00985         ckfree((char *) pidPtr);
00986     }
00987     numPids = -1;
00988     goto cleanup;
00989 }
00990 
00991 /*
00992  *----------------------------------------------------------------------
00993  *
00994  * Tcl_OpenCommandChannel --
00995  *
00996  *      Opens an I/O channel to one or more subprocesses specified by argc and
00997  *      argv. The flags argument determines the disposition of the stdio
00998  *      handles. If the TCL_STDIN flag is set then the standard input for the
00999  *      first subprocess will be tied to the channel: writing to the channel
01000  *      will provide input to the subprocess. If TCL_STDIN is not set, then
01001  *      standard input for the first subprocess will be the same as this
01002  *      application's standard input. If TCL_STDOUT is set then standard
01003  *      output from the last subprocess can be read from the channel;
01004  *      otherwise it goes to this application's standard output. If TCL_STDERR
01005  *      is set, standard error output for all subprocesses is returned to the
01006  *      channel and results in an error when the channel is closed; otherwise
01007  *      it goes to this application's standard error. If TCL_ENFORCE_MODE is
01008  *      not set, then argc and argv can redirect the stdio handles to override
01009  *      TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it is an
01010  *      error for argc and argv to override stdio channels for which
01011  *      TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set.
01012  *
01013  * Results:
01014  *      A new command channel, or NULL on failure with an error message left
01015  *      in interp.
01016  *
01017  * Side effects:
01018  *      Creates processes, opens pipes.
01019  *
01020  *----------------------------------------------------------------------
01021  */
01022 
01023 Tcl_Channel
01024 Tcl_OpenCommandChannel(
01025     Tcl_Interp *interp,         /* Interpreter for error reporting. Can NOT be
01026                                  * NULL. */
01027     int argc,                   /* How many arguments. */
01028     CONST char **argv,          /* Array of arguments for command pipe. */
01029     int flags)                  /* Or'ed combination of TCL_STDIN, TCL_STDOUT,
01030                                  * TCL_STDERR, and TCL_ENFORCE_MODE. */
01031 {
01032     TclFile *inPipePtr, *outPipePtr, *errFilePtr;
01033     TclFile inPipe, outPipe, errFile;
01034     int numPids;
01035     Tcl_Pid *pidPtr;
01036     Tcl_Channel channel;
01037 
01038     inPipe = outPipe = errFile = NULL;
01039 
01040     inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL;
01041     outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL;
01042     errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL;
01043 
01044     numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr,
01045             outPipePtr, errFilePtr);
01046 
01047     if (numPids < 0) {
01048         goto error;
01049     }
01050 
01051     /*
01052      * Verify that the pipes that were created satisfy the readable/writable
01053      * constraints.
01054      */
01055 
01056     if (flags & TCL_ENFORCE_MODE) {
01057         if ((flags & TCL_STDOUT) && (outPipe == NULL)) {
01058             Tcl_AppendResult(interp, "can't read output from command:"
01059                     " standard output was redirected", NULL);
01060             goto error;
01061         }
01062         if ((flags & TCL_STDIN) && (inPipe == NULL)) {
01063             Tcl_AppendResult(interp, "can't write input to command:"
01064                     " standard input was redirected", NULL);
01065             goto error;
01066         }
01067     }
01068 
01069     channel = TclpCreateCommandChannel(outPipe, inPipe, errFile,
01070             numPids, pidPtr);
01071 
01072     if (channel == (Tcl_Channel) NULL) {
01073         Tcl_AppendResult(interp, "pipe for command could not be created",
01074                 NULL);
01075         goto error;
01076     }
01077     return channel;
01078 
01079   error:
01080     if (numPids > 0) {
01081         Tcl_DetachPids(numPids, pidPtr);
01082         ckfree((char *) pidPtr);
01083     }
01084     if (inPipe != NULL) {
01085         TclpCloseFile(inPipe);
01086     }
01087     if (outPipe != NULL) {
01088         TclpCloseFile(outPipe);
01089     }
01090     if (errFile != NULL) {
01091         TclpCloseFile(errFile);
01092     }
01093     return NULL;
01094 }
01095 
01096 /*
01097  * Local Variables:
01098  * mode: c
01099  * c-basic-offset: 4
01100  * fill-column: 78
01101  * End:
01102  */



Generated on Wed Mar 12 12:18:20 2008 by  doxygen 1.5.1