From 7d75a96b15327050a294b1f54981781ce6e551e2 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Thu, 22 Nov 2007 08:16:57 +0000 Subject: ash: fix bug where redirection of closed fd was leaving it open afterwards. redirect 983 1024 +41 bb_echo 276 301 +25 popredir 118 132 +14 evalcommand 1163 1176 +13 bbunpack 358 366 +8 echocmd 13 5 -8 echo_main 13 5 -8 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 5/2 up/down: 101/-16) Total: 85 bytes text data bss dec hex filename 774999 962 9236 785197 bfb2d busybox_old 775084 962 9236 785282 bfb82 busybox_unstripped --- coreutils/echo.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) (limited to 'coreutils') diff --git a/coreutils/echo.c b/coreutils/echo.c index 860853f02..4c7a91743 100644 --- a/coreutils/echo.c +++ b/coreutils/echo.c @@ -25,7 +25,9 @@ #include "libbb.h" -int bb_echo(char **argv) +/* argc is unused, but removing it precludes compiler from + * using call -> jump optimization */ +int bb_echo(int argc, char **argv) { const char *arg; #if !ENABLE_FEATURE_FANCY_ECHO @@ -33,6 +35,18 @@ int bb_echo(char **argv) eflag = '\\', nflag = 1, /* 1 -- print '\n' */ }; + + /* We must check that stdout is not closed. + * The reason for this is highly non-obvious. + * bb_echo is used from shell. Shell must correctly handle "echo foo" + * if stdout is closed. With stdio, output gets shoveled into + * stdout buffer, and even fflush cannot clear it out. It seems that + * even if libc receives EBADF on write attempts, it feels determined + * to output data no matter what. So it will try later, + * and possibly will clobber future output. Not good. */ + if (dup2(1, 1) != 1) + return -1; + arg = *++argv; if (!arg) goto newline_ret; @@ -41,6 +55,10 @@ int bb_echo(char **argv) char nflag = 1; char eflag = 0; + /* We must check that stdout is not closed. */ + if (dup2(1, 1) != 1) + return -1; + while (1) { arg = *++argv; if (!arg) @@ -122,7 +140,7 @@ int bb_echo(char **argv) int echo_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int echo_main(int argc, char **argv) { - return bb_echo(argv); + return bb_echo(argc, argv); } /*- @@ -163,3 +181,122 @@ int echo_main(int argc, char **argv) * * @(#)echo.c 8.1 (Berkeley) 5/31/93 */ + +#ifdef VERSION_WITH_WRITEV +/* We can't use stdio. + * The reason for this is highly non-obvious. + * bb_echo is used from shell. Shell must correctly handle "echo foo" + * if stdout is closed. With stdio, output gets shoveled into + * stdout buffer, and even fflush cannot clear it out. It seems that + * even if libc receives EBADF on write attempts, it feels determined + * to output data no matter what. So it will try later, + * and possibly will clobber future output. Not good. + * + * Using writev instead, with 'direct' conversion of argv vector. + */ + +int bb_echo(int argc, char **argv) +{ + struct iovec io[argc]; + struct iovec *cur_io = io; + char *arg; + char *p; +#if !ENABLE_FEATURE_FANCY_ECHO + enum { + eflag = '\\', + nflag = 1, /* 1 -- print '\n' */ + }; + arg = *++argv; + if (!arg) + goto newline_ret; +#else + char nflag = 1; + char eflag = 0; + + while (1) { + arg = *++argv; + if (!arg) + goto newline_ret; + if (*arg != '-') + break; + + /* If it appears that we are handling options, then make sure + * that all of the options specified are actually valid. + * Otherwise, the string should just be echoed. + */ + p = arg + 1; + if (!*p) /* A single '-', so echo it. */ + goto just_echo; + + do { + if (!strrchr("neE", *p)) + goto just_echo; + } while (*++p); + + /* All of the options in this arg are valid, so handle them. */ + p = arg + 1; + do { + if (*p == 'n') + nflag = 0; + if (*p == 'e') + eflag = '\\'; + } while (*++p); + } + just_echo: +#endif + + while (1) { + /* arg is already == *argv and isn't NULL */ + int c; + + cur_io->iov_base = p = arg; + + if (!eflag) { + /* optimization for very common case */ + p += strlen(arg); + } else while ((c = *arg++)) { + if (c == eflag) { /* Check for escape seq. */ + if (*arg == 'c') { + /* '\c' means cancel newline and + * ignore all subsequent chars. */ + cur_io->iov_len = p - (char*)cur_io->iov_base; + cur_io++; + goto ret; + } +#if !ENABLE_FEATURE_FANCY_ECHO + /* SUSv3 specifies that octal escapes must begin with '0'. */ + if ( (((unsigned char)*arg) - '1') >= 7) +#endif + { + /* Since SUSv3 mandates a first digit of 0, 4-digit octals + * of the form \0### are accepted. */ + if (*arg == '0' && ((unsigned char)(arg[1]) - '0') < 8) { + arg++; + } + /* bb_process_escape_sequence can handle nul correctly */ + c = bb_process_escape_sequence( (void*) &arg); + } + } + *p++ = c; + } + + arg = *++argv; + if (arg) + *p++ = ' '; + cur_io->iov_len = p - (char*)cur_io->iov_base; + cur_io++; + if (!arg) + break; + } + + newline_ret: + if (nflag) { + cur_io->iov_base = (char*)"\n"; + cur_io->iov_len = 1; + cur_io++; + } + ret: + /* TODO: implement and use full_writev? */ + return writev(1, io, (cur_io - io)) >= 0; +} +#endif -- cgit v1.2.3