Commit | Line | Data |
---|---|---|
48bb1f76 | 1 | /* -*-c-*- |
48bb1f76 | 2 | * |
3 | * File source and target | |
4 | * | |
5 | * (c) 1999 Straylight/Edgeware | |
6 | */ | |
7 | ||
206212ca | 8 | /*----- Licensing notice --------------------------------------------------* |
48bb1f76 | 9 | * |
9155ea97 | 10 | * This file is part of the `fwd' port forwarder. |
48bb1f76 | 11 | * |
9155ea97 | 12 | * `fwd' is free software; you can redistribute it and/or modify |
48bb1f76 | 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. | |
206212ca | 16 | * |
9155ea97 | 17 | * `fwd' is distributed in the hope that it will be useful, |
48bb1f76 | 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. | |
206212ca | 21 | * |
48bb1f76 | 22 | * You should have received a copy of the GNU General Public License |
9155ea97 | 23 | * along with `fwd'; if not, write to the Free Software Foundation, |
48bb1f76 | 24 | * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
25 | */ | |
26 | ||
9155ea97 | 27 | #include "fwd.h" |
48bb1f76 | 28 | |
29 | /*----- Data structures ---------------------------------------------------*/ | |
30 | ||
31 | /* --- File specification --- */ | |
32 | ||
33 | typedef struct fspec { | |
34 | unsigned type; | |
35 | union { | |
36 | reffd *fd; | |
37 | char *name; | |
38 | } u; | |
39 | } fspec; | |
40 | ||
41 | #define FTYPE_GUESS 0u | |
42 | #define FTYPE_FD 1u | |
43 | #define FTYPE_NAME 2u | |
44 | #define FTYPE_NULL 3u | |
45 | ||
46 | /* --- File options --- */ | |
47 | ||
48 | typedef struct fopts { | |
49 | unsigned of; | |
50 | } fopts; | |
51 | ||
52 | /* --- File data block --- */ | |
53 | ||
54 | typedef struct fdata { | |
55 | fspec in, out; | |
56 | fattr fa; | |
57 | fopts fo; | |
58 | } fdata; | |
59 | ||
60 | /* --- File source block --- */ | |
61 | ||
62 | typedef struct fsource { | |
63 | source s; | |
64 | fdata f; | |
206212ca | 65 | } fsource; |
48bb1f76 | 66 | |
67 | /* --- File target block --- */ | |
68 | ||
69 | typedef struct ftarget { | |
70 | target t; | |
71 | fdata f; | |
72 | } ftarget; | |
73 | ||
74 | /*----- Static variables --------------------------------------------------*/ | |
75 | ||
76 | static fopts file_opts = { O_TRUNC }; | |
77 | ||
78 | static reffd *rstdin, *rstdout; | |
79 | static reffd *rnull; | |
80 | ||
81 | /*----- File endpoint operations ------------------------------------------*/ | |
82 | ||
83 | /* --- @wclose@ --- */ | |
84 | ||
85 | static void fept_wclose(endpt *e) | |
86 | { | |
87 | if (e->out) { | |
88 | REFFD_DEC(e->out); | |
89 | e->out = 0; | |
90 | } | |
91 | } | |
92 | ||
93 | /* --- @close@ --- */ | |
94 | ||
95 | static void fept_close(endpt *e) | |
96 | { | |
97 | if (e->in) | |
98 | REFFD_DEC(e->in); | |
99 | if (e->out) | |
100 | REFFD_DEC(e->out); | |
101 | fw_dec(); | |
102 | DESTROY(e); | |
103 | } | |
104 | ||
105 | /* --- Endpoint operations table --- */ | |
106 | ||
268f6af4 | 107 | static endpt_ops fept_ops = { 0, 0, fept_wclose, fept_close }; |
48bb1f76 | 108 | |
109 | /*----- General operations on sources and targets -------------------------*/ | |
110 | ||
111 | /* --- @file_null@ --- * | |
112 | * | |
113 | * Arguments: @void *p@ = an uninteresting argument | |
114 | * | |
115 | * Returns: --- | |
116 | * | |
117 | * Use: Removes the reference to @rnull@ when the file closes. | |
118 | */ | |
119 | ||
120 | static void file_null(void *p) | |
121 | { | |
122 | rnull = 0; | |
123 | } | |
124 | ||
125 | /* --- @file_nullref@ --- * | |
126 | * | |
127 | * Arguments: --- | |
128 | * | |
129 | * Returns: A reference to a file descriptor open on /dev/null. | |
130 | * | |
131 | * Use: Obtains a file descriptor for /dev/null. The reference | |
132 | * @rnull@ is `weak', so that I only have a descriptor open when | |
133 | * I actually need one. | |
134 | */ | |
135 | ||
136 | static reffd *file_nullref(void) | |
137 | { | |
138 | if (rnull) | |
139 | REFFD_INC(rnull); | |
140 | else { | |
141 | int n; | |
142 | if ((n = open("/dev/null", O_RDWR)) < 0) { | |
d935f68b | 143 | fw_log(NOW, "couldn't open `/dev/null': %s", strerror(errno)); |
48bb1f76 | 144 | return (0); |
145 | } | |
146 | fdflags(n, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); | |
147 | rnull = reffd_init(n); | |
148 | reffd_handler(rnull, file_null, 0); | |
149 | } | |
150 | return (rnull); | |
206212ca | 151 | } |
48bb1f76 | 152 | |
153 | /* --- @file_fspec@ --- * | |
154 | * | |
155 | * Arguments: @fspec *f@ = pointer to filespec to fill in | |
156 | * @scanner *sc@ = pointer to scanner to read from | |
157 | * | |
158 | * Returns: --- | |
159 | * | |
160 | * Use: Reads in a file spec. | |
161 | */ | |
162 | ||
163 | static void file_fspec(fspec *f, scanner *sc) | |
164 | { | |
165 | int c = 0; | |
166 | unsigned type = FTYPE_GUESS; | |
167 | reffd *fd = 0; | |
168 | ||
169 | /* --- Set up references to @stdin@ and @stdout@ --- */ | |
170 | ||
171 | if (!rstdin) { | |
172 | fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); | |
173 | fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); | |
174 | rstdin = reffd_init(STDIN_FILENO); | |
175 | rstdout = reffd_init(STDOUT_FILENO); | |
176 | } | |
177 | ||
178 | /* --- Try to get an access type --- */ | |
179 | ||
180 | if (sc->t == ':') { | |
181 | c = 1; | |
182 | token(sc); | |
183 | } | |
184 | ||
185 | if (sc->t == CTOK_WORD) { | |
186 | int i = conf_enum(sc, "fd,name,null", c ? ENUM_ABBREV : ENUM_NONE, | |
187 | "file access type"); | |
188 | type = i + 1; | |
189 | if (type && sc->t == ':') | |
190 | token(sc); | |
191 | } | |
192 | ||
193 | /* --- Check for a valid file descriptor name --- */ | |
194 | ||
195 | if (sc->t == CTOK_WORD && type != FTYPE_NAME && type != FTYPE_NULL) { | |
196 | if (strcmp(sc->d.buf, "stdin") == 0) { | |
197 | fd = rstdin; | |
198 | REFFD_INC(rstdin); | |
199 | } else if (strcmp(sc->d.buf, "stdout") == 0) { | |
200 | fd = rstdout; | |
201 | REFFD_INC(rstdout); | |
202 | } | |
203 | if (fd) | |
204 | REFFD_INC(fd); | |
205 | else if (isdigit((unsigned char)*sc->d.buf)) { | |
206 | int nfd = atoi(sc->d.buf); | |
207 | switch (nfd) { | |
208 | case STDIN_FILENO: fd = rstdin; REFFD_INC(rstdin); break; | |
209 | case STDOUT_FILENO: fd = rstdout; REFFD_INC(rstdout); break; | |
210 | default: | |
211 | fd = reffd_init(nfd); | |
212 | fdflags(nfd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); | |
213 | break; | |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | /* --- Sort out what to do as a result --- */ | |
219 | ||
220 | if (type == FTYPE_NULL) | |
221 | f->type = FTYPE_NULL; | |
222 | else if (fd) { | |
223 | f->type = FTYPE_FD; | |
224 | f->u.fd = fd; | |
206212ca | 225 | token(sc); |
48bb1f76 | 226 | } else if (type == FTYPE_FD) { |
227 | if (sc->t == CTOK_WORD) | |
228 | error(sc, "bad file descriptor `%s'", sc->d.buf); | |
229 | else | |
230 | error(sc, "parse error, expected file descriptor"); | |
231 | } else { | |
232 | dstr d = DSTR_INIT; | |
35a142ca | 233 | conf_fname(sc, &d); |
48bb1f76 | 234 | f->type = FTYPE_NAME; |
235 | f->u.name = xstrdup(d.buf); | |
236 | dstr_destroy(&d); | |
237 | } | |
238 | } | |
239 | ||
240 | /* --- @read@ --- */ | |
241 | ||
242 | static void file_read(fdata *f, scanner *sc) | |
243 | { | |
244 | file_fspec(&f->in, sc); | |
d105b00f MW |
245 | if (sc->t == ',') { |
246 | token(sc); | |
247 | file_fspec(&f->out, sc); | |
248 | } else { | |
48bb1f76 | 249 | f->out = f->in; |
250 | if (f->out.type == FTYPE_FD && f->out.u.fd == rstdin) | |
251 | f->out.u.fd = rstdout; | |
d105b00f MW |
252 | else if (f->out.type == FTYPE_NAME) |
253 | f->out.u.name = xstrdup(f->in.u.name); | |
48bb1f76 | 254 | } |
255 | f->fa = fattr_global; | |
256 | f->fo = file_opts; | |
257 | } | |
258 | ||
259 | /* --- @print@ --- */ | |
260 | ||
261 | static void file_pfspec(fspec *f, dstr *d) | |
262 | { | |
263 | switch (f->type) { | |
264 | case FTYPE_FD: | |
265 | switch (f->u.fd->fd) { | |
266 | case STDIN_FILENO: | |
267 | dstr_puts(d, "stdin"); | |
268 | break; | |
269 | case STDOUT_FILENO: | |
270 | dstr_puts(d, "stdout"); | |
271 | break; | |
272 | default: | |
273 | dstr_putf(d, "fd:%i", f->u.fd->fd); | |
274 | break; | |
275 | } | |
276 | break; | |
277 | case FTYPE_NAME: | |
278 | dstr_putf(d, "name:%s", f->u.name); | |
279 | break; | |
280 | case FTYPE_NULL: | |
281 | dstr_puts(d, "null"); | |
282 | break; | |
206212ca | 283 | } |
48bb1f76 | 284 | } |
285 | ||
286 | static void file_desc(fdata *f, dstr *d) | |
287 | { | |
288 | dstr_puts(d, "file "); | |
289 | file_pfspec(&f->in, d); | |
290 | dstr_puts(d, ", "); | |
291 | file_pfspec(&f->out, d); | |
292 | } | |
293 | ||
294 | /* --- @option@ --- */ | |
295 | ||
296 | static int file_option(fdata *f, scanner *sc) | |
297 | { | |
298 | fopts *fo = f ? &f->fo : &file_opts; | |
299 | ||
300 | CONF_BEGIN(sc, "file", "file") | |
301 | ||
302 | /* --- Handle file-specific options --- */ | |
303 | ||
304 | if (strcmp(sc->d.buf, "create") == 0) { | |
305 | token(sc); | |
306 | if (sc->t == '=') | |
307 | token(sc); | |
308 | switch (conf_enum(sc, "no,yes", ENUM_ABBREV, "create status")) { | |
309 | case 0: fo->of &= ~O_CREAT; break; | |
310 | case 1: fo->of |= O_CREAT; break; | |
311 | } | |
312 | CONF_ACCEPT; | |
313 | } | |
314 | ||
315 | else if (strcmp(sc->d.buf, "open") == 0) { | |
316 | token(sc); | |
317 | if (sc->t == '=') | |
318 | token(sc); | |
319 | fo->of &= ~(O_TRUNC | O_APPEND | O_EXCL); | |
320 | switch (conf_enum(sc, "no,truncate,append", | |
321 | ENUM_ABBREV, "open status")) { | |
322 | case 0: fo->of |= O_EXCL; break; | |
323 | case 1: fo->of |= O_TRUNC; break; | |
324 | case 2: fo->of |= O_APPEND; break; | |
325 | } | |
326 | CONF_ACCEPT; | |
327 | } | |
328 | ||
329 | /* --- See if it's a file attribute --- */ | |
330 | ||
331 | { | |
332 | fattr *fa = f ? &f->fa : &fattr_global; | |
333 | if (fattr_option(sc, fa)) | |
334 | CONF_ACCEPT; | |
335 | } | |
336 | ||
337 | /* --- Nobody understood it --- */ | |
338 | ||
339 | CONF_END; | |
340 | } | |
341 | ||
342 | /* --- @endpt@ --- */ | |
343 | ||
344 | static endpt *file_endpt(fdata *f, const char *desc) | |
345 | { | |
346 | reffd *in = 0, *out = 0; | |
347 | endpt *e; | |
348 | ||
349 | /* --- Make the input file first --- */ | |
350 | ||
351 | switch (f->in.type) { | |
352 | case FTYPE_NULL: | |
353 | in = file_nullref(); | |
354 | break; | |
355 | case FTYPE_FD: | |
356 | in = f->in.u.fd; | |
357 | REFFD_INC(in); | |
358 | break; | |
359 | case FTYPE_NAME: { | |
360 | int fd; | |
361 | if ((fd = open(f->in.u.name, O_RDONLY | O_NONBLOCK)) < 0) { | |
d935f68b | 362 | fw_log(NOW, "[%s] couldn't open `%s' for reading: %s", |
48bb1f76 | 363 | desc, f->in.u.name, strerror(errno)); |
364 | return (0); | |
365 | } | |
366 | in = reffd_init(fd); | |
367 | } break; | |
368 | } | |
369 | ||
370 | /* --- Make the output file second --- */ | |
371 | ||
372 | switch (f->out.type) { | |
373 | case FTYPE_NULL: | |
374 | out = file_nullref(); | |
375 | break; | |
376 | case FTYPE_FD: | |
377 | out = f->out.u.fd; | |
378 | REFFD_INC(out); | |
379 | break; | |
380 | case FTYPE_NAME: { | |
381 | int m = f->fo.of | O_WRONLY | O_NONBLOCK; | |
382 | int fd = -1; | |
383 | ||
384 | /* --- First try creating the file --- * | |
385 | * | |
386 | * This lets me know whether to set the file attributes or not. It | |
387 | * also stands a chance of noticing people playing silly beggars with | |
388 | * dangling symlinks. | |
389 | */ | |
390 | ||
391 | if ((m & O_CREAT) && | |
392 | (fd = open(f->out.u.name, m | O_EXCL, f->fa.mode)) < 0 && | |
393 | (errno != EEXIST || (m & O_EXCL))) { | |
d935f68b | 394 | fw_log(NOW, "[%s] couldn't create `%s': %s", |
48bb1f76 | 395 | desc, f->out.u.name, strerror(errno)); |
396 | REFFD_DEC(in); | |
397 | return (0); | |
398 | } | |
399 | ||
400 | if (fd != -1) { | |
401 | if (fattr_apply(f->out.u.name, &f->fa)) { | |
d935f68b | 402 | fw_log(NOW, "[%s] couldn't apply file attributes to `%s': %s", |
48bb1f76 | 403 | desc, f->out.u.name, strerror(errno)); |
404 | } | |
405 | } else if ((fd = open(f->out.u.name, m & ~O_CREAT)) < 0) { | |
d935f68b | 406 | fw_log(NOW, "[%s] couldn't open `%s': %s", |
48bb1f76 | 407 | desc, f->out.u.name, strerror(errno)); |
408 | REFFD_DEC(in); | |
409 | return (0); | |
410 | } | |
411 | ||
412 | fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); | |
413 | out = reffd_init(fd); | |
414 | } break; | |
415 | } | |
416 | ||
417 | /* --- Set up the endpoint --- */ | |
418 | ||
419 | e = CREATE(endpt); | |
420 | e->ops = &fept_ops; | |
421 | e->other = 0; | |
422 | e->f = EPF_FILE; | |
423 | e->t = 0; | |
424 | e->in = in; | |
425 | e->out = out; | |
426 | fw_inc(); | |
427 | return (e); | |
428 | } | |
429 | ||
430 | /* --- @destroy@ --- */ | |
431 | ||
432 | static void file_destroy(fdata *f) | |
433 | { | |
434 | if (f->in.type == FTYPE_NAME) | |
b0805b27 | 435 | xfree(f->in.u.name); |
48bb1f76 | 436 | if (f->out.type == FTYPE_NAME) |
b0805b27 | 437 | xfree(f->out.u.name); |
48bb1f76 | 438 | } |
439 | ||
440 | /*----- File source description -------------------------------------------*/ | |
441 | ||
442 | /* --- @option@ --- */ | |
443 | ||
444 | static int fsource_option(source *s, scanner *sc) | |
445 | { | |
446 | fsource *fs = (fsource *)s; | |
447 | return (file_option(fs ? &fs->f : 0, sc)); | |
448 | } | |
449 | ||
450 | /* --- @read@ --- */ | |
451 | ||
452 | static source *fsource_read(scanner *sc) | |
453 | { | |
454 | fsource *fs; | |
455 | ||
456 | if (!conf_prefix(sc, "file")) | |
457 | return (0); | |
458 | fs = CREATE(fsource); | |
459 | fs->s.ops = &fsource_ops; | |
57ceb980 MW |
460 | fs->s.ref = 1; |
461 | fs->s.f = 0; | |
48bb1f76 | 462 | fs->s.desc = 0; |
463 | file_read(&fs->f, sc); | |
464 | return (&fs->s); | |
465 | } | |
466 | ||
467 | /* --- @attach@ --- */ | |
468 | ||
469 | static void fsource_destroy(source */*s*/); | |
470 | ||
471 | static void fsource_attach(source *s, scanner *sc, target *t) | |
472 | { | |
473 | fsource *fs = (fsource *)s; | |
474 | endpt *e, *ee; | |
475 | ||
476 | /* --- Set up the source description string --- */ | |
477 | ||
478 | { | |
479 | dstr d = DSTR_INIT; | |
480 | file_desc(&fs->f, &d); | |
481 | dstr_puts(&d, " -> "); | |
482 | dstr_puts(&d, t->desc); | |
483 | fs->s.desc = xstrdup(d.buf); | |
484 | dstr_destroy(&d); | |
485 | } | |
486 | ||
487 | /* --- And then create the endpoints --- */ | |
488 | ||
489 | if ((ee = t->ops->create(t, fs->s.desc)) == 0) | |
57ceb980 | 490 | return; |
48bb1f76 | 491 | if ((e = file_endpt(&fs->f, fs->s.desc)) == 0) { |
492 | ee->ops->close(ee); | |
57ceb980 | 493 | return; |
48bb1f76 | 494 | } |
86c9bc4b | 495 | endpt_join(e, ee, fs->s.desc); |
48bb1f76 | 496 | } |
497 | ||
498 | /* --- @destroy@ --- */ | |
499 | ||
500 | static void fsource_destroy(source *s) | |
501 | { | |
502 | fsource *fs = (fsource *)s; | |
b0805b27 | 503 | xfree(fs->s.desc); |
48bb1f76 | 504 | file_destroy(&fs->f); |
505 | DESTROY(fs); | |
506 | } | |
507 | ||
508 | /* --- File source operations --- */ | |
509 | ||
510 | source_ops fsource_ops = { | |
511 | "file", | |
57ceb980 | 512 | fsource_option, fsource_read, fsource_attach, 0, fsource_destroy |
48bb1f76 | 513 | }; |
514 | ||
515 | /*----- File target description -------------------------------------------*/ | |
516 | ||
517 | /* --- @option@ --- */ | |
518 | ||
519 | static int ftarget_option(target *t, scanner *sc) | |
520 | { | |
521 | ftarget *ft = (ftarget *)t; | |
522 | return (file_option(ft ? &ft->f : 0, sc)); | |
523 | } | |
524 | ||
525 | /* --- @read@ --- */ | |
526 | ||
527 | static target *ftarget_read(scanner *sc) | |
528 | { | |
529 | ftarget *ft; | |
530 | dstr d = DSTR_INIT; | |
531 | ||
532 | if (!conf_prefix(sc, "file")) | |
533 | return (0); | |
534 | ft = CREATE(ftarget); | |
535 | ft->t.ops = &ftarget_ops; | |
57ceb980 | 536 | ft->t.ref = 1; |
48bb1f76 | 537 | file_read(&ft->f, sc); |
538 | file_desc(&ft->f, &d); | |
539 | ft->t.desc = xstrdup(d.buf); | |
540 | dstr_destroy(&d); | |
541 | return (&ft->t); | |
542 | } | |
543 | ||
544 | /* --- @create@ --- */ | |
545 | ||
546 | static endpt *ftarget_create(target *t, const char *desc) | |
547 | { | |
548 | ftarget *ft = (ftarget *)t; | |
549 | endpt *e = file_endpt(&ft->f, desc); | |
550 | return (e); | |
551 | } | |
552 | ||
553 | /* --- @destroy@ --- */ | |
554 | ||
555 | static void ftarget_destroy(target *t) | |
556 | { | |
557 | ftarget *ft = (ftarget *)t; | |
558 | file_destroy(&ft->f); | |
b0805b27 | 559 | xfree(ft->t.desc); |
48bb1f76 | 560 | DESTROY(ft); |
561 | } | |
562 | ||
563 | /* --- File target operations --- */ | |
564 | ||
565 | target_ops ftarget_ops = { | |
566 | "file", | |
ee599f55 | 567 | ftarget_option, ftarget_read, 0, ftarget_create, ftarget_destroy |
48bb1f76 | 568 | }; |
569 | ||
570 | /*----- That's all, folks -------------------------------------------------*/ |