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