ftp://ftp.metalab.unc.edu/pub/Linux/apps/serialcomm/fax/efax-0.9.tar.gz
[efax.git] / efaxio.c
1 #include <ctype.h>              /* ANSI C */
2 #include <signal.h>    
3 #include <stdio.h>
4 #include <string.h>
5
6 #include "efaxio.h"             /* EFAX */
7 #include "efaxmsg.h"
8 #include "efaxos.h"
9
10 #define MAXRESPB 1024       /* maximum bytes of modem responses saved */
11
12 char *prompts[] = {             /* modem responses that are prompts */
13   "OOK", "-CONNECT FAX", "CCONNECT", "NNO CARRIER", "EERROR",
14   "NNO DIALTONE", "BBUSY", "NNO ANSWER", "M+FCERROR", "VVCON", 
15   "DDATA", 0 } ;
16
17 int lockpolldelay = 8000 ;      /* milliseconds between checks of lock files */
18
19                             /* signals to be caught so can hang up phone */
20 int catch [] = { CATCHSIGS, 0 } ;
21
22
23 /* Modem features */
24
25 int c1=0, c20=0 ;               /* use class 1/class 2.0 */
26 int c2=0 ;                      /* force class 2 */
27 int cmdpause = T_CMD ;          /* delay before each init command */
28 int vfc = 0 ;                   /* virtual flow control */
29 uchar startchar = DC2 ; /* character to start reception */
30
31                                 /* response detector lookup tables */
32 uchar rd_nexts [ 256 ] = { 0 }, rd_allowed [ 256 ] = { 0 } ;
33
34 /* Initialize two lookup tables used by a state machine to detect
35    modem responses embedded in data.  The first shows which
36    characters are allowed in each state.  The second shows which
37    characters in each state increment the state.  Each table is
38    indexed by character.  The detector sequences through 6 states
39    corresponding to sequences of the form: <CR><LF>AX...<CR><LF>
40    where A is an upper-case letter and X is an u.c. letter or
41    space.  The state values are 01, 02, 04, 08, 10 and 20 (hex)
42    and are used to mask in a bit from the tables.  When the state
43    reaches 0x20 a modem response has been detected.  With random
44    data there is a small O(10^-10) chance of spurious
45    detection. */
46
47 void rd_init ( void )
48 {
49   int c ;
50
51   rd_nexts[CR] = rd_allowed[CR] = 0x01 | 0x08 ;
52   rd_nexts[LF] = rd_allowed[LF] = 0x02 | 0x10 ;
53   for ( c='A' ; c<'Z' ; c++ ) {
54       rd_allowed[c] = 0x04 | 0x08 ;
55       rd_nexts[c] = 0x04 ;
56     }
57   rd_allowed[' '] = 0x08 ;
58 }
59
60
61 /* Get a modem response into buffer s, storing up to n bytes.
62    The response includes characters from the most recent control
63    character until the first LF following some text.  Returns s
64    or null if times-out in t deciseconds or on i/o error. Trace
65    messages are buffered to reduce possible timing problems. */
66
67 char *tgets( TFILE *f, char *s, int len, int t )
68 {
69   int i, n, c ;
70
71   for ( i=n=0 ; 1 ; i++ ) {
72     if ( ( c = tgetc ( f, t ) ) == EOF ) break ;
73     if ( i == 0 ) msg ( "M-+ .%03d [", time_ms ( ) ) ;
74     msg ( "M-+ %s", cname ( c ) ) ;
75     if ( n > 0 && c == LF ) break ;
76     if ( ! iscntrl ( c ) && n < len ) s[n++] = c ;
77   }
78
79   if ( n >= len ) msg ( "W- modem response overflow" ) ;
80   s[ n < len ? n : len-1 ] = '\0' ;
81   if ( i > 0 ) {
82     if ( c == EOF ) msg ( "M- <...%.1f s>]", (float)t/10 ) ;
83     else msg ( "M- ]" ) ;
84   }
85
86   return c == EOF ? 0 : s ;
87 }
88
89
90 /* Send bytes to the modem, doing bit-reversal and escaping DLEs.
91    Returns 0 or 2 on errors. */
92
93 int sendbuf ( TFILE *f, uchar *p, int n, int dcecps )
94 {
95   int err=0, c, over ;
96   uchar *order = f->obitorder ;
97   uchar buf [ MINWRITE+1 ] ;
98   int i ;
99
100   for ( i=0 ; ! err && n > 0 ; n-- ) {
101     c  = order [ *p++ ] ;
102     if ( c == DLE ) buf[i++] = DLE ;
103     buf[i++] = c ;
104     if ( i >= MINWRITE || n == 1 ) {
105
106       /* ``virtual'' flow control */
107
108       if ( vfc && dcecps > 0 ) {
109         over = f->bytes - ( proc_ms ( ) - f->mstart ) * dcecps / 1000 
110           - MAXDCEBUF ;
111         if ( over > 0 ) msleep ( over * 1000 / dcecps ) ;
112       }
113
114       if ( tput ( f, buf, i ) < 0 )
115         err = msg ( "ES2fax device write error:" ) ;
116
117       i = 0 ;
118     }
119   }
120
121   return err ;
122 }
123
124
125 /* Scan responses since giving previous command (by cmd()) for a
126    match to string 's' at start of response.  If a matching
127    response is found it finds the start of the data field which
128    is defined as the next non-space character in the current or
129    any subsequent responses. If ip is not null, reads one integer
130    (decimal format) into ip. [problem: Class 2.0 status responses
131    are in hex.] Returns pointer to start of data field of
132    response string or NULL if not found. */
133
134 char responses [ MAXRESPB ], *lresponse = responses ;
135
136 char *sresponse ( char *s, int *ip )
137 {
138   char *p, *r = 0 ;
139   int lens, lenr ;
140   
141   lens = strlen ( s ) ;
142   for ( p=responses ; p<lresponse ; p += strlen(p) + 1 ) {
143
144     if ( ! strncmp ( p, s, lens ) ) {
145       r = p + lens ;
146
147       lenr = strlen ( r ) ;
148       if ( strspn ( r, " \r\n" ) == lenr && r+lenr < lresponse ) r += lenr ;
149
150       if ( ip && sscanf ( r, "%d", ip ) > 0 )
151         msg ( "R read value %d from \"%s\"", *ip, r ) ;
152     }
153     
154   }
155
156   return r ;
157 }
158
159
160 /* Search for the string s in responses since last command.
161    Skips lines beginning with "AT" (command echo) and removes
162    trailing spaces.  Returns pointer to the string or NULL if not
163    found. */
164
165 char *strinresp ( char *s )
166 {
167   char *p, *r = 0 ;
168
169   for ( p=responses ; p<lresponse && !r ; p += strlen(p) + 1 )
170     if ( strncmp ( p, "AT", 2 ) )
171       r = strstr ( p, s ) ;
172
173   return r ;
174 }
175
176
177 /* Appends the value of the first response that includes the
178    string s to buffer buf of length len.  Skips characters before
179    and including "=" in the response and doesn't copy trailing
180    spaces.  */
181
182 int getresp ( char *s, char *buf, int len )
183 {
184   int err=0, n ;
185   char *p, *q ;
186   
187   if ( ( p = strinresp ( s ) ) ) {
188     if ( ( q = strchr ( p, '=' ) ) ) p = q+1 ;
189     for ( q = p+strlen(p)-1 ; q>=p && isspace(*q) ; q-- ) ;
190     n = q - p + 1 ;
191     if ( n + strlen(buf) < len )
192       strncat ( buf, p, n ) ;
193     else
194       strncat ( buf, p, len - strlen(buf) - 1 ) ;
195   } else {
196     err = 1 ;
197   }
198   return err ;
199 }
200
201
202 /* Search for a match to the string s in a null-terminated array of
203    possible prefix strings pointed to by p.  The first character of each
204    prefix string is skipped.  Returns pointer to the table entry or NULL if
205    not found. */
206
207 char *strtabmatch ( char **p, char *s )
208 {
209   while ( *p && strncmp ( *p+1, s, strlen ( *p+1 ) ) ) p++ ;
210   return ( ! *p || **p == '-' ) ? NULL : *p ;
211 }
212
213
214 /* Send command to modem and check responses.  All modem commands
215    go through this function. Collects pending (unexpected)
216    responses and then pauses for inter-command delay (cmdpause)
217    if t is negative.  Writes command s to modem if s is not null.
218    Reads responses and terminates when a response is one of the
219    prompts in prompts[] or if times out in t deciseconds.
220    Repeats command if detects a RING response (probable
221    collision). Returns the first character of the matching prefix
222    string (e.g. 'O' for OK, 'C' for CONNECT, etc.)  or EOF if no
223    such response was received within timeout t. */
224
225 int cmd ( TFILE *f, char *s, int t )
226 {
227   char buf [ CMDBUFSIZE ], *p = "" ;
228   int resplen=0, pause=0 ;
229
230   if ( t < 0 ) {
231     pause = cmdpause ;
232     t = -t ;
233   }
234
235   lresponse = responses ;
236
237   retry:
238
239   if ( s ) { 
240
241     while ( tgets ( f, buf, CMDBUFSIZE, pause ) )
242       msg ( "W- unexpected response \"%s\"", buf ) ;
243
244     msg ( "C- command  \"%s\"", s ) ;
245
246     if ( strlen(s) >= CMDBUFSIZE-4 ) {
247       msg ( "E modem command \"%s\" too long", s ) ;
248     } else {
249       sprintf ( buf, "AT%s\r", s ) ;
250       tput ( f, buf, strlen(buf) ) ;
251     }
252   }
253
254   if ( t ) {
255
256     msg ( "C- waiting %.1f s", ((float) t)/10 ) ;
257
258     while ( ( p = tgets ( f, buf, CMDBUFSIZE, t ) ) ) {
259
260       if ( ( resplen += strlen ( buf ) + 1 ) <= MAXRESPB ) {
261         strcpy ( lresponse, buf ) ;
262         lresponse += strlen ( buf ) + 1 ;
263       }
264       
265       if ( ( p = strtabmatch ( (char**) prompts, buf ) ) ) {
266         msg ( "C- response \"%s\"", buf ) ;
267         break ;
268       }
269       
270       if ( ! strcmp ( buf, "RING" ) ) { msleep ( 100 ) ; goto retry ; }
271     }
272   }
273
274   return p ? *p : EOF ;
275 }
276
277
278 /* Send command to modem and wait for reply after testing (and
279    possibly setting) current error status via err
280    pointer. Returns 0 if err is already set, command response, or
281    EOF on timeout. */
282
283 int ckcmd ( TFILE *f, int *err, char *s, int t, int r )
284 {
285   int c=0 ;
286   if ( ( ! err || ! *err ) && ( c = cmd ( f, s, t ) ) != r && r ) {
287     msg ( err ? "E %s %s %s" : "W %s %s %s",
288          c == EOF ? "timed out" : "wrong response",
289          s ? "after command: " :  "after waiting",
290          s ? s : "" ) ;
291     if ( err ) *err = 3 ;
292   }
293   return c ;
294 }
295
296
297 /* Resynchronize modem from an unknown state.  If no immediate
298    response, try pulsing DTR low (needs &D{2,3,4}), and then
299    cancelling data or fax data modes.  In each case, discards any
300    responses for about 2 seconds and then tries command ATQ0V1 to
301    enable text responses.  Returns 0 if OK or 4 if no response.
302    */
303
304 int modemsync ( TFILE *f )
305 {
306   int err=0, method=0 ;
307
308   for ( method=0 ; ! err ; method++ ) {
309     switch ( method ) {
310     case 0 : 
311       break ;
312     case 1 :
313       break ;
314       ttymode ( f, VOICECOMMAND ) ;
315     case 2 : 
316       msg ("Isync: dropping DTR") ;
317       ttymode ( f, COMMAND ) ; msleep ( 200 ) ;
318       ttymode ( f, DROPDTR ) ; msleep ( 200 ) ;
319       ttymode ( f, COMMAND ) ; 
320       break ;
321     case 3 :
322       msg ("Isync: sending escapes") ;
323       ttymode ( f, VOICECOMMAND ) ;
324       tput ( f, CAN_STR, 1 ) ;
325       tput ( f, DLE_ETX, 2 ) ; 
326       msleep ( 100 ) ;
327       ttymode ( f, COMMAND ) ;
328       tput ( f, CAN_STR, 1 ) ;
329       tput ( f, DLE_ETX, 2 ) ; 
330       msleep ( 1500 ) ;
331       tput ( f, "+++", 3 ) ; 
332       break ;
333     case 4 :
334       err = msg ("E4sync: modem not responding") ;
335       continue ;
336     }
337     while ( method && cmd ( f, 0, 20 ) != EOF ) ;
338     if ( cmd ( f, "Q0V1", -20 ) == OK ) break ;
339   }
340   return err ;
341
342
343
344 /* Set up modem by sending initialization/reset commands.
345    Accepts either OK or CONNECT responses. Optionally changes
346    baud rate if a command begins with a number. Returns 0 if OK,
347    3 on errors. */
348
349 int setup ( TFILE *f, char **cmds, int ignerr )
350 {
351   int err=0 ;
352   char c ;
353
354   for ( ; ! err && *cmds ; cmds++ ) {
355 #if 0
356     if ( *cmds && isdigit( **cmds ) ) {
357       
358     }
359 #endif
360     if ( ( c = cmd ( f, *cmds, -TO_RESET ) ) != OK && c !=  VCONNECT && 
361         ! ignerr ) {
362       err = msg ( "E3modem command (%s) failed", *cmds ? *cmds : "none" ) ;
363     }
364   }
365
366   return err ;
367 }
368
369
370 /* Terminate session.  Makes sure modem is responding, sends
371    modem reset commands or hang-up command if none, removes lock
372    files. Returns 0 if OK, 3 on error.*/
373
374 int end_session ( TFILE *f, char **zcmd,  char **lkfile, int sync )
375 {
376   int err = 0 ;
377
378   if ( f && sync ) 
379     err = modemsync ( f ) ;
380   if ( f && zcmd && ! err && sync ) 
381     err = setup ( f, zcmd, 0 ) ;
382   if ( f )
383     ttymode ( f, ORIGINAL ) ;
384   if ( lkfile )
385     unlockall ( lkfile ) ;
386   return err ;
387
388     
389
390 /* Initialize session.  Try locking and opening fax device until opened or
391    get error. Then set tty modes, register signal handler, set up
392    modem. Returns 0 if OK, 2 on errors, 3 if initialization failed, 4 if no
393    modem response. */
394
395 int begin_session ( TFILE *f, char *fname, int reverse, int hwfc, 
396                     char **lkfile, ttymodes mode, void (*onsig) (int) )
397 {
398   int i, err=0, busy=0, minbusy=0 ;
399
400   do {
401     err = lockall ( lkfile, busy >= minbusy ) ;
402     if ( ! err ) err = ttyopen ( f, fname, reverse, hwfc ) ;
403     if ( err == 1 ) { 
404       if ( busy++ >= minbusy ) {
405         msg ( "W %s locked or busy. waiting.", fname ) ;
406         minbusy = minbusy ? minbusy*2 : 1 ;
407       }
408       msleep ( lockpolldelay ) ;
409     }
410   } while ( err == 1 ) ;
411   
412   if ( ! err ) msg ( "Iopened %s", fname ) ;
413
414   if ( ! err ) err = ttymode ( f, mode ) ;
415
416   if ( ! err ) {
417     rd_init ( ) ;
418     f->rd_state = RD_BEGIN ;
419   }
420   
421   for ( i=0 ; ! err && catch [ i ] ; i++ ) 
422     if ( signal ( catch [ i ], onsig ) == SIG_ERR ) 
423       err = msg ( "ES2can't set signal %d handler:", catch [ i ] ) ;
424   
425   if ( !err ) err = modemsync ( f ) ;
426
427   return err ;
428 }