tclPipe.cGo 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 1.5.1 |