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

Annotation of /projs/trunk/shared_source/c_tcl_base_7_5_w_mods/tclwinconsole.c

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:keywords Header

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25