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