--- /dev/null
+/* Socket event dispatching library - by D.C. van Moolenbroek */
+
+#include <minix/drivers.h>
+#include <minix/sockdriver.h>
+#include <minix/sockevent.h>
+#include <sys/ioctl.h>
+
+#include "sockevent_proc.h"
+
+#define US 1000000UL /* microseconds per second */
+
+#define SOCKHASH_SLOTS 256 /* # slots in ID-to-sock hash table */
+
+static SLIST_HEAD(, sock) sockhash[SOCKHASH_SLOTS];
+
+static SLIST_HEAD(, sock) socktimer;
+
+static minix_timer_t sockevent_timer;
+
+static SIMPLEQ_HEAD(, sock) sockevent_pending;
+
+static sockevent_socket_cb_t sockevent_socket_cb = NULL;
+
+static int sockevent_working;
+
+static void socktimer_del(struct sock * sock);
+static void sockevent_cancel_send(struct sock * sock,
+ struct sockevent_proc * spr, int err);
+static void sockevent_cancel_recv(struct sock * sock,
+ struct sockevent_proc * spr, int err);
+
+/*
+ * Initialize the hash table of sock objects.
+ */
+static void
+sockhash_init(void)
+{
+ unsigned int slot;
+
+ for (slot = 0; slot < __arraycount(sockhash); slot++)
+ SLIST_INIT(&sockhash[slot]);
+}
+
+/*
+ * Given a socket identifier, return a hash table slot number.
+ */
+static unsigned int
+sockhash_slot(sockid_t id)
+{
+
+ /*
+ * The idea of the shift is that a socket driver may offer multiple
+ * classes of sockets, and put the class in the higher bits. The shift
+ * aims to prevent that all classes' first sockets end up in the same
+ * hash slot.
+ */
+ return (id + (id >> 16)) % SOCKHASH_SLOTS;
+}
+
+/*
+ * Obtain a sock object from the hash table using its unique identifier.
+ * Return a pointer to the object if found, or NULL otherwise.
+ */
+static struct sock *
+sockhash_get(sockid_t id)
+{
+ struct sock *sock;
+ unsigned int slot;
+
+ slot = sockhash_slot(id);
+
+ SLIST_FOREACH(sock, &sockhash[slot], sock_hash) {
+ if (sock->sock_id == id)
+ return sock;
+ }
+
+ return NULL;
+}
+
+/*
+ * Add a sock object to the hash table. The sock object must have a valid ID
+ * in its 'sock_id' field, and must not be in the hash table already.
+ */
+static void
+sockhash_add(struct sock * sock)
+{
+ unsigned int slot;
+
+ slot = sockhash_slot(sock->sock_id);
+
+ SLIST_INSERT_HEAD(&sockhash[slot], sock, sock_hash);
+}
+
+/*
+ * Remove a sock object from the hash table. The sock object must be in the
+ * hash table.
+ */
+static void
+sockhash_del(struct sock * sock)
+{
+ unsigned int slot;
+
+ slot = sockhash_slot(sock->sock_id);
+
+ /* This macro is O(n). */
+ SLIST_REMOVE(&sockhash[slot], sock, sock, sock_hash);
+}
+
+/*
+ * Reset a socket object to a proper initial state, with a particular socket
+ * identifier, a SOCK_ type, and a socket operations table. The socket is
+ * added to the ID-to-object hash table. This function always succeeds.
+ */
+static void
+sockevent_reset(struct sock * sock, sockid_t id, int domain, int type,
+ const struct sockevent_ops * ops)
+{
+
+ assert(sock != NULL);
+
+ memset(sock, 0, sizeof(*sock));
+
+ sock->sock_id = id;
+ sock->sock_domain = domain;
+ sock->sock_type = type;
+
+ sock->sock_slowat = 1;
+ sock->sock_rlowat = 1;
+
+ sock->sock_ops = ops;
+ sock->sock_proc = NULL;
+ sock->sock_select.ss_endpt = NONE;
+
+ sockhash_add(sock);
+}
+
+/*
+ * Initialize a new socket that will serve as an accepted socket on the given
+ * listening socket 'sock'. The new socket is given as 'newsock', and its new
+ * socket identifier is given as 'newid'. This function always succeeds.
+ */
+void
+sockevent_clone(struct sock * sock, struct sock * newsock, sockid_t newid)
+{
+
+ sockevent_reset(newsock, newid, (int)sock->sock_domain,
+ sock->sock_type, sock->sock_ops);
+
+ /* These are the settings that are currently inherited. */
+ newsock->sock_opt = sock->sock_opt & ~SO_ACCEPTCONN;
+ newsock->sock_linger = sock->sock_linger;
+ newsock->sock_stimeo = sock->sock_stimeo;
+ newsock->sock_rtimeo = sock->sock_rtimeo;
+ newsock->sock_slowat = sock->sock_slowat;
+ newsock->sock_rlowat = sock->sock_rlowat;
+
+ newsock->sock_flags |= SFL_CLONED;
+}
+
+/*
+ * A new socket has just been accepted. The corresponding listening socket is
+ * given as 'sock'. The new socket has ID 'newid', and if it had not already
+ * been added to the hash table through sockevent_clone() before, 'newsock' is
+ * a non-NULL pointer which identifies the socket object to clone into.
+ */
+static void
+sockevent_accepted(struct sock * sock, struct sock * newsock, sockid_t newid)
+{
+
+ if (newsock == NULL) {
+ if ((newsock = sockhash_get(newid)) == NULL)
+ panic("libsockdriver: socket driver returned unknown "
+ "ID %d from accept callback", newid);
+ } else
+ sockevent_clone(sock, newsock, newid);
+
+ assert(newsock->sock_flags & SFL_CLONED);
+ newsock->sock_flags &= ~SFL_CLONED;
+}
+
+/*
+ * Allocate a sock object, by asking the socket driver for one. On success,
+ * return OK, with a pointer to the new object stored in 'sockp'. This new
+ * object has all its fields set to initial values, in part based on the given
+ * parameters. On failure, return an error code. Failure has two typical
+ * cause: either the given domain, type, protocol combination is not supported,
+ * or the socket driver is out of sockets (globally or for this combination).
+ */
+static int
+sockevent_alloc(int domain, int type, int protocol, endpoint_t user_endpt,
+ struct sock ** sockp)
+{
+ struct sock *sock;
+ const struct sockevent_ops *ops;
+ sockid_t r;
+
+ /*
+ * Verify that the given domain is sane. Unlike the type and protocol,
+ * the domain is already verified by VFS, so we do not limit ourselves
+ * here. The result is that we can store the domain in just a byte.
+ */
+ if (domain < 0 || domain > UINT8_MAX)
+ return EAFNOSUPPORT;
+
+ /* Make sure that the library has actually been initialized. */
+ if (sockevent_socket_cb == NULL)
+ panic("libsockevent: not initialized");
+
+ sock = NULL;
+ ops = NULL;
+
+ /*
+ * Ask the socket driver to create a socket for the given combination
+ * of domain, type, and protocol. If so, let it return a new sock
+ * object, a unique socket identifier for that object, and an
+ * operations table for it.
+ */
+ if ((r = sockevent_socket_cb(domain, type, protocol, user_endpt, &sock,
+ &ops)) < 0)
+ return r;
+
+ assert(sock != NULL);
+ assert(ops != NULL);
+
+ sockevent_reset(sock, r, domain, type, ops);
+
+ *sockp = sock;
+ return OK;
+}
+
+/*
+ * Free a previously allocated sock object.
+ */
+static void
+sockevent_free(struct sock * sock)
+{
+ const struct sockevent_ops *ops;
+
+ assert(sock->sock_proc == NULL);
+
+ socktimer_del(sock);
+
+ sockhash_del(sock);
+
+ /*
+ * Invalidate the operations table on the socket, before freeing the
+ * socket. This allows us to detect cases where sockevent functions
+ * are called on sockets that have already been freed.
+ */
+ ops = sock->sock_ops;
+ sock->sock_ops = NULL;
+
+ assert(ops != NULL);
+ assert(ops->sop_free != NULL);
+
+ ops->sop_free(sock);
+}
+
+/*
+ * Create a new socket.
+ */
+static sockid_t
+sockevent_socket(int domain, int type, int protocol, endpoint_t user_endpt)
+{
+ struct sock *sock;
+ int r;
+
+ if ((r = sockevent_alloc(domain, type, protocol, user_endpt,
+ &sock)) != OK)
+ return r;
+
+ return sock->sock_id;
+}
+
+/*
+ * Create a pair of connected sockets.
+ */
+static int
+sockevent_socketpair(int domain, int type, int protocol, endpoint_t user_endpt,
+ sockid_t id[2])
+{
+ struct sock *sock1, *sock2;
+ int r;
+
+ if ((r = sockevent_alloc(domain, type, protocol, user_endpt,
+ &sock1)) != OK)
+ return r;
+
+ /* Creating socket pairs is not always supported. */
+ if (sock1->sock_ops->sop_pair == NULL) {
+ sockevent_free(sock1);
+
+ return EOPNOTSUPP;
+ }
+
+ if ((r = sockevent_alloc(domain, type, protocol, user_endpt,
+ &sock2)) != OK) {
+ sockevent_free(sock1);
+
+ return r;
+ }
+
+ assert(sock1->sock_ops == sock2->sock_ops);
+
+ r = sock1->sock_ops->sop_pair(sock1, sock2, user_endpt);
+
+ if (r != OK) {
+ sockevent_free(sock2);
+ sockevent_free(sock1);
+
+ return r;
+ }
+
+ id[0] = sock1->sock_id;
+ id[1] = sock2->sock_id;
+ return OK;
+}
+
+/*
+ * A send request returned EPIPE. If desired, send a SIGPIPE signal to the
+ * user process that issued the request.
+ */
+static void
+sockevent_sigpipe(struct sock * sock, endpoint_t user_endpt, int flags)
+{
+
+ /*
+ * POSIX says that pipe signals should be generated for SOCK_STREAM
+ * sockets. Linux does just this, NetBSD raises signals for all socket
+ * types.
+ */
+ if (sock->sock_type != SOCK_STREAM)
+ return;
+
+ /*
+ * Why would there be fewer than four ways to do the same thing?
+ * O_NOSIGPIPE, MSG_NOSIGNAL, SO_NOSIGPIPE, and of course blocking
+ * SIGPIPE. VFS already sets MSG_NOSIGNAL for calls on sockets with
+ * O_NOSIGPIPE. The fact that SO_NOSIGPIPE is a thing, is also the
+ * reason why we cannot let VFS handle signal generation altogether.
+ */
+ if (flags & MSG_NOSIGNAL)
+ return;
+ if (sock->sock_opt & SO_NOSIGPIPE)
+ return;
+
+ /*
+ * Send a SIGPIPE signal to the user process. Unfortunately we cannot
+ * guarantee that the SIGPIPE reaches the user process before the send
+ * call returns. Usually, the scheduling priorities of system services
+ * are such that the signal is likely to arrive first anyway, but if
+ * timely arrival of the signal is required, a more fundamental change
+ * to the system would be needed.
+ */
+ sys_kill(user_endpt, SIGPIPE);
+}
+
+/*
+ * Suspend a request without data, that is, a bind, connect, accept, or close
+ * request.
+ */
+static void
+sockevent_suspend(struct sock * sock, unsigned int event,
+ const struct sockdriver_call * __restrict call, endpoint_t user_endpt)
+{
+ struct sockevent_proc *spr, **sprp;
+
+ /* There is one slot for each process, so this should never fail. */
+ if ((spr = sockevent_proc_alloc()) == NULL)
+ panic("libsockevent: too many suspended processes");
+
+ spr->spr_next = NULL;
+ spr->spr_event = event;
+ spr->spr_timer = FALSE;
+ spr->spr_call = *call;
+ spr->spr_endpt = user_endpt;
+
+ /*
+ * Add the request to the tail of the queue. This operation is O(n),
+ * but the number of suspended requests per socket is expected to be
+ * low at all times.
+ */
+ for (sprp = &sock->sock_proc; *sprp != NULL;
+ sprp = &(*sprp)->spr_next);
+ *sprp = spr;
+}
+
+/*
+ * Suspend a request with data, that is, a send or receive request.
+ */
+static void
+sockevent_suspend_data(struct sock * sock, unsigned int event, int timer,
+ const struct sockdriver_call * __restrict call, endpoint_t user_endpt,
+ const struct sockdriver_data * __restrict data, size_t len, size_t off,
+ const struct sockdriver_data * __restrict ctl, socklen_t ctl_len,
+ socklen_t ctl_off, int flags, int rflags, clock_t time)
+{
+ struct sockevent_proc *spr, **sprp;
+
+ /* There is one slot for each process, so this should never fail. */
+ if ((spr = sockevent_proc_alloc()) == NULL)
+ panic("libsockevent: too many suspended processes");
+
+ spr->spr_next = NULL;
+ spr->spr_event = event;
+ spr->spr_timer = timer;
+ spr->spr_call = *call;
+ spr->spr_endpt = user_endpt;
+ sockdriver_pack_data(&spr->spr_data, call, data, len);
+ spr->spr_datalen = len;
+ spr->spr_dataoff = off;
+ sockdriver_pack_data(&spr->spr_ctl, call, ctl, ctl_len);
+ spr->spr_ctllen = ctl_len;
+ spr->spr_ctloff = ctl_off;
+ spr->spr_flags = flags;
+ spr->spr_rflags = rflags;
+ spr->spr_time = time;
+
+ /*
+ * Add the request to the tail of the queue. This operation is O(n),
+ * but the number of suspended requests per socket is expected to be
+ * low at all times.
+ */
+ for (sprp = &sock->sock_proc; *sprp != NULL;
+ sprp = &(*sprp)->spr_next);
+ *sprp = spr;
+}
+
+/*
+ * Return TRUE if there are any suspended requests on the given socket's queue
+ * that match any of the events in the given event mask, or FALSE otherwise.
+ */
+static int
+sockevent_has_suspended(struct sock * sock, unsigned int mask)
+{
+ struct sockevent_proc *spr;
+
+ for (spr = sock->sock_proc; spr != NULL; spr = spr->spr_next)
+ if (spr->spr_event & mask)
+ return TRUE;
+
+ return FALSE;
+}
+
+/*
+ * Check whether the given call is on the given socket's queue of suspended
+ * requests. If so, remove it from the queue and return a pointer to the
+ * suspension data structure. The caller is then responsible for freeing that
+ * data structure using sockevent_proc_free(). If the call was not found, the
+ * function returns NULL.
+ */
+static struct sockevent_proc *
+sockevent_unsuspend(struct sock * sock, const struct sockdriver_call * call)
+{
+ struct sockevent_proc *spr, **sprp;
+
+ /* Find the suspended request being canceled. */
+ for (sprp = &sock->sock_proc; (spr = *sprp) != NULL;
+ sprp = &spr->spr_next) {
+ if (spr->spr_call.sc_endpt == call->sc_endpt &&
+ spr->spr_call.sc_req == call->sc_req) {
+ /* Found; remove and return it. */
+ *sprp = spr->spr_next;
+
+ return spr;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Attempt to resume the given suspended request for the given socket object.
+ * Return TRUE if the suspended request has been fully resumed and can be
+ * removed from the queue of suspended requests, or FALSE if it has not been
+ * fully resumed and should stay on the queue. In the latter case, no
+ * resumption will be attempted for other suspended requests of the same type.
+ */
+static int
+sockevent_resume(struct sock * sock, struct sockevent_proc * spr)
+{
+ struct sock *newsock;
+ struct sockdriver_data data, ctl;
+ char addr[SOCKADDR_MAX];
+ socklen_t addr_len;
+ size_t len, min;
+ sockid_t r;
+
+ switch (spr->spr_event) {
+ case SEV_CONNECT:
+ /*
+ * If the connect call was suspended for the purpose of
+ * intercepting resumption, simply remove it from the queue.
+ */
+ if (spr->spr_call.sc_endpt == NONE)
+ return TRUE;
+
+ /* FALLTHROUGH */
+ case SEV_BIND:
+ if ((r = sock->sock_err) != OK)
+ sock->sock_err = OK;
+
+ sockdriver_reply_generic(&spr->spr_call, r);
+
+ return TRUE;
+
+ case SEV_ACCEPT:
+ /*
+ * A previous accept call may not have blocked on a socket that
+ * was not in listening mode.
+ */
+ assert(sock->sock_opt & SO_ACCEPTCONN);
+
+ addr_len = 0;
+ newsock = NULL;
+
+ /*
+ * This call is suspended, which implies that the call table
+ * pointer has already tested to be non-NULL.
+ */
+ if ((r = sock->sock_ops->sop_accept(sock,
+ (struct sockaddr *)&addr, &addr_len, spr->spr_endpt,
+ &newsock)) == SUSPEND)
+ return FALSE;
+
+ if (r >= 0) {
+ assert(addr_len <= sizeof(addr));
+
+ sockevent_accepted(sock, newsock, r);
+ }
+
+ sockdriver_reply_accept(&spr->spr_call, r,
+ (struct sockaddr *)&addr, addr_len);
+
+ return TRUE;
+
+ case SEV_SEND:
+ if (sock->sock_err != OK || (sock->sock_flags & SFL_SHUT_WR)) {
+ if (spr->spr_dataoff > 0 || spr->spr_ctloff > 0)
+ r = (int)spr->spr_dataoff;
+ else if ((r = sock->sock_err) != OK)
+ sock->sock_err = OK;
+ else
+ r = EPIPE;
+ } else {
+ sockdriver_unpack_data(&data, &spr->spr_call,
+ &spr->spr_data, spr->spr_datalen);
+ sockdriver_unpack_data(&ctl, &spr->spr_call,
+ &spr->spr_ctl, spr->spr_ctllen);
+
+ len = spr->spr_datalen - spr->spr_dataoff;
+
+ min = sock->sock_slowat;
+ if (min > len)
+ min = len;
+
+ /*
+ * As mentioned elsewhere, we do not save the address
+ * upon suspension so we cannot supply it anymore here.
+ */
+ r = sock->sock_ops->sop_send(sock, &data, len,
+ &spr->spr_dataoff, &ctl,
+ spr->spr_ctllen - spr->spr_ctloff,
+ &spr->spr_ctloff, NULL, 0, spr->spr_endpt,
+ spr->spr_flags, min);
+
+ assert(r <= 0);
+
+ if (r == SUSPEND)
+ return FALSE;
+
+ /*
+ * If an error occurred but some data were already
+ * sent, return the progress rather than the error.
+ * Note that if the socket driver detects an
+ * asynchronous error during the send, it itself must
+ * perform this check and call sockevent_set_error() as
+ * needed, to make sure the error does not get lost.
+ */
+ if (spr->spr_dataoff > 0 || spr->spr_ctloff > 0)
+ r = spr->spr_dataoff;
+ }
+
+ if (r == EPIPE)
+ sockevent_sigpipe(sock, spr->spr_endpt,
+ spr->spr_flags);
+
+ sockdriver_reply_generic(&spr->spr_call, r);
+
+ return TRUE;
+
+ case SEV_RECV:
+ addr_len = 0;
+
+ if (sock->sock_flags & SFL_SHUT_RD)
+ r = SOCKEVENT_EOF;
+ else {
+ len = spr->spr_datalen - spr->spr_dataoff;
+
+ if (sock->sock_err == OK) {
+ min = sock->sock_rlowat;
+ if (min > len)
+ min = len;
+ } else
+ min = 0;
+
+ sockdriver_unpack_data(&data, &spr->spr_call,
+ &spr->spr_data, spr->spr_datalen);
+ sockdriver_unpack_data(&ctl, &spr->spr_call,
+ &spr->spr_ctl, spr->spr_ctllen);
+
+ r = sock->sock_ops->sop_recv(sock, &data, len,
+ &spr->spr_dataoff, &ctl,
+ spr->spr_ctllen - spr->spr_ctloff,
+ &spr->spr_ctloff, (struct sockaddr *)&addr,
+ &addr_len, spr->spr_endpt, spr->spr_flags, min,
+ &spr->spr_rflags);
+
+ /*
+ * If the call remains suspended but a socket error is
+ * pending, return the pending socket error instead.
+ */
+ if (r == SUSPEND) {
+ if (sock->sock_err == OK)
+ return FALSE;
+
+ r = SOCKEVENT_EOF;
+ }
+
+ assert(addr_len <= sizeof(addr));
+ }
+
+ /*
+ * If the receive call reported success, or if some data were
+ * already received, return the (partial) result. Otherwise,
+ * return a pending error if any, or otherwise a regular error
+ * or 0 for EOF.
+ */
+ if (r == OK || spr->spr_dataoff > 0 || spr->spr_ctloff > 0)
+ r = (int)spr->spr_dataoff;
+ else if (sock->sock_err != OK) {
+ r = sock->sock_err;
+
+ sock->sock_err = OK;
+ } else if (r == SOCKEVENT_EOF)
+ r = 0; /* EOF */
+
+ sockdriver_reply_recv(&spr->spr_call, r, spr->spr_ctloff,
+ (struct sockaddr *)&addr, addr_len, spr->spr_rflags);
+
+ return TRUE;
+
+ case SEV_CLOSE:
+ sockdriver_reply_generic(&spr->spr_call, OK);
+
+ return TRUE;
+
+ default:
+ panic("libsockevent: process suspended on unknown event 0x%x",
+ spr->spr_event);
+ }
+}
+
+/*
+ * Return TRUE if the given socket is ready for reading for a select call, or
+ * FALSE otherwise.
+ */
+static int
+sockevent_test_readable(struct sock * sock)
+{
+ int r;
+
+ /*
+ * The meaning of "ready-to-read" depends on whether the socket is a
+ * listening socket or not. For the former, it is a test on whether
+ * there are any new sockets to accept. However, shutdown flags take
+ * precedence in both cases.
+ */
+ if (sock->sock_flags & SFL_SHUT_RD)
+ return TRUE;
+
+ if (sock->sock_err != OK)
+ return TRUE;
+
+ /*
+ * Depending on whether this is a listening-mode socket, test whether
+ * either accepts or receives would block.
+ */
+ if (sock->sock_opt & SO_ACCEPTCONN) {
+ if (sock->sock_ops->sop_test_accept == NULL)
+ return TRUE;
+
+ r = sock->sock_ops->sop_test_accept(sock);
+ } else {
+ if (sock->sock_ops->sop_test_recv == NULL)
+ return TRUE;
+
+ r = sock->sock_ops->sop_test_recv(sock, sock->sock_rlowat,
+ NULL);
+ }
+
+ return (r != SUSPEND);
+}
+
+/*
+ * Return TRUE if the given socket is ready for writing for a select call, or
+ * FALSE otherwise.
+ */
+static int
+sockevent_test_writable(struct sock * sock)
+{
+ int r;
+
+ if (sock->sock_err != OK)
+ return TRUE;
+
+ if (sock->sock_flags & SFL_SHUT_WR)
+ return TRUE;
+
+ if (sock->sock_ops->sop_test_send == NULL)
+ return TRUE;
+
+ /*
+ * Test whether sends would block. The low send watermark is relevant
+ * for stream-type sockets only.
+ */
+ r = sock->sock_ops->sop_test_send(sock, sock->sock_slowat);
+
+ return (r != SUSPEND);
+}
+
+/*
+ * Test whether any of the given select operations are ready on the given
+ * socket. Return the subset of ready operations; zero if none.
+ */
+static unsigned int
+sockevent_test_select(struct sock * sock, unsigned int ops)
+{
+ unsigned int ready_ops;
+
+ assert(!(ops & ~(SDEV_OP_RD | SDEV_OP_WR | SDEV_OP_ERR)));
+
+ /*
+ * We do not support the "bind in progress" case here. If a blocking
+ * bind call is in progress, the file descriptor should not be ready
+ * for either reading or writing. Currently, socket drivers will have
+ * to cover this case themselves. Otherwise we would have to check the
+ * queue of suspended calls, or create a custom flag for this.
+ */
+
+ ready_ops = 0;
+
+ if ((ops & SDEV_OP_RD) && sockevent_test_readable(sock))
+ ready_ops |= SDEV_OP_RD;
+
+ if ((ops & SDEV_OP_WR) && sockevent_test_writable(sock))
+ ready_ops |= SDEV_OP_WR;
+
+ /* TODO: OOB receive support. */
+
+ return ready_ops;
+}
+
+/*
+ * Fire the given mask of events on the given socket object now.
+ */
+static void
+sockevent_fire(struct sock * sock, unsigned int mask)
+{
+ struct sockevent_proc *spr, **sprp;
+ unsigned int r, flag, ops;
+
+ /*
+ * A completed connection attempt (successful or not) also always
+ * implies that the socket becomes writable. For convenience we
+ * enforce this rule here, because it is easy to forget. Note that in
+ * any case, a suspended connect request should be the first in the
+ * list, so we do not risk returning 0 from a connect call as a result
+ * of sock_err getting eaten by another resumed call.
+ */
+ if (mask & SEV_CONNECT)
+ mask |= SEV_SEND;
+
+ /*
+ * First try resuming regular system calls.
+ */
+ for (sprp = &sock->sock_proc; (spr = *sprp) != NULL; ) {
+ flag = spr->spr_event;
+
+ if ((mask & flag) && sockevent_resume(sock, spr)) {
+ *sprp = spr->spr_next;
+
+ sockevent_proc_free(spr);
+ } else {
+ mask &= ~flag;
+
+ sprp = &spr->spr_next;
+ }
+ }
+
+ /*
+ * Then see if we can satisfy pending select queries.
+ */
+ if ((mask & (SEV_ACCEPT | SEV_SEND | SEV_RECV)) &&
+ sock->sock_select.ss_endpt != NONE) {
+ assert(sock->sock_selops != 0);
+
+ /*
+ * Only retest select operations that, based on the given event
+ * mask, could possibly be satisfied now.
+ */
+ ops = sock->sock_selops;
+ if (!(mask & (SEV_ACCEPT | SEV_RECV)))
+ ops &= ~SDEV_OP_RD;
+ if (!(mask & SEV_SEND))
+ ops &= ~SDEV_OP_WR;
+ if (!(0)) /* TODO: OOB receive support */
+ ops &= ~SDEV_OP_ERR;
+
+ /* Are there any operations to test? */
+ if (ops != 0) {
+ /* Test those operations. */
+ r = sockevent_test_select(sock, ops);
+
+ /* Were any satisfied? */
+ if (r != 0) {
+ /* Let the caller know. */
+ sockdriver_reply_select(&sock->sock_select,
+ sock->sock_id, r);
+
+ sock->sock_selops &= ~r;
+
+ /* Are there any saved operations left now? */
+ if (sock->sock_selops == 0)
+ sock->sock_select.ss_endpt = NONE;
+ }
+ }
+ }
+
+ /*
+ * Finally, a SEV_CLOSE event unconditionally frees the sock object.
+ * This event should be fired only for sockets that are either not yet,
+ * or not anymore, in use by userland.
+ */
+ if (mask & SEV_CLOSE) {
+ assert(sock->sock_flags & (SFL_CLONED | SFL_CLOSING));
+
+ sockevent_free(sock);
+ }
+}
+
+/*
+ * Process all pending events. Events must still be blocked, so that if
+ * handling one event generates a new event, that event is handled from here
+ * rather than immediately.
+ */
+static void
+sockevent_pump(void)
+{
+ struct sock *sock;
+ unsigned int mask;
+
+ assert(sockevent_working);
+
+ while (!SIMPLEQ_EMPTY(&sockevent_pending)) {
+ sock = SIMPLEQ_FIRST(&sockevent_pending);
+ SIMPLEQ_REMOVE_HEAD(&sockevent_pending, sock_next);
+
+ mask = sock->sock_events;
+ assert(mask != 0);
+ sock->sock_events = 0;
+
+ sockevent_fire(sock, mask);
+ /*
+ * At this point, the sock object may already have been readded
+ * to the event list, or even be deallocated altogether.
+ */
+ }
+}
+
+/*
+ * Return TRUE if any events are pending on any sockets, or FALSE otherwise.
+ */
+static int
+sockevent_has_events(void)
+{
+
+ return (!SIMPLEQ_EMPTY(&sockevent_pending));
+}
+
+/*
+ * Raise the given bitwise-OR'ed set of events on the given socket object.
+ * Depending on the context of the call, they events may or may not be
+ * processed immediately.
+ */
+void
+sockevent_raise(struct sock * sock, unsigned int mask)
+{
+
+ assert(sock->sock_ops != NULL);
+
+ /*
+ * Handle SEV_CLOSE first. This event must not be deferred, so as to
+ * let socket drivers recycle sock objects as they are needed. For
+ * example, a user-closed TCP socket may stay open to transmit the
+ * remainder of its send buffer, until the TCP driver runs out of
+ * sockets, in which case the connection is aborted. The driver would
+ * then raise SEV_CLOSE on the sock object so as to clean it up, and
+ * immediately reuse it afterward. If the close event were to be
+ * deferred, this immediate reuse would not be possible.
+ *
+ * The sop_free() callback routine may not raise new events, and thus,
+ * the state of 'sockevent_working' need not be checked or set here.
+ */
+ if (mask & SEV_CLOSE) {
+ assert(mask == SEV_CLOSE);
+
+ sockevent_fire(sock, mask);
+
+ return;
+ }
+
+ /*
+ * If we are currently processing a socket message, store the event for
+ * later. If not, this call is not coming from inside libsockevent,
+ * and we must handle the event immediately.
+ */
+ if (sockevent_working) {
+ assert(mask != 0);
+ assert(mask <= UCHAR_MAX); /* sock_events field size check */
+
+ if (sock->sock_events == 0)
+ SIMPLEQ_INSERT_TAIL(&sockevent_pending, sock,
+ sock_next);
+
+ sock->sock_events |= mask;
+ } else {
+ sockevent_working = TRUE;
+
+ sockevent_fire(sock, mask);
+
+ if (sockevent_has_events())
+ sockevent_pump();
+
+ sockevent_working = FALSE;
+ }
+}
+
+/*
+ * Set a pending error on the socket object, and wake up any suspended
+ * operations that are affected by this.
+ */
+void
+sockevent_set_error(struct sock * sock, int err)
+{
+
+ assert(err < 0);
+ assert(sock->sock_ops != NULL);
+
+ /* If an error was set already, it will be overridden. */
+ sock->sock_err = err;
+
+ sockevent_raise(sock, SEV_BIND | SEV_CONNECT | SEV_SEND | SEV_RECV);
+}
+
+/*
+ * Initialize timer-related data structures.
+ */
+static void
+socktimer_init(void)
+{
+
+ SLIST_INIT(&socktimer);
+
+ init_timer(&sockevent_timer);
+}
+
+/*
+ * Check whether the given socket object has any suspended requests that have
+ * now expired. If so, cancel them. Also, if the socket object has any
+ * suspended requests with a timeout that has not yet expired, return the
+ * earliest (relative) timeout of all of them, or TMR_NEVER if no such requests
+ * are present.
+ */
+static clock_t
+sockevent_expire(struct sock * sock, clock_t now)
+{
+ struct sockevent_proc *spr, **sprp;
+ clock_t lowest, left;
+ int r;
+
+ /*
+ * First handle the case that the socket is closed. In this case,
+ * there may be a linger timer, although the socket may also simply
+ * still be on the timer list because of a request that did not time
+ * out right before the socket was closed.
+ */
+ if (sock->sock_flags & SFL_CLOSING) {
+ /* Was there a linger timer and has it expired? */
+ if ((sock->sock_opt & SO_LINGER) &&
+ tmr_is_first(sock->sock_linger, now)) {
+ assert(sock->sock_ops->sop_close != NULL);
+
+ /*
+ * Whatever happens next, we must now resume the
+ * pending close operation, if it was not canceled
+ * earlier. As before, we return OK rather than the
+ * standardized EWOULDBLOCK, to ensure that the user
+ * process knows the file descriptor has been closed.
+ */
+ if ((spr = sock->sock_proc) != NULL) {
+ assert(spr->spr_event == SEV_CLOSE);
+ assert(spr->spr_next == NULL);
+
+ sock->sock_proc = NULL;
+
+ sockdriver_reply_generic(&spr->spr_call, OK);
+
+ sockevent_proc_free(spr);
+ }
+
+ /*
+ * Tell the socket driver that closing the socket is
+ * now a bit more desired than the last time we asked.
+ */
+ r = sock->sock_ops->sop_close(sock, TRUE /*force*/);
+
+ assert(r == OK || r == SUSPEND);
+
+ /*
+ * The linger timer fires once. After that, the socket
+ * driver is free to decide that it still will not
+ * close the socket. If it does, do not fire the
+ * linger timer again.
+ */
+ if (r == SUSPEND)
+ sock->sock_opt &= ~SO_LINGER;
+ else
+ sockevent_free(sock);
+ }
+
+ return TMR_NEVER;
+ }
+
+ /*
+ * Then see if any send and/or receive requests have expired. Also see
+ * if there are any send and/or receive requests left that have not yet
+ * expired but do have a timeout, so that we can return the lowest of
+ * those timeouts.
+ */
+ lowest = TMR_NEVER;
+
+ for (sprp = &sock->sock_proc; (spr = *sprp) != NULL; ) {
+ /* Skip requests without a timeout. */
+ if (spr->spr_timer == 0) {
+ sprp = &spr->spr_next;
+
+ continue;
+ }
+
+ assert(spr->spr_event == SEV_SEND ||
+ spr->spr_event == SEV_RECV);
+
+ /*
+ * If the request has expired, cancel it and remove it from the
+ * list. Otherwise, see if the request has the lowest number
+ * of ticks until its timeout so far.
+ */
+ if (tmr_is_first(spr->spr_time, now)) {
+ *sprp = spr->spr_next;
+
+ if (spr->spr_event == SEV_SEND)
+ sockevent_cancel_send(sock, spr, EWOULDBLOCK);
+ else
+ sockevent_cancel_recv(sock, spr, EWOULDBLOCK);
+
+ sockevent_proc_free(spr);
+ } else {
+ left = spr->spr_time - now;
+
+ if (lowest == TMR_NEVER || lowest > left)
+ lowest = left;
+
+ sprp = &spr->spr_next;
+ }
+ }
+
+ return lowest;
+}
+
+/*
+ * The socket event alarm went off. Go through the set of socket objects with
+ * timers, and see if any of their requests have now expired. Set a new alarm
+ * as necessary.
+ */
+static void
+socktimer_expire(int arg __unused)
+{
+ SLIST_HEAD(, sock) oldtimer;
+ struct sock *sock, *tsock;
+ clock_t now, lowest, left;
+ int working;
+
+ /*
+ * This function may or may not be called from a context where we are
+ * already deferring events, so we have to cover both cases here.
+ */
+ if ((working = sockevent_working) == FALSE)
+ sockevent_working = TRUE;
+
+ /* Start a new list. */
+ memcpy(&oldtimer, &socktimer, sizeof(oldtimer));
+ SLIST_INIT(&socktimer);
+
+ now = getticks();
+ lowest = TMR_NEVER;
+
+ /*
+ * Go through all sockets that have or had a request with a timeout,
+ * canceling any expired requests and building a new list of sockets
+ * that still have requests with timeouts as we go.
+ */
+ SLIST_FOREACH_SAFE(sock, &oldtimer, sock_timer, tsock) {
+ assert(sock->sock_flags & SFL_TIMER);
+ sock->sock_flags &= ~SFL_TIMER;
+
+ left = sockevent_expire(sock, now);
+ /*
+ * The sock object may already have been deallocated now.
+ * If 'next' is TMR_NEVER, do not touch 'sock' anymore.
+ */
+
+ if (left != TMR_NEVER) {
+ if (lowest == TMR_NEVER || lowest > left)
+ lowest = left;
+
+ SLIST_INSERT_HEAD(&socktimer, sock, sock_timer);
+
+ sock->sock_flags |= SFL_TIMER;
+ }
+ }
+
+ /* If there is a new lowest timeout at all, set a new timer. */
+ if (lowest != TMR_NEVER)
+ set_timer(&sockevent_timer, lowest, socktimer_expire, 0);
+
+ if (!working) {
+ /* If any new events were raised, process them now. */
+ if (sockevent_has_events())
+ sockevent_pump();
+
+ sockevent_working = FALSE;
+ }
+}
+
+/*
+ * Set a timer for the given (relative) number of clock ticks, adding the
+ * associated socket object to the set of socket objects with timers, if it was
+ * not already in that set. Set a new alarm if necessary, and return the
+ * absolute timeout for the timer. Since the timers list is maintained lazily,
+ * the caller need not take the object off the set if the call was canceled
+ * later; see also socktimer_del().
+ */
+static clock_t
+socktimer_add(struct sock * sock, clock_t ticks)
+{
+ clock_t now;
+
+ /*
+ * Relative time comparisons require that any two times are no more
+ * than half the comparison space (clock_t, unsigned long) apart.
+ */
+ assert(ticks <= TMRDIFF_MAX);
+
+ /* If the socket was not already on the timers list, put it on. */
+ if (!(sock->sock_flags & SFL_TIMER)) {
+ SLIST_INSERT_HEAD(&socktimer, sock, sock_timer);
+
+ sock->sock_flags |= SFL_TIMER;
+ }
+
+ /*
+ * (Re)set the timer if either it was not running at all or this new
+ * timeout will occur sooner than the currently scheduled alarm. Note
+ * that setting a timer that was already set is allowed.
+ */
+ now = getticks();
+
+ if (!tmr_is_set(&sockevent_timer) ||
+ tmr_is_first(now + ticks, tmr_exp_time(&sockevent_timer)))
+ set_timer(&sockevent_timer, ticks, socktimer_expire, 0);
+
+ /* Return the absolute timeout. */
+ return now + ticks;
+}
+
+/*
+ * Remove a socket object from the set of socket objects with timers. Since
+ * the timer list is maintained lazily, this needs to be done only right before
+ * the socket object is freed.
+ */
+static void
+socktimer_del(struct sock * sock)
+{
+
+ if (sock->sock_flags & SFL_TIMER) {
+ /* This macro is O(n). */
+ SLIST_REMOVE(&socktimer, sock, sock, sock_timer);
+
+ sock->sock_flags &= ~SFL_TIMER;
+ }
+}
+
+/*
+ * Bind a socket to a local address.
+ */
+static int
+sockevent_bind(sockid_t id, const struct sockaddr * __restrict addr,
+ socklen_t addr_len, endpoint_t user_endpt,
+ const struct sockdriver_call * __restrict call)
+{
+ struct sock *sock;
+ int r;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (sock->sock_ops->sop_bind == NULL)
+ return EOPNOTSUPP;
+
+ /* Binding a socket in listening mode is never supported. */
+ if (sock->sock_opt & SO_ACCEPTCONN)
+ return EINVAL;
+
+ r = sock->sock_ops->sop_bind(sock, addr, addr_len, user_endpt);
+
+ if (r == SUSPEND) {
+ if (call == NULL)
+ return EINPROGRESS;
+
+ sockevent_suspend(sock, SEV_BIND, call, user_endpt);
+ }
+
+ return r;
+}
+
+/*
+ * Connect a socket to a remote address.
+ */
+static int
+sockevent_connect(sockid_t id, const struct sockaddr * __restrict addr,
+ socklen_t addr_len, endpoint_t user_endpt,
+ const struct sockdriver_call * call)
+{
+ struct sockdriver_call fakecall;
+ struct sockevent_proc *spr;
+ struct sock *sock;
+ int r;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (sock->sock_ops->sop_connect == NULL)
+ return EOPNOTSUPP;
+
+ /* Connecting a socket in listening mode is never supported. */
+ if (sock->sock_opt & SO_ACCEPTCONN)
+ return EOPNOTSUPP;
+
+ /*
+ * The upcoming connect call may fire an accept event for which the
+ * handler may in turn fire a connect event on this socket. Since we
+ * delay event processing until after processing calls, this would
+ * create the problem that even if the connection is accepted right
+ * away, non-blocking connect requests would return EINPROGRESS. For
+ * UDS, this is undesirable behavior. To remedy this, we use a hack:
+ * we temporarily suspend the connect even if non-blocking, then
+ * process events, and then cancel the connect request again. If the
+ * connection was accepted immediately, the cancellation will have no
+ * effect, since the request has already been replied to. In order not
+ * to violate libsockdriver rules with this hack, we fabricate a fake
+ * 'conn' object.
+ */
+ r = sock->sock_ops->sop_connect(sock, addr, addr_len, user_endpt);
+
+ if (r == SUSPEND) {
+ if (call != NULL || sockevent_has_events()) {
+ if (call == NULL) {
+ fakecall.sc_endpt = NONE;
+
+ call = &fakecall;
+ }
+
+ assert(!sockevent_has_suspended(sock,
+ SEV_SEND | SEV_RECV));
+
+ sockevent_suspend(sock, SEV_CONNECT, call, user_endpt);
+
+ if (call == &fakecall) {
+ /* Process any pending events first now. */
+ sockevent_pump();
+
+ /*
+ * If the connect request has not been resumed
+ * yet now, we must remove it from the queue
+ * again, and return EINPROGRESS ourselves.
+ * Otherwise, return OK or a pending error.
+ */
+ spr = sockevent_unsuspend(sock, call);
+ if (spr != NULL) {
+ sockevent_proc_free(spr);
+
+ r = EINPROGRESS;
+ } else if ((r = sock->sock_err) != OK)
+ sock->sock_err = OK;
+ }
+ } else
+ r = EINPROGRESS;
+ }
+
+ if (r == OK) {
+ /*
+ * A completed connection attempt also always implies that the
+ * socket becomes writable. For convenience we enforce this
+ * rule here, because it is easy to forget.
+ */
+ sockevent_raise(sock, SEV_SEND);
+ }
+
+ return r;
+}
+
+/*
+ * Put a socket in listening mode.
+ */
+static int
+sockevent_listen(sockid_t id, int backlog)
+{
+ struct sock *sock;
+ int r;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (sock->sock_ops->sop_listen == NULL)
+ return EOPNOTSUPP;
+
+ /*
+ * Perform a general adjustment on the backlog value, applying the
+ * customary BSD "fudge factor" of 1.5x. Keep the value within bounds
+ * though. POSIX imposes that a negative backlog value is equal to a
+ * backlog value of zero. A backlog value of zero, in turn, may mean
+ * anything; we take it to be one. POSIX also imposes that all socket
+ * drivers accept up to at least SOMAXCONN connections on the queue.
+ */
+ if (backlog < 0)
+ backlog = 0;
+ if (backlog < SOMAXCONN)
+ backlog += 1 + ((unsigned int)backlog >> 1);
+ if (backlog > SOMAXCONN)
+ backlog = SOMAXCONN;
+
+ r = sock->sock_ops->sop_listen(sock, backlog);
+
+ /*
+ * On success, the socket is now in listening mode. As part of that,
+ * a select(2) ready-to-read condition now indicates that a connection
+ * may be accepted on the socket, rather than that data may be read.
+ * Since libsockevent is responsible for this distinction, we keep
+ * track of the listening mode at this level. Conveniently, there is a
+ * socket option for this, which we support out of the box as a result.
+ */
+ if (r == OK) {
+ sock->sock_opt |= SO_ACCEPTCONN;
+
+ /*
+ * For the extremely unlikely case that right after the socket
+ * is put into listening mode, it has a connection ready tor
+ * accept, we retest blocked ready-to-read select queries now.
+ */
+ sockevent_raise(sock, SEV_ACCEPT);
+ }
+
+ return r;
+}
+
+/*
+ * Accept a connection on a listening socket, creating a new socket.
+ */
+static sockid_t
+sockevent_accept(sockid_t id, struct sockaddr * __restrict addr,
+ socklen_t * __restrict addr_len, endpoint_t user_endpt,
+ const struct sockdriver_call * __restrict call)
+{
+ struct sock *sock, *newsock;
+ sockid_t r;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (sock->sock_ops->sop_accept == NULL)
+ return EOPNOTSUPP;
+
+ /*
+ * Attempt to accept a connection. The socket driver is responsible
+ * for allocating a sock object (and identifier) on success. It may
+ * already have done so before, in which case it should leave newsock
+ * filled with NULL; otherwise, the returned sock object is cloned from
+ * the listening socket. The socket driver is also responsible for
+ * failing the call if the socket is not in listening mode, because it
+ * must specify the error to return: EOPNOTSUPP or EINVAL.
+ */
+ newsock = NULL;
+
+ if ((r = sock->sock_ops->sop_accept(sock, addr, addr_len, user_endpt,
+ &newsock)) == SUSPEND) {
+ assert(sock->sock_opt & SO_ACCEPTCONN);
+
+ if (call == NULL)
+ return EWOULDBLOCK;
+
+ sockevent_suspend(sock, SEV_ACCEPT, call, user_endpt);
+
+ return SUSPEND;
+ }
+
+ if (r >= 0)
+ sockevent_accepted(sock, newsock, r);
+
+ return r;
+}
+
+/*
+ * Send regular and/or control data.
+ */
+static int
+sockevent_send(sockid_t id, const struct sockdriver_data * __restrict data,
+ size_t len, const struct sockdriver_data * __restrict ctl_data,
+ socklen_t ctl_len, const struct sockaddr * __restrict addr,
+ socklen_t addr_len, endpoint_t user_endpt, int flags,
+ const struct sockdriver_call * __restrict call)
+{
+ struct sock *sock;
+ clock_t time;
+ size_t min, off;
+ socklen_t ctl_off;
+ int r, timer;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ /*
+ * The order of the following checks is not necessarily fixed, and may
+ * be changed later. As far as applicable, they should match the order
+ * of the checks during call resumption, though.
+ */
+ if ((r = sock->sock_err) != OK) {
+ sock->sock_err = OK;
+
+ return r;
+ }
+
+ if (sock->sock_flags & SFL_SHUT_WR) {
+ sockevent_sigpipe(sock, user_endpt, flags);
+
+ return EPIPE;
+ }
+
+ /*
+ * Translate the sticky SO_DONTROUTE option to a per-request
+ * MSG_DONTROUTE flag. This achieves two purposes: socket drivers have
+ * to check only one flag, and socket drivers that do not support the
+ * flag will fail send requests in a consistent way.
+ */
+ if (sock->sock_opt & SO_DONTROUTE)
+ flags |= MSG_DONTROUTE;
+
+ /*
+ * Check if this is a valid send request as far as the socket driver is
+ * concerned. We do this separately from sop_send for the reason that
+ * this send request may immediately be queued behind other pending
+ * send requests (without a call to sop_send), which means even invalid
+ * requests would be queued and not return failure until much later.
+ */
+ if (sock->sock_ops->sop_pre_send != NULL &&
+ (r = sock->sock_ops->sop_pre_send(sock, len, ctl_len, addr,
+ addr_len, user_endpt,
+ flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL))) != OK)
+ return r;
+
+ if (sock->sock_ops->sop_send == NULL)
+ return EOPNOTSUPP;
+
+ off = 0;
+ ctl_off = 0;
+
+ /*
+ * Sending out-of-band data is treated differently from regular data:
+ *
+ * - sop_send is called immediately, even if a partial non-OOB send
+ * operation is currently suspended (TODO: it may have to be aborted
+ * in order to maintain atomicity guarantees - that should be easy);
+ * - sop_send must not return SUSPEND; instead, if it cannot process
+ * the OOB data immediately, it must return an appropriate error;
+ * - the send low watermark is ignored.
+ *
+ * Given that none of the current socket drivers support OOB data at
+ * all, more sophisticated approaches would have no added value now.
+ */
+ if (flags & MSG_OOB) {
+ r = sock->sock_ops->sop_send(sock, data, len, &off, ctl_data,
+ ctl_len, &ctl_off, addr, addr_len, user_endpt, flags, 0);
+
+ if (r == SUSPEND)
+ panic("libsockevent: MSG_OOB send calls may not be "
+ "suspended");
+
+ return (r == OK) ? (int)off : r;
+ }
+
+ /*
+ * Only call the actual sop_send function now if no other send calls
+ * are suspended already.
+ *
+ * Call sop_send with 'min' set to the minimum of the request size and
+ * the socket's send low water mark, but only if the call is non-
+ * blocking. For stream-oriented sockets, this should have the effect
+ * that non-blocking calls fail with EWOULDBLOCK if not at least that
+ * much can be sent immediately. For consistency, we choose to apply
+ * the same threshold to blocking calls. For datagram-oriented
+ * sockets, the minimum is not a factor to be considered.
+ */
+ if (!sockevent_has_suspended(sock, SEV_SEND)) {
+ min = sock->sock_slowat;
+ if (min > len)
+ min = len;
+
+ r = sock->sock_ops->sop_send(sock, data, len, &off, ctl_data,
+ ctl_len, &ctl_off, addr, addr_len, user_endpt, flags, min);
+ } else
+ r = SUSPEND;
+
+ if (r == SUSPEND) {
+ /*
+ * We do not store the target's address on suspension, because
+ * that would add significantly to the per-process suspension
+ * state. As a result, we disallow socket drivers from
+ * suspending send calls with addresses, because we would no
+ * longer have the address for proper call resumption.
+ * However, we do not know here whether the socket is in
+ * connection-oriented mode; if it is, the address is to be
+ * ignored altogether. Therefore, there is no test on 'addr'
+ * here. Resumed calls will get a NULL address pointer, and
+ * the socket driver is expected to do the right thing.
+ */
+
+ /*
+ * For non-blocking socket calls, return an error only if we
+ * were not able to send anything at all. If only control data
+ * were sent, the return value is therefore zero.
+ */
+ if (call != NULL) {
+ if (sock->sock_stimeo != 0) {
+ timer = TRUE;
+ time = socktimer_add(sock, sock->sock_stimeo);
+ } else {
+ timer = FALSE;
+ time = 0;
+ }
+
+ sockevent_suspend_data(sock, SEV_SEND, timer, call,
+ user_endpt, data, len, off, ctl_data, ctl_len,
+ ctl_off, flags, 0, time);
+ } else
+ r = (off > 0 || ctl_off > 0) ? OK : EWOULDBLOCK;
+ } else if (r == EPIPE)
+ sockevent_sigpipe(sock, user_endpt, flags);
+
+ return (r == OK) ? (int)off : r;
+}
+
+/*
+ * The inner part of the receive request handler. An error returned from here
+ * may be overridden by an error pending on the socket, although data returned
+ * from here trumps such pending errors.
+ */
+static int
+sockevent_recv_inner(struct sock * sock,
+ const struct sockdriver_data * __restrict data,
+ size_t len, size_t * __restrict off,
+ const struct sockdriver_data * __restrict ctl_data,
+ socklen_t ctl_len, socklen_t * __restrict ctl_off,
+ struct sockaddr * __restrict addr,
+ socklen_t * __restrict addr_len, endpoint_t user_endpt,
+ int * __restrict flags, const struct sockdriver_call * __restrict call)
+{
+ clock_t time;
+ size_t min;
+ int r, oob, inflags, timer;
+
+ /*
+ * Check if this is a valid receive request as far as the socket driver
+ * is concerned. We do this separately from sop_recv for the reason
+ * that this receive request may immediately be queued behind other
+ * pending receive requests (without a call to sop_recv), which means
+ * even invalid requests would be queued and not return failure until
+ * much later.
+ */
+ inflags = *flags;
+ *flags = 0;
+
+ if (sock->sock_ops->sop_pre_recv != NULL &&
+ (r = sock->sock_ops->sop_pre_recv(sock, user_endpt,
+ inflags & ~(MSG_DONTWAIT | MSG_NOSIGNAL))) != OK)
+ return r;
+
+ /*
+ * The order of the following checks is not necessarily fixed, and may
+ * be changed later. As far as applicable, they should match the order
+ * of the checks during call resumption, though.
+ */
+ if (sock->sock_flags & SFL_SHUT_RD)
+ return SOCKEVENT_EOF;
+
+ if (sock->sock_ops->sop_recv == NULL)
+ return EOPNOTSUPP;
+
+ /*
+ * Receiving out-of-band data is treated differently from regular data:
+ *
+ * - sop_recv is called immediately, even if a partial non-OOB receive
+ * operation is currently suspended (TODO: it may have to be aborted
+ * in order to maintain atomicity guarantees - that should be easy);
+ * - sop_recv must not return SUSPEND; instead, if it cannot return any
+ * the OOB data immediately, it must return an appropriate error;
+ * - the receive low watermark is ignored.
+ *
+ * Given that none of the current socket drivers support OOB data at
+ * all, more sophisticated approaches would have no added value now.
+ */
+ oob = (inflags & MSG_OOB);
+
+ if (oob && (sock->sock_opt & SO_OOBINLINE))
+ return EINVAL;
+
+ /*
+ * Only call the actual sop_recv function now if no other receive
+ * calls are suspended already.
+ *
+ * Call sop_recv with 'min' set to the minimum of the request size and
+ * the socket's socket's low water mark, unless there is a pending
+ * error. As a result, blocking calls will block, and non-blocking
+ * calls will yield EWOULDBLOCK, if at least that much can be received,
+ * unless another condition (EOF or that pending error) prevents more
+ * from being received anyway. For datagram-oriented sockets, the
+ * minimum is not a factor to be considered.
+ */
+ if (oob || !sockevent_has_suspended(sock, SEV_RECV)) {
+ if (!oob && sock->sock_err == OK) {
+ min = sock->sock_rlowat;
+ if (min > len)
+ min = len;
+ } else
+ min = 0; /* receive even no-data segments */
+
+ r = sock->sock_ops->sop_recv(sock, data, len, off, ctl_data,
+ ctl_len, ctl_off, addr, addr_len, user_endpt, inflags, min,
+ flags);
+ } else
+ r = SUSPEND;
+
+ assert(r <= 0 || r == SOCKEVENT_EOF);
+
+ if (r == SUSPEND) {
+ if (oob)
+ panic("libsockevent: MSG_OOB receive calls may not be "
+ "suspended");
+
+ /*
+ * For non-blocking socket calls, return EWOULDBLOCK only if we
+ * did not receive anything at all. If only control data were
+ * received, the return value is therefore zero. Suspension
+ * implies that there is nothing to read. For the purpose of
+ * the calling wrapper function, never suspend a call when
+ * there is a pending error.
+ */
+ if (call != NULL && sock->sock_err == OK) {
+ if (sock->sock_rtimeo != 0) {
+ timer = TRUE;
+ time = socktimer_add(sock, sock->sock_rtimeo);
+ } else {
+ timer = FALSE;
+ time = 0;
+ }
+
+ sockevent_suspend_data(sock, SEV_RECV, timer, call,
+ user_endpt, data, len, *off, ctl_data,
+ ctl_len, *ctl_off, inflags, *flags, time);
+ } else
+ r = EWOULDBLOCK;
+ }
+
+ return r;
+}
+
+/*
+ * Receive regular and/or control data.
+ */
+static int
+sockevent_recv(sockid_t id, const struct sockdriver_data * __restrict data,
+ size_t len, const struct sockdriver_data * __restrict ctl_data,
+ socklen_t * __restrict ctl_len, struct sockaddr * __restrict addr,
+ socklen_t * __restrict addr_len, endpoint_t user_endpt,
+ int * __restrict flags, const struct sockdriver_call * __restrict call)
+{
+ struct sock *sock;
+ size_t off;
+ socklen_t ctl_inlen;
+ int r;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ /*
+ * This function is a wrapper around the actual receive functionality.
+ * The reason for this is that receiving data should take precedence
+ * over a pending socket error, while a pending socket error should
+ * take precedence over both regular errors as well as EOF. In other
+ * words: if there is a pending error, we must try to receive anything
+ * at all; if receiving does not work, we must fail the call with the
+ * pending error. However, until we call the receive callback, we have
+ * no way of telling whether any data can be received. So we must try
+ * that before we can decide whether to return a pending error.
+ */
+ off = 0;
+ ctl_inlen = *ctl_len;
+ *ctl_len = 0;
+
+ /*
+ * Attempt to perform the actual receive call.
+ */
+ r = sockevent_recv_inner(sock, data, len, &off, ctl_data, ctl_inlen,
+ ctl_len, addr, addr_len, user_endpt, flags, call);
+
+ /*
+ * If the receive request succeeded, or it failed but yielded a partial
+ * result, then return the (partal) result. Otherwise, if an error is
+ * pending, return that error. Otherwise, return either a regular
+ * error or 0 for EOF.
+ */
+ if (r == OK || (r != SUSPEND && (off > 0 || *ctl_len > 0)))
+ r = (int)off;
+ else if (sock->sock_err != OK) {
+ assert(r != SUSPEND);
+
+ r = sock->sock_err;
+
+ sock->sock_err = OK;
+ } else if (r == SOCKEVENT_EOF)
+ r = 0;
+
+ return r;
+}
+
+/*
+ * Process an I/O control call.
+ */
+static int
+sockevent_ioctl(sockid_t id, unsigned long request,
+ const struct sockdriver_data * __restrict data, endpoint_t user_endpt,
+ const struct sockdriver_call * __restrict call __unused)
+{
+ struct sock *sock;
+ size_t size;
+ int r, val;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ /* We handle a very small subset of generic IOCTLs here. */
+ switch (request) {
+ case FIONREAD:
+ size = 0;
+ if (!(sock->sock_flags & SFL_SHUT_RD) &&
+ sock->sock_ops->sop_test_recv != NULL)
+ (void)sock->sock_ops->sop_test_recv(sock, 0, &size);
+
+ val = (int)size;
+
+ return sockdriver_copyout(data, 0, &val, sizeof(val));
+ }
+
+ if (sock->sock_ops->sop_ioctl == NULL)
+ return ENOTTY;
+
+ r = sock->sock_ops->sop_ioctl(sock, request, data, user_endpt);
+
+ /*
+ * Suspending IOCTL requests is not currently supported by this
+ * library, even though the VFS protocol and libsockdriver do support
+ * it. The reason is that IOCTLs do not match our proces suspension
+ * model: they could be neither queued nor repeated. For now, it seems
+ * that this feature is not needed by the socket drivers either. Thus,
+ * even though there are possible solutions, we defer implementing them
+ * until we know what exactly is needed.
+ */
+ if (r == SUSPEND)
+ panic("libsockevent: socket driver suspended IOCTL 0x%lx",
+ request);
+
+ return r;
+}
+
+/*
+ * Set socket options.
+ */
+static int
+sockevent_setsockopt(sockid_t id, int level, int name,
+ const struct sockdriver_data * data, socklen_t len)
+{
+ struct sock *sock;
+ struct linger linger;
+ struct timeval tv;
+ clock_t secs, ticks;
+ int r, val;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (level == SOL_SOCKET) {
+ /*
+ * Handle a subset of the socket-level options here. For most
+ * of them, this means that the socket driver itself need not
+ * handle changing or returning the options, but still needs to
+ * implement the correct behavior based on them where needed.
+ * A few of them are handled exclusively in this library:
+ * SO_ACCEPTCONN, SO_NOSIGPIPE, SO_ERROR, SO_TYPE, SO_LINGER,
+ * SO_SNDLOWAT, SO_RCVLOWAT, SO_SNDTIMEO, and SO_RCVTIMEO.
+ * The SO_USELOOPBACK option is explicitly absent, as it is
+ * valid for routing sockets only and is set by default there.
+ */
+ switch (name) {
+ case SO_DEBUG:
+ case SO_REUSEADDR:
+ case SO_KEEPALIVE:
+ case SO_DONTROUTE:
+ case SO_BROADCAST:
+ case SO_OOBINLINE:
+ case SO_REUSEPORT:
+ case SO_NOSIGPIPE:
+ case SO_TIMESTAMP:
+ /*
+ * Simple on-off options. Changing them does not
+ * involve the socket driver.
+ */
+ if ((r = sockdriver_copyin_opt(data, &val, sizeof(val),
+ len)) != OK)
+ return r;
+
+ if (val)
+ sock->sock_opt |= (unsigned int)name;
+ else
+ sock->sock_opt &= ~(unsigned int)name;
+
+ /*
+ * In priciple these on-off options are maintained in
+ * this library, but some socket drivers may need to
+ * apply the options elsewhere, so we notify them that
+ * something has changed. Using the sop_setsockopt
+ * callback would be inconvenient for this for two
+ * reasons: multiple value copy-ins and default errors.
+ */
+ if (sock->sock_ops->sop_setsockmask != NULL)
+ sock->sock_ops->sop_setsockmask(sock,
+ sock->sock_opt);
+
+ /*
+ * The inlining of OOB data may make new data available
+ * through regular receive calls. Thus, see if we can
+ * wake up any suspended receive calls now.
+ */
+ if (name == SO_OOBINLINE && val)
+ sockevent_raise(sock, SEV_RECV);
+
+ return OK;
+
+ case SO_LINGER:
+ /* The only on-off option with an associated value. */
+ if ((r = sockdriver_copyin_opt(data, &linger,
+ sizeof(linger), len)) != OK)
+ return r;
+
+ if (linger.l_onoff) {
+ if (linger.l_linger < 0)
+ return EINVAL;
+ /* EDOM is the closest applicable error.. */
+ secs = (clock_t)linger.l_linger;
+ if (secs >= TMRDIFF_MAX / sys_hz())
+ return EDOM;
+
+ sock->sock_opt |= SO_LINGER;
+ sock->sock_linger = secs * sys_hz();
+ } else {
+ sock->sock_opt &= ~SO_LINGER;
+ sock->sock_linger = 0;
+ }
+
+ return OK;
+
+ case SO_SNDLOWAT:
+ case SO_RCVLOWAT:
+ if ((r = sockdriver_copyin_opt(data, &val, sizeof(val),
+ len)) != OK)
+ return r;
+
+ if (val <= 0)
+ return EINVAL;
+
+ /*
+ * Setting these values may allow suspended operations
+ * (send, recv, select) to be resumed, so recheck.
+ */
+ if (name == SO_SNDLOWAT) {
+ sock->sock_slowat = (size_t)val;
+
+ sockevent_raise(sock, SEV_SEND);
+ } else {
+ sock->sock_rlowat = (size_t)val;
+
+ sockevent_raise(sock, SEV_RECV);
+ }
+
+ return OK;
+
+ case SO_SNDTIMEO:
+ case SO_RCVTIMEO:
+ if ((r = sockdriver_copyin_opt(data, &tv, sizeof(tv),
+ len)) != OK)
+ return r;
+
+ if (tv.tv_sec < 0 || tv.tv_usec < 0 ||
+ (unsigned long)tv.tv_usec >= US)
+ return EINVAL;
+ if (tv.tv_sec >= TMRDIFF_MAX / sys_hz())
+ return EDOM;
+
+ ticks = tv.tv_sec * sys_hz() +
+ (tv.tv_usec * sys_hz() + US - 1) / US;
+
+ if (name == SO_SNDTIMEO)
+ sock->sock_stimeo = ticks;
+ else
+ sock->sock_rtimeo = ticks;
+
+ /*
+ * The timeouts for any calls already in progress for
+ * this socket are left as is.
+ */
+ return OK;
+
+ case SO_ACCEPTCONN:
+ case SO_ERROR:
+ case SO_TYPE:
+ /* These options may be retrieved but not set. */
+ return ENOPROTOOPT;
+
+ default:
+ /*
+ * The remaining options either cannot be handled in a
+ * generic way, or are not recognized altogether. Pass
+ * them to the socket driver, which should handle what
+ * it knows and reject the rest.
+ */
+ break;
+ }
+ }
+
+ if (sock->sock_ops->sop_setsockopt == NULL)
+ return ENOPROTOOPT;
+
+ /*
+ * The socket driver must return ENOPROTOOPT for all options it does
+ * not recognize.
+ */
+ return sock->sock_ops->sop_setsockopt(sock, level, name, data, len);
+}
+
+/*
+ * Retrieve socket options.
+ */
+static int
+sockevent_getsockopt(sockid_t id, int level, int name,
+ const struct sockdriver_data * __restrict data,
+ socklen_t * __restrict len)
+{
+ struct sock *sock;
+ struct linger linger;
+ struct timeval tv;
+ clock_t ticks;
+ int val;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (level == SOL_SOCKET) {
+ /*
+ * As with setting, handle a subset of the socket-level options
+ * here. The rest is to be taken care of by the socket driver.
+ */
+ switch (name) {
+ case SO_DEBUG:
+ case SO_ACCEPTCONN:
+ case SO_REUSEADDR:
+ case SO_KEEPALIVE:
+ case SO_DONTROUTE:
+ case SO_BROADCAST:
+ case SO_OOBINLINE:
+ case SO_REUSEPORT:
+ case SO_NOSIGPIPE:
+ case SO_TIMESTAMP:
+ val = !!(sock->sock_opt & (unsigned int)name);
+
+ return sockdriver_copyout_opt(data, &val, sizeof(val),
+ len);
+
+ case SO_LINGER:
+ linger.l_onoff = !!(sock->sock_opt & SO_LINGER);
+ linger.l_linger = sock->sock_linger / sys_hz();
+
+ return sockdriver_copyout_opt(data, &linger,
+ sizeof(linger), len);
+
+ case SO_ERROR:
+ if ((val = -sock->sock_err) != OK)
+ sock->sock_err = OK;
+
+ return sockdriver_copyout_opt(data, &val, sizeof(val),
+ len);
+
+ case SO_TYPE:
+ val = sock->sock_type;
+
+ return sockdriver_copyout_opt(data, &val, sizeof(val),
+ len);
+
+ case SO_SNDLOWAT:
+ val = (int)sock->sock_slowat;
+
+ return sockdriver_copyout_opt(data, &val, sizeof(val),
+ len);
+
+ case SO_RCVLOWAT:
+ val = (int)sock->sock_rlowat;
+
+ return sockdriver_copyout_opt(data, &val, sizeof(val),
+ len);
+
+ case SO_SNDTIMEO:
+ case SO_RCVTIMEO:
+ if (name == SO_SNDTIMEO)
+ ticks = sock->sock_stimeo;
+ else
+ ticks = sock->sock_rtimeo;
+
+ tv.tv_sec = ticks / sys_hz();
+ tv.tv_usec = (ticks % sys_hz()) * US / sys_hz();
+
+ return sockdriver_copyout_opt(data, &tv, sizeof(tv),
+ len);
+
+ default:
+ break;
+ }
+ }
+
+ if (sock->sock_ops->sop_getsockopt == NULL)
+ return ENOPROTOOPT;
+
+ /*
+ * The socket driver must return ENOPROTOOPT for all options it does
+ * not recognize.
+ */
+ return sock->sock_ops->sop_getsockopt(sock, level, name, data, len);
+}
+
+/*
+ * Retrieve a socket's local address.
+ */
+static int
+sockevent_getsockname(sockid_t id, struct sockaddr * __restrict addr,
+ socklen_t * __restrict addr_len)
+{
+ struct sock *sock;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ if (sock->sock_ops->sop_getsockname == NULL)
+ return EOPNOTSUPP;
+
+ return sock->sock_ops->sop_getsockname(sock, addr, addr_len);
+}
+
+/*
+ * Retrieve a socket's remote address.
+ */
+static int
+sockevent_getpeername(sockid_t id, struct sockaddr * __restrict addr,
+ socklen_t * __restrict addr_len)
+{
+ struct sock *sock;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ /* Listening-mode sockets cannot possibly have a peer address. */
+ if (sock->sock_opt & SO_ACCEPTCONN)
+ return ENOTCONN;
+
+ if (sock->sock_ops->sop_getpeername == NULL)
+ return EOPNOTSUPP;
+
+ return sock->sock_ops->sop_getpeername(sock, addr, addr_len);
+}
+
+/*
+ * Mark the socket object as shut down for sending and/or receiving. The flags
+ * parameter may be a bitwise-OR'ed combination of SFL_SHUT_RD and SFL_SHUT_WR.
+ * This function will wake up any suspended requests affected by this change,
+ * but it will not invoke the sop_shutdown() callback function on the socket.
+ * The function may in fact be called from sop_shutdown() before completion to
+ * mark the socket as shut down as reflected by sockevent_is_shutdown().
+ */
+void
+sockevent_set_shutdown(struct sock * sock, unsigned int flags)
+{
+ unsigned int mask;
+
+ assert(sock->sock_ops != NULL);
+ assert(!(flags & ~(SFL_SHUT_RD | SFL_SHUT_WR)));
+
+ /* Look at the newly set flags only. */
+ flags &= ~(unsigned int)sock->sock_flags;
+
+ if (flags != 0) {
+ sock->sock_flags |= flags;
+
+ /*
+ * Wake up any blocked calls that are affected by the shutdown.
+ * Shutting down listening sockets causes ongoing accept calls
+ * to be rechecked.
+ */
+ mask = 0;
+ if (flags & SFL_SHUT_RD)
+ mask |= SEV_RECV;
+ if (flags & SFL_SHUT_WR)
+ mask |= SEV_SEND;
+ if (sock->sock_opt & SO_ACCEPTCONN)
+ mask |= SEV_ACCEPT;
+
+ assert(mask != 0);
+ sockevent_raise(sock, mask);
+ }
+}
+
+/*
+ * Shut down socket send and receive operations.
+ */
+static int
+sockevent_shutdown(sockid_t id, int how)
+{
+ struct sock *sock;
+ unsigned int flags;
+ int r;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ /* Convert the request to a set of flags. */
+ flags = 0;
+ if (how == SHUT_RD || how == SHUT_RDWR)
+ flags |= SFL_SHUT_RD;
+ if (how == SHUT_WR || how == SHUT_RDWR)
+ flags |= SFL_SHUT_WR;
+
+ if (sock->sock_ops->sop_shutdown != NULL)
+ r = sock->sock_ops->sop_shutdown(sock, flags);
+ else
+ r = OK;
+
+ /* On success, update our internal state as well. */
+ if (r == OK)
+ sockevent_set_shutdown(sock, flags);
+
+ return r;
+}
+
+/*
+ * Close a socket.
+ */
+static int
+sockevent_close(sockid_t id, const struct sockdriver_call * call)
+{
+ struct sock *sock;
+ int r, force;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ assert(sock->sock_proc == NULL);
+ sock->sock_select.ss_endpt = NONE;
+
+ /*
+ * There are several scenarios when it comes to closing sockets. First
+ * of all, we never actually force the socket driver to close a socket.
+ * The driver may always suspend the close call and take as long as it
+ * wants. After a suspension, it signals its completion of the close
+ * through the SEV_CLOSE socket event.
+ *
+ * With that said, we offer two levels of urgency regarding the close
+ * request: regular and forced. The former allows for a graceful
+ * close; the latter urges the socket driver to close the socket as
+ * soon as possible. A socket that has been requested to be closed
+ * gracefully can, as long as it is still open (i.e., no SEV_CLOSE was
+ * fired yet), later be requested to be closed forcefully. This is how
+ * SO_LINGER with a nonzero timeout is implemented. If SO_LINGER is
+ * set with a zero timeout, the socket is force-closed immediately.
+ * Finally, if SO_LINGER is not set, the socket will be closed normally
+ * and never be forced--akin to SO_LINGER with an infinite timeout.
+ *
+ * The return value of the caller's close(2) may only ever be either
+ * OK or EINPROGRESS, to ensure that the caller knows that the file
+ * descriptor is freed up, as per Austin Group Defect #529. In fact,
+ * EINPROGRESS is to be returned only on signal interruption (i.e.,
+ * cancel). For that reason, this function only ever returns OK.
+ */
+ force = ((sock->sock_opt & SO_LINGER) && sock->sock_linger == 0);
+
+ if (sock->sock_ops->sop_close != NULL)
+ r = sock->sock_ops->sop_close(sock, force);
+ else
+ r = OK;
+
+ assert(r == OK || r == SUSPEND);
+
+ if (r == SUSPEND) {
+ sock->sock_flags |= SFL_CLOSING;
+
+ /*
+ * If we were requested to force-close the socket immediately,
+ * but the socket driver needs more time anyway, then tell the
+ * caller that the socket was closed right away.
+ */
+ if (force)
+ return OK;
+
+ /*
+ * If we are to force-close the socket only after a specific
+ * linger timeout, set the timer for that now, even if the call
+ * is non-blocking. This also means that we cannot associate
+ * the linger timeout with the close call. Instead, we convert
+ * the sock_linger value from a (relative) duration to an
+ * (absolute) timeout time, and use the SFL_CLOSING flag (along
+ * with SFL_TIMER) to tell the difference. Since the socket is
+ * otherwise unreachable from userland at this point, the
+ * conversion is never visible in any way.
+ *
+ * The socket may already be in the timers list, so we must
+ * always check the SO_LINGER flag before checking sock_linger.
+ *
+ * If SO_LINGER is not set, we must never suspend the call.
+ */
+ if (sock->sock_opt & SO_LINGER) {
+ sock->sock_linger =
+ socktimer_add(sock, sock->sock_linger);
+ } else
+ call = NULL;
+
+ /*
+ * A non-blocking close is completed asynchronously. The
+ * caller is not told about this with EWOULDBLOCK as usual, for
+ * the reasons mentioned above.
+ */
+ if (call != NULL)
+ sockevent_suspend(sock, SEV_CLOSE, call, NONE);
+ else
+ r = OK;
+ } else if (r == OK)
+ sockevent_free(sock);
+
+ return r;
+}
+
+/*
+ * Cancel a suspended send request.
+ */
+static void
+sockevent_cancel_send(struct sock * sock, struct sockevent_proc * spr, int err)
+{
+ int r;
+
+ /*
+ * If any regular or control data were sent, return the number of data
+ * bytes sent--possibly zero. Otherwise return the given error code.
+ */
+ if (spr->spr_dataoff > 0 || spr->spr_ctloff > 0)
+ r = (int)spr->spr_dataoff;
+ else
+ r = err;
+
+ sockdriver_reply_generic(&spr->spr_call, r);
+
+ /*
+ * In extremely rare circumstances, one send may be queued behind
+ * another send even though the former can actually be sent on the
+ * socket right away. For this reason, we retry sending when canceling
+ * a send. We need to do this only when the first send in the queue
+ * was canceled, but multiple blocked sends on a single socket should
+ * be rare anyway.
+ */
+ sockevent_raise(sock, SEV_SEND);
+}
+
+/*
+ * Cancel a suspended receive request.
+ */
+static void
+sockevent_cancel_recv(struct sock * sock, struct sockevent_proc * spr, int err)
+{
+ int r;
+
+ /*
+ * If any regular or control data were received, return the number of
+ * data bytes received--possibly zero. Otherwise return the given
+ * error code.
+ */
+ if (spr->spr_dataoff > 0 || spr->spr_ctloff > 0)
+ r = (int)spr->spr_dataoff;
+ else
+ r = err;
+
+ /*
+ * Also return any flags set for the data received so far, e.g.
+ * MSG_CTRUNC. Do not return an address: receive calls on unconnected
+ * sockets must never block after receiving some data--instead, they
+ * are supposed to return MSG_TRUNC if not all data were copied out.
+ */
+ sockdriver_reply_recv(&spr->spr_call, r, spr->spr_ctloff, NULL, 0,
+ spr->spr_rflags);
+
+ /*
+ * The same story as for sends (see above) applies to receives,
+ * although this case should be even more rare in practice.
+ */
+ sockevent_raise(sock, SEV_RECV);
+}
+
+/*
+ * Cancel a previous request that may currently be suspended. The cancel
+ * operation itself does not have a reply. Instead, if the given request was
+ * found to be suspended, that request must be aborted and an appropriate reply
+ * must be sent for the request. If no matching request was found, no reply
+ * must be sent at all.
+ */
+static void
+sockevent_cancel(sockid_t id, const struct sockdriver_call * call)
+{
+ struct sockevent_proc *spr;
+ struct sock *sock;
+
+ /*
+ * Due to asynchronous close(2) operations, not even the sock object
+ * may be found. If this (entirely legitimate) case, do not send any
+ * reply.
+ */
+ if ((sock = sockhash_get(id)) == NULL)
+ return;
+
+ /*
+ * The request may already have completed by the time we receive the
+ * cancel request, in which case we can not find it. In this (entirely
+ * legitimate) case, do not send any reply.
+ */
+ if ((spr = sockevent_unsuspend(sock, call)) == NULL)
+ return;
+
+ /*
+ * We found the operation. Cancel it according to its call type.
+ * Then, once fully done with it, free the suspension data structure.
+ *
+ * Note that we have to use the call structure from the suspension data
+ * structure rather than the given 'call' pointer: only the former
+ * includes all the information necessary to resume the request!
+ */
+ switch (spr->spr_event) {
+ case SEV_BIND:
+ case SEV_CONNECT:
+ assert(spr->spr_call.sc_endpt != NONE);
+
+ sockdriver_reply_generic(&spr->spr_call, EINTR);
+
+ break;
+
+ case SEV_ACCEPT:
+ sockdriver_reply_accept(&spr->spr_call, EINTR, NULL, 0);
+
+ break;
+
+ case SEV_SEND:
+ sockevent_cancel_send(sock, spr, EINTR);
+
+ break;
+
+ case SEV_RECV:
+ sockevent_cancel_recv(sock, spr, EINTR);
+
+ break;
+
+ case SEV_CLOSE:
+ /*
+ * Return EINPROGRESS rather than EINTR, so that the user
+ * process can tell from the close(2) result that the file
+ * descriptor has in fact been closed.
+ */
+ sockdriver_reply_generic(&spr->spr_call, EINPROGRESS);
+
+ /*
+ * Do not free the sock object here: the socket driver will
+ * complete the close in the background, and fire SEV_CLOSE
+ * once it is done. Only then is the sock object freed.
+ */
+ break;
+
+ default:
+ panic("libsockevent: process suspended on unknown event 0x%x",
+ spr->spr_event);
+ }
+
+ sockevent_proc_free(spr);
+}
+
+/*
+ * Process a select request.
+ */
+static int
+sockevent_select(sockid_t id, unsigned int ops,
+ const struct sockdriver_select * sel)
+{
+ struct sock *sock;
+ unsigned int r, notify;
+
+ if ((sock = sockhash_get(id)) == NULL)
+ return EINVAL;
+
+ notify = (ops & SDEV_NOTIFY);
+ ops &= (SDEV_OP_RD | SDEV_OP_WR | SDEV_OP_ERR);
+
+ /*
+ * See if any of the requested select operations can be satisfied
+ * immediately.
+ */
+ r = sockevent_test_select(sock, ops);
+
+ /*
+ * If select operations were pending, the new results must not indicate
+ * that any of those were satisfied, as that would indicate an internal
+ * logic error: the socket driver is supposed to update its state
+ * proactively, and thus, discovering that things have changed here is
+ * not something that should ever happen.
+ */
+ assert(!(sock->sock_selops & r));
+
+ /*
+ * If any select operations are not satisfied immediately, and we are
+ * asked to notify the caller when they are satisfied later, save them
+ * for later retesting.
+ */
+ ops &= ~r;
+
+ if (notify && ops != 0) {
+ /*
+ * For now, we support only one caller when it comes to select
+ * queries: VFS. If we want to support a networked file system
+ * (or so) directly calling select as well, this library will
+ * have to be extended accordingly (should not be too hard).
+ */
+ if (sock->sock_select.ss_endpt != NONE) {
+ if (sock->sock_select.ss_endpt != sel->ss_endpt) {
+ printf("libsockevent: no support for multiple "
+ "select callers yet\n");
+
+ return EIO;
+ }
+
+ /*
+ * If a select query was already pending for this
+ * caller, we must simply merge in the new operations.
+ */
+ sock->sock_selops |= ops;
+ } else {
+ assert(sel->ss_endpt != NONE);
+
+ sock->sock_select = *sel;
+ sock->sock_selops = ops;
+ }
+ }
+
+ return r;
+}
+
+/*
+ * An alarm has triggered. Expire any timers. Socket drivers that do not pass
+ * clock notification messages to libsockevent must call expire_timers(3)
+ * themselves instead.
+ */
+static void
+sockevent_alarm(clock_t now)
+{
+
+ expire_timers(now);
+}
+
+static const struct sockdriver sockevent_tab = {
+ .sdr_socket = sockevent_socket,
+ .sdr_socketpair = sockevent_socketpair,
+ .sdr_bind = sockevent_bind,
+ .sdr_connect = sockevent_connect,
+ .sdr_listen = sockevent_listen,
+ .sdr_accept = sockevent_accept,
+ .sdr_send = sockevent_send,
+ .sdr_recv = sockevent_recv,
+ .sdr_ioctl = sockevent_ioctl,
+ .sdr_setsockopt = sockevent_setsockopt,
+ .sdr_getsockopt = sockevent_getsockopt,
+ .sdr_getsockname = sockevent_getsockname,
+ .sdr_getpeername = sockevent_getpeername,
+ .sdr_shutdown = sockevent_shutdown,
+ .sdr_close = sockevent_close,
+ .sdr_cancel = sockevent_cancel,
+ .sdr_select = sockevent_select,
+ .sdr_alarm = sockevent_alarm
+};
+
+/*
+ * Initialize the socket event library.
+ */
+void
+sockevent_init(sockevent_socket_cb_t socket_cb)
+{
+
+ sockhash_init();
+
+ socktimer_init();
+
+ sockevent_proc_init();
+
+ SIMPLEQ_INIT(&sockevent_pending);
+
+ assert(socket_cb != NULL);
+ sockevent_socket_cb = socket_cb;
+
+ /* Announce we are up. */
+ sockdriver_announce();
+
+ sockevent_working = FALSE;
+}
+
+/*
+ * Process a socket driver request message.
+ */
+void
+sockevent_process(const message * m_ptr, int ipc_status)
+{
+
+ /* Block events until after we have processed the request. */
+ assert(!sockevent_working);
+ sockevent_working = TRUE;
+
+ /* Actually process the request. */
+ sockdriver_process(&sockevent_tab, m_ptr, ipc_status);
+
+ /*
+ * If any events were fired while processing the request, they will
+ * have been queued for later. Go through them now.
+ */
+ if (sockevent_has_events())
+ sockevent_pump();
+
+ sockevent_working = FALSE;
+}