freebsd_amp_hwpstate/contrib/tcl/unix/tclUnixPipe.c

497 lines
12 KiB
C
Raw Normal View History

/*
* 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.29 96/04/18 15:56:26
*/
#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));
/*
*----------------------------------------------------------------------
*
* 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;
}
/*
*----------------------------------------------------------------------
*
* TclSpawnPipeline --
*
* Given an argc/argv array, instantiate a pipeline of processes
* as described by the argv.
*
* Results:
* The return value is 1 on success, 0 on error
*
* Side effects:
* Processes and pipes are created.
*
*----------------------------------------------------------------------
*/
int
TclSpawnPipeline(interp, pidPtr, numPids, argc, argv, inputFile,
outputFile, errorFile, intIn, finalOut)
Tcl_Interp *interp; /* Interpreter in which to process pipeline. */
int *pidPtr; /* Array of pids which are created. */
int *numPids; /* Number of pids created. */
int argc; /* Number of entries in argv. */
char **argv; /* Array of strings describing commands in
* pipeline plus I/O redirection with <,
* <<, >, etc. argv[argc] must be NULL. */
Tcl_File inputFile; /* If >=0, gives file id to use as input for
* first process in pipeline (specified via <
* or <@). */
Tcl_File outputFile; /* Writable file id for output from last
* command in pipeline (could be file or
* pipe). NULL means use stdout. */
Tcl_File errorFile; /* Writable file id for error output from all
* commands in the pipeline. NULL means use
* stderr */
char *intIn; /* File name for initial input (for Win32s). */
char *finalOut; /* File name for final output (for Win32s). */
{
int firstArg, lastArg;
int pid, count;
Tcl_DString buffer;
char *execName;
char errSpace[200];
Tcl_File pipeIn, errPipeIn, errPipeOut;
int joinThisError;
Tcl_File curOutFile = NULL, curInFile;
Tcl_DStringInit(&buffer);
pipeIn = errPipeIn = errPipeOut = NULL;
curInFile = inputFile;
for (firstArg = 0; firstArg < argc; firstArg = lastArg+1) {
/*
* Convert the program name into native form.
*/
Tcl_DStringFree(&buffer);
execName = Tcl_TranslateFileName(interp, argv[firstArg], &buffer);
if (execName == NULL) {
goto error;
}
/*
* Find the end of the current segment of the pipeline.
*/
joinThisError = 0;
for (lastArg = firstArg; lastArg < argc; lastArg++) {
if (argv[lastArg][0] == '|') {
if (argv[lastArg][1] == 0) {
break;
}
if ((argv[lastArg][1] == '&') && (argv[lastArg][2] == 0)) {
joinThisError = 1;
break;
}
}
}
argv[lastArg] = NULL;
/*
* If this is the last segment, use the specified outputFile.
* Otherwise create an intermediate pipe.
*/
if (lastArg == argc) {
curOutFile = outputFile;
} else {
if (TclCreatePipe(&pipeIn, &curOutFile) == 0) {
Tcl_AppendResult(interp, "couldn't create pipe: ",
Tcl_PosixError(interp), (char *) NULL);
goto error;
}
}
/*
* 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;
}
pid = vfork();
if (pid == 0) {
/*
* Set up stdio file handles for the child process.
*/
if (!SetupStdFile(curInFile, TCL_STDIN)
|| !SetupStdFile(curOutFile, TCL_STDOUT)
|| (!joinThisError && !SetupStdFile(errorFile, TCL_STDERR))
|| (joinThisError &&
((dup2(1,2) == -1) ||
(fcntl(2, F_SETFD, 0) != 0)))) {
sprintf(errSpace,
"%dforked process couldn't set up input/output: ",
errno);
TclWriteFile(errPipeOut, 1, errSpace, (int) strlen(errSpace));
_exit(1);
}
/*
* Close the input side of the error pipe.
*/
RestoreSignals();
execvp(execName, &argv[firstArg]);
sprintf(errSpace, "%dcouldn't execute \"%.150s\": ", errno,
argv[firstArg]);
TclWriteFile(errPipeOut, 1, errSpace, (int) strlen(errSpace));
_exit(1);
}
Tcl_DStringFree(&buffer);
if (pid == -1) {
Tcl_AppendResult(interp, "couldn't fork child process: ",
Tcl_PosixError(interp), (char *) NULL);
goto error;
}
/*
* Add the child process to the list of those to be reaped.
* Note: must do it now, so that the process will be reaped even if
* an error occurs during its startup.
*/
pidPtr[*numPids] = pid;
(*numPids)++;
/*
* Read back from the error pipe to see if the child startup
* up OK. The info in the pipe (if any) consists of a decimal
* errno value followed by an error message.
*/
TclCloseFile(errPipeOut);
errPipeOut = NULL;
count = TclReadFile(errPipeIn, 1, errSpace,
(size_t) (sizeof(errSpace) - 1));
if (count > 0) {
char *end;
errSpace[count] = 0;
errno = strtol(errSpace, &end, 10);
Tcl_AppendResult(interp, end, Tcl_PosixError(interp),
(char *) NULL);
goto error;
}
TclCloseFile(errPipeIn);
errPipeIn = NULL;
/*
* Close off our copies of file descriptors that were set up for
* this child, then set up the input for the next child.
*/
if (curInFile && (curInFile != inputFile)) {
TclCloseFile(curInFile);
}
curInFile = pipeIn;
pipeIn = NULL;
if (curOutFile && (curOutFile != outputFile)) {
TclCloseFile(curOutFile);
}
curOutFile = NULL;
}
return 1;
/*
* An error occured, so we need to clean up any open pipes.
*/
error:
Tcl_DStringFree(&buffer);
if (errPipeIn) {
TclCloseFile(errPipeIn);
}
if (errPipeOut) {
TclCloseFile(errPipeOut);
}
if (pipeIn) {
TclCloseFile(pipeIn);
}
if (curOutFile && (curOutFile != outputFile)) {
TclCloseFile(curOutFile);
}
if (curInFile && (curInFile != inputFile)) {
TclCloseFile(curInFile);
}
return 0;
}
/*
*----------------------------------------------------------------------
*
* 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;
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreatePipeline --
*
* This function is a compatibility wrapper for TclCreatePipeline.
* It is only available under Unix, and may be removed from later
* versions.
*
* Results:
* Same as TclCreatePipeline.
*
* Side effects:
* Same as TclCreatePipeline.
*
*----------------------------------------------------------------------
*/
int
Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
outPipePtr, errFilePtr)
Tcl_Interp *interp;
int argc;
char **argv;
int **pidArrayPtr;
int *inPipePtr;
int *outPipePtr;
int *errFilePtr;
{
Tcl_File inFile, outFile, errFile;
int result;
result = TclCreatePipeline(interp, argc, argv, pidArrayPtr,
(inPipePtr ? &inFile : NULL),
(outPipePtr ? &outFile : NULL),
(errFilePtr ? &errFile : NULL));
if (inPipePtr) {
if (inFile) {
*inPipePtr = (int) Tcl_GetFileInfo(inFile, NULL);
Tcl_FreeFile(inFile);
} else {
*inPipePtr = -1;
}
}
if (outPipePtr) {
if (outFile) {
*outPipePtr = (int) Tcl_GetFileInfo(outFile, NULL);
Tcl_FreeFile(outFile);
} else {
*outPipePtr = -1;
}
}
if (errFilePtr) {
if (errFile) {
*errFilePtr = (int) Tcl_GetFileInfo(errFile, NULL);
Tcl_FreeFile(errFile);
} else {
*errFilePtr = -1;
}
}
return result;
}