New audio subsystem.
[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
29/*----- Revision history --------------------------------------------------*
30 *
31 * $Log: txport.c,v $
0db4a803 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 *
2ec1e693 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
0db4a803 49#include <ctype.h>
2ec1e693 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>
2ec1e693 63#include <mLib/tv.h>
64
65#include "err.h"
0db4a803 66#include "jog.h"
2ec1e693 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"
74txport_ops *txlist = TX_LIST;
75
76const char *txname = 0;
77const char *txfile = 0;
78const 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
93static void newline(char *s, size_t len, void *txv)
94{
95 txport *tx = txv;
96 txline *l;
97
98 if (!s)
99 return;
0db4a803 100 T( trace(T_TX, "tx: completed line: `%s'", s); )
2ec1e693 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
0db4a803 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
125int 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;
209err:
210 xfree(c);
211 return (rc);
212}
213
2ec1e693 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
225txport *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
250found:
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;
0db4a803 269 if ((tx = o->create(file)) == 0)
2ec1e693 270 goto fail_0;
271 tx->ops = o;
0db4a803 272 lbuf_init(&tx->lb, newline, tx);
273 if (tx_configure(tx, config))
274 goto fail_1;
2ec1e693 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);
0db4a803 299 DA_CREATE(&tx->buf);
2ec1e693 300 tx->s = TX_READY;
301 DDESTROY(&d);
302 return (tx);
303
304 /* --- Something went wrong --- */
305
306fail_4:
307 pthread_attr_destroy(&ta);
308fail_3:
309 pthread_cond_destroy(&tx->cv);
310fail_2:
311 pthread_mutex_destroy(&tx->mx);
312fail_1:
0db4a803 313 lbuf_destroy(&tx->lb);
2ec1e693 314 tx->ops->destroy(tx);
315fail_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
331int tx_write(txport *tx, const void *p, size_t sz)
332{
0db4a803 333 T( trace_block(T_TX, "tx: outgoing data", p, sz); )
2ec1e693 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
352int 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
364int 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
0db4a803 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
384int 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
2ec1e693 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
427txline *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
0db4a803 440 T( trace(T_TXSYS, "txsys: tx_readx begin"); )
2ec1e693 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
450again:
451 for (; *ll; ll = &l->next) {
452 l = *ll;
0db4a803 453 if (!filter || filter(l->s, p)) {
454 T( trace(T_TXSYS, "txsys: matched line; done"); )
2ec1e693 455 goto done;
0db4a803 456 }
2ec1e693 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;
0db4a803 473 T( trace(T_TXSYS, "txsys: locked buffer"); )
2ec1e693 474 }
475
476 /* --- Push more stuff through the line buffer --- */
477
478check:
479 if (DA_LEN(&tx->buf)) {
0db4a803 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);
2ec1e693 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);
0db4a803 494 T( trace(T_TXSYS, "txsys: transport closed; flushing"); )
2ec1e693 495 tx->s = TX_CLOSED;
496 goto again;
497 }
0db4a803 498 if (!t || tx->s == TX_CLOSED) {
499 T( trace(T_TX, "tx: transport is closed"); )
2ec1e693 500 goto done;
0db4a803 501 }
2ec1e693 502
503 /* --- Wait for some more data to arrive --- */
504
0db4a803 505 T( trace(T_TXSYS, "txsys: waiting for data"); )
2ec1e693 506 if (t == FOREVER)
507 e = pthread_cond_wait(&tx->cv, &tx->mx);
0db4a803 508 else {
509 gettimeofday(&now, 0);
510 if (TV_CMP(&now, >=, &tv)) {
511 T( trace(T_TXSYS, "txsys: timed out"); )
512 goto done;
513 }
2ec1e693 514 e = pthread_cond_timedwait(&tx->cv, &tx->mx, &ts);
0db4a803 515 }
2ec1e693 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 }
0db4a803 521 T( trace(T_TXSYS, "txsys: woken, checking again"); )
2ec1e693 522 goto check;
523
524 /* --- Everything is finished --- */
525
526done:
0db4a803 527 if (f & f_lock) {
2ec1e693 528 pthread_mutex_unlock(&tx->mx);
0db4a803 529 T( trace(T_TXSYS, "txsys: unlock buffer"); )
530 }
531 T( trace(T_TXSYS, "tx_readx done"); )
2ec1e693 532 return (l);
533
534#undef f_lock
535}
536
537txline *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
551void 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
575void 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 -------------------------------------------------*/