/* This file is in the public domain. */ #include #include /* * Implementation of the interface defined by pollloop.h. */ #include "pollloop.h" /* * This implementation uses ID values which are small nonnegative ints * clustered near zero; they are indices into (mallocked) arrays. * However, as pollloop.h says, it is a bug to depend on this (except * for tasks like debugging that naturally peek under the hood). * * The removal paradigm used here - just mark removed IDs as dead and * deal with actually removing them next time around the main loop - * greatly simplifies the implementation, especially when dealing with * ID removal within callbacks from the library. It does, however, * mean that certain pathological usage patterns can make the arrays * grow drastically, and some relatively common patterns can push the * arrays to about twice what they otherwise would be. For our * intended use cases these issues are ignorable. */ typedef struct iofd IOFD; typedef struct blockfn BLOCKFN; /* * An IOFD represents a thing set up by add_poll_fd(). * * The first six elements are direct copies of the arguments to * add_poll_fd(). * * flags holds flags; WANT_R and WANT_W remember whether the rtest and * wtest functions indicated interest, since they may not return the * same thing post-poll as pre-poll, especially if other poll or block * functions get called in between. DEAD is set when remove_poll_id * is called; it exists so we can remove things from the arrays in * controlled fashion - this design allows (for example) calls to * remove_poll_id from within a wtest function to behave sanely. * * px is the index into the struct pollfd vector passed to poll(2), or * -1 if it does not appear in that vector (which happens if neither * rtest nor wtest indicates interest). */ struct iofd { int fd; int (*rtest)(void *); int (*wtest)(void *); void (*rd)(void *); void (*wr)(void *); void *arg; unsigned int flags; #define IOFF_WANT_R 0x00000001 #define IOFF_WANT_W 0x00000002 #define IOFF_DEAD 0x00000004 int px; } ; /* * A BLOCKFN represents a thing set up by add_block_fn(). * * The first two arguments are direct copies of the arguments to * add_block_fn(). * * flags holds flags, of course. DEAD is set by remove_block_id(); * this allows actual deletion from the array to happen controlledly, * which makes calls to remove_block_id() from arbitrary places * (within another block function, within poll test or do-I/O * functions) work sanely. */ struct blockfn { int (*fn)(void *); void *arg; unsigned int flags; #define BFF_DEAD 0x00000001 } ; /* * iofds is the array of IOFDs; niofds is the size of the array. * Unused slots hold nil pointers. This array is grown as needed. */ static IOFD **iofds; static int niofds; /* * blockfns is the array of BLOCKFNs; nblockfns is the size of the * array. Unused slots hold nil pointers. This array is grown as * needed. * * cblockfns hold a count of used slots, slots with non-nil pointers in * them. */ static BLOCKFN **blockfns; static int nblockfns; static int cblockfns; /* * pfds is the array of struct pollfds passed to poll(2). pfdn is the * number of meaningful entries in this array; pfdmax is the number of * entries pfds points to. This array is grown as necessary, ie, when * pfdn would otherwise need to surpass pfdmax. */ static struct pollfd *pfds; static int pfdn; static int pfdmax; /* * justloop is set whenever we want to just go around the loop again, * such as when a block function returns BLOCK_LOOP. It is used to * bypass the rest of the main loop when appropriate. */ static int justloop; /* * Used to detect add_poll_id() calls from within rtest and wtest * functions, which could otherwise cause a fd to be missed for one * cycle. */ static int added_poll_fd; /* * Used to detect add_block_fn() calls from within other block * functions, which could otherwise cause a block function to be * missed for one cycle. */ static int added_block_fn; /* * Just set up the variables. */ void init_polling(void) { iofds = 0; niofds = 0; blockfns = 0; nblockfns = 0; cblockfns = 0; pfds = 0; pfdn = 0; pfdmax = 0; justloop = 0; added_poll_fd = 0; added_block_fn = 0; } /* * Add a poll fd. Not much to do here; just find a slot, growing iofds * if necessary, and set up the IOFD. * * Note that the IOFD is carefully set up to be harmless in case this * is done from within an rtest/wtest/rd/wr function within pre_poll * or post_poll, below. */ int add_poll_fd(int fd, int (*rtest)(void *), int (*wtest)(void *), void (*rd)(void *), void (*wr)(void *), void *arg) { IOFD *io; int i; io = malloc(sizeof(IOFD)); do <"gotspace"> { for (i=0;i; iofds = realloc(iofds,sizeof(IOFD *)*++niofds); } while (0); iofds[i] = io; io->fd = fd; io->rtest = rtest; io->wtest = wtest; io->rd = rd; io->wr = wr; io->arg = arg; io->flags = 0; added_poll_fd = 1; return(i); } /* * Add a block function. Not much to do here; just find a slot, * growing blockfns if necessary, and set up the BLOCKFN. */ int add_block_fn(int (*fn)(void *), void *arg) { BLOCKFN *bf; int i; bf = malloc(sizeof(BLOCKFN)); do <"gotspace"> { for (i=0;i; blockfns = realloc(blockfns,sizeof(BLOCKFN *)*++nblockfns); } while (0); blockfns[i] = bf; bf->fn = fn; bf->arg = arg; bf->flags = 0; cblockfns ++; added_block_fn = 1; return(i); } /* * Remove an IOFD by ID. Just find it and set its DEAD bit; actual * removal is handled elsewhere. */ void remove_poll_id(int id) { if ((id < 0) || (id >= niofds) || !iofds[id] || (iofds[id]->flags & IOFF_DEAD)) abort(); iofds[id]->flags |= IOFF_DEAD; } /* * Remove an BLOCKFN by ID. Just find it and set its DEAD bit; actual * removal is handled elsewhere. */ void remove_block_id(int id) { if ((id < 0) || (id >= nblockfns) || !blockfns[id] || (blockfns[id]->flags & BFF_DEAD)) abort(); blockfns[id]->flags |= BFF_DEAD; } /* * Pre-poll activities. This is where we call the rtest and wtest * functions for polls; we remember the results with the WANT_R and * WANT_W bits for later reference. We also build the vector to pass * to poll(2) here. This is where dead IOFDs are swept up. * * This code has nothing to do with block functions. They are entirely * the province of do_poll(). * * Note that the rtest and wtest functions could register new poll fds; * niofds may increase during execution of this loop. New IOFDs are * set up such that this is not a problem - they look just as though * they'd been set up just before calling pre_poll(). */ void pre_poll(void) { int i; IOFD *io; pfdn = 0; added_poll_fd = 0; for (i=0;iflags & IOFF_DEAD) { free(io); iofds[i] = 0; continue; } io->flags = (io->flags & ~(IOFF_WANT_R|IOFF_WANT_W)) | ((*io->rtest)(io->arg) ? IOFF_WANT_R : 0) | ((*io->wtest)(io->arg) ? IOFF_WANT_W : 0); if (io->flags & (IOFF_WANT_R|IOFF_WANT_W)) { struct pollfd *pfd; if (pfdn >= pfdmax) pfds = realloc(pfds,(pfdmax=pfdn+8)*sizeof(*pfds)); io->px = pfdn++; pfd = &pfds[io->px]; pfd->fd = io->fd; pfd->events = ((io->flags & IOFF_WANT_R) ? POLLIN|POLLRDNORM : 0) | ((io->flags & IOFF_WANT_W) ? POLLOUT|POLLWRNORM : 0); } else { io->px = -1; } } if (added_poll_fd) justloop = 1; } /* * Actually do the poll(2) call(s), possibly calling block functions. * This is where dead BLOCKFNs are swept up. */ int do_poll(void) { int n; int again; int us; int tmo; if (justloop) return(0); tmo = INFTIM; if (cblockfns) { int i; int c; BLOCKFN *bf; n = poll(pfds,pfdn,0); if (n != 0) return(n); c = 0; again = 0; added_block_fn = 0; for (i=0;iflags & BFF_DEAD) { free(bf); blockfns[i] = 0; cblockfns --; continue; } c ++; us = (*bf->fn)(bf->arg); if (us < 0) { switch (us) { case BLOCK_NIL: break; case BLOCK_LOOP: again = 1; break; default: abort(); break; } } else { if ((tmo == INFTIM) || (us < tmo)) tmo = us; } } if (c != cblockfns) abort(); if (again || added_block_fn) { justloop = 1; return(0); } } return(poll(pfds,pfdn,INFTIM)); } /* * Handle post-poll tasks. This mostly means calling rd and wr * functions for poll fds that show I/O possible. This is bypassed * completely if any block function returned BLOCK_LOOP in do_poll(), * since in that case anything sitting in pfds[] is dubious at best. * * Note that it is possible to call add_poll_fd from within an rd or wr * function. Because of the way they are set up, this is harmless; * we'll pick them up next time around. */ void post_poll(void) { int i; IOFD *io; if (justloop) { justloop = 0; return; } for (i=0;iflags & IOFF_DEAD) continue; if ( (io->flags & IOFF_WANT_R) && (pfds[io->px].revents & (POLLIN|POLLRDNORM)) ) (*io->rd)(io->arg); if ( (io->flags & IOFF_WANT_W) && (pfds[io->px].revents & (POLLOUT|POLLWRNORM)) ) (*io->wr)(io->arg); } } /* * The trivial utility test functions. */ int rwtest_always(void *arg __attribute__((__unused__))) { return(1); } int rwtest_never(void *arg __attribute__((__unused__))) { return(0); }