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