chiark / gitweb /
More ignored files.
[jog] / txport.c
1 /* -*-c-*-
2  *
3  * $Id: txport.c,v 1.2 2002/01/30 09:27:10 mdw Exp $
4  *
5  * Transport switch glue
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: txport.c,v $
32  * Revision 1.2  2002/01/30 09:27:10  mdw
33  * Transport configuration overhaul.  Configuration string is now a list of
34  * `;'-separated `key=value' strings, which can be handled either by the
35  * core or the transport module.  The newline character(s) are a core
36  * parameter now.
37  *
38  * Revision 1.1  2002/01/25 19:34:45  mdw
39  * Initial revision
40  *
41  */
42
43 /*----- Header files ------------------------------------------------------*/
44
45 #ifdef HAVE_CONFIG_H
46 #  include "config.h"
47 #endif
48
49 #include <ctype.h>
50 #include <errno.h>
51 #include <stdio.h>
52 #include <stdarg.h>
53
54 #include <sys/types.h>
55 #include <sys/time.h>
56 #include <unistd.h>
57 #include <pthread.h>
58
59 #include <mLib/darray.h>
60 #include <mLib/dstr.h>
61 #include <mLib/lbuf.h>
62 #include <mLib/sub.h>
63 #include <mLib/tv.h>
64
65 #include "err.h"
66 #include "jog.h"
67 #include "txport.h"
68
69 /*----- Global variables --------------------------------------------------*/
70
71 #define TX_LIST 0
72 #include "tx-socket.h"
73 #include "tx-serial-unix.h"
74 txport_ops *txlist = TX_LIST;
75
76 const char *txname = 0;
77 const char *txfile = 0;
78 const char *txconf = 0;
79
80 /*----- Main code ---------------------------------------------------------*/
81
82 /* --- @newline@ --- *
83  *
84  * Arguments:   @char *s@ = pointer to line
85  *              @size_t len@ = length of line
86  *              @void *txv@ = pointer to transport context
87  *
88  * Returns:     ---
89  *
90  * Use:         Adds a line to the list.
91  */
92
93 static void newline(char *s, size_t len, void *txv)
94 {
95   txport *tx = txv;
96   txline *l;
97
98   if (!s)
99     return;
100   T( trace(T_TX, "tx: completed line: `%s'", s); )
101   l = CREATE(txline);
102   l->s = xmalloc(len + 1);
103   memcpy(l->s, s, len + 1);
104   l->len = len;
105   l->tx = tx;
106   l->next = 0;
107   l->prev = tx->ll_tail;
108   if (tx->ll_tail)
109     tx->ll_tail->next = l;
110   else
111     tx->ll = l;
112   tx->ll_tail = l;
113 }
114
115 /* --- @tx_configure@ --- *
116  *
117  * Arguments:   @txport *tx@ = pointer to transport block
118  *              @const char *config@ = config string
119  *
120  * Returns:     Zero if OK, nonzero on errors.
121  *
122  * Use:         Applies a configuration string to a transport.
123  */
124
125 int tx_configure(txport *tx, const char *config)
126 {
127   char *c;
128   char *k, *v;
129   int rc = -1;
130
131   if (!config)
132     return (0);
133   c = xstrdup(config);
134   for (k = strtok(c, ";"); k; k = strtok(0, ";")) {
135     if ((v = strchr(k, '=')) != 0)
136       *v++ = 0;
137     if (strcmp(k, "nl") == 0 || strcmp(k, "newline") == 0) {
138       int d;
139       if (!v)
140         d = LBUF_CRLF;
141       else if (strcmp(v, "none") == 0)
142         d = 0;
143       else if (strcmp(v, "crlf") == 0)
144         d = LBUF_CRLF;
145       else if (strcmp(v, "crlf-strict") == 0 ||
146                strcmp(v, "strict-crlf") == 0)
147         d = LBUF_STRICTCRLF;
148       else if (strcmp(v, "cr") == 0)
149         d = '\r';
150       else if (strcmp(v, "lf") == 0)
151         d = '\n';
152       else if (v[0] == '\\') switch (v[1]) {
153         case 0: d = '\\'; break;
154         case 'a': d = 0x07; goto d_single;
155         case 'b': d = 0x08; goto d_single;
156         case 'f': d = 0x0c; goto d_single;
157         case 'n': d = 0x0a; goto d_single;
158         case 'r': d = 0x0d; goto d_single;
159         case 't': d = 0x09; goto d_single;
160         case 'v': d = 0x0b; goto d_single;
161         case 'e': d = 0x1b; goto d_single;
162         case 'x':
163           if (!isxdigit((unsigned char)v[2]) ||
164               (d = strtoul(v + 2, &v, 16) || *v)) {
165             err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
166                        "bad hex escape `%s' in `newline' config", v);
167             goto err;
168           }
169           break;
170         default:
171           if (isdigit((unsigned char)v[0])) {
172             d = strtoul(v + 1, &v, 8);
173             if (*v) {
174               err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
175                          "bad octal escape `%s' in `newline' config", v);
176               goto err;
177             }
178           }
179           d = v[1];
180       d_single:
181           if (v[2]) {
182             err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
183                        "unknown escape `%s' in `newline' config", v);
184             goto err;
185           }
186           break;
187       } else if (v[1] == 0)
188         d = v[0];
189       else {
190         err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
191                    "unknown delimiter `%s' in `newline' config", v);
192         goto err;
193       }
194       tx->lb.delim = d;
195     } else {
196       int e = 0;
197       if (tx->ops->configure)
198         e = tx->ops->configure(tx, k, v);
199       if (!e) {
200         err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
201                    "unrecognized configuration keyword `%s'", k);
202       }
203       if (e <= 0)
204         goto err;
205     }
206   }
207
208   rc = 0;
209 err:
210   xfree(c);
211   return (rc);
212 }
213
214 /* --- @tx_create@ --- *
215  *
216  * Arguments:   @const char *name@ = name of transport to instantiate
217  *              @const char *file@ = filename for transport
218  *              @const char *config@ = config string
219  *
220  * Returns:     A pointer to the transport context, or null on error.
221  *
222  * Use:         Creates a new transport.
223  */
224
225 txport *tx_create(const char *name, const char *file, const char *config)
226 {
227   txport_ops *o;
228   txport *tx;
229   pthread_attr_t ta;
230   dstr d = DSTR_INIT;
231   size_t len;
232   int e;
233
234   /* --- Look up the transport by name --- */
235
236   if (!name) {
237     o = txlist;
238     goto found;
239   }
240   len = strlen(name);
241   for (o = txlist; o; o = o->next) {
242     if (strncmp(name, o->name, len) == 0)
243       goto found;
244   }
245   err_report(ERR_TXPORT, ERRTX_BADTX, 0, "unknown transport `%s'", name);
246   return (0);
247
248   /* --- Set up the transport block --- */
249
250 found:
251   if (!file) {
252     const struct txfile *fv;
253     for (fv = o->fv; fv->env || fv->name; fv++) {
254       if (fv->env && (file = getenv(fv->env)) == 0)
255         continue;
256       DRESET(&d);
257       if (file)
258         DPUTS(&d, file);
259       if (file && fv->name)
260         DPUTC(&d, '/');
261       if (fv->name)
262         DPUTS(&d, fv->name);
263       break;
264     }
265     file = d.buf;
266   }
267   if (!config)
268     config = o->config;
269   if ((tx = o->create(file)) == 0)
270     goto fail_0;
271   tx->ops = o;
272   lbuf_init(&tx->lb, newline, tx);
273   if (tx_configure(tx, config))
274     goto fail_1;
275   tx->ll = 0;
276   tx->ll_tail = 0;
277   if ((e = pthread_mutex_init(&tx->mx, 0)) != 0) {
278     err_report(ERR_TXPORT, ERRTX_CREATE, e,
279                "mutex creation failed: %s", strerror(e));
280     goto fail_1;
281   }
282   if ((e = pthread_cond_init(&tx->cv, 0)) != 0) {
283     err_report(ERR_TXPORT, ERRTX_CREATE, e,
284                "condvar creation failed: %s", strerror(e));
285     goto fail_2;
286   }
287   if ((e = pthread_attr_init(&ta)) != 0) {
288     err_report(ERR_TXPORT, ERRTX_CREATE, e,
289                "thread attribute creation failed: %s", strerror(e));
290     goto fail_3;
291   } 
292   if ((e = pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED)) ||
293       (e = pthread_create(&tx->tid, &ta, tx->ops->fetch, tx)) != 0) {
294     err_report(ERR_TXPORT, ERRTX_CREATE, e,
295                "thread creation failed: %s", strerror(e));
296     goto fail_4;
297   }
298   pthread_attr_destroy(&ta);
299   DA_CREATE(&tx->buf);
300   tx->s = TX_READY;
301   DDESTROY(&d);
302   return (tx);
303
304   /* --- Something went wrong --- */
305
306 fail_4:
307   pthread_attr_destroy(&ta);
308 fail_3:
309   pthread_cond_destroy(&tx->cv);
310 fail_2:
311   pthread_mutex_destroy(&tx->mx);
312 fail_1:
313   lbuf_destroy(&tx->lb);
314   tx->ops->destroy(tx);
315 fail_0:
316   DDESTROY(&d);
317   return (0);
318 }
319
320 /* --- @tx_write@ --- *
321  *
322  * Arguments:   @txport *tx@ = pointer to transport context
323  *              @const void *p@ = pointer to buffer to write
324  *              @size_t sz@ = size of buffer
325  *
326  * Returns:     Zero if OK, or @-1@ on error.
327  *
328  * Use:         Writes some data to a transport.
329  */
330
331 int tx_write(txport *tx, const void *p, size_t sz)
332 {
333   T( trace_block(T_TX, "tx: outgoing data", p, sz); )
334   if (tx->ops->write(tx, p, sz) < 0) {
335     err_report(ERR_TXPORT, ERRTX_WRITE, errno,
336                "error writing to transport: %s", strerror(errno));
337     return (-1);
338   }
339   return (0);
340 }
341
342 /* --- @tx_printf@ --- *
343  *
344  * Arguments:   @txport *tx@ = pointer to transport context
345  *              @const char *p@ = pointer to string to write
346  *
347  * Returns:     The number of characters printed, or @EOF@ on error.
348  *
349  * Use:         Writes a textual message to a transport.
350  */
351
352 int tx_vprintf(txport *tx, const char *p, va_list *ap)
353 {
354   dstr d = DSTR_INIT;
355   int rc;
356
357   dstr_vputf(&d, p, *ap);
358   rc = d.len;
359   rc = tx_write(tx, d.buf, d.len);
360   DDESTROY(&d);
361   return (rc);
362 }
363
364 int tx_printf(txport *tx, const char *p, ...)
365 {
366   va_list ap;
367   int rc;
368
369   va_start(ap, p);
370   rc = tx_vprintf(tx, p, &ap);
371   va_end(ap);
372   return (rc);
373 }
374
375 /* --- @tx_newline@ --- *
376  *
377  * Arguments:   @txport *tx@ = pointer to transport context
378  *
379  * Returns:     Zero if OK, nonzero on error.
380  *
381  * Use:         Writes a newline (record boundary) to the output.
382  */
383
384 int tx_newline(txport *tx)
385 {
386   static const char crlf[2] = { 0x0d, 0x0a };
387   char c;
388   const char *p;
389   size_t sz;
390
391   switch (tx->lb.delim) {
392     case LBUF_CRLF:
393     case LBUF_STRICTCRLF:
394       p = crlf;
395       sz = 2;
396       break;
397     case 0:
398       return (0);
399     default:
400       c = tx->lb.delim;
401       p = &c;
402       sz = 1;
403       break;
404   }
405   return (tx_write(tx, p, sz));
406 }
407
408 /* --- @tx_read@, @tx_readx@ --- *
409  *
410  * Arguments:   @txport *tx@ = pointer to transport context
411  *              @unsigned long t@ = time to wait for data (ms)
412  *              @int (*filter)(const char *s, void *p)@ = filtering function
413  *              @void *p@ = pointer argument for filter
414  *
415  * Returns:     A pointer to a line block, which must be freed using
416  *              @tx_freeline@.
417  *
418  * Use:         Fetches a line from the buffer.  Each line is passed to the
419  *              filter function in oldest-to-newest order; the filter
420  *              function returns nonzero to choose a line.  If no suitable
421  *              line is waiting in the raw buffer, the program blocks while
422  *              more data is fetched, until the time limit @t@ is exceeded,
423  *              in which case a null pointer is returned.  A null filter
424  *              function is equivalent to one which always selects its line.
425  */
426
427 txline *tx_readx(txport *tx, unsigned long t,
428                  int (*filter)(const char *s, void *p), void *p)
429 {
430   txline *l, **ll = &tx->ll;
431   int e;
432   struct timeval now, tv;
433   struct timespec ts;
434   unsigned f = 0;
435
436 #define f_lock 1u
437
438   /* --- Get the time to wait until --- */
439
440   T( trace(T_TXSYS, "txsys: tx_readx begin"); )
441   if (t != FOREVER) {
442     gettimeofday(&now, 0);
443     tv_addl(&tv, &now, t / 1000, (t % 1000) * 1000);
444     ts.tv_sec = tv.tv_sec;
445     ts.tv_nsec = tv.tv_usec * 1000;
446   }
447
448   /* --- Check for a matching line --- */
449
450 again:
451   for (; *ll; ll = &l->next) {
452     l = *ll;
453     if (!filter || filter(l->s, p)) {
454       T( trace(T_TXSYS, "txsys: matched line; done"); )
455       goto done;
456     }
457   }
458   l = 0;
459
460   /* --- Lock the buffer --- *
461    *
462    * The following operations require a lock on the buffer, so we obtain that
463    * here.
464    */
465
466   if (!(f & f_lock)) {
467     if ((e = pthread_mutex_lock(&tx->mx)) != 0) {
468       err_report(ERR_TXPORT, ERRTX_READ, e,
469                  "error locking mutex: %s", strerror(errno));
470       goto done;
471     }
472     f |= f_lock;
473     T( trace(T_TXSYS, "txsys: locked buffer"); )
474   }
475
476   /* --- Push more stuff through the line buffer --- */
477
478 check:
479   if (DA_LEN(&tx->buf)) {
480     T( trace_block(T_TX, "tx: incoming data",
481                    DA(&tx->buf), DA_LEN(&tx->buf)); )
482     if (tx->lb.delim) 
483       lbuf_snarf(&tx->lb, DA(&tx->buf), DA_LEN(&tx->buf));
484     else
485       newline((char *)DA(&tx->buf), DA_LEN(&tx->buf), tx);
486     DA_SHRINK(&tx->buf, DA_LEN(&tx->buf));
487     goto again;
488   }
489
490   /* --- If nothing else can arrive, give up --- */
491
492   if (tx->s == TX_CLOSE) {
493     lbuf_close(&tx->lb);
494     T( trace(T_TXSYS, "txsys: transport closed; flushing"); )
495     tx->s = TX_CLOSED;
496     goto again;
497   }
498   if (!t || tx->s == TX_CLOSED) {
499     T( trace(T_TX, "tx: transport is closed"); )
500     goto done;
501   }
502
503   /* --- Wait for some more data to arrive --- */
504
505   T( trace(T_TXSYS, "txsys: waiting for data"); )
506   if (t == FOREVER)
507     e = pthread_cond_wait(&tx->cv, &tx->mx);
508   else {
509     gettimeofday(&now, 0);
510     if (TV_CMP(&now, >=, &tv)) {
511       T( trace(T_TXSYS, "txsys: timed out"); )
512       goto done;
513     }
514     e = pthread_cond_timedwait(&tx->cv, &tx->mx, &ts);
515   }
516   if (e && e != ETIMEDOUT && e != EINTR) {
517     err_report(ERR_TXPORT, ERRTX_READ, e,
518                "error waiting on condvar: %s", strerror(errno));
519     goto done;
520   }
521   T( trace(T_TXSYS, "txsys: woken, checking again"); )
522   goto check;
523
524   /* --- Everything is finished --- */
525
526 done:
527   if (f & f_lock) {
528     pthread_mutex_unlock(&tx->mx);
529     T( trace(T_TXSYS, "txsys: unlock buffer"); )
530   }
531   T( trace(T_TXSYS, "tx_readx done"); )
532   return (l);
533
534 #undef f_lock
535 }
536
537 txline *tx_read(txport *tx, unsigned long t)
538 {
539   return (tx_readx(tx, t, 0, 0));
540 }
541
542 /* --- @tx_freeline@ --- *
543  *
544  * Arguments:   @txline *l@ = pointer to line
545  *
546  * Returns:     ---
547  *
548  * Use:         Frees a line block.
549  */
550
551 void tx_freeline(txline *l)
552 {
553   txport *tx = l->tx;
554   if (l->next)
555     l->next->prev = l->prev;
556   else
557     tx->ll_tail = l->prev;
558   if (l->prev)
559     l->prev->next = l->next;
560   else
561     tx->ll = l->next;
562   xfree(l->s);
563   DESTROY(l);
564 }
565
566 /* --- @tx_destroy@ --- *
567  *
568  * Arguments:   @txport *tx@ = transport context
569  *
570  * Returns:     ---
571  *
572  * Use:         Destroys a transport.
573  */
574
575 void tx_destroy(txport *tx)
576 {
577   txline *l, *ll;
578
579   if (tx->s == TX_READY) {
580     pthread_mutex_lock(&tx->mx);
581     if (tx->s == TX_READY)
582       pthread_cancel(tx->tid);
583     pthread_mutex_unlock(&tx->mx);
584   }
585   pthread_mutex_destroy(&tx->mx);
586   pthread_cond_destroy(&tx->cv);
587   DA_DESTROY(&tx->buf);
588   lbuf_destroy(&tx->lb);
589   for (l = tx->ll; l; l = ll) {
590     ll = l->next;
591     xfree(l->s);
592     DESTROY(l);
593   }
594   tx->ops->destroy(tx);    
595 }
596
597 /*----- That's all, folks -------------------------------------------------*/