1/*
2 * tclWinChan.c
3 *
4 *	Channel drivers for Windows channels based on files, command pipes and
5 *	TCP sockets.
6 *
7 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tclWinChan.c,v 1.49.4.3 2010/09/08 15:42:13 dgp Exp $
13 */
14
15#include "tclWinInt.h"
16#include "tclIO.h"
17
18/*
19 * State flags used in the info structures below.
20 */
21
22#define FILE_PENDING	(1<<0)	/* Message is pending in the queue. */
23#define FILE_ASYNC	(1<<1)	/* Channel is non-blocking. */
24#define FILE_APPEND	(1<<2)	/* File is in append mode. */
25
26#define FILE_TYPE_SERIAL  (FILE_TYPE_PIPE+1)
27#define FILE_TYPE_CONSOLE (FILE_TYPE_PIPE+2)
28
29/*
30 * The following structure contains per-instance data for a file based channel.
31 */
32
33typedef struct FileInfo {
34    Tcl_Channel channel;	/* Pointer to channel structure. */
35    int validMask;		/* OR'ed combination of TCL_READABLE,
36				 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
37				 * which operations are valid on the file. */
38    int watchMask;		/* OR'ed combination of TCL_READABLE,
39				 * TCL_WRITABLE, or TCL_EXCEPTION: indicates
40				 * which events should be reported. */
41    int flags;			/* State flags, see above for a list. */
42    HANDLE handle;		/* Input/output file. */
43    struct FileInfo *nextPtr;	/* Pointer to next registered file. */
44    int dirty;			/* Boolean flag. Set if the OS may have data
45				 * pending on the channel. */
46} FileInfo;
47
48typedef struct ThreadSpecificData {
49    /*
50     * List of all file channels currently open.
51     */
52
53    FileInfo *firstFilePtr;
54} ThreadSpecificData;
55
56static Tcl_ThreadDataKey dataKey;
57
58/*
59 * The following structure is what is added to the Tcl event queue when file
60 * events are generated.
61 */
62
63typedef struct FileEvent {
64    Tcl_Event header;		/* Information that is standard for all
65				 * events. */
66    FileInfo *infoPtr;		/* Pointer to file info structure. Note that
67				 * we still have to verify that the file
68				 * exists before dereferencing this
69				 * pointer. */
70} FileEvent;
71
72/*
73 * Static routines for this file:
74 */
75
76static int		FileBlockProc(ClientData instanceData, int mode);
77static void		FileChannelExitHandler(ClientData clientData);
78static void		FileCheckProc(ClientData clientData, int flags);
79static int		FileCloseProc(ClientData instanceData,
80			    Tcl_Interp *interp);
81static int		FileEventProc(Tcl_Event *evPtr, int flags);
82static int		FileGetHandleProc(ClientData instanceData,
83			    int direction, ClientData *handlePtr);
84static ThreadSpecificData *FileInit(void);
85static int		FileInputProc(ClientData instanceData, char *buf,
86			    int toRead, int *errorCode);
87static int		FileOutputProc(ClientData instanceData,
88			    CONST char *buf, int toWrite, int *errorCode);
89static int		FileSeekProc(ClientData instanceData, long offset,
90			    int mode, int *errorCode);
91static Tcl_WideInt	FileWideSeekProc(ClientData instanceData,
92			    Tcl_WideInt offset, int mode, int *errorCode);
93static void		FileSetupProc(ClientData clientData, int flags);
94static void		FileWatchProc(ClientData instanceData, int mask);
95static void		FileThreadActionProc(ClientData instanceData,
96			    int action);
97static int		FileTruncateProc(ClientData instanceData,
98			    Tcl_WideInt length);
99static DWORD		FileGetType(HANDLE handle);
100
101/*
102 * This structure describes the channel type structure for file based IO.
103 */
104
105static Tcl_ChannelType fileChannelType = {
106    "file",			/* Type name. */
107    TCL_CHANNEL_VERSION_5,	/* v5 channel */
108    FileCloseProc,		/* Close proc. */
109    FileInputProc,		/* Input proc. */
110    FileOutputProc,		/* Output proc. */
111    FileSeekProc,		/* Seek proc. */
112    NULL,			/* Set option proc. */
113    NULL,			/* Get option proc. */
114    FileWatchProc,		/* Set up the notifier to watch the channel. */
115    FileGetHandleProc,		/* Get an OS handle from channel. */
116    NULL,			/* close2proc. */
117    FileBlockProc,		/* Set blocking or non-blocking mode.*/
118    NULL,			/* flush proc. */
119    NULL,			/* handler proc. */
120    FileWideSeekProc,		/* Wide seek proc. */
121    FileThreadActionProc,	/* Thread action proc. */
122    FileTruncateProc,		/* Truncate proc. */
123};
124
125#ifdef HAVE_NO_SEH
126/*
127 * Unlike Borland and Microsoft, we don't register exception handlers by
128 * pushing registration records onto the runtime stack. Instead, we register
129 * them by creating an EXCEPTION_REGISTRATION within the activation record.
130 */
131
132typedef struct EXCEPTION_REGISTRATION {
133    struct EXCEPTION_REGISTRATION* link;
134    EXCEPTION_DISPOSITION (*handler)(
135	    struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*);
136    void* ebp;
137    void* esp;
138    int status;
139} EXCEPTION_REGISTRATION;
140#endif
141
142/*
143 *----------------------------------------------------------------------
144 *
145 * FileInit --
146 *
147 *	This function creates the window used to simulate file events.
148 *
149 * Results:
150 *	None.
151 *
152 * Side effects:
153 *	Creates a new window and creates an exit handler.
154 *
155 *----------------------------------------------------------------------
156 */
157
158static ThreadSpecificData *
159FileInit(void)
160{
161    ThreadSpecificData *tsdPtr =
162	    (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey);
163
164    if (tsdPtr == NULL) {
165	tsdPtr = TCL_TSD_INIT(&dataKey);
166	tsdPtr->firstFilePtr = NULL;
167	Tcl_CreateEventSource(FileSetupProc, FileCheckProc, NULL);
168	Tcl_CreateThreadExitHandler(FileChannelExitHandler, NULL);
169    }
170    return tsdPtr;
171}
172
173/*
174 *----------------------------------------------------------------------
175 *
176 * FileChannelExitHandler --
177 *
178 *	This function is called to cleanup the channel driver before Tcl is
179 *	unloaded.
180 *
181 * Results:
182 *	None.
183 *
184 * Side effects:
185 *	Destroys the communication window.
186 *
187 *----------------------------------------------------------------------
188 */
189
190static void
191FileChannelExitHandler(
192    ClientData clientData)	/* Old window proc */
193{
194    Tcl_DeleteEventSource(FileSetupProc, FileCheckProc, NULL);
195}
196
197/*
198 *----------------------------------------------------------------------
199 *
200 * FileSetupProc --
201 *
202 *	This function is invoked before Tcl_DoOneEvent blocks waiting for an
203 *	event.
204 *
205 * Results:
206 *	None.
207 *
208 * Side effects:
209 *	Adjusts the block time if needed.
210 *
211 *----------------------------------------------------------------------
212 */
213
214void
215FileSetupProc(
216    ClientData data,		/* Not used. */
217    int flags)			/* Event flags as passed to Tcl_DoOneEvent. */
218{
219    FileInfo *infoPtr;
220    Tcl_Time blockTime = { 0, 0 };
221    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
222
223    if (!(flags & TCL_FILE_EVENTS)) {
224	return;
225    }
226
227    /*
228     * Check to see if there is a ready file. If so, poll.
229     */
230
231    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
232	    infoPtr = infoPtr->nextPtr) {
233	if (infoPtr->watchMask) {
234	    Tcl_SetMaxBlockTime(&blockTime);
235	    break;
236	}
237    }
238}
239
240/*
241 *----------------------------------------------------------------------
242 *
243 * FileCheckProc --
244 *
245 *	This function is called by Tcl_DoOneEvent to check the file event
246 *	source for events.
247 *
248 * Results:
249 *	None.
250 *
251 * Side effects:
252 *	May queue an event.
253 *
254 *----------------------------------------------------------------------
255 */
256
257static void
258FileCheckProc(
259    ClientData data,		/* Not used. */
260    int flags)			/* Event flags as passed to Tcl_DoOneEvent. */
261{
262    FileEvent *evPtr;
263    FileInfo *infoPtr;
264    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
265
266    if (!(flags & TCL_FILE_EVENTS)) {
267	return;
268    }
269
270    /*
271     * Queue events for any ready files that don't already have events queued
272     * (caused by persistent states that won't generate WinSock events).
273     */
274
275    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
276	    infoPtr = infoPtr->nextPtr) {
277	if (infoPtr->watchMask && !(infoPtr->flags & FILE_PENDING)) {
278	    infoPtr->flags |= FILE_PENDING;
279	    evPtr = (FileEvent *) ckalloc(sizeof(FileEvent));
280	    evPtr->header.proc = FileEventProc;
281	    evPtr->infoPtr = infoPtr;
282	    Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
283	}
284    }
285}
286
287/*
288 *----------------------------------------------------------------------
289 *
290 * FileEventProc --
291 *
292 *	This function is invoked by Tcl_ServiceEvent when a file event reaches
293 *	the front of the event queue. This function invokes Tcl_NotifyChannel
294 *	on the file.
295 *
296 * Results:
297 *	Returns 1 if the event was handled, meaning it should be removed from
298 *	the queue. Returns 0 if the event was not handled, meaning it should
299 *	stay on the queue. The only time the event isn't handled is if the
300 *	TCL_FILE_EVENTS flag bit isn't set.
301 *
302 * Side effects:
303 *	Whatever the notifier callback does.
304 *
305 *----------------------------------------------------------------------
306 */
307
308static int
309FileEventProc(
310    Tcl_Event *evPtr,		/* Event to service. */
311    int flags)			/* Flags that indicate what events to handle,
312				 * such as TCL_FILE_EVENTS. */
313{
314    FileEvent *fileEvPtr = (FileEvent *)evPtr;
315    FileInfo *infoPtr;
316    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
317
318    if (!(flags & TCL_FILE_EVENTS)) {
319	return 0;
320    }
321
322    /*
323     * Search through the list of watched files for the one whose handle
324     * matches the event. We do this rather than simply dereferencing the
325     * handle in the event so that files can be deleted while the event is in
326     * the queue.
327     */
328
329    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
330	    infoPtr = infoPtr->nextPtr) {
331	if (fileEvPtr->infoPtr == infoPtr) {
332	    infoPtr->flags &= ~(FILE_PENDING);
333	    Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask);
334	    break;
335	}
336    }
337    return 1;
338}
339
340/*
341 *----------------------------------------------------------------------
342 *
343 * FileBlockProc --
344 *
345 *	Set blocking or non-blocking mode on channel.
346 *
347 * Results:
348 *	0 if successful, errno when failed.
349 *
350 * Side effects:
351 *	Sets the device into blocking or non-blocking mode.
352 *
353 *----------------------------------------------------------------------
354 */
355
356static int
357FileBlockProc(
358    ClientData instanceData,	/* Instance data for channel. */
359    int mode)			/* TCL_MODE_BLOCKING or
360				 * TCL_MODE_NONBLOCKING. */
361{
362    FileInfo *infoPtr = (FileInfo *) instanceData;
363
364    /*
365     * Files on Windows can not be switched between blocking and nonblocking,
366     * hence we have to emulate the behavior. This is done in the input
367     * function by checking against a bit in the state. We set or unset the
368     * bit here to cause the input function to emulate the correct behavior.
369     */
370
371    if (mode == TCL_MODE_NONBLOCKING) {
372	infoPtr->flags |= FILE_ASYNC;
373    } else {
374	infoPtr->flags &= ~(FILE_ASYNC);
375    }
376    return 0;
377}
378
379/*
380 *----------------------------------------------------------------------
381 *
382 * FileCloseProc --
383 *
384 *	Closes the IO channel.
385 *
386 * Results:
387 *	0 if successful, the value of errno if failed.
388 *
389 * Side effects:
390 *	Closes the physical channel
391 *
392 *----------------------------------------------------------------------
393 */
394
395static int
396FileCloseProc(
397    ClientData instanceData,	/* Pointer to FileInfo structure. */
398    Tcl_Interp *interp)		/* Not used. */
399{
400    FileInfo *fileInfoPtr = (FileInfo *) instanceData;
401    FileInfo *infoPtr;
402    ThreadSpecificData *tsdPtr;
403    int errorCode = 0;
404
405    /*
406     * Remove the file from the watch list.
407     */
408
409    FileWatchProc(instanceData, 0);
410
411    /*
412     * Don't close the Win32 handle if the handle is a standard channel during
413     * the thread exit process. Otherwise, one thread may kill the stdio of
414     * another.
415     */
416
417    if (!TclInThreadExit()
418	    || ((GetStdHandle(STD_INPUT_HANDLE) != fileInfoPtr->handle)
419	    &&  (GetStdHandle(STD_OUTPUT_HANDLE) != fileInfoPtr->handle)
420	    &&  (GetStdHandle(STD_ERROR_HANDLE) != fileInfoPtr->handle))) {
421	if (CloseHandle(fileInfoPtr->handle) == FALSE) {
422	    TclWinConvertError(GetLastError());
423	    errorCode = errno;
424	}
425    }
426
427    /*
428     * See if this FileInfo* is still on the thread local list.
429     */
430
431    tsdPtr = TCL_TSD_INIT(&dataKey);
432    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
433	    infoPtr = infoPtr->nextPtr) {
434	if (infoPtr == fileInfoPtr) {
435	    /*
436	     * This channel exists on the thread local list. It should have
437	     * been removed by an earlier Threadaction call, but do that now
438	     * since just deallocating fileInfoPtr would leave an deallocated
439	     * pointer on the thread local list.
440	     */
441
442	    FileThreadActionProc(fileInfoPtr,TCL_CHANNEL_THREAD_REMOVE);
443	    break;
444	}
445    }
446    ckfree((char *)fileInfoPtr);
447    return errorCode;
448}
449
450/*
451 *----------------------------------------------------------------------
452 *
453 * FileSeekProc --
454 *
455 *	Seeks on a file-based channel. Returns the new position.
456 *
457 * Results:
458 *	-1 if failed, the new position if successful. If failed, it also sets
459 *	*errorCodePtr to the error code.
460 *
461 * Side effects:
462 *	Moves the location at which the channel will be accessed in future
463 *	operations.
464 *
465 *----------------------------------------------------------------------
466 */
467
468static int
469FileSeekProc(
470    ClientData instanceData,	/* File state. */
471    long offset,		/* Offset to seek to. */
472    int mode,			/* Relative to where should we seek? */
473    int *errorCodePtr)		/* To store error code. */
474{
475    FileInfo *infoPtr = (FileInfo *) instanceData;
476    LONG newPos, newPosHigh, oldPos, oldPosHigh;
477    DWORD moveMethod;
478
479    *errorCodePtr = 0;
480    if (mode == SEEK_SET) {
481	moveMethod = FILE_BEGIN;
482    } else if (mode == SEEK_CUR) {
483	moveMethod = FILE_CURRENT;
484    } else {
485	moveMethod = FILE_END;
486    }
487
488    /*
489     * Save our current place in case we need to roll-back the seek.
490     */
491
492    oldPosHigh = 0;
493    oldPos = SetFilePointer(infoPtr->handle, 0, &oldPosHigh, FILE_CURRENT);
494    if (oldPos == (LONG)INVALID_SET_FILE_POINTER) {
495	DWORD winError = GetLastError();
496
497	if (winError != NO_ERROR) {
498	    TclWinConvertError(winError);
499	    *errorCodePtr = errno;
500	    return -1;
501	}
502    }
503
504    newPosHigh = (offset < 0 ? -1 : 0);
505    newPos = SetFilePointer(infoPtr->handle, offset, &newPosHigh, moveMethod);
506    if (newPos == (LONG)INVALID_SET_FILE_POINTER) {
507	DWORD winError = GetLastError();
508
509	if (winError != NO_ERROR) {
510	    TclWinConvertError(winError);
511	    *errorCodePtr = errno;
512	    return -1;
513	}
514    }
515
516    /*
517     * Check for expressability in our return type, and roll-back otherwise.
518     */
519
520    if (newPosHigh != 0) {
521	*errorCodePtr = EOVERFLOW;
522	SetFilePointer(infoPtr->handle, oldPos, &oldPosHigh, FILE_BEGIN);
523	return -1;
524    }
525    return (int) newPos;
526}
527
528/*
529 *----------------------------------------------------------------------
530 *
531 * FileWideSeekProc --
532 *
533 *	Seeks on a file-based channel. Returns the new position.
534 *
535 * Results:
536 *	-1 if failed, the new position if successful. If failed, it also sets
537 *	*errorCodePtr to the error code.
538 *
539 * Side effects:
540 *	Moves the location at which the channel will be accessed in future
541 *	operations.
542 *
543 *----------------------------------------------------------------------
544 */
545
546static Tcl_WideInt
547FileWideSeekProc(
548    ClientData instanceData,	/* File state. */
549    Tcl_WideInt offset,		/* Offset to seek to. */
550    int mode,			/* Relative to where should we seek? */
551    int *errorCodePtr)		/* To store error code. */
552{
553    FileInfo *infoPtr = (FileInfo *) instanceData;
554    DWORD moveMethod;
555    LONG newPos, newPosHigh;
556
557    *errorCodePtr = 0;
558    if (mode == SEEK_SET) {
559	moveMethod = FILE_BEGIN;
560    } else if (mode == SEEK_CUR) {
561	moveMethod = FILE_CURRENT;
562    } else {
563	moveMethod = FILE_END;
564    }
565
566    newPosHigh = Tcl_WideAsLong(offset >> 32);
567    newPos = SetFilePointer(infoPtr->handle, Tcl_WideAsLong(offset),
568	    &newPosHigh, moveMethod);
569    if (newPos == (LONG)INVALID_SET_FILE_POINTER) {
570	DWORD winError = GetLastError();
571
572	if (winError != NO_ERROR) {
573	    TclWinConvertError(winError);
574	    *errorCodePtr = errno;
575	    return -1;
576	}
577    }
578    return (((Tcl_WideInt)((unsigned)newPos)) | (Tcl_LongAsWide(newPosHigh) << 32));
579}
580
581/*
582 *----------------------------------------------------------------------
583 *
584 * FileTruncateProc --
585 *
586 *	Truncates a file-based channel. Returns the error code.
587 *
588 * Results:
589 *	0 if successful, POSIX-y error code if it failed.
590 *
591 * Side effects:
592 *	Truncates the file, may move file pointers too.
593 *
594 *----------------------------------------------------------------------
595 */
596
597static int
598FileTruncateProc(
599    ClientData instanceData,	/* File state. */
600    Tcl_WideInt length)		/* Length to truncate at. */
601{
602    FileInfo *infoPtr = (FileInfo *) instanceData;
603    LONG newPos, newPosHigh, oldPos, oldPosHigh;
604
605    /*
606     * Save where we were...
607     */
608
609    oldPosHigh = 0;
610    oldPos = SetFilePointer(infoPtr->handle, 0, &oldPosHigh, FILE_CURRENT);
611    if (oldPos == (LONG)INVALID_SET_FILE_POINTER) {
612	DWORD winError = GetLastError();
613	if (winError != NO_ERROR) {
614	    TclWinConvertError(winError);
615	    return errno;
616	}
617    }
618
619    /*
620     * Move to where we want to truncate
621     */
622
623    newPosHigh = Tcl_WideAsLong(length >> 32);
624    newPos = SetFilePointer(infoPtr->handle, Tcl_WideAsLong(length),
625	    &newPosHigh, FILE_BEGIN);
626    if (newPos == (LONG)INVALID_SET_FILE_POINTER) {
627	DWORD winError = GetLastError();
628	if (winError != NO_ERROR) {
629	    TclWinConvertError(winError);
630	    return errno;
631	}
632    }
633
634    /*
635     * Perform the truncation (unlike POSIX ftruncate(), we needed to move to
636     * the location to truncate at first).
637     */
638
639    if (!SetEndOfFile(infoPtr->handle)) {
640	TclWinConvertError(GetLastError());
641	return errno;
642    }
643
644    /*
645     * Move back. If this last step fails, we don't care; it's just a "best
646     * effort" attempt to restore our file pointer to where it was.
647     */
648
649    SetFilePointer(infoPtr->handle, oldPos, &oldPosHigh, FILE_BEGIN);
650    return 0;
651}
652
653/*
654 *----------------------------------------------------------------------
655 *
656 * FileInputProc --
657 *
658 *	Reads input from the IO channel into the buffer given. Returns count
659 *	of how many bytes were actually read, and an error indication.
660 *
661 * Results:
662 *	A count of how many bytes were read is returned and an error
663 *	indication is returned in an output argument.
664 *
665 * Side effects:
666 *	Reads input from the actual channel.
667 *
668 *----------------------------------------------------------------------
669 */
670
671static int
672FileInputProc(
673    ClientData instanceData,	/* File state. */
674    char *buf,			/* Where to store data read. */
675    int bufSize,		/* Num bytes available in buffer. */
676    int *errorCode)		/* Where to store error code. */
677{
678    FileInfo *infoPtr;
679    DWORD bytesRead;
680
681    *errorCode = 0;
682    infoPtr = (FileInfo *) instanceData;
683
684    /*
685     * Note that we will block on reads from a console buffer until a full
686     * line has been entered. The only way I know of to get around this is to
687     * write a console driver. We should probably do this at some point, but
688     * for now, we just block. The same problem exists for files being read
689     * over the network.
690     */
691
692    if (ReadFile(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead,
693	    (LPOVERLAPPED) NULL) != FALSE) {
694	return bytesRead;
695    }
696
697    TclWinConvertError(GetLastError());
698    *errorCode = errno;
699    if (errno == EPIPE) {
700	return 0;
701    }
702    return -1;
703}
704
705/*
706 *----------------------------------------------------------------------
707 *
708 * FileOutputProc --
709 *
710 *	Writes the given output on the IO channel. Returns count of how many
711 *	characters were actually written, and an error indication.
712 *
713 * Results:
714 *	A count of how many characters were written is returned and an error
715 *	indication is returned in an output argument.
716 *
717 * Side effects:
718 *	Writes output on the actual channel.
719 *
720 *----------------------------------------------------------------------
721 */
722
723static int
724FileOutputProc(
725    ClientData instanceData,	/* File state. */
726    CONST char *buf,		/* The data buffer. */
727    int toWrite,		/* How many bytes to write? */
728    int *errorCode)		/* Where to store error code. */
729{
730    FileInfo *infoPtr = (FileInfo *) instanceData;
731    DWORD bytesWritten;
732
733    *errorCode = 0;
734
735    /*
736     * If we are writing to a file that was opened with O_APPEND, we need to
737     * seek to the end of the file before writing the current buffer.
738     */
739
740    if (infoPtr->flags & FILE_APPEND) {
741	SetFilePointer(infoPtr->handle, 0, NULL, FILE_END);
742    }
743
744    if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite,
745	    &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) {
746	TclWinConvertError(GetLastError());
747	*errorCode = errno;
748	return -1;
749    }
750    infoPtr->dirty = 1;
751    return bytesWritten;
752}
753
754/*
755 *----------------------------------------------------------------------
756 *
757 * FileWatchProc --
758 *
759 *	Called by the notifier to set up to watch for events on this channel.
760 *
761 * Results:
762 *	None.
763 *
764 * Side effects:
765 *	None.
766 *
767 *----------------------------------------------------------------------
768 */
769
770static void
771FileWatchProc(
772    ClientData instanceData,	/* File state. */
773    int mask)			/* What events to watch for; OR-ed combination
774				 * of TCL_READABLE, TCL_WRITABLE and
775				 * TCL_EXCEPTION. */
776{
777    FileInfo *infoPtr = (FileInfo *) instanceData;
778    Tcl_Time blockTime = { 0, 0 };
779
780    /*
781     * Since the file is always ready for events, we set the block time to
782     * zero so we will poll.
783     */
784
785    infoPtr->watchMask = mask & infoPtr->validMask;
786    if (infoPtr->watchMask) {
787	Tcl_SetMaxBlockTime(&blockTime);
788    }
789}
790
791/*
792 *----------------------------------------------------------------------
793 *
794 * FileGetHandleProc --
795 *
796 *	Called from Tcl_GetChannelHandle to retrieve OS handles from a file
797 *	based channel.
798 *
799 * Results:
800 *	Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no
801 *	handle for the specified direction.
802 *
803 * Side effects:
804 *	None.
805 *
806 *----------------------------------------------------------------------
807 */
808
809static int
810FileGetHandleProc(
811    ClientData instanceData,	/* The file state. */
812    int direction,		/* TCL_READABLE or TCL_WRITABLE */
813    ClientData *handlePtr)	/* Where to store the handle.  */
814{
815    FileInfo *infoPtr = (FileInfo *) instanceData;
816
817    if (direction & infoPtr->validMask) {
818	*handlePtr = (ClientData) infoPtr->handle;
819	return TCL_OK;
820    } else {
821	return TCL_ERROR;
822    }
823}
824
825/*
826 *----------------------------------------------------------------------
827 *
828 * TclpOpenFileChannel --
829 *
830 *	Open an File based channel on Unix systems.
831 *
832 * Results:
833 *	The new channel or NULL. If NULL, the output argument errorCodePtr is
834 *	set to a POSIX error.
835 *
836 * Side effects:
837 *	May open the channel and may cause creation of a file on the file
838 *	system.
839 *
840 *----------------------------------------------------------------------
841 */
842
843Tcl_Channel
844TclpOpenFileChannel(
845    Tcl_Interp *interp,		/* Interpreter for error reporting; can be
846				 * NULL. */
847    Tcl_Obj *pathPtr,		/* Name of file to open. */
848    int mode,			/* POSIX mode. */
849    int permissions)		/* If the open involves creating a file, with
850				 * what modes to create it? */
851{
852    Tcl_Channel channel = 0;
853    int channelPermissions = 0;
854    DWORD accessMode = 0, createMode, shareMode, flags;
855    CONST TCHAR *nativeName;
856    HANDLE handle;
857    char channelName[16 + TCL_INTEGER_SPACE];
858    TclFile readFile = NULL, writeFile = NULL;
859
860    nativeName = (TCHAR*) Tcl_FSGetNativePath(pathPtr);
861    if (nativeName == NULL) {
862	return NULL;
863    }
864
865    switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) {
866    case O_RDONLY:
867	accessMode = GENERIC_READ;
868	channelPermissions = TCL_READABLE;
869	break;
870    case O_WRONLY:
871	accessMode = GENERIC_WRITE;
872	channelPermissions = TCL_WRITABLE;
873	break;
874    case O_RDWR:
875	accessMode = (GENERIC_READ | GENERIC_WRITE);
876	channelPermissions = (TCL_READABLE | TCL_WRITABLE);
877	break;
878    default:
879	Tcl_Panic("TclpOpenFileChannel: invalid mode value");
880	break;
881    }
882
883    /*
884     * Map the creation flags to the NT create mode.
885     */
886
887    switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) {
888    case (O_CREAT | O_EXCL):
889    case (O_CREAT | O_EXCL | O_TRUNC):
890	createMode = CREATE_NEW;
891	break;
892    case (O_CREAT | O_TRUNC):
893	createMode = CREATE_ALWAYS;
894	break;
895    case O_CREAT:
896	createMode = OPEN_ALWAYS;
897	break;
898    case O_TRUNC:
899    case (O_TRUNC | O_EXCL):
900	createMode = TRUNCATE_EXISTING;
901	break;
902    default:
903	createMode = OPEN_EXISTING;
904	break;
905    }
906
907    /*
908     * If the file is being created, get the file attributes from the
909     * permissions argument, else use the existing file attributes.
910     */
911
912    if (mode & O_CREAT) {
913	if (permissions & S_IWRITE) {
914	    flags = FILE_ATTRIBUTE_NORMAL;
915	} else {
916	    flags = FILE_ATTRIBUTE_READONLY;
917	}
918    } else {
919	flags = (*tclWinProcs->getFileAttributesProc)(nativeName);
920	if (flags == 0xFFFFFFFF) {
921	    flags = 0;
922	}
923    }
924
925    /*
926     * Set up the file sharing mode.  We want to allow simultaneous access.
927     */
928
929    shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
930
931    /*
932     * Now we get to create the file.
933     */
934
935    handle = (*tclWinProcs->createFileProc)(nativeName, accessMode,
936	    shareMode, NULL, createMode, flags, (HANDLE) NULL);
937
938    if (handle == INVALID_HANDLE_VALUE) {
939	DWORD err = GetLastError();
940
941	if ((err & 0xffffL) == ERROR_OPEN_FAILED) {
942	    err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND;
943	}
944	TclWinConvertError(err);
945	if (interp != (Tcl_Interp *) NULL) {
946	    Tcl_AppendResult(interp, "couldn't open \"", TclGetString(pathPtr),
947		    "\": ", Tcl_PosixError(interp), NULL);
948	}
949	return NULL;
950    }
951
952    channel = NULL;
953
954    switch (FileGetType(handle)) {
955    case FILE_TYPE_SERIAL:
956	/*
957	 * Reopen channel for OVERLAPPED operation. Normally this shouldn't
958	 * fail, because the channel exists.
959	 */
960
961	handle = TclWinSerialReopen(handle, nativeName, accessMode);
962	if (handle == INVALID_HANDLE_VALUE) {
963	    TclWinConvertError(GetLastError());
964	    if (interp != (Tcl_Interp *) NULL) {
965		Tcl_AppendResult(interp, "couldn't reopen serial \"",
966			TclGetString(pathPtr), "\": ",
967			Tcl_PosixError(interp), NULL);
968	    }
969	    return NULL;
970	}
971	channel = TclWinOpenSerialChannel(handle, channelName,
972		channelPermissions);
973	break;
974    case FILE_TYPE_CONSOLE:
975	channel = TclWinOpenConsoleChannel(handle, channelName,
976		channelPermissions);
977	break;
978    case FILE_TYPE_PIPE:
979	if (channelPermissions & TCL_READABLE) {
980	    readFile = TclWinMakeFile(handle);
981	}
982	if (channelPermissions & TCL_WRITABLE) {
983	    writeFile = TclWinMakeFile(handle);
984	}
985	channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL);
986	break;
987    case FILE_TYPE_CHAR:
988    case FILE_TYPE_DISK:
989    case FILE_TYPE_UNKNOWN:
990	channel = TclWinOpenFileChannel(handle, channelName,
991		channelPermissions, (mode & O_APPEND) ? FILE_APPEND : 0);
992	break;
993
994    default:
995	/*
996	 * The handle is of an unknown type, probably /dev/nul equivalent or
997	 * possibly a closed handle.
998	 */
999
1000	channel = NULL;
1001	Tcl_AppendResult(interp, "couldn't open \"", TclGetString(pathPtr),
1002		"\": bad file type", NULL);
1003	break;
1004    }
1005
1006    return channel;
1007}
1008
1009/*
1010 *----------------------------------------------------------------------
1011 *
1012 * Tcl_MakeFileChannel --
1013 *
1014 *	Creates a Tcl_Channel from an existing platform specific file handle.
1015 *
1016 * Results:
1017 *	The Tcl_Channel created around the preexisting file.
1018 *
1019 * Side effects:
1020 *	None.
1021 *
1022 *----------------------------------------------------------------------
1023 */
1024
1025Tcl_Channel
1026Tcl_MakeFileChannel(
1027    ClientData rawHandle,	/* OS level handle */
1028    int mode)			/* ORed combination of TCL_READABLE and
1029				 * TCL_WRITABLE to indicate file mode. */
1030{
1031#ifdef HAVE_NO_SEH
1032    EXCEPTION_REGISTRATION registration;
1033#endif
1034    char channelName[16 + TCL_INTEGER_SPACE];
1035    Tcl_Channel channel = NULL;
1036    HANDLE handle = (HANDLE) rawHandle;
1037    HANDLE dupedHandle;
1038    TclFile readFile = NULL, writeFile = NULL;
1039    BOOL result;
1040
1041    if (mode == 0) {
1042	return NULL;
1043    }
1044
1045    switch (FileGetType(handle)) {
1046    case FILE_TYPE_SERIAL:
1047	channel = TclWinOpenSerialChannel(handle, channelName, mode);
1048	break;
1049    case FILE_TYPE_CONSOLE:
1050	channel = TclWinOpenConsoleChannel(handle, channelName, mode);
1051	break;
1052    case FILE_TYPE_PIPE:
1053	if (mode & TCL_READABLE) {
1054	    readFile = TclWinMakeFile(handle);
1055	}
1056	if (mode & TCL_WRITABLE) {
1057	    writeFile = TclWinMakeFile(handle);
1058	}
1059	channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL);
1060	break;
1061
1062    case FILE_TYPE_DISK:
1063    case FILE_TYPE_CHAR:
1064	channel = TclWinOpenFileChannel(handle, channelName, mode, 0);
1065	break;
1066
1067    case FILE_TYPE_UNKNOWN:
1068    default:
1069	/*
1070	 * The handle is of an unknown type. Test the validity of this OS
1071	 * handle by duplicating it, then closing the dupe. The Win32 API
1072	 * doesn't provide an IsValidHandle() function, so we have to emulate
1073	 * it here. This test will not work on a console handle reliably,
1074	 * which is why we can't test every handle that comes into this
1075	 * function in this way.
1076	 */
1077
1078	result = DuplicateHandle(GetCurrentProcess(), handle,
1079		GetCurrentProcess(), &dupedHandle, 0, FALSE,
1080		DUPLICATE_SAME_ACCESS);
1081
1082	if (result == 0) {
1083	    /*
1084	     * Unable to make a duplicate. It's definately invalid at this
1085	     * point.
1086	     */
1087
1088	    return NULL;
1089	}
1090
1091	/*
1092	 * Use structured exception handling (Win32 SEH) to protect the close
1093	 * of this duped handle which might throw EXCEPTION_INVALID_HANDLE.
1094	 */
1095
1096	result = 0;
1097#if defined(HAVE_NO_SEH) && !defined(_WIN64)
1098	/*
1099	 * Don't have SEH available, do things the hard way. Note that this
1100	 * needs to be one block of asm, to avoid stack imbalance; also, it is
1101	 * illegal for one asm block to contain a jump to another.
1102	 */
1103
1104	__asm__ __volatile__ (
1105
1106	    /*
1107	     * Pick up parameters before messing with the stack
1108	     */
1109
1110	    "movl       %[dupedHandle], %%ebx"          "\n\t"
1111
1112	    /*
1113	     * Construct an EXCEPTION_REGISTRATION to protect the call to
1114	     * CloseHandle.
1115	     */
1116
1117	    "leal       %[registration], %%edx"         "\n\t"
1118	    "movl       %%fs:0,         %%eax"          "\n\t"
1119	    "movl       %%eax,          0x0(%%edx)"     "\n\t" /* link */
1120	    "leal       1f,             %%eax"          "\n\t"
1121	    "movl       %%eax,          0x4(%%edx)"     "\n\t" /* handler */
1122	    "movl       %%ebp,          0x8(%%edx)"     "\n\t" /* ebp */
1123	    "movl       %%esp,          0xc(%%edx)"     "\n\t" /* esp */
1124	    "movl       $0,             0x10(%%edx)"    "\n\t" /* status */
1125
1126	    /*
1127	     * Link the EXCEPTION_REGISTRATION on the chain.
1128	     */
1129
1130	    "movl       %%edx,          %%fs:0"         "\n\t"
1131
1132	    /*
1133	     * Call CloseHandle(dupedHandle).
1134	     */
1135
1136	    "pushl      %%ebx"                          "\n\t"
1137	    "call       _CloseHandle@4"                 "\n\t"
1138
1139	    /*
1140	     * Come here on normal exit. Recover the EXCEPTION_REGISTRATION
1141	     * and put a TRUE status return into it.
1142	     */
1143
1144	    "movl       %%fs:0,         %%edx"          "\n\t"
1145	    "movl	$1,		%%eax"		"\n\t"
1146	    "movl       %%eax,          0x10(%%edx)"    "\n\t"
1147	    "jmp        2f"                             "\n"
1148
1149	    /*
1150	     * Come here on an exception. Recover the EXCEPTION_REGISTRATION
1151	     */
1152
1153	    "1:"                                        "\t"
1154	    "movl       %%fs:0,         %%edx"          "\n\t"
1155	    "movl       0x8(%%edx),     %%edx"          "\n\t"
1156
1157	    /*
1158	     * Come here however we exited. Restore context from the
1159	     * EXCEPTION_REGISTRATION in case the stack is unbalanced.
1160	     */
1161
1162	    "2:"                                        "\t"
1163	    "movl       0xc(%%edx),     %%esp"          "\n\t"
1164	    "movl       0x8(%%edx),     %%ebp"          "\n\t"
1165	    "movl       0x0(%%edx),     %%eax"          "\n\t"
1166	    "movl       %%eax,          %%fs:0"         "\n\t"
1167
1168	    :
1169	    /* No outputs */
1170	    :
1171	    [registration]  "m"     (registration),
1172	    [dupedHandle]   "m"	    (dupedHandle)
1173	    :
1174	    "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"
1175	    );
1176	result = registration.status;
1177#else
1178#ifndef HAVE_NO_SEH
1179	__try {
1180#endif
1181	    CloseHandle(dupedHandle);
1182	    result = 1;
1183#ifndef HAVE_NO_SEH
1184	} __except (EXCEPTION_EXECUTE_HANDLER) {}
1185#endif
1186#endif
1187	if (result == FALSE) {
1188	    return NULL;
1189	}
1190
1191	/*
1192	 * Fall through, the handle is valid.
1193	 *
1194	 * Create the undefined channel, anyways, because we know the handle
1195	 * is valid to something.
1196	 */
1197
1198	channel = TclWinOpenFileChannel(handle, channelName, mode, 0);
1199    }
1200
1201    return channel;
1202}
1203
1204/*
1205 *----------------------------------------------------------------------
1206 *
1207 * TclpGetDefaultStdChannel --
1208 *
1209 *	Constructs a channel for the specified standard OS handle.
1210 *
1211 * Results:
1212 *	Returns the specified default standard channel, or NULL.
1213 *
1214 * Side effects:
1215 *	May cause the creation of a standard channel and the underlying file.
1216 *
1217 *----------------------------------------------------------------------
1218 */
1219
1220Tcl_Channel
1221TclpGetDefaultStdChannel(
1222    int type)			/* One of TCL_STDIN, TCL_STDOUT, or
1223				 * TCL_STDERR. */
1224{
1225    Tcl_Channel channel;
1226    HANDLE handle;
1227    int mode = -1;
1228    char *bufMode = NULL;
1229    DWORD handleId = (DWORD)INVALID_HANDLE_VALUE;
1230				/* Standard handle to retrieve. */
1231
1232    switch (type) {
1233    case TCL_STDIN:
1234	handleId = STD_INPUT_HANDLE;
1235	mode = TCL_READABLE;
1236	bufMode = "line";
1237	break;
1238    case TCL_STDOUT:
1239	handleId = STD_OUTPUT_HANDLE;
1240	mode = TCL_WRITABLE;
1241	bufMode = "line";
1242	break;
1243    case TCL_STDERR:
1244	handleId = STD_ERROR_HANDLE;
1245	mode = TCL_WRITABLE;
1246	bufMode = "none";
1247	break;
1248    default:
1249	Tcl_Panic("TclGetDefaultStdChannel: Unexpected channel type");
1250	break;
1251    }
1252
1253    handle = GetStdHandle(handleId);
1254
1255    /*
1256     * Note that we need to check for 0 because Windows may return 0 if this
1257     * is not a console mode application, even though this is not a valid
1258     * handle.
1259     */
1260
1261    if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) {
1262	return (Tcl_Channel) NULL;
1263    }
1264
1265    channel = Tcl_MakeFileChannel(handle, mode);
1266
1267    if (channel == NULL) {
1268	return (Tcl_Channel) NULL;
1269    }
1270
1271    /*
1272     * Set up the normal channel options for stdio handles.
1273     */
1274
1275    if (Tcl_SetChannelOption(NULL,channel,"-translation","auto")!=TCL_OK ||
1276	    Tcl_SetChannelOption(NULL,channel,"-eofchar","\032 {}")!=TCL_OK ||
1277	    Tcl_SetChannelOption(NULL,channel,"-buffering",bufMode)!=TCL_OK) {
1278	Tcl_Close(NULL, channel);
1279	return (Tcl_Channel) NULL;
1280    }
1281    return channel;
1282}
1283
1284/*
1285 *----------------------------------------------------------------------
1286 *
1287 * TclWinOpenFileChannel --
1288 *
1289 *	Constructs a File channel for the specified standard OS handle. This
1290 *	is a helper function to break up the construction of channels into
1291 *	File, Console, or Serial.
1292 *
1293 * Results:
1294 *	Returns the new channel, or NULL.
1295 *
1296 * Side effects:
1297 *	May open the channel and may cause creation of a file on the file
1298 *	system.
1299 *
1300 *----------------------------------------------------------------------
1301 */
1302
1303Tcl_Channel
1304TclWinOpenFileChannel(
1305    HANDLE handle,		/* Win32 HANDLE to swallow */
1306    char *channelName,		/* Buffer to receive channel name */
1307    int permissions,		/* OR'ed combination of TCL_READABLE,
1308				 * TCL_WRITABLE, or TCL_EXCEPTION, indicating
1309				 * which operations are valid on the file. */
1310    int appendMode)		/* OR'ed combination of bits indicating what
1311				 * additional configuration of the channel is
1312				 * present. */
1313{
1314    FileInfo *infoPtr;
1315    ThreadSpecificData *tsdPtr = FileInit();
1316
1317    /*
1318     * See if a channel with this handle already exists.
1319     */
1320
1321    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
1322	    infoPtr = infoPtr->nextPtr) {
1323	if (infoPtr->handle == (HANDLE) handle) {
1324	    return (permissions==infoPtr->validMask) ? infoPtr->channel : NULL;
1325	}
1326    }
1327
1328    infoPtr = (FileInfo *) ckalloc((unsigned) sizeof(FileInfo));
1329
1330    /*
1331     * TIP #218. Removed the code inserting the new structure into the global
1332     * list. This is now handled in the thread action callbacks, and only
1333     * there.
1334     */
1335
1336    infoPtr->nextPtr = NULL;
1337    infoPtr->validMask = permissions;
1338    infoPtr->watchMask = 0;
1339    infoPtr->flags = appendMode;
1340    infoPtr->handle = handle;
1341    infoPtr->dirty = 0;
1342    wsprintfA(channelName, "file%lx", (int) infoPtr);
1343
1344    infoPtr->channel = Tcl_CreateChannel(&fileChannelType, channelName,
1345	    (ClientData) infoPtr, permissions);
1346
1347    /*
1348     * Files have default translation of AUTO and ^Z eof char, which means
1349     * that a ^Z will be accepted as EOF when reading.
1350     */
1351
1352    Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
1353    Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}");
1354
1355    return infoPtr->channel;
1356}
1357
1358/*
1359 *----------------------------------------------------------------------
1360 *
1361 * TclWinFlushDirtyChannels --
1362 *
1363 *	Flush all dirty channels to disk, so that requesting the size of any
1364 *	file returns the correct value.
1365 *
1366 * Results:
1367 *	None.
1368 *
1369 * Side effects:
1370 *	Information is actually written to disk now, rather than later. Don't
1371 *	call this too often, or there will be a performance hit (i.e. only
1372 *	call when we need to ask for the size of a file).
1373 *
1374 *----------------------------------------------------------------------
1375 */
1376
1377void
1378TclWinFlushDirtyChannels(void)
1379{
1380    FileInfo *infoPtr;
1381    ThreadSpecificData *tsdPtr = FileInit();
1382
1383    /*
1384     * Flush all channels which are dirty, i.e. may have data pending in the
1385     * OS.
1386     */
1387
1388    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
1389	    infoPtr = infoPtr->nextPtr) {
1390	if (infoPtr->dirty) {
1391	    FlushFileBuffers(infoPtr->handle);
1392	    infoPtr->dirty = 0;
1393	}
1394    }
1395}
1396
1397/*
1398 *----------------------------------------------------------------------
1399 *
1400 * FileThreadActionProc --
1401 *
1402 *	Insert or remove any thread local refs to this channel.
1403 *
1404 * Results:
1405 *	None.
1406 *
1407 * Side effects:
1408 *	Changes thread local list of valid channels.
1409 *
1410 *----------------------------------------------------------------------
1411 */
1412
1413static void
1414FileThreadActionProc(
1415    ClientData instanceData,
1416    int action)
1417{
1418    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1419    FileInfo *infoPtr = (FileInfo *) instanceData;
1420
1421    if (action == TCL_CHANNEL_THREAD_INSERT) {
1422	infoPtr->nextPtr = tsdPtr->firstFilePtr;
1423	tsdPtr->firstFilePtr = infoPtr;
1424    } else {
1425	FileInfo **nextPtrPtr;
1426	int removed = 0;
1427
1428	for (nextPtrPtr = &(tsdPtr->firstFilePtr); (*nextPtrPtr) != NULL;
1429		nextPtrPtr = &((*nextPtrPtr)->nextPtr)) {
1430	    if ((*nextPtrPtr) == infoPtr) {
1431		(*nextPtrPtr) = infoPtr->nextPtr;
1432		removed = 1;
1433		break;
1434	    }
1435	}
1436
1437	/*
1438	 * This could happen if the channel was created in one thread and then
1439	 * moved to another without updating the thread local data in each
1440	 * thread.
1441	 */
1442
1443	if (!removed) {
1444	    Tcl_Panic("file info ptr not on thread channel list");
1445	}
1446    }
1447}
1448
1449/*
1450 *----------------------------------------------------------------------
1451 *
1452 * FileGetType --
1453 *
1454 *	Given a file handle, return its type
1455 *
1456 * Results:
1457 *	None.
1458 *
1459 * Side effects:
1460 *	None.
1461 *
1462 *----------------------------------------------------------------------
1463 */
1464
1465DWORD
1466FileGetType(
1467    HANDLE handle)		/* Opened file handle */
1468{
1469    DWORD type;
1470
1471    type = GetFileType(handle);
1472
1473    /*
1474     * If the file is a character device, we need to try to figure out whether
1475     * it is a serial port, a console, or something else. We test for the
1476     * console case first because this is more common.
1477     */
1478
1479    if ((type == FILE_TYPE_CHAR)
1480	    || ((type == FILE_TYPE_UNKNOWN) && !GetLastError())) {
1481	DWORD consoleParams;
1482
1483	if (GetConsoleMode(handle, &consoleParams)) {
1484	    type = FILE_TYPE_CONSOLE;
1485	} else {
1486	    DCB dcb;
1487
1488	    dcb.DCBlength = sizeof(DCB);
1489	    if (GetCommState(handle, &dcb)) {
1490		type = FILE_TYPE_SERIAL;
1491	    }
1492	}
1493    }
1494
1495    return type;
1496}
1497
1498/*
1499 * Local Variables:
1500 * mode: c
1501 * c-basic-offset: 4
1502 * fill-column: 78
1503 * End:
1504 */
1505