X-Git-Url: https://git.distorted.org.uk/~mdw/fwd/blobdiff_plain/07d71f34ab3887b63c6ff2d635fce07368f90295..48bb1f76fd805b19aa32a374a8a9d8106ab7f420:/file.c diff --git a/file.c b/file.c new file mode 100644 index 0000000..300ac04 --- /dev/null +++ b/file.c @@ -0,0 +1,610 @@ +/* -*-c-*- + * + * $Id: file.c,v 1.1 1999/07/26 23:33:32 mdw Exp $ + * + * File source and target + * + * (c) 1999 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of the `fw' port forwarder. + * + * `fw' is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * `fw' is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with `fw'; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: file.c,v $ + * Revision 1.1 1999/07/26 23:33:32 mdw + * New sources and targets. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "conf.h" +#include "endpt.h" +#include "fattr.h" +#include "file.h" +#include "fw.h" +#include "reffd.h" +#include "scan.h" +#include "source.h" +#include "target.h" + +/*----- Data structures ---------------------------------------------------*/ + +/* --- File specification --- */ + +typedef struct fspec { + unsigned type; + union { + reffd *fd; + char *name; + } u; +} fspec; + +#define FTYPE_GUESS 0u +#define FTYPE_FD 1u +#define FTYPE_NAME 2u +#define FTYPE_NULL 3u + +/* --- File options --- */ + +typedef struct fopts { + unsigned of; +} fopts; + +/* --- File data block --- */ + +typedef struct fdata { + fspec in, out; + fattr fa; + fopts fo; +} fdata; + +/* --- File source block --- */ + +typedef struct fsource { + source s; + fdata f; +} fsource; + +/* --- File target block --- */ + +typedef struct ftarget { + target t; + fdata f; +} ftarget; + +/*----- Static variables --------------------------------------------------*/ + +static fopts file_opts = { O_TRUNC }; + +static reffd *rstdin, *rstdout; +static reffd *rnull; + +/*----- File endpoint operations ------------------------------------------*/ + +/* --- @wclose@ --- */ + +static void fept_wclose(endpt *e) +{ + if (e->out) { + REFFD_DEC(e->out); + e->out = 0; + } +} + +/* --- @close@ --- */ + +static void fept_close(endpt *e) +{ + if (e->in) + REFFD_DEC(e->in); + if (e->out) + REFFD_DEC(e->out); + fw_dec(); + DESTROY(e); +} + +/* --- Endpoint operations table --- */ + +static endpt_ops fept_ops = { 0, fept_wclose, fept_close }; + +/*----- General operations on sources and targets -------------------------*/ + +/* --- @file_null@ --- * + * + * Arguments: @void *p@ = an uninteresting argument + * + * Returns: --- + * + * Use: Removes the reference to @rnull@ when the file closes. + */ + +static void file_null(void *p) +{ + rnull = 0; +} + +/* --- @file_nullref@ --- * + * + * Arguments: --- + * + * Returns: A reference to a file descriptor open on /dev/null. + * + * Use: Obtains a file descriptor for /dev/null. The reference + * @rnull@ is `weak', so that I only have a descriptor open when + * I actually need one. + */ + +static reffd *file_nullref(void) +{ + if (rnull) + REFFD_INC(rnull); + else { + int n; + if ((n = open("/dev/null", O_RDWR)) < 0) { + fw_log(-1, "couldn't open `/dev/null': %s", strerror(errno)); + return (0); + } + fdflags(n, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + rnull = reffd_init(n); + reffd_handler(rnull, file_null, 0); + } + return (rnull); +} + +/* --- @file_fspec@ --- * + * + * Arguments: @fspec *f@ = pointer to filespec to fill in + * @scanner *sc@ = pointer to scanner to read from + * + * Returns: --- + * + * Use: Reads in a file spec. + */ + +static void file_fspec(fspec *f, scanner *sc) +{ + int c = 0; + unsigned type = FTYPE_GUESS; + reffd *fd = 0; + + /* --- Set up references to @stdin@ and @stdout@ --- */ + + if (!rstdin) { + fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + rstdin = reffd_init(STDIN_FILENO); + rstdout = reffd_init(STDOUT_FILENO); + } + + /* --- Try to get an access type --- */ + + if (sc->t == ':') { + c = 1; + token(sc); + } + + if (sc->t == CTOK_WORD) { + int i = conf_enum(sc, "fd,name,null", c ? ENUM_ABBREV : ENUM_NONE, + "file access type"); + type = i + 1; + if (type && sc->t == ':') + token(sc); + } + + /* --- Check for a valid file descriptor name --- */ + + if (sc->t == CTOK_WORD && type != FTYPE_NAME && type != FTYPE_NULL) { + if (strcmp(sc->d.buf, "stdin") == 0) { + fd = rstdin; + REFFD_INC(rstdin); + } else if (strcmp(sc->d.buf, "stdout") == 0) { + fd = rstdout; + REFFD_INC(rstdout); + } + if (fd) + REFFD_INC(fd); + else if (isdigit((unsigned char)*sc->d.buf)) { + int nfd = atoi(sc->d.buf); + switch (nfd) { + case STDIN_FILENO: fd = rstdin; REFFD_INC(rstdin); break; + case STDOUT_FILENO: fd = rstdout; REFFD_INC(rstdout); break; + default: + fd = reffd_init(nfd); + fdflags(nfd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + break; + } + } + } + + /* --- Sort out what to do as a result --- */ + + if (type == FTYPE_NULL) + f->type = FTYPE_NULL; + else if (fd) { + f->type = FTYPE_FD; + f->u.fd = fd; + token(sc); + } else if (type == FTYPE_FD) { + if (sc->t == CTOK_WORD) + error(sc, "bad file descriptor `%s'", sc->d.buf); + else + error(sc, "parse error, expected file descriptor"); + } else { + dstr d = DSTR_INIT; + conf_name(sc, '/', &d); + f->type = FTYPE_NAME; + f->u.name = xstrdup(d.buf); + dstr_destroy(&d); + } +} + +/* --- @read@ --- */ + +static void file_read(fdata *f, scanner *sc) +{ + file_fspec(&f->in, sc); + if (sc->t != ',') { + f->out = f->in; + if (f->out.type == FTYPE_FD && f->out.u.fd == rstdin) + f->out.u.fd = rstdout; + } else { + token(sc); + file_fspec(&f->out, sc); + } + f->fa = fattr_global; + f->fo = file_opts; +} + +/* --- @print@ --- */ + +static void file_pfspec(fspec *f, dstr *d) +{ + switch (f->type) { + case FTYPE_FD: + switch (f->u.fd->fd) { + case STDIN_FILENO: + dstr_puts(d, "stdin"); + break; + case STDOUT_FILENO: + dstr_puts(d, "stdout"); + break; + default: + dstr_putf(d, "fd:%i", f->u.fd->fd); + break; + } + break; + case FTYPE_NAME: + dstr_putf(d, "name:%s", f->u.name); + break; + case FTYPE_NULL: + dstr_puts(d, "null"); + break; + } +} + +static void file_desc(fdata *f, dstr *d) +{ + dstr_puts(d, "file "); + file_pfspec(&f->in, d); + dstr_puts(d, ", "); + file_pfspec(&f->out, d); +} + +/* --- @option@ --- */ + +static int file_option(fdata *f, scanner *sc) +{ + fopts *fo = f ? &f->fo : &file_opts; + + CONF_BEGIN(sc, "file", "file") + + /* --- Handle file-specific options --- */ + + if (strcmp(sc->d.buf, "create") == 0) { + token(sc); + if (sc->t == '=') + token(sc); + switch (conf_enum(sc, "no,yes", ENUM_ABBREV, "create status")) { + case 0: fo->of &= ~O_CREAT; break; + case 1: fo->of |= O_CREAT; break; + } + CONF_ACCEPT; + } + + else if (strcmp(sc->d.buf, "open") == 0) { + token(sc); + if (sc->t == '=') + token(sc); + fo->of &= ~(O_TRUNC | O_APPEND | O_EXCL); + switch (conf_enum(sc, "no,truncate,append", + ENUM_ABBREV, "open status")) { + case 0: fo->of |= O_EXCL; break; + case 1: fo->of |= O_TRUNC; break; + case 2: fo->of |= O_APPEND; break; + } + CONF_ACCEPT; + } + + /* --- See if it's a file attribute --- */ + + { + fattr *fa = f ? &f->fa : &fattr_global; + if (fattr_option(sc, fa)) + CONF_ACCEPT; + } + + /* --- Nobody understood it --- */ + + CONF_END; +} + +/* --- @endpt@ --- */ + +static endpt *file_endpt(fdata *f, const char *desc) +{ + reffd *in = 0, *out = 0; + endpt *e; + + /* --- Make the input file first --- */ + + switch (f->in.type) { + case FTYPE_NULL: + in = file_nullref(); + break; + case FTYPE_FD: + in = f->in.u.fd; + REFFD_INC(in); + break; + case FTYPE_NAME: { + int fd; + if ((fd = open(f->in.u.name, O_RDONLY | O_NONBLOCK)) < 0) { + fw_log(-1, "[%s] couldn't open `%s' for reading: %s", + desc, f->in.u.name, strerror(errno)); + return (0); + } + in = reffd_init(fd); + } break; + } + + /* --- Make the output file second --- */ + + switch (f->out.type) { + case FTYPE_NULL: + out = file_nullref(); + break; + case FTYPE_FD: + out = f->out.u.fd; + REFFD_INC(out); + break; + case FTYPE_NAME: { + int m = f->fo.of | O_WRONLY | O_NONBLOCK; + int fd = -1; + + /* --- First try creating the file --- * + * + * This lets me know whether to set the file attributes or not. It + * also stands a chance of noticing people playing silly beggars with + * dangling symlinks. + */ + + if ((m & O_CREAT) && + (fd = open(f->out.u.name, m | O_EXCL, f->fa.mode)) < 0 && + (errno != EEXIST || (m & O_EXCL))) { + fw_log(-1, "[%s] couldn't create `%s': %s", + desc, f->out.u.name, strerror(errno)); + REFFD_DEC(in); + return (0); + } + + if (fd != -1) { + if (fattr_apply(f->out.u.name, &f->fa)) { + fw_log(-1, "[%s] couldn't apply file attributes: %s", + desc, f->out.u.name, strerror(errno)); + } + } else if ((fd = open(f->out.u.name, m & ~O_CREAT)) < 0) { + fw_log(-1, "[%s] couldn't open `%s': %s", + desc, f->out.u.name, strerror(errno)); + REFFD_DEC(in); + return (0); + } + + fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC); + out = reffd_init(fd); + } break; + } + + /* --- Set up the endpoint --- */ + + e = CREATE(endpt); + e->ops = &fept_ops; + e->other = 0; + e->f = EPF_FILE; + e->t = 0; + e->in = in; + e->out = out; + fw_inc(); + return (e); +} + +/* --- @destroy@ --- */ + +static void file_destroy(fdata *f) +{ + if (f->in.type == FTYPE_NAME) + free(f->in.u.name); + if (f->out.type == FTYPE_NAME) + free(f->out.u.name); +} + +/*----- File source description -------------------------------------------*/ + +/* --- @option@ --- */ + +static int fsource_option(source *s, scanner *sc) +{ + fsource *fs = (fsource *)s; + return (file_option(fs ? &fs->f : 0, sc)); +} + +/* --- @read@ --- */ + +static source *fsource_read(scanner *sc) +{ + fsource *fs; + + if (!conf_prefix(sc, "file")) + return (0); + fs = CREATE(fsource); + fs->s.ops = &fsource_ops; + fs->s.desc = 0; + file_read(&fs->f, sc); + return (&fs->s); +} + +/* --- @attach@ --- */ + +static void fsource_destroy(source */*s*/); + +static void fsource_attach(source *s, scanner *sc, target *t) +{ + fsource *fs = (fsource *)s; + endpt *e, *ee; + + /* --- Set up the source description string --- */ + + { + dstr d = DSTR_INIT; + file_desc(&fs->f, &d); + dstr_puts(&d, " -> "); + dstr_puts(&d, t->desc); + fs->s.desc = xstrdup(d.buf); + dstr_destroy(&d); + } + + /* --- And then create the endpoints --- */ + + if ((ee = t->ops->create(t, fs->s.desc)) == 0) + goto tidy; + if ((e = file_endpt(&fs->f, fs->s.desc)) == 0) { + ee->ops->close(ee); + goto tidy; + } + endpt_join(e, ee); + + /* --- Dispose of the source and target now --- */ + +tidy: + t->ops->destroy(t); + fsource_destroy(&fs->s); +} + +/* --- @destroy@ --- */ + +static void fsource_destroy(source *s) +{ + fsource *fs = (fsource *)s; + + /* free(fs->s.desc); */ + file_destroy(&fs->f); + DESTROY(fs); +} + +/* --- File source operations --- */ + +source_ops fsource_ops = { + "file", + fsource_option, fsource_read, fsource_attach, fsource_destroy +}; + +/*----- File target description -------------------------------------------*/ + +/* --- @option@ --- */ + +static int ftarget_option(target *t, scanner *sc) +{ + ftarget *ft = (ftarget *)t; + return (file_option(ft ? &ft->f : 0, sc)); +} + +/* --- @read@ --- */ + +static target *ftarget_read(scanner *sc) +{ + ftarget *ft; + dstr d = DSTR_INIT; + + if (!conf_prefix(sc, "file")) + return (0); + ft = CREATE(ftarget); + ft->t.ops = &ftarget_ops; + file_read(&ft->f, sc); + file_desc(&ft->f, &d); + ft->t.desc = xstrdup(d.buf); + dstr_destroy(&d); + return (&ft->t); +} + +/* --- @create@ --- */ + +static endpt *ftarget_create(target *t, const char *desc) +{ + ftarget *ft = (ftarget *)t; + endpt *e = file_endpt(&ft->f, desc); + return (e); +} + +/* --- @destroy@ --- */ + +static void ftarget_destroy(target *t) +{ + ftarget *ft = (ftarget *)t; + file_destroy(&ft->f); + /* free(ft->t.desc); */ + DESTROY(ft); +} + +/* --- File target operations --- */ + +target_ops ftarget_ops = { + "file", + ftarget_option, ftarget_read, ftarget_create, ftarget_destroy +}; + +/*----- That's all, folks -------------------------------------------------*/