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.
13 #define ENABLE_FEATURE_CHAT_NOFAIL 1 // +126 bytes
14 #define ENABLE_FEATURE_CHAT_TTY_HIFI 0 // + 70 bytes
15 #define ENABLE_FEATURE_CHAT_IMPLICIT_CR 1 // + 44 bytes
16 #define ENABLE_FEATURE_CHAT_SEND_ESCAPES 0 // +103 bytes
17 #define ENABLE_FEATURE_CHAT_VAR_ABORT_LEN 0 // + 70 bytes
18 #define ENABLE_FEATURE_CHAT_CLR_ABORT 0 // +113 bytes
19 #define ENABLE_FEATURE_CHAT_SWALLOW_OPTS 0 // + 23 bytes
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 // N.B> 10 bytes for volatile. Why all these signals?!
41 static /*volatile*/ smallint exitcode;
43 // trap for critical signals
44 static void signal_handler(UNUSED_PARAM int signo)
46 // report I/O error condition
50 #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
51 #define unescape(s, nocr) unescape(s)
53 static size_t unescape(char *s, int *nocr)
60 // do we need special processing?
61 // standard escapes + \s for space and \N for \0
62 // \c inhibits terminating \r for commands and is noop for expects
66 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
74 } else if ('s' == c) {
76 #if ENABLE_FEATURE_CHAT_NOFAIL
77 // unescape leading dash only
78 // TODO: and only for expect, not command string
79 } else if ('-' == c && (start + 1 == s)) {
83 c = bb_process_escape_sequence((const char **)&s);
87 // ^A becomes \001, ^B -- \002 and so on...
88 } else if ('^' == c) {
93 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
105 int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
106 int chat_main(int argc UNUSED_PARAM, char **argv)
108 // should we dump device output? to what fd? by default no.
109 // this can be controlled later via ECHO {ON|OFF} chat directive
112 // collection of device replies which cause unconditional termination
113 llist_t *aborts = NULL;
115 int timeout = DEFAULT_CHAT_TIMEOUT;
116 // maximum length of abort string
117 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
118 size_t max_abort_len = 0;
120 #define max_abort_len MAX_ABORT_LEN
122 #if ENABLE_FEATURE_CHAT_TTY_HIFI
123 struct termios tio0, tio;
129 #if ENABLE_FEATURE_CHAT_CLR_ABORT
137 // make x* functions fail with correct exitcode
138 xfunc_error_retval = ERR_IO;
140 // trap vanilla signals to prevent process from being killed suddenly
148 #if ENABLE_FEATURE_CHAT_TTY_HIFI
149 tcgetattr(STDIN_FILENO, &tio);
152 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
155 #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
156 getopt32(argv, "vVsSE");
159 argv++; // goto first arg
161 // handle chat expect-send pairs
163 // directive given? process it
164 int key = index_in_strings(
166 #if ENABLE_FEATURE_CHAT_CLR_ABORT
169 "TIMEOUT\0" "ECHO\0" "SAY\0"
173 // cache directive value
175 // ON -> 1, anything else -> 0
176 bool onoff = !strcmp("ON", arg);
178 if (DIR_HANGUP == key) {
179 // turn SIGHUP on/off
180 signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
181 } else if (DIR_ABORT == key) {
182 // append the string to abort conditions
183 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
184 size_t len = strlen(arg);
185 if (len > max_abort_len)
188 llist_add_to_end(&aborts, arg);
189 #if ENABLE_FEATURE_CHAT_CLR_ABORT
190 } else if (DIR_CLR_ABORT == key) {
191 // remove the string from abort conditions
192 // N.B. gotta refresh maximum length too...
193 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
196 for (llist_t *l = aborts; l; l = l->link) {
197 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
198 size_t len = strlen(l->data);
200 if (!strcmp(arg, l->data)) {
201 llist_unlink(&aborts, l);
204 #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
205 if (len > max_abort_len)
210 } else if (DIR_TIMEOUT == key) {
213 timeout = atoi(arg) * 1000;
215 // >0 means value in msecs
217 timeout = DEFAULT_CHAT_TIMEOUT;
218 } else if (DIR_ECHO == key) {
220 // N.B. echo means dumping output
221 // from stdin (device) to stderr
223 //TODO? echo_fd = onoff * STDERR_FILENO;
224 //TODO? echo_fd = xopen(arg, O_WRONLY|O_CREAT|O_TRUNC);
225 } else if (DIR_SAY == key) {
226 // just print argument verbatim
227 fprintf(stderr, arg);
231 // ordinary expect-send pair!
233 //-----------------------
235 //-----------------------
238 size_t max_len = max_abort_len;
241 #if ENABLE_FEATURE_CHAT_NOFAIL
244 char *expect = *argv++;
246 // sanity check: shall we really expect something?
250 #if ENABLE_FEATURE_CHAT_NOFAIL
251 // if expect starts with -
252 if ('-' == *expect) {
255 // and enter nofail mode
260 #ifdef ___TEST___BUF___ // test behaviour with a small buffer
261 # undef COMMON_BUFSIZE
262 # define COMMON_BUFSIZE 6
264 // expand escape sequences in expect
265 expect_len = unescape(expect, &expect_len /*dummy*/);
266 if (expect_len > max_len)
267 max_len = expect_len;
269 // we should expect more than nothing but not more than input buffer
270 // TODO: later we'll get rid of fixed-size buffer
273 if (max_len >= COMMON_BUFSIZE) {
279 pfd.fd = STDIN_FILENO;
282 && poll(&pfd, 1, timeout) > 0
283 && (pfd.revents & POLLIN)
285 #define buf bb_common_bufsiz1
289 // read next char from device
290 if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
291 // dump device output if ECHO ON or RECORD fname
292 //TODO? if (echo_fd > 0) {
293 //TODO? full_write(echo_fd, buf+buf_len, 1);
296 full_write(STDERR_FILENO, buf+buf_len, 1);
298 // move input frame if we've reached higher bound
299 if (buf_len > COMMON_BUFSIZE) {
300 memmove(buf, buf+buf_len-max_len, max_len);
304 // N.B. rule of thumb: values being looked for can
305 // be found only at the end of input buffer
306 // this allows to get rid of strstr() and memmem()
308 // TODO: make expect and abort strings processed uniformly
309 // abort condition is met? -> bail out
310 for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
311 size_t len = strlen(l->data);
313 if (delta >= 0 && !memcmp(buf+delta, l->data, len))
318 // expected reply received? -> goto next command
319 delta = buf_len - expect_len;
320 if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
325 // device timed out or unexpected reply received
326 exitcode = ERR_TIMEOUT;
328 #if ENABLE_FEATURE_CHAT_NOFAIL
329 // on success and when in nofail mode
330 // we should skip following subsend-subexpect pairs
333 // find last send before non-dashed expect
334 while (*argv && argv[1] && '-' == argv[1][0])
337 // N.B. do we really need this?!
338 if (!*argv++ || !*argv++)
341 // nofail mode also clears all but IO errors (or signals)
342 if (ERR_IO != exitcode)
346 // bail out unless we expected successfully
350 //-----------------------
352 //-----------------------
354 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
355 int nocr = 0; // inhibit terminating command with \r
357 char *loaded = NULL; // loaded command
361 // if command starts with @
362 // load "real" command from file named after @
364 // skip the @ and any following white-space
366 buf = loaded = xmalloc_xopen_read_close(buf, NULL);
369 // expand escape sequences in command
370 len = unescape(buf, &nocr);
373 #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
374 pfd.fd = STDOUT_FILENO;
375 pfd.events = POLLOUT;
376 while (len && !exitcode
377 && poll(&pfd, 1, timeout) > 0
378 && (pfd.revents & POLLOUT)
381 // gotta send char by char to achieve this!
383 // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
384 // "\\K" means send BREAK
392 } else if ('p' == c) {
396 } else if ('K' == c) {
397 tcsendbreak(STDOUT_FILENO, 0);
404 if (safe_write(STDOUT_FILENO, buf, 1) > 0) {
413 len -= full_write(STDOUT_FILENO, buf, len);
418 // report I/O error if there still exists at least one non-sent char
422 // free loaded command (if any)
425 #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
426 // or terminate command with \r (if not inhibited)
428 xwrite(STDOUT_FILENO, "\r", 1);
431 // bail out unless we sent command successfully
439 #if ENABLE_FEATURE_CHAT_TTY_HIFI
440 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);