379 lines
9.5 KiB
C
379 lines
9.5 KiB
C
|
/*
|
|||
|
* tclUnixPipe.c -- This file implements the UNIX-specific exec pipeline
|
|||
|
* functions.
|
|||
|
*
|
|||
|
* Copyright (c) 1991-1994 The Regents of the University of California.
|
|||
|
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
|
|||
|
*
|
|||
|
* See the file "license.terms" for information on usage and redistribution
|
|||
|
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|||
|
*
|
|||
|
* SCCS: @(#) tclUnixPipe.c 1.30 96/09/12 14:57:15
|
|||
|
*/
|
|||
|
|
|||
|
#include "tclInt.h"
|
|||
|
#include "tclPort.h"
|
|||
|
|
|||
|
/*
|
|||
|
* Declarations for local procedures defined in this file:
|
|||
|
*/
|
|||
|
|
|||
|
static void RestoreSignals _ANSI_ARGS_((void));
|
|||
|
static int SetupStdFile _ANSI_ARGS_((Tcl_File file, int type));
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclpCreateProcess --
|
|||
|
*
|
|||
|
* Create a child process that has the specified files as its
|
|||
|
* standard input, output, and error. The child process runs
|
|||
|
* asynchronously and runs with the same environment variables
|
|||
|
* as the creating process.
|
|||
|
*
|
|||
|
* The path is searched to find the specified executable.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The return value is TCL_ERROR and an error message is left in
|
|||
|
* interp->result if there was a problem creating the child
|
|||
|
* process. Otherwise, the return value is TCL_OK and *pidPtr is
|
|||
|
* filled with the process id of the child process.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A process is created.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
/* ARGSUSED */
|
|||
|
int
|
|||
|
TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile,
|
|||
|
inputFileName, outputFileName, errorFileName, pidPtr)
|
|||
|
Tcl_Interp *interp; /* Interpreter in which to leave errors that
|
|||
|
* occurred when creating the child process.
|
|||
|
* Error messages from the child process
|
|||
|
* itself are sent to errorFile. */
|
|||
|
int argc; /* Number of arguments in following array. */
|
|||
|
char **argv; /* Array of argument strings. argv[0]
|
|||
|
* contains the name of the executable
|
|||
|
* converted to native format (using the
|
|||
|
* Tcl_TranslateFileName call). Additional
|
|||
|
* arguments have not been converted. */
|
|||
|
Tcl_File 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. */
|
|||
|
Tcl_File 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. */
|
|||
|
Tcl_File 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. */
|
|||
|
char *inputFileName; /* If non-NULL, gives the name of the disk
|
|||
|
* file that corresponds to inputFile
|
|||
|
* (unused). */
|
|||
|
char *outputFileName; /* If non-NULL, gives the name of the disk
|
|||
|
* file that corresponds to outputFile
|
|||
|
* (unused). */
|
|||
|
char *errorFileName; /* If non-NULL, gives the name of the disk
|
|||
|
* file that corresponds to errorFile
|
|||
|
* (unused). */
|
|||
|
int *pidPtr; /* If this procedure is successful, pidPtr
|
|||
|
* is filled with the process id of the child
|
|||
|
* process. */
|
|||
|
{
|
|||
|
Tcl_File errPipeIn, errPipeOut;
|
|||
|
int pid, joinThisError, count, status;
|
|||
|
char errSpace[200];
|
|||
|
|
|||
|
errPipeIn = NULL;
|
|||
|
errPipeOut = NULL;
|
|||
|
pid = -1;
|
|||
|
|
|||
|
/*
|
|||
|
* Create a pipe that the child can use to return error
|
|||
|
* information if anything goes wrong.
|
|||
|
*/
|
|||
|
|
|||
|
if (TclCreatePipe(&errPipeIn, &errPipeOut) == 0) {
|
|||
|
Tcl_AppendResult(interp, "couldn't create pipe: ",
|
|||
|
Tcl_PosixError(interp), (char *) NULL);
|
|||
|
goto error;
|
|||
|
}
|
|||
|
|
|||
|
joinThisError = (errorFile == outputFile);
|
|||
|
pid = vfork();
|
|||
|
if (pid == 0) {
|
|||
|
|
|||
|
/*
|
|||
|
* 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);
|
|||
|
TclWriteFile(errPipeOut, 1, errSpace, (int) strlen(errSpace));
|
|||
|
_exit(1);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Close the input side of the error pipe.
|
|||
|
*/
|
|||
|
|
|||
|
RestoreSignals();
|
|||
|
execvp(argv[0], &argv[0]);
|
|||
|
sprintf(errSpace, "%dcouldn't execute \"%.150s\": ", errno,
|
|||
|
argv[0]);
|
|||
|
TclWriteFile(errPipeOut, 1, errSpace, (int) strlen(errSpace));
|
|||
|
_exit(1);
|
|||
|
}
|
|||
|
if (pid == -1) {
|
|||
|
Tcl_AppendResult(interp, "couldn't fork child process: ",
|
|||
|
Tcl_PosixError(interp), (char *) NULL);
|
|||
|
goto error;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Read back from the error pipe to see if the child startup
|
|||
|
* up OK. The info in the pipe (if any) consists of a decimal
|
|||
|
* errno value followed by an error message.
|
|||
|
*/
|
|||
|
|
|||
|
TclCloseFile(errPipeOut);
|
|||
|
errPipeOut = NULL;
|
|||
|
|
|||
|
count = TclReadFile(errPipeIn, 1, errSpace,
|
|||
|
(size_t) (sizeof(errSpace) - 1));
|
|||
|
if (count > 0) {
|
|||
|
char *end;
|
|||
|
errSpace[count] = 0;
|
|||
|
errno = strtol(errSpace, &end, 10);
|
|||
|
Tcl_AppendResult(interp, end, Tcl_PosixError(interp),
|
|||
|
(char *) NULL);
|
|||
|
goto error;
|
|||
|
}
|
|||
|
|
|||
|
TclCloseFile(errPipeIn);
|
|||
|
*pidPtr = pid;
|
|||
|
return TCL_OK;
|
|||
|
|
|||
|
error:
|
|||
|
if (pid != -1) {
|
|||
|
/*
|
|||
|
* Reap the child process now if an error occurred during its
|
|||
|
* startup.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_WaitPid(pid, &status, WNOHANG);
|
|||
|
}
|
|||
|
|
|||
|
if (errPipeIn) {
|
|||
|
TclCloseFile(errPipeIn);
|
|||
|
}
|
|||
|
if (errPipeOut) {
|
|||
|
TclCloseFile(errPipeOut);
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* 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.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Signal settings get changed.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
RestoreSignals()
|
|||
|
{
|
|||
|
#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(file, type)
|
|||
|
Tcl_File 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 = Tcl_GetChannelFile(channel, direction);
|
|||
|
}
|
|||
|
}
|
|||
|
if (file) {
|
|||
|
fd = (int)Tcl_GetFileInfo(file, NULL);
|
|||
|
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 {
|
|||
|
int result;
|
|||
|
|
|||
|
/*
|
|||
|
* Since we aren't dup'ing the file, we need to explicitly clear
|
|||
|
* the close-on-exec flag.
|
|||
|
*/
|
|||
|
|
|||
|
result = fcntl(fd, F_SETFD, 0);
|
|||
|
}
|
|||
|
} else {
|
|||
|
close(targetFd);
|
|||
|
}
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclCreatePipe --
|
|||
|
*
|
|||
|
* Creates a pipe - simply calls the pipe() function.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns 1 on success, 0 on failure.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Creates a pipe.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
int
|
|||
|
TclCreatePipe(readPipe, writePipe)
|
|||
|
Tcl_File *readPipe; /* Location to store file handle for
|
|||
|
* read side of pipe. */
|
|||
|
Tcl_File *writePipe; /* Location to store file handle for
|
|||
|
* write side of pipe. */
|
|||
|
{
|
|||
|
int pipeIds[2];
|
|||
|
|
|||
|
if (pipe(pipeIds) != 0) {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
fcntl(pipeIds[0], F_SETFD, FD_CLOEXEC);
|
|||
|
fcntl(pipeIds[1], F_SETFD, FD_CLOEXEC);
|
|||
|
|
|||
|
*readPipe = Tcl_GetFile((ClientData)pipeIds[0], TCL_UNIX_FD);
|
|||
|
*writePipe = Tcl_GetFile((ClientData)pipeIds[1], TCL_UNIX_FD);
|
|||
|
return 1;
|
|||
|
}
|