3 * $Id: txport.c,v 1.2 2002/01/30 09:27:10 mdw Exp $
5 * Transport switch glue
7 * (c) 2001 Mark Wooding
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of Jog: Programming for a jogging machine.
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.
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.
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.
29 /*----- Revision history --------------------------------------------------*
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
38 * Revision 1.1 2002/01/25 19:34:45 mdw
43 /*----- Header files ------------------------------------------------------*/
54 #include <sys/types.h>
59 #include <mLib/darray.h>
60 #include <mLib/dstr.h>
61 #include <mLib/lbuf.h>
69 /*----- Global variables --------------------------------------------------*/
72 #include "tx-socket.h"
73 #include "tx-serial-unix.h"
74 txport_ops *txlist = TX_LIST;
76 const char *txname = 0;
77 const char *txfile = 0;
78 const char *txconf = 0;
80 /*----- Main code ---------------------------------------------------------*/
82 /* --- @newline@ --- *
84 * Arguments: @char *s@ = pointer to line
85 * @size_t len@ = length of line
86 * @void *txv@ = pointer to transport context
90 * Use: Adds a line to the list.
93 static void newline(char *s, size_t len, void *txv)
100 T( trace(T_TX, "tx: completed line: `%s'", s); )
102 l->s = xmalloc(len + 1);
103 memcpy(l->s, s, len + 1);
107 l->prev = tx->ll_tail;
109 tx->ll_tail->next = l;
115 /* --- @tx_configure@ --- *
117 * Arguments: @txport *tx@ = pointer to transport block
118 * @const char *config@ = config string
120 * Returns: Zero if OK, nonzero on errors.
122 * Use: Applies a configuration string to a transport.
125 int tx_configure(txport *tx, const char *config)
134 for (k = strtok(c, ";"); k; k = strtok(0, ";")) {
135 if ((v = strchr(k, '=')) != 0)
137 if (strcmp(k, "nl") == 0 || strcmp(k, "newline") == 0) {
141 else if (strcmp(v, "none") == 0)
143 else if (strcmp(v, "crlf") == 0)
145 else if (strcmp(v, "crlf-strict") == 0 ||
146 strcmp(v, "strict-crlf") == 0)
148 else if (strcmp(v, "cr") == 0)
150 else if (strcmp(v, "lf") == 0)
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;
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);
171 if (isdigit((unsigned char)v[0])) {
172 d = strtoul(v + 1, &v, 8);
174 err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
175 "bad octal escape `%s' in `newline' config", v);
182 err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
183 "unknown escape `%s' in `newline' config", v);
187 } else if (v[1] == 0)
190 err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
191 "unknown delimiter `%s' in `newline' config", v);
197 if (tx->ops->configure)
198 e = tx->ops->configure(tx, k, v);
200 err_report(ERR_TXPORT, ERRTX_CONFIG, 0,
201 "unrecognized configuration keyword `%s'", k);
214 /* --- @tx_create@ --- *
216 * Arguments: @const char *name@ = name of transport to instantiate
217 * @const char *file@ = filename for transport
218 * @const char *config@ = config string
220 * Returns: A pointer to the transport context, or null on error.
222 * Use: Creates a new transport.
225 txport *tx_create(const char *name, const char *file, const char *config)
234 /* --- Look up the transport by name --- */
241 for (o = txlist; o; o = o->next) {
242 if (strncmp(name, o->name, len) == 0)
245 err_report(ERR_TXPORT, ERRTX_BADTX, 0, "unknown transport `%s'", name);
248 /* --- Set up the transport block --- */
252 const struct txfile *fv;
253 for (fv = o->fv; fv->env || fv->name; fv++) {
254 if (fv->env && (file = getenv(fv->env)) == 0)
259 if (file && fv->name)
269 if ((tx = o->create(file)) == 0)
272 lbuf_init(&tx->lb, newline, tx);
273 if (tx_configure(tx, config))
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));
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));
287 if ((e = pthread_attr_init(&ta)) != 0) {
288 err_report(ERR_TXPORT, ERRTX_CREATE, e,
289 "thread attribute creation failed: %s", strerror(e));
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));
298 pthread_attr_destroy(&ta);
304 /* --- Something went wrong --- */
307 pthread_attr_destroy(&ta);
309 pthread_cond_destroy(&tx->cv);
311 pthread_mutex_destroy(&tx->mx);
313 lbuf_destroy(&tx->lb);
314 tx->ops->destroy(tx);
320 /* --- @tx_write@ --- *
322 * Arguments: @txport *tx@ = pointer to transport context
323 * @const void *p@ = pointer to buffer to write
324 * @size_t sz@ = size of buffer
326 * Returns: Zero if OK, or @-1@ on error.
328 * Use: Writes some data to a transport.
331 int tx_write(txport *tx, const void *p, size_t sz)
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));
342 /* --- @tx_printf@ --- *
344 * Arguments: @txport *tx@ = pointer to transport context
345 * @const char *p@ = pointer to string to write
347 * Returns: The number of characters printed, or @EOF@ on error.
349 * Use: Writes a textual message to a transport.
352 int tx_vprintf(txport *tx, const char *p, va_list *ap)
357 dstr_vputf(&d, p, *ap);
359 rc = tx_write(tx, d.buf, d.len);
364 int tx_printf(txport *tx, const char *p, ...)
370 rc = tx_vprintf(tx, p, &ap);
375 /* --- @tx_newline@ --- *
377 * Arguments: @txport *tx@ = pointer to transport context
379 * Returns: Zero if OK, nonzero on error.
381 * Use: Writes a newline (record boundary) to the output.
384 int tx_newline(txport *tx)
386 static const char crlf[2] = { 0x0d, 0x0a };
391 switch (tx->lb.delim) {
393 case LBUF_STRICTCRLF:
405 return (tx_write(tx, p, sz));
408 /* --- @tx_read@, @tx_readx@ --- *
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
415 * Returns: A pointer to a line block, which must be freed using
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.
427 txline *tx_readx(txport *tx, unsigned long t,
428 int (*filter)(const char *s, void *p), void *p)
430 txline *l, **ll = &tx->ll;
432 struct timeval now, tv;
438 /* --- Get the time to wait until --- */
440 T( trace(T_TXSYS, "txsys: tx_readx begin"); )
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;
448 /* --- Check for a matching line --- */
451 for (; *ll; ll = &l->next) {
453 if (!filter || filter(l->s, p)) {
454 T( trace(T_TXSYS, "txsys: matched line; done"); )
460 /* --- Lock the buffer --- *
462 * The following operations require a lock on the buffer, so we obtain that
467 if ((e = pthread_mutex_lock(&tx->mx)) != 0) {
468 err_report(ERR_TXPORT, ERRTX_READ, e,
469 "error locking mutex: %s", strerror(errno));
473 T( trace(T_TXSYS, "txsys: locked buffer"); )
476 /* --- Push more stuff through the line buffer --- */
479 if (DA_LEN(&tx->buf)) {
480 T( trace_block(T_TX, "tx: incoming data",
481 DA(&tx->buf), DA_LEN(&tx->buf)); )
483 lbuf_snarf(&tx->lb, DA(&tx->buf), DA_LEN(&tx->buf));
485 newline((char *)DA(&tx->buf), DA_LEN(&tx->buf), tx);
486 DA_SHRINK(&tx->buf, DA_LEN(&tx->buf));
490 /* --- If nothing else can arrive, give up --- */
492 if (tx->s == TX_CLOSE) {
494 T( trace(T_TXSYS, "txsys: transport closed; flushing"); )
498 if (!t || tx->s == TX_CLOSED) {
499 T( trace(T_TX, "tx: transport is closed"); )
503 /* --- Wait for some more data to arrive --- */
505 T( trace(T_TXSYS, "txsys: waiting for data"); )
507 e = pthread_cond_wait(&tx->cv, &tx->mx);
509 gettimeofday(&now, 0);
510 if (TV_CMP(&now, >=, &tv)) {
511 T( trace(T_TXSYS, "txsys: timed out"); )
514 e = pthread_cond_timedwait(&tx->cv, &tx->mx, &ts);
516 if (e && e != ETIMEDOUT && e != EINTR) {
517 err_report(ERR_TXPORT, ERRTX_READ, e,
518 "error waiting on condvar: %s", strerror(errno));
521 T( trace(T_TXSYS, "txsys: woken, checking again"); )
524 /* --- Everything is finished --- */
528 pthread_mutex_unlock(&tx->mx);
529 T( trace(T_TXSYS, "txsys: unlock buffer"); )
531 T( trace(T_TXSYS, "tx_readx done"); )
537 txline *tx_read(txport *tx, unsigned long t)
539 return (tx_readx(tx, t, 0, 0));
542 /* --- @tx_freeline@ --- *
544 * Arguments: @txline *l@ = pointer to line
548 * Use: Frees a line block.
551 void tx_freeline(txline *l)
555 l->next->prev = l->prev;
557 tx->ll_tail = l->prev;
559 l->prev->next = l->next;
566 /* --- @tx_destroy@ --- *
568 * Arguments: @txport *tx@ = transport context
572 * Use: Destroys a transport.
575 void tx_destroy(txport *tx)
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);
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) {
594 tx->ops->destroy(tx);
597 /*----- That's all, folks -------------------------------------------------*/