Logo Search packages:      
Sourcecode: tcl8.5 version File versions  Download package

tclWinConsole.c

/*
 * 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.19 2006/03/27 18:08:51 andreas_kupries Exp $
 */

#include "tclWinInt.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 stopWriter;        /* Auto-reset event used by the main thread to
                         * signal when the writer thread should exit */
    HANDLE startReader;       /* Auto-reset event used by the main thread to
                         * signal when the reader thread should
                         * attempt to read from the console. */
    HANDLE stopReader;        /* Auto-reset event used by the main thread to
                         * signal when the reader thread should exit */
    DWORD writeError;         /* An error caused by the last background
                         * write. Set to 0 if no error has been
                         * detected. This word is shared with the
                         * writer thread so access must be
                         * synchronized with the writable object. */
    char *writeBuf;           /* Current background output buffer. Access is
                         * synchronized with the writable object. */
    int writeBufLen;          /* Size of write buffer. Access is
                         * synchronized with the writable object. */
    int toWrite;        /* Current amount to be written. Access is
                         * synchronized with the writable object. */
    int readFlags;            /* Flags that are shared with the reader
                         * thread. Access is synchronized with the
                         * readable object. */
    int bytesRead;            /* number of bytes in the buffer */
    int offset;               /* number of bytes read out of the buffer */
    char buffer[CONSOLE_BUFFER_SIZE];
                        /* Data consumed by reader thread. */
} ConsoleInfo;

typedef struct ThreadSpecificData {
    /*
     * The following pointer refers to the head of the list of consoles that
     * are being watched for file events.
     */

    ConsoleInfo *firstConsolePtr;
} ThreadSpecificData;

static Tcl_ThreadDataKey dataKey;

/*
 * The following structure is what is added to the Tcl event queue when
 * console events are generated.
 */

typedef struct 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        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 void       ConsoleInit(void);
static int        ConsoleInputProc(ClientData instanceData, char *buf,
                      int toRead, int *errorCode);
static int        ConsoleOutputProc(ClientData instanceData,
                      CONST char *buf, int toWrite, int *errorCode);
static DWORD WINAPI     ConsoleReaderThread(LPVOID arg);
static void       ConsoleSetupProc(ClientData clientData, int flags);
static void       ConsoleWatchProc(ClientData instanceData, int mask);
static DWORD WINAPI     ConsoleWriterThread(LPVOID arg);
static void       ProcExitHandler(ClientData clientData);
static int        WaitForRead(ConsoleInfo *infoPtr, int blocking);
static void       ConsoleThreadActionProc(ClientData instanceData,
                      int action);

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

static Tcl_ChannelType consoleChannelType = {
    "console",                /* Type name. */
    TCL_CHANNEL_VERSION_5,    /* v5 channel */
    ConsoleCloseProc,         /* Close proc. */
    ConsoleInputProc,         /* Input proc. */
    ConsoleOutputProc,        /* Output proc. */
    NULL,               /* Seek proc. */
    NULL,               /* Set option proc. */
    NULL,               /* Get option proc. */
    ConsoleWatchProc,         /* Set up notifier to watch the channel. */
    ConsoleGetHandleProc,     /* Get an OS handle from channel. */
    NULL,               /* close2proc. */
    ConsoleBlockModeProc,     /* Set blocking or non-blocking mode.*/
    NULL,               /* flush proc. */
    NULL,               /* handler proc. */
    NULL,               /* wide seek proc */
    ConsoleThreadActionProc,    /* thread action proc */
    NULL,                       /* truncation */
};

/*
 *----------------------------------------------------------------------
 *
 * readConsoleBytes, writeConsoleBytes --
 * Wrapper for ReadConsole{A,W}, that takes and returns number of bytes
 * instead of number of TCHARS
 */
static BOOL
readConsoleBytes(
    HANDLE hConsole,
    LPVOID lpBuffer,
    DWORD nbytes,
    LPDWORD nbytesread)
{
    DWORD ntchars;
    BOOL result;
    int tcharsize;
    tcharsize = tclWinProcs->useWide? 2 : 1;
    result = tclWinProcs->readConsoleProc(
          hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL);
    if (nbytesread)
      *nbytesread = (ntchars*tcharsize);
    return result;
}

static BOOL
writeConsoleBytes(
    HANDLE hConsole,
    const VOID *lpBuffer,
    DWORD nbytes,
    LPDWORD nbyteswritten)
{
    DWORD ntchars;
    BOOL result;
    int tcharsize;
    tcharsize = tclWinProcs->useWide? 2 : 1;
    result = tclWinProcs->writeConsoleProc(
          hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL);
    if (nbyteswritten)
      *nbyteswritten = (ntchars*tcharsize);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleInit --
 *
 *    This function initializes the static variables for this file.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Creates a new event source.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleInit(void)
{
    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);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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);
    DWORD exitCode;

    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) {
      /*
       * The thread may already have closed on it's own. Check it's exit
       * code.
       */

      GetExitCodeThread(consolePtr->readThread, &exitCode);

      if (exitCode == STILL_ACTIVE) {
          /*
           * Set the stop event so that if the reader thread is blocked in
           * ConsoleReaderThread on WaitForMultipleEvents, it will exit
           * cleanly.
           */

          SetEvent(consolePtr->stopReader);

          /*
           * Wait at most 20 milliseconds for the reader thread to close.
           */

          if (WaitForSingleObject(consolePtr->readThread, 20)
                == WAIT_TIMEOUT) {
            /*
             * Forcibly terminate the background thread as a last resort.
             * Note that we need to guard against terminating the thread
             * while it is in the middle of Tcl_ThreadAlert because it
             * won't be able to release the notifier lock.
             */

            Tcl_MutexLock(&consoleMutex);

            /* BUG: this leaks memory. */
            TerminateThread(consolePtr->readThread, 0);
            Tcl_MutexUnlock(&consoleMutex);
          }
      }

      CloseHandle(consolePtr->readThread);
      CloseHandle(consolePtr->readable);
      CloseHandle(consolePtr->startReader);
      CloseHandle(consolePtr->stopReader);
      consolePtr->readThread = NULL;
    }
    consolePtr->validMask &= ~TCL_READABLE;

    /*
     * Wait for the writer thread to finish the current buffer, then terminate
     * the thread and close the handles. If the channel is nonblocking, there
     * should be no pending write operations.
     */

    if (consolePtr->writeThread) {
      if (consolePtr->toWrite) {
          /*
           * We only need to wait if there is something to write. This may
           * prevent infinite wait on exit. [python bug 216289]
           */

          WaitForSingleObject(consolePtr->writable, INFINITE);
      }

      /*
       * The thread may already have closed on it's own. Check it's exit
       * code.
       */

      GetExitCodeThread(consolePtr->writeThread, &exitCode);

      if (exitCode == STILL_ACTIVE) {
          /*
           * Set the stop event so that if the reader thread is blocked in
           * ConsoleWriterThread on WaitForMultipleEvents, it will exit
           * cleanly.
           */

          SetEvent(consolePtr->stopWriter);

          /*
           * Wait at most 20 milliseconds for the writer thread to close.
           */

          if (WaitForSingleObject(consolePtr->writeThread, 20)
                == WAIT_TIMEOUT) {
            /*
             * Forcibly terminate the background thread as a last resort.
             * Note that we need to guard against terminating the thread
             * while it is in the middle of Tcl_ThreadAlert because it
             * won't be able to release the notifier lock.
             */

            Tcl_MutexLock(&consoleMutex);

            /* BUG: this leaks memory. */
            TerminateThread(consolePtr->writeThread, 0);
            Tcl_MutexUnlock(&consoleMutex);
          }
      }

      CloseHandle(consolePtr->writeThread);
      CloseHandle(consolePtr->writable);
      CloseHandle(consolePtr->startWriter);
      CloseHandle(consolePtr->stopWriter);
      consolePtr->writeThread = NULL;
    }
    consolePtr->validMask &= ~TCL_WRITABLE;


    /*
     * Don't close the Win32 handle if the handle is a standard channel during
     * the thread exit process. Otherwise, one thread may kill the stdio of
     * another.
     */

    if (!TclInThreadExit()
          || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle)
            && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle)
            && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) {
      if (CloseHandle(consolePtr->handle) == FALSE) {
          TclWinConvertError(GetLastError());
          errorCode = errno;
      }
    }

    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], (size_t) bufSize);
          bytesRead = bufSize;
          infoPtr->offset += bufSize;
      } else {
          memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) 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 (readConsoleBytes(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count)
          == 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. */
    CONST 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((size_t)toWrite);
      }
      memcpy(infoPtr->writeBuf, buf, (size_t)toWrite);
      infoPtr->toWrite = toWrite;
      ResetEvent(infoPtr->writable);
      SetEvent(infoPtr->startWriter);
      bytesWritten = toWrite;
    } else {
      /*
       * In the blocking case, just try to write the buffer directly. This
       * avoids an unnecessary copy.
       */

      if (writeConsoleBytes(infoPtr->handle, buf, (DWORD)toWrite,
                        &bytesWritten)
            == 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, waitResult;
    HANDLE wEvents[2];

    /* The first event takes precedence. */
    wEvents[0] = infoPtr->stopReader;
    wEvents[1] = infoPtr->startReader;

    for (;;) {
      /*
       * Wait for the main thread to signal before attempting to wait.
       */

      waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);

      if (waitResult != (WAIT_OBJECT_0 + 1)) {
          /*
           * The start event was not signaled. It must be the stop event or
           * an error, so exit this thread.
           */

          break;
      }

      count = 0;

      /*
       * Look for data on the console, but first ignore any events that are
       * not KEY_EVENTs.
       */

      if (readConsoleBytes(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE,
            (LPDWORD) &infoPtr->bytesRead) != 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);
      if (infoPtr->threadId != NULL) {
          /*
           * TIP #218. When in flight ignore the event, no one will receive
           * it anyway.
           */
          Tcl_ThreadAlert(infoPtr->threadId);
      }
      Tcl_MutexUnlock(&consoleMutex);
    }

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * 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, waitResult;
    char *buf;
    HANDLE wEvents[2];

    /* The first event takes precedence. */
    wEvents[0] = infoPtr->stopWriter;
    wEvents[1] = infoPtr->startWriter;

    for (;;) {
      /*
       * Wait for the main thread to signal before attempting to write.
       */

      waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);

      if (waitResult != (WAIT_OBJECT_0 + 1)) {
          /*
           * The start event was not signaled. It must be the stop event or
           * an error, so exit this thread.
           */

          break;
      }

      buf = infoPtr->writeBuf;
      toWrite = infoPtr->toWrite;

      /*
       * Loop until all of the bytes are written or an error occurs.
       */

      while (toWrite > 0) {
          if (writeConsoleBytes(handle, buf, (DWORD)toWrite,
                          &count) == FALSE) {
            infoPtr->writeError = GetLastError();
            break;
          } else {
            toWrite -= count;
            buf += count;
          }
      }

      /*
       * 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);
      if (infoPtr->threadId != NULL) {
          /*
           * TIP #218. When in flight ignore the event, no one will receive
           * it anyway.
           */

          Tcl_ThreadAlert(infoPtr->threadId);
      }
      Tcl_MutexUnlock(&consoleMutex);
    }

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * TclWinOpenConsoleChannel --
 *
 *    Constructs a Console channel for the specified standard OS handle.
 *    This is a helper function to break up the construction of channels
 *    into File, Console, or Serial.
 *
 * Results:
 *    Returns the new channel, or NULL.
 *
 * Side effects:
 *    May open the channel
 *
 *----------------------------------------------------------------------
 */

Tcl_Channel
TclWinOpenConsoleChannel(
    HANDLE handle,
    char *channelName,
    int permissions)
{
    char encoding[4 + TCL_INTEGER_SPACE];
    ConsoleInfo *infoPtr;
    DWORD id, modes;

    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;
    infoPtr->channel = (Tcl_Channel) NULL;

    wsprintfA(encoding, "cp%d", GetConsoleCP());

    infoPtr->threadId = Tcl_GetCurrentThread();

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

    wsprintfA(channelName, "file%lx", (int) infoPtr);

    infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName,
          (ClientData) infoPtr, permissions);

    if (permissions & TCL_READABLE) {
      /*
       * Make sure the console input buffer is ready for only character
       * input notifications and the buffer is set for line buffering. IOW,
       * we only want to catch when complete lines are ready for reading.
       */

      GetConsoleMode(infoPtr->handle, &modes);
      modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
      modes |= ENABLE_LINE_INPUT;
      SetConsoleMode(infoPtr->handle, modes);

      infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
      infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);
      infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL);
      infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread,
            infoPtr, 0, &id);
      SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
    }

    if (permissions & TCL_WRITABLE) {
      infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
      infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
      infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
      infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread,
            infoPtr, 0, &id);
      SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST);
    }

    /*
     * 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 {}");
    if (tclWinProcs->useWide)
      Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", "unicode");
    else
      Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding);

    return infoPtr->channel;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleThreadActionProc --
 *
 *    Insert or remove any thread local refs to this channel.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Changes thread local list of valid channels.
 *
 *----------------------------------------------------------------------
 */

static void
ConsoleThreadActionProc(
    ClientData instanceData,
    int action)
{
    ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;

    /* We do not access firstConsolePtr in the thread structures. This is not
     * for all serials managed by the thread, but only those we are watching.
     * Removal of the filevent handlers before transfer thus takes care of
     * this structure.
     */

    Tcl_MutexLock(&consoleMutex);
    if (action == TCL_CHANNEL_THREAD_INSERT) {
      /*
       * We can't copy the thread information from the channel when the
       * channel is created. At this time the channel back pointer has not
       * been set yet. However in that case the threadId has already been
       * set by TclpCreateCommandChannel itself, so the structure is still
       * good.
       */

      ConsoleInit();
      if (infoPtr->channel != NULL) {
          infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel);
      }
    } else {
      infoPtr->threadId = NULL;
    }
    Tcl_MutexUnlock(&consoleMutex);
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */

Generated by  Doxygen 1.6.0   Back to index