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 -------------------------------------------------*/