Use new mLib selbuf features.
[fwd] / file.c
CommitLineData
48bb1f76 1/* -*-c-*-
2 *
59571fdf 3 * $Id: file.c,v 1.3 1999/12/22 15:43:47 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 $
59571fdf 32 * Revision 1.3 1999/12/22 15:43:47 mdw
33 * Fix log messages.
34 *
268f6af4 35 * Revision 1.2 1999/10/22 22:47:13 mdw
36 * New (empty) endpoint method required.
37 *
48bb1f76 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
77typedef 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
92typedef struct fopts {
93 unsigned of;
94} fopts;
95
96/* --- File data block --- */
97
98typedef struct fdata {
99 fspec in, out;
100 fattr fa;
101 fopts fo;
102} fdata;
103
104/* --- File source block --- */
105
106typedef struct fsource {
107 source s;
108 fdata f;
109} fsource;
110
111/* --- File target block --- */
112
113typedef struct ftarget {
114 target t;
115 fdata f;
116} ftarget;
117
118/*----- Static variables --------------------------------------------------*/
119
120static fopts file_opts = { O_TRUNC };
121
122static reffd *rstdin, *rstdout;
123static reffd *rnull;
124
125/*----- File endpoint operations ------------------------------------------*/
126
127/* --- @wclose@ --- */
128
129static 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
139static 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
268f6af4 151static endpt_ops fept_ops = { 0, 0, fept_wclose, fept_close };
48bb1f76 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
164static 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
180static 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
207static 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
286static 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
303static 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
328static 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
338static 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
386static 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)) {
59571fdf 444 fw_log(-1, "[%s] couldn't apply file attributes to `%s': %s",
48bb1f76 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
474static 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
486static 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
494static 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
509static void fsource_destroy(source */*s*/);
510
511static 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
539tidy:
540 t->ops->destroy(t);
541 fsource_destroy(&fs->s);
542}
543
544/* --- @destroy@ --- */
545
546static 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
557source_ops fsource_ops = {
558 "file",
559 fsource_option, fsource_read, fsource_attach, fsource_destroy
560};
561
562/*----- File target description -------------------------------------------*/
563
564/* --- @option@ --- */
565
566static 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
574static 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
592static 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
601static 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
611target_ops ftarget_ops = {
612 "file",
613 ftarget_option, ftarget_read, ftarget_create, ftarget_destroy
614};
615
616/*----- That's all, folks -------------------------------------------------*/