From 4aca87515a5083ae0e31ce3177189fd43b6d05ac Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sat, 3 Jan 2015 13:58:15 +0100 Subject: patch to Vanilla Tomato 1.28 --- release/src/router/busybox/miscutils/chat.c | 435 ++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 release/src/router/busybox/miscutils/chat.c (limited to 'release/src/router/busybox/miscutils/chat.c') diff --git a/release/src/router/busybox/miscutils/chat.c b/release/src/router/busybox/miscutils/chat.c new file mode 100644 index 00000000..3ffd7b22 --- /dev/null +++ b/release/src/router/busybox/miscutils/chat.c @@ -0,0 +1,435 @@ +/* vi: set sw=4 ts=4: */ +/* + * bare bones chat utility + * inspired by ppp's chat + * + * Copyright (C) 2008 by Vladimir Dronnikov + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ +#include "libbb.h" + +// default timeout: 45 sec +#define DEFAULT_CHAT_TIMEOUT 45*1000 +// max length of "abort string", +// i.e. device reply which causes termination +#define MAX_ABORT_LEN 50 + +// possible exit codes +enum { + ERR_OK = 0, // all's well + ERR_MEM, // read too much while expecting + ERR_IO, // signalled or I/O error + ERR_TIMEOUT, // timed out while expecting + ERR_ABORT, // first abort condition was met +// ERR_ABORT2, // second abort condition was met +// ... +}; + +// exit code +#define exitcode bb_got_signal + +// trap for critical signals +static void signal_handler(UNUSED_PARAM int signo) +{ + // report I/O error condition + exitcode = ERR_IO; +} + +#if !ENABLE_FEATURE_CHAT_IMPLICIT_CR +#define unescape(s, nocr) unescape(s) +#endif +static size_t unescape(char *s, int *nocr) +{ + char *start = s; + char *p = s; + + while (*s) { + char c = *s; + // do we need special processing? + // standard escapes + \s for space and \N for \0 + // \c inhibits terminating \r for commands and is noop for expects + if ('\\' == c) { + c = *++s; + if (c) { +#if ENABLE_FEATURE_CHAT_IMPLICIT_CR + if ('c' == c) { + *nocr = 1; + goto next; + } +#endif + if ('N' == c) { + c = '\0'; + } else if ('s' == c) { + c = ' '; +#if ENABLE_FEATURE_CHAT_NOFAIL + // unescape leading dash only + // TODO: and only for expect, not command string + } else if ('-' == c && (start + 1 == s)) { + //c = '-'; +#endif + } else { + c = bb_process_escape_sequence((const char **)&s); + s--; + } + } + // ^A becomes \001, ^B -- \002 and so on... + } else if ('^' == c) { + c = *++s-'@'; + } + // put unescaped char + *p++ = c; +#if ENABLE_FEATURE_CHAT_IMPLICIT_CR + next: +#endif + // next char + s++; + } + *p = '\0'; + + return p - start; +} + +int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int chat_main(int argc UNUSED_PARAM, char **argv) +{ + int record_fd = -1; + bool echo = 0; + // collection of device replies which cause unconditional termination + llist_t *aborts = NULL; + // inactivity period + int timeout = DEFAULT_CHAT_TIMEOUT; + // maximum length of abort string +#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN + size_t max_abort_len = 0; +#else +#define max_abort_len MAX_ABORT_LEN +#endif +#if ENABLE_FEATURE_CHAT_TTY_HIFI + struct termios tio0, tio; +#endif + // directive names + enum { + DIR_HANGUP = 0, + DIR_ABORT, +#if ENABLE_FEATURE_CHAT_CLR_ABORT + DIR_CLR_ABORT, +#endif + DIR_TIMEOUT, + DIR_ECHO, + DIR_SAY, + DIR_RECORD, + }; + + // make x* functions fail with correct exitcode + xfunc_error_retval = ERR_IO; + + // trap vanilla signals to prevent process from being killed suddenly + bb_signals(0 + + (1 << SIGHUP) + + (1 << SIGINT) + + (1 << SIGTERM) + + (1 << SIGPIPE) + , signal_handler); + +#if ENABLE_FEATURE_CHAT_TTY_HIFI + tcgetattr(STDIN_FILENO, &tio); + tio0 = tio; + cfmakeraw(&tio); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio); +#endif + +#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS + getopt32(argv, "vVsSE"); + argv += optind; +#else + argv++; // goto first arg +#endif + // handle chat expect-send pairs + while (*argv) { + // directive given? process it + int key = index_in_strings( + "HANGUP\0" "ABORT\0" +#if ENABLE_FEATURE_CHAT_CLR_ABORT + "CLR_ABORT\0" +#endif + "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0" + , *argv + ); + if (key >= 0) { + // cache directive value + char *arg = *++argv; + // OFF -> 0, anything else -> 1 + bool onoff = (0 != strcmp("OFF", arg)); + // process directive + if (DIR_HANGUP == key) { + // turn SIGHUP on/off + signal(SIGHUP, onoff ? signal_handler : SIG_IGN); + } else if (DIR_ABORT == key) { + // append the string to abort conditions +#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN + size_t len = strlen(arg); + if (len > max_abort_len) + max_abort_len = len; +#endif + llist_add_to_end(&aborts, arg); +#if ENABLE_FEATURE_CHAT_CLR_ABORT + } else if (DIR_CLR_ABORT == key) { + // remove the string from abort conditions + // N.B. gotta refresh maximum length too... +#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN + max_abort_len = 0; +#endif + for (llist_t *l = aborts; l; l = l->link) { +#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN + size_t len = strlen(l->data); +#endif + if (!strcmp(arg, l->data)) { + llist_unlink(&aborts, l); + continue; + } +#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN + if (len > max_abort_len) + max_abort_len = len; +#endif + } +#endif + } else if (DIR_TIMEOUT == key) { + // set new timeout + // -1 means OFF + timeout = atoi(arg) * 1000; + // 0 means default + // >0 means value in msecs + if (!timeout) + timeout = DEFAULT_CHAT_TIMEOUT; + } else if (DIR_ECHO == key) { + // turn echo on/off + // N.B. echo means dumping device input/output to stderr + echo = onoff; + } else if (DIR_RECORD == key) { + // turn record on/off + // N.B. record means dumping device input to a file + // close previous record_fd + if (record_fd > 0) + close(record_fd); + // N.B. do we have to die here on open error? + record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1; + } else if (DIR_SAY == key) { + // just print argument verbatim + // TODO: should we use full_write() to avoid unistd/stdio conflict? + bb_error_msg("%s", arg); + } + // next, please! + argv++; + // ordinary expect-send pair! + } else { + //----------------------- + // do expect + //----------------------- + int expect_len; + size_t buf_len = 0; + size_t max_len = max_abort_len; + + struct pollfd pfd; +#if ENABLE_FEATURE_CHAT_NOFAIL + int nofail = 0; +#endif + char *expect = *argv++; + + // sanity check: shall we really expect something? + if (!expect) + goto expect_done; + +#if ENABLE_FEATURE_CHAT_NOFAIL + // if expect starts with - + if ('-' == *expect) { + // swallow - + expect++; + // and enter nofail mode + nofail++; + } +#endif + +#ifdef ___TEST___BUF___ // test behaviour with a small buffer +# undef COMMON_BUFSIZE +# define COMMON_BUFSIZE 6 +#endif + // expand escape sequences in expect + expect_len = unescape(expect, &expect_len /*dummy*/); + if (expect_len > max_len) + max_len = expect_len; + // sanity check: + // we should expect more than nothing but not more than input buffer + // TODO: later we'll get rid of fixed-size buffer + if (!expect_len) + goto expect_done; + if (max_len >= COMMON_BUFSIZE) { + exitcode = ERR_MEM; + goto expect_done; + } + + // get reply + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; + while (!exitcode + && poll(&pfd, 1, timeout) > 0 + && (pfd.revents & POLLIN) + ) { +#define buf bb_common_bufsiz1 + llist_t *l; + ssize_t delta; + + // read next char from device + if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) { + // dump device input if RECORD fname + if (record_fd > 0) { + full_write(record_fd, buf+buf_len, 1); + } + // dump device input if ECHO ON + if (echo > 0) { +// if (buf[buf_len] < ' ') { +// full_write(STDERR_FILENO, "^", 1); +// buf[buf_len] += '@'; +// } + full_write(STDERR_FILENO, buf+buf_len, 1); + } + buf_len++; + // move input frame if we've reached higher bound + if (buf_len > COMMON_BUFSIZE) { + memmove(buf, buf+buf_len-max_len, max_len); + buf_len = max_len; + } + } + // N.B. rule of thumb: values being looked for can + // be found only at the end of input buffer + // this allows to get rid of strstr() and memmem() + + // TODO: make expect and abort strings processed uniformly + // abort condition is met? -> bail out + for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) { + size_t len = strlen(l->data); + delta = buf_len-len; + if (delta >= 0 && !memcmp(buf+delta, l->data, len)) + goto expect_done; + } + exitcode = ERR_OK; + + // expected reply received? -> goto next command + delta = buf_len - expect_len; + if (delta >= 0 && !memcmp(buf+delta, expect, expect_len)) + goto expect_done; +#undef buf + } /* while (have data) */ + + // device timed out or unexpected reply received + exitcode = ERR_TIMEOUT; + expect_done: +#if ENABLE_FEATURE_CHAT_NOFAIL + // on success and when in nofail mode + // we should skip following subsend-subexpect pairs + if (nofail) { + if (!exitcode) { + // find last send before non-dashed expect + while (*argv && argv[1] && '-' == argv[1][0]) + argv += 2; + // skip the pair + // N.B. do we really need this?! + if (!*argv++ || !*argv++) + break; + } + // nofail mode also clears all but IO errors (or signals) + if (ERR_IO != exitcode) + exitcode = ERR_OK; + } +#endif + // bail out unless we expected successfully + if (exitcode) + break; + + //----------------------- + // do send + //----------------------- + if (*argv) { +#if ENABLE_FEATURE_CHAT_IMPLICIT_CR + int nocr = 0; // inhibit terminating command with \r +#endif + char *loaded = NULL; // loaded command + size_t len; + char *buf = *argv++; + + // if command starts with @ + // load "real" command from file named after @ + if ('@' == *buf) { + // skip the @ and any following white-space + trim(++buf); + buf = loaded = xmalloc_xopen_read_close(buf, NULL); + } + // expand escape sequences in command + len = unescape(buf, &nocr); + + // send command + alarm(timeout); + pfd.fd = STDOUT_FILENO; + pfd.events = POLLOUT; + while (len && !exitcode + && poll(&pfd, 1, -1) > 0 + && (pfd.revents & POLLOUT) + ) { +#if ENABLE_FEATURE_CHAT_SEND_ESCAPES + // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay + // "\\K" means send BREAK + char c = *buf; + if ('\\' == c) { + c = *++buf; + if ('d' == c) { + sleep(1); + len--; + continue; + } + if ('p' == c) { + usleep(10000); + len--; + continue; + } + if ('K' == c) { + tcsendbreak(STDOUT_FILENO, 0); + len--; + continue; + } + buf--; + } + if (safe_write(STDOUT_FILENO, buf, 1) != 1) + break; + len--; + buf++; +#else + len -= full_write(STDOUT_FILENO, buf, len); +#endif + } /* while (can write) */ + alarm(0); + + // report I/O error if there still exists at least one non-sent char + if (len) + exitcode = ERR_IO; + + // free loaded command (if any) + if (loaded) + free(loaded); +#if ENABLE_FEATURE_CHAT_IMPLICIT_CR + // or terminate command with \r (if not inhibited) + else if (!nocr) + xwrite(STDOUT_FILENO, "\r", 1); +#endif + // bail out unless we sent command successfully + if (exitcode) + break; + } /* if (*argv) */ + } + } /* while (*argv) */ + +#if ENABLE_FEATURE_CHAT_TTY_HIFI + tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0); +#endif + + return exitcode; +} -- cgit v1.2.3-54-g00ecf