Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/win/tclWinConsole.c @ 68

Last change on this file since 68 was 25, checked in by landauf, 16 years ago

added tcl to libs

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