Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/tcl8.5.2/win/tclWinChan.c @ 47

Last change on this file since 47 was 25, checked in by landauf, 17 years ago

added tcl to libs

File size: 38.6 KB
Line 
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 2007/04/16 13:36:36 dkf 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 == 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 == 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 == 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_LongAsWide(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 == 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 == 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#ifndef HAVE_NO_SEH
1098        __try {
1099            CloseHandle(dupedHandle);
1100            result = 1;
1101        } __except (EXCEPTION_EXECUTE_HANDLER) {}
1102#else
1103        /*
1104         * Don't have SEH available, do things the hard way. Note that this
1105         * needs to be one block of asm, to avoid stack imbalance; also, it is
1106         * illegal for one asm block to contain a jump to another.
1107         */
1108
1109        __asm__ __volatile__ (
1110
1111            /*
1112             * Pick up parameters before messing with the stack
1113             */
1114
1115            "movl       %[dupedHandle], %%ebx"          "\n\t"
1116
1117            /*
1118             * Construct an EXCEPTION_REGISTRATION to protect the call to
1119             * CloseHandle.
1120             */
1121
1122            "leal       %[registration], %%edx"         "\n\t"
1123            "movl       %%fs:0,         %%eax"          "\n\t"
1124            "movl       %%eax,          0x0(%%edx)"     "\n\t" /* link */
1125            "leal       1f,             %%eax"          "\n\t"
1126            "movl       %%eax,          0x4(%%edx)"     "\n\t" /* handler */
1127            "movl       %%ebp,          0x8(%%edx)"     "\n\t" /* ebp */
1128            "movl       %%esp,          0xc(%%edx)"     "\n\t" /* esp */
1129            "movl       $0,             0x10(%%edx)"    "\n\t" /* status */
1130
1131            /*
1132             * Link the EXCEPTION_REGISTRATION on the chain.
1133             */
1134
1135            "movl       %%edx,          %%fs:0"         "\n\t"
1136
1137            /*
1138             * Call CloseHandle(dupedHandle).
1139             */
1140
1141            "pushl      %%ebx"                          "\n\t"
1142            "call       _CloseHandle@4"                 "\n\t"
1143
1144            /*
1145             * Come here on normal exit. Recover the EXCEPTION_REGISTRATION
1146             * and put a TRUE status return into it.
1147             */
1148
1149            "movl       %%fs:0,         %%edx"          "\n\t"
1150            "movl       $1,             %%eax"          "\n\t"
1151            "movl       %%eax,          0x10(%%edx)"    "\n\t"
1152            "jmp        2f"                             "\n"
1153
1154            /*
1155             * Come here on an exception. Recover the EXCEPTION_REGISTRATION
1156             */
1157
1158            "1:"                                        "\t"
1159            "movl       %%fs:0,         %%edx"          "\n\t"
1160            "movl       0x8(%%edx),     %%edx"          "\n\t"
1161
1162            /*
1163             * Come here however we exited. Restore context from the
1164             * EXCEPTION_REGISTRATION in case the stack is unbalanced.
1165             */
1166
1167            "2:"                                        "\t"
1168            "movl       0xc(%%edx),     %%esp"          "\n\t"
1169            "movl       0x8(%%edx),     %%ebp"          "\n\t"
1170            "movl       0x0(%%edx),     %%eax"          "\n\t"
1171            "movl       %%eax,          %%fs:0"         "\n\t"
1172
1173            :
1174            /* No outputs */
1175            :
1176            [registration]  "m"     (registration),
1177            [dupedHandle]   "m"     (dupedHandle)
1178            :
1179            "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"
1180            );
1181        result = registration.status;
1182
1183#endif
1184        if (result == FALSE) {
1185            return NULL;
1186        }
1187
1188        /*
1189         * Fall through, the handle is valid.
1190         *
1191         * Create the undefined channel, anyways, because we know the handle
1192         * is valid to something.
1193         */
1194
1195        channel = TclWinOpenFileChannel(handle, channelName, mode, 0);
1196    }
1197
1198    return channel;
1199}
1200
1201/*
1202 *----------------------------------------------------------------------
1203 *
1204 * TclpGetDefaultStdChannel --
1205 *
1206 *      Constructs a channel for the specified standard OS handle.
1207 *
1208 * Results:
1209 *      Returns the specified default standard channel, or NULL.
1210 *
1211 * Side effects:
1212 *      May cause the creation of a standard channel and the underlying file.
1213 *
1214 *----------------------------------------------------------------------
1215 */
1216
1217Tcl_Channel
1218TclpGetDefaultStdChannel(
1219    int type)                   /* One of TCL_STDIN, TCL_STDOUT, or
1220                                 * TCL_STDERR. */
1221{
1222    Tcl_Channel channel;
1223    HANDLE handle;
1224    int mode = -1;
1225    char *bufMode = NULL;
1226    DWORD handleId = (DWORD)INVALID_HANDLE_VALUE;
1227                                /* Standard handle to retrieve. */
1228
1229    switch (type) {
1230    case TCL_STDIN:
1231        handleId = STD_INPUT_HANDLE;
1232        mode = TCL_READABLE;
1233        bufMode = "line";
1234        break;
1235    case TCL_STDOUT:
1236        handleId = STD_OUTPUT_HANDLE;
1237        mode = TCL_WRITABLE;
1238        bufMode = "line";
1239        break;
1240    case TCL_STDERR:
1241        handleId = STD_ERROR_HANDLE;
1242        mode = TCL_WRITABLE;
1243        bufMode = "none";
1244        break;
1245    default:
1246        Tcl_Panic("TclGetDefaultStdChannel: Unexpected channel type");
1247        break;
1248    }
1249
1250    handle = GetStdHandle(handleId);
1251
1252    /*
1253     * Note that we need to check for 0 because Windows may return 0 if this
1254     * is not a console mode application, even though this is not a valid
1255     * handle.
1256     */
1257
1258    if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) {
1259        return (Tcl_Channel) NULL;
1260    }
1261
1262    channel = Tcl_MakeFileChannel(handle, mode);
1263
1264    if (channel == NULL) {
1265        return (Tcl_Channel) NULL;
1266    }
1267
1268    /*
1269     * Set up the normal channel options for stdio handles.
1270     */
1271
1272    if (Tcl_SetChannelOption(NULL,channel,"-translation","auto")!=TCL_OK ||
1273            Tcl_SetChannelOption(NULL,channel,"-eofchar","\032 {}")!=TCL_OK ||
1274            Tcl_SetChannelOption(NULL,channel,"-buffering",bufMode)!=TCL_OK) {
1275        Tcl_Close(NULL, channel);
1276        return (Tcl_Channel) NULL;
1277    }
1278    return channel;
1279}
1280
1281/*
1282 *----------------------------------------------------------------------
1283 *
1284 * TclWinOpenFileChannel --
1285 *
1286 *      Constructs a File channel for the specified standard OS handle. This
1287 *      is a helper function to break up the construction of channels into
1288 *      File, Console, or Serial.
1289 *
1290 * Results:
1291 *      Returns the new channel, or NULL.
1292 *
1293 * Side effects:
1294 *      May open the channel and may cause creation of a file on the file
1295 *      system.
1296 *
1297 *----------------------------------------------------------------------
1298 */
1299
1300Tcl_Channel
1301TclWinOpenFileChannel(
1302    HANDLE handle,              /* Win32 HANDLE to swallow */
1303    char *channelName,          /* Buffer to receive channel name */
1304    int permissions,            /* OR'ed combination of TCL_READABLE,
1305                                 * TCL_WRITABLE, or TCL_EXCEPTION, indicating
1306                                 * which operations are valid on the file. */
1307    int appendMode)             /* OR'ed combination of bits indicating what
1308                                 * additional configuration of the channel is
1309                                 * present. */
1310{
1311    FileInfo *infoPtr;
1312    ThreadSpecificData *tsdPtr = FileInit();
1313
1314    /*
1315     * See if a channel with this handle already exists.
1316     */
1317
1318    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
1319            infoPtr = infoPtr->nextPtr) {
1320        if (infoPtr->handle == (HANDLE) handle) {
1321            return (permissions==infoPtr->validMask) ? infoPtr->channel : NULL;
1322        }
1323    }
1324
1325    infoPtr = (FileInfo *) ckalloc((unsigned) sizeof(FileInfo));
1326
1327    /*
1328     * TIP #218. Removed the code inserting the new structure into the global
1329     * list. This is now handled in the thread action callbacks, and only
1330     * there.
1331     */
1332
1333    infoPtr->nextPtr = NULL;
1334    infoPtr->validMask = permissions;
1335    infoPtr->watchMask = 0;
1336    infoPtr->flags = appendMode;
1337    infoPtr->handle = handle;
1338    infoPtr->dirty = 0;
1339    wsprintfA(channelName, "file%lx", (int) infoPtr);
1340
1341    infoPtr->channel = Tcl_CreateChannel(&fileChannelType, channelName,
1342            (ClientData) infoPtr, permissions);
1343
1344    /*
1345     * Files have default translation of AUTO and ^Z eof char, which means
1346     * that a ^Z will be accepted as EOF when reading.
1347     */
1348
1349    Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
1350    Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}");
1351
1352    return infoPtr->channel;
1353}
1354
1355/*
1356 *----------------------------------------------------------------------
1357 *
1358 * TclWinFlushDirtyChannels --
1359 *
1360 *      Flush all dirty channels to disk, so that requesting the size of any
1361 *      file returns the correct value.
1362 *
1363 * Results:
1364 *      None.
1365 *
1366 * Side effects:
1367 *      Information is actually written to disk now, rather than later. Don't
1368 *      call this too often, or there will be a performance hit (i.e. only
1369 *      call when we need to ask for the size of a file).
1370 *
1371 *----------------------------------------------------------------------
1372 */
1373
1374void
1375TclWinFlushDirtyChannels(void)
1376{
1377    FileInfo *infoPtr;
1378    ThreadSpecificData *tsdPtr = FileInit();
1379
1380    /*
1381     * Flush all channels which are dirty, i.e. may have data pending in the
1382     * OS.
1383     */
1384
1385    for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
1386            infoPtr = infoPtr->nextPtr) {
1387        if (infoPtr->dirty) {
1388            FlushFileBuffers(infoPtr->handle);
1389            infoPtr->dirty = 0;
1390        }
1391    }
1392}
1393
1394/*
1395 *----------------------------------------------------------------------
1396 *
1397 * FileThreadActionProc --
1398 *
1399 *      Insert or remove any thread local refs to this channel.
1400 *
1401 * Results:
1402 *      None.
1403 *
1404 * Side effects:
1405 *      Changes thread local list of valid channels.
1406 *
1407 *----------------------------------------------------------------------
1408 */
1409
1410static void
1411FileThreadActionProc(
1412    ClientData instanceData,
1413    int action)
1414{
1415    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
1416    FileInfo *infoPtr = (FileInfo *) instanceData;
1417
1418    if (action == TCL_CHANNEL_THREAD_INSERT) {
1419        infoPtr->nextPtr = tsdPtr->firstFilePtr;
1420        tsdPtr->firstFilePtr = infoPtr;
1421    } else {
1422        FileInfo **nextPtrPtr;
1423        int removed = 0;
1424
1425        for (nextPtrPtr = &(tsdPtr->firstFilePtr); (*nextPtrPtr) != NULL;
1426                nextPtrPtr = &((*nextPtrPtr)->nextPtr)) {
1427            if ((*nextPtrPtr) == infoPtr) {
1428                (*nextPtrPtr) = infoPtr->nextPtr;
1429                removed = 1;
1430                break;
1431            }
1432        }
1433
1434        /*
1435         * This could happen if the channel was created in one thread and then
1436         * moved to another without updating the thread local data in each
1437         * thread.
1438         */
1439
1440        if (!removed) {
1441            Tcl_Panic("file info ptr not on thread channel list");
1442        }
1443    }
1444}
1445
1446/*
1447 *----------------------------------------------------------------------
1448 *
1449 * FileGetType --
1450 *
1451 *      Given a file handle, return its type
1452 *
1453 * Results:
1454 *      None.
1455 *
1456 * Side effects:
1457 *      None.
1458 *
1459 *----------------------------------------------------------------------
1460 */
1461
1462DWORD
1463FileGetType(
1464    HANDLE handle)              /* Opened file handle */
1465{
1466    DWORD type;
1467
1468    type = GetFileType(handle);
1469
1470    /*
1471     * If the file is a character device, we need to try to figure out whether
1472     * it is a serial port, a console, or something else. We test for the
1473     * console case first because this is more common.
1474     */
1475
1476    if ((type == FILE_TYPE_CHAR)
1477            || ((type == FILE_TYPE_UNKNOWN) && !GetLastError())) {
1478        DWORD consoleParams;
1479
1480        if (GetConsoleMode(handle, &consoleParams)) {
1481            type = FILE_TYPE_CONSOLE;
1482        } else {
1483            DCB dcb;
1484
1485            dcb.DCBlength = sizeof(DCB);
1486            if (GetCommState(handle, &dcb)) {
1487                type = FILE_TYPE_SERIAL;
1488            }
1489        }
1490    }
1491
1492    return type;
1493}
1494
1495/*
1496 * Local Variables:
1497 * mode: c
1498 * c-basic-offset: 4
1499 * fill-column: 78
1500 * End:
1501 */
Note: See TracBrowser for help on using the repository browser.