+/* -*-c-*-
+ *
+ * $Id: endpt.c,v 1.1 1999/07/26 23:33:01 mdw Exp $
+ *
+ * Generic endpoint abstraction
+ *
+ * (c) 1999 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the `fw' port forwarder.
+ *
+ * `fw' 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 of the License, or
+ * (at your option) any later version.
+ *
+ * `fw' 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 `fw'; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------*
+ *
+ * $Log: endpt.c,v $
+ * Revision 1.1 1999/07/26 23:33:01 mdw
+ * Infrastructure for the new design.
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "chan.h"
+#include "endpt.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Pairs of channels --- *
+ *
+ * Channels are always allocated and freed in pairs. It makes sense to keep
+ * the pair together. (It also wastes less memory.)
+ */
+
+typedef struct chanpair {
+ struct chanpair *next;
+ chan ab, ba;
+} chanpair;
+
+/* --- The `private data structure' --- *
+ *
+ * This is called a @tango@ because it takes two (endpoints).
+ */
+
+typedef struct tango {
+ struct tango *next, *prev; /* A big list of all tangos */
+ endpt *a, *b; /* The two endpoints */
+ unsigned s; /* State of the connection */
+ chanpair *c; /* The pair of channels */
+} tango;
+
+#define EPS_AB 1u
+#define EPS_BA 2u
+
+/*----- Static variables --------------------------------------------------*/
+
+static chanpair *chans = 0; /* List of spare channel pairs */
+static tango *tangos = 0; /* List of tangos */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @doneab@, @doneba@ --- *
+ *
+ * Arguments: @void *p@ = pointer to a tango block
+ *
+ * Returns: ---
+ *
+ * Use: Handles completion of a channel.
+ */
+
+static void doneab(void *p)
+{
+ tango *t = p;
+ t->s &= ~EPS_AB;
+ t->b->ops->wclose(t->b);
+ if (!t->s)
+ endpt_kill(t->a);
+}
+
+static void doneba(void *p)
+{
+ tango *t = p;
+ t->s &= ~EPS_BA;
+ t->a->ops->wclose(t->a);
+ if (!t->s)
+ endpt_kill(t->a);
+}
+
+/* --- @endpt_kill@ --- *
+ *
+ * Arguments: @endpt *a@ = an endpoint
+ *
+ * Returns: ---
+ *
+ * Use: Kills an endpoint. If the endpoint is joined to another, the
+ * other endpoint is also killed, as is the connection between
+ * them (and that's the tricky bit).
+ */
+
+void endpt_kill(endpt *a)
+{
+ tango *t;
+ endpt *b;
+
+ /* --- If the endpont is unconnected, just close it --- */
+
+ if (!a->t) {
+ a->ops->close(a);
+ return;
+ }
+ t = a->t;
+ a = t->a;
+ b = t->b;
+
+ /* --- See whether to close channels --- *
+ *
+ * I'll only have opened channels if both things are files. Also, the
+ * channel from @b@ to @a@ will only be open if @b@ is not pending.
+ */
+
+ if (a->f & b->f & EPF_FILE) {
+ if (t->s & EPS_AB)
+ chan_close(&t->c->ab);
+ if (!(b->f & EPF_PENDING) && (t->s & EPS_BA))
+ chan_close(&t->c->ba);
+ t->c->next = chans;
+ chans = t->c;
+ }
+
+ /* --- Now just throw the endpoints and tango block away --- */
+
+ a->ops->close(a);
+ b->ops->close(b);
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ tangos = t->next;
+ DESTROY(t);
+}
+
+/* --- @endpt_killall@ --- *
+ *
+ * Arguments: ---
+ *
+ * Returns: ---
+ *
+ * Use: Destroys all current endpoint connections. Used when
+ * shutting down.
+ */
+
+void endpt_killall(void)
+{
+ tango *t = tangos;
+ while (t) {
+ tango *tt = t;
+ t = t->next;
+ endpt_kill(tt->a);
+ }
+ tangos = 0;
+}
+
+/* --- @endpt_join@ --- *
+ *
+ * Arguments: @endpt *a@ = pointer to first endpoint
+ * @endpt *b@ = pointer to second endpoint
+ *
+ * Returns: ---
+ *
+ * Use: Joins two endpoints together. It's OK to join endpoints
+ * which are already joined; in fact, the the right thing to do
+ * when your endpoint decides that it's not pending any more is
+ * to join it to its partner again.
+ */
+
+void endpt_join(endpt *a, endpt *b)
+{
+ tango *t = a->t;
+
+ /* --- If there is no tango yet, create one --- */
+
+ if (!t) {
+ t = CREATE(tango);
+ t->next = tangos;
+ t->prev = 0;
+ t->a = b->other = a;
+ t->b = a->other = b;
+ a->t = b->t = t;
+ t->s = EPS_AB | EPS_BA;
+ t->c = 0;
+ if (tangos)
+ tangos->prev = t;
+ tangos = t;
+ }
+
+ /* --- If both endpoints are unfinished, leave them for a while --- */
+
+ if (a->f & b->f & EPF_PENDING)
+ return;
+
+ /* --- At least one endpoint is ready --- *
+ *
+ * If both endpoints are files then I can speculatively create the
+ * channels, which will let data start flowing a bit. Otherwise, I'll just
+ * have to wait some more.
+ */
+
+ if ((a->f | b->f) & EPF_PENDING) {
+
+ /* --- Only be interested if both things are files --- */
+
+ if (!t->c && (a->f & b->f & EPF_FILE)) {
+
+ /* --- Allocate a pair of channels --- */
+
+ if (!chans)
+ t->c = xmalloc(sizeof(*t->c));
+ else {
+ t->c = chans;
+ chans = chans->next;
+ }
+
+ /* --- Make @a@ the endpoint which is ready --- */
+
+ if (a->f & EPF_PENDING) {
+ t->b = a; t->a = b;
+ a = t->a; b = t->b;
+ }
+
+ /* --- Open one channel to read from @a@ --- */
+
+ chan_open(&t->c->ab, a->in->fd, -1, doneab, t);
+ }
+ return;
+ }
+
+ /* --- Both endpoints are now ready --- *
+ *
+ * There are four cases to consider here.
+ */
+
+ /* --- Case 1 --- *
+ *
+ * Neither endpoint is a file. I need to make a pair of pipes and tell
+ * both endpoints to attach to them. I can then close the pipes and
+ * discard the tango.
+ */
+
+ if (!((a->f | b->f) & EPF_FILE)) {
+ int pab[2], pba[2];
+ reffd *rab, *wab, *rba, *wba;
+
+ /* --- Create the pipes --- */
+
+ if (pipe(pab)) goto tidy_nofile_0;
+ if (pipe(pba)) goto tidy_nofile_1;
+
+ /* --- Attach reference counters --- */
+
+ rab = reffd_init(pab[0]); wab = reffd_init(pab[1]);
+ rba = reffd_init(pba[0]); wba = reffd_init(pba[1]);
+ a->ops->attach(a, rba, wab);
+ b->ops->attach(b, rab, wba);
+ REFFD_DEC(rab); REFFD_DEC(wab);
+ REFFD_DEC(rba); REFFD_DEC(wba);
+ goto tidy_nofile_0;
+
+ /* --- Tidy up after a mess --- */
+
+ tidy_nofile_1:
+ close(pab[0]); close(pab[1]);
+ tidy_nofile_0:
+ a->ops->close(a);
+ b->ops->close(b);
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ tangos = t->next;
+ DESTROY(t);
+ return;
+ }
+
+ /* --- Case 2 --- *
+ *
+ * One endpoint is a file, the other isn't. I just attach the other
+ * endpoint to the file and stand well back.
+ */
+
+ if ((a->f ^ b->f) & EPF_FILE) {
+
+ /* --- Let @a@ be the endpoint with the file --- */
+
+ if (b->f & EPF_FILE) {
+ endpt *e;
+ e = a; a = b; b = e;
+ }
+
+ /* --- Attach the non-file endpoint to the file and run away --- */
+
+ b->ops->attach(b, a->in, a->out);
+ a->ops->close(a);
+ b->ops->close(b);
+ if (t->next)
+ t->next->prev = t->prev;
+ if (t->prev)
+ t->prev->next = t->next;
+ else
+ tangos = t->next;
+ DESTROY(t);
+ return;
+ }
+
+ /* --- Case 3 --- *
+ *
+ * Both endpoints are files and I have a partially created channel pair. I
+ * need to create the other channel and add a destination to the first one.
+ * In this case, @t->b@ is the endpoint which has just finished setting
+ * itself up.
+ */
+
+ if (t->c) {
+ a = t->a; b = t->b;
+ chan_open(&t->c->ba, b->in->fd, a->out->fd, doneba, t);
+ chan_dest(&t->c->ab, b->out->fd);
+ return;
+ }
+
+ /* --- Case 4 --- *
+ *
+ * Both endpoints are files and I don't have a partially created channel
+ * pair. I need to allocate a channel pair and create both channels.
+ */
+
+ if (!chans)
+ t->c = xmalloc(sizeof(*t->c));
+ else {
+ t->c = chans;
+ chans = chans->next;
+ }
+ chan_open(&t->c->ab, a->in->fd, b->out->fd, doneab, t);
+ chan_open(&t->c->ba, b->in->fd, a->out->fd, doneba, t);
+ return;
+}
+
+/*----- That's all, folks -------------------------------------------------*/