/* * bltBgexec.c -- * * This module implements a background "exec" command for the * BLT toolkit. * * Copyright 1993-1998 Lucent Technologies, Inc. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and warranty * disclaimer appear in supporting documentation, and that the names * of Lucent Technologies any of their entities not be used in * advertising or publicity pertaining to distribution of the software * without specific, written prior permission. * * Lucent Technologies disclaims all warranties with regard to this * software, including all implied warranties of merchantability and * fitness. In no event shall Lucent Technologies be liable for any * special, indirect or consequential damages or any damages * whatsoever resulting from loss of use, data or profits, whether in * an action of contract, negligence or other tortuous action, arising * out of or in connection with the use or performance of this * software. * * The "bgexec" command was created by George Howlett. */ #include "bltInt.h" #ifndef NO_BGEXEC #include #include #ifdef HAVE_SYS_PARAM_H #include #endif #include #include "bltWait.h" #include "bltSwitch.h" #if (TCL_MAJOR_VERSION == 7) #define FILEHANDLER_USES_TCLFILES 1 #else typedef int Tcl_File; #endif static Tcl_CmdProc BgexecCmd; #ifdef WIN32 typedef struct { DWORD pid; HANDLE hProcess; } Process; #else typedef int Process; #endif #if (TCL_VERSION_NUMBER < _VERSION(8,1,0)) typedef void *Tcl_Encoding; /* Make up dummy type for encoding. */ #endif #define ENCODING_ASCII ((Tcl_Encoding)NULL) #define ENCODING_BINARY ((Tcl_Encoding)1) /* * As of Tcl 7.6, we're using our own version of the old * Tcl_CreatePipeline routine. I would have tried to use * Tcl_OpenCommandChannel but you can't get at the array of * process ids, unless of course you pry open the undocumented * structure PipeStatus as clientData. Nor could I figure out * how to set one side of the pipe to be non-blocking. The whole * channel API seems overly complex for what its supposed to * do. [And maybe that's why it keeps changing every release.] */ extern int Blt_CreatePipeline _ANSI_ARGS_((Tcl_Interp *interp, int argc, char **argv, Process **pidPtrPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr)); #ifdef WIN32 #define read(fd, buf, size) Blt_AsyncRead((fd),(buf),(size)) #define close(fd) CloseHandle((HANDLE)fd) #define Tcl_CreateFileHandler Blt_CreateFileHandler #define Tcl_DeleteFileHandler Blt_DeleteFileHandler #define kill KillProcess #define waitpid WaitProcess #endif #define READ_AGAIN (0) #define READ_EOF (-1) #define READ_ERROR (-2) /* The wait-related definitions are taken from tclUnix.h */ #define TRACE_FLAGS (TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY) #define BLOCK_SIZE 1024 /* Size of allocation blocks for buffer */ #define DEF_BUFFER_SIZE (BLOCK_SIZE * 8) #define MAX_READS 100 /* Maximum number of successful reads * before stopping to let Tcl catch up * on events */ #ifndef NSIG #define NSIG 32 /* Number of signals available */ #endif /*NSIG*/ #ifndef SIGINT #define SIGINT 2 #endif /* SIGINT */ #ifndef SIGQUIT #define SIGQUIT 3 #endif /* SIGQUIT */ #ifndef SIGKILL #define SIGKILL 9 #endif /* SIGKILL */ #ifndef SIGTERM #define SIGTERM 14 #endif /* SIGTERM */ typedef struct { int number; char *name; } SignalId; static SignalId signalIds[] = { #ifdef SIGABRT {SIGABRT, "SIGABRT"}, #endif #ifdef SIGALRM {SIGALRM, "SIGALRM"}, #endif #ifdef SIGBUS {SIGBUS, "SIGBUS"}, #endif #ifdef SIGCHLD {SIGCHLD, "SIGCHLD"}, #endif #if defined(SIGCLD) && (!defined(SIGCHLD) || (SIGCLD != SIGCHLD)) {SIGCLD, "SIGCLD"}, #endif #ifdef SIGCONT {SIGCONT, "SIGCONT"}, #endif #if defined(SIGEMT) && (!defined(SIGXCPU) || (SIGEMT != SIGXCPU)) {SIGEMT, "SIGEMT"}, #endif #ifdef SIGFPE {SIGFPE, "SIGFPE"}, #endif #ifdef SIGHUP {SIGHUP, "SIGHUP"}, #endif #ifdef SIGILL {SIGILL, "SIGILL"}, #endif #ifdef SIGINT {SIGINT, "SIGINT"}, #endif #ifdef SIGIO {SIGIO, "SIGIO"}, #endif #if defined(SIGIOT) && (!defined(SIGABRT) || (SIGIOT != SIGABRT)) {SIGIOT, "SIGIOT"}, #endif #ifdef SIGKILL {SIGKILL, "SIGKILL"}, #endif #if defined(SIGLOST) && (!defined(SIGIOT) || (SIGLOST != SIGIOT)) && (!defined(SIGURG) || (SIGLOST != SIGURG)) {SIGLOST, "SIGLOST"}, #endif #ifdef SIGPIPE {SIGPIPE, "SIGPIPE"}, #endif #if defined(SIGPOLL) && (!defined(SIGIO) || (SIGPOLL != SIGIO)) {SIGPOLL, "SIGPOLL"}, #endif #ifdef SIGPROF {SIGPROF, "SIGPROF"}, #endif #if defined(SIGPWR) && (!defined(SIGXFSZ) || (SIGPWR != SIGXFSZ)) {SIGPWR, "SIGPWR"}, #endif #ifdef SIGQUIT {SIGQUIT, "SIGQUIT"}, #endif #ifdef SIGSEGV {SIGSEGV, "SIGSEGV"}, #endif #ifdef SIGSTOP {SIGSTOP, "SIGSTOP"}, #endif #ifdef SIGSYS {SIGSYS, "SIGSYS"}, #endif #ifdef SIGTERM {SIGTERM, "SIGTERM"}, #endif #ifdef SIGTRAP {SIGTRAP, "SIGTRAP"}, #endif #ifdef SIGTSTP {SIGTSTP, "SIGTSTP"}, #endif #ifdef SIGTTIN {SIGTTIN, "SIGTTIN"}, #endif #ifdef SIGTTOU {SIGTTOU, "SIGTTOU"}, #endif #if defined(SIGURG) && (!defined(SIGIO) || (SIGURG != SIGIO)) {SIGURG, "SIGURG"}, #endif #if defined(SIGUSR1) && (!defined(SIGIO) || (SIGUSR1 != SIGIO)) {SIGUSR1, "SIGUSR1"}, #endif #if defined(SIGUSR2) && (!defined(SIGURG) || (SIGUSR2 != SIGURG)) {SIGUSR2, "SIGUSR2"}, #endif #ifdef SIGVTALRM {SIGVTALRM, "SIGVTALRM"}, #endif #ifdef SIGWINCH {SIGWINCH, "SIGWINCH"}, #endif #ifdef SIGXCPU {SIGXCPU, "SIGXCPU"}, #endif #ifdef SIGXFSZ {SIGXFSZ, "SIGXFSZ"}, #endif {-1, "unknown signal"}, }; /* * Sink buffer: * ____________________ * | | "size" current allocated length of buffer. * | | * |--------------------| "fill" fill point (# characters in buffer). * | Raw | * |--------------------| "mark" Marks end of cooked characters. * | | * | Cooked | * | | * | | * |--------------------| "lastMark" Mark end of processed characters. * | | * | | * | Processed | * | | * |____________________| 0 */ typedef struct { char *name; /* Name of the sink */ char *doneVar; /* Name of a Tcl variable (malloc'ed) * set to the collected data of the * last UNIX subprocess. */ char *updateVar; /* Name of a Tcl variable (malloc'ed) * updated as data is read from the * pipe. */ char **updateCmd; /* Start of a Tcl command executed * whenever data is read from the * pipe. */ #if (TCL_MAJOR_VERSION >= 8) Tcl_Obj **objv; /* */ int objc; /* */ #endif int flags; Tcl_File file; /* Used for backward compatability * with Tcl 7.5 */ Tcl_Encoding encoding; int fd; /* File descriptor of the pipe. */ int status; int echo; /* Indicates if the pipeline's stderr stream * should be echoed */ unsigned char *byteArr; /* Stores pipeline output (malloc-ed): * Initially points to static storage */ size_t size; /* Size of dynamically allocated buffer. */ size_t fill; /* # of bytes read into the buffer. Marks * the current fill point of the buffer. */ size_t mark; /* # of bytes translated (cooked). */ size_t lastMark; /* # of bytes as of the last read. * This indicates the start of the new * data in the buffer since the last * time the "update" variable was * set. */ unsigned char staticSpace[DEF_BUFFER_SIZE]; /* Static space */ } Sink; #define SINK_BUFFERED (1<<0) #define SINK_KEEP_NL (1<<1) #define SINK_NOTIFY (1<<2) typedef struct { char *statVar; /* Name of a Tcl variable set to the * exit status of the last * process. Setting this variable * triggers the termination of all * subprocesses (regardless whether * they have already completed) */ int signalNum; /* If non-zero, indicates the signal * to send subprocesses when cleaning * up.*/ int keepNewline; /* If non-zero, indicates to set Tcl * output variables with trailing * newlines intact */ int lineBuffered; /* If non-zero, indicates provide data * to update variable and update proc on * a line-by-line basis. */ int interval; /* Interval to poll for the exiting * processes */ char *outputEncodingName; /* Name of decoding scheme to use when * translating output data. */ char *errorEncodingName; /* Name of decoding scheme to use when * translating output data. */ /* Private */ Tcl_Interp *interp; /* Interpreter containing variables */ int nProcs; /* Number of processes in pipeline */ Process *procArr; /* Array of process tokens from pipeline. * The token for Unix are pid_t, while * for Win32 they're handles. */ int traced; /* Indicates that the status variable * is currently being traced. */ int detached; /* Indicates that the pipeline is * detached from standard I/O, running * in the background. */ Tcl_TimerToken timerToken; /* Token for timer handler which polls * for the exit status of each * sub-process. If zero, there's no * timer handler queued. */ int *exitCodePtr; /* Pointer to a memory location to * contain the last process' exit * code. */ int *donePtr; Sink sink1, sink2; } BackgroundInfo; static Blt_SwitchParseProc StringToSignal; static Blt_SwitchCustom killSignalSwitch = { StringToSignal, (Blt_SwitchFreeProc *)NULL, (ClientData)0, }; static Blt_SwitchSpec switchSpecs[] = { {BLT_SWITCH_STRING, "-decodeoutput", Blt_Offset(BackgroundInfo, outputEncodingName), 0}, {BLT_SWITCH_STRING, "-decodeerror", Blt_Offset(BackgroundInfo, errorEncodingName), 0}, {BLT_SWITCH_BOOLEAN, "-echo", Blt_Offset(BackgroundInfo, sink2.echo), 0}, {BLT_SWITCH_STRING, "-error", Blt_Offset(BackgroundInfo, sink2.doneVar), 0}, {BLT_SWITCH_STRING, "-update", Blt_Offset(BackgroundInfo, sink1.updateVar), 0}, {BLT_SWITCH_STRING, "-output", Blt_Offset(BackgroundInfo, sink1.doneVar), 0}, {BLT_SWITCH_STRING, "-lasterror", Blt_Offset(BackgroundInfo, sink2.updateVar), 0}, {BLT_SWITCH_STRING, "-lastoutput", Blt_Offset(BackgroundInfo, sink1.updateVar), 0}, {BLT_SWITCH_LIST, "-onerror", Blt_Offset(BackgroundInfo, sink2.updateCmd), 0}, {BLT_SWITCH_LIST, "-onoutput", Blt_Offset(BackgroundInfo, sink1.updateCmd), 0}, {BLT_SWITCH_BOOLEAN, "-keepnewline", Blt_Offset(BackgroundInfo, keepNewline), 0}, {BLT_SWITCH_BOOLEAN, "-check", Blt_Offset(BackgroundInfo, interval), 0}, {BLT_SWITCH_CUSTOM, "-killsignal", Blt_Offset(BackgroundInfo, signalNum), 0, &killSignalSwitch}, {BLT_SWITCH_BOOLEAN, "-linebuffered", Blt_Offset(BackgroundInfo, lineBuffered), 0}, {BLT_SWITCH_END, NULL, 0, 0} }; static char *VariableProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *part1, char *part2, int flags)); static void TimerProc _ANSI_ARGS_((ClientData clientData)); static void StdoutProc _ANSI_ARGS_((ClientData clientData, int mask)); static void StderrProc _ANSI_ARGS_((ClientData clientData, int mask)); /* *---------------------------------------------------------------------- * * GetSignal -- * * Convert a string represent a signal number into its integer * value. * * Results: * The return value is a standard Tcl result. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToSignal(clientData, interp, switchName, string, record, offset) ClientData clientData; /* Contains a pointer to the tabset containing * this image. */ Tcl_Interp *interp; /* Interpreter to send results back to */ char *switchName; /* Not used. */ char *string; /* String representation */ char *record; /* Structure record */ int offset; /* Offset to field in structure */ { int *signalPtr = (int *)(record + offset); int signalNum; if ((string == NULL) || (*string == '\0')) { *signalPtr = 0; return TCL_OK; } if (isdigit(UCHAR(string[0]))) { if (Tcl_GetInt(interp, string, &signalNum) != TCL_OK) { return TCL_ERROR; } } else { char *name; register SignalId *sigPtr; name = string; /* Clip off any "SIG" prefix from the signal name */ if ((name[0] == 'S') && (name[1] == 'I') && (name[2] == 'G')) { name += 3; } signalNum = -1; for (sigPtr = signalIds; sigPtr->number > 0; sigPtr++) { if (strcmp(sigPtr->name + 3, name) == 0) { signalNum = sigPtr->number; break; } } if (signalNum < 0) { Tcl_AppendResult(interp, "unknown signal \"", string, "\"", (char *)NULL); return TCL_ERROR; } } if ((signalNum < 0) || (signalNum > NSIG)) { /* Outside range of signals */ Tcl_AppendResult(interp, "signal number \"", string, "\" is out of range", (char *)NULL); return TCL_ERROR; } *signalPtr = signalNum; return TCL_OK; } /* *---------------------------------------------------------------------- * * GetSinkData -- * * Returns the data currently saved in the buffer * *---------------------------------------------------------------------- */ static void GetSinkData(sinkPtr, dataPtr, lengthPtr) Sink *sinkPtr; unsigned char **dataPtr; size_t *lengthPtr; { size_t length; sinkPtr->byteArr[sinkPtr->mark] = '\0'; length = sinkPtr->mark; if ((sinkPtr->mark > 0) && (sinkPtr->encoding != ENCODING_BINARY)) { unsigned char *last; last = sinkPtr->byteArr + (sinkPtr->mark - 1); if ((!(sinkPtr->flags & SINK_KEEP_NL)) && (*last == '\n')) { length--; } } *dataPtr = sinkPtr->byteArr; *lengthPtr = length; } /* *---------------------------------------------------------------------- * * NextBlock -- * * Returns the next block of data since the last time this * routine was called. * *---------------------------------------------------------------------- */ static unsigned char * NextBlock(sinkPtr, lengthPtr) Sink *sinkPtr; int *lengthPtr; { unsigned char *string; int length; string = sinkPtr->byteArr + sinkPtr->lastMark; length = sinkPtr->mark - sinkPtr->lastMark; sinkPtr->lastMark = sinkPtr->mark; if (length > 0) { if ((!(sinkPtr->flags & SINK_KEEP_NL)) && (string[length - 1] == '\n')) { length--; } *lengthPtr = length; return string; } return NULL; } /* *---------------------------------------------------------------------- * * NextLine -- * * Returns the next line of data. * *---------------------------------------------------------------------- */ static unsigned char * NextLine(sinkPtr, lengthPtr) Sink *sinkPtr; int *lengthPtr; { if (sinkPtr->mark > sinkPtr->lastMark) { unsigned char *string; int newBytes; register int i; string = sinkPtr->byteArr + sinkPtr->lastMark; newBytes = sinkPtr->mark - sinkPtr->lastMark; for (i = 0; i < newBytes; i++) { if (string[i] == '\n') { int length; length = i + 1; sinkPtr->lastMark += length; if (!(sinkPtr->flags & SINK_KEEP_NL)) { length--; /* Backup over the newline. */ } *lengthPtr = length; return string; } } /* Newline not found. On errors or EOF, also return a partial line. */ if (sinkPtr->status < 0) { *lengthPtr = newBytes; sinkPtr->lastMark = sinkPtr->mark; return string; } } return NULL; } /* *---------------------------------------------------------------------- * * ResetSink -- * * Removes the bytes already processed from the buffer, possibly * resetting it to empty. This used when we don't care about * keeping all the data collected from the channel (no -output * flag and the process is detached). * *---------------------------------------------------------------------- */ static void ResetSink(sinkPtr) Sink *sinkPtr; { if ((sinkPtr->flags & SINK_BUFFERED) && (sinkPtr->fill > sinkPtr->lastMark)) { register size_t i, j; /* There may be bytes remaining in the buffer, awaiting * another read before we see the next newline. So move the * bytes to the front of the array. */ for (i = 0, j = sinkPtr->lastMark; j < sinkPtr->fill; i++, j++) { sinkPtr->byteArr[i] = sinkPtr->byteArr[j]; } /* Move back the fill point and processed point. */ sinkPtr->fill -= sinkPtr->lastMark; sinkPtr->mark -= sinkPtr->lastMark; } else { sinkPtr->mark = sinkPtr->fill = 0; } sinkPtr->lastMark = 0; } /* *---------------------------------------------------------------------- * * InitSink -- * * Initializes the buffer's storage. * * Results: * None. * * Side effects: * Storage is cleared. * *---------------------------------------------------------------------- */ static void InitSink(bgPtr, sinkPtr, name, encoding) BackgroundInfo *bgPtr; Sink *sinkPtr; char *name; Tcl_Encoding encoding; { sinkPtr->name = name; sinkPtr->echo = FALSE; sinkPtr->fd = -1; sinkPtr->file = (Tcl_File)NULL; sinkPtr->byteArr = sinkPtr->staticSpace; sinkPtr->size = DEF_BUFFER_SIZE; sinkPtr->encoding = encoding; if (bgPtr->keepNewline) { sinkPtr->flags |= SINK_KEEP_NL; } if (bgPtr->lineBuffered) { sinkPtr->flags |= SINK_BUFFERED; } if ((sinkPtr->updateCmd != NULL) || (sinkPtr->updateVar != NULL) || (sinkPtr->echo)) { sinkPtr->flags |= SINK_NOTIFY; } #if (TCL_MAJOR_VERSION >= 8) if (sinkPtr->updateCmd != NULL) { Tcl_Obj **objArr; char **p; int count; register int i; count = 0; for (p = sinkPtr->updateCmd; *p != NULL; p++) { count++; } objArr = Blt_Malloc((count + 1) * sizeof(Tcl_Obj *)); for (i = 0; i < count; i++) { objArr[i] = Tcl_NewStringObj(sinkPtr->updateCmd[i], -1); Tcl_IncrRefCount(objArr[i]); } sinkPtr->objv = objArr; sinkPtr->objc = count + 1; } #endif ResetSink(sinkPtr); } /* *---------------------------------------------------------------------- * * FreeSinkBuffer -- * * Frees the buffer's storage, freeing any malloc'ed space. * * Results: * None. * *---------------------------------------------------------------------- */ static void FreeSinkBuffer(sinkPtr) Sink *sinkPtr; { if (sinkPtr->byteArr != sinkPtr->staticSpace) { Blt_Free(sinkPtr->byteArr); } sinkPtr->fd = -1; sinkPtr->file = (Tcl_File)NULL; #if (TCL_MAJOR_VERSION >= 8) if (sinkPtr->objv != NULL) { register int i; for (i = 0; i < sinkPtr->objc - 1; i++) { Tcl_DecrRefCount(sinkPtr->objv[i]); } Blt_Free(sinkPtr->objv); } #endif } /* *---------------------------------------------------------------------- * * ExtendSinkBuffer -- * * Doubles the size of the current buffer. * * Results: * None. * *---------------------------------------------------------------------- */ static int ExtendSinkBuffer(sinkPtr) Sink *sinkPtr; { unsigned char *arrayPtr; register unsigned char *srcPtr, *destPtr, *endPtr; /* * Allocate a new array, double the old size */ sinkPtr->size += sinkPtr->size; arrayPtr = Blt_Malloc(sizeof(unsigned char) * sinkPtr->size); if (arrayPtr == NULL) { return -1; } srcPtr = sinkPtr->byteArr; endPtr = sinkPtr->byteArr + sinkPtr->fill; destPtr = arrayPtr; while (srcPtr < endPtr) { *destPtr++ = *srcPtr++; } if (sinkPtr->byteArr != sinkPtr->staticSpace) { Blt_Free(sinkPtr->byteArr); } sinkPtr->byteArr = arrayPtr; return (sinkPtr->size - sinkPtr->fill); /* Return bytes left. */ } /* *---------------------------------------------------------------------- * * ReadBytes -- * * Reads and appends any available data from a given file descriptor * to the buffer. * * Results: * Returns TCL_OK when EOF is found, TCL_RETURN if reading * data would block, and TCL_ERROR if an error occurred. * *---------------------------------------------------------------------- */ static void ReadBytes(sinkPtr) Sink *sinkPtr; { int nBytes, bytesLeft; register int i; unsigned char *array; /* * ------------------------------------------------------------------ * * Worry about indefinite postponement. * * Typically we want to stay in the read loop as long as it takes * to collect all the data that's currently available. But if * it's coming in at a constant high rate, we need to arbitrarily * break out at some point. This allows for both setting the * update variable and the Tk program to handle idle events. * * ------------------------------------------------------------------ */ for (i = 0; i < MAX_READS; i++) { /* Allocate a larger buffer when the number of remaining bytes * is below the threshold BLOCK_SIZE. */ bytesLeft = sinkPtr->size - sinkPtr->fill; if (bytesLeft < BLOCK_SIZE) { bytesLeft = ExtendSinkBuffer(sinkPtr); if (bytesLeft < 0) { sinkPtr->status = READ_ERROR; return; } } array = sinkPtr->byteArr + sinkPtr->fill; /* * Read into a buffer but make sure we leave room for a * trailing NUL byte. */ nBytes = read(sinkPtr->fd, array, bytesLeft - 1); if (nBytes == 0) { /* EOF: break out of loop. */ sinkPtr->status = READ_EOF; return; } if (nBytes < 0) { #ifdef O_NONBLOCK #define BLOCKED EAGAIN #else #define BLOCKED EWOULDBLOCK #endif /*O_NONBLOCK*/ /* Either an error has occurred or no more data is * currently available to read. */ if (errno == BLOCKED) { sinkPtr->status = READ_AGAIN; return; } sinkPtr->byteArr[0] = '\0'; sinkPtr->status = READ_ERROR; return; } sinkPtr->fill += nBytes; sinkPtr->byteArr[sinkPtr->fill] = '\0'; } sinkPtr->status = nBytes; } #define IsOpenSink(sinkPtr) ((sinkPtr)->fd != -1) static void CloseSink(interp, sinkPtr) Tcl_Interp *interp; Sink *sinkPtr; { if (IsOpenSink(sinkPtr)) { close(sinkPtr->fd); #ifdef FILEHANDLER_USES_TCLFILES Tcl_DeleteFileHandler(sinkPtr->file); Tcl_FreeFile(sinkPtr->file); #else Tcl_DeleteFileHandler(sinkPtr->fd); #endif sinkPtr->file = (Tcl_File)NULL; sinkPtr->fd = -1; #if WINDEBUG PurifyPrintf("CloseSink: set done var %s\n", sinkPtr->name); #endif if (sinkPtr->doneVar != NULL) { unsigned char *data; size_t length; /* * If data is to be collected, set the "done" variable * with the contents of the buffer. */ GetSinkData(sinkPtr, &data, &length); #if (TCL_VERSION_NUMBER < _VERSION(8,1,0)) data[length] = '\0'; if (Tcl_SetVar(interp, sinkPtr->doneVar, data, TCL_GLOBAL_ONLY) == NULL) { Tcl_BackgroundError(interp); } #else if (Tcl_SetVar2Ex(interp, sinkPtr->doneVar, NULL, Tcl_NewByteArrayObj(data, length), (TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)) == NULL) { Tcl_BackgroundError(interp); } #endif } #if WINDEBUG PurifyPrintf("CloseSink %s: done\n", sinkPtr->name); #endif } } /* *---------------------------------------------------------------------- * * CookSink -- * * For Windows, translate CR/NL combinations to NL alone. * * Results: * None. * * Side Effects: * The size of the byte array may shrink and array contents * shifted as carriage returns are found and removed. * *---------------------------------------------------------------------- */ static void CookSink(interp, sinkPtr) Tcl_Interp *interp; Sink *sinkPtr; { unsigned char *srcPtr, *endPtr; #ifdef WIN32 size_t oldMark; oldMark = sinkPtr->mark; #endif if (sinkPtr->encoding == ENCODING_BINARY) { /* binary */ /* No translation needed. */ sinkPtr->mark = sinkPtr->fill; } else if (sinkPtr->encoding == ENCODING_ASCII) { /* ascii */ #if (TCL_VERSION_NUMBER < _VERSION(8,1,0)) /* Convert NUL bytes to question marks. */ srcPtr = sinkPtr->byteArr + sinkPtr->mark; endPtr = sinkPtr->byteArr + sinkPtr->fill; while (srcPtr < endPtr) { if (*srcPtr == '\0') { *srcPtr = '?'; } srcPtr++; } #endif /* < 8.1.0 */ /* One-to-one translation. mark == fill. */ sinkPtr->mark = sinkPtr->fill; #if (TCL_VERSION_NUMBER >= _VERSION(8,1,0)) } else { /* unicode. */ int nSrcCooked, nCooked; int result; size_t cookedSize, spaceLeft, needed; size_t nRaw, nLeftOver; unsigned char *destPtr; unsigned char *raw, *cooked; unsigned char leftover[100]; raw = sinkPtr->byteArr + sinkPtr->mark; nRaw = sinkPtr->fill - sinkPtr->mark; /* Ideally, the cooked buffer size should be smaller */ cookedSize = nRaw * TCL_UTF_MAX + 1; cooked = Blt_Malloc(cookedSize); result = Tcl_ExternalToUtf(interp, sinkPtr->encoding, (char *)raw, nRaw, 0, NULL, (char *)cooked, cookedSize, &nSrcCooked, &nCooked, NULL); nLeftOver = 0; if (result == TCL_CONVERT_MULTIBYTE) { /* * Last multibyte sequence wasn't completed. Save the * extra characters in a temporary buffer. */ nLeftOver = (nRaw - nSrcCooked); srcPtr = sinkPtr->byteArr + (sinkPtr->mark + nSrcCooked); endPtr = srcPtr + nLeftOver; destPtr = leftover; while (srcPtr < endPtr) { *destPtr++ = *srcPtr++; } } /* * Create a bigger */ needed = nLeftOver + nCooked; spaceLeft = sinkPtr->size - sinkPtr->mark; if (spaceLeft >= needed) { spaceLeft = ExtendSinkBuffer(sinkPtr); } assert(spaceLeft > needed); /* * Replace the characters from the mark with the translated * characters. */ srcPtr = cooked; endPtr = cooked + nCooked; destPtr = sinkPtr->byteArr + sinkPtr->mark; while (srcPtr < endPtr) { *destPtr++ = *srcPtr++; } /* Add the number of newly translated characters to the mark */ sinkPtr->mark += nCooked; srcPtr = leftover; endPtr = leftover + nLeftOver; while (srcPtr < endPtr) { *destPtr++ = *srcPtr++; } sinkPtr->fill = sinkPtr->mark + nLeftOver; #endif /* >= 8.1.0 */ } #ifdef WIN32 /* * Translate CRLF character sequences to LF characters. We have to * do this after converting the string to UTF from UNICODE. */ if (sinkPtr->encoding != ENCODING_BINARY) { int count; unsigned char *destPtr; destPtr = srcPtr = sinkPtr->byteArr + oldMark; endPtr = sinkPtr->byteArr + sinkPtr->fill; *endPtr = '\0'; count = 0; for (endPtr--; srcPtr < endPtr; srcPtr++) { if ((*srcPtr == '\r') && (*(srcPtr + 1) == '\n')) { count++; continue; /* Skip the CR in CR/LF sequences. */ } if (srcPtr != destPtr) { *destPtr = *srcPtr; /* Collapse the string, overwriting * the \r's encountered. */ } destPtr++; } sinkPtr->mark -= count; sinkPtr->fill -= count; *destPtr = *srcPtr; /* Copy the last byte */ if (*destPtr == '\r') { sinkPtr->mark--; } } #endif /* WIN32 */ } #ifdef WIN32 /* *---------------------------------------------------------------------- * * WaitProcess -- * * Emulates the waitpid system call under the Win32 API. * * Results: * Returns 0 if the process is still alive, -1 on an error, or * the pid on a clean close. * * Side effects: * Unless WNOHANG is set and the wait times out, the process * information record will be deleted and the process handle * will be closed. * *---------------------------------------------------------------------- */ #define WINDEBUG 0 static int WaitProcess( Process child, int *statusPtr, int flags) { int result; DWORD status, exitCode; int timeout; #if WINDEBUG PurifyPrintf("WAITPID(%x)\n", child.hProcess); #endif *statusPtr = 0; if (child.hProcess == INVALID_HANDLE_VALUE) { errno = EINVAL; return -1; } #if WINDEBUG PurifyPrintf("WAITPID: waiting for 0x%x\n", child.hProcess); #endif timeout = (flags & WNOHANG) ? 0 : INFINITE; status = WaitForSingleObject(child.hProcess, timeout); #if WINDEBUG PurifyPrintf("WAITPID: wait status is %d\n", status); #endif switch (status) { case WAIT_FAILED: errno = ECHILD; *statusPtr = ECHILD; result = -1; break; case WAIT_TIMEOUT: if (timeout == 0) { return 0; /* Try again */ } result = 0; break; default: case WAIT_ABANDONED: case WAIT_OBJECT_0: GetExitCodeProcess(child.hProcess, &exitCode); *statusPtr = ((exitCode << 8) & 0xff00); #if WINDEBUG PurifyPrintf("WAITPID: exit code of %d is %d (%x)\n", child.hProcess, *statusPtr, exitCode); #endif result = child.pid; assert(result != -1); break; } CloseHandle(child.hProcess); return result; } static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) { DWORD pid = 0; Process *procPtr = (Process *)lParam; GetWindowThreadProcessId(hWnd, &pid); if (pid == procPtr->pid) { PostMessage(hWnd, WM_CLOSE, 0, 0); } return TRUE; } /* *---------------------------------------------------------------------- * * KillProcess -- * * Emulates the UNIX kill system call under Win32 API. * * Results: * Returns 0 if the process is killed, -1 on an error. * * Side effects: * Process is terminated. * *---------------------------------------------------------------------- */ static int KillProcess(Process proc, int signal) { DWORD status; if ((proc.hProcess == NULL) || (proc.hProcess == INVALID_HANDLE_VALUE)) { errno = EINVAL; return -1; } EnumWindows(EnumWindowsProc, (LPARAM)&proc); /* * Wait on the handle. If it signals, great. If it times out, * then call TerminateProcess on it. * * On Windows 95/98 this also has the added benefit of stopping * KERNEL32.dll from dumping. The 2 second number is arbitrary. * (1 second seems to fail intermittently). */ status = WaitForSingleObject(proc.hProcess, 2000); if (status == WAIT_OBJECT_0) { return 0; } if (!TerminateProcess(proc.hProcess, 1)) { #if WINDEBUG PurifyPrintf("can't terminate process (handle=%d): %s\n", proc.hProcess, Blt_LastError()); #endif /* WINDEBUG */ return -1; } return 0; } #endif /* WIN32 */ #if (TCL_VERSION_NUMBER < _VERSION(8,1,0)) static void NotifyOnUpdate(interp, sinkPtr, data, nBytes) Tcl_Interp *interp; Sink *sinkPtr; unsigned char *data; int nBytes; { char save; #if WINDEBUG_0 PurifyPrintf("read %s\n", data); #endif if (data[0] == '\0') { return; } save = data[nBytes]; data[nBytes] = '\0'; if (sinkPtr->echo) { Tcl_Channel channel; channel = Tcl_GetStdChannel(TCL_STDERR); if (channel == NULL) { Tcl_AppendResult(interp, "can't get stderr channel", (char *)NULL); Tcl_BackgroundError(interp); sinkPtr->echo = FALSE; } else { Tcl_Write(channel, data, nBytes); if (save == '\n') { Tcl_Write(channel, "\n", 1); } Tcl_Flush(channel); } } if (sinkPtr->updateCmd != NULL) { Tcl_DString dString; int result; register char **p; Tcl_DStringInit(&dString); for (p = sinkPtr->updateCmd; *p != NULL; p++) { Tcl_DStringAppendElement(&dString, *p); } Tcl_DStringAppendElement(&dString, data); result = Tcl_GlobalEval(interp, Tcl_DStringValue(&dString)); Tcl_DStringFree(&dString); if (result != TCL_OK) { Tcl_BackgroundError(interp); } } if (sinkPtr->updateVar != NULL) { int flags; char *result; flags = (TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG); result = Tcl_SetVar(interp, sinkPtr->updateVar, data, flags); if (result == NULL) { Tcl_BackgroundError(interp); } } data[nBytes] = save; } #else static void NotifyOnUpdate(interp, sinkPtr, data, nBytes) Tcl_Interp *interp; Sink *sinkPtr; unsigned char *data; int nBytes; { Tcl_Obj *objPtr; #if WINDEBUG_0 PurifyPrintf("read %s\n", data); #endif if ((nBytes == 0) || (data[0] == '\0')) { return; } if (sinkPtr->echo) { Tcl_Channel channel; channel = Tcl_GetStdChannel(TCL_STDERR); if (channel == NULL) { Tcl_AppendResult(interp, "can't get stderr channel", (char *)NULL); Tcl_BackgroundError(interp); sinkPtr->echo = FALSE; } else { if (data[nBytes] == '\n') { objPtr = Tcl_NewByteArrayObj(data, nBytes + 1); } else { objPtr = Tcl_NewByteArrayObj(data, nBytes); } Tcl_WriteObj(channel, objPtr); Tcl_Flush(channel); } } objPtr = Tcl_NewByteArrayObj(data, nBytes); Tcl_IncrRefCount(objPtr); if (sinkPtr->objv != NULL) { int result; sinkPtr->objv[sinkPtr->objc - 1] = objPtr; result = Tcl_EvalObjv(interp, sinkPtr->objc, sinkPtr->objv, 0); if (result != TCL_OK) { Tcl_BackgroundError(interp); } } if (sinkPtr->updateVar != NULL) { Tcl_Obj *result; result = Tcl_SetVar2Ex(interp, sinkPtr->updateVar, NULL, objPtr, (TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)); if (result == NULL) { Tcl_BackgroundError(interp); } } Tcl_DecrRefCount(objPtr); } #endif /* < 8.1.0 */ static int CollectData(bgPtr, sinkPtr) BackgroundInfo *bgPtr; Sink *sinkPtr; { if ((bgPtr->detached) && (sinkPtr->doneVar == NULL)) { ResetSink(sinkPtr); } ReadBytes(sinkPtr); CookSink(bgPtr->interp, sinkPtr); if ((sinkPtr->mark > sinkPtr->lastMark) && (sinkPtr->flags & SINK_NOTIFY)) { unsigned char *data; int length; if (sinkPtr->flags & SINK_BUFFERED) { /* For line-by-line updates, call NotifyOnUpdate for each * new complete line. */ while ((data = NextLine(sinkPtr, &length)) != NULL) { NotifyOnUpdate(bgPtr->interp, sinkPtr, data, length); } } else { data = NextBlock(sinkPtr, &length); NotifyOnUpdate(bgPtr->interp, sinkPtr, data, length); } } if (sinkPtr->status >= 0) { return TCL_OK; } if (sinkPtr->status == READ_ERROR) { Tcl_AppendResult(bgPtr->interp, "can't read data from ", sinkPtr->name, ": ", Tcl_PosixError(bgPtr->interp), (char *)NULL); Tcl_BackgroundError(bgPtr->interp); return TCL_ERROR; } #if WINDEBUG PurifyPrintf("CollectData %s: done\n", sinkPtr->name); #endif return TCL_RETURN; } /* *---------------------------------------------------------------------- * * CreateSinkHandler -- * * Creates a file handler for the given sink. The file * descriptor is also set for non-blocking I/O. * * Results: * None. * * Side effects: * The memory allocated to the BackgroundInfo structure released. * *---------------------------------------------------------------------- */ static int CreateSinkHandler(bgPtr, sinkPtr, proc) BackgroundInfo *bgPtr; Sink *sinkPtr; Tcl_FileProc *proc; { #ifndef WIN32 int flags; flags = fcntl(sinkPtr->fd, F_GETFL); #ifdef O_NONBLOCK flags |= O_NONBLOCK; #else flags |= O_NDELAY; #endif if (fcntl(sinkPtr->fd, F_SETFL, flags) < 0) { Tcl_AppendResult(bgPtr->interp, "can't set file descriptor ", Blt_Itoa(sinkPtr->fd), " to non-blocking:", Tcl_PosixError(bgPtr->interp), (char *)NULL); return TCL_ERROR; } #endif /* WIN32 */ #ifdef FILEHANDLER_USES_TCLFILES sinkPtr->file = Tcl_GetFile((ClientData)sinkPtr->fd, TCL_UNIX_FD); Tcl_CreateFileHandler(sinkPtr->file, TCL_READABLE, proc, bgPtr); #else Tcl_CreateFileHandler(sinkPtr->fd, TCL_READABLE, proc, bgPtr); #endif /* FILEHANDLER_USES_TCLFILES */ return TCL_OK; } static void DisableTriggers(bgPtr) BackgroundInfo *bgPtr; /* Background info record. */ { if (bgPtr->traced) { Tcl_UntraceVar(bgPtr->interp, bgPtr->statVar, TRACE_FLAGS, VariableProc, bgPtr); bgPtr->traced = FALSE; } if (IsOpenSink(&bgPtr->sink1)) { CloseSink(bgPtr->interp, &bgPtr->sink1); } if (IsOpenSink(&bgPtr->sink2)) { CloseSink(bgPtr->interp, &bgPtr->sink2); } if (bgPtr->timerToken != (Tcl_TimerToken) 0) { Tcl_DeleteTimerHandler(bgPtr->timerToken); bgPtr->timerToken = 0; } if (bgPtr->donePtr != NULL) { *bgPtr->donePtr = TRUE; } } /* *---------------------------------------------------------------------- * * FreeBackgroundInfo -- * * Releases the memory allocated for the backgrounded process. * *---------------------------------------------------------------------- */ static void FreeBackgroundInfo(bgPtr) BackgroundInfo *bgPtr; { Blt_FreeSwitches(switchSpecs, (char *)bgPtr, 0); if (bgPtr->statVar != NULL) { Blt_Free(bgPtr->statVar); } if (bgPtr->procArr != NULL) { Blt_Free(bgPtr->procArr); } Blt_Free(bgPtr); } /* *---------------------------------------------------------------------- * * DestroyBackgroundInfo -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure (BackgroundInfo) at a safe * time (when no one is using it anymore). * * Results: * None.b * * Side effects: * The memory allocated to the BackgroundInfo structure released. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void DestroyBackgroundInfo(bgPtr) BackgroundInfo *bgPtr; /* Background info record. */ { DisableTriggers(bgPtr); FreeSinkBuffer(&bgPtr->sink2); FreeSinkBuffer(&bgPtr->sink1); if (bgPtr->procArr != NULL) { register int i; for (i = 0; i < bgPtr->nProcs; i++) { if (bgPtr->signalNum > 0) { kill(bgPtr->procArr[i], bgPtr->signalNum); } #ifdef WIN32 Tcl_DetachPids(1, (Tcl_Pid *)&bgPtr->procArr[i].pid); #else #if (TCL_MAJOR_VERSION == 7) Tcl_DetachPids(1, &bgPtr->procArr[i]); #else Tcl_DetachPids(1, (Tcl_Pid *)bgPtr->procArr[i]); #endif /* TCL_MAJOR_VERSION == 7 */ #endif /* WIN32 */ } } FreeBackgroundInfo(bgPtr); Tcl_ReapDetachedProcs(); } /* * ---------------------------------------------------------------------- * * VariableProc -- * * Kills all currently running subprocesses (given the specified * signal). This procedure is called when the user sets the status * variable associated with this group of child subprocesses. * * Results: * Always returns NULL. Only called from a variable trace. * * Side effects: * The subprocesses are signaled for termination using the * specified kill signal. Additionally, any resources allocated * to track the subprocesses is released. * * ---------------------------------------------------------------------- */ /* ARGSUSED */ static char * VariableProc( ClientData clientData, /* File output information. */ Tcl_Interp *interp, char *part1, char *part2, /* Not Used. */ int flags) { if (flags & TRACE_FLAGS) { BackgroundInfo *bgPtr = clientData; /* Kill all child processes that remain alive. */ if ((bgPtr->procArr != NULL) && (bgPtr->signalNum > 0)) { register int i; for (i = 0; i < bgPtr->nProcs; i++) { kill(bgPtr->procArr[i], bgPtr->signalNum); } } } return NULL; } /* *---------------------------------------------------------------------- * * TimerProc -- * * This is a timer handler procedure which gets called * periodically to reap any of the sub-processes if they have * terminated. After the last process has terminated, the * contents of standard output are stored * in the output variable, which triggers the cleanup proc (using * a variable trace). The status the last process to exit is * written to the status variable. * * Results: * None. Called from the Tcl event loop. * * Side effects: * Many. The contents of procArr is shifted, leaving only those * sub-processes which have not yet terminated. If there are * still subprocesses left, this procedure is placed in the timer * queue again. Otherwise the output and possibly the status * variables are updated. The former triggers the cleanup * routine which will destroy the information and resources * associated with these background processes. * *---------------------------------------------------------------------- */ static void TimerProc(clientData) ClientData clientData; { BackgroundInfo *bgPtr = clientData; register int i; unsigned int lastPid; int pid; enum PROCESS_STATUS { PROCESS_EXITED, PROCESS_STOPPED, PROCESS_KILLED, PROCESS_UNKNOWN } pcode; WAIT_STATUS_TYPE waitStatus, lastStatus; int nLeft; /* Number of processes still not reaped */ char string[200]; Tcl_DString dString; int code; CONST char *result; lastPid = (unsigned int)-1; *((int *)&waitStatus) = 0; *((int *)&lastStatus) = 0; nLeft = 0; for (i = 0; i < bgPtr->nProcs; i++) { #ifdef WIN32 pid = WaitProcess(bgPtr->procArr[i], (int *)&waitStatus, WNOHANG); #else pid = waitpid(bgPtr->procArr[i], (int *)&waitStatus, WNOHANG); #endif if (pid == 0) { /* Process has not terminated yet */ if (nLeft < i) { bgPtr->procArr[nLeft] = bgPtr->procArr[i]; } nLeft++; /* Count the number of processes left */ } else if (pid != -1) { /* * Save the status information associated with the subprocess. * We'll use it only if this is the last subprocess to be reaped. */ lastStatus = waitStatus; lastPid = (unsigned int)pid; } } bgPtr->nProcs = nLeft; if ((nLeft > 0) || (IsOpenSink(&bgPtr->sink1)) || (IsOpenSink(&bgPtr->sink2))) { /* Keep polling for the status of the children that are left */ bgPtr->timerToken = Tcl_CreateTimerHandler(bgPtr->interval, TimerProc, bgPtr); #if WINDEBUG PurifyPrintf("schedule TimerProc(nProcs=%d)\n", nLeft); #endif return; } /* * All child processes have completed. Set the status variable * with the status of the last process reaped. The status is a * list of an error token, the exit status, and a message. */ code = WEXITSTATUS(lastStatus); Tcl_DStringInit(&dString); if (WIFEXITED(lastStatus)) { Tcl_DStringAppendElement(&dString, "EXITED"); pcode = PROCESS_EXITED; } else if (WIFSIGNALED(lastStatus)) { Tcl_DStringAppendElement(&dString, "KILLED"); pcode = PROCESS_KILLED; code = -1; } else if (WIFSTOPPED(lastStatus)) { Tcl_DStringAppendElement(&dString, "STOPPED"); pcode = PROCESS_STOPPED; code = -1; } else { Tcl_DStringAppendElement(&dString, "UNKNOWN"); pcode = PROCESS_UNKNOWN; } #ifdef WIN32 sprintf(string, "%u", lastPid); Tcl_DStringAppendElement(&dString, string); #else Tcl_DStringAppendElement(&dString, Blt_Itoa(lastPid)); #endif Tcl_DStringAppendElement(&dString, Blt_Itoa(code)); switch(pcode) { case PROCESS_EXITED: Tcl_DStringAppendElement(&dString, "child completed normally"); break; case PROCESS_KILLED: Tcl_DStringAppendElement(&dString, Tcl_SignalMsg((int)(WTERMSIG(lastStatus)))); break; case PROCESS_STOPPED: Tcl_DStringAppendElement(&dString, Tcl_SignalMsg((int)(WSTOPSIG(lastStatus)))); break; case PROCESS_UNKNOWN: sprintf(string, "child completed with unknown status 0x%x", *((int *)&lastStatus)); Tcl_DStringAppendElement(&dString, string); break; } if (bgPtr->exitCodePtr != NULL) { *bgPtr->exitCodePtr = code; } DisableTriggers(bgPtr); result = Tcl_SetVar(bgPtr->interp, bgPtr->statVar, Tcl_DStringValue(&dString), TCL_GLOBAL_ONLY); Tcl_DStringFree(&dString); if (result == NULL) { Tcl_BackgroundError(bgPtr->interp); } if (bgPtr->detached) { DestroyBackgroundInfo(bgPtr); } } /* *---------------------------------------------------------------------- * * Stdoutproc -- * * This procedure is called when output from the detached pipeline * is available. The output is read and saved in a buffer in the * BackgroundInfo structure. * * Results: * None. * * Side effects: * Data is stored in the buffer. This character array may * be increased as more space is required to contain the output * of the pipeline. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void StdoutProc(clientData, mask) ClientData clientData; /* File output information. */ int mask; /* Not used. */ { BackgroundInfo *bgPtr = clientData; if (CollectData(bgPtr, &bgPtr->sink1) == TCL_OK) { return; } /* * Either EOF or an error has occurred. In either case, close the * sink. Note that closing the sink will also remove the file * handler, so this routine will not be called again. */ CloseSink(bgPtr->interp, &bgPtr->sink1); /* * If both sinks (stdout and stderr) are closed, this doesn't * necessarily mean that the process has terminated. Set up a * timer handler to periodically poll for the exit status of each * process. Initially check at the next idle interval. */ if (!IsOpenSink(&bgPtr->sink2)) { bgPtr->timerToken = Tcl_CreateTimerHandler(0, TimerProc, clientData); } } /* *---------------------------------------------------------------------- * * StderrProc -- * * This procedure is called when error from the detached pipeline * is available. The error is read and saved in a buffer in the * BackgroundInfo structure. * * Results: * None. * * Side effects: * Data is stored in the buffer. This character array may * be increased as more space is required to contain the stderr * of the pipeline. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void StderrProc(clientData, mask) ClientData clientData; /* File output information. */ int mask; /* Not used. */ { BackgroundInfo *bgPtr = clientData; if (CollectData(bgPtr, &bgPtr->sink2) == TCL_OK) { return; } /* * Either EOF or an error has occurred. In either case, close the * sink. Note that closing the sink will also remove the file * handler, so this routine will not be called again. */ CloseSink(bgPtr->interp, &bgPtr->sink2); /* * If both sinks (stdout and stderr) are closed, this doesn't * necessarily mean that the process has terminated. Set up a * timer handler to periodically poll for the exit status of each * process. Initially check at the next idle interval. */ if (!IsOpenSink(&bgPtr->sink1)) { bgPtr->timerToken = Tcl_CreateTimerHandler(0, TimerProc, clientData); } } /* *---------------------------------------------------------------------- * * BgexecCmd -- * * This procedure is invoked to process the "bgexec" Tcl command. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static int BgexecCmd(clientData, interp, argc, argv) ClientData clientData; /* Thread-specific data. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { int *outFdPtr, *errFdPtr; int nProcs; Process *pidPtr; char *lastArg; BackgroundInfo *bgPtr; int i; int detached; Tcl_Encoding encoding; if (argc < 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " varName ?options? command ?arg...?\"", (char *)NULL); return TCL_ERROR; } /* Check if the command line is to be run detached (the last * argument is "&") */ lastArg = argv[argc - 1]; detached = ((lastArg[0] == '&') && (lastArg[1] == '\0')); if (detached) { argc--; argv[argc] = NULL; /* Remove the '&' argument */ } bgPtr = Blt_Calloc(1, sizeof(BackgroundInfo)); assert(bgPtr); /* Initialize the background information record */ bgPtr->interp = interp; bgPtr->signalNum = SIGKILL; bgPtr->nProcs = -1; bgPtr->interval = 1000; bgPtr->detached = detached; bgPtr->keepNewline = FALSE; bgPtr->statVar = Blt_Strdup(argv[1]); /* Try to clean up any detached processes */ Tcl_ReapDetachedProcs(); i = Blt_ProcessSwitches(interp, switchSpecs, argc - 2, argv + 2, (char *)bgPtr, BLT_SWITCH_ARGV_PARTIAL); if (i < 0) { FreeBackgroundInfo(bgPtr); return TCL_ERROR; } i += 2; /* Must be at least one argument left as the command to execute. */ if (argc <= i) { Tcl_AppendResult(interp, "missing command to execute: should be \"", argv[0], " varName ?options? command ?arg...?\"", (char *)NULL); FreeBackgroundInfo(bgPtr); return TCL_ERROR; } /* Put a trace on the exit status variable. The will also allow * the user to prematurely terminate the pipeline by simply * setting it. */ Tcl_TraceVar(interp, bgPtr->statVar, TRACE_FLAGS, VariableProc, bgPtr); bgPtr->traced = TRUE; encoding = ENCODING_ASCII; if (bgPtr->outputEncodingName != NULL) { if (strcmp(bgPtr->outputEncodingName, "binary") == 0) { encoding = ENCODING_BINARY; } else { #if (TCL_VERSION_NUMBER >= _VERSION(8,1,0)) encoding = Tcl_GetEncoding(interp, bgPtr->outputEncodingName); if (encoding == NULL) { goto error; } #endif } } InitSink(bgPtr, &bgPtr->sink1, "stdout", encoding); if (bgPtr->errorEncodingName != NULL) { if (strcmp(bgPtr->errorEncodingName, "binary") == 0) { encoding = ENCODING_BINARY; } else { #if (TCL_VERSION_NUMBER >= _VERSION(8,1,0)) encoding = Tcl_GetEncoding(interp, bgPtr->errorEncodingName); if (encoding == NULL) { goto error; } #endif } } InitSink(bgPtr, &bgPtr->sink2, "stderr", encoding); outFdPtr = errFdPtr = (int *)NULL; #ifdef WIN32 if ((!bgPtr->detached) || (bgPtr->sink1.doneVar != NULL) || (bgPtr->sink1.updateVar != NULL) || (bgPtr->sink1.updateCmd != NULL)) { outFdPtr = &bgPtr->sink1.fd; } #else outFdPtr = &bgPtr->sink1.fd; #endif if ((bgPtr->sink2.doneVar != NULL) || (bgPtr->sink2.updateVar != NULL) || (bgPtr->sink2.updateCmd != NULL) || (bgPtr->sink2.echo)) { errFdPtr = &bgPtr->sink2.fd; } nProcs = Blt_CreatePipeline(interp, argc - i, argv + i, &pidPtr, (int *)NULL, outFdPtr, errFdPtr); if (nProcs < 0) { goto error; } bgPtr->procArr = pidPtr; bgPtr->nProcs = nProcs; if (bgPtr->sink1.fd == -1) { /* If output has been redirected, start polling immediately * for the exit status of each process. Normally, this is * done only after stdout has been closed by the last process, * but here stdout has been redirected. The default polling * interval is every 1 second. */ bgPtr->timerToken = Tcl_CreateTimerHandler(bgPtr->interval, TimerProc, bgPtr); } else if (CreateSinkHandler(bgPtr, &bgPtr->sink1, StdoutProc) != TCL_OK) { goto error; } if ((bgPtr->sink2.fd != -1) && (CreateSinkHandler(bgPtr, &bgPtr->sink2, StderrProc) != TCL_OK)) { goto error; } if (bgPtr->detached) { char string[200]; /* If detached, return a list of the child process ids instead * of the output of the pipeline. */ for (i = 0; i < nProcs; i++) { #ifdef WIN32 sprintf(string, "%u", (unsigned int)bgPtr->procArr[i].pid); #else sprintf(string, "%d", bgPtr->procArr[i]); #endif Tcl_AppendElement(interp, string); } } else { int exitCode; int done; bgPtr->exitCodePtr = &exitCode; bgPtr->donePtr = &done; exitCode = done = 0; while (!done) { Tcl_DoOneEvent(0); } DisableTriggers(bgPtr); if ((exitCode == 0) && (bgPtr->sink1.doneVar == NULL)) { unsigned char *data; size_t length; /* Return the output of the pipeline. */ GetSinkData(&bgPtr->sink1, &data, &length); #if (TCL_VERSION_NUMBER < _VERSION(8,1,0)) data[length] = '\0'; Tcl_SetResult(interp, data, TCL_VOLATILE); #else Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(data, length)); #endif } /* Clean up resources used. */ DestroyBackgroundInfo(bgPtr); if (exitCode != 0) { Tcl_AppendResult(interp, "child process exited abnormally", (char *)NULL); return TCL_ERROR; } } return TCL_OK; error: DisableTriggers(bgPtr); DestroyBackgroundInfo(bgPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Blt_BgexecInit -- * * This procedure is invoked to initialize the "bgexec" Tcl * command. See the user documentation for details on what it * does. * * Results: * None. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Blt_BgexecInit(interp) Tcl_Interp *interp; { static Blt_CmdSpec cmdSpec = {"bgexec", BgexecCmd, }; if (Blt_InitCmd(interp, "blt", &cmdSpec) == NULL) { return TCL_ERROR; } return TCL_OK; } #endif /* NO_BGEXEC */