/*
* tclKqueueNotfy.c --
*
* This file contains the implementation of the kqueue()-based
* DragonFly/Free/Net/OpenBSD-specific notifier, which is the lowest-
* level part of the Tcl event loop. This file works together with
* generic/tclNotify.c.
*
* Copyright (c) 1995-1997 Sun Microsystems, Inc.
* Copyright (c) 2016 Lucio Andrés Illanes Albornoz <l.illanes@gmx.de>
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
#ifdef NOTIFIER_KQUEUE
#include "tclInt.h"
#ifndef HAVE_COREFOUNDATION /* Darwin/Mac OS X CoreFoundation notifier is
* in tclMacOSXNotify.c */
#include <signal.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/queue.h>
#include <sys/time.h>
/*
* This structure is used to keep track of the notifier info for a registered
* file.
*/
struct PlatformEventData;
typedef struct FileHandler {
int fd;
int mask; /* Mask of desired events: TCL_READABLE,
* etc. */
int readyMask; /* Mask of events that have been seen since
* the last time file handlers were invoked
* for this file. */
Tcl_FileProc *proc; /* Function to call, in the style of
* Tcl_CreateFileHandler. */
ClientData clientData; /* Argument to pass to proc. */
struct FileHandler *nextPtr;/* Next in list of all files we care about. */
LIST_ENTRY(FileHandler) readyNode;
/* Next/previous in list of FileHandlers asso-
* ciated with regular files (S_IFREG) that are
* ready for I/O. */
struct PlatformEventData *pedPtr;
/* Pointer to PlatformEventData associating this
* FileHandler with kevent(2) events. */
} FileHandler;
/*
* The following structure associates a FileHandler and the thread that owns it
* with the file descriptors of interest and their event masks passed to kevent(2)
* and their corresponding event(s) returned by kevent(2).
*/
struct ThreadSpecificData;
struct PlatformEventData {
FileHandler *filePtr;
struct ThreadSpecificData *tsdPtr;
};
/*
* The following structure is what is added to the Tcl event queue when file
* handlers are ready to fire.
*/
typedef struct {
Tcl_Event header; /* Information that is standard for all
* events. */
int fd; /* File descriptor that is ready. Used to find
* the FileHandler structure for the file
* (can't point directly to the FileHandler
* structure because it could go away while
* the event is queued). */
} FileHandlerEvent;
/*
* The following static structure contains the state information for the
* kqueue based implementation of the Tcl notifier. One of these structures is
* created for each thread that is using the notifier.
*/
LIST_HEAD(PlatformReadyFileHandlerList, FileHandler);
typedef struct ThreadSpecificData {
FileHandler *firstFileHandlerPtr;
/* Pointer to head of file handler list. */
struct PlatformReadyFileHandlerList firstReadyFileHandlerPtr;
/* Pointer to head of list of FileHandlers
* associated with regular files (S_IFREG)
* that are ready for I/O. */
pthread_mutex_t notifierMutex;
/* Mutex protecting notifier termination in
* PlatformEventsFinalize. */
int triggerPipe[2]; /* pipe(2) used by other threads to wake
* up this thread for inter-thread IPC. */
int eventsFd; /* kqueue(2) file descriptor used to wait for fds. */
struct kevent *readyEvents; /* Pointer to at most maxReadyEvents events
* returned by kevent(2). */
size_t maxReadyEvents; /* Count of kevents in readyEvents. */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;
void PlatformEventsControl(FileHandler *filePtr, ThreadSpecificData *tsdPtr, int op, int isNew);
static void PlatformEventsFinalize(void);
void PlatformEventsInit(void);
static int PlatformEventsTranslate(struct kevent *eventPtr);
static int PlatformEventsWait(struct kevent *events, size_t numEvents, struct timeval *timePtr);
#include "tclUnixNotfy.c"
/*
*----------------------------------------------------------------------
*
* Tcl_InitNotifier --
*
* Initializes the platform specific notifier state.
*
* Results:
* Returns a handle to the notifier state for this thread.
*
* Side effects:
* If no initNotifierProc notifier hook exists, PlatformEventsInit
* is called.
*
*----------------------------------------------------------------------
*/
ClientData
Tcl_InitNotifier(void)
{
if (tclNotifierHooks.initNotifierProc) {
return tclNotifierHooks.initNotifierProc();
} else {
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
PlatformEventsInit();
return tsdPtr;
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_FinalizeNotifier --
*
* This function is called to cleanup the notifier state before a thread
* is terminated.
*
* Results:
* None.
*
* Side effects:
* If no finalizeNotifierProc notifier hook exists, PlatformEvents-
* Finalize is called.
*
*----------------------------------------------------------------------
*/
void
Tcl_FinalizeNotifier(
ClientData clientData) /* Not used. */
{
if (tclNotifierHooks.finalizeNotifierProc) {
tclNotifierHooks.finalizeNotifierProc(clientData);
return;
} else {
PlatformEventsFinalize();
}
}
/*
*----------------------------------------------------------------------
*
* PlatformEventsControl --
*
* This function registers interest for the file descriptor and the mask
* of TCL_* bits associated with filePtr on the kqueue file descriptor
* associated with tsdPtr.
* Future calls to kevent will return filePtr and tsdPtr alongside with
* the event registered here via the PlatformEventData struct.
*
* Results:
* None.
*
* Side effects:
* If adding a new file descriptor, a PlatformEventData struct will be
* allocated and associated with filePtr.
* fstat is called on the file descriptor; if it is associated with
* a regular file (S_IFREG,) filePtr is considered to be ready for I/O
* and added to or deleted from the corresponding list in tsdPtr.
* If it is not associated with a regular file, the file descriptor is
* added, modified concerning its mask of events of interest, or deleted
* from the epoll file descriptor of the calling thread.
* If deleting a file descriptor, kevent(2) is called twice specifying
* EVFILT_READ first and then EVFILT_WRITE (see note below.)
*
*----------------------------------------------------------------------
*/
void
PlatformEventsControl(
FileHandler *filePtr,
ThreadSpecificData *tsdPtr,
int op,
int isNew)
{
int numChanges;
struct kevent changeList[2];
struct PlatformEventData *newPedPtr;
struct stat fdStat;
if (isNew) {
newPedPtr = ckalloc(sizeof(*newPedPtr));
newPedPtr->filePtr = filePtr;
newPedPtr->tsdPtr = tsdPtr;
filePtr->pedPtr = newPedPtr;
}
/*
* N.B. As discussed in Tcl_WaitForEvent(), kqueue(2) does not repro-
* duce the `always ready' {select,poll}(2) behaviour for regular
* files (S_IFREG) prior to FreeBSD 11.0-RELEASE. Therefore, file-
* Ptr is in these cases simply added or deleted from the list of
* FileHandlers associated with regular files belonging to tsdPtr.
*/
if (fstat(filePtr->fd, &fdStat) == -1) {
Tcl_Panic("fstat: %s", strerror(errno));
} else if ((fdStat.st_mode & S_IFMT) == S_IFREG) {
switch (op) {
case EV_ADD:
if (isNew) {
LIST_INSERT_HEAD(&tsdPtr->firstReadyFileHandlerPtr, filePtr, readyNode);
}
break;
case EV_DELETE:
LIST_REMOVE(filePtr, readyNode);
break;
}
return;
}
numChanges = 0;
switch (op) {
case EV_ADD:
if (filePtr->mask & (TCL_READABLE | TCL_EXCEPTION)) {
EV_SET(&changeList[numChanges], (uintptr_t)filePtr->fd, EVFILT_READ,
op, 0, 0, filePtr->pedPtr);
numChanges++;
}
if (filePtr->mask & TCL_WRITABLE) {
EV_SET(&changeList[numChanges], (uintptr_t)filePtr->fd, EVFILT_WRITE,
op, 0, 0, filePtr->pedPtr);
numChanges++;
}
if (numChanges) {
if (kevent(tsdPtr->eventsFd, changeList, numChanges, NULL, 0, NULL) == -1) {
Tcl_Panic("kevent: %s", strerror(errno));
}
}
break;
case EV_DELETE:
/*
* N.B. kqueue(2) has separate filters for readability and writabi-
* lity fd events. We therefore need to ensure that fds are
* ompletely removed from the kqueue(2) fd when deleting.
* This is exacerbated by changes to filePtr->mask w/o calls
* to PlatforEventsControl() after e.g. an exec(3) in a child
* process.
* As one of these calls can fail, two separate kevent(2) calls
* are made for EVFILT_{READ,WRITE}.
*/
EV_SET(&changeList[0], (uintptr_t)filePtr->fd, EVFILT_READ, op, 0, 0, NULL);
if ((kevent(tsdPtr->eventsFd, changeList, 1, NULL, 0, NULL) == -1)
&& (errno != ENOENT)) {
Tcl_Panic("kevent: %s", strerror(errno));
}
EV_SET(&changeList[0], (uintptr_t)filePtr->fd, EVFILT_WRITE, op, 0, 0, NULL);
if ((kevent(tsdPtr->eventsFd, changeList, 1, NULL, 0, NULL) == -1)
&& (errno != ENOENT)) {
Tcl_Panic("kevent: %s", strerror(errno));
}
break;
}
}
/*
*----------------------------------------------------------------------
*
* PlatformEventsFinalize --
*
* This function closes the pipe and the kqueue file descriptors
* and frees the kevent structs owned by the thread of the caller.
* The above operations are protected by tsdPtr->notifierMutex, which
* is destroyed thereafter.
*
* Results:
* None.
*
* Side effects:
* While tsdPtr->notifierMutex is held:
* The per-thread pipe(2) fds are closed, if non-zero, and set to -1.
* The per-thread kqueue(2) fd is closed, if non-zero, and set to 0.
* The per-thread kevent structs are freed, if any, and set to 0.
*
* tsdPtr->notifierMutex is destroyed.
*
*----------------------------------------------------------------------
*/
void
PlatformEventsFinalize(
void)
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
pthread_mutex_lock(&tsdPtr->notifierMutex);
if (tsdPtr->triggerPipe[0]) {
close(tsdPtr->triggerPipe[0]);
tsdPtr->triggerPipe[0] = -1;
}
if (tsdPtr->triggerPipe[1]) {
close(tsdPtr->triggerPipe[1]);
tsdPtr->triggerPipe[1] = -1;
}
if (tsdPtr->eventsFd > 0) {
close(tsdPtr->eventsFd);
tsdPtr->eventsFd = 0;
}
if (tsdPtr->readyEvents) {
ckfree(tsdPtr->readyEvents);
tsdPtr->maxReadyEvents = 0;
}
pthread_mutex_unlock(&tsdPtr->notifierMutex);
if ((errno = pthread_mutex_destroy(&tsdPtr->notifierMutex))) {
Tcl_Panic("pthread_mutex_destroy: %s", strerror(errno));
}
}
/*
*----------------------------------------------------------------------
*
* PlatformEventsInit --
*
* This function abstracts creating a kqueue fd via the kqueue
* system call and allocating memory for the kevents structs in
* tsdPtr for the thread of the caller.
*
* Results:
* None.
*
* Side effects:
* The following per-thread entities are initialised:
* notifierMutex is initialised.
* The pipe(2) is created; fcntl(2) is called on both fds to set
* FD_CLOEXEC and O_NONBLOCK.
* The kqueue(2) fd is created; fcntl(2) is called on it to set
* FD_CLOEXEC.
* A FileHandler struct is allocated and initialised for the event-
* fd(2), registering interest for TCL_READABLE on it via Platform-
* EventsControl().
* readyEvents and maxReadyEvents are initialised with 512 kevents.
*----------------------------------------------------------------------
*/
void
PlatformEventsInit(
void)
{
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
int i, fdFl;
FileHandler *filePtr;
errno = pthread_mutex_init(&tsdPtr->notifierMutex, NULL);
if (errno) {
Tcl_Panic("Tcl_InitNotifier: %s", "could not create mutex");
}
if (pipe(tsdPtr->triggerPipe) != 0) {
Tcl_Panic("Tcl_InitNotifier: %s", "could not create trigger pipe");
} else for (i = 0; i < 2; i++) {
if (fcntl(tsdPtr->triggerPipe[i], F_SETFD, FD_CLOEXEC) == -1) {
Tcl_Panic("fcntl: %s", strerror(errno));
} else {
fdFl = fcntl(tsdPtr->triggerPipe[i], F_GETFL);
fdFl |= O_NONBLOCK;
}
if (fcntl(tsdPtr->triggerPipe[i], F_SETFL, fdFl) == -1) {
Tcl_Panic("fcntl: %s", strerror(errno));
}
}
if ((tsdPtr->eventsFd = kqueue()) == -1) {
Tcl_Panic("kqueue: %s", strerror(errno));
} else if (fcntl(tsdPtr->eventsFd, F_SETFD, FD_CLOEXEC) == -1) {
Tcl_Panic("fcntl: %s", strerror(errno));
}
filePtr = ckalloc(sizeof(*filePtr));
filePtr->fd = tsdPtr->triggerPipe[0];
filePtr->mask = TCL_READABLE;
PlatformEventsControl(filePtr, tsdPtr, EV_ADD, 1);
if (!tsdPtr->readyEvents) {
tsdPtr->maxReadyEvents = 512;
tsdPtr->readyEvents = ckalloc(tsdPtr->maxReadyEvents
* sizeof(tsdPtr->readyEvents[0]));
}
LIST_INIT(&tsdPtr->firstReadyFileHandlerPtr);
}
/*
*----------------------------------------------------------------------
*
* PlatformEventsTranslate --
*
* This function translates the platform-specific mask of returned
* events in eventPtr to a mask of TCL_* bits.
*
* Results:
* Returns the translated mask.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
int
PlatformEventsTranslate(
struct kevent *eventPtr)
{
int mask;
mask = 0;
if (eventPtr->filter == EVFILT_READ) {
mask |= TCL_READABLE;
if (eventPtr->flags & EV_ERROR) {
mask |= TCL_EXCEPTION;
}
}
if (eventPtr->filter == EVFILT_WRITE) {
mask |= TCL_WRITABLE;
if (eventPtr->flags & EV_ERROR) {
mask |= TCL_EXCEPTION;
}
}
return mask;
}
/*
*----------------------------------------------------------------------
*
* PlatformEventsWait --
*
* This function abstracts waiting for I/O events via the kevent
* system call.
*
* Results:
* Returns -1 if kevent failed. Returns 0 if polling and if no events
* became available whilst polling. Returns a pointer to and the count
* of all returned events in all other cases.
*
* Side effects:
* gettimeofday(2), kevent(2), and gettimeofday(2) are called,
* in the specified order.
* If timePtr specifies a positive value, it is updated to reflect
* the amount of time that has passed; if its value would {under,
* over}flow, it is set to zero.
*
*----------------------------------------------------------------------
*/
int
PlatformEventsWait(
struct kevent *events,
size_t numEvents,
struct timeval *timePtr)
{
int numFound;
struct timeval tv0, tv1, tv_delta;
struct timespec timeout, *timeoutPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
* If timePtr is NULL, kevent(2) will wait indefinitely. If it speci-
* fies a timeout of {0,0}, kevent(2) will poll. Otherwise, the time-
* out will simply be converted to a timespec.
*/
if (!timePtr) {
timeoutPtr = NULL;
} else if (!timePtr->tv_sec && !timePtr->tv_usec) {
timeout.tv_sec = 0;
timeout.tv_nsec = 0;
timeoutPtr = &timeout;
} else {
timeout.tv_sec = timePtr->tv_sec;
timeout.tv_nsec = timePtr->tv_usec * 1000;
timeoutPtr = &timeout;
}
/*
* Call (and possibly block on) kevent(2) and substract the delta of
* gettimeofday(2) before and after the call from timePtr if the latter
* is not NULL. Return the number of events returned by kevent(2).
*/
gettimeofday(&tv0, NULL);
numFound = kevent(tsdPtr->eventsFd, NULL, 0, events, (int)numEvents, timeoutPtr);
gettimeofday(&tv1, NULL);
if (timePtr && (timePtr->tv_sec && timePtr->tv_usec)) {
timersub(&tv1, &tv0, &tv_delta);
if (!timercmp(&tv_delta, timePtr, >)) {
timersub(timePtr, &tv_delta, timePtr);
} else {
timePtr->tv_sec = 0;
timePtr->tv_usec = 0;
}
}
return numFound;
}
/*
*----------------------------------------------------------------------
*
* Tcl_CreateFileHandler --
*
* This function registers a file handler with the kqueue notifier
* of the thread of the caller.
*
* Results:
* None.
*
* Side effects:
* Creates a new file handler structure.
* PlatformEventsControl() is called for the new file handler structure.
*
*----------------------------------------------------------------------
*/
void
Tcl_CreateFileHandler(
int fd, /* Handle of stream to watch. */
int mask, /* OR'ed combination of TCL_READABLE,
* TCL_WRITABLE, and TCL_EXCEPTION: indicates
* conditions under which proc should be
* called. */
Tcl_FileProc *proc, /* Function to call for each selected
* event. */
ClientData clientData) /* Arbitrary data to pass to proc. */
{
int isNew;
if (tclNotifierHooks.createFileHandlerProc) {
tclNotifierHooks.createFileHandlerProc(fd, mask, proc, clientData);
return;
} else {
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
FileHandler *filePtr;
for (filePtr = tsdPtr->firstFileHandlerPtr; filePtr != NULL;
filePtr = filePtr->nextPtr) {
if (filePtr->fd == fd) {
break;
}
}
if (filePtr == NULL) {
filePtr = ckalloc(sizeof(FileHandler));
filePtr->fd = fd;
filePtr->readyMask = 0;
filePtr->nextPtr = tsdPtr->firstFileHandlerPtr;
tsdPtr->firstFileHandlerPtr = filePtr;
isNew = 1;
} else {
isNew = 0;
}
filePtr->proc = proc;
filePtr->clientData = clientData;
filePtr->mask = mask;
PlatformEventsControl(filePtr, tsdPtr, EV_ADD, isNew);
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_DeleteFileHandler --
*
* Cancel a previously-arranged callback arrangement for a file on
* the kqueue of the thread of the caller.
*
* Results:
* None.
*
* Side effects:
* If a callback was previously registered on file, remove it.
* PlatformEventsControl() is called for the file handler structure.
* The PlatformEventData struct associated with the new file handler
* structure is freed.
*
*----------------------------------------------------------------------
*/
void
Tcl_DeleteFileHandler(
int fd) /* Stream id for which to remove callback
* function. */
{
if (tclNotifierHooks.deleteFileHandlerProc) {
tclNotifierHooks.deleteFileHandlerProc(fd);
return;
} else {
FileHandler *filePtr, *prevPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
* Find the entry for the given file (and return if there isn't one).
*/
for (prevPtr = NULL, filePtr = tsdPtr->firstFileHandlerPtr; ;
prevPtr = filePtr, filePtr = filePtr->nextPtr) {
if (filePtr == NULL) {
return;
}
if (filePtr->fd == fd) {
break;
}
}
/*
* Update the check masks for this file.
*/
PlatformEventsControl(filePtr, tsdPtr, EV_DELETE, 0);
if (filePtr->pedPtr) {
ckfree(filePtr->pedPtr);
}
/*
* Clean up information in the callback record.
*/
if (prevPtr == NULL) {
tsdPtr->firstFileHandlerPtr = filePtr->nextPtr;
} else {
prevPtr->nextPtr = filePtr->nextPtr;
}
ckfree(filePtr);
}
}
/*
*----------------------------------------------------------------------
*
* Tcl_WaitForEvent --
*
* This function is called by Tcl_DoOneEvent to wait for new events on
* the message queue. If the block time is 0, then Tcl_WaitForEvent just
* polls without blocking.
* The waiting logic is implemented in PlatformEventsWait.
*
* Results:
* Returns -1 if PlatformEventsWait() would block forever, otherwise
* returns 0.
*
* Side effects:
* Queues file events that are detected by PlatformEventsWait().
*
*----------------------------------------------------------------------
*/
int
Tcl_WaitForEvent(
const Tcl_Time *timePtr) /* Maximum block time, or NULL. */
{
if (tclNotifierHooks.waitForEventProc) {
return tclNotifierHooks.waitForEventProc(timePtr);
} else {
FileHandler *filePtr;
int mask;
Tcl_Time vTime;
/*
* Impl. notes: timeout & timeoutPtr are used if, and only if threads
* are not enabled. They are the arguments for the regular epoll_wait()
* used when the core is not thread-enabled.
*/
struct timeval timeout, *timeoutPtr;
int numFound, numEvent;
struct PlatformEventData *pedPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
int numQueued;
ssize_t i;
char buf[1];
/*
* Set up the timeout structure. Note that if there are no events to
* check for, we return with a negative result rather than blocking
* forever.
*/
if (timePtr != NULL) {
/*
* TIP #233 (Virtualized Time). Is virtual time in effect? And do
* we actually have something to scale? If yes to both then we
* call the handler to do this scaling.
*/
if (timePtr->sec != 0 || timePtr->usec != 0) {
vTime = *timePtr;
tclScaleTimeProcPtr(&vTime, tclTimeClientData);
timePtr = &vTime;
}
timeout.tv_sec = timePtr->sec;
timeout.tv_usec = timePtr->usec;
timeoutPtr = &timeout;
} else {
timeoutPtr = NULL;
}
/*
* Walk the list of FileHandlers associated with regular files
* (S_IFREG) belonging to tsdPtr, queue Tcl events for them, and
* update their mask of events of interest.
* kqueue(2), unlike epoll(7), does support regular files, but
* EVFILT_READ only `[r]eturns when the file pointer is not at
* the end of file' as opposed to unconditionally. While FreeBSD
* 11.0-RELEASE adds support for this mode (NOTE_FILE_POLL,) this
* is not used for reasons of compatibility.
* Therefore, the behaviour of {select,poll}(2) is simply simulated
* here: fds associated with regular files are added to this list by
* PlatformEventsControl() and processed here before calling (and
* possibly blocking) on PlatformEventsWait().
*/
numQueued = 0;
LIST_FOREACH(filePtr, &tsdPtr->firstReadyFileHandlerPtr, readyNode) {
mask = 0;
if (filePtr->mask & TCL_READABLE) {
mask |= TCL_READABLE;
}
if (filePtr->mask & TCL_WRITABLE) {
mask |= TCL_WRITABLE;
}
/*
* Don't bother to queue an event if the mask was previously
* non-zero since an event must still be on the queue.
*/
if (filePtr->readyMask == 0) {
FileHandlerEvent *fileEvPtr =
ckalloc(sizeof(FileHandlerEvent));
fileEvPtr->header.proc = FileHandlerEventProc;
fileEvPtr->fd = filePtr->fd;
Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
numQueued++;
}
filePtr->readyMask = mask;
}
/*
* If any events were queued in the above loop, force PlatformEvents-
* Wait() to poll as there already are events that need to be processed
* at this point.
*/
if (numQueued) {
timeout.tv_sec = 0;
timeout.tv_usec = 0;
timeoutPtr = &timeout;
}
/*
* Wait or poll for new events, queue Tcl events for the FileHandlers
* corresponding to them, and update the FileHandlers' mask of events
* of interest registered by the last call to Tcl_CreateFileHandler().
*
* Events for the trigger pipe are processed here in order to facilitate
* inter-thread IPC. If another thread intends to wake up this thread
* whilst it's blocking on PlatformEventsWait(), it write(2)s to the
* other end of the pipe (see Tcl_AlertNotifier(),) which in turn will
* cause PlatformEventsWait() to return immediately.
*/
numFound = PlatformEventsWait(tsdPtr->readyEvents, tsdPtr->maxReadyEvents, timeoutPtr);
for (numEvent = 0; numEvent < numFound; numEvent++) {
pedPtr = (struct PlatformEventData *)tsdPtr->readyEvents[numEvent].udata;
filePtr = pedPtr->filePtr;
mask = PlatformEventsTranslate(&tsdPtr->readyEvents[numEvent]);
if (filePtr->fd == tsdPtr->triggerPipe[0]) {
do {
i = read(tsdPtr->triggerPipe[0], buf, 1);
if ((i == -1) && (errno != EAGAIN)) {
Tcl_Panic("Tcl_WaitForEvent: "
"read from %p->triggerPipe: %s",
(void *)tsdPtr, strerror(errno));
} else {
break;
}
} while (1);
continue;
}
if (!mask) {
continue;
}
/*
* Don't bother to queue an event if the mask was previously
* non-zero since an event must still be on the queue.
*/
if (filePtr->readyMask == 0) {
FileHandlerEvent *fileEvPtr =
ckalloc(sizeof(FileHandlerEvent));
fileEvPtr->header.proc = FileHandlerEventProc;
fileEvPtr->fd = filePtr->fd;
Tcl_QueueEvent((Tcl_Event *) fileEvPtr, TCL_QUEUE_TAIL);
}
filePtr->readyMask |= mask;
}
return 0;
}
}
#endif /* !HAVE_COREFOUNDATION */
#endif /* NOTIFIER_KQUEUE */
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/