diff options
Diffstat (limited to 'generic/tclIO.c')
| -rw-r--r-- | generic/tclIO.c | 4746 |
1 files changed, 1971 insertions, 2775 deletions
diff --git a/generic/tclIO.c b/generic/tclIO.c index d3842ef..7bc849e 100644 --- a/generic/tclIO.c +++ b/generic/tclIO.c @@ -6,6 +6,7 @@ * * Copyright (c) 1998-2000 Ajuba Solutions * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Contributions from Don Porter, NIST, 2014. (not subject to US copyright) * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -16,6 +17,108 @@ #include <assert.h> /* + * For each channel handler registered in a call to Tcl_CreateChannelHandler, + * there is one record of the following type. All of records for a specific + * channel are chained together in a singly linked list which is stored in + * the channel structure. + */ + +typedef struct ChannelHandler { + Channel *chanPtr; /* The channel structure for this channel. */ + int mask; /* Mask of desired events. */ + Tcl_ChannelProc *proc; /* Procedure to call in the type of + * Tcl_CreateChannelHandler. */ + ClientData clientData; /* Argument to pass to procedure. */ + struct ChannelHandler *nextPtr; + /* Next one in list of registered handlers. */ +} ChannelHandler; + +/* + * This structure keeps track of the current ChannelHandler being invoked in + * the current invocation of ChannelHandlerEventProc. There is a potential + * problem if a ChannelHandler is deleted while it is the current one, since + * ChannelHandlerEventProc needs to look at the nextPtr field. To handle this + * problem, structures of the type below indicate the next handler to be + * processed for any (recursively nested) dispatches in progress. The + * nextHandlerPtr field is updated if the handler being pointed to is deleted. + * The nextPtr field is used to chain together all recursive invocations, so + * that Tcl_DeleteChannelHandler can find all the recursively nested + * invocations of ChannelHandlerEventProc and compare the handler being + * deleted against the NEXT handler to be invoked in that invocation; when it + * finds such a situation, Tcl_DeleteChannelHandler updates the nextHandlerPtr + * field of the structure to the next handler. + */ + +typedef struct NextChannelHandler { + ChannelHandler *nextHandlerPtr; /* The next handler to be invoked in + * this invocation. */ + struct NextChannelHandler *nestedHandlerPtr; + /* Next nested invocation of + * ChannelHandlerEventProc. */ +} NextChannelHandler; + +/* + * The following structure describes the event that is added to the Tcl + * event queue by the channel handler check procedure. + */ + +typedef struct ChannelHandlerEvent { + Tcl_Event header; /* Standard header for all events. */ + Channel *chanPtr; /* The channel that is ready. */ + int readyMask; /* Events that have occurred. */ +} ChannelHandlerEvent; + +/* + * The following structure is used by Tcl_GetsObj() to encapsulates the + * state for a "gets" operation. + */ + +typedef struct GetsState { + Tcl_Obj *objPtr; /* The object to which UTF-8 characters + * will be appended. */ + char **dstPtr; /* Pointer into objPtr's string rep where + * next character should be stored. */ + Tcl_Encoding encoding; /* The encoding to use to convert raw bytes + * to UTF-8. */ + ChannelBuffer *bufPtr; /* The current buffer of raw bytes being + * emptied. */ + Tcl_EncodingState state; /* The encoding state just before the last + * external to UTF-8 conversion in + * FilterInputBytes(). */ + int rawRead; /* The number of bytes removed from bufPtr + * in the last call to FilterInputBytes(). */ + int bytesWrote; /* The number of bytes of UTF-8 data + * appended to objPtr during the last call to + * FilterInputBytes(). */ + int charsWrote; /* The corresponding number of UTF-8 + * characters appended to objPtr during the + * last call to FilterInputBytes(). */ + int totalChars; /* The total number of UTF-8 characters + * appended to objPtr so far, just before the + * last call to FilterInputBytes(). */ +} GetsState; + +/* + * The following structure encapsulates the state for a background channel + * copy. Note that the data buffer for the copy will be appended to this + * structure. + */ + +typedef struct CopyState { + struct Channel *readPtr; /* Pointer to input channel. */ + struct Channel *writePtr; /* Pointer to output channel. */ + int readFlags; /* Original read channel flags. */ + int writeFlags; /* Original write channel flags. */ + int toRead; /* Number of bytes to copy, or -1. */ + Tcl_WideInt total; /* Total bytes transferred (written). */ + Tcl_Interp *interp; /* Interp that started the copy. */ + Tcl_Obj *cmdPtr; /* Command to be invoked at completion. */ + int bufSize; /* Size of appended buffer. */ + char buffer[1]; /* Copy buffer, this must be the last + * field. */ +} CopyState; + +/* * All static variables used in this file are collected into a single instance * of the following structure. For multi-threaded implementations, there is * one instance of this structure for each thread. @@ -44,15 +147,30 @@ typedef struct ThreadSpecificData { static Tcl_ThreadDataKey dataKey; /* + * Structure to record a close callback. One such record exists for + * each close callback registered for a channel. + */ + +typedef struct CloseCallback { + Tcl_CloseProc *proc; /* The procedure to call. */ + ClientData clientData; /* Arbitrary one-word data to pass + * to the callback. */ + struct CloseCallback *nextPtr; /* For chaining close callbacks. */ +} CloseCallback; + +/* * Static functions in this file: */ static ChannelBuffer * AllocChannelBuffer(int length); +static void PreserveChannelBuffer(ChannelBuffer *bufPtr); +static void ReleaseChannelBuffer(ChannelBuffer *bufPtr); +static int IsShared(ChannelBuffer *bufPtr); +static void ChannelFree(Channel *chanPtr); static void ChannelTimerProc(ClientData clientData); +static int ChanRead(Channel *chanPtr, char *dst, int dstSize); static int CheckChannelErrors(ChannelState *statePtr, int direction); -static int CheckFlush(Channel *chanPtr, ChannelBuffer *bufPtr, - int newlineFlag); static int CheckForDeadChannel(Tcl_Interp *interp, ChannelState *statePtr); static void CheckForStdChannelsBeingClosed(Tcl_Channel chan); @@ -60,14 +178,7 @@ static void CleanupChannelHandlers(Tcl_Interp *interp, Channel *chanPtr); static int CloseChannel(Tcl_Interp *interp, Channel *chanPtr, int errorCode); -static int CloseChannelPart(Tcl_Interp *interp, Channel *chanPtr, - int errorCode, int flags); -static int CloseWrite(Tcl_Interp *interp, Channel *chanPtr); static void CommonGetsCleanup(Channel *chanPtr); -static size_t CopyAndTranslateBuffer(ChannelState *statePtr, - char *result, size_t space); -static size_t CopyBuffer(Channel *chanPtr, char *result, - size_t space); static int CopyData(CopyState *csPtr, int mask); static void CopyEventProc(ClientData clientData, int mask); static void CreateScriptRecord(Tcl_Interp *interp, @@ -80,19 +191,15 @@ static int DetachChannel(Tcl_Interp *interp, Tcl_Channel chan); static void DiscardInputQueued(ChannelState *statePtr, int discardSavedBuffers); static void DiscardOutputQueued(ChannelState *chanPtr); -static ssize_t DoRead(Channel *chanPtr, char *srcPtr, size_t slen, - int allowShortReads); -static ssize_t DoWrite(Channel *chanPtr, const char *src, - size_t srcLen); -static ssize_t DoReadChars(Channel *chan, Tcl_Obj *objPtr, - size_t toRead, int appendFlag); -static ssize_t DoWriteChars(Channel *chan, const char *src, - size_t len); +static int DoRead(Channel *chanPtr, char *dst, int bytesToRead); +static int DoReadChars(Channel *chan, Tcl_Obj *objPtr, int toRead, + int appendFlag); static int FilterInputBytes(Channel *chanPtr, GetsState *statePtr); static int FlushChannel(Tcl_Interp *interp, Channel *chanPtr, int calledFromAsyncFlush); -static ssize_t TclGetsObjBinary(Tcl_Channel chan, Tcl_Obj *objPtr); +static int TclGetsObjBinary(Tcl_Channel chan, Tcl_Obj *objPtr); +static Tcl_Encoding GetBinaryEncoding(); static void FreeBinaryEncoding(ClientData clientData); static Tcl_HashTable * GetChannelTable(Tcl_Interp *interp); static int GetInput(Channel *chanPtr); @@ -100,32 +207,30 @@ static int HaveVersion(const Tcl_ChannelType *typePtr, Tcl_ChannelTypeVersion minimumVersion); static void PeekAhead(Channel *chanPtr, char **dstEndPtr, GetsState *gsPtr); -static ssize_t ReadBytes(ChannelState *statePtr, Tcl_Obj *objPtr, - size_t charsLeft, size_t *offsetPtr); -static ssize_t ReadChars(ChannelState *statePtr, Tcl_Obj *objPtr, - size_t charsLeft, size_t *offsetPtr, - size_t *factorPtr); +static int ReadBytes(ChannelState *statePtr, Tcl_Obj *objPtr, + int charsLeft); +static int ReadChars(ChannelState *statePtr, Tcl_Obj *objPtr, + int charsLeft, int *factorPtr); static void RecycleBuffer(ChannelState *statePtr, ChannelBuffer *bufPtr, int mustDiscard); static int StackSetBlockMode(Channel *chanPtr, int mode); static int SetBlockMode(Tcl_Interp *interp, Channel *chanPtr, int mode); static void StopCopy(CopyState *csPtr); -static int TranslateInputEOL(ChannelState *statePtr, char *dst, - const char *src, size_t *dstLenPtr, - size_t *srcLenPtr); -static int TranslateOutputEOL(ChannelState *statePtr, char *dst, - const char *src, size_t *dstLenPtr, - size_t *srcLenPtr); +static void TranslateInputEOL(ChannelState *statePtr, char *dst, + const char *src, int *dstLenPtr, int *srcLenPtr); static void UpdateInterest(Channel *chanPtr); -static ssize_t WriteBytes(Channel *chanPtr, const char *src, - size_t srcLen); -static ssize_t WriteChars(Channel *chanPtr, const char *src, - size_t srcLen); +static int Write(Channel *chanPtr, const char *src, + int srcLen, Tcl_Encoding encoding); static Tcl_Obj * FixLevelCode(Tcl_Obj *msg); static void SpliceChannel(Tcl_Channel chan); static void CutChannel(Tcl_Channel chan); -static inline int WillRead(Channel *chanPtr); +static int WillRead(Channel *chanPtr); + +#define WriteChars(chanPtr, src, srcLen) \ + Write(chanPtr, src, srcLen, chanPtr->state->encoding) +#define WriteBytes(chanPtr, src, srcLen) \ + Write(chanPtr, src, srcLen, tclIdentityEncoding) /* * Simplifying helper macros. All may use their argument(s) multiple times. @@ -133,11 +238,11 @@ static inline int WillRead(Channel *chanPtr); * short description of what the macro does. * * -------------------------------------------------------------------------- - * size_t BytesLeft(ChannelBuffer *bufPtr) + * int BytesLeft(ChannelBuffer *bufPtr) * * Returns the number of bytes of data remaining in the buffer. * - * ssize_t SpaceLeft(ChannelBuffer *bufPtr) + * int SpaceLeft(ChannelBuffer *bufPtr) * * Returns the number of bytes of space remaining at the end of the * buffer. @@ -171,21 +276,21 @@ static inline int WillRead(Channel *chanPtr); * -------------------------------------------------------------------------- */ -#define BytesLeft(bufPtr) ((bufPtr)->nextAdded - (bufPtr)->nextRemoved) +#define BytesLeft(bufPtr) ((bufPtr)->nextAdded - (bufPtr)->nextRemoved) -#define SpaceLeft(bufPtr) ((bufPtr)->bufLength - (bufPtr)->nextAdded) +#define SpaceLeft(bufPtr) ((bufPtr)->bufLength - (bufPtr)->nextAdded) -#define IsBufferReady(bufPtr) ((bufPtr)->nextAdded > (bufPtr)->nextRemoved) +#define IsBufferReady(bufPtr) ((bufPtr)->nextAdded > (bufPtr)->nextRemoved) -#define IsBufferEmpty(bufPtr) ((bufPtr)->nextAdded == (bufPtr)->nextRemoved) +#define IsBufferEmpty(bufPtr) ((bufPtr)->nextAdded == (bufPtr)->nextRemoved) -#define IsBufferFull(bufPtr) ((bufPtr)->nextAdded >= (bufPtr)->bufLength) +#define IsBufferFull(bufPtr) ((bufPtr) && (bufPtr)->nextAdded >= (bufPtr)->bufLength) -#define IsBufferOverflowing(bufPtr) ((bufPtr)->nextAdded>(bufPtr)->bufLength) +#define IsBufferOverflowing(bufPtr) ((bufPtr)->nextAdded > (bufPtr)->bufLength) -#define InsertPoint(bufPtr) ((bufPtr)->buf + (bufPtr)->nextAdded) +#define InsertPoint(bufPtr) ((bufPtr)->buf + (bufPtr)->nextAdded) -#define RemovePoint(bufPtr) ((bufPtr)->buf + (bufPtr)->nextRemoved) +#define RemovePoint(bufPtr) ((bufPtr)->buf + (bufPtr)->nextRemoved) /* * For working with channel state flag bits. @@ -214,29 +319,27 @@ static inline int WillRead(Channel *chanPtr); */ static void DupChannelIntRep(Tcl_Obj *objPtr, Tcl_Obj *copyPtr); -static int SetChannelFromAny(Tcl_Interp *interp, - Tcl_Obj *objPtr); -static void UpdateStringOfChannel(Tcl_Obj *objPtr); +static int SetChannelFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void FreeChannelIntRep(Tcl_Obj *objPtr); -static const Tcl_ObjType tclChannelType = { +static Tcl_ObjType chanObjType = { "channel", /* name for this type */ FreeChannelIntRep, /* freeIntRepProc */ DupChannelIntRep, /* dupIntRepProc */ - NULL, /* updateStringProc UpdateStringOfChannel */ + NULL, /* updateStringProc */ NULL /* setFromAnyProc SetChannelFromAny */ }; #define GET_CHANNELSTATE(objPtr) \ - ((ChannelState *) (objPtr)->internalRep.otherValuePtr) + ((ChannelState *) (objPtr)->internalRep.twoPtrValue.ptr1) #define SET_CHANNELSTATE(objPtr, storePtr) \ - ((objPtr)->internalRep.otherValuePtr = (void *) (storePtr)) + ((objPtr)->internalRep.twoPtrValue.ptr1 = (void *) (storePtr)) #define GET_CHANNELINTERP(objPtr) \ - ((Interp *) (objPtr)->internalRep.twoPtrValue.ptr2) + ((Tcl_Interp *) (objPtr)->internalRep.twoPtrValue.ptr2) #define SET_CHANNELINTERP(objPtr, storePtr) \ ((objPtr)->internalRep.twoPtrValue.ptr2 = (void *) (storePtr)) -#define BUSY_STATE(st, fl) \ +#define BUSY_STATE(st,fl) \ ((((st)->csPtrR) && ((fl) & TCL_READABLE)) || \ (((st)->csPtrW) && ((fl) & TCL_WRITABLE))) @@ -245,49 +348,84 @@ static const Tcl_ObjType tclChannelType = { /* *--------------------------------------------------------------------------- * - * ChanClose, ChanRead, ChanSeek, ChanThreadAction, ChanWatch, ChanWrite -- + * ChanRead -- * - * Simplify the access to selected channel driver "methods" that are used - * in multiple places in a stereotypical fashion. These are just thin - * wrappers around the driver functions. + * Read up to dstSize bytes using the inputProc of chanPtr, store + * them at dst, and return the number of bytes stored. + * + * Results: + * The return value of the driver inputProc, + * - number of bytes stored at dst, ot + * - -1 on error, with a Posix error code available to the + * caller by calling Tcl_GetErrno(). + * + * Side effects: + * The CHANNEL_BLOCKED and CHANNEL_EOF flags of the channel state are + * set as appropriate. + * On EOF, the inputEncodingFlags are set to perform ending operations + * on decoding. + * TODO - Is this really the right place for that? * *--------------------------------------------------------------------------- */ - -static inline int -ChanClose( - Channel *chanPtr, - Tcl_Interp *interp) -{ - if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) { - return chanPtr->typePtr->closeProc(chanPtr->instanceData, interp); - } else { - return chanPtr->typePtr->close2Proc(chanPtr->instanceData, interp, 0); - } -} - -static inline int -ChanCloseHalf( - Channel *chanPtr, - Tcl_Interp *interp, - int flags) -{ - return chanPtr->typePtr->close2Proc(chanPtr->instanceData, interp, flags); -} - -static inline ssize_t +static int ChanRead( Channel *chanPtr, char *dst, - size_t dstSize, - int *errnoPtr) + int dstSize) { - if (WillRead(chanPtr)) { + int bytesRead, result; + + /* + * If the caller asked for zero bytes, we'd force the inputProc + * to return zero bytes, and then misinterpret that as EOF. + */ + assert(dstSize > 0); + + /* + * Each read op must set the blocked and eof states anew, not let + * the effect of prior reads leak through. + */ + if (GotFlag(chanPtr->state, CHANNEL_EOF)) { + chanPtr->state->inputEncodingFlags |= TCL_ENCODING_START; + } + ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF); + chanPtr->state->inputEncodingFlags &= ~TCL_ENCODING_END; + if (WillRead(chanPtr) < 0) { return -1; } - return chanPtr->typePtr->inputProc(chanPtr->instanceData, dst, dstSize, - errnoPtr); + bytesRead = chanPtr->typePtr->inputProc(chanPtr->instanceData, + dst, dstSize, &result); + + /* Stop any flag leakage through stacked channel levels */ + if (GotFlag(chanPtr->state, CHANNEL_EOF)) { + chanPtr->state->inputEncodingFlags |= TCL_ENCODING_START; + } + ResetFlag(chanPtr->state, CHANNEL_BLOCKED | CHANNEL_EOF); + chanPtr->state->inputEncodingFlags &= ~TCL_ENCODING_END; + if (bytesRead > 0) { + /* + * If we get a short read, signal up that we may be BLOCKED. + * We should avoid calling the driver because on some + * platforms we will block in the low level reading code even + * though the channel is set into nonblocking mode. + */ + + if (bytesRead < dstSize) { + SetFlag(chanPtr->state, CHANNEL_BLOCKED); + } + } else if (bytesRead == 0) { + SetFlag(chanPtr->state, CHANNEL_EOF); + chanPtr->state->inputEncodingFlags |= TCL_ENCODING_END; + } else if (bytesRead < 0) { + if ((result == EWOULDBLOCK) || (result == EAGAIN)) { + SetFlag(chanPtr->state, CHANNEL_BLOCKED); + result = EAGAIN; + } + Tcl_SetErrno(result); + } + return bytesRead; } static inline Tcl_WideInt @@ -317,37 +455,6 @@ ChanSeek( Tcl_WideAsLong(offset), mode, errnoPtr)); } -static inline void -ChanThreadAction( - Channel *chanPtr, - int action) -{ - Tcl_DriverThreadActionProc *threadActionProc = - Tcl_ChannelThreadActionProc(chanPtr->typePtr); - - if (threadActionProc != NULL) { - threadActionProc(chanPtr->instanceData, action); - } -} - -static inline void -ChanWatch( - Channel *chanPtr, - int mask) -{ - chanPtr->typePtr->watchProc(chanPtr->instanceData, mask); -} - -static inline ssize_t -ChanWrite( - Channel *chanPtr, - const char *src, - size_t srcLen, - int *errnoPtr) -{ - return chanPtr->typePtr->outputProc(chanPtr->instanceData, src, srcLen, - errnoPtr); -} /* *--------------------------------------------------------------------------- @@ -403,19 +510,6 @@ TclFinalizeIOSubsystem(void) Channel *chanPtr = NULL; /* Iterates over open channels. */ ChannelState *statePtr; /* State of channel stack */ int active = 1; /* Flag == 1 while there's still work to do */ - int doflushnb; - - /* Fetch the pre-TIP#398 compatibility flag */ - { - const char *s; - Tcl_DString ds; - - s = TclGetEnv("TCL_FLUSH_NONBLOCKING_ON_EXIT", &ds); - doflushnb = ((s != NULL) && strcmp(s, "0")); - if (s != NULL) { - Tcl_DStringFree(&ds); - } - } /* * Walk all channel state structures known to this thread and close @@ -434,38 +528,26 @@ TclFinalizeIOSubsystem(void) statePtr != NULL; statePtr = statePtr->nextCSPtr) { chanPtr = statePtr->topChanPtr; - if (GotFlag(statePtr, CHANNEL_DEAD)) { - continue; - } - if (!GotFlag(statePtr, CHANNEL_INCLOSE | CHANNEL_CLOSED ) - || GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { - ResetFlag(statePtr, BG_FLUSH_SCHEDULED); + if (!GotFlag(statePtr, CHANNEL_INCLOSE|CHANNEL_CLOSED|CHANNEL_DEAD)) { active = 1; break; } } /* - * We've found a live (or bg-closing) channel. Close it. + * We've found a live channel. Close it. */ if (active) { /* - * TIP #398: by default, we no longer set the channel back into - * blocking mode. To restore the old blocking behavior, the - * environment variable TCL_FLUSH_NONBLOCKING_ON_EXIT must be set - * and not be "0". + * Set the channel back into blocking mode to ensure that we wait + * for all data to flush out. */ - if (doflushnb) { - /* - * Set the channel back into blocking mode to ensure that we - * wait for all data to flush out. - */ + TclChannelPreserve((Tcl_Channel)chanPtr); - (void) Tcl_SetChannelOption(NULL, (Tcl_Channel) chanPtr, - "-blocking", "on"); - } + (void) Tcl_SetChannelOption(NULL, (Tcl_Channel) chanPtr, + "-blocking", "on"); if ((chanPtr == (Channel *) tsdPtr->stdinChannel) || (chanPtr == (Channel *) tsdPtr->stdoutChannel) || @@ -498,7 +580,12 @@ TclFinalizeIOSubsystem(void) * device for this channel. */ - (void) ChanClose(chanPtr, NULL); + if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) { + (chanPtr->typePtr->closeProc)(chanPtr->instanceData, NULL); + } else { + (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, + NULL, 0); + } /* * Finally, we clean up the fields in the channel data @@ -510,6 +597,7 @@ TclFinalizeIOSubsystem(void) chanPtr->instanceData = NULL; SetFlag(statePtr, CHANNEL_DEAD); } + TclChannelRelease((Tcl_Channel)chanPtr); } } @@ -660,7 +748,7 @@ Tcl_CreateCloseHandler( ChannelState *statePtr = ((Channel *) chan)->state; CloseCallback *cbPtr; - cbPtr = ckalloc(sizeof(CloseCallback)); + cbPtr = (CloseCallback *) ckalloc(sizeof(CloseCallback)); cbPtr->proc = proc; cbPtr->clientData = clientData; @@ -703,11 +791,14 @@ Tcl_DeleteCloseHandler( if ((cbPtr->proc == proc) && (cbPtr->clientData == clientData)) { if (cbPrevPtr == NULL) { statePtr->closeCbPtr = cbPtr->nextPtr; + } else { + cbPrevPtr->nextPtr = cbPtr->nextPtr; } - ckfree(cbPtr); + ckfree((char *) cbPtr); break; + } else { + cbPrevPtr = cbPtr; } - cbPrevPtr = cbPtr; } } @@ -739,7 +830,7 @@ GetChannelTable( hTblPtr = Tcl_GetAssocData(interp, "tclIO", NULL); if (hTblPtr == NULL) { - hTblPtr = ckalloc(sizeof(Tcl_HashTable)); + hTblPtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(hTblPtr, TCL_STRING_KEYS); Tcl_SetAssocData(interp, "tclIO", (Tcl_InterpDeleteProc *) DeleteChannelTable, hTblPtr); @@ -828,10 +919,10 @@ DeleteChannelTable( } Tcl_DeleteChannelHandler((Tcl_Channel) chanPtr, - TclChannelEventScriptInvoker, sPtr); + TclChannelEventScriptInvoker, (ClientData) sPtr); TclDecrRefCount(sPtr->scriptPtr); - ckfree(sPtr); + ckfree((char *) sPtr); } else { prevPtr = sPtr; } @@ -855,7 +946,7 @@ DeleteChannelTable( } Tcl_DeleteHashTable(hTblPtr); - ckfree(hTblPtr); + ckfree((char *) hTblPtr); } /* @@ -1041,9 +1132,8 @@ Tcl_UnregisterChannel( if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "illegal recursive call to close through close-handler" - " of channel", TCL_STRLEN)); + Tcl_AppendResult(interp, "Illegal recursive call to close " + "through close-handler of channel", NULL); } return TCL_ERROR; } @@ -1069,16 +1159,7 @@ Tcl_UnregisterChannel( */ if (statePtr->refCount <= 0) { - /* - * Ensure that if there is another buffer, it gets flushed whether or - * not we are doing a background flush. - */ - - if ((statePtr->curOutPtr != NULL) && - IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - Tcl_Preserve(statePtr); + Tcl_Preserve((ClientData)statePtr); if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { /* * We don't want to re-enter Tcl_Close(). @@ -1087,13 +1168,13 @@ Tcl_UnregisterChannel( if (!GotFlag(statePtr, CHANNEL_CLOSED)) { if (Tcl_Close(interp, chan) != TCL_OK) { SetFlag(statePtr, CHANNEL_CLOSED); - Tcl_Release(statePtr); + Tcl_Release((ClientData)statePtr); return TCL_ERROR; } } } SetFlag(statePtr, CHANNEL_CLOSED); - Tcl_Release(statePtr); + Tcl_Release((ClientData)statePtr); } return TCL_OK; } @@ -1278,8 +1359,8 @@ Tcl_GetChannel( hTblPtr = GetChannelTable(interp); hPtr = Tcl_FindHashEntry(hTblPtr, name); if (hPtr == NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "can not find channel named \"%s\"", chanName)); + Tcl_AppendResult(interp, "can not find channel named \"", chanName, + "\"", NULL); Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "CHANNEL", chanName, NULL); return NULL; } @@ -1293,7 +1374,7 @@ Tcl_GetChannel( chanPtr = Tcl_GetHashValue(hPtr); chanPtr = chanPtr->state->bottomChanPtr; if (modePtr != NULL) { - *modePtr = chanPtr->state->flags & (TCL_READABLE|TCL_WRITABLE); + *modePtr = GotFlag(chanPtr->state, TCL_READABLE|TCL_WRITABLE); } return (Tcl_Channel) chanPtr; @@ -1338,10 +1419,10 @@ TclGetChannelFromObj( } statePtr = GET_CHANNELSTATE(objPtr); - *channelPtr = (Tcl_Channel) statePtr->bottomChanPtr; + *channelPtr = (Tcl_Channel) (statePtr->bottomChanPtr); if (modePtr != NULL) { - *modePtr = statePtr->flags & (TCL_READABLE|TCL_WRITABLE); + *modePtr = GotFlag(statePtr, TCL_READABLE|TCL_WRITABLE); } return TCL_OK; @@ -1365,7 +1446,7 @@ TclGetChannelFromObj( Tcl_Channel Tcl_CreateChannel( - const Tcl_ChannelType *typePtr, /* The channel type record. */ + Tcl_ChannelType *typePtr, /* The channel type record. */ const char *chanName, /* Name of channel to record. */ ClientData instanceData, /* Instance specific data. */ int mask) /* TCL_READABLE & TCL_WRITABLE to indicate if @@ -1375,7 +1456,6 @@ Tcl_CreateChannel( ChannelState *statePtr; /* The stack-level independent state info for * the channel. */ const char *name; - char *tmp; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* @@ -1388,15 +1468,15 @@ Tcl_CreateChannel( * as well. */ - assert(sizeof(Tcl_ChannelTypeVersion) == sizeof(Tcl_DriverBlockModeProc *)); + assert(sizeof(Tcl_ChannelTypeVersion) == sizeof(Tcl_DriverBlockModeProc*)); /* * JH: We could subsequently memset these to 0 to avoid the numerous * assignments to 0/NULL below. */ - chanPtr = ckalloc(sizeof(Channel)); - statePtr = ckalloc(sizeof(ChannelState)); + chanPtr = (Channel *) ckalloc(sizeof(Channel)); + statePtr = (ChannelState *) ckalloc(sizeof(ChannelState)); chanPtr->state = statePtr; chanPtr->instanceData = instanceData; @@ -1408,20 +1488,14 @@ Tcl_CreateChannel( */ if (chanName != NULL) { - unsigned len = strlen(chanName) + 1; + char *tmp = ckalloc((unsigned) (strlen(chanName) + 1)); - /* - * Make sure we allocate at least 7 bytes, so it fits for "stdout" - * later. - */ - - tmp = ckalloc((len < 7) ? 7 : len); + statePtr->channelName = tmp; strcpy(tmp, chanName); } else { - tmp = ckalloc(7); - tmp[0] = '\0'; + Tcl_Panic("Tcl_CreateChannel: NULL channel name"); } - statePtr->channelName = tmp; + statePtr->flags = mask; /* @@ -1473,11 +1547,7 @@ Tcl_CreateChannel( statePtr->timer = NULL; statePtr->csPtrR = NULL; statePtr->csPtrW = NULL; - statePtr->outputStage = NULL; - if ((statePtr->encoding != NULL) && GotFlag(statePtr, TCL_WRITABLE)) { - statePtr->outputStage = ckalloc(statePtr->bufSize + 2); - } /* * As we are creating the channel, it is obviously the top for now. @@ -1489,6 +1559,7 @@ Tcl_CreateChannel( chanPtr->upChanPtr = NULL; chanPtr->inQueueHead = NULL; chanPtr->inQueueTail = NULL; + chanPtr->refCount = 0; /* * TIP #219, Tcl Channel Reflection API @@ -1523,17 +1594,14 @@ Tcl_CreateChannel( */ if ((tsdPtr->stdinChannel == NULL) && (tsdPtr->stdinInitialized == 1)) { - strcpy(tmp, "stdin"); Tcl_SetStdChannel((Tcl_Channel) chanPtr, TCL_STDIN); Tcl_RegisterChannel(NULL, (Tcl_Channel) chanPtr); } else if ((tsdPtr->stdoutChannel == NULL) && (tsdPtr->stdoutInitialized == 1)) { - strcpy(tmp, "stdout"); Tcl_SetStdChannel((Tcl_Channel) chanPtr, TCL_STDOUT); Tcl_RegisterChannel(NULL, (Tcl_Channel) chanPtr); } else if ((tsdPtr->stderrChannel == NULL) && (tsdPtr->stderrInitialized == 1)) { - strcpy(tmp, "stderr"); Tcl_SetStdChannel((Tcl_Channel) chanPtr, TCL_STDERR); Tcl_RegisterChannel(NULL, (Tcl_Channel) chanPtr); } @@ -1570,8 +1638,7 @@ Tcl_CreateChannel( Tcl_Channel Tcl_StackChannel( Tcl_Interp *interp, /* The interpreter we are working in */ - const Tcl_ChannelType *typePtr, - /* The channel type record for the new + Tcl_ChannelType *typePtr, /* The channel type record for the new * channel. */ ClientData instanceData, /* Instance specific data for the new * channel. */ @@ -1582,6 +1649,7 @@ Tcl_StackChannel( ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); Channel *chanPtr, *prevChanPtr; ChannelState *statePtr; + Tcl_DriverThreadActionProc *threadActionProc; /* * Find the given channel (prevChan) in the list of all channels. If we do @@ -1599,9 +1667,8 @@ Tcl_StackChannel( if (statePtr == NULL) { if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't find state for channel \"%s\"", - Tcl_GetChannelName(prevChan))); + Tcl_AppendResult(interp, "couldn't find state for channel \"", + Tcl_GetChannelName(prevChan), "\"", NULL); } return NULL; } @@ -1619,11 +1686,11 @@ Tcl_StackChannel( * --+---+---+---+----+ */ - if ((mask & (statePtr->flags & (TCL_READABLE | TCL_WRITABLE))) == 0) { + if ((mask & (GotFlag(statePtr, TCL_READABLE | TCL_WRITABLE))) == 0) { if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "reading and writing both disallowed for channel \"%s\"", - Tcl_GetChannelName(prevChan))); + Tcl_AppendResult(interp, + "reading and writing both disallowed for channel \"", + Tcl_GetChannelName(prevChan), "\"", NULL); } return NULL; } @@ -1642,13 +1709,16 @@ Tcl_StackChannel( statePtr->csPtrR = NULL; statePtr->csPtrW = NULL; + /* + * TODO: Examine what can go wrong if Tcl_Flush() call disturbs + * the stacking state of this channel during its operations. + */ if (Tcl_Flush((Tcl_Channel) prevChanPtr) != TCL_OK) { statePtr->csPtrR = csPtrR; statePtr->csPtrW = csPtrW; if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "could not flush channel \"%s\"", - Tcl_GetChannelName(prevChan))); + Tcl_AppendResult(interp, "could not flush channel \"", + Tcl_GetChannelName(prevChan), "\"", NULL); } return NULL; } @@ -1673,23 +1743,23 @@ Tcl_StackChannel( */ if (((mask & TCL_READABLE) != 0) && (statePtr->inQueueHead != NULL)) { + /* - * Remark: It is possible that the channel buffers contain data from - * some earlier push-backs. + * When statePtr->inQueueHead is not NULL, we know + * prevChanPtr->inQueueHead must be NULL. */ - statePtr->inQueueTail->nextPtr = prevChanPtr->inQueueHead; - prevChanPtr->inQueueHead = statePtr->inQueueHead; + assert(prevChanPtr->inQueueHead == NULL); + assert(prevChanPtr->inQueueTail == NULL); - if (prevChanPtr->inQueueTail == NULL) { - prevChanPtr->inQueueTail = statePtr->inQueueTail; - } + prevChanPtr->inQueueHead = statePtr->inQueueHead; + prevChanPtr->inQueueTail = statePtr->inQueueTail; statePtr->inQueueHead = NULL; statePtr->inQueueTail = NULL; } - chanPtr = ckalloc(sizeof(Channel)); + chanPtr = (Channel *) ckalloc(sizeof(Channel)); /* * Save some of the current state into the new structure, reinitialize the @@ -1705,6 +1775,7 @@ Tcl_StackChannel( chanPtr->upChanPtr = NULL; chanPtr->inQueueHead = NULL; chanPtr->inQueueTail = NULL; + chanPtr->refCount = 0; /* * Place new block at the head of a possibly existing list of previously @@ -1725,10 +1796,48 @@ Tcl_StackChannel( * time, mangling it. */ - ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_INSERT); + threadActionProc = Tcl_ChannelThreadActionProc(chanPtr->typePtr); + if (threadActionProc != NULL) { + (*threadActionProc)(chanPtr->instanceData, TCL_CHANNEL_THREAD_INSERT); + } return (Tcl_Channel) chanPtr; } + +void +TclChannelPreserve( + Tcl_Channel chan) +{ + ((Channel *)chan)->refCount++; +} + +void +TclChannelRelease( + Tcl_Channel chan) +{ + Channel *chanPtr = (Channel *) chan; + + if (chanPtr->refCount == 0) { + Tcl_Panic("Channel released more than preserved"); + } + if (--chanPtr->refCount) { + return; + } + if (chanPtr->typePtr == NULL) { + ckfree((char *)chanPtr); + } +} + +static void +ChannelFree( + Channel *chanPtr) +{ + if (chanPtr->refCount == 0) { + ckfree((char *)chanPtr); + return; + } + chanPtr->typePtr = NULL; +} /* *---------------------------------------------------------------------- @@ -1756,6 +1865,7 @@ Tcl_UnstackChannel( Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; int result = 0; + Tcl_DriverThreadActionProc *threadActionProc; /* * This operation should occur at the top of a channel stack. @@ -1766,9 +1876,16 @@ Tcl_UnstackChannel( if (chanPtr->downChanPtr != NULL) { /* * Instead of manipulating the per-thread / per-interp list/hashtable - * of registered channels we wind down the state of the - * transformation, and then restore the state of underlying channel - * into the old structure. + * of registered channels we wind down the state of the transformation, + * and then restore the state of underlying channel into the old + * structure. + */ + + /* + * TODO: Figure out how to handle the situation where the chan + * operations called below by this unstacking operation cause + * another unstacking recursively. In that case the downChanPtr + * value we're holding on to will not be the right thing. */ Channel *downChanPtr = chanPtr->downChanPtr; @@ -1801,9 +1918,9 @@ Tcl_UnstackChannel( */ if (!TclChanCaughtErrorBypass(interp, chan) && interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "could not flush channel \"%s\"", - Tcl_GetChannelName((Tcl_Channel) chanPtr))); + Tcl_AppendResult(interp, "could not flush channel \"", + Tcl_GetChannelName((Tcl_Channel) chanPtr), "\"", + NULL); } return TCL_ERROR; } @@ -1853,7 +1970,11 @@ Tcl_UnstackChannel( * the state which are still active. */ - ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_REMOVE); + threadActionProc = Tcl_ChannelThreadActionProc(chanPtr->typePtr); + if (threadActionProc != NULL) { + (*threadActionProc)(chanPtr->instanceData, + TCL_CHANNEL_THREAD_REMOVE); + } statePtr->topChanPtr = downChanPtr; downChanPtr->upChanPtr = NULL; @@ -1867,15 +1988,17 @@ Tcl_UnstackChannel( * Close and free the channel driver state. */ - result = ChanClose(chanPtr, interp); - chanPtr->typePtr = NULL; + if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) { + result = (chanPtr->typePtr->closeProc)(chanPtr->instanceData, + interp); + } else { + result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, + interp, 0); + } - /* - * AK: Tcl_NotifyChannel may hold a reference to this block of memory - */ + ChannelFree(chanPtr); - Tcl_EventuallyFree(chanPtr, TCL_DYNAMIC); - UpdateInterest(downChanPtr); + UpdateInterest(statePtr->topChanPtr); if (result != 0) { Tcl_SetErrno(result); @@ -2043,7 +2166,7 @@ Tcl_GetChannelThread( *---------------------------------------------------------------------- */ -const Tcl_ChannelType * +Tcl_ChannelType * Tcl_GetChannelType( Tcl_Channel chan) /* The channel to return type for. */ { @@ -2078,7 +2201,7 @@ Tcl_GetChannelMode( ChannelState *statePtr = ((Channel *) chan)->state; /* State of actual channel. */ - return (statePtr->flags & (TCL_READABLE | TCL_WRITABLE)); + return GotFlag(statePtr, TCL_READABLE | TCL_WRITABLE); } /* @@ -2102,9 +2225,9 @@ const char * Tcl_GetChannelName( Tcl_Channel chan) /* The channel for which to return the name. */ { - ChannelState *statePtr = ((Channel *) chan)->state; - /* State of actual channel. */ + ChannelState *statePtr; /* State of actual channel. */ + statePtr = ((Channel *) chan)->state; return statePtr->channelName; } @@ -2137,13 +2260,15 @@ Tcl_GetChannelHandle( chanPtr = ((Channel *) chan)->state->bottomChanPtr; if (!chanPtr->typePtr->getHandleProc) { - Tcl_SetChannelError(chan, Tcl_ObjPrintf( - "channel \"%s\" does not support OS handles", - Tcl_GetChannelName(chan))); + Tcl_Obj* err; + TclNewLiteralStringObj(err, "channel \""); + Tcl_AppendToObj(err, Tcl_GetChannelName(chan), -1); + Tcl_AppendToObj(err, "\" does not support OS handles", -1); + Tcl_SetChannelError (chan,err); return TCL_ERROR; } - result = chanPtr->typePtr->getHandleProc(chanPtr->instanceData, direction, - &handle); + result = (chanPtr->typePtr->getHandleProc)(chanPtr->instanceData, + direction, &handle); if (handlePtr) { *handlePtr = handle; } @@ -2182,13 +2307,38 @@ AllocChannelBuffer( int n; n = length + CHANNELBUFFER_HEADER_SIZE + BUFFER_PADDING + BUFFER_PADDING; - bufPtr = ckalloc(n); + bufPtr = (ChannelBuffer *) ckalloc((unsigned) n); bufPtr->nextAdded = BUFFER_PADDING; bufPtr->nextRemoved = BUFFER_PADDING; bufPtr->bufLength = length + BUFFER_PADDING; bufPtr->nextPtr = NULL; + bufPtr->refCount = 1; return bufPtr; } + +static void +PreserveChannelBuffer( + ChannelBuffer *bufPtr) +{ + bufPtr->refCount++; +} + +static void +ReleaseChannelBuffer( + ChannelBuffer *bufPtr) +{ + if (--bufPtr->refCount) { + return; + } + ckfree((char *) bufPtr); +} + +static int +IsShared( + ChannelBuffer *bufPtr) +{ + return bufPtr->refCount > 1; +} /* *---------------------------------------------------------------------- @@ -2219,20 +2369,23 @@ RecycleBuffer( /* * Do we have to free the buffer to the OS? */ + if (IsShared(bufPtr)) { + mustDiscard = 1; + } if (mustDiscard) { - ckfree(bufPtr); + ReleaseChannelBuffer(bufPtr); return; } /* - * Only save buffers which are at least as big as the requested buffersize - * for the channel. This is to honor dynamic changes of the buffersize + * Only save buffers which have the requested buffersize for the + * channel. This is to honor dynamic changes of the buffersize * made by the user. */ - if ((bufPtr->bufLength - BUFFER_PADDING) < statePtr->bufSize) { - ckfree(bufPtr); + if ((bufPtr->bufLength - BUFFER_PADDING) != statePtr->bufSize) { + ReleaseChannelBuffer(bufPtr); return; } @@ -2267,7 +2420,7 @@ RecycleBuffer( * If we reached this code we return the buffer to the OS. */ - ckfree(bufPtr); + ReleaseChannelBuffer(bufPtr); return; keepBuffer: @@ -2305,6 +2458,11 @@ DiscardOutputQueued( } statePtr->outQueueHead = NULL; statePtr->outQueueTail = NULL; + bufPtr = statePtr->curOutPtr; + if (bufPtr && BytesLeft(bufPtr)) { + statePtr->curOutPtr = NULL; + RecycleBuffer(statePtr, bufPtr, 0); + } } /* @@ -2329,16 +2487,15 @@ CheckForDeadChannel( Tcl_Interp *interp, /* For error reporting (can be NULL) */ ChannelState *statePtr) /* The channel state to check. */ { - if (!GotFlag(statePtr, CHANNEL_DEAD)) { - return 0; - } - - Tcl_SetErrno(EINVAL); - if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "unable to access channel: invalid channel", TCL_STRLEN)); + if (GotFlag(statePtr, CHANNEL_DEAD)) { + Tcl_SetErrno(EINVAL); + if (interp) { + Tcl_AppendResult(interp, + "unable to access channel: invalid channel", NULL); + } + return 1; } - return 1; + return 0; } /* @@ -2346,9 +2503,9 @@ CheckForDeadChannel( * * FlushChannel -- * - * This function flushes as much of the queued output as is possible now. - * If calledFromAsyncFlush is nonzero, it is being called in an event - * handler to flush channel output asynchronously. + * This function flushes as much of the queued output as is possible + * now. If calledFromAsyncFlush is nonzero, it is being called in an + * event handler to flush channel output asynchronously. * * Results: * 0 if successful, else the error code that was returned by the channel @@ -2372,8 +2529,6 @@ FlushChannel( ChannelState *statePtr = chanPtr->state; /* State of the channel stack. */ ChannelBuffer *bufPtr; /* Iterates over buffered output queue. */ - int toWrite; /* Amount of output data in current buffer - * available to be written. */ int written; /* Amount of output data actually written in * current round. */ int errorCode = 0; /* Stores POSIX error codes from channel @@ -2393,63 +2548,61 @@ FlushChannel( } /* - * Loop over the queued buffers and attempt to flush as much as possible - * of the queued output to the channel. - */ - - Tcl_Preserve(chanPtr); - while (1) { - /* - * If the queue is empty and there is a ready current buffer, OR if - * the current buffer is full, then move the current buffer to the - * queue. - */ - - if (((statePtr->curOutPtr != NULL) && - IsBufferFull(statePtr->curOutPtr)) - || (GotFlag(statePtr, BUFFER_READY) && - (statePtr->outQueueHead == NULL))) { - ResetFlag(statePtr, BUFFER_READY); - statePtr->curOutPtr->nextPtr = NULL; - if (statePtr->outQueueHead == NULL) { - statePtr->outQueueHead = statePtr->curOutPtr; - } else { - statePtr->outQueueTail->nextPtr = statePtr->curOutPtr; - } - statePtr->outQueueTail = statePtr->curOutPtr; - statePtr->curOutPtr = NULL; + * Should we shift the current output buffer over to the output queue? + * First check that there are bytes in it. If so then... + * If the output queue is empty, then yes, trusting the caller called + * us only when written bytes ought to be flushed. + * If the current output buffer is full, then yes, so we can meet + * the post-condition that on a successful return to caller we've + * left space in the current output buffer for more writing (the flush + * call was to make new room). + * If the channel is blocking, then yes, so we guarantee that + * blocking flushes actually flush all pending data. + * Otherwise, no. Keep the current output buffer where it is so more + * can be written to it, possibly filling it, to promote more efficient + * buffer usage. + */ + + bufPtr = statePtr->curOutPtr; + if (bufPtr && BytesLeft(bufPtr) && /* Keep empties off queue */ + (statePtr->outQueueHead == NULL || IsBufferFull(bufPtr) + || !GotFlag(statePtr, CHANNEL_NONBLOCKING))) { + if (statePtr->outQueueHead == NULL) { + statePtr->outQueueHead = bufPtr; + } else { + statePtr->outQueueTail->nextPtr = bufPtr; } - bufPtr = statePtr->outQueueHead; + statePtr->outQueueTail = bufPtr; + statePtr->curOutPtr = NULL; + } - /* - * If we are not being called from an async flush and an async flush - * is active, we just return without producing any output. - */ + assert(!IsBufferFull(statePtr->curOutPtr)); - if (!calledFromAsyncFlush && GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { - errorCode = 0; - goto done; - } + /* + * If we are not being called from an async flush and an async flush + * is active, we just return without producing any output. + */ - /* - * If the output queue is still empty, break out of the while loop. - */ + if (!calledFromAsyncFlush && GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { + return 0; + } - if (bufPtr == NULL) { - break; /* Out of the "while (1)". */ - } + /* + * Loop over the queued buffers and attempt to flush as much as possible + * of the queued output to the channel. + */ + + TclChannelPreserve((Tcl_Channel)chanPtr); + while (statePtr->outQueueHead) { + bufPtr = statePtr->outQueueHead; /* * Produce the output on the channel. */ - toWrite = BytesLeft(bufPtr); - if (toWrite == 0) { - written = 0; - } else { - written = ChanWrite(chanPtr, RemovePoint(bufPtr), toWrite, - &errorCode); - } + PreserveChannelBuffer(bufPtr); + written = (chanPtr->typePtr->outputProc)(chanPtr->instanceData, + RemovePoint(bufPtr), BytesLeft(bufPtr), &errorCode); /* * If the write failed completely attempt to start the asynchronous @@ -2464,6 +2617,7 @@ FlushChannel( if (errorCode == EINTR) { errorCode = 0; + ReleaseChannelBuffer(bufPtr); continue; } @@ -2485,6 +2639,7 @@ FlushChannel( UpdateInterest(chanPtr); } errorCode = 0; + ReleaseChannelBuffer(bufPtr); break; } @@ -2530,9 +2685,14 @@ FlushChannel( Tcl_SetErrno(errorCode); if (interp != NULL && !TclChanCaughtErrorBypass(interp, (Tcl_Channel) chanPtr)) { - Tcl_SetObjResult(interp, - Tcl_NewStringObj(Tcl_PosixError(interp), - TCL_STRLEN)); + /* + * Casting away const here is safe because the + * TCL_VOLATILE flag guarantees const treatment of the + * Posix error string. + */ + + Tcl_SetResult(interp, (char *) Tcl_PosixError(interp), + TCL_VOLATILE); } /* @@ -2547,14 +2707,15 @@ FlushChannel( */ DiscardOutputQueued(statePtr); - continue; + ReleaseChannelBuffer(bufPtr); + break; } else { + /* TODO: Consider detecting and reacting to short writes + * on blocking channels. Ought not happen. See iocmd-24.2. */ wroteSome = 1; } - if (!IsBufferEmpty(bufPtr)) { - bufPtr->nextRemoved += written; - } + bufPtr->nextRemoved += written; /* * If this buffer is now empty, recycle it. @@ -2567,7 +2728,8 @@ FlushChannel( } RecycleBuffer(statePtr, bufPtr, 0); } - } /* Closes "while (1)". */ + ReleaseChannelBuffer(bufPtr); + } /* Closes "while". */ /* * If we wrote some data while flushing in the background, we are done. @@ -2581,7 +2743,14 @@ FlushChannel( goto done; } else if (statePtr->outQueueHead == NULL) { ResetFlag(statePtr, BG_FLUSH_SCHEDULED); - ChanWatch(chanPtr, statePtr->interestMask); + (chanPtr->typePtr->watchProc)(chanPtr->instanceData, + statePtr->interestMask); + } else { + /* TODO: If code reaches this point, it means a writable + * event is being handled on the channel, but the channel + * could not in fact be written to. This ought not happen, + * but Unix pipes appear to act this way (see io-53.4). + * Also can imagine broken reflected channels. */ } } @@ -2596,25 +2765,9 @@ FlushChannel( ((statePtr->curOutPtr == NULL) || IsBufferEmpty(statePtr->curOutPtr))) { errorCode = CloseChannel(interp, chanPtr, errorCode); - goto done; } - - /* - * If the write-side of the channel is flagged as closed, delete it when - * the output queue is empty and there is no output in the current output - * buffer. - */ - - if (GotFlag(statePtr, CHANNEL_CLOSEDWRITE) && - (statePtr->outQueueHead == NULL) && - ((statePtr->curOutPtr == NULL) || - IsBufferEmpty(statePtr->curOutPtr))) { - errorCode = CloseChannelPart(interp, chanPtr, errorCode, TCL_CLOSE_WRITE); - goto done; - } - done: - Tcl_Release(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); return errorCode; } @@ -2668,7 +2821,7 @@ CloseChannel( */ if (statePtr->curOutPtr != NULL) { - ckfree(statePtr->curOutPtr); + ReleaseChannelBuffer(statePtr->curOutPtr); statePtr->curOutPtr = NULL; } @@ -2689,7 +2842,7 @@ CloseChannel( int dummy; char c = (char) statePtr->outEofChar; - (void) ChanWrite(chanPtr, &c, 1, &dummy); + (chanPtr->typePtr->outputProc)(chanPtr->instanceData, &c, 1, &dummy); } /* @@ -2700,7 +2853,7 @@ CloseChannel( if (statePtr->chanMsg != NULL) { if (interp != NULL) { - Tcl_SetChannelErrorInterp(interp, statePtr->chanMsg); + Tcl_SetChannelErrorInterp(interp,statePtr->chanMsg); } TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; @@ -2717,7 +2870,12 @@ CloseChannel( * This may leave a TIP #219 error message in the interp. */ - result = ChanClose(chanPtr, interp); + if (chanPtr->typePtr->closeProc != TCL_CLOSE2PROC) { + result = (chanPtr->typePtr->closeProc)(chanPtr->instanceData, interp); + } else { + result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, + interp, 0); + } /* * Some resources can be cleared only if the bottom channel in a stack is @@ -2726,15 +2884,11 @@ CloseChannel( if (chanPtr == statePtr->bottomChanPtr) { if (statePtr->channelName != NULL) { - ckfree(statePtr->channelName); + ckfree((char *) statePtr->channelName); statePtr->channelName = NULL; } Tcl_FreeEncoding(statePtr->encoding); - if (statePtr->outputStage != NULL) { - ckfree(statePtr->outputStage); - statePtr->outputStage = NULL; - } } /* @@ -2756,7 +2910,7 @@ CloseChannel( statePtr->chanMsg = NULL; } if (interp) { - Tcl_SetChannelErrorInterp(interp, statePtr->unreportedMsg); + Tcl_SetChannelErrorInterp(interp,statePtr->unreportedMsg); } } if (errorCode == 0) { @@ -2784,9 +2938,9 @@ CloseChannel( statePtr->topChanPtr = downChanPtr; downChanPtr->upChanPtr = NULL; - chanPtr->typePtr = NULL; - Tcl_EventuallyFree(chanPtr, TCL_DYNAMIC); + ChannelFree(chanPtr); + return Tcl_Close(interp, (Tcl_Channel) downChanPtr); } @@ -2794,13 +2948,11 @@ CloseChannel( * There is only the TOP Channel, so we free the remaining pointers we * have and then ourselves. Since this is the last of the channels in the * stack, make sure to free the ChannelState structure associated with it. - * We use Tcl_EventuallyFree to allow for any last references. */ - chanPtr->typePtr = NULL; + ChannelFree(chanPtr); Tcl_EventuallyFree(statePtr, TCL_DYNAMIC); - Tcl_EventuallyFree(chanPtr, TCL_DYNAMIC); return errorCode; } @@ -2841,6 +2993,7 @@ CutChannel( * the list on close. */ ChannelState *statePtr = ((Channel *) chan)->state; /* State of the channel stack. */ + Tcl_DriverThreadActionProc *threadActionProc; /* * Remove this channel from of the list of all channels (in the current @@ -2867,7 +3020,11 @@ CutChannel( * TIP #218, Channel Thread Actions */ - ChanThreadAction((Channel *) chan, TCL_CHANNEL_THREAD_REMOVE); + threadActionProc = Tcl_ChannelThreadActionProc(Tcl_GetChannelType(chan)); + if (threadActionProc != NULL) { + (*threadActionProc)(Tcl_GetChannelInstanceData(chan), + TCL_CHANNEL_THREAD_REMOVE); + } } void @@ -2882,6 +3039,7 @@ Tcl_CutChannel( * the list on close. */ ChannelState *statePtr = chanPtr->state; /* State of the channel stack. */ + Tcl_DriverThreadActionProc *threadActionProc; /* * Remove this channel from of the list of all channels (in the current @@ -2909,8 +3067,13 @@ Tcl_CutChannel( * For all transformations and the base channel. */ - for (; chanPtr != NULL ; chanPtr = chanPtr->upChanPtr) { - ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_REMOVE); + while (chanPtr) { + threadActionProc = Tcl_ChannelThreadActionProc(chanPtr->typePtr); + if (threadActionProc != NULL) { + (*threadActionProc)(chanPtr->instanceData, + TCL_CHANNEL_THREAD_REMOVE); + } + chanPtr= chanPtr->upChanPtr; } } @@ -2947,6 +3110,7 @@ SpliceChannel( { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *statePtr = ((Channel *) chan)->state; + Tcl_DriverThreadActionProc *threadActionProc; if (statePtr->nextCSPtr != NULL) { Tcl_Panic("SpliceChannel: trying to add channel used in different list"); @@ -2967,7 +3131,11 @@ SpliceChannel( * TIP #218, Channel Thread Actions */ - ChanThreadAction((Channel *) chan, TCL_CHANNEL_THREAD_INSERT); + threadActionProc = Tcl_ChannelThreadActionProc(Tcl_GetChannelType(chan)); + if (threadActionProc != NULL) { + (*threadActionProc) (Tcl_GetChannelInstanceData(chan), + TCL_CHANNEL_THREAD_INSERT); + } } void @@ -2978,6 +3146,7 @@ Tcl_SpliceChannel( Channel *chanPtr = ((Channel *) chan)->state->bottomChanPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); ChannelState *statePtr = chanPtr->state; + Tcl_DriverThreadActionProc *threadActionProc; if (statePtr->nextCSPtr != NULL) { Tcl_Panic("SpliceChannel: trying to add channel used in different list"); @@ -2999,8 +3168,13 @@ Tcl_SpliceChannel( * For all transformations and the base channel. */ - for (; chanPtr != NULL ; chanPtr = chanPtr->upChanPtr) { - ChanThreadAction(chanPtr, TCL_CHANNEL_THREAD_INSERT); + while (chanPtr) { + threadActionProc = Tcl_ChannelThreadActionProc(chanPtr->typePtr); + if (threadActionProc != NULL) { + (*threadActionProc)(chanPtr->instanceData, + TCL_CHANNEL_THREAD_INSERT); + } + chanPtr= chanPtr->upChanPtr; } } @@ -3069,9 +3243,8 @@ Tcl_Close( if (GotFlag(statePtr, CHANNEL_INCLOSE)) { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "illegal recursive call to close through close-handler" - " of channel", TCL_STRLEN)); + Tcl_AppendResult(interp, "Illegal recursive call to close " + "through close-handler of channel", NULL); } return TCL_ERROR; } @@ -3084,10 +3257,18 @@ Tcl_Close( stickyError = 0; - if ((statePtr->encoding != NULL) && (statePtr->curOutPtr != NULL) - && (CheckChannelErrors(statePtr, TCL_WRITABLE) == 0)) { - statePtr->outputEncodingFlags |= TCL_ENCODING_END; - if (WriteChars(chanPtr, "", 0) < 0) { + if (GotFlag(statePtr, TCL_WRITABLE) && (statePtr->encoding != NULL) + && !(statePtr->outputEncodingFlags & TCL_ENCODING_START)) { + + int code = CheckChannelErrors(statePtr, TCL_WRITABLE); + + if (code == 0) { + statePtr->outputEncodingFlags |= TCL_ENCODING_END; + code = WriteChars(chanPtr, "", 0); + statePtr->outputEncodingFlags &= ~TCL_ENCODING_END; + statePtr->outputEncodingFlags |= TCL_ENCODING_START; + } + if (code < 0) { stickyError = Tcl_GetErrno(); } @@ -3099,7 +3280,7 @@ Tcl_Close( if (statePtr->chanMsg != NULL) { if (interp != NULL) { - Tcl_SetChannelErrorInterp(interp, statePtr->chanMsg); + Tcl_SetChannelErrorInterp(interp,statePtr->chanMsg); } TclDecrRefCount(statePtr->chanMsg); statePtr->chanMsg = NULL; @@ -3115,27 +3296,19 @@ Tcl_Close( while (statePtr->closeCbPtr != NULL) { cbPtr = statePtr->closeCbPtr; statePtr->closeCbPtr = cbPtr->nextPtr; - cbPtr->proc(cbPtr->clientData); - ckfree(cbPtr); + (cbPtr->proc)(cbPtr->clientData); + ckfree((char *) cbPtr); } ResetFlag(statePtr, CHANNEL_INCLOSE); /* - * Ensure that the last output buffer will be flushed. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - - /* * If this channel supports it, close the read side, since we don't need * it anymore and this will help avoid deadlocks on some channel types. */ if (chanPtr->typePtr->closeProc == TCL_CLOSE2PROC) { - result = chanPtr->typePtr->close2Proc(chanPtr->instanceData, interp, + result = (chanPtr->typePtr->close2Proc)(chanPtr->instanceData, interp, TCL_CLOSE_READ); } else { result = 0; @@ -3172,361 +3345,23 @@ Tcl_Close( Tcl_SetErrno(stickyError); if (interp != NULL) { Tcl_SetObjResult(interp, - Tcl_NewStringObj(Tcl_PosixError(interp), TCL_STRLEN)); + Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } - flushcode = -1; - } - if ((flushcode != 0) || (result != 0)) { return TCL_ERROR; } - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_CloseEx -- - * - * Closes one side of a channel, read or write. - * - * Results: - * A standard Tcl result. - * - * Side effects: - * Closes one direction of the channel. - * - * NOTE: - * Tcl_CloseEx closes the specified direction of the channel as far as - * the user is concerned. The channel keeps existing however. You cannot - * calls this function to close the last possible direction of the - * channel. Use Tcl_Close for that. - * - *---------------------------------------------------------------------- - */ - - /* ARGSUSED */ -int -Tcl_CloseEx( - Tcl_Interp *interp, /* Interpreter for errors. */ - Tcl_Channel chan, /* The channel being closed. May still be used - * by some interpreter. */ - int flags) /* Flags telling us which side to close. */ -{ - Channel *chanPtr; /* The real IO channel. */ - ChannelState *statePtr; /* State of real IO channel. */ - - if (chan == NULL) { - return TCL_OK; - } - - /* TODO: assert flags validity ? */ - - chanPtr = (Channel *) chan; - statePtr = chanPtr->state; - - /* - * Does the channel support half-close anyway? Error if not. - */ - - if (!chanPtr->typePtr->close2Proc) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "half-close of channels not supported by %ss", - chanPtr->typePtr->typeName)); - return TCL_ERROR; - } - - /* - * Is the channel unstacked ? If not we fail. - */ - - if (chanPtr != statePtr->topChanPtr) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "half-close not applicable to stack of transformations", - TCL_STRLEN)); - return TCL_ERROR; - } - - /* - * Check direction against channel mode. It is an error if we try to close - * a direction not supported by the channel (already closed, or never - * opened for that direction). - */ - - if (!(statePtr->flags & (TCL_READABLE | TCL_WRITABLE) & flags)) { - const char *msg; - - if (flags & TCL_CLOSE_READ) { - msg = "read"; - } else { - msg = "write"; - } - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "Half-close of %s-side not possible, side not opened or" - " already closed", msg)); - return TCL_ERROR; - } - - /* - * A user may try to call half-close from within a channel close - * handler. That won't do. - */ - - if (statePtr->flags & CHANNEL_INCLOSE) { - if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "illegal recursive call to close through close-handler" - " of channel", TCL_STRLEN)); - } - return TCL_ERROR; - } - - if (flags & TCL_CLOSE_READ) { - /* - * Call the finalization code directly. There are no events to handle, - * there cannot be for the read-side. - */ - - return CloseChannelPart(interp, chanPtr, 0, flags); - } else if (flags & TCL_CLOSE_WRITE) { - if ((statePtr->curOutPtr != NULL) && - IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - Tcl_Preserve(statePtr); - if (!GotFlag(statePtr, BG_FLUSH_SCHEDULED)) { - /* - * We don't want to re-enter CloseWrite(). - */ - - if (!GotFlag(statePtr, CHANNEL_CLOSEDWRITE)) { - if (CloseWrite(interp, chanPtr) != TCL_OK) { - SetFlag(statePtr, CHANNEL_CLOSEDWRITE); - Tcl_Release(statePtr); - return TCL_ERROR; - } - } - } - SetFlag(statePtr, CHANNEL_CLOSEDWRITE); - Tcl_Release(statePtr); - } - - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * CloseWrite -- - * - * Closes the write side a channel. - * - * Results: - * A standard Tcl result. - * - * Side effects: - * Closes the write side of the channel. - * - * NOTE: - * CloseWrite removes the channel as far as the user is concerned. - * However, the ooutput data structures may continue to exist for a while - * longer if it has a background flush scheduled. The device itself is - * eventually closed and the channel structures modified, in - * CloseChannelPart, below. - * - *---------------------------------------------------------------------- - */ - -static int -CloseWrite( - Tcl_Interp *interp, /* Interpreter for errors. */ - Channel *chanPtr) /* The channel whose write side is being - * closed. May still be used by some - * interpreter */ -{ - /* Notes: clear-channel-handlers - write side only ? or keep around, just - * not called. */ - /* No close cllbacks are run - channel is still open (read side) */ - - ChannelState *statePtr = chanPtr->state; - /* State of real IO channel. */ - int flushcode; - int result = 0; - - /* - * Ensure that the last output buffer will be flushed. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - - /* - * The call to FlushChannel will flush any queued output and invoke the - * close function of the channel driver, or it will set up the channel to - * be flushed and closed asynchronously. - */ - - SetFlag(statePtr, CHANNEL_CLOSEDWRITE); - - flushcode = FlushChannel(interp, chanPtr, 0); - /* - * TIP #219. - * Capture error messages put by the driver into the bypass area and put - * them into the regular interpreter result. - * - * Notes: Due to the assertion of CHANNEL_CLOSEDWRITE in the flags - * FlushChannel() has called CloseChannelPart(). While we can still access - * "chan" (no structures were freed), the only place which may still - * contain a message is the interpreter itself, and "CloseChannelPart" made - * sure to lift any channel message it generated into it. Hence the NULL - * argument in the call below. + * Bug 97069ea11a: set error message if a flush code is set and no error + * message set up to now. */ - - if (TclChanCaughtErrorBypass(interp, NULL)) { - result = EINVAL; + if (flushcode != 0 && interp != NULL + && 0 == Tcl_GetCharLength(Tcl_GetObjResult(interp)) ) { + Tcl_SetErrno(flushcode); + Tcl_SetObjResult(interp, + Tcl_NewStringObj(Tcl_PosixError(interp), -1)); } - if ((flushcode != 0) || (result != 0)) { return TCL_ERROR; } - - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * CloseChannelPart -- - * - * Utility procedure to close a channel partially and free associated - * resources. If the channel was stacked it will never be run (The higher - * level forbid this). If the channel was not stacked, then we will free - * all the bits of the chosen side (read, or write) for the TOP channel. - * - * Results: - * Error code from an unreported error or the driver close2 operation. - * - * Side effects: - * May free memory, may change the value of errno. - * - *---------------------------------------------------------------------- - */ - -static int -CloseChannelPart( - Tcl_Interp *interp, /* Interpreter for errors. */ - Channel *chanPtr, /* The channel being closed. May still be used - * by some interpreter. */ - int errorCode, /* Status of operation so far. */ - int flags) /* Flags telling us which side to close. */ -{ - ChannelState *statePtr; /* State of real IO channel. */ - int result; /* Of calling the close2proc. */ - - statePtr = chanPtr->state; - - if (flags & TCL_CLOSE_READ) { - /* - * No more input can be consumed so discard any leftover input. - */ - - DiscardInputQueued(statePtr, 1); - } else if (flags & TCL_CLOSE_WRITE) { - /* - * The caller guarantees that there are no more buffers queued for - * output. - */ - - if (statePtr->outQueueHead != NULL) { - Tcl_Panic("ClosechanHalf, closed write-side of channel: " - "queued output left"); - } - - /* - * If the EOF character is set in the channel, append that to the - * output device. - */ - - if ((statePtr->outEofChar != 0) && GotFlag(statePtr, TCL_WRITABLE)) { - int dummy; - char c = (char) statePtr->outEofChar; - - (void) ChanWrite(chanPtr, &c, 1, &dummy); - } - - /* - * TIP #219, Tcl Channel Reflection API. - * Move a leftover error message in the channel bypass into the - * interpreter bypass. Just clear it if there is no interpreter. - */ - - if (statePtr->chanMsg != NULL) { - if (interp != NULL) { - Tcl_SetChannelErrorInterp(interp, statePtr->chanMsg); - } - TclDecrRefCount(statePtr->chanMsg); - statePtr->chanMsg = NULL; - } - } - - /* - * Finally do what is asked of us. Close and free the channel driver state - * for the chosen side of the channel. This may leave a TIP #219 error - * message in the interp. - */ - - result = ChanCloseHalf(chanPtr, interp, flags); - - /* - * If we are being called synchronously, report either any latent error on - * the channel or the current error. - */ - - if (statePtr->unreportedError != 0) { - errorCode = statePtr->unreportedError; - - /* - * TIP #219, Tcl Channel Reflection API. - * Move an error message found in the unreported area into the regular - * bypass (interp). This kills any message in the channel bypass area. - */ - - if (statePtr->chanMsg != NULL) { - TclDecrRefCount(statePtr->chanMsg); - statePtr->chanMsg = NULL; - } - if (interp) { - Tcl_SetChannelErrorInterp(interp, statePtr->unreportedMsg); - } - } - if (errorCode == 0) { - errorCode = result; - if (errorCode != 0) { - Tcl_SetErrno(errorCode); - } - } - - /* - * TIP #219. - * Capture error messages put by the driver into the bypass area and put - * them into the regular interpreter result. See also the bottom of - * CloseWrite(). - */ - - if (TclChanCaughtErrorBypass(interp, (Tcl_Channel) chanPtr)) { - result = EINVAL; - } - - if (result != 0) { - return TCL_ERROR; - } - - /* - * Remove the closed side from the channel mode/flags. - */ - - ResetFlag(statePtr, flags & (TCL_READABLE | TCL_WRITABLE)); return TCL_OK; } @@ -3592,7 +3427,7 @@ Tcl_ClearChannelHandlers( for (chPtr = statePtr->chPtr; chPtr != NULL; chPtr = chNext) { chNext = chPtr->nextPtr; - ckfree(chPtr); + ckfree((char *) chPtr); } statePtr->chPtr = NULL; @@ -3619,7 +3454,7 @@ Tcl_ClearChannelHandlers( for (ePtr = statePtr->scriptRecordPtr; ePtr != NULL; ePtr = eNextPtr) { eNextPtr = ePtr->nextPtr; TclDecrRefCount(ePtr->scriptPtr); - ckfree(ePtr); + ckfree((char *) ePtr); } statePtr->scriptRecordPtr = NULL; } @@ -3648,11 +3483,11 @@ Tcl_ClearChannelHandlers( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_Write( Tcl_Channel chan, /* The channel to buffer output for. */ const char *src, /* Data to queue in output buffer. */ - size_t srcLen) /* Length of data in bytes, or TCL_STRLEN for + int srcLen) /* Length of data in bytes, or < 0 for * strlen(). */ { /* @@ -3669,10 +3504,13 @@ Tcl_Write( return -1; } - if (srcLen == TCL_STRLEN) { + if (srcLen < 0) { srcLen = strlen(src); } - return DoWrite(chanPtr, src, srcLen); + if (WriteBytes(chanPtr, src, srcLen) < 0) { + return -1; + } + return srcLen; } /* @@ -3699,11 +3537,11 @@ Tcl_Write( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_WriteRaw( Tcl_Channel chan, /* The channel to buffer output for. */ const char *src, /* Data to queue in output buffer. */ - size_t srcLen) /* Length of data in bytes, or TCL_STRLEN for + int srcLen) /* Length of data in bytes, or < 0 for * strlen(). */ { Channel *chanPtr = ((Channel *) chan); @@ -3715,7 +3553,7 @@ Tcl_WriteRaw( return -1; } - if (srcLen == TCL_STRLEN) { + if (srcLen < 0) { srcLen = strlen(src); } @@ -3724,7 +3562,9 @@ Tcl_WriteRaw( * The code was stolen from 'FlushChannel'. */ - written = ChanWrite(chanPtr, src, srcLen, &errorCode); + written = (chanPtr->typePtr->outputProc) (chanPtr->instanceData, + src, srcLen, &errorCode); + if (written < 0) { Tcl_SetErrno(errorCode); } @@ -3755,90 +3595,48 @@ Tcl_WriteRaw( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_WriteChars( Tcl_Channel chan, /* The channel to buffer output for. */ const char *src, /* UTF-8 characters to queue in output * buffer. */ - size_t len) /* Length of string in bytes, or TCL_STRLEN - * for strlen(). */ + int len) /* Length of string in bytes, or < 0 for + * strlen(). */ { - ChannelState *statePtr; /* State info for channel */ - - statePtr = ((Channel *) chan)->state; + Channel *chanPtr = (Channel *) chan; + ChannelState *statePtr = chanPtr->state; /* State info for channel */ + int result; + Tcl_Obj *objPtr; if (CheckChannelErrors(statePtr, TCL_WRITABLE) != 0) { return -1; } - return DoWriteChars((Channel *) chan, src, len); -} - -/* - *--------------------------------------------------------------------------- - * - * DoWriteChars -- - * - * Takes a sequence of UTF-8 characters and converts them for output - * using the channel's current encoding, may queue the buffer for output - * if it gets full, and also remembers whether the current buffer is - * ready e.g. if it contains a newline and we are in line buffering mode. - * Compensates stacking, i.e. will redirect the data from the specified - * channel to the topmost channel in a stack. - * - * Results: - * The number of bytes written or -1 in case of error. If -1, - * Tcl_GetErrno will return the error code. - * - * Side effects: - * May buffer up output and may cause output to be produced on the - * channel. - * - *---------------------------------------------------------------------- - */ - -static ssize_t -DoWriteChars( - Channel *chanPtr, /* The channel to buffer output for. */ - const char *src, /* UTF-8 characters to queue in output - * buffer. */ - size_t len) /* Length of string in bytes, or TCL_STRLEN - * for strlen(). */ -{ - /* - * Always use the topmost channel of the stack - */ - - ChannelState *statePtr; /* State info for channel */ - - statePtr = chanPtr->state; chanPtr = statePtr->topChanPtr; - if (len == TCL_STRLEN) { + if (len < 0) { len = strlen(src); } - if (statePtr->encoding == NULL) { - /* - * Inefficient way to convert UTF-8 to byte-array, but the code - * parallels the way it is done for objects. - * Special case for 1-byte (used by eg [puts] for the \n) could - * be extended to more efficient translation of the src string. - */ - - int result; + if (statePtr->encoding) { + return WriteChars(chanPtr, src, len); + } - if ((len == 1) && (UCHAR(*src) < 0xC0)) { - result = WriteBytes(chanPtr, src, len); - } else { - Tcl_Obj *objPtr = Tcl_NewStringObj(src, len); + /* + * Inefficient way to convert UTF-8 to byte-array, but the code + * parallels the way it is done for objects. Special case for 1-byte + * (used by eg [puts] for the \n) could be extended to more efficient + * translation of the src string. + */ - src = (char *) Tcl_GetByteArrayFromObj(objPtr, &len); - result = WriteBytes(chanPtr, src, len); - TclDecrRefCount(objPtr); - } - return result; + if ((len == 1) && (UCHAR(*src) < 0xC0)) { + return WriteBytes(chanPtr, src, len); } - return WriteChars(chanPtr, src, len); + + objPtr = Tcl_NewStringObj(src, len); + src = (char *) Tcl_GetByteArrayFromObj(objPtr, &len); + result = WriteBytes(chanPtr, src, len); + TclDecrRefCount(objPtr); + return result; } /* @@ -3866,7 +3664,7 @@ DoWriteChars( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_WriteObj( Tcl_Channel chan, /* The channel to buffer output for. */ Tcl_Obj *objPtr) /* The object to write. */ @@ -3877,8 +3675,8 @@ Tcl_WriteObj( Channel *chanPtr; ChannelState *statePtr; /* State info for channel */ - const char *src; - size_t srcLen; + char *src; + int srcLen; statePtr = ((Channel *) chan)->state; chanPtr = statePtr->topChanPtr; @@ -3895,33 +3693,41 @@ Tcl_WriteObj( } } -static void -WillWrite( - Channel *chanPtr) +static void WillWrite(Channel *chanPtr) { int inputBuffered; - if ((chanPtr->typePtr->seekProc != NULL) && - ((inputBuffered = Tcl_InputBuffered((Tcl_Channel) chanPtr)) > 0)){ + if ((chanPtr->typePtr->seekProc != NULL) + && ((inputBuffered = Tcl_InputBuffered((Tcl_Channel) chanPtr)) > 0)) { int ignore; - DiscardInputQueued(chanPtr->state, 0); - ChanSeek(chanPtr, -inputBuffered, SEEK_CUR, &ignore); + ChanSeek(chanPtr, - inputBuffered, SEEK_CUR, &ignore); } } -static inline int -WillRead( - Channel *chanPtr) +static int WillRead(Channel *chanPtr) { + if (chanPtr->typePtr == NULL) { + /* Prevent read attempts on a closed channel */ + DiscardInputQueued(chanPtr->state, 0); + Tcl_SetErrno(EINVAL); + return -1; + } if ((chanPtr->typePtr->seekProc != NULL) - && (Tcl_OutputBuffered((Tcl_Channel) chanPtr) > 0)) { - if ((chanPtr->state->curOutPtr != NULL) - && IsBufferReady(chanPtr->state->curOutPtr)) { - SetFlag(chanPtr->state, BUFFER_READY); - } + && (Tcl_OutputBuffered((Tcl_Channel) chanPtr) > 0)) { + + /* + * CAVEAT - The assumption here is that FlushChannel() will + * push out the bytes of any writes that are in progress. + * Since this is a seekable channel, we assume it is not one + * that can block and force bg flushing. Channels we know that + * can do that -- sockets, pipes -- are not seekable. If the + * assumption is wrong, more drastic measures may be required here + * like temporarily setting the channel into blocking mode. + */ + if (FlushChannel(NULL, chanPtr, 0) != 0) { - return 1; + return -1; } } return 0; @@ -3930,111 +3736,9 @@ WillRead( /* *---------------------------------------------------------------------- * - * WriteBytes -- - * - * Write a sequence of bytes into an output buffer, may queue the buffer - * for output if it gets full, and also remembers whether the current - * buffer is ready e.g. if it contains a newline and we are in line - * buffering mode. - * - * Results: - * The number of bytes written or -1 in case of error. If -1, - * Tcl_GetErrno will return the error code. - * - * Side effects: - * May buffer up output and may cause output to be produced on the - * channel. + * Write -- * - *---------------------------------------------------------------------- - */ - -static ssize_t -WriteBytes( - Channel *chanPtr, /* The channel to buffer output for. */ - const char *src, /* Bytes to write. */ - size_t srcLen) /* Number of bytes to write. */ -{ - ChannelState *statePtr = chanPtr->state; - /* State info for channel */ - ChannelBuffer *bufPtr; - char *dst; - int translate; - size_t dstMax, sawLF, savedLF, total, dstLen, toWrite; - - if (srcLen) { - WillWrite(chanPtr); - } - - total = 0; - sawLF = 0; - savedLF = 0; - translate = GotFlag(statePtr, CHANNEL_LINEBUFFERED) - || (statePtr->outputTranslation != TCL_TRANSLATE_LF); - - /* - * Loop over all bytes in src, storing them in output buffer with proper - * EOL translation. - */ - - while (srcLen + savedLF > 0) { - bufPtr = statePtr->curOutPtr; - if (bufPtr == NULL) { - bufPtr = AllocChannelBuffer(statePtr->bufSize); - statePtr->curOutPtr = bufPtr; - } - dst = InsertPoint(bufPtr); - dstMax = SpaceLeft(bufPtr); - dstLen = dstMax; - - toWrite = dstLen; - if (toWrite > srcLen) { - toWrite = srcLen; - } - - if (translate) { - if (savedLF) { - /* - * A '\n' was left over from last call to TranslateOutputEOL() - * and we need to store it in this buffer. If the channel is - * line-based, we will need to flush it. - */ - - *dst++ = '\n'; - dstLen--; - sawLF++; - } - if (TranslateOutputEOL(statePtr, dst, src, &dstLen, &toWrite)) { - sawLF++; - } - dstLen += savedLF; - savedLF = 0; - if (dstLen > dstMax) { - savedLF = 1; - dstLen = dstMax; - } - } else { - memcpy(dst, src, toWrite); - dstLen = toWrite; - } - - bufPtr->nextAdded += dstLen; - if (CheckFlush(chanPtr, bufPtr, sawLF) != 0) { - return -1; - } - total += dstLen; - src += toWrite; - srcLen -= toWrite; - sawLF = 0; - } - return total; -} - -/* - *---------------------------------------------------------------------- - * - * WriteChars -- - * - * Convert UTF-8 bytes to the channel's external encoding and write the + * Convert srcLen bytes starting at src according to encoding and write * produced bytes into an output buffer, may queue the buffer for output * if it gets full, and also remembers whether the current buffer is * ready e.g. if it contains a newline and we are in line buffering mode. @@ -4050,382 +3754,176 @@ WriteBytes( *---------------------------------------------------------------------- */ -static ssize_t -WriteChars( +static int +Write( Channel *chanPtr, /* The channel to buffer output for. */ const char *src, /* UTF-8 string to write. */ - size_t srcLen) /* Length of UTF-8 string in bytes. */ + int srcLen, /* Length of UTF-8 string in bytes. */ + Tcl_Encoding encoding) { ChannelState *statePtr = chanPtr->state; /* State info for channel */ - ChannelBuffer *bufPtr; - char *dst, *stage; - int savedLF, sawLF, endEncoding, result, consumedSomething, translate; - size_t saved; - size_t total, dstLen, dstWrote, stageLen, stageMax, stageRead, toWrite; - Tcl_Encoding encoding; - char safe[BUFFER_PADDING]; + char *nextNewLine = NULL; + int endEncoding, saved = 0, total = 0, flushed = 0, needNlFlush = 0; if (srcLen) { WillWrite(chanPtr); } - total = 0; - sawLF = 0; - savedLF = 0; - saved = 0; - encoding = statePtr->encoding; - /* * Write the terminated escape sequence even if srcLen is 0. */ endEncoding = ((statePtr->outputEncodingFlags & TCL_ENCODING_END) != 0); - translate = GotFlag(statePtr, CHANNEL_LINEBUFFERED) - || (statePtr->outputTranslation != TCL_TRANSLATE_LF); - - /* - * Loop over all UTF-8 characters in src, storing them in staging buffer - * with proper EOL translation. - */ + if (GotFlag(statePtr, CHANNEL_LINEBUFFERED) + || (statePtr->outputTranslation != TCL_TRANSLATE_LF)) { + nextNewLine = memchr(src, '\n', srcLen); + } - consumedSomething = 1; - while (consumedSomething && (srcLen + savedLF + endEncoding > 0)) { - consumedSomething = 0; - stage = statePtr->outputStage; - stageMax = statePtr->bufSize; - stageLen = stageMax; + while (srcLen + saved + endEncoding > 0) { + ChannelBuffer *bufPtr; + char *dst, safe[BUFFER_PADDING]; + int result, srcRead, dstLen, dstWrote, srcLimit = srcLen; - toWrite = stageLen; - if (toWrite > srcLen) { - toWrite = srcLen; + if (nextNewLine) { + srcLimit = nextNewLine - src; } - - if (translate) { - if (savedLF) { - /* - * A '\n' was left over from last call to TranslateOutputEOL() - * and we need to store it in the staging buffer. If the - * channel is line-based, we will need to flush the output - * buffer (after translating the staging buffer). - */ - - *stage++ = '\n'; - stageLen--; - sawLF++; - } - if (TranslateOutputEOL(statePtr, stage, src, &stageLen, - &toWrite)) { - sawLF++; - } - - stage -= savedLF; - stageLen += savedLF; - savedLF = 0; - - if (stageLen > stageMax) { - savedLF = 1; - stageLen = stageMax; - } - } else { - memcpy(stage, src, toWrite); - stageLen = toWrite; + + /* Get space to write into */ + bufPtr = statePtr->curOutPtr; + if (bufPtr == NULL) { + bufPtr = AllocChannelBuffer(statePtr->bufSize); + statePtr->curOutPtr = bufPtr; } - src += toWrite; - srcLen -= toWrite; - - /* - * Loop over all UTF-8 characters in staging buffer, converting them - * to external encoding, storing them in output buffer. - */ - - while (stageLen + saved + endEncoding > 0) { - bufPtr = statePtr->curOutPtr; - if (bufPtr == NULL) { - bufPtr = AllocChannelBuffer(statePtr->bufSize); - statePtr->curOutPtr = bufPtr; - } - dst = InsertPoint(bufPtr); - dstLen = SpaceLeft(bufPtr); - - if (saved != 0) { - /* - * Here's some translated bytes left over from the last buffer - * that we need to stick at the beginning of this buffer. - */ - - memcpy(dst, safe, saved); - bufPtr->nextAdded += saved; - dst += saved; - dstLen -= saved; - saved = 0; - } - - result = Tcl_UtfToExternal(NULL, encoding, stage, stageLen, - statePtr->outputEncodingFlags, - &statePtr->outputEncodingState, dst, - dstLen + BUFFER_PADDING, &stageRead, &dstWrote, NULL); - + if (saved) { /* - * Fix for [Bug 506297], reported by Martin Forssen - * <ruric@users.sourceforge.net>. - * - * The encoding chosen in the script exposing the bug writes out - * three intro characters when TCL_ENCODING_START is set, but does - * not consume any input as TCL_ENCODING_END is cleared. As some - * output was generated the enclosing loop calls UtfToExternal - * again, again with START set. Three more characters in the out - * and still no use of input ... To break this infinite loop we - * remove TCL_ENCODING_START from the set of flags after the first - * call (no condition is required, the later calls remove an unset - * flag, which is a no-op). This causes the subsequent calls to - * UtfToExternal to consume and convert the actual input. + * Here's some translated bytes left over from the last buffer + * that we need to stick at the beginning of this buffer. */ - statePtr->outputEncodingFlags &= ~TCL_ENCODING_START; + memcpy(InsertPoint(bufPtr), safe, (size_t) saved); + bufPtr->nextAdded += saved; + saved = 0; + } + PreserveChannelBuffer(bufPtr); + dst = InsertPoint(bufPtr); + dstLen = SpaceLeft(bufPtr); + + result = Tcl_UtfToExternal(NULL, encoding, src, srcLimit, + statePtr->outputEncodingFlags, + &statePtr->outputEncodingState, dst, + dstLen + BUFFER_PADDING, &srcRead, &dstWrote, NULL); + + /* See chan-io-1.[89]. Tcl Bug 506297. */ + statePtr->outputEncodingFlags &= ~TCL_ENCODING_START; + + if ((result != TCL_OK) && (srcRead + dstWrote == 0)) { + /* We're reading from invalid/incomplete UTF-8 */ + ReleaseChannelBuffer(bufPtr); + if (total == 0) { + Tcl_SetErrno(EINVAL); + return -1; + } + break; + } - /* - * The following code must be executed only when result is not 0. - */ + bufPtr->nextAdded += dstWrote; + src += srcRead; + srcLen -= srcRead; + total += dstWrote; + dst += dstWrote; + dstLen -= dstWrote; - if ((result != 0) && (stageRead + dstWrote == 0)) { - /* - * We have an incomplete UTF-8 character at the end of the - * staging buffer. It will get moved to the beginning of the - * staging buffer followed by more bytes from src. - */ + if (src == nextNewLine && dstLen > 0) { + static char crln[3] = "\r\n"; + char *nl = NULL; + int nlLen = 0; - src -= stageLen; - srcLen += stageLen; - stageLen = 0; - savedLF = 0; + switch (statePtr->outputTranslation) { + case TCL_TRANSLATE_LF: + nl = crln + 1; + nlLen = 1; + break; + case TCL_TRANSLATE_CR: + nl = crln; + nlLen = 1; + break; + case TCL_TRANSLATE_CRLF: + nl = crln; + nlLen = 2; + break; + default: + Tcl_Panic("unknown output translation requested"); break; } - bufPtr->nextAdded += dstWrote; - if (IsBufferOverflowing(bufPtr)) { - /* - * When translating from UTF-8 to external encoding, we - * allowed the translation to produce a character that crossed - * the end of the output buffer, so that we would get a - * completely full buffer before flushing it. The extra bytes - * will be moved to the beginning of the next buffer. - */ + + result |= Tcl_UtfToExternal(NULL, encoding, nl, nlLen, + statePtr->outputEncodingFlags, + &statePtr->outputEncodingState, dst, + dstLen + BUFFER_PADDING, &srcRead, &dstWrote, NULL); - saved = -SpaceLeft(bufPtr); - memcpy(safe, dst + dstLen, saved); - bufPtr->nextAdded = bufPtr->bufLength; - } - if (CheckFlush(chanPtr, bufPtr, sawLF) != 0) { - return -1; - } + assert (srcRead == nlLen); + bufPtr->nextAdded += dstWrote; + src++; + srcLen--; total += dstWrote; - stage += stageRead; - stageLen -= stageRead; - sawLF = 0; - - consumedSomething = 1; + dst += dstWrote; + dstLen -= dstWrote; + nextNewLine = memchr(src, '\n', srcLen); + needNlFlush = 1; + } + if (IsBufferOverflowing(bufPtr)) { /* - * If all translated characters are written to the buffer, - * endEncoding is set to 0 because the escape sequence may be - * output. + * When translating from UTF-8 to external encoding, we + * allowed the translation to produce a character that crossed + * the end of the output buffer, so that we would get a + * completely full buffer before flushing it. The extra bytes + * will be moved to the beginning of the next buffer. */ - if ((stageLen + saved == 0) && (result == 0)) { - endEncoding = 0; - } + saved = -SpaceLeft(bufPtr); + memcpy(safe, dst + dstLen, (size_t) saved); + bufPtr->nextAdded = bufPtr->bufLength; } - } - - /* - * If nothing was written and it happened because there was no progress in - * the UTF conversion, we throw an error. - */ - if (!consumedSomething && (total == 0)) { - Tcl_SetErrno(EINVAL); - return -1; - } - return total; -} - -/* - *--------------------------------------------------------------------------- - * - * TranslateOutputEOL -- - * - * Helper function for WriteBytes() and WriteChars(). Converts the '\n' - * characters in the source buffer into the appropriate EOL form - * specified by the output translation mode. - * - * EOL translation stops either when the source buffer is empty or the - * output buffer is full. - * - * When converting to CRLF mode and there is only 1 byte left in the - * output buffer, this routine stores the '\r' in the last byte and then - * stores the '\n' in the byte just past the end of the buffer. The - * caller is responsible for passing in a buffer that is large enough to - * hold the extra byte. - * - * Results: - * The return value is 1 if a '\n' was translated from the source buffer, - * or 0 otherwise -- this can be used by the caller to decide to flush a - * line-based channel even though the channel buffer is not full. - * - * *dstLenPtr is filled with how many bytes of the output buffer were - * used. As mentioned above, this can be one more that the output - * buffer's specified length if a CRLF was stored. - * - * *srcLenPtr is filled with how many bytes of the source buffer were - * consumed. - * - * Side effects: - * It may be obvious, but bears mentioning that when converting in CRLF - * mode (which requires two bytes of storage in the output buffer), the - * number of bytes consumed from the source buffer will be less than the - * number of bytes stored in the output buffer. - * - *--------------------------------------------------------------------------- - */ - -static int -TranslateOutputEOL( - ChannelState *statePtr, /* Channel being read, for translation and - * buffering modes. */ - char *dst, /* Output buffer filled with UTF-8 chars by - * applying appropriate EOL translation to - * source characters. */ - const char *src, /* Source UTF-8 characters. */ - size_t *dstLenPtr, /* On entry, the maximum length of output - * buffer in bytes. On exit, the number of - * bytes actually used in output buffer. */ - size_t *srcLenPtr) /* On entry, the length of source buffer. On - * exit, the number of bytes read from the - * source buffer. */ -{ - char *dstEnd; - int srcLen, newlineFound; - - newlineFound = 0; - srcLen = *srcLenPtr; - - switch (statePtr->outputTranslation) { - case TCL_TRANSLATE_LF: - for (dstEnd = dst + srcLen; dst < dstEnd; ) { - if (*src == '\n') { - newlineFound = 1; - } - *dst++ = *src++; + if ((srcLen + saved == 0) && (result == TCL_OK)) { + endEncoding = 0; } - *dstLenPtr = srcLen; - break; - case TCL_TRANSLATE_CR: - for (dstEnd = dst + srcLen; dst < dstEnd;) { - if (*src == '\n') { - *dst++ = '\r'; - newlineFound = 1; - src++; - } else { - *dst++ = *src++; - } - } - *dstLenPtr = srcLen; - break; - case TCL_TRANSLATE_CRLF: { - /* - * Since this causes the number of bytes to grow, we start off trying - * to put 'srcLen' bytes into the output buffer, but allow it to store - * more bytes, as long as there's still source bytes and room in the - * output buffer. - */ - - char *dstStart, *dstMax; - const char *srcStart; - - dstStart = dst; - dstMax = dst + *dstLenPtr; - - srcStart = src; - - if (srcLen < *dstLenPtr) { - dstEnd = dst + srcLen; - } else { - dstEnd = dst + *dstLenPtr; - } - while (dst < dstEnd) { - if (*src == '\n') { - if (dstEnd < dstMax) { - dstEnd++; - } - *dst++ = '\r'; - newlineFound = 1; - } - *dst++ = *src++; - } - *srcLenPtr = src - srcStart; - *dstLenPtr = dst - dstStart; - break; - } - default: - break; - } - return newlineFound; -} - -/* - *--------------------------------------------------------------------------- - * - * CheckFlush -- - * - * Helper function for WriteBytes() and WriteChars(). If the channel - * buffer is ready to be flushed, flush it. - * - * Results: - * The return value is -1 if there was a problem flushing the channel - * buffer, or 0 otherwise. - * - * Side effects: - * The buffer will be recycled if it is flushed. - * - *--------------------------------------------------------------------------- - */ -static int -CheckFlush( - Channel *chanPtr, /* Channel being read, for buffering mode. */ - ChannelBuffer *bufPtr, /* Channel buffer to possibly flush. */ - int newlineFlag) /* Non-zero if a the channel buffer contains a - * newline. */ -{ - ChannelState *statePtr = chanPtr->state; - /* State info for channel */ - - /* - * The current buffer is ready for output: - * 1. if it is full. - * 2. if it contains a newline and this channel is line-buffered. - * 3. if it contains any output and this channel is unbuffered. - */ - - if (!GotFlag(statePtr, BUFFER_READY)) { if (IsBufferFull(bufPtr)) { - SetFlag(statePtr, BUFFER_READY); - } else if (GotFlag(statePtr, CHANNEL_LINEBUFFERED)) { - if (newlineFlag != 0) { - SetFlag(statePtr, BUFFER_READY); + if (FlushChannel(NULL, chanPtr, 0) != 0) { + ReleaseChannelBuffer(bufPtr); + return -1; } - } else if (GotFlag(statePtr, CHANNEL_UNBUFFERED)) { - SetFlag(statePtr, BUFFER_READY); - } - } - if (GotFlag(statePtr, BUFFER_READY)) { + flushed += statePtr->bufSize; + + /* + * We just flushed. So if we have needNlFlush set to record + * that we need to flush because theres a (translated) newline + * in the buffer, that's likely not true any more. But there + * is a tricky exception. If we have saved bytes that did not + * really get flushed and those bytes came from a translation + * of a newline as the last thing taken from the src array, + * then needNlFlush needs to remain set to flag that the + * next buffer still needs a newline flush. + */ + if (needNlFlush && (saved == 0 || src[-1] != '\n')) { + needNlFlush = 0; + } + } + ReleaseChannelBuffer(bufPtr); + } + if ((flushed < total) && (GotFlag(statePtr, CHANNEL_UNBUFFERED) || + (needNlFlush && GotFlag(statePtr, CHANNEL_LINEBUFFERED)))) { if (FlushChannel(NULL, chanPtr, 0) != 0) { return -1; } } - return 0; + + return total; } /* @@ -4447,7 +3945,7 @@ CheckFlush( *--------------------------------------------------------------------------- */ -ssize_t +int Tcl_Gets( Tcl_Channel chan, /* Channel from which to read. */ Tcl_DString *lineRead) /* The line read will be appended to this @@ -4456,12 +3954,14 @@ Tcl_Gets( * for managing the storage. */ { Tcl_Obj *objPtr; - ssize_t charsStored; + int charsStored, length; + char *string; TclNewObj(objPtr); charsStored = Tcl_GetsObj(chan, objPtr); if (charsStored > 0) { - TclDStringAppendObj(lineRead, objPtr); + string = TclGetStringFromObj(objPtr, &length); + Tcl_DStringAppend(lineRead, string, length); } TclDecrRefCount(objPtr); return charsStored; @@ -4490,7 +3990,7 @@ Tcl_Gets( *--------------------------------------------------------------------------- */ -ssize_t +int Tcl_GetsObj( Tcl_Channel chan, /* Channel from which to read. */ Tcl_Obj *objPtr) /* The line read will be appended to this @@ -4501,15 +4001,28 @@ Tcl_GetsObj( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - int inEofChar, skip, oldFlags; - size_t copiedTotal, oldLength, oldRemoved; + int inEofChar, skip, copiedTotal, oldLength, oldFlags, oldRemoved; Tcl_Encoding encoding; char *dst, *dstEnd, *eol, *eof; Tcl_EncodingState oldState; if (CheckChannelErrors(statePtr, TCL_READABLE) != 0) { - copiedTotal = -1; - goto done; + return -1; + } + + /* + * If we're sitting ready to read the eofchar, there's no need to + * do it. + */ + + if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { + SetFlag(statePtr, CHANNEL_EOF); + assert( statePtr->inputEncodingFlags & TCL_ENCODING_END ); + assert( !GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR) ); + + /* TODO: Do we need this? */ + UpdateInterest(chanPtr); + return -1; } /* @@ -4529,6 +4042,7 @@ Tcl_GetsObj( */ chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); bufPtr = statePtr->inQueueHead; encoding = statePtr->encoding; @@ -4552,16 +4066,7 @@ Tcl_GetsObj( */ if (encoding == NULL) { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - if (tsdPtr->binaryEncoding == NULL) { - tsdPtr->binaryEncoding = Tcl_GetEncoding(NULL, "iso8859-1"); - Tcl_CreateThreadExitHandler(FreeBinaryEncoding, NULL); - } - encoding = tsdPtr->binaryEncoding; - if (encoding == NULL) { - Tcl_Panic("attempted gets on binary channel where no iso8859-1 encoding available"); - } + encoding = GetBinaryEncoding(); } /* @@ -4586,6 +4091,7 @@ Tcl_GetsObj( eof = NULL; inEofChar = statePtr->inEofChar; + ResetFlag(statePtr, CHANNEL_BLOCKED); while (1) { if (dst >= dstEnd) { if (FilterInputBytes(chanPtr, &gs) != 0) { @@ -4678,7 +4184,7 @@ Tcl_GetsObj( */ char tmp[1 + TCL_UTF_MAX]; - size_t rawRead; + int rawRead; bufPtr = gs.bufPtr; Tcl_ExternalToUtf(NULL, gs.encoding, RemovePoint(bufPtr), @@ -4737,6 +4243,7 @@ Tcl_GetsObj( dstEnd = eof; SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; + ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); } if (GotFlag(statePtr, CHANNEL_EOF)) { skip = 0; @@ -4750,6 +4257,7 @@ Tcl_GetsObj( Tcl_SetObjLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; + ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; @@ -4771,7 +4279,11 @@ Tcl_GetsObj( * self-modifying reflected transforms. */ - chanPtr = statePtr->topChanPtr; + if (chanPtr != statePtr->topChanPtr) { + TclChannelRelease((Tcl_Channel)chanPtr); + chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); + } bufPtr = gs.bufPtr; if (bufPtr == NULL) { @@ -4805,16 +4317,18 @@ Tcl_GetsObj( * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ - - chanPtr = statePtr->topChanPtr; - + if (chanPtr != statePtr->topChanPtr) { + TclChannelRelease((Tcl_Channel)chanPtr); + chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); + } bufPtr = statePtr->inQueueHead; - if (bufPtr == NULL) { - Tcl_Panic("Tcl_GetsObj: restore reached with bufPtr==NULL"); + if (bufPtr != NULL) { + bufPtr->nextRemoved = oldRemoved; + bufPtr = bufPtr->nextPtr; } - bufPtr->nextRemoved = oldRemoved; - for (bufPtr = bufPtr->nextPtr; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { + for ( ; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bufPtr->nextRemoved = BUFFER_PADDING; } CommonGetsCleanup(chanPtr); @@ -4843,14 +4357,24 @@ Tcl_GetsObj( */ done: + assert(!GotFlag(statePtr, CHANNEL_EOF) + || GotFlag(statePtr, CHANNEL_STICKY_EOF) + || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); + + assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) + == (CHANNEL_EOF|CHANNEL_BLOCKED)) ); + /* * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ - - chanPtr = statePtr->topChanPtr; - + if (chanPtr != statePtr->topChanPtr) { + TclChannelRelease((Tcl_Channel)chanPtr); + chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); + } UpdateInterest(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); return copiedTotal; } @@ -4863,6 +4387,11 @@ Tcl_GetsObj( * end-of-line or end-of-file has been seen. Bytes read from the input * channel return as a ByteArray obj. * + * WARNING! The notion of "binary" used here is different from + * notions of "binary" used in other places. In particular, this + * "binary" routine may be called when an -eofchar is set on the + * channel. + * * Results: * Number of characters accumulated in the object or -1 if error, * blocked, or EOF. If -1, use Tcl_GetErrno() to retrieve the POSIX error @@ -4877,7 +4406,7 @@ Tcl_GetsObj( *--------------------------------------------------------------------------- */ -static ssize_t +static int TclGetsObjBinary( Tcl_Channel chan, /* Channel from which to read. */ Tcl_Obj *objPtr) /* The line read will be appended to this @@ -4887,8 +4416,8 @@ TclGetsObjBinary( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - int inEofChar, skip, oldFlags, eolChar; - size_t rawLen, byteLen, copiedTotal, oldLength, oldRemoved; + int inEofChar, skip, copiedTotal, oldLength, oldFlags, oldRemoved; + int rawLen, byteLen, eolChar; unsigned char *dst, *dstEnd, *eol, *eof, *byteArray; /* @@ -4896,6 +4425,7 @@ TclGetsObjBinary( */ chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); bufPtr = statePtr->inQueueHead; @@ -4916,13 +4446,10 @@ TclGetsObjBinary( skip = 0; eof = NULL; inEofChar = statePtr->inEofChar; - - /* - * Only handle TCL_TRANSLATE_LF and TCL_TRANSLATE_CR. - */ - + /* Only handle TCL_TRANSLATE_LF and TCL_TRANSLATE_CR */ eolChar = (statePtr->inputTranslation == TCL_TRANSLATE_LF) ? '\n' : '\r'; + ResetFlag(statePtr, CHANNEL_BLOCKED); while (1) { /* * Subtract the number of bytes that were removed from channel @@ -4942,17 +4469,24 @@ TclGetsObjBinary( * hasn't seen EOL. Need to read more bytes from the channel * device. Side effect is to allocate another channel buffer. */ - - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - goto restore; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); - } if (GetInput(chanPtr) != 0) { goto restore; } bufPtr = statePtr->inQueueTail; + if (bufPtr == NULL) { + goto restore; + } + } else { + /* + * Incoming CHANNEL_STICKY_EOF is filtered out on entry. + * A new CHANNEL_STICKY_EOF set in this routine leads to + * return before coming back here. When we are not dealing + * with CHANNEL_STICKY_EOF, a CHANNEL_EOF implies an + * empty buffer. Here the buffer is non-empty so we know + * we're a non-EOF */ + + assert ( !GotFlag(statePtr, CHANNEL_STICKY_EOF) ); + assert ( !GotFlag(statePtr, CHANNEL_EOF) ); } dst = (unsigned char *) RemovePoint(bufPtr); @@ -4994,6 +4528,7 @@ TclGetsObjBinary( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; + ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); } if (GotFlag(statePtr, CHANNEL_EOF)) { skip = 0; @@ -5007,10 +4542,15 @@ TclGetsObjBinary( byteArray = Tcl_SetByteArrayLength(objPtr, oldLength); CommonGetsCleanup(chanPtr); copiedTotal = -1; + ResetFlag(statePtr, CHANNEL_BLOCKED); goto done; } goto gotEOL; } + if (GotFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_NONBLOCKING) + == (CHANNEL_BLOCKED|CHANNEL_NONBLOCKING)) { + goto restore; + } /* * Copy bytes from the channel buffer to the ByteArray. @@ -5019,7 +4559,7 @@ TclGetsObjBinary( rawLen = dstEnd - dst; byteArray = Tcl_SetByteArrayLength(objPtr, byteLen + rawLen); - memcpy(byteArray + byteLen, dst, rawLen); + memcpy(byteArray + byteLen, dst, (size_t) rawLen); byteLen += rawLen; } @@ -5036,7 +4576,7 @@ TclGetsObjBinary( rawLen = eol - dst; byteArray = Tcl_SetByteArrayLength(objPtr, byteLen + rawLen); - memcpy(byteArray + byteLen, dst, rawLen); + memcpy(byteArray + byteLen, dst, (size_t) rawLen); byteLen += rawLen; bufPtr->nextRemoved += rawLen + skip; @@ -5065,12 +4605,12 @@ TclGetsObjBinary( restore: bufPtr = statePtr->inQueueHead; - if (bufPtr == NULL) { - Tcl_Panic("TclGetsObjBinary: restore reached with bufPtr==NULL"); + if (bufPtr) { + bufPtr->nextRemoved = oldRemoved; + bufPtr = bufPtr->nextPtr; } - bufPtr->nextRemoved = oldRemoved; - for (bufPtr = bufPtr->nextPtr; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { + for ( ; bufPtr != NULL; bufPtr = bufPtr->nextPtr) { bufPtr->nextRemoved = BUFFER_PADDING; } CommonGetsCleanup(chanPtr); @@ -5098,7 +4638,13 @@ TclGetsObjBinary( */ done: + assert(!GotFlag(statePtr, CHANNEL_EOF) + || GotFlag(statePtr, CHANNEL_STICKY_EOF) + || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); + assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) + == (CHANNEL_EOF|CHANNEL_BLOCKED)) ); UpdateInterest(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); return copiedTotal; } @@ -5127,6 +4673,21 @@ FreeBinaryEncoding( tsdPtr->binaryEncoding = NULL; } } + +static Tcl_Encoding +GetBinaryEncoding() +{ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + + if (tsdPtr->binaryEncoding == NULL) { + tsdPtr->binaryEncoding = Tcl_GetEncoding(NULL, "iso8859-1"); + Tcl_CreateThreadExitHandler(FreeBinaryEncoding, NULL); + } + if (tsdPtr->binaryEncoding == NULL) { + Tcl_Panic("binary encoding is not available"); + } + return tsdPtr->binaryEncoding; +} /* *--------------------------------------------------------------------------- @@ -5160,9 +4721,8 @@ FilterInputBytes( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - char *raw, *rawStart, *dst; - int result; - size_t offset, toRead, dstNeeded, spaceLeft, rawLen; + char *raw, *dst; + int offset, toRead, dstNeeded, spaceLeft, result, rawLen; Tcl_Obj *objPtr; #define ENCODING_LINESIZE 20 /* Lower bound on how many bytes to convert at * a time. Since we don't know a priori how @@ -5195,13 +4755,11 @@ FilterInputBytes( */ read: - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - gsPtr->charsWrote = 0; - gsPtr->rawRead = 0; - return -1; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { + gsPtr->charsWrote = 0; + gsPtr->rawRead = 0; + return -1; } if (GetInput(chanPtr) != 0) { gsPtr->charsWrote = 0; @@ -5210,6 +4768,22 @@ FilterInputBytes( } bufPtr = statePtr->inQueueTail; gsPtr->bufPtr = bufPtr; + if (bufPtr == NULL) { + gsPtr->charsWrote = 0; + gsPtr->rawRead = 0; + return -1; + } + } else { + /* + * Incoming CHANNEL_STICKY_EOF is filtered out on entry. + * A new CHANNEL_STICKY_EOF set in this routine leads to + * return before coming back here. When we are not dealing + * with CHANNEL_STICKY_EOF, a CHANNEL_EOF implies an + * empty buffer. Here the buffer is non-empty so we know + * we're a non-EOF */ + + assert ( !GotFlag(statePtr, CHANNEL_STICKY_EOF) ); + assert ( !GotFlag(statePtr, CHANNEL_EOF) ); } /* @@ -5218,8 +4792,7 @@ FilterInputBytes( * string rep if we need more space. */ - rawStart = RemovePoint(bufPtr); - raw = rawStart; + raw = RemovePoint(bufPtr); rawLen = BytesLeft(bufPtr); dst = *gsPtr->dstPtr; @@ -5231,7 +4804,7 @@ FilterInputBytes( dstNeeded = toRead * TCL_UTF_MAX; spaceLeft = objPtr->length - offset; if (dstNeeded > spaceLeft) { - size_t length = offset + ((offset < dstNeeded) ? dstNeeded : offset); + int length = offset + ((offset < dstNeeded) ? dstNeeded : offset); if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { length = offset + dstNeeded; @@ -5266,7 +4839,7 @@ FilterInputBytes( */ ChannelBuffer *nextPtr; - size_t extra; + int extra; nextPtr = bufPtr->nextPtr; if (!IsBufferFull(bufPtr)) { @@ -5286,7 +4859,7 @@ FilterInputBytes( } else { /* * There are no more cached raw bytes left. See if we can get - * some more. + * some more, but avoid blocking on a non-blocking channel. */ goto read; @@ -5299,7 +4872,7 @@ FilterInputBytes( } extra = rawLen - gsPtr->rawRead; memcpy(nextPtr->buf + (BUFFER_PADDING - extra), - raw + gsPtr->rawRead, extra); + raw + gsPtr->rawRead, (size_t) extra); nextPtr->nextRemoved -= extra; bufPtr->nextAdded -= extra; } @@ -5442,12 +5015,13 @@ CommonGetsCleanup( nextPtr = bufPtr->nextPtr; for ( ; nextPtr != NULL; nextPtr = bufPtr->nextPtr) { - size_t extra; + int extra; extra = SpaceLeft(bufPtr); if (extra > 0) { memcpy(InsertPoint(bufPtr), - nextPtr->buf + (BUFFER_PADDING - extra), extra); + nextPtr->buf + BUFFER_PADDING - extra, + (size_t) extra); bufPtr->nextAdded += extra; nextPtr->nextRemoved = BUFFER_PADDING; } @@ -5478,11 +5052,11 @@ CommonGetsCleanup( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_Read( Tcl_Channel chan, /* The channel from which to read. */ char *dst, /* Where to store input read. */ - size_t bytesToRead) /* Maximum number of bytes to read. */ + int bytesToRead) /* Maximum number of bytes to read. */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; @@ -5498,7 +5072,7 @@ Tcl_Read( return -1; } - return DoRead(chanPtr, dst, bytesToRead, 0); + return DoRead(chanPtr, dst, bytesToRead); } /* @@ -5523,139 +5097,90 @@ Tcl_Read( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_ReadRaw( Tcl_Channel chan, /* The channel from which to read. */ - char *bufPtr, /* Where to store input read. */ - size_t bytesToRead) /* Maximum number of bytes to read. */ + char *readBuf, /* Where to store input read. */ + int bytesToRead) /* Maximum number of bytes to read. */ { Channel *chanPtr = (Channel *) chan; ChannelState *statePtr = chanPtr->state; /* State info for channel */ - ssize_t nread; - size_t copied, copiedNow; - int result; - - /* - * The check below does too much because it will reject a call to this - * function with a channel which is part of an 'fcopy'. But we have to - * allow this here or else the chaining in the transformation drivers will - * fail with 'file busy' error instead of retrieving and transforming the - * data to copy. - * - * We let the check procedure now believe that there is no fcopy in - * progress. A better solution than this might be an additional flag - * argument to switch off specific checks. - */ + int copied = 0; + assert(bytesToRead > 0); if (CheckChannelErrors(statePtr, TCL_READABLE | CHANNEL_RAW_MODE) != 0) { return -1; } - /* - * Check for information in the push-back buffers. If there is some, use - * it. Go to the driver only if there is none (anymore) and the caller - * requests more bytes. - */ + /* First read bytes from the push-back buffers. */ - for (copied = 0; copied < bytesToRead; copied += copiedNow) { - copiedNow = CopyBuffer(chanPtr, bufPtr + copied, - bytesToRead - copied); - if (copiedNow == 0) { - if (GotFlag(statePtr, CHANNEL_EOF)) { - goto done; - } - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - goto done; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); - } + while (chanPtr->inQueueHead && bytesToRead > 0) { + ChannelBuffer *bufPtr = chanPtr->inQueueHead; + int bytesInBuffer = BytesLeft(bufPtr); + int toCopy = (bytesInBuffer < bytesToRead) ? bytesInBuffer + : bytesToRead; -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * [Bug 943274]. Better emulation of non-blocking channels for - * channels without BlockModeProc, by keeping track of true - * fileevents generated by the OS == Data waiting and reading if - * and only if we are sure to have data. - */ + /* Copy the current chunk into the read buffer. */ - if (GotFlag(statePtr, CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !GotFlag(statePtr, CHANNEL_HAS_MORE_DATA)) { - /* - * We bypass the driver; it would block as no data is - * available. - */ + memcpy(readBuf, RemovePoint(bufPtr), (size_t) toCopy); + bufPtr->nextRemoved += toCopy; + copied += toCopy; + readBuf += toCopy; + bytesToRead -= toCopy; - nread = -1; - result = EWOULDBLOCK; - } else -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - { - /* - * Now go to the driver to get as much as is possible to fill - * the remaining request. Do all the error handling by - * ourselves. The code was stolen from 'GetInput' and slightly - * adapted (different return value here). - * - * The case of 'bytesToRead == 0' at this point cannot happen. - */ + /* If the current buffer is empty recycle it. */ - nread = ChanRead(chanPtr, bufPtr + copied, - bytesToRead - copied, &result); + if (IsBufferEmpty(bufPtr)) { + chanPtr->inQueueHead = bufPtr->nextPtr; + if (chanPtr->inQueueHead == NULL) { + chanPtr->inQueueTail = NULL; } + RecycleBuffer(chanPtr->state, bufPtr, 0); + } + } - if (nread > 0) { - /* - * If we get a short read, signal up that we may be BLOCKED. - * We should avoid calling the driver because on some - * platforms we will block in the low level reading code even - * though the channel is set into nonblocking mode. - */ - - if (nread < ((ssize_t) bytesToRead - (ssize_t) copied)) { - SetFlag(statePtr, CHANNEL_BLOCKED); - } - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - if (nread <= (bytesToRead - copied)) { - /* - * [Bug 943274] We have read the available data, clear - * flag. - */ + /* + * Go to the driver only if we got nothing from pushback. + * Have to do it this way to avoid EOF mis-timings when we + * consider the ability that EOF may not be a permanent + * condition in the driver, and in that case we have to + * synchronize. + */ - ResetFlag(statePtr, CHANNEL_HAS_MORE_DATA); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - } else if (nread == 0) { - SetFlag(statePtr, CHANNEL_EOF); - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - - } else if (nread < 0) { - if ((result == EWOULDBLOCK) || (result == EAGAIN)) { - if (copied > 0) { - /* - * Information that was copied earlier has precedence - * over EAGAIN/WOULDBLOCK handling. - */ + if (copied) { + return copied; + } - return copied; - } + /* This test not needed. */ + if (bytesToRead > 0) { - SetFlag(statePtr, CHANNEL_BLOCKED); - result = EAGAIN; - } + int nread = ChanRead(chanPtr, readBuf, bytesToRead); - Tcl_SetErrno(result); - return -1; + if (nread > 0) { + /* Successful read (short is OK) - add to bytes copied */ + copied += nread; + } else if (nread < 0) { + /* + * An error signaled. If CHANNEL_BLOCKED, then the error + * is not real, but an indication of blocked state. In + * that case, retain the flag and let caller receive the + * short read of copied bytes from the pushback. + * HOWEVER, if copied==0 bytes from pushback then repeat + * signalling the blocked state as an error to caller so + * there is no false report of an EOF. + * When !CHANNEL_BLOCKED, the error is real and passes on + * to caller. + */ + if (!GotFlag(statePtr, CHANNEL_BLOCKED) || copied == 0) { + copied = -1; } - - return copied + nread; + } else { + /* + * nread == 0. Driver is at EOF. Let that state filter up. + */ } } - - done: return copied; } @@ -5681,13 +5206,13 @@ Tcl_ReadRaw( *--------------------------------------------------------------------------- */ -ssize_t +int Tcl_ReadChars( Tcl_Channel chan, /* The channel to read. */ Tcl_Obj *objPtr, /* Input data is stored in this object. */ - size_t toRead, /* Maximum number of characters to store, or - * TCL_STRLEN to read all available data (up - * to EOF or when channel blocks). */ + int toRead, /* Maximum number of characters to store, or + * -1 to read all available data (up to EOF or + * when channel blocks). */ int appendFlag) /* If non-zero, data read from the channel * will be appended to the object. Otherwise, * the data will replace the existing contents @@ -5737,13 +5262,13 @@ Tcl_ReadChars( *--------------------------------------------------------------------------- */ -static ssize_t +static int DoReadChars( Channel *chanPtr, /* The channel to read. */ Tcl_Obj *objPtr, /* Input data is stored in this object. */ - size_t toRead, /* Maximum number of characters to store, or - * TCL_STRLEN to read all available data (up - * to EOF or when channel blocks). */ + int toRead, /* Maximum number of characters to store, or + * -1 to read all available data (up to EOF or + * when channel blocks). */ int appendFlag) /* If non-zero, data read from the channel * will be appended to the object. Otherwise, * the data will replace the existing contents @@ -5752,22 +5277,18 @@ DoReadChars( ChannelState *statePtr = chanPtr->state; /* State info for channel */ ChannelBuffer *bufPtr; - size_t offset, factor; - ssize_t copied, copiedNow; - int result; - Tcl_Encoding encoding; + int copied, copiedNow, result; + Tcl_Encoding encoding = statePtr->encoding; + int binaryMode; #define UTF_EXPANSION_FACTOR 1024 + int factor = UTF_EXPANSION_FACTOR; - /* - * This operation should occur at the top of a channel stack. - */ - - chanPtr = statePtr->topChanPtr; - encoding = statePtr->encoding; - factor = UTF_EXPANSION_FACTOR; + binaryMode = (encoding == NULL) + && (statePtr->inputTranslation == TCL_TRANSLATE_LF) + && (statePtr->inEofChar == '\0'); if (appendFlag == 0) { - if (encoding == NULL) { + if (binaryMode) { Tcl_SetByteArrayLength(objPtr, 0); } else { Tcl_SetObjLength(objPtr, 0); @@ -5776,27 +5297,63 @@ DoReadChars( * We're going to access objPtr->bytes directly, so we must ensure * that this is actually a string object (otherwise it might have * been pure Unicode). + * + * Probably not needed anymore. */ TclGetString(objPtr); } - offset = 0; - } else { - if (encoding == NULL) { - Tcl_GetByteArrayFromObj(objPtr, &offset); - } else { - TclGetStringFromObj(objPtr, &offset); + } + + /* + * Early out when next read will see eofchar. + * + * NOTE: See DoRead for argument that it's a bug (one we're keeping) + * to have this escape before the one for zero-char read request. + */ + + if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { + SetFlag(statePtr, CHANNEL_EOF); + assert( statePtr->inputEncodingFlags & TCL_ENCODING_END ); + assert( !GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR) ); + + /* TODO: We don't need this call? */ + UpdateInterest(chanPtr); + return 0; + } + + /* Special handling for zero-char read request. */ + if (toRead == 0) { + if (GotFlag(statePtr, CHANNEL_EOF)) { + statePtr->inputEncodingFlags |= TCL_ENCODING_START; } + ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF); + statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; + /* TODO: We don't need this call? */ + UpdateInterest(chanPtr); + return 0; } - for (copied = 0; toRead > 0; ) { + /* + * This operation should occur at the top of a channel stack. + */ + + chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); + + /* Must clear the BLOCKED flag here since we check before reading */ + if (GotFlag(statePtr, CHANNEL_EOF)) { + statePtr->inputEncodingFlags |= TCL_ENCODING_START; + } + ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF); + statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; + for (copied = 0; (unsigned) toRead > 0; ) { copiedNow = -1; if (statePtr->inQueueHead != NULL) { - if (encoding == NULL) { - copiedNow = ReadBytes(statePtr, objPtr, toRead, &offset); + if (binaryMode) { + copiedNow = ReadBytes(statePtr, objPtr, toRead); } else { - copiedNow = ReadChars(statePtr, objPtr, toRead, &offset, - &factor); + copiedNow = ReadChars(statePtr, objPtr, toRead, &factor); } /* @@ -5805,8 +5362,9 @@ DoReadChars( bufPtr = statePtr->inQueueHead; if (IsBufferEmpty(bufPtr)) { - ChannelBuffer *nextPtr = bufPtr->nextPtr; + ChannelBuffer *nextPtr; + nextPtr = bufPtr->nextPtr; RecycleBuffer(statePtr, bufPtr, 0); statePtr->inQueueHead = nextPtr; if (nextPtr == NULL) { @@ -5815,53 +5373,62 @@ DoReadChars( } } - if (copiedNow == -1) { + if (copiedNow < 0) { if (GotFlag(statePtr, CHANNEL_EOF)) { break; } - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - break; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { + break; } result = GetInput(chanPtr); + if (chanPtr != statePtr->topChanPtr) { + TclChannelRelease((Tcl_Channel)chanPtr); + chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); + } if (result != 0) { - if (result == EAGAIN) { - break; + if (!GotFlag(statePtr, CHANNEL_BLOCKED)) { + copied = -1; } - copied = -1; - goto done; + break; } } else { copied += copiedNow; - if (toRead != TCL_STRLEN) { - toRead -= copiedNow; - } + toRead -= copiedNow; } } - ResetFlag(statePtr, CHANNEL_BLOCKED); - if (encoding == NULL) { - Tcl_SetByteArrayLength(objPtr, offset); - } else { - Tcl_SetObjLength(objPtr, offset); - } - /* - * Update the notifier state so we don't block while there is still data - * in the buffers. + * Failure to fill a channel buffer may have left channel reporting + * a "blocked" state, but so long as we fulfilled the request here, + * the caller does not consider us blocked. */ + if (toRead == 0) { + ResetFlag(statePtr, CHANNEL_BLOCKED); + } - done: /* * Regenerate the top channel, in case it was changed due to * self-modifying reflected transforms. */ + if (chanPtr != statePtr->topChanPtr) { + TclChannelRelease((Tcl_Channel)chanPtr); + chanPtr = statePtr->topChanPtr; + TclChannelPreserve((Tcl_Channel)chanPtr); + } - chanPtr = statePtr->topChanPtr; - + /* + * Update the notifier state so we don't block while there is still data + * in the buffers. + */ + assert(!GotFlag(statePtr, CHANNEL_EOF) + || GotFlag(statePtr, CHANNEL_STICKY_EOF) + || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); + assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) + == (CHANNEL_EOF|CHANNEL_BLOCKED)) ); UpdateInterest(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); return copied; } @@ -5879,18 +5446,16 @@ DoReadChars( * allocated to hold data read from the channel as needed. * * Results: - * The return value is the number of bytes appended to the object and - * *offsetPtr is filled with the total number of bytes in the object - * (greater than the return value if there were already bytes in the - * object). + * The return value is the number of bytes appended to the object, or + * -1 to indicate that zero bytes were read due to an EOF. * * Side effects: - * None. + * The storage of bytes in objPtr can cause (re-)allocation of memory. * *--------------------------------------------------------------------------- */ -static ssize_t +static int ReadBytes( ChannelState *statePtr, /* State of the channel to read. */ Tcl_Obj *objPtr, /* Input data is appended to this ByteArray @@ -5898,72 +5463,22 @@ ReadBytes( * been allocated to hold data, not how many * bytes of data have been stored in the * object. */ - size_t bytesToRead, /* Maximum number of bytes to store, or - * TCL_STRLEN to get all available bytes. - * Bytes are obtained from the first buffer in - * the queue - even if this number is larger - * than the number of bytes available in the - * first buffer, only the bytes from the first - * buffer are returned. */ - size_t *offsetPtr) /* On input, contains how many bytes of objPtr - * have been used to hold data. On output, - * filled with how many bytes are now being - * used. */ -{ - size_t toRead, srcLen, offset, length, srcRead, dstWrote; - ChannelBuffer *bufPtr; - char *src, *dst; - - offset = *offsetPtr; - - bufPtr = statePtr->inQueueHead; - src = RemovePoint(bufPtr); - srcLen = BytesLeft(bufPtr); - - toRead = bytesToRead; - if (toRead == TCL_STRLEN || toRead > srcLen) { - toRead = srcLen; - } - - dst = (char *) Tcl_GetByteArrayFromObj(objPtr, &length); - if (offset + toRead + 1 > length) { - /* - * Double the existing size of the object or make enough room to hold - * all the characters we may get from the source buffer, whichever is - * larger. - */ - - length = offset * 2; - if (offset < toRead) { - length = offset + toRead + 1; - } - dst = (char *) Tcl_SetByteArrayLength(objPtr, length); - } - dst += offset; - - if (GotFlag(statePtr, INPUT_NEED_NL)) { - ResetFlag(statePtr, INPUT_NEED_NL); - if ((srcLen == 0) || (*src != '\n')) { - *dst = '\r'; - *offsetPtr += 1; - return 1; - } - *dst++ = '\n'; - src++; - srcLen--; - toRead--; - } + int bytesToRead) /* Maximum number of bytes to store, or < 0 to + * get all available bytes. Bytes are obtained + * from the first buffer in the queue - even + * if this number is larger than the number of + * bytes available in the first buffer, only + * the bytes from the first buffer are + * returned. */ +{ + ChannelBuffer *bufPtr = statePtr->inQueueHead; + int srcLen = BytesLeft(bufPtr); + int toRead = bytesToRead>srcLen || bytesToRead<0 ? srcLen : bytesToRead; - srcRead = srcLen; - dstWrote = toRead; - if (TranslateInputEOL(statePtr, dst, src, &dstWrote, &srcRead)) { - if (dstWrote == 0) { - return -1; - } - } - bufPtr->nextRemoved += srcRead; - *offsetPtr += dstWrote; - return dstWrote; + TclAppendBytesToByteArray(objPtr, (unsigned char *) RemovePoint(bufPtr), + toRead); + bufPtr->nextRemoved += toRead; + return toRead; } /* @@ -5993,276 +5508,379 @@ ReadBytes( *--------------------------------------------------------------------------- */ -static ssize_t +static int ReadChars( ChannelState *statePtr, /* State of channel to read. */ Tcl_Obj *objPtr, /* Input data is appended to this object. * objPtr->length is how much space has been * allocated to hold data, not how many bytes * of data have been stored in the object. */ - size_t charsToRead, /* Maximum number of characters to store, or - * TCL_STRLEN to get all available characters. + int charsToRead, /* Maximum number of characters to store, or + * -1 to get all available characters. * Characters are obtained from the first * buffer in the queue -- even if this number * is larger than the number of characters * available in the first buffer, only the * characters from the first buffer are - * returned. */ - size_t *offsetPtr, /* On input, contains how many bytes of objPtr - * have been used to hold data. On output, - * filled with how many bytes are now being - * used. */ - size_t *factorPtr) /* On input, contains a guess of how many + * returned. The execption is when there is + * not any complete character in the first + * buffer. In that case, a recursive call + * effectively obtains chars from the + * second buffer. */ + int *factorPtr) /* On input, contains a guess of how many * bytes need to be allocated to hold the * result of converting N source bytes to * UTF-8. On output, contains another guess * based on the data seen so far. */ { - size_t toRead, factor, offset, spaceLeft, srcLen, dstNeeded; - size_t srcRead, dstWrote, numChars, dstRead; - ChannelBuffer *bufPtr; - char *src, *dst; - Tcl_EncodingState oldState; - int encEndFlagSuppressed = 0; - - factor = *factorPtr; - offset = *offsetPtr; + Tcl_Encoding encoding = statePtr->encoding? statePtr->encoding + : GetBinaryEncoding(); + Tcl_EncodingState savedState = statePtr->inputEncodingState; + ChannelBuffer *bufPtr = statePtr->inQueueHead; + int savedIEFlags = statePtr->inputEncodingFlags; + int savedFlags = statePtr->flags; + char *dst, *src = RemovePoint(bufPtr); + int dstLimit, numBytes, srcLen = BytesLeft(bufPtr); - bufPtr = statePtr->inQueueHead; - src = RemovePoint(bufPtr); - srcLen = BytesLeft(bufPtr); + /* + * One src byte can yield at most one character. So when the + * number of src bytes we plan to read is less than the limit on + * character count to be read, clearly we will remain within that + * limit, and we can use the value of "srcLen" as a tighter limit + * for sizing receiving buffers. + */ - toRead = charsToRead; - if (toRead == TCL_STRLEN || toRead > srcLen) { - toRead = srcLen; - } + int toRead = ((unsigned) charsToRead > (unsigned) srcLen) ? srcLen : charsToRead; /* * 'factor' is how much we guess that the bytes in the source buffer will * expand when converted to UTF-8 chars. This guess comes from analyzing * how many characters were produced by the previous pass. */ + + int factor = *factorPtr; + int dstNeeded = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; - dstNeeded = TCL_UTF_MAX - 1 + toRead * factor / UTF_EXPANSION_FACTOR; - spaceLeft = objPtr->length - offset; - - if (dstNeeded > spaceLeft) { - /* - * Double the existing size of the object or make enough room to hold - * all the characters we want from the source buffer, whichever is - * larger. - */ - - int length = offset + ((offset < dstNeeded) ? dstNeeded : offset); - - if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { - length = offset + dstNeeded; - if (Tcl_AttemptSetObjLength(objPtr, length) == 0) { - dstNeeded = TCL_UTF_MAX - 1 + toRead; - length = offset + dstNeeded; - Tcl_SetObjLength(objPtr, length); - } - } - spaceLeft = length - offset; - } + (void) TclGetStringFromObj(objPtr, &numBytes); + Tcl_AppendToObj(objPtr, NULL, dstNeeded); if (toRead == srcLen) { - /* - * Want to convert the whole buffer in one pass. If we have enough - * space, convert it using all available space in object rather than - * using the factor. - */ - - dstNeeded = spaceLeft; + unsigned int size; + dst = TclGetStringStorage(objPtr, &size) + numBytes; + dstNeeded = size - numBytes; + } else { + dst = TclGetString(objPtr) + numBytes; } - dst = objPtr->bytes + offset; /* - * [Bug 1462248]: The cause of the crash reported in this bug is this: - * - * - ReadChars, called with a single buffer, with a incomplete - * multi-byte character at the end (only the first byte of it). - * - Encoding translation fails, asks for more data - * - Data is read, and eof is reached, TCL_ENCODING_END (TEE) is set. - * - ReadChar is called again, converts the first buffer, but due to TEE - * it does not check for incomplete multi-byte data, and the character - * just after the end of the first buffer is a valid completion of the - * multi-byte header in the actual buffer. The conversion reads more - * characters from the buffer then present. This causes nextRemoved to - * overshoot nextAdded and the next reads compute a negative srcLen, - * cause further translations to fail, causing copying of data into the - * next buffer using bad arguments, causing the mecpy for to eventually - * fail. - * - * In the end it is a memory access bug spiraling out of control if the - * conditions are _just so_. And ultimate cause is that TEE is given to a - * conversion where it should not. TEE signals that this is the last - * buffer. Except in our case it is not. - * - * My solution is to suppress TEE if the first buffer is not the last. We - * will eventually need it given that EOF has been reached, but not right - * now. This is what the new flag "endEncSuppressFlag" is for. + * This routine is burdened with satisfying several constraints. + * It cannot append more than 'charsToRead` chars onto objPtr. + * This is measured after encoding and translation transformations + * are completed. There is no precise number of src bytes that can + * be associated with the limit. Yet, when we are done, we must know + * precisely the number of src bytes that were consumed to produce + * the appended chars, so that all subsequent bytes are left in + * the buffers for future read operations. * - * The bug in 'Tcl_Utf2UtfProc' where it read from memory behind the - * actual buffer has been fixed as well, and fixes the problem with the - * crash too, but this would still allow the generic layer to - * accidentially break a multi-byte sequence if the conditions are just - * right, because again the ExternalToUtf would be successful where it - * should not. + * The consequence is that we have no choice but to implement a + * "trial and error" approach, where in general we may need to + * perform transformations and copies multiple times to achieve + * a consistent set of results. This takes the shape of a loop. */ - if ((statePtr->inputEncodingFlags & TCL_ENCODING_END) && - (bufPtr->nextPtr != NULL)) { + dstLimit = dstNeeded + 1; + while (1) { + int dstDecoded, dstRead, dstWrote, srcRead, numChars, code; + /* - * TEE is set for a buffer which is not the last. Squash it for now, - * and restore it later, before yielding control to our caller. + * Perform the encoding transformation. Read no more than + * srcLen bytes, write no more than dstLimit bytes. + * + * Some trickiness with encoding flags here. We do not want + * the end of a buffer to be treated as the end of all input + * when the presence of bytes in a next buffer are already + * known to exist. This is checked with an assert() because + * so far no test case causing the assertion to be false has + * been created. The normal operations of channel reading + * appear to cause EOF and TCL_ENCODING_END setting to appear + * only in situations where there are no further bytes in + * any buffers. */ - statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; - encEndFlagSuppressed = 1; - } + assert(bufPtr->nextPtr == NULL || BytesLeft(bufPtr->nextPtr) == 0 + || (statePtr->inputEncodingFlags & TCL_ENCODING_END) == 0); + + code = Tcl_ExternalToUtf(NULL, encoding, src, srcLen, + statePtr->inputEncodingFlags, &statePtr->inputEncodingState, + dst, dstLimit, &srcRead, &dstDecoded, &numChars); - oldState = statePtr->inputEncodingState; - if (GotFlag(statePtr, INPUT_NEED_NL)) { /* - * We want a '\n' because the last character we saw was '\r'. + * Perform the translation transformation in place. Read no more + * than the dstDecoded bytes the encoding transformation actually + * produced. Capture the number of bytes written in dstWrote. + * Capture the number of bytes actually consumed in dstRead. */ - ResetFlag(statePtr, INPUT_NEED_NL); - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, - dst, TCL_UTF_MAX + 1, &srcRead, &dstWrote, &numChars); - if ((dstWrote > 0) && (*dst == '\n')) { + dstWrote = dstLimit; + dstRead = dstDecoded; + TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); + + if (dstRead < dstDecoded) { + /* - * The next char was a '\n'. Consume it and produce a '\n'. + * The encoding transformation produced bytes that the + * translation transformation did not consume. Why did + * this happen? */ - bufPtr->nextRemoved += srcRead; - } else { + if (statePtr->inEofChar && dst[dstRead] == statePtr->inEofChar) { + /* + * 1) There's an eof char set on the channel, and + * we saw it and stopped translating at that point. + * + * NOTE the bizarre spec of TranslateInputEOL in this case. + * Clearly the eof char had to be read in order to account + * for the stopping, but the value of dstRead does not + * include it. + * + * Also rather bizarre, our caller can only notice an + * EOF condition if we return the value -1 as the number + * of chars read. This forces us to perform a 2-call + * dance where the first call can read all the chars + * up to the eof char, and the second call is solely + * for consuming the encoded eof char then pointed at + * by src so that we can return that magic -1 value. + * This seems really wasteful, especially since + * the first decoding pass of each call is likely to + * decode many bytes beyond that eof char that's all we + * care about. + */ + + if (dstRead == 0) { + /* + * Curious choice in the eof char handling. We leave + * the eof char in the buffer. So, no need to compute + * a proper srcRead value. At this point, there + * are no chars before the eof char in the buffer. + */ + Tcl_SetObjLength(objPtr, numBytes); + return -1; + } + + { + /* + * There are chars leading the buffer before the eof + * char. Adjust the dstLimit so we go back and read + * only those and do not encounter the eof char this + * time. + */ + + dstLimit = dstRead + TCL_UTF_MAX; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; + } + } + /* - * The next char was not a '\n'. Produce a '\r'. + * 2) The other way to read fewer bytes than are decoded + * is when the final byte is \r and we're in a CRLF + * translation mode so we cannot decide whether to + * record \r or \n yet. */ - *dst = '\r'; - } - statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; - *offsetPtr += 1; + assert(dst[dstRead] == '\r'); + assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); - if (encEndFlagSuppressed) { - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - } - return 1; - } + if (dstWrote > 0) { + /* + * There are chars we can read before we hit the bare cr. + * Go back with a smaller dstLimit so we get them in the + * next pass, compute a matching srcRead, and don't end + * up back here in this call. + */ - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, dst, - dstNeeded + 1, &srcRead, &dstWrote, &numChars); + dstLimit = dstRead + TCL_UTF_MAX; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; + } - if (encEndFlagSuppressed) { - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - } + assert(dstWrote == 0); + assert(dstRead == 0); - if (srcRead == 0) { - /* - * Not enough bytes in src buffer to make a complete char. Copy the - * bytes to the next buffer to make a new contiguous string, then tell - * the caller to fill the buffer with more bytes. - */ + /* + * We decoded only the bare cr, and we cannot read a + * translated char from that alone. We have to know what's + * next. So why do we only have the one decoded char? + */ - ChannelBuffer *nextPtr; + if (code != TCL_OK) { + char buffer[TCL_UTF_MAX + 2]; + int read, decoded, count; + + /* + * Didn't get everything the buffer could offer + */ + + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + + assert(bufPtr->nextPtr == NULL + || BytesLeft(bufPtr->nextPtr) == 0 || 0 == + (statePtr->inputEncodingFlags & TCL_ENCODING_END)); + + Tcl_ExternalToUtf(NULL, encoding, src, srcLen, + statePtr->inputEncodingFlags, &statePtr->inputEncodingState, + buffer, TCL_UTF_MAX + 2, &read, &decoded, &count); + + if (count == 2) { + if (buffer[1] == '\n') { + /* \r\n translate to \n */ + dst[0] = '\n'; + bufPtr->nextRemoved += read; + } else { + dst[0] = '\r'; + bufPtr->nextRemoved += srcRead; + } + + dst[1] = '\0'; + statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; + + Tcl_SetObjLength(objPtr, numBytes + 1); + return 1; + } + + } else if (GotFlag(statePtr, CHANNEL_EOF)) { - nextPtr = bufPtr->nextPtr; - if (nextPtr == NULL) { - if (srcLen > 0) { /* - * There isn't enough data in the buffers to complete the next - * character, so we need to wait for more data before the next - * file event can be delivered. [Bug 478856] - * - * The exception to this is if the input buffer was completely - * empty before we tried to convert its contents. Nothing in, - * nothing out, and no incomplete character data. The - * conversion before the current one was complete. + * The bare \r is the only char and we will never read + * a subsequent char to make the determination. */ - SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); + dst[0] = '\r'; + bufPtr->nextRemoved = bufPtr->nextAdded; + Tcl_SetObjLength(objPtr, numBytes + 1); + return 1; } - return -1; + + /* + * Revise the dstRead value so that the numChars calc + * below correctly computes zero characters read. + */ + + dstRead = numChars; + + /* FALL THROUGH - get more data (dstWrote == 0) */ } - /* - * Space is made at the beginning of the buffer to copy the previous - * unused bytes there. Check first if the buffer we are using actually - * has enough space at its beginning for the data we are copying. - * Because if not we will write over the buffer management - * information, especially the 'nextPtr'. - * - * Note that the BUFFER_PADDING (See AllocChannelBuffer) is used to - * prevent exactly this situation. I.e. it should never happen. - * Therefore it is ok to panic should it happen despite the - * precautions. + /* + * The translation transformation can only reduce the number + * of chars when it converts \r\n into \n. The reduction in + * the number of chars is the difference in bytes read and written. */ - if (nextPtr->nextRemoved < srcLen) { - Tcl_Panic("Buffer Underflow, BUFFER_PADDING not enough"); - } + numChars -= (dstRead - dstWrote); - nextPtr->nextRemoved -= srcLen; - memcpy(RemovePoint(nextPtr), src, srcLen); - RecycleBuffer(statePtr, bufPtr, 0); - statePtr->inQueueHead = nextPtr; - return ReadChars(statePtr, objPtr, charsToRead, offsetPtr, factorPtr); - } + if (charsToRead > 0 && numChars > charsToRead) { - dstRead = dstWrote; - if (TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead)) { - /* - * Hit EOF char. How many bytes of src correspond to where the EOF was - * located in dst? Run the conversion again with an output buffer just - * big enough to hold the data so we can get the correct value for - * srcRead. - */ + /* + * We read more chars than allowed. Reset limits to + * prevent that and try again. Don't forget the extra + * padding of TCL_UTF_MAX bytes demanded by the + * Tcl_ExternalToUtf() call! + */ - if (dstWrote == 0) { - return -1; + dstLimit = Tcl_UtfAtIndex(dst, charsToRead) + TCL_UTF_MAX - dst; + statePtr->flags = savedFlags; + statePtr->inputEncodingFlags = savedIEFlags; + statePtr->inputEncodingState = savedState; + continue; } - statePtr->inputEncodingState = oldState; - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, - dst, dstRead + TCL_UTF_MAX, &srcRead, &dstWrote, &numChars); - TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); - } - /* - * The number of characters that we got may be less than the number that - * we started with because "\r\n" sequences may have been turned into just - * '\n' in dst. - */ + if (dstWrote == 0) { + ChannelBuffer *nextPtr; - numChars -= dstRead - dstWrote; + /* We were not able to read any chars. */ - if (numChars > toRead) { - /* - * Got too many chars. - */ + assert (numChars == 0); - const char *eof = Tcl_UtfAtIndex(dst, toRead); + /* + * There is one situation where this is the correct final + * result. If the src buffer contains only a single \n + * byte, and we are in TCL_TRANSLATE_AUTO mode, and + * when the translation pass was made the INPUT_SAW_CR + * flag was set on the channel. In that case, the + * correct behavior is to consume that \n and produce the + * empty string. + */ - statePtr->inputEncodingState = oldState; - Tcl_ExternalToUtf(NULL, statePtr->encoding, src, srcLen, - statePtr->inputEncodingFlags, &statePtr->inputEncodingState, - dst, eof - dst + TCL_UTF_MAX, &srcRead, &dstWrote, &numChars); - dstRead = dstWrote; - TranslateInputEOL(statePtr, dst, dst, &dstWrote, &dstRead); - numChars -= (dstRead - dstWrote); - } - statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; + if (dst[0] == '\n') { + assert(statePtr->inputTranslation == TCL_TRANSLATE_AUTO); + assert(dstRead == 1); + + goto consume; + } + + /* Otherwise, reading zero characters indicates there's + * something incomplete at the end of the src buffer. + * Maybe there were not enough src bytes to decode into + * a char. Maybe a lone \r could not be translated (crlf + * mode). Need to combine any unused src bytes we have + * in the first buffer with subsequent bytes to try again. + */ + + nextPtr = bufPtr->nextPtr; + + if (nextPtr == NULL) { + if (srcLen > 0) { + SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); + } + Tcl_SetObjLength(objPtr, numBytes); + return -1; + } + + /* + * Space is made at the beginning of the buffer to copy the + * previous unused bytes there. Check first if the buffer we + * are using actually has enough space at its beginning for + * the data we are copying. Because if not we will write over + * the buffer management information, especially the 'nextPtr'. + * + * Note that the BUFFER_PADDING (See AllocChannelBuffer) is + * used to prevent exactly this situation. I.e. it should never + * happen. Therefore it is ok to panic should it happen despite + * the precautions. + */ - bufPtr->nextRemoved += srcRead; - if (dstWrote > srcRead + 1) { - *factorPtr = dstWrote * UTF_EXPANSION_FACTOR / srcRead; + if (nextPtr->nextRemoved - srcLen < 0) { + Tcl_Panic("Buffer Underflow, BUFFER_PADDING not enough"); + } + + nextPtr->nextRemoved -= srcLen; + memcpy(RemovePoint(nextPtr), src, (size_t) srcLen); + RecycleBuffer(statePtr, bufPtr, 0); + statePtr->inQueueHead = nextPtr; + Tcl_SetObjLength(objPtr, numBytes); + return ReadChars(statePtr, objPtr, charsToRead, factorPtr); + } + + statePtr->inputEncodingFlags &= ~TCL_ENCODING_START; + + consume: + bufPtr->nextRemoved += srcRead; + /* + * If this read contained multibyte characters, revise factorPtr + * so the next read will allocate bigger buffers. + */ + if (numChars && numChars < srcRead) { + *factorPtr = srcRead * UTF_EXPANSION_FACTOR / numChars; + } + Tcl_SetObjLength(objPtr, numBytes + dstWrote); + return numChars; } - *offsetPtr += dstWrote; - return numChars; } /* @@ -6283,7 +5901,7 @@ ReadChars( *--------------------------------------------------------------------------- */ -static int +static void TranslateInputEOL( ChannelState *statePtr, /* Channel being read, for EOL translation and * EOF character. */ @@ -6291,133 +5909,139 @@ TranslateInputEOL( * appropriate EOL translation to source * characters. */ const char *srcStart, /* Source characters. */ - size_t *dstLenPtr, /* On entry, the maximum length of output - * buffer in bytes; must be <= *srcLenPtr. On - * exit, the number of bytes actually used in - * output buffer. */ - size_t *srcLenPtr) /* On entry, the length of source buffer. On + int *dstLenPtr, /* On entry, the maximum length of output + * buffer in bytes. On exit, the number of + * bytes actually used in output buffer. */ + int *srcLenPtr) /* On entry, the length of source buffer. On * exit, the number of bytes read from the * source buffer. */ { - size_t dstLen, srcLen, inEofChar; - const char *eof; + const char *eof = NULL; + int dstLen = *dstLenPtr; + int srcLen = *srcLenPtr; + int inEofChar = statePtr->inEofChar; - dstLen = *dstLenPtr; + /* + * Depending on the translation mode in use, there's no need + * to scan more srcLen bytes at srcStart than can possibly transform + * to dstLen bytes. This keeps the scan for eof char below from + * being pointlessly long. + */ + + switch (statePtr->inputTranslation) { + case TCL_TRANSLATE_LF: + case TCL_TRANSLATE_CR: + if (srcLen > dstLen) { + /* In these modes, each src byte become a dst byte. */ + srcLen = dstLen; + } + break; + default: + /* In other modes, at most 2 src bytes become a dst byte. */ + if (srcLen/2 > dstLen) { + srcLen = 2 * dstLen; + } + break; + } - eof = NULL; - inEofChar = statePtr->inEofChar; if (inEofChar != '\0') { /* - * Find EOF in translated buffer then compress out the EOL. The source - * buffer may be much longer than the destination buffer - we only - * want to return EOF if the EOF has been copied to the destination - * buffer. + * Make sure we do not read past any logical end of channel input + * created by the presence of the input eof char. */ - const char *src, *srcMax = srcStart + *srcLenPtr; - - for (src = srcStart; src < srcMax; src++) { - if (*src == inEofChar) { - eof = src; - srcLen = src - srcStart; - if (srcLen < dstLen) { - dstLen = srcLen; - } - *srcLenPtr = srcLen; - break; - } + if ((eof = memchr(srcStart, inEofChar, srcLen))) { + srcLen = eof - srcStart; } } + switch (statePtr->inputTranslation) { case TCL_TRANSLATE_LF: + case TCL_TRANSLATE_CR: if (dstStart != srcStart) { - memcpy(dstStart, srcStart, dstLen); + memcpy(dstStart, srcStart, (size_t) srcLen); } - srcLen = dstLen; - break; - case TCL_TRANSLATE_CR: { - char *dst, *dstEnd; + if (statePtr->inputTranslation == TCL_TRANSLATE_CR) { + char *dst = dstStart; + char *dstEnd = dstStart + srcLen; - if (dstStart != srcStart) { - memcpy(dstStart, srcStart, dstLen); - } - dstEnd = dstStart + dstLen; - for (dst = dstStart; dst < dstEnd; dst++) { - if (*dst == '\r') { - *dst = '\n'; + while ((dst = memchr(dst, '\r', dstEnd - dst))) { + *dst++ = '\n'; } } - srcLen = dstLen; + dstLen = srcLen; break; - } case TCL_TRANSLATE_CRLF: { - char *dst; - const char *src, *srcEnd, *srcMax; - - dst = dstStart; - src = srcStart; - srcEnd = srcStart + dstLen; - srcMax = srcStart + *srcLenPtr; - - for ( ; src < srcEnd; ) { - if (*src == '\r') { - src++; - if (src >= srcMax) { - SetFlag(statePtr, INPUT_NEED_NL); - } else if (*src == '\n') { - *dst++ = *src++; - } else { + const char *crFound, *src = srcStart; + char *dst = dstStart; + int lesser = (dstLen < srcLen) ? dstLen : srcLen; + + while ((crFound = memchr(src, '\r', lesser))) { + int numBytes = crFound - src; + memmove(dst, src, numBytes); + + dst += numBytes; dstLen -= numBytes; + src += numBytes; srcLen -= numBytes; + if (srcLen == 1) { + /* valid src bytes end in \r */ + if (eof) { *dst++ = '\r'; + src++; srcLen--; + } else { + lesser = 0; + break; } + } else if (src[1] == '\n') { + *dst++ = '\n'; + src += 2; srcLen -= 2; } else { - *dst++ = *src++; + *dst++ = '\r'; + src++; srcLen--; } + dstLen--; + lesser = (dstLen < srcLen) ? dstLen : srcLen; } - srcLen = src - srcStart; - dstLen = dst - dstStart; + memmove(dst, src, lesser); + srcLen = src + lesser - srcStart; + dstLen = dst + lesser - dstStart; break; } case TCL_TRANSLATE_AUTO: { - char *dst; - const char *src, *srcEnd, *srcMax; - - dst = dstStart; - src = srcStart; - srcEnd = srcStart + dstLen; - srcMax = srcStart + *srcLenPtr; + const char *crFound, *src = srcStart; + char *dst = dstStart; + int lesser; - if (GotFlag(statePtr, INPUT_SAW_CR) && (src < srcMax)) { - if (*src == '\n') { - src++; - } + if (GotFlag(statePtr, INPUT_SAW_CR) && srcLen) { + if (*src == '\n') { src++; srcLen--; } ResetFlag(statePtr, INPUT_SAW_CR); } - for ( ; src < srcEnd; ) { - if (*src == '\r') { - src++; - if (src >= srcMax) { - SetFlag(statePtr, INPUT_SAW_CR); - } else if (*src == '\n') { - if (srcEnd < srcMax) { - srcEnd++; - } - src++; - } - *dst++ = '\n'; - } else { - *dst++ = *src++; + lesser = (dstLen < srcLen) ? dstLen : srcLen; + while ((crFound = memchr(src, '\r', lesser))) { + int numBytes = crFound - src; + memmove(dst, src, numBytes); + + dst[numBytes] = '\n'; + dst += numBytes + 1; dstLen -= numBytes + 1; + src += numBytes + 1; srcLen -= numBytes + 1; + if (srcLen == 0) { + SetFlag(statePtr, INPUT_SAW_CR); + } else if (*src == '\n') { + src++; srcLen--; } + lesser = (dstLen < srcLen) ? dstLen : srcLen; } - srcLen = src - srcStart; - dstLen = dst - dstStart; + memmove(dst, src, lesser); + srcLen = src + lesser - srcStart; + dstLen = dst + lesser - dstStart; break; } default: - return 0; + Tcl_Panic("unknown input translation %d", statePtr->inputTranslation); } *dstLenPtr = dstLen; + *srcLenPtr = srcLen; - if ((eof != NULL) && (srcStart + srcLen >= eof)) { + if (srcStart + srcLen == eof) { /* * EOF character was seen in EOL translated range. Leave current file * position pointing at the EOF character, but don't store the EOF @@ -6426,12 +6050,8 @@ TranslateInputEOL( SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); statePtr->inputEncodingFlags |= TCL_ENCODING_END; - ResetFlag(statePtr, INPUT_SAW_CR | INPUT_NEED_NL); - return 1; + ResetFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR); } - - *srcLenPtr = srcLen; - return 0; } /* @@ -6451,11 +6071,11 @@ TranslateInputEOL( *---------------------------------------------------------------------- */ -ssize_t +int Tcl_Ungets( Tcl_Channel chan, /* The channel for which to add the input. */ const char *str, /* The input itself. */ - size_t length, /* The length of the input. */ + int len, /* The length of the input. */ int atEnd) /* If non-zero, add at end of queue; otherwise * add at head of queue. */ { @@ -6463,7 +6083,6 @@ Tcl_Ungets( ChannelState *statePtr; /* State of actual channel. */ ChannelBuffer *bufPtr; /* Buffer to contain the data. */ int flags; - ssize_t len = (ssize_t) length; chanPtr = (Channel *) chan; statePtr = chanPtr->state; @@ -6486,16 +6105,15 @@ Tcl_Ungets( statePtr->flags = flags; /* - * If we have encountered a sticky EOF, just punt without storing (sticky - * EOF is set if we have seen the input eofChar, to prevent reading beyond - * the eofChar). Otherwise, clear the EOF flags, and clear the BLOCKED - * bit. We want to discover these conditions anew in each operation. + * Clear the EOF flags, and clear the BLOCKED bit. */ - - if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { - goto done; + + if (GotFlag(statePtr, CHANNEL_EOF)) { + statePtr->inputEncodingFlags |= TCL_ENCODING_START; } - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_EOF); + ResetFlag(statePtr, + CHANNEL_BLOCKED | CHANNEL_STICKY_EOF | CHANNEL_EOF | INPUT_SAW_CR); + statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; bufPtr = AllocChannelBuffer(len); memcpy(InsertPoint(bufPtr), str, (size_t) len); @@ -6560,14 +6178,6 @@ Tcl_Flush( return -1; } - /* - * Force current output buffer to be output also. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - result = FlushChannel(NULL, chanPtr, 0); if (result != 0) { return TCL_ERROR; @@ -6618,7 +6228,7 @@ DiscardInputQueued( */ if (discardSavedBuffers && statePtr->saveInBufPtr != NULL) { - ckfree(statePtr->saveInBufPtr); + ReleaseChannelBuffer(statePtr->saveInBufPtr); statePtr->saveInBufPtr = NULL; } } @@ -6630,6 +6240,9 @@ DiscardInputQueued( * * Reads input data from a device into a channel buffer. * + * IMPORTANT! This routine is only called on a chanPtr argument + * that is the top channel of a stack! + * * Results: * The return value is the Posix error code if an error occurred while * reading from the file, or 0 otherwise. @@ -6644,13 +6257,21 @@ static int GetInput( Channel *chanPtr) /* Channel to read input from. */ { - size_t toRead; /* How much to read? */ + int toRead; /* How much to read? */ int result; /* Of calling driver. */ - ssize_t nread; /* How much was read from channel? */ + int nread; /* How much was read from channel? */ ChannelBuffer *bufPtr; /* New buffer to add to input queue. */ ChannelState *statePtr = chanPtr->state; /* State info for channel */ + /* + * Verify that all callers know better than to call us when + * it's recorded that the next char waiting to be read is the + * eofchar. + */ + + assert( !GotFlag(statePtr, CHANNEL_STICKY_EOF) ); + /* * Prevent reading from a dead channel -- a channel that has been closed * but not yet deallocated, which can happen if the exit handler for @@ -6663,20 +6284,34 @@ GetInput( } /* + * WARNING: There was once a comment here claiming that it was + * a bad idea to make another call to the inputproc of a channel + * driver when EOF has already been detected on the channel. Through + * much of Tcl's history, this warning was then completely negated + * by having all (most?) read paths clear the EOF setting before + * reaching here. So we had a guard that was never triggered. + * + * Don't be tempted to restore the guard. Even if EOF is set on + * the channel, continue through and call the inputproc again. This + * is the way to enable the ability to [read] again beyond the EOF, + * which seems a strange thing to do, but for which use cases exist + * [Tcl Bug 5adc350683] and which may even be essential for channels + * representing things like ttys or other devices where the stream + * might take the logical form of a series of 'files' separated by + * an EOF condition. + */ + + /* * First check for more buffers in the pushback area of the topmost * channel in the stack and use them. They can be the result of a * transformation which went away without reading all the information * placed in the area when it was stacked. - * - * Two possibilities for the state: No buffers in it, or a single empty - * buffer. In the latter case we can recycle it now. */ if (chanPtr->inQueueHead != NULL) { - if (statePtr->inQueueHead != NULL) { - RecycleBuffer(statePtr, statePtr->inQueueHead, 0); - statePtr->inQueueHead = NULL; - } + + /* TODO: Tests to cover this. */ + assert(statePtr->inQueueHead == NULL); statePtr->inQueueHead = chanPtr->inQueueHead; statePtr->inQueueTail = chanPtr->inQueueTail; @@ -6697,21 +6332,21 @@ GetInput( */ bufPtr = statePtr->inQueueTail; - if ((bufPtr != NULL) && !IsBufferFull(bufPtr)) { - toRead = SpaceLeft(bufPtr); - } else { + + if ((bufPtr == NULL) || IsBufferFull(bufPtr)) { bufPtr = statePtr->saveInBufPtr; statePtr->saveInBufPtr = NULL; /* * Check the actual buffersize against the requested buffersize. - * Buffers which are smaller than requested are squashed. This is done + * Saved buffers of the wrong size are squashed. This is done * to honor dynamic changes of the buffersize made by the user. + * TODO: Tests to cover this. */ if ((bufPtr != NULL) - && (bufPtr->bufLength - BUFFER_PADDING < statePtr->bufSize)) { - ckfree(bufPtr); + && (bufPtr->bufLength - BUFFER_PADDING != statePtr->bufSize)) { + ReleaseChannelBuffer(bufPtr); bufPtr = NULL; } @@ -6720,21 +6355,8 @@ GetInput( } bufPtr->nextPtr = NULL; - /* - * SF #427196: Use the actual size of the buffer to determine the - * number of bytes to read from the channel and not the size for new - * buffers. They can be different if the buffersize was changed - * between reads. - * - * Note: This affects performance negatively if the buffersize was - * extended but this small buffer is reused for all subsequent reads. - * The system never uses buffers with the requested bigger size in - * that case. An adjunct patch could try and delete all unused buffers - * it encounters and which are smaller than the formally requested - * buffersize. - */ - toRead = SpaceLeft(bufPtr); + assert(toRead == statePtr->bufSize); if (statePtr->inQueueTail == NULL) { statePtr->inQueueHead = bufPtr; @@ -6742,75 +6364,22 @@ GetInput( statePtr->inQueueTail->nextPtr = bufPtr; } statePtr->inQueueTail = bufPtr; + } else { + toRead = SpaceLeft(bufPtr); } - /* - * If EOF is set, we should avoid calling the driver because on some - * platforms it is impossible to read from a device after EOF. - */ - - if (GotFlag(statePtr, CHANNEL_EOF)) { - return 0; - } - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * [Bug 943274]: Better emulation of non-blocking channels for channels - * without BlockModeProc, by keeping track of true fileevents generated by - * the OS == Data waiting and reading if and only if we are sure to have - * data. - */ - - if (GotFlag(statePtr, CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !GotFlag(statePtr, CHANNEL_HAS_MORE_DATA)) { - /* - * Bypass the driver, it would block, as no data is available - */ + PreserveChannelBuffer(bufPtr); + nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead); - nread = -1; - result = EWOULDBLOCK; - } else -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - { - nread = ChanRead(chanPtr, InsertPoint(bufPtr), toRead, &result); - } - - if (nread > 0) { + if (nread < 0) { + result = Tcl_GetErrno(); + } else { + result = 0; bufPtr->nextAdded += nread; - - /* - * If we get a short read, signal up that we may be BLOCKED. We should - * avoid calling the driver because on some platforms we will block in - * the low level reading code even though the channel is set into - * nonblocking mode. - */ - - if (nread < toRead) { - SetFlag(statePtr, CHANNEL_BLOCKED); - } - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - if (nread <= toRead) { - /* - * [Bug 943274]: We have read the available data, clear flag. - */ - - ResetFlag(statePtr, CHANNEL_HAS_MORE_DATA); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - } else if (nread == 0) { - SetFlag(statePtr, CHANNEL_EOF); - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - } else if (nread < 0) { - if ((result == EWOULDBLOCK) || (result == EAGAIN)) { - SetFlag(statePtr, CHANNEL_BLOCKED); - result = EAGAIN; - } - Tcl_SetErrno(result); - return result; } - return 0; + + ReleaseChannelBuffer(bufPtr); + return result; } /* @@ -6914,8 +6483,12 @@ Tcl_Seek( * point. Also clear CR related flags. */ - ResetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF | CHANNEL_BLOCKED | - INPUT_SAW_CR); + if (GotFlag(statePtr, CHANNEL_EOF)) { + statePtr->inputEncodingFlags |= TCL_ENCODING_START; + } + statePtr->flags &= + ~(CHANNEL_EOF | CHANNEL_STICKY_EOF | CHANNEL_BLOCKED | INPUT_SAW_CR); + statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; /* * If the channel is in asynchronous output mode, switch it back to @@ -6938,15 +6511,6 @@ Tcl_Seek( } /* - * If there is data buffered in statePtr->curOutPtr then mark the channel - * as ready to flush before invoking FlushChannel. - */ - - if ((statePtr->curOutPtr != NULL) && IsBufferReady(statePtr->curOutPtr)) { - SetFlag(statePtr, BUFFER_READY); - } - - /* * If the flush fails we cannot recover the original position. In that * case the seek is not attempted because we do not know where the access * position is - instead we return the error. FlushChannel has already @@ -6959,10 +6523,23 @@ Tcl_Seek( } else { /* * Now seek to the new position in the channel as requested by the - * caller. + * caller. Note that we prefer the wideSeekProc if that is available + * and non-NULL... */ - curPos = ChanSeek(chanPtr, offset, mode, &result); + if (HaveVersion(chanPtr->typePtr, TCL_CHANNEL_VERSION_3) && + chanPtr->typePtr->wideSeekProc != NULL) { + curPos = (chanPtr->typePtr->wideSeekProc) (chanPtr->instanceData, + offset, mode, &result); + } else if (offset < Tcl_LongAsWide(LONG_MIN) || + offset > Tcl_LongAsWide(LONG_MAX)) { + result = EOVERFLOW; + curPos = Tcl_LongAsWide(-1); + } else { + curPos = Tcl_LongAsWide((chanPtr->typePtr->seekProc) ( + chanPtr->instanceData, Tcl_WideAsLong(offset), mode, + &result)); + } if (curPos == Tcl_LongAsWide(-1)) { Tcl_SetErrno(result); } @@ -7057,13 +6634,25 @@ Tcl_Tell( inputBuffered = Tcl_InputBuffered(chan); outputBuffered = Tcl_OutputBuffered(chan); + if ((inputBuffered != 0) && (outputBuffered != 0)) { + /*Tcl_SetErrno(EFAULT);*/ + /*return Tcl_LongAsWide(-1);*/ + } + /* * Get the current position in the device and compute the position where * the next character will be read or written. Note that we prefer the * wideSeekProc if that is available and non-NULL... */ - curPos = ChanSeek(chanPtr, Tcl_LongAsWide(0), SEEK_CUR, &result); + if (HaveVersion(chanPtr->typePtr, TCL_CHANNEL_VERSION_3) && + chanPtr->typePtr->wideSeekProc != NULL) { + curPos = (chanPtr->typePtr->wideSeekProc) (chanPtr->instanceData, + Tcl_LongAsWide(0), SEEK_CUR, &result); + } else { + curPos = Tcl_LongAsWide((chanPtr->typePtr->seekProc) ( + chanPtr->instanceData, 0, SEEK_CUR, &result)); + } if (curPos == Tcl_LongAsWide(-1)) { Tcl_SetErrno(result); return Tcl_LongAsWide(-1); @@ -7078,6 +6667,48 @@ Tcl_Tell( /* *--------------------------------------------------------------------------- * + * Tcl_SeekOld, Tcl_TellOld -- + * + * Backward-compatability versions of the seek/tell interface that do not + * support 64-bit offsets. This interface is not documented or expected + * to be supported indefinitely. + * + * Results: + * As for Tcl_Seek and Tcl_Tell respectively, except truncated to + * whatever value will fit in an 'int'. + * + * Side effects: + * As for Tcl_Seek and Tcl_Tell respectively. + * + *--------------------------------------------------------------------------- + */ + +int +Tcl_SeekOld( + Tcl_Channel chan, /* The channel on which to seek. */ + int offset, /* Offset to seek to. */ + int mode) /* Relative to which location to seek? */ +{ + Tcl_WideInt wOffset, wResult; + + wOffset = Tcl_LongAsWide((long)offset); + wResult = Tcl_Seek(chan, wOffset, mode); + return (int)Tcl_WideAsLong(wResult); +} + +int +Tcl_TellOld( + Tcl_Channel chan) /* The channel to return pos for. */ +{ + Tcl_WideInt wResult; + + wResult = Tcl_Tell(chan); + return (int)Tcl_WideAsLong(wResult); +} + +/* + *--------------------------------------------------------------------------- + * * Tcl_TruncateChannel -- * * Truncate a channel to the given length. @@ -7113,7 +6744,7 @@ Tcl_TruncateChannel( return TCL_ERROR; } - if (!GotFlag(chanPtr->state, TCL_WRITABLE)) { + if (!(chanPtr->state->flags & TCL_WRITABLE)) { /* * We require that the file was opened of writing. Do that check now * so that we only flush if we think we're going to succeed. @@ -7130,7 +6761,7 @@ Tcl_TruncateChannel( WillWrite(chanPtr); - if (WillRead(chanPtr)) { + if (WillRead(chanPtr) < 0) { return TCL_ERROR; } @@ -7210,7 +6841,7 @@ CheckChannelErrors( * Fail if the channel is not opened for desired operation. */ - if ((statePtr->flags & direction) == 0) { + if (GotFlag(statePtr, direction) == 0) { Tcl_SetErrno(EACCES); return -1; } @@ -7229,17 +6860,7 @@ CheckChannelErrors( } if (direction == TCL_READABLE) { - /* - * If we have not encountered a sticky EOF, clear the EOF bit (sticky - * EOF is set if we have seen the input eofChar, to prevent reading - * beyond the eofChar). Also, always clear the BLOCKED bit. We want to - * discover these conditions anew in each operation. - */ - - if (!GotFlag(statePtr, CHANNEL_STICKY_EOF)) { - ResetFlag(statePtr, CHANNEL_EOF); - } - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); + ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA); } return 0; @@ -7268,9 +6889,7 @@ Tcl_Eof( ChannelState *statePtr = ((Channel *) chan)->state; /* State of real channel structure. */ - return (GotFlag(statePtr, CHANNEL_STICKY_EOF) || - (GotFlag(statePtr, CHANNEL_EOF) && - (Tcl_InputBuffered(chan) == 0))) ? 1 : 0; + return GotFlag(statePtr, CHANNEL_EOF) ? 1 : 0; } /* @@ -7449,20 +7068,32 @@ Tcl_SetChannelBufferSize( */ if (sz < 1) { - sz = 1; + sz = 1; } else if (sz > MAX_CHANNEL_BUFFER_SIZE) { - sz = MAX_CHANNEL_BUFFER_SIZE; + sz = MAX_CHANNEL_BUFFER_SIZE; } statePtr = ((Channel *) chan)->state; + + if (statePtr->bufSize == sz) { + return; + } statePtr->bufSize = sz; - if (statePtr->outputStage != NULL) { - ckfree(statePtr->outputStage); - statePtr->outputStage = NULL; + /* + * If bufsize changes, need to get rid of old utility buffer. + */ + + if (statePtr->saveInBufPtr != NULL) { + RecycleBuffer(statePtr, statePtr->saveInBufPtr, 1); + statePtr->saveInBufPtr = NULL; } - if ((statePtr->encoding != NULL) && GotFlag(statePtr, TCL_WRITABLE)) { - statePtr->outputStage = ckalloc(statePtr->bufSize + 2); + if ((statePtr->inQueueHead != NULL) + && (statePtr->inQueueHead->nextPtr == NULL) + && IsBufferEmpty(statePtr->inQueueHead)) { + RecycleBuffer(statePtr, statePtr->inQueueHead, 1); + statePtr->inQueueHead = NULL; + statePtr->inQueueTail = NULL; } } @@ -7530,36 +7161,33 @@ Tcl_BadChannelOption( * standard generic options. Can be NULL for * generic options only. */ { - static const char *genericopt = - "blocking buffering buffersize encoding eofchar translation"; - if (interp != NULL) { + const char *genericopt = + "blocking buffering buffersize encoding eofchar translation"; const char **argv; - size_t argc, i; + int argc, i; Tcl_DString ds; - Tcl_Obj *errObj; Tcl_DStringInit(&ds); - Tcl_DStringAppend(&ds, genericopt, TCL_STRLEN); + Tcl_DStringAppend(&ds, genericopt, -1); if (optionList && (*optionList)) { - TclDStringAppendLiteral(&ds, " "); - Tcl_DStringAppend(&ds, optionList, TCL_STRLEN); + Tcl_DStringAppend(&ds, " ", 1); + Tcl_DStringAppend(&ds, optionList, -1); } if (Tcl_SplitList(interp, Tcl_DStringValue(&ds), &argc, &argv) != TCL_OK) { Tcl_Panic("malformed option list in channel driver"); } Tcl_ResetResult(interp); - errObj = Tcl_ObjPrintf("bad option \"%s\": should be one of ", - optionName); + Tcl_AppendResult(interp, "bad option \"", optionName, + "\": should be one of ", NULL); argc--; for (i = 0; i < argc; i++) { - Tcl_AppendPrintfToObj(errObj, "-%s, ", argv[i]); + Tcl_AppendResult(interp, "-", argv[i], ", ", NULL); } - Tcl_AppendPrintfToObj(errObj, "or -%s", argv[i]); - Tcl_SetObjResult(interp, errObj); + Tcl_AppendResult(interp, "or -", argv[i], NULL); Tcl_DStringFree(&ds); - ckfree(argv); + ckfree((char *) argv); } Tcl_SetErrno(EINVAL); return TCL_ERROR; @@ -7621,9 +7249,9 @@ Tcl_GetChannelOption( */ if (statePtr->csPtrR) { - flags = statePtr->csPtrR->readFlags; + flags = statePtr->csPtrR->readFlags; } else if (statePtr->csPtrW) { - flags = statePtr->csPtrW->writeFlags; + flags = statePtr->csPtrW->writeFlags; } else { flags = statePtr->flags; } @@ -7783,8 +7411,8 @@ Tcl_GetChannelOption( * and message. */ - return chanPtr->typePtr->getOptionProc(chanPtr->instanceData, interp, - optionName, dsPtr); + return (chanPtr->typePtr->getOptionProc) (chanPtr->instanceData, + interp, optionName, dsPtr); } else { /* * No driver specific options case. @@ -7826,7 +7454,7 @@ Tcl_SetChannelOption( ChannelState *statePtr = chanPtr->state; /* State info for channel */ size_t len; /* Length of optionName string. */ - size_t argc; + int argc; const char **argv; /* @@ -7835,9 +7463,8 @@ Tcl_SetChannelOption( if (statePtr->csPtrR || statePtr->csPtrW) { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "unable to set channel options: background copy in" - " progress", TCL_STRLEN)); + Tcl_AppendResult(interp, "unable to set channel options: " + "background copy in progress", NULL); } return TCL_ERROR; } @@ -7876,7 +7503,8 @@ Tcl_SetChannelOption( } else if (HaveOpt(7, "-buffering")) { len = strlen(newValue); if ((newValue[0] == 'f') && (strncmp(newValue, "full", len) == 0)) { - ResetFlag(statePtr, CHANNEL_UNBUFFERED | CHANNEL_LINEBUFFERED); + statePtr->flags &= + ~(CHANNEL_UNBUFFERED|CHANNEL_LINEBUFFERED); } else if ((newValue[0] == 'l') && (strncmp(newValue, "line", len) == 0)) { ResetFlag(statePtr, CHANNEL_UNBUFFERED); @@ -7885,11 +7513,12 @@ Tcl_SetChannelOption( (strncmp(newValue, "none", len) == 0)) { ResetFlag(statePtr, CHANNEL_LINEBUFFERED); SetFlag(statePtr, CHANNEL_UNBUFFERED); - } else if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "bad value for -buffering: must be one of" - " full, line, or none", TCL_STRLEN)); - return TCL_ERROR; + } else { + if (interp) { + Tcl_AppendResult(interp, "bad value for -buffering: " + "must be one of full, line, or none", NULL); + return TCL_ERROR; + } } return TCL_OK; } else if (HaveOpt(7, "-buffersize")) { @@ -7899,6 +7528,7 @@ Tcl_SetChannelOption( return TCL_ERROR; } Tcl_SetChannelBufferSize(chan, newBufferSize); + return TCL_OK; } else if (HaveOpt(2, "-encoding")) { Tcl_Encoding encoding; @@ -7916,7 +7546,8 @@ Tcl_SetChannelOption( * iso2022, the terminated escape sequence must write to the buffer. */ - if ((statePtr->encoding != NULL) && (statePtr->curOutPtr != NULL) + if ((statePtr->encoding != NULL) + && !(statePtr->outputEncodingFlags & TCL_ENCODING_START) && (CheckChannelErrors(statePtr, TCL_WRITABLE) == 0)) { statePtr->outputEncodingFlags |= TCL_ENCODING_END; WriteChars(chanPtr, "", 0); @@ -7929,6 +7560,7 @@ Tcl_SetChannelOption( statePtr->outputEncodingFlags = TCL_ENCODING_START; ResetFlag(statePtr, CHANNEL_NEED_MORE_DATA); UpdateInterest(chanPtr); + return TCL_OK; } else if (HaveOpt(2, "-eofchar")) { if (Tcl_SplitList(interp, newValue, &argc, &argv) == TCL_ERROR) { return TCL_ERROR; @@ -7940,14 +7572,12 @@ Tcl_SetChannelOption( int outIndex = (argc - 1); int inValue = (int) argv[0][0]; int outValue = (int) argv[outIndex][0]; - if (inValue & 0x80 || outValue & 0x80) { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "bad value for -eofchar: must be non-NUL ASCII" - " character", TCL_STRLEN)); + Tcl_AppendResult(interp, "bad value for -eofchar: ", + "must be non-NUL ASCII character", NULL); } - ckfree(argv); + ckfree((char *) argv); return TCL_ERROR; } if (GotFlag(statePtr, TCL_READABLE)) { @@ -7958,15 +7588,15 @@ Tcl_SetChannelOption( } } else { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( + Tcl_AppendResult(interp, "bad value for -eofchar: should be a list of zero," - " one, or two elements", TCL_STRLEN)); + " one, or two elements", NULL); } - ckfree(argv); + ckfree((char *) argv); return TCL_ERROR; } if (argv != NULL) { - ckfree(argv); + ckfree((char *) argv); } /* @@ -7975,7 +7605,13 @@ Tcl_SetChannelOption( * ahead'. Ditto for blocked. */ - ResetFlag(statePtr, CHANNEL_EOF|CHANNEL_STICKY_EOF|CHANNEL_BLOCKED); + if (GotFlag(statePtr, CHANNEL_EOF)) { + statePtr->inputEncodingFlags |= TCL_ENCODING_START; + } + statePtr->flags &= + ~(CHANNEL_EOF | CHANNEL_STICKY_EOF | CHANNEL_BLOCKED); + statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; + return TCL_OK; } else if (HaveOpt(1, "-translation")) { const char *readMode, *writeMode; @@ -7992,17 +7628,16 @@ Tcl_SetChannelOption( writeMode = GotFlag(statePtr, TCL_WRITABLE) ? argv[1] : NULL; } else { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( + Tcl_AppendResult(interp, "bad value for -translation: must be a one or two" - " element list", TCL_STRLEN)); + " element list", NULL); } - ckfree(argv); + ckfree((char *) argv); return TCL_ERROR; } if (readMode) { TclEolTranslation translation; - if (*readMode == '\0') { translation = statePtr->inputTranslation; } else if (strcmp(readMode, "auto") == 0) { @@ -8022,12 +7657,12 @@ Tcl_SetChannelOption( translation = TCL_PLATFORM_TRANSLATION; } else { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "bad value for -translation: must be one of " - "auto, binary, cr, lf, crlf, or platform", - TCL_STRLEN)); + Tcl_AppendResult(interp, + "bad value for -translation: " + "must be one of auto, binary, cr, lf, crlf," + " or platform", NULL); } - ckfree(argv); + ckfree((char *) argv); return TCL_ERROR; } @@ -8073,51 +7708,24 @@ Tcl_SetChannelOption( statePtr->outputTranslation = TCL_PLATFORM_TRANSLATION; } else { if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "bad value for -translation: must be one of " - "auto, binary, cr, lf, crlf, or platform", - TCL_STRLEN)); + Tcl_AppendResult(interp, + "bad value for -translation: " + "must be one of auto, binary, cr, lf, crlf," + " or platform", NULL); } - ckfree(argv); + ckfree((char *) argv); return TCL_ERROR; } } - ckfree(argv); + ckfree((char *) argv); return TCL_OK; } else if (chanPtr->typePtr->setOptionProc != NULL) { - return chanPtr->typePtr->setOptionProc(chanPtr->instanceData, interp, - optionName, newValue); + return (*chanPtr->typePtr->setOptionProc)(chanPtr->instanceData, + interp, optionName, newValue); } else { return Tcl_BadChannelOption(interp, optionName, NULL); } - /* - * If bufsize changes, need to get rid of old utility buffer. - */ - - if (statePtr->saveInBufPtr != NULL) { - RecycleBuffer(statePtr, statePtr->saveInBufPtr, 1); - statePtr->saveInBufPtr = NULL; - } - if ((statePtr->inQueueHead != NULL) - && (statePtr->inQueueHead->nextPtr == NULL) - && IsBufferEmpty(statePtr->inQueueHead)) { - RecycleBuffer(statePtr, statePtr->inQueueHead, 1); - statePtr->inQueueHead = NULL; - statePtr->inQueueTail = NULL; - } - - /* - * If encoding or bufsize changes, need to update output staging buffer. - */ - - if (statePtr->outputStage != NULL) { - ckfree(statePtr->outputStage); - statePtr->outputStage = NULL; - } - if ((statePtr->encoding != NULL) && GotFlag(statePtr, TCL_WRITABLE)) { - statePtr->outputStage = ckalloc(statePtr->bufSize + 2); - } return TCL_OK; } @@ -8168,7 +7776,7 @@ CleanupChannelHandlers( TclChannelEventScriptInvoker, sPtr); TclDecrRefCount(sPtr->scriptPtr); - ckfree(sPtr); + ckfree((char *) sPtr); } else { prevPtr = sPtr; } @@ -8209,21 +7817,6 @@ Tcl_NotifyChannel( Channel *upChanPtr; const Tcl_ChannelType *upTypePtr; -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * [SF Tcl Bug 943274] For a non-blocking channel without blockmodeproc we - * keep track of actual input coming from the OS so that we can do a - * credible imitation of non-blocking behaviour. - */ - - if ((mask & TCL_READABLE) && - GotFlag(statePtr, CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL) && - !GotFlag(statePtr, CHANNEL_TIMER_FEV)) { - SetFlag(statePtr, CHANNEL_HAS_MORE_DATA); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - /* * In contrast to the other API functions this procedure walks towards the * top of a stack and not down from it. @@ -8236,14 +7829,14 @@ Tcl_NotifyChannel( * their own events and pass them upward. */ - while (mask && (chanPtr->upChanPtr != NULL)) { + while (mask && (chanPtr->upChanPtr != (NULL))) { Tcl_DriverHandlerProc *upHandlerProc; upChanPtr = chanPtr->upChanPtr; upTypePtr = upChanPtr->typePtr; upHandlerProc = Tcl_ChannelHandlerProc(upTypePtr); if (upHandlerProc != NULL) { - mask = upHandlerProc(upChanPtr->instanceData, mask); + mask = (*upHandlerProc) (upChanPtr->instanceData, mask); } /* @@ -8273,7 +7866,7 @@ Tcl_NotifyChannel( * Preserve the channel struct in case the script closes it. */ - Tcl_Preserve(channel); + TclChannelPreserve((Tcl_Channel)channel); Tcl_Preserve(statePtr); /* @@ -8283,8 +7876,9 @@ Tcl_NotifyChannel( */ if (GotFlag(statePtr, BG_FLUSH_SCHEDULED) && (mask & TCL_WRITABLE)) { - FlushChannel(NULL, chanPtr, 1); - mask &= ~TCL_WRITABLE; + if (0 == FlushChannel(NULL, chanPtr, 1)) { + mask &= ~TCL_WRITABLE; + } } /* @@ -8304,7 +7898,7 @@ Tcl_NotifyChannel( if ((chPtr->mask & mask) != 0) { nh.nextHandlerPtr = chPtr->nextPtr; - chPtr->proc(chPtr->clientData, mask); + (*(chPtr->proc))(chPtr->clientData, chPtr->mask & mask); chPtr = nh.nextHandlerPtr; } else { chPtr = chPtr->nextPtr; @@ -8318,11 +7912,16 @@ Tcl_NotifyChannel( */ if (chanPtr->typePtr != NULL) { + /* + * TODO: This call may not be needed. If a handler induced a + * change in interest, that handler should have made its own + * UpdateInterest() call, one would think. + */ UpdateInterest(chanPtr); } Tcl_Release(statePtr); - Tcl_Release(channel); + TclChannelRelease(channel); tsdPtr->nestedHandlerPtr = nh.nestedHandlerPtr; } @@ -8352,6 +7951,11 @@ UpdateInterest( /* State info for channel */ int mask = statePtr->interestMask; + if (chanPtr->typePtr == NULL) { + /* Do not update interest on a closed channel */ + return; + } + /* * If there are flushed buffers waiting to be written, then we need to * watch for the channel to become writable. @@ -8415,12 +8019,12 @@ UpdateInterest( mask &= ~TCL_EXCEPTION; if (!statePtr->timer) { - statePtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, - ChannelTimerProc, chanPtr); + statePtr->timer = Tcl_CreateTimerHandler(0, ChannelTimerProc, + chanPtr); } } } - ChanWatch(chanPtr, mask); + (chanPtr->typePtr->watchProc)(chanPtr->instanceData, mask); } /* @@ -8457,31 +8061,9 @@ ChannelTimerProc( * before UpdateInterest gets called by Tcl_NotifyChannel. */ - statePtr->timer = Tcl_CreateTimerHandler(SYNTHETIC_EVENT_TIME, - ChannelTimerProc,chanPtr); - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - /* - * Set the TIMER flag to notify the higher levels that the driver - * might have no data for us. We do this only if we are in - * non-blocking mode and the driver has no BlockModeProc because only - * then we really don't know if the driver will block or not. A - * similar test is done in "PeekAhead". - */ - - if (GotFlag(statePtr, CHANNEL_NONBLOCKING) && - (Tcl_ChannelBlockModeProc(chanPtr->typePtr) == NULL)) { - SetFlag(statePtr, CHANNEL_TIMER_FEV); - } -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - + statePtr->timer = Tcl_CreateTimerHandler(0, ChannelTimerProc,chanPtr); Tcl_Preserve(statePtr); Tcl_NotifyChannel((Tcl_Channel) chanPtr, TCL_READABLE); - -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - ResetFlag(statePtr, CHANNEL_TIMER_FEV); -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ - Tcl_Release(statePtr); } else { statePtr->timer = NULL; @@ -8540,7 +8122,7 @@ Tcl_CreateChannelHandler( } } if (chPtr == NULL) { - chPtr = ckalloc(sizeof(ChannelHandler)); + chPtr = (ChannelHandler *) ckalloc(sizeof(ChannelHandler)); chPtr->mask = 0; chPtr->proc = proc; chPtr->clientData = clientData; @@ -8644,7 +8226,7 @@ Tcl_DeleteChannelHandler( } else { prevChPtr->nextPtr = chPtr->nextPtr; } - ckfree(chPtr); + ckfree((char *) chPtr); /* * Recompute the interest list for the channel, so that infinite loops @@ -8695,7 +8277,6 @@ DeleteScriptRecord( if (esPtr == statePtr->scriptRecordPtr) { statePtr->scriptRecordPtr = esPtr->nextPtr; } else { - CLANG_ASSERT(prevEsPtr); prevEsPtr->nextPtr = esPtr->nextPtr; } @@ -8703,7 +8284,7 @@ DeleteScriptRecord( TclChannelEventScriptInvoker, esPtr); TclDecrRefCount(esPtr->scriptPtr); - ckfree(esPtr); + ckfree((char *) esPtr); break; } @@ -8752,12 +8333,12 @@ CreateScriptRecord( makeCH = (esPtr == NULL); if (makeCH) { - esPtr = ckalloc(sizeof(EventScriptRecord)); + esPtr = (EventScriptRecord *) ckalloc(sizeof(EventScriptRecord)); } /* * Initialize the structure before calling Tcl_CreateChannelHandler, - * because a reflected channel calling 'chan postevent' aka + * because a reflected channel caling 'chan postevent' aka * 'Tcl_NotifyChannel' in its 'watch'Proc will invoke * 'TclChannelEventScriptInvoker' immediately, and we do not wish it to * see uninitialized memory and crash. See [Bug 2918110]. @@ -8820,7 +8401,7 @@ TclChannelEventScriptInvoker( */ Tcl_Preserve(interp); - Tcl_Preserve(chanPtr); + TclChannelPreserve((Tcl_Channel)chanPtr); result = Tcl_EvalObjEx(interp, esPtr->scriptPtr, TCL_EVAL_GLOBAL); /* @@ -8835,9 +8416,9 @@ TclChannelEventScriptInvoker( if (chanPtr->typePtr != NULL) { DeleteScriptRecord(interp, chanPtr, mask); } - Tcl_BackgroundException(interp, result); + TclBackgroundException(interp, result); } - Tcl_Release(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); Tcl_Release(interp); } @@ -8866,17 +8447,17 @@ Tcl_FileEventObjCmd( ClientData clientData, /* Not used. */ Tcl_Interp *interp, /* Interpreter in which the channel for which * to create the handler is found. */ - size_t objc, /* Number of arguments. */ + int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { Channel *chanPtr; /* The channel to create the handler for. */ ChannelState *statePtr; /* State info for channel */ Tcl_Channel chan; /* The opaque type for the channel. */ - const char *chanName; + char *chanName; int modeIndex; /* Index of mode argument. */ int mask; - static const char *const modeOptions[] = {"readable", "writable", NULL}; - static const int maskArray[] = {TCL_READABLE, TCL_WRITABLE}; + static const char *modeOptions[] = {"readable", "writable", NULL}; + static CONST int maskArray[] = {TCL_READABLE, TCL_WRITABLE}; if ((objc != 3) && (objc != 4)) { Tcl_WrongNumArgs(interp, 1, objv, "channelId event ?script?"); @@ -8895,9 +8476,9 @@ Tcl_FileEventObjCmd( } chanPtr = (Channel *) chan; statePtr = chanPtr->state; - if ((statePtr->flags & mask) == 0) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf("channel is not %s", - (mask == TCL_READABLE) ? "readable" : "writable")); + if (GotFlag(statePtr, mask) == 0) { + Tcl_AppendResult(interp, "channel is not ", + (mask == TCL_READABLE) ? "readable" : "writable", NULL); return TCL_ERROR; } @@ -8907,7 +8488,6 @@ Tcl_FileEventObjCmd( if (objc == 3) { EventScriptRecord *esPtr; - for (esPtr = statePtr->scriptRecordPtr; esPtr != NULL; esPtr = esPtr->nextPtr) { if ((esPtr->interp == interp) && (esPtr->mask == mask)) { @@ -8991,7 +8571,7 @@ TclCopyChannel( Tcl_Interp *interp, /* Current interpreter. */ Tcl_Channel inChan, /* Channel to read from. */ Tcl_Channel outChan, /* Channel to write to. */ - Tcl_WideInt toRead, /* Amount of data to copy, or -1 for all. */ + int toRead, /* Amount of data to copy, or -1 for all. */ Tcl_Obj *cmdPtr) /* Pointer to script to execute or NULL. */ { Channel *inPtr = (Channel *) inChan; @@ -9004,17 +8584,17 @@ TclCopyChannel( inStatePtr = inPtr->state; outStatePtr = outPtr->state; - if (BUSY_STATE(inStatePtr, TCL_READABLE)) { + if (BUSY_STATE(inStatePtr,TCL_READABLE)) { if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "channel \"%s\" is busy", Tcl_GetChannelName(inChan))); + Tcl_AppendResult(interp, "channel \"", + Tcl_GetChannelName(inChan), "\" is busy", NULL); } return TCL_ERROR; } - if (BUSY_STATE(outStatePtr, TCL_WRITABLE)) { + if (BUSY_STATE(outStatePtr,TCL_WRITABLE)) { if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "channel \"%s\" is busy", Tcl_GetChannelName(outChan))); + Tcl_AppendResult(interp, "channel \"", + Tcl_GetChannelName(outChan), "\" is busy", NULL); } return TCL_ERROR; } @@ -9047,8 +8627,8 @@ TclCopyChannel( * Make sure the output side is unbuffered. */ - outStatePtr->flags = (outStatePtr->flags & ~CHANNEL_LINEBUFFERED) - | CHANNEL_UNBUFFERED; + outStatePtr->flags = (outStatePtr->flags & ~(CHANNEL_LINEBUFFERED)) + | CHANNEL_UNBUFFERED; /* * Allocate a new CopyState to maintain info about the current copy in @@ -9056,14 +8636,14 @@ TclCopyChannel( * completed. */ - csPtr = ckalloc(sizeof(CopyState) + inStatePtr->bufSize); + csPtr = (CopyState *) ckalloc(sizeof(CopyState) + inStatePtr->bufSize); csPtr->bufSize = inStatePtr->bufSize; csPtr->readPtr = inPtr; csPtr->writePtr = outPtr; csPtr->readFlags = readFlags; csPtr->writeFlags = writeFlags; csPtr->toRead = toRead; - csPtr->total = (Tcl_WideInt) 0; + csPtr->total = 0; csPtr->interp = interp; if (cmdPtr) { Tcl_IncrRefCount(cmdPtr); @@ -9116,11 +8696,9 @@ CopyData( Tcl_Obj *cmdPtr, *errObj = NULL, *bufObj = NULL, *msg = NULL; Tcl_Channel inChan, outChan; ChannelState *inStatePtr, *outStatePtr; - int result = TCL_OK; - ssize_t size, sizeb; - size_t sizeout; + int result = TCL_OK, size, sizeb; Tcl_WideInt total; - const char *buffer; + char *buffer; int inBinary, outBinary, sameEncoding; /* Encoding control */ int underflow; /* Input underflow */ @@ -9149,7 +8727,7 @@ CopyData( Tcl_IncrRefCount(bufObj); } - while (csPtr->toRead != (Tcl_WideInt) 0) { + while (csPtr->toRead != 0) { /* * Check for unreported background errors. */ @@ -9173,26 +8751,24 @@ CopyData( * underflow instead to prime the readable fileevent. */ - size = 0; + size = 0; underflow = 1; } else { /* * Read up to bufSize bytes. */ - if ((csPtr->toRead == (Tcl_WideInt) -1) - || (csPtr->toRead > (Tcl_WideInt) csPtr->bufSize)) { + if ((csPtr->toRead == -1) || (csPtr->toRead > csPtr->bufSize)) { sizeb = csPtr->bufSize; } else { - sizeb = (size_t) csPtr->toRead; + sizeb = csPtr->toRead; } if (inBinary || sameEncoding) { - size = DoRead(inStatePtr->topChanPtr, csPtr->buffer, sizeb, - !GotFlag(inStatePtr, CHANNEL_NONBLOCKING)); + size = DoRead(inStatePtr->topChanPtr, csPtr->buffer, sizeb); } else { size = DoReadChars(inStatePtr->topChanPtr, bufObj, sizeb, - 0 /* No append */); + 0 /* No append */); } underflow = (size >= 0) && (size < sizeb); /* Input underflow */ } @@ -9225,8 +8801,8 @@ CopyData( if ((size == 0) && Tcl_Eof(inChan) && !(cmdPtr && (mask == 0))) { break; } - if (cmdPtr && (!Tcl_Eof(inChan) || (mask == 0)) && - !(mask & TCL_READABLE)) { + if (((!Tcl_Eof(inChan)) || (cmdPtr && (mask == 0))) && + !(mask & TCL_READABLE)) { if (mask & TCL_WRITABLE) { Tcl_DeleteChannelHandler(outChan, CopyEventProc, csPtr); } @@ -9248,15 +8824,15 @@ CopyData( if (inBinary || sameEncoding) { buffer = csPtr->buffer; - sizeout = size; + sizeb = size; } else { - buffer = TclGetStringFromObj(bufObj, &sizeout); + buffer = TclGetStringFromObj(bufObj, &sizeb); } if (outBinary || sameEncoding) { - sizeb = DoWrite(outStatePtr->topChanPtr, buffer, sizeout); + sizeb = WriteBytes(outStatePtr->topChanPtr, buffer, sizeb); } else { - sizeb = DoWriteChars(outStatePtr->topChanPtr, buffer, sizeout); + sizeb = WriteChars(outStatePtr->topChanPtr, buffer, sizeb); } /* @@ -9291,7 +8867,7 @@ CopyData( } /* - * Update the current byte count. Do it now so the count is valid + * (UP) Update the current byte count. Do it now so the count is valid * before a return or break takes us out of the loop. The invariant at * the top of the loop should be that csPtr->toRead holds the number * of bytes left to copy. @@ -9317,7 +8893,7 @@ CopyData( * therefore we don't need a writable handler. */ - if (!underflow && GotFlag(outStatePtr, BG_FLUSH_SCHEDULED)) { + if (!underflow && (outStatePtr->flags & BG_FLUSH_SCHEDULED)) { if (!(mask & TCL_WRITABLE)) { if (mask & TCL_READABLE) { Tcl_DeleteChannelHandler(inChan, CopyEventProc, csPtr); @@ -9368,7 +8944,6 @@ CopyData( total = csPtr->total; if (cmdPtr && interp) { int code; - /* * Get a private copy of the command so we can mutate it by adding * arguments. Note that StopCopy frees our saved reference to the @@ -9386,7 +8961,7 @@ CopyData( } code = Tcl_EvalObjEx(interp, cmdPtr, TCL_EVAL_GLOBAL); if (code != TCL_OK) { - Tcl_BackgroundException(interp, code); + TclBackgroundException(interp, code); result = TCL_ERROR; } TclDecrRefCount(cmdPtr); @@ -9411,12 +8986,24 @@ CopyData( * * DoRead -- * - * Reads a given number of bytes from a channel. No encoding conversions - * are applied to the bytes being read. + * Stores up to "bytesToRead" bytes in memory pointed to by "dst". + * These bytes come from reading the channel "chanPtr" and + * performing the configured translations. + * + * No encoding conversions are applied to the bytes being read. * * Results: - * The number of characters read, or -1 on error. Use Tcl_GetErrno() to - * retrieve the error code for the error that occurred. + * The number of bytes actually stored (<= bytesToRead), + * or -1 if there is an error in reading the channel. Use + * Tcl_GetErrno() to retrieve the error code for the error + * that occurred. + * + * The number of bytes stored can be less than the number + * requested when + * - EOF is reached on the channel; or + * - the channel is non-blocking, and we've read all we can + * without blocking. + * - a channel reading error occurs (and we return -1) * * Side effects: * May cause input to be buffered. @@ -9424,541 +9011,199 @@ CopyData( *---------------------------------------------------------------------- */ -static ssize_t +static int DoRead( Channel *chanPtr, /* The channel from which to read. */ - char *bufPtr, /* Where to store input read. */ - size_t toRead, /* Maximum number of bytes to read. */ - int allowShortReads) /* Allow half-blocking (pipes,sockets) */ + char *dst, /* Where to store input read. */ + int bytesToRead) /* Maximum number of bytes to read. */ { ChannelState *statePtr = chanPtr->state; - /* State info for channel */ - size_t copied; /* How many characters were copied into the - * result string? */ - size_t copiedNow; /* How many characters were copied from the - * current input buffer? */ - int result; /* Of calling GetInput. */ - - /* - * If we have not encountered a sticky EOF, clear the EOF bit. Either way - * clear the BLOCKED bit. We want to discover these anew during each - * operation. - */ - - if (!GotFlag(statePtr, CHANNEL_STICKY_EOF)) { - ResetFlag(statePtr, CHANNEL_EOF); - } - ResetFlag(statePtr, CHANNEL_BLOCKED | CHANNEL_NEED_MORE_DATA); - - for (copied = 0; copied < toRead; copied += copiedNow) { - copiedNow = CopyAndTranslateBuffer(statePtr, bufPtr + copied, - toRead - copied); - if (copiedNow == 0) { - if (GotFlag(statePtr, CHANNEL_EOF)) { - goto done; - } - if (GotFlag(statePtr, CHANNEL_BLOCKED)) { - if (GotFlag(statePtr, CHANNEL_NONBLOCKING)) { - goto done; - } - ResetFlag(statePtr, CHANNEL_BLOCKED); - } - result = GetInput(chanPtr); - if (result != 0) { - if (result != EAGAIN) { - copied = -1; - } - goto done; - } - } else if (allowShortReads) { - copied += copiedNow; - break; - } - } + char *p = dst; - ResetFlag(statePtr, CHANNEL_BLOCKED); + assert (bytesToRead >= 0); /* - * Update the notifier state so we don't block while there is still data - * in the buffers. + * Early out when we know a read will get the eofchar. + * + * NOTE: This seems to be a bug. The special handling for + * a zero-char read request ought to come first. As coded + * the EOF due to eofchar has distinguishing behavior from + * the EOF due to reported EOF on the underlying device, and + * that seems undesirable. However recent history indicates + * that new inconsistent behavior in a patchlevel has problems + * too. Keep on keeping on for now. */ - done: - UpdateInterest(chanPtr); - return copied; -} - -/* - *---------------------------------------------------------------------- - * - * CopyAndTranslateBuffer -- - * - * Copy at most one buffer of input to the result space, doing eol - * translations according to mode in effect currently. - * - * Results: - * Number of bytes stored in the result buffer (as opposed to the number - * of bytes read from the channel). May return zero if no input is - * available to be translated. - * - * Side effects: - * Consumes buffered input. May deallocate one buffer. - * - *---------------------------------------------------------------------- - */ - -static size_t -CopyAndTranslateBuffer( - ChannelState *statePtr, /* Channel state from which to read input. */ - char *result, /* Where to store the copied input. */ - size_t space) /* How many bytes are available in result to - * store the copied input? */ -{ - ChannelBuffer *bufPtr; /* The buffer from which to copy bytes. */ - size_t bytesInBuffer; /* How many bytes are available to be copied - * in the current input buffer? */ - size_t copied; /* How many characters were already copied - * into the destination space? */ - size_t i; /* Iterates over the copied input looking for - * the input eofChar. */ - - /* - * If there is no input at all, return zero. The invariant is that either - * there is no buffer in the queue, or if the first buffer is empty, it is - * also the last buffer (and thus there is no input in the queue). Note - * also that if the buffer is empty, we leave it in the queue. - */ + if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { + SetFlag(statePtr, CHANNEL_EOF); + assert( statePtr->inputEncodingFlags & TCL_ENCODING_END ); + assert( !GotFlag(statePtr, CHANNEL_BLOCKED|INPUT_SAW_CR) ); - if (statePtr->inQueueHead == NULL) { + /* TODO: Don't need this call */ + UpdateInterest(chanPtr); return 0; } - bufPtr = statePtr->inQueueHead; - bytesInBuffer = BytesLeft(bufPtr); - copied = 0; - switch (statePtr->inputTranslation) { - case TCL_TRANSLATE_LF: - if (bytesInBuffer == 0) { - return 0; + /* Special handling for zero-char read request. */ + if (bytesToRead == 0) { + if (GotFlag(statePtr, CHANNEL_EOF)) { + statePtr->inputEncodingFlags |= TCL_ENCODING_START; } + ResetFlag(statePtr, CHANNEL_BLOCKED|CHANNEL_EOF); + statePtr->inputEncodingFlags &= ~TCL_ENCODING_END; + /* TODO: Don't need this call */ + UpdateInterest(chanPtr); + return 0; + } + TclChannelPreserve((Tcl_Channel)chanPtr); + while (bytesToRead) { /* - * Copy the current chunk into the result buffer. + * Each pass through the loop is intended to process up to + * one channel buffer. */ - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), space); - bufPtr->nextRemoved += space; - copied = space; - break; - case TCL_TRANSLATE_CR: { - char *end; - - if (bytesInBuffer == 0) { - return 0; - } + int bytesRead, bytesWritten; + ChannelBuffer *bufPtr = statePtr->inQueueHead; /* - * Copy the current chunk into the result buffer, then replace all \r - * with \n. + * Don't read more data if we have what we need. */ - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), space); - bufPtr->nextRemoved += space; - copied = space; + while (!bufPtr || /* We got no buffer! OR */ + (!IsBufferFull(bufPtr) && /* Our buffer has room AND */ + (BytesLeft(bufPtr) < bytesToRead) ) ) { + /* Not enough bytes in it + * yet to fill the dst */ + int code; - for (end = result + copied; result < end; result++) { - if (*result == '\r') { - *result = '\n'; - } - } - break; - } - case TCL_TRANSLATE_CRLF: { - char *src, *end, *dst; - int curByte; + moreData: + code = GetInput(chanPtr); + bufPtr = statePtr->inQueueHead; - /* - * If there is a held-back "\r" at EOF, produce it now. - */ + assert (bufPtr != NULL); - if (bytesInBuffer == 0) { - if ((statePtr->flags & (INPUT_SAW_CR | CHANNEL_EOF)) == - (INPUT_SAW_CR | CHANNEL_EOF)) { - result[0] = '\r'; - ResetFlag(statePtr, INPUT_SAW_CR); - return 1; + if (GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED)) { + /* Further reads cannot do any more */ + break; } - return 0; - } - /* - * Copy the current chunk and replace "\r\n" with "\n" (but not - * standalone "\r"!). - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), space); - bufPtr->nextRemoved += space; - copied = space; - - end = result + copied; - dst = result; - for (src = result; src < end; src++) { - curByte = *src; - if (curByte == '\n') { - ResetFlag(statePtr, INPUT_SAW_CR); - } else if (GotFlag(statePtr, INPUT_SAW_CR)) { - ResetFlag(statePtr, INPUT_SAW_CR); - *dst = '\r'; - dst++; - } - if (curByte == '\r') { - SetFlag(statePtr, INPUT_SAW_CR); - } else { - *dst = (char) curByte; - dst++; + if (code) { + /* Read error */ + UpdateInterest(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); + return -1; } - } - copied = dst - result; - break; - } - case TCL_TRANSLATE_AUTO: { - char *src, *end, *dst; - int curByte; - if (bytesInBuffer == 0) { - return 0; + assert (IsBufferFull(bufPtr)); } - /* - * Loop over the current buffer, converting "\r" and "\r\n" to "\n". - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - memcpy(result, RemovePoint(bufPtr), space); - bufPtr->nextRemoved += space; - copied = space; + assert (bufPtr != NULL); - end = result + copied; - dst = result; - for (src = result; src < end; src++) { - curByte = *src; - if (curByte == '\r') { - SetFlag(statePtr, INPUT_SAW_CR); - *dst = '\n'; - dst++; - } else { - if ((curByte != '\n') || !GotFlag(statePtr, INPUT_SAW_CR)) { - *dst = (char) curByte; - dst++; - } - ResetFlag(statePtr, INPUT_SAW_CR); - } - } - copied = dst - result; - break; - } - default: - Tcl_Panic("unknown eol translation mode"); - } + bytesRead = BytesLeft(bufPtr); + bytesWritten = bytesToRead; - /* - * If an in-stream EOF character is set for this channel, check that the - * input we copied so far does not contain the EOF char. If it does, copy - * only up to and excluding that character. - */ + TranslateInputEOL(statePtr, p, RemovePoint(bufPtr), + &bytesWritten, &bytesRead); + bufPtr->nextRemoved += bytesRead; + p += bytesWritten; + bytesToRead -= bytesWritten; - if (statePtr->inEofChar != 0) { - for (i = 0; i < copied; i++) { - if (result[i] == (char) statePtr->inEofChar) { - /* - * Set sticky EOF so that no further input is presented to the - * caller. - */ + if (!IsBufferEmpty(bufPtr)) { + /* + * Buffer is not empty. How can that be? + * + * 0) We stopped early because we got all the bytes + * we were seeking. That's fine. + */ - SetFlag(statePtr, CHANNEL_EOF | CHANNEL_STICKY_EOF); - statePtr->inputEncodingFlags |= TCL_ENCODING_END; - copied = i; + if (bytesToRead == 0) { break; } - } - } - - /* - * If the current buffer is empty recycle it. - */ - - if (IsBufferEmpty(bufPtr)) { - statePtr->inQueueHead = bufPtr->nextPtr; - if (statePtr->inQueueHead == NULL) { - statePtr->inQueueTail = NULL; - } - RecycleBuffer(statePtr, bufPtr, 0); - } - - /* - * Return the number of characters copied into the result buffer. This may - * be different from the number of bytes consumed, because of EOL - * translations. - */ - - return copied; -} - -/* - *---------------------------------------------------------------------- - * - * CopyBuffer -- - * - * Copy at most one buffer of input to the result space. - * - * Results: - * Number of bytes stored in the result buffer. May return zero if no - * input is available. - * - * Side effects: - * Consumes buffered input. May deallocate one buffer. - * - *---------------------------------------------------------------------- - */ - -static size_t -CopyBuffer( - Channel *chanPtr, /* Channel from which to read input. */ - char *result, /* Where to store the copied input. */ - size_t space) /* How many bytes are available in result to - * store the copied input? */ -{ - ChannelBuffer *bufPtr; /* The buffer from which to copy bytes. */ - size_t bytesInBuffer; /* How many bytes are available to be copied - * in the current input buffer? */ - size_t copied; /* How many characters were already copied - * into the destination space? */ - /* - * If there is no input at all, return zero. The invariant is that either - * there is no buffer in the queue, or if the first buffer is empty, it is - * also the last buffer (and thus there is no input in the queue). Note - * also that if the buffer is empty, we don't leave it in the queue, but - * recycle it. - */ - - if (chanPtr->inQueueHead == NULL) { - return 0; - } - bufPtr = chanPtr->inQueueHead; - bytesInBuffer = BytesLeft(bufPtr); - - copied = 0; - - if (bytesInBuffer == 0) { - RecycleBuffer(chanPtr->state, bufPtr, 0); - chanPtr->inQueueHead = NULL; - chanPtr->inQueueTail = NULL; - return 0; - } - - /* - * Copy the current chunk into the result buffer. - */ - - if (bytesInBuffer < space) { - space = bytesInBuffer; - } - - memcpy(result, RemovePoint(bufPtr), space); - bufPtr->nextRemoved += space; - copied = space; - - /* - * We don't care about in-stream EOF characters here as the data read here - * may still flow through one or more transformations, i.e. is not in its - * final state yet. - */ - - /* - * If the current buffer is empty recycle it. - */ - - if (IsBufferEmpty(bufPtr)) { - chanPtr->inQueueHead = bufPtr->nextPtr; - if (chanPtr->inQueueHead == NULL) { - chanPtr->inQueueTail = NULL; - } - RecycleBuffer(chanPtr->state, bufPtr, 0); - } + /* + * 1) We're @EOF because we saw eof char. + */ - /* - * Return the number of characters copied into the result buffer. - */ + if (GotFlag(statePtr, CHANNEL_STICKY_EOF)) { + break; + } - return copied; -} - -/* - *---------------------------------------------------------------------- - * - * DoWrite -- - * - * Puts a sequence of characters into an output buffer, may queue the - * buffer for output if it gets full, and also remembers whether the - * current buffer is ready e.g. if it contains a newline and we are in - * line buffering mode. - * - * Results: - * The number of bytes written or -1 in case of error. If -1, - * Tcl_GetErrno will return the error code. - * - * Side effects: - * May buffer up output and may cause output to be produced on the - * channel. - * - *---------------------------------------------------------------------- - */ + /* + * 2) The buffer holds a \r while in CRLF translation, + * followed by the end of the buffer. + */ -static ssize_t -DoWrite( - Channel *chanPtr, /* The channel to buffer output for. */ - const char *src, /* Data to write. */ - size_t srcLen) /* Number of bytes to write. */ -{ - ChannelState *statePtr = chanPtr->state; - /* State info for channel */ - ChannelBuffer *outBufPtr; /* Current output buffer. */ - int foundNewline; /* Did we find a newline in output? */ - char *dPtr; - const char *sPtr; /* Search variables for newline. */ - int crsent; /* In CRLF eol translation mode, remember the - * fact that a CR was output to the channel - * without its following NL. */ - size_t i; /* Loop index for newline search. */ - size_t destCopied; /* How many bytes were used in this - * destination buffer to hold the output? */ - size_t totalDestCopied; /* How many bytes total were copied to the - * channel buffer? */ - size_t srcCopied; /* How many bytes were copied from the source - * string? */ - char *destPtr; /* Where in line to copy to? */ + assert(statePtr->inputTranslation == TCL_TRANSLATE_CRLF); + assert(RemovePoint(bufPtr)[0] == '\r'); + assert(BytesLeft(bufPtr) == 1); - /* - * If we are in network (or windows) translation mode, record the fact - * that we have not yet sent a CR to the channel. - */ + if (bufPtr->nextPtr == NULL) { + /* There's no more buffered data.... */ - crsent = 0; + if (GotFlag(statePtr, CHANNEL_EOF)) { + /* ...and there never will be. */ - /* - * Loop filling buffers and flushing them until all output has been - * consumed. - */ + *p++ = '\r'; + bytesToRead--; + bufPtr->nextRemoved++; + } else if (GotFlag(statePtr, CHANNEL_BLOCKED)) { + /* ...and we cannot get more now. */ + SetFlag(statePtr, CHANNEL_NEED_MORE_DATA); + break; + } else { + /* ... so we need to get some. */ + goto moreData; + } + } - srcCopied = 0; - totalDestCopied = 0; + if (bufPtr->nextPtr) { + /* There's a next buffer. Shift orphan \r to it. */ - while (srcLen > 0) { - /* - * Make sure there is a current output buffer to accept output. - */ + ChannelBuffer *nextPtr = bufPtr->nextPtr; - if (statePtr->curOutPtr == NULL) { - statePtr->curOutPtr = AllocChannelBuffer(statePtr->bufSize); + nextPtr->nextRemoved -= 1; + RemovePoint(nextPtr)[0] = '\r'; + bufPtr->nextRemoved++; + } } - outBufPtr = statePtr->curOutPtr; - - destCopied = SpaceLeft(outBufPtr); - if (destCopied > srcLen) { - destCopied = srcLen; + if (IsBufferEmpty(bufPtr)) { + statePtr->inQueueHead = bufPtr->nextPtr; + if (statePtr->inQueueHead == NULL) { + statePtr->inQueueTail = NULL; + } + RecycleBuffer(statePtr, bufPtr, 0); + bufPtr = statePtr->inQueueHead; } - destPtr = InsertPoint(outBufPtr); - switch (statePtr->outputTranslation) { - case TCL_TRANSLATE_LF: - srcCopied = destCopied; - memcpy(destPtr, src, destCopied); - break; - case TCL_TRANSLATE_CR: - srcCopied = destCopied; - memcpy(destPtr, src, destCopied); - for (dPtr = destPtr; dPtr < destPtr + destCopied; dPtr++) { - if (*dPtr == '\n') { - *dPtr = '\r'; - } - } + if (GotFlag(statePtr, CHANNEL_NONBLOCKING|CHANNEL_BLOCKED) + == (CHANNEL_NONBLOCKING|CHANNEL_BLOCKED)) { break; - case TCL_TRANSLATE_CRLF: - for (srcCopied = 0, dPtr = destPtr, sPtr = src; - dPtr < destPtr + destCopied; - dPtr++, sPtr++, srcCopied++) { - if (*sPtr == '\n') { - if (crsent) { - *dPtr = '\n'; - crsent = 0; - } else { - *dPtr = '\r'; - crsent = 1; - sPtr--, srcCopied--; - } - } else { - *dPtr = *sPtr; - } - } - break; - case TCL_TRANSLATE_AUTO: - Tcl_Panic("Tcl_Write: AUTO output translation mode not supported"); - default: - Tcl_Panic("Tcl_Write: unknown output translation mode"); } /* - * The current buffer is ready for output if it is full, or if it - * contains a newline and this channel is line-buffered, or if it - * contains any output and this channel is unbuffered. + * When there's no buffered data to read, and we're at EOF, + * escape to the caller. */ - outBufPtr->nextAdded += destCopied; - if (!GotFlag(statePtr, BUFFER_READY)) { - if (IsBufferFull(outBufPtr)) { - SetFlag(statePtr, BUFFER_READY); - } else if (GotFlag(statePtr, CHANNEL_LINEBUFFERED)) { - for (sPtr = src, i = 0, foundNewline = 0; - (i < srcCopied) && (!foundNewline); - i++, sPtr++) { - if (*sPtr == '\n') { - foundNewline = 1; - break; - } - } - if (foundNewline) { - SetFlag(statePtr, BUFFER_READY); - } - } else if (GotFlag(statePtr, CHANNEL_UNBUFFERED)) { - SetFlag(statePtr, BUFFER_READY); - } - } - - totalDestCopied += srcCopied; - src += srcCopied; - srcLen -= srcCopied; - - if (GotFlag(statePtr, BUFFER_READY)) { - if (FlushChannel(NULL, chanPtr, 0) != 0) { - return -1; - } + if (GotFlag(statePtr, CHANNEL_EOF) + && (bufPtr == NULL || IsBufferEmpty(bufPtr))) { + break; } - } /* Closes "while" */ + } + if (bytesToRead == 0) { + ResetFlag(statePtr, CHANNEL_BLOCKED); + } - return totalDestCopied; + assert(!GotFlag(statePtr, CHANNEL_EOF) + || GotFlag(statePtr, CHANNEL_STICKY_EOF) + || Tcl_InputBuffered((Tcl_Channel)chanPtr) == 0); + assert( !(GotFlag(statePtr, CHANNEL_EOF|CHANNEL_BLOCKED) + == (CHANNEL_EOF|CHANNEL_BLOCKED)) ); + UpdateInterest(chanPtr); + TclChannelRelease((Tcl_Channel)chanPtr); + return (int)(p - dst); } /* @@ -9984,7 +9229,7 @@ CopyEventProc( ClientData clientData, int mask) { - (void) CopyData(clientData, mask); + (void) CopyData((CopyState *) clientData, mask); } /* @@ -10022,19 +9267,19 @@ StopCopy( * Restore the old blocking mode and output buffering mode. */ - nonBlocking = csPtr->readFlags & CHANNEL_NONBLOCKING; + nonBlocking = (csPtr->readFlags & CHANNEL_NONBLOCKING); if (nonBlocking != (inStatePtr->flags & CHANNEL_NONBLOCKING)) { SetBlockMode(NULL, csPtr->readPtr, nonBlocking ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING); } if (csPtr->readPtr != csPtr->writePtr) { - nonBlocking = csPtr->writeFlags & CHANNEL_NONBLOCKING; + nonBlocking = (csPtr->writeFlags & CHANNEL_NONBLOCKING); if (nonBlocking != (outStatePtr->flags & CHANNEL_NONBLOCKING)) { SetBlockMode(NULL, csPtr->writePtr, nonBlocking ? TCL_MODE_NONBLOCKING : TCL_MODE_BLOCKING); } } - ResetFlag(outStatePtr, CHANNEL_LINEBUFFERED | CHANNEL_UNBUFFERED); + outStatePtr->flags &= ~(CHANNEL_LINEBUFFERED | CHANNEL_UNBUFFERED); outStatePtr->flags |= csPtr->writeFlags & (CHANNEL_LINEBUFFERED | CHANNEL_UNBUFFERED); @@ -10049,7 +9294,7 @@ StopCopy( } inStatePtr->csPtrR = NULL; outStatePtr->csPtrW = NULL; - ckfree(csPtr); + ckfree((char *) csPtr); } /* @@ -10078,16 +9323,19 @@ StackSetBlockMode( { int result = 0; Tcl_DriverBlockModeProc *blockModeProc; + ChannelState *statePtr = chanPtr->state; /* * Start at the top of the channel stack + * TODO: Examine what can go wrong when blockModeProc calls + * disturb the stacking state of the channel. */ - chanPtr = chanPtr->state->topChanPtr; + chanPtr = statePtr->topChanPtr; while (chanPtr != NULL) { blockModeProc = Tcl_ChannelBlockModeProc(chanPtr->typePtr); if (blockModeProc != NULL) { - result = blockModeProc(chanPtr->instanceData, mode); + result = (*blockModeProc) (chanPtr->instanceData, mode); if (result != 0) { Tcl_SetErrno(result); return result; @@ -10142,9 +9390,8 @@ SetBlockMode( */ if (!TclChanCaughtErrorBypass(interp, (Tcl_Channel) chanPtr)) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "error setting blocking mode: %s", - Tcl_PosixError(interp))); + Tcl_AppendResult(interp, "error setting blocking mode: ", + Tcl_PosixError(interp), NULL); } } else { /* @@ -10236,16 +9483,15 @@ Tcl_GetChannelNamesEx( && (pattern[2] == 'd'))) { if ((Tcl_FindHashEntry(hTblPtr, pattern) != NULL) && (Tcl_ListObjAppendElement(interp, resultPtr, - Tcl_NewStringObj(pattern, TCL_STRLEN)) != TCL_OK)) { + Tcl_NewStringObj(pattern, -1)) != TCL_OK)) { goto error; } goto done; } - for (hPtr = Tcl_FirstHashEntry(hTblPtr, &hSearch); hPtr != NULL; hPtr = Tcl_NextHashEntry(&hSearch)) { - statePtr = ((Channel *) Tcl_GetHashValue(hPtr))->state; + statePtr = ((Channel *) Tcl_GetHashValue(hPtr))->state; if (statePtr->topChanPtr == (Channel *) tsdPtr->stdinChannel) { name = "stdin"; } else if (statePtr->topChanPtr == (Channel *) tsdPtr->stdoutChannel) { @@ -10263,7 +9509,7 @@ Tcl_GetChannelNamesEx( if (((pattern == NULL) || Tcl_StringMatch(name, pattern)) && (Tcl_ListObjAppendElement(interp, resultPtr, - Tcl_NewStringObj(name, TCL_STRLEN)) != TCL_OK)) { + Tcl_NewStringObj(name, -1)) != TCL_OK)) { error: TclDecrRefCount(resultPtr); return TCL_ERROR; @@ -10391,8 +9637,11 @@ Tcl_IsChannelExisting( name = statePtr->channelName; } + /* Bug 2333466. Include \0 in the compare to prevent partial matching + * on prefixes. + */ if ((*chanName == *name) && - (memcmp(name, chanName, (size_t) chanNameLen + 1) == 0)) { + (memcmp(name, chanName, (size_t) chanNameLen+1) == 0)) { return 1; } } @@ -10511,13 +9760,13 @@ Tcl_ChannelBlockModeProc( { if (HaveVersion(chanTypePtr, TCL_CHANNEL_VERSION_2)) { return chanTypePtr->blockModeProc; - } - - /* - * The v1 structure had the blockModeProc in a different place. - */ + } else { + /* + * The v1 structure had the blockModeProc in a different place. + */ - return (Tcl_DriverBlockModeProc *) chanTypePtr->version; + return (Tcl_DriverBlockModeProc *) (chanTypePtr->version); + } } /* @@ -10759,8 +10008,9 @@ Tcl_ChannelFlushProc( { if (HaveVersion(chanTypePtr, TCL_CHANNEL_VERSION_2)) { return chanTypePtr->flushProc; + } else { + return NULL; } - return NULL; } /* @@ -10786,8 +10036,9 @@ Tcl_ChannelHandlerProc( { if (HaveVersion(chanTypePtr, TCL_CHANNEL_VERSION_2)) { return chanTypePtr->handlerProc; + } else { + return NULL; } - return NULL; } /* @@ -10813,8 +10064,9 @@ Tcl_ChannelWideSeekProc( { if (HaveVersion(chanTypePtr, TCL_CHANNEL_VERSION_3)) { return chanTypePtr->wideSeekProc; + } else { + return NULL; } - return NULL; } /* @@ -10841,8 +10093,9 @@ Tcl_ChannelThreadActionProc( { if (HaveVersion(chanTypePtr, TCL_CHANNEL_VERSION_4)) { return chanTypePtr->threadActionProc; + } else { + return NULL; } - return NULL; } /* @@ -10940,9 +10193,10 @@ static Tcl_Obj * FixLevelCode( Tcl_Obj *msg) { - size_t explicitResult, numOptions, lc, lcn, i, j; + int explicitResult, numOptions, lc, lcn; Tcl_Obj **lv, **lvn; - int res, val, lignore, cignore, newlevel = -1, newcode = -1; + int res, i, j, val, lignore, cignore; + int newlevel = -1, newcode = -1; /* ASSERT msg != NULL */ @@ -10958,10 +10212,10 @@ FixLevelCode( res = Tcl_ListObjGetElements(NULL, msg, &lc, &lv); if (res != TCL_OK) { - Tcl_Panic("Tcl_SetChannelError: bad syntax of message"); + Tcl_Panic("Tcl_SetChannelError(Interp): Bad syntax of message"); } - explicitResult = (lc % 2); + explicitResult = (1 == (lc % 2)); numOptions = lc - explicitResult; /* @@ -11018,7 +10272,7 @@ FixLevelCode( lcn += 2; } - lvn = ckalloc(lcn * sizeof(Tcl_Obj *)); + lvn = (Tcl_Obj **) ckalloc(lcn * sizeof(Tcl_Obj *)); /* * New level/code information is spliced into the first occurence of @@ -11071,7 +10325,7 @@ FixLevelCode( msg = Tcl_NewListObj(j, lvn); - ckfree(lvn); + ckfree((char *) lvn); return msg; } @@ -11155,8 +10409,9 @@ Tcl_ChannelTruncateProc( { if (HaveVersion(chanTypePtr, TCL_CHANNEL_VERSION_5)) { return chanTypePtr->truncateProc; + } else { + return NULL; } - return NULL; } /* @@ -11185,12 +10440,11 @@ DupChannelIntRep( * currently have an internal rep.*/ { ChannelState *statePtr = GET_CHANNELSTATE(srcPtr); - Interp *interpPtr = GET_CHANNELINTERP(srcPtr); SET_CHANNELSTATE(copyPtr, statePtr); - SET_CHANNELINTERP(copyPtr, interpPtr); - Tcl_Preserve(statePtr); - copyPtr->typePtr = &tclChannelType; + SET_CHANNELINTERP(copyPtr, GET_CHANNELINTERP(srcPtr)); + Tcl_Preserve((ClientData) statePtr); + copyPtr->typePtr = srcPtr->typePtr; } /* @@ -11216,53 +10470,40 @@ SetChannelFromAny( register Tcl_Obj *objPtr) /* The object to convert. */ { ChannelState *statePtr; - Interp *interpPtr; if (interp == NULL) { return TCL_ERROR; } - if (objPtr->typePtr == &tclChannelType) { + if (objPtr->typePtr == &chanObjType) { /* + * TODO: TAINT Flag and dup'd channel values? * The channel is valid until any call to DetachChannel occurs. * Ensure consistency checks are done. */ statePtr = GET_CHANNELSTATE(objPtr); - interpPtr = GET_CHANNELINTERP(objPtr); if (GotFlag(statePtr, CHANNEL_TAINTED|CHANNEL_CLOSED)) { ResetFlag(statePtr, CHANNEL_TAINTED); - Tcl_Release(statePtr); - UpdateStringOfChannel(objPtr); + Tcl_Release((ClientData) statePtr); objPtr->typePtr = NULL; - } else if (interpPtr != (Interp*) interp) { - Tcl_Release(statePtr); - UpdateStringOfChannel(objPtr); + } else if (interp != GET_CHANNELINTERP(objPtr)) { + Tcl_Release((ClientData) statePtr); objPtr->typePtr = NULL; } } - if (objPtr->typePtr != &tclChannelType) { - Tcl_Channel chan; - - /* - * We need a valid string with which to check for a valid channel, but - * make sure not to free internal rep until validated. [Bug 1847044] - */ + if (objPtr->typePtr != &chanObjType) { + Tcl_Channel chan = Tcl_GetChannel(interp, TclGetString(objPtr), NULL); - if ((objPtr->typePtr != NULL) && (objPtr->bytes == NULL)) { - objPtr->typePtr->updateStringProc(objPtr); - } - - chan = Tcl_GetChannel(interp, objPtr->bytes, NULL); if (chan == NULL) { return TCL_ERROR; } TclFreeIntRep(objPtr); - statePtr = ((Channel *) chan)->state; - Tcl_Preserve(statePtr); + statePtr = ((Channel *)chan)->state; + Tcl_Preserve((ClientData) statePtr); SET_CHANNELSTATE(objPtr, statePtr); SET_CHANNELINTERP(objPtr, interp); - objPtr->typePtr = &tclChannelType; + objPtr->typePtr = &chanObjType; } return TCL_OK; } @@ -11270,45 +10511,6 @@ SetChannelFromAny( /* *---------------------------------------------------------------------- * - * UpdateStringOfChannel -- - * - * Update the string representation for an object whose internal - * representation is "Channel". - * - * Results: - * None. - * - * Side effects: - * The object's string may be set by converting its Unicode represention - * to UTF format. - * - *---------------------------------------------------------------------- - */ - -static void -UpdateStringOfChannel( - Tcl_Obj *objPtr) /* Object with string rep to update. */ -{ - if (objPtr->bytes == NULL) { - ChannelState *statePtr = GET_CHANNELSTATE(objPtr); - const char *name = statePtr->channelName; - - if (name) { - size_t len = strlen(name); - - objPtr->bytes = ckalloc(len + 1); - objPtr->length = len; - memcpy(objPtr->bytes, name, len); - } else { - objPtr->bytes = tclEmptyStringRep; - objPtr->length = 0; - } - } -} - -/* - *---------------------------------------------------------------------- - * * FreeChannelIntRep -- * * Release statePtr storage. @@ -11326,8 +10528,7 @@ static void FreeChannelIntRep( Tcl_Obj *objPtr) /* Object with internal rep to free. */ { - Tcl_Release(GET_CHANNELSTATE(objPtr)); - objPtr->typePtr = NULL; + Tcl_Release((ClientData) GET_CHANNELSTATE(objPtr)); } #if 0 @@ -11344,27 +10545,21 @@ DumpFlags( char buf[20]; int i = 0; -#define ChanFlag(chr, bit) (buf[i++] = ((flags & (bit)) ? (chr) : '_')) +#define ChanFlag(chr,bit) (buf[i++] = ((flags & (bit)) ? (chr) : '_')) ChanFlag('r', TCL_READABLE); ChanFlag('w', TCL_WRITABLE); ChanFlag('n', CHANNEL_NONBLOCKING); ChanFlag('l', CHANNEL_LINEBUFFERED); ChanFlag('u', CHANNEL_UNBUFFERED); - ChanFlag('R', BUFFER_READY); ChanFlag('F', BG_FLUSH_SCHEDULED); ChanFlag('c', CHANNEL_CLOSED); ChanFlag('E', CHANNEL_EOF); ChanFlag('S', CHANNEL_STICKY_EOF); ChanFlag('B', CHANNEL_BLOCKED); ChanFlag('/', INPUT_SAW_CR); - ChanFlag('*', INPUT_NEED_NL); ChanFlag('D', CHANNEL_DEAD); ChanFlag('R', CHANNEL_RAW_MODE); -#ifdef TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING - ChanFlag('T', CHANNEL_TIMER_FEV); - ChanFlag('H', CHANNEL_HAS_MORE_DATA); -#endif /* TCL_IO_TRACK_OS_FOR_DRIVER_WITH_BAD_BLOCKING */ ChanFlag('x', CHANNEL_INCLOSE); buf[i] ='\0'; @@ -11380,5 +10575,6 @@ DumpFlags( * c-basic-offset: 4 * fill-column: 78 * tab-width: 8 + * indent-tabs-mode: nil * End: */ |
