chiark / gitweb /
Remove crufty CVS $Id$ tags.
[fwd] / endpt.c
1 /* -*-c-*-
2  *
3  * Generic endpoint abstraction
4  *
5  * (c) 1999 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------* 
9  *
10  * This file is part of the `fw' port forwarder.
11  *
12  * `fw' is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  * 
17  * `fw' is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  * 
22  * You should have received a copy of the GNU General Public License
23  * along with `fw'; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #include "config.h"
30
31 #include <errno.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include <sys/types.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39
40 #include <mLib/alloc.h>
41
42 #include "chan.h"
43 #include "endpt.h"
44
45 /*----- Data structures ---------------------------------------------------*/
46
47 /* --- Pairs of channels --- *
48  *
49  * Channels are always allocated and freed in pairs.  It makes sense to keep
50  * the pair together.  (It also wastes less memory.)
51  */
52
53 typedef struct chanpair {
54   struct chanpair *next;
55   chan ab, ba;
56 } chanpair;
57
58 /* --- The `private data structure' --- *
59  *
60  * This is called a @tango@ because it takes two (endpoints).
61  */
62
63 typedef struct tango {
64   struct tango *next, *prev;            /* A big list of all tangos */
65   endpt *a, *b;                         /* The two endpoints */
66   unsigned s;                           /* State of the connection */
67   chanpair *c;                          /* The pair of channels */
68 } tango;
69
70 #define EPS_AB 1u
71 #define EPS_BA 2u
72
73 /*----- Static variables --------------------------------------------------*/
74
75 static chanpair *chans = 0;             /* List of spare channel pairs */
76 static tango *tangos = 0;               /* List of tangos */
77
78 /*----- Main code ---------------------------------------------------------*/
79
80 /* --- @doneab@, @doneba@ --- *
81  *
82  * Arguments:   @void *p@ = pointer to a tango block
83  *
84  * Returns:     ---
85  *
86  * Use:         Handles completion of a channel.
87  */
88
89 static void doneab(void *p)
90 {
91   tango *t = p;
92   t->s &= ~EPS_AB;
93   t->b->ops->wclose(t->b);
94   if (!t->s)
95     endpt_kill(t->a);
96 }
97
98 static void doneba(void *p)
99 {
100   tango *t = p;
101   t->s &= ~EPS_BA;
102   t->a->ops->wclose(t->a);
103   if (!t->s)
104     endpt_kill(t->a);
105 }
106
107 /* --- @endpt_kill@ --- *
108  *
109  * Arguments:   @endpt *a@ = an endpoint
110  *
111  * Returns:     ---
112  *
113  * Use:         Kills an endpoint.  If the endpoint is joined to another, the
114  *              other endpoint is also killed, as is the connection between
115  *              them (and that's the tricky bit).
116  */
117
118 void endpt_kill(endpt *a)
119 {
120   tango *t;
121   endpt *b;
122
123   /* --- If the endpont is unconnected, just close it --- */
124
125   if (!a->t) {
126     a->ops->close(a);
127     return;
128   }
129   t = a->t;
130   a = t->a;
131   b = t->b;
132
133   /* --- See whether to close channels --- *
134    *
135    * I'll only have opened channels if both things are files.  Also, the
136    * channel from @b@ to @a@ will only be open if @b@ is not pending.
137    */
138
139   if (a->f & b->f & EPF_FILE) {
140     if (t->s & EPS_AB)
141       chan_close(&t->c->ab);
142     if (!(b->f & EPF_PENDING) && (t->s & EPS_BA))
143       chan_close(&t->c->ba);
144     t->c->next = chans;
145     chans = t->c;
146   }
147
148   /* --- Now just throw the endpoints and tango block away --- */
149
150   a->ops->close(a);
151   b->ops->close(b);
152   if (t->next)
153     t->next->prev = t->prev;
154   if (t->prev)
155     t->prev->next = t->next;
156   else
157     tangos = t->next;
158   DESTROY(t);
159 }
160
161 /* --- @endpt_killall@ --- *
162  *
163  * Arguments:   ---
164  *
165  * Returns:     ---
166  *
167  * Use:         Destroys all current endpoint connections.  Used when
168  *              shutting down.
169  */
170
171 void endpt_killall(void)
172 {
173   tango *t = tangos;
174   while (t) {
175     tango *tt = t;
176     t = t->next;
177     endpt_kill(tt->a);
178   }
179   tangos = 0;
180 }
181
182 /* --- @endpt_join@ --- *
183  *
184  * Arguments:   @endpt *a@ = pointer to first endpoint
185  *              @endpt *b@ = pointer to second endpoint
186  *
187  * Returns:     ---
188  *
189  * Use:         Joins two endpoints together.  It's OK to join endpoints
190  *              which are already joined; in fact, the the right thing to do
191  *              when your endpoint decides that it's not pending any more is
192  *              to join it to its partner again.
193  */
194
195 void endpt_join(endpt *a, endpt *b)
196 {
197   tango *t = a->t;
198
199   /* --- If there is no tango yet, create one --- */
200
201   if (!t) {
202     t = CREATE(tango);
203     t->next = tangos;
204     t->prev = 0;
205     t->a = b->other = a;
206     t->b = a->other = b;
207     a->t = b->t = t;
208     t->s = EPS_AB | EPS_BA;
209     t->c = 0;
210     if (tangos)
211       tangos->prev = t;
212     tangos = t;
213   }
214
215   /* --- If both endpoints are unfinished, leave them for a while --- */
216
217   if (a->f & b->f & EPF_PENDING)
218     return;
219
220   /* --- At least one endpoint is ready --- *
221    *
222    * If both endpoints are files then I can speculatively create the
223    * channels, which will let data start flowing a bit.  Otherwise, I'll just
224    * have to wait some more.
225    */
226
227   if ((a->f | b->f) & EPF_PENDING) {
228
229     /* --- Only be interested if both things are files --- */
230
231     if (!t->c && (a->f & b->f & EPF_FILE)) {
232
233       /* --- Allocate a pair of channels --- */
234
235       if (!chans)
236         t->c = xmalloc(sizeof(*t->c));
237       else {
238         t->c = chans;
239         chans = chans->next;
240       }
241
242       /* --- Make @a@ the endpoint which is ready --- */
243
244       if (a->f & EPF_PENDING) {
245         t->b = a; t->a = b;
246         a = t->a; b = t->b;
247       }
248
249       /* --- Open one channel to read from @a@ --- */
250
251       chan_open(&t->c->ab, a->in->fd, -1, doneab, t);
252     }
253     return;
254   }
255
256   /* --- Both endpoints are now ready --- *
257    *
258    * There are four cases to consider here.
259    */
260
261   /* --- Case 1 --- *
262    *
263    * Neither endpoint is a file.  I need to make a pair of pipes and tell
264    * both endpoints to attach to them.  I can then close the pipes and
265    * discard the tango.
266    */
267
268   if (!((a->f | b->f) & EPF_FILE)) {
269     int pab[2], pba[2];
270     reffd *rab, *wab, *rba, *wba;
271
272     /* --- Create the pipes --- */
273
274     if (pipe(pab)) goto tidy_nofile_0;
275     if (pipe(pba)) goto tidy_nofile_1;
276
277     /* --- Attach reference counters --- */
278
279     rab = reffd_init(pab[0]); wab = reffd_init(pab[1]);
280     rba = reffd_init(pba[0]); wba = reffd_init(pba[1]);
281     a->ops->attach(a, rba, wab);
282     b->ops->attach(b, rab, wba);
283     REFFD_DEC(rab); REFFD_DEC(wab);
284     REFFD_DEC(rba); REFFD_DEC(wba);
285     goto tidy_nofile_0;
286
287     /* --- Tidy up after a mess --- */
288
289   tidy_nofile_1:
290     close(pab[0]); close(pab[1]);
291   tidy_nofile_0:
292     a->ops->close(a);
293     b->ops->close(b);
294     if (t->next)
295       t->next->prev = t->prev;
296     if (t->prev)
297       t->prev->next = t->next;
298     else
299       tangos = t->next;
300     DESTROY(t);
301     return;
302   }
303
304   /* --- Case 2 --- *
305    *
306    * One endpoint is a file, the other isn't.  I just attach the other
307    * endpoint to the file and stand well back.
308    */
309
310   if ((a->f ^ b->f) & EPF_FILE) {
311
312     /* --- Let @a@ be the endpoint with the file --- */
313
314     if (b->f & EPF_FILE) {
315       endpt *e;
316       e = a; a = b; b = e;
317     }
318
319     /* --- Attach the non-file endpoint to the file and run away --- *
320      *
321      * Leave it as the non-file's responsibility to close the other endpoint
322      * when it's ready.  It should also close itself at that time.
323      */
324
325     b->ops->attach(b, a->in, a->out);
326     b->ops->file(b, a);
327     if (t->next)
328       t->next->prev = t->prev;
329     if (t->prev)
330       t->prev->next = t->next;
331     else
332       tangos = t->next;
333     DESTROY(t);
334     return;
335   }
336
337   /* --- Case 3 --- *
338    *
339    * Both endpoints are files and I have a partially created channel pair.  I
340    * need to create the other channel and add a destination to the first one.
341    * In this case, @t->b@ is the endpoint which has just finished setting
342    * itself up.
343    */
344
345   if (t->c) {
346     a = t->a; b = t->b;
347     chan_open(&t->c->ba, b->in->fd, a->out->fd, doneba, t);
348     chan_dest(&t->c->ab, b->out->fd);
349     return;
350   }
351
352   /* --- Case 4 --- *
353    *
354    * Both endpoints are files and I don't have a partially created channel
355    * pair.  I need to allocate a channel pair and create both channels.
356    */
357
358   if (!chans)
359     t->c = xmalloc(sizeof(*t->c));
360   else {
361     t->c = chans;
362     chans = chans->next;
363   }
364   chan_open(&t->c->ab, a->in->fd, b->out->fd, doneab, t);
365   chan_open(&t->c->ba, b->in->fd, a->out->fd, doneba, t);
366   return;
367 }  
368
369 /*----- That's all, folks -------------------------------------------------*/