1326 lines
32 KiB
C
1326 lines
32 KiB
C
/*
|
||
* 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-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.
|
||
*/
|
||
|
||
#include "tclInt.h"
|
||
|
||
#ifdef USE_VFORK
|
||
#define fork vfork
|
||
#endif
|
||
|
||
/*
|
||
* 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) 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. */
|
||
} PipeState;
|
||
|
||
/*
|
||
* Declarations for local functions defined in this file:
|
||
*/
|
||
|
||
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
|
||
* I/O:
|
||
*/
|
||
|
||
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 */
|
||
};
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpMakeFile --
|
||
*
|
||
* Make a TclFile from a channel.
|
||
*
|
||
* Results:
|
||
* Returns a new TclFile or NULL on failure.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
TclFile
|
||
TclpMakeFile(
|
||
Tcl_Channel channel, /* Channel to get file from. */
|
||
int direction) /* Either TCL_READABLE or TCL_WRITABLE. */
|
||
{
|
||
ClientData data;
|
||
|
||
if (Tcl_GetChannelHandle(channel, direction, &data) != TCL_OK) {
|
||
return NULL;
|
||
}
|
||
|
||
return MakeFile(PTR2INT(data));
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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(
|
||
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;
|
||
|
||
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);
|
||
|
||
/*
|
||
* 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) && !(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.
|
||
*/
|
||
|
||
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(
|
||
const char *contents) /* String to write into temp file, or 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;
|
||
}
|
||
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 *retVal, *nameObj;
|
||
int fd;
|
||
|
||
TclNewObj(nameObj);
|
||
Tcl_IncrRefCount(nameObj);
|
||
fd = TclUnixOpenTemporaryFile(NULL, NULL, NULL, nameObj);
|
||
if (fd == -1) {
|
||
Tcl_DecrRefCount(nameObj);
|
||
return NULL;
|
||
}
|
||
|
||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||
TclpObjDeleteFile(nameObj);
|
||
close(fd);
|
||
retVal = Tcl_DuplicateObj(nameObj);
|
||
Tcl_DecrRefCount(nameObj);
|
||
return retVal;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------------
|
||
*
|
||
* 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 retval;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpCreatePipe --
|
||
*
|
||
* Creates a pipe - simply calls the pipe() function.
|
||
*
|
||
* Results:
|
||
* Returns 1 on success, 0 on failure.
|
||
*
|
||
* Side effects:
|
||
* Creates a pipe.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
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];
|
||
|
||
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(
|
||
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 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.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
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.
|
||
* 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 function is successful, pidPtr is
|
||
* filled with the process id of the child
|
||
* process. */
|
||
{
|
||
TclFile errPipeIn, errPipeOut;
|
||
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.
|
||
*/
|
||
|
||
if (TclpCreatePipe(&errPipeIn, &errPipeOut) == 0) {
|
||
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
|
||
*/
|
||
|
||
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);
|
||
|
||
/*
|
||
* 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);
|
||
len = strlen(errSpace);
|
||
if (len != (size_t) write(fd, errSpace, len)) {
|
||
Tcl_Panic("TclpCreateProcess: unable to write to errPipeOut");
|
||
}
|
||
_exit(1);
|
||
}
|
||
|
||
/*
|
||
* Close the input side of the error pipe.
|
||
*/
|
||
|
||
RestoreSignals();
|
||
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_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.
|
||
*/
|
||
|
||
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_SetObjResult(interp, Tcl_ObjPrintf("%s: %s",
|
||
end, Tcl_PosixError(interp)));
|
||
goto error;
|
||
}
|
||
|
||
TclpCloseFile(errPipeIn);
|
||
*pidPtr = (Tcl_Pid) INT2PTR(pid);
|
||
return TCL_OK;
|
||
|
||
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]
|
||
*/
|
||
|
||
Tcl_WaitPid((Tcl_Pid) INT2PTR(pid), &status, 0);
|
||
}
|
||
|
||
if (errPipeIn) {
|
||
TclpCloseFile(errPipeIn);
|
||
}
|
||
if (errPipeOut) {
|
||
TclpCloseFile(errPipeOut);
|
||
}
|
||
return TCL_ERROR;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* RestoreSignals --
|
||
*
|
||
* 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.
|
||
*
|
||
* Side effects:
|
||
* Signal settings get changed.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static void
|
||
RestoreSignals(void)
|
||
{
|
||
#ifdef SIGABRT
|
||
signal(SIGABRT, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGALRM
|
||
signal(SIGALRM, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGFPE
|
||
signal(SIGFPE, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGHUP
|
||
signal(SIGHUP, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGILL
|
||
signal(SIGILL, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGINT
|
||
signal(SIGINT, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGPIPE
|
||
signal(SIGPIPE, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGQUIT
|
||
signal(SIGQUIT, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGSEGV
|
||
signal(SIGSEGV, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGTERM
|
||
signal(SIGTERM, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGUSR1
|
||
signal(SIGUSR1, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGUSR2
|
||
signal(SIGUSR2, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGCHLD
|
||
signal(SIGCHLD, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGCONT
|
||
signal(SIGCONT, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGTSTP
|
||
signal(SIGTSTP, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGTTIN
|
||
signal(SIGTTIN, SIG_DFL);
|
||
#endif
|
||
#ifdef SIGTTOU
|
||
signal(SIGTTOU, SIG_DFL);
|
||
#endif
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* SetupStdFile --
|
||
*
|
||
* Set up stdio file handles for the child process, using the current
|
||
* standard channels if no other files are specified. If no standard
|
||
* channel is defined, or if no file is associated with the channel, then
|
||
* the corresponding standard fd is closed.
|
||
*
|
||
* Results:
|
||
* Returns 1 on success, or 0 on failure.
|
||
*
|
||
* Side effects:
|
||
* Replaces stdio fds.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
SetupStdFile(
|
||
TclFile file, /* File to dup, or NULL. */
|
||
int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */
|
||
{
|
||
Tcl_Channel channel;
|
||
int fd;
|
||
int targetFd = 0; /* Initializations here needed only to */
|
||
int direction = 0; /* prevent warnings about using uninitialized
|
||
* variables. */
|
||
|
||
switch (type) {
|
||
case TCL_STDIN:
|
||
targetFd = 0;
|
||
direction = TCL_READABLE;
|
||
break;
|
||
case TCL_STDOUT:
|
||
targetFd = 1;
|
||
direction = TCL_WRITABLE;
|
||
break;
|
||
case TCL_STDERR:
|
||
targetFd = 2;
|
||
direction = TCL_WRITABLE;
|
||
break;
|
||
}
|
||
|
||
if (!file) {
|
||
channel = Tcl_GetStdChannel(type);
|
||
if (channel) {
|
||
file = TclpMakeFile(channel, direction);
|
||
}
|
||
}
|
||
if (file) {
|
||
fd = GetFd(file);
|
||
if (fd != targetFd) {
|
||
if (dup2(fd, targetFd) == -1) {
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Must clear the close-on-exec flag for the target FD, since some
|
||
* systems (e.g. Ultrix) do not clear the CLOEXEC flag on the
|
||
* target FD.
|
||
*/
|
||
|
||
fcntl(targetFd, F_SETFD, 0);
|
||
} else {
|
||
/*
|
||
* Since we aren't dup'ing the file, we need to explicitly clear
|
||
* the close-on-exec flag.
|
||
*/
|
||
|
||
fcntl(fd, F_SETFD, 0);
|
||
}
|
||
} else {
|
||
close(targetFd);
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* TclpCreateCommandChannel --
|
||
*
|
||
* 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.
|
||
*
|
||
* Side effects:
|
||
* Allocates a new channel.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Channel
|
||
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). */
|
||
{
|
||
char channelName[16 + TCL_INTEGER_SPACE];
|
||
int channelId;
|
||
PipeState *statePtr = ckalloc(sizeof(PipeState));
|
||
int mode;
|
||
|
||
statePtr->inFile = readFile;
|
||
statePtr->outFile = writeFile;
|
||
statePtr->errorFile = errorFile;
|
||
statePtr->numPids = numPids;
|
||
statePtr->pidPtr = pidPtr;
|
||
statePtr->isNonBlocking = 0;
|
||
|
||
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.
|
||
*/
|
||
|
||
if (readFile) {
|
||
channelId = GetFd(readFile);
|
||
} else if (writeFile) {
|
||
channelId = GetFd(writeFile);
|
||
} else if (errorFile) {
|
||
channelId = GetFd(errorFile);
|
||
} else {
|
||
channelId = 0;
|
||
}
|
||
|
||
/*
|
||
* 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,
|
||
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 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 the interp's result. Detaches processes.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
void
|
||
TclGetAndDetachPids(
|
||
Tcl_Interp *interp, /* Interpreter to append the PIDs to. */
|
||
Tcl_Channel chan) /* Handle for the pipeline. */
|
||
{
|
||
PipeState *pipePtr;
|
||
const Tcl_ChannelType *chanTypePtr;
|
||
Tcl_Obj *pidsObj;
|
||
int i;
|
||
|
||
/*
|
||
* Punt if the channel is not a command channel.
|
||
*/
|
||
|
||
chanTypePtr = Tcl_GetChannelType(chan);
|
||
if (chanTypePtr != &pipeChannelType) {
|
||
return;
|
||
}
|
||
|
||
pipePtr = Tcl_GetChannelInstanceData(chan);
|
||
TclNewObj(pidsObj);
|
||
for (i = 0; i < pipePtr->numPids; 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(pipePtr->pidPtr);
|
||
pipePtr->numPids = 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* PipeBlockModeProc --
|
||
*
|
||
* 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.
|
||
*
|
||
* Side effects:
|
||
* Sets the device into blocking or non-blocking mode.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
PipeBlockModeProc(
|
||
ClientData instanceData, /* Pipe state. */
|
||
int mode) /* The mode to set. Can be one of
|
||
* TCL_MODE_BLOCKING or
|
||
* TCL_MODE_NONBLOCKING. */
|
||
{
|
||
PipeState *psPtr = instanceData;
|
||
|
||
if (psPtr->inFile
|
||
&& TclUnixSetBlockingMode(GetFd(psPtr->inFile), mode) < 0) {
|
||
return errno;
|
||
}
|
||
if (psPtr->outFile
|
||
&& TclUnixSetBlockingMode(GetFd(psPtr->outFile), mode) < 0) {
|
||
return errno;
|
||
}
|
||
|
||
psPtr->isNonBlocking = (mode == TCL_MODE_NONBLOCKING);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* PipeClose2Proc
|
||
*
|
||
* This function is invoked by the generic IO level to perform
|
||
* pipeline-type-specific half or full-close.
|
||
*
|
||
* Results:
|
||
* 0 on success, errno otherwise.
|
||
*
|
||
* Side effects:
|
||
* Closes the command pipeline channel.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
PipeClose2Proc(
|
||
ClientData instanceData, /* The pipe to close. */
|
||
Tcl_Interp *interp, /* For error reporting. */
|
||
int flags) /* Flags that indicate which side to close. */
|
||
{
|
||
PipeState *pipePtr = instanceData;
|
||
Tcl_Channel errChan;
|
||
int errorCode, result;
|
||
|
||
errorCode = 0;
|
||
result = 0;
|
||
|
||
if (((!flags) || (flags & TCL_CLOSE_READ)) && (pipePtr->inFile != NULL)) {
|
||
if (TclpCloseFile(pipePtr->inFile) < 0) {
|
||
errorCode = errno;
|
||
} else {
|
||
pipePtr->inFile = NULL;
|
||
}
|
||
}
|
||
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) {
|
||
TclpCloseFile(pipePtr->errorFile);
|
||
}
|
||
} else {
|
||
/*
|
||
* Wrap the error file into a channel and give it to the cleanup
|
||
* routine.
|
||
*/
|
||
|
||
if (pipePtr->errorFile) {
|
||
errChan = Tcl_MakeFileChannel(
|
||
INT2PTR(GetFd(pipePtr->errorFile)),
|
||
TCL_READABLE);
|
||
} else {
|
||
errChan = NULL;
|
||
}
|
||
result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr,
|
||
errChan);
|
||
}
|
||
|
||
if (pipePtr->numPids != 0) {
|
||
ckfree(pipePtr->pidPtr);
|
||
}
|
||
ckfree(pipePtr);
|
||
if (errorCode == 0) {
|
||
return result;
|
||
}
|
||
return errorCode;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* PipeInputProc --
|
||
*
|
||
* 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
|
||
* 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(
|
||
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 = 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]
|
||
*/
|
||
|
||
do {
|
||
bytesRead = read(GetFd(psPtr->inFile), buf, (size_t) toRead);
|
||
} while ((bytesRead < 0) && (errno == EINTR));
|
||
|
||
if (bytesRead < 0) {
|
||
*errorCodePtr = errno;
|
||
return -1;
|
||
}
|
||
return bytesRead;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* PipeOutputProc--
|
||
*
|
||
* 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.
|
||
*
|
||
* Side effects:
|
||
* Writes output on the output device of the channel.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
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 = instanceData;
|
||
int written;
|
||
|
||
*errorCodePtr = 0;
|
||
|
||
/*
|
||
* 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;
|
||
}
|
||
return written;
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* 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(
|
||
ClientData instanceData, /* The pipe state. */
|
||
int mask) /* Events of interest; an OR-ed combination of
|
||
* TCL_READABLE, TCL_WRITABLE and
|
||
* TCL_EXCEPTION. */
|
||
{
|
||
PipeState *psPtr = instanceData;
|
||
int newmask;
|
||
|
||
if (psPtr->inFile) {
|
||
newmask = mask & (TCL_READABLE | TCL_EXCEPTION);
|
||
if (newmask) {
|
||
Tcl_CreateFileHandler(GetFd(psPtr->inFile), newmask,
|
||
(Tcl_FileProc *) Tcl_NotifyChannel, psPtr->channel);
|
||
} else {
|
||
Tcl_DeleteFileHandler(GetFd(psPtr->inFile));
|
||
}
|
||
}
|
||
if (psPtr->outFile) {
|
||
newmask = mask & (TCL_WRITABLE | TCL_EXCEPTION);
|
||
if (newmask) {
|
||
Tcl_CreateFileHandler(GetFd(psPtr->outFile), newmask,
|
||
(Tcl_FileProc *) Tcl_NotifyChannel, psPtr->channel);
|
||
} else {
|
||
Tcl_DeleteFileHandler(GetFd(psPtr->outFile));
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* PipeGetHandleProc --
|
||
*
|
||
* 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.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
PipeGetHandleProc(
|
||
ClientData instanceData, /* The pipe state. */
|
||
int direction, /* TCL_READABLE or TCL_WRITABLE */
|
||
ClientData *handlePtr) /* Where to store the handle. */
|
||
{
|
||
PipeState *psPtr = instanceData;
|
||
|
||
if (direction == TCL_READABLE && psPtr->inFile) {
|
||
*handlePtr = INT2PTR(GetFd(psPtr->inFile));
|
||
return TCL_OK;
|
||
}
|
||
if (direction == TCL_WRITABLE && psPtr->outFile) {
|
||
*handlePtr = INT2PTR(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.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Pid
|
||
Tcl_WaitPid(
|
||
Tcl_Pid pid,
|
||
int *statPtr,
|
||
int options)
|
||
{
|
||
int result;
|
||
pid_t real_pid = (pid_t) PTR2INT(pid);
|
||
|
||
while (1) {
|
||
result = (int) waitpid(real_pid, statPtr, options);
|
||
if ((result != -1) || (errno != EINTR)) {
|
||
return (Tcl_Pid) INT2PTR(result);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* Tcl_PidObjCmd --
|
||
*
|
||
* 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.
|
||
*
|
||
* Side effects:
|
||
* See the user documentation.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
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;
|
||
PipeState *pipePtr;
|
||
int i;
|
||
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 {
|
||
/*
|
||
* 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;
|
||
}
|
||
if (Tcl_GetChannelType(chan) != &pipeChannelType) {
|
||
return TCL_OK;
|
||
}
|
||
|
||
/*
|
||
* Extract the process IDs from the pipe structure.
|
||
*/
|
||
|
||
pipePtr = Tcl_GetChannelInstanceData(chan);
|
||
TclNewObj(resultPtr);
|
||
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:
|
||
*/
|