1 /* exp_event.c - event interface for Expect
3 Written by: Don Libes, NIST, 2/6/90
5 Design and implementation of this program was paid for by U.S. tax
6 dollars. Therefore it is public domain. However, the author and NIST
7 would appreciate credit if this program or parts of it are used.
12 I'm only a little worried because Tk does not check for errno == EBADF
13 after calling select. I imagine that if the user passes in a bad file
14 descriptor, we'll never get called back, and thus, we'll hang forever
15 - it would be better to at least issue a diagnostic to the user.
17 Another possible problem: Tk does not do file callbacks round-robin.
19 Another possible problem: Calling Create/DeleteFileHandler
20 before/after every Tcl_Eval... in expect/interact could be very
26 #include "expect_cf.h"
29 #include <sys/types.h>
32 #ifdef HAVE_SYS_WAIT_H
37 # include <sys/ptyio.h>
42 #include "exp_command.h" /* for struct exp_f defs */
43 #include "exp_event.h"
45 /* Tcl_DoOneEvent will call our filehandler which will set the following */
46 /* vars enabling us to know where and what kind of I/O we can do */
47 /*#define EXP_SPAWN_ID_BAD -1*/
48 /*#define EXP_SPAWN_ID_TIMEOUT -2*/ /* really indicates a timeout */
50 static int ready_fd = EXP_SPAWN_ID_BAD;
51 static int ready_mask;
52 static int default_mask = TCL_READABLE | TCL_EXCEPTION;
59 #if TCL_MAJOR_VERSION < 8
60 Tcl_DeleteFileHandler(exp_fs[fd].Master);
62 Tcl_DeleteFileHandler(fd);
65 /* remember that filehandler has been disabled so that */
66 /* it can be turned on for fg expect's as well as bg */
67 exp_fs[fd].fg_armed = FALSE;
71 exp_event_disarm_fast(fd,filehandler)
73 Tcl_FileProc *filehandler;
75 /* Temporarily delete the filehandler by assigning it a mask */
76 /* that permits no events! */
77 /* This reduces the calls to malloc/free inside Tcl_...FileHandler */
78 /* Tk insists on having a valid proc here even though it isn't used */
79 #if TCL_MAJOR_VERSION < 8
80 Tcl_CreateFileHandler(exp_fs[fd].Master,0,filehandler,(ClientData)0);
82 Tcl_CreateFileHandler(fd,0,filehandler,(ClientData)0);
85 /* remember that filehandler has been disabled so that */
86 /* it can be turned on for fg expect's as well as bg */
87 exp_fs[fd].fg_armed = FALSE;
91 exp_arm_background_filehandler_force(m)
94 #if TCL_MAJOR_VERSION < 8
95 Tcl_CreateFileHandler(exp_fs[m].Master,
97 Tcl_CreateFileHandler(m,
99 TCL_READABLE|TCL_EXCEPTION,
100 exp_background_filehandler,
101 (ClientData)(exp_fs[m].fd_ptr));
103 exp_fs[m].bg_status = armed;
107 exp_arm_background_filehandler(m)
110 switch (exp_fs[m].bg_status) {
112 exp_arm_background_filehandler_force(m);
114 case disarm_req_while_blocked:
115 exp_fs[m].bg_status = blocked; /* forget request */
125 exp_disarm_background_filehandler(m)
128 switch (exp_fs[m].bg_status) {
130 exp_fs[m].bg_status = disarm_req_while_blocked;
133 exp_fs[m].bg_status = unarmed;
136 case disarm_req_while_blocked:
143 /* ignore block status and forcibly disarm handler - called from exp_close. */
144 /* After exp_close returns, we will not have an opportunity to disarm */
145 /* because the fd will be invalid, so we force it here. */
147 exp_disarm_background_filehandler_force(m)
150 switch (exp_fs[m].bg_status) {
152 case disarm_req_while_blocked:
154 exp_fs[m].bg_status = unarmed;
163 /* this can only be called at the end of the bg handler in which */
164 /* case we know the status is some kind of "blocked" */
166 exp_unblock_background_filehandler(m)
169 switch (exp_fs[m].bg_status) {
171 exp_arm_background_filehandler_force(m);
173 case disarm_req_while_blocked:
174 exp_disarm_background_filehandler_force(m);
178 /* Not handled, FIXME? */
183 /* this can only be called at the beginning of the bg handler in which */
184 /* case we know the status must be "armed" */
186 exp_block_background_filehandler(m)
189 exp_fs[m].bg_status = blocked;
190 exp_event_disarm_fast(m,exp_background_filehandler);
196 exp_timehandler(clientData)
197 ClientData clientData;
199 *(int *)clientData = TRUE;
202 static void exp_filehandler(clientData,mask)
203 ClientData clientData;
206 /* if input appears, record the fd on which it appeared */
208 ready_fd = *(int *)clientData;
210 exp_event_disarm_fast(ready_fd,exp_filehandler);
213 if (ready_fd == *(int *)clientData) {
214 /* if input appears from an fd which we've already heard */
215 /* forcibly tell it to shut up. We could also shut up */
216 /* every instance, but it is more efficient to leave the */
217 /* fd enabled with the belief that we may rearm soon enough */
220 exp_event_disarm_fast(ready_fd,exp_filehandler);
222 ready_fd = *(int *)clientData;
228 /* returns status, one of EOF, TIMEOUT, ERROR or DATA */
229 /* can now return RECONFIGURE, too */
231 int exp_get_next_event(interp,masters, n,master_out,timeout,key)
234 int n; /* # of masters */
235 int *master_out; /* 1st ready master, not set if none */
236 int timeout; /* seconds */
239 static rr = 0; /* round robin ptr */
240 int i; /* index into in-array */
242 struct request_info ioctl_info;
245 int old_configure_count = exp_configure_count;
247 int timer_created = FALSE;
248 int timer_fired = FALSE;
249 Tcl_TimerToken timetoken;/* handle to Tcl timehandler descriptor */
255 /* if anything has been touched by someone else, report that */
256 /* an event has been received */
267 f->force_read = FALSE;
269 return(EXP_DATA_OLD);
270 } else if ((!f->force_read) && (f->size != 0)) {
272 return(EXP_DATA_OLD);
276 if (!timer_created) {
278 timetoken = Tcl_CreateTimerHandler(1000*timeout,
280 (ClientData)&timer_fired);
281 timer_created = TRUE;
288 /* make sure that all fds that should be armed are */
292 if (!exp_fs[k].fg_armed) {
293 Tcl_CreateFileHandler(
294 #if TCL_MAJOR_VERSION < 8
301 (ClientData)exp_fs[k].fd_ptr);
302 exp_fs[k].fg_armed = TRUE;
306 Tcl_DoOneEvent(0); /* do any event */
308 if (timer_fired) return(EXP_TIMEOUT);
310 if (old_configure_count != exp_configure_count) {
311 if (timer_created) Tcl_DeleteTimerHandler(timetoken);
312 return EXP_RECONFIGURE;
315 if (ready_fd == EXP_SPAWN_ID_BAD) continue;
317 /* if it was from something we're not looking for at */
318 /* the moment, ignore it */
320 if (ready_fd == masters[j]) goto found;
324 exp_event_disarm_fast(ready_fd,exp_filehandler);
325 ready_fd = EXP_SPAWN_ID_BAD;
328 *master_out = ready_fd;
329 ready_fd = EXP_SPAWN_ID_BAD;
331 /* this test should be redundant but SunOS */
332 /* raises both READABLE and EXCEPTION (for no */
333 /* apparent reason) when selecting on a plain file */
334 if (ready_mask & TCL_READABLE) {
335 if (timer_created) Tcl_DeleteTimerHandler(timetoken);
339 /* ready_mask must contain TCL_EXCEPTION */
341 if (timer_created) Tcl_DeleteTimerHandler(timetoken);
344 if (ioctl(*master_out,TIOCREQCHECK,&ioctl_info) < 0) {
346 Tcl_DeleteTimerHandler(timetoken);
347 exp_debuglog("ioctl error on TIOCREQCHECK: %s", Tcl_PosixError(interp));
348 return(EXP_TCLERROR);
350 if (ioctl_info.request == TIOCCLOSE) {
352 Tcl_DeleteTimerHandler(timetoken);
355 if (ioctl(*master_out, TIOCREQSET, &ioctl_info) < 0) {
356 exp_debuglog("ioctl error on TIOCREQSET after ioctl or open on slave: %s", Tcl_ErrnoMsg(errno));
358 /* presumably, we trapped an open here */
360 #endif /* !HAVE_PTYTRAP */
365 /* Having been told there was an event for a specific fd, get it */
366 /* returns status, one of EOF, TIMEOUT, ERROR or DATA */
369 exp_get_next_event_info(interp,fd,ready_mask)
375 struct request_info ioctl_info;
378 if (ready_mask & TCL_READABLE) return EXP_DATA_NEW;
380 /* ready_mask must contain TCL_EXCEPTION */
385 if (ioctl(fd,TIOCREQCHECK,&ioctl_info) < 0) {
386 exp_debuglog("ioctl error on TIOCREQCHECK: %s",
387 Tcl_PosixError(interp));
388 return(EXP_TCLERROR);
390 if (ioctl_info.request == TIOCCLOSE) {
393 if (ioctl(fd, TIOCREQSET, &ioctl_info) < 0) {
394 exp_debuglog("ioctl error on TIOCREQSET after ioctl or open on slave: %s", Tcl_ErrnoMsg(errno));
396 /* presumably, we trapped an open here */
397 /* call it an error for lack of anything more descriptive */
398 /* it will be thrown away by caller anyway */
404 int /* returns TCL_XXX */
405 exp_dsleep(interp,sec)
409 int timer_fired = FALSE;
411 Tcl_CreateTimerHandler((int)(sec*1000),exp_timehandler,(ClientData)&timer_fired);
415 if (timer_fired) return TCL_OK;
417 if (ready_fd == EXP_SPAWN_ID_BAD) continue;
419 exp_event_disarm_fast(ready_fd,exp_filehandler);
420 ready_fd = EXP_SPAWN_ID_BAD;
426 int /* returns TCL_XXX */
427 exp_usleep(interp,usec)
431 int timer_fired = FALSE;
433 Tcl_CreateTimerHandler(usec/1000,exp_timehandler,(ClientData)&timer_fired);
437 if (timer_fired) return TCL_OK;
439 if (ready_fd == EXP_SPAWN_ID_BAD) continue;
441 exp_event_disarm_fast(ready_fd,exp_filehandler);
442 ready_fd = EXP_SPAWN_ID_BAD;
447 static char destroy_cmd[] = "destroy .";
450 exp_event_exit_real(interp)
453 Tcl_Eval(interp,destroy_cmd);
456 /* set things up for later calls to event handler */
460 exp_event_exit = exp_event_exit_real;
466 /* The Tcl_CreateFileHandler call is only defined on Unix. We provide
467 our own implementation here that works on cygwin32. */
470 #include <sys/socket.h>
473 #if TCL_MAJOR_VERSION < 7
474 # error not implemented
477 static void pipe_setup _ANSI_ARGS_((ClientData, int));
478 static void pipe_check _ANSI_ARGS_((ClientData, int));
479 static void pipe_exit _ANSI_ARGS_((ClientData));
480 static int pipe_close _ANSI_ARGS_((ClientData, Tcl_Interp *));
481 static int pipe_input _ANSI_ARGS_((ClientData, char *, int, int *));
482 static int pipe_output _ANSI_ARGS_((ClientData, char *, int, int *));
483 static void pipe_watch _ANSI_ARGS_((ClientData, int));
484 static int pipe_get_handle _ANSI_ARGS_((ClientData, int, ClientData *));
485 static int pipe_event _ANSI_ARGS_((Tcl_Event *, int));
487 /* The pipe channel interface. */
489 static Tcl_ChannelType pipe_channel = {
496 NULL, /* set option */
497 NULL, /* get option */
502 /* The structure we use to represent a pipe channel. */
505 struct pipe_info *next; /* Next pipe. */
506 Tcl_Channel channel; /* The Tcl channel. */
507 int fd; /* cygwin32 file descriptor. */
508 int watch_mask; /* Events that should be reported. */
509 int flags; /* State flags; see below. */
510 HANDLE flags_mutex; /* Mutex to control access to flags. */
511 HANDLE mutex; /* Mutex to control access to pipe. */
512 HANDLE try_read; /* Event to tell thread to try a read. */
513 HANDLE pthread; /* Handle of thread inspecting the pipe. */
516 /* Values that can appear in the flags field of a pipe_info structure. */
518 #define PIPE_PENDING (1 << 0) /* Message pending. */
519 #define PIPE_READABLE (1 << 1) /* Pipe is readable. */
520 #define PIPE_CLOSED (1 << 2) /* Pipe is closed. */
521 #define PIPE_HAS_THREAD (1 << 3) /* A thread is running. */
523 /* A pipe event structure. */
526 Tcl_Event header; /* Standard Tcl event header. */
527 struct pipe_info *pipe; /* Pipe information. */
530 /* Whether we've initialized the pipe code. */
532 static int pipe_initialized;
534 /* The list of pipes. */
536 static struct pipe_info *pipes;
538 /* A hidden window we use for pipe events. */
540 static HWND pipe_window;
542 /* A message we use for pipe events. */
544 #define PIPE_MESSAGE (WM_USER + 1)
546 /* Get the flags for a pipe. */
549 pipe_get_flags (pipe)
550 struct pipe_info *pipe;
554 WaitForSingleObject (pipe->flags_mutex, INFINITE);
556 ReleaseMutex (pipe->flags_mutex);
560 /* Set a flag for a pipe. */
563 pipe_set_flag (pipe, flag)
564 struct pipe_info *pipe;
567 WaitForSingleObject (pipe->flags_mutex, INFINITE);
569 ReleaseMutex (pipe->flags_mutex);
572 /* Reset a flag for a pipe. */
575 pipe_reset_flag (pipe, flag)
576 struct pipe_info *pipe;
579 WaitForSingleObject (pipe->flags_mutex, INFINITE);
580 pipe->flags &= ~ flag;
581 ReleaseMutex (pipe->flags_mutex);
584 /* This function runs in a separate thread. When requested, it sends
585 a message if there is something to read from the pipe. FIXME: I'm
586 not sure that this thread will ever be killed off at present. */
592 struct pipe_info *pipe = (struct pipe_info *) arg;
593 HANDLE handle = get_osfhandle(pipe->fd);
594 struct timeval timeout;
600 /* time out in case this thread was "forgotten" */
601 if (WaitForSingleObject (pipe->try_read, 10000) == WAIT_TIMEOUT)
603 n = PeekNamedPipe(handle, NULL, 0, NULL, &tba, NULL);
605 break; /* pipe closed? */
608 if (pipe_get_flags (pipe) & PIPE_CLOSED) {
612 /* We use a loop periodically trying PeekNamedPipe.
613 This is inefficient, but unfortunately Windows
614 doesn't have any asynchronous mechanism to read
620 FD_SET (pipe->fd, &r);
622 FD_SET (pipe->fd, &x);
623 if ((n = select (pipe->fd + 1, &r, NULL, &x, &timeout)) <= 0 ||
624 FD_ISSET(pipe->fd, &x))
625 /* pipe_set_flag (pipe, PIPE_CLOSED)*/;
627 /* Something can be read from the pipe. */
628 pipe_set_flag (pipe, PIPE_READABLE);
630 if (pipe_get_flags (pipe) & PIPE_CLOSED) {
634 /* Post a message to wake up the event loop. */
635 PostMessage (pipe_window, PIPE_MESSAGE, 0, (LPARAM) pipe);
636 if (n < 0 || FD_ISSET (pipe->fd, &x))
640 /* The pipe is closed. */
642 CloseHandle (pipe->flags_mutex); pipe->flags_mutex = NULL;
643 CloseHandle (pipe->try_read); pipe->try_read = NULL;
644 pipe_reset_flag (pipe, PIPE_HAS_THREAD);
648 /* This function is called when pipe_thread posts a message. */
650 static LRESULT CALLBACK
651 pipe_proc (hwnd, message, wParam, lParam)
657 if (message != PIPE_MESSAGE) {
658 return DefWindowProc (hwnd, message, wParam, lParam);
661 /* This function really only exists to wake up the main Tcl
662 event loop. We don't actually have to do anything. */
667 /* Initialize the pipe channel. */
674 pipe_initialized = 1;
676 Tcl_CreateEventSource (pipe_setup, pipe_check, NULL);
677 Tcl_CreateExitHandler (pipe_exit, NULL);
679 /* Create a hidden window for asynchronous notification. */
681 memset (&class, 0, sizeof class);
682 class.hInstance = GetModuleHandle (NULL);
683 class.lpszClassName = "expect_pipe";
684 class.lpfnWndProc = pipe_proc;
686 if (! RegisterClass (&class)) {
687 exp_errorlog ("RegisterClass failed: %d\n", GetLastError ());
691 pipe_window = CreateWindow ("expect_pipe", "expect_pipe",
692 WS_TILED, 0, 0, 0, 0, NULL, NULL,
693 class.hInstance, NULL);
694 if (pipe_window == NULL) {
695 exp_errorlog ("CreateWindow failed: %d\n", GetLastError ());
700 /* Clean up the pipe channel when we exit. */
706 Tcl_DeleteEventSource (pipe_setup, pipe_check, NULL);
707 UnregisterClass ("expect_pipe", GetModuleHandle (NULL));
708 DestroyWindow (pipe_window);
709 pipe_initialized = 0;
712 /* Set up for a pipe event. */
715 pipe_setup (cd, flags)
720 Tcl_Time zero_time = { 0, 0 };
722 if (! (flags & TCL_FILE_EVENTS)) {
726 /* See if there is a watched pipe for which we already have an
727 event. If there is, set the maximum block time to 0. */
729 for (p = pipes; p != NULL; p = p->next) {
730 if ((p->watch_mask &~ TCL_READABLE)
731 || ((p->watch_mask & TCL_READABLE)
732 && ((pipe_get_flags (p) & PIPE_HAS_THREAD) == 0
733 || (pipe_get_flags (p) & PIPE_READABLE)))) {
734 Tcl_SetMaxBlockTime (&zero_time);
736 } else if (p->watch_mask & TCL_READABLE) {
737 /* Tell the thread to try reads and let us
738 know when it is done. */
739 SetEvent (p->try_read);
744 /* Check pipes for events. */
747 pipe_check (cd, flags)
753 if (! (flags & TCL_FILE_EVENTS)) {
757 /* Queue events for any watched pipes that don't already have
760 for (p = pipes; p != NULL; p = p->next) {
761 if (((p->watch_mask &~ TCL_READABLE)
762 || ((p->watch_mask & TCL_READABLE)
763 && ((pipe_get_flags (p) & PIPE_HAS_THREAD) == 0
764 || (pipe_get_flags (p) & PIPE_READABLE))))
765 && ! (pipe_get_flags (p) & PIPE_PENDING)) {
766 struct pipe_event *ev;
768 pipe_set_flag (p, PIPE_PENDING);
769 ev = (struct pipe_event *) Tcl_Alloc (sizeof *ev);
770 ev->header.proc = pipe_event;
772 Tcl_QueueEvent ((Tcl_Event *) ev, TCL_QUEUE_TAIL);
777 /* Handle closing a pipe. This is probably never called at present. */
780 pipe_close (cd, interp)
784 struct pipe_info *p = (struct pipe_info *) cd;
785 struct pipe_info **pp;
787 for (pp = &pipes; *pp != NULL; pp = &(*pp)->next) {
794 /* FIXME: How should we handle closing the handle? At
795 present, this code will only work correctly if the handle
796 is closed before this code is called; otherwise, we may
797 wait forever for the thread. */
799 if (pipe_get_flags (p) & PIPE_HAS_THREAD) {
801 pipe_set_flag (p, PIPE_CLOSED);
802 (void) SetEvent (p->try_read);
803 WaitForSingleObject (p->pthread, 10000);
804 CloseHandle (p->pthread);
806 CloseHandle (p->flags_mutex);
808 Tcl_Free ((char *) p);
813 /* Handle reading from a pipe. This will never be called. */
816 pipe_input (cd, buf, size, error)
822 exp_errorlog ("pipe_input called");
826 /* Handle writing to a pipe. This will never be called. */
829 pipe_output (cd, buf, size, error)
835 exp_errorlog ("pipe_output called");
839 /* Handle a pipe event. This is called when a pipe event created by
840 pipe_check reaches the head of the Tcl queue. */
843 pipe_event (ev, flags)
847 struct pipe_event *pev = (struct pipe_event *) ev;
851 if (! (flags & TCL_FILE_EVENTS)) {
855 /* Make sure the pipe is still being watched. */
856 for (p = pipes; p != NULL; p = p->next) {
857 if (p == pev->pipe) {
858 pipe_reset_flag (p, PIPE_PENDING);
867 if (pipe_get_flags (p) & PIPE_HAS_THREAD) {
869 if (pipe_get_flags (p) & PIPE_READABLE) {
870 mask |= TCL_READABLE;
873 mask = TCL_WRITABLE | TCL_READABLE;
876 /* Tell the channel about any events. */
878 Tcl_NotifyChannel (p->channel, p->watch_mask & mask);
883 /* Set up to watch a pipe. */
886 pipe_watch (cd, mask)
890 struct pipe_info *p = (struct pipe_info *) cd;
893 old_mask = p->watch_mask;
894 p->watch_mask = mask & (TCL_READABLE | TCL_WRITABLE);
895 if (p->watch_mask != 0) {
896 Tcl_Time zero_time = { 0, 0 };
898 if ((p->watch_mask & TCL_READABLE) != 0
899 && (pipe_get_flags (p) & PIPE_HAS_THREAD) == 0) {
903 p->try_read = CreateEvent (NULL, FALSE, FALSE,
905 pipe_set_flag (p, PIPE_HAS_THREAD);
906 p->pthread = CreateThread (NULL, 0, pipe_thread,
908 /* CYGNUS LOCAL: plug a handle leak - dj */
911 fprintf(stderr, "Error: cannot create pipe thread, error=%d\n", GetLastError());
915 CloseHandle(p->pthread);
923 Tcl_SetMaxBlockTime (&zero_time);
926 struct pipe_info **pp;
928 for (pp = &pipes; *pp != NULL; pp = &(*pp)->next) {
938 /* Get the handle of a pipe. */
941 pipe_get_handle (cd, dir, handle_ptr)
944 ClientData *handle_ptr;
946 struct pipe_info *p = (struct pipe_info *) cd;
948 *handle_ptr = (ClientData *)p->fd;
952 /* Make a pipe channel. */
955 make_pipe_channel (fd, handle)
963 if (! pipe_initialized) {
967 p = (struct pipe_info *) Tcl_Alloc (sizeof *p);
973 p->flags_mutex = CreateMutex (NULL, FALSE, NULL);
976 sprintf (namebuf, "expect_pipe%d", handle);
977 p->channel = Tcl_CreateChannel (&pipe_channel, namebuf,
979 TCL_READABLE | TCL_WRITABLE);
981 Tcl_SetChannelOption ((Tcl_Interp *) NULL, p->channel,
982 "-translation", "binary");
987 /* This is called when we read from a file descriptor. If that file
988 descriptor turns out to be a pipe, we turn off the PIPE_READABLE
989 flag. If we didn't do this, then every time we entered the Tcl
990 event loop we would think the pipe was readable. If we read the
991 pipe using Tcl channel functions, we wouldn't have this problem. */
994 exp_reading_from_descriptor (fd)
999 for (p = pipes; p != NULL; p = p->next) {
1001 pipe_reset_flag (p, PIPE_READABLE);
1007 /* Implement Tcl_CreateFileHandler for cygwin32. */
1010 Tcl_CreateFileHandler (fd, mask, proc, cd)
1016 if (exp_fs[fd].channel == NULL) {
1022 /* Tcl can handle channel events on Windows for
1023 sockets and regular files. For pipes we defines
1024 our own channel type. FIXME: The channels we
1025 create here are never deleted. */
1027 handle = (HANDLE) get_osfhandle (fd);
1028 if (handle == (HANDLE) -1)
1034 if (getsockname (fd, &sa, &salen) == 0)
1035 chan = Tcl_MakeTcpClientChannel ((ClientData) handle);
1036 else if (GetFileType (handle) != FILE_TYPE_PIPE)
1037 chan = Tcl_MakeFileChannel ((ClientData) fd,
1041 /* We assume that we can always write to a
1043 if ((mask & TCL_READABLE) == 0)
1044 chan = Tcl_MakeFileChannel ((ClientData) fd,
1047 chan = make_pipe_channel (fd, handle);
1053 exp_fs[fd].channel = chan;
1056 if (exp_fs[fd].fileproc != NULL)
1057 Tcl_DeleteChannelHandler (exp_fs[fd].channel,
1058 exp_fs[fd].fileproc,
1059 exp_fs[fd].procdata);
1061 Tcl_CreateChannelHandler (exp_fs[fd].channel, mask, proc, cd);
1062 exp_fs[fd].fileproc = proc;
1063 exp_fs[fd].procdata = cd;
1066 /* Implement Tcl_DeleteFileHandler for cygwin32. */
1069 Tcl_DeleteFileHandler (fd)
1072 if (exp_fs[fd].channel != NULL && exp_fs[fd].fileproc != NULL) {
1073 Tcl_DeleteChannelHandler (exp_fs[fd].channel,
1074 exp_fs[fd].fileproc,
1075 exp_fs[fd].procdata);
1076 exp_fs[fd].fileproc = NULL;
1081 #endif /* __CYGWIN32__ */