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 tarball for details.
12 // default timeout: 45 sec
13 #define DEFAULT_CHAT_TIMEOUT 45*1000
14 // max length of "abort string",
15 // i.e. device reply which causes termination
16 #define MAX_ABORT_LEN 50
18 // possible exit codes
20 ERR_OK
= 0, // all's well
21 ERR_MEM
, // read too much while expecting
22 ERR_IO
, // signalled or I/O error
23 ERR_TIMEOUT
, // timed out while expecting
24 ERR_ABORT
, // first abort condition was met
25 // ERR_ABORT2, // second abort condition was met
30 #define exitcode bb_got_signal
32 // trap for critical signals
33 static void signal_handler(UNUSED_PARAM
int signo
)
35 // report I/O error condition
39 #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
40 #define unescape(s, nocr) unescape(s)
42 static size_t unescape(char *s
, int *nocr
)
49 // do we need special processing?
50 // standard escapes + \s for space and \N for \0
51 // \c inhibits terminating \r for commands and is noop for expects
55 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
63 } else if ('s' == c
) {
65 #if ENABLE_FEATURE_CHAT_NOFAIL
66 // unescape leading dash only
67 // TODO: and only for expect, not command string
68 } else if ('-' == c
&& (start
+ 1 == s
)) {
72 c
= bb_process_escape_sequence((const char **)&s
);
76 // ^A becomes \001, ^B -- \002 and so on...
77 } else if ('^' == c
) {
82 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
93 int chat_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
94 int chat_main(int argc UNUSED_PARAM
, char **argv
)
98 // collection of device replies which cause unconditional termination
99 llist_t
*aborts
= NULL
;
101 int timeout
= DEFAULT_CHAT_TIMEOUT
;
102 // maximum length of abort string
103 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
104 size_t max_abort_len
= 0;
106 #define max_abort_len MAX_ABORT_LEN
108 #if ENABLE_FEATURE_CHAT_TTY_HIFI
109 struct termios tio0
, tio
;
115 #if ENABLE_FEATURE_CHAT_CLR_ABORT
124 // make x* functions fail with correct exitcode
125 xfunc_error_retval
= ERR_IO
;
127 // trap vanilla signals to prevent process from being killed suddenly
135 #if ENABLE_FEATURE_CHAT_TTY_HIFI
136 tcgetattr(STDIN_FILENO
, &tio
);
139 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &tio
);
142 #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
143 getopt32(argv
, "vVsSE");
146 argv
++; // goto first arg
148 // handle chat expect-send pairs
150 // directive given? process it
151 int key
= index_in_strings(
153 #if ENABLE_FEATURE_CHAT_CLR_ABORT
156 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
160 // cache directive value
162 // OFF -> 0, anything else -> 1
163 bool onoff
= (0 != strcmp("OFF", arg
));
165 if (DIR_HANGUP
== key
) {
166 // turn SIGHUP on/off
167 signal(SIGHUP
, onoff
? signal_handler
: SIG_IGN
);
168 } else if (DIR_ABORT
== key
) {
169 // append the string to abort conditions
170 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
171 size_t len
= strlen(arg
);
172 if (len
> max_abort_len
)
175 llist_add_to_end(&aborts
, arg
);
176 #if ENABLE_FEATURE_CHAT_CLR_ABORT
177 } else if (DIR_CLR_ABORT
== key
) {
178 // remove the string from abort conditions
179 // N.B. gotta refresh maximum length too...
180 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
183 for (llist_t
*l
= aborts
; l
; l
= l
->link
) {
184 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
185 size_t len
= strlen(l
->data
);
187 if (!strcmp(arg
, l
->data
)) {
188 llist_unlink(&aborts
, l
);
191 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
192 if (len
> max_abort_len
)
197 } else if (DIR_TIMEOUT
== key
) {
200 timeout
= atoi(arg
) * 1000;
202 // >0 means value in msecs
204 timeout
= DEFAULT_CHAT_TIMEOUT
;
205 } else if (DIR_ECHO
== key
) {
207 // N.B. echo means dumping device input/output to stderr
209 } else if (DIR_RECORD
== key
) {
210 // turn record on/off
211 // N.B. record means dumping device input to a file
212 // close previous record_fd
215 // N.B. do we have to die here on open error?
216 record_fd
= (onoff
) ? xopen(arg
, O_WRONLY
|O_CREAT
|O_TRUNC
) : -1;
217 } else if (DIR_SAY
== key
) {
218 // just print argument verbatim
219 // TODO: should we use full_write() to avoid unistd/stdio conflict?
220 bb_error_msg("%s", arg
);
224 // ordinary expect-send pair!
226 //-----------------------
228 //-----------------------
231 size_t max_len
= max_abort_len
;
234 #if ENABLE_FEATURE_CHAT_NOFAIL
237 char *expect
= *argv
++;
239 // sanity check: shall we really expect something?
243 #if ENABLE_FEATURE_CHAT_NOFAIL
244 // if expect starts with -
245 if ('-' == *expect
) {
248 // and enter nofail mode
253 #ifdef ___TEST___BUF___ // test behaviour with a small buffer
254 # undef COMMON_BUFSIZE
255 # define COMMON_BUFSIZE 6
257 // expand escape sequences in expect
258 expect_len
= unescape(expect
, &expect_len
/*dummy*/);
259 if (expect_len
> max_len
)
260 max_len
= expect_len
;
262 // we should expect more than nothing but not more than input buffer
263 // TODO: later we'll get rid of fixed-size buffer
266 if (max_len
>= COMMON_BUFSIZE
) {
272 pfd
.fd
= STDIN_FILENO
;
275 && poll(&pfd
, 1, timeout
) > 0
276 && (pfd
.revents
& POLLIN
)
278 #define buf bb_common_bufsiz1
282 // read next char from device
283 if (safe_read(STDIN_FILENO
, buf
+buf_len
, 1) > 0) {
284 // dump device input if RECORD fname
286 full_write(record_fd
, buf
+buf_len
, 1);
288 // dump device input if ECHO ON
290 // if (buf[buf_len] < ' ') {
291 // full_write(STDERR_FILENO, "^", 1);
292 // buf[buf_len] += '@';
294 full_write(STDERR_FILENO
, buf
+buf_len
, 1);
297 // move input frame if we've reached higher bound
298 if (buf_len
> COMMON_BUFSIZE
) {
299 memmove(buf
, buf
+buf_len
-max_len
, max_len
);
303 // N.B. rule of thumb: values being looked for can
304 // be found only at the end of input buffer
305 // this allows to get rid of strstr() and memmem()
307 // TODO: make expect and abort strings processed uniformly
308 // abort condition is met? -> bail out
309 for (l
= aborts
, exitcode
= ERR_ABORT
; l
; l
= l
->link
, ++exitcode
) {
310 size_t len
= strlen(l
->data
);
312 if (delta
>= 0 && !memcmp(buf
+delta
, l
->data
, len
))
317 // expected reply received? -> goto next command
318 delta
= buf_len
- expect_len
;
319 if (delta
>= 0 && !memcmp(buf
+delta
, expect
, expect_len
))
322 } /* while (have data) */
324 // device timed out or unexpected reply received
325 exitcode
= ERR_TIMEOUT
;
327 #if ENABLE_FEATURE_CHAT_NOFAIL
328 // on success and when in nofail mode
329 // we should skip following subsend-subexpect pairs
332 // find last send before non-dashed expect
333 while (*argv
&& argv
[1] && '-' == argv
[1][0])
336 // N.B. do we really need this?!
337 if (!*argv
++ || !*argv
++)
340 // nofail mode also clears all but IO errors (or signals)
341 if (ERR_IO
!= exitcode
)
345 // bail out unless we expected successfully
349 //-----------------------
351 //-----------------------
353 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
354 int nocr
= 0; // inhibit terminating command with \r
356 char *loaded
= NULL
; // loaded command
360 // if command starts with @
361 // load "real" command from file named after @
363 // skip the @ and any following white-space
365 buf
= loaded
= xmalloc_xopen_read_close(buf
, NULL
);
367 // expand escape sequences in command
368 len
= unescape(buf
, &nocr
);
372 pfd
.fd
= STDOUT_FILENO
;
373 pfd
.events
= POLLOUT
;
374 while (len
&& !exitcode
375 && poll(&pfd
, 1, -1) > 0
376 && (pfd
.revents
& POLLOUT
)
378 #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
379 // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
380 // "\\K" means send BREAK
395 tcsendbreak(STDOUT_FILENO
, 0);
401 if (safe_write(STDOUT_FILENO
, buf
, 1) != 1)
406 len
-= full_write(STDOUT_FILENO
, buf
, len
);
408 } /* while (can write) */
411 // report I/O error if there still exists at least one non-sent char
415 // free loaded command (if any)
418 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
419 // or terminate command with \r (if not inhibited)
421 xwrite(STDOUT_FILENO
, "\r", 1);
423 // bail out unless we sent command successfully
428 } /* while (*argv) */
430 #if ENABLE_FEATURE_CHAT_TTY_HIFI
431 tcsetattr(STDIN_FILENO
, TCSAFLUSH
, &tio0
);