3
0

chat.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. /* vi: set sw=4 ts=4: */
  2. /*
  3. * bare bones chat utility
  4. * inspired by ppp's chat
  5. *
  6. * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
  7. *
  8. * Licensed under GPLv2, see file LICENSE in this source tree.
  9. */
  10. #include "libbb.h"
  11. // default timeout: 45 sec
  12. #define DEFAULT_CHAT_TIMEOUT 45*1000
  13. // max length of "abort string",
  14. // i.e. device reply which causes termination
  15. #define MAX_ABORT_LEN 50
  16. // possible exit codes
  17. enum {
  18. ERR_OK = 0, // all's well
  19. ERR_MEM, // read too much while expecting
  20. ERR_IO, // signalled or I/O error
  21. ERR_TIMEOUT, // timed out while expecting
  22. ERR_ABORT, // first abort condition was met
  23. // ERR_ABORT2, // second abort condition was met
  24. // ...
  25. };
  26. // exit code
  27. #define exitcode bb_got_signal
  28. // trap for critical signals
  29. static void signal_handler(UNUSED_PARAM int signo)
  30. {
  31. // report I/O error condition
  32. exitcode = ERR_IO;
  33. }
  34. #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
  35. #define unescape(s, nocr) unescape(s)
  36. #endif
  37. static size_t unescape(char *s, int *nocr)
  38. {
  39. char *start = s;
  40. char *p = s;
  41. while (*s) {
  42. char c = *s;
  43. // do we need special processing?
  44. // standard escapes + \s for space and \N for \0
  45. // \c inhibits terminating \r for commands and is noop for expects
  46. if ('\\' == c) {
  47. c = *++s;
  48. if (c) {
  49. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  50. if ('c' == c) {
  51. *nocr = 1;
  52. goto next;
  53. }
  54. #endif
  55. if ('N' == c) {
  56. c = '\0';
  57. } else if ('s' == c) {
  58. c = ' ';
  59. #if ENABLE_FEATURE_CHAT_NOFAIL
  60. // unescape leading dash only
  61. // TODO: and only for expect, not command string
  62. } else if ('-' == c && (start + 1 == s)) {
  63. //c = '-';
  64. #endif
  65. } else {
  66. c = bb_process_escape_sequence((const char **)&s);
  67. s--;
  68. }
  69. }
  70. // ^A becomes \001, ^B -- \002 and so on...
  71. } else if ('^' == c) {
  72. c = *++s-'@';
  73. }
  74. // put unescaped char
  75. *p++ = c;
  76. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  77. next:
  78. #endif
  79. // next char
  80. s++;
  81. }
  82. *p = '\0';
  83. return p - start;
  84. }
  85. int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  86. int chat_main(int argc UNUSED_PARAM, char **argv)
  87. {
  88. int record_fd = -1;
  89. bool echo = 0;
  90. // collection of device replies which cause unconditional termination
  91. llist_t *aborts = NULL;
  92. // inactivity period
  93. int timeout = DEFAULT_CHAT_TIMEOUT;
  94. // maximum length of abort string
  95. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  96. size_t max_abort_len = 0;
  97. #else
  98. #define max_abort_len MAX_ABORT_LEN
  99. #endif
  100. #if ENABLE_FEATURE_CHAT_TTY_HIFI
  101. struct termios tio0, tio;
  102. #endif
  103. // directive names
  104. enum {
  105. DIR_HANGUP = 0,
  106. DIR_ABORT,
  107. #if ENABLE_FEATURE_CHAT_CLR_ABORT
  108. DIR_CLR_ABORT,
  109. #endif
  110. DIR_TIMEOUT,
  111. DIR_ECHO,
  112. DIR_SAY,
  113. DIR_RECORD,
  114. };
  115. // make x* functions fail with correct exitcode
  116. xfunc_error_retval = ERR_IO;
  117. // trap vanilla signals to prevent process from being killed suddenly
  118. bb_signals(0
  119. + (1 << SIGHUP)
  120. + (1 << SIGINT)
  121. + (1 << SIGTERM)
  122. + (1 << SIGPIPE)
  123. , signal_handler);
  124. #if ENABLE_FEATURE_CHAT_TTY_HIFI
  125. tcgetattr(STDIN_FILENO, &tio);
  126. tio0 = tio;
  127. cfmakeraw(&tio);
  128. tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
  129. #endif
  130. #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
  131. getopt32(argv, "vVsSE");
  132. argv += optind;
  133. #else
  134. argv++; // goto first arg
  135. #endif
  136. // handle chat expect-send pairs
  137. while (*argv) {
  138. // directive given? process it
  139. int key = index_in_strings(
  140. "HANGUP\0" "ABORT\0"
  141. #if ENABLE_FEATURE_CHAT_CLR_ABORT
  142. "CLR_ABORT\0"
  143. #endif
  144. "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
  145. , *argv
  146. );
  147. if (key >= 0) {
  148. // cache directive value
  149. char *arg = *++argv;
  150. // OFF -> 0, anything else -> 1
  151. bool onoff = (0 != strcmp("OFF", arg));
  152. // process directive
  153. if (DIR_HANGUP == key) {
  154. // turn SIGHUP on/off
  155. signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
  156. } else if (DIR_ABORT == key) {
  157. // append the string to abort conditions
  158. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  159. size_t len = strlen(arg);
  160. if (len > max_abort_len)
  161. max_abort_len = len;
  162. #endif
  163. llist_add_to_end(&aborts, arg);
  164. #if ENABLE_FEATURE_CHAT_CLR_ABORT
  165. } else if (DIR_CLR_ABORT == key) {
  166. // remove the string from abort conditions
  167. // N.B. gotta refresh maximum length too...
  168. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  169. max_abort_len = 0;
  170. #endif
  171. for (llist_t *l = aborts; l; l = l->link) {
  172. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  173. size_t len = strlen(l->data);
  174. #endif
  175. if (!strcmp(arg, l->data)) {
  176. llist_unlink(&aborts, l);
  177. continue;
  178. }
  179. #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
  180. if (len > max_abort_len)
  181. max_abort_len = len;
  182. #endif
  183. }
  184. #endif
  185. } else if (DIR_TIMEOUT == key) {
  186. // set new timeout
  187. // -1 means OFF
  188. timeout = atoi(arg) * 1000;
  189. // 0 means default
  190. // >0 means value in msecs
  191. if (!timeout)
  192. timeout = DEFAULT_CHAT_TIMEOUT;
  193. } else if (DIR_ECHO == key) {
  194. // turn echo on/off
  195. // N.B. echo means dumping device input/output to stderr
  196. echo = onoff;
  197. } else if (DIR_RECORD == key) {
  198. // turn record on/off
  199. // N.B. record means dumping device input to a file
  200. // close previous record_fd
  201. if (record_fd > 0)
  202. close(record_fd);
  203. // N.B. do we have to die here on open error?
  204. record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
  205. } else if (DIR_SAY == key) {
  206. // just print argument verbatim
  207. // TODO: should we use full_write() to avoid unistd/stdio conflict?
  208. bb_error_msg("%s", arg);
  209. }
  210. // next, please!
  211. argv++;
  212. // ordinary expect-send pair!
  213. } else {
  214. //-----------------------
  215. // do expect
  216. //-----------------------
  217. int expect_len;
  218. size_t buf_len = 0;
  219. size_t max_len = max_abort_len;
  220. struct pollfd pfd;
  221. #if ENABLE_FEATURE_CHAT_NOFAIL
  222. int nofail = 0;
  223. #endif
  224. char *expect = *argv++;
  225. // sanity check: shall we really expect something?
  226. if (!expect)
  227. goto expect_done;
  228. #if ENABLE_FEATURE_CHAT_NOFAIL
  229. // if expect starts with -
  230. if ('-' == *expect) {
  231. // swallow -
  232. expect++;
  233. // and enter nofail mode
  234. nofail++;
  235. }
  236. #endif
  237. #ifdef ___TEST___BUF___ // test behaviour with a small buffer
  238. # undef COMMON_BUFSIZE
  239. # define COMMON_BUFSIZE 6
  240. #endif
  241. // expand escape sequences in expect
  242. expect_len = unescape(expect, &expect_len /*dummy*/);
  243. if (expect_len > max_len)
  244. max_len = expect_len;
  245. // sanity check:
  246. // we should expect more than nothing but not more than input buffer
  247. // TODO: later we'll get rid of fixed-size buffer
  248. if (!expect_len)
  249. goto expect_done;
  250. if (max_len >= COMMON_BUFSIZE) {
  251. exitcode = ERR_MEM;
  252. goto expect_done;
  253. }
  254. // get reply
  255. pfd.fd = STDIN_FILENO;
  256. pfd.events = POLLIN;
  257. while (!exitcode
  258. && poll(&pfd, 1, timeout) > 0
  259. && (pfd.revents & POLLIN)
  260. ) {
  261. #define buf bb_common_bufsiz1
  262. llist_t *l;
  263. ssize_t delta;
  264. // read next char from device
  265. if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) {
  266. // dump device input if RECORD fname
  267. if (record_fd > 0) {
  268. full_write(record_fd, buf+buf_len, 1);
  269. }
  270. // dump device input if ECHO ON
  271. if (echo > 0) {
  272. // if (buf[buf_len] < ' ') {
  273. // full_write(STDERR_FILENO, "^", 1);
  274. // buf[buf_len] += '@';
  275. // }
  276. full_write(STDERR_FILENO, buf+buf_len, 1);
  277. }
  278. buf_len++;
  279. // move input frame if we've reached higher bound
  280. if (buf_len > COMMON_BUFSIZE) {
  281. memmove(buf, buf+buf_len-max_len, max_len);
  282. buf_len = max_len;
  283. }
  284. }
  285. // N.B. rule of thumb: values being looked for can
  286. // be found only at the end of input buffer
  287. // this allows to get rid of strstr() and memmem()
  288. // TODO: make expect and abort strings processed uniformly
  289. // abort condition is met? -> bail out
  290. for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
  291. size_t len = strlen(l->data);
  292. delta = buf_len-len;
  293. if (delta >= 0 && !memcmp(buf+delta, l->data, len))
  294. goto expect_done;
  295. }
  296. exitcode = ERR_OK;
  297. // expected reply received? -> goto next command
  298. delta = buf_len - expect_len;
  299. if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
  300. goto expect_done;
  301. #undef buf
  302. } /* while (have data) */
  303. // device timed out or unexpected reply received
  304. exitcode = ERR_TIMEOUT;
  305. expect_done:
  306. #if ENABLE_FEATURE_CHAT_NOFAIL
  307. // on success and when in nofail mode
  308. // we should skip following subsend-subexpect pairs
  309. if (nofail) {
  310. if (!exitcode) {
  311. // find last send before non-dashed expect
  312. while (*argv && argv[1] && '-' == argv[1][0])
  313. argv += 2;
  314. // skip the pair
  315. // N.B. do we really need this?!
  316. if (!*argv++ || !*argv++)
  317. break;
  318. }
  319. // nofail mode also clears all but IO errors (or signals)
  320. if (ERR_IO != exitcode)
  321. exitcode = ERR_OK;
  322. }
  323. #endif
  324. // bail out unless we expected successfully
  325. if (exitcode)
  326. break;
  327. //-----------------------
  328. // do send
  329. //-----------------------
  330. if (*argv) {
  331. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  332. int nocr = 0; // inhibit terminating command with \r
  333. #endif
  334. char *loaded = NULL; // loaded command
  335. size_t len;
  336. char *buf = *argv++;
  337. // if command starts with @
  338. // load "real" command from file named after @
  339. if ('@' == *buf) {
  340. // skip the @ and any following white-space
  341. trim(++buf);
  342. buf = loaded = xmalloc_xopen_read_close(buf, NULL);
  343. }
  344. // expand escape sequences in command
  345. len = unescape(buf, &nocr);
  346. // send command
  347. alarm(timeout);
  348. pfd.fd = STDOUT_FILENO;
  349. pfd.events = POLLOUT;
  350. while (len && !exitcode
  351. && poll(&pfd, 1, -1) > 0
  352. && (pfd.revents & POLLOUT)
  353. ) {
  354. #if ENABLE_FEATURE_CHAT_SEND_ESCAPES
  355. // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
  356. // "\\K" means send BREAK
  357. char c = *buf;
  358. if ('\\' == c) {
  359. c = *++buf;
  360. if ('d' == c) {
  361. sleep(1);
  362. len--;
  363. continue;
  364. }
  365. if ('p' == c) {
  366. usleep(10000);
  367. len--;
  368. continue;
  369. }
  370. if ('K' == c) {
  371. tcsendbreak(STDOUT_FILENO, 0);
  372. len--;
  373. continue;
  374. }
  375. buf--;
  376. }
  377. if (safe_write(STDOUT_FILENO, buf, 1) != 1)
  378. break;
  379. len--;
  380. buf++;
  381. #else
  382. len -= full_write(STDOUT_FILENO, buf, len);
  383. #endif
  384. } /* while (can write) */
  385. alarm(0);
  386. // report I/O error if there still exists at least one non-sent char
  387. if (len)
  388. exitcode = ERR_IO;
  389. // free loaded command (if any)
  390. if (loaded)
  391. free(loaded);
  392. #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
  393. // or terminate command with \r (if not inhibited)
  394. else if (!nocr)
  395. xwrite(STDOUT_FILENO, "\r", 1);
  396. #endif
  397. // bail out unless we sent command successfully
  398. if (exitcode)
  399. break;
  400. } /* if (*argv) */
  401. }
  402. } /* while (*argv) */
  403. #if ENABLE_FEATURE_CHAT_TTY_HIFI
  404. tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
  405. #endif
  406. return exitcode;
  407. }