blob: ea35fc62365f71e62a638e093056a003d7d62e2e [file] [log] [blame]
Jack Jansenb0195ec1998-08-18 14:51:27 +00001/*
2 * tclSelectNotify.c --
3 *
4 * Partial even handling, select only. This file is adapted from TclUnixNotify.c, and
5 * meant as an add-in for Mac (and possibly Windows) environments where select *is* available.
6 * TclMacNotify.c works together with this file.
7 *
8 * Copyright (c) 1995-1997 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.42 97/07/02 20:55:44
14 */
15
16#if defined(__CFM68K__) && !defined(__USING_STATIC_LIBS__)
17 #pragma import on
18#endif
19
20#include <unistd.h>
21
22#if defined(__CFM68K__) && !defined(__USING_STATIC_LIBS__)
23 #pragma import reset
24#endif
25
26#include "tclInt.h"
27#include "tclPort.h"
28#include <signal.h>
29
30#ifndef MASK_SIZE
31#define MASK_SIZE howmany(FD_SETSIZE, NFDBITS)
32#endif
33#ifndef SELECT_MASK
34#define SELECT_MASK fd_set
35#endif
36
37/* Prototype (too lazy to create new .h) */
38int Tcl_PollSelectEvent(void);
39
40/*
41 * This structure is used to keep track of the notifier info for a
42 * a registered file.
43 */
44
45typedef struct FileHandler {
46 int fd;
47 int mask; /* Mask of desired events: TCL_READABLE,
48 * etc. */
49 int readyMask; /* Mask of events that have been seen since the
50 * last time file handlers were invoked for
51 * this file. */
52 Tcl_FileProc *proc; /* Procedure to call, in the style of
53 * Tcl_CreateFileHandler. */
54 ClientData clientData; /* Argument to pass to proc. */
55 struct FileHandler *nextPtr;/* Next in list of all files we care about. */
56} FileHandler;
57
58/*
59 * The following structure is what is added to the Tcl event queue when
60 * file handlers are ready to fire.
61 */
62
63typedef struct FileHandlerEvent {
64 Tcl_Event header; /* Information that is standard for
65 * all events. */
66 int fd; /* File descriptor that is ready. Used
67 * to find the FileHandler structure for
68 * the file (can't point directly to the
69 * FileHandler structure because it could
70 * go away while the event is queued). */
71} FileHandlerEvent;
72
73/*
74 * The following static structure contains the state information for the
75 * select based implementation of the Tcl notifier.
76 */
77
78static struct {
79 FileHandler *firstFileHandlerPtr;
80 /* Pointer to head of file handler list. */
81 fd_mask checkMasks[3*MASK_SIZE];
82 /* This array is used to build up the masks
83 * to be used in the next call to select.
84 * Bits are set in response to calls to
85 * Tcl_CreateFileHandler. */
86 fd_mask readyMasks[3*MASK_SIZE];
87 /* This array reflects the readable/writable
88 * conditions that were found to exist by the
89 * last call to select. */
90 int numFdBits; /* Number of valid bits in checkMasks
91 * (one more than highest fd for which
92 * Tcl_WatchFile has been called). */
93} notifier;
94
95/*
96 * The following static indicates whether this module has been initialized.
97 */
98
99static int initialized = 0;
100
101/*
102 * Static routines defined in this file.
103 */
104
105static void InitNotifier _ANSI_ARGS_((void));
106static void NotifierExitHandler _ANSI_ARGS_((
107 ClientData clientData));
108static int FileHandlerEventProc _ANSI_ARGS_((Tcl_Event *evPtr,
109 int flags));
110
111/*
112 *----------------------------------------------------------------------
113 *
114 * InitNotifier --
115 *
116 * Initializes the notifier state.
117 *
118 * Results:
119 * None.
120 *
121 * Side effects:
122 * Creates a new exit handler.
123 *
124 *----------------------------------------------------------------------
125 */
126
127static void
128InitNotifier()
129{
130 initialized = 1;
131 memset(&notifier, 0, sizeof(notifier));
132 Tcl_CreateExitHandler(NotifierExitHandler, NULL);
133}
134
135/*
136 *----------------------------------------------------------------------
137 *
138 * NotifierExitHandler --
139 *
140 * This function is called to cleanup the notifier state before
141 * Tcl is unloaded.
142 *
143 * Results:
144 * None.
145 *
146 * Side effects:
147 * Destroys the notifier window.
148 *
149 *----------------------------------------------------------------------
150 */
151
152static void
153NotifierExitHandler(clientData)
154 ClientData clientData; /* Not used. */
155{
156 initialized = 0;
157}
158
159/*
160 *----------------------------------------------------------------------
161 *
162 * Tcl_CreateFileHandler --
163 *
164 * This procedure registers a file handler with the Xt notifier.
165 *
166 * Results:
167 * None.
168 *
169 * Side effects:
170 * Creates a new file handler structure and registers one or more
171 * input procedures with Xt.
172 *
173 *----------------------------------------------------------------------
174 */
175
176void
177Tcl_CreateFileHandler(fd, mask, proc, clientData)
178 int fd; /* Handle of stream to watch. */
179 int mask; /* OR'ed combination of TCL_READABLE,
180 * TCL_WRITABLE, and TCL_EXCEPTION:
181 * indicates conditions under which
182 * proc should be called. */
183 Tcl_FileProc *proc; /* Procedure to call for each
184 * selected event. */
185 ClientData clientData; /* Arbitrary data to pass to proc. */
186{
187 FileHandler *filePtr;
188 int index, bit;
189
190 if (!initialized) {
191 InitNotifier();
192 }
193
194 for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL;
195 filePtr = filePtr->nextPtr) {
196 if (filePtr->fd == fd) {
197 break;
198 }
199 }
200 if (filePtr == NULL) {
201 filePtr = (FileHandler*) ckalloc(sizeof(FileHandler)); /* MLK */
202 filePtr->fd = fd;
203 filePtr->readyMask = 0;
204 filePtr->nextPtr = notifier.firstFileHandlerPtr;
205 notifier.firstFileHandlerPtr = filePtr;
206 }
207 filePtr->proc = proc;
208 filePtr->clientData = clientData;
209 filePtr->mask = mask;
210
211 /*
212 * Update the check masks for this file.
213 */
214
215 index = fd/(NBBY*sizeof(fd_mask));
216 bit = 1 << (fd%(NBBY*sizeof(fd_mask)));
217 if (mask & TCL_READABLE) {
218 notifier.checkMasks[index] |= bit;
219 } else {
220 notifier.checkMasks[index] &= ~bit;
221 }
222 if (mask & TCL_WRITABLE) {
223 (notifier.checkMasks+MASK_SIZE)[index] |= bit;
224 } else {
225 (notifier.checkMasks+MASK_SIZE)[index] &= ~bit;
226 }
227 if (mask & TCL_EXCEPTION) {
228 (notifier.checkMasks+2*(MASK_SIZE))[index] |= bit;
229 } else {
230 (notifier.checkMasks+2*(MASK_SIZE))[index] &= ~bit;
231 }
232 if (notifier.numFdBits <= fd) {
233 notifier.numFdBits = fd+1;
234 }
235}
236
237/*
238 *----------------------------------------------------------------------
239 *
240 * Tcl_DeleteFileHandler --
241 *
242 * Cancel a previously-arranged callback arrangement for
243 * a file.
244 *
245 * Results:
246 * None.
247 *
248 * Side effects:
249 * If a callback was previously registered on file, remove it.
250 *
251 *----------------------------------------------------------------------
252 */
253
254void
255Tcl_DeleteFileHandler(fd)
256 int fd; /* Stream id for which to remove callback procedure. */
257{
258 FileHandler *filePtr, *prevPtr;
259 int index, bit, mask, i;
260
261 if (!initialized) {
262 InitNotifier();
263 }
264
265 /*
266 * Find the entry for the given file (and return if there
267 * isn't one).
268 */
269
270 for (prevPtr = NULL, filePtr = notifier.firstFileHandlerPtr; ;
271 prevPtr = filePtr, filePtr = filePtr->nextPtr) {
272 if (filePtr == NULL) {
273 return;
274 }
275 if (filePtr->fd == fd) {
276 break;
277 }
278 }
279
280 /*
281 * Update the check masks for this file.
282 */
283
284 index = fd/(NBBY*sizeof(fd_mask));
285 bit = 1 << (fd%(NBBY*sizeof(fd_mask)));
286
287 if (filePtr->mask & TCL_READABLE) {
288 notifier.checkMasks[index] &= ~bit;
289 }
290 if (filePtr->mask & TCL_WRITABLE) {
291 (notifier.checkMasks+MASK_SIZE)[index] &= ~bit;
292 }
293 if (filePtr->mask & TCL_EXCEPTION) {
294 (notifier.checkMasks+2*(MASK_SIZE))[index] &= ~bit;
295 }
296
297 /*
298 * Find current max fd.
299 */
300
301 if (fd+1 == notifier.numFdBits) {
302 for (notifier.numFdBits = 0; index >= 0; index--) {
303 mask = notifier.checkMasks[index]
304 | (notifier.checkMasks+MASK_SIZE)[index]
305 | (notifier.checkMasks+2*(MASK_SIZE))[index];
306 if (mask) {
307 for (i = (NBBY*sizeof(fd_mask)); i > 0; i--) {
308 if (mask & (1 << (i-1))) {
309 break;
310 }
311 }
312 notifier.numFdBits = index * (NBBY*sizeof(fd_mask)) + i;
313 break;
314 }
315 }
316 }
317
318 /*
319 * Clean up information in the callback record.
320 */
321
322 if (prevPtr == NULL) {
323 notifier.firstFileHandlerPtr = filePtr->nextPtr;
324 } else {
325 prevPtr->nextPtr = filePtr->nextPtr;
326 }
327 ckfree((char *) filePtr);
328}
329
330/*
331 *----------------------------------------------------------------------
332 *
333 * FileHandlerEventProc --
334 *
335 * This procedure is called by Tcl_ServiceEvent when a file event
336 * reaches the front of the event queue. This procedure is
337 * responsible for actually handling the event by invoking the
338 * callback for the file handler.
339 *
340 * Results:
341 * Returns 1 if the event was handled, meaning it should be removed
342 * from the queue. Returns 0 if the event was not handled, meaning
343 * it should stay on the queue. The only time the event isn't
344 * handled is if the TCL_FILE_EVENTS flag bit isn't set.
345 *
346 * Side effects:
347 * Whatever the file handler's callback procedure does.
348 *
349 *----------------------------------------------------------------------
350 */
351
352static int
353FileHandlerEventProc(evPtr, flags)
354 Tcl_Event *evPtr; /* Event to service. */
355 int flags; /* Flags that indicate what events to
356 * handle, such as TCL_FILE_EVENTS. */
357{
358 FileHandler *filePtr;
359 FileHandlerEvent *fileEvPtr = (FileHandlerEvent *) evPtr;
360 int mask;
361
362 if (!(flags & TCL_FILE_EVENTS)) {
363 return 0;
364 }
365
366 /*
367 * Search through the file handlers to find the one whose handle matches
368 * the event. We do this rather than keeping a pointer to the file
369 * handler directly in the event, so that the handler can be deleted
370 * while the event is queued without leaving a dangling pointer.
371 */
372
373 for (filePtr = notifier.firstFileHandlerPtr; filePtr != NULL;
374 filePtr = filePtr->nextPtr) {
375 if (filePtr->fd != fileEvPtr->fd) {
376 continue;
377 }
378
379 /*
380 * The code is tricky for two reasons:
381 * 1. The file handler's desired events could have changed
382 * since the time when the event was queued, so AND the
383 * ready mask with the desired mask.
384 * 2. The file could have been closed and re-opened since
385 * the time when the event was queued. This is why the
386 * ready mask is stored in the file handler rather than
387 * the queued event: it will be zeroed when a new
388 * file handler is created for the newly opened file.
389 */
390
391 mask = filePtr->readyMask & filePtr->mask;
392 filePtr->readyMask = 0;
393 if (mask != 0) {
394 (*filePtr->proc)(filePtr->clientData, mask);
395 }
396 break;
397 }
398 return 1;
399}
400
401/*
402 *----------------------------------------------------------------------
403 *
404 * Tcl_PollSelectEvent --
405 *
406 * This function is called by Tcl_WaitForEvent to wait for new
407 * events on the message queue. If the block time is 0, then
408 * Tcl_WaitForEvent just polls without blocking.
409 *
410 * Results:
411 * Returns 1 if any event handled, 0 otherwise.
412 *
413 * Side effects:
414 * Queues file events that are detected by the select.
415 *
416 *----------------------------------------------------------------------
417 */
418
419int
420Tcl_PollSelectEvent(void)
421{
422 FileHandler *filePtr;
423 FileHandlerEvent *fileEvPtr;
424 struct timeval timeout, *timeoutPtr;
425 int bit, index, mask, numFound;
426
427 if (!initialized) {
428 InitNotifier();
429 }
430
431 /*
432 * Set up the timeout structure.
433 */
434
435 timeout.tv_sec = 0;
436 timeout.tv_usec = 0;
437 timeoutPtr = &timeout;
438
439 memcpy((VOID *) notifier.readyMasks, (VOID *) notifier.checkMasks,
440 3*MASK_SIZE*sizeof(fd_mask));
441 numFound = select(notifier.numFdBits,
442 (SELECT_MASK *) &notifier.readyMasks[0],
443 (SELECT_MASK *) &notifier.readyMasks[MASK_SIZE],
444 (SELECT_MASK *) &notifier.readyMasks[2*MASK_SIZE], timeoutPtr);
445
446 /*
447 * Some systems don't clear the masks after an error, so
448 * we have to do it here.
449 */
450
451 if (numFound == -1) {
452 memset((VOID *) notifier.readyMasks, 0, 3*MASK_SIZE*sizeof(fd_mask));
453 }
454
455 /*
456 * Return if nothing to do.
457 */
458 if ( numFound == 0 )
459 return 0;
460
461 /*
462 * Queue all detected file events before returning.
463 */
464
465 for (filePtr = notifier.firstFileHandlerPtr;
466 (filePtr != NULL) && (numFound > 0);
467 filePtr = filePtr->nextPtr) {
468 index = filePtr->fd / (NBBY*sizeof(fd_mask));
469 bit = 1 << (filePtr->fd % (NBBY*sizeof(fd_mask)));
470 mask = 0;
471
472 if (notifier.readyMasks[index] & bit) {
473 mask |= TCL_READABLE;
474 }
475 if ((notifier.readyMasks+MASK_SIZE)[index] & bit) {
476 mask |= TCL_WRITABLE;
477 }
478 if ((notifier.readyMasks+2*(MASK_SIZE))[index] & bit) {
479 mask |= TCL_EXCEPTION;
480 }
481
482 if (!mask) {
483 continue;
484 } else {
485 numFound--;
486 }
487
488 /*
489 * Don't bother to queue an event if the mask was previously
490 * non-zero since an event must still be on the queue.
491 */
492
493 if (filePtr->readyMask == 0) {
494 fileEvPtr = (FileHandlerEvent *) ckalloc(
495 sizeof(FileHandlerEvent));
496 fileEvPtr->header.proc = FileHandlerEventProc;
497 fileEvPtr->fd = filePtr->fd;
498 Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
499 }
500 filePtr->readyMask = mask;
501 }
502 return 1;
503}