/* exp_poll.c - This file contains UNIX specific procedures for * poll-based notifier, which is the lowest-level part of the Tcl * event loop. This file works together with ../generic/tclNotify.c. * * Design and implementation of this program was paid for by U.S. tax * dollars. Therefore it is public domain. However, the author and * NIST would appreciate credit if this program or parts of it are * used. * * Written by Don Libes, NIST, 2/6/90 * Rewritten by Don Libes, 2/96 for new Tcl notifier paradigm. * */ #include "tclInt.h" #include "tclPort.h" #include <signal.h> #include <poll.h> #include <sys/types.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif /* * The information below is used to provide read, write, and * exception masks to select during calls to Tcl_DoOneEvent. */ /* Some systems require that the poll array be non-empty. Alas, Tcl * provides no initialization opportunity to dynamically allocate it, * so provide a 1-elt array for starters. It will be ignored as soon * as it grows larger. */ static struct pollfd initialFdArray; static struct pollfd *fdArray = &initialFdArray; static int fdsInUse = 0; /* space in use */ static int fdsMaxSpace = 1; /* space that has actually been allocated */ /* *---------------------------------------------------------------------- * * Tcl_WatchFile -- * * Arrange for Tcl_DoOneEvent to include this file in the masks * for the next call to select. This procedure is invoked by * event sources, which are in turn invoked by Tcl_DoOneEvent * before it invokes select. * * Results: * None. * * Side effects: * * The notifier will generate a file event when the I/O channel * given by fd next becomes ready in the way indicated by mask. * If fd is already registered then the old mask will be replaced * with the new one. Once the event is sent, the notifier will * not send any more events about the fd until the next call to * Tcl_NotifyFile. * * Assumption for poll implementation: Tcl_WatchFile is presumed NOT * to be called on the same file descriptior without intervening calls * to Tcl_DoOneEvent. * *---------------------------------------------------------------------- */ void Tcl_WatchFile(file, mask) Tcl_File file; /* Generic file handle for a stream. */ int mask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: * indicates conditions to wait for * in select. */ { int fd, type; int cur_fd_index = fdsInUse; fd = (int) Tcl_GetFileInfo(file, &type); if (type != TCL_UNIX_FD) { panic("Tcl_WatchFile: unexpected file type"); } fdsInUse++; if (fdsInUse > fdsMaxSpace) { if (fdArray != &initialFdArray) ckfree((char *)fdArray); fdArray = (struct pollfd *)ckalloc(fdsInUse*sizeof(struct pollfd)); fdsMaxSpace = fdsInUse; } fdArray[cur_fd_index].fd = fd; /* I know that POLLIN/OUT is right. But I have no idea if POLLPRI * corresponds well to TCL_EXCEPTION. */ if (mask & TCL_READABLE) { fdArray[cur_fd_index].events = POLLIN; } if (mask & TCL_WRITABLE) { fdArray[cur_fd_index].events = POLLOUT; } if (mask & TCL_EXCEPTION) { fdArray[cur_fd_index].events = POLLPRI; } } /* *---------------------------------------------------------------------- * * Tcl_FileReady -- * * Indicates what conditions (readable, writable, etc.) were * present on a file the last time the notifier invoked select. * This procedure is typically invoked by event sources to see * if they should queue events. * * Results: * The return value is 0 if none of the conditions specified by mask * was true for fd the last time the system checked. If any of the * conditions were true, then the return value is a mask of those * that were true. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_FileReady(file, mask) Tcl_File file; /* Generic file handle for a stream. */ int mask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, and TCL_EXCEPTION: * indicates conditions caller cares about. */ { int index, result, type, fd; fd_mask bit; fd = (int) Tcl_GetFileInfo(file, &type); if (type != TCL_UNIX_FD) { panic("Tcl_FileReady: unexpected file type"); } result = 0; if ((mask & TCL_READABLE) && (fdArray[fd].revents & POLLIN)) { result |= TCL_READABLE; } if ((mask & TCL_WRITABLE) && (fdArray[fd].revents & POLLOUT)) { result |= TCL_WRITABLE; } /* I have no idea if this is right ... */ if ((mask & TCL_EXCEPTION) && (fdArray[fd].revents & (POLLPRI|POLLERR|POLLHUP|POLLNVAL))) { result |= TCL_EXCEPTION; } return result; } /* *---------------------------------------------------------------------- * * Tcl_WaitForEvent -- * * This procedure does the lowest level wait for events in a * platform-specific manner. It uses information provided by * previous calls to Tcl_WatchFile, plus the timePtr argument, * to determine what to wait for and how long to wait. * * Results: * None. * * Side effects: * May put the process to sleep for a while, depending on timePtr. * When this procedure returns, an event of interest to the application * has probably, but not necessarily, occurred. * *---------------------------------------------------------------------- */ void Tcl_WaitForEvent(timePtr) Tcl_Time *timePtr; /* Specifies the maximum amount of time * that this procedure should block before * returning. The time is given as an * interval, not an absolute wakeup time. * NULL means block forever. */ { int timeout; struct timeval *timeoutPtr; /* no need to clear revents */ if (timePtr == NULL) { timeout = -1; } else { timeout = timePtr->sec*1000 + timePtr->usec/1000; } poll(fdsInUse,fdArray,timeout); fdsInUse = 0; } /* *---------------------------------------------------------------------- * * Tcl_Sleep -- * * Delay execution for the specified number of milliseconds. * * Results: * None. * * Side effects: * Time passes. * *---------------------------------------------------------------------- */ void Tcl_Sleep(ms) int ms; /* Number of milliseconds to sleep. */ { static struct timeval delay; Tcl_Time before, after; /* * The only trick here is that select appears to return early * under some conditions, so we have to check to make sure that * the right amount of time really has elapsed. If it's too * early, go back to sleep again. */ TclGetTime(&before); after = before; after.sec += ms/1000; after.usec += (ms%1000)*1000; if (after.usec > 1000000) { after.usec -= 1000000; after.sec += 1; } while (1) { delay.tv_sec = after.sec - before.sec; delay.tv_usec = after.usec - before.usec; if (delay.tv_usec < 0) { delay.tv_usec += 1000000; delay.tv_sec -= 1; } /* * Special note: must convert delay.tv_sec to int before comparing * to zero, since delay.tv_usec is unsigned on some platforms. */ if ((((int) delay.tv_sec) < 0) || ((delay.tv_usec == 0) && (delay.tv_sec == 0))) { break; } /* poll understands milliseconds, sigh */ poll(0,fdArray,delay.tv_sec*1000 + delay.tv_usec/1000); TclGetTime(&before); } }