Initial revision
[jog] / rxglue.c
CommitLineData
2ec1e693 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
69static 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
101static 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
111static void rxs_putd(RXSTRING *x, dstr *d) { RXS_PUTD(x, d); }
112
113static 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
121static 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
149static 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
162static 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
222static 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
244static 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
273static 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
289static 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
305static 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
327static 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
367static 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
388static 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
417static 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
441static 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
463static 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
481struct rxfntab { char *name; RexxFunctionHandler *fn; };
482
483static 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
506void 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
535int 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
591int 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
623fail_1:
624 dstr_destroy(&d);
625 fclose(fp);
626fail_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 -------------------------------------------------*/