diff options
Diffstat (limited to 'win/tclWinConsole.c')
| -rw-r--r-- | win/tclWinConsole.c | 2511 |
1 files changed, 763 insertions, 1748 deletions
diff --git a/win/tclWinConsole.c b/win/tclWinConsole.c index c7e12ae..361fb3d 100644 --- a/win/tclWinConsole.c +++ b/win/tclWinConsole.c @@ -2,208 +2,128 @@ * tclWinConsole.c -- * * This file implements the Windows-specific console functions, and the - * "console" channel driver. Windows 7 or later required. + * "console" channel driver. * - * Copyright © 2022 Ashok P. Nadkarni + * Copyright (c) 1999 by Scriptics Corp. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ -#ifdef TCL_CONSOLE_DEBUG -#undef NDEBUG /* Enable asserts */ -#endif - #include "tclWinInt.h" -#include <assert.h> -#include <ctype.h> + +#include <fcntl.h> +#include <io.h> /* - * A general note on the design: The console channel driver differs from - * most other drivers in the following respects: - * - * - There can be at most 3 console handles at any time since Windows does - * support allocation of more than one console (with three handles - * corresponding to stdin, stdout, stderr) - * - * - Consoles are created / inherited at process startup. There is currently - * no way in Tcl to programmatically create a console. Even if these were - * added the above Windows limitation would still apply. - * - * - Unlike files, sockets etc. where there is a one-to-one - * correspondence between Tcl channels and operating system handles, - * std* channels are shared amongst threads which means there can be - * multiple Tcl channels corresponding to a single console handle. - * - * - Even with multiple threads, more than one file event handler is - * unlikely. It does not make sense for multiple threads to register - * handlers for stdin because the input would be randomly fragmented amongst - * the threads. - * - * Various design factors are driven by the above, e.g. use of lists instead - * of hash tables (at most 3 console handles) and use of global instead of - * per thread queues which simplifies lock management particularly because - * thread-console relation is not one-one and is likely more performant as - * well with fewer locks needing to be obtained. - * - * Some additional design notes/reminders for the future: - * - * Aligned, synchronous reads are done directly by interpreter thread. - * Unaligned or asynchronous reads are done through the reader thread. - * - * The reader thread does not read ahead. That is, it will not post a read - * until some interpreter thread is actually requesting a read. This is - * because an interpreter may (for example) turn off echo for passwords and - * the read ahead would come in the way of that. - * - * If multiple threads are reading from stdin, the input is sprayed in - * random fashion. This is not good application design and hence no plan to - * address this (not clear what should be done even in theory) - * - * For output, we do not restrict all output to the console writer threads. - * See ConsoleOutputProc for the conditions. - * - * Locks are never held when calling the ReadConsole/WriteConsole API's - * since they may block. + * The following variable is used to tell whether this module has been + * initialized. */ -static int gInitialized = 0; +static int initialized = 0; /* - * INPUT_BUFFER_SIZE is size of buffer passed to ReadConsole in bytes. - * Note that ReadConsole will only allow reading of line lengths up to the - * max of 256 and buffer size passed to it. So dropping this below 512 - * means user can type at most 256 chars. + * The consoleMutex locks around access to the initialized variable, and it is + * used to protect background threads from being terminated while they are + * using APIs that hold locks. */ -#ifndef INPUT_BUFFER_SIZE -#define INPUT_BUFFER_SIZE 8192 /* In bytes, so 4096 chars */ -#endif -/* - * CONSOLE_BUFFER_SIZE is size of storage used in ring buffers. - * In theory, at least sizeof(WCHAR) but note the Tcl channel bug - * https://core.tcl-lang.org/tcl/tktview/b3977d199b08e3979a8da970553d5209b3042e9c - * will cause failures in test suite if close to max input line in the suite. - */ -#ifndef CONSOLE_BUFFER_SIZE -#define CONSOLE_BUFFER_SIZE 8192 /* In bytes */ -#endif +TCL_DECLARE_MUTEX(consoleMutex) /* - * Ring buffer for storing data. Actual data is from bufPtr[start]:bufPtr[size-1] - * and bufPtr[0]:bufPtr[length - (size-start)]. + * Bit masks used in the flags field of the ConsoleInfo structure below. */ -typedef struct RingBuffer { - char *bufPtr; /* Pointer to buffer storage */ - Tcl_Size capacity; /* Size of the buffer in RingBufferChar */ - Tcl_Size start; /* Start of the data within the buffer. */ - Tcl_Size length; /* Number of RingBufferChar*/ -} RingBuffer; -#define RingBufferLength(ringPtr_) ((ringPtr_)->length) -#define RingBufferHasFreeSpace(ringPtr_) ((ringPtr_)->length < (ringPtr_)->capacity) -#define RINGBUFFER_ASSERT(ringPtr_) assert(RingBufferCheck(ringPtr_)) + +#define CONSOLE_PENDING (1<<0) /* Message is pending in the queue. */ +#define CONSOLE_ASYNC (1<<1) /* Channel is non-blocking. */ /* - * The Win32 console API does not support non-blocking I/O in any form. Thus - * the actual calls are made on a separate thread. Moreover, separate - * threads are needed for each handle because (for example) blocking on user - * input on stdin should not prevent output to stdout when non-blocking i/o - * is configured at the script level. - * - * In the input (e.g. stdin) case, the console stdin thread is the producer - * writing to the buffer ring buffer. The Tcl interpreter threads are the - * consumer. For the output (e.g. stdout/stderr) case, the Tcl interpreter - * are the producers while the console stdout/stderr threads are the - * consumers. - * - * Consoles are identified purely by handles and multiple threads may open - * them (as stdin/stdout/stderr are shared). - * - * Note on reference counting - a ConsoleHandleInfo instance has multiple - * references to it - one each from every channel that is attached to it - * plus one from the console thread itself which also serves as the reference - * from gConsoleHandleInfoList. + * Bit masks used in the sharedFlags field of the ConsoleInfo structure below. */ -typedef struct ConsoleHandleInfo { - struct ConsoleHandleInfo *nextPtr; /* Process-global list of consoles */ - HANDLE console; /* Console handle */ - HANDLE consoleThread; /* Handle to thread doing actual i/o on the console */ - SRWLOCK lock; /* Controls access to this structure. - * Cheaper than CRITICAL_SECTION but note does not - * support recursive locks or Try* style attempts.*/ - CONDITION_VARIABLE consoleThreadCV;/* For awakening console thread */ - CONDITION_VARIABLE interpThreadCV; /* For awakening interpthread(s) */ - RingBuffer buffer; /* Buffer for data transferred between console - * threads and Tcl threads. For input consoles, - * written by the console thread and read by Tcl - * threads. The converse for output threads */ - DWORD initMode; /* Initial console mode. */ - DWORD lastError; /* An error caused by the last background - * operation. Set to 0 if no error has been - * detected. */ - int numRefs; /* See comments above */ - int permissions; /* TCL_READABLE for input consoles, TCL_WRITABLE - * for output. Only one or the other can be set. */ - int flags; -#define CONSOLE_DATA_AWAITED 0x0001 /* An interpreter is awaiting data */ -} ConsoleHandleInfo; + +#define CONSOLE_EOF (1<<2) /* Console has reached EOF. */ +#define CONSOLE_BUFFERED (1<<3) /* Data was read into a buffer by the reader + * thread. */ + +#define CONSOLE_BUFFER_SIZE (8*1024) /* * This structure describes per-instance data for a console based channel. - * - * Note on locking - this structure has no locks because it is accessed - * only from the thread owning channel EXCEPT when a console traverses it - * looking for a channel that is watching for events on the console. Even - * in that case, no locking is required because that access is only under - * the gConsoleLock lock which prevents the channel from being removed from - * the gWatchingChannelList which in turn means it will not be deallocated - * from under the console thread. Access to individual fields does not need - * to be controlled because - * - the console thread does not write to any fields - * - changes to the nextWatchingChannelPtr field - * - changes to other fields do not matter because after being read for - * queueing events, they are verified again when the event is received - * in the interpreter thread (since they could have changed anyways while - * the event was in-flight on the event queue) - * - * Note on reference counting - a structure instance may be referenced from - * three places: - * - the Tcl channel subsystem. This reference is created when on channel - * opening and dropped on channel close. This also covers the reference - * from gWatchingChannelList since queueing / dequeuing from that list - * happens in conjunction with channel operations. - * - the Tcl event queue entries. This reference is added when the event - * is queued and dropped on receipt. */ -typedef struct ConsoleChannelInfo { - HANDLE handle; /* Console handle */ - Tcl_ThreadId threadId; /* Id of owning thread */ - struct ConsoleChannelInfo *nextWatchingChannelPtr; - /* Pointer to next channel watching events. */ + +typedef struct ConsoleInfo { + HANDLE handle; + int type; + struct ConsoleInfo *nextPtr;/* Pointer to next registered console. */ Tcl_Channel channel; /* Pointer to channel structure. */ - DWORD initMode; /* Initial console mode. */ - int numRefs; /* See comments above */ - int permissions; /* OR'ed combination of TCL_READABLE, + int validMask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates * which operations are valid on the file. */ int watchMask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates * which events should be reported. */ - int flags; /* State flags */ -#define CONSOLE_EVENT_QUEUED 0x0001 /* Notification event already queued */ -#define CONSOLE_ASYNC 0x0002 /* Channel is non-blocking. */ -#define CONSOLE_READ_OPS 0x0004 /* Channel supports read-related ops. */ -} ConsoleChannelInfo; + int flags; /* State flags, see above for a list. */ + Tcl_ThreadId threadId; /* Thread to which events should be reported. + * This value is used by the reader/writer + * threads. */ + HANDLE writeThread; /* Handle to writer thread. */ + HANDLE readThread; /* Handle to reader thread. */ + HANDLE writable; /* Manual-reset event to signal when the + * writer thread has finished waiting for the + * current buffer to be written. */ + HANDLE readable; /* Manual-reset event to signal when the + * reader thread has finished waiting for + * input. */ + HANDLE startWriter; /* Auto-reset event used by the main thread to + * signal when the writer thread should + * attempt to write to the console. */ + HANDLE stopWriter; /* Auto-reset event used by the main thread to + * signal when the writer thread should exit */ + HANDLE startReader; /* Auto-reset event used by the main thread to + * signal when the reader thread should + * attempt to read from the console. */ + HANDLE stopReader; /* Auto-reset event used by the main thread to + * signal when the reader thread should exit */ + DWORD writeError; /* An error caused by the last background + * write. Set to 0 if no error has been + * detected. This word is shared with the + * writer thread so access must be + * synchronized with the writable object. */ + char *writeBuf; /* Current background output buffer. Access is + * synchronized with the writable object. */ + int writeBufLen; /* Size of write buffer. Access is + * synchronized with the writable object. */ + int toWrite; /* Current amount to be written. Access is + * synchronized with the writable object. */ + int readFlags; /* Flags that are shared with the reader + * thread. Access is synchronized with the + * readable object. */ + int bytesRead; /* number of bytes in the buffer */ + int offset; /* number of bytes read out of the buffer */ + char buffer[CONSOLE_BUFFER_SIZE]; + /* Data consumed by reader thread. */ +} ConsoleInfo; + +typedef struct ThreadSpecificData { + /* + * The following pointer refers to the head of the list of consoles that + * are being watched for file events. + */ + + ConsoleInfo *firstConsolePtr; +} ThreadSpecificData; + +static Tcl_ThreadDataKey dataKey; /* * The following structure is what is added to the Tcl event queue when * console events are generated. */ -typedef struct { - Tcl_Event header; /* Information that is standard for all events. */ - ConsoleChannelInfo *chanInfoPtr; - /* Pointer to console info structure. Note +typedef struct ConsoleEvent { + Tcl_Event header; /* Information that is standard for all + * events. */ + ConsoleInfo *infoPtr; /* Pointer to console info structure. Note * that we still have to verify that the * console exists before dereferencing this * pointer. */ @@ -213,408 +133,94 @@ typedef struct { * Declarations for functions used only in this file. */ -static int ConsoleBlockModeProc(void *instanceData, int mode); -static void ConsoleCheckProc(void *clientData, int flags); -static int ConsoleCloseProc(void *instanceData, - Tcl_Interp *interp, int flags); +static int ConsoleBlockModeProc(ClientData instanceData,int mode); +static void ConsoleCheckProc(ClientData clientData, int flags); +static int ConsoleCloseProc(ClientData instanceData, + Tcl_Interp *interp); static int ConsoleEventProc(Tcl_Event *evPtr, int flags); -static void ConsoleExitHandler(void *clientData); -static int ConsoleGetHandleProc(void *instanceData, - int direction, void **handlePtr); -static int ConsoleGetOptionProc(void *instanceData, - Tcl_Interp *interp, const char *optionName, - Tcl_DString *dsPtr); +static void ConsoleExitHandler(ClientData clientData); +static int ConsoleGetHandleProc(ClientData instanceData, + int direction, ClientData *handlePtr); static void ConsoleInit(void); -static int ConsoleInputProc(void *instanceData, char *buf, +static int ConsoleInputProc(ClientData instanceData, char *buf, int toRead, int *errorCode); -static int ConsoleOutputProc(void *instanceData, - const char *buf, int toWrite, int *errorCode); -static int ConsoleSetOptionProc(void *instanceData, - Tcl_Interp *interp, const char *optionName, - const char *value); -static void ConsoleSetupProc(void *clientData, int flags); -static void ConsoleWatchProc(void *instanceData, int mask); -static void ProcExitHandler(void *clientData); -static void ConsoleThreadActionProc(void *instanceData, int action); -static DWORD ReadConsoleChars(HANDLE hConsole, WCHAR *lpBuffer, - Tcl_Size nChars, Tcl_Size *nCharsReadPtr); -static DWORD WriteConsoleChars(HANDLE hConsole, - const WCHAR *lpBuffer, Tcl_Size nChars, - Tcl_Size *nCharsWritten); -static void RingBufferInit(RingBuffer *ringPtr, Tcl_Size capacity); -static void RingBufferClear(RingBuffer *ringPtr); -static Tcl_Size RingBufferIn(RingBuffer *ringPtr, const char *srcPtr, - Tcl_Size srcLen, int partialCopyOk); -static Tcl_Size RingBufferOut(RingBuffer *ringPtr, char *dstPtr, - Tcl_Size dstCapacity, int partialCopyOk); -static ConsoleHandleInfo *AllocateConsoleHandleInfo(HANDLE consoleHandle, - int permissions); -static ConsoleHandleInfo *FindConsoleInfo(const ConsoleChannelInfo *); +static int ConsoleOutputProc(ClientData instanceData, + CONST char *buf, int toWrite, int *errorCode); static DWORD WINAPI ConsoleReaderThread(LPVOID arg); +static void ConsoleSetupProc(ClientData clientData, int flags); +static void ConsoleWatchProc(ClientData instanceData, int mask); static DWORD WINAPI ConsoleWriterThread(LPVOID arg); -static void NudgeWatchers(HANDLE consoleHandle); -#ifndef NDEBUG -static int RingBufferCheck(const RingBuffer *ringPtr); -#endif - -/* - * Static data. - */ - -typedef struct { - /* Currently this struct is only used to detect thread initialization */ - int notUsed; /* Dummy field */ -} ThreadSpecificData; -static Tcl_ThreadDataKey dataKey; - -/* - * All access to static data is controlled through a single process-wide - * lock. A process can have only a single console at a time, with three - * handles for stdin, stdout and stderr. Creation/destruction of consoles is - * a relatively rare event (currently only possible during process start), - * the number of consoles (as opposed to channels) is small (only stdin, - * stdout and stderr), and contention low. More finer-grained locking would - * likely not only complicate implementation but be slower due to multiple - * locks being held. Note console channels also differ from other Tcl - * channel types in that the channel<->OS descriptor mapping is not one-to-one. - */ -SRWLOCK gConsoleLock; - - -/* Process-wide list of console handles. Access control through gConsoleLock */ -static ConsoleHandleInfo *gConsoleHandleInfoList; - -/* - * Process-wide list of channels that are listening for events. Again access - * control through gConsoleLock. Common list for all threads is simplifies - * locking and bookkeeping and is workable because in practice multiple - * threads are very unlikely to be all waiting on stdin (not workable - * because input would be randomly distributed to threads) - */ -static ConsoleChannelInfo *gWatchingChannelList; +static void ProcExitHandler(ClientData clientData); +static int WaitForRead(ConsoleInfo *infoPtr, int blocking); +static void ConsoleThreadActionProc(ClientData instanceData, + int action); /* * This structure describes the channel type structure for command console * based IO. */ -static const Tcl_ChannelType consoleChannelType = { - "console", /* Type name. */ - TCL_CHANNEL_VERSION_5, /* v5 channel */ - TCL_CLOSE2PROC, /* Close proc. */ - ConsoleInputProc, /* Input proc. */ - ConsoleOutputProc, /* Output proc. */ - NULL, /* Seek proc. */ - ConsoleSetOptionProc, /* Set option proc. */ - ConsoleGetOptionProc, /* Get option proc. */ - ConsoleWatchProc, /* Set up notifier to watch the channel. */ - ConsoleGetHandleProc, /* Get an OS handle from channel. */ - ConsoleCloseProc, /* close2proc. */ - ConsoleBlockModeProc, /* Set blocking or non-blocking mode. */ - NULL, /* Flush proc. */ - NULL, /* Handler proc. */ - NULL, /* Wide seek proc. */ - ConsoleThreadActionProc, /* Thread action proc. */ - NULL /* Truncation proc. */ +static Tcl_ChannelType consoleChannelType = { + "console", /* Type name. */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ + ConsoleCloseProc, /* Close proc. */ + ConsoleInputProc, /* Input proc. */ + ConsoleOutputProc, /* Output proc. */ + NULL, /* Seek proc. */ + NULL, /* Set option proc. */ + NULL, /* Get option proc. */ + ConsoleWatchProc, /* Set up notifier to watch the channel. */ + ConsoleGetHandleProc, /* Get an OS handle from channel. */ + NULL, /* close2proc. */ + ConsoleBlockModeProc, /* Set blocking or non-blocking mode.*/ + NULL, /* flush proc. */ + NULL, /* handler proc. */ + NULL, /* wide seek proc */ + ConsoleThreadActionProc, /* thread action proc */ + NULL, /* truncation */ }; - -/* - *------------------------------------------------------------------------ - * - * RingBufferInit -- - * - * Initializes the ring buffer to a given size. - * - * Results: - * None. - * - * Side effects: - * Panics on allocation failure. - * - *------------------------------------------------------------------------ - */ -static void -RingBufferInit( - RingBuffer *ringPtr, - Tcl_Size capacity) -{ - if (capacity <= 0 || capacity > TCL_SIZE_MAX) { - Tcl_Panic("Internal error: invalid ring buffer capacity requested."); - } - ringPtr->bufPtr = (char *) ckalloc(capacity); - ringPtr->capacity = capacity; - ringPtr->start = 0; - ringPtr->length = 0; -} - -/* - *------------------------------------------------------------------------ - * - * RingBufferClear - * - * Clears the contents of a ring buffer. - * - * Results: - * None. - * - * Side effects: - * The allocated internal buffer is freed. - * - *------------------------------------------------------------------------ - */ -static void -RingBufferClear( - RingBuffer *ringPtr) -{ - if (ringPtr->bufPtr) { - ckfree(ringPtr->bufPtr); - ringPtr->bufPtr = NULL; - } - ringPtr->capacity = 0; - ringPtr->start = 0; - ringPtr->length = 0; -} /* - *------------------------------------------------------------------------ - * - * RingBufferIn -- - * - * Appends data to the ring buffer. - * - * Results: - * Returns number of bytes copied. - * - * Side effects: - * Internal buffer is updated. - * - *------------------------------------------------------------------------ - */ -static Tcl_Size -RingBufferIn( - RingBuffer *ringPtr, - const char *srcPtr, /* Source to be copied */ - Tcl_Size srcLen, /* Length of source */ - int partialCopyOk) /* If true, partial copy is permitted */ -{ - Tcl_Size freeSpace; - - RINGBUFFER_ASSERT(ringPtr); - - freeSpace = ringPtr->capacity - ringPtr->length; - if (freeSpace < srcLen) { - if (!partialCopyOk) { - return 0; - } - /* Copy only as much as free space allows */ - srcLen = freeSpace; - } - - if (ringPtr->capacity - ringPtr->start > ringPtr->length) { - /* There is room at the back */ - Tcl_Size endSpaceStart = ringPtr->start + ringPtr->length; - Tcl_Size endSpace = ringPtr->capacity - endSpaceStart; - if (endSpace >= srcLen) { - /* Everything fits at the back */ - memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, srcLen); - } else { - /* srcLen > endSpace */ - memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, endSpace); - memmove(ringPtr->bufPtr, endSpace + srcPtr, srcLen - endSpace); - } - } else { - /* No room at the back. Existing data wrap to front. */ - Tcl_Size wrapLen = - ringPtr->start + ringPtr->length - ringPtr->capacity; - memmove(wrapLen + ringPtr->bufPtr, srcPtr, srcLen); - } - - ringPtr->length += srcLen; - - RINGBUFFER_ASSERT(ringPtr); - - return srcLen; -} - -/* - *------------------------------------------------------------------------ - * - * RingBufferOut -- - * - * Moves data out of the ring buffer. If dstPtr is NULL, the data - * is simply removed. - * - * Results: - * Returns number of bytes copied or removed. - * - * Side effects: - * Internal buffer is updated. - * - *------------------------------------------------------------------------ - */ -static Tcl_Size -RingBufferOut( - RingBuffer *ringPtr, - char *dstPtr, /* Buffer for output data. May be NULL */ - Tcl_Size dstCapacity, /* Size of buffer */ - int partialCopyOk) /* If true, return what's available */ -{ - Tcl_Size leadLen; - - RINGBUFFER_ASSERT(ringPtr); - - if (dstCapacity > ringPtr->length) { - if (dstPtr && !partialCopyOk) { - return 0; - } - dstCapacity = ringPtr->length; - } - - if (ringPtr->start <= (ringPtr->capacity - ringPtr->length)) { - /* No content wrap around. So leadLen is entire content */ - leadLen = ringPtr->length; - } else { - /* Content wraps around so lead segment stretches to end of buffer */ - leadLen = ringPtr->capacity - ringPtr->start; - } - if (leadLen >= dstCapacity) { - if (dstPtr) { - memmove(dstPtr, ringPtr->start + ringPtr->bufPtr, dstCapacity); - } - ringPtr->start += dstCapacity; - } else { - Tcl_Size wrapLen = dstCapacity - leadLen; - if (dstPtr) { - memmove(dstPtr, - ringPtr->start + ringPtr->bufPtr, - leadLen); - memmove( - leadLen + dstPtr, ringPtr->bufPtr, wrapLen); - } - ringPtr->start = wrapLen; - } - - ringPtr->length -= dstCapacity; - if (ringPtr->start == ringPtr->capacity || ringPtr->length == 0) { - ringPtr->start = 0; - } - - RINGBUFFER_ASSERT(ringPtr); - - return dstCapacity; -} - -#ifndef NDEBUG -static int -RingBufferCheck( - const RingBuffer *ringPtr) -{ - return (ringPtr->bufPtr != NULL && ringPtr->capacity == CONSOLE_BUFFER_SIZE - && ringPtr->start < ringPtr->capacity - && ringPtr->length <= ringPtr->capacity); -} -#endif - -/* - *------------------------------------------------------------------------ - * - * ReadConsoleChars -- - * - * Wrapper for ReadConsoleW. - * - * Results: - * Returns 0 on success, else Windows error code. - * - * Side effects: - * On success the number of characters (not bytes) read is stored in - * *nCharsReadPtr. This will be 0 if the operation was interrupted by - * a Ctrl-C or a CancelIo call. + *---------------------------------------------------------------------- * - *------------------------------------------------------------------------ + * readConsoleBytes, writeConsoleBytes -- + * Wrapper for ReadConsole{A,W}, that takes and returns number of bytes + * instead of number of TCHARS */ -static DWORD -ReadConsoleChars( +static BOOL +readConsoleBytes( HANDLE hConsole, - WCHAR *lpBuffer, - Tcl_Size nChars, - Tcl_Size *nCharsReadPtr) + LPVOID lpBuffer, + DWORD nbytes, + LPDWORD nbytesread) { - DWORD nRead; + DWORD ntchars; BOOL result; - - /* - * If user types a Ctrl-Break or Ctrl-C, ReadConsole will return success - * with ntchars == 0 and GetLastError() will be ERROR_OPERATION_ABORTED. - * If no Ctrl signal handlers have been established, the default signal - * OS handler in a separate thread will terminate the program. If a Ctrl - * signal handler has been established (through an extension for - * example), it will run and take whatever action it deems appropriate. - * - * If one thread closes its channel, it calls CancelSynchronousIo on the - * console handle which results again in success being returned and - * GetLastError() being ERROR_OPERATION_ABORTED but ntchars in - * unmodified. - * - * In both cases above we will return success but with nbytesread as 0. - * This allows caller to check for thread termination etc. - * - * See https://bugs.python.org/issue30237 - * or https://github.com/microsoft/terminal/issues/12143 - */ - nRead = (DWORD)-1; - result = ReadConsoleW(hConsole, lpBuffer, nChars, &nRead, NULL); - if (result) { - if ((nRead == 0 || nRead == (DWORD)-1) - && GetLastError() == ERROR_OPERATION_ABORTED) { - nRead = 0; - } - *nCharsReadPtr = nRead; - return 0; - } else { - return GetLastError(); - } + int tcharsize; + tcharsize = tclWinProcs->useWide? 2 : 1; + result = tclWinProcs->readConsoleProc( + hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL); + if (nbytesread) + *nbytesread = (ntchars*tcharsize); + return result; } - -/* - *------------------------------------------------------------------------ - * - * WriteConsoleChars -- - * - * Wrapper for WriteConsoleW. - * - * Results: - * Returns 0 on success, Windows error code on failure. - * - * Side effects: - * On success the number of characters (not bytes) written is stored in - * *nCharsWrittenPtr. This will be 0 if the operation was interrupted by - * a Ctrl-C or a CancelIo call. - * - *------------------------------------------------------------------------ - */ -static DWORD -WriteConsoleChars( +static BOOL +writeConsoleBytes( HANDLE hConsole, - const WCHAR *lpBuffer, - Tcl_Size nChars, - Tcl_Size *nCharsWrittenPtr) + const VOID *lpBuffer, + DWORD nbytes, + LPDWORD nbyteswritten) { - DWORD nCharsWritten; + DWORD ntchars; BOOL result; - - /* See comments in ReadConsoleChars, not sure that applies here */ - nCharsWritten = (DWORD)-1; - result = WriteConsoleW(hConsole, lpBuffer, nChars, &nCharsWritten, NULL); - if (result) { - if (nCharsWritten == (DWORD) -1) { - nCharsWritten = 0; - } - *nCharsWrittenPtr = nCharsWritten; - return 0; - } else { - return GetLastError(); - } + int tcharsize; + tcharsize = tclWinProcs->useWide? 2 : 1; + result = tclWinProcs->writeConsoleProc( + hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL); + if (nbyteswritten) + *nbyteswritten = (ntchars*tcharsize); + return result; } /* @@ -636,24 +242,26 @@ WriteConsoleChars( static void ConsoleInit(void) { + ThreadSpecificData *tsdPtr; + /* * Check the initialized flag first, then check again in the mutex. This * is a speed enhancement. */ - if (!gInitialized) { - AcquireSRWLockExclusive(&gConsoleLock); - if (!gInitialized) { - gInitialized = 1; + if (!initialized) { + Tcl_MutexLock(&consoleMutex); + if (!initialized) { + initialized = 1; Tcl_CreateExitHandler(ProcExitHandler, NULL); } - ReleaseSRWLockExclusive(&gConsoleLock); + Tcl_MutexUnlock(&consoleMutex); } - if (TclThreadDataKeyGet(&dataKey) == NULL) { - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - - tsdPtr->notUsed = 0; + tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); + if (tsdPtr == NULL) { + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->firstConsolePtr = NULL; Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL); } @@ -678,7 +286,7 @@ ConsoleInit(void) static void ConsoleExitHandler( - TCL_UNUSED(void *)) + ClientData clientData) /* Old window proc */ { Tcl_DeleteEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); } @@ -702,50 +310,11 @@ ConsoleExitHandler( static void ProcExitHandler( - TCL_UNUSED(void *)) + ClientData clientData) /* Old window proc */ { - AcquireSRWLockExclusive(&gConsoleLock); - gInitialized = 0; - ReleaseSRWLockExclusive(&gConsoleLock); -} - -/* - *------------------------------------------------------------------------ - * - * NudgeWatchers -- - * - * Wakes up all threads which have file event watchers on the passed - * console handle. - * - * The function locks and releases gConsoleLock. - * Caller must not be holding locks that will violate lock hierarchy. - * - * Results: - * None. - * - * Side effects: - * As above. - *------------------------------------------------------------------------ - */ -static void -NudgeWatchers( - HANDLE consoleHandle) -{ - ConsoleChannelInfo *chanInfoPtr; - AcquireSRWLockShared(&gConsoleLock); /* Shared-read lock */ - for (chanInfoPtr = gWatchingChannelList; chanInfoPtr; - chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { - /* - * Notify channels interested in our handle AND that have - * a thread attached. - * No lock needed for chanInfoPtr. See ConsoleChannelInfo. - */ - if (chanInfoPtr->handle == consoleHandle - && chanInfoPtr->threadId != NULL) { - Tcl_ThreadAlert(chanInfoPtr->threadId); - } - } - ReleaseSRWLockShared(&gConsoleLock); + Tcl_MutexLock(&consoleMutex); + initialized = 0; + Tcl_MutexUnlock(&consoleMutex); } /* @@ -754,9 +323,7 @@ NudgeWatchers( * ConsoleSetupProc -- * * This procedure is invoked before Tcl_DoOneEvent blocks waiting for an - * event. It walks the channel list and if any input channel has data - * available or output channel has space for data, sets the event loop - * blocking time to 0 so that it will poll immediately. + * event. * * Results: * None. @@ -769,48 +336,36 @@ NudgeWatchers( void ConsoleSetupProc( - TCL_UNUSED(void *), + ClientData data, /* Not used. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - ConsoleChannelInfo *chanInfoPtr; + ConsoleInfo *infoPtr; Tcl_Time blockTime = { 0, 0 }; int block = 1; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return; } /* - * Walk the list of channels. See general comments for struct - * ConsoleChannelInfo with regard to locking and field access. + * Look to see if any events are already pending. If they are, poll. */ - AcquireSRWLockShared(&gConsoleLock); /* READ lock - no data modification */ - - for (chanInfoPtr = gWatchingChannelList; block && chanInfoPtr != NULL; - chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { - ConsoleHandleInfo *handleInfoPtr; - handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr != NULL) { - AcquireSRWLockShared(&handleInfoPtr->lock); - /* Remember at most one of READABLE, WRITABLE set */ - if (chanInfoPtr->watchMask & TCL_READABLE) { - if (RingBufferLength(&handleInfoPtr->buffer) > 0 - || handleInfoPtr->lastError != ERROR_SUCCESS) { - block = 0; /* Input data available */ - } - } else if (chanInfoPtr->watchMask & TCL_WRITABLE) { - if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { - /* TCL_WRITABLE */ - block = 0; /* Output space available */ - } + + for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask & TCL_WRITABLE) { + if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { + block = 0; + } + } + if (infoPtr->watchMask & TCL_READABLE) { + if (WaitForRead(infoPtr, 0) >= 0) { + block = 0; } - ReleaseSRWLockShared(&handleInfoPtr->lock); } } - ReleaseSRWLockShared(&gConsoleLock); - if (!block) { - /* At least one channel is readable/writable. Set block time to 0 */ Tcl_SetMaxBlockTime(&blockTime); } } @@ -834,87 +389,57 @@ ConsoleSetupProc( static void ConsoleCheckProc( - TCL_UNUSED(void *), + ClientData data, /* Not used. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { - ConsoleChannelInfo *chanInfoPtr; - Tcl_ThreadId me; + ConsoleInfo *infoPtr; + ConsoleEvent *evPtr; int needEvent; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return; } - me = Tcl_GetCurrentThread(); - /* - * Acquire a shared lock. Note this is ok even though we potentially - * modify the chanInfoPtr->flags because chanInfoPtr is only modified - * when it belongs to this thread and no other thread will write to it. - * THe shared lock is intended to protect the global gWatchingChannelList - * as we traverse it. + * Queue events for any ready consoles that don't already have events + * queued. */ - AcquireSRWLockShared(&gConsoleLock); - - for (chanInfoPtr = gWatchingChannelList; chanInfoPtr != NULL; - chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) { - ConsoleHandleInfo *handleInfoPtr; - if (chanInfoPtr->threadId != me) { - /* Some other thread owns the channel */ + for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->flags & CONSOLE_PENDING) { continue; } - if (chanInfoPtr->flags & CONSOLE_EVENT_QUEUED) { - /* A notification event already queued. No point in another. */ - continue; - } - - handleInfoPtr = FindConsoleInfo(chanInfoPtr); - /* Pointer is safe to access as we are holding gConsoleLock */ - if (handleInfoPtr == NULL) { - /* Stale event */ - continue; - } + /* + * Queue an event if the console is signaled for reading or writing. + */ needEvent = 0; - AcquireSRWLockShared(&handleInfoPtr->lock); - /* Rememeber channel is read or write, never both */ - if (chanInfoPtr->watchMask & TCL_READABLE) { - if (RingBufferLength(&handleInfoPtr->buffer) > 0 - || handleInfoPtr->lastError != ERROR_SUCCESS) { - needEvent = 1; /* Input data available or error/EOF */ + if (infoPtr->watchMask & TCL_WRITABLE) { + if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { + needEvent = 1; } - /* - * TCL_READABLE watch means someone is looking out for data being - * available, let reader thread know. Note channel need not be - * ASYNC! (Bug [baa51423c2]) - */ - handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - } else if (chanInfoPtr->watchMask & TCL_WRITABLE) { - if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { - needEvent = 1; /* Output space available */ + } + + if (infoPtr->watchMask & TCL_READABLE) { + if (WaitForRead(infoPtr, 0) >= 0) { + needEvent = 1; } } - ReleaseSRWLockShared(&handleInfoPtr->lock); if (needEvent) { - ConsoleEvent *evPtr = (ConsoleEvent *)ckalloc(sizeof(ConsoleEvent)); - - /* See note above loop why this can be accessed without locks */ - chanInfoPtr->flags |= CONSOLE_EVENT_QUEUED; - chanInfoPtr->numRefs += 1; /* So it does not go away while event - is in queue */ + infoPtr->flags |= CONSOLE_PENDING; + evPtr = (ConsoleEvent *) ckalloc(sizeof(ConsoleEvent)); evPtr->header.proc = ConsoleEventProc; - evPtr->chanInfoPtr = chanInfoPtr; + evPtr->infoPtr = infoPtr; Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); } } - - ReleaseSRWLockShared(&gConsoleLock); } - + + /* *---------------------------------------------------------------------- * @@ -933,11 +458,11 @@ ConsoleCheckProc( static int ConsoleBlockModeProc( - void *instanceData, /* Instance data for channel. */ + ClientData instanceData, /* Instance data for channel. */ int mode) /* TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; /* * Consoles on Windows can not be switched between blocking and @@ -948,9 +473,9 @@ ConsoleBlockModeProc( */ if (mode == TCL_MODE_NONBLOCKING) { - chanInfoPtr->flags |= CONSOLE_ASYNC; + infoPtr->flags |= CONSOLE_ASYNC; } else { - chanInfoPtr->flags &= ~CONSOLE_ASYNC; + infoPtr->flags &= ~(CONSOLE_ASYNC); } return 0; } @@ -973,102 +498,166 @@ ConsoleBlockModeProc( static int ConsoleCloseProc( - void *instanceData, /* Pointer to ConsoleChannelInfo structure. */ - TCL_UNUSED(Tcl_Interp *), - int flags) + ClientData instanceData, /* Pointer to ConsoleInfo structure. */ + Tcl_Interp *interp) /* For error reporting. */ { - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - ConsoleHandleInfo *handleInfoPtr; - int errorCode = 0; - ConsoleChannelInfo **nextPtrPtr; - int closeHandle; - - if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) { - return EINVAL; - } - /* - * Don't close the Win32 handle if the handle is a standard channel - * during the thread exit process. Otherwise, one thread may kill the - * stdio of another while exiting. Note an explicit close in script will - * still close the handle. That's historical behavior on all platforms. - */ - if (!TclInThreadExit() - || ( (GetStdHandle(STD_INPUT_HANDLE) != chanInfoPtr->handle) - && (GetStdHandle(STD_OUTPUT_HANDLE) != chanInfoPtr->handle) - && (GetStdHandle(STD_ERROR_HANDLE) != chanInfoPtr->handle))) { - closeHandle = 1; - } else { - closeHandle = 0; - } + ConsoleInfo *consolePtr = (ConsoleInfo *) instanceData; + int errorCode; + ConsoleInfo *infoPtr, **nextPtrPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + DWORD exitCode; - AcquireSRWLockExclusive(&gConsoleLock); + errorCode = 0; - /* Remove channel from watchers' list */ - for (nextPtrPtr = &gWatchingChannelList; *nextPtrPtr != NULL; - nextPtrPtr = &(*nextPtrPtr)->nextWatchingChannelPtr) { - if (*nextPtrPtr == (ConsoleChannelInfo *) chanInfoPtr) { - *nextPtrPtr = (*nextPtrPtr)->nextWatchingChannelPtr; - break; - } - } + /* + * Clean up the background thread if necessary. Note that this must be + * done before we can close the file, since the thread may be blocking + * trying to read from the console. + */ - handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr) { + if (consolePtr->readThread) { /* - * Console thread may be blocked either waiting for console i/o - * or waiting on the condition variable for buffer empty/full + * The thread may already have closed on it's own. Check it's exit + * code. */ - AcquireSRWLockShared(&handleInfoPtr->lock); - if (closeHandle) { - handleInfoPtr->console = INVALID_HANDLE_VALUE; + GetExitCodeThread(consolePtr->readThread, &exitCode); + + if (exitCode == STILL_ACTIVE) { + /* + * Set the stop event so that if the reader thread is blocked in + * ConsoleReaderThread on WaitForMultipleEvents, it will exit + * cleanly. + */ + + SetEvent(consolePtr->stopReader); + + /* + * Wait at most 20 milliseconds for the reader thread to close. + */ + + if (WaitForSingleObject(consolePtr->readThread, 20) + == WAIT_TIMEOUT) { + /* + * Forcibly terminate the background thread as a last resort. + * Note that we need to guard against terminating the thread + * while it is in the middle of Tcl_ThreadAlert because it + * won't be able to release the notifier lock. + */ + + Tcl_MutexLock(&consoleMutex); + + /* BUG: this leaks memory. */ + TerminateThread(consolePtr->readThread, 0); + Tcl_MutexUnlock(&consoleMutex); + } } - /* Break the thread out of blocking console i/o */ - handleInfoPtr->numRefs -= 1; /* Remove reference from this channel */ - if (handleInfoPtr->numRefs == 1) { + CloseHandle(consolePtr->readThread); + CloseHandle(consolePtr->readable); + CloseHandle(consolePtr->startReader); + CloseHandle(consolePtr->stopReader); + consolePtr->readThread = NULL; + } + consolePtr->validMask &= ~TCL_READABLE; + + /* + * Wait for the writer thread to finish the current buffer, then terminate + * the thread and close the handles. If the channel is nonblocking, there + * should be no pending write operations. + */ + + if (consolePtr->writeThread) { + if (consolePtr->toWrite) { /* - * Abort the i/o if no other threads are listening on it. - * Note without this check, an input line will be skipped on - * the cancel. + * We only need to wait if there is something to write. This may + * prevent infinite wait on exit. [python bug 216289] */ - CancelSynchronousIo(handleInfoPtr->consoleThread); + + WaitForSingleObject(consolePtr->writable, INFINITE); } /* - * Wake up the console handling thread. Note we do not explicitly - * tell it handle is closed (below). It will find out on next access + * The thread may already have closed on it's own. Check it's exit + * code. */ - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - ReleaseSRWLockShared(&handleInfoPtr->lock); + GetExitCodeThread(consolePtr->writeThread, &exitCode); + + if (exitCode == STILL_ACTIVE) { + /* + * Set the stop event so that if the reader thread is blocked in + * ConsoleWriterThread on WaitForMultipleEvents, it will exit + * cleanly. + */ + + SetEvent(consolePtr->stopWriter); + + /* + * Wait at most 20 milliseconds for the writer thread to close. + */ + + if (WaitForSingleObject(consolePtr->writeThread, 20) + == WAIT_TIMEOUT) { + /* + * Forcibly terminate the background thread as a last resort. + * Note that we need to guard against terminating the thread + * while it is in the middle of Tcl_ThreadAlert because it + * won't be able to release the notifier lock. + */ + + Tcl_MutexLock(&consoleMutex); + + /* BUG: this leaks memory. */ + TerminateThread(consolePtr->writeThread, 0); + Tcl_MutexUnlock(&consoleMutex); + } + } + + CloseHandle(consolePtr->writeThread); + CloseHandle(consolePtr->writable); + CloseHandle(consolePtr->startWriter); + CloseHandle(consolePtr->stopWriter); + consolePtr->writeThread = NULL; } + consolePtr->validMask &= ~TCL_WRITABLE; - ReleaseSRWLockExclusive(&gConsoleLock); - chanInfoPtr->channel = NULL; - chanInfoPtr->watchMask = 0; - chanInfoPtr->permissions = 0; + /* + * Don't close the Win32 handle if the handle is a standard channel during + * the thread exit process. Otherwise, one thread may kill the stdio of + * another. + */ - if (closeHandle && chanInfoPtr->handle != INVALID_HANDLE_VALUE) { - if (CloseHandle(chanInfoPtr->handle) == FALSE) { - Tcl_WinConvertError(GetLastError()); + if (!TclInThreadExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) { + if (CloseHandle(consolePtr->handle) == FALSE) { + TclWinConvertError(GetLastError()); errorCode = errno; } - chanInfoPtr->handle = INVALID_HANDLE_VALUE; } + consolePtr->watchMask &= consolePtr->validMask; + /* - * Note, we can check and manipulate numRefs without a lock because - * we have removed it from the watch queue so the console thread cannot - * get at it. + * Remove the file from the list of watched files. */ - if (chanInfoPtr->numRefs > 1) { - /* There may be references already on the event queue */ - chanInfoPtr->numRefs -= 1; - } else { - ckfree(chanInfoPtr); + + for (nextPtrPtr = &(tsdPtr->firstConsolePtr), infoPtr = *nextPtrPtr; + infoPtr != NULL; + nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { + if (infoPtr == (ConsoleInfo *)consolePtr) { + *nextPtrPtr = infoPtr->nextPtr; + break; + } } + if (consolePtr->writeBuf != NULL) { + ckfree(consolePtr->writeBuf); + consolePtr->writeBuf = 0; + } + ckfree((char*) consolePtr); return errorCode; } @@ -1090,143 +679,73 @@ ConsoleCloseProc( * *---------------------------------------------------------------------- */ + static int ConsoleInputProc( - void *instanceData, /* Console state. */ - char *bufPtr, /* Where to store data read. */ + ClientData instanceData, /* Console state. */ + char *buf, /* Where to store data read. */ int bufSize, /* How much space is available in the * buffer? */ int *errorCode) /* Where to store error code. */ { - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - ConsoleHandleInfo *handleInfoPtr; - Tcl_Size numRead; - - if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { - return 0; /* EOF */ - } + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; + DWORD count, bytesRead = 0; + int result; *errorCode = 0; - AcquireSRWLockShared(&gConsoleLock); - handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr == NULL) { - /* Really shouldn't happen since channel is holding a reference */ - ReleaseSRWLockShared(&gConsoleLock); - return 0; /* EOF */ + /* + * Synchronize with the reader thread. + */ + + result = WaitForRead(infoPtr, (infoPtr->flags & CONSOLE_ASYNC) ? 0 : 1); + + /* + * If an error occurred, return immediately. + */ + + if (result == -1) { + *errorCode = errno; + return -1; } - AcquireSRWLockExclusive(&handleInfoPtr->lock); - ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */ - while (1) { - numRead = RingBufferOut(&handleInfoPtr->buffer, bufPtr, bufSize, 1); - /* - * Note: even if channel is closed or has an error, as long there is - * buffered data, we will pass it up. - */ - if (numRead != 0) { - break; - } + if (infoPtr->readFlags & CONSOLE_BUFFERED) { /* - * No data available. - * - If an error was recorded, generate that and reset it. - * - If EOF, indicate as much. It is up to the application to close - * the channel. - * - Otherwise, if non-blocking return EAGAIN or wait for more data. + * Data is stored in the buffer. */ - if (handleInfoPtr->lastError != 0) { - if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) { - numRead = 0; /* Treat as EOF */ - } else { - Tcl_WinConvertError(handleInfoPtr->lastError); - handleInfoPtr->lastError = 0; - *errorCode = Tcl_GetErrno(); - numRead = -1; - } - break; - } - if (handleInfoPtr->console == INVALID_HANDLE_VALUE) { - /* EOF - break with numRead == 0 */ - chanInfoPtr->handle = INVALID_HANDLE_VALUE; - break; - } - /* For async, tell caller we are blocked */ - if (chanInfoPtr->flags & CONSOLE_ASYNC) { - *errorCode = EWOULDBLOCK; - numRead = -1; - break; - } + if (bufSize < (infoPtr->bytesRead - infoPtr->offset)) { + memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize); + bytesRead = bufSize; + infoPtr->offset += bufSize; + } else { + memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize); + bytesRead = infoPtr->bytesRead - infoPtr->offset; - /* - * Blocking read. Just get data from directly from console. There - * is a small complication in that - * 1. The destination buffer should be WCHAR aligned. - * 2. We can only read even number of bytes (wide-character API). - * 3. Caller has large enough buffer (else length of line user can - * enter will be limited) - * If any condition is not met, we defer to the - * reader thread which handles these cases rather than dealing with - * them here (which is a little trickier than it might sound.) - * - * TODO - not clear this block is a useful optimization. bufSize by - * default is 4K which is < INPUT_BUFFER_SIZE and will rarely be - * increased on stdin. - */ - if ((1 & (size_t)bufPtr) == 0 /* aligned buffer */ - && (1 & bufSize) == 0 /* Even number of bytes */ - && bufSize > INPUT_BUFFER_SIZE) { - DWORD lastError; - Tcl_Size numChars; - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - lastError = ReadConsoleChars(chanInfoPtr->handle, - (WCHAR *)bufPtr, bufSize / sizeof(WCHAR), &numChars); - /* NOTE lock released so DON'T break. Return instead */ - if (lastError != ERROR_SUCCESS) { - Tcl_WinConvertError(lastError); - *errorCode = Tcl_GetErrno(); - return -1; - } else if (numChars > 0) { - /* Successfully read something. */ - return numChars * sizeof(WCHAR); - } else { - /* - * Ctrl-C/Ctrl-Brk interrupt. Loop around to retry. - * We have to reacquire the lock. No worried about handleInfoPtr - * having gone away since the channel holds a reference. - */ - AcquireSRWLockExclusive(&handleInfoPtr->lock); - continue; - } - } - /* - * Deferring blocking read to reader thread. - * Release the lock and sleep. Note that because the channel - * holds a reference count on handleInfoPtr, it will not - * be deallocated while the lock is released. - */ - handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV, - &handleInfoPtr->lock, INFINITE, 0)) { - Tcl_WinConvertError(GetLastError()); - *errorCode = Tcl_GetErrno(); - numRead = -1; - break; + /* + * Reset the buffer + */ + + infoPtr->readFlags &= ~CONSOLE_BUFFERED; + infoPtr->offset = 0; } - /* Lock is reacquired, loop back to try again */ + return bytesRead; } - /* We read data. Ask for more if either async or watching for reads */ - if ((chanInfoPtr->flags & CONSOLE_ASYNC) - || (chanInfoPtr->watchMask & TCL_READABLE)) { - handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); + /* + * Attempt to read bufSize bytes. The read will return immediately if + * there is any data available. Otherwise it will block until at least one + * byte is available or an EOF occurs. + */ + + if (readConsoleBytes(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count) + == TRUE) { + buf[count] = '\0'; + return count; } - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - return numRead; + return -1; } /* @@ -1246,113 +765,79 @@ ConsoleInputProc( * *---------------------------------------------------------------------- */ + static int ConsoleOutputProc( - void *instanceData, /* Console state. */ - const char *buf, /* The data buffer. */ + ClientData instanceData, /* Console state. */ + CONST char *buf, /* The data buffer. */ int toWrite, /* How many bytes to write? */ int *errorCode) /* Where to store error code. */ { - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - ConsoleHandleInfo *handleInfoPtr; - Tcl_Size numWritten; + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; + DWORD bytesWritten, timeout; *errorCode = 0; + timeout = (infoPtr->flags & CONSOLE_ASYNC) ? 0 : INFINITE; + if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) { + /* + * The writer thread is blocked waiting for a write to complete and + * the channel is in non-blocking mode. + */ - if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { - /* Some other thread would have *previously* closed the stdio handle */ - *errorCode = EPIPE; - return -1; + errno = EAGAIN; + goto error; } - AcquireSRWLockShared(&gConsoleLock); - handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr == NULL) { - /* Really shouldn't happen since channel is holding a reference */ - *errorCode = EPIPE; - ReleaseSRWLockShared(&gConsoleLock); - return -1; - } - AcquireSRWLockExclusive(&handleInfoPtr->lock); - ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */ + /* + * Check for a background error on the last write. + */ - /* Keep looping until all written. Break out for async and errors */ - numWritten = 0; - while (1) { - /* Check for error and closing on every loop. */ - if (handleInfoPtr->lastError != 0) { - Tcl_WinConvertError(handleInfoPtr->lastError); - *errorCode = Tcl_GetErrno(); - numWritten = -1; - break; - } - if (handleInfoPtr->console == INVALID_HANDLE_VALUE) { - *errorCode = EPIPE; - chanInfoPtr->handle = INVALID_HANDLE_VALUE; - numWritten = -1; - break; - } + if (infoPtr->writeError) { + TclWinConvertError(infoPtr->writeError); + infoPtr->writeError = 0; + goto error; + } + if (infoPtr->flags & CONSOLE_ASYNC) { /* - * We can either write directly or through the console thread's - * ring buffer. We have to do the latter when - * (1) the operation is async since WriteConsoleChars is always blocking - * (2) when there is already data in the ring buffer because we don't - * want to reorder output from within a thread - * (3) when there are an odd number of bytes since WriteConsole - * takes whole WCHARs - * (4) when the pointer is not aligned on WCHAR - * The ring buffer deals with cases (3) and (4). It would be harder - * to duplicate that here. + * The console is non-blocking, so copy the data into the output + * buffer and restart the writer thread. */ - if ((chanInfoPtr->flags & CONSOLE_ASYNC) /* Case (1) */ - || RingBufferLength(&handleInfoPtr->buffer) != 0 /* Case (2) */ - || (toWrite & 1) != 0 /* Case (3) */ - || (PTR2INT(buf) & 1) != 0) { /* Case (4) */ - numWritten += RingBufferIn(&handleInfoPtr->buffer, - numWritten + buf, toWrite - numWritten, 1); - if (numWritten == toWrite || chanInfoPtr->flags & CONSOLE_ASYNC) { - /* All done or async, just accept whatever was written */ - break; - } + + if (toWrite > infoPtr->writeBufLen) { /* - * Release the lock and sleep. Note that because the channel - * holds a reference count on handleInfoPtr, it will not - * be deallocated while the lock is released. + * Reallocate the buffer to be large enough to hold the data. */ - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV, - &handleInfoPtr->lock, INFINITE, 0)) { - /* Report the error */ - Tcl_WinConvertError(GetLastError()); - *errorCode = Tcl_GetErrno(); - numWritten = -1; - break; - } - } else { - /* Direct output */ - DWORD winStatus; - HANDLE consoleHandle = handleInfoPtr->console; - /* Unlock before blocking in WriteConsole */ - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - /* UNLOCKED so return, DON'T break out of loop as it will unlock - * again! */ - winStatus = WriteConsoleChars(consoleHandle, - (WCHAR *)buf, toWrite / sizeof(WCHAR), &numWritten); - if (winStatus == ERROR_SUCCESS) { - return numWritten * sizeof(WCHAR); - } else { - Tcl_WinConvertError(winStatus); - *errorCode = Tcl_GetErrno(); - return -1; + + if (infoPtr->writeBuf) { + ckfree(infoPtr->writeBuf); } + infoPtr->writeBufLen = toWrite; + infoPtr->writeBuf = ckalloc((size_t)toWrite); } + memcpy(infoPtr->writeBuf, buf, (size_t)toWrite); + infoPtr->toWrite = toWrite; + ResetEvent(infoPtr->writable); + SetEvent(infoPtr->startWriter); + bytesWritten = toWrite; + } else { + /* + * In the blocking case, just try to write the buffer directly. This + * avoids an unnecessary copy. + */ - /* Lock must have been reacquired before continuing loop */ + if (writeConsoleBytes(infoPtr->handle, buf, (DWORD)toWrite, + &bytesWritten) + == FALSE) { + TclWinConvertError(GetLastError()); + goto error; + } } - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - return numWritten; + return bytesWritten; + + error: + *errorCode = errno; + return -1; } /* @@ -1382,84 +867,66 @@ ConsoleEventProc( int flags) /* Flags that indicate what events to handle, * such as TCL_FILE_EVENTS. */ { - ConsoleEvent *consoleEvPtr = (ConsoleEvent *) evPtr; - ConsoleChannelInfo *chanInfoPtr; - int freeChannel; - int mask = 0; + ConsoleEvent *consoleEvPtr = (ConsoleEvent *)evPtr; + ConsoleInfo *infoPtr; + int mask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return 0; } - chanInfoPtr = consoleEvPtr->chanInfoPtr; /* - * We know chanInfoPtr is valid because its reference count would have - * been incremented when the event was queued. The corresponding release - * happens in this function. + * Search through the list of watched consoles for the one whose handle + * matches the event. We do this rather than simply dereferencing the + * handle in the event so that consoles can be deleted while the event is + * in the queue. */ + for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (consoleEvPtr->infoPtr == infoPtr) { + infoPtr->flags &= ~(CONSOLE_PENDING); + break; + } + } + /* - * Global lock used for chanInfoPtr. A read (shared) lock suffices - * because all access is within the channel owning thread with the - * exception of watchers which is a read-only access. See comments - * to ConsoleChannelInfo. + * Remove stale events. */ - AcquireSRWLockShared(&gConsoleLock); - chanInfoPtr->flags &= ~CONSOLE_EVENT_QUEUED; + + if (!infoPtr) { + return 1; + } /* - * Only handle the event if the Tcl channel has not gone away AND is - * still owned by this thread AND is still watching events. + * Check to see if the console is readable. Note that we can't tell if a + * console is writable, so we always report it as being writable unless we + * have detected EOF. */ - if (chanInfoPtr->channel && chanInfoPtr->threadId == Tcl_GetCurrentThread() - && (chanInfoPtr->watchMask & (TCL_READABLE|TCL_WRITABLE))) { - ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr == NULL) { - /* Console was closed. EOF->read event only (not write) */ - if (chanInfoPtr->watchMask & TCL_READABLE) { - mask = TCL_READABLE; - } - } else { - AcquireSRWLockShared(&handleInfoPtr->lock); - /* Remember at most one of READABLE, WRITABLE set */ - if ((chanInfoPtr->watchMask & TCL_READABLE) - && RingBufferLength(&handleInfoPtr->buffer)) { + + mask = 0; + if (infoPtr->watchMask & TCL_WRITABLE) { + if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { + mask = TCL_WRITABLE; + } + } + + if (infoPtr->watchMask & TCL_READABLE) { + if (WaitForRead(infoPtr, 0) >= 0) { + if (infoPtr->readFlags & CONSOLE_EOF) { mask = TCL_READABLE; - } else if ((chanInfoPtr->watchMask & TCL_WRITABLE) - && RingBufferHasFreeSpace(&handleInfoPtr->buffer)) { - /* Generate write event space available */ - mask = TCL_WRITABLE; + } else { + mask |= TCL_READABLE; } - ReleaseSRWLockShared(&handleInfoPtr->lock); } } /* - * Tcl_NotifyChannel can recurse through the file event callback so need - * to release locks first. Our reference still holds so no danger of - * chanInfoPtr being deallocated if the callback closes the channel. + * Inform the channel of the events. */ - ReleaseSRWLockShared(&gConsoleLock); - if (mask) { - Tcl_NotifyChannel(chanInfoPtr->channel, mask); - /* Note: chanInfoPtr ref count may have changed */ - } - - /* No need to lock - see comments earlier */ - - /* Remove the reference to the channel from event record */ - if (chanInfoPtr->numRefs > 1) { - chanInfoPtr->numRefs -= 1; - freeChannel = 0; - } else { - assert(chanInfoPtr->channel == NULL); - freeChannel = 1; - } - - if (freeChannel) { - ckfree(chanInfoPtr); - } + Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); return 1; } @@ -1481,57 +948,42 @@ ConsoleEventProc( static void ConsoleWatchProc( - void *instanceData, /* Console state. */ - int newMask) /* What events to watch for, one of - * of TCL_READABLE, TCL_WRITABLE */ + ClientData instanceData, /* Console state. */ + int mask) /* What events to watch for, OR-ed combination + * of TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ { - ConsoleChannelInfo **nextPtrPtr, *ptr; - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - int oldMask = chanInfoPtr->watchMask; + ConsoleInfo **nextPtrPtr, *ptr; + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; + int oldMask = infoPtr->watchMask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* * Since most of the work is handled by the background threads, we just * need to update the watchMask and then force the notifier to poll once. */ - chanInfoPtr->watchMask = newMask & chanInfoPtr->permissions; - if (chanInfoPtr->watchMask) { + infoPtr->watchMask = mask & infoPtr->validMask; + if (infoPtr->watchMask) { Tcl_Time blockTime = { 0, 0 }; - if (!oldMask) { - AcquireSRWLockExclusive(&gConsoleLock); - /* Add to list of watched channels */ - chanInfoPtr->nextWatchingChannelPtr = gWatchingChannelList; - gWatchingChannelList = chanInfoPtr; - - /* - * For read channels, need to tell the console reader thread - * that we are looking for data since it will not do reads until - * it knows someone is awaiting. - */ - ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr) { - AcquireSRWLockExclusive(&handleInfoPtr->lock); - handleInfoPtr->flags |= CONSOLE_DATA_AWAITED; - WakeConditionVariable(&handleInfoPtr->consoleThreadCV); - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - } - ReleaseSRWLockExclusive(&gConsoleLock); + infoPtr->nextPtr = tsdPtr->firstConsolePtr; + tsdPtr->firstConsolePtr = infoPtr; } Tcl_SetMaxBlockTime(&blockTime); } else if (oldMask) { - /* Remove from list of watched channels */ + /* + * Remove the console from the list of watched consoles. + */ - AcquireSRWLockExclusive(&gConsoleLock); - for (nextPtrPtr = &gWatchingChannelList, ptr = *nextPtrPtr; + for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr; ptr != NULL; - nextPtrPtr = &ptr->nextWatchingChannelPtr, ptr = *nextPtrPtr) { - if (chanInfoPtr == ptr) { - *nextPtrPtr = ptr->nextWatchingChannelPtr; + nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { + if (infoPtr == ptr) { + *nextPtrPtr = ptr->nextPtr; break; } } - ReleaseSRWLockExclusive(&gConsoleLock); } } @@ -1555,70 +1007,117 @@ ConsoleWatchProc( static int ConsoleGetHandleProc( - void *instanceData, /* The console state. */ - TCL_UNUSED(int) /*direction*/, - void **handlePtr) /* Where to store the handle. */ + ClientData instanceData, /* The console state. */ + int direction, /* TCL_READABLE or TCL_WRITABLE */ + ClientData *handlePtr) /* Where to store the handle. */ { - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; - if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) { - return TCL_ERROR; - } else { - *handlePtr = chanInfoPtr->handle; - return TCL_OK; - } + *handlePtr = (ClientData) infoPtr->handle; + return TCL_OK; } /* - *------------------------------------------------------------------------ + *---------------------------------------------------------------------- * - * ConsoleDataAvailable -- + * WaitForRead -- * - * Checks if there is data in the console input queue. + * Wait until some data is available, the console is at EOF or the reader + * thread is blocked waiting for data (if the channel is in non-blocking + * mode). * * Results: - * Returns 1 if the input queue has data, -1 on error else 0 if empty. + * Returns 1 if console is readable. Returns 0 if there is no data on the + * console, but there is buffered data. Returns -1 if an error occurred. + * If an error occurred, the threads may not be synchronized. * * Side effects: - * None. + * Updates the shared state flags. If no error occurred, the reader + * thread is blocked waiting for a signal from the main thread. * - *------------------------------------------------------------------------ + *---------------------------------------------------------------------- */ - static int - ConsoleDataAvailable( - HANDLE consoleHandle) + +static int +WaitForRead( + ConsoleInfo *infoPtr, /* Console state. */ + int blocking) /* Indicates whether call should be blocking + * or not. */ { - INPUT_RECORD input[10]; - DWORD count; - DWORD i; + DWORD timeout, count; + HANDLE *handle = infoPtr->handle; + INPUT_RECORD input; - /* - * Need at least one keyboard event. - */ - if (PeekConsoleInputW(consoleHandle, input, - sizeof(input) / sizeof(input[0]), &count) == FALSE) { - return -1; - } - /* - * Even if windows size and mouse events are disabled, can still have - * events other than keyboard, like focus events. Look for at least one - * keydown event because a trailing LF keyup is always present from the - * last input. However, if our buffer is full, assume there is a key - * down somewhere in the unread buffer. I suppose we could expand the - * buffer but not worth... - */ - if (count == (sizeof(input)/sizeof(input[0]))) { - return 1; - } - for (i = 0; i < count; ++i) { - if (input[i].EventType == KEY_EVENT - && input[i].Event.KeyEvent.bKeyDown) { + while (1) { + /* + * Synchronize with the reader thread. + */ + + timeout = blocking ? INFINITE : 0; + if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) { + /* + * The reader thread is blocked waiting for data and the channel + * is in non-blocking mode. + */ + + errno = EAGAIN; + return -1; + } + + /* + * At this point, the two threads are synchronized, so it is safe to + * access shared state. + */ + + /* + * If the console has hit EOF, it is always readable. + */ + + if (infoPtr->readFlags & CONSOLE_EOF) { + return 1; + } + + if (PeekConsoleInput(handle, &input, 1, &count) == FALSE) { + /* + * Check to see if the peek failed because of EOF. + */ + + TclWinConvertError(GetLastError()); + + if (errno == EOF) { + infoPtr->readFlags |= CONSOLE_EOF; + return 1; + } + + /* + * Ignore errors if there is data in the buffer. + */ + + if (infoPtr->readFlags & CONSOLE_BUFFERED) { + return 0; + } else { + return -1; + } + } + + /* + * If there is data in the buffer, the console must be readable (since + * it is a line-oriented device). + */ + + if (infoPtr->readFlags & CONSOLE_BUFFERED) { return 1; } + + /* + * There wasn't any data available, so reset the thread and try again. + */ + + ResetEvent(infoPtr->readable); + SetEvent(infoPtr->startReader); } - return 0; } - + /* *---------------------------------------------------------------------- * @@ -1628,10 +1127,12 @@ ConsoleGetHandleProc( * available on a console. * * Results: - * Always 0. + * None. * * Side effects: - * Signals the main thread when input become available. + * Signals the main thread when input become available. May cause the + * main thread to wake up by posting a message. May one line from the + * console for each wait operation. * *---------------------------------------------------------------------- */ @@ -1640,182 +1141,75 @@ static DWORD WINAPI ConsoleReaderThread( LPVOID arg) { - ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg; - ConsoleHandleInfo **iterator; - Tcl_Size inputLen = 0; - Tcl_Size inputOffset = 0; - Tcl_Size lastReadSize = 0; - DWORD sleepTime; - char inputChars[INPUT_BUFFER_SIZE]; + ConsoleInfo *infoPtr = (ConsoleInfo *)arg; + HANDLE *handle = infoPtr->handle; + DWORD waitResult; + HANDLE wEvents[2]; - /* - * Keep looping until one of the following happens. - * - there are no more channels listening on the console - * - the console handle has been closed - */ + /* The first event takes precedence. */ + wEvents[0] = infoPtr->stopReader; + wEvents[1] = infoPtr->startReader; - /* This thread is holding a reference so pointer is safe */ - AcquireSRWLockExclusive(&handleInfoPtr->lock); + for (;;) { + /* + * Wait for the main thread to signal before attempting to wait. + */ - while (1) { + waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); - if (handleInfoPtr->numRefs == 1) { + if (waitResult != (WAIT_OBJECT_0 + 1)) { /* - * Sole reference. That's this thread. Exit since no clients - * and no way for a thread to attach to a console after process - * start. + * The start event was not signaled. It must be the stop event or + * an error, so exit this thread. */ + break; } /* - * Shared buffer has no data. If we have some in our private buffer - * copy that. Else check if there has been an error. In both cases - * notify the interp threads. + * Look for data on the console, but first ignore any events that are + * not KEY_EVENTs. */ - if (inputLen > 0 || handleInfoPtr->lastError != 0) { - HANDLE consoleHandle; - if (inputLen > 0) { - /* Private buffer has data. Copy it over. */ - Tcl_Size nStored; - - assert((inputLen - inputOffset) > 0); - nStored = RingBufferIn(&handleInfoPtr->buffer, - inputOffset + inputChars, inputLen - inputOffset, - 1); - inputOffset += nStored; - if (inputOffset == inputLen) { - /* Temp buffer now empty */ - inputOffset = 0; - inputLen = 0; - } - } else { - /* - * On error, nothing but inform caller and wait - * We do not want to exit until there are no client interps. - */ - } + if (readConsoleBytes(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, + (LPDWORD) &infoPtr->bytesRead) != FALSE) { /* - * Wake up any threads waiting either synchronously or - * asynchronously. Since we are providing data, turn off the - * AWAITED flag. If the data provided is not sufficient the - * clients will request again. Note we have to wake up ALL - * awaiting threads, not just one, so they can all reissue - * requests if needed. (In a properly designed app, at most one - * thread should be reading standard input but...) - */ - handleInfoPtr->flags &= ~CONSOLE_DATA_AWAITED; - /* Wake synchronous channels */ - WakeAllConditionVariable(&handleInfoPtr->interpThreadCV); - /* - * Wake up async channels registered for file events. Note in - * order to follow the locking hierarchy, we need to release - * handleInfoPtr->lock before calling NudgeWatchers. + * Data was stored in the buffer. */ - consoleHandle = handleInfoPtr->console; - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - NudgeWatchers(consoleHandle); - AcquireSRWLockExclusive(&handleInfoPtr->lock); - - /* - * Loop back to recheck for exit conditions changes while the - * the lock was not held. - */ - continue; - } - assert(inputLen == 0); + infoPtr->readFlags |= CONSOLE_BUFFERED; + } else { + DWORD err; + err = GetLastError(); - /* - * Read more data in two cases: - * 1. The previous read filled the buffer and there could be more - * data in the console internal *text* buffer. Note - * ConsolePendingInput (checked in ConsoleDataAvailable) will NOT - * show this. It holds input events not yet translated to text. - * 2. Tcl threads want more data AND there is data in the - * ConsolePendingInput buffer. The latter check necessary because - * we do not want to read ahead because the interp thread might - * change the read mode, e.g. turning off echo for password - * input. So only do so if at least one interpreter has requested - * data. - */ - if (lastReadSize == sizeof(inputChars) - || ((handleInfoPtr->flags & CONSOLE_DATA_AWAITED) - && ConsoleDataAvailable(handleInfoPtr->console))) { - DWORD error; - /* Do not hold the lock while blocked in console */ - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - error = ReadConsoleChars(handleInfoPtr->console, - (WCHAR *)inputChars, sizeof(inputChars) / sizeof(WCHAR), - &inputLen); - AcquireSRWLockExclusive(&handleInfoPtr->lock); - if (error == 0) { - inputLen *= sizeof(WCHAR); - lastReadSize = inputLen; - } else { - /* - * We only store the last error. It is up to channel - * handlers whether to close or not in case of errors. - */ - lastReadSize = 0; - handleInfoPtr->lastError = error; - if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) { - handleInfoPtr->console = INVALID_HANDLE_VALUE; - } + if (err == (DWORD)EOF) { + infoPtr->readFlags = CONSOLE_EOF; } - } else { - /* - * Either no one was asking for data, or no data was available. - * In the former case, wait until someone wakes us asking for - * data. In the latter case, there is no alternative but to - * poll since ReadConsole does not support async operation. - * So sleep for a short while and loop back to retry. - */ - sleepTime = - handleInfoPtr->flags & CONSOLE_DATA_AWAITED ? 50 : INFINITE; - SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV, - &handleInfoPtr->lock, sleepTime, 0); } - /* Loop again to check for exit or wait for readers to wake us */ - } - - /* - * Exiting: - * - remove the console from global list - * - close the handle if still valid - * - release the structure - * Note there is not need to check for any watchers because we only - * exit when there are no channels open to this console. - */ - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */ - for (iterator = &gConsoleHandleInfoList; *iterator; - iterator = &(*iterator)->nextPtr) { - if (*iterator == handleInfoPtr) { - *iterator = handleInfoPtr->nextPtr; - break; - } - } - ReleaseSRWLockExclusive(&gConsoleLock); + /* + * Signal the main thread by signalling the readable event and then + * waking up the notifier thread. + */ - /* No need for relocking - no other thread should have access to it now */ - RingBufferClear(&handleInfoPtr->buffer); + SetEvent(infoPtr->readable); - if (handleInfoPtr->console != INVALID_HANDLE_VALUE - && handleInfoPtr->lastError != ERROR_INVALID_HANDLE) { - SetConsoleMode(handleInfoPtr->console, handleInfoPtr->initMode); /* - * NOTE: we do not call CloseHandle(handleInfoPtr->console) here. - * As per the GetStdHandle documentation, it need not be closed. - * Other components may be directly using it. Note however that - * an explicit chan close script command does close the handle - * for all threads. + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. */ - } - ckfree(handleInfoPtr); + Tcl_MutexLock(&consoleMutex); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + Tcl_ThreadAlert(infoPtr->threadId); + } + Tcl_MutexUnlock(&consoleMutex); + } return 0; } @@ -1832,255 +1226,91 @@ ConsoleReaderThread( * Always returns 0. * * Side effects: - * Signals the main thread when an output operation is completed. + + * Signals the main thread when an output operation is completed. May + * cause the main thread to wake up by posting a message. * *---------------------------------------------------------------------- */ + static DWORD WINAPI ConsoleWriterThread( LPVOID arg) { - ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg; - ConsoleHandleInfo **iterator; - BOOL success; - Tcl_Size numBytes; - /* - * This buffer size has no relation really with the size of the shared - * buffer. Could be bigger or smaller. Make larger as multiple threads - * could potentially be writing to it. - */ - char buffer[2*CONSOLE_BUFFER_SIZE]; - /* - * Keep looping until one of the following happens. - * - * - there are not more channels listening on the console - * - the console handle has been closed - * - * On each iteration, - * - if the channel buffer is empty, wait for some channel writer to write - * - if there is data in our buffer, write it to the console - */ - - /* This thread is holding a reference so pointer is safe */ - AcquireSRWLockExclusive(&handleInfoPtr->lock); - while (1) { - /* handleInfoPtr->lock must be held on entry to loop */ + ConsoleInfo *infoPtr = (ConsoleInfo *)arg; + HANDLE *handle = infoPtr->handle; + DWORD count, toWrite, waitResult; + char *buf; + HANDLE wEvents[2]; - int offset; - HANDLE consoleHandle; + /* The first event takes precedence. */ + wEvents[0] = infoPtr->stopWriter; + wEvents[1] = infoPtr->startWriter; + for (;;) { /* - * Sadly, we need to do another copy because do not want to hold - * a lock on handleInfoPtr->buffer while calling WriteConsole as that - * might block. Also, we only want to copy an integral number of - * WCHAR's, i.e. even number of chars so do some length checks up - * front. + * Wait for the main thread to signal before attempting to write. */ - numBytes = RingBufferLength(&handleInfoPtr->buffer); - numBytes &= ~1; /* Copy integral number of WCHARs -> even number of bytes */ - if (numBytes == 0) { - /* No data to write */ - if (handleInfoPtr->numRefs == 1) { - /* - * Sole reference. That's this thread. Exit since no clients - * and no buffered output. - */ - break; - } - /* Wake up any threads waiting synchronously. */ - WakeConditionVariable(&handleInfoPtr->interpThreadCV); - success = SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV, - &handleInfoPtr->lock, INFINITE, 0); - /* Note: lock has been acquired again! */ - if (!success && GetLastError() != ERROR_TIMEOUT) { - /* TODO - what can be done? Should not happen */ - /* For now keep going */ - } - continue; - } - /* We have data to write */ - if ((size_t)numBytes > (sizeof(buffer) / sizeof(buffer[0]))) { - numBytes = sizeof(buffer); + waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); + + if (waitResult != (WAIT_OBJECT_0 + 1)) { + /* + * The start event was not signaled. It must be the stop event or + * an error, so exit this thread. + */ + + break; } - /* No need to check result, we already checked length bytes available */ - RingBufferOut(&handleInfoPtr->buffer, buffer, numBytes, 0); - - consoleHandle = handleInfoPtr->console; - WakeConditionVariable(&handleInfoPtr->interpThreadCV); - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - offset = 0; - while (numBytes > 0) { - Tcl_Size numWChars = numBytes / sizeof(WCHAR); - DWORD status; - status = WriteConsoleChars(handleInfoPtr->console, - (WCHAR *)(offset + buffer), numWChars, &numWChars); - if (status != 0) { - /* Only overwrite if no previous error */ - if (handleInfoPtr->lastError == 0) { - handleInfoPtr->lastError = status; - } - if (status == ERROR_INVALID_HANDLE) { - handleInfoPtr->console = INVALID_HANDLE_VALUE; - } - /* Assume this write is done but keep looping in case - * it is a transient error. Not sure just closing handle - * and exiting thread is a good idea until all references - * from interp threads are gone. - */ + + buf = infoPtr->writeBuf; + toWrite = infoPtr->toWrite; + + /* + * Loop until all of the bytes are written or an error occurs. + */ + + while (toWrite > 0) { + if (writeConsoleBytes(handle, buf, (DWORD)toWrite, + &count) == FALSE) { + infoPtr->writeError = GetLastError(); break; + } else { + toWrite -= count; + buf += count; } - numBytes -= numWChars * sizeof(WCHAR); - offset += numWChars * sizeof(WCHAR); } - /* Wake up any threads waiting synchronously. */ - WakeConditionVariable(&handleInfoPtr->interpThreadCV); /* - * Wake up all channels registered for file events. Note in - * order to follow the locking hierarchy, we cannot hold any locks - * when calling NudgeWatchers. + * Signal the main thread by signalling the writable event and then + * waking up the notifier thread. */ - NudgeWatchers(consoleHandle); - - AcquireSRWLockExclusive(&handleInfoPtr->lock); - } - /* - * Exiting: - * - remove the console from global list - * - release the structure - * NOTE: we do not call CloseHandle(handleInfoPtr->console) here. - * As per the GetStdHandle documentation, it need not be closed. - * Other components may be directly using it. Note however that - * an explicit chan close script command does close the handle - * for all threads. - */ - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */ - for (iterator = &gConsoleHandleInfoList; *iterator; - iterator = &(*iterator)->nextPtr) { - if (*iterator == handleInfoPtr) { - *iterator = handleInfoPtr->nextPtr; - break; - } - } - ReleaseSRWLockExclusive(&gConsoleLock); + SetEvent(infoPtr->writable); - RingBufferClear(&handleInfoPtr->buffer); + /* + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. + */ - ckfree(handleInfoPtr); + Tcl_MutexLock(&consoleMutex); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ - return 0; -} - -/* - *------------------------------------------------------------------------ - * - * AllocateConsoleHandleInfo -- - * - * Allocates a ConsoleHandleInfo for the passed console handle. As - * a side effect starts a console thread to handle i/o on the handle. - * - * Important: Caller must be holding an EXCLUSIVE lock on gConsoleLock - * when calling this function. The lock continues to be held on return. - * - * Results: - * Pointer to an unlocked ConsoleHandleInfo structure. The reference - * count on the structure is 1. This corresponds to the common reference - * from the console thread and the gConsoleHandleInfoList. Returns NULL - * on error. - * - * Side effects: - * A console reader or writer thread is started. The returned structure - * is placed on the active console handler list gConsoleHandleInfoList. - * - *------------------------------------------------------------------------ - */ -static ConsoleHandleInfo * -AllocateConsoleHandleInfo( - HANDLE consoleHandle, - int permissions) /* TCL_READABLE or TCL_WRITABLE */ -{ - ConsoleHandleInfo *handleInfoPtr; - DWORD consoleMode; - - handleInfoPtr = (ConsoleHandleInfo *) ckalloc(sizeof(*handleInfoPtr)); - memset(handleInfoPtr, 0, sizeof(*handleInfoPtr)); - memset(handleInfoPtr, 0, sizeof(*handleInfoPtr)); - handleInfoPtr->console = consoleHandle; - InitializeSRWLock(&handleInfoPtr->lock); - InitializeConditionVariable(&handleInfoPtr->consoleThreadCV); - InitializeConditionVariable(&handleInfoPtr->interpThreadCV); - RingBufferInit(&handleInfoPtr->buffer, CONSOLE_BUFFER_SIZE); - handleInfoPtr->lastError = 0; - handleInfoPtr->permissions = permissions; - handleInfoPtr->numRefs = 1; /* See function header */ - if (permissions == TCL_READABLE) { - GetConsoleMode(consoleHandle, &handleInfoPtr->initMode); - consoleMode = handleInfoPtr->initMode; - consoleMode &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); - consoleMode |= ENABLE_LINE_INPUT; - SetConsoleMode(consoleHandle, consoleMode); - } - handleInfoPtr->consoleThread = CreateThread( - NULL, /* default security descriptor */ - 2*CONSOLE_BUFFER_SIZE, /* Stack size - gets rounded up to granularity */ - permissions == TCL_READABLE ? ConsoleReaderThread : ConsoleWriterThread, - handleInfoPtr, /* Pass to thread */ - 0, /* Flags - no special cases */ - NULL); /* Don't care about thread id */ - if (handleInfoPtr->consoleThread == NULL) { - /* Note - SRWLock and condition variables do not need finalization */ - RingBufferClear(&handleInfoPtr->buffer); - ckfree(handleInfoPtr); - return NULL; + Tcl_ThreadAlert(infoPtr->threadId); + } + Tcl_MutexUnlock(&consoleMutex); } - /* Chain onto global list */ - handleInfoPtr->nextPtr = gConsoleHandleInfoList; - gConsoleHandleInfoList = handleInfoPtr; - - return handleInfoPtr; + return 0; } /* - *------------------------------------------------------------------------ - * - * FindConsoleInfo -- - * - * Finds the ConsoleHandleInfo record for a given ConsoleChannelInfo. - * The found record must match the console handle. It is the caller's - * responsibility to check the permissions (read/write) in the returned - * ConsoleHandleInfo match permissions in chanInfoPtr. This function does - * not check that. - * - * Important: Caller must be holding an shared or exclusive lock on - * gConsoleMutex. That ensures the returned pointer stays valid on - * return without risk of deallocation by other threads. - * - * Results: - * Pointer to the found ConsoleHandleInfo or NULL if not found - * - * Side effects: - * None. - * - *------------------------------------------------------------------------ - */ -static ConsoleHandleInfo * -FindConsoleInfo(const ConsoleChannelInfo *chanInfoPtr) -{ - ConsoleHandleInfo *handleInfoPtr; - for (handleInfoPtr = gConsoleHandleInfoList; handleInfoPtr; handleInfoPtr = handleInfoPtr->nextPtr) { - if (handleInfoPtr->console == chanInfoPtr->handle) { - return handleInfoPtr; - } - } - return NULL; -} - -/* *---------------------------------------------------------------------- * * TclWinOpenConsoleChannel -- @@ -2093,34 +1323,37 @@ FindConsoleInfo(const ConsoleChannelInfo *chanInfoPtr) * Returns the new channel, or NULL. * * Side effects: - * May open the channel. + * May open the channel * *---------------------------------------------------------------------- */ + Tcl_Channel TclWinOpenConsoleChannel( HANDLE handle, char *channelName, int permissions) { - ConsoleChannelInfo *chanInfoPtr; - ConsoleHandleInfo *handleInfoPtr; - - /* A console handle can either be input or output, not both */ - if (permissions != TCL_READABLE && permissions != TCL_WRITABLE) { - return NULL; - } + char encoding[4 + TCL_INTEGER_SPACE]; + ConsoleInfo *infoPtr; + DWORD id, modes; ConsoleInit(); - chanInfoPtr = (ConsoleChannelInfo *)ckalloc(sizeof(*chanInfoPtr)); - memset(chanInfoPtr, 0, sizeof(*chanInfoPtr)); + /* + * See if a channel with this handle already exists. + */ + + infoPtr = (ConsoleInfo *) ckalloc((unsigned) sizeof(ConsoleInfo)); + memset(infoPtr, 0, sizeof(ConsoleInfo)); + + infoPtr->validMask = permissions; + infoPtr->handle = handle; + infoPtr->channel = (Tcl_Channel) NULL; - chanInfoPtr->permissions = permissions; - chanInfoPtr->handle = handle; - chanInfoPtr->channel = (Tcl_Channel) NULL; + wsprintfA(encoding, "cp%d", GetConsoleCP()); - chanInfoPtr->threadId = Tcl_GetCurrentThread(); + infoPtr->threadId = Tcl_GetCurrentThread(); /* * Use the pointer for the name of the result channel. This keeps the @@ -2128,7 +1361,10 @@ TclWinOpenConsoleChannel( * for instance). */ - TclWinGenerateChannelName(channelName, "file", chanInfoPtr); + sprintf(channelName, "file%" TCL_I_MODIFIER "x", (size_t)infoPtr); + + infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, + (ClientData) infoPtr, permissions); if (permissions & TCL_READABLE) { /* @@ -2137,76 +1373,41 @@ TclWinOpenConsoleChannel( * we only want to catch when complete lines are ready for reading. */ - chanInfoPtr->flags |= CONSOLE_READ_OPS; - GetConsoleMode(handle, &chanInfoPtr->initMode); - -#ifdef OBSOLETE - /* Why was priority being set on console input? Code smell */ - SetThreadPriority(infoPtr->reader.thread, THREAD_PRIORITY_HIGHEST); -#endif - } else { - /* Already checked permissions is WRITABLE if not READABLE */ - /* TODO - enable ansi escape processing? */ + GetConsoleMode(infoPtr->handle, &modes); + modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); + modes |= ENABLE_LINE_INPUT; + SetConsoleMode(infoPtr->handle, modes); + + infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL); + infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); } - /* - * Global lock but that's ok. See comments top of file. Allocations - * will happen only a few times in the life of a process and that too - * generally at start up where only one thread is active. - */ - AcquireSRWLockExclusive(&gConsoleLock); /*Allocate needs exclusive lock */ - - handleInfoPtr = FindConsoleInfo(chanInfoPtr); - if (handleInfoPtr == NULL) { - /* Not found. Allocate one */ - handleInfoPtr = AllocateConsoleHandleInfo(handle, permissions); - } else { - /* Found. Its direction (read/write) better be the same */ - if (handleInfoPtr->permissions != permissions) { - handleInfoPtr = NULL; - } + if (permissions & TCL_WRITABLE) { + infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL); + infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST); } - if (handleInfoPtr == NULL) { - ReleaseSRWLockExclusive(&gConsoleLock); - if (permissions == TCL_READABLE) { - SetConsoleMode(handle, chanInfoPtr->initMode); - } - ckfree(chanInfoPtr); - return NULL; - } - - /* - * There is effectively a reference to this structure from the Tcl - * channel subsystem. So record that. This reference will be dropped - * when the Tcl channel is closed. - */ - chanInfoPtr->numRefs = 1; - - /* - * Need to keep track of number of referencing channels for closing. - * The pointer is safe since there is a reference held to it from - * gConsoleHandleInfoList but still need to lock the structure itself - */ - AcquireSRWLockExclusive(&handleInfoPtr->lock); - handleInfoPtr->numRefs += 1; - ReleaseSRWLockExclusive(&handleInfoPtr->lock); - - ReleaseSRWLockExclusive(&gConsoleLock); - - /* Note Tcl_CreateChannel never fails other than panic on error */ - chanInfoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, - chanInfoPtr, permissions); - /* - * Consoles have default translation of auto and ^Z eof char, which means + * Files have default translation of AUTO and ^Z eof char, which means * that a ^Z will be accepted as EOF when reading. */ - Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-translation", "auto"); - Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-eofchar", "\x1A {}"); - Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-encoding", "utf-16"); - return chanInfoPtr->channel; + Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); + Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); + if (tclWinProcs->useWide) + Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", "unicode"); + else + Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding); + + return infoPtr->channel; } /* @@ -2227,221 +1428,35 @@ TclWinOpenConsoleChannel( static void ConsoleThreadActionProc( - void *instanceData, + ClientData instanceData, int action) { - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - - /* No need for any locks as no other thread will be writing to it */ - if (action == TCL_CHANNEL_THREAD_INSERT) { - ConsoleInit(); /* Needed to set up event source handlers for this thread */ - chanInfoPtr->threadId = Tcl_GetCurrentThread(); - } else { - chanInfoPtr->threadId = NULL; - } -} - -/* - *---------------------------------------------------------------------- - * - * ConsoleSetOptionProc -- - * - * Sets an option on a channel. - * - * Results: - * A standard Tcl result. Also sets the interp's result on error if - * interp is not NULL. - * - * Side effects: - * May modify an option on a console. Sets Error message if needed (by - * calling Tcl_BadChannelOption). - * - *---------------------------------------------------------------------- - */ -static int -ConsoleSetOptionProc( - void *instanceData, /* File state. */ - Tcl_Interp *interp, /* For error reporting - can be NULL. */ - const char *optionName, /* Which option to set? */ - const char *value) /* New value for option. */ -{ - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - int len = strlen(optionName); - int vlen = strlen(value); - - /* - * Option -inputmode normal|password|raw - */ - - if ((chanInfoPtr->flags & CONSOLE_READ_OPS) && (len > 1) && - (strncmp(optionName, "-inputmode", len) == 0)) { - DWORD mode; - - if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) { - Tcl_WinConvertError(GetLastError()); - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read console mode: %s", - Tcl_PosixError(interp))); - } - return TCL_ERROR; - } - if (strncasecmp(value, "NORMAL", vlen) == 0) { - mode |= - ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; - } else if (strncasecmp(value, "PASSWORD", vlen) == 0) { - mode |= ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT; - mode &= ~ENABLE_ECHO_INPUT; - } else if (strncasecmp(value, "RAW", vlen) == 0) { - mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); - } else if (strncasecmp(value, "RESET", vlen) == 0) { - /* - * Reset to the initial mode, whatever that is. - */ - mode = chanInfoPtr->initMode; - } else { - if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "bad mode \"%s\" for -inputmode: must be" - " normal, password, raw, or reset", value)); - Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE", - "VALUE", (char *)NULL); - } - return TCL_ERROR; - } - if (SetConsoleMode(chanInfoPtr->handle, mode) == 0) { - Tcl_WinConvertError(GetLastError()); - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't set console mode: %s", - Tcl_PosixError(interp))); - } - return TCL_ERROR; - } - - return TCL_OK; - } - - if (chanInfoPtr->flags & CONSOLE_READ_OPS) { - return Tcl_BadChannelOption(interp, optionName, "inputmode"); - } else { - return Tcl_BadChannelOption(interp, optionName, ""); - } -} - -/* - *---------------------------------------------------------------------- - * - * ConsoleGetOptionProc -- - * - * Gets a mode associated with an IO channel. If the optionName arg is - * non-NULL, retrieves the value of that option. If the optionName arg is - * NULL, retrieves a list of alternating option names and values for the - * given channel. - * - * Results: - * A standard Tcl result. Also sets the supplied DString to the string - * value of the option(s) returned. Sets error message if needed - * (by calling Tcl_BadChannelOption). - * - *---------------------------------------------------------------------- - */ - -static int -ConsoleGetOptionProc( - void *instanceData, /* File state. */ - Tcl_Interp *interp, /* For error reporting - can be NULL. */ - const char *optionName, /* Option to get. */ - Tcl_DString *dsPtr) /* Where to store value(s). */ -{ - ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData; - int valid = 0; /* Flag if valid option parsed. */ - unsigned int len; - char buf[TCL_INTEGER_SPACE]; - - if (optionName == NULL) { - len = 0; - } else { - len = strlen(optionName); - } + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; - /* - * Get option -inputmode - * - * This is a great simplification of the underlying reality, but actually - * represents what almost all scripts really want to know. + /* We do not access firstConsolePtr in the thread structures. This is not + * for all serials managed by the thread, but only those we are watching. + * Removal of the filevent handlers before transfer thus takes care of + * this structure. */ - if (chanInfoPtr->flags & CONSOLE_READ_OPS) { - if (len == 0) { - Tcl_DStringAppendElement(dsPtr, "-inputmode"); - } - if (len==0 || (len>1 && strncmp(optionName, "-inputmode", len)==0)) { - DWORD mode; - - valid = 1; - if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) { - Tcl_WinConvertError(GetLastError()); - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read console mode: %s", - Tcl_PosixError(interp))); - } - return TCL_ERROR; - } - if (mode & ENABLE_LINE_INPUT) { - if (mode & ENABLE_ECHO_INPUT) { - Tcl_DStringAppendElement(dsPtr, "normal"); - } else { - Tcl_DStringAppendElement(dsPtr, "password"); - } - } else { - Tcl_DStringAppendElement(dsPtr, "raw"); - } - } - } else { + Tcl_MutexLock(&consoleMutex); + if (action == TCL_CHANNEL_THREAD_INSERT) { /* - * Output channel. Get option -winsize - * Option is readonly and returned by [fconfigure chan -winsize] but not - * returned by [fconfigure chan] without explicit option name. + * We can't copy the thread information from the channel when the + * channel is created. At this time the channel back pointer has not + * been set yet. However in that case the threadId has already been + * set by TclpCreateCommandChannel itself, so the structure is still + * good. */ - if (len == 0) { - Tcl_DStringAppendElement(dsPtr, "-winsize"); - } - if (len == 0 || (len > 1 && strncmp(optionName, "-winsize", len) == 0)) { - CONSOLE_SCREEN_BUFFER_INFO consoleInfo; - - valid = 1; - if (!GetConsoleScreenBufferInfo(chanInfoPtr->handle, - &consoleInfo)) { - Tcl_WinConvertError(GetLastError()); - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read console size: %s", - Tcl_PosixError(interp))); - } - return TCL_ERROR; - } - Tcl_DStringStartSublist(dsPtr); - snprintf(buf, sizeof(buf), "%d", - consoleInfo.srWindow.Right - consoleInfo.srWindow.Left + 1); - Tcl_DStringAppendElement(dsPtr, buf); - snprintf(buf, sizeof(buf), "%d", - consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1); - Tcl_DStringAppendElement(dsPtr, buf); - Tcl_DStringEndSublist(dsPtr); + ConsoleInit(); + if (infoPtr->channel != NULL) { + infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel); } - } - - if (valid) { - return TCL_OK; - } - if (chanInfoPtr->flags & CONSOLE_READ_OPS) { - return Tcl_BadChannelOption(interp, optionName, "inputmode"); } else { - return Tcl_BadChannelOption(interp, optionName, "winsize"); + infoPtr->threadId = NULL; } + Tcl_MutexUnlock(&consoleMutex); } /* |
