chiark / gitweb /
cc319ce0be40046e92f6056dbb350b2a18d43815
[fwd] / file.c
1 /* -*-c-*-
2  *
3  * File source and target
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 /* --- 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;
65 } fsource;
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
107 static endpt_ops fept_ops = { 0, 0, fept_wclose, fept_close };
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) {
143       fw_log(-1, "couldn't open `/dev/null': %s", strerror(errno));
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);
151 }
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;
225     token(sc);
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;
233     conf_fname(sc, &d);
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);
245   if (sc->t != ',') {
246     f->out = f->in;
247     if (f->out.type == FTYPE_FD && f->out.u.fd == rstdin)
248       f->out.u.fd = rstdout;
249   } else {
250     token(sc);
251     file_fspec(&f->out, sc);
252   }
253   f->fa = fattr_global;
254   f->fo = file_opts;
255 }
256
257 /* --- @print@ --- */
258
259 static void file_pfspec(fspec *f, dstr *d)
260 {
261   switch (f->type) {
262     case FTYPE_FD:
263       switch (f->u.fd->fd) {
264         case STDIN_FILENO:
265           dstr_puts(d, "stdin");
266           break;
267         case STDOUT_FILENO:
268           dstr_puts(d, "stdout");
269           break;
270         default:
271           dstr_putf(d, "fd:%i", f->u.fd->fd);
272           break;
273       }
274       break;
275     case FTYPE_NAME:
276       dstr_putf(d, "name:%s", f->u.name);
277       break;
278     case FTYPE_NULL:
279       dstr_puts(d, "null");
280       break;
281   }
282 }
283
284 static void file_desc(fdata *f, dstr *d)
285 {
286   dstr_puts(d, "file ");
287   file_pfspec(&f->in, d);
288   dstr_puts(d, ", ");
289   file_pfspec(&f->out, d);
290 }
291
292 /* --- @option@ --- */
293
294 static int file_option(fdata *f, scanner *sc)
295 {
296   fopts *fo = f ? &f->fo : &file_opts;
297
298   CONF_BEGIN(sc, "file", "file")
299
300   /* --- Handle file-specific options --- */
301
302   if (strcmp(sc->d.buf, "create") == 0) {
303     token(sc);
304     if (sc->t == '=')
305       token(sc);
306     switch (conf_enum(sc, "no,yes", ENUM_ABBREV, "create status")) {
307       case 0: fo->of &= ~O_CREAT; break;
308       case 1: fo->of |= O_CREAT; break;
309     }
310     CONF_ACCEPT;
311   }
312
313   else if (strcmp(sc->d.buf, "open") == 0) {
314     token(sc);
315     if (sc->t == '=')
316       token(sc);
317     fo->of &= ~(O_TRUNC | O_APPEND | O_EXCL);
318     switch (conf_enum(sc, "no,truncate,append",
319                       ENUM_ABBREV, "open status")) {
320       case 0: fo->of |= O_EXCL; break;
321       case 1: fo->of |= O_TRUNC; break;
322       case 2: fo->of |= O_APPEND; break;
323     }
324     CONF_ACCEPT;
325   }
326
327   /* --- See if it's a file attribute --- */
328
329   {
330     fattr *fa = f ? &f->fa : &fattr_global;
331     if (fattr_option(sc, fa))
332       CONF_ACCEPT;
333   }
334
335   /* --- Nobody understood it --- */
336
337   CONF_END;
338 }
339
340 /* --- @endpt@ --- */
341
342 static endpt *file_endpt(fdata *f, const char *desc)
343 {
344   reffd *in = 0, *out = 0;
345   endpt *e;
346
347   /* --- Make the input file first --- */
348
349   switch (f->in.type) {
350     case FTYPE_NULL:
351       in = file_nullref();
352       break;
353     case FTYPE_FD:
354       in = f->in.u.fd;
355       REFFD_INC(in);
356       break;
357     case FTYPE_NAME: {
358       int fd;
359       if ((fd = open(f->in.u.name, O_RDONLY | O_NONBLOCK)) < 0) {
360         fw_log(-1, "[%s] couldn't open `%s' for reading: %s",
361                desc, f->in.u.name, strerror(errno));
362         return (0);
363       }
364       in = reffd_init(fd);
365     } break;
366   }
367
368   /* --- Make the output file second --- */
369
370   switch (f->out.type) {
371     case FTYPE_NULL:
372       out = file_nullref();
373       break;
374     case FTYPE_FD:
375       out = f->out.u.fd;
376       REFFD_INC(out);
377       break;
378     case FTYPE_NAME: {
379       int m = f->fo.of | O_WRONLY | O_NONBLOCK;
380       int fd = -1;
381
382       /* --- First try creating the file --- *
383        *
384        * This lets me know whether to set the file attributes or not.  It
385        * also stands a chance of noticing people playing silly beggars with
386        * dangling symlinks.
387        */
388
389       if ((m & O_CREAT) &&
390           (fd = open(f->out.u.name, m | O_EXCL, f->fa.mode)) < 0 &&
391           (errno != EEXIST || (m & O_EXCL))) {
392         fw_log(-1, "[%s] couldn't create `%s': %s",
393                desc, f->out.u.name, strerror(errno));
394         REFFD_DEC(in);
395         return (0);
396       }
397
398       if (fd != -1) {
399         if (fattr_apply(f->out.u.name, &f->fa)) {
400           fw_log(-1, "[%s] couldn't apply file attributes to `%s': %s",
401                  desc, f->out.u.name, strerror(errno));
402         }
403       } else if ((fd = open(f->out.u.name, m & ~O_CREAT)) < 0) {
404         fw_log(-1, "[%s] couldn't open `%s': %s",
405                desc, f->out.u.name, strerror(errno));
406         REFFD_DEC(in);
407         return (0);
408       }
409
410       fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
411       out = reffd_init(fd);
412     } break;
413   }
414
415   /* --- Set up the endpoint --- */
416
417   e = CREATE(endpt);
418   e->ops = &fept_ops;
419   e->other = 0;
420   e->f = EPF_FILE;
421   e->t = 0;
422   e->in = in;
423   e->out = out;
424   fw_inc();
425   return (e);
426 }
427
428 /* --- @destroy@ --- */
429
430 static void file_destroy(fdata *f)
431 {
432   if (f->in.type == FTYPE_NAME)
433     xfree(f->in.u.name);
434   if (f->out.type == FTYPE_NAME)
435     xfree(f->out.u.name);
436 }
437
438 /*----- File source description -------------------------------------------*/
439
440 /* --- @option@ --- */
441
442 static int fsource_option(source *s, scanner *sc)
443 {
444   fsource *fs = (fsource *)s;
445   return (file_option(fs ? &fs->f : 0, sc));
446 }
447
448 /* --- @read@ --- */
449
450 static source *fsource_read(scanner *sc)
451 {
452   fsource *fs;
453
454   if (!conf_prefix(sc, "file"))
455     return (0);
456   fs = CREATE(fsource);
457   fs->s.ops = &fsource_ops;
458   fs->s.desc = 0;
459   file_read(&fs->f, sc);
460   return (&fs->s);
461 }
462
463 /* --- @attach@ --- */
464
465 static void fsource_destroy(source */*s*/);
466
467 static void fsource_attach(source *s, scanner *sc, target *t)
468 {
469   fsource *fs = (fsource *)s;
470   endpt *e, *ee;
471
472   /* --- Set up the source description string --- */
473
474   {
475     dstr d = DSTR_INIT;
476     file_desc(&fs->f, &d);
477     dstr_puts(&d, " -> ");
478     dstr_puts(&d, t->desc);
479     fs->s.desc = xstrdup(d.buf);
480     dstr_destroy(&d);
481   }
482
483   /* --- And then create the endpoints --- */
484
485   if ((ee = t->ops->create(t, fs->s.desc)) == 0)
486     goto tidy;
487   if ((e = file_endpt(&fs->f, fs->s.desc)) == 0) {
488     ee->ops->close(ee);
489     goto tidy;
490   }
491   endpt_join(e, ee, fs->s.desc);
492
493   /* --- Dispose of the source and target now --- */
494
495 tidy:
496   t->ops->destroy(t);
497   fsource_destroy(&fs->s);
498 }
499
500 /* --- @destroy@ --- */
501
502 static void fsource_destroy(source *s)
503 {
504   fsource *fs = (fsource *)s;
505   xfree(fs->s.desc);
506   file_destroy(&fs->f);
507   DESTROY(fs);
508 }
509
510 /* --- File source operations --- */
511
512 source_ops fsource_ops = {
513   "file",
514   fsource_option, fsource_read, fsource_attach, fsource_destroy
515 };
516
517 /*----- File target description -------------------------------------------*/
518
519 /* --- @option@ --- */
520
521 static int ftarget_option(target *t, scanner *sc)
522 {
523   ftarget *ft = (ftarget *)t;
524   return (file_option(ft ? &ft->f : 0, sc));
525 }
526
527 /* --- @read@ --- */
528
529 static target *ftarget_read(scanner *sc)
530 {
531   ftarget *ft;
532   dstr d = DSTR_INIT;
533
534   if (!conf_prefix(sc, "file"))
535     return (0);
536   ft = CREATE(ftarget);
537   ft->t.ops = &ftarget_ops;
538   file_read(&ft->f, sc);
539   file_desc(&ft->f, &d);
540   ft->t.desc = xstrdup(d.buf);
541   dstr_destroy(&d);
542   return (&ft->t);
543 }
544
545 /* --- @create@ --- */
546
547 static endpt *ftarget_create(target *t, const char *desc)
548 {
549   ftarget *ft = (ftarget *)t;
550   endpt *e = file_endpt(&ft->f, desc);
551   return (e);
552 }
553
554 /* --- @destroy@ --- */
555
556 static void ftarget_destroy(target *t)
557 {
558   ftarget *ft = (ftarget *)t;
559   file_destroy(&ft->f);
560   xfree(ft->t.desc);
561   DESTROY(ft);
562 }
563
564 /* --- File target operations --- */
565
566 target_ops ftarget_ops = {
567   "file",
568   ftarget_option, ftarget_read, 0, ftarget_create, ftarget_destroy
569 };
570
571 /*----- That's all, folks -------------------------------------------------*/