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