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