static int PipeClose2Proc( ClientData instanceData, /* The pipe to close. */ Tcl_Interp *interp, /* For error reporting. */ int flags) /* Flags that indicate which side to close. */ { PipeState *pipePtr = instanceData; Tcl_Channel errChan; int errorCode, result; errorCode = 0; result = 0; if (((!flags) || (flags & TCL_CLOSE_READ)) && (pipePtr->inFile != NULL)) { if (TclpCloseFile(pipePtr->inFile) < 0) { errorCode = errno; } else { pipePtr->inFile = NULL; } } if (((!flags) || (flags & TCL_CLOSE_WRITE)) && (pipePtr->outFile != NULL) && (errorCode == 0)) { if (TclpCloseFile(pipePtr->outFile) < 0) { errorCode = errno; } else { pipePtr->outFile = NULL; } } /* * If half-closing, stop here. */ if (flags) { return errorCode; } if (pipePtr->isNonBlocking || TclInExit()) { /* * If the channel is non-blocking or Tcl is being cleaned up, just * detach the children PIDs, reap them (important if we are in a * dynamic load module), and discard the errorFile. */ Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); Tcl_ReapDetachedProcs(); if (pipePtr->errorFile) { TclpCloseFile(pipePtr->errorFile); } } else { /* * Wrap the error file into a channel and give it to the cleanup * routine. */ if (pipePtr->errorFile) { errChan = Tcl_MakeFileChannel( INT2PTR(GetFd(pipePtr->errorFile)), TCL_READABLE); } else { errChan = NULL; } result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, errChan); } if (pipePtr->numPids != 0) { ckfree(pipePtr->pidPtr); } ckfree(pipePtr); if (errorCode == 0) { return result; } return errorCode; }
/* ARGSUSED */ static int PipeCloseProc( ClientData instanceData, /* The pipe to close. */ Tcl_Interp *interp) /* For error reporting. */ { PipeState *pipePtr; Tcl_Channel errChan; int errorCode, result; errorCode = 0; result = 0; pipePtr = (PipeState *) instanceData; if (pipePtr->inFile) { if (TclpCloseFile(pipePtr->inFile) < 0) { errorCode = errno; } } if (pipePtr->outFile) { if ((TclpCloseFile(pipePtr->outFile) < 0) && (errorCode == 0)) { errorCode = errno; } } if (pipePtr->isNonBlocking || TclInExit()) { /* * If the channel is non-blocking or Tcl is being cleaned up, just * detach the children PIDs, reap them (important if we are in a * dynamic load module), and discard the errorFile. */ Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); Tcl_ReapDetachedProcs(); if (pipePtr->errorFile) { TclpCloseFile(pipePtr->errorFile); } } else { /* * Wrap the error file into a channel and give it to the cleanup * routine. */ if (pipePtr->errorFile) { errChan = Tcl_MakeFileChannel( (ClientData) INT2PTR(GetFd(pipePtr->errorFile)), TCL_READABLE); } else { errChan = NULL; } result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, errChan); } if (pipePtr->numPids != 0) { ckfree((char *) pipePtr->pidPtr); } ckfree((char *) pipePtr); if (errorCode == 0) { return result; } return errorCode; }
/* *--------------------------------------------------------------------------- * * Blt_CreatePipeline -- * * Given an objc/objv array, instantiate a pipeline of processes as * described by the objv. * * Results: * The return value is a count of the number of new processes created, or * -1 if an error occurred while creating the pipeline. *pidArrayPtr is * filled in with the address of a dynamically allocated array giving the * ids of all of the processes. * * It is up to the caller to free this array when it isn't needed * anymore. * * If stdinPipePtr isn't NULL, then *stdinPipePtr is filled with the file * id for the input pipe for the pipeline (if any): the caller must * eventually close this file. * * If stdoutPipePtr isn't NULL, then *stdoutPipePtr is filled with the * file id for the output pipe from the pipeline: the caller must close * this file. * * If stderrPipePtr isn't NULL, then *stderrPipePtr is filled with a file * id that may be used to read error output after the pipeline completes. * * Side effects: * Processes and pipes are created. * *--------------------------------------------------------------------------- */ int Blt_CreatePipeline( Tcl_Interp *interp, /* Interpreter to use for error reporting. */ int objc, /* Number of entries in objv. */ Tcl_Obj *const *objv, /* Array of strings describing commands in * pipeline plus I/O redirection with <, <<, * >, etc. Objv[objc] must be NULL. */ ProcessId **pidArrayPtr, /* (out) Word at *pidArrayPtr gets filled in * with address of array of pids for processes * in pipeline (first pid is first process in * pipeline). */ int *stdinPipePtr, /* (out) If non-NULL, input to the pipeline * comes from a pipe (unless overridden by * redirection in the command). The file id * with which to write to this pipe is stored * at *stdinPipePtr. NULL means command * specified its own input source. */ int *stdoutPipePtr, /* (out) If non-NULL, output to the pipeline * goes to a pipe, unless overriden by * redirection in the command. The file id * with which to read frome this pipe is * stored at *stdoutPipePtr. NULL means * command specified its own output sink. */ int *stderrPipePtr) /* (out) If non-NULL, all stderr output from * the pipeline will go to a temporary file * created here, and a descriptor to read the * file will be left at *stderrPipePtr. The * file will be removed already, so closing * this descriptor will be the end of the * file. If this is NULL, then all stderr * output goes to our stderr. If the pipeline * specifies redirection then the file will * still be created but it will never get any * data. */ { int *pids = NULL; /* Points to malloc-ed array holding all the * pids of child processes. */ int nPids; /* Actual number of processes that exist at * *pids right now. */ int cmdCount; /* Count of number of distinct commands found * in objc/objv. */ char *inputLiteral = NULL; /* If non-null, then this points to a string * containing input data (specified via <<) to * be piped to the first process in the * pipeline. */ char *p; int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput; Tcl_DString execBuffer; int pipeIn; int isOpen[3]; int curFd[3]; /* If non-zero, then fd should be closed * when cleaning up. */ int fd[3]; char **argv; fd[0] = fd[1] = fd[2] = -1; isOpen[0] = isOpen[1] = isOpen[2] = FALSE; if (stdinPipePtr != NULL) { *stdinPipePtr = -1; } if (stdoutPipePtr != NULL) { *stdoutPipePtr = -1; } if (stderrPipePtr != NULL) { *stderrPipePtr = -1; } Tcl_DStringInit(&execBuffer); pipeIn = curFd[0] = curFd[1] = -1; nPids = 0; /* * First, scan through all the arguments to figure out the structure of * the pipeline. Process all of the input and output redirection * arguments and remove them from the argument list in the pipeline. * Count the number of distinct processes (it's the number of "|" * arguments plus one) but don't remove the "|" arguments because they'll * be used in the second pass to seperate the individual child processes. * * Cannot start the child processes in this pass because the redirection * symbols may appear anywhere in the command line -- e.g., the '<' that * specifies the input to the entire pipe may appear at the very end of * the argument list. */ /* Convert all the Tcl_Objs to strings. */ argv = Blt_AssertMalloc((objc + 1) * sizeof(char *)); for (i = 0; i < objc; i++) { argv[i] = Tcl_GetString(objv[i]); } argv[i] = NULL; lastBar = -1; cmdCount = 1; for (i = 0; i < objc; i++) { skip = 0; p = argv[i]; switch (*p++) { case '\\': p++; continue; case '|': if (*p == '&') { p++; } if (*p == '\0') { if ((i == (lastBar + 1)) || (i == (objc - 1))) { Tcl_AppendResult(interp, "illegal use of | or |& in command", (char *)NULL); goto error; } } lastBar = i; cmdCount++; break; case '<': if (isOpen[0] != 0) { isOpen[0] = FALSE; CloseFile(fd[0]); } if (*p == '<') { fd[0] = -1; inputLiteral = p + 1; skip = 1; if (*inputLiteral == '\0') { inputLiteral = argv[i + 1]; if (inputLiteral == NULL) { Tcl_AppendResult(interp, "can't specify \"", argv[i], "\" as last word in command", (char *)NULL); goto error; } skip = 2; } } else { inputLiteral = NULL; fd[0] = FileForRedirect(interp, p, argv[i], TRUE, argv[i + 1], O_RDONLY, &skip, &isOpen[0]); if (fd[0] < 0) { goto error; } } break; case '>': atOK = TRUE; flags = O_WRONLY | O_CREAT | O_TRUNC; errorToOutput = FALSE; if (*p == '>') { p++; atOK = FALSE; flags = O_WRONLY | O_CREAT; } if (*p == '&') { if (isOpen[2] != 0) { isOpen[2] = FALSE; CloseFile(fd[2]); } errorToOutput = TRUE; p++; } if (isOpen[1] != 0) { isOpen[1] = FALSE; CloseFile(fd[1]); } fd[1] = FileForRedirect(interp, p, argv[i], atOK, argv[i + 1], flags, &skip, &isOpen[1]); if (fd[1] < 0) { goto error; } if (errorToOutput) { isOpen[2] = FALSE; fd[2] = fd[1]; } break; case '2': if (*p != '>') { break; } p++; atOK = TRUE; flags = O_WRONLY | O_CREAT | O_TRUNC; if (*p == '>') { p++; atOK = FALSE; flags = O_WRONLY | O_CREAT; } if (isOpen[2] != 0) { isOpen[2] = FALSE; CloseFile(fd[2]); } fd[2] = FileForRedirect(interp, p, argv[i], atOK, argv[i + 1], flags, &skip, &isOpen[2]); if (fd[2] < 0) { goto error; } break; } if (skip != 0) { for (j = i + skip; j < objc; j++) { argv[j - skip] = argv[j]; } objc -= skip; i -= 1; } } if (fd[0] == -1) { if (inputLiteral != NULL) { /* * The input for the first process is immediate data coming from * Tcl. Create a temporary file for it and put the data into the * file. */ fd[0] = CreateTempFile(inputLiteral); if (fd[0] < 0) { Tcl_AppendResult(interp, "can't create input file for command: ", Tcl_PosixError(interp), (char *)NULL); goto error; } isOpen[0] = TRUE; } else if (stdinPipePtr != NULL) { /* * The input for the first process in the pipeline is to come from * a pipe that can be written from by the caller. */ if (CreatePipe(interp, &fd[0], stdinPipePtr) != TCL_OK) { goto error; } isOpen[0] = TRUE; } else { /* * The input for the first process comes from stdin. */ fd[0] = 0; } } if (fd[1] == -1) { if (stdoutPipePtr != NULL) { /* * Output from the last process in the pipeline is to go to a pipe * that can be read by the caller. */ if (CreatePipe(interp, stdoutPipePtr, &fd[1]) != TCL_OK) { goto error; } isOpen[1] = TRUE; } else { /* * The output for the last process goes to stdout. */ fd[1] = 1; } } if (fd[2] == -1) { if (stderrPipePtr != NULL) { /* * Stderr from the last process in the pipeline is to go to a pipe * that can be read by the caller. */ if (CreatePipe(interp, stderrPipePtr, &fd[2]) != TCL_OK) { goto error; } isOpen[2] = TRUE; } else { /* * Errors from the pipeline go to stderr. */ fd[2] = 2; } } /* * Scan through the objc array, creating a process for each group of * arguments between the "|" characters. */ Tcl_ReapDetachedProcs(); pids = Blt_AssertMalloc(cmdCount * sizeof(int)); curFd[0] = fd[0]; lastArg = 0; /* Suppress compiler warning */ for (i = 0; i < objc; i = lastArg + 1) { int joinThisError; int pid; /* * Convert the program name into native form. */ argv[i] = Tcl_TranslateFileName(interp, argv[i], &execBuffer); if (argv[i] == NULL) { goto error; } /* * Find the end of the curent segment of the pipeline. */ joinThisError = 0; for (lastArg = i + 1; lastArg < objc; lastArg++) { if (argv[lastArg][0] == '|') { if (argv[lastArg][1] == '\0') { break; } if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == '\0')) { joinThisError = 1; break; } } } argv[lastArg] = NULL; /* * If this is the last segment, use the specified fd[1]. Otherwise * create an intermediate pipe. pipeIn will become the curInFile for * the next segment of the pipe. */ if (lastArg == objc) { curFd[1] = fd[1]; } else { if (CreatePipe(interp, &pipeIn, &curFd[1]) != TCL_OK) { goto error; } } if (joinThisError != 0) { curFd[2] = curFd[1]; } else { curFd[2] = fd[2]; } if (CreateProcess(interp, lastArg - i, argv + i, curFd[0], curFd[1], curFd[2], &pid) != TCL_OK) { goto error; } Tcl_DStringFree(&execBuffer); pids[nPids] = pid; nPids++; /* * Close off our copies of file descriptors that were set up for this * child, then set up the input for the next child. */ if ((curFd[0] >= 0) && (curFd[0] != fd[0])) { CloseFile(curFd[0]); } curFd[0] = pipeIn; pipeIn = -1; if ((curFd[1] >= 0) && (curFd[1] != fd[1])) { CloseFile(curFd[1]); } curFd[1] = -1; } *pidArrayPtr = pids; /* * All done. Cleanup open files lying around and then return. */ cleanup: Tcl_DStringFree(&execBuffer); for (i = 0; i < 3; i++) { if (isOpen[i]) { CloseFile(fd[i]); } } if (argv != NULL) { Blt_Free(argv); } return nPids; /* * An error occured. There could have been extra files open, such as * pipes between children. Clean them all up. Detach any child processes * that have been created. */ error: if (pipeIn >= 0) { CloseFile(pipeIn); } if ((curFd[2] >= 0) && (curFd[2] != fd[2])) { CloseFile(curFd[2]); } if ((curFd[1] >= 0) && (curFd[1] != fd[1])) { CloseFile(curFd[1]); } if ((curFd[0] >= 0) && (curFd[0] != fd[0])) { CloseFile(curFd[0]); } if ((stdinPipePtr != NULL) && (*stdinPipePtr >= 0)) { CloseFile(*stdinPipePtr); *stdinPipePtr = -1; } if ((stdoutPipePtr != NULL) && (*stdoutPipePtr >= 0)) { CloseFile(*stdoutPipePtr); *stdoutPipePtr = -1; } if ((stderrPipePtr != NULL) && (*stderrPipePtr >= 0)) { CloseFile(*stderrPipePtr); *stderrPipePtr = -1; } if (pids != NULL) { for (i = 0; i < nPids; i++) { if (pids[i] != -1) { Tcl_DetachPids(1, (Tcl_Pid *)(pids + i)); } } Blt_Free(pids); } nPids = -1; goto cleanup; }