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