diff options
Diffstat (limited to 'contrib/tcl/unix/tclUnixPipe.c')
-rw-r--r-- | contrib/tcl/unix/tclUnixPipe.c | 1141 |
1 files changed, 897 insertions, 244 deletions
diff --git a/contrib/tcl/unix/tclUnixPipe.c b/contrib/tcl/unix/tclUnixPipe.c index a7ff1b3d7504d..f6d90d702cb22 100644 --- a/contrib/tcl/unix/tclUnixPipe.c +++ b/contrib/tcl/unix/tclUnixPipe.c @@ -1,25 +1,440 @@ /* - * tclUnixPipe.c -- This file implements the UNIX-specific exec pipeline - * functions. + * tclUnixPipe.c -- + * + * This file implements the UNIX-specific exec pipeline functions, + * the "pipe" channel driver, and the "pid" Tcl command. * * Copyright (c) 1991-1994 The Regents of the University of California. - * Copyright (c) 1994-1996 Sun Microsystems, Inc. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * SCCS: @(#) tclUnixPipe.c 1.29 96/04/18 15:56:26 + * SCCS: @(#) tclUnixPipe.c 1.36 97/05/14 13:24:24 */ #include "tclInt.h" #include "tclPort.h" /* + * The following macros convert between TclFile's and fd's. The conversion + * simple involves shifting fd's up by one to ensure that no valid fd is ever + * the same as NULL. + */ + +#define MakeFile(fd) ((TclFile)((fd)+1)) +#define GetFd(file) (((int)file)-1) + +/* + * This structure describes per-instance state of a pipe based channel. + */ + +typedef struct PipeState { + Tcl_Channel channel;/* Channel associated with this file. */ + TclFile inFile; /* Output from pipe. */ + TclFile outFile; /* Input to pipe. */ + TclFile errorFile; /* Error output from pipe. */ + int numPids; /* How many processes are attached to this pipe? */ + Tcl_Pid *pidPtr; /* The process IDs themselves. Allocated by + * the creator of the pipe. */ + int isNonBlocking; /* Nonzero when the pipe is in nonblocking mode. + * Used to decide whether to wait for the children + * at close time. */ +} PipeState; + +/* * Declarations for local procedures defined in this file: */ -static void RestoreSignals _ANSI_ARGS_((void)); -static int SetupStdFile _ANSI_ARGS_((Tcl_File file, int type)); +static int PipeBlockModeProc _ANSI_ARGS_((ClientData instanceData, + int mode)); +static int PipeCloseProc _ANSI_ARGS_((ClientData instanceData, + Tcl_Interp *interp)); +static int PipeGetHandleProc _ANSI_ARGS_((ClientData instanceData, + int direction, ClientData *handlePtr)); +static int PipeInputProc _ANSI_ARGS_((ClientData instanceData, + char *buf, int toRead, int *errorCode)); +static int PipeOutputProc _ANSI_ARGS_(( + ClientData instanceData, char *buf, int toWrite, + int *errorCode)); +static void PipeWatchProc _ANSI_ARGS_((ClientData instanceData, int mask)); +static void RestoreSignals _ANSI_ARGS_((void)); +static int SetupStdFile _ANSI_ARGS_((TclFile file, int type)); + +/* + * This structure describes the channel type structure for command pipe + * based IO: + */ + +static Tcl_ChannelType pipeChannelType = { + "pipe", /* Type name. */ + PipeBlockModeProc, /* Set blocking/nonblocking mode.*/ + PipeCloseProc, /* Close proc. */ + PipeInputProc, /* Input proc. */ + PipeOutputProc, /* Output proc. */ + NULL, /* Seek proc. */ + NULL, /* Set option proc. */ + NULL, /* Get option proc. */ + PipeWatchProc, /* Initialize notifier. */ + PipeGetHandleProc, /* Get OS handles out of channel. */ +}; + +/* + *---------------------------------------------------------------------- + * + * TclpMakeFile -- + * + * Make a TclFile from a channel. + * + * Results: + * Returns a new TclFile or NULL on failure. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpMakeFile(channel, direction) + Tcl_Channel channel; /* Channel to get file from. */ + int direction; /* Either TCL_READABLE or TCL_WRITABLE. */ +{ + int fd; + + if (Tcl_GetChannelHandle(channel, direction, (ClientData *) &fd) + == TCL_OK) { + return MakeFile(fd); + } else { + return (TclFile) NULL; + } +} + +/* + *---------------------------------------------------------------------- + * + * TclpOpenFile -- + * + * 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. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpOpenFile(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); + } + + /* + * Increment the fd so it can't be 0, which would conflict with + * the NULL return for errors. + */ + + return MakeFile(fd); + } + return NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateTempFile -- + * + * 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. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpCreateTempFile(contents, namePtr) + char *contents; /* String to write into temp file, or NULL. */ + Tcl_DString *namePtr; /* If non-NULL, pointer to initialized + * DString that is filled with the name of + * the temp file that was created. */ +{ + char fileName[L_tmpnam]; + TclFile file; + size_t length = (contents == NULL) ? 0 : strlen(contents); + + tmpnam(fileName); + file = TclpOpenFile(fileName, O_RDWR|O_CREAT|O_TRUNC); + unlink(fileName); + + if ((file != NULL) && (length > 0)) { + int fd = GetFd(file); + while (1) { + if (write(fd, contents, length) != -1) { + break; + } else if (errno != EINTR) { + close(fd); + return NULL; + } + } + lseek(fd, 0, SEEK_SET); + } + if (namePtr != NULL) { + Tcl_DStringAppend(namePtr, fileName, -1); + } + return file; +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreatePipe -- + * + * Creates a pipe - simply calls the pipe() function. + * + * Results: + * Returns 1 on success, 0 on failure. + * + * Side effects: + * Creates a pipe. + * + *---------------------------------------------------------------------- + */ + +int +TclpCreatePipe(readPipe, writePipe) + TclFile *readPipe; /* Location to store file handle for + * read side of pipe. */ + TclFile *writePipe; /* Location to store file handle 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); + + *readPipe = MakeFile(pipeIds[0]); + *writePipe = MakeFile(pipeIds[1]); + return 1; +} + +/* + *---------------------------------------------------------------------- + * + * TclpCloseFile -- + * + * 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. + * + *---------------------------------------------------------------------- + */ + +int +TclpCloseFile(file) + TclFile file; /* The file to close. */ +{ + int fd = GetFd(file); + + /* + * Refuse to close the fds for stdin, stdout and stderr. + */ + + if ((fd == 0) || (fd == 1) || (fd == 2)) { + return 0; + } + + Tcl_DeleteFileHandler(fd); + return close(fd); +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateProcess -- + * + * 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 */ +int +TclpCreateProcess(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. */ + TclFile 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. */ + TclFile 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. */ + TclFile 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. */ + Tcl_Pid *pidPtr; /* If this procedure is successful, pidPtr + * is filled with the process id of the child + * process. */ +{ + TclFile errPipeIn, errPipeOut; + int joinThisError, count, status, fd; + char errSpace[200]; + int pid; + + errPipeIn = NULL; + errPipeOut = NULL; + pid = -1; + + /* + * Create a pipe that the child can use to return error + * information if anything goes wrong. + */ + + if (TclpCreatePipe(&errPipeIn, &errPipeOut) == 0) { + Tcl_AppendResult(interp, "couldn't create pipe: ", + Tcl_PosixError(interp), (char *) NULL); + goto error; + } + + joinThisError = (errorFile == outputFile); + pid = vfork(); + if (pid == 0) { + fd = GetFd(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 couldn'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, "%dcouldn't execute \"%.150s\": ", errno, + argv[0]); + write(fd, errSpace, (size_t) strlen(errSpace)); + _exit(1); + } + if (pid == -1) { + Tcl_AppendResult(interp, "couldn'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. + */ + + TclpCloseFile(errPipeOut); + errPipeOut = NULL; + + fd = GetFd(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; + } + + TclpCloseFile(errPipeIn); + *pidPtr = (Tcl_Pid) 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) { + TclpCloseFile(errPipeIn); + } + if (errPipeOut) { + TclpCloseFile(errPipeOut); + } + return TCL_ERROR; +} /* *---------------------------------------------------------------------- @@ -116,7 +531,7 @@ RestoreSignals() static int SetupStdFile(file, type) - Tcl_File file; /* File to dup, or NULL. */ + TclFile file; /* File to dup, or NULL. */ int type; /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */ { Tcl_Channel channel; @@ -143,11 +558,11 @@ SetupStdFile(file, type) if (!file) { channel = Tcl_GetStdChannel(type); if (channel) { - file = Tcl_GetChannelFile(channel, direction); + file = TclpMakeFile(channel, direction); } } if (file) { - fd = (int)Tcl_GetFileInfo(file, NULL); + fd = GetFd(file); if (fd != targetFd) { if (dup2(fd, targetFd) == -1) { return 0; @@ -179,318 +594,556 @@ SetupStdFile(file, type) /* *---------------------------------------------------------------------- * - * TclSpawnPipeline -- + * TclpCreateCommandChannel -- * - * Given an argc/argv array, instantiate a pipeline of processes - * as described by the argv. + * This function is called by the generic IO level to perform + * the platform specific channel initialization for a command + * channel. * * Results: - * The return value is 1 on success, 0 on error + * Returns a new channel or NULL on failure. * * Side effects: - * Processes and pipes are created. + * Allocates a new channel. * *---------------------------------------------------------------------- */ -int -TclSpawnPipeline(interp, pidPtr, numPids, argc, argv, inputFile, - outputFile, errorFile, intIn, finalOut) - Tcl_Interp *interp; /* Interpreter in which to process pipeline. */ - int *pidPtr; /* Array of pids which are created. */ - int *numPids; /* Number of pids created. */ - 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. */ - Tcl_File inputFile; /* If >=0, gives file id to use as input for - * first process in pipeline (specified via < - * or <@). */ - Tcl_File outputFile; /* Writable file id for output from last - * command in pipeline (could be file or - * pipe). NULL means use stdout. */ - Tcl_File errorFile; /* Writable file id for error output from all - * commands in the pipeline. NULL means use - * stderr */ - char *intIn; /* File name for initial input (for Win32s). */ - char *finalOut; /* File name for final output (for Win32s). */ -{ - int firstArg, lastArg; - int pid, count; - Tcl_DString buffer; - char *execName; - char errSpace[200]; - Tcl_File pipeIn, errPipeIn, errPipeOut; - int joinThisError; - Tcl_File curOutFile = NULL, curInFile; - - Tcl_DStringInit(&buffer); - pipeIn = errPipeIn = errPipeOut = NULL; - curInFile = inputFile; +Tcl_Channel +TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) + TclFile readFile; /* If non-null, gives the file for reading. */ + TclFile writeFile; /* If non-null, gives the file for writing. */ + TclFile errorFile; /* If non-null, gives the file where errors + * can be read. */ + int numPids; /* The number of pids in the pid array. */ + Tcl_Pid *pidPtr; /* An array of process identifiers. + * Allocated by the caller, freed when + * the channel is closed or the processes + * are detached (in a background exec). */ +{ + char channelName[20]; + int channelId; + PipeState *statePtr = (PipeState *) ckalloc((unsigned) sizeof(PipeState)); + int mode; - for (firstArg = 0; firstArg < argc; firstArg = lastArg+1) { + statePtr->inFile = readFile; + statePtr->outFile = writeFile; + statePtr->errorFile = errorFile; + statePtr->numPids = numPids; + statePtr->pidPtr = pidPtr; + statePtr->isNonBlocking = 0; - /* - * Convert the program name into native form. - */ + mode = 0; + if (readFile) { + mode |= TCL_READABLE; + } + if (writeFile) { + mode |= TCL_WRITABLE; + } + + /* + * Use one of the fds associated with the channel as the + * channel id. + */ - Tcl_DStringFree(&buffer); - execName = Tcl_TranslateFileName(interp, argv[firstArg], &buffer); - if (execName == NULL) { - goto error; - } + if (readFile) { + channelId = GetFd(readFile); + } else if (writeFile) { + channelId = GetFd(writeFile); + } else if (errorFile) { + channelId = GetFd(errorFile); + } else { + channelId = 0; + } - /* - * Find the end of the current segment of the pipeline. - */ + /* + * For backward compatibility with previous versions of Tcl, we + * use "file%d" as the base name for pipes even though it would + * be more natural to use "pipe%d". + */ - joinThisError = 0; - for (lastArg = firstArg; 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; + sprintf(channelName, "file%d", channelId); + statePtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, + (ClientData) statePtr, mode); + return statePtr->channel; +} + +/* + *---------------------------------------------------------------------- + * + * TclGetAndDetachPids -- + * + * This procedure is invoked in the generic implementation of a + * background "exec" (An exec when invoked with a terminating "&") + * to store a list of the PIDs for processes in a command pipeline + * in interp->result and to detach the processes. + * + * Results: + * None. + * + * Side effects: + * Modifies interp->result. Detaches processes. + * + *---------------------------------------------------------------------- + */ - /* - * If this is the last segment, use the specified outputFile. - * Otherwise create an intermediate pipe. - */ +void +TclGetAndDetachPids(interp, chan) + Tcl_Interp *interp; + Tcl_Channel chan; +{ + PipeState *pipePtr; + Tcl_ChannelType *chanTypePtr; + int i; + char buf[20]; - if (lastArg == argc) { - curOutFile = outputFile; - } else { - if (TclCreatePipe(&pipeIn, &curOutFile) == 0) { - Tcl_AppendResult(interp, "couldn't create pipe: ", - Tcl_PosixError(interp), (char *) NULL); - goto error; - } - } + /* + * Punt if the channel is not a command channel. + */ - /* - * Create a pipe that the child can use to return error - * information if anything goes wrong. - */ + chanTypePtr = Tcl_GetChannelType(chan); + if (chanTypePtr != &pipeChannelType) { + return; + } - if (TclCreatePipe(&errPipeIn, &errPipeOut) == 0) { - Tcl_AppendResult(interp, "couldn't create pipe: ", - Tcl_PosixError(interp), (char *) NULL); - goto error; - } + pipePtr = (PipeState *) Tcl_GetChannelInstanceData(chan); + for (i = 0; i < pipePtr->numPids; i++) { + sprintf(buf, "%ld", TclpGetPid(pipePtr->pidPtr[i])); + Tcl_AppendElement(interp, buf); + Tcl_DetachPids(1, &(pipePtr->pidPtr[i])); + } + if (pipePtr->numPids > 0) { + ckfree((char *) pipePtr->pidPtr); + pipePtr->numPids = 0; + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeBlockModeProc -- + * + * Helper procedure to set blocking and nonblocking modes on a + * pipe based channel. Invoked by generic IO level code. + * + * Results: + * 0 if successful, errno when failed. + * + * Side effects: + * Sets the device into blocking or non-blocking mode. + * + *---------------------------------------------------------------------- + */ - pid = vfork(); - if (pid == 0) { + /* ARGSUSED */ +static int +PipeBlockModeProc(instanceData, mode) + ClientData instanceData; /* Pipe state. */ + int mode; /* The mode to set. Can be one of + * TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ +{ + PipeState *psPtr = (PipeState *) instanceData; + int curStatus; + int fd; - /* - * Set up stdio file handles for the child process. - */ +#ifndef USE_FIONBIO + if (psPtr->inFile) { + fd = GetFd(psPtr->inFile); + curStatus = fcntl(fd, F_GETFL); + if (mode == TCL_MODE_BLOCKING) { + curStatus &= (~(O_NONBLOCK)); + } else { + curStatus |= O_NONBLOCK; + } + if (fcntl(fd, F_SETFL, curStatus) < 0) { + return errno; + } + curStatus = fcntl(fd, F_GETFL); + } + if (psPtr->outFile) { + fd = GetFd(psPtr->outFile); + curStatus = fcntl(fd, F_GETFL); + if (mode == TCL_MODE_BLOCKING) { + curStatus &= (~(O_NONBLOCK)); + } else { + curStatus |= O_NONBLOCK; + } + if (fcntl(fd, F_SETFL, curStatus) < 0) { + return errno; + } + } +#endif /* !FIONBIO */ - if (!SetupStdFile(curInFile, TCL_STDIN) - || !SetupStdFile(curOutFile, TCL_STDOUT) - || (!joinThisError && !SetupStdFile(errorFile, TCL_STDERR)) - || (joinThisError && - ((dup2(1,2) == -1) || - (fcntl(2, F_SETFD, 0) != 0)))) { - sprintf(errSpace, - "%dforked process couldn't set up input/output: ", - errno); - TclWriteFile(errPipeOut, 1, errSpace, (int) strlen(errSpace)); - _exit(1); - } +#ifdef USE_FIONBIO + if (psPtr->inFile) { + fd = GetFd(psPtr->inFile); + if (mode == TCL_MODE_BLOCKING) { + curStatus = 0; + } else { + curStatus = 1; + } + if (ioctl(fd, (int) FIONBIO, &curStatus) < 0) { + return errno; + } + } + if (psPtr->outFile != NULL) { + fd = GetFd(psPtr->outFile); + if (mode == TCL_MODE_BLOCKING) { + curStatus = 0; + } else { + curStatus = 1; + } + if (ioctl(fd, (int) FIONBIO, &curStatus) < 0) { + return errno; + } + } +#endif /* USE_FIONBIO */ + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * PipeCloseProc -- + * + * This procedure is invoked by the generic IO level to perform + * channel-type-specific cleanup when a command pipeline channel + * is closed. + * + * Results: + * 0 on success, errno otherwise. + * + * Side effects: + * Closes the command pipeline channel. + * + *---------------------------------------------------------------------- + */ - /* - * Close the input side of the error pipe. - */ + /* ARGSUSED */ +static int +PipeCloseProc(instanceData, interp) + ClientData instanceData; /* The pipe to close. */ + Tcl_Interp *interp; /* For error reporting. */ +{ + PipeState *pipePtr; + Tcl_Channel errChan; + int errorCode, result; - RestoreSignals(); - execvp(execName, &argv[firstArg]); - sprintf(errSpace, "%dcouldn't execute \"%.150s\": ", errno, - argv[firstArg]); - TclWriteFile(errPipeOut, 1, errSpace, (int) strlen(errSpace)); - _exit(1); + errorCode = 0; + result = 0; + pipePtr = (PipeState *) instanceData; + if (pipePtr->inFile) { + if (TclpCloseFile(pipePtr->inFile) < 0) { + errorCode = errno; } - Tcl_DStringFree(&buffer); - if (pid == -1) { - Tcl_AppendResult(interp, "couldn't fork child process: ", - Tcl_PosixError(interp), (char *) NULL); - goto error; + } + if (pipePtr->outFile) { + if ((TclpCloseFile(pipePtr->outFile) < 0) && (errorCode == 0)) { + errorCode = errno; } + } + if (pipePtr->isNonBlocking || TclInExit()) { + /* - * Add the child process to the list of those to be reaped. - * Note: must do it now, so that the process will be reaped even if - * an error occurs during its startup. - */ - - pidPtr[*numPids] = pid; - (*numPids)++; - - /* - * Read back from the error pipe to see if the child startup - * up OK. The info in the pipe (if any) consists of a decimal - * errno value followed by an error message. - */ - - TclCloseFile(errPipeOut); - errPipeOut = NULL; - - count = TclReadFile(errPipeIn, 1, 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; - } - TclCloseFile(errPipeIn); - errPipeIn = NULL; + * 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 { + /* - * Close off our copies of file descriptors that were set up for - * this child, then set up the input for the next child. - */ + * Wrap the error file into a channel and give it to the cleanup + * routine. + */ - if (curInFile && (curInFile != inputFile)) { - TclCloseFile(curInFile); - } - curInFile = pipeIn; - pipeIn = NULL; + if (pipePtr->errorFile) { + errChan = Tcl_MakeFileChannel( + (ClientData) GetFd(pipePtr->errorFile), TCL_READABLE); + } else { + errChan = NULL; + } + result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, + errChan); + } - if (curOutFile && (curOutFile != outputFile)) { - TclCloseFile(curOutFile); - } - curOutFile = NULL; + if (pipePtr->numPids != 0) { + ckfree((char *) pipePtr->pidPtr); } - return 1; + ckfree((char *) pipePtr); + if (errorCode == 0) { + return result; + } + return errorCode; +} + +/* + *---------------------------------------------------------------------- + * + * PipeInputProc -- + * + * This procedure is invoked from the generic IO level to read + * input from a command pipeline based channel. + * + * Results: + * The number of bytes read is returned or -1 on error. An output + * argument contains a POSIX error code if an error occurs, or zero. + * + * Side effects: + * Reads input from the input device of the channel. + * + *---------------------------------------------------------------------- + */ + +static int +PipeInputProc(instanceData, buf, toRead, errorCodePtr) + ClientData instanceData; /* Pipe state. */ + char *buf; /* Where to store data read. */ + int toRead; /* How much space is available + * in the buffer? */ + int *errorCodePtr; /* Where to store error code. */ +{ + PipeState *psPtr = (PipeState *) instanceData; + int bytesRead; /* How many bytes were actually + * read from the input device? */ + *errorCodePtr = 0; + /* - * An error occured, so we need to clean up any open pipes. + * Assume there is always enough input available. This will block + * appropriately, and read will unblock as soon as a short read is + * possible, if the channel is in blocking mode. If the channel is + * nonblocking, the read will never block. */ -error: - Tcl_DStringFree(&buffer); - if (errPipeIn) { - TclCloseFile(errPipeIn); - } - if (errPipeOut) { - TclCloseFile(errPipeOut); + bytesRead = read(GetFd(psPtr->inFile), buf, (size_t) toRead); + if (bytesRead > -1) { + return bytesRead; } - if (pipeIn) { - TclCloseFile(pipeIn); + *errorCodePtr = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * PipeOutputProc-- + * + * This procedure is invoked from the generic IO level to write + * output to a command pipeline based channel. + * + * Results: + * The number of bytes written is returned or -1 on error. An + * output argument contains a POSIX error code if an error occurred, + * or zero. + * + * Side effects: + * Writes output on the output device of the channel. + * + *---------------------------------------------------------------------- + */ + +static int +PipeOutputProc(instanceData, buf, toWrite, errorCodePtr) + ClientData instanceData; /* Pipe state. */ + char *buf; /* The data buffer. */ + int toWrite; /* How many bytes to write? */ + int *errorCodePtr; /* Where to store error code. */ +{ + PipeState *psPtr = (PipeState *) instanceData; + int written; + + *errorCodePtr = 0; + written = write(GetFd(psPtr->outFile), buf, (size_t) toWrite); + if (written > -1) { + return written; } - if (curOutFile && (curOutFile != outputFile)) { - TclCloseFile(curOutFile); + *errorCodePtr = errno; + return -1; +} + +/* + *---------------------------------------------------------------------- + * + * PipeWatchProc -- + * + * Initialize the notifier to watch the fds from this channel. + * + * Results: + * None. + * + * Side effects: + * Sets up the notifier so that a future event on the channel will + * be seen by Tcl. + * + *---------------------------------------------------------------------- + */ + +static void +PipeWatchProc(instanceData, mask) + ClientData instanceData; /* The pipe state. */ + int mask; /* Events of interest; an OR-ed + * combination of TCL_READABLE, + * TCL_WRITABEL and TCL_EXCEPTION. */ +{ + PipeState *psPtr = (PipeState *) instanceData; + int newmask; + + if (psPtr->inFile) { + newmask = mask & (TCL_READABLE | TCL_EXCEPTION); + if (newmask) { + Tcl_CreateFileHandler(GetFd(psPtr->inFile), mask, + (Tcl_FileProc *) Tcl_NotifyChannel, + (ClientData) psPtr->channel); + } else { + Tcl_DeleteFileHandler(GetFd(psPtr->inFile)); + } } - if (curInFile && (curInFile != inputFile)) { - TclCloseFile(curInFile); + if (psPtr->outFile) { + newmask = mask & (TCL_WRITABLE | TCL_EXCEPTION); + if (newmask) { + Tcl_CreateFileHandler(GetFd(psPtr->outFile), mask, + (Tcl_FileProc *) Tcl_NotifyChannel, + (ClientData) psPtr->channel); + } else { + Tcl_DeleteFileHandler(GetFd(psPtr->outFile)); + } } - return 0; } /* *---------------------------------------------------------------------- * - * TclCreatePipe -- + * PipeGetHandleProc -- * - * Creates a pipe - simply calls the pipe() function. + * Called from Tcl_GetChannelHandle to retrieve OS handles from + * inside a command pipeline based channel. * * Results: - * Returns 1 on success, 0 on failure. + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if + * there is no handle for the specified direction. * * Side effects: - * Creates a pipe. + * None. * *---------------------------------------------------------------------- */ -int -TclCreatePipe(readPipe, writePipe) - Tcl_File *readPipe; /* Location to store file handle for - * read side of pipe. */ - Tcl_File *writePipe; /* Location to store file handle for - * write side of pipe. */ + +static int +PipeGetHandleProc(instanceData, direction, handlePtr) + ClientData instanceData; /* The pipe state. */ + int direction; /* TCL_READABLE or TCL_WRITABLE */ + ClientData *handlePtr; /* Where to store the handle. */ { - int pipeIds[2]; + PipeState *psPtr = (PipeState *) instanceData; - if (pipe(pipeIds) != 0) { - return 0; + if (direction == TCL_READABLE && psPtr->inFile) { + *handlePtr = (ClientData) GetFd(psPtr->inFile); + return TCL_OK; + } + if (direction == TCL_WRITABLE && psPtr->outFile) { + *handlePtr = (ClientData) GetFd(psPtr->outFile); + return TCL_OK; } + return TCL_ERROR; +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_WaitPid -- + * + * Implements the waitpid system call on Unix systems. + * + * Results: + * Result of calling waitpid. + * + * Side effects: + * Waits for a process to terminate. + * + *---------------------------------------------------------------------- + */ - fcntl(pipeIds[0], F_SETFD, FD_CLOEXEC); - fcntl(pipeIds[1], F_SETFD, FD_CLOEXEC); +Tcl_Pid +Tcl_WaitPid(pid, statPtr, options) + Tcl_Pid pid; + int *statPtr; + int options; +{ + int result; + pid_t real_pid; - *readPipe = Tcl_GetFile((ClientData)pipeIds[0], TCL_UNIX_FD); - *writePipe = Tcl_GetFile((ClientData)pipeIds[1], TCL_UNIX_FD); - return 1; + real_pid = (pid_t) pid; + while (1) { + result = (int) waitpid(real_pid, statPtr, options); + if ((result != -1) || (errno != EINTR)) { + return (Tcl_Pid) result; + } + } } /* *---------------------------------------------------------------------- * - * Tcl_CreatePipeline -- + * Tcl_PidObjCmd -- * - * This function is a compatibility wrapper for TclCreatePipeline. - * It is only available under Unix, and may be removed from later - * versions. + * This procedure is invoked to process the "pid" Tcl command. + * See the user documentation for details on what it does. * * Results: - * Same as TclCreatePipeline. + * A standard Tcl result. * * Side effects: - * Same as TclCreatePipeline. + * See the user documentation. * *---------------------------------------------------------------------- */ + /* ARGSUSED */ int -Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr, - outPipePtr, errFilePtr) - Tcl_Interp *interp; - int argc; - char **argv; - int **pidArrayPtr; - int *inPipePtr; - int *outPipePtr; - int *errFilePtr; +Tcl_PidObjCmd(dummy, interp, objc, objv) + ClientData dummy; /* Not used. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST *objv; /* Argument strings. */ { - Tcl_File inFile, outFile, errFile; - int result; - - result = TclCreatePipeline(interp, argc, argv, pidArrayPtr, - (inPipePtr ? &inFile : NULL), - (outPipePtr ? &outFile : NULL), - (errFilePtr ? &errFile : NULL)); + Tcl_Channel chan; + Tcl_ChannelType *chanTypePtr; + PipeState *pipePtr; + int i; + Tcl_Obj *resultPtr, *longObjPtr; - if (inPipePtr) { - if (inFile) { - *inPipePtr = (int) Tcl_GetFileInfo(inFile, NULL); - Tcl_FreeFile(inFile); - } else { - *inPipePtr = -1; - } + if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?channelId?"); + return TCL_ERROR; } - if (outPipePtr) { - if (outFile) { - *outPipePtr = (int) Tcl_GetFileInfo(outFile, NULL); - Tcl_FreeFile(outFile); - } else { - *outPipePtr = -1; + if (objc == 1) { + Tcl_SetLongObj(Tcl_GetObjResult(interp), (long) getpid()); + } else { + chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), + NULL); + if (chan == (Tcl_Channel) NULL) { + return TCL_ERROR; } - } - if (errFilePtr) { - if (errFile) { - *errFilePtr = (int) Tcl_GetFileInfo(errFile, NULL); - Tcl_FreeFile(errFile); - } else { - *errFilePtr = -1; + chanTypePtr = Tcl_GetChannelType(chan); + if (chanTypePtr != &pipeChannelType) { + return TCL_OK; + } + pipePtr = (PipeState *) Tcl_GetChannelInstanceData(chan); + resultPtr = Tcl_GetObjResult(interp); + for (i = 0; i < pipePtr->numPids; i++) { + longObjPtr = Tcl_NewLongObj((long) TclpGetPid(pipePtr->pidPtr[i])); + Tcl_ListObjAppendElement(NULL, resultPtr, longObjPtr); } } - return result; + return TCL_OK; } |