source: MondoRescue/branches/3.3/mindi-busybox/miscutils/chat.c@ 3865

Last change on this file since 3865 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

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