/[dtapublic]/projs/ets/trunk/src/c_tcl_base_7_5_w_mods/tclwinconsole.c
ViewVC logotype

Diff of /projs/ets/trunk/src/c_tcl_base_7_5_w_mods/tclwinconsole.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.25  
changed lines
  Added in v.220

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25