blob: 83aac37de815649712135ad4b75a2b8de5831ae0 [file] [log] [blame]
Denis Vlasenkoe9355082008-02-19 11:35:08 +00001/* 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 *
Denys Vlasenko0ef64bd2010-08-16 20:14:46 +02008 * Licensed under GPLv2, see file LICENSE in this source tree.
Denis Vlasenkoe9355082008-02-19 11:35:08 +00009 */
Denys Vlasenkofb4da162016-11-22 23:14:24 +010010//config:config CHAT
Denys Vlasenkob097a842018-12-28 03:20:17 +010011//config: bool "chat (6.3 kb)"
Denys Vlasenkofb4da162016-11-22 23:14:24 +010012//config: default y
13//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020014//config: Simple chat utility.
Denys Vlasenkofb4da162016-11-22 23:14:24 +010015//config:
16//config:config FEATURE_CHAT_NOFAIL
17//config: bool "Enable NOFAIL expect strings"
18//config: depends on CHAT
19//config: default y
20//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020021//config: When enabled expect strings which are started with a dash trigger
22//config: no-fail mode. That is when expectation is not met within timeout
23//config: the script is not terminated but sends next SEND string and waits
24//config: for next EXPECT string. This allows to compose far more flexible
25//config: scripts.
Denys Vlasenkofb4da162016-11-22 23:14:24 +010026//config:
27//config:config FEATURE_CHAT_TTY_HIFI
28//config: bool "Force STDIN to be a TTY"
29//config: depends on CHAT
30//config: default n
31//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020032//config: Original chat always treats STDIN as a TTY device and sets for it
33//config: so-called raw mode. This option turns on such behaviour.
Denys Vlasenkofb4da162016-11-22 23:14:24 +010034//config:
35//config:config FEATURE_CHAT_IMPLICIT_CR
36//config: bool "Enable implicit Carriage Return"
37//config: depends on CHAT
38//config: default y
39//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020040//config: When enabled make chat to terminate all SEND strings with a "\r"
41//config: unless "\c" is met anywhere in the string.
Denys Vlasenkofb4da162016-11-22 23:14:24 +010042//config:
43//config:config FEATURE_CHAT_SWALLOW_OPTS
44//config: bool "Swallow options"
45//config: depends on CHAT
46//config: default y
47//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020048//config: Busybox chat require no options. To make it not fail when used
49//config: in place of original chat (which has a bunch of options) turn
50//config: this on.
Denys Vlasenkofb4da162016-11-22 23:14:24 +010051//config:
52//config:config FEATURE_CHAT_SEND_ESCAPES
53//config: bool "Support weird SEND escapes"
54//config: depends on CHAT
55//config: default y
56//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020057//config: Original chat uses some escape sequences in SEND arguments which
58//config: are not sent to device but rather performs special actions.
59//config: E.g. "\K" means to send a break sequence to device.
60//config: "\d" delays execution for a second, "\p" -- for a 1/100 of second.
61//config: Before turning this option on think twice: do you really need them?
Denys Vlasenkofb4da162016-11-22 23:14:24 +010062//config:
63//config:config FEATURE_CHAT_VAR_ABORT_LEN
64//config: bool "Support variable-length ABORT conditions"
65//config: depends on CHAT
66//config: default y
67//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020068//config: Original chat uses fixed 50-bytes length ABORT conditions. Say N here.
Denys Vlasenkofb4da162016-11-22 23:14:24 +010069//config:
70//config:config FEATURE_CHAT_CLR_ABORT
71//config: bool "Support revoking of ABORT conditions"
72//config: depends on CHAT
73//config: default y
74//config: help
Denys Vlasenko72089cf2017-07-21 09:50:55 +020075//config: Support CLR_ABORT directive.
Pere Orga5bc8c002011-04-11 03:29:49 +020076
Denys Vlasenkof88e3bf2016-11-22 23:54:17 +010077//applet:IF_CHAT(APPLET(chat, BB_DIR_USR_SBIN, BB_SUID_DROP))
78
79//kbuild:lib-$(CONFIG_CHAT) += chat.o
80
Pere Orga5bc8c002011-04-11 03:29:49 +020081//usage:#define chat_trivial_usage
Denys Vlasenko1f60d882021-06-15 10:00:18 +020082//usage: "EXPECT [SEND [EXPECT [SEND]]...]"
Pere Orga5bc8c002011-04-11 03:29:49 +020083//usage:#define chat_full_usage "\n\n"
84//usage: "Useful for interacting with a modem connected to stdin/stdout.\n"
Denys Vlasenko74c05f52017-08-04 17:36:16 +020085//usage: "A script consists of \"expect-send\" argument pairs.\n"
86//usage: "Example:\n"
Pere Orga5bc8c002011-04-11 03:29:49 +020087//usage: "chat '' ATZ OK ATD123456 CONNECT '' ogin: pppuser word: ppppass '~'"
88
Denis Vlasenkoe9355082008-02-19 11:35:08 +000089#include "libbb.h"
Denys Vlasenkoe6a2f4c2016-04-21 16:26:30 +020090#include "common_bufsiz.h"
Denis Vlasenkoe9355082008-02-19 11:35:08 +000091
Denis Vlasenkoe9355082008-02-19 11:35:08 +000092// default timeout: 45 sec
Denys Vlasenkoe4dcba12010-10-28 18:57:19 +020093#define DEFAULT_CHAT_TIMEOUT 45*1000
Denis Vlasenkoe9355082008-02-19 11:35:08 +000094// max length of "abort string",
95// i.e. device reply which causes termination
96#define MAX_ABORT_LEN 50
97
98// possible exit codes
99enum {
100 ERR_OK = 0, // all's well
101 ERR_MEM, // read too much while expecting
102 ERR_IO, // signalled or I/O error
103 ERR_TIMEOUT, // timed out while expecting
104 ERR_ABORT, // first abort condition was met
105// ERR_ABORT2, // second abort condition was met
106// ...
107};
108
109// exit code
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000110#define exitcode bb_got_signal
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000111
112// trap for critical signals
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000113static void signal_handler(UNUSED_PARAM int signo)
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000114{
115 // report I/O error condition
116 exitcode = ERR_IO;
117}
118
119#if !ENABLE_FEATURE_CHAT_IMPLICIT_CR
120#define unescape(s, nocr) unescape(s)
121#endif
122static size_t unescape(char *s, int *nocr)
123{
124 char *start = s;
125 char *p = s;
126
127 while (*s) {
128 char c = *s;
129 // do we need special processing?
130 // standard escapes + \s for space and \N for \0
131 // \c inhibits terminating \r for commands and is noop for expects
132 if ('\\' == c) {
133 c = *++s;
134 if (c) {
135#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
136 if ('c' == c) {
137 *nocr = 1;
138 goto next;
139 }
140#endif
141 if ('N' == c) {
142 c = '\0';
143 } else if ('s' == c) {
144 c = ' ';
145#if ENABLE_FEATURE_CHAT_NOFAIL
146 // unescape leading dash only
147 // TODO: and only for expect, not command string
148 } else if ('-' == c && (start + 1 == s)) {
149 //c = '-';
150#endif
151 } else {
152 c = bb_process_escape_sequence((const char **)&s);
153 s--;
154 }
155 }
156 // ^A becomes \001, ^B -- \002 and so on...
157 } else if ('^' == c) {
158 c = *++s-'@';
159 }
160 // put unescaped char
161 *p++ = c;
162#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
163 next:
164#endif
165 // next char
166 s++;
167 }
168 *p = '\0';
169
170 return p - start;
171}
172
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000173int chat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000174int chat_main(int argc UNUSED_PARAM, char **argv)
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000175{
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000176 int record_fd = -1;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000177 bool echo = 0;
178 // collection of device replies which cause unconditional termination
179 llist_t *aborts = NULL;
180 // inactivity period
181 int timeout = DEFAULT_CHAT_TIMEOUT;
182 // maximum length of abort string
183#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
184 size_t max_abort_len = 0;
185#else
186#define max_abort_len MAX_ABORT_LEN
187#endif
188#if ENABLE_FEATURE_CHAT_TTY_HIFI
189 struct termios tio0, tio;
190#endif
191 // directive names
192 enum {
193 DIR_HANGUP = 0,
194 DIR_ABORT,
195#if ENABLE_FEATURE_CHAT_CLR_ABORT
196 DIR_CLR_ABORT,
197#endif
198 DIR_TIMEOUT,
199 DIR_ECHO,
200 DIR_SAY,
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000201 DIR_RECORD,
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000202 };
203
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200204#define inbuf bb_common_bufsiz1
205 setup_common_bufsiz();
206
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000207 // make x* functions fail with correct exitcode
208 xfunc_error_retval = ERR_IO;
209
210 // trap vanilla signals to prevent process from being killed suddenly
211 bb_signals(0
212 + (1 << SIGHUP)
213 + (1 << SIGINT)
214 + (1 << SIGTERM)
215 + (1 << SIGPIPE)
216 , signal_handler);
217
218#if ENABLE_FEATURE_CHAT_TTY_HIFI
Denys Vlasenko01ccdd12017-01-11 16:17:59 +0100219//TODO: use set_termios_to_raw()
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000220 tcgetattr(STDIN_FILENO, &tio);
221 tio0 = tio;
222 cfmakeraw(&tio);
223 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio);
224#endif
225
226#if ENABLE_FEATURE_CHAT_SWALLOW_OPTS
227 getopt32(argv, "vVsSE");
228 argv += optind;
229#else
230 argv++; // goto first arg
231#endif
232 // handle chat expect-send pairs
233 while (*argv) {
234 // directive given? process it
235 int key = index_in_strings(
236 "HANGUP\0" "ABORT\0"
237#if ENABLE_FEATURE_CHAT_CLR_ABORT
238 "CLR_ABORT\0"
239#endif
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000240 "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0"
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000241 , *argv
242 );
243 if (key >= 0) {
Denys Vlasenko9d858f52017-09-05 19:16:40 +0200244 bool onoff;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000245 // cache directive value
246 char *arg = *++argv;
Denys Vlasenko9d858f52017-09-05 19:16:40 +0200247
248 if (!arg) {
249#if ENABLE_FEATURE_CHAT_TTY_HIFI
250 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
251#endif
252 bb_show_usage();
253 }
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000254 // OFF -> 0, anything else -> 1
Denys Vlasenko9d858f52017-09-05 19:16:40 +0200255 onoff = (0 != strcmp("OFF", arg));
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000256 // process directive
257 if (DIR_HANGUP == key) {
258 // turn SIGHUP on/off
259 signal(SIGHUP, onoff ? signal_handler : SIG_IGN);
260 } else if (DIR_ABORT == key) {
261 // append the string to abort conditions
262#if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
263 size_t len = strlen(arg);
264 if (len > max_abort_len)
265 max_abort_len = len;
266#endif
267 llist_add_to_end(&aborts, arg);
268#if ENABLE_FEATURE_CHAT_CLR_ABORT
269 } else if (DIR_CLR_ABORT == key) {
Denys Vlasenkoa3aea322010-12-18 01:42:50 +0100270 llist_t *l;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000271 // remove the string from abort conditions
272 // N.B. gotta refresh maximum length too...
Denys Vlasenkoa3aea322010-12-18 01:42:50 +0100273# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000274 max_abort_len = 0;
Denys Vlasenkoa3aea322010-12-18 01:42:50 +0100275# endif
276 for (l = aborts; l; l = l->link) {
277# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000278 size_t len = strlen(l->data);
Denys Vlasenkoa3aea322010-12-18 01:42:50 +0100279# endif
280 if (strcmp(arg, l->data) == 0) {
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000281 llist_unlink(&aborts, l);
282 continue;
283 }
Denys Vlasenkoa3aea322010-12-18 01:42:50 +0100284# if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000285 if (len > max_abort_len)
286 max_abort_len = len;
Denys Vlasenkoa3aea322010-12-18 01:42:50 +0100287# endif
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000288 }
289#endif
290 } else if (DIR_TIMEOUT == key) {
291 // set new timeout
292 // -1 means OFF
293 timeout = atoi(arg) * 1000;
294 // 0 means default
295 // >0 means value in msecs
296 if (!timeout)
297 timeout = DEFAULT_CHAT_TIMEOUT;
298 } else if (DIR_ECHO == key) {
299 // turn echo on/off
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000300 // N.B. echo means dumping device input/output to stderr
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000301 echo = onoff;
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000302 } else if (DIR_RECORD == key) {
303 // turn record on/off
304 // N.B. record means dumping device input to a file
305 // close previous record_fd
306 if (record_fd > 0)
307 close(record_fd);
308 // N.B. do we have to die here on open error?
309 record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000310 } else if (DIR_SAY == key) {
311 // just print argument verbatim
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000312 // TODO: should we use full_write() to avoid unistd/stdio conflict?
James Byrne69374872019-07-02 11:35:03 +0200313 bb_simple_error_msg(arg);
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000314 }
315 // next, please!
316 argv++;
317 // ordinary expect-send pair!
318 } else {
319 //-----------------------
320 // do expect
321 //-----------------------
Denis Vlasenko85c24712008-03-17 09:04:04 +0000322 int expect_len;
323 size_t buf_len = 0;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000324 size_t max_len = max_abort_len;
325
326 struct pollfd pfd;
327#if ENABLE_FEATURE_CHAT_NOFAIL
328 int nofail = 0;
329#endif
330 char *expect = *argv++;
331
332 // sanity check: shall we really expect something?
333 if (!expect)
334 goto expect_done;
335
336#if ENABLE_FEATURE_CHAT_NOFAIL
337 // if expect starts with -
338 if ('-' == *expect) {
339 // swallow -
340 expect++;
341 // and enter nofail mode
342 nofail++;
343 }
344#endif
345
346#ifdef ___TEST___BUF___ // test behaviour with a small buffer
347# undef COMMON_BUFSIZE
348# define COMMON_BUFSIZE 6
349#endif
350 // expand escape sequences in expect
351 expect_len = unescape(expect, &expect_len /*dummy*/);
352 if (expect_len > max_len)
353 max_len = expect_len;
354 // sanity check:
355 // we should expect more than nothing but not more than input buffer
356 // TODO: later we'll get rid of fixed-size buffer
357 if (!expect_len)
358 goto expect_done;
359 if (max_len >= COMMON_BUFSIZE) {
360 exitcode = ERR_MEM;
361 goto expect_done;
362 }
363
364 // get reply
365 pfd.fd = STDIN_FILENO;
366 pfd.events = POLLIN;
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200367 while (exitcode == ERR_OK
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000368 && poll(&pfd, 1, timeout) > 0
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200369 /* && (pfd.revents & POLLIN) - may be untrue (e.g. only POLLERR set) */
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000370 ) {
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000371 llist_t *l;
372 ssize_t delta;
373
374 // read next char from device
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200375 if (safe_read(STDIN_FILENO, inbuf + buf_len, 1) <= 0) {
376 exitcode = ERR_IO;
377 goto expect_done;
378 }
379
380 // dump device input if RECORD fname
381 if (record_fd > 0) {
382 full_write(record_fd, inbuf + buf_len, 1);
383 }
384 // dump device input if ECHO ON
385 if (echo) {
386// if (inbuf[buf_len] < ' ') {
387// full_write2_str("^");
388// inbuf[buf_len] += '@';
389// }
390 full_write(STDERR_FILENO, inbuf + buf_len, 1);
391 }
392 buf_len++;
393 // move input frame if we've reached higher bound
394 if (buf_len > COMMON_BUFSIZE) {
395 memmove(inbuf, inbuf + buf_len - max_len, max_len);
396 buf_len = max_len;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000397 }
398 // N.B. rule of thumb: values being looked for can
399 // be found only at the end of input buffer
400 // this allows to get rid of strstr() and memmem()
401
402 // TODO: make expect and abort strings processed uniformly
403 // abort condition is met? -> bail out
404 for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) {
405 size_t len = strlen(l->data);
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200406 delta = buf_len - len;
407 if (delta >= 0 && !memcmp(inbuf + delta, l->data, len))
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000408 goto expect_done;
409 }
410 exitcode = ERR_OK;
411
412 // expected reply received? -> goto next command
Denis Vlasenko85c24712008-03-17 09:04:04 +0000413 delta = buf_len - expect_len;
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200414 if (delta >= 0 && memcmp(inbuf + delta, expect, expect_len) == 0)
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000415 goto expect_done;
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000416 } /* while (have data) */
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000417
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200418 // device timed out, or unexpected reply received,
419 // or we got a signal (poll() returned -1 with EINTR).
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000420 exitcode = ERR_TIMEOUT;
421 expect_done:
422#if ENABLE_FEATURE_CHAT_NOFAIL
423 // on success and when in nofail mode
424 // we should skip following subsend-subexpect pairs
425 if (nofail) {
426 if (!exitcode) {
427 // find last send before non-dashed expect
428 while (*argv && argv[1] && '-' == argv[1][0])
429 argv += 2;
430 // skip the pair
431 // N.B. do we really need this?!
432 if (!*argv++ || !*argv++)
433 break;
434 }
435 // nofail mode also clears all but IO errors (or signals)
436 if (ERR_IO != exitcode)
437 exitcode = ERR_OK;
438 }
439#endif
440 // bail out unless we expected successfully
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200441 if (exitcode != ERR_OK)
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000442 break;
443
444 //-----------------------
445 // do send
446 //-----------------------
447 if (*argv) {
448#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
449 int nocr = 0; // inhibit terminating command with \r
450#endif
451 char *loaded = NULL; // loaded command
452 size_t len;
453 char *buf = *argv++;
454
455 // if command starts with @
456 // load "real" command from file named after @
457 if ('@' == *buf) {
458 // skip the @ and any following white-space
459 trim(++buf);
Denis Vlasenkof3745ea2008-04-19 19:32:08 +0000460 buf = loaded = xmalloc_xopen_read_close(buf, NULL);
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000461 }
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000462 // expand escape sequences in command
463 len = unescape(buf, &nocr);
464
465 // send command
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000466 alarm(timeout);
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000467 pfd.fd = STDOUT_FILENO;
468 pfd.events = POLLOUT;
469 while (len && !exitcode
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000470 && poll(&pfd, 1, -1) > 0
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000471 && (pfd.revents & POLLOUT)
472 ) {
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000473#if ENABLE_FEATURE_CHAT_SEND_ESCAPES
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000474 // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay
475 // "\\K" means send BREAK
476 char c = *buf;
477 if ('\\' == c) {
478 c = *++buf;
479 if ('d' == c) {
Denys Vlasenkoec16c032020-11-29 11:37:34 +0100480 sleep1();
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000481 len--;
482 continue;
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000483 }
484 if ('p' == c) {
Denys Vlasenko4b032a42021-09-06 17:38:18 +0200485 msleep(10);
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000486 len--;
487 continue;
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000488 }
489 if ('K' == c) {
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000490 tcsendbreak(STDOUT_FILENO, 0);
491 len--;
492 continue;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000493 }
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000494 buf--;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000495 }
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000496 if (safe_write(STDOUT_FILENO, buf, 1) != 1)
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000497 break;
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000498 len--;
499 buf++;
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000500#else
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000501 len -= full_write(STDOUT_FILENO, buf, len);
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000502#endif
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000503 } /* while (can write) */
504 alarm(0);
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000505
506 // report I/O error if there still exists at least one non-sent char
507 if (len)
508 exitcode = ERR_IO;
509
510 // free loaded command (if any)
511 if (loaded)
512 free(loaded);
513#if ENABLE_FEATURE_CHAT_IMPLICIT_CR
514 // or terminate command with \r (if not inhibited)
515 else if (!nocr)
Denys Vlasenkob86a9ed2020-11-29 11:49:37 +0100516 xwrite_str(STDOUT_FILENO, "\r");
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000517#endif
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000518 // bail out unless we sent command successfully
519 if (exitcode)
520 break;
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000521 } /* if (*argv) */
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000522 }
Denis Vlasenko99d71da2009-03-16 23:06:23 +0000523 } /* while (*argv) */
Denis Vlasenkoe9355082008-02-19 11:35:08 +0000524
525#if ENABLE_FEATURE_CHAT_TTY_HIFI
526 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0);
527#endif
528
529 return exitcode;
530}