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