Initial revision
authormdw <mdw>
Fri, 25 Jan 2002 19:34:45 +0000 (19:34 +0000)
committermdw <mdw>
Fri, 25 Jan 2002 19:34:45 +0000 (19:34 +0000)
20 files changed:
.cvsignore [new file with mode: 0644]
.links [new file with mode: 0644]
.skelrc [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
acconfig.h [new file with mode: 0644]
configure.in [new file with mode: 0644]
err.c [new file with mode: 0644]
err.h [new file with mode: 0644]
main.c [new file with mode: 0644]
rxglue.c [new file with mode: 0644]
rxglue.h [new file with mode: 0644]
serial.c [new file with mode: 0644]
serial.h [new file with mode: 0644]
setup [new file with mode: 0755]
tx-serial-unix.c [new file with mode: 0644]
tx-serial-unix.h [new file with mode: 0644]
tx-socket.c [new file with mode: 0644]
tx-socket.h [new file with mode: 0644]
txport.c [new file with mode: 0644]
txport.h [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..af3840d
--- /dev/null
@@ -0,0 +1,6 @@
+Makefile.in
+aclocal.m4
+config.h.in
+configure
+stamp-h.in
+build
diff --git a/.links b/.links
new file mode 100644 (file)
index 0000000..b3f8cad
--- /dev/null
+++ b/.links
@@ -0,0 +1,4 @@
+COPYING
+install-sh
+mkinstalldirs
+missing
diff --git a/.skelrc b/.skelrc
new file mode 100644 (file)
index 0000000..d5d8340
--- /dev/null
+++ b/.skelrc
@@ -0,0 +1,8 @@
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+      (append
+       '((author . "Mark Wooding")
+        (full-title . "Jog: Programming for a jogging machine")
+        (program . "Jog"))
+       skel-alist))
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..b536981
--- /dev/null
@@ -0,0 +1,46 @@
+## -*-makefile-*-
+##
+## $Id: Makefile.am,v 1.1 2002/01/25 19:34:45 mdw Exp $
+##
+## Makefile for jog
+##
+## (c) 2001 Mark Wooding
+##
+
+##----- Licensing notice ----------------------------------------------------
+##
+## This file is part of Jog: Programming for a jogging machine.
+##
+## Jog 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.
+## 
+## Jog 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 Jog; if not, write to the Free Software Foundation,
+## Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+##----- Revision history ----------------------------------------------------
+##
+## $Log: Makefile.am,v $
+## Revision 1.1  2002/01/25 19:34:45  mdw
+## Initial revision
+##
+
+AUTOMAKE_OPTIONS = foreign
+
+bin_PROGRAMS = jogrx
+
+jogrx_SOURCES = \
+       main.c \
+       err.c rxglue.c txport.c serial.c \
+       err.h rxglue.h txport.h serial.h \
+       tx-socket.c tx-socket.h \
+       tx-serial-unix.c tx-serial-unix.h
+
+##----- That's all, folks ---------------------------------------------------
diff --git a/acconfig.h b/acconfig.h
new file mode 100644 (file)
index 0000000..45739b8
--- /dev/null
@@ -0,0 +1,65 @@
+/* -*-c-*-
+ *
+ * $Id: acconfig.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Configuration header
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: acconfig.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef ACCONFIG_H
+#define ACCONFIG_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Configuration macros ----------------------------------------------*/
+@TOP@
+
+/* Package name.  */
+#define PACKAGE "jog"
+
+/* Package version number.  */
+#define VERSION "1.0.0"
+
+/* If it's not provided already, define to be a signed integer type capable
+ * of representing any object size.  (If in doubt, make it an `int'.)  */
+#undef ssize_t
+
+@BOTTOM@
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..67475a3
--- /dev/null
@@ -0,0 +1,78 @@
+dnl -*-fundamental-*-
+dnl
+dnl $Id: configure.in,v 1.1 2002/01/25 19:34:45 mdw Exp $
+dnl
+dnl Configuration script for jog
+dnl
+dnl (c) 2001 Mark Wooding
+dnl
+
+dnl ----- Licensing notice --------------------------------------------------
+dnl
+dnl This file is part of Jog: Programming for a jogging machine.
+dnl
+dnl Jog is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl 
+dnl Jog is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU General Public License for more details.
+dnl 
+dnl You should have received a copy of the GNU General Public License
+dnl along with Jog; if not, write to the Free Software Foundation,
+dnl Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+dnl ----- Revision history --------------------------------------------------
+dnl
+dnl $Log: configure.in,v $
+dnl Revision 1.1  2002/01/25 19:34:45  mdw
+dnl Initial revision
+dnl
+
+AC_INIT(rxglue.c)
+AM_INIT_AUTOMAKE(jog, 1.0.0)
+AM_CONFIG_HEADER(config.h)
+
+AC_PROG_CC
+AC_PROG_CPP
+mdw_GCC_FLAGS
+
+AC_CACHE_CHECK([where to find <rexxsaa.h>], [mdw_cv_rexxsaa_path], [
+  bad=true
+  tmp_CPPFLAGS=$CPPFLAGS
+  for i in present /usr/include/regina /usr/local/include/regina; do
+    case $i in
+      present) ;;
+      /*) CPPFLAGS="-I$i $tmp_CPPFLAGS" ;;
+      *) AC_MSG_ERROR([Buggered!]) ;;
+    esac
+    ac_cpp='$CPP $CPPFLAGS'
+    AC_TRY_CPP([#include <rexxsaa.h>], [bad=false; break;])
+  done
+  if $bad; then
+    AC_MSG_ERROR([header file <rexxsaa.h> not found])
+  fi
+  CPPFLAGS=$tmp_CPPFLAGS
+  mdw_cv_rexxsaa_path=$i
+])
+case $mdw_cv_rexxsaa_path in
+  present) ;;
+  /*) CPPFLAGS="-I$mdw_cv_rexxsaa_path $tmp_CPPFLAGS" ;;
+  *) AC_MSG_ERROR([Buggered!]) ;;
+esac
+
+mdw_CHECK_MANYLIBS(crypt, crypt)
+mdw_CHECK_MANYLIBS(dlopen, dl)
+mdw_CHECK_MANYLIBS(RexxStart, regina rexx)
+mdw_CHECK_MANYLIBS(pthread_create, pthread)
+mdw_CHECK_MANYLIBS(socket, socket)
+mdw_MLIB(2.0.0pre4)
+
+mdw_TYPE_SSIZE_T
+
+AC_OUTPUT(Makefile)
+
+dnl ----- That's all, folks -------------------------------------------------
diff --git a/err.c b/err.c
new file mode 100644 (file)
index 0000000..27065b0
--- /dev/null
+++ b/err.c
@@ -0,0 +1,206 @@
+/* -*-c-*-
+ *
+ * $Id: err.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Error reporting
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: err.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <mLib/exc.h>
+#include <mLib/quis.h>
+
+/* #include "au.h" */
+#include "err.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static FILE *logfp = 0;
+static unsigned flags = 0;
+
+#define f_thread 1u
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @err_abort@ --- *
+ *
+ * Arguments:  @int reason@ = abort reason code
+ *             @unsigned long err@ = abort error code
+ *             @const char *msg@ = error message
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Reports a fatal error.
+ */
+
+void err_abortv(int reason, unsigned long err, const char *msg, va_list *ap)
+{
+  fprintf(stderr, "%s: fatal error (code %d-%lu): ", QUIS, reason, err);
+  vfprintf(stderr, msg, *ap);
+  putc('\n', stderr);
+  /* au_abort(reason, err); */
+  abort();
+}
+
+void err_abort(int reason, unsigned long err, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  err_abortv(reason, err, msg, &ap);
+  va_end(ap);
+}
+
+/* --- @err_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Attempts to initialize the logging system.  It is a
+ *             catastrophic failure if logging can't start up.
+ */
+
+static void err_exc(exc_extype ex, exc_exval v)
+{
+  switch (ex) {
+    case EXC_NOMEM:
+      err_report(ERR_EXC, 0, ex, "out of memory");
+      break;
+    default:
+      err_report(ERR_EXC, 0, ex, "uncaught mLib exception");
+      break;
+  }
+  exit(EXIT_FAILURE);
+}
+
+void err_init(void)
+{
+  const char *lf;
+
+  lf = getenv("JOG_LOGFILE");
+  if (!lf)
+    logfp = stderr;
+  else if ((logfp = fopen(lf, "w")) == 0) {
+    err_abort(ERRABORT_LOGOPEN, errno,
+             "couldn't open logfile `%s': %s", lf, strerror(errno));
+  }
+  exc_uncaught(err_exc);
+}
+
+/* --- @err_report@ --- *
+ *
+ * Arguments:  @int ctx@ = context code
+ *             @int reason@ = reason code
+ *             @unsigned long err@ = system error code
+ *             @const char *msg@ = textual message to log
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports an error.  Doesn't abort anything unless something
+ *             really serious happens.
+ */
+
+void err_reportv(int ctx, int reason, unsigned long err,
+                const char *msg, va_list *ap)
+{
+  char buf[256];
+  time_t t;
+  struct tm *tm;
+
+  if (ctx && !(flags & f_thread)) {
+    unsigned f = flags;
+    flags |= f_thread;
+/*     au_misc(AU_ERROR, ctx, reason, err); */
+    flags = f;
+  }
+  t = time(0);
+  tm = localtime(&t);
+  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
+  if (fputs(buf, logfp) == EOF ||
+      fprintf(stderr, " %s: ", QUIS) == EOF ||
+      vfprintf(logfp, msg, *ap) == EOF ||
+      (ctx && (fprintf(logfp, " (error %d", ctx) == EOF ||
+              (reason && fprintf(logfp, "-%d", reason) == EOF) ||
+              (err && fprintf(logfp, ":%lu", err) == EOF) ||
+              putc(')', logfp) == EOF)) ||
+      putc('\n', logfp) == EOF ||
+      fflush(logfp) == EOF) {
+    err_abort(ERRABORT_LOGWRITE, errno,
+             "error writing logfile: %s", strerror(errno));
+  }
+}
+
+void err_report(int ctx, int reason, unsigned long err, const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  err_reportv(ctx, reason, err, msg, &ap);
+  va_end(ap);
+}
+
+/* --- @err_log@ --- *
+ *
+ * Arguments:   @const char *msg@ = textual message to log
+ *
+ * Returns:    ---
+ *
+ * Use:                Logs a message.
+ */
+
+void err_logv(const char *msg, va_list *ap)
+{
+  err_reportv(0, 0, 0, msg, ap);
+}
+
+void err_log(const char *msg, ...)
+{
+  va_list ap;
+
+  va_start(ap, msg);
+  err_reportv(0, 0, 0, msg, &ap);
+  va_end(ap);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/err.h b/err.h
new file mode 100644 (file)
index 0000000..fef75b7
--- /dev/null
+++ b/err.h
@@ -0,0 +1,164 @@
+/* -*-c-*-
+ *
+ * $Id: err.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Error reporting
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: err.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef ERR_H
+#define ERR_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdarg.h>
+
+/*----- Error codes -------------------------------------------------------*/
+
+/* --- Abort codes --- *
+ *
+ * Only for the most serious errors, pertaining to the log file.
+ */
+
+#define ERRABORT_LOGOPEN 1u            /* Log file wouldn't open */
+#define ERRABORT_LOGWRITE 2u           /* Couldn't write to log file */
+
+/* --- Error contexts and reason codes --- *
+ *
+ * Most errors are reported as a group of three numeric codes.  These are, in
+ * order:
+ *
+ *   * the `context' code, which describes where whatever it was went wrong
+ *     went wrong;
+ *
+ *   * the `reason' code (specific to a particular context), which describes
+ *     what the program was trying to do at the time; and
+ *
+ *   * the `error' code (again specific to a particular context, but usually
+ *     an @errno@ error code), which explains what the problem actually was.
+ */
+
+#define ERR_EXC 1u                     /* mLib exception error code */
+  /* Reason code is zero; error code is exception number */
+
+#define ERR_RXGLUE 2u                  /* REXX interface stuff */
+#  define ERRRX_SCRIPTREAD 1u          /* Problem reading script file */
+#  define ERRRX_INTERP 2u              /* Error code from interpreter */
+#  define ERRRX_INIT 3u                        /* Initialization error */
+
+#define ERR_RXERR 3u                   /* REXX error */
+  /* Reason code is line number; error code is REXX error number */
+
+#define ERR_TXPORT 4u                  /* Transport switch stuff */
+#  define ERRTX_BADTX 1u               /* Unknown transport name */
+#  define ERRTX_CREATE 2u              /* Error creating transport */
+#  define ERRTX_WRITE 3u               /* Error writing to transport */
+#  define ERRTX_READ 4u                        /* Read error from transport */
+#  define ERRTX_CONFIG 5u              /* Error in configuration */
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @err_abort@ --- *
+ *
+ * Arguments:  @int reason@ = abort reason code
+ *             @unsigned long err@ = abort error code
+ *             @const char *msg@ = error message
+ *
+ * Returns:    Doesn't.
+ *
+ * Use:                Reports a fatal error.
+ */
+
+extern void err_abortv(int /*reason*/, unsigned long /*err*/,
+                      const char */*msg*/, va_list */*ap*/);
+
+extern void err_abort(int /*reason*/, unsigned long /*err*/,
+                     const char */*msg*/, ...);
+
+/* --- @err_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Attempts to initialize the logging system.  It is a
+ *             catastrophic failure if logging can't start up.
+ */
+
+extern void err_init(void);
+
+/* --- @err_report@ --- *
+ *
+ * Arguments:  @int ctx@ = context code
+ *             @int reason@ = reason code
+ *             @unsigned long err@ = system error code
+ *             @const char *msg@ = textual message to log
+ *
+ * Returns:    ---
+ *
+ * Use:                Reports an error.  Doesn't abort anything unless something
+ *             really serious happens.
+ */
+
+extern void err_reportv(int /*ctx*/, int /*reason*/, unsigned long /*err*/,
+                       const char */*msg*/, va_list */*ap*/);
+
+extern void err_report(int /*ctx*/, int /*reason*/, unsigned long /*err*/,
+                      const char */*msg*/, ...);
+
+/* --- @err_log@ --- *
+ *
+ * Arguments:   @const char *msg@ = textual message to log
+ *
+ * Returns:    ---
+ *
+ * Use:                Logs a message.
+ */
+
+extern void err_logv(const char */*msg*/, va_list */*ap*/);
+
+extern void err_log(const char */*msg*/, ...);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..6855a86
--- /dev/null
+++ b/main.c
@@ -0,0 +1,205 @@
+/* -*-c-*-
+ *
+ * $Id: main.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Main program
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: main.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <mLib/alloc.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/trace.h>
+
+#include "err.h"
+#include "rxglue.h"
+#include "txport.h"
+#include "tx-serial-unix.h"
+
+/*----- Shutdown stuff ----------------------------------------------------*/
+
+static int sigtab[] = { SIGINT, SIGQUIT, SIGTERM, SIGHUP, -1 };
+
+static void tidy(void)
+{
+  txsu_shutdown();
+}
+
+static void sigtidy(int sig)
+{
+  tidy();
+  signal(sig, SIG_DFL);
+  raise(sig);
+}
+
+/*----- Help functions ----------------------------------------------------*/
+
+static void usage(FILE *fp)
+{
+  pquis(fp, "Usage: $ [-t TRANSPORT] [-f FILE] [-c CONFIG] SCRIPT [ARG]\n");
+}
+
+static void version(FILE *fp)
+{
+  pquis(fp, "$, version " VERSION "\n");
+}
+
+static void help(FILE *fp)
+{
+  version(fp);
+  fputc('\n', fp);
+  usage(fp);
+  fputs("\n\
+Options provided:\n\
+\n\
+-h, --help             Print this help message.\n\
+-v, --version          Show the version number.\n\
+-u, --usage            Show terse usage summary.\n\
+\n\
+-t, --transport=NAME   Use transport type NAME.\n\
+-f, --tx-file=FILE     Communicate using the named FILE.\n\
+-c, --tx-config=CONFIG Use CONFIG as transport configuration.\n\
+",
+       fp);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+  unsigned f = 0;
+  int rc = 0;
+  int i;
+
+#define f_bogus 1u
+
+  ego(argv[0]);
+  atexit(tidy);
+  for (i = 0; sigtab[i] >= 0; i++)
+    signal(sigtab[i], sigtidy);
+
+  err_init();
+  rx_init();
+  trace_on(stderr, 0u);
+  if ((txname = getenv("JOGTX")) != 0)
+    ;
+  else
+    txname = txlist->name;
+
+  for (;;) {
+    static const struct option opt[] = {
+
+      /* --- Standard help options --- */
+
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+
+      /* --- Transport configuration stuff --- */
+
+      { "transport",   OPTF_ARGREQ,    0,      't' },
+      { "transport-config",
+                       OPTF_ARGREQ,    0,      'c' },
+      { "tx-config",   OPTF_ARGREQ,    0,      'c' },
+      { "txconfig",    OPTF_ARGREQ,    0,      'c' },
+      { "config",      OPTF_ARGREQ,    0,      'c' },
+      { "transport-file",
+                       OPTF_ARGREQ,    0,      'f' },
+      { "tx-file",     OPTF_ARGREQ,    0,      'f' },
+      { "txfile",      OPTF_ARGREQ,    0,      'f' },
+      { "file",                OPTF_ARGREQ,    0,      'f' },
+
+      /* --- End marker --- */
+
+      { 0,             0,              0,      0 }
+    };
+
+    i = mdwopt(argc, argv, "hvut:c:f:", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+
+    switch (i) {
+
+      /* --- Standard help options --- */
+
+      case 'h':
+       help(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'u':
+       usage(stdout);
+       exit(0);
+
+      /* --- Transport configuration stuff --- */
+
+      case 't':
+       txname = optarg;
+       break;
+      case 'c':
+       txconf = optarg;
+       break;
+      case 'f':
+       txfile = optarg;
+       break;
+
+      /* --- Errors --- */
+
+      default:
+       f |= f_bogus;
+       break;
+    }
+  }
+
+  if ((f & f_bogus) || (optind != argc - 1 && optind != argc - 2)) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+
+  rc = rx_runfile(argv[optind],
+                 argc - optind - 1, (const char *const *)argv + optind + 1);
+  return (rc ? EXIT_FAILURE : 0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/rxglue.c b/rxglue.c
new file mode 100644 (file)
index 0000000..75cd5c8
--- /dev/null
+++ b/rxglue.c
@@ -0,0 +1,632 @@
+/* -*-c-*-
+ *
+ * $Id: rxglue.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * REXX glue for C core functionality
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: rxglue.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#define INCL_RXFUNC
+#define RX_STRONGTYPING
+#include <rexxsaa.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+
+#include "err.h"
+#include "rxglue.h"
+#include "txport.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static txport *tx = 0;
+
+/*----- Conversion functions ----------------------------------------------*/
+
+/* --- @rxs_putm@ --- *
+ *
+ * Arguments:  @RXSTRING *x@ = pointer to REXX string structure
+ *             For @rxs_putm@:
+ *               @const void *p@ = pointer to data block
+ *               @size_t sz@ = size of data
+ *             For @rxs_putd@:
+ *               @const dstr *d@ = pointer to source string
+ *             For @rxs_putf@ and @rxs_vputf@:
+ *               @const char *m@ = message format string
+ *
+ * Returns:    ---
+ *
+ * Use:                Stashes some text in an @RXSTRING@, overwriting whatever was
+ *             there before.  We assume that the previous contents don't
+ *             require freeing.
+ */
+
+#define RXS_PUTM(x, p, sz) do {                                                \
+  RXSTRING *_x = (x);                                                  \
+  const void *_p = (p);                                                        \
+  size_t _sz = (sz);                                                   \
+  if (!_x->strptr || _x->strlength < _sz)                              \
+    _x->strptr = xmalloc(_sz);                                         \
+  memcpy(_x->strptr, _p, _sz);                                         \
+  _x->strlength = _sz;                                                 \
+} while (0)
+
+static void rxs_putm(RXSTRING *x, const void *p, size_t sz)
+{
+  RXS_PUTM(x, p, sz);
+}
+
+#define RXS_PUTD(x, d) do {                                            \
+  dstr *_d = (d);                                                      \
+  RXS_PUTM((x), _d->buf, _d->len);                                     \
+} while (0)
+
+static void rxs_putd(RXSTRING *x, dstr *d) { RXS_PUTD(x, d); }
+
+static void rxs_vputf(RXSTRING *x, const char *m, va_list *ap)
+{
+  dstr d = DSTR_INIT;
+  dstr_vputf(&d, m, ap);
+  RXS_PUTD(x, &d);
+  DDESTROY(&d);
+}
+
+static void rxs_putf(RXSTRING *x, const char *m, ...)
+{
+  va_list ap;
+  dstr d = DSTR_INIT;
+  va_start(ap, m);
+  dstr_vputf(&d, m, &ap);
+  RXS_PUTD(x, &d);
+  va_end(ap);
+  DDESTROY(&d);
+}
+
+/* --- @rxs_get@ --- *
+ *
+ * Arguments:  @const RXSTRING *x@ = pointer to a REXX string
+ *             @dstr *d@ = where to put it
+ *
+ * Returns:    ---
+ *
+ * Use:                Pulls a REXX string out and puts it in a dynamic string.
+ */
+
+#define RXS_GET(x, d) do {                                             \
+  const RXSTRING *_x = (x);                                            \
+  dstr *_dd = (d);                                                     \
+  DPUTM(_dd, _x->strptr, _x->strlength);                               \
+  DPUTZ(_dd);                                                          \
+} while (0)
+
+static void rxs_get(const RXSTRING *x, dstr *d) { RXS_GET(x, d); }
+
+/* --- @rxs_tol@ --- *
+ *
+ * Arguments:  @const RXSTRING *x@ = pointer to a REXX string
+ *             @long *ii@ = where to put the answer
+ *
+ * Returns:    Zero on success, or nonzero on error.
+ *
+ * Use:                Fetches an integer from a REXX string.  This doesn't cope
+ *             with multiprecision integers or similar silliness.
+ */
+
+static int rxs_tol(const RXSTRING *x, long *ii)
+{
+  long i = 0;
+  const char *p = x->strptr, *l = p + x->strlength;
+  unsigned f = 0;
+
+#define f_neg 1u
+#define f_ok 2u
+
+#define MINR (LONG_MIN/10)
+#define MIND (LONG_MIN%10)
+
+  while (p < l && isspace((unsigned char)*p))
+    p++;
+  if (p >= l)
+    return (-1);
+  if (*p == '+')
+    p++;
+  else if (*p == '-') {
+    f |= f_neg;
+    p++;
+  }
+  while (p < l && isspace((unsigned char)*p))
+    p++;
+  while (p < l && isdigit((unsigned char)*p)) {
+    int j = *p++ - '0';
+    if (i < MINR || (i == MINR && -j < MIND))
+      return (-1);
+    i = (i * 10) - j;
+    f |= f_ok;
+  }
+  while (p < l && isspace((unsigned char)*p))
+    p++;
+  if (p < l || !(f & f_ok))
+    return (-1);
+  if (!(f & f_neg)) {
+    if (i < -LONG_MAX)
+      return (-1);
+    i = -i;
+  }
+  *ii = i;
+  return (0);
+
+#undef MINR
+#undef MIND
+
+#undef f_neg
+#undef f_ok
+}
+
+/* --- @rxs_block@ --- *
+ *
+ * Arguments:  @const RXSTRING *x@ = a REXX string
+ *             @unsigned long *t@ = where to put the block spec
+ *
+ * Returns:    Zero if OK, nonzero on error.
+ *
+ * Use:                Picks out a blockingness spec.
+ */
+
+static int rxs_block(const RXSTRING *x, unsigned long *t)
+{
+  long i;
+
+  if (!x->strptr || x->strlength < 1)
+    return (-1);
+  switch (x->strptr[0]) {
+    case 'f':
+    case 'F':
+      *t = FOREVER;
+      break;
+    default:
+      if (rxs_tol(x, &i) || i < 0)
+       return (-1);
+      *t = i;
+      break;
+  }
+  return (0);
+}
+
+/*----- REXX functions ----------------------------------------------------*/
+
+static APIRET APIENTRY rxfn_test(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                char *sn, RXSTRING *r)
+{
+  ULONG i;
+
+  printf("test entry\n"
+        "  fn = `%s'\n", fn);
+  for (i = 0; i < ac; i++) {
+    long l;
+
+    printf("  av[%lu] = `", i);
+    fwrite(av[i].strptr, 1, av[i].strlength, stdout);
+    if (rxs_tol(&av[i], &l))
+      printf("'\n");
+    else
+      printf("' (%ld)\n", l);
+  }
+  printf("tx = `%s'; f = `%s'; c = `%s'.", txname, txfile, txconf);
+  rxs_putf(r, "function `%s' completed ok", fn);
+  return (0);
+}
+
+/* --- @txname()@ ---
+ *
+ * Arguments:  ---
+ *
+ * Returns:    The currently-selected transport name.
+ */
+
+static APIRET APIENTRY rxfn_txname(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                  char *sn, RXSTRING *r)
+{
+  if (ac)
+    return (-1);
+  rxs_putf(r, "%s", txname);
+  return (0);
+}
+
+/* --- @txfile()@ ---
+ *
+ * Arguments:  ---
+ *
+ * Returns:    The currently-selected transport filename.
+ */
+
+static APIRET APIENTRY rxfn_txfile(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                  char *sn, RXSTRING *r)
+{
+  if (ac)
+    return (-1);
+  rxs_putf(r, "%s", txfile ? txfile : "");
+  return (0);
+}
+
+/* --- @txfile()@ ---
+ *
+ * Arguments:  ---
+ *
+ * Returns:    The currently-selected transport configuration string.
+ */
+
+static APIRET APIENTRY rxfn_txconf(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                  char *sn, RXSTRING *r)
+{
+  if (ac)
+    return (-1);
+  rxs_putf(r, "%s", txconf ? txconf : "");
+  return (0);
+}
+  
+/* --- @txinit([NAME], [FILE], [CONFIG])@ ---
+ *
+ * Arguments:  @NAME@ = transport name to select
+ *             @FILE@ = transport filename
+ *             @CONFIG@ = transport configuration string
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes a transport using the given settings.  Omitted
+ *             arguments are filled in from the command line, or internal
+ *             defaults.
+ */
+
+static APIRET APIENTRY rxfn_txinit(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                  char *sn, RXSTRING *r)
+{
+  const char *n = txname, *f = txfile, *c = txconf;
+  dstr dn = DSTR_INIT, df = DSTR_INIT, dc = DSTR_INIT;
+
+  if (tx)
+    return (-1);
+  if (ac > 3)
+    return (-1);
+  if (ac >= 1 && av[0].strptr) {
+    rxs_get(&av[0], &dn);
+    n = dn.buf;
+  }
+  if (ac >= 2 && av[1].strptr) {
+    rxs_get(&av[1], &df);
+    f = df.buf;
+  }
+  if (ac >= 3 && av[2].strptr) {
+    rxs_get(&av[2], &dn);
+    c = dc.buf;
+  }
+  tx = tx_create(n, f, c);
+  dstr_destroy(&dn);
+  dstr_destroy(&df);
+  dstr_destroy(&dc);
+  if (!tx)
+    return (-1);
+  return (0);
+}
+
+/* --- @txsend(STRING)@ --- *
+ *
+ * Arguments:  @STRING@ = string to send
+ *
+ * Returns:    ---
+ *
+ * Use:                Sends a string (exactly as written) to the transport.
+ */
+
+static APIRET APIENTRY rxfn_txsend(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                  char *sn, RXSTRING *r)
+{
+  if (ac != 1 || !tx || !av[0].strptr)
+    return (-1);
+  tx_write(tx, av[0].strptr, av[0].strlength);
+  return (0);
+}
+
+/* --- @txrecv([MILLIS])@ --- *
+ *
+ * Arguments:  @MILLIS@ = how long (in milliseconds) to wait, or `forever'
+ *
+ * Returns:    The string read (may be null if nothing available -- sorry).
+ *
+ * Use:                Reads the next line from the transport.  If @MILLIS@ is an
+ *             integer, then give up after that many milliseconds of
+ *             waiting; if it is `forever' (or anything beginning with an
+ *             `f') then don't give up.  The default is to wait forever.
+ */
+
+static APIRET APIENTRY rxfn_txrecv(unsigned char *fn, ULONG ac, RXSTRING *av,
+                                  char *sn, RXSTRING *r)
+{
+  txline *l;
+  unsigned long t = FOREVER;
+
+  if (ac > 1 || !tx)
+    return (-1);
+  if (ac >= 1 && rxs_block(&av[0], &t))
+    return (-1);
+
+  l = tx_read(tx, t);
+  if (!l)
+    r->strlength = 0;
+  else {
+    rxs_putm(r, l->s, l->len);
+    tx_freeline(l);
+  }
+  return (0);
+}
+
+/* --- @TXEOF()@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    True if end-of-file has been seen on the transport, otherwise
+ *             false.
+ */
+
+static APIRET APIENTRY rxfn_txeof(unsigned char *fn, ULONG ac,
+                                 RXSTRING *av, char *sn, RXSTRING *r)
+{
+  if (ac || !tx)
+    return (-1);
+  rxs_putf(r, "%d", tx->s == TX_CLOSED && !tx->ll);
+  return (0);
+}
+
+/* --- @txready([MILLIS])@ --- *
+ *
+ * Arguments:  @MILLIS@ = how long (in milliseconds) to wait, or `forever'
+ *
+ * Returns:    True if a line is ready, otherwise false.
+ *
+ * Use:                Returns whether the transport is ready for reading.  If
+ *             @MILLIS@ is an integer, then wait for at most that many
+ *             milliseconds before returning.  If @MILLIS@ is `forever' (or
+ *             anything beginning with `f') then wait forever for
+ *             readiness.  This isn't useless: it can trip the end-of-file
+ *             detector.  If @MILLIS@ is omitted, return immediately (as if
+ *             0 had been specified).
+ */
+
+static APIRET APIENTRY rxfn_txready(unsigned char *fn, ULONG ac,
+                                   RXSTRING *av, char *sn, RXSTRING *r)
+{
+  unsigned long t = 0;
+
+  if (ac > 1 || !tx)
+    return (-1);
+  if (ac >= 1 && rxs_block(&av[0], &t))
+    return (-1);
+  rxs_putf(r, "%d", !!tx_read(tx, t));
+  return (0);
+}
+
+/* --- @MILLIWAIT(MILLIS)@ --- *
+ *
+ * Arguments:  @MILLIS@ = how long (in milliseconds) to wait
+ *
+ * Returns:    ---
+ *
+ * Use:                Waits for @MILLIS@ milliseconds.  Always.
+ */
+
+static APIRET APIENTRY rxfn_milliwait(unsigned char *fn, ULONG ac,
+                                     RXSTRING *av, char *sn, RXSTRING *r)
+{
+  long l;
+  struct timeval tv;
+
+  if (ac != 1 || !av[0].strptr)
+    return (-1);
+  if (rxs_tol(&av[0], &l) || l < 0)
+    return (-1);
+  tv.tv_sec = l / 1000;
+  tv.tv_usec = (l % 1000) * 1000;
+  select(0, 0, 0, 0, &tv);
+  return (0);
+}
+
+/*----- Initialization ----------------------------------------------------*/
+
+struct rxfntab { char *name; RexxFunctionHandler *fn; };
+
+static const struct rxfntab rxfntab[] = {
+  { "test",    rxfn_test },
+  { "txname",  rxfn_txname },
+  { "txfile",  rxfn_txfile },
+  { "txconf",  rxfn_txconf },
+  { "txinit",  rxfn_txinit },
+  { "txsend",  rxfn_txsend },
+  { "txrecv",  rxfn_txrecv },
+  { "txeof",   rxfn_txeof },
+  { "txready", rxfn_txready },
+  { "milliwait", rxfn_milliwait },
+  { 0,         0 }
+};
+
+/* --- @rx_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the REXX external functions.
+ */
+
+void rx_init(void)
+{
+  const struct rxfntab *f;
+  int rc;
+
+  for (f = rxfntab; f->fn; f++) {
+    if ((rc = RexxRegisterFunctionExe(f->name, f->fn)) != 0) {
+      err_report(ERR_RXGLUE, ERRRX_INIT, rc,
+                "couldn't register function `%s' (code %d)", f->name, rc);
+      abort();
+    }
+  }
+}
+
+/*----- Running REXX programs ---------------------------------------------*/
+
+/* --- @rx_run@ --- *
+ *
+ * Arguments:  @const char *name@ = pointer to filename (or null)
+ *             @const void *p@ = pointer to program text
+ *             @size_t sz@ = size of program text
+ *             @int ac@ = number of arguments
+ *             @const char *const *av@ = vector of command-line arguments
+ *
+ * Returns:    Exit code from program.
+ *
+ * Use:                Runs a REXX script from memory.
+ */
+
+int rx_run(const char *name, const void *p, size_t sz,
+          int ac, const char *const *av)
+{
+  RXSTRING prog[2];
+  RXSTRING *argv;
+  RXSTRING res;
+  dstr d = DSTR_INIT;
+  short badrc;
+  int rc;
+  int i;
+
+  /* --- Set things up --- */
+
+  if (!name)
+    name = "incore";
+  MAKERXSTRING(prog[0], (void *)p, sz);
+  MAKERXSTRING(prog[1], 0, 0);
+  argv = xmalloc(ac * sizeof(*argv));
+  for (i = 0; i < ac; i++)
+    MAKERXSTRING(argv[i], (char *)av[i], strlen(av[i]));
+
+  /* --- Run the script --- */
+
+  MAKERXSTRING(res, 0, 0);
+  rc = RexxStart(ac, argv, (char *)name, prog,
+                "CMD", RXCOMMAND, 0, &badrc, &res);
+  if (rc) {
+    free(RXSTRPTR(res));
+    if (rc < 0)
+      err_report(ERR_RXERR, 0, -rc, "rexx error from script `%s'", name);
+    else
+      err_report(ERR_RXGLUE, ERRRX_INTERP, rc, "intepreter internal error");
+    return (-1);
+  }
+
+  /* --- Pick apart the results --- */
+
+  dstr_putm(&d, RXSTRPTR(res), RXSTRLEN(res));
+  free(RXSTRPTR(res));
+  dstr_putz(&d);
+  rc = atoi(d.buf);
+  dstr_destroy(&d);
+  return (rc);
+}
+
+/* --- @rx_runfile@ --- *
+ *
+ * Arguments:  @const char *name@ = pointer to filename
+ *             @int ac@ = number of command-line arguments
+ *             @const char *const *av@ = vector of command-line arguments
+ *
+ * Returns:    Exit code from program.
+ *
+ * Use:                Runs a REXX script from a file, given its name.
+ */
+
+int rx_runfile(const char *name, int ac, const char *const *av)
+{
+  FILE *fp;
+  dstr d = DSTR_INIT;
+  char buf[BUFSIZ];
+  size_t n;
+  int rc;
+
+  /* --- Read the file into memory --- *
+   *
+   * This way avoids any crapness in the REXX implementation and means we can
+   * report errors in a more sensible way.
+   */
+
+  if ((fp = fopen(name, "r")) == 0)
+    goto fail_0;
+  do {
+    n = fread(buf, 1, sizeof(buf), fp);
+    DPUTM(&d, buf, n);
+  } while (n == sizeof(buf));
+  if (ferror(fp))
+    goto fail_1;
+  fclose(fp);
+
+  /* --- Now do the from-memory thing --- */
+
+  rc = rx_run(name, d.buf, d.len, ac, av);
+  dstr_destroy(&d);
+  return (rc);
+
+  /* --- Tidy up on errors --- */
+
+fail_1:
+  dstr_destroy(&d);
+  fclose(fp);
+fail_0:
+  err_report(ERR_RXGLUE, ERRRX_SCRIPTREAD, errno,
+            "couldn't read script `%s': %s", name, strerror(errno));
+  return (-1);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/rxglue.h b/rxglue.h
new file mode 100644 (file)
index 0000000..ee9ad09
--- /dev/null
+++ b/rxglue.h
@@ -0,0 +1,101 @@
+/* -*-c-*-
+ *
+ * $Id: rxglue.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * REXX glue for C core functionality
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: rxglue.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef RXGLUE_H
+#define RXGLUE_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stddef.h>
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @rx_run@ --- *
+ *
+ * Arguments:  @const char *name@ = pointer to filename (or null)
+ *             @const void *p@ = pointer to program text
+ *             @size_t sz@ = size of program text
+ *             @int ac@ = number of arguments
+ *             @const char *const *av@ = vector of command-line arguments
+ *
+ * Returns:    Exit code from program.
+ *
+ * Use:                Runs a REXX script from memory.
+ */
+
+extern int rx_run(const char */*name*/, const void */*p*/, size_t /*sz*/,
+                 int /*ac*/, const char *const */*av*/);
+
+/* --- @rx_runfile@ --- *
+ *
+ * Arguments:  @const char *name@ = pointer to filename
+ *             @int ac@ = number of command-line arguments
+ *             @const char *const *av@ = vector of command-line arguments
+ *
+ * Returns:    Exit code from program.
+ *
+ * Use:                Runs a REXX script from a file, given its name.
+ */
+
+extern int rx_runfile(const char */*name*/,
+                     int /*ac*/, const char *const */*av*/);
+
+/* --- @rx_init@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the REXX external functions.
+ */
+
+extern void rx_init(void);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/serial.c b/serial.c
new file mode 100644 (file)
index 0000000..65f0417
--- /dev/null
+++ b/serial.c
@@ -0,0 +1,223 @@
+/* -*-c-*-
+ *
+ * $Id: serial.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Common serial functionality
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: serial.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "serial.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @getbaud@ --- *
+ *
+ * Arguments:  @const char *p@ = pointer to string
+ *             @const char **pp@ = where to store final pointer
+ *
+ * Returns:    Baud rate, or @-1@ on failure.
+ *
+ * Use:                Parses a baud rate, either as a raw number or as an
+ *             expression like `9k6'.
+ */
+
+static long getbaud(const char *p, const char **pp)
+{
+  long x = 0;
+  long f = 1, s = 10;
+  int ok = 0;
+
+  /* --- Main loop --- */
+
+  for (;;) {
+    int ch = (unsigned char)*p;
+
+    switch (ch) {
+      case 'k':
+      case 'K':
+       f = 1000;
+       goto factor;
+      case 'm':
+      case 'M':
+       f = 1000000;
+       goto factor;
+      factor:
+       if (ok != 1)
+         return (-1);
+       x *= f;
+       s = 1;
+       ok = 2;
+       break;
+      default:
+       if (!isdigit(ch))
+         goto done;
+       if (!ok)
+         ok = 1;
+       if (ok == 2) {
+         if (f == 1)
+           return (-1);
+         f /= 10;
+       }
+       x = x * s + (ch - '0') * f;
+       break;
+    }
+    p++;
+  }
+done:
+  if (!ok)
+    return (-1);
+  if (pp)
+    *pp = p;
+  return (x);
+}
+
+/* --- @serial_parse@ --- *
+ *
+ * Arguments:  @const char *p@ = configuration string to parse
+ *             @serial_config *sc@ = pointer to serial config structure
+ *
+ * Returns:    Zero if OK, or @-1@ on error.
+ *
+ * Use:                Parses a serial config string into something more
+ *             reasonable.  The config structure should have been
+ *             initialized to something sensible (e.g., @SERIAL_INIT@)
+ *             already.
+ */
+
+int serial_parse(const char *p, serial_config *sc)
+{
+  long x;
+  const char *q;
+  char *qq;
+
+  /* --- Pick out an initial baud rate --- */
+
+  while (isspace((unsigned char)*p))
+    p++;
+  if (*p != 0 && *p != ':') {
+    if ((x = getbaud(p, &p)) < 0)
+      goto fail;
+    sc->baud = x;
+  }
+  while (isspace((unsigned char)*p))
+    p++;
+  if (*p == ':')
+    p++;
+
+  /* --- Pick out a word length --- */
+
+  while (isspace((unsigned char)*p))
+    p++;
+  if ((x = strtol(p, &qq, 10)) == 0)
+    goto fail;
+  p = qq;
+  sc->wordlen = x;
+  while (isspace((unsigned char)*p))
+    p++;
+  if (*p == '-')
+    p++;
+
+  /* --- Pick out a parity designation --- */
+
+  while (isspace((unsigned char)*p))
+    p++;
+  for (q = p; isalpha((unsigned char)*q); q++)
+    ;
+  if (q == p)
+    goto fail;
+  if (strncmp(p, "none", q - p) == 0)
+    sc->parity = PARITY_NONE;
+  else if (strncmp(p, "odd", q - p) == 0)
+    sc->parity = PARITY_ODD;
+  else if (strncmp(p, "even", q - p) == 0)
+    sc->parity = PARITY_EVEN;
+  else
+    goto fail;
+  p = q;
+  while (isspace((unsigned char)*p))
+    p++;
+  if (*p == '-')
+    p++;
+
+  /* --- Pick out a number of stop bits --- */
+
+  while (isspace((unsigned char)*p))
+    p++;
+  if ((x = strtol(p, &qq, 10)) == 0)
+    goto fail;
+  p = qq;
+  sc->stopbits = x;
+  while (isspace((unsigned char)*p))
+    p++;
+
+  /* --- Done --- */
+
+  if (*p)
+    goto fail;
+  return (0);
+
+fail:
+  return (-1);
+}
+
+/*----- Test rig ----------------------------------------------------------*/
+
+#ifdef TEST_RIG
+
+#include <stdio.h>
+
+int main(int argc, char *argv[])
+{
+  int i;
+  static const char *ptab[] = { "none", "odd", "even" };
+
+  for (i = 1; i < argc; i++) {
+    serial_config sc = SERIAL_INIT;
+
+    if (serial_parse(argv[i], &sc))
+      printf("invalid serial configuration `%s'\n", argv[i]);
+    else {
+      printf("%lu %u-%s-%u\n", sc.baud, sc.wordlen,
+            ptab[sc.parity], sc.stopbits);
+    }
+  }
+  return (0);
+}
+
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/serial.h b/serial.h
new file mode 100644 (file)
index 0000000..f514bda
--- /dev/null
+++ b/serial.h
@@ -0,0 +1,86 @@
+/* -*-c-*-
+ *
+ * $Id: serial.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Common serial functionality
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: serial.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef SERIAL_H
+#define SERIAL_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct serial_config {
+  unsigned long baud;                  /* Baud rate */
+  unsigned wordlen;                    /* Word length */
+  unsigned parity;                     /* Parity type (magic constant) */
+  unsigned stopbits;                   /* Number of stop bits */
+} serial_config;
+
+enum { PARITY_NONE, PARITY_ODD, PARITY_EVEN };
+
+#define SERIAL_INIT { 9600, 8, PARITY_NONE, 1 }
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @serial_parse@ --- *
+ *
+ * Arguments:  @const char *p@ = configuration string to parse
+ *             @serial_config *sc@ = pointer to serial config structure
+ *
+ * Returns:    Zero if OK, or @-1@ on error.
+ *
+ * Use:                Parses a serial config string into something more
+ *             reasonable.  The config structure should have been
+ *             initialized to something sensible (e.g., @SERIAL_INIT@)
+ *             already.
+ */
+
+extern int serial_parse(const char */*p*/, serial_config */*sc*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/setup b/setup
new file mode 100755 (executable)
index 0000000..5173638
--- /dev/null
+++ b/setup
@@ -0,0 +1,9 @@
+#! /bin/sh
+
+set -e
+mklinks
+mkaclocal
+autoheader
+autoconf
+automake
+mkdir build
diff --git a/tx-serial-unix.c b/tx-serial-unix.c
new file mode 100644 (file)
index 0000000..3d45d9b
--- /dev/null
@@ -0,0 +1,349 @@
+/* -*-c-*-
+ *
+ * $Id: tx-serial-unix.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Unix/POSIX serial transport
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: tx-serial-unix.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <mLib/darray.h>
+#include <mLib/fdflags.h>
+#include <mLib/sub.h>
+
+#include "err.h"
+#include "serial.h"
+#include "txport.h"
+
+#ifndef O_NOCTTY
+#  define O_NOCTTY 0
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct txsu {
+  txport tx;                           /* Transport base */
+  struct txsu *next, **prev;           /* Chain of serial transports */
+  int fd;                              /* File descriptor */
+  struct termios old_ta;               /* Old terminal settings */
+} txsu;
+
+/*----- Static variables --------------------------------------------------*/
+
+struct baudmap { unsigned long baud; unsigned long magic; };
+
+static struct baudmap baudmap[] = {
+#ifdef B50
+  {    50,     B50 },
+#endif
+#ifdef B75
+  {    75,     B75 },
+#endif
+#ifdef B110
+  {    110,    B110 },
+#endif
+#ifdef B134
+  {    134,    B134 },
+#endif
+#ifdef B150
+  {    150,    B150 },
+#endif
+#ifdef B200
+  {    200,    B200 },
+#endif
+#ifdef B300
+  {    300,    B300 },
+#endif
+#ifdef B600
+  {    600,    B600 },
+#endif
+#ifdef B1200
+  {   1200,   B1200 },
+#endif
+#ifdef B1800
+  {   1800,   B1800 },
+#endif
+#ifdef B2400
+  {   2400,   B2400 },
+#endif
+#ifdef B4800
+  {   4800,   B4800 },
+#endif
+#ifdef B9600
+  {   9600,   B9600 },
+#endif
+#ifdef B19200
+  {  19200,  B19200 },
+#endif
+#ifdef B38400
+  {  38400,  B38400 },
+#endif
+#ifdef B57600
+  {  57600,  B57600 },
+#endif
+#ifdef B115200
+  { 115200, B115200 },
+#endif
+#ifdef B230400
+  { 230400, B230400 },
+#endif
+  {      0,       0 }
+};
+
+static unsigned long csize[] = { CS5, CS6, CS7, CS8 };
+
+static txsu *active = 0;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @txsu_shutdown@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Restores terminal settings on exit.
+ */
+
+void txsu_shutdown(void)
+{
+  txsu *tx;
+
+  for (tx = active; tx; tx = tx->next)
+    tcsetattr(tx->fd, TCSAFLUSH, &tx->old_ta);
+}
+
+/* --- @txsu_create@ --- *
+ *
+ * Arguments:  @const char *file@ = filename for serial port
+ *             @const char *config@ = configuration string
+ *
+ * Returns:    Pointer to created transport block.
+ *
+ * Use:                Creates a serial port transport.
+ */
+
+txport *txsu_create(const char *file, const char *config)
+{
+  txsu *tx;
+  serial_config sc = SERIAL_INIT;
+  struct termios ta, old_ta;
+  struct baudmap *b;
+  int fd;
+
+  /* --- Parse the configuration and check it --- */
+
+  if (config && *config && serial_parse(config, &sc))
+    goto conferr;
+
+  for (b = baudmap; b->baud && b->baud != sc.baud; b++)
+    ;
+  if (!b->baud || 
+      sc.wordlen < 5 || sc.wordlen > 8 ||
+      sc.stopbits < 1 || sc.stopbits > 2)
+    goto conferr;
+
+  /* --- Open the serial port and fetch attributes --- */
+
+  if ((fd = open(file, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, errno,
+              "couldn't open device `%s': %s", file, strerror(errno));
+    goto fail_0;
+  }
+  if (fdflags(fd, O_NONBLOCK, 0, 0, 0)) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, errno,
+              "fcntl(clear O_NONBLOCK): %s", file, strerror(errno));
+    goto fail_1;
+  }    
+
+  if (tcgetattr(fd, &ta)) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, errno,
+              "couldn't get terminal attributes: %s", strerror(errno));
+    goto fail_1;
+  }
+  old_ta = ta;
+
+  /* --- Fix the attributes --- */
+
+  ta.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+  ta.c_oflag &= ~OPOST;
+  ta.c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
+  ta.c_cflag &= ~(CRTSCTS);
+  ta.c_cc[VMIN] = 1;
+  ta.c_cc[VTIME] = 0;
+
+  cfsetospeed(&ta, b->magic);
+  cfsetispeed(&ta, b->magic);
+  ta.c_cflag = (ta.c_cflag & ~CSIZE) | csize[sc.wordlen - 5];
+  switch (sc.parity) {
+    case PARITY_NONE: ta.c_cflag &= ~PARENB; break;
+    case PARITY_ODD:  ta.c_cflag |= PARENB | PARODD; break;
+    case PARITY_EVEN: ta.c_cflag |= PARENB; ta.c_cflag &= ~PARODD; break;
+  }
+  switch (sc.stopbits) {
+    case 1: ta.c_cflag &= ~CSTOPB; break;
+    case 2: ta.c_cflag |= CSTOPB; break;
+  }
+
+  /* --- Set attributes --- */
+
+  if (tcsetattr(fd, TCSAFLUSH, &ta)) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, errno,
+              "couldn't set terminal attributes: %s", strerror(errno));
+    goto fail_1;
+  }
+
+  /* --- Done --- */
+
+  tx = CREATE(txsu);
+  tx->fd = fd;
+  tx->old_ta = old_ta;
+  tx->next = active;
+  tx->prev = &active;
+  active = tx;
+  return (&tx->tx);
+
+  /* --- Tidy up because it all went horribly wrong --- */
+
+fail_1:
+  close(fd);
+fail_0:
+  return (0);
+
+conferr:
+  err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
+            "bad configuration for serial port transport");
+  goto fail_0;
+}
+
+/* --- @txsu_write@ --- *
+ *
+ * Arguments:  @txport *txg@ = pointer to transport context
+ *             @const void *p@ = pointer to buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    Number of bytes written, or @-1@ on error.
+ *
+ * Use:                Writes data to a transport.
+ */
+
+ssize_t txsu_write(txport *txg, const void *p, size_t sz)
+{
+  txsu *tx = (txsu *)txg;
+
+  return (write(tx->fd, p, sz));
+}
+
+/* --- @txsu_fetch@ --- *
+ *
+ * Arguments:  @void *txv@ = pointer to transport context
+ *
+ * Returns:    Nothing of interest.
+ *
+ * Use:                Thread to fetch data from a serial port.
+ */
+
+void *txsu_fetch(void *txv)
+{
+  txsu *tx = txv;
+  unsigned char buf[BUFSIZ];
+  ssize_t n;
+  int e;
+
+  /* --- Read data while it arrives --- */
+
+  for (;;) {
+    n = read(tx->fd, buf, sizeof(buf));
+    if (n < 0) {
+      err_report(ERR_TXPORT, ERRTX_READ, errno,
+                "error reading from serial port: %s", strerror(errno));
+      break;
+    }
+#ifdef TERMINAL_EOF
+    if (!n) break;
+#else
+    if (!n) continue;
+#endif
+    if ((e = pthread_mutex_lock(&tx->tx.mx)) != 0) {
+      err_report(ERR_TXPORT, ERRTX_READ, e,
+                "error locking mutex: %s", strerror(e));
+      break;
+    }
+    DA_ENSURE(&tx->tx.buf, n);
+    memcpy(DA(&tx->tx.buf) + DA_LEN(&tx->tx.buf), buf, n);
+    DA_EXTEND(&tx->tx.buf, n);
+    pthread_cond_signal(&tx->tx.cv);
+    pthread_mutex_unlock(&tx->tx.mx);
+  }
+
+  /* --- Deal with crapness --- */
+
+  e = pthread_mutex_lock(&tx->tx.mx);
+  tx->tx.s = TX_CLOSE;
+  pthread_cond_signal(&tx->tx.cv);
+  pthread_mutex_unlock(&tx->tx.mx);
+  return (0);
+}
+
+/* --- @txsu_destroy@ --- *
+ *
+ * Arguments:  @txport *txg@ = pointer to transport context
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a serial port transport.
+ */
+
+void txsu_destroy(txport *txg)
+{
+  txsu *tx = (txsu *)txg;
+
+  tcsetattr(tx->fd, TCSAFLUSH, &tx->old_ta);
+  close(tx->fd);
+  *tx->prev = tx->next;
+  if (tx->next)
+    tx->next->prev = tx->prev;
+  DESTROY(tx);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/tx-serial-unix.h b/tx-serial-unix.h
new file mode 100644 (file)
index 0000000..b61e4ae
--- /dev/null
@@ -0,0 +1,93 @@
+/* -*-c-*-
+ *
+ * $Id: tx-serial-unix.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Unix/POSIX serial port transport
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: tx-serial-unix.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef TX_SERIAL_UNIX_H
+#define TX_SERIAL_UNIX_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#ifndef TXPORT_H
+#  include "txport.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @txsu_shutdown@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Returns:    ---
+ *
+ * Use:                Restores terminal settings on exit.
+ */
+
+extern void txsu_shutdown(void);
+
+extern txport *txsu_create(const char */*file*/, const char */*config*/);
+extern void *txsu_fetch(void */*txv*/);
+extern ssize_t txsu_write(txport */*txg*/,
+                           const void */*p*/, size_t /*sz*/);
+extern void txsu_destroy(txport */*txg*/);
+
+#ifdef TX_LIST
+   static const struct txfile txsu_fv[] = {
+     { "JOGSERIAL", 0 },
+     { 0, "/dev/ttyS0" },
+   };
+
+   static txport_ops txsu_ops = {
+     TX_LIST, "serial-unix", txsu_fv, "9600:8-none-1",
+     txsu_create, txsu_fetch, txsu_write, txsu_destroy
+   };
+#  undef TX_LIST
+#  define TX_LIST &txsu_ops
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/tx-socket.c b/tx-socket.c
new file mode 100644 (file)
index 0000000..8fcf968
--- /dev/null
@@ -0,0 +1,223 @@
+/* -*-c-*-
+ *
+ * $Id: tx-socket.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Socket transport
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: tx-socket.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <pthread.h>
+
+#include <mLib/alloc.h>
+#include <mLib/darray.h>
+#include <mLib/sub.h>
+
+#include "err.h"
+#include "txport.h"
+#include "tx-socket.h"
+
+#undef sun
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct txsock {
+  txport tx;                           /* Transport base */
+  int fd;                              /* File descriptor */
+} txsock;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @txsock_create@ --- *
+ *
+ * Arguments:  @const char *file@ = filename for socket
+ *             @const char *config@ = configuration string
+ *
+ * Returns:    Pointer to created transport block.
+ *
+ * Use:                Creates a socket transport.
+ */
+
+txport *txsock_create(const char *file, const char *config)
+{
+  txsock *tx;
+  int fd;
+  struct sockaddr_un *sun;
+  size_t len, sunsz;
+
+  /* --- Parse the configuration --- */
+
+  if (config && *config) {
+    err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
+              "bad configuration for socket transport");
+    goto fail_0;
+  }
+
+  /* --- Set up the address block --- */
+
+  len = strlen(file) + 1;
+  sunsz = offsetof(struct sockaddr_un, sun_path) + len;
+  sun = xmalloc(sunsz);
+  sun->sun_family = AF_UNIX;
+  memcpy(sun->sun_path, file, len);
+
+  /* --- Create the socket --- */
+
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, errno,
+              "error creating socket: %s", strerror(errno));
+    goto fail_1;
+  }
+
+  /* --- Connect --- */
+
+  if (connect(fd, (struct sockaddr *)sun, sunsz)) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, errno,
+              "couldn't connect to `%s': %s", file, strerror(errno));
+    goto fail_2;
+  }
+
+  /* --- Done --- */
+
+  tx = CREATE(txsock);
+  tx->fd = fd;
+  xfree(sun);
+  return (&tx->tx);
+
+  /* --- Tidy up because it all went horribly wrong --- */
+
+fail_2:
+  close(fd);
+fail_1:
+  xfree(sun);
+fail_0:
+  return (0);
+}
+
+/* --- @txsock_write@ --- *
+ *
+ * Arguments:  @txport *txg@ = pointer to transport context
+ *             @const void *p@ = pointer to buffer
+ *             @size_t sz@ = size of the buffer
+ *
+ * Returns:    Number of bytes written, or @-1@ on error.
+ *
+ * Use:                Writes data to a transport.
+ */
+
+ssize_t txsock_write(txport *txg, const void *p, size_t sz)
+{
+  txsock *tx = (txsock *)txg;
+
+  return (write(tx->fd, p, sz));
+}
+
+/* --- @txsock_fetch@ --- *
+ *
+ * Arguments:  @void *txv@ = pointer to transport context
+ *
+ * Returns:    Nothing of interest.
+ *
+ * Use:                Thread to fetch data from a socket.
+ */
+
+void *txsock_fetch(void *txv)
+{
+  txsock *tx = txv;
+  unsigned char buf[BUFSIZ];
+  ssize_t n;
+  int e;
+
+  /* --- Read data while it arrives --- */
+
+  for (;;) {
+    n = read(tx->fd, buf, sizeof(buf));
+    if (n < 0) {
+      err_report(ERR_TXPORT, ERRTX_READ, errno,
+                "error reading from socket: %s", strerror(errno));
+      break;
+    }
+    if (!n)
+      break;
+    if ((e = pthread_mutex_lock(&tx->tx.mx)) != 0) {
+      err_report(ERR_TXPORT, ERRTX_READ, e,
+                "error locking mutex: %s", strerror(e));
+      break;
+    }
+    DA_ENSURE(&tx->tx.buf, n);
+    memcpy(DA(&tx->tx.buf) + DA_LEN(&tx->tx.buf), buf, n);
+    DA_EXTEND(&tx->tx.buf, n);
+    pthread_cond_signal(&tx->tx.cv);
+    pthread_mutex_unlock(&tx->tx.mx);
+  }
+
+  /* --- Deal with crapness --- */
+
+  e = pthread_mutex_lock(&tx->tx.mx);
+  tx->tx.s = TX_CLOSE;
+  pthread_cond_signal(&tx->tx.cv);
+  pthread_mutex_unlock(&tx->tx.mx);
+  return (0);
+}
+
+/* --- @txsock_destroy@ --- *
+ *
+ * Arguments:  @txport *txg@ = pointer to transport context
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a socket transport.
+ */
+
+void txsock_destroy(txport *txg)
+{
+  txsock *tx = (txsock *)txg;
+
+  close(tx->fd);
+  DESTROY(tx);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/tx-socket.h b/tx-socket.h
new file mode 100644 (file)
index 0000000..f6e4d00
--- /dev/null
@@ -0,0 +1,83 @@
+/* -*-c-*-
+ *
+ * $Id: tx-socket.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Socket transport
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: tx-socket.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef TX_SOCKET_H
+#define TX_SOCKET_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#ifndef TXPORT_H
+#  include "txport.h"
+#endif
+
+/*----- Functions provided ------------------------------------------------*/
+
+extern txport *txsock_create(const char */*file*/, const char */*config*/);
+extern void *txsock_fetch(void */*txv*/);
+extern ssize_t txsock_write(txport */*txg*/,
+                           const void */*p*/, size_t /*sz*/);
+extern void txsock_destroy(txport */*txg*/);
+
+#ifdef TX_LIST
+   static const struct txfile txsock_fv[] = {
+     { "JOGSOCK", 0 },
+     { DIRVAR, "sim" },
+     { 0, "/var/lib/jog/sim" },
+   };
+
+   static txport_ops txsock_ops = {
+     TX_LIST, "socket", txsock_fv, 0,
+     txsock_create, txsock_fetch, txsock_write, txsock_destroy
+   };
+#  undef TX_LIST
+#  define TX_LIST &txsock_ops
+#endif
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/txport.c b/txport.c
new file mode 100644 (file)
index 0000000..d6bdcbd
--- /dev/null
+++ b/txport.c
@@ -0,0 +1,435 @@
+/* -*-c-*-
+ *
+ * $Id: txport.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Transport switch glue
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: txport.c,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include <mLib/darray.h>
+#include <mLib/dstr.h>
+#include <mLib/lbuf.h>
+#include <mLib/sub.h>
+#include <mLib/trace.h>
+#include <mLib/tv.h>
+
+#include "err.h"
+#include "txport.h"
+
+/*----- Global variables --------------------------------------------------*/
+
+#define TX_LIST 0
+#include "tx-socket.h"
+#include "tx-serial-unix.h"
+txport_ops *txlist = TX_LIST;
+
+const char *txname = 0;
+const char *txfile = 0;
+const char *txconf = 0;
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @newline@ --- *
+ *
+ * Arguments:  @char *s@ = pointer to line
+ *             @size_t len@ = length of line
+ *             @void *txv@ = pointer to transport context
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a line to the list.
+ */
+
+static void newline(char *s, size_t len, void *txv)
+{
+  txport *tx = txv;
+  txline *l;
+
+  if (!s)
+    return;
+  l = CREATE(txline);
+  l->s = xmalloc(len + 1);
+  memcpy(l->s, s, len + 1);
+  l->len = len;
+  l->tx = tx;
+  l->next = 0;
+  l->prev = tx->ll_tail;
+  if (tx->ll_tail)
+    tx->ll_tail->next = l;
+  else
+    tx->ll = l;
+  tx->ll_tail = l;
+}
+
+/* --- @tx_create@ --- *
+ *
+ * Arguments:  @const char *name@ = name of transport to instantiate
+ *             @const char *file@ = filename for transport
+ *             @const char *config@ = config string
+ *
+ * Returns:    A pointer to the transport context, or null on error.
+ *
+ * Use:                Creates a new transport.
+ */
+
+txport *tx_create(const char *name, const char *file, const char *config)
+{
+  txport_ops *o;
+  txport *tx;
+  pthread_attr_t ta;
+  dstr d = DSTR_INIT;
+  size_t len;
+  int e;
+
+  /* --- Look up the transport by name --- */
+
+  if (!name) {
+    o = txlist;
+    goto found;
+  }
+  len = strlen(name);
+  for (o = txlist; o; o = o->next) {
+    if (strncmp(name, o->name, len) == 0)
+      goto found;
+  }
+  err_report(ERR_TXPORT, ERRTX_BADTX, 0, "unknown transport `%s'", name);
+  return (0);
+
+  /* --- Set up the transport block --- */
+
+found:
+  if (!file) {
+    const struct txfile *fv;
+    for (fv = o->fv; fv->env || fv->name; fv++) {
+      if (fv->env && (file = getenv(fv->env)) == 0)
+       continue;
+      DRESET(&d);
+      if (file)
+       DPUTS(&d, file);
+      if (file && fv->name)
+       DPUTC(&d, '/');
+      if (fv->name)
+       DPUTS(&d, fv->name);
+      break;
+    }
+    file = d.buf;
+  }
+  if (!config)
+    config = o->config;
+  if ((tx = o->create(file, config)) == 0)
+    goto fail_0;
+  tx->ops = o;
+  DA_CREATE(&tx->buf);
+  tx->ll = 0;
+  tx->ll_tail = 0;
+  if ((e = pthread_mutex_init(&tx->mx, 0)) != 0) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, e,
+              "mutex creation failed: %s", strerror(e));
+    goto fail_1;
+  }
+  if ((e = pthread_cond_init(&tx->cv, 0)) != 0) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, e,
+              "condvar creation failed: %s", strerror(e));
+    goto fail_2;
+  }
+  if ((e = pthread_attr_init(&ta)) != 0) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, e,
+              "thread attribute creation failed: %s", strerror(e));
+    goto fail_3;
+  } 
+  if ((e = pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED)) ||
+      (e = pthread_create(&tx->tid, &ta, tx->ops->fetch, tx)) != 0) {
+    err_report(ERR_TXPORT, ERRTX_CREATE, e,
+              "thread creation failed: %s", strerror(e));
+    goto fail_4;
+  }
+  pthread_attr_destroy(&ta);
+  lbuf_init(&tx->lb, newline, tx);
+  tx->lb.delim = '\r';
+  tx->s = TX_READY;
+  DDESTROY(&d);
+  return (tx);
+
+  /* --- Something went wrong --- */
+
+fail_4:
+  pthread_attr_destroy(&ta);
+fail_3:
+  pthread_cond_destroy(&tx->cv);
+fail_2:
+  pthread_mutex_destroy(&tx->mx);
+fail_1:
+  tx->ops->destroy(tx);
+fail_0:
+  DDESTROY(&d);
+  return (0);
+}
+
+/* --- @tx_write@ --- *
+ *
+ * Arguments:  @txport *tx@ = pointer to transport context
+ *             @const void *p@ = pointer to buffer to write
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    Zero if OK, or @-1@ on error.
+ *
+ * Use:                Writes some data to a transport.
+ */
+
+int tx_write(txport *tx, const void *p, size_t sz)
+{
+  if (tx->ops->write(tx, p, sz) < 0) {
+    err_report(ERR_TXPORT, ERRTX_WRITE, errno,
+              "error writing to transport: %s", strerror(errno));
+    return (-1);
+  }
+  return (0);
+}
+
+/* --- @tx_printf@ --- *
+ *
+ * Arguments:  @txport *tx@ = pointer to transport context
+ *             @const char *p@ = pointer to string to write
+ *
+ * Returns:    The number of characters printed, or @EOF@ on error.
+ *
+ * Use:                Writes a textual message to a transport.
+ */
+
+int tx_vprintf(txport *tx, const char *p, va_list *ap)
+{
+  dstr d = DSTR_INIT;
+  int rc;
+
+  dstr_vputf(&d, p, *ap);
+  rc = d.len;
+  rc = tx_write(tx, d.buf, d.len);
+  DDESTROY(&d);
+  return (rc);
+}
+
+int tx_printf(txport *tx, const char *p, ...)
+{
+  va_list ap;
+  int rc;
+
+  va_start(ap, p);
+  rc = tx_vprintf(tx, p, &ap);
+  va_end(ap);
+  return (rc);
+}
+
+/* --- @tx_read@, @tx_readx@ --- *
+ *
+ * Arguments:  @txport *tx@ = pointer to transport context
+ *             @unsigned long t@ = time to wait for data (ms)
+ *             @int (*filter)(const char *s, void *p)@ = filtering function
+ *             @void *p@ = pointer argument for filter
+ *
+ * Returns:    A pointer to a line block, which must be freed using
+ *             @tx_freeline@.
+ *
+ * Use:                Fetches a line from the buffer.  Each line is passed to the
+ *             filter function in oldest-to-newest order; the filter
+ *             function returns nonzero to choose a line.  If no suitable
+ *             line is waiting in the raw buffer, the program blocks while
+ *             more data is fetched, until the time limit @t@ is exceeded,
+ *             in which case a null pointer is returned.  A null filter
+ *             function is equivalent to one which always selects its line.
+ */
+
+txline *tx_readx(txport *tx, unsigned long t,
+                int (*filter)(const char *s, void *p), void *p)
+{
+  txline *l, **ll = &tx->ll;
+  int e;
+  struct timeval now, tv;
+  struct timespec ts;
+  unsigned f = 0;
+
+#define f_lock 1u
+
+  /* --- Get the time to wait until --- */
+
+  if (t != FOREVER) {
+    gettimeofday(&now, 0);
+    tv_addl(&tv, &now, t / 1000, (t % 1000) * 1000);
+    ts.tv_sec = tv.tv_sec;
+    ts.tv_nsec = tv.tv_usec * 1000;
+  }
+
+  /* --- Check for a matching line --- */
+
+again:
+  for (; *ll; ll = &l->next) {
+    l = *ll;
+    if (!filter || filter(l->s, p))
+      goto done;
+  }
+  l = 0;
+
+  /* --- Lock the buffer --- *
+   *
+   * The following operations require a lock on the buffer, so we obtain that
+   * here.
+   */
+
+  if (!(f & f_lock)) {
+    if ((e = pthread_mutex_lock(&tx->mx)) != 0) {
+      err_report(ERR_TXPORT, ERRTX_READ, e,
+                "error locking mutex: %s", strerror(errno));
+      goto done;
+    }
+    f |= f_lock;
+  }
+
+  /* --- Push more stuff through the line buffer --- */
+
+check:
+  if (DA_LEN(&tx->buf)) {
+    trace_block(1u, "incoming data", DA(&tx->buf), DA_LEN(&tx->buf));
+    lbuf_snarf(&tx->lb, DA(&tx->buf), DA_LEN(&tx->buf));
+    DA_SHRINK(&tx->buf, DA_LEN(&tx->buf));
+    goto again;
+  }
+
+  /* --- If nothing else can arrive, give up --- */
+
+  if (tx->s == TX_CLOSE) {
+    lbuf_close(&tx->lb);
+    tx->s = TX_CLOSED;
+    goto again;
+  }
+  if (!t || tx->s == TX_CLOSED)
+    goto done;
+  gettimeofday(&now, 0);
+  if (TV_CMP(&now, >=, &tv))
+    goto done;
+
+  /* --- Wait for some more data to arrive --- */
+
+  if (t == FOREVER)
+    e = pthread_cond_wait(&tx->cv, &tx->mx);
+  else
+    e = pthread_cond_timedwait(&tx->cv, &tx->mx, &ts);
+  if (e && e != ETIMEDOUT && e != EINTR) {
+    err_report(ERR_TXPORT, ERRTX_READ, e,
+              "error waiting on condvar: %s", strerror(errno));
+    goto done;
+  }
+  goto check;
+
+  /* --- Everything is finished --- */
+
+done:
+  if (f & f_lock)
+    pthread_mutex_unlock(&tx->mx);
+  return (l);
+
+#undef f_lock
+}
+
+txline *tx_read(txport *tx, unsigned long t)
+{
+  return (tx_readx(tx, t, 0, 0));
+}
+
+/* --- @tx_freeline@ --- *
+ *
+ * Arguments:  @txline *l@ = pointer to line
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a line block.
+ */
+
+void tx_freeline(txline *l)
+{
+  txport *tx = l->tx;
+  if (l->next)
+    l->next->prev = l->prev;
+  else
+    tx->ll_tail = l->prev;
+  if (l->prev)
+    l->prev->next = l->next;
+  else
+    tx->ll = l->next;
+  xfree(l->s);
+  DESTROY(l);
+}
+
+/* --- @tx_destroy@ --- *
+ *
+ * Arguments:  @txport *tx@ = transport context
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a transport.
+ */
+
+void tx_destroy(txport *tx)
+{
+  txline *l, *ll;
+
+  if (tx->s == TX_READY) {
+    pthread_mutex_lock(&tx->mx);
+    if (tx->s == TX_READY)
+      pthread_cancel(tx->tid);
+    pthread_mutex_unlock(&tx->mx);
+  }
+  pthread_mutex_destroy(&tx->mx);
+  pthread_cond_destroy(&tx->cv);
+  DA_DESTROY(&tx->buf);
+  lbuf_destroy(&tx->lb);
+  for (l = tx->ll; l; l = ll) {
+    ll = l->next;
+    xfree(l->s);
+    DESTROY(l);
+  }
+  tx->ops->destroy(tx);    
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/txport.h b/txport.h
new file mode 100644 (file)
index 0000000..ad4f338
--- /dev/null
+++ b/txport.h
@@ -0,0 +1,223 @@
+/* -*-c-*-
+ *
+ * $Id: txport.h,v 1.1 2002/01/25 19:34:45 mdw Exp $
+ *
+ * Transport switch glue
+ *
+ * (c) 2001 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------* 
+ *
+ * This file is part of Jog: Programming for a jogging machine.
+ *
+ * Jog 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.
+ * 
+ * Jog 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 Jog; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Revision history --------------------------------------------------* 
+ *
+ * $Log: txport.h,v $
+ * Revision 1.1  2002/01/25 19:34:45  mdw
+ * Initial revision
+ *
+ */
+
+#ifndef TXPORTSW_H
+#define TXPORTSW_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#include <pthread.h>
+
+#include <mLib/darray.h>
+#include <mLib/lbuf.h>
+
+/*----- Data structures ---------------------------------------------------*/
+
+/* --- Vector of bytes --- */
+
+#ifndef UCHAR_V
+#  define UCHAR_V
+   DA_DECL(uchar_v, unsigned char);
+#endif
+
+/* --- Node for lines --- */
+
+typedef struct txline {
+  struct txline *next, *prev;          /* Next node in the list */
+  struct txport *tx;                   /* Owning transport */
+  char *s;                             /* Pointer to the text */
+  size_t len;                          /* Length of the string */
+} txline;
+
+/* --- A transport context --- *
+ *
+ * The only members for which there is contention are the state @s@ and the
+ * raw incoming buffer @buf@.  Other members may be accessed without locking
+ * the structure.  Thus, the thread messing about is essentially isolated to
+ * the data- fetching thread and the line buffering code.
+ */
+
+typedef struct txport {
+  struct txport_ops *ops;              /* Operations table */
+  pthread_t tid;                       /* Fetching thread id */
+  pthread_mutex_t mx;                  /* Lock for this structure */
+  pthread_cond_t cv;                   /* `New data has arrived' */
+  unsigned s;                          /* State of this transport */
+  uchar_v buf;                         /* Buffer of incoming data */
+  lbuf lb;                             /* Buffer for extracting lines */
+  txline *ll, *ll_tail;                        /* List of waiting lines, in order */
+} txport;
+
+enum {
+  TX_READY,                            /* More data may arrive */
+  TX_CLOSE,                            /* No more data will arrive */
+  TX_CLOSED                            /* Done the closure thing already */
+};
+
+/* --- Transport operations --- */
+
+struct txfile { const char *env; const char *name; };
+
+typedef struct txport_ops {
+  struct txport_ops *next;
+  const char *name;
+  const struct txfile *fv;
+  const char *config;
+  txport *(*create)(const char */*file*/, const char */*config*/);
+  void *(*fetch)(void */*txv*/);
+  ssize_t (*write)(txport */*tx*/, const void */*p*/, size_t /*sz*/);
+  void (*destroy)(txport */*tx*/);
+} txport_ops;
+
+/*----- Global variables --------------------------------------------------*/
+
+extern txport_ops *txlist;
+extern const char *txname;
+extern const char *txfile;
+extern const char *txconf;
+
+#define DIRVAR "JOGDIR"
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @tx_create@ --- *
+ *
+ * Arguments:  @const char *name@ = name of transport to instantiate
+ *             @const char *file@ = filename for transport
+ *             @const char *config@ = config string
+ *
+ * Returns:    A pointer to the transport context, or null on error.
+ *
+ * Use:                Creates a new transport.
+ */
+
+extern txport *tx_create(const char */*name*/, const char */*file*/,
+                        const char */*config*/);
+
+/* --- @tx_write@ --- *
+ *
+ * Arguments:  @txport *tx@ = pointer to transport context
+ *             @const void *p@ = pointer to buffer to write
+ *             @size_t sz@ = size of buffer
+ *
+ * Returns:    Zero if OK, or @-1@ on error.
+ *
+ * Use:                Writes some data to a transport.
+ */
+
+extern int tx_write(txport */*tx*/, const void */*p*/, size_t /*sz*/);
+
+/* --- @tx_printf@ --- *
+ *
+ * Arguments:  @txport *tx@ = pointer to transport context
+ *             @const char *p@ = pointer to string to write
+ *
+ * Returns:    The number of characters printed, or @EOF@ on error.
+ *
+ * Use:                Writes a textual message to a transport.
+ */
+
+extern int tx_vprintf(txport */*tx*/, const char */*p*/, va_list */*ap*/);
+
+extern int tx_printf(txport */*tx*/, const char */*p*/, ...);
+
+/* --- @tx_read@, @tx_readx@ --- *
+ *
+ * Arguments:  @txport *tx@ = pointer to transport context
+ *             @unsigned long t@ = time to wait for data (ms)
+ *             @int (*filter)(const char *s, void *p)@ = filtering function
+ *             @void *p@ = pointer argument for filter
+ *
+ * Returns:    A pointer to a line block, which must be freed using
+ *             @tx_freeline@.
+ *
+ * Use:                Fetches a line from the buffer.  Each line is passed to the
+ *             filter function in oldest-to-newest order; the filter
+ *             function returns nonzero to choose a line.  If no suitable
+ *             line is waiting in the raw buffer, the program blocks while
+ *             more data is fetched, until the time limit @t@ is exceeded,
+ *             in which case a null pointer is returned.  A null filter
+ *             function is equivalent to one which always selects its line.
+ */
+
+#define FOREVER (~0ul)
+
+extern txline *tx_readx(txport */*tx*/, unsigned long /*t*/,
+                       int (*/*filter*/)(const char */*s*/, void */*p*/),
+                       void */*p*/);
+
+extern txline *tx_read(txport */*tx*/, unsigned long /*t*/);
+
+/* --- @tx_freeline@ --- *
+ *
+ * Arguments:  @txline *l@ = pointer to line
+ *
+ * Returns:    ---
+ *
+ * Use:                Frees a line block.
+ */
+
+extern void tx_freeline(txline */*l*/);
+
+/* --- @tx_destroy@ --- *
+ *
+ * Arguments:  @txport *tx@ = transport context
+ *
+ * Returns:    ---
+ *
+ * Use:                Destroys a transport.
+ */
+
+extern void tx_destroy(txport */*tx*/);
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif