diff options
Diffstat (limited to 'unix/tclUnixPipe.c')
-rw-r--r-- | unix/tclUnixPipe.c | 932 |
1 files changed, 505 insertions, 427 deletions
diff --git a/unix/tclUnixPipe.c b/unix/tclUnixPipe.c index ee8bacc..9c21b28 100644 --- a/unix/tclUnixPipe.c +++ b/unix/tclUnixPipe.c @@ -1,16 +1,14 @@ -/* +/* * 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.26 2004/10/06 16:08:57 dgp 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" @@ -20,59 +18,58 @@ #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, CONST 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 = { +static const Tcl_ChannelType pipeChannelType = { "pipe", /* Type name. */ - TCL_CHANNEL_VERSION_2, /* v2 channel */ - PipeCloseProc, /* Close proc. */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ + TCL_CLOSE2PROC, /* Close proc. */ PipeInputProc, /* Input proc. */ PipeOutputProc, /* Output proc. */ NULL, /* Seek proc. */ @@ -80,10 +77,13 @@ static Tcl_ChannelType pipeChannelType = { NULL, /* Get option proc. */ PipeWatchProc, /* Initialize notifier. */ PipeGetHandleProc, /* Get OS handles out of channel. */ - NULL, /* close2proc. */ + 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 */ }; /* @@ -103,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)); } /* @@ -122,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. @@ -134,32 +133,32 @@ TclpMakeFile(channel, direction) */ TclFile -TclpOpenFile(fname, mode) - CONST 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; + const char *native; Tcl_DString ds; 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) { + 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); @@ -172,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. @@ -186,33 +185,21 @@ TclpOpenFile(fname, mode) */ TclFile -TclpCreateTempFile(contents) - CONST char *contents; /* String to write into temp file, or NULL. */ +TclpCreateTempFile( + const char *contents) /* String to write into temp file, or NULL. */ { - char fileName[L_tmpnam + 9]; - CONST char *native; - Tcl_DString dstring; - int fd; + int fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, NULL); - /* - * We should also check against making more then TMP_MAX of these. - */ - - strcpy(fileName, P_tmpdir); /* INTL: Native. */ - if (fileName[strlen(fileName) - 1] != '/') { - strcat(fileName, "/"); /* INTL: Native. */ - } - strcat(fileName, "tclXXXXXX"); - fd = mkstemp(fileName); /* INTL: Native. */ if (fd == -1) { return NULL; } fcntl(fd, F_SETFD, FD_CLOEXEC); - unlink(fileName); /* INTL: Native. */ - if (contents != NULL) { + Tcl_DString dstring; + char *native; + native = Tcl_UtfToExternalDString(NULL, contents, -1, &dstring); - if (write(fd, native, strlen(native)) == -1) { + if (write(fd, native, Tcl_DStringLength(&dstring)) == -1) { close(fd); Tcl_DStringFree(&dstring); return NULL; @@ -239,32 +226,56 @@ TclpCreateTempFile(contents) *---------------------------------------------------------------------- */ -Tcl_Obj* -TclpTempFileName() +Tcl_Obj * +TclpTempFileName(void) { - char fileName[L_tmpnam + 9]; - Tcl_Obj *result = NULL; + Tcl_Obj *nameObj = Tcl_NewObj(); int fd; - /* - * We should also check against making more then TMP_MAX of these. - */ - - strcpy(fileName, P_tmpdir); /* INTL: Native. */ - if (fileName[strlen(fileName) - 1] != '/') { - strcat(fileName, "/"); /* INTL: Native. */ - } - strcat(fileName, "tclXXXXXX"); - fd = mkstemp(fileName); /* INTL: Native. */ + Tcl_IncrRefCount(nameObj); + fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, nameObj); if (fd == -1) { + Tcl_DecrRefCount(nameObj); return NULL; } + fcntl(fd, F_SETFD, FD_CLOEXEC); - unlink(fileName); /* INTL: Native. */ + 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(); - result = TclpNativeToNormalized((ClientData) fileName); - close (fd); - return result; + if (retval == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't create temporary file: %s", + Tcl_PosixError(interp))); + } + return retval; } /* @@ -272,23 +283,23 @@ TclpTempFileName() * * 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]; @@ -321,19 +332,19 @@ 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); } @@ -343,93 +354,114 @@ TclpCloseFile(file) * * 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 - * 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. - * + * 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. */ - CONST char **argv; /* Array of argument strings in UTF-8. + 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 + * 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 + 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; + 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; } /* - * We need to allocate and convert this before the fork - * so it is properly deallocated later + * We need to allocate and convert this before the fork so it is properly + * deallocated later */ - dsArray = (Tcl_DString *) ckalloc(argc * sizeof(Tcl_DString)); - newArgv = (char **) ckalloc((argc+1) * sizeof(char *)); + + 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]); } - joinThisError = errorFile && (errorFile == outputFile); +#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); /* @@ -440,11 +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); } @@ -454,30 +488,34 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, RestoreSignals(); execvp(newArgv[0], newArgv); /* INTL: Native. */ - sprintf(errSpace, "%dcouldn't execute \"%.150s\": ", errno, argv[0]); - write(fd, errSpace, (size_t) strlen(errSpace)); + 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]); } - ckfree((char *) dsArray); - ckfree((char *) newArgv); + 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); @@ -487,29 +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. 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] + * 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, 0); + Tcl_WaitPid((Tcl_Pid) INT2PTR(pid), &status, 0); } - + if (errPipeIn) { TclpCloseFile(errPipeIn); } @@ -524,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); @@ -598,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. @@ -613,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; @@ -624,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) { @@ -651,13 +690,13 @@ 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); + /* + * 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 @@ -677,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. @@ -691,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[16 + TCL_INTEGER_SPACE]; int channelId; - PipeState *statePtr = (PipeState *) ckalloc((unsigned) sizeof(PipeState)); + PipeState *statePtr = ckalloc(sizeof(PipeState)); int mode; statePtr->inFile = readFile; @@ -716,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) { @@ -738,26 +775,69 @@ 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 the interp's 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. @@ -769,14 +849,14 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) */ 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[TCL_INTEGER_SPACE]; /* * Punt if the channel is not a command channel. @@ -784,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++) { - TclFormatInt(buf, (long) 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; } } @@ -804,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. @@ -818,67 +900,22 @@ 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; - } + 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); @@ -888,11 +925,10 @@ PipeBlockModeProc(instanceData, mode) /* *---------------------------------------------------------------------- * - * 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. @@ -903,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; } @@ -973,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 @@ -987,38 +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. - * Some OSes can throw an interrupt error, for which we should - * immediately retry. [Bug #415131] + * nonblocking, the read will never block. Some OSes can throw an + * interrupt error, for which we should immediately retry. [Bug #415131] */ do { - bytesRead = read (GetFd(psPtr->inFile), buf, (size_t) toRead); + bytesRead = read(GetFd(psPtr->inFile), buf, (size_t) toRead); } while ((bytesRead < 0) && (errno == EINTR)); if (bytesRead < 0) { *errorCodePtr = errno; return -1; - } else { - return bytesRead; } + return bytesRead; } /* @@ -1026,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. @@ -1041,20 +1086,20 @@ PipeInputProc(instanceData, buf, toRead, errorCodePtr) */ static int -PipeOutputProc(instanceData, buf, toWrite, errorCodePtr) - ClientData instanceData; /* Pipe state. */ - CONST 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; /* - * Some OSes can throw an interrupt error, for which we should - * immediately retry. [Bug #415131] + * Some OSes can throw an interrupt error, for which we should immediately + * retry. [Bug #415131] */ do { @@ -1064,9 +1109,8 @@ PipeOutputProc(instanceData, buf, toWrite, errorCodePtr) if (written < 0) { *errorCodePtr = errno; return -1; - } else { - return written; } + return written; } /* @@ -1080,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)); } @@ -1110,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)); } @@ -1123,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. @@ -1137,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; @@ -1172,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); } } } @@ -1194,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. @@ -1208,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_SetObjResult(interp, Tcl_NewLongObj((long) getpid())); } else { - chan = Tcl_GetChannel(interp, Tcl_GetString(objv[1]), 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); + + /* + * Extract the process IDs from the pipe structure. + */ + + pipePtr = Tcl_GetChannelInstanceData(chan); resultPtr = Tcl_NewObj(); - for (i = 0; i < pipePtr->numPids; i++) { - longObjPtr = Tcl_NewLongObj((long) TclpGetPid(pipePtr->pidPtr[i])); - Tcl_ListObjAppendElement(NULL, resultPtr, longObjPtr); + 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: + */ |