--- /dev/null
+Makefile.in
+aclocal.m4
+config.h.in
+configure
+stamp-h.in
+build
--- /dev/null
+COPYING
+install-sh
+mkinstalldirs
+missing
--- /dev/null
+;;; -*-emacs-lisp-*-
+
+(setq skel-alist
+ (append
+ '((author . "Mark Wooding")
+ (full-title . "Jog: Programming for a jogging machine")
+ (program . "Jog"))
+ skel-alist))
--- /dev/null
+## -*-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 ---------------------------------------------------
--- /dev/null
+/* -*-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
--- /dev/null
+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 -------------------------------------------------
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+#! /bin/sh
+
+set -e
+mklinks
+mkaclocal
+autoheader
+autoconf
+automake
+mkdir build
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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
--- /dev/null
+/* -*-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 -------------------------------------------------*/
--- /dev/null
+/* -*-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