--- /dev/null
+/*
+ * sel.c: implementation of sel.h.
+ */
+
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/select.h>
+
+#include "sel.h"
+#include "malloc.h"
+
+/* ----------------------------------------------------------------------
+ * Chunk of code lifted from PuTTY's misc.c to manage buffers of
+ * data to be written to an fd.
+ */
+
+#define BUFFER_GRANULE 512
+
+typedef struct bufchain_tag {
+ struct bufchain_granule *head, *tail;
+ size_t buffersize; /* current amount of buffered data */
+} bufchain;
+struct bufchain_granule {
+ struct bufchain_granule *next;
+ size_t buflen, bufpos;
+ char buf[BUFFER_GRANULE];
+};
+
+static void bufchain_init(bufchain *ch)
+{
+ ch->head = ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+static void bufchain_clear(bufchain *ch)
+{
+ struct bufchain_granule *b;
+ while (ch->head) {
+ b = ch->head;
+ ch->head = ch->head->next;
+ sfree(b);
+ }
+ ch->tail = NULL;
+ ch->buffersize = 0;
+}
+
+static size_t bufchain_size(bufchain *ch)
+{
+ return ch->buffersize;
+}
+
+static void bufchain_add(bufchain *ch, const void *data, size_t len)
+{
+ const char *buf = (const char *)data;
+
+ if (len == 0) return;
+
+ ch->buffersize += len;
+
+ if (ch->tail && ch->tail->buflen < BUFFER_GRANULE) {
+ size_t copylen = BUFFER_GRANULE - ch->tail->buflen;
+ if (copylen > len)
+ copylen = len;
+ memcpy(ch->tail->buf + ch->tail->buflen, buf, copylen);
+ buf += copylen;
+ len -= copylen;
+ ch->tail->buflen += copylen;
+ }
+ while (len > 0) {
+ struct bufchain_granule *newbuf;
+ size_t grainlen = BUFFER_GRANULE;
+ if (grainlen > len)
+ grainlen = len;
+ newbuf = snew(struct bufchain_granule);
+ newbuf->bufpos = 0;
+ newbuf->buflen = grainlen;
+ memcpy(newbuf->buf, buf, grainlen);
+ buf += grainlen;
+ len -= grainlen;
+ if (ch->tail)
+ ch->tail->next = newbuf;
+ else
+ ch->head = ch->tail = newbuf;
+ newbuf->next = NULL;
+ ch->tail = newbuf;
+ }
+}
+
+static void bufchain_consume(bufchain *ch, size_t len)
+{
+ struct bufchain_granule *tmp;
+
+ assert(ch->buffersize >= len);
+ while (len > 0) {
+ size_t remlen = len;
+ assert(ch->head != NULL);
+ if (remlen >= ch->head->buflen - ch->head->bufpos) {
+ remlen = ch->head->buflen - ch->head->bufpos;
+ tmp = ch->head;
+ ch->head = tmp->next;
+ sfree(tmp);
+ if (!ch->head)
+ ch->tail = NULL;
+ } else
+ ch->head->bufpos += remlen;
+ ch->buffersize -= remlen;
+ len -= remlen;
+ }
+}
+
+static void bufchain_prefix(bufchain *ch, void **data, size_t *len)
+{
+ *len = ch->head->buflen - ch->head->bufpos;
+ *data = ch->head->buf + ch->head->bufpos;
+}
+
+/* ----------------------------------------------------------------------
+ * The actual implementation of the sel interface.
+ */
+
+struct sel {
+ void *ctx;
+ sel_rfd *rhead, *rtail;
+ sel_wfd *whead, *wtail;
+};
+
+struct sel_rfd {
+ sel *parent;
+ sel_rfd *prev, *next;
+ sel_readdata_fn_t readdata;
+ sel_readerr_fn_t readerr;
+ void *ctx;
+ int fd;
+ int frozen;
+};
+
+struct sel_wfd {
+ sel *parent;
+ sel_wfd *prev, *next;
+ sel_written_fn_t written;
+ sel_writeerr_fn_t writeerr;
+ void *ctx;
+ int fd;
+ bufchain buf;
+};
+
+sel *sel_new(void *ctx)
+{
+ sel *sel = snew(struct sel);
+
+ sel->ctx = ctx;
+ sel->rhead = sel->rtail = NULL;
+ sel->whead = sel->wtail = NULL;
+
+ return sel;
+}
+
+sel_wfd *sel_wfd_add(sel *sel, int fd,
+ sel_written_fn_t written, sel_writeerr_fn_t writeerr,
+ void *ctx)
+{
+ sel_wfd *wfd = snew(sel_wfd);
+
+ wfd->written = written;
+ wfd->writeerr = writeerr;
+ wfd->ctx = ctx;
+ wfd->fd = fd;
+ bufchain_init(&wfd->buf);
+
+ wfd->next = NULL;
+ wfd->prev = sel->wtail;
+ if (sel->wtail)
+ sel->wtail->next = wfd;
+ else
+ sel->whead = wfd;
+ sel->wtail = wfd;
+ wfd->parent = sel;
+
+ return wfd;
+}
+
+sel_rfd *sel_rfd_add(sel *sel, int fd,
+ sel_readdata_fn_t readdata, sel_readerr_fn_t readerr,
+ void *ctx)
+{
+ sel_rfd *rfd = snew(sel_rfd);
+
+ rfd->readdata = readdata;
+ rfd->readerr = readerr;
+ rfd->ctx = ctx;
+ rfd->fd = fd;
+ rfd->frozen = 0;
+
+ rfd->next = NULL;
+ rfd->prev = sel->rtail;
+ if (sel->rtail)
+ sel->rtail->next = rfd;
+ else
+ sel->rhead = rfd;
+ sel->rtail = rfd;
+ rfd->parent = sel;
+
+ return rfd;
+}
+
+size_t sel_write(sel_wfd *wfd, const void *data, size_t len)
+{
+ bufchain_add(&wfd->buf, data, len);
+ return bufchain_size(&wfd->buf);
+}
+
+void sel_wfd_setfd(sel_wfd *wfd, int fd)
+{
+ wfd->fd = fd;
+}
+
+void sel_rfd_setfd(sel_rfd *rfd, int fd)
+{
+ rfd->fd = fd;
+}
+
+void sel_rfd_freeze(sel_rfd *rfd)
+{
+ rfd->frozen = 1;
+}
+
+void sel_rfd_unfreeze(sel_rfd *rfd)
+{
+ rfd->frozen = 0;
+}
+
+int sel_wfd_delete(sel_wfd *wfd)
+{
+ sel *sel = wfd->parent;
+ int ret;
+
+ if (wfd->prev)
+ wfd->prev->next = wfd->next;
+ else
+ sel->whead = wfd->next;
+ if (wfd->next)
+ wfd->next->prev = wfd->prev;
+ else
+ sel->wtail = wfd->prev;
+
+ bufchain_clear(&wfd->buf);
+
+ ret = wfd->fd;
+ sfree(wfd);
+ return ret;
+}
+
+int sel_rfd_delete(sel_rfd *rfd)
+{
+ sel *sel = rfd->parent;
+ int ret;
+
+ if (rfd->prev)
+ rfd->prev->next = rfd->next;
+ else
+ sel->rhead = rfd->next;
+ if (rfd->next)
+ rfd->next->prev = rfd->prev;
+ else
+ sel->rtail = rfd->prev;
+
+ ret = rfd->fd;
+ sfree(rfd);
+ return ret;
+}
+
+void sel_free(sel *sel)
+{
+ while (sel->whead)
+ sel_wfd_delete(sel->whead);
+ while (sel->rhead)
+ sel_rfd_delete(sel->rhead);
+ sfree(sel);
+}
+
+void *sel_get_ctx(sel *sel) { return sel->ctx; }
+void sel_set_ctx(sel *sel, void *ctx) { sel->ctx = ctx; }
+void *sel_wfd_get_ctx(sel_wfd *wfd) { return wfd->ctx; }
+void sel_wfd_set_ctx(sel_wfd *wfd, void *ctx) { wfd->ctx = ctx; }
+void *sel_rfd_get_ctx(sel_rfd *rfd) { return rfd->ctx; }
+void sel_rfd_set_ctx(sel_rfd *rfd, void *ctx) { rfd->ctx = ctx; }
+
+int sel_iterate(sel *sel, long timeout)
+{
+ sel_rfd *rfd;
+ sel_wfd *wfd;
+ fd_set rset, wset;
+ int maxfd = 0;
+ struct timeval tv, *ptv;
+ char buf[65536];
+ int ret;
+
+ FD_ZERO(&rset);
+ FD_ZERO(&wset);
+
+ for (rfd = sel->rhead; rfd; rfd = rfd->next) {
+ if (rfd->fd >= 0 && !rfd->frozen) {
+ FD_SET(rfd->fd, &rset);
+ if (maxfd < rfd->fd + 1)
+ maxfd = rfd->fd + 1;
+ }
+ }
+
+ for (wfd = sel->whead; wfd; wfd = wfd->next) {
+ if (wfd->fd >= 0 && bufchain_size(&wfd->buf)) {
+ FD_SET(wfd->fd, &wset);
+ if (maxfd < wfd->fd + 1)
+ maxfd = wfd->fd + 1;
+ }
+ }
+
+ if (timeout < 0) {
+ ptv = NULL;
+ } else {
+ ptv = &tv;
+ tv.tv_sec = timeout / 1000;
+ tv.tv_usec = 1000 * (timeout % 1000);
+ }
+
+ do {
+ ret = select(maxfd, &rset, &wset, NULL, ptv);
+ } while (ret < 0 && (errno == EINTR || errno == EAGAIN));
+
+ if (ret < 0)
+ return errno;
+
+ /*
+ * Just in case one of the callbacks destroys an rfd or wfd we
+ * had yet to get round to, we must loop from the start every
+ * single time. Algorithmically irritating, but necessary
+ * unless we want to store the rfd structures in a heavyweight
+ * tree sorted by fd. And let's face it, if we cared about
+ * good algorithmic complexity it's not at all clear we'd be
+ * using select in the first place.
+ */
+ do {
+ for (wfd = sel->whead; wfd; wfd = wfd->next)
+ if (wfd->fd >= 0 && FD_ISSET(wfd->fd, &wset)) {
+ void *data;
+ size_t len;
+
+ FD_CLR(wfd->fd, &wset);
+ bufchain_prefix(&wfd->buf, &data, &len);
+ ret = write(wfd->fd, data, len);
+ assert(ret != 0);
+ if (ret < 0) {
+ if (wfd->writeerr)
+ wfd->writeerr(wfd, errno);
+ } else {
+ bufchain_consume(&wfd->buf, len);
+ if (wfd->written)
+ wfd->written(wfd, bufchain_size(&wfd->buf));
+ }
+ break;
+ }
+ } while (wfd);
+ do {
+ for (rfd = sel->rhead; rfd; rfd = rfd->next)
+ if (rfd->fd >= 0 && !rfd->frozen && FD_ISSET(rfd->fd, &rset)) {
+ FD_CLR(rfd->fd, &rset);
+ ret = read(rfd->fd, buf, sizeof(buf));
+ if (ret < 0) {
+ if (rfd->readerr)
+ rfd->readerr(rfd, errno);
+ } else {
+ if (rfd->readdata)
+ rfd->readdata(rfd, buf, ret);
+ }
+ break;
+ }
+ } while (rfd);
+
+ return 0;
+}