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 '~'"
20 #include "common_bufsiz.h"
22 // default timeout: 45 sec
23 #define DEFAULT_CHAT_TIMEOUT 45*1000
24 // max length of "abort string",
25 // i.e. device reply which causes termination
26 #define MAX_ABORT_LEN 50
28 // possible exit codes
30 ERR_OK = 0, // all's well
31 ERR_MEM, // read too much while expecting
32 ERR_IO, // signalled or I/O error
33 ERR_TIMEOUT, // timed out while expecting
34 ERR_ABORT, // first abort condition was met
35 // ERR_ABORT2, // second abort condition was met
40 #define exitcode bb_got_signal
42 // trap for critical signals
43 static void signal_handler(UNUSED_PARAM int signo)
45 // report I/O error condition
49 #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
50 #define unescape(s, nocr) unescape(s)
52 static size_t unescape(char *s, int *nocr)
59 // do we need special processing?
60 // standard escapes + \s for space and \N for \0
61 // \c inhibits terminating \r for commands and is noop for expects
65 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
73 } else if ('s' == c) {
75 #if ENABLE_FEATURE_CHAT_NOFAIL
76 // unescape leading dash only
77 // TODO: and only for expect, not command string
78 } else if ('-' == c && (start + 1 == s)) {
82 c = bb_process_escape_sequence((const char **)&s);
86 // ^A becomes \001, ^B -- \002 and so on...
87 } else if ('^' == c) {
92 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
103 int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
104 int chat_main(int argc UNUSED_PARAM, char **argv)
108 // collection of device replies which cause unconditional termination
109 llist_t *aborts = NULL;
111 int timeout = DEFAULT_CHAT_TIMEOUT;
112 // maximum length of abort string
113 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
114 size_t max_abort_len = 0;
116 #define max_abort_len MAX_ABORT_LEN
118 #if ENABLE_FEATURE_CHAT_TTY_HIFI
119 struct termios tio0, tio;
125 #if ENABLE_FEATURE_CHAT_CLR_ABORT
134 // make x* functions fail with correct exitcode
135 xfunc_error_retval = ERR_IO;
137 // trap vanilla signals to prevent process from being killed suddenly
145 #if ENABLE_FEATURE_CHAT_TTY_HIFI
146 tcgetattr(STDIN_FILENO, &tio);
149 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
152 #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
153 getopt32(argv, "vVsSE");
156 argv++; // goto first arg
158 // handle chat expect-send pairs
160 // directive given? process it
161 int key = index_in_strings(
163 #if ENABLE_FEATURE_CHAT_CLR_ABORT
166 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
170 // cache directive value
172 // OFF -> 0, anything else -> 1
173 bool onoff = (0 != strcmp("OFF", arg));
175 if (DIR_HANGUP == key) {
176 // turn SIGHUP on/off
177 signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
178 } else if (DIR_ABORT == key) {
179 // append the string to abort conditions
180 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
181 size_t len = strlen(arg);
182 if (len > max_abort_len)
185 llist_add_to_end(&aborts, arg);
186 #if ENABLE_FEATURE_CHAT_CLR_ABORT
187 } else if (DIR_CLR_ABORT == key) {
189 // remove the string from abort conditions
190 // N.B. gotta refresh maximum length too...
191 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
194 for (l = aborts; l; l = l->link) {
195 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
196 size_t len = strlen(l->data);
198 if (strcmp(arg, l->data) == 0) {
199 llist_unlink(&aborts, l);
202 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
203 if (len > max_abort_len)
208 } else if (DIR_TIMEOUT == key) {
211 timeout = atoi(arg) * 1000;
213 // >0 means value in msecs
215 timeout = DEFAULT_CHAT_TIMEOUT;
216 } else if (DIR_ECHO == key) {
218 // N.B. echo means dumping device input/output to stderr
220 } else if (DIR_RECORD == key) {
221 // turn record on/off
222 // N.B. record means dumping device input to a file
223 // close previous record_fd
226 // N.B. do we have to die here on open error?
227 record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
228 } else if (DIR_SAY == key) {
229 // just print argument verbatim
230 // TODO: should we use full_write() to avoid unistd/stdio conflict?
231 bb_error_msg("%s", arg);
235 // ordinary expect-send pair!
237 //-----------------------
239 //-----------------------
242 size_t max_len = max_abort_len;
245 #if ENABLE_FEATURE_CHAT_NOFAIL
248 char *expect = *argv++;
250 // sanity check: shall we really expect something?
254 #if ENABLE_FEATURE_CHAT_NOFAIL
255 // if expect starts with -
256 if ('-' == *expect) {
259 // and enter nofail mode
264 #ifdef ___TEST___BUF___ // test behaviour with a small buffer
265 # undef COMMON_BUFSIZE
266 # define COMMON_BUFSIZE 6
268 // expand escape sequences in expect
269 expect_len = unescape(expect, &expect_len /*dummy*/);
270 if (expect_len > max_len)
271 max_len = expect_len;
273 // we should expect more than nothing but not more than input buffer
274 // TODO: later we'll get rid of fixed-size buffer
277 if (max_len >= COMMON_BUFSIZE) {
283 pfd.fd = STDIN_FILENO;
286 && poll(&pfd, 1, timeout) > 0
287 && (pfd.revents & POLLIN)
289 #define buf bb_common_bufsiz1
293 // read next char from device
294 if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
295 // dump device input if RECORD fname
297 full_write(record_fd, buf+buf_len, 1);
299 // dump device input if ECHO ON
301 // if (buf[buf_len] < ' ') {
302 // full_write(STDERR_FILENO, "^", 1);
303 // buf[buf_len] += '@';
305 full_write(STDERR_FILENO, buf+buf_len, 1);
308 // move input frame if we've reached higher bound
309 if (buf_len > COMMON_BUFSIZE) {
310 memmove(buf, buf+buf_len-max_len, max_len);
314 // N.B. rule of thumb: values being looked for can
315 // be found only at the end of input buffer
316 // this allows to get rid of strstr() and memmem()
318 // TODO: make expect and abort strings processed uniformly
319 // abort condition is met? -> bail out
320 for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
321 size_t len = strlen(l->data);
323 if (delta >= 0 && !memcmp(buf+delta, l->data, len))
328 // expected reply received? -> goto next command
329 delta = buf_len - expect_len;
330 if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
333 } /* while (have data) */
335 // device timed out or unexpected reply received
336 exitcode = ERR_TIMEOUT;
338 #if ENABLE_FEATURE_CHAT_NOFAIL
339 // on success and when in nofail mode
340 // we should skip following subsend-subexpect pairs
343 // find last send before non-dashed expect
344 while (*argv && argv[1] && '-' == argv[1][0])
347 // N.B. do we really need this?!
348 if (!*argv++ || !*argv++)
351 // nofail mode also clears all but IO errors (or signals)
352 if (ERR_IO != exitcode)
356 // bail out unless we expected successfully
360 //-----------------------
362 //-----------------------
364 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
365 int nocr = 0; // inhibit terminating command with \r
367 char *loaded = NULL; // loaded command
371 // if command starts with @
372 // load "real" command from file named after @
374 // skip the @ and any following white-space
376 buf = loaded = xmalloc_xopen_read_close(buf, NULL);
378 // expand escape sequences in command
379 len = unescape(buf, &nocr);
383 pfd.fd = STDOUT_FILENO;
384 pfd.events = POLLOUT;
385 while (len && !exitcode
386 && poll(&pfd, 1, -1) > 0
387 && (pfd.revents & POLLOUT)
389 #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
390 // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
391 // "\\K" means send BREAK
406 tcsendbreak(STDOUT_FILENO, 0);
412 if (safe_write(STDOUT_FILENO, buf, 1) != 1)
417 len -= full_write(STDOUT_FILENO, buf, len);
419 } /* while (can write) */
422 // report I/O error if there still exists at least one non-sent char
426 // free loaded command (if any)
429 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
430 // or terminate command with \r (if not inhibited)
432 xwrite(STDOUT_FILENO, "\r", 1);
434 // bail out unless we sent command successfully
439 } /* while (*argv) */
441 #if ENABLE_FEATURE_CHAT_TTY_HIFI
442 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);