Diff
Not logged in

Differences From Artifact [ca0199709b]:

To Artifact [9cb68d6f55]:


1
2
3
4
5
6
7
8
9
10
11
12

13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19











-
+







/* 
 * tclWinConsole.c --
 *
 *	This file implements the Windows-specific console functions,
 *	and the "console" channel driver.
 *
 * Copyright (c) 1999 by Scriptics Corp.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclWinConsole.c,v 1.9 2002/11/07 02:13:37 mdejong Exp $
 * RCS: @(#) $Id: tclWinConsole.c,v 1.10 2002/11/26 22:35:20 davygrvy Exp $
 */

#include "tclWinInt.h"

#include <fcntl.h>
#include <io.h>
#include <sys/stat.h>
75
76
77
78
79
80
81



82
83
84



85
86
87
88
89
90
91
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97







+
+
+



+
+
+







				 * the current buffer to be written. */
    HANDLE readable;		/* Manual-reset event to signal when the
				 * reader thread has finished waiting for
				 * input. */
    HANDLE startWriter;		/* Auto-reset event used by the main thread to
				 * signal when the writer thread should attempt
				 * to write to the console. */
    HANDLE stopWriter;		/* Auto-reset event used by the main thread to
				 * signal when the writer thread should exit.
				 */
    HANDLE startReader;		/* Auto-reset event used by the main thread to
				 * signal when the reader thread should attempt
				 * to read from the console. */
    HANDLE stopReader;		/* Auto-reset event used by the main thread to
				 * signal when the reader thread should exit.
				 */
    DWORD writeError;		/* An error caused by the last background
				 * write.  Set to 0 if no error has been
				 * detected.  This word is shared with the
				 * writer thread so access must be
				 * synchronized with the writable object.
				 */
    char *writeBuf;		/* Current background output buffer.
454
455
456
457
458
459
460

461
462
463
464
465
466
467
468
469
470

471



472
473
474
475
476
477
478
479


























480
481
482
483






484
485
486
487
488

489
490
491
492
493
494

495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514



515
516
517
518
519
520
521
522

























523
524
525
526




527
528
529
530
531

532
533
534
535
536
537

538
539
540
541
542
543
544
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482








483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509



510
511
512
513
514
515





516


517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544








545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570



571
572
573
574





575


576
577
578
579
580
581
582
583
584
585
586
587







+










+

+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
+
+
+
+
+
+
-
-
-
-
-
+
-
-




+




















+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
+
+
+
+
-
-
-
-
-
+
-
-




+







    ClientData instanceData,	/* Pointer to ConsoleInfo structure. */
    Tcl_Interp *interp)		/* For error reporting. */
{
    ConsoleInfo *consolePtr = (ConsoleInfo *) instanceData;
    int errorCode;
    ConsoleInfo *infoPtr, **nextPtrPtr;
    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
    DWORD exitCode;

    errorCode = 0;
    
    /*
     * Clean up the background thread if necessary.  Note that this
     * must be done before we can close the file, since the 
     * thread may be blocking trying to read from the console.
     */
    
    if (consolePtr->readThread) {

	/*
	 * The thread may already have closed on it's own.  Check it's
	 * exit code.
	 */
	 * Forcibly terminate the background thread.  We cannot rely on the
	 * thread to cleanly terminate itself because we have no way of
	 * closing the handle without blocking in the case where the
	 * thread is in the middle of an I/O operation.  Note that we need
	 * to guard against terminating the thread while it is in the
	 * middle of Tcl_ThreadAlert because it won't be able to release
	 * the notifier lock.
	 */

	GetExitCodeThread(consolePtr->readThread, &exitCode);

	if (exitCode == STILL_ACTIVE) {

	    /*
	     * Set the stop event so that if the reader thread is blocked
	     * in ConsoleReaderThread on WaitForMultipleEvents, it will exit
	     * cleanly.
	     */

	    SetEvent(consolePtr->stopReader);

	    /*
	     * Wait at most 20 milliseconds for the reader thread to close.
	     */

	    if (WaitForSingleObject(consolePtr->readThread, 20)
		    == WAIT_TIMEOUT) {
		/*
		 * Forcibly terminate the background thread as a last
		 * resort.  Note that we need to guard against
		 * terminating the thread while it is in the middle of
		 * Tcl_ThreadAlert because it won't be able to release
		 * the notifier lock.
		 */

	Tcl_MutexLock(&consoleMutex);
	TerminateThread(consolePtr->readThread, 0);

		Tcl_MutexLock(&consoleMutex);

		/* BUG: this leaks memory. */
		TerminateThread(consolePtr->readThread, 0);
		Tcl_MutexUnlock(&consoleMutex);
	    }
	/*
	 * Wait for the thread to terminate.  This ensures that we are
	 * completely cleaned up before we leave this function. 
	 */

	}
	WaitForSingleObject(consolePtr->readThread, INFINITE);
	Tcl_MutexUnlock(&consoleMutex);

	CloseHandle(consolePtr->readThread);
	CloseHandle(consolePtr->readable);
	CloseHandle(consolePtr->startReader);
	CloseHandle(consolePtr->stopReader);
	consolePtr->readThread = NULL;
    }
    consolePtr->validMask &= ~TCL_READABLE;

    /*
     * Wait for the writer thread to finish the current buffer, then
     * terminate the thread and close the handles.  If the channel is
     * nonblocking, there should be no pending write operations.
     */
    
    if (consolePtr->writeThread) {
	if (consolePtr->toWrite) {
	    /*
	     * We only need to wait if there is something to write.
	     * This may prevent infinite wait on exit. [python bug 216289]
	     */
	    WaitForSingleObject(consolePtr->writable, INFINITE);
	}

	/*
	 * The thread may already have closed on it's own.  Check it's
	 * exit code.
	 */
	 * Forcibly terminate the background thread.  We cannot rely on the
	 * thread to cleanly terminate itself because we have no way of
	 * closing the handle without blocking in the case where the
	 * thread is in the middle of an I/O operation.  Note that we need
	 * to guard against terminating the thread while it is in the
	 * middle of Tcl_ThreadAlert because it won't be able to release
	 * the notifier lock.
	 */

	GetExitCodeThread(consolePtr->writeThread, &exitCode);

	if (exitCode == STILL_ACTIVE) {
	    /*
	     * Set the stop event so that if the reader thread is blocked
	     * in ConsoleWriterThread on WaitForMultipleEvents, it will
	     * exit cleanly.
	     */

	    SetEvent(consolePtr->stopWriter);

	    /*
	     * Wait at most 20 milliseconds for the writer thread to close.
	     */

	    if (WaitForSingleObject(consolePtr->writeThread, 20)
		    == WAIT_TIMEOUT) {
		/*
		 * Forcibly terminate the background thread as a last
		 * resort.  Note that we need to guard against
		 * terminating the thread while it is in the middle of
		 * Tcl_ThreadAlert because it won't be able to release
		 * the notifier lock.
		 */

	Tcl_MutexLock(&consoleMutex);
	TerminateThread(consolePtr->writeThread, 0);

		Tcl_MutexLock(&consoleMutex);
		TerminateThread(consolePtr->writeThread, 0);
		Tcl_MutexUnlock(&consoleMutex);
	    }
	/*
	 * Wait for the thread to terminate.  This ensures that we are
	 * completely cleaned up before we leave this function. 
	 */

	}
	WaitForSingleObject(consolePtr->writeThread, INFINITE);
	Tcl_MutexUnlock(&consoleMutex);

	CloseHandle(consolePtr->writeThread);
	CloseHandle(consolePtr->writable);
	CloseHandle(consolePtr->startWriter);
	CloseHandle(consolePtr->stopWriter);
	consolePtr->writeThread = NULL;
    }
    consolePtr->validMask &= ~TCL_WRITABLE;


    /*
     * Don't close the Win32 handle if the handle is a standard channel
1062
1063
1064
1065
1066
1067
1068
1069






1070
1071
1072
1073
1074
1075

1076









1077
1078
1079
1080
1081
1082
1083
1084

1085
1086
1087
1088
1089
1090
1091
1105
1106
1107
1108
1109
1110
1111

1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124

1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140

1141
1142
1143
1144
1145
1146
1147
1148







-
+
+
+
+
+
+






+
-
+
+
+
+
+
+
+
+
+







-
+







 */

static DWORD WINAPI
ConsoleReaderThread(LPVOID arg)
{
    ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
    HANDLE *handle = infoPtr->handle;
    DWORD count;
    DWORD count, waitResult;
    HANDLE wEvents[2];

    /* The first event takes precedence. */
    wEvents[0] = infoPtr->stopReader;
    wEvents[1] = infoPtr->startReader;

    for (;;) {
	/*
	 * Wait for the main thread to signal before attempting to wait.
	 */

	waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
	WaitForSingleObject(infoPtr->startReader, INFINITE);

	if (waitResult != (WAIT_OBJECT_0 + 1)) {
	    /*
	     * The start event was not signaled.  It must be the stop event
	     * or an error, so exit this thread.
	     */

	    break;
	}

	count = 0;

	/* 
	 * Look for data on the console, but first ignore any events
	 * that are not KEY_EVENTs 
	 */
	if (ReadConsole(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE,
	if (ReadConsoleA(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE,
		(LPDWORD) &infoPtr->bytesRead, NULL) != FALSE) {
	    /*
	     * Data was stored in the buffer.
	     */
	    
	    infoPtr->readFlags |= CONSOLE_BUFFERED;
	} else {
1110
1111
1112
1113
1114
1115
1116

1117

1118
1119
1120
1121
1122
1123
1124
1167
1168
1169
1170
1171
1172
1173
1174

1175
1176
1177
1178
1179
1180
1181
1182







+
-
+







	 * this thread while we are holding a mutex in the notifier code.
	 */

	Tcl_MutexLock(&consoleMutex);
	Tcl_ThreadAlert(infoPtr->threadId);
	Tcl_MutexUnlock(&consoleMutex);
    }

    return 0;			/* NOT REACHED */
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ConsoleWriterThread --
 *
1137
1138
1139
1140
1141
1142
1143
1144

1145





1146
1147
1148
1149
1150
1151

1152









1153
1154
1155
1156
1157
1158
1159
1160
1161
1162

1163
1164
1165
1166
1167
1168
1169
1195
1196
1197
1198
1199
1200
1201

1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215

1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233

1234
1235
1236
1237
1238
1239
1240
1241







-
+

+
+
+
+
+






+
-
+
+
+
+
+
+
+
+
+









-
+








static DWORD WINAPI
ConsoleWriterThread(LPVOID arg)
{

    ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
    HANDLE *handle = infoPtr->handle;
    DWORD count, toWrite;
    DWORD count, toWrite, waitResult;
    char *buf;
    HANDLE wEvents[2];

    /* The first event takes precedence. */
    wEvents[0] = infoPtr->stopWriter;
    wEvents[1] = infoPtr->startWriter;

    for (;;) {
	/*
	 * Wait for the main thread to signal before attempting to write.
	 */

	waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
	WaitForSingleObject(infoPtr->startWriter, INFINITE);

	if (waitResult != (WAIT_OBJECT_0 + 1)) {
	    /*
	     * The start event was not signaled.  It must be the stop event
	     * or an error, so exit this thread.
	     */

	    break;
	}

	buf = infoPtr->writeBuf;
	toWrite = infoPtr->toWrite;

	/*
	 * Loop until all of the bytes are written or an error occurs.
	 */

	while (toWrite > 0) {
	    if (WriteFile(handle, buf, toWrite, &count, NULL) == FALSE) {
	    if (WriteConsoleA(handle, buf, toWrite, &count, NULL) == FALSE) {
		infoPtr->writeError = GetLastError();
		break;
	    } else {
		toWrite -= count;
		buf += count;
	    }
	}
1181
1182
1183
1184
1185
1186
1187

1188

1189
1190
1191
1192
1193
1194
1195
1253
1254
1255
1256
1257
1258
1259
1260

1261
1262
1263
1264
1265
1266
1267
1268







+
-
+







	 * this thread while we are holding a mutex in the notifier code.
	 */

	Tcl_MutexLock(&consoleMutex);
	Tcl_ThreadAlert(infoPtr->threadId);
	Tcl_MutexUnlock(&consoleMutex);
    }

    return 0;			/* NOT REACHED */
    return 0;
}



/*
 *----------------------------------------------------------------------
 *
1213
1214
1215
1216
1217
1218
1219
1220

1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235

1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249











1250
1251

1252

1253
1254

1255
1256
1257
1258
1259

1260

1261

1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1286
1287
1288
1289
1290
1291
1292

1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307

1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336

1337
1338

1339
1340
1341
1342
1343
1344
1345

1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361







-
+














-
+














+
+
+
+
+
+
+
+
+
+
+


+
-
+

-
+





+
-
+

+













    HANDLE handle;
    char *channelName;
    int permissions;
{
    char encoding[4 + TCL_INTEGER_SPACE];
    ConsoleInfo *infoPtr;
    ThreadSpecificData *tsdPtr;
    DWORD id;
    DWORD id, modes;

    tsdPtr = ConsoleInit();

    /*
     * See if a channel with this handle already exists.
     */
    
    infoPtr = (ConsoleInfo *) ckalloc((unsigned) sizeof(ConsoleInfo));
    memset(infoPtr, 0, sizeof(ConsoleInfo));

    infoPtr->validMask = permissions;
    infoPtr->handle = handle;

    wsprintfA(encoding, "cp%d", GetConsoleCP());
    

    /*
     * Use the pointer for the name of the result channel.
     * This keeps the channel names unique, since some may share
     * handles (stdin/stdout/stderr for instance).
     */

    wsprintfA(channelName, "file%lx", (int) infoPtr);
    
    infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName,
            (ClientData) infoPtr, permissions);

    infoPtr->threadId = Tcl_GetCurrentThread();

    if (permissions & TCL_READABLE) {
	/*
	 * Make sure the console input buffer is ready for only character
	 * input notifications and the buffer is set for line buffering.
	 * IOW, we only want to catch when complete lines are ready for
	 * reading.
	 */
	GetConsoleMode(infoPtr->handle, &modes);
	modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
	modes |= ENABLE_LINE_INPUT;
	SetConsoleMode(infoPtr->handle, modes);

	infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
	infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->readThread = CreateThread(NULL, 8000, ConsoleReaderThread,
	infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread,
	        infoPtr, 0, &id);
	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); 
	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
    }

    if (permissions & TCL_WRITABLE) {
	infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
	infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->writeThread = CreateThread(NULL, 8000, ConsoleWriterThread,
	infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread,
	        infoPtr, 0, &id);
	SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST);
    }

    /*
     * Files have default translation of AUTO and ^Z eof char, which
     * means that a ^Z will be accepted as EOF when reading.
     */
    
    Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
    Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}");
    Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding);

    return infoPtr->channel;
}