summaryrefslogtreecommitdiffstats
path: root/contrib/tcl/generic/tclIO.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/tcl/generic/tclIO.c')
-rw-r--r--contrib/tcl/generic/tclIO.c348
1 files changed, 289 insertions, 59 deletions
diff --git a/contrib/tcl/generic/tclIO.c b/contrib/tcl/generic/tclIO.c
index 0c54c12..f501357 100644
--- a/contrib/tcl/generic/tclIO.c
+++ b/contrib/tcl/generic/tclIO.c
@@ -9,7 +9,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * SCCS: @(#) tclIO.c 1.211 96/04/18 09:59:06
+ * SCCS: @(#) tclIO.c 1.227 96/07/30 09:26:30
*/
#include "tclInt.h"
@@ -203,6 +203,13 @@ typedef struct Channel {
#define INPUT_SAW_CR (1<<12) /* Channel is in CRLF eol input
* translation mode and the last
* byte seen was a "\r". */
+#define CHANNEL_DEAD (1<<13) /* The channel has been closed by
+ * the exit handler (on exit) but
+ * not deallocated. When any IO
+ * operation sees this flag on a
+ * channel, it does not call driver
+ * level functions to avoid referring
+ * to deallocated data. */
/*
* For each channel handler registered in a call to Tcl_CreateChannelHandler,
@@ -282,13 +289,6 @@ typedef struct ChannelHandlerEvent {
} ChannelHandlerEvent;
/*
- * Static buffer used to sprintf channel option values and return
- * them to the caller.
- */
-
-static char optionVal[128];
-
-/*
* Static variables to hold channels for stdin, stdout and stderr.
*/
@@ -315,6 +315,8 @@ static void ChannelHandlerSetupProc _ANSI_ARGS_((
ClientData clientData, int flags));
static void ChannelEventScriptInvoker _ANSI_ARGS_((
ClientData clientData, int flags));
+static void CleanupChannelHandlers _ANSI_ARGS_((
+ Tcl_Interp *interp, Channel *chanPtr));
static int CloseChannel _ANSI_ARGS_((Tcl_Interp *interp,
Channel *chanPtr, int errorCode));
static void CloseChannelsOnExit _ANSI_ARGS_((ClientData data));
@@ -352,6 +354,50 @@ static int ScanInputForEOL _ANSI_ARGS_((Channel *chanPtr,
/*
*----------------------------------------------------------------------
*
+ * TclFindChannel --
+ *
+ * Finds a channel given two Tcl_Files.
+ *
+ * Results:
+ * The Tcl_Channel found. Also returns nonzero in fileUsedPtr output
+ * parameter if it finds that the Tcl_File is already used in another
+ * channel.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Channel
+TclFindFileChannel(inFile, outFile, fileUsedPtr)
+ Tcl_File inFile, outFile; /* Channel has these Tcl_Files. */
+ int *fileUsedPtr;
+{
+ Channel *chanPtr;
+
+ *fileUsedPtr = 0;
+ for (chanPtr = firstChanPtr;
+ chanPtr != (Channel *) NULL;
+ chanPtr = chanPtr->nextChanPtr) {
+ if ((chanPtr->inFile == inFile) && (chanPtr->outFile == outFile)) {
+ return (Tcl_Channel) chanPtr;
+ }
+ if ((inFile != (Tcl_File) NULL) && (chanPtr->inFile == inFile)) {
+ *fileUsedPtr = 1;
+ return (Tcl_Channel) NULL;
+ }
+ if ((outFile != (Tcl_File) NULL) && (chanPtr->outFile == outFile)) {
+ *fileUsedPtr = 1;
+ return (Tcl_Channel) NULL;
+ }
+ }
+ return (Tcl_Channel) NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* Tcl_SetStdChannel --
*
* This function is used to change the channels that are used
@@ -373,7 +419,7 @@ Tcl_SetStdChannel(channel, type)
{
switch (type) {
case TCL_STDIN:
- stdinInitialized = 1;
+ stdinInitialized = 1;
stdinChannel = channel;
break;
case TCL_STDOUT:
@@ -564,22 +610,48 @@ CloseChannelsOnExit(clientData)
nextChanPtr = chanPtr->nextChanPtr;
/*
- * Close it only if the refcount indicates that the channel is not
- * referenced from any interpreter. If it is, that interpreter will
- * close the channel when it gets destroyed.
+ * Set the channel back into blocking mode to ensure that we wait
+ * for all data to flush out.
*/
+
+ (void) Tcl_SetChannelOption(NULL, (Tcl_Channel) chanPtr,
+ "-blocking", "on");
if (chanPtr->refCount <= 0) {
-
+
+ /*
+ * Close it only if the refcount indicates that the channel is not
+ * referenced from any interpreter. If it is, that interpreter will
+ * close the channel when it gets destroyed.
+ */
+
+ Tcl_Close((Tcl_Interp *) NULL, (Tcl_Channel) chanPtr);
+ } else {
+
/*
- * Switch the channel back into synchronous mode to ensure that it
- * gets flushed now.
+ * The refcount is greater than zero, so flush the channel.
*/
- (void) Tcl_SetChannelOption(NULL, (Tcl_Channel) chanPtr,
- "-blocking", "on");
+ Tcl_Flush((Tcl_Channel) chanPtr);
- Tcl_Close((Tcl_Interp *) NULL, (Tcl_Channel) chanPtr);
+ /*
+ * And close the OS level handles using the driver function:
+ */
+
+ (chanPtr->typePtr->closeProc) (chanPtr->instanceData,
+ (Tcl_Interp *) NULL, chanPtr->inFile, chanPtr->outFile);
+
+ /*
+ * Finally, we clean up the fields in the channel data structure
+ * since all of them have been deleted already. We mark the
+ * channel with CHANNEL_DEAD to prevent any further IO operations
+ * on it.
+ */
+
+ chanPtr->inFile = (Tcl_File) NULL;
+ chanPtr->outFile = (Tcl_File) NULL;
+ chanPtr->instanceData = (ClientData) NULL;
+ chanPtr->flags |= CHANNEL_DEAD;
}
}
}
@@ -609,7 +681,7 @@ GetChannelTable(interp)
Tcl_Interp *interp;
{
Tcl_HashTable *hTblPtr; /* Hash table of channels. */
- Tcl_Channel stdinChannel, stdoutChannel, stderrChannel;
+ Tcl_Channel stdinChan, stdoutChan, stderrChan;
hTblPtr = (Tcl_HashTable *) Tcl_GetAssocData(interp, "tclIO", NULL);
if (hTblPtr == (Tcl_HashTable *) NULL) {
@@ -627,17 +699,17 @@ GetChannelTable(interp)
*/
if (Tcl_IsSafe(interp) == 0) {
- stdinChannel = Tcl_GetStdChannel(TCL_STDIN);
- if (stdinChannel != NULL) {
- Tcl_RegisterChannel(interp, stdinChannel);
+ stdinChan = Tcl_GetStdChannel(TCL_STDIN);
+ if (stdinChan != NULL) {
+ Tcl_RegisterChannel(interp, stdinChan);
}
- stdoutChannel = Tcl_GetStdChannel(TCL_STDOUT);
- if (stdoutChannel != NULL) {
- Tcl_RegisterChannel(interp, stdoutChannel);
+ stdoutChan = Tcl_GetStdChannel(TCL_STDOUT);
+ if (stdoutChan != NULL) {
+ Tcl_RegisterChannel(interp, stdoutChan);
}
- stderrChannel = Tcl_GetStdChannel(TCL_STDERR);
- if (stderrChannel != NULL) {
- Tcl_RegisterChannel(interp, stderrChannel);
+ stderrChan = Tcl_GetStdChannel(TCL_STDERR);
+ if (stderrChan != NULL) {
+ Tcl_RegisterChannel(interp, stderrChan);
}
}
@@ -776,8 +848,29 @@ Tcl_UnregisterChannel(interp, chan)
return TCL_OK;
}
Tcl_DeleteHashEntry(hPtr);
+
+ /*
+ * Remove channel handlers that refer to this interpreter, so that they
+ * will not be present if the actual close is delayed and more events
+ * happen on the channel. This may occur if the channel is shared between
+ * several interpreters, or if the channel has async flushing active.
+ */
+
+ CleanupChannelHandlers(interp, chanPtr);
+
chanPtr->refCount--;
if (chanPtr->refCount <= 0) {
+
+ /*
+ * Ensure that if there is another buffer, it gets flushed
+ * whether or not we are doing a background flush.
+ */
+
+ if ((chanPtr->curOutPtr != NULL) &&
+ (chanPtr->curOutPtr->nextAdded >
+ chanPtr->curOutPtr->nextRemoved)) {
+ chanPtr->flags |= BUFFER_READY;
+ }
chanPtr->flags |= CHANNEL_CLOSED;
if (!(chanPtr->flags & BG_FLUSH_SCHEDULED)) {
if (Tcl_Close(interp, chan) != TCL_OK) {
@@ -995,7 +1088,7 @@ Tcl_CreateChannel(typePtr, chanName, inFile, outFile, instanceData)
channelExitHandlerCreated = 1;
Tcl_CreateExitHandler(CloseChannelsOnExit, (ClientData) NULL);
}
-
+
/*
* Install this channel in the first empty standard channel slot.
*/
@@ -1272,6 +1365,18 @@ FlushChannel(interp, chanPtr, calledFromAsyncFlush)
* channel driver operations. */
errorCode = 0;
+
+ /*
+ * Prevent writing on a dead channel -- a channel that has been closed
+ * but not yet deallocated. This can occur if the exit handler for the
+ * channel deallocation runs before all channels are deregistered in
+ * all interpreters.
+ */
+
+ if (chanPtr->flags & CHANNEL_DEAD) {
+ Tcl_SetErrno(EINVAL);
+ return -1;
+ }
/*
* Loop over the queued buffers and attempt to flush as
@@ -1342,6 +1447,7 @@ FlushChannel(interp, chanPtr, calledFromAsyncFlush)
*/
if (errorCode == EINTR) {
+ errorCode = 0;
continue;
}
@@ -1370,6 +1476,7 @@ FlushChannel(interp, chanPtr, calledFromAsyncFlush)
*/
TclWaitForFile(chanPtr->outFile, TCL_WRITABLE, -1);
+ errorCode = 0;
continue;
}
}
@@ -1464,12 +1571,25 @@ CloseChannel(interp, chanPtr, errorCode)
Channel *chanPtr; /* The channel to close. */
int errorCode; /* Status of operation so far. */
{
- int result; /* Of calling driver close
+ int result = 0; /* Of calling driver close
* operation. */
Channel *prevChanPtr; /* Preceding channel in list of
* all channels - used to splice a
* channel out of the list on close. */
+
+ /*
+ * Remove the channel from the standard channel table.
+ */
+
+ if (Tcl_GetStdChannel(TCL_STDIN) == (Tcl_Channel) chanPtr) {
+ Tcl_SetStdChannel(NULL, TCL_STDIN);
+ } else if (Tcl_GetStdChannel(TCL_STDOUT) == (Tcl_Channel) chanPtr) {
+ Tcl_SetStdChannel(NULL, TCL_STDOUT);
+ } else if (Tcl_GetStdChannel(TCL_STDERR) == (Tcl_Channel) chanPtr) {
+ Tcl_SetStdChannel(NULL, TCL_STDERR);
+ }
+
/*
* No more input can be consumed so discard any leftover input.
*/
@@ -1504,8 +1624,10 @@ CloseChannel(interp, chanPtr, errorCode)
char c;
c = (char) chanPtr->outEofChar;
- (chanPtr->typePtr->outputProc) (chanPtr->instanceData,
- chanPtr->outFile, &c, 1, &dummy);
+ if (!(chanPtr->flags & CHANNEL_DEAD)) {
+ (chanPtr->typePtr->outputProc) (chanPtr->instanceData,
+ chanPtr->outFile, &c, 1, &dummy);
+ }
}
/*
@@ -1537,16 +1659,17 @@ CloseChannel(interp, chanPtr, errorCode)
prevChanPtr->nextChanPtr = chanPtr->nextChanPtr;
}
- if (chanPtr->channelName != (char *) NULL) {
- ckfree(chanPtr->channelName);
- }
-
/*
* OK, close the channel itself.
*/
- result = (chanPtr->typePtr->closeProc) (chanPtr->instanceData, interp,
- chanPtr->inFile, chanPtr->outFile);
+ if (!(chanPtr->flags & CHANNEL_DEAD)) {
+ result = (chanPtr->typePtr->closeProc) (chanPtr->instanceData, interp,
+ chanPtr->inFile, chanPtr->outFile);
+ }
+ if (chanPtr->channelName != (char *) NULL) {
+ ckfree(chanPtr->channelName);
+ }
/*
* If we are being called synchronously, report either
@@ -1610,18 +1733,6 @@ Tcl_Close(interp, chan)
if (chanPtr->refCount > 0) {
panic("called Tcl_Close on channel with refcount > 0");
}
-
- /*
- * Remove the channel from the standard channel table.
- */
-
- if (Tcl_GetStdChannel(TCL_STDIN) == chan) {
- Tcl_SetStdChannel(NULL, TCL_STDIN);
- } else if (Tcl_GetStdChannel(TCL_STDOUT) == chan) {
- Tcl_SetStdChannel(NULL, TCL_STDOUT);
- } else if (Tcl_GetStdChannel(TCL_STDERR) == chan) {
- Tcl_SetStdChannel(NULL, TCL_STDERR);
- }
/*
* Remove all the channel handler records attached to the channel
@@ -2066,6 +2177,18 @@ GetInput(chanPtr)
ChannelBuffer *bufPtr; /* New buffer to add to input queue. */
/*
+ * Prevent reading from a dead channel -- a channel that has been closed
+ * but not yet deallocated, which can happen if the exit handler for
+ * channel cleanup has run but the channel is still registered in some
+ * interpreter.
+ */
+
+ if (chanPtr->flags & CHANNEL_DEAD) {
+ Tcl_SetErrno(EINVAL);
+ return -1;
+ }
+
+ /*
* See if we can fill an existing buffer. If we can, read only
* as much as will fit in it. Otherwise allocate a new buffer,
* add it to the input queue and attempt to fill it to the max.
@@ -2894,6 +3017,18 @@ Tcl_Seek(chan, offset, mode)
}
/*
+ * Disallow seek on dead channels -- channels that have been closed but
+ * not yet been deallocated. Such channels can be found if the exit
+ * handler for channel cleanup has run but the channel is still
+ * registered in an interpreter.
+ */
+
+ if (chanPtr->flags & CHANNEL_DEAD) {
+ Tcl_SetErrno(EINVAL);
+ return -1;
+ }
+
+ /*
* Disallow seek on channels whose type does not have a seek procedure
* defined. This means that the channel does not support seeking.
*/
@@ -3070,6 +3205,18 @@ Tcl_Tell(chan)
}
/*
+ * Disallow tell on dead channels -- channels that have been closed but
+ * not yet been deallocated. Such channels can be found if the exit
+ * handler for channel cleanup has run but the channel is still
+ * registered in an interpreter.
+ */
+
+ if (chanPtr->flags & CHANNEL_DEAD) {
+ Tcl_SetErrno(EINVAL);
+ return -1;
+ }
+
+ /*
* Disallow tell on channels that are open for neither
* writing nor reading (e.g. socket server channels).
*/
@@ -3316,10 +3463,23 @@ Tcl_GetChannelOption(chan, optionName, dsPtr)
{
Channel *chanPtr; /* The real IO channel. */
size_t len; /* Length of optionName string. */
+ char optionVal[128]; /* Buffer for sprintf. */
chanPtr = (Channel *) chan;
/*
+ * Disallow options on dead channels -- channels that have been closed but
+ * not yet been deallocated. Such channels can be found if the exit
+ * handler for channel cleanup has run but the channel is still
+ * registered in an interpreter.
+ */
+
+ if (chanPtr->flags & CHANNEL_DEAD) {
+ Tcl_SetErrno(EINVAL);
+ return TCL_ERROR;
+ }
+
+ /*
* If the optionName is NULL it means that we want a list of all
* options and values.
*/
@@ -3374,8 +3534,8 @@ Tcl_GetChannelOption(chan, optionName, dsPtr)
if (len == 0) {
Tcl_DStringAppendElement(dsPtr, "-eofchar");
}
- if ((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
- (TCL_READABLE|TCL_WRITABLE)) {
+ if (((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
+ (TCL_READABLE|TCL_WRITABLE)) && (len == 0)) {
Tcl_DStringStartSublist(dsPtr);
}
if (chanPtr->flags & TCL_READABLE) {
@@ -3398,8 +3558,8 @@ Tcl_GetChannelOption(chan, optionName, dsPtr)
Tcl_DStringAppendElement(dsPtr, buf);
}
}
- if ((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
- (TCL_READABLE|TCL_WRITABLE)) {
+ if (((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
+ (TCL_READABLE|TCL_WRITABLE)) && (len == 0)) {
Tcl_DStringEndSublist(dsPtr);
}
if (len > 0) {
@@ -3412,8 +3572,8 @@ Tcl_GetChannelOption(chan, optionName, dsPtr)
if (len == 0) {
Tcl_DStringAppendElement(dsPtr, "-translation");
}
- if ((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
- (TCL_READABLE|TCL_WRITABLE)) {
+ if (((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
+ (TCL_READABLE|TCL_WRITABLE)) && (len == 0)) {
Tcl_DStringStartSublist(dsPtr);
}
if (chanPtr->flags & TCL_READABLE) {
@@ -3438,8 +3598,8 @@ Tcl_GetChannelOption(chan, optionName, dsPtr)
Tcl_DStringAppendElement(dsPtr, "lf");
}
}
- if ((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
- (TCL_READABLE|TCL_WRITABLE)) {
+ if (((chanPtr->flags & (TCL_READABLE|TCL_WRITABLE)) ==
+ (TCL_READABLE|TCL_WRITABLE)) && (len == 0)) {
Tcl_DStringEndSublist(dsPtr);
}
if (len > 0) {
@@ -3489,6 +3649,18 @@ Tcl_SetChannelOption(interp, chan, optionName, newValue)
char **argv;
chanPtr = (Channel *) chan;
+
+ /*
+ * Disallow options on dead channels -- channels that have been closed but
+ * not yet been deallocated. Such channels can be found if the exit
+ * handler for channel cleanup has run but the channel is still
+ * registered in an interpreter.
+ */
+
+ if (chanPtr->flags & CHANNEL_DEAD) {
+ Tcl_SetErrno(EINVAL);
+ return -1;
+ }
len = strlen(optionName);
@@ -3770,6 +3942,61 @@ Tcl_SetChannelOption(interp, chan, optionName, newValue)
/*
*----------------------------------------------------------------------
*
+ * CleanupChannelHandlers --
+ *
+ * Removes channel handlers that refer to the supplied interpreter,
+ * so that if the actual channel is not closed now, these handlers
+ * will not run on subsequent events on the channel. This would be
+ * erroneous, because the interpreter no longer has a reference to
+ * this channel.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Removes channel handlers.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+CleanupChannelHandlers(interp, chanPtr)
+ Tcl_Interp *interp;
+ Channel *chanPtr;
+{
+ EventScriptRecord *sPtr, *prevPtr, *nextPtr;
+
+ /*
+ * Remove fileevent records on this channel that refer to the
+ * given interpreter.
+ */
+
+ for (sPtr = chanPtr->scriptRecordPtr,
+ prevPtr = (EventScriptRecord *) NULL;
+ sPtr != (EventScriptRecord *) NULL;
+ sPtr = nextPtr) {
+ nextPtr = sPtr->nextPtr;
+ if (sPtr->interp == interp) {
+ if (prevPtr == (EventScriptRecord *) NULL) {
+ chanPtr->scriptRecordPtr = nextPtr;
+ } else {
+ prevPtr->nextPtr = nextPtr;
+ }
+
+ Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr,
+ ChannelEventScriptInvoker, (ClientData) sPtr);
+
+ Tcl_EventuallyFree((ClientData) sPtr->script, TCL_DYNAMIC);
+ ckfree((char *) sPtr);
+ } else {
+ prevPtr = sPtr;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* ChannelEventSourceExitProc --
*
* This procedure is called during exit cleanup to delete the channel
@@ -4464,11 +4691,14 @@ ChannelEventScriptInvoker(clientData, mask)
/*
* On error, cause a background error and remove the channel handler
* and the script record.
+ *
+ * NOTE: Must delete channel handler before causing the background error
+ * because the background error may want to reinstall the handler.
*/
if (result != TCL_OK) {
- Tcl_BackgroundError(interp);
DeleteScriptRecord(interp, chanPtr, mask);
+ Tcl_BackgroundError(interp);
}
Tcl_Release((ClientData) chanPtr);
Tcl_Release((ClientData) script);
OpenPOWER on IntegriCloud