1 /*
2  * tclUnixNotify.c --
3  *
4  *	This file contains Unix-specific procedures for the notifier,
5  *	which is the lowest-level part of the Tcl event loop.  This file
6  *	works together with ../generic/tclNotify.c.
7  *
8  * Copyright (c) 1995 Sun Microsystems, Inc.
9  *
10  * See the file "license.terms" for information on usage and redistribution
11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  *
13  * SCCS: @(#) tclUnixNotfy.c 1.31 96/07/23 16:17:29
14  */
15 
16 #include "tclInt.h"
17 #include "tclPort.h"
18 #include <signal.h>
19 
20 #ifdef WIN32
21 #undef _DLL
22 #undef VOID
23 #include <windows.h>
24 #define _DLL 1
25 #endif
26 
27 #if HAVE_SYS_SELECT_H && !defined(NFDBITS)
28 #include <sys/select.h>
29 #endif
30 
31 /*
32  * The information below is used to provide read, write, and
33  * exception masks to select during calls to Tcl_DoOneEvent.
34  */
35 
36 static fd_mask checkMasks[3*MASK_SIZE];
37 				/* This array is used to build up the masks
38 				 * to be used in the next call to select.
39 				 * Bits are set in response to calls to
40 				 * Tcl_WatchFile. */
41 static fd_mask readyMasks[3*MASK_SIZE];
42 				/* This array reflects the readable/writable
43 				 * conditions that were found to exist by the
44 				 * last call to select. */
45 static int numFdBits;		/* Number of valid bits in checkMasks
46 				 * (one more than highest fd for which
47 				 * Tcl_WatchFile has been called). */
48 
49 #if 1
50 static int eventsFound;
51 static int ignoreEvents;
52 #endif
53 
54 /*
55  * Static routines in this file:
56  */
57 
58 static int	MaskEmpty _ANSI_ARGS_((long *maskPtr));
59 
TclWinFlushEvents()60 extern void TclWinFlushEvents()
61 {
62     ignoreEvents = 1;
63     while (Tcl_DoOneEvent(TCL_DONT_WAIT|TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS)) {
64     }
65     ignoreEvents = 0;
66 }
67 
68 /*
69  *----------------------------------------------------------------------
70  *
71  * Tcl_WatchFile --
72  *
73  *	Arrange for Tcl_DoOneEvent to include this file in the masks
74  *	for the next call to select.  This procedure is invoked by
75  *	event sources, which are in turn invoked by Tcl_DoOneEvent
76  *	before it invokes select.
77  *
78  * Results:
79  *	None.
80  *
81  * Side effects:
82  *
83  *	The notifier will generate a file event when the I/O channel
84  *	given by fd next becomes ready in the way indicated by mask.
85  *	If fd is already registered then the old mask will be replaced
86  *	with the new one.  Once the event is sent, the notifier will
87  *	not send any more events about the fd until the next call to
88  *	Tcl_NotifyFile.
89  *
90  *----------------------------------------------------------------------
91  */
92 
93 void
Tcl_WatchFile(file,mask)94 Tcl_WatchFile(file, mask)
95     Tcl_File file;	/* Generic file handle for a stream. */
96     int mask;			/* OR'ed combination of TCL_READABLE,
97 				 * TCL_WRITABLE, and TCL_EXCEPTION:
98 				 * indicates conditions to wait for
99 				 * in select. */
100 {
101     int fd, type, index;
102     fd_mask bit;
103 
104     fd = (int) Tcl_GetFileInfo(file, &type);
105 
106     if (type != TCL_UNIX_FD) {
107 	panic("Tcl_WatchFile: unexpected file type");
108     }
109 
110     if (fd >= FD_SETSIZE) {
111 	panic("Tcl_WatchFile can't handle file id %d", fd);
112     }
113 
114     index = fd/(NBBY*sizeof(fd_mask));
115     bit = 1 << (fd%(NBBY*sizeof(fd_mask)));
116     if (mask & TCL_READABLE) {
117 	checkMasks[index] |= bit;
118     }
119     if (mask & TCL_WRITABLE) {
120 	(checkMasks+MASK_SIZE)[index] |= bit;
121     }
122     if (mask & TCL_EXCEPTION) {
123 	(checkMasks+2*(MASK_SIZE))[index] |= bit;
124     }
125     if (numFdBits <= fd) {
126 	numFdBits = fd+1;
127     }
128 }
129 
130 #if 1
Tcl_NumEventsFound()131 extern int Tcl_NumEventsFound()
132 {
133    return eventsFound;
134 }
135 #endif
136 
137 /*
138  *----------------------------------------------------------------------
139  *
140  * Tcl_FileReady --
141  *
142  *	Indicates what conditions (readable, writable, etc.) were
143  *	present on a file the last time the notifier invoked select.
144  *	This procedure is typically invoked by event sources to see
145  *	if they should queue events.
146  *
147  * Results:
148  *	The return value is 0 if none of the conditions specified by mask
149  *	was true for fd the last time the system checked.  If any of the
150  *	conditions were true, then the return value is a mask of those
151  *	that were true.
152  *
153  * Side effects:
154  *	None.
155  *
156  *----------------------------------------------------------------------
157  */
158 
159 int
Tcl_FileReady(file,mask)160 Tcl_FileReady(file, mask)
161     Tcl_File file;	/* Generic file handle for a stream. */
162     int mask;			/* OR'ed combination of TCL_READABLE,
163 				 * TCL_WRITABLE, and TCL_EXCEPTION:
164 				 * indicates conditions caller cares about. */
165 {
166     int index, result, type, fd;
167     fd_mask bit;
168 
169     fd = (int) Tcl_GetFileInfo(file, &type);
170     if (type != TCL_UNIX_FD) {
171 	panic("Tcl_FileReady: unexpected file type");
172     }
173 
174     index = fd/(NBBY*sizeof(fd_mask));
175     bit = 1 << (fd%(NBBY*sizeof(fd_mask)));
176     result = 0;
177     if ((mask & TCL_READABLE) && (readyMasks[index] & bit)) {
178 	result |= TCL_READABLE;
179     }
180     if ((mask & TCL_WRITABLE) && ((readyMasks+MASK_SIZE)[index] & bit)) {
181 	result |= TCL_WRITABLE;
182     }
183     if ((mask & TCL_EXCEPTION) && ((readyMasks+(2*MASK_SIZE))[index] & bit)) {
184 	result |= TCL_EXCEPTION;
185     }
186     return result;
187 }
188 
189 /*
190  *----------------------------------------------------------------------
191  *
192  * MaskEmpty --
193  *
194  *	Returns nonzero if mask is empty (has no bits set).
195  *
196  * Results:
197  *	Nonzero if the mask is empty, zero otherwise.
198  *
199  * Side effects:
200  *	None
201  *
202  *----------------------------------------------------------------------
203  */
204 
205 static int
MaskEmpty(maskPtr)206 MaskEmpty(maskPtr)
207     long *maskPtr;
208 {
209     long *runPtr, *tailPtr;
210     int found, sz;
211 
212     sz = 3 * ((MASK_SIZE) / sizeof(long)) * sizeof(fd_mask);
213     for (runPtr = maskPtr, tailPtr = maskPtr + sz, found = 0;
214              runPtr < tailPtr;
215              runPtr++) {
216         if (*runPtr != 0) {
217             found = 1;
218             break;
219         }
220     }
221     return !found;
222 }
223 
224 /*
225  *----------------------------------------------------------------------
226  *
227  * Tcl_WaitForEvent --
228  *
229  *	This procedure does the lowest level wait for events in a
230  *	platform-specific manner.  It uses information provided by
231  *	previous calls to Tcl_WatchFile, plus the timePtr argument,
232  *	to determine what to wait for and how long to wait.
233  *
234  * Results:
235  *	The return value is normally TCL_OK.  However, if there are
236  *	no events to wait for (e.g. no files and no timers) so that
237  *	the procedure would block forever, then it returns TCL_ERROR.
238  *
239  * Side effects:
240  *	May put the process to sleep for a while, depending on timePtr.
241  *	When this procedure returns, an event of interest to the application
242  *	has probably, but not necessarily, occurred.
243  *
244  *----------------------------------------------------------------------
245  */
246 
247 int
Tcl_WaitForEvent(timePtr)248 Tcl_WaitForEvent(timePtr)
249     Tcl_Time *timePtr;		/* Specifies the maximum amount of time
250 				 * that this procedure should block before
251 				 * returning.  The time is given as an
252 				 * interval, not an absolute wakeup time.
253 				 * NULL means block forever. */
254 {
255     struct timeval timeout, *timeoutPtr;
256     int numFound;
257 #ifdef WIN32
258 	static int winFd = -1;
259 	if (ignoreEvents)
260 		return TCL_OK;
261 	if (winFd < 0)
262 		winFd = open("/dev/windows", 0);
263 	Tcl_WatchFile(Tcl_GetFile((ClientData)winFd,TCL_UNIX_FD),TCL_READABLE);
264 #endif
265 
266     memcpy((VOID *) readyMasks, (VOID *) checkMasks,
267 	    3*MASK_SIZE*sizeof(fd_mask));
268     if (timePtr == NULL) {
269 	if ((numFdBits == 0) || (MaskEmpty((long *) readyMasks))) {
270 	    return TCL_ERROR;
271 	}
272 	timeoutPtr = NULL;
273     } else {
274 	timeoutPtr = &timeout;
275 	timeout.tv_sec = timePtr->sec;
276 	timeout.tv_usec = timePtr->usec;
277     }
278     numFound = select(numFdBits, (SELECT_MASK *) &readyMasks[0],
279 	    (SELECT_MASK *) &readyMasks[MASK_SIZE],
280 	    (SELECT_MASK *) &readyMasks[2*MASK_SIZE], timeoutPtr);
281 
282 #ifdef WIN32
283 	if (FD_ISSET(winFd, ((SELECT_MASK *) &readyMasks[0])))
284 	{
285 		MSG msg;
286 		GetMessage(&msg, NULL, 0, 0);
287 		TranslateMessage(&msg);
288 		DispatchMessage(&msg);
289 	}
290 #endif
291 
292     /*
293      * Some systems don't clear the masks after an error, so
294      * we have to do it here.
295      */
296 
297     eventsFound = numFound;
298     if (numFound == -1) {
299 	memset((VOID *) readyMasks, 0, 3*MASK_SIZE*sizeof(fd_mask));
300     }
301 
302     /*
303      * Reset the check masks in preparation for the next call to
304      * select.
305      */
306 
307     numFdBits = 0;
308     memset((VOID *) checkMasks, 0, 3*MASK_SIZE*sizeof(fd_mask));
309     return TCL_OK;
310 }
311 
312 /*
313  *----------------------------------------------------------------------
314  *
315  * Tcl_Sleep --
316  *
317  *	Delay execution for the specified number of milliseconds.
318  *
319  * Results:
320  *	None.
321  *
322  * Side effects:
323  *	Time passes.
324  *
325  *----------------------------------------------------------------------
326  */
327 
328 void
Tcl_Sleep(ms)329 Tcl_Sleep(ms)
330     int ms;			/* Number of milliseconds to sleep. */
331 {
332     static struct timeval delay;
333     Tcl_Time before, after;
334 
335     /*
336      * The only trick here is that select appears to return early
337      * under some conditions, so we have to check to make sure that
338      * the right amount of time really has elapsed.  If it's too
339      * early, go back to sleep again.
340      */
341 
342     TclpGetTime(&before);
343     after = before;
344     after.sec += ms/1000;
345     after.usec += (ms%1000)*1000;
346     if (after.usec > 1000000) {
347 	after.usec -= 1000000;
348 	after.sec += 1;
349     }
350     while (1) {
351 	delay.tv_sec = after.sec - before.sec;
352 	delay.tv_usec = after.usec - before.usec;
353 	if (delay.tv_usec < 0) {
354 	    delay.tv_usec += 1000000;
355 	    delay.tv_sec -= 1;
356 	}
357 
358 	/*
359 	 * Special note:  must convert delay.tv_sec to int before comparing
360 	 * to zero, since delay.tv_usec is unsigned on some platforms.
361 	 */
362 
363 	if ((((int) delay.tv_sec) < 0)
364 		|| ((delay.tv_usec == 0) && (delay.tv_sec == 0))) {
365 	    break;
366 	}
367 	(void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0,
368 		(SELECT_MASK *) 0, &delay);
369 	TclpGetTime(&before);
370     }
371 }
372 
373