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 /*----- Header files ------------------------------------------------------*/
40 #include <sys/types.h>
45 #include <mLib/darray.h>
46 #include <mLib/dstr.h>
47 #include <mLib/lbuf.h>
55 /*----- Global variables --------------------------------------------------*/
58 #include "tx-socket.h"
59 #include "tx-serial-unix.h"
60 txport_ops
*txlist
= TX_LIST
;
62 const char *txname
= 0;
63 const char *txfile
= 0;
64 const char *txconf
= 0;
66 /*----- Main code ---------------------------------------------------------*/
68 /* --- @newline@ --- *
70 * Arguments: @char *s@ = pointer to line
71 * @size_t len@ = length of line
72 * @void *txv@ = pointer to transport context
76 * Use: Adds a line to the list.
79 static void newline(char *s
, size_t len
, void *txv
)
86 T( trace(T_TX
, "tx: completed line: `%s'", s
); )
88 l
->s
= xmalloc(len
+ 1);
89 memcpy(l
->s
, s
, len
+ 1);
93 l
->prev
= tx
->ll_tail
;
95 tx
->ll_tail
->next
= l
;
101 /* --- @tx_configure@ --- *
103 * Arguments: @txport *tx@ = pointer to transport block
104 * @const char *config@ = config string
106 * Returns: Zero if OK, nonzero on errors.
108 * Use: Applies a configuration string to a transport.
111 int tx_configure(txport
*tx
, const char *config
)
120 for (k
= strtok(c
, ";"); k
; k
= strtok(0, ";")) {
121 if ((v
= strchr(k
, '=')) != 0)
123 if (strcmp(k
, "nl") == 0 || strcmp(k
, "newline") == 0) {
127 else if (strcmp(v
, "none") == 0)
129 else if (strcmp(v
, "crlf") == 0)
131 else if (strcmp(v
, "crlf-strict") == 0 ||
132 strcmp(v
, "strict-crlf") == 0)
134 else if (strcmp(v
, "cr") == 0)
136 else if (strcmp(v
, "lf") == 0)
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
;
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
);
157 if (isdigit((unsigned char)v
[0])) {
158 d
= strtoul(v
+ 1, &v
, 8);
160 err_report(ERR_TXPORT
, ERRTX_CONFIG
, 0,
161 "bad octal escape `%s' in `newline' config", v
);
168 err_report(ERR_TXPORT
, ERRTX_CONFIG
, 0,
169 "unknown escape `%s' in `newline' config", v
);
173 } else if (v
[1] == 0)
176 err_report(ERR_TXPORT
, ERRTX_CONFIG
, 0,
177 "unknown delimiter `%s' in `newline' config", v
);
183 if (tx
->ops
->configure
)
184 e
= tx
->ops
->configure(tx
, k
, v
);
186 err_report(ERR_TXPORT
, ERRTX_CONFIG
, 0,
187 "unrecognized configuration keyword `%s'", k
);
200 /* --- @tx_create@ --- *
202 * Arguments: @const char *name@ = name of transport to instantiate
203 * @const char *file@ = filename for transport
204 * @const char *config@ = config string
206 * Returns: A pointer to the transport context, or null on error.
208 * Use: Creates a new transport.
211 txport
*tx_create(const char *name
, const char *file
, const char *config
)
220 /* --- Look up the transport by name --- */
227 for (o
= txlist
; o
; o
= o
->next
) {
228 if (strncmp(name
, o
->name
, len
) == 0)
231 err_report(ERR_TXPORT
, ERRTX_BADTX
, 0, "unknown transport `%s'", name
);
234 /* --- Set up the transport block --- */
238 const struct txfile
*fv
;
239 for (fv
= o
->fv
; fv
->env
|| fv
->name
; fv
++) {
240 if (fv
->env
&& (file
= getenv(fv
->env
)) == 0)
245 if (file
&& fv
->name
)
255 if ((tx
= o
->create(file
)) == 0)
258 lbuf_init(&tx
->lb
, newline
, tx
);
259 if (tx_configure(tx
, config
))
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
));
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
));
273 if ((e
= pthread_attr_init(&ta
)) != 0) {
274 err_report(ERR_TXPORT
, ERRTX_CREATE
, e
,
275 "thread attribute creation failed: %s", strerror(e
));
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
));
284 pthread_attr_destroy(&ta
);
290 /* --- Something went wrong --- */
293 pthread_attr_destroy(&ta
);
295 pthread_cond_destroy(&tx
->cv
);
297 pthread_mutex_destroy(&tx
->mx
);
299 lbuf_destroy(&tx
->lb
);
300 tx
->ops
->destroy(tx
);
306 /* --- @tx_write@ --- *
308 * Arguments: @txport *tx@ = pointer to transport context
309 * @const void *p@ = pointer to buffer to write
310 * @size_t sz@ = size of buffer
312 * Returns: Zero if OK, or @-1@ on error.
314 * Use: Writes some data to a transport.
317 int tx_write(txport
*tx
, const void *p
, size_t sz
)
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
));
328 /* --- @tx_printf@ --- *
330 * Arguments: @txport *tx@ = pointer to transport context
331 * @const char *p@ = pointer to string to write
333 * Returns: The number of characters printed, or @EOF@ on error.
335 * Use: Writes a textual message to a transport.
338 int tx_vprintf(txport
*tx
, const char *p
, va_list *ap
)
343 dstr_vputf(&d
, p
, *ap
);
345 rc
= tx_write(tx
, d
.buf
, d
.len
);
350 int tx_printf(txport
*tx
, const char *p
, ...)
356 rc
= tx_vprintf(tx
, p
, &ap
);
361 /* --- @tx_newline@ --- *
363 * Arguments: @txport *tx@ = pointer to transport context
365 * Returns: Zero if OK, nonzero on error.
367 * Use: Writes a newline (record boundary) to the output.
370 int tx_newline(txport
*tx
)
372 static const char crlf
[2] = { 0x0d, 0x0a };
377 switch (tx
->lb
.delim
) {
379 case LBUF_STRICTCRLF
:
391 return (tx_write(tx
, p
, sz
));
394 /* --- @tx_read@, @tx_readx@ --- *
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
401 * Returns: A pointer to a line block, which must be freed using
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.
413 txline
*tx_readx(txport
*tx
, unsigned long t
,
414 int (*filter
)(const char *s
, void *p
), void *p
)
416 txline
*l
, **ll
= &tx
->ll
;
418 struct timeval now
, tv
;
424 /* --- Get the time to wait until --- */
426 T( trace(T_TXSYS
, "txsys: tx_readx begin"); )
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;
434 /* --- Check for a matching line --- */
437 for (; *ll
; ll
= &l
->next
) {
439 if (!filter
|| filter(l
->s
, p
)) {
440 T( trace(T_TXSYS
, "txsys: matched line; done"); )
446 /* --- Lock the buffer --- *
448 * The following operations require a lock on the buffer, so we obtain that
453 if ((e
= pthread_mutex_lock(&tx
->mx
)) != 0) {
454 err_report(ERR_TXPORT
, ERRTX_READ
, e
,
455 "error locking mutex: %s", strerror(errno
));
459 T( trace(T_TXSYS
, "txsys: locked buffer"); )
462 /* --- Push more stuff through the line buffer --- */
465 if (DA_LEN(&tx
->buf
)) {
466 T( trace_block(T_TX
, "tx: incoming data",
467 DA(&tx
->buf
), DA_LEN(&tx
->buf
)); )
469 lbuf_snarf(&tx
->lb
, DA(&tx
->buf
), DA_LEN(&tx
->buf
));
471 newline((char *)DA(&tx
->buf
), DA_LEN(&tx
->buf
), tx
);
472 DA_SHRINK(&tx
->buf
, DA_LEN(&tx
->buf
));
476 /* --- If nothing else can arrive, give up --- */
478 if (tx
->s
== TX_CLOSE
) {
480 T( trace(T_TXSYS
, "txsys: transport closed; flushing"); )
484 if (!t
|| tx
->s
== TX_CLOSED
) {
485 T( trace(T_TX
, "tx: transport is closed"); )
489 /* --- Wait for some more data to arrive --- */
491 T( trace(T_TXSYS
, "txsys: waiting for data"); )
493 e
= pthread_cond_wait(&tx
->cv
, &tx
->mx
);
495 gettimeofday(&now
, 0);
496 if (TV_CMP(&now
, >=, &tv
)) {
497 T( trace(T_TXSYS
, "txsys: timed out"); )
500 e
= pthread_cond_timedwait(&tx
->cv
, &tx
->mx
, &ts
);
502 if (e
&& e
!= ETIMEDOUT
&& e
!= EINTR
) {
503 err_report(ERR_TXPORT
, ERRTX_READ
, e
,
504 "error waiting on condvar: %s", strerror(errno
));
507 T( trace(T_TXSYS
, "txsys: woken, checking again"); )
510 /* --- Everything is finished --- */
514 pthread_mutex_unlock(&tx
->mx
);
515 T( trace(T_TXSYS
, "txsys: unlock buffer"); )
517 T( trace(T_TXSYS
, "tx_readx done"); )
523 txline
*tx_read(txport
*tx
, unsigned long t
)
525 return (tx_readx(tx
, t
, 0, 0));
528 /* --- @tx_freeline@ --- *
530 * Arguments: @txline *l@ = pointer to line
534 * Use: Frees a line block.
537 void tx_freeline(txline
*l
)
541 l
->next
->prev
= l
->prev
;
543 tx
->ll_tail
= l
->prev
;
545 l
->prev
->next
= l
->next
;
552 /* --- @tx_destroy@ --- *
554 * Arguments: @txport *tx@ = transport context
558 * Use: Destroys a transport.
561 void tx_destroy(txport
*tx
)
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
);
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
) {
580 tx
->ops
->destroy(tx
);
583 /*----- That's all, folks -------------------------------------------------*/