summaryrefslogtreecommitdiff
path: root/contrib/tcl/unix/tclUnixPipe.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/tcl/unix/tclUnixPipe.c')
-rw-r--r--contrib/tcl/unix/tclUnixPipe.c1141
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;
}