@@ -1,9 +1,9 @@ /* * Copyright (C) 1997-2000 Matt Newman * - * $Header: /home/rkeene/tmp/cvs2fossil/../tcltls/tls/tls/tlsIO.c,v 1.7 2000/06/05 18:09:54 welch Exp $ + * $Header: /home/rkeene/tmp/cvs2fossil/../tcltls/tls/tls/tlsIO.c,v 1.7.2.4 2000/07/26 22:15:07 hobbs Exp $ * * TLS (aka SSL) Channel - can be layered on any bi-directional * Tcl_Channel (Note: Requires Trf Core Patch) * * This was built from scratch based upon observation of OpenSSL 0.9.2B @@ -30,51 +30,75 @@ /* * Forward declarations */ -static int BlockModeProc _ANSI_ARGS_((ClientData instanceData, int mode)); -static int CloseProc _ANSI_ARGS_ ((ClientData instanceData, Tcl_Interp *interp)); -static int InputProc _ANSI_ARGS_((ClientData instanceData, - char *buf, int bufSize, int *errorCodePtr)); -static int OutputProc _ANSI_ARGS_((ClientData instanceData, - char *buf, int toWrite, int *errorCodePtr)); -static int GetOptionProc _ANSI_ARGS_ ((ClientData instanceData, - Tcl_Interp *interp, char *optionName, Tcl_DString *dsPtr)); -static void WatchProc _ANSI_ARGS_((ClientData instanceData, int mask)); -static int GetHandleProc _ANSI_ARGS_ ((ClientData instanceData, - int direction, ClientData *handlePtr)); -static void ChannelHandler _ANSI_ARGS_ ((ClientData clientData, int mask)); -static void ChannelHandlerTimer _ANSI_ARGS_ ((ClientData clientData)); +static int TlsBlockModeProc _ANSI_ARGS_((ClientData instanceData, + int mode)); +static int TlsCloseProc _ANSI_ARGS_ ((ClientData instanceData, + Tcl_Interp *interp)); +static int TlsInputProc _ANSI_ARGS_((ClientData instanceData, + char *buf, int bufSize, int *errorCodePtr)); +static int TlsOutputProc _ANSI_ARGS_((ClientData instanceData, + char *buf, int toWrite, int *errorCodePtr)); +static int TlsGetOptionProc _ANSI_ARGS_ ((ClientData instanceData, + Tcl_Interp *interp, char *optionName, + Tcl_DString *dsPtr)); +static void TlsWatchProc _ANSI_ARGS_((ClientData instanceData, int mask)); +static int TlsGetHandleProc _ANSI_ARGS_ ((ClientData instanceData, + int direction, ClientData *handlePtr)); +static int TlsNotifyProc _ANSI_ARGS_ ((ClientData instanceData, + int mask)); +static void TlsChannelHandler _ANSI_ARGS_ ((ClientData clientData, + int mask)); +static void TlsChannelHandlerTimer _ANSI_ARGS_ ((ClientData clientData)); /* * This structure describes the channel type structure for TCP socket * based IO: */ - +#ifdef TCL_CHANNEL_VERSION_2 +static Tcl_ChannelType tlsChannelType = { + "tls", /* Type name. */ + TCL_CHANNEL_VERSION_2, /* A v2 channel (8.3.2/8.4a2+) */ + TlsCloseProc, /* Close proc. */ + TlsInputProc, /* Input proc. */ + TlsOutputProc, /* Output proc. */ + NULL, /* Seek proc. */ + NULL, /* Set option proc. */ + TlsGetOptionProc, /* Get option proc. */ + TlsWatchProc, /* Initialize notifier. */ + TlsGetHandleProc, /* Get file handle out of channel. */ + NULL, /* Close2Proc. */ + TlsBlockModeProc, /* Set blocking/nonblocking mode.*/ + NULL, /* FlushProc. */ + TlsNotifyProc, /* handlerProc. */ +}; +#else static Tcl_ChannelType tlsChannelType = { "tls", /* Type name. */ - BlockModeProc, /* Set blocking/nonblocking mode.*/ - CloseProc, /* Close proc. */ - InputProc, /* Input proc. */ - OutputProc, /* Output proc. */ + TlsBlockModeProc, /* Set blocking/nonblocking mode.*/ + TlsCloseProc, /* Close proc. */ + TlsInputProc, /* Input proc. */ + TlsOutputProc, /* Output proc. */ NULL, /* Seek proc. */ NULL, /* Set option proc. */ - GetOptionProc, /* Get option proc. */ - WatchProc, /* Initialize notifier. */ - GetHandleProc, /* Get file handle out of channel. */ + TlsGetOptionProc, /* Get option proc. */ + TlsWatchProc, /* Initialize notifier. */ + TlsGetHandleProc, /* Get file handle out of channel. */ }; +#endif Tcl_ChannelType *Tls_ChannelType() { return &tlsChannelType; } /* *------------------------------------------------------------------- * - * BlockModeProc -- + * TlsBlockModeProc -- * * This procedure is invoked by the generic IO level * to set blocking and nonblocking modes * Results: * 0 if successful, errno when failed. @@ -84,11 +108,11 @@ * *------------------------------------------------------------------- */ static int -BlockModeProc(ClientData instanceData, /* Socket state. */ +TlsBlockModeProc(ClientData instanceData, /* Socket state. */ int mode) /* The mode to set. Can be one of * TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { State *statePtr = (State *) instanceData; @@ -96,18 +120,22 @@ if (mode == TCL_MODE_NONBLOCKING) { statePtr->flags |= TLS_TCL_ASYNC; } else { statePtr->flags &= ~(TLS_TCL_ASYNC); } +#ifdef TCL_CHANNEL_VERSION_2 + return 0; +#else return Tcl_SetChannelOption(statePtr->interp, Tls_GetParent(statePtr), "-blocking", (mode == TCL_MODE_NONBLOCKING) ? "0" : "1"); +#endif } /* *------------------------------------------------------------------- * - * CloseProc -- + * TlsCloseProc -- * * This procedure is invoked by the generic IO level to perform * channel-type-specific cleanup when a SSL socket based channel * is closed. * @@ -120,39 +148,36 @@ * Closes the socket of the channel. * *------------------------------------------------------------------- */ static int -CloseProc(ClientData instanceData, /* The socket to close. */ +TlsCloseProc(ClientData instanceData, /* The socket to close. */ Tcl_Interp *interp) /* For error reporting - unused. */ { State *statePtr = (State *) instanceData; - dprintf(stderr,"\nCloseProc(0x%x)", statePtr); + dprintf(stderr,"\nTlsCloseProc(0x%x)", statePtr); +#ifndef TCL_CHANNEL_VERSION_2 /* * Remove event handler to underlying channel, this could * be because we are closing for real, or being "unstacked". */ Tcl_DeleteChannelHandler(Tls_GetParent(statePtr), - ChannelHandler, (ClientData) statePtr); - - if (statePtr->timer != (Tcl_TimerToken)NULL) { - Tcl_DeleteTimerHandler (statePtr->timer); - statePtr->timer = (Tcl_TimerToken)NULL; - } + TlsChannelHandler, (ClientData) statePtr); +#endif Tls_Clean(statePtr); - Tcl_EventuallyFree( (ClientData)statePtr, Tls_Free); + Tcl_EventuallyFree((ClientData)statePtr, Tls_Free); return TCL_OK; } /* *------------------------------------------------------------------- * - * InputProc -- + * TlsInputProc -- * * This procedure is invoked by the generic IO level * to read input from a SSL socket based channel. * * Results: @@ -165,15 +190,15 @@ * *------------------------------------------------------------------- */ static int -InputProc(ClientData instanceData, /* Socket state. */ - char *buf, /* Where to store data read. */ - int bufSize, /* How much space is available - * in the buffer? */ - int *errorCodePtr) /* Where to store error code. */ +TlsInputProc(ClientData instanceData, /* Socket state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available + * in the buffer? */ + int *errorCodePtr) /* Where to store error code. */ { State *statePtr = (State *) instanceData; int bytesRead; /* How many bytes were read? */ *errorCodePtr = 0; @@ -187,43 +212,52 @@ } } if (statePtr->flags & TLS_TCL_INIT) { statePtr->flags &= ~(TLS_TCL_INIT); } + /* + * We need to clear the SSL error stack now because we sometimes reach + * this function with leftover errors in the stack. If BIO_read + * returns -1 and intends EAGAIN, there is a leftover error, it will be + * misconstrued as an error, not EAGAIN. + * + * Alternatively, we may want to handle the <0 return codes from + * BIO_read specially (as advised in the RSA docs). TLS's lower level BIO + * functions play with the retry flags though, and this seems to work + * correctly. Similar fix in TlsOutputProc. - hobbs + */ + ERR_clear_error(); bytesRead = BIO_read(statePtr->bio, buf, bufSize); dprintf(stderr,"\nBIO_read -> %d", bytesRead); if (bytesRead < 0) { int err = SSL_get_error(statePtr->ssl, bytesRead); if (err == SSL_ERROR_SSL) { Tls_Error(statePtr, SSL_ERROR(statePtr->ssl, bytesRead)); *errorCodePtr = ECONNABORTED; - goto input; } else if (BIO_should_retry(statePtr->bio)) { dprintf(stderr,"RE! "); *errorCodePtr = EAGAIN; - goto input; - } - if (Tcl_GetErrno() == ECONNRESET) { - /* Soft EOF */ - bytesRead = 0; - goto input; } else { *errorCodePtr = Tcl_GetErrno(); - goto input; + if (*errorCodePtr == ECONNRESET) { + /* Soft EOF */ + *errorCodePtr = 0; + bytesRead = 0; + } } } -input: + input: dprintf(stderr, "\nInput(%d) -> %d [%d]", bufSize, bytesRead, *errorCodePtr); return bytesRead; } /* *------------------------------------------------------------------- * - * OutputProc -- + * TlsOutputProc -- * * This procedure is invoked by the generic IO level * to write output to a SSL socket based channel. * * Results: @@ -235,21 +269,21 @@ * *------------------------------------------------------------------- */ static int -OutputProc(ClientData instanceData, /* Socket state. */ - char *buf, /* The data buffer. */ +TlsOutputProc(ClientData instanceData, /* Socket state. */ + char *buf, /* The data buffer. */ int toWrite, /* How many bytes to write? */ int *errorCodePtr) /* Where to store error code. */ { State *statePtr = (State *) instanceData; int written, err; *errorCodePtr = 0; - dprintf(stderr,"\nBIO_write(%d)", toWrite); + dprintf(stderr,"\nBIO_write(0x%x, %d)", statePtr, toWrite); if (!SSL_is_init_finished(statePtr->ssl)) { written = Tls_WaitForConnect(statePtr, errorCodePtr); if (written <= 0) { goto output; @@ -262,57 +296,71 @@ dprintf(stderr, "zero-write\n"); BIO_flush(statePtr->bio); written = 0; goto output; } else { + /* + * We need to clear the SSL error stack now because we sometimes reach + * this function with leftover errors in the stack. If BIO_write + * returns -1 and intends EAGAIN, there is a leftover error, it will be + * misconstrued as an error, not EAGAIN. + * + * Alternatively, we may want to handle the <0 return codes from + * BIO_write specially (as advised in the RSA docs). TLS's lower level + * BIO functions play with the retry flags though, and this seems to + * work correctly. Similar fix in TlsInputProc. - hobbs + */ + ERR_clear_error(); written = BIO_write(statePtr->bio, buf, toWrite); - dprintf(stderr,"\nBIO_write(%d) -> [%d]", toWrite, written); + dprintf(stderr,"\nBIO_write(0x%x, %d) -> [%d]", + statePtr, toWrite, written); } - if (written < 0 || written == 0) { + if (written <= 0) { switch ((err = SSL_get_error(statePtr->ssl, written))) { - case SSL_ERROR_NONE: - if (written <= 0) { - written = 0; - goto output; - } - break; - case SSL_ERROR_WANT_WRITE: - dprintf(stderr,"write W BLOCK\n"); - break; - case SSL_ERROR_WANT_READ: - dprintf(stderr,"write R BLOCK\n"); - break; - case SSL_ERROR_WANT_X509_LOOKUP: - dprintf(stderr,"write X BLOCK\n"); - break; - case SSL_ERROR_ZERO_RETURN: - dprintf(stderr,"closed\n"); - written = 0; - goto output; - case SSL_ERROR_SYSCALL: - *errorCodePtr = Tcl_GetErrno(); - dprintf(stderr,"[%d] syscall errr: %d\n", written, Tcl_GetErrno()); - written = -1; - goto output; - case SSL_ERROR_SSL: - Tls_Error(statePtr, SSL_ERROR(statePtr->ssl, written)); - *errorCodePtr = ECONNABORTED; - written = -1; - goto output; - default: - dprintf(stderr,"unknown err: %d\n", err); - } - } -output: + case SSL_ERROR_NONE: + if (written < 0) { + written = 0; + } + break; + case SSL_ERROR_WANT_WRITE: + dprintf(stderr," write W BLOCK"); + break; + case SSL_ERROR_WANT_READ: + dprintf(stderr," write R BLOCK"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + dprintf(stderr," write X BLOCK"); + break; + case SSL_ERROR_ZERO_RETURN: + dprintf(stderr," closed\n"); + written = 0; + break; + case SSL_ERROR_SYSCALL: + *errorCodePtr = Tcl_GetErrno(); + dprintf(stderr," [%d] syscall errr: %d", + written, *errorCodePtr); + written = -1; + break; + case SSL_ERROR_SSL: + Tls_Error(statePtr, SSL_ERROR(statePtr->ssl, written)); + *errorCodePtr = ECONNABORTED; + written = -1; + break; + default: + dprintf(stderr," unknown err: %d\n", err); + break; + } + } + output: dprintf(stderr, "\nOutput(%d) -> %d", toWrite, written); return written; } /* *------------------------------------------------------------------- * - * GetOptionProc -- + * TlsGetOptionProc -- * * Computes an option value for a SSL socket based channel, or a * list of all options and their values. * * Note: This code is based on code contributed by John Haxby. @@ -326,19 +374,39 @@ * None. * *------------------------------------------------------------------- */ static int -GetOptionProc(ClientData instanceData, /* Socket state. */ +TlsGetOptionProc(ClientData instanceData, /* Socket state. */ Tcl_Interp *interp, /* For errors - can be NULL. */ char *optionName, /* Name of the option to * retrieve the value for, or * NULL to get all options and * their values. */ Tcl_DString *dsPtr) /* Where to store the computed value * initialized by caller. */ { +#ifdef TCL_CHANNEL_VERSION_2 + State *statePtr = (State *) instanceData; + Tcl_Channel downChan = Tls_GetParent(statePtr); + Tcl_DriverGetOptionProc *getOptionProc; + + getOptionProc = Tcl_ChannelGetOptionProc(Tcl_GetChannelType(downChan)); + if (getOptionProc != NULL) { + return (*getOptionProc)(Tcl_GetChannelInstanceData(downChan), + interp, optionName, dsPtr); + } else if (optionName == (char*) NULL) { + /* + * Request is query for all options, this is ok. + */ + return TCL_OK; + } + /* + * Request for a specific option has to fail, we don't have any. + */ + return TCL_ERROR; +#else State *statePtr = (State *) instanceData; size_t len = 0; if (optionName != (char *) NULL) { len = strlen(optionName); @@ -355,16 +423,17 @@ return TCL_OK; } } #endif return TCL_OK; +#endif } /* *------------------------------------------------------------------- * - * WatchProc -- + * TlsWatchProc -- * * Initialize the notifier to watch Tcl_Files from this channel. * * Results: * None. @@ -375,17 +444,52 @@ * *------------------------------------------------------------------- */ static void -WatchProc(ClientData instanceData, /* The socket state. */ +TlsWatchProc(ClientData instanceData, /* The socket state. */ int mask) /* Events of interest; an OR-ed * combination of TCL_READABLE, * TCL_WRITABLE and TCL_EXCEPTION. */ { State *statePtr = (State *) instanceData; +#ifdef TCL_CHANNEL_VERSION_2 + Tcl_Channel downChan; + + statePtr->watchMask = mask; + + /* No channel handlers any more. We will be notified automatically + * about events on the channel below via a call to our + * 'TransformNotifyProc'. But we have to pass the interest down now. + * We are allowed to add additional 'interest' to the mask if we want + * to. But this transformation has no such interest. It just passes + * the request down, unchanged. + */ + + downChan = Tls_GetParent(statePtr); + + (Tcl_GetChannelType(downChan)) + ->watchProc(Tcl_GetChannelInstanceData(downChan), mask); + + /* + * Management of the internal timer. + */ + + if (statePtr->timer != (Tcl_TimerToken) NULL) { + Tcl_DeleteTimerHandler(statePtr->timer); + statePtr->timer = (Tcl_TimerToken) NULL; + } + if ((mask & TCL_READABLE) && Tcl_InputBuffered(statePtr->self) > 0) { + /* + * There is interest in readable events and we actually have + * data waiting, so generate a timer to flush that. + */ + statePtr->timer = Tcl_CreateTimerHandler(TLS_TCL_DELAY, + TlsChannelHandlerTimer, (ClientData) statePtr); + } +#else if (mask == statePtr->watchMask) return; if (statePtr->watchMask) { /* @@ -392,27 +496,28 @@ * Remove event handler to underlying channel, this could * be because we are closing for real, or being "unstacked". */ Tcl_DeleteChannelHandler(Tls_GetParent(statePtr), - ChannelHandler, (ClientData) statePtr); + TlsChannelHandler, (ClientData) statePtr); } statePtr->watchMask = mask; if (statePtr->watchMask) { /* * Setup active monitor for events on underlying Channel. */ Tcl_CreateChannelHandler(Tls_GetParent(statePtr), - statePtr->watchMask, ChannelHandler, (ClientData) statePtr); + statePtr->watchMask, TlsChannelHandler, (ClientData) statePtr); } +#endif } /* *------------------------------------------------------------------- * - * GetHandleProc -- + * TlsGetHandleProc -- * * Called from Tcl_GetChannelFile to retrieve o/s file handler * from the SSL socket based channel. * * Results: @@ -422,23 +527,70 @@ * None. * *------------------------------------------------------------------- */ static int -GetHandleProc(ClientData instanceData, /* The socket state. */ +TlsGetHandleProc(ClientData instanceData, /* The socket state. */ int direction, /* Which Tcl_File to retrieve? */ ClientData *handlePtr) /* Where to store the handle. */ { State *statePtr = (State *) instanceData; - return Tcl_GetChannelHandle (Tls_GetParent(statePtr), direction, handlePtr); + return Tcl_GetChannelHandle(Tls_GetParent(statePtr), direction, handlePtr); +} + +/* + *------------------------------------------------------------------- + * + * TlsNotifyProc -- + * + * Handler called by Tcl to inform us of activity + * on the underlying channel. + * + * Results: + * None. + * + * Side effects: + * May process the incoming event by itself. + * + *------------------------------------------------------------------- + */ + +static int +TlsNotifyProc(instanceData, mask) + ClientData instanceData; /* The state of the notified transformation */ + int mask; /* The mask of occuring events */ +{ + State *statePtr = (State *) instanceData; + + /* + * An event occured in the underlying channel. This + * transformation doesn't process such events thus returns the + * incoming mask unchanged. + */ + + if (statePtr->timer != (Tcl_TimerToken) NULL) { + /* + * Delete an existing timer. It was not fired, yet we are + * here, so the channel below generated such an event and we + * don't have to. The renewal of the interest after the + * execution of channel handlers will eventually cause us to + * recreate the timer (in WatchProc). + */ + + Tcl_DeleteTimerHandler(statePtr->timer); + statePtr->timer = (Tcl_TimerToken) NULL; + } + + return mask; } +#ifndef TCL_CHANNEL_VERSION_2 /* *------------------------------------------------------* * - * ChannelHandler -- + * TlsChannelHandler -- * * ------------------------------------------------* * Handler called by Tcl as a result of * Tcl_CreateChannelHandler - to inform us of activity * on the underlying channel. @@ -453,13 +605,13 @@ * *------------------------------------------------------* */ static void -ChannelHandler (clientData, mask) -ClientData clientData; -int mask; +TlsChannelHandler (clientData, mask) + ClientData clientData; + int mask; { State *statePtr = (State *) clientData; dprintf(stderr, "HANDLER(0x%x)\n", mask); Tcl_Preserve( (ClientData)statePtr); @@ -501,41 +653,42 @@ if (statePtr->timer != (Tcl_TimerToken)NULL) { Tcl_DeleteTimerHandler(statePtr->timer); statePtr->timer = (Tcl_TimerToken)NULL; } - if ((mask & TCL_READABLE) && Tcl_InputBuffered (statePtr->self) > 0) { + if ((mask & TCL_READABLE) && Tcl_InputBuffered(statePtr->self) > 0) { /* * Data is waiting, flush it out in short time */ statePtr->timer = Tcl_CreateTimerHandler(TLS_TCL_DELAY, - ChannelHandlerTimer, (ClientData) statePtr); + TlsChannelHandlerTimer, (ClientData) statePtr); } Tcl_Release( (ClientData)statePtr); } +#endif /* *------------------------------------------------------* * - * ChannelHandlerTimer -- + * TlsChannelHandlerTimer -- * * ------------------------------------------------* * Called by the notifier (-> timer) to flush out * information waiting in channel buffers. * ------------------------------------------------* * * Sideeffects: - * As of 'ChannelHandler'. + * As of 'TlsChannelHandler'. * * Result: * None. * *------------------------------------------------------* */ static void -ChannelHandlerTimer (clientData) +TlsChannelHandlerTimer (clientData) ClientData clientData; /* Transformation to query */ { State *statePtr = (State *) clientData; int mask = 0; @@ -578,18 +731,20 @@ err = SSL_accept(statePtr->ssl); } else { err = SSL_connect(statePtr->ssl); } /*SSL_write(statePtr->ssl, (char*)&err, 0); HACK!!! */ - if (err > 0) + if (err > 0) { BIO_flush(statePtr->bio); + } if (err <= 0) { int rc = SSL_get_error(statePtr->ssl, err); if (rc == SSL_ERROR_SSL) { - Tls_Error(statePtr, (char*)ERR_reason_error_string(ERR_get_error())); + Tls_Error(statePtr, + (char *)ERR_reason_error_string(ERR_get_error())); *errorCodePtr = ECONNABORTED; return -1; } else if (BIO_should_retry(statePtr->bio)) { if (statePtr->flags & TLS_TCL_ASYNC) { dprintf(stderr,"E! "); @@ -604,11 +759,12 @@ return -1; } if (statePtr->flags & TLS_TCL_SERVER) { err = SSL_get_verify_result(statePtr->ssl); if (err != X509_V_OK) { - Tls_Error(statePtr, (char*)X509_verify_cert_error_string(err)); + Tls_Error(statePtr, + (char *)X509_verify_cert_error_string(err)); *errorCodePtr = ECONNABORTED; return -1; } } *errorCodePtr = Tcl_GetErrno(); @@ -622,10 +778,13 @@ Tcl_Channel Tls_GetParent( statePtr ) State *statePtr; { +#ifdef TCL_CHANNEL_VERSION_2 + return Tcl_GetStackedChannel(statePtr->self); +#else #if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 2 return statePtr->parent; #else /* The reason for the existence of this procedure is * the fact that stacking a transform over another @@ -640,29 +799,31 @@ * It walks the chain of Channel structures until it * finds the one pointing having 'ctrl' as instanceData * and then returns the superceding channel to that. (AK) */ - Tcl_Channel self = statePtr->self; - Tcl_Channel next; - - while ((ClientData) statePtr != Tcl_GetChannelInstanceData (self)) { - next = Tcl_GetStackedChannel (self); - if (next == (Tcl_Channel) NULL) { - /* 09/24/1999 Unstacking bug, found by Matt Newman . - * - * We were unable to find the channel structure for this - * transformation in the chain of stacked channel. This - * means that we are currently in the process of unstacking - * it *and* there were some bytes waiting which are now - * flushed. In this situation the pointer to the channel - * itself already refers to the parent channel we have to - * write the bytes into, so we return that. - */ - return statePtr->self; - } - self = next; - } - - return Tcl_GetStackedChannel (self); + Tcl_Channel self = statePtr->self; + Tcl_Channel next; + + while ((ClientData) statePtr != Tcl_GetChannelInstanceData (self)) { + next = Tcl_GetStackedChannel (self); + if (next == (Tcl_Channel) NULL) { + /* 09/24/1999 Unstacking bug, + * found by Matt Newman . + * + * We were unable to find the channel structure for this + * transformation in the chain of stacked channel. This + * means that we are currently in the process of unstacking + * it *and* there were some bytes waiting which are now + * flushed. In this situation the pointer to the channel + * itself already refers to the parent channel we have to + * write the bytes into, so we return that. + */ + return statePtr->self; + } + self = next; + } + + return Tcl_GetStackedChannel (self); +#endif #endif }