infra: Clean up project setup
[jog] / txport.c
CommitLineData
2ec1e693 1/* -*-c-*-
2 *
0db4a803 3 * $Id: txport.c,v 1.2 2002/01/30 09:27:10 mdw Exp $
2ec1e693 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
2ec1e693 29/*----- Header files ------------------------------------------------------*/
30
31#ifdef HAVE_CONFIG_H
32# include "config.h"
33#endif
34
0db4a803 35#include <ctype.h>
2ec1e693 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>
2ec1e693 49#include <mLib/tv.h>
50
51#include "err.h"
0db4a803 52#include "jog.h"
2ec1e693 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"
60txport_ops *txlist = TX_LIST;
61
62const char *txname = 0;
63const char *txfile = 0;
64const 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
79static void newline(char *s, size_t len, void *txv)
80{
81 txport *tx = txv;
82 txline *l;
83
84 if (!s)
85 return;
0db4a803 86 T( trace(T_TX, "tx: completed line: `%s'", s); )
2ec1e693 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
0db4a803 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
111int 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;
195err:
196 xfree(c);
197 return (rc);
198}
199
2ec1e693 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
211txport *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
236found:
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;
0db4a803 255 if ((tx = o->create(file)) == 0)
2ec1e693 256 goto fail_0;
257 tx->ops = o;
0db4a803 258 lbuf_init(&tx->lb, newline, tx);
259 if (tx_configure(tx, config))
260 goto fail_1;
2ec1e693 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);
0db4a803 285 DA_CREATE(&tx->buf);
2ec1e693 286 tx->s = TX_READY;
287 DDESTROY(&d);
288 return (tx);
289
290 /* --- Something went wrong --- */
291
292fail_4:
293 pthread_attr_destroy(&ta);
294fail_3:
295 pthread_cond_destroy(&tx->cv);
296fail_2:
297 pthread_mutex_destroy(&tx->mx);
298fail_1:
0db4a803 299 lbuf_destroy(&tx->lb);
2ec1e693 300 tx->ops->destroy(tx);
301fail_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
317int tx_write(txport *tx, const void *p, size_t sz)
318{
0db4a803 319 T( trace_block(T_TX, "tx: outgoing data", p, sz); )
2ec1e693 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
338int 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
350int 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
0db4a803 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
370int 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
2ec1e693 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
413txline *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
0db4a803 426 T( trace(T_TXSYS, "txsys: tx_readx begin"); )
2ec1e693 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
436again:
437 for (; *ll; ll = &l->next) {
438 l = *ll;
0db4a803 439 if (!filter || filter(l->s, p)) {
440 T( trace(T_TXSYS, "txsys: matched line; done"); )
2ec1e693 441 goto done;
0db4a803 442 }
2ec1e693 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;
0db4a803 459 T( trace(T_TXSYS, "txsys: locked buffer"); )
2ec1e693 460 }
461
462 /* --- Push more stuff through the line buffer --- */
463
464check:
465 if (DA_LEN(&tx->buf)) {
0db4a803 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);
2ec1e693 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);
0db4a803 480 T( trace(T_TXSYS, "txsys: transport closed; flushing"); )
2ec1e693 481 tx->s = TX_CLOSED;
482 goto again;
483 }
0db4a803 484 if (!t || tx->s == TX_CLOSED) {
485 T( trace(T_TX, "tx: transport is closed"); )
2ec1e693 486 goto done;
0db4a803 487 }
2ec1e693 488
489 /* --- Wait for some more data to arrive --- */
490
0db4a803 491 T( trace(T_TXSYS, "txsys: waiting for data"); )
2ec1e693 492 if (t == FOREVER)
493 e = pthread_cond_wait(&tx->cv, &tx->mx);
0db4a803 494 else {
495 gettimeofday(&now, 0);
496 if (TV_CMP(&now, >=, &tv)) {
497 T( trace(T_TXSYS, "txsys: timed out"); )
498 goto done;
499 }
2ec1e693 500 e = pthread_cond_timedwait(&tx->cv, &tx->mx, &ts);
0db4a803 501 }
2ec1e693 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 }
0db4a803 507 T( trace(T_TXSYS, "txsys: woken, checking again"); )
2ec1e693 508 goto check;
509
510 /* --- Everything is finished --- */
511
512done:
0db4a803 513 if (f & f_lock) {
2ec1e693 514 pthread_mutex_unlock(&tx->mx);
0db4a803 515 T( trace(T_TXSYS, "txsys: unlock buffer"); )
516 }
517 T( trace(T_TXSYS, "tx_readx done"); )
2ec1e693 518 return (l);
519
520#undef f_lock
521}
522
523txline *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
537void 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
561void 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 -------------------------------------------------*/