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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 42 - (show annotations) (download)
Fri Oct 14 01:50:00 2016 UTC (8 years, 2 months ago) by dashley
File MIME type: text/plain
File size: 35740 byte(s)
Move shared source code to commonize.
1 /* $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