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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25