Critcl wrapper of standalone unix bgexec stripped out from BLT and enhanced with -input option as suggested by KJN on bgexec.
Note that all windows code and all pre-Tcl 8.1 code has been stripped from the original ::blt::bgexec.
[ Daniel Steffen 24/01/05 ]
#!/bin/sh # ---------------------------------------------------------------------------- # # Critcl wrapper of standalone unix bgexec stripped out from BLT and enhanced # with -input option (C code modifications marked by //DAS). # Note that all windows code and all pre-Tcl 8.1 code has been stripped. # # Process this file with 'critcl -pkg' to build a loadable package (or simply # source this file if [package require critcl] and a compiler are available at # deployment). # # In addition to the [bgexec] command, this file defines (when sourced) the # proc [bgexec_write_manpage], run it to output the bgexec.n manpage. # # RCS: @(#) $Id: 13400,v 1.4 2005-01-24 07:00:49 jcw Exp $ # # See below for original BLT copyright and license. # # Modifications Copyright (c) 2005, Daniel A. Steffen <[email protected]> # # Modifications under BSD License: # c.f. <http://www.opensource.org/licenses/bsd-license> # # ---------------------------------------------------------------------------- # \ exec tclsh "$0" "$@" package require critcl 0.3 if {![::critcl::compiling]} {error "No compiler found"} #------------------------------------------------------------------------------- package provide bgexec 3.0 ::critcl::ccode { /**************************************************************************/ /* Configuration: adjust as necessary, the following works on Mac OS X 10.3 */ /* Define to 1 if you have the <ctype.h> header file. */ #define HAVE_CTYPE_H 1 /* Define to 1 if you have the <errno.h> header file. */ #define HAVE_ERRNO_H 1 /* Define to 1 if you have the <limits.h> header file. */ #define HAVE_LIMITS_H 1 /* Define to 1 if you have the <memory.h> header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the <malloc.h> header file. */ /* #undef HAVE_MALLOC_H */ /* Define to 1 if you have the <stdlib.h> header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the <sys/param.h> header file. */ #define HAVE_SYS_PARAM_H 1 /* Define to 1 if you have the <sys/wait.h> header file. */ #define HAVE_SYS_WAIT_H 1 /* Define if 'wait' is a union. */ #define HAVE_UNION_WAIT 1 /* Define to 1 if you have the <unistd.h> header file. */ #define HAVE_UNISTD_H 1 /* Define to 1 if you have the <waitflags.h> header file. */ /* #undef HAVE_WAITFLAGS_H */ #define USE_VFORK #define NDEBUG /**************************************************************************/ /* * bltBgexec.c -- * * This module implements a background "exec" command for the BLT * toolkit. * * Copyright 1993-1998 George A Howlett. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef CONST84 #define CONST84 #endif #include <stdio.h> #include <assert.h> #include <fcntl.h> #include <signal.h> #include <sys/types.h> #include <stdarg.h> #ifdef HAVE_STDLIB_H #include <stdlib.h> #endif /* HAVE_STDLIB_H */ #ifdef HAVE_ERRNO_H #include <errno.h> #endif /* HAVE_ERRNO_H */ #ifdef HAVE_CTYPE_H #include <ctype.h> #endif /* HAVE_CTYPE_H */ #ifdef HAVE_MEMORY_H #include <memory.h> #endif /* HAVE_MEMORY_H */ #ifdef HAVE_UNISTD_H #include <unistd.h> #endif /* HAVE_UNISTD_H */ #ifdef HAVE_LIMITS_H #include <limits.h> #endif /* HAVE_LIMITS_H */ #ifdef HAVE_SYS_PARAM_H #include <sys/param.h> #endif /* HAVE_SYS_PARAM_H */ #ifdef HAVE_WAITFLAGS_H #include <waitflags.h> #endif /* HAVE_WAITFLAGS_H */ #ifdef HAVE_SYS_WAIT_H #include <sys/wait.h> #endif /* HAVE_SYS_WAIT_H */ #ifdef HAVE_MALLOC_H #include <malloc.h> #endif /* HAVE_MALLOC_H */ #ifdef USE_VFORK #define fork vfork #endif /* * Since the Tcl/Tk distribution doesn't perform any asserts, dynamic * loading can fail to find the __assert function. As a workaround, * we'll include our own. */ #undef assert #ifdef NDEBUG #define assert(EX) ((void)0) #else static void Blt_Assert (char *expr, char *file, int line); #ifdef __STDC__ #define assert(EX) (void)((EX) || (Blt_Assert(#EX, __FILE__, __LINE__), 0)) #else #define assert(EX) (void)((EX) || (Blt_Assert("EX", __FILE__, __LINE__), 0)) #endif /* __STDC__ */ #endif /* NDEBUG */ #define TRUE 1 #define FALSE 0 #define COUNT_NONNEGATIVE 0 #define COUNT_POSITIVE 1 #define COUNT_ANY 2 static char *Blt_Itoa (int value); #define Blt_Malloc(size) ((void*)ckalloc(size)) #define Blt_Free(ptr) (ckfree((char*)ptr)) #define Blt_Realloc(ptr, size) ((void*)ckrealloc((char*)ptr, size)) static char *Blt_Strdup (CONST char *ptr); static void *Blt_Calloc (unsigned int nElem, size_t size); #ifndef Blt_Offset #ifdef offsetof #define Blt_Offset(type, field) ((int) offsetof(type, field)) #else #define Blt_Offset(type, field) ((int) ((char *) &((type *) 0)->field)) #endif #endif /* Blt_Offset */ typedef int (Blt_SwitchParseProc) _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *switchName, Tcl_Obj *valueObjPtr, char *record, int offset, int flags)); typedef void (Blt_SwitchFreeProc) _ANSI_ARGS_((char *record, int offset, int flags)); typedef struct { Blt_SwitchParseProc *parseProc; /* Procedure to parse a switch
Blt_SwitchFreeProc *freeProc; /* Procedure to free a switch. */ ClientData clientData; /* Arbitrary one-word value used by
} Blt_SwitchCustom; /* * Type values for Blt_SwitchSpec structures. See the user * documentation for details. */ typedef enum { BLT_SWITCH_BOOLEAN, BLT_SWITCH_DOUBLE, BLT_SWITCH_FLAG, BLT_SWITCH_FLOAT, BLT_SWITCH_INT, BLT_SWITCH_INT_NONNEGATIVE, BLT_SWITCH_INT_POSITIVE, BLT_SWITCH_LIST, BLT_SWITCH_LONG, BLT_SWITCH_LONG_NONNEGATIVE, BLT_SWITCH_LONG_POSITIVE, BLT_SWITCH_OBJ, BLT_SWITCH_STRING, BLT_SWITCH_VALUE, BLT_SWITCH_CUSTOM, BLT_SWITCH_END } Blt_SwitchTypes; typedef struct { Blt_SwitchTypes type; /* Type of option, such as
char *switchName; /* Switch used to specify option in
int offset; /* Where in widget record to store
int flags; /* Any combination of the values
Blt_SwitchCustom *customPtr; /* If type is BLT_SWITCH_CUSTOM then
int value; } Blt_SwitchSpec; #define BLT_SWITCH_DEFAULTS (0) #define BLT_SWITCH_ARGV_PARTIAL (1<<1) #define BLT_SWITCH_OBJV_PARTIAL (1<<1) /* * Possible flag values for Blt_SwitchSpec structures. Any bits at or * above BLT_SWITCH_USER_BIT may be used by clients for selecting * certain entries. */ #define BLT_SWITCH_NULL_OK (1<<0) #define BLT_SWITCH_DONT_SET_DEFAULT (1<<3) #define BLT_SWITCH_SPECIFIED (1<<4) #define BLT_SWITCH_USER_BIT (1<<8) static int Blt_ParseSwitches _ANSI_ARGS_((Tcl_Interp *interp, Blt_SwitchSpec *specPtr, int objc, Tcl_Obj *CONST *objv, void *record, int flags)); static void Blt_FreeSwitches _ANSI_ARGS_((Blt_SwitchSpec *specs, void *record, int flags)); /* * The type of the status returned by wait varies from UNIX system * to UNIX system. The macro below defines it: */ #ifdef AIX # define WAIT_STATUS_TYPE pid_t #else #ifdef HAVE_UNION_WAIT # define WAIT_STATUS_TYPE union wait #else # define WAIT_STATUS_TYPE int #endif #endif typedef int ProcessId; #define ENCODING_ASCII ((Tcl_Encoding)NULL) #define ENCODING_BINARY ((Tcl_Encoding)1) /* * We're using our own replacement for the old Tcl_CreatePipeline * call in the Tcl C library. The prescribed workaround is * Tcl_OpenCommandChannel. But it hides the pids of the pipeline * away (unless of course you pry open the undocumented structure * PipeStatus as clientData). Worse, I couldn't figure any way * to make one side of the pipe to be non-blocking. */ static int Blt_CreatePipeline _ANSI_ARGS_((Tcl_Interp *interp, int objc, Tcl_Obj *CONST *objv, ProcessId **pidPtrPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr)); #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
#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)
char *updateVar; /* Name of a Tcl variable (malloc'ed)
char **updateCmd; /* Start of a Tcl command executed
Tcl_Obj **objv; /* */ int objc; /* */ int flags; Tcl_Encoding encoding; int fd; /* File descriptor of the pipe. */ int status; int echo; /* Indicates if the pipeline's stderr stream
unsigned char *bytes; /* Stores pipeline output (malloc-ed):
*/ size_t size; /* Size of dynamically allocated buffer. */ size_t fill; /* # of bytes read into the buffer. Marks
size_t mark; /* # of bytes translated (cooked). */ size_t lastMark; /* # of bytes as of the last read.
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
int signalNum; /* If non-zero, indicates the signal
int keepNewline; /* If non-zero, indicates to set Tcl
int lineBuffered; /* If non-zero, indicates provide data
int interval; /* Interval to poll for the exiting
char *outputEncodingName; /* Name of decoding scheme to use when
char *errorEncodingName; /* Name of decoding scheme to use when
char *inputVar; /* Name of a Tcl variable set to the
/* Private */ Tcl_Interp *interp; /* Interpreter containing variables */ int nProcs; /* Number of processes in pipeline */ ProcessId *procIds; /* Array of process tokens from
int traced; /* Indicates that the status variable
int detached; /* Indicates that the pipeline is
int ignoreExitCode; /* If non-zero, don't check for 0 exit
Tcl_TimerToken timerToken; /* Token for timer handler which polls
int *exitCodePtr; /* Pointer to a memory location to
int *donePtr; Sink sink1, sink2; char *inputChanName; /* Name of input channel */ //DAS } BgExec; static Blt_SwitchParseProc ObjToSignal; static Blt_SwitchCustom killSignalSwitch = { ObjToSignal, NULL, (ClientData)0, }; static Blt_SwitchSpec switchSpecs[] = { {BLT_SWITCH_STRING, "-decodeoutput", Blt_Offset(BgExec, outputEncodingName), 0}, {BLT_SWITCH_STRING, "-decodeerror", Blt_Offset(BgExec, errorEncodingName), 0}, {BLT_SWITCH_BOOLEAN, "-echo", Blt_Offset(BgExec, sink2.echo), 0}, {BLT_SWITCH_STRING, "-error", Blt_Offset(BgExec, sink2.doneVar), 0}, {BLT_SWITCH_STRING, "-update", Blt_Offset(BgExec, sink1.updateVar), 0}, {BLT_SWITCH_STRING, "-output", Blt_Offset(BgExec, sink1.doneVar), 0}, {BLT_SWITCH_STRING, "-lasterror", Blt_Offset(BgExec, sink2.updateVar), 0}, {BLT_SWITCH_STRING, "-lastoutput", Blt_Offset(BgExec, sink1.updateVar), 0}, {BLT_SWITCH_LIST, "-onerror", Blt_Offset(BgExec, sink2.updateCmd), 0}, {BLT_SWITCH_LIST, "-onoutput", Blt_Offset(BgExec, sink1.updateCmd), 0}, {BLT_SWITCH_BOOLEAN, "-keepnewline", Blt_Offset(BgExec, keepNewline), 0}, {BLT_SWITCH_BOOLEAN, "-check", Blt_Offset(BgExec, interval), 0}, {BLT_SWITCH_CUSTOM, "-killsignal", Blt_Offset(BgExec, signalNum), 0, &killSignalSwitch}, {BLT_SWITCH_BOOLEAN, "-linebuffered", Blt_Offset(BgExec, lineBuffered), 0}, {BLT_SWITCH_BOOLEAN, "-ignorecode", Blt_Offset(BgExec, ignoreExitCode), 0}, {BLT_SWITCH_STRING, "-input", Blt_Offset(BgExec, inputVar), 0}, //DAS {BLT_SWITCH_END, NULL, 0, 0} }; static Tcl_VarTraceProc VariableProc; static Tcl_TimerProc TimerProc; static Tcl_FileProc StdoutProc, StderrProc; /* *---------------------------------------------------------------------- * * GetSignal -- * * Convert a string represent a signal number into its integer * value. * * Results: * The return value is a standard Tcl result. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int ObjToSignal( ClientData clientData, /* Contains a pointer to the tabset containing
Tcl_Interp *interp, /* Interpreter to send results back to */ char *switchName, /* Not used. */ Tcl_Obj *objPtr, /* Value representation */ char *record, /* Structure record */ int offset, /* Offset to field in structure */ int flags) { char *string; int *signalPtr = (int *)(record + offset); int signalNum; string = Tcl_GetString(objPtr); if (string[0] == '\0') { *signalPtr = 0; return TCL_OK; } if (isdigit((unsigned char)(string[0]))) { if (Tcl_GetIntFromObj(interp, objPtr, &signalNum) != TCL_OK) { return TCL_ERROR; } } else { char *name; SignalId *sp; 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 (sp = signalIds; sp->number > 0; sp++) { if (strcmp(sp->name + 3, name) == 0) { signalNum = sp->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( Sink *sinkPtr, unsigned char **dataPtr, size_t *lengthPtr) { size_t length; sinkPtr->bytes[sinkPtr->mark] = '\0'; length = sinkPtr->mark; if ((sinkPtr->mark > 0) && (sinkPtr->encoding != ENCODING_BINARY)) { unsigned char *last; last = sinkPtr->bytes + (sinkPtr->mark - 1); if ((!(sinkPtr->flags & SINK_KEEP_NL)) && (*last == '\n')) { length--; } } *dataPtr = sinkPtr->bytes; *lengthPtr = length; } /* *---------------------------------------------------------------------- * * NextBlock -- * * Returns the next block of data since the last time this * routine was called. * *---------------------------------------------------------------------- */ static unsigned char * NextBlock(Sink *sinkPtr, int *lengthPtr) { unsigned char *string; size_t length; string = sinkPtr->bytes + 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(Sink *sinkPtr, int *lengthPtr) { if (sinkPtr->mark > sinkPtr->lastMark) { unsigned char *string; size_t newBytes; register size_t i; string = sinkPtr->bytes + 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(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
for (i = 0, j = sinkPtr->lastMark; j < sinkPtr->fill; i++, j++) { sinkPtr->bytes[i] = sinkPtr->bytes[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( BgExec *bgPtr, Sink *sinkPtr, char *name, Tcl_Encoding encoding) { sinkPtr->name = name; sinkPtr->echo = FALSE; sinkPtr->fd = -1; sinkPtr->bytes = 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 (sinkPtr->updateCmd != NULL) { Tcl_Obj **objv; char **p; int objc; register int i; objc = 0; for (p = sinkPtr->updateCmd; *p != NULL; p++) { objc++; } objv = Blt_Malloc((objc + 1) * sizeof(Tcl_Obj *)); for (i = 0; i < objc; i++) { objv[i] = Tcl_NewStringObj(sinkPtr->updateCmd[i], -1); Tcl_IncrRefCount(objv[i]); } sinkPtr->objv = objv; sinkPtr->objc = objc + 1; } ResetSink(sinkPtr); } /* *---------------------------------------------------------------------- * * FreeSinkBuffer -- * * Frees the buffer's storage, freeing any malloc'ed space. * * Results: * None. * *---------------------------------------------------------------------- */ static void FreeSinkBuffer(Sink *sinkPtr) { if (sinkPtr->bytes != sinkPtr->staticSpace) { Blt_Free(sinkPtr->bytes); } sinkPtr->fd = -1; if (sinkPtr->objv != NULL) { int i; for (i = 0; i < sinkPtr->objc - 1; i++) { Tcl_DecrRefCount(sinkPtr->objv[i]); } Blt_Free(sinkPtr->objv); } } /* *---------------------------------------------------------------------- * * ExtendSinkBuffer -- * * Doubles the size of the current buffer. * * Results: * None. * *---------------------------------------------------------------------- */ static int ExtendSinkBuffer(Sink *sinkPtr) { unsigned char *bytes; /* * Allocate a new array, double the old size */ sinkPtr->size += sinkPtr->size; bytes = Blt_Malloc(sizeof(unsigned char) * sinkPtr->size); if (bytes != NULL) { unsigned char *sp, *dp, *send; dp = bytes; for (sp = sinkPtr->bytes, send = sp + sinkPtr->fill; sp < send; /*empty*/) { *dp++ = *sp++; } if (sinkPtr->bytes != sinkPtr->staticSpace) { Blt_Free(sinkPtr->bytes); } sinkPtr->bytes = bytes; return (sinkPtr->size - sinkPtr->fill); /* Return bytes left. */ } return -1; } /* *---------------------------------------------------------------------- * * 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(Sink *sinkPtr) { int nBytes; int 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
bytesLeft = sinkPtr->size - sinkPtr->fill; if (bytesLeft < BLOCK_SIZE) { bytesLeft = ExtendSinkBuffer(sinkPtr); if (bytesLeft < 0) { sinkPtr->status = READ_ERROR; return; } } array = sinkPtr->bytes + sinkPtr->fill; /*
*/ 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
if (errno == BLOCKED) { sinkPtr->status = READ_AGAIN; return; } sinkPtr->bytes[0] = '\0'; sinkPtr->status = READ_ERROR; return; } sinkPtr->fill += nBytes; sinkPtr->bytes[sinkPtr->fill] = '\0'; } sinkPtr->status = nBytes; } #define SINKOPEN(sinkPtr) ((sinkPtr)->fd != -1) static void CloseSink(Tcl_Interp *interp, Sink *sinkPtr) { if (SINKOPEN(sinkPtr)) { close(sinkPtr->fd); Tcl_DeleteFileHandler(sinkPtr->fd); sinkPtr->fd = -1; if (sinkPtr->doneVar != NULL) { unsigned char *data; size_t length; /*
*/ GetSinkData(sinkPtr, &data, &length); if (Tcl_SetVar2Ex(interp, sinkPtr->doneVar, NULL, Tcl_NewByteArrayObj(data, length), (TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG)) == NULL) { Tcl_BackgroundError(interp); } } } } /* *---------------------------------------------------------------------- * * 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(Tcl_Interp *interp, Sink *sinkPtr) { unsigned char *srcPtr, *endPtr; if (sinkPtr->encoding == ENCODING_BINARY) { /* binary */ /* No translation needed. */ sinkPtr->mark = sinkPtr->fill; } else if (sinkPtr->encoding == ENCODING_ASCII) { /* ascii */ /* One-to-one translation. mark == fill. */ sinkPtr->mark = sinkPtr->fill; } 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->bytes + 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) { /*
*/ nLeftOver = (nRaw - nSrcCooked); srcPtr = sinkPtr->bytes + (sinkPtr->mark + nSrcCooked); endPtr = srcPtr + nLeftOver; destPtr = leftover; while (srcPtr < endPtr) { *destPtr++ = *srcPtr++; } } /*
*/ needed = nLeftOver + nCooked; spaceLeft = sinkPtr->size - sinkPtr->mark; if (spaceLeft >= needed) { spaceLeft = ExtendSinkBuffer(sinkPtr); } assert(spaceLeft > needed); /*
*/ srcPtr = cooked; endPtr = cooked + nCooked; destPtr = sinkPtr->bytes + 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; } } static void NotifyOnUpdate( Tcl_Interp *interp, Sink *sinkPtr, unsigned char *data, int nBytes) { Tcl_Obj *objPtr; 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); } static int CollectData(BgExec *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
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; } 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 BgExec structure released. * *---------------------------------------------------------------------- */ static int CreateSinkHandler(BgExec *bgPtr, Sink *sinkPtr, Tcl_FileProc *proc) { 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; } Tcl_CreateFileHandler(sinkPtr->fd, TCL_READABLE, proc, bgPtr); return TCL_OK; } static void DisableTriggers(BgExec *bgPtr) /* Background info record. */ { if (bgPtr->traced) { Tcl_UntraceVar(bgPtr->interp, bgPtr->statVar, TRACE_FLAGS, VariableProc, bgPtr); bgPtr->traced = FALSE; } if (SINKOPEN(&bgPtr->sink1)) { CloseSink(bgPtr->interp, &bgPtr->sink1); } if (SINKOPEN(&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; } } /* *---------------------------------------------------------------------- * * FreeBgExec -- * * Releases the memory allocated for the backgrounded process. * *---------------------------------------------------------------------- */ static void FreeBgExec(BgExec *bgPtr) { Blt_FreeSwitches(switchSpecs, (char *)bgPtr, 0); if (bgPtr->statVar != NULL) { Blt_Free(bgPtr->statVar); } if (bgPtr->procIds != NULL) { Blt_Free(bgPtr->procIds); } Blt_Free(bgPtr); } /* *---------------------------------------------------------------------- * * DestroyBgExec -- * * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release * to clean up the internal structure (BgExec) at a safe * time (when no one is using it anymore). * * Results: * None.b * * Side effects: * The memory allocated to the BgExec structure released. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void DestroyBgExec(BgExec *bgPtr) /* Background info record. */ { DisableTriggers(bgPtr); FreeSinkBuffer(&bgPtr->sink2); FreeSinkBuffer(&bgPtr->sink1); if (bgPtr->inputChanName) { //DAS begin Tcl_Channel chan = Tcl_GetChannel(bgPtr->interp, bgPtr->inputChanName, NULL); if (chan) { Tcl_UnregisterChannel(bgPtr->interp, chan); } } //DAS end if (bgPtr->procIds != NULL) { int i; for (i = 0; i < bgPtr->nProcs; i++) { if (bgPtr->signalNum > 0) { kill(bgPtr->procIds[i], bgPtr->signalNum); } Tcl_DetachPids(1, (Tcl_Pid *)bgPtr->procIds[i]); } } FreeBgExec(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, CONST84 char *part1, CONST84 char *part2, /* Not Used. */ int flags) { if (flags & TRACE_FLAGS) { BgExec *bgPtr = clientData; /* Kill all child processes that remain alive. */ if ((bgPtr->procIds != NULL) && (bgPtr->signalNum > 0)) { int i; for (i = 0; i < bgPtr->nProcs; i++) { kill(bgPtr->procIds[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 procIds 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) { BgExec *bgPtr = clientData; register int i; unsigned int lastPid; 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++) { int pid; pid = waitpid(bgPtr->procIds[i], (int *)&waitStatus, WNOHANG); if (pid == 0) { /* Process has not terminated yet */ if (nLeft < i) { bgPtr->procIds[nLeft] = bgPtr->procIds[i]; } nLeft++; /* Count the number of processes left */ } else if (pid != -1) { /*
*/ lastStatus = waitStatus; lastPid = (unsigned int)pid; } } bgPtr->nProcs = nLeft; if ((nLeft > 0) || (SINKOPEN(&bgPtr->sink1)) || (SINKOPEN(&bgPtr->sink2))) { /* Keep polling for the status of the children that are left */ bgPtr->timerToken = Tcl_CreateTimerHandler(bgPtr->interval, TimerProc, bgPtr); 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; } Tcl_DStringAppendElement(&dString, Blt_Itoa(lastPid)); 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) { DestroyBgExec(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 * BgExec 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 clientData, /* Background information structure. */ int mask) /* Not used. */ { BgExec *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 (!SINKOPEN(&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 * BgExec 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 clientData, /* Background information structure. */ int mask) /* Not used. */ { BgExec *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 (!SINKOPEN(&bgPtr->sink1)) { bgPtr->timerToken = Tcl_CreateTimerHandler(0, TimerProc, clientData); } } /* *---------------------------------------------------------------------- * * OpenFile -- * * 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. * *---------------------------------------------------------------------- */ static int OpenFile( char *fname, /* The name of the file to open. */ int mode) /* In what mode to open the file? */ { int fd; fd = open(fname, mode, 0666); if (fd != -1) { fcntl(fd, F_SETFD, FD_CLOEXEC); /*
*/ if (mode & O_WRONLY) { lseek(fd, 0, SEEK_END); } return fd; } return -1; } /* *---------------------------------------------------------------------- * * CreateTempFile -- * * 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. * *---------------------------------------------------------------------- */ static int CreateTempFile(char *contents) /* String to write into temp file, or NULL. */ { char fileName[L_tmpnam]; int fd; size_t length = (contents == NULL) ? 0 : strlen(contents); mkstemp(fileName); fd = OpenFile(fileName, O_RDWR | O_CREAT | O_TRUNC); unlink(fileName); if ((fd >= 0) && (length > 0)) { for (;;) { if (write(fd, contents, length) != -1) { break; } else if (errno != EINTR) { close(fd); return -1; } } lseek(fd, 0, SEEK_SET); } return fd; } /* *---------------------------------------------------------------------- * * CreatePipe -- * * Creates a pipe by simply calling the pipe() function. * * Results: * Returns 1 on success, 0 on failure. * * Side effects: * Creates a pipe. * *---------------------------------------------------------------------- */ static int CreatePipe( int *inFilePtr, /* (out) Descriptor for read side of pipe. */ int *outFilePtr) /* (out) Descriptor for write side of pipe. */ { int pipeDes[2]; if (pipe(pipeDes) != 0) { return 0; } fcntl(pipeDes[0], F_SETFD, FD_CLOEXEC); fcntl(pipeDes[1], F_SETFD, FD_CLOEXEC); *inFilePtr = pipeDes[0]; *outFilePtr = pipeDes[1]; return 1; } /* *---------------------------------------------------------------------- * * CloseFile -- * * 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. * *---------------------------------------------------------------------- */ static int CloseFile(int fd) /* File descriptor to be closed. */ { if ((fd == 0) || (fd == 1) || (fd == 2)) { return 0; /* Don't close stdin, stdout or stderr. */ } Tcl_DeleteFileHandler(fd); return close(fd); } /* *---------------------------------------------------------------------- * * 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( int fd, /* File descriptor to dup, or -1. */ int type) /* One of TCL_STDIN, TCL_STDOUT, TCL_STDERR */ { int targetFd = 0; /* Initializations here needed only to */ int direction = 0; /* prevent warnings about using uninitialized
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 (fd < 0) { Tcl_Channel channel; channel = Tcl_GetStdChannel(type); if (channel) { ClientData clientData; Tcl_GetChannelHandle(channel, direction, &clientData); fd = (int)clientData; } } if (fd >= 0) { if (fd != targetFd) { if (dup2(fd, targetFd) == -1) { return 0; } /*
*/ fcntl(targetFd, F_SETFD, 0); } else { /*
*/ fcntl(fd, F_SETFD, 0); } } else { close(targetFd); } return 1; } /* *---------------------------------------------------------------------- * * CreateProcess -- * * 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 */ static int CreateProcess( Tcl_Interp *interp, /* Interpreter in which to leave
int argc, /* Number of arguments in following
char **argv, /* Array of argument strings. argv[0]
int stdinFd, /* The file to use as input for the
int stdoutFd, /* The file that receives output from
int stderrFd, /* The file that receives errors from
int *pidPtr) /* (out) If this procedure is
{ Tcl_DString *dsArr; Tcl_Encoding encoding; char errSpace[200]; int errPipeIn, errPipeOut; int i; int joinThisError, status, fd; int pid; size_t count; errPipeIn = errPipeOut = -1; pid = -1; dsArr = Blt_Malloc(argc * sizeof(Tcl_DString)); encoding = Tcl_GetEncoding(interp, NULL); for(i = 0; i < argc; i++) { argv[i] = Tcl_UtfToExternalDString(encoding, argv[i], strlen(argv[i]), dsArr + i); } /* * Create a pipe that the child can use to return error * information if anything goes wrong. */ if (CreatePipe(&errPipeIn, &errPipeOut) == 0) { Tcl_AppendResult(interp, "can't create pipe: ", Tcl_PosixError(interp), (char *)NULL); goto error; } joinThisError = (stderrFd == stdoutFd); pid = fork(); if (pid == 0) { fd = errPipeOut; /*
*/ if (!SetupStdFile(stdinFd, TCL_STDIN) || !SetupStdFile(stdoutFd, TCL_STDOUT) || (!joinThisError && !SetupStdFile(stderrFd, TCL_STDERR)) || (joinThisError && ((dup2(1, 2) == -1) || (fcntl(2, F_SETFD, 0) != 0)))) { sprintf(errSpace, "%dforked process can't set up input/output: ", errno); write(fd, errSpace, (size_t) strlen(errSpace)); _exit(1); } /*
*/ RestoreSignals(); execvp(argv[0], &argv[0]); sprintf(errSpace, "%dcan't execute \"%.150s\": ", errno, argv[0]); write(fd, errSpace, (size_t) strlen(errSpace)); _exit(1); } if (pid == -1) { Tcl_AppendResult(interp, "can't fork child process: ", Tcl_PosixError(interp), (char *)NULL); 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. */ CloseFile(errPipeOut); errPipeOut = -1; fd = errPipeIn; count = read(fd, 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; } for(i = 0; i < argc; i++) { Tcl_DStringFree(dsArr + i); } Blt_Free(dsArr); CloseFile(errPipeIn); *pidPtr = pid; return TCL_OK; error: if (pid != -1) { /*
*/ Tcl_WaitPid((Tcl_Pid)pid, &status, WNOHANG); } if (errPipeIn >= 0) { CloseFile(errPipeIn); } if (errPipeOut >= 0) { CloseFile(errPipeOut); } for(i = 0; i < argc; i++) { Tcl_DStringFree(dsArr + i); } Blt_Free(dsArr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * FileForRedirect -- * * This procedure does much of the work of parsing redirection * operators. It handles "@" if specified and allowed, and a file * name, and opens the file if necessary. * * Results: * The return value is the descriptor number for the file. If an * error occurs then NULL is returned and an error message is left * in interp->result. Several arguments are side-effected; see * the argument list below for details. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FileForRedirect( Tcl_Interp *interp, /* Intepreter to use for error
char *spec, /* Points to character just after
char *arg, /* Pointer to entire argument
int atOK, /* Non-zero means that '@' notation
char *nextArg, /* Next argument in argc/argv array,
int flags, /* Flags to use for opening file or to
int *skipPtr, /* (out) Filled with 1 if redirection target
int *closePtr) /* (out) Filled with one if the caller
{ int writing = (flags & O_WRONLY); int fd; *skipPtr = 1; if ((atOK != 0) && (*spec == '@')) { ClientData clientData; int direction; Tcl_Channel chan; spec++; if (*spec == '\0') { spec = nextArg; if (spec == NULL) { goto badLastArg; } *skipPtr = 2; } chan = Tcl_GetChannel(interp, spec, NULL); if (chan == NULL) { return -1; } direction = (writing) ? TCL_WRITABLE : TCL_READABLE; if (Tcl_GetChannelHandle(chan, direction, &clientData) != TCL_OK) { fd = -1; } fd = (int)clientData; if (fd < 0) { Tcl_AppendResult(interp, "channel \"", Tcl_GetChannelName(chan), "\" wasn't opened for ", ((writing) ? "writing" : "reading"), (char *)NULL); return -1; } if (writing) { /*
*/ Tcl_Flush(chan); } } else { char *name; Tcl_DString nameString; if (*spec == '\0') { spec = nextArg; if (spec == NULL) { goto badLastArg; } *skipPtr = 2; } name = Tcl_TranslateFileName(interp, spec, &nameString); if (name != NULL) { fd = OpenFile(name, flags); } else { fd = -1; } Tcl_DStringFree(&nameString); if (fd < 0) { Tcl_AppendResult(interp, "can't ", ((writing) ? "write" : "read"), " file \"", spec, "\": ", Tcl_PosixError(interp), (char *)NULL); return -1; } *closePtr = TRUE; } return fd; badLastArg: Tcl_AppendResult(interp, "can't specify \"", arg, "\" as last word in command", (char *)NULL); return -1; } /* *---------------------------------------------------------------------- * * Blt_CreatePipeline -- * * Given an objc/objv array, instantiate a pipeline of processes * as described by the objv. * * 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 stdinPipePtr isn't NULL, then *stdinPipePtr is filled * with the file id for the input pipe for the pipeline (if any): * the caller must eventually close this file. * * If stdoutPipePtr isn't NULL, then *stdoutPipePtr is filled * with the file id for the output pipe from the pipeline: the * caller must close this file. * * If stderrPipePtr isn't NULL, then *stderrPipePtr 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. * *---------------------------------------------------------------------- */ static int Blt_CreatePipeline( Tcl_Interp *interp, /* Interpreter to use for error
int objc, /* Number of entries in objv. */ Tcl_Obj *CONST *objv, /* Array of strings describing
int **pidArrayPtr, /* (out) Word at *pidArrayPtr gets
int *stdinPipePtr, /* (out) If non-NULL, input to the
int *stdoutPipePtr, /* (out) If non-NULL, output to the
int *stderrPipePtr) /* (out) If non-NULL, all stderr
{ int *pids = NULL; /* Points to malloc-ed array holding all
int nPids; /* Actual number of processes that exist
int cmdCount; /* Count of number of distinct commands
char *inputLiteral = NULL; /* If non-null, then this points to a
int stdinFd; /* If != NULL, gives file to use as input for
int stdoutFd; /* Writable file for output from last command
int stderrFd; /* Writable file for error output from all
int stdinIsOpen; /* If non-zero, then stdinFd should be
int stdoutIsOpen; /* If non-zero, then stdoutFd should be
int stderrIsOpen; /* If non-zero, then stderrFd should be
char *p; int skip, lastBar, lastArg, i, j, atOK, flags, errorToOutput; Tcl_DString execBuffer; int pipeIn; int stdinCurrFd, stdoutCurrFd, stderrCurrFd; char **argv; stdinFd = stdoutFd = stderrFd = -1; stdinIsOpen = stdoutIsOpen = stderrIsOpen = FALSE; if (stdinPipePtr != NULL) { *stdinPipePtr = -1; } if (stdoutPipePtr != NULL) { *stdoutPipePtr = -1; } if (stderrPipePtr != NULL) { *stderrPipePtr = -1; } Tcl_DStringInit(&execBuffer); pipeIn = stdinCurrFd = stdoutCurrFd = -1; nPids = 0; /* * First, scan through all the arguments to figure out the * structure of the pipeline. Process all of the input and output * redirection arguments and remove them from the argument list in * the pipeline. Count the number of distinct processes (it's the * number of "|" arguments plus one) but don't remove the "|" * arguments because they'll be used in the second pass to * seperate the individual child processes. * * Cannot start the child processes in this pass because the * redirection symbols may appear anywhere in the command line -- * e.g., the '<' that specifies the input to the entire pipe may * appear at the very end of the argument list. */ /* Convert all the Tcl_Objs to strings. */ argv = Blt_Malloc((objc + 1) * sizeof(char *)); for (i = 0; i < objc; i++) { argv[i] = Tcl_GetString(objv[i]); } argv[i] = NULL; lastBar = -1; cmdCount = 1; for (i = 0; i < objc; i++) { skip = 0; p = argv[i]; switch (*p++) { case '\\': p++; continue; case '|': if (*p == '&') { p++; } if (*p == '\0') { if ((i == (lastBar + 1)) || (i == (objc - 1))) { Tcl_AppendResult(interp, "illegal use of | or |& in command", (char *)NULL); goto error; } } lastBar = i; cmdCount++; break; case '<': if (stdinIsOpen != 0) { stdinIsOpen = FALSE; CloseFile(stdinFd); } if (*p == '<') { stdinFd = -1; inputLiteral = p + 1; skip = 1; if (*inputLiteral == '\0') { inputLiteral = argv[i + 1]; if (inputLiteral == NULL) { Tcl_AppendResult(interp, "can't specify \"", argv[i], "\" as last word in command", (char *)NULL); goto error; } skip = 2; } } else { inputLiteral = NULL; stdinFd = FileForRedirect(interp, p, argv[i], TRUE, argv[i + 1], O_RDONLY, &skip, &stdinIsOpen); if (stdinFd < 0) { goto error; } } break; case '>': atOK = TRUE; flags = O_WRONLY | O_CREAT | O_TRUNC; errorToOutput = FALSE; if (*p == '>') { p++; atOK = FALSE; flags = O_WRONLY | O_CREAT; } if (*p == '&') { if (stderrIsOpen != 0) { stderrIsOpen = FALSE; CloseFile(stderrFd); } errorToOutput = TRUE; p++; } if (stdoutIsOpen != 0) { stdoutIsOpen = FALSE; CloseFile(stdoutFd); } stdoutFd = FileForRedirect(interp, p, argv[i], atOK, argv[i + 1], flags, &skip, &stdoutIsOpen); if (stdoutFd < 0) { goto error; } if (errorToOutput) { stderrIsOpen = FALSE; stderrFd = stdoutFd; } break; case '2': if (*p != '>') { break; } p++; atOK = TRUE; flags = O_WRONLY | O_CREAT | O_TRUNC; if (*p == '>') { p++; atOK = FALSE; flags = O_WRONLY | O_CREAT; } if (stderrIsOpen != 0) { stderrIsOpen = FALSE; CloseFile(stderrFd); } stderrFd = FileForRedirect(interp, p, argv[i], atOK, argv[i + 1], flags, &skip, &stderrIsOpen); if (stderrFd < 0) { goto error; } break; } if (skip != 0) { for (j = i + skip; j < objc; j++) { argv[j - skip] = argv[j]; } objc -= skip; i -= 1; } } if (stdinFd == -1) { if (inputLiteral != NULL) { /*
*/ stdinFd = CreateTempFile(inputLiteral); if (stdinFd < 0) { Tcl_AppendResult(interp, "can't create input file for command: ", Tcl_PosixError(interp), (char *)NULL); goto error; } stdinIsOpen = TRUE; } else if (stdinPipePtr != NULL) { /*
*/ if (CreatePipe(&stdinFd, stdinPipePtr) == 0) { Tcl_AppendResult(interp, "can't create input pipe for command: ", Tcl_PosixError(interp), (char *)NULL); goto error; } stdinIsOpen = TRUE; } else { /*
*/ stdinFd = 0; } } if (stdoutFd == -1) { if (stdoutPipePtr != NULL) { /*
*/ if (CreatePipe(stdoutPipePtr, &stdoutFd) == 0) { Tcl_AppendResult(interp, "can't create output pipe for command: ", Tcl_PosixError(interp), (char *)NULL); goto error; } stdoutIsOpen = TRUE; } else { /*
*/ stdoutFd = 1; } } if (stderrFd == -1) { if (stderrPipePtr != NULL) { /*
*/ if (CreatePipe(stderrPipePtr, &stderrFd) == 0) { Tcl_AppendResult(interp, "can't create error pipe for command: ", Tcl_PosixError(interp), (char *)NULL); goto error; } stderrIsOpen = TRUE; } else { /*
*/ stderrFd = 2; } } /* * Scan through the objc array, creating a process for each group * of arguments between the "|" characters. */ Tcl_ReapDetachedProcs(); pids = Blt_Malloc(cmdCount * sizeof(int)); stdinCurrFd = stdinFd; lastArg = 0; /* Suppress compiler warning */ for (i = 0; i < objc; i = lastArg + 1) { int joinThisError; int pid; /*
*/ argv[i] = Tcl_TranslateFileName(interp, argv[i], &execBuffer); if (argv[i] == NULL) { goto error; } /*
*/ joinThisError = 0; for (lastArg = i + 1; lastArg < objc; 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 (lastArg == objc) { stdoutCurrFd = stdoutFd; } else { if (CreatePipe(&pipeIn, &stdoutCurrFd) == 0) { Tcl_AppendResult(interp, "can't create pipe: ", Tcl_PosixError(interp), (char *)NULL); goto error; } } if (joinThisError != 0) { stderrCurrFd = stdoutCurrFd; } else { stderrCurrFd = stderrFd; } if (CreateProcess(interp, lastArg - i, argv + i, stdinCurrFd, stdoutCurrFd, stderrCurrFd, &pid) != TCL_OK) { goto error; } Tcl_DStringFree(&execBuffer); pids[nPids] = pid; nPids++; /*
*/ if ((stdinCurrFd >= 0) && (stdinCurrFd != stdinFd)) { CloseFile(stdinCurrFd); } stdinCurrFd = pipeIn; pipeIn = -1; if ((stdoutCurrFd >= 0) && (stdoutCurrFd != stdoutFd)) { CloseFile(stdoutCurrFd); } stdoutCurrFd = -1; } *pidArrayPtr = pids; /* * All done. Cleanup open files lying around and then return. */ cleanup: Tcl_DStringFree(&execBuffer); if (stdinIsOpen) { CloseFile(stdinFd); } if (stdoutIsOpen) { CloseFile(stdoutFd); } if (stderrIsOpen) { CloseFile(stderrFd); } if (argv != NULL) { Blt_Free(argv); } return nPids; /* * An error occurred. There could have been extra files open, * such as pipes between children. Clean them all up. Detach any * child processes that have been created. */ error: if (pipeIn >= 0) { CloseFile(pipeIn); } if ((stdoutCurrFd >= 0) && (stdoutCurrFd != stdoutFd)) { CloseFile(stdoutCurrFd); } if ((stdinCurrFd >= 0) && (stdinCurrFd != stdinFd)) { CloseFile(stdinCurrFd); } if ((stdinPipePtr != NULL) && (*stdinPipePtr >= 0)) { CloseFile(*stdinPipePtr); *stdinPipePtr = -1; } if ((stdoutPipePtr != NULL) && (*stdoutPipePtr >= 0)) { CloseFile(*stdoutPipePtr); *stdoutPipePtr = -1; } if ((stderrPipePtr != NULL) && (*stderrPipePtr >= 0)) { CloseFile(*stderrPipePtr); *stderrPipePtr = -1; } if (pids != NULL) { for (i = 0; i < nPids; i++) { if (pids[i] != -1) { Tcl_DetachPids(1, (Tcl_Pid *)(pids + i)); } } Blt_Free(pids); } nPids = -1; goto cleanup; } static char stringRep[200]; static char * Blt_Itoa(int value) { sprintf(stringRep, "%d", value); return stringRep; } #ifndef NDEBUG static void Blt_Assert(char *testExpr, char *fileName, int lineNumber) { fprintf(stderr, "line %d of %s: Assert \"%s\" failed\n", lineNumber, fileName, testExpr); fflush(stderr); abort(); } #endif static int Blt_GetCountFromObj( Tcl_Interp *interp, Tcl_Obj *objPtr, int check, /* Can be COUNT_POSITIVE, COUNT_NONNEGATIVE,
long *valuePtr) { long count; if (Tcl_GetLongFromObj(interp, objPtr, &count) != TCL_OK) { return TCL_ERROR; } switch (check) { case COUNT_NONNEGATIVE: if (count < 0) { Tcl_AppendResult(interp, "bad value \"", Tcl_GetString(objPtr), "\": can't be negative", (char *)NULL); return TCL_ERROR; } break; case COUNT_POSITIVE: if (count <= 0) { Tcl_AppendResult(interp, "bad value \"", Tcl_GetString(objPtr), "\": must be positive", (char *)NULL); return TCL_ERROR; } break; case COUNT_ANY: break; } *valuePtr = count; return TCL_OK; } static void * Blt_Calloc(unsigned int n, size_t size) { void *ptr; size_t nBytes; nBytes = n * size; ptr = Blt_Malloc(nBytes); if (ptr != NULL) { memset(ptr, 0, nBytes); } return ptr; } /* *---------------------------------------------------------------------- * * Blt_Strdup -- * * Create a copy of the string from heap storage. * * Results: * Returns a pointer to the need string copy. * *---------------------------------------------------------------------- */ char * Blt_Strdup(CONST char *string) { size_t size; char *ptr; size = strlen(string) + 1; ptr = Blt_Malloc(size * sizeof(char)); if (ptr != NULL) { strcpy(ptr, string); } return ptr; } /* *-------------------------------------------------------------- * * FindSwitchSpec -- * * Search through a table of configuration specs, looking for * one that matches a given argvName. * * Results: * The return value is a pointer to the matching entry, or NULL * if nothing matched. In that case an error message is left * in the interp's result. * * Side effects: * None. * *-------------------------------------------------------------- */ static Blt_SwitchSpec * FindSwitchSpec( Tcl_Interp *interp, /* Used for reporting errors. */ Blt_SwitchSpec *specs, /* Pointer to table of configuration
char *name, /* Name (suitable for use in a "switch"
int needFlags, /* Flags that must be present in matching
int hateFlags) /* Flags that must NOT be present in
{ Blt_SwitchSpec *sp; register char c; /* First character of current argument. */ Blt_SwitchSpec *matchPtr; /* Matching spec, or NULL. */ size_t length; c = name[1]; length = strlen(name); matchPtr = NULL; for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { if (sp->switchName == NULL) { continue; } if ((sp->switchName[1] != c) || (strncmp(sp->switchName, name, length) != 0)) { continue; } if (((sp->flags & needFlags) != needFlags) || (sp->flags & hateFlags)) { continue; } if (sp->switchName[length] == '\0') { return sp; /* Stop on a perfect match. */ } if (matchPtr != NULL) { Tcl_AppendResult(interp, "ambiguous option \"", name, "\"", (char *) NULL); return (Blt_SwitchSpec *) NULL; } matchPtr = sp; } if (matchPtr == NULL) { Tcl_AppendResult(interp, "unknown option \"", name, "\"", (char *)NULL); return (Blt_SwitchSpec *) NULL; } return matchPtr; } /* *-------------------------------------------------------------- * * DoSwitch -- * * This procedure applies a single configuration option * to a widget record. * * Results: * A standard Tcl return value. * * Side effects: * WidgRec is modified as indicated by specPtr and value. * The old value is recycled, if that is appropriate for * the value type. * *-------------------------------------------------------------- */ static int DoSwitch( Tcl_Interp *interp, /* Interpreter for error reporting. */ Blt_SwitchSpec *sp, /* Specifier to apply. */ Tcl_Obj *objPtr, /* Value to use to fill in widgRec. */ void *record) /* Record whose fields are to be
{ do { char *ptr; ptr = (char *)record + sp->offset; switch (sp->type) { case BLT_SWITCH_BOOLEAN: if (Tcl_GetBooleanFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { return TCL_ERROR; } break; case BLT_SWITCH_DOUBLE: if (Tcl_GetDoubleFromObj(interp, objPtr, (double *)ptr) != TCL_OK) { return TCL_ERROR; } break; case BLT_SWITCH_OBJ: *(Tcl_Obj **)ptr = objPtr; break; case BLT_SWITCH_FLOAT: { double value; if (Tcl_GetDoubleFromObj(interp, objPtr, &value) != TCL_OK) { return TCL_ERROR; } *(float *)ptr = (float)value; } break; case BLT_SWITCH_INT: if (Tcl_GetIntFromObj(interp, objPtr, (int *)ptr) != TCL_OK) { return TCL_ERROR; } break; case BLT_SWITCH_INT_NONNEGATIVE: { long value; if (Blt_GetCountFromObj(interp, objPtr, COUNT_NONNEGATIVE, &value) != TCL_OK) { return TCL_ERROR; } *(int *)ptr = (int)value; } break; case BLT_SWITCH_INT_POSITIVE: { long value; if (Blt_GetCountFromObj(interp, objPtr, COUNT_POSITIVE, &value) != TCL_OK) { return TCL_ERROR; } *(int *)ptr = (int)value; } break; case BLT_SWITCH_LIST: { int argc; if (Tcl_SplitList(interp, Tcl_GetString(objPtr), &argc, (CONST84 char ***)ptr) != TCL_OK) { return TCL_ERROR; } } break; case BLT_SWITCH_LONG: if (Tcl_GetLongFromObj(interp, objPtr, (long *)ptr) != TCL_OK) { return TCL_ERROR; } break; case BLT_SWITCH_LONG_NONNEGATIVE: { long value; if (Blt_GetCountFromObj(interp, objPtr, COUNT_NONNEGATIVE, &value) != TCL_OK) { return TCL_ERROR; } *(long *)ptr = value; } break; case BLT_SWITCH_LONG_POSITIVE: { long value; if (Blt_GetCountFromObj(interp, objPtr, COUNT_POSITIVE, &value) != TCL_OK) { return TCL_ERROR; } *(long *)ptr = value; } break; case BLT_SWITCH_STRING: { char *old, *new, **strPtr; char *string; string = Tcl_GetString(objPtr); strPtr = (char **)ptr; new = ((*string == '\0') && (sp->flags & BLT_SWITCH_NULL_OK)) ? NULL : Blt_Strdup(string); old = *strPtr; if (old != NULL) { Blt_Free(old); } *strPtr = new; } break; case BLT_SWITCH_CUSTOM: if ((*sp->customPtr->parseProc)(sp->customPtr->clientData, interp, sp->switchName, objPtr, (char *)record, sp->offset, sp->flags) != TCL_OK) { return TCL_ERROR; } break; default: Tcl_AppendResult(interp, "bad switch table: unknown type \"", Blt_Itoa(sp->type), "\"", (char *)NULL); return TCL_ERROR; } sp++; } while ((sp->switchName == NULL) && (sp->type != BLT_SWITCH_END)); return TCL_OK; } /* *-------------------------------------------------------------- * * Blt_ParseSwitches -- * * Process command-line options and database options to * fill in fields of a widget record with resources and * other parameters. * * Results: * Returns the number of arguments comsumed by parsing the * command line. If an error occurred, -1 will be returned * and an error messages can be found as the interpreter * result. * * Side effects: * The fields of widgRec get filled in with information * from argc/argv and the option database. Old information * in widgRec's fields gets recycled. * *-------------------------------------------------------------- */ static int Blt_ParseSwitches( Tcl_Interp *interp, /* Interpreter for error reporting. */ Blt_SwitchSpec *specs, /* Describes legal options. */ int objc, /* Number of elements in argv. */ Tcl_Obj *CONST *objv, /* Command-line options. */ void *record, /* Record whose fields are to be
int flags) /* Used to specify additional flags
{ Blt_SwitchSpec *sp; int count; int needFlags; /* Specs must contain this set of flags
int hateFlags; /* If a spec contains any bits here, it's
needFlags = flags & ~(BLT_SWITCH_USER_BIT - 1); hateFlags = 0; /* * Pass 1: Clear the change flags on all the specs so that we * can check it later. */ for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { sp->flags &= ~BLT_SWITCH_SPECIFIED; } /* * Pass 2: Process the arguments that match entries in the specs. * It's an error if the argument doesn't match anything. */ for (count = 0; count < objc; count++) { char *arg; arg = Tcl_GetString(objv[count]); if (flags & BLT_SWITCH_OBJV_PARTIAL) { /*
*/ if (arg[0] != '-') { return count; } if ((arg[1] == '-') && (arg[2] == '\0')) { return count + 1; /* include the "--" in the count. */ } } sp = FindSwitchSpec(interp, specs, arg, needFlags, hateFlags); if (sp == NULL) { return -1; } if (sp->type == BLT_SWITCH_FLAG) { char *ptr; ptr = (char *)record + sp->offset; *((int *)ptr) |= sp->value; } else if (sp->type == BLT_SWITCH_VALUE) { char *ptr; ptr = (char *)record + sp->offset; *((int *)ptr) = sp->value; } else { count++; if (count == objc) { Tcl_AppendResult(interp, "value for \"", arg, "\" missing", (char *) NULL); return -1; } if (DoSwitch(interp, sp, objv[count], record) != TCL_OK) { char msg[100]; sprintf(msg, "\n (processing \"%.40s\" option)", sp->switchName); Tcl_AddErrorInfo(interp, msg); return -1; } } sp->flags |= BLT_SWITCH_SPECIFIED; } return count; } /* *---------------------------------------------------------------------- * * Blt_FreeSwitches -- * * Free up all resources associated with switch options. * * Results: * None. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void Blt_FreeSwitches( Blt_SwitchSpec *specs, /* Describes legal options. */ void *record, /* Record whose fields contain current
int needFlags) /* Used to specify additional flags
{ Blt_SwitchSpec *sp; for (sp = specs; sp->type != BLT_SWITCH_END; sp++) { if ((sp->flags & needFlags) == needFlags) { char *ptr; ptr = (char *)record + sp->offset; switch (sp->type) { case BLT_SWITCH_STRING: case BLT_SWITCH_LIST: if (*((char **) ptr) != NULL) { Blt_Free(*((char **) ptr)); *((char **) ptr) = NULL; } break; case BLT_SWITCH_CUSTOM: if ((*(char **)ptr != NULL) && (sp->customPtr->freeProc != NULL)) { (*sp->customPtr->freeProc)((char *)record, sp->offset, sp->flags); } break; default: break; } } } } } #------------------------------------------------------------------------------- ::critcl::ccommand bgexec {ClientData interp objc objv} { BgExec *bgPtr; ProcessId *pidPtr; Tcl_Encoding encoding; char *lastArg; int *outFdPtr, *errFdPtr; int detached; int i; int nProcs; int inFd = -1, *inFdPtr = NULL; //DAS if (objc < 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", Tcl_GetString(objv[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 = Tcl_GetString(objv[objc - 1]); detached = ((lastArg[0] == '&') && (lastArg[1] == '\0')); if (detached) { objc--; /* Remove the '&' argument */ } bgPtr = Blt_Calloc(1, sizeof(BgExec)); 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(Tcl_GetString(objv[1])); bgPtr->inputChanName = NULL; //DAS /* Try to clean up any detached processes */ Tcl_ReapDetachedProcs(); i = Blt_ParseSwitches(interp, switchSpecs, objc - 2, objv + 2, bgPtr, BLT_SWITCH_OBJV_PARTIAL); if (i < 0) { FreeBgExec(bgPtr); return TCL_ERROR; } i += 2; /* Must be at least one argument left as the command to execute. */ if (objc <= i) { Tcl_AppendResult(interp, "missing command to execute: should be \"", Tcl_GetString(objv[0]), " varName ?options? command ?arg...?\"", (char *)NULL); FreeBgExec(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 { encoding = Tcl_GetEncoding(interp, bgPtr->outputEncodingName); if (encoding == NULL) { goto error; } } } InitSink(bgPtr, &bgPtr->sink1, "stdout", encoding); if (bgPtr->errorEncodingName != NULL) { if (strcmp(bgPtr->errorEncodingName, "binary") == 0) { encoding = ENCODING_BINARY; } else { encoding = Tcl_GetEncoding(interp, bgPtr->errorEncodingName); if (encoding == NULL) { goto error; } } } InitSink(bgPtr, &bgPtr->sink2, "stderr", encoding); outFdPtr = errFdPtr = (int *)NULL; outFdPtr = &bgPtr->sink1.fd; if ((bgPtr->sink2.doneVar != NULL) || (bgPtr->sink2.updateVar != NULL) || (bgPtr->sink2.updateCmd != NULL) || (bgPtr->sink2.echo)) { errFdPtr = &bgPtr->sink2.fd; } if (bgPtr->inputVar) { //DAS begin inFdPtr = &inFd; bgPtr->detached = 1; } //DAS end nProcs = Blt_CreatePipeline(interp, objc - i, objv + i, &pidPtr, inFdPtr, outFdPtr, errFdPtr); //DAS if (nProcs < 0) { goto error; } bgPtr->procIds = pidPtr; bgPtr->nProcs = nProcs; if (bgPtr->sink1.fd == -1) { /*
*/ 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->inputVar) { //DAS begin Tcl_Channel chan; CONST84 char *result; if (inFd == -1) goto error; chan = Tcl_MakeFileChannel((void*)inFd, TCL_WRITABLE); if (!chan) goto error; Tcl_RegisterChannel(interp, chan); bgPtr->inputChanName = Blt_Strdup(Tcl_GetChannelName(chan)); result = Tcl_SetVar(interp, bgPtr->inputVar, bgPtr->inputChanName, TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG); if (!result) goto error; } //DAS end if (bgPtr->detached) { /* If detached, return a list of the child process ids instead
for (i = 0; i < nProcs; i++) { char string[200]; sprintf(string, "%d", bgPtr->procIds[i]); 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 ((bgPtr->ignoreExitCode) || (exitCode == 0)) { if (bgPtr->sink1.doneVar == NULL) { unsigned char *data; size_t length; /* Return the output of the pipeline. */ GetSinkData(&bgPtr->sink1, &data, &length); Tcl_SetByteArrayObj(Tcl_GetObjResult(interp), data, length); } } else { DestroyBgExec(bgPtr); Tcl_AppendResult(interp, "child process exited abnormally", (char *)NULL); return TCL_ERROR; } DestroyBgExec(bgPtr); } return TCL_OK; error: DisableTriggers(bgPtr); DestroyBgExec(bgPtr); return TCL_ERROR; } #------------------------------------------------------------------------------- proc bgexec_write_manpage {} { set n [file join [file dirname [info script]] bgexec.n] puts stderr "Writing manpage $n" puts [set f [open $n w]] { '\" '\" Copyright 1991-1997 by Bell Labs Innovations for Lucent Technologies. '\" '\" 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. '\" '\" Bgexec command created by George Howlett. .if t .wh -1.3i ^B .nr ^l \n(.l .ad b '\" # Start an argument description .de AP .ie !"\\$4"" .TP \\$4 .el \{\ . ie !"\\$2"" .TP \\n()Cu . el .TP 15 .\} .ta \\n()Au \\n()Bu .ie !"\\$3"" \{\ \&\\$1 \\fI\\$2\\fP (\\$3) .\".b .\} .el \{\ .br .ie !"\\$2"" \{\ \&\\$1 \\fI\\$2\\fP .\} .el \{\ \&\\fI\\$1\\fP .\} .\} .. '\" # define tabbing values for .AP .de AS .nr )A 10n .if !"\\$1"" .nr )A \\w'\\$1'u+3n .nr )B \\n()Au+15n .\" .if !"\\$2"" .nr )B \\w'\\$2'u+\\n()Au+3n .nr )C \\n()Bu+\\w'(in/out)'u+2n .. .AS Tcl_Interp Tcl_CreateInterp in/out '\" # BS - start boxed text '\" # ^y = starting y location '\" # ^b = 1 .de BS .br .mk ^y .nr ^b 1u .if n .nf .if n .ti 0 .if n \l'\\n(.lu\(ul' .if n .fi .. '\" # BE - end boxed text (draw box now) .de BE .nf .ti 0 .mk ^t .ie n \l'\\n(^lu\(ul' .el \{\ .\" Draw four-sided box normally, but don't draw top of .\" box if the box started on an earlier page. .ie !\\n(^b-1 \{\ \h'-1.5n'\L'|\\n(^yu-1v'\l'\\n(^lu+3n\(ul'\L'\\n(^tu+1v-\\n(^yu'\l'|0u-1.5n\(ul' .\} .el \}\ \h'-1.5n'\L'|\\n(^yu-1v'\h'\\n(^lu+3n'\L'\\n(^tu+1v-\\n(^yu'\l'|0u-1.5n\(ul' .\} .\} .fi .br .nr ^b 0 .. '\" # VS - start vertical sidebar '\" # ^Y = starting y location '\" # ^v = 1 (for troff; for nroff this doesn't matter) .de VS .if !"\\$2"" .br .mk ^Y .ie n 'mc \s12\(br\s0 .el .nr ^v 1u .. '\" # VE - end of vertical sidebar .de VE .ie n 'mc .el \{\ .ev 2 .nf .ti 0 .mk ^t \h'|\\n(^lu+3n'\L'|\\n(^Yu-1v\(bv'\v'\\n(^tu+1v-\\n(^Yu'\h'-|\\n(^lu+3n' .sp -1 .fi .ev .\} .nr ^v 0 .. '\" # Special macro to handle page bottom: finish off current '\" # box/sidebar if in box/sidebar mode, then invoked standard '\" # page bottom macro. .de ^B .ev 2 'ti 0 'nf .mk ^t .if \\n(^b \{\ .\" Draw three-sided box if this is the box's first page, .\" draw two sides but no top otherwise. .ie !\\n(^b-1 \h'-1.5n'\L'|\\n(^yu-1v'\l'\\n(^lu+3n\(ul'\L'\\n(^tu+1v-\\n(^yu'\h'|0u'\c .el \h'-1.5n'\L'|\\n(^yu-1v'\h'\\n(^lu+3n'\L'\\n(^tu+1v-\\n(^yu'\h'|0u'\c .\} .if \\n(^v \{\ .nr ^x \\n(^tu+1v-\\n(^Yu \kx\h'-\\nxu'\h'|\\n(^lu+3n'\ky\L'-\\n(^xu'\v'\\n(^xu'\h'|0u'\c .\} .bp 'fi .ev .if \\n(^b \{\ .mk ^y .nr ^b 2 .\} .if \\n(^v \{\ .mk ^Y .\} .. '\" # DS - begin display .de DS .RS .nf .sp .. '\" # DE - end display .de DE .fi .RE .sp .. '\" # SO - start of list of standard options .de SO .SH "STANDARD OPTIONS" .LP .nf .ta 4c 8c 12c .ft B .. '\" # SE - end of list of standard options .de SE .fi .ft R .LP See the \\fBoptions\\fR manual entry for details on the standard options. .. '\" # OP - start of full description for a single option .de OP .LP .nf .ta 4c Command-Line Name: \\fB\\$1\\fR Database Name: \\fB\\$2\\fR Database Class: \\fB\\$3\\fR .fi .IP .. '\" # CS - begin code excerpt .de CS .RS .nf .ta .25i .5i .75i 1i .ft CW .sp .. '\" # CE - end code excerpt .de CE .fi .RE .ft R .sp .. .de UL \\$1\l'|0\(ul'\\$2 .. .TH bgexec n BLT_VERSION BLT "BLT Built-In Commands" .BS '\" Note: do not modify the .SH NAME line immediately below! .SH NAME \fBbgexec\fR \- Run programs in the background while handling Tk events. .SH SYNOPSIS \fBbgexec \fIvarName\fR ?\fIoption value\fR?... \fIprogram\fR ?\fIarg\fR?... .BE .SH DESCRIPTION .PP The \fBbgexec\fR command executes programs in the background, allowing Tk to handle events. A global Tcl variable \fIvarName\fR is set when the program has completed. .SH INTRODUCTION Tcl's \fBexec\fR command is very useful for gathering information from the operating system. It runs a program and returns the output as its result. This works well for Tcl-only applications. But for Tk applications, a problem occurs when the program takes time to process. Let's say we want the get the disk usage of a directory. We'll use the Unix program \f(CWdu\fR to get the summary. .CS set out [exec du -s $dir] puts "Disk usage for $dir is $out" .CE While \f(CWdu\fR is running, scrollbars won't respond. None of the Tk widgets will be redrawn properly. The \fBsend\fR command won't work. And the worst part is that the application appears hung up or dead. The problem is that while \fBexec\fR is waiting for \fIdu\fR to finish, Tk is not able to handle X events. .PP The \fBbgexec\fR command performs the same functions as \fBexec\fR, but also allows Tk to handle events. You can execute a long-running program and the Tk widgets will behave normally. When the program finishes, its output and the exit status are written to Tcl variables. This makes it easy to monitor and save the output of a program. .SH EXAMPLE Here is the disk usage example again, this time using \fBbgexec\fR. The syntax to invoke "du" is exactly the same as the previous example, when we used \fBexec\fR. .CS global myStatus myOutput bgexec myStatus -output myOutput du -s $dir puts "Disk usage for $dir is $myOutput" .CE Two global variables, \f(CWmyStatus\fR and \f(CWmyOutput\fR, will be set by \fBbgexec\fR when \f(CWdu\fR has completed. \f(CWMyStatus\fR will contain the program's exit status. \f(CWMyOutput\fR, specified by the \fB\-output\fR option, will store the output of the program. .PP You can also terminate the program by setting the variable \f(CWmyStatus\fR. If \f(CWmyStatus\fR is set before \f(CWdu\fR has completed, the process is killed. Under Unix, this is done sending by a configurable signal (by default it's SIGKILL). Under Win32, this is done by calling \fBTerminateProcess\fR. It makes no difference what \f(CWmyStatus\fR is set to. .CS set myStatus {} .CE There are several \fBbgexec\fR options to collect different types of information. .CS global myStatus myOutput myErrs bgexec myStatus -output myOutput -error myErrs du -s $dir .CE The \fB\-error\fR option is similar to \fB\-output\fR. It sets a global variable when the program completes. The variable will contain any data written to stderr by the program. .PP The \fB\-output\fR and \fB\-error\fR variables are set only after the program completes. But if the program takes a long time, to run you may want to receive its partial output. You can gather data as it becomes available using the \fB\-onoutput\fR option. It specifies a Tcl command prefix. Whenever new data is available, this command is executed, with the data appended as an argument to the command. .CS proc GetInfo { data } { puts $data } bgexec myStatus -onoutput GetInfo du -s $dir .CE When output is available, the procedure \f(CWGetInfo\fR is called. The \fB\-onerror\fR option performs a similar function for the stderr data stream. .PP Like \fBexec\fR, \fBbgexec\fR returns an error if the exit code of the program is not zero. If you think you may get a non-zero exit code, you might want to invoke \fBbgexec\fR from within a \fBcatch\fR. .CS catch { bgexec myStatus -output myOutput du -s $dir } .CE By default, \fBbgexec\fR will wait for the program to finish. But you can detach the program making ampersand (&) the last argument on the command line. .CS global myStatus myOutput bgexec myStatus -output myOutput du -s $dir & .CE \fBBgexec\fR will return immediately and its result will be a list of the spawned process ids. If at some point you need to wait for the program to finish up, you can use \fBtkwait\fR. When the program finishes, the variable \f(CWmyStatus\fR will be written to, breaking out the \fBtkwait\fR command. .CS global myStatus myOutput bgexec myStatus -output myOutput du -s $dir & ... tkwait variable myStatus .CE .SH SYNTAX The \fBbgexec\fR command takes the following form: .sp \fB bgexec \fIvarName\fR ?\fIoption value\fR?... \fIprogram\fR ?\fIarg\fR?... .sp \fIVarName\fR is the name of a global variable which is set when \fIprogram\fR has finished executing. The exit status of will be stored in \fIvarName\fR. The exit status is a list of a status token, the process-id of the program, the exit code, and a status message. You can also prematurely terminate the program by setting \fIvarName\fR. Under Unix, the program will be sent a signal to terminate it (by default the signal is a SIGKILL; see the \fB\-killsignal\fR option). .PP \fIProgram\fR is the name of the program to be executed and \fIargs\fR are any extra arguments for \fIprogram\fR. The syntax of \fIprogram\fR and \fIargs\fR is the same as the \fBexec\fR command. So you can redirect I/O, execute pipelines, etc. (see the \fBexec\fR manual for further information) just like \fBexec\fR. If the last argument is an ampersand (&), the program will be run detached, and \fBbgexec\fR will return immediately. \fIVarName\fR will still be set with the return status when \fIprogram\fR completes. .SH OPTIONS \fIOption\fR refers to the switch name always beginning with a dash (\-). \fIValue\fR is the value of the option. Option-value pairs are terminated either by the program name, or double dashes (\-\-). The following options are available for \fBbgexec\fR: .TP \fB\-decodeerror \fIencodingName\fR .br Specifies the encoding of the stderr channel. This affects only data returned to the Tcl interpreter. No translation is done on file redirection. .br For example if data is to be converted from Unicode for use in Tcl, you would use the "unicode" encoding. The default is that no tranlation is performed. .TP \fB\-decodeoutput \fIencodingName\fR .br Specifies the encoding of the stdout channels. This affects only data returned to the Tcl interpreter. No translation is done on file redirection. .br For example if data is to be converted from Unicode for use in Tcl, you would use the "unicode" encoding. The default is that no tranlation is performed. .TP \fB\-error \fIvarName\fR .br Specifies that a global variable \fIvarName\fR is to be set with the contents of stderr after the program has completed. .TP \fB\-keepnewline \fIboolean\fR Specifies that a trailing newline should be retained in the output. If \fIboolean\fR is true, the trailing newline is truncated from the output of the \fB\-onoutput\fR and \fB\-output\fR variables. The default value is \f(CWtrue\fR. .TP \fB\-killsignal \fIsignal\fR Specifies the signal to be sent to the program when terminating. This is available only under Unix. \fISignal\fR can either be a number (typically 1-32) or a mnemonic (such as SIGINT). If \fIsignal\fR is the empty string, then no signal is sent. The default signal is \f(CW9\fR (SIGKILL). .TP \fB\-lasterror \fIvarName\fR Specifies a variable \fIvarName\fR that is updated whenever data becomes available from standard error of the program. \fIVarName\fR is a global variable. Unlike the \fB\-error\fR option, data is available as soon as it arrives. .TP \fB\-lastoutput \fIvarName\fR Specifies a variable \fIvarName\fR that is updated whenever data becomes available from standard output of the program. \fIVarName\fR is a global variable. Unlike the \fB\-output\fR option, data is available as soon as it arrives. .TP \fB\-linebuffered \fIboolean\fR Specifies that updates should be made on a line-by-line basis. Normally when new data is available \fBbgexec\fR will set the variable (\fB\-lastoutput\fR and \fB\-lasterror\fR options) or invoke the command (\fB\-onoutput\fR and \fB\-onerror\fR options) delivering all the new data currently available. If \fIboolean\fR is true, only one line at a time will be delivered. This can be useful when you want to process the output on a line-by-line basis. The default value is \f(CWfalse\fR. .TP \fB\-output \fIvarName\fR .br Specifies that a global variable \fIvarName\fR is to be set with the output of the program, once it has completed. If this option is not set, no output will be accumulated. .TP \fB\-onerror \fIcommand\fR Specifies the start of a Tcl command that will be executed whenever new data is available from standard error. The data is appended to the command as an extra argument before it is executed. .TP \fB\-onoutput \fIcommand\fR Specifies the start of a Tcl command that will be executed whenever new data is available from standard output. The data is appended to the command as an extra argument before it is executed. .TP \fB\-update \fIvarName\fR Deprecated. This option is replaced by \fB\-lasterror\fR. .TP \fB\-\|\-\fR This marks the end of the options. The following argument will be considered the name of a program even if it starts with a dash (\fB\-\fR). .SH PREEMPTION Because \fBbgexec\fR allows Tk to handle events while a program is running, it's possible for an application to preempt itself with further user-interactions. Let's say your application has a button that runs the disk usage example. And while the \f(CWdu\fR program is running, the user accidently presses the button again. A second \fBbgexec\fR program will preempt the first. What this means is that the first program can not finish until the second program has completed. .PP Care must be taken to prevent an application from preempting itself by blocking further user-interactions (such as button clicks). The BLT \fBbusy\fR command is very useful for just these situations. See the \fBbusy\fR manual for details. .SH DIFFERENCES WITH FILEEVENT Since Tk 4.0, a subset of \fBbgexec\fR can be also achieved using the \fBfileevent\fR command. The steps for running a program in the background are: .PP Execute the program with the \fBopen\fR command (using the "|" syntax) and save the file handle. .CS global fileId set fileId [open "|du -s $dir" r] .CE Next register a Tcl code snippet with \fBfileevent\fR to be run whenever output is available on the file handle. The code snippet will read from the file handle and save the output in a variable. .CS fileevent fileId readable { if { [gets $fileId line] < 0 } { close $fileId set output $temp unset fileId temp } else { append temp $line } } .CE .PP The biggest advantage of \fBbgexec\fR is that, unlike \fBfileevent\fR, it requires no additional Tcl code to run a program. It's simpler and less error prone. You don't have to worry about non-blocking I/O. It's handled tranparently for you. .PP \fBBgexec\fR runs programs that \fBfileevent\fR can not. \fBFileevent\fR assumes that the when stdout is closed the program has completed. But some programs, like the Unix \f(CWcompress\fR program, reopen stdout, fooling \fBfileevent\fR into thinking the program has terminated. In the example above, we assume that the program will write and flush its output line-by-line. However running another program, your application may block in the \fBgets\fR command reading a partial line. .PP \fBBgexec\fR lets you get back the exit status of the program. It also allows you to collect data from both stdout and stderr simultaneously. Finally, since data collection is handled in C code, \fBbgexec\fR is faster. You get back to the Tk event loop more quickly, making your application seem more responsive. .SH SEE ALSO busy, exec, tkwait .SH KEYWORDS exec, background, busy } close $f } #-------------------------------------------------------------------------------