/* * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC") * Copyright (c) 1995-1999 by Internet Software Consortium * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* eventlib.c - implement glue for the eventlib * vix 09sep95 [initial] */ #if !defined(LINT) && !defined(CODECENTER) static const char rcsid[] = "$Id: eventlib.c,v 1.10 2006/03/09 23:57:56 marka Exp $"; #endif #include "port_before.h" #include "fd_setsize.h" #include #include #include #ifdef SOLARIS2 #include #endif /* SOLARIS2 */ #include #include #include #include #include #include #include #include "eventlib_p.h" #include "port_after.h" int __evOptMonoTime; #ifdef USE_POLL #define pselect Pselect #endif /* USE_POLL */ /* Forward. */ #if defined(NEED_PSELECT) || defined(USE_POLL) static int pselect(int, void *, void *, void *, struct timespec *, const sigset_t *); #endif int __evOptMonoTime; /* Public. */ int evCreate(evContext *opaqueCtx) { evContext_p *ctx; /* Make sure the memory heap is initialized. */ if (meminit(0, 0) < 0 && errno != EEXIST) return (-1); OKNEW(ctx); /* Global. */ ctx->cur = NULL; /* Debugging. */ ctx->debug = 0; ctx->output = NULL; /* Connections. */ ctx->conns = NULL; INIT_LIST(ctx->accepts); /* Files. */ ctx->files = NULL; #ifdef USE_POLL ctx->pollfds = NULL; ctx->maxnfds = 0; ctx->firstfd = 0; emulMaskInit(ctx, rdLast, EV_READ, 1); emulMaskInit(ctx, rdNext, EV_READ, 0); emulMaskInit(ctx, wrLast, EV_WRITE, 1); emulMaskInit(ctx, wrNext, EV_WRITE, 0); emulMaskInit(ctx, exLast, EV_EXCEPT, 1); emulMaskInit(ctx, exNext, EV_EXCEPT, 0); emulMaskInit(ctx, nonblockBefore, EV_WASNONBLOCKING, 0); #endif /* USE_POLL */ FD_ZERO(&ctx->rdNext); FD_ZERO(&ctx->wrNext); FD_ZERO(&ctx->exNext); FD_ZERO(&ctx->nonblockBefore); ctx->fdMax = -1; ctx->fdNext = NULL; ctx->fdCount = 0; /*%< Invalidate {rd,wr,ex}Last. */ #ifndef USE_POLL ctx->highestFD = FD_SETSIZE - 1; memset(ctx->fdTable, 0, sizeof ctx->fdTable); #else ctx->highestFD = INT_MAX / sizeof(struct pollfd); ctx->fdTable = NULL; #endif /* USE_POLL */ #ifdef EVENTLIB_TIME_CHECKS ctx->lastFdCount = 0; #endif /* Streams. */ ctx->streams = NULL; ctx->strDone = NULL; ctx->strLast = NULL; /* Timers. */ ctx->lastEventTime = evNowTime(); #ifdef EVENTLIB_TIME_CHECKS ctx->lastSelectTime = ctx->lastEventTime; #endif ctx->timers = evCreateTimers(ctx); if (ctx->timers == NULL) return (-1); /* Waits. */ ctx->waitLists = NULL; ctx->waitDone.first = ctx->waitDone.last = NULL; ctx->waitDone.prev = ctx->waitDone.next = NULL; opaqueCtx->opaque = ctx; return (0); } void evSetDebug(evContext opaqueCtx, int level, FILE *output) { evContext_p *ctx = opaqueCtx.opaque; ctx->debug = level; ctx->output = output; } int evDestroy(evContext opaqueCtx) { evContext_p *ctx = opaqueCtx.opaque; int revs = 424242; /*%< Doug Adams. */ evWaitList *this_wl, *next_wl; evWait *this_wait, *next_wait; /* Connections. */ while (revs-- > 0 && ctx->conns != NULL) { evConnID id; id.opaque = ctx->conns; (void) evCancelConn(opaqueCtx, id); } INSIST(revs >= 0); /* Streams. */ while (revs-- > 0 && ctx->streams != NULL) { evStreamID id; id.opaque = ctx->streams; (void) evCancelRW(opaqueCtx, id); } /* Files. */ while (revs-- > 0 && ctx->files != NULL) { evFileID id; id.opaque = ctx->files; (void) evDeselectFD(opaqueCtx, id); } INSIST(revs >= 0); /* Timers. */ evDestroyTimers(ctx); /* Waits. */ for (this_wl = ctx->waitLists; revs-- > 0 && this_wl != NULL; this_wl = next_wl) { next_wl = this_wl->next; for (this_wait = this_wl->first; revs-- > 0 && this_wait != NULL; this_wait = next_wait) { next_wait = this_wait->next; FREE(this_wait); } FREE(this_wl); } for (this_wait = ctx->waitDone.first; revs-- > 0 && this_wait != NULL; this_wait = next_wait) { next_wait = this_wait->next; FREE(this_wait); } FREE(ctx); return (0); } int evGetNext(evContext opaqueCtx, evEvent *opaqueEv, int options) { evContext_p *ctx = opaqueCtx.opaque; struct timespec nextTime; evTimer *nextTimer; evEvent_p *new; int x, pselect_errno, timerPast; #ifdef EVENTLIB_TIME_CHECKS struct timespec interval; #endif /* Ensure that exactly one of EV_POLL or EV_WAIT was specified. */ x = ((options & EV_POLL) != 0) + ((options & EV_WAIT) != 0); if (x != 1) EV_ERR(EINVAL); /* Get the time of day. We'll do this again after select() blocks. */ ctx->lastEventTime = evNowTime(); again: /* Finished accept()'s do not require a select(). */ if (!EMPTY(ctx->accepts)) { OKNEW(new); new->type = Accept; new->u.accept.this = HEAD(ctx->accepts); UNLINK(ctx->accepts, HEAD(ctx->accepts), link); opaqueEv->opaque = new; return (0); } /* Stream IO does not require a select(). */ if (ctx->strDone != NULL) { OKNEW(new); new->type = Stream; new->u.stream.this = ctx->strDone; ctx->strDone = ctx->strDone->nextDone; if (ctx->strDone == NULL) ctx->strLast = NULL; opaqueEv->opaque = new; return (0); } /* Waits do not require a select(). */ if (ctx->waitDone.first != NULL) { OKNEW(new); new->type = Wait; new->u.wait.this = ctx->waitDone.first; ctx->waitDone.first = ctx->waitDone.first->next; if (ctx->waitDone.first == NULL) ctx->waitDone.last = NULL; opaqueEv->opaque = new; return (0); } /* Get the status and content of the next timer. */ if ((nextTimer = heap_element(ctx->timers, 1)) != NULL) { nextTime = nextTimer->due; timerPast = (evCmpTime(nextTime, ctx->lastEventTime) <= 0); } else timerPast = 0; /*%< Make gcc happy. */ evPrintf(ctx, 9, "evGetNext: fdCount %d\n", ctx->fdCount); if (ctx->fdCount == 0) { static const struct timespec NoTime = {0, 0L}; enum { JustPoll, Block, Timer } m; struct timespec t, *tp; /* Are there any events at all? */ if ((options & EV_WAIT) != 0 && !nextTimer && ctx->fdMax == -1) EV_ERR(ENOENT); /* Figure out what select()'s timeout parameter should be. */ if ((options & EV_POLL) != 0) { m = JustPoll; t = NoTime; tp = &t; } else if (nextTimer == NULL) { m = Block; /* ``t'' unused. */ tp = NULL; } else if (timerPast) { m = JustPoll; t = NoTime; tp = &t; } else { m = Timer; /* ``t'' filled in later. */ tp = &t; } #ifdef EVENTLIB_TIME_CHECKS if (ctx->debug > 0) { interval = evSubTime(ctx->lastEventTime, ctx->lastSelectTime); if (interval.tv_sec > 0 || interval.tv_nsec > 0) evPrintf(ctx, 1, "time between pselect() %u.%09u count %d\n", interval.tv_sec, interval.tv_nsec, ctx->lastFdCount); } #endif do { #ifndef USE_POLL /* XXX need to copy only the bits we are using. */ ctx->rdLast = ctx->rdNext; ctx->wrLast = ctx->wrNext; ctx->exLast = ctx->exNext; #else /* * The pollfd structure uses separate fields for * the input and output events (corresponding to * the ??Next and ??Last fd sets), so there's no * need to copy one to the other. */ #endif /* USE_POLL */ if (m == Timer) { INSIST(tp == &t); t = evSubTime(nextTime, ctx->lastEventTime); } /* XXX should predict system's earliness and adjust. */ x = pselect(ctx->fdMax+1, &ctx->rdLast, &ctx->wrLast, &ctx->exLast, tp, NULL); pselect_errno = errno; #ifndef USE_POLL evPrintf(ctx, 4, "select() returns %d (err: %s)\n", x, (x == -1) ? strerror(errno) : "none"); #else evPrintf(ctx, 4, "poll() returns %d (err: %s)\n", x, (x == -1) ? strerror(errno) : "none"); #endif /* USE_POLL */ /* Anything but a poll can change the time. */ if (m != JustPoll) ctx->lastEventTime = evNowTime(); /* Select() likes to finish about 10ms early. */ } while (x == 0 && m == Timer && evCmpTime(ctx->lastEventTime, nextTime) < 0); #ifdef EVENTLIB_TIME_CHECKS ctx->lastSelectTime = ctx->lastEventTime; #endif if (x < 0) { if (pselect_errno == EINTR) { if ((options & EV_NULL) != 0) goto again; OKNEW(new); new->type = Null; /* No data. */ opaqueEv->opaque = new; return (0); } if (pselect_errno == EBADF) { for (x = 0; x <= ctx->fdMax; x++) { struct stat sb; if (FD_ISSET(x, &ctx->rdNext) == 0 && FD_ISSET(x, &ctx->wrNext) == 0 && FD_ISSET(x, &ctx->exNext) == 0) continue; if (fstat(x, &sb) == -1 && errno == EBADF) evPrintf(ctx, 1, "EBADF: %d\n", x); } abort(); } EV_ERR(pselect_errno); } if (x == 0 && (nextTimer == NULL || !timerPast) && (options & EV_POLL)) EV_ERR(EWOULDBLOCK); ctx->fdCount = x; #ifdef EVENTLIB_TIME_CHECKS ctx->lastFdCount = x; #endif } INSIST(nextTimer || ctx->fdCount); /* Timers go first since we'd like them to be accurate. */ if (nextTimer && !timerPast) { /* Has anything happened since we blocked? */ timerPast = (evCmpTime(nextTime, ctx->lastEventTime) <= 0); } if (nextTimer && timerPast) { OKNEW(new); new->type = Timer; new->u.timer.this = nextTimer; opaqueEv->opaque = new; return (0); } /* No timers, so there should be a ready file descriptor. */ x = 0; while (ctx->fdCount > 0) { evFile *fid; int fd, eventmask; if (ctx->fdNext == NULL) { if (++x == 2) { /* * Hitting the end twice means that the last * select() found some FD's which have since * been deselected. * * On some systems, the count returned by * selects is the total number of bits in * all masks that are set, and on others it's * the number of fd's that have some bit set, * and on others, it's just broken. We * always assume that it's the number of * bits set in all masks, because that's what * the man page says it should do, and * the worst that can happen is we do an * extra select(). */ ctx->fdCount = 0; break; } ctx->fdNext = ctx->files; } fid = ctx->fdNext; ctx->fdNext = fid->next; fd = fid->fd; eventmask = 0; if (FD_ISSET(fd, &ctx->rdLast)) eventmask |= EV_READ; if (FD_ISSET(fd, &ctx->wrLast)) eventmask |= EV_WRITE; if (FD_ISSET(fd, &ctx->exLast)) eventmask |= EV_EXCEPT; eventmask &= fid->eventmask; if (eventmask != 0) { if ((eventmask & EV_READ) != 0) { FD_CLR(fd, &ctx->rdLast); ctx->fdCount--; } if ((eventmask & EV_WRITE) != 0) { FD_CLR(fd, &ctx->wrLast); ctx->fdCount--; } if ((eventmask & EV_EXCEPT) != 0) { FD_CLR(fd, &ctx->exLast); ctx->fdCount--; } OKNEW(new); new->type = File; new->u.file.this = fid; new->u.file.eventmask = eventmask; opaqueEv->opaque = new; return (0); } } if (ctx->fdCount < 0) { /* * select()'s count is off on a number of systems, and * can result in fdCount < 0. */ evPrintf(ctx, 4, "fdCount < 0 (%d)\n", ctx->fdCount); ctx->fdCount = 0; } /* We get here if the caller deselect()'s an FD. Gag me with a goto. */ goto again; } int evDispatch(evContext opaqueCtx, evEvent opaqueEv) { evContext_p *ctx = opaqueCtx.opaque; evEvent_p *ev = opaqueEv.opaque; #ifdef EVENTLIB_TIME_CHECKS void *func; struct timespec start_time; struct timespec interval; #endif #ifdef EVENTLIB_TIME_CHECKS if (ctx->debug > 0) start_time = evNowTime(); #endif ctx->cur = ev; switch (ev->type) { case Accept: { evAccept *this = ev->u.accept.this; evPrintf(ctx, 5, "Dispatch.Accept: fd %d -> %d, func %p, uap %p\n", this->conn->fd, this->fd, this->conn->func, this->conn->uap); errno = this->ioErrno; (this->conn->func)(opaqueCtx, this->conn->uap, this->fd, &this->la, this->lalen, &this->ra, this->ralen); #ifdef EVENTLIB_TIME_CHECKS func = this->conn->func; #endif break; } case File: { evFile *this = ev->u.file.this; int eventmask = ev->u.file.eventmask; evPrintf(ctx, 5, "Dispatch.File: fd %d, mask 0x%x, func %p, uap %p\n", this->fd, this->eventmask, this->func, this->uap); (this->func)(opaqueCtx, this->uap, this->fd, eventmask); #ifdef EVENTLIB_TIME_CHECKS func = this->func; #endif break; } case Stream: { evStream *this = ev->u.stream.this; evPrintf(ctx, 5, "Dispatch.Stream: fd %d, func %p, uap %p\n", this->fd, this->func, this->uap); errno = this->ioErrno; (this->func)(opaqueCtx, this->uap, this->fd, this->ioDone); #ifdef EVENTLIB_TIME_CHECKS func = this->func; #endif break; } case Timer: { evTimer *this = ev->u.timer.this; evPrintf(ctx, 5, "Dispatch.Timer: func %p, uap %p\n", this->func, this->uap); (this->func)(opaqueCtx, this->uap, this->due, this->inter); #ifdef EVENTLIB_TIME_CHECKS func = this->func; #endif break; } case Wait: { evWait *this = ev->u.wait.this; evPrintf(ctx, 5, "Dispatch.Wait: tag %p, func %p, uap %p\n", this->tag, this->func, this->uap); (this->func)(opaqueCtx, this->uap, this->tag); #ifdef EVENTLIB_TIME_CHECKS func = this->func; #endif break; } case Null: { /* No work. */ #ifdef EVENTLIB_TIME_CHECKS func = NULL; #endif break; } default: { abort(); } } #ifdef EVENTLIB_TIME_CHECKS if (ctx->debug > 0) { interval = evSubTime(evNowTime(), start_time); /* * Complain if it took longer than 50 milliseconds. * * We call getuid() to make an easy to find mark in a kernel * trace. */ if (interval.tv_sec > 0 || interval.tv_nsec > 50000000) evPrintf(ctx, 1, "dispatch interval %u.%09u uid %d type %d func %p\n", interval.tv_sec, interval.tv_nsec, getuid(), ev->type, func); } #endif ctx->cur = NULL; evDrop(opaqueCtx, opaqueEv); return (0); } void evDrop(evContext opaqueCtx, evEvent opaqueEv) { evContext_p *ctx = opaqueCtx.opaque; evEvent_p *ev = opaqueEv.opaque; switch (ev->type) { case Accept: { FREE(ev->u.accept.this); break; } case File: { /* No work. */ break; } case Stream: { evStreamID id; id.opaque = ev->u.stream.this; (void) evCancelRW(opaqueCtx, id); break; } case Timer: { evTimer *this = ev->u.timer.this; evTimerID opaque; /* Check to see whether the user func cleared the timer. */ if (heap_element(ctx->timers, this->index) != this) { evPrintf(ctx, 5, "Dispatch.Timer: timer rm'd?\n"); break; } /* * Timer is still there. Delete it if it has expired, * otherwise set it according to its next interval. */ if (this->inter.tv_sec == (time_t)0 && this->inter.tv_nsec == 0L) { opaque.opaque = this; (void) evClearTimer(opaqueCtx, opaque); } else { opaque.opaque = this; (void) evResetTimer(opaqueCtx, opaque, this->func, this->uap, evAddTime((this->mode & EV_TMR_RATE) ? this->due : ctx->lastEventTime, this->inter), this->inter); } break; } case Wait: { FREE(ev->u.wait.this); break; } case Null: { /* No work. */ break; } default: { abort(); } } FREE(ev); } int evMainLoop(evContext opaqueCtx) { evEvent event; int x; while ((x = evGetNext(opaqueCtx, &event, EV_WAIT)) == 0) if ((x = evDispatch(opaqueCtx, event)) < 0) break; return (x); } int evHighestFD(evContext opaqueCtx) { evContext_p *ctx = opaqueCtx.opaque; return (ctx->highestFD); } void evPrintf(const evContext_p *ctx, int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (ctx->output != NULL && ctx->debug >= level) { vfprintf(ctx->output, fmt, ap); fflush(ctx->output); } va_end(ap); } int evSetOption(evContext *opaqueCtx, const char *option, int value) { /* evContext_p *ctx = opaqueCtx->opaque; */ UNUSED(opaqueCtx); UNUSED(value); #ifndef CLOCK_MONOTONIC UNUSED(option); #endif #ifdef CLOCK_MONOTONIC if (strcmp(option, "monotime") == 0) { if (opaqueCtx != NULL) errno = EINVAL; if (value == 0 || value == 1) { __evOptMonoTime = value; return (0); } else { errno = EINVAL; return (-1); } } #endif errno = ENOENT; return (-1); } int evGetOption(evContext *opaqueCtx, const char *option, int *value) { /* evContext_p *ctx = opaqueCtx->opaque; */ UNUSED(opaqueCtx); #ifndef CLOCK_MONOTONIC UNUSED(value); UNUSED(option); #endif #ifdef CLOCK_MONOTONIC if (strcmp(option, "monotime") == 0) { if (opaqueCtx != NULL) errno = EINVAL; *value = __evOptMonoTime; return (0); } #endif errno = ENOENT; return (-1); } #if defined(NEED_PSELECT) || defined(USE_POLL) /* XXX needs to move to the porting library. */ static int pselect(int nfds, void *rfds, void *wfds, void *efds, struct timespec *tsp, const sigset_t *sigmask) { struct timeval tv, *tvp; sigset_t sigs; int n; #ifdef USE_POLL int polltimeout = INFTIM; evContext_p *ctx; struct pollfd *fds; nfds_t pnfds; UNUSED(nfds); #endif /* USE_POLL */ if (tsp) { tvp = &tv; tv = evTimeVal(*tsp); #ifdef USE_POLL polltimeout = 1000 * tv.tv_sec + tv.tv_usec / 1000; #endif /* USE_POLL */ } else tvp = NULL; if (sigmask) sigprocmask(SIG_SETMASK, sigmask, &sigs); #ifndef USE_POLL n = select(nfds, rfds, wfds, efds, tvp); #else /* * rfds, wfds, and efds should all be from the same evContext_p, * so any of them will do. If they're all NULL, the caller is * presumably calling us to block. */ if (rfds != NULL) ctx = ((__evEmulMask *)rfds)->ctx; else if (wfds != NULL) ctx = ((__evEmulMask *)wfds)->ctx; else if (efds != NULL) ctx = ((__evEmulMask *)efds)->ctx; else ctx = NULL; if (ctx != NULL && ctx->fdMax != -1) { fds = &(ctx->pollfds[ctx->firstfd]); pnfds = ctx->fdMax - ctx->firstfd + 1; } else { fds = NULL; pnfds = 0; } n = poll(fds, pnfds, polltimeout); if (n > 0) { int i, e; INSIST(ctx != NULL); for (e = 0, i = ctx->firstfd; i <= ctx->fdMax; i++) { if (ctx->pollfds[i].fd < 0) continue; if (FD_ISSET(i, &ctx->rdLast)) e++; if (FD_ISSET(i, &ctx->wrLast)) e++; if (FD_ISSET(i, &ctx->exLast)) e++; } n = e; } #endif /* USE_POLL */ if (sigmask) sigprocmask(SIG_SETMASK, &sigs, NULL); if (tsp) *tsp = evTimeSpec(tv); return (n); } #endif #ifdef USE_POLL int evPollfdRealloc(evContext_p *ctx, int pollfd_chunk_size, int fd) { int i, maxnfds; void *pollfds, *fdTable; if (fd < ctx->maxnfds) return (0); /* Don't allow ridiculously small values for pollfd_chunk_size */ if (pollfd_chunk_size < 20) pollfd_chunk_size = 20; maxnfds = (1 + (fd/pollfd_chunk_size)) * pollfd_chunk_size; pollfds = realloc(ctx->pollfds, maxnfds * sizeof(*ctx->pollfds)); if (pollfds != NULL) ctx->pollfds = pollfds; fdTable = realloc(ctx->fdTable, maxnfds * sizeof(*ctx->fdTable)); if (fdTable != NULL) ctx->fdTable = fdTable; if (pollfds == NULL || fdTable == NULL) { evPrintf(ctx, 2, "pollfd() realloc (%ld) failed\n", (long)maxnfds*sizeof(struct pollfd)); return (-1); } for (i = ctx->maxnfds; i < maxnfds; i++) { ctx->pollfds[i].fd = -1; ctx->pollfds[i].events = 0; ctx->fdTable[i] = 0; } ctx->maxnfds = maxnfds; return (0); } /* Find the appropriate 'events' or 'revents' field in the pollfds array */ short * __fd_eventfield(int fd, __evEmulMask *maskp) { evContext_p *ctx = (evContext_p *)maskp->ctx; if (!maskp->result || maskp->type == EV_WASNONBLOCKING) return (&(ctx->pollfds[fd].events)); else return (&(ctx->pollfds[fd].revents)); } /* Translate to poll(2) event */ short __poll_event(__evEmulMask *maskp) { switch ((maskp)->type) { case EV_READ: return (POLLRDNORM); case EV_WRITE: return (POLLWRNORM); case EV_EXCEPT: return (POLLRDBAND | POLLPRI | POLLWRBAND); case EV_WASNONBLOCKING: return (POLLHUP); default: return (0); } } /* * Clear the events corresponding to the specified mask. If this leaves * the events mask empty (apart from the POLLHUP bit), set the fd field * to -1 so that poll(2) will ignore this fd. */ void __fd_clr(int fd, __evEmulMask *maskp) { evContext_p *ctx = maskp->ctx; *__fd_eventfield(fd, maskp) &= ~__poll_event(maskp); if ((ctx->pollfds[fd].events & ~POLLHUP) == 0) { ctx->pollfds[fd].fd = -1; if (fd == ctx->fdMax) while (ctx->fdMax > ctx->firstfd && ctx->pollfds[ctx->fdMax].fd < 0) ctx->fdMax--; if (fd == ctx->firstfd) while (ctx->firstfd <= ctx->fdMax && ctx->pollfds[ctx->firstfd].fd < 0) ctx->firstfd++; /* * Do we have a empty set of descriptors? */ if (ctx->firstfd > ctx->fdMax) { ctx->fdMax = -1; ctx->firstfd = 0; } } } /* * Set the events bit(s) corresponding to the specified mask. If the events * field has any other bits than POLLHUP set, also set the fd field so that * poll(2) will watch this fd. */ void __fd_set(int fd, __evEmulMask *maskp) { evContext_p *ctx = maskp->ctx; *__fd_eventfield(fd, maskp) |= __poll_event(maskp); if ((ctx->pollfds[fd].events & ~POLLHUP) != 0) { ctx->pollfds[fd].fd = fd; if (fd < ctx->firstfd || ctx->fdMax == -1) ctx->firstfd = fd; if (fd > ctx->fdMax) ctx->fdMax = fd; } } #endif /* USE_POLL */ /*! \file */