diff options
Diffstat (limited to 'blt/src/bltUnixPipe.c')
-rw-r--r-- | blt/src/bltUnixPipe.c | 1071 |
1 files changed, 1071 insertions, 0 deletions
diff --git a/blt/src/bltUnixPipe.c b/blt/src/bltUnixPipe.c new file mode 100644 index 00000000000..7eea26f97d1 --- /dev/null +++ b/blt/src/bltUnixPipe.c @@ -0,0 +1,1071 @@ +/* + * bltUnixPipe.c -- + * + * Originally taken from tclPipe.c and tclUnixPipe.c in the Tcl + * distribution, implements the former Tcl_CreatePipeline API. + * This file contains the generic portion of the command channel + * driver as well as various utility routines used in managing + * subprocesses. + * + * Copyright (c) 1997 by Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + */ + +#include "bltInt.h" +#include <fcntl.h> +#include <signal.h> + +#include "bltWait.h" + +#if (TCL_MAJOR_VERSION == 7) +typedef pid_t Tcl_Pid; + +#define FILEHANDLER_USES_TCLFILES 1 + +static int +Tcl_GetChannelHandle(channel, direction, clientDataPtr) + Tcl_Channel channel; + int direction; + ClientData *clientDataPtr; +{ + Tcl_File file; + + file = Tcl_GetChannelFile(channel, direction); + if (file == NULL) { + return TCL_ERROR; + } + *clientDataPtr = (ClientData)Tcl_GetFileInfo(file, NULL); + return TCL_OK; +} + +#else +typedef int Tcl_File; +#endif /* TCL_MAJOR_VERSION == 7 */ + +/* + *---------------------------------------------------------------------- + * + * OpenFile -- + * + * Open a file for use in a pipeline. + * + * Results: + * Returns a new TclFile handle or NULL on failure. + * + * Side effects: + * May cause a file to be created on the file system. + * + *---------------------------------------------------------------------- + */ + +static int +OpenFile(fname, mode) + char *fname; /* The name of the file to open. */ + int mode; /* In what mode to open the file? */ +{ + int fd; + + fd = open(fname, mode, 0666); + if (fd != -1) { + fcntl(fd, F_SETFD, FD_CLOEXEC); + + /* + * If the file is being opened for writing, seek to the end + * so we can append to any data already in the file. + */ + + if (mode & O_WRONLY) { + lseek(fd, 0, SEEK_END); + } + return fd; + } + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * CreateTempFile -- + * + * This function creates a temporary file initialized with an + * optional string, and returns a file handle with the file pointer + * at the beginning of the file. + * + * Results: + * A handle to a file. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +CreateTempFile(contents) + char *contents; /* String to write into temp file, or NULL. */ +{ + char fileName[L_tmpnam]; + int fd; + size_t length = (contents == NULL) ? 0 : strlen(contents); + + mkstemp(fileName); + fd = OpenFile(fileName, O_RDWR | O_CREAT | O_TRUNC); + unlink(fileName); + + if ((fd >= 0) && (length > 0)) { + for (;;) { + if (write(fd, contents, length) != -1) { + break; + } else if (errno != EINTR) { + close(fd); + return -1; + } + } + lseek(fd, 0, SEEK_SET); + } + return fd; +} + +/* + *---------------------------------------------------------------------- + * + * CreatePipe -- + * + * Creates a pipe - simply calls the pipe() function. + * + * Results: + * Returns 1 on success, 0 on failure. + * + * Side effects: + * Creates a pipe. + * + *---------------------------------------------------------------------- + */ + +static int +CreatePipe(inFilePtr, outFilePtr) + int *inFilePtr; /* (out) Descriptor for read side of pipe. */ + int *outFilePtr; /* (out) Descriptor for write side of pipe. */ +{ + int pipeIds[2]; + + if (pipe(pipeIds) != 0) { + return 0; + } + fcntl(pipeIds[0], F_SETFD, FD_CLOEXEC); + fcntl(pipeIds[1], F_SETFD, FD_CLOEXEC); + + *inFilePtr = pipeIds[0]; + *outFilePtr = pipeIds[1]; + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * CloseFile -- + * + * Implements a mechanism to close a UNIX file. + * + * Results: + * Returns 0 on success, or -1 on error, setting errno. + * + * Side effects: + * The file is closed. + * + *---------------------------------------------------------------------- + */ + +static int +CloseFile(fd) + int fd; /* File descriptor to be closed. */ +{ + if ((fd == 0) || (fd == 1) || (fd == 2)) { + return 0; /* Don't close stdin, stdout or stderr. */ + } +#if (TCL_MAJOR_VERSION > 7) + Tcl_DeleteFileHandler(fd); +#endif + return close(fd); +} + +/* + *---------------------------------------------------------------------- + * + * RestoreSignals -- + * + * This procedure is invoked in a forked child process just before + * exec-ing a new program to restore all signals to their default + * settings. + * + * Results: + * None. + * + * Side effects: + * Signal settings get changed. + * + *---------------------------------------------------------------------- + */ + +static void +RestoreSignals() +{ +#ifdef SIGABRT + signal(SIGABRT, SIG_DFL); +#endif +#ifdef SIGALRM + signal(SIGALRM, SIG_DFL); +#endif +#ifdef SIGFPE + signal(SIGFPE, SIG_DFL); +#endif +#ifdef SIGHUP + signal(SIGHUP, SIG_DFL); +#endif +#ifdef SIGILL + signal(SIGILL, SIG_DFL); +#endif +#ifdef SIGINT + signal(SIGINT, SIG_DFL); +#endif +#ifdef SIGPIPE + signal(SIGPIPE, SIG_DFL); +#endif +#ifdef SIGQUIT + signal(SIGQUIT, SIG_DFL); +#endif +#ifdef SIGSEGV + signal(SIGSEGV, SIG_DFL); +#endif +#ifdef SIGTERM + signal(SIGTERM, SIG_DFL); +#endif +#ifdef SIGUSR1 + signal(SIGUSR1, SIG_DFL); +#endif +#ifdef SIGUSR2 + signal(SIGUSR2, SIG_DFL); +#endif +#ifdef SIGCHLD + signal(SIGCHLD, SIG_DFL); +#endif +#ifdef SIGCONT + signal(SIGCONT, SIG_DFL); +#endif +#ifdef SIGTSTP + signal(SIGTSTP, SIG_DFL); +#endif +#ifdef SIGTTIN + signal(SIGTTIN, SIG_DFL); +#endif +#ifdef SIGTTOU + signal(SIGTTOU, SIG_DFL); +#endif +} + +/* + *---------------------------------------------------------------------- + * + * SetupStdFile -- + * + * Set up stdio file handles for the child process, using the + * current standard channels if no other files are specified. + * If no standard channel is defined, or if no file is associated + * with the channel, then the corresponding standard fd is closed. + * + * Results: + * Returns 1 on success, or 0 on failure. + * + * Side effects: + * Replaces stdio fds. + * + *---------------------------------------------------------------------- + */ + +static int +SetupStdFile(fd, type) + int fd; /* File descriptor to dup, or -1. */ + int type; /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */ +{ + int targetFd = 0; /* Initializations here needed only to */ + int direction = 0; /* prevent warnings about using uninitialized + * variables. */ + + switch (type) { + case TCL_STDIN: + targetFd = 0; + direction = TCL_READABLE; + break; + case TCL_STDOUT: + targetFd = 1; + direction = TCL_WRITABLE; + break; + case TCL_STDERR: + targetFd = 2; + direction = TCL_WRITABLE; + break; + } + if (fd < 0) { + Tcl_Channel channel; + + channel = Tcl_GetStdChannel(type); + if (channel) { + Tcl_GetChannelHandle(channel, direction, (ClientData *)&fd); + } + } + if (fd >= 0) { + if (fd != targetFd) { + if (dup2(fd, targetFd) == -1) { + return 0; + } + /* + * Must clear the close-on-exec flag for the target FD, since + * some systems (e.g. Ultrix) do not clear the CLOEXEC flag on + * the target FD. + */ + + fcntl(targetFd, F_SETFD, 0); + } else { + /* + * Since we aren't dup'ing the file, we need to explicitly clear + * the close-on-exec flag. + */ + fcntl(fd, F_SETFD, 0); + } + } else { + close(targetFd); + } + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * CreateProcess -- + * + * Create a child process that has the specified files as its + * standard input, output, and error. The child process runs + * asynchronously and runs with the same environment variables + * as the creating process. + * + * The path is searched to find the specified executable. + * + * Results: + * The return value is TCL_ERROR and an error message is left in + * interp->result if there was a problem creating the child + * process. Otherwise, the return value is TCL_OK and *pidPtr is + * filled with the process id of the child process. + * + * Side effects: + * A process is created. + * + *---------------------------------------------------------------------- + */ + +/* ARGSUSED */ +static int +CreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, pidPtr) + Tcl_Interp *interp; /* Interpreter in which to leave errors that + * occurred when creating the child process. + * Error messages from the child process + * itself are sent to errorFile. */ + int argc; /* Number of arguments in following array. */ + char **argv; /* Array of argument strings. argv[0] + * contains the name of the executable + * converted to native format (using the + * Tcl_TranslateFileName call). Additional + * arguments have not been converted. */ + int inputFile; /* If non-NULL, gives the file to use as + * input for the child process. If inputFile + * file is not readable or is NULL, the child + * will receive no standard input. */ + int outputFile; /* If non-NULL, gives the file that + * receives output from the child process. If + * outputFile file is not writeable or is + * NULL, output from the child will be + * discarded. */ + int errorFile; /* If non-NULL, gives the file that + * receives errors from the child process. If + * errorFile file is not writeable or is NULL, + * errors from the child will be discarded. + * errorFile may be the same as outputFile. */ + int *pidPtr; /* If this procedure is successful, pidPtr + * is filled with the process id of the child + * process. */ +{ + int errPipeIn, errPipeOut; + int joinThisError, count, status, fd; + char errSpace[200]; + int pid; + + errPipeIn = errPipeOut = -1; + pid = -1; + + /* + * Create a pipe that the child can use to return error + * information if anything goes wrong. + */ + + if (CreatePipe(&errPipeIn, &errPipeOut) == 0) { + Tcl_AppendResult(interp, "can't create pipe: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + joinThisError = (errorFile == outputFile); + pid = fork(); + if (pid == 0) { + fd = errPipeOut; + + /* + * Set up stdio file handles for the child process. + */ + + if (!SetupStdFile(inputFile, TCL_STDIN) || + !SetupStdFile(outputFile, TCL_STDOUT) || + (!joinThisError && !SetupStdFile(errorFile, TCL_STDERR)) || + (joinThisError && + ((dup2(1, 2) == -1) || (fcntl(2, F_SETFD, 0) != 0)))) { + sprintf(errSpace, "%dforked process can't set up input/output: ", + errno); + write(fd, errSpace, (size_t) strlen(errSpace)); + _exit(1); + } + /* + * Close the input side of the error pipe. + */ + + RestoreSignals(); + execvp(argv[0], &argv[0]); + sprintf(errSpace, "%dcan't execute \"%.150s\": ", errno, argv[0]); + write(fd, errSpace, (size_t) strlen(errSpace)); + _exit(1); + } + if (pid == -1) { + Tcl_AppendResult(interp, "can't fork child process: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + /* + * Read back from the error pipe to see if the child started + * up OK. The info in the pipe (if any) consists of a decimal + * errno value followed by an error message. + */ + + CloseFile(errPipeOut); + errPipeOut = -1; + + fd = errPipeIn; + count = read(fd, errSpace, (size_t) (sizeof(errSpace) - 1)); + if (count > 0) { + char *end; + + errSpace[count] = 0; + errno = strtol(errSpace, &end, 10); + Tcl_AppendResult(interp, end, Tcl_PosixError(interp), (char *)NULL); + goto error; + } + CloseFile(errPipeIn); + *pidPtr = pid; + return TCL_OK; + + error: + if (pid != -1) { + /* + * Reap the child process now if an error occurred during its + * startup. + */ + Tcl_WaitPid((Tcl_Pid)pid, &status, WNOHANG); + } + if (errPipeIn >= 0) { + CloseFile(errPipeIn); + } + if (errPipeOut >= 0) { + CloseFile(errPipeOut); + } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * FileForRedirect -- + * + * This procedure does much of the work of parsing redirection + * operators. It handles "@" if specified and allowed, and a file + * name, and opens the file if necessary. + * + * Results: + * The return value is the descriptor number for the file. If an + * error occurs then NULL is returned and an error message is left + * in interp->result. Several arguments are side-effected; see + * the argument list below for details. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +FileForRedirect(interp, spec, atOK, arg, nextArg, flags, skipPtr, closePtr) + Tcl_Interp *interp; /* Intepreter to use for error reporting. */ + char *spec; /* Points to character just after + * redirection character. */ + char *arg; /* Pointer to entire argument containing + * spec: used for error reporting. */ + int atOK; /* Non-zero means that '@' notation can be + * used to specify a channel, zero means that + * it isn't. */ + char *nextArg; /* Next argument in argc/argv array, if needed + * for file name or channel name. May be + * NULL. */ + int flags; /* Flags to use for opening file or to + * specify mode for channel. */ + int *skipPtr; /* Filled with 1 if redirection target was + * in spec, 2 if it was in nextArg. */ + int *closePtr; /* Filled with one if the caller should + * close the file when done with it, zero + * otherwise. */ +{ + int writing = (flags & O_WRONLY); + Tcl_Channel chan; + int fd; + int direction; + + *skipPtr = 1; + if ((atOK != 0) && (*spec == '@')) { + spec++; + if (*spec == '\0') { + spec = nextArg; + if (spec == NULL) { + goto badLastArg; + } + *skipPtr = 2; + } + chan = Tcl_GetChannel(interp, spec, NULL); + if (chan == NULL) { + return -1; + } + direction = (writing) ? TCL_WRITABLE : TCL_READABLE; + if (Tcl_GetChannelHandle(chan, direction, (ClientData *)&fd) != TCL_OK) { + fd = -1; + } + if (fd < 0) { + Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan), + "\" wasn't opened for ", + ((writing) ? "writing" : "reading"), (char *)NULL); + return -1; + } + if (writing) { + /* + * Be sure to flush output to the file, so that anything + * written by the child appears after stuff we've already + * written. + */ + Tcl_Flush(chan); + } + } else { + char *name; + Tcl_DString nameString; + + if (*spec == '\0') { + spec = nextArg; + if (spec == NULL) { + goto badLastArg; + } + *skipPtr = 2; + } + name = Tcl_TranslateFileName(interp, spec, &nameString); + + if (name != NULL) { + fd = OpenFile(name, flags); + } else { + fd = -1; + } + Tcl_DStringFree(&nameString); + if (fd < 0) { + Tcl_AppendResult(interp, "can't ", + ((writing) ? "write" : "read"), " file \"", spec, "\": ", + Tcl_PosixError(interp), (char *)NULL); + return -1; + } + *closePtr = 1; + } + return fd; + + badLastArg: + Tcl_AppendResult(interp, "can't specify \"", arg, + "\" as last word in command", (char *)NULL); + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * Blt_CreatePipeline -- + * + * Given an argc/argv array, instantiate a pipeline of processes + * as described by the argv. + * + * 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 inPipePtr is non-NULL, *inPipePtr is filled in + * with the file id for the input pipe for the pipeline (if any): + * the caller must eventually close this file. If outPipePtr + * isn't NULL, then *outPipePtr is filled in with the file id + * for the output pipe from the pipeline: the caller must close + * this file. If errPipePtr isn't NULL, then *errPipePtr 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(interp, argc, argv, pidArrayPtr, inPipePtr, + outPipePtr, errPipePtr) + Tcl_Interp *interp; /* Interpreter to use for error reporting. */ + int argc; /* Number of entries in argv. */ + char **argv; /* Array of strings describing commands in + * pipeline plus I/O redirection with <, + * <<, >, etc. Argv[argc] must be NULL. */ + int **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with + * address of array of pids for processes + * in pipeline (first pid is first process + * in pipeline). */ + int *inPipePtr; /* 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 *inPipePtr. NULL means command + * specified its own input source. */ + int *outPipePtr; /* 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 + * *outPipePtr. NULL means command specified + * its own output sink. */ + int *errPipePtr; /* 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 *errPipePtr. + * 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 *pidPtr = NULL; /* Points to malloc-ed array holding all + * the pids of child processes. */ + int nPids; /* Actual number of processes that exist + * at *pidPtr right now. */ + int cmdCount; /* Count of number of distinct commands + * found in argc/argv. */ + 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. */ + int inputFd = -1; /* If != NULL, gives file to use as input for + * first process in pipeline (specified via < + * or <@). */ + int inputClose = 0; /* If non-zero, then inputFd should be + * closed when cleaning up. */ + int outputFd = -1; /* Writable file for output from last command + * in pipeline (could be file or pipe). NULL + * means use stdout. */ + int outputClose = 0; /* If non-zero, then outputFd should be + * closed when cleaning up. */ + int errorFd = -1; /* Writable file for error output from all + * commands in pipeline. NULL means use + * stderr. */ + int errorClose = 0; /* If non-zero, then errorFd should be + * closed when cleaning up. */ + char *p; + int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput; + Tcl_DString execBuffer; + int pipeIn; + int curInFd, curOutFd, curErrFd; + + if (inPipePtr != NULL) { + *inPipePtr = -1; + } + if (outPipePtr != NULL) { + *outPipePtr = -1; + } + if (errPipePtr != NULL) { + *errPipePtr = -1; + } + Tcl_DStringInit(&execBuffer); + + pipeIn = curInFd = curOutFd = -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. + */ + + lastBar = -1; + cmdCount = 1; + for (i = 0; i < argc; i++) { + skip = 0; + p = argv[i]; + switch (*p++) { + case '|': + if (*p == '&') { + p++; + } + if (*p == '\0') { + if ((i == (lastBar + 1)) || (i == (argc - 1))) { + Tcl_AppendResult(interp, + "illegal use of | or |& in command", + (char *)NULL); + goto error; + } + } + lastBar = i; + cmdCount++; + break; + + case '<': + if (inputClose != 0) { + inputClose = 0; + CloseFile(inputFd); + } + if (*p == '<') { + inputFd = -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; + inputFd = FileForRedirect(interp, p, 1, argv[i], argv[i + 1], + O_RDONLY, &skip, &inputClose); + if (inputFd < 0) { + goto error; + } + } + break; + + case '>': + atOK = 1; + flags = O_WRONLY | O_CREAT | O_TRUNC; + errorToOutput = 0; + if (*p == '>') { + p++; + atOK = 0; + flags = O_WRONLY | O_CREAT; + } + if (*p == '&') { + if (errorClose != 0) { + errorClose = 0; + CloseFile(errorFd); + } + errorToOutput = 1; + p++; + } + if (outputClose != 0) { + outputClose = 0; + CloseFile(outputFd); + } + outputFd = FileForRedirect(interp, p, atOK, argv[i], argv[i + 1], + flags, &skip, &outputClose); + if (outputFd < 0) { + goto error; + } + if (errorToOutput) { + errorClose = 0; + errorFd = outputFd; + } + break; + + case '2': + if (*p != '>') { + break; + } + p++; + atOK = 1; + flags = O_WRONLY | O_CREAT | O_TRUNC; + if (*p == '>') { + p++; + atOK = 0; + flags = O_WRONLY | O_CREAT; + } + if (errorClose != 0) { + errorClose = 0; + CloseFile(errorFd); + } + errorFd = FileForRedirect(interp, p, atOK, argv[i], argv[i + 1], + flags, &skip, &errorClose); + if (errorFd < 0) { + goto error; + } + break; + } + + if (skip != 0) { + for (j = i + skip; j < argc; j++) { + argv[j - skip] = argv[j]; + } + argc -= skip; + i -= 1; + } + } + + if (inputFd == -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. + */ + inputFd = CreateTempFile(inputLiteral); + if (inputFd < 0) { + Tcl_AppendResult(interp, + "can't create input file for command: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + inputClose = 1; + } else if (inPipePtr != 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(&inputFd, inPipePtr) == 0) { + Tcl_AppendResult(interp, + "can't create input pipe for command: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + inputClose = 1; + } else { + /* + * The input for the first process comes from stdin. + */ + + inputFd = 0; + } + } + if (outputFd == -1) { + if (outPipePtr != NULL) { + /* + * Output from the last process in the pipeline is to go to a + * pipe that can be read by the caller. + */ + + if (CreatePipe(outPipePtr, &outputFd) == 0) { + Tcl_AppendResult(interp, + "can't create output pipe for command: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + outputClose = 1; + } else { + /* + * The output for the last process goes to stdout. + */ + outputFd = 1; + } + } + if (errorFd == -1) { + if (errPipePtr != NULL) { + /* + * Stderr from the last process in the pipeline is to go to a + * pipe that can be read by the caller. + */ + if (CreatePipe(errPipePtr, &errorFd) == 0) { + Tcl_AppendResult(interp, + "can't create error pipe for command: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + errorClose = 1; + } else { + /* + * Errors from the pipeline go to stderr. + */ + errorFd = 2; + } + } + /* + * Scan through the argc array, creating a process for each + * group of arguments between the "|" characters. + */ + + Tcl_ReapDetachedProcs(); + pidPtr = Blt_Malloc((unsigned)(cmdCount * sizeof(int))); + + curInFd = inputFd; + + lastArg = 0; /* Suppress compiler warning */ + for (i = 0; i < argc; 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 current segment of the pipeline. + */ + joinThisError = 0; + for (lastArg = i; lastArg < argc; 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 outputFile. + * Otherwise create an intermediate pipe. pipeIn will become the + * curInFile for the next segment of the pipe. + */ + + if (lastArg == argc) { + curOutFd = outputFd; + } else { + if (CreatePipe(&pipeIn, &curOutFd) == 0) { + Tcl_AppendResult(interp, "can't create pipe: ", + Tcl_PosixError(interp), (char *)NULL); + goto error; + } + } + + if (joinThisError != 0) { + curErrFd = curOutFd; + } else { + curErrFd = errorFd; + } + + if (CreateProcess(interp, lastArg - i, argv + i, + curInFd, curOutFd, curErrFd, &pid) != TCL_OK) { + goto error; + } + Tcl_DStringFree(&execBuffer); + + pidPtr[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 ((curInFd >= 0) && (curInFd != inputFd)) { + CloseFile(curInFd); + } + curInFd = pipeIn; + pipeIn = -1; + + if ((curOutFd >= 0) && (curOutFd != outputFd)) { + CloseFile(curOutFd); + } + curOutFd = -1; + } + + *pidArrayPtr = pidPtr; + + /* + * All done. Cleanup open files lying around and then return. + */ + + cleanup: + Tcl_DStringFree(&execBuffer); + + if (inputClose) { + CloseFile(inputFd); + } + if (outputClose) { + CloseFile(outputFd); + } + if (errorClose) { + CloseFile(errorFd); + } + return nPids; + + /* + * An error occurred. 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 ((curOutFd >= 0) && (curOutFd != outputFd)) { + CloseFile(curOutFd); + } + if ((curInFd >= 0) && (curInFd != inputFd)) { + CloseFile(curInFd); + } + if ((inPipePtr != NULL) && (*inPipePtr >= 0)) { + CloseFile(*inPipePtr); + *inPipePtr = -1; + } + if ((outPipePtr != NULL) && (*outPipePtr >= 0)) { + CloseFile(*outPipePtr); + *outPipePtr = -1; + } + if ((errPipePtr != NULL) && (*errPipePtr >= 0)) { + CloseFile(*errPipePtr); + *errPipePtr = -1; + } + if (pidPtr != NULL) { + for (i = 0; i < nPids; i++) { + if (pidPtr[i] != -1) { +#if (TCL_MAJOR_VERSION == 7) + Tcl_DetachPids(1, &pidPtr[i]); +#else + Tcl_DetachPids(1, (Tcl_Pid *)&pidPtr[i]); +#endif + } + } + Blt_Free(pidPtr); + } + nPids = -1; + goto cleanup; +} |