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