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