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 -------------------------------------------------*/