chat: now with "svn add"
authorDenis Vlasenko <vda.linux@googlemail.com>
Tue, 19 Feb 2008 11:35:08 +0000 (11:35 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Tue, 19 Feb 2008 11:35:08 +0000 (11:35 -0000)
miscutils/chat.c [new file with mode: 0644]

diff --git a/miscutils/chat.c b/miscutils/chat.c
new file mode 100644 (file)
index 0000000..4f55738
--- /dev/null
@@ -0,0 +1,443 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * bare bones chat utility
+ * inspired by ppp's chat
+ *
+ * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+#include "libbb.h"
+
+/*
+#define ENABLE_FEATURE_CHAT_NOFAIL              1 // +126 bytes
+#define ENABLE_FEATURE_CHAT_TTY_HIFI            0 // + 70 bytes
+#define ENABLE_FEATURE_CHAT_IMPLICIT_CR         1 // + 44 bytes
+#define ENABLE_FEATURE_CHAT_SEND_ESCAPES        0 // +103 bytes
+#define ENABLE_FEATURE_CHAT_VAR_ABORT_LEN       0 // + 70 bytes
+#define ENABLE_FEATURE_CHAT_CLR_ABORT           0 // +113 bytes
+#define ENABLE_FEATURE_CHAT_SWALLOW_OPTS        0 // + 23 bytes
+*/
+
+// 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
+// N.B> 10 bytes for volatile. Why all these signals?!
+static /*volatile*/ smallint exitcode;
+
+// trap for critical signals
+static void signal_handler(ATTRIBUTE_UNUSED 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, char **argv)
+{
+// should we dump device output? to what fd? by default no.
+// this can be controlled later via ECHO {ON|OFF} chat directive
+//     int echo_fd;
+       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,
+       };
+
+       // 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"
+                       , *argv
+               );
+               if (key >= 0) {
+                       // cache directive value
+                       char *arg = *++argv;
+                       // ON -> 1, anything else -> 0
+                       bool onoff = !strcmp("ON", 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 output
+                               // from stdin (device) to stderr
+                               echo = onoff;
+//TODO?                                echo_fd = onoff * STDERR_FILENO;
+//TODO?                                echo_fd = xopen(arg, O_WRONLY|O_CREAT|O_TRUNC);
+                       } else if (DIR_SAY == key) {
+                               // just print argument verbatim
+                               fprintf(stderr, arg);
+                       }
+                       // next, please!
+                       argv++;
+               // ordinary expect-send pair!
+               } else {
+                       //-----------------------
+                       // do expect
+                       //-----------------------
+                       size_t expect_len, 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 output if ECHO ON or RECORD fname
+//TODO?                                        if (echo_fd > 0) {
+//TODO?                                                full_write(echo_fd, buf+buf_len, 1);
+//TODO?                                        }
+                                       if (echo > 0)
+                                               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
+                       }
+
+                       // 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_open_read_close(buf, NULL);
+                               }
+
+                               // expand escape sequences in command
+                               len = unescape(buf, &nocr);
+
+                               // send command
+#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
+                               pfd.fd = STDOUT_FILENO;
+                               pfd.events = POLLOUT;
+                               while (len && !exitcode
+                                   && poll(&pfd, 1, timeout) > 0
+                                   && (pfd.revents & POLLOUT)
+                               ) {
+                                       // ugly! ugly! ugly!
+                                       // gotta send char by char to achieve this!
+                                       // Brrr...
+                                       // "\\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;
+                                               } else if ('p' == c) {
+                                                       usleep(10000);
+                                                       len--;
+                                                       continue;
+                                               } else if ('K' == c) {
+                                                       tcsendbreak(STDOUT_FILENO, 0);
+                                                       len--;
+                                                       continue;
+                                               } else {
+                                                       buf--;
+                                               }
+                                       }
+                                       if (safe_write(STDOUT_FILENO, buf, 1) > 0) {
+                                               len--;
+                                               buf++;
+                                       } else
+                                               break;
+                               }
+#else
+//                             if (len) {
+                                       alarm(timeout);
+                                       len -= full_write(STDOUT_FILENO, buf, len);
+                                       alarm(0);
+//                             }
+#endif
+
+                               // 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 ENABLE_FEATURE_CHAT_TTY_HIFI
+       tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
+#endif
+
+       return exitcode;
+}