/*1* tclIOUtil.c --2*3* This file contains a collection of utility procedures that4* are shared by the platform specific IO drivers.5*6* Parts of this file are based on code contributed by Karl7* Lehenbauer, Mark Diekhans and Peter da Silva.8*9* Copyright (c) 1991-1994 The Regents of the University of California.10* Copyright (c) 1994-1996 Sun Microsystems, Inc.11*12* See the file "license.terms" for information on usage and redistribution13* of this file, and for a DISCLAIMER OF ALL WARRANTIES.14*15* SCCS: @(#) tclIOUtil.c 1.128 96/10/02 12:25:3616*/1718#include "tclInt.h"19#include "tclPort.h"2021extern Tcl_Channel TclCreateCommandChannel _ANSI_ARGS_((Tcl_File, Tcl_File,22Tcl_File, int, int*));2324/*25* A linked list of the following structures is used to keep track26* of child processes that have been detached but haven't exited27* yet, so we can make sure that they're properly "reaped" (officially28* waited for) and don't lie around as zombies cluttering the29* system.30*/3132typedef struct Detached {33int pid; /* Id of process that's been detached34* but isn't known to have exited. */35struct Detached *nextPtr; /* Next in list of all detached36* processes. */37} Detached;3839static Detached *detList = NULL; /* List of all detached proceses. */4041/*42* Declarations for local procedures defined in this file:43*/4445#if 046static Tcl_File FileForRedirect _ANSI_ARGS_((Tcl_Interp *interp,47char *spec, int atOk, char *arg, char *nextArg,48int flags, int *skipPtr, int *closePtr,49Tcl_DString *namePtr));5051/*52*----------------------------------------------------------------------53*54* FileForRedirect --55*56* This procedure does much of the work of parsing redirection57* operators. It handles "@" if specified and allowed, and a file58* name, and opens the file if necessary.59*60* Results:61* The return value is the descriptor number for the file. If an62* error occurs then NULL is returned and an error message is left63* in interp->result. Several arguments are side-effected; see64* the argument list below for details.65*66* Side effects:67* None.68*69*----------------------------------------------------------------------70*/7172static Tcl_File73FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr,74namePtr)75Tcl_Interp *interp; /* Intepreter to use for error reporting. */76char *spec; /* Points to character just after77* redirection character. */78char *arg; /* Pointer to entire argument containing79* spec: used for error reporting. */80int atOK; /* Non-zero means that '@' notation can be81* used to specify a channel, zero means that82* it isn't. */83char *nextArg; /* Next argument in argc/argv array, if needed84* for file name or channel name. May be85* NULL. */86int flags; /* Flags to use for opening file or to87* specify mode for channel. */88int *skipPtr; /* Filled with 1 if redirection target was89* in spec, 2 if it was in nextArg. */90int *closePtr; /* Filled with one if the caller should91* close the file when done with it, zero92* otherwise. */93Tcl_DString *namePtr; /* Pointer to an initialized Tcl_DString that94* is filled with the name of the file that95* was opened. Unmodified if spec refers96* to a channel. */97{98int writing = (flags & O_ACCMODE) == O_WRONLY;99Tcl_Channel chan;100Tcl_File file;101102*skipPtr = 1;103if ((atOK != 0) && (*spec == '@')) {104spec++;105if (*spec == '\0') {106spec = nextArg;107if (spec == NULL) {108goto badLastArg;109}110*skipPtr = 2;111}112chan = Tcl_GetChannel(interp, spec, NULL);113if (chan == (Tcl_Channel) NULL) {114return NULL;115}116file = Tcl_GetChannelFile(chan, writing ? TCL_WRITABLE : TCL_READABLE);117if (file == NULL) {118Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan),119"\" wasn't opened for ",120((writing) ? "writing" : "reading"), (char *) NULL);121return NULL;122}123if (writing) {124125/*126* Be sure to flush output to the file, so that anything127* written by the child appears after stuff we've already128* written.129*/130131Tcl_Flush(chan);132}133} else {134char *name;135136if (*spec == '\0') {137spec = nextArg;138if (spec == NULL) {139goto badLastArg;140}141*skipPtr = 2;142}143name = Tcl_TranslateFileName(interp, spec, namePtr);144if (name != NULL) {145file = TclOpenFile(name, flags);146} else {147file = NULL;148}149if (file == NULL) {150Tcl_AppendResult(interp, "couldn't ",151((writing) ? "write" : "read"), " file \"", spec, "\": ",152Tcl_PosixError(interp), (char *) NULL);153Tcl_DStringFree(namePtr);154return NULL;155}156*closePtr = 1;157}158return file;159160badLastArg:161Tcl_AppendResult(interp, "can't specify \"", arg,162"\" as last word in command", (char *) NULL);163return NULL;164}165#endif166167/*168*----------------------------------------------------------------------169*170* TclGetOpenMode --171*172* Description:173* Computes a POSIX mode mask for opening a file, from a given string,174* and also sets a flag to indicate whether the caller should seek to175* EOF after opening the file.176*177* Results:178* On success, returns mode to pass to "open". If an error occurs, the179* returns -1 and if interp is not NULL, sets interp->result to an180* error message.181*182* Side effects:183* Sets the integer referenced by seekFlagPtr to 1 to tell the caller184* to seek to EOF after opening the file.185*186* Special note:187* This code is based on a prototype implementation contributed188* by Mark Diekhans.189*190*----------------------------------------------------------------------191*/192193int194TclGetOpenMode(interp, string, seekFlagPtr)195Tcl_Interp *interp; /* Interpreter to use for error196* reporting - may be NULL. */197char *string; /* Mode string, e.g. "r+" or198* "RDONLY CREAT". */199int *seekFlagPtr; /* Set this to 1 if the caller200* should seek to EOF during the201* opening of the file. */202{203int mode, modeArgc, c, i, gotRW;204char **modeArgv, *flag;205#define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)206207/*208* Check for the simpler fopen-like access modes (e.g. "r"). They209* are distinguished from the POSIX access modes by the presence210* of a lower-case first letter.211*/212213*seekFlagPtr = 0;214mode = 0;215if (islower(UCHAR(string[0]))) {216switch (string[0]) {217case 'r':218mode = O_RDONLY;219break;220case 'w':221mode = O_WRONLY|O_CREAT|O_TRUNC;222break;223case 'a':224mode = O_WRONLY|O_CREAT;225*seekFlagPtr = 1;226break;227default:228error:229if (interp != (Tcl_Interp *) NULL) {230Tcl_AppendResult(interp,231"illegal access mode \"", string, "\"",232(char *) NULL);233}234return -1;235}236if (string[1] == '+') {237mode &= ~O_ACCMODE;238mode |= O_RDWR;239if (string[2] != 0) {240goto error;241}242} else if (string[1] != 0) {243goto error;244}245return mode;246}247248/*249* The access modes are specified using a list of POSIX modes250* such as O_CREAT.251*252* IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when253* a NULL interpreter is passed in.254*/255256if (Tcl_SplitList(interp, string, &modeArgc, &modeArgv) != TCL_OK) {257if (interp != (Tcl_Interp *) NULL) {258Tcl_AddErrorInfo(interp,259"\n while processing open access modes \"");260Tcl_AddErrorInfo(interp, string);261Tcl_AddErrorInfo(interp, "\"");262}263return -1;264}265266gotRW = 0;267for (i = 0; i < modeArgc; i++) {268flag = modeArgv[i];269c = flag[0];270if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {271mode = (mode & ~O_ACCMODE) | O_RDONLY;272gotRW = 1;273} else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {274mode = (mode & ~O_ACCMODE) | O_WRONLY;275gotRW = 1;276} else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {277mode = (mode & ~O_ACCMODE) | O_RDWR;278gotRW = 1;279} else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {280mode |= O_APPEND;281*seekFlagPtr = 1;282} else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {283mode |= O_CREAT;284} else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {285mode |= O_EXCL;286} else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {287#ifdef O_NOCTTY288mode |= O_NOCTTY;289#else290if (interp != (Tcl_Interp *) NULL) {291Tcl_AppendResult(interp, "access mode \"", flag,292"\" not supported by this system", (char *) NULL);293}294ckfree((char *) modeArgv);295return -1;296#endif297} else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {298#if defined(O_NDELAY) || defined(O_NONBLOCK)299# ifdef O_NONBLOCK300mode |= O_NONBLOCK;301# else302mode |= O_NDELAY;303# endif304#else305if (interp != (Tcl_Interp *) NULL) {306Tcl_AppendResult(interp, "access mode \"", flag,307"\" not supported by this system", (char *) NULL);308}309ckfree((char *) modeArgv);310return -1;311#endif312} else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {313mode |= O_TRUNC;314} else {315if (interp != (Tcl_Interp *) NULL) {316Tcl_AppendResult(interp, "invalid access mode \"", flag,317"\": must be RDONLY, WRONLY, RDWR, APPEND, CREAT",318" EXCL, NOCTTY, NONBLOCK, or TRUNC", (char *) NULL);319}320ckfree((char *) modeArgv);321return -1;322}323}324ckfree((char *) modeArgv);325if (!gotRW) {326if (interp != (Tcl_Interp *) NULL) {327Tcl_AppendResult(interp, "access mode must include either",328" RDONLY, WRONLY, or RDWR", (char *) NULL);329}330return -1;331}332return mode;333}334335#if 0336/*337*----------------------------------------------------------------------338*339* Tcl_EvalFile --340*341* Read in a file and process the entire file as one gigantic342* Tcl command.343*344* Results:345* A standard Tcl result, which is either the result of executing346* the file or an error indicating why the file couldn't be read.347*348* Side effects:349* Depends on the commands in the file.350*351*----------------------------------------------------------------------352*/353354int355Tcl_EvalFile(interp, fileName)356Tcl_Interp *interp; /* Interpreter in which to process file. */357char *fileName; /* Name of file to process. Tilde-substitution358* will be performed on this name. */359{360int result;361struct stat statBuf;362char *cmdBuffer = (char *) NULL;363char *oldScriptFile = (char *) NULL;364Interp *iPtr = (Interp *) interp;365Tcl_DString buffer;366char *nativeName = (char *) NULL;367Tcl_Channel chan = (Tcl_Channel) NULL;368369Tcl_ResetResult(interp);370oldScriptFile = iPtr->scriptFile;371iPtr->scriptFile = fileName;372Tcl_DStringInit(&buffer);373nativeName = Tcl_TranslateFileName(interp, fileName, &buffer);374if (nativeName == NULL) {375goto error;376}377378/*379* If Tcl_TranslateFileName didn't already copy the file name, do it380* here. This way we don't depend on fileName staying constant381* throughout the execution of the script (e.g., what if it happens382* to point to a Tcl variable that the script could change?).383*/384385if (nativeName != Tcl_DStringValue(&buffer)) {386Tcl_DStringSetLength(&buffer, 0);387Tcl_DStringAppend(&buffer, nativeName, -1);388nativeName = Tcl_DStringValue(&buffer);389}390if (stat(nativeName, &statBuf) == -1) {391Tcl_SetErrno(errno);392Tcl_AppendResult(interp, "couldn't read file \"", fileName,393"\": ", Tcl_PosixError(interp), (char *) NULL);394goto error;395}396chan = Tcl_OpenFileChannel(interp, nativeName, "r", 0644);397if (chan == (Tcl_Channel) NULL) {398Tcl_ResetResult(interp);399Tcl_AppendResult(interp, "couldn't read file \"", fileName,400"\": ", Tcl_PosixError(interp), (char *) NULL);401goto error;402}403cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);404result = Tcl_Read(chan, cmdBuffer, statBuf.st_size);405if (result < 0) {406Tcl_Close(interp, chan);407Tcl_AppendResult(interp, "couldn't read file \"", fileName,408"\": ", Tcl_PosixError(interp), (char *) NULL);409goto error;410}411cmdBuffer[result] = 0;412if (Tcl_Close(interp, chan) != TCL_OK) {413goto error;414}415416result = Tcl_Eval(interp, cmdBuffer);417if (result == TCL_RETURN) {418result = TclUpdateReturnInfo(iPtr);419} else if (result == TCL_ERROR) {420char msg[200];421422/*423* Record information telling where the error occurred.424*/425426sprintf(msg, "\n (file \"%.150s\" line %d)", fileName,427interp->errorLine);428Tcl_AddErrorInfo(interp, msg);429}430iPtr->scriptFile = oldScriptFile;431ckfree(cmdBuffer);432Tcl_DStringFree(&buffer);433return result;434435error:436if (cmdBuffer != (char *) NULL) {437ckfree(cmdBuffer);438}439iPtr->scriptFile = oldScriptFile;440Tcl_DStringFree(&buffer);441return TCL_ERROR;442}443444/*445*----------------------------------------------------------------------446*447* Tcl_DetachPids --448*449* This procedure is called to indicate that one or more child450* processes have been placed in background and will never be451* waited for; they should eventually be reaped by452* Tcl_ReapDetachedProcs.453*454* Results:455* None.456*457* Side effects:458* None.459*460*----------------------------------------------------------------------461*/462463void464Tcl_DetachPids(numPids, pidPtr)465int numPids; /* Number of pids to detach: gives size466* of array pointed to by pidPtr. */467int *pidPtr; /* Array of pids to detach. */468{469register Detached *detPtr;470int i;471472for (i = 0; i < numPids; i++) {473detPtr = (Detached *) ckalloc(sizeof(Detached));474detPtr->pid = pidPtr[i];475detPtr->nextPtr = detList;476detList = detPtr;477}478}479480/*481*----------------------------------------------------------------------482*483* Tcl_ReapDetachedProcs --484*485* This procedure checks to see if any detached processes have486* exited and, if so, it "reaps" them by officially waiting on487* them. It should be called "occasionally" to make sure that488* all detached processes are eventually reaped.489*490* Results:491* None.492*493* Side effects:494* Processes are waited on, so that they can be reaped by the495* system.496*497*----------------------------------------------------------------------498*/499500void501Tcl_ReapDetachedProcs()502{503register Detached *detPtr;504Detached *nextPtr, *prevPtr;505int status;506int pid;507508for (detPtr = detList, prevPtr = NULL; detPtr != NULL; ) {509pid = (int) Tcl_WaitPid(detPtr->pid, &status, WNOHANG);510if ((pid == 0) || ((pid == -1) && (errno != ECHILD))) {511prevPtr = detPtr;512detPtr = detPtr->nextPtr;513continue;514}515nextPtr = detPtr->nextPtr;516if (prevPtr == NULL) {517detList = detPtr->nextPtr;518} else {519prevPtr->nextPtr = detPtr->nextPtr;520}521ckfree((char *) detPtr);522detPtr = nextPtr;523}524}525526/*527*----------------------------------------------------------------------528*529* TclCleanupChildren --530*531* This is a utility procedure used to wait for child processes532* to exit, record information about abnormal exits, and then533* collect any stderr output generated by them.534*535* Results:536* The return value is a standard Tcl result. If anything at537* weird happened with the child processes, TCL_ERROR is returned538* and a message is left in interp->result.539*540* Side effects:541* If the last character of interp->result is a newline, then it542* is removed unless keepNewline is non-zero. File errorId gets543* closed, and pidPtr is freed back to the storage allocator.544*545*----------------------------------------------------------------------546*/547548int549TclCleanupChildren(interp, numPids, pidPtr, errorChan)550Tcl_Interp *interp; /* Used for error messages. */551int numPids; /* Number of entries in pidPtr array. */552int *pidPtr; /* Array of process ids of children. */553Tcl_Channel errorChan; /* Channel for file containing stderr output554* from pipeline. NULL means there isn't any555* stderr output. */556{557int result = TCL_OK;558int i, pid, abnormalExit, anyErrorInfo;559WAIT_STATUS_TYPE waitStatus;560char *msg;561562abnormalExit = 0;563for (i = 0; i < numPids; i++) {564pid = (int) Tcl_WaitPid(pidPtr[i], (int *) &waitStatus, 0);565if (pid == -1) {566result = TCL_ERROR;567if (interp != (Tcl_Interp *) NULL) {568msg = Tcl_PosixError(interp);569if (errno == ECHILD) {570/*571* This changeup in message suggested by Mark Diekhans572* to remind people that ECHILD errors can occur on573* some systems if SIGCHLD isn't in its default state.574*/575576msg =577"child process lost (is SIGCHLD ignored or trapped?)";578}579Tcl_AppendResult(interp, "error waiting for process to exit: ",580msg, (char *) NULL);581}582continue;583}584585/*586* Create error messages for unusual process exits. An587* extra newline gets appended to each error message, but588* it gets removed below (in the same fashion that an589* extra newline in the command's output is removed).590*/591592if (!WIFEXITED(waitStatus) || (WEXITSTATUS(waitStatus) != 0)) {593char msg1[20], msg2[20];594595result = TCL_ERROR;596sprintf(msg1, "%d", pid);597if (WIFEXITED(waitStatus)) {598if (interp != (Tcl_Interp *) NULL) {599sprintf(msg2, "%d", WEXITSTATUS(waitStatus));600Tcl_SetErrorCode(interp, "CHILDSTATUS", msg1, msg2,601(char *) NULL);602}603abnormalExit = 1;604} else if (WIFSIGNALED(waitStatus)) {605if (interp != (Tcl_Interp *) NULL) {606char *p;607608p = Tcl_SignalMsg((int) (WTERMSIG(waitStatus)));609Tcl_SetErrorCode(interp, "CHILDKILLED", msg1,610Tcl_SignalId((int) (WTERMSIG(waitStatus))), p,611(char *) NULL);612Tcl_AppendResult(interp, "child killed: ", p, "\n",613(char *) NULL);614}615} else if (WIFSTOPPED(waitStatus)) {616if (interp != (Tcl_Interp *) NULL) {617char *p;618619p = Tcl_SignalMsg((int) (WSTOPSIG(waitStatus)));620Tcl_SetErrorCode(interp, "CHILDSUSP", msg1,621Tcl_SignalId((int) (WSTOPSIG(waitStatus))),622p, (char *) NULL);623Tcl_AppendResult(interp, "child suspended: ", p, "\n",624(char *) NULL);625}626} else {627if (interp != (Tcl_Interp *) NULL) {628Tcl_AppendResult(interp,629"child wait status didn't make sense\n",630(char *) NULL);631}632}633}634}635636/*637* Read the standard error file. If there's anything there,638* then return an error and add the file's contents to the result639* string.640*/641642anyErrorInfo = 0;643if (errorChan != NULL) {644645/*646* Make sure we start at the beginning of the file.647*/648649Tcl_Seek(errorChan, 0L, SEEK_SET);650651if (interp != (Tcl_Interp *) NULL) {652while (1) {653#define BUFFER_SIZE 1000654char buffer[BUFFER_SIZE+1];655int count;656657count = Tcl_Read(errorChan, buffer, BUFFER_SIZE);658if (count == 0) {659break;660}661result = TCL_ERROR;662if (count < 0) {663Tcl_AppendResult(interp,664"error reading stderr output file: ",665Tcl_PosixError(interp), (char *) NULL);666break; /* out of the "while (1)" loop. */667}668buffer[count] = 0;669Tcl_AppendResult(interp, buffer, (char *) NULL);670anyErrorInfo = 1;671}672}673674Tcl_Close((Tcl_Interp *) NULL, errorChan);675}676677/*678* If a child exited abnormally but didn't output any error information679* at all, generate an error message here.680*/681682if (abnormalExit && !anyErrorInfo && (interp != (Tcl_Interp *) NULL)) {683Tcl_AppendResult(interp, "child process exited abnormally",684(char *) NULL);685}686687return result;688}689690/*691*----------------------------------------------------------------------692*693* TclCreatePipeline --694*695* Given an argc/argv array, instantiate a pipeline of processes696* as described by the argv.697*698* Results:699* The return value is a count of the number of new processes700* created, or -1 if an error occurred while creating the pipeline.701* *pidArrayPtr is filled in with the address of a dynamically702* allocated array giving the ids of all of the processes. It703* is up to the caller to free this array when it isn't needed704* anymore. If inPipePtr is non-NULL, *inPipePtr is filled in705* with the file id for the input pipe for the pipeline (if any):706* the caller must eventually close this file. If outPipePtr707* isn't NULL, then *outPipePtr is filled in with the file id708* for the output pipe from the pipeline: the caller must close709* this file. If errFilePtr isn't NULL, then *errFilePtr is filled710* with a file id that may be used to read error output after the711* pipeline completes.712*713* Side effects:714* Processes and pipes are created.715*716*----------------------------------------------------------------------717*/718719int720TclCreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,721outPipePtr, errFilePtr)722Tcl_Interp *interp; /* Interpreter to use for error reporting. */723int argc; /* Number of entries in argv. */724char **argv; /* Array of strings describing commands in725* pipeline plus I/O redirection with <,726* <<, >, etc. Argv[argc] must be NULL. */727int **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with728* address of array of pids for processes729* in pipeline (first pid is first process730* in pipeline). */731Tcl_File *inPipePtr; /* If non-NULL, input to the pipeline comes732* from a pipe (unless overridden by733* redirection in the command). The file734* id with which to write to this pipe is735* stored at *inPipePtr. NULL means command736* specified its own input source. */737Tcl_File *outPipePtr; /* If non-NULL, output to the pipeline goes738* to a pipe, unless overriden by redirection739* in the command. The file id with which to740* read frome this pipe is stored at741* *outPipePtr. NULL means command specified742* its own output sink. */743Tcl_File *errFilePtr; /* If non-NULL, all stderr output from the744* pipeline will go to a temporary file745* created here, and a descriptor to read746* the file will be left at *errFilePtr.747* The file will be removed already, so748* closing this descriptor will be the end749* of the file. If this is NULL, then750* all stderr output goes to our stderr.751* If the pipeline specifies redirection752* then the file will still be created753* but it will never get any data. */754{755#if defined( MAC_TCL )756Tcl_AppendResult(interp,757"command pipelines not supported on Macintosh OS", NULL);758return -1;759#else /* !MAC_TCL */760int *pidPtr = NULL; /* Points to malloc-ed array holding all761* the pids of child processes. */762int numPids = 0; /* Actual number of processes that exist763* at *pidPtr right now. */764int cmdCount; /* Count of number of distinct commands765* found in argc/argv. */766char *inputLiteral = NULL; /* If non-null, then this points to a767* string containing input data (specified768* via <<) to be piped to the first process769* in the pipeline. */770Tcl_File inputFile = NULL; /* If != NULL, gives file to use as input for771* first process in pipeline (specified via <772* or <@). */773Tcl_DString inputFileName; /* If non-empty, gives name of file that774* corresponds to inputFile. */775int inputClose = 0; /* If non-zero, then inputFile should be776* closed when cleaning up. */777Tcl_File outputFile = NULL; /* Writable file for output from last command778* in pipeline (could be file or pipe). NULL779* means use stdout. */780Tcl_DString outputFileName; /* If non-empty, gives name of file that781* corresponds to outputFile. */782int outputClose = 0; /* If non-zero, then outputFile should be783* closed when cleaning up. */784Tcl_File errorFile = NULL; /* Writable file for error output from all785* commands in pipeline. NULL means use786* stderr. */787Tcl_DString errorFileName; /* If non-empty, gives name of file that788* corresponds to errorFile. */789int errorClose = 0; /* If non-zero, then errorFile should be790* closed when cleaning up. */791char *p;792int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput;793Tcl_DString execBuffer;794Tcl_File pipeIn;795Tcl_File curInFile, curOutFile, curErrFile;796char *curInFileName, *curOutFileName, *curErrFileName;797Tcl_Channel channel;798799if (inPipePtr != NULL) {800*inPipePtr = NULL;801}802if (outPipePtr != NULL) {803*outPipePtr = NULL;804}805if (errFilePtr != NULL) {806*errFilePtr = NULL;807}808809Tcl_DStringInit(&inputFileName);810Tcl_DStringInit(&outputFileName);811Tcl_DStringInit(&errorFileName);812Tcl_DStringInit(&execBuffer);813814pipeIn = NULL;815curInFile = NULL;816curOutFile = NULL;817curErrFile = NULL;818819numPids = 0;820pidPtr = NULL;821822/*823* First, scan through all the arguments to figure out the structure824* of the pipeline. Process all of the input and output redirection825* arguments and remove them from the argument list in the pipeline.826* Count the number of distinct processes (it's the number of "|"827* arguments plus one) but don't remove the "|" arguments because828* they'll be used in the second pass to seperate the individual829* child processes. Cannot start the child processes in this pass830* because the redirection symbols may appear anywhere in the831* command line -- e.g., the '<' that specifies the input to the832* entire pipe may appear at the very end of the argument list.833*/834835lastBar = -1;836cmdCount = 1;837for (i = 0; i < argc; i++) {838skip = 0;839p = argv[i];840switch (*p++) {841case '|':842if (*p == '&') {843p++;844}845if (*p == '\0') {846if ((i == (lastBar + 1)) || (i == (argc - 1))) {847interp->result = "illegal use of | or |& in command";848goto error;849}850}851lastBar = i;852cmdCount++;853break;854855case '<':856if (inputClose != 0) {857inputClose = 0;858Tcl_DStringFree(&inputFileName);859TclCloseFile(inputFile);860}861if (*p == '<') {862inputFile = NULL;863inputLiteral = p + 1;864skip = 1;865if (*inputLiteral == '\0') {866inputLiteral = argv[i + 1];867if (inputLiteral == NULL) {868Tcl_AppendResult(interp, "can't specify \"", argv[i],869"\" as last word in command", (char *) NULL);870goto error;871}872skip = 2;873}874} else {875inputLiteral = NULL;876inputFile = FileForRedirect(interp, p, 1, argv[i],877argv[i + 1], O_RDONLY, &skip, &inputClose,878&inputFileName);879if (inputFile == NULL) {880goto error;881}882}883break;884885case '>':886atOK = 1;887flags = O_WRONLY | O_CREAT | O_TRUNC;888errorToOutput = 0;889if (*p == '>') {890p++;891atOK = 0;892flags = O_WRONLY | O_CREAT;893}894if (*p == '&') {895if (errorClose != 0) {896errorClose = 0;897Tcl_DStringFree(&errorFileName);898TclCloseFile(errorFile);899}900errorToOutput = 1;901p++;902}903904if (outputClose != 0) {905outputClose = 0;906Tcl_DStringFree(&outputFileName);907TclCloseFile(outputFile);908}909outputFile = FileForRedirect(interp, p, atOK, argv[i],910argv[i + 1], flags, &skip, &outputClose,911&outputFileName);912if (outputFile == NULL) {913goto error;914}915if (atOK == 0) {916TclSeekFile(outputFile, 0, SEEK_END);917}918if (errorToOutput) {919errorClose = 0;920errorFile = outputFile;921}922break;923924case '2':925if (*p != '>') {926break;927}928p++;929atOK = 1;930flags = O_WRONLY | O_CREAT | O_TRUNC;931if (*p == '>') {932p++;933atOK = 0;934flags = O_WRONLY | O_CREAT;935}936if (errorClose != 0) {937errorClose = 0;938Tcl_DStringFree(&errorFileName);939TclCloseFile(errorFile);940}941errorFile = FileForRedirect(interp, p, atOK, argv[i],942argv[i + 1], flags, &skip, &errorClose,943&errorFileName);944if (errorFile == NULL) {945goto error;946}947if (atOK == 0) {948TclSeekFile(errorFile, 0, SEEK_END);949}950break;951}952953if (skip != 0) {954for (j = i + skip; j < argc; j++) {955argv[j - skip] = argv[j];956}957argc -= skip;958i -= 1;959}960}961962if (inputFile == NULL) {963if (inputLiteral != NULL) {964/*965* The input for the first process is immediate data coming from966* Tcl. Create a temporary file for it and put the data into the967* file.968*/969inputFile = TclCreateTempFile(inputLiteral, &inputFileName);970if (inputFile == NULL) {971Tcl_AppendResult(interp,972"couldn't create input file for command: ",973Tcl_PosixError(interp), (char *) NULL);974goto error;975}976inputClose = 1;977} else if (inPipePtr != NULL) {978/*979* The input for the first process in the pipeline is to980* come from a pipe that can be written from by the caller.981*/982983if (TclCreatePipe(&inputFile, inPipePtr) == 0) {984Tcl_AppendResult(interp,985"couldn't create input pipe for command: ",986Tcl_PosixError(interp), (char *) NULL);987goto error;988}989inputClose = 1;990} else {991/*992* The input for the first process comes from stdin.993*/994995channel = Tcl_GetStdChannel(TCL_STDIN);996if (channel != NULL) {997inputFile = Tcl_GetChannelFile(channel, TCL_READABLE);998}999}1000}10011002if (outputFile == NULL) {1003if (outPipePtr != NULL) {1004/*1005* Output from the last process in the pipeline is to go to a1006* pipe that can be read by the caller.1007*/10081009if (TclCreatePipe(outPipePtr, &outputFile) == 0) {1010Tcl_AppendResult(interp,1011"couldn't create output pipe for command: ",1012Tcl_PosixError(interp), (char *) NULL);1013goto error;1014}1015outputClose = 1;1016} else {1017/*1018* The output for the last process goes to stdout.1019*/10201021channel = Tcl_GetStdChannel(TCL_STDOUT);1022if (channel) {1023outputFile = Tcl_GetChannelFile(channel, TCL_WRITABLE);1024}1025}1026}10271028if (errorFile == NULL) {1029if (errFilePtr != NULL) {1030/*1031* Set up the standard error output sink for the pipeline, if1032* requested. Use a temporary file which is opened, then deleted.1033* Could potentially just use pipe, but if it filled up it could1034* cause the pipeline to deadlock: we'd be waiting for processes1035* to complete before reading stderr, and processes couldn't1036* complete because stderr was backed up.1037*/10381039errorFile = TclCreateTempFile(NULL, &errorFileName);1040if (errorFile == NULL) {1041Tcl_AppendResult(interp,1042"couldn't create error file for command: ",1043Tcl_PosixError(interp), (char *) NULL);1044goto error;1045}1046*errFilePtr = errorFile;1047} else {1048/*1049* Errors from the pipeline go to stderr.1050*/10511052channel = Tcl_GetStdChannel(TCL_STDERR);1053if (channel) {1054errorFile = Tcl_GetChannelFile(channel, TCL_WRITABLE);1055}1056}1057}10581059/*1060* Scan through the argc array, creating a process for each1061* group of arguments between the "|" characters.1062*/10631064Tcl_ReapDetachedProcs();1065pidPtr = (int *) ckalloc((unsigned) (cmdCount * sizeof(int)));10661067curInFile = inputFile;1068curInFileName = Tcl_DStringValue(&inputFileName);1069if (curInFileName[0] == '\0') {1070curInFileName = NULL;1071}10721073for (i = 0; i < argc; i = lastArg + 1) {1074int joinThisError, pid;10751076/*1077* Convert the program name into native form.1078*/10791080argv[i] = Tcl_TranslateFileName(interp, argv[i], &execBuffer);1081if (argv[i] == NULL) {1082goto error;1083}10841085/*1086* Find the end of the current segment of the pipeline.1087*/10881089joinThisError = 0;1090for (lastArg = i; lastArg < argc; lastArg++) {1091if (argv[lastArg][0] == '|') {1092if (argv[lastArg][1] == '\0') {1093break;1094}1095if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) {1096joinThisError = 1;1097break;1098}1099}1100}1101argv[lastArg] = NULL;11021103/*1104* If this is the last segment, use the specified outputFile.1105* Otherwise create an intermediate pipe. pipeIn will become the1106* curInFile for the next segment of the pipe.1107*/11081109if (lastArg == argc) {1110curOutFile = outputFile;1111curOutFileName = Tcl_DStringValue(&outputFileName);1112if (curOutFileName[0] == '\0') {1113curOutFileName = NULL;1114}1115} else {1116if (TclCreatePipe(&pipeIn, &curOutFile) == 0) {1117Tcl_AppendResult(interp, "couldn't create pipe: ",1118Tcl_PosixError(interp), (char *) NULL);1119goto error;1120}1121curOutFileName = NULL;1122}11231124if (joinThisError != 0) {1125curErrFile = curOutFile;1126curErrFileName = curOutFileName;1127} else {1128curErrFile = errorFile;1129curErrFileName = Tcl_DStringValue(&errorFileName);1130if (curErrFileName[0] == '\0') {1131curErrFileName = NULL;1132}1133}11341135if (TclpCreateProcess(interp, lastArg - i, argv + i,1136curInFile, curOutFile, curErrFile, curInFileName,1137curOutFileName, curErrFileName, &pid) != TCL_OK) {1138goto error;1139}1140Tcl_DStringFree(&execBuffer);11411142pidPtr[numPids] = pid;1143numPids++;11441145/*1146* Close off our copies of file descriptors that were set up for1147* this child, then set up the input for the next child.1148*/11491150if ((curInFile != NULL) && (curInFile != inputFile)) {1151TclCloseFile(curInFile);1152}1153curInFile = pipeIn;1154curInFileName = NULL;1155pipeIn = NULL;11561157if ((curOutFile != NULL) && (curOutFile != outputFile)) {1158TclCloseFile(curOutFile);1159}1160curOutFile = NULL;1161}11621163*pidArrayPtr = pidPtr;11641165/*1166* All done. Cleanup open files lying around and then return.1167*/11681169cleanup:1170Tcl_DStringFree(&inputFileName);1171Tcl_DStringFree(&outputFileName);1172Tcl_DStringFree(&errorFileName);1173Tcl_DStringFree(&execBuffer);11741175if (inputClose) {1176TclCloseFile(inputFile);1177}1178if (outputClose) {1179TclCloseFile(outputFile);1180}1181if (errorClose) {1182TclCloseFile(errorFile);1183}1184return numPids;11851186/*1187* An error occurred. There could have been extra files open, such1188* as pipes between children. Clean them all up. Detach any child1189* processes that have been created.1190*/11911192error:1193if (pipeIn != NULL) {1194TclCloseFile(pipeIn);1195}1196if ((curOutFile != NULL) && (curOutFile != outputFile)) {1197TclCloseFile(curOutFile);1198}1199if ((curInFile != NULL) && (curInFile != inputFile)) {1200TclCloseFile(curInFile);1201}1202if ((inPipePtr != NULL) && (*inPipePtr != NULL)) {1203TclCloseFile(*inPipePtr);1204*inPipePtr = NULL;1205}1206if ((outPipePtr != NULL) && (*outPipePtr != NULL)) {1207TclCloseFile(*outPipePtr);1208*outPipePtr = NULL;1209}1210if ((errFilePtr != NULL) && (*errFilePtr != NULL)) {1211TclCloseFile(*errFilePtr);1212*errFilePtr = NULL;1213}1214if (pidPtr != NULL) {1215for (i = 0; i < numPids; i++) {1216if (pidPtr[i] != -1) {1217Tcl_DetachPids(1, &pidPtr[i]);1218}1219}1220ckfree((char *) pidPtr);1221}1222numPids = -1;1223goto cleanup;1224#endif /* !MAC_TCL */1225}1226#endif12271228/*1229*----------------------------------------------------------------------1230*1231* Tcl_GetErrno --1232*1233* Gets the current value of the Tcl error code variable. This is1234* currently the global variable "errno" but could in the future1235* change to something else.1236*1237* Results:1238* The value of the Tcl error code variable.1239*1240* Side effects:1241* None. Note that the value of the Tcl error code variable is1242* UNDEFINED if a call to Tcl_SetErrno did not precede this call.1243*1244*----------------------------------------------------------------------1245*/12461247int1248Tcl_GetErrno()1249{1250return errno;1251}12521253/*1254*----------------------------------------------------------------------1255*1256* Tcl_SetErrno --1257*1258* Sets the Tcl error code variable to the supplied value.1259*1260* Results:1261* None.1262*1263* Side effects:1264* Modifies the value of the Tcl error code variable.1265*1266*----------------------------------------------------------------------1267*/12681269void1270Tcl_SetErrno(err)1271int err; /* The new value. */1272{1273errno = err;1274}12751276/*1277*----------------------------------------------------------------------1278*1279* Tcl_PosixError --1280*1281* This procedure is typically called after UNIX kernel calls1282* return errors. It stores machine-readable information about1283* the error in $errorCode returns an information string for1284* the caller's use.1285*1286* Results:1287* The return value is a human-readable string describing the1288* error.1289*1290* Side effects:1291* The global variable $errorCode is reset.1292*1293*----------------------------------------------------------------------1294*/12951296char *1297Tcl_PosixError(interp)1298Tcl_Interp *interp; /* Interpreter whose $errorCode variable1299* is to be changed. */1300{1301char *id, *msg;13021303msg = Tcl_ErrnoMsg(errno);1304id = Tcl_ErrnoId();1305Tcl_SetErrorCode(interp, "POSIX", id, msg, (char *) NULL);1306return msg;1307}13081309#if 11310/*1311*----------------------------------------------------------------------1312*1313* Tcl_OpenCommandChannel --1314*1315* Opens an I/O channel to one or more subprocesses specified1316* by argc and argv. The flags argument determines the1317* disposition of the stdio handles. If the TCL_STDIN flag is1318* set then the standard input for the first subprocess will1319* be tied to the channel: writing to the channel will provide1320* input to the subprocess. If TCL_STDIN is not set, then1321* standard input for the first subprocess will be the same as1322* this application's standard input. If TCL_STDOUT is set then1323* standard output from the last subprocess can be read from the1324* channel; otherwise it goes to this application's standard1325* output. If TCL_STDERR is set, standard error output for all1326* subprocesses is returned to the channel and results in an error1327* when the channel is closed; otherwise it goes to this1328* application's standard error. If TCL_ENFORCE_MODE is not set,1329* then argc and argv can redirect the stdio handles to override1330* TCL_STDIN, TCL_STDOUT, and TCL_STDERR; if it is set, then it1331* is an error for argc and argv to override stdio channels for1332* which TCL_STDIN, TCL_STDOUT, and TCL_STDERR have been set.1333*1334* Results:1335* A new command channel, or NULL on failure with an error1336* message left in interp.1337*1338* Side effects:1339* Creates processes, opens pipes.1340*1341*----------------------------------------------------------------------1342*/13431344Tcl_Channel1345Tcl_OpenCommandChannel(interp, argc, argv, flags)1346Tcl_Interp *interp; /* Interpreter for error reporting. Can1347* NOT be NULL. */1348int argc; /* How many arguments. */1349char **argv; /* Array of arguments for command pipe. */1350int flags; /* Or'ed combination of TCL_STDIN, TCL_STDOUT,1351* TCL_STDERR, and TCL_ENFORCE_MODE. */1352{1353Tcl_File *inPipePtr, *outPipePtr, *errFilePtr;1354Tcl_File inPipe, outPipe, errFile;1355int numPids, *pidPtr;1356Tcl_Channel channel;13571358inPipe = outPipe = errFile = NULL;13591360inPipePtr = (flags & TCL_STDIN) ? &inPipe : NULL;1361outPipePtr = (flags & TCL_STDOUT) ? &outPipe : NULL;1362errFilePtr = (flags & TCL_STDERR) ? &errFile : NULL;13631364numPids = TclCreatePipeline(interp, argc, argv, &pidPtr, inPipePtr,1365outPipePtr, errFilePtr);13661367if (numPids < 0) {1368goto error;1369}13701371/*1372* Verify that the pipes that were created satisfy the1373* readable/writable constraints.1374*/13751376if (flags & TCL_ENFORCE_MODE) {1377if ((flags & TCL_STDOUT) && (outPipe == NULL)) {1378Tcl_AppendResult(interp, "can't read output from command:",1379" standard output was redirected", (char *) NULL);1380goto error;1381}1382if ((flags & TCL_STDIN) && (inPipe == NULL)) {1383Tcl_AppendResult(interp, "can't write input to command:",1384" standard input was redirected", (char *) NULL);1385goto error;1386}1387}13881389channel = TclCreateCommandChannel(outPipe, inPipe, errFile,1390numPids, pidPtr);13911392if (channel == (Tcl_Channel) NULL) {1393Tcl_AppendResult(interp, "pipe for command could not be created",1394(char *) NULL);1395goto error;1396}1397return channel;13981399error:1400#if 01401if (numPids > 0) {1402Tcl_DetachPids(numPids, pidPtr);1403ckfree((char *) pidPtr);1404}1405#endif1406if (inPipe != NULL) {1407TclClosePipeFile(inPipe);1408}1409if (outPipe != NULL) {1410TclClosePipeFile(outPipe);1411}1412if (errFile != NULL) {1413TclClosePipeFile(errFile);1414}1415return NULL;1416}1417#endif141814191420