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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

Properties

Name Value
svn:keywords Header

dashley@gmail.com
ViewVC Help
Powered by ViewVC 1.1.25