1 /* vi: set sw=4 ts=4: */
3 * bare bones chat utility
4 * inspired by ppp's chat
6 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
8 * Licensed under GPLv2, see file LICENSE in this source tree.
11 //usage:#define chat_trivial_usage
12 //usage: "EXPECT [SEND [EXPECT [SEND...]]]"
13 //usage:#define chat_full_usage "\n\n"
14 //usage: "Useful for interacting with a modem connected to stdin/stdout.\n"
15 //usage: "A script consists of one or more \"expect-send\" pairs of strings,\n"
16 //usage: "each pair is a pair of arguments. Example:\n"
17 //usage: "chat '' ATZ OK ATD123456 CONNECT '' ogin: pppuser word: ppppass '~'"
21 // default timeout: 45 sec
22 #define DEFAULT_CHAT_TIMEOUT 45*1000
23 // max length of "abort string",
24 // i.e. device reply which causes termination
25 #define MAX_ABORT_LEN 50
27 // possible exit codes
29 ERR_OK
= 0, // all's well
30 ERR_MEM
, // read too much while expecting
31 ERR_IO
, // signalled or I/O error
32 ERR_TIMEOUT
, // timed out while expecting
33 ERR_ABORT
, // first abort condition was met
34 // ERR_ABORT2, // second abort condition was met
39 #define exitcode bb_got_signal
41 // trap for critical signals
42 static void signal_handler(UNUSED_PARAM
int signo
)
44 // report I/O error condition
48 #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
49 #define unescape(s, nocr) unescape(s)
51 static size_t unescape(char *s
, int *nocr
)
58 // do we need special processing?
59 // standard escapes + \s for space and \N for \0
60 // \c inhibits terminating \r for commands and is noop for expects
64 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
72 } else if ('s' == c
) {
74 #if ENABLE_FEATURE_CHAT_NOFAIL
75 // unescape leading dash only
76 // TODO: and only for expect, not command string
77 } else if ('-' == c
&& (start
+ 1 == s
)) {
81 c
= bb_process_escape_sequence((const char **)&s
);
85 // ^A becomes \001, ^B -- \002 and so on...
86 } else if ('^' == c
) {
91 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
102 int chat_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
103 int chat_main(int argc UNUSED_PARAM
, char **argv
)
107 // collection of device replies which cause unconditional termination
108 llist_t
*aborts
= NULL
;
110 int timeout
= DEFAULT_CHAT_TIMEOUT
;
111 // maximum length of abort string
112 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
113 size_t max_abort_len
= 0;
115 #define max_abort_len MAX_ABORT_LEN
117 #if ENABLE_FEATURE_CHAT_TTY_HIFI
118 struct termios tio0
, tio
;
124 #if ENABLE_FEATURE_CHAT_CLR_ABORT
133 // make x* functions fail with correct exitcode
134 xfunc_error_retval
= ERR_IO
;
136 // trap vanilla signals to prevent process from being killed suddenly
144 #if ENABLE_FEATURE_CHAT_TTY_HIFI
145 tcgetattr(STDIN_FILENO
, &tio
);
148 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &tio
);
151 #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
152 getopt32(argv
, "vVsSE");
155 argv
++; // goto first arg
157 // handle chat expect-send pairs
159 // directive given? process it
160 int key
= index_in_strings(
162 #if ENABLE_FEATURE_CHAT_CLR_ABORT
165 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
169 // cache directive value
171 // OFF -> 0, anything else -> 1
172 bool onoff
= (0 != strcmp("OFF", arg
));
174 if (DIR_HANGUP
== key
) {
175 // turn SIGHUP on/off
176 signal(SIGHUP
, onoff
? signal_handler
: SIG_IGN
);
177 } else if (DIR_ABORT
== key
) {
178 // append the string to abort conditions
179 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
180 size_t len
= strlen(arg
);
181 if (len
> max_abort_len
)
184 llist_add_to_end(&aborts
, arg
);
185 #if ENABLE_FEATURE_CHAT_CLR_ABORT
186 } else if (DIR_CLR_ABORT
== key
) {
188 // remove the string from abort conditions
189 // N.B. gotta refresh maximum length too...
190 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
193 for (l
= aborts
; l
; l
= l
->link
) {
194 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
195 size_t len
= strlen(l
->data
);
197 if (strcmp(arg
, l
->data
) == 0) {
198 llist_unlink(&aborts
, l
);
201 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
202 if (len
> max_abort_len
)
207 } else if (DIR_TIMEOUT
== key
) {
210 timeout
= atoi(arg
) * 1000;
212 // >0 means value in msecs
214 timeout
= DEFAULT_CHAT_TIMEOUT
;
215 } else if (DIR_ECHO
== key
) {
217 // N.B. echo means dumping device input/output to stderr
219 } else if (DIR_RECORD
== key
) {
220 // turn record on/off
221 // N.B. record means dumping device input to a file
222 // close previous record_fd
225 // N.B. do we have to die here on open error?
226 record_fd
= (onoff
) ? xopen(arg
, O_WRONLY
|O_CREAT
|O_TRUNC
) : -1;
227 } else if (DIR_SAY
== key
) {
228 // just print argument verbatim
229 // TODO: should we use full_write() to avoid unistd/stdio conflict?
230 bb_error_msg("%s", arg
);
234 // ordinary expect-send pair!
236 //-----------------------
238 //-----------------------
241 size_t max_len
= max_abort_len
;
244 #if ENABLE_FEATURE_CHAT_NOFAIL
247 char *expect
= *argv
++;
249 // sanity check: shall we really expect something?
253 #if ENABLE_FEATURE_CHAT_NOFAIL
254 // if expect starts with -
255 if ('-' == *expect
) {
258 // and enter nofail mode
263 #ifdef ___TEST___BUF___ // test behaviour with a small buffer
264 # undef COMMON_BUFSIZE
265 # define COMMON_BUFSIZE 6
267 // expand escape sequences in expect
268 expect_len
= unescape(expect
, &expect_len
/*dummy*/);
269 if (expect_len
> max_len
)
270 max_len
= expect_len
;
272 // we should expect more than nothing but not more than input buffer
273 // TODO: later we'll get rid of fixed-size buffer
276 if (max_len
>= COMMON_BUFSIZE
) {
282 pfd
.fd
= STDIN_FILENO
;
285 && poll(&pfd
, 1, timeout
) > 0
286 && (pfd
.revents
& POLLIN
)
288 #define buf bb_common_bufsiz1
292 // read next char from device
293 if (safe_read(STDIN_FILENO
, buf
+buf_len
, 1) > 0) {
294 // dump device input if RECORD fname
296 full_write(record_fd
, buf
+buf_len
, 1);
298 // dump device input if ECHO ON
300 // if (buf[buf_len] < ' ') {
301 // full_write(STDERR_FILENO, "^", 1);
302 // buf[buf_len] += '@';
304 full_write(STDERR_FILENO
, buf
+buf_len
, 1);
307 // move input frame if we've reached higher bound
308 if (buf_len
> COMMON_BUFSIZE
) {
309 memmove(buf
, buf
+buf_len
-max_len
, max_len
);
313 // N.B. rule of thumb: values being looked for can
314 // be found only at the end of input buffer
315 // this allows to get rid of strstr() and memmem()
317 // TODO: make expect and abort strings processed uniformly
318 // abort condition is met? -> bail out
319 for (l
= aborts
, exitcode
= ERR_ABORT
; l
; l
= l
->link
, ++exitcode
) {
320 size_t len
= strlen(l
->data
);
322 if (delta
>= 0 && !memcmp(buf
+delta
, l
->data
, len
))
327 // expected reply received? -> goto next command
328 delta
= buf_len
- expect_len
;
329 if (delta
>= 0 && !memcmp(buf
+delta
, expect
, expect_len
))
332 } /* while (have data) */
334 // device timed out or unexpected reply received
335 exitcode
= ERR_TIMEOUT
;
337 #if ENABLE_FEATURE_CHAT_NOFAIL
338 // on success and when in nofail mode
339 // we should skip following subsend-subexpect pairs
342 // find last send before non-dashed expect
343 while (*argv
&& argv
[1] && '-' == argv
[1][0])
346 // N.B. do we really need this?!
347 if (!*argv
++ || !*argv
++)
350 // nofail mode also clears all but IO errors (or signals)
351 if (ERR_IO
!= exitcode
)
355 // bail out unless we expected successfully
359 //-----------------------
361 //-----------------------
363 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
364 int nocr
= 0; // inhibit terminating command with \r
366 char *loaded
= NULL
; // loaded command
370 // if command starts with @
371 // load "real" command from file named after @
373 // skip the @ and any following white-space
375 buf
= loaded
= xmalloc_xopen_read_close(buf
, NULL
);
377 // expand escape sequences in command
378 len
= unescape(buf
, &nocr
);
382 pfd
.fd
= STDOUT_FILENO
;
383 pfd
.events
= POLLOUT
;
384 while (len
&& !exitcode
385 && poll(&pfd
, 1, -1) > 0
386 && (pfd
.revents
& POLLOUT
)
388 #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
389 // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
390 // "\\K" means send BREAK
405 tcsendbreak(STDOUT_FILENO
, 0);
411 if (safe_write(STDOUT_FILENO
, buf
, 1) != 1)
416 len
-= full_write(STDOUT_FILENO
, buf
, len
);
418 } /* while (can write) */
421 // report I/O error if there still exists at least one non-sent char
425 // free loaded command (if any)
428 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
429 // or terminate command with \r (if not inhibited)
431 xwrite(STDOUT_FILENO
, "\r", 1);
433 // bail out unless we sent command successfully
438 } /* while (*argv) */
440 #if ENABLE_FEATURE_CHAT_TTY_HIFI
441 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &tio0
);