/*
 * tclWinConsole.c --
 *
 *	This file implements the Windows-specific console functions, and the
 *	"console" channel driver. Windows 7 or later required.
 *
 * Copyright © 2022 Ashok P. Nadkarni
 *
 * 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>

/*
 * 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.
 */

static int gInitialized = 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.
 */
#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

/*
 * Ring buffer for storing data. Actual data is from bufPtr[start]:bufPtr[size-1]
 * and bufPtr[0]:bufPtr[length - (size-start)].
 */
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_))

/*
 * 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.
 */
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;

/*
 * 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. */
    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,
				 * 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;

/*
 * 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
				 * that we still have to verify that the
				 * console exists before dereferencing this
				 * pointer. */
} ConsoleEvent;

/*
 * 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		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		ConsoleInit(void);
static int		ConsoleInputProc(void *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 DWORD WINAPI	ConsoleReaderThread(LPVOID arg);
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;

/*
 * This structure describes the channel type structure for command console
 * based IO.
 */

static const Tcl_ChannelType consoleChannelType = {
    "console",
    TCL_CHANNEL_VERSION_5,
    NULL,			/* Deprecated. */
    ConsoleInputProc,
    ConsoleOutputProc,
    NULL,			/* Deprecated. */
    ConsoleSetOptionProc,
    ConsoleGetOptionProc,
    ConsoleWatchProc,
    ConsoleGetHandleProc,
    ConsoleCloseProc,
    ConsoleBlockModeProc,
    NULL,			/* Flush proc. */
    NULL,			/* Bubbled event handler proc. */
    NULL,			/* Seek proc. */
    ConsoleThreadActionProc,
    NULL			/* Truncation proc. */
};

/*
 *------------------------------------------------------------------------
 *
 * 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 *)Tcl_Alloc(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) {
	Tcl_Free(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.
 *
 *------------------------------------------------------------------------
 */
static DWORD
ReadConsoleChars(
    HANDLE hConsole,
    WCHAR *lpBuffer,
    Tcl_Size nChars,
    Tcl_Size *nCharsReadPtr)
{
    DWORD nRead;
    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();
    }
}

/*
 *------------------------------------------------------------------------
 *
 * 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(
    HANDLE hConsole,
    const WCHAR *lpBuffer,
    Tcl_Size nChars,
    Tcl_Size *nCharsWrittenPtr)
{
    DWORD nCharsWritten;
    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();
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleInit --
 *
 *	This function initializes the static variables for this file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Creates a new event source.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleInit(void)
{
    /*
     * Check the initialized flag first, then check again in the mutex. This
     * is a speed enhancement.
     */

    if (!gInitialized) {
	AcquireSRWLockExclusive(&gConsoleLock);
	if (!gInitialized) {
	    gInitialized = 1;
	    Tcl_CreateExitHandler(ProcExitHandler, NULL);
	}
	ReleaseSRWLockExclusive(&gConsoleLock);
    }

    if (TclThreadDataKeyGet(&dataKey) == NULL) {
	ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);

	tsdPtr->notUsed = 0;
	Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL);
	Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleExitHandler --
 *
 *	This function is called to cleanup the console module before Tcl is
 *	unloaded.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Removes the console event source.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleExitHandler(
    TCL_UNUSED(void *))
{
    Tcl_DeleteEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL);
}

/*
 *----------------------------------------------------------------------
 *
 * ProcExitHandler --
 *
 *	This function is called to cleanup the process list before Tcl is
 *	unloaded.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resets the process list.
 *
 *----------------------------------------------------------------------
 */

static void
ProcExitHandler(
    TCL_UNUSED(void *))
{
    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);
}

/*
 *----------------------------------------------------------------------
 *
 * 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.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adjusts the block time if needed.
 *
 *----------------------------------------------------------------------
 */

void
ConsoleSetupProc(
    TCL_UNUSED(void *),
    int flags)			/* Event flags as passed to Tcl_DoOneEvent. */
{
    ConsoleChannelInfo *chanInfoPtr;
    Tcl_Time blockTime = { 0, 0 };
    int block = 1;

    if (!(flags & TCL_FILE_EVENTS)) {
	return;
    }

    /*
     * Walk the list of channels. See general comments for struct
     * ConsoleChannelInfo with regard to locking and field access.
     */
    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 */
		}
	    }
	    ReleaseSRWLockShared(&handleInfoPtr->lock);
	}
    }
    ReleaseSRWLockShared(&gConsoleLock);

    if (!block) {
	/* At least one channel is readable/writable. Set block time to 0 */
	Tcl_SetMaxBlockTime(&blockTime);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleCheckProc --
 *
 *	This procedure is called by Tcl_DoOneEvent to check the console event
 *	source for events.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May queue an event.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleCheckProc(
    TCL_UNUSED(void *),
    int flags)			/* Event flags as passed to Tcl_DoOneEvent. */
{
    ConsoleChannelInfo *chanInfoPtr;
    Tcl_ThreadId me;
    int needEvent;

    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.
     */
    AcquireSRWLockShared(&gConsoleLock);

    for (chanInfoPtr = gWatchingChannelList; chanInfoPtr != NULL;
	    chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) {
	ConsoleHandleInfo *handleInfoPtr;

	if (chanInfoPtr->threadId != me) {
	    /* Some other thread owns the channel */
	    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;
	}

	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 */
	    }
	    /*
	     * 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 */
	    }
	}
	ReleaseSRWLockShared(&handleInfoPtr->lock);

	if (needEvent) {
	    ConsoleEvent *evPtr = (ConsoleEvent *)Tcl_Alloc(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 */
	    evPtr->header.proc = ConsoleEventProc;
	    evPtr->chanInfoPtr = chanInfoPtr;
	    Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
	}
    }

    ReleaseSRWLockShared(&gConsoleLock);
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleBlockModeProc --
 *
 *	Set blocking or non-blocking mode on channel.
 *
 * Results:
 *	0 if successful, errno when failed.
 *
 * Side effects:
 *	Sets the device into blocking or non-blocking mode.
 *
 *----------------------------------------------------------------------
 */

static int
ConsoleBlockModeProc(
    void *instanceData,		/* Instance data for channel. */
    int mode)			/* TCL_MODE_BLOCKING or
				 * TCL_MODE_NONBLOCKING. */
{
    ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;

    /*
     * Consoles on Windows can not be switched between blocking and
     * nonblocking, hence we have to emulate the behavior. This is done in the
     * input function by checking against a bit in the state. We set or unset
     * the bit here to cause the input function to emulate the correct
     * behavior.
     */

    if (mode == TCL_MODE_NONBLOCKING) {
	chanInfoPtr->flags |= CONSOLE_ASYNC;
    } else {
	chanInfoPtr->flags &= ~CONSOLE_ASYNC;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleCloseProc --
 *
 *	Closes a console based IO channel.
 *
 * Results:
 *	0 on success, errno otherwise.
 *
 * Side effects:
 *	Closes the physical channel.
 *
 *----------------------------------------------------------------------
 */

static int
ConsoleCloseProc(
    void *instanceData,		/* Pointer to ConsoleChannelInfo structure. */
    TCL_UNUSED(Tcl_Interp *),
    int flags)
{
    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;
    }

    AcquireSRWLockExclusive(&gConsoleLock);

    /* Remove channel from watchers' list */
    for (nextPtrPtr = &gWatchingChannelList; *nextPtrPtr != NULL;
	    nextPtrPtr = &(*nextPtrPtr)->nextWatchingChannelPtr) {
	if (*nextPtrPtr == (ConsoleChannelInfo *) chanInfoPtr) {
	    *nextPtrPtr = (*nextPtrPtr)->nextWatchingChannelPtr;
	    break;
	}
    }

    handleInfoPtr = FindConsoleInfo(chanInfoPtr);
    if (handleInfoPtr) {
	/*
	 * Console thread may be blocked either waiting for console i/o
	 * or waiting on the condition variable for buffer empty/full
	 */
	AcquireSRWLockShared(&handleInfoPtr->lock);

	if (closeHandle) {
	    handleInfoPtr->console = INVALID_HANDLE_VALUE;
	}

	/* Break the thread out of blocking console i/o */
	handleInfoPtr->numRefs -= 1; /* Remove reference from this channel */
	if (handleInfoPtr->numRefs == 1) {
	    /*
	     * 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.
	     */
	    CancelSynchronousIo(handleInfoPtr->consoleThread);
	}

	/*
	 * Wake up the console handling thread. Note we do not explicitly
	 * tell it handle is closed (below). It will find out on next access
	 */
	WakeConditionVariable(&handleInfoPtr->consoleThreadCV);

	ReleaseSRWLockShared(&handleInfoPtr->lock);
    }

    ReleaseSRWLockExclusive(&gConsoleLock);

    chanInfoPtr->channel     = NULL;
    chanInfoPtr->watchMask   = 0;
    chanInfoPtr->permissions = 0;

    if (closeHandle && chanInfoPtr->handle != INVALID_HANDLE_VALUE) {
	if (CloseHandle(chanInfoPtr->handle) == FALSE) {
	    Tcl_WinConvertError(GetLastError());
	    errorCode = errno;
	}
	chanInfoPtr->handle = INVALID_HANDLE_VALUE;
    }

    /*
     * 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.
     */
    if (chanInfoPtr->numRefs > 1) {
	/* There may be references already on the event queue */
	chanInfoPtr->numRefs -= 1;
    } else {
	Tcl_Free(chanInfoPtr);
    }

    return errorCode;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleInputProc --
 *
 *	Reads input from the IO channel into the buffer given. Returns count
 *	of how many bytes were actually read, and an error indication.
 *
 * Results:
 *	A count of how many bytes were read is returned and an error
 *	indication is returned in an output argument.
 *
 * Side effects:
 *	Reads input from the actual channel.
 *
 *----------------------------------------------------------------------
 */
static int
ConsoleInputProc(
    void *instanceData,		/* Console state. */
    char *bufPtr,		/* 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 */
    }

    *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 */
    }
    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;
	}
	/*
	 * 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.
	 */
	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;
	}

	/*
	 * 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;
	}

	/* Lock is reacquired, loop back to try again */
    }

    /* 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);
    }

    ReleaseSRWLockExclusive(&handleInfoPtr->lock);
    return numRead;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleOutputProc --
 *
 *	Writes the given output on the IO channel. Returns count of how many
 *	characters were actually written, and an error indication.
 *
 * Results:
 *	A count of how many characters were written is returned and an error
 *	indication is returned in an output argument.
 *
 * Side effects:
 *	Writes output on the actual channel.
 *
 *----------------------------------------------------------------------
 */
static int
ConsoleOutputProc(
    void *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;

    *errorCode = 0;

    if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) {
	/* Some other thread would have *previously* closed the stdio handle */
	*errorCode = EPIPE;
	return -1;
    }

    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 */

    /* 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;
	}

	/*
	 * 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.
	 */
	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;
	    }
	    /*
	     * 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.
	     */
	    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;
	    }
	}

	/* Lock must have been reacquired before continuing loop */
    }
    WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
    ReleaseSRWLockExclusive(&handleInfoPtr->lock);
    return numWritten;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleEventProc --
 *
 *	This function is invoked by Tcl_ServiceEvent when a file event reaches
 *	the front of the event queue. This procedure invokes Tcl_NotifyChannel
 *	on the console.
 *
 * Results:
 *	Returns 1 if the event was handled, meaning it should be removed from
 *	the queue. Returns 0 if the event was not handled, meaning it should
 *	stay on the queue. The only time the event isn't handled is if the
 *	TCL_FILE_EVENTS flag bit isn't set.
 *
 * Side effects:
 *	Whatever the notifier callback does.
 *
 *----------------------------------------------------------------------
 */

static int
ConsoleEventProc(
    Tcl_Event *evPtr,		/* Event to service. */
    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;

    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.
     */

    /*
     * 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.
     */
    AcquireSRWLockShared(&gConsoleLock);
    chanInfoPtr->flags &= ~CONSOLE_EVENT_QUEUED;

    /*
     * Only handle the event if the Tcl channel has not gone away AND is
     * still owned by this thread AND is still watching events.
     */
    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 = TCL_READABLE;
	    } else if ((chanInfoPtr->watchMask & TCL_WRITABLE)
		    && RingBufferHasFreeSpace(&handleInfoPtr->buffer)) {
		/* Generate write event space available */
		mask = TCL_WRITABLE;
	    }
	    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.
     */
    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) {
	Tcl_Free(chanInfoPtr);
    }

    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleWatchProc --
 *
 *	Called by the notifier to set up to watch for events on this channel.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleWatchProc(
    void *instanceData,		/* Console state. */
    int newMask)		/* What events to watch for, one of
				 * TCL_READABLE, TCL_WRITABLE */
{
    ConsoleChannelInfo **nextPtrPtr, *ptr;
    ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
    int oldMask = chanInfoPtr->watchMask;

    /*
     * 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) {
	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);
	}
	Tcl_SetMaxBlockTime(&blockTime);
    } else if (oldMask) {
	/* Remove from list of watched channels */

	AcquireSRWLockExclusive(&gConsoleLock);
	for (nextPtrPtr = &gWatchingChannelList, ptr = *nextPtrPtr;
		ptr != NULL;
		nextPtrPtr = &ptr->nextWatchingChannelPtr, ptr = *nextPtrPtr) {
	    if (chanInfoPtr == ptr) {
		*nextPtrPtr = ptr->nextWatchingChannelPtr;
		break;
	    }
	}
	ReleaseSRWLockExclusive(&gConsoleLock);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleGetHandleProc --
 *
 *	Called from Tcl_GetChannelHandle to retrieve OS handles from inside a
 *	command consoleline based channel.
 *
 * Results:
 *	Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no
 *	handle for the specified direction.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
ConsoleGetHandleProc(
    void *instanceData,		/* The console state. */
    TCL_UNUSED(int) /*direction*/,
    void **handlePtr)		/* Where to store the handle. */
{
    ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;

    if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) {
	return TCL_ERROR;
    } else {
	*handlePtr = chanInfoPtr->handle;
	return TCL_OK;
    }
}

/*
 *------------------------------------------------------------------------
 *
 * ConsoleDataAvailable --
 *
 *    Checks if there is data in the console input queue.
 *
 * Results:
 *    Returns 1 if the input queue has data, -1 on error else 0 if empty.
 *
 * Side effects:
 *    None.
 *
 *------------------------------------------------------------------------
 */
 static int
 ConsoleDataAvailable(
    HANDLE consoleHandle)
{
    INPUT_RECORD input[10];
    DWORD count;
    DWORD i;

    /*
     * 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) {
	    return 1;
	}
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleReaderThread --
 *
 *	This function runs in a separate thread and waits for input to become
 *	available on a console.
 *
 * Results:
 *	Always 0.
 *
 * Side effects:
 *	Signals the main thread when input become available.
 *
 *----------------------------------------------------------------------
 */

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];

    /*
     * Keep looping until one of the following happens.
     * - there are no more channels listening on the console
     * - the console handle has been closed
     */

    /* This thread is holding a reference so pointer is safe */
    AcquireSRWLockExclusive(&handleInfoPtr->lock);

    while (1) {

	if (handleInfoPtr->numRefs == 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.
	     */
	    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.
	 */
	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.
		 */
	    }

	    /*
	     * 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.
	     */
	    consoleHandle = handleInfoPtr->console;
	    ReleaseSRWLockExclusive(&handleInfoPtr->lock);
	    NudgeWatchers(consoleHandle);
	    AcquireSRWLockExclusive(&handleInfoPtr->lock);

	    /*
	     * Loop back to recheck for exit conditions changes while the
	     * lock was not held.
	     */
	    continue;
	}

	assert(inputLen == 0);

	/*
	 * 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;
		}
	    }
	} 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);

    /* No need for relocking - no other thread should have access to it now */
    RingBufferClear(&handleInfoPtr->buffer);

    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.
	 */
    }

    Tcl_Free(handleInfoPtr);

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleWriterThread --
 *
 *	This function runs in a separate thread and writes data onto a
 *	console.
 *
 * Results:
 *	Always returns 0.
 *
 * Side effects:
 *	Signals the main thread when an output operation is completed.
 *
 *----------------------------------------------------------------------
 */
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 */

	int offset;
	HANDLE consoleHandle;

	/*
	 * 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.
	 */
	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);
	}
	/* 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.
		 */
		break;
	    }
	    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.
	 */
	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);

    RingBufferClear(&handleInfoPtr->buffer);

    Tcl_Free(handleInfoPtr);

    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 *)Tcl_Alloc(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);
	Tcl_Free(handleInfoPtr);
	return NULL;
    }

    /* Chain onto global list */
    handleInfoPtr->nextPtr = gConsoleHandleInfoList;
    gConsoleHandleInfoList = handleInfoPtr;

    return handleInfoPtr;
}

/*
 *------------------------------------------------------------------------
 *
 * 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 --
 *
 *	Constructs a Console channel for the specified standard OS handle.
 *	This is a helper function to break up the construction of channels
 *	into File, Console, or Serial.
 *
 * Results:
 *	Returns the new channel, or NULL.
 *
 * Side effects:
 *	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;
    }

    ConsoleInit();

    chanInfoPtr = (ConsoleChannelInfo *)Tcl_Alloc(sizeof(*chanInfoPtr));
    memset(chanInfoPtr, 0, sizeof(*chanInfoPtr));

    chanInfoPtr->permissions = permissions;
    chanInfoPtr->handle = handle;
    chanInfoPtr->channel = (Tcl_Channel) NULL;

    chanInfoPtr->threadId = Tcl_GetCurrentThread();

    /*
     * Use the pointer for the name of the result channel. This keeps the
     * channel names unique, since some may share handles (stdin/stdout/stderr
     * for instance).
     */

    TclWinGenerateChannelName(channelName, "file", chanInfoPtr);

    if (permissions & TCL_READABLE) {
	/*
	 * Make sure the console input buffer is ready for only character
	 * input notifications and the buffer is set for line buffering. IOW,
	 * 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? */
    }

    /*
     * 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 (handleInfoPtr == NULL) {
	ReleaseSRWLockExclusive(&gConsoleLock);
	if (permissions == TCL_READABLE) {
	    SetConsoleMode(handle, chanInfoPtr->initMode);
	}
	Tcl_Free(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
     * that a ^Z will be accepted as EOF when reading.
     */

    Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-translation", "auto");
    Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-encoding", "utf-16");
    return chanInfoPtr->channel;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleThreadActionProc --
 *
 *	Insert or remove any thread local refs to this channel.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Changes thread local list of valid channels.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleThreadActionProc(
    void *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);
    }

    /*
     * Get option -inputmode
     *
     * This is a great simplification of the underlying reality, but actually
     * represents what almost all scripts really want to know.
     */

    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 {
	/*
	 * Output channel. Get option -winsize
	 * Option is readonly and returned by [fconfigure chan -winsize] but not
	 * returned by [fconfigure chan] without explicit option name.
	 */
	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);
	}
    }

    if (valid) {
	return TCL_OK;
    }
    if (chanInfoPtr->flags & CONSOLE_READ_OPS) {
	return Tcl_BadChannelOption(interp, optionName, "inputmode");
    } else {
	return Tcl_BadChannelOption(interp, optionName, "winsize");
    }
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
