aboutsummaryrefslogtreecommitdiff
path: root/pflocal/connq.c
diff options
context:
space:
mode:
Diffstat (limited to 'pflocal/connq.c')
-rw-r--r--pflocal/connq.c275
1 files changed, 275 insertions, 0 deletions
diff --git a/pflocal/connq.c b/pflocal/connq.c
new file mode 100644
index 00000000..862c9a14
--- /dev/null
+++ b/pflocal/connq.c
@@ -0,0 +1,275 @@
+/* Listen queue functions
+
+ Copyright (C) 1995, 1996 Free Software Foundation, Inc.
+
+ Written by Miles Bader <miles@gnu.ai.mit.edu>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <cthreads.h>
+
+#include "connq.h"
+
+/* A queue for queueing incoming connections. */
+struct connq
+{
+ /* True if all connection requests should be treated as non-blocking. */
+ int noqueue;
+
+ /* The connection request queue. */
+ struct connq_request **queue;
+ unsigned length;
+ /* Head is the position in QUEUE of the first request, and TAIL is the
+ first free position in the queue. If HEAD == TAIL, then the queue is
+ empty. Starting at HEAD, successive positions can be calculated by
+ using qnext(). */
+ unsigned head, tail;
+
+ /* Threads that have done an accept on this queue wait on this condition. */
+ struct condition listeners;
+ unsigned num_listeners;
+
+ struct mutex lock;
+};
+
+/* Returns the position CQ's queue after POS. */
+static inline unsigned
+qnext (struct connq *cq, unsigned pos)
+{
+ return (pos + 1 == cq->length) ? 0 : pos + 1;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* A data block allocated by a thread waiting on a connq, which is used to
+ get information from and to the thread. */
+struct connq_request
+{
+ /* The socket that's waiting to connect. */
+ struct sock *sock;
+
+ /* What the waiting thread blocks on. */
+ struct condition signal;
+ struct mutex lock;
+
+ /* Set to true when this request has been dealt with, to guard against
+ spurious conditions being signaled. */
+ int completed;
+
+ /* After the waiting thread is unblocked, this is the result, either 0 if
+ SOCK has been connected, or an error. */
+ error_t err;
+};
+
+static inline void
+connq_request_init (struct connq_request *req, struct sock *sock)
+{
+ req->err = 0;
+ req->sock = sock;
+ req->completed = 0;
+ condition_init (&req->signal);
+ mutex_init (&req->lock);
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Create a new listening queue, returning it in CQ. The resulting queue
+ will be of zero length, that is it won't allow connections unless someone
+ is already listening (change this with connq_set_length). */
+error_t
+connq_create (struct connq **cq)
+{
+ struct connq *new = malloc (sizeof (struct connq));
+
+ if (!new)
+ return ENOMEM;
+
+ new->noqueue = 1; /* By default, don't queue requests. */
+ new->length = 0;
+ new->head = new->tail = 0;
+ new->queue = NULL;
+ new->num_listeners = 0;
+
+ mutex_init (&new->lock);
+ condition_init (&new->listeners);
+
+ *cq = new;
+ return 0;
+}
+
+/* ---------------------------------------------------------------- */
+
+/* Wait for a connection attempt to be made on CQ, and return the connecting
+ socket in SOCK, and a request tag in REQ. If REQ is NULL, the request is
+ left in the queue, otherwise connq_request_complete must be called on REQ
+ to allow the requesting thread to continue. If NOBLOCK is true,
+ EWOULDBLOCK is returned when there are no immediate connections
+ available. */
+error_t
+connq_listen (struct connq *cq, int noblock,
+ struct connq_request **req, struct sock **sock)
+{
+ mutex_lock (&cq->lock);
+
+ if (noblock && cq->head == cq->tail)
+ {
+ mutex_unlock (&cq->lock);
+ return EWOULDBLOCK;
+ }
+
+ cq->num_listeners++;
+
+ while (cq->head == cq->tail)
+ if (hurd_condition_wait (&cq->listeners, &cq->lock))
+ {
+ cq->num_listeners--;
+ mutex_unlock (&cq->lock);
+ return EINTR;
+ }
+
+ if (req != NULL)
+ /* Dequeue the next request, if desired. */
+ {
+ *req = cq->queue[cq->head];
+ cq->head = qnext (cq, cq->head);
+ if (sock != NULL)
+ *sock = (*req)->sock;
+ }
+
+ cq->num_listeners--;
+
+ mutex_unlock (&cq->lock);
+
+ return 0;
+}
+
+/* Return the error code ERR to the thread that made the listen request REQ,
+ returned from a previous connq_listen. */
+void
+connq_request_complete (struct connq_request *req, error_t err)
+{
+ mutex_lock (&req->lock);
+ req->err = err;
+ req->completed = 1;
+ condition_signal (&req->signal);
+ mutex_unlock (&req->lock);
+}
+
+/* Try to connect SOCK with the socket listening on CQ. If NOBLOCK is true,
+ then return EWOULDBLOCK immediately when there are no immediate
+ connections available. Neither SOCK nor CQ should be locked. */
+error_t
+connq_connect (struct connq *cq, int noblock, struct sock *sock)
+{
+ error_t err = 0;
+ unsigned next;
+
+ mutex_lock (&cq->lock);
+
+ /* Check for listeners after we've locked CQ for good. */
+ if ((noblock || cq->noqueue) && cq->num_listeners == 0)
+ {
+ mutex_unlock (&cq->lock);
+ return EWOULDBLOCK;
+ }
+
+ next = qnext (cq, cq->tail);
+ if (next == cq->tail)
+ /* The queue is full. */
+ err = ECONNREFUSED;
+ else
+ {
+ struct connq_request req;
+
+ connq_request_init (&req, sock);
+
+ cq->queue[cq->tail] = &req;
+ cq->tail = next;
+
+ /* Hold REQ.LOCK before we signal the condition so that we're sure
+ to be woken up. */
+ mutex_lock (&req.lock);
+ condition_signal (&cq->listeners);
+ mutex_unlock (&cq->lock);
+
+ while (!req.completed)
+ condition_wait (&req.signal, &req.lock);
+ err = req.err;
+
+ mutex_unlock (&req.lock);
+ }
+
+ return err;
+}
+
+/* `Compresses' CQ, by removing any NULL entries. CQ should be locked. */
+static void
+connq_compress (struct connq *cq)
+{
+ unsigned pos;
+ unsigned comp_tail = cq->head;
+
+ /* Now compress the queue to remove any null entries we put in. */
+ for (pos = cq->head; pos != cq->tail; pos = qnext (cq, pos))
+ if (cq->queue[pos] != NULL)
+ /* This position has a non-NULL request, so move it to the end of the
+ compressed queue. */
+ {
+ cq->queue[comp_tail] = cq->queue[pos];
+ comp_tail = qnext (cq, comp_tail);
+ }
+
+ /* Move back tail to only include what we kept in the queue. */
+ cq->tail = comp_tail;
+}
+
+/* Set CQ's queue length to LENGTH. Any sockets already waiting for a
+ connections that are past the new length will fail with ECONNREFUSED. */
+error_t
+connq_set_length (struct connq *cq, int length)
+{
+ mutex_lock (&cq->lock);
+
+ if (length > cq->length)
+ /* Growing the queue is simple... */
+ cq->queue = realloc (cq->queue, sizeof (struct connq_request *) * length);
+ else
+ /* Shrinking it less so. */
+ {
+ int i;
+ struct connq_request **new_queue =
+ malloc (sizeof (struct connq_request *) * length);
+
+ for (i = 0; i < cq->length && cq->head != cq->tail; i++)
+ {
+ if (i < length)
+ /* Keep this connect request in the queue. */
+ new_queue[length - i] = cq->queue[cq->head];
+ else
+ /* Punt this one. */
+ connq_request_complete (cq->queue[cq->head], ECONNREFUSED);
+ cq->head = qnext (cq, cq->head);
+ }
+
+ free (cq->queue);
+ cq->queue = new_queue;
+ }
+
+ cq->noqueue = 0; /* Turn on queueing. */
+
+ mutex_unlock (&cq->lock);
+
+ return 0;
+}