Magellan Linux

Annotation of /tags/mkinitrd-6_1_11/busybox/miscutils/chat.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 928 - (hide annotations) (download)
Wed Oct 28 13:31:19 2009 UTC (14 years, 11 months ago) by niro
File MIME type: text/plain
File size: 11114 byte(s)
tagged 'mkinitrd-6_1_11'
1 niro 816 /* 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 tarball for details.
9     */
10     #include "libbb.h"
11    
12     /*
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
20     */
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
29     enum {
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     // N.B> 10 bytes for volatile. Why all these signals?!
41     static /*volatile*/ smallint exitcode;
42    
43     // trap for critical signals
44     static void signal_handler(UNUSED_PARAM int signo)
45     {
46     // report I/O error condition
47     exitcode = ERR_IO;
48     }
49    
50     #if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
51     #define unescape(s, nocr) unescape(s)
52     #endif
53     static size_t unescape(char *s, int *nocr)
54     {
55     char *start = s;
56     char *p = s;
57    
58     while (*s) {
59     char c = *s;
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
63     if ('\\' == c) {
64     c = *++s;
65     if (c) {
66     #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
67     if ('c' == c) {
68     *nocr = 1;
69     goto next;
70     }
71     #endif
72     if ('N' == c) {
73     c = '\0';
74     } else if ('s' == c) {
75     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)) {
80     //c = '-';
81     #endif
82     } else {
83     c = bb_process_escape_sequence((const char **)&s);
84     s--;
85     }
86     }
87     // ^A becomes \001, ^B -- \002 and so on...
88     } else if ('^' == c) {
89     c = *++s-'@';
90     }
91     // put unescaped char
92     *p++ = c;
93     #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
94     next:
95     #endif
96     // next char
97     s++;
98     }
99     *p = '\0';
100    
101     return p - start;
102     }
103    
104    
105     int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
106     int chat_main(int argc UNUSED_PARAM, char **argv)
107     {
108     // should we dump device output? to what fd? by default no.
109     // this can be controlled later via ECHO {ON|OFF} chat directive
110     // int echo_fd;
111     bool echo = 0;
112     // collection of device replies which cause unconditional termination
113     llist_t *aborts = NULL;
114     // inactivity period
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;
119     #else
120     #define max_abort_len MAX_ABORT_LEN
121     #endif
122     #if ENABLE_FEATURE_CHAT_TTY_HIFI
123     struct termios tio0, tio;
124     #endif
125     // directive names
126     enum {
127     DIR_HANGUP = 0,
128     DIR_ABORT,
129     #if ENABLE_FEATURE_CHAT_CLR_ABORT
130     DIR_CLR_ABORT,
131     #endif
132     DIR_TIMEOUT,
133     DIR_ECHO,
134     DIR_SAY,
135     };
136    
137     // make x* functions fail with correct exitcode
138     xfunc_error_retval = ERR_IO;
139    
140     // trap vanilla signals to prevent process from being killed suddenly
141     bb_signals(0
142     + (1 << SIGHUP)
143     + (1 << SIGINT)
144     + (1 << SIGTERM)
145     + (1 << SIGPIPE)
146     , signal_handler);
147    
148     #if ENABLE_FEATURE_CHAT_TTY_HIFI
149     tcgetattr(STDIN_FILENO, &tio);
150     tio0 = tio;
151     cfmakeraw(&tio);
152     tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
153     #endif
154    
155     #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
156     getopt32(argv, "vVsSE");
157     argv += optind;
158     #else
159     argv++; // goto first arg
160     #endif
161     // handle chat expect-send pairs
162     while (*argv) {
163     // directive given? process it
164     int key = index_in_strings(
165     "HANGUP\0" "ABORT\0"
166     #if ENABLE_FEATURE_CHAT_CLR_ABORT
167     "CLR_ABORT\0"
168     #endif
169     "TIMEOUT\0" "ECHO\0" "SAY\0"
170     , *argv
171     );
172     if (key >= 0) {
173     // cache directive value
174     char *arg = *++argv;
175     // ON -> 1, anything else -> 0
176     bool onoff = !strcmp("ON", arg);
177     // process directive
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)
186     max_abort_len = len;
187     #endif
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
194     max_abort_len = 0;
195     #endif
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);
199     #endif
200     if (!strcmp(arg, l->data)) {
201     llist_unlink(&aborts, l);
202     continue;
203     }
204     #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
205     if (len > max_abort_len)
206     max_abort_len = len;
207     #endif
208     }
209     #endif
210     } else if (DIR_TIMEOUT == key) {
211     // set new timeout
212     // -1 means OFF
213     timeout = atoi(arg) * 1000;
214     // 0 means default
215     // >0 means value in msecs
216     if (!timeout)
217     timeout = DEFAULT_CHAT_TIMEOUT;
218     } else if (DIR_ECHO == key) {
219     // turn echo on/off
220     // N.B. echo means dumping output
221     // from stdin (device) to stderr
222     echo = onoff;
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);
228     }
229     // next, please!
230     argv++;
231     // ordinary expect-send pair!
232     } else {
233     //-----------------------
234     // do expect
235     //-----------------------
236     int expect_len;
237     size_t buf_len = 0;
238     size_t max_len = max_abort_len;
239    
240     struct pollfd pfd;
241     #if ENABLE_FEATURE_CHAT_NOFAIL
242     int nofail = 0;
243     #endif
244     char *expect = *argv++;
245    
246     // sanity check: shall we really expect something?
247     if (!expect)
248     goto expect_done;
249    
250     #if ENABLE_FEATURE_CHAT_NOFAIL
251     // if expect starts with -
252     if ('-' == *expect) {
253     // swallow -
254     expect++;
255     // and enter nofail mode
256     nofail++;
257     }
258     #endif
259    
260     #ifdef ___TEST___BUF___ // test behaviour with a small buffer
261     # undef COMMON_BUFSIZE
262     # define COMMON_BUFSIZE 6
263     #endif
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;
268     // sanity check:
269     // we should expect more than nothing but not more than input buffer
270     // TODO: later we'll get rid of fixed-size buffer
271     if (!expect_len)
272     goto expect_done;
273     if (max_len >= COMMON_BUFSIZE) {
274     exitcode = ERR_MEM;
275     goto expect_done;
276     }
277    
278     // get reply
279     pfd.fd = STDIN_FILENO;
280     pfd.events = POLLIN;
281     while (!exitcode
282     && poll(&pfd, 1, timeout) > 0
283     && (pfd.revents & POLLIN)
284     ) {
285     #define buf bb_common_bufsiz1
286     llist_t *l;
287     ssize_t delta;
288    
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);
294     //TODO? }
295     if (echo > 0)
296     full_write(STDERR_FILENO, buf+buf_len, 1);
297     buf_len++;
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);
301     buf_len = max_len;
302     }
303     }
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()
307    
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);
312     delta = buf_len-len;
313     if (delta >= 0 && !memcmp(buf+delta, l->data, len))
314     goto expect_done;
315     }
316     exitcode = ERR_OK;
317    
318     // expected reply received? -> goto next command
319     delta = buf_len - expect_len;
320     if (delta >= 0 && !memcmp(buf+delta, expect, expect_len))
321     goto expect_done;
322     #undef buf
323     }
324    
325     // device timed out or unexpected reply received
326     exitcode = ERR_TIMEOUT;
327     expect_done:
328     #if ENABLE_FEATURE_CHAT_NOFAIL
329     // on success and when in nofail mode
330     // we should skip following subsend-subexpect pairs
331     if (nofail) {
332     if (!exitcode) {
333     // find last send before non-dashed expect
334     while (*argv && argv[1] && '-' == argv[1][0])
335     argv += 2;
336     // skip the pair
337     // N.B. do we really need this?!
338     if (!*argv++ || !*argv++)
339     break;
340     }
341     // nofail mode also clears all but IO errors (or signals)
342     if (ERR_IO != exitcode)
343     exitcode = ERR_OK;
344     }
345     #endif
346     // bail out unless we expected successfully
347     if (exitcode)
348     break;
349    
350     //-----------------------
351     // do send
352     //-----------------------
353     if (*argv) {
354     #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
355     int nocr = 0; // inhibit terminating command with \r
356     #endif
357     char *loaded = NULL; // loaded command
358     size_t len;
359     char *buf = *argv++;
360    
361     // if command starts with @
362     // load "real" command from file named after @
363     if ('@' == *buf) {
364     // skip the @ and any following white-space
365     trim(++buf);
366     buf = loaded = xmalloc_xopen_read_close(buf, NULL);
367     }
368    
369     // expand escape sequences in command
370     len = unescape(buf, &nocr);
371    
372     // send command
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)
379     ) {
380     // ugly! ugly! ugly!
381     // gotta send char by char to achieve this!
382     // Brrr...
383     // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
384     // "\\K" means send BREAK
385     char c = *buf;
386     if ('\\' == c) {
387     c = *++buf;
388     if ('d' == c) {
389     sleep(1);
390     len--;
391     continue;
392     } else if ('p' == c) {
393     usleep(10000);
394     len--;
395     continue;
396     } else if ('K' == c) {
397     tcsendbreak(STDOUT_FILENO, 0);
398     len--;
399     continue;
400     } else {
401     buf--;
402     }
403     }
404     if (safe_write(STDOUT_FILENO, buf, 1) > 0) {
405     len--;
406     buf++;
407     } else
408     break;
409     }
410     #else
411     // if (len) {
412     alarm(timeout);
413     len -= full_write(STDOUT_FILENO, buf, len);
414     alarm(0);
415     // }
416     #endif
417    
418     // report I/O error if there still exists at least one non-sent char
419     if (len)
420     exitcode = ERR_IO;
421    
422     // free loaded command (if any)
423     if (loaded)
424     free(loaded);
425     #if ENABLE_FEATURE_CHAT_IMPLICIT_CR
426     // or terminate command with \r (if not inhibited)
427     else if (!nocr)
428     xwrite(STDOUT_FILENO, "\r", 1);
429     #endif
430    
431     // bail out unless we sent command successfully
432     if (exitcode)
433     break;
434    
435     }
436     }
437     }
438    
439     #if ENABLE_FEATURE_CHAT_TTY_HIFI
440     tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
441     #endif
442    
443     return exitcode;
444     }