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