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 //config: bool "chat (6.6 kb)"
14 //config: Simple chat utility.
16 //config:config FEATURE_CHAT_NOFAIL
17 //config: bool "Enable NOFAIL expect strings"
18 //config: depends on CHAT
21 //config: When enabled expect strings which are started with a dash trigger
22 //config: no-fail mode. That is when expectation is not met within timeout
23 //config: the script is not terminated but sends next SEND string and waits
24 //config: for next EXPECT string. This allows to compose far more flexible
27 //config:config FEATURE_CHAT_TTY_HIFI
28 //config: bool "Force STDIN to be a TTY"
29 //config: depends on CHAT
32 //config: Original chat always treats STDIN as a TTY device and sets for it
33 //config: so-called raw mode. This option turns on such behaviour.
35 //config:config FEATURE_CHAT_IMPLICIT_CR
36 //config: bool "Enable implicit Carriage Return"
37 //config: depends on CHAT
40 //config: When enabled make chat to terminate all SEND strings with a "\r"
41 //config: unless "\c" is met anywhere in the string.
43 //config:config FEATURE_CHAT_SWALLOW_OPTS
44 //config: bool "Swallow options"
45 //config: depends on CHAT
48 //config: Busybox chat require no options. To make it not fail when used
49 //config: in place of original chat (which has a bunch of options) turn
52 //config:config FEATURE_CHAT_SEND_ESCAPES
53 //config: bool "Support weird SEND escapes"
54 //config: depends on CHAT
57 //config: Original chat uses some escape sequences in SEND arguments which
58 //config: are not sent to device but rather performs special actions.
59 //config: E.g. "\K" means to send a break sequence to device.
60 //config: "\d" delays execution for a second, "\p" -- for a 1/100 of second.
61 //config: Before turning this option on think twice: do you really need them?
63 //config:config FEATURE_CHAT_VAR_ABORT_LEN
64 //config: bool "Support variable-length ABORT conditions"
65 //config: depends on CHAT
68 //config: Original chat uses fixed 50-bytes length ABORT conditions. Say N here.
70 //config:config FEATURE_CHAT_CLR_ABORT
71 //config: bool "Support revoking of ABORT conditions"
72 //config: depends on CHAT
75 //config: Support CLR_ABORT directive.
77 //applet:IF_CHAT(APPLET(chat, BB_DIR_USR_SBIN, BB_SUID_DROP))
79 //kbuild:lib-$(CONFIG_CHAT) += chat.o
81 //usage:#define chat_trivial_usage
82 //usage: "EXPECT [SEND [EXPECT [SEND...]]]"
83 //usage:#define chat_full_usage "\n\n"
84 //usage: "Useful for interacting with a modem connected to stdin/stdout.\n"
85 //usage: "A script consists of one or more \"expect-send\" pairs of strings,\n"
86 //usage: "each pair is a pair of arguments. Example:\n"
87 //usage: "chat '' ATZ OK ATD123456 CONNECT '' ogin: pppuser word: ppppass '~'"
90 #include "common_bufsiz.h"
92 // default timeout: 45 sec
93 #define DEFAULT_CHAT_TIMEOUT 45*1000
94 // max length of "abort string",
95 // i.e. device reply which causes termination
96 #define MAX_ABORT_LEN 50
98 // possible exit codes
100 ERR_OK = 0, // all's well
101 ERR_MEM, // read too much while expecting
102 ERR_IO, // signalled or I/O error
103 ERR_TIMEOUT, // timed out while expecting
104 ERR_ABORT, // first abort condition was met
105 // ERR_ABORT2, // second abort condition was met
110 #define exitcode bb_got_signal
112 // trap for critical signals
113 static void signal_handler(UNUSED_PARAM int signo)
115 // report I/O error condition
119 #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
120 #define unescape(s, nocr) unescape(s)
122 static size_t unescape(char *s, int *nocr)
129 // do we need special processing?
130 // standard escapes + \s for space and \N for \0
131 // \c inhibits terminating \r for commands and is noop for expects
135 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
143 } else if ('s' == c) {
145 #if ENABLE_FEATURE_CHAT_NOFAIL
146 // unescape leading dash only
147 // TODO: and only for expect, not command string
148 } else if ('-' == c && (start + 1 == s)) {
152 c = bb_process_escape_sequence((const char **)&s);
156 // ^A becomes \001, ^B -- \002 and so on...
157 } else if ('^' == c) {
160 // put unescaped char
162 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
173 int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
174 int chat_main(int argc UNUSED_PARAM, char **argv)
178 // collection of device replies which cause unconditional termination
179 llist_t *aborts = NULL;
181 int timeout = DEFAULT_CHAT_TIMEOUT;
182 // maximum length of abort string
183 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
184 size_t max_abort_len = 0;
186 #define max_abort_len MAX_ABORT_LEN
188 #if ENABLE_FEATURE_CHAT_TTY_HIFI
189 struct termios tio0, tio;
195 #if ENABLE_FEATURE_CHAT_CLR_ABORT
204 // make x* functions fail with correct exitcode
205 xfunc_error_retval = ERR_IO;
207 // trap vanilla signals to prevent process from being killed suddenly
215 #if ENABLE_FEATURE_CHAT_TTY_HIFI
216 //TODO: use set_termios_to_raw()
217 tcgetattr(STDIN_FILENO, &tio);
220 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
223 #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
224 getopt32(argv, "vVsSE");
227 argv++; // goto first arg
229 // handle chat expect-send pairs
231 // directive given? process it
232 int key = index_in_strings(
234 #if ENABLE_FEATURE_CHAT_CLR_ABORT
237 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
241 // cache directive value
243 // OFF -> 0, anything else -> 1
244 bool onoff = (0 != strcmp("OFF", arg));
246 if (DIR_HANGUP == key) {
247 // turn SIGHUP on/off
248 signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
249 } else if (DIR_ABORT == key) {
250 // append the string to abort conditions
251 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
252 size_t len = strlen(arg);
253 if (len > max_abort_len)
256 llist_add_to_end(&aborts, arg);
257 #if ENABLE_FEATURE_CHAT_CLR_ABORT
258 } else if (DIR_CLR_ABORT == key) {
260 // remove the string from abort conditions
261 // N.B. gotta refresh maximum length too...
262 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
265 for (l = aborts; l; l = l->link) {
266 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
267 size_t len = strlen(l->data);
269 if (strcmp(arg, l->data) == 0) {
270 llist_unlink(&aborts, l);
273 # if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
274 if (len > max_abort_len)
279 } else if (DIR_TIMEOUT == key) {
282 timeout = atoi(arg) * 1000;
284 // >0 means value in msecs
286 timeout = DEFAULT_CHAT_TIMEOUT;
287 } else if (DIR_ECHO == key) {
289 // N.B. echo means dumping device input/output to stderr
291 } else if (DIR_RECORD == key) {
292 // turn record on/off
293 // N.B. record means dumping device input to a file
294 // close previous record_fd
297 // N.B. do we have to die here on open error?
298 record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
299 } else if (DIR_SAY == key) {
300 // just print argument verbatim
301 // TODO: should we use full_write() to avoid unistd/stdio conflict?
302 bb_error_msg("%s", arg);
306 // ordinary expect-send pair!
308 //-----------------------
310 //-----------------------
313 size_t max_len = max_abort_len;
316 #if ENABLE_FEATURE_CHAT_NOFAIL
319 char *expect = *argv++;
321 // sanity check: shall we really expect something?
325 #if ENABLE_FEATURE_CHAT_NOFAIL
326 // if expect starts with -
327 if ('-' == *expect) {
330 // and enter nofail mode
335 #ifdef ___TEST___BUF___ // test behaviour with a small buffer
336 # undef COMMON_BUFSIZE
337 # define COMMON_BUFSIZE 6
339 // expand escape sequences in expect
340 expect_len = unescape(expect, &expect_len /*dummy*/);
341 if (expect_len > max_len)
342 max_len = expect_len;
344 // we should expect more than nothing but not more than input buffer
345 // TODO: later we'll get rid of fixed-size buffer
348 if (max_len >= COMMON_BUFSIZE) {
354 pfd.fd = STDIN_FILENO;
357 && poll(&pfd, 1, timeout) > 0
358 && (pfd.revents & POLLIN)
362 #define buf bb_common_bufsiz1
363 setup_common_bufsiz();
365 // read next char from device
366 if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
367 // dump device input if RECORD fname
369 full_write(record_fd, buf+buf_len, 1);
371 // dump device input if ECHO ON
373 // if (buf[buf_len] < ' ') {
374 // full_write(STDERR_FILENO, "^", 1);
375 // buf[buf_len] += '@';
377 full_write(STDERR_FILENO, buf+buf_len, 1);
380 // move input frame if we've reached higher bound
381 if (buf_len > COMMON_BUFSIZE) {
382 memmove(buf, buf+buf_len-max_len, max_len);
386 // N.B. rule of thumb: values being looked for can
387 // be found only at the end of input buffer
388 // this allows to get rid of strstr() and memmem()
390 // TODO: make expect and abort strings processed uniformly
391 // abort condition is met? -> bail out
392 for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
393 size_t len = strlen(l->data);
395 if (delta >= 0 && !memcmp(buf+delta, l->data, len))
400 // expected reply received? -> goto next command
401 delta = buf_len - expect_len;
402 if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
405 } /* while (have data) */
407 // device timed out or unexpected reply received
408 exitcode = ERR_TIMEOUT;
410 #if ENABLE_FEATURE_CHAT_NOFAIL
411 // on success and when in nofail mode
412 // we should skip following subsend-subexpect pairs
415 // find last send before non-dashed expect
416 while (*argv && argv[1] && '-' == argv[1][0])
419 // N.B. do we really need this?!
420 if (!*argv++ || !*argv++)
423 // nofail mode also clears all but IO errors (or signals)
424 if (ERR_IO != exitcode)
428 // bail out unless we expected successfully
432 //-----------------------
434 //-----------------------
436 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
437 int nocr = 0; // inhibit terminating command with \r
439 char *loaded = NULL; // loaded command
443 // if command starts with @
444 // load "real" command from file named after @
446 // skip the @ and any following white-space
448 buf = loaded = xmalloc_xopen_read_close(buf, NULL);
450 // expand escape sequences in command
451 len = unescape(buf, &nocr);
455 pfd.fd = STDOUT_FILENO;
456 pfd.events = POLLOUT;
457 while (len && !exitcode
458 && poll(&pfd, 1, -1) > 0
459 && (pfd.revents & POLLOUT)
461 #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
462 // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
463 // "\\K" means send BREAK
478 tcsendbreak(STDOUT_FILENO, 0);
484 if (safe_write(STDOUT_FILENO, buf, 1) != 1)
489 len -= full_write(STDOUT_FILENO, buf, len);
491 } /* while (can write) */
494 // report I/O error if there still exists at least one non-sent char
498 // free loaded command (if any)
501 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
502 // or terminate command with \r (if not inhibited)
504 xwrite(STDOUT_FILENO, "\r", 1);
506 // bail out unless we sent command successfully
511 } /* while (*argv) */
513 #if ENABLE_FEATURE_CHAT_TTY_HIFI
514 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);