/* select - Module containing unix select(2) call. Under Unix, the file descriptors are small integers. Under Win32, select only exists for sockets, and sockets may have any value except INVALID_SOCKET. Under BeOS, we suffer the same dichotomy as Win32; sockets can be anything >= 0. */ #include "Python.h" #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_POLL_H #include #endif #ifdef __sgi /* This is missing from unistd.h */ extern void bzero(void *, int); #endif #ifndef DONT_HAVE_SYS_TYPES_H #include #endif #if defined(PYOS_OS2) #include #include #endif #ifdef MS_WINDOWS #include #else #ifdef __BEOS__ #include #define SOCKET int #else #define SOCKET int #endif #endif static PyObject *SelectError; /* list of Python objects and their file descriptor */ typedef struct { PyObject *obj; /* owned reference */ SOCKET fd; int sentinel; /* -1 == sentinel */ } pylist; static void reap_obj(pylist fd2obj[FD_SETSIZE + 3]) { int i; for (i = 0; i < FD_SETSIZE + 3 && fd2obj[i].sentinel >= 0; i++) { Py_XDECREF(fd2obj[i].obj); fd2obj[i].obj = NULL; } fd2obj[0].sentinel = -1; } /* returns -1 and sets the Python exception if an error occurred, otherwise returns a number >= 0 */ static int list2set(PyObject *list, fd_set *set, pylist fd2obj[FD_SETSIZE + 3]) { int i; int max = -1; int index = 0; int len = PyList_Size(list); PyObject* o = NULL; fd2obj[0].obj = (PyObject*)0; /* set list to zero size */ FD_ZERO(set); for (i = 0; i < len; i++) { SOCKET v; /* any intervening fileno() calls could decr this refcnt */ if (!(o = PyList_GetItem(list, i))) return -1; Py_INCREF(o); v = PyObject_AsFileDescriptor( o ); if (v == -1) goto finally; #if defined(_MSC_VER) max = 0; /* not used for Win32 */ #else /* !_MSC_VER */ if (v < 0 || v >= FD_SETSIZE) { PyErr_SetString(PyExc_ValueError, "filedescriptor out of range in select()"); goto finally; } if (v > max) max = v; #endif /* _MSC_VER */ FD_SET(v, set); /* add object and its file descriptor to the list */ if (index >= FD_SETSIZE) { PyErr_SetString(PyExc_ValueError, "too many file descriptors in select()"); goto finally; } fd2obj[index].obj = o; fd2obj[index].fd = v; fd2obj[index].sentinel = 0; fd2obj[++index].sentinel = -1; } return max+1; finally: Py_XDECREF(o); return -1; } /* returns NULL and sets the Python exception if an error occurred */ static PyObject * set2list(fd_set *set, pylist fd2obj[FD_SETSIZE + 3]) { int i, j, count=0; PyObject *list, *o; SOCKET fd; for (j = 0; fd2obj[j].sentinel >= 0; j++) { if (FD_ISSET(fd2obj[j].fd, set)) count++; } list = PyList_New(count); if (!list) return NULL; i = 0; for (j = 0; fd2obj[j].sentinel >= 0; j++) { fd = fd2obj[j].fd; if (FD_ISSET(fd, set)) { #ifndef _MSC_VER if (fd > FD_SETSIZE) { PyErr_SetString(PyExc_SystemError, "filedescriptor out of range returned in select()"); goto finally; } #endif o = fd2obj[j].obj; fd2obj[j].obj = NULL; /* transfer ownership */ if (PyList_SetItem(list, i, o) < 0) goto finally; i++; } } return list; finally: Py_DECREF(list); return NULL; } static PyObject * select_select(PyObject *self, PyObject *args) { #ifdef MS_WINDOWS /* This would be an awful lot of stack space on Windows! */ pylist *rfd2obj, *wfd2obj, *efd2obj; #else pylist rfd2obj[FD_SETSIZE + 3]; pylist wfd2obj[FD_SETSIZE + 3]; pylist efd2obj[FD_SETSIZE + 3]; #endif PyObject *ifdlist, *ofdlist, *efdlist; PyObject *ret = NULL; PyObject *tout = Py_None; fd_set ifdset, ofdset, efdset; double timeout; struct timeval tv, *tvp; long seconds; int imax, omax, emax, max; int n; /* convert arguments */ if (!PyArg_ParseTuple(args, "OOO|O:select", &ifdlist, &ofdlist, &efdlist, &tout)) return NULL; if (tout == Py_None) tvp = (struct timeval *)0; else if (!PyArg_Parse(tout, "d", &timeout)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float or None"); return NULL; } else { if (timeout > (double)LONG_MAX) { PyErr_SetString(PyExc_OverflowError, "timeout period too long"); return NULL; } seconds = (long)timeout; timeout = timeout - (double)seconds; tv.tv_sec = seconds; tv.tv_usec = (long)(timeout*1000000.0); tvp = &tv; } /* sanity check first three arguments */ if (!PyList_Check(ifdlist) || !PyList_Check(ofdlist) || !PyList_Check(efdlist)) { PyErr_SetString(PyExc_TypeError, "arguments 1-3 must be lists"); return NULL; } #ifdef MS_WINDOWS /* Allocate memory for the lists */ rfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 3); wfd2obj = PyMem_NEW(pylist, FD_SETSIZE + 3); efd2obj = PyMem_NEW(pylist, FD_SETSIZE + 3); if (rfd2obj == NULL || wfd2obj == NULL || efd2obj == NULL) { if (rfd2obj) PyMem_DEL(rfd2obj); if (wfd2obj) PyMem_DEL(wfd2obj); if (efd2obj) PyMem_DEL(efd2obj); return NULL; } #endif /* Convert lists to fd_sets, and get maximum fd number * propagates the Python exception set in list2set() */ rfd2obj[0].sentinel = -1; wfd2obj[0].sentinel = -1; efd2obj[0].sentinel = -1; if ((imax=list2set(ifdlist, &ifdset, rfd2obj)) < 0) goto finally; if ((omax=list2set(ofdlist, &ofdset, wfd2obj)) < 0) goto finally; if ((emax=list2set(efdlist, &efdset, efd2obj)) < 0) goto finally; max = imax; if (omax > max) max = omax; if (emax > max) max = emax; Py_BEGIN_ALLOW_THREADS n = select(max, &ifdset, &ofdset, &efdset, tvp); Py_END_ALLOW_THREADS if (n < 0) { PyErr_SetFromErrno(SelectError); } else if (n == 0) { /* optimization */ ifdlist = PyList_New(0); if (ifdlist) { ret = Py_BuildValue("OOO", ifdlist, ifdlist, ifdlist); Py_DECREF(ifdlist); } } else { /* any of these three calls can raise an exception. it's more convenient to test for this after all three calls... but is that acceptable? */ ifdlist = set2list(&ifdset, rfd2obj); ofdlist = set2list(&ofdset, wfd2obj); efdlist = set2list(&efdset, efd2obj); if (PyErr_Occurred()) ret = NULL; else ret = Py_BuildValue("OOO", ifdlist, ofdlist, efdlist); Py_DECREF(ifdlist); Py_DECREF(ofdlist); Py_DECREF(efdlist); } finally: reap_obj(rfd2obj); reap_obj(wfd2obj); reap_obj(efd2obj); #ifdef MS_WINDOWS PyMem_DEL(rfd2obj); PyMem_DEL(wfd2obj); PyMem_DEL(efd2obj); #endif return ret; } #ifdef HAVE_POLL /* * poll() support */ typedef struct { PyObject_HEAD PyObject *dict; int ufd_uptodate; int ufd_len; struct pollfd *ufds; } pollObject; staticforward PyTypeObject poll_Type; /* Update the malloc'ed array of pollfds to match the dictionary contained within a pollObject. Return 1 on success, 0 on an error. */ static int update_ufd_array(pollObject *self) { int i, j, pos; PyObject *key, *value; self->ufd_len = PyDict_Size(self->dict); PyMem_Resize(self->ufds, struct pollfd, self->ufd_len); if (self->ufds == NULL) { PyErr_NoMemory(); return 0; } i = pos = 0; while ((j = PyDict_Next(self->dict, &pos, &key, &value))) { self->ufds[i].fd = PyInt_AsLong(key); self->ufds[i].events = PyInt_AsLong(value); i++; } self->ufd_uptodate = 1; return 1; } static char poll_register_doc[] = "register(fd [, eventmask] ) -> None\n\n\ Register a file descriptor with the polling object.\n\ fd -- either an integer, or an object with a fileno() method returning an int.\n\ events -- an optional bitmask describing the type of events to check for"; static PyObject * poll_register(pollObject *self, PyObject *args) { PyObject *o, *key, *value; int fd, events = POLLIN | POLLPRI | POLLOUT; if (!PyArg_ParseTuple(args, "O|i", &o, &events)) { return NULL; } fd = PyObject_AsFileDescriptor(o); if (fd == -1) return NULL; /* Add entry to the internal dictionary: the key is the file descriptor, and the value is the event mask. */ if ( (NULL == (key = PyInt_FromLong(fd))) || (NULL == (value = PyInt_FromLong(events))) || (PyDict_SetItem(self->dict, key, value)) == -1) { return NULL; } self->ufd_uptodate = 0; Py_INCREF(Py_None); return Py_None; } static char poll_unregister_doc[] = "unregister(fd) -> None\n\n\ Remove a file descriptor being tracked by the polling object."; static PyObject * poll_unregister(pollObject *self, PyObject *args) { PyObject *o, *key; int fd; if (!PyArg_ParseTuple(args, "O", &o)) { return NULL; } fd = PyObject_AsFileDescriptor( o ); if (fd == -1) return NULL; /* Check whether the fd is already in the array */ key = PyInt_FromLong(fd); if (key == NULL) return NULL; if (PyDict_DelItem(self->dict, key) == -1) { Py_DECREF(key); /* This will simply raise the KeyError set by PyDict_DelItem if the file descriptor isn't registered. */ return NULL; } Py_DECREF(key); self->ufd_uptodate = 0; Py_INCREF(Py_None); return Py_None; } static char poll_poll_doc[] = "poll( [timeout] ) -> list of (fd, event) 2-tuples\n\n\ Polls the set of registered file descriptors, returning a list containing \n\ any descriptors that have events or errors to report."; static PyObject * poll_poll(pollObject *self, PyObject *args) { PyObject *result_list = NULL, *tout = NULL; int timeout = 0, poll_result, i, j; PyObject *value = NULL, *num = NULL; if (!PyArg_ParseTuple(args, "|O", &tout)) { return NULL; } /* Check values for timeout */ if (tout == NULL || tout == Py_None) timeout = -1; else if (!PyArg_Parse(tout, "i", &timeout)) { PyErr_SetString(PyExc_TypeError, "timeout must be an integer or None"); return NULL; } /* Ensure the ufd array is up to date */ if (!self->ufd_uptodate) if (update_ufd_array(self) == 0) return NULL; /* call poll() */ Py_BEGIN_ALLOW_THREADS; poll_result = poll(self->ufds, self->ufd_len, timeout); Py_END_ALLOW_THREADS; if (poll_result < 0) { PyErr_SetFromErrno(SelectError); return NULL; } /* build the result list */ result_list = PyList_New(poll_result); if (!result_list) return NULL; else { for (i = 0, j = 0; j < poll_result; j++) { /* skip to the next fired descriptor */ while (!self->ufds[i].revents) { i++; } /* if we hit a NULL return, set value to NULL and break out of loop; code at end will clean up result_list */ value = PyTuple_New(2); if (value == NULL) goto error; num = PyInt_FromLong(self->ufds[i].fd); if (num == NULL) { Py_DECREF(value); goto error; } PyTuple_SET_ITEM(value, 0, num); num = PyInt_FromLong(self->ufds[i].revents); if (num == NULL) { Py_DECREF(value); goto error; } PyTuple_SET_ITEM(value, 1, num); if ((PyList_SetItem(result_list, j, value)) == -1) { Py_DECREF(value); goto error; } i++; } } return result_list; error: Py_DECREF(result_list); return NULL; } static PyMethodDef poll_methods[] = { {"register", (PyCFunction)poll_register, METH_VARARGS, poll_register_doc}, {"unregister", (PyCFunction)poll_unregister, METH_VARARGS, poll_unregister_doc}, {"poll", (PyCFunction)poll_poll, METH_VARARGS, poll_poll_doc}, {NULL, NULL} /* sentinel */ }; static pollObject * newPollObject(void) { pollObject *self; self = PyObject_New(pollObject, &poll_Type); if (self == NULL) return NULL; /* ufd_uptodate is a Boolean, denoting whether the array pointed to by ufds matches the contents of the dictionary. */ self->ufd_uptodate = 0; self->ufds = NULL; self->dict = PyDict_New(); if (self->dict == NULL) { Py_DECREF(self); return NULL; } return self; } static void poll_dealloc(pollObject *self) { if (self->ufds != NULL) PyMem_DEL(self->ufds); Py_XDECREF(self->dict); PyObject_Del(self); } static PyObject * poll_getattr(pollObject *self, char *name) { return Py_FindMethod(poll_methods, (PyObject *)self, name); } statichere PyTypeObject poll_Type = { /* The ob_type field must be initialized in the module init function * to be portable to Windows without using C++. */ PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "poll", /*tp_name*/ sizeof(pollObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)poll_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ (getattrfunc)poll_getattr, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ }; static char poll_doc[] = "Returns a polling object, which supports registering and\n\ unregistering file descriptors, and then polling them for I/O events."; static PyObject * select_poll(PyObject *self, PyObject *args) { pollObject *rv; if (!PyArg_ParseTuple(args, ":poll")) return NULL; rv = newPollObject(); if ( rv == NULL ) return NULL; return (PyObject *)rv; } #endif /* HAVE_POLL */ static char select_doc[] = "select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist)\n\ \n\ Wait until one or more file descriptors are ready for some kind of I/O.\n\ The first three arguments are lists of file descriptors to be waited for:\n\ rlist -- wait until ready for reading\n\ wlist -- wait until ready for writing\n\ xlist -- wait for an ``exceptional condition''\n\ If only one kind of condition is required, pass [] for the other lists.\n\ A file descriptor is either a socket or file object, or a small integer\n\ gotten from a fileno() method call on one of those.\n\ \n\ The optional 4th argument specifies a timeout in seconds; it may be\n\ a floating point number to specify fractions of seconds. If it is absent\n\ or None, the call will never time out.\n\ \n\ The return value is a tuple of three lists corresponding to the first three\n\ arguments; each contains the subset of the corresponding file descriptors\n\ that are ready.\n\ \n\ *** IMPORTANT NOTICE ***\n\ On Windows, only sockets are supported; on Unix, all file descriptors."; static PyMethodDef select_methods[] = { {"select", select_select, METH_VARARGS, select_doc}, #ifdef HAVE_POLL {"poll", select_poll, METH_VARARGS, poll_doc}, #endif /* HAVE_POLL */ {0, 0}, /* sentinel */ }; static char module_doc[] = "This module supports asynchronous I/O on multiple file descriptors.\n\ \n\ *** IMPORTANT NOTICE ***\n\ On Windows, only sockets are supported; on Unix, all file descriptors."; /* * Convenience routine to export an integer value. * For simplicity, errors (which are unlikely anyway) are ignored. */ static void insint(PyObject *d, char *name, int value) { PyObject *v = PyInt_FromLong((long) value); if (v == NULL) { /* Don't bother reporting this error */ PyErr_Clear(); } else { PyDict_SetItemString(d, name, v); Py_DECREF(v); } } DL_EXPORT(void) initselect(void) { PyObject *m, *d; m = Py_InitModule3("select", select_methods, module_doc); d = PyModule_GetDict(m); SelectError = PyErr_NewException("select.error", NULL, NULL); PyDict_SetItemString(d, "error", SelectError); #ifdef HAVE_POLL poll_Type.ob_type = &PyType_Type; insint(d, "POLLIN", POLLIN); insint(d, "POLLPRI", POLLPRI); insint(d, "POLLOUT", POLLOUT); insint(d, "POLLERR", POLLERR); insint(d, "POLLHUP", POLLHUP); insint(d, "POLLNVAL", POLLNVAL); insint(d, "POLLRDNORM", POLLRDNORM); insint(d, "POLLRDBAND", POLLRDBAND); insint(d, "POLLWRNORM", POLLWRNORM); insint(d, "POLLWRBAND", POLLWRBAND); #ifdef POLLMSG insint(d, "POLLMSG", POLLMSG); #endif #endif /* HAVE_POLL */ }