diff options
Diffstat (limited to 'unix/tclUnixPipe.c')
-rw-r--r-- | unix/tclUnixPipe.c | 1050 |
1 files changed, 613 insertions, 437 deletions
diff --git a/unix/tclUnixPipe.c b/unix/tclUnixPipe.c index 871830c..9c21b28 100644 --- a/unix/tclUnixPipe.c +++ b/unix/tclUnixPipe.c @@ -1,82 +1,89 @@ -/* +/* * tclUnixPipe.c -- * - * This file implements the UNIX-specific exec pipeline functions, - * the "pipe" channel driver, and the "pid" Tcl command. + * 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-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. - * - * RCS: @(#) $Id: tclUnixPipe.c,v 1.2 1998/09/14 18:40:17 stanton Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" -#include "tclPort.h" + +#ifdef USE_VFORK +#define fork vfork +#endif /* - * The following macros convert between TclFile's and fd's. The conversion + * 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)(((int)fd)+1)) -#define GetFd(file) (((int)file)-1) +#define MakeFile(fd) ((TclFile) INT2PTR(((int) (fd)) + 1)) +#define GetFd(file) (PTR2INT(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. */ + 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: + * Declarations for local functions defined in this file: */ -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)); +static int PipeBlockModeProc(ClientData instanceData, int mode); +static int PipeClose2Proc(ClientData instanceData, + Tcl_Interp *interp, int flags); +static int PipeGetHandleProc(ClientData instanceData, + int direction, ClientData *handlePtr); +static int PipeInputProc(ClientData instanceData, char *buf, + int toRead, int *errorCode); +static int PipeOutputProc(ClientData instanceData, + const char *buf, int toWrite, int *errorCode); +static void PipeWatchProc(ClientData instanceData, int mask); +static void RestoreSignals(void); +static int SetupStdFile(TclFile file, int type); /* - * This structure describes the channel type structure for command pipe - * based IO: + * This structure describes the channel type structure for command pipe based + * I/O: */ -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. */ +static const Tcl_ChannelType pipeChannelType = { + "pipe", /* Type name. */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ + TCL_CLOSE2PROC, /* 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. */ + PipeClose2Proc, /* close2proc. */ + PipeBlockModeProc, /* Set blocking or non-blocking mode.*/ + NULL, /* flush proc. */ + NULL, /* handler proc. */ + NULL, /* wide seek proc */ + NULL, /* thread action proc */ + NULL /* truncation */ }; /* @@ -96,18 +103,17 @@ static Tcl_ChannelType pipeChannelType = { */ TclFile -TclpMakeFile(channel, direction) - Tcl_Channel channel; /* Channel to get file from. */ - int direction; /* Either TCL_READABLE or TCL_WRITABLE. */ +TclpMakeFile( + Tcl_Channel channel, /* Channel to get file from. */ + int direction) /* Either TCL_READABLE or TCL_WRITABLE. */ { ClientData data; - if (Tcl_GetChannelHandle(channel, direction, (ClientData *) &data) - == TCL_OK) { - return MakeFile((int)data); - } else { - return (TclFile) NULL; + if (Tcl_GetChannelHandle(channel, direction, &data) != TCL_OK) { + return NULL; } + + return MakeFile(PTR2INT(data)); } /* @@ -115,7 +121,7 @@ TclpMakeFile(channel, direction) * * TclpOpenFile -- * - * Open a file for use in a pipeline. + * Open a file for use in a pipeline. * * Results: * Returns a new TclFile handle or NULL on failure. @@ -127,28 +133,32 @@ TclpMakeFile(channel, direction) */ TclFile -TclpOpenFile(fname, mode) - char *fname; /* The name of the file to open. */ - int mode; /* In what mode to open the file? */ +TclpOpenFile( + const char *fname, /* The name of the file to open. */ + int mode) /* In what mode to open the file? */ { int fd; + const char *native; + Tcl_DString ds; - fd = open(fname, mode, 0666); + native = Tcl_UtfToExternalDString(NULL, fname, -1, &ds); + fd = TclOSopen(native, mode, 0666); /* INTL: Native. */ + Tcl_DStringFree(&ds); if (fd != -1) { - fcntl(fd, F_SETFD, FD_CLOEXEC); + 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 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); + if ((mode & O_WRONLY) && !(mode & O_APPEND)) { + TclOSseek(fd, (Tcl_SeekOffset) 0, SEEK_END); } /* - * Increment the fd so it can't be 0, which would conflict with - * the NULL return for errors. + * Increment the fd so it can't be 0, which would conflict with the + * NULL return for errors. */ return MakeFile(fd); @@ -161,9 +171,9 @@ TclpOpenFile(fname, mode) * * 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. + * 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. @@ -175,36 +185,97 @@ TclpOpenFile(fname, mode) */ 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. */ +TclpCreateTempFile( + const char *contents) /* String to write into temp file, or NULL. */ { - 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; - } + int fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, NULL); + + if (fd == -1) { + return NULL; + } + fcntl(fd, F_SETFD, FD_CLOEXEC); + if (contents != NULL) { + Tcl_DString dstring; + char *native; + + native = Tcl_UtfToExternalDString(NULL, contents, -1, &dstring); + if (write(fd, native, Tcl_DStringLength(&dstring)) == -1) { + close(fd); + Tcl_DStringFree(&dstring); + return NULL; } - lseek(fd, 0, SEEK_SET); + Tcl_DStringFree(&dstring); + TclOSseek(fd, (Tcl_SeekOffset) 0, SEEK_SET); + } + return MakeFile(fd); +} + +/* + *---------------------------------------------------------------------- + * + * TclpTempFileName -- + * + * This function returns unique filename. + * + * Results: + * Returns a valid Tcl_Obj* with refCount 0, or NULL on failure. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpTempFileName(void) +{ + Tcl_Obj *nameObj = Tcl_NewObj(); + int fd; + + Tcl_IncrRefCount(nameObj); + fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, nameObj); + if (fd == -1) { + Tcl_DecrRefCount(nameObj); + return NULL; } - if (namePtr != NULL) { - Tcl_DStringAppend(namePtr, fileName, -1); + + fcntl(fd, F_SETFD, FD_CLOEXEC); + TclpObjDeleteFile(nameObj); + close(fd); + return nameObj; +} + +/* + *---------------------------------------------------------------------------- + * + * TclpTempFileNameForLibrary -- + * + * Constructs a file name in the native file system where a dynamically + * loaded library may be placed. + * + * Results: + * Returns the constructed file name. If an error occurs, returns NULL + * and leaves an error message in the interpreter result. + * + * On Unix, it works to load a shared object from a file of any name, so this + * function is merely a thin wrapper around TclpTempFileName(). + * + *---------------------------------------------------------------------------- + */ + +Tcl_Obj * +TclpTempFileNameForLibrary( + Tcl_Interp *interp, /* Tcl interpreter. */ + Tcl_Obj *path) /* Path name of the library in the VFS. */ +{ + Tcl_Obj *retval = TclpTempFileName(); + + if (retval == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't create temporary file: %s", + Tcl_PosixError(interp))); } - return file; + return retval; } /* @@ -212,23 +283,23 @@ TclpCreateTempFile(contents, namePtr) * * TclpCreatePipe -- * - * Creates a pipe - simply calls the pipe() function. + * Creates a pipe - simply calls the pipe() function. * * Results: - * Returns 1 on success, 0 on failure. + * Returns 1 on success, 0 on failure. * * Side effects: - * Creates a pipe. + * 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. */ +TclpCreatePipe( + 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]; @@ -261,102 +332,136 @@ TclpCreatePipe(readPipe, writePipe) */ int -TclpCloseFile(file) - TclFile file; /* The file to close. */ +TclpCloseFile( + 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; + 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. + * 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. + * 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. - * + * The return value is TCL_ERROR and an error message is left in the + * interp's 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 +TclpCreateProcess( + 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 + int argc, /* Number of arguments in following array. */ + const char **argv, /* Array of argument strings in UTF-8. + * argv[0] contains the name of the executable + * translated using 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 + 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 function 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; - + int count, status, fd; + char errSpace[200 + TCL_INTEGER_SPACE]; + Tcl_DString *dsArray; + char **newArgv; + int pid, i; + errPipeIn = NULL; errPipeOut = NULL; pid = -1; /* - * Create a pipe that the child can use to return error - * information if anything goes wrong. + * 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); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't create pipe: %s", Tcl_PosixError(interp))); goto error; } - joinThisError = (errorFile == outputFile); - pid = vfork(); + /* + * We need to allocate and convert this before the fork so it is properly + * deallocated later + */ + + dsArray = TclStackAlloc(interp, argc * sizeof(Tcl_DString)); + newArgv = TclStackAlloc(interp, (argc+1) * sizeof(char *)); + newArgv[argc] = NULL; + for (i = 0; i < argc; i++) { + newArgv[i] = Tcl_UtfToExternalDString(NULL, argv[i], -1, &dsArray[i]); + } + +#ifdef USE_VFORK + /* + * After vfork(), do not call code in the child that changes global state, + * because it is using the parent's memory space at that point and writes + * might corrupt the parent: so ensure standard channels are initialized + * in the parent, otherwise SetupStdFile() might initialize them in the + * child. + */ + + if (!inputFile) { + Tcl_GetStdChannel(TCL_STDIN); + } + if (!outputFile) { + Tcl_GetStdChannel(TCL_STDOUT); + } + if (!errorFile) { + Tcl_GetStdChannel(TCL_STDERR); + } +#endif + + pid = fork(); if (pid == 0) { + size_t len; + int joinThisError = errorFile && (errorFile == outputFile); + fd = GetFd(errPipeOut); /* @@ -367,12 +472,13 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, || !SetupStdFile(outputFile, TCL_STDOUT) || (!joinThisError && !SetupStdFile(errorFile, TCL_STDERR)) || (joinThisError && - ((dup2(1,2) == -1) || - (fcntl(2, F_SETFD, 0) != 0)))) { + ((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)); + "%dforked process couldn't set up input/output", errno); + len = strlen(errSpace); + if (len != (size_t) write(fd, errSpace, len)) { + Tcl_Panic("TclpCreateProcess: unable to write to errPipeOut"); + } _exit(1); } @@ -381,22 +487,35 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, */ RestoreSignals(); - execvp(argv[0], &argv[0]); - sprintf(errSpace, "%dcouldn't execute \"%.150s\": ", errno, - argv[0]); - write(fd, errSpace, (size_t) strlen(errSpace)); + execvp(newArgv[0], newArgv); /* INTL: Native. */ + sprintf(errSpace, "%dcouldn't execute \"%.150s\"", errno, argv[0]); + len = strlen(errSpace); + if (len != (size_t) write(fd, errSpace, len)) { + Tcl_Panic("TclpCreateProcess: unable to write to errPipeOut"); + } _exit(1); } + + /* + * Free the mem we used for the fork + */ + + for (i = 0; i < argc; i++) { + Tcl_DStringFree(&dsArray[i]); + } + TclStackFree(interp, newArgv); + TclStackFree(interp, dsArray); + if (pid == -1) { - Tcl_AppendResult(interp, "couldn't fork child process: ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't fork child process: %s", Tcl_PosixError(interp))); 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. + * 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); @@ -406,27 +525,30 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, 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); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s: %s", + end, Tcl_PosixError(interp))); goto error; } - + TclpCloseFile(errPipeIn); - *pidPtr = (Tcl_Pid) pid; + *pidPtr = (Tcl_Pid) INT2PTR(pid); return TCL_OK; - error: + error: if (pid != -1) { /* - * Reap the child process now if an error occurred during its - * startup. + * Reap the child process now if an error occurred during its startup. + * We don't call this with WNOHANG because that can lead to defunct + * processes on an MP system. We shouldn't have to worry about hanging + * here, since this is the error case. [Bug: 6148] */ - Tcl_WaitPid((Tcl_Pid) pid, &status, WNOHANG); + Tcl_WaitPid((Tcl_Pid) INT2PTR(pid), &status, 0); } - + if (errPipeIn) { TclpCloseFile(errPipeIn); } @@ -441,21 +563,21 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, * * 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. + * This function is invoked in a forked child process just before + * exec-ing a new program to restore all signals to their default + * settings. * * Results: - * None. + * None. * * Side effects: - * Signal settings get changed. + * Signal settings get changed. * *---------------------------------------------------------------------- */ - + static void -RestoreSignals() +RestoreSignals(void) { #ifdef SIGABRT signal(SIGABRT, SIG_DFL); @@ -515,10 +637,10 @@ RestoreSignals() * * 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. + * 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. @@ -530,9 +652,9 @@ RestoreSignals() */ static int -SetupStdFile(file, type) - TclFile file; /* File to dup, or NULL. */ - int type; /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */ +SetupStdFile( + TclFile file, /* File to dup, or NULL. */ + int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */ { Tcl_Channel channel; int fd; @@ -541,18 +663,18 @@ SetupStdFile(file, type) * 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; + 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 (!file) { @@ -568,22 +690,20 @@ SetupStdFile(file, type) 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 { - int result; + /* + * 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. */ - result = fcntl(fd, F_SETFD, 0); + fcntl(fd, F_SETFD, 0); } } else { close(targetFd); @@ -596,9 +716,8 @@ SetupStdFile(file, type) * * TclpCreateCommandChannel -- * - * This function is called by the generic IO level to perform - * the platform specific channel initialization for a command - * channel. + * This function is called by the generic IO level to perform the + * platform specific channel initialization for a command channel. * * Results: * Returns a new channel or NULL on failure. @@ -610,20 +729,20 @@ SetupStdFile(file, type) */ 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 +TclpCreateCommandChannel( + 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). */ + 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]; + char channelName[16 + TCL_INTEGER_SPACE]; int channelId; - PipeState *statePtr = (PipeState *) ckalloc((unsigned) sizeof(PipeState)); + PipeState *statePtr = ckalloc(sizeof(PipeState)); int mode; statePtr->inFile = readFile; @@ -635,15 +754,14 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) mode = 0; if (readFile) { - mode |= TCL_READABLE; + mode |= TCL_READABLE; } if (writeFile) { - mode |= TCL_WRITABLE; + mode |= TCL_WRITABLE; } - + /* - * Use one of the fds associated with the channel as the - * channel id. + * Use one of the fds associated with the channel as the channel id. */ if (readFile) { @@ -657,45 +775,88 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) } /* - * 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". + * 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". */ sprintf(channelName, "file%d", channelId); statePtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, - (ClientData) statePtr, mode); + statePtr, mode); return statePtr->channel; } /* *---------------------------------------------------------------------- * + * Tcl_CreatePipe -- + * + * System dependent interface to create a pipe for the [chan pipe] + * command. Stolen from TclX. + * + * Results: + * TCL_OK or TCL_ERROR. + * + * Side effects: + * Registers two channels. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_CreatePipe( + Tcl_Interp *interp, /* Errors returned in result. */ + Tcl_Channel *rchan, /* Returned read side. */ + Tcl_Channel *wchan, /* Returned write side. */ + int flags) /* Reserved for future use. */ +{ + int fileNums[2]; + + if (pipe(fileNums) < 0) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("pipe creation failed: %s", + Tcl_PosixError(interp))); + return TCL_ERROR; + } + + fcntl(fileNums[0], F_SETFD, FD_CLOEXEC); + fcntl(fileNums[1], F_SETFD, FD_CLOEXEC); + + *rchan = Tcl_MakeFileChannel(INT2PTR(fileNums[0]), TCL_READABLE); + Tcl_RegisterChannel(interp, *rchan); + *wchan = Tcl_MakeFileChannel(INT2PTR(fileNums[1]), TCL_WRITABLE); + Tcl_RegisterChannel(interp, *wchan); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * 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. + * This function 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 the + * interp's result and to detach the processes. * * Results: * None. * * Side effects: - * Modifies interp->result. Detaches processes. + * Modifies the interp's result. Detaches processes. * *---------------------------------------------------------------------- */ void -TclGetAndDetachPids(interp, chan) - Tcl_Interp *interp; - Tcl_Channel chan; +TclGetAndDetachPids( + Tcl_Interp *interp, /* Interpreter to append the PIDs to. */ + Tcl_Channel chan) /* Handle for the pipeline. */ { PipeState *pipePtr; - Tcl_ChannelType *chanTypePtr; + const Tcl_ChannelType *chanTypePtr; + Tcl_Obj *pidsObj; int i; - char buf[20]; /* * Punt if the channel is not a command channel. @@ -703,18 +864,20 @@ TclGetAndDetachPids(interp, chan) chanTypePtr = Tcl_GetChannelType(chan); if (chanTypePtr != &pipeChannelType) { - return; + return; } - pipePtr = (PipeState *) Tcl_GetChannelInstanceData(chan); + pipePtr = Tcl_GetChannelInstanceData(chan); + TclNewObj(pidsObj); for (i = 0; i < pipePtr->numPids; i++) { - sprintf(buf, "%ld", TclpGetPid(pipePtr->pidPtr[i])); - Tcl_AppendElement(interp, buf); - Tcl_DetachPids(1, &(pipePtr->pidPtr[i])); + Tcl_ListObjAppendElement(NULL, pidsObj, Tcl_NewIntObj( + PTR2INT(pipePtr->pidPtr[i]))); + Tcl_DetachPids(1, &pipePtr->pidPtr[i]); } + Tcl_SetObjResult(interp, pidsObj); if (pipePtr->numPids > 0) { - ckfree((char *) pipePtr->pidPtr); - pipePtr->numPids = 0; + ckfree(pipePtr->pidPtr); + pipePtr->numPids = 0; } } @@ -723,8 +886,8 @@ TclGetAndDetachPids(interp, chan) * * PipeBlockModeProc -- * - * Helper procedure to set blocking and nonblocking modes on a - * pipe based channel. Invoked by generic IO level code. + * Helper function to set blocking and nonblocking modes on a pipe based + * channel. Invoked by generic IO level code. * * Results: * 0 if successful, errno when failed. @@ -737,80 +900,35 @@ TclGetAndDetachPids(interp, chan) /* 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. */ +PipeBlockModeProc( + 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; + PipeState *psPtr = instanceData; -#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->inFile + && TclUnixSetBlockingMode(GetFd(psPtr->inFile), mode) < 0) { + return errno; } - 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; - } + if (psPtr->outFile + && TclUnixSetBlockingMode(GetFd(psPtr->outFile), mode) < 0) { + return errno; } -#endif /* !FIONBIO */ -#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 */ - + psPtr->isNonBlocking = (mode == TCL_MODE_NONBLOCKING); + return 0; } /* *---------------------------------------------------------------------- * - * PipeCloseProc -- + * PipeClose2Proc * - * This procedure is invoked by the generic IO level to perform - * channel-type-specific cleanup when a command pipeline channel - * is closed. + * This function is invoked by the generic IO level to perform + * pipeline-type-specific half or full-close. * * Results: * 0 on success, errno otherwise. @@ -821,67 +939,79 @@ PipeBlockModeProc(instanceData, mode) *---------------------------------------------------------------------- */ - /* ARGSUSED */ static int -PipeCloseProc(instanceData, interp) - ClientData instanceData; /* The pipe to close. */ - Tcl_Interp *interp; /* For error reporting. */ +PipeClose2Proc( + ClientData instanceData, /* The pipe to close. */ + Tcl_Interp *interp, /* For error reporting. */ + int flags) /* Flags that indicate which side to close. */ { - PipeState *pipePtr; + PipeState *pipePtr = instanceData; Tcl_Channel errChan; int errorCode, result; errorCode = 0; result = 0; - pipePtr = (PipeState *) instanceData; - if (pipePtr->inFile) { + + if (((!flags) || (flags & TCL_CLOSE_READ)) && (pipePtr->inFile != NULL)) { if (TclpCloseFile(pipePtr->inFile) < 0) { errorCode = errno; + } else { + pipePtr->inFile = NULL; } } - if (pipePtr->outFile) { - if ((TclpCloseFile(pipePtr->outFile) < 0) && (errorCode == 0)) { + if (((!flags) || (flags & TCL_CLOSE_WRITE)) && (pipePtr->outFile != NULL) + && (errorCode == 0)) { + if (TclpCloseFile(pipePtr->outFile) < 0) { errorCode = errno; + } else { + pipePtr->outFile = NULL; } } + + /* + * If half-closing, stop here. + */ + + if (flags) { + return errorCode; + } if (pipePtr->isNonBlocking || TclInExit()) { - /* - * If the channel is non-blocking or Tcl is being cleaned up, just - * detach the children PIDs, reap them (important if we are in a - * dynamic load module), and discard the errorFile. - */ - - Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); - Tcl_ReapDetachedProcs(); - - if (pipePtr->errorFile) { + * If the channel is non-blocking or Tcl is being cleaned up, just + * detach the children PIDs, reap them (important if we are in a + * dynamic load module), and discard the errorFile. + */ + + Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); + Tcl_ReapDetachedProcs(); + + if (pipePtr->errorFile) { TclpCloseFile(pipePtr->errorFile); - } + } } else { - /* - * Wrap the error file into a channel and give it to the cleanup - * routine. - */ + * Wrap the error file into a channel and give it to the cleanup + * routine. + */ - if (pipePtr->errorFile) { + if (pipePtr->errorFile) { errChan = Tcl_MakeFileChannel( - (ClientData) GetFd(pipePtr->errorFile), TCL_READABLE); - } else { - errChan = NULL; - } - result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, - errChan); + INT2PTR(GetFd(pipePtr->errorFile)), + TCL_READABLE); + } else { + errChan = NULL; + } + result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, + errChan); } if (pipePtr->numPids != 0) { - ckfree((char *) pipePtr->pidPtr); + ckfree(pipePtr->pidPtr); } - ckfree((char *) pipePtr); + ckfree(pipePtr); if (errorCode == 0) { - return result; + return result; } return errorCode; } @@ -891,8 +1021,8 @@ PipeCloseProc(instanceData, interp) * * PipeInputProc -- * - * This procedure is invoked from the generic IO level to read - * input from a command pipeline based channel. + * This function 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 @@ -905,32 +1035,36 @@ PipeCloseProc(instanceData, interp) */ 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. */ +PipeInputProc( + 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? */ + PipeState *psPtr = instanceData; + int bytesRead; /* How many bytes were actually read from the + * input device? */ *errorCodePtr = 0; - + /* * 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. + * nonblocking, the read will never block. Some OSes can throw an + * interrupt error, for which we should immediately retry. [Bug #415131] */ - bytesRead = read(GetFd(psPtr->inFile), buf, (size_t) toRead); - if (bytesRead > -1) { - return bytesRead; + do { + bytesRead = read(GetFd(psPtr->inFile), buf, (size_t) toRead); + } while ((bytesRead < 0) && (errno == EINTR)); + + if (bytesRead < 0) { + *errorCodePtr = errno; + return -1; } - *errorCodePtr = errno; - return -1; + return bytesRead; } /* @@ -938,13 +1072,12 @@ PipeInputProc(instanceData, buf, toRead, errorCodePtr) * * PipeOutputProc-- * - * This procedure is invoked from the generic IO level to write - * output to a command pipeline based channel. + * This function 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. + * 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. @@ -953,22 +1086,31 @@ PipeInputProc(instanceData, buf, toRead, errorCodePtr) */ 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. */ +PipeOutputProc( + ClientData instanceData, /* Pipe state. */ + const char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCodePtr) /* Where to store error code. */ { - PipeState *psPtr = (PipeState *) instanceData; + PipeState *psPtr = instanceData; int written; *errorCodePtr = 0; - written = write(GetFd(psPtr->outFile), buf, (size_t) toWrite); - if (written > -1) { - return written; + + /* + * Some OSes can throw an interrupt error, for which we should immediately + * retry. [Bug #415131] + */ + + do { + written = write(GetFd(psPtr->outFile), buf, (size_t) toWrite); + } while ((written < 0) && (errno == EINTR)); + + if (written < 0) { + *errorCodePtr = errno; + return -1; } - *errorCodePtr = errno; - return -1; + return written; } /* @@ -982,28 +1124,27 @@ PipeOutputProc(instanceData, buf, toWrite, errorCodePtr) * None. * * Side effects: - * Sets up the notifier so that a future event on the channel will - * be seen by Tcl. + * 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. */ +PipeWatchProc( + ClientData instanceData, /* The pipe state. */ + int mask) /* Events of interest; an OR-ed combination of + * TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ { - PipeState *psPtr = (PipeState *) instanceData; + PipeState *psPtr = 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); + (Tcl_FileProc *) Tcl_NotifyChannel, psPtr->channel); } else { Tcl_DeleteFileHandler(GetFd(psPtr->inFile)); } @@ -1012,8 +1153,7 @@ PipeWatchProc(instanceData, mask) newmask = mask & (TCL_WRITABLE | TCL_EXCEPTION); if (newmask) { Tcl_CreateFileHandler(GetFd(psPtr->outFile), mask, - (Tcl_FileProc *) Tcl_NotifyChannel, - (ClientData) psPtr->channel); + (Tcl_FileProc *) Tcl_NotifyChannel, psPtr->channel); } else { Tcl_DeleteFileHandler(GetFd(psPtr->outFile)); } @@ -1025,12 +1165,12 @@ PipeWatchProc(instanceData, mask) * * PipeGetHandleProc -- * - * Called from Tcl_GetChannelHandle to retrieve OS handles from - * inside a command pipeline based channel. + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * command pipeline based channel. * * Results: - * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if - * there is no handle for the specified direction. + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. * * Side effects: * None. @@ -1039,19 +1179,19 @@ PipeWatchProc(instanceData, mask) */ 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. */ +PipeGetHandleProc( + ClientData instanceData, /* The pipe state. */ + int direction, /* TCL_READABLE or TCL_WRITABLE */ + ClientData *handlePtr) /* Where to store the handle. */ { - PipeState *psPtr = (PipeState *) instanceData; + PipeState *psPtr = instanceData; if (direction == TCL_READABLE && psPtr->inFile) { - *handlePtr = (ClientData) GetFd(psPtr->inFile); + *handlePtr = INT2PTR(GetFd(psPtr->inFile)); return TCL_OK; } if (direction == TCL_WRITABLE && psPtr->outFile) { - *handlePtr = (ClientData) GetFd(psPtr->outFile); + *handlePtr = INT2PTR(GetFd(psPtr->outFile)); return TCL_OK; } return TCL_ERROR; @@ -1074,19 +1214,18 @@ PipeGetHandleProc(instanceData, direction, handlePtr) */ Tcl_Pid -Tcl_WaitPid(pid, statPtr, options) - Tcl_Pid pid; - int *statPtr; - int options; +Tcl_WaitPid( + Tcl_Pid pid, + int *statPtr, + int options) { int result; - pid_t real_pid; + pid_t real_pid = (pid_t) PTR2INT(pid); - real_pid = (pid_t) pid; while (1) { result = (int) waitpid(real_pid, statPtr, options); if ((result != -1) || (errno != EINTR)) { - return (Tcl_Pid) result; + return (Tcl_Pid) INT2PTR(result); } } } @@ -1096,8 +1235,8 @@ Tcl_WaitPid(pid, statPtr, options) * * Tcl_PidObjCmd -- * - * This procedure is invoked to process the "pid" Tcl command. - * See the user documentation for details on what it does. + * This function is invoked to process the "pid" Tcl command. See the + * user documentation for details on what it does. * * Results: * A standard Tcl result. @@ -1110,40 +1249,77 @@ Tcl_WaitPid(pid, statPtr, options) /* ARGSUSED */ int -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_PidObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const *objv) /* Argument strings. */ { Tcl_Channel chan; - Tcl_ChannelType *chanTypePtr; PipeState *pipePtr; int i; - Tcl_Obj *resultPtr, *longObjPtr; + Tcl_Obj *resultPtr; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?channelId?"); return TCL_ERROR; } + if (objc == 1) { - Tcl_SetLongObj(Tcl_GetObjResult(interp), (long) getpid()); + Tcl_SetObjResult(interp, Tcl_NewLongObj((long) getpid())); } else { - chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), - NULL); - if (chan == (Tcl_Channel) NULL) { + /* + * Get the channel and make sure that it refers to a pipe. + */ + + chan = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), NULL); + if (chan == NULL) { return TCL_ERROR; } - chanTypePtr = Tcl_GetChannelType(chan); - if (chanTypePtr != &pipeChannelType) { + if (Tcl_GetChannelType(chan) != &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); + + /* + * Extract the process IDs from the pipe structure. + */ + + pipePtr = Tcl_GetChannelInstanceData(chan); + resultPtr = Tcl_NewObj(); + for (i = 0; i < pipePtr->numPids; i++) { + Tcl_ListObjAppendElement(NULL, resultPtr, + Tcl_NewIntObj(PTR2INT(TclpGetPid(pipePtr->pidPtr[i])))); } + Tcl_SetObjResult(interp, resultPtr); } return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * TclpFinalizePipes -- + * + * Cleans up the pipe subsystem from Tcl_FinalizeThread + * + * Results: + * None. + * + * Notes: + * This function carries out no operation on Unix. + * + *---------------------------------------------------------------------- + */ + +void +TclpFinalizePipes(void) +{ +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |