tclWinConsole.c
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:37k
源码类别:

通讯编程

开发平台:

Visual C++

  1. /* 
  2.  * tclWinConsole.c --
  3.  *
  4.  * This file implements the Windows-specific console functions,
  5.  * and the "console" channel driver.
  6.  *
  7.  * Copyright (c) 1999 by Scriptics Corp.
  8.  *
  9.  * See the file "license.terms" for information on usage and redistribution
  10.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  11.  *
  12.  * RCS: @(#) $Id: tclWinConsole.c,v 1.11.2.3 2006/03/28 21:02:37 hobbs Exp $
  13.  */
  14. #include "tclWinInt.h"
  15. #include <fcntl.h>
  16. #include <io.h>
  17. #include <sys/stat.h>
  18. /*
  19.  * The following variable is used to tell whether this module has been
  20.  * initialized.
  21.  */
  22. static int initialized = 0;
  23. /*
  24.  * The consoleMutex locks around access to the initialized variable, and it is
  25.  * used to protect background threads from being terminated while they are
  26.  * using APIs that hold locks.
  27.  */
  28. TCL_DECLARE_MUTEX(consoleMutex)
  29. /*
  30.  * Bit masks used in the flags field of the ConsoleInfo structure below.
  31.  */
  32. #define CONSOLE_PENDING (1<<0) /* Message is pending in the queue. */
  33. #define CONSOLE_ASYNC (1<<1) /* Channel is non-blocking. */
  34. /*
  35.  * Bit masks used in the sharedFlags field of the ConsoleInfo structure below.
  36.  */
  37. #define CONSOLE_EOF   (1<<2)  /* Console has reached EOF. */
  38. #define CONSOLE_BUFFERED  (1<<3)  /* data was read into a buffer by the reader
  39.      thread */
  40. #define CONSOLE_BUFFER_SIZE (8*1024)
  41. /*
  42.  * This structure describes per-instance data for a console based channel.
  43.  */
  44. typedef struct ConsoleInfo {
  45.     HANDLE handle;
  46.     int type;
  47.     struct ConsoleInfo *nextPtr;/* Pointer to next registered console. */
  48.     Tcl_Channel channel; /* Pointer to channel structure. */
  49.     int validMask; /* OR'ed combination of TCL_READABLE,
  50.  * TCL_WRITABLE, or TCL_EXCEPTION: indicates
  51.  * which operations are valid on the file. */
  52.     int watchMask; /* OR'ed combination of TCL_READABLE,
  53.  * TCL_WRITABLE, or TCL_EXCEPTION: indicates
  54.  * which events should be reported. */
  55.     int flags; /* State flags, see above for a list. */
  56.     Tcl_ThreadId threadId; /* Thread to which events should be reported.
  57.  * This value is used by the reader/writer
  58.  * threads. */
  59.     HANDLE writeThread; /* Handle to writer thread. */
  60.     HANDLE readThread; /* Handle to reader thread. */
  61.     HANDLE writable; /* Manual-reset event to signal when the
  62.  * writer thread has finished waiting for
  63.  * the current buffer to be written. */
  64.     HANDLE readable; /* Manual-reset event to signal when the
  65.  * reader thread has finished waiting for
  66.  * input. */
  67.     HANDLE startWriter; /* Auto-reset event used by the main thread to
  68.  * signal when the writer thread should attempt
  69.  * to write to the console. */
  70.     HANDLE stopWriter; /* Auto-reset event used by the main thread to
  71.  * signal when the writer thread should exit.
  72.  */
  73.     HANDLE startReader; /* Auto-reset event used by the main thread to
  74.  * signal when the reader thread should attempt
  75.  * to read from the console. */
  76.     HANDLE stopReader; /* Auto-reset event used by the main thread to
  77.  * signal when the reader thread should exit.
  78.  */
  79.     DWORD writeError; /* An error caused by the last background
  80.  * write.  Set to 0 if no error has been
  81.  * detected.  This word is shared with the
  82.  * writer thread so access must be
  83.  * synchronized with the writable object.
  84.  */
  85.     char *writeBuf; /* Current background output buffer.
  86.  * Access is synchronized with the writable
  87.  * object. */
  88.     int writeBufLen; /* Size of write buffer.  Access is
  89.  * synchronized with the writable
  90.  * object. */
  91.     int toWrite; /* Current amount to be written.  Access is
  92.  * synchronized with the writable object. */
  93.     int readFlags; /* Flags that are shared with the reader
  94.  * thread.  Access is synchronized with the
  95.  * readable object.  */
  96.     int bytesRead;              /* number of bytes in the buffer */
  97.     int offset;                 /* number of bytes read out of the buffer */
  98.     char buffer[CONSOLE_BUFFER_SIZE];
  99.                                 /* Data consumed by reader thread. */
  100. } ConsoleInfo;
  101. typedef struct ThreadSpecificData {
  102.     /*
  103.      * The following pointer refers to the head of the list of consoles
  104.      * that are being watched for file events.
  105.      */
  106.     
  107.     ConsoleInfo *firstConsolePtr;
  108. } ThreadSpecificData;
  109. static Tcl_ThreadDataKey dataKey;
  110. /*
  111.  * The following structure is what is added to the Tcl event queue when
  112.  * console events are generated.
  113.  */
  114. typedef struct ConsoleEvent {
  115.     Tcl_Event header; /* Information that is standard for
  116.  * all events. */
  117.     ConsoleInfo *infoPtr; /* Pointer to console info structure.  Note
  118.  * that we still have to verify that the
  119.  * console exists before dereferencing this
  120.  * pointer. */
  121. } ConsoleEvent;
  122. /*
  123.  * Declarations for functions used only in this file.
  124.  */
  125. static int ConsoleBlockModeProc(ClientData instanceData, int mode);
  126. static void ConsoleCheckProc(ClientData clientData, int flags);
  127. static int ConsoleCloseProc(ClientData instanceData,
  128.     Tcl_Interp *interp);
  129. static int ConsoleEventProc(Tcl_Event *evPtr, int flags);
  130. static void ConsoleExitHandler(ClientData clientData);
  131. static int ConsoleGetHandleProc(ClientData instanceData,
  132.     int direction, ClientData *handlePtr);
  133. static void             ConsoleInit(void);
  134. static int ConsoleInputProc(ClientData instanceData, char *buf,
  135.     int toRead, int *errorCode);
  136. static int ConsoleOutputProc(ClientData instanceData,
  137.     CONST char *buf, int toWrite, int *errorCode);
  138. static DWORD WINAPI ConsoleReaderThread(LPVOID arg);
  139. static void ConsoleSetupProc(ClientData clientData, int flags);
  140. static void ConsoleWatchProc(ClientData instanceData, int mask);
  141. static DWORD WINAPI ConsoleWriterThread(LPVOID arg);
  142. static void ProcExitHandler(ClientData clientData);
  143. static int WaitForRead(ConsoleInfo *infoPtr, int blocking);
  144. static void             ConsoleThreadActionProc _ANSI_ARGS_ ((
  145.    ClientData instanceData, int action));
  146. /*
  147.  * This structure describes the channel type structure for command console
  148.  * based IO.
  149.  */
  150. static Tcl_ChannelType consoleChannelType = {
  151.     "console", /* Type name. */
  152.     TCL_CHANNEL_VERSION_4, /* v4 channel */
  153.     ConsoleCloseProc, /* Close proc. */
  154.     ConsoleInputProc, /* Input proc. */
  155.     ConsoleOutputProc, /* Output proc. */
  156.     NULL, /* Seek proc. */
  157.     NULL, /* Set option proc. */
  158.     NULL, /* Get option proc. */
  159.     ConsoleWatchProc, /* Set up notifier to watch the channel. */
  160.     ConsoleGetHandleProc, /* Get an OS handle from channel. */
  161.     NULL, /* close2proc. */
  162.     ConsoleBlockModeProc, /* Set blocking or non-blocking mode.*/
  163.     NULL, /* flush proc. */
  164.     NULL, /* handler proc. */
  165.     NULL,                       /* wide seek proc */
  166.     ConsoleThreadActionProc,    /* thread action proc */
  167. };
  168. /*
  169.  *----------------------------------------------------------------------
  170.  *
  171.  * ConsoleInit --
  172.  *
  173.  * This function initializes the static variables for this file.
  174.  *
  175.  * Results:
  176.  * None.
  177.  *
  178.  * Side effects:
  179.  * Creates a new event source.
  180.  *
  181.  *----------------------------------------------------------------------
  182.  */
  183. static void
  184. ConsoleInit()
  185. {
  186.     ThreadSpecificData *tsdPtr;
  187.     /*
  188.      * Check the initialized flag first, then check again in the mutex.
  189.      * This is a speed enhancement.
  190.      */
  191.     if (!initialized) {
  192. Tcl_MutexLock(&consoleMutex);
  193. if (!initialized) {
  194.     initialized = 1;
  195.     Tcl_CreateExitHandler(ProcExitHandler, NULL);
  196. }
  197. Tcl_MutexUnlock(&consoleMutex);
  198.     }
  199.     tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey);
  200.     if (tsdPtr == NULL) {
  201. tsdPtr = TCL_TSD_INIT(&dataKey);
  202. tsdPtr->firstConsolePtr = NULL;
  203. Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL);
  204. Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL);
  205.     }
  206. }
  207. /*
  208.  *----------------------------------------------------------------------
  209.  *
  210.  * ConsoleExitHandler --
  211.  *
  212.  * This function is called to cleanup the console module before
  213.  * Tcl is unloaded.
  214.  *
  215.  * Results:
  216.  * None.
  217.  *
  218.  * Side effects:
  219.  * Removes the console event source.
  220.  *
  221.  *----------------------------------------------------------------------
  222.  */
  223. static void
  224. ConsoleExitHandler(
  225.     ClientData clientData) /* Old window proc */
  226. {
  227.     Tcl_DeleteEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL);
  228. }
  229. /*
  230.  *----------------------------------------------------------------------
  231.  *
  232.  * ProcExitHandler --
  233.  *
  234.  * This function is called to cleanup the process list before
  235.  * Tcl is unloaded.
  236.  *
  237.  * Results:
  238.  * None.
  239.  *
  240.  * Side effects:
  241.  * Resets the process list.
  242.  *
  243.  *----------------------------------------------------------------------
  244.  */
  245. static void
  246. ProcExitHandler(
  247.     ClientData clientData) /* Old window proc */
  248. {
  249.     Tcl_MutexLock(&consoleMutex);
  250.     initialized = 0;
  251.     Tcl_MutexUnlock(&consoleMutex);
  252. }
  253. /*
  254.  *----------------------------------------------------------------------
  255.  *
  256.  * ConsoleSetupProc --
  257.  *
  258.  * This procedure is invoked before Tcl_DoOneEvent blocks waiting
  259.  * for an event.
  260.  *
  261.  * Results:
  262.  * None.
  263.  *
  264.  * Side effects:
  265.  * Adjusts the block time if needed.
  266.  *
  267.  *----------------------------------------------------------------------
  268.  */
  269. void
  270. ConsoleSetupProc(
  271.     ClientData data, /* Not used. */
  272.     int flags) /* Event flags as passed to Tcl_DoOneEvent. */
  273. {
  274.     ConsoleInfo *infoPtr;
  275.     Tcl_Time blockTime = { 0, 0 };
  276.     int block = 1;
  277.     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  278.     if (!(flags & TCL_FILE_EVENTS)) {
  279. return;
  280.     }
  281.     
  282.     /*
  283.      * Look to see if any events are already pending.  If they are, poll.
  284.      */
  285.     for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; 
  286.     infoPtr = infoPtr->nextPtr) {
  287. if (infoPtr->watchMask & TCL_WRITABLE) {
  288.     if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) {
  289. block = 0;
  290.     }
  291. }
  292. if (infoPtr->watchMask & TCL_READABLE) {
  293.     if (WaitForRead(infoPtr, 0) >= 0) {
  294. block = 0;
  295.     }
  296. }
  297.     }
  298.     if (!block) {
  299. Tcl_SetMaxBlockTime(&blockTime);
  300.     }
  301. }
  302. /*
  303.  *----------------------------------------------------------------------
  304.  *
  305.  * ConsoleCheckProc --
  306.  *
  307.  * This procedure is called by Tcl_DoOneEvent to check the console
  308.  * event source for events. 
  309.  *
  310.  * Results:
  311.  * None.
  312.  *
  313.  * Side effects:
  314.  * May queue an event.
  315.  *
  316.  *----------------------------------------------------------------------
  317.  */
  318. static void
  319. ConsoleCheckProc(
  320.     ClientData data, /* Not used. */
  321.     int flags) /* Event flags as passed to Tcl_DoOneEvent. */
  322. {
  323.     ConsoleInfo *infoPtr;
  324.     ConsoleEvent *evPtr;
  325.     int needEvent;
  326.     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  327.     if (!(flags & TCL_FILE_EVENTS)) {
  328. return;
  329.     }
  330.     
  331.     /*
  332.      * Queue events for any ready consoles that don't already have events
  333.      * queued.
  334.      */
  335.     for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; 
  336.     infoPtr = infoPtr->nextPtr) {
  337. if (infoPtr->flags & CONSOLE_PENDING) {
  338.     continue;
  339. }
  340. /*
  341.  * Queue an event if the console is signaled for reading or writing.
  342.  */
  343. needEvent = 0;
  344. if (infoPtr->watchMask & TCL_WRITABLE) {
  345.     if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) {
  346. needEvent = 1;
  347.     }
  348. }
  349. if (infoPtr->watchMask & TCL_READABLE) {
  350.     if (WaitForRead(infoPtr, 0) >= 0) {
  351. needEvent = 1;
  352.     }
  353. }
  354. if (needEvent) {
  355.     infoPtr->flags |= CONSOLE_PENDING;
  356.     evPtr = (ConsoleEvent *) ckalloc(sizeof(ConsoleEvent));
  357.     evPtr->header.proc = ConsoleEventProc;
  358.     evPtr->infoPtr = infoPtr;
  359.     Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
  360. }
  361.     }
  362. }
  363. /*
  364.  *----------------------------------------------------------------------
  365.  *
  366.  * ConsoleBlockModeProc --
  367.  *
  368.  * Set blocking or non-blocking mode on channel.
  369.  *
  370.  * Results:
  371.  * 0 if successful, errno when failed.
  372.  *
  373.  * Side effects:
  374.  * Sets the device into blocking or non-blocking mode.
  375.  *
  376.  *----------------------------------------------------------------------
  377.  */
  378. static int
  379. ConsoleBlockModeProc(
  380.     ClientData instanceData, /* Instance data for channel. */
  381.     int mode) /* TCL_MODE_BLOCKING or
  382.                                  * TCL_MODE_NONBLOCKING. */
  383. {
  384.     ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
  385.     
  386.     /*
  387.      * Consoles on Windows can not be switched between blocking and nonblocking,
  388.      * hence we have to emulate the behavior. This is done in the input
  389.      * function by checking against a bit in the state. We set or unset the
  390.      * bit here to cause the input function to emulate the correct behavior.
  391.      */
  392.     if (mode == TCL_MODE_NONBLOCKING) {
  393. infoPtr->flags |= CONSOLE_ASYNC;
  394.     } else {
  395. infoPtr->flags &= ~(CONSOLE_ASYNC);
  396.     }
  397.     return 0;
  398. }
  399. /*
  400.  *----------------------------------------------------------------------
  401.  *
  402.  * ConsoleCloseProc --
  403.  *
  404.  * Closes a console based IO channel.
  405.  *
  406.  * Results:
  407.  * 0 on success, errno otherwise.
  408.  *
  409.  * Side effects:
  410.  * Closes the physical channel.
  411.  *
  412.  *----------------------------------------------------------------------
  413.  */
  414. static int
  415. ConsoleCloseProc(
  416.     ClientData instanceData, /* Pointer to ConsoleInfo structure. */
  417.     Tcl_Interp *interp) /* For error reporting. */
  418. {
  419.     ConsoleInfo *consolePtr = (ConsoleInfo *) instanceData;
  420.     int errorCode;
  421.     ConsoleInfo *infoPtr, **nextPtrPtr;
  422.     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  423.     DWORD exitCode;
  424.     errorCode = 0;
  425.     
  426.     /*
  427.      * Clean up the background thread if necessary.  Note that this
  428.      * must be done before we can close the file, since the 
  429.      * thread may be blocking trying to read from the console.
  430.      */
  431.     
  432.     if (consolePtr->readThread) {
  433. /*
  434.  * The thread may already have closed on it's own.  Check it's
  435.  * exit code.
  436.  */
  437. GetExitCodeThread(consolePtr->readThread, &exitCode);
  438. if (exitCode == STILL_ACTIVE) {
  439.     /*
  440.      * Set the stop event so that if the reader thread is blocked
  441.      * in ConsoleReaderThread on WaitForMultipleEvents, it will exit
  442.      * cleanly.
  443.      */
  444.     SetEvent(consolePtr->stopReader);
  445.     /*
  446.      * Wait at most 20 milliseconds for the reader thread to close.
  447.      */
  448.     if (WaitForSingleObject(consolePtr->readThread, 20)
  449.     == WAIT_TIMEOUT) {
  450. /*
  451.  * Forcibly terminate the background thread as a last
  452.  * resort.  Note that we need to guard against
  453.  * terminating the thread while it is in the middle of
  454.  * Tcl_ThreadAlert because it won't be able to release
  455.  * the notifier lock.
  456.  */
  457. Tcl_MutexLock(&consoleMutex);
  458. /* BUG: this leaks memory. */
  459. TerminateThread(consolePtr->readThread, 0);
  460. Tcl_MutexUnlock(&consoleMutex);
  461.     }
  462. }
  463. CloseHandle(consolePtr->readThread);
  464. CloseHandle(consolePtr->readable);
  465. CloseHandle(consolePtr->startReader);
  466. CloseHandle(consolePtr->stopReader);
  467. consolePtr->readThread = NULL;
  468.     }
  469.     consolePtr->validMask &= ~TCL_READABLE;
  470.     /*
  471.      * Wait for the writer thread to finish the current buffer, then
  472.      * terminate the thread and close the handles.  If the channel is
  473.      * nonblocking, there should be no pending write operations.
  474.      */
  475.     
  476.     if (consolePtr->writeThread) {
  477. if (consolePtr->toWrite) {
  478.     /*
  479.      * We only need to wait if there is something to write.
  480.      * This may prevent infinite wait on exit. [python bug 216289]
  481.      */
  482.     WaitForSingleObject(consolePtr->writable, INFINITE);
  483. }
  484. /*
  485.  * The thread may already have closed on it's own.  Check it's
  486.  * exit code.
  487.  */
  488. GetExitCodeThread(consolePtr->writeThread, &exitCode);
  489. if (exitCode == STILL_ACTIVE) {
  490.     /*
  491.      * Set the stop event so that if the reader thread is blocked
  492.      * in ConsoleWriterThread on WaitForMultipleEvents, it will
  493.      * exit cleanly.
  494.      */
  495.     SetEvent(consolePtr->stopWriter);
  496.     /*
  497.      * Wait at most 20 milliseconds for the writer thread to close.
  498.      */
  499.     if (WaitForSingleObject(consolePtr->writeThread, 20)
  500.     == WAIT_TIMEOUT) {
  501. /*
  502.  * Forcibly terminate the background thread as a last
  503.  * resort.  Note that we need to guard against
  504.  * terminating the thread while it is in the middle of
  505.  * Tcl_ThreadAlert because it won't be able to release
  506.  * the notifier lock.
  507.  */
  508. Tcl_MutexLock(&consoleMutex);
  509. /* BUG: this leaks memory. */
  510. TerminateThread(consolePtr->writeThread, 0);
  511. Tcl_MutexUnlock(&consoleMutex);
  512.     }
  513. }
  514. CloseHandle(consolePtr->writeThread);
  515. CloseHandle(consolePtr->writable);
  516. CloseHandle(consolePtr->startWriter);
  517. CloseHandle(consolePtr->stopWriter);
  518. consolePtr->writeThread = NULL;
  519.     }
  520.     consolePtr->validMask &= ~TCL_WRITABLE;
  521.     /*
  522.      * Don't close the Win32 handle if the handle is a standard channel
  523.      * during the thread exit process.  Otherwise, one thread may kill
  524.      * the stdio of another.
  525.      */
  526.     if (!TclInThreadExit() 
  527.     || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle)
  528. && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle)
  529. && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) {
  530. if (CloseHandle(consolePtr->handle) == FALSE) {
  531.     TclWinConvertError(GetLastError());
  532.     errorCode = errno;
  533. }
  534.     }
  535.     
  536.     consolePtr->watchMask &= consolePtr->validMask;
  537.     /*
  538.      * Remove the file from the list of watched files.
  539.      */
  540.     for (nextPtrPtr = &(tsdPtr->firstConsolePtr), infoPtr = *nextPtrPtr;
  541.     infoPtr != NULL;
  542.     nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) {
  543. if (infoPtr == (ConsoleInfo *)consolePtr) {
  544.     *nextPtrPtr = infoPtr->nextPtr;
  545.     break;
  546. }
  547.     }
  548.     if (consolePtr->writeBuf != NULL) {
  549. ckfree(consolePtr->writeBuf);
  550. consolePtr->writeBuf = 0;
  551.     }
  552.     ckfree((char*) consolePtr);
  553.     return errorCode;
  554. }
  555. /*
  556.  *----------------------------------------------------------------------
  557.  *
  558.  * ConsoleInputProc --
  559.  *
  560.  * Reads input from the IO channel into the buffer given. Returns
  561.  * count of how many bytes were actually read, and an error indication.
  562.  *
  563.  * Results:
  564.  * A count of how many bytes were read is returned and an error
  565.  * indication is returned in an output argument.
  566.  *
  567.  * Side effects:
  568.  * Reads input from the actual channel.
  569.  *
  570.  *----------------------------------------------------------------------
  571.  */
  572. static int
  573. ConsoleInputProc(
  574.     ClientData instanceData, /* Console state. */
  575.     char *buf, /* Where to store data read. */
  576.     int bufSize, /* How much space is available
  577.                                          * in the buffer? */
  578.     int *errorCode) /* Where to store error code. */
  579. {
  580.     ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
  581.     DWORD count, bytesRead = 0;
  582.     int result;
  583.     *errorCode = 0;
  584.     /*
  585.      * Synchronize with the reader thread.
  586.      */
  587.     
  588.     result = WaitForRead(infoPtr, (infoPtr->flags & CONSOLE_ASYNC) ? 0 : 1);
  589.     
  590.     /*
  591.      * If an error occurred, return immediately.
  592.      */
  593.     
  594.     if (result == -1) {
  595. *errorCode = errno;
  596. return -1;
  597.     }
  598.     if (infoPtr->readFlags & CONSOLE_BUFFERED) {
  599. /*
  600.  * Data is stored in the buffer.
  601.  */
  602. if (bufSize < (infoPtr->bytesRead - infoPtr->offset)) {
  603.     memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize);
  604.     bytesRead = bufSize;
  605.     infoPtr->offset += bufSize;
  606. } else {
  607.     memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize);
  608.     bytesRead = infoPtr->bytesRead - infoPtr->offset;
  609.     /*
  610.      * Reset the buffer
  611.      */
  612.     
  613.     infoPtr->readFlags &= ~CONSOLE_BUFFERED;
  614.     infoPtr->offset = 0;
  615. }
  616. return bytesRead;
  617.     }
  618.     
  619.     /*
  620.      * Attempt to read bufSize bytes.  The read will return immediately
  621.      * if there is any data available.  Otherwise it will block until
  622.      * at least one byte is available or an EOF occurs.
  623.      */
  624.     if (ReadConsole(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count,
  625.     (LPOVERLAPPED) NULL) == TRUE) {
  626. buf[count] = '';
  627. return count;
  628.     }
  629.     return -1;
  630. }
  631. /*
  632.  *----------------------------------------------------------------------
  633.  *
  634.  * ConsoleOutputProc --
  635.  *
  636.  * Writes the given output on the IO channel. Returns count of how
  637.  * many characters were actually written, and an error indication.
  638.  *
  639.  * Results:
  640.  * A count of how many characters were written is returned and an
  641.  * error indication is returned in an output argument.
  642.  *
  643.  * Side effects:
  644.  * Writes output on the actual channel.
  645.  *
  646.  *----------------------------------------------------------------------
  647.  */
  648. static int
  649. ConsoleOutputProc(
  650.     ClientData instanceData, /* Console state. */
  651.     CONST char *buf, /* The data buffer. */
  652.     int toWrite, /* How many bytes to write? */
  653.     int *errorCode) /* Where to store error code. */
  654. {
  655.     ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
  656.     DWORD bytesWritten, timeout;
  657.     
  658.     *errorCode = 0;
  659.     timeout = (infoPtr->flags & CONSOLE_ASYNC) ? 0 : INFINITE;
  660.     if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) {
  661. /*
  662.  * The writer thread is blocked waiting for a write to complete
  663.  * and the channel is in non-blocking mode.
  664.  */
  665. errno = EAGAIN;
  666. goto error;
  667.     }
  668.     
  669.     /*
  670.      * Check for a background error on the last write.
  671.      */
  672.     if (infoPtr->writeError) {
  673. TclWinConvertError(infoPtr->writeError);
  674. infoPtr->writeError = 0;
  675. goto error;
  676.     }
  677.     if (infoPtr->flags & CONSOLE_ASYNC) {
  678. /*
  679.  * The console is non-blocking, so copy the data into the output
  680.  * buffer and restart the writer thread.
  681.  */
  682. if (toWrite > infoPtr->writeBufLen) {
  683.     /*
  684.      * Reallocate the buffer to be large enough to hold the data.
  685.      */
  686.     if (infoPtr->writeBuf) {
  687. ckfree(infoPtr->writeBuf);
  688.     }
  689.     infoPtr->writeBufLen = toWrite;
  690.     infoPtr->writeBuf = ckalloc((unsigned int) toWrite);
  691. }
  692. memcpy(infoPtr->writeBuf, buf, (size_t) toWrite);
  693. infoPtr->toWrite = toWrite;
  694. ResetEvent(infoPtr->writable);
  695. SetEvent(infoPtr->startWriter);
  696. bytesWritten = toWrite;
  697.     } else {
  698. /*
  699.  * In the blocking case, just try to write the buffer directly.
  700.  * This avoids an unnecessary copy.
  701.  */
  702. if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite,
  703. &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) {
  704.     TclWinConvertError(GetLastError());
  705.     goto error;
  706. }
  707.     }
  708.     return bytesWritten;
  709.     error:
  710.     *errorCode = errno;
  711.     return -1;
  712. }
  713. /*
  714.  *----------------------------------------------------------------------
  715.  *
  716.  * ConsoleEventProc --
  717.  *
  718.  * This function is invoked by Tcl_ServiceEvent when a file event
  719.  * reaches the front of the event queue.  This procedure invokes
  720.  * Tcl_NotifyChannel on the console.
  721.  *
  722.  * Results:
  723.  * Returns 1 if the event was handled, meaning it should be removed
  724.  * from the queue.  Returns 0 if the event was not handled, meaning
  725.  * it should stay on the queue.  The only time the event isn't
  726.  * handled is if the TCL_FILE_EVENTS flag bit isn't set.
  727.  *
  728.  * Side effects:
  729.  * Whatever the notifier callback does.
  730.  *
  731.  *----------------------------------------------------------------------
  732.  */
  733. static int
  734. ConsoleEventProc(
  735.     Tcl_Event *evPtr, /* Event to service. */
  736.     int flags) /* Flags that indicate what events to
  737.  * handle, such as TCL_FILE_EVENTS. */
  738. {
  739.     ConsoleEvent *consoleEvPtr = (ConsoleEvent *)evPtr;
  740.     ConsoleInfo *infoPtr;
  741.     int mask;
  742.     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  743.     if (!(flags & TCL_FILE_EVENTS)) {
  744. return 0;
  745.     }
  746.     /*
  747.      * Search through the list of watched consoles for the one whose handle
  748.      * matches the event.  We do this rather than simply dereferencing
  749.      * the handle in the event so that consoles can be deleted while the
  750.      * event is in the queue.
  751.      */
  752.     for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL;
  753.     infoPtr = infoPtr->nextPtr) {
  754. if (consoleEvPtr->infoPtr == infoPtr) {
  755.     infoPtr->flags &= ~(CONSOLE_PENDING);
  756.     break;
  757. }
  758.     }
  759.     /*
  760.      * Remove stale events.
  761.      */
  762.     if (!infoPtr) {
  763. return 1;
  764.     }
  765.     /*
  766.      * Check to see if the console is readable.  Note
  767.      * that we can't tell if a console is writable, so we always report it
  768.      * as being writable unless we have detected EOF.
  769.      */
  770.     mask = 0;
  771.     if (infoPtr->watchMask & TCL_WRITABLE) {
  772. if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) {
  773.     mask = TCL_WRITABLE;
  774. }
  775.     }
  776.     if (infoPtr->watchMask & TCL_READABLE) {
  777. if (WaitForRead(infoPtr, 0) >= 0) {
  778.     if (infoPtr->readFlags & CONSOLE_EOF) {
  779. mask = TCL_READABLE;
  780.     } else {
  781. mask |= TCL_READABLE;
  782.     }
  783.     }
  784.     /*
  785.      * Inform the channel of the events.
  786.      */
  787.     Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask);
  788.     return 1;
  789. }
  790. /*
  791.  *----------------------------------------------------------------------
  792.  *
  793.  * ConsoleWatchProc --
  794.  *
  795.  * Called by the notifier to set up to watch for events on this
  796.  * channel.
  797.  *
  798.  * Results:
  799.  * None.
  800.  *
  801.  * Side effects:
  802.  * None.
  803.  *
  804.  *----------------------------------------------------------------------
  805.  */
  806. static void
  807. ConsoleWatchProc(
  808.     ClientData instanceData, /* Console state. */
  809.     int mask) /* What events to watch for, OR-ed
  810.                                          * combination of TCL_READABLE,
  811.                                          * TCL_WRITABLE and TCL_EXCEPTION. */
  812. {
  813.     ConsoleInfo **nextPtrPtr, *ptr;
  814.     ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
  815.     int oldMask = infoPtr->watchMask;
  816.     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  817.     /*
  818.      * Since most of the work is handled by the background threads,
  819.      * we just need to update the watchMask and then force the notifier
  820.      * to poll once. 
  821.      */
  822.     infoPtr->watchMask = mask & infoPtr->validMask;
  823.     if (infoPtr->watchMask) {
  824. Tcl_Time blockTime = { 0, 0 };
  825. if (!oldMask) {
  826.     infoPtr->nextPtr = tsdPtr->firstConsolePtr;
  827.     tsdPtr->firstConsolePtr = infoPtr;
  828. }
  829. Tcl_SetMaxBlockTime(&blockTime);
  830.     } else {
  831. if (oldMask) {
  832.     /*
  833.      * Remove the console from the list of watched consoles.
  834.      */
  835.     for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr;
  836.  ptr != NULL;
  837.  nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) {
  838. if (infoPtr == ptr) {
  839.     *nextPtrPtr = ptr->nextPtr;
  840.     break;
  841. }
  842.     }
  843. }
  844.     }
  845. }
  846. /*
  847.  *----------------------------------------------------------------------
  848.  *
  849.  * ConsoleGetHandleProc --
  850.  *
  851.  * Called from Tcl_GetChannelHandle to retrieve OS handles from
  852.  * inside a command consoleline based channel.
  853.  *
  854.  * Results:
  855.  * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if
  856.  * there is no handle for the specified direction. 
  857.  *
  858.  * Side effects:
  859.  * None.
  860.  *
  861.  *----------------------------------------------------------------------
  862.  */
  863. static int
  864. ConsoleGetHandleProc(
  865.     ClientData instanceData, /* The console state. */
  866.     int direction, /* TCL_READABLE or TCL_WRITABLE */
  867.     ClientData *handlePtr) /* Where to store the handle.  */
  868. {
  869.     ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
  870.     *handlePtr = (ClientData) infoPtr->handle;
  871.     return TCL_OK;
  872. }
  873. /*
  874.  *----------------------------------------------------------------------
  875.  *
  876.  * WaitForRead --
  877.  *
  878.  * Wait until some data is available, the console is at
  879.  * EOF or the reader thread is blocked waiting for data (if the
  880.  * channel is in non-blocking mode).
  881.  *
  882.  * Results:
  883.  * Returns 1 if console is readable.  Returns 0 if there is no data
  884.  * on the console, but there is buffered data.  Returns -1 if an
  885.  * error occurred.  If an error occurred, the threads may not
  886.  * be synchronized.
  887.  *
  888.  * Side effects:
  889.  * Updates the shared state flags.  If no error occurred,
  890.  *      the reader thread is blocked waiting for a signal from the 
  891.  *      main thread.
  892.  *
  893.  *----------------------------------------------------------------------
  894.  */
  895. static int
  896. WaitForRead(
  897.     ConsoleInfo *infoPtr, /* Console state. */
  898.     int blocking) /* Indicates whether call should be
  899.  * blocking or not. */
  900. {
  901.     DWORD timeout, count;
  902.     HANDLE *handle = infoPtr->handle;
  903.     INPUT_RECORD input;
  904.     
  905.     while (1) {
  906. /*
  907.  * Synchronize with the reader thread.
  908.  */
  909.        
  910. timeout = blocking ? INFINITE : 0;
  911. if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) {
  912.     /*
  913.      * The reader thread is blocked waiting for data and the channel
  914.      * is in non-blocking mode.
  915.      */
  916.     errno = EAGAIN;
  917.     return -1;
  918. }
  919. /*
  920.  * At this point, the two threads are synchronized, so it is safe
  921.  * to access shared state.
  922.  */
  923. /*
  924.  * If the console has hit EOF, it is always readable.
  925.  */
  926. if (infoPtr->readFlags & CONSOLE_EOF) {
  927.     return 1;
  928. }
  929. if (PeekConsoleInput(handle, &input, 1, &count) == FALSE) {
  930.             /*
  931.      * Check to see if the peek failed because of EOF.
  932.      */
  933.     
  934.     TclWinConvertError(GetLastError());
  935.     
  936.     if (errno == EOF) {
  937. infoPtr->readFlags |= CONSOLE_EOF;
  938. return 1;
  939.     }
  940.     /*
  941.      * Ignore errors if there is data in the buffer.
  942.      */
  943.     
  944.     if (infoPtr->readFlags & CONSOLE_BUFFERED) {
  945. return 0;
  946.     } else {
  947. return -1;
  948.     }
  949. }
  950. /*
  951.  * If there is data in the buffer, the console must be
  952.  * readable (since it is a line-oriented device).
  953.  */
  954. if (infoPtr->readFlags & CONSOLE_BUFFERED) {
  955.     return 1;
  956. }
  957. /*
  958.  * There wasn't any data available, so reset the thread and
  959.  * try again.
  960.  */
  961.     
  962. ResetEvent(infoPtr->readable);
  963. SetEvent(infoPtr->startReader);
  964.     }
  965. }
  966. /*
  967.  *----------------------------------------------------------------------
  968.  *
  969.  * ConsoleReaderThread --
  970.  *
  971.  * This function runs in a separate thread and waits for input
  972.  * to become available on a console.
  973.  *
  974.  * Results:
  975.  * None.
  976.  *
  977.  * Side effects:
  978.  * Signals the main thread when input become available.  May
  979.  * cause the main thread to wake up by posting a message.  May
  980.  * one line from the console for each wait operation.
  981.  *
  982.  *----------------------------------------------------------------------
  983.  */
  984. static DWORD WINAPI
  985. ConsoleReaderThread(LPVOID arg)
  986. {
  987.     ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
  988.     HANDLE *handle = infoPtr->handle;
  989.     DWORD count, waitResult;
  990.     HANDLE wEvents[2];
  991.     /* The first event takes precedence. */
  992.     wEvents[0] = infoPtr->stopReader;
  993.     wEvents[1] = infoPtr->startReader;
  994.     for (;;) {
  995. /*
  996.  * Wait for the main thread to signal before attempting to wait.
  997.  */
  998. waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
  999. if (waitResult != (WAIT_OBJECT_0 + 1)) {
  1000.     /*
  1001.      * The start event was not signaled.  It must be the stop event
  1002.      * or an error, so exit this thread.
  1003.      */
  1004.     break;
  1005. }
  1006. count = 0;
  1007. /* 
  1008.  * Look for data on the console, but first ignore any events
  1009.  * that are not KEY_EVENTs 
  1010.  */
  1011. if (ReadConsoleA(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE,
  1012. (LPDWORD) &infoPtr->bytesRead, NULL) != FALSE) {
  1013.     /*
  1014.      * Data was stored in the buffer.
  1015.      */
  1016.     
  1017.     infoPtr->readFlags |= CONSOLE_BUFFERED;
  1018. } else {
  1019.     DWORD err;
  1020.     err = GetLastError();
  1021.     
  1022.     if (err == EOF) {
  1023. infoPtr->readFlags = CONSOLE_EOF;
  1024.     }
  1025. }
  1026. /*
  1027.  * Signal the main thread by signalling the readable event and
  1028.  * then waking up the notifier thread.
  1029.  */
  1030. SetEvent(infoPtr->readable);
  1031. /*
  1032.  * Alert the foreground thread.  Note that we need to treat this like
  1033.  * a critical section so the foreground thread does not terminate
  1034.  * this thread while we are holding a mutex in the notifier code.
  1035.  */
  1036. Tcl_MutexLock(&consoleMutex);
  1037. if (infoPtr->threadId != NULL) {
  1038.     /* TIP #218. When in flight ignore the event, no one will receive it anyway */
  1039.     Tcl_ThreadAlert(infoPtr->threadId);
  1040. }
  1041. Tcl_MutexUnlock(&consoleMutex);
  1042.     }
  1043.     return 0;
  1044. }
  1045. /*
  1046.  *----------------------------------------------------------------------
  1047.  *
  1048.  * ConsoleWriterThread --
  1049.  *
  1050.  * This function runs in a separate thread and writes data
  1051.  * onto a console.
  1052.  *
  1053.  * Results:
  1054.  * Always returns 0.
  1055.  *
  1056.  * Side effects:
  1057.  * Signals the main thread when an output operation is completed.
  1058.  * May cause the main thread to wake up by posting a message.  
  1059.  *
  1060.  *----------------------------------------------------------------------
  1061.  */
  1062. static DWORD WINAPI
  1063. ConsoleWriterThread(LPVOID arg)
  1064. {
  1065.     ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
  1066.     HANDLE *handle = infoPtr->handle;
  1067.     DWORD count, toWrite, waitResult;
  1068.     char *buf;
  1069.     HANDLE wEvents[2];
  1070.     /* The first event takes precedence. */
  1071.     wEvents[0] = infoPtr->stopWriter;
  1072.     wEvents[1] = infoPtr->startWriter;
  1073.     for (;;) {
  1074. /*
  1075.  * Wait for the main thread to signal before attempting to write.
  1076.  */
  1077. waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
  1078. if (waitResult != (WAIT_OBJECT_0 + 1)) {
  1079.     /*
  1080.      * The start event was not signaled.  It must be the stop event
  1081.      * or an error, so exit this thread.
  1082.      */
  1083.     break;
  1084. }
  1085. buf = infoPtr->writeBuf;
  1086. toWrite = infoPtr->toWrite;
  1087. /*
  1088.  * Loop until all of the bytes are written or an error occurs.
  1089.  */
  1090. while (toWrite > 0) {
  1091.     if (WriteConsoleA(handle, buf, toWrite, &count, NULL) == FALSE) {
  1092. infoPtr->writeError = GetLastError();
  1093. break;
  1094.     } else {
  1095. toWrite -= count;
  1096. buf += count;
  1097.     }
  1098. }
  1099. /*
  1100.  * Signal the main thread by signalling the writable event and
  1101.  * then waking up the notifier thread.
  1102.  */
  1103. SetEvent(infoPtr->writable);
  1104. /*
  1105.  * Alert the foreground thread.  Note that we need to treat this like
  1106.  * a critical section so the foreground thread does not terminate
  1107.  * this thread while we are holding a mutex in the notifier code.
  1108.  */
  1109. Tcl_MutexLock(&consoleMutex);
  1110. if (infoPtr->threadId != NULL) {
  1111.     /* TIP #218. When in flight ignore the event, no one will receive it anyway */
  1112.     Tcl_ThreadAlert(infoPtr->threadId);
  1113. }
  1114. Tcl_MutexUnlock(&consoleMutex);
  1115.     }
  1116.     return 0;
  1117. }
  1118. /*
  1119.  *----------------------------------------------------------------------
  1120.  *
  1121.  * TclWinOpenConsoleChannel --
  1122.  *
  1123.  * Constructs a Console channel for the specified standard OS handle.
  1124.  *      This is a helper function to break up the construction of 
  1125.  *      channels into File, Console, or Serial.
  1126.  *
  1127.  * Results:
  1128.  * Returns the new channel, or NULL.
  1129.  *
  1130.  * Side effects:
  1131.  * May open the channel
  1132.  *
  1133.  *----------------------------------------------------------------------
  1134.  */
  1135. Tcl_Channel
  1136. TclWinOpenConsoleChannel(handle, channelName, permissions)
  1137.     HANDLE handle;
  1138.     char *channelName;
  1139.     int permissions;
  1140. {
  1141.     char encoding[4 + TCL_INTEGER_SPACE];
  1142.     ConsoleInfo *infoPtr;
  1143.     DWORD id, modes;
  1144.     ConsoleInit();
  1145.     /*
  1146.      * See if a channel with this handle already exists.
  1147.      */
  1148.     
  1149.     infoPtr = (ConsoleInfo *) ckalloc((unsigned) sizeof(ConsoleInfo));
  1150.     memset(infoPtr, 0, sizeof(ConsoleInfo));
  1151.     infoPtr->validMask = permissions;
  1152.     infoPtr->handle = handle;
  1153.     infoPtr->channel = (Tcl_Channel) NULL;
  1154.     wsprintfA(encoding, "cp%d", GetConsoleCP());
  1155.     infoPtr->threadId = Tcl_GetCurrentThread();
  1156.     /*
  1157.      * Use the pointer for the name of the result channel.
  1158.      * This keeps the channel names unique, since some may share
  1159.      * handles (stdin/stdout/stderr for instance).
  1160.      */
  1161.     wsprintfA(channelName, "file%lx", (int) infoPtr);
  1162.     
  1163.     infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName,
  1164.             (ClientData) infoPtr, permissions);
  1165.     if (permissions & TCL_READABLE) {
  1166. /*
  1167.  * Make sure the console input buffer is ready for only character
  1168.  * input notifications and the buffer is set for line buffering.
  1169.  * IOW, we only want to catch when complete lines are ready for
  1170.  * reading.
  1171.  */
  1172. GetConsoleMode(infoPtr->handle, &modes);
  1173. modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
  1174. modes |= ENABLE_LINE_INPUT;
  1175. SetConsoleMode(infoPtr->handle, modes);
  1176. infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
  1177. infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);
  1178. infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL);
  1179. infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread,
  1180.         infoPtr, 0, &id);
  1181. SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
  1182.     }
  1183.     if (permissions & TCL_WRITABLE) {
  1184. infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
  1185. infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
  1186. infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
  1187. infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread,
  1188.         infoPtr, 0, &id);
  1189. SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST);
  1190.     }
  1191.     /*
  1192.      * Files have default translation of AUTO and ^Z eof char, which
  1193.      * means that a ^Z will be accepted as EOF when reading.
  1194.      */
  1195.     
  1196.     Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
  1197.     Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "32 {}");
  1198.     Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding);
  1199.     return infoPtr->channel;
  1200. }
  1201. /*
  1202.  *----------------------------------------------------------------------
  1203.  *
  1204.  * ConsoleThreadActionProc --
  1205.  *
  1206.  * Insert or remove any thread local refs to this channel.
  1207.  *
  1208.  * Results:
  1209.  * None.
  1210.  *
  1211.  * Side effects:
  1212.  * Changes thread local list of valid channels.
  1213.  *
  1214.  *----------------------------------------------------------------------
  1215.  */
  1216. static void
  1217. ConsoleThreadActionProc (instanceData, action)
  1218.      ClientData instanceData;
  1219.      int action;
  1220. {
  1221.     ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
  1222.     /* We do not access firstConsolePtr in the thread structures. This is
  1223.      * not for all serials managed by the thread, but only those we are
  1224.      * watching. Removal of the filevent handlers before transfer thus
  1225.      * takes care of this structure.
  1226.      */
  1227.     Tcl_MutexLock(&consoleMutex);
  1228.     if (action == TCL_CHANNEL_THREAD_INSERT) {
  1229.         /* We can't copy the thread information from the channel when
  1230.  * the channel is created. At this time the channel back
  1231.  * pointer has not been set yet. However in that case the
  1232.  * threadId has already been set by TclpCreateCommandChannel
  1233.  * itself, so the structure is still good.
  1234.  */
  1235.         ConsoleInit ();
  1236.         if (infoPtr->channel != NULL) {
  1237.     infoPtr->threadId = Tcl_GetChannelThread (infoPtr->channel);
  1238. }
  1239.     } else {
  1240. infoPtr->threadId = NULL;
  1241.     }
  1242.     Tcl_MutexUnlock(&consoleMutex);
  1243. }