📄 tcluxutl.c
字号:
/*
* tclUnixUtil.c --
*
* This file contains a collection of utility procedures that
* are present in the Tcl's UNIX core but not in the generic
* core. For example, they do file manipulation and process
* manipulation.
*
* The Tcl_Fork and Tcl_WaitPids procedures are based on code
* contributed by Karl Lehenbauer, Mark Diekhans and Peter
* da Silva.
*
* Copyright 1991 Regents of the University of California
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that this copyright
* notice appears in all copies. The University of California
* makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without
* express or implied warranty.
*
* $Id: tcluxutl.c,v 1.1.1.1 2001/04/29 20:35:47 karll Exp $
*/
#include "tclInt.h"
#include "tclEcos.h"
/*
* Data structures of the following type are used by Tcl_Fork and
* Tcl_WaitPids to keep track of child processes.
*/
#define WAIT_STATUS_TYPE int
typedef struct {
int pid; /* Process id of child. */
WAIT_STATUS_TYPE status; /* Status returned when child exited or
* suspended. */
int flags; /* Various flag bits; see below for
* definitions. */
} WaitInfo;
/*
* Flag bits in WaitInfo structures:
*
* WI_READY - Non-zero means process has exited or
* suspended since it was forked or last
* returned by Tcl_WaitPids.
* WI_DETACHED - Non-zero means no-one cares about the
* process anymore. Ignore it until it
* exits, then forget about it.
*/
#define WI_READY 1
#define WI_DETACHED 2
#if TCL_FORK_ENABLED
static WaitInfo *waitTable = NULL;
static int waitTableSize = 0; /* Total number of entries available in
* waitTable. */
static int waitTableUsed = 0; /* Number of entries in waitTable that
* are actually in use right now. Active
* entries are always at the beginning
* of the table. */
#define WAIT_TABLE_GROW_BY 4
#endif
/*
*----------------------------------------------------------------------
*
* Tcl_EvalFile --
*
* Read in a file and process the entire file as one gigantic
* Tcl command.
*
* Results:
* A standard Tcl result, which is either the result of executing
* the file or an error indicating why the file couldn't be read.
*
* Side effects:
* Depends on the commands in the file.
*
*----------------------------------------------------------------------
*/
int
Tcl_EvalFile(interp, fileName)
Tcl_Interp *interp; /* Interpreter in which to process file. */
char *fileName; /* Name of file to process. Tilde-substitution
* will be performed on this name. */
{
int fileId, result;
struct stat statBuf;
char *cmdBuffer, *end, *oldScriptFile;
Interp *iPtr = (Interp *) interp;
oldScriptFile = iPtr->scriptFile;
iPtr->scriptFile = fileName;
fileName = Tcl_TildeSubst(interp, fileName);
if (fileName == NULL) {
goto error;
}
fileId = open(fileName, O_RDONLY, 0);
if (fileId < 0) {
Tcl_AppendResult(interp, "couldn't read file \"", fileName,
"\": ", Tcl_UnixError(interp), (char *) NULL);
goto error;
}
if (fstat(fileId, &statBuf) == -1) {
Tcl_AppendResult(interp, "couldn't stat file \"", fileName,
"\": ", Tcl_UnixError(interp), (char *) NULL);
close(fileId);
goto error;
}
cmdBuffer = (char *) ckalloc((unsigned) statBuf.st_size+1);
if (read(fileId, cmdBuffer, (int) statBuf.st_size) != statBuf.st_size) {
Tcl_AppendResult(interp, "error in reading file \"", fileName,
"\": ", Tcl_UnixError(interp), (char *) NULL);
close(fileId);
ckfree(cmdBuffer);
goto error;
}
if (close(fileId) != 0) {
Tcl_AppendResult(interp, "error closing file \"", fileName,
"\": ", Tcl_UnixError(interp), (char *) NULL);
ckfree(cmdBuffer);
goto error;
}
cmdBuffer[statBuf.st_size] = 0;
result = Tcl_Eval(interp, cmdBuffer, 0, &end);
if (result == TCL_RETURN) {
result = TCL_OK;
}
if (result == TCL_ERROR) {
char msg[200];
/*
* Record information telling where the error occurred.
*/
sprintf(msg, "\n (file \"%.150s\" line %d)", fileName,
interp->errorLine);
Tcl_AddErrorInfo(interp, msg);
}
ckfree(cmdBuffer);
iPtr->scriptFile = oldScriptFile;
return result;
error:
iPtr->scriptFile = oldScriptFile;
return TCL_ERROR;
}
/*
*----------------------------------------------------------------------
*
* Tcl_Fork --
*
* Create a new process using the vfork system call, and keep
* track of it for "safe" waiting with Tcl_WaitPids.
*
* Results:
* The return value is the value returned by the vfork system
* call (0 means child, > 0 means parent (value is child id),
* < 0 means error).
*
* Side effects:
* A new process is created, and an entry is added to an internal
* table of child processes if the process is created successfully.
*
*----------------------------------------------------------------------
*/
#if TCL_FORK_ENABLED
int
Tcl_Fork()
{
WaitInfo *waitPtr;
pid_t pid;
/*
* Disable SIGPIPE signals: if they were allowed, this process
* might go away unexpectedly if children misbehave. This code
* can potentially interfere with other application code that
* expects to handle SIGPIPEs; what's really needed is an
* arbiter for signals to allow them to be "shared".
*/
if (waitTable == NULL) {
(void) signal(SIGPIPE, SIG_IGN);
}
/*
* Enlarge the wait table if there isn't enough space for a new
* entry.
*/
if (waitTableUsed == waitTableSize) {
int newSize;
WaitInfo *newWaitTable;
newSize = waitTableSize + WAIT_TABLE_GROW_BY;
newWaitTable = (WaitInfo *) ckalloc((unsigned)
(newSize * sizeof(WaitInfo)));
memcpy((VOID *) newWaitTable, (VOID *) waitTable,
(waitTableSize * sizeof(WaitInfo)));
if (waitTable != NULL) {
ckfree((char *) waitTable);
}
waitTable = newWaitTable;
waitTableSize = newSize;
}
/*
* Make a new process and enter it into the table if the fork
* is successful.
*/
waitPtr = &waitTable[waitTableUsed];
pid = vfork();
if (pid > 0) {
waitPtr->pid = pid;
waitPtr->flags = 0;
waitTableUsed++;
}
return pid;
}
/*
*----------------------------------------------------------------------
*
* Tcl_WaitPids --
*
* This procedure is used to wait for one or more processes created
* by Tcl_Fork to exit or suspend. It records information about
* all processes that exit or suspend, even those not waited for,
* so that later waits for them will be able to get the status
* information.
*
* Results:
* -1 is returned if there is an error in the wait kernel call.
* Otherwise the pid of an exited/suspended process from *pidPtr
* is returned and *statusPtr is set to the status value returned
* by the wait kernel call.
*
* Side effects:
* Doesn't return until one of the pids at *pidPtr exits or suspends.
*
*----------------------------------------------------------------------
*/
int
Tcl_WaitPids(numPids, pidPtr, statusPtr)
int numPids; /* Number of pids to wait on: gives size
* of array pointed to by pidPtr. */
int *pidPtr; /* Pids to wait on: return when one of
* these processes exits or suspends. */
int *statusPtr; /* Wait status is returned here. */
{
int i, count, pid;
register WaitInfo *waitPtr;
int anyProcesses;
WAIT_STATUS_TYPE status;
while (1) {
/*
* Scan the table of child processes to see if one of the
* specified children has already exited or suspended. If so,
* remove it from the table and return its status.
*/
anyProcesses = 0;
for (waitPtr = waitTable, count = waitTableUsed;
count > 0; waitPtr++, count--) {
for (i = 0; i < numPids; i++) {
if (pidPtr[i] != waitPtr->pid) {
continue;
}
anyProcesses = 1;
if (waitPtr->flags & WI_READY) {
*statusPtr = *((int *) &waitPtr->status);
pid = waitPtr->pid;
if (WIFEXITED(waitPtr->status)
|| WIFSIGNALED(waitPtr->status)) {
*waitPtr = waitTable[waitTableUsed-1];
waitTableUsed--;
} else {
waitPtr->flags &= ~WI_READY;
}
return pid;
}
}
}
/*
* Make sure that the caller at least specified one valid
* process to wait for.
*/
if (!anyProcesses) {
errno = ECHILD;
return -1;
}
/*
* Wait for a process to exit or suspend, then update its
* entry in the table and go back to the beginning of the
* loop to see if it's one of the desired processes.
*/
pid = wait(&status);
if (pid < 0) {
return pid;
}
for (waitPtr = waitTable, count = waitTableUsed; ;
waitPtr++, count--) {
if (count == 0) {
break; /* Ignore unknown processes. */
}
if (pid != waitPtr->pid) {
continue;
}
/*
* If the process has been detached, then ignore anything
* other than an exit, and drop the entry on exit.
*/
if (waitPtr->flags & WI_DETACHED) {
if (WIFEXITED(status) || WIFSIGNALED(status)) {
*waitPtr = waitTable[waitTableUsed-1];
waitTableUsed--;
}
} else {
waitPtr->status = status;
waitPtr->flags |= WI_READY;
}
break;
}
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_DetachPids --
*
* This procedure is called to indicate that one or more child
* processes have been placed in background and are no longer
* cared about. They should be ignored in future calls to
* Tcl_WaitPids.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
Tcl_DetachPids(numPids, pidPtr)
int numPids; /* Number of pids to detach: gives size
* of array pointed to by pidPtr. */
int *pidPtr; /* Array of pids to detach: must have
* been created by Tcl_Fork. */
{
register WaitInfo *waitPtr;
int i, count, pid;
for (i = 0; i < numPids; i++) {
pid = pidPtr[i];
for (waitPtr = waitTable, count = waitTableUsed;
count > 0; waitPtr++, count--) {
if (pid != waitPtr->pid) {
continue;
}
/*
* If the process has already exited then destroy its
* table entry now.
*/
if ((waitPtr->flags & WI_READY) && (WIFEXITED(waitPtr->status)
|| WIFSIGNALED(waitPtr->status))) {
*waitPtr = waitTable[waitTableUsed-1];
waitTableUsed--;
} else {
waitPtr->flags |= WI_DETACHED;
}
goto nextPid;
}
panic("Tcl_Detach couldn't find process");
nextPid:
continue;
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreatePipeline --
*
* Given an argc/argv array, instantiate a pipeline of processes
* as described by the argv.
*
* Results:
* The return value is a count of the number of new processes
* created, or -1 if an error occurred while creating the pipeline.
* *pidArrayPtr is filled in with the address of a dynamically
* allocated array giving the ids of all of the processes. It
* is up to the caller to free this array when it isn't needed
* anymore. If inPipePtr is non-NULL, *inPipePtr is filled in
* with the file id for the input pipe for the pipeline (if any):
* the caller must eventually close this file. If outPipePtr
* isn't NULL, then *outPipePtr is filled in with the file id
* for the output pipe from the pipeline: the caller must close
* this file. If errFilePtr isn't NULL, then *errFilePtr is filled
* with a file id that may be used to read error output after the
* pipeline completes.
*
* Side effects:
* Processes and pipes are created.
*
*----------------------------------------------------------------------
*/
int
Tcl_CreatePipeline(interp, argc, argv, pidArrayPtr, inPipePtr,
outPipePtr, errFilePtr)
Tcl_Interp *interp; /* Interpreter to use for error reporting. */
int argc; /* Number of entries in argv. */
char **argv; /* Array of strings describing commands in
* pipeline plus I/O redirection with <,
* <<, and >. Argv[argc] must be NULL. */
int **pidArrayPtr; /* Word at *pidArrayPtr gets filled in with
* address of array of pids for processes
* in pipeline (first pid is first process
* in pipeline). */
int *inPipePtr; /* If non-NULL, input to the pipeline comes
* from a pipe (unless overridden by
* redirection in the command). The file
* id with which to write to this pipe is
* stored at *inPipePtr. -1 means command
* specified its own input source. */
int *outPipePtr; /* If non-NULL, output to the pipeline goes
* to a pipe, unless overriden by redirection
* in the command. The file id with which to
* read frome this pipe is stored at
* *outPipePtr. -1 means command specified
* its own output sink. */
int *errFilePtr; /* If non-NULL, all stderr output from the
* pipeline will go to a temporary file
* created here, and a descriptor to read
* the file will be left at *errFilePtr.
* The file will be removed already, so
* closing this descriptor will be the end
* of the file. If this is NULL, then
* all stderr output goes to our stderr. */
{
int *pidPtr = NULL; /* Points to malloc-ed array holding all
* the pids of child processes. */
int numPids = 0; /* Actual number of processes that exist
* at *pidPtr right now. */
int cmdCount; /* Count of number of distinct commands
* found in argc/argv. */
char *input = NULL; /* Describes input for pipeline, depending
* on "inputFile". NULL means take input
* from stdin/pipe. */
int inputFile = 0; /* Non-zero means input is name of input
* file. Zero means input holds actual
* text to be input to command. */
char *output = NULL; /* Holds name of output file to pipe to,
* or NULL if output goes to stdout/pipe. */
int inputId = -1; /* Readable file id input to current command in
* pipeline (could be file or pipe). -1
* means use stdin. */
int outputId = -1; /* Writable file id for output from current
* command in pipeline (could be file or pipe).
* -1 means use stdout. */
int errorId = -1; /* Writable file id for all standard error
* output from all commands in pipeline. -1
* means use stderr. */
int lastOutputId = -1; /* Write file id for output from last command
* in pipeline (could be file or pipe).
* -1 means use stdout. */
int pipeIds[2]; /* File ids for pipe that's being created. */
int firstArg, lastArg; /* Indexes of first and last arguments in
* current command. */
int lastBar;
char *execName;
int i, j, pid;
if (inPipePtr != NULL) {
*inPipePtr = -1;
}
if (outPipePtr != NULL) {
*outPipePtr = -1;
}
if (errFilePtr != NULL) {
*errFilePtr = -1;
}
pipeIds[0] = pipeIds[1] = -1;
/*
* First, scan through all the arguments to figure out the structure
* of the pipeline. Count the number of distinct processes (it's the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -