New sources and targets.
[fwd] / file.c
diff --git a/file.c b/file.c
new file mode 100644 (file)
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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/fdflags.h>
+#include <mLib/sub.h>
+
+#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 -------------------------------------------------*/