chiark / gitweb /
Initial revision
[jog] / rxglue.c
1 /* -*-c-*-
2  *
3  * $Id: rxglue.c,v 1.1 2002/01/25 19:34:45 mdw Exp $
4  *
5  * REXX glue for C core functionality
6  *
7  * (c) 2001 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------* 
11  *
12  * This file is part of Jog: Programming for a jogging machine.
13  *
14  * Jog is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  * 
19  * Jog is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * 
24  * You should have received a copy of the GNU General Public License
25  * along with Jog; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Revision history --------------------------------------------------* 
30  *
31  * $Log: rxglue.c,v $
32  * Revision 1.1  2002/01/25 19:34:45  mdw
33  * Initial revision
34  *
35  */
36
37 /*----- Header files ------------------------------------------------------*/
38
39 #ifdef HAVE_CONFIG_H
40 #  include "config.h"
41 #endif
42
43 #include <ctype.h>
44 #include <errno.h>
45 #include <limits.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <time.h>
51
52 #include <sys/types.h>
53 #include <sys/time.h>
54 #include <unistd.h>
55
56 #define INCL_RXFUNC
57 #define RX_STRONGTYPING
58 #include <rexxsaa.h>
59
60 #include <mLib/alloc.h>
61 #include <mLib/dstr.h>
62
63 #include "err.h"
64 #include "rxglue.h"
65 #include "txport.h"
66
67 /*----- Static variables --------------------------------------------------*/
68
69 static txport *tx = 0;
70
71 /*----- Conversion functions ----------------------------------------------*/
72
73 /* --- @rxs_putm@ --- *
74  *
75  * Arguments:   @RXSTRING *x@ = pointer to REXX string structure
76  *              For @rxs_putm@:
77  *                @const void *p@ = pointer to data block
78  *                @size_t sz@ = size of data
79  *              For @rxs_putd@:
80  *                @const dstr *d@ = pointer to source string
81  *              For @rxs_putf@ and @rxs_vputf@:
82  *                @const char *m@ = message format string
83  *
84  * Returns:     ---
85  *
86  * Use:         Stashes some text in an @RXSTRING@, overwriting whatever was
87  *              there before.  We assume that the previous contents don't
88  *              require freeing.
89  */
90
91 #define RXS_PUTM(x, p, sz) do {                                         \
92   RXSTRING *_x = (x);                                                   \
93   const void *_p = (p);                                                 \
94   size_t _sz = (sz);                                                    \
95   if (!_x->strptr || _x->strlength < _sz)                               \
96     _x->strptr = xmalloc(_sz);                                          \
97   memcpy(_x->strptr, _p, _sz);                                          \
98   _x->strlength = _sz;                                                  \
99 } while (0)
100
101 static void rxs_putm(RXSTRING *x, const void *p, size_t sz)
102 {
103   RXS_PUTM(x, p, sz);
104 }
105
106 #define RXS_PUTD(x, d) do {                                             \
107   dstr *_d = (d);                                                       \
108   RXS_PUTM((x), _d->buf, _d->len);                                      \
109 } while (0)
110
111 static void rxs_putd(RXSTRING *x, dstr *d) { RXS_PUTD(x, d); }
112
113 static void rxs_vputf(RXSTRING *x, const char *m, va_list *ap)
114 {
115   dstr d = DSTR_INIT;
116   dstr_vputf(&d, m, ap);
117   RXS_PUTD(x, &d);
118   DDESTROY(&d);
119 }
120
121 static void rxs_putf(RXSTRING *x, const char *m, ...)
122 {
123   va_list ap;
124   dstr d = DSTR_INIT;
125   va_start(ap, m);
126   dstr_vputf(&d, m, &ap);
127   RXS_PUTD(x, &d);
128   va_end(ap);
129   DDESTROY(&d);
130 }
131
132 /* --- @rxs_get@ --- *
133  *
134  * Arguments:   @const RXSTRING *x@ = pointer to a REXX string
135  *              @dstr *d@ = where to put it
136  *
137  * Returns:     ---
138  *
139  * Use:         Pulls a REXX string out and puts it in a dynamic string.
140  */
141
142 #define RXS_GET(x, d) do {                                              \
143   const RXSTRING *_x = (x);                                             \
144   dstr *_dd = (d);                                                      \
145   DPUTM(_dd, _x->strptr, _x->strlength);                                \
146   DPUTZ(_dd);                                                           \
147 } while (0)
148
149 static void rxs_get(const RXSTRING *x, dstr *d) { RXS_GET(x, d); }
150
151 /* --- @rxs_tol@ --- *
152  *
153  * Arguments:   @const RXSTRING *x@ = pointer to a REXX string
154  *              @long *ii@ = where to put the answer
155  *
156  * Returns:     Zero on success, or nonzero on error.
157  *
158  * Use:         Fetches an integer from a REXX string.  This doesn't cope
159  *              with multiprecision integers or similar silliness.
160  */
161
162 static int rxs_tol(const RXSTRING *x, long *ii)
163 {
164   long i = 0;
165   const char *p = x->strptr, *l = p + x->strlength;
166   unsigned f = 0;
167
168 #define f_neg 1u
169 #define f_ok 2u
170
171 #define MINR (LONG_MIN/10)
172 #define MIND (LONG_MIN%10)
173
174   while (p < l && isspace((unsigned char)*p))
175     p++;
176   if (p >= l)
177     return (-1);
178   if (*p == '+')
179     p++;
180   else if (*p == '-') {
181     f |= f_neg;
182     p++;
183   }
184   while (p < l && isspace((unsigned char)*p))
185     p++;
186   while (p < l && isdigit((unsigned char)*p)) {
187     int j = *p++ - '0';
188     if (i < MINR || (i == MINR && -j < MIND))
189       return (-1);
190     i = (i * 10) - j;
191     f |= f_ok;
192   }
193   while (p < l && isspace((unsigned char)*p))
194     p++;
195   if (p < l || !(f & f_ok))
196     return (-1);
197   if (!(f & f_neg)) {
198     if (i < -LONG_MAX)
199       return (-1);
200     i = -i;
201   }
202   *ii = i;
203   return (0);
204
205 #undef MINR
206 #undef MIND
207
208 #undef f_neg
209 #undef f_ok
210 }
211
212 /* --- @rxs_block@ --- *
213  *
214  * Arguments:   @const RXSTRING *x@ = a REXX string
215  *              @unsigned long *t@ = where to put the block spec
216  *
217  * Returns:     Zero if OK, nonzero on error.
218  *
219  * Use:         Picks out a blockingness spec.
220  */
221
222 static int rxs_block(const RXSTRING *x, unsigned long *t)
223 {
224   long i;
225
226   if (!x->strptr || x->strlength < 1)
227     return (-1);
228   switch (x->strptr[0]) {
229     case 'f':
230     case 'F':
231       *t = FOREVER;
232       break;
233     default:
234       if (rxs_tol(x, &i) || i < 0)
235         return (-1);
236       *t = i;
237       break;
238   }
239   return (0);
240 }
241
242 /*----- REXX functions ----------------------------------------------------*/
243
244 static APIRET APIENTRY rxfn_test(unsigned char *fn, ULONG ac, RXSTRING *av,
245                                  char *sn, RXSTRING *r)
246 {
247   ULONG i;
248
249   printf("test entry\n"
250          "  fn = `%s'\n", fn);
251   for (i = 0; i < ac; i++) {
252     long l;
253
254     printf("  av[%lu] = `", i);
255     fwrite(av[i].strptr, 1, av[i].strlength, stdout);
256     if (rxs_tol(&av[i], &l))
257       printf("'\n");
258     else
259       printf("' (%ld)\n", l);
260   }
261   printf("tx = `%s'; f = `%s'; c = `%s'.", txname, txfile, txconf);
262   rxs_putf(r, "function `%s' completed ok", fn);
263   return (0);
264 }
265
266 /* --- @txname()@ ---
267  *
268  * Arguments:   ---
269  *
270  * Returns:     The currently-selected transport name.
271  */
272
273 static APIRET APIENTRY rxfn_txname(unsigned char *fn, ULONG ac, RXSTRING *av,
274                                    char *sn, RXSTRING *r)
275 {
276   if (ac)
277     return (-1);
278   rxs_putf(r, "%s", txname);
279   return (0);
280 }
281
282 /* --- @txfile()@ ---
283  *
284  * Arguments:   ---
285  *
286  * Returns:     The currently-selected transport filename.
287  */
288
289 static APIRET APIENTRY rxfn_txfile(unsigned char *fn, ULONG ac, RXSTRING *av,
290                                    char *sn, RXSTRING *r)
291 {
292   if (ac)
293     return (-1);
294   rxs_putf(r, "%s", txfile ? txfile : "");
295   return (0);
296 }
297
298 /* --- @txfile()@ ---
299  *
300  * Arguments:   ---
301  *
302  * Returns:     The currently-selected transport configuration string.
303  */
304
305 static APIRET APIENTRY rxfn_txconf(unsigned char *fn, ULONG ac, RXSTRING *av,
306                                    char *sn, RXSTRING *r)
307 {
308   if (ac)
309     return (-1);
310   rxs_putf(r, "%s", txconf ? txconf : "");
311   return (0);
312 }
313   
314 /* --- @txinit([NAME], [FILE], [CONFIG])@ ---
315  *
316  * Arguments:   @NAME@ = transport name to select
317  *              @FILE@ = transport filename
318  *              @CONFIG@ = transport configuration string
319  *
320  * Returns:     ---
321  *
322  * Use:         Initializes a transport using the given settings.  Omitted
323  *              arguments are filled in from the command line, or internal
324  *              defaults.
325  */
326
327 static APIRET APIENTRY rxfn_txinit(unsigned char *fn, ULONG ac, RXSTRING *av,
328                                    char *sn, RXSTRING *r)
329 {
330   const char *n = txname, *f = txfile, *c = txconf;
331   dstr dn = DSTR_INIT, df = DSTR_INIT, dc = DSTR_INIT;
332
333   if (tx)
334     return (-1);
335   if (ac > 3)
336     return (-1);
337   if (ac >= 1 && av[0].strptr) {
338     rxs_get(&av[0], &dn);
339     n = dn.buf;
340   }
341   if (ac >= 2 && av[1].strptr) {
342     rxs_get(&av[1], &df);
343     f = df.buf;
344   }
345   if (ac >= 3 && av[2].strptr) {
346     rxs_get(&av[2], &dn);
347     c = dc.buf;
348   }
349   tx = tx_create(n, f, c);
350   dstr_destroy(&dn);
351   dstr_destroy(&df);
352   dstr_destroy(&dc);
353   if (!tx)
354     return (-1);
355   return (0);
356 }
357
358 /* --- @txsend(STRING)@ --- *
359  *
360  * Arguments:   @STRING@ = string to send
361  *
362  * Returns:     ---
363  *
364  * Use:         Sends a string (exactly as written) to the transport.
365  */
366
367 static APIRET APIENTRY rxfn_txsend(unsigned char *fn, ULONG ac, RXSTRING *av,
368                                    char *sn, RXSTRING *r)
369 {
370   if (ac != 1 || !tx || !av[0].strptr)
371     return (-1);
372   tx_write(tx, av[0].strptr, av[0].strlength);
373   return (0);
374 }
375
376 /* --- @txrecv([MILLIS])@ --- *
377  *
378  * Arguments:   @MILLIS@ = how long (in milliseconds) to wait, or `forever'
379  *
380  * Returns:     The string read (may be null if nothing available -- sorry).
381  *
382  * Use:         Reads the next line from the transport.  If @MILLIS@ is an
383  *              integer, then give up after that many milliseconds of
384  *              waiting; if it is `forever' (or anything beginning with an
385  *              `f') then don't give up.  The default is to wait forever.
386  */
387
388 static APIRET APIENTRY rxfn_txrecv(unsigned char *fn, ULONG ac, RXSTRING *av,
389                                    char *sn, RXSTRING *r)
390 {
391   txline *l;
392   unsigned long t = FOREVER;
393
394   if (ac > 1 || !tx)
395     return (-1);
396   if (ac >= 1 && rxs_block(&av[0], &t))
397     return (-1);
398
399   l = tx_read(tx, t);
400   if (!l)
401     r->strlength = 0;
402   else {
403     rxs_putm(r, l->s, l->len);
404     tx_freeline(l);
405   }
406   return (0);
407 }
408
409 /* --- @TXEOF()@ --- *
410  *
411  * Arguments:   ---
412  *
413  * Returns:     True if end-of-file has been seen on the transport, otherwise
414  *              false.
415  */
416
417 static APIRET APIENTRY rxfn_txeof(unsigned char *fn, ULONG ac,
418                                   RXSTRING *av, char *sn, RXSTRING *r)
419 {
420   if (ac || !tx)
421     return (-1);
422   rxs_putf(r, "%d", tx->s == TX_CLOSED && !tx->ll);
423   return (0);
424 }
425
426 /* --- @txready([MILLIS])@ --- *
427  *
428  * Arguments:   @MILLIS@ = how long (in milliseconds) to wait, or `forever'
429  *
430  * Returns:     True if a line is ready, otherwise false.
431  *
432  * Use:         Returns whether the transport is ready for reading.  If
433  *              @MILLIS@ is an integer, then wait for at most that many
434  *              milliseconds before returning.  If @MILLIS@ is `forever' (or
435  *              anything beginning with `f') then wait forever for
436  *              readiness.  This isn't useless: it can trip the end-of-file
437  *              detector.  If @MILLIS@ is omitted, return immediately (as if
438  *              0 had been specified).
439  */
440
441 static APIRET APIENTRY rxfn_txready(unsigned char *fn, ULONG ac,
442                                     RXSTRING *av, char *sn, RXSTRING *r)
443 {
444   unsigned long t = 0;
445
446   if (ac > 1 || !tx)
447     return (-1);
448   if (ac >= 1 && rxs_block(&av[0], &t))
449     return (-1);
450   rxs_putf(r, "%d", !!tx_read(tx, t));
451   return (0);
452 }
453
454 /* --- @MILLIWAIT(MILLIS)@ --- *
455  *
456  * Arguments:   @MILLIS@ = how long (in milliseconds) to wait
457  *
458  * Returns:     ---
459  *
460  * Use:         Waits for @MILLIS@ milliseconds.  Always.
461  */
462
463 static APIRET APIENTRY rxfn_milliwait(unsigned char *fn, ULONG ac,
464                                       RXSTRING *av, char *sn, RXSTRING *r)
465 {
466   long l;
467   struct timeval tv;
468
469   if (ac != 1 || !av[0].strptr)
470     return (-1);
471   if (rxs_tol(&av[0], &l) || l < 0)
472     return (-1);
473   tv.tv_sec = l / 1000;
474   tv.tv_usec = (l % 1000) * 1000;
475   select(0, 0, 0, 0, &tv);
476   return (0);
477 }
478
479 /*----- Initialization ----------------------------------------------------*/
480
481 struct rxfntab { char *name; RexxFunctionHandler *fn; };
482
483 static const struct rxfntab rxfntab[] = {
484   { "test",     rxfn_test },
485   { "txname",   rxfn_txname },
486   { "txfile",   rxfn_txfile },
487   { "txconf",   rxfn_txconf },
488   { "txinit",   rxfn_txinit },
489   { "txsend",   rxfn_txsend },
490   { "txrecv",   rxfn_txrecv },
491   { "txeof",    rxfn_txeof },
492   { "txready",  rxfn_txready },
493   { "milliwait", rxfn_milliwait },
494   { 0,          0 }
495 };
496
497 /* --- @rx_init@ --- *
498  *
499  * Arguments:   ---
500  *
501  * Returns:     ---
502  *
503  * Use:         Initializes the REXX external functions.
504  */
505
506 void rx_init(void)
507 {
508   const struct rxfntab *f;
509   int rc;
510
511   for (f = rxfntab; f->fn; f++) {
512     if ((rc = RexxRegisterFunctionExe(f->name, f->fn)) != 0) {
513       err_report(ERR_RXGLUE, ERRRX_INIT, rc,
514                  "couldn't register function `%s' (code %d)", f->name, rc);
515       abort();
516     }
517   }
518 }
519
520 /*----- Running REXX programs ---------------------------------------------*/
521
522 /* --- @rx_run@ --- *
523  *
524  * Arguments:   @const char *name@ = pointer to filename (or null)
525  *              @const void *p@ = pointer to program text
526  *              @size_t sz@ = size of program text
527  *              @int ac@ = number of arguments
528  *              @const char *const *av@ = vector of command-line arguments
529  *
530  * Returns:     Exit code from program.
531  *
532  * Use:         Runs a REXX script from memory.
533  */
534
535 int rx_run(const char *name, const void *p, size_t sz,
536            int ac, const char *const *av)
537 {
538   RXSTRING prog[2];
539   RXSTRING *argv;
540   RXSTRING res;
541   dstr d = DSTR_INIT;
542   short badrc;
543   int rc;
544   int i;
545
546   /* --- Set things up --- */
547
548   if (!name)
549     name = "incore";
550   MAKERXSTRING(prog[0], (void *)p, sz);
551   MAKERXSTRING(prog[1], 0, 0);
552   argv = xmalloc(ac * sizeof(*argv));
553   for (i = 0; i < ac; i++)
554     MAKERXSTRING(argv[i], (char *)av[i], strlen(av[i]));
555
556   /* --- Run the script --- */
557
558   MAKERXSTRING(res, 0, 0);
559   rc = RexxStart(ac, argv, (char *)name, prog,
560                  "CMD", RXCOMMAND, 0, &badrc, &res);
561   if (rc) {
562     free(RXSTRPTR(res));
563     if (rc < 0)
564       err_report(ERR_RXERR, 0, -rc, "rexx error from script `%s'", name);
565     else
566       err_report(ERR_RXGLUE, ERRRX_INTERP, rc, "intepreter internal error");
567     return (-1);
568   }
569
570   /* --- Pick apart the results --- */
571
572   dstr_putm(&d, RXSTRPTR(res), RXSTRLEN(res));
573   free(RXSTRPTR(res));
574   dstr_putz(&d);
575   rc = atoi(d.buf);
576   dstr_destroy(&d);
577   return (rc);
578 }
579
580 /* --- @rx_runfile@ --- *
581  *
582  * Arguments:   @const char *name@ = pointer to filename
583  *              @int ac@ = number of command-line arguments
584  *              @const char *const *av@ = vector of command-line arguments
585  *
586  * Returns:     Exit code from program.
587  *
588  * Use:         Runs a REXX script from a file, given its name.
589  */
590
591 int rx_runfile(const char *name, int ac, const char *const *av)
592 {
593   FILE *fp;
594   dstr d = DSTR_INIT;
595   char buf[BUFSIZ];
596   size_t n;
597   int rc;
598
599   /* --- Read the file into memory --- *
600    *
601    * This way avoids any crapness in the REXX implementation and means we can
602    * report errors in a more sensible way.
603    */
604
605   if ((fp = fopen(name, "r")) == 0)
606     goto fail_0;
607   do {
608     n = fread(buf, 1, sizeof(buf), fp);
609     DPUTM(&d, buf, n);
610   } while (n == sizeof(buf));
611   if (ferror(fp))
612     goto fail_1;
613   fclose(fp);
614
615   /* --- Now do the from-memory thing --- */
616
617   rc = rx_run(name, d.buf, d.len, ac, av);
618   dstr_destroy(&d);
619   return (rc);
620
621   /* --- Tidy up on errors --- */
622
623 fail_1:
624   dstr_destroy(&d);
625   fclose(fp);
626 fail_0:
627   err_report(ERR_RXGLUE, ERRRX_SCRIPTREAD, errno,
628              "couldn't read script `%s': %s", name, strerror(errno));
629   return (-1);
630 }
631
632 /*----- That's all, folks -------------------------------------------------*/